Loading include/client/fuse/fuse_client.hpp +25 −64 Original line number Diff line number Diff line Loading @@ -97,6 +97,31 @@ struct __dirstream { #include <client/preload_context.hpp> #include <client/user_functions.hpp> // TODO do we really need the stat here? no i dont think so struct Inode { std::string path; struct stat st; uint64_t lookup_count; }; enum { CACHE_NEVER, CACHE_NORMAL, CACHE_ALWAYS, }; struct u_data { pthread_mutex_t mutex; int debug; int writeback; int flock; int xattr; char* source; double timeout; int cache; int timeout_set; }; struct GkfsDir { // Hypothetical structure that might be used if DIR is cast int fd; long int tell_pos; // for telldir/seekdir Loading @@ -104,68 +129,4 @@ struct GkfsDir { // Hypothetical structure that might be used if DIR is cast // other members libc DIR might have }; /* * Creates files on the underlying file system in response to a FUSE_MKNOD * operation */ static inline int mknod_wrapper(int dirfd, const char* path, const char* link, int mode, dev_t rdev) { fuse_log(FUSE_LOG_DEBUG, "mknod_wrapper \n"); int res = -1; if(S_ISREG(mode)) { // res = lol_openat(dirfd, path, mode, O_CREAT | O_EXCL | O_WRONLY); fuse_log(FUSE_LOG_DEBUG, "lol_openat internal %s\n", res); if(res >= 0) res = gkfs::syscall::gkfs_close(res); } else if(S_ISDIR(mode)) { // GKFS_PATH_OPERATION(create, dirfd, path, mode | S_IFDIR) // res = gkfs::syscall::gkfs_create(resolved, mode | S_IFDIR); // res = mkdirat(dirfd, path, mode); } else if(S_ISLNK(mode) && link != NULL) { fuse_log(FUSE_LOG_ERR, "fifo in mknod_wrapper not supported\n"); errno = ENOTSUP; return -1; // res = symlinkat(link, dirfd, path); } else if(S_ISFIFO(mode)) { fuse_log(FUSE_LOG_ERR, "fifo in mknod_wrapper not supported\n"); errno = ENOTSUP; return -1; // res = mkfifoat(dirfd, path, mode); #ifdef __FreeBSD__ } else if(S_ISSOCK(mode)) { struct sockaddr_un su; int fd; if(strlen(path) >= sizeof(su.sun_path)) { errno = ENAMETOOLONG; return -1; } fd = socket(AF_UNIX, SOCK_STREAM, 0); if(fd >= 0) { /* * We must bind the socket to the underlying file * system to create the socket file, even though * we'll never listen on this socket. */ su.sun_family = AF_UNIX; strncpy(su.sun_path, path, sizeof(su.sun_path)); res = bindat(dirfd, fd, (struct sockaddr*) &su, sizeof(su)); if(res == 0) close(fd); } else { res = -1; } #endif } else { fuse_log(FUSE_LOG_ERR, "mknodat in mknod_wrapper not supported\n"); errno = ENOTSUP; return -1; // res = mknodat(dirfd, path, mode, rdev); } return res; } #endif // GKFS_CLIENT_FUSE_CONTEXT_HPP src/client/fuse/fuse_client.cpp +243 −264 Original line number Diff line number Diff line Loading @@ -39,47 +39,44 @@ #include <client/fuse/fuse_client.hpp> struct lo_inode { struct lo_inode* next; /* protected by lo->mutex */ struct lo_inode* prev; /* protected by lo->mutex */ int fd; ino_t ino; dev_t dev; uint64_t refcount; /* protected by lo->mutex */ }; enum { CACHE_NEVER, CACHE_NORMAL, CACHE_ALWAYS, }; struct lo_data { pthread_mutex_t mutex; int debug; int writeback; int flock; int xattr; char* source; double timeout; int cache; int timeout_set; struct lo_inode root; /* protected by lo->mutex */ }; static struct fuse_lowlevel_ops ll_ops; static std::mutex ino_mutex; static std::unordered_map<fuse_ino_t, Inode> ino_map; static std::unordered_map<std::string, fuse_ino_t> path_map; static fuse_ino_t next_ino = 2; // reserve 1 for root static fuse_ino_t alloc_inode(const std::string& path) { std::lock_guard<std::mutex> lk(ino_mutex); fuse_ino_t ino = next_ino++; ino_map[ino] = {path, {}, 1}; return ino; } static Inode* get_inode(fuse_ino_t ino) { std::lock_guard<std::mutex> lk(ino_mutex); auto it = ino_map.find(ino); return it != ino_map.end() ? &it->second : nullptr; } static struct u_data* udata(fuse_req_t req) { return (struct u_data*) fuse_req_userdata(req); } static const struct fuse_opt lo_opts[] = { {"writeback", offsetof(struct lo_data, writeback), 1}, {"no_writeback", offsetof(struct lo_data, writeback), 0}, {"source=%s", offsetof(struct lo_data, source), 0}, {"flock", offsetof(struct lo_data, flock), 1}, {"no_flock", offsetof(struct lo_data, flock), 0}, {"xattr", offsetof(struct lo_data, xattr), 1}, {"no_xattr", offsetof(struct lo_data, xattr), 0}, {"timeout=%lf", offsetof(struct lo_data, timeout), 0}, {"timeout=", offsetof(struct lo_data, timeout_set), 1}, {"cache=never", offsetof(struct lo_data, cache), CACHE_NEVER}, {"cache=auto", offsetof(struct lo_data, cache), CACHE_NORMAL}, {"cache=always", offsetof(struct lo_data, cache), CACHE_ALWAYS}, {"writeback", offsetof(struct u_data, writeback), 1}, {"no_writeback", offsetof(struct u_data, writeback), 0}, {"flock", offsetof(struct u_data, flock), 1}, {"no_flock", offsetof(struct u_data, flock), 0}, {"xattr", offsetof(struct u_data, xattr), 1}, {"no_xattr", offsetof(struct u_data, xattr), 0}, {"timeout=%lf", offsetof(struct u_data, timeout), 0}, {"timeout=", offsetof(struct u_data, timeout_set), 1}, {"cache=never", offsetof(struct u_data, cache), CACHE_NEVER}, {"cache=auto", offsetof(struct u_data, cache), CACHE_NORMAL}, {"cache=always", offsetof(struct u_data, cache), CACHE_ALWAYS}, FUSE_OPT_END}; Loading @@ -99,85 +96,59 @@ passthrough_ll_help(void) { " -o cache=always Cache always\n"); } static struct lo_data* lo_data(fuse_req_t req) { return (struct lo_data*) fuse_req_userdata(req); } static struct lo_inode* lo_inode(fuse_req_t req, fuse_ino_t ino) { if(ino == FUSE_ROOT_ID) return &lo_data(req)->root; else return (struct lo_inode*) (uintptr_t) ino; } static int lo_fd(fuse_req_t req, fuse_ino_t ino) { return lo_inode(req, ino)->fd; } static bool lo_debug(fuse_req_t req) { return lo_data(req)->debug != 0; } static void lo_init(void* userdata, struct fuse_conn_info* conn) { // TODO init gkfs struct lo_data* lo = (struct lo_data*) userdata; bool has_flag; if(lo->writeback) { has_flag = fuse_set_feature_flag(conn, FUSE_CAP_WRITEBACK_CACHE); if(lo->debug && has_flag) fuse_log(FUSE_LOG_DEBUG, "lo_init: activating writeback\n"); } if(lo->flock && conn->capable & FUSE_CAP_FLOCK_LOCKS) { has_flag = fuse_set_feature_flag(conn, FUSE_CAP_FLOCK_LOCKS); if(lo->debug && has_flag) fuse_log(FUSE_LOG_DEBUG, "lo_init: activating flock locks\n"); } init_handler(void* userdata, struct fuse_conn_info* conn) { fuse_log(FUSE_LOG_DEBUG, "init handler \n"); // struct u_data* lo = (struct u_data*) userdata; // bool has_flag; // TODO check other capabilities e.g. FUSE_CAP_READDIRPLUS // if(lo->writeback) { // has_flag = fuse_set_feature_flag(conn, FUSE_CAP_WRITEBACK_CACHE); // if(lo->debug && has_flag) // fuse_log(FUSE_LOG_DEBUG, "init_handler: activating writeback\n"); // } // if(lo->flock && conn->capable & FUSE_CAP_FLOCK_LOCKS) { // has_flag = fuse_set_feature_flag(conn, FUSE_CAP_FLOCK_LOCKS); // if(lo->debug && has_flag) // fuse_log(FUSE_LOG_DEBUG, "init_handler: activating flock // locks\n"); // } /* Disable the receiving and processing of FUSE_INTERRUPT requests */ conn->no_interrupt = 1; // conn->no_interrupt = 1; } // Simplified inode structure struct Inode { std::string path; struct stat st; uint64_t lookup_count; }; static std::mutex ino_mutex; static std::unordered_map<fuse_ino_t, Inode> ino_map; static fuse_ino_t next_ino = 2; // reserve 1 for root static fuse_ino_t alloc_inode(const std::string& path) { std::lock_guard<std::mutex> lk(ino_mutex); fuse_ino_t ino = next_ino++; ino_map[ino] = {path, {}, 1}; return ino; } static Inode* get_inode(fuse_ino_t ino) { std::lock_guard<std::mutex> lk(ino_mutex); auto it = ino_map.find(ino); return it != ino_map.end() ? &it->second : nullptr; static void destroy_handler(void* userdata) { fuse_log(FUSE_LOG_DEBUG, "destroy handler \n"); // userdata is GekkoFuse* if passed } static void lookup_handler(fuse_req_t req, fuse_ino_t parent, const char* name) { fuse_log(FUSE_LOG_DEBUG, "lookup handler ino %u\n", parent); auto* ud = udata(req); auto* parent_inode = get_inode(parent); if(!parent_inode) { fuse_log(FUSE_LOG_DEBUG, "this error 1 \n", parent); fuse_reply_err(req, ENOENT); return; } std::string child = parent_inode->path + name; fuse_log(FUSE_LOG_DEBUG, "lookup %s\n", child.c_str()); // See if we already have this path auto it = path_map.find(child); fuse_ino_t ino; if(it != path_map.end()) { ino = it->second; ino_map[ino].lookup_count++; } else { ino = alloc_inode(child); path_map[child] = ino; } struct stat st; int rc = gkfs::syscall::gkfs_stat(child, &st); if(rc < 0) { Loading @@ -185,19 +156,19 @@ lookup_handler(fuse_req_t req, fuse_ino_t parent, const char* name) { fuse_reply_err(req, ENOENT); return; } fuse_ino_t ino = alloc_inode(child); ino_map[ino].st = st; fuse_entry_param e = {}; e.ino = ino; e.attr = st; e.attr_timeout = 1.0; e.entry_timeout = 1.0; e.attr_timeout = ud->timeout; e.entry_timeout = ud->timeout; fuse_reply_entry(req, &e); } static void getattr_handler(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info* fi) { fuse_log(FUSE_LOG_DEBUG, "getattr handler \n"); auto* ud = udata(req); auto* inode = get_inode(ino); if(!inode) { fuse_reply_err(req, ENOENT); Loading @@ -212,13 +183,14 @@ getattr_handler(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info* fi) { return; } inode->st = st; fuse_reply_attr(req, &st, 1.0); fuse_reply_attr(req, &st, ud->timeout); } static void setattr_handler(fuse_req_t req, fuse_ino_t ino, struct stat* attr, int to_set, struct fuse_file_info* fi) { fuse_log(FUSE_LOG_DEBUG, "setattr handler ino %u\n", ino); auto* ud = udata(req); auto* inode = get_inode(ino); if(!inode) { fuse_reply_err(req, ENOENT); Loading @@ -229,7 +201,8 @@ setattr_handler(fuse_req_t req, fuse_ino_t ino, struct stat* attr, int to_set, off_t new_size = attr->st_size; int res = gkfs::syscall::gkfs_truncate(inode->path, new_size); if(res < 0) { fuse_log(FUSE_LOG_DEBUG, "setattr truncate failed on %s\n", inode->path.c_str()); fuse_log(FUSE_LOG_DEBUG, "setattr truncate failed on %s\n", inode->path.c_str()); fuse_reply_err(req, EIO); return; } Loading @@ -244,7 +217,7 @@ setattr_handler(fuse_req_t req, fuse_ino_t ino, struct stat* attr, int to_set, // TODO because we cannot save the attributes in gekko, we just return the // buffered results of stat fuse_reply_attr(req, &inode->st, 1.0); fuse_reply_attr(req, &inode->st, ud->timeout); return; } Loading Loading @@ -320,6 +293,7 @@ static void create_handler(fuse_req_t req, fuse_ino_t parent, const char* name, mode_t mode, struct fuse_file_info* fi) { fuse_log(FUSE_LOG_DEBUG, "create handler \n"); auto* ud = udata(req); auto* parent_inode = get_inode(parent); if(!parent_inode) { fuse_reply_err(req, ENOENT); Loading Loading @@ -352,8 +326,8 @@ create_handler(fuse_req_t req, fuse_ino_t parent, const char* name, mode_t mode, fuse_entry_param e = {}; e.ino = ino; e.attr = st; e.attr_timeout = 1.0; e.entry_timeout = 1.0; e.attr_timeout = ud->timeout; e.entry_timeout = ud->timeout; fuse_reply_create(req, &e, fi); } Loading Loading @@ -429,7 +403,7 @@ readdir_handler(fuse_req_t req, fuse_ino_t ino, size_t size, off_t off, } size_t bytes_filled = 0; off_t pos = off; size_t pos = off; while(pos < open_dir->size()) { auto de = open_dir->getdent(pos); Loading Loading @@ -458,23 +432,9 @@ readdir_handler(fuse_req_t req, fuse_ino_t ino, size_t size, off_t off, free(buf); } static void init_handler(void* userdata, struct fuse_conn_info* conn) { fuse_log(FUSE_LOG_DEBUG, "init handler \n"); // userdata is GekkoFuse* if passed // optional: adjust conn->max_write, enable writeback etc. } static void destroy_handler(void* userdata) { fuse_log(FUSE_LOG_DEBUG, "destroy handler \n"); // userdata is GekkoFuse* if passed // optional: adjust conn->max_write, enable writeback etc. } /// releases file descriptor, not connected to lookup_count static void release_handler(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info* fi) { // TODO decrease inode count fuse_log(FUSE_LOG_DEBUG, "release handler \n"); auto* inode = get_inode(ino); if(!inode) { Loading @@ -492,11 +452,31 @@ release_handler(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info* fi) { fuse_reply_err(req, 0); } /// decrement lookup count static void forget_handler(fuse_req_t req, fuse_ino_t ino, uint64_t nlookup) { // TODO remove inode at some point if count == 0 fuse_log(FUSE_LOG_DEBUG, "forget handler \n"); fuse_reply_err(req, 0); auto it = ino_map.find(ino); if(it == ino_map.end()) { fuse_reply_none(req); return; } Inode& inode = it->second; if(inode.lookup_count > nlookup) inode.lookup_count -= nlookup; else inode.lookup_count = 0; if(inode.lookup_count == 0) { // && inode.open_count == 0 path_map.erase(inode.path); ino_map.erase(it); fuse_log(FUSE_LOG_DEBUG, "reached lookup_count 0 \n"); } fuse_reply_none(req); // fuse_reply_err(req, 0); } static void Loading @@ -518,77 +498,8 @@ flush_handler(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info* fi) { fuse_reply_err(req, 0); } static const struct fuse_lowlevel_ops lo_oper = { .init = init_handler, .destroy = destroy_handler, .lookup = lookup_handler, .forget = forget_handler, .getattr = getattr_handler, .setattr = setattr_handler, //.readlink = lo_readlink, //.mknod = lo_mknod, //.mkdir = lo_mkdir, //.unlink = lo_unlink, //.rmdir = lo_rmdir, //.symlink = lo_symlink, //.rename = lo_rename, //.link = lo_link, .open = open_handler, .read = read_handler, .write = write_handler, .flush = flush_handler, .release = release_handler, //.fsync = lo_fsync, .opendir = opendir_handler, .readdir = readdir_handler, //.releasedir = lo_releasedir, //.fsyncdir = lo_fsyncdir, //.statfs = lo_statfs, //.setxattr = lo_setxattr, //.getxattr = lo_getxattr, //.listxattr = lo_listxattr, //.removexattr = lo_removexattr, // access .create = create_handler, // getlk // setlk // bmap // ioctl //.write_buf = lo_write_buf, // poll // retrive_reply //.forget_multi = lo_forget_multi, //.flock = lo_flock, //.fallocate = lo_fallocate, //.readdirplus = lo_readdirplus, #ifdef HAVE_COPY_FILE_RANGE //.copy_file_range = lo_copy_file_range, #endif //.lseek = lo_lseek, //.tmpfile = lo_tmpfile, #ifdef HAVE_STATX //.statx = lo_statx, #endif }; int main(int argc, char* argv[]) { struct fuse_args args = FUSE_ARGS_INIT(argc, argv); struct fuse_session* se; struct fuse_cmdline_opts opts; struct fuse_loop_config* config; struct lo_data lo = {.debug = 0, .writeback = 0}; int ret = -1; /* Don't mask creation mode, kernel already did that */ umask(0); // TODO do we need this and why? pthread_mutex_init(&lo.mutex, NULL); lo.root.next = lo.root.prev = &lo.root; lo.root.fd = -1; lo.cache = CACHE_NORMAL; // init gekkofs static void init_gekkofs() { // TODO how to handle mount point int res = gkfs_init(); if(res != 0) { Loading @@ -612,7 +523,107 @@ main(int argc, char* argv[]) { ino_map[FUSE_ROOT_ID] = {root_path, {}, 1}; ino_map[FUSE_ROOT_ID].st = st; std::cout << "root node allocated" << std::endl; } static void init_ll_ops(fuse_lowlevel_ops* ops) { // file ops->getattr = getattr_handler; ops->setattr = setattr_handler; ops->open = open_handler; ops->create = create_handler; // ops->unlink ops->forget = forget_handler; // ops->forget_multi // ops->readlink // ops->mknod // ops->symlink // ops->rename // ops->link ops->flush = flush_handler; ops->release = release_handler; // ops->fsync // ops->write_buf // xattr // ops->setxattr // ops->getxattr // ops->listxattr // ops->removexattr // directory ops->lookup = lookup_handler; // ops->mkdir // ops->rmdir ops->readdir = readdir_handler; ops->opendir = opendir_handler; // ops->releasedir // ops->fsyncdir = nullptr; // ops->readdirplus // I/O ops->write = write_handler; ops->read = read_handler; // permission // ops->access // misc ops->init = init_handler; ops->destroy = destroy_handler; ops->statfs = nullptr; // ops->flock // ops->getlk // ops->setlk // ops->bmap // ops->ioctl // ops->poll // ops->retrive_reply // ops->fallocate // ops->copy_file_range // ops->lseek // ops->tmpfile // ops->statx } void err_cleanup1(fuse_cmdline_opts opts, fuse_args& args) { free(opts.mountpoint); fuse_opt_free_args(&args); std::cout << "# Resources released" << std::endl; } void err_cleanup2(fuse_session& se) { fuse_session_destroy(&se); std::cout << "# Fuse session destroyed" << std::endl; } void err_cleanup3(fuse_session& se) { fuse_remove_signal_handlers(&se); std::cout << "# Signal handlers removed" << std::endl; } int main(int argc, char* argv[]) { init_ll_ops(&ll_ops); struct fuse_args args = FUSE_ARGS_INIT(argc, argv); struct fuse_session* se; struct fuse_cmdline_opts opts; struct fuse_loop_config* config; struct u_data ud{}; ud.debug = 0; ud.writeback = 0; int ret = -1; /* Don't mask creation mode, kernel already did that */ umask(0); // TODO do we need this and why? pthread_mutex_init(&ud.mutex, NULL); ud.cache = CACHE_NORMAL; if(fuse_parse_cmdline(&args, &opts) != 0) return 1; Loading @@ -621,86 +632,69 @@ main(int argc, char* argv[]) { fuse_cmdline_help(); fuse_lowlevel_help(); passthrough_ll_help(); ret = 0; goto err_out1; err_cleanup1(opts, args); return 0; } else if(opts.show_version) { printf("FUSE library version %s\n", fuse_pkgversion()); fuse_lowlevel_version(); ret = 0; goto err_out1; err_cleanup1(opts, args); return 0; } if(opts.mountpoint == NULL) { printf("usage: %s [options] <mountpoint>\n", argv[0]); printf(" %s --help\n", argv[0]); ret = 1; goto err_out1; err_cleanup1(opts, args); return 0; } if(fuse_opt_parse(&args, &lo, lo_opts, NULL) == -1) if(fuse_opt_parse(&args, &ud, lo_opts, NULL) == -1) return 1; lo.debug = opts.debug; lo.root.refcount = 2; if(lo.source) { struct stat stat; int res; res = lstat(lo.source, &stat); if(res == -1) { fuse_log(FUSE_LOG_ERR, "failed to stat source (\"%s\"): %m\n", lo.source); exit(1); } if(!S_ISDIR(stat.st_mode)) { fuse_log(FUSE_LOG_ERR, "source is not a directory\n"); exit(1); } } else { lo.source = strdup("/"); if(!lo.source) { fuse_log(FUSE_LOG_ERR, "fuse: memory allocation failed\n"); exit(1); } } if(!lo.timeout_set) { switch(lo.cache) { ud.debug = opts.debug; if(!ud.timeout_set) { switch(ud.cache) { case CACHE_NEVER: lo.timeout = 0.0; ud.timeout = 0.0; break; case CACHE_NORMAL: lo.timeout = 1.0; ud.timeout = 1.0; break; case CACHE_ALWAYS: lo.timeout = 86400.0; ud.timeout = 86400.0; break; } } else if(lo.timeout < 0) { fuse_log(FUSE_LOG_ERR, "timeout is negative (%lf)\n", lo.timeout); } else if(ud.timeout < 0) { fuse_log(FUSE_LOG_ERR, "timeout is negative (%lf)\n", ud.timeout); exit(1); } // TODO do we still want this? what do we want? fuse_log(FUSE_LOG_DEBUG, "hier 1\n"); lo.root.fd = gkfs::syscall::gkfs_open(lo.source, 755, O_PATH); if(lo.root.fd == -1) { fuse_log(FUSE_LOG_ERR, "open(\"%s\", O_PATH): %m\n", lo.source); exit(1); } fuse_log(FUSE_LOG_DEBUG, "hier 2\n"); se = fuse_session_new(&args, &lo_oper, sizeof(lo_oper), &lo); if(se == NULL) goto err_out1; init_gekkofs(); if(fuse_set_signal_handlers(se) != 0) goto err_out2; if(fuse_session_mount(se, opts.mountpoint) != 0) goto err_out3; se = fuse_session_new(&args, &ll_ops, sizeof(ll_ops), &ud); if(se == nullptr) { err_cleanup1(opts, args); return 0; } if(fuse_set_signal_handlers(se) != 0) { err_cleanup2(*se); err_cleanup1(opts, args); return 0; } if(fuse_session_mount(se, opts.mountpoint) != 0) { err_cleanup3(*se); err_cleanup2(*se); err_cleanup1(opts, args); return 0; } fuse_daemonize(opts.foreground); Loading @@ -724,20 +718,5 @@ main(int argc, char* argv[]) { } fuse_session_unmount(se); err_out3: fuse_log(FUSE_LOG_DEBUG, "hier 3\n"); fuse_remove_signal_handlers(se); err_out2: fuse_log(FUSE_LOG_DEBUG, "hier 4\n"); fuse_session_destroy(se); err_out1: fuse_log(FUSE_LOG_DEBUG, "hier 5\n"); free(opts.mountpoint); fuse_opt_free_args(&args); if(lo.root.fd >= 0) close(lo.root.fd); free(lo.source); return ret ? 1 : 0; } tests/integration/fuse/test_basic_operations.py +3 −0 Original line number Diff line number Diff line Loading @@ -49,3 +49,6 @@ def test_read(gkfs_daemon, fuse_client): assert sh.ls(fuse_client.mountdir) == "file\n" assert sh.cat(file) == "baum\n" sh.touch(str(file2)) assert sh.wc("-c", str(file2)) == "0 " + str(file2) + "\n" sh.truncate("-s", "20", str(file2)) assert sh.wc("-c", str(file2)) == "20 " + str(file2) + "\n" Loading
include/client/fuse/fuse_client.hpp +25 −64 Original line number Diff line number Diff line Loading @@ -97,6 +97,31 @@ struct __dirstream { #include <client/preload_context.hpp> #include <client/user_functions.hpp> // TODO do we really need the stat here? no i dont think so struct Inode { std::string path; struct stat st; uint64_t lookup_count; }; enum { CACHE_NEVER, CACHE_NORMAL, CACHE_ALWAYS, }; struct u_data { pthread_mutex_t mutex; int debug; int writeback; int flock; int xattr; char* source; double timeout; int cache; int timeout_set; }; struct GkfsDir { // Hypothetical structure that might be used if DIR is cast int fd; long int tell_pos; // for telldir/seekdir Loading @@ -104,68 +129,4 @@ struct GkfsDir { // Hypothetical structure that might be used if DIR is cast // other members libc DIR might have }; /* * Creates files on the underlying file system in response to a FUSE_MKNOD * operation */ static inline int mknod_wrapper(int dirfd, const char* path, const char* link, int mode, dev_t rdev) { fuse_log(FUSE_LOG_DEBUG, "mknod_wrapper \n"); int res = -1; if(S_ISREG(mode)) { // res = lol_openat(dirfd, path, mode, O_CREAT | O_EXCL | O_WRONLY); fuse_log(FUSE_LOG_DEBUG, "lol_openat internal %s\n", res); if(res >= 0) res = gkfs::syscall::gkfs_close(res); } else if(S_ISDIR(mode)) { // GKFS_PATH_OPERATION(create, dirfd, path, mode | S_IFDIR) // res = gkfs::syscall::gkfs_create(resolved, mode | S_IFDIR); // res = mkdirat(dirfd, path, mode); } else if(S_ISLNK(mode) && link != NULL) { fuse_log(FUSE_LOG_ERR, "fifo in mknod_wrapper not supported\n"); errno = ENOTSUP; return -1; // res = symlinkat(link, dirfd, path); } else if(S_ISFIFO(mode)) { fuse_log(FUSE_LOG_ERR, "fifo in mknod_wrapper not supported\n"); errno = ENOTSUP; return -1; // res = mkfifoat(dirfd, path, mode); #ifdef __FreeBSD__ } else if(S_ISSOCK(mode)) { struct sockaddr_un su; int fd; if(strlen(path) >= sizeof(su.sun_path)) { errno = ENAMETOOLONG; return -1; } fd = socket(AF_UNIX, SOCK_STREAM, 0); if(fd >= 0) { /* * We must bind the socket to the underlying file * system to create the socket file, even though * we'll never listen on this socket. */ su.sun_family = AF_UNIX; strncpy(su.sun_path, path, sizeof(su.sun_path)); res = bindat(dirfd, fd, (struct sockaddr*) &su, sizeof(su)); if(res == 0) close(fd); } else { res = -1; } #endif } else { fuse_log(FUSE_LOG_ERR, "mknodat in mknod_wrapper not supported\n"); errno = ENOTSUP; return -1; // res = mknodat(dirfd, path, mode, rdev); } return res; } #endif // GKFS_CLIENT_FUSE_CONTEXT_HPP
src/client/fuse/fuse_client.cpp +243 −264 Original line number Diff line number Diff line Loading @@ -39,47 +39,44 @@ #include <client/fuse/fuse_client.hpp> struct lo_inode { struct lo_inode* next; /* protected by lo->mutex */ struct lo_inode* prev; /* protected by lo->mutex */ int fd; ino_t ino; dev_t dev; uint64_t refcount; /* protected by lo->mutex */ }; enum { CACHE_NEVER, CACHE_NORMAL, CACHE_ALWAYS, }; struct lo_data { pthread_mutex_t mutex; int debug; int writeback; int flock; int xattr; char* source; double timeout; int cache; int timeout_set; struct lo_inode root; /* protected by lo->mutex */ }; static struct fuse_lowlevel_ops ll_ops; static std::mutex ino_mutex; static std::unordered_map<fuse_ino_t, Inode> ino_map; static std::unordered_map<std::string, fuse_ino_t> path_map; static fuse_ino_t next_ino = 2; // reserve 1 for root static fuse_ino_t alloc_inode(const std::string& path) { std::lock_guard<std::mutex> lk(ino_mutex); fuse_ino_t ino = next_ino++; ino_map[ino] = {path, {}, 1}; return ino; } static Inode* get_inode(fuse_ino_t ino) { std::lock_guard<std::mutex> lk(ino_mutex); auto it = ino_map.find(ino); return it != ino_map.end() ? &it->second : nullptr; } static struct u_data* udata(fuse_req_t req) { return (struct u_data*) fuse_req_userdata(req); } static const struct fuse_opt lo_opts[] = { {"writeback", offsetof(struct lo_data, writeback), 1}, {"no_writeback", offsetof(struct lo_data, writeback), 0}, {"source=%s", offsetof(struct lo_data, source), 0}, {"flock", offsetof(struct lo_data, flock), 1}, {"no_flock", offsetof(struct lo_data, flock), 0}, {"xattr", offsetof(struct lo_data, xattr), 1}, {"no_xattr", offsetof(struct lo_data, xattr), 0}, {"timeout=%lf", offsetof(struct lo_data, timeout), 0}, {"timeout=", offsetof(struct lo_data, timeout_set), 1}, {"cache=never", offsetof(struct lo_data, cache), CACHE_NEVER}, {"cache=auto", offsetof(struct lo_data, cache), CACHE_NORMAL}, {"cache=always", offsetof(struct lo_data, cache), CACHE_ALWAYS}, {"writeback", offsetof(struct u_data, writeback), 1}, {"no_writeback", offsetof(struct u_data, writeback), 0}, {"flock", offsetof(struct u_data, flock), 1}, {"no_flock", offsetof(struct u_data, flock), 0}, {"xattr", offsetof(struct u_data, xattr), 1}, {"no_xattr", offsetof(struct u_data, xattr), 0}, {"timeout=%lf", offsetof(struct u_data, timeout), 0}, {"timeout=", offsetof(struct u_data, timeout_set), 1}, {"cache=never", offsetof(struct u_data, cache), CACHE_NEVER}, {"cache=auto", offsetof(struct u_data, cache), CACHE_NORMAL}, {"cache=always", offsetof(struct u_data, cache), CACHE_ALWAYS}, FUSE_OPT_END}; Loading @@ -99,85 +96,59 @@ passthrough_ll_help(void) { " -o cache=always Cache always\n"); } static struct lo_data* lo_data(fuse_req_t req) { return (struct lo_data*) fuse_req_userdata(req); } static struct lo_inode* lo_inode(fuse_req_t req, fuse_ino_t ino) { if(ino == FUSE_ROOT_ID) return &lo_data(req)->root; else return (struct lo_inode*) (uintptr_t) ino; } static int lo_fd(fuse_req_t req, fuse_ino_t ino) { return lo_inode(req, ino)->fd; } static bool lo_debug(fuse_req_t req) { return lo_data(req)->debug != 0; } static void lo_init(void* userdata, struct fuse_conn_info* conn) { // TODO init gkfs struct lo_data* lo = (struct lo_data*) userdata; bool has_flag; if(lo->writeback) { has_flag = fuse_set_feature_flag(conn, FUSE_CAP_WRITEBACK_CACHE); if(lo->debug && has_flag) fuse_log(FUSE_LOG_DEBUG, "lo_init: activating writeback\n"); } if(lo->flock && conn->capable & FUSE_CAP_FLOCK_LOCKS) { has_flag = fuse_set_feature_flag(conn, FUSE_CAP_FLOCK_LOCKS); if(lo->debug && has_flag) fuse_log(FUSE_LOG_DEBUG, "lo_init: activating flock locks\n"); } init_handler(void* userdata, struct fuse_conn_info* conn) { fuse_log(FUSE_LOG_DEBUG, "init handler \n"); // struct u_data* lo = (struct u_data*) userdata; // bool has_flag; // TODO check other capabilities e.g. FUSE_CAP_READDIRPLUS // if(lo->writeback) { // has_flag = fuse_set_feature_flag(conn, FUSE_CAP_WRITEBACK_CACHE); // if(lo->debug && has_flag) // fuse_log(FUSE_LOG_DEBUG, "init_handler: activating writeback\n"); // } // if(lo->flock && conn->capable & FUSE_CAP_FLOCK_LOCKS) { // has_flag = fuse_set_feature_flag(conn, FUSE_CAP_FLOCK_LOCKS); // if(lo->debug && has_flag) // fuse_log(FUSE_LOG_DEBUG, "init_handler: activating flock // locks\n"); // } /* Disable the receiving and processing of FUSE_INTERRUPT requests */ conn->no_interrupt = 1; // conn->no_interrupt = 1; } // Simplified inode structure struct Inode { std::string path; struct stat st; uint64_t lookup_count; }; static std::mutex ino_mutex; static std::unordered_map<fuse_ino_t, Inode> ino_map; static fuse_ino_t next_ino = 2; // reserve 1 for root static fuse_ino_t alloc_inode(const std::string& path) { std::lock_guard<std::mutex> lk(ino_mutex); fuse_ino_t ino = next_ino++; ino_map[ino] = {path, {}, 1}; return ino; } static Inode* get_inode(fuse_ino_t ino) { std::lock_guard<std::mutex> lk(ino_mutex); auto it = ino_map.find(ino); return it != ino_map.end() ? &it->second : nullptr; static void destroy_handler(void* userdata) { fuse_log(FUSE_LOG_DEBUG, "destroy handler \n"); // userdata is GekkoFuse* if passed } static void lookup_handler(fuse_req_t req, fuse_ino_t parent, const char* name) { fuse_log(FUSE_LOG_DEBUG, "lookup handler ino %u\n", parent); auto* ud = udata(req); auto* parent_inode = get_inode(parent); if(!parent_inode) { fuse_log(FUSE_LOG_DEBUG, "this error 1 \n", parent); fuse_reply_err(req, ENOENT); return; } std::string child = parent_inode->path + name; fuse_log(FUSE_LOG_DEBUG, "lookup %s\n", child.c_str()); // See if we already have this path auto it = path_map.find(child); fuse_ino_t ino; if(it != path_map.end()) { ino = it->second; ino_map[ino].lookup_count++; } else { ino = alloc_inode(child); path_map[child] = ino; } struct stat st; int rc = gkfs::syscall::gkfs_stat(child, &st); if(rc < 0) { Loading @@ -185,19 +156,19 @@ lookup_handler(fuse_req_t req, fuse_ino_t parent, const char* name) { fuse_reply_err(req, ENOENT); return; } fuse_ino_t ino = alloc_inode(child); ino_map[ino].st = st; fuse_entry_param e = {}; e.ino = ino; e.attr = st; e.attr_timeout = 1.0; e.entry_timeout = 1.0; e.attr_timeout = ud->timeout; e.entry_timeout = ud->timeout; fuse_reply_entry(req, &e); } static void getattr_handler(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info* fi) { fuse_log(FUSE_LOG_DEBUG, "getattr handler \n"); auto* ud = udata(req); auto* inode = get_inode(ino); if(!inode) { fuse_reply_err(req, ENOENT); Loading @@ -212,13 +183,14 @@ getattr_handler(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info* fi) { return; } inode->st = st; fuse_reply_attr(req, &st, 1.0); fuse_reply_attr(req, &st, ud->timeout); } static void setattr_handler(fuse_req_t req, fuse_ino_t ino, struct stat* attr, int to_set, struct fuse_file_info* fi) { fuse_log(FUSE_LOG_DEBUG, "setattr handler ino %u\n", ino); auto* ud = udata(req); auto* inode = get_inode(ino); if(!inode) { fuse_reply_err(req, ENOENT); Loading @@ -229,7 +201,8 @@ setattr_handler(fuse_req_t req, fuse_ino_t ino, struct stat* attr, int to_set, off_t new_size = attr->st_size; int res = gkfs::syscall::gkfs_truncate(inode->path, new_size); if(res < 0) { fuse_log(FUSE_LOG_DEBUG, "setattr truncate failed on %s\n", inode->path.c_str()); fuse_log(FUSE_LOG_DEBUG, "setattr truncate failed on %s\n", inode->path.c_str()); fuse_reply_err(req, EIO); return; } Loading @@ -244,7 +217,7 @@ setattr_handler(fuse_req_t req, fuse_ino_t ino, struct stat* attr, int to_set, // TODO because we cannot save the attributes in gekko, we just return the // buffered results of stat fuse_reply_attr(req, &inode->st, 1.0); fuse_reply_attr(req, &inode->st, ud->timeout); return; } Loading Loading @@ -320,6 +293,7 @@ static void create_handler(fuse_req_t req, fuse_ino_t parent, const char* name, mode_t mode, struct fuse_file_info* fi) { fuse_log(FUSE_LOG_DEBUG, "create handler \n"); auto* ud = udata(req); auto* parent_inode = get_inode(parent); if(!parent_inode) { fuse_reply_err(req, ENOENT); Loading Loading @@ -352,8 +326,8 @@ create_handler(fuse_req_t req, fuse_ino_t parent, const char* name, mode_t mode, fuse_entry_param e = {}; e.ino = ino; e.attr = st; e.attr_timeout = 1.0; e.entry_timeout = 1.0; e.attr_timeout = ud->timeout; e.entry_timeout = ud->timeout; fuse_reply_create(req, &e, fi); } Loading Loading @@ -429,7 +403,7 @@ readdir_handler(fuse_req_t req, fuse_ino_t ino, size_t size, off_t off, } size_t bytes_filled = 0; off_t pos = off; size_t pos = off; while(pos < open_dir->size()) { auto de = open_dir->getdent(pos); Loading Loading @@ -458,23 +432,9 @@ readdir_handler(fuse_req_t req, fuse_ino_t ino, size_t size, off_t off, free(buf); } static void init_handler(void* userdata, struct fuse_conn_info* conn) { fuse_log(FUSE_LOG_DEBUG, "init handler \n"); // userdata is GekkoFuse* if passed // optional: adjust conn->max_write, enable writeback etc. } static void destroy_handler(void* userdata) { fuse_log(FUSE_LOG_DEBUG, "destroy handler \n"); // userdata is GekkoFuse* if passed // optional: adjust conn->max_write, enable writeback etc. } /// releases file descriptor, not connected to lookup_count static void release_handler(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info* fi) { // TODO decrease inode count fuse_log(FUSE_LOG_DEBUG, "release handler \n"); auto* inode = get_inode(ino); if(!inode) { Loading @@ -492,11 +452,31 @@ release_handler(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info* fi) { fuse_reply_err(req, 0); } /// decrement lookup count static void forget_handler(fuse_req_t req, fuse_ino_t ino, uint64_t nlookup) { // TODO remove inode at some point if count == 0 fuse_log(FUSE_LOG_DEBUG, "forget handler \n"); fuse_reply_err(req, 0); auto it = ino_map.find(ino); if(it == ino_map.end()) { fuse_reply_none(req); return; } Inode& inode = it->second; if(inode.lookup_count > nlookup) inode.lookup_count -= nlookup; else inode.lookup_count = 0; if(inode.lookup_count == 0) { // && inode.open_count == 0 path_map.erase(inode.path); ino_map.erase(it); fuse_log(FUSE_LOG_DEBUG, "reached lookup_count 0 \n"); } fuse_reply_none(req); // fuse_reply_err(req, 0); } static void Loading @@ -518,77 +498,8 @@ flush_handler(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info* fi) { fuse_reply_err(req, 0); } static const struct fuse_lowlevel_ops lo_oper = { .init = init_handler, .destroy = destroy_handler, .lookup = lookup_handler, .forget = forget_handler, .getattr = getattr_handler, .setattr = setattr_handler, //.readlink = lo_readlink, //.mknod = lo_mknod, //.mkdir = lo_mkdir, //.unlink = lo_unlink, //.rmdir = lo_rmdir, //.symlink = lo_symlink, //.rename = lo_rename, //.link = lo_link, .open = open_handler, .read = read_handler, .write = write_handler, .flush = flush_handler, .release = release_handler, //.fsync = lo_fsync, .opendir = opendir_handler, .readdir = readdir_handler, //.releasedir = lo_releasedir, //.fsyncdir = lo_fsyncdir, //.statfs = lo_statfs, //.setxattr = lo_setxattr, //.getxattr = lo_getxattr, //.listxattr = lo_listxattr, //.removexattr = lo_removexattr, // access .create = create_handler, // getlk // setlk // bmap // ioctl //.write_buf = lo_write_buf, // poll // retrive_reply //.forget_multi = lo_forget_multi, //.flock = lo_flock, //.fallocate = lo_fallocate, //.readdirplus = lo_readdirplus, #ifdef HAVE_COPY_FILE_RANGE //.copy_file_range = lo_copy_file_range, #endif //.lseek = lo_lseek, //.tmpfile = lo_tmpfile, #ifdef HAVE_STATX //.statx = lo_statx, #endif }; int main(int argc, char* argv[]) { struct fuse_args args = FUSE_ARGS_INIT(argc, argv); struct fuse_session* se; struct fuse_cmdline_opts opts; struct fuse_loop_config* config; struct lo_data lo = {.debug = 0, .writeback = 0}; int ret = -1; /* Don't mask creation mode, kernel already did that */ umask(0); // TODO do we need this and why? pthread_mutex_init(&lo.mutex, NULL); lo.root.next = lo.root.prev = &lo.root; lo.root.fd = -1; lo.cache = CACHE_NORMAL; // init gekkofs static void init_gekkofs() { // TODO how to handle mount point int res = gkfs_init(); if(res != 0) { Loading @@ -612,7 +523,107 @@ main(int argc, char* argv[]) { ino_map[FUSE_ROOT_ID] = {root_path, {}, 1}; ino_map[FUSE_ROOT_ID].st = st; std::cout << "root node allocated" << std::endl; } static void init_ll_ops(fuse_lowlevel_ops* ops) { // file ops->getattr = getattr_handler; ops->setattr = setattr_handler; ops->open = open_handler; ops->create = create_handler; // ops->unlink ops->forget = forget_handler; // ops->forget_multi // ops->readlink // ops->mknod // ops->symlink // ops->rename // ops->link ops->flush = flush_handler; ops->release = release_handler; // ops->fsync // ops->write_buf // xattr // ops->setxattr // ops->getxattr // ops->listxattr // ops->removexattr // directory ops->lookup = lookup_handler; // ops->mkdir // ops->rmdir ops->readdir = readdir_handler; ops->opendir = opendir_handler; // ops->releasedir // ops->fsyncdir = nullptr; // ops->readdirplus // I/O ops->write = write_handler; ops->read = read_handler; // permission // ops->access // misc ops->init = init_handler; ops->destroy = destroy_handler; ops->statfs = nullptr; // ops->flock // ops->getlk // ops->setlk // ops->bmap // ops->ioctl // ops->poll // ops->retrive_reply // ops->fallocate // ops->copy_file_range // ops->lseek // ops->tmpfile // ops->statx } void err_cleanup1(fuse_cmdline_opts opts, fuse_args& args) { free(opts.mountpoint); fuse_opt_free_args(&args); std::cout << "# Resources released" << std::endl; } void err_cleanup2(fuse_session& se) { fuse_session_destroy(&se); std::cout << "# Fuse session destroyed" << std::endl; } void err_cleanup3(fuse_session& se) { fuse_remove_signal_handlers(&se); std::cout << "# Signal handlers removed" << std::endl; } int main(int argc, char* argv[]) { init_ll_ops(&ll_ops); struct fuse_args args = FUSE_ARGS_INIT(argc, argv); struct fuse_session* se; struct fuse_cmdline_opts opts; struct fuse_loop_config* config; struct u_data ud{}; ud.debug = 0; ud.writeback = 0; int ret = -1; /* Don't mask creation mode, kernel already did that */ umask(0); // TODO do we need this and why? pthread_mutex_init(&ud.mutex, NULL); ud.cache = CACHE_NORMAL; if(fuse_parse_cmdline(&args, &opts) != 0) return 1; Loading @@ -621,86 +632,69 @@ main(int argc, char* argv[]) { fuse_cmdline_help(); fuse_lowlevel_help(); passthrough_ll_help(); ret = 0; goto err_out1; err_cleanup1(opts, args); return 0; } else if(opts.show_version) { printf("FUSE library version %s\n", fuse_pkgversion()); fuse_lowlevel_version(); ret = 0; goto err_out1; err_cleanup1(opts, args); return 0; } if(opts.mountpoint == NULL) { printf("usage: %s [options] <mountpoint>\n", argv[0]); printf(" %s --help\n", argv[0]); ret = 1; goto err_out1; err_cleanup1(opts, args); return 0; } if(fuse_opt_parse(&args, &lo, lo_opts, NULL) == -1) if(fuse_opt_parse(&args, &ud, lo_opts, NULL) == -1) return 1; lo.debug = opts.debug; lo.root.refcount = 2; if(lo.source) { struct stat stat; int res; res = lstat(lo.source, &stat); if(res == -1) { fuse_log(FUSE_LOG_ERR, "failed to stat source (\"%s\"): %m\n", lo.source); exit(1); } if(!S_ISDIR(stat.st_mode)) { fuse_log(FUSE_LOG_ERR, "source is not a directory\n"); exit(1); } } else { lo.source = strdup("/"); if(!lo.source) { fuse_log(FUSE_LOG_ERR, "fuse: memory allocation failed\n"); exit(1); } } if(!lo.timeout_set) { switch(lo.cache) { ud.debug = opts.debug; if(!ud.timeout_set) { switch(ud.cache) { case CACHE_NEVER: lo.timeout = 0.0; ud.timeout = 0.0; break; case CACHE_NORMAL: lo.timeout = 1.0; ud.timeout = 1.0; break; case CACHE_ALWAYS: lo.timeout = 86400.0; ud.timeout = 86400.0; break; } } else if(lo.timeout < 0) { fuse_log(FUSE_LOG_ERR, "timeout is negative (%lf)\n", lo.timeout); } else if(ud.timeout < 0) { fuse_log(FUSE_LOG_ERR, "timeout is negative (%lf)\n", ud.timeout); exit(1); } // TODO do we still want this? what do we want? fuse_log(FUSE_LOG_DEBUG, "hier 1\n"); lo.root.fd = gkfs::syscall::gkfs_open(lo.source, 755, O_PATH); if(lo.root.fd == -1) { fuse_log(FUSE_LOG_ERR, "open(\"%s\", O_PATH): %m\n", lo.source); exit(1); } fuse_log(FUSE_LOG_DEBUG, "hier 2\n"); se = fuse_session_new(&args, &lo_oper, sizeof(lo_oper), &lo); if(se == NULL) goto err_out1; init_gekkofs(); if(fuse_set_signal_handlers(se) != 0) goto err_out2; if(fuse_session_mount(se, opts.mountpoint) != 0) goto err_out3; se = fuse_session_new(&args, &ll_ops, sizeof(ll_ops), &ud); if(se == nullptr) { err_cleanup1(opts, args); return 0; } if(fuse_set_signal_handlers(se) != 0) { err_cleanup2(*se); err_cleanup1(opts, args); return 0; } if(fuse_session_mount(se, opts.mountpoint) != 0) { err_cleanup3(*se); err_cleanup2(*se); err_cleanup1(opts, args); return 0; } fuse_daemonize(opts.foreground); Loading @@ -724,20 +718,5 @@ main(int argc, char* argv[]) { } fuse_session_unmount(se); err_out3: fuse_log(FUSE_LOG_DEBUG, "hier 3\n"); fuse_remove_signal_handlers(se); err_out2: fuse_log(FUSE_LOG_DEBUG, "hier 4\n"); fuse_session_destroy(se); err_out1: fuse_log(FUSE_LOG_DEBUG, "hier 5\n"); free(opts.mountpoint); fuse_opt_free_args(&args); if(lo.root.fd >= 0) close(lo.root.fd); free(lo.source); return ret ? 1 : 0; }
tests/integration/fuse/test_basic_operations.py +3 −0 Original line number Diff line number Diff line Loading @@ -49,3 +49,6 @@ def test_read(gkfs_daemon, fuse_client): assert sh.ls(fuse_client.mountdir) == "file\n" assert sh.cat(file) == "baum\n" sh.touch(str(file2)) assert sh.wc("-c", str(file2)) == "0 " + str(file2) + "\n" sh.truncate("-s", "20", str(file2)) assert sh.wc("-c", str(file2)) == "20 " + str(file2) + "\n"