/* FUSE: Filesystem in Userspace Copyright (C) 2001-2007 Miklos Szeredi This program can be distributed under the terms of the GNU LGPLv2. See the file COPYING.LIB */ #ifndef _GNU_SOURCE #define _GNU_SOURCE #endif #include "syslog.hpp" #include "crc32b.h" #include "debug.hpp" #include "fatal.hpp" #include "kvec.h" #include "mutex.hpp" #include "node.hpp" #include "xalloc.hpp" #include "fuse_cfg.hpp" #include "fuse_req.hpp" #include "fuse_dirents.hpp" #include "fuse_i.hpp" #include "fuse_kernel.h" #include "fuse_lowlevel.h" #include "fuse_opt.h" #include "fuse_pollhandle.h" #include "fuse_msgbuf.hpp" #include "stat_utils.h" #include "maintenance_thread.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef __GLIBC__ #include #endif #define FUSE_UNKNOWN_INO UINT64_MAX #define OFFSET_MAX 0x7fffffffffffffffLL #define PATH_BUFSIZE_HALF_MAX 0x80000000u #define PATH_BUFSIZE_MAX 0xffffffffu #define INFO_FILE_MAX_SIZE (1024 * 1024) #define NODE_TABLE_MIN_SIZE 8192 #define PARAM(inarg) ((void*)(((char*)(inarg)) + sizeof(*(inarg)))) struct lock_queue_element { struct lock_queue_element *next; pthread_cond_t cond; uint64_t nodeid1; const char *name1; char **path1; node_t **wnode1; uint64_t nodeid2; const char *name2; char **path2; node_t **wnode2; int err; bool done : 1; }; struct node_table { node_t **array; size_t use; size_t size; size_t split; }; struct list_head { struct list_head *next; struct list_head *prev; }; typedef struct nodeid_gen_t nodeid_gen_t; struct nodeid_gen_t { uint64_t nodeid; uint64_t generation; }; struct fuse { struct fuse_session *se; struct node_table name_table; struct node_table id_table; nodeid_gen_t nodeid_gen; unsigned int hidectr; mutex_t lock; fuse_operations ops; struct lock_queue_element *lockq; }; #define TREELOCK_WRITE -1 #define TREELOCK_WAIT_OFFSET INT_MIN struct fuse_dh { mutex_t lock; uint64_t fh; fuse_dirents_t d; }; static struct fuse f = {}; /* Why was the nodeid:generation logic simplified? nodeid is uint64_t: max value of 18446744073709551616 If nodes were created at a rate of 1048576 per second it would take over 500 thousand years to roll over. I'm fine with risking that. */ static uint64_t generate_nodeid(nodeid_gen_t *ng_) { ng_->nodeid++; return ng_->nodeid; } static void* fuse_hdr_arg(const struct fuse_in_header *hdr_) { return (void*)&hdr_[1]; } static size_t id_hash(uint64_t ino) { uint64_t hash = ((uint32_t)ino * 2654435761U) % f.id_table.size; uint64_t oldhash = hash % (f.id_table.size / 2); if(oldhash >= f.id_table.split) return oldhash; else return hash; } static node_t* get_node(const uint64_t nodeid) { size_t hash = id_hash(nodeid); node_t *node; for(node = f.id_table.array[hash]; node != NULL; node = node->id_next) if(node->nodeid == nodeid) return node; return NULL; } static uint32_t stat_crc32b(const struct stat *st_) { uint32_t crc; const auto mtimensec = ST_MTIM_NSEC(st_); const auto ctimensec = ST_CTIM_NSEC(st_); crc = crc32b_start(); /* st_dev is not exposed by FUSE attr replies, but the underlying value is still useful as a proxy for whether the backing stat source changed. */ crc = crc32b_continue(&st_->st_dev,sizeof(st_->st_dev),crc); crc = crc32b_continue(&st_->st_ino,sizeof(st_->st_ino),crc); crc = crc32b_continue(&st_->st_size,sizeof(st_->st_size),crc); crc = crc32b_continue(&st_->st_mtime,sizeof(st_->st_mtime),crc); crc = crc32b_continue(&mtimensec,sizeof(mtimensec),crc); crc = crc32b_continue(&st_->st_ctime,sizeof(st_->st_ctime),crc); crc = crc32b_continue(&ctimensec,sizeof(ctimensec),crc); crc = crc32b_finish(crc); /* 0 is reserved to mean "no valid stat cache fingerprint". */ return (crc ? crc : UINT32_MAX); } static void free_node(node_t *node_) { free(node_->name); node_free(node_); } static void node_table_reduce(struct node_table *t) { size_t newsize = t->size / 2; void *newarray; if(newsize < NODE_TABLE_MIN_SIZE) return; newarray = realloc(t->array,sizeof(node_t*) * newsize); if(newarray == NULL) return; t->array = (node_t**)newarray; t->size = newsize; t->split = t->size / 2; } static void remerge_id() { struct node_table *t = &f.id_table; int iter; if(t->split == 0) node_table_reduce(t); for(iter = 8; t->split > 0 && iter; iter--) { node_t **upper; t->split--; upper = &t->array[t->split + t->size / 2]; if(*upper) { node_t **nodep; for(nodep = &t->array[t->split]; *nodep; nodep = &(*nodep)->id_next); *nodep = *upper; *upper = NULL; break; } } } static void unhash_id(node_t *node) { node_t **nodep = &f.id_table.array[id_hash(node->nodeid)]; for(; *nodep != NULL; nodep = &(*nodep)->id_next) if(*nodep == node) { *nodep = node->id_next; f.id_table.use--; if(f.id_table.use < (f.id_table.size / 4)) remerge_id(); return; } } static int node_table_resize(struct node_table *t) { size_t newsize = t->size * 2; void *newarray; newarray = realloc(t->array,sizeof(node_t*) * newsize); if(newarray == NULL) return -1; t->array = (node_t**)newarray; memset(t->array + t->size,0,t->size * sizeof(node_t*)); t->size = newsize; t->split = 0; return 0; } static void rehash_id() { struct node_table *t = &f.id_table; node_t **nodep; node_t **next; size_t hash; if(t->split == t->size / 2) return; hash = t->split; t->split++; for(nodep = &t->array[hash]; *nodep != NULL; nodep = next) { node_t *node = *nodep; size_t newhash = id_hash(node->nodeid); if(newhash != hash) { next = nodep; *nodep = node->id_next; node->id_next = t->array[newhash]; t->array[newhash] = node; } else { next = &node->id_next; } } if(t->split == t->size / 2) node_table_resize(t); } static void hash_id(node_t *node) { size_t hash; hash = id_hash(node->nodeid); node->id_next = f.id_table.array[hash]; f.id_table.array[hash] = node; f.id_table.use++; if(f.id_table.use >= (f.id_table.size / 2)) rehash_id(); } static size_t name_hash(uint64_t parent, const char *name) { uint64_t hash = parent; uint64_t oldhash; for(; *name; name++) hash = hash * 31 + (unsigned char)*name; hash %= f.name_table.size; oldhash = hash % (f.name_table.size / 2); if(oldhash >= f.name_table.split) return oldhash; else return hash; } static void unref_node(node_t *node); static void remerge_name() { int iter; struct node_table *t = &f.name_table; if(t->split == 0) node_table_reduce(t); for(iter = 8; t->split > 0 && iter; iter--) { node_t **upper; t->split--; upper = &t->array[t->split + t->size / 2]; if(*upper) { node_t **nodep; for(nodep = &t->array[t->split]; *nodep; nodep = &(*nodep)->name_next); *nodep = *upper; *upper = NULL; break; } } } static void unhash_name(node_t *node) { if(not (node && node->name && node->parent)) return; size_t hash = name_hash(node->parent->nodeid,node->name); node_t **nodep = &f.name_table.array[hash]; for(; *nodep != NULL; nodep = &(*nodep)->name_next) { if(*nodep != node) continue; *nodep = node->name_next; node->name_next = NULL; unref_node(node->parent); free(node->name); node->name = NULL; node->parent = NULL; f.name_table.use--; if(f.name_table.use < f.name_table.size / 4) remerge_name(); return; } fatal::abort("mergerfs: unable to unhash node: {}", node->nodeid); } static void rehash_name() { struct node_table *t = &f.name_table; node_t **nodep; node_t **next; size_t hash; if(t->split == t->size / 2) return; hash = t->split; t->split++; for(nodep = &t->array[hash]; *nodep != NULL; nodep = next) { node_t *node = *nodep; if(node->parent == NULL) { next = &node->name_next; continue; } size_t newhash = name_hash(node->parent->nodeid,node->name); if(newhash != hash) { next = nodep; *nodep = node->name_next; node->name_next = t->array[newhash]; t->array[newhash] = node; } else { next = &node->name_next; } } if(t->split == t->size / 2) node_table_resize(t); } static int hash_name(node_t *node, uint64_t parentid, const char *name) { size_t hash; node_t *parent; hash = name_hash(parentid,name); parent = get_node(parentid); if(parent == NULL) return -1; node->name = strdup(name); if(node->name == NULL) return -1; parent->refctr++; node->parent = parent; node->name_next = f.name_table.array[hash]; f.name_table.array[hash] = node; f.name_table.use++; if(f.name_table.use >= f.name_table.size / 2) rehash_name(); return 0; } static inline bool remember_nodes() { return fuse_cfg.remember_nodes; } static void delete_node(node_t *node) { assert(node->treelock == 0); unhash_name(node); unhash_id(node); node_free(node); } static void unref_node(node_t *node) { assert(node->refctr > 0); node->refctr--; if(!node->refctr) delete_node(node); } static uint64_t rand64(void) { uint64_t rv; rv = rand(); rv <<= 32; rv |= rand(); return rv; } static node_t* lookup_node(uint64_t parent, const char *name) { size_t hash; node_t *node; hash = name_hash(parent,name); for(node = f.name_table.array[hash]; node != NULL; node = node->name_next) if(node->parent->nodeid == parent && strcmp(node->name,name) == 0) return node; return NULL; } static void inc_nlookup(node_t *node) { if(!node->nlookup) node->refctr++; node->nlookup++; } static void remember_node(node_t *node) { if(node->remembered) return; inc_nlookup(node); node->remembered = true; } static void unref_node_if_unused(node_t *node) { if((node->nlookup == 0) && (node->open_count == 0) && (node->treelock == 0)) unref_node(node); } static void unremember_node(node_t *node) { if(!node->remembered) return; assert(node->nlookup > 0); node->remembered = false; node->nlookup--; unref_node_if_unused(node); } static node_t* find_node(uint64_t parent, const char *name, bool remember) { node_t *node; mutex_lockguard(f.lock); if(!name) { node = get_node(parent); if(node == NULL) goto out_err; } else { node = lookup_node(parent,name); } if(node == NULL) { node = node_alloc(); if(node == NULL) goto out_err; node->nodeid = generate_nodeid(&f.nodeid_gen); if(remember && remember_nodes()) remember_node(node); if(hash_name(node,parent,name) == -1) { free_node(node); node = NULL; goto out_err; } hash_id(node); } inc_nlookup(node); out_err: return node; } static char* add_name(char **buf, unsigned *bufsize, char *s, const char *name) { size_t len = strlen(name); if(s - len <= *buf) { unsigned pathlen = *bufsize - (s - *buf); unsigned newbufsize = *bufsize; char *newbuf; while(newbufsize < pathlen + len + 1) { if(newbufsize >= PATH_BUFSIZE_HALF_MAX) newbufsize = PATH_BUFSIZE_MAX; else newbufsize *= 2; } newbuf = (char*)realloc(*buf,newbufsize); if(newbuf == NULL) return NULL; *buf = newbuf; s = newbuf + newbufsize - pathlen; memmove(s,newbuf + *bufsize - pathlen,pathlen); *bufsize = newbufsize; } s -= len; strncpy(s,name,len); s--; *s = '/'; return s; } static void unlock_path(uint64_t nodeid, node_t *wnode, node_t *end) { node_t *node; if(wnode) { assert(wnode->treelock == TREELOCK_WRITE); wnode->treelock = 0; /* unlink_node() can drop the retained lookup while the node is locked. */ unref_node_if_unused(wnode); } node = get_node(nodeid); if(node == NULL) return; for(;node != end && node->nodeid != FUSE_ROOT_ID; node = node->parent) { assert(node->treelock != 0); assert(node->treelock != TREELOCK_WAIT_OFFSET); assert(node->treelock != TREELOCK_WRITE); node->treelock--; if(node->treelock == TREELOCK_WAIT_OFFSET) node->treelock = 0; } } /* Throughout this file all calls to the op.func() callbacks will be '&fusepath[1]' because the current path generation always prefixes a '/' for each path but we need a relative path for usage with `openat()` functions and would rather do that here than in the called function. */ static int try_get_path(uint64_t nodeid, const char *name, char **path, node_t **wnodep, bool need_lock) { unsigned bufsize = 256; char *buf; char *s; node_t *node; node_t *wnode = NULL; int err; *path = NULL; err = -ENOMEM; buf = (char*)malloc(bufsize); if(buf == NULL) goto out_err; s = buf + bufsize - 1; *s = '\0'; if(name != NULL) { s = add_name(&buf,&bufsize,s,name); err = -ENOMEM; if(s == NULL) goto out_free; } if(wnodep) { assert(need_lock); wnode = lookup_node(nodeid,name); if(wnode) { if(wnode->treelock != 0) { if(wnode->treelock > 0) wnode->treelock += TREELOCK_WAIT_OFFSET; err = -EAGAIN; goto out_free; } wnode->treelock = TREELOCK_WRITE; } } node = get_node(nodeid); if(node == NULL) { err = -ESTALE; goto out_unlock; } for(; node->nodeid != FUSE_ROOT_ID; node = node->parent) { err = -ESTALE; if(node->name == NULL || node->parent == NULL) goto out_unlock; err = -ENOMEM; s = add_name(&buf,&bufsize,s,node->name); if(s == NULL) goto out_unlock; if(need_lock) { err = -EAGAIN; if(node->treelock < 0) goto out_unlock; node->treelock++; } } if(s[0]) memmove(buf,s,bufsize - (s - buf)); else strcpy(buf,"/"); *path = buf; if(wnodep) *wnodep = wnode; return 0; out_unlock: if(need_lock) unlock_path(nodeid,wnode,node); out_free: free(buf); out_err: return err; } static int try_get_path2(uint64_t nodeid1, const char *name1, uint64_t nodeid2, const char *name2, char **path1, char **path2, node_t **wnode1, node_t **wnode2) { int err; err = try_get_path(nodeid1,name1,path1,wnode1,true); if(!err) { err = try_get_path(nodeid2,name2,path2,wnode2,true); if(err) { node_t *wn1 = wnode1 ? *wnode1 : NULL; unlock_path(nodeid1,wn1,NULL); free(*path1); } } return err; } static void queue_element_wakeup(struct lock_queue_element *qe) { int err; if(!qe->path1) { /* Just waiting for it to be unlocked */ node_t *qnode = get_node(qe->nodeid1); if(qnode && (qnode->treelock == 0)) pthread_cond_signal(&qe->cond); return; } if(qe->done) return; if(!qe->path2) { err = try_get_path(qe->nodeid1, qe->name1, qe->path1, qe->wnode1, true); } else { err = try_get_path2(qe->nodeid1, qe->name1, qe->nodeid2, qe->name2, qe->path1, qe->path2, qe->wnode1, qe->wnode2); } if(err == -EAGAIN) return; qe->err = err; qe->done = true; pthread_cond_signal(&qe->cond); } static void wake_up_queued() { struct lock_queue_element *qe; for(qe = f.lockq; qe != NULL; qe = qe->next) queue_element_wakeup(qe); } static void queue_path(struct lock_queue_element *qe) { struct lock_queue_element **qp; qe->done = false; pthread_cond_init(&qe->cond,NULL); qe->next = NULL; for(qp = &f.lockq; *qp != NULL; qp = &(*qp)->next); *qp = qe; } static void dequeue_path(struct lock_queue_element *qe) { struct lock_queue_element **qp; pthread_cond_destroy(&qe->cond); for(qp = &f.lockq; *qp != qe; qp = &(*qp)->next); *qp = qe->next; } static int wait_path(struct lock_queue_element *qe) { queue_path(qe); do { pthread_cond_wait(&qe->cond,&f.lock); } while(!qe->done); dequeue_path(qe); return qe->err; } static int get_path_common(uint64_t nodeid, const char *name, char **path, node_t **wnode) { int err; mutex_lock(f.lock); err = try_get_path(nodeid,name,path,wnode,true); if(err == -EAGAIN) { struct lock_queue_element qe = {}; qe.nodeid1 = nodeid; qe.name1 = name; qe.path1 = path; qe.wnode1 = wnode; err = wait_path(&qe); } mutex_unlock(f.lock); return err; } static int get_path(uint64_t nodeid, char **path) { return get_path_common(nodeid,NULL,path,NULL); } static int get_path_name(uint64_t nodeid, const char *name, char **path) { return get_path_common(nodeid,name,path,NULL); } static int get_path_wrlock(uint64_t nodeid, const char *name, char **path, node_t **wnode) { return get_path_common(nodeid,name,path,wnode); } static int get_path2(uint64_t nodeid1, const char *name1, uint64_t nodeid2, const char *name2, char **path1, char **path2, node_t **wnode1, node_t **wnode2) { int err; mutex_lock(f.lock); err = try_get_path2(nodeid1,name1,nodeid2,name2, path1,path2,wnode1,wnode2); if(err == -EAGAIN) { struct lock_queue_element qe = {}; qe.nodeid1 = nodeid1; qe.name1 = name1; qe.path1 = path1; qe.wnode1 = wnode1; qe.nodeid2 = nodeid2; qe.name2 = name2; qe.path2 = path2; qe.wnode2 = wnode2; err = wait_path(&qe); } mutex_unlock(f.lock); return err; } static void free_path_wrlock(uint64_t nodeid, node_t *wnode, char *path) { mutex_lock(f.lock); unlock_path(nodeid,wnode,NULL); if(f.lockq) wake_up_queued(); mutex_unlock(f.lock); free(path); } static void update_stat(node_t *node_, const struct stat *stnew_) { uint32_t crc32b; // This function can only invalidate an existing fingerprint; it // never arms a new one. Arming (writing a non-zero CRC into // state_crc32b) happens exclusively in open_auto_cache(), which is // the single point that decides a file's page cache is worth // retaining across opens. If stat_crc32b is 0 here the cache is // already considered invalid and we have nothing to compare // against. if(node_->stat_crc32b == 0) return; crc32b = stat_crc32b(stnew_); if(crc32b != node_->stat_crc32b) node_->stat_crc32b = 0; } static void free_path(uint64_t nodeid, char *path) { if(path) free_path_wrlock(nodeid,NULL,path); } static node_t* free_path_and_inc_open(u64 free_nodeid_, char *path_, u64 inc_open_ino_, bool remember_too_) { node_t *node; mutex_lock(f.lock); unlock_path(free_nodeid_,NULL,NULL); node = get_node(inc_open_ino_); if(node) { if(remember_too_ && remember_nodes()) remember_node(node); node->open_count++; } if(f.lockq) wake_up_queued(); mutex_unlock(f.lock); free(path_); return node; } static bool free_path_and_update_stat(u64 nodeid_, char *path_, const struct stat *stnew_) { bool ok; node_t *node; mutex_lock(f.lock); unlock_path(nodeid_,NULL,NULL); node = get_node(nodeid_); ok = (node != NULL); if(ok) update_stat(node,stnew_); if(f.lockq) wake_up_queued(); mutex_unlock(f.lock); free(path_); return ok; } static void free_path2(uint64_t nodeid1, uint64_t nodeid2, node_t *wnode1, node_t *wnode2, char *path1, char *path2) { mutex_lock(f.lock); unlock_path(nodeid1,wnode1,NULL); unlock_path(nodeid2,wnode2,NULL); wake_up_queued(); mutex_unlock(f.lock); free(path1); free(path2); } static void forget_node(const uint64_t nodeid, const uint64_t nlookup) { node_t *node; if(nodeid == FUSE_ROOT_ID) return; mutex_lockguard(f.lock); node = get_node(nodeid); if(node == NULL) return; /* * Node may still be locked due to interrupt idiocy in open, * create and opendir */ while(node->nlookup == nlookup && node->treelock) { struct lock_queue_element qe = {}; qe.nodeid1 = nodeid; queue_path(&qe); do { pthread_cond_wait(&qe.cond,&f.lock); } while((node->nlookup == nlookup) && node->treelock); dequeue_path(&qe); } assert(node->nlookup >= nlookup); node->nlookup -= nlookup; unref_node_if_unused(node); } static void unlink_node(node_t *node) { unhash_name(node); unremember_node(node); } static void remove_node(uint64_t dir, const char *name) { node_t *node; mutex_lock(f.lock); node = lookup_node(dir,name); if(node != NULL) unlink_node(node); mutex_unlock(f.lock); } static int rename_node(uint64_t olddir, const char *oldname, uint64_t newdir, const char *newname) { int err = 0; node_t *node; node_t *newnode; mutex_lock(f.lock); node = lookup_node(olddir,oldname); newnode = lookup_node(newdir,newname); if(node == NULL) goto out; if((newnode != NULL) && (newnode != node)) unlink_node(newnode); unhash_name(node); if(hash_name(node,newdir,newname) == -1) { err = -ENOMEM; goto out; } out: mutex_unlock(f.lock); return err; } static void set_stat(uint64_t nodeid, struct stat *stbuf) { if(fuse_cfg.valid_uid()) stbuf->st_uid = fuse_cfg.uid; if(fuse_cfg.valid_gid()) stbuf->st_gid = fuse_cfg.gid; if(fuse_cfg.valid_umask()) stbuf->st_mode = (stbuf->st_mode & S_IFMT) | (0777 & ~fuse_cfg.umask); } static int node_open(const node_t *node_) { return ((node_ != NULL) && (node_->open_count > 0)); } static int set_path_info(uint64_t nodeid_, const char *name_, struct fuse_entry_param *e_, bool remember_ = true) { node_t *node; mutex_lock(f.lock); if(!name_) { node = get_node(nodeid_); if(node == NULL) goto out_unlock; } else { node = lookup_node(nodeid_,name_); } if(node == NULL) { node = node_alloc(); if(node == NULL) goto out_unlock; node->nodeid = generate_nodeid(&f.nodeid_gen); if(remember_ && remember_nodes()) remember_node(node); if(hash_name(node,nodeid_,name_) == -1) { free_node(node); node = NULL; goto out_unlock; } hash_id(node); } inc_nlookup(node); e_->ino = node->nodeid; e_->generation = ((e_->ino == FUSE_ROOT_ID) ? 0 : f.nodeid_gen.generation); update_stat(node,&e_->attr); mutex_unlock(f.lock); set_stat(e_->ino,&e_->attr); return 0; out_unlock: mutex_unlock(f.lock); return -ENOMEM; } /* lookup requests only come in for FUSE_ROOT_ID when a "parent of child of root node" request is made. This can happen when using EXPORT_SUPPORT=true and a file handle is used to keep a reference to a node which has been forgotten. Mostly a NFS concern but not excluslively. Root node always has a nodeid of 1 and generation of 0. To ensure this set_path_info() explicitly ensures the root id has a generation of 0. */ static int lookup_path(fuse_req_t *req_, uint64_t nodeid, const char *name, const char *fusepath, struct fuse_entry_param *e, fuse_file_info_t *fi, bool remember = true) { int rv; assert(req_ != NULL); memset(e,0,sizeof(struct fuse_entry_param)); rv = ((fi == NULL) ? f.ops.getattr(&req_->ctx,&fusepath[1],&e->attr,&e->timeout) : f.ops.fgetattr(&req_->ctx,fi->fh,&e->attr,&e->timeout)); if(rv) return rv; return set_path_info(nodeid,name,e,remember); } static void reply_entry(fuse_req_t *req_, const struct fuse_entry_param *e, int err) { if(!err) { if(fuse_reply_entry(req_,e) == -ENOENT) { /* Skip forget for negative result */ if(e->ino != 0) forget_node(e->ino,1); } } else { fuse_reply_err(req_,err); } } static void fuse_lib_init(fuse_conn_info_t *conn) { f.ops.init(conn); } static void fuse_lib_destroy() { f.ops.destroy(); } static void fuse_lib_lookup(fuse_req_t *req_, struct fuse_in_header *hdr_) { int err; uint64_t nodeid; char *fusepath; const char *name; node_t *dot = NULL; struct fuse_entry_param e = {}; name = (const char*)fuse_hdr_arg(hdr_); nodeid = hdr_->nodeid; if(name[0] == '.') { if(name[1] == '\0') { name = NULL; mutex_lockguard(f.lock); dot = get_node(nodeid); if(dot == NULL) { reply_entry(req_,&e,-ESTALE); return; } dot->refctr++; } else if((name[1] == '.') && (name[2] == '\0')) { if(nodeid == FUSE_ROOT_ID) { reply_entry(req_,&e,-ENOENT); return; } name = NULL; mutex_lockguard(f.lock); { node_t *pnode = get_node(nodeid); if((pnode == NULL) || (pnode->parent == NULL)) { reply_entry(req_,&e,-ESTALE); return; } nodeid = pnode->parent->nodeid; } } } err = get_path_name(nodeid,name,&fusepath); if(!err) { err = lookup_path(req_,nodeid,name,fusepath,&e,NULL); if(err == -ENOENT) { e.ino = 0; err = 0; } free_path(nodeid,fusepath); } if(dot) { mutex_lock(f.lock); unref_node(dot); mutex_unlock(f.lock); } reply_entry(req_,&e,err); } static void fuse_lib_lseek(fuse_req_t *req_, struct fuse_in_header *hdr_) { fuse_reply_err(req_,ENOSYS); } static void fuse_lib_forget(fuse_req_t *req_, struct fuse_in_header *hdr_) { struct fuse_forget_in *arg; arg = (fuse_forget_in*)fuse_hdr_arg(hdr_); forget_node(hdr_->nodeid,arg->nlookup); fuse_reply_none(req_); } static void fuse_lib_forget_multi(fuse_req_t *req_, struct fuse_in_header *hdr_) { struct fuse_batch_forget_in *arg; struct fuse_forget_one *entry; arg = (fuse_batch_forget_in*)fuse_hdr_arg(hdr_); entry = (fuse_forget_one*)PARAM(arg); for(uint32_t i = 0; i < arg->count; i++) { forget_node(entry[i].nodeid, entry[i].nlookup); } fuse_reply_none(req_); } static void fuse_lib_getattr(fuse_req_t *req_, struct fuse_in_header *hdr_) { int err; char *fusepath; uint64_t fh; struct stat buf; node_t *node; fuse_timeouts_t timeouts{}; const struct fuse_getattr_in *arg; arg = (fuse_getattr_in*)fuse_hdr_arg(hdr_); fh = 0; if(arg->getattr_flags & FUSE_GETATTR_FH) fh = arg->fh; memset(&buf,0,sizeof(buf)); err = 0; fusepath = NULL; if(fh == 0) { err = get_path(hdr_->nodeid,&fusepath); if(err == -ESTALE) // unlinked but open err = 0; } if(!err) { bool node_ok = true; if(fusepath != NULL) { err = f.ops.getattr(&req_->ctx,&fusepath[1],&buf,&timeouts); if(!err) node_ok = free_path_and_update_stat(hdr_->nodeid,fusepath,&buf); else free_path(hdr_->nodeid,fusepath); } else { err = f.ops.fgetattr(&req_->ctx,fh,&buf,&timeouts); if(!err) { mutex_lockguard(f.lock); node = get_node(hdr_->nodeid); node_ok = (node != NULL); if(node_ok) update_stat(node,&buf); } } if(!err && !node_ok) { fuse_reply_err(req_,ESTALE); return; } } if(!err) { set_stat(hdr_->nodeid,&buf); fuse_reply_attr(req_,&buf,timeouts.attr); } else { fuse_reply_err(req_,err); } } static void fuse_lib_statx_path(fuse_req_t *req_, struct fuse_in_header *hdr_, fuse_statx_in *inarg_) { int err; char *fusepath; struct fuse_statx st{}; fuse_timeouts_t timeouts{}; err = get_path(hdr_->nodeid,&fusepath); if(err == -ESTALE) err = 0; if(err) { fuse_reply_err(req_,err); return; } if(fusepath != NULL) err = f.ops.statx(&req_->ctx, &fusepath[1], inarg_->sx_flags, inarg_->sx_mask, &st, &timeouts); else err = f.ops.statx_fh(&req_->ctx, 0, inarg_->sx_flags, inarg_->sx_mask, &st, &timeouts); free_path(hdr_->nodeid,fusepath); if(err) fuse_reply_err(req_,err); else fuse_reply_statx(req_,0,&st,timeouts.attr); } static void fuse_lib_statx_fh(fuse_req_t *req_, struct fuse_in_header *hdr_, fuse_statx_in *inarg_) { int err; struct fuse_statx st{}; fuse_timeouts_t timeouts{}; err = f.ops.statx_fh(&req_->ctx, inarg_->fh, inarg_->sx_flags, inarg_->sx_mask, &st, &timeouts); if(err) fuse_reply_err(req_,err); else fuse_reply_statx(req_,0,&st,timeouts.attr); } static void fuse_lib_statx(fuse_req_t *req_, struct fuse_in_header *hdr_) { fuse_statx_in *inarg; inarg = (fuse_statx_in*)fuse_hdr_arg(hdr_); if(inarg->getattr_flags & FUSE_GETATTR_FH) fuse_lib_statx_fh(req_,hdr_,inarg); else fuse_lib_statx_path(req_,hdr_,inarg); } static void fuse_lib_setattr(fuse_req_t *req_, struct fuse_in_header *hdr_) { uint64_t fh; struct stat stbuf = {}; char *fusepath; int err; fuse_timeouts_t timeouts{}; struct fuse_setattr_in *arg; arg = (fuse_setattr_in*)fuse_hdr_arg(hdr_); fh = 0; if(arg->valid & FATTR_FH) fh = arg->fh; err = 0; fusepath = NULL; if(fh == 0) { err = get_path(hdr_->nodeid,&fusepath); if(err == -ESTALE) err = 0; } if(!err) { err = 0; if(!err && (arg->valid & FATTR_MODE)) err = ((fusepath != NULL) ? f.ops.chmod(&req_->ctx,&fusepath[1],arg->mode) : f.ops.fchmod(&req_->ctx,fh,arg->mode)); if(!err && (arg->valid & (FATTR_UID | FATTR_GID))) { uid_t uid = ((arg->valid & FATTR_UID) ? arg->uid : (uid_t)-1); gid_t gid = ((arg->valid & FATTR_GID) ? arg->gid : (gid_t)-1); err = ((fusepath != NULL) ? f.ops.chown(&req_->ctx,&fusepath[1],uid,gid) : f.ops.fchown(&req_->ctx,fh,uid,gid)); } if(!err && (arg->valid & FATTR_SIZE)) err = ((fusepath != NULL) ? f.ops.truncate(&req_->ctx,&fusepath[1],arg->size) : f.ops.ftruncate(&req_->ctx,fh,arg->size)); if(!err && (arg->valid & (FATTR_ATIME | FATTR_MTIME))) { struct timespec tv[2]; tv[0].tv_sec = 0; tv[1].tv_sec = 0; tv[0].tv_nsec = UTIME_OMIT; tv[1].tv_nsec = UTIME_OMIT; if(arg->valid & FATTR_ATIME_NOW) tv[0].tv_nsec = UTIME_NOW; else if(arg->valid & FATTR_ATIME) tv[0] = (struct timespec){ static_cast(arg->atime), static_cast(arg->atimensec) }; if(arg->valid & FATTR_MTIME_NOW) tv[1].tv_nsec = UTIME_NOW; else if(arg->valid & FATTR_MTIME) tv[1] = (struct timespec){ static_cast(arg->mtime), static_cast(arg->mtimensec) }; err = ((fusepath != NULL) ? f.ops.utimens(&req_->ctx,&fusepath[1],tv) : f.ops.futimens(&req_->ctx,fh,tv)); } if(!err) err = ((fusepath != NULL) ? f.ops.getattr(&req_->ctx,&fusepath[1],&stbuf,&timeouts) : f.ops.fgetattr(&req_->ctx,fh,&stbuf,&timeouts)); bool stat_updated = false; if(!err && (fusepath != NULL)) { node_t *node; mutex_lock(f.lock); unlock_path(hdr_->nodeid,NULL,NULL); node = get_node(hdr_->nodeid); if(node) update_stat(node,&stbuf); if(f.lockq) wake_up_queued(); mutex_unlock(f.lock); free(fusepath); stat_updated = true; } else { free_path(hdr_->nodeid,fusepath); } if(!err) { if(!stat_updated) { node_t *node; mutex_lockguard(f.lock); node = get_node(hdr_->nodeid); if(node) update_stat(node,&stbuf); } set_stat(hdr_->nodeid,&stbuf); fuse_reply_attr(req_,&stbuf,timeouts.attr); } else { fuse_reply_err(req_,err); } return; } fuse_reply_err(req_,err); } static void fuse_lib_access(fuse_req_t *req_, struct fuse_in_header *hdr_) { int err; char *fusepath; struct fuse_access_in *arg; arg = (fuse_access_in*)fuse_hdr_arg(hdr_); err = get_path(hdr_->nodeid,&fusepath); if(!err) { err = f.ops.access(&req_->ctx, &fusepath[1], arg->mask); free_path(hdr_->nodeid,fusepath); } fuse_reply_err(req_,err); } static void fuse_lib_readlink(fuse_req_t *req_, struct fuse_in_header *hdr_) { ssize_t rv; char *fusepath; char linkname[PATH_MAX + 1]; rv = get_path(hdr_->nodeid,&fusepath); if(!rv) { rv = f.ops.readlink(&req_->ctx, &fusepath[1], linkname, sizeof(linkname)); free_path(hdr_->nodeid,fusepath); } if(rv >= 0) fuse_reply_readlink(req_,linkname,rv); else fuse_reply_err(req_,rv); } static void fuse_lib_mknod(fuse_req_t *req_, struct fuse_in_header *hdr_) { int err; char *fusepath; const char* name; struct fuse_entry_param e; struct fuse_mknod_in *arg; arg = (fuse_mknod_in*)fuse_hdr_arg(hdr_); name = (const char*)PARAM(arg); if(req_->conn.proto_minor >= 12) req_->ctx.umask = arg->umask; else name = (char*)arg + FUSE_COMPAT_MKNOD_IN_SIZE; err = get_path_name(hdr_->nodeid,name,&fusepath); if(!err) { err = -ENOSYS; if(S_ISREG(arg->mode)) { fuse_file_info_t ffi = {}; ffi.flags = O_CREAT | O_EXCL | O_WRONLY; err = f.ops.create(&req_->ctx, &fusepath[1], arg->mode, &ffi); if(!err) { err = lookup_path(req_,hdr_->nodeid,name,fusepath,&e,&ffi); f.ops.release(&req_->ctx, &ffi); } } if(err == -ENOSYS) { err = f.ops.mknod(&req_->ctx, &fusepath[1], arg->mode, arg->rdev); if(!err) err = lookup_path(req_,hdr_->nodeid,name,fusepath,&e,NULL); } free_path(hdr_->nodeid,fusepath); } reply_entry(req_,&e,err); } static void fuse_lib_mkdir(fuse_req_t *req_, struct fuse_in_header *hdr_) { int err; char *fusepath; const char *name; struct fuse_entry_param e; struct fuse_mkdir_in *arg; arg = (fuse_mkdir_in*)fuse_hdr_arg(hdr_); name = (const char*)PARAM(arg); if(req_->conn.proto_minor >= 12) req_->ctx.umask = arg->umask; err = get_path_name(hdr_->nodeid,name,&fusepath); if(!err) { err = f.ops.mkdir(&req_->ctx, &fusepath[1], arg->mode); if(!err) err = lookup_path(req_,hdr_->nodeid,name,fusepath,&e,NULL); free_path(hdr_->nodeid,fusepath); } reply_entry(req_,&e,err); } static void fuse_lib_unlink(fuse_req_t *req_, struct fuse_in_header *hdr_) { int err; char *fusepath; const char *name; node_t *wnode; name = (const char*)PARAM(hdr_); err = get_path_wrlock(hdr_->nodeid,name,&fusepath,&wnode); if(!err) { if(node_open(wnode)) req_->ctx.nodeid = wnode->nodeid; err = f.ops.unlink(&req_->ctx, &fusepath[1]); if(!err) remove_node(hdr_->nodeid,name); free_path_wrlock(hdr_->nodeid,wnode,fusepath); } fuse_reply_err(req_,err); } static void fuse_lib_rmdir(fuse_req_t *req_, struct fuse_in_header *hdr_) { int err; char *fusepath; const char *name; node_t *wnode; name = (const char*)PARAM(hdr_); err = get_path_wrlock(hdr_->nodeid,name,&fusepath,&wnode); if(!err) { err = f.ops.rmdir(&req_->ctx, &fusepath[1]); if(!err) remove_node(hdr_->nodeid,name); free_path_wrlock(hdr_->nodeid,wnode,fusepath); } fuse_reply_err(req_,err); } static void fuse_lib_symlink(fuse_req_t *req_, struct fuse_in_header *hdr_) { int rv; char *fusepath; const char *name; const char *linkname; struct fuse_entry_param e = {}; name = (const char*)fuse_hdr_arg(hdr_); linkname = (name + strlen(name) + 1); rv = get_path_name(hdr_->nodeid,name,&fusepath); if(rv == 0) { rv = f.ops.symlink(&req_->ctx, linkname, &fusepath[1], &e.attr, &e.timeout); if(rv == 0) rv = set_path_info(hdr_->nodeid,name,&e); free_path(hdr_->nodeid,fusepath); } reply_entry(req_,&e,rv); } static void fuse_lib_rename(fuse_req_t *req_, struct fuse_in_header *hdr_) { int err; char *oldpath; char *newpath; const char *oldname; const char *newname; node_t *wnode1; node_t *wnode2; struct fuse_rename_in *arg; arg = (fuse_rename_in*)fuse_hdr_arg(hdr_); oldname = (const char*)PARAM(arg); newname = (oldname + strlen(oldname) + 1); err = get_path2(hdr_->nodeid, oldname, arg->newdir, newname, &oldpath, &newpath, &wnode1, &wnode2); if(!err) { err = f.ops.rename(&req_->ctx, &oldpath[1], &newpath[1]); if(!err) err = rename_node(hdr_->nodeid,oldname,arg->newdir,newname); free_path2(hdr_->nodeid,arg->newdir,wnode1,wnode2,oldpath,newpath); } fuse_reply_err(req_,err); } static void fuse_lib_rename2(fuse_req_t *req_, struct fuse_in_header *hdr_) { fuse_reply_err(req_,ENOSYS); } static void fuse_lib_retrieve_reply(fuse_req_t *req_, void *cookie_, uint64_t ino_, off_t offset_) { } static void fuse_lib_link(fuse_req_t *req_, struct fuse_in_header *hdr_) { int rv; char *oldpath; char *newpath; const char *newname; struct fuse_link_in *arg; struct fuse_entry_param e = {}; arg = (fuse_link_in*)fuse_hdr_arg(hdr_); newname = (const char*)PARAM(arg); rv = get_path2(arg->oldnodeid, NULL, hdr_->nodeid, newname, &oldpath, &newpath, NULL, NULL); if(!rv) { rv = f.ops.link(&req_->ctx, &oldpath[1], &newpath[1], &e.attr, &e.timeout); if(rv == 0) rv = set_path_info(hdr_->nodeid,newname,&e); free_path2(arg->oldnodeid,hdr_->nodeid,NULL,NULL,oldpath,newpath); } reply_entry(req_,&e,rv); } static void fuse_do_release(fuse_req_ctx_t *req_ctx_, uint64_t ino_, fuse_file_info_t *ffi_) { f.ops.release(req_ctx_, ffi_); mutex_lockguard(f.lock); { node_t *node; node = get_node(ino_); if(node) { assert(node->open_count > 0); node->open_count--; unref_node_if_unused(node); } } } static void fuse_lib_create(fuse_req_t *req_, struct fuse_in_header *hdr_) { int err; char *fusepath; const char *name; uint64_t new_nodeid; fuse_file_info_t ffi = {}; struct fuse_entry_param e; struct fuse_create_in *arg; arg = (fuse_create_in*)fuse_hdr_arg(hdr_); name = (const char*)PARAM(arg); ffi.flags = arg->flags; if(req_->conn.proto_minor >= 12) req_->ctx.umask = arg->umask; else name = ((char*)arg + sizeof(struct fuse_open_in)); // Opportunistically allocate a provisional node so passthrough create has // a nodeid. It is remembered only after create fully succeeds. { node_t *node; node = find_node(hdr_->nodeid,name,false); if(node == NULL) { fuse_reply_err(req_,ENOMEM); return; } new_nodeid = node->nodeid; req_->ctx.nodeid = new_nodeid; } err = get_path_name(hdr_->nodeid,name,&fusepath); if(!err) { err = f.ops.create(&req_->ctx, &fusepath[1], arg->mode, &ffi); if(!err) { err = lookup_path(req_,hdr_->nodeid,name,fusepath,&e,&ffi,false); if(err) { f.ops.release(&req_->ctx,&ffi); } else if(!S_ISREG(e.attr.st_mode)) { err = -EIO; f.ops.release(&req_->ctx,&ffi); forget_node(e.ino,1); } } } if(!err) { free_path_and_inc_open(hdr_->nodeid,fusepath,e.ino,true); fusepath = NULL; if(fuse_reply_create(req_,&e,&ffi) == -ENOENT) { /* The open syscall was interrupted,so it must be cancelled */ fuse_do_release(&req_->ctx,e.ino,&ffi); forget_node(e.ino,1); } } else { forget_node(new_nodeid,1); fuse_reply_err(req_,err); } free_path(hdr_->nodeid,fusepath); } static void open_auto_cache(fuse_req_ctx_t *req_ctx_, uint64_t ino, const char *path, fuse_file_info_t *fi) { node_t *node; fuse_timeouts_t timeouts{}; mutex_lock(f.lock); node = get_node(ino); if(not node) { mutex_unlock(f.lock); return; } { int err; struct stat stbuf; mutex_unlock(f.lock); err = f.ops.fgetattr(req_ctx_, fi->fh, &stbuf, &timeouts); mutex_lock(f.lock); if(!err) { uint32_t old_crc32b; uint32_t new_crc32b; old_crc32b = node->stat_crc32b; new_crc32b = stat_crc32b(&stbuf); if((old_crc32b != 0) && (old_crc32b == new_crc32b)) fi->keep_cache = 1; node->stat_crc32b = new_crc32b; } else { node->stat_crc32b = 0; } } mutex_unlock(f.lock); } static void fuse_lib_open(fuse_req_t *req_, struct fuse_in_header *hdr_) { int err; char *fusepath; fuse_file_info_t ffi = {}; struct fuse_open_in *arg; arg = (fuse_open_in*)fuse_hdr_arg(hdr_); ffi.flags = arg->flags; err = get_path(hdr_->nodeid,&fusepath); if(!err) { err = f.ops.open(&req_->ctx, &fusepath[1], &ffi); if(!err) { if(ffi.auto_cache && (ffi.passthrough == false)) open_auto_cache(&req_->ctx,hdr_->nodeid,fusepath,&ffi); } } if(!err) { free_path_and_inc_open(hdr_->nodeid,fusepath,hdr_->nodeid,false); fusepath = NULL; /* The open syscall was interrupted,so it must be cancelled */ if(fuse_reply_open(req_,&ffi) == -ENOENT) fuse_do_release(&req_->ctx,hdr_->nodeid,&ffi); } else { fuse_reply_err(req_,err); } free_path(hdr_->nodeid,fusepath); } static void fuse_lib_read_mem(fuse_req_t *req_, const fuse_file_info_t *ffi_, const size_t size_, const off_t offset_) { int res; fuse_msgbuf_t *msgbuf; msgbuf = msgbuf_alloc_page_aligned(); if(msgbuf == NULL) { fuse_reply_err(req_,ENOMEM); return; } res = f.ops.read(&req_->ctx, ffi_, msgbuf->mem, size_, offset_); if(res >= 0) fuse_reply_data(req_,msgbuf->mem,res); else fuse_reply_err(req_,res); msgbuf_free(msgbuf); } /* Large reads: ask the filesystem for a branch-file region and splice it into /dev/fuse. Every staging failure falls back to the memory path above, which is always correct. A failure of the final submission splice is answered with an error-only reply and never replayed: the kernel may already have consumed the payload. The region's lock is released by the reply path once the data is staged; after sent/failed only libfuse's own req is touched here, since the handle may already be released. */ static bool fuse_lib_read_splice(fuse_req_t *req_, const fuse_file_info_t *ffi_, const size_t size_, const off_t offset_) { int res; FileRegion region; res = f.ops.read_buf(&req_->ctx,ffi_,®ion,size_,offset_); if(res < 0) { fuse_reply_err(req_,res); return true; } if(res > 0) return false; /* The reply length comes from region.size alone (see read_buf in fuse.h): a region smaller than the request would be sent as a short reply, which the kernel treats as end-of-file. Decline such a region instead; its destructor releases the lock before the copy path. */ if(region.size != size_) return false; switch(fuse_reply_data_splice_fd(req_,region)) { case FuseReplyResult::sent: case FuseReplyResult::failed: fuse_req_free(req_); return true; case FuseReplyResult::unsupported: if(f.ops.read_buf_rejected) f.ops.read_buf_rejected(&req_->ctx,ffi_); return false; case FuseReplyResult::fallback: break; } return false; } static void fuse_lib_read(fuse_req_t *req_, struct fuse_in_header *hdr_) { fuse_file_info_t ffi = {}; struct fuse_read_in *arg; arg = (fuse_read_in*)fuse_hdr_arg(hdr_); ffi.fh = arg->fh; if(req_->conn.proto_minor >= 9) { ffi.flags = arg->flags; ffi.lock_owner = arg->lock_owner; } /* A region is always the whole request, so whether its reply can be spliced at all (the threshold, the pipe limit) is known here, before the filesystem does any work for it. */ if((f.ops.read_buf != nullptr) && fuse_reply_splice_fits(arg->offset,arg->size) && fuse_lib_read_splice(req_,&ffi,arg->size,arg->offset)) return; fuse_lib_read_mem(req_,&ffi,arg->size,arg->offset); } static void fuse_lib_write(fuse_req_t *req_, struct fuse_in_header *hdr_) { int res; char *data; fuse_file_info_t ffi = {}; struct fuse_write_in *arg; arg = (fuse_write_in*)fuse_hdr_arg(hdr_); ffi.fh = arg->fh; ffi.writepage = !!(arg->write_flags & 1); if(req_->conn.proto_minor < 9) { data = ((char*)arg) + FUSE_COMPAT_WRITE_IN_SIZE; } else { ffi.flags = arg->flags; ffi.lock_owner = arg->lock_owner; data = (char*)PARAM(arg); } res = f.ops.write(&req_->ctx, &ffi, data, arg->size, arg->offset); if(res >= 0) fuse_reply_write(req_,res); else fuse_reply_err(req_,res); } static void fuse_lib_fsync(fuse_req_t *req_, struct fuse_in_header *hdr_) { int err; int is_datasync; struct fuse_fsync_in *arg; arg = (fuse_fsync_in*)fuse_hdr_arg(hdr_); is_datasync = !!(arg->fsync_flags & FUSE_FSYNC_FDATASYNC); err = f.ops.fsync(&req_->ctx, arg->fh, is_datasync); fuse_reply_err(req_,err); } static struct fuse_dh* get_dirhandle(const fuse_file_info_t *llfi, fuse_file_info_t *ffi) { struct fuse_dh *dh = (struct fuse_dh *)(uintptr_t)llfi->fh; if(dh == NULL) return NULL; *ffi = {}; ffi->fh = dh->fh; return dh; } static void fuse_lib_opendir(fuse_req_t *req_, struct fuse_in_header *hdr_) { int err; char *fusepath; struct fuse_dh *dh; fuse_file_info_t llffi = {}; fuse_file_info_t ffi = {}; struct fuse_open_in *arg; arg = (fuse_open_in*)fuse_hdr_arg(hdr_); llffi.flags = arg->flags; dh = (fuse_dh*)calloc(1,sizeof(fuse_dh)); if(dh == NULL) { fuse_reply_err(req_,ENOMEM); return; } err = fuse_dirents_init(&dh->d); if(err) { free(dh); fuse_reply_err(req_,ENOMEM); return; } mutex_init(dh->lock); llffi.fh = (uintptr_t)dh; ffi.flags = llffi.flags; err = get_path(hdr_->nodeid,&fusepath); if(!err) { err = f.ops.opendir(&req_->ctx, &fusepath[1], &ffi); dh->fh = ffi.fh; llffi.keep_cache = ffi.keep_cache; llffi.cache_readdir = ffi.cache_readdir; } if(!err) { if(fuse_reply_open(req_,&llffi) == -ENOENT) { /* The opendir syscall was interrupted,so it must be cancelled */ f.ops.releasedir(&req_->ctx,&ffi); fuse_dirents_free(&dh->d); mutex_destroy(dh->lock); free(dh); } } else { fuse_reply_err(req_,err); fuse_dirents_free(&dh->d); mutex_destroy(dh->lock); free(dh); } free_path(hdr_->nodeid,fusepath); } static size_t readdir_buf_size(fuse_dirents_t *d_, size_t size_, off_t off_) { if((size_t)off_ >= kv_size(d_->offs)) return 0; if((kv_A(d_->offs,off_) + size_) > kv_size(d_->data)) return (kv_size(d_->data) - kv_A(d_->offs,off_)); return size_; } static char* readdir_buf(fuse_dirents_t *d_, off_t off_) { size_t i; i = kv_A(d_->offs,off_); return &kv_A(d_->data,i); } static void fuse_lib_readdir(fuse_req_t *req_, struct fuse_in_header *hdr_) { int rv; size_t size; fuse_dirents_t *d; struct fuse_dh *dh; fuse_file_info_t ffi = {}; fuse_file_info_t llffi = {}; struct fuse_read_in *arg; arg = (fuse_read_in*)fuse_hdr_arg(hdr_); size = arg->size; llffi.fh = arg->fh; dh = get_dirhandle(&llffi,&ffi); if(not dh) { fuse_reply_err(req_,EBADF); return; } d = &dh->d; mutex_lockguard(dh->lock); rv = 0; if((arg->offset == 0) || (kv_size(d->data) == 0)) rv = f.ops.readdir(&req_->ctx, &ffi, d); if(rv) { fuse_reply_err(req_,rv); return; } size = readdir_buf_size(d,size,arg->offset); if(size == 0) fuse_reply_buf(req_,NULL,0); else fuse_reply_buf(req_, readdir_buf(d,arg->offset), size); } static void fuse_lib_readdir_plus(fuse_req_t *req_, struct fuse_in_header *hdr_) { int rv; size_t size; fuse_dirents_t *d; struct fuse_dh *dh; fuse_file_info_t ffi = {}; fuse_file_info_t llffi = {}; struct fuse_read_in *arg; arg = (fuse_read_in*)fuse_hdr_arg(hdr_); size = arg->size; llffi.fh = arg->fh; dh = get_dirhandle(&llffi,&ffi); if(not dh) { fuse_reply_err(req_,EBADF); return; } d = &dh->d; mutex_lockguard(dh->lock); rv = 0; if((arg->offset == 0) || (kv_size(d->data) == 0)) rv = f.ops.readdir_plus(&req_->ctx, &ffi, d); if(rv) { fuse_reply_err(req_,rv); return; } size = readdir_buf_size(d,size,arg->offset); if(size == 0) fuse_reply_buf(req_,NULL,0); else fuse_reply_buf(req_, readdir_buf(d,arg->offset), size); } static void fuse_lib_releasedir(fuse_req_t *req_, struct fuse_in_header *hdr_) { struct fuse_dh *dh; fuse_file_info_t ffi = {}; fuse_file_info_t llffi = {}; struct fuse_release_in *arg; arg = (fuse_release_in*)fuse_hdr_arg(hdr_); llffi.fh = arg->fh; llffi.flags = arg->flags; dh = get_dirhandle(&llffi,&ffi); if(not dh) { fuse_reply_err(req_,EBADF); return; } f.ops.releasedir(&req_->ctx, &ffi); // Acquire/release barrier: not a real critical section. A // concurrent readdir may still hold dh->lock at this point. This // lock+unlock pair blocks until the last readdir drops the lock // guaranteeing that the subsequent destroy/free sees no in-flight // readdir accessing dh->d. Do NOT remove. mutex_lock(dh->lock); mutex_unlock(dh->lock); mutex_destroy(dh->lock); fuse_dirents_free(&dh->d); free(dh); fuse_reply_err(req_,0); } static void fuse_lib_fsyncdir(fuse_req_t *req_, struct fuse_in_header *hdr_) { int err; int is_datasync; fuse_dh *dh; fuse_file_info_t ffi = {}; fuse_file_info_t llffi = {}; struct fuse_fsync_in *arg; arg = (fuse_fsync_in*)fuse_hdr_arg(hdr_); is_datasync = !!(arg->fsync_flags & FUSE_FSYNC_FDATASYNC); llffi.fh = arg->fh; dh = get_dirhandle(&llffi,&ffi); if(not dh) { fuse_reply_err(req_,EBADF); return; } err = f.ops.fsyncdir(&req_->ctx, &ffi, is_datasync); fuse_reply_err(req_,err); } static void fuse_lib_statfs(fuse_req_t *req_, struct fuse_in_header *hdr_) { int err = 0; char *fusepath; struct statvfs buf = {}; fusepath = NULL; if(hdr_->nodeid) err = get_path(hdr_->nodeid,&fusepath); if(!err) { err = f.ops.statfs(&req_->ctx, fusepath ? &fusepath[1] : "", &buf); free_path(hdr_->nodeid,fusepath); } if(!err) fuse_reply_statfs(req_,&buf); else fuse_reply_err(req_,err); } static void fuse_lib_setxattr(fuse_req_t *req_, struct fuse_in_header *hdr_) { int err; char *fusepath; const char *name; const char *value; struct fuse_setxattr_in *arg; arg = (fuse_setxattr_in*)fuse_hdr_arg(hdr_); if((req_->conn.capable & FUSE_SETXATTR_EXT) && (req_->conn.want & FUSE_SETXATTR_EXT)) name = (const char*)PARAM(arg); else name = (((char*)arg) + FUSE_COMPAT_SETXATTR_IN_SIZE); value = (name + strlen(name) + 1); err = get_path(hdr_->nodeid,&fusepath); if(!err) { err = f.ops.setxattr(&req_->ctx, &fusepath[1], name, value, arg->size, arg->flags); free_path(hdr_->nodeid,fusepath); } fuse_reply_err(req_,err); } static int common_getxattr(fuse_req_t *req_, uint64_t ino, const char *name, char *value, size_t size) { int err; char *fusepath; err = get_path(ino,&fusepath); if(!err) { err = f.ops.getxattr(&req_->ctx, &fusepath[1], name, value, size); free_path(ino,fusepath); } return err; } static void fuse_lib_getxattr(fuse_req_t *req_, struct fuse_in_header *hdr_) { int res; const char* name; struct fuse_getxattr_in *arg; arg = (fuse_getxattr_in*)fuse_hdr_arg(hdr_); name = (const char*)PARAM(arg); if(arg->size) { char *value = (char*)malloc(arg->size); if(value == NULL) { fuse_reply_err(req_,ENOMEM); return; } res = common_getxattr(req_,hdr_->nodeid,name,value,arg->size); if(res > 0) fuse_reply_buf(req_,value,res); else fuse_reply_err(req_,res); free(value); } else { res = common_getxattr(req_,hdr_->nodeid,name,NULL,0); if(res >= 0) fuse_reply_xattr(req_,res); else fuse_reply_err(req_,res); } } static int common_listxattr(fuse_req_t *req_, uint64_t ino, char *list, size_t size) { int err; char *fusepath; err = get_path(ino,&fusepath); if(!err) { err = f.ops.listxattr(&req_->ctx, &fusepath[1], list, size); free_path(ino,fusepath); } return err; } static void fuse_lib_listxattr(fuse_req_t *req_, struct fuse_in_header *hdr_) { int res; struct fuse_getxattr_in *arg; arg = (fuse_getxattr_in*)fuse_hdr_arg(hdr_); if(arg->size) { char *list = (char*)malloc(arg->size); if(list == NULL) { fuse_reply_err(req_,ENOMEM); return; } res = common_listxattr(req_,hdr_->nodeid,list,arg->size); if(res > 0) fuse_reply_buf(req_,list,res); else fuse_reply_err(req_,res); free(list); } else { res = common_listxattr(req_,hdr_->nodeid,NULL,0); if(res >= 0) fuse_reply_xattr(req_,res); else fuse_reply_err(req_,res); } } static void fuse_lib_removexattr(fuse_req_t *req_, const struct fuse_in_header *hdr_) { int err; char *fusepath; const char *name; name = (const char*)fuse_hdr_arg(hdr_); err = get_path(hdr_->nodeid,&fusepath); if(!err) { err = f.ops.removexattr(&req_->ctx, &fusepath[1], name); free_path(hdr_->nodeid,fusepath); } fuse_reply_err(req_,err); } static void fuse_lib_copy_file_range(fuse_req_t *req_, const struct fuse_in_header *hdr_) { ssize_t rv; fuse_file_info_t ffi_in = {}; fuse_file_info_t ffi_out = {}; const struct fuse_copy_file_range_in *arg; struct fuse_copy_file_range_in arg_tmp; arg = (fuse_copy_file_range_in*)fuse_hdr_arg(hdr_); if(((hdr_->opcode == FUSE_COPY_FILE_RANGE) || (sizeof(size_t) == 4)) && (arg->len > 0xfffff000ULL)) { /* Avoid truncation in the old reply format or 32-bit size_t conversion. */ arg_tmp = *arg; arg_tmp.len = 0xfffff000ULL; arg = &arg_tmp; } ffi_in.fh = arg->fh_in; ffi_out.fh = arg->fh_out; rv = f.ops.copy_file_range(&req_->ctx, &ffi_in, arg->off_in, &ffi_out, arg->off_out, arg->len, arg->flags); if(rv >= 0) { if(hdr_->opcode == FUSE_COPY_FILE_RANGE_64) fuse_reply_copy_file_range_64(req_,rv); else fuse_reply_write(req_,rv); } else fuse_reply_err(req_,rv); } static void fuse_lib_setupmapping(fuse_req_t *req_, const struct fuse_in_header *hdr_) { fuse_reply_err(req_,ENOSYS); } static void fuse_lib_removemapping(fuse_req_t *req_, const struct fuse_in_header *hdr_) { fuse_reply_err(req_,ENOSYS); } static void fuse_lib_syncfs(fuse_req_t *req_, const struct fuse_in_header *hdr_) { fuse_reply_err(req_,ENOSYS); } // name is always '/' // nodeid is the base directory static void fuse_lib_tmpfile(fuse_req_t *req_, const struct fuse_in_header *hdr_) { int err; char *fusepath; const char *name; uint64_t new_nodeid; fuse_file_info_t ffi = {}; struct fuse_entry_param e; struct fuse_create_in *arg; arg = (fuse_create_in*)fuse_hdr_arg(hdr_); name = (const char*)PARAM(arg); ffi.flags = arg->flags; if(req_->conn.proto_minor >= 12) req_->ctx.umask = arg->umask; else name = (char*)arg + sizeof(struct fuse_open_in); // Opportunistically allocate a provisional node so passthrough tmpfile has // a nodeid. It is remembered only after tmpfile fully succeeds. { node_t *node; node = find_node(hdr_->nodeid,name,false); if(node == NULL) { fuse_reply_err(req_,ENOMEM); return; } new_nodeid = node->nodeid; req_->ctx.nodeid = new_nodeid; } err = get_path_name(hdr_->nodeid,name,&fusepath); if(!err) { err = f.ops.tmpfile(&req_->ctx, &fusepath[1], arg->mode, &ffi); if(!err) { err = lookup_path(req_,hdr_->nodeid,name,fusepath,&e,&ffi,false); if(err) { f.ops.release(&req_->ctx, &ffi); } else if(!S_ISREG(e.attr.st_mode)) { err = -EIO; f.ops.release(&req_->ctx, &ffi); forget_node(e.ino,1); } } } if(!err) { free_path_and_inc_open(hdr_->nodeid,fusepath,e.ino,true); fusepath = NULL; if(fuse_reply_create(req_,&e,&ffi) == -ENOENT) { /* The open syscall was interrupted,so it must be cancelled */ fuse_do_release(&req_->ctx,e.ino,&ffi); forget_node(e.ino,1); } } else { forget_node(new_nodeid,1); fuse_reply_err(req_,err); } free_path(hdr_->nodeid,fusepath); } static int fuse_flush_common(fuse_req_t *req_, fuse_file_info_t *ffi_) { return f.ops.flush(&req_->ctx, ffi_); } static void fuse_lib_release(fuse_req_t *req_, struct fuse_in_header *hdr_) { int err = 0; fuse_file_info_t ffi = {}; struct fuse_release_in *arg; arg = (fuse_release_in*)fuse_hdr_arg(hdr_); ffi.fh = arg->fh; ffi.flags = arg->flags; if(req_->conn.proto_minor >= 8) { ffi.flush = !!(arg->release_flags & FUSE_RELEASE_FLUSH); ffi.lock_owner = arg->lock_owner; } else { ffi.flock_release = 1; ffi.lock_owner = arg->lock_owner; } if(ffi.flush) { err = fuse_flush_common(req_,&ffi); if(err == -ENOSYS) err = 0; } fuse_do_release(&req_->ctx, hdr_->nodeid, &ffi); fuse_reply_err(req_,err); } static void fuse_lib_flush(fuse_req_t *req_, struct fuse_in_header *hdr_) { int err; fuse_file_info_t ffi = {}; struct fuse_flush_in *arg; arg = (fuse_flush_in*)fuse_hdr_arg(hdr_); ffi.fh = arg->fh; ffi.flush = 1; if(req_->conn.proto_minor >= 7) ffi.lock_owner = arg->lock_owner; err = fuse_flush_common(req_,&ffi); fuse_reply_err(req_,err); } static void fuse_lib_bmap(fuse_req_t *req_, const struct fuse_in_header *hdr_) { int err; char *fusepath; uint64_t block; const struct fuse_bmap_in *arg; arg = (fuse_bmap_in*)fuse_hdr_arg(hdr_); block = arg->block; err = get_path(hdr_->nodeid,&fusepath); if(!err) { err = f.ops.bmap(&req_->ctx, &fusepath[1], arg->blocksize, &block); free_path(hdr_->nodeid,fusepath); } if(!err) fuse_reply_bmap(req_,block); else fuse_reply_err(req_,err); } static void fuse_lib_ioctl(fuse_req_t *req_, const struct fuse_in_header *hdr_) { int err; char *out_buf = NULL; fuse_dh *dh; fuse_file_info_t ffi = {}; fuse_file_info_t llffi = {}; const void *in_buf; uint32_t out_size; const struct fuse_ioctl_in *arg; arg = (fuse_ioctl_in*)fuse_hdr_arg(hdr_); err = -EPERM; if(arg->flags & FUSE_IOCTL_UNRESTRICTED) goto err; if((sizeof(void*) == 4) && (req_->conn.proto_minor >= 16) && !(arg->flags & FUSE_IOCTL_32BIT)) { req_->ioctl_64bit = 1; } llffi.fh = arg->fh; out_size = arg->out_size; in_buf = (arg->in_size ? PARAM(arg) : NULL); if(arg->flags & FUSE_IOCTL_DIR) { dh = get_dirhandle(&llffi,&ffi); if(not dh) { err = -EBADF; goto err; } } else { ffi = llffi; } if((arg->in_size > 0) && (out_size > 0) && (arg->in_size != out_size)) { err = -EINVAL; goto err; } if(out_size) { err = -ENOMEM; out_buf = (char*)malloc(out_size); if(!out_buf) goto err; } if(in_buf && out_buf) memcpy(out_buf,in_buf,out_size); err = f.ops.ioctl(&req_->ctx, &ffi, arg->cmd, (void*)(uintptr_t)arg->arg, arg->flags, out_buf ? out_buf : (void*)in_buf, &out_size); if(err < 0) goto err; fuse_reply_ioctl(req_,err,out_buf,out_size); goto out; err: fuse_reply_err(req_,err); out: free(out_buf); } static void fuse_lib_poll(fuse_req_t *req_, const struct fuse_in_header *hdr_) { int err; unsigned revents = 0; fuse_file_info_t ffi = {}; fuse_pollhandle_t *ph = NULL; const struct fuse_poll_in *arg; arg = (fuse_poll_in*)fuse_hdr_arg(hdr_); ffi.fh = arg->fh; if(arg->flags & FUSE_POLL_SCHEDULE_NOTIFY) { ph = (fuse_pollhandle_t*)malloc(sizeof(fuse_pollhandle_t)); if(ph == NULL) { fuse_reply_err(req_,ENOMEM); return; } ph->kh = arg->kh; ph->se = req_->se; } err = f.ops.poll(&req_->ctx, &ffi, ph, &revents); if(!err) fuse_reply_poll(req_,revents); else fuse_reply_err(req_,err); } static void fuse_lib_fallocate(fuse_req_t *req_, const struct fuse_in_header *hdr_) { int err; const struct fuse_fallocate_in *arg; arg = (fuse_fallocate_in*)fuse_hdr_arg(hdr_); err = f.ops.fallocate(&req_->ctx, arg->fh, arg->mode, arg->offset, arg->length); fuse_reply_err(req_,err); } static struct fuse_lowlevel_ops fuse_path_ops = { .access = fuse_lib_access, .bmap = fuse_lib_bmap, .copy_file_range = fuse_lib_copy_file_range, .create = fuse_lib_create, .destroy = fuse_lib_destroy, .fallocate = fuse_lib_fallocate, .flush = fuse_lib_flush, .forget = fuse_lib_forget, .forget_multi = fuse_lib_forget_multi, .fsync = fuse_lib_fsync, .fsyncdir = fuse_lib_fsyncdir, .getattr = fuse_lib_getattr, .getxattr = fuse_lib_getxattr, .init = fuse_lib_init, .ioctl = fuse_lib_ioctl, .link = fuse_lib_link, .listxattr = fuse_lib_listxattr, .lookup = fuse_lib_lookup, .lseek = fuse_lib_lseek, .mkdir = fuse_lib_mkdir, .mknod = fuse_lib_mknod, .open = fuse_lib_open, .opendir = fuse_lib_opendir, .poll = fuse_lib_poll, .read = fuse_lib_read, .readdir = fuse_lib_readdir, .readdir_plus = fuse_lib_readdir_plus, .readlink = fuse_lib_readlink, .release = fuse_lib_release, .releasedir = fuse_lib_releasedir, .removemapping = fuse_lib_removemapping, .removexattr = fuse_lib_removexattr, .rename = fuse_lib_rename, .rename2 = fuse_lib_rename2, .retrieve_reply = fuse_lib_retrieve_reply, .rmdir = fuse_lib_rmdir, .setattr = fuse_lib_setattr, .setupmapping = fuse_lib_setupmapping, .setxattr = fuse_lib_setxattr, .statfs = fuse_lib_statfs, .statx = fuse_lib_statx, .symlink = fuse_lib_symlink, .syncfs = fuse_lib_syncfs, .tmpfile = fuse_lib_tmpfile, .unlink = fuse_lib_unlink, .write = fuse_lib_write, }; int fuse_notify_poll(fuse_pollhandle_t *ph) { return fuse_lowlevel_notify_poll(ph); } int fuse_exited() { return fuse_session_exited(f.se); } struct fuse_session* fuse_get_session() { return f.se; } void fuse_exit() { f.se->exited = 1; } static int node_table_init(struct node_table *t) { t->size = NODE_TABLE_MIN_SIZE; t->array = (node_t **)calloc(1,sizeof(node_t *) * t->size); if(t->array == NULL) { fprintf(stderr,"fuse: memory allocation failed\n"); return -1; } t->use = 0; t->split = 0; return 0; } static void metrics_log_nodes_info(FILE *file_) { char buf[1024]; char time_str[64]; struct tm tm; struct timeval tv; uint64_t sizeof_node; gettimeofday(&tv,NULL); localtime_r(&tv.tv_sec,&tm); strftime(time_str,sizeof(time_str),"%Y-%m-%dT%H:%M:%S.000%z",&tm); sizeof_node = sizeof(node_t); snprintf(buf,sizeof(buf), "time: %s\n" "sizeof(node): %" PRIu64 "\n" "node id_table size: %" PRIu64 "\n" "node id_table usage: %" PRIu64 "\n" "node id_table total allocated memory: %" PRIu64 "\n" "node name_table size: %" PRIu64 "\n" "node name_table usage: %" PRIu64 "\n" "node name_table total allocated memory: %" PRIu64 "\n" "msgbuf bufsize: %" PRIu64 "\n" "msgbuf allocation count: %" PRIu64 "\n" "msgbuf total allocated memory: %" PRIu64 "\n" "\n" , time_str, sizeof_node, (uint64_t)f.id_table.size, (uint64_t)f.id_table.use, (uint64_t)(f.id_table.size * sizeof(node_t*)), (uint64_t)f.name_table.size, (uint64_t)f.name_table.use, (uint64_t)(f.name_table.size * sizeof(node_t*)), msgbuf_get_bufsize(), msgbuf_alloc_count(), msgbuf_alloc_count() * msgbuf_get_bufsize() ); fputs(buf,file_); } static void metrics_log_nodes_info_to_tmp_dir(struct fuse *f_) { int rv; FILE *file; char filepath[256]; struct stat st; char const *mode = "a"; snprintf(filepath, sizeof(filepath), "/tmp/mergerfs.%d.info", getpid()); rv = lstat(filepath,&st); if((rv == 0) && (st.st_size > INFO_FILE_MAX_SIZE)) mode = "w"; file = fopen(filepath,mode); if(file == NULL) return; metrics_log_nodes_info(file); fclose(file); } static void fuse_malloc_trim(void) { #ifdef __GLIBC__ malloc_trim(1024 * 1024); #endif } void fuse_invalidate_all_nodes() { std::vector names; mutex_lock(f.lock); for(size_t i = 0; i < f.id_table.size; i++) { node_t *node; for(node = f.id_table.array[i]; node != NULL; node = node->id_next) { if(node->nodeid == FUSE_ROOT_ID) continue; if(node->name == NULL) continue; if(node->parent == NULL) continue; if(node->parent->nodeid != FUSE_ROOT_ID) continue; names.emplace_back(node->name); } } mutex_unlock(f.lock); SysLog::info("invalidating {} file entries", names.size()); for(auto &name : names) { fuse_lowlevel_notify_inval_entry(f.se, FUSE_ROOT_ID, name.c_str(), name.size()); } } void fuse_gc() { SysLog::info("running thorough garbage collection"); node_clear(); msgbuf_clear(); fuse_malloc_trim(); } void fuse_gc1() { SysLog::info("running basic garbage collection"); node_gc(); msgbuf_gc(); fuse_malloc_trim(); } void fuse_populate_maintenance_thread(struct fuse *f_) { MaintenanceThread::push_job([](u64 count_) { if((count_ % 15) == 0) fuse_gc1(); }); MaintenanceThread::push_job([=](u64 count_) { if(fuse_cfg.debug) metrics_log_nodes_info_to_tmp_dir(f_); }); } struct fuse* fuse_new(int fd_, size_t bufsize_, struct fuse_args *args, const struct fuse_operations *ops_) { node_t *root; struct fuse_lowlevel_ops llop = fuse_path_ops; f.ops = *ops_; f.se = fuse_lowlevel_new_common(args,&llop,sizeof(llop)); if(f.se == NULL) goto out_free_fs; /* Inlined channel fields - direct assignment */ f.se->fd = fd_; f.se->bufsize = bufsize_; /* Trace topmost layer by default */ srand(time(NULL)); f.nodeid_gen.nodeid = FUSE_ROOT_ID; f.nodeid_gen.generation = rand64(); if(node_table_init(&f.name_table) == -1) goto out_free_session; if(node_table_init(&f.id_table) == -1) goto out_free_name_table; mutex_init(f.lock); root = node_alloc(); if(root == NULL) { fprintf(stderr,"mergerfs: root node alloc failed\n"); goto out_free_id_table; } root->name = xstrdup("/"); root->parent = NULL; root->nodeid = FUSE_ROOT_ID; inc_nlookup(root); hash_id(root); return &f; out_free_id_table: free(f.id_table.array); out_free_name_table: free(f.name_table.array); out_free_session: fuse_session_destroy(f.se); out_free_fs: /* Horrible compatibility hack to stop the destructor from being called on the filesystem without init being called first */ f.ops.destroy = NULL; return NULL; } void fuse_destroy(struct fuse *) { size_t i; for(i = 0; i < f.id_table.size; i++) { node_t *node; node_t *next; for(node = f.id_table.array[i]; node != NULL; node = next) { next = node->id_next; free_node(node); f.id_table.use--; } } free(f.id_table.array); free(f.name_table.array); mutex_destroy(f.lock); fuse_session_destroy(f.se); } int fuse_passthrough_open(const int fd_) { int rv; int dev_fuse_fd; struct fuse_backing_map bm = {}; dev_fuse_fd = f.se->fd; bm.fd = fd_; rv = ::ioctl(dev_fuse_fd,FUSE_DEV_IOC_BACKING_OPEN,&bm); return ((rv < 0) ? INVALID_BACKING_ID : rv); } int fuse_passthrough_close(const int backing_id_) { int dev_fuse_fd; if(!fuse_backing_id_is_valid(backing_id_)) return 0; dev_fuse_fd = f.se->fd; return ::ioctl(dev_fuse_fd,FUSE_DEV_IOC_BACKING_CLOSE,&backing_id_); }