/** * @file dll/fuse3/fuse2to3.c * * @copyright 2015-2018 Bill Zissimopoulos */ /* * This file is part of WinFsp. * * You can redistribute it and/or modify it under the terms of the GNU * General Public License version 3 as published by the Free Software * Foundation. * * Licensees holding a valid commercial license may use this software * in accordance with the commercial license agreement provided in * conjunction with the software. The terms and conditions of any such * commercial license agreement shall govern, supersede, and render * ineffective any application of the GPLv3 license to this software, * notwithstanding of any reference thereto in the software or * associated repository. */ #include static inline struct fuse3 *fuse2to3_getfuse3(void) { return fsp_fuse_get_context_internal()->fuse->fuse3; } static inline void fuse2to3_fi2from3(struct fuse_file_info *fi, struct fuse3_file_info *fi3) { memset(fi, 0, sizeof *fi); fi->flags = fi3->flags; fi->writepage = fi3->writepage; fi->direct_io = fi3->direct_io; fi->keep_cache = fi3->keep_cache; fi->flush = fi3->flush; fi->nonseekable = fi3->nonseekable; fi->fh = fi3->fh; fi->lock_owner = fi3->lock_owner; } static inline void fuse2to3_fi3from2(struct fuse3_file_info *fi3, struct fuse_file_info *fi) { memset(fi3, 0, sizeof *fi3); fi3->flags = fi->flags; fi3->writepage = fi->writepage; fi3->direct_io = fi->direct_io; fi3->keep_cache = fi->keep_cache; fi3->flush = fi->flush; fi3->nonseekable = fi->nonseekable; fi3->fh = fi->fh; fi3->lock_owner = fi->lock_owner; } static inline void fuse2to3_conn3from2(struct fuse3_conn_info *conn3, struct fuse_conn_info *conn) { memset(conn3, 0, sizeof *conn3); conn3->proto_major = 7; /* pretend that we are FUSE kernel protocol 7.26 */ conn3->proto_minor = 26; /* which was current at the time of FUSE 3.2 */ conn3->max_write = conn->max_write; conn3->max_read = conn->max_write; conn3->max_readahead = conn->max_readahead; conn3->capable = (conn->capable & ~FSP_FUSE_CAP_READDIR_PLUS) | FUSE_CAP_READDIRPLUS; conn3->want = conn->want; } static int fuse2to3_getattr(const char *path, struct fuse_stat *stbuf) { struct fuse3 *f3 = fuse2to3_getfuse3(); return f3->ops.getattr(path, stbuf, 0); } static int fuse2to3_readlink(const char *path, char *buf, size_t size) { struct fuse3 *f3 = fuse2to3_getfuse3(); return f3->ops.readlink(path, buf, size); } static int fuse2to3_mknod(const char *path, fuse_mode_t mode, fuse_dev_t dev) { struct fuse3 *f3 = fuse2to3_getfuse3(); return f3->ops.mknod(path, mode, dev); } static int fuse2to3_mkdir(const char *path, fuse_mode_t mode) { struct fuse3 *f3 = fuse2to3_getfuse3(); return f3->ops.mkdir(path, mode); } static int fuse2to3_unlink(const char *path) { struct fuse3 *f3 = fuse2to3_getfuse3(); return f3->ops.unlink(path); } static int fuse2to3_rmdir(const char *path) { struct fuse3 *f3 = fuse2to3_getfuse3(); return f3->ops.rmdir(path); } static int fuse2to3_symlink(const char *dstpath, const char *srcpath) { struct fuse3 *f3 = fuse2to3_getfuse3(); return f3->ops.symlink(dstpath, srcpath); } static int fuse2to3_rename(const char *oldpath, const char *newpath) { struct fuse3 *f3 = fuse2to3_getfuse3(); return f3->ops.rename(oldpath, newpath, 0); } static int fuse2to3_link(const char *srcpath, const char *dstpath) { struct fuse3 *f3 = fuse2to3_getfuse3(); return f3->ops.link(srcpath, dstpath); } static int fuse2to3_chmod(const char *path, fuse_mode_t mode) { struct fuse3 *f3 = fuse2to3_getfuse3(); return f3->ops.chmod(path, mode, 0); } static int fuse2to3_chown(const char *path, fuse_uid_t uid, fuse_gid_t gid) { struct fuse3 *f3 = fuse2to3_getfuse3(); return f3->ops.chown(path, uid, gid, 0); } static int fuse2to3_truncate(const char *path, fuse_off_t size) { struct fuse3 *f3 = fuse2to3_getfuse3(); return f3->ops.truncate(path, size, 0); } static int fuse2to3_open(const char *path, struct fuse_file_info *fi) { struct fuse3 *f3 = fuse2to3_getfuse3(); struct fuse3_file_info fi3; fuse2to3_fi3from2(&fi3, fi); int res = f3->ops.open(path, &fi3); fuse2to3_fi2from3(fi, &fi3); return res; } static int fuse2to3_read(const char *path, char *buf, size_t size, fuse_off_t off, struct fuse_file_info *fi) { struct fuse3 *f3 = fuse2to3_getfuse3(); struct fuse3_file_info fi3; fuse2to3_fi3from2(&fi3, fi); int res = f3->ops.read(path, buf, size, off, &fi3); fuse2to3_fi2from3(fi, &fi3); return res; } static int fuse2to3_write(const char *path, const char *buf, size_t size, fuse_off_t off, struct fuse_file_info *fi) { struct fuse3 *f3 = fuse2to3_getfuse3(); struct fuse3_file_info fi3; fuse2to3_fi3from2(&fi3, fi); int res = f3->ops.write(path, buf, size, off, &fi3); fuse2to3_fi2from3(fi, &fi3); return res; } static int fuse2to3_statfs(const char *path, struct fuse_statvfs *stbuf) { struct fuse3 *f3 = fuse2to3_getfuse3(); return f3->ops.statfs(path, stbuf); } static int fuse2to3_flush(const char *path, struct fuse_file_info *fi) { struct fuse3 *f3 = fuse2to3_getfuse3(); struct fuse3_file_info fi3; fuse2to3_fi3from2(&fi3, fi); int res = f3->ops.flush(path, &fi3); fuse2to3_fi2from3(fi, &fi3); return res; } static int fuse2to3_release(const char *path, struct fuse_file_info *fi) { struct fuse3 *f3 = fuse2to3_getfuse3(); struct fuse3_file_info fi3; fuse2to3_fi3from2(&fi3, fi); int res = f3->ops.release(path, &fi3); fuse2to3_fi2from3(fi, &fi3); return res; } static int fuse2to3_fsync(const char *path, int datasync, struct fuse_file_info *fi) { struct fuse3 *f3 = fuse2to3_getfuse3(); struct fuse3_file_info fi3; fuse2to3_fi3from2(&fi3, fi); int res = f3->ops.fsync(path, datasync, &fi3); fuse2to3_fi2from3(fi, &fi3); return res; } static int fuse2to3_setxattr(const char *path, const char *name, const char *value, size_t size, int flags) { struct fuse3 *f3 = fuse2to3_getfuse3(); return f3->ops.setxattr(path, name, value, size, flags); } static int fuse2to3_getxattr(const char *path, const char *name, char *value, size_t size) { struct fuse3 *f3 = fuse2to3_getfuse3(); return f3->ops.getxattr(path, name, value, size); } static int fuse2to3_listxattr(const char *path, char *namebuf, size_t size) { struct fuse3 *f3 = fuse2to3_getfuse3(); return f3->ops.listxattr(path, namebuf, size); } static int fuse2to3_removexattr(const char *path, const char *name) { struct fuse3 *f3 = fuse2to3_getfuse3(); return f3->ops.removexattr(path, name); } static int fuse2to3_opendir(const char *path, struct fuse_file_info *fi) { struct fuse3 *f3 = fuse2to3_getfuse3(); struct fuse3_file_info fi3; fuse2to3_fi3from2(&fi3, fi); int res = f3->ops.opendir(path, &fi3); fuse2to3_fi2from3(fi, &fi3); return res; } static int fuse2to3_candel_filldir(void *buf, const char *name, const struct fuse_stat *stbuf, fuse_off_t off, enum fuse3_fill_dir_flags flags) { return fsp_fuse_intf_CanDeleteAddDirInfo(buf, name, 0, off); } static int fuse2to3_filldir(void *buf, const char *name, const struct fuse_stat *stbuf, fuse_off_t off, enum fuse3_fill_dir_flags flags) { return 0 != (flags & FUSE_FILL_DIR_PLUS) ? fsp_fuse_intf_AddDirInfo(buf, name, stbuf, off) : fsp_fuse_intf_AddDirInfo(buf, name, 0, off); } static int fuse2to3_readdir(const char *path, void *buf, fuse_fill_dir_t filler, fuse_off_t off, struct fuse_file_info *fi) { struct fuse3 *f3 = fuse2to3_getfuse3(); struct fuse_dirhandle *dh = buf; struct fuse3_file_info fi3; fuse2to3_fi3from2(&fi3, fi); int res; if (fsp_fuse_intf_CanDeleteAddDirInfo == filler) res = f3->ops.readdir(path, buf, &fuse2to3_candel_filldir, off, &fi3, 0); else if (fsp_fuse_intf_AddDirInfo == filler) res = f3->ops.readdir(path, buf, &fuse2to3_filldir, off, &fi3, dh->ReaddirPlus ? FUSE_READDIR_PLUS : 0); else { FspDebugLog("fuse2to3_readdir = -ENOSYS (internal error: unknown filler)\n"); res = -ENOSYS_(f3->fuse->env); } fuse2to3_fi2from3(fi, &fi3); return res; } static int fuse2to3_releasedir(const char *path, struct fuse_file_info *fi) { struct fuse3 *f3 = fuse2to3_getfuse3(); struct fuse3_file_info fi3; fuse2to3_fi3from2(&fi3, fi); int res = f3->ops.releasedir(path, &fi3); fuse2to3_fi2from3(fi, &fi3); return res; } static int fuse2to3_fsyncdir(const char *path, int datasync, struct fuse_file_info *fi) { struct fuse3 *f3 = fuse2to3_getfuse3(); struct fuse3_file_info fi3; fuse2to3_fi3from2(&fi3, fi); int res = f3->ops.fsyncdir(path, datasync, &fi3); fuse2to3_fi2from3(fi, &fi3); return res; } static void *fuse2to3_init(struct fuse_conn_info *conn) { struct fuse_context *context = fsp_fuse_get_context_internal(); struct fuse *f = context->fuse; struct fuse3 *f3 = f->fuse3; struct fuse3_conn_info conn3; fuse2to3_conn3from2(&conn3, conn); struct fuse3_config conf3; memset(&conf3, 0, sizeof conf3); conf3.set_gid = f->set_gid; conf3.gid = f->gid; conf3.set_uid = f->set_uid; conf3.uid = f->uid; conf3.set_mode = f->set_umask; conf3.umask = f->umask; #if 0 /* * Cannot set timeouts because of lack of floating point support. * * FUSE uses the `double` type for timeouts. This DLL does not use the standard library * for a variety of reasons. This means that we cannot easily perform the computations * below. * * If this becomes important (double) floating point values could perhaps be calculated * using bit tricks. See below: * - http://locklessinc.com/articles/i2f/ * - https://stackoverflow.com/a/20308114 */ conf3.entry_timeout = f->VolumeParams.DirInfoTimeoutValid ? f->VolumeParams.DirInfoTimeout / 1000 : f->VolumeParams.FileInfoTimeout / 1000; conf3.negative_timeout = 0; conf3.attr_timeout = f->VolumeParams.FileInfoTimeout / 1000; conf3.ac_attr_timeout = conf3.attr_timeout; #endif void *res = f3->ops.init(&conn3, &conf3); conn->max_write = conn3.max_write; conn->max_readahead = conn3.max_readahead; conn->want = 0 != (conn3.want & FUSE_CAP_READDIRPLUS) ? FSP_FUSE_CAP_READDIR_PLUS : 0; conn->want |= conn3.want & ~FUSE_CAP_READDIRPLUS; return res; } static void fuse2to3_destroy(void *data) { struct fuse3 *f3 = fuse2to3_getfuse3(); f3->ops.destroy(data); } static int fuse2to3_access(const char *path, int mask) { struct fuse3 *f3 = fuse2to3_getfuse3(); return f3->ops.access(path, mask); } static int fuse2to3_create(const char *path, fuse_mode_t mode, struct fuse_file_info *fi) { struct fuse3 *f3 = fuse2to3_getfuse3(); struct fuse3_file_info fi3; fuse2to3_fi3from2(&fi3, fi); int res = f3->ops.create(path, mode, &fi3); fuse2to3_fi2from3(fi, &fi3); return res; } static int fuse2to3_ftruncate(const char *path, fuse_off_t off, struct fuse_file_info *fi) { struct fuse3 *f3 = fuse2to3_getfuse3(); struct fuse3_file_info fi3; fuse2to3_fi3from2(&fi3, fi); int res = f3->ops.truncate(path, off, &fi3); fuse2to3_fi2from3(fi, &fi3); return res; } static int fuse2to3_fgetattr(const char *path, struct fuse_stat *stbuf, struct fuse_file_info *fi) { struct fuse3 *f3 = fuse2to3_getfuse3(); struct fuse3_file_info fi3; fuse2to3_fi3from2(&fi3, fi); int res = f3->ops.getattr(path, stbuf, &fi3); fuse2to3_fi2from3(fi, &fi3); return res; } static int fuse2to3_lock(const char *path, struct fuse_file_info *fi, int cmd, struct fuse_flock *lock) { struct fuse3 *f3 = fuse2to3_getfuse3(); struct fuse3_file_info fi3; fuse2to3_fi3from2(&fi3, fi); int res = f3->ops.lock(path, &fi3, cmd, lock); fuse2to3_fi2from3(fi, &fi3); return res; } static int fuse2to3_utimens(const char *path, const struct fuse_timespec tv[2]) { struct fuse3 *f3 = fuse2to3_getfuse3(); return f3->ops.utimens(path, tv, 0); } static int fuse2to3_bmap(const char *path, size_t blocksize, uint64_t *idx) { struct fuse3 *f3 = fuse2to3_getfuse3(); return f3->ops.bmap(path, blocksize, idx); } static int fuse2to3_ioctl(const char *path, int cmd, void *arg, struct fuse_file_info *fi, unsigned int flags, void *data) { struct fuse3 *f3 = fuse2to3_getfuse3(); struct fuse3_file_info fi3; fuse2to3_fi3from2(&fi3, fi); int res = f3->ops.ioctl(path, cmd, arg, &fi3, flags, data); fuse2to3_fi2from3(fi, &fi3); return res; } static int fuse2to3_poll(const char *path, struct fuse_file_info *fi, struct fuse_pollhandle *ph, unsigned *reventsp) { struct fuse3 *f3 = fuse2to3_getfuse3(); struct fuse3_file_info fi3; fuse2to3_fi3from2(&fi3, fi); int res = f3->ops.poll(path, &fi3, (struct fuse3_pollhandle *)ph, reventsp); fuse2to3_fi2from3(fi, &fi3); return res; } static int fuse2to3_write_buf(const char *path, struct fuse_bufvec *buf, fuse_off_t off, struct fuse_file_info *fi) { struct fuse3 *f3 = fuse2to3_getfuse3(); struct fuse3_file_info fi3; fuse2to3_fi3from2(&fi3, fi); int res = f3->ops.write_buf(path, (struct fuse3_bufvec *)buf, /* revisit if we implement bufvec's */ off, &fi3); fuse2to3_fi2from3(fi, &fi3); return res; } static int fuse2to3_read_buf(const char *path, struct fuse_bufvec **bufp, size_t size, fuse_off_t off, struct fuse_file_info *fi) { struct fuse3 *f3 = fuse2to3_getfuse3(); struct fuse3_file_info fi3; fuse2to3_fi3from2(&fi3, fi); int res = f3->ops.read_buf(path, (struct fuse3_bufvec **)bufp, /* revisit if we implement bufvec's */ size, off, &fi3); fuse2to3_fi2from3(fi, &fi3); return res; } static int fuse2to3_flock(const char *path, struct fuse_file_info *fi, int op) { struct fuse3 *f3 = fuse2to3_getfuse3(); struct fuse3_file_info fi3; fuse2to3_fi3from2(&fi3, fi); int res = f3->ops.flock(path, &fi3, op); fuse2to3_fi2from3(fi, &fi3); return res; } static int fuse2to3_fallocate(const char *path, int mode, fuse_off_t off, fuse_off_t len, struct fuse_file_info *fi) { struct fuse3 *f3 = fuse2to3_getfuse3(); struct fuse3_file_info fi3; fuse2to3_fi3from2(&fi3, fi); int res = f3->ops.fallocate(path, mode, off, len, &fi3); fuse2to3_fi2from3(fi, &fi3); return res; } static int fsp_fuse3_copy_args(struct fsp_fuse_env *env, const struct fuse_args *args, struct fuse_args *outargs) { outargs->argc = 0; outargs->argv = 0; outargs->allocated = 0; for (int argi = 0; args->argc > argi; argi++) if (-1 == fsp_fuse_opt_add_arg(env, outargs, args->argv[argi])) goto fail; return 0; fail: fsp_fuse_opt_free_args(env, outargs); return -1; } static struct fuse3 *fsp_fuse3_new_common(struct fsp_fuse_env *env, struct fuse_args *args, const struct fuse3_operations *ops, size_t opsize, void *data, int help) { /* preflight args */ struct fsp_fuse_core_opt_data opt_data; struct fuse_args pfargs; memset(&opt_data, 0, sizeof opt_data); if (-1 == fsp_fuse3_copy_args(env, args, &pfargs)) return 0; int optres = fsp_fuse_core_opt_parse(env, &pfargs, &opt_data, /*help=*/1); fsp_fuse_opt_free_args(env, &pfargs); if (-1 == optres) return 0; if (opt_data.help) return 0; struct fuse3 *f3 = 0; if (opsize > sizeof(struct fuse3_operations)) opsize = sizeof(struct fuse3_operations); f3 = fsp_fuse_obj_alloc(env, sizeof *f3); if (0 == f3) goto fail; if (-1 == fsp_fuse3_copy_args(env, args, &f3->args)) goto fail; memcpy(&f3->ops, ops, opsize); f3->data = data; return f3; fail: if (0 != f3) fsp_fuse3_destroy(env, f3); return 0; } FSP_FUSE_API struct fuse3 *fsp_fuse3_new_30(struct fsp_fuse_env *env, struct fuse_args *args, const struct fuse3_operations *ops, size_t opsize, void *data) { return fsp_fuse3_new_common(env, args, ops, opsize, data, /*help=*/1); } FSP_FUSE_API struct fuse3 *fsp_fuse3_new(struct fsp_fuse_env *env, struct fuse_args *args, const struct fuse3_operations *ops, size_t opsize, void *data) { return fsp_fuse3_new_common(env, args, ops, opsize, data, /*help=*/0); } FSP_FUSE_API void fsp_fuse3_destroy(struct fsp_fuse_env *env, struct fuse3 *f3) { if (0 != f3->fuse) fsp_fuse_destroy(env, f3->fuse); fsp_fuse_opt_free_args(env, &f3->args); fsp_fuse_obj_free(f3); } FSP_FUSE_API int fsp_fuse3_mount(struct fsp_fuse_env *env, struct fuse3 *f3, const char *mountpoint) { struct fuse_chan *ch = 0; struct fuse *f = 0; struct fuse_operations fuse2to3_ops = { .getattr = 0 != f3->ops.getattr ? fuse2to3_getattr : 0, .readlink = 0 != f3->ops.readlink ? fuse2to3_readlink : 0, .mknod = 0 != f3->ops.mknod ? fuse2to3_mknod : 0, .mkdir = 0 != f3->ops.mkdir ? fuse2to3_mkdir : 0, .unlink = 0 != f3->ops.unlink ? fuse2to3_unlink : 0, .rmdir = 0 != f3->ops.rmdir ? fuse2to3_rmdir : 0, .symlink = 0 != f3->ops.symlink ? fuse2to3_symlink : 0, .rename = 0 != f3->ops.rename ? fuse2to3_rename : 0, .link = 0 != f3->ops.link ? fuse2to3_link : 0, .chmod = 0 != f3->ops.chmod ? fuse2to3_chmod : 0, .chown = 0 != f3->ops.chown ? fuse2to3_chown : 0, .truncate = 0 != f3->ops.truncate ? fuse2to3_truncate : 0, .open = 0 != f3->ops.open ? fuse2to3_open : 0, .read = 0 != f3->ops.read ? fuse2to3_read : 0, .write = 0 != f3->ops.write ? fuse2to3_write : 0, .statfs = 0 != f3->ops.statfs ? fuse2to3_statfs : 0, .flush = 0 != f3->ops.flush ? fuse2to3_flush : 0, .release = 0 != f3->ops.release ? fuse2to3_release : 0, .fsync = 0 != f3->ops.fsync ? fuse2to3_fsync : 0, .setxattr = 0 != f3->ops.setxattr ? fuse2to3_setxattr : 0, .getxattr = 0 != f3->ops.getxattr ? fuse2to3_getxattr : 0, .listxattr = 0 != f3->ops.listxattr ? fuse2to3_listxattr : 0, .removexattr = 0 != f3->ops.removexattr ? fuse2to3_removexattr : 0, .opendir = 0 != f3->ops.opendir ? fuse2to3_opendir : 0, .readdir = 0 != f3->ops.readdir ? fuse2to3_readdir : 0, .releasedir = 0 != f3->ops.releasedir ? fuse2to3_releasedir : 0, .fsyncdir = 0 != f3->ops.fsyncdir ? fuse2to3_fsyncdir : 0, .init = 0 != f3->ops.init ? fuse2to3_init : 0, .destroy = 0 != f3->ops.destroy ? fuse2to3_destroy : 0, .access = 0 != f3->ops.access ? fuse2to3_access : 0, .create = 0 != f3->ops.create ? fuse2to3_create : 0, .ftruncate = 0 != f3->ops.truncate ? fuse2to3_ftruncate : 0, .fgetattr = 0 != f3->ops.getattr ? fuse2to3_fgetattr : 0, .lock = 0 != f3->ops.lock ? fuse2to3_lock : 0, .utimens = 0 != f3->ops.utimens ? fuse2to3_utimens : 0, .bmap = 0 != f3->ops.bmap ? fuse2to3_bmap : 0, .ioctl = 0 != f3->ops.ioctl ? fuse2to3_ioctl : 0, .poll = 0 != f3->ops.poll ? fuse2to3_poll : 0, .write_buf = 0 != f3->ops.write_buf ? fuse2to3_write_buf : 0, .read_buf = 0 != f3->ops.read_buf ? fuse2to3_read_buf : 0, .flock = 0 != f3->ops.flock ? fuse2to3_flock : 0, .fallocate = 0 != f3->ops.fallocate ? fuse2to3_fallocate : 0, }; ch = fsp_fuse_mount(env, mountpoint, &f3->args); if (0 == ch) goto fail; f = fsp_fuse_new(env, ch, &f3->args, &fuse2to3_ops, sizeof fuse2to3_ops, f3->data); if (0 == f) goto fail; /* * Free the fuse_chan which is no longer needed. Note that this behavior is WinFsp-FUSE * specific, because WinFsp-FUSE only allocates/frees fuse_chan memory during fuse_mount/ * fuse_unmount and does not perform any actual mounting/unmounting. This would not work * on a different FUSE implementation. * * (Store mountpoint and ch inside struct fuse3 so that they can be freed during fuse_destroy * in that case.) */ fsp_fuse_unmount(env, mountpoint, ch); /* Free the args which are no longer needed. */ fsp_fuse_opt_free_args(env, &f3->args); f->fuse3 = f3; f3->fuse = f; return 0; fail: if (0 != f) fsp_fuse_destroy(env, f); if (0 != ch) fsp_fuse_unmount(env, mountpoint, ch); return -1; } FSP_FUSE_API void fsp_fuse3_unmount(struct fsp_fuse_env *env, struct fuse3 *f3) { fsp_fuse_destroy(env, f3->fuse); f3->fuse = 0; }