OpenSolaris_b135/cmd/fs.d/autofs/autod_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
 */
/*
 *	autod_mount.c
 *
 * Copyright 2007 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

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

#include <stdio.h>
#include <ctype.h>
#include <string.h>
#include <syslog.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/param.h>
#include <errno.h>
#include <pwd.h>
#include <netinet/in.h>
#include <netdb.h>
#include <sys/tiuser.h>
#include <locale.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/mntent.h>
#include <sys/mnttab.h>
#include <sys/wait.h>
#include <sys/mount.h>
#include <sys/fs/autofs.h>
#include <nfs/nfs.h>
#include <thread.h>
#include <limits.h>
#include <assert.h>
#include <fcntl.h>
#include <strings.h>

#include "automount.h"
#include "replica.h"

static int unmount_mntpnt(struct mnttab *);
static int call_fork_exec(char *, char *, char **, int);
static void remove_browse_options(char *);
static int inherit_options(char *, char **);

int
do_mount1(
	char *mapname,
	char *key,
	char *subdir,
	char *mapopts,
	char *path,
	uint_t isdirect,
	uid_t uid,
	action_list **alpp,
	int flags)
{
	struct mapline ml;
	struct mapent *me, *mapents = NULL;
	char mntpnt[MAXPATHLEN];
	char spec_mntpnt[MAXPATHLEN];
	int err = 0;
	char *private;	/* fs specific data. eg prevhost in case of nfs */
	int mount_ok = 0;
	ssize_t len;
	action_list *alp, *prev, *tmp;
	char root[MAXPATHLEN];
	int overlay = 1;
	char next_subdir[MAXPATHLEN];
	bool_t mount_access = TRUE;
	bool_t iswildcard;
	bool_t isrestricted = hasrestrictopt(mapopts);
	char *stack[STACKSIZ];
	char **stkptr = stack;

