Commit 7a4a8661 authored by Kent Overstreet's avatar Kent Overstreet
Browse files

bcachefs: Implement fileattr_(get|set)

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: default avatarKent Overstreet <kent.overstreet@linux.dev>
parent 4ede80a9
Loading
Loading
Loading
Loading
+0 −217
Original line number Diff line number Diff line
@@ -21,206 +21,6 @@
#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);
+0 −75
Original line number Diff line number Diff line
@@ -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);

+173 −0
Original line number Diff line number Diff line
@@ -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 = {
+38 −0
Original line number Diff line number Diff line
@@ -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 */