From: Miklos Szeredi Date: Fri, 5 Dec 2008 10:55:36 +0000 (+0000) Subject: * Implement ioctl support. On high level interface only X-Git-Tag: fuse_2_8_0_pre2~3 X-Git-Url: http://git.maquefel.me/?a=commitdiff_plain;h=ecfa5263ab5b19a58d53a7116fb079f3b956b918;p=qemu-gpiodev%2Flibfuse.git * Implement ioctl support. On high level interface only "restricted" ioctls are supported (which are defined with the _IO(), _IOR(), _IOW() or _IOWR() macros). Unrestricted ioctls will only be allwed to CUSE (Character Device in Userspace) servers. Patch by Tejun Heo --- diff --git a/ChangeLog b/ChangeLog index d6de6ff..5220ff5 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,11 @@ +2008-12-05 Miklos Szeredi + + * Implement ioctl support. On high level interface only + "restricted" ioctls are supported (which are defined with the + _IO(), _IOR(), _IOW() or _IOWR() macros). Unrestricted ioctls + will only be allwed to CUSE (Character Device in Userspace) + servers. Patch by Tejun Heo + 2008-11-28 Miklos Szeredi * If open sets fi->nonseekable, libfuse will tell the kernel that diff --git a/example/.cvsignore b/example/.cvsignore index 56dabc1..7332bc0 100644 --- a/example/.cvsignore +++ b/example/.cvsignore @@ -6,4 +6,6 @@ fusexmp_fh null hello hello_ll +fioc +fioclient .libs 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..d0dce15 --- /dev/null +++ b/example/fioc.c @@ -0,0 +1,211 @@ +/* + FUSE fioc: FUSE ioctl example + Copyright (C) 2008 SUSE Linux Products GmbH + Copyright (C) 2008 Tejun Heo + + 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 26 + +#include +#include +#include +#include +#include +#include +#include + +#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 + + This program can be distributed under the terms of the GNU GPL. + See the file COPYING. +*/ + +#include +#include +#include + +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 + + This program can be distributed under the terms of the GNU GPL. + See the file COPYING. + + gcc -Wall fioclient.c -o fioclient +*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#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 8d47bc5..2016ccc 100644 --- a/include/fuse.h +++ b/include/fuse.h @@ -31,6 +31,7 @@ #include #include #include +#include #ifdef __cplusplus extern "C" { @@ -457,6 +458,22 @@ 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); + }; /** Extra context that may be needed by some filesystems @@ -689,6 +706,8 @@ 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); 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 9bbc386..fb18b61 100644 --- a/include/fuse_common.h +++ b/include/fuse_common.h @@ -93,6 +93,21 @@ struct fuse_file_info { #define FUSE_CAP_ATOMIC_O_TRUNC (1 << 3) #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 * diff --git a/include/fuse_kernel.h b/include/fuse_kernel.h index b37d969..eb28a35 100644 --- a/include/fuse_kernel.h +++ b/include/fuse_kernel.h @@ -1,6 +1,6 @@ /* This file defines the kernel interface of FUSE - Copyright (C) 2001-2007 Miklos Szeredi + Copyright (C) 2001-2008 Miklos Szeredi This program can be distributed under the terms of the GNU GPL. See the file COPYING. @@ -46,36 +46,28 @@ * * 7.10 * - add nonseekable open flag + * + * 7.11 + * - add IOCTL message */ #ifndef _LINUX_FUSE_H #define _LINUX_FUSE_H -#ifndef linux #include #define __u64 uint64_t #define __u32 uint32_t #define __s32 int32_t -#else -#include -#include -#endif /** Version number of this interface */ #define FUSE_KERNEL_VERSION 7 /** Minor version number of this interface */ -#define FUSE_KERNEL_MINOR_VERSION 10 +#define FUSE_KERNEL_MINOR_VERSION 11 /** The node ID of the root inode */ #define FUSE_ROOT_ID 1 -/** The major number of the fuse character device */ -#define FUSE_MAJOR MISC_MAJOR - -/** The minor number of the fuse character device */ -#define FUSE_MINOR 229 - /* Make sure all structures are padded to 64bit boundary, so 32bit userspace works under 64bit kernels */ @@ -184,6 +176,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 */ @@ -221,6 +228,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 */ @@ -421,6 +429,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 9330548..e17274f 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 9c5dd0f..453fca5 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 #include @@ -1652,6 +1653,20 @@ 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; +} + static int is_open(struct fuse *f, fuse_ino_t dir, const char *name) { struct node *node; @@ -3169,6 +3184,57 @@ static void fuse_lib_bmap(fuse_req_t req, fuse_ino_t ino, size_t blocksize, reply_err(req, err); } +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 (*flagsp & FUSE_IOCTL_UNRESTRICTED) { + reply_err(req, -EPERM); + return; + } + + if (out_bufsz) { + out_buf = malloc(out_bufsz); + if (!out_buf) + goto enomem; + } + + 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); + + 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); + + 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 +3270,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 34ff76c..6b5fdce 100644 --- a/lib/fuse_lowlevel.c +++ b/lib/fuse_lowlevel.c @@ -451,6 +451,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; @@ -1004,6 +1056,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; @@ -1183,6 +1254,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 2f6aff3..4aa1c0b 100644 --- a/lib/fuse_versionscript +++ b/lib/fuse_versionscript @@ -155,8 +155,11 @@ FUSE_2.7 { FUSE_2.8 { global: + fuse_fs_ioctl; fuse_opt_add_opt_escaped; fuse_reply_bmap; + fuse_reply_ioctl; + fuse_reply_ioctl_retry; local: *;