Browse Source
A lot of work went into defining the state diagram for lockfiles and ensuring correct, race-resistant cleanup in all circumstances. Most of that infrastructure can be applied directly to *any* temporary file. So extract a new "tempfile" module from the "lockfile" module. Reimplement lockfile on top of tempfile. Subsequent commits will add more users of the new module. Signed-off-by: Michael Haggerty <mhagger@alum.mit.edu> Signed-off-by: Junio C Hamano <gitster@pobox.com>maint


5 changed files with 470 additions and 270 deletions
@ -0,0 +1,238 @@ |
|||||||
|
/* |
||||||
|
* State diagram and cleanup |
||||||
|
* ------------------------- |
||||||
|
* |
||||||
|
* If the program exits while a temporary file is active, we want to |
||||||
|
* make sure that we remove it. This is done by remembering the active |
||||||
|
* temporary files in a linked list, `tempfile_list`. An `atexit(3)` |
||||||
|
* handler and a signal handler are registered, to clean up any active |
||||||
|
* temporary files. |
||||||
|
* |
||||||
|
* Because the signal handler can run at any time, `tempfile_list` and |
||||||
|
* the `tempfile` objects that comprise it must be kept in |
||||||
|
* self-consistent states at all times. |
||||||
|
* |
||||||
|
* The possible states of a `tempfile` object are as follows: |
||||||
|
* |
||||||
|
* - Uninitialized. In this state the object's `on_list` field must be |
||||||
|
* zero but the rest of its contents need not be initialized. As |
||||||
|
* soon as the object is used in any way, it is irrevocably |
||||||
|
* registered in `tempfile_list`, and `on_list` is set. |
||||||
|
* |
||||||
|
* - Active, file open (after `create_tempfile()` or |
||||||
|
* `reopen_tempfile()`). In this state: |
||||||
|
* |
||||||
|
* - the temporary file exists |
||||||
|
* - `active` is set |
||||||
|
* - `filename` holds the filename of the temporary file |
||||||
|
* - `fd` holds a file descriptor open for writing to it |
||||||
|
* - `fp` holds a pointer to an open `FILE` object if and only if |
||||||
|
* `fdopen_tempfile()` has been called on the object |
||||||
|
* - `owner` holds the PID of the process that created the file |
||||||
|
* |
||||||
|
* - Active, file closed (after successful `close_tempfile()`). Same |
||||||
|
* as the previous state, except that the temporary file is closed, |
||||||
|
* `fd` is -1, and `fp` is `NULL`. |
||||||
|
* |
||||||
|
* - Inactive (after `delete_tempfile()`, `rename_tempfile()`, a |
||||||
|
* failed attempt to create a temporary file, or a failed |
||||||
|
* `close_tempfile()`). In this state: |
||||||
|
* |
||||||
|
* - `active` is unset |
||||||
|
* - `filename` is empty (usually, though there are transitory |
||||||
|
* states in which this condition doesn't hold). Client code should |
||||||
|
* *not* rely on the filename being empty in this state. |
||||||
|
* - `fd` is -1 and `fp` is `NULL` |
||||||
|
* - the object is left registered in the `tempfile_list`, and |
||||||
|
* `on_list` is set. |
||||||
|
* |
||||||
|
* A temporary file is owned by the process that created it. The |
||||||
|
* `tempfile` has an `owner` field that records the owner's PID. This |
||||||
|
* field is used to prevent a forked process from deleting a temporary |
||||||
|
* file created by its parent. |
||||||
|
*/ |
||||||
|
|
||||||
|
#include "cache.h" |
||||||
|
#include "tempfile.h" |
||||||
|
#include "sigchain.h" |
||||||
|
|
||||||
|
static struct tempfile *volatile tempfile_list; |
||||||
|
|
||||||
|
static void remove_tempfiles(int skip_fclose) |
||||||
|
{ |
||||||
|
pid_t me = getpid(); |
||||||
|
|
||||||
|
while (tempfile_list) { |
||||||
|
if (tempfile_list->owner == me) { |
||||||
|
/* fclose() is not safe to call in a signal handler */ |
||||||
|
if (skip_fclose) |
||||||
|
tempfile_list->fp = NULL; |
||||||
|
delete_tempfile(tempfile_list); |
||||||
|
} |
||||||
|
tempfile_list = tempfile_list->next; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
static void remove_tempfiles_on_exit(void) |
||||||
|
{ |
||||||
|
remove_tempfiles(0); |
||||||
|
} |
||||||
|
|
||||||
|
static void remove_tempfiles_on_signal(int signo) |
||||||
|
{ |
||||||
|
remove_tempfiles(1); |
||||||
|
sigchain_pop(signo); |
||||||
|
raise(signo); |
||||||
|
} |
||||||
|
|
||||||
|
/* Make sure errno contains a meaningful value on error */ |
||||||
|
int create_tempfile(struct tempfile *tempfile, const char *path) |
||||||
|
{ |
||||||
|
size_t pathlen = strlen(path); |
||||||
|
|
||||||
|
if (!tempfile_list) { |
||||||
|
/* One-time initialization */ |
||||||
|
sigchain_push_common(remove_tempfiles_on_signal); |
||||||
|
atexit(remove_tempfiles_on_exit); |
||||||
|
} |
||||||
|
|
||||||
|
if (tempfile->active) |
||||||
|
die("BUG: create_tempfile called for active object"); |
||||||
|
if (!tempfile->on_list) { |
||||||
|
/* Initialize *tempfile and add it to tempfile_list: */ |
||||||
|
tempfile->fd = -1; |
||||||
|
tempfile->fp = NULL; |
||||||
|
tempfile->active = 0; |
||||||
|
tempfile->owner = 0; |
||||||
|
strbuf_init(&tempfile->filename, pathlen); |
||||||
|
tempfile->next = tempfile_list; |
||||||
|
tempfile_list = tempfile; |
||||||
|
tempfile->on_list = 1; |
||||||
|
} else if (tempfile->filename.len) { |
||||||
|
/* This shouldn't happen, but better safe than sorry. */ |
||||||
|
die("BUG: create_tempfile called for improperly-reset object"); |
||||||
|
} |
||||||
|
|
||||||
|
strbuf_add_absolute_path(&tempfile->filename, path); |
||||||
|
tempfile->fd = open(tempfile->filename.buf, O_RDWR | O_CREAT | O_EXCL, 0666); |
||||||
|
if (tempfile->fd < 0) { |
||||||
|
strbuf_reset(&tempfile->filename); |
||||||
|
return -1; |
||||||
|
} |
||||||
|
tempfile->owner = getpid(); |
||||||
|
tempfile->active = 1; |
||||||
|
if (adjust_shared_perm(tempfile->filename.buf)) { |
||||||
|
int save_errno = errno; |
||||||
|
error("cannot fix permission bits on %s", tempfile->filename.buf); |
||||||
|
delete_tempfile(tempfile); |
||||||
|
errno = save_errno; |
||||||
|
return -1; |
||||||
|
} |
||||||
|
return tempfile->fd; |
||||||
|
} |
||||||
|
|
||||||
|
FILE *fdopen_tempfile(struct tempfile *tempfile, const char *mode) |
||||||
|
{ |
||||||
|
if (!tempfile->active) |
||||||
|
die("BUG: fdopen_tempfile() called for inactive object"); |
||||||
|
if (tempfile->fp) |
||||||
|
die("BUG: fdopen_tempfile() called for open object"); |
||||||
|
|
||||||
|
tempfile->fp = fdopen(tempfile->fd, mode); |
||||||
|
return tempfile->fp; |
||||||
|
} |
||||||
|
|
||||||
|
const char *get_tempfile_path(struct tempfile *tempfile) |
||||||
|
{ |
||||||
|
if (!tempfile->active) |
||||||
|
die("BUG: get_tempfile_path() called for inactive object"); |
||||||
|
return tempfile->filename.buf; |
||||||
|
} |
||||||
|
|
||||||
|
int get_tempfile_fd(struct tempfile *tempfile) |
||||||
|
{ |
||||||
|
if (!tempfile->active) |
||||||
|
die("BUG: get_tempfile_fd() called for inactive object"); |
||||||
|
return tempfile->fd; |
||||||
|
} |
||||||
|
|
||||||
|
FILE *get_tempfile_fp(struct tempfile *tempfile) |
||||||
|
{ |
||||||
|
if (!tempfile->active) |
||||||
|
die("BUG: get_tempfile_fp() called for inactive object"); |
||||||
|
return tempfile->fp; |
||||||
|
} |
||||||
|
|
||||||
|
int close_tempfile(struct tempfile *tempfile) |
||||||
|
{ |
||||||
|
int fd = tempfile->fd; |
||||||
|
FILE *fp = tempfile->fp; |
||||||
|
int err; |
||||||
|
|
||||||
|
if (fd < 0) |
||||||
|
return 0; |
||||||
|
|
||||||
|
tempfile->fd = -1; |
||||||
|
if (fp) { |
||||||
|
tempfile->fp = NULL; |
||||||
|
|
||||||
|
/* |
||||||
|
* Note: no short-circuiting here; we want to fclose() |
||||||
|
* in any case! |
||||||
|
*/ |
||||||
|
err = ferror(fp) | fclose(fp); |
||||||
|
} else { |
||||||
|
err = close(fd); |
||||||
|
} |
||||||
|
|
||||||
|
if (err) { |
||||||
|
int save_errno = errno; |
||||||
|
delete_tempfile(tempfile); |
||||||
|
errno = save_errno; |
||||||
|
return -1; |
||||||
|
} |
||||||
|
|
||||||
|
return 0; |
||||||
|
} |
||||||
|
|
||||||
|
int reopen_tempfile(struct tempfile *tempfile) |
||||||
|
{ |
||||||
|
if (0 <= tempfile->fd) |
||||||
|
die("BUG: reopen_tempfile called for an open object"); |
||||||
|
if (!tempfile->active) |
||||||
|
die("BUG: reopen_tempfile called for an inactive object"); |
||||||
|
tempfile->fd = open(tempfile->filename.buf, O_WRONLY); |
||||||
|
return tempfile->fd; |
||||||
|
} |
||||||
|
|
||||||
|
int rename_tempfile(struct tempfile *tempfile, const char *path) |
||||||
|
{ |
||||||
|
if (!tempfile->active) |
||||||
|
die("BUG: rename_tempfile called for inactive object"); |
||||||
|
|
||||||
|
if (close_tempfile(tempfile)) |
||||||
|
return -1; |
||||||
|
|
||||||
|
if (rename(tempfile->filename.buf, path)) { |
||||||
|
int save_errno = errno; |
||||||
|
delete_tempfile(tempfile); |
||||||
|
errno = save_errno; |
||||||
|
return -1; |
||||||
|
} |
||||||
|
|
||||||
|
tempfile->active = 0; |
||||||
|
strbuf_reset(&tempfile->filename); |
||||||
|
return 0; |
||||||
|
} |
||||||
|
|
||||||
|
void delete_tempfile(struct tempfile *tempfile) |
||||||
|
{ |
||||||
|
if (!tempfile->active) |
||||||
|
return; |
||||||
|
|
||||||
|
if (!close_tempfile(tempfile)) { |
||||||
|
unlink_or_warn(tempfile->filename.buf); |
||||||
|
tempfile->active = 0; |
||||||
|
strbuf_reset(&tempfile->filename); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,167 @@ |
|||||||
|
#ifndef TEMPFILE_H |
||||||
|
#define TEMPFILE_H |
||||||
|
|
||||||
|
/* |
||||||
|
* Handle temporary files. |
||||||
|
* |
||||||
|
* The tempfile API allows temporary files to be created, deleted, and |
||||||
|
* atomically renamed. Temporary files that are still active when the |
||||||
|
* program ends are cleaned up automatically. Lockfiles (see |
||||||
|
* "lockfile.h") are built on top of this API. |
||||||
|
* |
||||||
|
* |
||||||
|
* Calling sequence |
||||||
|
* ---------------- |
||||||
|
* |
||||||
|
* The caller: |
||||||
|
* |
||||||
|
* * Allocates a `struct tempfile` either as a static variable or on |
||||||
|
* the heap, initialized to zeros. Once you use the structure to |
||||||
|
* call `create_tempfile()`, it belongs to the tempfile subsystem |
||||||
|
* and its storage must remain valid throughout the life of the |
||||||
|
* program (i.e. you cannot use an on-stack variable to hold this |
||||||
|
* structure). |
||||||
|
* |
||||||
|
* * Attempts to create a temporary file by calling |
||||||
|
* `create_tempfile()`. |
||||||
|
* |
||||||
|
* * Writes new content to the file by either: |
||||||
|
* |
||||||
|
* * writing to the file descriptor returned by `create_tempfile()` |
||||||
|
* (also available via `tempfile->fd`). |
||||||
|
* |
||||||
|
* * calling `fdopen_tempfile()` to get a `FILE` pointer for the |
||||||
|
* open file and writing to the file using stdio. |
||||||
|
* |
||||||
|
* When finished writing, the caller can: |
||||||
|
* |
||||||
|
* * Close the file descriptor and remove the temporary file by |
||||||
|
* calling `delete_tempfile()`. |
||||||
|
* |
||||||
|
* * Close the temporary file and rename it atomically to a specified |
||||||
|
* filename by calling `rename_tempfile()`. This relinquishes |
||||||
|
* control of the file. |
||||||
|
* |
||||||
|
* * Close the file descriptor without removing or renaming the |
||||||
|
* temporary file by calling `close_tempfile()`, and later call |
||||||
|
* `delete_tempfile()` or `rename_tempfile()`. |
||||||
|
* |
||||||
|
* Even after the temporary file is renamed or deleted, the `tempfile` |
||||||
|
* object must not be freed or altered by the caller. However, it may |
||||||
|
* be reused; just pass it to another call of `create_tempfile()`. |
||||||
|
* |
||||||
|
* If the program exits before `rename_tempfile()` or |
||||||
|
* `delete_tempfile()` is called, an `atexit(3)` handler will close |
||||||
|
* and remove the temporary file. |
||||||
|
* |
||||||
|
* If you need to close the file descriptor yourself, do so by calling |
||||||
|
* `close_tempfile()`. You should never call `close(2)` or `fclose(3)` |
||||||
|
* yourself, otherwise the `struct tempfile` structure would still |
||||||
|
* think that the file descriptor needs to be closed, and a later |
||||||
|
* cleanup would result in duplicate calls to `close(2)`. Worse yet, |
||||||
|
* if you close and then later open another file descriptor for a |
||||||
|
* completely different purpose, then the unrelated file descriptor |
||||||
|
* might get closed. |
||||||
|
* |
||||||
|
* |
||||||
|
* Error handling |
||||||
|
* -------------- |
||||||
|
* |
||||||
|
* `create_tempfile()` returns a file descriptor on success or -1 on |
||||||
|
* failure. On errors, `errno` describes the reason for failure. |
||||||
|
* |
||||||
|
* `delete_tempfile()`, `rename_tempfile()`, and `close_tempfile()` |
||||||
|
* return 0 on success. On failure they set `errno` appropriately, do |
||||||
|
* their best to delete the temporary file, and return -1. |
||||||
|
*/ |
||||||
|
|
||||||
|
struct tempfile { |
||||||
|
struct tempfile *volatile next; |
||||||
|
volatile sig_atomic_t active; |
||||||
|
volatile int fd; |
||||||
|
FILE *volatile fp; |
||||||
|
volatile pid_t owner; |
||||||
|
char on_list; |
||||||
|
struct strbuf filename; |
||||||
|
}; |
||||||
|
|
||||||
|
/* |
||||||
|
* Attempt to create a temporary file at the specified `path`. Return |
||||||
|
* a file descriptor for writing to it, or -1 on error. It is an error |
||||||
|
* if a file already exists at that path. |
||||||
|
*/ |
||||||
|
extern int create_tempfile(struct tempfile *tempfile, const char *path); |
||||||
|
|
||||||
|
/* |
||||||
|
* Associate a stdio stream with the temporary file (which must still |
||||||
|
* be open). Return `NULL` (*without* deleting the file) on error. The |
||||||
|
* stream is closed automatically when `close_tempfile()` is called or |
||||||
|
* when the file is deleted or renamed. |
||||||
|
*/ |
||||||
|
extern FILE *fdopen_tempfile(struct tempfile *tempfile, const char *mode); |
||||||
|
|
||||||
|
static inline int is_tempfile_active(struct tempfile *tempfile) |
||||||
|
{ |
||||||
|
return tempfile->active; |
||||||
|
} |
||||||
|
|
||||||
|
/* |
||||||
|
* Return the path of the lockfile. The return value is a pointer to a |
||||||
|
* field within the lock_file object and should not be freed. |
||||||
|
*/ |
||||||
|
extern const char *get_tempfile_path(struct tempfile *tempfile); |
||||||
|
|
||||||
|
extern int get_tempfile_fd(struct tempfile *tempfile); |
||||||
|
extern FILE *get_tempfile_fp(struct tempfile *tempfile); |
||||||
|
|
||||||
|
/* |
||||||
|
* If the temporary file is still open, close it (and the file pointer |
||||||
|
* too, if it has been opened using `fdopen_tempfile()`) without |
||||||
|
* deleting the file. Return 0 upon success. On failure to `close(2)`, |
||||||
|
* return a negative value and delete the file. Usually |
||||||
|
* `delete_tempfile()` or `rename_tempfile()` should eventually be |
||||||
|
* called if `close_tempfile()` succeeds. |
||||||
|
*/ |
||||||
|
extern int close_tempfile(struct tempfile *tempfile); |
||||||
|
|
||||||
|
/* |
||||||
|
* Re-open a temporary file that has been closed using |
||||||
|
* `close_tempfile()` but not yet deleted or renamed. This can be used |
||||||
|
* to implement a sequence of operations like the following: |
||||||
|
* |
||||||
|
* * Create temporary file. |
||||||
|
* |
||||||
|
* * Write new contents to file, then `close_tempfile()` to cause the |
||||||
|
* contents to be written to disk. |
||||||
|
* |
||||||
|
* * Pass the name of the temporary file to another program to allow |
||||||
|
* it (and nobody else) to inspect or even modify the file's |
||||||
|
* contents. |
||||||
|
* |
||||||
|
* * `reopen_tempfile()` to reopen the temporary file. Make further |
||||||
|
* updates to the contents. |
||||||
|
* |
||||||
|
* * `rename_tempfile()` to move the file to its permanent location. |
||||||
|
*/ |
||||||
|
extern int reopen_tempfile(struct tempfile *tempfile); |
||||||
|
|
||||||
|
/* |
||||||
|
* Close the file descriptor and/or file pointer and remove the |
||||||
|
* temporary file associated with `tempfile`. It is a NOOP to call |
||||||
|
* `delete_tempfile()` for a `tempfile` object that has already been |
||||||
|
* deleted or renamed. |
||||||
|
*/ |
||||||
|
extern void delete_tempfile(struct tempfile *tempfile); |
||||||
|
|
||||||
|
/* |
||||||
|
* Close the file descriptor and/or file pointer if they are still |
||||||
|
* open, and atomically rename the temporary file to `path`. `path` |
||||||
|
* must be on the same filesystem as the lock file. Return 0 on |
||||||
|
* success. On failure, delete the temporary file and return -1, with |
||||||
|
* `errno` set to the value from the failing call to `close(2)` or |
||||||
|
* `rename(2)`. It is a bug to call `rename_tempfile()` for a |
||||||
|
* `tempfile` object that is not currently active. |
||||||
|
*/ |
||||||
|
extern int rename_tempfile(struct tempfile *tempfile, const char *path); |
||||||
|
|
||||||
|
#endif /* TEMPFILE_H */ |
Loading…
Reference in new issue