summaryrefslogtreecommitdiff
path: root/lib/clean-temp.c
diff options
context:
space:
mode:
Diffstat (limited to 'lib/clean-temp.c')
-rw-r--r--lib/clean-temp.c393
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;