Files
Antonio S. Musumeci 947024914a Replace std::filesystem::path with custom fs::relpath
std::filesystem::path is wrong for mergerfs's hot path:
- `branch->path / fusepath_` heap-allocates per FUSE op (3 allocs each
  on libstdc++; the SSO buffer is 15 bytes and any joined path exceeds it).
- The implicit `operator string_type()` adds another std::string when
  passing to `fs::*` wrappers that take `const std::string&`.
- Per-branch loops rebuild fullpath from scratch every iteration.

The new fs::relpath is a fixed 8192-byte (2 * PATH_MAX) inline buffer,
NUL-terminated, never heap-allocates. The rel data is anchored at offset
REL_OFFSET = 4096; the 4096 bytes before the anchor form the "prefix
area" so a long base/prefix can be written there in place via
set_prefix() without disturbing the rel. The remaining ~4096 bytes
after the rel are available for trailing append (filenames in
readdir-style loops, etc.).

set_prefix's cost is O(prefix size) -- only the actually-used bytes
are touched -- so the buffer size is a stack-footprint question, not a
hot-path cost. FUSE worker threads use the pthread default stack
(~8 MB on Linux); a 4-relpath frame in fuse_link/fuse_rename is 32 KB,
~0.4% of the stack budget.

The per-branch hot loop:

  fs::relpath fullpath = fusepath_;       // one copy of the rel
  for (auto branch : branches) {
    fullpath.set_prefix(branch->path);   // writes only [start, 4096)
    fs::syscall(fullpath);
  }

set_prefix only rewrites the prefix area; the rel stays anchored. A
cached _rel_size means no recomputation across iterations.

Microbenchmark (8 branches, 30-char fusepath, -O3, 4 M path builds):

  variant                                 ns/build   allocs/build
  --------------------------------------  --------   ------------
  origin/master (std::filesystem::path)   158        3.00
  fs::relpath assign_concat (single-shot) 9.1        0
  fs::relpath set_prefix (loop-optimized) 4.5        0

35x faster, zero allocations in the hot path.

Contract: paths flow through fs::relpath in canonical form -- prefix
without trailing '/', rel without leading '/'. No sanitization;
violations yield double slashes. Mergerfs satisfies this by
construction:
- libfuse's try_get_path() walks leaf-to-root via prepend(), then
  calls consolidate_to_rel(strip=true) which drops the leading '/'
  that prepend produces.
- branch paths are realpath-canonicalized at config load so they
  never have a trailing slash.

Mergerfs has exactly one path-manipulation type: a relative fusepath.
Branch paths and other absolute paths are stored as std::string -- they
are spliced into the prefix area of an fs::relpath at syscall time but
are never themselves mutated as path objects.

Major changes:
- vendored/libfuse/{include/fs_path.hpp,lib/fs_path.cpp}: new
  fs::relpath class (8 KB inline, REL anchor at 4096, set_prefix /
  clear_prefix / consolidate_to_rel, _rel_size cache, self-aliasing-
  safe assign_concat, sticky ENAMETOOLONG via errnum()).
- 30+ FUSE-op call sites converted from `fullpath = branch / fusepath`
  to the `set_prefix` loop pattern (fuse_unlink, fuse_chmod,
  fuse_chown, fuse_listxattr, fuse_truncate, fuse_utimens,
  fuse_setxattr, fuse_rmdir, fuse_removexattr, fuse_link's
  preserve-path and create-path loops, fuse_rename's create-path
  and preserve-path loops, fuse_statfs FULL mode, fs::findallfiles,
  fs::findonfs).
- fs::* syscall wrappers that previously only took
  `const std::string&` got `const char*` overloads so fs::relpath's
  implicit `operator const char*()` lands cleanly (fs::link,
  fs::lutimens, fs::umount_lazy, fs::open_dir_ro, fs::readlink,
  fs::lremovexattr, fs::lstatvfs, fs::info, fs::statvfs_cache).
- fs::exists gained a (fs::relpath&, basepath, ...) overload that
  calls set_prefix internally so per-branch policy fan-out loops
  share a single fullpath, rewriting only the prefix area on each
  iteration.
- All policies (epall, epff, eplfs, eplus, epmfs, eppfrd, ff, lup,
  msplfs, msplus, mspmfs, msppfrd, newest) refactored to hoist the
  fs::relpath fullpath construction out of the per-branch loop and
  use the set_prefix-based fs::exists overload.
