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