From: Ali Ahmet Memis <ali@iusegentoo.com>
To: Alexander Viro <viro@zeniv.linux.org.uk>,
Christian Brauner <brauner@kernel.org>
Cc: Jan Kara <jack@suse.cz>,
linux-fsdevel@vger.kernel.org, linux-kernel@vger.kernel.org
Subject: [PATCH 03/10] ufs: free UFS2 external attribute blocks on inode deletion
Date: Sat, 1 Aug 2026 22:55:23 +0000 [thread overview]
Message-ID: <20260801225530.148386-4-ali@iusegentoo.com> (raw)
In-Reply-To: <20260801225530.148386-1-ali@iusegentoo.com>
A UFS2 inode can own up to UFS_NXADDR blocks holding extended
attributes, described by di_extsize and di_extb[]. They sit outside the
block tree of the file, but ffs_alloc() counts them in di_blocks like
any other allocation.
Linux never reads those two fields. ufs_truncate_blocks() only walks
di_db[] and di_ib[], so on unlink the extattr blocks stay marked in use
while ufs_update_inode() clears the whole on-disk inode, di_extb[]
included. The only record of where the blocks were is gone, and nothing
can ever free them. Deleting FreeBSD files that carry an extended
attribute, an ACL or a MAC label therefore leaks disk space until fsck
reclaims it.
Keep di_extsize and di_extb[] in the in-core inode and release them from
ufs_evict_inode(), before the on-disk inode is cleared. The last block
of the area can be a fragment, so size it the way FreeBSD's sblksize()
does rather than assuming a full block.
An external attribute size larger than the area can describe means the
inode is corrupted and the block numbers cannot be trusted. Warn and
leave the blocks alone in that case; leaking them is better than freeing
blocks that may belong to something else.
Signed-off-by: Ali Ahmet Memis <ali@iusegentoo.com>
---
fs/ufs/ialloc.c | 2 ++
fs/ufs/inode.c | 55 +++++++++++++++++++++++++++++++++++++++++++++++++
fs/ufs/ufs.h | 2 ++
3 files changed, 59 insertions(+)
diff --git a/fs/ufs/ialloc.c b/fs/ufs/ialloc.c
index 8e51f4630d18..4c3c76c4e7ff 100644
--- a/fs/ufs/ialloc.c
+++ b/fs/ufs/ialloc.c
@@ -294,6 +294,8 @@ struct inode *ufs_new_inode(struct inode *dir, umode_t mode)
inode->i_generation = 0;
simple_inode_init_ts(inode);
ufsi->i_flags = UFS_I(dir)->i_flags;
+ ufsi->i_extsize = 0;
+ memset(ufsi->i_extb, 0, sizeof(ufsi->i_extb));
ufsi->i_lastfrag = 0;
ufsi->i_shadow = 0;
ufsi->i_osync = 0;
diff --git a/fs/ufs/inode.c b/fs/ufs/inode.c
index 802bc6e01db2..7079285bac2a 100644
--- a/fs/ufs/inode.c
+++ b/fs/ufs/inode.c
@@ -585,6 +585,8 @@ static int ufs1_read_inode(struct inode *inode, struct ufs_inode *ufs_inode)
inode->i_blocks = fs32_to_cpu(sb, ufs_inode->ui_blocks);
inode->i_generation = fs32_to_cpu(sb, ufs_inode->ui_gen);
ufsi->i_flags = fs32_to_cpu(sb, ufs_inode->ui_flags);
+ ufsi->i_extsize = 0;
+ memset(ufsi->i_extb, 0, sizeof(ufsi->i_extb));
ufsi->i_shadow = fs32_to_cpu(sb, ufs_inode->ui_u3.ui_sun.ui_shadow);
ufsi->i_oeftflag = fs32_to_cpu(sb, ufs_inode->ui_u3.ui_sun.ui_oeftflag);
@@ -605,6 +607,7 @@ static int ufs2_read_inode(struct inode *inode, struct ufs2_inode *ufs2_inode)
struct ufs_inode_info *ufsi = UFS_I(inode);
struct super_block *sb = inode->i_sb;
umode_t mode;
+ unsigned int i;
UFSD("Reading ufs2 inode, ino %llu\n", inode->i_ino);
/*
@@ -631,6 +634,9 @@ static int ufs2_read_inode(struct inode *inode, struct ufs2_inode *ufs2_inode)
inode->i_blocks = fs64_to_cpu(sb, ufs2_inode->ui_blocks);
inode->i_generation = fs32_to_cpu(sb, ufs2_inode->ui_gen);
ufsi->i_flags = fs32_to_cpu(sb, ufs2_inode->ui_flags);
+ ufsi->i_extsize = fs32_to_cpu(sb, ufs2_inode->ui_extsize);
+ for (i = 0; i < UFS_NXADDR; i++)
+ ufsi->i_extb[i] = fs64_to_cpu(sb, ufs2_inode->ui_extb[i]);
/*
ufsi->i_shadow = fs32_to_cpu(sb, ufs_inode->ui_u3.ui_sun.ui_shadow);
ufsi->i_oeftflag = fs32_to_cpu(sb, ufs_inode->ui_u3.ui_sun.ui_oeftflag);
@@ -846,6 +852,54 @@ int ufs_sync_inode (struct inode *inode)
return ufs_update_inode (inode, 1);
}
+/*
+ * UFS2 keeps extended attributes in up to UFS_NXADDR blocks of their own.
+ * They live outside the block tree of the file but are counted in di_blocks.
+ * Linux does not implement extended attributes, but it still has to release
+ * those blocks when the inode goes away: ufs_update_inode() clears the whole
+ * on-disk inode, taking di_extb[] with it, so nothing would ever free them.
+ */
+static void ufs_free_ext_blocks(struct inode *inode)
+{
+ struct super_block *sb = inode->i_sb;
+ struct ufs_sb_private_info *uspi = UFS_SB(sb)->s_uspi;
+ struct ufs_inode_info *ufsi = UFS_I(inode);
+ u64 size = ufsi->i_extsize;
+ unsigned int i;
+
+ if ((UFS_SB(sb)->s_flags & UFS_TYPE_MASK) != UFS_TYPE_UFS2 || !size)
+ return;
+
+ if (size > ((u64)UFS_NXADDR << uspi->s_bshift)) {
+ // leaking is better than freeing something we cannot locate
+ ufs_warning(sb, __func__,
+ "inode %llu: bad external attribute size %llu\n",
+ inode->i_ino, size);
+ return;
+ }
+
+ for (i = 0; i < UFS_NXADDR; i++) {
+ u64 off = (u64)i << uspi->s_bshift;
+ u64 block = ufsi->i_extb[i];
+ unsigned int frags;
+
+ if (!block || size <= off)
+ continue;
+
+ if (size - off >= uspi->s_bsize)
+ frags = uspi->s_fpb;
+ else
+ frags = (size - off + uspi->s_fsize - 1) >> uspi->s_fshift;
+
+ ufsi->i_extb[i] = 0;
+ if (frags == uspi->s_fpb)
+ ufs_free_blocks(inode, block, frags);
+ else
+ ufs_free_fragments(inode, block, frags);
+ }
+ ufsi->i_extsize = 0;
+}
+
void ufs_evict_inode(struct inode * inode)
{
int want_delete = 0;
@@ -864,6 +918,7 @@ void ufs_evict_inode(struct inode * inode)
(S_ISREG(inode->i_mode) || S_ISDIR(inode->i_mode) ||
S_ISLNK(inode->i_mode)))
ufs_truncate_blocks(inode);
+ ufs_free_ext_blocks(inode);
ufs_update_inode(inode, inode_needs_sync(inode));
}
diff --git a/fs/ufs/ufs.h b/fs/ufs/ufs.h
index 788e025056b2..6cac5f8222eb 100644
--- a/fs/ufs/ufs.h
+++ b/fs/ufs/ufs.h
@@ -40,6 +40,8 @@ struct ufs_inode_info {
__fs64 u2_i_data[15];
} i_u1;
__u32 i_flags;
+ __u32 i_extsize; /* UFS2 external attribute area size */
+ __u64 i_extb[UFS_NXADDR]; /* UFS2 external attribute blocks */
__u32 i_shadow;
__u32 i_unused1;
__u32 i_unused2;
--
2.55.0
next prev parent reply other threads:[~2026-08-01 22:56 UTC|newest]
Thread overview: 11+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-08-01 22:55 [PATCH 00/10] ufs: correctness fixes for valid on-disk filesystems Ali Ahmet Memis
2026-08-01 22:55 ` [PATCH 01/10] ufs: name the modern UFS2 superblock fields Ali Ahmet Memis
2026-08-01 22:55 ` [PATCH 02/10] ufs: use i_size to detect fast symlinks Ali Ahmet Memis
2026-08-01 22:55 ` Ali Ahmet Memis [this message]
2026-08-01 22:55 ` [PATCH 04/10] ufs: do not inherit file flags from the parent directory Ali Ahmet Memis
2026-08-01 22:55 ` [PATCH 05/10] ufs: honour on-disk immutable and append-only flags Ali Ahmet Memis
2026-08-01 22:55 ` [PATCH 06/10] ufs: fix fragment relocation offsets within a folio Ali Ahmet Memis
2026-08-01 22:55 ` [PATCH 07/10] ufs: revalidate filesystem state before remounting read-write Ali Ahmet Memis
2026-08-01 22:55 ` [PATCH 08/10] ufs: refuse read-write mount when fsck or journal replay is needed Ali Ahmet Memis
2026-08-01 22:55 ` [PATCH 09/10] ufs: refuse read-write mount of filesystems with active snapshots Ali Ahmet Memis
2026-08-01 22:55 ` [PATCH 10/10] ufs: refuse read-write mount of check-hashed filesystems Ali Ahmet Memis
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260801225530.148386-4-ali@iusegentoo.com \
--to=ali@iusegentoo.com \
--cc=brauner@kernel.org \
--cc=jack@suse.cz \
--cc=linux-fsdevel@vger.kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=viro@zeniv.linux.org.uk \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®