exp2l: Work around a NetBSD 10.0/i386 bug.
[gnulib.git] / lib / clean-temp-simple.c
blob6ceace116d9036e832480a9a22d2d4385a36aa59
1 /* Temporary files with automatic cleanup.
2 Copyright (C) 2006-2024 Free Software Foundation, Inc.
3 Written by Bruno Haible <bruno@clisp.org>, 2006.
5 This file is free software: you can redistribute it and/or modify
6 it under the terms of the GNU Lesser General Public License as
7 published by the Free Software Foundation; either version 2.1 of the
8 License, or (at your option) any later version.
10 This file is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU Lesser General Public License for more details.
15 You should have received a copy of the GNU Lesser General Public License
16 along with this program. If not, see <https://www.gnu.org/licenses/>. */
18 #include <config.h>
20 /* Specification. */
21 #include "clean-temp-simple.h"
22 #include "clean-temp-private.h"
24 #include <errno.h>
25 #include <limits.h>
26 #include <signal.h>
27 #include <stdlib.h>
28 #include <string.h>
29 #include <unistd.h>
31 #include <error.h>
32 #include "fatal-signal.h"
33 #include "asyncsafe-spin.h"
34 #include "glthread/lock.h"
35 #include "thread-optim.h"
36 #include "gl_list.h"
37 #include "gl_linkedhash_list.h"
38 #include "gettext.h"
40 #define _(str) gettext (str)
43 /* Lock that protects the file_cleanup_list from concurrent modification in
44 different threads. */
45 gl_lock_define_initialized (static, file_cleanup_list_lock)
47 /* List of all temporary files without temporary directories. */
48 static gl_list_t /* <char *> */ volatile file_cleanup_list;
51 /* List of all temporary directories. */
52 struct all_tempdirs dir_cleanup_list /* = { NULL, 0, 0 } */;
55 /* List of all open file descriptors to temporary files. */
56 gl_list_t /* <closeable_fd *> */ volatile descriptors;
59 /* For the subdirs and for the files, we use a gl_list_t of type LINKEDHASH.
60 Why? We need a data structure that
62 1) Can contain an arbitrary number of 'char *' values. The strings
63 are compared via strcmp, not pointer comparison.
64 2) Has insertion and deletion operations that are fast: ideally O(1),
65 or possibly O(log n). This is important for GNU sort, which may
66 create a large number of temporary files.
67 3) Allows iteration through all elements from within a signal handler.
68 4) May or may not allow duplicates. It doesn't matter here, since
69 any file or subdir can only be removed once.
71 Criterion 1) would allow any gl_list_t or gl_oset_t implementation.
73 Criterion 2) leaves only GL_LINKEDHASH_LIST, GL_TREEHASH_LIST, or
74 GL_TREE_OSET.
76 Criterion 3) puts at disadvantage GL_TREEHASH_LIST and GL_TREE_OSET.
77 Namely, iteration through the elements of a binary tree requires access
78 to many ->left, ->right, ->parent pointers. However, the rebalancing
79 code for insertion and deletion in an AVL or red-black tree is so
80 complicated that we cannot assume that >left, ->right, ->parent pointers
81 are in a consistent state throughout these operations. Therefore, to
82 avoid a crash in the signal handler, all destructive operations to the
83 lists would have to be protected by a
84 block_fatal_signals ();
85 ...
86 unblock_fatal_signals ();
87 pair. Which causes extra system calls.
89 Criterion 3) would also discourage GL_ARRAY_LIST and GL_CARRAY_LIST,
90 if they were not already excluded. Namely, these implementations use
91 xrealloc(), leaving a time window in which in the list->elements pointer
92 points to already deallocated memory. To avoid a crash in the signal
93 handler at such a moment, all destructive operations would have to
94 protected by block/unblock_fatal_signals (), in this case too.
96 A list of type GL_LINKEDHASH_LIST without duplicates fulfills all
97 requirements:
98 2) Insertion and deletion are O(1) on average.
99 3) The gl_list_iterator, gl_list_iterator_next implementations do
100 not trigger memory allocations, nor other system calls, and are
101 therefore safe to be called from a signal handler.
102 Furthermore, since SIGNAL_SAFE_LIST is defined, the implementation
103 of the destructive functions ensures that the list structure is
104 safe to be traversed at any moment, even when interrupted by an
105 asynchronous signal.
108 /* String equality and hash code functions used by the lists. */
110 bool
111 clean_temp_string_equals (const void *x1, const void *x2)
113 const char *s1 = (const char *) x1;
114 const char *s2 = (const char *) x2;
115 return strcmp (s1, s2) == 0;
118 #define SIZE_BITS (sizeof (size_t) * CHAR_BIT)
120 /* A hash function for NUL-terminated char* strings using
121 the method described by Bruno Haible.
122 See https://www.haible.de/bruno/hashfunc.html. */
123 size_t
124 clean_temp_string_hash (const void *x)
126 const char *s = (const char *) x;
127 size_t h = 0;
129 for (; *s; s++)
130 h = *s + ((h << 9) | (h >> (SIZE_BITS - 9)));
132 return h;
136 /* The set of fatal signal handlers.
137 Cached here because we are not allowed to call get_fatal_signal_set ()
138 from a signal handler. */
139 static const sigset_t *fatal_signal_set /* = NULL */;
141 static void
142 init_fatal_signal_set (void)
144 if (fatal_signal_set == NULL)
145 fatal_signal_set = get_fatal_signal_set ();
149 /* Close a file descriptor.
150 Avoids race conditions with normal thread code or signal-handler code that
151 might want to close the same file descriptor. */
152 _GL_ASYNC_SAFE int
153 clean_temp_asyncsafe_close (struct closeable_fd *element)
155 sigset_t saved_mask;
156 int ret;
157 int saved_errno;
159 asyncsafe_spin_lock (&element->lock, fatal_signal_set, &saved_mask);
160 if (!element->closed)
162 ret = close (element->fd);
163 saved_errno = errno;
164 element->closed = true;
166 else
168 ret = 0;
169 saved_errno = 0;
171 asyncsafe_spin_unlock (&element->lock, &saved_mask);
172 element->done = true;
174 errno = saved_errno;
175 return ret;
177 /* Initializations for use of this function. */
178 void
179 clean_temp_init_asyncsafe_close (void)
181 init_fatal_signal_set ();
184 /* The signal handler. It gets called asynchronously. */
185 static _GL_ASYNC_SAFE void
186 cleanup_action (_GL_UNUSED int sig)
188 size_t i;
190 /* First close all file descriptors to temporary files. */
192 gl_list_t fds = descriptors;
194 if (fds != NULL)
196 gl_list_iterator_t iter;
197 const void *element;
199 iter = gl_list_iterator (fds);
200 while (gl_list_iterator_next (&iter, &element, NULL))
202 clean_temp_asyncsafe_close ((struct closeable_fd *) element);
204 gl_list_iterator_free (&iter);
209 gl_list_t files = file_cleanup_list;
211 if (files != NULL)
213 gl_list_iterator_t iter;
214 const void *element;
216 iter = gl_list_iterator (files);
217 while (gl_list_iterator_next (&iter, &element, NULL))
219 const char *file = (const char *) element;
220 unlink (file);
222 gl_list_iterator_free (&iter);
226 for (i = 0; i < dir_cleanup_list.tempdir_count; i++)
228 struct tempdir *dir = dir_cleanup_list.tempdir_list[i];
230 if (dir != NULL)
232 gl_list_iterator_t iter;
233 const void *element;
235 /* First cleanup the files in the subdirectories. */
236 iter = gl_list_iterator (dir->files);
237 while (gl_list_iterator_next (&iter, &element, NULL))
239 const char *file = (const char *) element;
240 unlink (file);
242 gl_list_iterator_free (&iter);
244 /* Then cleanup the subdirectories. */
245 iter = gl_list_iterator (dir->subdirs);
246 while (gl_list_iterator_next (&iter, &element, NULL))
248 const char *subdir = (const char *) element;
249 rmdir (subdir);
251 gl_list_iterator_free (&iter);
253 /* Then cleanup the temporary directory itself. */
254 rmdir (dir->dirname);
260 /* Set to -1 if initialization of this facility failed. */
261 static int volatile init_failed /* = 0 */;
263 /* Initializes this facility. */
264 static void
265 do_clean_temp_init (void)
267 /* Initialize the data used by the cleanup handler. */
268 init_fatal_signal_set ();
269 /* Register the cleanup handler. */
270 if (at_fatal_signal (&cleanup_action) < 0)
271 init_failed = -1;
274 /* Ensure that do_clean_temp_init is called once only. */
275 gl_once_define(static, clean_temp_once)
277 /* Initializes this facility upon first use.
278 Return 0 upon success, or -1 if there was a memory allocation problem. */
280 clean_temp_init (void)
282 gl_once (clean_temp_once, do_clean_temp_init);
283 return init_failed;
287 /* Remove a file, with optional error message.
288 Return 0 upon success, or -1 if there was some problem. */
290 clean_temp_unlink (const char *absolute_file_name, bool cleanup_verbose)
292 if (unlink (absolute_file_name) < 0 && cleanup_verbose
293 && errno != ENOENT)
295 error (0, errno,
296 _("cannot remove temporary file %s"), absolute_file_name);
297 return -1;
299 return 0;
303 /* ============= Temporary files without temporary directories ============= */
305 /* Register the given ABSOLUTE_FILE_NAME as being a file that needs to be
306 removed.
307 Should be called before the file ABSOLUTE_FILE_NAME is created.
308 Return 0 upon success, or -1 if there was a memory allocation problem. */
310 register_temporary_file (const char *absolute_file_name)
312 bool mt = gl_multithreaded ();
314 if (mt) gl_lock_lock (file_cleanup_list_lock);
316 int ret = 0;
318 /* Make sure that this facility and the file_cleanup_list are initialized. */
319 if (file_cleanup_list == NULL)
321 if (clean_temp_init () < 0)
323 ret = -1;
324 goto done;
326 file_cleanup_list =
327 gl_list_nx_create_empty (GL_LINKEDHASH_LIST,
328 clean_temp_string_equals,
329 clean_temp_string_hash,
330 NULL, false);
331 if (file_cleanup_list == NULL)
333 ret = -1;
334 goto done;
338 /* Add absolute_file_name to file_cleanup_list, without duplicates. */
339 if (gl_list_search (file_cleanup_list, absolute_file_name) == NULL)
341 char *absolute_file_name_copy = strdup (absolute_file_name);
342 if (absolute_file_name_copy == NULL)
344 ret = -1;
345 goto done;
347 if (gl_list_nx_add_first (file_cleanup_list, absolute_file_name_copy)
348 == NULL)
350 free (absolute_file_name_copy);
351 ret = -1;
352 goto done;
356 done:
357 if (mt) gl_lock_unlock (file_cleanup_list_lock);
359 return ret;
362 /* Unregister the given ABSOLUTE_FILE_NAME as being a file that needs to be
363 removed.
364 Should be called when the file ABSOLUTE_FILE_NAME could not be created. */
365 void
366 unregister_temporary_file (const char *absolute_file_name)
368 bool mt = gl_multithreaded ();
370 if (mt) gl_lock_lock (file_cleanup_list_lock);
372 gl_list_t list = file_cleanup_list;
373 if (list != NULL)
375 gl_list_node_t node = gl_list_search (list, absolute_file_name);
376 if (node != NULL)
378 char *old_string = (char *) gl_list_node_value (list, node);
380 gl_list_remove_node (list, node);
381 free (old_string);
385 if (mt) gl_lock_unlock (file_cleanup_list_lock);
388 /* Remove the given ABSOLUTE_FILE_NAME and unregister it.
389 CLEANUP_VERBOSE determines whether errors are reported to standard error.
390 Return 0 upon success, or -1 if there was some problem. */
392 cleanup_temporary_file (const char *absolute_file_name, bool cleanup_verbose)
394 int err;
396 err = clean_temp_unlink (absolute_file_name, cleanup_verbose);
397 unregister_temporary_file (absolute_file_name);
399 return err;