[PATCH 02/10] ufs: use i_size to detect fast symlinks
Ali Ahmet Memis <[email protected]> Sat, 1 Aug 2026 22:55:22 +0000
| Newsgroups | org.kernel.vger.linux-fsdevel,org.kernel.vger.linux-kernel |
|---|---|
| Message-ID | <[email protected]> |
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 <[email protected]>
---
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