/*
 * FUSE: Filesystem in Userspace
 * Copyright (C) 2025-2026 Oracle.
 * Author: Darrick J. Wong <djwong@kernel.org>
 *
 * This program can be distributed under the terms of the GNU GPLv2.
 * See the file GPL2.txt.
 *
 * This program does the mounting of FUSE filesystems that run in systemd
 */
#define _GNU_SOURCE
#include "fuse_config.h"
#include <stdint.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <fcntl.h>
#include <stdbool.h>
#include <limits.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <sys/mman.h>
#include <sys/mount.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include <linux/fs.h>

#ifdef HAVE_NEW_MOUNT_API
#include <sys/mount.h>
#include <linux/mount.h>
#endif

#include "mount_util.h"
#include "util.h"
#include "fuse_i.h"
#include "fuse_service_priv.h"
#include "mount_service.h"
#include "fuser_conf.h"
#ifdef HAVE_NEW_MOUNT_API
#include "mount_i_linux.h"
#endif
#include "fuse_mount_compat.h"

struct mount_service {
	/* prefix for printing error messages */
	const char *msgtag;

	/* fuse subtype based on -t cli argument */
	char *subtype;

	/* source argument to mount() */
	char *source;

	/* target argument (aka mountpoint) to mount() */
	char *mountpoint;

	/* mountpoint that we pass to mount() */
	char *real_mountpoint;

	/* resolved path to mountpoint that we use for mtab updates */
	char *resv_mountpoint;

	/* mount options */
	char *mntopts;

	/* mtab options */
	const char *mtabopts;

	/* socket fd */
	int sockfd;

	/* /dev/fuse device */
	int fusedevfd;

	/* memfd for cli arguments */
	int argvfd;

	/* fd for mount point */
	int mountfd;

	/* fd for fsopen */
	int fsopenfd;

	/* did we actually mount successfully? */
	bool mounted;

	/* has the fsopen command already been submitted? */
	bool fsopened;

	/* is this a fuseblk mount? */
	bool fuseblk;
};

static const char ignore_mtab;

static inline bool have_real_mtabopts(const struct mount_service *mo)
{
	return mo->mtabopts && mo->mtabopts != &ignore_mtab;
}

static inline void free_mtabopts(struct mount_service *mo)
{
	if (have_real_mtabopts(mo)) {
		free((void *)mo->mtabopts);
		mo->mtabopts = NULL;
	}
}

static ssize_t __send_fd(const struct mount_service *mo,
			 struct fuse_service_requested_file *req,
			 size_t req_sz, int fd)
{
	union {
		struct cmsghdr cmsghdr;
		char control[CMSG_SPACE(sizeof(int))];
	} cmsgu;
	struct iovec iov = {
		.iov_base = req,
		.iov_len = req_sz,
	};
	struct msghdr msg = {
		.msg_iov = &iov,
		.msg_iovlen = 1,
		.msg_control = cmsgu.control,
		.msg_controllen = sizeof(cmsgu.control),
	};
	struct cmsghdr *cmsg = CMSG_FIRSTHDR(&msg);

	if (!cmsg) {
		errno = EINVAL;
		return -1;
	}

	memset(&cmsgu, 0, sizeof(cmsgu));
	cmsg->cmsg_len = CMSG_LEN(sizeof(int));
	cmsg->cmsg_level = SOL_SOCKET;
	cmsg->cmsg_type = SCM_RIGHTS;

	*((int *)CMSG_DATA(cmsg)) = fd;

	return sendmsg(mo->sockfd, &msg, MSG_EOR | MSG_NOSIGNAL);
}

static ssize_t __send_packet(const struct mount_service *mo, void *ptr,
			     size_t len)
{
	struct iovec iov = {
		.iov_base = ptr,
		.iov_len = len,
	};
	struct msghdr msg = {
		.msg_iov = &iov,
		.msg_iovlen = 1,
	};

	return sendmsg(mo->sockfd, &msg, MSG_EOR | MSG_NOSIGNAL);
}

static ssize_t __recv_packet_size(const struct mount_service *mo)
{
	struct iovec iov = { };
	struct msghdr msg = {
		.msg_iov = &iov,
		.msg_iovlen = 1,
	};
	return recvmsg(mo->sockfd, &msg, MSG_PEEK | MSG_TRUNC);
}

static ssize_t __recv_packet(const struct mount_service *mo, void *ptr,
			     size_t len)
{
	struct iovec iov = {
		.iov_base = ptr,
		.iov_len = len,
	};
	struct msghdr msg = {
		.msg_iov = &iov,
		.msg_iovlen = 1,
	};

	return recvmsg(mo->sockfd, &msg, MSG_TRUNC);
}

/*
 * Filter out the subtype of the filesystem (e.g. fuse.Y[.Z] -> Y[.Z]).  The
 * fuse server determines if it's appropriate to set the "blockdev" mount
 * option (aka fuseblk).
 */
const char *mount_service_subtype(const char *fstype)
{
	const char *subtype;

	if (!strncmp(fstype, "fuse.", 5))
		subtype = fstype + 5;
	else if (!strncmp(fstype, "fuseblk.", 8))
		subtype = fstype + 8;
	else
		subtype = fstype;

	if (strchr(subtype, '/') != NULL) {
		fprintf(stderr,
			"%s: fs subtype cannot contain path separators\n",
			fstype);
		return NULL;
	}

	return subtype;
}

static int mount_service_init(struct mount_service *mo, int argc, char *argv[])
{
	const char *fstype = NULL;
	const char *subtype;
	int i;

	mo->sockfd = -1;
	mo->argvfd = -1;
	mo->fusedevfd = -1;
	mo->mountfd = -1;
	mo->fsopenfd = -1;

	for (i = 0; i < argc; i++) {
		if (!strcmp(argv[i], "-t") && i + 1 < argc) {
			fstype = argv[i + 1];
			break;
		}
	}
	if (!fstype) {
		fprintf(stderr, "%s: cannot determine filesystem type.\n",
			mo->msgtag);
		return -1;
	}

	subtype = mount_service_subtype(fstype);
	if (!subtype)
		return -1;

	mo->subtype = strdup(subtype);
	if (!mo->subtype) {
		int error = errno;

		fprintf(stderr, "%s: cannot alloc memory for fs subtype: %s\n",
			mo->msgtag, strerror(error));
		return -1;
	}

	return 0;
}

#ifdef SO_PASSRIGHTS
static int try_drop_passrights(const struct mount_service *mo, int sockfd)
{
	int zero = 0;
	int ret;

	/*
	 * Don't let a malicious mount helper send us any fds.  We don't trust
	 * the fuse server not to pollute our fd namespace, so we'll end now.
	 */
	ret = setsockopt(sockfd, SOL_SOCKET, SO_PASSRIGHTS, &zero,
			 sizeof(zero));
	if (ret) {
		fprintf(stderr, "%s: disabling fd passing: %s\n",
			mo->msgtag, strerror(errno));
		return -1;
	}

	return 0;
}
#else
# define try_drop_passrights(...)	(0)
#endif

