|
From: Tejun H. <tj...@ke...> - 2008-11-20 15:18:17
|
Generic ioctl support is tricky to implement because only the ioctl
implementation itself knows which memory regions need to be read
and/or written. To support this, fuse client can request retry of
ioctl specifying memory regions to read and write. Deep copying
(nested pointers) can be implemented by retrying multiple times
resolving one depth of dereference at a time.
For security and cleanliness considerations, ioctl implementation has
restricted mode where the kernel determines data transfer directions
and sizes using the _IOC_*() macros on the ioctl command. In this
mode, retry is not allowed.
For all FUSE servers, restricted mode is enforced. Unrestricted ioctl
will be used by CUSE.
Please take a look at example/fioc.[hc] for details.
Signed-off-by: Tejun Heo <tj...@ke...>
---
example/Makefile.am | 8 ++-
example/fioc.c | 210 +++++++++++++++++++++++++++++++++++++++++++++++
example/fioc.h | 17 ++++
example/fioclient.c | 66 +++++++++++++++
include/fuse.h | 60 +++++++++++++
include/fuse_common.h | 15 ++++
include/fuse_kernel.h | 32 +++++++
include/fuse_lowlevel.h | 62 ++++++++++++++
lib/fuse.c | 128 ++++++++++++++++++++++++++++
lib/fuse_lowlevel.c | 72 ++++++++++++++++
lib/fuse_versionscript | 3 +
11 files changed, 672 insertions(+), 1 deletions(-)
create mode 100644 example/fioc.c
create mode 100644 example/fioc.h
create mode 100644 example/fioclient.c
diff --git a/example/Makefile.am b/example/Makefile.am
index 91f33cc..43b195d 100644
--- a/example/Makefile.am
+++ b/example/Makefile.am
@@ -1,7 +1,13 @@
## Process this file with automake to produce Makefile.in
AM_CPPFLAGS = -I$(top_srcdir)/include -D_FILE_OFFSET_BITS=64 -D_REENTRANT
-noinst_PROGRAMS = fusexmp fusexmp_fh null hello hello_ll
+noinst_HEADERS = fioc.h
+noinst_PROGRAMS = fusexmp fusexmp_fh null hello hello_ll fioc fioclient
LDADD = ../lib/libfuse.la @libfuse_libs@
fusexmp_fh_LDADD = ../lib/libfuse.la ../lib/libulockmgr.la @libfuse_libs@
+
+fioclient_CPPFLAGS =
+fioclient_LDFLAGS =
+fioclient_LDADD =
+
diff --git a/example/fioc.c b/example/fioc.c
new file mode 100644
index 0000000..617e5d5
--- /dev/null
+++ b/example/fioc.c
@@ -0,0 +1,210 @@
+/*
+ FUSE fioc: FUSE ioctl example
+ Copyright (C) 2008 SUSE Linux Products GmbH
+ Copyright (C) 2008 Tejun Heo <te...@su...>
+
+ This program can be distributed under the terms of the GNU GPL.
+ See the file COPYING.
+
+ gcc -Wall `pkg-config fuse --cflags --libs` fioc.c -o fioc
+*/
+
+#define FUSE_USE_VERSION 29
+
+#include <fuse.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <time.h>
+#include <errno.h>
+
+#include "fioc.h"
+
+#define FIOC_NAME "fioc"
+
+enum {
+ FIOC_NONE,
+ FIOC_ROOT,
+ FIOC_FILE,
+};
+
+static void *fioc_buf;
+static size_t fioc_size;
+
+static int fioc_resize(size_t new_size)
+{
+ void *new_buf;
+
+ if (new_size == fioc_size)
+ return 0;
+
+ new_buf = realloc(fioc_buf, new_size);
+ if (!new_buf && new_size)
+ return -ENOMEM;
+
+ if (new_size > fioc_size)
+ memset(new_buf + fioc_size, 0, new_size - fioc_size);
+
+ fioc_buf = new_buf;
+ fioc_size = new_size;
+
+ return 0;
+}
+
+static int fioc_expand(size_t new_size)
+{
+ if (new_size > fioc_size)
+ return fioc_resize(new_size);
+ return 0;
+}
+
+static int fioc_file_type(const char *path)
+{
+ if (strcmp(path, "/") == 0)
+ return FIOC_ROOT;
+ if (strcmp(path, "/" FIOC_NAME) == 0)
+ return FIOC_FILE;
+ return FIOC_NONE;
+}
+
+static int fioc_getattr(const char *path, struct stat *stbuf)
+{
+ stbuf->st_uid = getuid();
+ stbuf->st_gid = getgid();
+ stbuf->st_atime = stbuf->st_mtime = time(NULL);
+
+ switch (fioc_file_type(path)) {
+ case FIOC_ROOT:
+ stbuf->st_mode = S_IFDIR | 0755;
+ stbuf->st_nlink = 2;
+ break;
+ case FIOC_FILE:
+ stbuf->st_mode = S_IFREG | 0644;
+ stbuf->st_nlink = 1;
+ stbuf->st_size = fioc_size;
+ break;
+ case FIOC_NONE:
+ return -ENOENT;
+ }
+
+ return 0;
+}
+
+static int fioc_open(const char *path, struct fuse_file_info *fi)
+{
+ (void) fi;
+
+ if (fioc_file_type(path) != FIOC_NONE)
+ return 0;
+ return -ENOENT;
+}
+
+static int fioc_do_read(char *buf, size_t size, off_t offset)
+{
+ if (offset >= fioc_size)
+ return 0;
+
+ if (size > fioc_size - offset)
+ size = fioc_size - offset;
+
+ memcpy(buf, fioc_buf + offset, size);
+
+ return size;
+}
+
+static int fioc_read(const char *path, char *buf, size_t size,
+ off_t offset, struct fuse_file_info *fi)
+{
+ (void) fi;
+
+ if (fioc_file_type(path) != FIOC_FILE)
+ return -EINVAL;
+
+ return fioc_do_read(buf, size, offset);
+}
+
+static int fioc_do_write(const char *buf, size_t size, off_t offset)
+{
+ if (fioc_expand(offset + size))
+ return -ENOMEM;
+
+ memcpy(fioc_buf + offset, buf, size);
+
+ return size;
+}
+
+static int fioc_write(const char *path, const char *buf, size_t size,
+ off_t offset, struct fuse_file_info *fi)
+{
+ (void) fi;
+
+ if (fioc_file_type(path) != FIOC_FILE)
+ return -EINVAL;
+
+ return fioc_do_write(buf, size, offset);
+}
+
+static int fioc_truncate(const char *path, off_t size)
+{
+ if (fioc_file_type(path) != FIOC_FILE)
+ return -EINVAL;
+
+ return fioc_resize(size);
+}
+
+static int fioc_readdir(const char *path, void *buf, fuse_fill_dir_t filler,
+ off_t offset, struct fuse_file_info *fi)
+{
+ (void) fi;
+ (void) offset;
+
+ if (fioc_file_type(path) != FIOC_ROOT)
+ return -ENOENT;
+
+ filler(buf, ".", NULL, 0);
+ filler(buf, "..", NULL, 0);
+ filler(buf, FIOC_NAME, NULL, 0);
+
+ return 0;
+}
+
+static int fioc_ioctl(const char *path, int cmd, void *arg,
+ struct fuse_file_info *fi, unsigned int flags, void *data)
+{
+ (void) arg;
+ (void) fi;
+ (void) flags;
+
+ if (fioc_file_type(path) != FIOC_FILE)
+ return -EINVAL;
+
+ if (flags & FUSE_IOCTL_COMPAT)
+ return -ENOSYS;
+
+ switch (cmd) {
+ case FIOC_GET_SIZE:
+ *(size_t *)data = fioc_size;
+ return 0;
+
+ case FIOC_SET_SIZE:
+ fioc_resize(*(size_t *)data);
+ return 0;
+ }
+
+ return -EINVAL;
+}
+
+static struct fuse_operations fioc_oper = {
+ .getattr = fioc_getattr,
+ .readdir = fioc_readdir,
+ .truncate = fioc_truncate,
+ .open = fioc_open,
+ .read = fioc_read,
+ .write = fioc_write,
+ .ioctl = fioc_ioctl,
+};
+
+int main(int argc, char *argv[])
+{
+ return fuse_main(argc, argv, &fioc_oper, NULL);
+}
diff --git a/example/fioc.h b/example/fioc.h
new file mode 100644
index 0000000..c1d9cdf
--- /dev/null
+++ b/example/fioc.h
@@ -0,0 +1,17 @@
+/*
+ FUSE-ioctl: ioctl support for FUSE
+ Copyright (C) 2008 SUSE Linux Products GmbH
+ Copyright (C) 2008 Tejun Heo <te...@su...>
+
+ This program can be distributed under the terms of the GNU GPL.
+ See the file COPYING.
+*/
+
+#include <sys/types.h>
+#include <sys/uio.h>
+#include <sys/ioctl.h>
+
+enum {
+ FIOC_GET_SIZE = _IOR('E', 0, size_t),
+ FIOC_SET_SIZE = _IOW('E', 1, size_t),
+};
diff --git a/example/fioclient.c b/example/fioclient.c
new file mode 100644
index 0000000..3ab63b2
--- /dev/null
+++ b/example/fioclient.c
@@ -0,0 +1,66 @@
+/*
+ FUSE fioclient: FUSE ioctl example client
+ Copyright (C) 2008 SUSE Linux Products GmbH
+ Copyright (C) 2008 Tejun Heo <te...@su...>
+
+ This program can be distributed under the terms of the GNU GPL.
+ See the file COPYING.
+
+ gcc -Wall fioclient.c -o fioclient
+*/
+
+#include <sys/types.h>
+#include <sys/fcntl.h>
+#include <sys/stat.h>
+#include <sys/ioctl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <ctype.h>
+#include <errno.h>
+#include "fioc.h"
+
+const char *usage =
+"Usage: fioclient FIOC_FILE [SIZE]\n"
+"\n"
+" get size if SIZE is omitted, set size otherwise\n"
+"\n";
+
+int main(int argc, char **argv)
+{
+ size_t size;
+ int fd;
+
+ if (argc < 2) {
+ goto usage;
+ }
+
+ fd = open(argv[1], O_RDWR);
+ if (fd < 0) {
+ perror("open");
+ return 1;
+ }
+
+ if (argc == 2) {
+ if (ioctl(fd, FIOC_GET_SIZE, &size)) {
+ perror("ioctl");
+ return 1;
+ }
+ printf("%zu\n", size);
+ } else {
+ char *endp;
+
+ size = strtoul(argv[2], &endp, 0);
+ if (endp == argv[2] || *endp != '\0')
+ goto usage;
+
+ if (ioctl(fd, FIOC_SET_SIZE, &size)) {
+ perror("ioctl");
+ return 1;
+ }
+ }
+ return 0;
+
+usage:
+ fprintf(stderr, usage);
+ return 1;
+}
diff --git a/include/fuse.h b/include/fuse.h
index 0a1645e..aa9aeba 100644
--- a/include/fuse.h
+++ b/include/fuse.h
@@ -31,6 +31,7 @@
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/statvfs.h>
+#include <sys/uio.h>
#ifdef __cplusplus
extern "C" {
@@ -457,6 +458,57 @@ struct fuse_operations {
* Reserved flags, don't set
*/
unsigned int flag_reserved : 31;
+
+ /**
+ * Ioctl
+ *
+ * @flags will have FUSE_IOCTL_COMPAT set for 32bit ioctls in
+ * 64bit environment. The size and direction of @data is
+ * determined by _IOC_*() decoding of @cmd. For _IOC_NONE,
+ * @data will be NULL, for _IOC_WRITE @data is out area, for
+ * _IOC_READ in area and if both are set in/out area. In all
+ * non-NULL cases, the area is of _IOC_SIZE(@cmd) bytes.
+ *
+ * Introduced in version 2.9
+ */
+ int (*ioctl) (const char *, int cmd, void *arg,
+ struct fuse_file_info *, unsigned int flags, void *data);
+
+ /**
+ * Unrestricted ioctl
+ *
+ * Note: On initial call, only @path, @cmd and @arg are passed
+ * as the kernel can't know memory access requirements for
+ * each ioctl. Note that the kernel doesn't allow
+ * unrestricted ioctls for FUSE servers. This interface is
+ * accessible only to CUSE.
+ *
+ * The handler can request the kernel to copy in arbitrary
+ * data and/or prepare output buffer by filling in @in_iov
+ * and/or @out_iov respectively. The combined number of
+ * entries can't over FUSE_IOCTL_MAX_IOV. NULL entry (base
+ * NULL, size 0) terminates the list.
+ *
+ * If either or both of @in_iov or @out_iov are set, data is
+ * read as specified by @in_iov and output buffer is prepared
+ * according to @out_iov and ioctl is retried with the read
+ * data at @in_buf and the output buffer at @out_buf of length
+ * *@out_sizep.
+ *
+ * Returning without modifying the iovs completes the ioctl.
+ * Non-zero return with modified iovs is disallowed. On
+ * completion, regardless of return status *@out_sizep is
+ * copied to the caller, so don't forget to clear *@out_sizep
+ * to zero when the command needs to fail without side-effect.
+ *
+ * Introduced in version 2.9
+ */
+ int (*unrestricted_ioctl) (const char *, int cmd, void *arg,
+ struct fuse_file_info *,
+ unsigned int *flagsp,
+ const void *in_buf, size_t in_bufsz,
+ void *out_buf, size_t *out_bufszp,
+ struct iovec *in_iov, struct iovec *out_iov);
};
/** Extra context that may be needed by some filesystems
@@ -689,6 +741,14 @@ int fuse_fs_removexattr(struct fuse_fs *fs, const char *path,
const char *name);
int fuse_fs_bmap(struct fuse_fs *fs, const char *path, size_t blocksize,
uint64_t *idx);
+int fuse_fs_ioctl(struct fuse_fs *fs, const char *path, int cmd, void *arg,
+ struct fuse_file_info *fi, unsigned int flags, void *data);
+int fuse_fs_unrestricted_ioctl(struct fuse_fs *fs, const char *path,
+ int cmd, void *arg, struct fuse_file_info *fi,
+ unsigned int *flagsp,
+ const void *in_buf, size_t in_size,
+ void *out_buf, size_t *out_sizep,
+ struct iovec *in_iov, struct iovec *out_iov);
void fuse_fs_init(struct fuse_fs *fs, struct fuse_conn_info *conn);
void fuse_fs_destroy(struct fuse_fs *fs);
diff --git a/include/fuse_common.h b/include/fuse_common.h
index 4d37acf..6a7d45d 100644
--- a/include/fuse_common.h
+++ b/include/fuse_common.h
@@ -94,6 +94,21 @@ struct fuse_file_info {
#define FUSE_CAP_BIG_WRITES (1 << 5)
/**
+ * Ioctl flags
+ *
+ * FUSE_IOCTL_COMPAT: 32bit compat ioctl on 64bit machine
+ * FUSE_IOCTL_UNRESTRICTED: not restricted to well-formed ioctls, retry allowed
+ * FUSE_IOCTL_RETRY: retry with new iovecs
+ *
+ * FUSE_IOCTL_MAX_IOV: maximum of in_iovecs + out_iovecs
+ */
+#define FUSE_IOCTL_COMPAT (1 << 0)
+#define FUSE_IOCTL_UNRESTRICTED (1 << 1)
+#define FUSE_IOCTL_RETRY (1 << 2)
+
+#define FUSE_IOCTL_MAX_IOV 256
+
+/**
* Connection information, passed to the ->init() method
*
* Some of the elements are read-write, these can be changed to
diff --git a/include/fuse_kernel.h b/include/fuse_kernel.h
index 3e87106..2cc9219 100644
--- a/include/fuse_kernel.h
+++ b/include/fuse_kernel.h
@@ -178,6 +178,21 @@ struct fuse_file_lock {
*/
#define FUSE_READ_LOCKOWNER (1 << 1)
+/**
+ * Ioctl flags
+ *
+ * FUSE_IOCTL_COMPAT: 32bit compat ioctl on 64bit machine
+ * FUSE_IOCTL_UNRESTRICTED: not restricted to well-formed ioctls, retry allowed
+ * FUSE_IOCTL_RETRY: retry with new iovecs
+ *
+ * FUSE_IOCTL_MAX_IOV: maximum of in_iovecs + out_iovecs
+ */
+#define FUSE_IOCTL_COMPAT (1 << 0)
+#define FUSE_IOCTL_UNRESTRICTED (1 << 1)
+#define FUSE_IOCTL_RETRY (1 << 2)
+
+#define FUSE_IOCTL_MAX_IOV 256
+
enum fuse_opcode {
FUSE_LOOKUP = 1,
FUSE_FORGET = 2, /* no reply */
@@ -215,6 +230,7 @@ enum fuse_opcode {
FUSE_INTERRUPT = 36,
FUSE_BMAP = 37,
FUSE_DESTROY = 38,
+ FUSE_IOCTL = 39,
};
/* The read buffer is required to be at least 8k, but may be much larger */
@@ -415,6 +431,22 @@ struct fuse_bmap_out {
__u64 block;
};
+struct fuse_ioctl_in {
+ __u64 fh;
+ __u32 flags;
+ __u32 cmd;
+ __u64 arg;
+ __u32 in_size;
+ __u32 out_size;
+};
+
+struct fuse_ioctl_out {
+ __s32 result;
+ __u32 flags;
+ __u32 in_iovs;
+ __u32 out_iovs;
+};
+
struct fuse_in_header {
__u32 len;
__u32 opcode;
diff --git a/include/fuse_lowlevel.h b/include/fuse_lowlevel.h
index 857de81..e8737d7 100644
--- a/include/fuse_lowlevel.h
+++ b/include/fuse_lowlevel.h
@@ -807,6 +807,36 @@ struct fuse_lowlevel_ops {
*/
void (*bmap) (fuse_req_t req, fuse_ino_t ino, size_t blocksize,
uint64_t idx);
+
+ /**
+ * Ioctl
+ *
+ * Note: For unrestricted ioctls (not allowed for FUSE
+ * servers), data in and out areas can be discovered by giving
+ * iovs and setting FUSE_IOCTL_RETRY in *@flagsp. For
+ * restricted ioctls, kernel prepares in/out data area
+ * according to the information encoded in @cmd.
+ *
+ * Introduced in version 2.9
+ *
+ * Valid replies:
+ * fuse_reply_ioctl_retry
+ * fuse_reply_ioctl
+ * fuse_reply_err
+ *
+ * @param req request handle
+ * @param ino the inode number
+ * @param cmd ioctl command
+ * @param arg ioctl argument
+ * @param fi file information
+ * @param flagsp io/out parameter for FUSE_IOCTL_* flags
+ * @param in_buf data fetched from the caller
+ * @param in_size number of fetched bytes
+ * @param out_size maximum size of output data
+ */
+ void (*ioctl) (fuse_req_t req, fuse_ino_t ino, int cmd, void *arg,
+ struct fuse_file_info *fi, unsigned *flagsp,
+ const void *in_buf, size_t in_bufsz, size_t out_bufszp);
};
/**
@@ -1022,6 +1052,38 @@ size_t fuse_add_direntry(fuse_req_t req, char *buf, size_t bufsize,
const char *name, const struct stat *stbuf,
off_t off);
+/**
+ * Reply to ask for data fetch and output buffer preparation. ioctl
+ * will be retried with the specified input data fetched and output
+ * buffer prepared.
+ *
+ * Possible requests:
+ * ioctl
+ *
+ * @param req request handle
+ * @param in_iov iovec specifying data to fetch from the caller
+ * @param in_count number of entries in @in_iov
+ * @param out_iov iovec specifying addresses to write output to
+ * @param out_count number of entries in @out_iov
+ * @return zero for success, -errno for failure to send reply
+ */
+int fuse_reply_ioctl_retry(fuse_req_t req,
+ const struct iovec *in_iov, size_t in_count,
+ const struct iovec *out_iov, size_t out_count);
+
+/**
+ * Reply to finish ioctl
+ *
+ * Possible requests:
+ * ioctl
+ *
+ * @param req request handle
+ * @param result result to be passed to the caller
+ * @param buf buffer containing output data
+ * @param size length of output data
+ */
+int fuse_reply_ioctl(fuse_req_t req, int result, const void *buf, size_t size);
+
/* ----------------------------------------------------------- *
* Utility functions *
* ----------------------------------------------------------- */
diff --git a/lib/fuse.c b/lib/fuse.c
index 7ed694f..be3c0b0 100644
--- a/lib/fuse.c
+++ b/lib/fuse.c
@@ -16,6 +16,7 @@
#include "fuse_misc.h"
#include "fuse_common_compat.h"
#include "fuse_compat.h"
+#include "fuse_kernel.h"
#include <stdio.h>
#include <string.h>
@@ -1652,6 +1653,51 @@ int fuse_fs_removexattr(struct fuse_fs *fs, const char *path, const char *name)
}
}
+int fuse_fs_ioctl(struct fuse_fs *fs, const char *path, int cmd, void *arg,
+ struct fuse_file_info *fi, unsigned int flags, void *data)
+{
+ fuse_get_context()->private_data = fs->user_data;
+ if (fs->op.ioctl) {
+ if (fs->debug)
+ fprintf(stderr, "ioctl[%llu] 0x%x flags: 0x%x\n",
+ (unsigned long long) fi->fh, cmd, flags);
+
+ return fs->op.ioctl(path, cmd, arg, fi, flags, data);
+ } else
+ return -ENOSYS;
+}
+
+int fuse_fs_unrestricted_ioctl(struct fuse_fs *fs, const char *path,
+ int cmd, void *arg,
+ struct fuse_file_info *fi, unsigned int *flagsp,
+ const void *in_buf, size_t in_bufsz,
+ void *out_buf, size_t *out_bufszp,
+ struct iovec *in_iov, struct iovec *out_iov)
+{
+ fuse_get_context()->private_data = fs->user_data;
+ if (fs->op.unrestricted_ioctl) {
+ int res;
+
+ if (fs->debug)
+ fprintf(stderr, "unrestricted_ioctl[%llu] "
+ "0x%x in: %zu out: %zu\n",
+ (unsigned long long) fi->fh,
+ cmd, in_bufsz, *out_bufszp);
+
+ res = fs->op.unrestricted_ioctl(path, cmd, arg, fi, flagsp,
+ in_buf, in_bufsz,
+ out_buf, out_bufszp,
+ in_iov, out_iov);
+
+ if (fs->debug && !res && (*flagsp & FUSE_IOCTL_RETRY))
+ fprintf(stderr, " unrestricted_ioctl[%llu] retry\n",
+ (unsigned long long) fi->fh);
+
+ return res;
+ } else
+ return -ENOSYS;
+}
+
static int is_open(struct fuse *f, fuse_ino_t dir, const char *name)
{
struct node *node;
@@ -3169,6 +3215,87 @@ static void fuse_lib_bmap(fuse_req_t req, fuse_ino_t ino, size_t blocksize,
reply_err(req, err);
}
+static size_t iov_count(struct iovec *iov)
+{
+ size_t count = 0;
+
+ while (iov->iov_base && iov->iov_len) {
+ count++;
+ iov++;
+ }
+ return count;
+}
+
+static void fuse_lib_ioctl(fuse_req_t req, fuse_ino_t ino, int cmd, void *arg,
+ struct fuse_file_info *fi, unsigned int *flagsp,
+ const void *in_buf, size_t in_bufsz,
+ size_t out_bufsz)
+{
+ struct fuse *f = req_fuse_prepare(req);
+ struct fuse_intr_data d;
+ char *path, *out_buf = NULL;
+ struct iovec *in_iov = NULL, *out_iov = NULL;
+ int err;
+
+ if (out_bufsz) {
+ out_buf = malloc(out_bufsz);
+ if (!out_buf)
+ goto enomem;
+ }
+
+ if (*flagsp & FUSE_IOCTL_UNRESTRICTED) {
+ in_iov = calloc(FUSE_IOCTL_MAX_IOV + 1, sizeof(in_iov[0]));
+ out_iov = calloc(FUSE_IOCTL_MAX_IOV + 1, sizeof(out_iov[0]));
+ if (!in_iov || !out_iov)
+ goto enomem;
+ } else {
+ assert(!in_bufsz || !out_bufsz || in_bufsz == out_bufsz);
+ if (out_buf)
+ memcpy(out_buf, in_buf, in_bufsz);
+ }
+
+ err = get_path(f, ino, &path);
+ if (err)
+ goto out;
+
+ fuse_prepare_interrupt(f, req, &d);
+
+ if (*flagsp & FUSE_IOCTL_UNRESTRICTED)
+ err = fuse_fs_unrestricted_ioctl(f->fs, path, cmd, arg, fi,
+ flagsp, in_buf, in_bufsz,
+ out_buf, &out_bufsz,
+ in_iov, out_iov);
+ else
+ err = fuse_fs_ioctl(f->fs, path, cmd, arg, fi, *flagsp,
+ out_buf ?: (void *)in_buf);
+
+ fuse_finish_interrupt(f, req, &d);
+ free_path(f, ino, path);
+
+ if (*flagsp & FUSE_IOCTL_RETRY) {
+ size_t in_count = iov_count(in_iov);
+ size_t out_count = iov_count(out_iov);
+
+ assert(in_count < FUSE_IOCTL_MAX_IOV);
+ assert(out_count < FUSE_IOCTL_MAX_IOV);
+ assert(err == 0);
+
+ fuse_reply_ioctl_retry(req, in_iov, in_count,
+ out_iov, out_count);
+ } else
+ fuse_reply_ioctl(req, err, out_buf, out_bufsz);
+
+out:
+ free(out_buf);
+ free(in_iov);
+ free(out_iov);
+ return;
+
+enomem:
+ reply_err(req, -ENOMEM);
+ goto out;
+}
+
static struct fuse_lowlevel_ops fuse_path_ops = {
.init = fuse_lib_init,
.destroy = fuse_lib_destroy,
@@ -3204,6 +3331,7 @@ static struct fuse_lowlevel_ops fuse_path_ops = {
.getlk = fuse_lib_getlk,
.setlk = fuse_lib_setlk,
.bmap = fuse_lib_bmap,
+ .ioctl = fuse_lib_ioctl,
};
static void free_cmd(struct fuse_cmd *cmd)
diff --git a/lib/fuse_lowlevel.c b/lib/fuse_lowlevel.c
index 35c4608..11069aa 100644
--- a/lib/fuse_lowlevel.c
+++ b/lib/fuse_lowlevel.c
@@ -454,6 +454,58 @@ int fuse_reply_bmap(fuse_req_t req, uint64_t idx)
return send_reply_ok(req, &arg, sizeof(arg));
}
+int fuse_reply_ioctl_retry(fuse_req_t req,
+ const struct iovec *in_iov, size_t in_count,
+ const struct iovec *out_iov, size_t out_count)
+{
+ struct fuse_ioctl_out arg;
+ struct iovec iov[4];
+ size_t count = 1;
+
+ memset(&arg, 0, sizeof(arg));
+ arg.flags |= FUSE_IOCTL_RETRY;
+ arg.in_iovs = in_count;
+ arg.out_iovs = out_count;
+ iov[count].iov_base = &arg;
+ iov[count].iov_len = sizeof(arg);
+ count++;
+
+ if (in_count) {
+ iov[count].iov_base = (void *)in_iov;
+ iov[count].iov_len = sizeof(in_iov[0]) * in_count;
+ count++;
+ }
+
+ if (out_count) {
+ iov[count].iov_base = (void *)out_iov;
+ iov[count].iov_len = sizeof(out_iov[0]) * out_count;
+ count++;
+ }
+
+ return send_reply_iov(req, 0, iov, count);
+}
+
+int fuse_reply_ioctl(fuse_req_t req, int result, const void *buf, size_t size)
+{
+ struct fuse_ioctl_out arg;
+ struct iovec iov[3];
+ size_t count = 1;
+
+ memset(&arg, 0, sizeof(arg));
+ arg.result = result;
+ iov[count].iov_base = &arg;
+ iov[count].iov_len = sizeof(arg);
+ count++;
+
+ if (size) {
+ iov[count].iov_base = (char *) buf;
+ iov[count].iov_len = size;
+ count++;
+ }
+
+ return send_reply_iov(req, 0, iov, count);
+}
+
static void do_lookup(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
{
char *name = (char *) inarg;
@@ -1002,6 +1054,25 @@ static void do_bmap(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
fuse_reply_err(req, ENOSYS);
}
+static void do_ioctl(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+{
+ struct fuse_ioctl_in *arg = (struct fuse_ioctl_in *) inarg;
+ unsigned int flags = arg->flags;
+ void *in_buf = arg->in_size ? PARAM(arg) : NULL;
+ struct fuse_file_info fi;
+
+ memset(&fi, 0, sizeof(fi));
+ fi.fh = arg->fh;
+ fi.fh_old = fi.fh;
+
+ if (req->f->op.ioctl)
+ req->f->op.ioctl(req, nodeid, arg->cmd,
+ (void *)(uintptr_t)arg->arg, &fi, &flags,
+ in_buf, arg->in_size, arg->out_size);
+ else
+ fuse_reply_err(req, ENOSYS);
+}
+
static void do_init(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
{
struct fuse_init_in *arg = (struct fuse_init_in *) inarg;
@@ -1179,6 +1250,7 @@ static struct {
[FUSE_CREATE] = { do_create, "CREATE" },
[FUSE_INTERRUPT] = { do_interrupt, "INTERRUPT" },
[FUSE_BMAP] = { do_bmap, "BMAP" },
+ [FUSE_IOCTL] = { do_ioctl, "IOCTL" },
[FUSE_DESTROY] = { do_destroy, "DESTROY" },
};
diff --git a/lib/fuse_versionscript b/lib/fuse_versionscript
index a8277f3..031fd26 100644
--- a/lib/fuse_versionscript
+++ b/lib/fuse_versionscript
@@ -150,6 +150,7 @@ FUSE_2.7 {
FUSE_2.9 {
global:
+ fuse_fs_ioctl;
fuse_fs_new;
fuse_fs_new_compat26;
fuse_lowlevel_new;
@@ -158,6 +159,8 @@ FUSE_2.9 {
fuse_main_real_compat26;
fuse_new;
fuse_new_compat26;
+ fuse_reply_ioctl;
+ fuse_reply_ioctl_retry;
fuse_setup;
fuse_setup_compat26;
--
1.5.6
|