Several RPCs opened, ended or held their journal handle in ways that
could deadlock, stall a commit, or lose a commit's error:
- An inode write could be the outermost handle start with the node
lock held, which can wait for a commit that drains a transaction
whose participant needs that lock. Open the handle before the node
lock on every path to diskfs_node_update.
- io_write and io_read held their handle through the data copy, which
can wait for the disk or for memory, and every commit with it.
Release it once the metadata is done, unless the RPC must commit.
- diskfs_journal_commit_transaction now returns an error, which
file_sync passes on.
- An operation that needs a block on a full disk whose free blocks were
freed by its own transaction got ENOSPC, although they come back at
the next commit. io_write, dir_mkdir, dir_link, dir_rename,
dir_lookup with O_CREAT and file_set_translator now let go of
everything and try again, as ext4_should_retry_alloc does, through a
new diskfs_journal_should_retry_alloc hook and diskfs_retry_enospc.
- diskfs_rename_dir committed its nested handle on a synchronous mount,
which asserts; it now only stops it.
ext2fs gets its side of the changed and new hooks.
---
ext2fs/balloc.c | 11 +++++++++
ext2fs/ext2fs.h | 4 ++++
ext2fs/journal.c | 38 ++++++++++++++++++++++++++++---
ext2fs/pager.c | 11 +++++++++
libdiskfs/dir-link.c | 22 +++++++++++++-----
libdiskfs/dir-lookup.c | 46 +++++++++++++++++++++++++++++---------
libdiskfs/dir-mkdir.c | 19 +++++++++++-----
libdiskfs/dir-readdir.c | 10 ++++++++-
libdiskfs/dir-rename.c | 26 +++++++++++++++------
libdiskfs/dir-renamed.c | 6 +++--
libdiskfs/diskfs.h | 17 ++++++++++++--
libdiskfs/file-get-trans.c | 8 ++++++-
libdiskfs/file-set-trans.c | 39 ++++++++++++++++++++++++--------
libdiskfs/file-sync.c | 10 +++++++--
libdiskfs/fsys-getroot.c | 14 ++++++++++++
libdiskfs/init-startup.c | 6 +++++
libdiskfs/io-get-conch.c | 7 ++++++
libdiskfs/io-modes-off.c | 7 ++++++
libdiskfs/io-modes-on.c | 7 ++++++
libdiskfs/io-modes-set.c | 7 ++++++
libdiskfs/io-read.c | 17 +++++++++++++-
libdiskfs/io-readable.c | 6 +++++
libdiskfs/io-rel-conch.c | 7 ++++++
libdiskfs/io-seek.c | 6 +++++
libdiskfs/io-write.c | 19 ++++++++++++++++
libdiskfs/journal.c | 18 +++++++++++----
libdiskfs/priv.h | 15 +++++++++++++
27 files changed, 350 insertions(+), 53 deletions(-)
diff --git a/ext2fs/balloc.c b/ext2fs/balloc.c
index 32eafc5d0..74f710853 100644
--- a/ext2fs/balloc.c
+++ b/ext2fs/balloc.c
@@ -256,6 +256,17 @@ ext2_release_busy_blocks (block_t block, unsigned long
count)
pthread_spin_unlock (&global_lock);
}
+unsigned long
+ext2_free_blocks_now (void)
+{
+ unsigned long free;
+
+ pthread_spin_lock (&global_lock);
+ free = le32toh (sblock->s_free_blocks_count);
+ pthread_spin_unlock (&global_lock);
+ return free;
+}
+
/* Free COUNT blocks starting at BLOCK. With a journal, the blocks stay
busy until the transaction freeing them commits. When they cannot be
kept busy, they stay allocated instead: reusing them could put a
diff --git a/ext2fs/ext2fs.h b/ext2fs/ext2fs.h
index 0df45ced4..fc716bb74 100644
--- a/ext2fs/ext2fs.h
+++ b/ext2fs/ext2fs.h
@@ -722,6 +722,10 @@ void ext2_unreserve_blocks (block_t block, unsigned long
count);
/* Make COUNT blocks starting at BLOCK, freed by a transaction that has
now committed, available to ext2_new_block again. */
void ext2_release_busy_blocks (block_t block, unsigned long count);
+
+/* Returns how many blocks are free in the bitmap, those still busy
+ (waiting for the commit that freed them) included. */
+unsigned long ext2_free_blocks_now (void);
/* ---------------------------------------------------------------- */
diff --git a/ext2fs/journal.c b/ext2fs/journal.c
index 3ebd6a249..1abdb2a87 100644
--- a/ext2fs/journal.c
+++ b/ext2fs/journal.c
@@ -2010,6 +2010,35 @@ journal_wait_freed_blocks (diskfs_transaction_t *txn)
return waited;
}
+/* Whether an operation that got ENOSPC should try again, as
+ ext4_should_retry_alloc. Blocks freed by a transaction that has not
+ committed, or not given them back yet, are busy, and the allocator
+ cannot wait for them under the caller's handle: an operation whose own
+ transaction freed them gets ENOSPC although they come back at the next
+ commit. The caller has let go of its handle and node locks, so commit
+ the running transaction here and wait for the freed blocks, at most
+ three times an operation (*RETRIES). Only no free block at all, busy
+ ones included, means that the space is gone: the commit can have
+ given the freed blocks back since the allocation failed, and then
+ nothing is busy any more, though they are what the caller needs. */
+int
+diskfs_journal_should_retry_alloc (int *retries)
+{
+ diskfs_transaction_t *txn;
+
+ if (!ext2_journal || journal_thread_is_pager || journal_thread_depth > 0)
+ return 0;
+ if (*retries >= 3 || ext2_free_blocks_now () == 0)
+ return 0;
+ (*retries)++;
+
+ txn = diskfs_journal_start_transaction ();
+ if (diskfs_journal_commit_transaction (txn))
+ return 0;
+ journal_wait_freed_blocks (NULL);
+ return 1;
+}
+
void
journal_mark_dirty (diskfs_transaction_t *txn, block_t fs_blocknr)
{
@@ -3164,12 +3193,14 @@ diskfs_journal_stop_transaction (diskfs_transaction_t
*txn)
for top-level VFS RPCs that enforce POSIX fsync/O_SYNC guarantees.
This function consumes TXN. The caller must not use TXN after this call,
- and it MUST NOT call diskfs_journal_stop_transaction on it. */
-void
+ and it MUST NOT call diskfs_journal_stop_transaction on it.
+
+ Returns 0 once TXN is durable. */
+error_t
diskfs_journal_commit_transaction (diskfs_transaction_t *opaque_txn)
{
if (!ext2_journal || !opaque_txn)
- return;
+ return 0;
diskfs_transaction_t *txn = (diskfs_transaction_t *) opaque_txn;
uint32_t tid = txn->t_tid;
@@ -3210,6 +3241,7 @@ diskfs_journal_commit_transaction (diskfs_transaction_t
*opaque_txn)
journal_wait_on_tid_locked (ext2_journal, tid);
out:
JOURNAL_UNLOCK (ext2_journal);
+ return 0;
}
/**
diff --git a/ext2fs/pager.c b/ext2fs/pager.c
index 3fc1f64c4..fd2fafd5b 100644
--- a/ext2fs/pager.c
+++ b/ext2fs/pager.c
@@ -909,6 +909,7 @@ void
diskfs_file_update (struct node *node, int wait)
{
struct pager *pager;
+ diskfs_transaction_t *txn;
/* File data is not journaled, so a file's pages are written back here.
With a journal, a directory's blocks are: the commit makes its edits
@@ -941,7 +942,13 @@ diskfs_file_update (struct node *node, int wait)
else
pokel_sync (&diskfs_node_disknode (node)->indir_pokel, wait);
+ /* Every caller holds a handle except diskfs_S_file_sync, which opens
+ none around the writeback above (holding one there would stall every
+ other start for as long as a commit waited for it) and holds no node
+ lock here, so this start may wait. For the others it nests. */
+ txn = diskfs_journal_start_transaction ();
diskfs_node_update (node, wait);
+ diskfs_journal_stop_transaction (txn);
}
/* Invalidate any pager data associated with NODE. */
@@ -1680,8 +1687,12 @@ enable_caching (void)
turned off, if that's really necessary. */
if (upi->type == FILE_DATA)
{
+ /* For an unlinked file, the release writes the node back and
+ can drop it. No lock is held here. */
+ diskfs_transaction_t *txn = diskfs_journal_start_transaction ();
diskfs_nref (upi->node);
diskfs_nrele (upi->node);
+ diskfs_journal_stop_transaction (txn);
}
return 0;
diff --git a/libdiskfs/dir-link.c b/libdiskfs/dir-link.c
index 7892d49e9..29567e7a6 100644
--- a/libdiskfs/dir-link.c
+++ b/libdiskfs/dir-link.c
@@ -19,12 +19,12 @@
#include "priv.h"
#include "fs_S.h"
-/* Implement dir_link as described in <hurd/fs.defs>. */
-kern_return_t
-diskfs_S_dir_link (struct protid *dircred,
- struct protid *filecred,
- const_string_t name,
- int excl)
+/* One try of dir_link; see diskfs_retry_enospc. */
+static error_t
+try_link (struct protid *dircred,
+ struct protid *filecred,
+ const_string_t name,
+ int excl)
{
struct node *np; /* node being linked */
struct node *tnp; /* node being deleted implicitly */
@@ -150,3 +150,13 @@ diskfs_S_dir_link (struct protid *dircred,
diskfs_journal_end_transaction (txn, !err, diskfs_synchronous);
return err;
}
+
+/* Implement dir_link as described in <hurd/fs.defs>. */
+kern_return_t
+diskfs_S_dir_link (struct protid *dircred,
+ struct protid *filecred,
+ const_string_t name,
+ int excl)
+{
+ return diskfs_retry_enospc (try_link (dircred, filecred, name, excl));
+}
diff --git a/libdiskfs/dir-lookup.c b/libdiskfs/dir-lookup.c
index 60316a3f6..7c41683b9 100644
--- a/libdiskfs/dir-lookup.c
+++ b/libdiskfs/dir-lookup.c
@@ -29,16 +29,16 @@
#include "priv.h"
#include "fs_S.h"
-/* Implement dir_lookup as described in <hurd/fs.defs>. */
-kern_return_t
-diskfs_S_dir_lookup (struct protid *dircred,
- const_string_t filename,
- int flags,
- mode_t mode,
- retry_type *do_retry,
- string_t retry_name,
- mach_port_t *retry_port,
- mach_msg_type_name_t *retry_port_type)
+/* One try of dir_lookup; see diskfs_retry_enospc. */
+static error_t
+try_lookup (struct protid *dircred,
+ const_string_t filename,
+ int flags,
+ mode_t mode,
+ retry_type *do_retry,
+ string_t retry_name,
+ mach_port_t *retry_port,
+ mach_msg_type_name_t *retry_port_type)
{
struct node *dnp;
struct node *np;
@@ -563,3 +563,29 @@ diskfs_S_dir_lookup (struct protid *dircred,
return err;
}
+
+/* Implement dir_lookup as described in <hurd/fs.defs>. */
+kern_return_t
+diskfs_S_dir_lookup (struct protid *dircred,
+ const_string_t filename,
+ int flags,
+ mode_t mode,
+ retry_type *do_retry,
+ string_t retry_name,
+ mach_port_t *retry_port,
+ mach_msg_type_name_t *retry_port_type)
+{
+ /* Only a create can need a block. */
+ if (!(flags & O_CREAT))
+ return try_lookup (dircred, filename, flags, mode, do_retry, retry_name,
+ retry_port, retry_port_type);
+
+ /* try_lookup cuts FILENAME at each slash, and a retry needs it whole:
+ each try gets a copy. */
+ size_t len = strlen (filename) + 1;
+ char *name = alloca (len);
+ return diskfs_retry_enospc ((memcpy (name, filename, len),
+ try_lookup (dircred, name, flags, mode,
+ do_retry, retry_name, retry_port,
+ retry_port_type)));
+}
diff --git a/libdiskfs/dir-mkdir.c b/libdiskfs/dir-mkdir.c
index 55295c6be..c79ad9ca1 100644
--- a/libdiskfs/dir-mkdir.c
+++ b/libdiskfs/dir-mkdir.c
@@ -19,11 +19,11 @@
#include "priv.h"
#include "fs_S.h"
-/* Implement dir_mkdir as found in <hurd/fs.defs>. */
-kern_return_t
-diskfs_S_dir_mkdir (struct protid *dircred,
- const_string_t name,
- mode_t mode)
+/* One try of dir_mkdir; see diskfs_retry_enospc. */
+static error_t
+try_mkdir (struct protid *dircred,
+ const_string_t name,
+ mode_t mode)
{
struct node *dnp;
struct node *np = 0;
@@ -72,3 +72,12 @@ diskfs_S_dir_mkdir (struct protid *dircred,
diskfs_journal_end_transaction (txn, !error, diskfs_synchronous);
return error;
}
+
+/* Implement dir_mkdir as found in <hurd/fs.defs>. */
+kern_return_t
+diskfs_S_dir_mkdir (struct protid *dircred,
+ const_string_t name,
+ mode_t mode)
+{
+ return diskfs_retry_enospc (try_mkdir (dircred, name, mode));
+}
diff --git a/libdiskfs/dir-readdir.c b/libdiskfs/dir-readdir.c
index 3ee141c50..5f657ec19 100644
--- a/libdiskfs/dir-readdir.c
+++ b/libdiskfs/dir-readdir.c
@@ -25,7 +25,7 @@
#include <sys/stat.h>
#include <mach.h>
-#include "diskfs.h"
+#include "priv.h"
#include <hurd/hurd_types.h>
#include "fs_S.h"
@@ -41,27 +41,35 @@ diskfs_S_dir_readdir (struct protid *cred,
{
error_t err;
struct node *np;
+ diskfs_transaction_t *txn;
if (!cred)
return EOPNOTSUPP;
np = cred->po->np;
+ /* Reading the directory updates its access time, which writes the
+ inode. Take the handle before the node lock so that start never
+ waits while holding it. */
+ txn = diskfs_journal_start_transaction ();
pthread_mutex_lock (&np->lock);
if ((cred->po->openstat & O_READ) == 0)
{
pthread_mutex_unlock (&np->lock);
+ diskfs_journal_stop_transaction (txn);
return EBADF;
}
if ((np->dn_stat.st_mode & S_IFMT) != S_IFDIR)
{
pthread_mutex_unlock (&np->lock);
+ diskfs_journal_stop_transaction (txn);
return ENOTDIR;
}
err = diskfs_get_directs (np, entry, nentries, data, datacnt, bufsiz, amt);
*data_dealloc = 1; /* XXX */
pthread_mutex_unlock (&np->lock);
+ diskfs_journal_end_transaction (txn, 1, diskfs_synchronous);
return err;
}
diff --git a/libdiskfs/dir-rename.c b/libdiskfs/dir-rename.c
index 63aff41f1..3e1854473 100644
--- a/libdiskfs/dir-rename.c
+++ b/libdiskfs/dir-rename.c
@@ -27,13 +27,13 @@
directores with this lock */
static pthread_mutex_t renamedirlock = PTHREAD_MUTEX_INITIALIZER;
-/* Implement dir_rename as described in <hurd/fs.defs>. */
-kern_return_t
-diskfs_S_dir_rename (struct protid *fromcred,
- const_string_t fromname,
- struct protid *tocred,
- const_string_t toname,
- int excl)
+/* One try of dir_rename; see diskfs_retry_enospc. */
+static error_t
+try_rename (struct protid *fromcred,
+ const_string_t fromname,
+ struct protid *tocred,
+ const_string_t toname,
+ int excl)
{
struct node *fdp, *tdp, *fnp, *tnp, *tmpnp;
error_t err;
@@ -262,3 +262,15 @@ out:
mach_port_deallocate (mach_task_self (), tocred->pi.port_right);
return err;
}
+
+/* Implement dir_rename as described in <hurd/fs.defs>. */
+kern_return_t
+diskfs_S_dir_rename (struct protid *fromcred,
+ const_string_t fromname,
+ struct protid *tocred,
+ const_string_t toname,
+ int excl)
+{
+ return diskfs_retry_enospc (try_rename (fromcred, fromname, tocred, toname,
+ excl));
+}
diff --git a/libdiskfs/dir-renamed.c b/libdiskfs/dir-renamed.c
index 2a7ff51f6..e74a8df87 100644
--- a/libdiskfs/dir-renamed.c
+++ b/libdiskfs/dir-renamed.c
@@ -292,7 +292,9 @@ diskfs_rename_dir (struct node *fdp, struct node *fnp,
const char *fromname,
if (ds)
diskfs_drop_dirstat (tdp, ds);
- /* FINALIZE TRANSACTION */
- diskfs_journal_end_transaction (txn, !err, diskfs_synchronous);
+ /* The handle is nested in diskfs_S_dir_rename's, whose end commits on
+ a synchronous mount: a commit here would wait for the outer handle
+ to drain. */
+ diskfs_journal_stop_transaction (txn);
return err;
}
diff --git a/libdiskfs/diskfs.h b/libdiskfs/diskfs.h
index 01511c8c5..b3d9cab4b 100644
--- a/libdiskfs/diskfs.h
+++ b/libdiskfs/diskfs.h
@@ -560,8 +560,12 @@ void diskfs_journal_stop_transaction (diskfs_transaction_t
*txn);
operation (e.g., the directory has the O_SYNC flag).
This function consumes TXN. The caller must not use TXN after this call,
- and it MUST NOT call diskfs_journal_stop_transaction on it. */
-void diskfs_journal_commit_transaction (diskfs_transaction_t *txn);
+ and it MUST NOT call diskfs_journal_stop_transaction on it.
+
+ Returns 0 once the transaction is durable, or an error if its commit,
+ or an earlier one, failed (EIO, say); the journal may then refuse every
+ later commit with that error too. */
+error_t diskfs_journal_commit_transaction (diskfs_transaction_t *txn);
/* Flags the transaction TXN to require a synchronous commit upon completion.
This is used by nested filesystem operations that receive a request for
@@ -586,6 +590,15 @@ int diskfs_journal_needs_sync (diskfs_transaction_t *txn);
The default definition does nothing. */
void diskfs_journal_shutdown (void);
+/* Called by an operation that failed with ENOSPC, once it holds no
+ transaction handle and no node lock. Blocks the operation freed, or
+ other operations of its transaction, can be kept from reuse until that
+ transaction commits; a journal may then commit and wait for them.
+ Returns non-zero if the operation should be done again: RETRIES, zero
+ at the start of the operation, counts the tries. The default
+ definition returns 0. */
+int diskfs_journal_should_retry_alloc (int *retries);
+
/* Orphan Inode List hooks.
These are called by libdiskfs when a file is unlinked (nlink drops to
0) but still held open, and when the inode is finally freed.
diff --git a/libdiskfs/file-get-trans.c b/libdiskfs/file-get-trans.c
index 793a82365..67e5ac1e7 100644
--- a/libdiskfs/file-get-trans.c
+++ b/libdiskfs/file-get-trans.c
@@ -24,7 +24,7 @@
#include <errno.h>
#include <assert-backtrace.h>
-#include "diskfs.h"
+#include "priv.h"
#include <mach.h>
#include <hurd/hurd_types.h>
#include <hurd/paths.h>
@@ -38,12 +38,17 @@ diskfs_S_file_get_translator (struct protid *cred,
{
struct node *np;
error_t err = 0;
+ diskfs_transaction_t *txn;
if (!cred)
return EOPNOTSUPP;
np = cred->po->np;
+ /* Reading a symlink that is not stored in the inode updates its access
+ time, which writes the inode. Take the handle before the node lock
+ so that start never waits while holding it. */
+ txn = diskfs_journal_start_transaction ();
pthread_mutex_lock (&np->lock);
if (np->dn_stat.st_mode & S_IPTRANS)
@@ -183,6 +188,7 @@ diskfs_S_file_get_translator (struct protid *cred,
out:
pthread_mutex_unlock (&np->lock);
+ diskfs_journal_end_transaction (txn, 1, diskfs_synchronous);
return err;
}
diff --git a/libdiskfs/file-set-trans.c b/libdiskfs/file-set-trans.c
index 3aa732866..3923a2ab4 100644
--- a/libdiskfs/file-set-trans.c
+++ b/libdiskfs/file-set-trans.c
@@ -23,15 +23,15 @@
#include <hurd/paths.h>
#include <hurd/fsys.h>
-/* Implement file_set_translator as described in <hurd/fs.defs>. */
-kern_return_t
-diskfs_S_file_set_translator (struct protid *cred,
- int passive_flags,
- int active_flags,
- int killtrans_flags,
- const_data_t passive,
- mach_msg_type_number_t passivelen,
- fsys_t active)
+/* One try of file_set_translator; see diskfs_retry_enospc. */
+static error_t
+try_set_translator (struct protid *cred,
+ int passive_flags,
+ int active_flags,
+ int killtrans_flags,
+ const_data_t passive,
+ mach_msg_type_number_t passivelen,
+ fsys_t active)
{
struct node *np;
error_t err;
@@ -249,3 +249,24 @@ out:
return ret_val;
}
+
+/* Implement file_set_translator as described in <hurd/fs.defs>. */
+kern_return_t
+diskfs_S_file_set_translator (struct protid *cred,
+ int passive_flags,
+ int active_flags,
+ int killtrans_flags,
+ const_data_t passive,
+ mach_msg_type_number_t passivelen,
+ fsys_t active)
+{
+ /* An active translator is set first, and a retry would set it again:
+ only a passive one, such as a symlink, is tried again. */
+ if (active_flags & FS_TRANS_SET)
+ return try_set_translator (cred, passive_flags, active_flags,
+ killtrans_flags, passive, passivelen, active);
+ return diskfs_retry_enospc (try_set_translator (cred, passive_flags,
+ active_flags, killtrans_flags,
+ passive, passivelen,
+ active));
+}
diff --git a/libdiskfs/file-sync.c b/libdiskfs/file-sync.c
index ac376702f..b1d4d2143 100644
--- a/libdiskfs/file-sync.c
+++ b/libdiskfs/file-sync.c
@@ -34,9 +34,15 @@ diskfs_S_file_sync (struct protid *cred,
np = cred->po->np;
+ /* Taking the conch back from a client that mapped the shared page
+ can update the node, which writes the inode. Take the handle before
+ the node lock so that start never waits while holding it. It is
+ released before the writeback below; see the comment there. */
+ diskfs_transaction_t *txn = diskfs_journal_start_transaction ();
pthread_mutex_lock (&np->lock);
iohelp_get_conch (&np->conch);
pthread_mutex_unlock (&np->lock);
+ diskfs_journal_stop_transaction (txn);
diskfs_file_update (np, wait);
if (wait)
@@ -50,8 +56,8 @@ diskfs_S_file_sync (struct protid *cred,
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);
+ txn = diskfs_journal_start_transaction ();
+ return diskfs_journal_commit_transaction (txn);
}
return 0;
}
diff --git a/libdiskfs/fsys-getroot.c b/libdiskfs/fsys-getroot.c
index 4debdf8b5..5faa74df0 100644
--- a/libdiskfs/fsys-getroot.c
+++ b/libdiskfs/fsys-getroot.c
@@ -46,6 +46,7 @@ diskfs_S_fsys_getroot (struct diskfs_control *pt,
struct protid *newpi;
struct peropen *newpo;
struct iouser user;
+ diskfs_transaction_t *txn;
struct peropen peropen_context =
{
root_parent: dotdot,
@@ -66,6 +67,13 @@ diskfs_S_fsys_getroot (struct diskfs_control *pt,
#define drop_idvec() idvec_free (user.gids); idvec_free (user.uids)
pthread_rwlock_rdlock (&diskfs_fsys_lock);
+ /* Reading a root symlink that is not stored in the inode updates its
+ access time, and releasing the peropen on an error can drop a node.
+ Both write metadata. Take the handle before the node lock so that
+ start never waits while holding it, but after diskfs_fsys_lock:
+ diskfs_shutdown starts transactions while it holds that lock for
+ writing. */
+ txn = diskfs_journal_start_transaction ();
pthread_mutex_lock (&diskfs_root_node->lock);
/* This code is similar (but not the same as) the code in
@@ -92,6 +100,7 @@ diskfs_S_fsys_getroot (struct diskfs_control *pt,
pthread_mutex_unlock (&diskfs_root_node->lock);
pthread_rwlock_unlock (&diskfs_fsys_lock);
drop_idvec ();
+ diskfs_journal_stop_transaction (txn);
if (!err)
*returned_port_poly = MACH_MSG_TYPE_MOVE_SEND;
return err;
@@ -122,6 +131,7 @@ diskfs_S_fsys_getroot (struct diskfs_control *pt,
if (err)
{
drop_idvec ();
+ diskfs_journal_stop_transaction (txn);
return err;
}
@@ -133,6 +143,7 @@ diskfs_S_fsys_getroot (struct diskfs_control *pt,
strcpy (retryname, pathbuf);
mach_port_deallocate (mach_task_self (), dotdot);
drop_idvec ();
+ diskfs_journal_stop_transaction (txn);
return 0;
}
else
@@ -142,6 +153,7 @@ diskfs_S_fsys_getroot (struct diskfs_control *pt,
*returned_port_poly = MACH_MSG_TYPE_MOVE_SEND;
strcpy (retryname, pathbuf);
drop_idvec ();
+ diskfs_journal_stop_transaction (txn);
return 0;
}
}
@@ -173,6 +185,7 @@ diskfs_S_fsys_getroot (struct diskfs_control *pt,
pthread_mutex_unlock (&diskfs_root_node->lock);
pthread_rwlock_unlock (&diskfs_fsys_lock);
drop_idvec ();
+ diskfs_journal_stop_transaction (txn);
return err;
}
@@ -207,6 +220,7 @@ diskfs_S_fsys_getroot (struct diskfs_control *pt,
pthread_rwlock_unlock (&diskfs_fsys_lock);
drop_idvec ();
+ diskfs_journal_stop_transaction (txn);
return err;
}
diff --git a/libdiskfs/init-startup.c b/libdiskfs/init-startup.c
index 736cdb502..2685a337a 100644
--- a/libdiskfs/init-startup.c
+++ b/libdiskfs/init-startup.c
@@ -82,11 +82,16 @@ diskfs_startup_diskfs (mach_port_t fs_bootstrap, int flags)
struct node *np, *old;
struct protid *rootpi;
struct peropen *rootpo;
+ diskfs_transaction_t *txn;
/* Skip leading slashes. */
while (*_diskfs_chroot_directory == '/')
++_diskfs_chroot_directory;
+ /* The journal is running already, and with diskfs_synchronous the
+ lookup writes the directory's access time. Take the handle before
+ the node lock so that start never waits while holding it. */
+ txn = diskfs_journal_start_transaction ();
pthread_mutex_lock (&diskfs_root_node->lock);
/* Create a protid we can use in diskfs_lookup. */
@@ -124,6 +129,7 @@ diskfs_startup_diskfs (mach_port_t fs_bootstrap, int flags)
if (np != old)
pthread_mutex_unlock (&np->lock);
diskfs_nrele (old);
+ diskfs_journal_stop_transaction (txn);
}
if (fs_bootstrap != MACH_PORT_NULL)
diff --git a/libdiskfs/io-get-conch.c b/libdiskfs/io-get-conch.c
index fa63a3983..fa6fdc402 100644
--- a/libdiskfs/io-get-conch.c
+++ b/libdiskfs/io-get-conch.c
@@ -23,22 +23,29 @@ kern_return_t
diskfs_S_io_get_conch (struct protid *cred)
{
struct node *np;
+ diskfs_transaction_t *txn;
if (!cred)
return EOPNOTSUPP;
np = cred->po->np;
+ /* Taking the conch back from a client that mapped the shared page
+ can update the node, which writes the inode. Take the handle before
+ the node lock so that start never waits while holding it. */
+ txn = diskfs_journal_start_transaction ();
pthread_mutex_lock (&np->lock);
if (!cred->mapped)
{
pthread_mutex_unlock (&np->lock);
+ diskfs_journal_stop_transaction (txn);
return EINVAL;
}
iohelp_handle_io_get_conch (&np->conch, cred, cred->mapped);
pthread_mutex_unlock (&np->lock);
+ diskfs_journal_end_transaction (txn, 1, diskfs_synchronous);
return 0;
}
diff --git a/libdiskfs/io-modes-off.c b/libdiskfs/io-modes-off.c
index b1e8f978a..e784f6562 100644
--- a/libdiskfs/io-modes-off.c
+++ b/libdiskfs/io-modes-off.c
@@ -24,12 +24,19 @@ kern_return_t
diskfs_S_io_clear_some_openmodes (struct protid *cred,
int offbits)
{
+ diskfs_transaction_t *txn;
+
if (!cred)
return EOPNOTSUPP;
+ /* Taking the conch back from a client that mapped the shared page
+ can update the node, which writes the inode. Take the handle before
+ the node lock so that start never waits while holding it. */
+ txn = diskfs_journal_start_transaction ();
pthread_mutex_lock (&cred->po->np->lock);
iohelp_get_conch (&cred->po->np->conch);
cred->po->openstat &= ~(offbits & HONORED_STATE_MODES);
pthread_mutex_unlock (&cred->po->np->lock);
+ diskfs_journal_end_transaction (txn, 1, diskfs_synchronous);
return 0;
}
diff --git a/libdiskfs/io-modes-on.c b/libdiskfs/io-modes-on.c
index c6ac3b121..02796a21a 100644
--- a/libdiskfs/io-modes-on.c
+++ b/libdiskfs/io-modes-on.c
@@ -24,12 +24,19 @@ kern_return_t
diskfs_S_io_set_some_openmodes (struct protid *cred,
int newbits)
{
+ diskfs_transaction_t *txn;
+
if (!cred)
return EOPNOTSUPP;
+ /* Taking the conch back from a client that mapped the shared page
+ can update the node, which writes the inode. Take the handle before
+ the node lock so that start never waits while holding it. */
+ txn = diskfs_journal_start_transaction ();
pthread_mutex_lock (&cred->po->np->lock);
iohelp_get_conch (&cred->po->np->conch);
cred->po->openstat |= (newbits & HONORED_STATE_MODES);
pthread_mutex_unlock (&cred->po->np->lock);
+ diskfs_journal_end_transaction (txn, 1, diskfs_synchronous);
return 0;
}
diff --git a/libdiskfs/io-modes-set.c b/libdiskfs/io-modes-set.c
index a8e2d7faa..9044bfe26 100644
--- a/libdiskfs/io-modes-set.c
+++ b/libdiskfs/io-modes-set.c
@@ -24,13 +24,20 @@ kern_return_t
diskfs_S_io_set_all_openmodes (struct protid *cred,
int newbits)
{
+ diskfs_transaction_t *txn;
+
if (!cred)
return EOPNOTSUPP;
+ /* Taking the conch back from a client that mapped the shared page
+ can update the node, which writes the inode. Take the handle before
+ the node lock so that start never waits while holding it. */
+ txn = diskfs_journal_start_transaction ();
pthread_mutex_lock (&cred->po->np->lock);
iohelp_get_conch (&cred->po->np->conch);
cred->po->openstat &= ~HONORED_STATE_MODES;
cred->po->openstat |= (newbits & HONORED_STATE_MODES);
pthread_mutex_unlock (&cred->po->np->lock);
+ diskfs_journal_end_transaction (txn, 1, diskfs_synchronous);
return 0;
}
diff --git a/libdiskfs/io-read.c b/libdiskfs/io-read.c
index 11d0b56f5..bd5e8576e 100644
--- a/libdiskfs/io-read.c
+++ b/libdiskfs/io-read.c
@@ -33,6 +33,7 @@ diskfs_S_io_read (struct protid *cred,
off_t off = offset;
char *buf;
int ourbuf = 0;
+ int atime_deferred = 0;
diskfs_transaction_t *txn;
if (!cred)
@@ -80,6 +81,19 @@ diskfs_S_io_read (struct protid *cred,
*datalen = maxread;
+ /* The handle is only for the atime update after the copy. A commit
+ waits for every open handle, so holding it through the copy, which
+ can wait for the disk, would hold every commit, and every RPC waiting
+ for the next transaction, behind it. Unless the filesystem is
+ synchronous, release it now and leave the atime to the next write of
+ the inode. */
+ if (txn && !diskfs_synchronous)
+ {
+ diskfs_journal_stop_transaction (txn);
+ txn = NULL;
+ atime_deferred = 1;
+ }
+
if (maxread == 0)
err = 0;
else if (S_ISLNK (np->dn_stat.st_mode))
@@ -107,7 +121,8 @@ diskfs_S_io_read (struct protid *cred,
err = _diskfs_rdwr_internal (np, buf, off, datalen, 0,
cred->po->openstat & O_NOATIME);
- diskfs_node_update (np, diskfs_synchronous); /* atime! */
+ if (!atime_deferred)
+ diskfs_node_update (np, diskfs_synchronous); /* atime! */
if (offset == -1 && !err)
cred->po->filepointer += *datalen;
diff --git a/libdiskfs/io-readable.c b/libdiskfs/io-readable.c
index 03218bf34..245b21360 100644
--- a/libdiskfs/io-readable.c
+++ b/libdiskfs/io-readable.c
@@ -25,6 +25,7 @@ diskfs_S_io_readable (struct protid *cred,
vm_size_t *amount)
{
struct node *np;
+ diskfs_transaction_t *txn;
if (!cred)
return EOPNOTSUPP;
@@ -34,6 +35,10 @@ diskfs_S_io_readable (struct protid *cred,
np = cred->po->np;
+ /* Taking the conch back from a client that mapped the shared page
+ can update the node, which writes the inode. Take the handle before
+ the node lock so that start never waits while holding it. */
+ txn = diskfs_journal_start_transaction ();
pthread_mutex_lock (&np->lock);
iohelp_get_conch (&np->conch);
if (np->dn_stat.st_size > cred->po->filepointer)
@@ -42,5 +47,6 @@ diskfs_S_io_readable (struct protid *cred,
*amount = 0;
pthread_mutex_unlock (&np->lock);
+ diskfs_journal_end_transaction (txn, 1, diskfs_synchronous);
return 0;
}
diff --git a/libdiskfs/io-rel-conch.c b/libdiskfs/io-rel-conch.c
index a42d1ba5b..69cfa65c3 100644
--- a/libdiskfs/io-rel-conch.c
+++ b/libdiskfs/io-rel-conch.c
@@ -23,15 +23,21 @@ kern_return_t
diskfs_S_io_release_conch (struct protid *cred)
{
struct node *np;
+ diskfs_transaction_t *txn;
if (!cred)
return EOPNOTSUPP;
np = cred->po->np;
+ /* Taking the conch back from a client that mapped the shared page
+ can update the node, which writes the inode. Take the handle before
+ the node lock so that start never waits while holding it. */
+ txn = diskfs_journal_start_transaction ();
pthread_mutex_lock (&np->lock);
if (!cred->mapped)
{
pthread_mutex_unlock (&np->lock);
+ diskfs_journal_stop_transaction (txn);
return EINVAL;
}
@@ -40,5 +46,6 @@ diskfs_S_io_release_conch (struct protid *cred)
iohelp_handle_io_release_conch (&np->conch, cred);
pthread_mutex_unlock (&np->lock);
+ diskfs_journal_end_transaction (txn, 1, diskfs_synchronous);
return 0;
}
diff --git a/libdiskfs/io-seek.c b/libdiskfs/io-seek.c
index c72462975..0307334bc 100644
--- a/libdiskfs/io-seek.c
+++ b/libdiskfs/io-seek.c
@@ -28,12 +28,17 @@ diskfs_S_io_seek (struct protid *cred,
{
error_t err = 0;
struct node *np;
+ diskfs_transaction_t *txn;
if (!cred)
return EOPNOTSUPP;
np = cred->po->np;
+ /* Taking the conch back from a client that mapped the shared page
+ can update the node, which writes the inode. Take the handle before
+ the node lock so that start never waits while holding it. */
+ txn = diskfs_journal_start_transaction ();
pthread_mutex_lock (&np->lock);
iohelp_get_conch (&np->conch);
@@ -66,5 +71,6 @@ diskfs_S_io_seek (struct protid *cred,
}
pthread_mutex_unlock (&np->lock);
+ diskfs_journal_end_transaction (txn, 1, diskfs_synchronous);
return err;
}
diff --git a/libdiskfs/io-write.c b/libdiskfs/io-write.c
index c07b8c790..f8e00124a 100644
--- a/libdiskfs/io-write.c
+++ b/libdiskfs/io-write.c
@@ -34,6 +34,7 @@ diskfs_S_io_write (struct protid *cred,
mach_msg_type_number_t nwritten;
int should_sync;
diskfs_transaction_t *txn;
+ int retries = 0;
if (!cred)
return EOPNOTSUPP;
@@ -45,6 +46,7 @@ diskfs_S_io_write (struct protid *cred,
if (!(cred->po->openstat & O_WRITE))
return EBADF;
should_sync = (cred->po->openstat & O_FSYNC) || diskfs_synchronous;
+ retry:
txn = diskfs_journal_start_transaction ();
pthread_mutex_lock (&np->lock);
@@ -81,6 +83,18 @@ diskfs_S_io_write (struct protid *cred,
diskfs_node_update (np, should_sync);
}
+ /* The metadata is done; the copy below only faults in pages, whose
+ blocks the pager allocates under handles of its own. A commit waits
+ for every open handle, so holding this one through the copy would
+ hold every commit, and every RPC waiting for the next transaction,
+ behind it. Release it now unless this write must commit: a commit
+ cannot wait under the node lock. */
+ if (txn && !should_sync && !diskfs_journal_needs_sync (txn))
+ {
+ diskfs_journal_stop_transaction (txn);
+ txn = NULL;
+ }
+
nwritten = datalen;
err = _diskfs_rdwr_internal (np, (char *) data, off, &nwritten, 1, 0);
if (!err)
@@ -97,5 +111,10 @@ diskfs_S_io_write (struct protid *cred,
out:
pthread_mutex_unlock (&np->lock);
diskfs_journal_end_transaction (txn, !err, should_sync);
+ /* Freed blocks can come back once the journal commits. The write is
+ done again from the start, at the same offset: OFF is no longer -1,
+ so an append goes where it was going to go. */
+ if (err == ENOSPC && diskfs_journal_should_retry_alloc (&retries))
+ goto retry;
return err;
}
diff --git a/libdiskfs/journal.c b/libdiskfs/journal.c
index eb93b8f69..df83b4d0f 100644
--- a/libdiskfs/journal.c
+++ b/libdiskfs/journal.c
@@ -1,15 +1,17 @@
/* Default version of Journal in libdiskfs.
- It implements default implementations of 6 functions:
+ It implements default implementations of 7 functions:
- diskfs_journal_start_transaction
- diskfs_journal_stop_transaction
- diskfs_journal_commit_transaction
- diskfs_journal_needs_sync
- diskfs_journal_set_sync
- diskfs_journal_shutdown
+ - diskfs_journal_should_retry_alloc
diskfs_journal_start_transaction returns NULL,
- diskfs_journal_needs_sync returns 0.
- All others are empty and do nothing.
+ diskfs_journal_commit_transaction, diskfs_journal_needs_sync and
+ diskfs_journal_should_retry_alloc return 0. All others are empty and
+ do nothing.
Written by Milos Nikic.
Copyright (C) 2026 Free Software Foundation, Inc.
@@ -49,10 +51,11 @@ diskfs_journal_stop_transaction (diskfs_transaction_t *tx)
/* Do nothing */
}
-void __attribute__((weak))
+error_t __attribute__((weak))
diskfs_journal_commit_transaction (diskfs_transaction_t *tx)
{
/* Do nothing */
+ return 0;
}
void __attribute__((weak))
@@ -73,3 +76,10 @@ diskfs_journal_shutdown (void)
{
/* Do nothing */
}
+
+int __attribute__((weak))
+diskfs_journal_should_retry_alloc (int *retries)
+{
+ /* Without a journal, no freed block waits for a commit. */
+ return 0;
+}
diff --git a/libdiskfs/priv.h b/libdiskfs/priv.h
index 071f707ed..b8bbbe32e 100644
--- a/libdiskfs/priv.h
+++ b/libdiskfs/priv.h
@@ -140,6 +140,21 @@ diskfs_journal_end_transaction (diskfs_transaction_t *txn,
diskfs_journal_stop_transaction (txn);
}
+/* Evaluate CALL, an operation that ends its transaction handle and
+ releases its node locks before it returns, and again while it fails
+ with ENOSPC and the journal says that waiting for a commit can give
+ blocks back (diskfs_journal_should_retry_alloc). CALL must leave
+ nothing changed when it fails with ENOSPC. */
+#define diskfs_retry_enospc(call) \
+({ \
+ int retries_ = 0; \
+ error_t err_;
\
+ do \
+ err_ = (call); \
+ while (err_ == ENOSPC && diskfs_journal_should_retry_alloc (&retries_)); \
+ err_;
\
+})
+
/* This macro locks the node associated with PROTID, and then
evaluates the expression OPERATION; then it syncs the inode
(without waiting) and unlocks everything, and then returns
--
2.56.0