static int check_sendbuf_size(const struct mount_service *mo, int sockfd)
{
	const size_t min_size = sizeof_fuse_service_open_command(PATH_MAX);
	int sendbuf_size = -1;
	socklen_t optlen = sizeof(sendbuf_size);
	int ret;

	/*
	 * If we can't query the maximum send buffer length, just keep going.
	 * Most likely we won't be sending huge open commands, and if we do,
	 * the sendmsg will fail there too.
	 */
	ret = getsockopt(sockfd, SOL_SOCKET, SO_SNDBUF, &sendbuf_size, &optlen);
	if (ret || sendbuf_size < 0)
		return 0;

	if (sendbuf_size >= min_size)
		return 0;

	fprintf(stderr, "%s: max socket send buffer is %d, need at least %zu.\n",
		mo->msgtag, sendbuf_size, min_size);
	return MOUNT_SERVICE_FALLBACK_NEEDED;
}

static int mount_service_connect(struct mount_service *mo)
{
	struct sockaddr_un name = {
		.sun_family = AF_UNIX,
	};
	int sockfd;
	ssize_t written;
	int ret;

	written = snprintf(name.sun_path, sizeof(name.sun_path),
			FUSE_SERVICE_SOCKET_DIR "/%s", mo->subtype);
	if (written >= sizeof(name.sun_path)) {
		fprintf(stderr, "%s: filesystem type name `%s' is too long.\n",
			mo->msgtag, mo->subtype);
		return -1;
	}

	sockfd = socket(AF_UNIX, SOCK_SEQPACKET | SOCK_CLOEXEC, 0);
	if (sockfd < 0) {
		int error = errno;

		fprintf(stderr, "%s: opening %s service socket: %s\n",
			mo->msgtag, mo->subtype, strerror(error));
		return -1;
	}

	ret = check_sendbuf_size(mo, sockfd);
	if (ret)
		return ret;

	drop_privs();
	ret = connect(sockfd, (const struct sockaddr *)&name, sizeof(name));
	if (ret && (errno == ENOENT || errno == ECONNREFUSED)) {
		restore_privs();
		fprintf(stderr, "%s: no safe filesystem driver for %s available.\n",
			mo->msgtag, mo->subtype);
		close(sockfd);
		return MOUNT_SERVICE_FALLBACK_NEEDED;
	}
	if (ret) {
		int error = errno;

		restore_privs();
		fprintf(stderr, "%s: %s: %s\n",
			mo->msgtag, name.sun_path, strerror(error));
		goto out;
	}
	restore_privs();

	ret = try_drop_passrights(mo, sockfd);
	if (ret)
		goto out;

	mo->sockfd = sockfd;
	return 0;
out:
	close(sockfd);
	return -1;
}

static int mount_service_send_hello(const struct mount_service *mo)
{
	struct fuse_service_hello hello = {
		.p.magic = htonl(FUSE_SERVICE_HELLO_CMD),
		.min_version = htons(FUSE_SERVICE_MIN_PROTO),
		.max_version = htons(FUSE_SERVICE_MAX_PROTO),
	};
	struct fuse_service_hello_reply reply = { };
	ssize_t size;

	if (getuid() == 0)
		hello.flags |= htonl(FUSE_SERVICE_FLAG_FUSEBLK |
				     FUSE_SERVICE_FLAG_ALLOW_OTHER);
	else if (user_allow_other)
		hello.flags |= htonl(FUSE_SERVICE_FLAG_ALLOW_OTHER);

	size = __send_packet(mo, &hello, sizeof(hello));
	if (size < 0) {
		fprintf(stderr, "%s: send hello: %s\n",
			mo->msgtag, strerror(errno));
		return -1;
	}

	size = __recv_packet(mo, &reply, sizeof(reply));
	if (size < 0) {
		fprintf(stderr, "%s: hello reply: %s\n",
			mo->msgtag, strerror(errno));
		return -1;
	}
	if (size != sizeof(reply)) {
		fprintf(stderr, "%s: wrong hello reply size %zd, expected %zu\n",
			mo->msgtag, size, sizeof(reply));
		return -1;
	}

	if (ntohl(reply.p.magic) != FUSE_SERVICE_HELLO_REPLY) {
		fprintf(stderr, "%s: %s service server did not reply to hello\n",
			mo->msgtag, mo->subtype);
		return -1;
	}

	if (ntohs(reply.version) < FUSE_SERVICE_MIN_PROTO ||
	    ntohs(reply.version) > FUSE_SERVICE_MAX_PROTO) {
		fprintf(stderr, "%s: unsupported protocol version %u\n",
			mo->msgtag, ntohs(reply.version));
		return -1;
	}

	if (reply.padding) {
		fprintf(stderr, "%s: nonzero value in padding field\n",
			mo->msgtag);
		return -1;
	}

	return 0;
}

static int mount_service_capture_arg(const struct mount_service *mo,
				     struct fuse_service_memfd_argv *args,
				     const char *string, off_t *array_pos,
				     off_t *string_pos)
{
	const size_t string_len = strlen(string) + 1;
	struct fuse_service_memfd_arg arg = {
		.pos = htonl(*string_pos),
		.len = htonl(string_len),
	};
	ssize_t written;

	written = pwrite(mo->argvfd, string, string_len, *string_pos);
	if (written < 0) {
		fprintf(stderr, "%s: memfd argv write: %s\n",
			mo->msgtag, strerror(errno));
		return -1;
	}
	if (written < string_len) {
		fprintf(stderr, "%s: memfd argv[%u] wrote %zd, expected %zu\n",
			mo->msgtag, args->argc, written, string_len);
		return -1;
	}

	written = pwrite(mo->argvfd, &arg, sizeof(arg), *array_pos);
	if (written < 0) {
		fprintf(stderr, "%s: memfd arg write: %s\n",
			mo->msgtag, strerror(errno));
		return -1;
	}
	if (written < sizeof(arg)) {
		fprintf(stderr, "%s: memfd arg[%u] wrote %zd, expected %zu\n",
			mo->msgtag, args->argc, written, sizeof(arg));
		return -1;
	}

	args->argc++;
	*string_pos += string_len;
	*array_pos += sizeof(arg);

	return 0;
}

static int mount_service_capture_args(struct mount_service *mo, int argc,
				      char *argv[])
{
	struct fuse_service_memfd_argv args = {
		.magic = htonl(FUSE_SERVICE_ARGS_MAGIC),
	};
	off_t array_pos = sizeof(struct fuse_service_memfd_argv);
	off_t string_pos = array_pos +
			(argc * sizeof(struct fuse_service_memfd_arg));
	ssize_t written;
	int i;
	int ret;

	if (argc < 0) {
		fprintf(stderr, "%s: argc cannot be negative\n",
			mo->msgtag);
		return -1;
	}

	/*
	 * Create the memfd in which we'll stash arguments, and set the write
	 * pointer for the names.
	 */
	mo->argvfd = memfd_create("fuse service argv", MFD_CLOEXEC);
	if (mo->argvfd < 0) {
		fprintf(stderr, "%s: argvfd create: %s\n",
			mo->msgtag, strerror(errno));
		return -1;
	}

	/*
	 * Write the alleged subtype as if it were argv[0], then write the rest
	 * of the argv arguments.
	 */
	ret = mount_service_capture_arg(mo, &args, mo->subtype, &array_pos,
					&string_pos);
	if (ret)
		return ret;

	i = 1;
	while (i < argc) {
		/* skip the -t(ype) argument */
		if (!strcmp(argv[i], "-t") && i + 1 < argc) {
			i += 2;
			continue;
		}

		ret = mount_service_capture_arg(mo, &args, argv[i],
						&array_pos, &string_pos);
		if (ret)
			return ret;
		i++;
	}

	/* Now write the header */
	args.argc = htonl(args.argc);
	written = pwrite(mo->argvfd, &args, sizeof(args), 0);
	if (written < 0) {
		fprintf(stderr, "%s: memfd argv write: %s\n",
			mo->msgtag, strerror(errno));
		return -1;
	}
	if (written < sizeof(args)) {
		fprintf(stderr, "%s: memfd argv wrote %zd, expected %zu\n",
			mo->msgtag, written, sizeof(args));
		return -1;
	}

	return 0;
}

