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