| /* |
| FUSE: Filesystem in Userspace |
| Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu> |
| |
| Architecture-independent mounting code. |
| |
| This program can be distributed under the terms of the GNU LGPLv2. |
| See the file LGPL2.txt. |
| */ |
| |
| #define _GNU_SOURCE |
| |
| #include "fuse_config.h" |
| #include "fuse_log.h" |
| #include "mount_util.h" |
| #ifdef __linux__ |
| #include "mount_i_linux.h" |
| #endif |
| |
| #include <stdio.h> |
| #include <unistd.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <signal.h> |
| #include <dirent.h> |
| #include <errno.h> |
| #include <fcntl.h> |
| #include <limits.h> |
| #include <paths.h> |
| #if !defined( __NetBSD__) && !defined(__FreeBSD__) && !defined(__DragonFly__) && !defined(__ANDROID__) |
| #include <mntent.h> |
| #else |
| #define IGNORE_MTAB |
| #endif |
| #include <sys/stat.h> |
| #include <sys/wait.h> |
| |
| #include "fuse_mount_compat.h" |
| |
| #include <sys/param.h> |
| #include <spawn.h> |
| |
| /* Environment variable for FUSE kernel device */ |
| #define FUSE_KERN_DEVICE_ENV "FUSE_KERN_DEVICE" |
| #define FUSE_DEF_KERN_DEVICE "/dev/fuse" |
| |
| #if defined(__NetBSD__) || defined(__FreeBSD__) || defined(__DragonFly__) || defined(__FreeBSD_kernel__) |
| #include <sys/mount.h> |
| #ifdef __NetBSD__ |
| #include <perfuse.h> |
| #endif |
| |
| #define umount2(mnt, flags) unmount(mnt, ((flags) == 2) ? MNT_FORCE : 0) |
| |
| #define MS_RDONLY MNT_RDONLY |
| #define MS_NOSUID MNT_NOSUID |
| #ifdef MNT_NODEV |
| #define MS_NODEV MNT_NODEV |
| #else |
| #define MS_NODEV 0 |
| #endif |
| #define MS_NOEXEC MNT_NOEXEC |
| #define MS_SYNCHRONOUS MNT_SYNCHRONOUS |
| #define MS_NOATIME MNT_NOATIME |
| #define MS_NOSYMFOLLOW 0 |
| #define MS_DIRSYNC 0 |
| |
| /* BSD doesn't have these, define as 0 */ |
| #ifndef MS_NODIRATIME |
| #define MS_NODIRATIME 0 |
| #endif |
| #ifndef MS_RELATIME |
| #define MS_RELATIME 0 |
| #endif |
| #ifndef MS_STRICTATIME |
| #define MS_STRICTATIME 0 |
| #endif |
| |
| #endif /* BSD */ |
| |
| /* |
| * MOUNT_ATTR_* constants from linux/mount.h for the new mount API. |
| * If not available from system headers (older kernels), define to 0 |
| * so the mount_attr field will indicate these are fsconfig flags. |
| * The new mount API (fsmount) won't be available on such systems anyway. |
| */ |
| #ifndef MOUNT_ATTR_RDONLY |
| #define MOUNT_ATTR_RDONLY 0 |
| #endif |
| #ifndef MOUNT_ATTR_NOSUID |
| #define MOUNT_ATTR_NOSUID 0 |
| #endif |
| #ifndef MOUNT_ATTR_NODEV |
| #define MOUNT_ATTR_NODEV 0 |
| #endif |
| #ifndef MOUNT_ATTR_NOEXEC |
| #define MOUNT_ATTR_NOEXEC 0 |
| #endif |
| #ifndef MOUNT_ATTR_NOATIME |
| #define MOUNT_ATTR_NOATIME 0 |
| #endif |
| #ifndef MOUNT_ATTR_RELATIME |
| #define MOUNT_ATTR_RELATIME 0 |
| #endif |
| #ifndef MOUNT_ATTR_STRICTATIME |
| #define MOUNT_ATTR_STRICTATIME 0 |
| #endif |
| #ifndef MOUNT_ATTR_NODIRATIME |
| #define MOUNT_ATTR_NODIRATIME 0 |
| #endif |
| #ifndef MOUNT_ATTR_NOSYMFOLLOW |
| #define MOUNT_ATTR_NOSYMFOLLOW 0 |
| #endif |
| |
| /* |
| * is_fsconfig and mount_attr are independent: ro/rw need both legs |
| * (SB_RDONLY on the superblock via fsconfig SET_FLAG, plus |
| * MOUNT_ATTR_RDONLY on the vfsmount via fsmount). Everything else is |
| * exclusively one or the other. |
| */ |
| const struct mount_flags mount_flags[] = { |
| /* opt flag on safe fsconfig mount_attr */ |
| {"rw", MS_RDONLY, 0, 1, 1, MOUNT_ATTR_RDONLY}, |
| {"ro", MS_RDONLY, 1, 1, 1, MOUNT_ATTR_RDONLY}, |
| {"suid", MS_NOSUID, 0, 0, 0, MOUNT_ATTR_NOSUID}, |
| {"nosuid", MS_NOSUID, 1, 1, 0, MOUNT_ATTR_NOSUID}, |
| {"dev", MS_NODEV, 0, 1, 0, MOUNT_ATTR_NODEV}, |
| {"nodev", MS_NODEV, 1, 1, 0, MOUNT_ATTR_NODEV}, |
| {"exec", MS_NOEXEC, 0, 1, 0, MOUNT_ATTR_NOEXEC}, |
| {"noexec", MS_NOEXEC, 1, 1, 0, MOUNT_ATTR_NOEXEC}, |
| {"async", MS_SYNCHRONOUS, 0, 1, 1, 0}, |
| {"sync", MS_SYNCHRONOUS, 1, 1, 1, 0}, |
| {"noatime", MS_NOATIME, 1, 1, 0, MOUNT_ATTR_NOATIME}, |
| {"nodiratime", MS_NODIRATIME, 1, 1, 0, MOUNT_ATTR_NODIRATIME}, |
| {"norelatime", MS_RELATIME, 0, 1, 0, MOUNT_ATTR_RELATIME}, |
| {"nostrictatime", MS_STRICTATIME, 0, 1, 0, MOUNT_ATTR_STRICTATIME}, |
| {"symfollow", MS_NOSYMFOLLOW, 0, 1, 0, MOUNT_ATTR_NOSYMFOLLOW}, |
| {"nosymfollow", MS_NOSYMFOLLOW, 1, 1, 0, MOUNT_ATTR_NOSYMFOLLOW}, |
| {"dirsync", MS_DIRSYNC, 1, 1, 1, 0}, |
| {NULL, 0, 0, 0, 0, 0} |
| }; |
| |
| #ifdef IGNORE_MTAB |
| #define mtab_needs_update(mnt) 0 |
| #else |
| static int mtab_needs_update(const char *mnt) |
| { |
| int res; |
| struct stat stbuf; |
| |
| /* If mtab is within new mount, don't touch it */ |
| if (strncmp(mnt, _PATH_MOUNTED, strlen(mnt)) == 0 && |
| _PATH_MOUNTED[strlen(mnt)] == '/') |
| return 0; |
| |
| /* |
| * Skip mtab update if /etc/mtab: |
| * |
| * - doesn't exist, |
| * - is on a read-only filesystem. |
| */ |
| res = lstat(_PATH_MOUNTED, &stbuf); |
| if (res == -1) { |
| if (errno == ENOENT) |
| return 0; |
| } else { |
| uid_t ruid; |
| int err; |
| |
| ruid = getuid(); |
| if (ruid != 0) |
| setreuid(0, -1); |
| |
| res = access(_PATH_MOUNTED, W_OK); |
| err = (res == -1) ? errno : 0; |
| if (ruid != 0) |
| setreuid(ruid, -1); |
| |
| if (err == EROFS) |
| return 0; |
| |
| res = access("/run/mount/utab", F_OK); |
| if (res == -1) |
| return 0; |
| } |
| |
| return 1; |
| } |
| #endif /* IGNORE_MTAB */ |
| |
| /* |
| * @brief Spawn @cmd with a clean environment and the given signal mask. |
| * |
| * @param[out] pid Child pid on success. |
| * @param[in] cmd Absolute path of the executable. |
| * @param[in] argv NULL-terminated argument vector. |
| * @param[in] mask Signal mask to install in the child. |
| * @return 0 on success, positive errno on failure. |
| */ |
| static int fuse_mount_spawn(pid_t *pid, const char *cmd, char *const argv[], |
| sigset_t *mask) |
| { |
| posix_spawnattr_t attr; |
| char *const envp[] = { NULL }; |
| uid_t ruid = getuid(); |
| uid_t euid = geteuid(); |
| int res; |
| |
| res = posix_spawnattr_init(&attr); |
| if (res) { |
| fuse_log(FUSE_LOG_ERR, "Failed to init posix_spawn attr for cmd %s: %s\n", |
| cmd, strerror(res)); |
| return res; |
| } |
| |
| res = posix_spawnattr_setflags(&attr, POSIX_SPAWN_SETSIGMASK); |
| if (res) { |
| fuse_log(FUSE_LOG_ERR, "Failed to set posix_spawn flags for cmd %s: %s\n", |
| cmd, strerror(res)); |
| goto out_destroy; |
| } |
| |
| res = posix_spawnattr_setsigmask(&attr, mask); |
| if (res) { |
| fuse_log(FUSE_LOG_ERR, "Failed to set posix_spawn sigmask for cmd %s: %s\n", |
| cmd, strerror(res)); |
| goto out_destroy; |
| } |
| |
| /* |
| * Reached via setuid-root fusermount (ruid != euid, euid 0) or via |
| * lib/mount.c in a privileged daemon after a direct mount (ruid == |
| * euid). /bin/mount keys "restricted" mode off the real uid, so raise |
| * it to euid for the fusermount case; ruid == euid needs no change. |
| * Done in this single-threaded parent as posix_spawn cannot do it. |
| */ |
| if (ruid != euid && setreuid(euid, -1) == -1) { |
| res = errno; |
| fuse_log(FUSE_LOG_ERR, "Failed to set real uid for cmd %s: %s\n", |
| cmd, strerror(res)); |
| goto out_destroy; |
| } |
| |
| res = posix_spawn(pid, cmd, NULL, &attr, argv, envp); |
| |
| /* |
| * A failed restore would leave this process with the raised real uid; |
| * refuse to continue rather than run on privileged. |
| */ |
| if (ruid != euid && setreuid(ruid, -1) == -1) { |
| fuse_log(FUSE_LOG_ERR, "Failed to restore real uid for cmd %s: %s\n", |
| cmd, strerror(errno)); |
| abort(); |
| } |
| |
| out_destroy: |
| posix_spawnattr_destroy(&attr); |
| return res; |
| } |
| |
| /* |
| * @return caller expects 0 or -1 |
| */ |
| static int add_mount(const char *progname, const char *fsname, |
| const char *mnt, const char *type, const char *opts) |
| { |
| int res; |
| int status; |
| pid_t pid; |
| sigset_t blockmask; |
| sigset_t oldmask; |
| const char *cmd = "/bin/mount"; |
| |
| sigemptyset(&blockmask); |
| sigaddset(&blockmask, SIGCHLD); |
| res = sigprocmask(SIG_BLOCK, &blockmask, &oldmask); |
| if (res == -1) { |
| fuse_log(FUSE_LOG_ERR, "%s: sigprocmask: %s\n", progname, strerror(errno)); |
| return -1; |
| } |
| |
| char const * const argv[] = { |
| cmd, "--no-canonicalize", "-i", "-f", "-t", type, "-o", opts, |
| fsname, mnt, NULL |
| }; |
| |
| res = fuse_mount_spawn(&pid, cmd, (char * const *) argv, &oldmask); |
| if (res) { |
| fuse_log(FUSE_LOG_ERR, "%s: failed to execute %s: %s\n", |
| progname, cmd, strerror(res)); |
| res = -1; |
| goto out_restore; |
| } |
| |
| res = waitpid(pid, &status, 0); |
| if (res == -1) |
| fuse_log(FUSE_LOG_ERR, "%s: waitpid of %d: %s\n", progname, |
| pid, strerror(errno)); |
| else |
| res = (WIFEXITED(status) && WEXITSTATUS(status) == 0) ? 0 : -1; |
| |
| out_restore: |
| sigprocmask(SIG_SETMASK, &oldmask, NULL); |
| |
| return res; |
| } |
| |
| int fuse_mnt_add_mount(const char *progname, const char *fsname, |
| const char *mnt, const char *type, const char *opts) |
| { |
| if (!mtab_needs_update(mnt)) |
| return 0; |
| |
| return add_mount(progname, fsname, mnt, type, opts); |
| } |
| |
| static int exec_umount(const char *progname, const char *rel_mnt, int lazy) |
| { |
| int res; |
| int status; |
| pid_t pid; |
| sigset_t blockmask; |
| sigset_t oldmask; |
| const char *cmd = "/bin/umount"; |
| |
| sigemptyset(&blockmask); |
| sigaddset(&blockmask, SIGCHLD); |
| res = sigprocmask(SIG_BLOCK, &blockmask, &oldmask); |
| if (res == -1) { |
| fuse_log(FUSE_LOG_ERR, "%s: sigprocmask: %s\n", progname, strerror(errno)); |
| return -1; |
| } |
| |
| char const * const argv_lazy[] = { |
| cmd, "-i", "-l", rel_mnt, NULL |
| }; |
| char const * const argv_normal[] = { |
| cmd, "-i", rel_mnt, NULL |
| }; |
| char const * const *argv = lazy ? argv_lazy : argv_normal; |
| |
| res = fuse_mount_spawn(&pid, cmd, (char * const *) argv, &oldmask); |
| if (res) { |
| fuse_log(FUSE_LOG_ERR, "%s: failed to execute %s: %s\n", |
| progname, cmd, strerror(res)); |
| res = -1; |
| goto out_restore; |
| } |
| |
| res = waitpid(pid, &status, 0); |
| if (res == -1) |
| fuse_log(FUSE_LOG_ERR, "%s: waitpid: %s\n", progname, strerror(errno)); |
| else |
| res = (WIFEXITED(status) && WEXITSTATUS(status) == 0) ? 0 : -1; |
| |
| out_restore: |
| sigprocmask(SIG_SETMASK, &oldmask, NULL); |
| return res; |
| |
| } |
| |
| int fuse_mnt_umount(const char *progname, const char *abs_mnt, |
| const char *rel_mnt, int lazy) |
| { |
| if (!mtab_needs_update(abs_mnt)) { |
| int res = umount2(rel_mnt, lazy ? 2 : 0); |
| if (res == -1) |
| fuse_log(FUSE_LOG_ERR, "%s: failed to unmount %s: %s\n", |
| progname, abs_mnt, strerror(errno)); |
| return res; |
| } |
| |
| return exec_umount(progname, rel_mnt, lazy); |
| } |
| |
| static int remove_mount(const char *progname, const char *mnt) |
| { |
| int res; |
| int status; |
| pid_t pid; |
| sigset_t blockmask; |
| sigset_t oldmask; |
| const char *cmd = "/bin/umount"; |
| |
| sigemptyset(&blockmask); |
| sigaddset(&blockmask, SIGCHLD); |
| res = sigprocmask(SIG_BLOCK, &blockmask, &oldmask); |
| if (res == -1) { |
| fuse_log(FUSE_LOG_ERR, "%s: sigprocmask: %s\n", progname, strerror(errno)); |
| return -1; |
| } |
| |
| char const * const argv[] = { |
| cmd, "--no-canonicalize", "-i", "--fake", mnt, NULL |
| }; |
| |
| res = fuse_mount_spawn(&pid, cmd, (char * const *) argv, &oldmask); |
| if (res) { |
| fuse_log(FUSE_LOG_ERR, "%s: failed to execute %s: %s\n", |
| progname, cmd, strerror(res)); |
| res = -1; |
| goto out_restore; |
| } |
| |
| res = waitpid(pid, &status, 0); |
| if (res == -1) |
| fuse_log(FUSE_LOG_ERR, "%s: waitpid: %s\n", progname, strerror(errno)); |
| else |
| res = (WIFEXITED(status) && WEXITSTATUS(status) == 0) ? 0 : -1; |
| |
| out_restore: |
| sigprocmask(SIG_SETMASK, &oldmask, NULL); |
| return res; |
| } |
| |
| int fuse_mnt_remove_mount(const char *progname, const char *mnt) |
| { |
| if (!mtab_needs_update(mnt)) |
| return 0; |
| |
| return remove_mount(progname, mnt); |
| } |
| |
| char *fuse_mnt_resolve_path(const char *progname, const char *orig) |
| { |
| char buf[PATH_MAX]; |
| char *copy; |
| char *dst; |
| char *end; |
| const char *lastcomp; |
| const char *toresolv; |
| |
| if (!orig[0]) { |
| fuse_log(FUSE_LOG_ERR, "%s: invalid mountpoint '%s'\n", progname, |
| orig); |
| return NULL; |
| } |
| |
| copy = strdup(orig); |
| if (copy == NULL) { |
| fuse_log(FUSE_LOG_ERR, "%s: failed to allocate memory\n", progname); |
| return NULL; |
| } |
| |
| toresolv = copy; |
| lastcomp = NULL; |
| for (end = copy + strlen(copy) - 1; end > copy && *end == '/'; end --); |
| if (end[0] != '/') { |
| char *tmp; |
| end[1] = '\0'; |
| tmp = strrchr(copy, '/'); |
| if (tmp == NULL) { |
| lastcomp = copy; |
| toresolv = "."; |
| } else { |
| lastcomp = tmp + 1; |
| if (tmp == copy) |
| toresolv = "/"; |
| } |
| if (strcmp(lastcomp, ".") == 0 || strcmp(lastcomp, "..") == 0) { |
| lastcomp = NULL; |
| toresolv = copy; |
| } |
| else if (tmp) |
| tmp[0] = '\0'; |
| } |
| if (realpath(toresolv, buf) == NULL) { |
| fuse_log(FUSE_LOG_ERR, "%s: bad mount point %s: %s\n", progname, orig, |
| strerror(errno)); |
| free(copy); |
| return NULL; |
| } |
| if (lastcomp == NULL) |
| dst = strdup(buf); |
| else { |
| dst = (char *) malloc(strlen(buf) + 1 + strlen(lastcomp) + 1); |
| if (dst) { |
| unsigned buflen = strlen(buf); |
| if (buflen && buf[buflen-1] == '/') |
| sprintf(dst, "%s%s", buf, lastcomp); |
| else |
| sprintf(dst, "%s/%s", buf, lastcomp); |
| } |
| } |
| free(copy); |
| if (dst == NULL) |
| fuse_log(FUSE_LOG_ERR, "%s: failed to allocate memory\n", progname); |
| return dst; |
| } |
| |
| int fuse_mnt_check_fuseblk(void) |
| { |
| char buf[256]; |
| FILE *f = fopen("/proc/filesystems", "r"); |
| if (!f) |
| return 1; |
| |
| while (fgets(buf, sizeof(buf), f)) |
| if (strstr(buf, "fuseblk\n")) { |
| fclose(f); |
| return 1; |
| } |
| |
| fclose(f); |
| return 0; |
| } |
| |
| int fuse_mnt_parse_fuse_fd(const char *mountpoint) |
| { |
| unsigned int fd; |
| int len = 0; |
| |
| if (mountpoint == NULL) { |
| fuse_log(FUSE_LOG_ERR, "Invalid null-ptr mount-point!\n"); |
| return -1; |
| } |
| |
| if (sscanf(mountpoint, "/dev/fd/%u%n", &fd, &len) == 1 && |
| len == strlen(mountpoint)) { |
| if (fd > INT_MAX) { |
| fuse_log(FUSE_LOG_ERR, "%s: fd=%u > INT_MAX\n", __func__, fd); |
| return -1; |
| } |
| return (int)fd; |
| } |
| |
| return -1; |
| } |
| |
| int fuse_mnt_add_mount_helper(const char *mnt, const char *source, |
| const char *type, const char *mtab_opts) |
| { |
| #ifndef IGNORE_MTAB |
| if (geteuid() == 0) { |
| char *newmnt = fuse_mnt_resolve_path("fuse", mnt); |
| int res; |
| |
| if (!newmnt) |
| return -1; |
| |
| res = fuse_mnt_add_mount("fuse", source, newmnt, type, |
| mtab_opts); |
| free(newmnt); |
| return res; |
| } |
| #endif |
| (void)mnt; |
| (void)source; |
| (void)type; |
| (void)mtab_opts; |
| return 0; |
| } |
| |
| const char *fuse_mnt_get_devname(void) |
| { |
| const char *devname = getenv(FUSE_KERN_DEVICE_ENV); |
| struct stat stbuf; |
| |
| if (!devname) |
| return FUSE_DEF_KERN_DEVICE; |
| |
| /* Validation 1: Must start with /dev/ */ |
| if (strncmp(devname, "/dev/", 5) != 0) { |
| fuse_log(FUSE_LOG_ERR, |
| "fuse: invalid device name from %s: %s (must start with /dev/)\n", |
| FUSE_KERN_DEVICE_ENV, devname); |
| return "/dev/fuse"; |
| } |
| |
| /* Validation 2: Must be a character device */ |
| if (stat(devname, &stbuf) == 0) { |
| if (!S_ISCHR(stbuf.st_mode)) { |
| fuse_log(FUSE_LOG_ERR, |
| "fuse: invalid device from %s: %s (not a character device)\n", |
| FUSE_KERN_DEVICE_ENV, devname); |
| return "/dev/fuse"; |
| } |
| /* Valid character device */ |
| return devname; |
| } |
| /* |
| * stat() failed - device might not exist yet, but path is valid. |
| * Let it through and fail later in open() with a better error message. |
| */ |
| return devname; |
| } |
| |
| char *fuse_mnt_build_source(const char *fsname, const char *subtype, |
| const char *devname, bool use_subtype_prefix) |
| { |
| char *source; |
| int ret; |
| |
| if (use_subtype_prefix && fsname && subtype) { |
| ret = asprintf(&source, "%s#%s", subtype, fsname); |
| } else { |
| const char *src_str; |
| |
| if (fsname) |
| src_str = fsname; |
| else if (subtype) |
| src_str = subtype; |
| else |
| src_str = devname; |
| ret = asprintf(&source, "%s", src_str); |
| } |
| |
| if (ret == -1) |
| return NULL; |
| |
| return source; |
| } |
| |
| char *fuse_mnt_build_type(int blkdev, const char *subtype) |
| { |
| char *type; |
| int ret; |
| |
| if (subtype) |
| ret = asprintf(&type, "%s.%s", blkdev ? "fuseblk" : "fuse", |
| subtype); |
| else |
| ret = asprintf(&type, "%s", blkdev ? "fuseblk" : "fuse"); |
| |
| if (ret == -1) |
| return NULL; |
| |
| return type; |
| } |