git
/
tempfile.c
373 строки · 9.3 Кб
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#include "git-compat-util.h"46#include "abspath.h"47#include "path.h"48#include "tempfile.h"49#include "sigchain.h"50
51static VOLATILE_LIST_HEAD(tempfile_list);52
53static int remove_template_directory(struct tempfile *tempfile,54int in_signal_handler)55{
56if (tempfile->directory) {57if (in_signal_handler)58return rmdir(tempfile->directory);59else60return rmdir_or_warn(tempfile->directory);61}62
63return 0;64}
65
66static void remove_tempfiles(int in_signal_handler)67{
68pid_t me = getpid();69volatile struct volatile_list_head *pos;70
71list_for_each(pos, &tempfile_list) {72struct tempfile *p = list_entry(pos, struct tempfile, list);73
74if (!is_tempfile_active(p) || p->owner != me)75continue;76
77if (p->fd >= 0)78close(p->fd);79
80if (in_signal_handler)81unlink(p->filename.buf);82else83unlink_or_warn(p->filename.buf);84remove_template_directory(p, in_signal_handler);85}86}
87
88static void remove_tempfiles_on_exit(void)89{
90remove_tempfiles(0);91}
92
93static void remove_tempfiles_on_signal(int signo)94{
95remove_tempfiles(1);96sigchain_pop(signo);97raise(signo);98}
99
100static struct tempfile *new_tempfile(void)101{
102struct tempfile *tempfile = xmalloc(sizeof(*tempfile));103tempfile->fd = -1;104tempfile->fp = NULL;105tempfile->owner = 0;106INIT_LIST_HEAD(&tempfile->list);107strbuf_init(&tempfile->filename, 0);108tempfile->directory = NULL;109return tempfile;110}
111
112static void activate_tempfile(struct tempfile *tempfile)113{
114static int initialized;115
116if (!initialized) {117sigchain_push_common(remove_tempfiles_on_signal);118atexit(remove_tempfiles_on_exit);119initialized = 1;120}121
122volatile_list_add(&tempfile->list, &tempfile_list);123tempfile->owner = getpid();124}
125
126static void deactivate_tempfile(struct tempfile *tempfile)127{
128volatile_list_del(&tempfile->list);129strbuf_release(&tempfile->filename);130free(tempfile->directory);131free(tempfile);132}
133
134/* Make sure errno contains a meaningful value on error */
135struct tempfile *create_tempfile_mode(const char *path, int mode)136{
137struct tempfile *tempfile = new_tempfile();138
139strbuf_add_absolute_path(&tempfile->filename, path);140tempfile->fd = open(tempfile->filename.buf,141O_RDWR | O_CREAT | O_EXCL | O_CLOEXEC, mode);142if (O_CLOEXEC && tempfile->fd < 0 && errno == EINVAL)143/* Try again w/o O_CLOEXEC: the kernel might not support it */144tempfile->fd = open(tempfile->filename.buf,145O_RDWR | O_CREAT | O_EXCL, mode);146if (tempfile->fd < 0) {147deactivate_tempfile(tempfile);148return NULL;149}150activate_tempfile(tempfile);151if (adjust_shared_perm(tempfile->filename.buf)) {152int save_errno = errno;153error("cannot fix permission bits on %s", tempfile->filename.buf);154delete_tempfile(&tempfile);155errno = save_errno;156return NULL;157}158
159return tempfile;160}
161
162struct tempfile *register_tempfile(const char *path)163{
164struct tempfile *tempfile = new_tempfile();165strbuf_add_absolute_path(&tempfile->filename, path);166activate_tempfile(tempfile);167return tempfile;168}
169
170struct tempfile *mks_tempfile_sm(const char *filename_template, int suffixlen, int mode)171{
172struct tempfile *tempfile = new_tempfile();173
174strbuf_add_absolute_path(&tempfile->filename, filename_template);175tempfile->fd = git_mkstemps_mode(tempfile->filename.buf, suffixlen, mode);176if (tempfile->fd < 0) {177deactivate_tempfile(tempfile);178return NULL;179}180activate_tempfile(tempfile);181return tempfile;182}
183
184struct tempfile *mks_tempfile_tsm(const char *filename_template, int suffixlen, int mode)185{
186struct tempfile *tempfile = new_tempfile();187const char *tmpdir;188
189tmpdir = getenv("TMPDIR");190if (!tmpdir)191tmpdir = "/tmp";192
193strbuf_addf(&tempfile->filename, "%s/%s", tmpdir, filename_template);194tempfile->fd = git_mkstemps_mode(tempfile->filename.buf, suffixlen, mode);195if (tempfile->fd < 0) {196deactivate_tempfile(tempfile);197return NULL;198}199activate_tempfile(tempfile);200return tempfile;201}
202
203struct tempfile *mks_tempfile_dt(const char *directory_template,204const char *filename)205{
206struct tempfile *tempfile;207const char *tmpdir;208struct strbuf sb = STRBUF_INIT;209int fd;210size_t directorylen;211
212if (!ends_with(directory_template, "XXXXXX")) {213errno = EINVAL;214return NULL;215}216
217tmpdir = getenv("TMPDIR");218if (!tmpdir)219tmpdir = "/tmp";220
221strbuf_addf(&sb, "%s/%s", tmpdir, directory_template);222directorylen = sb.len;223if (!mkdtemp(sb.buf)) {224int orig_errno = errno;225strbuf_release(&sb);226errno = orig_errno;227return NULL;228}229
230strbuf_addf(&sb, "/%s", filename);231fd = open(sb.buf, O_CREAT | O_EXCL | O_RDWR, 0600);232if (fd < 0) {233int orig_errno = errno;234strbuf_setlen(&sb, directorylen);235rmdir(sb.buf);236strbuf_release(&sb);237errno = orig_errno;238return NULL;239}240
241tempfile = new_tempfile();242strbuf_swap(&tempfile->filename, &sb);243tempfile->directory = xmemdupz(tempfile->filename.buf, directorylen);244tempfile->fd = fd;245activate_tempfile(tempfile);246return tempfile;247}
248
249struct tempfile *xmks_tempfile_m(const char *filename_template, int mode)250{
251struct tempfile *tempfile;252struct strbuf full_template = STRBUF_INIT;253
254strbuf_add_absolute_path(&full_template, filename_template);255tempfile = mks_tempfile_m(full_template.buf, mode);256if (!tempfile)257die_errno("Unable to create temporary file '%s'",258full_template.buf);259
260strbuf_release(&full_template);261return tempfile;262}
263
264FILE *fdopen_tempfile(struct tempfile *tempfile, const char *mode)265{
266if (!is_tempfile_active(tempfile))267BUG("fdopen_tempfile() called for inactive object");268if (tempfile->fp)269BUG("fdopen_tempfile() called for open object");270
271tempfile->fp = fdopen(tempfile->fd, mode);272return tempfile->fp;273}
274
275const char *get_tempfile_path(struct tempfile *tempfile)276{
277if (!is_tempfile_active(tempfile))278BUG("get_tempfile_path() called for inactive object");279return tempfile->filename.buf;280}
281
282int get_tempfile_fd(struct tempfile *tempfile)283{
284if (!is_tempfile_active(tempfile))285BUG("get_tempfile_fd() called for inactive object");286return tempfile->fd;287}
288
289FILE *get_tempfile_fp(struct tempfile *tempfile)290{
291if (!is_tempfile_active(tempfile))292BUG("get_tempfile_fp() called for inactive object");293return tempfile->fp;294}
295
296int close_tempfile_gently(struct tempfile *tempfile)297{
298int fd;299FILE *fp;300int err;301
302if (!is_tempfile_active(tempfile) || tempfile->fd < 0)303return 0;304
305fd = tempfile->fd;306fp = tempfile->fp;307tempfile->fd = -1;308if (fp) {309tempfile->fp = NULL;310if (ferror(fp)) {311err = -1;312if (!fclose(fp))313errno = EIO;314} else {315err = fclose(fp);316}317} else {318err = close(fd);319}320
321return err ? -1 : 0;322}
323
324int reopen_tempfile(struct tempfile *tempfile)325{
326if (!is_tempfile_active(tempfile))327BUG("reopen_tempfile called for an inactive object");328if (0 <= tempfile->fd)329BUG("reopen_tempfile called for an open object");330tempfile->fd = open(tempfile->filename.buf, O_WRONLY|O_TRUNC);331return tempfile->fd;332}
333
334int rename_tempfile(struct tempfile **tempfile_p, const char *path)335{
336struct tempfile *tempfile = *tempfile_p;337
338if (!is_tempfile_active(tempfile))339BUG("rename_tempfile called for inactive object");340
341if (close_tempfile_gently(tempfile)) {342delete_tempfile(tempfile_p);343return -1;344}345
346if (rename(tempfile->filename.buf, path)) {347int save_errno = errno;348delete_tempfile(tempfile_p);349errno = save_errno;350return -1;351}352
353deactivate_tempfile(tempfile);354*tempfile_p = NULL;355return 0;356}
357
358int delete_tempfile(struct tempfile **tempfile_p)359{
360struct tempfile *tempfile = *tempfile_p;361int err = 0;362
363if (!is_tempfile_active(tempfile))364return 0;365
366err |= close_tempfile_gently(tempfile);367err |= unlink_or_warn(tempfile->filename.buf);368err |= remove_template_directory(tempfile, 0);369deactivate_tempfile(tempfile);370*tempfile_p = NULL;371
372return err ? -1 : 0;373}
374