Git fork
1/*
2 * State diagram and cleanup
3 * -------------------------
4 *
5 * If the program exits while a temporary file is active, we want to
6 * make sure that we remove it. This is done by remembering the active
7 * temporary files in a linked list, `tempfile_list`. An `atexit(3)`
8 * handler and a signal handler are registered, to clean up any active
9 * temporary files.
10 *
11 * Because the signal handler can run at any time, `tempfile_list` and
12 * the `tempfile` objects that comprise it must be kept in
13 * self-consistent states at all times.
14 *
15 * The possible states of a `tempfile` object are as follows:
16 *
17 * - Inactive/unallocated. The only way to get a tempfile is via a creation
18 * function like create_tempfile(). Once allocated, the tempfile is on the
19 * global tempfile_list and considered active.
20 *
21 * - Active, file open (after `create_tempfile()` or
22 * `reopen_tempfile()`). In this state:
23 *
24 * - the temporary file exists
25 * - `filename` holds the filename of the temporary file
26 * - `fd` holds a file descriptor open for writing to it
27 * - `fp` holds a pointer to an open `FILE` object if and only if
28 * `fdopen_tempfile()` has been called on the object
29 * - `owner` holds the PID of the process that created the file
30 *
31 * - Active, file closed (after `close_tempfile_gently()`). Same
32 * as the previous state, except that the temporary file is closed,
33 * `fd` is -1, and `fp` is `NULL`.
34 *
35 * - Inactive (after `delete_tempfile()`, `rename_tempfile()`, or a
36 * failed attempt to create a temporary file). The struct is removed from
37 * the global tempfile_list and deallocated.
38 *
39 * A temporary file is owned by the process that created it. The
40 * `tempfile` has an `owner` field that records the owner's PID. This
41 * field is used to prevent a forked process from deleting a temporary
42 * file created by its parent.
43 */
44
45#define USE_THE_REPOSITORY_VARIABLE
46
47#include "git-compat-util.h"
48#include "abspath.h"
49#include "path.h"
50#include "tempfile.h"
51#include "sigchain.h"
52
53static VOLATILE_LIST_HEAD(tempfile_list);
54
55static int remove_template_directory(struct tempfile *tempfile,
56 int in_signal_handler)
57{
58 if (tempfile->directory) {
59 if (in_signal_handler)
60 return rmdir(tempfile->directory);
61 else
62 return rmdir_or_warn(tempfile->directory);
63 }
64
65 return 0;
66}
67
68static void remove_tempfiles(int in_signal_handler)
69{
70 pid_t me = getpid();
71 volatile struct volatile_list_head *pos;
72
73 list_for_each(pos, &tempfile_list) {
74 struct tempfile *p = list_entry(pos, struct tempfile, list);
75
76 if (!is_tempfile_active(p) || p->owner != me)
77 continue;
78
79 if (p->fd >= 0)
80 close(p->fd);
81
82 if (in_signal_handler)
83 unlink(p->filename.buf);
84 else
85 unlink_or_warn(p->filename.buf);
86 remove_template_directory(p, in_signal_handler);
87 }
88}
89
90static void remove_tempfiles_on_exit(void)
91{
92 remove_tempfiles(0);
93}
94
95static void remove_tempfiles_on_signal(int signo)
96{
97 remove_tempfiles(1);
98 sigchain_pop(signo);
99 raise(signo);
100}
101
102static struct tempfile *new_tempfile(void)
103{
104 struct tempfile *tempfile = xmalloc(sizeof(*tempfile));
105 tempfile->fd = -1;
106 tempfile->fp = NULL;
107 tempfile->owner = 0;
108 INIT_LIST_HEAD(&tempfile->list);
109 strbuf_init(&tempfile->filename, 0);
110 tempfile->directory = NULL;
111 return tempfile;
112}
113
114static void activate_tempfile(struct tempfile *tempfile)
115{
116 static int initialized;
117
118 if (!initialized) {
119 sigchain_push_common(remove_tempfiles_on_signal);
120 atexit(remove_tempfiles_on_exit);
121 initialized = 1;
122 }
123
124 volatile_list_add(&tempfile->list, &tempfile_list);
125 tempfile->owner = getpid();
126}
127
128static void deactivate_tempfile(struct tempfile *tempfile)
129{
130 volatile_list_del(&tempfile->list);
131 strbuf_release(&tempfile->filename);
132 free(tempfile->directory);
133 free(tempfile);
134}
135
136/* Make sure errno contains a meaningful value on error */
137struct tempfile *create_tempfile_mode(const char *path, int mode)
138{
139 struct tempfile *tempfile = new_tempfile();
140
141 strbuf_add_absolute_path(&tempfile->filename, path);
142 tempfile->fd = open(tempfile->filename.buf,
143 O_RDWR | O_CREAT | O_EXCL | O_CLOEXEC, mode);
144 if (O_CLOEXEC && tempfile->fd < 0 && errno == EINVAL)
145 /* Try again w/o O_CLOEXEC: the kernel might not support it */
146 tempfile->fd = open(tempfile->filename.buf,
147 O_RDWR | O_CREAT | O_EXCL, mode);
148 if (tempfile->fd < 0) {
149 deactivate_tempfile(tempfile);
150 return NULL;
151 }
152 activate_tempfile(tempfile);
153 if (adjust_shared_perm(the_repository, tempfile->filename.buf)) {
154 int save_errno = errno;
155 error("cannot fix permission bits on %s", tempfile->filename.buf);
156 delete_tempfile(&tempfile);
157 errno = save_errno;
158 return NULL;
159 }
160
161 return tempfile;
162}
163
164struct tempfile *register_tempfile(const char *path)
165{
166 struct tempfile *tempfile = new_tempfile();
167 strbuf_add_absolute_path(&tempfile->filename, path);
168 activate_tempfile(tempfile);
169 return tempfile;
170}
171
172struct tempfile *mks_tempfile_sm(const char *filename_template, int suffixlen, int mode)
173{
174 struct tempfile *tempfile = new_tempfile();
175
176 strbuf_add_absolute_path(&tempfile->filename, filename_template);
177 tempfile->fd = git_mkstemps_mode(tempfile->filename.buf, suffixlen, mode);
178 if (tempfile->fd < 0) {
179 deactivate_tempfile(tempfile);
180 return NULL;
181 }
182 activate_tempfile(tempfile);
183 return tempfile;
184}
185
186struct tempfile *mks_tempfile_tsm(const char *filename_template, int suffixlen, int mode)
187{
188 struct tempfile *tempfile = new_tempfile();
189 const char *tmpdir;
190
191 tmpdir = getenv("TMPDIR");
192 if (!tmpdir)
193 tmpdir = "/tmp";
194
195 strbuf_addf(&tempfile->filename, "%s/%s", tmpdir, filename_template);
196 tempfile->fd = git_mkstemps_mode(tempfile->filename.buf, suffixlen, mode);
197 if (tempfile->fd < 0) {
198 deactivate_tempfile(tempfile);
199 return NULL;
200 }
201 activate_tempfile(tempfile);
202 return tempfile;
203}
204
205struct tempfile *mks_tempfile_dt(const char *directory_template,
206 const char *filename)
207{
208 struct tempfile *tempfile;
209 const char *tmpdir;
210 struct strbuf sb = STRBUF_INIT;
211 int fd;
212 size_t directorylen;
213
214 if (!ends_with(directory_template, "XXXXXX")) {
215 errno = EINVAL;
216 return NULL;
217 }
218
219 tmpdir = getenv("TMPDIR");
220 if (!tmpdir)
221 tmpdir = "/tmp";
222
223 strbuf_addf(&sb, "%s/%s", tmpdir, directory_template);
224 directorylen = sb.len;
225 if (!mkdtemp(sb.buf)) {
226 int orig_errno = errno;
227 strbuf_release(&sb);
228 errno = orig_errno;
229 return NULL;
230 }
231
232 strbuf_addf(&sb, "/%s", filename);
233 fd = open(sb.buf, O_CREAT | O_EXCL | O_RDWR, 0600);
234 if (fd < 0) {
235 int orig_errno = errno;
236 strbuf_setlen(&sb, directorylen);
237 rmdir(sb.buf);
238 strbuf_release(&sb);
239 errno = orig_errno;
240 return NULL;
241 }
242
243 tempfile = new_tempfile();
244 strbuf_swap(&tempfile->filename, &sb);
245 tempfile->directory = xmemdupz(tempfile->filename.buf, directorylen);
246 tempfile->fd = fd;
247 activate_tempfile(tempfile);
248 return tempfile;
249}
250
251struct tempfile *xmks_tempfile_m(const char *filename_template, int mode)
252{
253 struct tempfile *tempfile;
254 struct strbuf full_template = STRBUF_INIT;
255
256 strbuf_add_absolute_path(&full_template, filename_template);
257 tempfile = mks_tempfile_m(full_template.buf, mode);
258 if (!tempfile)
259 die_errno("Unable to create temporary file '%s'",
260 full_template.buf);
261
262 strbuf_release(&full_template);
263 return tempfile;
264}
265
266FILE *fdopen_tempfile(struct tempfile *tempfile, const char *mode)
267{
268 if (!is_tempfile_active(tempfile))
269 BUG("fdopen_tempfile() called for inactive object");
270 if (tempfile->fp)
271 BUG("fdopen_tempfile() called for open object");
272
273 tempfile->fp = fdopen(tempfile->fd, mode);
274 return tempfile->fp;
275}
276
277const char *get_tempfile_path(struct tempfile *tempfile)
278{
279 if (!is_tempfile_active(tempfile))
280 BUG("get_tempfile_path() called for inactive object");
281 return tempfile->filename.buf;
282}
283
284int get_tempfile_fd(struct tempfile *tempfile)
285{
286 if (!is_tempfile_active(tempfile))
287 BUG("get_tempfile_fd() called for inactive object");
288 return tempfile->fd;
289}
290
291FILE *get_tempfile_fp(struct tempfile *tempfile)
292{
293 if (!is_tempfile_active(tempfile))
294 BUG("get_tempfile_fp() called for inactive object");
295 return tempfile->fp;
296}
297
298int close_tempfile_gently(struct tempfile *tempfile)
299{
300 int fd;
301 FILE *fp;
302 int err;
303
304 if (!is_tempfile_active(tempfile) || tempfile->fd < 0)
305 return 0;
306
307 fd = tempfile->fd;
308 fp = tempfile->fp;
309 tempfile->fd = -1;
310 if (fp) {
311 tempfile->fp = NULL;
312 if (ferror(fp)) {
313 err = -1;
314 if (!fclose(fp))
315 errno = EIO;
316 } else {
317 err = fclose(fp);
318 }
319 } else {
320 err = close(fd);
321 }
322
323 return err ? -1 : 0;
324}
325
326int reopen_tempfile(struct tempfile *tempfile)
327{
328 if (!is_tempfile_active(tempfile))
329 BUG("reopen_tempfile called for an inactive object");
330 if (0 <= tempfile->fd)
331 BUG("reopen_tempfile called for an open object");
332 tempfile->fd = open(tempfile->filename.buf, O_WRONLY|O_TRUNC);
333 return tempfile->fd;
334}
335
336int rename_tempfile(struct tempfile **tempfile_p, const char *path)
337{
338 struct tempfile *tempfile = *tempfile_p;
339
340 if (!is_tempfile_active(tempfile))
341 BUG("rename_tempfile called for inactive object");
342
343 if (close_tempfile_gently(tempfile)) {
344 delete_tempfile(tempfile_p);
345 return -1;
346 }
347
348 if (rename(tempfile->filename.buf, path)) {
349 int save_errno = errno;
350 delete_tempfile(tempfile_p);
351 errno = save_errno;
352 return -1;
353 }
354
355 deactivate_tempfile(tempfile);
356 *tempfile_p = NULL;
357 return 0;
358}
359
360int delete_tempfile(struct tempfile **tempfile_p)
361{
362 struct tempfile *tempfile = *tempfile_p;
363 int err = 0;
364
365 if (!is_tempfile_active(tempfile))
366 return 0;
367
368 err |= close_tempfile_gently(tempfile);
369 err |= unlink_or_warn(tempfile->filename.buf);
370 err |= remove_template_directory(tempfile, 0);
371 deactivate_tempfile(tempfile);
372 *tempfile_p = NULL;
373
374 return err ? -1 : 0;
375}