git@vger.kernel.org mailing list mirror (one of many)
 help / color / mirror / code / Atom feed
* [PATCH v2 00/16] Introduce a tempfile module
@ 2015-08-10  9:47 Michael Haggerty
  2015-08-10  9:47 ` [PATCH v2 01/16] Move lockfile documentation to lockfile.h and lockfile.c Michael Haggerty
                   ` (16 more replies)
  0 siblings, 17 replies; 29+ messages in thread
From: Michael Haggerty @ 2015-08-10  9:47 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: Johannes Sixt, git, Michael Haggerty

This is a re-roll of the tempfile patch series [1]. I'm sorry for the
long delay getting v2 out. Thanks to Junio and Johannes Sixt for their
feedback about v1. I think I have addressed all of their points.

This version is very similar to v1 in spirit, though quite a few
details have changed. The main difference is that I add some more
wrapper functions for both lockfile and tempfile (a) to add some
abstraction and (b) so that users of the former don't need to know
that it is based on the latter:

* Add new lockfile wrappers around the corresponding tempfile
  functions:

  * lockfile:
    * fdopen_lock_file()
    * close_lock_file()
    * reopen_lock_file()

* Add accessors:

  * lockfile:
    * get_lock_file_path()
    * get_lock_file_fd()
    * get_lock_file_fp()

  * tempfile:
    * is_tempfile_active()
    * get_tempfile_path()
    * get_tempfile_fd()
    * get_tempfile_fp()

Other changes in this version:

* Make some trivial wrapper functions inline.

* Change create_bundle() to dup() the file descriptor that it passes
  to write_pack_data() so that it doesn't have to tinker with
  lock->tempfile.fd to prevent the file from being closed twice.

* Move some docs about the implementation from tempfile.h to
  tempfile.c.

* Rename register_tempfile_object() to prepare_tempfile_object() to
  reduce confusion with register_tempfile(). Remove its "path"
  parameter and add a docstring.

* Simplify some `die("BUG:...")` error messages.

This series applies to the same commit as v1, namely
v2.4.3-368-g7974889. There is one small conflict when merging to
master or next or (pu minus gitster/mh/tempfile).

This patch series is also available from my GitHub fork [2] as branch
"tempfile".

[1] http://thread.gmane.org/gmane.comp.version-control.git/270998
[2] https://github.com/mhagger/git

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

 Documentation/technical/api-lockfile.txt | 220 --------------------
 Makefile                                 |   1 +
 builtin/commit.c                         |  15 +-
 builtin/gc.c                             |  32 +--
 bundle.c                                 |  26 ++-
 config.c                                 |  14 +-
 credential-cache--daemon.c               |  25 +--
 credential-store.c                       |   2 +-
 diff.c                                   |  29 +--
 lockfile.c                               | 205 +++----------------
 lockfile.h                               | 336 +++++++++++++++++++++++++------
 read-cache.c                             |  40 +---
 refs.c                                   |  18 +-
 shallow.c                                |  41 +---
 tempfile.c                               | 305 ++++++++++++++++++++++++++++
 tempfile.h                               | 271 +++++++++++++++++++++++++
 16 files changed, 951 insertions(+), 629 deletions(-)
 delete mode 100644 Documentation/technical/api-lockfile.txt
 create mode 100644 tempfile.c
 create mode 100644 tempfile.h

-- 
2.5.0

^ permalink raw reply	[flat|nested] 29+ messages in thread

* [PATCH v2 01/16] Move lockfile documentation to lockfile.h and lockfile.c
  2015-08-10  9:47 [PATCH v2 00/16] Introduce a tempfile module Michael Haggerty
@ 2015-08-10  9:47 ` Michael Haggerty
  2015-08-11 19:27   ` Junio C Hamano
  2015-08-10  9:47 ` [PATCH v2 02/16] create_bundle(): duplicate file descriptor to avoid closing it twice Michael Haggerty
                   ` (15 subsequent siblings)
  16 siblings, 1 reply; 29+ messages in thread
From: Michael Haggerty @ 2015-08-10  9:47 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: Johannes Sixt, git, Michael Haggerty

Rearrange/rewrite it somewhat to fit its new environment.

Signed-off-by: Michael Haggerty <mhagger@alum.mit.edu>
---
 Documentation/technical/api-lockfile.txt | 220 -----------------------
 lockfile.c                               |  53 ++++++
 lockfile.h                               | 290 ++++++++++++++++++++++++-------
 3 files changed, 283 insertions(+), 280 deletions(-)
 delete mode 100644 Documentation/technical/api-lockfile.txt

