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