/*
  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 "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;
  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
free_path(uint64_t  nodeid,
          char     *path)
{
  if(path)
    free_path_wrlock(nodeid,NULL,path);
}

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
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
int
set_path_info(uint64_t                 nodeid,
              const char              *name,
              struct fuse_entry_param *e,
              bool                     remember = true)
{
  node_t *node;

  node = find_node(nodeid,name,remember);
  if(node == NULL)
    return -ENOMEM;

  e->ino        = node->nodeid;
  e->generation = ((e->ino == FUSE_ROOT_ID) ? 0 : f.nodeid_gen.generation);

  mutex_lock(f.lock);
  update_stat(node,&e->attr);
  mutex_unlock(f.lock);

  set_stat(e->ino,&e->attr);

  return 0;
}

/*
  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)
    {
      if(fusepath != NULL)
        err = f.ops.getattr(&req_->ctx,&fusepath[1],&buf,&timeouts);
      else
        err = f.ops.fgetattr(&req_->ctx,fh,&buf,&timeouts);

      free_path(hdr_->nodeid,fusepath);
    }

  if(!err)
    {
      {
        mutex_lockguard(f.lock);
        node = get_node(hdr_->nodeid);
        if(node == NULL)
          {
            fuse_reply_err(req_,ESTALE);
            return;
          }
        update_stat(node,&buf);
      }
      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<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 = ((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));

      free_path(hdr_->nodeid,fusepath);
    }

  if(!err)
    {
      {
        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);
    }
}

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)
    {
      mutex_lock(f.lock);
      node_t *node = get_node(e.ino);
      if(node)
        {
          if(remember_nodes())
            remember_node(node);
          node->open_count++;
        }
      mutex_unlock(f.lock);

      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)
    {
      mutex_lock(f.lock);
      node_t *node = get_node(hdr_->nodeid);
      if(node)
        node->open_count++;
      mutex_unlock(f.lock);
      /* 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;
  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);

  /* Done to keep race condition between last readdir reply and the unlock */
  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)
    {
      mutex_lock(f.lock);
      node_t *node = get_node(e.ino);
      if(node)
        {
          if(remember_nodes())
            remember_node(node);
          node->open_count++;
        }
      mutex_unlock(f.lock);

      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<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_);
}