diff --git a/Documentation/technical/api-lockfile.txt b/Documentation/technical/api-lockfile.txt
deleted file mode 100644
index 93b5f23..0000000
--- a/Documentation/technical/api-lockfile.txt
+++ /dev/null
@@ -1,220 +0,0 @@
-lockfile API
-============
-
-The lockfile API serves two purposes:
-
-* Mutual exclusion and atomic file updates. When we want to change a
-  file, we create a lockfile `<filename>.lock`, write the new file
-  contents into it, and then rename the lockfile to its final
-  destination `<filename>`. We create the `<filename>.lock` file with
-  `O_CREAT|O_EXCL` so that we can notice and fail if somebody else has
-  already locked the file, then atomically rename the lockfile to its
-  final destination to commit the changes and unlock the file.
-
-* Automatic cruft removal. If the program exits after we lock a file
-  but before the changes have been committed, we want to make sure
-  that we remove the lockfile. This is done by remembering the
-  lockfiles we have created in a linked list and setting up an
-  `atexit(3)` handler and a signal handler that clean up the
-  lockfiles. This mechanism ensures that outstanding lockfiles are
-  cleaned up if the program exits (including when `die()` is called)
-  or if the program dies on a signal.
-
-Please note that lockfiles only block other writers. Readers do not
-block, but they are guaranteed to see either the old contents of the
-file or the new contents of the file (assuming that the filesystem
-implements `rename(2)` atomically).
-
-
-Calling sequence
-----------------
-
-The caller:
-
-* Allocates a `struct lock_file` either as a static variable or on the
-  heap, initialized to zeros. Once you use the structure to call the
-  `hold_lock_file_*` family of functions, it belongs to the lockfile
-  subsystem and its storage must remain valid throughout the life of
-  the program (i.e. you cannot use an on-stack variable to hold this
-  structure).
-
-* Attempts to create a lockfile by passing that variable and the path
-  of the final destination (e.g. `$GIT_DIR/index`) to
-  `hold_lock_file_for_update` or `hold_lock_file_for_append`.
-
-* Writes new content for the destination file by either:
-
-  * writing to the file descriptor returned by the `hold_lock_file_*`
-    functions (also available via `lock->fd`).
-
-  * calling `fdopen_lock_file` to get a `FILE` pointer for the open
-    file and writing to the file using stdio.
-
-When finished writing, the caller can:
-
-* Close the file descriptor and rename the lockfile to its final
-  destination by calling `commit_lock_file` or `commit_lock_file_to`.
-
-* Close the file descriptor and remove the lockfile by calling
-  `rollback_lock_file`.
-
-* Close the file descriptor without removing or renaming the lockfile
-  by calling `close_lock_file`, and later call `commit_lock_file`,
-  `commit_lock_file_to`, `rollback_lock_file`, or `reopen_lock_file`.
-
-Even after the lockfile is committed or rolled back, the `lock_file`
-object must not be freed or altered by the caller. However, it may be
-reused; just pass it to another call of `hold_lock_file_for_update` or
-`hold_lock_file_for_append`.
-
-If the program exits before you have called one of `commit_lock_file`,
-`commit_lock_file_to`, `rollback_lock_file`, or `close_lock_file`, an
-`atexit(3)` handler will close and remove the lockfile, rolling back
-any uncommitted changes.
-
-If you need to close the file descriptor you obtained from a
-`hold_lock_file_*` function yourself, do so by calling
-`close_lock_file`. You should never call `close(2)` or `fclose(3)`
-yourself! Otherwise the `struct lock_file` structure would still think
-that the file descriptor needs to be closed, and a commit or rollback
-would result in duplicate calls to `close(2)`. Worse yet, if you close
-and then later open another file descriptor for a completely different
-purpose, then a commit or rollback might close that unrelated file
-descriptor.
-
-
-Error handling
---------------
-
-The `hold_lock_file_*` functions return a file descriptor on success
-or -1 on failure (unless `LOCK_DIE_ON_ERROR` is used; see below). On
-errors, `errno` describes the reason for failure. Errors can be
-reported by passing `errno` to one of the following helper functions:
-
-unable_to_lock_message::
-
-	Append an appropriate error message to a `strbuf`.
-
-unable_to_lock_error::
-
-	Emit an appropriate error message using `error()`.
-
-unable_to_lock_die::
-
-	Emit an appropriate error message and `die()`.
-
-Similarly, `commit_lock_file`, `commit_lock_file_to`, and
-`close_lock_file` return 0 on success. On failure they set `errno`
-appropriately, do their best to roll back the lockfile, and return -1.
-
-
-Flags
------
-
-The following flags can be passed to `hold_lock_file_for_update` or
-`hold_lock_file_for_append`:
-
-LOCK_NO_DEREF::
-
-	Usually symbolic links in the destination path are resolved
-	and the lockfile is created by adding ".lock" to the resolved
-	path. If `LOCK_NO_DEREF` is set, then the lockfile is created
-	by adding ".lock" to the path argument itself. This option is
-	used, for example, when locking a symbolic reference, which
-	for backwards-compatibility reasons can be a symbolic link
-	containing the name of the referred-to-reference.
-
-LOCK_DIE_ON_ERROR::
-
-	If a lock is already taken for the file, `die()` with an error
-	message. If this option is not specified, trying to lock a
-	file that is already locked returns -1 to the caller.
-
-
-The functions
--------------
-
-hold_lock_file_for_update::
-
-	Take a pointer to `struct lock_file`, the path of the file to
-	be locked (e.g. `$GIT_DIR/index`) and a flags argument (see
-	above). Attempt to create a lockfile for the destination and
-	return the file descriptor for writing to the file.
-
-hold_lock_file_for_append::
-
-	Like `hold_lock_file_for_update`, but before returning copy
-	the existing contents of the file (if any) to the lockfile and
-	position its write pointer at the end of the file.
-
-fdopen_lock_file::
-
-	Associate a stdio stream with the lockfile. Return NULL
-	(*without* rolling back the lockfile) on error. The stream is
-	closed automatically when `close_lock_file` is called or when
-	the file is committed or rolled back.
-
-get_locked_file_path::
-
-	Return the path of the file that is locked by the specified
-	lock_file object. The caller must free the memory.
-
-commit_lock_file::
-
-	Take a pointer to the `struct lock_file` initialized with an
-	earlier call to `hold_lock_file_for_update` or
-	`hold_lock_file_for_append`, close the file descriptor, and
-	rename the lockfile to its final destination. Return 0 upon
-	success. On failure, roll back the lock file and return -1,
-	with `errno` set to the value from the failing call to
-	`close(2)` or `rename(2)`. It is a bug to call
-	`commit_lock_file` for a `lock_file` object that is not
-	currently locked.
-
-commit_lock_file_to::
-
-	Like `commit_lock_file()`, except that it takes an explicit
-	`path` argument to which the lockfile should be renamed. The
-	`path` must be on the same filesystem as the lock file.
-
-rollback_lock_file::
-
-	Take a pointer to the `struct lock_file` initialized with an
-	earlier call to `hold_lock_file_for_update` or
-	`hold_lock_file_for_append`, close the file descriptor and
-	remove the lockfile. It is a NOOP to call
-	`rollback_lock_file()` for a `lock_file` object that has
-	already been committed or rolled back.
-
-close_lock_file::
-
-	Take a pointer to the `struct lock_file` initialized with an
-	earlier call to `hold_lock_file_for_update` or
-	`hold_lock_file_for_append`. Close the file descriptor (and
-	the file pointer if it has been opened using
-	`fdopen_lock_file`). Return 0 upon success. On failure to
-	`close(2)`, return a negative value and roll back the lock
-	file. Usually `commit_lock_file`, `commit_lock_file_to`, or
-	`rollback_lock_file` should eventually be called if
-	`close_lock_file` succeeds.
-
-reopen_lock_file::
-
-	Re-open a lockfile that has been closed (using
-	`close_lock_file`) but not yet committed or rolled back. This
-	can be used to implement a sequence of operations like the
-	following:
-
-	* Lock file.
-
-	* Write new contents to lockfile, then `close_lock_file` to
-	  cause the contents to be written to disk.
-
-	* Pass the name of the lockfile to another program to allow it
-	  (and nobody else) to inspect the contents you wrote, while
-	  still holding the lock yourself.
-
-	* `reopen_lock_file` to reopen the lockfile. Make further
-	  updates to the contents.
-
-	* `commit_lock_file` to make the final version permanent.
diff --git a/lockfile.c b/lockfile.c
index 5a93bc7..2369eff 100644
--- a/lockfile.c
+++ b/lockfile.c
@@ -1,6 +1,59 @@
 /*
  * 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"
diff --git a/lockfile.h b/lockfile.h
index b4abc61..a483cc9 100644
--- a/lockfile.h
+++ b/lockfile.h
@@ -4,54 +4,103 @@
 /*
  * 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).
+ *
+ *
+ * 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, an
+ * `atexit(3)` handler 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.
+ *
+ * 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.
  */
 
 struct lock_file {
@@ -68,16 +117,51 @@ struct lock_file {
 #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 +169,101 @@ 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.
+ */
+extern FILE *fdopen_lock_file(struct lock_file *lk, const char *mode);
+
+/*
+ * 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.
+ */
+extern int close_lock_file(struct lock_file *lk);
+
+/*
+ * 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.
+ */
+extern int reopen_lock_file(struct lock_file *lk);
+
+/*
+ * 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.
+ */
+extern int commit_lock_file_to(struct lock_file *lk, const char *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.
+ */
+extern void rollback_lock_file(struct lock_file *lk);
 
 #endif /* LOCKFILE_H */
-- 
2.5.0

^ permalink raw reply related	[flat|nested] 29+ messages in thread

* [PATCH v2 02/16] create_bundle(): duplicate file descriptor to avoid closing it twice
  2015-08-10  9:47 [PATCH v2 00/16] Introduce a tempfile module Michael Haggerty
  2015-08-10  9:47 ` [PATCH v2 01/16] Move lockfile documentation to lockfile.h and lockfile.c Michael Haggerty
@ 2015-08-10  9:47 ` Michael Haggerty
  2015-08-10  9:47 ` [PATCH v2 03/16] lockfile: add accessors get_lock_file_fd() and get_lock_file_fp() Michael Haggerty
                   ` (14 subsequent siblings)
  16 siblings, 0 replies; 29+ messages in thread
From: Michael Haggerty @ 2015-08-10  9:47 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: Johannes Sixt, git, Michael Haggerty

write_pack_data() passes bundle_fd to start_command() to be used as
the stdout of pack-objects. But start_command() closes its stdout if
it is > 1. This is a problem if bundle_fd is the fd of a lock_file,
because commit_lock_file() will also try to close the fd.

So the old code suppressed commit_lock_file()'s usual behavior of
closing the file descriptor by setting the lock_file object's fd field
to -1.

But this is not really kosher. Code here shouldn't be mutating fields
within the lock_file object.

Instead, duplicate the file descriptor before passing it to
write_pack_data(). Then that function can close its copy without
closing the copy held in the lock_file object.

Signed-off-by: Michael Haggerty <mhagger@alum.mit.edu>
---
This is new since v1. I like that it is better decoupled than the old
code, but let me know if you think otherwise.

Actually, it seems to me that start_command()'s special case of not
closing fd==0 is weird. I suppose that is because fd==0 is used to
mean "no redirections" whereas 0 also happens to be the fd for stdin.
But I don't want to dig into that now.

 bundle.c | 26 ++++++++++++++++----------
 1 file changed, 16 insertions(+), 10 deletions(-)

diff --git a/bundle.c b/bundle.c
index f732c92..b9dacc0 100644
--- 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) {
-- 
2.5.0

^ permalink raw reply related	[flat|nested] 29+ messages in thread

* [PATCH v2 03/16] lockfile: add accessors get_lock_file_fd() and get_lock_file_fp()
  2015-08-10  9:47 [PATCH v2 00/16] Introduce a tempfile module Michael Haggerty
  2015-08-10  9:47 ` [PATCH v2 01/16] Move lockfile documentation to lockfile.h and lockfile.c Michael Haggerty
  2015-08-10  9:47 ` [PATCH v2 02/16] create_bundle(): duplicate file descriptor to avoid closing it twice Michael Haggerty
@ 2015-08-10  9:47 ` Michael Haggerty
  2015-08-11 19:29   ` Junio C Hamano
  2015-08-10  9:47 ` [PATCH v2 04/16] lockfile: add accessor get_lock_file_path() Michael Haggerty
                   ` (13 subsequent siblings)
  16 siblings, 1 reply; 29+ messages in thread
From: Michael Haggerty @ 2015-08-10  9:47 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: Johannes Sixt, git, Michael Haggerty

We are about to move those members, so change client code to read them
through accessor functions.

Signed-off-by: Michael Haggerty <mhagger@alum.mit.edu>
---
 credential-store.c |  2 +-
 lockfile.c         | 14 ++++++++++++++
 lockfile.h         |  3 +++
 read-cache.c       |  2 +-
 refs.c             | 12 +++++++-----
 5 files changed, 26 insertions(+), 7 deletions(-)

diff --git a/credential-store.c b/credential-store.c
index f692509..00aea3a 100644
--- a/credential-store.c
+++ b/credential-store.c
@@ -52,7 +52,7 @@ static void print_entry(struct credential *c)
 static void print_line(struct strbuf *buf)
 {
 	strbuf_addch(buf, '\n');
-	write_or_die(credential_lock.fd, buf->buf, buf->len);
+	write_or_die(get_lock_file_fd(&credential_lock), buf->buf, buf->len);
 }
 
 static void rewrite_credential_file(const char *fn, struct credential *c,
diff --git a/lockfile.c b/lockfile.c
index 2369eff..df9c704 100644
--- a/lockfile.c
+++ b/lockfile.c
@@ -364,6 +364,20 @@ FILE *fdopen_lock_file(struct lock_file *lk, const char *mode)
 	return lk->fp;
 }
 
+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)
diff --git a/lockfile.h b/lockfile.h
index a483cc9..d9dfbc9 100644
--- a/lockfile.h
+++ b/lockfile.h
@@ -203,6 +203,9 @@ extern NORETURN void unable_to_lock_die(const char *path, int err);
  */
 extern FILE *fdopen_lock_file(struct lock_file *lk, const char *mode);
 
+extern int get_lock_file_fd(struct lock_file *lk);
+extern FILE *get_lock_file_fp(struct lock_file *lk);
+
 /*
  * Return the path of the file that is locked by the specified
  * lock_file object. The caller must free the memory.
diff --git a/read-cache.c b/read-cache.c
index 723d48d..96cb9a3 100644
--- a/read-cache.c
+++ b/read-cache.c
@@ -2112,7 +2112,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)) !=
diff --git a/refs.c b/refs.c
index a742d79..0f49a62 100644
--- a/refs.c
+++ b/refs.c
@@ -3162,6 +3162,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) {
@@ -3178,8 +3179,9 @@ static int write_ref_to_lockfile(struct ref_lock *lock,
 		errno = EINVAL;
 		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) {
 		int save_errno = errno;
 		error("Couldn't write %s", lock->lk->filename.buf);
@@ -4264,10 +4266,10 @@ 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);
 			rollback_lock_file(&reflog_lock);
-- 
2.5.0

^ permalink raw reply related	[flat|nested] 29+ messages in thread

* [PATCH v2 04/16] lockfile: add accessor get_lock_file_path()
  2015-08-10  9:47 [PATCH v2 00/16] Introduce a tempfile module Michael Haggerty
                   ` (2 preceding siblings ...)
  2015-08-10  9:47 ` [PATCH v2 03/16] lockfile: add accessors get_lock_file_fd() and get_lock_file_fp() Michael Haggerty
@ 2015-08-10  9:47 ` Michael Haggerty
  2015-08-11 19:36   ` Junio C Hamano
  2015-08-10  9:47 ` [PATCH v2 05/16] commit_lock_file(): use get_locked_file_path() Michael Haggerty
                   ` (12 subsequent siblings)
  16 siblings, 1 reply; 29+ messages in thread
From: Michael Haggerty @ 2015-08-10  9:47 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: Johannes Sixt, git, Michael Haggerty

Signed-off-by: Michael Haggerty <mhagger@alum.mit.edu>
---
 builtin/commit.c | 15 ++++++++-------
 config.c         | 14 +++++++-------
 lockfile.c       |  7 +++++++
 lockfile.h       |  6 ++++++
 refs.c           |  6 +++---
 shallow.c        |  6 +++---
 6 files changed, 34 insertions(+), 20 deletions(-)

diff --git a/builtin/commit.c b/builtin/commit.c
index 254477f..96aee0c 100644
--- a/builtin/commit.c
+++ b/builtin/commit.c
@@ -324,6 +324,7 @@ static const char *prepare_index(int argc, const char **argv, const char *prefix
 	struct string_list partial;
 	struct pathspec pathspec;
 	int refresh_flags = REFRESH_QUIET;
+	const char *ret;
 
 	if (is_status)
 		refresh_flags |= REFRESH_UNMERGED;
@@ -344,7 +345,7 @@ static const char *prepare_index(int argc, const char **argv, const char *prefix
 			die(_("unable to create temporary index"));
 
 		old_index_env = getenv(INDEX_ENVIRONMENT);
-		setenv(INDEX_ENVIRONMENT, index_lock.filename.buf, 1);
+		setenv(INDEX_ENVIRONMENT, get_lock_file_path(&index_lock), 1);
 
 		if (interactive_add(argc, argv, prefix, patch_interactive) != 0)
 			die(_("interactive add failed"));
@@ -355,7 +356,7 @@ static const char *prepare_index(int argc, const char **argv, const char *prefix
 			unsetenv(INDEX_ENVIRONMENT);
 
 		discard_cache();
-		read_cache_from(index_lock.filename.buf);
+		read_cache_from(get_lock_file_path(&index_lock));
 		if (update_main_cache_tree(WRITE_TREE_SILENT) == 0) {
 			if (reopen_lock_file(&index_lock) < 0)
 				die(_("unable to write index file"));
@@ -365,7 +366,7 @@ static const char *prepare_index(int argc, const char **argv, const char *prefix
 			warning(_("Failed to update main cache tree"));
 
 		commit_style = COMMIT_NORMAL;
-		return index_lock.filename.buf;
+		return get_lock_file_path(&index_lock);
 	}
 
 	/*
@@ -388,7 +389,7 @@ static const char *prepare_index(int argc, const char **argv, const char *prefix
 		if (write_locked_index(&the_index, &index_lock, CLOSE_LOCK))
 			die(_("unable to write new_index file"));
 		commit_style = COMMIT_NORMAL;
-		return index_lock.filename.buf;
+		return get_lock_file_path(&index_lock);
 	}
 
 	/*
@@ -475,9 +476,9 @@ static const char *prepare_index(int argc, const char **argv, const char *prefix
 		die(_("unable to write temporary index file"));
 
 	discard_cache();
-	read_cache_from(false_lock.filename.buf);
-
-	return false_lock.filename.buf;
+	ret = get_lock_file_path(&false_lock);
+	read_cache_from(ret);
+	return ret;
 }
 
 static int run_status(FILE *fp, const char *index_file, const char *prefix, int nowarn,
diff --git a/config.c b/config.c
index ab46462..adf8b53 100644
--- a/config.c
+++ b/config.c
@@ -2056,9 +2056,9 @@ int git_config_set_multivar_in_file(const char *config_filename,
 			MAP_PRIVATE, in_fd, 0);
 		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;
 		}
@@ -2138,7 +2138,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;
 
 }
@@ -2239,9 +2239,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;
 	}
 
@@ -2262,7 +2262,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;
 				}
 				/*
@@ -2288,7 +2288,7 @@ int git_config_rename_section_in_file(const char *config_filename,
 			continue;
 		length = strlen(output);
 		if (write_in_full(out_fd, output, length) != length) {
-			ret = write_error(lock->filename.buf);
+			ret = write_error(get_lock_file_path(lock));
 			goto out;
 		}
 	}
diff --git a/lockfile.c b/lockfile.c
index df9c704..5e954ba 100644
--- a/lockfile.c
+++ b/lockfile.c
@@ -364,6 +364,13 @@ FILE *fdopen_lock_file(struct lock_file *lk, const char *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)
diff --git a/lockfile.h b/lockfile.h
index d9dfbc9..a204ab6 100644
--- a/lockfile.h
+++ b/lockfile.h
@@ -203,6 +203,12 @@ extern NORETURN void unable_to_lock_die(const char *path, int err);
  */
 extern FILE *fdopen_lock_file(struct lock_file *lk, const char *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);
+
 extern int get_lock_file_fd(struct lock_file *lk);
 extern FILE *get_lock_file_fp(struct lock_file *lk);
 
diff --git a/refs.c b/refs.c
index 0f49a62..bf68015 100644
--- a/refs.c
+++ b/refs.c
@@ -3184,7 +3184,7 @@ static int write_ref_to_lockfile(struct ref_lock *lock,
 	    write_in_full(fd, &term, 1) != 1 ||
 	    close_ref(lock) < 0) {
 		int save_errno = errno;
-		error("Couldn't write %s", lock->lk->filename.buf);
+		error("Couldn't write %s", get_lock_file_path(lock->lk));
 		unlock_ref(lock);
 		errno = save_errno;
 		return -1;
@@ -4241,7 +4241,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;
 		}
 	}
@@ -4271,7 +4271,7 @@ int reflog_expire(const char *refname, const unsigned char *sha1,
 			    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)",
diff --git a/shallow.c b/shallow.c
index 257d811..7973e74 100644
--- a/shallow.c
+++ b/shallow.c
@@ -267,8 +267,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 +314,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"));
-- 
2.5.0

^ permalink raw reply related	[flat|nested] 29+ messages in thread

* [PATCH v2 05/16] commit_lock_file(): use get_locked_file_path()
  2015-08-10  9:47 [PATCH v2 00/16] Introduce a tempfile module Michael Haggerty
                   ` (3 preceding siblings ...)
  2015-08-10  9:47 ` [PATCH v2 04/16] lockfile: add accessor get_lock_file_path() Michael Haggerty
@ 2015-08-10  9:47 ` Michael Haggerty
  2015-08-10  9:47 ` [PATCH v2 06/16] tempfile: a new module for handling temporary files Michael Haggerty
                   ` (11 subsequent siblings)
  16 siblings, 0 replies; 29+ messages in thread
From: Michael Haggerty @ 2015-08-10  9:47 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: Johannes Sixt, git, Michael Haggerty

First beef up the sanity checking in get_locked_file_path() to match
that in commit_lock_file(). Then rewrite commit_lock_file() to use
get_locked_file_path() for its pathname computation.

Signed-off-by: Michael Haggerty <mhagger@alum.mit.edu>
---
 lockfile.c | 28 ++++++++++++----------------
 1 file changed, 12 insertions(+), 16 deletions(-)

diff --git a/lockfile.c b/lockfile.c
index 5e954ba..3904803 100644
--- a/lockfile.c
+++ b/lockfile.c
@@ -389,8 +389,10 @@ 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)
+	if (lk->filename.len <= LOCK_SUFFIX_LEN ||
+	    strcmp(lk->filename.buf + lk->filename.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);
 }
 
@@ -458,22 +460,16 @@ int commit_lock_file_to(struct lock_file *lk, const char *path)
 
 int commit_lock_file(struct lock_file *lk)
 {
-	static struct strbuf result_file = STRBUF_INIT;
-	int err;
+	char *result_path = get_locked_file_path(lk);
 
-	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;
+	if (commit_lock_file_to(lk, result_path)) {
+		int save_errno = errno;
+		free(result_path);
+		errno = save_errno;
+		return -1;
+	}
+	free(result_path);
+	return 0;
 }
 
 void rollback_lock_file(struct lock_file *lk)
-- 
2.5.0

^ permalink raw reply related	[flat|nested] 29+ messages in thread

* [PATCH v2 06/16] tempfile: a new module for handling temporary files
  2015-08-10  9:47 [PATCH v2 00/16] Introduce a tempfile module Michael Haggerty
                   ` (4 preceding siblings ...)
  2015-08-10  9:47 ` [PATCH v2 05/16] commit_lock_file(): use get_locked_file_path() Michael Haggerty
@ 2015-08-10  9:47 ` Michael Haggerty
  2015-08-10  9:47 ` [PATCH v2 07/16] prepare_tempfile_object(): new function, extracted from create_tempfile() Michael Haggerty
                   ` (10 subsequent siblings)
  16 siblings, 0 replies; 29+ messages in thread
From: Michael Haggerty @ 2015-08-10  9:47 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: Johannes Sixt, git, Michael Haggerty

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>
---
 Makefile   |   1 +
 lockfile.c | 261 ++++---------------------------------------------------------
 lockfile.h |  73 +++++++++++------
 tempfile.c | 238 +++++++++++++++++++++++++++++++++++++++++++++++++++++++
 tempfile.h | 167 +++++++++++++++++++++++++++++++++++++++
 5 files changed, 470 insertions(+), 270 deletions(-)
 create mode 100644 tempfile.c
 create mode 100644 tempfile.h

diff --git a/Makefile b/Makefile
index 54ec511..2573f89 100644
--- 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
diff --git a/lockfile.c b/lockfile.c
index 3904803..e1d68f7 100644
--- a/lockfile.c
+++ b/lockfile.c
@@ -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);
-	}
-}
diff --git a/lockfile.h b/lockfile.h
index a204ab6..8131fa3 100644
--- a/lockfile.h
+++ b/lockfile.h
@@ -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
  * ----------------
