/* Temporary directories and temporary files with automatic cleanup.
- Copyright (C) 2001, 2003, 2006 Free Software Foundation, Inc.
+ Copyright (C) 2001, 2003, 2006-2007, 2009-2010 Free Software Foundation,
+ Inc.
Written by Bruno Haible <bruno@clisp.org>, 2006.
- This program is free software; you can redistribute it and/or modify
+ This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
- the Free Software Foundation; either version 2, or (at your option)
- any later version.
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
- along with this program; if not, write to the Free Software Foundation,
- Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */
+ along with this program. If not, see <http://www.gnu.org/licenses/>. */
#include <config.h>
#include "clean-temp.h"
#include <errno.h>
+#include <fcntl.h>
#include <limits.h>
#include <stdbool.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
+#if (defined _WIN32 || defined __WIN32__) && ! defined __CYGWIN__
+# define WIN32_LEAN_AND_MEAN /* avoid including junk */
+# include <windows.h>
+#endif
+
#include "error.h"
#include "fatal-signal.h"
#include "pathmax.h"
#include "tmpdir.h"
-#include "mkdtemp.h"
#include "xalloc.h"
-#include "xallocsa.h"
+#include "xmalloca.h"
+#include "gl_xlist.h"
#include "gl_linkedhash_list.h"
#include "gettext.h"
+#if GNULIB_FWRITEERROR
+# include "fwriteerror.h"
+#endif
+#if GNULIB_CLOSE_STREAM
+# include "close-stream.h"
+#endif
+#if GNULIB_FCNTL_SAFER
+# include "fcntl--.h"
+#endif
+#if GNULIB_FOPEN_SAFER
+# include "stdio--.h"
+#endif
#define _(str) gettext (str)
+/* GNU Hurd doesn't have PATH_MAX. */
+#ifndef PATH_MAX
+# ifdef MAXPATHLEN
+# define PATH_MAX MAXPATHLEN
+# else
+# define PATH_MAX 1024
+# endif
+#endif
+
#ifndef uintptr_t
# define uintptr_t unsigned long
#endif
+#if !GNULIB_FCNTL_SAFER
+/* The results of open() in this file are not used with fchdir,
+ therefore save some unnecessary work in fchdir.c. */
+# undef open
+# undef close
+#endif
+
/* The use of 'volatile' in the types below (and ISO C 99 section 5.1.2.3.(5))
ensure that while constructing or modifying the data structures, the field
static bool
string_equals (const void *x1, const void *x2)
{
- const char *s1 = x1;
- const char *s2 = x2;
+ const char *s1 = (const char *) x1;
+ const char *s2 = (const char *) x2;
return strcmp (s1, s2) == 0;
}
static size_t
string_hash (const void *x)
{
- const char *s = x;
+ const char *s = (const char *) x;
size_t h = 0;
for (; *s; s++)
if (fds != NULL)
{
- gl_list_iterator_t iter;
- const void *element;
-
- iter = gl_list_iterator (fds);
- while (gl_list_iterator_next (&iter, &element, NULL))
- {
- int fd = (int) (uintptr_t) element;
- close (fd);
- }
- gl_list_iterator_free (&iter);
+ gl_list_iterator_t iter;
+ const void *element;
+
+ iter = gl_list_iterator (fds);
+ while (gl_list_iterator_next (&iter, &element, NULL))
+ {
+ int fd = (int) (uintptr_t) element;
+ close (fd);
+ }
+ gl_list_iterator_free (&iter);
}
}
struct tempdir *dir = cleanup_list.tempdir_list[i];
if (dir != NULL)
- {
- gl_list_iterator_t iter;
- const void *element;
-
- /* First cleanup the files in the subdirectories. */
- iter = gl_list_iterator (dir->files);
- while (gl_list_iterator_next (&iter, &element, NULL))
- {
- const char *file = (const char *) element;
- unlink (file);
- }
- gl_list_iterator_free (&iter);
-
- /* Then cleanup the subdirectories. */
- iter = gl_list_iterator (dir->subdirs);
- while (gl_list_iterator_next (&iter, &element, NULL))
- {
- const char *subdir = (const char *) element;
- rmdir (subdir);
- }
- gl_list_iterator_free (&iter);
-
- /* Then cleanup the temporary directory itself. */
- rmdir (dir->dirname);
- }
+ {
+ gl_list_iterator_t iter;
+ const void *element;
+
+ /* First cleanup the files in the subdirectories. */
+ iter = gl_list_iterator (dir->files);
+ while (gl_list_iterator_next (&iter, &element, NULL))
+ {
+ const char *file = (const char *) element;
+ unlink (file);
+ }
+ gl_list_iterator_free (&iter);
+
+ /* Then cleanup the subdirectories. */
+ iter = gl_list_iterator (dir->subdirs);
+ while (gl_list_iterator_next (&iter, &element, NULL))
+ {
+ const char *subdir = (const char *) element;
+ rmdir (subdir);
+ }
+ gl_list_iterator_free (&iter);
+
+ /* Then cleanup the temporary directory itself. */
+ rmdir (dir->dirname);
+ }
}
}
is shown and NULL is returned. */
struct temp_dir *
create_temp_dir (const char *prefix, const char *parentdir,
- bool cleanup_verbose)
+ bool cleanup_verbose)
{
struct tempdir * volatile *tmpdirp = NULL;
struct tempdir *tmpdir;
size_t i;
- char *template;
+ char *xtemplate;
char *tmpdirname;
/* See whether it can take the slot of an earlier temporary directory
for (i = 0; i < cleanup_list.tempdir_count; i++)
if (cleanup_list.tempdir_list[i] == NULL)
{
- tmpdirp = &cleanup_list.tempdir_list[i];
- break;
+ tmpdirp = &cleanup_list.tempdir_list[i];
+ break;
}
if (tmpdirp == NULL)
{
/* See whether the array needs to be extended. */
if (cleanup_list.tempdir_count == cleanup_list.tempdir_allocated)
- {
- /* Note that we cannot use xrealloc(), because then the cleanup()
- function could access an already deallocated array. */
- struct tempdir * volatile *old_array = cleanup_list.tempdir_list;
- size_t old_allocated = cleanup_list.tempdir_allocated;
- size_t new_allocated = 2 * cleanup_list.tempdir_allocated + 1;
- struct tempdir * volatile *new_array =
- (struct tempdir * volatile *)
- xmalloc (new_allocated * sizeof (struct tempdir * volatile));
-
- if (old_allocated == 0)
- /* First use of this facility. Register the cleanup handler. */
- at_fatal_signal (&cleanup);
- else
- {
- /* Don't use memcpy() here, because memcpy takes non-volatile
- arguments and is therefore not guaranteed to complete all
- memory stores before the next statement. */
- size_t k;
-
- for (k = 0; k < old_allocated; k++)
- new_array[k] = old_array[k];
- }
-
- cleanup_list.tempdir_list = new_array;
- cleanup_list.tempdir_allocated = new_allocated;
-
- /* Now we can free the old array. */
- if (old_array != NULL)
- free ((struct tempdir **) old_array);
- }
+ {
+ /* Note that we cannot use xrealloc(), because then the cleanup()
+ function could access an already deallocated array. */
+ struct tempdir * volatile *old_array = cleanup_list.tempdir_list;
+ size_t old_allocated = cleanup_list.tempdir_allocated;
+ size_t new_allocated = 2 * cleanup_list.tempdir_allocated + 1;
+ struct tempdir * volatile *new_array =
+ XNMALLOC (new_allocated, struct tempdir * volatile);
+
+ if (old_allocated == 0)
+ /* First use of this facility. Register the cleanup handler. */
+ at_fatal_signal (&cleanup);
+ else
+ {
+ /* Don't use memcpy() here, because memcpy takes non-volatile
+ arguments and is therefore not guaranteed to complete all
+ memory stores before the next statement. */
+ size_t k;
+
+ for (k = 0; k < old_allocated; k++)
+ new_array[k] = old_array[k];
+ }
+
+ cleanup_list.tempdir_list = new_array;
+ cleanup_list.tempdir_allocated = new_allocated;
+
+ /* Now we can free the old array. */
+ if (old_array != NULL)
+ free ((struct tempdir **) old_array);
+ }
tmpdirp = &cleanup_list.tempdir_list[cleanup_list.tempdir_count];
/* Initialize *tmpdirp before incrementing tempdir_count, so that
- cleanup() will skip this entry before it is fully initialized. */
+ cleanup() will skip this entry before it is fully initialized. */
*tmpdirp = NULL;
cleanup_list.tempdir_count++;
}
/* Initialize a 'struct tempdir'. */
- tmpdir = (struct tempdir *) xmalloc (sizeof (struct tempdir));
+ tmpdir = XMALLOC (struct tempdir);
tmpdir->dirname = NULL;
tmpdir->cleanup_verbose = cleanup_verbose;
tmpdir->subdirs = gl_list_create_empty (GL_LINKEDHASH_LIST,
- string_equals, string_hash, false);
+ string_equals, string_hash, NULL,
+ false);
tmpdir->files = gl_list_create_empty (GL_LINKEDHASH_LIST,
- string_equals, string_hash, false);
+ string_equals, string_hash, NULL,
+ false);
/* Create the temporary directory. */
- template = (char *) xallocsa (PATH_MAX);
- if (path_search (template, PATH_MAX, parentdir, prefix, parentdir == NULL))
+ xtemplate = (char *) xmalloca (PATH_MAX);
+ if (path_search (xtemplate, PATH_MAX, parentdir, prefix, parentdir == NULL))
{
error (0, errno,
- _("cannot find a temporary directory, try setting $TMPDIR"));
+ _("cannot find a temporary directory, try setting $TMPDIR"));
goto quit;
}
block_fatal_signals ();
- tmpdirname = mkdtemp (template);
+ tmpdirname = mkdtemp (xtemplate);
if (tmpdirname != NULL)
{
tmpdir->dirname = tmpdirname;
if (tmpdirname == NULL)
{
error (0, errno,
- _("cannot create a temporary directory using template \"%s\""),
- template);
+ _("cannot create a temporary directory using template \"%s\""),
+ xtemplate);
goto quit;
}
/* Replace tmpdir->dirname with a copy that has indefinite extent.
block because then the cleanup handler would not remove the directory
if xstrdup fails. */
tmpdir->dirname = xstrdup (tmpdirname);
- freesa (template);
+ freea (xtemplate);
return (struct temp_dir *) tmpdir;
quit:
- freesa (template);
+ freea (xtemplate);
return NULL;
}
Should be called before the file ABSOLUTE_FILE_NAME is created. */
void
register_temp_file (struct temp_dir *dir,
- const char *absolute_file_name)
+ const char *absolute_file_name)
{
struct tempdir *tmpdir = (struct tempdir *)dir;
Should be called when the file ABSOLUTE_FILE_NAME could not be created. */
void
unregister_temp_file (struct temp_dir *dir,
- const char *absolute_file_name)
+ const char *absolute_file_name)
{
struct tempdir *tmpdir = (struct tempdir *)dir;
gl_list_t list = tmpdir->files;
Should be called before the subdirectory ABSOLUTE_DIR_NAME is created. */
void
register_temp_subdir (struct temp_dir *dir,
- const char *absolute_dir_name)
+ const char *absolute_dir_name)
{
struct tempdir *tmpdir = (struct tempdir *)dir;
created. */
void
unregister_temp_subdir (struct temp_dir *dir,
- const char *absolute_dir_name)
+ const char *absolute_dir_name)
{
struct tempdir *tmpdir = (struct tempdir *)dir;
gl_list_t list = tmpdir->subdirs;
}
}
-/* Remove a file, with optional error message. */
-static void
+/* Remove a file, with optional error message.
+ Return 0 upon success, or -1 if there was some problem. */
+static int
do_unlink (struct temp_dir *dir, const char *absolute_file_name)
{
if (unlink (absolute_file_name) < 0 && dir->cleanup_verbose
&& errno != ENOENT)
- error (0, errno, _("cannot remove temporary file %s"), absolute_file_name);
+ {
+ error (0, errno, _("cannot remove temporary file %s"), absolute_file_name);
+ return -1;
+ }
+ return 0;
}
-/* Remove a directory, with optional error message. */
-static void
+/* Remove a directory, with optional error message.
+ Return 0 upon success, or -1 if there was some problem. */
+static int
do_rmdir (struct temp_dir *dir, const char *absolute_dir_name)
{
if (rmdir (absolute_dir_name) < 0 && dir->cleanup_verbose
&& errno != ENOENT)
- error (0, errno,
- _("cannot remove temporary directory %s"), absolute_dir_name);
+ {
+ error (0, errno,
+ _("cannot remove temporary directory %s"), absolute_dir_name);
+ return -1;
+ }
+ return 0;
}
-/* Remove the given ABSOLUTE_FILE_NAME and unregister it. */
-void
+/* Remove the given ABSOLUTE_FILE_NAME and unregister it.
+ Return 0 upon success, or -1 if there was some problem. */
+int
cleanup_temp_file (struct temp_dir *dir,
- const char *absolute_file_name)
+ const char *absolute_file_name)
{
- do_unlink (dir, absolute_file_name);
+ int err;
+
+ err = do_unlink (dir, absolute_file_name);
unregister_temp_file (dir, absolute_file_name);
+
+ return err;
}
-/* Remove the given ABSOLUTE_DIR_NAME and unregister it. */
-void
+/* Remove the given ABSOLUTE_DIR_NAME and unregister it.
+ Return 0 upon success, or -1 if there was some problem. */
+int
cleanup_temp_subdir (struct temp_dir *dir,
- const char *absolute_dir_name)
+ const char *absolute_dir_name)
{
- do_rmdir (dir, absolute_dir_name);
+ int err;
+
+ err = do_rmdir (dir, absolute_dir_name);
unregister_temp_subdir (dir, absolute_dir_name);
+
+ return err;
}
-/* Remove all registered files and subdirectories inside DIR. */
-void
+/* Remove all registered files and subdirectories inside DIR.
+ Return 0 upon success, or -1 if there was some problem. */
+int
cleanup_temp_dir_contents (struct temp_dir *dir)
{
struct tempdir *tmpdir = (struct tempdir *)dir;
+ int err = 0;
gl_list_t list;
gl_list_iterator_t iter;
const void *element;
{
char *file = (char *) element;
- do_unlink (dir, file);
+ err |= do_unlink (dir, file);
gl_list_remove_node (list, node);
/* Now only we can free file. */
free (file);
{
char *subdir = (char *) element;
- do_rmdir (dir, subdir);
+ err |= do_rmdir (dir, subdir);
gl_list_remove_node (list, node);
/* Now only we can free subdir. */
free (subdir);
}
gl_list_iterator_free (&iter);
+
+ return err;
}
/* Remove all registered files and subdirectories inside DIR and DIR itself.
- DIR cannot be used any more after this call. */
-void
+ DIR cannot be used any more after this call.
+ Return 0 upon success, or -1 if there was some problem. */
+int
cleanup_temp_dir (struct temp_dir *dir)
{
struct tempdir *tmpdir = (struct tempdir *)dir;
+ int err = 0;
size_t i;
- cleanup_temp_dir_contents (dir);
- do_rmdir (dir, tmpdir->dirname);
+ err |= cleanup_temp_dir_contents (dir);
+ err |= do_rmdir (dir, tmpdir->dirname);
for (i = 0; i < cleanup_list.tempdir_count; i++)
if (cleanup_list.tempdir_list[i] == tmpdir)
{
- /* Remove cleanup_list.tempdir_list[i]. */
- if (i + 1 == cleanup_list.tempdir_count)
- {
- while (i > 0 && cleanup_list.tempdir_list[i - 1] == NULL)
- i--;
- cleanup_list.tempdir_count = i;
- }
- else
- cleanup_list.tempdir_list[i] = NULL;
- /* Now only we can free the tmpdir->dirname and tmpdir itself. */
- free (tmpdir->dirname);
- free (tmpdir);
- return;
+ /* Remove cleanup_list.tempdir_list[i]. */
+ if (i + 1 == cleanup_list.tempdir_count)
+ {
+ while (i > 0 && cleanup_list.tempdir_list[i - 1] == NULL)
+ i--;
+ cleanup_list.tempdir_count = i;
+ }
+ else
+ cleanup_list.tempdir_list[i] = NULL;
+ /* Now only we can free the tmpdir->dirname and tmpdir itself. */
+ free (tmpdir->dirname);
+ free (tmpdir);
+ return err;
}
/* The user passed an invalid DIR argument. */
}
+#if (defined _WIN32 || defined __WIN32__) && ! defined __CYGWIN__
+
+/* On Windows, opening a file with _O_TEMPORARY has the effect of passing
+ the FILE_FLAG_DELETE_ON_CLOSE flag to CreateFile(), which has the effect
+ of deleting the file when it is closed - even when the program crashes.
+ But (according to the Cygwin sources) it works only on Windows NT or newer.
+ So we cache the info whether we are running on Windows NT or newer. */
+
+static bool
+supports_delete_on_close ()
+{
+ static int known; /* 1 = yes, -1 = no, 0 = unknown */
+ if (!known)
+ {
+ OSVERSIONINFO v;
+
+ if (GetVersionEx (&v))
+ known = (v.dwPlatformId == VER_PLATFORM_WIN32_NT ? 1 : -1);
+ else
+ known = -1;
+ }
+ return (known > 0);
+}
+
+#endif
+
+
/* Register a file descriptor to be closed. */
static void
register_fd (int fd)
{
if (descriptors == NULL)
- descriptors = gl_list_create_empty (GL_LINKEDHASH_LIST, NULL, NULL, false);
+ descriptors = gl_list_create_empty (GL_LINKEDHASH_LIST, NULL, NULL, NULL,
+ false);
gl_list_add_first (descriptors, (void *) (uintptr_t) fd);
}
int saved_errno;
block_fatal_signals ();
- fd = open (file_name, flags, mode);
+ /* Note: 'open' here is actually open() or open_safer(). */
+#if (defined _WIN32 || defined __WIN32__) && ! defined __CYGWIN__
+ /* Use _O_TEMPORARY when possible, to increase the chances that the
+ temporary file is removed when the process crashes. */
+ if (supports_delete_on_close ())
+ fd = open (file_name, flags | _O_TEMPORARY, mode);
+ else
+#endif
+ fd = open (file_name, flags, mode);
saved_errno = errno;
if (fd >= 0)
register_fd (fd);
int saved_errno;
block_fatal_signals ();
- fp = fopen (file_name, mode);
- saved_errno = errno;
+ /* Note: 'fopen' here is actually fopen() or fopen_safer(). */
+#if (defined _WIN32 || defined __WIN32__) && ! defined __CYGWIN__
+ /* Use _O_TEMPORARY when possible, to increase the chances that the
+ temporary file is removed when the process crashes. */
+ if (supports_delete_on_close ())
+ {
+ size_t mode_len = strlen (mode);
+ char *augmented_mode = (char *) xmalloca (mode_len + 2);
+ memcpy (augmented_mode, mode, mode_len);
+ memcpy (augmented_mode + mode_len, "D", 2);
+
+ fp = fopen (file_name, augmented_mode);
+ saved_errno = errno;
+
+ freea (augmented_mode);
+ }
+ else
+#endif
+ {
+ fp = fopen (file_name, mode);
+ saved_errno = errno;
+ }
if (fp != NULL)
{
/* It is sufficient to register fileno (fp) instead of the entire fp,
- because at cleanup time there is no need to do an fflush (fp); a
- close (fileno (fp)) will be enough. */
+ because at cleanup time there is no need to do an fflush (fp); a
+ close (fileno (fp)) will be enough. */
int fd = fileno (fp);
if (!(fd >= 0))
- abort ();
+ abort ();
register_fd (fd);
}
unblock_fatal_signals ();
if (fd >= 0)
{
/* No blocking of signals is needed here, since a double close of a
- file descriptor is harmless. */
+ file descriptor is harmless. */
int result = close (fd);
int saved_errno = errno;
/* No race condition here: we assume a single-threaded program, hence
- fd cannot be re-opened here. */
+ fd cannot be re-opened here. */
unregister_fd (fd);
return result;
}
#endif
+
+#if GNULIB_CLOSE_STREAM
+/* Like close_stream.
+ Unregisters the previously registered file descriptor. */
+int
+close_stream_temp (FILE *fp)
+{
+ int fd = fileno (fp);
+ /* No blocking of signals is needed here, since a double close of a
+ file descriptor is harmless. */
+ int result = close_stream (fp);
+ int saved_errno = errno;
+
+ /* No race condition here: we assume a single-threaded program, hence
+ fd cannot be re-opened here. */
+
+ unregister_fd (fd);
+
+ errno = saved_errno;
+ return result;
+}
+#endif