diff options
-rw-r--r-- | Documentation/technical/api-lockfile.txt | 220 | ||||
-rw-r--r-- | Makefile | 1 | ||||
-rw-r--r-- | builtin/am.c | 1 | ||||
-rw-r--r-- | builtin/commit.c | 15 | ||||
-rw-r--r-- | builtin/gc.c | 32 | ||||
-rw-r--r-- | builtin/pull.c | 1 | ||||
-rw-r--r-- | bundle.c | 26 | ||||
-rw-r--r-- | config.c | 14 | ||||
-rw-r--r-- | credential-cache--daemon.c | 25 | ||||
-rw-r--r-- | credential-store.c | 2 | ||||
-rw-r--r-- | diff.c | 46 | ||||
-rw-r--r-- | lockfile.c | 205 | ||||
-rw-r--r-- | lockfile.h | 336 | ||||
-rw-r--r-- | read-cache.c | 40 | ||||
-rw-r--r-- | refs.c | 18 | ||||
-rw-r--r-- | shallow.c | 41 | ||||
-rw-r--r-- | tempfile.c | 305 | ||||
-rw-r--r-- | tempfile.h | 271 |
18 files changed, 969 insertions, 630 deletions
diff --git a/Documentation/technical/api-lockfile.txt b/Documentation/technical/api-lockfile.txt deleted file mode 100644 index 93b5f23e4c..0000000000 --- a/Documentation/technical/api-lockfile.txt +++ /dev/null @@ -1,220 +0,0 @@ -lockfile API -============ - -The lockfile API serves two purposes: - -* Mutual exclusion and atomic file updates. When we want to change a - file, we create a lockfile `<filename>.lock`, write the new file - contents into it, and then rename the lockfile to its final - destination `<filename>`. We create the `<filename>.lock` file with - `O_CREAT|O_EXCL` so that we can notice and fail if somebody else has - already locked the file, then atomically rename the lockfile to its - final destination to commit the changes and unlock the file. - -* Automatic cruft removal. If the program exits after we lock a file - but before the changes have been committed, we want to make sure - that we remove the lockfile. This is done by remembering the - lockfiles we have created in a linked list and setting up an - `atexit(3)` handler and a signal handler that clean up the - lockfiles. This mechanism ensures that outstanding lockfiles are - cleaned up if the program exits (including when `die()` is called) - or if the program dies on a signal. - -Please note that lockfiles only block other writers. Readers do not -block, but they are guaranteed to see either the old contents of the -file or the new contents of the file (assuming that the filesystem -implements `rename(2)` atomically). - - -Calling sequence ----------------- - -The caller: - -* Allocates a `struct lock_file` either as a static variable or on the - heap, initialized to zeros. Once you use the structure to call the - `hold_lock_file_*` family of functions, it belongs to the lockfile - subsystem and its storage must remain valid throughout the life of - the program (i.e. you cannot use an on-stack variable to hold this - structure). - -* Attempts to create a lockfile by passing that variable and the path - of the final destination (e.g. `$GIT_DIR/index`) to - `hold_lock_file_for_update` or `hold_lock_file_for_append`. - -* Writes new content for the destination file by either: - - * writing to the file descriptor returned by the `hold_lock_file_*` - functions (also available via `lock->fd`). - - * calling `fdopen_lock_file` to get a `FILE` pointer for the open - file and writing to the file using stdio. - -When finished writing, the caller can: - -* Close the file descriptor and rename the lockfile to its final - destination by calling `commit_lock_file` or `commit_lock_file_to`. - -* Close the file descriptor and remove the lockfile by calling - `rollback_lock_file`. - -* Close the file descriptor without removing or renaming the lockfile - by calling `close_lock_file`, and later call `commit_lock_file`, - `commit_lock_file_to`, `rollback_lock_file`, or `reopen_lock_file`. - -Even after the lockfile is committed or rolled back, the `lock_file` -object must not be freed or altered by the caller. However, it may be -reused; just pass it to another call of `hold_lock_file_for_update` or -`hold_lock_file_for_append`. - -If the program exits before you have called one of `commit_lock_file`, -`commit_lock_file_to`, `rollback_lock_file`, or `close_lock_file`, an -`atexit(3)` handler will close and remove the lockfile, rolling back -any uncommitted changes. - -If you need to close the file descriptor you obtained from a -`hold_lock_file_*` function yourself, do so by calling -`close_lock_file`. You should never call `close(2)` or `fclose(3)` -yourself! Otherwise the `struct lock_file` structure would still think -that the file descriptor needs to be closed, and a commit or rollback -would result in duplicate calls to `close(2)`. Worse yet, if you close -and then later open another file descriptor for a completely different -purpose, then a commit or rollback might close that unrelated file -descriptor. - - -Error handling --------------- - -The `hold_lock_file_*` functions return a file descriptor on success -or -1 on failure (unless `LOCK_DIE_ON_ERROR` is used; see below). On -errors, `errno` describes the reason for failure. Errors can be -reported by passing `errno` to one of the following helper functions: - -unable_to_lock_message:: - - Append an appropriate error message to a `strbuf`. - -unable_to_lock_error:: - - Emit an appropriate error message using `error()`. - -unable_to_lock_die:: - - Emit an appropriate error message and `die()`. - -Similarly, `commit_lock_file`, `commit_lock_file_to`, and -`close_lock_file` return 0 on success. On failure they set `errno` -appropriately, do their best to roll back the lockfile, and return -1. - - -Flags ------ - -The following flags can be passed to `hold_lock_file_for_update` or -`hold_lock_file_for_append`: - -LOCK_NO_DEREF:: - - Usually symbolic links in the destination path are resolved - and the lockfile is created by adding ".lock" to the resolved - path. If `LOCK_NO_DEREF` is set, then the lockfile is created - by adding ".lock" to the path argument itself. This option is - used, for example, when locking a symbolic reference, which - for backwards-compatibility reasons can be a symbolic link - containing the name of the referred-to-reference. - -LOCK_DIE_ON_ERROR:: - - If a lock is already taken for the file, `die()` with an error - message. If this option is not specified, trying to lock a - file that is already locked returns -1 to the caller. - - -The functions -------------- - -hold_lock_file_for_update:: - - Take a pointer to `struct lock_file`, the path of the file to - be locked (e.g. `$GIT_DIR/index`) and a flags argument (see - above). Attempt to create a lockfile for the destination and - return the file descriptor for writing to the file. - -hold_lock_file_for_append:: - - Like `hold_lock_file_for_update`, but before returning copy - the existing contents of the file (if any) to the lockfile and - position its write pointer at the end of the file. - -fdopen_lock_file:: - - Associate a stdio stream with the lockfile. Return NULL - (*without* rolling back the lockfile) on error. The stream is - closed automatically when `close_lock_file` is called or when - the file is committed or rolled back. - -get_locked_file_path:: - - Return the path of the file that is locked by the specified - lock_file object. The caller must free the memory. - -commit_lock_file:: - - Take a pointer to the `struct lock_file` initialized with an - earlier call to `hold_lock_file_for_update` or - `hold_lock_file_for_append`, close the file descriptor, and - rename the lockfile to its final destination. Return 0 upon - success. On failure, roll back the lock file and return -1, - with `errno` set to the value from the failing call to - `close(2)` or `rename(2)`. It is a bug to call - `commit_lock_file` for a `lock_file` object that is not - currently locked. - -commit_lock_file_to:: - - Like `commit_lock_file()`, except that it takes an explicit - `path` argument to which the lockfile should be renamed. The - `path` must be on the same filesystem as the lock file. - -rollback_lock_file:: - - Take a pointer to the `struct lock_file` initialized with an - earlier call to `hold_lock_file_for_update` or - `hold_lock_file_for_append`, close the file descriptor and - remove the lockfile. It is a NOOP to call - `rollback_lock_file()` for a `lock_file` object that has - already been committed or rolled back. - -close_lock_file:: - - Take a pointer to the `struct lock_file` initialized with an - earlier call to `hold_lock_file_for_update` or - `hold_lock_file_for_append`. Close the file descriptor (and - the file pointer if it has been opened using - `fdopen_lock_file`). Return 0 upon success. On failure to - `close(2)`, return a negative value and roll back the lock - file. Usually `commit_lock_file`, `commit_lock_file_to`, or - `rollback_lock_file` should eventually be called if - `close_lock_file` succeeds. - -reopen_lock_file:: - - Re-open a lockfile that has been closed (using - `close_lock_file`) but not yet committed or rolled back. This - can be used to implement a sequence of operations like the - following: - - * Lock file. - - * Write new contents to lockfile, then `close_lock_file` to - cause the contents to be written to disk. - - * Pass the name of the lockfile to another program to allow it - (and nobody else) to inspect the contents you wrote, while - still holding the lock yourself. - - * `reopen_lock_file` to reopen the lockfile. Make further - updates to the contents. - - * `commit_lock_file` to make the final version permanent. @@ -786,6 +786,7 @@ LIB_OBJS += string-list.o LIB_OBJS += submodule.o LIB_OBJS += symlinks.o LIB_OBJS += tag.o +LIB_OBJS += tempfile.o LIB_OBJS += trace.o LIB_OBJS += trailer.o LIB_OBJS += transport.o diff --git a/builtin/am.c b/builtin/am.c index 634f7a7aa7..b9c62e3de3 100644 --- a/builtin/am.c +++ b/builtin/am.c @@ -10,6 +10,7 @@ #include "dir.h" #include "run-command.h" #include "quote.h" +#include "tempfile.h" #include "lockfile.h" #include "cache-tree.h" #include "refs.h" diff --git a/builtin/commit.c b/builtin/commit.c index 4cbd5ff4de..b37cb6c8b7 100644 --- a/builtin/commit.c +++ b/builtin/commit.c @@ -324,6 +324,7 @@ static const char *prepare_index(int argc, const char **argv, const char *prefix struct string_list partial; struct pathspec pathspec; int refresh_flags = REFRESH_QUIET; + const char *ret; if (is_status) refresh_flags |= REFRESH_UNMERGED; @@ -344,7 +345,7 @@ static const char *prepare_index(int argc, const char **argv, const char *prefix die(_("unable to create temporary index")); old_index_env = getenv(INDEX_ENVIRONMENT); - setenv(INDEX_ENVIRONMENT, index_lock.filename.buf, 1); + setenv(INDEX_ENVIRONMENT, get_lock_file_path(&index_lock), 1); if (interactive_add(argc, argv, prefix, patch_interactive) != 0) die(_("interactive add failed")); @@ -355,7 +356,7 @@ static const char *prepare_index(int argc, const char **argv, const char *prefix unsetenv(INDEX_ENVIRONMENT); discard_cache(); - read_cache_from(index_lock.filename.buf); + read_cache_from(get_lock_file_path(&index_lock)); if (update_main_cache_tree(WRITE_TREE_SILENT) == 0) { if (reopen_lock_file(&index_lock) < 0) die(_("unable to write index file")); @@ -365,7 +366,7 @@ static const char *prepare_index(int argc, const char **argv, const char *prefix warning(_("Failed to update main cache tree")); commit_style = COMMIT_NORMAL; - return index_lock.filename.buf; + return get_lock_file_path(&index_lock); } /* @@ -388,7 +389,7 @@ static const char *prepare_index(int argc, const char **argv, const char *prefix if (write_locked_index(&the_index, &index_lock, CLOSE_LOCK)) die(_("unable to write new_index file")); commit_style = COMMIT_NORMAL; - return index_lock.filename.buf; + return get_lock_file_path(&index_lock); } /* @@ -475,9 +476,9 @@ static const char *prepare_index(int argc, const char **argv, const char *prefix die(_("unable to write temporary index file")); discard_cache(); - read_cache_from(false_lock.filename.buf); - - return false_lock.filename.buf; + ret = get_lock_file_path(&false_lock); + read_cache_from(ret); + return ret; } static int run_status(FILE *fp, const char *index_file, const char *prefix, int nowarn, diff --git a/builtin/gc.c b/builtin/gc.c index bcc75d9bfc..0ad8d30b56 100644 --- a/builtin/gc.c +++ b/builtin/gc.c @@ -11,6 +11,7 @@ */ #include "builtin.h" +#include "tempfile.h" #include "lockfile.h" #include "parse-options.h" #include "run-command.h" @@ -42,20 +43,7 @@ static struct argv_array prune = ARGV_ARRAY_INIT; static struct argv_array prune_worktrees = ARGV_ARRAY_INIT; static struct argv_array rerere = ARGV_ARRAY_INIT; -static char *pidfile; - -static void remove_pidfile(void) -{ - if (pidfile) - unlink(pidfile); -} - -static void remove_pidfile_on_signal(int signo) -{ - remove_pidfile(); - sigchain_pop(signo); - raise(signo); -} +static struct tempfile pidfile; static void git_config_date_string(const char *key, const char **output) { @@ -199,20 +187,22 @@ static const char *lock_repo_for_gc(int force, pid_t* ret_pid) uintmax_t pid; FILE *fp; int fd; + char *pidfile_path; - if (pidfile) + if (is_tempfile_active(&pidfile)) /* already locked */ return NULL; if (gethostname(my_host, sizeof(my_host))) strcpy(my_host, "unknown"); - fd = hold_lock_file_for_update(&lock, git_path("gc.pid"), + pidfile_path = git_pathdup("gc.pid"); + fd = hold_lock_file_for_update(&lock, pidfile_path, LOCK_DIE_ON_ERROR); if (!force) { static char locking_host[128]; int should_exit; - fp = fopen(git_path("gc.pid"), "r"); + fp = fopen(pidfile_path, "r"); memset(locking_host, 0, sizeof(locking_host)); should_exit = fp != NULL && @@ -236,6 +226,7 @@ static const char *lock_repo_for_gc(int force, pid_t* ret_pid) if (fd >= 0) rollback_lock_file(&lock); *ret_pid = pid; + free(pidfile_path); return locking_host; } } @@ -245,11 +236,8 @@ static const char *lock_repo_for_gc(int force, pid_t* ret_pid) write_in_full(fd, sb.buf, sb.len); strbuf_release(&sb); commit_lock_file(&lock); - - pidfile = git_pathdup("gc.pid"); - sigchain_push_common(remove_pidfile_on_signal); - atexit(remove_pidfile); - + register_tempfile(&pidfile, pidfile_path); + free(pidfile_path); return NULL; } diff --git a/builtin/pull.c b/builtin/pull.c index b7bc1fff5e..7e3c11ea63 100644 --- a/builtin/pull.c +++ b/builtin/pull.c @@ -15,6 +15,7 @@ #include "dir.h" #include "refs.h" #include "revision.h" +#include "tempfile.h" #include "lockfile.h" enum rebase_type { @@ -235,7 +235,9 @@ out: return result; } -static int write_pack_data(int bundle_fd, struct lock_file *lock, struct rev_info *revs) + +/* Write the pack data to bundle_fd, then close it if it is > 1. */ +static int write_pack_data(int bundle_fd, struct rev_info *revs) { struct child_process pack_objects = CHILD_PROCESS_INIT; int i; @@ -250,13 +252,6 @@ static int write_pack_data(int bundle_fd, struct lock_file *lock, struct rev_inf if (start_command(&pack_objects)) return error(_("Could not spawn pack-objects")); - /* - * start_command closed bundle_fd if it was > 1 - * so set the lock fd to -1 so commit_lock_file() - * won't fail trying to close it. - */ - lock->fd = -1; - for (i = 0; i < revs->pending.nr; i++) { struct object *object = revs->pending.objects[i].item; if (object->flags & UNINTERESTING) @@ -416,10 +411,21 @@ int create_bundle(struct bundle_header *header, const char *path, bundle_to_stdout = !strcmp(path, "-"); if (bundle_to_stdout) bundle_fd = 1; - else + else { bundle_fd = hold_lock_file_for_update(&lock, path, LOCK_DIE_ON_ERROR); + /* + * write_pack_data() will close the fd passed to it, + * but commit_lock_file() will also try to close the + * lockfile's fd. So make a copy of the file + * descriptor to avoid trying to close it twice. + */ + bundle_fd = dup(bundle_fd); + if (bundle_fd < 0) + die_errno("unable to dup file descriptor"); + } + /* write signature */ write_or_die(bundle_fd, bundle_signature, strlen(bundle_signature)); @@ -445,7 +451,7 @@ int create_bundle(struct bundle_header *header, const char *path, return -1; /* write pack */ - if (write_pack_data(bundle_fd, &lock, &revs)) + if (write_pack_data(bundle_fd, &revs)) return -1; if (!bundle_to_stdout) { @@ -2066,9 +2066,9 @@ int git_config_set_multivar_in_file(const char *config_filename, } close(in_fd); - if (chmod(lock->filename.buf, st.st_mode & 07777) < 0) { + if (chmod(get_lock_file_path(lock), st.st_mode & 07777) < 0) { error("chmod on %s failed: %s", - lock->filename.buf, strerror(errno)); + get_lock_file_path(lock), strerror(errno)); ret = CONFIG_NO_WRITE; goto out_free; } @@ -2151,7 +2151,7 @@ out_free: return ret; write_err_out: - ret = write_error(lock->filename.buf); + ret = write_error(get_lock_file_path(lock)); goto out_free; } @@ -2252,9 +2252,9 @@ int git_config_rename_section_in_file(const char *config_filename, fstat(fileno(config_file), &st); - if (chmod(lock->filename.buf, st.st_mode & 07777) < 0) { + if (chmod(get_lock_file_path(lock), st.st_mode & 07777) < 0) { ret = error("chmod on %s failed: %s", - lock->filename.buf, strerror(errno)); + get_lock_file_path(lock), strerror(errno)); goto out; } @@ -2275,7 +2275,7 @@ int git_config_rename_section_in_file(const char *config_filename, } store.baselen = strlen(new_name); if (!store_write_section(out_fd, new_name)) { - ret = write_error(lock->filename.buf); + ret = write_error(get_lock_file_path(lock)); goto out; } /* @@ -2301,7 +2301,7 @@ int git_config_rename_section_in_file(const char *config_filename, continue; length = strlen(output); if (write_in_full(out_fd, output, length) != length) { - ret = write_error(lock->filename.buf); + ret = write_error(get_lock_file_path(lock)); goto out; } } diff --git a/credential-cache--daemon.c b/credential-cache--daemon.c index c2f00498f6..eef6fce4c7 100644 --- a/credential-cache--daemon.c +++ b/credential-cache--daemon.c @@ -1,23 +1,11 @@ #include "cache.h" +#include "tempfile.h" #include "credential.h" #include "unix-socket.h" #include "sigchain.h" #include "parse-options.h" -static const char *socket_path; - -static void cleanup_socket(void) -{ - if (socket_path) - unlink(socket_path); -} - -static void cleanup_socket_on_signal(int sig) -{ - cleanup_socket(); - sigchain_pop(sig); - raise(sig); -} +static struct tempfile socket_file; struct credential_cache_entry { struct credential item; @@ -221,7 +209,6 @@ static void serve_cache(const char *socket_path, int debug) ; /* nothing */ close(fd); - unlink(socket_path); } static const char permissions_advice[] = @@ -257,6 +244,7 @@ static void check_socket_directory(const char *path) int main(int argc, const char **argv) { + const char *socket_path; static const char *usage[] = { "git-credential-cache--daemon [opts] <socket_path>", NULL @@ -273,12 +261,11 @@ int main(int argc, const char **argv) if (!socket_path) usage_with_options(usage, options); - check_socket_directory(socket_path); - - atexit(cleanup_socket); - sigchain_push_common(cleanup_socket_on_signal); + check_socket_directory(socket_path); + register_tempfile(&socket_file, socket_path); serve_cache(socket_path, debug); + delete_tempfile(&socket_file); return 0; } diff --git a/credential-store.c b/credential-store.c index f6925096ff..00aea3aa30 100644 --- a/credential-store.c +++ b/credential-store.c @@ -52,7 +52,7 @@ static void print_entry(struct credential *c) static void print_line(struct strbuf *buf) { strbuf_addch(buf, '\n'); - write_or_die(credential_lock.fd, buf->buf, buf->len); + write_or_die(get_lock_file_fd(&credential_lock), buf->buf, buf->len); } static void rewrite_credential_file(const char *fn, struct credential *c, @@ -2,6 +2,7 @@ * Copyright (C) 2005 Junio C Hamano */ #include "cache.h" +#include "tempfile.h" #include "quote.h" #include "diff.h" #include "diffcore.h" @@ -308,11 +309,26 @@ static const char *external_diff(void) return external_diff_cmd; } +/* + * Keep track of files used for diffing. Sometimes such an entry + * refers to a temporary file, sometimes to an existing file, and + * sometimes to "/dev/null". + */ static struct diff_tempfile { - const char *name; /* filename external diff should read from */ + /* + * filename external diff should read from, or NULL if this + * entry is currently not in use: + */ + const char *name; + char hex[41]; char mode[10]; - char tmp_path[PATH_MAX]; + + /* + * If this diff_tempfile instance refers to a temporary file, + * this tempfile object is used to manage its lifetime. + */ + struct tempfile tempfile; } diff_temp[2]; typedef unsigned long (*sane_truncate_fn)(char *line, unsigned long len); @@ -597,25 +613,16 @@ static struct diff_tempfile *claim_diff_tempfile(void) { die("BUG: diff is failing to clean up its tempfiles"); } -static int remove_tempfile_installed; - static void remove_tempfile(void) { int i; for (i = 0; i < ARRAY_SIZE(diff_temp); i++) { - if (diff_temp[i].name == diff_temp[i].tmp_path) - unlink_or_warn(diff_temp[i].name); + if (is_tempfile_active(&diff_temp[i].tempfile)) + delete_tempfile(&diff_temp[i].tempfile); diff_temp[i].name = NULL; } } -static void remove_tempfile_on_signal(int signo) -{ - remove_tempfile(); - sigchain_pop(signo); - raise(signo); -} - static void print_line_count(FILE *file, int count) { switch (count) { @@ -2858,8 +2865,7 @@ static void prep_temp_blob(const char *path, struct diff_tempfile *temp, strbuf_addstr(&template, "XXXXXX_"); strbuf_addstr(&template, base); - fd = git_mkstemps(temp->tmp_path, PATH_MAX, template.buf, - strlen(base) + 1); + fd = mks_tempfile_ts(&temp->tempfile, template.buf, strlen(base) + 1); if (fd < 0) die_errno("unable to create temp-file"); if (convert_to_working_tree(path, @@ -2869,8 +2875,8 @@ static void prep_temp_blob(const char *path, struct diff_tempfile *temp, } if (write_in_full(fd, blob, size) != size) die_errno("unable to write temp-file"); - close(fd); - temp->name = temp->tmp_path; + close_tempfile(&temp->tempfile); + temp->name = get_tempfile_path(&temp->tempfile); strcpy(temp->hex, sha1_to_hex(sha1)); temp->hex[40] = 0; sprintf(temp->mode, "%06o", mode); @@ -2895,12 +2901,6 @@ static struct diff_tempfile *prepare_temp_file(const char *name, return temp; } - if (!remove_tempfile_installed) { - atexit(remove_tempfile); - sigchain_push_common(remove_tempfile_on_signal); - remove_tempfile_installed = 1; - } - if (!S_ISGITLINK(one->mode) && (!one->sha1_valid || reuse_worktree_file(name, one->sha1, 1))) { diff --git a/lockfile.c b/lockfile.c index 993bb82748..637b8cf743 100644 --- a/lockfile.c +++ b/lockfile.c @@ -1,38 +1,9 @@ /* * Copyright (c) 2005, Junio C Hamano */ + #include "cache.h" #include "lockfile.h" -#include "sigchain.h" - -static struct lock_file *volatile lock_file_list; - -static void remove_lock_files(int skip_fclose) -{ - pid_t me = getpid(); - - while (lock_file_list) { - if (lock_file_list->owner == me) { - /* fclose() is not safe to call in a signal handler */ - if (skip_fclose) - lock_file_list->fp = NULL; - rollback_lock_file(lock_file_list); - } - lock_file_list = lock_file_list->next; - } -} - -static void remove_lock_files_on_exit(void) -{ - remove_lock_files(0); -} - -static void remove_lock_files_on_signal(int signo) -{ - remove_lock_files(1); - sigchain_pop(signo); - raise(signo); -} /* * path = absolute or relative path name @@ -101,60 +72,17 @@ static void resolve_symlink(struct strbuf *path) /* Make sure errno contains a meaningful value on error */ static int lock_file(struct lock_file *lk, const char *path, int flags) { - size_t pathlen = strlen(path); - - if (!lock_file_list) { - /* One-time initialization */ - sigchain_push_common(remove_lock_files_on_signal); - atexit(remove_lock_files_on_exit); - } - - if (lk->active) - die("BUG: cannot lock_file(\"%s\") using active struct lock_file", - path); - if (!lk->on_list) { - /* Initialize *lk and add it to lock_file_list: */ - lk->fd = -1; - lk->fp = NULL; - lk->active = 0; - lk->owner = 0; - strbuf_init(&lk->filename, pathlen + LOCK_SUFFIX_LEN); - lk->next = lock_file_list; - lock_file_list = lk; - lk->on_list = 1; - } else if (lk->filename.len) { - /* This shouldn't happen, but better safe than sorry. */ - die("BUG: lock_file(\"%s\") called with improperly-reset lock_file object", - path); - } + int fd; + struct strbuf filename = STRBUF_INIT; - if (flags & LOCK_NO_DEREF) { - strbuf_add_absolute_path(&lk->filename, path); - } else { - struct strbuf resolved_path = STRBUF_INIT; - - strbuf_add(&resolved_path, path, pathlen); - resolve_symlink(&resolved_path); - strbuf_add_absolute_path(&lk->filename, resolved_path.buf); - strbuf_release(&resolved_path); - } + strbuf_addstr(&filename, path); + if (!(flags & LOCK_NO_DEREF)) + resolve_symlink(&filename); - strbuf_addstr(&lk->filename, LOCK_SUFFIX); - lk->fd = open(lk->filename.buf, O_RDWR | O_CREAT | O_EXCL, 0666); - if (lk->fd < 0) { - strbuf_reset(&lk->filename); - return -1; - } - lk->owner = getpid(); - lk->active = 1; - if (adjust_shared_perm(lk->filename.buf)) { - int save_errno = errno; - error("cannot fix permission bits on %s", lk->filename.buf); - rollback_lock_file(lk); - errno = save_errno; - return -1; - } - return lk->fd; + strbuf_addstr(&filename, LOCK_SUFFIX); + fd = create_tempfile(&lk->tempfile, filename.buf); + strbuf_release(&filename); + return fd; } /* @@ -287,116 +215,29 @@ int hold_lock_file_for_append(struct lock_file *lk, const char *path, int flags) return fd; } -FILE *fdopen_lock_file(struct lock_file *lk, const char *mode) -{ - if (!lk->active) - die("BUG: fdopen_lock_file() called for unlocked object"); - if (lk->fp) - die("BUG: fdopen_lock_file() called twice for file '%s'", lk->filename.buf); - - lk->fp = fdopen(lk->fd, mode); - return lk->fp; -} - char *get_locked_file_path(struct lock_file *lk) { - if (!lk->active) - die("BUG: get_locked_file_path() called for unlocked object"); - if (lk->filename.len <= LOCK_SUFFIX_LEN) - die("BUG: get_locked_file_path() called for malformed lock object"); - return xmemdupz(lk->filename.buf, lk->filename.len - LOCK_SUFFIX_LEN); -} - -int close_lock_file(struct lock_file *lk) -{ - int fd = lk->fd; - FILE *fp = lk->fp; - int err; - - if (fd < 0) - return 0; - - lk->fd = -1; - if (fp) { - lk->fp = NULL; - - /* - * Note: no short-circuiting here; we want to fclose() - * in any case! - */ - err = ferror(fp) | fclose(fp); - } else { - err = close(fd); - } + struct strbuf ret = STRBUF_INIT; - if (err) { - int save_errno = errno; - rollback_lock_file(lk); - errno = save_errno; - return -1; - } - - return 0; -} - -int reopen_lock_file(struct lock_file *lk) -{ - if (0 <= lk->fd) - die(_("BUG: reopen a lockfile that is still open")); - if (!lk->active) - die(_("BUG: reopen a lockfile that has been committed")); - lk->fd = open(lk->filename.buf, O_WRONLY); - return lk->fd; + strbuf_addstr(&ret, get_tempfile_path(&lk->tempfile)); + if (ret.len <= LOCK_SUFFIX_LEN || + strcmp(ret.buf + ret.len - LOCK_SUFFIX_LEN, LOCK_SUFFIX)) + die("BUG: get_locked_file_path() called for malformed lock object"); + /* remove ".lock": */ + strbuf_setlen(&ret, ret.len - LOCK_SUFFIX_LEN); + return strbuf_detach(&ret, NULL); } -int commit_lock_file_to(struct lock_file *lk, const char *path) +int commit_lock_file(struct lock_file *lk) { - if (!lk->active) - die("BUG: attempt to commit unlocked object to \"%s\"", path); + char *result_path = get_locked_file_path(lk); - if (close_lock_file(lk)) - return -1; - - if (rename(lk->filename.buf, path)) { + if (commit_lock_file_to(lk, result_path)) { int save_errno = errno; - rollback_lock_file(lk); + free(result_path); errno = save_errno; return -1; } - - lk->active = 0; - strbuf_reset(&lk->filename); + free(result_path); return 0; } - -int commit_lock_file(struct lock_file *lk) -{ - static struct strbuf result_file = STRBUF_INIT; - int err; - - if (!lk->active) - die("BUG: attempt to commit unlocked object"); - - if (lk->filename.len <= LOCK_SUFFIX_LEN || - strcmp(lk->filename.buf + lk->filename.len - LOCK_SUFFIX_LEN, LOCK_SUFFIX)) - die("BUG: lockfile filename corrupt"); - - /* remove ".lock": */ - strbuf_add(&result_file, lk->filename.buf, - lk->filename.len - LOCK_SUFFIX_LEN); - err = commit_lock_file_to(lk, result_file.buf); - strbuf_reset(&result_file); - return err; -} - -void rollback_lock_file(struct lock_file *lk) -{ - if (!lk->active) - return; - - if (!close_lock_file(lk)) { - unlink_or_warn(lk->filename.buf); - lk->active = 0; - strbuf_reset(&lk->filename); - } -} diff --git a/lockfile.h b/lockfile.h index b4abc61c00..8131fa31b4 100644 --- a/lockfile.h +++ b/lockfile.h @@ -4,80 +4,162 @@ /* * File write-locks as used by Git. * - * For an overview of how to use the lockfile API, please see - * - * Documentation/technical/api-lockfile.txt - * - * This module keeps track of all locked files in lock_file_list for - * use at cleanup. This list and the lock_file objects that comprise - * it must be kept in self-consistent states at all time, because the - * program can be interrupted any time by a signal, in which case the - * signal handler will walk through the list attempting to clean up - * any open lock files. - * - * A lockfile is owned by the process that created it. The lock_file - * object has an "owner" field that records its owner. This field is - * used to prevent a forked process from closing a lockfile created by - * its parent. - * - * The possible states of a lock_file object are as follows: - * - * - Uninitialized. In this state the object's on_list field must be - * zero but the rest of its contents need not be initialized. As - * soon as the object is used in any way, it is irrevocably - * registered in the lock_file_list, and on_list is set. - * - * - Locked, lockfile open (after hold_lock_file_for_update(), - * hold_lock_file_for_append(), or reopen_lock_file()). In this - * state: - * - the lockfile exists - * - active is set - * - filename holds the filename of the lockfile - * - fd holds a file descriptor open for writing to the lockfile - * - fp holds a pointer to an open FILE object if and only if - * fdopen_lock_file() has been called on the object - * - owner holds the PID of the process that locked the file - * - * - Locked, lockfile closed (after successful close_lock_file()). - * Same as the previous state, except that the lockfile is closed - * and fd is -1. - * - * - Unlocked (after commit_lock_file(), commit_lock_file_to(), - * rollback_lock_file(), a failed attempt to lock, or a failed - * close_lock_file()). In this state: - * - active is unset - * - filename is empty (usually, though there are transitory - * states in which this condition doesn't hold). Client code should - * *not* rely on the filename being empty in this state. - * - fd is -1 - * - the object is left registered in the lock_file_list, and - * on_list is set. + * The lockfile API serves two purposes: + * + * * Mutual exclusion and atomic file updates. When we want to change + * a file, we create a lockfile `<filename>.lock`, write the new + * file contents into it, and then rename the lockfile to its final + * destination `<filename>`. We create the `<filename>.lock` file + * with `O_CREAT|O_EXCL` so that we can notice and fail if somebody + * else has already locked the file, then atomically rename the + * lockfile to its final destination to commit the changes and + * unlock the file. + * + * * Automatic cruft removal. If the program exits after we lock a + * file but before the changes have been committed, we want to make + * sure that we remove the lockfile. This is done by remembering the + * lockfiles we have created in a linked list and setting up an + * `atexit(3)` handler and a signal handler that clean up the + * lockfiles. This mechanism ensures that outstanding lockfiles are + * cleaned up if the program exits (including when `die()` is + * called) or if the program is terminated by a signal. + * + * Please note that lockfiles only block other writers. Readers do not + * block, but they are guaranteed to see either the old contents of + * the file or the new contents of the file (assuming that the + * filesystem implements `rename(2)` atomically). + * + * Most of the heavy lifting is done by the tempfile module (see + * "tempfile.h"). + * + * Calling sequence + * ---------------- + * + * The caller: + * + * * Allocates a `struct lock_file` either as a static variable or on + * the heap, initialized to zeros. Once you use the structure to + * call the `hold_lock_file_for_*()` family of functions, it belongs + * to the lockfile subsystem and its storage must remain valid + * throughout the life of the program (i.e. you cannot use an + * on-stack variable to hold this structure). + * + * * Attempts to create a lockfile by calling + * `hold_lock_file_for_update()` or `hold_lock_file_for_append()`. + * + * * Writes new content for the destination file by either: + * + * * writing to the file descriptor returned by the + * `hold_lock_file_for_*()` functions (also available via + * `lock->fd`). + * + * * calling `fdopen_lock_file()` to get a `FILE` pointer for the + * open file and writing to the file using stdio. + * + * When finished writing, the caller can: + * + * * Close the file descriptor and rename the lockfile to its final + * destination by calling `commit_lock_file()` or + * `commit_lock_file_to()`. + * + * * Close the file descriptor and remove the lockfile by calling + * `rollback_lock_file()`. + * + * * Close the file descriptor without removing or renaming the + * lockfile by calling `close_lock_file()`, and later call + * `commit_lock_file()`, `commit_lock_file_to()`, + * `rollback_lock_file()`, or `reopen_lock_file()`. + * + * Even after the lockfile is committed or rolled back, the + * `lock_file` object must not be freed or altered by the caller. + * However, it may be reused; just pass it to another call of + * `hold_lock_file_for_update()` or `hold_lock_file_for_append()`. + * + * If the program exits before `commit_lock_file()`, + * `commit_lock_file_to()`, or `rollback_lock_file()` is called, the + * tempfile module will close and remove the lockfile, thereby rolling + * back any uncommitted changes. + * + * If you need to close the file descriptor you obtained from a + * `hold_lock_file_for_*()` function yourself, do so by calling + * `close_lock_file()`. See "tempfile.h" for more information. + * + * + * Under the covers, a lockfile is just a tempfile with a few helper + * functions. In particular, the state diagram and the cleanup + * machinery are all implemented in the tempfile module. + * + * + * Error handling + * -------------- + * + * The `hold_lock_file_for_*()` functions return a file descriptor on + * success or -1 on failure (unless `LOCK_DIE_ON_ERROR` is used; see + * "flags" below). On errors, `errno` describes the reason for + * failure. Errors can be reported by passing `errno` to + * `unable_to_lock_message()` or `unable_to_lock_die()`. + * + * Similarly, `commit_lock_file`, `commit_lock_file_to`, and + * `close_lock_file` return 0 on success. On failure they set `errno` + * appropriately, do their best to roll back the lockfile, and return + * -1. */ +#include "tempfile.h" + struct lock_file { - struct lock_file *volatile next; - volatile sig_atomic_t active; - volatile int fd; - FILE *volatile fp; - volatile pid_t owner; - char on_list; - struct strbuf filename; + struct tempfile tempfile; }; /* String appended to a filename to derive the lockfile name: */ #define LOCK_SUFFIX ".lock" #define LOCK_SUFFIX_LEN 5 + +/* + * Flags + * ----- + * + * The following flags can be passed to `hold_lock_file_for_update()` + * or `hold_lock_file_for_append()`. + */ + +/* + * If a lock is already taken for the file, `die()` with an error + * message. If this flag is not specified, trying to lock a file that + * is already locked returns -1 to the caller. + */ #define LOCK_DIE_ON_ERROR 1 + +/* + * Usually symbolic links in the destination path are resolved. This + * means that (1) the lockfile is created by adding ".lock" to the + * resolved path, and (2) upon commit, the resolved path is + * overwritten. However, if `LOCK_NO_DEREF` is set, then the lockfile + * is created by adding ".lock" to the path argument itself. This + * option is used, for example, when detaching a symbolic reference, + * which for backwards-compatibility reasons, can be a symbolic link + * containing the name of the referred-to-reference. + */ #define LOCK_NO_DEREF 2 -extern void unable_to_lock_message(const char *path, int err, - struct strbuf *buf); -extern NORETURN void unable_to_lock_die(const char *path, int err); +/* + * Attempt to create a lockfile for the file at `path` and return a + * file descriptor for writing to it, or -1 on error. If the file is + * currently locked, retry with quadratic backoff for at least + * timeout_ms milliseconds. If timeout_ms is 0, try exactly once; if + * timeout_ms is -1, retry indefinitely. The flags argument and error + * handling are described above. + */ extern int hold_lock_file_for_update_timeout( struct lock_file *lk, const char *path, int flags, long timeout_ms); +/* + * Attempt to create a lockfile for the file at `path` and return a + * file descriptor for writing to it, or -1 on error. The flags + * argument and error handling are described above. + */ static inline int hold_lock_file_for_update( struct lock_file *lk, const char *path, int flags) @@ -85,15 +167,135 @@ static inline int hold_lock_file_for_update( return hold_lock_file_for_update_timeout(lk, path, flags, 0); } -extern int hold_lock_file_for_append(struct lock_file *lk, const char *path, - int flags); +/* + * Like `hold_lock_file_for_update()`, but before returning copy the + * existing contents of the file (if any) to the lockfile and position + * its write pointer at the end of the file. The flags argument and + * error handling are described above. + */ +extern int hold_lock_file_for_append(struct lock_file *lk, + const char *path, int flags); + +/* + * Append an appropriate error message to `buf` following the failure + * of `hold_lock_file_for_update()` or `hold_lock_file_for_append()` + * to lock `path`. `err` should be the `errno` set by the failing + * call. + */ +extern void unable_to_lock_message(const char *path, int err, + struct strbuf *buf); -extern FILE *fdopen_lock_file(struct lock_file *, const char *mode); -extern char *get_locked_file_path(struct lock_file *); -extern int commit_lock_file_to(struct lock_file *, const char *path); -extern int commit_lock_file(struct lock_file *); -extern int reopen_lock_file(struct lock_file *); -extern int close_lock_file(struct lock_file *); -extern void rollback_lock_file(struct lock_file *); +/* + * Emit an appropriate error message and `die()` following the failure + * of `hold_lock_file_for_update()` or `hold_lock_file_for_append()` + * to lock `path`. `err` should be the `errno` set by the failing + * call. + */ +extern NORETURN void unable_to_lock_die(const char *path, int err); + +/* + * Associate a stdio stream with the lockfile (which must still be + * open). Return `NULL` (*without* rolling back the lockfile) on + * error. The stream is closed automatically when `close_lock_file()` + * is called or when the file is committed or rolled back. + */ +static inline FILE *fdopen_lock_file(struct lock_file *lk, const char *mode) +{ + return fdopen_tempfile(&lk->tempfile, mode); +} + +/* + * Return the path of the lockfile. The return value is a pointer to a + * field within the lock_file object and should not be freed. + */ +static inline const char *get_lock_file_path(struct lock_file *lk) +{ + return get_tempfile_path(&lk->tempfile); +} + +static inline int get_lock_file_fd(struct lock_file *lk) +{ + return get_tempfile_fd(&lk->tempfile); +} + +static inline FILE *get_lock_file_fp(struct lock_file *lk) +{ + return get_tempfile_fp(&lk->tempfile); +} + +/* + * Return the path of the file that is locked by the specified + * lock_file object. The caller must free the memory. + */ +extern char *get_locked_file_path(struct lock_file *lk); + +/* + * If the lockfile is still open, close it (and the file pointer if it + * has been opened using `fdopen_lock_file()`) without renaming the + * lockfile over the file being locked. Return 0 upon success. On + * failure to `close(2)`, return a negative value and roll back the + * lock file. Usually `commit_lock_file()`, `commit_lock_file_to()`, + * or `rollback_lock_file()` should eventually be called if + * `close_lock_file()` succeeds. + */ +static inline int close_lock_file(struct lock_file *lk) +{ + return close_tempfile(&lk->tempfile); +} + +/* + * Re-open a lockfile that has been closed using `close_lock_file()` + * but not yet committed or rolled back. This can be used to implement + * a sequence of operations like the following: + * + * * Lock file. + * + * * Write new contents to lockfile, then `close_lock_file()` to + * cause the contents to be written to disk. + * + * * Pass the name of the lockfile to another program to allow it (and + * nobody else) to inspect the contents you wrote, while still + * holding the lock yourself. + * + * * `reopen_lock_file()` to reopen the lockfile. Make further updates + * to the contents. + * + * * `commit_lock_file()` to make the final version permanent. + */ +static inline int reopen_lock_file(struct lock_file *lk) +{ + return reopen_tempfile(&lk->tempfile); +} + +/* + * Commit the change represented by `lk`: close the file descriptor + * and/or file pointer if they are still open and rename the lockfile + * to its final destination. Return 0 upon success. On failure, roll + * back the lock file and return -1, with `errno` set to the value + * from the failing call to `close(2)` or `rename(2)`. It is a bug to + * call `commit_lock_file()` for a `lock_file` object that is not + * currently locked. + */ +extern int commit_lock_file(struct lock_file *lk); + +/* + * Like `commit_lock_file()`, but rename the lockfile to the provided + * `path`. `path` must be on the same filesystem as the lock file. + */ +static inline int commit_lock_file_to(struct lock_file *lk, const char *path) +{ + return rename_tempfile(&lk->tempfile, path); +} + +/* + * Roll back `lk`: close the file descriptor and/or file pointer and + * remove the lockfile. It is a NOOP to call `rollback_lock_file()` + * for a `lock_file` object that has already been committed or rolled + * back. + */ +static inline void rollback_lock_file(struct lock_file *lk) +{ + delete_tempfile(&lk->tempfile); +} #endif /* LOCKFILE_H */ diff --git a/read-cache.c b/read-cache.c index 89dbc0837a..ab1bfc94d6 100644 --- a/read-cache.c +++ b/read-cache.c @@ -5,6 +5,7 @@ */ #define NO_THE_INDEX_COMPATIBILITY_MACROS #include "cache.h" +#include "tempfile.h" #include "lockfile.h" #include "cache-tree.h" #include "refs.h" @@ -2113,7 +2114,7 @@ static int commit_locked_index(struct lock_file *lk) static int do_write_locked_index(struct index_state *istate, struct lock_file *lock, unsigned flags) { - int ret = do_write_index(istate, lock->fd, 0); + int ret = do_write_index(istate, get_lock_file_fd(lock), 0); if (ret) return ret; assert((flags & (COMMIT_LOCK | CLOSE_LOCK)) != @@ -2137,54 +2138,27 @@ static int write_split_index(struct index_state *istate, return ret; } -static char *temporary_sharedindex; - -static void remove_temporary_sharedindex(void) -{ - if (temporary_sharedindex) { - unlink_or_warn(temporary_sharedindex); - free(temporary_sharedindex); - temporary_sharedindex = NULL; - } -} - -static void remove_temporary_sharedindex_on_signal(int signo) -{ - remove_temporary_sharedindex(); - sigchain_pop(signo); - raise(signo); -} +static struct tempfile temporary_sharedindex; static int write_shared_index(struct index_state *istate, struct lock_file *lock, unsigned flags) { struct split_index *si = istate->split_index; - static int installed_handler; int fd, ret; - temporary_sharedindex = git_pathdup("sharedindex_XXXXXX"); - fd = mkstemp(temporary_sharedindex); + fd = mks_tempfile(&temporary_sharedindex, git_path("sharedindex_XXXXXX")); if (fd < 0) { - free(temporary_sharedindex); - temporary_sharedindex = NULL; hashclr(si->base_sha1); return do_write_locked_index(istate, lock, flags); } - if (!installed_handler) { - atexit(remove_temporary_sharedindex); - sigchain_push_common(remove_temporary_sharedindex_on_signal); - } move_cache_to_base_index(istate); ret = do_write_index(si->base, fd, 1); - close(fd); if (ret) { - remove_temporary_sharedindex(); + delete_tempfile(&temporary_sharedindex); return ret; } - ret = rename(temporary_sharedindex, - git_path("sharedindex.%s", sha1_to_hex(si->base->sha1))); - free(temporary_sharedindex); - temporary_sharedindex = NULL; + ret = rename_tempfile(&temporary_sharedindex, + git_path("sharedindex.%s", sha1_to_hex(si->base->sha1))); if (!ret) hashcpy(si->base_sha1, si->base->sha1); return ret; @@ -3401,6 +3401,7 @@ static int write_ref_to_lockfile(struct ref_lock *lock, { static char term = '\n'; struct object *o; + int fd; o = parse_object(sha1); if (!o) { @@ -3417,11 +3418,12 @@ static int write_ref_to_lockfile(struct ref_lock *lock, unlock_ref(lock); return -1; } - if (write_in_full(lock->lk->fd, sha1_to_hex(sha1), 40) != 40 || - write_in_full(lock->lk->fd, &term, 1) != 1 || + fd = get_lock_file_fd(lock->lk); + if (write_in_full(fd, sha1_to_hex(sha1), 40) != 40 || + write_in_full(fd, &term, 1) != 1 || close_ref(lock) < 0) { strbuf_addf(err, - "Couldn't write %s", lock->lk->filename.buf); + "Couldn't write %s", get_lock_file_path(lock->lk)); unlock_ref(lock); return -1; } @@ -4608,7 +4610,7 @@ int reflog_expire(const char *refname, const unsigned char *sha1, cb.newlog = fdopen_lock_file(&reflog_lock, "w"); if (!cb.newlog) { error("cannot fdopen %s (%s)", - reflog_lock.filename.buf, strerror(errno)); + get_lock_file_path(&reflog_lock), strerror(errno)); goto failure; } } @@ -4633,12 +4635,12 @@ int reflog_expire(const char *refname, const unsigned char *sha1, status |= error("couldn't write %s: %s", log_file, strerror(errno)); } else if (update && - (write_in_full(lock->lk->fd, + (write_in_full(get_lock_file_fd(lock->lk), sha1_to_hex(cb.last_kept_sha1), 40) != 40 || - write_str_in_full(lock->lk->fd, "\n") != 1 || - close_ref(lock) < 0)) { + write_str_in_full(get_lock_file_fd(lock->lk), "\n") != 1 || + close_ref(lock) < 0)) { status |= error("couldn't write %s", - lock->lk->filename.buf); + get_lock_file_path(lock->lk)); rollback_lock_file(&reflog_lock); } else if (commit_lock_file(&reflog_lock)) { status |= error("unable to commit reflog '%s' (%s)", @@ -1,4 +1,5 @@ #include "cache.h" +#include "tempfile.h" #include "lockfile.h" #include "commit.h" #include "tag.h" @@ -208,50 +209,28 @@ int write_shallow_commits(struct strbuf *out, int use_pack_protocol, return write_shallow_commits_1(out, use_pack_protocol, extra, 0); } -static struct strbuf temporary_shallow = STRBUF_INIT; - -static void remove_temporary_shallow(void) -{ - if (temporary_shallow.len) { - unlink_or_warn(temporary_shallow.buf); - strbuf_reset(&temporary_shallow); - } -} - -static void remove_temporary_shallow_on_signal(int signo) -{ - remove_temporary_shallow(); - sigchain_pop(signo); - raise(signo); -} +static struct tempfile temporary_shallow; const char *setup_temporary_shallow(const struct sha1_array *extra) { struct strbuf sb = STRBUF_INIT; int fd; - if (temporary_shallow.len) - die("BUG: attempt to create two temporary shallow files"); - if (write_shallow_commits(&sb, 0, extra)) { - strbuf_addstr(&temporary_shallow, git_path("shallow_XXXXXX")); - fd = xmkstemp(temporary_shallow.buf); - - atexit(remove_temporary_shallow); - sigchain_push_common(remove_temporary_shallow_on_signal); + fd = xmks_tempfile(&temporary_shallow, git_path("shallow_XXXXXX")); if (write_in_full(fd, sb.buf, sb.len) != sb.len) die_errno("failed to write to %s", - temporary_shallow.buf); - close(fd); + get_tempfile_path(&temporary_shallow)); + close_tempfile(&temporary_shallow); strbuf_release(&sb); - return temporary_shallow.buf; + return get_tempfile_path(&temporary_shallow); } /* * is_repository_shallow() sees empty string as "no shallow * file". */ - return temporary_shallow.buf; + return get_tempfile_path(&temporary_shallow); } void setup_alternate_shallow(struct lock_file *shallow_lock, @@ -267,8 +246,8 @@ void setup_alternate_shallow(struct lock_file *shallow_lock, if (write_shallow_commits(&sb, 0, extra)) { if (write_in_full(fd, sb.buf, sb.len) != sb.len) die_errno("failed to write to %s", - shallow_lock->filename.buf); - *alternate_shallow_file = shallow_lock->filename.buf; + get_lock_file_path(shallow_lock)); + *alternate_shallow_file = get_lock_file_path(shallow_lock); } else /* * is_repository_shallow() sees empty string as "no @@ -314,7 +293,7 @@ void prune_shallow(int show_only) if (write_shallow_commits_1(&sb, 0, NULL, SEEN_ONLY)) { if (write_in_full(fd, sb.buf, sb.len) != sb.len) die_errno("failed to write to %s", - shallow_lock.filename.buf); + get_lock_file_path(&shallow_lock)); commit_lock_file(&shallow_lock); } else { unlink(git_path_shallow()); diff --git a/tempfile.c b/tempfile.c new file mode 100644 index 0000000000..0af7ebf016 --- /dev/null +++ b/tempfile.c @@ -0,0 +1,305 @@ +/* + * State diagram and cleanup + * ------------------------- + * + * If the program exits while a temporary file is active, we want to + * make sure that we remove it. This is done by remembering the active + * temporary files in a linked list, `tempfile_list`. An `atexit(3)` + * handler and a signal handler are registered, to clean up any active + * temporary files. + * + * Because the signal handler can run at any time, `tempfile_list` and + * the `tempfile` objects that comprise it must be kept in + * self-consistent states at all times. + * + * The possible states of a `tempfile` object are as follows: + * + * - Uninitialized. In this state the object's `on_list` field must be + * zero but the rest of its contents need not be initialized. As + * soon as the object is used in any way, it is irrevocably + * registered in `tempfile_list`, and `on_list` is set. + * + * - Active, file open (after `create_tempfile()` or + * `reopen_tempfile()`). In this state: + * + * - the temporary file exists + * - `active` is set + * - `filename` holds the filename of the temporary file + * - `fd` holds a file descriptor open for writing to it + * - `fp` holds a pointer to an open `FILE` object if and only if + * `fdopen_tempfile()` has been called on the object + * - `owner` holds the PID of the process that created the file + * + * - Active, file closed (after successful `close_tempfile()`). Same + * as the previous state, except that the temporary file is closed, + * `fd` is -1, and `fp` is `NULL`. + * + * - Inactive (after `delete_tempfile()`, `rename_tempfile()`, a + * failed attempt to create a temporary file, or a failed + * `close_tempfile()`). In this state: + * + * - `active` is unset + * - `filename` is empty (usually, though there are transitory + * states in which this condition doesn't hold). Client code should + * *not* rely on the filename being empty in this state. + * - `fd` is -1 and `fp` is `NULL` + * - the object is left registered in the `tempfile_list`, and + * `on_list` is set. + * + * A temporary file is owned by the process that created it. The + * `tempfile` has an `owner` field that records the owner's PID. This + * field is used to prevent a forked process from deleting a temporary + * file created by its parent. + */ + +#include "cache.h" +#include "tempfile.h" +#include "sigchain.h" + +static struct tempfile *volatile tempfile_list; + +static void remove_tempfiles(int skip_fclose) +{ + pid_t me = getpid(); + + while (tempfile_list) { + if (tempfile_list->owner == me) { + /* fclose() is not safe to call in a signal handler */ + if (skip_fclose) + tempfile_list->fp = NULL; + delete_tempfile(tempfile_list); + } + tempfile_list = tempfile_list->next; + } +} + +static void remove_tempfiles_on_exit(void) +{ + remove_tempfiles(0); +} + +static void remove_tempfiles_on_signal(int signo) +{ + remove_tempfiles(1); + sigchain_pop(signo); + raise(signo); +} + +/* + * Initialize *tempfile if necessary and add it to tempfile_list. + */ +static void prepare_tempfile_object(struct tempfile *tempfile) +{ + if (!tempfile_list) { + /* One-time initialization */ + sigchain_push_common(remove_tempfiles_on_signal); + atexit(remove_tempfiles_on_exit); + } + + if (tempfile->active) + die("BUG: prepare_tempfile_object called for active object"); + if (!tempfile->on_list) { + /* Initialize *tempfile and add it to tempfile_list: */ + tempfile->fd = -1; + tempfile->fp = NULL; + tempfile->active = 0; + tempfile->owner = 0; + strbuf_init(&tempfile->filename, 0); + tempfile->next = tempfile_list; + tempfile_list = tempfile; + tempfile->on_list = 1; + } else if (tempfile->filename.len) { + /* This shouldn't happen, but better safe than sorry. */ + die("BUG: prepare_tempfile_object called for improperly-reset object"); + } +} + +/* Make sure errno contains a meaningful value on error */ +int create_tempfile(struct tempfile *tempfile, const char *path) +{ + prepare_tempfile_object(tempfile); + + strbuf_add_absolute_path(&tempfile->filename, path); + tempfile->fd = open(tempfile->filename.buf, O_RDWR | O_CREAT | O_EXCL, 0666); + if (tempfile->fd < 0) { + strbuf_reset(&tempfile->filename); + return -1; + } + tempfile->owner = getpid(); + tempfile->active = 1; + if (adjust_shared_perm(tempfile->filename.buf)) { + int save_errno = errno; + error("cannot fix permission bits on %s", tempfile->filename.buf); + delete_tempfile(tempfile); + errno = save_errno; + return -1; + } + return tempfile->fd; +} + +void register_tempfile(struct tempfile *tempfile, const char *path) +{ + prepare_tempfile_object(tempfile); + strbuf_add_absolute_path(&tempfile->filename, path); + tempfile->owner = getpid(); + tempfile->active = 1; +} + +int mks_tempfile_sm(struct tempfile *tempfile, + const char *template, int suffixlen, int mode) +{ + prepare_tempfile_object(tempfile); + + strbuf_add_absolute_path(&tempfile->filename, template); + tempfile->fd = git_mkstemps_mode(tempfile->filename.buf, suffixlen, mode); + if (tempfile->fd < 0) { + strbuf_reset(&tempfile->filename); + return -1; + } + tempfile->owner = getpid(); + tempfile->active = 1; + return tempfile->fd; +} + +int mks_tempfile_tsm(struct tempfile *tempfile, + const char *template, int suffixlen, int mode) +{ + const char *tmpdir; + + prepare_tempfile_object(tempfile); + + tmpdir = getenv("TMPDIR"); + if (!tmpdir) + tmpdir = "/tmp"; + + strbuf_addf(&tempfile->filename, "%s/%s", tmpdir, template); + tempfile->fd = git_mkstemps_mode(tempfile->filename.buf, suffixlen, mode); + if (tempfile->fd < 0) { + strbuf_reset(&tempfile->filename); + return -1; + } + tempfile->owner = getpid(); + tempfile->active = 1; + return tempfile->fd; +} + +int xmks_tempfile_m(struct tempfile *tempfile, const char *template, int mode) +{ + int fd; + struct strbuf full_template = STRBUF_INIT; + + strbuf_add_absolute_path(&full_template, template); + fd = mks_tempfile_m(tempfile, full_template.buf, mode); + if (fd < 0) + die_errno("Unable to create temporary file '%s'", + full_template.buf); + + strbuf_release(&full_template); + return fd; +} + +FILE *fdopen_tempfile(struct tempfile *tempfile, const char *mode) +{ + if (!tempfile->active) + die("BUG: fdopen_tempfile() called for inactive object"); + if (tempfile->fp) + die("BUG: fdopen_tempfile() called for open object"); + + tempfile->fp = fdopen(tempfile->fd, mode); + return tempfile->fp; +} + +const char *get_tempfile_path(struct tempfile *tempfile) +{ + if (!tempfile->active) + die("BUG: get_tempfile_path() called for inactive object"); + return tempfile->filename.buf; +} + +int get_tempfile_fd(struct tempfile *tempfile) +{ + if (!tempfile->active) + die("BUG: get_tempfile_fd() called for inactive object"); + return tempfile->fd; +} + +FILE *get_tempfile_fp(struct tempfile *tempfile) +{ + if (!tempfile->active) + die("BUG: get_tempfile_fp() called for inactive object"); + return tempfile->fp; +} + +int close_tempfile(struct tempfile *tempfile) +{ + int fd = tempfile->fd; + FILE *fp = tempfile->fp; + int err; + + if (fd < 0) + return 0; + + tempfile->fd = -1; + if (fp) { + tempfile->fp = NULL; + + /* + * Note: no short-circuiting here; we want to fclose() + * in any case! + */ + err = ferror(fp) | fclose(fp); + } else { + err = close(fd); + } + + if (err) { + int save_errno = errno; + delete_tempfile(tempfile); + errno = save_errno; + return -1; + } + + return 0; +} + +int reopen_tempfile(struct tempfile *tempfile) +{ + if (0 <= tempfile->fd) + die("BUG: reopen_tempfile called for an open object"); + if (!tempfile->active) + die("BUG: reopen_tempfile called for an inactive object"); + tempfile->fd = open(tempfile->filename.buf, O_WRONLY); + return tempfile->fd; +} + +int rename_tempfile(struct tempfile *tempfile, const char *path) +{ + if (!tempfile->active) + die("BUG: rename_tempfile called for inactive object"); + + if (close_tempfile(tempfile)) + return -1; + + if (rename(tempfile->filename.buf, path)) { + int save_errno = errno; + delete_tempfile(tempfile); + errno = save_errno; + return -1; + } + + tempfile->active = 0; + strbuf_reset(&tempfile->filename); + return 0; +} + +void delete_tempfile(struct tempfile *tempfile) +{ + if (!tempfile->active) + return; + + if (!close_tempfile(tempfile)) { + unlink_or_warn(tempfile->filename.buf); + tempfile->active = 0; + strbuf_reset(&tempfile->filename); + } +} diff --git a/tempfile.h b/tempfile.h new file mode 100644 index 0000000000..4219fe41bd --- /dev/null +++ b/tempfile.h @@ -0,0 +1,271 @@ +#ifndef TEMPFILE_H +#define TEMPFILE_H + +/* + * Handle temporary files. + * + * The tempfile API allows temporary files to be created, deleted, and + * atomically renamed. Temporary files that are still active when the + * program ends are cleaned up automatically. Lockfiles (see + * "lockfile.h") are built on top of this API. + * + * + * Calling sequence + * ---------------- + * + * The caller: + * + * * Allocates a `struct tempfile` either as a static variable or on + * the heap, initialized to zeros. Once you use the structure to + * call `create_tempfile()`, it belongs to the tempfile subsystem + * and its storage must remain valid throughout the life of the + * program (i.e. you cannot use an on-stack variable to hold this + * structure). + * + * * Attempts to create a temporary file by calling + * `create_tempfile()`. + * + * * Writes new content to the file by either: + * + * * writing to the file descriptor returned by `create_tempfile()` + * (also available via `tempfile->fd`). + * + * * calling `fdopen_tempfile()` to get a `FILE` pointer for the + * open file and writing to the file using stdio. + * + * When finished writing, the caller can: + * + * * Close the file descriptor and remove the temporary file by + * calling `delete_tempfile()`. + * + * * Close the temporary file and rename it atomically to a specified + * filename by calling `rename_tempfile()`. This relinquishes + * control of the file. + * + * * Close the file descriptor without removing or renaming the + * temporary file by calling `close_tempfile()`, and later call + * `delete_tempfile()` or `rename_tempfile()`. + * + * Even after the temporary file is renamed or deleted, the `tempfile` + * object must not be freed or altered by the caller. However, it may + * be reused; just pass it to another call of `create_tempfile()`. + * + * If the program exits before `rename_tempfile()` or + * `delete_tempfile()` is called, an `atexit(3)` handler will close + * and remove the temporary file. + * + * If you need to close the file descriptor yourself, do so by calling + * `close_tempfile()`. You should never call `close(2)` or `fclose(3)` + * yourself, otherwise the `struct tempfile` structure would still + * think that the file descriptor needs to be closed, and a later + * cleanup would result in duplicate calls to `close(2)`. Worse yet, + * if you close and then later open another file descriptor for a + * completely different purpose, then the unrelated file descriptor + * might get closed. + * + * + * Error handling + * -------------- + * + * `create_tempfile()` returns a file descriptor on success or -1 on + * failure. On errors, `errno` describes the reason for failure. + * + * `delete_tempfile()`, `rename_tempfile()`, and `close_tempfile()` + * return 0 on success. On failure they set `errno` appropriately, do + * their best to delete the temporary file, and return -1. + */ + +struct tempfile { + struct tempfile *volatile next; + volatile sig_atomic_t active; + volatile int fd; + FILE *volatile fp; + volatile pid_t owner; + char on_list; + struct strbuf filename; +}; + +/* + * Attempt to create a temporary file at the specified `path`. Return + * a file descriptor for writing to it, or -1 on error. It is an error + * if a file already exists at that path. + */ +extern int create_tempfile(struct tempfile *tempfile, const char *path); + +/* + * Register an existing file as a tempfile, meaning that it will be + * deleted when the program exits. The tempfile is considered closed, + * but it can be worked with like any other closed tempfile (for + * example, it can be opened using reopen_tempfile()). + */ +extern void register_tempfile(struct tempfile *tempfile, const char *path); + + +/* + * mks_tempfile functions + * + * The following functions attempt to create and open temporary files + * with names derived automatically from a template, in the manner of + * mkstemps(), and arrange for them to be deleted if the program ends + * before they are deleted explicitly. There is a whole family of such + * functions, named according to the following pattern: + * + * x?mks_tempfile_t?s?m?() + * + * The optional letters have the following meanings: + * + * x - die if the temporary file cannot be created. + * + * t - create the temporary file under $TMPDIR (as opposed to + * relative to the current directory). When these variants are + * used, template should be the pattern for the filename alone, + * without a path. + * + * s - template includes a suffix that is suffixlen characters long. + * + * m - the temporary file should be created with the specified mode + * (otherwise, the mode is set to 0600). + * + * None of these functions modify template. If the caller wants to + * know the (absolute) path of the file that was created, it can be + * read from tempfile->filename. + * + * On success, the functions return a file descriptor that is open for + * writing the temporary file. On errors, they return -1 and set errno + * appropriately (except for the "x" variants, which die() on errors). + */ + +/* See "mks_tempfile functions" above. */ +extern int mks_tempfile_sm(struct tempfile *tempfile, + const char *template, int suffixlen, int mode); + +/* See "mks_tempfile functions" above. */ +static inline int mks_tempfile_s(struct tempfile *tempfile, + const char *template, int suffixlen) +{ + return mks_tempfile_sm(tempfile, template, suffixlen, 0600); +} + +/* See "mks_tempfile functions" above. */ +static inline int mks_tempfile_m(struct tempfile *tempfile, + const char *template, int mode) +{ + return mks_tempfile_sm(tempfile, template, 0, mode); +} + +/* See "mks_tempfile functions" above. */ +static inline int mks_tempfile(struct tempfile *tempfile, + const char *template) +{ + return mks_tempfile_sm(tempfile, template, 0, 0600); +} + +/* See "mks_tempfile functions" above. */ +extern int mks_tempfile_tsm(struct tempfile *tempfile, + const char *template, int suffixlen, int mode); + +/* See "mks_tempfile functions" above. */ +static inline int mks_tempfile_ts(struct tempfile *tempfile, + const char *template, int suffixlen) +{ + return mks_tempfile_tsm(tempfile, template, suffixlen, 0600); +} + +/* See "mks_tempfile functions" above. */ +static inline int mks_tempfile_tm(struct tempfile *tempfile, + const char *template, int mode) +{ + return mks_tempfile_tsm(tempfile, template, 0, mode); +} + +/* See "mks_tempfile functions" above. */ +static inline int mks_tempfile_t(struct tempfile *tempfile, + const char *template) +{ + return mks_tempfile_tsm(tempfile, template, 0, 0600); +} + +/* See "mks_tempfile functions" above. */ +extern int xmks_tempfile_m(struct tempfile *tempfile, + const char *template, int mode); + +/* See "mks_tempfile functions" above. */ +static inline int xmks_tempfile(struct tempfile *tempfile, + const char *template) +{ + return xmks_tempfile_m(tempfile, template, 0600); +} + +/* + * Associate a stdio stream with the temporary file (which must still + * be open). Return `NULL` (*without* deleting the file) on error. The + * stream is closed automatically when `close_tempfile()` is called or + * when the file is deleted or renamed. + */ +extern FILE *fdopen_tempfile(struct tempfile *tempfile, const char *mode); + +static inline int is_tempfile_active(struct tempfile *tempfile) +{ + return tempfile->active; +} + +/* + * Return the path of the lockfile. The return value is a pointer to a + * field within the lock_file object and should not be freed. + */ +extern const char *get_tempfile_path(struct tempfile *tempfile); + +extern int get_tempfile_fd(struct tempfile *tempfile); +extern FILE *get_tempfile_fp(struct tempfile *tempfile); + +/* + * If the temporary file is still open, close it (and the file pointer + * too, if it has been opened using `fdopen_tempfile()`) without + * deleting the file. Return 0 upon success. On failure to `close(2)`, + * return a negative value and delete the file. Usually + * `delete_tempfile()` or `rename_tempfile()` should eventually be + * called if `close_tempfile()` succeeds. + */ +extern int close_tempfile(struct tempfile *tempfile); + +/* + * Re-open a temporary file that has been closed using + * `close_tempfile()` but not yet deleted or renamed. This can be used + * to implement a sequence of operations like the following: + * + * * Create temporary file. + * + * * Write new contents to file, then `close_tempfile()` to cause the + * contents to be written to disk. + * + * * Pass the name of the temporary file to another program to allow + * it (and nobody else) to inspect or even modify the file's + * contents. + * + * * `reopen_tempfile()` to reopen the temporary file. Make further + * updates to the contents. + * + * * `rename_tempfile()` to move the file to its permanent location. + */ +extern int reopen_tempfile(struct tempfile *tempfile); + +/* + * Close the file descriptor and/or file pointer and remove the + * temporary file associated with `tempfile`. It is a NOOP to call + * `delete_tempfile()` for a `tempfile` object that has already been + * deleted or renamed. + */ +extern void delete_tempfile(struct tempfile *tempfile); + +/* + * Close the file descriptor and/or file pointer if they are still + * open, and atomically rename the temporary file to `path`. `path` + * must be on the same filesystem as the lock file. Return 0 on + * success. On failure, delete the temporary file and return -1, with + * `errno` set to the value from the failing call to `close(2)` or + * `rename(2)`. It is a bug to call `rename_tempfile()` for a + * `tempfile` object that is not currently active. + */ +extern int rename_tempfile(struct tempfile *tempfile, const char *path); + +#endif /* TEMPFILE_H */ |