OpenSolaris_b135/lib/brand/lx/lx_brand/common/mount.c

Compare this file to the similar file:
Show the results in this format:

/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License (the "License").
 * You may not use this file except in compliance with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */

/*
 * Copyright 2007 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

#pragma ident	"%Z%%M%	%I%	%E% SMI"

#include <alloca.h>
#include <assert.h>
#include <ctype.h>
#include <fcntl.h>
#include <errno.h>
#include <signal.h>
#include <string.h>
#include <strings.h>
#include <nfs/mount.h>
#include <sys/types.h>
#include <sys/mount.h>
#include <sys/param.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>

#include <sys/lx_autofs.h>
#include <sys/lx_debug.h>
#include <sys/lx_misc.h>
#include <sys/lx_mount.h>

/*
 * support definitions
 */
union fh_buffer {
	struct nfs_fid	fh2;
	struct nfs_fh3	fh3;
	char		fh_data[NFS3_FHSIZE + 2];
};

typedef enum mount_opt_type {
	MOUNT_OPT_INVALID	= 0,
	MOUNT_OPT_NORMAL	= 1,	/* option value: none */
	MOUNT_OPT_UINT		= 2	/* option value: unsigned int */
} mount_opt_type_t;

typedef struct mount_opt {
	char			*mo_name;
	mount_opt_type_t	mo_type;
} mount_opt_t;


/*
 * Globals
 */
mount_opt_t lofs_options[] = {
	{ NULL, MOUNT_OPT_INVALID }
};

mount_opt_t lx_proc_options[] = {
	{ NULL, MOUNT_OPT_INVALID }
};

mount_opt_t lx_autofs_options[] = {
	{ LX_MNTOPT_FD,		MOUNT_OPT_UINT },
	{ LX_MNTOPT_PGRP,	MOUNT_OPT_UINT },
	{ LX_MNTOPT_MINPROTO,	MOUNT_OPT_UINT },
	{ LX_MNTOPT_MAXPROTO,	MOUNT_OPT_UINT },
};


/*
 * i_lx_opt_verify() - Check the mount options.
 *
 * You might wonder why we're being so strict about the mount options
 * we allow.  The reason is that normally all mount option verification
 * is done by the Solaris userland mount command.  Once mount options
 * are passed to the kernel, invalid options are simply ignored.  So
 * if we actually want to catch requests for functionality that we
 * don't support, or if we want to make sure that we don't randomly
 * enable options that we haven't check to make sure they have the
 * same syntax on Linux and Solaris, we need to reject any options
 * we don't know to be ok here.
 */
static int
i_lx_opt_verify(char *opts, mount_opt_t *mop)
{
	int	opts_len = strlen(opts);
	char	*opts_tmp, *opt;
	int	opt_len, i;

	assert((opts != NULL) && (mop != NULL));

	/* If no options were specified, there's no problem. */
	if (opts_len == 0)
		return (1);

	/* If no options are allowed, fail. */
	if (mop[0].mo_name == NULL)
		return (0);

	/* Don't accept leading or trailing ','. */
	if ((opts[0] == ',') || (opts[opts_len] == ','))
		return (0);

	/* Don't accept sequential ','. */
	for (i = 1; i < opts_len; i++)
		if ((opts[i - 1] ==  ',') && (opts[i] ==  ','))
			return (0);

	/*
	 * We're going to use strtok() which modifies the target
	 * string so make a temporary copy.
	 */
	opts_tmp = SAFE_ALLOCA(opts_len);
	if (opts_tmp == NULL)
		return (-1);
	bcopy(opts, opts_tmp, opts_len + 1);

	/* Verify each prop one at a time. */
	opt = strtok(opts_tmp, ",");
	opt_len = strlen(opt);
	for (;;) {

		/* Check for matching option/value pair. */
		for (i = 0; mop[i].mo_name != NULL; i++) {
			char	*ovalue;
			int	ovalue_len, mo_len;

			/* If the options is too short don't bother comparing */
			mo_len = strlen(mop[i].mo_name);
			if (opt_len < mo_len) {
				/* Keep trying to find a match. */
				continue;
			}

			/* Compare the option to an allowed option. */
			if (strncmp(mop[i].mo_name, opt, mo_len) != 0) {
				/* Keep trying to find a match. */
				continue;
			}

			if (mop[i].mo_type == MOUNT_OPT_NORMAL) {
				/* The option doesn't take a value. */
				if (opt_len == mo_len) {
					/* This option is ok. */
					break;
				} else {
					/* Keep trying to find a match. */
					continue;
				}
			}

			/* This options takes a value. */
			if ((opt_len == mo_len) || (opt[mo_len] != '=')) {
				/* Keep trying to find a match. */
				continue;
			}

			/* We have an option match.  Verify option value. */
			ovalue = &opt[mo_len] + 1;
			ovalue_len = strlen(ovalue);

			/* Value can't be zero length string. */
			if (ovalue_len == 0)
				return (0);

			if (mop[i].mo_type == MOUNT_OPT_UINT) {
				int j;
				/* Verify that value is an unsigned int. */
				for (j = 0; j < ovalue_len; j++)
					if (!isdigit(ovalue[j]))
						return (0);
			} else {
				/* Unknown option type specified. */
				assert(0);
			}

			/* The option is ok. */
			break;
		}

		/* If there were no matches this is an unsupported option. */
		if (mop[i].mo_name == NULL)
			return (0);

		/* This option is ok, move onto the next option. */
		if ((opt = strtok(NULL, ",")) == NULL)
			break;
		opt_len = strlen(opt);
	};

	/* We verified all the options. */
	return (1);
}

