exp2l: Work around a NetBSD 10.0/i386 bug.
[gnulib.git] / lib / clean-temp.c
blob382de1aa750041c26418287bb5b42a18bf6bceb1
1 /* Temporary directories and temporary files with automatic cleanup.
2 Copyright (C) 2001, 2003, 2006-2007, 2009-2024 Free Software Foundation,
3 Inc.
4 Written by Bruno Haible <bruno@clisp.org>, 2006.
6 This program is free software: you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation, either version 3 of the License, or
9 (at your option) any later version.
11 This program is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 GNU General Public License for more details.
16 You should have received a copy of the GNU General Public License
17 along with this program. If not, see <https://www.gnu.org/licenses/>. */
19 #include <config.h>
21 /* Specification. */
22 #include "clean-temp.h"
24 #include <errno.h>
25 #include <fcntl.h>
26 #include <signal.h>
27 #include <stdio.h>
28 #include <stdlib.h>
29 #include <string.h>
30 #include <unistd.h>
32 #if defined _WIN32 && ! defined __CYGWIN__
33 # define WIN32_LEAN_AND_MEAN /* avoid including junk */
34 # include <windows.h>
35 #endif
37 #include "clean-temp-simple.h"
38 #include "clean-temp-private.h"
39 #include <error.h>
40 #include "fatal-signal.h"
41 #include "asyncsafe-spin.h"
42 #include "pathmax.h"
43 #include "tmpdir.h"
44 #include "xalloc.h"
45 #include "xmalloca.h"
46 #include "glthread/lock.h"
47 #include "thread-optim.h"
48 #include "gl_xlist.h"
49 #include "gl_linkedhash_list.h"
50 #include "gl_linked_list.h"
51 #include "gettext.h"
52 #if GNULIB_TEMPNAME
53 # include "tempname.h"
54 #endif
55 #if GNULIB_FWRITEERROR
56 # include "fwriteerror.h"
57 #endif
58 #if GNULIB_CLOSE_STREAM
59 # include "close-stream.h"
60 #endif
61 #if GNULIB_FCNTL_SAFER
62 # include "fcntl--.h"
63 #endif
64 #if GNULIB_FOPEN_SAFER
65 # include "stdio--.h"
66 #endif
68 #define _(str) gettext (str)
70 /* GNU Hurd doesn't have PATH_MAX. Use a fallback.
71 Temporary directory names are usually not that long. */
72 #ifndef PATH_MAX
73 # define PATH_MAX 1024
74 #endif
76 #if defined _WIN32 && ! defined __CYGWIN__
77 /* Don't assume that UNICODE is not defined. */
78 # undef OSVERSIONINFO
79 # define OSVERSIONINFO OSVERSIONINFOA
80 # undef GetVersionEx
81 # define GetVersionEx GetVersionExA
82 #endif
85 /* Lock that protects the dir_cleanup_list from concurrent modification in
86 different threads. */
87 gl_lock_define_initialized (static, dir_cleanup_list_lock)
89 /* Lock that protects the descriptors list from concurrent modification in
90 different threads. */
91 gl_lock_define_initialized (static, descriptors_lock)
94 /* Close a file descriptor and the stream that contains it.
95 Avoids race conditions with signal-handler code that might want to close the
96 same file descriptor. */
97 static int
98 asyncsafe_fclose_variant (struct closeable_fd *element, FILE *fp,
99 int (*fclose_variant) (FILE *))
101 if (fileno (fp) != element->fd)
102 abort ();
104 /* Flush buffered data first, to minimize the duration of the spin lock. */
105 fflush (fp);
107 sigset_t saved_mask;
108 int ret;
109 int saved_errno;
111 asyncsafe_spin_lock (&element->lock, get_fatal_signal_set (), &saved_mask);
112 if (!element->closed)
114 ret = fclose_variant (fp); /* invokes close (element->fd) */
115 saved_errno = errno;
116 element->closed = true;
118 else
120 ret = 0;
121 saved_errno = 0;
123 asyncsafe_spin_unlock (&element->lock, &saved_mask);
124 element->done = true;
126 errno = saved_errno;
127 return ret;
131 /* ========= Temporary directories and temporary files inside them ========= */
133 /* Create a temporary directory.
134 PREFIX is used as a prefix for the name of the temporary directory. It
135 should be short and still give an indication about the program.
136 PARENTDIR can be used to specify the parent directory; if NULL, a default
137 parent directory is used (either $TMPDIR or /tmp or similar).
138 CLEANUP_VERBOSE determines whether errors during explicit cleanup are
139 reported to standard error.
140 Return a fresh 'struct temp_dir' on success. Upon error, an error message
141 is shown and NULL is returned. */
142 struct temp_dir *
143 create_temp_dir (const char *prefix, const char *parentdir,
144 bool cleanup_verbose)
146 bool mt = gl_multithreaded ();
148 if (mt) gl_lock_lock (dir_cleanup_list_lock);
150 struct tempdir * volatile *tmpdirp = NULL;
151 struct tempdir *tmpdir;
152 size_t i;
153 char *xtemplate;
154 char *tmpdirname;
156 /* See whether it can take the slot of an earlier temporary directory
157 already cleaned up. */
158 for (i = 0; i < dir_cleanup_list.tempdir_count; i++)
159 if (dir_cleanup_list.tempdir_list[i] == NULL)
161 tmpdirp = &dir_cleanup_list.tempdir_list[i];
162 break;
164 if (tmpdirp == NULL)
166 /* See whether the array needs to be extended. */
167 if (dir_cleanup_list.tempdir_count == dir_cleanup_list.tempdir_allocated)
169 /* Note that we cannot use xrealloc(), because then the cleanup()
170 function could access an already deallocated array. */
171 struct tempdir * volatile *old_array = dir_cleanup_list.tempdir_list;
172 size_t old_allocated = dir_cleanup_list.tempdir_allocated;
173 size_t new_allocated = 2 * dir_cleanup_list.tempdir_allocated + 1;
174 struct tempdir * volatile *new_array =
175 XNMALLOC (new_allocated, struct tempdir * volatile);
177 if (old_allocated == 0)
179 /* First use of this facility. */
180 if (clean_temp_init () < 0)
181 xalloc_die ();
183 else
185 /* Don't use memcpy() here, because memcpy takes non-volatile
186 arguments and is therefore not guaranteed to complete all
187 memory stores before the next statement. */
188 size_t k;
190 for (k = 0; k < old_allocated; k++)
191 new_array[k] = old_array[k];
194 dir_cleanup_list.tempdir_list = new_array;
195 dir_cleanup_list.tempdir_allocated = new_allocated;
197 /* Now we can free the old array. */
198 /* No, we can't do that. If cleanup_action is running in a different
199 thread and has already fetched the tempdir_list pointer (getting
200 old_array) but not yet accessed its i-th element, that thread may
201 crash when accessing an element of the already freed old_array
202 array. */
203 #if 0
204 if (old_array != NULL)
205 free ((struct tempdir **) old_array);
206 #endif
209 tmpdirp = &dir_cleanup_list.tempdir_list[dir_cleanup_list.tempdir_count];
210 /* Initialize *tmpdirp before incrementing tempdir_count, so that
211 cleanup() will skip this entry before it is fully initialized. */
212 *tmpdirp = NULL;
213 dir_cleanup_list.tempdir_count++;
216 /* Initialize a 'struct tempdir'. */
217 tmpdir = XMALLOC (struct tempdir);
218 tmpdir->dirname = NULL;
219 tmpdir->cleanup_verbose = cleanup_verbose;
220 tmpdir->subdirs =
221 gl_list_create_empty (GL_LINKEDHASH_LIST,
222 clean_temp_string_equals, clean_temp_string_hash,
223 NULL, false);
224 tmpdir->files =
225 gl_list_create_empty (GL_LINKEDHASH_LIST,
226 clean_temp_string_equals, clean_temp_string_hash,
227 NULL, false);
229 /* Create the temporary directory. */
230 xtemplate = (char *) xmalloca (PATH_MAX);
231 if (path_search (xtemplate, PATH_MAX, parentdir, prefix, parentdir == NULL))
233 error (0, errno,
234 _("cannot find a temporary directory, try setting $TMPDIR"));
235 goto quit;
237 block_fatal_signals ();
238 tmpdirname = mkdtemp (xtemplate);
239 int saved_errno = errno;
240 if (tmpdirname != NULL)
242 tmpdir->dirname = tmpdirname;
243 *tmpdirp = tmpdir;
245 unblock_fatal_signals ();
246 if (tmpdirname == NULL)
248 error (0, saved_errno,
249 _("cannot create a temporary directory using template \"%s\""),
250 xtemplate);
251 goto quit;
253 /* Replace tmpdir->dirname with a copy that has indefinite extent.
254 We cannot do this inside the block_fatal_signals/unblock_fatal_signals
255 block because then the cleanup handler would not remove the directory
256 if xstrdup fails. */
257 tmpdir->dirname = xstrdup (tmpdirname);
258 if (mt) gl_lock_unlock (dir_cleanup_list_lock);
259 freea (xtemplate);
260 return (struct temp_dir *) tmpdir;
262 quit:
263 if (mt) gl_lock_unlock (dir_cleanup_list_lock);
264 freea (xtemplate);
265 return NULL;
268 /* Register the given ABSOLUTE_FILE_NAME as being a file inside DIR, that
269 needs to be removed before DIR can be removed.
270 Should be called before the file ABSOLUTE_FILE_NAME is created. */
271 void
272 register_temp_file (struct temp_dir *dir,
273 const char *absolute_file_name)
275 struct tempdir *tmpdir = (struct tempdir *)dir;
276 bool mt = gl_multithreaded ();
278 if (mt) gl_lock_lock (dir_cleanup_list_lock);
280 /* Add absolute_file_name to tmpdir->files, without duplicates. */
281 if (gl_list_search (tmpdir->files, absolute_file_name) == NULL)
282 gl_list_add_first (tmpdir->files, xstrdup (absolute_file_name));
284 if (mt) gl_lock_unlock (dir_cleanup_list_lock);
287 /* Unregister the given ABSOLUTE_FILE_NAME as being a file inside DIR, that
288 needs to be removed before DIR can be removed.
289 Should be called when the file ABSOLUTE_FILE_NAME could not be created. */
290 void
291 unregister_temp_file (struct temp_dir *dir,
292 const char *absolute_file_name)
294 struct tempdir *tmpdir = (struct tempdir *)dir;
295 bool mt = gl_multithreaded ();
297 if (mt) gl_lock_lock (dir_cleanup_list_lock);
299 gl_list_t list = tmpdir->files;
300 gl_list_node_t node;
302 node = gl_list_search (list, absolute_file_name);
303 if (node != NULL)
305 char *old_string = (char *) gl_list_node_value (list, node);
307 gl_list_remove_node (list, node);
308 free (old_string);
311 if (mt) gl_lock_unlock (dir_cleanup_list_lock);
314 /* Register the given ABSOLUTE_DIR_NAME as being a subdirectory inside DIR,
315 that needs to be removed before DIR can be removed.
316 Should be called before the subdirectory ABSOLUTE_DIR_NAME is created. */
317 void
318 register_temp_subdir (struct temp_dir *dir,
319 const char *absolute_dir_name)
321 struct tempdir *tmpdir = (struct tempdir *)dir;
322 bool mt = gl_multithreaded ();
324 if (mt) gl_lock_lock (dir_cleanup_list_lock);
326 /* Add absolute_dir_name to tmpdir->subdirs, without duplicates. */
327 if (gl_list_search (tmpdir->subdirs, absolute_dir_name) == NULL)
328 gl_list_add_first (tmpdir->subdirs, xstrdup (absolute_dir_name));
330 if (mt) gl_lock_unlock (dir_cleanup_list_lock);
333 /* Unregister the given ABSOLUTE_DIR_NAME as being a subdirectory inside DIR,
334 that needs to be removed before DIR can be removed.
335 Should be called when the subdirectory ABSOLUTE_DIR_NAME could not be
336 created. */
337 void
338 unregister_temp_subdir (struct temp_dir *dir,
339 const char *absolute_dir_name)
341 struct tempdir *tmpdir = (struct tempdir *)dir;
342 bool mt = gl_multithreaded ();
344 if (mt) gl_lock_lock (dir_cleanup_list_lock);
346 gl_list_t list = tmpdir->subdirs;
347 gl_list_node_t node;
349 node = gl_list_search (list, absolute_dir_name);
350 if (node != NULL)
352 char *old_string = (char *) gl_list_node_value (list, node);
354 gl_list_remove_node (list, node);
355 free (old_string);
358 if (mt) gl_lock_unlock (dir_cleanup_list_lock);
361 /* Remove a directory, with optional error message.
362 Return 0 upon success, or -1 if there was some problem. */
363 static int
364 do_rmdir (const char *absolute_dir_name, bool cleanup_verbose)
366 if (rmdir (absolute_dir_name) < 0 && cleanup_verbose
367 && errno != ENOENT)
369 error (0, errno,
370 _("cannot remove temporary directory %s"), absolute_dir_name);
371 return -1;
373 return 0;
376 /* Remove the given ABSOLUTE_FILE_NAME and unregister it.
377 Return 0 upon success, or -1 if there was some problem. */
379 cleanup_temp_file (struct temp_dir *dir,
380 const char *absolute_file_name)
382 int err;
384 err = clean_temp_unlink (absolute_file_name, dir->cleanup_verbose);
385 unregister_temp_file (dir, absolute_file_name);
387 return err;
390 /* Remove the given ABSOLUTE_DIR_NAME and unregister it.
391 Return 0 upon success, or -1 if there was some problem. */
393 cleanup_temp_subdir (struct temp_dir *dir,
394 const char *absolute_dir_name)
396 int err;
398 err = do_rmdir (absolute_dir_name, dir->cleanup_verbose);
399 unregister_temp_subdir (dir, absolute_dir_name);
401 return err;
404 /* Remove all registered files and subdirectories inside DIR.
405 Only to be called with dir_cleanup_list_lock locked.
406 Return 0 upon success, or -1 if there was some problem. */
408 cleanup_temp_dir_contents (struct temp_dir *dir)
410 struct tempdir *tmpdir = (struct tempdir *)dir;
411 int err = 0;
412 gl_list_t list;
413 gl_list_iterator_t iter;
414 const void *element;
415 gl_list_node_t node;
417 /* First cleanup the files in the subdirectories. */
418 list = tmpdir->files;
419 iter = gl_list_iterator (list);
420 while (gl_list_iterator_next (&iter, &element, &node))
422 char *file = (char *) element;
424 err |= clean_temp_unlink (file, dir->cleanup_verbose);
425 gl_list_remove_node (list, node);
426 /* Now only we can free file. */
427 free (file);
429 gl_list_iterator_free (&iter);
431 /* Then cleanup the subdirectories. */
432 list = tmpdir->subdirs;
433 iter = gl_list_iterator (list);
434 while (gl_list_iterator_next (&iter, &element, &node))
436 char *subdir = (char *) element;
438 err |= do_rmdir (subdir, dir->cleanup_verbose);
439 gl_list_remove_node (list, node);
440 /* Now only we can free subdir. */
441 free (subdir);
443 gl_list_iterator_free (&iter);
445 return err;
448 /* Remove all registered files and subdirectories inside DIR and DIR itself.
449 DIR cannot be used any more after this call.
450 Return 0 upon success, or -1 if there was some problem. */
452 cleanup_temp_dir (struct temp_dir *dir)
454 bool mt = gl_multithreaded ();
456 if (mt) gl_lock_lock (dir_cleanup_list_lock);
458 struct tempdir *tmpdir = (struct tempdir *)dir;
459 int err = 0;
460 size_t i;
462 err |= cleanup_temp_dir_contents (dir);
463 err |= do_rmdir (tmpdir->dirname, dir->cleanup_verbose);
465 for (i = 0; i < dir_cleanup_list.tempdir_count; i++)
466 if (dir_cleanup_list.tempdir_list[i] == tmpdir)
468 /* Remove dir_cleanup_list.tempdir_list[i]. */
469 if (i + 1 == dir_cleanup_list.tempdir_count)
471 while (i > 0 && dir_cleanup_list.tempdir_list[i - 1] == NULL)
472 i--;
473 dir_cleanup_list.tempdir_count = i;
475 else
476 dir_cleanup_list.tempdir_list[i] = NULL;
477 /* Now only we can free the tmpdir->dirname, tmpdir->subdirs,
478 tmpdir->files, and tmpdir itself. */
479 gl_list_free (tmpdir->files);
480 gl_list_free (tmpdir->subdirs);
481 free (tmpdir->dirname);
482 free (tmpdir);
483 if (mt) gl_lock_unlock (dir_cleanup_list_lock);
484 return err;
487 /* The user passed an invalid DIR argument. */
488 abort ();
492 /* ================== Opening and closing temporary files ================== */
494 #if defined _WIN32 && ! defined __CYGWIN__
496 /* On Windows, opening a file with _O_TEMPORARY has the effect of passing
497 the FILE_FLAG_DELETE_ON_CLOSE flag to CreateFile(), which has the effect
498 of deleting the file when it is closed - even when the program crashes.
499 But (according to the Cygwin sources) it works only on Windows NT or newer.
500 So we cache the info whether we are running on Windows NT or newer. */
502 static bool
503 supports_delete_on_close ()
505 static int known; /* 1 = yes, -1 = no, 0 = unknown */
506 if (!known)
508 OSVERSIONINFO v;
510 /* According to
511 <https://docs.microsoft.com/en-us/windows/desktop/api/sysinfoapi/nf-sysinfoapi-getversionexa>
512 this structure must be initialized as follows: */
513 v.dwOSVersionInfoSize = sizeof (OSVERSIONINFO);
515 if (GetVersionEx (&v))
516 known = (v.dwPlatformId == VER_PLATFORM_WIN32_NT ? 1 : -1);
517 else
518 known = -1;
520 return (known > 0);
523 #endif
526 /* Register a file descriptor to be closed. */
527 static void
528 register_fd (int fd)
530 bool mt = gl_multithreaded ();
532 if (mt) gl_lock_lock (descriptors_lock);
534 if (descriptors == NULL)
535 descriptors = gl_list_create_empty (GL_LINKED_LIST, NULL, NULL, NULL,
536 false);
538 struct closeable_fd *element = XMALLOC (struct closeable_fd);
539 element->fd = fd;
540 element->closed = false;
541 asyncsafe_spin_init (&element->lock);
542 element->done = false;
544 gl_list_add_first (descriptors, element);
546 if (mt) gl_lock_unlock (descriptors_lock);
549 /* Open a temporary file in a temporary directory.
550 FILE_NAME must already have been passed to register_temp_file.
551 Registers the resulting file descriptor to be closed.
552 DELETE_ON_CLOSE indicates whether the file can be deleted when the resulting
553 file descriptor or stream is closed. */
555 open_temp (const char *file_name, int flags, mode_t mode, bool delete_on_close)
557 int fd;
558 int saved_errno;
560 block_fatal_signals ();
561 /* Note: 'open' here is actually open() or open_safer(). */
562 #if defined _WIN32 && ! defined __CYGWIN__
563 /* Use _O_TEMPORARY when possible, to increase the chances that the
564 temporary file is removed when the process crashes. */
565 if (delete_on_close && supports_delete_on_close ())
566 fd = open (file_name, flags | _O_TEMPORARY, mode);
567 else
568 #endif
569 fd = open (file_name, flags, mode);
570 saved_errno = errno;
571 if (fd >= 0)
572 register_fd (fd);
573 unblock_fatal_signals ();
574 errno = saved_errno;
575 return fd;
578 /* Open a temporary file in a temporary directory.
579 FILE_NAME must already have been passed to register_temp_file.
580 Registers the resulting file descriptor to be closed.
581 DELETE_ON_CLOSE indicates whether the file can be deleted when the resulting
582 file descriptor or stream is closed. */
583 FILE *
584 fopen_temp (const char *file_name, const char *mode, bool delete_on_close)
586 FILE *fp;
587 int saved_errno;
589 block_fatal_signals ();
590 /* Note: 'fopen' here is actually fopen() or fopen_safer(). */
591 #if defined _WIN32 && ! defined __CYGWIN__
592 /* Use _O_TEMPORARY when possible, to increase the chances that the
593 temporary file is removed when the process crashes. */
594 if (delete_on_close && supports_delete_on_close ())
596 size_t mode_len = strlen (mode);
597 char *augmented_mode = (char *) xmalloca (mode_len + 2);
598 memcpy (augmented_mode, mode, mode_len);
599 memcpy (augmented_mode + mode_len, "D", 2);
601 fp = fopen (file_name, augmented_mode);
602 saved_errno = errno;
604 freea (augmented_mode);
606 else
607 #endif
609 fp = fopen (file_name, mode);
610 saved_errno = errno;
612 if (fp != NULL)
614 /* It is sufficient to register fileno (fp) instead of the entire fp,
615 because at cleanup time there is no need to do an fflush (fp); a
616 close (fileno (fp)) will be enough. */
617 int fd = fileno (fp);
618 if (!(fd >= 0))
619 abort ();
620 register_fd (fd);
622 unblock_fatal_signals ();
623 errno = saved_errno;
624 return fp;
627 #if GNULIB_TEMPNAME
629 struct try_create_file_params
631 int flags;
632 mode_t mode;
635 static int
636 try_create_file (char *file_name_tmpl, void *params_)
638 struct try_create_file_params *params = params_;
639 return open (file_name_tmpl,
640 (params->flags & ~O_ACCMODE) | O_RDWR | O_CREAT | O_EXCL,
641 params->mode);
644 /* Open a temporary file, generating its name based on FILE_NAME_TMPL.
645 FILE_NAME_TMPL must match the rules for mk[s]temp (i.e. end in "XXXXXX",
646 possibly with a suffix). The name constructed does not exist at the time
647 of the call. FILE_NAME_TMPL is overwritten with the result.
648 A safe choice for MODE is S_IRUSR | S_IWUSR, a.k.a. 0600.
649 Registers the file for deletion.
650 Opens the file, with the given FLAGS and mode MODE.
651 Registers the resulting file descriptor to be closed. */
653 gen_register_open_temp (char *file_name_tmpl, int suffixlen,
654 int flags, mode_t mode)
656 block_fatal_signals ();
658 struct try_create_file_params params;
659 params.flags = flags;
660 params.mode = mode;
662 int fd = try_tempname (file_name_tmpl, suffixlen, &params, try_create_file);
664 int saved_errno = errno;
665 if (fd >= 0)
667 if (clean_temp_init () < 0)
668 xalloc_die ();
669 register_fd (fd);
670 if (register_temporary_file (file_name_tmpl) < 0)
671 xalloc_die ();
673 unblock_fatal_signals ();
674 errno = saved_errno;
675 return fd;
678 #endif
680 /* Close a temporary file.
681 FD must have been returned by open_temp or gen_register_open_temp.
682 Unregisters the previously registered file descriptor. */
684 close_temp (int fd)
686 if (fd < 0)
687 return close (fd);
689 clean_temp_init_asyncsafe_close ();
691 int result = 0;
692 int saved_errno = 0;
694 bool mt = gl_multithreaded ();
696 if (mt) gl_lock_lock (descriptors_lock);
698 gl_list_t list = descriptors;
699 if (list == NULL)
700 /* descriptors should already contain fd. */
701 abort ();
703 /* Search through the list, and clean it up on the fly. */
704 bool found = false;
705 gl_list_iterator_t iter = gl_list_iterator (list);
706 const void *elt;
707 gl_list_node_t node;
708 if (gl_list_iterator_next (&iter, &elt, &node))
709 for (;;)
711 struct closeable_fd *element = (struct closeable_fd *) elt;
713 /* Close the file descriptor, avoiding races with the signal
714 handler. */
715 if (element->fd == fd)
717 found = true;
718 result = clean_temp_asyncsafe_close (element);
719 saved_errno = errno;
722 bool free_this_node = element->done;
723 struct closeable_fd *element_to_free = element;
724 gl_list_node_t node_to_free = node;
726 bool have_next = gl_list_iterator_next (&iter, &elt, &node);
728 if (free_this_node)
730 free (element_to_free);
731 gl_list_remove_node (list, node_to_free);
734 if (!have_next)
735 break;
737 gl_list_iterator_free (&iter);
738 if (!found)
739 /* descriptors should already contain fd. */
740 abort ();
742 if (mt) gl_lock_unlock (descriptors_lock);
744 errno = saved_errno;
745 return result;
748 static int
749 fclose_variant_temp (FILE *fp, int (*fclose_variant) (FILE *))
751 int fd = fileno (fp);
753 int result = 0;
754 int saved_errno = 0;
756 bool mt = gl_multithreaded ();
758 if (mt) gl_lock_lock (descriptors_lock);
760 gl_list_t list = descriptors;
761 if (list == NULL)
762 /* descriptors should already contain fd. */
763 abort ();
765 /* Search through the list, and clean it up on the fly. */
766 bool found = false;
767 gl_list_iterator_t iter = gl_list_iterator (list);
768 const void *elt;
769 gl_list_node_t node;
770 if (gl_list_iterator_next (&iter, &elt, &node))
771 for (;;)
773 struct closeable_fd *element = (struct closeable_fd *) elt;
775 /* Close the file descriptor and the stream, avoiding races with the
776 signal handler. */
777 if (element->fd == fd)
779 found = true;
780 result = asyncsafe_fclose_variant (element, fp, fclose_variant);
781 saved_errno = errno;
784 bool free_this_node = element->done;
785 struct closeable_fd *element_to_free = element;
786 gl_list_node_t node_to_free = node;
788 bool have_next = gl_list_iterator_next (&iter, &elt, &node);
790 if (free_this_node)
792 free (element_to_free);
793 gl_list_remove_node (list, node_to_free);
796 if (!have_next)
797 break;
799 gl_list_iterator_free (&iter);
800 if (!found)
801 /* descriptors should have contained fd. */
802 abort ();
804 if (mt) gl_lock_unlock (descriptors_lock);
806 errno = saved_errno;
807 return result;
810 /* Close a temporary file.
811 FP must have been returned by fopen_temp, or by fdopen on a file descriptor
812 returned by open_temp or gen_register_open_temp.
813 Unregisters the previously registered file descriptor. */
815 fclose_temp (FILE *fp)
817 return fclose_variant_temp (fp, fclose);
820 #if GNULIB_FWRITEERROR
821 /* Like fwriteerror.
822 FP must have been returned by fopen_temp, or by fdopen on a file descriptor
823 returned by open_temp or gen_register_open_temp.
824 Unregisters the previously registered file descriptor. */
826 fwriteerror_temp (FILE *fp)
828 return fclose_variant_temp (fp, fwriteerror);
830 #endif
832 #if GNULIB_CLOSE_STREAM
833 /* Like close_stream.
834 FP must have been returned by fopen_temp, or by fdopen on a file descriptor
835 returned by open_temp or gen_register_open_temp.
836 Unregisters the previously registered file descriptor. */
838 close_stream_temp (FILE *fp)
840 return fclose_variant_temp (fp, close_stream);
842 #endif