]> git.ipfire.org Git - thirdparty/git.git/commitdiff
tempfile: a new module for handling temporary files
authorMichael Haggerty <mhagger@alum.mit.edu>
Mon, 10 Aug 2015 09:47:41 +0000 (11:47 +0200)
committerJunio C Hamano <gitster@pobox.com>
Mon, 10 Aug 2015 19:57:14 +0000 (12:57 -0700)
A lot of work went into defining the state diagram for lockfiles and
ensuring correct, race-resistant cleanup in all circumstances.

Most of that infrastructure can be applied directly to *any* temporary
file. So extract a new "tempfile" module from the "lockfile" module.
Reimplement lockfile on top of tempfile.

Subsequent commits will add more users of the new module.

Signed-off-by: Michael Haggerty <mhagger@alum.mit.edu>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
Makefile
lockfile.c
lockfile.h
tempfile.c [new file with mode: 0644]
tempfile.h [new file with mode: 0644]

index 54ec511dffb0a7b6775f360fc10deaa2008fdee9..2573f8966952ca48e87db269b80473315264c24a 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 3904803686587725d4ea219b6139a566fbace6e9..e1d68f77cadc5d93f3bb5ae39f446d4390af2c59 100644 (file)
@@ -2,90 +2,8 @@
  * Copyright (c) 2005, Junio C Hamano
  */
 
-/*
- * State diagram and cleanup
- * -------------------------
- *
- * 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.
- *
- * 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 `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.
- *
- * A lockfile is owned by the process that created it. The `lock_file`
- * has an `owner` field that records the owner's PID. This field is
- * used to prevent a forked process from closing a lockfile created by
- * its parent.
- */
-
 #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
@@ -154,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);
-       }
+       int fd;
+       struct strbuf filename = STRBUF_INIT;
 
-       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);
-       }
+       strbuf_addstr(&filename, path);
+       if (!(flags & LOCK_NO_DEREF))
+               resolve_symlink(&filename);
 
-       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(&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;
 }
 
 static int sleep_microseconds(long us)
@@ -353,109 +228,17 @@ 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;
-}
-
-const char *get_lock_file_path(struct lock_file *lk)
-{
-       if (!lk->active)
-               die("BUG: get_lock_file_path() called for unlocked object");
-       return lk->filename.buf;
-}
-
-int get_lock_file_fd(struct lock_file *lk)
-{
-       if (!lk->active)
-               die("BUG: get_lock_file_fd() called for unlocked object");
-       return lk->fd;
-}
-
-FILE *get_lock_file_fp(struct lock_file *lk)
-{
-       if (!lk->active)
-               die("BUG: get_lock_file_fp() called for unlocked object");
-       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 ||
-           strcmp(lk->filename.buf + lk->filename.len - LOCK_SUFFIX_LEN, LOCK_SUFFIX))
+       struct strbuf ret = STRBUF_INIT;
+
+       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": */
-       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);
-       }
-
-       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;
-}
-
-int commit_lock_file_to(struct lock_file *lk, const char *path)
-{
-       if (!lk->active)
-               die("BUG: attempt to commit unlocked object to \"%s\"", path);
-
-       if (close_lock_file(lk))
-               return -1;
-
-       if (rename(lk->filename.buf, path)) {
-               int save_errno = errno;
-               rollback_lock_file(lk);
-               errno = save_errno;
-               return -1;
-       }
-
-       lk->active = 0;
-       strbuf_reset(&lk->filename);
-       return 0;
+       strbuf_setlen(&ret, ret.len - LOCK_SUFFIX_LEN);
+       return strbuf_detach(&ret, NULL);
 }
 
 int commit_lock_file(struct lock_file *lk)
@@ -471,15 +254,3 @@ int commit_lock_file(struct lock_file *lk)
        free(result_path);
        return 0;
 }
-
-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 a204ab6f60d1539444f9730222215925231cd878..8131fa31b407201054896b9b29baa550cd870fe6 100644 (file)
@@ -29,6 +29,8 @@
  * 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
  * ----------------
  * `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, an
- * `atexit(3)` handler will close and remove the lockfile, thereby
- * rolling back any uncommitted changes.
+ * `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()`. 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.
+ * `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
  * --------------
  * -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: */
@@ -201,16 +199,29 @@ extern NORETURN void unable_to_lock_die(const char *path, int err);
  * error. The stream is closed automatically when `close_lock_file()`
  * is called or when the file is committed or rolled back.
  */
-extern FILE *fdopen_lock_file(struct lock_file *lk, const char *mode);
+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.
  */
-extern const char *get_lock_file_path(struct lock_file *lk);
+static inline const char *get_lock_file_path(struct lock_file *lk)
+{
+       return get_tempfile_path(&lk->tempfile);
+}
 
-extern int get_lock_file_fd(struct lock_file *lk);
-extern FILE *get_lock_file_fp(struct lock_file *lk);
+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
@@ -227,7 +238,10 @@ extern char *get_locked_file_path(struct lock_file *lk);
  * or `rollback_lock_file()` should eventually be called if
  * `close_lock_file()` succeeds.
  */
-extern int close_lock_file(struct lock_file *lk);
+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()`
@@ -248,7 +262,10 @@ extern int close_lock_file(struct lock_file *lk);
  *
  * * `commit_lock_file()` to make the final version permanent.
  */
-extern int reopen_lock_file(struct lock_file *lk);
+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
@@ -265,7 +282,10 @@ 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.
  */
-extern int commit_lock_file_to(struct lock_file *lk, const char *path);
+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
@@ -273,6 +293,9 @@ extern int commit_lock_file_to(struct lock_file *lk, const char *path);
  * for a `lock_file` object that has already been committed or rolled
  * back.
  */
-extern void rollback_lock_file(struct lock_file *lk);
+static inline void rollback_lock_file(struct lock_file *lk)
+{
+       delete_tempfile(&lk->tempfile);
+}
 
 #endif /* LOCKFILE_H */
diff --git a/tempfile.c b/tempfile.c
new file mode 100644 (file)
index 0000000..d835818
--- /dev/null
@@ -0,0 +1,238 @@
+/*
+ * 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);
+}
+
+/* Make sure errno contains a meaningful value on error */
+int create_tempfile(struct tempfile *tempfile, const char *path)
+{
+       size_t pathlen = strlen(path);
+
+       if (!tempfile_list) {
+               /* One-time initialization */
+               sigchain_push_common(remove_tempfiles_on_signal);
+               atexit(remove_tempfiles_on_exit);
+       }
+
+       if (tempfile->active)
+               die("BUG: create_tempfile 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, pathlen);
+               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: create_tempfile called for improperly-reset object");
+       }
+
+       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;
+}
+
+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..bcc229f
--- /dev/null
@@ -0,0 +1,167 @@
+#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);
+
+/*
+ * 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 */