1 /* Temporary directories and temporary files with automatic cleanup.
2 Copyright (C) 2001, 2003, 2006 Free Software Foundation, Inc.
3 Written by Bruno Haible <bruno@clisp.org>, 2006.
4
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation; either version 2, or (at your option)
8 any later version.
9
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
14
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software Foundation,
17 Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */
18
19
20 #include <config.h>
21
22 /* Specification. */
23 #include "clean-temp.h"
24
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>
32
33 #include "error.h"
34 #include "fatal-signal.h"
35 #include "pathmax.h"
36 #include "tmpdir.h"
37 #include "mkdtemp.h"
38 #include "xalloc.h"
39 #include "xallocsa.h"
40 #include "gl_linkedhash_list.h"
41 #include "gettext.h"
42 #if GNULIB_FWRITEERROR
43 # include "fwriteerror.h"
44 #endif
45 #if GNULIB_CLOSE_STREAM
46 # include "close-stream.h"
47 #endif
48 #if GNULIB_FCNTL_SAFER
49 # include "fcntl--.h"
50 #endif
51 #if GNULIB_FOPEN_SAFER
52 # include "stdio--.h"
53 #endif
54
55 #define _(str) gettext (str)
56
57 /* GNU Hurd doesn't have PATH_MAX. */
58 #ifndef PATH_MAX
59 # ifdef MAXPATHLEN
60 # define PATH_MAX MAXPATHLEN
61 # else
62 # define PATH_MAX 1024
63 # endif
64 #endif
65
66 #ifndef uintptr_t
67 # define uintptr_t unsigned long
68 #endif
69
70
71 /* The use of 'volatile' in the types below (and ISO C 99 section 5.1.2.3.(5))
72 ensure that while constructing or modifying the data structures, the field
73 values are written to memory in the order of the C statements. So the
74 signal handler can rely on these field values to be up to date. */
75
76
77 /* Registry for a single temporary directory.
78 'struct temp_dir' from the public header file overlaps with this. */
79 struct tempdir
80 {
81 /* The absolute pathname of the directory. */
82 char * volatile dirname;
83 /* Whether errors during explicit cleanup are reported to standard error. */
84 bool cleanup_verbose;
85 /* Absolute pathnames of subdirectories. */
86 gl_list_t /* <char *> */ volatile subdirs;
87 /* Absolute pathnames of files. */
88 gl_list_t /* <char *> */ volatile files;
89 };
90
91 /* List of all temporary directories. */
92 static struct
93 {
94 struct tempdir * volatile * volatile tempdir_list;
95 size_t volatile tempdir_count;
96 size_t tempdir_allocated;
97 } cleanup_list /* = { NULL, 0, 0 } */;
98
99 /* List of all open file descriptors to temporary files. */
100 static gl_list_t /* <int> */ volatile descriptors;
101
102
103 /* For the subdirs and for the files, we use a gl_list_t of type LINKEDHASH.
104 Why? We need a data structure that
105
106 1) Can contain an arbitrary number of 'char *' values. The strings
107 are compared via strcmp, not pointer comparison.
108 2) Has insertion and deletion operations that are fast: ideally O(1),
109 or possibly O(log n). This is important for GNU sort, which may
110 create a large number of temporary files.
111 3) Allows iteration through all elements from within a signal handler.
112 4) May or may not allow duplicates. It doesn't matter here, since
113 any file or subdir can only be removed once.
114
115 Criterion 1) would allow any gl_list_t or gl_oset_t implementation.
116
117 Criterion 2) leaves only GL_LINKEDHASH_LIST, GL_TREEHASH_LIST, or
118 GL_TREE_OSET.
119
120 Criterion 3) puts at disadvantage GL_TREEHASH_LIST and GL_TREE_OSET.
121 Namely, iteration through the elements of a binary tree requires access
122 to many ->left, ->right, ->parent pointers. However, the rebalancing
123 code for insertion and deletion in an AVL or red-black tree is so
124 complicated that we cannot assume that >left, ->right, ->parent pointers
125 are in a consistent state throughout these operations. Therefore, to
126 avoid a crash in the signal handler, all destructive operations to the
127 lists would have to be protected by a
128 block_fatal_signals ();
129 ...
130 unblock_fatal_signals ();
131 pair. Which causes extra system calls.
132
133 Criterion 3) would also discourage GL_ARRAY_LIST and GL_CARRAY_LIST,
134 if they were not already excluded. Namely, these implementations use
135 xrealloc(), leaving a time window in which in the list->elements pointer
136 points to already deallocated memory. To avoid a crash in the signal
137 handler at such a moment, all destructive operations would have to
138 protected by block/unblock_fatal_signals (), in this case too.
139
140 A list of type GL_LINKEDHASH_LIST without duplicates fulfills all
141 requirements:
142 2) Insertion and deletion are O(1) on average.
143 3) The gl_list_iterator, gl_list_iterator_next implementations do
144 not trigger memory allocations, nor other system calls, and are
145 therefore safe to be called from a signal handler.
146 Furthermore, since SIGNAL_SAFE_LIST is defined, the implementation
147 of the destructive functions ensures that the list structure is
148 safe to be traversed at any moment, even when interrupted by an
149 asynchronous signal.
150 */
151
152 /* String equality and hash code functions used by the lists. */
153
154 static bool
string_equals(const void * x1,const void * x2)155 string_equals (const void *x1, const void *x2)
156 {
157 const char *s1 = x1;
158 const char *s2 = x2;
159 return strcmp (s1, s2) == 0;
160 }
161
162 #define SIZE_BITS (sizeof (size_t) * CHAR_BIT)
163
164 /* A hash function for NUL-terminated char* strings using
165 the method described by Bruno Haible.
166 See http://www.haible.de/bruno/hashfunc.html. */
167 static size_t
string_hash(const void * x)168 string_hash (const void *x)
169 {
170 const char *s = x;
171 size_t h = 0;
172
173 for (; *s; s++)
174 h = *s + ((h << 9) | (h >> (SIZE_BITS - 9)));
175
176 return h;
177 }
178
179
180 /* The signal handler. It gets called asynchronously. */
181 static void
cleanup()182 cleanup ()
183 {
184 size_t i;
185
186 /* First close all file descriptors to temporary files. */
187 {
188 gl_list_t fds = descriptors;
189
190 if (fds != NULL)
191 {
192 gl_list_iterator_t iter;
193 const void *element;
194
195 iter = gl_list_iterator (fds);
196 while (gl_list_iterator_next (&iter, &element, NULL))
197 {
198 int fd = (int) (uintptr_t) element;
199 close (fd);
200 }
201 gl_list_iterator_free (&iter);
202 }
203 }
204
205 for (i = 0; i < cleanup_list.tempdir_count; i++)
206 {
207 struct tempdir *dir = cleanup_list.tempdir_list[i];
208
209 if (dir != NULL)
210 {
211 gl_list_iterator_t iter;
212 const void *element;
213
214 /* First cleanup the files in the subdirectories. */
215 iter = gl_list_iterator (dir->files);
216 while (gl_list_iterator_next (&iter, &element, NULL))
217 {
218 const char *file = (const char *) element;
219 unlink (file);
220 }
221 gl_list_iterator_free (&iter);
222
223 /* Then cleanup the subdirectories. */
224 iter = gl_list_iterator (dir->subdirs);
225 while (gl_list_iterator_next (&iter, &element, NULL))
226 {
227 const char *subdir = (const char *) element;
228 rmdir (subdir);
229 }
230 gl_list_iterator_free (&iter);
231
232 /* Then cleanup the temporary directory itself. */
233 rmdir (dir->dirname);
234 }
235 }
236 }
237
238 /* Create a temporary directory.
239 PREFIX is used as a prefix for the name of the temporary directory. It
240 should be short and still give an indication about the program.
241 PARENTDIR can be used to specify the parent directory; if NULL, a default
242 parent directory is used (either $TMPDIR or /tmp or similar).
243 CLEANUP_VERBOSE determines whether errors during explicit cleanup are
244 reported to standard error.
245 Return a fresh 'struct temp_dir' on success. Upon error, an error message
246 is shown and NULL is returned. */
247 struct temp_dir *
create_temp_dir(const char * prefix,const char * parentdir,bool cleanup_verbose)248 create_temp_dir (const char *prefix, const char *parentdir,
249 bool cleanup_verbose)
250 {
251 struct tempdir * volatile *tmpdirp = NULL;
252 struct tempdir *tmpdir;
253 size_t i;
254 char *template;
255 char *tmpdirname;
256
257 /* See whether it can take the slot of an earlier temporary directory
258 already cleaned up. */
259 for (i = 0; i < cleanup_list.tempdir_count; i++)
260 if (cleanup_list.tempdir_list[i] == NULL)
261 {
262 tmpdirp = &cleanup_list.tempdir_list[i];
263 break;
264 }
265 if (tmpdirp == NULL)
266 {
267 /* See whether the array needs to be extended. */
268 if (cleanup_list.tempdir_count == cleanup_list.tempdir_allocated)
269 {
270 /* Note that we cannot use xrealloc(), because then the cleanup()
271 function could access an already deallocated array. */
272 struct tempdir * volatile *old_array = cleanup_list.tempdir_list;
273 size_t old_allocated = cleanup_list.tempdir_allocated;
274 size_t new_allocated = 2 * cleanup_list.tempdir_allocated + 1;
275 struct tempdir * volatile *new_array =
276 (struct tempdir * volatile *)
277 xmalloc (new_allocated * sizeof (struct tempdir * volatile));
278
279 if (old_allocated == 0)
280 /* First use of this facility. Register the cleanup handler. */
281 at_fatal_signal (&cleanup);
282 else
283 {
284 /* Don't use memcpy() here, because memcpy takes non-volatile
285 arguments and is therefore not guaranteed to complete all
286 memory stores before the next statement. */
287 size_t k;
288
289 for (k = 0; k < old_allocated; k++)
290 new_array[k] = old_array[k];
291 }
292
293 cleanup_list.tempdir_list = new_array;
294 cleanup_list.tempdir_allocated = new_allocated;
295
296 /* Now we can free the old array. */
297 if (old_array != NULL)
298 free ((struct tempdir **) old_array);
299 }
300
301 tmpdirp = &cleanup_list.tempdir_list[cleanup_list.tempdir_count];
302 /* Initialize *tmpdirp before incrementing tempdir_count, so that
303 cleanup() will skip this entry before it is fully initialized. */
304 *tmpdirp = NULL;
305 cleanup_list.tempdir_count++;
306 }
307
308 /* Initialize a 'struct tempdir'. */
309 tmpdir = (struct tempdir *) xmalloc (sizeof (struct tempdir));
310 tmpdir->dirname = NULL;
311 tmpdir->cleanup_verbose = cleanup_verbose;
312 tmpdir->subdirs = gl_list_create_empty (GL_LINKEDHASH_LIST,
313 string_equals, string_hash, false);
314 tmpdir->files = gl_list_create_empty (GL_LINKEDHASH_LIST,
315 string_equals, string_hash, false);
316
317 /* Create the temporary directory. */
318 template = (char *) xallocsa (PATH_MAX);
319 if (path_search (template, PATH_MAX, parentdir, prefix, parentdir == NULL))
320 {
321 error (0, errno,
322 _("cannot find a temporary directory, try setting $TMPDIR"));
323 goto quit;
324 }
325 block_fatal_signals ();
326 tmpdirname = mkdtemp (template);
327 if (tmpdirname != NULL)
328 {
329 tmpdir->dirname = tmpdirname;
330 *tmpdirp = tmpdir;
331 }
332 unblock_fatal_signals ();
333 if (tmpdirname == NULL)
334 {
335 error (0, errno,
336 _("cannot create a temporary directory using template \"%s\""),
337 template);
338 goto quit;
339 }
340 /* Replace tmpdir->dirname with a copy that has indefinite extent.
341 We cannot do this inside the block_fatal_signals/unblock_fatal_signals
342 block because then the cleanup handler would not remove the directory
343 if xstrdup fails. */
344 tmpdir->dirname = xstrdup (tmpdirname);
345 freesa (template);
346 return (struct temp_dir *) tmpdir;
347
348 quit:
349 freesa (template);
350 return NULL;
351 }
352
353 /* Register the given ABSOLUTE_FILE_NAME as being a file inside DIR, that
354 needs to be removed before DIR can be removed.
355 Should be called before the file ABSOLUTE_FILE_NAME is created. */
356 void
register_temp_file(struct temp_dir * dir,const char * absolute_file_name)357 register_temp_file (struct temp_dir *dir,
358 const char *absolute_file_name)
359 {
360 struct tempdir *tmpdir = (struct tempdir *)dir;
361
362 /* Add absolute_file_name to tmpdir->files, without duplicates. */
363 if (gl_list_search (tmpdir->files, absolute_file_name) == NULL)
364 gl_list_add_first (tmpdir->files, xstrdup (absolute_file_name));
365 }
366
367 /* Unregister the given ABSOLUTE_FILE_NAME as being a file inside DIR, that
368 needs to be removed before DIR can be removed.
369 Should be called when the file ABSOLUTE_FILE_NAME could not be created. */
370 void
unregister_temp_file(struct temp_dir * dir,const char * absolute_file_name)371 unregister_temp_file (struct temp_dir *dir,
372 const char *absolute_file_name)
373 {
374 struct tempdir *tmpdir = (struct tempdir *)dir;
375 gl_list_t list = tmpdir->files;
376 gl_list_node_t node;
377
378 node = gl_list_search (list, absolute_file_name);
379 if (node != NULL)
380 {
381 char *old_string = (char *) gl_list_node_value (list, node);
382
383 gl_list_remove_node (list, node);
384 free (old_string);
385 }
386 }
387
388 /* Register the given ABSOLUTE_DIR_NAME as being a subdirectory inside DIR,
389 that needs to be removed before DIR can be removed.
390 Should be called before the subdirectory ABSOLUTE_DIR_NAME is created. */
391 void
register_temp_subdir(struct temp_dir * dir,const char * absolute_dir_name)392 register_temp_subdir (struct temp_dir *dir,
393 const char *absolute_dir_name)
394 {
395 struct tempdir *tmpdir = (struct tempdir *)dir;
396
397 /* Add absolute_dir_name to tmpdir->subdirs, without duplicates. */
398 if (gl_list_search (tmpdir->subdirs, absolute_dir_name) == NULL)
399 gl_list_add_first (tmpdir->subdirs, xstrdup (absolute_dir_name));
400 }
401
402 /* Unregister the given ABSOLUTE_DIR_NAME as being a subdirectory inside DIR,
403 that needs to be removed before DIR can be removed.
404 Should be called when the subdirectory ABSOLUTE_DIR_NAME could not be
405 created. */
406 void
unregister_temp_subdir(struct temp_dir * dir,const char * absolute_dir_name)407 unregister_temp_subdir (struct temp_dir *dir,
408 const char *absolute_dir_name)
409 {
410 struct tempdir *tmpdir = (struct tempdir *)dir;
411 gl_list_t list = tmpdir->subdirs;
412 gl_list_node_t node;
413
414 node = gl_list_search (list, absolute_dir_name);
415 if (node != NULL)
416 {
417 char *old_string = (char *) gl_list_node_value (list, node);
418
419 gl_list_remove_node (list, node);
420 free (old_string);
421 }
422 }
423
424 /* Remove a file, with optional error message.
425 Return 0 upon success, or -1 if there was some problem. */
426 static int
do_unlink(struct temp_dir * dir,const char * absolute_file_name)427 do_unlink (struct temp_dir *dir, const char *absolute_file_name)
428 {
429 if (unlink (absolute_file_name) < 0 && dir->cleanup_verbose
430 && errno != ENOENT)
431 {
432 error (0, errno, _("cannot remove temporary file %s"), absolute_file_name);
433 return -1;
434 }
435 return 0;
436 }
437
438 /* Remove a directory, with optional error message.
439 Return 0 upon success, or -1 if there was some problem. */
440 static int
do_rmdir(struct temp_dir * dir,const char * absolute_dir_name)441 do_rmdir (struct temp_dir *dir, const char *absolute_dir_name)
442 {
443 if (rmdir (absolute_dir_name) < 0 && dir->cleanup_verbose
444 && errno != ENOENT)
445 {
446 error (0, errno,
447 _("cannot remove temporary directory %s"), absolute_dir_name);
448 return -1;
449 }
450 return 0;
451 }
452
453 /* Remove the given ABSOLUTE_FILE_NAME and unregister it.
454 Return 0 upon success, or -1 if there was some problem. */
455 int
cleanup_temp_file(struct temp_dir * dir,const char * absolute_file_name)456 cleanup_temp_file (struct temp_dir *dir,
457 const char *absolute_file_name)
458 {
459 int err;
460
461 err = do_unlink (dir, absolute_file_name);
462 unregister_temp_file (dir, absolute_file_name);
463
464 return err;
465 }
466
467 /* Remove the given ABSOLUTE_DIR_NAME and unregister it.
468 Return 0 upon success, or -1 if there was some problem. */
469 int
cleanup_temp_subdir(struct temp_dir * dir,const char * absolute_dir_name)470 cleanup_temp_subdir (struct temp_dir *dir,
471 const char *absolute_dir_name)
472 {
473 int err;
474
475 err = do_rmdir (dir, absolute_dir_name);
476 unregister_temp_subdir (dir, absolute_dir_name);
477
478 return err;
479 }
480
481 /* Remove all registered files and subdirectories inside DIR.
482 Return 0 upon success, or -1 if there was some problem. */
483 int
cleanup_temp_dir_contents(struct temp_dir * dir)484 cleanup_temp_dir_contents (struct temp_dir *dir)
485 {
486 struct tempdir *tmpdir = (struct tempdir *)dir;
487 int err = 0;
488 gl_list_t list;
489 gl_list_iterator_t iter;
490 const void *element;
491 gl_list_node_t node;
492
493 /* First cleanup the files in the subdirectories. */
494 list = tmpdir->files;
495 iter = gl_list_iterator (list);
496 while (gl_list_iterator_next (&iter, &element, &node))
497 {
498 char *file = (char *) element;
499
500 err |= do_unlink (dir, file);
501 gl_list_remove_node (list, node);
502 /* Now only we can free file. */
503 free (file);
504 }
505 gl_list_iterator_free (&iter);
506
507 /* Then cleanup the subdirectories. */
508 list = tmpdir->subdirs;
509 iter = gl_list_iterator (list);
510 while (gl_list_iterator_next (&iter, &element, &node))
511 {
512 char *subdir = (char *) element;
513
514 err |= do_rmdir (dir, subdir);
515 gl_list_remove_node (list, node);
516 /* Now only we can free subdir. */
517 free (subdir);
518 }
519 gl_list_iterator_free (&iter);
520
521 return err;
522 }
523
524 /* Remove all registered files and subdirectories inside DIR and DIR itself.
525 DIR cannot be used any more after this call.
526 Return 0 upon success, or -1 if there was some problem. */
527 int
cleanup_temp_dir(struct temp_dir * dir)528 cleanup_temp_dir (struct temp_dir *dir)
529 {
530 struct tempdir *tmpdir = (struct tempdir *)dir;
531 int err = 0;
532 size_t i;
533
534 err |= cleanup_temp_dir_contents (dir);
535 err |= do_rmdir (dir, tmpdir->dirname);
536
537 for (i = 0; i < cleanup_list.tempdir_count; i++)
538 if (cleanup_list.tempdir_list[i] == tmpdir)
539 {
540 /* Remove cleanup_list.tempdir_list[i]. */
541 if (i + 1 == cleanup_list.tempdir_count)
542 {
543 while (i > 0 && cleanup_list.tempdir_list[i - 1] == NULL)
544 i--;
545 cleanup_list.tempdir_count = i;
546 }
547 else
548 cleanup_list.tempdir_list[i] = NULL;
549 /* Now only we can free the tmpdir->dirname and tmpdir itself. */
550 free (tmpdir->dirname);
551 free (tmpdir);
552 return err;
553 }
554
555 /* The user passed an invalid DIR argument. */
556 abort ();
557 }
558
559
560 /* Register a file descriptor to be closed. */
561 static void
register_fd(int fd)562 register_fd (int fd)
563 {
564 if (descriptors == NULL)
565 descriptors = gl_list_create_empty (GL_LINKEDHASH_LIST, NULL, NULL, false);
566 gl_list_add_first (descriptors, (void *) (uintptr_t) fd);
567 }
568
569 /* Unregister a file descriptor to be closed. */
570 static void
unregister_fd(int fd)571 unregister_fd (int fd)
572 {
573 gl_list_t fds = descriptors;
574 gl_list_node_t node;
575
576 if (fds == NULL)
577 /* descriptors should already contain fd. */
578 abort ();
579 node = gl_list_search (fds, (void *) (uintptr_t) fd);
580 if (node == NULL)
581 /* descriptors should already contain fd. */
582 abort ();
583 gl_list_remove_node (fds, node);
584 }
585
586 /* Open a temporary file in a temporary directory.
587 Registers the resulting file descriptor to be closed. */
588 int
open_temp(const char * file_name,int flags,mode_t mode)589 open_temp (const char *file_name, int flags, mode_t mode)
590 {
591 int fd;
592 int saved_errno;
593
594 block_fatal_signals ();
595 fd = open (file_name, flags, mode); /* actually open or open_safer */
596 saved_errno = errno;
597 if (fd >= 0)
598 register_fd (fd);
599 unblock_fatal_signals ();
600 errno = saved_errno;
601 return fd;
602 }
603
604 /* Open a temporary file in a temporary directory.
605 Registers the resulting file descriptor to be closed. */
606 FILE *
fopen_temp(const char * file_name,const char * mode)607 fopen_temp (const char *file_name, const char *mode)
608 {
609 FILE *fp;
610 int saved_errno;
611
612 block_fatal_signals ();
613 fp = fopen (file_name, mode); /* actually fopen or fopen_safer */
614 saved_errno = errno;
615 if (fp != NULL)
616 {
617 /* It is sufficient to register fileno (fp) instead of the entire fp,
618 because at cleanup time there is no need to do an fflush (fp); a
619 close (fileno (fp)) will be enough. */
620 int fd = fileno (fp);
621 if (!(fd >= 0))
622 abort ();
623 register_fd (fd);
624 }
625 unblock_fatal_signals ();
626 errno = saved_errno;
627 return fp;
628 }
629
630 /* Close a temporary file in a temporary directory.
631 Unregisters the previously registered file descriptor. */
632 int
close_temp(int fd)633 close_temp (int fd)
634 {
635 if (fd >= 0)
636 {
637 /* No blocking of signals is needed here, since a double close of a
638 file descriptor is harmless. */
639 int result = close (fd);
640 int saved_errno = errno;
641
642 /* No race condition here: we assume a single-threaded program, hence
643 fd cannot be re-opened here. */
644
645 unregister_fd (fd);
646
647 errno = saved_errno;
648 return result;
649 }
650 else
651 return close (fd);
652 }
653
654 /* Close a temporary file in a temporary directory.
655 Unregisters the previously registered file descriptor. */
656 int
fclose_temp(FILE * fp)657 fclose_temp (FILE *fp)
658 {
659 int fd = fileno (fp);
660 /* No blocking of signals is needed here, since a double close of a
661 file descriptor is harmless. */
662 int result = fclose (fp);
663 int saved_errno = errno;
664
665 /* No race condition here: we assume a single-threaded program, hence
666 fd cannot be re-opened here. */
667
668 unregister_fd (fd);
669
670 errno = saved_errno;
671 return result;
672 }
673
674 #if GNULIB_FWRITEERROR
675 /* Like fwriteerror.
676 Unregisters the previously registered file descriptor. */
677 int
fwriteerror_temp(FILE * fp)678 fwriteerror_temp (FILE *fp)
679 {
680 int fd = fileno (fp);
681 /* No blocking of signals is needed here, since a double close of a
682 file descriptor is harmless. */
683 int result = fwriteerror (fp);
684 int saved_errno = errno;
685
686 /* No race condition here: we assume a single-threaded program, hence
687 fd cannot be re-opened here. */
688
689 unregister_fd (fd);
690
691 errno = saved_errno;
692 return result;
693 }
694 #endif
695
696 #if GNULIB_CLOSE_STREAM
697 /* Like close_stream.
698 Unregisters the previously registered file descriptor. */
699 int
close_stream_temp(FILE * fp)700 close_stream_temp (FILE *fp)
701 {
702 int fd = fileno (fp);
703 /* No blocking of signals is needed here, since a double close of a
704 file descriptor is harmless. */
705 int result = close_stream (fp);
706 int saved_errno = errno;
707
708 /* No race condition here: we assume a single-threaded program, hence
709 fd cannot be re-opened here. */
710
711 unregister_fd (fd);
712
713 errno = saved_errno;
714 return result;
715 }
716 #endif
717