bcachefs: Implement fileattr_(get|set)
authorKent Overstreet <kent.overstreet@linux.dev>
Fri, 28 Mar 2025 16:26:24 +0000 (12:26 -0400)
committerKent Overstreet <kent.overstreet@linux.dev>
Mon, 21 Apr 2025 23:50:56 +0000 (19:50 -0400)
inode_operations.fileattr_(get|set) didn't exist when the various flag
ioctls where implemented - but they do now, which means we can delete a
bunch of ioctl code in favor of standard VFS level wrappers.

Closes: https://lore.kernel.org/linux-bcachefs/7ltgrgqgfummyrlvw7hnfhnu42rfiamoq3lpcvrjnlyytldmzp@yazbhusnztqn/
Cc: Petr Vorel <pvorel@suse.cz>
Cc: Andrea Cervesato <andrea.cervesato@suse.de>
Cc: Dave Chinner <david@fromorbit.com>
Signed-off-by: Kent Overstreet <kent.overstreet@linux.dev>
fs/bcachefs/fs-ioctl.c
fs/bcachefs/fs-ioctl.h
fs/bcachefs/fs.c
fs/bcachefs/util.h

index 14886e1d4d6dff1c6beecc92780cdb1e0f43cba4..a82dfce9e4ad3735279fbfe1310e907ff24ac0c0 100644 (file)
 #define FSOP_GOING_FLAGS_LOGFLUSH      0x1     /* flush log but not data */
 #define FSOP_GOING_FLAGS_NOLOGFLUSH    0x2     /* don't flush log nor data */
 
