X-Git-Url: https://diplodocus.org/git/nmh/blobdiff_plain/2f689a1cb907a5de04e6d39ffd217a69af3216c7..e6c91771:/sbr/m_mktemp.c?ds=sidebyside diff --git a/sbr/m_mktemp.c b/sbr/m_mktemp.c index 22290363..9a28b48a 100644 --- a/sbr/m_mktemp.c +++ b/sbr/m_mktemp.c @@ -1,17 +1,18 @@ -/* - * m_mktemp.c -- Construct a temporary file. - * - * $Id$ +/* 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 * complete copyright information. */ -#include #include +#include +#include +#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). @@ -29,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. @@ -61,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; @@ -73,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); @@ -93,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() @@ -109,6 +113,7 @@ m_mktemp2 ( { static char buffer[BUFSIZ]; char *cp; + int n; if (dir_in == NULL) { if (pfx_in == NULL) { @@ -122,16 +127,112 @@ m_mktemp2 ( /* No directory component */ return m_mktemp(pfx_in, fd_ret, fp_ret); } - int n = (int)(cp-dir_in-1); /* Length of dir component */ + n = (int)(cp-dir_in); /* Length of dir component */ snprintf(buffer, sizeof(buffer), "%.*s%s", n, dir_in, pfx_in); return m_mktemp(buffer, fd_ret, fp_ret); } -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 + /* Ignore envvars if we are setuid */ if ((getuid()==geteuid()) && (getgid()==getegid())) { char *tmpdir = NULL; tmpdir = getenv("MHTMPDIR"); @@ -139,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, 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(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); + } +}