sys_stat: Fix 'implicit declaration of function' warning on OS/2 kLIBC.
[gnulib.git] / lib / clean-temp.c
blobe2ae3f27007d5b15b026f65c7987dae2b98bfaaf
1 /* Temporary directories and temporary files with automatic cleanup.
2 Copyright (C) 2001, 2003, 2006-2007, 2009-2019 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/>. */
20 #include <config.h>
22 /* Specification. */
23 #include "clean-temp.h"
25 #include <errno.h>
26 #include <fcntl.h>
27 #include <limits.h>
28 #include <stdbool.h>
29 #include <stdlib.h>
30 #include <string.h>
31 #include <unistd.h>
33 #if defined _WIN32 && ! defined __CYGWIN__
34 # define WIN32_LEAN_AND_MEAN /* avoid including junk */
35 # include <windows.h>
36 #endif
38 #include "error.h"
39 #include "fatal-signal.h"
40 #include "pathmax.h"
41 #include "tmpdir.h"
42 #include "xalloc.h"
43 #include "xmalloca.h"
44 #include "gl_xlist.h"
45 #include "gl_linkedhash_list.h"
46 #include "gettext.h"
47 #if GNULIB_FWRITEERROR
48 # include "fwriteerror.h"
49 #endif
50 #if GNULIB_CLOSE_STREAM
51 # include "close-stream.h"
52 #endif
53 #if GNULIB_FCNTL_SAFER
54 # include "fcntl--.h"
55 #endif
56 #if GNULIB_FOPEN_SAFER
57 # include "stdio--.h"
58 #endif
60 #define _(str) gettext (str)
62 /* GNU Hurd doesn't have PATH_MAX. Use a fallback.
63 Temporary directory names are usually not that long. */
64 #ifndef PATH_MAX
65 # define PATH_MAX 1024
66 #endif
68 #ifndef uintptr_t
69 # define uintptr_t unsigned long
70 #endif
73 /* The use of 'volatile' in the types below (and ISO C 99 section 5.1.2.3.(5))
74 ensure that while constructing or modifying the data structures, the field
75 values are written to memory in the order of the C statements. So the
76 signal handler can rely on these field values to be up to date. */
79 /* Registry for a single temporary directory.
80 'struct temp_dir' from the public header file overlaps with this. */
81 struct tempdir
83 /* The absolute pathname of the directory. */
84 char * volatile dirname;
85 /* Whether errors during explicit cleanup are reported to standard error. */
86 bool cleanup_verbose;
87 /* Absolute pathnames of subdirectories. */
88 gl_list_t /* <char *> */ volatile subdirs;
89 /* Absolute pathnames of files. */
90 gl_list_t /* <char *> */ volatile files;
93 /* List of all temporary directories. */
94 static struct
96 struct tempdir * volatile * volatile tempdir_list;
97 size_t volatile tempdir_count;
98 size_t tempdir_allocated;
99 } cleanup_list /* = { NULL, 0, 0 } */;
101 /* List of all open file descriptors to temporary files. */
102 static gl_list_t /* <int> */ volatile descriptors;
105 /* For the subdirs and for the files, we use a gl_list_t of type LINKEDHASH.
106 Why? We need a data structure that
108 1) Can contain an arbitrary number of 'char *' values. The strings
109 are compared via strcmp, not pointer comparison.
110 2) Has insertion and deletion operations that are fast: ideally O(1),
111 or possibly O(log n). This is important for GNU sort, which may
112 create a large number of temporary files.
113 3) Allows iteration through all elements from within a signal handler.
114 4) May or may not allow duplicates. It doesn't matter here, since
115 any file or subdir can only be removed once.
117 Criterion 1) would allow any gl_list_t or gl_oset_t implementation.
119 Criterion 2) leaves only GL_LINKEDHASH_LIST, GL_TREEHASH_LIST, or
120 GL_TREE_OSET.
122 Criterion 3) puts at disadvantage GL_TREEHASH_LIST and GL_TREE_OSET.
123 Namely, iteration through the elements of a binary tree requires access
124 to many ->left, ->right, ->parent pointers. However, the rebalancing
125 code for insertion and deletion in an AVL or red-black tree is so
126 complicated that we cannot assume that >left, ->right, ->parent pointers
127 are in a consistent state throughout these operations. Therefore, to
128 avoid a crash in the signal handler, all destructive operations to the
129 lists would have to be protected by a
130 block_fatal_signals ();
132 unblock_fatal_signals ();
133 pair. Which causes extra system calls.
135 Criterion 3) would also discourage GL_ARRAY_LIST and GL_CARRAY_LIST,
136 if they were not already excluded. Namely, these implementations use
137 xrealloc(), leaving a time window in which in the list->elements pointer
138 points to already deallocated memory. To avoid a crash in the signal
139 handler at such a moment, all destructive operations would have to
140 protected by block/unblock_fatal_signals (), in this case too.
142 A list of type GL_LINKEDHASH_LIST without duplicates fulfills all
143 requirements:
144 2) Insertion and deletion are O(1) on average.
145 3) The gl_list_iterator, gl_list_iterator_next implementations do
146 not trigger memory allocations, nor other system calls, and are
147 therefore safe to be called from a signal handler.
148 Furthermore, since SIGNAL_SAFE_LIST is defined, the implementation
149 of the destructive functions ensures that the list structure is
150 safe to be traversed at any moment, even when interrupted by an
151 asynchronous signal.
154 /* String equality and hash code functions used by the lists. */
156 static bool
157 string_equals (const void *x1, const void *x2)
159 const char *s1 = (const char *) x1;
160 const char *s2 = (const char *) x2;
161 return strcmp (s1, s2) == 0;
164 #define SIZE_BITS (sizeof (size_t) * CHAR_BIT)
166 /* A hash function for NUL-terminated char* strings using
167 the method described by Bruno Haible.
168 See https://www.haible.de/bruno/hashfunc.html. */
169 static size_t
170 string_hash (const void *x)
172 const char *s = (const char *) x;
173 size_t h = 0;
175 for (; *s; s++)
176 h = *s + ((h << 9) | (h >> (SIZE_BITS - 9)));
178 return h;
182 /* The signal handler. It gets called asynchronously. */
183 static void
184 cleanup ()
186 size_t i;
188 /* First close all file descriptors to temporary files. */
190 gl_list_t fds = descriptors;
192 if (fds != NULL)
194 gl_list_iterator_t iter;
195 const void *element;
197 iter = gl_list_iterator (fds);
198 while (gl_list_iterator_next (&iter, &element, NULL))
200 int fd = (int) (uintptr_t) element;
201 close (fd);
203 gl_list_iterator_free (&iter);
207 for (i = 0; i < cleanup_list.tempdir_count; i++)
209 struct tempdir *dir = cleanup_list.tempdir_list[i];
211 if (dir != NULL)
213 gl_list_iterator_t iter;
214 const void *element;
216 /* First cleanup the files in the subdirectories. */
217 iter = gl_list_iterator (dir->files);
218 while (gl_list_iterator_next (&iter, &element, NULL))
220 const char *file = (const char *) element;
221 unlink (file);
223 gl_list_iterator_free (&iter);
225 /* Then cleanup the subdirectories. */
226 iter = gl_list_iterator (dir->subdirs);
227 while (gl_list_iterator_next (&iter, &element, NULL))
229 const char *subdir = (const char *) element;
230 rmdir (subdir);
232 gl_list_iterator_free (&iter);
234 /* Then cleanup the temporary directory itself. */
235 rmdir (dir->dirname);
240 /* Create a temporary directory.
241 PREFIX is used as a prefix for the name of the temporary directory. It
242 should be short and still give an indication about the program.
243 PARENTDIR can be used to specify the parent directory; if NULL, a default
244 parent directory is used (either $TMPDIR or /tmp or similar).
245 CLEANUP_VERBOSE determines whether errors during explicit cleanup are
246 reported to standard error.
247 Return a fresh 'struct temp_dir' on success. Upon error, an error message
248 is shown and NULL is returned. */
249 struct temp_dir *
250 create_temp_dir (const char *prefix, const char *parentdir,
251 bool cleanup_verbose)
253 struct tempdir * volatile *tmpdirp = NULL;
254 struct tempdir *tmpdir;
255 size_t i;
256 char *xtemplate;
257 char *tmpdirname;
259 /* See whether it can take the slot of an earlier temporary directory
260 already cleaned up. */
261 for (i = 0; i < cleanup_list.tempdir_count; i++)
262 if (cleanup_list.tempdir_list[i] == NULL)
264 tmpdirp = &cleanup_list.tempdir_list[i];
265 break;
267 if (tmpdirp == NULL)
269 /* See whether the array needs to be extended. */
270 if (cleanup_list.tempdir_count == cleanup_list.tempdir_allocated)
272 /* Note that we cannot use xrealloc(), because then the cleanup()
273 function could access an already deallocated array. */
274 struct tempdir * volatile *old_array = cleanup_list.tempdir_list;
275 size_t old_allocated = cleanup_list.tempdir_allocated;
276 size_t new_allocated = 2 * cleanup_list.tempdir_allocated + 1;
277 struct tempdir * volatile *new_array =
278 XNMALLOC (new_allocated, struct tempdir * volatile);
280 if (old_allocated == 0)
281 /* First use of this facility. Register the cleanup handler. */
282 at_fatal_signal (&cleanup);
283 else
285 /* Don't use memcpy() here, because memcpy takes non-volatile
286 arguments and is therefore not guaranteed to complete all
287 memory stores before the next statement. */
288 size_t k;
290 for (k = 0; k < old_allocated; k++)
291 new_array[k] = old_array[k];
294 cleanup_list.tempdir_list = new_array;
295 cleanup_list.tempdir_allocated = new_allocated;
297 /* Now we can free the old array. */
298 if (old_array != NULL)
299 free ((struct tempdir **) old_array);
302 tmpdirp = &cleanup_list.tempdir_list[cleanup_list.tempdir_count];
303 /* Initialize *tmpdirp before incrementing tempdir_count, so that
304 cleanup() will skip this entry before it is fully initialized. */
305 *tmpdirp = NULL;
306 cleanup_list.tempdir_count++;
309 /* Initialize a 'struct tempdir'. */
310 tmpdir = XMALLOC (struct tempdir);
311 tmpdir->dirname = NULL;
312 tmpdir->cleanup_verbose = cleanup_verbose;
313 tmpdir->subdirs = gl_list_create_empty (GL_LINKEDHASH_LIST,
314 string_equals, string_hash, NULL,
315 false);
316 tmpdir->files = gl_list_create_empty (GL_LINKEDHASH_LIST,
317 string_equals, string_hash, NULL,
318 false);
320 /* Create the temporary directory. */
321 xtemplate = (char *) xmalloca (PATH_MAX);
322 if (path_search (xtemplate, PATH_MAX, parentdir, prefix, parentdir == NULL))
324 error (0, errno,
325 _("cannot find a temporary directory, try setting $TMPDIR"));
326 goto quit;
328 block_fatal_signals ();
329 tmpdirname = mkdtemp (xtemplate);
330 if (tmpdirname != NULL)
332 tmpdir->dirname = tmpdirname;
333 *tmpdirp = tmpdir;
335 unblock_fatal_signals ();
336 if (tmpdirname == NULL)
338 error (0, errno,
339 _("cannot create a temporary directory using template \"%s\""),
340 xtemplate);
341 goto quit;
343 /* Replace tmpdir->dirname with a copy that has indefinite extent.
344 We cannot do this inside the block_fatal_signals/unblock_fatal_signals
345 block because then the cleanup handler would not remove the directory
346 if xstrdup fails. */
347 tmpdir->dirname = xstrdup (tmpdirname);
348 freea (xtemplate);
349 return (struct temp_dir *) tmpdir;
351 quit:
352 freea (xtemplate);
353 return NULL;
356 /* Register the given ABSOLUTE_FILE_NAME as being a file inside DIR, that
357 needs to be removed before DIR can be removed.
358 Should be called before the file ABSOLUTE_FILE_NAME is created. */
359 void
360 register_temp_file (struct temp_dir *dir,
361 const char *absolute_file_name)
363 struct tempdir *tmpdir = (struct tempdir *)dir;
365 /* Add absolute_file_name to tmpdir->files, without duplicates. */
366 if (gl_list_search (tmpdir->files, absolute_file_name) == NULL)
367 gl_list_add_first (tmpdir->files, xstrdup (absolute_file_name));
370 /* Unregister the given ABSOLUTE_FILE_NAME as being a file inside DIR, that
371 needs to be removed before DIR can be removed.
372 Should be called when the file ABSOLUTE_FILE_NAME could not be created. */
373 void
374 unregister_temp_file (struct temp_dir *dir,
375 const char *absolute_file_name)
377 struct tempdir *tmpdir = (struct tempdir *)dir;
378 gl_list_t list = tmpdir->files;
379 gl_list_node_t node;
381 node = gl_list_search (list, absolute_file_name);
382 if (node != NULL)
384 char *old_string = (char *) gl_list_node_value (list, node);
386 gl_list_remove_node (list, node);
387 free (old_string);
391 /* Register the given ABSOLUTE_DIR_NAME as being a subdirectory inside DIR,
392 that needs to be removed before DIR can be removed.
393 Should be called before the subdirectory ABSOLUTE_DIR_NAME is created. */
394 void
395 register_temp_subdir (struct temp_dir *dir,
396 const char *absolute_dir_name)
398 struct tempdir *tmpdir = (struct tempdir *)dir;
400 /* Add absolute_dir_name to tmpdir->subdirs, without duplicates. */
401 if (gl_list_search (tmpdir->subdirs, absolute_dir_name) == NULL)
402 gl_list_add_first (tmpdir->subdirs, xstrdup (absolute_dir_name));
405 /* Unregister the given ABSOLUTE_DIR_NAME as being a subdirectory inside DIR,
406 that needs to be removed before DIR can be removed.
407 Should be called when the subdirectory ABSOLUTE_DIR_NAME could not be
408 created. */
409 void
410 unregister_temp_subdir (struct temp_dir *dir,
411 const char *absolute_dir_name)
413 struct tempdir *tmpdir = (struct tempdir *)dir;
414 gl_list_t list = tmpdir->subdirs;
415 gl_list_node_t node;
417 node = gl_list_search (list, absolute_dir_name);
418 if (node != NULL)
420 char *old_string = (char *) gl_list_node_value (list, node);
422 gl_list_remove_node (list, node);
423 free (old_string);
427 /* Remove a file, with optional error message.
428 Return 0 upon success, or -1 if there was some problem. */
429 static int
430 do_unlink (struct temp_dir *dir, const char *absolute_file_name)
432 if (unlink (absolute_file_name) < 0 && dir->cleanup_verbose
433 && errno != ENOENT)
435 error (0, errno, _("cannot remove temporary file %s"), absolute_file_name);
436 return -1;
438 return 0;
441 /* Remove a directory, with optional error message.
442 Return 0 upon success, or -1 if there was some problem. */
443 static int
444 do_rmdir (struct temp_dir *dir, const char *absolute_dir_name)
446 if (rmdir (absolute_dir_name) < 0 && dir->cleanup_verbose
447 && errno != ENOENT)
449 error (0, errno,
450 _("cannot remove temporary directory %s"), absolute_dir_name);
451 return -1;
453 return 0;
456 /* Remove the given ABSOLUTE_FILE_NAME and unregister it.
457 Return 0 upon success, or -1 if there was some problem. */
459 cleanup_temp_file (struct temp_dir *dir,
460 const char *absolute_file_name)
462 int err;
464 err = do_unlink (dir, absolute_file_name);
465 unregister_temp_file (dir, absolute_file_name);
467 return err;
470 /* Remove the given ABSOLUTE_DIR_NAME and unregister it.
471 Return 0 upon success, or -1 if there was some problem. */
473 cleanup_temp_subdir (struct temp_dir *dir,
474 const char *absolute_dir_name)
476 int err;
478 err = do_rmdir (dir, absolute_dir_name);
479 unregister_temp_subdir (dir, absolute_dir_name);
481 return err;
484 /* Remove all registered files and subdirectories inside DIR.
485 Return 0 upon success, or -1 if there was some problem. */
487 cleanup_temp_dir_contents (struct temp_dir *dir)
489 struct tempdir *tmpdir = (struct tempdir *)dir;
490 int err = 0;
491 gl_list_t list;
492 gl_list_iterator_t iter;
493 const void *element;
494 gl_list_node_t node;
496 /* First cleanup the files in the subdirectories. */
497 list = tmpdir->files;
498 iter = gl_list_iterator (list);
499 while (gl_list_iterator_next (&iter, &element, &node))
501 char *file = (char *) element;
503 err |= do_unlink (dir, file);
504 gl_list_remove_node (list, node);
505 /* Now only we can free file. */
506 free (file);
508 gl_list_iterator_free (&iter);
510 /* Then cleanup the subdirectories. */
511 list = tmpdir->subdirs;
512 iter = gl_list_iterator (list);
513 while (gl_list_iterator_next (&iter, &element, &node))
515 char *subdir = (char *) element;
517 err |= do_rmdir (dir, subdir);
518 gl_list_remove_node (list, node);
519 /* Now only we can free subdir. */
520 free (subdir);
522 gl_list_iterator_free (&iter);
524 return err;
527 /* Remove all registered files and subdirectories inside DIR and DIR itself.
528 DIR cannot be used any more after this call.
529 Return 0 upon success, or -1 if there was some problem. */
531 cleanup_temp_dir (struct temp_dir *dir)
533 struct tempdir *tmpdir = (struct tempdir *)dir;
534 int err = 0;
535 size_t i;
537 err |= cleanup_temp_dir_contents (dir);
538 err |= do_rmdir (dir, tmpdir->dirname);
540 for (i = 0; i < cleanup_list.tempdir_count; i++)
541 if (cleanup_list.tempdir_list[i] == tmpdir)
543 /* Remove cleanup_list.tempdir_list[i]. */
544 if (i + 1 == cleanup_list.tempdir_count)
546 while (i > 0 && cleanup_list.tempdir_list[i - 1] == NULL)
547 i--;
548 cleanup_list.tempdir_count = i;
550 else
551 cleanup_list.tempdir_list[i] = NULL;
552 /* Now only we can free the tmpdir->dirname, tmpdir->subdirs,
553 tmpdir->files, and tmpdir itself. */
554 gl_list_free (tmpdir->files);
555 gl_list_free (tmpdir->subdirs);
556 free (tmpdir->dirname);
557 free (tmpdir);
558 return err;
561 /* The user passed an invalid DIR argument. */
562 abort ();
566 #if defined _WIN32 && ! defined __CYGWIN__
568 /* On Windows, opening a file with _O_TEMPORARY has the effect of passing
569 the FILE_FLAG_DELETE_ON_CLOSE flag to CreateFile(), which has the effect
570 of deleting the file when it is closed - even when the program crashes.
571 But (according to the Cygwin sources) it works only on Windows NT or newer.
572 So we cache the info whether we are running on Windows NT or newer. */
574 static bool
575 supports_delete_on_close ()
577 static int known; /* 1 = yes, -1 = no, 0 = unknown */
578 if (!known)
580 OSVERSIONINFO v;
582 /* According to
583 <https://msdn.microsoft.com/en-us/library/ms724451.aspx>
584 this structure must be initialized as follows: */
585 v.dwOSVersionInfoSize = sizeof (OSVERSIONINFO);
587 if (GetVersionEx (&v))
588 known = (v.dwPlatformId == VER_PLATFORM_WIN32_NT ? 1 : -1);
589 else
590 known = -1;
592 return (known > 0);
595 #endif
598 /* Register a file descriptor to be closed. */
599 static void
600 register_fd (int fd)
602 if (descriptors == NULL)
603 descriptors = gl_list_create_empty (GL_LINKEDHASH_LIST, NULL, NULL, NULL,
604 false);
605 gl_list_add_first (descriptors, (void *) (uintptr_t) fd);
608 /* Unregister a file descriptor to be closed. */
609 static void
610 unregister_fd (int fd)
612 gl_list_t fds = descriptors;
613 gl_list_node_t node;
615 if (fds == NULL)
616 /* descriptors should already contain fd. */
617 abort ();
618 node = gl_list_search (fds, (void *) (uintptr_t) fd);
619 if (node == NULL)
620 /* descriptors should already contain fd. */
621 abort ();
622 gl_list_remove_node (fds, node);
625 /* Open a temporary file in a temporary directory.
626 Registers the resulting file descriptor to be closed. */
628 open_temp (const char *file_name, int flags, mode_t mode)
630 int fd;
631 int saved_errno;
633 block_fatal_signals ();
634 /* Note: 'open' here is actually open() or open_safer(). */
635 #if defined _WIN32 && ! defined __CYGWIN__
636 /* Use _O_TEMPORARY when possible, to increase the chances that the
637 temporary file is removed when the process crashes. */
638 if (supports_delete_on_close ())
639 fd = open (file_name, flags | _O_TEMPORARY, mode);
640 else
641 #endif
642 fd = open (file_name, flags, mode);
643 saved_errno = errno;
644 if (fd >= 0)
645 register_fd (fd);
646 unblock_fatal_signals ();
647 errno = saved_errno;
648 return fd;
651 /* Open a temporary file in a temporary directory.
652 Registers the resulting file descriptor to be closed. */
653 FILE *
654 fopen_temp (const char *file_name, const char *mode)
656 FILE *fp;
657 int saved_errno;
659 block_fatal_signals ();
660 /* Note: 'fopen' here is actually fopen() or fopen_safer(). */
661 #if defined _WIN32 && ! defined __CYGWIN__
662 /* Use _O_TEMPORARY when possible, to increase the chances that the
663 temporary file is removed when the process crashes. */
664 if (supports_delete_on_close ())
666 size_t mode_len = strlen (mode);
667 char *augmented_mode = (char *) xmalloca (mode_len + 2);
668 memcpy (augmented_mode, mode, mode_len);
669 memcpy (augmented_mode + mode_len, "D", 2);
671 fp = fopen (file_name, augmented_mode);
672 saved_errno = errno;
674 freea (augmented_mode);
676 else
677 #endif
679 fp = fopen (file_name, mode);
680 saved_errno = errno;
682 if (fp != NULL)
684 /* It is sufficient to register fileno (fp) instead of the entire fp,
685 because at cleanup time there is no need to do an fflush (fp); a
686 close (fileno (fp)) will be enough. */
687 int fd = fileno (fp);
688 if (!(fd >= 0))
689 abort ();
690 register_fd (fd);
692 unblock_fatal_signals ();
693 errno = saved_errno;
694 return fp;
697 /* Close a temporary file in a temporary directory.
698 Unregisters the previously registered file descriptor. */
700 close_temp (int fd)
702 if (fd >= 0)
704 /* No blocking of signals is needed here, since a double close of a
705 file descriptor is harmless. */
706 int result = close (fd);
707 int saved_errno = errno;
709 /* No race condition here: we assume a single-threaded program, hence
710 fd cannot be re-opened here. */
712 unregister_fd (fd);
714 errno = saved_errno;
715 return result;
717 else
718 return close (fd);
721 /* Close a temporary file in a temporary directory.
722 Unregisters the previously registered file descriptor. */
724 fclose_temp (FILE *fp)
726 int fd = fileno (fp);
727 /* No blocking of signals is needed here, since a double close of a
728 file descriptor is harmless. */
729 int result = fclose (fp);
730 int saved_errno = errno;
732 /* No race condition here: we assume a single-threaded program, hence
733 fd cannot be re-opened here. */
735 unregister_fd (fd);
737 errno = saved_errno;
738 return result;
741 #if GNULIB_FWRITEERROR
742 /* Like fwriteerror.
743 Unregisters the previously registered file descriptor. */
745 fwriteerror_temp (FILE *fp)
747 int fd = fileno (fp);
748 /* No blocking of signals is needed here, since a double close of a
749 file descriptor is harmless. */
750 int result = fwriteerror (fp);
751 int saved_errno = errno;
753 /* No race condition here: we assume a single-threaded program, hence
754 fd cannot be re-opened here. */
756 unregister_fd (fd);
758 errno = saved_errno;
759 return result;
761 #endif
763 #if GNULIB_CLOSE_STREAM
764 /* Like close_stream.
765 Unregisters the previously registered file descriptor. */
767 close_stream_temp (FILE *fp)
769 int fd = fileno (fp);
770 /* No blocking of signals is needed here, since a double close of a
771 file descriptor is harmless. */
772 int result = close_stream (fp);
773 int saved_errno = errno;
775 /* No race condition here: we assume a single-threaded program, hence
776 fd cannot be re-opened here. */
778 unregister_fd (fd);
780 errno = saved_errno;
781 return result;
783 #endif