Browse Source
Rearrange/rewrite it somewhat to fit its new environment. Signed-off-by: Michael Haggerty <mhagger@alum.mit.edu> Signed-off-by: Junio C Hamano <gitster@pobox.com>maint
Michael Haggerty
10 years ago
committed by
Junio C Hamano
3 changed files with 283 additions and 280 deletions
@ -1,220 +0,0 @@
@@ -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. |
Loading…
Reference in new issue