[PATCH] f2fs: add f2fs_ioc_[get|set]_extra_attr

From: Sheng Yong
Date: Sun May 28 2023 - 21:35:41 EST


This patch introduces two ioctls:
* f2fs_ioc_get_extra_attr
* f2fs_ioc_set_extra_attr
to get or modify values in extra attribute area.

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/f2fs.h | 2 +
fs/f2fs/file.c | 289 ++++++++++++++++++++++++++++++++++++--
fs/f2fs/inode.c | 21 +++
fs/f2fs/super.c | 2 +-
fs/f2fs/xattr.h | 1 +
include/uapi/linux/f2fs.h | 35 +++++
6 files changed, 337 insertions(+), 13 deletions(-)

diff --git a/fs/f2fs/f2fs.h b/fs/f2fs/f2fs.h
index faa27f41f39d4..08e8527734df5 100644
--- a/fs/f2fs/f2fs.h
+++ b/fs/f2fs/f2fs.h
@@ -3475,6 +3475,8 @@ int f2fs_pin_file_control(struct inode *inode, bool inc);
void f2fs_set_inode_flags(struct inode *inode);
bool f2fs_inode_chksum_verify(struct f2fs_sb_info *sbi, struct page *page);
void f2fs_inode_chksum_set(struct f2fs_sb_info *sbi, struct page *page);
+int f2fs_inode_chksum_get(struct f2fs_sb_info *sbi, struct inode *inode,
+ u32 *chksum);
struct inode *f2fs_iget(struct super_block *sb, unsigned long ino);
struct inode *f2fs_iget_retry(struct super_block *sb, unsigned long ino);
int f2fs_try_to_free_nats(struct f2fs_sb_info *sbi, int nr_shrink);
diff --git a/fs/f2fs/file.c b/fs/f2fs/file.c
index 78aa8cff4b41d..cdb192faf7c36 100644
--- a/fs/f2fs/file.c
+++ b/fs/f2fs/file.c
@@ -25,6 +25,8 @@
#include <linux/fileattr.h>
#include <linux/fadvise.h>
#include <linux/iomap.h>
+#include <linux/lz4.h>
+#include <linux/zstd.h>

#include "f2fs.h"
#include "node.h"
@@ -3376,10 +3378,12 @@ static int f2fs_ioc_setfslabel(struct file *filp, unsigned long arg)
return err;
}

-static int f2fs_get_compress_blocks(struct file *filp, unsigned long arg)
+static int f2fs_get_compress_blocks(struct file *filp, unsigned int attr_size)
{
struct inode *inode = file_inode(filp);
- __u64 blocks;
+
+ if (attr_size != sizeof(__u64))
+ return -EINVAL;

if (!f2fs_sb_has_compression(F2FS_I_SB(inode)))
return -EOPNOTSUPP;
@@ -3387,7 +3391,14 @@ static int f2fs_get_compress_blocks(struct file *filp, unsigned long arg)
if (!f2fs_compressed_file(inode))
return -EINVAL;

- blocks = atomic_read(&F2FS_I(inode)->i_compr_blocks);
+ return atomic_read(&F2FS_I(inode)->i_compr_blocks);
+}
+
+static int f2fs_ioc_get_compress_blocks(struct file *filp, unsigned long arg)
+{
+ __u64 blocks;
+
+ blocks = f2fs_get_compress_blocks(filp, sizeof(blocks));
return put_user(blocks, (u64 __user *)arg);
}

@@ -3905,10 +3916,14 @@ static int f2fs_sec_trim_file(struct file *filp, unsigned long arg)
return ret;
}

-static int f2fs_ioc_get_compress_option(struct file *filp, unsigned long arg)
+static int f2fs_get_compress_option_v2(struct file *filp,
+ unsigned long attr, __u16 *attr_size)
{
struct inode *inode = file_inode(filp);
- struct f2fs_comp_option option;
+ struct f2fs_comp_option_v2 option;
+
+ if (sizeof(option) < *attr_size)
+ *attr_size = sizeof(option);

if (!f2fs_sb_has_compression(F2FS_I_SB(inode)))
return -EOPNOTSUPP;
@@ -3922,31 +3937,42 @@ static int f2fs_ioc_get_compress_option(struct file *filp, unsigned long arg)

option.algorithm = F2FS_I(inode)->i_compress_algorithm;
option.log_cluster_size = F2FS_I(inode)->i_log_cluster_size;
+ option.level = F2FS_I(inode)->i_compress_level;
+ option.flag = F2FS_I(inode)->i_compress_flag;

inode_unlock_shared(inode);

- if (copy_to_user((struct f2fs_comp_option __user *)arg, &option,
- sizeof(option)))
+ if (copy_to_user((void __user *)attr, &option, *attr_size))
return -EFAULT;

return 0;
}