- fs::inode::ReaddirCalc::_fusepath migrated from std::string to
  fs::relpath with the same resize/append API.
- std::filesystem removed entirely from the project: branches.cpp
  and option_parser.cpp's exists/is_directory/equivalent calls now
  use direct ::stat(); mergerfs_fsck.cpp's recursive_directory_iterator
  was replaced with an opendir-based walk.

Tests (tests/tests.cpp): 27 new fs::relpath test cases covering
construction, set_prefix in/out of REL anchor, prefix()/rel()
accessors, clear_prefix, concat canonical form, self-aliasing
(`p /= "x"`), operator/=, resize zero-fill, swap, copy/move,
equality, implicit conversions, lexically_relative, remove_filename,
_rel_size invariant across every mutator, capacity bounds.

Full suite: 213/213 pass.
2026-05-23 21:15:09 -04:00

3655 lines
77 KiB
C++

/*
FUSE: Filesystem in Userspace
Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
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 "fs_path.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 <string>
#include <vector>
#include <assert.h>
#include <errno.h>
#include <fcntl.h>
#include <inttypes.h>
#include <limits.h>
#include <poll.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/file.h>
#include <sys/ioctl.h>
#include <sys/param.h>
#include <sys/time.h>
#include <sys/uio.h>
#include <time.h>
#include <unistd.h>
#ifdef __GLIBC__
#include <malloc.h>
#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;
// path1 / path2 are non-owning pointers to the caller's stack-
// allocated fs::relpath objects (try_get_path / try_get_path2 take
// a relpath* and fill it in place). Lifetime is bounded by the
// calling FUSE op, which blocks on `cond` until the lock holder
// signals -- the caller's stack frame is therefore guaranteed to
// outlive the queue entry. Do not copy a lock_queue_element to a
// longer-lived storage without reconsidering this invariant.
fs::relpath *path1;
node_t **wnode1;
uint64_t nodeid2;
const char *name2;
fs::relpath *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
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;
}
}
/*
Path storage is the caller's stack-allocated fs::relpath; this function
fills it via prepend() (walk leaf-to-root, prepending "/component").
No heap allocation per op.
Final step before returning: consolidate_to_rel(true) moves the data
to the rel anchor (REL_OFFSET) and strips the leading '/'. Callers
(f.ops.* callbacks) receive a REL-layout fs::relpath with no leading
slash, ready for set_prefix-style assembly in mergerfs's per-branch
loops without further conversion.
*/
static
int
try_get_path(uint64_t nodeid,
const char *name,
fs::relpath *path,
node_t **wnodep,
bool need_lock)
{
node_t *node;
node_t *wnode = NULL;
int err;
path->clear();
if(name != NULL)
path->prepend(name);
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_err;
}
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;
path->prepend(node->name);
if(need_lock)
{
err = -EAGAIN;
if(node->treelock < 0)
goto out_unlock;
node->treelock++;
}
}
// Move data from the prefix area to the rel anchor and strip the
// leading '/' produced by prepend(). After this the path is in
// mergerfs's canonical REL form (no leading slash, anchored at
// REL_OFFSET).
path->consolidate_to_rel(/*strip_leading_slash=*/true);
if(wnodep)
*wnodep = wnode;
return 0;
out_unlock:
if(need_lock)
unlock_path(nodeid,wnode,node);
// Reset the path to empty on every error exit. This is load-bearing
// for callers that mask -ESTALE to 0 (statx_path, see fuse_lib_
// statx_path): a nameless node yields a partial prepend chain that
// must not be observed as a valid path. After reset, the
// dispatcher's "is the path empty?" test cleanly distinguishes
// (nodeid == FUSE_ROOT_ID) "root walk produced empty" from
// (nodeid != FUSE_ROOT_ID) "nameless / ESTALE-masked" without
// looking at `err`.
path->clear();
out_err:
return err;
}
static
int
try_get_path2(uint64_t nodeid1,
const char *name1,
uint64_t nodeid2,
const char *name2,
fs::relpath *path1,
fs::relpath *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);
// path1 storage is stack-owned by the caller; no free.
}
}
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,
fs::relpath *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,
fs::relpath *path)
{
return get_path_common(nodeid,NULL,path,NULL);
}
static
int
get_path_name(uint64_t nodeid,
const char *name,
fs::relpath *path)
{
return get_path_common(nodeid,name,path,NULL);
}
static
int
get_path_wrlock(uint64_t nodeid,
const char *name,
fs::relpath *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,
fs::relpath *path1,
fs::relpath *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)
{
mutex_lock(f.lock);
unlock_path(nodeid,wnode,NULL);
if(f.lockq)
wake_up_queued();
mutex_unlock(f.lock);
}
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,
const fs::relpath *path)
{
// The caller's stack-owned fs::relpath is empty when no get_path*()
// succeeded for this op (or when an intermediate "free + reuse"
// step like free_path_and_inc_open already released the read locks
// and clear()d the path). In either case there is nothing to do.
if(path->empty())
return;
free_path_wrlock(nodeid,NULL);
}
static
node_t*
free_path_and_inc_open(u64 free_nodeid_,
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);
return node;
}
static
bool
free_path_and_update_stat(u64 nodeid_,
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);
return ok;
}
static
void
free_path2(uint64_t nodeid1,
uint64_t nodeid2,
node_t *wnode1,
node_t *wnode2)
{
mutex_lock(f.lock);
unlock_path(nodeid1,wnode1,NULL);
unlock_path(nodeid2,wnode2,NULL);
wake_up_queued();
mutex_unlock(f.lock);
}
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 fs::relpath &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,&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;
fs::relpath 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;
fs::relpath 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.clear();
if(fh == 0)
{
err = get_path(hdr_->nodeid,&fusepath);
if(err == -ESTALE) // unlinked but open
err = 0;
}
if(!err)
{
bool node_ok = true;
// Three-way dispatch:
// - explicit fh (fh != 0) -> fgetattr(fh)
// - empty fusepath, not FUSE_ROOT_ID -> nameless node
// (ESTALE was masked
// above): fall back
// to fgetattr(0) so
// mergerfs can find
// the file via
// OpenFiles.
// - otherwise -> path-based getattr,
// including
// FUSE_ROOT_ID where
// the rel is empty.
const bool path_based =
(fh == 0) &&
(!fusepath.empty() || hdr_->nodeid == FUSE_ROOT_ID);
if(path_based)
{
err = f.ops.getattr(&req_->ctx,fusepath,&buf,&timeouts);
if(!err)
node_ok = free_path_and_update_stat(hdr_->nodeid,&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);
}
// get_path may have run and produced an empty path on the
// ESTALE-masked nameless branch; release any state it left.
if(fh == 0)
free_path(hdr_->nodeid,&fusepath);
}
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;
fs::relpath 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;
}
// Three cases for an empty fusepath after get_path:
// 1. nodeid == FUSE_ROOT_ID, walk produced 0 components -- a real
// path query for the mountpoint root.
// 2. nodeid != FUSE_ROOT_ID, ESTALE was masked above -- a nameless
// node (file held open after unlink). The named-path lookup
// cannot resolve it; fall back to fh-based statx, which lets
// mergerfs find the file via OpenFiles by its FUSE handle.
// 3. nodeid != FUSE_ROOT_ID, success -- the path is actually
// empty only if the call returned 0 with empty(); the only
// such path is FUSE_ROOT_ID, so this case does not arise.
// try_get_path()'s error paths now reset the path to empty, so
// case 2 always observably produces an empty fusepath -- the
// (nodeid, empty) pair uniquely identifies the nameless case.
if(!fusepath.empty() || hdr_->nodeid == FUSE_ROOT_ID)
err = f.ops.statx(&req_->ctx,
fusepath,
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 = {};
fs::relpath 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.clear();
if(fh == 0)
{
err = get_path(hdr_->nodeid,&fusepath);
if(err == -ESTALE)
err = 0;
}
if(!err)
{
// Three-way dispatch (see fuse_lib_getattr for full notes):
// - explicit fh -> fXXX(fh)
// - empty fusepath, not FUSE_ROOT_ID -> nameless node
// (ESTALE was masked):
// fall back to fXXX(0)
// - otherwise -> path-based op
const bool path_based =
(fh == 0) &&
(!fusepath.empty() || hdr_->nodeid == FUSE_ROOT_ID);
err = 0;
if(!err && (arg->valid & FATTR_MODE))
err = (path_based ?
f.ops.chmod(&req_->ctx,fusepath,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 = (path_based ?
f.ops.chown(&req_->ctx,fusepath,uid,gid) :
f.ops.fchown(&req_->ctx,fh,uid,gid));
}
if(!err && (arg->valid & FATTR_SIZE))
err = (path_based ?
f.ops.truncate(&req_->ctx,fusepath,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<time_t>(arg->atime), static_cast<long>(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<time_t>(arg->mtime), static_cast<long>(arg->mtimensec) };
err = (path_based ?
f.ops.utimens(&req_->ctx,fusepath,tv) :
f.ops.futimens(&req_->ctx,fh,tv));
}
if(!err)
err = (path_based ?
f.ops.getattr(&req_->ctx,fusepath,&stbuf,&timeouts) :
f.ops.fgetattr(&req_->ctx,fh,&stbuf,&timeouts));
bool stat_updated = false;
// Only the path-based branch held a path lock that needs
// unlock_path; the fh-based branch (incl. nameless fallback)
// never acquired one. free_path() handles both cases on the
// shared cleanup edge below.
if(!err && path_based)
{
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);
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;
fs::relpath 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,
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;
fs::relpath fusepath;
char linkname[PATH_MAX + 1];
rv = get_path(hdr_->nodeid,&fusepath);
if(!rv)
{
rv = f.ops.readlink(&req_->ctx,
fusepath,
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;
fs::relpath 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,
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,
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;
fs::relpath 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,
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;
fs::relpath 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);
if(!err)
remove_node(hdr_->nodeid,name);
free_path_wrlock(hdr_->nodeid,wnode);
}
fuse_reply_err(req_,err);
}
static
void
fuse_lib_rmdir(fuse_req_t *req_,
struct fuse_in_header *hdr_)
{
int err;
fs::relpath 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);
if(!err)
remove_node(hdr_->nodeid,name);
free_path_wrlock(hdr_->nodeid,wnode);
}
fuse_reply_err(req_,err);
}
static
void
fuse_lib_symlink(fuse_req_t *req_,
struct fuse_in_header *hdr_)
{
int rv;
fs::relpath 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,
&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;
fs::relpath oldpath;
fs::relpath 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,
newpath);
if(!err)
err = rename_node(hdr_->nodeid,oldname,arg->newdir,newname);
free_path2(hdr_->nodeid,arg->newdir,wnode1,wnode2);
}
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;
fs::relpath oldpath;
fs::relpath 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,
newpath,
&e.attr,
&e.timeout);
if(rv == 0)
rv = set_path_info(hdr_->nodeid,newname,&e);
free_path2(arg->oldnodeid,hdr_->nodeid,NULL,NULL);
}
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;
fs::relpath 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,
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,e.ino,true);
fusepath.clear();
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;
fs::relpath 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,
&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,hdr_->nodeid,false);
fusepath.clear();
/* 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(fuse_req_t *req_,
struct fuse_in_header *hdr_)
{
int res;
fuse_file_info_t ffi = {};
struct fuse_read_in *arg;
fuse_msgbuf_t *msgbuf;
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;
}
msgbuf = msgbuf_alloc_page_aligned();
if(msgbuf == NULL)
{
fuse_reply_err(req_,ENOMEM);
return;
}
res = f.ops.read(&req_->ctx,
&ffi,
msgbuf->mem,
arg->size,
arg->offset);
if(res >= 0)
fuse_reply_data(req_,msgbuf->mem,res);
else
fuse_reply_err(req_,res);
msgbuf_free(msgbuf);
}
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;
fs::relpath 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,
&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;
fs::relpath fusepath;
struct statvfs buf = {};
if(hdr_->nodeid)
err = get_path(hdr_->nodeid,&fusepath);
if(!err)
{
err = f.ops.statfs(&req_->ctx,
fusepath,
&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;
fs::relpath 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,
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;
fs::relpath fusepath;
err = get_path(ino,&fusepath);
if(!err)
{
err = f.ops.getxattr(&req_->ctx,
fusepath,
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;
fs::relpath fusepath;
err = get_path(ino,&fusepath);
if(!err)
{
err = f.ops.listxattr(&req_->ctx,
fusepath,
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;
fs::relpath 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,
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;
fs::relpath 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,
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,e.ino,true);
fusepath.clear();
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;
fs::relpath 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,
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<std::string> 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_);
}