fsync calls file_sync with WAIT set and OMIT_METADATA clear, which
fs.defs defines as syncing the file's metadata too, and POSIX requires
fsync not to return before the data is on the storage device.  With a
journal, diskfs_file_update writes the data but leaves the metadata to
the journal, and diskfs_S_file_sync never asked for a commit.  The new
inode, size and block map stayed in the running transaction until
kjournald's next commit, up to five seconds later, so a file could be
lost in a crash right after a successful fsync.  Creating files,
running "sync FILE...", which calls fsync, and killing the translator
at once lost all of them, while "sync -f", which goes through syncfs
and diskfs_sync_everything, kept them.  This dates from commit
bc8ac23d6"ext2fs: Add JBD2 journaling to ext2/libdiskfs".

When WAIT is set, open a handle after diskfs_file_update and commit
it: everything so far is then on disk, the file's metadata included,
as diskfs_journal_commit_transaction now also waits for the older
transactions.  This is what ext4's fsync does with
jbd2_complete_transaction.  The handle is opened only after the data
writeback: held during it, a commit of its transaction would wait for
it, and every other start would wait for that commit.  Without a
journal the handle is NULL and nothing changes.
---
 libdiskfs/file-sync.c | 15 +++++++++++++++
 1 file changed, 15 insertions(+)

diff --git a/libdiskfs/file-sync.c b/libdiskfs/file-sync.c
index 20fa005c6..ac376702f 100644
--- a/libdiskfs/file-sync.c
+++ b/libdiskfs/file-sync.c
@@ -38,5 +38,20 @@ diskfs_S_file_sync (struct protid *cred,
   iohelp_get_conch (&np->conch);
   pthread_mutex_unlock (&np->lock);
   diskfs_file_update (np, wait);
+
+  if (wait)
+    {
+      /* With a journal, diskfs_file_update leaves the metadata to it.
+        Commit everything so far and wait for it, as jbd2's
+        jbd2_complete_transaction does for ext4's fsync, so that the file
+        survives a crash once this returns.  The metadata need not be in
+        this handle's transaction: committing it also waits for the older
+        ones.  The handle is opened only after the data writeback, since
+        holding it there would stall every other start for as long as a
+        commit of the transaction waits for it.  Without a journal it is
+        NULL, and committing it does nothing.  */
+      diskfs_transaction_t *txn = diskfs_journal_start_transaction ();
+      diskfs_journal_commit_transaction (txn);
+    }
   return 0;
 }
-- 
2.56.0


Reply via email to