/* $NetBSD$ */

/*
 * Copyright (c) 2009 Arnaud Ysmal.  All Rights Reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS
 * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

#include <sys/types.h>
#include <sys/dirent.h>
#include <sys/fstypes.h>
#include <sys/mount.h>
#include <sys/sysctl.h>

#include <assert.h>
#include <dirent.h>
#include <err.h>
#include <errno.h>
#include <fcntl.h>
#include <mntopts.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#include <prop/proplib.h>

#include <rump/rump.h>
#include <rump/rump_syscalls.h>

#include "prop_mount.h"
#include "internal.h"

#define FSU_MAX_MOUNT (10)

static int	specific_getmntopts(prop_dictionary_t, char *);
static ssize_t	get_vfstypes(char *, size_t);
static int	mount_from_array(char *, int);
static int	mount_from_dict(prop_dictionary_t, const char *, int);

int		getnfsargs(char *, prop_dictionary_t);

static const struct mntopt mopts[] = {
	MOPT_STDOPTS,
	MOPT_FORCE,
	MOPT_UPDATE,
	MOPT_NULL
};

static char *mounted[FSU_MAX_MOUNT];
static int nbmounted = 0;

int
prop_mount(int *argc, char **argv[])
{
	prop_dictionary_t dict;
	mntoptparse_t mp;
	int ch, mntflags, rv;
	char *fs, *outputfile, *fspec, *progname;

	nbmounted = mntflags = rv = 0;
	dict = NULL;
	fs = outputfile = fspec = NULL;
	progname = (*argv)[0];
	
	if (*argc == 1)
		return -1;
	
	while ((ch = getopt(*argc, *argv, "f:m:o:s:t:w:")) != -1)
		switch (ch) {
		case 'f': /* load dictionary from a file */
			if (dict != NULL)
				return -1;
			dict = prop_dictionary_internalize_from_file(optarg);
			if (dict == NULL) {
				warn("prop_dictionary_create");
				return -1;
			}
			break;
		case 'm': /* mount from an array of prop_dictionary */
			return mount_from_array(optarg, mntflags);
			/* NOTREACHED */
		case 's': /* use specific fs mount options */
			if (dict == NULL &&
			    (dict = prop_dictionary_create()) == NULL) {
					warn("prop_dictionary_create");
					return -1;
			}
			specific_getmntopts(dict, optarg);
			break;
		case 'o': /* use generic mount options */
			mp = getmntopts(optarg, mopts, &mntflags, 0);
			if (mp == NULL)
				return -1;
			freemntopts(mp);
			break;
		case 't': /* use a given file system */
			fs = optarg;
			break;
		case 'w': /* write dictionary to a file */
			outputfile = optarg;
			break;
		default:
			return -1;
		}

	*argv += optind - 1;
	*argc -= optind - 1;
	(*argv)[0] = progname;

	optind = optreset = 1;

	/*
	 * fspec from file or given as "-o fspec=filename"
	 */
	if (dict != NULL)
		prop_dictionary_get_cstring(dict, "fspec", &fspec);
	else
		dict = prop_dictionary_create();

	if (dict == NULL ||
	    (fspec == NULL && (fspec = (*argv)[1]) == NULL) ||
	    !prop_dictionary_set_cstring(dict, "fspec", fspec))
		return -1;

	if (fspec == (*argv)[1]) {
		(*argv)[1] = progname;
		++(*argv);
		--(*argc);
	}

#ifdef NFS
	if (strncmp(fspec, "nfs://", 6) == 0) {
		if (fspec[6] == '\0' || getnfsargs(fspec + 6, dict) == 0)
			return -1;
		fs = strdup("nfs");
	}
#endif

	if (fs != NULL)
		prop_dictionary_set_cstring(dict, "fs", fs);

	/* if -w, write the dictionary to outputfile */
	if (outputfile != NULL)
		prop_dictionary_externalize_to_file(dict, outputfile);

	rump_init();
	fsu_ukfs_modload_dir("/usr/lib");

	rv = mount_from_dict(dict, "/", mntflags);
	prop_object_release(dict);
	return rv;
}

/* XXX: now rump allows to mkdir mount points */
void
prop_unmount(void)
{

	if (nbmounted != 0) {
		for (; nbmounted > 0; --nbmounted)
			rump_sys_unmount(mounted[nbmounted - 1], MNT_FORCE);
	} else
		rump_sys_unmount("/", MNT_FORCE);
}

static int
mount_from_array(char *dicts, int mntflags)
{
	int count, i, rv;
	prop_array_t fss;
	prop_dictionary_t cur;
	char *mntpath;

	mntpath = NULL;

	fss = prop_array_internalize_from_file(dicts);
	if (fss == NULL)
		return -1;

	count = prop_array_count(fss);
	if (count == 0)
		return -1;

	rump_init();
	fsu_ukfs_modload_dir("/usr/lib");

	cur = prop_dictionary_create();
	prop_dictionary_set_cstring(cur, "fspec", "mem://");
	prop_dictionary_set_cstring(cur, "fs", "tmpfs");
	mount_from_dict(cur, "/", 0);
	prop_object_release(cur);
	cur = NULL;
	
	for (rv = i = 0; i < count; ++i) {
		if ((cur = prop_array_get(fss, i)) == NULL)
			continue;
		
		mntpath = NULL;
		prop_dictionary_get_cstring(cur, "mntpath", &mntpath);
		if (nbmounted < FSU_MAX_MOUNT && mntpath != NULL) {
			rv = rump_sys_mkdir(mntpath, 0777);
			if (rv != 0)
				warn("mkdir: %s", mntpath);
			rv = mount_from_dict(cur, mntpath, mntflags);
			if (rv != 0)
				warn("mount_from_dict: %s", mntpath);
			mounted[nbmounted++] = mntpath;
		}
		prop_object_release(cur);
		cur = NULL;
	}
	return rv;
}

