>From 83c222f7a65279e4e659a4caf8b85d34f91d5eeb Mon Sep 17 00:00:00 2001 From: Bruno Haible Date: Mon, 22 Mar 2021 22:48:23 +0100 Subject: [PATCH 1/3] clean-temp-simple: New module. * lib/clean-temp-simple.h: New file, based on lib/clean-temp.h. * lib/clean-temp-private.h: New file, based on lib/clean-temp.c. * lib/clean-temp-simple.c: New file, based on lib/clean-temp.c. * lib/clean-temp.h: Include clean-temp-simple.h. (register_temporary_file, unregister_temporary_file, cleanup_temporary_file): Remove declarations. * lib/clean-temp.c: Don't include , . Include , , clean-temp-simple.h, clean-temp-private.h. (file_cleanup_list_lock, file_cleanup_list): Moved to clean-temp-simple.c. (struct tempdir, dir_cleanup_list, struct closeable_fd, descriptors): Moved to clean-temp-private.h. (string_equals, SIZE_BITS, string_hash, fatal_signal_set, init_fatal_signal_set, asyncsafe_close): Moved to clean-temp-simple.c. (asyncsafe_fclose_variant): Use get_fatal_signal_set() here. (cleanup_action, do_init_clean_temp, clean_temp_once, init_clean_temp, register_temporary_file, unregister_temporary_file, do_unlink, cleanup_temporary_file): Moved to clean-temp-simple.c. (create_temp_dir, cleanup_temp_file, cleanup_temp_dir_contents, gen_register_open_temp, close_temp): Update for changed function names. (fclose_variant_temp): Don't call init_fatal_signal_set(). * modules/clean-temp-simple: New file, based on modules/clean-temp. * modules/clean-temp (Depends-on): Add clean-temp-simple, list. Remove stdint. (configure.ac): Don't define SIGNAL_SAFE_LIST here. --- ChangeLog | 29 ++++ lib/clean-temp-private.h | 82 ++++++++++ lib/clean-temp-simple.c | 367 +++++++++++++++++++++++++++++++++++++++++++ lib/clean-temp-simple.h | 51 ++++++ lib/clean-temp.c | 393 +++------------------------------------------- lib/clean-temp.h | 25 +-- modules/clean-temp | 6 +- modules/clean-temp-simple | 42 +++++ 8 files changed, 599 insertions(+), 396 deletions(-) create mode 100644 lib/clean-temp-private.h create mode 100644 lib/clean-temp-simple.c create mode 100644 lib/clean-temp-simple.h create mode 100644 modules/clean-temp-simple diff --git a/ChangeLog b/ChangeLog index db793f8..d16c61e 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,5 +1,34 @@ 2021-03-22 Bruno Haible + clean-temp-simple: New module. + * lib/clean-temp-simple.h: New file, based on lib/clean-temp.h. + * lib/clean-temp-private.h: New file, based on lib/clean-temp.c. + * lib/clean-temp-simple.c: New file, based on lib/clean-temp.c. + * lib/clean-temp.h: Include clean-temp-simple.h. + (register_temporary_file, unregister_temporary_file, + cleanup_temporary_file): Remove declarations. + * lib/clean-temp.c: Don't include , . Include + , , clean-temp-simple.h, clean-temp-private.h. + (file_cleanup_list_lock, file_cleanup_list): Moved to + clean-temp-simple.c. + (struct tempdir, dir_cleanup_list, struct closeable_fd, descriptors): + Moved to clean-temp-private.h. + (string_equals, SIZE_BITS, string_hash, fatal_signal_set, + init_fatal_signal_set, asyncsafe_close): Moved to clean-temp-simple.c. + (asyncsafe_fclose_variant): Use get_fatal_signal_set() here. + (cleanup_action, do_init_clean_temp, clean_temp_once, init_clean_temp, + register_temporary_file, unregister_temporary_file, do_unlink, + cleanup_temporary_file): Moved to clean-temp-simple.c. + (create_temp_dir, cleanup_temp_file, cleanup_temp_dir_contents, + gen_register_open_temp, close_temp): Update for changed function names. + (fclose_variant_temp): Don't call init_fatal_signal_set(). + * modules/clean-temp-simple: New file, based on modules/clean-temp. + * modules/clean-temp (Depends-on): Add clean-temp-simple, list. Remove + stdint. + (configure.ac): Don't define SIGNAL_SAFE_LIST here. + +2021-03-22 Bruno Haible + error: Relicense under LGPLv2+. Pino Toscano's approval is in . diff --git a/lib/clean-temp-private.h b/lib/clean-temp-private.h new file mode 100644 index 0000000..655eb3f --- /dev/null +++ b/lib/clean-temp-private.h @@ -0,0 +1,82 @@ +/* Private interface between modules 'clean-temp-simple' and 'clean-temp'. + Copyright (C) 2006-2021 Free Software Foundation, Inc. + + 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 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 + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + 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, see . */ + +#ifndef _CLEAN_TEMP_PRIVATE_H +#define _CLEAN_TEMP_PRIVATE_H + +#include +#include +#include "gl_list.h" +#include "asyncsafe-spin.h" + +/* 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. */ + +/* 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 /* */ volatile subdirs; + /* Absolute pathnames of files. */ + gl_list_t /* */ volatile files; +}; + +/* List of all temporary directories. */ +struct all_tempdirs +{ + struct tempdir * volatile * volatile tempdir_list; + size_t volatile tempdir_count; + size_t tempdir_allocated; +}; +#define dir_cleanup_list clean_temp_dir_cleanup_list +extern struct all_tempdirs dir_cleanup_list; + +/* 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; +}; +#define descriptors clean_temp_descriptors +extern gl_list_t /* */ volatile descriptors; + +extern bool clean_temp_string_equals (const void *x1, const void *x2); +extern size_t clean_temp_string_hash (const void *x); + +extern _GL_ASYNC_SAFE int clean_temp_asyncsafe_close (struct closeable_fd *element); +extern void clean_temp_init_asyncsafe_close (void); + +extern void clean_temp_init (void); + +extern int clean_temp_unlink (const char *absolute_file_name, bool cleanup_verbose); + +#endif /* _CLEAN_TEMP_PRIVATE_H */ diff --git a/lib/clean-temp-simple.c b/lib/clean-temp-simple.c new file mode 100644 index 0000000..e4fd3e8 --- /dev/null +++ b/lib/clean-temp-simple.c @@ -0,0 +1,367 @@ +/* Temporary files with automatic cleanup. + Copyright (C) 2006-2021 Free Software Foundation, Inc. + Written by Bruno Haible , 2006. + + 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 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 + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + 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, see . */ + +#include + +/* Specification. */ +#include "clean-temp-simple.h" +#include "clean-temp-private.h" + +#include +#include +#include +#include +#include +#include +#include + +#include "error.h" +#include "fatal-signal.h" +#include "asyncsafe-spin.h" +#include "xalloc.h" +#include "glthread/lock.h" +#include "thread-optim.h" +#include "gl_xlist.h" +#include "gl_linkedhash_list.h" +#include "gettext.h" + +#define _(str) gettext (str) + + +/* 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 /* */ volatile file_cleanup_list; + + +/* List of all temporary directories. */ +struct all_tempdirs dir_cleanup_list /* = { NULL, 0, 0 } */; + + +/* List of all open file descriptors to temporary files. */ +gl_list_t /* */ 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. */ + +bool +clean_temp_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. */ +size_t +clean_temp_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. */ +_GL_ASYNC_SAFE int +clean_temp_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; +} +/* Initializations for use of this function. */ +void +clean_temp_init_asyncsafe_close (void) +{ + init_fatal_signal_set (); +} + +/* 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)) + { + clean_temp_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_clean_temp_init (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_clean_temp_init is called once only. */ +gl_once_define(static, clean_temp_once) + +/* Initializes this facility upon first use. */ +void +clean_temp_init (void) +{ + gl_once (clean_temp_once, do_clean_temp_init); +} + + +/* Remove a file, with optional error message. + Return 0 upon success, or -1 if there was some problem. */ +int +clean_temp_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; +} + + +/* ============= 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) + { + clean_temp_init (); + file_cleanup_list = + gl_list_create_empty (GL_LINKEDHASH_LIST, + clean_temp_string_equals, clean_temp_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 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 = clean_temp_unlink (absolute_file_name, cleanup_verbose); + unregister_temporary_file (absolute_file_name); + + return err; +} diff --git a/lib/clean-temp-simple.h b/lib/clean-temp-simple.h new file mode 100644 index 0000000..dce6812 --- /dev/null +++ b/lib/clean-temp-simple.h @@ -0,0 +1,51 @@ +/* Temporary files with automatic cleanup. + Copyright (C) 2006-2021 Free Software Foundation, Inc. + Written by Bruno Haible , 2006. + + 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 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 + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + 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, see . */ + +#ifndef _CLEAN_TEMP_SIMPLE_H +#define _CLEAN_TEMP_SIMPLE_H + +#include + +#ifdef __cplusplus +extern "C" { +#endif + + +/* See clean-temp.h for a general discussion of this module. */ + +/* 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. */ +extern void register_temporary_file (const char *absolute_file_name); + +/* 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. */ +extern void unregister_temporary_file (const char *absolute_file_name); + +/* 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. */ +extern int cleanup_temporary_file (const char *absolute_file_name, + bool cleanup_verbose); + + +#ifdef __cplusplus +} +#endif + +#endif /* _CLEAN_TEMP_SIMPLE_H */ diff --git a/lib/clean-temp.c b/lib/clean-temp.c index 268aa48..4091d93 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 . */ - #include /* Specification. */ @@ -24,9 +23,9 @@ #include #include -#include +#include #include -#include +#include #include #include #include @@ -36,6 +35,8 @@ # include #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 /* */ 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 /* */ volatile subdirs; - /* Absolute pathnames of files. */ - gl_list_t /* */ 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 /* */ 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; diff --git a/lib/clean-temp.h b/lib/clean-temp.h index 72e3e26..db4d7fe 100644 --- a/lib/clean-temp.h +++ b/lib/clean-temp.h @@ -37,10 +37,11 @@ extern "C" { and the temporary directories can be removed, because only on Unix (excluding Cygwin) can one remove directories containing open files. - This module provides support for - - temporary directories and temporary files inside these temporary - directories, - - temporary files without temporary directories. + There are two modules: + - 'clean-temp' provides support for temporary directories and temporary + files inside these temporary directories, + - 'clean-temp-simple' provides support for temporary files without + temporary directories. The temporary directories and files are automatically cleaned up (at the latest) when the program exits or dies from a fatal signal such as SIGINT, SIGTERM, SIGHUP, but not if it dies from a fatal signal such as SIGQUIT, @@ -64,21 +65,7 @@ extern "C" { /* ============= 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. */ -extern void register_temporary_file (const char *absolute_file_name); - -/* 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. */ -extern void unregister_temporary_file (const char *absolute_file_name); - -/* 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. */ -extern int cleanup_temporary_file (const char *absolute_file_name, - bool cleanup_verbose); +#include "clean-temp-simple.h" /* ========= Temporary directories and temporary files inside them ========= */ diff --git a/modules/clean-temp b/modules/clean-temp index 2eca006..a83f249 100644 --- a/modules/clean-temp +++ b/modules/clean-temp @@ -8,13 +8,14 @@ lib/clean-temp.c Depends-on: c99 stdbool -stdint +clean-temp-simple +list +asyncsafe-spin unistd lock thread-optim error fatal-signal -asyncsafe-spin open pathmax tmpdir @@ -29,7 +30,6 @@ xlist gettext-h configure.ac: -AC_DEFINE([SIGNAL_SAFE_LIST], [1], [Define if lists must be signal-safe.]) Makefile.am: lib_SOURCES += clean-temp.h clean-temp.c diff --git a/modules/clean-temp-simple b/modules/clean-temp-simple new file mode 100644 index 0000000..4187ee6 --- /dev/null +++ b/modules/clean-temp-simple @@ -0,0 +1,42 @@ +Description: +Temporary files with automatic cleanup. + +Files: +lib/clean-temp-simple.h +lib/clean-temp-private.h +lib/clean-temp-simple.c + +Depends-on: +c99 +stdbool +list +asyncsafe-spin +unistd +lock +thread-optim +error +fatal-signal +rmdir +xalloc +xalloc-die +linkedhash-list +xlist +gettext-h + +configure.ac: +AC_DEFINE([SIGNAL_SAFE_LIST], [1], [Define if lists must be signal-safe.]) + +Makefile.am: +lib_SOURCES += clean-temp-simple.h clean-temp-simple.c + +Include: +"clean-temp-simple.h" + +Link: +$(LIBTHREAD) + +License: +GPL + +Maintainer: +all -- 2.7.4