static int mount_service_send_file(const struct mount_service *mo,
				   const char *path, int fd)
{
	struct fuse_service_requested_file *req;
	const size_t path_len = strlen(path);
	const size_t req_sz = sizeof_fuse_service_requested_file(path_len);
	ssize_t written;
	int ret = 0;

	req = calloc(1, req_sz);
	if (!req) {
		fprintf(stderr, "%s: alloc send file reply: %s\n",
			mo->msgtag, strerror(errno));
		return -1;
	}
	req->p.magic = htonl(FUSE_SERVICE_OPEN_REPLY);
	req->error = 0;
	memcpy(req->path, path, path_len + 1);

	written = __send_fd(mo, req, req_sz, fd);
	if (written < 0) {
		fprintf(stderr, "%s: send file reply: %s\n",
			mo->msgtag, strerror(errno));
		ret = -1;
		goto out_req;
	}
	if (written < req_sz) {
		fprintf(stderr, "%s: send file reply wrote %zd, expected %zu\n",
			mo->msgtag, written, req_sz);
		ret = -1;
		goto out_req;
	}

out_req:
	free(req);
	return ret;
}

static int mount_service_send_file_error(const struct mount_service *mo,
					 int error, const char *path)
{
	struct fuse_service_requested_file *req;
	const size_t path_len = strlen(path);
	const size_t req_sz = sizeof_fuse_service_requested_file(path_len);
	ssize_t written;
	int ret = 0;

	req = calloc(1, req_sz);
	if (!req) {
		fprintf(stderr, "%s: alloc send file error: %s\n",
			mo->msgtag, strerror(errno));
		return -1;
	}
	req->p.magic = htonl(FUSE_SERVICE_OPEN_REPLY);
	req->error = htonl(error);
	memcpy(req->path, path, path_len + 1);

	written = __send_packet(mo, req, req_sz);
	if (written < 0) {
		fprintf(stderr, "%s: send file error: %s\n",
			mo->msgtag, strerror(errno));
		ret = -1;
		goto out_req;
	}
	if (written < req_sz) {
		fprintf(stderr, "%s: send file error wrote %zd, expected %zu\n",
			mo->msgtag, written, req_sz);
		ret = -1;
		goto out_req;
	}

out_req:
	free(req);
	return ret;
}

static int mount_service_send_required_files(struct mount_service *mo,
					     const char *fusedev)
{
	int ret;

	drop_privs();
	mo->fusedevfd = open(fusedev, O_RDWR | O_CLOEXEC);
	if (mo->fusedevfd < 0) {
		int error = errno;

		restore_privs();
		fprintf(stderr, "%s: %s: %s\n",
			mo->msgtag, fusedev, strerror(error));
		return -1;
	}
	restore_privs();

	ret = mount_service_send_file(mo, FUSE_SERVICE_ARGV, mo->argvfd);
	if (ret)
		goto out_fusedevfd;

	close(mo->argvfd);
	mo->argvfd = -1;

	return mount_service_send_file(mo, FUSE_SERVICE_FUSEDEV,
				       mo->fusedevfd);

out_fusedevfd:
	close(mo->fusedevfd);
	mo->fusedevfd = -1;
	return ret;
}

static int mount_service_receive_command(const struct mount_service *mo,
					 struct fuse_service_packet **commandp,
					 size_t *commandsz)
{
	struct fuse_service_packet *command;
	ssize_t alleged_size, size;

	alleged_size = __recv_packet_size(mo);
	if (alleged_size < 0) {
		fprintf(stderr, "%s: peek service command: %s\n",
			mo->msgtag, strerror(errno));
		return -1;
	}
	if (alleged_size == 0) {
		/* fuse server probably exited early */
		fprintf(stderr, "%s: fuse server exited without saying goodbye!\n",
			mo->msgtag);
		return -1;
	}
	if (alleged_size < sizeof(struct fuse_service_packet)) {
		fprintf(stderr, "%s: wrong command packet size %zd, expected at least %zu\n",
			mo->msgtag, alleged_size,
			sizeof(struct fuse_service_packet));
		return -1;
	}
	if (alleged_size > FUSE_SERVICE_MAX_CMD_SIZE) {
		fprintf(stderr, "%s: wrong command packet size %zd, expected less than %d\n",
			mo->msgtag, alleged_size, FUSE_SERVICE_MAX_CMD_SIZE);
		return -1;
	}

	command = calloc(1, alleged_size + 1);
	if (!command) {
		fprintf(stderr, "%s: alloc service command: %s\n",
			mo->msgtag, strerror(errno));
		return -1;
	}

	size = __recv_packet(mo, command, alleged_size);
	if (size < 0) {
		fprintf(stderr, "%s: receive service command: %s\n",
			mo->msgtag, strerror(errno));
		free(command);
		return -1;
	}
	if (size != alleged_size) {
		fprintf(stderr, "%s: wrong service command size %zd, expected %zd\n",
			mo->msgtag, size, alleged_size);
		free(command);
		return -1;
	}

	*commandp = command;
	*commandsz = size;
	return 0;
}

static int mount_service_send_reply(const struct mount_service *mo, int error)
{
	struct fuse_service_simple_reply reply = {
		.p.magic = htonl(FUSE_SERVICE_SIMPLE_REPLY),
		.error = htonl(error),
	};
	ssize_t size;

	size = __send_packet(mo, &reply, sizeof(reply));
	if (size < 0) {
		fprintf(stderr, "%s: send service reply: %s\n",
			mo->msgtag, strerror(errno));
		return -1;
	}

	return 0;
}

static int prepare_bdev(const struct mount_service *mo,
			const struct fuse_service_open_command *oc, int fd,
			unsigned int request_flags)
{
	struct stat stbuf;
	const bool quiet = (request_flags & FUSE_SERVICE_OPEN_QUIET);
	int ret;

	ret = fstat(fd, &stbuf);
	if (ret) {
		int error = errno;

		if (!quiet)
			fprintf(stderr, "%s: %s: %s\n",
				mo->msgtag, oc->path, strerror(error));
		return -error;
	}

	if (!S_ISBLK(stbuf.st_mode)) {
		if (!quiet)
			fprintf(stderr, "%s: %s: %s\n",
				mo->msgtag, oc->path, strerror(ENOTBLK));
		return -ENOTBLK;
	}

	if (oc->block_size) {
		int block_size = ntohl(oc->block_size);

		drop_privs();
		ret = ioctl(fd, BLKBSZSET, &block_size);
		if (ret) {
			int error = errno;

			restore_privs();
			if (!quiet)
				fprintf(stderr, "%s: %s: %s\n",
					mo->msgtag, oc->path, strerror(error));
			return -error;
		}
		restore_privs();
	}

	return 0;
}