static int
mount_from_dict(prop_dictionary_t dict, const char *mntpath, int mntflags)
{
	struct plistref mntdata;
	int rv, regged;
	char *fspec, *fs, sfs[1024];
	struct stat sb;
	
	regged = rv = 0;
	fspec = fs = NULL;

	prop_dictionary_get_cstring(dict, "fspec", &fspec);
	prop_dictionary_get_cstring(dict, "fs", &fs);

	/* using plistref to pass the dictionary to the mount syscall */
	mntdata.pref_plist = prop_dictionary_externalize(dict);
	mntdata.pref_len = strlen(mntdata.pref_plist) + 1;

	if (fspec != NULL && stat(fspec, &sb) == 0) {
		rv = rump_etfs_register(fspec, fspec, RUMP_ETFS_BLK);
		if (rv != 0)
			goto out;
		else
			regged = 1;
	}

	if (fs != NULL) {
		rv = rump_sys_mount(fs, mntpath, mntflags, &mntdata, sizeof(mntdata));
		goto out;
	}
	
	if (get_vfstypes(sfs, sizeof(sfs)) != 0) {
#ifdef DEBUG
		printf("supported fs: %s\n", sfs);
#endif
		fs = strtok(sfs, " ");
		do {
#ifdef DEBUG
			printf("Trying %s: ", fs);
#endif
			rv = rump_sys_mount(fs, mntpath, mntflags,
			    &mntdata, sizeof(mntdata));
		} while ((fs = strtok(NULL, " ")) != NULL && rv != 0);
	}

out:
	if (regged)
		rump_etfs_remove(fspec);
	if (rv != 0)
		warn("%s: %i", mntpath, rv);
	return rv;
}

const char *
fsu_mount_usage(void)
{

	return "[-t fstype] [-o generic opts] [-s fs specific opts] "
	    "[-w outputfile.xml] [-f inputfile.xml] [device]";
}

/*
 * proplib uses 64bits int for numbers
 */
int
specific_getmntopts(prop_dictionary_t dict, char *opts)
{
	char *opt, *sep, *ep;
	int64_t tmp;
	bool bval;
	
	opt = strtok(opts, ",");
	do {
		sep = strchr(opt, '=');
		if (sep != NULL && *(sep + 1) == '\0') {
			*sep = '\0';
			sep = NULL;
		}
		if (sep != NULL) {
			*sep++ = 0;
			if (strstr(opt, "uid") != NULL)
				tmp = a_uid(sep);
			else if (strstr(opt, "gid") != NULL)
				tmp = a_gid(sep);
			else if (strstr(opt, "mode") != NULL ||
			    strstr(opt, "mask") != NULL)
				tmp = a_mask(sep);
			else {
				tmp = strtoll(sep, &ep, 0);
				if (*ep != '\0' ||
				    ((tmp == LLONG_MIN || tmp == LLONG_MAX) &&
					errno == ERANGE)) {
					prop_dictionary_set_cstring(dict,
					    opt, sep);
#ifdef DEBUG
					printf("Adding (str): %s = %s\n",
					    opt, sep);
#endif
					continue;
				}
			}
#ifdef DEBUG
			printf("Adding (int): %s = %"PRIu64"\n", opt, tmp);
#endif
			if (tmp >= 0)
				prop_dictionary_set_uint64(dict, opt, tmp);
			else
				prop_dictionary_set_int64(dict, opt, tmp);

		} else {
			if (*opt == 'n' && *(opt + 1) == 'o') {
				/* XXX: handle options starting with no */
#ifdef DEBUG
				printf("Adding (bool): %s = true\n", opt);
#endif	
				prop_dictionary_set_bool(dict, opt, 1);
				bval = 0;
				opt += 2;
			} else
				bval = 1;
#ifdef DEBUG
			printf("Adding (bool): %s = %s\n", opt,
			    bval == 1 ? "true" : "false");
#endif
			prop_dictionary_set_bool(dict, opt, bval);
		}
	} while ((opt = strtok(NULL, ",")) != NULL);

	return 0;
}

/* function coming from libukfs */
/*
 * Copyright (c) 2007, 2008  Antti Kantee.  All Rights Reserved.
 *
 * Development of this software was supported by the
 * Finnish Cultural Foundation.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS
 * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */
ssize_t
get_vfstypes(char *buf, size_t buflen)
{
	int mib[3];
	struct sysctlnode q, ans[128];
	size_t alen;
	unsigned int i;

	mib[0] = CTL_VFS;
	mib[1] = VFS_GENERIC;
	mib[2] = CTL_QUERY;
	alen = sizeof(ans);

	memset(&q, 0, sizeof(q));
	q.sysctl_flags = SYSCTL_VERSION;

	if (rump_sys___sysctl(mib, 3, ans, &alen, &q, sizeof(q)) == -1) {
		return -1;
	}

	for (i = 0; i < alen/sizeof(ans[0]); i++)
		if (strcmp("fstypes", ans[i].sysctl_name) == 0)
			break;
	if (i == alen/sizeof(ans[0])) {
		errno = ENXIO;
		return -1;
	}

	mib[0] = CTL_VFS;
	mib[1] = VFS_GENERIC;
	mib[2] = ans[i].sysctl_num;

	if (rump_sys___sysctl(mib, 3, buf, &buflen, NULL, 0) == -1) {
		return -1;
	}

	return buflen;
}