@@ -74,19 +76,19 @@
  * `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
  * --------------
@@ -103,14 +105,10 @@
  * -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
index 0000000..d835818
--- /dev/null
+++ b/tempfile.c
@@ -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
index 0000000..bcc229f
--- /dev/null
+++ b/tempfile.h
@@ -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 */
-- 
2.5.0

^ permalink raw reply related	[flat|nested] 29+ messages in thread

* [PATCH v2 07/16] prepare_tempfile_object(): new function, extracted from create_tempfile()
  2015-08-10  9:47 [PATCH v2 00/16] Introduce a tempfile module Michael Haggerty
                   ` (5 preceding siblings ...)
  2015-08-10  9:47 ` [PATCH v2 06/16] tempfile: a new module for handling temporary files Michael Haggerty
@ 2015-08-10  9:47 ` Michael Haggerty
  2015-08-11 19:38   ` Junio C Hamano
  2015-08-10  9:47 ` [PATCH v2 08/16] tempfile: add several functions for creating temporary files Michael Haggerty
                   ` (9 subsequent siblings)
  16 siblings, 1 reply; 29+ messages in thread
From: Michael Haggerty @ 2015-08-10  9:47 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: Johannes Sixt, git, Michael Haggerty

This makes the next step easier.

The old code used to use "path" to set the initial length of
tempfile->filename. This was not helpful because path was usually
relative whereas the value stored to filename will be absolute. So
just initialize the length to 0.

Signed-off-by: Michael Haggerty <mhagger@alum.mit.edu>
---
 tempfile.c | 20 +++++++++++++-------
 1 file changed, 13 insertions(+), 7 deletions(-)

diff --git a/tempfile.c b/tempfile.c
index d835818..d840f04 100644
--- a/tempfile.c
+++ b/tempfile.c
@@ -85,11 +85,11 @@ static void remove_tempfiles_on_signal(int signo)
 	raise(signo);
 }
 
-/* Make sure errno contains a meaningful value on error */
-int create_tempfile(struct tempfile *tempfile, const char *path)
+/*
+ * Initialize *tempfile if necessary and add it to tempfile_list.
+ */
+static void prepare_tempfile_object(struct tempfile *tempfile)
 {
-	size_t pathlen = strlen(path);
-
 	if (!tempfile_list) {
 		/* One-time initialization */
 		sigchain_push_common(remove_tempfiles_on_signal);
@@ -97,21 +97,27 @@ int create_tempfile(struct tempfile *tempfile, const char *path)
 	}
 
 	if (tempfile->active)
-		die("BUG: create_tempfile called for active object");
+		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, pathlen);
+		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: create_tempfile called for improperly-reset object");
+		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);
-- 
2.5.0

^ permalink raw reply related	[flat|nested] 29+ messages in thread

* [PATCH v2 08/16] tempfile: add several functions for creating temporary files
  2015-08-10  9:47 [PATCH v2 00/16] Introduce a tempfile module Michael Haggerty
                   ` (6 preceding siblings ...)
  2015-08-10  9:47 ` [PATCH v2 07/16] prepare_tempfile_object(): new function, extracted from create_tempfile() Michael Haggerty
@ 2015-08-10  9:47 ` Michael Haggerty
  2015-08-10  9:47 ` [PATCH v2 09/16] register_tempfile(): new function to handle an existing temporary file Michael Haggerty
                   ` (8 subsequent siblings)
  16 siblings, 0 replies; 29+ messages in thread
