#include "fs.h" #include #include #include #include #include "args.h" uint8 *fsdata; int fslen; void panic(char *fmt, ...) { va_list ap; va_start(ap, fmt); vfprintf(stderr, fmt, ap); fprintf(stderr, "\n"); va_end(ap); exit(1); } void* emalloc(int size) { void *p; p = malloc(size); if(p == nil) panic("error: no memory"); return p; } FILE* mustopen(const char *name, const char *mode) { FILE *f; if(f = fopen(name, mode), f == nil) panic("couldn't open file: %s", name); return f; } typedef struct Options Options; struct Options { int singlethread; int foreground; int debug; int nodefault_subtype; char *device; char *mountpoint; int show_version; int show_help; int clone_fd; unsigned int max_idle_threads; }; static Options options; #define offsetof(type, field) ((long)&((type*)0)->field) #define OPTION(t, p) \ { t, offsetof(Options, p), 1 } static const struct fuse_opt myopts[] = { OPTION("-h", show_help), OPTION("--help", show_help), OPTION("-V", show_version), OPTION("--version", show_version), OPTION("-d", debug), OPTION("debug", debug), OPTION("-d", foreground), OPTION("debug", foreground), FUSE_OPT_KEY("-d", FUSE_OPT_KEY_KEEP), FUSE_OPT_KEY("debug", FUSE_OPT_KEY_KEEP), OPTION("-f", foreground), OPTION("-s", singlethread), OPTION("clone_fd", clone_fd), OPTION("max_idle_threads=%u", max_idle_threads), FUSE_OPT_END }; static int opt_proc(void *data, const char *arg, int key, struct fuse_args *outargs) { (void)data; (void)outargs; switch(key){ case FUSE_OPT_KEY_NONOPT: if(options.device == nil){ options.device = strdup(arg); return 0; } if(options.mountpoint == nil){ options.mountpoint = strdup(arg); return 0; } return -1; default: panic("invalid option"); } return -1; } void usage(void) { fprintf(stderr, " usage: asdf [options] filesystem mountpoint\n" " -h --help print help\n" " -V --version print version\n" " -d -o debug enable debug output (implies -f)\n" " -f foreground operation\n" " -s disable multi-threaded operation\n" " -o clone_fd use separate fuse device fd for each thread\n" " (may improve performance)\n" " -o max_idle_threads the maximum number of idle worker threads\n" " allowed (default: 10)\n"); exit(1); } void dirbuf_add(fuse_req_t req, Dirbuf *b, const char *name, fuse_ino_t ino) { struct stat stbuf; size_t oldsize = b->size; b->size += fuse_add_direntry(req, NULL, 0, name, NULL, 0); b->p = (char *) realloc(b->p, b->size); memset(&stbuf, 0, sizeof(stbuf)); stbuf.st_ino = ino; fuse_add_direntry(req, b->p + oldsize, b->size - oldsize, name, &stbuf, b->size); } #define min(x, y) ((x) < (y) ? (x) : (y)) int reply_buf_limited(fuse_req_t req, const char *buf, size_t bufsize, off_t off, size_t maxsize) { if(off < bufsize) return fuse_reply_buf(req, buf + off, min(bufsize - off, maxsize)); else return fuse_reply_buf(req, NULL, 0); } static void vfs_getattr(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi) { struct stat stbuf; USED(fi); memset(&stbuf, 0, sizeof(stbuf)); if(fs_stat(ino, &stbuf) == -1) fuse_reply_err(req, ENOENT); else fuse_reply_attr(req, &stbuf, 1.0); } static void vfs_setattr(fuse_req_t req, fuse_ino_t ino, struct stat *attr, int to_set, struct fuse_file_info *fi) { int ret = 0; USED(fi); if(to_set & FUSE_SET_ATTR_ATIME) ret = fs_atime(ino, attr->st_atime); if(to_set & FUSE_SET_ATTR_MTIME) ret = fs_mtime(ino, attr->st_mtime); if(to_set & FUSE_SET_ATTR_GID) ret = fs_gid(ino, attr->st_gid); if(to_set & FUSE_SET_ATTR_UID) ret = fs_uid(ino, attr->st_uid); if(ret) fuse_reply_err(req, ret); else vfs_getattr(req, ino, fi); } void vfs_open(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi) { int e; e = fs_open(ino, fi->flags); if(e) fuse_reply_err(req, e); else fuse_reply_open(req, fi); } void vfs_read(fuse_req_t req, fuse_ino_t ino, size_t size, off_t off, struct fuse_file_info *fi) { USED(fi); char *buf; int n; buf = malloc(size); n = fs_read(ino, buf, off, size); reply_buf_limited(req, buf, n, off, size); free(buf); } void vfs_write(fuse_req_t req, fuse_ino_t ino, const char *buf, size_t size, off_t off, struct fuse_file_info *fi) { int n; USED(fi); n = fs_write(ino, (void*)buf, off, size); fuse_reply_write(req, n); } void vfs_readdir(fuse_req_t req, fuse_ino_t ino, size_t size, off_t off, struct fuse_file_info *fi) { struct dirent *dirs, *dp; Dirbuf b; USED(fi); dirs = fs_readdir(ino); if(dirs == nil){ fuse_reply_err(req, ENOTDIR); return; } memset(&b, 0, sizeof(b)); for(dp = dirs; dp->d_ino; dp++) dirbuf_add(req, &b, dp->d_name, dp->d_ino); free(dirs); reply_buf_limited(req, b.p, b.size, off, size); free(b.p); } /* returns static pointer! */ struct dirent* lookup(uint ino, const char *name) { struct dirent *dirs, *dp; static struct dirent de; dirs = fs_readdir(ino); if(dirs == nil) return nil; for(dp = dirs; dp->d_ino; dp++) if(strcmp(dp->d_name, name) == 0){ de = *dp; free(dirs); return &de; } free(dirs); return nil; } void vfs_lookup(fuse_req_t req, fuse_ino_t parent, const char *name) { struct fuse_entry_param e; struct dirent *de; de = lookup(parent, name); if(de == nil){ fuse_reply_err(req, ENOENT); return; } memset(&e, 0, sizeof(e)); e.ino = de->d_ino; e.attr_timeout = 1.0; e.entry_timeout = 1.0; fs_stat(e.ino, &e.attr); /* increment ref count */ fs_iget(e.ino); fuse_reply_entry(req, &e); } void vfs_forget(fuse_req_t req, fuse_ino_t ino, uint64_t nlookup) { Inode *ip; printf("forgetting %d\n", nlookup); ip = fs_iget(ino); fs_iput(ip); while(nlookup--) fs_iput(ip); fuse_reply_none(req); } void vfs_mknod(fuse_req_t req, fuse_ino_t parent, const char *name, mode_t mode, dev_t rdev) { int ret; ret = fs_mknod(parent, name, mode, rdev, nil); if(ret) fuse_reply_err(req, ret); else vfs_lookup(req, parent, name); } void vfs_mkdir(fuse_req_t req, fuse_ino_t parent, const char *name, mode_t mode) { int ret; ret = fs_mkdir(parent, name, mode); if(ret) fuse_reply_err(req, ret); else vfs_lookup(req, parent, name); } void vfs_unlink(fuse_req_t req, fuse_ino_t parent, const char *name) { int ret; ret = fs_unlink(parent, name); fuse_reply_err(req, ret); } int isdirempty(struct dirent *de) { for(; de->d_ino; de++) if(strcmp(de->d_name, ".") != 0 && strcmp(de->d_name, "..") != 0) return 0; return 1; } void vfs_rmdir(fuse_req_t req, fuse_ino_t parent, const char *name) { struct dirent *dir, *de; int ret; int dino; ret = 0; de = lookup(parent, name); /* this shouldn't happen */ if(de == nil) return; dino = de->d_ino; dir = fs_readdir(dino); assert(dir); if(!isdirempty(dir)){ free(dir); fuse_reply_err(req, ENOTEMPTY); return; } printf("dir to delete: %d %s\n", dino, name); for(de = dir; de->d_ino; de++) fs_unlink(dino, de->d_name); free(dir); fs_unlink(parent, name); fuse_reply_err(req, ret); } void vfs_link(fuse_req_t req, fuse_ino_t ino, fuse_ino_t newparent, const char *newname) { int ret; ret = fs_link(ino, newparent, newname); if(ret) fuse_reply_err(req, ret); else vfs_lookup(req, newparent, newname); } static struct fuse_lowlevel_ops hello_ll_oper = { .lookup = vfs_lookup, .getattr = vfs_getattr, .setattr = vfs_setattr, .readdir = vfs_readdir, .open = vfs_open, .read = vfs_read, .write = vfs_write, .mknod = vfs_mknod, .mkdir = vfs_mkdir, .unlink = vfs_unlink, .rmdir = vfs_rmdir, .link = vfs_link, .forget = vfs_forget, // TODO // rename }; void fsload(const char *filename) { FILE *f = mustopen(filename, "rb"); fseek(f, 0, SEEK_END); fslen = ftell(f); fseek(f, 0, SEEK_SET); fsdata = emalloc(fslen); fread(fsdata, 1, fslen, f); fclose(f); } int main(int argc, char *argv[]) { struct fuse_args args = FUSE_ARGS_INIT(argc, argv); struct fuse_session *se; char *m; int ret = -1; if(fuse_opt_parse(&args, &options, myopts, opt_proc) == -1 || options.device == nil || options.mountpoint == nil) usage(); m = options.mountpoint; options.mountpoint = realpath(m, nil); if(options.mountpoint == nil) panic("bad mount point %s", m); printf("device: %s\n", options.device); printf("mountpoint: %s\n", options.mountpoint); if(options.show_help){ usage(); return 0; } if(options.show_version){ printf("FUSE library version %s\n", fuse_pkgversion()); fuse_lowlevel_version(); return 0; } fsload(options.device); fs_init(); dcheck(); se = fuse_session_new(&args, &hello_ll_oper, sizeof(hello_ll_oper), NULL); if(se == NULL) return 1; if(fuse_set_signal_handlers(se) != 0) goto err_out2; if(fuse_session_mount(se, options.mountpoint) != 0) goto err_out3; fuse_daemonize(options.foreground); /* Block until ctrl+c or fusermount -u */ if(options.singlethread) ret = fuse_session_loop(se); else ret = fuse_session_loop_mt(se, options.clone_fd); fuse_session_unmount(se); err_out3: fuse_remove_signal_handlers(se); err_out2: fuse_session_destroy(se); free(options.mountpoint); fuse_opt_free_args(&args); return ret ? 1 : 0; }