
Nowadays, we unlock operations as we commit them. However, this could lead to
potential issues if we have an error in the middle of a commit when a
partially-overlapping transaction gets in the way.

The problem is that rollback gets done without locking because it assumes the
operations are already locked (by the caller commit), but in the case of
partially applied transactions they won't be, so it allows another transaction
to step into, leaving things in an inconsistent state.

This also leads to recovery and ordering problems.

The patch fixes this by doing all unlock at once, after the transaction has
been decided.

---

 cur-root/trans.c |   18 +++++++++++-------
 1 files changed, 11 insertions(+), 7 deletions(-)

diff -puN trans.c~fix_commit_locking trans.c
--- cur/trans.c~fix_commit_locking	2004-10-03 16:34:46.000000000 -0300
+++ cur-root/trans.c	2004-10-03 17:15:40.000000000 -0300
@@ -353,10 +353,6 @@ ssize_t jtrans_commit(struct jtrans *ts)
 	written = 0;
 	for (op = ts->op; op != NULL; op = op->next) {
 		rv = spwrite(ts->fs->fd, op->buf, op->len, op->offset);
-
-		plockf(ts->fs->fd, F_UNLOCK, op->offset, op->len);
-		op->locked = 0;
-
 		if (rv != op->len)
 			goto rollback_exit;
 
@@ -415,9 +411,17 @@ unlink_exit:
 	}
 
 	close(fd);
-	for (op = ts->op; op != NULL; op = op->next) {
-		if (op->locked)
-			plockf(ts->fs->fd, F_UNLOCK, op->offset, op->len);
+
+	/* always unlock everything at the end; otherwise we could have
+	 * half-overlapping transactions applying simultaneously, and if
+	 * anything goes wrong it's possible to break consistency */
+	if (!(ts->flags & J_NOLOCK)) {
+		for (op = ts->op; op != NULL; op = op->next) {
+			if (op->locked) {
+				plockf(ts->fs->fd, F_UNLOCK,
+						op->offset, op->len);
+			}
+		}
 	}
 
 exit:
_