static int mount_service_open_path(const struct mount_service *mo,
				   mode_t expected_fmt,
				   struct fuse_service_packet *p, size_t psz)
{
	const struct fuse_service_open_command *oc =
			container_of(p, struct fuse_service_open_command, p);
	uint32_t request_flags;
	int open_flags;
	int ret;
	int fd;

	if (psz < sizeof_fuse_service_open_command(1)) {
		fprintf(stderr, "%s: open command too small\n",
			mo->msgtag);
		return mount_service_send_file_error(mo, EINVAL, "?");
	}

	if (!check_null_endbyte(p, psz)) {
		fprintf(stderr, "%s: open command must be null terminated\n",
			mo->msgtag);
		return mount_service_send_file_error(mo, EINVAL, "?");
	}

	request_flags = ntohl(oc->request_flags);
	if (request_flags & ~FUSE_SERVICE_OPEN_FLAGS) {
		fprintf(stderr, "%s: open flags 0x%x not recognized\n",
			mo->msgtag, request_flags & ~FUSE_SERVICE_OPEN_FLAGS);
		return mount_service_send_file_error(mo, EINVAL, oc->path);
	}

	open_flags = ntohl(oc->open_flags) | O_CLOEXEC;
	drop_privs();
	fd = open(oc->path, open_flags, ntohl(oc->create_mode));
	if (fd < 0) {
		int error = errno;

		/*
		 * Don't print a busy device error report because the
		 * filesystem might decide to retry.
		 */
		restore_privs();
		if (error != EBUSY && !(request_flags & FUSE_SERVICE_OPEN_QUIET))
			fprintf(stderr, "%s: %s: %s\n",
				mo->msgtag, oc->path, strerror(error));
		return mount_service_send_file_error(mo, error, oc->path);
	}
	restore_privs();

	if (S_ISBLK(expected_fmt)) {
		ret = prepare_bdev(mo, oc, fd, request_flags);
		if (ret < 0) {
			close(fd);
			return mount_service_send_file_error(mo, -ret,
							     oc->path);
		}
	}

	ret = mount_service_send_file(mo, oc->path, fd);
	close(fd);
	return ret;
}

static int mount_service_handle_open_cmd(const struct mount_service *mo,
					 struct fuse_service_packet *p,
					 size_t psz)
{
	return mount_service_open_path(mo, 0, p, psz);
}

static int mount_service_handle_open_bdev_cmd(const struct mount_service *mo,
					      struct fuse_service_packet *p,
					      size_t psz)
{
	return mount_service_open_path(mo, S_IFBLK, p, psz);
}

static inline const char *fsname(const struct mount_service *mo)
{
	return mo->fuseblk ? "fuseblk" : "fuse";
}

#ifdef HAVE_NEW_MOUNT_API
static void try_fsopen(struct mount_service *mo)
{
	/*
	 * As of Linux 7.0 you can pass subtypes to fsopen, but the manpage for
	 * fsopen only says that you can pass any value of the second column of
	 * /proc/filesystems into fsopen.  You must still call fsconfig() for
	 * the subtype separately, however.
	 */
	mo->fsopenfd = fsopen(fsname(mo), FSOPEN_CLOEXEC);
}
#else
# define try_fsopen(...)	((void)0)
#endif

static int mount_service_handle_fsopen_cmd(struct mount_service *mo,
					   const struct fuse_service_packet *p,
					   size_t psz)
{
	const struct fuse_service_fsopen_command *oc =
			container_of(p, struct fuse_service_fsopen_command, p);
	uint32_t fsopen_flags;

	if (psz != sizeof(struct fuse_service_fsopen_command)) {
		fprintf(stderr, "%s: fsopen command wrong size %zu, expected %zu\n",
			mo->msgtag, psz, sizeof(*oc));
		return mount_service_send_reply(mo, EINVAL);
	}

	if (mo->fsopened) {
		fprintf(stderr, "%s: fsopen command respecified\n",
			mo->msgtag);
		return mount_service_send_reply(mo, EINVAL);
	}

	fsopen_flags = ntohl(oc->fsopen_flags);
	if (fsopen_flags & ~FUSE_SERVICE_FSOPEN_FLAGS) {
		fprintf(stderr, "%s: unknown fsopen flags, 0x%x\n",
			mo->msgtag, fsopen_flags & ~FUSE_SERVICE_FSOPEN_FLAGS);
		return mount_service_send_reply(mo, EINVAL);
	}

	if (fsopen_flags & FUSE_SERVICE_FSOPEN_FUSEBLK) {
		if (getuid() != 0) {
			fprintf(stderr, "%s: fuseblk requires root privilege\n",
				mo->msgtag);
			return mount_service_send_reply(mo, EPERM);
		}

		mo->fuseblk = true;
	}
	mo->fsopened = true;

	/* If this fails we fall back on mount(); oc->value is mutated */
	try_fsopen(mo);
	return mount_service_send_reply(mo, 0);
}

static int mount_service_handle_source_cmd(struct mount_service *mo,
					   const struct fuse_service_packet *p,
					   size_t psz)
{
	const struct fuse_service_string_command *oc =
			container_of(p, struct fuse_service_string_command, p);
	char *source;

	if (psz < sizeof_fuse_service_string_command(1)) {
		fprintf(stderr, "%s: source command too small\n",
			mo->msgtag);
		return mount_service_send_reply(mo, EINVAL);
	}

	if (!check_null_endbyte(p, psz)) {
		fprintf(stderr, "%s: source command must be null terminated\n",
			mo->msgtag);
		return mount_service_send_reply(mo, EINVAL);
	}

	if (mo->source) {
		fprintf(stderr, "%s: source respecified!\n",
			mo->msgtag);
		return mount_service_send_reply(mo, EINVAL);
	}

	source = strdup(oc->value);
	if (!source) {
		int error = errno;

		fprintf(stderr, "%s: alloc source string: %s\n",
			mo->msgtag, strerror(error));
		return mount_service_send_reply(mo, error);
	}

#ifdef HAVE_NEW_MOUNT_API
	if (mo->fsopenfd >= 0) {
		int ret = apply_fsconfig_opt_string(mo->fsopenfd, "source",
						    oc->value);
		if (ret < 0) {
			free(source);
			return mount_service_send_reply(mo, -ret);
		}
	}
#endif

	mo->source = source;
	return mount_service_send_reply(mo, 0);
}

