/*
  FUSE: Filesystem in Userspace
  Copyright (C) 2001-2007  Miklos Szeredi <miklos@szeredi.hu>

  Architecture-independent mounting code.

  This program can be distributed under the terms of the GNU LGPLv2.
  See the file LGPL2.txt.
*/

#include "fuse_config.h"
#include "mount_util.h"
#ifdef __linux__
#include "mount_i_linux.h"
#endif

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <dirent.h>
#include <errno.h>
#include <fcntl.h>
#include <limits.h>
#include <paths.h>
#if !defined( __NetBSD__) && !defined(__FreeBSD__) && !defined(__DragonFly__) && !defined(__ANDROID__)
#include <mntent.h>
#else
#define IGNORE_MTAB
#endif
#include <sys/stat.h>
#include <sys/wait.h>

#include "fuse_mount_compat.h"

#include <sys/param.h>

/* Environment variable for FUSE kernel device */
#define FUSE_KERN_DEVICE_ENV "FUSE_KERN_DEVICE"
#define FUSE_DEF_KERN_DEVICE "/dev/fuse"

#if defined(__NetBSD__) || defined(__FreeBSD__) || defined(__DragonFly__) || defined(__FreeBSD_kernel__)
#include <sys/mount.h>
#ifdef __NetBSD__
#include <perfuse.h>
#endif

#define umount2(mnt, flags) unmount(mnt, ((flags) == 2) ? MNT_FORCE : 0)

#define MS_RDONLY	MNT_RDONLY
#define MS_NOSUID	MNT_NOSUID
#ifdef MNT_NODEV
#define MS_NODEV	MNT_NODEV
#else
#define MS_NODEV	0
#endif
#define MS_NOEXEC	MNT_NOEXEC
#define MS_SYNCHRONOUS	MNT_SYNCHRONOUS
#define MS_NOATIME	MNT_NOATIME
#define MS_NOSYMFOLLOW	0
#define MS_DIRSYNC	0

/* BSD doesn't have these, define as 0 */
#ifndef MS_NODIRATIME
#define MS_NODIRATIME 0
#endif
#ifndef MS_RELATIME
#define MS_RELATIME 0
#endif
#ifndef MS_STRICTATIME
#define MS_STRICTATIME 0
#endif

#endif /* BSD */

/*
 * MOUNT_ATTR_* constants from linux/mount.h for the new mount API.
 * If not available from system headers (older kernels), define to 0
 * so the mount_attr field will indicate these are fsconfig flags.
 * The new mount API (fsmount) won't be available on such systems anyway.
 */
#ifndef MOUNT_ATTR_RDONLY
#define MOUNT_ATTR_RDONLY	0
#endif
#ifndef MOUNT_ATTR_NOSUID
#define MOUNT_ATTR_NOSUID	0
#endif
#ifndef MOUNT_ATTR_NODEV
#define MOUNT_ATTR_NODEV	0
#endif
#ifndef MOUNT_ATTR_NOEXEC
#define MOUNT_ATTR_NOEXEC	0
#endif
#ifndef MOUNT_ATTR_NOATIME
#define MOUNT_ATTR_NOATIME	0
#endif
#ifndef MOUNT_ATTR_RELATIME
#define MOUNT_ATTR_RELATIME	0
#endif
#ifndef MOUNT_ATTR_STRICTATIME
#define MOUNT_ATTR_STRICTATIME	0
#endif
#ifndef MOUNT_ATTR_NODIRATIME
#define MOUNT_ATTR_NODIRATIME	0
#endif
#ifndef MOUNT_ATTR_NOSYMFOLLOW
#define MOUNT_ATTR_NOSYMFOLLOW	0
#endif

/*
 * is_fsconfig and mount_attr are independent: ro/rw need both legs
 * (SB_RDONLY on the superblock via fsconfig SET_FLAG, plus
 * MOUNT_ATTR_RDONLY on the vfsmount via fsmount). Everything else is
 * exclusively one or the other.
 */