From: Michael Haggerty @ 2015-08-10  9:47 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: Johannes Sixt, git, Michael Haggerty

Add several functions for creating temporary files with
automatically-generated names, analogous to mkstemps(), but also
arranging for the files to be deleted on program exit.

The functions are named according to a pattern depending how they
operate. They will be used to replace many places in the code where
temporary files are created and cleaned up ad-hoc.

Signed-off-by: Michael Haggerty <mhagger@alum.mit.edu>
---
 tempfile.c | 53 ++++++++++++++++++++++++++++++++++
 tempfile.h | 96 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 149 insertions(+)

diff --git a/tempfile.c b/tempfile.c
index d840f04..0b5d8ce 100644
--- a/tempfile.c
+++ b/tempfile.c
@@ -137,6 +137,59 @@ int create_tempfile(struct tempfile *tempfile, const char *path)
 	return tempfile->fd;
 }
 
+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)
diff --git a/tempfile.h b/tempfile.h
index bcc229f..a30e12c 100644
--- a/tempfile.h
+++ b/tempfile.h
@@ -92,6 +92,102 @@ struct tempfile {
  */
 extern int create_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
-- 
2.5.0

^ permalink raw reply related	[flat|nested] 29+ messages in thread

* [PATCH v2 09/16] register_tempfile(): new function to handle an existing temporary file
  2015-08-10  9:47 [PATCH v2 00/16] Introduce a tempfile module Michael Haggerty
                   ` (7 preceding siblings ...)
  2015-08-10  9:47 ` [PATCH v2 08/16] tempfile: add several functions for creating temporary files Michael Haggerty
@ 2015-08-10  9:47 ` Michael Haggerty
  2015-08-10  9:47 ` [PATCH v2 10/16] write_shared_index(): use tempfile module Michael Haggerty
                   ` (7 subsequent siblings)
  16 siblings, 0 replies; 29+ messages in thread
From: Michael Haggerty @ 2015-08-10  9:47 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: Johannes Sixt, git, Michael Haggerty

Allow an existing file to be registered with the tempfile-handling
infrastructure; in particular, arrange for it to be deleted on program
exit. This can be used if the temporary file has to be created in a
more complicated way than just open(). For example:

* If the file itself needs to be created via the lockfile API
* If it is not a regular file (e.g., a socket)

Signed-off-by: Michael Haggerty <mhagger@alum.mit.edu>
---
 tempfile.c | 8 ++++++++
 tempfile.h | 8 ++++++++
 2 files changed, 16 insertions(+)

diff --git a/tempfile.c b/tempfile.c
index 0b5d8ce..0af7ebf 100644
--- a/tempfile.c
+++ b/tempfile.c
@@ -137,6 +137,14 @@ int create_tempfile(struct tempfile *tempfile, const char *path)
 	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)
 {
diff --git a/tempfile.h b/tempfile.h
index a30e12c..4219fe4 100644
--- a/tempfile.h
+++ b/tempfile.h
@@ -92,6 +92,14 @@ struct tempfile {
  */
 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
-- 
2.5.0

^ permalink raw reply related	[flat|nested] 29+ messages in thread

* [PATCH v2 10/16] write_shared_index(): use tempfile module
  2015-08-10  9:47 [PATCH v2 00/16] Introduce a tempfile module Michael Haggerty
                   ` (8 preceding siblings ...)
  2015-08-10  9:47 ` [PATCH v2 09/16] register_tempfile(): new function to handle an existing temporary file Michael Haggerty
@ 2015-08-10  9:47 ` Michael Haggerty
  2015-08-10  9:47 ` [PATCH v2 11/16] setup_temporary_shallow(): " Michael Haggerty
                   ` (6 subsequent siblings)
  16 siblings, 0 replies; 29+ messages in thread
From: Michael Haggerty @ 2015-08-10  9:47 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: Johannes Sixt, git, Michael Haggerty

Signed-off-by: Michael Haggerty <mhagger@alum.mit.edu>
---
 read-cache.c | 38 ++++++--------------------------------
 1 file changed, 6 insertions(+), 32 deletions(-)

diff --git a/read-cache.c b/read-cache.c
index 96cb9a3..89be226 100644
--- a/read-cache.c
+++ b/read-cache.c
@@ -5,6 +5,7 @@
  */
 #define NO_THE_INDEX_COMPATIBILITY_MACROS
 #include "cache.h"
+#include "tempfile.h"
 #include "lockfile.h"
 #include "cache-tree.h"
 #include "refs.h"
@@ -2136,54 +2137,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;
-- 
2.5.0

^ permalink raw reply related	[flat|nested] 29+ messages in thread

* [PATCH v2 11/16] setup_temporary_shallow(): use tempfile module
  2015-08-10  9:47 [PATCH v2 00/16] Introduce a tempfile module Michael Haggerty
                   ` (9 preceding siblings ...)
  2015-08-10  9:47 ` [PATCH v2 10/16] write_shared_index(): use tempfile module Michael Haggerty
@ 2015-08-10  9:47 ` Michael Haggerty
  2015-08-10  9:47 ` [PATCH v2 12/16] diff: " Michael Haggerty
                   ` (5 subsequent siblings)
  16 siblings, 0 replies; 29+ messages in thread
From: Michael Haggerty @ 2015-08-10  9:47 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: Johannes Sixt, git, Michael Haggerty

Signed-off-by: Michael Haggerty <mhagger@alum.mit.edu>
---
 shallow.c | 35 +++++++----------------------------
 1 file changed, 7 insertions(+), 28 deletions(-)

diff --git a/shallow.c b/shallow.c
index 7973e74..2ba29a5 100644
--- 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,
-- 
2.5.0

^ permalink raw reply related	[flat|nested] 29+ messages in thread

* [PATCH v2 12/16] diff: use tempfile module
  2015-08-10  9:47 [PATCH v2 00/16] Introduce a tempfile module Michael Haggerty
                   ` (10 preceding siblings ...)
  2015-08-10  9:47 ` [PATCH v2 11/16] setup_temporary_shallow(): " Michael Haggerty
@ 2015-08-10  9:47 ` Michael Haggerty
  2015-08-11 20:03   ` Junio C Hamano
  2015-08-10  9:47 ` [PATCH v2 13/16] lock_repo_for_gc(): compute the path to "gc.pid" only once Michael Haggerty
                   ` (4 subsequent siblings)
  16 siblings, 1 reply; 29+ messages in thread
From: Michael Haggerty @ 2015-08-10  9:47 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: Johannes Sixt, git, Michael Haggerty

Signed-off-by: Michael Haggerty <mhagger@alum.mit.edu>
---
 diff.c | 29 +++++++----------------------
 1 file changed, 7 insertions(+), 22 deletions(-)

diff --git a/diff.c b/diff.c
index 7500c55..dc95247 100644
--- 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"
@@ -312,7 +313,7 @@ static struct diff_tempfile {
 	const char *name; /* filename external diff should read from */
 	char hex[41];
 	char mode[10];
-	char tmp_path[PATH_MAX];
+	struct tempfile tempfile;
 } diff_temp[2];
 
 typedef unsigned long (*sane_truncate_fn)(char *line, unsigned long len);
@@ -564,25 +565,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) {
@@ -2817,8 +2809,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,
@@ -2828,8 +2819,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);
@@ -2854,12 +2845,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))) {
-- 
2.5.0

^ permalink raw reply related	[flat|nested] 29+ messages in thread

* [PATCH v2 13/16] lock_repo_for_gc(): compute the path to "gc.pid" only once
  2015-08-10  9:47 [PATCH v2 00/16] Introduce a tempfile module Michael Haggerty
                   ` (11 preceding siblings ...)
  2015-08-10  9:47 ` [PATCH v2 12/16] diff: " Michael Haggerty
@ 2015-08-10  9:47 ` Michael Haggerty
  2015-08-11 20:06   ` Junio C Hamano
  2015-08-10  9:47 ` [PATCH v2 14/16] gc: use tempfile module to handle gc.pid file Michael Haggerty
                   ` (3 subsequent siblings)
  16 siblings, 1 reply; 29+ messages in thread
From: Michael Haggerty @ 2015-08-10  9:47 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: Johannes Sixt, git, Michael Haggerty

Signed-off-by: Michael Haggerty <mhagger@alum.mit.edu>
---
 builtin/gc.c | 9 ++++++---
 1 file changed, 6 insertions(+), 3 deletions(-)

diff --git a/builtin/gc.c b/builtin/gc.c
index 36fe333..c41354b 100644
--- a/builtin/gc.c
+++ b/builtin/gc.c
@@ -199,6 +199,7 @@ 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)
 		/* already locked */
@@ -207,12 +208,13 @@ static const char *lock_repo_for_gc(int force, pid_t* ret_pid)
 	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 +238,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;
 		}
 	}
@@ -246,7 +249,7 @@ static const char *lock_repo_for_gc(int force, pid_t* ret_pid)
 	strbuf_release(&sb);
 	commit_lock_file(&lock);
 
-	pidfile = git_pathdup("gc.pid");
+	pidfile = pidfile_path;
 	sigchain_push_common(remove_pidfile_on_signal);
 	atexit(remove_pidfile);
 
-- 
2.5.0

^ permalink raw reply related	[flat|nested] 29+ messages in thread

* [PATCH v2 14/16] gc: use tempfile module to handle gc.pid file
  2015-08-10  9:47 [PATCH v2 00/16] Introduce a tempfile module Michael Haggerty
                   ` (12 preceding siblings ...)
  2015-08-10  9:47 ` [PATCH v2 13/16] lock_repo_for_gc(): compute the path to "gc.pid" only once Michael Haggerty
@ 2015-08-10  9:47 ` Michael Haggerty
  2015-08-10  9:47 ` [PATCH v2 15/16] credential-cache--daemon: delete socket from main() Michael Haggerty
                   ` (2 subsequent siblings)
  16 siblings, 0 replies; 29+ messages in thread
From: Michael Haggerty @ 2015-08-10  9:47 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: Johannes Sixt, git, Michael Haggerty

Signed-off-by: Michael Haggerty <mhagger@alum.mit.edu>
---
 builtin/gc.c | 25 +++++--------------------
 1 file changed, 5 insertions(+), 20 deletions(-)

diff --git a/builtin/gc.c b/builtin/gc.c
index c41354b..bfe589f 100644
--- a/builtin/gc.c
+++ b/builtin/gc.c
@@ -11,6 +11,7 @@
  */
 
 #include "builtin.h"
+#include "tempfile.h"
 #include "lockfile.h"
 #include "parse-options.h"
 #include "run-command.h"
@@ -42,20 +43,7 @@ static struct argv_array prune = ARGV_ARRAY_INIT;
 static struct argv_array prune_worktrees = ARGV_ARRAY_INIT;
 static struct argv_array rerere = ARGV_ARRAY_INIT;
 
-static char *pidfile;
-
-static void remove_pidfile(void)
-{
-	if (pidfile)
-		unlink(pidfile);
-}
-
-static void remove_pidfile_on_signal(int signo)
-{
-	remove_pidfile();
-	sigchain_pop(signo);
-	raise(signo);
-}
+static struct tempfile pidfile;
 
 static void git_config_date_string(const char *key, const char **output)
 {
@@ -201,7 +189,7 @@ static const char *lock_repo_for_gc(int force, pid_t* ret_pid)
 	int fd;
 	char *pidfile_path;
 
-	if (pidfile)
+	if (is_tempfile_active(&pidfile))
 		/* already locked */
 		return NULL;
 
@@ -248,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 = pidfile_path;
-	sigchain_push_common(remove_pidfile_on_signal);
-	atexit(remove_pidfile);
-
+	register_tempfile(&pidfile, pidfile_path);
+	free(pidfile_path);
 	return NULL;
 }
 
-- 
2.5.0

^ permalink raw reply related	[flat|nested] 29+ messages in thread

* [PATCH v2 15/16] credential-cache--daemon: delete socket from main()
  2015-08-10  9:47 [PATCH v2 00/16] Introduce a tempfile module Michael Haggerty
                   ` (13 preceding siblings ...)
  2015-08-10  9:47 ` [PATCH v2 14/16] gc: use tempfile module to handle gc.pid file Michael Haggerty
@ 2015-08-10  9:47 ` Michael Haggerty
  2015-08-10  9:47 ` [PATCH v2 16/16] credential-cache--daemon: use tempfile module Michael Haggerty
  2015-08-11 20:21 ` [PATCH v2 00/16] Introduce a " Junio C Hamano
  16 siblings, 0 replies; 29+ messages in thread
From: Michael Haggerty @ 2015-08-10  9:47 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: Johannes Sixt, git, Michael Haggerty

main() is responsible for cleaning up the socket in the case of
errors, so it is reasonable to also make it responsible for cleaning
it up when there are no errors. This change also makes the next step
easier.

Signed-off-by: Michael Haggerty <mhagger@alum.mit.edu>
---
 credential-cache--daemon.c | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/credential-cache--daemon.c b/credential-cache--daemon.c
index c2f0049..a671b2b 100644
--- a/credential-cache--daemon.c
+++ b/credential-cache--daemon.c
@@ -221,7 +221,6 @@ static void serve_cache(const char *socket_path, int debug)
 		; /* nothing */
 
 	close(fd);
-	unlink(socket_path);
 }
 
 static const char permissions_advice[] =
@@ -280,5 +279,7 @@ int main(int argc, const char **argv)
 
 	serve_cache(socket_path, debug);
 
+	unlink(socket_path);
+
 	return 0;
 }
-- 
2.5.0

^ permalink raw reply related	[flat|nested] 29+ messages in thread

* [PATCH v2 16/16] credential-cache--daemon: use tempfile module
  2015-08-10  9:47 [PATCH v2 00/16] Introduce a tempfile module Michael Haggerty
                   ` (14 preceding siblings ...)
  2015-08-10  9:47 ` [PATCH v2 15/16] credential-cache--daemon: delete socket from main() Michael Haggerty
@ 2015-08-10  9:47 ` Michael Haggerty
  2015-08-11 20:21 ` [PATCH v2 00/16] Introduce a " Junio C Hamano
  16 siblings, 0 replies; 29+ messages in thread
From: Michael Haggerty @ 2015-08-10  9:47 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: Johannes Sixt, git, Michael Haggerty

Use the tempfile module to ensure that the socket file gets deleted on
program exit.

Signed-off-by: Michael Haggerty <mhagger@alum.mit.edu>
---
 credential-cache--daemon.c | 26 ++++++--------------------
 1 file changed, 6 insertions(+), 20 deletions(-)

diff --git a/credential-cache--daemon.c b/credential-cache--daemon.c
index a671b2b..eef6fce 100644
--- a/credential-cache--daemon.c
+++ b/credential-cache--daemon.c
@@ -1,23 +1,11 @@
 #include "cache.h"
+#include "tempfile.h"
 #include "credential.h"
 #include "unix-socket.h"
 #include "sigchain.h"
 #include "parse-options.h"
 
-static const char *socket_path;
-
-static void cleanup_socket(void)
-{
-	if (socket_path)
-		unlink(socket_path);
-}
-
-static void cleanup_socket_on_signal(int sig)
-{
-	cleanup_socket();
-	sigchain_pop(sig);
-	raise(sig);
-}
+static struct tempfile socket_file;
 
 struct credential_cache_entry {
 	struct credential item;
@@ -256,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
@@ -272,14 +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);
-
-	unlink(socket_path);
+	delete_tempfile(&socket_file);
 
 	return 0;
 }
-- 
2.5.0

^ permalink raw reply related	[flat|nested] 29+ messages in thread

* Re: [PATCH v2 01/16] Move lockfile documentation to lockfile.h and lockfile.c
  2015-08-10  9:47 ` [PATCH v2 01/16] Move lockfile documentation to lockfile.h and lockfile.c Michael Haggerty
@ 2015-08-11 19:27   ` Junio C Hamano
  0 siblings, 0 replies; 29+ messages in thread
From: Junio C Hamano @ 2015-08-11 19:27 UTC (permalink / raw)
  To: Michael Haggerty; +Cc: Johannes Sixt, git

Michael Haggerty <mhagger@alum.mit.edu> writes:

> Rearrange/rewrite it somewhat to fit its new environment.
> ...
> diff --git a/lockfile.h b/lockfile.h
> index b4abc61..a483cc9 100644
> --- a/lockfile.h
> +++ b/lockfile.h
> @@ -4,54 +4,103 @@
> ...
> @@ -68,16 +117,51 @@ struct lock_file {
>  #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.
> + */
> ...

Thanks.  I really like the way these per-item descriptions explain
each item much better.  The old documentation may have contained all
the same info, but a better organization makes a big difference.

^ permalink raw reply	[flat|nested] 29+ messages in thread

* Re: [PATCH v2 03/16] lockfile: add accessors get_lock_file_fd() and get_lock_file_fp()
  2015-08-10  9:47 ` [PATCH v2 03/16] lockfile: add accessors get_lock_file_fd() and get_lock_file_fp() Michael Haggerty
@ 2015-08-11 19:29   ` Junio C Hamano
  0 siblings, 0 replies; 29+ messages in thread
From: Junio C Hamano @ 2015-08-11 19:29 UTC (permalink / raw)
  To: Michael Haggerty; +Cc: Johannes Sixt, git

Michael Haggerty <mhagger@alum.mit.edu> writes:

> We are about to move those members, so change client code to read them
> through accessor functions.

Hmph, _fp() variant does not seem to be used at all at this step, though.

^ permalink raw reply	[flat|nested] 29+ messages in thread

* Re: [PATCH v2 04/16] lockfile: add accessor get_lock_file_path()
  2015-08-10  9:47 ` [PATCH v2 04/16] lockfile: add accessor get_lock_file_path() Michael Haggerty
@ 2015-08-11 19:36   ` Junio C Hamano
  0 siblings, 0 replies; 29+ messages in thread
From: Junio C Hamano @ 2015-08-11 19:36 UTC (permalink / raw)
  To: Michael Haggerty; +Cc: Johannes Sixt, git

Michael Haggerty <mhagger@alum.mit.edu> writes:

> Signed-off-by: Michael Haggerty <mhagger@alum.mit.edu>
> ---

I was briefly confused by the similarity between get_locked_file_path()
and this new helper ;-) but names of both make sense to me.

^ permalink raw reply	[flat|nested] 29+ messages in thread

* Re: [PATCH v2 07/16] prepare_tempfile_object(): new function, extracted from create_tempfile()
  2015-08-10  9:47 ` [PATCH v2 07/16] prepare_tempfile_object(): new function, extracted from create_tempfile() Michael Haggerty
@ 2015-08-11 19:38   ` Junio C Hamano
  0 siblings, 0 replies; 29+ messages in thread
From: Junio C Hamano @ 2015-08-11 19:38 UTC (permalink / raw)
  To: Michael Haggerty; +Cc: Johannes Sixt, git

Michael Haggerty <mhagger@alum.mit.edu> writes:

> This makes the next step easier.
>
> The old code used to use "path" to set the initial length of
> tempfile->filename. This was not helpful because path was usually
> relative whereas the value stored to filename will be absolute. So
> just initialize the length to 0.

Makes sense.

^ permalink raw reply	[flat|nested] 29+ messages in thread

* Re: [PATCH v2 12/16] diff: use tempfile module
  2015-08-10  9:47 ` [PATCH v2 12/16] diff: " Michael Haggerty
@ 2015-08-11 20:03   ` Junio C Hamano
  2015-08-12 15:13     ` Michael Haggerty
  0 siblings, 1 reply; 29+ messages in thread
From: Junio C Hamano @ 2015-08-11 20:03 UTC (permalink / raw)
  To: Michael Haggerty; +Cc: Johannes Sixt, git

Michael Haggerty <mhagger@alum.mit.edu> writes:

> Signed-off-by: Michael Haggerty <mhagger@alum.mit.edu>
> ---
>  diff.c | 29 +++++++----------------------
>  1 file changed, 7 insertions(+), 22 deletions(-)

Nice code reduction.

> diff --git a/diff.c b/diff.c
> index 7500c55..dc95247 100644
> --- 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"
> @@ -312,7 +313,7 @@ static struct diff_tempfile {
>  	const char *name; /* filename external diff should read from */
>  	char hex[41];
>  	char mode[10];
> -	char tmp_path[PATH_MAX];
> +	struct tempfile tempfile;
>  } diff_temp[2];
>  
>  typedef unsigned long (*sane_truncate_fn)(char *line, unsigned long len);
> @@ -564,25 +565,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);