static int
i_add_option(char *option, char *buf, size_t buf_size)
{
	char *fmt_str = NULL;

	assert((option != NULL) && (strlen(option) > 0));
	assert((buf != NULL) && (buf_size > 0));

	if (buf[0] == '\0') {
		fmt_str = "%s";
	} else {
		fmt_str = ",%s";
	}

	buf_size -= strlen(buf);
	buf += strlen(buf);

	/*LINTED*/
	if (snprintf(buf, buf_size, fmt_str, option) > (buf_size - 1))
		return (-EOVERFLOW);
	return (0);
}

static int
i_add_option_int(char *option, int val, char *buf, size_t buf_size)
{
	char *fmt_str = NULL;

	assert((option != NULL) && (strlen(option) > 0));
	assert((buf != NULL) && (buf_size > 0));

	if (buf[0] == '\0') {
		fmt_str = "%s=%d";
	} else {
		fmt_str = ",%s=%d";
	}

	buf_size -= strlen(buf);
	buf += strlen(buf);

	/*LINTED*/
	if (snprintf(buf, buf_size, fmt_str, option, val) > (buf_size - 1))
		return (-EOVERFLOW);
	return (0);
}

static int
i_make_nfs_args(lx_nfs_mount_data_t *lx_nmd, struct nfs_args *nfs_args,
    struct netbuf *nfs_args_addr, struct knetconfig *nfs_args_knconf,
    union fh_buffer *nfs_args_fh, struct sec_data *nfs_args_secdata,
    char *fstype, char *options, int options_size)
{
	struct stat	statbuf;
	int		i, rv, use_tcp;

	/* Sanity check the incomming Linux request. */
	if ((lx_nmd->nmd_rsize < 0) || (lx_nmd->nmd_wsize < 0) ||
	    (lx_nmd->nmd_timeo < 0) || (lx_nmd->nmd_retrans < 0) ||
	    (lx_nmd->nmd_acregmin < 0) || (lx_nmd->nmd_acregmax < 0) ||
	    (lx_nmd->nmd_acdirmax < 0)) {
		return (-EINVAL);
	}

	/*
	 * Additional sanity checks of incomming request.
	 *
	 * Some of the sanity checks below should probably return
	 * EINVAL (or some other error code) instead or ENOTSUP,
	 * but without experiminting on Linux to see how it
	 * deals with certain strange values there is no way
	 * to really know what we should return, hence we return
	 * ENOTSUP to tell us that eventually if we see some
	 * application hitting the problem we can go to a real
	 * Linux system, figure out how it deals with the situation
	 * and update our code to handle it in the same fashion.
	 */
	if (lx_nmd->nmd_version != 4) {
		lx_unsupported("unsupported nfs mount request, "
		    "unrecognized NFS mount structure: %d\n",
		    lx_nmd->nmd_version);
		return (-ENOTSUP);
	}
	if ((lx_nmd->nmd_flags & ~LX_NFS_MOUNT_SUPPORTED) != 0) {
		lx_unsupported("unsupported nfs mount request, "
		    "flags: 0x%x\n", lx_nmd->nmd_flags);
		return (-ENOTSUP);
	}
	if (lx_nmd->nmd_addr.sin_family != AF_INET) {
		lx_unsupported("unsupported nfs mount request, "
		    "transport address family: 0x%x\n",
		    lx_nmd->nmd_addr.sin_family);
		return (-ENOTSUP);
	}
	for (i = 0; i < LX_NMD_MAXHOSTNAMELEN; i++) {
		if (lx_nmd->nmd_hostname[i] == '\0')
			break;
	}
	if (i == 0) {
		lx_unsupported("unsupported nfs mount request, "
		    "no hostname specified\n");
		return (-ENOTSUP);
	}
	if (i == LX_NMD_MAXHOSTNAMELEN) {
		lx_unsupported("unsupported nfs mount request, "
		    "hostname not terminated\n");
		return (-ENOTSUP);
	}
	if (lx_nmd->nmd_namlen < i) {
		lx_unsupported("unsupported nfs mount request, "
		    "invalid namlen value: 0x%x\n", lx_nmd->nmd_namlen);
		return (-ENOTSUP);
	}
	if (lx_nmd->nmd_bsize != 0) {
		lx_unsupported("unsupported nfs mount request, "
		    "bsize value: 0x%x\n", lx_nmd->nmd_bsize);
		return (-ENOTSUP);
	}

	/* Initialize and clear the output structure pointers passed in. */
	bzero(nfs_args, sizeof (*nfs_args));
	bzero(nfs_args_addr, sizeof (*nfs_args_addr));
	bzero(nfs_args_knconf, sizeof (*nfs_args_knconf));
	bzero(nfs_args_fh, sizeof (*nfs_args_fh));
	bzero(nfs_args_secdata, sizeof (*nfs_args_secdata));
	nfs_args->addr = nfs_args_addr;
	nfs_args->knconf = nfs_args_knconf;
	nfs_args->fh = (caddr_t)nfs_args_fh;
	nfs_args->nfs_ext_u.nfs_extB.secdata = nfs_args_secdata;

	/* Check if we're using tcp. */
	use_tcp = (lx_nmd->nmd_flags & LX_NFS_MOUNT_TCP) ? 1 : 0;

	/*
	 * These seem to be the default flags used by Solaris for v2 and v3
	 * nfs mounts.
	 *
	 * Don't bother with NFSMNT_TRYRDMA since we always specify a
	 * transport (either udp or tcp).
	 */
	nfs_args->flags = NFSMNT_NEWARGS | NFSMNT_KNCONF | NFSMNT_INT |
	    NFSMNT_HOSTNAME;

	/* Translate some Linux mount flags into Solaris mount flags. */
	if (lx_nmd->nmd_flags & LX_NFS_MOUNT_SOFT)
		nfs_args->flags |= NFSMNT_SOFT;
	if (lx_nmd->nmd_flags & LX_NFS_MOUNT_INTR)
		nfs_args->flags |= NFSMNT_INT;
	if (lx_nmd->nmd_flags & LX_NFS_MOUNT_POSIX)
		nfs_args->flags |= NFSMNT_POSIX;
	if (lx_nmd->nmd_flags & LX_NFS_MOUNT_NOCTO)
		nfs_args->flags |= NFSMNT_NOCTO;
	if (lx_nmd->nmd_flags & LX_NFS_MOUNT_NOAC)
		nfs_args->flags |= NFSMNT_NOAC;
	if (lx_nmd->nmd_flags & LX_NFS_MOUNT_NONLM)
		nfs_args->flags |= NFSMNT_LLOCK;

	if ((lx_nmd->nmd_flags & LX_NFS_MOUNT_VER3) != 0) {
		(void) strcpy(fstype, "nfs3");
		if ((rv = i_add_option_int("vers", 3,
			    options, options_size)) != 0)
			return (rv);

		if (lx_nmd->nmd_root.lx_fh3_length >
		    sizeof (nfs_args_fh->fh3.fh3_u.data)) {
			lx_unsupported("unsupported nfs mount request, "
			    "nfs file handle length: 0x%x\n",
			    lx_nmd->nmd_root.lx_fh3_length);
			return (-ENOTSUP);
		}

		/* Set the v3 file handle info. */
		nfs_args_fh->fh3.fh3_length = lx_nmd->nmd_root.lx_fh3_length;
		bcopy(&lx_nmd->nmd_root.lx_fh3_data,
		    nfs_args_fh->fh3.fh3_u.data,
		    lx_nmd->nmd_root.lx_fh3_length);
	} else {
		/*
		 * Assume nfs v2.  Note that this could also be a v1
		 * mount request but there doesn't seem to be any difference
		 * in the parameters passed to the Linux mount system
		 * call for v1 or v2 mounts so there is no way of really
		 * knowing.
		 */
		(void) strcpy(fstype, "nfs");
		if ((rv = i_add_option_int("vers", 2,
			    options, options_size)) != 0)
			return (rv);

		/* Solaris seems to add this flag when using v2. */
		nfs_args->flags |= NFSMNT_SECDEFAULT;

		/* Set the v2 file handle info. */
		bcopy(&lx_nmd->nmd_old_root,
		    nfs_args_fh, sizeof (nfs_args_fh->fh2));
	}

	/*
	 * We can't use getnetconfig() here because there is no netconfig
	 * database in linux.
	 */
	nfs_args_knconf->knc_protofmly = "inet";
	if (use_tcp) {
		/*
		 * TCP uses NC_TPI_COTS_ORD semantics.
		 * See /etc/netconfig.
		 */
		nfs_args_knconf->knc_semantics = NC_TPI_COTS_ORD;
		nfs_args_knconf->knc_proto = "tcp";
		if ((rv = i_add_option("proto=tcp",
			    options, options_size)) != 0)
			return (rv);
		if (stat("/dev/tcp", &statbuf) != 0)
			return (-errno);
		nfs_args_knconf->knc_rdev = statbuf.st_rdev;
	} else {
		/*
		 * Assume UDP.  UDP uses NC_TPI_CLTS semantics.
		 * See /etc/netconfig.
		 */
		nfs_args_knconf->knc_semantics = NC_TPI_CLTS;
		nfs_args_knconf->knc_proto = "udp";
		if ((rv = i_add_option("proto=udp",
			    options, options_size)) != 0)
			return (rv);
		if (stat("/dev/udp", &statbuf) != 0)
			return (-errno);
		nfs_args_knconf->knc_rdev = statbuf.st_rdev;
	}

	/* Set the server address. */
	nfs_args_addr->maxlen = nfs_args_addr->len =
	    sizeof (struct sockaddr_in);
	nfs_args_addr->buf = (char *)&lx_nmd->nmd_addr;

	/* Set the server hostname string. */
	nfs_args->hostname = lx_nmd->nmd_hostname;

	/* Translate Linux nfs mount parameters into Solaris mount options. */
	if (lx_nmd->nmd_rsize != LX_NMD_DEFAULT_RSIZE) {
		if ((rv = i_add_option_int("rsize", lx_nmd->nmd_rsize,
			    options, options_size)) != 0)
			return (rv);
		nfs_args->rsize = lx_nmd->nmd_rsize;
		nfs_args->flags |= NFSMNT_RSIZE;
	}
	if (lx_nmd->nmd_wsize != LX_NMD_DEFAULT_WSIZE) {
		if ((rv = i_add_option_int("wsize", lx_nmd->nmd_wsize,
			    options, options_size)) != 0)
			return (rv);
		nfs_args->wsize = lx_nmd->nmd_wsize;
		nfs_args->flags |= NFSMNT_WSIZE;
	}
	if ((rv = i_add_option_int("timeo", lx_nmd->nmd_timeo,
		    options, options_size)) != 0)
		return (rv);
	nfs_args->timeo = lx_nmd->nmd_timeo;
	nfs_args->flags |= NFSMNT_TIMEO;
	if ((rv = i_add_option_int("retrans", lx_nmd->nmd_retrans,
		    options, options_size)) != 0)
		return (rv);
	nfs_args->retrans = lx_nmd->nmd_retrans;
	nfs_args->flags |= NFSMNT_RETRANS;
	if ((rv = i_add_option_int("acregmin", lx_nmd->nmd_acregmin,
		    options, options_size)) != 0)
		return (rv);
	nfs_args->acregmin = lx_nmd->nmd_acregmin;
	nfs_args->flags |= NFSMNT_ACREGMIN;
	if ((rv = i_add_option_int("acregmax", lx_nmd->nmd_acregmax,
		    options, options_size)) != 0)
		return (rv);
	nfs_args->acregmax = lx_nmd->nmd_acregmax;
	nfs_args->flags |= NFSMNT_ACREGMAX;
	if ((rv = i_add_option_int("acdirmin", lx_nmd->nmd_acdirmin,
		    options, options_size)) != 0)
		return (rv);
	nfs_args->acdirmin = lx_nmd->nmd_acdirmin;
	nfs_args->flags |= NFSMNT_ACDIRMIN;
	if ((rv = i_add_option_int("acdirmax", lx_nmd->nmd_acdirmax,
		    options, options_size)) != 0)
		return (rv);
	nfs_args->acdirmax = lx_nmd->nmd_acdirmax;
	nfs_args->flags |= NFSMNT_ACDIRMAX;

	/* We only support nfs with a security type of AUTH_SYS. */
	nfs_args->nfs_args_ext = NFS_ARGS_EXTB;
	nfs_args_secdata->secmod = AUTH_SYS;
	nfs_args_secdata->rpcflavor = AUTH_SYS;
	nfs_args_secdata->flags = 0;
	nfs_args_secdata->uid = 0;
	nfs_args_secdata->data = NULL;
	nfs_args->nfs_ext_u.nfs_extB.next = NULL;

	/*
	 * The Linux nfs mount command seems to pass an open socket fd
	 * to the kernel during the mount system call.  We don't need
	 * this fd on Solaris so just close it.
	 */
	(void) close(lx_nmd->nmd_fd);

	return (0);
}

