Merge branch 'miklos.fileattr' of git://git.kernel.org/pub/scm/linux/kernel/git/viro/vfs

Pull fileattr conversion updates from Miklos Szeredi via Al Viro:
 "This splits the handling of FS_IOC_[GS]ETFLAGS from ->ioctl() into a
  separate method.

  The interface is reasonably uniform across the filesystems that
  support it and gives nice boilerplate removal"

* 'miklos.fileattr' of git://git.kernel.org/pub/scm/linux/kernel/git/viro/vfs: (23 commits)
  ovl: remove unneeded ioctls
  fuse: convert to fileattr
  fuse: add internal open/release helpers
  fuse: unsigned open flags
  fuse: move ioctl to separate source file
  vfs: remove unused ioctl helpers
  ubifs: convert to fileattr
  reiserfs: convert to fileattr
  ocfs2: convert to fileattr
  nilfs2: convert to fileattr
  jfs: convert to fileattr
  hfsplus: convert to fileattr
  efivars: convert to fileattr
  xfs: convert to fileattr
  orangefs: convert to fileattr
  gfs2: convert to fileattr
  f2fs: convert to fileattr
  ext4: convert to fileattr
  ext2: convert to fileattr
  btrfs: convert to fileattr
  ...
This commit is contained in:
Linus Torvalds 2021-04-27 11:18:24 -07:00
commit a4f7fae101
71 changed files with 1738 additions and 2021 deletions

View File

@ -80,13 +80,16 @@ prototypes::
struct file *, unsigned open_flag,
umode_t create_mode);
int (*tmpfile) (struct inode *, struct dentry *, umode_t);
int (*fileattr_set)(struct user_namespace *mnt_userns,
struct dentry *dentry, struct fileattr *fa);
int (*fileattr_get)(struct dentry *dentry, struct fileattr *fa);
locking rules:
all may block
============ =============================================
============= =============================================
ops i_rwsem(inode)
============ =============================================
============= =============================================
lookup: shared
create: exclusive
link: exclusive (both)
@ -107,7 +110,9 @@ fiemap: no
update_time: no
atomic_open: shared (exclusive if O_CREAT is set in open flags)
tmpfile: no
============ =============================================
fileattr_get: no or exclusive
fileattr_set: exclusive
============= =============================================
Additionally, ->rmdir(), ->unlink() and ->rename() have ->i_rwsem

View File

@ -441,6 +441,9 @@ As of kernel 2.6.22, the following members are defined:
unsigned open_flag, umode_t create_mode);
int (*tmpfile) (struct user_namespace *, struct inode *, struct dentry *, umode_t);
int (*set_acl)(struct user_namespace *, struct inode *, struct posix_acl *, int);
int (*fileattr_set)(struct user_namespace *mnt_userns,
struct dentry *dentry, struct fileattr *fa);
int (*fileattr_get)(struct dentry *dentry, struct fileattr *fa);
};
Again, all methods are called without any locks being held, unless
@ -588,6 +591,18 @@ otherwise noted.
atomically creating, opening and unlinking a file in given
directory.
``fileattr_get``
called on ioctl(FS_IOC_GETFLAGS) and ioctl(FS_IOC_FSGETXATTR) to
retrieve miscellaneous file flags and attributes. Also called
before the relevant SET operation to check what is being changed
(in this case with i_rwsem locked exclusive). If unset, then
fall back to f_op->ioctl().
``fileattr_set``
called on ioctl(FS_IOC_SETFLAGS) and ioctl(FS_IOC_FSSETXATTR) to
change miscellaneous file flags and attributes. Callers hold
i_rwsem exclusive. If unset, then fall back to f_op->ioctl().
The Address Space Object
========================

View File

