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