Re: [PATCH v4 5/6] f2fs: add f2fs_ioc_[get|set]_extra_attr

From: Sheng Yong
Date: Mon Jun 12 2023 - 21:54:03 EST




On 2023/6/12 23:47, Jaegeuk Kim wrote:
On 06/12, Sheng Yong wrote:
This patch introduces two ioctls:
* f2fs_ioc_get_extra_attr
* f2fs_ioc_set_extra_attr
to get or modify values in f2fs_inode's extra attribute area.

What'd be the main purpose of this new ioctl? Use-cases or examples?

Hi, Jaegeuk,

When a new feature is added, f2fs_inode extra attributes needs to be
extended sometimes to save status or control values, say compression,
inode chksum, inode crtime.

If some of them could be read or modified during runtime, a new ioctl
is required in most cases, like F2FS_IOC_GET_COMPRESS_BLOCKS,
F2FS_IOC_GET_COMPRESS_OPTION...
And Yangtao [1] and I [2] also tried to add new ioctls to allow getting
and settingcompress level and flags.

If new features are added in the future, we may get a verty long and
not-easy-to-extend ioctl list.

So these 2 ioctls could help manage all extra attributes:
* if a new extra attribute is added, we only need to add a new field type,
and corresponding get/set functions
* attr_size can indicate which version of attribute is used
* besides, ioc_set_extra_attr(i_inline_xattr_size) gives us a chance to
adjust inline xattr size for an empty file

thanks,
shengyong

Links:
[1] https://lore.kernel.org/linux-f2fs-devel/20230112133503.16802-1-frank.li@xxxxxxxx/
[2] https://sourceforge.net/p/linux-f2fs/mailman/linux-f2fs-devel/?viewmonth=202303


The argument of these two ioctls is `struct f2fs_extra_attr', which has
three members:
* field: indicates which field in extra attribute area is handled
* attr: value or userspace pointer
* attr_size: size of `attr'

