1 /* temp-stream.c -- provide a stream to a per process temp file
2 
3    Copyright (C) 2023 Free Software Foundation, Inc.
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 3 of the License, or
8    (at your option) 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, see <https://www.gnu.org/licenses/>.  */
17 
18 #include <config.h>
19 
20 #include <stdbool.h>
21 #include <stdio.h>
22 
23 #include "stdlib--.h"  /* For mkstemp that returns safer FDs.  */
24 #include "system.h"
25 #include "tmpdir.h"
26 
27 #include "temp-stream.h"
28 
29 
30 #if defined __MSDOS__ || defined _WIN32
31 /* Define this to non-zero on systems for which the regular mechanism
32    (of unlinking an open file and expecting to be able to write, seek
33    back to the beginning, then reread it) doesn't work.  E.g., on Windows
34    and DOS systems.  */
35 # define DONT_UNLINK_WHILE_OPEN 1
36 #endif
37 
38 #if DONT_UNLINK_WHILE_OPEN
39 
40 /* FIXME-someday: remove all of this DONT_UNLINK_WHILE_OPEN junk.
41    Using atexit like this is wrong, since it can fail
42    when called e.g. 32 or more times.
43    But this isn't a big deal, since the code is used only on WOE/DOS
44    systems, and few people invoke tac on that many nonseekable files.  */
45 
46 static char const *file_to_remove;
47 static FILE *fp_to_close;
48 
49 static void
unlink_tempfile(void)50 unlink_tempfile (void)
51 {
52   fclose (fp_to_close);
53   unlink (file_to_remove);
54 }
55 
56 static void
record_or_unlink_tempfile(char const * fn,FILE * fp)57 record_or_unlink_tempfile (char const *fn, FILE *fp)
58 {
59   if (!file_to_remove)
60     {
61       file_to_remove = fn;
62       fp_to_close = fp;
63       atexit (unlink_tempfile);
64     }
65 }
66 
67 #else
68 
69 static void
record_or_unlink_tempfile(char const * fn,MAYBE_UNUSED FILE * fp)70 record_or_unlink_tempfile (char const *fn, MAYBE_UNUSED FILE *fp)
71 {
72   unlink (fn);
73 }
74 
75 #endif
76 
77 /* A wrapper around mkstemp that gives us both an open stream pointer,
78    FP, and the corresponding FILE_NAME.  Always return the same FP/name
79    pair, rewinding/truncating it upon each reuse.
80 
81    Note this honors $TMPDIR, unlike the standard defined tmpfile().
82 
83    Returns TRUE on success.  */
84 bool
temp_stream(FILE ** fp,char ** file_name)85 temp_stream (FILE **fp, char **file_name)
86 {
87   static char *tempfile = nullptr;
88   static FILE *tmp_fp;
89   if (tempfile == nullptr)
90     {
91       char *tempbuf = nullptr;
92       size_t tempbuf_len = 128;
93 
94       while (true)
95         {
96           if (! (tempbuf = realloc (tempbuf, tempbuf_len)))
97             {
98               error (0, errno, _("failed to make temporary file name"));
99               return false;
100             }
101 
102           if (path_search (tempbuf, tempbuf_len, nullptr, "cutmp", true) == 0)
103             break;
104 
105           if (errno != EINVAL || PATH_MAX / 2 < tempbuf_len)
106             {
107               error (0, errno == EINVAL ? ENAMETOOLONG : errno,
108                      _("failed to make temporary file name"));
109               return false;
110             }
111 
112           tempbuf_len *= 2;
113         }
114 
115       tempfile = tempbuf;
116 
117       /* FIXME: there's a small window between a successful mkstemp call
118          and the unlink that's performed by record_or_unlink_tempfile.
119          If we're interrupted in that interval, this code fails to remove
120          the temporary file.  On systems that define DONT_UNLINK_WHILE_OPEN,
121          the window is much larger -- it extends to the atexit-called
122          unlink_tempfile.
123          FIXME: clean up upon fatal signal.  Don't block them, in case
124          $TMPDIR is a remote file system.  */
125 
126       int fd = mkstemp (tempfile);
127       if (fd < 0)
128         {
129           error (0, errno, _("failed to create temporary file %s"),
130                  quoteaf (tempfile));
131           goto Reset;
132         }
133 
134       tmp_fp = fdopen (fd, (O_BINARY ? "w+b" : "w+"));
135       if (! tmp_fp)
136         {
137           error (0, errno, _("failed to open %s for writing"),
138                  quoteaf (tempfile));
139           close (fd);
140           unlink (tempfile);
141         Reset:
142           free (tempfile);
143           tempfile = nullptr;
144           return false;
145         }
146 
147       record_or_unlink_tempfile (tempfile, tmp_fp);
148     }
149   else
150     {
151       clearerr (tmp_fp);
152       if (fseeko (tmp_fp, 0, SEEK_SET) < 0
153           || ftruncate (fileno (tmp_fp), 0) < 0)
154         {
155           error (0, errno, _("failed to rewind stream for %s"),
156                  quoteaf (tempfile));
157           return false;
158         }
159     }
160 
161   *fp = tmp_fp;
162   if (file_name)
163     *file_name = tempfile;
164   return true;
165 }
166