From: Miklos Szeredi Date: Mon, 11 Jul 2005 12:32:31 +0000 (+0000) Subject: added lowlevel API and (non yet working) implementation X-Git-Tag: fuse_2_4_0_pre2~51 X-Git-Url: http://git.maquefel.me/?a=commitdiff_plain;h=1274494c809faad28dfed77856f1850a9a9659da;p=qemu-gpiodev%2Flibfuse.git added lowlevel API and (non yet working) implementation --- diff --git a/include/fuse_common.h b/include/fuse_common.h new file mode 100644 index 0000000..cd19435 --- /dev/null +++ b/include/fuse_common.h @@ -0,0 +1,49 @@ +/* + FUSE: Filesystem in Userspace + Copyright (C) 2001-2005 Miklos Szeredi + + This program can be distributed under the terms of the GNU LGPL. + See the file COPYING.LIB. +*/ + +#if !defined(_FUSE_H_) && !defined(_FUSE_LOWLEVEL_H_) +#error "Never include directly; use or + + This program can be distributed under the terms of the GNU LGPL. + See the file COPYING.LIB. +*/ + +/* ----------------------------------------------------------- * + * Low level API * + * ----------------------------------------------------------- */ + +typedef unsigned long fuse_ino_t; +typedef struct fuse_req *fuse_req_t; + +/* 'to_set' flags in setattr */ +#define FUSE_SET_ATTR_MODE (1 << 0) +#define FUSE_SET_ATTR_UID (1 << 1) +#define FUSE_SET_ATTR_GID (1 << 2) +#define FUSE_SET_ATTR_SIZE (1 << 3) +#define FUSE_SET_ATTR_ATIME (1 << 4) +#define FUSE_SET_ATTR_MTIME (1 << 5) +#define FUSE_SET_ATTR_CTIME (1 << 6) + +struct fuse_lowlevel_operations { + void (*lookup) (fuse_req_t req, fuse_ino_t parent, const char *name); + void (*forget) (fuse_req_t req, fuse_ino_t ino); + void (*getattr) (fuse_req_t req, fuse_ino_t ino); + void (*readlink)(fuse_req_t req, fuse_ino_t ino); + void (*mknod) (fuse_req_t req, fuse_ino_t parent, const char *name, + mode_t mode, dev_t rdev); + void (*mkdir) (fuse_req_t req, fuse_ino_t parent, const char *name, + mode_t mode); + void (*unlink) (fuse_req_t req, fuse_ino_t parent, const char *name); + void (*rmdir) (fuse_req_t req, fuse_ino_t parent, const char *name); + void (*symlink) (fuse_req_t req, const char *link, fuse_ino_t parent, + const char *name); + void (*rename) (fuse_req_t req, fuse_ino_t parent, const char *name, + fuse_ino_t newparent, const char *newname); + void (*link) (fuse_req_t req, fuse_ino_t ino, fuse_ino_t newparent, + const char *newname); + void (*setattr) (fuse_req_t req, fuse_ino_t ino, struct stat *attr, + int to_set); + void (*open) (fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *f); + void (*read) (fuse_req_t req, fuse_ino_t ino, size_t size, off_t off, + struct fuse_file_info *f); + void (*write) (fuse_req_t req, fuse_ino_t ino, const char *buf, + size_t size, off_t off, struct fuse_file_info *f); + void (*flush) (fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *f); + void (*release) (fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *f); + void (*fsync) (fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *f); + void (*opendir) (fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *f); + void (*readdir) (fuse_req_t req, fuse_ino_t ino, size_t size, off_t off, + struct fuse_file_info *f); + void (*releasedir) (fuse_req_t req, fuse_ino_t ino, + struct fuse_file_info *f); + void (*fsyncdir) (fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *f); + void (*statfs) (fuse_req_t req, fuse_ino_t ino); + void (*setxattr)(fuse_req_t req, fuse_ino_t ino, const char *name, + const char *value, size_t size, int flags); + void (*getxattr)(fuse_req_t req, fuse_ino_t ino, const char *name, + size_t size); + void (*listxattr)(fuse_req_t req, fuse_ino_t ino, size_t size); + void (*removexattr)(fuse_req_t req, fuse_ino_t ino, const char *name); +}; + +/* all except release and forget */ +int fuse_reply_err(fuse_req_t req, int err); + +/* forget, unlink, rmdir, rename, setxattr, removexattr, release, releasedir */ +int fuse_reply_ok(fuse_req_t req); + +/* lookup, mknod, mkdir, symlink, link */ +int fuse_reply_entry(fuse_req_t req, fuse_ino_t ino, unsigned long generation, + const struct stat *attr, double attr_timeout, + double entry_timeout); + +/* getattr, setattr */ +int fuse_reply_attr(fuse_req_t req, int struct stat *attr, double attr_timeout); +/* readlink */ +int fuse_reply_readlink(fuse_req_t req, const char *link); + +/* open, write, flush, fsync, opendir, fsyncdir */ +int fuse_reply_file_info(fuse_req_t req, const struct fuse_file_info *f); + +/* read, readdir */ +int fuse_reply_buf(fuse_req_t req, const char *buf, size_t size, + const struct fuse_file_info *f); + +/* statfs */ +int fuse_reply_statfs(fuse_req_t req, const struct statfs *statfs); + +/* getxattr, listxattr */ +int fuse_reply_xattr(fuse_req_t req, const char *buf, size_t size); + +/* return the size of a directory entry */ +size_t fuse_dirent_size(size_t namelen); + +/* add a directory entry to the buffer */ +int fuse_add_dirent(char *buf, const char *name, const struct stat *stat, + off_t off); diff --git a/lib/fuse_lowlevel.c b/lib/fuse_lowlevel.c new file mode 100644 index 0000000..c5e1bf7 --- /dev/null +++ b/lib/fuse_lowlevel.c @@ -0,0 +1,1726 @@ +/* + FUSE: Filesystem in Userspace + Copyright (C) 2001-2005 Miklos Szeredi + + This program can be distributed under the terms of the GNU LGPL. + See the file COPYING.LIB +*/ + +#include "fuse_i.h" +#include "fuse_compat.h" +#include "fuse_kernel.h" +#include "fuse_kernel_compat5.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* FUSE flags: */ + +/** Enable debuging output */ +#define FUSE_DEBUG (1 << 1) + +/** If a file is removed but it's still open, don't hide the file but + remove it immediately */ +#define FUSE_HARD_REMOVE (1 << 2) + +/** Use st_ino field in getattr instead of generating inode numbers */ +#define FUSE_USE_INO (1 << 3) + +/** Only allow root or the owner to access the filesystem */ +#define FUSE_ALLOW_ROOT (1 << 4) + +/** Make a best effort to fill in inode number in a readdir **/ +#define FUSE_READDIR_INO (1 << 5) + +/** Ignore file mode supplied by the filesystem, and create one based + on the 'umask' option */ +#define FUSE_SET_MODE (1 << 6) + +/** Ignore st_uid supplied by the filesystem and set it based on the + 'uid' option*/ +#define FUSE_SET_UID (1 << 7) + +/** Ignore st_gid supplied by the filesystem and set it based on the + 'gid' option*/ +#define FUSE_SET_GID (1 << 8) + + +struct fuse_ll { + int flags; + int fd; + struct fuse_lowlevel_operations op; + volatile int exited; + int got_init; + void *user_data; + int major; + int minor; + uid_t owner; + uid_t uid; + gid_t gid; + mode_t umask; +}; + +struct fuse_cmd { + char *buf; + size_t buflen; +}; + +static const char *opname(enum fuse_opcode opcode) +{ + switch (opcode) { + case FUSE_LOOKUP: return "LOOKUP"; + case FUSE_FORGET: return "FORGET"; + case FUSE_GETATTR: return "GETATTR"; + case FUSE_SETATTR: return "SETATTR"; + case FUSE_READLINK: return "READLINK"; + case FUSE_SYMLINK: return "SYMLINK"; + case FUSE_MKNOD: return "MKNOD"; + case FUSE_MKDIR: return "MKDIR"; + case FUSE_UNLINK: return "UNLINK"; + case FUSE_RMDIR: return "RMDIR"; + case FUSE_RENAME: return "RENAME"; + case FUSE_LINK: return "LINK"; + case FUSE_OPEN: return "OPEN"; + case FUSE_READ: return "READ"; + case FUSE_WRITE: return "WRITE"; + case FUSE_STATFS: return "STATFS"; + case FUSE_FLUSH: return "FLUSH"; + case FUSE_RELEASE: return "RELEASE"; + case FUSE_FSYNC: return "FSYNC"; + case FUSE_SETXATTR: return "SETXATTR"; + case FUSE_GETXATTR: return "GETXATTR"; + case FUSE_LISTXATTR: return "LISTXATTR"; + case FUSE_REMOVEXATTR: return "REMOVEXATTR"; + case FUSE_INIT: return "INIT"; + case FUSE_OPENDIR: return "OPENDIR"; + case FUSE_READDIR: return "READDIR"; + case FUSE_RELEASEDIR: return "RELEASEDIR"; + case FUSE_FSYNCDIR: return "FSYNCDIR"; + default: return "???"; + } +} + + +static void convert_stat(struct fuse_ll *f, nodeid_t nodeid, struct stat *stbuf, + struct fuse_attr *attr) +{ + attr->ino = (f->flags & FUSE_USE_INO) ? stbuf->st_ino : nodeid; + attr->mode = stbuf->st_mode; + if (f->flags & FUSE_SET_MODE) + attr->mode = (attr->mode & S_IFMT) | (0777 & ~f->umask); + attr->nlink = stbuf->st_nlink; + attr->uid = (f->flags & FUSE_SET_UID) ? f->uid : stbuf->st_uid; + attr->gid = (f->flags & FUSE_SET_GID) ? f->gid : stbuf->st_gid; + attr->rdev = stbuf->st_rdev; + attr->size = stbuf->st_size; + attr->blocks = stbuf->st_blocks; + attr->atime = stbuf->st_atime; + attr->mtime = stbuf->st_mtime; + attr->ctime = stbuf->st_ctime; +#ifdef HAVE_STRUCT_STAT_ST_ATIM + attr->atimensec = stbuf->st_atim.tv_nsec; + attr->mtimensec = stbuf->st_mtim.tv_nsec; + attr->ctimensec = stbuf->st_ctim.tv_nsec; +#endif +} + +static size_t iov_length(const struct iovec *iov, size_t count) +{ + size_t seg; + size_t ret = 0; + + for (seg = 0; seg < count; seg++) + ret += iov[seg].iov_len; + return ret; +} + +static int send_reply_raw(struct fuse_ll *f, const struct iovec iov[], + size_t count) +{ + int res; + unsigned outsize = iov_length(iov, count); + struct fuse_out_header *out = (struct fuse_out_header *) iov[0].iov_base; + out->len = outsize; + + if ((f->flags & FUSE_DEBUG)) { + printf(" unique: %llu, error: %i (%s), outsize: %i\n", + out->unique, out->error, strerror(-out->error), outsize); + fflush(stdout); + } + + /* This needs to be done before the reply, otherwise the scheduler + could play tricks with us, and only let the counter be + increased long after the operation is done */ + fuse_inc_avail(f); + + res = writev(f->fd, iov, count); + if (res == -1) { + /* ENOENT means the operation was interrupted */ + if (!f->exited && errno != ENOENT) + perror("fuse: writing device"); + return -errno; + } + return 0; +} + +static int send_reply(struct fuse_ll *f, struct fuse_in_header *in, int error, + void *arg, size_t argsize) +{ + struct fuse_out_header out; + struct iovec iov[2]; + size_t count; + + if (error <= -1000 || error > 0) { + fprintf(stderr, "fuse: bad error value: %i\n", error); + error = -ERANGE; + } + + out.unique = in->unique; + out.error = error; + count = 1; + iov[0].iov_base = &out; + iov[0].iov_len = sizeof(struct fuse_out_header); + if (argsize && !error) { + count++; + iov[1].iov_base = arg; + iov[1].iov_len = argsize; + } + return send_reply_raw(f, iov, count); +} + +static void do_lookup(struct fuse_ll *f, struct fuse_in_header *in, char *name) +{ + int res; + int res2; + char *path; + struct fuse_entry_out arg; + + res = -ENOENT; + pthread_rwlock_rdlock(&f->tree_lock); + path = get_path_name(f, in->nodeid, name); + if (path != NULL) { + if (f->flags & FUSE_DEBUG) { + printf("LOOKUP %s\n", path); + fflush(stdout); + } + res = -ENOSYS; + if (f->op.getattr) + res = lookup_path(f, in->nodeid, in->unique, name, path, &arg); + free(path); + } + pthread_rwlock_unlock(&f->tree_lock); + res2 = send_reply(f, in, res, &arg, sizeof(arg)); + if (res == 0 && res2 == -ENOENT) + cancel_lookup(f, arg.nodeid, in->unique); +} + +static void do_forget(struct fuse_ll *f, struct fuse_in_header *in, + struct fuse_forget_in *arg) +{ + if (f->flags & FUSE_DEBUG) { + printf("FORGET %lu/%llu\n", (unsigned long) in->nodeid, + arg->nlookup); + fflush(stdout); + } + if (f->major <= 6) + forget_node_old(f, in->nodeid, arg->nlookup); + else + forget_node(f, in->nodeid, arg->nlookup); +} + +static void do_getattr(struct fuse_ll *f, struct fuse_in_header *in) +{ + int res; + char *path; + struct stat buf; + struct fuse_attr_out arg; + + res = -ENOENT; + pthread_rwlock_rdlock(&f->tree_lock); + path = get_path(f, in->nodeid); + if (path != NULL) { + res = -ENOSYS; + if (f->op.getattr) + res = f->op.getattr(path, &buf); + free(path); + } + pthread_rwlock_unlock(&f->tree_lock); + + if (res == 0) { + memset(&arg, 0, sizeof(struct fuse_attr_out)); + arg.attr_valid = ATTR_REVALIDATE_TIME; + arg.attr_valid_nsec = 0; + convert_stat(f, in->nodeid, &buf, &arg.attr); + } + + send_reply(f, in, res, &arg, sizeof(arg)); +} + +static int do_chmod(struct fuse_ll *f, const char *path, struct fuse_attr *attr) +{ + int res; + + res = -ENOSYS; + if (f->op.chmod) + res = f->op.chmod(path, attr->mode); + + return res; +} + +static int do_chown(struct fuse_ll *f, const char *path, struct fuse_attr *attr, + int valid) +{ + int res; + uid_t uid = (valid & FATTR_UID) ? attr->uid : (uid_t) -1; + gid_t gid = (valid & FATTR_GID) ? attr->gid : (gid_t) -1; + + res = -ENOSYS; + if (f->op.chown) + res = f->op.chown(path, uid, gid); + + return res; +} + +static int do_truncate(struct fuse_ll *f, const char *path, + struct fuse_attr *attr) +{ + int res; + + res = -ENOSYS; + if (f->op.truncate) + res = f->op.truncate(path, attr->size); + + return res; +} + +static int do_utime(struct fuse_ll *f, const char *path, struct fuse_attr *attr) +{ + int res; + struct utimbuf buf; + buf.actime = attr->atime; + buf.modtime = attr->mtime; + res = -ENOSYS; + if (f->op.utime) + res = f->op.utime(path, &buf); + + return res; +} + +static void do_setattr(struct fuse_ll *f, struct fuse_in_header *in, + struct fuse_setattr_in *arg) +{ + int res; + char *path; + int valid = arg->valid; + struct fuse_attr *attr = MEMBER_COMPAT(f, arg, attr, fuse_setattr_in); + struct fuse_attr_out outarg; + + res = -ENOENT; + pthread_rwlock_rdlock(&f->tree_lock); + path = get_path(f, in->nodeid); + if (path != NULL) { + res = -ENOSYS; + if (f->op.getattr) { + res = 0; + if (!res && (valid & FATTR_MODE)) + res = do_chmod(f, path, attr); + if (!res && (valid & (FATTR_UID | FATTR_GID))) + res = do_chown(f, path, attr, valid); + if (!res && (valid & FATTR_SIZE)) + res = do_truncate(f, path, attr); + if (!res && (valid & (FATTR_ATIME | FATTR_MTIME)) == + (FATTR_ATIME | FATTR_MTIME)) + res = do_utime(f, path, attr); + if (!res) { + struct stat buf; + res = f->op.getattr(path, &buf); + if (!res) { + memset(&outarg, 0, sizeof(struct fuse_attr_out)); + outarg.attr_valid = ATTR_REVALIDATE_TIME; + outarg.attr_valid_nsec = 0; + convert_stat(f, in->nodeid, &buf, &outarg.attr); + } + } + } + free(path); + } + pthread_rwlock_unlock(&f->tree_lock); + send_reply(f, in, res, &outarg, sizeof(outarg)); +} + +static void do_readlink(struct fuse_ll *f, struct fuse_in_header *in) +{ + int res; + char link[PATH_MAX + 1]; + char *path; + + res = -ENOENT; + pthread_rwlock_rdlock(&f->tree_lock); + path = get_path(f, in->nodeid); + if (path != NULL) { + res = -ENOSYS; + if (f->op.readlink) + res = f->op.readlink(path, link, sizeof(link)); + free(path); + } + pthread_rwlock_unlock(&f->tree_lock); + link[PATH_MAX] = '\0'; + send_reply(f, in, res, link, res == 0 ? strlen(link) : 0); +} + +static void do_mknod(struct fuse_ll *f, struct fuse_in_header *in, + struct fuse_mknod_in *inarg) +{ + int res; + int res2; + char *path; + char *name = PARAM(inarg); + struct fuse_entry_out outarg; + + res = -ENOENT; + pthread_rwlock_rdlock(&f->tree_lock); + path = get_path_name(f, in->nodeid, name); + if (path != NULL) { + if (f->flags & FUSE_DEBUG) { + printf("MKNOD %s\n", path); + fflush(stdout); + } + res = -ENOSYS; + if (f->op.mknod && f->op.getattr) { + res = f->op.mknod(path, inarg->mode, inarg->rdev); + if (res == 0) + res = lookup_path(f, in->nodeid, in->unique, name, path, &outarg); + } + free(path); + } + pthread_rwlock_unlock(&f->tree_lock); + res2 = send_reply(f, in, res, &outarg, sizeof(outarg)); + if (res == 0 && res2 == -ENOENT) + cancel_lookup(f, outarg.nodeid, in->unique); +} + +static void do_mkdir(struct fuse_ll *f, struct fuse_in_header *in, + struct fuse_mkdir_in *inarg) +{ + int res; + int res2; + char *path; + char *name = PARAM_COMPAT(f, inarg, fuse_mkdir_in); + struct fuse_entry_out outarg; + + res = -ENOENT; + pthread_rwlock_rdlock(&f->tree_lock); + path = get_path_name(f, in->nodeid, name); + if (path != NULL) { + if (f->flags & FUSE_DEBUG) { + printf("MKDIR %s\n", path); + fflush(stdout); + } + res = -ENOSYS; + if (f->op.mkdir && f->op.getattr) { + res = f->op.mkdir(path, inarg->mode); + if (res == 0) + res = lookup_path(f, in->nodeid, in->unique, name, path, &outarg); + } + free(path); + } + pthread_rwlock_unlock(&f->tree_lock); + res2 = send_reply(f, in, res, &outarg, sizeof(outarg)); + if (res == 0 && res2 == -ENOENT) + cancel_lookup(f, outarg.nodeid, in->unique); +} + +static void do_unlink(struct fuse_ll *f, struct fuse_in_header *in, char *name) +{ + int res; + char *path; + + res = -ENOENT; + pthread_rwlock_wrlock(&f->tree_lock); + path = get_path_name(f, in->nodeid, name); + if (path != NULL) { + if (f->flags & FUSE_DEBUG) { + printf("UNLINK %s\n", path); + fflush(stdout); + } + res = -ENOSYS; + if (f->op.unlink) { + if (!(f->flags & FUSE_HARD_REMOVE) && is_open(f, in->nodeid, name)) + res = hide_node(f, path, in->nodeid, name); + else { + res = f->op.unlink(path); + if (res == 0) + remove_node(f, in->nodeid, name); + + } + } + free(path); + } + pthread_rwlock_unlock(&f->tree_lock); + send_reply(f, in, res, NULL, 0); +} + +static void do_rmdir(struct fuse_ll *f, struct fuse_in_header *in, char *name) +{ + int res; + char *path; + + res = -ENOENT; + pthread_rwlock_wrlock(&f->tree_lock); + path = get_path_name(f, in->nodeid, name); + if (path != NULL) { + if (f->flags & FUSE_DEBUG) { + printf("RMDIR %s\n", path); + fflush(stdout); + } + res = -ENOSYS; + if (f->op.rmdir) { + res = f->op.rmdir(path); + if (res == 0) + remove_node(f, in->nodeid, name); + } + free(path); + } + pthread_rwlock_unlock(&f->tree_lock); + send_reply(f, in, res, NULL, 0); +} + +static void do_symlink(struct fuse_ll *f, struct fuse_in_header *in, char *name, + char *link) +{ + int res; + int res2; + char *path; + struct fuse_entry_out outarg; + + res = -ENOENT; + pthread_rwlock_rdlock(&f->tree_lock); + path = get_path_name(f, in->nodeid, name); + if (path != NULL) { + if (f->flags & FUSE_DEBUG) { + printf("SYMLINK %s\n", path); + fflush(stdout); + } + res = -ENOSYS; + if (f->op.symlink && f->op.getattr) { + res = f->op.symlink(link, path); + if (res == 0) + res = lookup_path(f, in->nodeid, in->unique, name, path, &outarg); + } + free(path); + } + pthread_rwlock_unlock(&f->tree_lock); + res2 = send_reply(f, in, res, &outarg, sizeof(outarg)); + if (res == 0 && res2 == -ENOENT) + cancel_lookup(f, outarg.nodeid, in->unique); + +} + +static void do_rename(struct fuse_ll *f, struct fuse_in_header *in, + struct fuse_rename_in *inarg) +{ + int res; + nodeid_t olddir = in->nodeid; + nodeid_t newdir = inarg->newdir; + char *oldname = PARAM(inarg); + char *newname = oldname + strlen(oldname) + 1; + char *oldpath; + char *newpath; + + res = -ENOENT; + pthread_rwlock_wrlock(&f->tree_lock); + oldpath = get_path_name(f, olddir, oldname); + if (oldpath != NULL) { + newpath = get_path_name(f, newdir, newname); + if (newpath != NULL) { + if (f->flags & FUSE_DEBUG) { + printf("RENAME %s -> %s\n", oldpath, newpath); + fflush(stdout); + } + res = -ENOSYS; + if (f->op.rename) { + res = 0; + if (!(f->flags & FUSE_HARD_REMOVE) && + is_open(f, newdir, newname)) + res = hide_node(f, newpath, newdir, newname); + if (res == 0) { + res = f->op.rename(oldpath, newpath); + if (res == 0) + res = rename_node(f, olddir, oldname, newdir, newname, 0); + } + } + free(newpath); + } + free(oldpath); + } + pthread_rwlock_unlock(&f->tree_lock); + send_reply(f, in, res, NULL, 0); +} + +static void do_link(struct fuse_ll *f, struct fuse_in_header *in, + struct fuse_link_in *arg) +{ + int res; + int res2; + char *oldpath; + char *newpath; + char *name = PARAM(arg); + struct fuse_entry_out outarg; + + res = -ENOENT; + pthread_rwlock_rdlock(&f->tree_lock); + oldpath = get_path(f, arg->oldnodeid); + if (oldpath != NULL) { + newpath = get_path_name(f, in->nodeid, name); + if (newpath != NULL) { + if (f->flags & FUSE_DEBUG) { + printf("LINK %s\n", newpath); + fflush(stdout); + } + res = -ENOSYS; + if (f->op.link && f->op.getattr) { + res = f->op.link(oldpath, newpath); + if (res == 0) + res = lookup_path(f, in->nodeid, in->unique, name, + newpath, &outarg); + } + free(newpath); + } + free(oldpath); + } + pthread_rwlock_unlock(&f->tree_lock); + res2 = send_reply(f, in, res, &outarg, sizeof(outarg)); + if (res == 0 && res2 == -ENOENT) + cancel_lookup(f, outarg.nodeid, in->unique); +} + +static void do_open(struct fuse_ll *f, struct fuse_in_header *in, + struct fuse_open_in *arg) +{ + int res; + char *path; + struct fuse_open_out outarg; + struct fuse_file_info fi; + + memset(&outarg, 0, sizeof(outarg)); + memset(&fi, 0, sizeof(fi)); + fi.flags = arg->flags; + res = -ENOENT; + pthread_rwlock_rdlock(&f->tree_lock); + path = get_path(f, in->nodeid); + if (path != NULL) { + res = -ENOSYS; + if (f->op.open) { + if (!f->compat) + res = f->op.open(path, &fi); + else + res = ((struct fuse_operations_compat2 *) &f->op)->open(path, fi.flags); + } + } + if (res == 0) { + int res2; + outarg.fh = fi.fh; + if (f->flags & FUSE_DEBUG) { + printf("OPEN[%lu] flags: 0x%x\n", fi.fh, arg->flags); + fflush(stdout); + } + + pthread_mutex_lock(&f->lock); + res2 = send_reply(f, in, res, &outarg, SIZEOF_COMPAT(f, fuse_open_out)); + if(res2 == -ENOENT) { + /* The open syscall was interrupted, so it must be cancelled */ + if(f->op.release) { + if (!f->compat) + f->op.release(path, &fi); + else + ((struct fuse_operations_compat2 *) &f->op)->release(path, fi.flags); + } + } else { + struct node *node = get_node(f, in->nodeid); + node->open_count ++; + } + pthread_mutex_unlock(&f->lock); + } else + send_reply(f, in, res, NULL, 0); + + if (path) + free(path); + pthread_rwlock_unlock(&f->tree_lock); +} + +static void do_flush(struct fuse_ll *f, struct fuse_in_header *in, + struct fuse_flush_in *arg) +{ + char *path; + int res; + struct fuse_file_info fi; + + memset(&fi, 0, sizeof(fi)); + fi.fh = arg->fh; + res = -ENOENT; + pthread_rwlock_rdlock(&f->tree_lock); + path = get_path(f, in->nodeid); + if (path != NULL) { + if (f->flags & FUSE_DEBUG) { + printf("FLUSH[%lu]\n", (unsigned long) arg->fh); + fflush(stdout); + } + res = -ENOSYS; + if (f->op.flush) + res = f->op.flush(path, &fi); + free(path); + } + pthread_rwlock_unlock(&f->tree_lock); + send_reply(f, in, res, NULL, 0); +} + +static void do_release(struct fuse_ll *f, struct fuse_in_header *in, + struct fuse_release_in *arg) +{ + struct node *node; + char *path; + struct fuse_file_info fi; + int unlink_hidden; + + memset(&fi, 0, sizeof(fi)); + fi.flags = arg->flags; + fi.fh = arg->fh; + + pthread_mutex_lock(&f->lock); + node = get_node(f, in->nodeid); + assert(node->open_count > 0); + --node->open_count; + unlink_hidden = (node->is_hidden && !node->open_count); + pthread_mutex_unlock(&f->lock); + + pthread_rwlock_rdlock(&f->tree_lock); + path = get_path(f, in->nodeid); + if (f->flags & FUSE_DEBUG) { + printf("RELEASE[%lu] flags: 0x%x\n", fi.fh, fi.flags); + fflush(stdout); + } + if (f->op.release) { + if (!f->compat) + f->op.release(path ? path : "-", &fi); + else if (path) + ((struct fuse_operations_compat2 *) &f->op)->release(path, fi.flags); + } + + if(unlink_hidden && path) + f->op.unlink(path); + + if (path) + free(path); + pthread_rwlock_unlock(&f->tree_lock); + + send_reply(f, in, 0, NULL, 0); +} + +static void do_read(struct fuse_ll *f, struct fuse_in_header *in, + struct fuse_read_in *arg) +{ + int res; + char *path; + size_t size; + char *buf; + struct fuse_file_info fi; + + buf = (char *) malloc(arg->size); + if (buf == NULL) { + send_reply(f, in, -ENOMEM, NULL, 0); + return; + } + + memset(&fi, 0, sizeof(fi)); + fi.fh = arg->fh; + + res = -ENOENT; + pthread_rwlock_rdlock(&f->tree_lock); + path = get_path(f, in->nodeid); + if (path != NULL) { + if (f->flags & FUSE_DEBUG) { + printf("READ[%lu] %u bytes from %llu\n", + (unsigned long) arg->fh, arg->size, arg->offset); + fflush(stdout); + } + + res = -ENOSYS; + if (f->op.read) + res = f->op.read(path, buf, arg->size, arg->offset, &fi); + free(path); + } + pthread_rwlock_unlock(&f->tree_lock); + + size = 0; + if (res >= 0) { + size = res; + res = 0; + if (f->flags & FUSE_DEBUG) { + printf(" READ[%lu] %u bytes\n", (unsigned long) arg->fh, + size); + fflush(stdout); + } + } + + send_reply(f, in, res, buf, size); + free(buf); +} + +static void do_write(struct fuse_ll *f, struct fuse_in_header *in, + struct fuse_write_in *arg) +{ + int res; + char *path; + struct fuse_write_out outarg; + struct fuse_file_info fi; + + memset(&fi, 0, sizeof(fi)); + fi.fh = arg->fh; + fi.writepage = arg->write_flags & 1; + + res = -ENOENT; + pthread_rwlock_rdlock(&f->tree_lock); + path = get_path(f, in->nodeid); + if (path != NULL) { + if (f->flags & FUSE_DEBUG) { + printf("WRITE%s[%lu] %u bytes to %llu\n", + (arg->write_flags & 1) ? "PAGE" : "", + (unsigned long) arg->fh, arg->size, arg->offset); + fflush(stdout); + } + + res = -ENOSYS; + if (f->op.write) + res = f->op.write(path, PARAM(arg), arg->size, arg->offset, &fi); + free(path); + } + pthread_rwlock_unlock(&f->tree_lock); + + memset(&outarg, 0, sizeof(outarg)); + if (res >= 0) { + outarg.size = res; + res = 0; + } + + send_reply(f, in, res, &outarg, SIZEOF_COMPAT(f, fuse_write_out)); +} + +static int default_statfs(struct statfs *buf) +{ + buf->f_namelen = 255; + buf->f_bsize = 512; + return 0; +} + +static void convert_statfs_compat(struct fuse_statfs_compat1 *compatbuf, + struct statfs *statfs) +{ + statfs->f_bsize = compatbuf->block_size; + statfs->f_blocks = compatbuf->blocks; + statfs->f_bfree = compatbuf->blocks_free; + statfs->f_bavail = compatbuf->blocks_free; + statfs->f_files = compatbuf->files; + statfs->f_ffree = compatbuf->files_free; + statfs->f_namelen = compatbuf->namelen; +} + +static void convert_statfs(struct statfs *statfs, struct fuse_kstatfs *kstatfs) +{ + kstatfs->bsize = statfs->f_bsize; + kstatfs->blocks = statfs->f_blocks; + kstatfs->bfree = statfs->f_bfree; + kstatfs->bavail = statfs->f_bavail; + kstatfs->files = statfs->f_files; + kstatfs->ffree = statfs->f_ffree; + kstatfs->namelen = statfs->f_namelen; +} + +static void do_statfs(struct fuse_ll *f, struct fuse_in_header *in) +{ + int res; + struct fuse_statfs_out arg; + struct statfs buf; + + memset(&buf, 0, sizeof(struct statfs)); + if (f->op.statfs) { + if (!f->compat || f->compat > 11) + res = f->op.statfs("/", &buf); + else { + struct fuse_statfs_compat1 compatbuf; + memset(&compatbuf, 0, sizeof(struct fuse_statfs_compat1)); + res = ((struct fuse_operations_compat1 *) &f->op)->statfs(&compatbuf); + if (res == 0) + convert_statfs_compat(&compatbuf, &buf); + } + } + else + res = default_statfs(&buf); + + if (res == 0) + convert_statfs(&buf, &arg.st); + + send_reply(f, in, res, &arg, sizeof(arg)); +} + +static void do_fsync(struct fuse_ll *f, struct fuse_in_header *in, + struct fuse_fsync_in *inarg) +{ + int res; + char *path; + struct fuse_file_info fi; + + memset(&fi, 0, sizeof(fi)); + fi.fh = inarg->fh; + + res = -ENOENT; + pthread_rwlock_rdlock(&f->tree_lock); + path = get_path(f, in->nodeid); + if (path != NULL) { + if (f->flags & FUSE_DEBUG) { + printf("FSYNC[%lu]\n", (unsigned long) inarg->fh); + fflush(stdout); + } + res = -ENOSYS; + if (f->op.fsync) + res = f->op.fsync(path, inarg->fsync_flags & 1, &fi); + free(path); + } + pthread_rwlock_unlock(&f->tree_lock); + send_reply(f, in, res, NULL, 0); +} + +static void do_setxattr(struct fuse_ll *f, struct fuse_in_header *in, + struct fuse_setxattr_in *arg) +{ + int res; + char *path; + char *name = PARAM(arg); + unsigned char *value = name + strlen(name) + 1; + + res = -ENOENT; + pthread_rwlock_rdlock(&f->tree_lock); + path = get_path(f, in->nodeid); + if (path != NULL) { + res = -ENOSYS; + if (f->op.setxattr) + res = f->op.setxattr(path, name, value, arg->size, arg->flags); + free(path); + } + pthread_rwlock_unlock(&f->tree_lock); + send_reply(f, in, res, NULL, 0); +} + +static int common_getxattr(struct fuse_ll *f, struct fuse_in_header *in, + const char *name, char *value, size_t size) +{ + int res; + char *path; + + res = -ENOENT; + pthread_rwlock_rdlock(&f->tree_lock); + path = get_path(f, in->nodeid); + if (path != NULL) { + res = -ENOSYS; + if (f->op.getxattr) + res = f->op.getxattr(path, name, value, size); + free(path); + } + pthread_rwlock_unlock(&f->tree_lock); + return res; +} + +static void do_getxattr_read(struct fuse_ll *f, struct fuse_in_header *in, + const char *name, size_t size) +{ + int res; + char *value = (char *) malloc(size); + if (value == NULL) { + send_reply(f, in, -ENOMEM, NULL, 0); + return; + } + res = common_getxattr(f, in, name, value, size); + size = 0; + if (res > 0) { + size = res; + res = 0; + } + send_reply(f, in, res, value, size); + free(value); +} + +static void do_getxattr_size(struct fuse_ll *f, struct fuse_in_header *in, + const char *name) +{ + int res; + struct fuse_getxattr_out arg; + + memset(&arg, 0, sizeof(arg)); + res = common_getxattr(f, in, name, NULL, 0); + if (res >= 0) { + arg.size = res; + res = 0; + } + send_reply(f, in, res, &arg, SIZEOF_COMPAT(f, fuse_getxattr_out)); +} + +static void do_getxattr(struct fuse_ll *f, struct fuse_in_header *in, + struct fuse_getxattr_in *arg) +{ + char *name = PARAM(arg); + + if (arg->size) + do_getxattr_read(f, in, name, arg->size); + else + do_getxattr_size(f, in, name); +} + +static int common_listxattr(struct fuse_ll *f, struct fuse_in_header *in, + char *list, size_t size) +{ + int res; + char *path; + + res = -ENOENT; + pthread_rwlock_rdlock(&f->tree_lock); + path = get_path(f, in->nodeid); + if (path != NULL) { + res = -ENOSYS; + if (f->op.listxattr) + res = f->op.listxattr(path, list, size); + free(path); + } + pthread_rwlock_unlock(&f->tree_lock); + return res; +} + +static void do_listxattr_read(struct fuse_ll *f, struct fuse_in_header *in, + size_t size) +{ + int res; + char *list = (char *) malloc(size); + if (list == NULL) { + send_reply(f, in, -ENOMEM, NULL, 0); + return; + } + res = common_listxattr(f, in, list, size); + size = 0; + if (res > 0) { + size = res; + res = 0; + } + send_reply(f, in, res, list, size); + free(list); +} + +static void do_listxattr_size(struct fuse_ll *f, struct fuse_in_header *in) +{ + int res; + struct fuse_getxattr_out arg; + + memset(&arg, 0, sizeof(arg)); + res = common_listxattr(f, in, NULL, 0); + if (res >= 0) { + arg.size = res; + res = 0; + } + send_reply(f, in, res, &arg, SIZEOF_COMPAT(f, fuse_getxattr_out)); +} + +static void do_listxattr(struct fuse_ll *f, struct fuse_in_header *in, + struct fuse_getxattr_in *arg) +{ + if (arg->size) + do_listxattr_read(f, in, arg->size); + else + do_listxattr_size(f, in); +} + +static void do_removexattr(struct fuse_ll *f, struct fuse_in_header *in, + char *name) +{ + int res; + char *path; + + res = -ENOENT; + pthread_rwlock_rdlock(&f->tree_lock); + path = get_path(f, in->nodeid); + if (path != NULL) { + res = -ENOSYS; + if (f->op.removexattr) + res = f->op.removexattr(path, name); + free(path); + } + pthread_rwlock_unlock(&f->tree_lock); + send_reply(f, in, res, NULL, 0); +} + +static void do_init(struct fuse_ll *f, struct fuse_in_header *in, + struct fuse_init_in_out *arg) +{ + struct fuse_init_in_out outarg; + + if (in->padding == 5) { + arg->minor = arg->major; + arg->major = in->padding; + } + + if (f->flags & FUSE_DEBUG) { + printf("INIT: %u.%u\n", arg->major, arg->minor); + fflush(stdout); + } + f->got_init = 1; + if (f->op.init) + f->user_data = f->op.init(); + + if (arg->major == 5) { + f->major = 5; + f->minor = 1; + } else if (arg->major == 6) { + f->major = 6; + f->minor = 1; + } else { + f->major = FUSE_KERNEL_VERSION; + f->minor = FUSE_KERNEL_MINOR_VERSION; + } + memset(&outarg, 0, sizeof(outarg)); + outarg.major = f->major; + outarg.minor = f->minor; + + if (f->flags & FUSE_DEBUG) { + printf(" INIT: %u.%u\n", outarg.major, outarg.minor); + fflush(stdout); + } + + send_reply(f, in, 0, &outarg, sizeof(outarg)); +} + +static struct fuse_dirhandle *get_dirhandle(unsigned long fh) +{ + return (struct fuse_dirhandle *) fh; +} + +static void do_opendir(struct fuse_ll *f, struct fuse_in_header *in, + struct fuse_open_in *arg) +{ + int res; + struct fuse_open_out outarg; + struct fuse_dirhandle *dh; + + dh = (struct fuse_dirhandle *) malloc(sizeof(struct fuse_dirhandle)); + if (dh == NULL) { + send_reply(f, in, -ENOMEM, NULL, 0); + return; + } + memset(dh, 0, sizeof(struct fuse_dirhandle)); + dh->fuse = f; + dh->contents = NULL; + dh->len = 0; + dh->filled = 0; + dh->nodeid = in->nodeid; + mutex_init(&dh->lock); + + memset(&outarg, 0, sizeof(outarg)); + outarg.fh = (unsigned long) dh; + + if (f->op.opendir) { + struct fuse_file_info fi; + char *path; + + memset(&fi, 0, sizeof(fi)); + fi.flags = arg->flags; + + res = -ENOENT; + pthread_rwlock_rdlock(&f->tree_lock); + path = get_path(f, in->nodeid); + if (path != NULL) { + res = f->op.opendir(path, &fi); + dh->fh = fi.fh; + } + if (res == 0) { + int res2; + pthread_mutex_lock(&f->lock); + res2 = send_reply(f, in, res, &outarg, SIZEOF_COMPAT(f, fuse_open_out)); + if(res2 == -ENOENT) { + /* The opendir syscall was interrupted, so it must be + cancelled */ + if(f->op.releasedir) + f->op.releasedir(path, &fi); + pthread_mutex_destroy(&dh->lock); + free(dh); + } + pthread_mutex_unlock(&f->lock); + } else { + send_reply(f, in, res, NULL, 0); + free(dh); + } + free(path); + pthread_rwlock_unlock(&f->tree_lock); + } else + send_reply(f, in, 0, &outarg, SIZEOF_COMPAT(f, fuse_open_out)); +} + +static int fill_dir_common(struct fuse_dirhandle *dh, const char *name, + int type, ino_t ino, off_t off) +{ + unsigned namelen = strlen(name); + unsigned entlen; + unsigned entsize; + unsigned padlen; + unsigned newlen; + unsigned char *newptr; + + if (!(dh->fuse->flags & FUSE_USE_INO)) { + ino = (ino_t) -1; + if (dh->fuse->flags & FUSE_READDIR_INO) { + struct node *node; + pthread_mutex_lock(&dh->fuse->lock); + node = lookup_node(dh->fuse, dh->nodeid, name); + if (node) + ino = (ino_t) node->nodeid; + pthread_mutex_unlock(&dh->fuse->lock); + } + } + + if (namelen > FUSE_NAME_MAX) + namelen = FUSE_NAME_MAX; + else if (!namelen) { + dh->error = -EIO; + return 1; + } + + entlen = (dh->fuse->major == 5 ? + FUSE_NAME_OFFSET_COMPAT5 : FUSE_NAME_OFFSET) + namelen; + entsize = FUSE_DIRENT_ALIGN(entlen); + padlen = entsize - entlen; + newlen = dh->len + entsize; + if (off && dh->fuse->major != 5) { + dh->filled = 0; + if (newlen > dh->needlen) + return 1; + } + + newptr = realloc(dh->contents, newlen); + if (!newptr) { + dh->error = -ENOMEM; + return 1; + } + dh->contents = newptr; + if (dh->fuse->major == 5) { + struct fuse_dirent_compat5 *dirent; + dirent = (struct fuse_dirent_compat5 *) (dh->contents + dh->len); + dirent->ino = ino; + dirent->namelen = namelen; + dirent->type = type; + strncpy(dirent->name, name, namelen); + } else { + struct fuse_dirent *dirent; + dirent = (struct fuse_dirent *) (dh->contents + dh->len); + dirent->ino = ino; + dirent->off = off ? off : newlen; + dirent->namelen = namelen; + dirent->type = type; + strncpy(dirent->name, name, namelen); + } + if (padlen) + memset(dh->contents + dh->len + entlen, 0, padlen); + dh->len = newlen; + return 0; +} + +static int fill_dir(void *buf, const char *name, const struct stat *stat, + off_t off) +{ + int type = stat ? (stat->st_mode & 0170000) >> 12 : 0; + ino_t ino = stat ? stat->st_ino : (ino_t) -1; + return fill_dir_common(buf, name, type, ino, off); +} + +static int fill_dir_old(struct fuse_dirhandle *dh, const char *name, int type, + ino_t ino) +{ + fill_dir_common(dh, name, type, ino, 0); + return dh->error; +} + +static int readdir_fill(struct fuse_ll *f, struct fuse_in_header *in, + struct fuse_read_in *arg, struct fuse_dirhandle *dh) +{ + int err = -ENOENT; + char *path; + pthread_rwlock_rdlock(&f->tree_lock); + path = get_path(f, in->nodeid); + if (path != NULL) { + struct fuse_file_info fi; + + memset(&fi, 0, sizeof(fi)); + fi.fh = dh->fh; + + dh->len = 0; + dh->error = 0; + dh->needlen = arg->size; + dh->filled = 1; + err = -ENOSYS; + if (f->op.readdir) { + off_t offset = f->major == 5 ? 0 : arg->offset; + err = f->op.readdir(path, dh, fill_dir, offset, &fi); + } else if (f->op.getdir) + err = f->op.getdir(path, dh, fill_dir_old); + if (!err) + err = dh->error; + if (err) + dh->filled = 0; + free(path); + } + pthread_rwlock_unlock(&f->tree_lock); + return err; +} + +static void do_readdir(struct fuse_ll *f, struct fuse_in_header *in, + struct fuse_read_in *arg) +{ + int err = 0; + struct fuse_dirhandle *dh = get_dirhandle(arg->fh); + size_t size = 0; + unsigned char *buf = NULL; + + pthread_mutex_lock(&dh->lock); + if (!dh->filled) { + err = readdir_fill(f, in, arg, dh); + if (err) + goto out; + } + if (dh->filled) { + if (arg->offset < dh->len) { + size = arg->size; + if (arg->offset + size > dh->len) + size = dh->len - arg->offset; + buf = dh->contents + arg->offset; + } + } else { + size = dh->len; + buf = dh->contents; + } + + out: + send_reply(f, in, err, buf, size); + pthread_mutex_unlock(&dh->lock); +} + +static void do_releasedir(struct fuse_ll *f, struct fuse_in_header *in, + struct fuse_release_in *arg) +{ + struct fuse_dirhandle *dh = get_dirhandle(arg->fh); + if (f->op.releasedir) { + char *path; + struct fuse_file_info fi; + + memset(&fi, 0, sizeof(fi)); + fi.fh = dh->fh; + + pthread_rwlock_rdlock(&f->tree_lock); + path = get_path(f, in->nodeid); + f->op.releasedir(path ? path : "-", &fi); + free(path); + pthread_rwlock_unlock(&f->tree_lock); + } + pthread_mutex_lock(&dh->lock); + pthread_mutex_unlock(&dh->lock); + pthread_mutex_destroy(&dh->lock); + free(dh->contents); + free(dh); + send_reply(f, in, 0, NULL, 0); +} + +static void do_fsyncdir(struct fuse_ll *f, struct fuse_in_header *in, + struct fuse_fsync_in *inarg) +{ + int res; + char *path; + struct fuse_dirhandle *dh = get_dirhandle(inarg->fh); + struct fuse_file_info fi; + + memset(&fi, 0, sizeof(fi)); + fi.fh = dh->fh; + + res = -ENOENT; + pthread_rwlock_rdlock(&f->tree_lock); + path = get_path(f, in->nodeid); + if (path != NULL) { + res = -ENOSYS; + if (f->op.fsyncdir) + res = f->op.fsyncdir(path, inarg->fsync_flags & 1, &fi); + free(path); + } + pthread_rwlock_unlock(&f->tree_lock); + send_reply(f, in, res, NULL, 0); +} + +static void free_cmd(struct fuse_cmd *cmd) +{ + free(cmd->buf); + free(cmd); +} + +void fuse_process_cmd(struct fuse_ll *f, struct fuse_cmd *cmd) +{ + struct fuse_in_header *in = (struct fuse_in_header *) cmd->buf; + void *inarg = cmd->buf + SIZEOF_COMPAT(f, fuse_in_header); + struct fuse_context *ctx = fuse_get_context(); + + fuse_dec_avail(f); + + if ((f->flags & FUSE_DEBUG)) { + printf("unique: %llu, opcode: %s (%i), nodeid: %lu, insize: %i\n", + in->unique, opname(in->opcode), in->opcode, + (unsigned long) in->nodeid, cmd->buflen); + fflush(stdout); + } + + if (!f->got_init && in->opcode != FUSE_INIT) { + /* Old kernel version probably */ + send_reply(f, in, -EPROTO, NULL, 0); + goto out; + } + + if ((f->flags & FUSE_ALLOW_ROOT) && in->uid != f->owner && in->uid != 0 && + in->opcode != FUSE_INIT && in->opcode != FUSE_READ && + in->opcode != FUSE_WRITE && in->opcode != FUSE_FSYNC && + in->opcode != FUSE_RELEASE && in->opcode != FUSE_READDIR && + in->opcode != FUSE_FSYNCDIR && in->opcode != FUSE_RELEASEDIR) { + send_reply(f, in, -EACCES, NULL, 0); + goto out; + } + + ctx->fuse = f; + ctx->uid = in->uid; + ctx->gid = in->gid; + ctx->pid = in->pid; + ctx->private_data = f->user_data; + + switch (in->opcode) { + case FUSE_LOOKUP: + do_lookup(f, in, (char *) inarg); + break; + + case FUSE_GETATTR: + do_getattr(f, in); + break; + + case FUSE_SETATTR: + do_setattr(f, in, (struct fuse_setattr_in *) inarg); + break; + + case FUSE_READLINK: + do_readlink(f, in); + break; + + case FUSE_MKNOD: + do_mknod(f, in, (struct fuse_mknod_in *) inarg); + break; + + case FUSE_MKDIR: + do_mkdir(f, in, (struct fuse_mkdir_in *) inarg); + break; + + case FUSE_UNLINK: + do_unlink(f, in, (char *) inarg); + break; + + case FUSE_RMDIR: + do_rmdir(f, in, (char *) inarg); + break; + + case FUSE_SYMLINK: + do_symlink(f, in, (char *) inarg, + ((char *) inarg) + strlen((char *) inarg) + 1); + break; + + case FUSE_RENAME: + do_rename(f, in, (struct fuse_rename_in *) inarg); + break; + + case FUSE_LINK: + do_link(f, in, (struct fuse_link_in *) inarg); + break; + + case FUSE_OPEN: + do_open(f, in, (struct fuse_open_in *) inarg); + break; + + case FUSE_FLUSH: + do_flush(f, in, (struct fuse_flush_in *) inarg); + break; + + case FUSE_RELEASE: + do_release(f, in, (struct fuse_release_in *) inarg); + break; + + case FUSE_READ: + do_read(f, in, (struct fuse_read_in *) inarg); + break; + + case FUSE_WRITE: + do_write(f, in, (struct fuse_write_in *) inarg); + break; + + case FUSE_STATFS: + do_statfs(f, in); + break; + + case FUSE_FSYNC: + do_fsync(f, in, (struct fuse_fsync_in *) inarg); + break; + + case FUSE_SETXATTR: + do_setxattr(f, in, (struct fuse_setxattr_in *) inarg); + break; + + case FUSE_GETXATTR: + do_getxattr(f, in, (struct fuse_getxattr_in *) inarg); + break; + + case FUSE_LISTXATTR: + do_listxattr(f, in, (struct fuse_getxattr_in *) inarg); + break; + + case FUSE_REMOVEXATTR: + do_removexattr(f, in, (char *) inarg); + break; + + case FUSE_INIT: + do_init(f, in, (struct fuse_init_in_out *) inarg); + break; + + case FUSE_OPENDIR: + do_opendir(f, in, (struct fuse_open_in *) inarg); + break; + + case FUSE_READDIR: + do_readdir(f, in, (struct fuse_read_in *) inarg); + break; + + case FUSE_RELEASEDIR: + do_releasedir(f, in, (struct fuse_release_in *) inarg); + break; + + case FUSE_FSYNCDIR: + do_fsyncdir(f, in, (struct fuse_fsync_in *) inarg); + break; + + default: + send_reply(f, in, -ENOSYS, NULL, 0); + } + + out: + free_cmd(cmd); +} + +int fuse_exited(struct fuse_ll* f) +{ + return f->exited; +} + +struct fuse_cmd *fuse_read_cmd(struct fuse_ll *f) +{ + ssize_t res; + struct fuse_cmd *cmd; + struct fuse_in_header *in; + void *inarg; + + cmd = (struct fuse_cmd *) malloc(sizeof(struct fuse_cmd)); + if (cmd == NULL) { + fprintf(stderr, "fuse: failed to allocate cmd in read\n"); + return NULL; + } + cmd->buf = (char *) malloc(FUSE_MAX_IN); + if (cmd->buf == NULL) { + fprintf(stderr, "fuse: failed to allocate read buffer\n"); + free(cmd); + return NULL; + } + in = (struct fuse_in_header *) cmd->buf; + inarg = cmd->buf + SIZEOF_COMPAT(f, fuse_in_header); + + res = read(f->fd, cmd->buf, FUSE_MAX_IN); + if (res == -1) { + free_cmd(cmd); + if (fuse_exited(f) || errno == EINTR || errno == ENOENT) + return NULL; + + /* ENODEV means we got unmounted, so we silenty return failure */ + if (errno != ENODEV) { + /* BAD... This will happen again */ + perror("fuse: reading device"); + } + + fuse_exit(f); + return NULL; + } + if ((size_t) res < SIZEOF_COMPAT(f, fuse_in_header)) { + free_cmd(cmd); + /* Cannot happen */ + fprintf(stderr, "short read on fuse device\n"); + fuse_exit(f); + return NULL; + } + cmd->buflen = res; + + /* Forget is special, it can be done without messing with threads. */ + if (in->opcode == FUSE_FORGET) { + do_forget(f, in, (struct fuse_forget_in *) inarg); + free_cmd(cmd); + return NULL; + } + + return cmd; +} + +int fuse_loop(struct fuse_ll *f) +{ + if (f == NULL) + return -1; + + while (1) { + struct fuse_cmd *cmd; + + if (fuse_exited(f)) + break; + + cmd = fuse_read_cmd(f); + if (cmd == NULL) + continue; + + fuse_process_cmd(f, cmd); + } + f->exited = 0; + return 0; +} + +int fuse_invalidate(struct fuse_ll *f, const char *path) +{ + (void) f; + (void) path; + return -EINVAL; +} + +void fuse_exit(struct fuse_ll *f) +{ + f->exited = 1; +} + +struct fuse_context *fuse_get_context() +{ + static struct fuse_context context; + if (fuse_getcontext) + return fuse_getcontext(); + else + return &context; +} + +void fuse_set_getcontext_func(struct fuse_context *(*func)(void)) +{ + fuse_getcontext = func; +} + +static int begins_with(const char *s, const char *beg) +{ + if (strncmp(s, beg, strlen(beg)) == 0) + return 1; + else + return 0; +} + +int fuse_is_lib_option(const char *opt) +{ + if (strcmp(opt, "debug") == 0 || + strcmp(opt, "hard_remove") == 0 || + strcmp(opt, "use_ino") == 0 || + strcmp(opt, "allow_root") == 0 || + strcmp(opt, "readdir_ino") == 0 || + begins_with(opt, "umask=") || + begins_with(opt, "uid=") || + begins_with(opt, "gid=")) + return 1; + else + return 0; +} + +static int parse_lib_opts(struct fuse_ll *f, const char *opts) +{ + if (opts) { + char *xopts = strdup(opts); + char *s = xopts; + char *opt; + + if (xopts == NULL) { + fprintf(stderr, "fuse: memory allocation failed\n"); + return -1; + } + + while((opt = strsep(&s, ","))) { + if (strcmp(opt, "debug") == 0) + f->flags |= FUSE_DEBUG; + else if (strcmp(opt, "hard_remove") == 0) + f->flags |= FUSE_HARD_REMOVE; + else if (strcmp(opt, "use_ino") == 0) + f->flags |= FUSE_USE_INO; + else if (strcmp(opt, "allow_root") == 0) + f->flags |= FUSE_ALLOW_ROOT; + else if (strcmp(opt, "readdir_ino") == 0) + f->flags |= FUSE_READDIR_INO; + else if (sscanf(opt, "umask=%o", &f->umask) == 1) + f->flags |= FUSE_SET_MODE; + else if (sscanf(opt, "uid=%u", &f->uid) == 1) + f->flags |= FUSE_SET_UID; + else if(sscanf(opt, "gid=%u", &f->gid) == 1) + f->flags |= FUSE_SET_GID; + else + fprintf(stderr, "fuse: warning: unknown option `%s'\n", opt); + } + free(xopts); + } + return 0; +} + +struct fuse_ll *fuse_lowlevel_new(int fd, const char *opts, + const struct fuse_lowlevel_operations *op, + size_t op_size) +{ + struct fuse_ll *f; + + if (sizeof(struct fuse_lowlevel_operations) < op_size) { + fprintf(stderr, "fuse: warning: library too old, some operations may not not work\n"); + op_size = sizeof(struct fuse_lowlevel_operations); + } + + f = (struct fuse_ll *) calloc(1, sizeof(struct fuse_ll)); + if (f == NULL) { + fprintf(stderr, "fuse: failed to allocate fuse object\n"); + goto out; + } + + if (parse_lib_opts(f, opts) == -1) + goto out_free; + + f->fd = fd; + memcpy(&f->op, op, op_size); + f->exited = 0; + f->owner = getuid(); + + return f; + + out_free: + free(f); + out: + return NULL; +} + +