From 1ddbef6a652aee5f6c6c8c5cba3732508ca0dfe0 Mon Sep 17 00:00:00 2001 From: fedir Date: Sun, 15 Dec 2024 15:41:28 +0100 Subject: [PATCH] Used the libfuse example as a foundation. The main.c was completely replaced by one of the libfuse examples: [passthrough_fh.c](https://github.com/libfuse/libfuse/blob/master/example/passthrough_fh.c) . This choice is made based on that there is little point in spending time on reinventing the wheel by reimplementing passthrough all over again. Also, the [passthrough_hp.cc](https://github.com/libfuse/libfuse/blob/master/example/passthrough_hp.cc) wasn't used because it uses a vastly different and much more comlex API, even though the authors claim it to be faster. --- sources/main.c | 719 ++++++++++++++++++++++++++++++++++++++++----- sources/sourcefs.c | 51 +++- sources/sourcefs.h | 30 +- 3 files changed, 698 insertions(+), 102 deletions(-) diff --git a/sources/main.c b/sources/main.c index 3bf17e8..84e0b7e 100644 --- a/sources/main.c +++ b/sources/main.c @@ -1,25 +1,653 @@ +/* + FUSE: Filesystem in Userspace + Copyright (C) 2001-2007 Miklos Szeredi + Copyright (C) 2011 Sebastian Pipping + + This program can be distributed under the terms of the GNU GPLv2. + See the file COPYING. +*/ + +/** @file + * + * This file system mirrors the existing file system hierarchy of the + * system, starting at the root file system. This is implemented by + * just "passing through" all requests to the corresponding user-space + * libc functions. This implementation is a little more sophisticated + * than the one in passthrough.c, so performance is not quite as bad. + * + * Compile with: + * + * gcc -Wall passthrough_fh.c `pkg-config fuse3 --cflags --libs` -lulockmgr + * -o passthrough_fh + * + * ## Source code ## + * \include passthrough_fh.c + */ + +#define FUSE_USE_VERSION 31 + +#define _GNU_SOURCE + +#include + +#ifdef HAVE_LIBULOCKMGR +#include +#endif + #include #include -#include +#include #include #include #include -#define FUSE_USE_VERSION 31 +#include +#include +#include +#ifdef HAVE_SETXATTR +#include +#endif +#include /* flock(2) */ #include "sourcefs.h" -#include const char *mountpoint = NULL; -/* - * #In this function we need to: - * - * * Open the folder we are mounting over (and remember it). - * * Initialize the process table. - */ -static void *icfs_init(struct fuse_conn_info *conn, struct fuse_config *cfg) { - (void)conn; /* Explicit way to tell we ignore these arguments. */ - (void)cfg; /* This also silences the compiler warnings. */ +static void *xmp_init(struct fuse_conn_info *conn, struct fuse_config *cfg) { + (void)conn; + cfg->use_ino = 1; + cfg->nullpath_ok = 1; + + /* parallel_direct_writes feature depends on direct_io features. + To make parallel_direct_writes valid, need either set cfg->direct_io + in current function (recommended in high level API) or set fi->direct_io + in xmp_create() or xmp_open(). */ + // cfg->direct_io = 1; + // cfg->parallel_direct_writes = 1; + + /* Pick up changes from lower filesystem right away. This is + also necessary for better hardlink support. When the kernel + calls the unlink() handler, it does not know the inode of + the to-be-removed entry and can therefore not invalidate + the cache of the associated inode - resulting in an + incorrect st_nlink value being reported for any remaining + hardlinks to this inode. */ + cfg->entry_timeout = 0; + cfg->attr_timeout = 0; + cfg->negative_timeout = 0; + + return NULL; +} + +static int xmp_getattr(const char *path, struct stat *stbuf, + struct fuse_file_info *fi) { + int res; + + (void)path; + + if (fi) + res = fstat(fi->fh, stbuf); + else + res = source_stat(path, stbuf); + if (res == -1) { + perror("Stat failed"); + return -errno; + } + + return 0; +} + +static int xmp_access(const char *path, int mask) { + int res; + + res = access(path, mask); + if (res == -1) + return -errno; + + return 0; +} + +static int xmp_readlink(const char *path, char *buf, size_t size) { + int res; + + res = readlink(path, buf, size - 1); + if (res == -1) + return -errno; + + buf[res] = '\0'; + return 0; +} + +struct xmp_dirp { + DIR *dp; + struct dirent *entry; + off_t offset; +}; + +static int xmp_opendir(const char *path, struct fuse_file_info *fi) { + int res; + struct xmp_dirp *d = malloc(sizeof(struct xmp_dirp)); + if (d == NULL) + return -ENOMEM; + + d->dp = source_opendir(path); + if (d->dp == NULL) { + perror("Opendir failed"); + res = -errno; + free(d); + return res; + } + d->offset = 0; + d->entry = NULL; + + fi->fh = (unsigned long)d; + return 0; +} + +static inline struct xmp_dirp *get_dirp(struct fuse_file_info *fi) { + return (struct xmp_dirp *)(uintptr_t)fi->fh; +} + +/* Complete copy of the example method(no need to modify anything so far) */ +static int xmp_readdir(const char *path, void *buf, fuse_fill_dir_t filler, + off_t offset, struct fuse_file_info *fi, + enum fuse_readdir_flags flags) { + struct xmp_dirp *d = get_dirp(fi); + + (void)path; + if (offset != d->offset) { +#ifndef __FreeBSD__ + seekdir(d->dp, offset); +#else + /* Subtract the one that we add when calling + telldir() below */ + seekdir(d->dp, offset - 1); +#endif + d->entry = NULL; + d->offset = offset; + } + while (1) { + struct stat st; + off_t nextoff; + // enum fuse_fill_dir_flags fill_flags = FUSE_FILL_DIR_DEFAULTS; + enum fuse_fill_dir_flags fill_flags = 0; + + if (!d->entry) { + d->entry = readdir(d->dp); + if (!d->entry) + break; + } +#ifdef HAVE_FSTATAT + if (flags & FUSE_READDIR_PLUS) { + int res; + + res = fstatat(dirfd(d->dp), d->entry->d_name, &st, AT_SYMLINK_NOFOLLOW); + if (res != -1) + fill_flags |= FUSE_FILL_DIR_PLUS; + } +#endif + if (!(fill_flags & FUSE_FILL_DIR_PLUS)) { + memset(&st, 0, sizeof(st)); + st.st_ino = d->entry->d_ino; + st.st_mode = d->entry->d_type << 12; + } + nextoff = telldir(d->dp); +#ifdef __FreeBSD__ + /* Under FreeBSD, telldir() may return 0 the first time + it is called. But for libfuse, an offset of zero + means that offsets are not supported, so we shift + everything by one. */ + nextoff++; +#endif + if (filler(buf, d->entry->d_name, &st, nextoff, fill_flags)) + break; + + d->entry = NULL; + d->offset = nextoff; + } + + return 0; +} + +/* Complete copy of the example method(no need to modify anything so far) */ +static int xmp_releasedir(const char *path, struct fuse_file_info *fi) { + struct xmp_dirp *d = get_dirp(fi); + (void)path; + closedir(d->dp); + free(d); + return 0; +} + +// TODO: make this work +static int xmp_mknod(const char *path, mode_t mode, dev_t rdev) { + int res; + + if (S_ISFIFO(mode)) + res = mkfifo(path, mode); + else + res = mknod(path, mode, rdev); + if (res == -1) + return -errno; + + return 0; +} + +static int xmp_mkdir(const char *path, mode_t mode) { + int res; + + res = source_mkdir(path, mode); + if (res == -1) + return -errno; + + return 0; +} + +static int xmp_unlink(const char *path) { + int res; + + res = source_unlink(path); + if (res == -1) + return -errno; + + return 0; +} + +static int xmp_rmdir(const char *path) { + int res; + + res = source_rmdir(path); + if (res == -1) + return -errno; + + return 0; +} + +static int xmp_symlink(const char *from, const char *to) { + int res; + + res = source_symlink(from, to); + if (res == -1) + return -errno; + + return 0; +} + +static int xmp_rename(const char *from, const char *to, unsigned int flags) { + int res; + + /* When we have renameat2() in libc, then we can implement flags */ + if (flags) + return -EINVAL; + + res = source_rename(from, to); + if (res == -1) + return -errno; + + return 0; +} + +static int xmp_link(const char *from, const char *to) { + int res; + + res = link(from, to); + if (res == -1) + return -errno; + + return 0; +} + +static int xmp_chmod(const char *path, mode_t mode, struct fuse_file_info *fi) { + int res; + + if (fi) + res = fchmod(fi->fh, mode); + else + res = chmod(path, mode); + if (res == -1) + return -errno; + + return 0; +} + +static int xmp_chown(const char *path, uid_t uid, gid_t gid, + struct fuse_file_info *fi) { + int res; + + if (fi) + res = fchown(fi->fh, uid, gid); + else + res = lchown(path, uid, gid); + if (res == -1) + return -errno; + + return 0; +} + +static int xmp_truncate(const char *path, off_t size, + struct fuse_file_info *fi) { + int res; + + if (fi) + res = ftruncate(fi->fh, size); + else + res = truncate(path, size); + + if (res == -1) + return -errno; + + return 0; +} + +#ifdef HAVE_UTIMENSAT +static int xmp_utimens(const char *path, const struct timespec ts[2], + struct fuse_file_info *fi) { + int res; + + /* don't use utime/utimes since they follow symlinks */ + if (fi) + res = futimens(fi->fh, ts); + else + res = utimensat(0, path, ts, AT_SYMLINK_NOFOLLOW); + if (res == -1) + return -errno; + + return 0; +} +#endif + +static int xmp_create(const char *path, mode_t mode, + struct fuse_file_info *fi) { + int fd; + + fd = open(path, fi->flags, mode); + if (fd == -1) + return -errno; + + fi->fh = fd; + return 0; +} + +static int xmp_open(const char *path, struct fuse_file_info *fi) { + int fd; + + fd = open(path, fi->flags); + if (fd == -1) + return -errno; + + /* Enable direct_io when open has flags O_DIRECT to enjoy the feature + parallel_direct_writes (i.e., to get a shared lock, not exclusive lock, + for writes to the same file). + if (fi->flags & O_DIRECT) { + fi->direct_io = 1; + fi->parallel_direct_writes = 1; + } + */ + + fi->fh = fd; + return 0; +} + +static int xmp_read(const char *path, char *buf, size_t size, off_t offset, + struct fuse_file_info *fi) { + int res; + + (void)path; + res = pread(fi->fh, buf, size, offset); + if (res == -1) + res = -errno; + + return res; +} + +static int xmp_read_buf(const char *path, struct fuse_bufvec **bufp, + size_t size, off_t offset, struct fuse_file_info *fi) { + struct fuse_bufvec *src; + + (void)path; + + src = malloc(sizeof(struct fuse_bufvec)); + if (src == NULL) + return -ENOMEM; + + *src = FUSE_BUFVEC_INIT(size); + + src->buf[0].flags = FUSE_BUF_IS_FD | FUSE_BUF_FD_SEEK; + src->buf[0].fd = fi->fh; + src->buf[0].pos = offset; + + *bufp = src; + + return 0; +} + +static int xmp_write(const char *path, const char *buf, size_t size, + off_t offset, struct fuse_file_info *fi) { + int res; + + (void)path; + res = pwrite(fi->fh, buf, size, offset); + if (res == -1) + res = -errno; + + return res; +} + +static int xmp_write_buf(const char *path, struct fuse_bufvec *buf, + off_t offset, struct fuse_file_info *fi) { + struct fuse_bufvec dst = FUSE_BUFVEC_INIT(fuse_buf_size(buf)); + + (void)path; + + dst.buf[0].flags = FUSE_BUF_IS_FD | FUSE_BUF_FD_SEEK; + dst.buf[0].fd = fi->fh; + dst.buf[0].pos = offset; + + return fuse_buf_copy(&dst, buf, FUSE_BUF_SPLICE_NONBLOCK); +} + +static int xmp_statfs(const char *path, struct statvfs *stbuf) { + int res; + + res = statvfs(path, stbuf); + if (res == -1) + return -errno; + + return 0; +} + +static int xmp_flush(const char *path, struct fuse_file_info *fi) { + int res; + + (void)path; + /* This is called from every close on an open file, so call the + close on the underlying filesystem. But since flush may be + called multiple times for an open file, this must not really + close the file. This is important if used on a network + filesystem like NFS which flush the data/metadata on close() */ + res = close(dup(fi->fh)); + if (res == -1) + return -errno; + + return 0; +} + +static int xmp_release(const char *path, struct fuse_file_info *fi) { + (void)path; + close(fi->fh); + + return 0; +} + +static int xmp_fsync(const char *path, int isdatasync, + struct fuse_file_info *fi) { + int res; + (void)path; + +#ifndef HAVE_FDATASYNC + (void)isdatasync; +#else + if (isdatasync) + res = fdatasync(fi->fh); + else +#endif + res = fsync(fi->fh); + if (res == -1) + return -errno; + + return 0; +} + +#ifdef HAVE_POSIX_FALLOCATE +static int xmp_fallocate(const char *path, int mode, off_t offset, off_t length, + struct fuse_file_info *fi) { + (void)path; + + if (mode) + return -EOPNOTSUPP; + + return -posix_fallocate(fi->fh, offset, length); +} +#endif + +#ifdef HAVE_SETXATTR +/* xattr operations are optional and can safely be left unimplemented */ +static int xmp_setxattr(const char *path, const char *name, const char *value, + size_t size, int flags) { + int res = lsetxattr(path, name, value, size, flags); + if (res == -1) + return -errno; + return 0; +} + +static int xmp_getxattr(const char *path, const char *name, char *value, + size_t size) { + int res = lgetxattr(path, name, value, size); + if (res == -1) + return -errno; + return res; +} + +static int xmp_listxattr(const char *path, char *list, size_t size) { + int res = llistxattr(path, list, size); + if (res == -1) + return -errno; + return res; +} + +static int xmp_removexattr(const char *path, const char *name) { + int res = lremovexattr(path, name); + if (res == -1) + return -errno; + return 0; +} +#endif /* HAVE_SETXATTR */ + +#ifdef HAVE_LIBULOCKMGR +static int xmp_lock(const char *path, struct fuse_file_info *fi, int cmd, + struct flock *lock) { + (void)path; + + return ulockmgr_op(fi->fh, cmd, lock, &fi->lock_owner, + sizeof(fi->lock_owner)); +} +#endif + +static int xmp_flock(const char *path, struct fuse_file_info *fi, int op) { + int res; + (void)path; + + res = flock(fi->fh, op); + if (res == -1) + return -errno; + + return 0; +} + +#ifdef HAVE_COPY_FILE_RANGE +static ssize_t xmp_copy_file_range(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) { + ssize_t res; + (void)path_in; + (void)path_out; + + res = copy_file_range(fi_in->fh, &off_in, fi_out->fh, &off_out, len, flags); + if (res == -1) + return -errno; + + return res; +} +#endif + +static off_t xmp_lseek(const char *path, off_t off, int whence, + struct fuse_file_info *fi) { + off_t res; + (void)path; + + res = lseek(fi->fh, off, whence); + if (res == -1) + return -errno; + + return res; +} + +static const struct fuse_operations xmp_oper = { + .init = xmp_init, + .getattr = xmp_getattr, + .access = xmp_access, + .readlink = xmp_readlink, + .opendir = xmp_opendir, + .readdir = xmp_readdir, + .releasedir = xmp_releasedir, + .mknod = xmp_mknod, + .mkdir = xmp_mkdir, + .symlink = xmp_symlink, + .unlink = xmp_unlink, + .rmdir = xmp_rmdir, + .rename = xmp_rename, + .link = xmp_link, + .chmod = xmp_chmod, + .chown = xmp_chown, + .truncate = xmp_truncate, +#ifdef HAVE_UTIMENSAT + .utimens = xmp_utimens, +#endif + .create = xmp_create, + .open = xmp_open, + .read = xmp_read, + .read_buf = xmp_read_buf, + .write = xmp_write, + .write_buf = xmp_write_buf, + .statfs = xmp_statfs, + .flush = xmp_flush, + .release = xmp_release, + .fsync = xmp_fsync, +#ifdef HAVE_POSIX_FALLOCATE + .fallocate = xmp_fallocate, +#endif +#ifdef HAVE_SETXATTR + .setxattr = xmp_setxattr, + .getxattr = xmp_getxattr, + .listxattr = xmp_listxattr, + .removexattr = xmp_removexattr, +#endif +#ifdef HAVE_LIBULOCKMGR + .lock = xmp_lock, +#endif + .flock = xmp_flock, +#ifdef HAVE_COPY_FILE_RANGE + .copy_file_range = xmp_copy_file_range, +#endif + .lseek = xmp_lseek, +}; + +int main(int argc, char *argv[]) { + umask(0); + + // FUSE won't tell us the mountpoint on it's own, so we need to extract it + // ourselves. + mountpoint = realpath(argv[argc - 1], NULL); int ret = source_init(mountpoint); if (ret != 0) { @@ -27,70 +655,5 @@ static void *icfs_init(struct fuse_conn_info *conn, struct fuse_config *cfg) { exit(EXIT_FAILURE); } - return NULL; -} - -static int icfs_getattr(const char *path, struct stat *stbuf, - struct fuse_file_info *fi) { - (void)stbuf; - (void)fi; - - int statret = source_stat(path, stbuf); - if (statret != 0) - perror("Stat failed."); - - return statret; -} - -static int icfs_opendir(const char *path, struct fuse_file_info *fi) { - DIR *dir_stream = source_opendir(path); - - if (dir_stream == NULL) { - perror("Opendir failed."); - return 1; - } - - fi->fh = (intptr_t)dir_stream; - - return 0; -} - -static int icfs_readdir(const char *path, void *buf, fuse_fill_dir_t filler, - off_t offset, struct fuse_file_info *fi, - enum fuse_readdir_flags flags) { - DIR *dir_stream = (DIR *)fi->fh; - struct dirent *dir_entry = source_readdir(dir_stream); - if (dir_entry == NULL) { - perror("Readdir failed."); - return 1; - } - - do { - int filler_ret = filler(buf, dir_entry->d_name, NULL, 0, 0); - if (filler_ret != 0) { - return -ENOMEM; - } - } while ((dir_entry = source_readdir(dir_stream)) != NULL); - - return 0; -} - -static const struct fuse_operations icfs_oper = { - .init = icfs_init, - .getattr = icfs_getattr, - .opendir = icfs_opendir, - .readdir = icfs_readdir, -}; - -int main(int argc, char *argv[]) { - - int ret; - - // FUSE won't tell us the mountpoint on it's own, so we need to extract it - // ourselves. - mountpoint = realpath(argv[argc - 1], NULL); - - ret = fuse_main(argc, argv, &icfs_oper, NULL); - - return ret; + return fuse_main(argc, argv, &xmp_oper, NULL); } diff --git a/sources/sourcefs.c b/sources/sourcefs.c index 00f6a68..e83a26f 100644 --- a/sources/sourcefs.c +++ b/sources/sourcefs.c @@ -2,15 +2,24 @@ #include "sourcefs.h" #include +#include #include #include +#include #include +#include static struct source_files_handle { int root_fd; } handle; -void source_fname_translate(const char *filename) {} +const char *source_fname_translate(const char *filename) { + if (strcmp("/", filename) == 0) { + return "."; + } else { + return filename + 1; + } +} int source_init(const char *root_path) { int root_fd = open(root_path, O_PATH); @@ -24,17 +33,43 @@ int source_init(const char *root_path) { return 0; } -// Currently this literally is a fstatat wrapper. -int source_stat(const char *restrict pathname, struct stat *restrict statbuf) { - int statret = fstatat(handle.root_fd, pathname, statbuf, 0); - return statret; +int source_mkdir(const char *filename, mode_t mode) { + const char *relative_filename = source_fname_translate(filename); + return mkdirat(handle.root_fd, relative_filename, mode); +} + +int source_unlink(const char *filename) { + const char *relative_filename = source_fname_translate(filename); + return unlinkat(handle.root_fd, relative_filename, 0); +} + +int source_stat(const char *restrict filename, struct stat *restrict statbuf) { + const char *relative_filename = source_fname_translate(filename); + return fstatat(handle.root_fd, relative_filename, statbuf, 0); +} + +int source_rmdir(const char *filename) { + const char *relative_filename = source_fname_translate(filename); + return unlinkat(handle.root_fd, relative_filename, AT_REMOVEDIR); +} + +int source_symlink(const char *target, const char *linkpath) { + const char *relative_linkpath = source_fname_translate(linkpath); + return symlinkat(target, handle.root_fd, relative_linkpath); } DIR *source_opendir(const char *filename) { - int fd = openat(handle.root_fd, filename, NULL); + const char *relative_filename = source_fname_translate(filename); + int fd = openat(handle.root_fd, relative_filename, NULL); + if (fd < 0) { + perror("Openat failed"); + return NULL; + } DIR *dir_pointer = fdopendir(fd); return dir_pointer; } -// Just regular readdir in disguise. -struct dirent *source_readdir(DIR *dirp) { return readdir(dirp); } +int source_rename(const char *oldpath, const char *newpath) { + printf("{\"%s\", \"%s\"}\n", oldpath, newpath); + return -1; +} diff --git a/sources/sourcefs.h b/sources/sourcefs.h index 1a017ba..4bb633a 100644 --- a/sources/sourcefs.h +++ b/sources/sourcefs.h @@ -14,25 +14,23 @@ */ int source_init(const char *root_path); -/** - * `stat()`, but for source files. - * - * @see `stat()` - */ -int source_stat(const char *restrict pathname, struct stat *restrict statbuf); +/* All of the functions below are designed to behave exactly as their non-source + * counterparts. */ + +int source_stat(const char *restrict filename, struct stat *restrict statbuf); -/** - * `readdir()`, but for source files. - * - * @see `readdir(3)` - */ struct dirent *source_readdir(DIR *dirp); -/** - * `opendir()`, but for source files. - * - * @see `opendir()` - */ DIR *source_opendir(const char *filename); +int source_unlink(const char *filename); + +int source_mkdir(const char *filename, mode_t mode); + +int source_rmdir(const char *filename); + +int source_symlink(const char *target, const char *linkpath); + +int source_rename(const char *oldpath, const char *newpath); + #endif // !SOURCEFS_H