-static int f2fs_ioc_set_compress_option(struct file *filp, unsigned long arg)
+static int f2fs_ioc_get_compress_option(struct file *filp, unsigned long arg)
+{
+ __u16 size = sizeof(struct f2fs_comp_option);
+
+ return f2fs_get_compress_option_v2(filp, arg, &size);
+}
+
+static int f2fs_set_compress_option_v2(struct file *filp,
+ unsigned long attr, __u16 *attr_size)
{
struct inode *inode = file_inode(filp);
struct f2fs_sb_info *sbi = F2FS_I_SB(inode);
- struct f2fs_comp_option option;
+ struct f2fs_comp_option_v2 option;
int ret = 0;

+ if (sizeof(option) < *attr_size)
+ *attr_size = sizeof(option);
+
if (!f2fs_sb_has_compression(sbi))
return -EOPNOTSUPP;

if (!(filp->f_mode & FMODE_WRITE))
return -EBADF;

- if (copy_from_user(&option, (struct f2fs_comp_option __user *)arg,
- sizeof(option)))
+ if (copy_from_user(&option, (void __user *)attr, *attr_size))
return -EFAULT;

if (!f2fs_compressed_file(inode) ||
@@ -3955,6 +3981,28 @@ static int f2fs_ioc_set_compress_option(struct file *filp, unsigned long arg)
option.algorithm >= COMPRESS_MAX)
return -EINVAL;

+ if (*attr_size == sizeof(struct f2fs_comp_option_v2)) {
+ if (option.level != 0) {
+ switch (option.algorithm) {
+ case COMPRESS_LZO:
+ case COMPRESS_LZORLE:
+ return -EINVAL;
+ case COMPRESS_LZ4:
+ if (option.level < LZ4HC_MIN_CLEVEL ||
+ option.level > LZ4HC_MAX_CLEVEL)
+ return -EINVAL;
+ break;
+ case COMPRESS_ZSTD:
+ if (option.level > zstd_max_clevel())
+ return -EINVAL;
+ break;
+ }
+ }
+
+ if (option.flag > BIT(COMPRESS_MAX_FLAG) - 1)
+ return -EINVAL;
+ }
+
file_start_write(filp);
inode_lock(inode);

@@ -3971,6 +4019,10 @@ static int f2fs_ioc_set_compress_option(struct file *filp, unsigned long arg)
F2FS_I(inode)->i_compress_algorithm = option.algorithm;
F2FS_I(inode)->i_log_cluster_size = option.log_cluster_size;
F2FS_I(inode)->i_cluster_size = BIT(option.log_cluster_size);
+ if (*attr_size == sizeof(struct f2fs_comp_option_v2)) {
+ F2FS_I(inode)->i_compress_level = option.level;
+ F2FS_I(inode)->i_compress_flag = option.flag;
+ }
f2fs_mark_inode_dirty_sync(inode, true);

if (!f2fs_is_compress_backend_ready(inode))
@@ -3983,6 +4035,13 @@ static int f2fs_ioc_set_compress_option(struct file *filp, unsigned long arg)
return ret;
}

