Merge remote-tracking branch 'origin/master' into djwong-dev3

Merge upstream origin/master into bsbernd/fuse-service-container.

Signed-off-by: "Darrick J. Wong" <djwong@kernel.org>
This commit is contained in:
Darrick J. Wong
2026-05-11 09:57:01 -07:00
14 changed files with 473 additions and 161 deletions
+1 -1
View File
@@ -58,7 +58,7 @@ foreach ex : threaded_examples
install: false)
endforeach
if platform != 'dragonfly' and add_languages('cpp', required : false)
if platform != 'dragonfly' and add_languages('cpp', native : false, required : false)
executable('passthrough_hp', 'passthrough_hp.cc',
dependencies: [ thread_dep, libfuse_dep ],
install: false)
+10 -6
View File
@@ -30,8 +30,8 @@
#define FUSE_DAEMONIZE_SUCCESS 0
#define FUSE_DAEMONIZE_FAILURE 1
/* Private/internal data */
struct fuse_daemonize {
/* Private/internal data */
struct fuse_daemonize {
unsigned int flags;
int signal_pipe_wr; /* write end for signaling parent */
int death_pipe_rd; /* read end, POLLHUP when parent dies */
@@ -39,8 +39,8 @@
int stop_pipe_wr; /* write end for stop signal */
pthread_t watcher;
bool watcher_started;
_Atomic bool active;
_Atomic bool daemonized;
_Atomic bool active; /* daemonization in progress */
_Atomic bool daemonized; /* daemonized successfully */
_Atomic bool mounted; /* fuse_session_mount() completed */
_Atomic bool got_init; /* got FUSE_INIT */
};
@@ -211,18 +211,22 @@ int fuse_daemonize_early_start(unsigned int flags)
struct fuse_daemonize *dm = &daemonize;
int err = 0;
if (dm->active)
return 0; /* already called, no need for another fork */
dm->flags = flags;
dm->signal_pipe_wr = -1;
dm->death_pipe_rd = -1;
dm->stop_pipe_rd = -1;
dm->stop_pipe_wr = -1;
dm->active = true;
if (!(flags & FUSE_DAEMONIZE_NO_CHDIR))
(void)chdir("/");
if (!(flags & FUSE_DAEMONIZE_NO_BACKGROUND))
if (!(flags & FUSE_DAEMONIZE_NO_BACKGROUND)) {
dm->active = true;
err = do_daemonize(dm);
}
return err;
}
+38 -22
View File
@@ -4470,14 +4470,25 @@ static void *session_sync_init_worker(void *data)
return NULL;
}
/* Enable synchronous FUSE_INIT and start worker thread */
static int session_start_sync_init(struct fuse_session *se, int fd)
/*
* Enable synchronous FUSE_INIT and start worker thread
*
* @param is_sync_init set to true on return if sync init is enabled
* Returns 0 on success or kernel does not support it, -errno on unexpected failure
*/
static int session_start_sync_init(struct fuse_session *se, int fd,
bool *is_sync_init)
{
int err, res;
if (!se->want_sync_init) {
/* SYNC_INIT is required for io_uring(?) */
if ((se->uring.enable && !fuse_daemonize_is_used()) || se->debug)
*is_sync_init = false;
/*
* Older fuse servers do not set want_sync_init or start the new
* daemonize code, so they get async init.
*/
if (!fuse_daemonize_is_used() || !se->want_sync_init) {
if (se->debug)
fuse_log(FUSE_LOG_DEBUG,
"fuse: sync init not enabled\n");
return 0;
@@ -4533,6 +4544,8 @@ static int session_start_sync_init(struct fuse_session *se, int fd)
return -EIO;
}
*is_sync_init = true;
return 0;
}
@@ -4590,8 +4603,9 @@ static int session_wait_sync_init_completion(struct fuse_session *se)
*/
static int new_api_fusermount(struct fuse_session *se,
const char *mountpoint,
const char *mnt_opts,
int *sock_fd, pid_t *fusermount_pid)
const char *mtab_opts,
int *sock_fd, pid_t *fusermount_pid,
bool *is_sync_init)
{
int fd, err;
@@ -4604,7 +4618,7 @@ static int new_api_fusermount(struct fuse_session *se,
fuse_log(FUSE_LOG_ERR, "fuse: sync init completion failed\n");
/* Call fusermount3 with --sync-init */
fd = mount_fusermount_obtain_fd(mountpoint, se->mo, mnt_opts, sock_fd,
fd = mount_fusermount_obtain_fd(mountpoint, se->mo, mtab_opts, sock_fd,
fusermount_pid);
if (fd < 0) {
fuse_log(FUSE_LOG_ERR,
@@ -4614,7 +4628,7 @@ static int new_api_fusermount(struct fuse_session *se,
/* Start worker thread with correct fd from fusermount3 */
se->fd = fd;
err = session_start_sync_init(se, fd);
err = session_start_sync_init(se, fd, is_sync_init);
if (err) {
fuse_log(FUSE_LOG_ERR,
"fuse: failed to start sync init worker\n");
@@ -4643,11 +4657,12 @@ static int fuse_session_mount_new_api(struct fuse_session *se,
int sock_fd = -1;
pid_t fusermount_pid = -1;
int res, err;
char *mnt_opts = NULL;
char *mnt_opts_with_fd = NULL;
char *mtab_opts = NULL;
char *mtab_opts_with_fd = NULL;
char fd_opt[32];
bool is_sync_init = false;
res = fuse_kern_mount_get_base_mnt_opts(se->mo, &mnt_opts);
res = fuse_kern_mount_get_base_mtab_opts(se->mo, &mtab_opts);
err = -EIO;
if (res == -1) {
fuse_log(FUSE_LOG_ERR,
@@ -4662,26 +4677,27 @@ static int fuse_session_mount_new_api(struct fuse_session *se,
}
se->fd = fd;
err = session_start_sync_init(se, fd);
err = session_start_sync_init(se, fd, &is_sync_init);
if (err)
goto err;
snprintf(fd_opt, sizeof(fd_opt), "fd=%i", fd);
err = -ENOMEM;
if (fuse_opt_add_opt(&mnt_opts_with_fd, mnt_opts) == -1 ||
fuse_opt_add_opt(&mnt_opts_with_fd, fd_opt) == -1) {
if (fuse_opt_add_opt(&mtab_opts_with_fd, mtab_opts) == -1 ||
fuse_opt_add_opt(&mtab_opts_with_fd, fd_opt) == -1) {
goto err;
}
/* Try to mount directly */
err = fuse_kern_fsmount_mo(mountpoint, se->mo, mnt_opts_with_fd);
err = fuse_kern_fsmount_mo(mountpoint, se->mo, mtab_opts_with_fd);
/* If mount failed with EPERM, fall back to fusermount3 with sync-init */
if (err < 0 && errno == EPERM) {
close(fd);
se->fd = -1;
fd = new_api_fusermount(se, mountpoint, mnt_opts,
&sock_fd, &fusermount_pid);
fd = new_api_fusermount(se, mountpoint, mtab_opts,
&sock_fd, &fusermount_pid,
&is_sync_init);
if (fd < 0) {
err = fd;
goto err_with_sock;
@@ -4708,13 +4724,14 @@ err:
se->fd = -1;
se->error = err;
}
se->is_sync_init = is_sync_init;
/* Wait for synchronous FUSE_INIT to complete */
if (session_wait_sync_init_completion(se) < 0)
fuse_log(FUSE_LOG_ERR, "fuse: sync init completion failed\n");
se->is_sync_init = true;
free(mnt_opts);
free(mnt_opts_with_fd);
free(mtab_opts);
free(mtab_opts_with_fd);
return fd;
}
#else
@@ -4788,7 +4805,6 @@ int fuse_session_mount(struct fuse_session *se, const char *_mountpoint)
out:
se->fd = fd;
se->mountpoint = mountpoint;
fuse_daemonize_early_set_mounted();
return 0;
+19 -11
View File
@@ -416,6 +416,7 @@ int fuse_main_real_versioned(int argc, char *argv[],
struct fuse_cmdline_opts opts;
int res;
struct fuse_loop_config *loop_config = NULL;
bool self_daemonize = false;
if (fuse_parse_cmdline(&args, &opts) != 0)
return 1;
@@ -445,6 +446,21 @@ int fuse_main_real_versioned(int argc, char *argv[],
goto out1;
}
/* The application might have already started daemonization itself */
if (!fuse_daemonize_early_is_active()) {
int daemonize_early_flags = 0;
if (opts.foreground)
daemonize_early_flags |= FUSE_DAEMONIZE_NO_BACKGROUND;
res = fuse_daemonize_early_start(daemonize_early_flags);
if (res != 0) {
fuse_log(FUSE_LOG_ERR, "fuse: daemonize_early_start failed\n");
goto out1;
}
self_daemonize = true;
}
fuse = _fuse_new_31(&args, op, op_size, version, user_data);
if (fuse == NULL) {
res = 3;
@@ -457,22 +473,14 @@ int fuse_main_real_versioned(int argc, char *argv[],
goto out2;
}
/*
* fuse_daemonize() already checks and fails then, but we need to
* handle it gracefully here, as this is done libfuse internal
* and caller didn't ask to daemonize with old API.
*/
if (!fuse_daemonize_early_is_active()) {
if (fuse_daemonize(opts.foreground) != 0) {
res = 5;
goto out3;
}
}
if (fuse_set_signal_handlers(se) != 0) {
res = 6;
goto out3;
}
if (self_daemonize)
fuse_daemonize_early_success();
if (opts.singlethread)
res = fuse_loop(fuse);
else {
+39 -23
View File
@@ -622,19 +622,20 @@ out_close:
* Wrapper for fuse_kern_fsmount that accepts struct mount_opts
* @mnt: mountpoint
* @mo: mount options
* @mnt_opts: mount options to pass to the kernel
* @mtab_opts: options recorded in /etc/mtab via fuse_mnt_add_mount_helper();
* see fuse_kern_fsmount() for details
*
* Returns: 0 on success, -1 on failure with errno set
*/
int fuse_kern_fsmount_mo(const char *mnt, const struct mount_opts *mo,
const char *mnt_opts)
const char *mtab_opts)
{
/* codeql[cpp/path-injection] verification is in the function */
const char *devname = fuse_mnt_get_devname();
return fuse_kern_fsmount(mnt, mo->flags, mo->blkdev, mo->fsname,
mo->subtype, devname, mo->kernel_opts,
mnt_opts);
mtab_opts);
}
#endif
@@ -642,13 +643,16 @@ int fuse_kern_fsmount_mo(const char *mnt, const struct mount_opts *mo,
* Complete the mount operation with an already-opened fd
* @mnt: mountpoint
* @mo: mount options
* @mnt_opts: mount options to pass to the kernel
* @mtab_opts: mtab/utab record string written via fuse_mnt_add_mount_helper().
* Built by fuse_kern_mount_get_base_mtab_opts(); intentionally
* overlaps with mo->kernel_opts so /etc/mtab reflects the options
* the kernel saw.
*
* Returns: 0 on success, -1 on failure,
* FUSE_MOUNT_FALLBACK_NEEDED if fusermount should be used
*/
int fuse_kern_do_mount(const char *mnt, struct mount_opts *mo,
const char *mnt_opts)
const char *mtab_opts)
{
char *source = NULL;
char *type = NULL;
@@ -708,7 +712,7 @@ int fuse_kern_do_mount(const char *mnt, struct mount_opts *mo,
goto out_close;
}
res = fuse_mnt_add_mount_helper(mnt, source, type, mnt_opts);
res = fuse_mnt_add_mount_helper(mnt, source, type, mtab_opts);
if (res == -1)
goto out_umount;
@@ -726,7 +730,7 @@ out_close:
}
static int fuse_mount_sys(const char *mnt, struct mount_opts *mo,
const char *mnt_opts)
const char *mtab_opts)
{
int fd;
int res;
@@ -735,7 +739,7 @@ static int fuse_mount_sys(const char *mnt, struct mount_opts *mo,
if (fd == -1)
return -1;
res = fuse_kern_do_mount(mnt, mo, mnt_opts);
res = fuse_kern_do_mount(mnt, mo, mtab_opts);
if (res) {
close(fd);
return res;
@@ -744,16 +748,20 @@ static int fuse_mount_sys(const char *mnt, struct mount_opts *mo,
return fd;
}
static int get_mnt_flag_opts(char **mnt_optsp, int flags)
/*
* Append the flag-mirror prefix (rw/nosuid/nodev/...) of the mtab record
* to @mtab_optsp, derived from the MS_* bitmask in @flags.
*/
static int get_mtab_flag_opts(char **mtab_optsp, int flags)
{
int i;
if (!(flags & MS_RDONLY) && fuse_opt_add_opt(mnt_optsp, "rw") == -1)
if (!(flags & MS_RDONLY) && fuse_opt_add_opt(mtab_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)
fuse_opt_add_opt(mtab_optsp, mount_flags[i].opt) == -1)
return -1;
}
return 0;
@@ -792,13 +800,21 @@ void destroy_mount_opts(struct mount_opts *mo)
free(mo);
}
int fuse_kern_mount_get_base_mnt_opts(const struct mount_opts *mo, char **mnt_optsp)
/*
* Build the mtab/utab record string for this mount: flag-mirrors of MS_*
* (rw/nosuid/nodev/...) + the kernel-bound -o options + the mtab-only
* annotations. The result is what fuse_mnt_add_mount_helper() writes into
* /etc/mtab (or /run/mount/utab). The overlap with @mo->kernel_opts is
* intentional so the mtab line reflects what the kernel saw.
*/
int fuse_kern_mount_get_base_mtab_opts(const struct mount_opts *mo,
char **mtab_optsp)
{
if (get_mnt_flag_opts(mnt_optsp, mo->flags) == -1)
if (get_mtab_flag_opts(mtab_optsp, mo->flags) == -1)
return -1;
if (mo->kernel_opts && fuse_opt_add_opt(mnt_optsp, mo->kernel_opts) == -1)
if (mo->kernel_opts && fuse_opt_add_opt(mtab_optsp, mo->kernel_opts) == -1)
return -1;
if (mo->mtab_opts && fuse_opt_add_opt(mnt_optsp, mo->mtab_opts) == -1)
if (mo->mtab_opts && fuse_opt_add_opt(mtab_optsp, mo->mtab_opts) == -1)
return -1;
return 0;
}
@@ -806,13 +822,13 @@ int fuse_kern_mount_get_base_mnt_opts(const struct mount_opts *mo, char **mnt_op
int fuse_kern_mount(const char *mountpoint, struct mount_opts *mo)
{
int res = -1;
char *mnt_opts = NULL;
char *mtab_opts = NULL;
res = -1;
if (fuse_kern_mount_get_base_mnt_opts(mo, &mnt_opts) == -1)
if (fuse_kern_mount_get_base_mtab_opts(mo, &mtab_opts) == -1)
goto out;
res = fuse_mount_sys(mountpoint, mo, mnt_opts);
res = fuse_mount_sys(mountpoint, mo, mtab_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.
@@ -821,14 +837,14 @@ int fuse_kern_mount(const char *mountpoint, struct mount_opts *mo)
}
} else if (res == FUSE_MOUNT_FALLBACK_NEEDED) {
if (mo->fusermount_opts &&
fuse_opt_add_opt(&mnt_opts, mo->fusermount_opts) == -1)
fuse_opt_add_opt(&mtab_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 ||
if (fuse_opt_add_opt(&tmp_opts, mtab_opts) == -1 ||
fuse_opt_add_opt(&tmp_opts, mo->subtype_opt) == -1) {
free(tmp_opts);
goto out;
@@ -838,13 +854,13 @@ int fuse_kern_mount(const char *mountpoint, struct mount_opts *mo)
free(tmp_opts);
if (res == -1)
res = fuse_mount_fusermount(mountpoint, mo,
mnt_opts, 0);
mtab_opts, 0);
} else {
res = fuse_mount_fusermount(mountpoint, mo, mnt_opts, 0);
res = fuse_mount_fusermount(mountpoint, mo, mtab_opts, 0);
}
}
out:
free(mnt_opts);
free(mtab_opts);
return res;
}
+26 -16
View File
@@ -39,6 +39,7 @@
* Convert MS_* mount flags to MOUNT_ATTR_* mount attributes.
* These flags are passed to fsmount(), not fsconfig().
* Mount attributes control mount-point level behavior.
* To called after set_ms_flags() which consumes the fsconfig flags.
*
* @attrs MOUNT_ATTR flags, built from MS_ flags
* @return remaining flags
@@ -108,8 +109,12 @@ static void log_fsconfig_kmsg(int fd)
/*
* Apply VFS superblock (fsconfig) flags to the filesystem context.
* Only handles flags that are filesystem parameters (ro, sync, dirsync).
* Mount attributes (nosuid, nodev, etc.) are handled separately via fsmount().
* Handles the fsconfig leg of every entry whose is_fsconfig is set
* (ro, rw, sync, async, dirsync). Mount attributes (nosuid, nodev, etc.)
* are handled separately via fsmount().
*
* Entries that have *both* legs (ro/rw) leave the MS_ bit in *ms_flags
* so that ms_flags_to_mount_attrs() can also pick them up.
*
* @ms_flags flags to set, outvalue are the remaining flags
* @return 0 on success, negative error code on failure
@@ -120,8 +125,7 @@ static int set_ms_flags(int fsfd, unsigned long *ms_flags)
int i;
for (i = 0; mount_flags[i].opt != NULL && flags != 0; i++) {
/* Only process fsconfig flags (mount_attr == 0) with on==1 */
if (mount_flags[i].mount_attr || !mount_flags[i].on)
if (!mount_flags[i].is_fsconfig || !mount_flags[i].on)
continue;
if (!(flags & mount_flags[i].flag))
@@ -137,7 +141,14 @@ static int set_ms_flags(int fsfd, unsigned long *ms_flags)
return -save_errno;
}
flags &= ~mount_flags[i].flag;
/*
* Only consume the bit if no fsmount mount-attr leg is
* also pending for this option. Otherwise leave it for
* ms_flags_to_mount_attrs() to apply via fsmount().
*/
if (!mount_flags[i].mount_attr)
flags &= ~mount_flags[i].flag;
}
*ms_flags = flags;
@@ -303,12 +314,11 @@ static int apply_mount_opts(int fsfd, const char *opts)
* not fsconfig().
*
* These string options (nosuid, nodev, etc.) are reconstructed
* Skip mtab-only options - they're for /run/mount/utab, not kernel
* from MS_* flags by get_mnt_flag_opts() in lib/mount.c and
* get_mnt_opts() in util/fusermount.c. Both the library path
* (via fuse_kern_mount_get_base_mnt_opts) and fusermount3 path
* from MS_* flags by get_mtab_flag_opts() in lib/mount.c and
* get_mtab_opts() in util/fusermount.c. Both the library path
* (via fuse_kern_mount_get_base_mtab_opts) and fusermount3 path
* rebuild these strings from the flags bitmask and pass them in
* mnt_opts. They must be filtered here because they are mount
* mtab_opts. They must be filtered here because they are mount
* attributes (passed to fsmount via MOUNT_ATTR_*), not
* filesystem parameters (which would be passed to fsconfig).
*
@@ -332,7 +342,7 @@ static int apply_mount_opts(int fsfd, const char *opts)
int fuse_kern_fsmount(const char *mnt, unsigned long flags, int blkdev,
const char *fsname, const char *subtype,
const char *source_dev, const char *kernel_opts,
const char *mnt_opts)
const char *mtab_opts)
{
char *type = NULL;
char *source = NULL;
@@ -397,13 +407,13 @@ int fuse_kern_fsmount(const char *mnt, unsigned long flags, int blkdev,
goto out_free;
}
/* Apply additional mount options */
err = apply_mount_opts(fsfd, mnt_opts);
/* Apply mtab options (overlap with kernel_opts is filtered and tolerated) */
err = apply_mount_opts(fsfd, mtab_opts);
if (err < 0) {
log_fsconfig_kmsg(fsfd);
fprintf(stderr,
"fuse: failed to apply additional mount options '%s'\n",
mnt_opts);
"fuse: failed to apply mtab options '%s'\n",
mtab_opts);
goto out_free;
}
@@ -448,7 +458,7 @@ int fuse_kern_fsmount(const char *mnt, unsigned long flags, int blkdev,
goto out_close_mntfd;
}
err = fuse_mnt_add_mount_helper(mnt, source, type, mnt_opts);
err = fuse_mnt_add_mount_helper(mnt, source, type, mtab_opts);
if (err == -1)
goto out_umount;
+10 -5
View File
@@ -31,7 +31,8 @@ struct mount_opts {
int fuse_kern_mount_prepare(const char *mnt, struct mount_opts *mo);
int fuse_kern_mount_get_base_mnt_opts(const struct mount_opts *mo, char **mnt_optsp);
int fuse_kern_mount_get_base_mtab_opts(const struct mount_opts *mo,
char **mtab_optsp);
/**
* Mount using the new Linux mount API (fsopen/fsconfig/fsmount/move_mount)
@@ -41,18 +42,22 @@ int fuse_kern_mount_get_base_mnt_opts(const struct mount_opts *mo, char **mnt_op
* @fsname: filesystem name (or NULL)
* @subtype: filesystem subtype (or NULL)
* @source_dev: device name for building source string
* @kernel_opts: kernel mount options string
* @mnt_opts: additional mount options to pass to the kernel
* @kernel_opts: kernel mount options applied via fsconfig()
* @mtab_opts: options recorded in /etc/mtab (or /run/mount/utab) via
* fuse_mnt_add_mount_helper(). May overlap with @kernel_opts
* because /etc/mtab is expected to display kernel-visible
* options; the overlap is filtered before fsconfig where
* needed and is idempotent at the kernel.
*
* Returns: 0 on success, -1 on failure with errno set
*/
int fuse_kern_fsmount(const char *mnt, unsigned long flags, int blkdev,
const char *fsname, const char *subtype,
const char *source_dev, const char *kernel_opts,
const char *mnt_opts);
const char *mtab_opts);
int fuse_kern_fsmount_mo(const char *mnt, const struct mount_opts *mo,
const char *mnt_opts);
const char *mtab_opts);
int mount_fusermount_obtain_fd(const char *mountpoint,
struct mount_opts *mo,
const char *opts, int *sock_fd_out,
+28 -22
View File
@@ -108,26 +108,32 @@
#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 mount_attr */
{"rw", MS_RDONLY, 0, 1, MOUNT_ATTR_RDONLY}, /* fsconfig */
{"ro", MS_RDONLY, 1, 1, MOUNT_ATTR_RDONLY}, /* fsconfig */
{"suid", MS_NOSUID, 0, 0, MOUNT_ATTR_NOSUID}, /* fsmount */
{"nosuid", MS_NOSUID, 1, 1, MOUNT_ATTR_NOSUID}, /* fsmount */
{"dev", MS_NODEV, 0, 1, MOUNT_ATTR_NODEV}, /* fsmount */
{"nodev", MS_NODEV, 1, 1, MOUNT_ATTR_NODEV}, /* fsmount */
{"exec", MS_NOEXEC, 0, 1, MOUNT_ATTR_NOEXEC}, /* fsmount */
{"noexec", MS_NOEXEC, 1, 1, MOUNT_ATTR_NOEXEC}, /* fsmount */
{"async", MS_SYNCHRONOUS, 0, 1, 0}, /* fsconfig */
{"sync", MS_SYNCHRONOUS, 1, 1, 0}, /* fsconfig */
{"noatime", MS_NOATIME, 1, 1, MOUNT_ATTR_NOATIME}, /* fsmount */
{"nodiratime", MS_NODIRATIME, 1, 1, MOUNT_ATTR_NODIRATIME}, /* fsmount */
{"norelatime", MS_RELATIME, 0, 1, MOUNT_ATTR_RELATIME}, /* fsmount */
{"nostrictatime", MS_STRICTATIME, 0, 1, MOUNT_ATTR_STRICTATIME},/* fsmount */
{"symfollow", MS_NOSYMFOLLOW, 0, 1, MOUNT_ATTR_NOSYMFOLLOW},/* fsmount */
{"nosymfollow", MS_NOSYMFOLLOW, 1, 1, MOUNT_ATTR_NOSYMFOLLOW},/* fsmount */
{"dirsync", MS_DIRSYNC, 1, 1, 0}, /* fsconfig */
{NULL, 0, 0, 0, 0}
/* 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
@@ -472,7 +478,7 @@ int fuse_mnt_parse_fuse_fd(const char *mountpoint)
}
int fuse_mnt_add_mount_helper(const char *mnt, const char *source,
const char *type, const char *mnt_opts)
const char *type, const char *mtab_opts)
{
#ifndef IGNORE_MTAB
if (geteuid() == 0) {
@@ -483,7 +489,7 @@ int fuse_mnt_add_mount_helper(const char *mnt, const char *source,
return -1;
res = fuse_mnt_add_mount("fuse", source, newmnt, type,
mnt_opts);
mtab_opts);
free(newmnt);
return res;
}
@@ -491,7 +497,7 @@ int fuse_mnt_add_mount_helper(const char *mnt, const char *source,
(void)mnt;
(void)source;
(void)type;
(void)mnt_opts;
(void)mtab_opts;
return 0;
}
+3 -2
View File
@@ -18,7 +18,8 @@ struct mount_flags {
unsigned long flag;
int on;
int safe; /* used by fusermount */
unsigned long mount_attr; /* MOUNT_ATTR_* value for fsmount (0 = fsconfig flag) */
int is_fsconfig; /* apply via fsconfig SET_FLAG (superblock-level) */
unsigned long mount_attr; /* MOUNT_ATTR_* for fsmount (0 = none) */
};
extern const struct mount_flags mount_flags[];
@@ -34,7 +35,7 @@ int fuse_mnt_parse_fuse_fd(const char *mountpoint);
/* Helper functions for mount operations */
const char *fuse_mnt_get_devname(void);
int fuse_mnt_add_mount_helper(const char *mnt, const char *source,
const char *type, const char *mnt_opts);
const char *type, const char *mtab_opts);
/* Build source and type strings for mounting */
char *fuse_mnt_build_source(const char *fsname, const char *subtype,
+7 -3
View File
@@ -2,7 +2,7 @@
set -e
TEST_CMD="pytest -vv --tb=short --maxfail=1 --log-level=INFO --log-cli-level=INFO test/"
TEST_CMD="meson test -C . --print-errorlogs"
SAN="-Db_sanitize=address,undefined"
# not default
@@ -136,8 +136,12 @@ sanitized_build()
sudo chmod 4755 util/fuservicemount3
fi
# Test as root and regular user
sudo env PATH=$PATH ${TEST_CMD}
# Test as root and regular user. Give the root run a distinct
# meson log basename so its meson-logs/testlog.* files don't end
# up owned by root and block the subsequent user run from writing
# them.
sudo env PATH=$PATH ${TEST_CMD} --logbase=testlog-root
# Cleanup temporary files (since they are now owned by root)
sudo rm -rf test/.pytest_cache/ test/__pycache__
+21 -5
View File
@@ -35,19 +35,35 @@ td += executable('test_loop_config', 'test_loop_config.c',
install: false)
test_scripts = [ 'conftest.py', 'pytest.ini', 'test_examples.py',
'util.py', 'test_ctests.py', 'test_custom_io.py' ]
'util.py', 'test_ctests.py', 'test_custom_io.py',
'test_mount_state.py' ]
td += custom_target('test_scripts', input: test_scripts,
output: test_scripts, build_by_default: true,
command: ['cp', '-fPp',
'@INPUT@', meson.current_build_dir() ])
# Provide something helpful when running 'ninja test'
if meson.is_subproject()
test('libfuse is a subproject, skipping tests', executable('wrong_command',
'wrong_command.c', install: false,
c_args: [ '-DMESON_IS_SUBPROJECT' ]))
else
test('wrong_command', executable('wrong_command', 'wrong_command.c',
install: false))
# Run the pytest suite via 'meson test'. The same definition works
# both as a regular user and under sudo: invoke 'meson test -C build'
# for the unprivileged subset, or 'sudo -E meson test -C build' to
# also exercise the tests that require root (they pick the uid up at
# runtime via os.geteuid()).
pytest = find_program('pytest', required: false)
if pytest.found()
test('pytest', pytest,
args: ['-vv', '--tb=short', '--maxfail=1',
'--log-level=INFO', '--log-cli-level=INFO',
meson.current_build_dir()],
depends: td,
workdir: meson.project_build_root(),
timeout: 600,
is_parallel: false)
else
test('pytest not found', executable('wrong_command',
'wrong_command.c', install: false))
endif
endif
+170
View File
@@ -0,0 +1,170 @@
#!/usr/bin/env python3
'''
Tests that observable mount state (as exposed by /proc/self/mountinfo)
matches the options requested at mount time.
Existing tests check filesystem behavior (read/write/xattr/...) but
never inspect the post-mount metadata recorded by the kernel. That
metadata is populated differently by the legacy mount(2) path and the
new fsopen/fsconfig/fsmount path, so an option dropped on one path can
go undetected the subtype regression in 5e9e16d6 is one example.
These tests assert what /proc/self/mountinfo reports for each mount,
so a parity bug between the two paths fails loudly.
'''
if __name__ == '__main__':
import pytest
import sys
sys.exit(pytest.main([__file__] + sys.argv[1:]))
import os
import subprocess
import pytest
from contextlib import contextmanager
from os.path import join as pjoin
from util import (wait_for_mount, umount, cleanup, base_cmdline, basename,
fuse_test_marker, parse_mountinfo)
pytestmark = fuse_test_marker()
@contextmanager
def hello_mount(tmpdir, output_checker, name, options=()):
mnt_dir = str(tmpdir)
cmdline = base_cmdline + [pjoin(basename, 'example', name),
'-f', mnt_dir]
if name == 'hello_ll':
cmdline.append('-s')
if options:
cmdline += ['-o', ','.join(options)]
mp = subprocess.Popen(cmdline, stdout=output_checker.fd,
stderr=output_checker.fd)
try:
wait_for_mount(mp, mnt_dir)
yield mnt_dir
except:
cleanup(mp, mnt_dir)
raise
else:
umount(mp, mnt_dir)
@pytest.mark.parametrize('name', ('hello', 'hello_ll'))
def test_mountinfo_baseline(tmpdir, output_checker, name):
# libfuse's add_default_subtype() (lib/helper.c) defaults the
# subtype to basename(argv[0]) when the caller didn't pass
# -o fsname=/-o subtype=, so the bare-mount fstype is
# 'fuse.<example-name>', not 'fuse'. The override case is what
# test_mountinfo_subtype below verifies; here we just assert the
# fuse-ness and the standard kernel-side identity options.
with hello_mount(tmpdir, output_checker, name) as mnt:
info = parse_mountinfo(mnt)
assert info is not None, 'mountpoint not found in /proc/self/mountinfo'
assert info['fstype'] in ('fuse', 'fuse.' + name), \
'unexpected fstype %r (expected fuse or fuse.%s)' % \
(info['fstype'], name)
assert any(o.startswith('user_id=') for o in info['super_options'])
assert any(o.startswith('group_id=') for o in info['super_options'])
@pytest.mark.parametrize('name', ('hello', 'hello_ll'))
def test_mountinfo_subtype(tmpdir, output_checker, name):
# Regression guard for 5e9e16d6: the new mount API needs an
# explicit fsconfig(SET_STRING,"subtype",...). Without it the
# kernel records fstype=='fuse' (or whatever basename default
# leaks through) instead of the user-requested 'fuse.<subtype>'.
# An explicit -o subtype= must override the basename default.
with hello_mount(tmpdir, output_checker, name,
('subtype=mysub',)) as mnt:
info = parse_mountinfo(mnt)
assert info is not None
assert info['fstype'] == 'fuse.mysub', \
'explicit subtype not propagated: fstype=%r' % info['fstype']
@pytest.mark.parametrize('name', ('hello', 'hello_ll'))
def test_mountinfo_fsname(tmpdir, output_checker, name):
with hello_mount(tmpdir, output_checker, name,
('fsname=myfsname',)) as mnt:
info = parse_mountinfo(mnt)
assert info is not None
assert info['source'] == 'myfsname', \
'fsname not propagated: source=%r' % info['source']
@pytest.mark.parametrize('name', ('hello', 'hello_ll'))
def test_mountinfo_subtype_fsname(tmpdir, output_checker, name):
with hello_mount(tmpdir, output_checker, name,
('subtype=mysub', 'fsname=myfsname')) as mnt:
info = parse_mountinfo(mnt)
assert info is not None
assert info['fstype'] == 'fuse.mysub'
# 'mysub#myfsname' is the ENODEV-fallback form when the kernel
# rejects fuse.<subtype>; accept either so the test isn't fragile.
assert info['source'] in ('myfsname', 'mysub#myfsname'), \
'unexpected source: %r' % info['source']
# (label, options, must-be-in mount_options, must-NOT-be-in mount_options)
#
# Library defaults are MS_NOSUID|MS_NODEV (lib/mount.c,
# util/fusermount.c), so a no-options mount is expected to land
# with both attrs set. The negation forms (suid/dev) clear the default
# flags via lib/mount.c:set_mount_flag(), which on the new mount API
# path means MOUNT_ATTR_NOSUID/MOUNT_ATTR_NODEV are not set in the
# fsmount() call. Asserting their absence catches a routing bug where
# the negation wasn't honored.
ATTR_CASES = [
('default', (), ('rw', 'nosuid', 'nodev'), ('ro',)),
('ro', ('ro',), ('ro',), ('rw',)),
('nosuid', ('nosuid',), ('nosuid',), ()),
('nodev', ('nodev',), ('nodev',), ()),
]
# suid/dev are root-only: the kernel rejects MS_NOSUID/MS_NODEV being
# cleared for unprivileged mounts, and fusermount3 hard-codes them on
# anyway (util/fusermount.c:988).
ATTR_CASES_ROOT = [
('suid', ('suid',), (), ('nosuid',)),
('dev', ('dev',), (), ('nodev',)),
]
def _check_attrs(info, must_have, must_not_have):
assert info is not None
for opt in must_have:
assert opt in info['mount_options'], \
'%r missing from mount_options=%r' % (opt, info['mount_options'])
for opt in must_not_have:
assert opt not in info['mount_options'], \
'unexpected %r in mount_options=%r' % (opt, info['mount_options'])
@pytest.mark.parametrize('name', ('hello', 'hello_ll'))
@pytest.mark.parametrize('label,opts,must_have,must_not_have', ATTR_CASES,
ids=[c[0] for c in ATTR_CASES])
def test_mountinfo_attrs(tmpdir, output_checker, name,
label, opts, must_have, must_not_have):
with hello_mount(tmpdir, output_checker, name, opts) as mnt:
info = parse_mountinfo(mnt)
_check_attrs(info, must_have, must_not_have)
# ro/rw also surface in super_options; if we asked for ro the
# superblock must agree. Catches a path that sets MOUNT_ATTR_RDONLY
# but forgets the FSCONFIG-side MS_RDONLY (or vice versa).
if 'ro' in must_have:
assert 'ro' in info['super_options'], \
'ro on mount but rw on superblock: super_options=%r' % \
info['super_options']
@pytest.mark.parametrize('name', ('hello', 'hello_ll'))
@pytest.mark.parametrize('label,opts,must_have,must_not_have',
ATTR_CASES_ROOT,
ids=[c[0] for c in ATTR_CASES_ROOT])
def test_mountinfo_attrs_root(tmpdir, output_checker, name,
label, opts, must_have, must_not_have):
if os.getuid() != 0:
pytest.skip('clearing nosuid/nodev requires root')
with hello_mount(tmpdir, output_checker, name, opts) as mnt:
info = parse_mountinfo(mnt)
_check_attrs(info, must_have, must_not_have)
+45
View File
@@ -125,6 +125,51 @@ def umount(mount_process, mnt_dir):
pytest.fail('mount process did not terminate')
def parse_mountinfo(mnt_dir):
'''Return the /proc/self/mountinfo entry for *mnt_dir*, or None.
Parses the line for the mountpoint and returns a dict with keys:
'mountpoint' - the mount point path (str)
'fstype' - filesystem type as the kernel sees it,
e.g. 'fuse' or 'fuse.<subtype>' (str)
'source' - mount source field, e.g. 'hello' or
'<subtype>#<fsname>' fallback form (str)
'mount_options' - per-mount options/attrs (set of str), e.g.
{'rw','nosuid','nodev','noatime','relatime'}
'super_options' - superblock options from the filesystem (set of
str), e.g. {'rw','user_id=1000','group_id=1000',
'default_permissions','allow_other','max_read=...'}
These fields are exactly what /proc/self/mountinfo exposes (see
`man 5 proc.5_mountinfo`); they capture the post-mount state that
differs between the legacy mount(2) path and the new fsopen/fsconfig/
fsmount path. Asserting on this dict catches bugs where one path
drops a parameter the other passes (e.g. `subtype` showing up in
`fstype`, `user=` ending up only in /run/mount/utab, ...).
'''
target = os.path.realpath(mnt_dir)
with open('/proc/self/mountinfo') as fh:
for line in fh:
parts = line.rstrip('\n').split(' ')
try:
sep = parts.index('-')
except ValueError:
continue
if len(parts) < sep + 4 or sep < 6:
continue
mountpoint = parts[4].replace('\\040', ' ')
if mountpoint != target:
continue
return {
'mountpoint': mountpoint,
'fstype': parts[sep + 1],
'source': parts[sep + 2].replace('\\040', ' '),
'mount_options': set(parts[5].split(',')),
'super_options': set(parts[sep + 3].split(',')),
}
return None
def safe_sleep(secs):
'''Like time.sleep(), but sleep for at least *secs*
+56 -45
View File
@@ -575,34 +575,41 @@ static int add_option(char **optsp, const char *opt, unsigned expand)
return 0;
}
static int get_mnt_opts(int flags, const char *opts, char **mnt_optsp)
/*
* 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(mnt_optsp, "rw", 0) == -1)
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(mnt_optsp, mount_flags[i].opt, 0) == -1)
add_option(mtab_optsp, mount_flags[i].opt, 0) == -1)
return -1;
}
if (add_option(mnt_optsp, opts, 0) == -1)
if (add_option(mtab_optsp, opts, 0) == -1)
return -1;
/* remove comma from end of opts*/
l = strlen(*mnt_optsp);
if ((*mnt_optsp)[l-1] == ',')
(*mnt_optsp)[l-1] = '\0';
l = strlen(*mtab_optsp);
if ((*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(mnt_optsp, "user=", strlen(user)) == -1)
if (add_option(mtab_optsp, "user=", strlen(user)) == -1)
return -1;
strcat(*mnt_optsp, user);
strcat(*mtab_optsp, user);
}
return 0;
}
@@ -674,7 +681,11 @@ struct mount_params {
/* Generated mount parameters */
char *source; /* Mount source string */
char *type; /* Filesystem type string */
char *mnt_opts; /* Mount table options */
/* 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 */
@@ -687,7 +698,7 @@ static void free_mount_params(struct mount_params *mp)
free(mp->subtype);
free(mp->source);
free(mp->type);
free(mp->mnt_opts);
free(mp->mtab_opts);
memset(mp, 0, sizeof(*mp));
}
@@ -801,7 +812,7 @@ static int prepare_mount(const char *opts, struct mount_params *mp)
s++;
}
*d = '\0';
res = get_mnt_opts(mp->flags, mp->optbuf, &mp->mnt_opts);
res = get_mtab_opts(mp->flags, mp->optbuf, &mp->mtab_opts);
if (res == -1)
goto err;
@@ -868,7 +879,7 @@ static int perform_mount(const char *mnt, struct mount_params *mp)
static int do_mount(const char *mnt, const char **typep, mode_t rootmode,
int fd, const char *opts, const char *dev, char **sourcep,
char **mnt_optsp)
char **mtab_optsp)
{
struct mount_params mp = { .fd = fd }; /* implicit zero of other params */
int res;
@@ -888,7 +899,7 @@ static int do_mount(const char *mnt, const char **typep, mode_t rootmode,
*sourcep = mp.source;
*typep = mp.type;
*mnt_optsp = mp.mnt_opts;
*mtab_optsp = mp.mtab_opts;
/* Free only the intermediate allocations, not the returned ones */
free(mp.fsname);
@@ -1005,9 +1016,9 @@ struct mount_context {
const char *dev;
struct stat stbuf;
char *source;
char *mnt_opts;
char *mtab_opts; /* mtab/utab record string for add_mount() */
char *x_opts;
char *kern_mnt_opts; /* mnt_opts with removed x_opts */
char *kern_mnt_opts; /* user-provided -o opts with x-* removed */
};
/*
@@ -1081,7 +1092,7 @@ static int mount_fuse_finish_fsmount(const char *mnt,
.rootmode = ctx->stbuf.st_mode & S_IFMT,
.dev = ctx->dev,
};
char *final_mnt_opts = NULL;
char *final_mtab_opts = NULL;
res = prepare_mount(ctx->kern_mnt_opts, &mp);
if (res == -1)
@@ -1091,27 +1102,27 @@ static int mount_fuse_finish_fsmount(const char *mnt,
* Merge x-options if running as root, root is allowed to update
* /etc/mtab or /run/mount/utab
*/
final_mnt_opts = mp.mnt_opts;
final_mtab_opts = mp.mtab_opts;
if (geteuid() == 0 && ctx->x_opts && strlen(ctx->x_opts) > 0) {
char *x_mnt_opts = NULL;
char *x_mtab_opts = NULL;
int ret;
if (strlen(mp.mnt_opts) > 0)
ret = asprintf(&x_mnt_opts, "%s,%s",
mp.mnt_opts, ctx->x_opts);
if (strlen(mp.mtab_opts) > 0)
ret = asprintf(&x_mtab_opts, "%s,%s",
mp.mtab_opts, ctx->x_opts);
else
ret = asprintf(&x_mnt_opts, "%s", ctx->x_opts);
ret = asprintf(&x_mtab_opts, "%s", ctx->x_opts);
if (ret < 0)
goto fail_free_params;
final_mnt_opts = x_mnt_opts;
final_mtab_opts = x_mtab_opts;
}
/* Use new mount API */
res = fuse_kern_fsmount(mnt, mp.flags, mp.blkdev,
mp.fsname, mp.subtype, ctx->dev,
mp.optbuf, final_mnt_opts);
mp.optbuf, final_mtab_opts);
if (res == -1)
goto fail_free_merged;
@@ -1124,7 +1135,7 @@ static int mount_fuse_finish_fsmount(const char *mnt,
/* Store results in context */
ctx->source = mp.source;
ctx->mnt_opts = final_mnt_opts;
ctx->mtab_opts = final_mtab_opts;
*type = mp.type;
res = 0;
@@ -1133,15 +1144,15 @@ static int mount_fuse_finish_fsmount(const char *mnt,
free(mp.fsname);
free(mp.subtype);
free(mp.optbuf);
if (final_mnt_opts != mp.mnt_opts)
free(mp.mnt_opts);
if (final_mtab_opts != mp.mtab_opts)
free(mp.mtab_opts);
out:
return res;
fail_free_merged:
if (final_mnt_opts != mp.mnt_opts)
free(final_mnt_opts);
if (final_mtab_opts != mp.mtab_opts)
free(final_mtab_opts);
fail_free_params:
free_mount_params(&mp);
fail:
@@ -1158,7 +1169,7 @@ static int mount_fuse(const char *mnt, const char *opts, const char **type)
const char *dev = fuse_mnt_get_devname();
struct stat stbuf;
char *source = NULL;
char *mnt_opts = NULL;
char *mtab_opts = NULL;
const char *real_mnt = mnt;
int mountpoint_fd = -1;
char *do_mount_opts = NULL;
@@ -1183,7 +1194,7 @@ static int mount_fuse(const char *mnt, const char *opts, const char **type)
restore_privs();
if (res != -1)
res = do_mount(real_mnt, type, stbuf.st_mode & S_IFMT,
fd, do_mount_opts, dev, &source, &mnt_opts);
fd, do_mount_opts, dev, &source, &mtab_opts);
if (mountpoint_fd != -1)
close(mountpoint_fd);
@@ -1203,24 +1214,24 @@ static int mount_fuse(const char *mnt, const char *opts, const char **type)
* Add back the options starting with "x-" to opts from
* do_mount. +2 for ',' and '\0'
*/
size_t mnt_opts_len = strlen(mnt_opts);
size_t x_mnt_opts_len = mnt_opts_len+
size_t mtab_opts_len = strlen(mtab_opts);
size_t x_mtab_opts_len = mtab_opts_len +
strlen(x_prefixed_opts) + 2;
char *x_mnt_opts = calloc(1, x_mnt_opts_len);
char *x_mtab_opts = calloc(1, x_mtab_opts_len);
if (mnt_opts_len) {
strcpy(x_mnt_opts, mnt_opts);
strncat(x_mnt_opts, ",", 2);
if (mtab_opts_len) {
strcpy(x_mtab_opts, mtab_opts);
strncat(x_mtab_opts, ",", 2);
}
strncat(x_mnt_opts, x_prefixed_opts,
x_mnt_opts_len - mnt_opts_len - 2);
strncat(x_mtab_opts, x_prefixed_opts,
x_mtab_opts_len - mtab_opts_len - 2);
free(mnt_opts);
mnt_opts = x_mnt_opts;
free(mtab_opts);
mtab_opts = x_mtab_opts;
}
res = add_mount(source, mnt, *type, mnt_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;
@@ -1229,7 +1240,7 @@ static int mount_fuse(const char *mnt, const char *opts, const char **type)
out_free:
free(source);
free(mnt_opts);
free(mtab_opts);
free(x_prefixed_opts);
free(do_mount_opts);
@@ -1288,7 +1299,7 @@ static int mount_fuse_sync_init(const char *mnt, const char *opts,
out:
close(fd);
free(ctx.source);
free(ctx.mnt_opts);
free(ctx.mtab_opts);
free(ctx.x_opts);
free(ctx.kern_mnt_opts);