I suspect that this indicates that there is something iffy in the
conversion.  The original invariant, that is consistently used
between claim_diff_tempfile() and remove_tempfile(), is that .name
field points at .tmp_path for a slot in diff_temp[] that holds a
temporary that is in use.  Otherwise, .name is NULL and it can be
claimed for your own use.

Here the updated code uses a different and new invariant: .tempfile
satisfies is_tempfile_active() for a slot in use.  But the check in
claim_diff_tempfile() still relies on the original invariant.

The updated code may happen to always have an active tempfile in
tempfile and always set NULL when it clears .name, but that would
mean (1) future changes may easily violate one of invariants (we
used to have only one, now we have two that have to be sync) by
mistake, and (2) we are keeping track of two closely linked things
as two invariants.

As the value that used to be in the .name field can now be obtained
by calling get_tempfile_path() on the .tempfile field, perhaps we
should drop .name (and its associated invariant) at the same time?

^ permalink raw reply	[flat|nested] 29+ messages in thread

* Re: [PATCH v2 13/16] lock_repo_for_gc(): compute the path to "gc.pid" only once
  2015-08-10  9:47 ` [PATCH v2 13/16] lock_repo_for_gc(): compute the path to "gc.pid" only once Michael Haggerty
@ 2015-08-11 20:06   ` Junio C Hamano
  2015-08-11 20:20     ` Junio C Hamano
  0 siblings, 1 reply; 29+ messages in thread
From: Junio C Hamano @ 2015-08-11 20:06 UTC (permalink / raw)
  To: Michael Haggerty; +Cc: Johannes Sixt, git

Michael Haggerty <mhagger@alum.mit.edu> writes:

> Signed-off-by: Michael Haggerty <mhagger@alum.mit.edu>
> ---
>  builtin/gc.c | 9 ++++++---
>  1 file changed, 6 insertions(+), 3 deletions(-)
>
> diff --git a/builtin/gc.c b/builtin/gc.c
> index 36fe333..c41354b 100644
> --- a/builtin/gc.c
> +++ b/builtin/gc.c
> @@ -199,6 +199,7 @@ 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)
>  		/* already locked */
> @@ -207,12 +208,13 @@ static const char *lock_repo_for_gc(int force, pid_t* ret_pid)
>  	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);

Looks correct; somehow this reminded me of the other topic from Peff
to reduce use of git_path() ;-)

> -	pidfile = git_pathdup("gc.pid");
> +	pidfile = pidfile_path;
>  	sigchain_push_common(remove_pidfile_on_signal);
>  	atexit(remove_pidfile);

I wonder if you can reduce the atexit() here by registering this as
a tempfile to be cleared?

^ permalink raw reply	[flat|nested] 29+ messages in thread

* Re: [PATCH v2 13/16] lock_repo_for_gc(): compute the path to "gc.pid" only once
  2015-08-11 20:06   ` Junio C Hamano
