| /* Temporary files with automatic cleanup. | |
| Copyright (C) 2006-2026 Free Software Foundation, Inc. | |
| Written by Bruno Haible <bruno@clisp.org>, 2006. | |
| This file is free software: you can redistribute it and/or modify | |
| it under the terms of the GNU Lesser General Public License as | |
| published by the Free Software Foundation; either version 2.1 of the | |
| License, or (at your option) any later version. | |
| This file 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 Lesser General Public License for more details. | |
| You should have received a copy of the GNU Lesser 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 <signal.h> | |
| #include <stdlib.h> | |
| #include <string.h> | |
| #include <unistd.h> | |
| #include <error.h> | |
| #include "fatal-signal.h" | |
| #include "asyncsafe-spin.h" | |
| #include "glthread/lock.h" | |
| #include "glthread/once.h" | |
| #include "thread-optim.h" | |
| #include "gl_list.h" | |
| #include "gl_linkedhash_list.h" | |
| #include "hashkey-string.h" | |
| #include "gettext.h" | |
| #define _(msgid) dgettext (GNULIB_TEXT_DOMAIN, msgid) | |
| /* 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. | |
| */ | |
| /* 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, true, 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, true, &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 (_GL_UNUSED int sig) | |
| { | |
| /* First close all file descriptors to temporary files. */ | |
| { | |
| gl_list_t fds = descriptors; | |
| if (fds != NULL) | |
| { | |
| gl_list_iterator_t iter = gl_list_iterator (fds); | |
| const void *element; | |
| 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 = gl_list_iterator (files); | |
| const void *element; | |
| while (gl_list_iterator_next (&iter, &element, NULL)) | |
| { | |
| const char *file = (const char *) element; | |
| unlink (file); | |
| } | |
| gl_list_iterator_free (&iter); | |
| } | |
| } | |
| for (size_t i = 0; i < dir_cleanup_list.tempdir_count; i++) | |
| { | |
| struct tempdir *dir = dir_cleanup_list.tempdir_list[i]; | |
| if (dir != NULL) | |
| { | |
| { | |
| /* First cleanup the files in the subdirectories. */ | |
| gl_list_iterator_t iter = gl_list_iterator (dir->files); | |
| const void *element; | |
| 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. */ | |
| gl_list_iterator_t iter = gl_list_iterator (dir->subdirs); | |
| const void *element; | |
| 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); | |
| } | |
| } | |
| } | |
| /* Set to -1 if initialization of this facility failed. */ | |
| static int volatile init_failed /* = 0 */; | |
| /* 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) | |
| init_failed = -1; | |
| } | |
| /* Ensure that do_clean_temp_init is called once only. */ | |
| gl_once_define(static, clean_temp_once) | |
| /* Initializes this facility upon first use. | |
| Return 0 upon success, or -1 if there was a memory allocation problem. */ | |
| int | |
| clean_temp_init (void) | |
| { | |
| gl_once (clean_temp_once, do_clean_temp_init); | |
| return init_failed; | |
| } | |
| /* 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. | |
| Return 0 upon success, or -1 if there was a memory allocation problem. */ | |
| int | |
| register_temporary_file (const char *absolute_file_name) | |
| { | |
| bool mt = gl_multithreaded (); | |
| if (mt) gl_lock_lock (file_cleanup_list_lock); | |
| int ret = 0; | |
| /* Make sure that this facility and the file_cleanup_list are initialized. */ | |
| if (file_cleanup_list == NULL) | |
| { | |
| if (clean_temp_init () < 0) | |
| { | |
| ret = -1; | |
| goto done; | |
| } | |
| file_cleanup_list = | |
| gl_list_nx_create_empty (GL_LINKEDHASH_LIST, | |
| hashkey_string_equals, | |
| hashkey_string_hash, | |
| NULL, false); | |
| if (file_cleanup_list == NULL) | |
| { | |
| ret = -1; | |
| goto done; | |
| } | |
| } | |
| /* Add absolute_file_name to file_cleanup_list, without duplicates. */ | |
| if (gl_list_search (file_cleanup_list, absolute_file_name) == NULL) | |
| { | |
| char *absolute_file_name_copy = strdup (absolute_file_name); | |
| if (absolute_file_name_copy == NULL) | |
| { | |
| ret = -1; | |
| goto done; | |
| } | |
| if (gl_list_nx_add_first (file_cleanup_list, absolute_file_name_copy) | |
| == NULL) | |
| { | |
| free (absolute_file_name_copy); | |
| ret = -1; | |
| goto done; | |
| } | |
| } | |
| done: | |
| if (mt) gl_lock_unlock (file_cleanup_list_lock); | |
| return ret; | |
| } | |
| /* 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 = clean_temp_unlink (absolute_file_name, cleanup_verbose); | |
| unregister_temporary_file (absolute_file_name); | |
| return err; | |
| } |