static int mount_service_handle_mntopts_cmd(struct mount_service *mo,
					    const struct fuse_service_packet *p,
					    size_t psz)
{
	struct fuse_service_string_command *oc =
			container_of(p, struct fuse_service_string_command, p);
	char *tokstr = oc->value;
	const char *tok;
	char *savetok;
	char *mntopts;

	if (psz < sizeof_fuse_service_string_command(1)) {
		fprintf(stderr, "%s: mount options command too small\n",
			mo->msgtag);
		return mount_service_send_reply(mo, EINVAL);
	}

	if (!check_null_endbyte(p, psz)) {
		fprintf(stderr, "%s: mount options command must be null terminated\n",
			mo->msgtag);
		return mount_service_send_reply(mo, EINVAL);
	}

	if (mo->mntopts) {
		fprintf(stderr, "%s: mount options respecified!\n",
			mo->msgtag);
		return mount_service_send_reply(mo, EINVAL);
	}

	mntopts = strdup(oc->value);
	if (!mntopts) {
		int error = errno;

		fprintf(stderr, "%s: alloc mount options string: %s\n",
			mo->msgtag, strerror(error));
		return mount_service_send_reply(mo, error);
	}

	/* strtok_r mutates tokstr aka oc->value */
	if (getuid() != 0 && !user_allow_other) {
		while ((tok = strtok_r(tokstr, ",", &savetok)) != NULL) {
			if (!strcmp(tok, "allow_other") ||
			    !strcmp(tok, "allow_root")) {
				fprintf(stderr,
	"%s: option %s only allowed if 'user_allow_other' is set in %s\n",
					mo->msgtag, tok, FUSE_CONF);
				free(mntopts);
				return mount_service_send_reply(mo, EPERM);
			}

			tokstr = NULL;
		}
	}

#ifdef HAVE_NEW_MOUNT_API
	if (mo->fsopenfd >= 0) {
		int ret = apply_fsconfig_mount_opts(mo->fsopenfd, mntopts);

		if (ret < 0) {
			free(mntopts);
			return mount_service_send_reply(mo, -ret);
		}
	}
#endif

	mo->mntopts = mntopts;
	return mount_service_send_reply(mo, 0);
}

static int mount_service_handle_mtabopts_cmd(struct mount_service *mo,
					     const struct fuse_service_packet *p,
					     size_t psz)
{
	struct fuse_service_string_command *oc =
			container_of(p, struct fuse_service_string_command, p);
	char *tokstr = oc->value;
	const char *tok;
	char *savetok;

	if (psz < sizeof_fuse_service_string_command(1)) {
		fprintf(stderr, "%s: mtab options command too small\n",
			mo->msgtag);
		return mount_service_send_reply(mo, EINVAL);
	}

	if (!check_null_endbyte(p, psz)) {
		fprintf(stderr, "%s: mtab options command must be null terminated\n",
			mo->msgtag);
		return mount_service_send_reply(mo, EINVAL);
	}

	if (mo->mtabopts) {
		fprintf(stderr, "%s: mtab options respecified!\n",
			mo->msgtag);
		return mount_service_send_reply(mo, EINVAL);
	}

	mo->mtabopts = strdup(oc->value);
	if (!mo->mtabopts) {
		int error = errno;

		fprintf(stderr, "%s: alloc mtab options string: %s\n",
			mo->msgtag, strerror(error));
		return mount_service_send_reply(mo, error);
	}

	/* strtok_r mutates tokstr aka oc->value */
	while ((tok = strtok_r(tokstr, ",", &savetok)) != NULL) {
		if (!strcmp(tok, "-n")) {
			free_mtabopts(mo);
			mo->mtabopts = &ignore_mtab;
		}

		tokstr = NULL;
	}

	return mount_service_send_reply(mo, 0);
}

static int open_mountpoint(const char *mntpt, bool *require_dir)
{
	int ret;

	*require_dir = false;

	if (getuid() == 0) {
		/*
		 * Open the alleged mountpoint.  We're root, so we only bother
		 * checking for readability.
		 */
		return open(mntpt, O_RDONLY | O_CLOEXEC);
	}

	/*
	 * Open the alleged mountpoint.  For unprivileged callers, we only
	 * allow mounting on paths that the user can write to.
	 */
	ret = open(mntpt, O_WRONLY | O_CLOEXEC);
	if (ret >= 0 || errno != EISDIR)
		return ret;

	/*
	 * However, we can't open directories with write access.  Try again in
	 * readonly mode, but require the caller to verify that we actually got
	 * a directory.
	 */
	*require_dir = true;
	ret = open(mntpt, O_RDONLY | O_CLOEXEC);
	if (ret >= 0 || (errno != EACCES && errno != EPERM))
		return ret;

#ifdef O_PATH
	/*
	 * If we can't open at all, let's try opening this directory with
	 * O_PATH.
	 */
	return open(mntpt, O_PATH | O_CLOEXEC);
#else
	/* No idea what to do now */
	errno = EACCES;
	return -1;
#endif
}

static int attach_to_mountpoint(struct mount_service *mo, mode_t expected_fmt,
				char *mntpt)
{
	struct stat stbuf;
	char *res_mntpt;
	bool require_dir;
	int mountfd = -1;
	int error;
	int ret;

	drop_privs();

	mountfd = open_mountpoint(mntpt, &require_dir);
	if (mountfd < 0) {
		error = errno;
		fprintf(stderr, "%s: %s: %s\n", mo->msgtag, mntpt,
			strerror(error));
		goto out_error;
	}

	/*
	 * Make sure we can access the mountpoint and that it's either a
	 * directory or a regular file.  Linux can handle mounting atop special
	 * files, but we don't care to do such crazy things.
	 */
	ret = fstat(mountfd, &stbuf);
	if (ret) {
		error = errno;
		fprintf(stderr, "%s: %s: %s\n", mo->msgtag, mntpt,
			strerror(error));
		goto out_mountfd;
	}

	if (!S_ISDIR(stbuf.st_mode) && !S_ISREG(stbuf.st_mode)) {
		error = EACCES;
		fprintf(stderr, "%s: %s: Mount point must be directory or regular file.\n",
			mo->msgtag, mntpt);
		goto out_mountfd;
	}

	if (require_dir && !S_ISDIR(stbuf.st_mode)) {
		error = EACCES;
		fprintf(stderr, "%s: %s: Mount point must be directory.\n",
			mo->msgtag, mntpt);
		goto out_mountfd;
	}

	/*
	 * Resolve the (possibly relative) mountpoint path before chdir'ing
	 * onto it.
	 */
	res_mntpt = fuse_mnt_resolve_path(mo->msgtag, mntpt);
	if (!res_mntpt) {
		error = EACCES;
		fprintf(stderr, "%s: %s: Could not resolve path to mount point.\n",
			mo->msgtag, mntpt);
		goto out_mountfd;
	}

	/* Make sure the mountpoint type matches what the caller wanted */
	switch (expected_fmt) {
	case S_IFDIR:
		if (!S_ISDIR(stbuf.st_mode)) {
			error = ENOTDIR;
			fprintf(stderr, "%s: %s: %s\n",
				mo->msgtag, mntpt, strerror(error));
			goto out_res_mntpt;
		}
		break;
	case S_IFREG:
		if (!S_ISREG(stbuf.st_mode)) {
			error = EISDIR;
			fprintf(stderr, "%s: %s: %s\n",
				mo->msgtag, mntpt, strerror(error));
			goto out_res_mntpt;
		}
		break;
	}

	switch (stbuf.st_mode & S_IFMT) {
	case S_IFREG:
		/*
		 * This is a regular file, so we point mount() at the open file
		 * descriptor.
		 */
		asprintf(&mo->real_mountpoint, "/dev/fd/%d", mountfd);
		break;
	case S_IFDIR:
		/*
		 * Pin the mount so it can't go anywhere.  This only works for
		 * directories, which is fortunately the common case.
		 */
		ret = fchdir(mountfd);
		if (ret) {
			error = errno;
			fprintf(stderr, "%s: %s: %s\n", mo->msgtag, mntpt,
				strerror(error));
			goto out_res_mntpt;
		}

		/*
		 * Now that we're sitting on the mountpoint directory, we can
		 * pass "." to mount() and avoid races with directory tree
		 * mutations.
		 */
		mo->real_mountpoint = strdup(".");
		break;
	default:
		/* Should never get here */
		error = EINVAL;
		goto out_res_mntpt;
	}
	if (!mo->real_mountpoint) {
		error = ENOMEM;
		fprintf(stderr, "%s: %s: %s\n", mo->msgtag, mntpt,
			strerror(error));
		goto out_res_mntpt;
	}

	mo->mountpoint = mntpt;
	mo->mountfd = mountfd;
	mo->resv_mountpoint = res_mntpt;

	restore_privs();
	return mount_service_send_reply(mo, 0);

out_res_mntpt:
	free(res_mntpt);
out_mountfd:
	close(mountfd);
out_error:
	free(mntpt);
	restore_privs();
	return mount_service_send_reply(mo, error);
}