const struct mount_flags mount_flags[] = {
/* opt            flag             on  safe  fsconfig  mount_attr */
{"rw",           MS_RDONLY,        0,  1,    1,        MOUNT_ATTR_RDONLY},
{"ro",           MS_RDONLY,        1,  1,    1,        MOUNT_ATTR_RDONLY},
{"suid",         MS_NOSUID,        0,  0,    0,        MOUNT_ATTR_NOSUID},
{"nosuid",       MS_NOSUID,        1,  1,    0,        MOUNT_ATTR_NOSUID},
{"dev",          MS_NODEV,         0,  1,    0,        MOUNT_ATTR_NODEV},
{"nodev",        MS_NODEV,         1,  1,    0,        MOUNT_ATTR_NODEV},
{"exec",         MS_NOEXEC,        0,  1,    0,        MOUNT_ATTR_NOEXEC},
{"noexec",       MS_NOEXEC,        1,  1,    0,        MOUNT_ATTR_NOEXEC},
{"async",        MS_SYNCHRONOUS,   0,  1,    1,        0},
{"sync",         MS_SYNCHRONOUS,   1,  1,    1,        0},
{"noatime",      MS_NOATIME,       1,  1,    0,        MOUNT_ATTR_NOATIME},
{"nodiratime",   MS_NODIRATIME,    1,  1,    0,        MOUNT_ATTR_NODIRATIME},
{"norelatime",   MS_RELATIME,      0,  1,    0,        MOUNT_ATTR_RELATIME},
{"nostrictatime", MS_STRICTATIME,  0,  1,    0,        MOUNT_ATTR_STRICTATIME},
{"symfollow",    MS_NOSYMFOLLOW,   0,  1,    0,        MOUNT_ATTR_NOSYMFOLLOW},
{"nosymfollow",  MS_NOSYMFOLLOW,   1,  1,    0,        MOUNT_ATTR_NOSYMFOLLOW},
{"dirsync",      MS_DIRSYNC,       1,  1,    1,        0},
{NULL,           0,                0,  0,    0,        0}
};

#ifdef IGNORE_MTAB
#define mtab_needs_update(mnt) 0
#else
static int mtab_needs_update(const char *mnt)
{
	int res;
	struct stat stbuf;

	/* If mtab is within new mount, don't touch it */
	if (strncmp(mnt, _PATH_MOUNTED, strlen(mnt)) == 0 &&
	    _PATH_MOUNTED[strlen(mnt)] == '/')
		return 0;

	/*
	 * Skip mtab update if /etc/mtab:
	 *
	 *  - doesn't exist,
	 *  - is on a read-only filesystem.
	 */
	res = lstat(_PATH_MOUNTED, &stbuf);
	if (res == -1) {
		if (errno == ENOENT)
			return 0;
	} else {
		uid_t ruid;
		int err;

		ruid = getuid();
		if (ruid != 0)
			setreuid(0, -1);

		res = access(_PATH_MOUNTED, W_OK);
		err = (res == -1) ? errno : 0;
		if (ruid != 0)
			setreuid(ruid, -1);

		if (err == EROFS)
			return 0;

		res = access("/run/mount/utab", F_OK);
		if (res == -1)
			return 0;
	}

	return 1;
}
#endif /* IGNORE_MTAB */

static int add_mount(const char *progname, const char *fsname,
		       const char *mnt, const char *type, const char *opts)
{
	int res;
	int status;
	sigset_t blockmask;
	sigset_t oldmask;

	sigemptyset(&blockmask);
	sigaddset(&blockmask, SIGCHLD);
	res = sigprocmask(SIG_BLOCK, &blockmask, &oldmask);
	if (res == -1) {
		fprintf(stderr, "%s: sigprocmask: %s\n", progname, strerror(errno));
		return -1;
	}

	res = fork();
	if (res == -1) {
		fprintf(stderr, "%s: fork: %s\n", progname, strerror(errno));
		goto out_restore;
	}
	if (res == 0) {
		char *env = NULL;

		sigprocmask(SIG_SETMASK, &oldmask, NULL);

		if(setuid(geteuid()) == -1) {
			fprintf(stderr, "%s: setuid: %s\n", progname, strerror(errno));
			res = -1;
			goto out_restore;
		}

		execle("/bin/mount", "/bin/mount", "--no-canonicalize", "-i",
		       "-f", "-t", type, "-o", opts, fsname, mnt, NULL, &env);
		fprintf(stderr, "%s: failed to execute /bin/mount: %s\n",
			progname, strerror(errno));
		exit(1);
	}
	res = waitpid(res, &status, 0);
	if (res == -1)
		fprintf(stderr, "%s: waitpid: %s\n", progname, strerror(errno));

	if (status != 0)
		res = -1;

 out_restore:
	sigprocmask(SIG_SETMASK, &oldmask, NULL);

	return res;
}

int fuse_mnt_add_mount(const char *progname, const char *fsname,
		       const char *mnt, const char *type, const char *opts)
{
	if (!mtab_needs_update(mnt))
		return 0;

	return add_mount(progname, fsname, mnt, type, opts);
}

