Files
RedBear-OS/local/recipes/dev/m4/source/lib/clean-temp-simple.c
T

369 lines
11 KiB
C

/* 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;
}