static int mount_service_handle_mountpoint_cmd(struct mount_service *mo,
					       const struct fuse_service_packet *p,
					       size_t psz, int argc,
					       const char * const argv[])
{
	const struct fuse_service_mountpoint_command *oc =
			container_of(p, struct fuse_service_mountpoint_command, p);
	char *mntpt;
	mode_t expected_fmt;
	bool foundit = false;
	int i;

	if (psz < sizeof_fuse_service_mountpoint_command(1)) {
		fprintf(stderr, "%s: mount point command too small\n",
			mo->msgtag);
		return mount_service_send_reply(mo, EINVAL);
	}

	if (!check_null_endbyte(p, psz)) {
		fprintf(stderr, "%s: mount point command must be null terminated\n",
			mo->msgtag);
		return mount_service_send_reply(mo, EINVAL);
	}

	if (oc->padding) {
		fprintf(stderr, "%s: nonzero value in padding field\n",
			mo->msgtag);
		return mount_service_send_reply(mo, EINVAL);
	}

	if (mo->mountpoint) {
		fprintf(stderr, "%s: mount point respecified!\n",
			mo->msgtag);
		return mount_service_send_reply(mo, EINVAL);
	}

	/* Make sure the mountpoint file format matches what the caller wanted */
	expected_fmt = ntohs(oc->expected_fmt);
	switch (expected_fmt) {
	case S_IFDIR:
	case S_IFREG:
	case 0:
		break;
	default:
		fprintf(stderr, "%s: %s: weird expected format 0%o\n",
			mo->msgtag, oc->value, expected_fmt);
		return mount_service_send_reply(mo, EINVAL);
	}

	/* Mountpoint must be mentioned in the caller's argument list */
	for (i = 0; i < argc; i++) {
		if (!strcmp(argv[i], oc->value)) {
			foundit = true;
			break;
		}
	}
	if (!foundit) {
		fprintf(stderr, "%s: mount point must be in command line arguments\n",
			mo->msgtag);
		return mount_service_send_reply(mo, EINVAL);
	}

	mntpt = strdup(oc->value);
	if (!mntpt) {
		int error = errno;

		fprintf(stderr, "%s: alloc mount point string: %s\n",
			mo->msgtag, strerror(error));
		return mount_service_send_reply(mo, error);
	}

	return attach_to_mountpoint(mo, expected_fmt, mntpt);
}

static inline int format_libfuse_mntopts(char *buf, size_t bufsz,
					 const struct mount_service *mo,
					 const struct stat *stbuf)
{
	if (mo->mntopts)
		return snprintf(buf, bufsz,
				"%s,fd=%i,rootmode=%o,user_id=%u,group_id=%u",
				mo->mntopts, mo->fusedevfd,
				stbuf->st_mode & S_IFMT,
				getuid(), getgid());

	return snprintf(buf, bufsz,
			"fd=%i,rootmode=%o,user_id=%u,group_id=%u",
			mo->fusedevfd, stbuf->st_mode & S_IFMT,
			getuid(), getgid());
}

static int mount_service_regular_mount(struct mount_service *mo,
				       const struct fuse_service_mount_command *oc,
				       const struct stat *stbuf)
{
	char *fstype = NULL;
	char *realmopts;
	int ret;

	/* Compute the amount of buffer space needed for the mount options */
	ret = format_libfuse_mntopts(NULL, 0, mo, stbuf);
	if (ret < 0) {
		int error = errno;

		fprintf(stderr, "%s: mount option preformatting: %s\n",
			mo->msgtag, strerror(error));
		return mount_service_send_reply(mo, error);
	}

	realmopts = calloc(1, ret + 1);
	if (!realmopts) {
		int error = errno;

		fprintf(stderr, "%s: alloc real mount options string: %s\n",
			mo->msgtag, strerror(error));
		return mount_service_send_reply(mo, error);
	}

	ret = format_libfuse_mntopts(realmopts, ret + 1, mo, stbuf);
	if (ret < 0) {
		int error = errno;

		fprintf(stderr, "%s: mount options formatting: %s\n",
			mo->msgtag, strerror(error));
		ret = mount_service_send_reply(mo, error);
		goto out_realmopts;
	}

	asprintf(&fstype, "%s.%s", fsname(mo), mo->subtype);
	if (!fstype) {
		int error = errno;

		fprintf(stderr, "%s: mount fstype formatting: %s\n",
			mo->msgtag, strerror(error));
		ret = mount_service_send_reply(mo, error);
		goto out_realmopts;
	}

	ret = mount(mo->source, mo->real_mountpoint, fstype,
		    ntohl(oc->ms_flags), realmopts);
	if (ret) {
		int error = errno;

		fprintf(stderr, "%s: mount: %s\n",
			mo->msgtag, strerror(error));
		ret = mount_service_send_reply(mo, error);
		goto out_fstype;
	}

	/*
	 * The mount succeeded, so we send a positive reply even if the mtab
	 * update fails.
	 */
	if (have_real_mtabopts(mo))
		fuse_mnt_add_mount(mo->msgtag, mo->source, mo->resv_mountpoint,
				   fstype, mo->mtabopts);

	mo->mounted = true;
	ret = mount_service_send_reply(mo, 0);
out_fstype:
	free(fstype);
out_realmopts:
	free(realmopts);
	return ret;
}

