diff options
Diffstat (limited to 'lib/mount.c')
-rw-r--r-- | lib/mount.c | 347 |
1 files changed, 346 insertions, 1 deletions
diff --git a/lib/mount.c b/lib/mount.c index 6ed4444..0a31f81 100644 --- a/lib/mount.c +++ b/lib/mount.c @@ -29,6 +29,7 @@ #include <sys/socket.h> #include <sys/un.h> #include <sys/wait.h> +#include <ctype.h> #include "fuse_mount_compat.h" @@ -54,6 +55,279 @@ #define MS_DIRSYNC 128 #endif +/* === new-mount-API helpers (Linux only) ================================= */ +#if defined(__linux__) +#include <linux/mount.h> +#include <sys/syscall.h> +#include <fcntl.h> +#include <errno.h> +#include <unistd.h> +#include <stdbool.h> +#include <stdlib.h> +#include <string.h> + +/* 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, @@ -502,6 +776,59 @@ static int fuse_mount_fusermount(const char *mountpoint, struct mount_opts *mo, #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) { @@ -562,7 +889,25 @@ static int fuse_mount_sys(const char *mnt, struct mount_opts *mo, strcpy(source, mo->fsname ? mo->fsname : (mo->subtype ? mo->subtype : devname)); - res = mount(source, mnt, type, mo->flags, mo->kernel_opts); +#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"); |