xref: /netbsd-src/external/gpl2/gettext/dist/gettext-tools/gnulib-lib/clean-temp.c (revision 946379e7b37692fc43f68eb0d1c10daa0a7f3b6c)
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