int
lx_mount(uintptr_t p1, uintptr_t p2, uintptr_t p3, uintptr_t p4,
    uintptr_t p5)
{
	/* Linux input arguments. */
	const char		*sourcep = (const char *)p1;
	const char		*targetp = (const char *)p2;
	const char		*fstypep = (const char *)p3;
	unsigned int		flags = (unsigned int)p4;
	const void		*datap = (const void *)p5;

	/* Variables needed for all mounts. */
	char			source[MAXPATHLEN], target[MAXPATHLEN];
	char			fstype[MAXPATHLEN], options[MAXPATHLEN];
	int			sflags, rv;

	/* Variables needed for nfs mounts. */
	lx_nfs_mount_data_t	lx_nmd;
	struct nfs_args		nfs_args;
	struct netbuf 		nfs_args_addr;
	struct knetconfig	nfs_args_knconf;
	union fh_buffer		nfs_args_fh;
	struct sec_data		nfs_args_secdata;
	char			*sdataptr = NULL;
	int			sdatalen = 0;

	/* Initialize Solaris mount arguments. */
	sflags = MS_OPTIONSTR;
	options[0] = '\0';
	sdatalen = 0;

	/* Copy in parameters that are always present. */
	rv = uucopystr((void *)sourcep, &source, sizeof (source));
	if ((rv == -1) || (rv == sizeof (source)))
		return (-EFAULT);

	rv = uucopystr((void *)targetp, &target, sizeof (target));
	if ((rv == -1) || (rv == sizeof (target)))
		return (-EFAULT);

	rv = uucopystr((void *)fstypep, &fstype, sizeof (fstype));
	if ((rv == -1) || (rv == sizeof (fstype)))
		return (-EFAULT);

	lx_debug("\tlinux mount source: %s", source);
	lx_debug("\tlinux mount target: %s", target);
	lx_debug("\tlinux mount fstype: %s", fstype);

	/* Make sure we support the requested mount flags. */
	if ((flags & ~LX_MS_SUPPORTED) != 0) {
		lx_unsupported(
		    "unsupported mount flags: 0x%x", flags);
		return (-ENOTSUP);
	}

	/* Do filesystem specific mount work. */
	if (flags & LX_MS_BIND) {

		/* If MS_BIND is set, we turn this into a lofs mount.  */
		(void) strcpy(fstype, "lofs");

		/* Copy in Linux mount options. */
		if (datap != NULL) {
			rv = uucopystr((void *)datap,
			    options, sizeof (options));
			if ((rv == -1) || (rv == sizeof (options)))
				return (-EFAULT);
		}
		lx_debug("\tlinux mount options: \"%s\"", options);

		/* Verify Linux mount options. */
		if (i_lx_opt_verify(options, lofs_options) == 0) {
			lx_unsupported("unsupported lofs mount options");
			return (-ENOTSUP);
		}
	} else if (strcmp(fstype, "proc") == 0) {

		/* Translate proc mount requests to lx_proc requests. */
		(void) strcpy(fstype, "lx_proc");

		/* Copy in Linux mount options. */
		if (datap != NULL) {
			rv = uucopystr((void *)datap,
			    options, sizeof (options));
			if ((rv == -1) || (rv == sizeof (options)))
				return (-EFAULT);
		}
		lx_debug("\tlinux mount options: \"%s\"", options);

		/* Verify Linux mount options. */
		if (i_lx_opt_verify(options, lx_proc_options) == 0) {
			lx_unsupported("unsupported lx_proc mount options");
			return (-ENOTSUP);
		}
	} else if (strcmp(fstype, "autofs") == 0) {

		/* Translate proc mount requests to lx_afs requests. */
		(void) strcpy(fstype, LX_AUTOFS_NAME);

		/* Copy in Linux mount options. */
		if (datap != NULL) {
			rv = uucopystr((void *)datap,
			    options, sizeof (options));
			if ((rv == -1) || (rv == sizeof (options)))
				return (-EFAULT);
		}
		lx_debug("\tlinux mount options: \"%s\"", options);

		/* Verify Linux mount options. */
		if (i_lx_opt_verify(options, lx_autofs_options) == 0) {
			lx_unsupported("unsupported lx_autofs mount options");
			return (-ENOTSUP);
		}
	} else if (strcmp(fstype, "nfs") == 0) {

		/*
		 * Copy in Linux mount options.  Note that for Linux
		 * nfs mounts the mount options pointer (which normally
		 * points to a string) points to a structure.
		 */
		if (uucopy((void *)datap, &lx_nmd, sizeof (lx_nmd)) < 0)
			return (-errno);

		/*
		 * For Solaris nfs mounts, the kernel expects a special
		 * strucutre, but a pointer to this structure is passed
		 * in via an extra parameter (sdataptr below.)
		 */
		if ((rv = i_make_nfs_args(&lx_nmd, &nfs_args,
		    &nfs_args_addr, &nfs_args_knconf, &nfs_args_fh,
		    &nfs_args_secdata, fstype,
		    options, sizeof (options))) != 0)
			return (rv);

		/*
		 * For nfs mounts we need to tell the mount system call
		 * to expect extra parameters.
		 */
		sflags |= MS_DATA;
		sdataptr = (char *)&nfs_args;
		sdatalen = sizeof (nfs_args);
	} else {
		lx_unsupported(
		    "unsupported mount filesystem type: %s", fstype);
		return (-ENOTSUP);
	}

	/* Convert some Linux flags to Solaris flags. */
	if (flags & LX_MS_RDONLY)
		sflags |= MS_RDONLY;
	if (flags & LX_MS_NOSUID)
		sflags |= MS_NOSUID;
	if (flags & LX_MS_REMOUNT)
		sflags |= MS_REMOUNT;

	/* Convert some Linux flags to Solaris option strings. */
	if ((flags & LX_MS_NODEV) &&
	    ((rv = i_add_option("nodev", options, sizeof (options))) != 0))
		return (rv);
	if ((flags & LX_MS_NOEXEC) &&
	    ((rv = i_add_option("noexec", options, sizeof (options))) != 0))
		return (rv);
	if ((flags & LX_MS_NOATIME) &&
	    ((rv = i_add_option("noatime", options, sizeof (options))) != 0))
		return (rv);

	lx_debug("\tsolaris mount fstype: %s", fstype);
	lx_debug("\tsolaris mount options: \"%s\"", options);

	return (mount(source, target, sflags, fstype, sdataptr, sdatalen,
	    options, sizeof (options)) ? -errno : 0);
}

/*
 * umount() is identical, though it is implemented on top of umount2() in
 * Solaris so it cannot be a pass-thru system call.
 */
int
lx_umount(uintptr_t p1)
{
	return (umount((char *)p1) ? -errno : 0);
}

/*
 * The Linux umount2() system call is identical but has a different value for
 * MNT_FORCE (the logical equivalent to MS_FORCE).
 */
#define	LX_MNT_FORCE	0x1

int
lx_umount2(uintptr_t p1, uintptr_t p2)
{
	char *path = (char *)p1;
	int flags = 0;

	if (p2 & ~LX_MNT_FORCE)
		return (-EINVAL);

	if (p2 & LX_MNT_FORCE)
		flags |= MS_FORCE;

	return (umount2(path, flags) ? -errno : 0);
}