]> Savannah Git Hosting - gnulib.git/commitdiff
clean-temp-simple: New module.
authorBruno Haible <bruno@clisp.org>
Mon, 22 Mar 2021 21:48:23 +0000 (22:48 +0100)
committerBruno Haible <bruno@clisp.org>
Mon, 22 Mar 2021 21:48:23 +0000 (22:48 +0100)
* 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 <limits.h>, <stdint.h>. Include
<signal.h>, <stdio.h>, 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
lib/clean-temp-private.h [new file with mode: 0644]
lib/clean-temp-simple.c [new file with mode: 0644]
lib/clean-temp-simple.h [new file with mode: 0644]
lib/clean-temp.c
lib/clean-temp.h
modules/clean-temp
modules/clean-temp-simple [new file with mode: 0644]

index db793f8917131dc9f61cd127677cf75536eebaee..d16c61e692d79447b3859a7db27fcbbdff3cccce 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,32 @@
+2021-03-22  Bruno Haible  <bruno@clisp.org>
+
+       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 <limits.h>, <stdint.h>. Include
+       <signal.h>, <stdio.h>, 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  <bruno@clisp.org>
 
        error: Relicense under LGPLv2+.
diff --git a/lib/clean-temp-private.h b/lib/clean-temp-private.h
new file mode 100644 (file)
index 0000000..655eb3f
--- /dev/null
@@ -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 <https://www.gnu.org/licenses/>.  */
+
+#ifndef _CLEAN_TEMP_PRIVATE_H
+#define _CLEAN_TEMP_PRIVATE_H
+
+#include <stdbool.h>
+#include <stddef.h>
+#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 /* <char *> */ volatile subdirs;
+  /* Absolute pathnames of files.  */
+  gl_list_t /* <char *> */ 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 /* <closeable_fd *> */ 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 (file)
index 0000000..e4fd3e8
--- /dev/null
@@ -0,0 +1,367 @@
+/* Temporary files with automatic cleanup.
+   Copyright (C) 2006-2021 Free Software Foundation, Inc.
+   Written by Bruno Haible <bruno@clisp.org>, 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 <https://www.gnu.org/licenses/>.  */
+
+#include <config.h>
+
+/* Specification.  */
+#include "clean-temp-simple.h"
+#include "clean-temp-private.h"
+
+#include <errno.h>
+#include <limits.h>
+#include <signal.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#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 /* <char *> */ 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 /* <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.  */
+
+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 (file)
index 0000000..dce6812
--- /dev/null
@@ -0,0 +1,51 @@
+/* Temporary files with automatic cleanup.
+   Copyright (C) 2006-2021 Free Software Foundation, Inc.
+   Written by Bruno Haible <bruno@clisp.org>, 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 <https://www.gnu.org/licenses/>.  */
+
+#ifndef _CLEAN_TEMP_SIMPLE_H
+#define _CLEAN_TEMP_SIMPLE_H
+
+#include <stdbool.h>
+
+#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 */
index 268aa48208d0931c9a8d99549c05a659da6a59a2..4091d932730a6f39a6d94d6818c995298e341738 100644 (file)
@@ -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"
 #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;
 
index 72e3e26b5092c44dbe4f7e04da1341875e5037b0..db4d7fea261ce19bcbe21571d0e02e72a3e4a84a 100644 (file)
@@ -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 ========= */
 
index 2eca0060a727206dbb409e9b9e4cee20574196b7..a83f2499034f6effbb76040bebcc9916b8783cbf 100644 (file)
@@ -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 (file)
index 0000000..4187ee6
--- /dev/null
@@ -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