+static int f2fs_ioc_set_compress_option(struct file *filp, unsigned long arg)
+{
+ __u16 size = sizeof(struct f2fs_comp_option);
+
+ return f2fs_set_compress_option_v2(filp, arg, &size);
+}
+
static int redirty_blocks(struct inode *inode, pgoff_t page_idx, int len)
{
DEFINE_READAHEAD(ractl, NULL, NULL, inode->i_mapping, page_idx);
@@ -4168,6 +4227,208 @@ 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 *)attr.attr))
+ return -EFAULT;
+ } else if (attr.attr_size == sizeof(struct old_timespec32)) {
+ if (put_old_timespec32(&fi->i_crtime,
+ (void __user *)attr.attr))
+ return -EFAULT;
+ } else {
+ return -EINVAL;
+ }
+ break;
+ case F2FS_EXTRA_ATTR_COMPR_BLOCKS:
+ ret = f2fs_get_compress_blocks(filp, attr.attr_size);
+ attr.attr = ret;
+ break;
+ case F2FS_EXTRA_ATTR_COMPR_OPTION:
+ ret = f2fs_get_compress_option_v2(filp, attr.attr,
+ &attr.attr_size);
+ 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_set_compress_option_v2(filp, attr.attr,
+ &attr.attr_size);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return ret;
+}
+
static long __f2fs_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
switch (cmd) {
@@ -4239,7 +4500,7 @@ static long __f2fs_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
case FS_IOC_SETFSLABEL:
return f2fs_ioc_setfslabel(filp, arg);
case F2FS_IOC_GET_COMPRESS_BLOCKS:
- return f2fs_get_compress_blocks(filp, arg);
+ return f2fs_ioc_get_compress_blocks(filp, arg);
case F2FS_IOC_RELEASE_COMPRESS_BLOCKS:
return f2fs_release_compress_blocks(filp, arg);
case F2FS_IOC_RESERVE_COMPRESS_BLOCKS:
@@ -4254,6 +4515,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/fs/f2fs/inode.c b/fs/f2fs/inode.c
index 0a17484443299..aef9c1fd37dca 100644
--- a/fs/f2fs/inode.c
+++ b/fs/f2fs/inode.c
@@ -204,6 +204,27 @@ void f2fs_inode_chksum_set(struct f2fs_sb_info *sbi, struct page *page)
ri->i_inode_checksum = cpu_to_le32(f2fs_inode_chksum(sbi, page));
}

+int f2fs_inode_chksum_get(struct f2fs_sb_info *sbi,
+ struct inode *inode, u32 *chksum)
+{
+ struct page *ipage;
+ struct f2fs_inode_info *fi = F2FS_I(inode);
+ struct f2fs_inode *ri;
+
+ if (!f2fs_sb_has_inode_chksum(sbi) ||
+ !f2fs_has_extra_attr(inode) ||
+ !F2FS_FITS_IN_INODE(ri, fi->i_extra_isize, i_inode_checksum))
+ return -EOPNOTSUPP;
+
+ ipage = f2fs_get_node_page(sbi, inode->i_ino);
+ if (IS_ERR(ipage))
+ return PTR_ERR(ipage);
+
+ *chksum = f2fs_inode_chksum(sbi, ipage);
+ f2fs_put_page(ipage, true);
+ return 0;
+}
+
static bool sanity_check_compress_inode(struct inode *inode,
struct f2fs_inode *ri)
{
diff --git a/fs/f2fs/super.c b/fs/f2fs/super.c
index 374c990810ead..64adaec4e98e0 100644
--- a/fs/f2fs/super.c
+++ b/fs/f2fs/super.c
@@ -1361,7 +1361,7 @@ static int parse_options(struct super_block *sb, char *options, bool is_remount)
return -EINVAL;
}

- min_size = sizeof(struct f2fs_xattr_header) / sizeof(__le32);
+ min_size = MIN_INLINE_XATTR_SIZE;
max_size = MAX_INLINE_XATTR_SIZE;

if (F2FS_OPTION(sbi).inline_xattr_size < min_size ||
diff --git a/fs/f2fs/xattr.h b/fs/f2fs/xattr.h
index 416d652774a33..b1811c392e6f1 100644
--- a/fs/f2fs/xattr.h
+++ b/fs/f2fs/xattr.h
@@ -83,6 +83,7 @@ struct f2fs_xattr_entry {
sizeof(struct f2fs_xattr_header) - \
sizeof(struct f2fs_xattr_entry))

+#define MIN_INLINE_XATTR_SIZE (sizeof(struct f2fs_xattr_header) / sizeof(__le32))
#define MAX_INLINE_XATTR_SIZE \
(DEF_ADDRS_PER_INODE - \
F2FS_TOTAL_EXTRA_ATTR_SIZE / sizeof(__le32) - \
diff --git a/include/uapi/linux/f2fs.h b/include/uapi/linux/f2fs.h
index 955d440be1046..a8fdaa22c7bda 100644
--- a/include/uapi/linux/f2fs.h
+++ b/include/uapi/linux/f2fs.h
@@ -44,6 +44,11 @@
#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.
* Flags for going down operation used by FS_IOC_GOINGDOWN
@@ -96,4 +101,34 @@ struct f2fs_comp_option {
__u8 log_cluster_size;
};

+struct f2fs_comp_option_v2 {
+ __u8 algorithm;
+ __u8 log_cluster_size;
+ __u8 level;
+ __u8 flag;
+};
+
+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,
+ * i_compress_flag
+ */
+ 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