-struct flags_set {
-       unsigned                mask;
-       unsigned                flags;
-
-       unsigned                projid;
-
-       bool                    set_projinherit;
-       bool                    projinherit;
-};
-
-static int bch2_inode_flags_set(struct btree_trans *trans,
-                               struct bch_inode_info *inode,
-                               struct bch_inode_unpacked *bi,
-                               void *p)
-{
-       struct bch_fs *c = inode->v.i_sb->s_fs_info;
-       /*
-        * We're relying on btree locking here for exclusion with other ioctl
-        * calls - use the flags in the btree (@bi), not inode->i_flags:
-        */
-       struct flags_set *s = p;
-       unsigned newflags = s->flags;
-       unsigned oldflags = bi->bi_flags & s->mask;
-
-       if (((newflags ^ oldflags) & (BCH_INODE_append|BCH_INODE_immutable)) &&
-           !capable(CAP_LINUX_IMMUTABLE))
-               return -EPERM;
-
-       if (!S_ISREG(bi->bi_mode) &&
-           !S_ISDIR(bi->bi_mode) &&
-           (newflags & (BCH_INODE_nodump|BCH_INODE_noatime)) != newflags)
-               return -EINVAL;
-
-       if ((newflags ^ oldflags) & BCH_INODE_casefolded) {
-#ifdef CONFIG_UNICODE
-               int ret = 0;
-               /* Not supported on individual files. */
-               if (!S_ISDIR(bi->bi_mode))
-                       return -EOPNOTSUPP;
-
-               /*
-                * Make sure the dir is empty, as otherwise we'd need to
-                * rehash everything and update the dirent keys.
-                */
-               ret = bch2_empty_dir_trans(trans, inode_inum(inode));
-               if (ret < 0)
-                       return ret;
-
-               ret = bch2_request_incompat_feature(c, bcachefs_metadata_version_casefolding);
-               if (ret)
-                       return ret;
-
-               bch2_check_set_feature(c, BCH_FEATURE_casefolding);
-#else
-               printk(KERN_ERR "Cannot use casefolding on a kernel without CONFIG_UNICODE\n");
-               return -EOPNOTSUPP;
-#endif
-       }
-
-       if (s->set_projinherit) {
-               bi->bi_fields_set &= ~(1 << Inode_opt_project);
-               bi->bi_fields_set |= ((int) s->projinherit << Inode_opt_project);
-       }
-
-       bi->bi_flags &= ~s->mask;
-       bi->bi_flags |= newflags;
-
-       bi->bi_ctime = timespec_to_bch2_time(c, current_time(&inode->v));
-       return 0;
-}
-
-static int bch2_ioc_getflags(struct bch_inode_info *inode, int __user *arg)
-{
-       unsigned flags = map_flags(bch_flags_to_uflags, inode->ei_inode.bi_flags);
-
-       return put_user(flags, arg);
-}
-
-static int bch2_ioc_setflags(struct bch_fs *c,
-                            struct file *file,
-                            struct bch_inode_info *inode,
-                            void __user *arg)
-{
-       struct flags_set s = { .mask = map_defined(bch_flags_to_uflags) };
-       unsigned uflags;
-       int ret;
-
-       if (get_user(uflags, (int __user *) arg))
-               return -EFAULT;
-
-       s.flags = map_flags_rev(bch_flags_to_uflags, uflags);
-       if (uflags)
-               return -EOPNOTSUPP;
-
-       ret = mnt_want_write_file(file);
-       if (ret)
-               return ret;
-
-       inode_lock(&inode->v);
-       if (!inode_owner_or_capable(file_mnt_idmap(file), &inode->v)) {
-               ret = -EACCES;
-               goto setflags_out;
-       }
-
-       mutex_lock(&inode->ei_update_lock);
-       ret   = bch2_subvol_is_ro(c, inode->ei_inum.subvol) ?:
-               bch2_write_inode(c, inode, bch2_inode_flags_set, &s,
-                              ATTR_CTIME);
-       mutex_unlock(&inode->ei_update_lock);
-
-setflags_out:
-       inode_unlock(&inode->v);
-       mnt_drop_write_file(file);
-       return ret;
-}
-
-static int bch2_ioc_fsgetxattr(struct bch_inode_info *inode,
-                              struct fsxattr __user *arg)
-{
-       struct fsxattr fa = { 0 };
-
-       fa.fsx_xflags = map_flags(bch_flags_to_xflags, inode->ei_inode.bi_flags);
-
-       if (inode->ei_inode.bi_fields_set & (1 << Inode_opt_project))
-               fa.fsx_xflags |= FS_XFLAG_PROJINHERIT;
-
-       fa.fsx_projid = inode->ei_qid.q[QTYP_PRJ];
-
-       if (copy_to_user(arg, &fa, sizeof(fa)))
-               return -EFAULT;
-
-       return 0;
-}
-
-static int fssetxattr_inode_update_fn(struct btree_trans *trans,
-                                     struct bch_inode_info *inode,
-                                     struct bch_inode_unpacked *bi,
-                                     void *p)
-{
-       struct flags_set *s = p;
-
-       if (s->projid != bi->bi_project) {
-               bi->bi_fields_set |= 1U << Inode_opt_project;
-               bi->bi_project = s->projid;
-       }
-
-       return bch2_inode_flags_set(trans, inode, bi, p);
-}
-
-static int bch2_ioc_fssetxattr(struct bch_fs *c,
-                              struct file *file,
-                              struct bch_inode_info *inode,
-                              struct fsxattr __user *arg)
-{
-       struct flags_set s = { .mask = map_defined(bch_flags_to_xflags) };
-       struct fsxattr fa;
-       int ret;
-
-       if (copy_from_user(&fa, arg, sizeof(fa)))
-               return -EFAULT;
-
-       s.set_projinherit = true;
-       s.projinherit = (fa.fsx_xflags & FS_XFLAG_PROJINHERIT) != 0;
-       fa.fsx_xflags &= ~FS_XFLAG_PROJINHERIT;
-
-       s.flags = map_flags_rev(bch_flags_to_xflags, fa.fsx_xflags);
-       if (fa.fsx_xflags)
-               return -EOPNOTSUPP;
-
-       if (fa.fsx_projid >= U32_MAX)
-               return -EINVAL;
-
-       /*
-        * inode fields accessible via the xattr interface are stored with a +1
-        * bias, so that 0 means unset:
-        */
-       s.projid = fa.fsx_projid + 1;
-
-       ret = mnt_want_write_file(file);
-       if (ret)
-               return ret;
-
-       inode_lock(&inode->v);
-       if (!inode_owner_or_capable(file_mnt_idmap(file), &inode->v)) {
-               ret = -EACCES;
-               goto err;
-       }
-
-       mutex_lock(&inode->ei_update_lock);
-       ret   = bch2_subvol_is_ro(c, inode->ei_inum.subvol) ?:
-               bch2_set_projid(c, inode, fa.fsx_projid) ?:
-               bch2_write_inode(c, inode, fssetxattr_inode_update_fn, &s,
-                              ATTR_CTIME);
-       mutex_unlock(&inode->ei_update_lock);
-err:
-       inode_unlock(&inode->v);
-       mnt_drop_write_file(file);
-       return ret;
-}
-
 static int bch2_reinherit_attrs_fn(struct btree_trans *trans,
                                   struct bch_inode_info *inode,
                                   struct bch_inode_unpacked *bi,
@@ -558,23 +358,6 @@ long bch2_fs_file_ioctl(struct file *file, unsigned cmd, unsigned long arg)
        long ret;
 
        switch (cmd) {
-       case FS_IOC_GETFLAGS:
-               ret = bch2_ioc_getflags(inode, (int __user *) arg);
-               break;
-
-       case FS_IOC_SETFLAGS:
-               ret = bch2_ioc_setflags(c, file, inode, (int __user *) arg);
-               break;
-
-       case FS_IOC_FSGETXATTR:
-               ret = bch2_ioc_fsgetxattr(inode, (void __user *) arg);
-               break;
-
-       case FS_IOC_FSSETXATTR:
-               ret = bch2_ioc_fssetxattr(c, file, inode,
-                                         (void __user *) arg);
-               break;
-
        case BCHFS_IOC_REINHERIT_ATTRS:
                ret = bch2_ioc_reinherit_attrs(c, file, inode,
                                               (void __user *) arg);
index ecd3bfdcde2183c28520ca9b9ab6337504412476..a657e4994b71531fcdc21deaf3b95afe3d3d6e1b 100644 (file)
@@ -2,81 +2,6 @@
 #ifndef _BCACHEFS_FS_IOCTL_H
 #define _BCACHEFS_FS_IOCTL_H
 
-/* Inode flags: */
-
-/* bcachefs inode flags -> vfs inode flags: */
-static const __maybe_unused unsigned bch_flags_to_vfs[] = {
-       [__BCH_INODE_sync]              = S_SYNC,
-       [__BCH_INODE_immutable]         = S_IMMUTABLE,
-       [__BCH_INODE_append]            = S_APPEND,
-       [__BCH_INODE_noatime]           = S_NOATIME,
-       [__BCH_INODE_casefolded]        = S_CASEFOLD,
-};
-
-/* bcachefs inode flags -> FS_IOC_GETFLAGS: */
-static const __maybe_unused unsigned bch_flags_to_uflags[] = {
-       [__BCH_INODE_sync]              = FS_SYNC_FL,
-       [__BCH_INODE_immutable]         = FS_IMMUTABLE_FL,
-       [__BCH_INODE_append]            = FS_APPEND_FL,
-       [__BCH_INODE_nodump]            = FS_NODUMP_FL,
-       [__BCH_INODE_noatime]           = FS_NOATIME_FL,
-       [__BCH_INODE_casefolded]        = FS_CASEFOLD_FL,
-};
-
-/* bcachefs inode flags -> FS_IOC_FSGETXATTR: */
-static const __maybe_unused unsigned bch_flags_to_xflags[] = {
-       [__BCH_INODE_sync]      = FS_XFLAG_SYNC,
-       [__BCH_INODE_immutable] = FS_XFLAG_IMMUTABLE,
-       [__BCH_INODE_append]    = FS_XFLAG_APPEND,
-       [__BCH_INODE_nodump]    = FS_XFLAG_NODUMP,
-       [__BCH_INODE_noatime]   = FS_XFLAG_NOATIME,
-       //[__BCH_INODE_PROJINHERIT] = FS_XFLAG_PROJINHERIT;
-};
-
-#define set_flags(_map, _in, _out)                                     \
-do {                                                                   \
-       unsigned _i;                                                    \
-                                                                       \
-       for (_i = 0; _i < ARRAY_SIZE(_map); _i++)                       \
-               if ((_in) & (1 << _i))                                  \
-                       (_out) |= _map[_i];                             \
-               else                                                    \
-                       (_out) &= ~_map[_i];                            \
-} while (0)
-
-#define map_flags(_map, _in)                                           \
-({                                                                     \
-       unsigned _out = 0;                                              \
-                                                                       \
-       set_flags(_map, _in, _out);                                     \
-       _out;                                                           \
-})
-
-#define map_flags_rev(_map, _in)                                       \
-({                                                                     \
-       unsigned _i, _out = 0;                                          \
-                                                                       \
-       for (_i = 0; _i < ARRAY_SIZE(_map); _i++)                       \
-               if ((_in) & _map[_i]) {                                 \
-                       (_out) |= 1 << _i;                              \
-                       (_in) &= ~_map[_i];                             \
-               }                                                       \
-       (_out);                                                         \
-})
-
-#define map_defined(_map)                                              \
-({                                                                     \
-       unsigned _in = ~0;                                              \
-                                                                       \
-       map_flags_rev(_map, _in);                                       \
-})
-
-/* Set VFS inode flags from bcachefs inode: */
-static inline void bch2_inode_flags_to_vfs(struct bch_inode_info *inode)
-{
-       set_flags(bch_flags_to_vfs, inode->ei_inode.bi_flags, inode->v.i_flags);
-}
-
 long bch2_fs_file_ioctl(struct file *, unsigned, unsigned long);
 long bch2_compat_fs_ioctl(struct file *, unsigned, unsigned long);
 
index 5a41b1a8e54fd89439123aa62c0e8e82056a8348..e220e3cba29c7355c2bb05cb3da5066f1bcd380a 100644 (file)
@@ -33,6 +33,7 @@
 #include <linux/backing-dev.h>
 #include <linux/exportfs.h>
 #include <linux/fiemap.h>
+#include <linux/fileattr.h>
 #include <linux/fs_context.h>
 #include <linux/module.h>
 #include <linux/pagemap.h>
@@ -51,6 +52,19 @@ static void bch2_vfs_inode_init(struct btree_trans *, subvol_inum,
                                struct bch_inode_unpacked *,
                                struct bch_subvolume *);
 
+/* Set VFS inode flags from bcachefs inode: */
+static inline void bch2_inode_flags_to_vfs(struct bch_inode_info *inode)
+{
+       static const __maybe_unused unsigned bch_flags_to_vfs[] = {
+               [__BCH_INODE_sync]              = S_SYNC,
+               [__BCH_INODE_immutable]         = S_IMMUTABLE,
+               [__BCH_INODE_append]            = S_APPEND,
+               [__BCH_INODE_noatime]           = S_NOATIME,
+               [__BCH_INODE_casefolded]        = S_CASEFOLD,
+       };
+       set_flags(bch_flags_to_vfs, inode->ei_inode.bi_flags, inode->v.i_flags);
+}
+
 void bch2_inode_update_after_write(struct btree_trans *trans,
                                   struct bch_inode_info *inode,
                                   struct bch_inode_unpacked *bi,
@@ -1449,6 +1463,157 @@ static int bch2_open(struct inode *vinode, struct file *file)
        return generic_file_open(vinode, file);
 }
 
+/* bcachefs inode flags -> FS_IOC_GETFLAGS: */
+static const __maybe_unused unsigned bch_flags_to_uflags[] = {
+       [__BCH_INODE_sync]              = FS_SYNC_FL,
+       [__BCH_INODE_immutable]         = FS_IMMUTABLE_FL,
+       [__BCH_INODE_append]            = FS_APPEND_FL,
+       [__BCH_INODE_nodump]            = FS_NODUMP_FL,
+       [__BCH_INODE_noatime]           = FS_NOATIME_FL,
+       [__BCH_INODE_casefolded]        = FS_CASEFOLD_FL,
+};
+
+/* bcachefs inode flags -> FS_IOC_FSGETXATTR: */
+static const __maybe_unused unsigned bch_flags_to_xflags[] = {
+       [__BCH_INODE_sync]      = FS_XFLAG_SYNC,
+       [__BCH_INODE_immutable] = FS_XFLAG_IMMUTABLE,
+       [__BCH_INODE_append]    = FS_XFLAG_APPEND,
+       [__BCH_INODE_nodump]    = FS_XFLAG_NODUMP,
+       [__BCH_INODE_noatime]   = FS_XFLAG_NOATIME,
+};
+
+static int bch2_fileattr_get(struct dentry *dentry,
+                            struct fileattr *fa)
+{
+       struct bch_inode_info *inode = to_bch_ei(d_inode(dentry));
+
+       fileattr_fill_xflags(fa, map_flags(bch_flags_to_xflags, inode->ei_inode.bi_flags));
+
+       if (inode->ei_inode.bi_fields_set & (1 << Inode_opt_project))
+               fa->fsx_xflags |= FS_XFLAG_PROJINHERIT;
+
+       if (inode->ei_inode.bi_flags & BCH_INODE_casefolded)
+               fa->flags |= FS_CASEFOLD_FL;
+
+       fa->fsx_projid = inode->ei_qid.q[QTYP_PRJ];
+       return 0;
+}
+
+struct flags_set {
+       unsigned                mask;
+       unsigned                flags;
+       unsigned                projid;
+       bool                    set_project;
+};
+
+static int fssetxattr_inode_update_fn(struct btree_trans *trans,
+                                     struct bch_inode_info *inode,
+                                     struct bch_inode_unpacked *bi,
+                                     void *p)
+{
+       struct bch_fs *c = trans->c;
+       struct flags_set *s = p;
+
+       /*
+        * We're relying on btree locking here for exclusion with other ioctl
+        * calls - use the flags in the btree (@bi), not inode->i_flags:
+        */
+       unsigned newflags = s->flags;
+       unsigned oldflags = bi->bi_flags & s->mask;
+
+       if (!S_ISREG(bi->bi_mode) &&
+           !S_ISDIR(bi->bi_mode) &&
+           (newflags & (BCH_INODE_nodump|BCH_INODE_noatime)) != newflags)
+               return -EINVAL;
+
+       if ((newflags ^ oldflags) & BCH_INODE_casefolded) {
+#ifdef CONFIG_UNICODE
+               int ret = 0;
+               /* Not supported on individual files. */
+               if (!S_ISDIR(bi->bi_mode))
+                       return -EOPNOTSUPP;
+
+               /*
+                * Make sure the dir is empty, as otherwise we'd need to
+                * rehash everything and update the dirent keys.
+                */
+               ret = bch2_empty_dir_trans(trans, inode_inum(inode));
+               if (ret < 0)
+                       return ret;
+
+               ret = bch2_request_incompat_feature(c, bcachefs_metadata_version_casefolding);
+               if (ret)
+                       return ret;
+
+               bch2_check_set_feature(c, BCH_FEATURE_casefolding);
+#else
+               printk(KERN_ERR "Cannot use casefolding on a kernel without CONFIG_UNICODE\n");
+               return -EOPNOTSUPP;
+#endif
+       }
+
+       if (s->set_project) {
+               bi->bi_project = s->projid;
+               bi->bi_fields_set |= BIT(Inode_opt_project);
+       }
+
+       bi->bi_flags &= ~s->mask;
+       bi->bi_flags |= newflags;
+
+       bi->bi_ctime = timespec_to_bch2_time(c, current_time(&inode->v));
+       return 0;
+}
+
+static int bch2_fileattr_set(struct mnt_idmap *idmap,
+                            struct dentry *dentry,
+                            struct fileattr *fa)
+{
+       struct bch_inode_info *inode = to_bch_ei(d_inode(dentry));
+       struct bch_fs *c = inode->v.i_sb->s_fs_info;
+       struct flags_set s = {};
+       int ret;
+
+       if (fa->fsx_valid) {
+               fa->fsx_xflags &= ~FS_XFLAG_PROJINHERIT;
+
+               s.mask = map_defined(bch_flags_to_xflags);
+               s.flags |= map_flags_rev(bch_flags_to_xflags, fa->fsx_xflags);
+               if (fa->fsx_xflags)
+                       return -EOPNOTSUPP;
+
+               if (fa->fsx_projid >= U32_MAX)
+                       return -EINVAL;
+
+               /*
+                * inode fields accessible via the xattr interface are stored with a +1
+                * bias, so that 0 means unset:
+                */
+               if ((inode->ei_inode.bi_project ||
+                    fa->fsx_projid) &&
+                   inode->ei_inode.bi_project != fa->fsx_projid + 1) {
+                       s.projid = fa->fsx_projid + 1;
+                       s.set_project = true;
+               }
+       }
+
+       if (fa->flags_valid) {
+               s.mask = map_defined(bch_flags_to_uflags);
+               s.flags |= map_flags_rev(bch_flags_to_uflags, fa->flags);
+               if (fa->flags)
+                       return -EOPNOTSUPP;
+       }
+
+       mutex_lock(&inode->ei_update_lock);
+       ret   = bch2_subvol_is_ro(c, inode->ei_inum.subvol) ?:
+               (s.set_project
+                ? bch2_set_projid(c, inode, fa->fsx_projid)
+                : 0) ?:
+               bch2_write_inode(c, inode, fssetxattr_inode_update_fn, &s,
+                              ATTR_CTIME);
+       mutex_unlock(&inode->ei_update_lock);
+       return ret;
+}
+
 static const struct file_operations bch_file_operations = {
        .open           = bch2_open,
        .llseek         = bch2_llseek,
@@ -1476,6 +1641,8 @@ static const struct inode_operations bch_file_inode_operations = {
        .get_inode_acl  = bch2_get_acl,
        .set_acl        = bch2_set_acl,
 #endif
+       .fileattr_get   = bch2_fileattr_get,
+       .fileattr_set   = bch2_fileattr_set,
 };
 
 static const struct inode_operations bch_dir_inode_operations = {
@@ -1496,6 +1663,8 @@ static const struct inode_operations bch_dir_inode_operations = {
        .get_inode_acl  = bch2_get_acl,
        .set_acl        = bch2_set_acl,
 #endif
+       .fileattr_get   = bch2_fileattr_get,
+       .fileattr_set   = bch2_fileattr_set,
 };
 
 static const struct file_operations bch_dir_file_operations = {
@@ -1518,6 +1687,8 @@ static const struct inode_operations bch_symlink_inode_operations = {
        .get_inode_acl  = bch2_get_acl,
        .set_acl        = bch2_set_acl,
 #endif
+       .fileattr_get   = bch2_fileattr_get,
+       .fileattr_set   = bch2_fileattr_set,
 };
 
 static const struct inode_operations bch_special_inode_operations = {
@@ -1528,6 +1699,8 @@ static const struct inode_operations bch_special_inode_operations = {
        .get_inode_acl  = bch2_get_acl,
        .set_acl        = bch2_set_acl,
 #endif
+       .fileattr_get   = bch2_fileattr_get,
+       .fileattr_set   = bch2_fileattr_set,
 };
 
 static const struct address_space_operations bch_address_space_operations = {
index 6ba5071ab6ddaaaa8825c39401da716ed4eeb63b..3e52c7f8ddd225d74d3cd9a4871833327dfd5ecd 100644 (file)
@@ -739,4 +739,42 @@ static inline void memcpy_swab(void *_dst, void *_src, size_t len)
                *--dst = *src++;
 }
 
+#define set_flags(_map, _in, _out)                                     \
+do {                                                                   \
+       unsigned _i;                                                    \
+                                                                       \
+       for (_i = 0; _i < ARRAY_SIZE(_map); _i++)                       \
+               if ((_in) & (1 << _i))                                  \
+                       (_out) |= _map[_i];                             \
+               else                                                    \
+                       (_out) &= ~_map[_i];                            \
+} while (0)
+
+#define map_flags(_map, _in)                                           \
+({                                                                     \
+       unsigned _out = 0;                                              \
+                                                                       \
+       set_flags(_map, _in, _out);                                     \
+       _out;                                                           \
+})
+
+#define map_flags_rev(_map, _in)                                       \
+({                                                                     \
+       unsigned _i, _out = 0;                                          \
+                                                                       \
+       for (_i = 0; _i < ARRAY_SIZE(_map); _i++)                       \
+               if ((_in) & _map[_i]) {                                 \
+                       (_out) |= 1 << _i;                              \
+                       (_in) &= ~_map[_i];                             \
+               }                                                       \
+       (_out);                                                         \
+})
+
+#define map_defined(_map)                                              \
+({                                                                     \
+       unsigned _in = ~0;                                              \
+                                                                       \
+       map_flags_rev(_map, _in);                                       \
+})
+
 #endif /* _BCACHEFS_UTIL_H */