diff options
| author | Jeff Layton <jlayton@kernel.org> | 2026-08-25 12:04:19 -0400 |
|---|---|---|
| committer | David Sterba <dsterba@suse.com> | 2026-09-07 14:36:38 +0200 |
| commit | 05203c667a1a6d42ef5fa9a3f428519efe8bcbc8 (patch) | |
| tree | e5e987f289a9fa0a295d09e5bec18dca0e5fc065 | |
| parent | b3e7a7a3d838f6b1001f815cdc0447bbdf37825d (diff) | |
| download | linux-next-05203c667a1a6d42ef5fa9a3f428519efe8bcbc8.tar.gz linux-next-05203c667a1a6d42ef5fa9a3f428519efe8bcbc8.zip | |
btrfs: handle ENOMEM from btrfs_insert_dir_item() without aborting
Now that btrfs_insert_dir_item() returns -ENOMEM before modifying the
btree (thanks to delayed dir index pre-allocation), callers can handle
ENOMEM gracefully instead of aborting the transaction.
- btrfs_add_link(): add -ENOMEM to the recoverable errors alongside
-EEXIST and -EOVERFLOW.
- btrfs_create_new_inode(): on -ENOMEM from btrfs_add_link(), orphan the
newly-created inode instead of aborting. The inode item was already
written with nlink 1, and discard_new_inode() marks it bad so eviction
won't delete it. So clear_nlink() alone is not enough: persist nlink 0
via btrfs_update_inode(), otherwise orphan cleanup would see nlink > 0,
drop the orphan item, and leak the inode. Fall back to aborting only if
that update also fails.
This turns a filesystem-killing abort into a graceful -ENOMEM return for
create(), mkdir(), mknod(), symlink(), and link() under memory pressure.
Assisted-by: LLM
Signed-off-by: Jeff Layton <jlayton@kernel.org>
Reviewed-by: David Sterba <dsterba@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
| -rw-r--r-- | fs/btrfs/inode.c | 24 |
1 files changed, 22 insertions, 2 deletions
diff --git a/fs/btrfs/inode.c b/fs/btrfs/inode.c index babc291751e9..67edf6618bda 100644 --- a/fs/btrfs/inode.c +++ b/fs/btrfs/inode.c @@ -6828,7 +6828,27 @@ int btrfs_create_new_inode(struct btrfs_trans_handle *trans, } else { ret = btrfs_add_link(trans, BTRFS_I(dir), BTRFS_I(inode), name, false, BTRFS_I(inode)->dir_index); - if (unlikely(ret)) { + if (ret == -ENOMEM) { + /* + * Orphan the new inode instead of aborting. The inode + * item was already written with nlink 1, and discard's + * eviction won't delete a bad inode, so nlink 0 must be + * persisted here or orphan cleanup would see nlink > 0, + * drop the orphan item, and leak the inode. + */ + clear_nlink(inode); + /* btrfs_orphan_add() aborts the transaction on failure. */ + ret = btrfs_orphan_add(trans, BTRFS_I(inode)); + if (ret) + goto discard; + ret = btrfs_update_inode(trans, BTRFS_I(inode)); + if (ret) { + btrfs_abort_transaction(trans, ret); + goto discard; + } + ret = -ENOMEM; + goto discard; + } else if (unlikely(ret)) { btrfs_abort_transaction(trans, ret); goto discard; } @@ -6890,7 +6910,7 @@ int btrfs_add_link(struct btrfs_trans_handle *trans, ret = btrfs_insert_dir_item(trans, name, parent_inode, &key, btrfs_inode_type(inode), index, NULL); - if (ret == -EEXIST || ret == -EOVERFLOW) + if (ret == -EEXIST || ret == -EOVERFLOW || ret == -ENOMEM) goto fail_dir_item; else if (unlikely(ret)) { btrfs_abort_transaction(trans, ret); |