retry:
	iswildcard = FALSE;

	/* initialize the stack of open files for this thread */
	stack_op(INIT, NULL, stack, &stkptr);

	err = getmapent(key, mapname, &ml, stack, &stkptr, &iswildcard,
		isrestricted);
	if (err == 0) {
		mapents = parse_entry(key, mapname, mapopts, &ml,
				    subdir, isdirect, mount_access);
	}

	if (trace) {
		struct mapfs *mfs;
		trace_prt(1, "  do_mount1:\n");
		for (me = mapents; me; me = me->map_next) {
			trace_prt(1, "  (%s,%s)\t%s%s%s\n",
			me->map_fstype ? me->map_fstype : "",
			me->map_mounter ? me->map_mounter : "",
			path ? path : "",
			me->map_root  ? me->map_root : "",
			me->map_mntpnt ? me->map_mntpnt : "");
			trace_prt(0, "\t\t-%s\n",
			me->map_mntopts ? me->map_mntopts : "");

			for (mfs = me->map_fs; mfs; mfs = mfs->mfs_next)
				trace_prt(0, "\t\t%s:%s\tpenalty=%d\n",
					mfs->mfs_host ? mfs->mfs_host: "",
					mfs->mfs_dir ? mfs->mfs_dir : "",
					mfs->mfs_penalty);
		}
	}

	*alpp = NULL;

	/*
	 * Each mapent in the list describes a mount to be done.
	 * Normally there's just a single entry, though in the
	 * case of /net mounts there may be many entries, that
	 * must be mounted as a hierarchy.  For each mount the
	 * automountd must make sure the required mountpoint
	 * exists and invoke the appropriate mount command for
	 * the fstype.
	 */
	private = "";
	for (me = mapents; me && !err; me = me->map_next) {
		len = snprintf(mntpnt, sizeof (mntpnt), "%s%s%s", path,
		    mapents->map_root, me->map_mntpnt);

		if (len >= sizeof (mntpnt)) {
			free_mapent(mapents);
			return (ENAMETOOLONG);
		}
		/*
		 * remove trailing /'s from mountpoint to avoid problems
		 * stating a directory with two or more trailing slashes.
		 * This will let us mount directories from machines
		 * which export with two or more slashes (apollo for instance).
		 */
		len -= 1;
		while (mntpnt[len] == '/')
			mntpnt[len--] = '\0';

		(void) strcpy(spec_mntpnt, mntpnt);

		if (isrestricted &&
		    inherit_options(mapopts, &me->map_mntopts) != 0) {
			syslog(LOG_ERR, "malloc of options failed");
			free_mapent(mapents);
			return (EAGAIN);
		}

		if (strcmp(me->map_fstype, MNTTYPE_NFS) == 0) {
			remove_browse_options(me->map_mntopts);
			if (flags == DOMOUNT_KERNEL) {
				alp = (action_list *)malloc(
					sizeof (action_list));
				if (alp == NULL) {
					syslog(LOG_ERR,
						"malloc of alp failed");
					continue;
				}
				memset(alp, 0, sizeof (action_list));
			} else
				alp = NULL;
			err =
			    mount_nfs(me, spec_mntpnt, private, overlay, uid,
				    &alp);
			/*
			 * We must retry if we don't have access to the
			 * root file system and there are other
			 * following mapents. The reason we can't
			 * continue because the rest of the mapent list
			 * depends on whether mount_access is TRUE or FALSE.
			 */
			if (err == NFSERR_ACCES && me->map_next != NULL) {
				/*
				 * don't expect mount_access to be
				 * FALSE here, but we do a check
				 * anyway.
				 */
				if (mount_access == TRUE) {
					mount_access = FALSE;
					err = 0;
					free_mapent(mapents);
					if (alp) {
						free(alp);
						alp = NULL;
					}
					goto retry;
				}
			}
			if (alp) {
				if (*alpp == NULL)
					*alpp = alp;
				else {
					for (tmp = *alpp; tmp != NULL;
						tmp = tmp->next)
						prev = tmp;
					prev->next = alp;
				}
			}
			mount_ok = !err;
		} else if (strcmp(me->map_fstype, MNTTYPE_AUTOFS) == 0) {
			if (isdirect) {
				len = strlcpy(root, path, sizeof (root));
			} else {
				len = snprintf(root, sizeof (root), "%s/%s",
				    path, key);
			}
			if (len >= sizeof (root)) {
				free_mapent(mapents);
				return (ENAMETOOLONG);
			}

			alp = (action_list *)malloc(sizeof (action_list));
			if (alp == NULL) {
				syslog(LOG_ERR, "malloc of alp failed");
				continue;
			}
			memset(alp, 0, sizeof (action_list));

			/*
			 * get the next subidr, but only if its a modified
			 * or faked autofs mount
			 */
			if (me->map_modified || me->map_faked) {
				len = snprintf(next_subdir,
					sizeof (next_subdir), "%s%s", subdir,
					me->map_mntpnt);
			} else {
				next_subdir[0] = '\0';
				len = 0;
			}

			if (trace > 2)
				trace_prt(1, "  root=%s\t next_subdir=%s\n",
						root, next_subdir);
			if (len < sizeof (next_subdir)) {
				err = mount_autofs(me, spec_mntpnt, alp,
					root, next_subdir, key);
			} else {
				err = ENAMETOOLONG;
			}
			if (err == 0) {
				/*
				 * append to action list
				 */
				mount_ok++;
				if (*alpp == NULL)
					*alpp = alp;
				else {
					for (tmp = *alpp; tmp != NULL;
					    tmp = tmp->next)
						prev = tmp;
					prev->next = alp;
				}
			} else {
				free(alp);
				mount_ok = 0;
			}
		} else if (strcmp(me->map_fstype, MNTTYPE_LOFS) == 0) {
			remove_browse_options(me->map_mntopts);
			err = loopbackmount(me->map_fs->mfs_dir, spec_mntpnt,
					    me->map_mntopts, overlay);
			mount_ok = !err;
		} else {
			remove_browse_options(me->map_mntopts);
			err = mount_generic(me->map_fs->mfs_dir,
					    me->map_fstype, me->map_mntopts,
					    spec_mntpnt, overlay);
			mount_ok = !err;
		}
	}
	if (mapents)
		free_mapent(mapents);

	/*
	 * If an error occurred,
	 * the filesystem doesn't exist, or could not be
	 * mounted.  Return EACCES to autofs indicating that
	 * the mountpoint can not be accessed if this is not
	 * a wildcard access.  If it is a wildcard access we
	 * return ENOENT since the lookup that triggered
	 * this mount request will fail and the entry will not
	 * be available.
	 */
	if (mount_ok) {
		/*
		 * No error occurred, return 0 to indicate success.
		 */
		err = 0;
	} else {
		/*
		 * The filesystem does not exist or could not be mounted.
		 * Return ENOENT if the lookup was triggered by a wildcard
		 * access.  Wildcard entries only exist if they can be
		 * mounted.  They can not be listed otherwise (through
		 * a readdir(2)).
		 * Return EACCES if the lookup was not triggered by a
		 * wildcard access.  Map entries that are explicitly defined
		 * in maps are visible via readdir(2), therefore we return
		 * EACCES to indicate that the entry exists, but the directory
		 * can not be opened.  This is the same behavior of a Unix
		 * directory that exists, but has its execute bit turned off.
		 * The directory is there, but the user does not have access
		 * to it.
		 */
		if (iswildcard)
			err = ENOENT;
		else
			err = EACCES;
	}
	return (err);
}

