From: Michael Haggerty <mhagger@alum.mit.edu>
To: Junio C Hamano <gitster@pobox.com>
Cc: git@vger.kernel.org, "Johannes Sixt" <j.sixt@viscovery.net>,
"Jeff King" <peff@peff.net>,
"Torsten Bögershausen" <tboegi@web.de>,
"Eric Sunshine" <sunshine@sunshineco.com>,
"Michael Haggerty" <mhagger@alum.mit.edu>
Subject: [PATCH v3 17/25] lockfile: avoid transitory invalid states
Date: Mon, 14 Apr 2014 15:54:47 +0200 [thread overview]
Message-ID: <1397483695-10888-18-git-send-email-mhagger@alum.mit.edu> (raw)
In-Reply-To: <1397483695-10888-1-git-send-email-mhagger@alum.mit.edu>
Because remove_lock_file() can be called any time by the signal
handler, it is important that any lock_file objects that are in the
lock_file_list are always in a valid state. And since lock_file
objects are often reused (but are never removed from lock_file_list),
that means we have to be careful whenever mutating a lock_file object
to always keep it in a well-defined state.
This was formerly not the case, because part of the state was recorded
by whether lk->filename was the empty string or a valid filename. It
is wrong to assume that this string can be updated atomically; for
example, even
strcpy(lk->filename, value)
is unsafe. But the old code was even more reckless; for example,
strcpy(lk->filename, path);
if (!(flags & LOCK_NODEREF))
resolve_symlink(lk->filename, max_path_len);
strcat(lk->filename, ".lock");
During the call to resolve_symlink(), lk->filename contained the name
of the file that was being locked, not the name of the lockfile. If a
signal would have been raised during that interval, then the signal
handler would have deleted the valuable file!
We could probably continue to use the filename field to encode the
state by being careful to write characters 1..N-1 of the filename
first, and then overwrite the NUL at filename[0] with the first
character of the filename, but that would be awkward and error-prone.
So, instead of using the filename field to determine whether the
lock_file object is active, add a new field "lock_file::active" for
this purpose. Be careful to set this field only when filename really
contains the name of a file that should be deleted on cleanup.
Helped-by: Johannes Sixt <j.sixt@viscovery.net>
Signed-off-by: Michael Haggerty <mhagger@alum.mit.edu>
---
cache.h | 1 +
lockfile.c | 45 +++++++++++++++++++++++++++++++++------------
2 files changed, 34 insertions(+), 12 deletions(-)
diff --git a/cache.h b/cache.h
index 825cd0a..b7af173 100644
--- a/cache.h
+++ b/cache.h
@@ -539,6 +539,7 @@ extern int refresh_index(struct index_state *, unsigned int flags, const struct
struct lock_file {
struct lock_file *next;
+ volatile sig_atomic_t active;
int fd;
pid_t owner;
char on_list;
diff --git a/lockfile.c b/lockfile.c
index 1453a7a..50a0541 100644
--- a/lockfile.c
+++ b/lockfile.c
@@ -27,11 +27,14 @@
* Instead of (3), the change can be rolled back by deleting lockfile.
*
* This module keeps track of all locked files in lock_file_list.
- * When the first file is locked, it registers an atexit(3) handler;
- * when the program exits, the handler rolls back any files that have
- * been locked but were never committed or rolled back.
+ * When the first file is locked, it registers an atexit(3) handler
+ * and a signal handler; when the program exits, the handler rolls
+ * back any files that have been locked but were never committed or
+ * rolled back.
*
- * A lock_file object can be in several states:
+ * Because the signal handler can be called at any time, a lock_file
+ * object must always be in a well-defined state. The possible states
+ * 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
@@ -39,18 +42,29 @@
* registered in the lock_file_list, and on_list is set.
*
* - Locked, lockfile open (after hold_lock_file_for_update() or
- * hold_lock_file_for_append()). In this state, the lockfile
- * exists, filename holds the filename of the lockfile, fd holds a
- * file descriptor open for writing to the lockfile, and owner holds
- * the PID of the process that locked the file.
+ * hold_lock_file_for_append()). 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
+ * - owner holds the PID of the process that locked the file
*
- * - Locked, lockfile closed (after close_lock_file()). Same as the
- * previous state, except that the lockfile is closed and fd is -1.
+ * - Locked, lockfile closed (after close_lock_file() or an
+ * unsuccessful commit_lock_file()). Same as the previous state,
+ * except that the lockfile is closed and fd is -1.
*
* - Unlocked (after commit_lock_file(), rollback_lock_file(), or a
* failed attempt to lock). In this state, filename[0] == '\0' and
* fd is -1. The object is left registered in the lock_file_list,
* and on_list is set.
+ * - Unlocked (after rollback_lock_file(), a successful
+ * commit_lock_file(), or a failed attempt to lock). In this state:
+ * - active is unset
+ * - filename[0] == '\0' (usually, though there are transitory states
+ * in which this condition doesn't hold)
+ * - fd is -1
+ * - the object is left registered in the lock_file_list, and
+ * on_list is set.
*
* See Documentation/api-lockfile.txt for more information.
*/
@@ -183,9 +197,12 @@ static int lock_file(struct lock_file *lk, const char *path, int flags)
atexit(remove_lock_file);
}
+ assert(!lk->active);
+
if (!lk->on_list) {
/* Initialize *lk and add it to lock_file_list: */
lk->fd = -1;
+ lk->active = 0;
lk->owner = 0;
lk->on_list = 1;
lk->filename[0] = 0;
@@ -205,6 +222,7 @@ static int lock_file(struct lock_file *lk, const char *path, int flags)
return -1;
}
lk->owner = getpid();
+ lk->active = 1;
if (adjust_shared_perm(lk->filename)) {
error("cannot fix permission bits on %s", lk->filename);
rollback_lock_file(lk);
@@ -292,7 +310,7 @@ int commit_lock_file(struct lock_file *lk)
if (lk->fd >= 0 && close_lock_file(lk))
return -1;
- if (!lk->filename[0])
+ if (!lk->active)
die("BUG: attempt to commit unlocked object");
strcpy(result_file, lk->filename);
@@ -301,6 +319,7 @@ int commit_lock_file(struct lock_file *lk)
if (rename(lk->filename, result_file))
return -1;
+ lk->active = 0;
lk->filename[0] = 0;
return 0;
}
@@ -325,6 +344,7 @@ int commit_locked_index(struct lock_file *lk)
return -1;
if (rename(lk->filename, alternate_index_output))
return -1;
+ lk->active = 0;
lk->filename[0] = 0;
return 0;
}
@@ -334,10 +354,11 @@ int commit_locked_index(struct lock_file *lk)
void rollback_lock_file(struct lock_file *lk)
{
- if (lk->filename[0]) {
+ if (lk->active) {
if (lk->fd >= 0)
close_lock_file(lk);
unlink_or_warn(lk->filename);
+ lk->active = 0;
lk->filename[0] = 0;
}
}
--
1.9.1
next prev parent reply other threads:[~2014-04-14 13:56 UTC|newest]
Thread overview: 32+ messages / expand[flat|nested] mbox.gz Atom feed top
2014-04-14 13:54 [PATCH v3 00/25] Lockfile correctness and refactoring Michael Haggerty
2014-04-14 13:54 ` [PATCH v3 01/25] unable_to_lock_die(): rename function from unable_to_lock_index_die() Michael Haggerty
2014-04-14 13:54 ` [PATCH v3 02/25] api-lockfile: expand the documentation Michael Haggerty
2014-04-14 13:54 ` [PATCH v3 03/25] rollback_lock_file(): do not clear filename redundantly Michael Haggerty
2014-04-14 13:54 ` [PATCH v3 04/25] rollback_lock_file(): set fd to -1 Michael Haggerty
2014-04-14 13:54 ` [PATCH v3 05/25] lockfile: unlock file if lockfile permissions cannot be adjusted Michael Haggerty
2014-04-14 13:54 ` [PATCH v3 06/25] hold_lock_file_for_append(): release lock on errors Michael Haggerty
2014-04-14 13:54 ` [PATCH v3 07/25] lock_file(): always add lock_file object to lock_file_list Michael Haggerty
2014-04-14 13:54 ` [PATCH v3 08/25] lockfile.c: document the various states of lock_file objects Michael Haggerty
2014-04-14 13:54 ` [PATCH v3 09/25] cache.h: define constants LOCK_SUFFIX and LOCK_SUFFIX_LEN Michael Haggerty
2014-04-14 13:54 ` [PATCH v3 10/25] delete_ref_loose(): don't muck around in the lock_file's filename Michael Haggerty
2014-04-14 13:54 ` [PATCH v3 11/25] prepare_index(): declare return value to be (const char *) Michael Haggerty
2014-04-14 13:54 ` [PATCH v3 12/25] write_packed_entry_fn(): convert cb_data into a (const int *) Michael Haggerty
2014-04-14 13:54 ` [PATCH v3 13/25] lock_file(): exit early if lockfile cannot be opened Michael Haggerty
2014-04-14 13:54 ` [PATCH v3 14/25] remove_lock_file(): call rollback_lock_file() Michael Haggerty
2014-04-14 13:54 ` [PATCH v3 15/25] commit_lock_file(): inline temporary variable Michael Haggerty
2014-04-14 13:54 ` [PATCH v3 16/25] commit_lock_file(): die() if called for unlocked lockfile object Michael Haggerty
2014-04-15 6:49 ` Johannes Sixt
2014-04-16 15:17 ` Michael Haggerty
2014-04-14 13:54 ` Michael Haggerty [this message]
2014-04-14 13:54 ` [PATCH v3 18/25] struct lock_file: declare some fields volatile Michael Haggerty
2014-04-15 6:55 ` Johannes Sixt
2014-04-16 15:36 ` Michael Haggerty
2014-04-14 13:54 ` [PATCH v3 19/25] try_merge_strategy(): remove redundant lock_file allocation Michael Haggerty
2014-04-14 13:54 ` [PATCH v3 20/25] try_merge_strategy(): use a statically-allocated lock_file object Michael Haggerty
2014-04-14 13:54 ` [PATCH v3 21/25] commit_lock_file(): use a strbuf to manage temporary space Michael Haggerty
2014-04-14 13:54 ` [PATCH v3 22/25] Change lock_file::filename into a strbuf Michael Haggerty
2014-04-14 13:54 ` [PATCH v3 23/25] resolve_symlink(): use a strbuf for internal scratch space Michael Haggerty
2014-04-14 13:54 ` [PATCH v3 24/25] resolve_symlink(): take a strbuf parameter Michael Haggerty
2014-04-14 13:54 ` [PATCH v3 25/25] trim_last_path_elm(): replace last_path_elm() Michael Haggerty
2014-04-15 18:40 ` [PATCH v3 00/25] Lockfile correctness and refactoring Torsten Bögershausen
2014-04-16 19:50 ` Michael Haggerty
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=1397483695-10888-18-git-send-email-mhagger@alum.mit.edu \
--to=mhagger@alum.mit.edu \
--cc=git@vger.kernel.org \
--cc=gitster@pobox.com \
--cc=j.sixt@viscovery.net \
--cc=peff@peff.net \
--cc=sunshine@sunshineco.com \
--cc=tboegi@web.de \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).