forked from Minki/linux
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:
commit
a4f7fae101
@ -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
|
||||
|
@ -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
|
||||
========================
|
||||
|
@ -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);
|
||||
|
@ -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,
|
||||
|
228
fs/btrfs/ioctl.c
228
fs/btrfs/ioctl.c
@ -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;
|
||||
|
@ -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,
|
||||
|
@ -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,
|
||||
};
|
||||
|
@ -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,
|
||||
};
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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,
|
||||
};
|
||||
|
@ -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;
|
||||
|
@ -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 = {
|
||||
|
@ -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 */
|
||||
|
@ -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,
|
||||
};
|
||||
|
||||
|
208
fs/ext4/ioctl.c
208
fs/ext4/ioctl.c
@ -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;
|
||||
|
@ -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 = {
|
||||
|
@ -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);
|
||||
|
216
fs/f2fs/file.c
216
fs/f2fs/file.c
@ -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:
|
||||
|
@ -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 = {
|
||||
|
@ -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
|
||||
|
@ -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 = {
|
||||
|
435
fs/fuse/file.c
435
fs/fuse/file.c
@ -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
|
||||
|
@ -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
490
fs/fuse/ioctl.c
Normal 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;
|
||||
}
|
@ -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:
|
||||
|
@ -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 = {
|
||||
|
@ -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
|
||||
|
@ -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 = {
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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:
|
||||
|
87
fs/inode.c
87
fs/inode.c
@ -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);
|
||||
|
325
fs/ioctl.c
325
fs/ioctl.c
@ -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
|
||||
|
@ -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,
|
||||
};
|
||||
|
121
fs/jfs/ioctl.c
121
fs/jfs/ioctl.c
@ -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
|
||||
|
@ -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 */
|
||||
|
@ -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 *);
|
||||
|
@ -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,
|
||||
};
|
||||
|
||||
|
@ -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 */
|
||||
|
@ -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;
|
||||
|
@ -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 = {
|
||||
|
@ -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 *,
|
||||
|
@ -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 = {
|
||||
|
@ -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:
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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,
|
||||
};
|
||||
|
@ -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.
|
||||
|
@ -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,
|
||||
|
@ -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)
|
||||
|
@ -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,
|
||||
};
|
||||
|
@ -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,
|
||||
|
||||
|
@ -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 = {
|
||||
|
@ -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);
|
||||
|
@ -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)
|
||||
|
@ -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,
|
||||
};
|
||||
|
@ -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;
|
||||
|
@ -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,
|
||||
};
|
||||
|
||||
/*
|
||||
|
@ -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);
|
||||
|
@ -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"
|
||||
|
@ -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 = {
|
||||
|
@ -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 = {
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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:
|
||||
|
@ -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,
|
||||
|
@ -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);
|
||||
|
@ -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
|
||||
|
||||
/*
|
||||
|
@ -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
59
include/linux/fileattr.h
Normal 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 */
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user