#define	ARGV_MAX	16
#define	VFS_PATH	"/usr/lib/fs"

int
mount_generic(special, fstype, opts, mntpnt, overlay)
	char *special, *fstype, *opts, *mntpnt;
	int overlay;
{
	struct mnttab m;
	struct stat stbuf;
	int i, res;
	char *newargv[ARGV_MAX];

	if (trace > 1) {
		trace_prt(1, "  mount: %s %s %s %s\n",
			special, mntpnt, fstype, opts);
	}

	if (stat(mntpnt, &stbuf) < 0) {
		syslog(LOG_ERR, "Couldn't stat %s: %m", mntpnt);
		return (ENOENT);
	}

	i = 2;

	if (overlay)
		newargv[i++] = "-O";

	/*
	 *  Use "quiet" option to suppress warnings about unsupported
	 *  mount options.
	 */
	newargv[i++] = "-q";

	if (opts && *opts) {
		m.mnt_mntopts = opts;
		if (hasmntopt(&m, MNTOPT_RO) != NULL)
			newargv[i++] = "-r";
		newargv[i++] = "-o";
		newargv[i++] = opts;
	}
	newargv[i++] = "--";
	newargv[i++] = special;
	newargv[i++] = mntpnt;
	newargv[i] = NULL;
	res = call_fork_exec(fstype, "mount", newargv, verbose);
	if (res == 0 && trace > 1) {
		if (stat(mntpnt, &stbuf) == 0) {
			trace_prt(1, "  mount of %s dev=%x rdev=%x OK\n",
				mntpnt, stbuf.st_dev, stbuf.st_rdev);
		} else {
			trace_prt(1, "  failed to stat %s\n", mntpnt);
		}
	}
	return (res);
}

