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 02/10] ufs: use i_size to detect fast symlinks
Date: Sat, 1 Aug 2026 22:55:22 +0000 [thread overview]
Message-ID: <20260801225530.148386-3-ali@iusegentoo.com> (raw)
In-Reply-To: <20260801225530.148386-1-ali@iusegentoo.com>
Whether a symlink stores its target inline is decided by testing
i_blocks == 0. The on-disk criterion is i_size < fs_maxsymlinklen;
FreeBSD's ufs_readlink() and ufs_symlink() both use it, and our own
ufs_symlink() already checks s_maxsymlinklen on the write side.
The two disagree on UFS2 inodes with extended attributes. ffs_alloc()
adds every allocation to di_blocks, including the IO_EXT path, so a
symlink carrying an extended attribute, an ACL or a MAC label has
di_blocks != 0 while its target is still stored inline.
Linux then takes it for a slow symlink. readlink() reads the target
through ufs_getfrag_block(), treating the ASCII bytes of the target as
block pointers. On a read-write mount unlinking it reaches
ufs_truncate_blocks() from ufs_evict_inode(), which hands the same bytes
to ufs_free_fragments() as block numbers. Values built from printable
characters fall outside the device, so this ends in ufs_panic() with the
inode already modified.
Add ufs_is_fast_symlink() and use it in the read, write and evict paths.
In ufs_evict_inode() it has to run before i_size is cleared.
Flavours that leave s_maxsymlinklen at 0 keep the old i_blocks
heuristic, so only 44BSD and UFS2 change behaviour.
Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2")
Signed-off-by: Ali Ahmet Memis <ali@iusegentoo.com>
---
fs/ufs/inode.c | 54 +++++++++++++++++++++++++++++++-------------------
1 file changed, 34 insertions(+), 20 deletions(-)
diff --git a/fs/ufs/inode.c b/fs/ufs/inode.c
index 440d014cc5ed..802bc6e01db2 100644
--- a/fs/ufs/inode.c
+++ b/fs/ufs/inode.c
@@ -517,6 +517,17 @@ const struct address_space_operations ufs_aops = {
.bmap = ufs_bmap
};
+// on-disk criterion is i_size < fs_maxsymlinklen, not i_blocks
+static bool ufs_is_fast_symlink(struct inode *inode)
+{
+ struct ufs_sb_private_info *uspi = UFS_SB(inode->i_sb)->s_uspi;
+
+ if (uspi->s_maxsymlinklen)
+ return inode->i_size < uspi->s_maxsymlinklen;
+
+ return inode->i_blocks == 0;
+}
+
static void ufs_set_inode_ops(struct inode *inode)
{
if (S_ISREG(inode->i_mode)) {
@@ -528,7 +539,7 @@ static void ufs_set_inode_ops(struct inode *inode)
inode->i_fop = &ufs_dir_operations;
inode->i_mapping->a_ops = &ufs_aops;
} else if (S_ISLNK(inode->i_mode)) {
- if (!inode->i_blocks) {
+ if (ufs_is_fast_symlink(inode)) {
inode->i_link = (char *)UFS_I(inode)->i_u1.i_symlink;
inode->i_op = &simple_symlink_inode_operations;
} else {
@@ -578,13 +589,13 @@ static int ufs1_read_inode(struct inode *inode, struct ufs_inode *ufs_inode)
ufsi->i_oeftflag = fs32_to_cpu(sb, ufs_inode->ui_u3.ui_sun.ui_oeftflag);
- if (S_ISCHR(mode) || S_ISBLK(mode) || inode->i_blocks) {
- memcpy(ufsi->i_u1.i_data, &ufs_inode->ui_u2.ui_addr,
- sizeof(ufs_inode->ui_u2.ui_addr));
- } else {
+ if (S_ISLNK(mode) && ufs_is_fast_symlink(inode)) {
memcpy(ufsi->i_u1.i_symlink, ufs_inode->ui_u2.ui_symlink,
sizeof(ufs_inode->ui_u2.ui_symlink) - 1);
ufsi->i_u1.i_symlink[sizeof(ufs_inode->ui_u2.ui_symlink) - 1] = 0;
+ } else {
+ memcpy(ufsi->i_u1.i_data, &ufs_inode->ui_u2.ui_addr,
+ sizeof(ufs_inode->ui_u2.ui_addr));
}
return 0;
}
@@ -625,13 +636,13 @@ static int ufs2_read_inode(struct inode *inode, struct ufs2_inode *ufs2_inode)
ufsi->i_oeftflag = fs32_to_cpu(sb, ufs_inode->ui_u3.ui_sun.ui_oeftflag);
*/
- if (S_ISCHR(mode) || S_ISBLK(mode) || inode->i_blocks) {
- memcpy(ufsi->i_u1.u2_i_data, &ufs2_inode->ui_u2.ui_addr,
- sizeof(ufs2_inode->ui_u2.ui_addr));
- } else {
+ if (S_ISLNK(mode) && ufs_is_fast_symlink(inode)) {
memcpy(ufsi->i_u1.i_symlink, ufs2_inode->ui_u2.ui_symlink,
sizeof(ufs2_inode->ui_u2.ui_symlink) - 1);
ufsi->i_u1.i_symlink[sizeof(ufs2_inode->ui_u2.ui_symlink) - 1] = 0;
+ } else {
+ memcpy(ufsi->i_u1.u2_i_data, &ufs2_inode->ui_u2.ui_addr,
+ sizeof(ufs2_inode->ui_u2.ui_addr));
}
return 0;
}
@@ -731,13 +742,12 @@ static void ufs1_update_inode(struct inode *inode, struct ufs_inode *ufs_inode)
if (S_ISCHR(inode->i_mode) || S_ISBLK(inode->i_mode)) {
/* ufs_inode->ui_u2.ui_addr.ui_db[0] = cpu_to_fs32(sb, inode->i_rdev); */
ufs_inode->ui_u2.ui_addr.ui_db[0] = ufsi->i_u1.i_data[0];
- } else if (inode->i_blocks) {
- memcpy(&ufs_inode->ui_u2.ui_addr, ufsi->i_u1.i_data,
- sizeof(ufs_inode->ui_u2.ui_addr));
- }
- else {
+ } else if (S_ISLNK(inode->i_mode) && ufs_is_fast_symlink(inode)) {
memcpy(&ufs_inode->ui_u2.ui_symlink, ufsi->i_u1.i_symlink,
sizeof(ufs_inode->ui_u2.ui_symlink));
+ } else {
+ memcpy(&ufs_inode->ui_u2.ui_addr, ufsi->i_u1.i_data,
+ sizeof(ufs_inode->ui_u2.ui_addr));
}
if (!inode->i_nlink)
@@ -774,13 +784,13 @@ static void ufs2_update_inode(struct inode *inode, struct ufs2_inode *ufs_inode)
if (S_ISCHR(inode->i_mode) || S_ISBLK(inode->i_mode)) {
/* ufs_inode->ui_u2.ui_addr.ui_db[0] = cpu_to_fs32(sb, inode->i_rdev); */
ufs_inode->ui_u2.ui_addr.ui_db[0] = ufsi->i_u1.u2_i_data[0];
- } else if (inode->i_blocks) {
- memcpy(&ufs_inode->ui_u2.ui_addr, ufsi->i_u1.u2_i_data,
- sizeof(ufs_inode->ui_u2.ui_addr));
- } else {
+ } else if (S_ISLNK(inode->i_mode) && ufs_is_fast_symlink(inode)) {
memcpy(&ufs_inode->ui_u2.ui_symlink, ufsi->i_u1.i_symlink,
sizeof(ufs_inode->ui_u2.ui_symlink));
- }
+ } else {
+ memcpy(&ufs_inode->ui_u2.ui_addr, ufsi->i_u1.u2_i_data,
+ sizeof(ufs_inode->ui_u2.ui_addr));
+ }
if (!inode->i_nlink)
memset (ufs_inode, 0, sizeof(struct ufs2_inode));
@@ -839,14 +849,18 @@ int ufs_sync_inode (struct inode *inode)
void ufs_evict_inode(struct inode * inode)
{
int want_delete = 0;
+ bool fast_symlink;
if (!inode->i_nlink && !is_bad_inode(inode))
want_delete = 1;
+ // must be evaluated before i_size is cleared below
+ fast_symlink = S_ISLNK(inode->i_mode) && ufs_is_fast_symlink(inode);
+
truncate_inode_pages_final(&inode->i_data);
if (want_delete) {
inode->i_size = 0;
- if (inode->i_blocks &&
+ if (inode->i_blocks && !fast_symlink &&
(S_ISREG(inode->i_mode) || S_ISDIR(inode->i_mode) ||
S_ISLNK(inode->i_mode)))
ufs_truncate_blocks(inode);
--
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 ` Ali Ahmet Memis [this message]
2026-08-01 22:55 ` [PATCH 03/10] ufs: free UFS2 external attribute blocks on inode deletion Ali Ahmet Memis
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-3-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®