/* FUSE: Filesystem in Userspace Copyright (C) 2001-2007 Miklos Szeredi This program can be distributed under the terms of the GNU LGPLv2. See the file COPYING.LIB. */ #pragma once #include "fuse_opt.h" #include "mount_util.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef __NetBSD__ #include #define MS_RDONLY MNT_RDONLY #define MS_NOSUID MNT_NOSUID #define MS_NODEV MNT_NODEV #define MS_NOEXEC MNT_NOEXEC #define MS_SYNCHRONOUS MNT_SYNCHRONOUS #define MS_NOATIME MNT_NOATIME #ifndef MNT_DETACH #define MNT_DETACH 2 #endif #define umount2(mnt, flags) unmount(mnt,(flags == MNT_DETACH) ? MNT_FORCE : 0) #endif #define FUSERMOUNT_PROG "fusermount" #define FUSERMOUNT3_PROG "fusermount3" #define FUSE_COMMFD_ENV "_FUSE_COMMFD" #ifndef MS_DIRSYNC #define MS_DIRSYNC 128 #endif enum { KEY_KERN_FLAG, KEY_KERN_OPT, KEY_FUSERMOUNT_OPT, KEY_SUBTYPE_OPT, KEY_MTAB_OPT, KEY_RO }; struct mount_opts { int allow_other; int flags; int auto_unmount; int blkdev; char *fsname; char *subtype; char *subtype_opt; char *mtab_opts; char *fusermount_opts; char *kernel_opts; }; #define FUSE_MOUNT_OPT(t,p) { t, offsetof(struct mount_opts,p), 1 } static const struct fuse_opt fuse_mount_opts[] = { FUSE_MOUNT_OPT("allow_other",allow_other), FUSE_MOUNT_OPT("blkdev",blkdev), FUSE_MOUNT_OPT("auto_unmount",auto_unmount), FUSE_MOUNT_OPT("fsname=%s",fsname), FUSE_MOUNT_OPT("subtype=%s",subtype), FUSE_OPT_KEY("allow_other",KEY_KERN_OPT), FUSE_OPT_KEY("auto_unmount",KEY_FUSERMOUNT_OPT), FUSE_OPT_KEY("blkdev",KEY_FUSERMOUNT_OPT), FUSE_OPT_KEY("fsname=",KEY_FUSERMOUNT_OPT), FUSE_OPT_KEY("subtype=",KEY_SUBTYPE_OPT), FUSE_OPT_KEY("large_read",KEY_KERN_OPT), FUSE_OPT_KEY("blksize=",KEY_KERN_OPT), FUSE_OPT_KEY("default_permissions",KEY_KERN_OPT), FUSE_OPT_KEY("context=",KEY_KERN_OPT), FUSE_OPT_KEY("fscontext=",KEY_KERN_OPT), FUSE_OPT_KEY("defcontext=",KEY_KERN_OPT), FUSE_OPT_KEY("rootcontext=",KEY_KERN_OPT), FUSE_OPT_KEY("max_read=",KEY_KERN_OPT), FUSE_OPT_KEY("max_read=",FUSE_OPT_KEY_KEEP), FUSE_OPT_KEY("user=",KEY_MTAB_OPT), FUSE_OPT_KEY("-n",KEY_MTAB_OPT), FUSE_OPT_KEY("-r",KEY_RO), FUSE_OPT_KEY("ro",KEY_KERN_FLAG), FUSE_OPT_KEY("rw",KEY_KERN_FLAG), FUSE_OPT_KEY("suid",KEY_KERN_FLAG), FUSE_OPT_KEY("nosuid",KEY_KERN_FLAG), FUSE_OPT_KEY("dev",KEY_KERN_FLAG), FUSE_OPT_KEY("nodev",KEY_KERN_FLAG), FUSE_OPT_KEY("exec",KEY_KERN_FLAG), FUSE_OPT_KEY("noexec",KEY_KERN_FLAG), FUSE_OPT_KEY("async",KEY_KERN_FLAG), FUSE_OPT_KEY("sync",KEY_KERN_FLAG), FUSE_OPT_KEY("dirsync",KEY_KERN_FLAG), FUSE_OPT_KEY("atime",KEY_KERN_FLAG), FUSE_OPT_KEY("noatime",KEY_KERN_FLAG), FUSE_OPT_KEY("relatime",KEY_KERN_FLAG), FUSE_OPT_KEY("norelatime",KEY_KERN_FLAG), FUSE_OPT_KEY("lazytime",KEY_KERN_FLAG), FUSE_OPT_KEY("nolazytime",KEY_KERN_FLAG), FUSE_OPT_KEY("diratime",KEY_KERN_FLAG), FUSE_OPT_KEY("nodiratime",KEY_KERN_FLAG), FUSE_OPT_KEY("strictatime",KEY_KERN_FLAG), FUSE_OPT_KEY("nostrictatime",KEY_KERN_FLAG), FUSE_OPT_END }; static void exec_fusermount(const char *argv[]) { execv(FUSERMOUNT_DIR "/mergerfs-" FUSERMOUNT_PROG,(char**)argv); execvp("mergerfs-" FUSERMOUNT_PROG,(char**)argv); execv(FUSERMOUNT_DIR "/" FUSERMOUNT_PROG,(char**)argv); execvp(FUSERMOUNT_PROG,(char**)argv); execv(FUSERMOUNT_DIR "/" FUSERMOUNT3_PROG,(char**)argv); execvp(FUSERMOUNT3_PROG,(char**)argv); } struct mount_flags { const char *opt; unsigned long flag; int on; }; static const struct mount_flags mount_flags[] = { { "rw", MS_RDONLY, 0 }, { "ro", MS_RDONLY, 1 }, { "suid", MS_NOSUID, 0 }, { "nosuid", MS_NOSUID, 1 }, { "dev", MS_NODEV, 0 }, { "nodev", MS_NODEV, 1 }, { "exec", MS_NOEXEC, 0 }, { "noexec", MS_NOEXEC, 1 }, { "async", MS_SYNCHRONOUS, 0 }, { "sync", MS_SYNCHRONOUS, 1 }, { "atime", MS_NOATIME, 0 }, { "noatime", MS_NOATIME, 1 }, { "relatime", MS_RELATIME, 1 }, { "norelatime", MS_RELATIME, 0 }, { "lazytime", MS_LAZYTIME, 1 }, { "nolazytime", MS_LAZYTIME, 0 }, { "diratime", MS_NODIRATIME, 0 }, { "nodiratime", MS_NODIRATIME, 1 }, { "strictatime", MS_STRICTATIME, 1 }, { "nostrictatime", MS_STRICTATIME, 0 }, #ifndef __NetBSD__ { "dirsync", MS_DIRSYNC, 1 }, #endif { NULL, 0, 0 } }; static void set_mount_flag(const char *s, int *flags) { int i; for(i = 0; mount_flags[i].opt != NULL; i++) { const char *opt = mount_flags[i].opt; if(strcmp(opt, s) == 0) { if(mount_flags[i].on) *flags |= mount_flags[i].flag; else *flags &= ~mount_flags[i].flag; return; } } fprintf(stderr, "mergerfs: internal error, can't find mount flag\n"); abort(); } static int fuse_mount_opt_proc(void *data, const char *arg, int key, struct fuse_args *outargs) { struct mount_opts *mo = static_cast(data); switch(key) { case KEY_RO: arg = "ro"; /* fall through */ case KEY_KERN_FLAG: set_mount_flag(arg, &mo->flags); return 0; case KEY_KERN_OPT: return fuse_opt_add_opt(&mo->kernel_opts, arg); case KEY_FUSERMOUNT_OPT: return fuse_opt_add_opt_escaped(&mo->fusermount_opts, arg); case KEY_SUBTYPE_OPT: return fuse_opt_add_opt(&mo->subtype_opt, arg); case KEY_MTAB_OPT: return fuse_opt_add_opt(&mo->mtab_opts, arg); case FUSE_OPT_KEY_OPT: if(strncmp("x-", arg, 2) == 0) return fuse_opt_add_opt(&mo->mtab_opts, arg); return 1; } /* passthrough unknown options */ return 1; } /* return value: * >= 0 => fd * -1 => error */ static int receive_fd(int fd) { struct msghdr msg; struct iovec iov; char buf[1]; int rv; size_t ccmsg[CMSG_SPACE(sizeof(int)) / sizeof(size_t)]; struct cmsghdr *cmsg; iov.iov_base = buf; iov.iov_len = 1; memset(&msg, 0, sizeof(msg)); msg.msg_name = 0; msg.msg_namelen = 0; msg.msg_iov = &iov; msg.msg_iovlen = 1; /* old BSD implementations should use msg_accrights instead of * msg_control; the interface is different. */ msg.msg_control = ccmsg; msg.msg_controllen = sizeof(ccmsg); while(((rv = recvmsg(fd, &msg, 0)) == -1) && errno == EINTR) ; if(rv == -1) { perror("recvmsg"); return -1; } if(!rv) { /* EOF */ return -1; } cmsg = CMSG_FIRSTHDR(&msg); if(!cmsg) { fprintf(stderr, "no control message received or not enough buf space\n"); return -1; } if(cmsg->cmsg_type != SCM_RIGHTS) { fprintf(stderr, "got control message of unknown type %d\n", cmsg->cmsg_type); return -1; } return *reinterpret_cast(CMSG_DATA(cmsg)); } void fuse_kern_unmount(const char *mountpoint, int fd) { int res; int pid; if(!mountpoint) return; if(fd != -1) { struct pollfd pfd; pfd.fd = fd; pfd.events = 0; res = poll(&pfd, 1, 0); /* Need to close file descriptor, otherwise synchronous umount would recurse into filesystem, and deadlock. Caller expects fuse_kern_unmount to close the fd, so close it anyway. */ close(fd); /* If file poll returns POLLERR on the device file descriptor, then the filesystem is already unmounted */ if(res == 1 &&(pfd.revents & POLLERR)) return; } if(geteuid() == 0) { fuse_mnt_umount("fuse", mountpoint, mountpoint, 1); return; } res = umount2(mountpoint,MNT_DETACH); if(res == 0) return; pid = fork(); if(pid == -1) return; if(pid == 0) { const char *argv[] = { FUSERMOUNT_PROG, "-u", "-q", "-z", "--", mountpoint, NULL }; exec_fusermount(argv); perror("mergerfs: failed to exec fusermount"); _exit(1); } waitpid(pid, NULL, 0); } static int fuse_mount_fusermount(const char *mountpoint, struct mount_opts *mo, const char *opts, int quiet) { int fds[2]; int pid; int res; int rv; if(!mountpoint) { fprintf(stderr, "mergerfs: missing mountpoint parameter\n"); return -1; } res = socketpair(PF_UNIX, SOCK_STREAM, 0, fds); if(res == -1) { perror("mergerfs: socketpair() failed"); return -1; } pid = fork(); if(pid == -1) { perror("mergerfs: fork() failed"); close(fds[0]); close(fds[1]); return -1; } if(pid == 0) { char env[10]; const char *argv[32]; int a = 0; if(quiet) { int fd = open("/dev/null", O_RDONLY); if(fd != -1) { dup2(fd, 1); dup2(fd, 2); } } argv[a++] = FUSERMOUNT_PROG; if(opts) { argv[a++] = "-o"; argv[a++] = opts; } argv[a++] = "--"; argv[a++] = mountpoint; argv[a++] = NULL; close(fds[1]); fcntl(fds[0], F_SETFD, 0); snprintf(env, sizeof(env), "%i", fds[0]); setenv(FUSE_COMMFD_ENV, env, 1); exec_fusermount(argv); perror("mergerfs: failed to exec fusermount"); _exit(1); } close(fds[0]); rv = receive_fd(fds[1]); if(!mo->auto_unmount) { /* with auto_unmount option fusermount will not exit until this socket is closed */ close(fds[1]); waitpid(pid, NULL, 0); /* bury zombie */ } return rv; } static int fuse_mount_sys(const char *mnt, struct mount_opts *mo, const char *mnt_opts) { int fd; int res; char tmp[128]; const char *devname = "/dev/fuse"; std::string source; std::string type; struct stat stbuf; if(!mnt) { fprintf(stderr, "mergerfs: missing mountpoint parameter\n"); return -1; } res = stat(mnt, &stbuf); if(res == -1) { switch(errno) { case ENOTCONN: fprintf(stderr, "mergerfs: mountpoint %s appears to be a broken FUSE mount; " "unmounting it\n", mnt); res = umount(mnt); if(res == 0) fprintf(stderr, "mergerfs: umount succeeded"); break; default: fprintf(stderr, "mergerfs: failed to access mountpoint %s: %s; " "attempting to mount anyway\n", mnt, strerror(errno)); break; } memset(&stbuf, 0, sizeof(stbuf)); res = stat(mnt,&stbuf); if(res == -1) stbuf.st_mode = S_IFDIR; } if(mo->auto_unmount) { /* Tell the caller to fallback to fusermount because auto-unmount does not work otherwise. */ return -2; } fd = open(devname, O_RDWR); if(fd == -1) { if(errno == ENODEV || errno == ENOENT) fprintf(stderr, "mergerfs: device not found, try 'modprobe fuse' first\n"); else fprintf(stderr, "mergerfs: failed to open %s: %s\n", devname, strerror(errno)); return -1; } snprintf(tmp, sizeof(tmp), "fd=%i,rootmode=%o,user_id=%u,group_id=%u", fd, (stbuf.st_mode & S_IFMT), getuid(), getgid()); res = fuse_opt_add_opt(&mo->kernel_opts, tmp); if(res == -1) goto out_close; type = mo->blkdev ? "fuseblk" : "fuse"; if(mo->subtype) type += "." + std::string(mo->subtype); source = mo->fsname ? mo->fsname :(mo->subtype ? mo->subtype : devname); res = mount(source.c_str(), mnt, type.c_str(), mo->flags, mo->kernel_opts); if(res == -1 && errno == ENODEV && mo->subtype) { /* Probably missing subtype support */ type = mo->blkdev ? "fuseblk" : "fuse"; if(mo->fsname) { if(!mo->blkdev) source = std::string(mo->subtype) + "#" + mo->fsname; } else { source = type; } res = mount(source.c_str(), mnt, type.c_str(), mo->flags, mo->kernel_opts); } if(res == -1) { /* * Maybe kernel doesn't support unprivileged mounts, in this * case try falling back to fusermount */ if(errno == EPERM) { res = -2; } else { int errno_save = errno; if(mo->blkdev && errno == ENODEV && !fuse_mnt_check_fuseblk()) { fprintf(stderr,"mergerfs: 'fuseblk' support missing\n"); } else { fprintf(stderr, "mergerfs: mount failed: %s\n", strerror(errno_save)); } } goto out_close; } #ifndef __NetBSD__ #ifndef IGNORE_MTAB if(geteuid() == 0) { char *newmnt = fuse_mnt_resolve_path("fuse", mnt); res = -1; if(!newmnt) goto out_umount; res = fuse_mnt_add_mount("fuse", source.c_str(), newmnt, type.c_str(), mnt_opts); free(newmnt); if(res == -1) goto out_umount; } #endif /* IGNORE_MTAB */ #endif /* __NetBSD__ */ return fd; out_umount: umount2(mnt,MNT_DETACH); /* lazy umount */ out_close: close(fd); return res; } static int get_mnt_flag_opts(char **mnt_optsp, int flags) { int i; if(!(flags & MS_RDONLY) && fuse_opt_add_opt(mnt_optsp, "rw") == -1) return -1; for(i = 0; mount_flags[i].opt != NULL; i++) { if(mount_flags[i].on &&(flags & mount_flags[i].flag) && fuse_opt_add_opt(mnt_optsp, mount_flags[i].opt) == -1) return -1; } return 0; } int fuse_kern_mount(const char *mountpoint, struct fuse_args *args) { struct mount_opts mo; int res = -1; char *mnt_opts = NULL; memset(&mo, 0, sizeof(mo)); mo.flags = MS_NOSUID | MS_NODEV; if(args && fuse_opt_parse(args, &mo, fuse_mount_opts, fuse_mount_opt_proc) == -1) return -1; res = -1; if(get_mnt_flag_opts(&mnt_opts, mo.flags) == -1) goto out; if(mo.kernel_opts && fuse_opt_add_opt(&mnt_opts, mo.kernel_opts) == -1) goto out; if(mo.mtab_opts && fuse_opt_add_opt(&mnt_opts, mo.mtab_opts) == -1) goto out; res = fuse_mount_sys(mountpoint, &mo, mnt_opts); if(res == -2) { if(mo.fusermount_opts && fuse_opt_add_opt(&mnt_opts, mo.fusermount_opts) == -1) goto out; if(mo.subtype) { char *tmp_opts = NULL; res = -1; if((fuse_opt_add_opt(&tmp_opts, mnt_opts) == -1) || (fuse_opt_add_opt(&tmp_opts, mo.subtype_opt) == -1)) { free(tmp_opts); goto out; } res = fuse_mount_fusermount(mountpoint, &mo, tmp_opts, 1); free(tmp_opts); if(res == -1) res = fuse_mount_fusermount(mountpoint, &mo, mnt_opts, 0); } else { res = fuse_mount_fusermount(mountpoint, &mo, mnt_opts, 0); } } out: free(mnt_opts); free(mo.fsname); free(mo.subtype); free(mo.fusermount_opts); free(mo.subtype_opt); free(mo.kernel_opts); free(mo.mtab_opts); return res; }