Files
Bernd Schubert 1fbf44e06c fusermount: restrict pin_mountpoint owner check to sticky directories
The pinned-fd TOCTOU fix rejected any mountpoint not owned by the
mounting user, but check_nonroot_dir_access() only enforces ownership
for sticky directories. Restrict the pinned-inode check to match.

Fixes: bad8b22c91 ("fusermount: fix sync-init TOCTOU by mounting on a pinned mountpoint fd")
Signed-off-by: Bernd Schubert <bernd@bsbernd.com>
2026-06-12 12:08:57 +02:00

1895 lines
43 KiB
C

/*
FUSE: Filesystem in Userspace
Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
This program can be distributed under the terms of the GNU GPLv2.
See the file GPL2.txt.
*/
/* This program does the mounting and unmounting of FUSE filesystems */
#define _GNU_SOURCE /* for clone,strchrnul and close_range */
#include "fuse_config.h"
#include "mount_util.h"
#include "util.h"
#include "fuser_conf.h"
#if __linux__
#include "mount_i_linux.h"
#endif
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <getopt.h>
#include <errno.h>
#include <fcntl.h>
#include <pwd.h>
#include <paths.h>
#include <mntent.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <sys/param.h>
#include "fuse_mount_compat.h"
#include <sys/fsuid.h>
#include <sys/socket.h>
#include <sys/utsname.h>
#include <sched.h>
#include <stdbool.h>
#include <sys/vfs.h>
#if defined HAVE_CLOSE_RANGE && defined linux
#include <linux/close_range.h>
#endif
#if defined HAVE_LISTMOUNT
#include <linux/mount.h>
#include <syscall.h>
#include <stdint.h>
#endif
#define FUSE_COMMFD_ENV "_FUSE_COMMFD"
static const char *progname;
static int auto_unmount = 0;
/*
* Take a ',' separated option string and extract "x-" options
* @original: The original option string
* @regular_opts: The regular options
* @x_prefixed_opts: The "x-" options
*/
static int extract_x_options(const char *original, char **regular_opts,
char **x_prefixed_opts)
{
size_t orig_len;
const char *opt, *opt_end;
orig_len = strlen(original) + 1;
if (*regular_opts != NULL || *x_prefixed_opts != NULL) {
fprintf(stderr, "%s: regular_opts or x_prefixed_opts not NULL\n",
__func__);
return -EINVAL;
}
*regular_opts = calloc(1, orig_len);
*x_prefixed_opts = calloc(1, orig_len);
size_t regular_opts_len = orig_len;
size_t x_prefixed_opts_len = orig_len;
if (*regular_opts == NULL || *x_prefixed_opts == NULL) {
fprintf(stderr, "%s: Failed to allocate %zuB.\n",
__func__, orig_len);
return -ENOMEM;
}
for (opt = original; opt < original + orig_len; opt = opt_end + 1) {
char *opt_buf;
opt_end = strchr(opt, ',');
if (opt_end == NULL)
opt_end = original + orig_len;
size_t opt_len = opt_end - opt;
size_t opt_len_left = orig_len - (opt - original);
size_t buf_len;
bool is_x_prefixed_opts;
if (strncmp(opt, "x-", MIN(2, opt_len_left)) == 0) {
buf_len = x_prefixed_opts_len;
is_x_prefixed_opts = true;
opt_buf = *x_prefixed_opts;
} else {
buf_len = regular_opts_len;
is_x_prefixed_opts = false;
opt_buf = *regular_opts;
}
if (buf_len < orig_len) {
strncat(opt_buf, ",", 2);
buf_len -= 1;
}
/* omits ',' */
if ((ssize_t)(buf_len - opt_len) < 0) {
/* This would be a bug */
fprintf(stderr,
"%s: no buf space left in copy, orig='%s'\n",
__func__, original);
return -EIO;
}
strncat(opt_buf, opt, opt_end - opt);
buf_len -= opt_len;
if (is_x_prefixed_opts)
x_prefixed_opts_len = buf_len;
else
regular_opts_len = buf_len;
}
return 0;
}
static const char *get_user_name(void)
{
struct passwd *pw = getpwuid(getuid());
if (pw != NULL && pw->pw_name != NULL)
return pw->pw_name;
else {
fprintf(stderr, "%s: could not determine username\n", progname);
return NULL;
}
}
#ifndef IGNORE_MTAB
/*
* Make sure that /etc/mtab is checked and updated atomically
*/
static int lock_umount(void)
{
const char *mtab_lock = _PATH_MOUNTED ".fuselock";
int mtablock;
int res;
struct stat mtab_stat;
/* /etc/mtab could be a symlink to /proc/mounts */
if (lstat(_PATH_MOUNTED, &mtab_stat) == 0 && S_ISLNK(mtab_stat.st_mode))
return -1;
mtablock = open(mtab_lock, O_RDWR | O_CREAT, 0600);
if (mtablock == -1) {
fprintf(stderr, "%s: unable to open fuse lock file: %s\n",
progname, strerror(errno));
return -1;
}
res = lockf(mtablock, F_LOCK, 0);
if (res < 0) {
fprintf(stderr, "%s: error getting lock: %s\n", progname,
strerror(errno));
close(mtablock);
return -1;
}
return mtablock;
}
static void unlock_umount(int mtablock)
{
if (mtablock >= 0) {
int res;
res = lockf(mtablock, F_ULOCK, 0);
if (res < 0) {
fprintf(stderr, "%s: error releasing lock: %s\n",
progname, strerror(errno));
}
close(mtablock);
}
}
static int add_mount(const char *source, const char *mnt, const char *type,
const char *opts)
{
return fuse_mnt_add_mount(progname, source, mnt, type, opts);
}
static int may_unmount(const char *mnt, int quiet)
{
struct mntent *entp;
FILE *fp;
const char *user = NULL;
char uidstr[32];
unsigned uidlen = 0;
int found;
const char *mtab = _PATH_MOUNTED;
user = get_user_name();
if (user == NULL)
return -1;
fp = setmntent(mtab, "r");
if (fp == NULL) {
fprintf(stderr, "%s: failed to open %s: %s\n", progname, mtab,
strerror(errno));
return -1;
}
uidlen = sprintf(uidstr, "%u", getuid());
found = 0;
while ((entp = GETMNTENT(fp)) != NULL) {
if (!found && strcmp(entp->mnt_dir, mnt) == 0 &&
(strcmp(entp->mnt_type, "fuse") == 0 ||
strcmp(entp->mnt_type, "fuseblk") == 0 ||
strncmp(entp->mnt_type, "fuse.", 5) == 0 ||
strncmp(entp->mnt_type, "fuseblk.", 8) == 0)) {
char *p = strstr(entp->mnt_opts, "user=");
if (p &&
(p == entp->mnt_opts || *(p-1) == ',') &&
strcmp(p + 5, user) == 0) {
found = 1;
break;
}
/* /etc/mtab is a link pointing to
/proc/mounts: */
else if ((p =
strstr(entp->mnt_opts, "user_id=")) &&
(p == entp->mnt_opts ||
*(p-1) == ',') &&
strncmp(p + 8, uidstr, uidlen) == 0 &&
(*(p+8+uidlen) == ',' ||
*(p+8+uidlen) == '\0')) {
found = 1;
break;
}
}
}
endmntent(fp);
if (!found) {
if (!quiet)
fprintf(stderr,
"%s: entry for %s not found in %s\n",
progname, mnt, mtab);
return -1;
}
return 0;
}
#endif
/*
* Check whether the file specified in "fusermount3 -u" is really a
* mountpoint and not a symlink. This is necessary otherwise the user
* could move the mountpoint away and replace it with a symlink
* pointing to an arbitrary mount, thereby tricking fusermount3 into
* unmounting that (umount(2) will follow symlinks).
*
* This is the child process running in a separate mount namespace, so
* we don't mess with the global namespace and if the process is
* killed for any reason, mounts are automatically cleaned up.
*
* First make sure nothing is propagated back into the parent
* namespace by marking all mounts "private".
*
* Then bind mount parent onto a stable base where the user can't move
* it around.
*
* Finally check /proc/mounts for an entry matching the requested
* mountpoint. If it's found then we are OK, and the user can't move
* it around within the parent directory as rename() will return
* EBUSY. Be careful to ignore any mounts that existed before the
* bind.
*/
static int check_is_mount_child(void *p)
{
const char **a = p;
const char *last = a[0];
const char *mnt = a[1];
const char *type = a[2];
int res;
const char *procmounts = "/proc/mounts";
int found;
FILE *fp;
const struct mntent *entp;
int count;
res = mount("", "/", "", MS_PRIVATE | MS_REC, NULL);
if (res == -1) {
fprintf(stderr, "%s: failed to mark mounts private: %s\n",
progname, strerror(errno));
return 1;
}
fp = setmntent(procmounts, "r");
if (fp == NULL) {
fprintf(stderr, "%s: failed to open %s: %s\n", progname,
procmounts, strerror(errno));
return 1;
}
count = 0;
while (GETMNTENT(fp) != NULL)
count++;
endmntent(fp);
fp = setmntent(procmounts, "r");
if (fp == NULL) {
fprintf(stderr, "%s: failed to open %s: %s\n", progname,
procmounts, strerror(errno));
return 1;
}
res = mount(".", "/", "", MS_BIND | MS_REC, NULL);
if (res == -1) {
fprintf(stderr, "%s: failed to bind parent to /: %s\n",
progname, strerror(errno));
return 1;
}
found = 0;
while ((entp = GETMNTENT(fp)) != NULL) {
if (count > 0) {
count--;
continue;
}
if (entp->mnt_dir[0] == '/' &&
strcmp(entp->mnt_dir + 1, last) == 0 &&
(!type || strcmp(entp->mnt_type, type) == 0)) {
found = 1;
break;
}
}
endmntent(fp);
if (!found) {
fprintf(stderr, "%s: %s not mounted\n", progname, mnt);
return 1;
}
return 0;
}
static pid_t clone_newns(void *a)
{
char buf[131072];
char *stack = buf + (sizeof(buf) / 2 - ((size_t) buf & 15));
#ifdef __ia64__
extern int __clone2(int (*fn)(void *),
void *child_stack_base, size_t stack_size,
int flags, void *arg, pid_t *ptid,
void *tls, pid_t *ctid);
return __clone2(check_is_mount_child, stack, sizeof(buf) / 2,
CLONE_NEWNS, a, NULL, NULL, NULL);
#else
return clone(check_is_mount_child, stack, CLONE_NEWNS, a);
#endif
}
static int check_is_mount(const char *last, const char *mnt, const char *type)
{
pid_t pid, p;
int status;
const char *a[3] = { last, mnt, type };
pid = clone_newns((void *) a);
if (pid == (pid_t) -1) {
fprintf(stderr, "%s: failed to clone namespace: %s\n",
progname, strerror(errno));
return -1;
}
p = waitpid(pid, &status, __WCLONE);
if (p == (pid_t) -1) {
fprintf(stderr, "%s: waitpid failed: %s\n",
progname, strerror(errno));
return -1;
}
if (!WIFEXITED(status)) {
fprintf(stderr, "%s: child terminated abnormally (status %i)\n",
progname, status);
return -1;
}
if (WEXITSTATUS(status) != 0)
return -1;
return 0;
}
static int chdir_to_parent(char *copy, const char **lastp)
{
char *tmp;
const char *parent;
char buf[65536];
int res;
tmp = strrchr(copy, '/');
if (tmp == NULL || tmp[1] == '\0') {
fprintf(stderr, "%s: internal error: invalid abs path: <%s>\n",
progname, copy);
return -1;
}
if (tmp != copy) {
*tmp = '\0';
parent = copy;
*lastp = tmp + 1;
} else if (tmp[1] != '\0') {
*lastp = tmp + 1;
parent = "/";
} else {
*lastp = ".";
parent = "/";
}
res = chdir(parent);
if (res == -1) {
fprintf(stderr, "%s: failed to chdir to %s: %s\n",
progname, parent, strerror(errno));
return -1;
}
if (getcwd(buf, sizeof(buf)) == NULL) {
fprintf(stderr, "%s: failed to obtain current directory: %s\n",
progname, strerror(errno));
return -1;
}
if (strcmp(buf, parent) != 0) {
fprintf(stderr, "%s: mountpoint moved (%s -> %s)\n", progname,
parent, buf);
return -1;
}
return 0;
}
#ifndef IGNORE_MTAB
static int unmount_fuse_locked(const char *mnt, int quiet, int lazy)
{
int res;
char *copy;
const char *last;
int umount_flags = (lazy ? UMOUNT_DETACH : 0) | UMOUNT_NOFOLLOW;
if (getuid() != 0) {
res = may_unmount(mnt, quiet);
if (res == -1)
return -1;
}
copy = strdup(mnt);
if (copy == NULL) {
fprintf(stderr, "%s: failed to allocate memory\n", progname);
return -1;
}
drop_privs();
res = chdir_to_parent(copy, &last);
if (res == -1) {
restore_privs();
goto out;
}
res = umount2(last, umount_flags);
restore_privs();
if (res == -1 && !quiet) {
fprintf(stderr, "%s: failed to unmount %s: %s\n",
progname, mnt, strerror(errno));
}
out:
free(copy);
if (res == -1)
return -1;
res = chdir("/");
if (res == -1) {
fprintf(stderr, "%s: failed to chdir to '/'\n", progname);
return -1;
}
return fuse_mnt_remove_mount(progname, mnt);
}
static int unmount_fuse(const char *mnt, int quiet, int lazy)
{
int res;
int mtablock = lock_umount();
res = unmount_fuse_locked(mnt, quiet, lazy);
unlock_umount(mtablock);
return res;
}
#else /* IGNORE_MTAB */
static int add_mount(const char *source, const char *mnt, const char *type,
const char *opts)
{
(void) source;
(void) mnt;
(void) type;
(void) opts;
return 0;
}
static int unmount_fuse(const char *mnt, int quiet, int lazy)
{
(void) quiet;
return fuse_mnt_umount(progname, mnt, mnt, lazy);
}
#endif /* IGNORE_MTAB */
static int begins_with(const char *s, const char *beg)
{
if (strncmp(s, beg, strlen(beg)) == 0)
return 1;
else
return 0;
}
static int find_mount_flag(const char *s, unsigned len, int *on, int *flag)
{
int i;
for (i = 0; mount_flags[i].opt != NULL; i++) {
const char *opt = mount_flags[i].opt;
if (strlen(opt) == len && strncmp(opt, s, len) == 0) {
*on = mount_flags[i].on;
*flag = mount_flags[i].flag;
if (!mount_flags[i].safe && getuid() != 0) {
*flag = 0;
fprintf(stderr,
"%s: unsafe option %s ignored\n",
progname, opt);
}
return 1;
}
}
return 0;
}
static int add_option(char **optsp, const char *opt, unsigned expand)
{
char *newopts;
if (*optsp == NULL)
newopts = strdup(opt);
else {
unsigned oldsize = strlen(*optsp);
unsigned newsize = oldsize + 1 + strlen(opt) + expand + 1;
newopts = (char *) realloc(*optsp, newsize);
if (newopts)
sprintf(newopts + oldsize, ",%s", opt);
}
if (newopts == NULL) {
fprintf(stderr, "%s: failed to allocate memory\n", progname);
return -1;
}
*optsp = newopts;
return 0;
}
/*
* Build the mtab/utab record string for this mount: flag-mirrors of MS_*
* (rw/nosuid/nodev/...) + the kernel-bound -o options + "user=<n>" when
* mounted by a non-root user. The result is what add_mount() writes into
* /etc/mtab (or /run/mount/utab). The overlap with @opts is intentional so
* the mtab line reflects what the kernel saw.
*/
static int get_mtab_opts(int flags, const char *opts, char **mtab_optsp)
{
int i;
int l;
if (!(flags & MS_RDONLY) && add_option(mtab_optsp, "rw", 0) == -1)
return -1;
for (i = 0; mount_flags[i].opt != NULL; i++) {
if (mount_flags[i].on && (flags & mount_flags[i].flag) &&
add_option(mtab_optsp, mount_flags[i].opt, 0) == -1)
return -1;
}
if (add_option(mtab_optsp, opts, 0) == -1)
return -1;
/* remove comma from end of opts*/
l = strlen(*mtab_optsp);
if (l > 0 && (*mtab_optsp)[l-1] == ',')
(*mtab_optsp)[l-1] = '\0';
if (getuid() != 0) {
const char *user = get_user_name();
if (user == NULL)
return -1;
if (add_option(mtab_optsp, "user=", strlen(user)) == -1)
return -1;
strcat(*mtab_optsp, user);
}
return 0;
}
static int opt_eq(const char *s, unsigned len, const char *opt)
{
if(strlen(opt) == len && strncmp(s, opt, len) == 0)
return 1;
else
return 0;
}
static int get_string_opt(const char *s, unsigned len, const char *opt,
char **val)
{
int i;
unsigned opt_len = strlen(opt);
char *d;
if (*val)
free(*val);
*val = (char *) malloc(len - opt_len + 1);
if (!*val) {
fprintf(stderr, "%s: failed to allocate memory\n", progname);
return 0;
}
d = *val;
s += opt_len;
len -= opt_len;
for (i = 0; i < len; i++) {
if (s[i] == '\\' && i + 1 < len)
i++;
*d++ = s[i];
}
*d = '\0';
return 1;
}
/* The kernel silently truncates the "data" argument to PAGE_SIZE-1 characters.
* This can be dangerous if it e.g. truncates the option "group_id=1000" to
* "group_id=1".
* This wrapper detects this case and bails out with an error.
*/
static int mount_notrunc(const char *source, const char *target,
const char *filesystemtype, unsigned long mountflags,
const char *data) {
if (strlen(data) > sysconf(_SC_PAGESIZE) - 1) {
fprintf(stderr, "%s: mount options too long\n", progname);
errno = EINVAL;
return -1;
}
return mount(source, target, filesystemtype, mountflags, data);
}
struct mount_params {
/* Input parameters */
int fd; /* /dev/fuse file descriptor */
mode_t rootmode; /* Root mode from stat */
const char *dev; /* Device path (/dev/fuse) */
/* Parsed mount options */
unsigned long flags; /* Mount flags (MS_NOSUID, etc.) */
char *optbuf; /* Kernel mount options buffer */
char *fsname; /* Filesystem name from options */
char *subtype; /* Subtype from options */
int blkdev; /* Block device flag */
/* mtab/utab record content: MS_* flag mirrors + kernel-bound -o opts
* + user=<n> (non-root)
*/
char *mtab_opts;
/* Pointer for optbuf manipulation */
char *optbuf_end; /* Points to end of optbuf for sprintf */
};
static void free_mount_params(struct mount_params *mp)
{
free(mp->optbuf);
free(mp->fsname);
free(mp->subtype);
free(mp->mtab_opts);
memset(mp, 0, sizeof(*mp));
}
/*
* Check if user has permission to use allow_other or allow_root options.
*
* Returns -1 if permission denied, 0 if allowed or option is not
* allow_other/allow_root.
*/
static int check_allow_permission(const char *opt, unsigned int len)
{
if (getuid() != 0 && !user_allow_other &&
(opt_eq(opt, len, "allow_other") || opt_eq(opt, len, "allow_root"))) {
fprintf(stderr, "%s: option %.*s only allowed if 'user_allow_other' is set in %s\n",
progname, len, opt, FUSE_CONF);
return -1;
}
return 0;
}
/*
* Process generic mount option.
*
* Handles mount flags (ro, rw, suid, etc.), kernel options
* (default_permissions, allow_other, max_read, blksize), or exits on
* unknown options.
*/
static int process_generic_option(const char *opt, unsigned int len,
unsigned long *flags, char **dest)
{
int on;
int flag;
if (find_mount_flag(opt, len, &on, &flag)) {
if (on)
*flags |= flag;
else
*flags &= ~flag;
return 0;
}
if (opt_eq(opt, len, "default_permissions") ||
opt_eq(opt, len, "allow_other") ||
begins_with(opt, "max_read=") ||
begins_with(opt, "blksize=")) {
memcpy(*dest, opt, len);
*dest += len;
**dest = ',';
(*dest)++;
return 0;
}
fprintf(stderr, "%s: unknown option '%.*s'\n", progname, len, opt);
exit(1);
}
static int prepare_mount(const char *opts, struct mount_params *mp)
{
int res;
const char *s;
char *d;
mp->flags = MS_NOSUID | MS_NODEV;
mp->optbuf = (char *) malloc(strlen(opts) + 128);
if (!mp->optbuf) {
fprintf(stderr, "%s: failed to allocate memory\n", progname);
return -1;
}
for (s = opts, d = mp->optbuf; *s;) {
unsigned len;
const char *fsname_str = "fsname=";
const char *subtype_str = "subtype=";
bool escape_ok = begins_with(s, fsname_str) ||
begins_with(s, subtype_str);
for (len = 0; s[len]; len++) {
if (escape_ok && s[len] == '\\' && s[len + 1])
len++;
else if (s[len] == ',')
break;
}
if (begins_with(s, fsname_str)) {
if (!get_string_opt(s, len, fsname_str, &mp->fsname))
goto err;
} else if (begins_with(s, subtype_str)) {
if (!get_string_opt(s, len, subtype_str, &mp->subtype))
goto err;
} else if (opt_eq(s, len, "blkdev")) {
if (getuid() != 0) {
fprintf(stderr,
"%s: option blkdev is privileged\n",
progname);
goto err;
}
mp->blkdev = 1;
} else if (opt_eq(s, len, "auto_unmount")) {
auto_unmount = 1;
} else if (!opt_eq(s, len, "nonempty") &&
!begins_with(s, "fd=") &&
!begins_with(s, "rootmode=") &&
!begins_with(s, "user_id=") &&
!begins_with(s, "group_id=")) {
if (check_allow_permission(s, len) == -1)
goto err;
process_generic_option(s, len, &mp->flags, &d);
}
s += len;
if (*s)
s++;
}
*d = '\0';
res = get_mtab_opts(mp->flags, mp->optbuf, &mp->mtab_opts);
if (res == -1)
goto err;
mp->optbuf_end = d;
sprintf(d, "fd=%i,rootmode=%o,user_id=%u,group_id=%u",
mp->fd, mp->rootmode, getuid(), getgid());
return 0;
err:
free_mount_params(mp);
return -1;
}
/*
* Perform the actual mount operation using prepared parameters.
* Builds source and type strings and returns them via output parameters.
*
* Returns 0 on success, -1 on failure.
*/
static int perform_mount(const char *mnt, struct mount_params *mp,
char **source_out, char **type_out)
{
int res;
char *source = NULL;
char *type = NULL;
source = fuse_mnt_build_source(mp->fsname, mp->subtype, mp->dev, 0);
type = fuse_mnt_build_type(mp->blkdev, mp->subtype);
if (!type || !source) {
fprintf(stderr, "%s: failed to allocate memory\n", progname);
free(source);
free(type);
return -1;
}
res = mount_notrunc(source, mnt, type, mp->flags, mp->optbuf);
if (res == -1 && errno == ENODEV && mp->subtype) {
/* Probably missing subtype support */
free(source);
free(type);
type = fuse_mnt_build_type(mp->blkdev, NULL);
if (mp->fsname) {
if (!mp->blkdev) {
source = fuse_mnt_build_source(mp->fsname,
mp->subtype,
mp->dev, 1);
} else {
source = fuse_mnt_build_source(mp->fsname, NULL,
mp->dev, 0);
}
} else {
source = strdup(type);
}
if (!type || !source) {
fprintf(stderr, "%s: failed to allocate memory\n", progname);
free(source);
free(type);
return -1;
}
res = mount_notrunc(source, mnt, type, mp->flags, mp->optbuf);
}
if (res == -1 && errno == EINVAL) {
/* It could be an old version not supporting group_id */
sprintf(mp->optbuf_end, "fd=%i,rootmode=%o,user_id=%u",
mp->fd, mp->rootmode, getuid());
res = mount_notrunc(source, mnt, type, mp->flags, mp->optbuf);
}
if (res == -1) {
int errno_save = errno;
if (mp->blkdev && errno == ENODEV && !fuse_mnt_check_fuseblk())
fprintf(stderr, "%s: 'fuseblk' support missing\n",
progname);
else
fprintf(stderr, "%s: mount failed: %s\n", progname,
strerror(errno_save));
free(source);
free(type);
return -1;
}
*source_out = source;
*type_out = type;
return 0;
}
static int do_mount(const char *mnt, const char **typep, mode_t rootmode,
int fd, const char *opts, const char *dev, char **sourcep,
char **mtab_optsp)
{
struct mount_params mp = { .fd = fd }; /* implicit zero of other params */
int res;
char *source = NULL;
char *type = NULL;
mp.rootmode = rootmode;
mp.dev = dev;
res = prepare_mount(opts, &mp);
if (res == -1)
return -1;
res = perform_mount(mnt, &mp, &source, &type);
if (res == -1) {
free_mount_params(&mp);
return -1;
}
*sourcep = source;
*typep = type;
*mtab_optsp = mp.mtab_opts;
/* Free only the intermediate allocations, not the returned ones */
free(mp.fsname);
free(mp.subtype);
free(mp.optbuf);
return 0;
}
/*
* Pin @open_path (the validated mountpoint -- "." after chdir, or the path
* itself for root) as an O_PATH fd and fstat() it into @stbuf. Re-apply the
* sticky-directory ownership rule on the pinned inode: done on the held fd
* this is immune to a symlink swap of @open_path between validation and the
* mount. A writable non-sticky directory of another user stays a valid
* mountpoint. @name is the user-facing path for diagnostics.
* Returns the fd, or -1 on failure.
*/
static int pin_mountpoint(const char *open_path, const char *name,
uid_t want_uid, struct stat *stbuf)
{
int fd = open(open_path, O_PATH | O_CLOEXEC);
if (fd == -1) {
fprintf(stderr, "%s: failed to pin mountpoint %s: %s\n",
progname, name, strerror(errno));
return -1;
}
if (fstat(fd, stbuf) == -1) {
fprintf(stderr, "%s: failed to access mountpoint %s: %s\n",
progname, name, strerror(errno));
close(fd);
return -1;
}
if (want_uid != (uid_t)-1 && (stbuf->st_mode & S_ISVTX) &&
stbuf->st_uid != want_uid) {
fprintf(stderr,
"%s: mountpoint %s not owned by user\n",
progname, name);
close(fd);
return -1;
}
return fd;
}
static int check_perm(const char **mntp, struct stat *stbuf, int *mountpoint_fd)
{
int res;
const char *mnt = *mntp;
const char *origmnt = mnt;
struct statfs fs_buf;
res = lstat(mnt, stbuf);
if (res == -1) {
fprintf(stderr, "%s: failed to access mountpoint %s: %s\n",
progname, mnt, strerror(errno));
return -1;
}
/*
* Root skips the permission checks, but still pin the mountpoint inode:
* external tools may invoke this suid helper as root, and the pinned fd
* makes move_mount() target exactly the validated inode regardless of a
* later symlink swap.
*/
if (getuid() == 0) {
*mountpoint_fd = pin_mountpoint(mnt, mnt, (uid_t)-1, stbuf);
if (*mountpoint_fd == -1)
return -1;
return 0;
}
if (S_ISDIR(stbuf->st_mode)) {
res = chdir(mnt);
if (res == -1) {
fprintf(stderr,
"%s: failed to chdir to mountpoint: %s\n",
progname, strerror(errno));
return -1;
}
mnt = *mntp = ".";
res = lstat(mnt, stbuf);
if (res == -1) {
fprintf(stderr,
"%s: failed to access mountpoint %s: %s\n",
progname, origmnt, strerror(errno));
return -1;
}
res = check_nonroot_dir_access(progname, origmnt, mnt, stbuf);
if (res)
return res;
/* Reached only for non-root; root returned above. CWD is the
* just-validated directory after chdir.
*/
*mountpoint_fd = pin_mountpoint(".", origmnt, getuid(), stbuf);
if (*mountpoint_fd == -1)
return -1;
} else if (S_ISREG(stbuf->st_mode)) {
static char procfile[256];
*mountpoint_fd = open(mnt, O_WRONLY);
if (*mountpoint_fd == -1) {
fprintf(stderr, "%s: failed to open %s: %s\n",
progname, mnt, strerror(errno));
return -1;
}
res = fstat(*mountpoint_fd, stbuf);
if (res == -1) {
fprintf(stderr,
"%s: failed to access mountpoint %s: %s\n",
progname, mnt, strerror(errno));
return -1;
}
if (!S_ISREG(stbuf->st_mode)) {
fprintf(stderr,
"%s: mountpoint %s is no longer a regular file\n",
progname, mnt);
return -1;
}
sprintf(procfile, "/proc/self/fd/%i", *mountpoint_fd);
*mntp = procfile;
} else {
fprintf(stderr,
"%s: mountpoint %s is not a directory or a regular file\n",
progname, mnt);
return -1;
}
if (statfs(*mntp, &fs_buf)) {
fprintf(stderr, "%s: failed to access mountpoint %s: %s\n",
progname, mnt, strerror(errno));
return -1;
}
return check_nonroot_fstype(progname, &fs_buf);
}
static int open_fuse_device(const char *dev)
{
int fd;
drop_privs();
/* codeql[cpp/path-injection] dev is verified */
fd = open(dev, O_RDWR);
if (fd == -1) {
if (errno == ENODEV || errno == ENOENT)/* check for ENOENT too, for the udev case */
fprintf(stderr,
"%s: fuse device %s not found. Kernel module not loaded?\n",
progname, dev);
else
fprintf(stderr,
"%s: failed to open %s: %s\n", progname, dev, strerror(errno));
}
restore_privs();
return fd;
}
/*
* Context for split mount operation (sync-init mode)
*/
struct mount_context {
int fd;
const char *dev;
struct stat stbuf;
char *source;
char *mtab_opts; /* mtab/utab record string for add_mount() */
char *x_opts;
char *kern_mnt_opts; /* user-provided -o opts with x-* removed */
/* Pinned mountpoint inode, resolved once during check_perm(); the
* move_mount() target. Immune to symlink swaps. -1 if unset.
*/
int mnt_fd;
};
/*
* Phase 1: Open device and prepare for mount (sync-init mode)
* Returns fd on success, -1 on failure
*/
static int mount_fuse_prepare(const char *mnt, const char *opts,
struct mount_context *ctx)
{
int res;
int mountpoint_fd = -1;
const char *real_mnt = mnt;
memset(ctx, 0, sizeof(*ctx));
ctx->mnt_fd = -1;
ctx->dev = fuse_mnt_get_devname();
ctx->fd = open_fuse_device(ctx->dev);
if (ctx->fd == -1)
return -1;
drop_privs();
read_conf(progname);
if (getuid() != 0 && mount_max != -1) {
int mount_count = count_fuse_fs(progname);
if (mount_count >= mount_max) {
fprintf(stderr,
"%s: too many FUSE filesystems mounted; mount_max=N can be set in %s\n",
progname, FUSE_CONF);
goto fail_close_fd;
}
}
res = extract_x_options(opts, &ctx->kern_mnt_opts, &ctx->x_opts);
if (res)
goto fail_close_fd;
res = check_perm(&real_mnt, &ctx->stbuf, &mountpoint_fd);
restore_privs();
if (res == -1) {
if (mountpoint_fd != -1)
close(mountpoint_fd);
goto fail_close_fd;
}
/*
* check_perm() pinned the validated inode (directory or regular file,
* root or not) as mountpoint_fd, so the mount targets exactly that inode
* regardless of later symlink swaps of the path. The fd stays open until
* after the mount.
*/
ctx->mnt_fd = mountpoint_fd;
return ctx->fd;
fail_close_fd:
close(ctx->fd);
free(ctx->x_opts);
free(ctx->kern_mnt_opts);
ctx->fd = -1;
return -1;
}
#ifdef HAVE_NEW_MOUNT_API
/*
* Phase 2: Perform the actual mount using new mount API (sync-init mode)
* Returns 0 on success, -1 on failure
*/
static int mount_fuse_finish_fsmount(const char *mnt,
struct mount_context *ctx,
const char **type)
{
int res;
struct mount_params mp = {
.fd = ctx->fd,
.rootmode = ctx->stbuf.st_mode & S_IFMT,
.dev = ctx->dev,
};
char *final_mtab_opts = NULL;
res = prepare_mount(ctx->kern_mnt_opts, &mp);
if (res == -1)
goto fail;
/*
* Merge x-options if running as root, root is allowed to update
* /etc/mtab or /run/mount/utab
*/
final_mtab_opts = mp.mtab_opts;
if (geteuid() == 0 && ctx->x_opts && strlen(ctx->x_opts) > 0) {
char *x_mtab_opts = NULL;
int ret;
if (strlen(mp.mtab_opts) > 0)
ret = asprintf(&x_mtab_opts, "%s,%s",
mp.mtab_opts, ctx->x_opts);
else
ret = asprintf(&x_mtab_opts, "%s", ctx->x_opts);
if (ret < 0)
goto fail_free_params;
final_mtab_opts = x_mtab_opts;
}
/* Use new mount API; mount onto the pinned fd, not the path string */
res = fuse_kern_fsmount(mnt, ctx->mnt_fd, mp.flags, mp.blkdev,
mp.fsname, mp.subtype, ctx->dev, mp.optbuf,
final_mtab_opts);
if (res == -1)
goto fail_free_merged;
/* Change to root directory */
res = chdir("/");
if (res == -1) {
fprintf(stderr, "%s: failed to chdir to '/'\n", progname);
goto fail_free_merged;
}
/* Build source and type for mtab after successful mount */
ctx->source = fuse_mnt_build_source(mp.fsname, mp.subtype, ctx->dev, 0);
*type = fuse_mnt_build_type(mp.blkdev, mp.subtype);
if (!*type || !ctx->source) {
fprintf(stderr, "%s: failed to allocate memory\n", progname);
goto fail_free_merged;
}
/* Store mtab_opts in context */
ctx->mtab_opts = final_mtab_opts;
res = 0;
/* Only free what is not assigned to ctx */
free(mp.fsname);
free(mp.subtype);
free(mp.optbuf);
if (final_mtab_opts != mp.mtab_opts)
free(mp.mtab_opts);
out:
return res;
fail_free_merged:
if (final_mtab_opts != mp.mtab_opts)
free(final_mtab_opts);
fail_free_params:
free_mount_params(&mp);
fail:
res = -1;
goto out;
}
#endif /* HAVE_NEW_MOUNT_API */
static int mount_fuse(const char *mnt, const char *opts, const char **type)
{
int res;
int fd;
const char *dev = fuse_mnt_get_devname();
struct stat stbuf;
char *source = NULL;
char *mtab_opts = NULL;
const char *real_mnt = mnt;
int mountpoint_fd = -1;
char *do_mount_opts = NULL;
char *x_prefixed_opts = NULL;
fd = open_fuse_device(dev);
if (fd == -1)
return -1;
drop_privs();
read_conf(progname);
if (getuid() != 0 && check_nonroot_mount_count(progname) != 0)
goto fail_close_fd;
// Extract any options starting with "x-"
res = extract_x_options(opts, &do_mount_opts, &x_prefixed_opts);
if (res)
goto fail_close_fd;
res = check_perm(&real_mnt, &stbuf, &mountpoint_fd);
restore_privs();
if (res != -1)
res = do_mount(real_mnt, type, stbuf.st_mode & S_IFMT,
fd, do_mount_opts, dev, &source, &mtab_opts);
if (mountpoint_fd != -1)
close(mountpoint_fd);
if (res == -1)
goto fail_close_fd;
res = chdir("/");
if (res == -1) {
fprintf(stderr, "%s: failed to chdir to '/'\n", progname);
goto fail_close_fd;
}
if (geteuid() == 0) {
if (x_prefixed_opts && strlen(x_prefixed_opts) > 0) {
/*
* Add back the options starting with "x-" to opts from
* do_mount. +2 for ',' and '\0'
*/
size_t mtab_opts_len = strlen(mtab_opts);
size_t x_mtab_opts_len = mtab_opts_len +
strlen(x_prefixed_opts) + 2;
char *x_mtab_opts = calloc(1, x_mtab_opts_len);
if (x_mtab_opts == NULL) {
fprintf(stderr,
"%s: failed to allocate memory for mtab_opts\n",
progname);
goto fail_close_fd;
}
if (mtab_opts_len) {
strcpy(x_mtab_opts, mtab_opts);
strncat(x_mtab_opts, ",", 2);
}
strncat(x_mtab_opts, x_prefixed_opts,
x_mtab_opts_len - mtab_opts_len - 2);
free(mtab_opts);
mtab_opts = x_mtab_opts;
}
res = add_mount(source, mnt, *type, mtab_opts);
if (res == -1) {
/* Can't clean up mount in a non-racy way */
goto fail_close_fd;
}
}
out_free:
free(source);
free(mtab_opts);
free(x_prefixed_opts);
free(do_mount_opts);
return fd;
fail_close_fd:
close(fd);
fd = -1;
goto out_free;
}
/* Forward declarations for helper functions */
static int send_fd(int sock_fd, int fd);
static int wait_for_signal(int sock_fd);
#ifdef HAVE_NEW_MOUNT_API
/*
* Perform sync-init mount using new mount API
* Returns 0 on success, -1 on failure
*/
static int mount_fuse_sync_init(const char *mnt, const char *opts,
int cfd, const char **type)
{
struct mount_context ctx = { .fd = -1 };
int fd, res;
int32_t status, send_res;
/* Phase 1: Open device and prepare */
fd = mount_fuse_prepare(mnt, opts, &ctx);
if (fd == -1)
return -1;
/* Send fd to caller so it can start worker thread */
res = send_fd(cfd, fd);
if (res != 0)
goto out;
/* Wait for caller to signal that worker thread is ready */
res = wait_for_signal(cfd);
if (res != 0)
goto out;
/* Phase 2: Perform the actual mount using new API */
res = mount_fuse_finish_fsmount(mnt, &ctx, type);
/* Send mount result back to caller (4-byte error code) */
status = (res == 0) ? 0 : -(int32_t)errno;
do {
send_res = send(cfd, &status, sizeof(status), 0);
} while (send_res == -1 && errno == EINTR);
if (send_res != sizeof(status)) {
fprintf(stderr, "%s: failed to send mount status: %s\n",
progname, strerror(errno));
}
out:
close(fd);
if (ctx.mnt_fd != -1)
close(ctx.mnt_fd);
free(ctx.source);
free(ctx.mtab_opts);
free(ctx.x_opts);
free(ctx.kern_mnt_opts);
return res;
}
#endif /* HAVE_NEW_MOUNT_API */
static int send_fd(int sock_fd, int fd)
{
int retval;
struct msghdr msg;
struct cmsghdr *p_cmsg;
struct iovec vec;
size_t cmsgbuf[CMSG_SPACE(sizeof(fd)) / sizeof(size_t)];
int *p_fds;
char sendchar = 0;
msg.msg_control = cmsgbuf;
msg.msg_controllen = sizeof(cmsgbuf);
p_cmsg = CMSG_FIRSTHDR(&msg);
p_cmsg->cmsg_level = SOL_SOCKET;
p_cmsg->cmsg_type = SCM_RIGHTS;
p_cmsg->cmsg_len = CMSG_LEN(sizeof(fd));
p_fds = (int *) CMSG_DATA(p_cmsg);
*p_fds = fd;
msg.msg_controllen = p_cmsg->cmsg_len;
msg.msg_name = NULL;
msg.msg_namelen = 0;
msg.msg_iov = &vec;
msg.msg_iovlen = 1;
msg.msg_flags = 0;
/* "To pass file descriptors or credentials you need to send/read at
* least one byte" (man 7 unix) */
vec.iov_base = &sendchar;
vec.iov_len = sizeof(sendchar);
while ((retval = sendmsg(sock_fd, &msg, 0)) == -1 && errno == EINTR);
if (retval != 1) {
perror("sending file descriptor");
return -1;
}
return 0;
}
/*
* Wait for a signal byte from the caller.
* Returns 0 on success, -1 on error.
*/
static int wait_for_signal(int sock_fd)
{
char buf[1];
int res;
do {
res = recv(sock_fd, buf, sizeof(buf), 0);
} while (res == -1 && errno == EINTR);
if (res != 1) {
if (res == 0)
fprintf(stderr, "%s: connection closed while waiting for signal\n",
progname);
else
fprintf(stderr, "%s: error receiving signal: %s\n",
progname, strerror(errno));
return -1;
}
return 0;
}
/* Helper for should_auto_unmount
*
* fusermount typically has the s-bit set - initial open of `mnt` was as root
* and got EACCESS as 'allow_other' was not specified.
* Try opening `mnt` again with uid and guid of the calling process.
*/
static int recheck_ENOTCONN_as_owner(const char *mnt)
{
int pid = fork();
if(pid == -1) {
perror("fuse: recheck_ENOTCONN_as_owner can't fork");
_exit(EXIT_FAILURE);
} else if(pid == 0) {
uid_t uid = getuid();
gid_t gid = getgid();
if(setresgid(gid, gid, gid) == -1) {
perror("fuse: can't set resgid");
_exit(EXIT_FAILURE);
}
if(setresuid(uid, uid, uid) == -1) {
perror("fuse: can't set resuid");
_exit(EXIT_FAILURE);
}
int fd = open(mnt, O_RDONLY);
if(fd == -1 && errno == ENOTCONN)
_exit(EXIT_SUCCESS);
else
_exit(EXIT_FAILURE);
} else {
int status;
int res = waitpid(pid, &status, 0);
if (res == -1) {
perror("fuse: waiting for child failed");
_exit(EXIT_FAILURE);
}
return WIFEXITED(status) && WEXITSTATUS(status) == EXIT_SUCCESS;
}
}
/* The parent fuse process has died: decide whether to auto_unmount.
*
* In the normal case (umount or fusermount -u), the filesystem
* has already been unmounted. If we simply unmount again we can
* cause problems with stacked mounts (e.g. autofs).
*
* So we unmount here only in abnormal case where fuse process has
* died without unmount happening. To detect this, we first look in
* the mount table to make sure the mountpoint is still mounted and
* has proper type. If so, we then see if opening the mount dir is
* returning 'Transport endpoint is not connected'.
*
* The order of these is important, because if autofs is in use,
* opening the dir to check for ENOTCONN will cause a new mount
* in the normal case where filesystem has been unmounted cleanly.
*/
static int should_auto_unmount(const char *mnt, const char *type)
{
char *copy;
const char *last;
int result = 0;
int fd;
copy = strdup(mnt);
if (copy == NULL) {
fprintf(stderr, "%s: failed to allocate memory\n", progname);
return 0;
}
if (chdir_to_parent(copy, &last) == -1)
goto out;
if (check_is_mount(last, mnt, type) == -1)
goto out;
fd = open(mnt, O_RDONLY);
if (fd != -1) {
close(fd);
} else {
switch(errno) {
case ENOTCONN:
result = 1;
break;
case EACCES:
result = recheck_ENOTCONN_as_owner(mnt);
break;
default:
result = 0;
break;
}
}
out:
free(copy);
return result;
}
static void usage(void)
{
printf("%s: [options] mountpoint\n"
"Options:\n"
" -h print help\n"
" -V print version\n"
" -o opt[,opt...] mount options\n"
" -u unmount\n"
" -q quiet\n"
" -z lazy unmount\n",
progname);
exit(1);
}
static void show_version(void)
{
printf("fusermount3 version: %s\n", PACKAGE_VERSION);
exit(0);
}
static void close_range_loop(int min_fd, int max_fd, int cfd)
{
for (int fd = min_fd; fd <= max_fd; fd++)
if (fd != cfd)
close(fd);
}
/*
* Close all inherited fds that are not needed
* Ideally these wouldn't come up at all, applications should better
* use FD_CLOEXEC / O_CLOEXEC
*/
static int close_inherited_fds(int cfd)
{
int rc = -1;
int nullfd;
/* We can't even report an error */
if (cfd <= STDERR_FILENO)
return -EINVAL;
#ifdef HAVE_CLOSE_RANGE
if (cfd < STDERR_FILENO + 2) {
close_range_loop(STDERR_FILENO + 1, cfd - 1, cfd);
} else {
rc = close_range(STDERR_FILENO + 1, cfd - 1, 0);
if (rc < 0)
goto fallback;
}
/* Close high range */
rc = close_range(cfd + 1, ~0U, 0);
#else
goto fallback; /* make use of fallback to avoid compiler warnings */
#endif
fallback:
if (rc < 0) {
int max_fd = sysconf(_SC_OPEN_MAX) - 1;
close_range_loop(STDERR_FILENO + 1, max_fd, cfd);
}
nullfd = open("/dev/null", O_RDWR);
if (nullfd < 0) {
perror("fusermount: cannot open /dev/null");
return -errno;
}
/* Redirect stdin, stdout, stderr to /dev/null */
dup2(nullfd, STDIN_FILENO);
dup2(nullfd, STDOUT_FILENO);
dup2(nullfd, STDERR_FILENO);
if (nullfd > STDERR_FILENO)
close(nullfd);
return 0;
}
int main(int argc, char *argv[])
{
sigset_t sigset;
int ch;
int fd;
int res;
char *origmnt;
char *mnt;
static int unmount = 0;
static int lazy = 0;
static int quiet = 0;
char *commfd = NULL;
long cfd;
const char *opts = "";
const char *type = NULL;
int setup_auto_unmount_only = 0;
int sync_init_mode = 0;
static const struct option long_opts[] = {
{"unmount", no_argument, NULL, 'u'},
{"lazy", no_argument, NULL, 'z'},
{"quiet", no_argument, NULL, 'q'},
{"help", no_argument, NULL, 'h'},
{"version", no_argument, NULL, 'V'},
{"options", required_argument, NULL, 'o'},
// Note: auto-unmount and comm-fd don't have short versions.
// They'ne meant for internal use by mount.c
{"auto-unmount", no_argument, NULL, 'U'},
{"comm-fd", required_argument, NULL, 'c'},
{"sync-init", no_argument, NULL, 'S'},
{0, 0, 0, 0}};
progname = strdup(argc > 0 ? argv[0] : "fusermount");
if (progname == NULL) {
fprintf(stderr, "%s: failed to allocate memory\n", argv[0]);
exit(1);
}
while ((ch = getopt_long(argc, argv, "hVo:uzq", long_opts,
NULL)) != -1) {
switch (ch) {
case 'h':
usage();
break;
case 'V':
show_version();
break;
case 'o':
opts = optarg;
break;
case 'u':
unmount = 1;
break;
case 'U':
unmount = 1;
auto_unmount = 1;
setup_auto_unmount_only = 1;
break;
case 'c':
commfd = optarg;
break;
case 'S':
sync_init_mode = 1;
break;
case 'z':
lazy = 1;
break;
case 'q':
quiet = 1;
break;
default:
exit(1);
}
}
if (lazy && !unmount) {
fprintf(stderr, "%s: -z can only be used with -u\n", progname);
exit(1);
}
if (optind >= argc) {
fprintf(stderr, "%s: missing mountpoint argument\n", progname);
exit(1);
} else if (argc > optind + 1) {
fprintf(stderr, "%s: extra arguments after the mountpoint\n",
progname);
exit(1);
}
origmnt = argv[optind];
drop_privs();
mnt = fuse_mnt_resolve_path(progname, origmnt);
if (mnt != NULL) {
res = chdir("/");
if (res == -1) {
fprintf(stderr, "%s: failed to chdir to '/'\n", progname);
goto err_out;
}
}
restore_privs();
if (mnt == NULL)
exit(1);
umask(033);
if (!setup_auto_unmount_only && unmount)
goto do_unmount;
if(commfd == NULL)
commfd = getenv(FUSE_COMMFD_ENV);
if (commfd == NULL) {
fprintf(stderr, "%s: old style mounting not supported\n",
progname);
goto err_out;
}
res = libfuse_strtol(commfd, &cfd);
if (res) {
fprintf(stderr,
"%s: invalid _FUSE_COMMFD: %s\n",
progname, commfd);
goto err_out;
}
{
struct stat statbuf;
if (fstat(cfd, &statbuf) == -1) {
fprintf(stderr,
"%s: fstat of comm fd %li failed: %s\n",
progname, cfd, strerror(errno));
goto err_out;
}
if(!S_ISSOCK(statbuf.st_mode)) {
fprintf(stderr,
"%s: file descriptor %li is not a socket, can't send fuse fd\n",
progname, cfd);
goto err_out;
}
}
if (setup_auto_unmount_only)
goto wait_for_auto_unmount;
if (sync_init_mode) {
#ifdef HAVE_NEW_MOUNT_API
res = mount_fuse_sync_init(mnt, opts, cfd, &type);
if (res == -1)
goto err_out;
if (!auto_unmount) {
free(mnt);
free((void *) type);
return 0;
}
/* Continue to auto_unmount handling below */
#else
fprintf(stderr, "%s: sync-init mode requires new mount API support\n",
progname);
fprintf(stderr, "%s: kernel or headers too old (need fsopen/fsmount)\n",
progname);
goto err_out;
#endif
} else {
fd = mount_fuse(mnt, opts, &type);
if (fd == -1)
goto err_out;
res = send_fd(cfd, fd);
if (res != 0) {
umount2(mnt, MNT_DETACH); /* lazy umount */
goto err_out;
}
close(fd);
if (!auto_unmount) {
free(mnt);
free((void *) type);
return 0;
}
}
wait_for_auto_unmount:
/* Become a daemon and wait for the parent to exit or die.
ie For the control socket to get closed.
Btw, we don't want to use daemon() function here because
it forks and messes with the file descriptors. */
res = close_inherited_fds(cfd);
if (res < 0)
exit(EXIT_FAILURE);
setsid();
res = chdir("/");
if (res == -1) {
fprintf(stderr, "%s: failed to chdir to '/'\n", progname);
goto err_out;
}
sigfillset(&sigset);
sigprocmask(SIG_BLOCK, &sigset, NULL);
lazy = 1;
quiet = 1;
while (1) {
unsigned char buf[16];
int n = recv(cfd, buf, sizeof(buf), 0);
if (!n)
break;
if (n < 0) {
if (errno == EINTR)
continue;
break;
}
}
if (!should_auto_unmount(mnt, type)) {
goto success_out;
}
do_unmount:
if (geteuid() == 0)
res = unmount_fuse(mnt, quiet, lazy);
else {
res = umount2(mnt, lazy ? UMOUNT_DETACH : 0);
if (res == -1 && !quiet)
fprintf(stderr,
"%s: failed to unmount %s: %s\n",
progname, mnt, strerror(errno));
}
if (res == -1)
goto err_out;
success_out:
free((void*) type);
free(mnt);
return 0;
err_out:
free((void*) type);
free(mnt);
exit(1);
}