зеркало из https://github.com/microsoft/git.git
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:
Коммит
db86e61cbb
|
@ -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 += symlinks.o
|
||||
LIB_OBJS += tag.o
|
||||
LIB_OBJS += tempfile.o
|
||||
LIB_OBJS += trace.o
|
||||
LIB_OBJS += trailer.o
|
||||
LIB_OBJS += transport.o
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
#include "dir.h"
|
||||
#include "run-command.h"
|
||||
#include "quote.h"
|
||||
#include "tempfile.h"
|
||||
#include "lockfile.h"
|
||||
#include "cache-tree.h"
|
||||
#include "refs.h"
|
||||
|
|
|
@ -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,
|
||||
|
|
32
builtin/gc.c
32
builtin/gc.c
|
@ -11,6 +11,7 @@
|
|||
*/
|
||||
|
||||
#include "builtin.h"
|
||||
#include "tempfile.h"
|
||||
#include "lockfile.h"
|
||||
#include "parse-options.h"
|
||||
#include "run-command.h"
|
||||
|
@ -42,20 +43,7 @@ static struct argv_array prune = ARGV_ARRAY_INIT;
|
|||
static struct argv_array prune_worktrees = ARGV_ARRAY_INIT;
|
||||
static struct argv_array rerere = ARGV_ARRAY_INIT;
|
||||
|
||||
static char *pidfile;
|
||||
|
||||
static void remove_pidfile(void)
|
||||
{
|
||||
if (pidfile)
|
||||
unlink(pidfile);
|
||||
}
|
||||
|
||||
static void remove_pidfile_on_signal(int signo)
|
||||
{
|
||||
remove_pidfile();
|
||||
sigchain_pop(signo);
|
||||
raise(signo);
|
||||
}
|
||||
static struct tempfile pidfile;
|
||||
|
||||
static void git_config_date_string(const char *key, const char **output)
|
||||
{
|
||||
|
@ -199,20 +187,22 @@ static const char *lock_repo_for_gc(int force, pid_t* ret_pid)
|
|||
uintmax_t pid;
|
||||
FILE *fp;
|
||||
int fd;
|
||||
char *pidfile_path;
|
||||
|
||||
if (pidfile)
|
||||
if (is_tempfile_active(&pidfile))
|
||||
/* already locked */
|
||||
return NULL;
|
||||
|
||||
if (gethostname(my_host, sizeof(my_host)))
|
||||
strcpy(my_host, "unknown");
|
||||
|
||||
fd = hold_lock_file_for_update(&lock, git_path("gc.pid"),
|
||||
pidfile_path = git_pathdup("gc.pid");
|
||||
fd = hold_lock_file_for_update(&lock, pidfile_path,
|
||||
LOCK_DIE_ON_ERROR);
|
||||
if (!force) {
|
||||
static char locking_host[128];
|
||||
int should_exit;
|
||||
fp = fopen(git_path("gc.pid"), "r");
|
||||
fp = fopen(pidfile_path, "r");
|
||||
memset(locking_host, 0, sizeof(locking_host));
|
||||
should_exit =
|
||||
fp != NULL &&
|
||||
|
@ -236,6 +226,7 @@ static const char *lock_repo_for_gc(int force, pid_t* ret_pid)
|
|||
if (fd >= 0)
|
||||
rollback_lock_file(&lock);
|
||||
*ret_pid = pid;
|
||||
free(pidfile_path);
|
||||
return locking_host;
|
||||
}
|
||||
}
|
||||
|
@ -245,11 +236,8 @@ static const char *lock_repo_for_gc(int force, pid_t* ret_pid)
|
|||
write_in_full(fd, sb.buf, sb.len);
|
||||
strbuf_release(&sb);
|
||||
commit_lock_file(&lock);
|
||||
|
||||
pidfile = git_pathdup("gc.pid");
|
||||
sigchain_push_common(remove_pidfile_on_signal);
|
||||
atexit(remove_pidfile);
|
||||
|
||||
register_tempfile(&pidfile, pidfile_path);
|
||||
free(pidfile_path);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
#include "dir.h"
|
||||
#include "refs.h"
|
||||
#include "revision.h"
|
||||
#include "tempfile.h"
|
||||
#include "lockfile.h"
|
||||
|
||||
enum rebase_type {
|
||||
|
|
26
bundle.c
26
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) {
|
||||
|
|
14
config.c
14
config.c
|
@ -2066,9 +2066,9 @@ int git_config_set_multivar_in_file(const char *config_filename,
|
|||
}
|
||||
close(in_fd);
|
||||
|
||||
if (chmod(lock->filename.buf, st.st_mode & 07777) < 0) {
|
||||
if (chmod(get_lock_file_path(lock), st.st_mode & 07777) < 0) {
|
||||
error("chmod on %s failed: %s",
|
||||
lock->filename.buf, strerror(errno));
|
||||
get_lock_file_path(lock), strerror(errno));
|
||||
ret = CONFIG_NO_WRITE;
|
||||
goto out_free;
|
||||
}
|
||||
|
@ -2151,7 +2151,7 @@ out_free:
|
|||
return ret;
|
||||
|
||||
write_err_out:
|
||||
ret = write_error(lock->filename.buf);
|
||||
ret = write_error(get_lock_file_path(lock));
|
||||
goto out_free;
|
||||
|
||||
}
|
||||
|
@ -2252,9 +2252,9 @@ int git_config_rename_section_in_file(const char *config_filename,
|
|||
|
||||
fstat(fileno(config_file), &st);
|
||||
|
||||
if (chmod(lock->filename.buf, st.st_mode & 07777) < 0) {
|
||||
if (chmod(get_lock_file_path(lock), st.st_mode & 07777) < 0) {
|
||||
ret = error("chmod on %s failed: %s",
|
||||
lock->filename.buf, strerror(errno));
|
||||
get_lock_file_path(lock), strerror(errno));
|
||||
goto out;
|
||||
}
|
||||
|
||||
|
@ -2275,7 +2275,7 @@ int git_config_rename_section_in_file(const char *config_filename,
|
|||
}
|
||||
store.baselen = strlen(new_name);
|
||||
if (!store_write_section(out_fd, new_name)) {
|
||||
ret = write_error(lock->filename.buf);
|
||||
ret = write_error(get_lock_file_path(lock));
|
||||
goto out;
|
||||
}
|
||||
/*
|
||||
|
@ -2301,7 +2301,7 @@ int git_config_rename_section_in_file(const char *config_filename,
|
|||
continue;
|
||||
length = strlen(output);
|
||||
if (write_in_full(out_fd, output, length) != length) {
|
||||
ret = write_error(lock->filename.buf);
|
||||
ret = write_error(get_lock_file_path(lock));
|
||||
goto out;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,23 +1,11 @@
|
|||
#include "cache.h"
|
||||
#include "tempfile.h"
|
||||
#include "credential.h"
|
||||
#include "unix-socket.h"
|
||||
#include "sigchain.h"
|
||||
#include "parse-options.h"
|
||||
|
||||
static const char *socket_path;
|
||||
|
||||
static void cleanup_socket(void)
|
||||
{
|
||||
if (socket_path)
|
||||
unlink(socket_path);
|
||||
}
|
||||
|
||||
static void cleanup_socket_on_signal(int sig)
|
||||
{
|
||||
cleanup_socket();
|
||||
sigchain_pop(sig);
|
||||
raise(sig);
|
||||
}
|
||||
static struct tempfile socket_file;
|
||||
|
||||
struct credential_cache_entry {
|
||||
struct credential item;
|
||||
|
@ -221,7 +209,6 @@ static void serve_cache(const char *socket_path, int debug)
|
|||
; /* nothing */
|
||||
|
||||
close(fd);
|
||||
unlink(socket_path);
|
||||
}
|
||||
|
||||
static const char permissions_advice[] =
|
||||
|
@ -257,6 +244,7 @@ static void check_socket_directory(const char *path)
|
|||
|
||||
int main(int argc, const char **argv)
|
||||
{
|
||||
const char *socket_path;
|
||||
static const char *usage[] = {
|
||||
"git-credential-cache--daemon [opts] <socket_path>",
|
||||
NULL
|
||||
|
@ -273,12 +261,11 @@ int main(int argc, const char **argv)
|
|||
|
||||
if (!socket_path)
|
||||
usage_with_options(usage, options);
|
||||
|
||||
check_socket_directory(socket_path);
|
||||
|
||||
atexit(cleanup_socket);
|
||||
sigchain_push_common(cleanup_socket_on_signal);
|
||||
|
||||
register_tempfile(&socket_file, socket_path);
|
||||
serve_cache(socket_path, debug);
|
||||
delete_tempfile(&socket_file);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
46
diff.c
46
diff.c
|
@ -2,6 +2,7 @@
|
|||
* Copyright (C) 2005 Junio C Hamano
|
||||
*/
|
||||
#include "cache.h"
|
||||
#include "tempfile.h"
|
||||
#include "quote.h"
|
||||
#include "diff.h"
|
||||
#include "diffcore.h"
|
||||
|
@ -308,11 +309,26 @@ static const char *external_diff(void)
|
|||
return external_diff_cmd;
|
||||
}
|
||||
|
||||
/*
|
||||
* Keep track of files used for diffing. Sometimes such an entry
|
||||
* refers to a temporary file, sometimes to an existing file, and
|
||||
* sometimes to "/dev/null".
|
||||
*/
|
||||
static struct diff_tempfile {
|
||||
const char *name; /* filename external diff should read from */
|
||||
/*
|
||||
* filename external diff should read from, or NULL if this
|
||||
* entry is currently not in use:
|
||||
*/
|
||||
const char *name;
|
||||
|
||||
char hex[41];
|
||||
char mode[10];
|
||||
char tmp_path[PATH_MAX];
|
||||
|
||||
/*
|
||||
* If this diff_tempfile instance refers to a temporary file,
|
||||
* this tempfile object is used to manage its lifetime.
|
||||
*/
|
||||
struct tempfile tempfile;
|
||||
} diff_temp[2];
|
||||
|
||||
typedef unsigned long (*sane_truncate_fn)(char *line, unsigned long len);
|
||||
|
@ -597,25 +613,16 @@ static struct diff_tempfile *claim_diff_tempfile(void) {
|
|||
die("BUG: diff is failing to clean up its tempfiles");
|
||||
}
|
||||
|
||||
static int remove_tempfile_installed;
|
||||
|
||||
static void remove_tempfile(void)
|
||||
{
|
||||
int i;
|
||||
for (i = 0; i < ARRAY_SIZE(diff_temp); i++) {
|
||||
if (diff_temp[i].name == diff_temp[i].tmp_path)
|
||||
unlink_or_warn(diff_temp[i].name);
|
||||
if (is_tempfile_active(&diff_temp[i].tempfile))
|
||||
delete_tempfile(&diff_temp[i].tempfile);
|
||||
diff_temp[i].name = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
static void remove_tempfile_on_signal(int signo)
|
||||
{
|
||||
remove_tempfile();
|
||||
sigchain_pop(signo);
|
||||
raise(signo);
|
||||
}
|
||||
|
||||
static void print_line_count(FILE *file, int count)
|
||||
{
|
||||
switch (count) {
|
||||
|
@ -2858,8 +2865,7 @@ static void prep_temp_blob(const char *path, struct diff_tempfile *temp,
|
|||
strbuf_addstr(&template, "XXXXXX_");
|
||||
strbuf_addstr(&template, base);
|
||||
|
||||
fd = git_mkstemps(temp->tmp_path, PATH_MAX, template.buf,
|
||||
strlen(base) + 1);
|
||||
fd = mks_tempfile_ts(&temp->tempfile, template.buf, strlen(base) + 1);
|
||||
if (fd < 0)
|
||||
die_errno("unable to create temp-file");
|
||||
if (convert_to_working_tree(path,
|
||||
|
@ -2869,8 +2875,8 @@ static void prep_temp_blob(const char *path, struct diff_tempfile *temp,
|
|||
}
|
||||
if (write_in_full(fd, blob, size) != size)
|
||||
die_errno("unable to write temp-file");
|
||||
close(fd);
|
||||
temp->name = temp->tmp_path;
|
||||
close_tempfile(&temp->tempfile);
|
||||
temp->name = get_tempfile_path(&temp->tempfile);
|
||||
strcpy(temp->hex, sha1_to_hex(sha1));
|
||||
temp->hex[40] = 0;
|
||||
sprintf(temp->mode, "%06o", mode);
|
||||
|
@ -2895,12 +2901,6 @@ static struct diff_tempfile *prepare_temp_file(const char *name,
|
|||
return temp;
|
||||
}
|
||||
|
||||
if (!remove_tempfile_installed) {
|
||||
atexit(remove_tempfile);
|
||||
sigchain_push_common(remove_tempfile_on_signal);
|
||||
remove_tempfile_installed = 1;
|
||||
}
|
||||
|
||||
if (!S_ISGITLINK(one->mode) &&
|
||||
(!one->sha1_valid ||
|
||||
reuse_worktree_file(name, one->sha1, 1))) {
|
||||
|
|
211
lockfile.c
211
lockfile.c
|
@ -1,38 +1,9 @@
|
|||
/*
|
||||
* Copyright (c) 2005, Junio C Hamano
|
||||
*/
|
||||
|
||||
#include "cache.h"
|
||||
#include "lockfile.h"
|
||||
#include "sigchain.h"
|
||||
|
||||
static struct lock_file *volatile lock_file_list;
|
||||
|
||||
static void remove_lock_files(int skip_fclose)
|
||||
{
|
||||
pid_t me = getpid();
|
||||
|
||||
while (lock_file_list) {
|
||||
if (lock_file_list->owner == me) {
|
||||
/* fclose() is not safe to call in a signal handler */
|
||||
if (skip_fclose)
|
||||
lock_file_list->fp = NULL;
|
||||
rollback_lock_file(lock_file_list);
|
||||
}
|
||||
lock_file_list = lock_file_list->next;
|
||||
}
|
||||
}
|
||||
|
||||
static void remove_lock_files_on_exit(void)
|
||||
{
|
||||
remove_lock_files(0);
|
||||
}
|
||||
|
||||
static void remove_lock_files_on_signal(int signo)
|
||||
{
|
||||
remove_lock_files(1);
|
||||
sigchain_pop(signo);
|
||||
raise(signo);
|
||||
}
|
||||
|
||||
/*
|
||||
* path = absolute or relative path name
|
||||
|
@ -101,60 +72,17 @@ static void resolve_symlink(struct strbuf *path)
|
|||
/* Make sure errno contains a meaningful value on error */
|
||||
static int lock_file(struct lock_file *lk, const char *path, int flags)
|
||||
{
|
||||
size_t pathlen = strlen(path);
|
||||
int fd;
|
||||
struct strbuf filename = STRBUF_INIT;
|
||||
|
||||
if (!lock_file_list) {
|
||||
/* One-time initialization */
|
||||
sigchain_push_common(remove_lock_files_on_signal);
|
||||
atexit(remove_lock_files_on_exit);
|
||||
}
|
||||
strbuf_addstr(&filename, path);
|
||||
if (!(flags & LOCK_NO_DEREF))
|
||||
resolve_symlink(&filename);
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -287,116 +215,29 @@ int hold_lock_file_for_append(struct lock_file *lk, const char *path, int flags)
|
|||
return fd;
|
||||
}
|
||||
|
||||
FILE *fdopen_lock_file(struct lock_file *lk, const char *mode)
|
||||
{
|
||||
if (!lk->active)
|
||||
die("BUG: fdopen_lock_file() called for unlocked object");
|
||||
if (lk->fp)
|
||||
die("BUG: fdopen_lock_file() called twice for file '%s'", lk->filename.buf);
|
||||
|
||||
lk->fp = fdopen(lk->fd, mode);
|
||||
return lk->fp;
|
||||
}
|
||||
|
||||
char *get_locked_file_path(struct lock_file *lk)
|
||||
{
|
||||
if (!lk->active)
|
||||
die("BUG: get_locked_file_path() called for unlocked object");
|
||||
if (lk->filename.len <= LOCK_SUFFIX_LEN)
|
||||
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");
|
||||
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;
|
||||
/* remove ".lock": */
|
||||
strbuf_setlen(&ret, ret.len - LOCK_SUFFIX_LEN);
|
||||
return strbuf_detach(&ret, NULL);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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);
|
||||
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;
|
||||
}
|
||||
|
|
320
lockfile.h
320
lockfile.h
|
@ -4,80 +4,162 @@
|
|||
/*
|
||||
* File write-locks as used by Git.
|
||||
*
|
||||
* For an overview of how to use the lockfile API, please see
|
||||
* 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
|
||||
* 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.
|
||||
* * 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.
|
||||
*
|
||||
* 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.
|
||||
* 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).
|
||||
*
|
||||
* 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
|
||||
* 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.
|
||||
* Calling sequence
|
||||
* ----------------
|
||||
*
|
||||
* - 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
|
||||
* The caller:
|
||||
*
|
||||
* - Locked, lockfile closed (after successful close_lock_file()).
|
||||
* Same as the previous state, except that the lockfile is closed
|
||||
* and fd is -1.
|
||||
* * 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).
|
||||
*
|
||||
* - 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.
|
||||
* * Attempts to create a lockfile by calling
|
||||
* `hold_lock_file_for_update()` or `hold_lock_file_for_append()`.
|
||||
*
|
||||
* * Writes new content for the destination file by either:
|
||||
*
|
||||
* * writing to the file descriptor returned by the
|
||||
* `hold_lock_file_for_*()` functions (also available via
|
||||
* `lock->fd`).
|
||||
*
|
||||
* * calling `fdopen_lock_file()` to get a `FILE` pointer for the
|
||||
* open file and writing to the file using stdio.
|
||||
*
|
||||
* When finished writing, the caller can:
|
||||
*
|
||||
* * Close the file descriptor and rename the lockfile to its final
|
||||
* destination by calling `commit_lock_file()` or
|
||||
* `commit_lock_file_to()`.
|
||||
*
|
||||
* * Close the file descriptor and remove the lockfile by calling
|
||||
* `rollback_lock_file()`.
|
||||
*
|
||||
* * Close the file descriptor without removing or renaming the
|
||||
* lockfile by calling `close_lock_file()`, and later call
|
||||
* `commit_lock_file()`, `commit_lock_file_to()`,
|
||||
* `rollback_lock_file()`, or `reopen_lock_file()`.
|
||||
*
|
||||
* Even after the lockfile is committed or rolled back, the
|
||||
* `lock_file` object must not be freed or altered by the caller.
|
||||
* However, it may be reused; just pass it to another call of
|
||||
* `hold_lock_file_for_update()` or `hold_lock_file_for_append()`.
|
||||
*
|
||||
* If the program exits before `commit_lock_file()`,
|
||||
* `commit_lock_file_to()`, or `rollback_lock_file()` is called, the
|
||||
* tempfile module will close and remove the lockfile, thereby rolling
|
||||
* back any uncommitted changes.
|
||||
*
|
||||
* If you need to close the file descriptor you obtained from a
|
||||
* `hold_lock_file_for_*()` function yourself, do so by calling
|
||||
* `close_lock_file()`. See "tempfile.h" for more information.
|
||||
*
|
||||
*
|
||||
* Under the covers, a lockfile is just a tempfile with a few helper
|
||||
* functions. In particular, the state diagram and the cleanup
|
||||
* machinery are all implemented in the tempfile module.
|
||||
*
|
||||
*
|
||||
* Error handling
|
||||
* --------------
|
||||
*
|
||||
* The `hold_lock_file_for_*()` functions return a file descriptor on
|
||||
* success or -1 on failure (unless `LOCK_DIE_ON_ERROR` is used; see
|
||||
* "flags" below). On errors, `errno` describes the reason for
|
||||
* failure. Errors can be reported by passing `errno` to
|
||||
* `unable_to_lock_message()` or `unable_to_lock_die()`.
|
||||
*
|
||||
* Similarly, `commit_lock_file`, `commit_lock_file_to`, and
|
||||
* `close_lock_file` return 0 on success. On failure they set `errno`
|
||||
* appropriately, do their best to roll back the lockfile, and return
|
||||
* -1.
|
||||
*/
|
||||
|
||||
#include "tempfile.h"
|
||||
|
||||
struct lock_file {
|
||||
struct lock_file *volatile next;
|
||||
volatile sig_atomic_t active;
|
||||
volatile int fd;
|
||||
FILE *volatile fp;
|
||||
volatile pid_t owner;
|
||||
char on_list;
|
||||
struct strbuf filename;
|
||||
struct tempfile tempfile;
|
||||
};
|
||||
|
||||
/* String appended to a filename to derive the lockfile name: */
|
||||
#define LOCK_SUFFIX ".lock"
|
||||
#define LOCK_SUFFIX_LEN 5
|
||||
|
||||
|
||||
/*
|
||||
* Flags
|
||||
* -----
|
||||
*
|
||||
* The following flags can be passed to `hold_lock_file_for_update()`
|
||||
* or `hold_lock_file_for_append()`.
|
||||
*/
|
||||
|
||||
/*
|
||||
* If a lock is already taken for the file, `die()` with an error
|
||||
* message. If this flag is not specified, trying to lock a file that
|
||||
* is already locked returns -1 to the caller.
|
||||
*/
|
||||
#define LOCK_DIE_ON_ERROR 1
|
||||
|
||||
/*
|
||||
* Usually symbolic links in the destination path are resolved. This
|
||||
* means that (1) the lockfile is created by adding ".lock" to the
|
||||
* resolved path, and (2) upon commit, the resolved path is
|
||||
* overwritten. However, if `LOCK_NO_DEREF` is set, then the lockfile
|
||||
* is created by adding ".lock" to the path argument itself. This
|
||||
* option is used, for example, when detaching a symbolic reference,
|
||||
* which for backwards-compatibility reasons, can be a symbolic link
|
||||
* containing the name of the referred-to-reference.
|
||||
*/
|
||||
#define LOCK_NO_DEREF 2
|
||||
|
||||
extern void unable_to_lock_message(const char *path, int err,
|
||||
struct strbuf *buf);
|
||||
extern NORETURN void unable_to_lock_die(const char *path, int err);
|
||||
/*
|
||||
* Attempt to create a lockfile for the file at `path` and return a
|
||||
* file descriptor for writing to it, or -1 on error. If the file is
|
||||
* currently locked, retry with quadratic backoff for at least
|
||||
* timeout_ms milliseconds. If timeout_ms is 0, try exactly once; if
|
||||
* timeout_ms is -1, retry indefinitely. The flags argument and error
|
||||
* handling are described above.
|
||||
*/
|
||||
extern int hold_lock_file_for_update_timeout(
|
||||
struct lock_file *lk, const char *path,
|
||||
int flags, long timeout_ms);
|
||||
|
||||
/*
|
||||
* Attempt to create a lockfile for the file at `path` and return a
|
||||
* file descriptor for writing to it, or -1 on error. The flags
|
||||
* argument and error handling are described above.
|
||||
*/
|
||||
static inline int hold_lock_file_for_update(
|
||||
struct lock_file *lk, const char *path,
|
||||
int flags)
|
||||
|
@ -85,15 +167,135 @@ static inline int hold_lock_file_for_update(
|
|||
return hold_lock_file_for_update_timeout(lk, path, flags, 0);
|
||||
}
|
||||
|
||||
extern int hold_lock_file_for_append(struct lock_file *lk, const char *path,
|
||||
int flags);
|
||||
/*
|
||||
* Like `hold_lock_file_for_update()`, but before returning copy the
|
||||
* existing contents of the file (if any) to the lockfile and position
|
||||
* its write pointer at the end of the file. The flags argument and
|
||||
* error handling are described above.
|
||||
*/
|
||||
extern int hold_lock_file_for_append(struct lock_file *lk,
|
||||
const char *path, int flags);
|
||||
|
||||
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 *);
|
||||
/*
|
||||
* 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);
|
||||
|
||||
/*
|
||||
* 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 */
|
||||
|
|
40
read-cache.c
40
read-cache.c
|
@ -5,6 +5,7 @@
|
|||
*/
|
||||
#define NO_THE_INDEX_COMPATIBILITY_MACROS
|
||||
#include "cache.h"
|
||||
#include "tempfile.h"
|
||||
#include "lockfile.h"
|
||||
#include "cache-tree.h"
|
||||
#include "refs.h"
|
||||
|
@ -2113,7 +2114,7 @@ static int commit_locked_index(struct lock_file *lk)
|
|||
static int do_write_locked_index(struct index_state *istate, struct lock_file *lock,
|
||||
unsigned flags)
|
||||
{
|
||||
int ret = do_write_index(istate, lock->fd, 0);
|
||||
int ret = do_write_index(istate, get_lock_file_fd(lock), 0);
|
||||
if (ret)
|
||||
return ret;
|
||||
assert((flags & (COMMIT_LOCK | CLOSE_LOCK)) !=
|
||||
|
@ -2137,54 +2138,27 @@ static int write_split_index(struct index_state *istate,
|
|||
return ret;
|
||||
}
|
||||
|
||||
static char *temporary_sharedindex;
|
||||
|
||||
static void remove_temporary_sharedindex(void)
|
||||
{
|
||||
if (temporary_sharedindex) {
|
||||
unlink_or_warn(temporary_sharedindex);
|
||||
free(temporary_sharedindex);
|
||||
temporary_sharedindex = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
static void remove_temporary_sharedindex_on_signal(int signo)
|
||||
{
|
||||
remove_temporary_sharedindex();
|
||||
sigchain_pop(signo);
|
||||
raise(signo);
|
||||
}
|
||||
static struct tempfile temporary_sharedindex;
|
||||
|
||||
static int write_shared_index(struct index_state *istate,
|
||||
struct lock_file *lock, unsigned flags)
|
||||
{
|
||||
struct split_index *si = istate->split_index;
|
||||
static int installed_handler;
|
||||
int fd, ret;
|
||||
|
||||
temporary_sharedindex = git_pathdup("sharedindex_XXXXXX");
|
||||
fd = mkstemp(temporary_sharedindex);
|
||||
fd = mks_tempfile(&temporary_sharedindex, git_path("sharedindex_XXXXXX"));
|
||||
if (fd < 0) {
|
||||
free(temporary_sharedindex);
|
||||
temporary_sharedindex = NULL;
|
||||
hashclr(si->base_sha1);
|
||||
return do_write_locked_index(istate, lock, flags);
|
||||
}
|
||||
if (!installed_handler) {
|
||||
atexit(remove_temporary_sharedindex);
|
||||
sigchain_push_common(remove_temporary_sharedindex_on_signal);
|
||||
}
|
||||
move_cache_to_base_index(istate);
|
||||
ret = do_write_index(si->base, fd, 1);
|
||||
close(fd);
|
||||
if (ret) {
|
||||
remove_temporary_sharedindex();
|
||||
delete_tempfile(&temporary_sharedindex);
|
||||
return ret;
|
||||
}
|
||||
ret = rename(temporary_sharedindex,
|
||||
git_path("sharedindex.%s", sha1_to_hex(si->base->sha1)));
|
||||
free(temporary_sharedindex);
|
||||
temporary_sharedindex = NULL;
|
||||
ret = rename_tempfile(&temporary_sharedindex,
|
||||
git_path("sharedindex.%s", sha1_to_hex(si->base->sha1)));
|
||||
if (!ret)
|
||||
hashcpy(si->base_sha1, si->base->sha1);
|
||||
return ret;
|
||||
|
|
18
refs.c
18
refs.c
|
@ -3401,6 +3401,7 @@ static int write_ref_to_lockfile(struct ref_lock *lock,
|
|||
{
|
||||
static char term = '\n';
|
||||
struct object *o;
|
||||
int fd;
|
||||
|
||||
o = parse_object(sha1);
|
||||
if (!o) {
|
||||
|
@ -3417,11 +3418,12 @@ static int write_ref_to_lockfile(struct ref_lock *lock,
|
|||
unlock_ref(lock);
|
||||
return -1;
|
||||
}
|
||||
if (write_in_full(lock->lk->fd, sha1_to_hex(sha1), 40) != 40 ||
|
||||
write_in_full(lock->lk->fd, &term, 1) != 1 ||
|
||||
fd = get_lock_file_fd(lock->lk);
|
||||
if (write_in_full(fd, sha1_to_hex(sha1), 40) != 40 ||
|
||||
write_in_full(fd, &term, 1) != 1 ||
|
||||
close_ref(lock) < 0) {
|
||||
strbuf_addf(err,
|
||||
"Couldn't write %s", lock->lk->filename.buf);
|
||||
"Couldn't write %s", get_lock_file_path(lock->lk));
|
||||
unlock_ref(lock);
|
||||
return -1;
|
||||
}
|
||||
|
@ -4608,7 +4610,7 @@ int reflog_expire(const char *refname, const unsigned char *sha1,
|
|||
cb.newlog = fdopen_lock_file(&reflog_lock, "w");
|
||||
if (!cb.newlog) {
|
||||
error("cannot fdopen %s (%s)",
|
||||
reflog_lock.filename.buf, strerror(errno));
|
||||
get_lock_file_path(&reflog_lock), strerror(errno));
|
||||
goto failure;
|
||||
}
|
||||
}
|
||||
|
@ -4633,12 +4635,12 @@ int reflog_expire(const char *refname, const unsigned char *sha1,
|
|||
status |= error("couldn't write %s: %s", log_file,
|
||||
strerror(errno));
|
||||
} else if (update &&
|
||||
(write_in_full(lock->lk->fd,
|
||||
(write_in_full(get_lock_file_fd(lock->lk),
|
||||
sha1_to_hex(cb.last_kept_sha1), 40) != 40 ||
|
||||
write_str_in_full(lock->lk->fd, "\n") != 1 ||
|
||||
close_ref(lock) < 0)) {
|
||||
write_str_in_full(get_lock_file_fd(lock->lk), "\n") != 1 ||
|
||||
close_ref(lock) < 0)) {
|
||||
status |= error("couldn't write %s",
|
||||
lock->lk->filename.buf);
|
||||
get_lock_file_path(lock->lk));
|
||||
rollback_lock_file(&reflog_lock);
|
||||
} else if (commit_lock_file(&reflog_lock)) {
|
||||
status |= error("unable to commit reflog '%s' (%s)",
|
||||
|
|
41
shallow.c
41
shallow.c
|
@ -1,4 +1,5 @@
|
|||
#include "cache.h"
|
||||
#include "tempfile.h"
|
||||
#include "lockfile.h"
|
||||
#include "commit.h"
|
||||
#include "tag.h"
|
||||
|
@ -208,50 +209,28 @@ int write_shallow_commits(struct strbuf *out, int use_pack_protocol,
|
|||
return write_shallow_commits_1(out, use_pack_protocol, extra, 0);
|
||||
}
|
||||
|
||||
static struct strbuf temporary_shallow = STRBUF_INIT;
|
||||
|
||||
static void remove_temporary_shallow(void)
|
||||
{
|
||||
if (temporary_shallow.len) {
|
||||
unlink_or_warn(temporary_shallow.buf);
|
||||
strbuf_reset(&temporary_shallow);
|
||||
}
|
||||
}
|
||||
|
||||
static void remove_temporary_shallow_on_signal(int signo)
|
||||
{
|
||||
remove_temporary_shallow();
|
||||
sigchain_pop(signo);
|
||||
raise(signo);
|
||||
}
|
||||
static struct tempfile temporary_shallow;
|
||||
|
||||
const char *setup_temporary_shallow(const struct sha1_array *extra)
|
||||
{
|
||||
struct strbuf sb = STRBUF_INIT;
|
||||
int fd;
|
||||
|
||||
if (temporary_shallow.len)
|
||||
die("BUG: attempt to create two temporary shallow files");
|
||||
|
||||
if (write_shallow_commits(&sb, 0, extra)) {
|
||||
strbuf_addstr(&temporary_shallow, git_path("shallow_XXXXXX"));
|
||||
fd = xmkstemp(temporary_shallow.buf);
|
||||
|
||||
atexit(remove_temporary_shallow);
|
||||
sigchain_push_common(remove_temporary_shallow_on_signal);
|
||||
fd = xmks_tempfile(&temporary_shallow, git_path("shallow_XXXXXX"));
|
||||
|
||||
if (write_in_full(fd, sb.buf, sb.len) != sb.len)
|
||||
die_errno("failed to write to %s",
|
||||
temporary_shallow.buf);
|
||||
close(fd);
|
||||
get_tempfile_path(&temporary_shallow));
|
||||
close_tempfile(&temporary_shallow);
|
||||
strbuf_release(&sb);
|
||||
return temporary_shallow.buf;
|
||||
return get_tempfile_path(&temporary_shallow);
|
||||
}
|
||||
/*
|
||||
* is_repository_shallow() sees empty string as "no shallow
|
||||
* file".
|
||||
*/
|
||||
return temporary_shallow.buf;
|
||||
return get_tempfile_path(&temporary_shallow);
|
||||
}
|
||||
|
||||
void setup_alternate_shallow(struct lock_file *shallow_lock,
|
||||
|
@ -267,8 +246,8 @@ void setup_alternate_shallow(struct lock_file *shallow_lock,
|
|||
if (write_shallow_commits(&sb, 0, extra)) {
|
||||
if (write_in_full(fd, sb.buf, sb.len) != sb.len)
|
||||
die_errno("failed to write to %s",
|
||||
shallow_lock->filename.buf);
|
||||
*alternate_shallow_file = shallow_lock->filename.buf;
|
||||
get_lock_file_path(shallow_lock));
|
||||
*alternate_shallow_file = get_lock_file_path(shallow_lock);
|
||||
} else
|
||||
/*
|
||||
* is_repository_shallow() sees empty string as "no
|
||||
|
@ -314,7 +293,7 @@ void prune_shallow(int show_only)
|
|||
if (write_shallow_commits_1(&sb, 0, NULL, SEEN_ONLY)) {
|
||||
if (write_in_full(fd, sb.buf, sb.len) != sb.len)
|
||||
die_errno("failed to write to %s",
|
||||
shallow_lock.filename.buf);
|
||||
get_lock_file_path(&shallow_lock));
|
||||
commit_lock_file(&shallow_lock);
|
||||
} else {
|
||||
unlink(git_path_shallow());
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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 */
|
Загрузка…
Ссылка в новой задаче