void
automountd_do_fork_exec(void *cookie, char *argp, size_t arg_size,
		door_desc_t *dfd, uint_t n_desc)
{
	int stat_loc;
	int fd = 0;
	struct stat stbuf;
	int res;
	int child_pid;
	command_t *command;
	char *newargv[ARGV_MAX];
	int i;


	command = (command_t *)argp;
	if (sizeof (*command) != arg_size) {
		res = EINVAL;
		door_return((char *)&res, sizeof (res), NULL, 0);
	}

	switch ((child_pid = fork1())) {
	case -1:
		syslog(LOG_ERR, "Cannot fork: %m");
		res = errno;
		break;
	case 0:
		/*
		 * Child
		 */
		(void) setsid();
		fd = open(command->console ? "/dev/console" : "/dev/null",
			    O_WRONLY);
		if (fd != -1) {
			(void) dup2(fd, 1);
			(void) dup2(fd, 2);
			(void) close(fd);
		}

		for (i = 0; *command->argv[i]; i++) {
			newargv[i] = strdup(command->argv[i]);
			if (newargv[i] == (char *)NULL) {
				syslog(LOG_ERR, "failed to copy argument '%s'"
				    " of %s: %m", command->argv[i],
				    command->file);
				_exit(errno);
			}
		}
		newargv[i] = NULL;

		(void) execv(command->file, newargv);
		if (errno == EACCES)
			syslog(LOG_ERR, "exec %s: %m", command->file);

		_exit(errno);
	default:
		/*
		 * Parent
		 */
		(void) waitpid(child_pid, &stat_loc, WUNTRACED);

		if (WIFEXITED(stat_loc)) {
			if (trace > 1) {
				trace_prt(1,
				    "  fork_exec: returns exit status %d\n",
				    WEXITSTATUS(stat_loc));
			}

			res = WEXITSTATUS(stat_loc);
		} else if (WIFSIGNALED(stat_loc)) {
			if (trace > 1)
				trace_prt(1,
				    "  fork_exec: returns signal status %d\n",
				    WTERMSIG(stat_loc));
			res = 1;
		} else {
			if (trace > 1)
				trace_prt(1,
				    "  fork_exec: returns unknown status\n");
			res = 1;
		}

	}
	door_return((char *)&res, sizeof (res), NULL, 0);
	trace_prt(1, "automountd_do_fork_exec, door return failed %s, %s\n",
	    command->file, strerror(errno));
	door_return(NULL, 0, NULL, 0);
}

int
do_unmount1(ur)
	umntrequest *ur;
{

	struct mnttab m;
	int res = 0;

	m.mnt_special = ur->mntresource;
	m.mnt_mountp = ur->mntpnt;
	m.mnt_fstype = ur->fstype;
	m.mnt_mntopts = ur->mntopts;
	/*
	 * Special case for NFS mounts.
	 * Don't want to attempt unmounts from
	 * a dead server.  If any member of a
	 * hierarchy belongs to a dead server
	 * give up (try later).
	 */
	if (strcmp(ur->fstype, MNTTYPE_NFS) == 0) {
		struct replica *list;
		int i, n;
		bool_t pubopt = FALSE;
		int nfs_port;
		int got_port;

		/*
		 * See if a port number was specified.  If one was
		 * specified that is too large to fit in 16 bits, truncate
		 * the high-order bits (for historical compatibility).  Use
		 * zero to indicate "no port specified".
		 */
		got_port = nopt(&m, MNTOPT_PORT, &nfs_port);
		if (!got_port)
			nfs_port = 0;
		nfs_port &= USHRT_MAX;

		if (hasmntopt(&m, MNTOPT_PUBLIC))
			pubopt = TRUE;

		list = parse_replica(ur->mntresource, &n);
		if (list == NULL) {
			if (n >= 0)
				syslog(LOG_ERR, "Memory allocation failed: %m");
			res = 1;
			goto done;
		}

		for (i = 0; i < n; i++) {
			if (pingnfs(list[i].host, 1, NULL, 0, nfs_port,
			    pubopt, list[i].path, NULL) != RPC_SUCCESS) {
				res = 1;
				free_replica(list, n);
				goto done;
			}
		}
		free_replica(list, n);
	}

	res = unmount_mntpnt(&m);

done:	return (res);
}

static int
unmount_mntpnt(mnt)
	struct mnttab *mnt;
{
	char *fstype = mnt->mnt_fstype;
	char *mountp = mnt->mnt_mountp;
	char *newargv[ARGV_MAX];
	int res;

	if (strcmp(fstype, MNTTYPE_NFS) == 0) {
		res = nfsunmount(mnt);
	} else if (strcmp(fstype, MNTTYPE_LOFS) == 0) {
		if ((res = umount(mountp)) < 0)
			res = errno;
	} else {
		newargv[2] = mountp;
		newargv[3] = NULL;

		res = call_fork_exec(fstype, "umount", newargv, verbose);
		if (res == ENOENT) {
			/*
			 * filesystem specific unmount command not found
			 */
			if ((res = umount(mountp)) < 0)
				res = errno;
		}
	}

