Merge branch 'mh/tempfile'
authorJunio C Hamano <gitster@pobox.com>
Tue, 25 Aug 2015 21:57:09 +0000 (14:57 -0700)
committerJunio C Hamano <gitster@pobox.com>
Tue, 25 Aug 2015 21:57:09 +0000 (14:57 -0700)
The "lockfile" API has been rebuilt on top of a new "tempfile" API.

* mh/tempfile:
  credential-cache--daemon: use tempfile module
  credential-cache--daemon: delete socket from main()
  gc: use tempfile module to handle gc.pid file
  lock_repo_for_gc(): compute the path to "gc.pid" only once
  diff: use tempfile module
  setup_temporary_shallow(): use tempfile module
  write_shared_index(): use tempfile module
  register_tempfile(): new function to handle an existing temporary file
  tempfile: add several functions for creating temporary files
  prepare_tempfile_object(): new function, extracted from create_tempfile()
  tempfile: a new module for handling temporary files
  commit_lock_file(): use get_locked_file_path()
  lockfile: add accessor get_lock_file_path()
  lockfile: add accessors get_lock_file_fd() and get_lock_file_fp()
  create_bundle(): duplicate file descriptor to avoid closing it twice
  lockfile: move documentation to lockfile.h and lockfile.c

18 files changed:
Documentation/technical/api-lockfile.txt [deleted file]
Makefile
builtin/am.c
builtin/commit.c
builtin/gc.c
builtin/pull.c
bundle.c
config.c
credential-cache--daemon.c
credential-store.c
diff.c
lockfile.c
lockfile.h
read-cache.c
refs.c
shallow.c
tempfile.c [new file with mode: 0644]
tempfile.h [new file with mode: 0644]

diff --git a/Documentation/technical/api-lockfile.txt b/Documentation/technical/api-lockfile.txt
deleted file mode 100644 (file)
index 93b5f23..0000000
+++ /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.
index e39ca6c..704f7e7 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -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
index 634f7a7..b9c62e3 100644 (file)
@@ -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"
index 4cbd5ff..b37cb6c 100644 (file)
@@ -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,
index bcc75d9..0ad8d30 100644 (file)
@@ -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;
 }
 
index b7bc1ff..7e3c11e 100644 (file)
@@ -15,6 +15,7 @@
 #include "dir.h"
 #include "refs.h"
 #include "revision.h"
+#include "tempfile.h"
 #include "lockfile.h"
 
 enum rebase_type {
index f732c92..b9dacc0 100644 (file)
--- a/bundle.c
+++ b/bundle.c
@@ -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) {
index 9fd275f..a34b850 100644 (file)
--- a/config.c
+++ b/config.c
@@ -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;
                }
        }
index c2f0049..eef6fce 100644 (file)
@@ -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;
 }
index f692509..00aea3a 100644 (file)
@@ -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,
diff --git a/diff.c b/diff.c
index 7deac90..976362a 100644 (file)
--- a/diff.c
+++ b/diff.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))) {
index 993bb82..637b8cf 100644 (file)
@@ -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);
-       }
-}
index b4abc61..8131fa3 100644 (file)
 /*
  * 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 */
index 89dbc08..ab1bfc9 100644 (file)
@@ -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;
diff --git a/refs.c b/refs.c
index b0e0fe5..4e15f60 100644 (file)
--- a/refs.c
+++ b/refs.c
@@ -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)",
index 4ded4a8..d49a3d6 100644 (file)
--- a/shallow.c
+++ b/shallow.c
@@ -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 (file)
index 0000000..0af7ebf
--- /dev/null
@@ -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 (file)
index 0000000..4219fe4
--- /dev/null
@@ -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 */