#ifdef HAVE_NEW_MOUNT_API
static int mount_service_fsopen_mount(struct mount_service *mo,
				      const struct fuse_service_mount_command *oc,
				      const struct stat *stbuf)
{
	char tmp[64];
	unsigned long ms_flags = ntohl(oc->ms_flags);
	unsigned int attr_flags;
	int mfd;
	int error;
	int ret;

	ret = set_fsconfig_ms_flags(mo->fsopenfd, &ms_flags);
	if (ret) {
		error = errno;
		goto fail_mount;
	}

	/*
	 * We can't translate all the supplied MS_ flags into MOUNT_ATTR_ flags
	 * or string flags!  Return a magic code so the caller will fall back
	 * to regular mount(2).
	 */
	ms_flags = ms_flags_to_mount_attrs(ms_flags, &attr_flags);
	if (ms_flags)
		return FUSE_MOUNT_FALLBACK_NEEDED;

	ret = apply_fsconfig_opt_string(mo->fsopenfd, "subtype", mo->subtype);
	if (ret < 0) {
		/* The subtype option was merged after fsopen */
		if (ret == -EINVAL)
			return FUSE_MOUNT_FALLBACK_NEEDED;

		error = -ret;
		goto fail_fsconfig;
	}

	snprintf(tmp, sizeof(tmp), "%i", mo->fusedevfd);
	ret = apply_fsconfig_opt_fd(mo->fsopenfd, tmp);
	if (ret < 0) {
		error = -ret;
		goto fail_fsconfig;
	}

	snprintf(tmp, sizeof(tmp), "%o", stbuf->st_mode & S_IFMT);
	ret = apply_fsconfig_opt_string(mo->fsopenfd, "rootmode", tmp);
	if (ret < 0) {
		error = -ret;
		goto fail_fsconfig;
	}

	snprintf(tmp, sizeof(tmp), "%u", getuid());
	ret = apply_fsconfig_opt_string(mo->fsopenfd, "user_id", tmp);
	if (ret < 0) {
		error = -ret;
		goto fail_fsconfig;
	}

	snprintf(tmp, sizeof(tmp), "%u", getgid());
	ret = apply_fsconfig_opt_string(mo->fsopenfd, "group_id", tmp);
	if (ret < 0) {
		error = -ret;
		goto fail_fsconfig;
	}

	ret = fsconfig(mo->fsopenfd, FSCONFIG_CMD_CREATE, NULL, NULL, 0);
	if (ret) {
		error = errno;
		fprintf(stderr, "%s: creating filesystem: %s\n",
			mo->msgtag, strerror(error));
		goto fail_fsconfig;
	}

	mfd = fsmount(mo->fsopenfd, FSMOUNT_CLOEXEC, attr_flags);
	if (mfd < 0) {
		error = errno;
		fprintf(stderr, "%s: fsmount: %s\n",
			mo->msgtag, strerror(error));
		goto fail_fsconfig;
	}

	ret = move_mount(mfd, "", mo->mountfd, "",
			 MOVE_MOUNT_F_EMPTY_PATH | MOVE_MOUNT_T_EMPTY_PATH);
	close(mfd);
	if (ret) {
		error = errno;
		fprintf(stderr, "%s: move_mount: %s\n",
			mo->msgtag, strerror(error));
		goto fail_mount;
	}

	/*
	 * The mount succeeded, so we send a positive reply even if the mtab
	 * update fails.
	 */
	if (have_real_mtabopts(mo)) {
		char *fstype = NULL;

		asprintf(&fstype, "%s.%s", fsname(mo), mo->subtype);
		if (fstype) {
			fuse_mnt_add_mount(mo->msgtag, mo->source,
					   mo->resv_mountpoint, fstype,
					   mo->mtabopts);
			free(fstype);
		}
	}

	mo->mounted = true;
	return mount_service_send_reply(mo, 0);

fail_fsconfig:
	log_fsconfig_kmsg(mo->fsopenfd);
fail_mount:
	return mount_service_send_reply(mo, error);
}
#else
# define mount_service_fsopen_mount(...)	(FUSE_MOUNT_FALLBACK_NEEDED)
#endif

static int check_nonroot_file_access(struct mount_service *mo)
{
	struct stat sb1, sb2;
	int fd;
	int ret;

	/*
	 * If we already succeeded in opening the file with write access, then
	 * we're good.
	 */
	ret = fcntl(mo->mountfd, F_GETFL);
	if (ret < 0) {
		int error = errno;

		fprintf(stderr, "%s: %s: %s\n", mo->msgtag, mo->mountpoint,
			strerror(error));
		return -1;
	}

	if ((ret & O_ACCMODE) != O_RDONLY)
		return 0;

	ret = fstat(mo->mountfd, &sb1);
	if (ret) {
		int error = errno;

		fprintf(stderr, "%s: %s: %s\n",
			mo->msgtag, mo->mountpoint, strerror(error));
		return -1;
	}

	/* Try to reopen the file with write access this time. */
	fd = open(mo->real_mountpoint, O_WRONLY | O_CLOEXEC);
	if (fd < 0) {
		int error = errno;

		fprintf(stderr, "%s: %s: %s\n",
			mo->msgtag, mo->mountpoint, strerror(error));
		return -1;
	}

	/* Is this the same file? */
	ret = fstat(fd, &sb2);
	if (ret) {
		int error = errno;

		fprintf(stderr, "%s: %s: %s\n",
			mo->msgtag, mo->mountpoint, strerror(error));
		goto out_fd;
	}

	if (sb1.st_dev != sb2.st_dev || sb1.st_ino != sb2.st_ino) {
		fprintf(stderr, "%s: %s: Mount point moved during fuse startup.\n",
			mo->msgtag, mo->mountpoint);
		ret = -1;
		goto out_fd;
	}

	/*
	 * We reopened the same file with write access, everything is ok.  Swap
	 * the two file descriptors so that we retain our write access.
	 */
	ret = mo->mountfd;
	mo->mountfd = fd;
	fd = ret;
	ret = 0;
out_fd:
	close(fd);
	return ret;
}

static void adjust_nonroot_mount_flags(const struct mount_service *mo,
				       struct fuse_service_mount_command *oc)
{
	const struct mount_flags *mf;
	uint32_t ms_flags = ntohl(oc->ms_flags);

	/* only care that the unsafe flags are set to the value of @on */
	for (mf = mount_flags; mf->opt != NULL; mf++) {
		if (mf->safe)
			continue;
		if (!!(ms_flags & mf->flag) == !!mf->on) {
			ms_flags = (ms_flags & ~mf->flag) |
				   (mf->on ? 0 : mf->flag);

			fprintf(stderr, "%s: unsafe option %s ignored\n",
				mo->msgtag, mf->opt);
		}
	}

	oc->ms_flags = htonl(ms_flags);
}

/*
 * fuse.conf can limit the number of unprivileged fuse mounts.  For
 * unprivileged mounts (via setuid) we also require write access to the
 * mountpoint, and we'll only accept certain underlying filesystems.
 */
static int check_nonroot_access(struct mount_service *mo,
				struct fuse_service_mount_command *oc,
				const struct stat *stbuf)
{
	struct statfs fs_buf;
	int ret;

	ret = check_nonroot_mount_count(mo->msgtag);
	if (ret)
		return -EUSERS;

	ret = fstatfs(mo->mountfd, &fs_buf);
	if (ret) {
		int error = errno;

		fprintf(stderr, "%s: %s: %s\n",
			mo->msgtag, mo->mountpoint, strerror(error));
		return -error;
	}

	adjust_nonroot_mount_flags(mo, oc);

	drop_privs();
	if (S_ISDIR(stbuf->st_mode))
		ret = check_nonroot_dir_access(mo->msgtag,
					       mo->mountpoint,
					       mo->real_mountpoint,
					       stbuf);
	else
		ret = check_nonroot_file_access(mo);
	if (!ret)
		ret = check_nonroot_fstype(mo->msgtag, &fs_buf);
	restore_privs();

	return ret ? -EPERM : 0;
}

