/* $NetBSD: fs.c,v 1.1.2.1 2023/07/14 08:31:27 martin Exp $ */ /* * Copyright (c) 2021 The NetBSD Foundation, Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. The name of the author may not be used to endorse or promote * products derived from this software without specific prior written * permission. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include #if !defined(lint) __RCSID("$NetBSD: fs.c,v 1.1.2.1 2023/07/14 08:31:27 martin Exp $"); #endif /* !lint */ /* * Filesystem Stacking API, appeared on FUSE 2.7. * * So many callback functions in struct fuse_operations have different * prototypes between versions. We use the stacking API to abstract * that away to implement puffs operations in a manageable way. */ #include #include #include #include #include #include struct fuse_fs { void* op; int op_version; void* user_data; }; #define UNKNOWN_VERSION(op_version) \ errc(EXIT_FAILURE, ENOSYS, "%s: unknown fuse_operations version: %d", \ __func__, op_version) static void* clone_op(const void* op, int op_version) { void* cloned; switch (op_version) { #define CLONE_OP(VER) \ case VER: \ cloned = malloc(sizeof(struct __CONCAT(fuse_operations_v,VER))); \ if (!cloned) \ return NULL; \ memcpy(cloned, op, sizeof(struct __CONCAT(fuse_operations_v,VER))); \ return cloned CLONE_OP(11); CLONE_OP(21); CLONE_OP(22); CLONE_OP(23); CLONE_OP(25); CLONE_OP(26); CLONE_OP(28); CLONE_OP(29); CLONE_OP(30); CLONE_OP(34); CLONE_OP(35); CLONE_OP(38); #undef CLONE_OP default: UNKNOWN_VERSION(op_version); } } struct fuse_fs* __fuse_fs_new(const void* op, int op_version, void* user_data) { struct fuse_fs* fs; fs = malloc(sizeof(struct fuse_fs)); if (!fs) err(EXIT_FAILURE, __func__); /* Callers aren't obliged to keep "op" valid during the lifetime * of struct fuse_fs*. We must clone it now, even though it's * non-trivial. */ fs->op = clone_op(op, op_version); if (!fs->op) err(EXIT_FAILURE, __func__); fs->op_version = op_version; fs->user_data = user_data; return fs; } /* Clobber the context private_data with that of this filesystem * layer. This function needs to be called before invoking any of * operation callbacks. */ static void clobber_context_user_data(struct fuse_fs* fs) { fuse_get_context()->private_data = fs->user_data; } /* Ugly... These are like hand-written vtables... */ int fuse_fs_getattr_v27(struct fuse_fs *fs, const char *path, struct stat *buf) { return fuse_fs_getattr_v30(fs, path, buf, NULL); } int fuse_fs_getattr_v30(struct fuse_fs* fs, const char* path, struct stat* buf, struct fuse_file_info* fi) { clobber_context_user_data(fs); switch (fs->op_version) { #define CALL_OLD_GETATTR(VER) \ case VER: \ if (((const struct __CONCAT(fuse_operations_v,VER)*)fs->op)->getattr) \ return ((const struct __CONCAT(fuse_operations_v,VER)*)fs->op)->getattr(path, buf); \ else \ return -ENOSYS CALL_OLD_GETATTR(11); CALL_OLD_GETATTR(21); CALL_OLD_GETATTR(22); CALL_OLD_GETATTR(23); CALL_OLD_GETATTR(25); CALL_OLD_GETATTR(26); CALL_OLD_GETATTR(28); CALL_OLD_GETATTR(29); #undef CALL_OLD_GETATTR #define CALL_GETATTR(VER) \ case VER: \ if (((const struct __CONCAT(fuse_operations_v,VER)*)fs->op)->getattr) \ return ((const struct __CONCAT(fuse_operations_v,VER)*)fs->op)->getattr(path, buf, fi); \ else \ return -ENOSYS CALL_GETATTR(30); CALL_GETATTR(34); CALL_GETATTR(35); CALL_GETATTR(38); #undef CALL_GETATTR default: UNKNOWN_VERSION(fs->op_version); } } int fuse_fs_fgetattr(struct fuse_fs* fs, const char* path, struct stat* buf, struct fuse_file_info* fi) { clobber_context_user_data(fs); /* fgetattr() was introduced on FUSE 2.5 then disappeared on FUSE * 3.0. Fall back to getattr() if it's missing. */ switch (fs->op_version) { case 11: case 21: case 22: case 23: return fuse_fs_getattr_v30(fs, path, buf, fi); #define CALL_FGETATTR_OR_OLD_GETATTR(VER) \ case VER: \ if (((const struct __CONCAT(fuse_operations_v,VER)*)fs->op)->fgetattr) \ return ((const struct __CONCAT(fuse_operations_v,VER)*)fs->op)->fgetattr(path, buf, fi); \ else if (((const struct __CONCAT(fuse_operations_v,VER)*)fs->op)->getattr) \ return ((const struct __CONCAT(fuse_operations_v,VER)*)fs->op)->getattr(path, buf); \ else \ return -ENOSYS CALL_FGETATTR_OR_OLD_GETATTR(25); CALL_FGETATTR_OR_OLD_GETATTR(26); CALL_FGETATTR_OR_OLD_GETATTR(28); CALL_FGETATTR_OR_OLD_GETATTR(29); #undef CALL_FGETATTR_OR_OLD_GETATTR case 30: case 34: case 35: case 38: return fuse_fs_getattr_v30(fs, path, buf, fi); default: UNKNOWN_VERSION(fs->op_version); } } int fuse_fs_rename_v27(struct fuse_fs* fs, const char* oldpath, const char* newpath) { return fuse_fs_rename_v30(fs, oldpath, newpath, 0); } int fuse_fs_rename_v30(struct fuse_fs* fs, const char* oldpath, const char* newpath, unsigned int flags) { clobber_context_user_data(fs); switch (fs->op_version) { #define CALL_OLD_RENAME(VER) \ case VER: \ if (((const struct __CONCAT(fuse_operations_v,VER)*)fs->op)->rename) \ return ((const struct __CONCAT(fuse_operations_v,VER)*)fs->op)->rename(oldpath, newpath); \ else \ return -ENOSYS CALL_OLD_RENAME(11); CALL_OLD_RENAME(21); CALL_OLD_RENAME(22); CALL_OLD_RENAME(23); CALL_OLD_RENAME(25); CALL_OLD_RENAME(26); CALL_OLD_RENAME(28); CALL_OLD_RENAME(29); #undef CALL_OLD_RENAME #define CALL_RENAME(VER) \ case VER: \ if (((const struct __CONCAT(fuse_operations_v,VER)*)fs->op)->rename) \ return ((const struct __CONCAT(fuse_operations_v,VER)*)fs->op)->rename(oldpath, newpath, flags); \ else \ return -ENOSYS CALL_RENAME(30); CALL_RENAME(34); CALL_RENAME(35); CALL_RENAME(38); #undef CALL_RENAME default: UNKNOWN_VERSION(fs->op_version); } } int fuse_fs_unlink(struct fuse_fs* fs, const char* path) { clobber_context_user_data(fs); switch (fs->op_version) { #define CALL_UNLINK(VER) \ case VER: \ if (((const struct __CONCAT(fuse_operations_v,VER)*)fs->op)->unlink) \ return ((const struct __CONCAT(fuse_operations_v,VER)*)fs->op)->unlink(path); \ else \ return -ENOSYS CALL_UNLINK(11); CALL_UNLINK(21); CALL_UNLINK(22); CALL_UNLINK(23); CALL_UNLINK(25); CALL_UNLINK(26); CALL_UNLINK(28); CALL_UNLINK(29); CALL_UNLINK(30); CALL_UNLINK(34); CALL_UNLINK(35); CALL_UNLINK(38); #undef CALL_UNLINK default: UNKNOWN_VERSION(fs->op_version); } } int fuse_fs_rmdir(struct fuse_fs* fs, const char* path) { clobber_context_user_data(fs); switch (fs->op_version) { #define CALL_RMDIR(VER) \ case VER: \ if (((const struct __CONCAT(fuse_operations_v,VER)*)fs->op)->rmdir) \ return ((const struct __CONCAT(fuse_operations_v,VER)*)fs->op)->rmdir(path); \ else \ return -ENOSYS CALL_RMDIR(11); CALL_RMDIR(21); CALL_RMDIR(22); CALL_RMDIR(23); CALL_RMDIR(25); CALL_RMDIR(26); CALL_RMDIR(28); CALL_RMDIR(29); CALL_RMDIR(30); CALL_RMDIR(34); CALL_RMDIR(35); CALL_RMDIR(38); #undef CALL_RMDIR default: UNKNOWN_VERSION(fs->op_version); } } int fuse_fs_symlink(struct fuse_fs* fs, const char* linkname, const char* path) { clobber_context_user_data(fs); switch (fs->op_version) { #define CALL_SYMLINK(VER) \ case VER: \ if (((const struct __CONCAT(fuse_operations_v,VER)*)fs->op)->symlink) \ return ((const struct __CONCAT(fuse_operations_v,VER)*)fs->op)->symlink(linkname, path); \ else \ return -ENOSYS CALL_SYMLINK(11); CALL_SYMLINK(21); CALL_SYMLINK(22); CALL_SYMLINK(23); CALL_SYMLINK(25); CALL_SYMLINK(26); CALL_SYMLINK(28); CALL_SYMLINK(29); CALL_SYMLINK(30); CALL_SYMLINK(34); CALL_SYMLINK(35); CALL_SYMLINK(38); #undef CALL_SYMLINK default: UNKNOWN_VERSION(fs->op_version); } } int fuse_fs_link(struct fuse_fs* fs, const char* oldpath, const char* newpath) { clobber_context_user_data(fs); switch (fs->op_version) { #define CALL_LINK(VER) \ case VER: \ if (((const struct __CONCAT(fuse_operations_v,VER)*)fs->op)->link) \ return ((const struct __CONCAT(fuse_operations_v,VER)*)fs->op)->link(oldpath, newpath); \ else \ return -ENOSYS CALL_LINK(11); CALL_LINK(21); CALL_LINK(22); CALL_LINK(23); CALL_LINK(25); CALL_LINK(26); CALL_LINK(28); CALL_LINK(29); CALL_LINK(30); CALL_LINK(34); CALL_LINK(35); CALL_LINK(38); #undef CALL_LINK default: UNKNOWN_VERSION(fs->op_version); } } int fuse_fs_release(struct fuse_fs* fs, const char* path, struct fuse_file_info* fi) { clobber_context_user_data(fs); switch (fs->op_version) { #define CALL_OLD_RELEASE(VER) \ case VER: \ if (((const struct __CONCAT(fuse_operations_v,VER)*)fs->op)->release) \ return ((const struct __CONCAT(fuse_operations_v,VER)*)fs->op)->release(path, fi->flags); \ else \ return 0 /* Special case */ CALL_OLD_RELEASE(11); CALL_OLD_RELEASE(21); #undef CALL_OLD_RELEASE #define CALL_RELEASE(VER) \ case VER: \ if (((const struct __CONCAT(fuse_operations_v,VER)*)fs->op)->release) \ return ((const struct __CONCAT(fuse_operations_v,VER)*)fs->op)->release(path, fi); \ else \ return 0 /* Special case */ CALL_RELEASE(22); CALL_RELEASE(23); CALL_RELEASE(25); CALL_RELEASE(26); CALL_RELEASE(28); CALL_RELEASE(29); CALL_RELEASE(30); CALL_RELEASE(34); CALL_RELEASE(35); CALL_RELEASE(38); #undef CALL_RELEASE default: UNKNOWN_VERSION(fs->op_version); } } int fuse_fs_open(struct fuse_fs* fs, const char* path, struct fuse_file_info* fi) { clobber_context_user_data(fs); switch (fs->op_version) { #define CALL_OLD_OPEN(VER) \ case VER: \ if (((const struct __CONCAT(fuse_operations_v,VER)*)fs->op)->open) \ return ((const struct __CONCAT(fuse_operations_v,VER)*)fs->op)->open(path, fi->flags); \ else \ return 0 /* Special case */ CALL_OLD_OPEN(11); CALL_OLD_OPEN(21); #undef CALL_OLD_OPEN #define CALL_OPEN(VER) \ case VER: \ if (((const struct __CONCAT(fuse_operations_v,VER)*)fs->op)->open) \ return ((const struct __CONCAT(fuse_operations_v,VER)*)fs->op)->open(path, fi); \ else \ return 0 /* Special case */ CALL_OPEN(22); CALL_OPEN(23); CALL_OPEN(25); CALL_OPEN(26); CALL_OPEN(28); CALL_OPEN(29); CALL_OPEN(30); CALL_OPEN(34); CALL_OPEN(35); CALL_OPEN(38); #undef CALL_OPEN default: UNKNOWN_VERSION(fs->op_version); } } int fuse_fs_read(struct fuse_fs* fs, const char* path, char* buf, size_t size, off_t off, struct fuse_file_info* fi) { clobber_context_user_data(fs); switch (fs->op_version) { #define CALL_OLD_READ(VER) \ case VER: \ if (((const struct __CONCAT(fuse_operations_v,VER)*)fs->op)->read) \ return ((const struct __CONCAT(fuse_operations_v,VER)*)fs->op)->read(path, buf, size, off); \ else \ return -ENOSYS CALL_OLD_READ(11); CALL_OLD_READ(21); #undef CALL_OLD_READ #define CALL_READ(VER) \ case VER: \ if (((const struct __CONCAT(fuse_operations_v,VER)*)fs->op)->read) \ return ((const struct __CONCAT(fuse_operations_v,VER)*)fs->op)->read(path, buf, size, off, fi); \ else \ return -ENOSYS CALL_READ(22); CALL_READ(23); CALL_READ(25); CALL_READ(26); CALL_READ(28); CALL_READ(29); CALL_READ(30); CALL_READ(34); CALL_READ(35); CALL_READ(38); #undef CALL_READ default: UNKNOWN_VERSION(fs->op_version); } } int fuse_fs_read_buf(struct fuse_fs* fs, const char* path, struct fuse_bufvec** bufp, size_t size, off_t off, struct fuse_file_info* fi) { clobber_context_user_data(fs); switch (fs->op_version) { /* FUSE < 2.9 didn't have read_buf(). */ case 11: case 21: case 22: case 23: case 25: case 26: case 28: return -ENOSYS; #define CALL_READ_BUF(VER) \ case VER: \ if (((const struct __CONCAT(fuse_operations_v,VER)*)fs->op)->read_buf) \ return ((const struct __CONCAT(fuse_operations_v,VER)*)fs->op)->read_buf(path, bufp, size, off, fi); \ else \ return -ENOSYS CALL_READ_BUF(29); CALL_READ_BUF(30); CALL_READ_BUF(34); CALL_READ_BUF(35); CALL_READ_BUF(38); #undef CALL_READ_BUF default: UNKNOWN_VERSION(fs->op_version); } } int fuse_fs_write(struct fuse_fs* fs, const char* path, const char* buf, size_t size, off_t off, struct fuse_file_info* fi) { clobber_context_user_data(fs); switch (fs->op_version) { #define CALL_OLD_WRITE(VER) \ case VER: \ if (((const struct __CONCAT(fuse_operations_v,VER)*)fs->op)->write) \ return ((const struct __CONCAT(fuse_operations_v,VER)*)fs->op)->write(path, buf, size, off); \ else \ return -ENOSYS CALL_OLD_WRITE(11); CALL_OLD_WRITE(21); #undef CALL_OLD_WRITE #define CALL_WRITE(VER) \ case VER: \ if (((const struct __CONCAT(fuse_operations_v,VER)*)fs->op)->write) \ return ((const struct __CONCAT(fuse_operations_v,VER)*)fs->op)->write(path, buf, size, off, fi); \ else \ return -ENOSYS CALL_WRITE(22); CALL_WRITE(23); CALL_WRITE(25); CALL_WRITE(26); CALL_WRITE(28); CALL_WRITE(29); CALL_WRITE(30); CALL_WRITE(34); CALL_WRITE(35); CALL_WRITE(38); #undef CALL_WRITE default: UNKNOWN_VERSION(fs->op_version); } } int fuse_fs_write_buf(struct fuse_fs* fs, const char* path, struct fuse_bufvec* bufp, off_t off, struct fuse_file_info* fi) { clobber_context_user_data(fs); switch (fs->op_version) { /* FUSE < 2.9 didn't have write_buf(). */ case 11: case 21: case 22: case 23: case 25: case 26: case 28: return -ENOSYS; #define CALL_WRITE_BUF(VER) \ case VER: \ if (((const struct __CONCAT(fuse_operations_v,VER)*)fs->op)->write_buf) \ return ((const struct __CONCAT(fuse_operations_v,VER)*)fs->op)->write_buf(path, bufp, off, fi); \ else \ return -ENOSYS CALL_WRITE_BUF(29); CALL_WRITE_BUF(30); CALL_WRITE_BUF(34); CALL_WRITE_BUF(35); CALL_WRITE_BUF(38); #undef CALL_WRITE_BUF default: UNKNOWN_VERSION(fs->op_version); } } int fuse_fs_fsync(struct fuse_fs* fs, const char* path, int datasync, struct fuse_file_info* fi) { clobber_context_user_data(fs); switch (fs->op_version) { #define CALL_OLD_FSYNC(VER) \ case VER: \ if (((const struct __CONCAT(fuse_operations_v,VER)*)fs->op)->fsync) \ return ((const struct __CONCAT(fuse_operations_v,VER)*)fs->op)->fsync(path, datasync); \ else \ return -ENOSYS CALL_OLD_FSYNC(11); CALL_OLD_FSYNC(21); #undef CALL_OLD_FSYNC #define CALL_FSYNC(VER) \ case VER: \ if (((const struct __CONCAT(fuse_operations_v,VER)*)fs->op)->fsync) \ return ((const struct __CONCAT(fuse_operations_v,VER)*)fs->op)->fsync(path, datasync, fi); \ else \ return -ENOSYS CALL_FSYNC(22); CALL_FSYNC(23); CALL_FSYNC(25); CALL_FSYNC(26); CALL_FSYNC(28); CALL_FSYNC(29); CALL_FSYNC(30); CALL_FSYNC(34); CALL_FSYNC(35); CALL_FSYNC(38); #undef CALL_FSYNC default: UNKNOWN_VERSION(fs->op_version); } } int fuse_fs_flush(struct fuse_fs* fs, const char* path, struct fuse_file_info* fi) { clobber_context_user_data(fs); /* flush() appeared on FUSE 2.1 and its prototype was changed on * 2.2. */ switch (fs->op_version) { case 11: return -ENOSYS; case 21: if (((const struct fuse_operations_v21 *)fs->op)->flush) return ((const struct fuse_operations_v21 *)fs->op)->flush(path); else return -ENOSYS; #define CALL_FLUSH(VER) \ case VER: \ if (((const struct __CONCAT(fuse_operations_v,VER)*)fs->op)->flush) \ return ((const struct __CONCAT(fuse_operations_v,VER)*)fs->op)->flush(path, fi); \ else \ return -ENOSYS CALL_FLUSH(22); CALL_FLUSH(23); CALL_FLUSH(25); CALL_FLUSH(26); CALL_FLUSH(28); CALL_FLUSH(29); CALL_FLUSH(30); CALL_FLUSH(34); CALL_FLUSH(35); CALL_FLUSH(38); #undef CALL_FLUSH default: UNKNOWN_VERSION(fs->op_version); } } static void zero_statvfs(struct statvfs* dst) { dst->f_bsize = 0; dst->f_frsize = 0; dst->f_blocks = 0; dst->f_bfree = 0; dst->f_bavail = 0; dst->f_files = 0; dst->f_ffree = 0; dst->f_fresvd = 0; } static void fuse_statfs_to_statvfs(struct statvfs* dst, const struct fuse_statfs* src) { dst->f_bsize = (unsigned long)src->block_size; dst->f_frsize = (unsigned long)src->block_size; /* Dunno if this is correct. */ dst->f_blocks = (fsblkcnt_t)src->blocks; dst->f_bfree = (fsblkcnt_t)src->blocks_free; dst->f_bavail = (fsblkcnt_t)src->blocks_free; dst->f_files = (fsfilcnt_t)src->files; dst->f_ffree = (fsfilcnt_t)src->files_free; } static void linux_statfs_to_statvfs(struct statvfs* dst, const struct statfs* src) { dst->f_bsize = (unsigned long)src->f_bsize; dst->f_frsize = (unsigned long)src->f_bsize; /* Dunno if this is correct. */ dst->f_blocks = src->f_blocks; dst->f_bfree = src->f_bfree; dst->f_bavail = src->f_bavail; dst->f_files = src->f_files; dst->f_ffree = src->f_ffree; } int fuse_fs_statfs(struct fuse_fs* fs, const char* path, struct statvfs* buf) { clobber_context_user_data(fs); zero_statvfs(buf); switch (fs->op_version) { /* FUSE < 2.1 used "struct fuse_statfs". */ case 11: if (((const struct fuse_operations_v11*)fs->op)->statfs) { struct fuse_statfs statfs_v11; int ret; ret = ((const struct fuse_operations_v11*)fs->op)->statfs(path, &statfs_v11); if (ret == 0) fuse_statfs_to_statvfs(buf, &statfs_v11); return ret; } else return 0; /* Special case */ /* FUSE >= 2.2 && < 2.5 used Linux-specific "struct * statfs". */ #define CALL_LINUX_STATFS(VER) \ case VER: \ if (((const struct __CONCAT(fuse_operations_v,VER)*)fs->op)->statfs) { \ struct statfs statfs_v22; \ int ret; \ \ ret = ((const struct __CONCAT(fuse_operations_v,VER)*)fs->op)->statfs(path, &statfs_v22); \ if (ret == 0) \ linux_statfs_to_statvfs(buf, &statfs_v22); \ \ return ret; \ } \ else \ return 0; /* Special case */ CALL_LINUX_STATFS(22); CALL_LINUX_STATFS(23); #undef CALL_STATFS /* FUSE >= 2.5 use struct statvfs. */ #define CALL_STATFS(VER) \ case VER: \ if (((const struct __CONCAT(fuse_operations_v,VER)*)fs->op)->statfs) \ return ((const struct __CONCAT(fuse_operations_v,VER)*)fs->op)->statfs(path, buf); \ else \ return 0; /* Special case */ CALL_STATFS(25); CALL_STATFS(26); CALL_STATFS(28); CALL_STATFS(29); CALL_STATFS(30); CALL_STATFS(34); CALL_STATFS(35); CALL_STATFS(38); #undef CALL_STATFS default: UNKNOWN_VERSION(fs->op_version); } } int fuse_fs_opendir(struct fuse_fs* fs, const char* path, struct fuse_file_info* fi) { clobber_context_user_data(fs); switch (fs->op_version) { /* FUSE < 2.3 didn't have opendir() and used to read * directories without opening them. */ case 11: case 21: case 22: return 0; /* Special case */ #define CALL_OPENDIR(VER) \ case VER: \ if (((const struct __CONCAT(fuse_operations_v,VER)*)fs->op)->opendir) \ return ((const struct __CONCAT(fuse_operations_v,VER)*)fs->op)->opendir(path, fi); \ else \ return 0 /* Special case */ CALL_OPENDIR(23); CALL_OPENDIR(25); CALL_OPENDIR(26); CALL_OPENDIR(28); CALL_OPENDIR(29); CALL_OPENDIR(30); CALL_OPENDIR(34); CALL_OPENDIR(35); CALL_OPENDIR(38); #undef CALL_OPENDIR default: UNKNOWN_VERSION(fs->op_version); } } /* =================================== * -=- The readdir Madness -=- * Juggling with Nested Shims * =================================== */ struct fuse_fill_dir_v23_shim { void* dirh; fuse_fill_dir_t_v23 fill_dir_v23; }; /* Translate dirent DT_* to mode_t. Needed by shim functions. */ static mode_t dt_to_mode(int dt) { switch (dt) { case DT_UNKNOWN: return 0; case DT_FIFO: return S_IFIFO; case DT_CHR: return S_IFCHR; case DT_DIR: return S_IFCHR; case DT_BLK: return S_IFBLK; case DT_REG: return S_IFREG; case DT_LNK: return S_IFLNK; case DT_SOCK: return S_IFSOCK; case DT_WHT: return S_IFWHT; default: errx(EXIT_FAILURE, "%s: unknown dirent type: %d", __func__, dt); } } /* This is a shim function that satisfies the type of * fuse_dirfil_t_v11 but calls fuse_fill_dir_v23. */ static int fuse_dirfil_v11_to_fill_dir_v23(fuse_dirh_t handle, const char* name, int type) { struct fuse_fill_dir_v23_shim* shim = handle; struct stat stbuf; int res; /* 1 or 0 */ memset(&stbuf, 0, sizeof(stbuf)); stbuf.st_mode = dt_to_mode(type); res = shim->fill_dir_v23(shim->dirh, name, &stbuf, 0); return res ? -ENOMEM : 0; } /* This is a shim function that satisfies the type of * fuse_dirfil_t_v22 but calls fuse_fill_dir_v23. */ static int fuse_dirfil_v22_to_fill_dir_v23(fuse_dirh_t handle, const char* name, int type, ino_t ino) { struct fuse_fill_dir_v23_shim* shim = handle; struct stat stbuf; int res; /* 1 or 0 */ memset(&stbuf, 0, sizeof(stbuf)); stbuf.st_mode = dt_to_mode(type); stbuf.st_ino = ino; res = shim->fill_dir_v23(shim->dirh, name, &stbuf, 0); return res ? -ENOMEM : 0; } struct fuse_fill_dir_v30_shim { void* dirh; fuse_fill_dir_t_v30 fill_dir_v30; }; /* This is a shim function that satisfies the type of * fuse_fill_dir_v23 but calls fuse_fill_dir_v30. */ static int fuse_fill_dir_v23_to_v30(void* buf, const char* name, const struct stat* stat, off_t off) { struct fuse_fill_dir_v30_shim* shim = buf; return shim->fill_dir_v30(shim->dirh, name, stat, off, (enum fuse_fill_dir_flags)0); } int fuse_fs_readdir_v27(struct fuse_fs* fs, const char* path, void* buf, fuse_fill_dir_t_v23 filler, off_t off, struct fuse_file_info* fi) { struct fuse_fill_dir_v23_shim v23_shim; v23_shim.dirh = buf; v23_shim.fill_dir_v23 = filler; clobber_context_user_data(fs); switch (fs->op_version) { /* FUSE < 2.2 had getdir() that used fuse_dirfil_t_v11. */ #define CALL_GETDIR_V11(VER) \ case VER: \ if (((const struct __CONCAT(fuse_operations_v,VER)*)fs->op)->getdir) \ return ((const struct __CONCAT(fuse_operations_v,VER)*)fs->op)->getdir(path, &v23_shim, fuse_dirfil_v11_to_fill_dir_v23); \ else \ return -ENOSYS CALL_GETDIR_V11(11); CALL_GETDIR_V11(21); #undef CALL_GETDIR_V11 /* FUSE 2.2 had getdir() that used fuse_dirfil_t_v22 but * didn't have readdir(). */ case 22: if (((const struct fuse_operations_v22*)fs->op)->getdir) return ((const struct fuse_operations_v22*)fs->op)->getdir(path, &v23_shim, fuse_dirfil_v22_to_fill_dir_v23); else return -ENOSYS; /* FUSE 2.3 introduced readdir() but still had getdir() as * a deprecated operation. It had been this way until FUSE 3.0 * finally removed getdir() and also changed the prototype of * readdir(). */ #define CALL_READDIR_OR_GETDIR(VER) \ case VER: \ if (((const struct __CONCAT(fuse_operations_v,VER)*)fs->op)->readdir) \ return ((const struct __CONCAT(fuse_operations_v,VER)*)fs->op)->readdir(path, buf, filler, off, fi); \ else if (((const struct __CONCAT(fuse_operations_v,VER)*)fs->op)->getdir) \ return ((const struct __CONCAT(fuse_operations_v,VER)*)fs->op)->getdir(path, &v23_shim, fuse_dirfil_v22_to_fill_dir_v23); \ else \ return -ENOSYS CALL_READDIR_OR_GETDIR(23); CALL_READDIR_OR_GETDIR(25); CALL_READDIR_OR_GETDIR(26); CALL_READDIR_OR_GETDIR(28); CALL_READDIR_OR_GETDIR(29); #undef CALL_READDIR_OR_GETDIR default: /* FUSE >= 3.0 filesystems will never call this function. We * can safely ignore them here. */ UNKNOWN_VERSION(fs->op_version); } } int fuse_fs_readdir_v30(struct fuse_fs* fs, const char* path, void* buf, fuse_fill_dir_t_v30 filler, off_t off, struct fuse_file_info* fi, enum fuse_readdir_flags flags) { clobber_context_user_data(fs); if (fs->op_version < 30) { struct fuse_fill_dir_v30_shim v30_shim; v30_shim.dirh = buf; v30_shim.fill_dir_v30 = filler; return fuse_fs_readdir_v27(fs, path, &v30_shim, fuse_fill_dir_v23_to_v30, off, fi); } else { switch (fs->op_version) { #define CALL_READDIR(VER) \ case VER: \ if (((const struct __CONCAT(fuse_operations_v,VER)*)fs->op)->readdir) \ return ((const struct __CONCAT(fuse_operations_v,VER)*)fs->op)->readdir(path, buf, filler, off, fi, flags); \ else \ return -ENOSYS CALL_READDIR(30); CALL_READDIR(34); CALL_READDIR(35); CALL_READDIR(38); #undef CALL_READDIR default: UNKNOWN_VERSION(fs->op_version); } } } /* ============================== * The End of readdir Madness * ============================== */ int fuse_fs_fsyncdir(struct fuse_fs* fs, const char* path, int datasync, struct fuse_file_info* fi) { clobber_context_user_data(fs); /* fsyncdir() appeared on FUSE 2.3. */ switch (fs->op_version) { case 11: case 21: case 22: return -ENOSYS; #define CALL_FSYNCDIR(VER) \ case VER: \ if (((const struct __CONCAT(fuse_operations_v,VER)*)fs->op)->fsyncdir) \ return ((const struct __CONCAT(fuse_operations_v,VER)*)fs->op)->fsyncdir(path, datasync, fi); \ else \ return -ENOSYS CALL_FSYNCDIR(23); CALL_FSYNCDIR(25); CALL_FSYNCDIR(26); CALL_FSYNCDIR(28); CALL_FSYNCDIR(29); CALL_FSYNCDIR(30); CALL_FSYNCDIR(34); CALL_FSYNCDIR(35); CALL_FSYNCDIR(38); #undef CALL_FSYNCDIR default: UNKNOWN_VERSION(fs->op_version); } } int fuse_fs_releasedir(struct fuse_fs* fs, const char* path, struct fuse_file_info* fi) { clobber_context_user_data(fs); switch (fs->op_version) { /* FUSE < 2.3 didn't have releasedir() and was reading * directories without opening them. */ case 11: case 21: case 22: return 0; /* Special case */ #define CALL_RELEASEDIR(VER) \ case VER: \ if (((const struct __CONCAT(fuse_operations_v,VER)*)fs->op)->releasedir) \ return ((const struct __CONCAT(fuse_operations_v,VER)*)fs->op)->releasedir(path, fi); \ else \ return 0 /* Special case */ CALL_RELEASEDIR(23); CALL_RELEASEDIR(25); CALL_RELEASEDIR(26); CALL_RELEASEDIR(28); CALL_RELEASEDIR(29); CALL_RELEASEDIR(30); CALL_RELEASEDIR(34); CALL_RELEASEDIR(35); CALL_RELEASEDIR(38); #undef CALL_RELEASEDIR default: UNKNOWN_VERSION(fs->op_version); } } int fuse_fs_create(struct fuse_fs* fs, const char* path, mode_t mode, struct fuse_file_info* fi) { clobber_context_user_data(fs); switch (fs->op_version) { /* FUSE < 2.5 didn't have create(). */ case 11: case 21: case 22: case 23: return -ENOSYS; #define CALL_CREATE(VER) \ case VER: \ if (((const struct __CONCAT(fuse_operations_v,VER)*)fs->op)->create) \ return ((const struct __CONCAT(fuse_operations_v,VER)*)fs->op)->create(path, mode, fi); \ else \ return -ENOSYS CALL_CREATE(25); CALL_CREATE(26); CALL_CREATE(28); CALL_CREATE(29); CALL_CREATE(30); CALL_CREATE(34); CALL_CREATE(35); CALL_CREATE(38); #undef CALL_CREATE default: UNKNOWN_VERSION(fs->op_version); } } int fuse_fs_lock(struct fuse_fs* fs, const char* path, struct fuse_file_info* fi, int cmd, struct flock* lock) { clobber_context_user_data(fs); /* locK() appeared on FUSE 2.6. */ switch (fs->op_version) { case 11: case 21: case 22: case 23: case 25: return -ENOSYS; #define CALL_LOCK(VER) \ case VER: \ if (((const struct __CONCAT(fuse_operations_v,VER)*)fs->op)->lock) \ return ((const struct __CONCAT(fuse_operations_v,VER)*)fs->op)->lock(path, fi, cmd, lock); \ else \ return -ENOSYS CALL_LOCK(26); CALL_LOCK(28); CALL_LOCK(29); CALL_LOCK(30); CALL_LOCK(34); CALL_LOCK(35); CALL_LOCK(38); #undef CALL_LOCK default: UNKNOWN_VERSION(fs->op_version); } } int fuse_fs_flock(struct fuse_fs* fs, const char* path, struct fuse_file_info* fi, int op) { clobber_context_user_data(fs); /* flocK() appeared on FUSE 2.9. */ switch (fs->op_version) { case 11: case 21: case 22: case 23: case 25: case 26: case 28: return -ENOSYS; #define CALL_FLOCK(VER) \ case VER: \ if (((const struct __CONCAT(fuse_operations_v,VER)*)fs->op)->flock) \ return ((const struct __CONCAT(fuse_operations_v,VER)*)fs->op)->flock(path, fi, op); \ else \ return -ENOSYS CALL_FLOCK(29); CALL_FLOCK(30); CALL_FLOCK(34); CALL_FLOCK(35); CALL_FLOCK(38); #undef CALL_FLOCK default: UNKNOWN_VERSION(fs->op_version); } } int fuse_fs_chmod_v27(struct fuse_fs *fs, const char *path, mode_t mode) { return fuse_fs_chmod_v30(fs, path, mode, NULL); } int fuse_fs_chmod_v30(struct fuse_fs* fs, const char* path, mode_t mode, struct fuse_file_info* fi) { clobber_context_user_data(fs); switch (fs->op_version) { #define CALL_OLD_CHMOD(VER) \ case VER: \ if (((const struct __CONCAT(fuse_operations_v,VER)*)fs->op)->chmod) \ return ((const struct __CONCAT(fuse_operations_v,VER)*)fs->op)->chmod(path, mode); \ else \ return -ENOSYS CALL_OLD_CHMOD(11); CALL_OLD_CHMOD(21); CALL_OLD_CHMOD(22); CALL_OLD_CHMOD(23); CALL_OLD_CHMOD(25); CALL_OLD_CHMOD(26); CALL_OLD_CHMOD(28); CALL_OLD_CHMOD(29); #undef CALL_OLD_CHMOD #define CALL_CHMOD(VER) \ case VER: \ if (((const struct __CONCAT(fuse_operations_v,VER)*)fs->op)->chmod) \ return ((const struct __CONCAT(fuse_operations_v,VER)*)fs->op)->chmod(path, mode, fi); \ else \ return -ENOSYS CALL_CHMOD(30); CALL_CHMOD(34); CALL_CHMOD(35); CALL_CHMOD(38); #undef CALL_CHMOD default: UNKNOWN_VERSION(fs->op_version); } } int fuse_fs_chown_v27(struct fuse_fs *fs, const char *path, uid_t uid, gid_t gid) { return fuse_fs_chown_v30(fs, path, uid, gid, NULL); } int fuse_fs_chown_v30(struct fuse_fs* fs, const char* path, uid_t uid, gid_t gid, struct fuse_file_info* fi) { clobber_context_user_data(fs); switch (fs->op_version) { #define CALL_OLD_CHOWN(VER) \ case VER: \ if (((const struct __CONCAT(fuse_operations_v,VER)*)fs->op)->chown) \ return ((const struct __CONCAT(fuse_operations_v,VER)*)fs->op)->chown(path, uid, gid); \ else \ return -ENOSYS CALL_OLD_CHOWN(11); CALL_OLD_CHOWN(21); CALL_OLD_CHOWN(22); CALL_OLD_CHOWN(23); CALL_OLD_CHOWN(25); CALL_OLD_CHOWN(26); CALL_OLD_CHOWN(28); CALL_OLD_CHOWN(29); #undef CALL_OLD_CHOWN #define CALL_CHOWN(VER) \ case VER: \ if (((const struct __CONCAT(fuse_operations_v,VER)*)fs->op)->chown) \ return ((const struct __CONCAT(fuse_operations_v,VER)*)fs->op)->chown(path, uid, gid, fi); \ else \ return -ENOSYS CALL_CHOWN(30); CALL_CHOWN(34); CALL_CHOWN(35); CALL_CHOWN(38); #undef CALL_CHOWN default: UNKNOWN_VERSION(fs->op_version); } } int fuse_fs_truncate_v27(struct fuse_fs *fs, const char *path, off_t size) { return fuse_fs_truncate_v30(fs, path, size, NULL); } int fuse_fs_truncate_v30(struct fuse_fs* fs, const char* path, off_t size, struct fuse_file_info* fi) { clobber_context_user_data(fs); switch (fs->op_version) { #define CALL_OLD_TRUNCATE(VER) \ case VER: \ if (((const struct __CONCAT(fuse_operations_v,VER)*)fs->op)->truncate) \ return ((const struct __CONCAT(fuse_operations_v,VER)*)fs->op)->truncate(path, size); \ else \ return -ENOSYS CALL_OLD_TRUNCATE(11); CALL_OLD_TRUNCATE(21); CALL_OLD_TRUNCATE(22); CALL_OLD_TRUNCATE(23); CALL_OLD_TRUNCATE(25); CALL_OLD_TRUNCATE(26); CALL_OLD_TRUNCATE(28); CALL_OLD_TRUNCATE(29); #undef CALL_OLD_TRUNCATE #define CALL_TRUNCATE(VER) \ case VER: \ if (((const struct __CONCAT(fuse_operations_v,VER)*)fs->op)->truncate) \ return ((const struct __CONCAT(fuse_operations_v,VER)*)fs->op)->truncate(path, size, fi); \ else \ return -ENOSYS CALL_TRUNCATE(30); CALL_TRUNCATE(34); CALL_TRUNCATE(35); CALL_TRUNCATE(38); #undef CALL_TRUNCATE default: UNKNOWN_VERSION(fs->op_version); } } int fuse_fs_ftruncate(struct fuse_fs* fs, const char* path, off_t size, struct fuse_file_info* fi) { clobber_context_user_data(fs); switch (fs->op_version) { /* FUSE < 2.5 didn't have ftruncate(). Always fall back to * truncate(). */ #define CALL_OLD_TRUNCATE(VER) \ case VER: \ if (((const struct __CONCAT(fuse_operations_v,VER)*)fs->op)->truncate) \ return ((const struct __CONCAT(fuse_operations_v,VER)*)fs->op)->truncate(path, size); \ else \ return -ENOSYS CALL_OLD_TRUNCATE(11); CALL_OLD_TRUNCATE(21); CALL_OLD_TRUNCATE(22); CALL_OLD_TRUNCATE(23); #undef CALL_OLD_TRUNCATE /* ftruncate() appeared on FUSE 2.5 and then disappeared on * FUSE 3.0. Call it if it exists, or fall back to truncate() * otherwise. */ #define CALL_FTRUNCATE_OR_TRUNCATE(VER) \ case VER: \ if (((const struct __CONCAT(fuse_operations_v,VER)*)fs->op)->ftruncate) \ return ((const struct __CONCAT(fuse_operations_v,VER)*)fs->op)->ftruncate(path, size, fi); \ else if (((const struct __CONCAT(fuse_operations_v,VER)*)fs->op)->truncate) \ return ((const struct __CONCAT(fuse_operations_v,VER)*)fs->op)->truncate(path, size); \ else \ return -ENOSYS CALL_FTRUNCATE_OR_TRUNCATE(25); CALL_FTRUNCATE_OR_TRUNCATE(26); CALL_FTRUNCATE_OR_TRUNCATE(28); CALL_FTRUNCATE_OR_TRUNCATE(29); #undef CALL_FTRUNCATE_OR_TRUNCATE /* FUSE >= 3.0 have truncate() but with a different function * type. */ #define CALL_TRUNCATE(VER) \ case VER: \ if (((const struct __CONCAT(fuse_operations_v,VER)*)fs->op)->truncate) \ return ((const struct __CONCAT(fuse_operations_v,VER)*)fs->op)->truncate(path, size, fi); \ else \ return -ENOSYS CALL_TRUNCATE(30); CALL_TRUNCATE(34); CALL_TRUNCATE(35); CALL_TRUNCATE(38); #undef CALL_TRUNCATE default: UNKNOWN_VERSION(fs->op_version); } } int fuse_fs_utimens_v27(struct fuse_fs *fs, const char *path, const struct timespec tv[2]) { return fuse_fs_utimens_v30(fs, path, tv, NULL); } int fuse_fs_utimens_v30(struct fuse_fs* fs, const char* path, const struct timespec tv[2], struct fuse_file_info* fi) { struct utimbuf timbuf; timbuf.actime = tv[0].tv_sec; timbuf.modtime = tv[1].tv_sec; clobber_context_user_data(fs); switch (fs->op_version) { /* FUSE < 2.6 didn't have utimens() but had utime() * instead. */ #define CALL_UTIME(VER) \ case VER: \ if (((const struct __CONCAT(fuse_operations_v,VER)*)fs->op)->utime) \ return ((const struct __CONCAT(fuse_operations_v,VER)*)fs->op)->utime(path, &timbuf); \ else \ return -ENOSYS CALL_UTIME(11); CALL_UTIME(21); CALL_UTIME(22); CALL_UTIME(23); CALL_UTIME(25); #undef CALL_UTIME /* utimens() appeared on FUSE 2.6. Call it if it exists, or fall back to * utime() otherwise. */ #define CALL_UTIMENS_OR_UTIME(VER) \ case VER: \ if (((const struct __CONCAT(fuse_operations_v,VER)*)fs->op)->utimens) \ return ((const struct __CONCAT(fuse_operations_v,VER)*)fs->op)->utimens(path, tv); \ else if (((const struct __CONCAT(fuse_operations_v,VER)*)fs->op)->utime) \ return ((const struct __CONCAT(fuse_operations_v,VER)*)fs->op)->utime(path, &timbuf); \ else \ return -ENOSYS CALL_UTIMENS_OR_UTIME(26); CALL_UTIMENS_OR_UTIME(28); CALL_UTIMENS_OR_UTIME(29); #undef CALL_UTIMENS_OR_UTIME /* utime() disappeared on FUSE 3.0. */ #define CALL_UTIMENS(VER) \ case VER: \ if (((const struct __CONCAT(fuse_operations_v,VER)*)fs->op)->utimens) \ return ((const struct __CONCAT(fuse_operations_v,VER)*)fs->op)->utimens(path, tv, fi); \ else \ return -ENOSYS CALL_UTIMENS(30); CALL_UTIMENS(34); CALL_UTIMENS(35); CALL_UTIMENS(38); #undef CALL_UTIMENS default: UNKNOWN_VERSION(fs->op_version); } } int fuse_fs_access(struct fuse_fs* fs, const char* path, int mask) { clobber_context_user_data(fs); /* access() appeared on FUSE 2.5. */ switch (fs->op_version) { case 11: case 21: case 22: case 23: return -ENOSYS; #define CALL_ACCESS(VER) \ case VER: \ if (((const struct __CONCAT(fuse_operations_v,VER)*)fs->op)->access) \ return ((const struct __CONCAT(fuse_operations_v,VER)*)fs->op)->access(path, mask); \ else \ return -ENOSYS CALL_ACCESS(25); CALL_ACCESS(26); CALL_ACCESS(28); CALL_ACCESS(29); CALL_ACCESS(30); CALL_ACCESS(34); CALL_ACCESS(35); CALL_ACCESS(38); #undef CALL_ACCESS default: UNKNOWN_VERSION(fs->op_version); } } int fuse_fs_readlink(struct fuse_fs* fs, const char* path, char* buf, size_t len) { clobber_context_user_data(fs); switch (fs->op_version) { #define CALL_READLINK(VER) \ case VER: \ if (((const struct __CONCAT(fuse_operations_v,VER)*)fs->op)->readlink) \ return ((const struct __CONCAT(fuse_operations_v,VER)*)fs->op)->readlink(path, buf, len); \ else \ return -ENOSYS CALL_READLINK(11); CALL_READLINK(21); CALL_READLINK(22); CALL_READLINK(23); CALL_READLINK(25); CALL_READLINK(26); CALL_READLINK(28); CALL_READLINK(29); CALL_READLINK(30); CALL_READLINK(34); CALL_READLINK(35); CALL_READLINK(38); #undef CALL_READLINK default: UNKNOWN_VERSION(fs->op_version); } } int fuse_fs_mknod(struct fuse_fs* fs, const char* path, mode_t mode, dev_t rdev) { clobber_context_user_data(fs); switch (fs->op_version) { #define CALL_MKNOD(VER) \ case VER: \ if (((const struct __CONCAT(fuse_operations_v,VER)*)fs->op)->mknod) \ return ((const struct __CONCAT(fuse_operations_v,VER)*)fs->op)->mknod(path, mode, rdev); \ else \ return -ENOSYS CALL_MKNOD(11); CALL_MKNOD(21); CALL_MKNOD(22); CALL_MKNOD(23); CALL_MKNOD(25); CALL_MKNOD(26); CALL_MKNOD(28); CALL_MKNOD(29); CALL_MKNOD(30); CALL_MKNOD(34); CALL_MKNOD(35); CALL_MKNOD(38); #undef CALL_MKNOD default: UNKNOWN_VERSION(fs->op_version); } } int fuse_fs_mkdir(struct fuse_fs* fs, const char* path, mode_t mode) { clobber_context_user_data(fs); switch (fs->op_version) { #define CALL_MKDIR(VER) \ case VER: \ if (((const struct __CONCAT(fuse_operations_v,VER)*)fs->op)->mkdir) \ return ((const struct __CONCAT(fuse_operations_v,VER)*)fs->op)->mkdir(path, mode); \ else \ return -ENOSYS CALL_MKDIR(11); CALL_MKDIR(21); CALL_MKDIR(22); CALL_MKDIR(23); CALL_MKDIR(25); CALL_MKDIR(26); CALL_MKDIR(28); CALL_MKDIR(29); CALL_MKDIR(30); CALL_MKDIR(34); CALL_MKDIR(35); CALL_MKDIR(38); #undef CALL_MKDIR default: UNKNOWN_VERSION(fs->op_version); } } int fuse_fs_setxattr(struct fuse_fs* fs, const char* path, const char* name, const char* value, size_t size, int flags) { clobber_context_user_data(fs); /* setxattr() appeared on FUSE 2.1. */ switch (fs->op_version) { case 11: return -ENOSYS; #define CALL_SETXATTR(VER) \ case VER: \ if (((const struct __CONCAT(fuse_operations_v,VER)*)fs->op)->setxattr) \ return ((const struct __CONCAT(fuse_operations_v,VER)*)fs->op)->setxattr(path, name, value, size, flags); \ else \ return -ENOSYS CALL_SETXATTR(21); CALL_SETXATTR(22); CALL_SETXATTR(23); CALL_SETXATTR(25); CALL_SETXATTR(26); CALL_SETXATTR(28); CALL_SETXATTR(29); CALL_SETXATTR(30); CALL_SETXATTR(34); CALL_SETXATTR(35); CALL_SETXATTR(38); #undef CALL_SETXATTR default: UNKNOWN_VERSION(fs->op_version); } } int fuse_fs_getxattr(struct fuse_fs* fs, const char* path, const char* name, char* value, size_t size) { clobber_context_user_data(fs); /* getxattr() appeared on FUSE 2.1. */ switch (fs->op_version) { case 11: return -ENOSYS; #define CALL_GETXATTR(VER) \ case VER: \ if (((const struct __CONCAT(fuse_operations_v,VER)*)fs->op)->getxattr) \ return ((const struct __CONCAT(fuse_operations_v,VER)*)fs->op)->getxattr(path, name, value, size); \ else \ return -ENOSYS CALL_GETXATTR(21); CALL_GETXATTR(22); CALL_GETXATTR(23); CALL_GETXATTR(25); CALL_GETXATTR(26); CALL_GETXATTR(28); CALL_GETXATTR(29); CALL_GETXATTR(30); CALL_GETXATTR(34); CALL_GETXATTR(35); CALL_GETXATTR(38); #undef CALL_GETXATTR default: UNKNOWN_VERSION(fs->op_version); } } int fuse_fs_listxattr(struct fuse_fs* fs, const char* path, char* list, size_t size) { clobber_context_user_data(fs); /* listxattr() appeared on FUSE 2.1. */ switch (fs->op_version) { case 11: return -ENOSYS; #define CALL_LISTXATTR(VER) \ case VER: \ if (((const struct __CONCAT(fuse_operations_v,VER)*)fs->op)->listxattr) \ return ((const struct __CONCAT(fuse_operations_v,VER)*)fs->op)->listxattr(path, list, size); \ else \ return -ENOSYS CALL_LISTXATTR(21); CALL_LISTXATTR(22); CALL_LISTXATTR(23); CALL_LISTXATTR(25); CALL_LISTXATTR(26); CALL_LISTXATTR(28); CALL_LISTXATTR(29); CALL_LISTXATTR(30); CALL_LISTXATTR(34); CALL_LISTXATTR(35); CALL_LISTXATTR(38); #undef CALL_LISTXATTR default: UNKNOWN_VERSION(fs->op_version); } } int fuse_fs_removexattr(struct fuse_fs* fs, const char* path, const char* name) { clobber_context_user_data(fs); /* removexattr() appeared on FUSE 2.1. */ switch (fs->op_version) { case 11: return -ENOSYS; #define CALL_REMOVEXATTR(VER) \ case VER: \ if (((const struct __CONCAT(fuse_operations_v,VER)*)fs->op)->removexattr) \ return ((const struct __CONCAT(fuse_operations_v,VER)*)fs->op)->removexattr(path, name); \ else \ return -ENOSYS CALL_REMOVEXATTR(21); CALL_REMOVEXATTR(22); CALL_REMOVEXATTR(23); CALL_REMOVEXATTR(25); CALL_REMOVEXATTR(26); CALL_REMOVEXATTR(28); CALL_REMOVEXATTR(29); CALL_REMOVEXATTR(30); CALL_REMOVEXATTR(34); CALL_REMOVEXATTR(35); CALL_REMOVEXATTR(38); #undef CALL_REMOVEXATTR default: UNKNOWN_VERSION(fs->op_version); } } int fuse_fs_bmap(struct fuse_fs* fs, const char* path, size_t blocksize, uint64_t *idx) { clobber_context_user_data(fs); /* bmap() appeared on FUSE 2.6. */ switch (fs->op_version) { case 11: case 22: case 23: case 25: return -ENOSYS; #define CALL_BMAP(VER) \ case VER: \ if (((const struct __CONCAT(fuse_operations_v,VER)*)fs->op)->bmap) \ return ((const struct __CONCAT(fuse_operations_v,VER)*)fs->op)->bmap(path, blocksize, idx); \ else \ return -ENOSYS CALL_BMAP(26); CALL_BMAP(28); CALL_BMAP(29); CALL_BMAP(30); CALL_BMAP(34); CALL_BMAP(35); CALL_BMAP(38); #undef CALL_BMAP default: UNKNOWN_VERSION(fs->op_version); } } int fuse_fs_ioctl_v28(struct fuse_fs* fs, const char* path, int cmd, void* arg, struct fuse_file_info* fi, unsigned int flags, void* data) { return fuse_fs_ioctl_v35(fs, path, (unsigned int)cmd, arg, fi, flags, data); } int fuse_fs_ioctl_v35(struct fuse_fs* fs, const char* path, unsigned int cmd, void* arg, struct fuse_file_info* fi, unsigned int flags, void* data) { clobber_context_user_data(fs); switch (fs->op_version) { /* ioctl() appeared on FUSE 2.8 but with (int)cmd. */ case 11: case 22: case 23: case 25: case 26: return -ENOSYS; #define CALL_OLD_IOCTL(VER) \ case VER: \ if (((const struct __CONCAT(fuse_operations_v,VER)*)fs->op)->ioctl) \ return ((const struct __CONCAT(fuse_operations_v,VER)*)fs->op)->ioctl(path, (int)cmd, arg, fi, flags, data); \ else \ return -ENOSYS CALL_OLD_IOCTL(28); CALL_OLD_IOCTL(29); CALL_OLD_IOCTL(30); CALL_OLD_IOCTL(34); #undef CALL_OLD_IOCTL /* It was then changed to (unsigned int)cmd on FUSE 3.5. */ #define CALL_IOCTL(VER) \ case VER: \ if (((const struct __CONCAT(fuse_operations_v,VER)*)fs->op)->ioctl) \ return ((const struct __CONCAT(fuse_operations_v,VER)*)fs->op)->ioctl(path, cmd, arg, fi, flags, data); \ else \ return -ENOSYS CALL_IOCTL(35); CALL_IOCTL(38); #undef CALL_IOCTL default: UNKNOWN_VERSION(fs->op_version); } } int fuse_fs_poll(struct fuse_fs* fs, const char* path, struct fuse_file_info* fi, struct fuse_pollhandle* ph, unsigned* reventsp) { clobber_context_user_data(fs); /* poll() appeared on FUSE 2.8. */ switch (fs->op_version) { case 11: case 22: case 23: case 25: case 26: return -ENOSYS; #define CALL_POLL(VER) \ case VER: \ if (((const struct __CONCAT(fuse_operations_v,VER)*)fs->op)->poll) \ return ((const struct __CONCAT(fuse_operations_v,VER)*)fs->op)->poll(path, fi, ph, reventsp); \ else \ return -ENOSYS CALL_POLL(28); CALL_POLL(29); CALL_POLL(30); CALL_POLL(34); CALL_POLL(35); CALL_POLL(38); #undef CALL_POLL default: UNKNOWN_VERSION(fs->op_version); } } int fuse_fs_fallocate(struct fuse_fs* fs, const char* path, int mode, off_t offset, off_t length, struct fuse_file_info* fi) { clobber_context_user_data(fs); /* fallocate() appeared on FUSE 2.9. */ switch (fs->op_version) { case 11: case 22: case 23: case 25: case 26: case 28: return -ENOSYS; #define CALL_FALLOCATE(VER) \ case VER: \ if (((const struct __CONCAT(fuse_operations_v,VER)*)fs->op)->fallocate) \ return ((const struct __CONCAT(fuse_operations_v,VER)*)fs->op)->fallocate(path, mode, offset, length, fi); \ else \ return -ENOSYS CALL_FALLOCATE(29); CALL_FALLOCATE(30); CALL_FALLOCATE(34); CALL_FALLOCATE(35); CALL_FALLOCATE(38); #undef CALL_FALLOCATE default: UNKNOWN_VERSION(fs->op_version); } } ssize_t fuse_fs_copy_file_range(struct fuse_fs *fs, const char *path_in, struct fuse_file_info *fi_in, off_t off_in, const char *path_out, struct fuse_file_info *fi_out, off_t off_out, size_t len, int flags) { clobber_context_user_data(fs); /* copy_file_range() appeared on FUSE 3.4. */ switch (fs->op_version) { case 11: case 22: case 23: case 25: case 26: case 28: case 29: case 30: return -ENOSYS; #define CALL_COPY_FILE_RANGE(VER) \ case VER: \ if (((const struct __CONCAT(fuse_operations_v,VER)*)fs->op)->copy_file_range) \ return ((const struct __CONCAT(fuse_operations_v,VER)*)fs->op)->copy_file_range(path_in, fi_in, off_in, path_out, fi_out, off_out, len, flags); \ else \ return -ENOSYS CALL_COPY_FILE_RANGE(34); CALL_COPY_FILE_RANGE(35); CALL_COPY_FILE_RANGE(38); #undef CALL_COPY_FILE_RANGE default: UNKNOWN_VERSION(fs->op_version); } } off_t fuse_fs_lseek(struct fuse_fs* fs, const char* path, off_t off, int whence, struct fuse_file_info* fi) { clobber_context_user_data(fs); /* lseek() appeared on FUSE 3.8. */ switch (fs->op_version) { case 11: case 22: case 23: case 25: case 26: case 28: case 29: case 30: case 34: case 35: return -ENOSYS; #define CALL_LSEEK(VER) \ case VER: \ if (((const struct __CONCAT(fuse_operations_v,VER)*)fs->op)->lseek) \ return ((const struct __CONCAT(fuse_operations_v,VER)*)fs->op)->lseek(path, off, whence, fi); \ else \ return -ENOSYS CALL_LSEEK(38); #undef CALL_LSEEK default: UNKNOWN_VERSION(fs->op_version); } } void fuse_fs_init_v27(struct fuse_fs *fs, struct fuse_conn_info *conn) { fuse_fs_init_v30(fs, conn, NULL); } void fuse_fs_init_v30(struct fuse_fs* fs, struct fuse_conn_info* conn, struct fuse_config* cfg) { clobber_context_user_data(fs); switch (fs->op_version) { case 11: case 21: case 22: break; /* init() appeared on FUSE 2.3 as init(void). */ #define CALL_NULLARY_INIT(VER) \ case VER: \ if (((const struct __CONCAT(fuse_operations_v,VER)*)fs->op)->init) \ fs->user_data = ((const struct __CONCAT(fuse_operations_v,VER)*)fs->op)->init(); \ break CALL_NULLARY_INIT(23); CALL_NULLARY_INIT(25); #undef CALL_NULLARY_INIT /* It was changed to init(struct fuse_conn_info*) on FUSE * 2.6. */ #define CALL_UNARY_INIT(VER) \ case VER: \ if (((const struct __CONCAT(fuse_operations_v,VER)*)fs->op)->init) \ fs->user_data = ((const struct __CONCAT(fuse_operations_v,VER)*)fs->op)->init(conn); \ break CALL_UNARY_INIT(26); CALL_UNARY_INIT(28); CALL_UNARY_INIT(29); #undef CALL_INIT /* It was again changed to init(struct fuse_conn_info*, struct * fuse_config*) on FUSE 3.0. */ #define CALL_BINARY_INIT(VER) \ case VER: \ if (((const struct __CONCAT(fuse_operations_v,VER)*)fs->op)->init) \ fs->user_data = ((const struct __CONCAT(fuse_operations_v,VER)*)fs->op)->init(conn, cfg); \ break CALL_BINARY_INIT(30); CALL_BINARY_INIT(34); CALL_BINARY_INIT(35); CALL_BINARY_INIT(38); #undef CALL_BINARY_INIT default: UNKNOWN_VERSION(fs->op_version); } } void fuse_fs_destroy(struct fuse_fs *fs) { clobber_context_user_data(fs); switch (fs->op_version) { /* destroy() appeared on FUSE 2.3. */ case 11: case 21: case 22: break; #define CALL_DESTROY(VER) \ case VER: \ if (((const struct __CONCAT(fuse_operations_v,VER)*)fs->op)->destroy) \ ((const struct __CONCAT(fuse_operations_v,VER)*)fs->op)->destroy(fs->user_data); \ break CALL_DESTROY(23); CALL_DESTROY(25); CALL_DESTROY(26); CALL_DESTROY(28); CALL_DESTROY(29); CALL_DESTROY(30); CALL_DESTROY(34); CALL_DESTROY(35); CALL_DESTROY(38); #undef CALL_DESTROY default: UNKNOWN_VERSION(fs->op_version); } /* fuse_fs_destroy(3) also deallocates struct fuse_fs itself. */ free(fs->op); free(fs); }