34#define FUSE_USE_VERSION FUSE_MAKE_VERSION(3, 18)
36#include <fuse_lowlevel.h>
52#include "passthrough_helpers.h"
58#if defined(__GNUC__) && (__GNUC__ > 4 || __GNUC__ == 4 && __GNUC_MINOR__ >= 6) && !defined __cplusplus
59_Static_assert(
sizeof(
fuse_ino_t) >=
sizeof(uintptr_t),
60 "fuse_ino_t too small to hold uintptr_t values!");
62struct _uintptr_to_must_hold_fuse_ino_t_dummy_struct \
63 {
unsigned _uintptr_to_must_hold_fuse_ino_t:
64 ((
sizeof(
fuse_ino_t) >=
sizeof(uintptr_t)) ? 1 : -1); };
68 struct lo_inode *next;
69 struct lo_inode *prev;
83 pthread_mutex_t mutex;
95static const struct fuse_opt lo_opts[] = {
97 offsetof(
struct lo_data, writeback), 1 },
99 offsetof(
struct lo_data, writeback), 0 },
101 offsetof(
struct lo_data, source), 0 },
103 offsetof(
struct lo_data, flock), 1 },
105 offsetof(
struct lo_data, flock), 0 },
107 offsetof(
struct lo_data, xattr), 1 },
109 offsetof(
struct lo_data, xattr), 0 },
111 offsetof(
struct lo_data, timeout), 0 },
113 offsetof(
struct lo_data, timeout_set), 1 },
115 offsetof(
struct lo_data, cache), CACHE_NEVER },
117 offsetof(
struct lo_data, cache), CACHE_NORMAL },
119 offsetof(
struct lo_data, cache), CACHE_ALWAYS },
124static void passthrough_ll_help(
void)
127" -o writeback Enable writeback\n"
128" -o no_writeback Disable write back\n"
129" -o source=/home/dir Source directory to be mounted\n"
130" -o flock Enable flock\n"
131" -o no_flock Disable flock\n"
132" -o xattr Enable xattr\n"
133" -o no_xattr Disable xattr\n"
134" -o timeout=1.0 Caching timeout\n"
135" -o timeout=0/1 Timeout is set\n"
136" -o cache=never Disable cache\n"
137" -o cache=auto Auto enable cache\n"
138" -o cache=always Cache always\n");
148 if (ino == FUSE_ROOT_ID)
149 return &lo_data(req)->root;
151 return (
struct lo_inode *) (uintptr_t) ino;
156 return lo_inode(req, ino)->fd;
161 return lo_data(req)->debug != 0;
164static void lo_init(
void *userdata,
167 struct lo_data *lo = (
struct lo_data *)userdata;
172 if (lo->debug && has_flag)
174 "lo_init: activating writeback\n");
178 if (lo->debug && has_flag)
180 "lo_init: activating flock locks\n");
187static void lo_destroy(
void *userdata)
189 struct lo_data *lo = (
struct lo_data*) userdata;
191 while (lo->root.next != &lo->root) {
192 struct lo_inode* next = lo->root.next;
193 lo->root.next = next->next;
204 struct lo_data *lo = lo_data(req);
205 int fd = fi ? fi->
fh : lo_fd(req, ino);
209 res = fstatat(fd,
"", &buf, AT_EMPTY_PATH | AT_SYMLINK_NOFOLLOW);
221 struct lo_inode *inode = lo_inode(req, ino);
225 if (valid & FUSE_SET_ATTR_MODE) {
227 res = fchmod(fi->
fh, attr->st_mode);
229 sprintf(procname,
"/proc/self/fd/%i", ifd);
230 res = chmod(procname, attr->st_mode);
235 if (valid & (FUSE_SET_ATTR_UID | FUSE_SET_ATTR_GID)) {
236 uid_t uid = (valid & FUSE_SET_ATTR_UID) ?
237 attr->st_uid : (uid_t) -1;
238 gid_t gid = (valid & FUSE_SET_ATTR_GID) ?
239 attr->st_gid : (gid_t) -1;
241 res = fchownat(ifd,
"", uid, gid,
242 AT_EMPTY_PATH | AT_SYMLINK_NOFOLLOW);
246 if (valid & FUSE_SET_ATTR_SIZE) {
248 res = ftruncate(fi->
fh, attr->st_size);
250 sprintf(procname,
"/proc/self/fd/%i", ifd);
251 res = truncate(procname, attr->st_size);
256 if (valid & (FUSE_SET_ATTR_ATIME | FUSE_SET_ATTR_MTIME)) {
257 struct timespec tv[2];
261 tv[0].tv_nsec = UTIME_OMIT;
262 tv[1].tv_nsec = UTIME_OMIT;
264 if (valid & FUSE_SET_ATTR_ATIME_NOW)
265 tv[0].tv_nsec = UTIME_NOW;
266 else if (valid & FUSE_SET_ATTR_ATIME)
267 tv[0] = attr->st_atim;
269 if (valid & FUSE_SET_ATTR_MTIME_NOW)
270 tv[1].tv_nsec = UTIME_NOW;
271 else if (valid & FUSE_SET_ATTR_MTIME)
272 tv[1] = attr->st_mtim;
275 res = futimens(fi->
fh, tv);
277 sprintf(procname,
"/proc/self/fd/%i", ifd);
278 res = utimensat(AT_FDCWD, procname, tv, 0);
284 return lo_getattr(req, ino, fi);
291static struct lo_inode *lo_find(
struct lo_data *lo,
struct stat *st)
294 struct lo_inode *ret = NULL;
296 pthread_mutex_lock(&lo->mutex);
297 for (p = lo->root.next; p != &lo->root; p = p->next) {
298 if (p->ino == st->st_ino && p->dev == st->st_dev) {
299 assert(p->refcount > 0);
305 pthread_mutex_unlock(&lo->mutex);
310static struct lo_inode *create_new_inode(
int fd,
struct fuse_entry_param *e,
struct lo_data* lo)
312 struct lo_inode *inode = NULL;
313 struct lo_inode *prev, *next;
315 inode = calloc(1,
sizeof(
struct lo_inode));
321 inode->ino = e->
attr.st_ino;
322 inode->dev = e->
attr.st_dev;
324 pthread_mutex_lock(&lo->mutex);
331 pthread_mutex_unlock(&lo->mutex);
338 struct lo_data *lo = lo_data(req);
340 memset(e, 0,
sizeof(*e));
344 res = fstatat(fd,
"", &e->
attr, AT_EMPTY_PATH | AT_SYMLINK_NOFOLLOW);
348 e->
ino = (uintptr_t) create_new_inode(dup(fd), e, lo);
351 fuse_log(FUSE_LOG_DEBUG,
" %lli/%d -> %lli\n",
352 (
unsigned long long) parent, fd, (
unsigned long long) e->
ino);
364 struct lo_data *lo = lo_data(req);
365 struct lo_inode *inode;
367 memset(e, 0,
sizeof(*e));
371 newfd = openat(lo_fd(req, parent), name, O_PATH | O_NOFOLLOW);
375 res = fstatat(newfd,
"", &e->
attr, AT_EMPTY_PATH | AT_SYMLINK_NOFOLLOW);
379 inode = lo_find(lo_data(req), &e->
attr);
384 inode = create_new_inode(newfd, e, lo);
388 e->
ino = (uintptr_t) inode;
391 fuse_log(FUSE_LOG_DEBUG,
" %lli/%s -> %lli\n",
392 (
unsigned long long) parent, name, (
unsigned long long) e->
ino);
409 fuse_log(FUSE_LOG_DEBUG,
"lo_lookup(parent=%" PRIu64
", name=%s)\n",
412 err = lo_do_lookup(req, parent, name, &e);
420 const char *name, mode_t mode, dev_t rdev,
425 struct lo_inode *dir = lo_inode(req, parent);
428 res = mknod_wrapper(dir->fd, name, link, mode, rdev);
434 saverr = lo_do_lookup(req, parent, name, &e);
439 fuse_log(FUSE_LOG_DEBUG,
" %lli/%s -> %lli\n",
440 (
unsigned long long) parent, name, (
unsigned long long) e.
ino);
450 const char *name, mode_t mode, dev_t rdev)
452 lo_mknod_symlink(req, parent, name, mode, rdev, NULL);
458 lo_mknod_symlink(req, parent, name, S_IFDIR | mode, 0, NULL);
461static void lo_symlink(
fuse_req_t req,
const char *link,
464 lo_mknod_symlink(req, parent, name, S_IFLNK, 0, link);
471 struct lo_data *lo = lo_data(req);
472 struct lo_inode *inode = lo_inode(req, ino);
481 sprintf(procname,
"/proc/self/fd/%i", inode->fd);
482 res = linkat(AT_FDCWD, procname, lo_fd(req, parent), name,
487 res = fstatat(inode->fd,
"", &e.
attr, AT_EMPTY_PATH | AT_SYMLINK_NOFOLLOW);
491 pthread_mutex_lock(&lo->mutex);
493 pthread_mutex_unlock(&lo->mutex);
494 e.
ino = (uintptr_t) inode;
497 fuse_log(FUSE_LOG_DEBUG,
" %lli/%s -> %lli\n",
498 (
unsigned long long) parent, name,
499 (
unsigned long long) e.
ino);
513 res = unlinkat(lo_fd(req, parent), name, AT_REMOVEDIR);
529 res = renameat(lo_fd(req, parent), name,
530 lo_fd(req, newparent), newname);
539 res = unlinkat(lo_fd(req, parent), name, 0);
544static void unref_inode(
struct lo_data *lo,
struct lo_inode *inode, uint64_t n)
549 pthread_mutex_lock(&lo->mutex);
550 assert(inode->refcount >= n);
551 inode->refcount -= n;
552 if (!inode->refcount) {
553 struct lo_inode *prev, *next;
560 pthread_mutex_unlock(&lo->mutex);
565 pthread_mutex_unlock(&lo->mutex);
571 struct lo_data *lo = lo_data(req);
572 struct lo_inode *inode = lo_inode(req, ino);
575 fuse_log(FUSE_LOG_DEBUG,
" forget %lli %lli -%lli\n",
576 (
unsigned long long) ino,
577 (
unsigned long long) inode->refcount,
578 (
unsigned long long) nlookup);
581 unref_inode(lo, inode, nlookup);
586 lo_forget_one(req, ino, nlookup);
590static void lo_forget_multi(
fuse_req_t req,
size_t count,
591 struct fuse_forget_data *forgets)
595 for (i = 0; i < count; i++)
596 lo_forget_one(req, forgets[i].ino, forgets[i].nlookup);
602 char buf[PATH_MAX + 1];
605 res = readlinkat(lo_fd(req, ino),
"", buf,
sizeof(buf));
609 if (res ==
sizeof(buf))
619 struct dirent *entry;
625 return (
struct lo_dirp *) (uintptr_t) fi->
fh;
631 struct lo_data *lo = lo_data(req);
635 d = calloc(1,
sizeof(
struct lo_dirp));
639 fd = openat(lo_fd(req, ino),
".", O_RDONLY);
643 d->dp = fdopendir(fd);
650 fi->
fh = (uintptr_t) d;
651 if (lo->cache != CACHE_NEVER)
653 if (lo->cache == CACHE_ALWAYS)
669static int is_dot_or_dotdot(
const char *name)
671 return name[0] ==
'.' && (name[1] ==
'\0' ||
672 (name[1] ==
'.' && name[2] ==
'\0'));
678 struct lo_dirp *d = lo_dirp(fi);
686 buf = calloc(1, size);
693 if (offset != d->offset) {
694 seekdir(d->dp, offset);
705 d->entry = readdir(d->dp);
715 nextoff = d->entry->d_off;
716 name = d->entry->d_name;
720 if (is_dot_or_dotdot(name)) {
722 .
attr.st_ino = d->entry->d_ino,
723 .attr.st_mode = d->entry->d_type << 12,
726 err = lo_do_lookup(req, ino, name, &e);
736 .st_ino = d->entry->d_ino,
737 .st_mode = d->entry->d_type << 12,
744 lo_forget_one(req, entry_ino, 1);
761 if (err && rem == size)
771 lo_do_readdir(req, ino, size, offset, fi, 0);
777 lo_do_readdir(req, ino, size, offset, fi, 1);
782 struct lo_dirp *d = lo_dirp(fi);
793 struct lo_data *lo = lo_data(req);
798 fuse_log(FUSE_LOG_DEBUG,
"lo_tmpfile(parent=%" PRIu64
")\n",
801 fd = openat(lo_fd(req, parent),
".",
802 (fi->
flags | O_TMPFILE) & ~O_NOFOLLOW, mode);
807 if (lo->cache == CACHE_NEVER)
809 else if (lo->cache == CACHE_ALWAYS)
817 err = fill_entry_param_new_inode(req, parent, fd, &e);
828 struct lo_data *lo = lo_data(req);
833 fuse_log(FUSE_LOG_DEBUG,
"lo_create(parent=%" PRIu64
", name=%s)\n",
836 fd = openat(lo_fd(req, parent), name,
837 (fi->
flags | O_CREAT) & ~O_NOFOLLOW, mode);
842 if (lo->cache == CACHE_NEVER)
844 else if (lo->cache == CACHE_ALWAYS)
852 err = lo_do_lookup(req, parent, name, &e);
863 int fd = dirfd(lo_dirp(fi)->dp);
876 struct lo_data *lo = lo_data(req);
879 fuse_log(FUSE_LOG_DEBUG,
"lo_open(ino=%" PRIu64
", flags=%d)\n",
884 if (lo->writeback && (fi->
flags & O_ACCMODE) == O_WRONLY) {
885 fi->
flags &= ~O_ACCMODE;
895 if (lo->writeback && (fi->
flags & O_APPEND))
896 fi->
flags &= ~O_APPEND;
898 sprintf(buf,
"/proc/self/fd/%i", lo_fd(req, ino));
899 fd = open(buf, fi->
flags & ~O_NOFOLLOW);
904 if (lo->cache == CACHE_NEVER)
906 else if (lo->cache == CACHE_ALWAYS)
912 if (fi->
flags & O_DIRECT)
935 res = close(dup(fi->
fh));
945 res = fdatasync(fi->
fh);
957 fuse_log(FUSE_LOG_DEBUG,
"lo_read(ino=%" PRIu64
", size=%zd, "
958 "off=%lu)\n", ino, size, (
unsigned long) offset);
980 fuse_log(FUSE_LOG_DEBUG,
"lo_write(ino=%" PRIu64
", size=%zd, off=%jd)\n",
993 struct statvfs stbuf;
995 res = fstatvfs(lo_fd(req, ino), &stbuf);
1008 err = -do_fallocate(fi->
fh, mode, offset, length);
1019 res = flock(fi->
fh, op);
1029 struct lo_inode *inode = lo_inode(req, ino);
1034 if (!lo_data(req)->xattr)
1037 if (lo_debug(req)) {
1038 fuse_log(FUSE_LOG_DEBUG,
"lo_getxattr(ino=%" PRIu64
", name=%s size=%zd)\n",
1042 sprintf(procname,
"/proc/self/fd/%i", inode->fd);
1045 value = malloc(size);
1049 ret = getxattr(procname, name, value, size);
1058 ret = getxattr(procname, name, NULL, 0);
1079 struct lo_inode *inode = lo_inode(req, ino);
1084 if (!lo_data(req)->xattr)
1087 if (lo_debug(req)) {
1088 fuse_log(FUSE_LOG_DEBUG,
"lo_listxattr(ino=%" PRIu64
", size=%zd)\n",
1092 sprintf(procname,
"/proc/self/fd/%i", inode->fd);
1095 value = malloc(size);
1099 ret = listxattr(procname, value, size);
1108 ret = listxattr(procname, NULL, 0);
1126 const char *value,
size_t size,
int flags)
1129 struct lo_inode *inode = lo_inode(req, ino);
1134 if (!lo_data(req)->xattr)
1137 if (lo_debug(req)) {
1138 fuse_log(FUSE_LOG_DEBUG,
"lo_setxattr(ino=%" PRIu64
", name=%s value=%s size=%zd)\n",
1139 ino, name, value, size);
1142 sprintf(procname,
"/proc/self/fd/%i", inode->fd);
1144 ret = setxattr(procname, name, value, size, flags);
1145 saverr = ret == -1 ? errno : 0;
1154 struct lo_inode *inode = lo_inode(req, ino);
1159 if (!lo_data(req)->xattr)
1162 if (lo_debug(req)) {
1163 fuse_log(FUSE_LOG_DEBUG,
"lo_removexattr(ino=%" PRIu64
", name=%s)\n",
1167 sprintf(procname,
"/proc/self/fd/%i", inode->fd);
1169 ret = removexattr(procname, name);
1170 saverr = ret == -1 ? errno : 0;
1176#ifdef HAVE_COPY_FILE_RANGE
1187 "%s(ino=%lld fd=%lld off=%jd ino=%lld fd=%lld off=%jd, size=%zd, flags=0x%x)\n",
1188 __func__, (
unsigned long long)ino_in,
1189 (
unsigned long long)fi_in->
fh,
1190 (intmax_t) off_in, (
unsigned long long)ino_out,
1191 (
unsigned long long)fi_out->
fh, (intmax_t) off_out,
1194 res = copy_file_range(fi_in->
fh, &off_in, fi_out->
fh, &off_out, len,
1209 res = lseek(fi->
fh, off, whence);
1220 struct lo_data *lo = lo_data(req);
1228 fd = lo_fd(req, ino);
1230 res = statx(fd,
"", flags | AT_EMPTY_PATH | AT_SYMLINK_NOFOLLOW, mask, &buf);
1240 .destroy = lo_destroy,
1241 .lookup = lo_lookup,
1244 .symlink = lo_symlink,
1246 .unlink = lo_unlink,
1248 .rename = lo_rename,
1249 .forget = lo_forget,
1250 .forget_multi = lo_forget_multi,
1251 .getattr = lo_getattr,
1252 .setattr = lo_setattr,
1253 .readlink = lo_readlink,
1254 .opendir = lo_opendir,
1255 .readdir = lo_readdir,
1256 .readdirplus = lo_readdirplus,
1257 .releasedir = lo_releasedir,
1258 .fsyncdir = lo_fsyncdir,
1259 .create = lo_create,
1260 .tmpfile = lo_tmpfile,
1262 .release = lo_release,
1266 .write_buf = lo_write_buf,
1267 .statfs = lo_statfs,
1268 .fallocate = lo_fallocate,
1270 .getxattr = lo_getxattr,
1271 .listxattr = lo_listxattr,
1272 .setxattr = lo_setxattr,
1273 .removexattr = lo_removexattr,
1274#ifdef HAVE_COPY_FILE_RANGE
1275 .copy_file_range = lo_copy_file_range,
1283int main(
int argc,
char *argv[])
1286 struct fuse_session *se;
1289 struct lo_data lo = { .debug = 0,
1296 pthread_mutex_init(&lo.mutex, NULL);
1297 lo.root.next = lo.root.prev = &lo.root;
1299 lo.cache = CACHE_NORMAL;
1301 if (fuse_parse_cmdline(&args, &opts) != 0)
1303 if (opts.show_help) {
1304 printf(
"usage: %s [options] <mountpoint>\n\n", argv[0]);
1307 passthrough_ll_help();
1310 }
else if (opts.show_version) {
1317 if(opts.mountpoint == NULL) {
1318 printf(
"usage: %s [options] <mountpoint>\n", argv[0]);
1319 printf(
" %s --help\n", argv[0]);
1327 lo.debug = opts.debug;
1328 lo.root.refcount = 2;
1333 res = lstat(lo.source, &stat);
1335 fuse_log(FUSE_LOG_ERR,
"failed to stat source (\"%s\"): %m\n",
1339 if (!S_ISDIR(stat.st_mode)) {
1340 fuse_log(FUSE_LOG_ERR,
"source is not a directory\n");
1345 lo.source = strdup(
"/");
1347 fuse_log(FUSE_LOG_ERR,
"fuse: memory allocation failed\n");
1351 if (!lo.timeout_set) {
1362 lo.timeout = 86400.0;
1365 }
else if (lo.timeout < 0) {
1366 fuse_log(FUSE_LOG_ERR,
"timeout is negative (%lf)\n",
1371 lo.root.fd = open(lo.source, O_PATH);
1372 if (lo.root.fd == -1) {
1373 fuse_log(FUSE_LOG_ERR,
"open(\"%s\", O_PATH): %m\n",
1378 se = fuse_session_new(&args, &lo_oper,
sizeof(lo_oper), &lo);
1391 if (opts.singlethread)
1394 config = fuse_loop_cfg_create();
1395 fuse_loop_cfg_set_clone_fd(config, opts.clone_fd);
1396 fuse_loop_cfg_set_max_threads(config, opts.max_threads);
1397 ret = fuse_session_loop_mt(se, config);
1398 fuse_loop_cfg_destroy(config);
1408 free(opts.mountpoint);
1411 if (lo.root.fd >= 0)
bool fuse_set_feature_flag(struct fuse_conn_info *conn, uint64_t flag)
int fuse_set_signal_handlers(struct fuse_session *se)
size_t fuse_buf_size(const struct fuse_bufvec *bufv)
#define FUSE_CAP_WRITEBACK_CACHE
ssize_t fuse_buf_copy(struct fuse_bufvec *dst, struct fuse_bufvec *src, enum fuse_buf_copy_flags flags)
const char * fuse_pkgversion(void)
void fuse_remove_signal_handlers(struct fuse_session *se)
int fuse_daemonize(int foreground)
#define FUSE_CAP_FLOCK_LOCKS
void fuse_log(enum fuse_log_level level, const char *fmt,...)
void fuse_session_destroy(struct fuse_session *se)
int fuse_reply_data(fuse_req_t req, struct fuse_bufvec *bufv, enum fuse_buf_copy_flags flags)
int fuse_reply_open(fuse_req_t req, const struct fuse_file_info *fi)
int fuse_reply_err(fuse_req_t req, int err)
void * fuse_req_userdata(fuse_req_t req)
int fuse_reply_buf(fuse_req_t req, const char *buf, size_t size)
struct fuse_req * fuse_req_t
size_t fuse_add_direntry_plus(fuse_req_t req, char *buf, size_t bufsize, const char *name, const struct fuse_entry_param *e, off_t off)
int fuse_reply_readlink(fuse_req_t req, const char *link)
int fuse_session_loop(struct fuse_session *se)
int fuse_reply_entry(fuse_req_t req, const struct fuse_entry_param *e)
void fuse_session_unmount(struct fuse_session *se)
void fuse_cmdline_help(void)
void fuse_reply_none(fuse_req_t req)
void fuse_lowlevel_help(void)
int fuse_reply_statfs(fuse_req_t req, const struct statvfs *stbuf)
int fuse_reply_write(fuse_req_t req, size_t count)
int fuse_session_mount(struct fuse_session *se, const char *mountpoint)
int fuse_reply_create(fuse_req_t req, const struct fuse_entry_param *e, const struct fuse_file_info *fi)
int fuse_reply_lseek(fuse_req_t req, off_t off)
void fuse_lowlevel_version(void)
size_t fuse_add_direntry(fuse_req_t req, char *buf, size_t bufsize, const char *name, const struct stat *stbuf, off_t off)
int fuse_reply_attr(fuse_req_t req, const struct stat *attr, double attr_timeout)
int fuse_reply_xattr(fuse_req_t req, size_t count)
void fuse_opt_free_args(struct fuse_args *args)
int fuse_opt_parse(struct fuse_args *args, void *data, const struct fuse_opt opts[], fuse_opt_proc_t proc)
#define FUSE_ARGS_INIT(argc, argv)
int fuse_reply_statx(fuse_req_t req, int flags, struct statx *statx, double attr_timeout)
enum fuse_buf_flags flags
uint32_t parallel_direct_writes
void(* init)(void *userdata, struct fuse_conn_info *conn)