@@ -17,6 +17,6 @@ zuf-y += md.o t1.o t2.o
zuf-y += zuf-core.o zuf-root.o
# Main FS
-zuf-y += rw.o mmap.o
+zuf-y += rw.o mmap.o ioctl.o
zuf-y += super.o inode.o directory.o namei.o file.o symlink.o
zuf-y += module.o
@@ -46,6 +46,11 @@ bool zuf_dir_emit(struct super_block *sb, struct dir_context *ctx,
uint zuf_prepare_symname(struct zufs_ioc_new_inode *ioc_new_inode,
const char *symname, ulong len, struct page *pages[2]);
+/* ioctl.c */
+long zuf_ioctl(struct file *filp, uint cmd, ulong arg);
+#ifdef CONFIG_COMPAT
+long zuf_compat_ioctl(struct file *file, uint cmd, ulong arg);
+#endif
/* mmap.c */
int zuf_file_mmap(struct file *file, struct vm_area_struct *vma);
@@ -160,4 +160,8 @@ const struct file_operations zuf_dir_operations = {
.read = generic_read_dir,
.iterate_shared = zuf_readdir,
.fsync = noop_fsync,
+ .unlocked_ioctl = zuf_ioctl,
+#ifdef CONFIG_COMPAT
+ .compat_ioctl = zuf_compat_ioctl,
+#endif
};
@@ -507,7 +507,11 @@ const struct file_operations zuf_file_operations = {
.fsync = zuf_fsync,
.flush = zuf_flush,
.release = zuf_file_release,
+ .unlocked_ioctl = zuf_ioctl,
.fallocate = zuf_fallocate,
+#ifdef CONFIG_COMPAT
+ .compat_ioctl = zuf_compat_ioctl,
+#endif
.copy_file_range = zuf_copy_file_range,
.remap_file_range = zuf_clone_file_range,
};
new file mode 100644
@@ -0,0 +1,282 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * BRIEF DESCRIPTION
+ *
+ * Ioctl operations.
+ *
+ * Copyright (c) 2018 NetApp Inc. All rights reserved.
+ *
+ * ZUFS-License: GPL-2.0. See module.c for LICENSE details.
+ *
+ * Authors:
+ * Boaz Harrosh <boazh@netapp.com>
+ * Sagi Manole <sagim@netapp.com>"
+ */
+
+#include <linux/capability.h>
+#include <linux/time.h>
+#include <linux/sched.h>
+#include <linux/compat.h>
+#include <linux/mount.h>
+#include <linux/fadvise.h>
+#include <linux/vmalloc.h>
+#include <linux/capability.h>
+
+#include "zuf.h"
+
+#define ZUFS_SUPPORTED_FS_FLAGS (FS_SYNC_FL | FS_APPEND_FL | FS_IMMUTABLE_FL | \
+ FS_NOATIME_FL | FS_DIRTY_FL)
+
+#define ZUS_IOCTL_MAX_PAGES 8
+
+static int _ioctl_dispatch(struct inode *inode, uint cmd, ulong arg)
+{
+ struct _ioctl_info {
+ struct zufs_ioc_ioctl ctl;
+ char buf[900];
+ } ctl_alloc = {};
+ enum big_alloc_type bat;
+ struct zufs_ioc_ioctl *ioc_ioctl;
+ size_t ioc_size = _IOC_SIZE(cmd);
+ void __user *parg = (void __user *)arg;
+ struct timespec64 time = current_time(inode);
+ size_t size;
+ bool retry = false;
+ int err;
+
+again:
+ size = sizeof(*ioc_ioctl) + ioc_size;
+
+ zuf_dbg_vfs("[%ld] cmd=0x%x arg=0x%lx size=0x%zx IOC(%d, %d, %zd)\n",
+ inode->i_ino, cmd, arg, size, _IOC_TYPE(cmd),
+ _IOC_NR(cmd), ioc_size);
+
+ ioc_ioctl = big_alloc(size, sizeof(ctl_alloc), &ctl_alloc, GFP_KERNEL,
+ &bat);
+ if (unlikely(!ioc_ioctl))
+ return -ENOMEM;
+
+ memset(ioc_ioctl, 0, sizeof(*ioc_ioctl));
+ ioc_ioctl->hdr.in_len = size;
+ ioc_ioctl->hdr.out_start = offsetof(struct zufs_ioc_ioctl, arg);
+ ioc_ioctl->hdr.out_max = size;
+ ioc_ioctl->hdr.out_len = 0;
+ ioc_ioctl->hdr.operation = ZUFS_OP_IOCTL;
+ ioc_ioctl->zus_ii = ZUII(inode)->zus_ii;
+ ioc_ioctl->cmd = cmd;
+ timespec_to_mt(&ioc_ioctl->time, &time);
+
+ if (arg && ioc_size) {
+ if (copy_from_user(ioc_ioctl->arg, parg, ioc_size)) {
+ err = -EFAULT;
+ goto out;
+ }
+ }
+
+ err = zufc_dispatch(ZUF_ROOT(SBI(inode->i_sb)), &ioc_ioctl->hdr,
+ NULL, 0);
+
+ if (!retry && err == -EZUFS_RETRY) {
+ ioc_size = ioc_ioctl->new_size - sizeof(*ioc_ioctl);
+ big_free(ioc_ioctl, bat);
+ retry = true;
+ goto again;
+ }
+
+ if (unlikely(err)) {
+ zuf_dbg_err("zufc_dispatch failed => %d IOC(%d, %d, %zd)\n",
+ err, _IOC_TYPE(cmd), _IOC_NR(cmd), ioc_size);
+ goto out;
+ }
+
+ if (ioc_ioctl->hdr.out_len) {
+ if (copy_to_user(parg, ioc_ioctl->arg,
+ ioc_ioctl->hdr.out_len)) {
+ err = -EFAULT;
+ goto out;
+ }
+ }
+
+out:
+ big_free(ioc_ioctl, bat);
+
+ return err;
+}
+
+static uint _translate_to_ioc_flags(struct zus_inode *zi)
+{
+ uint zi_flags = le16_to_cpu(zi->i_flags);
+ uint ioc_flags = 0;
+
+ if (zi_flags & S_SYNC)
+ ioc_flags |= FS_SYNC_FL;
+ if (zi_flags & S_APPEND)
+ ioc_flags |= FS_APPEND_FL;
+ if (zi_flags & S_IMMUTABLE)
+ ioc_flags |= FS_IMMUTABLE_FL;
+ if (zi_flags & S_NOATIME)
+ ioc_flags |= FS_NOATIME_FL;
+ if (zi_flags & S_DIRSYNC)
+ ioc_flags |= FS_DIRSYNC_FL;
+
+ return ioc_flags;
+}
+
+static int _ioc_getflags(struct inode *inode, uint __user *parg)
+{
+ struct zus_inode *zi = zus_zi(inode);
+ uint flags = _translate_to_ioc_flags(zi);
+
+ return put_user(flags, parg);
+}
+
+static void _translate_to_zi_flags(struct zus_inode *zi, unsigned int flags)
+{
+ uint zi_flags = le16_to_cpu(zi->i_flags);
+
+ zi_flags &=
+ ~(S_SYNC | S_APPEND | S_IMMUTABLE | S_NOATIME | S_DIRSYNC);
+
+ if (flags & FS_SYNC_FL)
+ zi_flags |= S_SYNC;
+ if (flags & FS_APPEND_FL)
+ zi_flags |= S_APPEND;
+ if (flags & FS_IMMUTABLE_FL)
+ zi_flags |= S_IMMUTABLE;
+ if (flags & FS_NOATIME_FL)
+ zi_flags |= S_NOATIME;
+ if (flags & FS_DIRSYNC_FL)
+ zi_flags |= S_DIRSYNC;
+
+ zi->i_flags = cpu_to_le16(zi_flags);
+}
+
+/* use statx ioc to flush zi changes to fs */
+static int __ioc_dispatch_zi_update(struct inode *inode, uint flags)
+{
+ struct zufs_ioc_attr ioc_attr = {
+ .hdr.in_len = sizeof(ioc_attr),
+ .hdr.out_len = sizeof(ioc_attr),
+ .hdr.operation = ZUFS_OP_SETATTR,
+ .zus_ii = ZUII(inode)->zus_ii,
+ .zuf_attr = flags,
+ };
+ int err;
+
+ err = zufc_dispatch(ZUF_ROOT(SBI(inode->i_sb)), &ioc_attr.hdr, NULL, 0);
+ if (unlikely(err && err != -EINTR))
+ zuf_err("zufc_dispatch failed => %d\n", err);
+
+ return err;
+}
+
+static int _ioc_setflags(struct inode *inode, uint __user *parg)
+{
+ struct zus_inode *zi = zus_zi(inode);
+ uint flags, oldflags;
+ int err;
+
+ if (!inode_owner_or_capable(inode))
+ return -EPERM;
+
+ if (get_user(flags, parg))
+ return -EFAULT;
+
+ if (flags & ~ZUFS_SUPPORTED_FS_FLAGS)
+ return -EOPNOTSUPP;
+
+ inode_lock(inode);
+
+ oldflags = le32_to_cpu(zi->i_flags);
+
+ if ((flags ^ oldflags) &
+ (FS_APPEND_FL | FS_IMMUTABLE_FL)) {
+ if (!capable(CAP_LINUX_IMMUTABLE)) {
+ inode_unlock(inode);
+ return -EPERM;
+ }
+ }
+
+ if (!S_ISDIR(inode->i_mode))
+ flags &= ~FS_DIRSYNC_FL;
+
+ flags = flags & FS_FL_USER_MODIFIABLE;
+ flags |= oldflags & ~FS_FL_USER_MODIFIABLE;
+ inode->i_ctime = current_time(inode);
+ timespec_to_mt(&zi->i_ctime, &inode->i_ctime);
+ _translate_to_zi_flags(zi, flags);
+ zuf_set_inode_flags(inode, zi);
+
+ err = __ioc_dispatch_zi_update(inode, ZUFS_STATX_FLAGS | STATX_CTIME);
+
+ inode_unlock(inode);
+ return err;
+}
+
+static int _ioc_setversion(struct inode *inode, uint __user *parg)
+{
+ struct zus_inode *zi = zus_zi(inode);
+ __u32 generation;
+ int err;
+
+ if (!inode_owner_or_capable(inode))
+ return -EPERM;
+
+ if (get_user(generation, parg))
+ return -EFAULT;
+
+ inode_lock(inode);
+
+ inode->i_ctime = current_time(inode);
+ inode->i_generation = generation;
+ timespec_to_mt(&zi->i_ctime, &inode->i_ctime);
+ zi->i_generation = cpu_to_le32(inode->i_generation);
+
+ err = __ioc_dispatch_zi_update(inode, ZUFS_STATX_VERSION | STATX_CTIME);
+
+ inode_unlock(inode);
+ return err;
+}
+
+long zuf_ioctl(struct file *filp, unsigned int cmd, ulong arg)
+{
+ struct inode *inode = filp->f_inode;
+ void __user *parg = (void __user *)arg;
+
+ switch (cmd) {
+ case FS_IOC_GETFLAGS:
+ return _ioc_getflags(inode, parg);
+ case FS_IOC_SETFLAGS:
+ return _ioc_setflags(inode, parg);
+ case FS_IOC_GETVERSION:
+ return put_user(inode->i_generation, (int __user *)arg);
+ case FS_IOC_SETVERSION:
+ return _ioc_setversion(inode, parg);
+ default:
+ return _ioctl_dispatch(inode, cmd, arg);
+ }
+}
+
+#ifdef CONFIG_COMPAT
+long zuf_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;
+ case FS_IOC32_GETVERSION:
+ cmd = FS_IOC_GETVERSION;
+ break;
+ case FS_IOC32_SETVERSION:
+ cmd = FS_IOC_SETVERSION;
+ break;
+ default:
+ return -ENOIOCTLCMD;
+ }
+ return zuf_ioctl(file, cmd, (unsigned long)compat_ptr(arg));
+}
+#endif
+
@@ -787,6 +787,7 @@ const char *zuf_op_name(enum e_zufs_operation op)
CASE_ENUM_NAME(ZUFS_OP_SYNC );
CASE_ENUM_NAME(ZUFS_OP_FALLOCATE );
CASE_ENUM_NAME(ZUFS_OP_LLSEEK );
+ CASE_ENUM_NAME(ZUFS_OP_IOCTL );
CASE_ENUM_NAME(ZUFS_OP_BREAK );
default:
return "UNKNOWN";
@@ -350,6 +350,7 @@ enum e_zufs_operation {
ZUFS_OP_SYNC,
ZUFS_OP_FALLOCATE,
ZUFS_OP_LLSEEK,
+ ZUFS_OP_IOCTL,
ZUFS_OP_BREAK, /* Kernel telling Server to exit */
ZUFS_OP_MAX_OPT,
@@ -586,6 +587,21 @@ struct zufs_ioc_seek {
__u64 offset_out;
};
+/* ZUFS_OP_IOCTL */
+struct zufs_ioc_ioctl {
+ struct zufs_ioc_hdr hdr;
+ /* IN */
+ struct zus_inode_info *zus_ii;
+ __u32 cmd;
+ __u64 time;
+
+ /* OUT */
+ union {
+ __u32 new_size;
+ char arg[0];
+ };
+};
+
/* ~~~~ io_map structures && IOCTL(s) ~~~~ */
/*
* These set of structures and helpers are used in return of zufs_ioc_IO and