@ 2015-08-11 20:20     ` Junio C Hamano
  0 siblings, 0 replies; 29+ messages in thread
From: Junio C Hamano @ 2015-08-11 20:20 UTC (permalink / raw)
  To: Michael Haggerty; +Cc: Johannes Sixt, Git Mailing List

On Tue, Aug 11, 2015 at 1:06 PM, Junio C Hamano <gitster@pobox.com> wrote:
>
> Looks correct; somehow this reminded me of the other topic from Peff
> to reduce use of git_path() ;-)
>
>> -     pidfile = git_pathdup("gc.pid");
>> +     pidfile = pidfile_path;
>>       sigchain_push_common(remove_pidfile_on_signal);
>>       atexit(remove_pidfile);
>
> I wonder if you can reduce the atexit() here by registering this as
> a tempfile to be cleared?

Heh, I should have been slightly more patient. That is what 14/16 is about ;-)

^ permalink raw reply	[flat|nested] 29+ messages in thread

* Re: [PATCH v2 00/16] Introduce a tempfile module
  2015-08-10  9:47 [PATCH v2 00/16] Introduce a tempfile module Michael Haggerty
                   ` (15 preceding siblings ...)
  2015-08-10  9:47 ` [PATCH v2 16/16] credential-cache--daemon: use tempfile module Michael Haggerty
@ 2015-08-11 20:21 ` Junio C Hamano
  2015-08-12 15:14   ` Michael Haggerty
  16 siblings, 1 reply; 29+ messages in thread
From: Junio C Hamano @ 2015-08-11 20:21 UTC (permalink / raw)
  To: Michael Haggerty; +Cc: Johannes Sixt, git

Thanks for a pleasant read.  All looked reasonable.

^ permalink raw reply	[flat|nested] 29+ messages in thread

* Re: [PATCH v2 12/16] diff: use tempfile module
  2015-08-11 20:03   ` Junio C Hamano
