]> diplodocus.org Git - nmh/blobdiff - sbr/m_mktemp.c
new.c: Order two return statements to match comment.
[nmh] / sbr / m_mktemp.c
index ca4ab29cabdd7047c706d0b46420798d5477b50f..d4b8fde05154d28b954463c115697cfb62203339 100644 (file)
@@ -1,5 +1,4 @@
-/*
- * m_mktemp.c -- Construct a temporary file.
+/* m_mktemp.c -- Construct a temporary file.
  *
  * This code is Copyright (c) 2010, by the authors of nmh.  See the
  * COPYRIGHT file in the root directory of the nmh distribution for
@@ -7,8 +6,13 @@
  */
 
 #include <h/mh.h>
+#include <h/utils.h>
+#include <h/signals.h>
+#include "m_maildir.h"
+#include "m_mktemp.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).
@@ -26,10 +30,9 @@ static char *get_temp_dir();
  *
  *  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.
@@ -58,10 +61,14 @@ m_mktemp (
     }
 
     fd = mkstemp(tmpfil);
+
     if (fd < 0) {
         umask(oldmode);
         return NULL;
     }
+
+    register_for_removal(tmpfil);
+
     if (fd_ret != NULL) {
         *fd_ret = fd;
         keep_open = 1;
@@ -70,7 +77,7 @@ m_mktemp (
         FILE *fp = fdopen(fd, "w+");
         if (fp == NULL) {
             int olderr = errno;
-            unlink(tmpfil);
+            (void) m_unlink(tmpfil);
             close(fd);
             errno = olderr;
             umask(oldmode);
@@ -90,7 +97,7 @@ m_mktemp (
  * 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()
@@ -126,8 +133,104 @@ m_mktemp2 (
 }
 
 
-static char *
-get_temp_dir()
+/*
+ * 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(void)
 {
     /* Ignore envvars if we are setuid */
     if ((getuid()==geteuid()) && (getgid()==getegid())) {
@@ -137,9 +240,128 @@ get_temp_dir()
 
         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, mh_xstrdup(pathname));
+}
+
+/*
+ * 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(void) {
+    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);
+    }
+}