summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDarrick J. Wong <djwong@kernel.org>2026-01-23 09:27:36 -0800
committerDarrick J. Wong <djwong@kernel.org>2026-01-23 09:27:36 -0800
commiteaec8aeff31d0679eadb27a13a62942ddbfd7b87 (patch)
tree7e6b3afe4d124931335f4cc6ba9e6e91535a08ea
parentd693534513d8dcdaafcf855986d0fe0476a47462 (diff)
xfs: add a method to replace shortform attrs
If we're trying to replace an xattr in a shortform attr structure and the old entry fits the new entry, we can just memcpy and exit without having to delete, compact, and re-add the entry (or worse use the attr intent machinery). For parent pointers this only advantages renaming where the filename length stays the same (e.g. mv autoexec.bat scandisk.exe) but for regular xattrs it might be useful for updating security labels and the like. Signed-off-by: "Darrick J. Wong" <djwong@kernel.org> Reviewed-by: Christoph Hellwig <hch@lst.de>
-rw-r--r--fs/xfs/libxfs/xfs_attr.c4
-rw-r--r--fs/xfs/libxfs/xfs_attr_leaf.c38
-rw-r--r--fs/xfs/libxfs/xfs_attr_leaf.h1
-rw-r--r--fs/xfs/xfs_trace.h1
4 files changed, 44 insertions, 0 deletions
diff --git a/fs/xfs/libxfs/xfs_attr.c b/fs/xfs/libxfs/xfs_attr.c
index 54be75edb2eb..93caa1dae501 100644
--- a/fs/xfs/libxfs/xfs_attr.c
+++ b/fs/xfs/libxfs/xfs_attr.c
@@ -1085,6 +1085,10 @@ xfs_attr_replacename(
return 0;
}
+ error = xfs_attr_shortform_replace(args);
+ if (error != -ENOSPC)
+ return error;
+
args->op_flags |= XFS_DA_OP_ADDNAME | XFS_DA_OP_REPLACE;
error = xfs_attr_sf_removename(args);
diff --git a/fs/xfs/libxfs/xfs_attr_leaf.c b/fs/xfs/libxfs/xfs_attr_leaf.c
index c3327b10709c..47f48ae555c0 100644
--- a/fs/xfs/libxfs/xfs_attr_leaf.c
+++ b/fs/xfs/libxfs/xfs_attr_leaf.c
@@ -843,6 +843,44 @@ xfs_attr_sf_findname(
}
/*
+ * Replace a shortform xattr if it's the right length. Returns 0 on success,
+ * -ENOSPC if the length is wrong, or -ENOATTR if the attr was not found.
+ */
+int
+xfs_attr_shortform_replace(
+ struct xfs_da_args *args)
+{
+ struct xfs_attr_sf_entry *sfe;
+
+ ASSERT(args->dp->i_af.if_format == XFS_DINODE_FMT_LOCAL);
+
+ trace_xfs_attr_sf_replace(args);
+
+ sfe = xfs_attr_sf_findname(args);
+ if (!sfe)
+ return -ENOATTR;
+
+ if (args->attr_filter & XFS_ATTR_PARENT) {
+ if (sfe->namelen != args->new_namelen ||
+ sfe->valuelen != args->new_valuelen)
+ return -ENOSPC;
+
+ memcpy(sfe->nameval, args->new_name, sfe->namelen);
+ memcpy(&sfe->nameval[sfe->namelen], args->new_value,
+ sfe->valuelen);
+ } else {
+ if (sfe->valuelen != args->valuelen)
+ return -ENOSPC;
+ memcpy(&sfe->nameval[sfe->namelen], args->value,
+ sfe->valuelen);
+ }
+
+ xfs_trans_log_inode(args->trans, args->dp,
+ XFS_ILOG_CORE | XFS_ILOG_ADATA);
+ return 0;
+}
+
+/*
* Add a name/value pair to the shortform attribute list.
* Overflow from the inode has already been checked for.
*/
diff --git a/fs/xfs/libxfs/xfs_attr_leaf.h b/fs/xfs/libxfs/xfs_attr_leaf.h
index 589f810eedc0..aca46da2bc50 100644
--- a/fs/xfs/libxfs/xfs_attr_leaf.h
+++ b/fs/xfs/libxfs/xfs_attr_leaf.h
@@ -46,6 +46,7 @@ struct xfs_attr3_icleaf_hdr {
* Internal routines when attribute fork size < XFS_LITINO(mp).
*/
void xfs_attr_shortform_create(struct xfs_da_args *args);
+int xfs_attr_shortform_replace(struct xfs_da_args *args);
void xfs_attr_shortform_add(struct xfs_da_args *args, int forkoff);
int xfs_attr_shortform_getvalue(struct xfs_da_args *args);
int xfs_attr_shortform_to_leaf(struct xfs_da_args *args);
diff --git a/fs/xfs/xfs_trace.h b/fs/xfs/xfs_trace.h
index f70afbf3cb19..a8bea99e0024 100644
--- a/fs/xfs/xfs_trace.h
+++ b/fs/xfs/xfs_trace.h
@@ -2410,6 +2410,7 @@ DEFINE_ATTR_EVENT(xfs_attr_sf_addname);
DEFINE_ATTR_EVENT(xfs_attr_sf_create);
DEFINE_ATTR_EVENT(xfs_attr_sf_lookup);
DEFINE_ATTR_EVENT(xfs_attr_sf_remove);
+DEFINE_ATTR_EVENT(xfs_attr_sf_replace);
DEFINE_ATTR_EVENT(xfs_attr_sf_to_leaf);
DEFINE_ATTR_EVENT(xfs_attr_leaf_add);