* complete copyright information.
*/
-#include <errno.h>
#include <h/mh.h>
+#include <h/utils.h>
+#include <h/signals.h>
+
+static void register_for_removal(const char *);
-static char *get_temp_dir();
/* Create a temporary file. If pfx_in is null, the temporary file
* will be created in the temporary directory (more on that later).
*
* When pfx_in is null, the temporary directory is determined as
* follows, in order:
- *
+ *
* MHTMPDIR envvar
* TMPDIR envvar
- * TMP envvar
* User's mail directory.
*
* NOTE: One will probably use m_mktemp2() instead of this function.
}
fd = mkstemp(tmpfil);
+
if (fd < 0) {
umask(oldmode);
return NULL;
}
+
+ register_for_removal(tmpfil);
+
if (fd_ret != NULL) {
*fd_ret = fd;
keep_open = 1;
FILE *fp = fdopen(fd, "w+");
if (fp == NULL) {
int olderr = errno;
- unlink(tmpfil);
+ (void) m_unlink(tmpfil);
close(fd);
errno = olderr;
umask(oldmode);
* by created based on a given pathname. Although m_mktemp() technically
* supports this, this version is when the directory is defined by
* a separate variable from the prefix, eliminating the caller from having
- * to do string manipulation to generate the desired. pathname prefix.
+ * to do string manipulation to generate the desired pathname prefix.
*
* The pfx_in parameter specifies a basename prefix for the file. If dir_in
* is NULL, then the defined temporary directory (see comments to m_mktemp()
}
-static char *
+/*
+ * This version supports a suffix for the temp filename.
+ * It has two other differences from m_mktemp() and m_mktemp2():
+ * 1) It does not support specification of a directory for the temp
+ * file. Instead it always uses get_temp_dir().
+ * 2) It returns a dynamically allocated string. The caller is
+ * responsible for freeing the allocated memory.
+ */
+char *
+m_mktemps(
+ const char *pfx_in, /* Basename prefix for temp file. */
+ const char *suffix, /* Suffix, including any '.', for temp file. */
+ int *fd_ret, /* (return,optional) File descriptor to temp file. */
+ FILE **fp_ret /* (return,optional) FILE pointer to temp file. */
+)
+{
+ char *tmpfil;
+ int fd = -1;
+ int keep_open = 0;
+ mode_t oldmode = umask(077);
+
+ if (suffix == NULL) {
+ if ((tmpfil = m_mktemp2(NULL, pfx_in, fd_ret, fp_ret))) {
+ return mh_xstrdup(tmpfil);
+ }
+ return NULL;
+ }
+
+#if HAVE_MKSTEMPS
+ if (pfx_in == NULL) {
+ tmpfil = concat(get_temp_dir(), "/nmhXXXXXX", suffix, NULL);
+ } else {
+ tmpfil = concat(get_temp_dir(), "/", pfx_in, "XXXXXX", suffix, NULL);
+ }
+
+ fd = mkstemps(tmpfil, (int) strlen(suffix));
+#else /* ! HAVE_MKSTEMPS */
+ /* NetBSD, Solaris 10 */
+
+ if (pfx_in == NULL) {
+ tmpfil = concat(get_temp_dir(), "/nmhXXXXXX", NULL);
+ } else {
+ tmpfil = concat(get_temp_dir(), "/", pfx_in, "XXXXXX", NULL);
+ }
+
+ fd = mkstemp(tmpfil);
+ {
+ char *oldfilename = tmpfil;
+ tmpfil = concat(oldfilename, suffix, NULL);
+
+ if (rename(oldfilename, tmpfil) != 0) {
+ (void) unlink(oldfilename);
+ free(oldfilename);
+ free(tmpfil);
+ return NULL;
+ }
+
+ free(oldfilename);
+ }
+#endif /* ! HAVE_MKSTEMPS */
+
+ if (fd < 0) {
+ umask(oldmode);
+ free(tmpfil);
+ return NULL;
+ }
+
+ register_for_removal(tmpfil);
+
+ if (fd_ret != NULL) {
+ *fd_ret = fd;
+ keep_open = 1;
+ }
+ if (fp_ret != NULL) {
+ FILE *fp = fdopen(fd, "w+");
+ if (fp == NULL) {
+ int olderr = errno;
+ (void) m_unlink(tmpfil);
+ close(fd);
+ errno = olderr;
+ umask(oldmode);
+ free(tmpfil);
+ return NULL;
+ }
+ *fp_ret = fp;
+ keep_open = 1;
+ }
+ if (!keep_open) {
+ close(fd);
+ }
+ umask(oldmode);
+
+ return tmpfil;
+}
+
+
+char *
get_temp_dir()
{
- // Ignore envvars if we are setuid
+ /* Ignore envvars if we are setuid */
if ((getuid()==geteuid()) && (getgid()==getegid())) {
char *tmpdir = NULL;
tmpdir = getenv("MHTMPDIR");
tmpdir = getenv("TMPDIR");
if (tmpdir != NULL && *tmpdir != '\0') return tmpdir;
-
- tmpdir = getenv("TMP");
- if (tmpdir != NULL && *tmpdir != '\0') return tmpdir;
}
return m_maildir("");
}
+
+
+/*
+ * Array of files (full pathnames) to remove on process exit.
+ * Instead of removing slots from the array, just set to NULL
+ * to indicate that the file should no longer be removed.
+ */
+static svector_t exit_filelist = NULL;
+
+/*
+ * Register a file for removal at program termination.
+ */
+static void
+register_for_removal(const char *pathname) {
+ if (exit_filelist == NULL) exit_filelist = svector_create(20);
+ (void) svector_push_back(exit_filelist, add(pathname, NULL));
+}
+
+/*
+ * Unregister all files that were registered to be removed at program
+ * termination. When called with remove_files of 0, this function is
+ * intended for use by one of the programs after a fork(3) without a
+ * subsequent call to one of the exec(3) functions or _exit(3). When
+ * called with non-0 remove_files argument, it is intended for use by
+ * an atexit() function.
+ *
+ * Right after a fork() and before calling exec() or _exit(), if the
+ * child catches one of the appropriate signals, it will remove
+ * all the registered temporary files. Some of those may be needed by
+ * the parent. Some nmh programs ignore or block some of the signals
+ * in the child right after fork(). For the other programs, rather
+ * than complicate things by preventing that, just take the chance
+ * that it might happen. It is harmless to attempt to unlink a
+ * temporary file twice, assuming another one wasn't created too
+ * quickly created with the same name.
+ */
+void
+unregister_for_removal(int remove_files) {
+ if (exit_filelist) {
+ size_t i;
+
+ for (i = 0; i < svector_size(exit_filelist); ++i) {
+ char *filename = svector_at(exit_filelist, i);
+
+ if (filename) {
+ if (remove_files) (void) unlink(filename);
+ free(filename);
+ }
+ }
+
+ svector_free(exit_filelist);
+ exit_filelist = NULL;
+ }
+}
+
+/*
+ * If the file was registered for removal, deregister it. In
+ * any case, unlink it.
+ */
+int
+m_unlink(const char *pathname) {
+ if (exit_filelist) {
+ char **slot = svector_find(exit_filelist, pathname);
+
+ if (slot && *slot) {
+ free(*slot);
+ *slot = NULL;
+ }
+ }
+
+ return unlink(pathname);
+ /* errno should be set to ENOENT if file was not found */
+}
+
+/*
+ * Remove all registered temporary files.
+ */
+void
+remove_registered_files_atexit() {
+ unregister_for_removal(1);
+}
+
+/*
+ * Remove all registered temporary files. Suitable for use as a
+ * signal handler. If the signal is one of the usual process
+ * termination signals, calls exit(). Otherwise, disables itself
+ * by restoring the default action and then re-raises the signal,
+ * in case the use was expecting a core dump.
+ */
+void
+remove_registered_files(int sig) {
+ struct sigaction act;
+
+ /*
+ * Ignore this signal for the duration. And we either exit() or
+ * disable this signal handler below, so don't restore this handler.
+ */
+ act.sa_handler = SIG_IGN;
+ (void) sigemptyset(&act.sa_mask);
+ act.sa_flags = 0;
+ (void) sigaction(sig, &act, 0);
+
+ if (sig == SIGHUP || sig == SIGINT || sig == SIGQUIT || sig == SIGTERM) {
+ /*
+ * We don't need to call remove_registered_files_atexit() if
+ * it was registered with atexit(), but let's not assume that.
+ * It's harmless to call it twice.
+ */
+ remove_registered_files_atexit();
+
+ exit(1);
+ } else {
+ remove_registered_files_atexit();
+
+ act.sa_handler = SIG_DFL;
+ (void) sigemptyset(&act.sa_mask);
+ act.sa_flags = 0;
+ (void) sigaction(sig, &act, 0);
+
+ (void) raise(sig);
+ }
+}