	if (trace > 1)
		trace_prt(1, "  unmount %s %s\n",
			mountp, res ? "failed" : "OK");
	return (res);
}

/*
 * Remove the autofs specific options 'browse', 'nobrowse' and
 * 'restrict' from 'opts'.
 */
static void
remove_browse_options(char *opts)
{
	char *p, *pb;
	char buf[MAXOPTSLEN], new[MAXOPTSLEN];
	char *placeholder;

	new[0] = '\0';
	(void) strcpy(buf, opts);
	pb = buf;
	while (p = (char *)strtok_r(pb, ",", &placeholder)) {
		pb = NULL;
		if (strcmp(p, MNTOPT_NOBROWSE) != 0 &&
		    strcmp(p, MNTOPT_BROWSE) != 0 &&
		    strcmp(p, MNTOPT_RESTRICT) != 0) {
			if (new[0] != '\0')
				(void) strcat(new, ",");
			(void) strcat(new, p);
		}
	}
	(void) strcpy(opts, new);
}

static const char *restropts[] = {
	RESTRICTED_MNTOPTS
};
#define	NROPTS	(sizeof (restropts)/sizeof (restropts[0]))

static int
inherit_options(char *opts, char **mapentopts)
{
	int i;
	char *new;
	struct mnttab mtmap;
	struct mnttab mtopt;

	size_t len = strlen(*mapentopts);

	for (i = 0; i < NROPTS; i++)
		len += strlen(restropts[i]);

	/* "," for each new option plus the trailing NUL */
	len += NROPTS + 1;

	new = malloc(len);
	if (new == 0)
		return (-1);

	(void) strcpy(new, *mapentopts);

	mtmap.mnt_mntopts = *mapentopts;
	mtopt.mnt_mntopts = opts;

	for (i = 0; i < NROPTS; i++) {
		if (hasmntopt(&mtopt, (char *)restropts[i]) != NULL &&
		    hasmntopt(&mtmap, (char *)restropts[i]) == NULL) {
			if (*new != '\0')
				(void) strcat(new, ",");
			(void) strcat(new, restropts[i]);
		}
	}
	free(*mapentopts);
	*mapentopts = new;
	return (0);
}

bool_t
hasrestrictopt(char *opts)
{
	struct mnttab mt;

	mt.mnt_mntopts = opts;

	return (hasmntopt(&mt, MNTOPT_RESTRICT) != NULL);
}

static int
call_fork_exec(fstype, cmd, newargv, console)
	char *fstype;
	char *cmd;
	char **newargv;
	int console;
{
	command_t command;
	door_arg_t darg;
	char path[MAXPATHLEN];
	struct stat stbuf;
	int ret;
	int sz;
	int status;
	int i;

	bzero(&command, sizeof (command));
	/* build the full path name of the fstype dependent command */
	(void) snprintf(path, MAXPATHLEN, "%s/%s/%s", VFS_PATH, fstype, cmd);

	if (stat(path, &stbuf) != 0) {
		ret = errno;
		return (ret);
	}

	strlcpy(command.file, path, MAXPATHLEN);
	strlcpy(command.argv[0], path, MAXOPTSLEN);
	for (i = 2; newargv[i]; i++) {
		strlcpy(command.argv[i-1], newargv[i], MAXOPTSLEN);
	}
	if (trace > 1) {
		trace_prt(1, "  call_fork_exec: %s ", command.file);
		for (i = 0; *command.argv[i]; i++)
			trace_prt(0, "%s ", command.argv[i]);
		trace_prt(0, "\n");
	}

	command.console = console;

	darg.data_ptr = (char *)&command;
	darg.data_size = sizeof (command);
	darg.desc_ptr = NULL;
	darg.desc_num = 0;
	darg.rbuf = (char *)&status;
	darg.rsize = sizeof (status);

	ret = door_call(did_fork_exec, &darg);
	if (trace > 1) {
		trace_prt(1, "  call_fork_exec: door_call failed %d\n", ret);
	}

	return (status);
}