@ 2015-08-12 15:13     ` Michael Haggerty
  2015-08-12 16:41       ` Junio C Hamano
  0 siblings, 1 reply; 29+ messages in thread
From: Michael Haggerty @ 2015-08-12 15:13 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: Johannes Sixt, git

On 08/11/2015 10:03 PM, Junio C Hamano wrote:
> Michael Haggerty <mhagger@alum.mit.edu> writes:
> 
>> Signed-off-by: Michael Haggerty <mhagger@alum.mit.edu>
>> ---
>>  diff.c | 29 +++++++----------------------
>>  1 file changed, 7 insertions(+), 22 deletions(-)
> 
> Nice code reduction.
> 
>> diff --git a/diff.c b/diff.c
>> index 7500c55..dc95247 100644
>> --- 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"
>> @@ -312,7 +313,7 @@ static struct diff_tempfile {
>>  	const char *name; /* filename external diff should read from */
>>  	char hex[41];
>>  	char mode[10];
>> -	char tmp_path[PATH_MAX];
>> +	struct tempfile tempfile;
>>  } diff_temp[2];
>>  
>>  typedef unsigned long (*sane_truncate_fn)(char *line, unsigned long len);
>> @@ -564,25 +565,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);
> 
> I suspect that this indicates that there is something iffy in the
> conversion.  The original invariant, that is consistently used
> between claim_diff_tempfile() and remove_tempfile(), is that .name
> field points at .tmp_path for a slot in diff_temp[] that holds a
> temporary that is in use.  Otherwise, .name is NULL and it can be
> claimed for your own use.