@ -3207,6 +3207,9 @@ void btrfs_update_inode_bytes(struct btrfs_inode *inode,
/* ioctl.c */
long btrfs_ioctl(struct file *file, unsigned int cmd, unsigned long arg);
long btrfs_compat_ioctl(struct file *file, unsigned int cmd, unsigned long arg);
int btrfs_fileattr_get(struct dentry *dentry, struct fileattr *fa);
int btrfs_fileattr_set(struct user_namespace *mnt_userns,
struct dentry *dentry, struct fileattr *fa);
int btrfs_ioctl_get_supported_features(void __user *arg);
void btrfs_sync_inode_flags_to_i_flags(struct inode *inode);
int __pure btrfs_is_empty_uuid(u8 *uuid);

View File

@ -10620,6 +10620,8 @@ static const struct inode_operations btrfs_dir_inode_operations = {
.set_acl = btrfs_set_acl,
.update_time = btrfs_update_time,
.tmpfile = btrfs_tmpfile,
.fileattr_get = btrfs_fileattr_get,
.fileattr_set = btrfs_fileattr_set,
};
static const struct file_operations btrfs_dir_file_operations = {
@ -10673,6 +10675,8 @@ static const struct inode_operations btrfs_file_inode_operations = {
.get_acl = btrfs_get_acl,
.set_acl = btrfs_set_acl,
.update_time = btrfs_update_time,
.fileattr_get = btrfs_fileattr_get,
.fileattr_set = btrfs_fileattr_set,
};
static const struct inode_operations btrfs_special_inode_operations = {
.getattr = btrfs_getattr,

View File

@ -26,6 +26,7 @@
#include <linux/btrfs.h>
#include <linux/uaccess.h>
#include <linux/iversion.h>
#include <linux/fileattr.h>
#include "ctree.h"
#include "disk-io.h"
#include "export.h"
@ -153,16 +154,6 @@ void btrfs_sync_inode_flags_to_i_flags(struct inode *inode)
new_fl);
}
static int btrfs_ioctl_getflags(struct file *file, void __user *arg)
{
struct btrfs_inode *binode = BTRFS_I(file_inode(file));
unsigned int flags = btrfs_inode_flags_to_fsflags(binode->flags);
if (copy_to_user(arg, &flags, sizeof(flags)))
return -EFAULT;
return 0;
}
/*
* Check if @flags are a supported and valid set of FS_*_FL flags and that
* the old and new flags are not conflicting
@ -201,9 +192,22 @@ static int check_fsflags_compatible(struct btrfs_fs_info *fs_info,
return 0;
}
static int btrfs_ioctl_setflags(struct file *file, void __user *arg)
/*
* Set flags/xflags from the internal inode flags. The remaining items of
* fsxattr are zeroed.
*/
int btrfs_fileattr_get(struct dentry *dentry, struct fileattr *fa)
{
struct inode *inode = file_inode(file);
struct btrfs_inode *binode = BTRFS_I(d_inode(dentry));
fileattr_fill_flags(fa, btrfs_inode_flags_to_fsflags(binode->flags));
return 0;
}
int btrfs_fileattr_set(struct user_namespace *mnt_userns,
struct dentry *dentry, struct fileattr *fa)
{
struct inode *inode = d_inode(dentry);
struct btrfs_fs_info *fs_info = btrfs_sb(inode->i_sb);
struct btrfs_inode *binode = BTRFS_I(inode);
struct btrfs_root *root = binode->root;
@ -213,34 +217,21 @@ static int btrfs_ioctl_setflags(struct file *file, void __user *arg)
const char *comp = NULL;
u32 binode_flags;
if (!inode_owner_or_capable(&init_user_ns, inode))
return -EPERM;
if (btrfs_root_readonly(root))
return -EROFS;
if (copy_from_user(&fsflags, arg, sizeof(fsflags)))
return -EFAULT;
if (fileattr_has_fsx(fa))
return -EOPNOTSUPP;
ret = mnt_want_write_file(file);
fsflags = btrfs_mask_fsflags_for_type(inode, fa->flags);
old_fsflags = btrfs_inode_flags_to_fsflags(binode->flags);
ret = check_fsflags(old_fsflags, fsflags);
if (ret)
return ret;
btrfs_inode_lock(inode, 0);
fsflags = btrfs_mask_fsflags_for_type(inode, fsflags);
old_fsflags = btrfs_inode_flags_to_fsflags(binode->flags);
ret = vfs_ioc_setflags_prepare(inode, old_fsflags, fsflags);
if (ret)
goto out_unlock;
ret = check_fsflags(old_fsflags, fsflags);
if (ret)
goto out_unlock;
ret = check_fsflags_compatible(fs_info, fsflags);
if (ret)
goto out_unlock;
return ret;
binode_flags = binode->flags;
if (fsflags & FS_SYNC_FL)
@ -263,6 +254,14 @@ static int btrfs_ioctl_setflags(struct file *file, void __user *arg)
binode_flags |= BTRFS_INODE_NOATIME;
else
binode_flags &= ~BTRFS_INODE_NOATIME;
/* If coming from FS_IOC_FSSETXATTR then skip unconverted flags */
if (!fa->flags_valid) {
/* 1 item for the inode */
trans = btrfs_start_transaction(root, 1);
goto update_flags;
}
if (fsflags & FS_DIRSYNC_FL)
binode_flags |= BTRFS_INODE_DIRSYNC;
else
@ -303,10 +302,8 @@ static int btrfs_ioctl_setflags(struct file *file, void __user *arg)
binode_flags |= BTRFS_INODE_NOCOMPRESS;
} else if (fsflags & FS_COMPR_FL) {
if (IS_SWAPFILE(inode)) {
ret = -ETXTBSY;
goto out_unlock;
}
if (IS_SWAPFILE(inode))
return -ETXTBSY;
binode_flags |= BTRFS_INODE_COMPRESS;
binode_flags &= ~BTRFS_INODE_NOCOMPRESS;
@ -323,10 +320,8 @@ static int btrfs_ioctl_setflags(struct file *file, void __user *arg)
* 2 for properties
*/
trans = btrfs_start_transaction(root, 3);
if (IS_ERR(trans)) {
ret = PTR_ERR(trans);
goto out_unlock;
}
if (IS_ERR(trans))
return PTR_ERR(trans);
if (comp) {
ret = btrfs_set_prop(trans, inode, "btrfs.compression", comp,
@ -344,6 +339,7 @@ static int btrfs_ioctl_setflags(struct file *file, void __user *arg)
}
}
update_flags:
binode->flags = binode_flags;
btrfs_sync_inode_flags_to_i_flags(inode);
inode_inc_iversion(inode);
@ -352,44 +348,9 @@ static int btrfs_ioctl_setflags(struct file *file, void __user *arg)
out_end_trans:
btrfs_end_transaction(trans);
out_unlock:
btrfs_inode_unlock(inode, 0);
mnt_drop_write_file(file);
return ret;
}
/*
* Translate btrfs internal inode flags to xflags as expected by the
* FS_IOC_FSGETXATT ioctl. Filter only the supported ones, unknown flags are
* silently dropped.
*/
static unsigned int btrfs_inode_flags_to_xflags(unsigned int flags)
{
unsigned int xflags = 0;
if (flags & BTRFS_INODE_APPEND)
xflags |= FS_XFLAG_APPEND;
if (flags & BTRFS_INODE_IMMUTABLE)
xflags |= FS_XFLAG_IMMUTABLE;
if (flags & BTRFS_INODE_NOATIME)
xflags |= FS_XFLAG_NOATIME;
if (flags & BTRFS_INODE_NODUMP)
xflags |= FS_XFLAG_NODUMP;
if (flags & BTRFS_INODE_SYNC)
xflags |= FS_XFLAG_SYNC;
return xflags;
}
/* Check if @flags are a supported and valid set of FS_XFLAGS_* flags */
static int check_xflags(unsigned int flags)
{
if (flags & ~(FS_XFLAG_APPEND | FS_XFLAG_IMMUTABLE | FS_XFLAG_NOATIME |
FS_XFLAG_NODUMP | FS_XFLAG_SYNC))
return -EOPNOTSUPP;
return 0;
}
bool btrfs_exclop_start(struct btrfs_fs_info *fs_info,
enum btrfs_exclusive_operation type)
{
@ -402,111 +363,6 @@ void btrfs_exclop_finish(struct btrfs_fs_info *fs_info)
sysfs_notify(&fs_info->fs_devices->fsid_kobj, NULL, "exclusive_operation");
}
/*
* Set the xflags from the internal inode flags. The remaining items of fsxattr
* are zeroed.
*/
static int btrfs_ioctl_fsgetxattr(struct file *file, void __user *arg)
{
struct btrfs_inode *binode = BTRFS_I(file_inode(file));
struct fsxattr fa;
simple_fill_fsxattr(&fa, btrfs_inode_flags_to_xflags(binode->flags));
if (copy_to_user(arg, &fa, sizeof(fa)))
return -EFAULT;
return 0;
}
static int btrfs_ioctl_fssetxattr(struct file *file, void __user *arg)
{
struct inode *inode = file_inode(file);
struct btrfs_inode *binode = BTRFS_I(inode);
struct btrfs_root *root = binode->root;
struct btrfs_trans_handle *trans;
struct fsxattr fa, old_fa;
unsigned old_flags;
unsigned old_i_flags;
int ret = 0;
if (!inode_owner_or_capable(&init_user_ns, inode))
return -EPERM;
if (btrfs_root_readonly(root))
return -EROFS;
if (copy_from_user(&fa, arg, sizeof(fa)))
return -EFAULT;
ret = check_xflags(fa.fsx_xflags);
if (ret)
return ret;
if (fa.fsx_extsize != 0 || fa.fsx_projid != 0 || fa.fsx_cowextsize != 0)
return -EOPNOTSUPP;
ret = mnt_want_write_file(file);
if (ret)
return ret;
btrfs_inode_lock(inode, 0);
old_flags = binode->flags;
old_i_flags = inode->i_flags;
simple_fill_fsxattr(&old_fa,
btrfs_inode_flags_to_xflags(binode->flags));
ret = vfs_ioc_fssetxattr_check(inode, &old_fa, &fa);
if (ret)
goto out_unlock;
if (fa.fsx_xflags & FS_XFLAG_SYNC)
binode->flags |= BTRFS_INODE_SYNC;
else
binode->flags &= ~BTRFS_INODE_SYNC;
if (fa.fsx_xflags & FS_XFLAG_IMMUTABLE)
binode->flags |= BTRFS_INODE_IMMUTABLE;
else
binode->flags &= ~BTRFS_INODE_IMMUTABLE;
if (fa.fsx_xflags & FS_XFLAG_APPEND)
binode->flags |= BTRFS_INODE_APPEND;
else
binode->flags &= ~BTRFS_INODE_APPEND;
if (fa.fsx_xflags & FS_XFLAG_NODUMP)
binode->flags |= BTRFS_INODE_NODUMP;
else
binode->flags &= ~BTRFS_INODE_NODUMP;
if (fa.fsx_xflags & FS_XFLAG_NOATIME)
binode->flags |= BTRFS_INODE_NOATIME;
else
binode->flags &= ~BTRFS_INODE_NOATIME;
/* 1 item for the inode */
trans = btrfs_start_transaction(root, 1);
if (IS_ERR(trans)) {
ret = PTR_ERR(trans);
goto out_unlock;
}
btrfs_sync_inode_flags_to_i_flags(inode);
inode_inc_iversion(inode);
inode->i_ctime = current_time(inode);
ret = btrfs_update_inode(trans, root, BTRFS_I(inode));
btrfs_end_transaction(trans);
out_unlock:
if (ret) {
binode->flags = old_flags;
inode->i_flags = old_i_flags;
}
btrfs_inode_unlock(inode, 0);
mnt_drop_write_file(file);
return ret;
}
static int btrfs_ioctl_getversion(struct file *file, int __user *arg)
{
struct inode *inode = file_inode(file);
@ -4932,10 +4788,6 @@ long btrfs_ioctl(struct file *file, unsigned int
void __user *argp = (void __user *)arg;
switch (cmd) {
case FS_IOC_GETFLAGS:
return btrfs_ioctl_getflags(file, argp);
case FS_IOC_SETFLAGS:
return btrfs_ioctl_setflags(file, argp);
case FS_IOC_GETVERSION:
return btrfs_ioctl_getversion(file, argp);
case FS_IOC_GETFSLABEL:
@ -5061,10 +4913,6 @@ long btrfs_ioctl(struct file *file, unsigned int
return btrfs_ioctl_get_features(fs_info, argp);
case BTRFS_IOC_SET_FEATURES:
return btrfs_ioctl_set_features(file, argp);
case FS_IOC_FSGETXATTR:
return btrfs_ioctl_fsgetxattr(file, argp);
case FS_IOC_FSSETXATTR:
return btrfs_ioctl_fssetxattr(file, argp);
case BTRFS_IOC_GET_SUBVOL_INFO:
return btrfs_ioctl_get_subvol_info(file, argp);
case BTRFS_IOC_GET_SUBVOL_ROOTREF:
@ -5084,12 +4932,6 @@ long btrfs_compat_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
* handling is necessary.
*/
switch (cmd) {
case FS_IOC32_GETFLAGS:
cmd = FS_IOC_GETFLAGS;
break;
case FS_IOC32_SETFLAGS:
cmd = FS_IOC_SETFLAGS;
break;
case FS_IOC32_GETVERSION:
cmd = FS_IOC_GETVERSION;
break;

View File

@ -18,6 +18,7 @@
#include <linux/fs_stack.h>
#include <linux/slab.h>
#include <linux/xattr.h>
#include <linux/fileattr.h>
#include <asm/unaligned.h>
#include "ecryptfs_kernel.h"
@ -1118,6 +1119,23 @@ out:
return rc;
}
static int ecryptfs_fileattr_get(struct dentry *dentry, struct fileattr *fa)
{
return vfs_fileattr_get(ecryptfs_dentry_to_lower(dentry), fa);
}
static int ecryptfs_fileattr_set(struct user_namespace *mnt_userns,
struct dentry *dentry, struct fileattr *fa)
{
struct dentry *lower_dentry = ecryptfs_dentry_to_lower(dentry);
int rc;
rc = vfs_fileattr_set(&init_user_ns, lower_dentry, fa);
fsstack_copy_attr_all(d_inode(dentry), d_inode(lower_dentry));
return rc;
}
const struct inode_operations ecryptfs_symlink_iops = {
.get_link = ecryptfs_get_link,
.permission = ecryptfs_permission,
@ -1139,6 +1157,8 @@ const struct inode_operations ecryptfs_dir_iops = {
.permission = ecryptfs_permission,
.setattr = ecryptfs_setattr,
.listxattr = ecryptfs_listxattr,
.fileattr_get = ecryptfs_fileattr_get,
.fileattr_set = ecryptfs_fileattr_set,
};
const struct inode_operations ecryptfs_main_iops = {
@ -1146,6 +1166,8 @@ const struct inode_operations ecryptfs_main_iops = {
.setattr = ecryptfs_setattr,
.getattr = ecryptfs_getattr,
.listxattr = ecryptfs_listxattr,
.fileattr_get = ecryptfs_fileattr_get,
.fileattr_set = ecryptfs_fileattr_set,
};
static int ecryptfs_xattr_get(const struct xattr_handler *handler,

View File

@ -106,86 +106,9 @@ out_free:
return size;
}
static inline unsigned int efivarfs_getflags(struct inode *inode)
{
unsigned int i_flags;
unsigned int flags = 0;
i_flags = inode->i_flags;
if (i_flags & S_IMMUTABLE)
flags |= FS_IMMUTABLE_FL;
return flags;
}
static int
efivarfs_ioc_getxflags(struct file *file, void __user *arg)
{
struct inode *inode = file->f_mapping->host;
unsigned int flags = efivarfs_getflags(inode);
if (copy_to_user(arg, &flags, sizeof(flags)))
return -EFAULT;
return 0;
}
static int
efivarfs_ioc_setxflags(struct file *file, void __user *arg)
{
struct inode *inode = file->f_mapping->host;
unsigned int flags;
unsigned int i_flags = 0;
unsigned int oldflags = efivarfs_getflags(inode);
int error;
if (!inode_owner_or_capable(&init_user_ns, inode))
return -EACCES;
if (copy_from_user(&flags, arg, sizeof(flags)))
return -EFAULT;
if (flags & ~FS_IMMUTABLE_FL)
return -EOPNOTSUPP;
if (flags & FS_IMMUTABLE_FL)
i_flags |= S_IMMUTABLE;
error = mnt_want_write_file(file);
if (error)
return error;
inode_lock(inode);
error = vfs_ioc_setflags_prepare(inode, oldflags, flags);
if (error)
goto out;
inode_set_flags(inode, i_flags, S_IMMUTABLE);
out:
inode_unlock(inode);
mnt_drop_write_file(file);
return error;
}
static long
efivarfs_file_ioctl(struct file *file, unsigned int cmd, unsigned long p)
{
void __user *arg = (void __user *)p;
switch (cmd) {
case FS_IOC_GETFLAGS:
return efivarfs_ioc_getxflags(file, arg);
case FS_IOC_SETFLAGS:
return efivarfs_ioc_setxflags(file, arg);
}
return -ENOTTY;
}
const struct file_operations efivarfs_file_operations = {
.open = simple_open,
.read = efivarfs_file_read,
.write = efivarfs_file_write,
.llseek = no_llseek,
.unlocked_ioctl = efivarfs_file_ioctl,
};

View File

@ -10,9 +10,12 @@
#include <linux/kmemleak.h>
#include <linux/slab.h>
#include <linux/uuid.h>
#include <linux/fileattr.h>
#include "internal.h"
static const struct inode_operations efivarfs_file_inode_operations;
struct inode *efivarfs_get_inode(struct super_block *sb,
const struct inode *dir, int mode,
dev_t dev, bool is_removable)
@ -26,6 +29,7 @@ struct inode *efivarfs_get_inode(struct super_block *sb,
inode->i_flags = is_removable ? 0 : S_IMMUTABLE;
switch (mode & S_IFMT) {
case S_IFREG:
inode->i_op = &efivarfs_file_inode_operations;
inode->i_fop = &efivarfs_file_operations;
break;
case S_IFDIR:
@ -138,3 +142,43 @@ const struct inode_operations efivarfs_dir_inode_operations = {
.unlink = efivarfs_unlink,
.create = efivarfs_create,
};
static int
efivarfs_fileattr_get(struct dentry *dentry, struct fileattr *fa)
{
unsigned int i_flags;
unsigned int flags = 0;
i_flags = d_inode(dentry)->i_flags;
if (i_flags & S_IMMUTABLE)
flags |= FS_IMMUTABLE_FL;
fileattr_fill_flags(fa, flags);
return 0;
}
static int
efivarfs_fileattr_set(struct user_namespace *mnt_userns,
struct dentry *dentry, struct fileattr *fa)
{
unsigned int i_flags = 0;
if (fileattr_has_fsx(fa))
return -EOPNOTSUPP;
if (fa->flags & ~FS_IMMUTABLE_FL)
return -EOPNOTSUPP;
if (fa->flags & FS_IMMUTABLE_FL)
i_flags |= S_IMMUTABLE;
inode_set_flags(d_inode(dentry), i_flags, S_IMMUTABLE);
return 0;
}
static const struct inode_operations efivarfs_file_inode_operations = {
.fileattr_get = efivarfs_fileattr_get,
.fileattr_set = efivarfs_fileattr_set,
};

View File

@ -283,8 +283,6 @@ static inline __u32 ext2_mask_flags(umode_t mode, __u32 flags)
/*
* ioctl commands
*/
#define EXT2_IOC_GETFLAGS FS_IOC_GETFLAGS
#define EXT2_IOC_SETFLAGS FS_IOC_SETFLAGS
#define EXT2_IOC_GETVERSION FS_IOC_GETVERSION
#define EXT2_IOC_SETVERSION FS_IOC_SETVERSION
#define EXT2_IOC_GETRSVSZ _IOR('f', 5, long)
@ -293,8 +291,6 @@ static inline __u32 ext2_mask_flags(umode_t mode, __u32 flags)
/*
* ioctl commands in 32 bit emulation
*/
#define EXT2_IOC32_GETFLAGS FS_IOC32_GETFLAGS
#define EXT2_IOC32_SETFLAGS FS_IOC32_SETFLAGS
#define EXT2_IOC32_GETVERSION FS_IOC32_GETVERSION
#define EXT2_IOC32_SETVERSION FS_IOC32_SETVERSION
@ -772,6 +768,9 @@ extern int ext2_fiemap(struct inode *inode, struct fiemap_extent_info *fieinfo,
u64 start, u64 len);
/* ioctl.c */
extern int ext2_fileattr_get(struct dentry *dentry, struct fileattr *fa);
extern int ext2_fileattr_set(struct user_namespace *mnt_userns,
struct dentry *dentry, struct fileattr *fa);
extern long ext2_ioctl(struct file *, unsigned int, unsigned long);
extern long ext2_compat_ioctl(struct file *, unsigned int, unsigned long);

View File

@ -204,4 +204,6 @@ const struct inode_operations ext2_file_inode_operations = {
.get_acl = ext2_get_acl,
.set_acl = ext2_set_acl,
.fiemap = ext2_fiemap,
.fileattr_get = ext2_fileattr_get,
.fileattr_set = ext2_fileattr_set,
};

View File

@ -16,69 +16,51 @@
#include <linux/mount.h>
#include <asm/current.h>
#include <linux/uaccess.h>
#include <linux/fileattr.h>
int ext2_fileattr_get(struct dentry *dentry, struct fileattr *fa)
{
struct ext2_inode_info *ei = EXT2_I(d_inode(dentry));
fileattr_fill_flags(fa, ei->i_flags & EXT2_FL_USER_VISIBLE);
return 0;
}
int ext2_fileattr_set(struct user_namespace *mnt_userns,
struct dentry *dentry, struct fileattr *fa)
{
struct inode *inode = d_inode(dentry);
struct ext2_inode_info *ei = EXT2_I(inode);
if (fileattr_has_fsx(fa))
return -EOPNOTSUPP;
/* Is it quota file? Do not allow user to mess with it */
if (IS_NOQUOTA(inode))
return -EPERM;
ei->i_flags = (ei->i_flags & ~EXT2_FL_USER_MODIFIABLE) |
(fa->flags & EXT2_FL_USER_MODIFIABLE);
ext2_set_inode_flags(inode);
inode->i_ctime = current_time(inode);
mark_inode_dirty(inode);
return 0;
}
long ext2_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
struct inode *inode = file_inode(filp);
struct ext2_inode_info *ei = EXT2_I(inode);
unsigned int flags;
unsigned short rsv_window_size;
int ret;
ext2_debug ("cmd = %u, arg = %lu\n", cmd, arg);
switch (cmd) {
case EXT2_IOC_GETFLAGS:
flags = ei->i_flags & EXT2_FL_USER_VISIBLE;
return put_user(flags, (int __user *) arg);
case EXT2_IOC_SETFLAGS: {
unsigned int oldflags;
ret = mnt_want_write_file(filp);
if (ret)
return ret;
if (!inode_owner_or_capable(&init_user_ns, inode)) {
ret = -EACCES;
goto setflags_out;
}
if (get_user(flags, (int __user *) arg)) {
ret = -EFAULT;
goto setflags_out;
}
flags = ext2_mask_flags(inode->i_mode, flags);
inode_lock(inode);
/* Is it quota file? Do not allow user to mess with it */
if (IS_NOQUOTA(inode)) {
inode_unlock(inode);
ret = -EPERM;
goto setflags_out;
}
oldflags = ei->i_flags;
ret = vfs_ioc_setflags_prepare(inode, oldflags, flags);
if (ret) {
inode_unlock(inode);
goto setflags_out;
}
flags = flags & EXT2_FL_USER_MODIFIABLE;
flags |= oldflags & ~EXT2_FL_USER_MODIFIABLE;
ei->i_flags = flags;
ext2_set_inode_flags(inode);
inode->i_ctime = current_time(inode);
inode_unlock(inode);
mark_inode_dirty(inode);
setflags_out:
mnt_drop_write_file(filp);
return ret;
}
case EXT2_IOC_GETVERSION:
return put_user(inode->i_generation, (int __user *) arg);
case EXT2_IOC_SETVERSION: {
@ -163,12 +145,6 @@ long ext2_compat_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
/* These are just misnamed, they actually get/put from/to user an int */
switch (cmd) {
case EXT2_IOC32_GETFLAGS:
cmd = EXT2_IOC_GETFLAGS;
break;
case EXT2_IOC32_SETFLAGS:
cmd = EXT2_IOC_SETFLAGS;
break;
case EXT2_IOC32_GETVERSION:
cmd = EXT2_IOC_GETVERSION;
break;

View File

@ -427,6 +427,8 @@ const struct inode_operations ext2_dir_inode_operations = {
.get_acl = ext2_get_acl,
.set_acl = ext2_set_acl,
.tmpfile = ext2_tmpfile,
.fileattr_get = ext2_fileattr_get,
.fileattr_set = ext2_fileattr_set,
};
const struct inode_operations ext2_special_inode_operations = {

View File

@ -472,15 +472,6 @@ struct flex_groups {
EXT4_VERITY_FL | \
EXT4_INLINE_DATA_FL)
/* Flags we can manipulate with through FS_IOC_FSSETXATTR */
#define EXT4_FL_XFLAG_VISIBLE (EXT4_SYNC_FL | \
EXT4_IMMUTABLE_FL | \
EXT4_APPEND_FL | \
EXT4_NODUMP_FL | \
EXT4_NOATIME_FL | \
EXT4_PROJINHERIT_FL | \
EXT4_DAX_FL)
/* Flags that should be inherited by new inodes from their parent. */
#define EXT4_FL_INHERITED (EXT4_SECRM_FL | EXT4_UNRM_FL | EXT4_COMPR_FL |\
EXT4_SYNC_FL | EXT4_NODUMP_FL | EXT4_NOATIME_FL |\
@ -2928,6 +2919,9 @@ extern int ext4_ind_remove_space(handle_t *handle, struct inode *inode,
/* ioctl.c */
extern long ext4_ioctl(struct file *, unsigned int, unsigned long);
extern long ext4_compat_ioctl(struct file *, unsigned int, unsigned long);
int ext4_fileattr_set(struct user_namespace *mnt_userns,
struct dentry *dentry, struct fileattr *fa);
int ext4_fileattr_get(struct dentry *dentry, struct fileattr *fa);
extern void ext4_reset_inode_seed(struct inode *inode);
/* migrate.c */

View File

@ -919,5 +919,7 @@ const struct inode_operations ext4_file_inode_operations = {
.get_acl = ext4_get_acl,
.set_acl = ext4_set_acl,
.fiemap = ext4_fiemap,
.fileattr_get = ext4_fileattr_get,
.fileattr_set = ext4_fileattr_set,
};

View File

@ -20,6 +20,7 @@
#include <linux/uaccess.h>
#include <linux/delay.h>
#include <linux/iversion.h>
#include <linux/fileattr.h>
#include "ext4_jbd2.h"
#include "ext4.h"
#include <linux/fsmap.h>
@ -344,11 +345,6 @@ static int ext4_ioctl_setflags(struct inode *inode,
goto flags_out;
oldflags = ei->i_flags;
err = vfs_ioc_setflags_prepare(inode, oldflags, flags);
if (err)
goto flags_out;
/*
* The JOURNAL_DATA flag can only be changed by
* the relevant capability.
@ -459,9 +455,8 @@ flags_out:
}
#ifdef CONFIG_QUOTA
static int ext4_ioctl_setproject(struct file *filp, __u32 projid)
static int ext4_ioctl_setproject(struct inode *inode, __u32 projid)
{
struct inode *inode = file_inode(filp);
struct super_block *sb = inode->i_sb;
struct ext4_inode_info *ei = EXT4_I(inode);
int err, rc;
@ -545,7 +540,7 @@ out_stop:
return err;
}
#else
static int ext4_ioctl_setproject(struct file *filp, __u32 projid)
static int ext4_ioctl_setproject(struct inode *inode, __u32 projid)
{
if (projid != EXT4_DEF_PROJID)
return -EOPNOTSUPP;
@ -553,56 +548,6 @@ static int ext4_ioctl_setproject(struct file *filp, __u32 projid)
}
#endif
/* Transfer internal flags to xflags */
static inline __u32 ext4_iflags_to_xflags(unsigned long iflags)
{
__u32 xflags = 0;
if (iflags & EXT4_SYNC_FL)
xflags |= FS_XFLAG_SYNC;
if (iflags & EXT4_IMMUTABLE_FL)
xflags |= FS_XFLAG_IMMUTABLE;
if (iflags & EXT4_APPEND_FL)
xflags |= FS_XFLAG_APPEND;
if (iflags & EXT4_NODUMP_FL)
xflags |= FS_XFLAG_NODUMP;
if (iflags & EXT4_NOATIME_FL)
xflags |= FS_XFLAG_NOATIME;
if (iflags & EXT4_PROJINHERIT_FL)
xflags |= FS_XFLAG_PROJINHERIT;
if (iflags & EXT4_DAX_FL)
xflags |= FS_XFLAG_DAX;
return xflags;
}
#define EXT4_SUPPORTED_FS_XFLAGS (FS_XFLAG_SYNC | FS_XFLAG_IMMUTABLE | \
FS_XFLAG_APPEND | FS_XFLAG_NODUMP | \
FS_XFLAG_NOATIME | FS_XFLAG_PROJINHERIT | \
FS_XFLAG_DAX)
/* Transfer xflags flags to internal */
static inline unsigned long ext4_xflags_to_iflags(__u32 xflags)
{
unsigned long iflags = 0;
if (xflags & FS_XFLAG_SYNC)
iflags |= EXT4_SYNC_FL;
if (xflags & FS_XFLAG_IMMUTABLE)
iflags |= EXT4_IMMUTABLE_FL;
if (xflags & FS_XFLAG_APPEND)
iflags |= EXT4_APPEND_FL;
if (xflags & FS_XFLAG_NODUMP)
iflags |= EXT4_NODUMP_FL;
if (xflags & FS_XFLAG_NOATIME)
iflags |= EXT4_NOATIME_FL;
if (xflags & FS_XFLAG_PROJINHERIT)
iflags |= EXT4_PROJINHERIT_FL;
if (xflags & FS_XFLAG_DAX)
iflags |= EXT4_DAX_FL;
return iflags;
}
static int ext4_shutdown(struct super_block *sb, unsigned long arg)
{
struct ext4_sb_info *sbi = EXT4_SB(sb);
@ -770,15 +715,52 @@ group_add_out:
return err;
}
static void ext4_fill_fsxattr(struct inode *inode, struct fsxattr *fa)
int ext4_fileattr_get(struct dentry *dentry, struct fileattr *fa)
{
struct inode *inode = d_inode(dentry);
struct ext4_inode_info *ei = EXT4_I(inode);
u32 flags = ei->i_flags & EXT4_FL_USER_VISIBLE;
simple_fill_fsxattr(fa, ext4_iflags_to_xflags(ei->i_flags &
EXT4_FL_USER_VISIBLE));
if (S_ISREG(inode->i_mode))
flags &= ~FS_PROJINHERIT_FL;
fileattr_fill_flags(fa, flags);
if (ext4_has_feature_project(inode->i_sb))
fa->fsx_projid = from_kprojid(&init_user_ns, ei->i_projid);
return 0;
}
int ext4_fileattr_set(struct user_namespace *mnt_userns,
struct dentry *dentry, struct fileattr *fa)
{
struct inode *inode = d_inode(dentry);
u32 flags = fa->flags;
int err = -EOPNOTSUPP;
ext4_fc_start_update(inode);
if (flags & ~EXT4_FL_USER_VISIBLE)
goto out;
/*
* chattr(1) grabs flags via GETFLAGS, modifies the result and
* passes that to SETFLAGS. So we cannot easily make SETFLAGS
* more restrictive than just silently masking off visible but
* not settable flags as we always did.
*/
flags &= EXT4_FL_USER_MODIFIABLE;
if (ext4_mask_flags(inode->i_mode, flags) != flags)
goto out;
err = ext4_ioctl_check_immutable(inode, fa->fsx_projid, flags);
if (err)
goto out;
err = ext4_ioctl_setflags(inode, flags);
if (err)
goto out;
err = ext4_ioctl_setproject(inode, fa->fsx_projid);
out:
ext4_fc_stop_update(inode);
return err;
}
/* So that the fiemap access checks can't overflow on 32 bit machines. */
@ -816,55 +798,13 @@ static long __ext4_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
struct inode *inode = file_inode(filp);
struct super_block *sb = inode->i_sb;
struct ext4_inode_info *ei = EXT4_I(inode);
struct user_namespace *mnt_userns = file_mnt_user_ns(filp);
unsigned int flags;
ext4_debug("cmd = %u, arg = %lu\n", cmd, arg);
switch (cmd) {
case FS_IOC_GETFSMAP:
return ext4_ioc_getfsmap(sb, (void __user *)arg);
case FS_IOC_GETFLAGS:
flags = ei->i_flags & EXT4_FL_USER_VISIBLE;
if (S_ISREG(inode->i_mode))
flags &= ~EXT4_PROJINHERIT_FL;
return put_user(flags, (int __user *) arg);
case FS_IOC_SETFLAGS: {
int err;
if (!inode_owner_or_capable(mnt_userns, inode))
return -EACCES;
if (get_user(flags, (int __user *) arg))
return -EFAULT;
if (flags & ~EXT4_FL_USER_VISIBLE)
return -EOPNOTSUPP;
/*
* chattr(1) grabs flags via GETFLAGS, modifies the result and
* passes that to SETFLAGS. So we cannot easily make SETFLAGS
* more restrictive than just silently masking off visible but
* not settable flags as we always did.
*/
flags &= EXT4_FL_USER_MODIFIABLE;
if (ext4_mask_flags(inode->i_mode, flags) != flags)
return -EOPNOTSUPP;
err = mnt_want_write_file(filp);
if (err)
return err;
inode_lock(inode);
err = ext4_ioctl_check_immutable(inode,
from_kprojid(&init_user_ns, ei->i_projid),
flags);
if (!err)
err = ext4_ioctl_setflags(inode, flags);
inode_unlock(inode);
mnt_drop_write_file(filp);
return err;
}
case EXT4_IOC_GETVERSION:
case EXT4_IOC_GETVERSION_OLD:
return put_user(inode->i_generation, (int __user *) arg);
@ -1246,60 +1186,6 @@ resizefs_out:
case EXT4_IOC_GET_ES_CACHE:
return ext4_ioctl_get_es_cache(filp, arg);
case FS_IOC_FSGETXATTR:
{
struct fsxattr fa;
ext4_fill_fsxattr(inode, &fa);
if (copy_to_user((struct fsxattr __user *)arg,
&fa, sizeof(fa)))
return -EFAULT;
return 0;
}
case FS_IOC_FSSETXATTR:
{
struct fsxattr fa, old_fa;
int err;
if (copy_from_user(&fa, (struct fsxattr __user *)arg,
sizeof(fa)))
return -EFAULT;
/* Make sure caller has proper permission */
if (!inode_owner_or_capable(mnt_userns, inode))
return -EACCES;
if (fa.fsx_xflags & ~EXT4_SUPPORTED_FS_XFLAGS)
return -EOPNOTSUPP;
flags = ext4_xflags_to_iflags(fa.fsx_xflags);
if (ext4_mask_flags(inode->i_mode, flags) != flags)
return -EOPNOTSUPP;
err = mnt_want_write_file(filp);
if (err)
return err;
inode_lock(inode);
ext4_fill_fsxattr(inode, &old_fa);
err = vfs_ioc_fssetxattr_check(inode, &old_fa, &fa);
if (err)
goto out;
flags = (ei->i_flags & ~EXT4_FL_XFLAG_VISIBLE) |
(flags & EXT4_FL_XFLAG_VISIBLE);
err = ext4_ioctl_check_immutable(inode, fa.fsx_projid, flags);
if (err)
goto out;
err = ext4_ioctl_setflags(inode, flags);
if (err)
goto out;
err = ext4_ioctl_setproject(filp, fa.fsx_projid);
out:
inode_unlock(inode);
mnt_drop_write_file(filp);
return err;
}
case EXT4_IOC_SHUTDOWN:
return ext4_shutdown(sb, arg);
@ -1340,12 +1226,6 @@ long ext4_compat_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
/* These are just misnamed, they actually get/put from/to user an int */
switch (cmd) {
case FS_IOC32_GETFLAGS:
cmd = FS_IOC_GETFLAGS;
break;
case FS_IOC32_SETFLAGS:
cmd = FS_IOC_SETFLAGS;
break;
case EXT4_IOC32_GETVERSION:
cmd = EXT4_IOC_GETVERSION;
break;
@ -1405,8 +1285,6 @@ long ext4_compat_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
case EXT4_IOC_CLEAR_ES_CACHE:
case EXT4_IOC_GETSTATE:
case EXT4_IOC_GET_ES_CACHE:
case FS_IOC_FSGETXATTR:
case FS_IOC_FSSETXATTR:
break;
default:
return -ENOIOCTLCMD;

View File

@ -4172,6 +4172,8 @@ const struct inode_operations ext4_dir_inode_operations = {
.get_acl = ext4_get_acl,
.set_acl = ext4_set_acl,
.fiemap = ext4_fiemap,
.fileattr_get = ext4_fileattr_get,
.fileattr_set = ext4_fileattr_set,
};
const struct inode_operations ext4_special_inode_operations = {

View File

@ -3194,6 +3194,9 @@ int f2fs_setattr(struct user_namespace *mnt_userns, struct dentry *dentry,
int f2fs_truncate_hole(struct inode *inode, pgoff_t pg_start, pgoff_t pg_end);
void f2fs_truncate_data_blocks_range(struct dnode_of_data *dn, int count);
int f2fs_precache_extents(struct inode *inode);
int f2fs_fileattr_get(struct dentry *dentry, struct fileattr *fa);
int f2fs_fileattr_set(struct user_namespace *mnt_userns,
struct dentry *dentry, struct fileattr *fa);
long f2fs_ioctl(struct file *filp, unsigned int cmd, unsigned long arg);
long f2fs_compat_ioctl(struct file *file, unsigned int cmd, unsigned long arg);
int f2fs_transfer_project_quota(struct inode *inode, kprojid_t kprojid);

View File

@ -22,6 +22,7 @@
#include <linux/file.h>
#include <linux/nls.h>
#include <linux/sched/signal.h>
#include <linux/fileattr.h>
#include "f2fs.h"
#include "node.h"
@ -990,6 +991,8 @@ const struct inode_operations f2fs_file_inode_operations = {
.set_acl = f2fs_set_acl,
.listxattr = f2fs_listxattr,
.fiemap = f2fs_fiemap,
.fileattr_get = f2fs_fileattr_get,
.fileattr_set = f2fs_fileattr_set,
};
static int fill_zero(struct inode *inode, pgoff_t index,
@ -1871,13 +1874,16 @@ static int f2fs_setflags_common(struct inode *inode, u32 iflags, u32 mask)
return 0;
}
/* FS_IOC_GETFLAGS and FS_IOC_SETFLAGS support */
/* FS_IOC_[GS]ETFLAGS and FS_IOC_FS[GS]ETXATTR support */
/*
* To make a new on-disk f2fs i_flag gettable via FS_IOC_GETFLAGS, add an entry
* for it to f2fs_fsflags_map[], and add its FS_*_FL equivalent to
* F2FS_GETTABLE_FS_FL. To also make it settable via FS_IOC_SETFLAGS, also add
* its FS_*_FL equivalent to F2FS_SETTABLE_FS_FL.
*
* Translating flags to fsx_flags value used by FS_IOC_FSGETXATTR and
* FS_IOC_FSSETXATTR is done by the VFS.
*/
static const struct {
@ -1952,67 +1958,6 @@ static inline u32 f2fs_fsflags_to_iflags(u32 fsflags)
return iflags;
}
static int f2fs_ioc_getflags(struct file *filp, unsigned long arg)
{
struct inode *inode = file_inode(filp);
struct f2fs_inode_info *fi = F2FS_I(inode);
u32 fsflags = f2fs_iflags_to_fsflags(fi->i_flags);
if (IS_ENCRYPTED(inode))
fsflags |= FS_ENCRYPT_FL;
if (IS_VERITY(inode))
fsflags |= FS_VERITY_FL;
if (f2fs_has_inline_data(inode) || f2fs_has_inline_dentry(inode))
fsflags |= FS_INLINE_DATA_FL;
if (is_inode_flag_set(inode, FI_PIN_FILE))
fsflags |= FS_NOCOW_FL;
fsflags &= F2FS_GETTABLE_FS_FL;
return put_user(fsflags, (int __user *)arg);
}
static int f2fs_ioc_setflags(struct file *filp, unsigned long arg)
{
struct inode *inode = file_inode(filp);
struct f2fs_inode_info *fi = F2FS_I(inode);
u32 fsflags, old_fsflags;
u32 iflags;
int ret;
if (!inode_owner_or_capable(&init_user_ns, inode))
return -EACCES;
if (get_user(fsflags, (int __user *)arg))
return -EFAULT;
if (fsflags & ~F2FS_GETTABLE_FS_FL)
return -EOPNOTSUPP;
fsflags &= F2FS_SETTABLE_FS_FL;
iflags = f2fs_fsflags_to_iflags(fsflags);
if (f2fs_mask_flags(inode->i_mode, iflags) != iflags)
return -EOPNOTSUPP;
ret = mnt_want_write_file(filp);
if (ret)
return ret;
inode_lock(inode);
old_fsflags = f2fs_iflags_to_fsflags(fi->i_flags);
ret = vfs_ioc_setflags_prepare(inode, old_fsflags, fsflags);
if (ret)
goto out;
ret = f2fs_setflags_common(inode, iflags,
f2fs_fsflags_to_iflags(F2FS_SETTABLE_FS_FL));
out:
inode_unlock(inode);
mnt_drop_write_file(filp);
return ret;
}
static int f2fs_ioc_getversion(struct file *filp, unsigned long arg)
{
struct inode *inode = file_inode(filp);
@ -3019,9 +2964,8 @@ int f2fs_transfer_project_quota(struct inode *inode, kprojid_t kprojid)
return err;
}
static int f2fs_ioc_setproject(struct file *filp, __u32 projid)
static int f2fs_ioc_setproject(struct inode *inode, __u32 projid)
{
struct inode *inode = file_inode(filp);
struct f2fs_inode_info *fi = F2FS_I(inode);
struct f2fs_sb_info *sbi = F2FS_I_SB(inode);
struct page *ipage;
@ -3082,7 +3026,7 @@ int f2fs_transfer_project_quota(struct inode *inode, kprojid_t kprojid)
return 0;
}
static int f2fs_ioc_setproject(struct file *filp, __u32 projid)
static int f2fs_ioc_setproject(struct inode *inode, __u32 projid)
{
if (projid != F2FS_DEF_PROJID)
return -EOPNOTSUPP;
@ -3090,123 +3034,55 @@ static int f2fs_ioc_setproject(struct file *filp, __u32 projid)
}
#endif
/* FS_IOC_FSGETXATTR and FS_IOC_FSSETXATTR support */
/*
* To make a new on-disk f2fs i_flag gettable via FS_IOC_FSGETXATTR and settable
* via FS_IOC_FSSETXATTR, add an entry for it to f2fs_xflags_map[], and add its
* FS_XFLAG_* equivalent to F2FS_SUPPORTED_XFLAGS.
*/
static const struct {
u32 iflag;
u32 xflag;
} f2fs_xflags_map[] = {
{ F2FS_SYNC_FL, FS_XFLAG_SYNC },
{ F2FS_IMMUTABLE_FL, FS_XFLAG_IMMUTABLE },
{ F2FS_APPEND_FL, FS_XFLAG_APPEND },
{ F2FS_NODUMP_FL, FS_XFLAG_NODUMP },
{ F2FS_NOATIME_FL, FS_XFLAG_NOATIME },
{ F2FS_PROJINHERIT_FL, FS_XFLAG_PROJINHERIT },
};
#define F2FS_SUPPORTED_XFLAGS ( \
FS_XFLAG_SYNC | \
FS_XFLAG_IMMUTABLE | \
FS_XFLAG_APPEND | \
FS_XFLAG_NODUMP | \
FS_XFLAG_NOATIME | \
FS_XFLAG_PROJINHERIT)
/* Convert f2fs on-disk i_flags to FS_IOC_FS{GET,SET}XATTR flags */
static inline u32 f2fs_iflags_to_xflags(u32 iflags)
{
u32 xflags = 0;
int i;
for (i = 0; i < ARRAY_SIZE(f2fs_xflags_map); i++)
if (iflags & f2fs_xflags_map[i].iflag)
xflags |= f2fs_xflags_map[i].xflag;
return xflags;
}
/* Convert FS_IOC_FS{GET,SET}XATTR flags to f2fs on-disk i_flags */
static inline u32 f2fs_xflags_to_iflags(u32 xflags)
{
u32 iflags = 0;
int i;
for (i = 0; i < ARRAY_SIZE(f2fs_xflags_map); i++)
if (xflags & f2fs_xflags_map[i].xflag)
iflags |= f2fs_xflags_map[i].iflag;
return iflags;
}
static void f2fs_fill_fsxattr(struct inode *inode, struct fsxattr *fa)
int f2fs_fileattr_get(struct dentry *dentry, struct fileattr *fa)
{
struct inode *inode = d_inode(dentry);
struct f2fs_inode_info *fi = F2FS_I(inode);
u32 fsflags = f2fs_iflags_to_fsflags(fi->i_flags);
simple_fill_fsxattr(fa, f2fs_iflags_to_xflags(fi->i_flags));
if (IS_ENCRYPTED(inode))
fsflags |= FS_ENCRYPT_FL;
if (IS_VERITY(inode))
fsflags |= FS_VERITY_FL;
if (f2fs_has_inline_data(inode) || f2fs_has_inline_dentry(inode))
fsflags |= FS_INLINE_DATA_FL;
if (is_inode_flag_set(inode, FI_PIN_FILE))
fsflags |= FS_NOCOW_FL;
fileattr_fill_flags(fa, fsflags & F2FS_GETTABLE_FS_FL);
if (f2fs_sb_has_project_quota(F2FS_I_SB(inode)))
fa->fsx_projid = from_kprojid(&init_user_ns, fi->i_projid);
}
static int f2fs_ioc_fsgetxattr(struct file *filp, unsigned long arg)
{
struct inode *inode = file_inode(filp);
struct fsxattr fa;
f2fs_fill_fsxattr(inode, &fa);
if (copy_to_user((struct fsxattr __user *)arg, &fa, sizeof(fa)))
return -EFAULT;
return 0;
}
static int f2fs_ioc_fssetxattr(struct file *filp, unsigned long arg)
int f2fs_fileattr_set(struct user_namespace *mnt_userns,
struct dentry *dentry, struct fileattr *fa)
{
struct inode *inode = file_inode(filp);
struct fsxattr fa, old_fa;
struct inode *inode = d_inode(dentry);
u32 fsflags = fa->flags, mask = F2FS_SETTABLE_FS_FL;
u32 iflags;
int err;
if (copy_from_user(&fa, (struct fsxattr __user *)arg, sizeof(fa)))
return -EFAULT;
/* Make sure caller has proper permission */
if (!inode_owner_or_capable(&init_user_ns, inode))
return -EACCES;
if (fa.fsx_xflags & ~F2FS_SUPPORTED_XFLAGS)
if (unlikely(f2fs_cp_error(F2FS_I_SB(inode))))
return -EIO;
if (!f2fs_is_checkpoint_ready(F2FS_I_SB(inode)))
return -ENOSPC;
if (fsflags & ~F2FS_GETTABLE_FS_FL)
return -EOPNOTSUPP;
fsflags &= F2FS_SETTABLE_FS_FL;
if (!fa->flags_valid)
mask &= FS_COMMON_FL;
iflags = f2fs_xflags_to_iflags(fa.fsx_xflags);
iflags = f2fs_fsflags_to_iflags(fsflags);
if (f2fs_mask_flags(inode->i_mode, iflags) != iflags)
return -EOPNOTSUPP;
err = mnt_want_write_file(filp);
if (err)
return err;
err = f2fs_setflags_common(inode, iflags, f2fs_fsflags_to_iflags(mask));
if (!err)
err = f2fs_ioc_setproject(inode, fa->fsx_projid);
inode_lock(inode);
f2fs_fill_fsxattr(inode, &old_fa);
err = vfs_ioc_fssetxattr_check(inode, &old_fa, &fa);
if (err)
goto out;
err = f2fs_setflags_common(inode, iflags,
f2fs_xflags_to_iflags(F2FS_SUPPORTED_XFLAGS));
if (err)
goto out;
err = f2fs_ioc_setproject(filp, fa.fsx_projid);
out:
inode_unlock(inode);
mnt_drop_write_file(filp);
return err;
}
@ -4233,10 +4109,6 @@ out:
static long __f2fs_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
switch (cmd) {
case FS_IOC_GETFLAGS:
return f2fs_ioc_getflags(filp, arg);
case FS_IOC_SETFLAGS:
return f2fs_ioc_setflags(filp, arg);
case FS_IOC_GETVERSION:
return f2fs_ioc_getversion(filp, arg);
case F2FS_IOC_START_ATOMIC_WRITE:
@ -4285,10 +4157,6 @@ static long __f2fs_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
return f2fs_ioc_flush_device(filp, arg);
case F2FS_IOC_GET_FEATURES:
return f2fs_ioc_get_features(filp, arg);
case FS_IOC_FSGETXATTR:
return f2fs_ioc_fsgetxattr(filp, arg);
case FS_IOC_FSSETXATTR:
return f2fs_ioc_fssetxattr(filp, arg);
case F2FS_IOC_GET_PIN_FILE:
return f2fs_ioc_get_pin_file(filp, arg);
case F2FS_IOC_SET_PIN_FILE:
@ -4518,12 +4386,6 @@ long f2fs_compat_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
return -ENOSPC;
switch (cmd) {
case FS_IOC32_GETFLAGS:
cmd = FS_IOC_GETFLAGS;
break;
case FS_IOC32_SETFLAGS:
cmd = FS_IOC_SETFLAGS;
break;
case FS_IOC32_GETVERSION:
cmd = FS_IOC_GETVERSION;
break;
@ -4552,8 +4414,6 @@ long f2fs_compat_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
case F2FS_IOC_DEFRAGMENT:
case F2FS_IOC_FLUSH_DEVICE:
case F2FS_IOC_GET_FEATURES:
case FS_IOC_FSGETXATTR:
case FS_IOC_FSSETXATTR:
case F2FS_IOC_GET_PIN_FILE:
case F2FS_IOC_SET_PIN_FILE:
case F2FS_IOC_PRECACHE_EXTENTS:

View File

@ -1327,6 +1327,8 @@ const struct inode_operations f2fs_dir_inode_operations = {
.set_acl = f2fs_set_acl,
.listxattr = f2fs_listxattr,
.fiemap = f2fs_fiemap,
.fileattr_get = f2fs_fileattr_get,
.fileattr_set = f2fs_fileattr_set,
};
const struct inode_operations f2fs_symlink_inode_operations = {

View File

@ -7,7 +7,7 @@ obj-$(CONFIG_FUSE_FS) += fuse.o
obj-$(CONFIG_CUSE) += cuse.o
obj-$(CONFIG_VIRTIO_FS) += virtiofs.o
fuse-y := dev.o dir.o file.o inode.o control.o xattr.o acl.o readdir.o
fuse-y := dev.o dir.o file.o inode.o control.o xattr.o acl.o readdir.o ioctl.o
fuse-$(CONFIG_FUSE_DAX) += dax.o
virtiofs-y := virtio_fs.o

View File

@ -508,7 +508,7 @@ static struct dentry *fuse_lookup(struct inode *dir, struct dentry *entry,
* 'mknod' + 'open' requests.
*/
static int fuse_create_open(struct inode *dir, struct dentry *entry,
struct file *file, unsigned flags,
struct file *file, unsigned int flags,
umode_t mode)
{
int err;
@ -1866,6 +1866,8 @@ static const struct inode_operations fuse_dir_inode_operations = {
.listxattr = fuse_listxattr,
.get_acl = fuse_get_acl,
.set_acl = fuse_set_acl,
.fileattr_get = fuse_fileattr_get,
.fileattr_set = fuse_fileattr_set,
};
static const struct file_operations fuse_dir_operations = {
@ -1886,6 +1888,8 @@ static const struct inode_operations fuse_common_inode_operations = {
.listxattr = fuse_listxattr,
.get_acl = fuse_get_acl,
.set_acl = fuse_set_acl,
.fileattr_get = fuse_fileattr_get,
.fileattr_set = fuse_fileattr_set,
};
static const struct inode_operations fuse_symlink_inode_operations = {

View File

@ -14,32 +14,20 @@
#include <linux/sched.h>
#include <linux/sched/signal.h>
#include <linux/module.h>
#include <linux/compat.h>
#include <linux/swap.h>
#include <linux/falloc.h>
#include <linux/uio.h>
#include <linux/fs.h>
static struct page **fuse_pages_alloc(unsigned int npages, gfp_t flags,
struct fuse_page_desc **desc)
{
struct page **pages;
pages = kzalloc(npages * (sizeof(struct page *) +
sizeof(struct fuse_page_desc)), flags);
*desc = (void *) (pages + npages);
return pages;
}
static int fuse_send_open(struct fuse_mount *fm, u64 nodeid, struct file *file,
int opcode, struct fuse_open_out *outargp)
static int fuse_send_open(struct fuse_mount *fm, u64 nodeid,
unsigned int open_flags, int opcode,
struct fuse_open_out *outargp)
{
struct fuse_open_in inarg;
FUSE_ARGS(args);
memset(&inarg, 0, sizeof(inarg));
inarg.flags = file->f_flags & ~(O_CREAT | O_EXCL | O_NOCTTY);
inarg.flags = open_flags & ~(O_CREAT | O_EXCL | O_NOCTTY);
if (!fm->fc->atomic_o_trunc)
inarg.flags &= ~O_TRUNC;
@ -136,8 +124,8 @@ static void fuse_file_put(struct fuse_file *ff, bool sync, bool isdir)
}
}
int fuse_do_open(struct fuse_mount *fm, u64 nodeid, struct file *file,
bool isdir)
struct fuse_file *fuse_file_open(struct fuse_mount *fm, u64 nodeid,
unsigned int open_flags, bool isdir)
{
struct fuse_conn *fc = fm->fc;
struct fuse_file *ff;
@ -145,7 +133,7 @@ int fuse_do_open(struct fuse_mount *fm, u64 nodeid, struct file *file,
ff = fuse_file_alloc(fm);
if (!ff)
return -ENOMEM;
return ERR_PTR(-ENOMEM);
ff->fh = 0;
/* Default for no-open */
@ -154,14 +142,14 @@ int fuse_do_open(struct fuse_mount *fm, u64 nodeid, struct file *file,
struct fuse_open_out outarg;
int err;
err = fuse_send_open(fm, nodeid, file, opcode, &outarg);
err = fuse_send_open(fm, nodeid, open_flags, opcode, &outarg);
if (!err) {
ff->fh = outarg.fh;
ff->open_flags = outarg.open_flags;
} else if (err != -ENOSYS) {
fuse_file_free(ff);
return err;
return ERR_PTR(err);
} else {
if (isdir)
fc->no_opendir = 1;
@ -174,9 +162,19 @@ int fuse_do_open(struct fuse_mount *fm, u64 nodeid, struct file *file,
ff->open_flags &= ~FOPEN_DIRECT_IO;
ff->nodeid = nodeid;
file->private_data = ff;
return 0;
return ff;
}
int fuse_do_open(struct fuse_mount *fm, u64 nodeid, struct file *file,
bool isdir)
{
struct fuse_file *ff = fuse_file_open(fm, nodeid, file->f_flags, isdir);
if (!IS_ERR(ff))
file->private_data = ff;
return PTR_ERR_OR_ZERO(ff);
}
EXPORT_SYMBOL_GPL(fuse_do_open);
@ -268,7 +266,7 @@ out:
}
static void fuse_prepare_release(struct fuse_inode *fi, struct fuse_file *ff,
int flags, int opcode)
unsigned int flags, int opcode)
{
struct fuse_conn *fc = ff->fm->fc;
struct fuse_release_args *ra = ff->release_args;
@ -297,22 +295,21 @@ static void fuse_prepare_release(struct fuse_inode *fi, struct fuse_file *ff,
ra->args.nocreds = true;
}
void fuse_release_common(struct file *file, bool isdir)
void fuse_file_release(struct inode *inode, struct fuse_file *ff,
unsigned int open_flags, fl_owner_t id, bool isdir)
{
struct fuse_inode *fi = get_fuse_inode(file_inode(file));
struct fuse_file *ff = file->private_data;
struct fuse_inode *fi = get_fuse_inode(inode);
struct fuse_release_args *ra = ff->release_args;
int opcode = isdir ? FUSE_RELEASEDIR : FUSE_RELEASE;
fuse_prepare_release(fi, ff, file->f_flags, opcode);
fuse_prepare_release(fi, ff, open_flags, opcode);
if (ff->flock) {
ra->inarg.release_flags |= FUSE_RELEASE_FLOCK_UNLOCK;
ra->inarg.lock_owner = fuse_lock_owner_id(ff->fm->fc,
(fl_owner_t) file);
ra->inarg.lock_owner = fuse_lock_owner_id(ff->fm->fc, id);
}
/* Hold inode until release is finished */
ra->inode = igrab(file_inode(file));
ra->inode = igrab(inode);
/*
* Normally this will send the RELEASE request, however if
@ -326,6 +323,12 @@ void fuse_release_common(struct file *file, bool isdir)
fuse_file_put(ff, ff->fm->fc->destroy, isdir);
}
void fuse_release_common(struct file *file, bool isdir)
{
fuse_file_release(file_inode(file), file->private_data, file->f_flags,
(fl_owner_t) file, isdir);
}
static int fuse_open(struct inode *inode, struct file *file)
{
return fuse_open_common(inode, file, false);
@ -345,7 +348,8 @@ static int fuse_release(struct inode *inode, struct file *file)
return 0;
}
void fuse_sync_release(struct fuse_inode *fi, struct fuse_file *ff, int flags)
void fuse_sync_release(struct fuse_inode *fi, struct fuse_file *ff,
unsigned int flags)
{
WARN_ON(refcount_read(&ff->count) > 1);
fuse_prepare_release(fi, ff, flags, FUSE_RELEASE);
@ -1346,16 +1350,6 @@ out:
return written ? written : err;
}
static inline void fuse_page_descs_length_init(struct fuse_page_desc *descs,
unsigned int index,
unsigned int nr_pages)
{
int i;
for (i = index; i < index + nr_pages; i++)
descs[i].length = PAGE_SIZE - descs[i].offset;
}
static inline unsigned long fuse_get_user_addr(const struct iov_iter *ii)
{
return (unsigned long)ii->iov->iov_base + ii->iov_offset;
@ -2636,363 +2630,6 @@ static loff_t fuse_file_llseek(struct file *file, loff_t offset, int whence)
return retval;
}
/*
* CUSE servers compiled on 32bit broke on 64bit kernels because the
* ABI was defined to be 'struct iovec' which is different on 32bit
* and 64bit. Fortunately we can determine which structure the server
* used from the size of the reply.
*/
static int fuse_copy_ioctl_iovec_old(struct iovec *dst, void *src,
size_t transferred, unsigned count,
bool is_compat)
{
#ifdef CONFIG_COMPAT
if (count * sizeof(struct compat_iovec) == transferred) {
struct compat_iovec *ciov = src;
unsigned i;
/*
* With this interface a 32bit server cannot support
* non-compat (i.e. ones coming from 64bit apps) ioctl
* requests
*/
if (!is_compat)
return -EINVAL;
for (i = 0; i < count; i++) {
dst[i].iov_base = compat_ptr(ciov[i].iov_base);
dst[i].iov_len = ciov[i].iov_len;
}
return 0;
}
#endif
if (count * sizeof(struct iovec) != transferred)
return -EIO;
memcpy(dst, src, transferred);
return 0;
}
/* Make sure iov_length() won't overflow */
static int fuse_verify_ioctl_iov(struct fuse_conn *fc, struct iovec *iov,
size_t count)
{
size_t n;
u32 max = fc->max_pages << PAGE_SHIFT;
for (n = 0; n < count; n++, iov++) {
if (iov->iov_len > (size_t) max)
return -ENOMEM;
max -= iov->iov_len;
}
return 0;
}
static int fuse_copy_ioctl_iovec(struct fuse_conn *fc, struct iovec *dst,
void *src, size_t transferred, unsigned count,
bool is_compat)
{
unsigned i;
struct fuse_ioctl_iovec *fiov = src;
if (fc->minor < 16) {
return fuse_copy_ioctl_iovec_old(dst, src, transferred,
count, is_compat);
}
if (count * sizeof(struct fuse_ioctl_iovec) != transferred)
return -EIO;
for (i = 0; i < count; i++) {
/* Did the server supply an inappropriate value? */
if (fiov[i].base != (unsigned long) fiov[i].base ||
fiov[i].len != (unsigned long) fiov[i].len)
return -EIO;
dst[i].iov_base = (void __user *) (unsigned long) fiov[i].base;
dst[i].iov_len = (size_t) fiov[i].len;
#ifdef CONFIG_COMPAT
if (is_compat &&
(ptr_to_compat(dst[i].iov_base) != fiov[i].base ||
(compat_size_t) dst[i].iov_len != fiov[i].len))
return -EIO;
#endif
}
return 0;
}
/*
* For ioctls, there is no generic way to determine how much memory
* needs to be read and/or written. Furthermore, ioctls are allowed
* to dereference the passed pointer, so the parameter requires deep
* copying but FUSE has no idea whatsoever about what to copy in or
* out.
*
* This is solved by allowing FUSE server to retry ioctl with
* necessary in/out iovecs. Let's assume the ioctl implementation
* needs to read in the following structure.
*
* struct a {
* char *buf;
* size_t buflen;
* }
*
* On the first callout to FUSE server, inarg->in_size and
* inarg->out_size will be NULL; then, the server completes the ioctl
* with FUSE_IOCTL_RETRY set in out->flags, out->in_iovs set to 1 and
* the actual iov array to
*
* { { .iov_base = inarg.arg, .iov_len = sizeof(struct a) } }
*
* which tells FUSE to copy in the requested area and retry the ioctl.
* On the second round, the server has access to the structure and
* from that it can tell what to look for next, so on the invocation,
* it sets FUSE_IOCTL_RETRY, out->in_iovs to 2 and iov array to
*
* { { .iov_base = inarg.arg, .iov_len = sizeof(struct a) },
* { .iov_base = a.buf, .iov_len = a.buflen } }
*
* FUSE will copy both struct a and the pointed buffer from the
* process doing the ioctl and retry ioctl with both struct a and the
* buffer.
*
* This time, FUSE server has everything it needs and completes ioctl
* without FUSE_IOCTL_RETRY which finishes the ioctl call.
*
* Copying data out works the same way.
*
* Note that if FUSE_IOCTL_UNRESTRICTED is clear, the kernel
* automatically initializes in and out iovs by decoding @cmd with
* _IOC_* macros and the server is not allowed to request RETRY. This
* limits ioctl data transfers to well-formed ioctls and is the forced
* behavior for all FUSE servers.
*/
long fuse_do_ioctl(struct file *file, unsigned int cmd, unsigned long arg,
unsigned int flags)
{
struct fuse_file *ff = file->private_data;
struct fuse_mount *fm = ff->fm;
struct fuse_ioctl_in inarg = {
.fh = ff->fh,
.cmd = cmd,
.arg = arg,
.flags = flags
};
struct fuse_ioctl_out outarg;
struct iovec *iov_page = NULL;
struct iovec *in_iov = NULL, *out_iov = NULL;
unsigned int in_iovs = 0, out_iovs = 0, max_pages;
size_t in_size, out_size, c;
ssize_t transferred;
int err, i;
struct iov_iter ii;
struct fuse_args_pages ap = {};
#if BITS_PER_LONG == 32
inarg.flags |= FUSE_IOCTL_32BIT;
#else
if (flags & FUSE_IOCTL_COMPAT) {
inarg.flags |= FUSE_IOCTL_32BIT;
#ifdef CONFIG_X86_X32
if (in_x32_syscall())
inarg.flags |= FUSE_IOCTL_COMPAT_X32;
#endif
}
#endif
/* assume all the iovs returned by client always fits in a page */
BUILD_BUG_ON(sizeof(struct fuse_ioctl_iovec) * FUSE_IOCTL_MAX_IOV > PAGE_SIZE);
err = -ENOMEM;
ap.pages = fuse_pages_alloc(fm->fc->max_pages, GFP_KERNEL, &ap.descs);
iov_page = (struct iovec *) __get_free_page(GFP_KERNEL);
if (!ap.pages || !iov_page)
goto out;
fuse_page_descs_length_init(ap.descs, 0, fm->fc->max_pages);
/*
* If restricted, initialize IO parameters as encoded in @cmd.
* RETRY from server is not allowed.
*/
if (!(flags & FUSE_IOCTL_UNRESTRICTED)) {
struct iovec *iov = iov_page;
iov->iov_base = (void __user *)arg;
switch (cmd) {
case FS_IOC_GETFLAGS:
case FS_IOC_SETFLAGS:
iov->iov_len = sizeof(int);
break;
default:
iov->iov_len = _IOC_SIZE(cmd);
break;
}
if (_IOC_DIR(cmd) & _IOC_WRITE) {
in_iov = iov;
in_iovs = 1;
}
if (_IOC_DIR(cmd) & _IOC_READ) {
out_iov = iov;
out_iovs = 1;
}
}
retry:
inarg.in_size = in_size = iov_length(in_iov, in_iovs);
inarg.out_size = out_size = iov_length(out_iov, out_iovs);
/*
* Out data can be used either for actual out data or iovs,
* make sure there always is at least one page.
*/
out_size = max_t(size_t, out_size, PAGE_SIZE);
max_pages = DIV_ROUND_UP(max(in_size, out_size), PAGE_SIZE);
/* make sure there are enough buffer pages and init request with them */
err = -ENOMEM;
if (max_pages > fm->fc->max_pages)
goto out;
while (ap.num_pages < max_pages) {
ap.pages[ap.num_pages] = alloc_page(GFP_KERNEL | __GFP_HIGHMEM);
if (!ap.pages[ap.num_pages])
goto out;
ap.num_pages++;
}
/* okay, let's send it to the client */
ap.args.opcode = FUSE_IOCTL;
ap.args.nodeid = ff->nodeid;
ap.args.in_numargs = 1;
ap.args.in_args[0].size = sizeof(inarg);
ap.args.in_args[0].value = &inarg;
if (in_size) {
ap.args.in_numargs++;
ap.args.in_args[1].size = in_size;
ap.args.in_pages = true;
err = -EFAULT;
iov_iter_init(&ii, WRITE, in_iov, in_iovs, in_size);
for (i = 0; iov_iter_count(&ii) && !WARN_ON(i >= ap.num_pages); i++) {
c = copy_page_from_iter(ap.pages[i], 0, PAGE_SIZE, &ii);
if (c != PAGE_SIZE && iov_iter_count(&ii))
goto out;
}
}
ap.args.out_numargs = 2;
ap.args.out_args[0].size = sizeof(outarg);
ap.args.out_args[0].value = &outarg;
ap.args.out_args[1].size = out_size;
ap.args.out_pages = true;
ap.args.out_argvar = true;
transferred = fuse_simple_request(fm, &ap.args);
err = transferred;
if (transferred < 0)
goto out;
/* did it ask for retry? */
if (outarg.flags & FUSE_IOCTL_RETRY) {
void *vaddr;
/* no retry if in restricted mode */
err = -EIO;
if (!(flags & FUSE_IOCTL_UNRESTRICTED))
goto out;
in_iovs = outarg.in_iovs;
out_iovs = outarg.out_iovs;
/*
* Make sure things are in boundary, separate checks
* are to protect against overflow.
*/
err = -ENOMEM;
if (in_iovs > FUSE_IOCTL_MAX_IOV ||
out_iovs > FUSE_IOCTL_MAX_IOV ||
in_iovs + out_iovs > FUSE_IOCTL_MAX_IOV)
goto out;
vaddr = kmap_atomic(ap.pages[0]);
err = fuse_copy_ioctl_iovec(fm->fc, iov_page, vaddr,
transferred, in_iovs + out_iovs,
(flags & FUSE_IOCTL_COMPAT) != 0);
kunmap_atomic(vaddr);
if (err)
goto out;
in_iov = iov_page;
out_iov = in_iov + in_iovs;
err = fuse_verify_ioctl_iov(fm->fc, in_iov, in_iovs);
if (err)
goto out;
err = fuse_verify_ioctl_iov(fm->fc, out_iov, out_iovs);
if (err)
goto out;
goto retry;
}
err = -EIO;
if (transferred > inarg.out_size)
goto out;
err = -EFAULT;
iov_iter_init(&ii, READ, out_iov, out_iovs, transferred);
for (i = 0; iov_iter_count(&ii) && !WARN_ON(i >= ap.num_pages); i++) {
c = copy_page_to_iter(ap.pages[i], 0, PAGE_SIZE, &ii);
if (c != PAGE_SIZE && iov_iter_count(&ii))
goto out;
}
err = 0;
out:
free_page((unsigned long) iov_page);
while (ap.num_pages)
__free_page(ap.pages[--ap.num_pages]);
kfree(ap.pages);
return err ? err : outarg.result;
}
EXPORT_SYMBOL_GPL(fuse_do_ioctl);
long fuse_ioctl_common(struct file *file, unsigned int cmd,
unsigned long arg, unsigned int flags)
{
struct inode *inode = file_inode(file);
struct fuse_conn *fc = get_fuse_conn(inode);
if (!fuse_allow_current_process(fc))
return -EACCES;
if (fuse_is_bad(inode))
return -EIO;
return fuse_do_ioctl(file, cmd, arg, flags);
}
static long fuse_file_ioctl(struct file *file, unsigned int cmd,
unsigned long arg)
{
return fuse_ioctl_common(file, cmd, arg, 0);
}
static long fuse_file_compat_ioctl(struct file *file, unsigned int cmd,
unsigned long arg)
{
return fuse_ioctl_common(file, cmd, arg, FUSE_IOCTL_COMPAT);
}
/*
* All files which have been polled are linked to RB tree
* fuse_conn->polled_files which is indexed by kh. Walk the tree and

View File

@ -872,6 +872,28 @@ static inline bool fuse_is_bad(struct inode *inode)
return unlikely(test_bit(FUSE_I_BAD, &get_fuse_inode(inode)->state));
}
static inline struct page **fuse_pages_alloc(unsigned int npages, gfp_t flags,
struct fuse_page_desc **desc)
{
struct page **pages;
pages = kzalloc(npages * (sizeof(struct page *) +
sizeof(struct fuse_page_desc)), flags);
*desc = (void *) (pages + npages);
return pages;
}
static inline void fuse_page_descs_length_init(struct fuse_page_desc *descs,
unsigned int index,
unsigned int nr_pages)
{
int i;
for (i = index; i < index + nr_pages; i++)
descs[i].length = PAGE_SIZE - descs[i].offset;
}
/** Device operations */
extern const struct file_operations fuse_dev_operations;
@ -932,7 +954,8 @@ struct fuse_file *fuse_file_alloc(struct fuse_mount *fm);
void fuse_file_free(struct fuse_file *ff);
void fuse_finish_open(struct inode *inode, struct file *file);
void fuse_sync_release(struct fuse_inode *fi, struct fuse_file *ff, int flags);
void fuse_sync_release(struct fuse_inode *fi, struct fuse_file *ff,
unsigned int flags);
/**
* Send RELEASE or RELEASEDIR request
@ -1214,4 +1237,19 @@ void fuse_dax_inode_cleanup(struct inode *inode);
bool fuse_dax_check_alignment(struct fuse_conn *fc, unsigned int map_alignment);
void fuse_dax_cancel_work(struct fuse_conn *fc);
/* ioctl.c */
long fuse_file_ioctl(struct file *file, unsigned int cmd, unsigned long arg);
long fuse_file_compat_ioctl(struct file *file, unsigned int cmd,
unsigned long arg);
int fuse_fileattr_get(struct dentry *dentry, struct fileattr *fa);
int fuse_fileattr_set(struct user_namespace *mnt_userns,
struct dentry *dentry, struct fileattr *fa);
/* file.c */
struct fuse_file *fuse_file_open(struct fuse_mount *fm, u64 nodeid,
unsigned int open_flags, bool isdir);
void fuse_file_release(struct inode *inode, struct fuse_file *ff,
unsigned int open_flags, fl_owner_t id, bool isdir);
#endif /* _FS_FUSE_I_H */

490
fs/fuse/ioctl.c Normal file
View File

@ -0,0 +1,490 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (C) 2017 Red Hat, Inc.
*/
#include "fuse_i.h"
#include <linux/uio.h>
#include <linux/compat.h>
#include <linux/fileattr.h>
/*
* CUSE servers compiled on 32bit broke on 64bit kernels because the
* ABI was defined to be 'struct iovec' which is different on 32bit
* and 64bit. Fortunately we can determine which structure the server
* used from the size of the reply.
*/
static int fuse_copy_ioctl_iovec_old(struct iovec *dst, void *src,
size_t transferred, unsigned count,
bool is_compat)
{
#ifdef CONFIG_COMPAT
if (count * sizeof(struct compat_iovec) == transferred) {
struct compat_iovec *ciov = src;
unsigned i;
/*
* With this interface a 32bit server cannot support
* non-compat (i.e. ones coming from 64bit apps) ioctl
* requests
*/
if (!is_compat)
return -EINVAL;
for (i = 0; i < count; i++) {
dst[i].iov_base = compat_ptr(ciov[i].iov_base);
dst[i].iov_len = ciov[i].iov_len;
}
return 0;
}
#endif
if (count * sizeof(struct iovec) != transferred)
return -EIO;
memcpy(dst, src, transferred);
return 0;
}
/* Make sure iov_length() won't overflow */
static int fuse_verify_ioctl_iov(struct fuse_conn *fc, struct iovec *iov,
size_t count)
{
size_t n;
u32 max = fc->max_pages << PAGE_SHIFT;
for (n = 0; n < count; n++, iov++) {
if (iov->iov_len > (size_t) max)
return -ENOMEM;
max -= iov->iov_len;
}
return 0;
}
static int fuse_copy_ioctl_iovec(struct fuse_conn *fc, struct iovec *dst,
void *src, size_t transferred, unsigned count,
bool is_compat)
{
unsigned i;
struct fuse_ioctl_iovec *fiov = src;
if (fc->minor < 16) {
return fuse_copy_ioctl_iovec_old(dst, src, transferred,
count, is_compat);
}
if (count * sizeof(struct fuse_ioctl_iovec) != transferred)
return -EIO;
for (i = 0; i < count; i++) {
/* Did the server supply an inappropriate value? */
if (fiov[i].base != (unsigned long) fiov[i].base ||
fiov[i].len != (unsigned long) fiov[i].len)
return -EIO;
dst[i].iov_base = (void __user *) (unsigned long) fiov[i].base;
dst[i].iov_len = (size_t) fiov[i].len;
#ifdef CONFIG_COMPAT
if (is_compat &&
(ptr_to_compat(dst[i].iov_base) != fiov[i].base ||
(compat_size_t) dst[i].iov_len != fiov[i].len))
return -EIO;
#endif
}
return 0;
}
/*
* For ioctls, there is no generic way to determine how much memory
* needs to be read and/or written. Furthermore, ioctls are allowed
* to dereference the passed pointer, so the parameter requires deep
* copying but FUSE has no idea whatsoever about what to copy in or
* out.
*
* This is solved by allowing FUSE server to retry ioctl with
* necessary in/out iovecs. Let's assume the ioctl implementation
* needs to read in the following structure.
*
* struct a {
* char *buf;
* size_t buflen;
* }
*
* On the first callout to FUSE server, inarg->in_size and
* inarg->out_size will be NULL; then, the server completes the ioctl
* with FUSE_IOCTL_RETRY set in out->flags, out->in_iovs set to 1 and
* the actual iov array to
*
* { { .iov_base = inarg.arg, .iov_len = sizeof(struct a) } }
*
* which tells FUSE to copy in the requested area and retry the ioctl.
* On the second round, the server has access to the structure and
* from that it can tell what to look for next, so on the invocation,
* it sets FUSE_IOCTL_RETRY, out->in_iovs to 2 and iov array to
*
* { { .iov_base = inarg.arg, .iov_len = sizeof(struct a) },
* { .iov_base = a.buf, .iov_len = a.buflen } }
*
* FUSE will copy both struct a and the pointed buffer from the
* process doing the ioctl and retry ioctl with both struct a and the
* buffer.
*
* This time, FUSE server has everything it needs and completes ioctl
* without FUSE_IOCTL_RETRY which finishes the ioctl call.
*
* Copying data out works the same way.
*
* Note that if FUSE_IOCTL_UNRESTRICTED is clear, the kernel
* automatically initializes in and out iovs by decoding @cmd with
* _IOC_* macros and the server is not allowed to request RETRY. This
* limits ioctl data transfers to well-formed ioctls and is the forced
* behavior for all FUSE servers.
*/
long fuse_do_ioctl(struct file *file, unsigned int cmd, unsigned long arg,
unsigned int flags)
{
struct fuse_file *ff = file->private_data;
struct fuse_mount *fm = ff->fm;
struct fuse_ioctl_in inarg = {
.fh = ff->fh,
.cmd = cmd,
.arg = arg,
.flags = flags
};
struct fuse_ioctl_out outarg;
struct iovec *iov_page = NULL;
struct iovec *in_iov = NULL, *out_iov = NULL;
unsigned int in_iovs = 0, out_iovs = 0, max_pages;
size_t in_size, out_size, c;
ssize_t transferred;
int err, i;
struct iov_iter ii;
struct fuse_args_pages ap = {};
#if BITS_PER_LONG == 32
inarg.flags |= FUSE_IOCTL_32BIT;
#else
if (flags & FUSE_IOCTL_COMPAT) {
inarg.flags |= FUSE_IOCTL_32BIT;
#ifdef CONFIG_X86_X32
if (in_x32_syscall())
inarg.flags |= FUSE_IOCTL_COMPAT_X32;
#endif
}
#endif
/* assume all the iovs returned by client always fits in a page */
BUILD_BUG_ON(sizeof(struct fuse_ioctl_iovec) * FUSE_IOCTL_MAX_IOV > PAGE_SIZE);
err = -ENOMEM;
ap.pages = fuse_pages_alloc(fm->fc->max_pages, GFP_KERNEL, &ap.descs);
iov_page = (struct iovec *) __get_free_page(GFP_KERNEL);
if (!ap.pages || !iov_page)
goto out;
fuse_page_descs_length_init(ap.descs, 0, fm->fc->max_pages);
/*
* If restricted, initialize IO parameters as encoded in @cmd.
* RETRY from server is not allowed.
*/
if (!(flags & FUSE_IOCTL_UNRESTRICTED)) {
struct iovec *iov = iov_page;
iov->iov_base = (void __user *)arg;
iov->iov_len = _IOC_SIZE(cmd);
if (_IOC_DIR(cmd) & _IOC_WRITE) {
in_iov = iov;
in_iovs = 1;
}
if (_IOC_DIR(cmd) & _IOC_READ) {
out_iov = iov;
out_iovs = 1;
}
}
retry:
inarg.in_size = in_size = iov_length(in_iov, in_iovs);
inarg.out_size = out_size = iov_length(out_iov, out_iovs);
/*
* Out data can be used either for actual out data or iovs,
* make sure there always is at least one page.
*/
out_size = max_t(size_t, out_size, PAGE_SIZE);
max_pages = DIV_ROUND_UP(max(in_size, out_size), PAGE_SIZE);
/* make sure there are enough buffer pages and init request with them */
err = -ENOMEM;
if (max_pages > fm->fc->max_pages)
goto out;
while (ap.num_pages < max_pages) {
ap.pages[ap.num_pages] = alloc_page(GFP_KERNEL | __GFP_HIGHMEM);
if (!ap.pages[ap.num_pages])
goto out;
ap.num_pages++;
}
/* okay, let's send it to the client */
ap.args.opcode = FUSE_IOCTL;
ap.args.nodeid = ff->nodeid;
ap.args.in_numargs = 1;
ap.args.in_args[0].size = sizeof(inarg);
ap.args.in_args[0].value = &inarg;
if (in_size) {
ap.args.in_numargs++;
ap.args.in_args[1].size = in_size;
ap.args.in_pages = true;
err = -EFAULT;
iov_iter_init(&ii, WRITE, in_iov, in_iovs, in_size);
for (i = 0; iov_iter_count(&ii) && !WARN_ON(i >= ap.num_pages); i++) {
c = copy_page_from_iter(ap.pages[i], 0, PAGE_SIZE, &ii);
if (c != PAGE_SIZE && iov_iter_count(&ii))
goto out;
}
}
ap.args.out_numargs = 2;
ap.args.out_args[0].size = sizeof(outarg);
ap.args.out_args[0].value = &outarg;
ap.args.out_args[1].size = out_size;
ap.args.out_pages = true;
ap.args.out_argvar = true;
transferred = fuse_simple_request(fm, &ap.args);
err = transferred;
if (transferred < 0)
goto out;
/* did it ask for retry? */
if (outarg.flags & FUSE_IOCTL_RETRY) {
void *vaddr;
/* no retry if in restricted mode */
err = -EIO;
if (!(flags & FUSE_IOCTL_UNRESTRICTED))
goto out;
in_iovs = outarg.in_iovs;
out_iovs = outarg.out_iovs;
/*
* Make sure things are in boundary, separate checks
* are to protect against overflow.
*/
err = -ENOMEM;
if (in_iovs > FUSE_IOCTL_MAX_IOV ||
out_iovs > FUSE_IOCTL_MAX_IOV ||
in_iovs + out_iovs > FUSE_IOCTL_MAX_IOV)
goto out;
vaddr = kmap_atomic(ap.pages[0]);
err = fuse_copy_ioctl_iovec(fm->fc, iov_page, vaddr,
transferred, in_iovs + out_iovs,
(flags & FUSE_IOCTL_COMPAT) != 0);
kunmap_atomic(vaddr);
if (err)
goto out;
in_iov = iov_page;
out_iov = in_iov + in_iovs;
err = fuse_verify_ioctl_iov(fm->fc, in_iov, in_iovs);
if (err)
goto out;
err = fuse_verify_ioctl_iov(fm->fc, out_iov, out_iovs);
if (err)
goto out;
goto retry;
}
err = -EIO;
if (transferred > inarg.out_size)
goto out;
err = -EFAULT;
iov_iter_init(&ii, READ, out_iov, out_iovs, transferred);
for (i = 0; iov_iter_count(&ii) && !WARN_ON(i >= ap.num_pages); i++) {
c = copy_page_to_iter(ap.pages[i], 0, PAGE_SIZE, &ii);
if (c != PAGE_SIZE && iov_iter_count(&ii))
goto out;
}
err = 0;
out:
free_page((unsigned long) iov_page);
while (ap.num_pages)
__free_page(ap.pages[--ap.num_pages]);
kfree(ap.pages);
return err ? err : outarg.result;
}
EXPORT_SYMBOL_GPL(fuse_do_ioctl);
long fuse_ioctl_common(struct file *file, unsigned int cmd,
unsigned long arg, unsigned int flags)
{
struct inode *inode = file_inode(file);
struct fuse_conn *fc = get_fuse_conn(inode);
if (!fuse_allow_current_process(fc))
return -EACCES;
if (fuse_is_bad(inode))
return -EIO;
return fuse_do_ioctl(file, cmd, arg, flags);
}
long fuse_file_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
return fuse_ioctl_common(file, cmd, arg, 0);
}
long fuse_file_compat_ioctl(struct file *file, unsigned int cmd,
unsigned long arg)
{
return fuse_ioctl_common(file, cmd, arg, FUSE_IOCTL_COMPAT);
}
static int fuse_priv_ioctl(struct inode *inode, struct fuse_file *ff,
unsigned int cmd, void *ptr, size_t size)
{
struct fuse_mount *fm = ff->fm;
struct fuse_ioctl_in inarg;
struct fuse_ioctl_out outarg;
FUSE_ARGS(args);
int err;
memset(&inarg, 0, sizeof(inarg));
inarg.fh = ff->fh;
inarg.cmd = cmd;
#if BITS_PER_LONG == 32
inarg.flags |= FUSE_IOCTL_32BIT;
#endif
if (S_ISDIR(inode->i_mode))
inarg.flags |= FUSE_IOCTL_DIR;
if (_IOC_DIR(cmd) & _IOC_READ)
inarg.out_size = size;
if (_IOC_DIR(cmd) & _IOC_WRITE)
inarg.in_size = size;
args.opcode = FUSE_IOCTL;
args.nodeid = ff->nodeid;
args.in_numargs = 2;
args.in_args[0].size = sizeof(inarg);
args.in_args[0].value = &inarg;
args.in_args[1].size = inarg.in_size;
args.in_args[1].value = ptr;
args.out_numargs = 2;
args.out_args[0].size = sizeof(outarg);
args.out_args[0].value = &outarg;
args.out_args[1].size = inarg.out_size;
args.out_args[1].value = ptr;
err = fuse_simple_request(fm, &args);
if (!err && outarg.flags & FUSE_IOCTL_RETRY)
err = -EIO;
return err;
}
static struct fuse_file *fuse_priv_ioctl_prepare(struct inode *inode)
{
struct fuse_mount *fm = get_fuse_mount(inode);
bool isdir = S_ISDIR(inode->i_mode);
if (!S_ISREG(inode->i_mode) && !isdir)
return ERR_PTR(-ENOTTY);
return fuse_file_open(fm, get_node_id(inode), O_RDONLY, isdir);
}
static void fuse_priv_ioctl_cleanup(struct inode *inode, struct fuse_file *ff)
{
fuse_file_release(inode, ff, O_RDONLY, NULL, S_ISDIR(inode->i_mode));
}
int fuse_fileattr_get(struct dentry *dentry, struct fileattr *fa)
{
struct inode *inode = d_inode(dentry);
struct fuse_file *ff;
unsigned int flags;
struct fsxattr xfa;
int err;
ff = fuse_priv_ioctl_prepare(inode);
if (IS_ERR(ff))
return PTR_ERR(ff);
if (fa->flags_valid) {
err = fuse_priv_ioctl(inode, ff, FS_IOC_GETFLAGS,
&flags, sizeof(flags));
if (err)
goto cleanup;
fileattr_fill_flags(fa, flags);
} else {
err = fuse_priv_ioctl(inode, ff, FS_IOC_FSGETXATTR,
&xfa, sizeof(xfa));
if (err)
goto cleanup;
fileattr_fill_xflags(fa, xfa.fsx_xflags);
fa->fsx_extsize = xfa.fsx_extsize;
fa->fsx_nextents = xfa.fsx_nextents;
fa->fsx_projid = xfa.fsx_projid;
fa->fsx_cowextsize = xfa.fsx_cowextsize;
}
cleanup:
fuse_priv_ioctl_cleanup(inode, ff);
return err;
}
int fuse_fileattr_set(struct user_namespace *mnt_userns,
struct dentry *dentry, struct fileattr *fa)
{
struct inode *inode = d_inode(dentry);
struct fuse_file *ff;
unsigned int flags = fa->flags;
struct fsxattr xfa;
int err;
ff = fuse_priv_ioctl_prepare(inode);
if (IS_ERR(ff))
return PTR_ERR(ff);
if (fa->flags_valid) {
err = fuse_priv_ioctl(inode, ff, FS_IOC_SETFLAGS,
&flags, sizeof(flags));
if (err)
goto cleanup;
} else {
memset(&xfa, 0, sizeof(xfa));
xfa.fsx_xflags = fa->fsx_xflags;
xfa.fsx_extsize = fa->fsx_extsize;
xfa.fsx_nextents = fa->fsx_nextents;
xfa.fsx_projid = fa->fsx_projid;
xfa.fsx_cowextsize = fa->fsx_cowextsize;
err = fuse_priv_ioctl(inode, ff, FS_IOC_FSSETXATTR,
&xfa, sizeof(xfa));
}
cleanup:
fuse_priv_ioctl_cleanup(inode, ff);
return err;
}

View File

@ -25,6 +25,7 @@
#include <linux/dlm_plock.h>
#include <linux/delay.h>
#include <linux/backing-dev.h>
#include <linux/fileattr.h>
#include "gfs2.h"
#include "incore.h"
@ -153,14 +154,17 @@ static inline u32 gfs2_gfsflags_to_fsflags(struct inode *inode, u32 gfsflags)
return fsflags;
}
static int gfs2_get_flags(struct file *filp, u32 __user *ptr)
int gfs2_fileattr_get(struct dentry *dentry, struct fileattr *fa)
{
struct inode *inode = file_inode(filp);
struct inode *inode = d_inode(dentry);
struct gfs2_inode *ip = GFS2_I(inode);
struct gfs2_holder gh;
int error;
u32 fsflags;
if (d_is_special(dentry))
return -ENOTTY;
gfs2_holder_init(ip->i_gl, LM_ST_SHARED, 0, &gh);
error = gfs2_glock_nq(&gh);
if (error)
@ -168,8 +172,7 @@ static int gfs2_get_flags(struct file *filp, u32 __user *ptr)
fsflags = gfs2_gfsflags_to_fsflags(inode, ip->i_diskflags);
if (put_user(fsflags, ptr))
error = -EFAULT;
fileattr_fill_flags(fa, fsflags);
gfs2_glock_dq(&gh);
out_uninit:
@ -213,33 +216,19 @@ void gfs2_set_inode_flags(struct inode *inode)
* @fsflags: The FS_* inode flags passed in
*
*/
static int do_gfs2_set_flags(struct file *filp, u32 reqflags, u32 mask,
static int do_gfs2_set_flags(struct inode *inode, u32 reqflags, u32 mask,
const u32 fsflags)
{
struct inode *inode = file_inode(filp);
struct gfs2_inode *ip = GFS2_I(inode);
struct gfs2_sbd *sdp = GFS2_SB(inode);
struct buffer_head *bh;
struct gfs2_holder gh;
int error;
u32 new_flags, flags, oldflags;
error = mnt_want_write_file(filp);
if (error)
return error;
u32 new_flags, flags;
error = gfs2_glock_nq_init(ip->i_gl, LM_ST_EXCLUSIVE, 0, &gh);
if (error)
goto out_drop_write;
oldflags = gfs2_gfsflags_to_fsflags(inode, ip->i_diskflags);
error = vfs_ioc_setflags_prepare(inode, oldflags, fsflags);
if (error)
goto out;
error = -EACCES;
if (!inode_owner_or_capable(&init_user_ns, inode))
goto out;
return error;
error = 0;
flags = ip->i_diskflags;
@ -252,9 +241,6 @@ static int do_gfs2_set_flags(struct file *filp, u32 reqflags, u32 mask,
goto out;
if (IS_APPEND(inode) && (new_flags & GFS2_DIF_APPENDONLY))
goto out;
if (((new_flags ^ flags) & GFS2_DIF_IMMUTABLE) &&
!capable(CAP_LINUX_IMMUTABLE))
goto out;
if (!IS_IMMUTABLE(inode)) {
error = gfs2_permission(&init_user_ns, inode, MAY_WRITE);
if (error)
@ -291,20 +277,22 @@ out_trans_end:
gfs2_trans_end(sdp);
out:
gfs2_glock_dq_uninit(&gh);
out_drop_write:
mnt_drop_write_file(filp);
return error;
}
static int gfs2_set_flags(struct file *filp, u32 __user *ptr)
int gfs2_fileattr_set(struct user_namespace *mnt_userns,
struct dentry *dentry, struct fileattr *fa)
{
struct inode *inode = file_inode(filp);
u32 fsflags, gfsflags = 0;
struct inode *inode = d_inode(dentry);
u32 fsflags = fa->flags, gfsflags = 0;
u32 mask;
int i;
if (get_user(fsflags, ptr))
return -EFAULT;
if (d_is_special(dentry))
return -ENOTTY;
if (fileattr_has_fsx(fa))
return -EOPNOTSUPP;
for (i = 0; i < ARRAY_SIZE(fsflag_gfs2flag); i++) {
if (fsflags & fsflag_gfs2flag[i].fsflag) {
@ -325,7 +313,7 @@ static int gfs2_set_flags(struct file *filp, u32 __user *ptr)
mask &= ~(GFS2_DIF_TOPDIR | GFS2_DIF_INHERIT_JDATA);
}
return do_gfs2_set_flags(filp, gfsflags, mask, fsflags);
return do_gfs2_set_flags(inode, gfsflags, mask, fsflags);
}
static int gfs2_getlabel(struct file *filp, char __user *label)
@ -342,10 +330,6 @@ static int gfs2_getlabel(struct file *filp, char __user *label)
static long gfs2_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
switch(cmd) {
case FS_IOC_GETFLAGS:
return gfs2_get_flags(filp, (u32 __user *)arg);
case FS_IOC_SETFLAGS:
return gfs2_set_flags(filp, (u32 __user *)arg);
case FITRIM:
return gfs2_fitrim(filp, (void __user *)arg);
case FS_IOC_GETFSLABEL:
@ -359,13 +343,6 @@ static long gfs2_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
static long gfs2_compat_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
switch(cmd) {
/* These are just misnamed, they actually get/put from/to user an int */
case FS_IOC32_GETFLAGS:
cmd = FS_IOC_GETFLAGS;
break;
case FS_IOC32_SETFLAGS:
cmd = FS_IOC_SETFLAGS;
break;
/* Keep this list in sync with gfs2_ioctl */
case FITRIM:
case FS_IOC_GETFSLABEL:

View File

@ -2157,6 +2157,8 @@ static const struct inode_operations gfs2_file_iops = {
.get_acl = gfs2_get_acl,
.set_acl = gfs2_set_acl,
.update_time = gfs2_update_time,
.fileattr_get = gfs2_fileattr_get,
.fileattr_set = gfs2_fileattr_set,
};
static const struct inode_operations gfs2_dir_iops = {
@ -2178,6 +2180,8 @@ static const struct inode_operations gfs2_dir_iops = {
.set_acl = gfs2_set_acl,
.update_time = gfs2_update_time,
.atomic_open = gfs2_atomic_open,
.fileattr_get = gfs2_fileattr_get,
.fileattr_set = gfs2_fileattr_set,
};
static const struct inode_operations gfs2_symlink_iops = {

View File

@ -111,6 +111,9 @@ extern loff_t gfs2_seek_hole(struct file *file, loff_t offset);
extern const struct file_operations gfs2_file_fops_nolock;
extern const struct file_operations gfs2_dir_fops_nolock;
extern int gfs2_fileattr_get(struct dentry *dentry, struct fileattr *fa);
extern int gfs2_fileattr_set(struct user_namespace *mnt_userns,
struct dentry *dentry, struct fileattr *fa);
extern void gfs2_set_inode_flags(struct inode *inode);
#ifdef CONFIG_GFS2_FS_LOCKING_DLM

View File

@ -569,6 +569,8 @@ const struct inode_operations hfsplus_dir_inode_operations = {
.rename = hfsplus_rename,
.getattr = hfsplus_getattr,
.listxattr = hfsplus_listxattr,
.fileattr_get = hfsplus_fileattr_get,
.fileattr_set = hfsplus_fileattr_set,
};
const struct file_operations hfsplus_dir_operations = {

View File

@ -344,17 +344,6 @@ static inline unsigned short hfsplus_min_io_size(struct super_block *sb)
#define hfs_brec_goto hfsplus_brec_goto
#define hfs_part_find hfsplus_part_find
/*
* definitions for ext2 flag ioctls (linux really needs a generic
* interface for this).
*/
/* ext2 ioctls (EXT2_IOC_GETFLAGS and EXT2_IOC_SETFLAGS) to support
* chattr/lsattr */
#define HFSPLUS_IOC_EXT2_GETFLAGS FS_IOC_GETFLAGS
#define HFSPLUS_IOC_EXT2_SETFLAGS FS_IOC_SETFLAGS
/*
* hfs+-specific ioctl for making the filesystem bootable
*/
@ -493,6 +482,9 @@ int hfsplus_getattr(struct user_namespace *mnt_userns, const struct path *path,
unsigned int query_flags);
int hfsplus_file_fsync(struct file *file, loff_t start, loff_t end,
int datasync);
int hfsplus_fileattr_get(struct dentry *dentry, struct fileattr *fa);
int hfsplus_fileattr_set(struct user_namespace *mnt_userns,
struct dentry *dentry, struct fileattr *fa);
/* ioctl.c */
long hfsplus_ioctl(struct file *filp, unsigned int cmd, unsigned long arg);

View File

@ -17,6 +17,7 @@
#include <linux/sched.h>
#include <linux/cred.h>
#include <linux/uio.h>
#include <linux/fileattr.h>
#include "hfsplus_fs.h"
#include "hfsplus_raw.h"
@ -353,6 +354,8 @@ static const struct inode_operations hfsplus_file_inode_operations = {
.setattr = hfsplus_setattr,
.getattr = hfsplus_getattr,
.listxattr = hfsplus_listxattr,
.fileattr_get = hfsplus_fileattr_get,
.fileattr_set = hfsplus_fileattr_set,
};
static const struct file_operations hfsplus_file_operations = {
@ -628,3 +631,54 @@ out:
hfs_find_exit(&fd);
return 0;
}
int hfsplus_fileattr_get(struct dentry *dentry, struct fileattr *fa)
{
struct inode *inode = d_inode(dentry);
struct hfsplus_inode_info *hip = HFSPLUS_I(inode);
unsigned int flags = 0;
if (inode->i_flags & S_IMMUTABLE)
flags |= FS_IMMUTABLE_FL;
if (inode->i_flags & S_APPEND)
flags |= FS_APPEND_FL;
if (hip->userflags & HFSPLUS_FLG_NODUMP)
flags |= FS_NODUMP_FL;
fileattr_fill_flags(fa, flags);
return 0;
}
int hfsplus_fileattr_set(struct user_namespace *mnt_userns,
struct dentry *dentry, struct fileattr *fa)
{
struct inode *inode = d_inode(dentry);
struct hfsplus_inode_info *hip = HFSPLUS_I(inode);
unsigned int new_fl = 0;
if (fileattr_has_fsx(fa))
return -EOPNOTSUPP;
/* don't silently ignore unsupported ext2 flags */
if (fa->flags & ~(FS_IMMUTABLE_FL|FS_APPEND_FL|FS_NODUMP_FL))
return -EOPNOTSUPP;
if (fa->flags & FS_IMMUTABLE_FL)
new_fl |= S_IMMUTABLE;
if (fa->flags & FS_APPEND_FL)
new_fl |= S_APPEND;
inode_set_flags(inode, new_fl, S_IMMUTABLE | S_APPEND);
if (fa->flags & FS_NODUMP_FL)
hip->userflags |= HFSPLUS_FLG_NODUMP;
else
hip->userflags &= ~HFSPLUS_FLG_NODUMP;
inode->i_ctime = current_time(inode);
mark_inode_dirty(inode);
return 0;
}

View File

@ -57,95 +57,11 @@ static int hfsplus_ioctl_bless(struct file *file, int __user *user_flags)
return 0;
}
static inline unsigned int hfsplus_getflags(struct inode *inode)
{
struct hfsplus_inode_info *hip = HFSPLUS_I(inode);
unsigned int flags = 0;
if (inode->i_flags & S_IMMUTABLE)
flags |= FS_IMMUTABLE_FL;
if (inode->i_flags & S_APPEND)
flags |= FS_APPEND_FL;
if (hip->userflags & HFSPLUS_FLG_NODUMP)
flags |= FS_NODUMP_FL;
return flags;
}
static int hfsplus_ioctl_getflags(struct file *file, int __user *user_flags)
{
struct inode *inode = file_inode(file);
unsigned int flags = hfsplus_getflags(inode);
return put_user(flags, user_flags);
}
static int hfsplus_ioctl_setflags(struct file *file, int __user *user_flags)
{
struct inode *inode = file_inode(file);
struct hfsplus_inode_info *hip = HFSPLUS_I(inode);
unsigned int flags, new_fl = 0;
unsigned int oldflags = hfsplus_getflags(inode);
int err = 0;
err = mnt_want_write_file(file);
if (err)
goto out;
if (!inode_owner_or_capable(&init_user_ns, inode)) {
err = -EACCES;
goto out_drop_write;
}
if (get_user(flags, user_flags)) {
err = -EFAULT;
goto out_drop_write;
}
inode_lock(inode);
err = vfs_ioc_setflags_prepare(inode, oldflags, flags);
if (err)
goto out_unlock_inode;
/* don't silently ignore unsupported ext2 flags */
if (flags & ~(FS_IMMUTABLE_FL|FS_APPEND_FL|FS_NODUMP_FL)) {
err = -EOPNOTSUPP;
goto out_unlock_inode;
}
if (flags & FS_IMMUTABLE_FL)
new_fl |= S_IMMUTABLE;
if (flags & FS_APPEND_FL)
new_fl |= S_APPEND;
inode_set_flags(inode, new_fl, S_IMMUTABLE | S_APPEND);
if (flags & FS_NODUMP_FL)
hip->userflags |= HFSPLUS_FLG_NODUMP;
else
hip->userflags &= ~HFSPLUS_FLG_NODUMP;
inode->i_ctime = current_time(inode);
mark_inode_dirty(inode);
out_unlock_inode:
inode_unlock(inode);
out_drop_write:
mnt_drop_write_file(file);
out:
return err;
}
long hfsplus_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
void __user *argp = (void __user *)arg;
switch (cmd) {
case HFSPLUS_IOC_EXT2_GETFLAGS:
return hfsplus_ioctl_getflags(file, argp);
case HFSPLUS_IOC_EXT2_SETFLAGS:
return hfsplus_ioctl_setflags(file, argp);
case HFSPLUS_IOC_BLESS:
return hfsplus_ioctl_bless(file, argp);
default:

View File

@ -12,7 +12,6 @@
#include <linux/security.h>
#include <linux/cdev.h>
#include <linux/memblock.h>
#include <linux/fscrypt.h>
#include <linux/fsnotify.h>
#include <linux/mount.h>
#include <linux/posix_acl.h>
@ -2314,89 +2313,3 @@ struct timespec64 current_time(struct inode *inode)
return timestamp_truncate(now, inode);
}
EXPORT_SYMBOL(current_time);
/*
* Generic function to check FS_IOC_SETFLAGS values and reject any invalid
* configurations.
*
* Note: the caller should be holding i_mutex, or else be sure that they have
* exclusive access to the inode structure.
*/
int vfs_ioc_setflags_prepare(struct inode *inode, unsigned int oldflags,
unsigned int flags)
{
/*
* The IMMUTABLE and APPEND_ONLY flags can only be changed by
* the relevant capability.
*
* This test looks nicer. Thanks to Pauline Middelink
*/
if ((flags ^ oldflags) & (FS_APPEND_FL | FS_IMMUTABLE_FL) &&
!capable(CAP_LINUX_IMMUTABLE))
return -EPERM;
return fscrypt_prepare_setflags(inode, oldflags, flags);
}
EXPORT_SYMBOL(vfs_ioc_setflags_prepare);
/*
* Generic function to check FS_IOC_FSSETXATTR values and reject any invalid
* configurations.
*
* Note: the caller should be holding i_mutex, or else be sure that they have
* exclusive access to the inode structure.
*/
int vfs_ioc_fssetxattr_check(struct inode *inode, const struct fsxattr *old_fa,
struct fsxattr *fa)
{
/*
* Can't modify an immutable/append-only file unless we have
* appropriate permission.
*/
if ((old_fa->fsx_xflags ^ fa->fsx_xflags) &
(FS_XFLAG_IMMUTABLE | FS_XFLAG_APPEND) &&
!capable(CAP_LINUX_IMMUTABLE))
return -EPERM;
/*
* Project Quota ID state is only allowed to change from within the init
* namespace. Enforce that restriction only if we are trying to change
* the quota ID state. Everything else is allowed in user namespaces.
*/
if (current_user_ns() != &init_user_ns) {
if (old_fa->fsx_projid != fa->fsx_projid)
return -EINVAL;
if ((old_fa->fsx_xflags ^ fa->fsx_xflags) &
FS_XFLAG_PROJINHERIT)
return -EINVAL;
}
/* Check extent size hints. */
if ((fa->fsx_xflags & FS_XFLAG_EXTSIZE) && !S_ISREG(inode->i_mode))
return -EINVAL;
if ((fa->fsx_xflags & FS_XFLAG_EXTSZINHERIT) &&
!S_ISDIR(inode->i_mode))
return -EINVAL;
if ((fa->fsx_xflags & FS_XFLAG_COWEXTSIZE) &&
!S_ISREG(inode->i_mode) && !S_ISDIR(inode->i_mode))
return -EINVAL;
/*
* It is only valid to set the DAX flag on regular files and
* directories on filesystems.
*/
if ((fa->fsx_xflags & FS_XFLAG_DAX) &&
!(S_ISREG(inode->i_mode) || S_ISDIR(inode->i_mode)))
return -EINVAL;
/* Extent size hints of zero turn off the flags. */
if (fa->fsx_extsize == 0)
fa->fsx_xflags &= ~(FS_XFLAG_EXTSIZE | FS_XFLAG_EXTSZINHERIT);
if (fa->fsx_cowextsize == 0)
fa->fsx_xflags &= ~FS_XFLAG_COWEXTSIZE;
return 0;
}
EXPORT_SYMBOL(vfs_ioc_fssetxattr_check);

View File

@ -19,6 +19,9 @@
#include <linux/falloc.h>
#include <linux/sched/signal.h>
#include <linux/fiemap.h>
#include <linux/mount.h>
#include <linux/fscrypt.h>
#include <linux/fileattr.h>
#include "internal.h"
@ -657,6 +660,307 @@ out:
return ret;
}
/**
* fileattr_fill_xflags - initialize fileattr with xflags
* @fa: fileattr pointer
* @xflags: FS_XFLAG_* flags
*
* Set ->fsx_xflags, ->fsx_valid and ->flags (translated xflags). All
* other fields are zeroed.
*/
void fileattr_fill_xflags(struct fileattr *fa, u32 xflags)
{
memset(fa, 0, sizeof(*fa));
fa->fsx_valid = true;
fa->fsx_xflags = xflags;
if (fa->fsx_xflags & FS_XFLAG_IMMUTABLE)
fa->flags |= FS_IMMUTABLE_FL;
if (fa->fsx_xflags & FS_XFLAG_APPEND)
fa->flags |= FS_APPEND_FL;
if (fa->fsx_xflags & FS_XFLAG_SYNC)
fa->flags |= FS_SYNC_FL;
if (fa->fsx_xflags & FS_XFLAG_NOATIME)
fa->flags |= FS_NOATIME_FL;
if (fa->fsx_xflags & FS_XFLAG_NODUMP)
fa->flags |= FS_NODUMP_FL;
if (fa->fsx_xflags & FS_XFLAG_DAX)
fa->flags |= FS_DAX_FL;
if (fa->fsx_xflags & FS_XFLAG_PROJINHERIT)
fa->flags |= FS_PROJINHERIT_FL;
}
EXPORT_SYMBOL(fileattr_fill_xflags);
/**
* fileattr_fill_flags - initialize fileattr with flags
* @fa: fileattr pointer
* @flags: FS_*_FL flags
*
* Set ->flags, ->flags_valid and ->fsx_xflags (translated flags).
* All other fields are zeroed.
*/
void fileattr_fill_flags(struct fileattr *fa, u32 flags)
{
memset(fa, 0, sizeof(*fa));
fa->flags_valid = true;
fa->flags = flags;
if (fa->flags & FS_SYNC_FL)
fa->fsx_xflags |= FS_XFLAG_SYNC;
if (fa->flags & FS_IMMUTABLE_FL)
fa->fsx_xflags |= FS_XFLAG_IMMUTABLE;
if (fa->flags & FS_APPEND_FL)
fa->fsx_xflags |= FS_XFLAG_APPEND;
if (fa->flags & FS_NODUMP_FL)
fa->fsx_xflags |= FS_XFLAG_NODUMP;
if (fa->flags & FS_NOATIME_FL)
fa->fsx_xflags |= FS_XFLAG_NOATIME;
if (fa->flags & FS_DAX_FL)
fa->fsx_xflags |= FS_XFLAG_DAX;
if (fa->flags & FS_PROJINHERIT_FL)
fa->fsx_xflags |= FS_XFLAG_PROJINHERIT;
}
EXPORT_SYMBOL(fileattr_fill_flags);
/**
* vfs_fileattr_get - retrieve miscellaneous file attributes
* @dentry: the object to retrieve from
* @fa: fileattr pointer
*
* Call i_op->fileattr_get() callback, if exists.
*
* Return: 0 on success, or a negative error on failure.
*/
int vfs_fileattr_get(struct dentry *dentry, struct fileattr *fa)
{
struct inode *inode = d_inode(dentry);
if (!inode->i_op->fileattr_get)
return -ENOIOCTLCMD;
return inode->i_op->fileattr_get(dentry, fa);
}
EXPORT_SYMBOL(vfs_fileattr_get);
/**
* copy_fsxattr_to_user - copy fsxattr to userspace.
* @fa: fileattr pointer
* @ufa: fsxattr user pointer
*
* Return: 0 on success, or -EFAULT on failure.
*/
int copy_fsxattr_to_user(const struct fileattr *fa, struct fsxattr __user *ufa)
{
struct fsxattr xfa;
memset(&xfa, 0, sizeof(xfa));
xfa.fsx_xflags = fa->fsx_xflags;
xfa.fsx_extsize = fa->fsx_extsize;
xfa.fsx_nextents = fa->fsx_nextents;
xfa.fsx_projid = fa->fsx_projid;
xfa.fsx_cowextsize = fa->fsx_cowextsize;
if (copy_to_user(ufa, &xfa, sizeof(xfa)))
return -EFAULT;
return 0;
}
EXPORT_SYMBOL(copy_fsxattr_to_user);
static int copy_fsxattr_from_user(struct fileattr *fa,
struct fsxattr __user *ufa)
{
struct fsxattr xfa;
if (copy_from_user(&xfa, ufa, sizeof(xfa)))
return -EFAULT;
fileattr_fill_xflags(fa, xfa.fsx_xflags);
fa->fsx_extsize = xfa.fsx_extsize;
fa->fsx_nextents = xfa.fsx_nextents;
fa->fsx_projid = xfa.fsx_projid;
fa->fsx_cowextsize = xfa.fsx_cowextsize;
return 0;
}
/*
* Generic function to check FS_IOC_FSSETXATTR/FS_IOC_SETFLAGS values and reject
* any invalid configurations.
*
* Note: must be called with inode lock held.
*/
static int fileattr_set_prepare(struct inode *inode,
const struct fileattr *old_ma,
struct fileattr *fa)
{
int err;
/*
* The IMMUTABLE and APPEND_ONLY flags can only be changed by
* the relevant capability.
*/
if ((fa->flags ^ old_ma->flags) & (FS_APPEND_FL | FS_IMMUTABLE_FL) &&
!capable(CAP_LINUX_IMMUTABLE))
return -EPERM;
err = fscrypt_prepare_setflags(inode, old_ma->flags, fa->flags);
if (err)
return err;
/*
* Project Quota ID state is only allowed to change from within the init
* namespace. Enforce that restriction only if we are trying to change
* the quota ID state. Everything else is allowed in user namespaces.
*/
if (current_user_ns() != &init_user_ns) {
if (old_ma->fsx_projid != fa->fsx_projid)
return -EINVAL;
if ((old_ma->fsx_xflags ^ fa->fsx_xflags) &
FS_XFLAG_PROJINHERIT)
return -EINVAL;
}
/* Check extent size hints. */
if ((fa->fsx_xflags & FS_XFLAG_EXTSIZE) && !S_ISREG(inode->i_mode))
return -EINVAL;
if ((fa->fsx_xflags & FS_XFLAG_EXTSZINHERIT) &&
!S_ISDIR(inode->i_mode))
return -EINVAL;
if ((fa->fsx_xflags & FS_XFLAG_COWEXTSIZE) &&
!S_ISREG(inode->i_mode) && !S_ISDIR(inode->i_mode))
return -EINVAL;
/*
* It is only valid to set the DAX flag on regular files and
* directories on filesystems.
*/
if ((fa->fsx_xflags & FS_XFLAG_DAX) &&
!(S_ISREG(inode->i_mode) || S_ISDIR(inode->i_mode)))
return -EINVAL;
/* Extent size hints of zero turn off the flags. */
if (fa->fsx_extsize == 0)
fa->fsx_xflags &= ~(FS_XFLAG_EXTSIZE | FS_XFLAG_EXTSZINHERIT);
if (fa->fsx_cowextsize == 0)
fa->fsx_xflags &= ~FS_XFLAG_COWEXTSIZE;
return 0;
}
/**
* vfs_fileattr_set - change miscellaneous file attributes
* @mnt_userns: user namespace of the mount
* @dentry: the object to change
* @fa: fileattr pointer
*
* After verifying permissions, call i_op->fileattr_set() callback, if
* exists.
*
* Verifying attributes involves retrieving current attributes with
* i_op->fileattr_get(), this also allows initializing attributes that have
* not been set by the caller to current values. Inode lock is held
* thoughout to prevent racing with another instance.
*
* Return: 0 on success, or a negative error on failure.
*/
int vfs_fileattr_set(struct user_namespace *mnt_userns, struct dentry *dentry,
struct fileattr *fa)
{
struct inode *inode = d_inode(dentry);
struct fileattr old_ma = {};
int err;
if (!inode->i_op->fileattr_set)
return -ENOIOCTLCMD;
if (!inode_owner_or_capable(mnt_userns, inode))
return -EPERM;
inode_lock(inode);
err = vfs_fileattr_get(dentry, &old_ma);
if (!err) {
/* initialize missing bits from old_ma */
if (fa->flags_valid) {
fa->fsx_xflags |= old_ma.fsx_xflags & ~FS_XFLAG_COMMON;
fa->fsx_extsize = old_ma.fsx_extsize;
fa->fsx_nextents = old_ma.fsx_nextents;
fa->fsx_projid = old_ma.fsx_projid;
fa->fsx_cowextsize = old_ma.fsx_cowextsize;
} else {
fa->flags |= old_ma.flags & ~FS_COMMON_FL;
}
err = fileattr_set_prepare(inode, &old_ma, fa);
if (!err)
err = inode->i_op->fileattr_set(mnt_userns, dentry, fa);
}
inode_unlock(inode);
return err;
}
EXPORT_SYMBOL(vfs_fileattr_set);
static int ioctl_getflags(struct file *file, unsigned int __user *argp)
{
struct fileattr fa = { .flags_valid = true }; /* hint only */
int err;
err = vfs_fileattr_get(file->f_path.dentry, &fa);
if (!err)
err = put_user(fa.flags, argp);
return err;
}
static int ioctl_setflags(struct file *file, unsigned int __user *argp)
{
struct user_namespace *mnt_userns = file_mnt_user_ns(file);
struct dentry *dentry = file->f_path.dentry;
struct fileattr fa;
unsigned int flags;
int err;
err = get_user(flags, argp);
if (!err) {
err = mnt_want_write_file(file);
if (!err) {
fileattr_fill_flags(&fa, flags);
err = vfs_fileattr_set(mnt_userns, dentry, &fa);
mnt_drop_write_file(file);
}
}
return err;
}
static int ioctl_fsgetxattr(struct file *file, void __user *argp)
{
struct fileattr fa = { .fsx_valid = true }; /* hint only */
int err;
err = vfs_fileattr_get(file->f_path.dentry, &fa);
if (!err)
err = copy_fsxattr_to_user(&fa, argp);
return err;
}
static int ioctl_fssetxattr(struct file *file, void __user *argp)
{
struct user_namespace *mnt_userns = file_mnt_user_ns(file);
struct dentry *dentry = file->f_path.dentry;
struct fileattr fa;
int err;
err = copy_fsxattr_from_user(&fa, argp);
if (!err) {
err = mnt_want_write_file(file);
if (!err) {
err = vfs_fileattr_set(mnt_userns, dentry, &fa);
mnt_drop_write_file(file);
}
}
return err;
}
/*
* do_vfs_ioctl() is not for drivers and not intended to be EXPORT_SYMBOL()'d.
* It's just a simple helper for sys_ioctl and compat_sys_ioctl.
@ -727,6 +1031,18 @@ static int do_vfs_ioctl(struct file *filp, unsigned int fd,
return put_user(i_size_read(inode) - filp->f_pos,
(int __user *)argp);
case FS_IOC_GETFLAGS:
return ioctl_getflags(filp, argp);
case FS_IOC_SETFLAGS:
return ioctl_setflags(filp, argp);
case FS_IOC_FSGETXATTR:
return ioctl_fsgetxattr(filp, argp);
case FS_IOC_FSSETXATTR:
return ioctl_fssetxattr(filp, argp);
default:
if (S_ISREG(inode->i_mode))
return file_ioctl(filp, cmd, argp);
@ -827,6 +1143,15 @@ COMPAT_SYSCALL_DEFINE3(ioctl, unsigned int, fd, unsigned int, cmd,
break;
#endif
/*
* These access 32-bit values anyway so no further handling is
* necessary.
*/
case FS_IOC32_GETFLAGS:
case FS_IOC32_SETFLAGS:
cmd = (cmd == FS_IOC32_GETFLAGS) ?
FS_IOC_GETFLAGS : FS_IOC_SETFLAGS;
fallthrough;
/*
* everything else in do_vfs_ioctl() takes either a compatible
* pointer argument or no argument -- call it with a modified

View File

@ -130,6 +130,8 @@ int jfs_setattr(struct user_namespace *mnt_userns, struct dentry *dentry,
const struct inode_operations jfs_file_inode_operations = {
.listxattr = jfs_listxattr,
.setattr = jfs_setattr,
.fileattr_get = jfs_fileattr_get,
.fileattr_set = jfs_fileattr_set,
#ifdef CONFIG_JFS_POSIX_ACL
.get_acl = jfs_get_acl,
.set_acl = jfs_set_acl,
@ -147,7 +149,5 @@ const struct file_operations jfs_file_operations = {
.fsync = jfs_fsync,
.release = jfs_release,
.unlocked_ioctl = jfs_ioctl,
#ifdef CONFIG_COMPAT
.compat_ioctl = jfs_compat_ioctl,
#endif
.compat_ioctl = compat_ptr_ioctl,
};

View File

@ -15,6 +15,7 @@
#include <linux/blkdev.h>
#include <asm/current.h>
#include <linux/uaccess.h>
#include <linux/fileattr.h>
#include "jfs_filsys.h"
#include "jfs_debug.h"
@ -56,69 +57,56 @@ static long jfs_map_ext2(unsigned long flags, int from)
return mapped;
}
int jfs_fileattr_get(struct dentry *dentry, struct fileattr *fa)
{
struct jfs_inode_info *jfs_inode = JFS_IP(d_inode(dentry));
unsigned int flags = jfs_inode->mode2 & JFS_FL_USER_VISIBLE;
if (d_is_special(dentry))
return -ENOTTY;
fileattr_fill_flags(fa, jfs_map_ext2(flags, 0));
return 0;
}
int jfs_fileattr_set(struct user_namespace *mnt_userns,
struct dentry *dentry, struct fileattr *fa)
{
struct inode *inode = d_inode(dentry);
struct jfs_inode_info *jfs_inode = JFS_IP(inode);
unsigned int flags;
if (d_is_special(dentry))
return -ENOTTY;
if (fileattr_has_fsx(fa))
return -EOPNOTSUPP;
flags = jfs_map_ext2(fa->flags, 1);
if (!S_ISDIR(inode->i_mode))
flags &= ~JFS_DIRSYNC_FL;
/* Is it quota file? Do not allow user to mess with it */
if (IS_NOQUOTA(inode))
return -EPERM;
flags = flags & JFS_FL_USER_MODIFIABLE;
flags |= jfs_inode->mode2 & ~JFS_FL_USER_MODIFIABLE;
jfs_inode->mode2 = flags;
jfs_set_inode_flags(inode);
inode->i_ctime = current_time(inode);
mark_inode_dirty(inode);
return 0;
}
long jfs_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
struct inode *inode = file_inode(filp);
struct jfs_inode_info *jfs_inode = JFS_IP(inode);
unsigned int flags;
switch (cmd) {
case JFS_IOC_GETFLAGS:
flags = jfs_inode->mode2 & JFS_FL_USER_VISIBLE;
flags = jfs_map_ext2(flags, 0);
return put_user(flags, (int __user *) arg);
case JFS_IOC_SETFLAGS: {
unsigned int oldflags;
int err;
err = mnt_want_write_file(filp);
if (err)
return err;
if (!inode_owner_or_capable(&init_user_ns, inode)) {
err = -EACCES;
goto setflags_out;
}
if (get_user(flags, (int __user *) arg)) {
err = -EFAULT;
goto setflags_out;
}
flags = jfs_map_ext2(flags, 1);
if (!S_ISDIR(inode->i_mode))
flags &= ~JFS_DIRSYNC_FL;
/* Is it quota file? Do not allow user to mess with it */
if (IS_NOQUOTA(inode)) {
err = -EPERM;
goto setflags_out;
}
/* Lock against other parallel changes of flags */
inode_lock(inode);
oldflags = jfs_map_ext2(jfs_inode->mode2 & JFS_FL_USER_VISIBLE,
0);
err = vfs_ioc_setflags_prepare(inode, oldflags, flags);
if (err) {
inode_unlock(inode);
goto setflags_out;
}
flags = flags & JFS_FL_USER_MODIFIABLE;
flags |= jfs_inode->mode2 & ~JFS_FL_USER_MODIFIABLE;
jfs_inode->mode2 = flags;
jfs_set_inode_flags(inode);
inode_unlock(inode);
inode->i_ctime = current_time(inode);
mark_inode_dirty(inode);
setflags_out:
mnt_drop_write_file(filp);
return err;
}
case FITRIM:
{
struct super_block *sb = inode->i_sb;
@ -156,22 +144,3 @@ setflags_out:
return -ENOTTY;
}
}
#ifdef CONFIG_COMPAT
long jfs_compat_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
/* While these ioctl numbers defined with 'long' and have different
* numbers than the 64bit ABI,
* the actual implementation only deals with ints and is compatible.
*/
switch (cmd) {
case JFS_IOC_GETFLAGS32:
cmd = JFS_IOC_GETFLAGS;
break;
case JFS_IOC_SETFLAGS32:
cmd = JFS_IOC_SETFLAGS;
break;
}
return jfs_ioctl(filp, cmd, arg);
}
#endif

View File

@ -160,11 +160,4 @@ struct dinode {
#define JFS_FL_USER_MODIFIABLE 0x03F80000
#define JFS_FL_INHERIT 0x03C80000
/* These are identical to EXT[23]_IOC_GETFLAGS/SETFLAGS */
#define JFS_IOC_GETFLAGS _IOR('f', 1, long)
#define JFS_IOC_SETFLAGS _IOW('f', 2, long)
#define JFS_IOC_GETFLAGS32 _IOR('f', 1, int)
#define JFS_IOC_SETFLAGS32 _IOW('f', 2, int)
#endif /*_H_JFS_DINODE */

View File

@ -9,8 +9,10 @@ struct fid;
extern struct inode *ialloc(struct inode *, umode_t);
extern int jfs_fsync(struct file *, loff_t, loff_t, int);
extern int jfs_fileattr_get(struct dentry *dentry, struct fileattr *fa);
extern int jfs_fileattr_set(struct user_namespace *mnt_userns,
struct dentry *dentry, struct fileattr *fa);
extern long jfs_ioctl(struct file *, unsigned int, unsigned long);
extern long jfs_compat_ioctl(struct file *, unsigned int, unsigned long);
extern struct inode *jfs_iget(struct super_block *, unsigned long);
extern int jfs_commit_inode(struct inode *, int);
extern int jfs_write_inode(struct inode *, struct writeback_control *);

View File

@ -1522,6 +1522,8 @@ const struct inode_operations jfs_dir_inode_operations = {
.rename = jfs_rename,
.listxattr = jfs_listxattr,
.setattr = jfs_setattr,
.fileattr_get = jfs_fileattr_get,
.fileattr_set = jfs_fileattr_set,
#ifdef CONFIG_JFS_POSIX_ACL
.get_acl = jfs_get_acl,
.set_acl = jfs_set_acl,
@ -1533,9 +1535,7 @@ const struct file_operations jfs_dir_operations = {
.iterate = jfs_readdir,
.fsync = jfs_fsync,
.unlocked_ioctl = jfs_ioctl,
#ifdef CONFIG_COMPAT
.compat_ioctl = jfs_compat_ioctl,
#endif
.compat_ioctl = compat_ptr_ioctl,
.llseek = generic_file_llseek,
};

View File

@ -148,6 +148,8 @@ const struct inode_operations nilfs_file_inode_operations = {
.setattr = nilfs_setattr,
.permission = nilfs_permission,
.fiemap = nilfs_fiemap,
.fileattr_get = nilfs_fileattr_get,
.fileattr_set = nilfs_fileattr_set,
};
/* end of file */

View File

@ -16,6 +16,7 @@
#include <linux/compat.h> /* compat_ptr() */
#include <linux/mount.h> /* mnt_want_write_file(), mnt_drop_write_file() */
#include <linux/buffer_head.h>
#include <linux/fileattr.h>
#include "nilfs.h"
#include "segment.h"
#include "bmap.h"
@ -113,51 +114,39 @@ static int nilfs_ioctl_wrap_copy(struct the_nilfs *nilfs,
}
/**
* nilfs_ioctl_getflags - ioctl to support lsattr
* nilfs_fileattr_get - ioctl to support lsattr
*/
static int nilfs_ioctl_getflags(struct inode *inode, void __user *argp)
int nilfs_fileattr_get(struct dentry *dentry, struct fileattr *fa)
{
unsigned int flags = NILFS_I(inode)->i_flags & FS_FL_USER_VISIBLE;
struct inode *inode = d_inode(dentry);
return put_user(flags, (int __user *)argp);
fileattr_fill_flags(fa, NILFS_I(inode)->i_flags & FS_FL_USER_VISIBLE);
return 0;
}
/**
* nilfs_ioctl_setflags - ioctl to support chattr
* nilfs_fileattr_set - ioctl to support chattr
*/
static int nilfs_ioctl_setflags(struct inode *inode, struct file *filp,
void __user *argp)
int nilfs_fileattr_set(struct user_namespace *mnt_userns,
struct dentry *dentry, struct fileattr *fa)
{
struct inode *inode = d_inode(dentry);
struct nilfs_transaction_info ti;
unsigned int flags, oldflags;
int ret;
if (!inode_owner_or_capable(&init_user_ns, inode))
return -EACCES;
if (fileattr_has_fsx(fa))
return -EOPNOTSUPP;
if (get_user(flags, (int __user *)argp))
return -EFAULT;
ret = mnt_want_write_file(filp);
if (ret)
return ret;
flags = nilfs_mask_flags(inode->i_mode, flags);
inode_lock(inode);
oldflags = NILFS_I(inode)->i_flags;
ret = vfs_ioc_setflags_prepare(inode, oldflags, flags);
if (ret)
goto out;
flags = nilfs_mask_flags(inode->i_mode, fa->flags);
ret = nilfs_transaction_begin(inode->i_sb, &ti, 0);
if (ret)
goto out;
return ret;
NILFS_I(inode)->i_flags = (oldflags & ~FS_FL_USER_MODIFIABLE) |
(flags & FS_FL_USER_MODIFIABLE);
oldflags = NILFS_I(inode)->i_flags & ~FS_FL_USER_MODIFIABLE;
NILFS_I(inode)->i_flags = oldflags | (flags & FS_FL_USER_MODIFIABLE);
nilfs_set_inode_flags(inode);
inode->i_ctime = current_time(inode);
@ -165,11 +154,7 @@ static int nilfs_ioctl_setflags(struct inode *inode, struct file *filp,
nilfs_set_transaction_flag(NILFS_TI_SYNC);
nilfs_mark_inode_dirty(inode);
ret = nilfs_transaction_commit(inode->i_sb);
out:
inode_unlock(inode);
mnt_drop_write_file(filp);
return ret;
return nilfs_transaction_commit(inode->i_sb);
}
/**
@ -1282,10 +1267,6 @@ long nilfs_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
void __user *argp = (void __user *)arg;
switch (cmd) {
case FS_IOC_GETFLAGS:
return nilfs_ioctl_getflags(inode, argp);
case FS_IOC_SETFLAGS:
return nilfs_ioctl_setflags(inode, filp, argp);
case FS_IOC_GETVERSION:
return nilfs_ioctl_getversion(inode, argp);
case NILFS_IOCTL_CHANGE_CPMODE:
@ -1331,12 +1312,6 @@ long nilfs_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
long nilfs_compat_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
switch (cmd) {
case FS_IOC32_GETFLAGS:
cmd = FS_IOC_GETFLAGS;
break;
case FS_IOC32_SETFLAGS:
cmd = FS_IOC_SETFLAGS;
break;
case FS_IOC32_GETVERSION:
cmd = FS_IOC_GETVERSION;
break;

View File

@ -552,6 +552,8 @@ const struct inode_operations nilfs_dir_inode_operations = {
.setattr = nilfs_setattr,
.permission = nilfs_permission,
.fiemap = nilfs_fiemap,
.fileattr_get = nilfs_fileattr_get,
.fileattr_set = nilfs_fileattr_set,
};
const struct inode_operations nilfs_special_inode_operations = {

View File

@ -243,6 +243,9 @@ extern void nilfs_set_link(struct inode *, struct nilfs_dir_entry *,
extern int nilfs_sync_file(struct file *, loff_t, loff_t, int);
/* ioctl.c */
int nilfs_fileattr_get(struct dentry *dentry, struct fileattr *m);
int nilfs_fileattr_set(struct user_namespace *mnt_userns,
struct dentry *dentry, struct fileattr *fa);
long nilfs_ioctl(struct file *, unsigned int, unsigned long);
long nilfs_compat_ioctl(struct file *file, unsigned int cmd, unsigned long arg);
int nilfs_ioctl_prepare_clean_segments(struct the_nilfs *, struct nilfs_argv *,

View File

@ -2649,6 +2649,8 @@ const struct inode_operations ocfs2_file_iops = {
.fiemap = ocfs2_fiemap,
.get_acl = ocfs2_iop_get_acl,
.set_acl = ocfs2_iop_set_acl,
.fileattr_get = ocfs2_fileattr_get,
.fileattr_set = ocfs2_fileattr_set,
};
const struct inode_operations ocfs2_special_file_iops = {

View File

@ -10,6 +10,7 @@
#include <linux/mount.h>
#include <linux/blkdev.h>
#include <linux/compat.h>
#include <linux/fileattr.h>
#include <cluster/masklog.h>
@ -61,8 +62,10 @@ static inline int o2info_coherent(struct ocfs2_info_request *req)
return (!(req->ir_flags & OCFS2_INFO_FL_NON_COHERENT));
}
static int ocfs2_get_inode_attr(struct inode *inode, unsigned *flags)
int ocfs2_fileattr_get(struct dentry *dentry, struct fileattr *fa)
{
struct inode *inode = d_inode(dentry);
unsigned int flags;
int status;
status = ocfs2_inode_lock(inode, NULL, 0);
@ -71,15 +74,19 @@ static int ocfs2_get_inode_attr(struct inode *inode, unsigned *flags)
return status;
}
ocfs2_get_inode_flags(OCFS2_I(inode));
*flags = OCFS2_I(inode)->ip_attr;
flags = OCFS2_I(inode)->ip_attr;
ocfs2_inode_unlock(inode, 0);
fileattr_fill_flags(fa, flags & OCFS2_FL_VISIBLE);
return status;
}
static int ocfs2_set_inode_attr(struct inode *inode, unsigned flags,
unsigned mask)
int ocfs2_fileattr_set(struct user_namespace *mnt_userns,
struct dentry *dentry, struct fileattr *fa)
{
struct inode *inode = d_inode(dentry);
unsigned int flags = fa->flags;
struct ocfs2_inode_info *ocfs2_inode = OCFS2_I(inode);
struct ocfs2_super *osb = OCFS2_SB(inode->i_sb);
handle_t *handle = NULL;
@ -87,7 +94,8 @@ static int ocfs2_set_inode_attr(struct inode *inode, unsigned flags,
unsigned oldflags;
int status;
inode_lock(inode);
if (fileattr_has_fsx(fa))
return -EOPNOTSUPP;
status = ocfs2_inode_lock(inode, &bh, 1);
if (status < 0) {
@ -95,19 +103,17 @@ static int ocfs2_set_inode_attr(struct inode *inode, unsigned flags,
goto bail;
}
status = -EACCES;
if (!inode_owner_or_capable(&init_user_ns, inode))
goto bail_unlock;
if (!S_ISDIR(inode->i_mode))
flags &= ~OCFS2_DIRSYNC_FL;
oldflags = ocfs2_inode->ip_attr;
flags = flags & mask;
flags |= oldflags & ~mask;
flags = flags & OCFS2_FL_MODIFIABLE;
flags |= oldflags & ~OCFS2_FL_MODIFIABLE;
status = vfs_ioc_setflags_prepare(inode, oldflags, flags);
if (status)
/* Check already done by VFS, but repeat with ocfs lock */
status = -EPERM;
if ((flags ^ oldflags) & (FS_APPEND_FL | FS_IMMUTABLE_FL) &&
!capable(CAP_LINUX_IMMUTABLE))
goto bail_unlock;
handle = ocfs2_start_trans(osb, OCFS2_INODE_UPDATE_CREDITS);
@ -129,8 +135,6 @@ static int ocfs2_set_inode_attr(struct inode *inode, unsigned flags,
bail_unlock:
ocfs2_inode_unlock(inode, 1);
bail:
inode_unlock(inode);
brelse(bh);
return status;
@ -836,7 +840,6 @@ bail:
long ocfs2_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
struct inode *inode = file_inode(filp);
unsigned int flags;
int new_clusters;
int status;
struct ocfs2_space_resv sr;
@ -849,24 +852,6 @@ long ocfs2_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
void __user *argp = (void __user *)arg;
switch (cmd) {
case OCFS2_IOC_GETFLAGS:
status = ocfs2_get_inode_attr(inode, &flags);
if (status < 0)
return status;
flags &= OCFS2_FL_VISIBLE;
return put_user(flags, (int __user *) arg);
case OCFS2_IOC_SETFLAGS:
if (get_user(flags, (int __user *) arg))
return -EFAULT;
status = mnt_want_write_file(filp);
if (status)
return status;
status = ocfs2_set_inode_attr(inode, flags,
OCFS2_FL_MODIFIABLE);
mnt_drop_write_file(filp);
return status;
case OCFS2_IOC_RESVSP:
case OCFS2_IOC_RESVSP64:
case OCFS2_IOC_UNRESVSP:
@ -959,12 +944,6 @@ long ocfs2_compat_ioctl(struct file *file, unsigned cmd, unsigned long arg)
void __user *argp = (void __user *)arg;
switch (cmd) {
case OCFS2_IOC32_GETFLAGS:
cmd = OCFS2_IOC_GETFLAGS;
break;
case OCFS2_IOC32_SETFLAGS:
cmd = OCFS2_IOC_SETFLAGS;
break;
case OCFS2_IOC_RESVSP:
case OCFS2_IOC_RESVSP64:
case OCFS2_IOC_UNRESVSP:

View File

@ -11,6 +11,9 @@
#ifndef OCFS2_IOCTL_PROTO_H
#define OCFS2_IOCTL_PROTO_H
int ocfs2_fileattr_get(struct dentry *dentry, struct fileattr *fa);
int ocfs2_fileattr_set(struct user_namespace *mnt_userns,
struct dentry *dentry, struct fileattr *fa);
long ocfs2_ioctl(struct file *filp, unsigned int cmd, unsigned long arg);
long ocfs2_compat_ioctl(struct file *file, unsigned cmd, unsigned long arg);

View File

@ -50,6 +50,7 @@
#include "xattr.h"
#include "acl.h"
#include "ocfs2_trace.h"
#include "ioctl.h"
#include "buffer_head_io.h"
@ -2918,4 +2919,6 @@ const struct inode_operations ocfs2_dir_iops = {
.fiemap = ocfs2_fiemap,
.get_acl = ocfs2_iop_get_acl,
.set_acl = ocfs2_iop_set_acl,
.fileattr_get = ocfs2_fileattr_get,
.fileattr_set = ocfs2_fileattr_set,
};

View File

@ -12,14 +12,6 @@
#ifndef OCFS2_IOCTL_H
#define OCFS2_IOCTL_H
/*
* ioctl commands
*/
#define OCFS2_IOC_GETFLAGS FS_IOC_GETFLAGS
#define OCFS2_IOC_SETFLAGS FS_IOC_SETFLAGS
#define OCFS2_IOC32_GETFLAGS FS_IOC32_GETFLAGS
#define OCFS2_IOC32_SETFLAGS FS_IOC32_SETFLAGS
/*
* Space reservation / allocation / free ioctls and argument structure
* are designed to be compatible with XFS.

View File

@ -375,84 +375,6 @@ static ssize_t orangefs_file_write_iter(struct kiocb *iocb,
return ret;
}
static int orangefs_getflags(struct inode *inode, unsigned long *uval)
{
__u64 val = 0;
int ret;
ret = orangefs_inode_getxattr(inode,
"user.pvfs2.meta_hint",
&val, sizeof(val));
if (ret < 0 && ret != -ENODATA)
return ret;
else if (ret == -ENODATA)
val = 0;
*uval = val;
return 0;
}
/*
* Perform a miscellaneous operation on a file.
*/
static long orangefs_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
struct inode *inode = file_inode(file);
int ret = -ENOTTY;
__u64 val = 0;
unsigned long uval;
gossip_debug(GOSSIP_FILE_DEBUG,
"orangefs_ioctl: called with cmd %d\n",
cmd);
/*
* we understand some general ioctls on files, such as the immutable
* and append flags
*/
if (cmd == FS_IOC_GETFLAGS) {
ret = orangefs_getflags(inode, &uval);
if (ret)
return ret;
gossip_debug(GOSSIP_FILE_DEBUG,
"orangefs_ioctl: FS_IOC_GETFLAGS: %llu\n",
(unsigned long long)uval);
return put_user(uval, (int __user *)arg);
} else if (cmd == FS_IOC_SETFLAGS) {
unsigned long old_uval;
ret = 0;
if (get_user(uval, (int __user *)arg))
return -EFAULT;
/*
* ORANGEFS_MIRROR_FL is set internally when the mirroring mode
* is turned on for a file. The user is not allowed to turn
* on this bit, but the bit is present if the user first gets
* the flags and then updates the flags with some new
* settings. So, we ignore it in the following edit. bligon.
*/
if ((uval & ~ORANGEFS_MIRROR_FL) &
(~(FS_IMMUTABLE_FL | FS_APPEND_FL | FS_NOATIME_FL))) {
gossip_err("orangefs_ioctl: the FS_IOC_SETFLAGS only supports setting one of FS_IMMUTABLE_FL|FS_APPEND_FL|FS_NOATIME_FL\n");
return -EINVAL;
}
ret = orangefs_getflags(inode, &old_uval);
if (ret)
return ret;
ret = vfs_ioc_setflags_prepare(inode, old_uval, uval);
if (ret)
return ret;
val = uval;
gossip_debug(GOSSIP_FILE_DEBUG,
"orangefs_ioctl: FS_IOC_SETFLAGS: %llu\n",
(unsigned long long)val);
ret = orangefs_inode_setxattr(inode,
"user.pvfs2.meta_hint",
&val, sizeof(val), 0);
}
return ret;
}
static vm_fault_t orangefs_fault(struct vm_fault *vmf)
{
struct file *file = vmf->vma->vm_file;
@ -657,7 +579,6 @@ const struct file_operations orangefs_file_operations = {
.read_iter = orangefs_file_read_iter,
.write_iter = orangefs_file_write_iter,
.lock = orangefs_lock,
.unlocked_ioctl = orangefs_ioctl,
.mmap = orangefs_file_mmap,
.open = generic_file_open,
.splice_read = generic_file_splice_read,

View File

@ -11,6 +11,7 @@
*/
#include <linux/bvec.h>
#include <linux/fileattr.h>
#include "protocol.h"
#include "orangefs-kernel.h"
#include "orangefs-bufmap.h"
@ -954,6 +955,53 @@ int orangefs_update_time(struct inode *inode, struct timespec64 *time, int flags
return __orangefs_setattr(inode, &iattr);
}
static int orangefs_fileattr_get(struct dentry *dentry, struct fileattr *fa)
{
u64 val = 0;
int ret;
gossip_debug(GOSSIP_FILE_DEBUG, "%s: called on %pd\n", __func__,
dentry);
ret = orangefs_inode_getxattr(d_inode(dentry),
"user.pvfs2.meta_hint",
&val, sizeof(val));
if (ret < 0 && ret != -ENODATA)
return ret;
gossip_debug(GOSSIP_FILE_DEBUG, "%s: flags=%u\n", __func__, (u32) val);
fileattr_fill_flags(fa, val);
return 0;
}
static int orangefs_fileattr_set(struct user_namespace *mnt_userns,
struct dentry *dentry, struct fileattr *fa)
{
u64 val = 0;
gossip_debug(GOSSIP_FILE_DEBUG, "%s: called on %pd\n", __func__,
dentry);
/*
* ORANGEFS_MIRROR_FL is set internally when the mirroring mode is
* turned on for a file. The user is not allowed to turn on this bit,
* but the bit is present if the user first gets the flags and then
* updates the flags with some new settings. So, we ignore it in the
* following edit. bligon.
*/
if (fileattr_has_fsx(fa) ||
(fa->flags & ~(FS_IMMUTABLE_FL | FS_APPEND_FL | FS_NOATIME_FL | ORANGEFS_MIRROR_FL))) {
gossip_err("%s: only supports setting one of FS_IMMUTABLE_FL|FS_APPEND_FL|FS_NOATIME_FL\n",
__func__);
return -EOPNOTSUPP;
}
val = fa->flags;
gossip_debug(GOSSIP_FILE_DEBUG, "%s: flags=%u\n", __func__, (u32) val);
return orangefs_inode_setxattr(d_inode(dentry),
"user.pvfs2.meta_hint",
&val, sizeof(val), 0);
}
/* ORANGEFS2 implementation of VFS inode operations for files */
static const struct inode_operations orangefs_file_inode_operations = {
.get_acl = orangefs_get_acl,
@ -963,6 +1011,8 @@ static const struct inode_operations orangefs_file_inode_operations = {
.listxattr = orangefs_listxattr,
.permission = orangefs_permission,
.update_time = orangefs_update_time,
.fileattr_get = orangefs_fileattr_get,
.fileattr_set = orangefs_fileattr_set,
};
static int orangefs_init_iops(struct inode *inode)

View File

@ -1301,4 +1301,6 @@ const struct inode_operations ovl_dir_inode_operations = {
.listxattr = ovl_listxattr,
.get_acl = ovl_get_acl,
.update_time = ovl_update_time,
.fileattr_get = ovl_fileattr_get,
.fileattr_set = ovl_fileattr_set,
};

View File

@ -482,112 +482,6 @@ static int ovl_fadvise(struct file *file, loff_t offset, loff_t len, int advice)
return ret;
}
static long ovl_real_ioctl(struct file *file, unsigned int cmd,
unsigned long arg)
{
struct fd real;
long ret;
ret = ovl_real_fdget(file, &real);
if (ret)
return ret;
ret = security_file_ioctl(real.file, cmd, arg);
if (!ret) {
/*
* Don't override creds, since we currently can't safely check
* permissions before doing so.
*/
ret = vfs_ioctl(real.file, cmd, arg);
}
fdput(real);
return ret;
}
static long ovl_ioctl_set_flags(struct file *file, unsigned int cmd,
unsigned long arg)
{
long ret;
struct inode *inode = file_inode(file);
if (!inode_owner_or_capable(&init_user_ns, inode))
return -EACCES;
ret = mnt_want_write_file(file);
if (ret)
return ret;
inode_lock(inode);
/*
* Prevent copy up if immutable and has no CAP_LINUX_IMMUTABLE
* capability.
*/
ret = -EPERM;
if (!ovl_has_upperdata(inode) && IS_IMMUTABLE(inode) &&
!capable(CAP_LINUX_IMMUTABLE))
goto unlock;
ret = ovl_maybe_copy_up(file_dentry(file), O_WRONLY);
if (ret)
goto unlock;
ret = ovl_real_ioctl(file, cmd, arg);
ovl_copyflags(ovl_inode_real(inode), inode);
unlock:
inode_unlock(inode);
mnt_drop_write_file(file);
return ret;
}
long ovl_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
long ret;
switch (cmd) {
case FS_IOC_GETFLAGS:
case FS_IOC_FSGETXATTR:
ret = ovl_real_ioctl(file, cmd, arg);
break;
case FS_IOC_FSSETXATTR:
case FS_IOC_SETFLAGS:
ret = ovl_ioctl_set_flags(file, cmd, arg);
break;
default:
ret = -ENOTTY;
}
return ret;
}
#ifdef CONFIG_COMPAT
long ovl_compat_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
switch (cmd) {
case FS_IOC32_GETFLAGS:
cmd = FS_IOC_GETFLAGS;
break;
case FS_IOC32_SETFLAGS:
cmd = FS_IOC_SETFLAGS;
break;
default:
return -ENOIOCTLCMD;
}
return ovl_ioctl(file, cmd, arg);
}
#endif
enum ovl_copyop {
OVL_COPY,
OVL_CLONE,
@ -687,10 +581,6 @@ const struct file_operations ovl_file_operations = {
.mmap = ovl_mmap,
.fallocate = ovl_fallocate,
.fadvise = ovl_fadvise,
.unlocked_ioctl = ovl_ioctl,
#ifdef CONFIG_COMPAT
.compat_ioctl = ovl_compat_ioctl,
#endif
.splice_read = generic_file_splice_read,
.splice_write = iter_file_splice_write,

View File

@ -11,6 +11,8 @@
#include <linux/posix_acl.h>
#include <linux/ratelimit.h>
#include <linux/fiemap.h>
#include <linux/fileattr.h>
#include <linux/security.h>
#include "overlayfs.h"
@ -500,6 +502,79 @@ static int ovl_fiemap(struct inode *inode, struct fiemap_extent_info *fieinfo,
return err;
}
/*
* Work around the fact that security_file_ioctl() takes a file argument.
* Introducing security_inode_fileattr_get/set() hooks would solve this issue
* properly.
*/
static int ovl_security_fileattr(struct dentry *dentry, struct fileattr *fa,
bool set)
{
struct path realpath;
struct file *file;
unsigned int cmd;
int err;
ovl_path_real(dentry, &realpath);
file = dentry_open(&realpath, O_RDONLY, current_cred());
if (IS_ERR(file))
return PTR_ERR(file);
if (set)
cmd = fa->fsx_valid ? FS_IOC_FSSETXATTR : FS_IOC_SETFLAGS;
else
cmd = fa->fsx_valid ? FS_IOC_FSGETXATTR : FS_IOC_GETFLAGS;
err = security_file_ioctl(file, cmd, 0);
fput(file);
return err;
}
int ovl_fileattr_set(struct user_namespace *mnt_userns,
struct dentry *dentry, struct fileattr *fa)
{
struct inode *inode = d_inode(dentry);
struct dentry *upperdentry;
const struct cred *old_cred;
int err;
err = ovl_want_write(dentry);
if (err)
goto out;
err = ovl_copy_up(dentry);
if (!err) {
upperdentry = ovl_dentry_upper(dentry);
old_cred = ovl_override_creds(inode->i_sb);
err = ovl_security_fileattr(dentry, fa, true);
if (!err)
err = vfs_fileattr_set(&init_user_ns, upperdentry, fa);
revert_creds(old_cred);
ovl_copyflags(ovl_inode_real(inode), inode);
}
ovl_drop_write(dentry);
out:
return err;
}
int ovl_fileattr_get(struct dentry *dentry, struct fileattr *fa)
{
struct inode *inode = d_inode(dentry);
struct dentry *realdentry = ovl_dentry_real(dentry);
const struct cred *old_cred;
int err;
old_cred = ovl_override_creds(inode->i_sb);
err = ovl_security_fileattr(dentry, fa, false);
if (!err)
err = vfs_fileattr_get(realdentry, fa);
revert_creds(old_cred);
return err;
}
static const struct inode_operations ovl_file_inode_operations = {
.setattr = ovl_setattr,
.permission = ovl_permission,
@ -508,6 +583,8 @@ static const struct inode_operations ovl_file_inode_operations = {
.get_acl = ovl_get_acl,
.update_time = ovl_update_time,
.fiemap = ovl_fiemap,
.fileattr_get = ovl_fileattr_get,
.fileattr_set = ovl_fileattr_set,
};
static const struct inode_operations ovl_symlink_inode_operations = {

View File

@ -519,8 +519,9 @@ struct dentry *ovl_create_temp(struct dentry *workdir, struct ovl_cattr *attr);
extern const struct file_operations ovl_file_operations;
int __init ovl_aio_request_cache_init(void);
void ovl_aio_request_cache_destroy(void);
long ovl_ioctl(struct file *file, unsigned int cmd, unsigned long arg);
long ovl_compat_ioctl(struct file *file, unsigned int cmd, unsigned long arg);
int ovl_fileattr_get(struct dentry *dentry, struct fileattr *fa);
int ovl_fileattr_set(struct user_namespace *mnt_userns,
struct dentry *dentry, struct fileattr *fa);
/* copy_up.c */
int ovl_copy_up(struct dentry *dentry);

View File

@ -963,10 +963,6 @@ const struct file_operations ovl_dir_operations = {
.llseek = ovl_dir_llseek,
.fsync = ovl_dir_fsync,
.release = ovl_dir_release,
.unlocked_ioctl = ovl_ioctl,
#ifdef CONFIG_COMPAT
.compat_ioctl = ovl_compat_ioctl,
#endif
};
int ovl_check_empty_dir(struct dentry *dentry, struct list_head *list)

View File

@ -258,4 +258,6 @@ const struct inode_operations reiserfs_file_inode_operations = {
.permission = reiserfs_permission,
.get_acl = reiserfs_get_acl,
.set_acl = reiserfs_set_acl,
.fileattr_get = reiserfs_fileattr_get,
.fileattr_set = reiserfs_fileattr_set,
};

View File

@ -10,6 +10,59 @@
#include <linux/uaccess.h>
#include <linux/pagemap.h>
#include <linux/compat.h>
#include <linux/fileattr.h>
int reiserfs_fileattr_get(struct dentry *dentry, struct fileattr *fa)
{
struct inode *inode = d_inode(dentry);
if (!reiserfs_attrs(inode->i_sb))
return -ENOTTY;
fileattr_fill_flags(fa, REISERFS_I(inode)->i_attrs);
return 0;
}
int reiserfs_fileattr_set(struct user_namespace *mnt_userns,
struct dentry *dentry, struct fileattr *fa)
{
struct inode *inode = d_inode(dentry);
unsigned int flags = fa->flags;
int err;
reiserfs_write_lock(inode->i_sb);
err = -ENOTTY;
if (!reiserfs_attrs(inode->i_sb))
goto unlock;
err = -EOPNOTSUPP;
if (fileattr_has_fsx(fa))
goto unlock;
/*
* Is it quota file? Do not allow user to mess with it
*/
err = -EPERM;
if (IS_NOQUOTA(inode))
goto unlock;
if ((flags & REISERFS_NOTAIL_FL) && S_ISREG(inode->i_mode)) {
err = reiserfs_unpack(inode);
if (err)
goto unlock;
}
sd_attrs_to_i_attrs(flags, inode);
REISERFS_I(inode)->i_attrs = flags;
inode->i_ctime = current_time(inode);
mark_inode_dirty(inode);
err = 0;
unlock:
reiserfs_write_unlock(inode->i_sb);
return err;
}
/*
* reiserfs_ioctl - handler for ioctl for inode
@ -23,7 +76,6 @@
long reiserfs_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
struct inode *inode = file_inode(filp);
unsigned int flags;
int err = 0;
reiserfs_write_lock(inode->i_sb);
@ -32,7 +84,7 @@ long reiserfs_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
case REISERFS_IOC_UNPACK:
if (S_ISREG(inode->i_mode)) {
if (arg)
err = reiserfs_unpack(inode, filp);
err = reiserfs_unpack(inode);
} else
err = -ENOTTY;
break;
@ -40,63 +92,6 @@ long reiserfs_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
* following two cases are taken from fs/ext2/ioctl.c by Remy
* Card (card@masi.ibp.fr)
*/
case REISERFS_IOC_GETFLAGS:
if (!reiserfs_attrs(inode->i_sb)) {
err = -ENOTTY;
break;
}
flags = REISERFS_I(inode)->i_attrs;
err = put_user(flags, (int __user *)arg);
break;
case REISERFS_IOC_SETFLAGS:{
if (!reiserfs_attrs(inode->i_sb)) {
err = -ENOTTY;
break;
}
err = mnt_want_write_file(filp);
if (err)
break;
if (!inode_owner_or_capable(&init_user_ns, inode)) {
err = -EPERM;
goto setflags_out;
}
if (get_user(flags, (int __user *)arg)) {
err = -EFAULT;
goto setflags_out;
}
/*
* Is it quota file? Do not allow user to mess with it
*/
if (IS_NOQUOTA(inode)) {
err = -EPERM;
goto setflags_out;
}
err = vfs_ioc_setflags_prepare(inode,
REISERFS_I(inode)->i_attrs,
flags);
if (err)
goto setflags_out;
if ((flags & REISERFS_NOTAIL_FL) &&
S_ISREG(inode->i_mode)) {
int result;
result = reiserfs_unpack(inode, filp);
if (result) {
err = result;
goto setflags_out;
}
}
sd_attrs_to_i_attrs(flags, inode);
REISERFS_I(inode)->i_attrs = flags;
inode->i_ctime = current_time(inode);
mark_inode_dirty(inode);
setflags_out:
mnt_drop_write_file(filp);
break;
}
case REISERFS_IOC_GETVERSION:
err = put_user(inode->i_generation, (int __user *)arg);
break;
@ -138,12 +133,6 @@ long reiserfs_compat_ioctl(struct file *file, unsigned int cmd,
case REISERFS_IOC32_UNPACK:
cmd = REISERFS_IOC_UNPACK;
break;
case REISERFS_IOC32_GETFLAGS:
cmd = REISERFS_IOC_GETFLAGS;
break;
case REISERFS_IOC32_SETFLAGS:
cmd = REISERFS_IOC_SETFLAGS;
break;
case REISERFS_IOC32_GETVERSION:
cmd = REISERFS_IOC_GETVERSION;
break;
@ -165,7 +154,7 @@ int reiserfs_commit_write(struct file *f, struct page *page,
* Function try to convert tail from direct item into indirect.
* It set up nopack attribute in the REISERFS_I(inode)->nopack
*/
int reiserfs_unpack(struct inode *inode, struct file *filp)
int reiserfs_unpack(struct inode *inode)
{
int retval = 0;
int index;

View File

@ -1660,6 +1660,8 @@ const struct inode_operations reiserfs_dir_inode_operations = {
.permission = reiserfs_permission,
.get_acl = reiserfs_get_acl,
.set_acl = reiserfs_set_acl,
.fileattr_get = reiserfs_fileattr_get,
.fileattr_set = reiserfs_fileattr_set,
};
/*

View File

@ -18,8 +18,6 @@
/* the 32 bit compat definitions with int argument */
#define REISERFS_IOC32_UNPACK _IOW(0xCD, 1, int)
#define REISERFS_IOC32_GETFLAGS FS_IOC32_GETFLAGS
#define REISERFS_IOC32_SETFLAGS FS_IOC32_SETFLAGS
#define REISERFS_IOC32_GETVERSION FS_IOC32_GETVERSION
#define REISERFS_IOC32_SETVERSION FS_IOC32_SETVERSION
@ -3408,7 +3406,10 @@ __u32 r5_hash(const signed char *msg, int len);
#define SPARE_SPACE 500
/* prototypes from ioctl.c */
int reiserfs_fileattr_get(struct dentry *dentry, struct fileattr *fa);
int reiserfs_fileattr_set(struct user_namespace *mnt_userns,
struct dentry *dentry, struct fileattr *fa);
long reiserfs_ioctl(struct file *filp, unsigned int cmd, unsigned long arg);
long reiserfs_compat_ioctl(struct file *filp,
unsigned int cmd, unsigned long arg);
int reiserfs_unpack(struct inode *inode, struct file *filp);
int reiserfs_unpack(struct inode *inode);

View File

@ -2408,7 +2408,7 @@ static int reiserfs_quota_on(struct super_block *sb, int type, int format_id,
* IO to work
*/
if (!(REISERFS_I(inode)->i_flags & i_nopack_mask)) {
err = reiserfs_unpack(inode, NULL);
err = reiserfs_unpack(inode);
if (err) {
reiserfs_warning(sb, "super-6520",
"Unpacking tail of quota file failed"

View File

@ -1637,6 +1637,8 @@ const struct inode_operations ubifs_dir_inode_operations = {
.listxattr = ubifs_listxattr,
.update_time = ubifs_update_time,
.tmpfile = ubifs_tmpfile,
.fileattr_get = ubifs_fileattr_get,
.fileattr_set = ubifs_fileattr_set,
};
const struct file_operations ubifs_dir_operations = {

View File

@ -1648,6 +1648,8 @@ const struct inode_operations ubifs_file_inode_operations = {
.getattr = ubifs_getattr,
.listxattr = ubifs_listxattr,
.update_time = ubifs_update_time,
.fileattr_get = ubifs_fileattr_get,
.fileattr_set = ubifs_fileattr_set,
};
const struct inode_operations ubifs_symlink_inode_operations = {

View File

@ -14,6 +14,7 @@
#include <linux/compat.h>
#include <linux/mount.h>
#include <linux/fileattr.h>
#include "ubifs.h"
/* Need to be kept consistent with checked flags in ioctl2ubifs() */
@ -103,7 +104,7 @@ static int ubifs2ioctl(int ubifs_flags)
static int setflags(struct inode *inode, int flags)
{
int oldflags, err, release;
int err, release;
struct ubifs_inode *ui = ubifs_inode(inode);
struct ubifs_info *c = inode->i_sb->s_fs_info;
struct ubifs_budget_req req = { .dirtied_ino = 1,
@ -114,11 +115,6 @@ static int setflags(struct inode *inode, int flags)
return err;
mutex_lock(&ui->ui_mutex);
oldflags = ubifs2ioctl(ui->flags);
err = vfs_ioc_setflags_prepare(inode, oldflags, flags);
if (err)
goto out_unlock;
ui->flags &= ~ioctl2ubifs(UBIFS_SETTABLE_IOCTL_FLAGS);
ui->flags |= ioctl2ubifs(flags);
ubifs_set_inode_flags(inode);
@ -132,54 +128,52 @@ static int setflags(struct inode *inode, int flags)
if (IS_SYNC(inode))
err = write_inode_now(inode, 1);
return err;
}
out_unlock:
mutex_unlock(&ui->ui_mutex);
ubifs_release_budget(c, &req);
return err;
int ubifs_fileattr_get(struct dentry *dentry, struct fileattr *fa)
{
struct inode *inode = d_inode(dentry);
int flags = ubifs2ioctl(ubifs_inode(inode)->flags);
if (d_is_special(dentry))
return -ENOTTY;
dbg_gen("get flags: %#x, i_flags %#x", flags, inode->i_flags);
fileattr_fill_flags(fa, flags);
return 0;
}
int ubifs_fileattr_set(struct user_namespace *mnt_userns,
struct dentry *dentry, struct fileattr *fa)
{
struct inode *inode = d_inode(dentry);
int flags = fa->flags;
if (d_is_special(dentry))
return -ENOTTY;
if (fileattr_has_fsx(fa))
return -EOPNOTSUPP;
if (flags & ~UBIFS_GETTABLE_IOCTL_FLAGS)
return -EOPNOTSUPP;
flags &= UBIFS_SETTABLE_IOCTL_FLAGS;
if (!S_ISDIR(inode->i_mode))
flags &= ~FS_DIRSYNC_FL;
dbg_gen("set flags: %#x, i_flags %#x", flags, inode->i_flags);
return setflags(inode, flags);
}
long ubifs_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
int flags, err;
int err;
struct inode *inode = file_inode(file);
switch (cmd) {
case FS_IOC_GETFLAGS:
flags = ubifs2ioctl(ubifs_inode(inode)->flags);
dbg_gen("get flags: %#x, i_flags %#x", flags, inode->i_flags);
return put_user(flags, (int __user *) arg);
case FS_IOC_SETFLAGS: {
if (IS_RDONLY(inode))
return -EROFS;
if (!inode_owner_or_capable(&init_user_ns, inode))
return -EACCES;
if (get_user(flags, (int __user *) arg))
return -EFAULT;
if (flags & ~UBIFS_GETTABLE_IOCTL_FLAGS)
return -EOPNOTSUPP;
flags &= UBIFS_SETTABLE_IOCTL_FLAGS;
if (!S_ISDIR(inode->i_mode))
flags &= ~FS_DIRSYNC_FL;
/*
* Make sure the file-system is read-write and make sure it
* will not become read-only while we are changing the flags.
*/
err = mnt_want_write_file(file);
if (err)
return err;
dbg_gen("set flags: %#x, i_flags %#x", flags, inode->i_flags);
err = setflags(inode, flags);
mnt_drop_write_file(file);
return err;
}
case FS_IOC_SET_ENCRYPTION_POLICY: {
struct ubifs_info *c = inode->i_sb->s_fs_info;

View File

@ -2053,6 +2053,9 @@ int ubifs_recover_size(struct ubifs_info *c, bool in_place);
void ubifs_destroy_size_tree(struct ubifs_info *c);
/* ioctl.c */
int ubifs_fileattr_get(struct dentry *dentry, struct fileattr *fa);
int ubifs_fileattr_set(struct user_namespace *mnt_userns,
struct dentry *dentry, struct fileattr *fa);
long ubifs_ioctl(struct file *file, unsigned int cmd, unsigned long arg);
void ubifs_set_inode_flags(struct inode *inode);
#ifdef CONFIG_COMPAT

View File

@ -770,8 +770,6 @@ struct xfs_scrub_metadata {
/*
* ioctl commands that are used by Linux filesystems
*/
#define XFS_IOC_GETXFLAGS FS_IOC_GETFLAGS
#define XFS_IOC_SETXFLAGS FS_IOC_SETFLAGS
#define XFS_IOC_GETVERSION FS_IOC_GETVERSION
/*
@ -782,8 +780,6 @@ struct xfs_scrub_metadata {
#define XFS_IOC_ALLOCSP _IOW ('X', 10, struct xfs_flock64)
#define XFS_IOC_FREESP _IOW ('X', 11, struct xfs_flock64)
#define XFS_IOC_DIOINFO _IOR ('X', 30, struct dioattr)
#define XFS_IOC_FSGETXATTR FS_IOC_FSGETXATTR
#define XFS_IOC_FSSETXATTR FS_IOC_FSSETXATTR
#define XFS_IOC_ALLOCSP64 _IOW ('X', 36, struct xfs_flock64)
#define XFS_IOC_FREESP64 _IOW ('X', 37, struct xfs_flock64)
#define XFS_IOC_GETBMAP _IOWR('X', 38, struct getbmap)

View File

@ -40,6 +40,7 @@
#include <linux/mount.h>
#include <linux/namei.h>
#include <linux/fileattr.h>
/*
* xfs_find_handle maps from userspace xfs_fsop_handlereq structure to
@ -1053,73 +1054,15 @@ xfs_ioc_ag_geometry(
* Linux extended inode flags interface.
*/
STATIC unsigned int
xfs_merge_ioc_xflags(
unsigned int flags,
unsigned int start)
{
unsigned int xflags = start;
if (flags & FS_IMMUTABLE_FL)
xflags |= FS_XFLAG_IMMUTABLE;
else
xflags &= ~FS_XFLAG_IMMUTABLE;
if (flags & FS_APPEND_FL)
xflags |= FS_XFLAG_APPEND;
else
xflags &= ~FS_XFLAG_APPEND;
if (flags & FS_SYNC_FL)
xflags |= FS_XFLAG_SYNC;
else
xflags &= ~FS_XFLAG_SYNC;
if (flags & FS_NOATIME_FL)
xflags |= FS_XFLAG_NOATIME;
else
xflags &= ~FS_XFLAG_NOATIME;
if (flags & FS_NODUMP_FL)
xflags |= FS_XFLAG_NODUMP;
else
xflags &= ~FS_XFLAG_NODUMP;
if (flags & FS_DAX_FL)
xflags |= FS_XFLAG_DAX;
else
xflags &= ~FS_XFLAG_DAX;
return xflags;
}
STATIC unsigned int
xfs_di2lxflags(
uint16_t di_flags,
uint64_t di_flags2)
{
unsigned int flags = 0;
if (di_flags & XFS_DIFLAG_IMMUTABLE)
flags |= FS_IMMUTABLE_FL;
if (di_flags & XFS_DIFLAG_APPEND)
flags |= FS_APPEND_FL;
if (di_flags & XFS_DIFLAG_SYNC)
flags |= FS_SYNC_FL;
if (di_flags & XFS_DIFLAG_NOATIME)
flags |= FS_NOATIME_FL;
if (di_flags & XFS_DIFLAG_NODUMP)
flags |= FS_NODUMP_FL;
if (di_flags2 & XFS_DIFLAG2_DAX) {
flags |= FS_DAX_FL;
}
return flags;
}
static void
xfs_fill_fsxattr(
struct xfs_inode *ip,
bool attr,
struct fsxattr *fa)
int whichfork,
struct fileattr *fa)
{
struct xfs_ifork *ifp = attr ? ip->i_afp : &ip->i_df;
struct xfs_ifork *ifp = XFS_IFORK_PTR(ip, whichfork);
simple_fill_fsxattr(fa, xfs_ip2xflags(ip));
fileattr_fill_xflags(fa, xfs_ip2xflags(ip));
fa->fsx_extsize = ip->i_d.di_extsize << ip->i_mount->m_sb.sb_blocklog;
fa->fsx_cowextsize = ip->i_d.di_cowextsize <<
ip->i_mount->m_sb.sb_blocklog;
@ -1131,19 +1074,33 @@ xfs_fill_fsxattr(
}
STATIC int
xfs_ioc_fsgetxattr(
xfs_ioc_fsgetxattra(
xfs_inode_t *ip,
int attr,
void __user *arg)
{
struct fsxattr fa;
struct fileattr fa;
xfs_ilock(ip, XFS_ILOCK_SHARED);
xfs_fill_fsxattr(ip, attr, &fa);
xfs_fill_fsxattr(ip, XFS_ATTR_FORK, &fa);
xfs_iunlock(ip, XFS_ILOCK_SHARED);
return copy_fsxattr_to_user(&fa, arg);
}
int
xfs_fileattr_get(
struct dentry *dentry,
struct fileattr *fa)
{
struct xfs_inode *ip = XFS_I(d_inode(dentry));
if (d_is_special(dentry))
return -ENOTTY;
xfs_ilock(ip, XFS_ILOCK_SHARED);
xfs_fill_fsxattr(ip, XFS_DATA_FORK, fa);
xfs_iunlock(ip, XFS_ILOCK_SHARED);
if (copy_to_user(arg, &fa, sizeof(fa)))
return -EFAULT;
return 0;
}
@ -1210,7 +1167,7 @@ static int
xfs_ioctl_setattr_xflags(
struct xfs_trans *tp,
struct xfs_inode *ip,
struct fsxattr *fa)
struct fileattr *fa)
{
struct xfs_mount *mp = ip->i_mount;
uint64_t di_flags2;
@ -1253,7 +1210,7 @@ xfs_ioctl_setattr_xflags(
static void
xfs_ioctl_setattr_prepare_dax(
struct xfs_inode *ip,
struct fsxattr *fa)
struct fileattr *fa)
{
struct xfs_mount *mp = ip->i_mount;
struct inode *inode = VFS_I(ip);
@ -1280,10 +1237,9 @@ xfs_ioctl_setattr_prepare_dax(
*/
static struct xfs_trans *
xfs_ioctl_setattr_get_trans(
struct file *file,
struct xfs_inode *ip,
struct xfs_dquot *pdqp)
{
struct xfs_inode *ip = XFS_I(file_inode(file));
struct xfs_mount *mp = ip->i_mount;
struct xfs_trans *tp;
int error = -EROFS;
@ -1299,24 +1255,11 @@ xfs_ioctl_setattr_get_trans(
if (error)
goto out_error;
/*
* CAP_FOWNER overrides the following restrictions:
*
* The user ID of the calling process must be equal to the file owner
* ID, except in cases where the CAP_FSETID capability is applicable.
*/
if (!inode_owner_or_capable(file_mnt_user_ns(file), VFS_I(ip))) {
error = -EPERM;
goto out_cancel;
}
if (mp->m_flags & XFS_MOUNT_WSYNC)
xfs_trans_set_sync(tp);
return tp;
out_cancel:
xfs_trans_cancel(tp);
out_error:
return ERR_PTR(error);
}
@ -1340,12 +1283,15 @@ out_error:
static int
xfs_ioctl_setattr_check_extsize(
struct xfs_inode *ip,
struct fsxattr *fa)
struct fileattr *fa)
{
struct xfs_mount *mp = ip->i_mount;
xfs_extlen_t size;
xfs_fsblock_t extsize_fsb;
if (!fa->fsx_valid)
return 0;
if (S_ISREG(VFS_I(ip)->i_mode) && ip->i_df.if_nextents &&
((ip->i_d.di_extsize << mp->m_sb.sb_blocklog) != fa->fsx_extsize))
return -EINVAL;
@ -1390,12 +1336,15 @@ xfs_ioctl_setattr_check_extsize(
static int
xfs_ioctl_setattr_check_cowextsize(
struct xfs_inode *ip,
struct fsxattr *fa)
struct fileattr *fa)
{
struct xfs_mount *mp = ip->i_mount;
xfs_extlen_t size;
xfs_fsblock_t cowextsize_fsb;
if (!fa->fsx_valid)
return 0;
if (!(fa->fsx_xflags & FS_XFLAG_COWEXTSIZE))
return 0;
@ -1422,8 +1371,11 @@ xfs_ioctl_setattr_check_cowextsize(
static int
xfs_ioctl_setattr_check_projid(
struct xfs_inode *ip,
struct fsxattr *fa)
struct fileattr *fa)
{
if (!fa->fsx_valid)
return 0;
/* Disallow 32bit project ids if projid32bit feature is not enabled. */
if (fa->fsx_projid > (uint16_t)-1 &&
!xfs_sb_version_hasprojid32bit(&ip->i_mount->m_sb))
@ -1431,14 +1383,13 @@ xfs_ioctl_setattr_check_projid(
return 0;
}
STATIC int
xfs_ioctl_setattr(
struct file *file,
struct fsxattr *fa)
int
xfs_fileattr_set(
struct user_namespace *mnt_userns,
struct dentry *dentry,
struct fileattr *fa)
{
struct user_namespace *mnt_userns = file_mnt_user_ns(file);
struct xfs_inode *ip = XFS_I(file_inode(file));
struct fsxattr old_fa;
struct xfs_inode *ip = XFS_I(d_inode(dentry));
struct xfs_mount *mp = ip->i_mount;
struct xfs_trans *tp;
struct xfs_dquot *pdqp = NULL;
@ -1447,6 +1398,16 @@ xfs_ioctl_setattr(
trace_xfs_ioctl_setattr(ip);
if (d_is_special(dentry))
return -ENOTTY;
if (!fa->fsx_valid) {
if (fa->flags & ~(FS_IMMUTABLE_FL | FS_APPEND_FL |
FS_NOATIME_FL | FS_NODUMP_FL |
FS_SYNC_FL | FS_DAX_FL | FS_PROJINHERIT_FL))
return -EOPNOTSUPP;
}
error = xfs_ioctl_setattr_check_projid(ip, fa);
if (error)
return error;
@ -1459,7 +1420,7 @@ xfs_ioctl_setattr(
* If the IDs do change before we take the ilock, we're covered
* because the i_*dquot fields will get updated anyway.
*/
if (XFS_IS_QUOTA_ON(mp)) {
if (fa->fsx_valid && XFS_IS_QUOTA_ON(mp)) {
error = xfs_qm_vop_dqalloc(ip, VFS_I(ip)->i_uid,
VFS_I(ip)->i_gid, fa->fsx_projid,
XFS_QMOPT_PQUOTA, NULL, NULL, &pdqp);
@ -1469,17 +1430,12 @@ xfs_ioctl_setattr(
xfs_ioctl_setattr_prepare_dax(ip, fa);
tp = xfs_ioctl_setattr_get_trans(file, pdqp);
tp = xfs_ioctl_setattr_get_trans(ip, pdqp);
if (IS_ERR(tp)) {
error = PTR_ERR(tp);
goto error_free_dquots;
}
xfs_fill_fsxattr(ip, false, &old_fa);
error = vfs_ioc_fssetxattr_check(VFS_I(ip), &old_fa, fa);
if (error)
goto error_trans_cancel;
error = xfs_ioctl_setattr_check_extsize(ip, fa);
if (error)
goto error_trans_cancel;
@ -1492,6 +1448,8 @@ xfs_ioctl_setattr(
if (error)
goto error_trans_cancel;
if (!fa->fsx_valid)
goto skip_xattr;
/*
* Change file ownership. Must be the owner or privileged. CAP_FSETID
* overrides the following restrictions:
@ -1529,6 +1487,7 @@ xfs_ioctl_setattr(
else
ip->i_d.di_cowextsize = 0;
skip_xattr:
error = xfs_trans_commit(tp);
/*
@ -1546,91 +1505,6 @@ error_free_dquots:
return error;
}
STATIC int
xfs_ioc_fssetxattr(
struct file *filp,
void __user *arg)
{
struct fsxattr fa;
int error;
if (copy_from_user(&fa, arg, sizeof(fa)))
return -EFAULT;
error = mnt_want_write_file(filp);
if (error)
return error;
error = xfs_ioctl_setattr(filp, &fa);
mnt_drop_write_file(filp);
return error;
}
STATIC int
xfs_ioc_getxflags(
xfs_inode_t *ip,
void __user *arg)
{
unsigned int flags;
flags = xfs_di2lxflags(ip->i_d.di_flags, ip->i_d.di_flags2);
if (copy_to_user(arg, &flags, sizeof(flags)))
return -EFAULT;
return 0;
}
STATIC int
xfs_ioc_setxflags(
struct xfs_inode *ip,
struct file *filp,
void __user *arg)
{
struct xfs_trans *tp;
struct fsxattr fa;
struct fsxattr old_fa;
unsigned int flags;
int error;
if (copy_from_user(&flags, arg, sizeof(flags)))
return -EFAULT;
if (flags & ~(FS_IMMUTABLE_FL | FS_APPEND_FL | \
FS_NOATIME_FL | FS_NODUMP_FL | \
FS_SYNC_FL | FS_DAX_FL))
return -EOPNOTSUPP;
fa.fsx_xflags = xfs_merge_ioc_xflags(flags, xfs_ip2xflags(ip));
error = mnt_want_write_file(filp);
if (error)
return error;
xfs_ioctl_setattr_prepare_dax(ip, &fa);
tp = xfs_ioctl_setattr_get_trans(filp, NULL);
if (IS_ERR(tp)) {
error = PTR_ERR(tp);
goto out_drop_write;
}
xfs_fill_fsxattr(ip, false, &old_fa);
error = vfs_ioc_fssetxattr_check(VFS_I(ip), &old_fa, &fa);
if (error) {
xfs_trans_cancel(tp);
goto out_drop_write;
}
error = xfs_ioctl_setattr_xflags(tp, ip, &fa);
if (error) {
xfs_trans_cancel(tp);
goto out_drop_write;
}
error = xfs_trans_commit(tp);
out_drop_write:
mnt_drop_write_file(filp);
return error;
}
static bool
xfs_getbmap_format(
struct kgetbmap *p,
@ -2137,16 +2011,8 @@ xfs_file_ioctl(
case XFS_IOC_GETVERSION:
return put_user(inode->i_generation, (int __user *)arg);
case XFS_IOC_FSGETXATTR:
return xfs_ioc_fsgetxattr(ip, 0, arg);
case XFS_IOC_FSGETXATTRA:
return xfs_ioc_fsgetxattr(ip, 1, arg);
case XFS_IOC_FSSETXATTR:
return xfs_ioc_fssetxattr(filp, arg);
case XFS_IOC_GETXFLAGS:
return xfs_ioc_getxflags(ip, arg);
case XFS_IOC_SETXFLAGS:
return xfs_ioc_setxflags(ip, filp, arg);
return xfs_ioc_fsgetxattra(ip, arg);
case XFS_IOC_GETBMAP:
case XFS_IOC_GETBMAPA:

View File

@ -47,6 +47,17 @@ xfs_handle_to_dentry(
void __user *uhandle,
u32 hlen);
extern int
xfs_fileattr_get(
struct dentry *dentry,
struct fileattr *fa);
extern int
xfs_fileattr_set(
struct user_namespace *mnt_userns,
struct dentry *dentry,
struct fileattr *fa);
extern long
xfs_file_ioctl(
struct file *filp,

View File

@ -484,8 +484,6 @@ xfs_file_compat_ioctl(
}
#endif
/* long changes size, but xfs only copiese out 32 bits */
case XFS_IOC_GETXFLAGS_32:
case XFS_IOC_SETXFLAGS_32:
case XFS_IOC_GETVERSION_32:
cmd = _NATIVE_IOC(cmd, long);
return xfs_file_ioctl(filp, cmd, p);

View File

@ -17,8 +17,6 @@
*/
/* stock kernel-level ioctls we support */
#define XFS_IOC_GETXFLAGS_32 FS_IOC32_GETFLAGS
#define XFS_IOC_SETXFLAGS_32 FS_IOC32_SETFLAGS
#define XFS_IOC_GETVERSION_32 FS_IOC32_GETVERSION
/*

View File

@ -21,6 +21,7 @@
#include "xfs_dir2.h"
#include "xfs_iomap.h"
#include "xfs_error.h"
#include "xfs_ioctl.h"
#include <linux/posix_acl.h>
#include <linux/security.h>
@ -1152,6 +1153,8 @@ static const struct inode_operations xfs_inode_operations = {
.listxattr = xfs_vn_listxattr,
.fiemap = xfs_vn_fiemap,
.update_time = xfs_vn_update_time,
.fileattr_get = xfs_fileattr_get,
.fileattr_set = xfs_fileattr_set,
};
static const struct inode_operations xfs_dir_inode_operations = {
@ -1177,6 +1180,8 @@ static const struct inode_operations xfs_dir_inode_operations = {
.listxattr = xfs_vn_listxattr,
.update_time = xfs_vn_update_time,
.tmpfile = xfs_vn_tmpfile,
.fileattr_get = xfs_fileattr_get,
.fileattr_set = xfs_fileattr_set,
};
static const struct inode_operations xfs_dir_ci_inode_operations = {
@ -1202,6 +1207,8 @@ static const struct inode_operations xfs_dir_ci_inode_operations = {
.listxattr = xfs_vn_listxattr,
.update_time = xfs_vn_update_time,
.tmpfile = xfs_vn_tmpfile,
.fileattr_get = xfs_fileattr_get,
.fileattr_set = xfs_fileattr_set,
};
static const struct inode_operations xfs_symlink_inode_operations = {

59
include/linux/fileattr.h Normal file
View File

@ -0,0 +1,59 @@
/* SPDX-License-Identifier: GPL-2.0 */
#ifndef _LINUX_FILEATTR_H
#define _LINUX_FILEATTR_H
/* Flags shared betwen flags/xflags */
#define FS_COMMON_FL \
(FS_SYNC_FL | FS_IMMUTABLE_FL | FS_APPEND_FL | \
FS_NODUMP_FL | FS_NOATIME_FL | FS_DAX_FL | \
FS_PROJINHERIT_FL)
#define FS_XFLAG_COMMON \
(FS_XFLAG_SYNC | FS_XFLAG_IMMUTABLE | FS_XFLAG_APPEND | \
FS_XFLAG_NODUMP | FS_XFLAG_NOATIME | FS_XFLAG_DAX | \
FS_XFLAG_PROJINHERIT)
/*
* Merged interface for miscellaneous file attributes. 'flags' originates from
* ext* and 'fsx_flags' from xfs. There's some overlap between the two, which
* is handled by the VFS helpers, so filesystems are free to implement just one
* or both of these sub-interfaces.
*/
struct fileattr {
u32 flags; /* flags (FS_IOC_GETFLAGS/FS_IOC_SETFLAGS) */
/* struct fsxattr: */
u32 fsx_xflags; /* xflags field value (get/set) */
u32 fsx_extsize; /* extsize field value (get/set)*/
u32 fsx_nextents; /* nextents field value (get) */
u32 fsx_projid; /* project identifier (get/set) */
u32 fsx_cowextsize; /* CoW extsize field value (get/set)*/
/* selectors: */
bool flags_valid:1;
bool fsx_valid:1;
};
int copy_fsxattr_to_user(const struct fileattr *fa, struct fsxattr __user *ufa);
void fileattr_fill_xflags(struct fileattr *fa, u32 xflags);
void fileattr_fill_flags(struct fileattr *fa, u32 flags);
/**
* fileattr_has_fsx - check for extended flags/attributes
* @fa: fileattr pointer
*
* Return: true if any attributes are present that are not represented in
* ->flags.
*/
static inline bool fileattr_has_fsx(const struct fileattr *fa)
{
return fa->fsx_valid &&
((fa->fsx_xflags & ~FS_XFLAG_COMMON) || fa->fsx_extsize != 0 ||
fa->fsx_projid != 0 || fa->fsx_cowextsize != 0);
}
int vfs_fileattr_get(struct dentry *dentry, struct fileattr *fa);
int vfs_fileattr_set(struct user_namespace *mnt_userns, struct dentry *dentry,
struct fileattr *fa);
#endif /* _LINUX_FILEATTR_H */

View File

@ -70,6 +70,7 @@ struct fsverity_info;
struct fsverity_operations;
struct fs_context;
struct fs_parameter_spec;
struct fileattr;
extern void __init inode_init(void);
extern void __init inode_init_early(void);
@ -1963,6 +1964,9 @@ struct inode_operations {
struct dentry *, umode_t);
int (*set_acl)(struct user_namespace *, struct inode *,
struct posix_acl *, int);
int (*fileattr_set)(struct user_namespace *mnt_userns,
struct dentry *dentry, struct fileattr *fa);
int (*fileattr_get)(struct dentry *dentry, struct fileattr *fa);
} ____cacheline_aligned;
static inline ssize_t call_read_iter(struct file *file, struct kiocb *kio,
@ -3572,18 +3576,6 @@ extern int vfs_fadvise(struct file *file, loff_t offset, loff_t len,
extern int generic_fadvise(struct file *file, loff_t offset, loff_t len,
int advice);
int vfs_ioc_setflags_prepare(struct inode *inode, unsigned int oldflags,
unsigned int flags);
int vfs_ioc_fssetxattr_check(struct inode *inode, const struct fsxattr *old_fa,
struct fsxattr *fa);
static inline void simple_fill_fsxattr(struct fsxattr *fa, __u32 xflags)
{
memset(fa, 0, sizeof(*fa));
fa->fsx_xflags = xflags;
}
/*
* Flush file data before changing attributes. Caller must hold any locks
* required to prevent further writes to this file until we're done setting