static int mount_service_handle_mount_cmd(struct mount_service *mo,
					  struct fuse_service_packet *p,
					  size_t psz)
{
	struct stat stbuf;
	struct fuse_service_mount_command *oc =
			container_of(p, struct fuse_service_mount_command, p);
	int ret;

	if (psz != sizeof(struct fuse_service_mount_command)) {
		fprintf(stderr, "%s: mount command wrong size %zu, expected %zu\n",
			mo->msgtag, psz, sizeof(*oc));
		return mount_service_send_reply(mo, EINVAL);
	}

	if (!mo->source) {
		fprintf(stderr, "%s: missing mount source parameter\n",
			mo->msgtag);
		return mount_service_send_reply(mo, EINVAL);
	}

	if (!mo->mountpoint) {
		fprintf(stderr, "%s: missing mount point parameter\n",
			mo->msgtag);
		return mount_service_send_reply(mo, EINVAL);
	}

	/*
	 * Call fstat again because access modes might have changed since we
	 * validated the file type.  This is still racy with mount since we
	 * don't lock the path target.
	 */
	ret = fstat(mo->mountfd, &stbuf);
	if (ret < 0) {
		int error = errno;

		fprintf(stderr, "%s: %s: %s\n",
			mo->msgtag, mo->mountpoint, strerror(error));
		return mount_service_send_reply(mo, error);
	}

	if (getuid() != 0) {
		ret = check_nonroot_access(mo, oc, &stbuf);
		if (ret)
			return mount_service_send_reply(mo, -ret);
	}

	if (mo->fsopenfd >= 0) {
		ret = mount_service_fsopen_mount(mo, oc, &stbuf);
		if (ret != FUSE_MOUNT_FALLBACK_NEEDED)
			return ret;
	}

	return mount_service_regular_mount(mo, oc, &stbuf);
}

static int mount_service_handle_bye_cmd(const struct mount_service *mo,
					struct fuse_service_packet *p,
					size_t psz)
{
	const struct fuse_service_bye_command *bc =
			container_of(p, struct fuse_service_bye_command, p);
	int ret;

	if (psz != sizeof(struct fuse_service_bye_command)) {
		fprintf(stderr, "%s: bye command wrong size %zu, expected %zu\n",
			mo->msgtag, psz, sizeof(*bc));
		return mount_service_send_reply(mo, EINVAL);
	}

	ret = ntohl(bc->exitcode);
	if (ret)
		fprintf(stderr, "%s: fuse server failed mount, check dmesg/logs for details.\n",
			mo->msgtag);

	return ret;
}

static void mount_service_destroy(struct mount_service *mo)
{
	close(mo->mountfd);
	close(mo->fusedevfd);
	close(mo->argvfd);
	close(mo->fsopenfd);
	shutdown(mo->sockfd, SHUT_RDWR);
	close(mo->sockfd);

	free(mo->source);
	free(mo->mountpoint);
	free(mo->real_mountpoint);
	free(mo->resv_mountpoint);
	free_mtabopts(mo);
	free(mo->mntopts);
	free(mo->subtype);

	memset(mo, 0, sizeof(*mo));
	mo->sockfd = -1;
	mo->argvfd = -1;
	mo->fusedevfd = -1;
	mo->mountfd = -1;
	mo->fsopenfd = -1;
}

int mount_service_main(int argc, char *argv[])
{
	const char *fusedev = fuse_mnt_get_devname();
	struct mount_service mo = { };
	bool running = true;
	int ret;

	if (argc < 3 || !strcmp(argv[1], "--help")) {
		printf("Usage: %s source mountpoint -t type [-o options]\n",
				argv[0]);
		return EXIT_FAILURE;
	}

	if (argv[0])
		mo.msgtag = argv[0];
	else
		mo.msgtag = "mount.service";

	/* Yes, we can be called with a null argv[0] */
	if (!argv[0])
		argv[0] = strdup("mount.service");
	if (!argv[0]) {
		fprintf(stderr, "Could not synthesize argv[0]!\n");
		return EXIT_FAILURE;
	}

	drop_privs();
	read_conf(mo.msgtag);
	restore_privs();

	ret = mount_service_init(&mo, argc, argv);
	if (ret)
		return EXIT_FAILURE;

	ret = mount_service_connect(&mo);
	if (ret == MOUNT_SERVICE_FALLBACK_NEEDED)
		goto out;
	if (ret) {
		ret = EXIT_FAILURE;
		goto out;
	}

	ret = mount_service_send_hello(&mo);
	if (ret) {
		ret = EXIT_FAILURE;
		goto out;
	}

	ret = mount_service_capture_args(&mo, argc, argv);
	if (ret) {
		ret = EXIT_FAILURE;
		goto out;
	}

	ret = mount_service_send_required_files(&mo, fusedev);
	if (ret) {
		ret = EXIT_FAILURE;
		goto out;
	}

	while (running) {
		struct fuse_service_packet *p = NULL;
		size_t sz;

		ret = mount_service_receive_command(&mo, &p, &sz);
		if (ret) {
			ret = EXIT_FAILURE;
			goto out;
		}

		switch (ntohl(p->magic)) {
		case FUSE_SERVICE_OPEN_CMD:
			ret = mount_service_handle_open_cmd(&mo, p, sz);
			break;
		case FUSE_SERVICE_OPEN_BDEV_CMD:
			ret = mount_service_handle_open_bdev_cmd(&mo, p, sz);
			break;
		case FUSE_SERVICE_FSOPEN_CMD:
			ret = mount_service_handle_fsopen_cmd(&mo, p, sz);
			break;
		case FUSE_SERVICE_SOURCE_CMD:
			ret = mount_service_handle_source_cmd(&mo, p, sz);
			break;
		case FUSE_SERVICE_MNTOPTS_CMD:
			ret = mount_service_handle_mntopts_cmd(&mo, p, sz);
			break;
		case FUSE_SERVICE_MNTPT_CMD:
			ret = mount_service_handle_mountpoint_cmd(&mo, p, sz,
					argc, (const char * const *)argv);
			break;
		case FUSE_SERVICE_MTABOPTS_CMD:
			ret = mount_service_handle_mtabopts_cmd(&mo, p, sz);
			break;
		case FUSE_SERVICE_MOUNT_CMD:
			ret = mount_service_handle_mount_cmd(&mo, p, sz);
			break;
		case FUSE_SERVICE_BYE_CMD:
			ret = mount_service_handle_bye_cmd(&mo, p, sz);
			free(p);
			goto out;
		default:
			fprintf(stderr, "%s: unrecognized packet 0x%x\n",
				mo.msgtag, ntohl(p->magic));
			ret = EXIT_FAILURE;
			break;
		}
		free(p);

		if (ret) {
			ret = EXIT_FAILURE;
			goto out;
		}
	}

	ret = EXIT_SUCCESS;
out:
	mount_service_destroy(&mo);
	return ret;
}

bool mount_service_present(const char *fstype)
{
	struct sockaddr_un name;
	struct stat stbuf;
	char path[PATH_MAX];
	const char *subtype;
	ssize_t written;
	int ret;

	subtype = mount_service_subtype(fstype);
	if (!subtype)
		return false;

	/*
	 * The full path to the socket must fit within the AF_UNIX socket path
	 * buffer, which is much shorter than PATH_MAX.
	 */
	written = snprintf(path, sizeof(path), FUSE_SERVICE_SOCKET_DIR "/%s",
			   subtype);
	if (written >= sizeof(name.sun_path) || written >= sizeof(path))
		return false;

	ret = stat(path, &stbuf);
	if (ret)
		return false;

	if (!S_ISSOCK(stbuf.st_mode))
		return false;

#ifdef HAVE_FACCESSAT
	/*
	 * Can we write to the service socket with the real uid?  This accounts
	 * for setuid and ACLs on the socket.  Note that we connect() to the
	 * socket having dropped setuid privileges.
	 */
	ret = faccessat(AT_FDCWD, path, W_OK, 0);
#else
	/* Can we write to the service socket with the real uid? */
	ret = access(path, W_OK);
#endif
	return ret == 0;
}
