"Fossies" - the Fresh Open Source Software Archive

Member "fuse-3.10.4/example/passthrough_hp.cc" (9 Jun 2021, 35202 Bytes) of package /linux/misc/fuse-3.10.4.tar.xz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) C and C++ source code syntax highlighting (style: standard) with prefixed line numbers and code folding option. Alternatively you can here view or download the uninterpreted source code file. See also the latest Fossies "Diffs" side-by-side code changes report for "passthrough_hp.cc": 3.10.3_vs_3.10.4.

    1 /*
    2   FUSE: Filesystem in Userspace
    3   Copyright (C) 2001-2007  Miklos Szeredi <miklos@szeredi.hu>
    4   Copyright (C) 2017       Nikolaus Rath <Nikolaus@rath.org>
    5   Copyright (C) 2018       Valve, Inc
    6 
    7   This program can be distributed under the terms of the GNU GPLv2.
    8   See the file COPYING.
    9 */
   10 
   11 /** @file
   12  *
   13  * This is a "high-performance" version of passthrough_ll.c. While
   14  * passthrough_ll.c is designed to be as simple as possible, this
   15  * example intended to be as efficient and correct as possible.
   16  *
   17  * passthrough_hp.cc mirrors a specified "source" directory under a
   18  * specified the mountpoint with as much fidelity and performance as
   19  * possible.
   20  *
   21  * If --nocache is specified, the source directory may be changed
   22  * directly even while mounted and the filesystem will continue
   23  * to work correctly.
   24  *
   25  * Without --nocache, the source directory is assumed to be modified
   26  * only through the passthrough filesystem. This enables much better
   27  * performance, but if changes are made directly to the source, they
   28  * may not be immediately visible under the mountpoint and further
   29  * access to the mountpoint may result in incorrect behavior,
   30  * including data-loss.
   31  *
   32  * On its own, this filesystem fulfills no practical purpose. It is
   33  * intended as a template upon which additional functionality can be
   34  * built.
   35  *
   36  * Unless --nocache is specified, is only possible to write to files
   37  * for which the mounting user has read permissions. This is because
   38  * the writeback cache requires the kernel to be able to issue read
   39  * requests for all files (which the passthrough filesystem cannot
   40  * satisfy if it can't read the file in the underlying filesystem).
   41  *
   42  * ## Source code ##
   43  * \include passthrough_hp.cc
   44  */
   45 
   46 #define FUSE_USE_VERSION 35
   47 
   48 #ifdef HAVE_CONFIG_H
   49 #include "config.h"
   50 #endif
   51 
   52 #ifndef _GNU_SOURCE
   53 #define _GNU_SOURCE
   54 #endif
   55 
   56 // C includes
   57 #include <dirent.h>
   58 #include <err.h>
   59 #include <errno.h>
   60 #include <ftw.h>
   61 #include <fuse_lowlevel.h>
   62 #include <inttypes.h>
   63 #include <string.h>
   64 #include <sys/file.h>
   65 #include <sys/resource.h>
   66 #include <sys/xattr.h>
   67 #include <time.h>
   68 #include <unistd.h>
   69 #include <pthread.h>
   70 #include <limits.h>
   71 
   72 // C++ includes
   73 #include <cstddef>
   74 #include <cstdio>
   75 #include <cstdlib>
   76 #include <list>
   77 #include "cxxopts.hpp"
   78 #include <mutex>
   79 #include <fstream>
   80 #include <thread>
   81 #include <iomanip>
   82 
   83 using namespace std;
   84 
   85 /* We are re-using pointers to our `struct sfs_inode` and `struct
   86    sfs_dirp` elements as inodes and file handles. This means that we
   87    must be able to store pointer a pointer in both a fuse_ino_t
   88    variable and a uint64_t variable (used for file handles). */
   89 static_assert(sizeof(fuse_ino_t) >= sizeof(void*),
   90               "void* must fit into fuse_ino_t");
   91 static_assert(sizeof(fuse_ino_t) >= sizeof(uint64_t),
   92               "fuse_ino_t must be at least 64 bits");
   93 
   94 
   95 /* Forward declarations */
   96 struct Inode;
   97 static Inode& get_inode(fuse_ino_t ino);
   98 static void forget_one(fuse_ino_t ino, uint64_t n);
   99 
  100 // Uniquely identifies a file in the source directory tree. This could
  101 // be simplified to just ino_t since we require the source directory
  102 // not to contain any mountpoints. This hasn't been done yet in case
  103 // we need to reconsider this constraint (but relaxing this would have
  104 // the drawback that we can no longer re-use inode numbers, and thus
  105 // readdir() would need to do a full lookup() in order to report the
  106 // right inode number).
  107 typedef std::pair<ino_t, dev_t> SrcId;
  108 
  109 // Define a hash function for SrcId
  110 namespace std {
  111     template<>
  112     struct hash<SrcId> {
  113         size_t operator()(const SrcId& id) const {
  114             return hash<ino_t>{}(id.first) ^ hash<dev_t>{}(id.second);
  115         }
  116     };
  117 }
  118 
  119 // Maps files in the source directory tree to inodes
  120 typedef std::unordered_map<SrcId, Inode> InodeMap;
  121 
  122 struct Inode {
  123     int fd {-1};
  124     dev_t src_dev {0};
  125     ino_t src_ino {0};
  126     uint64_t nlookup {0};
  127     std::mutex m;
  128 
  129     // Delete copy constructor and assignments. We could implement
  130     // move if we need it.
  131     Inode() = default;
  132     Inode(const Inode&) = delete;
  133     Inode(Inode&& inode) = delete;
  134     Inode& operator=(Inode&& inode) = delete;
  135     Inode& operator=(const Inode&) = delete;
  136 
  137     ~Inode() {
  138         if(fd > 0)
  139             close(fd);
  140     }
  141 };
  142 
  143 struct Fs {
  144     // Must be acquired *after* any Inode.m locks.
  145     std::mutex mutex;
  146     InodeMap inodes; // protected by mutex
  147     Inode root;
  148     double timeout;
  149     bool debug;
  150     std::string source;
  151     size_t blocksize;
  152     dev_t src_dev;
  153     bool nosplice;
  154     bool nocache;
  155 };
  156 static Fs fs{};
  157 
  158 
  159 #define FUSE_BUF_COPY_FLAGS                      \
  160         (fs.nosplice ?                           \
  161             FUSE_BUF_NO_SPLICE :                 \
  162             static_cast<fuse_buf_copy_flags>(0))
  163 
  164 
  165 static Inode& get_inode(fuse_ino_t ino) {
  166     if (ino == FUSE_ROOT_ID)
  167         return fs.root;
  168 
  169     Inode* inode = reinterpret_cast<Inode*>(ino);
  170     if(inode->fd == -1) {
  171         cerr << "INTERNAL ERROR: Unknown inode " << ino << endl;
  172         abort();
  173     }
  174     return *inode;
  175 }
  176 
  177 
  178 static int get_fs_fd(fuse_ino_t ino) {
  179     int fd = get_inode(ino).fd;
  180     return fd;
  181 }
  182 
  183 
  184 static void sfs_init(void *userdata, fuse_conn_info *conn) {
  185     (void)userdata;
  186     if (conn->capable & FUSE_CAP_EXPORT_SUPPORT)
  187         conn->want |= FUSE_CAP_EXPORT_SUPPORT;
  188 
  189     if (fs.timeout && conn->capable & FUSE_CAP_WRITEBACK_CACHE)
  190         conn->want |= FUSE_CAP_WRITEBACK_CACHE;
  191 
  192     if (conn->capable & FUSE_CAP_FLOCK_LOCKS)
  193         conn->want |= FUSE_CAP_FLOCK_LOCKS;
  194 
  195     // Use splicing if supported. Since we are using writeback caching
  196     // and readahead, individual requests should have a decent size so
  197     // that splicing between fd's is well worth it.
  198     if (conn->capable & FUSE_CAP_SPLICE_WRITE && !fs.nosplice)
  199         conn->want |= FUSE_CAP_SPLICE_WRITE;
  200     if (conn->capable & FUSE_CAP_SPLICE_READ && !fs.nosplice)
  201         conn->want |= FUSE_CAP_SPLICE_READ;
  202 }
  203 
  204 
  205 static void sfs_getattr(fuse_req_t req, fuse_ino_t ino, fuse_file_info *fi) {
  206     (void)fi;
  207     Inode& inode = get_inode(ino);
  208     struct stat attr;
  209     auto res = fstatat(inode.fd, "", &attr,
  210                        AT_EMPTY_PATH | AT_SYMLINK_NOFOLLOW);
  211     if (res == -1) {
  212         fuse_reply_err(req, errno);
  213         return;
  214     }
  215     fuse_reply_attr(req, &attr, fs.timeout);
  216 }
  217 
  218 
  219 static void do_setattr(fuse_req_t req, fuse_ino_t ino, struct stat *attr,
  220                        int valid, struct fuse_file_info* fi) {
  221     Inode& inode = get_inode(ino);
  222     int ifd = inode.fd;
  223     int res;
  224 
  225     if (valid & FUSE_SET_ATTR_MODE) {
  226         if (fi) {
  227             res = fchmod(fi->fh, attr->st_mode);
  228         } else {
  229             char procname[64];
  230             sprintf(procname, "/proc/self/fd/%i", ifd);
  231             res = chmod(procname, attr->st_mode);
  232         }
  233         if (res == -1)
  234             goto out_err;
  235     }
  236     if (valid & (FUSE_SET_ATTR_UID | FUSE_SET_ATTR_GID)) {
  237         uid_t uid = (valid & FUSE_SET_ATTR_UID) ? attr->st_uid : static_cast<uid_t>(-1);
  238         gid_t gid = (valid & FUSE_SET_ATTR_GID) ? attr->st_gid : static_cast<gid_t>(-1);
  239 
  240         res = fchownat(ifd, "", uid, gid, AT_EMPTY_PATH | AT_SYMLINK_NOFOLLOW);
  241         if (res == -1)
  242             goto out_err;
  243     }
  244     if (valid & FUSE_SET_ATTR_SIZE) {
  245         if (fi) {
  246             res = ftruncate(fi->fh, attr->st_size);
  247         } else {
  248             char procname[64];
  249             sprintf(procname, "/proc/self/fd/%i", ifd);
  250             res = truncate(procname, attr->st_size);
  251         }
  252         if (res == -1)
  253             goto out_err;
  254     }
  255     if (valid & (FUSE_SET_ATTR_ATIME | FUSE_SET_ATTR_MTIME)) {
  256         struct timespec tv[2];
  257 
  258         tv[0].tv_sec = 0;
  259         tv[1].tv_sec = 0;
  260         tv[0].tv_nsec = UTIME_OMIT;
  261         tv[1].tv_nsec = UTIME_OMIT;
  262 
  263         if (valid & FUSE_SET_ATTR_ATIME_NOW)
  264             tv[0].tv_nsec = UTIME_NOW;
  265         else if (valid & FUSE_SET_ATTR_ATIME)
  266             tv[0] = attr->st_atim;
  267 
  268         if (valid & FUSE_SET_ATTR_MTIME_NOW)
  269             tv[1].tv_nsec = UTIME_NOW;
  270         else if (valid & FUSE_SET_ATTR_MTIME)
  271             tv[1] = attr->st_mtim;
  272 
  273         if (fi)
  274             res = futimens(fi->fh, tv);
  275         else {
  276 #ifdef HAVE_UTIMENSAT
  277             char procname[64];
  278             sprintf(procname, "/proc/self/fd/%i", ifd);
  279             res = utimensat(AT_FDCWD, procname, tv, 0);
  280 #else
  281             res = -1;
  282             errno = EOPNOTSUPP;
  283 #endif
  284         }
  285         if (res == -1)
  286             goto out_err;
  287     }
  288     return sfs_getattr(req, ino, fi);
  289 
  290 out_err:
  291     fuse_reply_err(req, errno);
  292 }
  293 
  294 
  295 static void sfs_setattr(fuse_req_t req, fuse_ino_t ino, struct stat *attr,
  296                         int valid, fuse_file_info *fi) {
  297     (void) ino;
  298     do_setattr(req, ino, attr, valid, fi);
  299 }
  300 
  301 
  302 static int do_lookup(fuse_ino_t parent, const char *name,
  303                      fuse_entry_param *e) {
  304     if (fs.debug)
  305         cerr << "DEBUG: lookup(): name=" << name
  306              << ", parent=" << parent << endl;
  307     memset(e, 0, sizeof(*e));
  308     e->attr_timeout = fs.timeout;
  309     e->entry_timeout = fs.timeout;
  310 
  311     auto newfd = openat(get_fs_fd(parent), name, O_PATH | O_NOFOLLOW);
  312     if (newfd == -1)
  313         return errno;
  314 
  315     auto res = fstatat(newfd, "", &e->attr, AT_EMPTY_PATH | AT_SYMLINK_NOFOLLOW);
  316     if (res == -1) {
  317         auto saveerr = errno;
  318         close(newfd);
  319         if (fs.debug)
  320             cerr << "DEBUG: lookup(): fstatat failed" << endl;
  321         return saveerr;
  322     }
  323 
  324     if (e->attr.st_dev != fs.src_dev) {
  325         cerr << "WARNING: Mountpoints in the source directory tree will be hidden." << endl;
  326         return ENOTSUP;
  327     } else if (e->attr.st_ino == FUSE_ROOT_ID) {
  328         cerr << "ERROR: Source directory tree must not include inode "
  329              << FUSE_ROOT_ID << endl;
  330         return EIO;
  331     }
  332 
  333     SrcId id {e->attr.st_ino, e->attr.st_dev};
  334     unique_lock<mutex> fs_lock {fs.mutex};
  335     Inode* inode_p;
  336     try {
  337         inode_p = &fs.inodes[id];
  338     } catch (std::bad_alloc&) {
  339         return ENOMEM;
  340     }
  341     e->ino = reinterpret_cast<fuse_ino_t>(inode_p);
  342     Inode& inode {*inode_p};
  343 
  344     if(inode.fd != -1) { // found existing inode
  345         fs_lock.unlock();
  346         if (fs.debug)
  347             cerr << "DEBUG: lookup(): inode " << e->attr.st_ino
  348                  << " (userspace) already known." << endl;
  349         lock_guard<mutex> g {inode.m};
  350         inode.nlookup++;
  351         close(newfd);
  352     } else { // no existing inode
  353         /* This is just here to make Helgrind happy. It violates the
  354            lock ordering requirement (inode.m must be acquired before
  355            fs.mutex), but this is of no consequence because at this
  356            point no other thread has access to the inode mutex */
  357         lock_guard<mutex> g {inode.m};
  358         inode.src_ino = e->attr.st_ino;
  359         inode.src_dev = e->attr.st_dev;
  360         inode.nlookup = 1;
  361         inode.fd = newfd;
  362         fs_lock.unlock();
  363 
  364         if (fs.debug)
  365             cerr << "DEBUG: lookup(): created userspace inode " << e->attr.st_ino
  366                  << endl;
  367     }
  368 
  369     return 0;
  370 }
  371 
  372 
  373 static void sfs_lookup(fuse_req_t req, fuse_ino_t parent, const char *name) {
  374     fuse_entry_param e {};
  375     auto err = do_lookup(parent, name, &e);
  376     if (err == ENOENT) {
  377         e.attr_timeout = fs.timeout;
  378         e.entry_timeout = fs.timeout;
  379         e.ino = e.attr.st_ino = 0;
  380         fuse_reply_entry(req, &e);
  381     } else if (err) {
  382         if (err == ENFILE || err == EMFILE)
  383             cerr << "ERROR: Reached maximum number of file descriptors." << endl;
  384         fuse_reply_err(req, err);
  385     } else {
  386         fuse_reply_entry(req, &e);
  387     }
  388 }
  389 
  390 
  391 static void mknod_symlink(fuse_req_t req, fuse_ino_t parent,
  392                               const char *name, mode_t mode, dev_t rdev,
  393                               const char *link) {
  394     int res;
  395     Inode& inode_p = get_inode(parent);
  396     auto saverr = ENOMEM;
  397 
  398     if (S_ISDIR(mode))
  399         res = mkdirat(inode_p.fd, name, mode);
  400     else if (S_ISLNK(mode))
  401         res = symlinkat(link, inode_p.fd, name);
  402     else
  403         res = mknodat(inode_p.fd, name, mode, rdev);
  404     saverr = errno;
  405     if (res == -1)
  406         goto out;
  407 
  408     fuse_entry_param e;
  409     saverr = do_lookup(parent, name, &e);
  410     if (saverr)
  411         goto out;
  412 
  413     fuse_reply_entry(req, &e);
  414     return;
  415 
  416 out:
  417     if (saverr == ENFILE || saverr == EMFILE)
  418         cerr << "ERROR: Reached maximum number of file descriptors." << endl;
  419     fuse_reply_err(req, saverr);
  420 }
  421 
  422 
  423 static void sfs_mknod(fuse_req_t req, fuse_ino_t parent, const char *name,
  424                       mode_t mode, dev_t rdev) {
  425     mknod_symlink(req, parent, name, mode, rdev, nullptr);
  426 }
  427 
  428 
  429 static void sfs_mkdir(fuse_req_t req, fuse_ino_t parent, const char *name,
  430                       mode_t mode) {
  431     mknod_symlink(req, parent, name, S_IFDIR | mode, 0, nullptr);
  432 }
  433 
  434 
  435 static void sfs_symlink(fuse_req_t req, const char *link, fuse_ino_t parent,
  436                         const char *name) {
  437     mknod_symlink(req, parent, name, S_IFLNK, 0, link);
  438 }
  439 
  440 
  441 static void sfs_link(fuse_req_t req, fuse_ino_t ino, fuse_ino_t parent,
  442                      const char *name) {
  443     Inode& inode = get_inode(ino);
  444     Inode& inode_p = get_inode(parent);
  445     fuse_entry_param e {};
  446 
  447     e.attr_timeout = fs.timeout;
  448     e.entry_timeout = fs.timeout;
  449 
  450     char procname[64];
  451     sprintf(procname, "/proc/self/fd/%i", inode.fd);
  452     auto res = linkat(AT_FDCWD, procname, inode_p.fd, name, AT_SYMLINK_FOLLOW);
  453     if (res == -1) {
  454         fuse_reply_err(req, errno);
  455         return;
  456     }
  457 
  458     res = fstatat(inode.fd, "", &e.attr, AT_EMPTY_PATH | AT_SYMLINK_NOFOLLOW);
  459     if (res == -1) {
  460         fuse_reply_err(req, errno);
  461         return;
  462     }
  463     e.ino = reinterpret_cast<fuse_ino_t>(&inode);
  464     {
  465         lock_guard<mutex> g {inode.m};
  466         inode.nlookup++;
  467     }
  468 
  469     fuse_reply_entry(req, &e);
  470     return;
  471 }
  472 
  473 
  474 static void sfs_rmdir(fuse_req_t req, fuse_ino_t parent, const char *name) {
  475     Inode& inode_p = get_inode(parent);
  476     lock_guard<mutex> g {inode_p.m};
  477     auto res = unlinkat(inode_p.fd, name, AT_REMOVEDIR);
  478     fuse_reply_err(req, res == -1 ? errno : 0);
  479 }
  480 
  481 
  482 static void sfs_rename(fuse_req_t req, fuse_ino_t parent, const char *name,
  483                        fuse_ino_t newparent, const char *newname,
  484                        unsigned int flags) {
  485     Inode& inode_p = get_inode(parent);
  486     Inode& inode_np = get_inode(newparent);
  487     if (flags) {
  488         fuse_reply_err(req, EINVAL);
  489         return;
  490     }
  491 
  492     auto res = renameat(inode_p.fd, name, inode_np.fd, newname);
  493     fuse_reply_err(req, res == -1 ? errno : 0);
  494 }
  495 
  496 
  497 static void sfs_unlink(fuse_req_t req, fuse_ino_t parent, const char *name) {
  498     Inode& inode_p = get_inode(parent);
  499     auto res = unlinkat(inode_p.fd, name, 0);
  500     fuse_reply_err(req, res == -1 ? errno : 0);
  501 }
  502 
  503 
  504 static void forget_one(fuse_ino_t ino, uint64_t n) {
  505     Inode& inode = get_inode(ino);
  506     unique_lock<mutex> l {inode.m};
  507 
  508     if(n > inode.nlookup) {
  509         cerr << "INTERNAL ERROR: Negative lookup count for inode "
  510              << inode.src_ino << endl;
  511         abort();
  512     }
  513     inode.nlookup -= n;
  514     if (!inode.nlookup) {
  515         if (fs.debug)
  516             cerr << "DEBUG: forget: cleaning up inode " << inode.src_ino << endl;
  517         {
  518             lock_guard<mutex> g_fs {fs.mutex};
  519             l.unlock();
  520             fs.inodes.erase({inode.src_ino, inode.src_dev});
  521         }
  522     } else if (fs.debug)
  523             cerr << "DEBUG: forget: inode " << inode.src_ino
  524                  << " lookup count now " << inode.nlookup << endl;
  525 }
  526 
  527 static void sfs_forget(fuse_req_t req, fuse_ino_t ino, uint64_t nlookup) {
  528     forget_one(ino, nlookup);
  529     fuse_reply_none(req);
  530 }
  531 
  532 
  533 static void sfs_forget_multi(fuse_req_t req, size_t count,
  534                              fuse_forget_data *forgets) {
  535     for (int i = 0; i < count; i++)
  536         forget_one(forgets[i].ino, forgets[i].nlookup);
  537     fuse_reply_none(req);
  538 }
  539 
  540 
  541 static void sfs_readlink(fuse_req_t req, fuse_ino_t ino) {
  542     Inode& inode = get_inode(ino);
  543     char buf[PATH_MAX + 1];
  544     auto res = readlinkat(inode.fd, "", buf, sizeof(buf));
  545     if (res == -1)
  546         fuse_reply_err(req, errno);
  547     else if (res == sizeof(buf))
  548         fuse_reply_err(req, ENAMETOOLONG);
  549     else {
  550         buf[res] = '\0';
  551         fuse_reply_readlink(req, buf);
  552     }
  553 }
  554 
  555 
  556 struct DirHandle {
  557     DIR *dp {nullptr};
  558     off_t offset;
  559 
  560     DirHandle() = default;
  561     DirHandle(const DirHandle&) = delete;
  562     DirHandle& operator=(const DirHandle&) = delete;
  563 
  564     ~DirHandle() {
  565         if(dp)
  566             closedir(dp);
  567     }
  568 };
  569 
  570 
  571 static DirHandle *get_dir_handle(fuse_file_info *fi) {
  572     return reinterpret_cast<DirHandle*>(fi->fh);
  573 }
  574 
  575 
  576 static void sfs_opendir(fuse_req_t req, fuse_ino_t ino, fuse_file_info *fi) {
  577     Inode& inode = get_inode(ino);
  578     auto d = new (nothrow) DirHandle;
  579     if (d == nullptr) {
  580         fuse_reply_err(req, ENOMEM);
  581         return;
  582     }
  583 
  584     // Make Helgrind happy - it can't know that there's an implicit
  585     // synchronization due to the fact that other threads cannot
  586     // access d until we've called fuse_reply_*.
  587     lock_guard<mutex> g {inode.m};
  588 
  589     auto fd = openat(inode.fd, ".", O_RDONLY);
  590     if (fd == -1)
  591         goto out_errno;
  592 
  593     // On success, dir stream takes ownership of fd, so we
  594     // do not have to close it.
  595     d->dp = fdopendir(fd);
  596     if(d->dp == nullptr)
  597         goto out_errno;
  598 
  599     d->offset = 0;
  600 
  601     fi->fh = reinterpret_cast<uint64_t>(d);
  602     if(fs.timeout) {
  603         fi->keep_cache = 1;
  604         fi->cache_readdir = 1;
  605     }
  606     fuse_reply_open(req, fi);
  607     return;
  608 
  609 out_errno:
  610     auto error = errno;
  611     delete d;
  612     if (error == ENFILE || error == EMFILE)
  613         cerr << "ERROR: Reached maximum number of file descriptors." << endl;
  614     fuse_reply_err(req, error);
  615 }
  616 
  617 
  618 static bool is_dot_or_dotdot(const char *name) {
  619     return name[0] == '.' &&
  620            (name[1] == '\0' || (name[1] == '.' && name[2] == '\0'));
  621 }
  622 
  623 
  624 static void do_readdir(fuse_req_t req, fuse_ino_t ino, size_t size,
  625                     off_t offset, fuse_file_info *fi, int plus) {
  626     auto d = get_dir_handle(fi);
  627     Inode& inode = get_inode(ino);
  628     lock_guard<mutex> g {inode.m};
  629     char *p;
  630     auto rem = size;
  631     int err = 0, count = 0;
  632 
  633     if (fs.debug)
  634         cerr << "DEBUG: readdir(): started with offset "
  635              << offset << endl;
  636 
  637     auto buf = new (nothrow) char[size];
  638     if (!buf) {
  639         fuse_reply_err(req, ENOMEM);
  640         return;
  641     }
  642     p = buf;
  643 
  644     if (offset != d->offset) {
  645         if (fs.debug)
  646             cerr << "DEBUG: readdir(): seeking to " << offset << endl;
  647         seekdir(d->dp, offset);
  648         d->offset = offset;
  649     }
  650 
  651     while (1) {
  652         struct dirent *entry;
  653         errno = 0;
  654         entry = readdir(d->dp);
  655         if (!entry) {
  656             if(errno) {
  657                 err = errno;
  658                 if (fs.debug)
  659                     warn("DEBUG: readdir(): readdir failed with");
  660                 goto error;
  661             }
  662             break; // End of stream
  663         }
  664         d->offset = entry->d_off;
  665         if (is_dot_or_dotdot(entry->d_name))
  666             continue;
  667 
  668         fuse_entry_param e{};
  669         size_t entsize;
  670         if(plus) {
  671             err = do_lookup(ino, entry->d_name, &e);
  672             if (err)
  673                 goto error;
  674             entsize = fuse_add_direntry_plus(req, p, rem, entry->d_name, &e, entry->d_off);
  675 
  676             if (entsize > rem) {
  677                 if (fs.debug)
  678                     cerr << "DEBUG: readdir(): buffer full, returning data. " << endl;
  679                 forget_one(e.ino, 1);
  680                 break;
  681             }
  682         } else {
  683             e.attr.st_ino = entry->d_ino;
  684             e.attr.st_mode = entry->d_type << 12;
  685             entsize = fuse_add_direntry(req, p, rem, entry->d_name, &e.attr, entry->d_off);
  686 
  687             if (entsize > rem) {
  688                 if (fs.debug)
  689                     cerr << "DEBUG: readdir(): buffer full, returning data. " << endl;
  690                 break;
  691             }
  692         }
  693 
  694         p += entsize;
  695         rem -= entsize;
  696         count++;
  697         if (fs.debug) {
  698             cerr << "DEBUG: readdir(): added to buffer: " << entry->d_name
  699                  << ", ino " << e.attr.st_ino << ", offset " << entry->d_off << endl;
  700         }
  701     }
  702     err = 0;
  703 error:
  704 
  705     // If there's an error, we can only signal it if we haven't stored
  706     // any entries yet - otherwise we'd end up with wrong lookup
  707     // counts for the entries that are already in the buffer. So we
  708     // return what we've collected until that point.
  709     if (err && rem == size) {
  710         if (err == ENFILE || err == EMFILE)
  711             cerr << "ERROR: Reached maximum number of file descriptors." << endl;
  712         fuse_reply_err(req, err);
  713     } else {
  714         if (fs.debug)
  715             cerr << "DEBUG: readdir(): returning " << count
  716                  << " entries, curr offset " << d->offset << endl;
  717         fuse_reply_buf(req, buf, size - rem);
  718     }
  719     delete[] buf;
  720     return;
  721 }
  722 
  723 
  724 static void sfs_readdir(fuse_req_t req, fuse_ino_t ino, size_t size,
  725                         off_t offset, fuse_file_info *fi) {
  726     // operation logging is done in readdir to reduce code duplication
  727     do_readdir(req, ino, size, offset, fi, 0);
  728 }
  729 
  730 
  731 static void sfs_readdirplus(fuse_req_t req, fuse_ino_t ino, size_t size,
  732                             off_t offset, fuse_file_info *fi) {
  733     // operation logging is done in readdir to reduce code duplication
  734     do_readdir(req, ino, size, offset, fi, 1);
  735 }
  736 
  737 
  738 static void sfs_releasedir(fuse_req_t req, fuse_ino_t ino, fuse_file_info *fi) {
  739     (void) ino;
  740     auto d = get_dir_handle(fi);
  741     delete d;
  742     fuse_reply_err(req, 0);
  743 }
  744 
  745 
  746 static void sfs_create(fuse_req_t req, fuse_ino_t parent, const char *name,
  747                        mode_t mode, fuse_file_info *fi) {
  748     Inode& inode_p = get_inode(parent);
  749 
  750     auto fd = openat(inode_p.fd, name,
  751                      (fi->flags | O_CREAT) & ~O_NOFOLLOW, mode);
  752     if (fd == -1) {
  753         auto err = errno;
  754         if (err == ENFILE || err == EMFILE)
  755             cerr << "ERROR: Reached maximum number of file descriptors." << endl;
  756         fuse_reply_err(req, err);
  757         return;
  758     }
  759 
  760     fi->fh = fd;
  761     fuse_entry_param e;
  762     auto err = do_lookup(parent, name, &e);
  763     if (err) {
  764         if (err == ENFILE || err == EMFILE)
  765             cerr << "ERROR: Reached maximum number of file descriptors." << endl;
  766         fuse_reply_err(req, err);
  767     } else
  768         fuse_reply_create(req, &e, fi);
  769 }
  770 
  771 
  772 static void sfs_fsyncdir(fuse_req_t req, fuse_ino_t ino, int datasync,
  773                          fuse_file_info *fi) {
  774     (void) ino;
  775     int res;
  776     int fd = dirfd(get_dir_handle(fi)->dp);
  777     if (datasync)
  778         res = fdatasync(fd);
  779     else
  780         res = fsync(fd);
  781     fuse_reply_err(req, res == -1 ? errno : 0);
  782 }
  783 
  784 
  785 static void sfs_open(fuse_req_t req, fuse_ino_t ino, fuse_file_info *fi) {
  786     Inode& inode = get_inode(ino);
  787 
  788     /* With writeback cache, kernel may send read requests even
  789        when userspace opened write-only */
  790     if (fs.timeout && (fi->flags & O_ACCMODE) == O_WRONLY) {
  791         fi->flags &= ~O_ACCMODE;
  792         fi->flags |= O_RDWR;
  793     }
  794 
  795     /* With writeback cache, O_APPEND is handled by the kernel.  This
  796        breaks atomicity (since the file may change in the underlying
  797        filesystem, so that the kernel's idea of the end of the file
  798        isn't accurate anymore). However, no process should modify the
  799        file in the underlying filesystem once it has been read, so
  800        this is not a problem. */
  801     if (fs.timeout && fi->flags & O_APPEND)
  802         fi->flags &= ~O_APPEND;
  803 
  804     /* Unfortunately we cannot use inode.fd, because this was opened
  805        with O_PATH (so it doesn't allow read/write access). */
  806     char buf[64];
  807     sprintf(buf, "/proc/self/fd/%i", inode.fd);
  808     auto fd = open(buf, fi->flags & ~O_NOFOLLOW);
  809     if (fd == -1) {
  810         auto err = errno;
  811         if (err == ENFILE || err == EMFILE)
  812             cerr << "ERROR: Reached maximum number of file descriptors." << endl;
  813         fuse_reply_err(req, err);
  814         return;
  815     }
  816 
  817     fi->keep_cache = (fs.timeout != 0);
  818     fi->fh = fd;
  819     fuse_reply_open(req, fi);
  820 }
  821 
  822 
  823 static void sfs_release(fuse_req_t req, fuse_ino_t ino, fuse_file_info *fi) {
  824     (void) ino;
  825     close(fi->fh);
  826     fuse_reply_err(req, 0);
  827 }
  828 
  829 
  830 static void sfs_flush(fuse_req_t req, fuse_ino_t ino, fuse_file_info *fi) {
  831     (void) ino;
  832     auto res = close(dup(fi->fh));
  833     fuse_reply_err(req, res == -1 ? errno : 0);
  834 }
  835 
  836 
  837 static void sfs_fsync(fuse_req_t req, fuse_ino_t ino, int datasync,
  838                       fuse_file_info *fi) {
  839     (void) ino;
  840     int res;
  841     if (datasync)
  842         res = fdatasync(fi->fh);
  843     else
  844         res = fsync(fi->fh);
  845     fuse_reply_err(req, res == -1 ? errno : 0);
  846 }
  847 
  848 
  849 static void do_read(fuse_req_t req, size_t size, off_t off, fuse_file_info *fi) {
  850 
  851     fuse_bufvec buf = FUSE_BUFVEC_INIT(size);
  852     buf.buf[0].flags = static_cast<fuse_buf_flags>(
  853         FUSE_BUF_IS_FD | FUSE_BUF_FD_SEEK);
  854     buf.buf[0].fd = fi->fh;
  855     buf.buf[0].pos = off;
  856 
  857     fuse_reply_data(req, &buf, FUSE_BUF_COPY_FLAGS);
  858 }
  859 
  860 static void sfs_read(fuse_req_t req, fuse_ino_t ino, size_t size, off_t off,
  861                      fuse_file_info *fi) {
  862     (void) ino;
  863     do_read(req, size, off, fi);
  864 }
  865 
  866 
  867 static void do_write_buf(fuse_req_t req, size_t size, off_t off,
  868                          fuse_bufvec *in_buf, fuse_file_info *fi) {
  869     fuse_bufvec out_buf = FUSE_BUFVEC_INIT(size);
  870     out_buf.buf[0].flags = static_cast<fuse_buf_flags>(
  871         FUSE_BUF_IS_FD | FUSE_BUF_FD_SEEK);
  872     out_buf.buf[0].fd = fi->fh;
  873     out_buf.buf[0].pos = off;
  874 
  875     auto res = fuse_buf_copy(&out_buf, in_buf, FUSE_BUF_COPY_FLAGS);
  876     if (res < 0)
  877         fuse_reply_err(req, -res);
  878     else
  879         fuse_reply_write(req, (size_t)res);
  880 }
  881 
  882 
  883 static void sfs_write_buf(fuse_req_t req, fuse_ino_t ino, fuse_bufvec *in_buf,
  884                           off_t off, fuse_file_info *fi) {
  885     (void) ino;
  886     auto size {fuse_buf_size(in_buf)};
  887     do_write_buf(req, size, off, in_buf, fi);
  888 }
  889 
  890 
  891 static void sfs_statfs(fuse_req_t req, fuse_ino_t ino) {
  892     struct statvfs stbuf;
  893 
  894     auto res = fstatvfs(get_fs_fd(ino), &stbuf);
  895     if (res == -1)
  896         fuse_reply_err(req, errno);
  897     else
  898         fuse_reply_statfs(req, &stbuf);
  899 }
  900 
  901 
  902 #ifdef HAVE_POSIX_FALLOCATE
  903 static void sfs_fallocate(fuse_req_t req, fuse_ino_t ino, int mode,
  904                           off_t offset, off_t length, fuse_file_info *fi) {
  905     (void) ino;
  906     if (mode) {
  907         fuse_reply_err(req, EOPNOTSUPP);
  908         return;
  909     }
  910 
  911     auto err = posix_fallocate(fi->fh, offset, length);
  912     fuse_reply_err(req, err);
  913 }
  914 #endif
  915 
  916 static void sfs_flock(fuse_req_t req, fuse_ino_t ino, fuse_file_info *fi,
  917                       int op) {
  918     (void) ino;
  919     auto res = flock(fi->fh, op);
  920     fuse_reply_err(req, res == -1 ? errno : 0);
  921 }
  922 
  923 
  924 #ifdef HAVE_SETXATTR
  925 static void sfs_getxattr(fuse_req_t req, fuse_ino_t ino, const char *name,
  926                          size_t size) {
  927     char *value = nullptr;
  928     Inode& inode = get_inode(ino);
  929     ssize_t ret;
  930     int saverr;
  931 
  932     char procname[64];
  933     sprintf(procname, "/proc/self/fd/%i", inode.fd);
  934 
  935     if (size) {
  936         value = new (nothrow) char[size];
  937         if (value == nullptr) {
  938             saverr = ENOMEM;
  939             goto out;
  940         }
  941 
  942         ret = getxattr(procname, name, value, size);
  943         if (ret == -1)
  944             goto out_err;
  945         saverr = 0;
  946         if (ret == 0)
  947             goto out;
  948 
  949         fuse_reply_buf(req, value, ret);
  950     } else {
  951         ret = getxattr(procname, name, nullptr, 0);
  952         if (ret == -1)
  953             goto out_err;
  954 
  955         fuse_reply_xattr(req, ret);
  956     }
  957 out_free:
  958     delete[] value;
  959     return;
  960 
  961 out_err:
  962     saverr = errno;
  963 out:
  964     fuse_reply_err(req, saverr);
  965     goto out_free;
  966 }
  967 
  968 
  969 static void sfs_listxattr(fuse_req_t req, fuse_ino_t ino, size_t size) {
  970     char *value = nullptr;
  971     Inode& inode = get_inode(ino);
  972     ssize_t ret;
  973     int saverr;
  974 
  975     char procname[64];
  976     sprintf(procname, "/proc/self/fd/%i", inode.fd);
  977 
  978     if (size) {
  979         value = new (nothrow) char[size];
  980         if (value == nullptr) {
  981             saverr = ENOMEM;
  982             goto out;
  983         }
  984 
  985         ret = listxattr(procname, value, size);
  986         if (ret == -1)
  987             goto out_err;
  988         saverr = 0;
  989         if (ret == 0)
  990             goto out;
  991 
  992         fuse_reply_buf(req, value, ret);
  993     } else {
  994         ret = listxattr(procname, nullptr, 0);
  995         if (ret == -1)
  996             goto out_err;
  997 
  998         fuse_reply_xattr(req, ret);
  999     }
 1000 out_free:
 1001     delete[] value;
 1002     return;
 1003 out_err:
 1004     saverr = errno;
 1005 out:
 1006     fuse_reply_err(req, saverr);
 1007     goto out_free;
 1008 }
 1009 
 1010 
 1011 static void sfs_setxattr(fuse_req_t req, fuse_ino_t ino, const char *name,
 1012                          const char *value, size_t size, int flags) {
 1013     Inode& inode = get_inode(ino);
 1014     ssize_t ret;
 1015     int saverr;
 1016 
 1017     char procname[64];
 1018     sprintf(procname, "/proc/self/fd/%i", inode.fd);
 1019 
 1020     ret = setxattr(procname, name, value, size, flags);
 1021     saverr = ret == -1 ? errno : 0;
 1022 
 1023     fuse_reply_err(req, saverr);
 1024 }
 1025 
 1026 
 1027 static void sfs_removexattr(fuse_req_t req, fuse_ino_t ino, const char *name) {
 1028     char procname[64];
 1029     Inode& inode = get_inode(ino);
 1030     ssize_t ret;
 1031     int saverr;
 1032 
 1033     sprintf(procname, "/proc/self/fd/%i", inode.fd);
 1034     ret = removexattr(procname, name);
 1035     saverr = ret == -1 ? errno : 0;
 1036 
 1037     fuse_reply_err(req, saverr);
 1038 }
 1039 #endif
 1040 
 1041 
 1042 static void assign_operations(fuse_lowlevel_ops &sfs_oper) {
 1043     sfs_oper.init = sfs_init;
 1044     sfs_oper.lookup = sfs_lookup;
 1045     sfs_oper.mkdir = sfs_mkdir;
 1046     sfs_oper.mknod = sfs_mknod;
 1047     sfs_oper.symlink = sfs_symlink;
 1048     sfs_oper.link = sfs_link;
 1049     sfs_oper.unlink = sfs_unlink;
 1050     sfs_oper.rmdir = sfs_rmdir;
 1051     sfs_oper.rename = sfs_rename;
 1052     sfs_oper.forget = sfs_forget;
 1053     sfs_oper.forget_multi = sfs_forget_multi;
 1054     sfs_oper.getattr = sfs_getattr;
 1055     sfs_oper.setattr = sfs_setattr;
 1056     sfs_oper.readlink = sfs_readlink;
 1057     sfs_oper.opendir = sfs_opendir;
 1058     sfs_oper.readdir = sfs_readdir;
 1059     sfs_oper.readdirplus = sfs_readdirplus;
 1060     sfs_oper.releasedir = sfs_releasedir;
 1061     sfs_oper.fsyncdir = sfs_fsyncdir;
 1062     sfs_oper.create = sfs_create;
 1063     sfs_oper.open = sfs_open;
 1064     sfs_oper.release = sfs_release;
 1065     sfs_oper.flush = sfs_flush;
 1066     sfs_oper.fsync = sfs_fsync;
 1067     sfs_oper.read = sfs_read;
 1068     sfs_oper.write_buf = sfs_write_buf;
 1069     sfs_oper.statfs = sfs_statfs;
 1070 #ifdef HAVE_POSIX_FALLOCATE
 1071     sfs_oper.fallocate = sfs_fallocate;
 1072 #endif
 1073     sfs_oper.flock = sfs_flock;
 1074 #ifdef HAVE_SETXATTR
 1075     sfs_oper.setxattr = sfs_setxattr;
 1076     sfs_oper.getxattr = sfs_getxattr;
 1077     sfs_oper.listxattr = sfs_listxattr;
 1078     sfs_oper.removexattr = sfs_removexattr;
 1079 #endif
 1080 }
 1081 
 1082 static void print_usage(char *prog_name) {
 1083     cout << "Usage: " << prog_name << " --help\n"
 1084          << "       " << prog_name << " [options] <source> <mountpoint>\n";
 1085 }
 1086 
 1087 static cxxopts::ParseResult parse_wrapper(cxxopts::Options& parser, int& argc, char**& argv) {
 1088     try {
 1089         return parser.parse(argc, argv);
 1090     } catch (cxxopts::option_not_exists_exception& exc) {
 1091         std::cout << argv[0] << ": " << exc.what() << std::endl;
 1092         print_usage(argv[0]);
 1093         exit(2);
 1094     }
 1095 }
 1096 
 1097 
 1098 static cxxopts::ParseResult parse_options(int argc, char **argv) {
 1099     cxxopts::Options opt_parser(argv[0]);
 1100     opt_parser.add_options()
 1101         ("debug", "Enable filesystem debug messages")
 1102         ("debug-fuse", "Enable libfuse debug messages")
 1103         ("help", "Print help")
 1104         ("nocache", "Disable all caching")
 1105         ("nosplice", "Do not use splice(2) to transfer data")
 1106         ("single", "Run single-threaded");
 1107 
 1108     // FIXME: Find a better way to limit the try clause to just
 1109     // opt_parser.parse() (cf. https://github.com/jarro2783/cxxopts/issues/146)
 1110     auto options = parse_wrapper(opt_parser, argc, argv);
 1111 
 1112     if (options.count("help")) {
 1113         print_usage(argv[0]);
 1114         // Strip everything before the option list from the
 1115         // default help string.
 1116         auto help = opt_parser.help();
 1117         std::cout << std::endl << "options:"
 1118                   << help.substr(help.find("\n\n") + 1, string::npos);
 1119         exit(0);
 1120 
 1121     } else if (argc != 3) {
 1122         std::cout << argv[0] << ": invalid number of arguments\n";
 1123         print_usage(argv[0]);
 1124         exit(2);
 1125     }
 1126 
 1127     fs.debug = options.count("debug") != 0;
 1128     fs.nosplice = options.count("nosplice") != 0;
 1129     char* resolved_path = realpath(argv[1], NULL);
 1130     if (resolved_path == NULL)
 1131         warn("WARNING: realpath() failed with");
 1132     fs.source = std::string {resolved_path};
 1133     free(resolved_path);
 1134 
 1135     return options;
 1136 }
 1137 
 1138 
 1139 static void maximize_fd_limit() {
 1140     struct rlimit lim {};
 1141     auto res = getrlimit(RLIMIT_NOFILE, &lim);
 1142     if (res != 0) {
 1143         warn("WARNING: getrlimit() failed with");
 1144         return;
 1145     }
 1146     lim.rlim_cur = lim.rlim_max;
 1147     res = setrlimit(RLIMIT_NOFILE, &lim);
 1148     if (res != 0)
 1149         warn("WARNING: setrlimit() failed with");
 1150 }
 1151 
 1152 
 1153 int main(int argc, char *argv[]) {
 1154 
 1155     // Parse command line options
 1156     auto options {parse_options(argc, argv)};
 1157 
 1158     // We need an fd for every dentry in our the filesystem that the
 1159     // kernel knows about. This is way more than most processes need,
 1160     // so try to get rid of any resource softlimit.
 1161     maximize_fd_limit();
 1162 
 1163     // Initialize filesystem root
 1164     fs.root.fd = -1;
 1165     fs.root.nlookup = 9999;
 1166     fs.timeout = options.count("nocache") ? 0 : 86400.0;
 1167 
 1168     struct stat stat;
 1169     auto ret = lstat(fs.source.c_str(), &stat);
 1170     if (ret == -1)
 1171         err(1, "ERROR: failed to stat source (\"%s\")", fs.source.c_str());
 1172     if (!S_ISDIR(stat.st_mode))
 1173         errx(1, "ERROR: source is not a directory");
 1174     fs.src_dev = stat.st_dev;
 1175 
 1176     fs.root.fd = open(fs.source.c_str(), O_PATH);
 1177     if (fs.root.fd == -1)
 1178         err(1, "ERROR: open(\"%s\", O_PATH)", fs.source.c_str());
 1179 
 1180     // Initialize fuse
 1181     fuse_args args = FUSE_ARGS_INIT(0, nullptr);
 1182     if (fuse_opt_add_arg(&args, argv[0]) ||
 1183         fuse_opt_add_arg(&args, "-o") ||
 1184         fuse_opt_add_arg(&args, "default_permissions,fsname=hpps") ||
 1185         (options.count("debug-fuse") && fuse_opt_add_arg(&args, "-odebug")))
 1186         errx(3, "ERROR: Out of memory");
 1187 
 1188     fuse_lowlevel_ops sfs_oper {};
 1189     assign_operations(sfs_oper);
 1190     auto se = fuse_session_new(&args, &sfs_oper, sizeof(sfs_oper), &fs);
 1191     if (se == nullptr)
 1192         goto err_out1;
 1193 
 1194     if (fuse_set_signal_handlers(se) != 0)
 1195         goto err_out2;
 1196 
 1197     // Don't apply umask, use modes exactly as specified
 1198     umask(0);
 1199 
 1200     // Mount and run main loop
 1201     struct fuse_loop_config loop_config;
 1202     loop_config.clone_fd = 0;
 1203     loop_config.max_idle_threads = 10;
 1204     if (fuse_session_mount(se, argv[2]) != 0)
 1205         goto err_out3;
 1206     if (options.count("single"))
 1207         ret = fuse_session_loop(se);
 1208     else
 1209         ret = fuse_session_loop_mt(se, &loop_config);
 1210 
 1211     fuse_session_unmount(se);
 1212 
 1213 err_out3:
 1214     fuse_remove_signal_handlers(se);
 1215 err_out2:
 1216     fuse_session_destroy(se);
 1217 err_out1:
 1218     fuse_opt_free_args(&args);
 1219 
 1220     return ret ? 1 : 0;
 1221 }
 1222