/* FUSE: Filesystem in Userspace Copyright (C) 2001-2007 Miklos Szeredi Architecture specific file system mounting (Linux). This program can be distributed under the terms of the GNU LGPLv2. See the file COPYING.LIB. */ /* For environ */ #define _GNU_SOURCE #include "fuse_config.h" #include "fuse_i.h" #include "fuse_misc.h" #include "fuse_opt.h" #include "mount_util.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include "fuse_mount_compat.h" #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 #define MS_NOSYMFOLLOW MNT_NOSYMFOLLOW #define umount2(mnt, flags) unmount(mnt, (flags == 2) ? MNT_FORCE : 0) #endif #define FUSERMOUNT_PROG "fusermount3" #define FUSE_COMMFD_ENV "_FUSE_COMMFD" #define FUSE_COMMFD2_ENV "_FUSE_COMMFD2" #ifndef MS_DIRSYNC #define MS_DIRSYNC 128 #endif /* === new-mount-API helpers (Linux only) ================================= */ #if defined(__linux__) #include #include #include #include #include #include #include #include /* Fallback-Defines, falls ältere Kernel-Header verwendet werden */ #ifndef FSCONFIG_SET_FLAG # define FSCONFIG_SET_FLAG 0 # define FSCONFIG_SET_STRING 1 # define FSCONFIG_SET_BINARY 2 # define FSCONFIG_SET_PATH 3 # define FSCONFIG_SET_PATH_EMPTY 4 # define FSCONFIG_SET_FD 5 # define FSCONFIG_CMD_CREATE 6 # define FSCONFIG_CMD_RECONFIGURE 7 #endif #ifndef MOVE_MOUNT_F_EMPTY_PATH # define MOVE_MOUNT_F_EMPTY_PATH 0x00000004 #endif /* Syscall-Nummern fallbacken, wenn libc sie nicht kennt */ #ifndef SYS_fsopen # if defined(__x86_64__) # define SYS_fsopen 430 # elif defined(__aarch64__) # define SYS_fsopen 434 # endif #endif #ifndef SYS_fsconfig # if defined(__x86_64__) # define SYS_fsconfig 431 # elif defined(__aarch64__) # define SYS_fsconfig 435 # endif #endif #ifndef SYS_fsmount # if defined(__x86_64__) # define SYS_fsmount 432 # elif defined(__aarch64__) # define SYS_fsmount 436 # endif #endif #ifndef SYS_move_mount # if defined(__x86_64__) # define SYS_move_mount 429 # elif defined(__aarch64__) # define SYS_move_mount 433 # endif #endif static inline int x_fsopen(const char *fsname, unsigned int flags) { errno = 0; return (int)syscall(SYS_fsopen, fsname, flags); } static inline int x_fsconfig(int fd, unsigned int cmd, const char *key, const void *val, int aux) { errno = 0; return (int)syscall(SYS_fsconfig, fd, cmd, key, val, aux); } static inline int x_fsmount(int fsfd, unsigned int flags, unsigned int ms_flags) { errno = 0; return (int)syscall(SYS_fsmount, fsfd, flags, ms_flags); } static inline int x_move_mount(int from_dfd, const char *from_pathname, int to_dfd, const char *to_pathname, unsigned int flags) { errno = 0; return (int)syscall(SYS_move_mount, from_dfd, from_pathname, to_dfd, to_pathname, flags); } /* robustes Splitten der -o Kette: - Respektiert Backslashes und '"/" Quotes - Ruft cb(key, value|NULL) pro Teiloption auf (value==NULL => reines Flag) */ struct opt_cb { int (*cb)(const char *key, const char *val, void *userdata); void *userdata; }; static void for_each_o_subopt(const char *oarg, struct opt_cb *h) { const char *p = oarg, *start = oarg; bool esc = false, in_sq = false, in_dq = false; for (; *p; ++p) { if (esc) { esc = false; continue; } if (*p == '\\') { esc = true; continue; } if (*p == '\'' && !in_dq) { in_sq = !in_sq; continue; } if (*p == '\"' && !in_sq) { in_dq = !in_dq; continue; } if (*p == ',' && !in_sq && !in_dq) { size_t len = (size_t)(p - start); if (len) { char *tmp = (char*)malloc(len + 1); memcpy(tmp, start, len); tmp[len] = '\0'; char *eq = strchr(tmp, '='); if (eq) { *eq = '\0'; h->cb(tmp, eq + 1, h->userdata); } else { h->cb(tmp, NULL, h->userdata); } free(tmp); } start = p + 1; } } if (p > start) { size_t len = (size_t)(p - start); char *tmp = (char*)malloc(len + 1); memcpy(tmp, start, len); tmp[len] = '\0'; char *eq = strchr(tmp, '='); if (eq) { *eq = '\0'; h->cb(tmp, eq + 1, h->userdata); } else { h->cb(tmp, NULL, h->userdata); } free(tmp); } } /* Sammelstruktur für Pflicht-Keys */ struct fuse_kv_acc { int fd; bool have_fd; }; static int collect_core_opts_cb(const char *key, const char *val, void *userdata) { struct fuse_kv_acc *acc = (struct fuse_kv_acc*)userdata; if (!key) return 0; if (strcmp(key, "fd") == 0 && val) { acc->fd = atoi(val); acc->have_fd = true; } return 0; } static const char *extract_subtype(const char *type) { if (!type) return NULL; const char *dot = strchr(type, '.'); return dot ? dot + 1 : NULL; } /* C-kompatibler Callback: setzt einen Key/Value mit fsconfig. Überspringt "fd", weil der separat als FSCONFIG_SET_FD gesetzt wird. */ static int fsconfig_apply_cb(const char *key, const char *val, void *userdata) { int fsfd_local = *(int *)userdata; if (!key) return 0; if (strcmp(key, "fd") == 0) return 0; /* schon separat gesetzt */ int rc; if (val && *val) { rc = x_fsconfig(fsfd_local, FSCONFIG_SET_STRING, key, val, 0); } else { rc = x_fsconfig(fsfd_local, FSCONFIG_SET_FLAG, key, NULL, 0); } return (rc < 0) ? -1 : 0; } /* Übergibt ALLE Optionen einzeln (inkl. context=… mit Kommata) via fsconfig */ static int set_all_opts_via_fsconfig(int fsfd, const char *optstr) { if (!optstr || !*optstr) return 0; /* eigener robuster Parser (wie oben), der Kommas in Quotes/mit Backslash respektiert */ const char *s = optstr; const char *p = s, *start = s; bool esc = false, in_sq = false, in_dq = false; for (; *p; ++p) { if (esc) { esc = false; continue; } if (*p == '\\') { esc = true; continue; } if (*p == '\'' && !in_dq) { in_sq = !in_sq; continue; } if (*p == '\"' && !in_sq) { in_dq = !in_dq; continue; } if (*p == ',' && !in_sq && !in_dq) { size_t len = (size_t)(p - start); if (len) { char *tmp = (char*)malloc(len + 1); if (!tmp) { errno = ENOMEM; return -1; } memcpy(tmp, start, len); tmp[len] = '\0'; char *eq = strchr(tmp, '='); int rc; if (eq) { *eq = '\0'; rc = fsconfig_apply_cb(tmp, eq + 1, &fsfd); } else { rc = fsconfig_apply_cb(tmp, NULL, &fsfd); } free(tmp); if (rc < 0) return -1; } start = p + 1; } } /* letztes Segment */ if (p > start) { size_t len = (size_t)(p - start); char *tmp = (char*)malloc(len + 1); if (!tmp) { errno = ENOMEM; return -1; } memcpy(tmp, start, len); tmp[len] = '\0'; char *eq = strchr(tmp, '='); int rc; if (eq) { *eq = '\0'; rc = fsconfig_apply_cb(tmp, eq + 1, &fsfd); } else { rc = fsconfig_apply_cb(tmp, NULL, &fsfd); } free(tmp); if (rc < 0) return -1; } return 0; } /* Hauptfunktion: versucht neue Mount-API; bei Erfolg 0, sonst -1 für Fallback */ static int fuse_mount_with_fsopen_try(const char *type, const char *mountpoint, const char *optstr, unsigned long ms_flags) { int fsfd = x_fsopen(type && strncmp(type, "fuseblk", 7)==0 ? "fuseblk" : "fuse", 0); if (fsfd < 0) { if (errno == ENOSYS) return -1; /* Kernel kann es nicht */ return -1; } /* Pflicht: fd aus optstr ziehen */ struct fuse_kv_acc acc = { .fd = -1, .have_fd = false }; if (optstr && *optstr) { struct opt_cb c = { .cb = collect_core_opts_cb, .userdata = &acc }; for_each_o_subopt(optstr, &c); } if (!acc.have_fd) { close(fsfd); errno = EINVAL; return -1; } /* subtype setzen (falls "fuse.xxx") */ const char *sub = extract_subtype(type); if (sub && *sub) { if (x_fsconfig(fsfd, FSCONFIG_SET_STRING, "subtype", sub, 0) < 0) { int saved = errno; close(fsfd); errno = saved; return -1; } } /* fd reinreichen */ if (x_fsconfig(fsfd, FSCONFIG_SET_FD, "fd", NULL, acc.fd) < 0) { int saved = errno; close(fsfd); errno = saved; return -1; } /* nun alle restlichen Optionen einzeln setzen (inkl. context=… mit Kommata) */ if (set_all_opts_via_fsconfig(fsfd, optstr) < 0) { int saved = errno; close(fsfd); errno = saved; return -1; } if (x_fsconfig(fsfd, FSCONFIG_CMD_CREATE, NULL, NULL, 0) < 0) { int saved = errno; close(fsfd); errno = saved; return -1; } int mfd = x_fsmount(fsfd, 0, (unsigned int)ms_flags); if (mfd < 0) { int saved = errno; close(fsfd); errno = saved; return -1; } if (x_move_mount(mfd, "", AT_FDCWD, mountpoint, MOVE_MOUNT_F_EMPTY_PATH) < 0) { int saved = errno; close(mfd); close(fsfd); errno = saved; return -1; } close(mfd); close(fsfd); return 0; } #endif /* __linux__ */ /* === end new-mount-API helpers ========================================= */ enum { KEY_KERN_FLAG, KEY_KERN_OPT, KEY_FUSERMOUNT_OPT, KEY_SUBTYPE_OPT, KEY_MTAB_OPT, KEY_ALLOW_OTHER, 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; unsigned max_read; }; #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("max_read=%u", max_read), 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("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("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("noatime", KEY_KERN_FLAG), FUSE_OPT_KEY("nodiratime", KEY_KERN_FLAG), FUSE_OPT_KEY("nostrictatime", KEY_KERN_FLAG), FUSE_OPT_KEY("symfollow", KEY_KERN_FLAG), FUSE_OPT_KEY("nosymfollow", KEY_KERN_FLAG), FUSE_OPT_END }; /* * Running fusermount by calling 'posix_spawn' * * @param out_pid might be NULL */ static int fusermount_posix_spawn(posix_spawn_file_actions_t *action, char const * const argv[], pid_t *out_pid) { const char *full_path = FUSERMOUNT_DIR "/" FUSERMOUNT_PROG; pid_t pid; /* See man 7 environ for the global environ pointer */ /* first try the install path */ int status = posix_spawn(&pid, full_path, action, NULL, (char * const *) argv, environ); if (status != 0) { /* if that fails, try a system install */ status = posix_spawnp(&pid, FUSERMOUNT_PROG, action, NULL, (char * const *) argv, environ); } if (status != 0) { fuse_log(FUSE_LOG_ERR, "On calling fusermount posix_spawn failed: %s\n", strerror(status)); return -status; } if (out_pid) *out_pid = pid; else waitpid(pid, NULL, 0); /* FIXME: check exit code and return error if any */ return 0; } void fuse_mount_version(void) { char const *const argv[] = {FUSERMOUNT_PROG, "--version", NULL}; int status = fusermount_posix_spawn(NULL, argv, NULL); if(status != 0) fuse_log(FUSE_LOG_ERR, "Running '%s --version' failed", FUSERMOUNT_PROG); } 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}, {"noatime", MS_NOATIME, 1}, {"nodiratime", MS_NODIRATIME, 1}, {"norelatime", MS_RELATIME, 0}, {"nostrictatime", MS_STRICTATIME, 0}, {"symfollow", MS_NOSYMFOLLOW, 0}, {"nosymfollow", MS_NOSYMFOLLOW, 1}, #ifndef __NetBSD__ {"dirsync", MS_DIRSYNC, 1}, #endif {NULL, 0, 0} }; unsigned get_max_read(struct mount_opts *o) { return o->max_read; } 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; } } fuse_log(FUSE_LOG_ERR, "fuse: 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) { (void) outargs; struct mount_opts *mo = 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); /* Third party options like 'x-gvfs-notrash' */ case FUSE_OPT_KEY_OPT: return (strncmp("x-", arg, 2) == 0) ? fuse_opt_add_opt(&mo->mtab_opts, arg) : 1; } /* Pass through 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) { fuse_log(FUSE_LOG_ERR, "recvmsg failed: %s", strerror(errno)); return -1; } if(!rv) { /* EOF */ return -1; } cmsg = CMSG_FIRSTHDR(&msg); if (cmsg->cmsg_type != SCM_RIGHTS) { fuse_log(FUSE_LOG_ERR, "got control message of unknown type %d\n", cmsg->cmsg_type); return -1; } return *(int*)CMSG_DATA(cmsg); } void fuse_kern_unmount(const char *mountpoint, int fd) { int res; 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 or the connection was severed via /sys/fs/fuse/connections/NNN/abort */ if (res == 1 && (pfd.revents & POLLERR)) return; } if (geteuid() == 0) { fuse_mnt_umount("fuse", mountpoint, mountpoint, 1); return; } res = umount2(mountpoint, 2); if (res == 0) return; char const * const argv[] = { FUSERMOUNT_PROG, "--unmount", "--quiet", "--lazy", "--", mountpoint, NULL }; int status = fusermount_posix_spawn(NULL, argv, NULL); if(status != 0) { fuse_log(FUSE_LOG_ERR, "Spawning %s to unmount failed: %s", FUSERMOUNT_PROG, strerror(-status)); return; } } static int setup_auto_unmount(const char *mountpoint, int quiet) { int fds[2]; pid_t pid; int res; if (!mountpoint) { fuse_log(FUSE_LOG_ERR, "fuse: missing mountpoint parameter\n"); return -1; } res = socketpair(PF_UNIX, SOCK_STREAM, 0, fds); if(res == -1) { fuse_log(FUSE_LOG_ERR, "Setting up auto-unmountsocketpair() failed", strerror(errno)); return -1; } char arg_fd_entry[30]; snprintf(arg_fd_entry, sizeof(arg_fd_entry), "%i", fds[0]); setenv(FUSE_COMMFD_ENV, arg_fd_entry, 1); /* * This helps to identify the FD hold by parent process. * In auto-unmount case, parent process can close this FD explicitly to do unmount. * The FD[1] can be got via getenv(FUSE_COMMFD2_ENV). * One potential use case is to satisfy FD-Leak checks. */ snprintf(arg_fd_entry, sizeof(arg_fd_entry), "%i", fds[1]); setenv(FUSE_COMMFD2_ENV, arg_fd_entry, 1); char const *const argv[] = { FUSERMOUNT_PROG, "--auto-unmount", "--", mountpoint, NULL, }; // TODO: add error handling for all manipulations of action. posix_spawn_file_actions_t action; posix_spawn_file_actions_init(&action); if (quiet) { posix_spawn_file_actions_addopen(&action, STDOUT_FILENO, "/dev/null", O_WRONLY, 0); posix_spawn_file_actions_addopen(&action, STDERR_FILENO, "/dev/null", O_WRONLY, 0); } posix_spawn_file_actions_addclose(&action, fds[1]); /* * auto-umount runs in the background - it is not waiting for the * process */ int status = fusermount_posix_spawn(&action, argv, &pid); posix_spawn_file_actions_destroy(&action); if(status != 0) { close(fds[0]); close(fds[1]); fuse_log(FUSE_LOG_ERR, "fuse: Setting up auto-unmount failed (spawn): %s", strerror(-status)); return -1; } // passed to child now, so can close here. close(fds[0]); // Now fusermount3 will only exit when fds[1] closes automatically when our // process exits. return 0; // Note: fds[1] is leakend and doesn't get FD_CLOEXEC } static int fuse_mount_fusermount(const char *mountpoint, struct mount_opts *mo, const char *opts, int quiet) { int fds[2]; pid_t pid; int res; if (!mountpoint) { fuse_log(FUSE_LOG_ERR, "fuse: missing mountpoint parameter\n"); return -1; } res = socketpair(PF_UNIX, SOCK_STREAM, 0, fds); if(res == -1) { fuse_log(FUSE_LOG_ERR, "Running %s: socketpair() failed: %s\n", FUSERMOUNT_PROG, strerror(errno)); return -1; } char arg_fd_entry[30]; snprintf(arg_fd_entry, sizeof(arg_fd_entry), "%i", fds[0]); setenv(FUSE_COMMFD_ENV, arg_fd_entry, 1); /* * This helps to identify the FD hold by parent process. * In auto-unmount case, parent process can close this FD explicitly to do unmount. * The FD[1] can be got via getenv(FUSE_COMMFD2_ENV). * One potential use case is to satisfy FD-Leak checks. */ snprintf(arg_fd_entry, sizeof(arg_fd_entry), "%i", fds[1]); setenv(FUSE_COMMFD2_ENV, arg_fd_entry, 1); char const *const argv[] = { FUSERMOUNT_PROG, "-o", opts ? opts : "", "--", mountpoint, NULL, }; posix_spawn_file_actions_t action; posix_spawn_file_actions_init(&action); if (quiet) { posix_spawn_file_actions_addopen(&action, STDOUT_FILENO, "/dev/null", O_WRONLY, 0); posix_spawn_file_actions_addopen(&action, STDERR_FILENO, "/dev/null", O_WRONLY, 0); } posix_spawn_file_actions_addclose(&action, fds[1]); int status = fusermount_posix_spawn(&action, argv, &pid); posix_spawn_file_actions_destroy(&action); if(status != 0) { close(fds[0]); close(fds[1]); fuse_log(FUSE_LOG_ERR, "posix_spawn(p)() for %s failed: %s", FUSERMOUNT_PROG, strerror(-status)); return -1; } // passed to child now, so can close here. close(fds[0]); int fd = receive_fd(fds[1]); if (!mo->auto_unmount) { /* with auto_unmount option fusermount3 will not exit until this socket is closed */ close(fds[1]); waitpid(pid, NULL, 0); /* bury zombie */ } if (fd >= 0) fcntl(fd, F_SETFD, FD_CLOEXEC); return fd; } #ifndef O_CLOEXEC #define O_CLOEXEC 0 #endif static char *escape_context_commas_precise(const char *opts_in) { if (!opts_in) return NULL; const char *ctx = strstr(opts_in, "context="); if (!ctx) return NULL; const char *val = ctx + 8; /* hinter "context=" */ const char *end = opts_in + strlen(opts_in); /* Tokens, die nach context= vorkommen können */ static const char *tokens[] = { ",fd=", ",rootmode=", ",user_id=", ",group_id=", ",fsname=", ",subtype=", ",rw", ",ro", ",allow_other", ",default_permissions", ",auto_unmount", ",kernel_cache", ",attr_timeout=", ",entry_timeout=", ",negative_timeout=", NULL }; /* finde frühestes Auftreten eines Tokens hinter val */ const char *limit = end; for (int i = 0; tokens[i]; i++) { const char *p = strstr(val, tokens[i]); if (p && p < limit) limit = p; } /* Falls nichts gefunden: der Rest der Zeichenkette ist der context-Wert */ size_t prefix_len = (size_t)(val - opts_in); size_t ctx_len = (size_t)(limit - val); size_t suffix_len = (size_t)(end - limit); /* worst case: jedes ',' → "\054" (×4) innerhalb des ctx-Bereichs */ size_t cap = prefix_len + ctx_len * 4 + suffix_len + 1; char *out = malloc(cap); if (!out) return NULL; /* kopiere prefix (inkl. "context=") */ memcpy(out, opts_in, prefix_len); size_t o = prefix_len; /* escapen innerhalb des context-Werts */ for (size_t i = 0; i < ctx_len; i++) { char c = val[i]; if (c == ',') { memcpy(out + o, "\\054", 4); o += 4; } else { out[o++] = c; } } /* suffix roh übernehmen */ memcpy(out + o, limit, suffix_len); o += suffix_len; out[o] = '\0'; return out; } static int fuse_mount_sys(const char *mnt, struct mount_opts *mo, const char *mnt_opts) { char tmp[128]; const char *devname = "/dev/fuse"; char *source = NULL; char *type = NULL; struct stat stbuf; int fd; int res; if (!mnt) { fuse_log(FUSE_LOG_ERR, "fuse: missing mountpoint parameter\n"); return -1; } res = stat(mnt, &stbuf); if (res == -1) { fuse_log(FUSE_LOG_ERR, "fuse: failed to access mountpoint %s: %s\n", mnt, strerror(errno)); return -1; } fd = open(devname, O_RDWR | O_CLOEXEC); if (fd == -1) { if (errno == ENODEV || errno == ENOENT) fuse_log(FUSE_LOG_ERR, "fuse: device not found, try 'modprobe fuse' first\n"); else fuse_log(FUSE_LOG_ERR, "fuse: failed to open %s: %s\n", devname, strerror(errno)); return -1; } if (!O_CLOEXEC) fcntl(fd, F_SETFD, FD_CLOEXEC); 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; source = malloc((mo->fsname ? strlen(mo->fsname) : 0) + (mo->subtype ? strlen(mo->subtype) : 0) + strlen(devname) + 32); type = malloc((mo->subtype ? strlen(mo->subtype) : 0) + 32); if (!type || !source) { fuse_log(FUSE_LOG_ERR, "fuse: failed to allocate memory\n"); goto out_close; } strcpy(type, mo->blkdev ? "fuseblk" : "fuse"); if (mo->subtype) { strcat(type, "."); strcat(type, mo->subtype); } strcpy(source, mo->fsname ? mo->fsname : (mo->subtype ? mo->subtype : devname)); #if defined(__linux__) /* Erst neue Mount-API probieren: jede Option (inkl. context=… mit Kommata) wird als separates K/V gesetzt – keine Ambiguität mehr. */ if (fuse_mount_with_fsopen_try(type, mnt, mo->kernel_opts, mo->flags) == 0) { res = 0; } else #endif { if (mo->kernel_opts) { char *escaped = escape_context_commas_precise(mo->kernel_opts); if (escaped) { free(mo->kernel_opts); mo->kernel_opts = escaped; } } /* Fallback: klassisches mount(2) mit kommaseparierter Optionskette */ res = mount(source, mnt, type, mo->flags, mo->kernel_opts); } if (res == -1 && errno == ENODEV && mo->subtype) { /* Probably missing subtype support */ strcpy(type, mo->blkdev ? "fuseblk" : "fuse"); if (mo->fsname) { if (!mo->blkdev) sprintf(source, "%s#%s", mo->subtype, mo->fsname); } else { strcpy(source, type); } res = mount(source, mnt, type, mo->flags, mo->kernel_opts); } if (res == -1) { /* * Maybe kernel doesn't support unprivileged mounts, in this * case try falling back to fusermount3 */ if (errno == EPERM) { res = -2; } else { int errno_save = errno; if (mo->blkdev && errno == ENODEV && !fuse_mnt_check_fuseblk()) fuse_log(FUSE_LOG_ERR, "fuse: 'fuseblk' support missing\n"); else fuse_log(FUSE_LOG_ERR, "fuse: mount failed: %s\n", strerror(errno_save)); } goto out_close; } #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, newmnt, type, mnt_opts); free(newmnt); if (res == -1) goto out_umount; } #endif /* IGNORE_MTAB */ free(type); free(source); return fd; out_umount: umount2(mnt, 2); /* lazy umount */ out_close: free(type); free(source); 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; } struct mount_opts *parse_mount_opts(struct fuse_args *args) { struct mount_opts *mo; mo = (struct mount_opts*) malloc(sizeof(struct mount_opts)); if (mo == NULL) return NULL; memset(mo, 0, sizeof(struct mount_opts)); mo->flags = MS_NOSUID | MS_NODEV; if (args && fuse_opt_parse(args, mo, fuse_mount_opts, fuse_mount_opt_proc) == -1) goto err_out; return mo; err_out: destroy_mount_opts(mo); return NULL; } void destroy_mount_opts(struct mount_opts *mo) { free(mo->fsname); free(mo->subtype); free(mo->fusermount_opts); free(mo->subtype_opt); free(mo->kernel_opts); free(mo->mtab_opts); free(mo); } int fuse_kern_mount(const char *mountpoint, struct mount_opts *mo) { int res = -1; char *mnt_opts = NULL; 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 >= 0 && mo->auto_unmount) { if(0 > setup_auto_unmount(mountpoint, 0)) { // Something went wrong, let's umount like in fuse_mount_sys. umount2(mountpoint, MNT_DETACH); /* lazy umount */ res = -1; } } else 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); return res; }