static int exec_umount(const char *progname, const char *rel_mnt, int lazy)
{
	int res;
	int status;
	sigset_t blockmask;
	sigset_t oldmask;

	sigemptyset(&blockmask);
	sigaddset(&blockmask, SIGCHLD);
	res = sigprocmask(SIG_BLOCK, &blockmask, &oldmask);
	if (res == -1) {
		fprintf(stderr, "%s: sigprocmask: %s\n", progname, strerror(errno));
		return -1;
	}

	res = fork();
	if (res == -1) {
		fprintf(stderr, "%s: fork: %s\n", progname, strerror(errno));
		goto out_restore;
	}
	if (res == 0) {
		char *env = NULL;

		sigprocmask(SIG_SETMASK, &oldmask, NULL);

		if(setuid(geteuid()) == -1) {
			fprintf(stderr, "%s: setuid: %s\n", progname, strerror(errno));
			res = -1;
			goto out_restore;
		}

		if (lazy) {
			execle("/bin/umount", "/bin/umount", "-i", rel_mnt,
			       "-l", NULL, &env);
		} else {
			execle("/bin/umount", "/bin/umount", "-i", rel_mnt,
			       NULL, &env);
		}
		fprintf(stderr, "%s: failed to execute /bin/umount: %s\n",
			progname, strerror(errno));
		exit(1);
	}
	res = waitpid(res, &status, 0);
	if (res == -1)
		fprintf(stderr, "%s: waitpid: %s\n", progname, strerror(errno));

	if (status != 0) {
		res = -1;
	}

 out_restore:
	sigprocmask(SIG_SETMASK, &oldmask, NULL);
	return res;

}

int fuse_mnt_umount(const char *progname, const char *abs_mnt,
		    const char *rel_mnt, int lazy)
{
	if (!mtab_needs_update(abs_mnt)) {
		int res = umount2(rel_mnt, lazy ? 2 : 0);
		if (res == -1)
			fprintf(stderr, "%s: failed to unmount %s: %s\n",
				progname, abs_mnt, strerror(errno));
		return res;
	}

	return exec_umount(progname, rel_mnt, lazy);
}

static int remove_mount(const char *progname, const char *mnt)
{
	int res;
	int status;
	sigset_t blockmask;
	sigset_t oldmask;

	sigemptyset(&blockmask);
	sigaddset(&blockmask, SIGCHLD);
	res = sigprocmask(SIG_BLOCK, &blockmask, &oldmask);
	if (res == -1) {
		fprintf(stderr, "%s: sigprocmask: %s\n", progname, strerror(errno));
		return -1;
	}

	res = fork();
	if (res == -1) {
		fprintf(stderr, "%s: fork: %s\n", progname, strerror(errno));
		goto out_restore;
	}
	if (res == 0) {
		char *env = NULL;

		sigprocmask(SIG_SETMASK, &oldmask, NULL);

		if(setuid(geteuid()) == -1) {
			fprintf(stderr, "%s: setuid: %s\n", progname, strerror(errno));
			res = -1;
			goto out_restore;
		}

		execle("/bin/umount", "/bin/umount", "--no-canonicalize", "-i",
		       "--fake", mnt, NULL, &env);
		fprintf(stderr, "%s: failed to execute /bin/umount: %s\n",
			progname, strerror(errno));
		exit(1);
	}
	res = waitpid(res, &status, 0);
	if (res == -1)
		fprintf(stderr, "%s: waitpid: %s\n", progname, strerror(errno));

	if (status != 0)
		res = -1;

 out_restore:
	sigprocmask(SIG_SETMASK, &oldmask, NULL);
	return res;
}

int fuse_mnt_remove_mount(const char *progname, const char *mnt)
{
	if (!mtab_needs_update(mnt))
		return 0;

	return remove_mount(progname, mnt);
}

char *fuse_mnt_resolve_path(const char *progname, const char *orig)
{
	char buf[PATH_MAX];
	char *copy;
	char *dst;
	char *end;
	const char *lastcomp;
	const char *toresolv;

	if (!orig[0]) {
		fprintf(stderr, "%s: invalid mountpoint '%s'\n", progname,
			orig);
		return NULL;
	}

	copy = strdup(orig);
	if (copy == NULL) {
		fprintf(stderr, "%s: failed to allocate memory\n", progname);
		return NULL;
	}

	toresolv = copy;
	lastcomp = NULL;
	for (end = copy + strlen(copy) - 1; end > copy && *end == '/'; end --);
	if (end[0] != '/') {
		char *tmp;
		end[1] = '\0';
		tmp = strrchr(copy, '/');
		if (tmp == NULL) {
			lastcomp = copy;
			toresolv = ".";
		} else {
			lastcomp = tmp + 1;
			if (tmp == copy)
				toresolv = "/";
		}
		if (strcmp(lastcomp, ".") == 0 || strcmp(lastcomp, "..") == 0) {
			lastcomp = NULL;
			toresolv = copy;
		}
		else if (tmp)
			tmp[0] = '\0';
	}
	if (realpath(toresolv, buf) == NULL) {
		fprintf(stderr, "%s: bad mount point %s: %s\n", progname, orig,
			strerror(errno));
		free(copy);
		return NULL;
	}
	if (lastcomp == NULL)
		dst = strdup(buf);
	else {
		dst = (char *) malloc(strlen(buf) + 1 + strlen(lastcomp) + 1);
		if (dst) {
			unsigned buflen = strlen(buf);
			if (buflen && buf[buflen-1] == '/')
				sprintf(dst, "%s%s", buf, lastcomp);
			else
				sprintf(dst, "%s/%s", buf, lastcomp);
		}
	}
	free(copy);
	if (dst == NULL)
		fprintf(stderr, "%s: failed to allocate memory\n", progname);
	return dst;
}