The `field' member could help extend functionality of these two ioctls
without modify or add new interfaces, if more fields are added into
extra attributes ares in the feture.

Signed-off-by: Sheng Yong <shengyong@xxxxxxxx>
---
fs/f2fs/file.c | 205 ++++++++++++++++++++++++++++++++++++++
include/uapi/linux/f2fs.h | 25 +++++
2 files changed, 230 insertions(+)

diff --git a/fs/f2fs/file.c b/fs/f2fs/file.c
index f8aa842b5d233..39d04f8f0bb6b 100644
--- a/fs/f2fs/file.c
+++ b/fs/f2fs/file.c
@@ -4179,6 +4179,207 @@ static int f2fs_ioc_compress_file(struct file *filp)
return ret;
}
+static bool extra_attr_fits_in_inode(struct inode *inode, int field)
+{
+ struct f2fs_inode_info *fi = F2FS_I(inode);
+ struct f2fs_inode *ri;
+
+ switch (field) {
+ case F2FS_EXTRA_ATTR_TOTAL_SIZE:
+ case F2FS_EXTRA_ATTR_ISIZE:
+ case F2FS_EXTRA_ATTR_INLINE_XATTR_SIZE:
+ return true;
+ case F2FS_EXTRA_ATTR_PROJID:
+ if (!F2FS_FITS_IN_INODE(ri, fi->i_extra_isize, i_projid))
+ return false;
+ return true;
+ case F2FS_EXTRA_ATTR_INODE_CHKSUM:
+ if (!F2FS_FITS_IN_INODE(ri, fi->i_extra_isize, i_inode_checksum))
+ return false;
+ return true;
+ case F2FS_EXTRA_ATTR_CRTIME:
+ if (!F2FS_FITS_IN_INODE(ri, fi->i_extra_isize, i_crtime))
+ return false;
+ return true;
+ case F2FS_EXTRA_ATTR_COMPR_BLOCKS:
+ case F2FS_EXTRA_ATTR_COMPR_OPTION:
+ if (!F2FS_FITS_IN_INODE(ri, fi->i_extra_isize, i_compr_blocks))
+ return false;
+ return true;
+ default:
+ BUG_ON(1);
+ return false;
+ }
+}
+
+static int f2fs_ioc_get_extra_attr(struct file *filp, unsigned long arg)
+{
+ struct inode *inode = file_inode(filp);
+ struct f2fs_inode_info *fi = F2FS_I(inode);
+ struct f2fs_sb_info *sbi = F2FS_I_SB(inode);
+ struct f2fs_extra_attr attr;
+ u32 chksum;
+ int ret = 0;
+
+ if (!f2fs_has_extra_attr(inode))
+ return -EOPNOTSUPP;
+
+ if (copy_from_user(&attr, (void __user *)arg, sizeof(attr)))
+ return -EFAULT;
+
+ if (attr.field >= F2FS_EXTRA_ATTR_MAX)
+ return -EINVAL;
+
+ if (!extra_attr_fits_in_inode(inode, attr.field))
+ return -EOPNOTSUPP;
+
+ switch (attr.field) {
+ case F2FS_EXTRA_ATTR_TOTAL_SIZE:
+ attr.attr = F2FS_TOTAL_EXTRA_ATTR_SIZE;
+ break;
+ case F2FS_EXTRA_ATTR_ISIZE:
+ attr.attr = fi->i_extra_isize;
+ break;
+ case F2FS_EXTRA_ATTR_INLINE_XATTR_SIZE:
+ if (!f2fs_has_inline_xattr(inode))
+ return -EOPNOTSUPP;
+ attr.attr = get_inline_xattr_addrs(inode);
+ break;
+ case F2FS_EXTRA_ATTR_PROJID:
+ if (!f2fs_sb_has_project_quota(F2FS_I_SB(inode)))
+ return -EOPNOTSUPP;
+ attr.attr = from_kprojid(&init_user_ns, fi->i_projid);
+ break;
+ case F2FS_EXTRA_ATTR_INODE_CHKSUM:
+ ret = f2fs_inode_chksum_get(sbi, inode, &chksum);
+ if (ret)
+ return ret;
+ attr.attr = chksum;
+ break;
+ case F2FS_EXTRA_ATTR_CRTIME:
+ if (!f2fs_sb_has_inode_crtime(sbi))
+ return -EOPNOTSUPP;
+ if (attr.attr_size == sizeof(struct timespec64)) {
+ if (put_timespec64(&fi->i_crtime,
+ (void __user *)(uintptr_t)attr.attr))
+ return -EFAULT;
+ } else if (attr.attr_size == sizeof(struct old_timespec32)) {
+ if (put_old_timespec32(&fi->i_crtime,
+ (void __user *)(uintptr_t)attr.attr))
+ return -EFAULT;
+ } else {
+ return -EINVAL;
+ }
+ break;
+ case F2FS_EXTRA_ATTR_COMPR_BLOCKS:
+ if (attr.attr_size != sizeof(__u64))
+ return -EINVAL;
+ ret = f2fs_get_compress_blocks(inode, &attr.attr);
+ break;
+ case F2FS_EXTRA_ATTR_COMPR_OPTION:
+ ret = f2fs_ioc_get_compress_option(filp, attr.attr);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ if (ret < 0)
+ return ret;
+
+ if (copy_to_user((void __user *)arg, &attr, sizeof(attr)))
+ return -EFAULT;
+
+ return 0;
+}
+
+static int f2fs_ioc_set_extra_attr(struct file *filp, unsigned long arg)
+{
+ struct inode *inode = file_inode(filp);
+ struct f2fs_sb_info *sbi = F2FS_I_SB(inode);
+ struct f2fs_extra_attr attr;
+ struct page *ipage;
+ void *inline_addr;
+ int ret;
+
+ if (!f2fs_has_extra_attr(inode))
+ return -EOPNOTSUPP;
+
+ if (copy_from_user(&attr, (void __user *)arg, sizeof(attr)))
+ return -EFAULT;
+
+ if (attr.field >= F2FS_EXTRA_ATTR_MAX)
+ return -EINVAL;
+
+ if (!extra_attr_fits_in_inode(inode, attr.field))
+ return -EOPNOTSUPP;
+
+ switch (attr.field) {
+ case F2FS_EXTRA_ATTR_TOTAL_SIZE:
+ case F2FS_EXTRA_ATTR_ISIZE:
+ case F2FS_EXTRA_ATTR_PROJID:
+ case F2FS_EXTRA_ATTR_INODE_CHKSUM:
+ case F2FS_EXTRA_ATTR_CRTIME:
+ case F2FS_EXTRA_ATTR_COMPR_BLOCKS:
+ /* read only attribtues */
+ return -EOPNOTSUPP;
+ case F2FS_EXTRA_ATTR_INLINE_XATTR_SIZE:
+ if (!f2fs_sb_has_flexible_inline_xattr(sbi) ||
+ !f2fs_has_inline_xattr(inode))
+ return -EOPNOTSUPP;
+ if (attr.attr < MIN_INLINE_XATTR_SIZE ||
+ attr.attr > MAX_INLINE_XATTR_SIZE)
+ return -EINVAL;
+ inode_lock(inode);
+ f2fs_lock_op(sbi);
+ f2fs_down_write(&F2FS_I(inode)->i_xattr_sem);
+ if (i_size_read(inode) || F2FS_I(inode)->i_xattr_nid) {
+ /*
+ * it is not allowed to set this field if the inode
+ * has data or xattr node
+ */
+ ret = -EFBIG;
+ goto xattr_out_unlock;
+ }
+ ipage = f2fs_get_node_page(sbi, inode->i_ino);
+ if (IS_ERR(ipage)) {
+ ret = PTR_ERR(ipage);
+ goto xattr_out_unlock;
+ }
+ inline_addr = inline_xattr_addr(inode, ipage);
+ if (!IS_XATTR_LAST_ENTRY(XATTR_FIRST_ENTRY(inline_addr))) {
+ ret = -EFBIG;
+ } else {
+ struct f2fs_xattr_header *hdr;
+ struct f2fs_xattr_entry *ent;
+
+ F2FS_I(inode)->i_inline_xattr_size = (int)attr.attr;
+ inline_addr = inline_xattr_addr(inode, ipage);
+ hdr = XATTR_HDR(inline_addr);
+ ent = XATTR_FIRST_ENTRY(inline_addr);
+ hdr->h_magic = cpu_to_le32(F2FS_XATTR_MAGIC);
+ hdr->h_refcount = cpu_to_le32(1);
+ memset(ent, 0, attr.attr - sizeof(*hdr));
+ set_page_dirty(ipage);
+ ret = 0;
+ }
+ f2fs_put_page(ipage, 1);
+xattr_out_unlock:
+ f2fs_up_write(&F2FS_I(inode)->i_xattr_sem);
+ f2fs_unlock_op(sbi);
+ inode_unlock(inode);
+ if (!ret)
+ f2fs_balance_fs(sbi, true);
+ break;
+ case F2FS_EXTRA_ATTR_COMPR_OPTION:
+ ret = f2fs_ioc_set_compress_option(filp, attr.attr);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return ret;
+}
+
static long __f2fs_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
switch (cmd) {
@@ -4265,6 +4466,10 @@ static long __f2fs_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
return f2fs_ioc_decompress_file(filp);
case F2FS_IOC_COMPRESS_FILE:
return f2fs_ioc_compress_file(filp);
+ case F2FS_IOC_GET_EXTRA_ATTR:
+ return f2fs_ioc_get_extra_attr(filp, arg);
+ case F2FS_IOC_SET_EXTRA_ATTR:
+ return f2fs_ioc_set_extra_attr(filp, arg);
default:
return -ENOTTY;
}
diff --git a/include/uapi/linux/f2fs.h b/include/uapi/linux/f2fs.h
index 955d440be1046..2b53e90421bfc 100644
--- a/include/uapi/linux/f2fs.h
+++ b/include/uapi/linux/f2fs.h
@@ -43,6 +43,10 @@
#define F2FS_IOC_DECOMPRESS_FILE _IO(F2FS_IOCTL_MAGIC, 23)
#define F2FS_IOC_COMPRESS_FILE _IO(F2FS_IOCTL_MAGIC, 24)
#define F2FS_IOC_START_ATOMIC_REPLACE _IO(F2FS_IOCTL_MAGIC, 25)
+#define F2FS_IOC_GET_EXTRA_ATTR _IOR(F2FS_IOCTL_MAGIC, 26, \
+ struct f2fs_extra_attr)
+#define F2FS_IOC_SET_EXTRA_ATTR _IOW(F2FS_IOCTL_MAGIC, 27, \
+ struct f2fs_extra_attr)
/*
* should be same as XFS_IOC_GOINGDOWN.
@@ -96,4 +100,25 @@ struct f2fs_comp_option {
__u8 log_cluster_size;
};
+enum {
+ F2FS_EXTRA_ATTR_TOTAL_SIZE, /* ro, size of extra attr area */
+ F2FS_EXTRA_ATTR_ISIZE, /* ro, i_extra_isize */
+ F2FS_EXTRA_ATTR_INLINE_XATTR_SIZE, /* rw, i_inline_xattr_size */
+ F2FS_EXTRA_ATTR_PROJID, /* ro, i_projid */
+ F2FS_EXTRA_ATTR_INODE_CHKSUM, /* ro, i_inode_chksum */
+ F2FS_EXTRA_ATTR_CRTIME, /* ro, i_crtime, i_crtime_nsec */
+ F2FS_EXTRA_ATTR_COMPR_BLOCKS, /* ro, i_compr_blocks */
+ F2FS_EXTRA_ATTR_COMPR_OPTION, /* rw, i_compress_algorithm,
+ * i_log_cluster_size
+ */
+ F2FS_EXTRA_ATTR_MAX,
+};
+
+struct f2fs_extra_attr {
+ __u8 field; /* F2FS_EXTRA_ATTR_* */
+ __u8 rsvd1;
+ __u16 attr_size; /* size of @attr */
+ __u32 rsvd2;
+ __u64 attr; /* attr value or pointer */
+};
#endif /* _UAPI_LINUX_F2FS_H */
--
2.40.1