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