]> diplodocus.org Git - nmh/blob - sbr/m_mktemp.c
Improve fmttest output
[nmh] / sbr / m_mktemp.c
1 /* m_mktemp.c -- Construct a temporary file.
2 *
3 * This code is Copyright (c) 2010, by the authors of nmh. See the
4 * COPYRIGHT file in the root directory of the nmh distribution for
5 * complete copyright information.
6 */
7
8 #include "h/mh.h"
9 #include "concat.h"
10 #include "r1bindex.h"
11 #include "h/utils.h"
12 #include "h/signals.h"
13 #include "m_maildir.h"
14 #include "m_mktemp.h"
15
16 static void register_for_removal(const char *);
17
18
19 /* Create a temporary file. If pfx_in is null, the temporary file
20 * will be created in the temporary directory (more on that later).
21 * If pfx_in is not null, then the temporary file location will be
22 * defined by the value pfx_in.
23 *
24 * The file created will be at the pathname specified appended with
25 * 6 random (we hope :) characters.
26 *
27 * The return value will be the pathname to the file created.
28 *
29 * CAUTION: The return pointer references static data. If
30 * you need to modify, or save, the return string, make a copy of it
31 * first.
32 *
33 * When pfx_in is null, the temporary directory is determined as
34 * follows, in order:
35 *
36 * MHTMPDIR envvar
37 * TMPDIR envvar
38 * User's mail directory.
39 *
40 * NOTE: One will probably use m_mktemp2() instead of this function.
41 * For example, if you want to create a temp file in the defined
42 * temporary directory, but with a custom basename prefix, do
43 * something like the following:
44 *
45 * char *tmp_pathname = m_mktemp2(NULL, "mypre", ...);
46 */
47 char *
48 m_mktemp (
49 const char *pfx_in, /* Pathname prefix for temporary file. */
50 int *fd_ret, /* (return,optional) File descriptor to temp file. */
51 FILE **fp_ret /* (return,optional) FILE pointer to temp file. */
52 )
53 {
54 static char tmpfil[BUFSIZ];
55 int fd = -1;
56 mode_t oldmode = umask(077);
57
58 if (pfx_in == NULL) {
59 snprintf(tmpfil, sizeof(tmpfil), "%s/nmhXXXXXX", get_temp_dir());
60 } else {
61 snprintf(tmpfil, sizeof(tmpfil), "%sXXXXXX", pfx_in);
62 }
63
64 fd = mkstemp(tmpfil);
65
66 if (fd < 0) {
67 umask(oldmode);
68 return NULL;
69 }
70
71 register_for_removal(tmpfil);
72
73 bool keep_open = false;
74 if (fd_ret != NULL) {
75 *fd_ret = fd;
76 keep_open = true;
77 }
78 if (fp_ret != NULL) {
79 FILE *fp = fdopen(fd, "w+");
80 if (fp == NULL) {
81 int olderr = errno;
82 (void) m_unlink(tmpfil);
83 close(fd);
84 errno = olderr;
85 umask(oldmode);
86 return NULL;
87 }
88 *fp_ret = fp;
89 keep_open = true;
90 }
91 if (!keep_open) {
92 close(fd);
93 }
94 umask(oldmode);
95 return tmpfil;
96 }
97
98 /* This version allows one to specify the directory the temp file should
99 * by created based on a given pathname. Although m_mktemp() technically
100 * supports this, this version is when the directory is defined by
101 * a separate variable from the prefix, eliminating the caller from having
102 * to do string manipulation to generate the desired pathname prefix.
103 *
104 * The pfx_in parameter specifies a basename prefix for the file. If dir_in
105 * is NULL, then the defined temporary directory (see comments to m_mktemp()
106 * above) is used to create the temp file.
107 */
108 char *
109 m_mktemp2 (
110 const char *dir_in, /* Directory to place temp file. */
111 const char *pfx_in, /* Basename prefix for temp file. */
112 int *fd_ret, /* (return,optional) File descriptor to temp file. */
113 FILE **fp_ret /* (return,optional) FILE pointer to temp file. */
114 )
115 {
116 static char buffer[BUFSIZ];
117 char *cp;
118 int n;
119
120 if (dir_in == NULL) {
121 if (pfx_in == NULL) {
122 return m_mktemp(NULL, fd_ret, fp_ret);
123 }
124 snprintf(buffer, sizeof(buffer), "%s/%s", get_temp_dir(), pfx_in);
125 return m_mktemp(buffer, fd_ret, fp_ret);
126 }
127
128 if ((cp = r1bindex ((char *)dir_in, '/')) == dir_in) {
129 /* No directory component */
130 return m_mktemp(pfx_in, fd_ret, fp_ret);
131 }
132 n = (int)(cp-dir_in); /* Length of dir component */
133 snprintf(buffer, sizeof(buffer), "%.*s%s", n, dir_in, pfx_in);
134 return m_mktemp(buffer, fd_ret, fp_ret);
135 }
136
137
138 /*
139 * This version supports a suffix for the temp filename.
140 * It has two other differences from m_mktemp() and m_mktemp2():
141 * 1) It does not support specification of a directory for the temp
142 * file. Instead it always uses get_temp_dir().
143 * 2) It returns a dynamically allocated string. The caller is
144 * responsible for freeing the allocated memory.
145 */
146 char *
147 m_mktemps(
148 const char *pfx_in, /* Basename prefix for temp file. */
149 const char *suffix, /* Suffix, including any '.', for temp file. */
150 int *fd_ret, /* (return,optional) File descriptor to temp file. */
151 FILE **fp_ret /* (return,optional) FILE pointer to temp file. */
152 )
153 {
154 char *tmpfil;
155 int fd = -1;
156 mode_t oldmode = umask(077);
157
158 if (suffix == NULL) {
159 if ((tmpfil = m_mktemp2(NULL, pfx_in, fd_ret, fp_ret))) {
160 return mh_xstrdup(tmpfil);
161 }
162 return NULL;
163 }
164
165 #if HAVE_MKSTEMPS
166 if (pfx_in == NULL) {
167 tmpfil = concat(get_temp_dir(), "/nmhXXXXXX", suffix, NULL);
168 } else {
169 tmpfil = concat(get_temp_dir(), "/", pfx_in, "XXXXXX", suffix, NULL);
170 }
171
172 fd = mkstemps(tmpfil, (int) strlen(suffix));
173 #else /* ! HAVE_MKSTEMPS */
174 /* NetBSD, Solaris 10 */
175
176 if (pfx_in == NULL) {
177 tmpfil = concat(get_temp_dir(), "/nmhXXXXXX", NULL);
178 } else {
179 tmpfil = concat(get_temp_dir(), "/", pfx_in, "XXXXXX", NULL);
180 }
181
182 fd = mkstemp(tmpfil);
183 {
184 char *oldfilename = tmpfil;
185 tmpfil = concat(oldfilename, suffix, NULL);
186
187 if (rename(oldfilename, tmpfil) != 0) {
188 (void) unlink(oldfilename);
189 free(oldfilename);
190 free(tmpfil);
191 return NULL;
192 }
193
194 free(oldfilename);
195 }
196 #endif /* ! HAVE_MKSTEMPS */
197
198 if (fd < 0) {
199 umask(oldmode);
200 free(tmpfil);
201 return NULL;
202 }
203
204 register_for_removal(tmpfil);
205
206 bool keep_open = false;
207 if (fd_ret != NULL) {
208 *fd_ret = fd;
209 keep_open = true;
210 }
211 if (fp_ret != NULL) {
212 FILE *fp = fdopen(fd, "w+");
213 if (fp == NULL) {
214 int olderr = errno;
215 (void) m_unlink(tmpfil);
216 close(fd);
217 errno = olderr;
218 umask(oldmode);
219 free(tmpfil);
220 return NULL;
221 }
222 *fp_ret = fp;
223 keep_open = true;
224 }
225 if (!keep_open) {
226 close(fd);
227 }
228 umask(oldmode);
229
230 return tmpfil;
231 }
232
233
234 char *
235 get_temp_dir(void)
236 {
237 /* Ignore envvars if we are setuid */
238 if ((getuid()==geteuid()) && (getgid()==getegid())) {
239 char *tmpdir = NULL;
240 tmpdir = getenv("MHTMPDIR");
241 if (tmpdir != NULL && *tmpdir != '\0') return tmpdir;
242
243 tmpdir = getenv("TMPDIR");
244 if (tmpdir != NULL && *tmpdir != '\0') return tmpdir;
245 }
246 return m_maildir("");
247 }
248
249
250 /*
251 * Array of files (full pathnames) to remove on process exit.
252 * Instead of removing slots from the array, just set to NULL
253 * to indicate that the file should no longer be removed.
254 */
255 static svector_t exit_filelist = NULL;
256
257 /*
258 * Register a file for removal at program termination.
259 */
260 static void
261 register_for_removal(const char *pathname)
262 {
263 if (exit_filelist == NULL) exit_filelist = svector_create(20);
264 (void) svector_push_back(exit_filelist, mh_xstrdup(pathname));
265 }
266
267 /*
268 * Unregister all files that were registered to be removed at program
269 * termination. When called with remove_files of 0, this function is
270 * intended for use by one of the programs after a fork(3) without a
271 * subsequent call to one of the exec(3) functions or _exit(3). When
272 * called with non-0 remove_files argument, it is intended for use by
273 * an atexit() function.
274 *
275 * Right after a fork() and before calling exec() or _exit(), if the
276 * child catches one of the appropriate signals, it will remove
277 * all the registered temporary files. Some of those may be needed by
278 * the parent. Some nmh programs ignore or block some of the signals
279 * in the child right after fork(). For the other programs, rather
280 * than complicate things by preventing that, just take the chance
281 * that it might happen. It is harmless to attempt to unlink a
282 * temporary file twice, assuming another one wasn't created too
283 * quickly created with the same name.
284 */
285 void
286 unregister_for_removal(int remove_files)
287 {
288 if (exit_filelist) {
289 size_t i;
290
291 for (i = 0; i < svector_size(exit_filelist); ++i) {
292 char *filename = svector_at(exit_filelist, i);
293
294 if (filename) {
295 if (remove_files) (void) unlink(filename);
296 free(filename);
297 }
298 }
299
300 svector_free(exit_filelist);
301 exit_filelist = NULL;
302 }
303 }
304
305 /*
306 * If the file was registered for removal, deregister it. In
307 * any case, unlink it.
308 */
309 int
310 m_unlink(const char *pathname)
311 {
312 if (exit_filelist) {
313 char **slot = svector_find(exit_filelist, pathname);
314
315 if (slot && *slot) {
316 free(*slot);
317 *slot = NULL;
318 }
319 }
320
321 return unlink(pathname);
322 /* errno should be set to ENOENT if file was not found */
323 }
324
325 /*
326 * Remove all registered temporary files.
327 */
328 void
329 remove_registered_files_atexit(void)
330 {
331 unregister_for_removal(1);
332 }
333
334 /*
335 * Remove all registered temporary files. Suitable for use as a
336 * signal handler. If the signal is one of the usual process
337 * termination signals, calls exit(). Otherwise, disables itself
338 * by restoring the default action and then re-raises the signal,
339 * in case the use was expecting a core dump.
340 */
341 void
342 remove_registered_files(int sig)
343 {
344 struct sigaction act;
345
346 /*
347 * Ignore this signal for the duration. And we either exit() or
348 * disable this signal handler below, so don't restore this handler.
349 */
350 act.sa_handler = SIG_IGN;
351 (void) sigemptyset(&act.sa_mask);
352 act.sa_flags = 0;
353 (void) sigaction(sig, &act, 0);
354
355 if (sig == SIGHUP || sig == SIGINT || sig == SIGQUIT || sig == SIGTERM) {
356 /*
357 * We don't need to call remove_registered_files_atexit() if
358 * it was registered with atexit(), but let's not assume that.
359 * It's harmless to call it twice.
360 */
361 remove_registered_files_atexit();
362
363 exit(1);
364 } else {
365 remove_registered_files_atexit();
366
367 act.sa_handler = SIG_DFL;
368 (void) sigemptyset(&act.sa_mask);
369 act.sa_flags = 0;
370 (void) sigaction(sig, &act, 0);
371
372 (void) raise(sig);
373 }
374 }