No, prepare_temp_file() sometimes sets diff_tempfile::name to
"/dev/null", and sometimes to point at its argument `name`. In either of
these cases .tmp_path can hold anything, and the file is *not* cleaned
up even though the diff_temp entry is considered by
claim_diff_tempfile() to be in use.

If I'm not mistaken, the old invariant was:

* Iff diff_tempfile::name is NULL, the entry is not in use.
* Iff diff_tempfile::name == diff_tempfile::tmp_path, then the entry is
in use and refers to a temporary file that needs to be cleaned up.
* Otherwise, the entry is in use but the corresponding file should *not*
be cleaned up.

The new invariant is:

* Iff diff_tempfile::name is NULL, the entry is not in use. In these
cases, is_tempfile_active() is always false.
* Iff is_tempfile_active(diff_tempfile::tempfile), then it refers to a
file that needs to get cleaned up. In these cases name points at the
tempfile object's filename.
* If neither of the above is true, then the entry is in use but the
corresponding file should not be cleaned up.

> Here the updated code uses a different and new invariant: .tempfile
> satisfies is_tempfile_active() for a slot in use.  But the check in
> claim_diff_tempfile() still relies on the original invariant.

That is not true. The is_tempfile_active() check is only used in
remove_tempfile() when deciding whether to clean up the file. The check
in claim_diff_tempfile() wants to know whether the entry is in use, so
it uses the other check.

> The updated code may happen to always have an active tempfile in
> tempfile and always set NULL when it clears .name, but that would
> mean (1) future changes may easily violate one of invariants (we
> used to have only one, now we have two that have to be sync) by
> mistake, and (2) we are keeping track of two closely linked things
> as two invariants.
> 
> As the value that used to be in the .name field can now be obtained
> by calling get_tempfile_path() on the .tempfile field, perhaps we
> should drop .name (and its associated invariant) at the same time?

This is also incorrect. See my first paragraph above.

I will change this patch to document the invariants.

Michael

-- 
Michael Haggerty
mhagger@alum.mit.edu

^ permalink raw reply	[flat|nested] 29+ messages in thread

* Re: [PATCH v2 00/16] Introduce a tempfile module
  2015-08-11 20:21 ` [PATCH v2 00/16] Introduce a " Junio C Hamano
@ 2015-08-12 15:14   ` Michael Haggerty
  0 siblings, 0 replies; 29+ messages in thread
From: Michael Haggerty @ 2015-08-12 15:14 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: Johannes Sixt, git

On 08/11/2015 10:21 PM, Junio C Hamano wrote:
> Thanks for a pleasant read.  All looked reasonable.

Thanks for your review!

Michael

-- 
Michael Haggerty
mhagger@alum.mit.edu

^ permalink raw reply	[flat|nested] 29+ messages in thread

* Re: [PATCH v2 12/16] diff: use tempfile module
  2015-08-12 15:13     ` Michael Haggerty
@ 2015-08-12 16:41       ` Junio C Hamano
  2015-08-12 17:12         ` [PATCH v2' " Michael Haggerty
  0 siblings, 1 reply; 29+ messages in thread
From: Junio C Hamano @ 2015-08-12 16:41 UTC (permalink / raw)
  To: Michael Haggerty; +Cc: Johannes Sixt, git

Michael Haggerty <mhagger@alum.mit.edu> writes:

> No, prepare_temp_file() sometimes sets diff_tempfile::name to
> "/dev/null", and sometimes to point at its argument `name`.

That explains everything.  Thanks.  It's been a while since I wrote
this part of the system ;-).

^ permalink raw reply	[flat|nested] 29+ messages in thread

* [PATCH v2' 12/16] diff: use tempfile module
  2015-08-12 16:41       ` Junio C Hamano
@ 2015-08-12 17:12         ` Michael Haggerty
  0 siblings, 0 replies; 29+ messages in thread
From: Michael Haggerty @ 2015-08-12 17:12 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: Johannes Sixt, git, Michael Haggerty

Also add some code comments explaining how the fields in "struct
diff_tempfile" are used.

Signed-off-by: Michael Haggerty <mhagger@alum.mit.edu>
---
This is a replacement for tempfile patch v2 12/16 that includes some
extra code comments. It is also available from my GitHub repo [1] on
branch "tempfile".

[1] https://github.com/mhagger/git

 diff.c | 46 +++++++++++++++++++++++-----------------------
 1 file changed, 23 insertions(+), 23 deletions(-)

diff --git a/diff.c b/diff.c
index 7500c55..528d25c 100644
--- 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);
@@ -564,25 +580,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) {
@@ -2817,8 +2824,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,
@@ -2828,8 +2834,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);
@@ -2854,12 +2860,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))) {
-- 
2.5.0

^ permalink raw reply related	[flat|nested] 29+ messages in thread

end of thread, other threads:[~2015-08-12 17:12 UTC | newest]

Thread overview: 29+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2015-08-10  9:47 [PATCH v2 00/16] Introduce a tempfile module Michael Haggerty
2015-08-10  9:47 ` [PATCH v2 01/16] Move lockfile documentation to lockfile.h and lockfile.c Michael Haggerty
2015-08-11 19:27   ` Junio C Hamano
2015-08-10  9:47 ` [PATCH v2 02/16] create_bundle(): duplicate file descriptor to avoid closing it twice Michael Haggerty
2015-08-10  9:47 ` [PATCH v2 03/16] lockfile: add accessors get_lock_file_fd() and get_lock_file_fp() Michael Haggerty
2015-08-11 19:29   ` Junio C Hamano
2015-08-10  9:47 ` [PATCH v2 04/16] lockfile: add accessor get_lock_file_path() Michael Haggerty
2015-08-11 19:36   ` Junio C Hamano
2015-08-10  9:47 ` [PATCH v2 05/16] commit_lock_file(): use get_locked_file_path() Michael Haggerty
2015-08-10  9:47 ` [PATCH v2 06/16] tempfile: a new module for handling temporary files Michael Haggerty
2015-08-10  9:47 ` [PATCH v2 07/16] prepare_tempfile_object(): new function, extracted from create_tempfile() Michael Haggerty
2015-08-11 19:38   ` Junio C Hamano
2015-08-10  9:47 ` [PATCH v2 08/16] tempfile: add several functions for creating temporary files Michael Haggerty
2015-08-10  9:47 ` [PATCH v2 09/16] register_tempfile(): new function to handle an existing temporary file Michael Haggerty
2015-08-10  9:47 ` [PATCH v2 10/16] write_shared_index(): use tempfile module Michael Haggerty
2015-08-10  9:47 ` [PATCH v2 11/16] setup_temporary_shallow(): " Michael Haggerty
2015-08-10  9:47 ` [PATCH v2 12/16] diff: " Michael Haggerty
2015-08-11 20:03   ` Junio C Hamano
2015-08-12 15:13     ` Michael Haggerty
2015-08-12 16:41       ` Junio C Hamano
2015-08-12 17:12         ` [PATCH v2' " Michael Haggerty
2015-08-10  9:47 ` [PATCH v2 13/16] lock_repo_for_gc(): compute the path to "gc.pid" only once Michael Haggerty
2015-08-11 20:06   ` Junio C Hamano
2015-08-11 20:20     ` Junio C Hamano
2015-08-10  9:47 ` [PATCH v2 14/16] gc: use tempfile module to handle gc.pid file Michael Haggerty
2015-08-10  9:47 ` [PATCH v2 15/16] credential-cache--daemon: delete socket from main() Michael Haggerty
2015-08-10  9:47 ` [PATCH v2 16/16] credential-cache--daemon: use tempfile module Michael Haggerty
2015-08-11 20:21 ` [PATCH v2 00/16] Introduce a " Junio C Hamano
2015-08-12 15:14   ` Michael Haggerty

Code repositories for project(s) associated with this public inbox

	https://80x24.org/mirrors/git.git

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).