diff options
Diffstat (limited to 'lib/clean-temp.c')
-rw-r--r-- | lib/clean-temp.c | 393 |
1 files changed, 19 insertions, 374 deletions
diff --git a/lib/clean-temp.c b/lib/clean-temp.c index 268aa48208..4091d93273 100644 --- a/lib/clean-temp.c +++ b/lib/clean-temp.c @@ -16,7 +16,6 @@ You should have received a copy of the GNU General Public License along with this program. If not, see <https://www.gnu.org/licenses/>. */ - #include <config.h> /* Specification. */ @@ -24,9 +23,9 @@ #include <errno.h> #include <fcntl.h> -#include <limits.h> +#include <signal.h> #include <stdbool.h> -#include <stdint.h> +#include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> @@ -36,6 +35,8 @@ # include <windows.h> #endif +#include "clean-temp-simple.h" +#include "clean-temp-private.h" #include "error.h" #include "fatal-signal.h" #include "asyncsafe-spin.h" @@ -82,189 +83,14 @@ #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 - values are written to memory in the order of the C statements. So the - signal handler can rely on these field values to be up to date. */ - - -/* Lock that protects the file_cleanup_list from concurrent modification in - different threads. */ -gl_lock_define_initialized (static, file_cleanup_list_lock) - -/* List of all temporary files without temporary directories. */ -static gl_list_t /* <char *> */ volatile file_cleanup_list; - - -/* Registry for a single temporary directory. - 'struct temp_dir' from the public header file overlaps with this. */ -struct tempdir -{ - /* The absolute pathname of the directory. */ - char * volatile dirname; - /* Whether errors during explicit cleanup are reported to standard error. */ - bool cleanup_verbose; - /* Absolute pathnames of subdirectories. */ - gl_list_t /* <char *> */ volatile subdirs; - /* Absolute pathnames of files. */ - gl_list_t /* <char *> */ volatile files; -}; - /* Lock that protects the dir_cleanup_list from concurrent modification in different threads. */ gl_lock_define_initialized (static, dir_cleanup_list_lock) -/* List of all temporary directories. */ -static struct -{ - struct tempdir * volatile * volatile tempdir_list; - size_t volatile tempdir_count; - size_t tempdir_allocated; -} dir_cleanup_list /* = { NULL, 0, 0 } */; - - -/* A file descriptor to be closed. - In multithreaded programs, it is forbidden to close the same fd twice, - because you never know what unrelated open() calls are being executed in - other threads. So, the 'close (fd)' must be guarded by a once-only guard. */ -struct closeable_fd -{ - /* The file descriptor to close. */ - int volatile fd; - /* Set to true when it has been closed. */ - bool volatile closed; - /* Lock that protects the fd from being closed twice. */ - asyncsafe_spinlock_t lock; - /* Tells whether this list element has been done and can be freed. */ - bool volatile done; -}; - /* Lock that protects the descriptors list from concurrent modification in different threads. */ gl_lock_define_initialized (static, descriptors_lock) -/* List of all open file descriptors to temporary files. */ -static gl_list_t /* <closeable_fd *> */ volatile descriptors; - - -/* For the subdirs and for the files, we use a gl_list_t of type LINKEDHASH. - Why? We need a data structure that - - 1) Can contain an arbitrary number of 'char *' values. The strings - are compared via strcmp, not pointer comparison. - 2) Has insertion and deletion operations that are fast: ideally O(1), - or possibly O(log n). This is important for GNU sort, which may - create a large number of temporary files. - 3) Allows iteration through all elements from within a signal handler. - 4) May or may not allow duplicates. It doesn't matter here, since - any file or subdir can only be removed once. - - Criterion 1) would allow any gl_list_t or gl_oset_t implementation. - - Criterion 2) leaves only GL_LINKEDHASH_LIST, GL_TREEHASH_LIST, or - GL_TREE_OSET. - - Criterion 3) puts at disadvantage GL_TREEHASH_LIST and GL_TREE_OSET. - Namely, iteration through the elements of a binary tree requires access - to many ->left, ->right, ->parent pointers. However, the rebalancing - code for insertion and deletion in an AVL or red-black tree is so - complicated that we cannot assume that >left, ->right, ->parent pointers - are in a consistent state throughout these operations. Therefore, to - avoid a crash in the signal handler, all destructive operations to the - lists would have to be protected by a - block_fatal_signals (); - ... - unblock_fatal_signals (); - pair. Which causes extra system calls. - - Criterion 3) would also discourage GL_ARRAY_LIST and GL_CARRAY_LIST, - if they were not already excluded. Namely, these implementations use - xrealloc(), leaving a time window in which in the list->elements pointer - points to already deallocated memory. To avoid a crash in the signal - handler at such a moment, all destructive operations would have to - protected by block/unblock_fatal_signals (), in this case too. - - A list of type GL_LINKEDHASH_LIST without duplicates fulfills all - requirements: - 2) Insertion and deletion are O(1) on average. - 3) The gl_list_iterator, gl_list_iterator_next implementations do - not trigger memory allocations, nor other system calls, and are - therefore safe to be called from a signal handler. - Furthermore, since SIGNAL_SAFE_LIST is defined, the implementation - of the destructive functions ensures that the list structure is - safe to be traversed at any moment, even when interrupted by an - asynchronous signal. - */ - -/* String equality and hash code functions used by the lists. */ - -static bool -string_equals (const void *x1, const void *x2) -{ - const char *s1 = (const char *) x1; - const char *s2 = (const char *) x2; - return strcmp (s1, s2) == 0; -} - -#define SIZE_BITS (sizeof (size_t) * CHAR_BIT) - -/* A hash function for NUL-terminated char* strings using - the method described by Bruno Haible. - See https://www.haible.de/bruno/hashfunc.html. */ -static size_t -string_hash (const void *x) -{ - const char *s = (const char *) x; - size_t h = 0; - - for (; *s; s++) - h = *s + ((h << 9) | (h >> (SIZE_BITS - 9))); - - return h; -} - - -/* The set of fatal signal handlers. - Cached here because we are not allowed to call get_fatal_signal_set () - from a signal handler. */ -static const sigset_t *fatal_signal_set /* = NULL */; - -static void -init_fatal_signal_set (void) -{ - if (fatal_signal_set == NULL) - fatal_signal_set = get_fatal_signal_set (); -} - - -/* Close a file descriptor. - Avoids race conditions with normal thread code or signal-handler code that - might want to close the same file descriptor. */ -static _GL_ASYNC_SAFE int -asyncsafe_close (struct closeable_fd *element) -{ - sigset_t saved_mask; - int ret; - int saved_errno; - - asyncsafe_spin_lock (&element->lock, fatal_signal_set, &saved_mask); - if (!element->closed) - { - ret = close (element->fd); - saved_errno = errno; - element->closed = true; - } - else - { - ret = 0; - saved_errno = 0; - } - asyncsafe_spin_unlock (&element->lock, &saved_mask); - element->done = true; - - errno = saved_errno; - return ret; -} /* Close a file descriptor and the stream that contains it. Avoids race conditions with signal-handler code that might want to close the @@ -283,7 +109,7 @@ asyncsafe_fclose_variant (struct closeable_fd *element, FILE *fp, int ret; int saved_errno; - asyncsafe_spin_lock (&element->lock, fatal_signal_set, &saved_mask); + asyncsafe_spin_lock (&element->lock, get_fatal_signal_set (), &saved_mask); if (!element->closed) { ret = fclose_variant (fp); /* invokes close (element->fd) */ @@ -302,187 +128,6 @@ asyncsafe_fclose_variant (struct closeable_fd *element, FILE *fp, return ret; } -/* The signal handler. It gets called asynchronously. */ -static _GL_ASYNC_SAFE void -cleanup_action (int sig _GL_UNUSED) -{ - size_t i; - - /* First close all file descriptors to temporary files. */ - { - gl_list_t fds = descriptors; - - if (fds != NULL) - { - gl_list_iterator_t iter; - const void *element; - - iter = gl_list_iterator (fds); - while (gl_list_iterator_next (&iter, &element, NULL)) - { - asyncsafe_close ((struct closeable_fd *) element); - } - gl_list_iterator_free (&iter); - } - } - - { - gl_list_t files = file_cleanup_list; - - if (files != NULL) - { - gl_list_iterator_t iter; - const void *element; - - iter = gl_list_iterator (files); - while (gl_list_iterator_next (&iter, &element, NULL)) - { - const char *file = (const char *) element; - unlink (file); - } - gl_list_iterator_free (&iter); - } - } - - for (i = 0; i < dir_cleanup_list.tempdir_count; i++) - { - struct tempdir *dir = 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); - } - } -} - - -/* Initializes this facility. */ -static void -do_init_clean_temp (void) -{ - /* Initialize the data used by the cleanup handler. */ - init_fatal_signal_set (); - /* Register the cleanup handler. */ - if (at_fatal_signal (&cleanup_action) < 0) - xalloc_die (); -} - -/* Ensure that do_init_clean_temp is called once only. */ -gl_once_define(static, clean_temp_once) - -/* Initializes this facility upon first use. */ -static void -init_clean_temp (void) -{ - gl_once (clean_temp_once, do_init_clean_temp); -} - - -/* ============= Temporary files without temporary directories ============= */ - -/* Register the given ABSOLUTE_FILE_NAME as being a file that needs to be - removed. - Should be called before the file ABSOLUTE_FILE_NAME is created. */ -void -register_temporary_file (const char *absolute_file_name) -{ - bool mt = gl_multithreaded (); - - if (mt) gl_lock_lock (file_cleanup_list_lock); - - /* Make sure that this facility and the file_cleanup_list are initialized. */ - if (file_cleanup_list == NULL) - { - init_clean_temp (); - file_cleanup_list = - gl_list_create_empty (GL_LINKEDHASH_LIST, - string_equals, string_hash, NULL, false); - } - - /* Add absolute_file_name to file_cleanup_list, without duplicates. */ - if (gl_list_search (file_cleanup_list, absolute_file_name) == NULL) - gl_list_add_first (file_cleanup_list, xstrdup (absolute_file_name)); - - if (mt) gl_lock_unlock (file_cleanup_list_lock); -} - -/* Unregister the given ABSOLUTE_FILE_NAME as being a file that needs to be - removed. - Should be called when the file ABSOLUTE_FILE_NAME could not be created. */ -void -unregister_temporary_file (const char *absolute_file_name) -{ - bool mt = gl_multithreaded (); - - if (mt) gl_lock_lock (file_cleanup_list_lock); - - gl_list_t list = file_cleanup_list; - if (list != NULL) - { - gl_list_node_t node = gl_list_search (list, absolute_file_name); - if (node != NULL) - { - char *old_string = (char *) gl_list_node_value (list, node); - - gl_list_remove_node (list, node); - free (old_string); - } - } - - if (mt) gl_lock_unlock (file_cleanup_list_lock); -} - -/* Remove a file, with optional error message. - Return 0 upon success, or -1 if there was some problem. */ -static int -do_unlink (const char *absolute_file_name, bool cleanup_verbose) -{ - if (unlink (absolute_file_name) < 0 && cleanup_verbose - && errno != ENOENT) - { - error (0, errno, - _("cannot remove temporary file %s"), absolute_file_name); - return -1; - } - return 0; -} - -/* Remove the given ABSOLUTE_FILE_NAME and unregister it. - CLEANUP_VERBOSE determines whether errors are reported to standard error. - Return 0 upon success, or -1 if there was some problem. */ -int -cleanup_temporary_file (const char *absolute_file_name, bool cleanup_verbose) -{ - int err; - - err = do_unlink (absolute_file_name, cleanup_verbose); - unregister_temporary_file (absolute_file_name); - - return err; -} - /* ========= Temporary directories and temporary files inside them ========= */ @@ -533,7 +178,7 @@ create_temp_dir (const char *prefix, const char *parentdir, if (old_allocated == 0) { /* First use of this facility. */ - init_clean_temp (); + clean_temp_init (); } else { @@ -572,12 +217,14 @@ create_temp_dir (const char *prefix, const char *parentdir, 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, NULL, - false); - tmpdir->files = gl_list_create_empty (GL_LINKEDHASH_LIST, - string_equals, string_hash, NULL, - false); + tmpdir->subdirs = + gl_list_create_empty (GL_LINKEDHASH_LIST, + clean_temp_string_equals, clean_temp_string_hash, + NULL, false); + tmpdir->files = + gl_list_create_empty (GL_LINKEDHASH_LIST, + clean_temp_string_equals, clean_temp_string_hash, + NULL, false); /* Create the temporary directory. */ xtemplate = (char *) xmalloca (PATH_MAX); @@ -734,7 +381,7 @@ cleanup_temp_file (struct temp_dir *dir, { int err; - err = do_unlink (absolute_file_name, dir->cleanup_verbose); + err = clean_temp_unlink (absolute_file_name, dir->cleanup_verbose); unregister_temp_file (dir, absolute_file_name); return err; @@ -774,7 +421,7 @@ cleanup_temp_dir_contents (struct temp_dir *dir) { char *file = (char *) element; - err |= do_unlink (file, dir->cleanup_verbose); + err |= clean_temp_unlink (file, dir->cleanup_verbose); gl_list_remove_node (list, node); /* Now only we can free file. */ free (file); @@ -1017,7 +664,7 @@ gen_register_open_temp (char *file_name_tmpl, int suffixlen, int saved_errno = errno; if (fd >= 0) { - init_clean_temp (); + clean_temp_init (); register_fd (fd); register_temporary_file (file_name_tmpl); } @@ -1037,7 +684,7 @@ close_temp (int fd) if (fd < 0) return close (fd); - init_fatal_signal_set (); + clean_temp_init_asyncsafe_close (); int result = 0; int saved_errno = 0; @@ -1066,7 +713,7 @@ close_temp (int fd) if (element->fd == fd) { found = true; - result = asyncsafe_close (element); + result = clean_temp_asyncsafe_close (element); saved_errno = errno; } @@ -1101,8 +748,6 @@ fclose_variant_temp (FILE *fp, int (*fclose_variant) (FILE *)) { int fd = fileno (fp); - init_fatal_signal_set (); - int result = 0; int saved_errno = 0; |