int fuse_mnt_check_fuseblk(void)
{
	char buf[256];
	FILE *f = fopen("/proc/filesystems", "r");
	if (!f)
		return 1;

	while (fgets(buf, sizeof(buf), f))
		if (strstr(buf, "fuseblk\n")) {
			fclose(f);
			return 1;
		}

	fclose(f);
	return 0;
}

int fuse_mnt_parse_fuse_fd(const char *mountpoint)
{
	unsigned int fd;
	int len = 0;

	if (mountpoint == NULL) {
		fprintf(stderr, "Invalid null-ptr mount-point!\n");
		return -1;
	}

	if (sscanf(mountpoint, "/dev/fd/%u%n", &fd, &len) == 1 &&
	    len == strlen(mountpoint)) {
		if (fd > INT_MAX) {
			fprintf(stderr, "%s: fd=%u > INT_MAX\n", __func__, fd);
			return -1;
		}
		return (int)fd;
	}

	return -1;
}

int fuse_mnt_add_mount_helper(const char *mnt, const char *source,
			       const char *type, const char *mtab_opts)
{
#ifndef IGNORE_MTAB
	if (geteuid() == 0) {
		char *newmnt = fuse_mnt_resolve_path("fuse", mnt);
		int res;

		if (!newmnt)
			return -1;

		res = fuse_mnt_add_mount("fuse", source, newmnt, type,
					 mtab_opts);
		free(newmnt);
		return res;
	}
#endif
	(void)mnt;
	(void)source;
	(void)type;
	(void)mtab_opts;
	return 0;
}

const char *fuse_mnt_get_devname(void)
{
	const char *devname = getenv(FUSE_KERN_DEVICE_ENV);
	struct stat stbuf;

	if (!devname)
		return FUSE_DEF_KERN_DEVICE;

	/* Validation 1: Must start with /dev/ */
	if (strncmp(devname, "/dev/", 5) != 0) {
		fprintf(stderr,
			"fuse: invalid device name from %s: %s (must start with /dev/)\n",
			FUSE_KERN_DEVICE_ENV, devname);
		return "/dev/fuse";
	}

	/* Validation 2: Must be a character device */
	if (stat(devname, &stbuf) == 0) {
		if (!S_ISCHR(stbuf.st_mode)) {
			fprintf(stderr,
				"fuse: invalid device from %s: %s (not a character device)\n",
				FUSE_KERN_DEVICE_ENV, devname);
			return "/dev/fuse";
		}
		/* Valid character device */
		return devname;
	}
	/*
	 * stat() failed - device might not exist yet, but path is valid.
	 * Let it through and fail later in open() with a better error message.
	 */
	return devname;
}

char *fuse_mnt_build_source(const char *fsname, const char *subtype,
			     const char *devname, bool use_subtype_prefix)
{
	char *source;
	int ret;

	if (use_subtype_prefix && fsname && subtype) {
		ret = asprintf(&source, "%s#%s", subtype, fsname);
	} else {
		const char *src_str;

		if (fsname)
			src_str = fsname;
		else if (subtype)
			src_str = subtype;
		else
			src_str = devname;
		ret = asprintf(&source, "%s", src_str);
	}

	if (ret == -1)
		return NULL;

	return source;
}

char *fuse_mnt_build_type(int blkdev, const char *subtype)
{
	char *type;
	int ret;

	if (subtype)
		ret = asprintf(&type, "%s.%s", blkdev ? "fuseblk" : "fuse",
			       subtype);
	else
		ret = asprintf(&type, "%s", blkdev ? "fuseblk" : "fuse");

	if (ret == -1)
		return NULL;

	return type;
}
