Bump to m4 1.4.19
[platform/upstream/m4.git] / lib / clean-temp.c
1 /* Temporary directories and temporary files with automatic cleanup.
2    Copyright (C) 2001, 2003, 2006-2007, 2009-2021 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 <stdbool.h>
28 #include <stdio.h>
29 #include <stdlib.h>
30 #include <string.h>
31 #include <unistd.h>
32
33 #if defined _WIN32 && ! defined __CYGWIN__
34 # define WIN32_LEAN_AND_MEAN  /* avoid including junk */
35 # include <windows.h>
36 #endif
37
38 #include "clean-temp-simple.h"
39 #include "clean-temp-private.h"
40 #include "error.h"
41 #include "fatal-signal.h"
42 #include "asyncsafe-spin.h"
43 #include "pathmax.h"
44 #include "tmpdir.h"
45 #include "xalloc.h"
46 #include "xmalloca.h"
47 #include "glthread/lock.h"
48 #include "thread-optim.h"
49 #include "gl_xlist.h"
50 #include "gl_linkedhash_list.h"
51 #include "gl_linked_list.h"
52 #include "gettext.h"
53 #if GNULIB_TEMPNAME
54 # include "tempname.h"
55 #endif
56 #if GNULIB_FWRITEERROR
57 # include "fwriteerror.h"
58 #endif
59 #if GNULIB_CLOSE_STREAM
60 # include "close-stream.h"
61 #endif
62 #if GNULIB_FCNTL_SAFER
63 # include "fcntl--.h"
64 #endif
65 #if GNULIB_FOPEN_SAFER
66 # include "stdio--.h"
67 #endif
68
69 #define _(str) gettext (str)
70
71 /* GNU Hurd doesn't have PATH_MAX.  Use a fallback.
72    Temporary directory names are usually not that long.  */
73 #ifndef PATH_MAX
74 # define PATH_MAX 1024
75 #endif
76
77 #if defined _WIN32 && ! defined __CYGWIN__
78 /* Don't assume that UNICODE is not defined.  */
79 # undef OSVERSIONINFO
80 # define OSVERSIONINFO OSVERSIONINFOA
81 # undef GetVersionEx
82 # define GetVersionEx GetVersionExA
83 #endif
84
85
86 /* Lock that protects the dir_cleanup_list from concurrent modification in
87    different threads.  */
88 gl_lock_define_initialized (static, dir_cleanup_list_lock)
89
90 /* Lock that protects the descriptors list from concurrent modification in
91    different threads.  */
92 gl_lock_define_initialized (static, descriptors_lock)
93
94
95 /* Close a file descriptor and the stream that contains it.
96    Avoids race conditions with signal-handler code that might want to close the
97    same file descriptor.  */
98 static int
99 asyncsafe_fclose_variant (struct closeable_fd *element, FILE *fp,
100                           int (*fclose_variant) (FILE *))
101 {
102   if (fileno (fp) != element->fd)
103     abort ();
104
105   /* Flush buffered data first, to minimize the duration of the spin lock.  */
106   fflush (fp);
107
108   sigset_t saved_mask;
109   int ret;
110   int saved_errno;
111
112   asyncsafe_spin_lock (&element->lock, get_fatal_signal_set (), &saved_mask);
113   if (!element->closed)
114     {
115       ret = fclose_variant (fp); /* invokes close (element->fd) */
116       saved_errno = errno;
117       element->closed = true;
118     }
119   else
120     {
121       ret = 0;
122       saved_errno = 0;
123     }
124   asyncsafe_spin_unlock (&element->lock, &saved_mask);
125   element->done = true;
126
127   errno = saved_errno;
128   return ret;
129 }
130
131
132 /* ========= Temporary directories and temporary files inside them ========= */
133
134 /* Create a temporary directory.
135    PREFIX is used as a prefix for the name of the temporary directory. It
136    should be short and still give an indication about the program.
137    PARENTDIR can be used to specify the parent directory; if NULL, a default
138    parent directory is used (either $TMPDIR or /tmp or similar).
139    CLEANUP_VERBOSE determines whether errors during explicit cleanup are
140    reported to standard error.
141    Return a fresh 'struct temp_dir' on success.  Upon error, an error message
142    is shown and NULL is returned.  */
143 struct temp_dir *
144 create_temp_dir (const char *prefix, const char *parentdir,
145                  bool cleanup_verbose)
146 {
147   bool mt = gl_multithreaded ();
148
149   if (mt) gl_lock_lock (dir_cleanup_list_lock);
150
151   struct tempdir * volatile *tmpdirp = NULL;
152   struct tempdir *tmpdir;
153   size_t i;
154   char *xtemplate;
155   char *tmpdirname;
156
157   /* See whether it can take the slot of an earlier temporary directory
158      already cleaned up.  */
159   for (i = 0; i < dir_cleanup_list.tempdir_count; i++)
160     if (dir_cleanup_list.tempdir_list[i] == NULL)
161       {
162         tmpdirp = &dir_cleanup_list.tempdir_list[i];
163         break;
164       }
165   if (tmpdirp == NULL)
166     {
167       /* See whether the array needs to be extended.  */
168       if (dir_cleanup_list.tempdir_count == dir_cleanup_list.tempdir_allocated)
169         {
170           /* Note that we cannot use xrealloc(), because then the cleanup()
171              function could access an already deallocated array.  */
172           struct tempdir * volatile *old_array = dir_cleanup_list.tempdir_list;
173           size_t old_allocated = dir_cleanup_list.tempdir_allocated;
174           size_t new_allocated = 2 * dir_cleanup_list.tempdir_allocated + 1;
175           struct tempdir * volatile *new_array =
176             XNMALLOC (new_allocated, struct tempdir * volatile);
177
178           if (old_allocated == 0)
179             {
180               /* First use of this facility.  */
181               if (clean_temp_init () < 0)
182                 xalloc_die ();
183             }
184           else
185             {
186               /* Don't use memcpy() here, because memcpy takes non-volatile
187                  arguments and is therefore not guaranteed to complete all
188                  memory stores before the next statement.  */
189               size_t k;
190
191               for (k = 0; k < old_allocated; k++)
192                 new_array[k] = old_array[k];
193             }
194
195           dir_cleanup_list.tempdir_list = new_array;
196           dir_cleanup_list.tempdir_allocated = new_allocated;
197
198           /* Now we can free the old array.  */
199           /* No, we can't do that.  If cleanup_action is running in a different
200              thread and has already fetched the tempdir_list pointer (getting
201              old_array) but not yet accessed its i-th element, that thread may
202              crash when accessing an element of the already freed old_array
203              array.  */
204           #if 0
205           if (old_array != NULL)
206             free ((struct tempdir **) old_array);
207           #endif
208         }
209
210       tmpdirp = &dir_cleanup_list.tempdir_list[dir_cleanup_list.tempdir_count];
211       /* Initialize *tmpdirp before incrementing tempdir_count, so that
212          cleanup() will skip this entry before it is fully initialized.  */
213       *tmpdirp = NULL;
214       dir_cleanup_list.tempdir_count++;
215     }
216
217   /* Initialize a 'struct tempdir'.  */
218   tmpdir = XMALLOC (struct tempdir);
219   tmpdir->dirname = NULL;
220   tmpdir->cleanup_verbose = cleanup_verbose;
221   tmpdir->subdirs =
222     gl_list_create_empty (GL_LINKEDHASH_LIST,
223                           clean_temp_string_equals, clean_temp_string_hash,
224                           NULL, false);
225   tmpdir->files =
226     gl_list_create_empty (GL_LINKEDHASH_LIST,
227                           clean_temp_string_equals, clean_temp_string_hash,
228                           NULL, false);
229
230   /* Create the temporary directory.  */
231   xtemplate = (char *) xmalloca (PATH_MAX);
232   if (path_search (xtemplate, PATH_MAX, parentdir, prefix, parentdir == NULL))
233     {
234       error (0, errno,
235              _("cannot find a temporary directory, try setting $TMPDIR"));
236       goto quit;
237     }
238   block_fatal_signals ();
239   tmpdirname = mkdtemp (xtemplate);
240   int saved_errno = errno;
241   if (tmpdirname != NULL)
242     {
243       tmpdir->dirname = tmpdirname;
244       *tmpdirp = tmpdir;
245     }
246   unblock_fatal_signals ();
247   if (tmpdirname == NULL)
248     {
249       error (0, saved_errno,
250              _("cannot create a temporary directory using template \"%s\""),
251              xtemplate);
252       goto quit;
253     }
254   /* Replace tmpdir->dirname with a copy that has indefinite extent.
255      We cannot do this inside the block_fatal_signals/unblock_fatal_signals
256      block because then the cleanup handler would not remove the directory
257      if xstrdup fails.  */
258   tmpdir->dirname = xstrdup (tmpdirname);
259   if (mt) gl_lock_unlock (dir_cleanup_list_lock);
260   freea (xtemplate);
261   return (struct temp_dir *) tmpdir;
262
263  quit:
264   if (mt) gl_lock_unlock (dir_cleanup_list_lock);
265   freea (xtemplate);
266   return NULL;
267 }
268
269 /* Register the given ABSOLUTE_FILE_NAME as being a file inside DIR, that
270    needs to be removed before DIR can be removed.
271    Should be called before the file ABSOLUTE_FILE_NAME is created.  */
272 void
273 register_temp_file (struct temp_dir *dir,
274                     const char *absolute_file_name)
275 {
276   struct tempdir *tmpdir = (struct tempdir *)dir;
277   bool mt = gl_multithreaded ();
278
279   if (mt) gl_lock_lock (dir_cleanup_list_lock);
280
281   /* Add absolute_file_name to tmpdir->files, without duplicates.  */
282   if (gl_list_search (tmpdir->files, absolute_file_name) == NULL)
283     gl_list_add_first (tmpdir->files, xstrdup (absolute_file_name));
284
285   if (mt) gl_lock_unlock (dir_cleanup_list_lock);
286 }
287
288 /* Unregister the given ABSOLUTE_FILE_NAME as being a file inside DIR, that
289    needs to be removed before DIR can be removed.
290    Should be called when the file ABSOLUTE_FILE_NAME could not be created.  */
291 void
292 unregister_temp_file (struct temp_dir *dir,
293                       const char *absolute_file_name)
294 {
295   struct tempdir *tmpdir = (struct tempdir *)dir;
296   bool mt = gl_multithreaded ();
297
298   if (mt) gl_lock_lock (dir_cleanup_list_lock);
299
300   gl_list_t list = tmpdir->files;
301   gl_list_node_t node;
302
303   node = gl_list_search (list, absolute_file_name);
304   if (node != NULL)
305     {
306       char *old_string = (char *) gl_list_node_value (list, node);
307
308       gl_list_remove_node (list, node);
309       free (old_string);
310     }
311
312   if (mt) gl_lock_unlock (dir_cleanup_list_lock);
313 }
314
315 /* Register the given ABSOLUTE_DIR_NAME as being a subdirectory inside DIR,
316    that needs to be removed before DIR can be removed.
317    Should be called before the subdirectory ABSOLUTE_DIR_NAME is created.  */
318 void
319 register_temp_subdir (struct temp_dir *dir,
320                       const char *absolute_dir_name)
321 {
322   struct tempdir *tmpdir = (struct tempdir *)dir;
323   bool mt = gl_multithreaded ();
324
325   if (mt) gl_lock_lock (dir_cleanup_list_lock);
326
327   /* Add absolute_dir_name to tmpdir->subdirs, without duplicates.  */
328   if (gl_list_search (tmpdir->subdirs, absolute_dir_name) == NULL)
329     gl_list_add_first (tmpdir->subdirs, xstrdup (absolute_dir_name));
330
331   if (mt) gl_lock_unlock (dir_cleanup_list_lock);
332 }
333
334 /* Unregister the given ABSOLUTE_DIR_NAME as being a subdirectory inside DIR,
335    that needs to be removed before DIR can be removed.
336    Should be called when the subdirectory ABSOLUTE_DIR_NAME could not be
337    created.  */
338 void
339 unregister_temp_subdir (struct temp_dir *dir,
340                         const char *absolute_dir_name)
341 {
342   struct tempdir *tmpdir = (struct tempdir *)dir;
343   bool mt = gl_multithreaded ();
344
345   if (mt) gl_lock_lock (dir_cleanup_list_lock);
346
347   gl_list_t list = tmpdir->subdirs;
348   gl_list_node_t node;
349
350   node = gl_list_search (list, absolute_dir_name);
351   if (node != NULL)
352     {
353       char *old_string = (char *) gl_list_node_value (list, node);
354
355       gl_list_remove_node (list, node);
356       free (old_string);
357     }
358
359   if (mt) gl_lock_unlock (dir_cleanup_list_lock);
360 }
361
362 /* Remove a directory, with optional error message.
363    Return 0 upon success, or -1 if there was some problem.  */
364 static int
365 do_rmdir (const char *absolute_dir_name, bool cleanup_verbose)
366 {
367   if (rmdir (absolute_dir_name) < 0 && cleanup_verbose
368       && errno != ENOENT)
369     {
370       error (0, errno,
371              _("cannot remove temporary directory %s"), absolute_dir_name);
372       return -1;
373     }
374   return 0;
375 }
376
377 /* Remove the given ABSOLUTE_FILE_NAME and unregister it.
378    Return 0 upon success, or -1 if there was some problem.  */
379 int
380 cleanup_temp_file (struct temp_dir *dir,
381                    const char *absolute_file_name)
382 {
383   int err;
384
385   err = clean_temp_unlink (absolute_file_name, dir->cleanup_verbose);
386   unregister_temp_file (dir, absolute_file_name);
387
388   return err;
389 }
390
391 /* Remove the given ABSOLUTE_DIR_NAME and unregister it.
392    Return 0 upon success, or -1 if there was some problem.  */
393 int
394 cleanup_temp_subdir (struct temp_dir *dir,
395                      const char *absolute_dir_name)
396 {
397   int err;
398
399   err = do_rmdir (absolute_dir_name, dir->cleanup_verbose);
400   unregister_temp_subdir (dir, absolute_dir_name);
401
402   return err;
403 }
404
405 /* Remove all registered files and subdirectories inside DIR.
406    Only to be called with dir_cleanup_list_lock locked.
407    Return 0 upon success, or -1 if there was some problem.  */
408 int
409 cleanup_temp_dir_contents (struct temp_dir *dir)
410 {
411   struct tempdir *tmpdir = (struct tempdir *)dir;
412   int err = 0;
413   gl_list_t list;
414   gl_list_iterator_t iter;
415   const void *element;
416   gl_list_node_t node;
417
418   /* First cleanup the files in the subdirectories.  */
419   list = tmpdir->files;
420   iter = gl_list_iterator (list);
421   while (gl_list_iterator_next (&iter, &element, &node))
422     {
423       char *file = (char *) element;
424
425       err |= clean_temp_unlink (file, dir->cleanup_verbose);
426       gl_list_remove_node (list, node);
427       /* Now only we can free file.  */
428       free (file);
429     }
430   gl_list_iterator_free (&iter);
431
432   /* Then cleanup the subdirectories.  */
433   list = tmpdir->subdirs;
434   iter = gl_list_iterator (list);
435   while (gl_list_iterator_next (&iter, &element, &node))
436     {
437       char *subdir = (char *) element;
438
439       err |= do_rmdir (subdir, dir->cleanup_verbose);
440       gl_list_remove_node (list, node);
441       /* Now only we can free subdir.  */
442       free (subdir);
443     }
444   gl_list_iterator_free (&iter);
445
446   return err;
447 }
448
449 /* Remove all registered files and subdirectories inside DIR and DIR itself.
450    DIR cannot be used any more after this call.
451    Return 0 upon success, or -1 if there was some problem.  */
452 int
453 cleanup_temp_dir (struct temp_dir *dir)
454 {
455   bool mt = gl_multithreaded ();
456
457   if (mt) gl_lock_lock (dir_cleanup_list_lock);
458
459   struct tempdir *tmpdir = (struct tempdir *)dir;
460   int err = 0;
461   size_t i;
462
463   err |= cleanup_temp_dir_contents (dir);
464   err |= do_rmdir (tmpdir->dirname, dir->cleanup_verbose);
465
466   for (i = 0; i < dir_cleanup_list.tempdir_count; i++)
467     if (dir_cleanup_list.tempdir_list[i] == tmpdir)
468       {
469         /* Remove dir_cleanup_list.tempdir_list[i].  */
470         if (i + 1 == dir_cleanup_list.tempdir_count)
471           {
472             while (i > 0 && dir_cleanup_list.tempdir_list[i - 1] == NULL)
473               i--;
474             dir_cleanup_list.tempdir_count = i;
475           }
476         else
477           dir_cleanup_list.tempdir_list[i] = NULL;
478         /* Now only we can free the tmpdir->dirname, tmpdir->subdirs,
479            tmpdir->files, and tmpdir itself.  */
480         gl_list_free (tmpdir->files);
481         gl_list_free (tmpdir->subdirs);
482         free (tmpdir->dirname);
483         free (tmpdir);
484         if (mt) gl_lock_unlock (dir_cleanup_list_lock);
485         return err;
486       }
487
488   /* The user passed an invalid DIR argument.  */
489   abort ();
490 }
491
492
493 /* ================== Opening and closing temporary files ================== */
494
495 #if defined _WIN32 && ! defined __CYGWIN__
496
497 /* On Windows, opening a file with _O_TEMPORARY has the effect of passing
498    the FILE_FLAG_DELETE_ON_CLOSE flag to CreateFile(), which has the effect
499    of deleting the file when it is closed - even when the program crashes.
500    But (according to the Cygwin sources) it works only on Windows NT or newer.
501    So we cache the info whether we are running on Windows NT or newer.  */
502
503 static bool
504 supports_delete_on_close ()
505 {
506   static int known; /* 1 = yes, -1 = no, 0 = unknown */
507   if (!known)
508     {
509       OSVERSIONINFO v;
510
511       /* According to
512          <https://docs.microsoft.com/en-us/windows/desktop/api/sysinfoapi/nf-sysinfoapi-getversionexa>
513          this structure must be initialized as follows:  */
514       v.dwOSVersionInfoSize = sizeof (OSVERSIONINFO);
515
516       if (GetVersionEx (&v))
517         known = (v.dwPlatformId == VER_PLATFORM_WIN32_NT ? 1 : -1);
518       else
519         known = -1;
520     }
521   return (known > 0);
522 }
523
524 #endif
525
526
527 /* Register a file descriptor to be closed.  */
528 static void
529 register_fd (int fd)
530 {
531   bool mt = gl_multithreaded ();
532
533   if (mt) gl_lock_lock (descriptors_lock);
534
535   if (descriptors == NULL)
536     descriptors = gl_list_create_empty (GL_LINKED_LIST, NULL, NULL, NULL,
537                                         false);
538
539   struct closeable_fd *element = XMALLOC (struct closeable_fd);
540   element->fd = fd;
541   element->closed = false;
542   asyncsafe_spin_init (&element->lock);
543   element->done = false;
544
545   gl_list_add_first (descriptors, element);
546
547   if (mt) gl_lock_unlock (descriptors_lock);
548 }
549
550 /* Open a temporary file in a temporary directory.
551    FILE_NAME must already have been passed to register_temp_file.
552    Registers the resulting file descriptor to be closed.
553    DELETE_ON_CLOSE indicates whether the file can be deleted when the resulting
554    file descriptor or stream is closed.  */
555 int
556 open_temp (const char *file_name, int flags, mode_t mode, bool delete_on_close)
557 {
558   int fd;
559   int saved_errno;
560
561   block_fatal_signals ();
562   /* Note: 'open' here is actually open() or open_safer().  */
563 #if defined _WIN32 && ! defined __CYGWIN__
564   /* Use _O_TEMPORARY when possible, to increase the chances that the
565      temporary file is removed when the process crashes.  */
566   if (delete_on_close && supports_delete_on_close ())
567     fd = open (file_name, flags | _O_TEMPORARY, mode);
568   else
569 #endif
570     fd = open (file_name, flags, mode);
571   saved_errno = errno;
572   if (fd >= 0)
573     register_fd (fd);
574   unblock_fatal_signals ();
575   errno = saved_errno;
576   return fd;
577 }
578
579 /* Open a temporary file in a temporary directory.
580    FILE_NAME must already have been passed to register_temp_file.
581    Registers the resulting file descriptor to be closed.
582    DELETE_ON_CLOSE indicates whether the file can be deleted when the resulting
583    file descriptor or stream is closed.  */
584 FILE *
585 fopen_temp (const char *file_name, const char *mode, bool delete_on_close)
586 {
587   FILE *fp;
588   int saved_errno;
589
590   block_fatal_signals ();
591   /* Note: 'fopen' here is actually fopen() or fopen_safer().  */
592 #if defined _WIN32 && ! defined __CYGWIN__
593   /* Use _O_TEMPORARY when possible, to increase the chances that the
594      temporary file is removed when the process crashes.  */
595   if (delete_on_close && supports_delete_on_close ())
596     {
597       size_t mode_len = strlen (mode);
598       char *augmented_mode = (char *) xmalloca (mode_len + 2);
599       memcpy (augmented_mode, mode, mode_len);
600       memcpy (augmented_mode + mode_len, "D", 2);
601
602       fp = fopen (file_name, augmented_mode);
603       saved_errno = errno;
604
605       freea (augmented_mode);
606     }
607   else
608 #endif
609     {
610       fp = fopen (file_name, mode);
611       saved_errno = errno;
612     }
613   if (fp != NULL)
614     {
615       /* It is sufficient to register fileno (fp) instead of the entire fp,
616          because at cleanup time there is no need to do an fflush (fp); a
617          close (fileno (fp)) will be enough.  */
618       int fd = fileno (fp);
619       if (!(fd >= 0))
620         abort ();
621       register_fd (fd);
622     }
623   unblock_fatal_signals ();
624   errno = saved_errno;
625   return fp;
626 }
627
628 #if GNULIB_TEMPNAME
629
630 struct try_create_file_params
631 {
632   int flags;
633   mode_t mode;
634 };
635
636 static int
637 try_create_file (char *file_name_tmpl, void *params_)
638 {
639   struct try_create_file_params *params = params_;
640   return open (file_name_tmpl,
641                (params->flags & ~O_ACCMODE) | O_RDWR | O_CREAT | O_EXCL,
642                params->mode);
643 }
644
645 /* Open a temporary file, generating its name based on FILE_NAME_TMPL.
646    FILE_NAME_TMPL must match the rules for mk[s]temp (i.e. end in "XXXXXX",
647    possibly with a suffix).  The name constructed does not exist at the time
648    of the call.  FILE_NAME_TMPL is overwritten with the result.
649    A safe choice for MODE is S_IRUSR | S_IWUSR, a.k.a. 0600.
650    Registers the file for deletion.
651    Opens the file, with the given FLAGS and mode MODE.
652    Registers the resulting file descriptor to be closed.  */
653 int
654 gen_register_open_temp (char *file_name_tmpl, int suffixlen,
655                         int flags, mode_t mode)
656 {
657   block_fatal_signals ();
658
659   struct try_create_file_params params;
660   params.flags = flags;
661   params.mode = mode;
662
663   int fd = try_tempname (file_name_tmpl, suffixlen, &params, try_create_file);
664
665   int saved_errno = errno;
666   if (fd >= 0)
667     {
668       if (clean_temp_init () < 0)
669         xalloc_die ();
670       register_fd (fd);
671       if (register_temporary_file (file_name_tmpl) < 0)
672         xalloc_die ();
673     }
674   unblock_fatal_signals ();
675   errno = saved_errno;
676   return fd;
677 }
678
679 #endif
680
681 /* Close a temporary file.
682    FD must have been returned by open_temp or gen_register_open_temp.
683    Unregisters the previously registered file descriptor.  */
684 int
685 close_temp (int fd)
686 {
687   if (fd < 0)
688     return close (fd);
689
690   clean_temp_init_asyncsafe_close ();
691
692   int result = 0;
693   int saved_errno = 0;
694
695   bool mt = gl_multithreaded ();
696
697   if (mt) gl_lock_lock (descriptors_lock);
698
699   gl_list_t list = descriptors;
700   if (list == NULL)
701     /* descriptors should already contain fd.  */
702     abort ();
703
704   /* Search through the list, and clean it up on the fly.  */
705   bool found = false;
706   gl_list_iterator_t iter = gl_list_iterator (list);
707   const void *elt;
708   gl_list_node_t node;
709   if (gl_list_iterator_next (&iter, &elt, &node))
710     for (;;)
711       {
712         struct closeable_fd *element = (struct closeable_fd *) elt;
713
714         /* Close the file descriptor, avoiding races with the signal
715            handler.  */
716         if (element->fd == fd)
717           {
718             found = true;
719             result = clean_temp_asyncsafe_close (element);
720             saved_errno = errno;
721           }
722
723         bool free_this_node = element->done;
724         struct closeable_fd *element_to_free = element;
725         gl_list_node_t node_to_free = node;
726
727         bool have_next = gl_list_iterator_next (&iter, &elt, &node);
728
729         if (free_this_node)
730           {
731             free (element_to_free);
732             gl_list_remove_node (list, node_to_free);
733           }
734
735         if (!have_next)
736           break;
737       }
738   gl_list_iterator_free (&iter);
739   if (!found)
740     /* descriptors should already contain fd.  */
741     abort ();
742
743   if (mt) gl_lock_unlock (descriptors_lock);
744
745   errno = saved_errno;
746   return result;
747 }
748
749 static int
750 fclose_variant_temp (FILE *fp, int (*fclose_variant) (FILE *))
751 {
752   int fd = fileno (fp);
753
754   int result = 0;
755   int saved_errno = 0;
756
757   bool mt = gl_multithreaded ();
758
759   if (mt) gl_lock_lock (descriptors_lock);
760
761   gl_list_t list = descriptors;
762   if (list == NULL)
763     /* descriptors should already contain fd.  */
764     abort ();
765
766   /* Search through the list, and clean it up on the fly.  */
767   bool found = false;
768   gl_list_iterator_t iter = gl_list_iterator (list);
769   const void *elt;
770   gl_list_node_t node;
771   if (gl_list_iterator_next (&iter, &elt, &node))
772     for (;;)
773       {
774         struct closeable_fd *element = (struct closeable_fd *) elt;
775
776         /* Close the file descriptor and the stream, avoiding races with the
777            signal handler.  */
778         if (element->fd == fd)
779           {
780             found = true;
781             result = asyncsafe_fclose_variant (element, fp, fclose_variant);
782             saved_errno = errno;
783           }
784
785         bool free_this_node = element->done;
786         struct closeable_fd *element_to_free = element;
787         gl_list_node_t node_to_free = node;
788
789         bool have_next = gl_list_iterator_next (&iter, &elt, &node);
790
791         if (free_this_node)
792           {
793             free (element_to_free);
794             gl_list_remove_node (list, node_to_free);
795           }
796
797         if (!have_next)
798           break;
799       }
800   gl_list_iterator_free (&iter);
801   if (!found)
802     /* descriptors should have contained fd.  */
803     abort ();
804
805   if (mt) gl_lock_unlock (descriptors_lock);
806
807   errno = saved_errno;
808   return result;
809 }
810
811 /* Close a temporary file.
812    FP must have been returned by fopen_temp, or by fdopen on a file descriptor
813    returned by open_temp or gen_register_open_temp.
814    Unregisters the previously registered file descriptor.  */
815 int
816 fclose_temp (FILE *fp)
817 {
818   return fclose_variant_temp (fp, fclose);
819 }
820
821 #if GNULIB_FWRITEERROR
822 /* Like fwriteerror.
823    FP must have been returned by fopen_temp, or by fdopen on a file descriptor
824    returned by open_temp or gen_register_open_temp.
825    Unregisters the previously registered file descriptor.  */
826 int
827 fwriteerror_temp (FILE *fp)
828 {
829   return fclose_variant_temp (fp, fwriteerror);
830 }
831 #endif
832
833 #if GNULIB_CLOSE_STREAM
834 /* Like close_stream.
835    FP must have been returned by fopen_temp, or by fdopen on a file descriptor
836    returned by open_temp or gen_register_open_temp.
837    Unregisters the previously registered file descriptor.  */
838 int
839 close_stream_temp (FILE *fp)
840 {
841   return fclose_variant_temp (fp, close_stream);
842 }
843 #endif