[{"id":3685190,"web_url":"http://patchwork.ozlabs.org/comment/3685190/","msgid":"<20260501173556.GY7739@frogsfrogsfrogs>","list_archive_url":null,"date":"2026-05-01T17:35:56","subject":"Re: [PATCH 02/13] mount_service: add systemd socket service mounting\n helper","submitter":{"id":77032,"url":"http://patchwork.ozlabs.org/api/people/77032/","name":"Darrick J. Wong","email":"djwong@kernel.org"},"content":"On Thu, Apr 30, 2026 at 02:15:32PM -0700, Darrick J. Wong wrote:\n> From: Darrick J. Wong <djwong@kernel.org>\n> \n> Create a mount helper program that can start a fuse server that runs as\n> a socket-based systemd service, and a new libfuse module to wrap all the\n> details of communicating between the mount helper and the containerized\n> fuse server.\n> \n> This enables untrusted ext4 mounts via systemd service containers, which\n> avoids the problem of malicious filesystems compromising the integrity\n> of the running kernel through memory corruption.\n> \n> In theory this could also be supported via inetd and clones, though the\n> author hasn't found one that supports AF_UNIX sockets.\n> \n> Signed-off-by: \"Darrick J. Wong\" <djwong@kernel.org>\n> ---\n>  include/fuse_service.h                           |  243 ++++\n>  include/fuse_service_priv.h                      |  160 ++\n>  lib/mount_common_i.h                             |    3 \n>  util/mount_service.h                             |   40 +\n>  .github/workflows/install-ubuntu-dependencies.sh |    4 \n>  doc/fuservicemount3.8                            |   24 \n>  doc/meson.build                                  |    3 \n>  include/meson.build                              |    4 \n>  lib/fuse_service.c                               | 1233 +++++++++++++++++++\n>  lib/fuse_service_stub.c                          |  106 ++\n>  lib/fuse_versionscript                           |   17 \n>  lib/helper.c                                     |   51 +\n>  lib/meson.build                                  |   17 \n>  lib/mount.c                                      |   12 \n>  meson.build                                      |   34 +\n>  meson_options.txt                                |    9 \n>  util/fuservicemount.c                            |   18 \n>  util/meson.build                                 |    9 \n>  util/mount_service.c                             | 1427 ++++++++++++++++++++++\n>  19 files changed, 3412 insertions(+), 2 deletions(-)\n>  create mode 100644 include/fuse_service.h\n>  create mode 100644 include/fuse_service_priv.h\n>  create mode 100644 util/mount_service.h\n>  create mode 100644 doc/fuservicemount3.8\n>  create mode 100644 lib/fuse_service.c\n>  create mode 100644 lib/fuse_service_stub.c\n>  create mode 100644 util/fuservicemount.c\n>  create mode 100644 util/mount_service.c\n> \n> \n> diff --git a/include/fuse_service.h b/include/fuse_service.h\n> new file mode 100644\n> index 00000000000000..7e4c204e7a70bf\n> --- /dev/null\n> +++ b/include/fuse_service.h\n> @@ -0,0 +1,243 @@\n> +/*\n> + * FUSE: Filesystem in Userspace\n> + * Copyright (C) 2025-2026 Oracle.\n> + * Author: Darrick J. Wong <djwong@kernel.org>\n> + *\n> + * This program can be distributed under the terms of the GNU LGPLv2.\n> + * See the file LGPL2.txt.\n> + */\n> +#ifndef FUSE_SERVICE_H_\n> +#define FUSE_SERVICE_H_\n> +\n> +/** @file\n> + *\n> + * Low level API\n> + *\n> + * IMPORTANT: you should define FUSE_USE_VERSION before including this\n> + * header.  To use the newest API define it to 319 (recommended for any\n> + * new application).\n> + */\n> +\n> +#ifndef FUSE_USE_VERSION\n> +#error FUSE_USE_VERSION not defined\n> +#endif\n> +\n> +#include \"fuse_common.h\"\n> +\n> +#ifdef __cplusplus\n> +extern \"C\" {\n> +#endif\n> +\n> +#if FUSE_MAKE_VERSION(3, 19) <= FUSE_USE_VERSION\n> +\n> +struct fuse_service;\n> +\n> +/**\n> + * Accept a socket created by mount.service for information exchange.\n> + *\n> + * @param sfp pointer to pointer to a service context.  The pointer will always\n> + *            be initialized by this function; use fuse_service_accepted to\n> + *            find out if the fuse server is actually running as a service.\n> + * @return 0 on success, or negative errno on failure\n> + */\n> +int fuse_service_accept(struct fuse_service **sfp);\n> +\n> +/**\n> + * Has the fuse server accepted a service context?\n> + *\n> + * @param sf service context\n> + * @return true if it has, false if not\n> + */\n> +static inline bool fuse_service_accepted(struct fuse_service *sf)\n> +{\n> +\treturn sf != NULL;\n> +}\n> +\n> +/**\n> + * Will the mount service helper accept the allow_other option?\n> + *\n> + * @param sf service context\n> + * @return true if it has, false if not\n> + */\n> +bool fuse_service_can_allow_other(struct fuse_service *sf);\n> +\n> +/**\n> + * Release all resources associated with the service context.\n> + *\n> + * @param sfp service context\n> + */\n> +void fuse_service_release(struct fuse_service *sf);\n> +\n> +/**\n> + * Destroy a service context and release all resources\n> + *\n> + * @param sfp pointer to pointer to a service context\n> + */\n> +void fuse_service_destroy(struct fuse_service **sfp);\n> +\n> +/**\n> + * Append the command line arguments from the mount service helper to an\n> + * existing fuse_args structure.  The fuse_args should have been initialized\n> + * with the argc and argv passed to main().\n> + *\n> + * @param sfp service context\n> + * @param args arguments to modify (input+output)\n> + * @return 0 on success, or negative errno on failure\n> + */\n> +int fuse_service_append_args(struct fuse_service *sf, struct fuse_args *args);\n> +\n> +/**\n> + * Generate the effective fuse server command line from the args structure.\n> + * The args structure should be the outcome from fuse_service_append_args.\n> + * The resulting string is suitable for setproctitle and must be freed by the\n> + * callre.\n> + *\n> + * @param argc argument count passed to main()\n> + * @param argv argument vector passed to main()\n> + * @param args fuse args structure\n> + * @return effective command line string, or NULL\n> + */\n> +char *fuse_service_cmdline(int argc, char *argv[], struct fuse_args *args);\n> +\n> +struct fuse_cmdline_opts;\n> +\n> +/**\n> + * Utility function to parse common options for simple file systems\n> + * using the low-level API. A help text that describes the available\n> + * options can be printed with `fuse_cmdline_help`. A single\n> + * non-option argument is treated as the mountpoint. Multiple\n> + * non-option arguments will result in an error.\n> + *\n> + * If neither -o subtype= or -o fsname= options are given, a new\n> + * subtype option will be added and set to the basename of the program\n> + * (the fsname will remain unset, and then defaults to \"fuse\").\n> + *\n> + * Known options will be removed from *args*, unknown options will\n> + * remain. The mountpoint will not be checked here; that is the job of\n> + * mount.service.\n> + *\n> + * @param args argument vector (input+output)\n> + * @param opts output argument for parsed options\n> + * @return 0 on success, -1 on failure\n> + */\n> +int fuse_service_parse_cmdline_opts(struct fuse_args *args,\n> +\t\t\t\t    struct fuse_cmdline_opts *opts);\n> +\n> +/**\n> + * Don't complain if this file cannot be opened.\n> + */\n> +#define FUSE_SERVICE_REQUEST_FILE_QUIET\t\t(1U << 0)\n> +\n> +/**\n> + * Ask the mount.service helper to open a file on behalf of the fuse server.\n> + *\n> + * @param sf service context\n> + * @param path the path to file\n> + * @param open_flags O_ flags\n> + * @param create_mode mode with which to create the file\n> + * @param request_flags set of FUSE_SERVICE_REQUEST_* flags\n> + * @return 0 on success, or negative errno on failure\n> + */\n> +int fuse_service_request_file(struct fuse_service *sf, const char *path,\n> +\t\t\t      int open_flags, mode_t create_mode,\n> +\t\t\t      unsigned int request_flags);\n> +\n> +/**\n> + * Ask the mount.service helper to open a block device on behalf of the fuse\n> + * server.\n> + *\n> + * @param sf service context\n> + * @param path the path to file\n> + * @param open_flags O_ flags\n> + * @param create_mode mode with which to create the file\n> + * @param request_flags set of FUSE_SERVICE_REQUEST_* flags\n> + * @param block_size set the block device block size to this value\n> + * @return 0 on success, or negative errno on failure\n> + */\n> +int fuse_service_request_blockdev(struct fuse_service *sf, const char *path,\n> +\t\t\t\t  int open_flags, mode_t create_mode,\n> +\t\t\t\t  unsigned int request_flags,\n> +\t\t\t\t  unsigned int block_size);\n> +\n> +/**\n> + * Receive a file previously requested.\n> + *\n> + * @param sf service context\n> + * @param path to file\n> + * @fdp pointer to file descriptor, which will be set a non-negative file\n> + *      descriptor value on success, or negative errno on failure\n> + * @return 0 on success, or negative errno on socket communication failure\n> + */\n> +int fuse_service_receive_file(struct fuse_service *sf,\n> +\t\t\t      const char *path, int *fdp);\n> +\n> +/**\n> + * Prevent the mount.service server from sending us any more open files.\n> + *\n> + * @param sf service context\n> + * @return 0 on success, or negative errno on failure\n> + */\n> +int fuse_service_finish_file_requests(struct fuse_service *sf);\n> +\n> +/**\n> + * Require that the filesystem mount point have the expected file format\n> + * (S_IFDIR/S_IFREG).  Can be overridden when calling\n> + * fuse_service_session_mount.\n> + *\n> + * @param sf service context\n> + * @param expected_fmt expected mode (S_IFDIR/S_IFREG) for mount point, or 0\n> + *                     to skip checks\n> + */\n> +void fuse_service_expect_mount_format(struct fuse_service *sf,\n> +\t\t\t\t      mode_t expected_fmt);\n> +\n> +/**\n> + * Bind a FUSE file system to the fuse session inside a fuse service process,\n> + * then ask the mount.service helper to mount the filesystem for us.  The fuse\n> + * client will begin sending requests to the fuse server immediately after\n> + * this.  Do not call fuse_daemonize() when running as a fuse service.\n> + *\n> + * @param sf service context\n> + * @param se fuse session\n> + * @param expected_fmt expected mode (S_IFDIR/S_IFREG) for mount point, or 0\n> + *                     to skip checks\n> + * @param opts command line options\n> + * @return 0 on success, or negative errno on failure\n> + */\n> +int fuse_service_session_mount(struct fuse_service *sf, struct fuse_session *se,\n> +\t\t\t       mode_t expected_fmt,\n> +\t\t\t       struct fuse_cmdline_opts *opts);\n> +\n> +/**\n> + * Ask the mount helper to unmount th e filesystem.\n> + *\n> + * @param sf service context\n> + * @return 0 on success, or negative errno on failure\n> + */\n> +int fuse_service_session_unmount(struct fuse_service *sf);\n> +\n> +/**\n> + * Bid farewell to the mount.service helper.  It is still necessary to call\n> + * fuse_service_destroy after this.\n> + *\n> + * @param sf service context\n> + * @param exitcode fuse server process exit status\n> + * @return 0 on success, or negative errno on failure\n> + */\n> +int fuse_service_send_goodbye(struct fuse_service *sf, int exitcode);\n> +\n> +/**\n> + * Exit routine for a fuse server running as a systemd service.\n> + *\n> + * @param ret 0 for success, nonzero for service failure.\n> + * @return a value to be passed to exit() or returned from main\n> + */\n> +int fuse_service_exit(int ret);\n> +\n> +#endif /* FUSE_USE_VERSION >= FUSE_MAKE_VERSION(3, 19) */\n> +\n> +#ifdef __cplusplus\n> +}\n> +#endif\n> +\n> +#endif /* FUSE_SERVICE_H_ */\n> diff --git a/include/fuse_service_priv.h b/include/fuse_service_priv.h\n> new file mode 100644\n> index 00000000000000..a3773d90c7db7e\n> --- /dev/null\n> +++ b/include/fuse_service_priv.h\n> @@ -0,0 +1,160 @@\n> +/*\n> + * FUSE: Filesystem in Userspace\n> + * Copyright (C) 2025-2026 Oracle.\n> + * Author: Darrick J. Wong <djwong@kernel.org>\n> + *\n> + * This program can be distributed under the terms of the GNU LGPLv2.\n> + * See the file LGPL2.txt.\n> + */\n> +#ifndef FUSE_SERVICE_PRIV_H_\n> +#define FUSE_SERVICE_PRIV_H_\n> +\n> +/* All numeric fields are network order (big-endian) when going across the socket */\n> +\n> +struct fuse_service_memfd_arg {\n> +\tuint32_t pos;\n> +\tuint32_t len;\n> +};\n> +\n> +struct fuse_service_memfd_argv {\n> +\tuint32_t magic;\n> +\tuint32_t argc;\n> +};\n> +\n> +#define FUSE_SERVICE_MAX_CMD_SIZE\t(65536)\n> +\n> +#define FUSE_SERVICE_ARGS_MAGIC\t\t0x41524753\t/* ARGS */\n> +\n> +/* mount.service sends a hello to the server and it replies */\n> +#define FUSE_SERVICE_HELLO_CMD\t\t0x53414654\t/* SAFT */\n> +#define FUSE_SERVICE_HELLO_REPLY\t0x4c415354\t/* LAST */\n> +\n> +/* fuse servers send commands to mount.service */\n> +#define FUSE_SERVICE_OPEN_CMD\t\t0x4f50454e\t/* OPEN */\n> +#define FUSE_SERVICE_OPEN_BDEV_CMD\t0x42444556\t/* BDEV */\n> +#define FUSE_SERVICE_FSOPEN_CMD\t\t0x54595045\t/* TYPE */\n> +#define FUSE_SERVICE_SOURCE_CMD\t\t0x4e414d45\t/* NAME */\n> +#define FUSE_SERVICE_MNTOPTS_CMD\t0x4f505453\t/* OPTS */\n> +#define FUSE_SERVICE_MNTPT_CMD\t\t0x4d4e5450\t/* MNTP */\n> +#define FUSE_SERVICE_MOUNT_CMD\t\t0x444f4954\t/* DOIT */\n> +#define FUSE_SERVICE_UNMOUNT_CMD\t0x554d4e54\t/* UMNT */\n> +#define FUSE_SERVICE_BYE_CMD\t\t0x42594545\t/* BYEE */\n> +\n> +/* mount.service sends replies to the fuse server */\n> +#define FUSE_SERVICE_OPEN_REPLY\t\t0x46494c45\t/* FILE */\n> +#define FUSE_SERVICE_SIMPLE_REPLY\t0x5245504c\t/* REPL */\n> +\n> +struct fuse_service_packet {\n> +\tuint32_t magic;\t\t\t/* FUSE_SERVICE_*_{CMD,REPLY} */\n> +};\n> +\n> +#define FUSE_SERVICE_PROTO\t(1)\n> +#define FUSE_SERVICE_MIN_PROTO\t(1)\n> +#define FUSE_SERVICE_MAX_PROTO\t(1)\n> +\n> +#define FUSE_SERVICE_FLAG_ALLOW_OTHER\t(1U << 0)\n> +\n> +#define FUSE_SERVICE_FLAGS\t\t(FUSE_SERVICE_FLAG_ALLOW_OTHER)\n> +\n> +struct fuse_service_hello {\n> +\tstruct fuse_service_packet p;\n> +\tuint16_t min_version;\n> +\tuint16_t max_version;\n> +\tuint32_t flags;\n> +};\n> +\n> +static inline bool check_null_endbyte(const void *p, size_t psz)\n> +{\n> +\treturn *((const char *)p + psz - 1) == 0;\n> +}\n> +\n> +struct fuse_service_hello_reply {\n> +\tstruct fuse_service_packet p;\n> +\tuint16_t version;\n> +\tuint16_t padding;\n> +};\n> +\n> +struct fuse_service_simple_reply {\n> +\tstruct fuse_service_packet p;\n> +\tuint32_t error;\t\t\t/* positive errno */\n> +};\n> +\n> +struct fuse_service_requested_file {\n> +\tstruct fuse_service_packet p;\n> +\tuint32_t error;\t\t\t/* positive errno */\n> +\tchar path[];\n> +};\n> +\n> +static inline size_t sizeof_fuse_service_requested_file(size_t pathlen)\n> +{\n> +\treturn sizeof(struct fuse_service_requested_file) + pathlen + 1;\n> +}\n> +\n> +#define FUSE_SERVICE_FSOPEN_FUSEBLK\t(1U << 0)\n> +#define FUSE_SERVICE_FSOPEN_FLAGS\t(FUSE_SERVICE_FSOPEN_FUSEBLK)\n> +\n> +struct fuse_service_fsopen_command {\n> +\tstruct fuse_service_packet p;\n> +\tuint32_t fsopen_flags;\n> +};\n> +\n> +#define FUSE_SERVICE_OPEN_QUIET\t\t(1U << 0)\n> +#define FUSE_SERVICE_OPEN_FLAGS\t\t(FUSE_SERVICE_OPEN_QUIET)\n> +\n> +struct fuse_service_open_command {\n> +\tstruct fuse_service_packet p;\n> +\tuint32_t open_flags;\n> +\tuint32_t create_mode;\n> +\tuint32_t request_flags;\n> +\tuint32_t block_size;\n> +\tchar path[];\n> +};\n> +\n> +static inline size_t sizeof_fuse_service_open_command(size_t pathlen)\n> +{\n> +\treturn sizeof(struct fuse_service_open_command) + pathlen + 1;\n> +}\n> +\n> +struct fuse_service_string_command {\n> +\tstruct fuse_service_packet p;\n> +\tchar value[];\n> +};\n> +\n> +static inline size_t sizeof_fuse_service_string_command(size_t len)\n> +{\n> +\treturn sizeof(struct fuse_service_string_command) + len + 1;\n> +}\n> +\n> +struct fuse_service_mountpoint_command {\n> +\tstruct fuse_service_packet p;\n> +\tuint16_t expected_fmt;\n> +\tuint16_t padding;\n> +\tchar value[];\n> +};\n> +\n> +static inline size_t sizeof_fuse_service_mountpoint_command(size_t len)\n> +{\n> +\treturn sizeof(struct fuse_service_mountpoint_command) + len + 1;\n> +}\n> +\n> +struct fuse_service_bye_command {\n> +\tstruct fuse_service_packet p;\n> +\tuint32_t exitcode;\n> +};\n> +\n> +struct fuse_service_mount_command {\n> +\tstruct fuse_service_packet p;\n> +\tuint32_t ms_flags;\n> +};\n> +\n> +struct fuse_service_unmount_command {\n> +\tstruct fuse_service_packet p;\n> +};\n> +\n> +int fuse_parse_cmdline_service(struct fuse_args *args,\n> +\t\t\t\t struct fuse_cmdline_opts *opts);\n> +\n> +#define FUSE_SERVICE_ARGV\t\"argv\"\n> +#define FUSE_SERVICE_FUSEDEV\t\"fusedev\"\n> +\n> +#endif /* FUSE_SERVICE_PRIV_H_ */\n> diff --git a/lib/mount_common_i.h b/lib/mount_common_i.h\n> index 6bcb055ff1c23f..631dff3e6f8aaf 100644\n> --- a/lib/mount_common_i.h\n> +++ b/lib/mount_common_i.h\n> @@ -14,5 +14,8 @@ struct mount_opts;\n>  \n>  char *fuse_mnt_build_source(const struct mount_opts *mo);\n>  char *fuse_mnt_build_type(const struct mount_opts *mo);\n> +char *fuse_mnt_kernel_opts(const struct mount_opts *mo);\n> +unsigned int fuse_mnt_flags(const struct mount_opts *mo);\n> +\n>  \n>  #endif /* FUSE_MOUNT_COMMON_I_H_ */\n> diff --git a/util/mount_service.h b/util/mount_service.h\n> new file mode 100644\n> index 00000000000000..a0b952a15dacf3\n> --- /dev/null\n> +++ b/util/mount_service.h\n> @@ -0,0 +1,40 @@\n> +/*\n> + * FUSE: Filesystem in Userspace\n> + * Copyright (C) 2025-2026 Oracle.\n> + * Author: Darrick J. Wong <djwong@kernel.org>\n> + *\n> + * This program can be distributed under the terms of the GNU GPLv2.\n> + * See the file GPL2.txt.\n> + */\n> +#ifndef MOUNT_SERVICE_H_\n> +#define MOUNT_SERVICE_H_\n> +\n> +/**\n> + * Magic value that means that we couldn't connect to the mount service,\n> + * so the caller should try to fall back to traditional means.\n> + */\n> +#define MOUNT_SERVICE_FALLBACK_NEEDED\t(2)\n> +\n> +/**\n> + * Connect to a fuse service socket and try to mount the filesystem as\n> + * specified with the CLI arguments.\n> + *\n> + * @argc argument count\n> + * @argv vector of argument strings\n> + * @return EXIT_SUCCESS for success, EXIT_FAILURE if mount fails, or\n> + *         MOUNT_SERVICE_FALLBACK_NEEDED if no service is available.\n> + */\n> +int mount_service_main(int argc, char *argv[]);\n> +\n> +/**\n> + * Return the fuse filesystem subtype from a full fuse filesystem type\n> + * specification.  IOWs, fuse.Y -> Y; fuseblk.Z -> Z; or A -> A.  The returned\n> + * pointer is within the caller's string.  The subtype must not contain a path\n> + * separator.\n> + *\n> + * @param fstype full fuse filesystem type\n> + * @return fuse subtype\n> + */\n> +const char *mount_service_subtype(const char *fstype);\n> +\n> +#endif /* MOUNT_SERVICE_H_ */\n> diff --git a/.github/workflows/install-ubuntu-dependencies.sh b/.github/workflows/install-ubuntu-dependencies.sh\n> index 0eb7e610729b7c..9f6e69701438f3 100755\n> --- a/.github/workflows/install-ubuntu-dependencies.sh\n> +++ b/.github/workflows/install-ubuntu-dependencies.sh\n> @@ -15,6 +15,8 @@ PACKAGES_CORE=(\n>      pkg-config\n>      python3\n>      python3-pip\n> +    libsystemd-dev\n> +    systemd-dev\n>  )\n>  \n>  PACKAGES_FULL=(\n> @@ -31,6 +33,8 @@ PACKAGES_FULL=(\n>      libudev-dev:i386\n>      pkg-config:i386\n>      python3-pytest\n> +    libsystemd-dev\n> +    systemd-dev\n>  )\n>  \n>  PACKAGES_CODECHECKER=(\n> diff --git a/doc/fuservicemount3.8 b/doc/fuservicemount3.8\n> new file mode 100644\n> index 00000000000000..e45d6a89c8b81a\n> --- /dev/null\n> +++ b/doc/fuservicemount3.8\n> @@ -0,0 +1,24 @@\n> +.TH fuservicemount3 \"8\"\n> +.SH NAME\n> +fuservicemount3 \\- mount a FUSE filesystem that runs as a system socket service\n> +.SH SYNOPSIS\n> +.B fuservicemount3\n> +.B source\n> +.B mountpoint\n> +.BI -t \" fstype\"\n> +[\n> +.I options\n> +]\n> +.SH DESCRIPTION\n> +Mount a filesystem using a FUSE server that runs as a socket service.\n> +These servers can be contained using the platform's service management\n> +framework.\n> +.SH \"AUTHORS\"\n> +.LP\n> +The author of the fuse socket service code is Darrick J. Wong <djwong@kernel.org>.\n> +Debian GNU/Linux distribution.\n> +.SH SEE ALSO\n> +.BR fusermount3 (1)\n> +.BR fusermount (1)\n> +.BR mount (8)\n> +.BR fuse (4)\n> diff --git a/doc/meson.build b/doc/meson.build\n> index db3e0b26f71975..c105cf3471fdf4 100644\n> --- a/doc/meson.build\n> +++ b/doc/meson.build\n> @@ -2,3 +2,6 @@ if not platform.endswith('bsd') and platform != 'dragonfly'\n>    install_man('fusermount3.1', 'mount.fuse3.8')\n>  endif\n>  \n> +if private_cfg.get('HAVE_SERVICEMOUNT', false)\n> +  install_man('fuservicemount3.8')\n> +endif\n> diff --git a/include/meson.build b/include/meson.build\n> index bf671977a5a6a9..da51180f87eea2 100644\n> --- a/include/meson.build\n> +++ b/include/meson.build\n> @@ -1,4 +1,8 @@\n>  libfuse_headers = [ 'fuse.h', 'fuse_common.h', 'fuse_lowlevel.h',\n>  \t            'fuse_opt.h', 'cuse_lowlevel.h', 'fuse_log.h' ]\n>  \n> +if private_cfg.get('HAVE_SERVICEMOUNT', false)\n> +  libfuse_headers += [ 'fuse_service.h' ]\n> +endif\n> +\n>  install_headers(libfuse_headers, subdir: 'fuse3')\n> diff --git a/lib/fuse_service.c b/lib/fuse_service.c\n> new file mode 100644\n> index 00000000000000..ef512c76120a0f\n> --- /dev/null\n> +++ b/lib/fuse_service.c\n> @@ -0,0 +1,1233 @@\n> +/*\n> + * FUSE: Filesystem in Userspace\n> + * Copyright (C) 2025-2026 Oracle.\n> + * Author: Darrick J. Wong <djwong@kernel.org>\n> + *\n> + * Library functions to support fuse servers that can be run as \"safe\" systemd\n> + * containers.\n> + *\n> + * This program can be distributed under the terms of the GNU LGPLv2.\n> + * See the file LGPL2.txt\n> + */\n> +\n> +#define _GNU_SOURCE\n> +#include <stdint.h>\n> +#include <stdlib.h>\n> +#include <string.h>\n> +#include <stdio.h>\n> +#include <errno.h>\n> +#include <sys/socket.h>\n> +#include <sys/un.h>\n> +#include <unistd.h>\n> +#include <sys/stat.h>\n> +#include <fcntl.h>\n> +#include <systemd/sd-daemon.h>\n> +#include <arpa/inet.h>\n> +#include <limits.h>\n> +\n> +#include \"fuse_config.h\"\n> +#include \"fuse_i.h\"\n> +#include \"fuse_service_priv.h\"\n> +#include \"fuse_service.h\"\n> +#include \"mount_common_i.h\"\n> +\n> +struct fuse_service {\n> +\t/* expected file format of the mount point */\n> +\tmode_t expected_fmt;\n> +\n> +\t/* socket fd */\n> +\tint sockfd;\n> +\n> +\t/* /dev/fuse device */\n> +\tint fusedevfd;\n> +\n> +\t/* memfd for cli arguments */\n> +\tint argvfd;\n> +\n> +\t/* do we own fusedevfd? */\n> +\tbool owns_fusedevfd;\n> +\n> +\t/* can we use allow_other? */\n> +\tbool allow_other;\n> +};\n> +\n> +static int __recv_fd(struct fuse_service *sf,\n> +\t\t     struct fuse_service_requested_file *buf,\n> +\t\t     ssize_t bufsize, int *fdp)\n> +{\n> +\tstruct iovec iov = {\n> +\t\t.iov_base = buf,\n> +\t\t.iov_len = bufsize,\n> +\t};\n> +\tunion {\n> +\t\tstruct cmsghdr cmsghdr;\n> +\t\tchar control[CMSG_SPACE(sizeof(int))];\n> +\t} cmsgu = { };\n> +\tstruct msghdr msg = {\n> +\t\t.msg_iov = &iov,\n> +\t\t.msg_iovlen = 1,\n> +\t\t.msg_control = cmsgu.control,\n> +\n> +\t\t/*\n> +\t\t * Do not include padding at the end of the control buffer,\n> +\t\t * because we don't want to receive fds that we weren't\n> +\t\t * expecting.\n> +\t\t */\n> +\t\t.msg_controllen = CMSG_LEN(sizeof(int)),\n> +\t};\n> +\tstruct cmsghdr *cmsg;\n> +\tssize_t size;\n> +\n> +\t/*\n> +\t * A kernel LSM could decide to deny the fd transfer by writing a\n> +\t * negative number (== invalid fd) into the cmsg buffer instead of\n\nThe kernel doesn't actually do this, so we only need to initialize the\nbuffer to all 1s to protect against the kernel not writing to the cmsg\nbuffer, which it shouldn't do without also setting MSG_CTRUNC or\nreturning an error, but who knows.\n\n> +\t * installing the fd.  Set the initial fd value to -1 to signal an\n> +\t * invalid fd in case the kernel doesn't even set the cmsg buffer.\n> +\t * It shouldn't do that, but we absolutely don't want a zero here.\n> +\t */\n> +\tmemset(cmsgu.control, -1, sizeof(cmsgu.control));\n> +\n> +\tsize = recvmsg(sf->sockfd, &msg, MSG_TRUNC | MSG_CMSG_CLOEXEC);\n> +\tif (size < 0) {\n> +\t\tint error = errno;\n> +\n> +\t\tfuse_log(FUSE_LOG_ERR, \"fuse: service file reply: %s\\n\",\n> +\t\t\t strerror(error));\n> +\t\treturn -error;\n> +\t}\n> +\tif (size > bufsize ||\n> +\t    size < offsetof(struct fuse_service_requested_file, path)) {\n> +\t\tfuse_log(FUSE_LOG_ERR, \"fuse: wrong service file reply size %zd, expected %zd\\n\",\n> +\t\t\t size, bufsize);\n> +\t\treturn -EBADMSG;\n> +\t}\n> +\n> +\tif (msg.msg_flags & MSG_CTRUNC) {\n> +\t\t/* SMACK does this */\n> +\t\tfuse_log(FUSE_LOG_ERR,\n> +\"fuse: service file reply control data truncated; did an LSM deny SCM_RIGHTS?\\n\");\n> +\t\treturn -EBADMSG;\n> +\t}\n> +\n> +\tcmsg = CMSG_FIRSTHDR(&msg);\n> +\tif (!cmsg) {\n> +\t\t/* no control message means mount.service sent us an error */\n> +\t\treturn 0;\n> +\t}\n> +\tif (cmsg->cmsg_len != CMSG_LEN(sizeof(int))) {\n> +\t\tfuse_log(FUSE_LOG_ERR,\n> +\t\t\t \"fuse: wrong service file reply control data size %zd, expected %zd\\n\",\n> +\t\t\t cmsg->cmsg_len, CMSG_LEN(sizeof(int)));\n> +\t\treturn -EBADMSG;\n> +\t}\n> +\tif (cmsg->cmsg_level != SOL_SOCKET || cmsg->cmsg_type != SCM_RIGHTS) {\n> +\t\tfuse_log(FUSE_LOG_ERR,\n> +\"fuse: wrong service file reply control data level %d type %d, expected %d and %d\\n\",\n> +\t\t\t cmsg->cmsg_level, cmsg->cmsg_type, SOL_SOCKET,\n> +\t\t\t SCM_RIGHTS);\n> +\t\treturn -EBADMSG;\n> +\t}\n> +\n> +\tmemcpy(fdp, (int *)CMSG_DATA(cmsg), sizeof(int));\n> +\treturn 0;\n> +}\n> +\n> +static ssize_t __send_packet(struct fuse_service *sf, void *ptr, size_t len)\n> +{\n> +\tstruct iovec iov = {\n> +\t\t.iov_base = ptr,\n> +\t\t.iov_len = len,\n> +\t};\n> +\tstruct msghdr msg = {\n> +\t\t.msg_iov = &iov,\n> +\t\t.msg_iovlen = 1,\n> +\t};\n> +\n> +\treturn sendmsg(sf->sockfd, &msg, MSG_EOR | MSG_NOSIGNAL);\n> +}\n> +\n> +static ssize_t __recv_packet(struct fuse_service *sf, void *ptr, size_t len)\n> +{\n> +\tstruct iovec iov = {\n> +\t\t.iov_base = ptr,\n> +\t\t.iov_len = len,\n> +\t};\n> +\tstruct msghdr msg = {\n> +\t\t.msg_iov = &iov,\n> +\t\t.msg_iovlen = 1,\n> +\t};\n> +\n> +\treturn recvmsg(sf->sockfd, &msg, MSG_TRUNC);\n> +}\n> +\n> +int fuse_service_receive_file(struct fuse_service *sf, const char *path,\n> +\t\t\t      int *fdp)\n> +{\n> +\tstruct fuse_service_requested_file *req;\n> +\tconst size_t req_sz = sizeof_fuse_service_requested_file(strlen(path));\n> +\tint fd = -ENOENT;\n> +\tint ret;\n> +\n> +\t*fdp = -ENOENT;\n> +\n> +\treq = calloc(1, req_sz + 1);\n> +\tif (!req) {\n> +\t\tint error = errno;\n> +\n> +\t\tfuse_log(FUSE_LOG_ERR, \"fuse: alloc service file reply: %s\\n\",\n> +\t\t\t strerror(error));\n> +\t\treturn -error;\n> +\t}\n> +\n> +\tret = __recv_fd(sf, req, req_sz, &fd);\n> +\tif (ret)\n> +\t\tgoto out_req;\n> +\n> +\tif (fd < 0) {\n> +\t\t/* The kernel might have given us an errno instead of an fd */\n> +\t\tfuse_log(FUSE_LOG_ERR, \"fuse: service fd transfer failed: %s\\n\",\n> +\t\t\t strerror(-fd));\n> +\t\tret = fd;\n> +\t\tgoto out_req;\n> +\t}\n\nCodex points out that this nearly redundant with the \"fd == -ENOENT\"\ncheck below and could be combined.  Worse, it actually breaks the two\nfailure reporting cases below, so...\n\n> +\n> +\tif (ntohl(req->p.magic) != FUSE_SERVICE_OPEN_REPLY) {\n> +\t\tfuse_log(FUSE_LOG_ERR, \"fuse: service file reply contains wrong magic!\\n\");\n> +\t\tret = -EBADMSG;\n> +\t\tgoto out_close;\n> +\t}\n> +\tif (strcmp(req->path, path)) {\n> +\t\tfuse_log(FUSE_LOG_ERR, \"fuse: `%s': not the requested service file, got `%s'\\n\",\n> +\t\t\t path, req->path);\n> +\t\tret = -EBADMSG;\n> +\t\tgoto out_close;\n> +\t}\n> +\n> +\tif (req->error) {\n> +\t\t*fdp = -ntohl(req->error);\n> +\t\tgoto out_close;\n> +\t}\n> +\n> +\tif (fd == -ENOENT)\n> +\t\tfuse_log(FUSE_LOG_ERR, \"fuse: did not receive `%s' but no error?\\n\",\n> +\t\t\t path);\n\n...I'll compress both clauses into:\n\n\tif (fd < 0) {\n\t\tfuse_log(FUSE_LOG_ERR,\n\t\t\t \"fuse: did not receive valid fd for `%s' but no error?\\n\",\n\t\t\t path);\n\t\tgoto out_req;\n\t}\n\n--D\n\n> +\n> +\t*fdp = fd;\n> +\tgoto out_req;\n> +\n> +out_close:\n> +\tclose(fd);\n> +out_req:\n> +\tfree(req);\n> +\treturn ret;\n> +}\n> +\n> +#define FUSE_SERVICE_REQUEST_FILE_FLAGS\t(FUSE_SERVICE_REQUEST_FILE_QUIET)\n> +\n> +static int fuse_service_request_path(struct fuse_service *sf, const char *path,\n> +\t\t\t\t     mode_t expected_fmt, int open_flags,\n> +\t\t\t\t     mode_t create_mode,\n> +\t\t\t\t     unsigned int request_flags,\n> +\t\t\t\t     unsigned int block_size)\n> +{\n> +\tstruct fuse_service_open_command *cmd;\n> +\tconst size_t cmdsz = sizeof_fuse_service_open_command(strlen(path));\n> +\tssize_t size;\n> +\tunsigned int rqflags = 0;\n> +\tint ret;\n> +\n> +\tif (request_flags & ~FUSE_SERVICE_REQUEST_FILE_FLAGS) {\n> +\t\tfuse_log(FUSE_LOG_ERR, \"fuse: invalid fuse service file request flags 0x%x\\n\",\n> +\t\t\t request_flags);\n> +\t\treturn -EINVAL;\n> +\t}\n> +\n> +\tif (request_flags & FUSE_SERVICE_REQUEST_FILE_QUIET)\n> +\t\trqflags |= FUSE_SERVICE_OPEN_QUIET;\n> +\n> +\tcmd = calloc(1, cmdsz);\n> +\tif (!cmd) {\n> +\t\tint error = errno;\n> +\n> +\t\tfuse_log(FUSE_LOG_ERR, \"fuse: alloc service file request: %s\\n\",\n> +\t\t\t strerror(error));\n> +\t\treturn -error;\n> +\t}\n> +\tif (S_ISBLK(expected_fmt)) {\n> +\t\tcmd->p.magic = htonl(FUSE_SERVICE_OPEN_BDEV_CMD);\n> +\t\tcmd->block_size = htonl(block_size);\n> +\t} else {\n> +\t\tcmd->p.magic = htonl(FUSE_SERVICE_OPEN_CMD);\n> +\t}\n> +\tcmd->open_flags = htonl(open_flags);\n> +\tcmd->create_mode = htonl(create_mode);\n> +\tcmd->request_flags = htonl(rqflags);\n> +\tstrcpy(cmd->path, path);\n> +\n> +\tsize = __send_packet(sf, cmd, cmdsz);\n> +\tif (size < 0) {\n> +\t\tint error = errno;\n> +\n> +\t\tfuse_log(FUSE_LOG_ERR, \"fuse: request service file: %s\\n\",\n> +\t\t\t strerror(error));\n> +\t\tret = -error;\n> +\t\tgoto out_free;\n> +\t}\n> +\n> +\tret = 0;\n> +out_free:\n> +\tfree(cmd);\n> +\treturn ret;\n> +}\n> +\n> +int fuse_service_request_file(struct fuse_service *sf, const char *path,\n> +\t\t\t      int open_flags, mode_t create_mode,\n> +\t\t\t      unsigned int request_flags)\n> +{\n> +\treturn fuse_service_request_path(sf, path, S_IFREG, open_flags,\n> +\t\t\t\t\t create_mode, request_flags, 0);\n> +}\n> +\n> +int fuse_service_request_blockdev(struct fuse_service *sf, const char *path,\n> +\t\t\t\t  int open_flags, mode_t create_mode,\n> +\t\t\t\t  unsigned int request_flags,\n> +\t\t\t\t  unsigned int block_size)\n> +{\n> +\treturn fuse_service_request_path(sf, path, S_IFBLK, open_flags,\n> +\t\t\t\t\t create_mode, request_flags,\n> +\t\t\t\t\t block_size);\n> +}\n> +\n> +int fuse_service_send_goodbye(struct fuse_service *sf, int exitcode)\n> +{\n> +\tstruct fuse_service_bye_command c = {\n> +\t\t.p.magic = htonl(FUSE_SERVICE_BYE_CMD),\n> +\t\t.exitcode = htonl(exitcode),\n> +\t};\n> +\tssize_t size;\n> +\n> +\t/* already gone? */\n> +\tif (sf->sockfd < 0)\n> +\t\treturn 0;\n> +\n> +\tsize = __send_packet(sf, &c, sizeof(c));\n> +\tif (size < 0) {\n> +\t\tint error = errno;\n> +\n> +\t\tfuse_log(FUSE_LOG_ERR, \"fuse: send service goodbye: %s\\n\",\n> +\t\t\t strerror(error));\n> +\t\treturn -error;\n> +\t}\n> +\n> +\tshutdown(sf->sockfd, SHUT_RDWR);\n> +\tclose(sf->sockfd);\n> +\tsf->sockfd = -1;\n> +\treturn 0;\n> +}\n> +\n> +static int count_listen_fds(void)\n> +{\n> +\tchar *listen_fds;\n> +\tchar *listen_pid;\n> +\tchar *p;\n> +\tlong l;\n> +\n> +\t/*\n> +\t * No environment variables means we're not running as a system socket\n> +\t * service, so we'll back out without logging anything.\n> +\t */\n> +\tlisten_fds = getenv(\"LISTEN_FDS\");\n> +\tlisten_pid = getenv(\"LISTEN_PID\");\n> +\tif (!listen_fds || !listen_pid)\n> +\t\treturn 0;\n> +\n> +\t/*\n> +\t * LISTEN_PID is the pid of the process to which systemd thinks it gave\n> +\t * the socket fd.  Hopefully that's us.\n> +\t */\n> +\terrno = 0;\n> +\tl = strtol(listen_pid, &p, 10);\n> +\tif (errno || *p != 0 || l != getpid())\n> +\t\treturn 0;\n> +\n> +\t/*\n> +\t * LISTEN_FDS is the number of sockets that were opened in this\n> +\t * process.\n> +\t */\n> +\terrno = 0;\n> +\tl = strtol(listen_fds, &p, 10);\n> +\tif (errno || *p != 0 || l > INT_MAX || l < 0)\n> +\t\treturn 0;\n> +\n> +\treturn l;\n> +}\n> +\n> +static int check_sendbuf_size(int sockfd)\n> +{\n> +\tconst size_t min_size = sizeof_fuse_service_open_command(PATH_MAX);\n> +\tint sendbuf_size = -1;\n> +\tsocklen_t optlen = sizeof(sendbuf_size);\n> +\tint ret;\n> +\n> +\t/*\n> +\t * If we can't query the maximum send buffer length, just keep going.\n> +\t * Most likely we won't be sending huge open commands, and if we do,\n> +\t * the sendmsg will fail there too.\n> +\t */\n> +\tret = getsockopt(sockfd, SOL_SOCKET, SO_SNDBUF, &sendbuf_size, &optlen);\n> +\tif (ret || sendbuf_size < 0)\n> +\t\treturn 0;\n> +\n> +\tif (sendbuf_size >= min_size)\n> +\t\treturn 0;\n> +\n> +\tfuse_log(FUSE_LOG_ERR, \"max socket send buffer is %d, need at least %zu.\\n\",\n> +\t\t sendbuf_size, min_size);\n> +\treturn -ENOBUFS;\n> +}\n> +\n> +static int find_socket_fd(int nr_fds)\n> +{\n> +\tstruct stat stbuf;\n> +\tstruct sockaddr_un urk;\n> +\tsocklen_t urklen = sizeof(urk);\n> +\tint ret;\n> +\n> +\tif (nr_fds != 1) {\n> +\t\tfuse_log(FUSE_LOG_ERR, \"fuse: can only handle 1 service socket, got %d.\\n\",\n> +\t\t\t nr_fds);\n> +\t\treturn -E2BIG;\n> +\t}\n> +\n> +\tret = fstat(SD_LISTEN_FDS_START, &stbuf);\n> +\tif (ret) {\n> +\t\tint error = errno;\n> +\n> +\t\tfuse_log(FUSE_LOG_ERR, \"fuse: service socket: %s\\n\",\n> +\t\t\t strerror(error));\n> +\t\treturn -error;\n> +\t}\n> +\n> +\tif (!S_ISSOCK(stbuf.st_mode)) {\n> +\t\tfuse_log(FUSE_LOG_ERR, \"fuse: expected service fd %d to be a socket\\n\",\n> +\t\t\t\tSD_LISTEN_FDS_START);\n> +\t\treturn -ENOTSOCK;\n> +\t}\n> +\n> +\tret = getsockname(SD_LISTEN_FDS_START, &urk, &urklen);\n> +\tif (ret < 0) {\n> +\t\tint error = errno;\n> +\n> +\t\tfuse_log(FUSE_LOG_ERR, \"fuse: service socket family: %s\\n\",\n> +\t\t\t strerror(error));\n> +\t\treturn -error;\n> +\t}\n> +\n> +\tif (ret > 0 || urk.sun_family != AF_UNIX) {\n> +\t\t/*\n> +\t\t * If getsockname wanted to return more data than fits in a\n> +\t\t * sockaddr_un, then it's obviously not an AF_UNIX socket.\n> +\t\t *\n> +\t\t * If it filled the buffer exactly but the family isn't AF_UNIX\n> +\t\t * then we also return false.\n> +\t\t */\n> +\t\tfuse_log(FUSE_LOG_ERR, \"fuse: service socket is not AF_UNIX\\n\");\n> +\t\treturn -EAFNOSUPPORT;\n> +\t}\n> +\n> +\tret = check_sendbuf_size(SD_LISTEN_FDS_START);\n> +\tif (ret)\n> +\t\treturn ret;\n> +\n> +\treturn SD_LISTEN_FDS_START;\n> +}\n> +\n> +static int negotiate_hello(struct fuse_service *sf)\n> +{\n> +\tstruct fuse_service_hello hello = { };\n> +\tstruct fuse_service_hello_reply reply = {\n> +\t\t.p.magic = htonl(FUSE_SERVICE_HELLO_REPLY),\n> +\t\t.version = htons(FUSE_SERVICE_PROTO),\n> +\t};\n> +\tuint32_t flags;\n> +\tssize_t size;\n> +\n> +\tsize = __recv_packet(sf, &hello, sizeof(hello));\n> +\tif (size < 0) {\n> +\t\tint error = errno;\n> +\n> +\t\tfuse_log(FUSE_LOG_ERR, \"fuse: receive service hello: %s\\n\",\n> +\t\t\t strerror(error));\n> +\t\treturn -error;\n> +\t}\n> +\tif (size != sizeof(hello)) {\n> +\t\tfuse_log(FUSE_LOG_ERR, \"fuse: wrong service hello size %zd, expected %zd\\n\",\n> +\t\t\t size, sizeof(hello));\n> +\t\treturn -EBADMSG;\n> +\t}\n> +\n> +\tif (ntohl(hello.p.magic) != FUSE_SERVICE_HELLO_CMD) {\n> +\t\tfuse_log(FUSE_LOG_ERR, \"fuse: service server did not send hello command\\n\");\n> +\t\treturn -EBADMSG;\n> +\t}\n> +\n> +\tif (ntohs(hello.min_version) < FUSE_SERVICE_MIN_PROTO) {\n> +\t\tfuse_log(FUSE_LOG_ERR, \"fuse: unsupported min service protocol version %u\\n\",\n> +\t\t\tntohs(hello.min_version));\n> +\t\treturn -EOPNOTSUPP;\n> +\t}\n> +\n> +\tif (ntohs(hello.max_version) > FUSE_SERVICE_MAX_PROTO) {\n> +\t\tfuse_log(FUSE_LOG_ERR, \"fuse: unsupported max service protocol version %u\\n\",\n> +\t\t\tntohs(hello.min_version));\n> +\t\treturn -EOPNOTSUPP;\n> +\t}\n> +\n> +\tflags = ntohl(hello.flags);\n> +\tif (flags & ~FUSE_SERVICE_FLAGS) {\n> +\t\tfprintf(stderr, \"fuse: invalid hello flags: 0x%x\\n\",\n> +\t\t\tflags & ~FUSE_SERVICE_FLAGS);\n> +\t\treturn -EINVAL;\n> +\t}\n> +\n> +\tif (flags & FUSE_SERVICE_FLAG_ALLOW_OTHER)\n> +\t\tsf->allow_other = true;\n> +\n> +\tsize = __send_packet(sf, &reply, sizeof(reply));\n> +\tif (size < 0) {\n> +\t\tint error = errno;\n> +\n> +\t\tfuse_log(FUSE_LOG_ERR, \"fuse: service hello reply: %s\\n\",\n> +\t\t\t strerror(error));\n> +\t\treturn -error;\n> +\t}\n> +\n> +\treturn 0;\n> +}\n> +\n> +int fuse_service_accept(struct fuse_service **sfp)\n> +{\n> +\tstruct fuse_service *sf;\n> +\tint nr_fds;\n> +\tint sockfd;\n> +\tint flags;\n> +\tint ret = 0;\n> +\n> +\t*sfp = NULL;\n> +\n> +\tnr_fds = count_listen_fds();\n> +\tif (nr_fds == 0)\n> +\t\treturn 0;\n> +\n> +\t/* Find the socket that connects us to mount.service */\n> +\tsockfd = find_socket_fd(nr_fds);\n> +\tif (sockfd < 0)\n> +\t\treturn sockfd;\n> +\n> +\tflags = fcntl(sockfd, F_GETFD);\n> +\tif (flags < 0) {\n> +\t\tint error = errno;\n> +\n> +\t\tfuse_log(FUSE_LOG_ERR, \"fuse: service socket getfd: %s\\n\",\n> +\t\t\t strerror(error));\n> +\t\treturn -error;\n> +\t}\n> +\n> +\tif (!(flags & FD_CLOEXEC)) {\n> +\t\tret = fcntl(sockfd, F_SETFD, flags | FD_CLOEXEC);\n> +\t\tif (ret) {\n> +\t\t\tint error = errno;\n> +\n> +\t\t\tfuse_log(FUSE_LOG_ERR, \"fuse: service socket set cloexec: %s\\n\",\n> +\t\t\t\t strerror(error));\n> +\t\t\treturn -error;\n> +\t\t}\n> +\t}\n> +\n> +\tsf = calloc(1, sizeof(struct fuse_service));\n> +\tif (!sf) {\n> +\t\tint error = errno;\n> +\n> +\t\tfuse_log(FUSE_LOG_ERR, \"fuse: service alloc: %s\\n\",\n> +\t\t\t strerror(error));\n> +\t\treturn -error;\n> +\t}\n> +\tsf->sockfd = sockfd;\n> +\n> +\tret = negotiate_hello(sf);\n> +\tif (ret)\n> +\t\tgoto out_sf;\n> +\n> +\t/* Receive the two critical sockets */\n> +\tret = fuse_service_receive_file(sf, FUSE_SERVICE_ARGV, &sf->argvfd);\n> +\tif (ret < 0)\n> +\t\tgoto out_sockfd;\n> +\tif (sf->argvfd < 0) {\n> +\t\tfuse_log(FUSE_LOG_ERR, \"fuse: service mount options file: %s\\n\",\n> +\t\t\t strerror(-sf->argvfd));\n> +\t\tret = sf->argvfd;\n> +\t\tgoto out_sockfd;\n> +\t}\n> +\n> +\tret = fuse_service_receive_file(sf, FUSE_SERVICE_FUSEDEV,\n> +\t\t\t\t\t&sf->fusedevfd);\n> +\tif (ret < 0)\n> +\t\tgoto out_argvfd;\n> +\tif (sf->fusedevfd < 0) {\n> +\t\tfuse_log(FUSE_LOG_ERR, \"fuse: service fuse device: %s\\n\",\n> +\t\t\t strerror(-sf->fusedevfd));\n> +\t\tret = sf->fusedevfd;\n> +\t\tgoto out_argvfd;\n> +\t}\n> +\n> +\tsf->owns_fusedevfd = true;\n> +\t*sfp = sf;\n> +\treturn 0;\n> +\n> +out_argvfd:\n> +\tclose(sf->argvfd);\n> +out_sockfd:\n> +\tshutdown(sf->sockfd, SHUT_RDWR);\n> +\tclose(sf->sockfd);\n> +out_sf:\n> +\tfree(sf);\n> +\treturn ret;\n> +}\n> +\n> +bool fuse_service_can_allow_other(struct fuse_service *sf)\n> +{\n> +\treturn sf->allow_other;\n> +}\n> +\n> +int fuse_service_append_args(struct fuse_service *sf,\n> +\t\t\t     struct fuse_args *existing_args)\n> +{\n> +\tstruct fuse_service_memfd_argv memfd_args = { };\n> +\tstruct fuse_args new_args = {\n> +\t\t.allocated = 1,\n> +\t};\n> +\tchar *str = NULL;\n> +\toff_t memfd_pos = 0;\n> +\tssize_t received;\n> +\tunsigned int i;\n> +\tint ret;\n> +\n> +\t/* Figure out how many arguments we're getting from the mount helper. */\n> +\treceived = pread(sf->argvfd, &memfd_args, sizeof(memfd_args), 0);\n> +\tif (received < 0) {\n> +\t\tint error = errno;\n> +\n> +\t\tfuse_log(FUSE_LOG_ERR, \"fuse: service args file: %s\\n\",\n> +\t\t\t strerror(error));\n> +\t\treturn -error;\n> +\t}\n> +\tif (received < sizeof(memfd_args)) {\n> +\t\tfuse_log(FUSE_LOG_ERR, \"fuse: service args file length unreadable\\n\");\n> +\t\treturn -EBADMSG;\n> +\t}\n> +\tif (ntohl(memfd_args.magic) != FUSE_SERVICE_ARGS_MAGIC) {\n> +\t\tfuse_log(FUSE_LOG_ERR, \"fuse: service args file corrupt\\n\");\n> +\t\treturn -EBADMSG;\n> +\t}\n> +\tmemfd_args.magic = htonl(memfd_args.magic);\n> +\tmemfd_args.argc = htonl(memfd_args.argc);\n> +\tmemfd_pos += sizeof(memfd_args);\n> +\n> +\t/* Allocate a new array of argv string pointers */\n> +\tnew_args.argv = calloc(memfd_args.argc + existing_args->argc,\n> +\t\t\t       sizeof(char *));\n> +\tif (!new_args.argv) {\n> +\t\tint error = errno;\n> +\n> +\t\tfuse_log(FUSE_LOG_ERR, \"fuse: service new args: %s\\n\",\n> +\t\t\t strerror(error));\n> +\t\treturn -error;\n> +\t}\n> +\n> +\t/*\n> +\t * Copy the fuse server's CLI arguments.  We'll leave new_args.argv[0]\n> +\t * unset for now, because we'll set it in the next step with the fstype\n> +\t * that the mount helper sent us.\n> +\t */\n> +\tnew_args.argc++;\n> +\tfor (i = 1; i < existing_args->argc; i++) {\n> +\t\tif (existing_args->allocated) {\n> +\t\t\tnew_args.argv[new_args.argc] = existing_args->argv[i];\n> +\t\t\texisting_args->argv[i] = NULL;\n> +\t\t} else {\n> +\t\t\tchar *dup = strdup(existing_args->argv[i]);\n> +\n> +\t\t\tif (!dup) {\n> +\t\t\t\tint error = errno;\n> +\n> +\t\t\t\tfuse_log(FUSE_LOG_ERR,\n> +\t\t\t\t\t \"fuse: service duplicate existing args: %s\\n\",\n> +\t\t\t\t\t strerror(error));\n> +\t\t\t\tret = -error;\n> +\t\t\t\tgoto out_new_args;\n> +\t\t\t}\n> +\n> +\t\t\tnew_args.argv[new_args.argc] = dup;\n> +\t\t}\n> +\n> +\t\tnew_args.argc++;\n> +\t}\n> +\n> +\t/* Copy the rest of the arguments from the helper */\n> +\tfor (i = 0; i < memfd_args.argc; i++) {\n> +\t\tstruct fuse_service_memfd_arg memfd_arg = { };\n> +\n> +\t\t/* Read argv iovec */\n> +\t\treceived = pread(sf->argvfd, &memfd_arg, sizeof(memfd_arg),\n> +\t\t\t\t memfd_pos);\n> +\t\tif (received < 0) {\n> +\t\t\tint error = errno;\n> +\n> +\t\t\tfuse_log(FUSE_LOG_ERR, \"fuse: service args file iovec read: %s\\n\",\n> +\t\t\t\t strerror(error));\n> +\t\t\tret = -error;\n> +\t\t\tgoto out_new_args;\n> +\t\t}\n> +\t\tif (received < sizeof(struct fuse_service_memfd_arg)) {\n> +\t\t\tfuse_log(FUSE_LOG_ERR,\n> +\t\t\t\t \"fuse: service args file argv[%u] iovec short read %zd\",\n> +\t\t\t\t i, received);\n> +\t\t\tret = -EBADMSG;\n> +\t\t\tgoto out_new_args;\n> +\t\t}\n> +\t\tmemfd_arg.pos = htonl(memfd_arg.pos);\n> +\t\tmemfd_arg.len = htonl(memfd_arg.len);\n> +\t\tmemfd_pos += sizeof(memfd_arg);\n> +\n> +\t\t/* read arg string from file */\n> +\t\tstr = calloc(1, memfd_arg.len + 1);\n> +\t\tif (!str) {\n> +\t\t\tint error = errno;\n> +\n> +\t\t\tfuse_log(FUSE_LOG_ERR, \"fuse: service arg alloc: %s\\n\",\n> +\t\t\t\t strerror(error));\n> +\t\t\tret = -error;\n> +\t\t\tgoto out_new_args;\n> +\t\t}\n> +\n> +\t\treceived = pread(sf->argvfd, str, memfd_arg.len, memfd_arg.pos);\n> +\t\tif (received < 0) {\n> +\t\t\tint error = errno;\n> +\n> +\t\t\tfuse_log(FUSE_LOG_ERR, \"fuse: service args file read: %s\\n\",\n> +\t\t\t\t strerror(error));\n> +\t\t\tret = -error;\n> +\t\t\tgoto out_str;\n> +\t\t}\n> +\t\tif (received < memfd_arg.len) {\n> +\t\t\tfuse_log(FUSE_LOG_ERR, \"fuse: service args file argv[%u] short read %zd\",\n> +\t\t\t\t i, received);\n> +\t\t\tret = -EBADMSG;\n> +\t\t\tgoto out_str;\n> +\t\t}\n> +\n> +\t\t/* move string into the args structure */\n> +\t\tif (i == 0) {\n> +\t\t\t/* the first argument is the fs type */\n> +\t\t\tnew_args.argv[0] = str;\n> +\t\t} else {\n> +\t\t\tnew_args.argv[new_args.argc] = str;\n> +\t\t\tnew_args.argc++;\n> +\t\t}\n> +\t\tstr = NULL;\n> +\t}\n> +\n> +\t/* drop existing args, move new args to existing args */\n> +\tfuse_opt_free_args(existing_args);\n> +\tmemcpy(existing_args, &new_args, sizeof(*existing_args));\n> +\n> +\tclose(sf->argvfd);\n> +\tsf->argvfd = -1;\n> +\n> +\treturn 0;\n> +\n> +out_str:\n> +\tfree(str);\n> +out_new_args:\n> +\tfuse_opt_free_args(&new_args);\n> +\treturn ret;\n> +}\n> +\n> +#ifdef SO_PASSRIGHTS\n> +int fuse_service_finish_file_requests(struct fuse_service *sf)\n> +{\n> +\tint zero = 0;\n> +\tint ret;\n> +\n> +\t/*\n> +\t * Don't let a malicious mount helper send us more fds.  If the kernel\n> +\t * doesn't know about this new(ish) option that's ok, we'll trust the\n> +\t * servicemount helper.\n> +\t */\n> +\tret = setsockopt(sf->sockfd, SOL_SOCKET, SO_PASSRIGHTS, &zero,\n> +\t\t\t sizeof(zero));\n> +\tif (ret && errno == ENOPROTOOPT)\n> +\t\tret = 0;\n> +\tif (ret) {\n> +\t\tint error = errno;\n> +\n> +\t\tfuse_log(FUSE_LOG_ERR, \"fuse: disabling fd passing: %s\\n\",\n> +\t\t\t strerror(error));\n> +\t\treturn -error;\n> +\t}\n> +\n> +\treturn 0;\n> +}\n> +#else\n> +int fuse_service_finish_file_requests(struct fuse_service *sf)\n> +{\n> +\t(void)sf;\n> +\treturn 0;\n> +}\n> +#endif\n> +\n> +static int send_fsopen(struct fuse_service *sf, const char *fstype,\n> +\t\t       int *errorp)\n> +{\n> +\tstruct fuse_service_simple_reply reply = { };\n> +\tstruct fuse_service_fsopen_command c = {\n> +\t\t.p.magic = htonl(FUSE_SERVICE_FSOPEN_CMD),\n> +\t};\n> +\tssize_t size;\n> +\n> +\tif (!strncmp(fstype, \"fuseblk\", 7))\n> +\t\tc.fsopen_flags |= htonl(FUSE_SERVICE_FSOPEN_FUSEBLK);\n> +\n> +\tsize = __send_packet(sf, &c, sizeof(c));\n> +\tif (size < 0) {\n> +\t\tint error = errno;\n> +\n> +\t\tfuse_log(FUSE_LOG_ERR, \"fuse: send service fsopen command: %s\\n\",\n> +\t\t\t strerror(error));\n> +\t\treturn -error;\n> +\t}\n> +\n> +\tsize = __recv_packet(sf, &reply, sizeof(reply));\n> +\tif (size < 0) {\n> +\t\tint error = errno;\n> +\n> +\t\tfuse_log(FUSE_LOG_ERR, \"fuse: service fsopen reply: %s\\n\",\n> +\t\t\t strerror(error));\n> +\t\treturn -error;\n> +\t}\n> +\tif (size != sizeof(reply)) {\n> +\t\tfuse_log(FUSE_LOG_ERR, \"fuse: wrong service fsopen reply size %zd, expected %zd\\n\",\n> +\t\t\tsize, sizeof(reply));\n> +\t\treturn -EBADMSG;\n> +\t}\n> +\n> +\tif (ntohl(reply.p.magic) != FUSE_SERVICE_SIMPLE_REPLY) {\n> +\t\tfuse_log(FUSE_LOG_ERR, \"fuse: service fsopen reply contains wrong magic!\\n\");\n> +\t\treturn -EBADMSG;\n> +\t}\n> +\n> +\t*errorp = ntohl(reply.error);\n> +\treturn 0;\n> +}\n> +\n> +static int send_string(struct fuse_service *sf, uint32_t command,\n> +\t\t       const char *value, int *errorp)\n> +{\n> +\tstruct fuse_service_simple_reply reply = { };\n> +\tstruct fuse_service_string_command *cmd;\n> +\tconst size_t cmdsz = sizeof_fuse_service_string_command(strlen(value));\n> +\tssize_t size;\n> +\n> +\tcmd = calloc(1, cmdsz);\n> +\tif (!cmd) {\n> +\t\tint error = errno;\n> +\n> +\t\tfuse_log(FUSE_LOG_ERR, \"fuse: alloc service string send: %s\\n\",\n> +\t\t\t strerror(error));\n> +\t\treturn -error;\n> +\t}\n> +\tcmd->p.magic = htonl(command);\n> +\tstrcpy(cmd->value, value);\n> +\n> +\tsize = __send_packet(sf, cmd, cmdsz);\n> +\tif (size < 0) {\n> +\t\tint error = errno;\n> +\n> +\t\tfuse_log(FUSE_LOG_ERR, \"fuse: send service string: %s\\n\",\n> +\t\t\t strerror(error));\n> +\t\treturn -error;\n> +\t}\n> +\tfree(cmd);\n> +\n> +\tsize = __recv_packet(sf, &reply, sizeof(reply));\n> +\tif (size < 0) {\n> +\t\tint error = errno;\n> +\n> +\t\tfuse_log(FUSE_LOG_ERR, \"fuse: service string reply: %s\\n\",\n> +\t\t\t strerror(error));\n> +\t\treturn -error;\n> +\t}\n> +\tif (size != sizeof(reply)) {\n> +\t\tfuse_log(FUSE_LOG_ERR, \"fuse: wrong service string reply size %zd, expected %zd\\n\",\n> +\t\t\tsize, sizeof(reply));\n> +\t\treturn -EBADMSG;\n> +\t}\n> +\n> +\tif (ntohl(reply.p.magic) != FUSE_SERVICE_SIMPLE_REPLY) {\n> +\t\tfuse_log(FUSE_LOG_ERR, \"fuse: service string reply contains wrong magic!\\n\");\n> +\t\treturn -EBADMSG;\n> +\t}\n> +\n> +\t*errorp = ntohl(reply.error);\n> +\treturn 0;\n> +}\n> +\n> +static int send_mountpoint(struct fuse_service *sf, mode_t expected_fmt,\n> +\t\t\t   const char *value, int *errorp)\n> +{\n> +\tstruct fuse_service_simple_reply reply = { };\n> +\tstruct fuse_service_mountpoint_command *cmd;\n> +\tconst size_t cmdsz =\n> +\t\t\tsizeof_fuse_service_mountpoint_command(strlen(value));\n> +\tssize_t size;\n> +\n> +\tcmd = calloc(1, cmdsz);\n> +\tif (!cmd) {\n> +\t\tint error = errno;\n> +\n> +\t\tfuse_log(FUSE_LOG_ERR, \"fuse: alloc service mountpoint send: %s\\n\",\n> +\t\t\t strerror(error));\n> +\t\treturn -error;\n> +\t}\n> +\tcmd->p.magic = htonl(FUSE_SERVICE_MNTPT_CMD);\n> +\tcmd->expected_fmt = htons(expected_fmt);\n> +\tstrcpy(cmd->value, value);\n> +\n> +\tsize = __send_packet(sf, cmd, cmdsz);\n> +\tif (size < 0) {\n> +\t\tint error = errno;\n> +\n> +\t\tfuse_log(FUSE_LOG_ERR, \"fuse: send service mountpoint: %s\\n\",\n> +\t\t\t strerror(error));\n> +\t\treturn -error;\n> +\t}\n> +\tfree(cmd);\n> +\n> +\tsize = __recv_packet(sf, &reply, sizeof(reply));\n> +\tif (size < 0) {\n> +\t\tint error = errno;\n> +\n> +\t\tfuse_log(FUSE_LOG_ERR, \"fuse: service mountpoint reply: %s\\n\",\n> +\t\t\t strerror(error));\n> +\t\treturn -error;\n> +\t}\n> +\tif (size != sizeof(reply)) {\n> +\t\tfuse_log(FUSE_LOG_ERR,\n> +\t\t\t \"fuse: wrong service mountpoint reply size %zd, expected %zd\\n\",\n> +\t\t\t size, sizeof(reply));\n> +\t\treturn -EBADMSG;\n> +\t}\n> +\n> +\tif (ntohl(reply.p.magic) != FUSE_SERVICE_SIMPLE_REPLY) {\n> +\t\tfuse_log(FUSE_LOG_ERR, \"fuse: service mountpoint reply contains wrong magic!\\n\");\n> +\t\treturn -EBADMSG;\n> +\t}\n> +\n> +\t*errorp = ntohl(reply.error);\n> +\treturn 0;\n> +}\n> +\n> +static int send_mount(struct fuse_service *sf, unsigned int ms_flags,\n> +\t\t      int *errorp)\n> +{\n> +\tstruct fuse_service_simple_reply reply = { };\n> +\tstruct fuse_service_mount_command c = {\n> +\t\t.p.magic = htonl(FUSE_SERVICE_MOUNT_CMD),\n> +\t\t.ms_flags = htonl(ms_flags),\n> +\t};\n> +\tssize_t size;\n> +\n> +\tsize = __send_packet(sf, &c, sizeof(c));\n> +\tif (size < 0) {\n> +\t\tint error = errno;\n> +\n> +\t\tfuse_log(FUSE_LOG_ERR, \"fuse: send service mount command: %s\\n\",\n> +\t\t\t strerror(error));\n> +\t\treturn -error;\n> +\t}\n> +\n> +\tsize = __recv_packet(sf, &reply, sizeof(reply));\n> +\tif (size < 0) {\n> +\t\tint error = errno;\n> +\n> +\t\tfuse_log(FUSE_LOG_ERR, \"fuse: service mount reply: %s\\n\",\n> +\t\t\t strerror(error));\n> +\t\treturn -error;\n> +\t}\n> +\tif (size != sizeof(reply)) {\n> +\t\tfuse_log(FUSE_LOG_ERR, \"fuse: wrong service mount reply size %zd, expected %zd\\n\",\n> +\t\t\tsize, sizeof(reply));\n> +\t\treturn -EBADMSG;\n> +\t}\n> +\n> +\tif (ntohl(reply.p.magic) != FUSE_SERVICE_SIMPLE_REPLY) {\n> +\t\tfuse_log(FUSE_LOG_ERR, \"fuse: service mount reply contains wrong magic!\\n\");\n> +\t\treturn -EBADMSG;\n> +\t}\n> +\n> +\t*errorp = ntohl(reply.error);\n> +\treturn 0;\n> +}\n> +\n> +void fuse_service_expect_mount_format(struct fuse_service *sf,\n> +\t\t\t\t      mode_t expected_fmt)\n> +{\n> +\tsf->expected_fmt = expected_fmt;\n> +}\n> +\n> +int fuse_service_session_mount(struct fuse_service *sf, struct fuse_session *se,\n> +\t\t\t       mode_t expected_fmt,\n> +\t\t\t       struct fuse_cmdline_opts *opts)\n> +{\n> +\tchar *fstype = fuse_mnt_build_type(se->mo);\n> +\tchar *source = fuse_mnt_build_source(se->mo);\n> +\tchar *mntopts = fuse_mnt_kernel_opts(se->mo);\n> +\tchar path[32];\n> +\tint ret;\n> +\tint error = 0;\n> +\n> +\tif (!fstype || !source) {\n> +\t\tfuse_log(FUSE_LOG_ERR, \"fuse: cannot allocate service strings\\n\");\n> +\t\tret = -ENOMEM;\n> +\t\tgoto out_strings;\n> +\t}\n> +\n> +\tif (!expected_fmt)\n> +\t\texpected_fmt = sf->expected_fmt;\n> +\n> +\t/*\n> +\t * The fuse session takes the fusedev fd if this succeeds.  It is\n> +\t * required to use the \"/dev/fd/XX\" format.\n> +\t */\n> +\tsnprintf(path, sizeof(path), \"/dev/fd/%d\", sf->fusedevfd);\n> +\terrno = 0;\n> +\tret = fuse_session_mount(se, path);\n> +\tif (ret) {\n> +\t\t/* Try to return richer errors than fuse_session_mount's -1 */\n> +\t\tret = errno ? -errno : -EINVAL;\n> +\t\tgoto out_strings;\n> +\t}\n> +\tsf->owns_fusedevfd = false;\n> +\n> +\tret = send_fsopen(sf, fstype, &error);\n> +\tif (ret)\n> +\t\tgoto out_strings;\n> +\tif (error) {\n> +\t\tfuse_log(FUSE_LOG_ERR, \"fuse: service fsopen: %s\\n\",\n> +\t\t\t strerror(error));\n> +\t\tret = -error;\n> +\t\tgoto out_strings;\n> +\t}\n> +\n> +\tret = send_string(sf, FUSE_SERVICE_SOURCE_CMD, source, &error);\n> +\tif (ret)\n> +\t\tgoto out_strings;\n> +\tif (error) {\n> +\t\tfuse_log(FUSE_LOG_ERR, \"fuse: service fs source: %s\\n\",\n> +\t\t\t strerror(error));\n> +\t\tret = -error;\n> +\t\tgoto out_strings;\n> +\t}\n> +\n> +\tret = send_mountpoint(sf, expected_fmt, opts->mountpoint, &error);\n> +\tif (ret)\n> +\t\tgoto out_strings;\n> +\tif (error) {\n> +\t\tfuse_log(FUSE_LOG_ERR, \"fuse: service fs mountpoint: %s\\n\",\n> +\t\t\t strerror(error));\n> +\t\tret = -error;\n> +\t\tgoto out_strings;\n> +\t}\n> +\n> +\tif (mntopts) {\n> +\t\tret = send_string(sf, FUSE_SERVICE_MNTOPTS_CMD, mntopts,\n> +\t\t\t\t  &error);\n> +\t\tif (ret)\n> +\t\t\tgoto out_strings;\n> +\t\tif (error) {\n> +\t\t\tfuse_log(FUSE_LOG_ERR, \"fuse: service fs mount options: %s\\n\",\n> +\t\t\t\t strerror(error));\n> +\t\t\tret = -error;\n> +\t\t\tgoto out_strings;\n> +\t\t}\n> +\t}\n> +\n> +\tret = send_mount(sf, fuse_mnt_flags(se->mo), &error);\n> +\tif (ret)\n> +\t\tgoto out_strings;\n> +\tif (error) {\n> +\t\tfuse_log(FUSE_LOG_ERR, \"fuse: service mount: %s\\n\",\n> +\t\t\t strerror(error));\n> +\t\tret = -error;\n> +\t\tgoto out_strings;\n> +\t}\n> +\n> +\t/*\n> +\t * foreground mode is needed so that systemd actually tracks the\n> +\t * service correctly and doesn't try to kill it; and so that\n> +\t * stdout/stderr don't get zapped.  Change to the root directory so\n> +\t * that the caller needn't call fuse_daemonize().\n> +\t */\n> +\topts->foreground = 1;\n> +\t(void)chdir(\"/\");\n> +\n> +out_strings:\n> +\tfree(mntopts);\n> +\tfree(source);\n> +\tfree(fstype);\n> +\treturn ret;\n> +}\n> +\n> +int fuse_service_session_unmount(struct fuse_service *sf)\n> +{\n> +\tstruct fuse_service_simple_reply reply = { };\n> +\tstruct fuse_service_unmount_command c = {\n> +\t\t.p.magic = htonl(FUSE_SERVICE_UNMOUNT_CMD),\n> +\t};\n> +\tssize_t size;\n> +\n> +\t/* already gone? */\n> +\tif (sf->sockfd < 0)\n> +\t\treturn 0;\n> +\n> +\tsize = __send_packet(sf, &c, sizeof(c));\n> +\tif (size < 0) {\n> +\t\tint error = errno;\n> +\n> +\t\tfuse_log(FUSE_LOG_ERR, \"fuse: send service unmount: %s\\n\",\n> +\t\t\t strerror(error));\n> +\t\treturn -error;\n> +\t}\n> +\n> +\tsize = __recv_packet(sf, &reply, sizeof(reply));\n> +\tif (size < 0) {\n> +\t\tint error = errno;\n> +\n> +\t\tfuse_log(FUSE_LOG_ERR, \"fuse: service unmount reply: %s\\n\",\n> +\t\t\t strerror(error));\n> +\t\treturn -error;\n> +\t}\n> +\tif (size != sizeof(reply)) {\n> +\t\tfuse_log(FUSE_LOG_ERR, \"fuse: wrong service unmount reply size %zd, expected %zd\\n\",\n> +\t\t\tsize, sizeof(reply));\n> +\t\treturn -EBADMSG;\n> +\t}\n> +\n> +\tif (ntohl(reply.p.magic) != FUSE_SERVICE_SIMPLE_REPLY) {\n> +\t\tfuse_log(FUSE_LOG_ERR, \"fuse: service unmount reply contains wrong magic!\\n\");\n> +\t\treturn -EBADMSG;\n> +\t}\n> +\n> +\tif (reply.error) {\n> +\t\tint error = ntohl(reply.error);\n> +\n> +\t\tfuse_log(FUSE_LOG_ERR, \"fuse: service unmount: %s\\n\",\n> +\t\t\t strerror(error));\n> +\t\treturn -error;\n> +\t}\n> +\n> +\treturn 0;\n> +}\n> +\n> +void fuse_service_release(struct fuse_service *sf)\n> +{\n> +\tif (sf->owns_fusedevfd)\n> +\t\tclose(sf->fusedevfd);\n> +\tsf->owns_fusedevfd = false;\n> +\tsf->fusedevfd = -1;\n> +\tclose(sf->argvfd);\n> +\tsf->argvfd = -1;\n> +\tshutdown(sf->sockfd, SHUT_RDWR);\n> +\tclose(sf->sockfd);\n> +\tsf->sockfd = -1;\n> +}\n> +\n> +void fuse_service_destroy(struct fuse_service **sfp)\n> +{\n> +\tstruct fuse_service *sf = *sfp;\n> +\n> +\tif (sf) {\n> +\t\tfuse_service_release(*sfp);\n> +\t\tfree(sf);\n> +\t}\n> +\n> +\t*sfp = NULL;\n> +}\n> +\n> +char *fuse_service_cmdline(int argc, char *argv[], struct fuse_args *args)\n> +{\n> +\tchar *p, *dst;\n> +\tsize_t len = 1;\n> +\tssize_t ret;\n> +\tchar *argv0;\n> +\tunsigned int i;\n> +\n> +\t/* Try to preserve argv[0] */\n> +\tif (argc > 0)\n> +\t\targv0 = argv[0];\n> +\telse if (args->argc > 0)\n> +\t\targv0 = args->argv[0];\n> +\telse\n> +\t\treturn NULL;\n> +\n> +\t/* Pick up the alleged fstype from args->argv[0] */\n> +\tif (args->argc == 0)\n> +\t\treturn NULL;\n> +\n> +\tlen += strlen(argv0) + 1;\n> +\tlen += 3; /* \" -t\" */\n> +\tfor (i = 0; i < args->argc; i++)\n> +\t\tlen += strlen(args->argv[i]) + 1;\n> +\n> +\tp = calloc(1, len);\n> +\tif (!p)\n> +\t\treturn NULL;\n> +\tdst = p;\n> +\n> +\t/* Format: argv0 -t alleged_fstype [all other options...] */\n> +\tret = sprintf(dst, \"%s -t\", argv0);\n> +\tdst += ret;\n> +\tfor (i = 0; i < args->argc; i++) {\n> +\t\tret = sprintf(dst, \" %s\", args->argv[i]);\n> +\t\tdst += ret;\n> +\t}\n> +\n> +\treturn p;\n> +}\n> +\n> +int fuse_service_parse_cmdline_opts(struct fuse_args *args,\n> +\t\t\t\t    struct fuse_cmdline_opts *opts)\n> +{\n> +\treturn fuse_parse_cmdline_service(args, opts);\n> +}\n> +\n> +int fuse_service_exit(int ret)\n> +{\n> +\t/*\n> +\t * We have to sleep 2 seconds here because journald uses the pid to\n> +\t * connect our log messages to the systemd service.  This is critical\n> +\t * for capturing all the log messages if the service fails, because\n> +\t * failure analysis tools use the service name to gather log messages\n> +\t * for reporting.\n> +\t */\n> +\tsleep(2);\n> +\n> +\t/*\n> +\t * If we're being run as a service, the return code must fit the LSB\n> +\t * init script action error guidelines, which is to say that we\n> +\t * compress all errors to 1 (\"generic or unspecified error\", LSB 5.0\n> +\t * section 22.2) and hope the admin will scan the log for what actually\n> +\t * happened.\n> +\t */\n> +\treturn ret != 0 ? EXIT_FAILURE : EXIT_SUCCESS;\n> +}\n> diff --git a/lib/fuse_service_stub.c b/lib/fuse_service_stub.c\n> new file mode 100644\n> index 00000000000000..d34df3891a6e31\n> --- /dev/null\n> +++ b/lib/fuse_service_stub.c\n> @@ -0,0 +1,106 @@\n> +/*\n> + * FUSE: Filesystem in Userspace\n> + * Copyright (C) 2025-2026 Oracle.\n> + * Author: Darrick J. Wong <djwong@kernel.org>\n> + *\n> + * Stub functions for platforms where we cannot have fuse servers run as \"safe\"\n> + * systemd containers.\n> + *\n> + * This program can be distributed under the terms of the GNU LGPLv2.\n> + * See the file LGPL2.txt\n> + */\n> +\n> +/* we don't use any parameters at all */\n> +#pragma GCC diagnostic ignored \"-Wunused-parameter\"\n> +\n> +#define _GNU_SOURCE\n> +#include <errno.h>\n> +\n> +#include \"fuse_config.h\"\n> +#include \"fuse_i.h\"\n> +#include \"fuse_service.h\"\n> +\n> +int fuse_service_receive_file(struct fuse_service *sf, const char *path,\n> +\t\t\t      int *fdp)\n> +{\n> +\treturn -EOPNOTSUPP;\n> +}\n> +\n> +int fuse_service_request_file(struct fuse_service *sf, const char *path,\n> +\t\t\t      int open_flags, mode_t create_mode,\n> +\t\t\t      unsigned int request_flags)\n> +{\n> +\treturn -EOPNOTSUPP;\n> +}\n> +\n> +int fuse_service_request_blockdev(struct fuse_service *sf, const char *path,\n> +\t\t\t\t  int open_flags, mode_t create_mode,\n> +\t\t\t\t  unsigned int request_flags,\n> +\t\t\t\t  unsigned int block_size)\n> +{\n> +\treturn -EOPNOTSUPP;\n> +}\n> +\n> +int fuse_service_send_goodbye(struct fuse_service *sf, int error)\n> +{\n> +\treturn -EOPNOTSUPP;\n> +}\n> +\n> +int fuse_service_accept(struct fuse_service **sfp)\n> +{\n> +\t*sfp = NULL;\n> +\treturn 0;\n> +}\n> +\n> +int fuse_service_append_args(struct fuse_service *sf,\n> +\t\t\t     struct fuse_args *existing_args)\n> +{\n> +\treturn -EOPNOTSUPP;\n> +}\n> +\n> +char *fuse_service_cmdline(int argc, char *argv[], struct fuse_args *args)\n> +{\n> +\treturn NULL;\n> +}\n> +\n> +int fuse_service_finish_file_requests(struct fuse_service *sf)\n> +{\n> +\treturn -EOPNOTSUPP;\n> +}\n> +\n> +void fuse_service_expect_mount_format(struct fuse_service *sf,\n> +\t\t\t\t      mode_t expected_fmt)\n> +{\n> +}\n> +\n> +int fuse_service_session_mount(struct fuse_service *sf, struct fuse_session *se,\n> +\t\t\t       mode_t expected_fmt,\n> +\t\t\t       struct fuse_cmdline_opts *opts)\n> +{\n> +\treturn -EOPNOTSUPP;\n> +}\n> +\n> +int fuse_service_session_unmount(struct fuse_service *sf)\n> +{\n> +\treturn -EOPNOTSUPP;\n> +}\n> +\n> +void fuse_service_release(struct fuse_service *sf)\n> +{\n> +}\n> +\n> +void fuse_service_destroy(struct fuse_service **sfp)\n> +{\n> +\t*sfp = NULL;\n> +}\n> +\n> +int fuse_service_parse_cmdline_opts(struct fuse_args *args,\n> +\t\t\t\t    struct fuse_cmdline_opts *opts)\n> +{\n> +\treturn -1;\n> +}\n> +\n> +int fuse_service_exit(int ret)\n> +{\n> +\treturn ret;\n> +}\n> diff --git a/lib/fuse_versionscript b/lib/fuse_versionscript\n> index cce09610316f4b..f34dc959a1d1e1 100644\n> --- a/lib/fuse_versionscript\n> +++ b/lib/fuse_versionscript\n> @@ -227,6 +227,23 @@ FUSE_3.19 {\n>  \t\tfuse_session_start_teardown_watchdog;\n>  \t\tfuse_session_stop_teardown_watchdog;\n>  \t\tfuse_lowlevel_notify_prune;\n> +\n> +\t\tfuse_service_accept;\n> +\t\tfuse_service_append_args;\n> +\t\tfuse_service_can_allow_other;\n> +\t\tfuse_service_cmdline;\n> +\t\tfuse_service_destroy;\n> +\t\tfuse_service_exit;\n> +\t\tfuse_service_expect_mount_format;\n> +\t\tfuse_service_finish_file_requests;\n> +\t\tfuse_service_parse_cmdline_opts;\n> +\t\tfuse_service_receive_file;\n> +\t\tfuse_service_release;\n> +\t\tfuse_service_request_file;\n> +\t\tfuse_service_request_blockdev;\n> +\t\tfuse_service_send_goodbye;\n> +\t\tfuse_service_session_mount;\n> +\t\tfuse_service_session_unmount;\n>  } FUSE_3.18;\n>  \n>  # Local Variables:\n> diff --git a/lib/helper.c b/lib/helper.c\n> index 74906fdcbd76d9..819b9a6e4d243c 100644\n> --- a/lib/helper.c\n> +++ b/lib/helper.c\n> @@ -26,6 +26,11 @@\n>  #include <errno.h>\n>  #include <sys/param.h>\n>  \n> +#ifdef HAVE_SERVICEMOUNT\n> +# include <linux/types.h>\n> +# include \"fuse_service_priv.h\"\n> +#endif\n> +\n>  #define FUSE_HELPER_OPT(t, p) \\\n>  \t{ t, offsetof(struct fuse_cmdline_opts, p), 1 }\n>  \n> @@ -228,6 +233,52 @@ int fuse_parse_cmdline_312(struct fuse_args *args,\n>  \treturn 0;\n>  }\n>  \n> +#ifdef HAVE_SERVICEMOUNT\n> +static int fuse_helper_opt_proc_service(void *data, const char *arg, int key,\n> +\t\t\t\t\tstruct fuse_args *outargs)\n> +{\n> +\t(void) outargs;\n> +\tstruct fuse_cmdline_opts *opts = data;\n> +\n> +\tswitch (key) {\n> +\tcase FUSE_OPT_KEY_NONOPT:\n> +\t\tif (!opts->mountpoint)\n> +\t\t\treturn fuse_opt_add_opt(&opts->mountpoint, arg);\n> +\n> +\t\tfuse_log(FUSE_LOG_ERR, \"fuse: invalid argument `%s'\\n\", arg);\n> +\t\treturn -1;\n> +\tdefault:\n> +\t\t/* Pass through unknown options */\n> +\t\treturn 1;\n> +\t}\n> +}\n> +\n> +int fuse_parse_cmdline_service(struct fuse_args *args,\n> +\t\t\t       struct fuse_cmdline_opts *opts)\n> +{\n> +\tmemset(opts, 0, sizeof(struct fuse_cmdline_opts));\n> +\n> +\topts->max_idle_threads = UINT_MAX; /* new default in fuse version 3.12 */\n> +\topts->max_threads = 10;\n> +\n> +\tif (fuse_opt_parse(args, opts, fuse_helper_opts,\n> +\t\t\t   fuse_helper_opt_proc_service) == -1)\n> +\t\treturn -1;\n> +\n> +\t/*\n> +\t * *Linux*: if neither -o subtype nor -o fsname are specified,\n> +\t * set subtype to program's basename.\n> +\t * *FreeBSD*: if fsname is not specified, set to program's\n> +\t * basename.\n> +\t */\n> +\tif (!opts->nodefault_subtype)\n> +\t\tif (add_default_subtype(args->argv[0], args) == -1)\n> +\t\t\treturn -1;\n> +\n> +\treturn 0;\n> +}\n> +#endif\n> +\n>  /**\n>   * struct fuse_cmdline_opts got extended in libfuse-3.12\n>   */\n> diff --git a/lib/meson.build b/lib/meson.build\n> index fcd95741c9d374..d9a902f74b558f 100644\n> --- a/lib/meson.build\n> +++ b/lib/meson.build\n> @@ -10,6 +10,12 @@ else\n>     libfuse_sources += [ 'mount_bsd.c' ]\n>  endif\n>  \n> +if private_cfg.get('HAVE_SERVICEMOUNT', false)\n> +  libfuse_sources += [ 'fuse_service.c' ]\n> +else\n> +  libfuse_sources += [ 'fuse_service_stub.c' ]\n> +endif\n> +\n>  deps = [ thread_dep ]\n>  if private_cfg.get('HAVE_ICONV')\n>     libfuse_sources += [ 'modules/iconv.c' ]\n> @@ -49,18 +55,25 @@ libfuse = library('fuse3',\n>                    dependencies: deps,\n>                    install: true,\n>                    link_depends: 'fuse_versionscript',\n> -                  c_args: [ '-DFUSE_USE_VERSION=317',\n> +                  c_args: [ '-DFUSE_USE_VERSION=319',\n>                              '-DFUSERMOUNT_DIR=\"@0@\"'.format(fusermount_path) ],\n>                    link_args: ['-Wl,--version-script,' + meson.current_source_dir()\n>                                + '/fuse_versionscript' ])\n>  \n> +vars = []\n> +if private_cfg.get('HAVE_SERVICEMOUNT', false)\n> +  service_socket_dir = private_cfg.get_unquoted('FUSE_SERVICE_SOCKET_DIR', '')\n> +  vars += ['service_socket_dir=' + service_socket_dir]\n> +  vars += ['service_socket_perms=' + service_socket_perms]\n> +endif\n>  pkg = import('pkgconfig')\n>  pkg.generate(libraries: [ libfuse, '-lpthread' ],\n>               libraries_private: '-ldl',\n>               version: meson.project_version(),\n>               name: 'fuse3',\n>               description: 'Filesystem in Userspace',\n> -             subdirs: 'fuse3')\n> +             subdirs: 'fuse3',\n> +             variables: vars)\n>  \n>  libfuse_dep = declare_dependency(include_directories: include_dirs,\n>                                   link_with: libfuse, dependencies: deps)\n> diff --git a/lib/mount.c b/lib/mount.c\n> index 2397c3fb2aa26b..952d8899dcf218 100644\n> --- a/lib/mount.c\n> +++ b/lib/mount.c\n> @@ -750,3 +750,15 @@ char *fuse_mnt_build_type(const struct mount_opts *mo)\n>  \n>  \treturn type;\n>  }\n> +\n> +char *fuse_mnt_kernel_opts(const struct mount_opts *mo)\n> +{\n> +\tif (mo->kernel_opts)\n> +\t\treturn strdup(mo->kernel_opts);\n> +\treturn NULL;\n> +}\n> +\n> +unsigned int fuse_mnt_flags(const struct mount_opts *mo)\n> +{\n> +\treturn mo->flags;\n> +}\n> diff --git a/meson.build b/meson.build\n> index 80c5f1dc0bd356..66425a0d4cc16f 100644\n> --- a/meson.build\n> +++ b/meson.build\n> @@ -69,6 +69,16 @@ args_default = [ '-D_GNU_SOURCE' ]\n>  #\n>  private_cfg = configuration_data()\n>  private_cfg.set_quoted('PACKAGE_VERSION', meson.project_version())\n> +service_socket_dir = get_option('service-socket-dir')\n> +service_socket_perms = get_option('service-socket-perms')\n> +if service_socket_dir == ''\n> +  service_socket_dir = '/run/filesystems'\n> +endif\n> +if service_socket_perms == ''\n> +  service_socket_perms = '0220'\n> +endif\n> +private_cfg.set_quoted('FUSE_SERVICE_SOCKET_DIR', service_socket_dir)\n> +private_cfg.set('FUSE_SERVICE_SOCKET_PERMS', service_socket_perms)\n>  \n>  # Test for presence of some functions\n>  test_funcs = [ 'fork', 'fstatat', 'openat', 'readlinkat', 'pipe2',\n> @@ -118,6 +128,13 @@ special_funcs = {\n>  \t    return -1;\n>  \t  }\n>  \t}\n> +    ''',\n> +    'systemd_headers': '''\n> +\t#include <systemd/sd-daemon.h>\n> +\n> +\tint main(int argc, char *argv[]) {\n> +          return SD_LISTEN_FDS_START;\n> +\t}\n>      '''\n>  }\n>  \n> @@ -180,6 +197,23 @@ if get_option('enable-io-uring') and liburing.found() and libnuma.found()\n>     endif\n>  endif\n>  \n> +# Check for systemd support\n> +systemd_system_unit_dir = get_option('systemd-system-unit-dir')\n> +if systemd_system_unit_dir == ''\n> +  systemd = dependency('systemd', required: false)\n> +  if systemd.found()\n> +     systemd_system_unit_dir = systemd.get_variable(pkgconfig: 'systemd_system_unit_dir')\n> +  endif\n> +endif\n> +\n> +if systemd_system_unit_dir == '' or private_cfg.get('HAVE_SYSTEMD_HEADERS', false) == false\n> +  warning('systemd service support will not be built')\n> +else\n> +  private_cfg.set_quoted('SYSTEMD_SYSTEM_UNIT_DIR', systemd_system_unit_dir)\n> +  private_cfg.set('HAVE_SYSTEMD', true)\n> +  private_cfg.set('HAVE_SERVICEMOUNT', true)\n> +endif\n> +\n>  #\n>  # Compiler configuration\n>  #\n> diff --git a/meson_options.txt b/meson_options.txt\n> index c1f8fe69467184..193a74c96d0676 100644\n> --- a/meson_options.txt\n> +++ b/meson_options.txt\n> @@ -27,3 +27,12 @@ option('enable-usdt', type : 'boolean', value : false,\n>  \n>  option('enable-io-uring', type: 'boolean', value: true,\n>         description: 'Enable fuse-over-io-uring support')\n> +\n> +option('service-socket-dir', type : 'string', value : '',\n> +       description: 'Where to install fuse server sockets (if empty, /run/filesystems)')\n> +\n> +option('service-socket-perms', type : 'string', value : '',\n> +       description: 'Default fuse server socket permissions (if empty, 0220)')\n> +\n> +option('systemd-system-unit-dir', type : 'string', value : '',\n> +       description: 'Where to install systemd unit files (if empty, query pkg-config(1))')\n> diff --git a/util/fuservicemount.c b/util/fuservicemount.c\n> new file mode 100644\n> index 00000000000000..9c694a4290f94e\n> --- /dev/null\n> +++ b/util/fuservicemount.c\n> @@ -0,0 +1,18 @@\n> +/*\n> + * FUSE: Filesystem in Userspace\n> + * Copyright (C) 2025-2026 Oracle.\n> + * Author: Darrick J. Wong <djwong@kernel.org>\n> + *\n> + * This program can be distributed under the terms of the GNU GPLv2.\n> + * See the file GPL2.txt.\n> + *\n> + * This program wraps the mounting of FUSE filesystems that run in systemd\n> + */\n> +#define _GNU_SOURCE\n> +#include \"fuse_config.h\"\n> +#include \"mount_service.h\"\n> +\n> +int main(int argc, char *argv[])\n> +{\n> +\treturn mount_service_main(argc, argv);\n> +}\n> diff --git a/util/meson.build b/util/meson.build\n> index 0e4b1cce95377e..04ea5ac201340d 100644\n> --- a/util/meson.build\n> +++ b/util/meson.build\n> @@ -6,6 +6,15 @@ executable('fusermount3', ['fusermount.c', '../lib/mount_util.c', '../lib/util.c\n>             install_dir: get_option('bindir'),\n>             c_args: '-DFUSE_CONF=\"@0@\"'.format(fuseconf_path))\n>  \n> +if private_cfg.get('HAVE_SERVICEMOUNT', false)\n> +  executable('fuservicemount3', ['mount_service.c', 'fuservicemount.c', '../lib/mount_util.c'],\n> +             include_directories: include_dirs,\n> +             link_with: [ libfuse ],\n> +             install: true,\n> +             install_dir: get_option('sbindir'),\n> +             c_args: '-DFUSE_USE_VERSION=319')\n> +endif\n> +\n>  executable('mount.fuse3', ['mount.fuse.c'],\n>             include_directories: include_dirs,\n>             link_with: [ libfuse ],\n> diff --git a/util/mount_service.c b/util/mount_service.c\n> new file mode 100644\n> index 00000000000000..a43ff79c7bfb6f\n> --- /dev/null\n> +++ b/util/mount_service.c\n> @@ -0,0 +1,1427 @@\n> +/*\n> + * FUSE: Filesystem in Userspace\n> + * Copyright (C) 2025-2026 Oracle.\n> + * Author: Darrick J. Wong <djwong@kernel.org>\n> + *\n> + * This program can be distributed under the terms of the GNU GPLv2.\n> + * See the file GPL2.txt.\n> + *\n> + * This program does the mounting of FUSE filesystems that run in systemd\n> + */\n> +#define _GNU_SOURCE\n> +#include \"fuse_config.h\"\n> +#include <stdint.h>\n> +#include <string.h>\n> +#include <stdio.h>\n> +#include <stdlib.h>\n> +#include <unistd.h>\n> +#include <errno.h>\n> +#include <fcntl.h>\n> +#include <stdbool.h>\n> +#include <limits.h>\n> +#include <arpa/inet.h>\n> +#include <sys/socket.h>\n> +#include <sys/un.h>\n> +#include <sys/mman.h>\n> +#include <sys/mount.h>\n> +#include <sys/stat.h>\n> +#include <sys/ioctl.h>\n> +#include <linux/fs.h>\n> +\n> +#include \"mount_util.h\"\n> +#include \"util.h\"\n> +#include \"fuse_i.h\"\n> +#include \"fuse_service_priv.h\"\n> +#include \"mount_service.h\"\n> +\n> +struct mount_service {\n> +\t/* prefix for printing error messages */\n> +\tconst char *msgtag;\n> +\n> +\t/* fuse subtype based on -t cli argument */\n> +\tchar *subtype;\n> +\n> +\t/* source argument to mount() */\n> +\tchar *source;\n> +\n> +\t/* target argument (aka mountpoint) to mount() */\n> +\tchar *mountpoint;\n> +\n> +\t/* mountpoint that we pass to mount() */\n> +\tchar *real_mountpoint;\n> +\n> +\t/* resolved path to mountpoint that we use for mtab updates */\n> +\tchar *resv_mountpoint;\n> +\n> +\t/* mount options */\n> +\tchar *mntopts;\n> +\n> +\t/* socket fd */\n> +\tint sockfd;\n> +\n> +\t/* /dev/fuse device */\n> +\tint fusedevfd;\n> +\n> +\t/* memfd for cli arguments */\n> +\tint argvfd;\n> +\n> +\t/* fd for mount point */\n> +\tint mountfd;\n> +\n> +\t/* did we actually mount successfully? */\n> +\tbool mounted;\n> +\n> +\t/* has the fsopen command already been submitted? */\n> +\tbool fsopened;\n> +\n> +\t/* is this a fuseblk mount? */\n> +\tbool fuseblk;\n> +};\n> +\n> +static ssize_t __send_fd(struct mount_service *mo,\n> +\t\t\t struct fuse_service_requested_file *req,\n> +\t\t\t size_t req_sz, int fd)\n> +{\n> +\tunion {\n> +\t\tstruct cmsghdr cmsghdr;\n> +\t\tchar control[CMSG_SPACE(sizeof(int))];\n> +\t} cmsgu;\n> +\tstruct iovec iov = {\n> +\t\t.iov_base = req,\n> +\t\t.iov_len = req_sz,\n> +\t};\n> +\tstruct msghdr msg = {\n> +\t\t.msg_iov = &iov,\n> +\t\t.msg_iovlen = 1,\n> +\t\t.msg_control = cmsgu.control,\n> +\t\t.msg_controllen = sizeof(cmsgu.control),\n> +\t};\n> +\tstruct cmsghdr *cmsg = CMSG_FIRSTHDR(&msg);\n> +\n> +\tif (!cmsg) {\n> +\t\terrno = EINVAL;\n> +\t\treturn -1;\n> +\t}\n> +\n> +\tmemset(&cmsgu, 0, sizeof(cmsgu));\n> +\tcmsg->cmsg_len = CMSG_LEN(sizeof(int));\n> +\tcmsg->cmsg_level = SOL_SOCKET;\n> +\tcmsg->cmsg_type = SCM_RIGHTS;\n> +\n> +\t*((int *)CMSG_DATA(cmsg)) = fd;\n> +\n> +\treturn sendmsg(mo->sockfd, &msg, MSG_EOR | MSG_NOSIGNAL);\n> +}\n> +\n> +static ssize_t __send_packet(struct mount_service *mo, void *ptr, size_t len)\n> +{\n> +\tstruct iovec iov = {\n> +\t\t.iov_base = ptr,\n> +\t\t.iov_len = len,\n> +\t};\n> +\tstruct msghdr msg = {\n> +\t\t.msg_iov = &iov,\n> +\t\t.msg_iovlen = 1,\n> +\t};\n> +\n> +\treturn sendmsg(mo->sockfd, &msg, MSG_EOR | MSG_NOSIGNAL);\n> +}\n> +\n> +static ssize_t __recv_packet_size(struct mount_service *mo)\n> +{\n> +\tstruct iovec iov = { };\n> +\tstruct msghdr msg = {\n> +\t\t.msg_iov = &iov,\n> +\t\t.msg_iovlen = 1,\n> +\t};\n> +\treturn recvmsg(mo->sockfd, &msg, MSG_PEEK | MSG_TRUNC);\n> +}\n> +\n> +static ssize_t __recv_packet(struct mount_service *mo, void *ptr, size_t len)\n> +{\n> +\tstruct iovec iov = {\n> +\t\t.iov_base = ptr,\n> +\t\t.iov_len = len,\n> +\t};\n> +\tstruct msghdr msg = {\n> +\t\t.msg_iov = &iov,\n> +\t\t.msg_iovlen = 1,\n> +\t};\n> +\n> +\treturn recvmsg(mo->sockfd, &msg, MSG_TRUNC);\n> +}\n> +\n> +/*\n> + * Filter out the subtype of the filesystem (e.g. fuse.Y[.Z] -> Y[.Z]).  The\n> + * fuse server determines if it's appropriate to set the \"blockdev\" mount\n> + * option (aka fuseblk).\n> + */\n> +const char *mount_service_subtype(const char *fstype)\n> +{\n> +\tconst char *subtype;\n> +\n> +\tif (!strncmp(fstype, \"fuse.\", 5))\n> +\t\tsubtype = fstype + 5;\n> +\telse if (!strncmp(fstype, \"fuseblk.\", 8))\n> +\t\tsubtype = fstype + 8;\n> +\telse\n> +\t\tsubtype = fstype;\n> +\n> +\tif (strchr(subtype, '/') != NULL) {\n> +\t\tfprintf(stderr,\n> +\t\t\t\"%s: fs subtype cannot contain path separators\\n\",\n> +\t\t\tfstype);\n> +\t\treturn NULL;\n> +\t}\n> +\n> +\treturn subtype;\n> +}\n> +\n> +static int mount_service_init(struct mount_service *mo, int argc, char *argv[])\n> +{\n> +\tchar *fstype = NULL;\n> +\tconst char *subtype;\n> +\tint i;\n> +\n> +\tmo->sockfd = -1;\n> +\tmo->argvfd = -1;\n> +\tmo->fusedevfd = -1;\n> +\tmo->mountfd = -1;\n> +\n> +\tfor (i = 0; i < argc; i++) {\n> +\t\tif (!strcmp(argv[i], \"-t\") && i + 1 < argc) {\n> +\t\t\tfstype = argv[i + 1];\n> +\t\t\tbreak;\n> +\t\t}\n> +\t}\n> +\tif (!fstype) {\n> +\t\tfprintf(stderr, \"%s: cannot determine filesystem type.\\n\",\n> +\t\t\tmo->msgtag);\n> +\t\treturn -1;\n> +\t}\n> +\n> +\tsubtype = mount_service_subtype(fstype);\n> +\tif (!subtype)\n> +\t\treturn -1;\n> +\n> +\tmo->subtype = strdup(subtype);\n> +\tif (!mo->subtype) {\n> +\t\tint error = errno;\n> +\n> +\t\tfprintf(stderr, \"%s: cannot alloc memory for fs subtype: %s\\n\",\n> +\t\t\tmo->msgtag, strerror(error));\n> +\t\treturn -1;\n> +\t}\n> +\n> +\treturn 0;\n> +}\n> +\n> +#ifdef SO_PASSRIGHTS\n> +static int try_drop_passrights(struct mount_service *mo, int sockfd)\n> +{\n> +\tint zero = 0;\n> +\tint ret;\n> +\n> +\t/*\n> +\t * Don't let a malicious mount helper send us any fds.  We don't trust\n> +\t * the fuse server not to pollute our fd namespace, so we'll end now.\n> +\t */\n> +\tret = setsockopt(sockfd, SOL_SOCKET, SO_PASSRIGHTS, &zero,\n> +\t\t\t sizeof(zero));\n> +\tif (ret) {\n> +\t\tfprintf(stderr, \"%s: disabling fd passing: %s\\n\",\n> +\t\t\tmo->msgtag, strerror(errno));\n> +\t\treturn -1;\n> +\t}\n> +\n> +\treturn 0;\n> +}\n> +#else\n> +# define try_drop_passrights(...)\t(0)\n> +#endif\n> +\n> +static int check_sendbuf_size(struct mount_service *mo, int sockfd)\n> +{\n> +\tconst size_t min_size = sizeof_fuse_service_open_command(PATH_MAX);\n> +\tint sendbuf_size = -1;\n> +\tsocklen_t optlen = sizeof(sendbuf_size);\n> +\tint ret;\n> +\n> +\t/*\n> +\t * If we can't query the maximum send buffer length, just keep going.\n> +\t * Most likely we won't be sending huge open commands, and if we do,\n> +\t * the sendmsg will fail there too.\n> +\t */\n> +\tret = getsockopt(sockfd, SOL_SOCKET, SO_SNDBUF, &sendbuf_size, &optlen);\n> +\tif (ret || sendbuf_size < 0)\n> +\t\treturn 0;\n> +\n> +\tif (sendbuf_size >= min_size)\n> +\t\treturn 0;\n> +\n> +\tfprintf(stderr, \"%s: max socket send buffer is %d, need at least %zu.\\n\",\n> +\t\tmo->msgtag, sendbuf_size, min_size);\n> +\treturn MOUNT_SERVICE_FALLBACK_NEEDED;\n> +}\n> +\n> +static int mount_service_connect(struct mount_service *mo)\n> +{\n> +\tstruct sockaddr_un name = {\n> +\t\t.sun_family = AF_UNIX,\n> +\t};\n> +\tint sockfd;\n> +\tssize_t written;\n> +\tint ret;\n> +\n> +\twritten = snprintf(name.sun_path, sizeof(name.sun_path),\n> +\t\t\tFUSE_SERVICE_SOCKET_DIR \"/%s\", mo->subtype);\n> +\tif (written >= sizeof(name.sun_path)) {\n> +\t\tfprintf(stderr, \"%s: filesystem type name `%s' is too long.\\n\",\n> +\t\t\tmo->msgtag, mo->subtype);\n> +\t\treturn -1;\n> +\t}\n> +\n> +\tsockfd = socket(AF_UNIX, SOCK_SEQPACKET | SOCK_CLOEXEC, 0);\n> +\tif (sockfd < 0) {\n> +\t\tint error = errno;\n> +\n> +\t\tfprintf(stderr, \"%s: opening %s service socket: %s\\n\",\n> +\t\t\tmo->msgtag, mo->subtype, strerror(error));\n> +\t\treturn -1;\n> +\t}\n> +\n> +\tret = check_sendbuf_size(mo, sockfd);\n> +\tif (ret)\n> +\t\treturn ret;\n> +\n> +\tret = connect(sockfd, (const struct sockaddr *)&name, sizeof(name));\n> +\tif (ret && (errno == ENOENT || errno == ECONNREFUSED)) {\n> +\t\tfprintf(stderr, \"%s: no safe filesystem driver for %s available.\\n\",\n> +\t\t\tmo->msgtag, mo->subtype);\n> +\t\tclose(sockfd);\n> +\t\treturn MOUNT_SERVICE_FALLBACK_NEEDED;\n> +\t}\n> +\tif (ret) {\n> +\t\tint error = errno;\n> +\n> +\t\tfprintf(stderr, \"%s: %s: %s\\n\",\n> +\t\t\tmo->msgtag, name.sun_path, strerror(error));\n> +\t\tgoto out;\n> +\t}\n> +\n> +\tret = try_drop_passrights(mo, sockfd);\n> +\tif (ret)\n> +\t\tgoto out;\n> +\n> +\tmo->sockfd = sockfd;\n> +\treturn 0;\n> +out:\n> +\tclose(sockfd);\n> +\treturn -1;\n> +}\n> +\n> +static int mount_service_send_hello(struct mount_service *mo)\n> +{\n> +\tstruct fuse_service_hello hello = {\n> +\t\t.p.magic = htonl(FUSE_SERVICE_HELLO_CMD),\n> +\t\t.min_version = htons(FUSE_SERVICE_MIN_PROTO),\n> +\t\t.max_version = htons(FUSE_SERVICE_MAX_PROTO),\n> +\t};\n> +\tstruct fuse_service_hello_reply reply = { };\n> +\tssize_t size;\n> +\n> +\tif (getuid() == 0)\n> +\t\thello.flags |= htonl(FUSE_SERVICE_FLAG_ALLOW_OTHER);\n> +\n> +\tsize = __send_packet(mo, &hello, sizeof(hello));\n> +\tif (size < 0) {\n> +\t\tfprintf(stderr, \"%s: send hello: %s\\n\",\n> +\t\t\tmo->msgtag, strerror(errno));\n> +\t\treturn -1;\n> +\t}\n> +\n> +\tsize = __recv_packet(mo, &reply, sizeof(reply));\n> +\tif (size < 0) {\n> +\t\tfprintf(stderr, \"%s: hello reply: %s\\n\",\n> +\t\t\tmo->msgtag, strerror(errno));\n> +\t\treturn -1;\n> +\t}\n> +\tif (size != sizeof(reply)) {\n> +\t\tfprintf(stderr, \"%s: wrong hello reply size %zd, expected %zu\\n\",\n> +\t\t\tmo->msgtag, size, sizeof(reply));\n> +\t\treturn -1;\n> +\t}\n> +\n> +\tif (ntohl(reply.p.magic) != FUSE_SERVICE_HELLO_REPLY) {\n> +\t\tfprintf(stderr, \"%s: %s service server did not reply to hello\\n\",\n> +\t\t\tmo->msgtag, mo->subtype);\n> +\t\treturn -1;\n> +\t}\n> +\n> +\tif (ntohs(reply.version) < FUSE_SERVICE_MIN_PROTO ||\n> +\t    ntohs(reply.version) > FUSE_SERVICE_MAX_PROTO) {\n> +\t\tfprintf(stderr, \"%s: unsupported protocol version %u\\n\",\n> +\t\t\tmo->msgtag, ntohs(reply.version));\n> +\t\treturn -1;\n> +\t}\n> +\n> +\tif (reply.padding) {\n> +\t\tfprintf(stderr, \"%s: nonzero value in padding field\\n\",\n> +\t\t\tmo->msgtag);\n> +\t\treturn -1;\n> +\t}\n> +\n> +\treturn 0;\n> +}\n> +\n> +static int mount_service_capture_arg(struct mount_service *mo,\n> +\t\t\t\t     struct fuse_service_memfd_argv *args,\n> +\t\t\t\t     const char *string, off_t *array_pos,\n> +\t\t\t\t     off_t *string_pos)\n> +{\n> +\tconst size_t string_len = strlen(string) + 1;\n> +\tstruct fuse_service_memfd_arg arg = {\n> +\t\t.pos = htonl(*string_pos),\n> +\t\t.len = htonl(string_len),\n> +\t};\n> +\tssize_t written;\n> +\n> +\twritten = pwrite(mo->argvfd, string, string_len, *string_pos);\n> +\tif (written < 0) {\n> +\t\tfprintf(stderr, \"%s: memfd argv write: %s\\n\",\n> +\t\t\tmo->msgtag, strerror(errno));\n> +\t\treturn -1;\n> +\t}\n> +\tif (written < string_len) {\n> +\t\tfprintf(stderr, \"%s: memfd argv[%u] wrote %zd, expected %zu\\n\",\n> +\t\t\tmo->msgtag, args->argc, written, string_len);\n> +\t\treturn -1;\n> +\t}\n> +\n> +\twritten = pwrite(mo->argvfd, &arg, sizeof(arg), *array_pos);\n> +\tif (written < 0) {\n> +\t\tfprintf(stderr, \"%s: memfd arg write: %s\\n\",\n> +\t\t\tmo->msgtag, strerror(errno));\n> +\t\treturn -1;\n> +\t}\n> +\tif (written < sizeof(arg)) {\n> +\t\tfprintf(stderr, \"%s: memfd arg[%u] wrote %zd, expected %zu\\n\",\n> +\t\t\tmo->msgtag, args->argc, written, sizeof(arg));\n> +\t\treturn -1;\n> +\t}\n> +\n> +\targs->argc++;\n> +\t*string_pos += string_len;\n> +\t*array_pos += sizeof(arg);\n> +\n> +\treturn 0;\n> +}\n> +\n> +static int mount_service_capture_args(struct mount_service *mo, int argc,\n> +\t\t\t\t      char *argv[])\n> +{\n> +\tstruct fuse_service_memfd_argv args = {\n> +\t\t.magic = htonl(FUSE_SERVICE_ARGS_MAGIC),\n> +\t};\n> +\toff_t array_pos = sizeof(struct fuse_service_memfd_argv);\n> +\toff_t string_pos = array_pos +\n> +\t\t\t(argc * sizeof(struct fuse_service_memfd_arg));\n> +\tssize_t written;\n> +\tint i;\n> +\tint ret;\n> +\n> +\tif (argc < 0) {\n> +\t\tfprintf(stderr, \"%s: argc cannot be negative\\n\",\n> +\t\t\tmo->msgtag);\n> +\t\treturn -1;\n> +\t}\n> +\n> +\t/*\n> +\t * Create the memfd in which we'll stash arguments, and set the write\n> +\t * pointer for the names.\n> +\t */\n> +\tmo->argvfd = memfd_create(\"fuse service argv\", MFD_CLOEXEC);\n> +\tif (mo->argvfd < 0) {\n> +\t\tfprintf(stderr, \"%s: argvfd create: %s\\n\",\n> +\t\t\tmo->msgtag, strerror(errno));\n> +\t\treturn -1;\n> +\t}\n> +\n> +\t/*\n> +\t * Write the alleged subtype as if it were argv[0], then write the rest\n> +\t * of the argv arguments.\n> +\t */\n> +\tret = mount_service_capture_arg(mo, &args, mo->subtype, &array_pos,\n> +\t\t\t\t\t&string_pos);\n> +\tif (ret)\n> +\t\treturn ret;\n> +\n> +\tfor (i = 1; i < argc; i++) {\n> +\t\t/* skip the -t(ype) argument */\n> +\t\tif (!strcmp(argv[i], \"-t\") && i + 1 < argc) {\n> +\t\t\ti++;\n> +\t\t\tcontinue;\n> +\t\t}\n> +\n> +\t\tret = mount_service_capture_arg(mo, &args, argv[i],\n> +\t\t\t\t\t\t&array_pos, &string_pos);\n> +\t\tif (ret)\n> +\t\t\treturn ret;\n> +\t}\n> +\n> +\t/* Now write the header */\n> +\targs.argc = htonl(args.argc);\n> +\twritten = pwrite(mo->argvfd, &args, sizeof(args), 0);\n> +\tif (written < 0) {\n> +\t\tfprintf(stderr, \"%s: memfd argv write: %s\\n\",\n> +\t\t\tmo->msgtag, strerror(errno));\n> +\t\treturn -1;\n> +\t}\n> +\tif (written < sizeof(args)) {\n> +\t\tfprintf(stderr, \"%s: memfd argv wrote %zd, expected %zu\\n\",\n> +\t\t\tmo->msgtag, written, sizeof(args));\n> +\t\treturn -1;\n> +\t}\n> +\n> +\treturn 0;\n> +}\n> +\n> +static int mount_service_send_file(struct mount_service *mo,\n> +\t\t\t\t   const char *path, int fd)\n> +{\n> +\tstruct fuse_service_requested_file *req;\n> +\tconst size_t req_sz =\n> +\t\t\tsizeof_fuse_service_requested_file(strlen(path));\n> +\tssize_t written;\n> +\tint ret = 0;\n> +\n> +\treq = calloc(1, req_sz);\n> +\tif (!req) {\n> +\t\tfprintf(stderr, \"%s: alloc send file reply: %s\\n\",\n> +\t\t\tmo->msgtag, strerror(errno));\n> +\t\treturn -1;\n> +\t}\n> +\treq->p.magic = htonl(FUSE_SERVICE_OPEN_REPLY);\n> +\treq->error = 0;\n> +\tstrcpy(req->path, path);\n> +\n> +\twritten = __send_fd(mo, req, req_sz, fd);\n> +\tif (written < 0) {\n> +\t\tfprintf(stderr, \"%s: send file reply: %s\\n\",\n> +\t\t\tmo->msgtag, strerror(errno));\n> +\t\tret = -1;\n> +\t\tgoto out_req;\n> +\t}\n> +\tif (written < req_sz) {\n> +\t\tfprintf(stderr, \"%s: send file reply wrote %zd, expected %zu\\n\",\n> +\t\t\tmo->msgtag, written, req_sz);\n> +\t\tret = -1;\n> +\t\tgoto out_req;\n> +\t}\n> +\n> +out_req:\n> +\tfree(req);\n> +\treturn ret;\n> +}\n> +\n> +static int mount_service_send_file_error(struct mount_service *mo, int error,\n> +\t\t\t\t\t const char *path)\n> +{\n> +\tstruct fuse_service_requested_file *req;\n> +\tconst size_t req_sz =\n> +\t\t\tsizeof_fuse_service_requested_file(strlen(path));\n> +\tssize_t written;\n> +\tint ret = 0;\n> +\n> +\treq = calloc(1, req_sz);\n> +\tif (!req) {\n> +\t\tfprintf(stderr, \"%s: alloc send file error: %s\\n\",\n> +\t\t\tmo->msgtag, strerror(errno));\n> +\t\treturn -1;\n> +\t}\n> +\treq->p.magic = htonl(FUSE_SERVICE_OPEN_REPLY);\n> +\treq->error = htonl(error);\n> +\tstrcpy(req->path, path);\n> +\n> +\twritten = __send_packet(mo, req, req_sz);\n> +\tif (written < 0) {\n> +\t\tfprintf(stderr, \"%s: send file error: %s\\n\",\n> +\t\t\tmo->msgtag, strerror(errno));\n> +\t\tret = -1;\n> +\t\tgoto out_req;\n> +\t}\n> +\tif (written < req_sz) {\n> +\t\tfprintf(stderr, \"%s: send file error wrote %zd, expected %zu\\n\",\n> +\t\t\tmo->msgtag, written, req_sz);\n> +\t\tret = -1;\n> +\t\tgoto out_req;\n> +\t}\n> +\n> +out_req:\n> +\tfree(req);\n> +\treturn ret;\n> +}\n> +\n> +static int mount_service_send_required_files(struct mount_service *mo,\n> +\t\t\t\t\t     const char *fusedev)\n> +{\n> +\tint ret;\n> +\n> +\tmo->fusedevfd = open(fusedev, O_RDWR | O_CLOEXEC);\n> +\tif (mo->fusedevfd < 0) {\n> +\t\tint error = errno;\n> +\n> +\t\tfprintf(stderr, \"%s: %s: %s\\n\",\n> +\t\t\tmo->msgtag, fusedev, strerror(error));\n> +\t\treturn -1;\n> +\t}\n> +\n> +\tret = mount_service_send_file(mo, FUSE_SERVICE_ARGV, mo->argvfd);\n> +\tif (ret)\n> +\t\tgoto out_fusedevfd;\n> +\n> +\tclose(mo->argvfd);\n> +\tmo->argvfd = -1;\n> +\n> +\treturn mount_service_send_file(mo, FUSE_SERVICE_FUSEDEV,\n> +\t\t\t\t       mo->fusedevfd);\n> +\n> +out_fusedevfd:\n> +\tclose(mo->fusedevfd);\n> +\tmo->fusedevfd = -1;\n> +\treturn ret;\n> +}\n> +\n> +static int mount_service_receive_command(struct mount_service *mo,\n> +\t\t\t\t\t struct fuse_service_packet **commandp,\n> +\t\t\t\t\t size_t *commandsz)\n> +{\n> +\tstruct fuse_service_packet *command;\n> +\tssize_t alleged_size, size;\n> +\n> +\talleged_size = __recv_packet_size(mo);\n> +\tif (alleged_size < 0) {\n> +\t\tfprintf(stderr, \"%s: peek service command: %s\\n\",\n> +\t\t\tmo->msgtag, strerror(errno));\n> +\t\treturn -1;\n> +\t}\n> +\tif (alleged_size == 0) {\n> +\t\t/* fuse server probably exited early */\n> +\t\tfprintf(stderr, \"%s: fuse server exited without saying goodbye!\\n\",\n> +\t\t\tmo->msgtag);\n> +\t\treturn -1;\n> +\t}\n> +\tif (alleged_size < sizeof(struct fuse_service_packet)) {\n> +\t\tfprintf(stderr, \"%s: wrong command packet size %zd, expected at least %zu\\n\",\n> +\t\t\tmo->msgtag, alleged_size,\n> +\t\t\tsizeof(struct fuse_service_packet));\n> +\t\treturn -1;\n> +\t}\n> +\tif (alleged_size > FUSE_SERVICE_MAX_CMD_SIZE) {\n> +\t\tfprintf(stderr, \"%s: wrong command packet size %zd, expected less than %d\\n\",\n> +\t\t\tmo->msgtag, alleged_size, FUSE_SERVICE_MAX_CMD_SIZE);\n> +\t\treturn -1;\n> +\t}\n> +\n> +\tcommand = calloc(1, alleged_size + 1);\n> +\tif (!command) {\n> +\t\tfprintf(stderr, \"%s: alloc service command: %s\\n\",\n> +\t\t\tmo->msgtag, strerror(errno));\n> +\t\treturn -1;\n> +\t}\n> +\n> +\tsize = __recv_packet(mo, command, alleged_size);\n> +\tif (size < 0) {\n> +\t\tfprintf(stderr, \"%s: receive service command: %s\\n\",\n> +\t\t\tmo->msgtag, strerror(errno));\n> +\t\tfree(command);\n> +\t\treturn -1;\n> +\t}\n> +\tif (size != alleged_size) {\n> +\t\tfprintf(stderr, \"%s: wrong service command size %zd, expected %zd\\n\",\n> +\t\t\tmo->msgtag, size, alleged_size);\n> +\t\tfree(command);\n> +\t\treturn -1;\n> +\t}\n> +\n> +\t*commandp = command;\n> +\t*commandsz = size;\n> +\treturn 0;\n> +}\n> +\n> +static int mount_service_send_reply(struct mount_service *mo, int error)\n> +{\n> +\tstruct fuse_service_simple_reply reply = {\n> +\t\t.p.magic = htonl(FUSE_SERVICE_SIMPLE_REPLY),\n> +\t\t.error = htonl(error),\n> +\t};\n> +\tssize_t size;\n> +\n> +\tsize = __send_packet(mo, &reply, sizeof(reply));\n> +\tif (size < 0) {\n> +\t\tfprintf(stderr, \"%s: send service reply: %s\\n\",\n> +\t\t\tmo->msgtag, strerror(errno));\n> +\t\treturn -1;\n> +\t}\n> +\n> +\treturn 0;\n> +}\n> +\n> +static int prepare_bdev(struct mount_service *mo,\n> +\t\t\tstruct fuse_service_open_command *oc, int fd)\n> +{\n> +\tstruct stat stbuf;\n> +\tint ret;\n> +\n> +\tret = fstat(fd, &stbuf);\n> +\tif (ret) {\n> +\t\tint error = errno;\n> +\n> +\t\tfprintf(stderr, \"%s: %s: %s\\n\",\n> +\t\t\tmo->msgtag, oc->path, strerror(error));\n> +\t\treturn -error;\n> +\t}\n> +\n> +\tif (!S_ISBLK(stbuf.st_mode)) {\n> +\t\tfprintf(stderr, \"%s: %s: %s\\n\",\n> +\t\t\tmo->msgtag, oc->path, strerror(ENOTBLK));\n> +\t\treturn -ENOTBLK;\n> +\t}\n> +\n> +\tif (oc->block_size) {\n> +\t\tint block_size = ntohl(oc->block_size);\n> +\n> +\t\tret = ioctl(fd, BLKBSZSET, &block_size);\n> +\t\tif (ret) {\n> +\t\t\tint error = errno;\n> +\n> +\t\t\tfprintf(stderr, \"%s: %s: %s\\n\",\n> +\t\t\t\tmo->msgtag, oc->path, strerror(error));\n> +\t\t\treturn -error;\n> +\t\t}\n> +\t}\n> +\n> +\treturn 0;\n> +}\n> +\n> +static int mount_service_open_path(struct mount_service *mo,\n> +\t\t\t\t   mode_t expected_fmt,\n> +\t\t\t\t   struct fuse_service_packet *p, size_t psz)\n> +{\n> +\tstruct fuse_service_open_command *oc =\n> +\t\t\tcontainer_of(p, struct fuse_service_open_command, p);\n> +\tuint32_t request_flags;\n> +\tint open_flags;\n> +\tint ret;\n> +\tint fd;\n> +\n> +\tif (psz < sizeof_fuse_service_open_command(1)) {\n> +\t\tfprintf(stderr, \"%s: open command too small\\n\",\n> +\t\t\tmo->msgtag);\n> +\t\treturn mount_service_send_file_error(mo, EINVAL, \"?\");\n> +\t}\n> +\n> +\tif (!check_null_endbyte(p, psz)) {\n> +\t\tfprintf(stderr, \"%s: open command must be null terminated\\n\",\n> +\t\t\tmo->msgtag);\n> +\t\treturn mount_service_send_file_error(mo, EINVAL, \"?\");\n> +\t}\n> +\n> +\trequest_flags = ntohl(oc->request_flags);\n> +\tif (request_flags & ~FUSE_SERVICE_OPEN_FLAGS) {\n> +\t\tfprintf(stderr, \"%s: open flags 0x%x not recognized\\n\",\n> +\t\t\tmo->msgtag, request_flags & ~FUSE_SERVICE_OPEN_FLAGS);\n> +\t\treturn mount_service_send_file_error(mo, EINVAL, oc->path);\n> +\t}\n> +\n> +\topen_flags = ntohl(oc->open_flags) | O_CLOEXEC;\n> +\tfd = open(oc->path, open_flags, ntohl(oc->create_mode));\n> +\tif (fd < 0) {\n> +\t\tint error = errno;\n> +\n> +\t\t/*\n> +\t\t * Don't print a busy device error report because the\n> +\t\t * filesystem might decide to retry.\n> +\t\t */\n> +\t\tif (error != EBUSY && !(request_flags & FUSE_SERVICE_OPEN_QUIET))\n> +\t\t\tfprintf(stderr, \"%s: %s: %s\\n\",\n> +\t\t\t\tmo->msgtag, oc->path, strerror(error));\n> +\t\treturn mount_service_send_file_error(mo, error, oc->path);\n> +\t}\n> +\n> +\tif (S_ISBLK(expected_fmt)) {\n> +\t\tret = prepare_bdev(mo, oc, fd);\n> +\t\tif (ret < 0) {\n> +\t\t\tclose(fd);\n> +\t\t\treturn mount_service_send_file_error(mo, -ret,\n> +\t\t\t\t\t\t\t     oc->path);\n> +\t\t}\n> +\t}\n> +\n> +\tret = mount_service_send_file(mo, oc->path, fd);\n> +\tclose(fd);\n> +\treturn ret;\n> +}\n> +\n> +static int mount_service_handle_open_cmd(struct mount_service *mo,\n> +\t\t\t\t\t struct fuse_service_packet *p,\n> +\t\t\t\t\t size_t psz)\n> +{\n> +\treturn mount_service_open_path(mo, 0, p, psz);\n> +}\n> +\n> +static int mount_service_handle_open_bdev_cmd(struct mount_service *mo,\n> +\t\t\t\t\t      struct fuse_service_packet *p,\n> +\t\t\t\t\t      size_t psz)\n> +{\n> +\treturn mount_service_open_path(mo, S_IFBLK, p, psz);\n> +}\n> +\n> +static inline const char *fsname(const struct mount_service *mo)\n> +{\n> +\treturn mo->fuseblk ? \"fuseblk\" : \"fuse\";\n> +}\n> +\n> +static int mount_service_handle_fsopen_cmd(struct mount_service *mo,\n> +\t\t\t\t\t   const struct fuse_service_packet *p,\n> +\t\t\t\t\t   size_t psz)\n> +{\n> +\tstruct fuse_service_fsopen_command *oc =\n> +\t\t\tcontainer_of(p, struct fuse_service_fsopen_command, p);\n> +\tuint32_t fsopen_flags;\n> +\n> +\tif (psz != sizeof(struct fuse_service_fsopen_command)) {\n> +\t\tfprintf(stderr, \"%s: fsopen command wrong size %zu, expected %zu\\n\",\n> +\t\t\tmo->msgtag, psz, sizeof(*oc));\n> +\t\treturn mount_service_send_reply(mo, EINVAL);\n> +\t}\n> +\n> +\tif (mo->fsopened) {\n> +\t\tfprintf(stderr, \"%s: fsopen command respecified\\n\",\n> +\t\t\tmo->msgtag);\n> +\t\treturn mount_service_send_reply(mo, EINVAL);\n> +\t}\n> +\n> +\tfsopen_flags = ntohl(oc->fsopen_flags);\n> +\tif (fsopen_flags & ~FUSE_SERVICE_FSOPEN_FLAGS) {\n> +\t\tfprintf(stderr, \"%s: unknown fsopen flags, 0x%x\\n\",\n> +\t\t\tmo->msgtag, fsopen_flags & ~FUSE_SERVICE_FSOPEN_FLAGS);\n> +\t\treturn mount_service_send_reply(mo, EINVAL);\n> +\t}\n> +\n> +\tif (fsopen_flags & FUSE_SERVICE_FSOPEN_FUSEBLK) {\n> +\t\tif (getuid() != 0) {\n> +\t\t\tfprintf(stderr, \"%s: fuseblk requires root privilege\\n\",\n> +\t\t\t\tmo->msgtag);\n> +\t\t\treturn mount_service_send_reply(mo, EPERM);\n> +\t\t}\n> +\n> +\t\tmo->fuseblk = true;\n> +\t}\n> +\tmo->fsopened = true;\n> +\n> +\treturn mount_service_send_reply(mo, 0);\n> +}\n> +\n> +static int mount_service_handle_source_cmd(struct mount_service *mo,\n> +\t\t\t\t\t   const struct fuse_service_packet *p,\n> +\t\t\t\t\t   size_t psz)\n> +{\n> +\tstruct fuse_service_string_command *oc =\n> +\t\t\tcontainer_of(p, struct fuse_service_string_command, p);\n> +\n> +\tif (psz < sizeof_fuse_service_string_command(1)) {\n> +\t\tfprintf(stderr, \"%s: source command too small\\n\",\n> +\t\t\tmo->msgtag);\n> +\t\treturn mount_service_send_reply(mo, EINVAL);\n> +\t}\n> +\n> +\tif (!check_null_endbyte(p, psz)) {\n> +\t\tfprintf(stderr, \"%s: source command must be null terminated\\n\",\n> +\t\t\tmo->msgtag);\n> +\t\treturn mount_service_send_reply(mo, EINVAL);\n> +\t}\n> +\n> +\tif (mo->source) {\n> +\t\tfprintf(stderr, \"%s: source respecified!\\n\",\n> +\t\t\tmo->msgtag);\n> +\t\treturn mount_service_send_reply(mo, EINVAL);\n> +\t}\n> +\n> +\tmo->source = strdup(oc->value);\n> +\tif (!mo->source) {\n> +\t\tint error = errno;\n> +\n> +\t\tfprintf(stderr, \"%s: alloc source string: %s\\n\",\n> +\t\t\tmo->msgtag, strerror(error));\n> +\t\treturn mount_service_send_reply(mo, error);\n> +\t}\n> +\n> +\treturn mount_service_send_reply(mo, 0);\n> +}\n> +\n> +static int mount_service_handle_mntopts_cmd(struct mount_service *mo,\n> +\t\t\t\t\t    const struct fuse_service_packet *p,\n> +\t\t\t\t\t    size_t psz)\n> +{\n> +\tstruct fuse_service_string_command *oc =\n> +\t\t\tcontainer_of(p, struct fuse_service_string_command, p);\n> +\n> +\tif (psz < sizeof_fuse_service_string_command(1)) {\n> +\t\tfprintf(stderr, \"%s: mount options command too small\\n\",\n> +\t\t\tmo->msgtag);\n> +\t\treturn mount_service_send_reply(mo, EINVAL);\n> +\t}\n> +\n> +\tif (!check_null_endbyte(p, psz)) {\n> +\t\tfprintf(stderr, \"%s: mount options command must be null terminated\\n\",\n> +\t\t\tmo->msgtag);\n> +\t\treturn mount_service_send_reply(mo, EINVAL);\n> +\t}\n> +\n> +\tif (mo->mntopts) {\n> +\t\tfprintf(stderr, \"%s: mount options respecified!\\n\",\n> +\t\t\tmo->msgtag);\n> +\t\treturn mount_service_send_reply(mo, EINVAL);\n> +\t}\n> +\n> +\tmo->mntopts = strdup(oc->value);\n> +\tif (!mo->mntopts) {\n> +\t\tint error = errno;\n> +\n> +\t\tfprintf(stderr, \"%s: alloc mount options string: %s\\n\",\n> +\t\t\tmo->msgtag, strerror(error));\n> +\t\treturn mount_service_send_reply(mo, error);\n> +\t}\n> +\n> +\treturn mount_service_send_reply(mo, 0);\n> +}\n> +\n> +static int attach_to_mountpoint(struct mount_service *mo, mode_t expected_fmt,\n> +\t\t\t\tchar *mntpt)\n> +{\n> +\tstruct stat stbuf;\n> +\tchar *res_mntpt;\n> +\tint mountfd = -1;\n> +\tint error;\n> +\tint ret;\n> +\n> +\t/*\n> +\t * Open the alleged mountpoint, make sure it's a dir or a file.\n> +\t */\n> +\tmountfd = open(mntpt, O_RDONLY | O_CLOEXEC);\n> +\tif (mountfd < 0) {\n> +\t\terror = errno;\n> +\t\tfprintf(stderr, \"%s: %s: %s\\n\", mo->msgtag, mntpt,\n> +\t\t\tstrerror(error));\n> +\t\tgoto out_error;\n> +\t}\n> +\n> +\t/*\n> +\t * Make sure we can access the mountpoint and that it's either a\n> +\t * directory or a regular file.  Linux can handle mounting atop special\n> +\t * files, but we don't care to do such crazy things.\n> +\t */\n> +\tret = fstat(mountfd, &stbuf);\n> +\tif (ret) {\n> +\t\terror = errno;\n> +\t\tfprintf(stderr, \"%s: %s: %s\\n\", mo->msgtag, mntpt,\n> +\t\t\tstrerror(error));\n> +\t\tgoto out_mountfd;\n> +\t}\n> +\n> +\tif (!S_ISDIR(stbuf.st_mode) && !S_ISREG(stbuf.st_mode)) {\n> +\t\terror = EACCES;\n> +\t\tfprintf(stderr, \"%s: %s: Mount point must be directory or regular file.\\n\",\n> +\t\t\tmo->msgtag, mntpt);\n> +\t\tgoto out_mountfd;\n> +\t}\n> +\n> +\t/*\n> +\t * Resolve the (possibly relative) mountpoint path before chdir'ing\n> +\t * onto it.\n> +\t */\n> +\tres_mntpt = fuse_mnt_resolve_path(mo->msgtag, mntpt);\n> +\tif (!res_mntpt) {\n> +\t\terror = EACCES;\n> +\t\tfprintf(stderr, \"%s: %s: Could not resolve path to mount point.\\n\",\n> +\t\t\tmo->msgtag, mntpt);\n> +\t\tgoto out_mountfd;\n> +\t}\n> +\n> +\t/* Make sure the mountpoint type matches what the caller wanted */\n> +\tswitch (expected_fmt) {\n> +\tcase S_IFDIR:\n> +\t\tif (!S_ISDIR(stbuf.st_mode)) {\n> +\t\t\terror = ENOTDIR;\n> +\t\t\tfprintf(stderr, \"%s: %s: %s\\n\",\n> +\t\t\t\tmo->msgtag, mntpt, strerror(error));\n> +\t\t\tgoto out_res_mntpt;\n> +\t\t}\n> +\t\tbreak;\n> +\tcase S_IFREG:\n> +\t\tif (!S_ISREG(stbuf.st_mode)) {\n> +\t\t\terror = EISDIR;\n> +\t\t\tfprintf(stderr, \"%s: %s: %s\\n\",\n> +\t\t\t\tmo->msgtag, mntpt, strerror(error));\n> +\t\t\tgoto out_res_mntpt;\n> +\t\t}\n> +\t\tbreak;\n> +\t}\n> +\n> +\tswitch (stbuf.st_mode & S_IFMT) {\n> +\tcase S_IFREG:\n> +\t\t/*\n> +\t\t * This is a regular file, so we point mount() at the open file\n> +\t\t * descriptor.\n> +\t\t */\n> +\t\tasprintf(&mo->real_mountpoint, \"/dev/fd/%d\", mountfd);\n> +\t\tbreak;\n> +\tcase S_IFDIR:\n> +\t\t/*\n> +\t\t * Pin the mount so it can't go anywhere.  This only works for\n> +\t\t * directories, which is fortunately the common case.\n> +\t\t */\n> +\t\tret = fchdir(mountfd);\n> +\t\tif (ret) {\n> +\t\t\terror = errno;\n> +\t\t\tfprintf(stderr, \"%s: %s: %s\\n\", mo->msgtag, mntpt,\n> +\t\t\t\tstrerror(error));\n> +\t\t\tgoto out_res_mntpt;\n> +\t\t}\n> +\n> +\t\t/*\n> +\t\t * Now that we're sitting on the mountpoint directory, we can\n> +\t\t * pass \".\" to mount() and avoid races with directory tree\n> +\t\t * mutations.\n> +\t\t */\n> +\t\tmo->real_mountpoint = strdup(\".\");\n> +\t\tbreak;\n> +\tdefault:\n> +\t\t/* Should never get here */\n> +\t\terror = EINVAL;\n> +\t\tgoto out_res_mntpt;\n> +\t}\n> +\tif (!mo->real_mountpoint) {\n> +\t\terror = ENOMEM;\n> +\t\tfprintf(stderr, \"%s: %s: %s\\n\", mo->msgtag, mntpt,\n> +\t\t\tstrerror(error));\n> +\t\tgoto out_res_mntpt;\n> +\t}\n> +\n> +\tmo->mountpoint = mntpt;\n> +\tmo->mountfd = mountfd;\n> +\tmo->resv_mountpoint = res_mntpt;\n> +\n> +\treturn mount_service_send_reply(mo, 0);\n> +\n> +out_res_mntpt:\n> +\tfree(res_mntpt);\n> +out_mountfd:\n> +\tclose(mountfd);\n> +out_error:\n> +\tfree(mntpt);\n> +\treturn mount_service_send_reply(mo, error);\n> +}\n> +\n> +static int mount_service_handle_mountpoint_cmd(struct mount_service *mo,\n> +\t\t\t\t\t       const struct fuse_service_packet *p,\n> +\t\t\t\t\t       size_t psz, int argc, char *argv[])\n> +{\n> +\tstruct fuse_service_mountpoint_command *oc =\n> +\t\t\tcontainer_of(p, struct fuse_service_mountpoint_command, p);\n> +\tchar *mntpt;\n> +\tmode_t expected_fmt;\n> +\tbool foundit = false;\n> +\tint i;\n> +\n> +\tif (psz < sizeof_fuse_service_mountpoint_command(1)) {\n> +\t\tfprintf(stderr, \"%s: mount point command too small\\n\",\n> +\t\t\tmo->msgtag);\n> +\t\treturn mount_service_send_reply(mo, EINVAL);\n> +\t}\n> +\n> +\tif (!check_null_endbyte(p, psz)) {\n> +\t\tfprintf(stderr, \"%s: mount point command must be null terminated\\n\",\n> +\t\t\tmo->msgtag);\n> +\t\treturn mount_service_send_reply(mo, EINVAL);\n> +\t}\n> +\n> +\tif (oc->padding) {\n> +\t\tfprintf(stderr, \"%s: nonzero value in padding field\\n\",\n> +\t\t\tmo->msgtag);\n> +\t\treturn mount_service_send_reply(mo, EINVAL);\n> +\t}\n> +\n> +\tif (mo->mountpoint) {\n> +\t\tfprintf(stderr, \"%s: mount point respecified!\\n\",\n> +\t\t\tmo->msgtag);\n> +\t\treturn mount_service_send_reply(mo, EINVAL);\n> +\t}\n> +\n> +\t/* Make sure the mountpoint file format matches what the caller wanted */\n> +\texpected_fmt = ntohs(oc->expected_fmt);\n> +\tswitch (expected_fmt) {\n> +\tcase S_IFDIR:\n> +\tcase S_IFREG:\n> +\tcase 0:\n> +\t\tbreak;\n> +\tdefault:\n> +\t\tfprintf(stderr, \"%s: %s: weird expected format 0%o\\n\",\n> +\t\t\tmo->msgtag, oc->value, expected_fmt);\n> +\t\treturn mount_service_send_reply(mo, EINVAL);\n> +\t}\n> +\n> +\t/* Mountpoint must be mentioned in the caller's argument list */\n> +\tfor (i = 0; i < argc; i++) {\n> +\t\tif (!strcmp(argv[i], oc->value)) {\n> +\t\t\tfoundit = true;\n> +\t\t\tbreak;\n> +\t\t}\n> +\t}\n> +\tif (!foundit) {\n> +\t\tfprintf(stderr, \"%s: mount point must be in command line arguments\\n\",\n> +\t\t\tmo->msgtag);\n> +\t\treturn mount_service_send_reply(mo, EINVAL);\n> +\t}\n> +\n> +\tmntpt = strdup(oc->value);\n> +\tif (!mntpt) {\n> +\t\tint error = errno;\n> +\n> +\t\tfprintf(stderr, \"%s: alloc mount point string: %s\\n\",\n> +\t\t\tmo->msgtag, strerror(error));\n> +\t\treturn mount_service_send_reply(mo, error);\n> +\t}\n> +\n> +\treturn attach_to_mountpoint(mo, expected_fmt, mntpt);\n> +}\n> +\n> +static inline int format_libfuse_mntopts(char *buf, size_t bufsz,\n> +\t\t\t\t\t const struct mount_service *mo,\n> +\t\t\t\t\t const struct stat *stbuf)\n> +{\n> +\tif (mo->mntopts)\n> +\t\treturn snprintf(buf, bufsz,\n> +\t\t\t\t\"%s,fd=%i,rootmode=%o,user_id=%u,group_id=%u\",\n> +\t\t\t\tmo->mntopts, mo->fusedevfd,\n> +\t\t\t\tstbuf->st_mode & S_IFMT,\n> +\t\t\t\tgetuid(), getgid());\n> +\n> +\treturn snprintf(buf, bufsz,\n> +\t\t\t\"fd=%i,rootmode=%o,user_id=%u,group_id=%u\",\n> +\t\t\tmo->fusedevfd, stbuf->st_mode & S_IFMT,\n> +\t\t\tgetuid(), getgid());\n> +}\n> +\n> +static int mount_service_regular_mount(struct mount_service *mo,\n> +\t\t\t\t       struct fuse_service_mount_command *oc,\n> +\t\t\t\t       struct stat *stbuf)\n> +{\n> +\tchar *fstype = NULL;\n> +\tchar *realmopts;\n> +\tint ret;\n> +\n> +\t/* Compute the amount of buffer space needed for the mount options */\n> +\tret = format_libfuse_mntopts(NULL, 0, mo, stbuf);\n> +\tif (ret < 0) {\n> +\t\tint error = errno;\n> +\n> +\t\tfprintf(stderr, \"%s: mount option preformatting: %s\\n\",\n> +\t\t\tmo->msgtag, strerror(error));\n> +\t\treturn mount_service_send_reply(mo, error);\n> +\t}\n> +\n> +\trealmopts = calloc(1, ret + 1);\n> +\tif (!realmopts) {\n> +\t\tint error = errno;\n> +\n> +\t\tfprintf(stderr, \"%s: alloc real mount options string: %s\\n\",\n> +\t\t\tmo->msgtag, strerror(error));\n> +\t\treturn mount_service_send_reply(mo, error);\n> +\t}\n> +\n> +\tret = format_libfuse_mntopts(realmopts, ret + 1, mo, stbuf);\n> +\tif (ret < 0) {\n> +\t\tint error = errno;\n> +\n> +\t\tfprintf(stderr, \"%s: mount options formatting: %s\\n\",\n> +\t\t\tmo->msgtag, strerror(error));\n> +\t\tret = mount_service_send_reply(mo, error);\n> +\t\tgoto out_realmopts;\n> +\t}\n> +\n> +\tasprintf(&fstype, \"%s.%s\", fsname(mo), mo->subtype);\n> +\tif (!fstype) {\n> +\t\tint error = errno;\n> +\n> +\t\tfprintf(stderr, \"%s: mount fstype formatting: %s\\n\",\n> +\t\t\tmo->msgtag, strerror(error));\n> +\t\tret = mount_service_send_reply(mo, error);\n> +\t\tgoto out_realmopts;\n> +\t}\n> +\n> +\tret = mount(mo->source, mo->real_mountpoint, fstype,\n> +\t\t    ntohl(oc->ms_flags), realmopts);\n> +\tif (ret) {\n> +\t\tint error = errno;\n> +\n> +\t\tfprintf(stderr, \"%s: mount: %s\\n\",\n> +\t\t\tmo->msgtag, strerror(error));\n> +\t\tret = mount_service_send_reply(mo, error);\n> +\t\tgoto out_fstype;\n> +\t}\n> +\n> +\tmo->mounted = true;\n> +\tret = mount_service_send_reply(mo, 0);\n> +out_fstype:\n> +\tfree(fstype);\n> +out_realmopts:\n> +\tfree(realmopts);\n> +\treturn ret;\n> +}\n> +\n> +static int mount_service_handle_mount_cmd(struct mount_service *mo,\n> +\t\t\t\t\t  struct fuse_service_packet *p,\n> +\t\t\t\t\t  size_t psz)\n> +{\n> +\tstruct stat stbuf;\n> +\tstruct fuse_service_mount_command *oc =\n> +\t\t\tcontainer_of(p, struct fuse_service_mount_command, p);\n> +\tint ret;\n> +\n> +\tif (psz != sizeof(struct fuse_service_mount_command)) {\n> +\t\tfprintf(stderr, \"%s: mount command wrong size %zu, expected %zu\\n\",\n> +\t\t\tmo->msgtag, psz, sizeof(*oc));\n> +\t\treturn mount_service_send_reply(mo, EINVAL);\n> +\t}\n> +\n> +\tif (!mo->source) {\n> +\t\tfprintf(stderr, \"%s: missing mount source parameter\\n\",\n> +\t\t\tmo->msgtag);\n> +\t\treturn mount_service_send_reply(mo, EINVAL);\n> +\t}\n> +\n> +\tif (!mo->mountpoint) {\n> +\t\tfprintf(stderr, \"%s: missing mount point parameter\\n\",\n> +\t\t\tmo->msgtag);\n> +\t\treturn mount_service_send_reply(mo, EINVAL);\n> +\t}\n> +\n> +\t/*\n> +\t * Call fstat again because access modes might have changed since we\n> +\t * validated the file type.  This is still racy with mount since we\n> +\t * don't lock the path target.\n> +\t */\n> +\tret = fstat(mo->mountfd, &stbuf);\n> +\tif (ret < 0) {\n> +\t\tint error = errno;\n> +\n> +\t\tfprintf(stderr, \"%s: %s: %s\\n\",\n> +\t\t\tmo->msgtag, mo->mountpoint, strerror(error));\n> +\t\treturn mount_service_send_reply(mo, error);\n> +\t}\n> +\n> +\treturn mount_service_regular_mount(mo, oc, &stbuf);\n> +}\n> +\n> +static int mount_service_handle_unmount_cmd(struct mount_service *mo,\n> +\t\t\t\t\t    struct fuse_service_packet *p,\n> +\t\t\t\t\t    size_t psz)\n> +{\n> +\tint ret;\n> +\n> +\t(void)p;\n> +\n> +\tif (psz != sizeof(struct fuse_service_unmount_command)) {\n> +\t\tfprintf(stderr, \"%s: unmount command wrong size %zu, expected %zu\\n\",\n> +\t\t\tmo->msgtag, psz, sizeof(struct fuse_service_unmount_command));\n> +\t\treturn mount_service_send_reply(mo, EINVAL);\n> +\t}\n> +\n> +\tif (!mo->mounted) {\n> +\t\tfprintf(stderr, \"%s: will not umount before successful mount!\\n\",\n> +\t\t\tmo->msgtag);\n> +\t\treturn mount_service_send_reply(mo, EINVAL);\n> +\t}\n> +\n> +\tret = chdir(\"/\");\n> +\tif (ret) {\n> +\t\tint error = errno;\n> +\n> +\t\tfprintf(stderr, \"%s: fuse server failed chdir: %s\\n\",\n> +\t\t\tmo->msgtag, strerror(error));\n> +\t\treturn mount_service_send_reply(mo, error);\n> +\t}\n> +\n> +\tclose(mo->mountfd);\n> +\tmo->mountfd = -1;\n> +\n> +\t/*\n> +\t * Try to unmount the resolved mountpoint, and hope that we're not the\n> +\t * victim of a race.\n> +\t */\n> +\tret = umount2(mo->resv_mountpoint, MNT_DETACH);\n> +\tif (ret) {\n> +\t\tint error = errno;\n> +\n> +\t\tfprintf(stderr, \"%s: fuse server failed unmount: %s\\n\",\n> +\t\t\tmo->msgtag, strerror(error));\n> +\t\treturn mount_service_send_reply(mo, error);\n> +\t}\n> +\n> +\tmo->mounted = false;\n> +\treturn mount_service_send_reply(mo, 0);\n> +}\n> +\n> +static int mount_service_handle_bye_cmd(struct mount_service *mo,\n> +\t\t\t\t\tstruct fuse_service_packet *p,\n> +\t\t\t\t\tsize_t psz)\n> +{\n> +\tstruct fuse_service_bye_command *bc =\n> +\t\t\tcontainer_of(p, struct fuse_service_bye_command, p);\n> +\tint ret;\n> +\n> +\tif (psz != sizeof(struct fuse_service_bye_command)) {\n> +\t\tfprintf(stderr, \"%s: bye command wrong size %zu, expected %zu\\n\",\n> +\t\t\tmo->msgtag, psz, sizeof(*bc));\n> +\t\treturn mount_service_send_reply(mo, EINVAL);\n> +\t}\n> +\n> +\tret = ntohl(bc->exitcode);\n> +\tif (ret)\n> +\t\tfprintf(stderr, \"%s: fuse server failed mount, check dmesg/logs for details.\\n\",\n> +\t\t\tmo->msgtag);\n> +\n> +\treturn ret;\n> +}\n> +\n> +static void mount_service_destroy(struct mount_service *mo)\n> +{\n> +\tclose(mo->mountfd);\n> +\tclose(mo->fusedevfd);\n> +\tclose(mo->argvfd);\n> +\tshutdown(mo->sockfd, SHUT_RDWR);\n> +\tclose(mo->sockfd);\n> +\n> +\tfree(mo->source);\n> +\tfree(mo->mountpoint);\n> +\tfree(mo->real_mountpoint);\n> +\tfree(mo->resv_mountpoint);\n> +\tfree(mo->mntopts);\n> +\tfree(mo->subtype);\n> +\n> +\tmemset(mo, 0, sizeof(*mo));\n> +\tmo->sockfd = -1;\n> +\tmo->argvfd = -1;\n> +\tmo->fusedevfd = -1;\n> +\tmo->mountfd = -1;\n> +}\n> +\n> +int mount_service_main(int argc, char *argv[])\n> +{\n> +\tconst char *fusedev = fuse_mnt_get_devname();\n> +\tstruct mount_service mo = { };\n> +\tbool running = true;\n> +\tint ret;\n> +\n> +\tif (argc < 3 || !strcmp(argv[1], \"--help\")) {\n> +\t\tprintf(\"Usage: %s source mountpoint -t type [-o options]\\n\",\n> +\t\t\t\targv[0]);\n> +\t\treturn EXIT_FAILURE;\n> +\t}\n> +\n> +\tif (argc > 0 && argv[0])\n> +\t\tmo.msgtag = argv[0];\n> +\telse\n> +\t\tmo.msgtag = \"mount.service\";\n> +\n> +\tret = mount_service_init(&mo, argc, argv);\n> +\tif (ret)\n> +\t\treturn EXIT_FAILURE;\n> +\n> +\tret = mount_service_connect(&mo);\n> +\tif (ret == MOUNT_SERVICE_FALLBACK_NEEDED)\n> +\t\tgoto out;\n> +\tif (ret) {\n> +\t\tret = EXIT_FAILURE;\n> +\t\tgoto out;\n> +\t}\n> +\n> +\tret = mount_service_send_hello(&mo);\n> +\tif (ret) {\n> +\t\tret = EXIT_FAILURE;\n> +\t\tgoto out;\n> +\t}\n> +\n> +\tret = mount_service_capture_args(&mo, argc, argv);\n> +\tif (ret) {\n> +\t\tret = EXIT_FAILURE;\n> +\t\tgoto out;\n> +\t}\n> +\n> +\tret = mount_service_send_required_files(&mo, fusedev);\n> +\tif (ret) {\n> +\t\tret = EXIT_FAILURE;\n> +\t\tgoto out;\n> +\t}\n> +\n> +\twhile (running) {\n> +\t\tstruct fuse_service_packet *p = NULL;\n> +\t\tsize_t sz;\n> +\n> +\t\tret = mount_service_receive_command(&mo, &p, &sz);\n> +\t\tif (ret) {\n> +\t\t\tret = EXIT_FAILURE;\n> +\t\t\tgoto out;\n> +\t\t}\n> +\n> +\t\tswitch (ntohl(p->magic)) {\n> +\t\tcase FUSE_SERVICE_OPEN_CMD:\n> +\t\t\tret = mount_service_handle_open_cmd(&mo, p, sz);\n> +\t\t\tbreak;\n> +\t\tcase FUSE_SERVICE_OPEN_BDEV_CMD:\n> +\t\t\tret = mount_service_handle_open_bdev_cmd(&mo, p, sz);\n> +\t\t\tbreak;\n> +\t\tcase FUSE_SERVICE_FSOPEN_CMD:\n> +\t\t\tret = mount_service_handle_fsopen_cmd(&mo, p, sz);\n> +\t\t\tbreak;\n> +\t\tcase FUSE_SERVICE_SOURCE_CMD:\n> +\t\t\tret = mount_service_handle_source_cmd(&mo, p, sz);\n> +\t\t\tbreak;\n> +\t\tcase FUSE_SERVICE_MNTOPTS_CMD:\n> +\t\t\tret = mount_service_handle_mntopts_cmd(&mo, p, sz);\n> +\t\t\tbreak;\n> +\t\tcase FUSE_SERVICE_MNTPT_CMD:\n> +\t\t\tret = mount_service_handle_mountpoint_cmd(&mo, p, sz,\n> +\t\t\t\t\t\t\t\t  argc, argv);\n> +\t\t\tbreak;\n> +\t\tcase FUSE_SERVICE_MOUNT_CMD:\n> +\t\t\tret = mount_service_handle_mount_cmd(&mo, p, sz);\n> +\t\t\tbreak;\n> +\t\tcase FUSE_SERVICE_UNMOUNT_CMD:\n> +\t\t\tret = mount_service_handle_unmount_cmd(&mo, p, sz);\n> +\t\t\tbreak;\n> +\t\tcase FUSE_SERVICE_BYE_CMD:\n> +\t\t\tret = mount_service_handle_bye_cmd(&mo, p, sz);\n> +\t\t\tfree(p);\n> +\t\t\tgoto out;\n> +\t\tdefault:\n> +\t\t\tfprintf(stderr, \"%s: unrecognized packet 0x%x\\n\",\n> +\t\t\t\tmo.msgtag, ntohl(p->magic));\n> +\t\t\tret = EXIT_FAILURE;\n> +\t\t\tbreak;\n> +\t\t}\n> +\t\tfree(p);\n> +\n> +\t\tif (ret) {\n> +\t\t\tret = EXIT_FAILURE;\n> +\t\t\tgoto out;\n> +\t\t}\n> +\t}\n> +\n> +\tret = EXIT_SUCCESS;\n> +out:\n> +\tmount_service_destroy(&mo);\n> +\treturn ret;\n> +}\n> \n>","headers":{"Return-Path":"\n <SRS0=TtGQ=C6=vger.kernel.org=linux-ext4+bounces-16274-patchwork-incoming=ozlabs.org@ozlabs.org>","X-Original-To":["incoming@patchwork.ozlabs.org","linux-ext4@vger.kernel.org"],"Delivered-To":["patchwork-incoming@legolas.ozlabs.org","patchwork-incoming@ozlabs.org"],"Authentication-Results":["legolas.ozlabs.org;\n\tdkim=pass (2048-bit key;\n unprotected) header.d=kernel.org header.i=@kernel.org header.a=rsa-sha256\n header.s=k20201202 header.b=SoAI576E;\n\tdkim-atps=neutral","legolas.ozlabs.org;\n spf=pass (sender SPF authorized) smtp.mailfrom=ozlabs.org\n (client-ip=2404:9400:2221:ea00::3; helo=mail.ozlabs.org;\n envelope-from=srs0=ttgq=c6=vger.kernel.org=linux-ext4+bounces-16274-patchwork-incoming=ozlabs.org@ozlabs.org;\n receiver=patchwork.ozlabs.org)","gandalf.ozlabs.org;\n arc=pass smtp.remote-ip=\"2600:3c04:e001:36c::12fc:5321\"\n arc.chain=subspace.kernel.org","gandalf.ozlabs.org;\n dmarc=pass (p=quarantine dis=none) header.from=kernel.org","gandalf.ozlabs.org;\n\tdkim=pass (2048-bit key;\n unprotected) header.d=kernel.org header.i=@kernel.org header.a=rsa-sha256\n header.s=k20201202 header.b=SoAI576E;\n\tdkim-atps=neutral","gandalf.ozlabs.org;\n spf=pass (sender SPF authorized) smtp.mailfrom=vger.kernel.org\n (client-ip=2600:3c04:e001:36c::12fc:5321; helo=tor.lore.kernel.org;\n envelope-from=linux-ext4+bounces-16274-patchwork-incoming=ozlabs.org@vger.kernel.org;\n receiver=ozlabs.org)","smtp.subspace.kernel.org;\n\tdkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org\n header.b=\"SoAI576E\"","smtp.subspace.kernel.org;\n arc=none smtp.client-ip=10.30.226.201"],"Received":["from mail.ozlabs.org (mail.ozlabs.org [IPv6:2404:9400:2221:ea00::3])\n\t(using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits)\n\t key-exchange x25519 server-signature ECDSA (secp384r1 raw public key)\n server-digest SHA384)\n\t(No client certificate requested)\n\tby legolas.ozlabs.org (Postfix) with ESMTPS id 4g6dVx3HMLz1xvV\n\tfor <incoming@patchwork.ozlabs.org>; Sat, 02 May 2026 03:36:09 +1000 (AEST)","from mail.ozlabs.org (mail.ozlabs.org [IPv6:2404:9400:2221:ea00::3])\n\tby gandalf.ozlabs.org (Postfix) with ESMTP id 4g6dVx0XjRz4wSr\n\tfor <incoming@patchwork.ozlabs.org>; Sat, 02 May 2026 03:36:09 +1000 (AEST)","by gandalf.ozlabs.org (Postfix)\n\tid 4g6dVx0CqPz4wL1; Sat, 02 May 2026 03:36:09 +1000 (AEST)","from tor.lore.kernel.org (tor.lore.kernel.org\n [IPv6:2600:3c04:e001:36c::12fc:5321])\n\t(using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits)\n\t key-exchange x25519)\n\t(No client certificate requested)\n\tby gandalf.ozlabs.org (Postfix) with ESMTPS id 4g6dVr6MBsz4wCQ\n\tfor <patchwork-incoming@ozlabs.org>; Sat, 02 May 2026 03:36:04 +1000 (AEST)","from smtp.subspace.kernel.org (conduit.subspace.kernel.org\n [100.90.174.1])\n\tby tor.lore.kernel.org (Postfix) with ESMTP id 244B330128FD\n\tfor <patchwork-incoming@ozlabs.org>; Fri,  1 May 2026 17:36:02 +0000 (UTC)","from localhost.localdomain (localhost.localdomain [127.0.0.1])\n\tby smtp.subspace.kernel.org (Postfix) with ESMTP id 16ABE314A6F;\n\tFri,  1 May 2026 17:35:58 +0000 (UTC)","from smtp.kernel.org (aws-us-west-2-korg-mail-1.web.codeaurora.org\n [10.30.226.201])\n\t(using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits))\n\t(No client certificate requested)\n\tby smtp.subspace.kernel.org (Postfix) with ESMTPS id B672831283E;\n\tFri,  1 May 2026 17:35:57 +0000 (UTC)","by smtp.kernel.org (Postfix) with ESMTPSA id 50ACEC2BCB4;\n\tFri,  1 May 2026 17:35:57 +0000 (UTC)"],"ARC-Seal":["i=2; a=rsa-sha256; d=ozlabs.org; s=201707; t=1777656969; cv=pass;\n\tb=Xpu0ua2ZAJ6fiDRES1Etw9rriRcsj/DVlDARYt+PuuDumCP8bU+EqzTisuirSTAYxe53KDRKGMAhV3Ott6L7Q61QnN/0iHExlYVd4laYvZDoBD6Dq06sMeHHmLUOEOnX37ejstT7NKE/JLHngiJd9rnxR9vecFYex+BzRTwXiKpcA0xGkmPcMkqrpGCcyxzYEsvmKGCekPNt98WeHswvTgVNx8kqQ56tqYwLsE6OqUdpYOWXAgkdUI5tyYvalX64MH6yvgF06yiTKwBSD1xhcpP5M8JYTm5CoalFnXnZqwxscexZ/F6c11tqJBmXU0Pyb4LTDyl8WKTdV79sB1Styw==","i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116;\n\tt=1777656957; cv=none;\n b=kGECFlNYH/WSXRJmO8NJu2QjCSgZW+D5CAdC5Pr09VbHzX7mu+XcrsxAkmZWWs9PJeCEIKr2vHDuVE0zprhb65avbCxghyoqvowBOBb/ECGuvGXvbzGsG3myTJRqRLV9R/TT70PLalbN/N2Zh+YtASjkKuKq/o5z/fHoj0Ror+A="],"ARC-Message-Signature":["i=2; a=rsa-sha256; d=ozlabs.org; s=201707;\n\tt=1777656969; c=relaxed/relaxed;\n\tbh=tmBujDDIswc+Fg1qa19puzAiow7WLrfpCrGyHPar6zw=;\n\th=Date:From:To:Cc:Subject:Message-ID:References:MIME-Version:\n\t Content-Type:Content-Disposition:In-Reply-To;\n b=y3gp0nHEE3MLRw0FCIeapW7MCThoUGg0mjlU6I1Oyva1aYK/SHB64SESNE7+jL3jEA7t0/b8QoTO6x8NQPWqgw3FUGdGMTJwEzPXZA7HyuUZBhZENBzaFMQ8EXmT7Ji+r42sLvRJ1fmtxjHjLot7HgNmqDDKulBBoSyMQBN3ODNG8xgDfv+c/mA6waNrHXtAOUh0P2FQeqfiduNT6PS/gw6TKAsWuG9EvyujHLbv3wTyVd6mKcOiA2/Y7aj5jaQ7ee+GLVyAaUcK0cS+XmoY1eIFjJw5+z3fnv9vk8k9oYmkEbuveaWzlsJ4/zFqS3l3Ss9WQ7v0jbd5592PQG9j1g==","i=1; a=rsa-sha256; d=subspace.kernel.org;\n\ts=arc-20240116; t=1777656957; c=relaxed/simple;\n\tbh=8dYOw/dlblPFmXis3do6gqEoVnCLZswFjU7s973kOms=;\n\th=Date:From:To:Cc:Subject:Message-ID:References:MIME-Version:\n\t Content-Type:Content-Disposition:In-Reply-To;\n b=FyLyWt3WKZfLXqMLNgcnEz/1k1R56XvdS59TOFhDyMhhd3DmF+bJUIFTiW745B7jKIHncjCYecVUdtAQD2bLIHwmCwlveYABBzOJzhOrgx1MIRuUHA2W0J2dgqW5Vk2qhiCOGPDG35ODN3T6QSTzh1fzw01DzSOq4zUWUWRj9cI="],"ARC-Authentication-Results":["i=2; gandalf.ozlabs.org;\n dmarc=pass (p=quarantine dis=none) header.from=kernel.org;\n dkim=pass (2048-bit key;\n unprotected) header.d=kernel.org header.i=@kernel.org header.a=rsa-sha256\n header.s=k20201202 header.b=SoAI576E; dkim-atps=neutral;\n spf=pass (client-ip=2600:3c04:e001:36c::12fc:5321; helo=tor.lore.kernel.org;\n envelope-from=linux-ext4+bounces-16274-patchwork-incoming=ozlabs.org@vger.kernel.org;\n receiver=ozlabs.org) smtp.mailfrom=vger.kernel.org","i=1; smtp.subspace.kernel.org;\n dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org\n header.b=SoAI576E; arc=none smtp.client-ip=10.30.226.201"],"DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org;\n\ts=k20201202; t=1777656957;\n\tbh=8dYOw/dlblPFmXis3do6gqEoVnCLZswFjU7s973kOms=;\n\th=Date:From:To:Cc:Subject:References:In-Reply-To:From;\n\tb=SoAI576E8Tu30TDFIYTWCLUdx7s+EArLcSyZAPV7UFTeUeUOR3B/QynDZjeXdPrvY\n\t MGYW6o9+yFA+CLnCoYxBP7MsMAz7OyW8UOpqII58B2naTpcfNjev/35BblP+kI7j1I\n\t nD6piSQtdu8dDa2T+Hl4IRuMK+h9HvngDJrCfr5vvM+eYQTxU2niwlBdeXdco9pc/v\n\t oLlcpARQIEvOFrqvdnK9MpC0IlzsFmH6A/r7jd4eUVKh44ZCFpxq6bLGwtKYBehwfi\n\t HY0efa1qcZZO+luOhhjvE8puTkBrsCR92/GkpJvKjlEBQFaP/TipGQjqT4XhxNCLNN\n\t 8cFzhcTMLXAJA==","Date":"Fri, 1 May 2026 10:35:56 -0700","From":"\"Darrick J. Wong\" <djwong@kernel.org>","To":"bernd@bsbernd.com","Cc":"linux-fsdevel@vger.kernel.org, fuse-devel@lists.linux.dev,\n\tlinux-ext4@vger.kernel.org, miklos@szeredi.hu, neal@gompa.dev,\n\tjoannelkoong@gmail.com","Subject":"Re: [PATCH 02/13] mount_service: add systemd socket service mounting\n helper","Message-ID":"<20260501173556.GY7739@frogsfrogsfrogs>","References":"<177758363484.1314717.11777978893472254088.stgit@frogsfrogsfrogs>\n <177758363568.1314717.5220084842430554136.stgit@frogsfrogsfrogs>","Precedence":"bulk","X-Mailing-List":"linux-ext4@vger.kernel.org","List-Id":"<linux-ext4.vger.kernel.org>","List-Subscribe":"<mailto:linux-ext4+subscribe@vger.kernel.org>","List-Unsubscribe":"<mailto:linux-ext4+unsubscribe@vger.kernel.org>","MIME-Version":"1.0","Content-Type":"text/plain; charset=us-ascii","Content-Disposition":"inline","In-Reply-To":"<177758363568.1314717.5220084842430554136.stgit@frogsfrogsfrogs>","X-Spam-Status":"No, score=-1.2 required=5.0 tests=ARC_SIGNED,ARC_VALID,\n\tDKIMWL_WL_HIGH,DKIM_SIGNED,DKIM_VALID,DKIM_VALID_AU,DMARC_PASS,\n\tMAILING_LIST_MULTI,SPF_HELO_NONE,SPF_PASS autolearn=disabled\n\tversion=4.0.1","X-Spam-Checker-Version":"SpamAssassin 4.0.1 (2024-03-25) on gandalf.ozlabs.org"}}]