]> diplodocus.org Git - nmh/blob - sbr/m_mktemp.c
Various IMAP protocol improvements
[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 <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 *
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 mode_t oldmode = umask(077);
55
56 if (pfx_in == NULL) {
57 snprintf(tmpfil, sizeof(tmpfil), "%s/nmhXXXXXX", get_temp_dir());
58 } else {
59 snprintf(tmpfil, sizeof(tmpfil), "%sXXXXXX", pfx_in);
60 }
61
62 fd = mkstemp(tmpfil);
63
64 if (fd < 0) {
65 umask(oldmode);
66 return NULL;
67 }
68
69 register_for_removal(tmpfil);
70
71 bool keep_open = false;
72 if (fd_ret != NULL) {
73 *fd_ret = fd;
74 keep_open = true;
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 = true;
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 *
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 *
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 mode_t oldmode = umask(077);
155
156 if (suffix == NULL) {
157 if ((tmpfil = m_mktemp2(NULL, pfx_in, fd_ret, fp_ret))) {
158 return mh_xstrdup(tmpfil);
159 }
160 return NULL;
161 }
162
163 #if HAVE_MKSTEMPS
164 if (pfx_in == NULL) {
165 tmpfil = concat(get_temp_dir(), "/nmhXXXXXX", suffix, NULL);
166 } else {
167 tmpfil = concat(get_temp_dir(), "/", pfx_in, "XXXXXX", suffix, NULL);
168 }
169
170 fd = mkstemps(tmpfil, (int) strlen(suffix));
171 #else /* ! HAVE_MKSTEMPS */
172 /* NetBSD, Solaris 10 */
173
174 if (pfx_in == NULL) {
175 tmpfil = concat(get_temp_dir(), "/nmhXXXXXX", NULL);
176 } else {
177 tmpfil = concat(get_temp_dir(), "/", pfx_in, "XXXXXX", NULL);
178 }
179
180 fd = mkstemp(tmpfil);
181 {
182 char *oldfilename = tmpfil;
183 tmpfil = concat(oldfilename, suffix, NULL);
184
185 if (rename(oldfilename, tmpfil) != 0) {
186 (void) unlink(oldfilename);
187 free(oldfilename);
188 free(tmpfil);
189 return NULL;
190 }
191
192 free(oldfilename);
193 }
194 #endif /* ! HAVE_MKSTEMPS */
195
196 if (fd < 0) {
197 umask(oldmode);
198 free(tmpfil);
199 return NULL;
200 }
201
202 register_for_removal(tmpfil);
203
204 bool keep_open = false;
205 if (fd_ret != NULL) {
206 *fd_ret = fd;
207 keep_open = true;
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 = true;
222 }
223 if (!keep_open) {
224 close(fd);
225 }
226 umask(oldmode);
227
228 return tmpfil;
229 }
230
231
232 char *
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
259 register_for_removal(const char *pathname)
260 {
261 if (exit_filelist == NULL) exit_filelist = svector_create(20);
262 (void) svector_push_back(exit_filelist, mh_xstrdup(pathname));
263 }
264
265 /*
266 * Unregister all files that were registered to be removed at program
267 * termination. When called with remove_files of 0, this function is
268 * intended for use by one of the programs after a fork(3) without a
269 * subsequent call to one of the exec(3) functions or _exit(3). When
270 * called with non-0 remove_files argument, it is intended for use by
271 * an atexit() function.
272 *
273 * Right after a fork() and before calling exec() or _exit(), if the
274 * child catches one of the appropriate signals, it will remove
275 * all the registered temporary files. Some of those may be needed by
276 * the parent. Some nmh programs ignore or block some of the signals
277 * in the child right after fork(). For the other programs, rather
278 * than complicate things by preventing that, just take the chance
279 * that it might happen. It is harmless to attempt to unlink a
280 * temporary file twice, assuming another one wasn't created too
281 * quickly created with the same name.
282 */
283 void
284 unregister_for_removal(int remove_files)
285 {
286 if (exit_filelist) {
287 size_t i;
288
289 for (i = 0; i < svector_size(exit_filelist); ++i) {
290 char *filename = svector_at(exit_filelist, i);
291
292 if (filename) {
293 if (remove_files) (void) unlink(filename);
294 free(filename);
295 }
296 }
297
298 svector_free(exit_filelist);
299 exit_filelist = NULL;
300 }
301 }
302
303 /*
304 * If the file was registered for removal, deregister it. In
305 * any case, unlink it.
306 */
307 int
308 m_unlink(const char *pathname)
309 {
310 if (exit_filelist) {
311 char **slot = svector_find(exit_filelist, pathname);
312
313 if (slot && *slot) {
314 free(*slot);
315 *slot = NULL;
316 }
317 }
318
319 return unlink(pathname);
320 /* errno should be set to ENOENT if file was not found */
321 }
322
323 /*
324 * Remove all registered temporary files.
325 */
326 void
327 remove_registered_files_atexit(void)
328 {
329 unregister_for_removal(1);
330 }
331
332 /*
333 * Remove all registered temporary files. Suitable for use as a
334 * signal handler. If the signal is one of the usual process
335 * termination signals, calls exit(). Otherwise, disables itself
336 * by restoring the default action and then re-raises the signal,
337 * in case the use was expecting a core dump.
338 */
339 void
340 remove_registered_files(int sig)
341 {
342 struct sigaction act;
343
344 /*
345 * Ignore this signal for the duration. And we either exit() or
346 * disable this signal handler below, so don't restore this handler.
347 */
348 act.sa_handler = SIG_IGN;
349 (void) sigemptyset(&act.sa_mask);
350 act.sa_flags = 0;
351 (void) sigaction(sig, &act, 0);
352
353 if (sig == SIGHUP || sig == SIGINT || sig == SIGQUIT || sig == SIGTERM) {
354 /*
355 * We don't need to call remove_registered_files_atexit() if
356 * it was registered with atexit(), but let's not assume that.
357 * It's harmless to call it twice.
358 */
359 remove_registered_files_atexit();
360
361 exit(1);
362 } else {
363 remove_registered_files_atexit();
364
365 act.sa_handler = SIG_DFL;
366 (void) sigemptyset(&act.sa_mask);
367 act.sa_flags = 0;
368 (void) sigaction(sig, &act, 0);
369
370 (void) raise(sig);
371 }
372 }