1 /* Temporary directories and temporary files with automatic cleanup.
2 Copyright (C) 2001, 2003, 2006-2007, 2009-2023 Free Software Foundation,
3 Inc.
4 Written by Bruno Haible <bruno@clisp.org>, 2006.
5
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.
10
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.
15
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/>. */
18
19 #include <config.h>
20
21 /* Specification. */
22 #include "clean-temp.h"
23
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>
31
32 #if defined _WIN32 && ! defined __CYGWIN__
33 # define WIN32_LEAN_AND_MEAN /* avoid including junk */
34 # include <windows.h>
35 #endif
36
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
67
68 #define _(str) gettext (str)
69
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
75
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
83
84
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)
88
89 /* Lock that protects the descriptors list from concurrent modification in
90 different threads. */
91 gl_lock_define_initialized (static, descriptors_lock)
92
93
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 *))
100 {
101 if (fileno (fp) != element->fd)
102 abort ();
103
104 /* Flush buffered data first, to minimize the duration of the spin lock. */
105 fflush (fp);
106
107 sigset_t saved_mask;
108 int ret;
109 int saved_errno;
110
111 asyncsafe_spin_lock (&element->lock, get_fatal_signal_set (), &saved_mask);
112 if (!element->closed)
113 {
114 ret = fclose_variant (fp); /* invokes close (element->fd) */
115 saved_errno = errno;
116 element->closed = true;
117 }
118 else
119 {
120 ret = 0;
121 saved_errno = 0;
122 }
123 asyncsafe_spin_unlock (&element->lock, &saved_mask);
124 element->done = true;
125
126 errno = saved_errno;
127 return ret;
128 }
129
130
131 /* ========= Temporary directories and temporary files inside them ========= */
132
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)
145 {
146 bool mt = gl_multithreaded ();
147
148 if (mt) gl_lock_lock (dir_cleanup_list_lock);
149
150 struct tempdir * volatile *tmpdirp = NULL;
151 struct tempdir *tmpdir;
152 size_t i;
153 char *xtemplate;
154 char *tmpdirname;
155
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)
160 {
161 tmpdirp = &dir_cleanup_list.tempdir_list[i];
162 break;
163 }
164 if (tmpdirp == NULL)
165 {
166 /* See whether the array needs to be extended. */
167 if (dir_cleanup_list.tempdir_count == dir_cleanup_list.tempdir_allocated)
168 {
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);
176
177 if (old_allocated == 0)
178 {
179 /* First use of this facility. */
180 if (clean_temp_init () < 0)
181 xalloc_die ();
182 }
183 else
184 {
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;
189
190 for (k = 0; k < old_allocated; k++)
191 new_array[k] = old_array[k];
192 }
193
194 dir_cleanup_list.tempdir_list = new_array;
195 dir_cleanup_list.tempdir_allocated = new_allocated;
196
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
207 }
208
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++;
214 }
215
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);
228
229 /* Create the temporary directory. */
230 xtemplate = (char *) xmalloca (PATH_MAX);
231 if (path_search (xtemplate, PATH_MAX, parentdir, prefix, parentdir == NULL))
232 {
233 error (0, errno,
234 _("cannot find a temporary directory, try setting $TMPDIR"));
235 goto quit;
236 }
237 block_fatal_signals ();
238 tmpdirname = mkdtemp (xtemplate);
239 int saved_errno = errno;
240 if (tmpdirname != NULL)
241 {
242 tmpdir->dirname = tmpdirname;
243 *tmpdirp = tmpdir;
244 }
245 unblock_fatal_signals ();
246 if (tmpdirname == NULL)
247 {
248 error (0, saved_errno,
249 _("cannot create a temporary directory using template \"%s\""),
250 xtemplate);
251 goto quit;
252 }
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;
261
262 quit:
263 if (mt) gl_lock_unlock (dir_cleanup_list_lock);
264 freea (xtemplate);
265 return NULL;
266 }
267
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)
274 {
275 struct tempdir *tmpdir = (struct tempdir *)dir;
276 bool mt = gl_multithreaded ();
277
278 if (mt) gl_lock_lock (dir_cleanup_list_lock);
279
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));
283
284 if (mt) gl_lock_unlock (dir_cleanup_list_lock);
285 }
286
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)
293 {
294 struct tempdir *tmpdir = (struct tempdir *)dir;
295 bool mt = gl_multithreaded ();
296
297 if (mt) gl_lock_lock (dir_cleanup_list_lock);
298
299 gl_list_t list = tmpdir->files;
300 gl_list_node_t node;
301
302 node = gl_list_search (list, absolute_file_name);
303 if (node != NULL)
304 {
305 char *old_string = (char *) gl_list_node_value (list, node);
306
307 gl_list_remove_node (list, node);
308 free (old_string);
309 }
310
311 if (mt) gl_lock_unlock (dir_cleanup_list_lock);
312 }
313
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)
320 {
321 struct tempdir *tmpdir = (struct tempdir *)dir;
322 bool mt = gl_multithreaded ();
323
324 if (mt) gl_lock_lock (dir_cleanup_list_lock);
325
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));
329
330 if (mt) gl_lock_unlock (dir_cleanup_list_lock);
331 }
332
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)
340 {
341 struct tempdir *tmpdir = (struct tempdir *)dir;
342 bool mt = gl_multithreaded ();
343
344 if (mt) gl_lock_lock (dir_cleanup_list_lock);
345
346 gl_list_t list = tmpdir->subdirs;
347 gl_list_node_t node;
348
349 node = gl_list_search (list, absolute_dir_name);
350 if (node != NULL)
351 {
352 char *old_string = (char *) gl_list_node_value (list, node);
353
354 gl_list_remove_node (list, node);
355 free (old_string);
356 }
357
358 if (mt) gl_lock_unlock (dir_cleanup_list_lock);
359 }
360
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)
365 {
366 if (rmdir (absolute_dir_name) < 0 && cleanup_verbose
367 && errno != ENOENT)
368 {
369 error (0, errno,
370 _("cannot remove temporary directory %s"), absolute_dir_name);
371 return -1;
372 }
373 return 0;
374 }
375
376 /* Remove the given ABSOLUTE_FILE_NAME and unregister it.
377 Return 0 upon success, or -1 if there was some problem. */
378 int
379 cleanup_temp_file (struct temp_dir *dir,
380 const char *absolute_file_name)
381 {
382 int err;
383
384 err = clean_temp_unlink (absolute_file_name, dir->cleanup_verbose);
385 unregister_temp_file (dir, absolute_file_name);
386
387 return err;
388 }
389
390 /* Remove the given ABSOLUTE_DIR_NAME and unregister it.
391 Return 0 upon success, or -1 if there was some problem. */
392 int
393 cleanup_temp_subdir (struct temp_dir *dir,
394 const char *absolute_dir_name)
395 {
396 int err;
397
398 err = do_rmdir (absolute_dir_name, dir->cleanup_verbose);
399 unregister_temp_subdir (dir, absolute_dir_name);
400
401 return err;
402 }
403
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. */
407 int
408 cleanup_temp_dir_contents (struct temp_dir *dir)
409 {
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;
416
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))
421 {
422 char *file = (char *) element;
423
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);
428 }
429 gl_list_iterator_free (&iter);
430
431 /* Then cleanup the subdirectories. */
432 list = tmpdir->subdirs;
433 iter = gl_list_iterator (list);
434 while (gl_list_iterator_next (&iter, &element, &node))
435 {
436 char *subdir = (char *) element;
437
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);
442 }
443 gl_list_iterator_free (&iter);
444
445 return err;
446 }
447
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. */
451 int
452 cleanup_temp_dir (struct temp_dir *dir)
453 {
454 bool mt = gl_multithreaded ();
455
456 if (mt) gl_lock_lock (dir_cleanup_list_lock);
457
458 struct tempdir *tmpdir = (struct tempdir *)dir;
459 int err = 0;
460 size_t i;
461
462 err |= cleanup_temp_dir_contents (dir);
463 err |= do_rmdir (tmpdir->dirname, dir->cleanup_verbose);
464
465 for (i = 0; i < dir_cleanup_list.tempdir_count; i++)
466 if (dir_cleanup_list.tempdir_list[i] == tmpdir)
467 {
468 /* Remove dir_cleanup_list.tempdir_list[i]. */
469 if (i + 1 == dir_cleanup_list.tempdir_count)
470 {
471 while (i > 0 && dir_cleanup_list.tempdir_list[i - 1] == NULL)
472 i--;
473 dir_cleanup_list.tempdir_count = i;
474 }
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;
485 }
486
487 /* The user passed an invalid DIR argument. */
488 abort ();
489 }
490
491
492 /* ================== Opening and closing temporary files ================== */
493
494 #if defined _WIN32 && ! defined __CYGWIN__
495
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. */
501
502 static bool
503 supports_delete_on_close ()
504 {
505 static int known; /* 1 = yes, -1 = no, 0 = unknown */
506 if (!known)
507 {
508 OSVERSIONINFO v;
509
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);
514
515 if (GetVersionEx (&v))
516 known = (v.dwPlatformId == VER_PLATFORM_WIN32_NT ? 1 : -1);
517 else
518 known = -1;
519 }
520 return (known > 0);
521 }
522
523 #endif
524
525
526 /* Register a file descriptor to be closed. */
527 static void
528 register_fd (int fd)
529 {
530 bool mt = gl_multithreaded ();
531
532 if (mt) gl_lock_lock (descriptors_lock);
533
534 if (descriptors == NULL)
535 descriptors = gl_list_create_empty (GL_LINKED_LIST, NULL, NULL, NULL,
536 false);
537
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;
543
544 gl_list_add_first (descriptors, element);
545
546 if (mt) gl_lock_unlock (descriptors_lock);
547 }
548
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. */
554 int
555 open_temp (const char *file_name, int flags, mode_t mode, bool delete_on_close)
556 {
557 int fd;
558 int saved_errno;
559
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;
576 }
577
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)
585 {
586 FILE *fp;
587 int saved_errno;
588
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 ())
595 {
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);
600
601 fp = fopen (file_name, augmented_mode);
602 saved_errno = errno;
603
604 freea (augmented_mode);
605 }
606 else
607 #endif
608 {
609 fp = fopen (file_name, mode);
610 saved_errno = errno;
611 }
612 if (fp != NULL)
613 {
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);
621 }
622 unblock_fatal_signals ();
623 errno = saved_errno;
624 return fp;
625 }
626
627 #if GNULIB_TEMPNAME
628
629 struct try_create_file_params
630 {
631 int flags;
632 mode_t mode;
633 };
634
635 static int
636 try_create_file (char *file_name_tmpl, void *params_)
637 {
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);
642 }
643
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. */
652 int
653 gen_register_open_temp (char *file_name_tmpl, int suffixlen,
654 int flags, mode_t mode)
655 {
656 block_fatal_signals ();
657
658 struct try_create_file_params params;
659 params.flags = flags;
660 params.mode = mode;
661
662 int fd = try_tempname (file_name_tmpl, suffixlen, ¶ms, try_create_file);
663
664 int saved_errno = errno;
665 if (fd >= 0)
666 {
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 ();
672 }
673 unblock_fatal_signals ();
674 errno = saved_errno;
675 return fd;
676 }
677
678 #endif
679
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. */
683 int
684 close_temp (int fd)
685 {
686 if (fd < 0)
687 return close (fd);
688
689 clean_temp_init_asyncsafe_close ();
690
691 int result = 0;
692 int saved_errno = 0;
693
694 bool mt = gl_multithreaded ();
695
696 if (mt) gl_lock_lock (descriptors_lock);
697
698 gl_list_t list = descriptors;
699 if (list == NULL)
700 /* descriptors should already contain fd. */
701 abort ();
702
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 (;;)
710 {
711 struct closeable_fd *element = (struct closeable_fd *) elt;
712
713 /* Close the file descriptor, avoiding races with the signal
714 handler. */
715 if (element->fd == fd)
716 {
717 found = true;
718 result = clean_temp_asyncsafe_close (element);
719 saved_errno = errno;
720 }
721
722 bool free_this_node = element->done;
723 struct closeable_fd *element_to_free = element;
724 gl_list_node_t node_to_free = node;
725
726 bool have_next = gl_list_iterator_next (&iter, &elt, &node);
727
728 if (free_this_node)
729 {
730 free (element_to_free);
731 gl_list_remove_node (list, node_to_free);
732 }
733
734 if (!have_next)
735 break;
736 }
737 gl_list_iterator_free (&iter);
738 if (!found)
739 /* descriptors should already contain fd. */
740 abort ();
741
742 if (mt) gl_lock_unlock (descriptors_lock);
743
744 errno = saved_errno;
745 return result;
746 }
747
748 static int
749 fclose_variant_temp (FILE *fp, int (*fclose_variant) (FILE *))
750 {
751 int fd = fileno (fp);
752
753 int result = 0;
754 int saved_errno = 0;
755
756 bool mt = gl_multithreaded ();
757
758 if (mt) gl_lock_lock (descriptors_lock);
759
760 gl_list_t list = descriptors;
761 if (list == NULL)
762 /* descriptors should already contain fd. */
763 abort ();
764
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 (;;)
772 {
773 struct closeable_fd *element = (struct closeable_fd *) elt;
774
775 /* Close the file descriptor and the stream, avoiding races with the
776 signal handler. */
777 if (element->fd == fd)
778 {
779 found = true;
780 result = asyncsafe_fclose_variant (element, fp, fclose_variant);
781 saved_errno = errno;
782 }
783
784 bool free_this_node = element->done;
785 struct closeable_fd *element_to_free = element;
786 gl_list_node_t node_to_free = node;
787
788 bool have_next = gl_list_iterator_next (&iter, &elt, &node);
789
790 if (free_this_node)
791 {
792 free (element_to_free);
793 gl_list_remove_node (list, node_to_free);
794 }
795
796 if (!have_next)
797 break;
798 }
799 gl_list_iterator_free (&iter);
800 if (!found)
801 /* descriptors should have contained fd. */
802 abort ();
803
804 if (mt) gl_lock_unlock (descriptors_lock);
805
806 errno = saved_errno;
807 return result;
808 }
809
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. */
814 int
815 fclose_temp (FILE *fp)
816 {
817 return fclose_variant_temp (fp, fclose);
818 }
819
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. */
825 int
826 fwriteerror_temp (FILE *fp)
827 {
828 return fclose_variant_temp (fp, fwriteerror);
829 }
830 #endif
831
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. */
837 int
838 close_stream_temp (FILE *fp)
839 {
840 return fclose_variant_temp (fp, close_stream);
841 }
842 #endif