/* ----------------------------------------------------------------------- *
 *
 *  Copyright 2013 Ian Kent <raven@themaw.net>
 *  Copyright 2013 Red Hat, Inc.
 *  All rights reserved.
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, Inc., 675 Mass Ave, Cambridge MA 02139,
 *  USA; either version 2 of the License, or (at your option) any later
 *  version; incorporated herein by reference.
 *
 * ----------------------------------------------------------------------- */

#include <stdio.h>
#include <malloc.h>
#include <netdb.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <limits.h>
#include <sys/param.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/vfs.h>
#include <sys/utsname.h>
#include <netinet/in.h>
#include <sys/mount.h>
#include <linux/fs.h>

#define MODULE_PARSE
#include "automount.h"
#include "nsswitch.h"

#define MODPREFIX "parse(amd): "

int parse_version = AUTOFS_PARSE_VERSION;	/* Required by protocol */

static struct mount_mod *mount_nfs = NULL;
static int init_ctr = 0;
static pthread_mutex_t instance_mutex = PTHREAD_MUTEX_INITIALIZER;

static void instance_mutex_lock(void)
{
	int status = pthread_mutex_lock(&instance_mutex);
	if (status)
		fatal(status);
}

static void instance_mutex_unlock(void)
{
	int status = pthread_mutex_unlock(&instance_mutex);
	if (status)
		fatal(status);
}

extern const char *global_options;

struct parse_context {
	char *optstr;		/* Mount options */
	char *macros;		/* Map wide macro defines */
	struct substvar *subst;	/* $-substitutions */
};

struct multi_mnt {
	char *path;
	char *options;
	char *location;
	struct multi_mnt *next;
};

/* Default context */

static struct parse_context default_context = {
	NULL,			/* No mount options */
	NULL,			/* No map wide macros */
	NULL			/* The substvar local vars table */
};

/* Free all storage associated with this context */
static void kill_context(struct parse_context *ctxt)
{
	macro_lock();
	macro_free_table(ctxt->subst);
	macro_unlock();
	if (ctxt->optstr)
		free(ctxt->optstr);
	if (ctxt->macros)
		free(ctxt->macros);
	free(ctxt);
}

int parse_init(int argc, const char *const *argv, void **context)
{
	struct parse_context *ctxt;
	char buf[MAX_ERR_BUF];

	sel_hash_init();

	/* Set up context and escape chain */

	if (!(ctxt = (struct parse_context *) malloc(sizeof(struct parse_context)))) {
		char *estr = strerror_r(errno, buf, MAX_ERR_BUF);
		logerr(MODPREFIX "malloc: %s", estr);
		*context = NULL;
		return 1;
	}
	*context = (void *) ctxt;

	*ctxt = default_context;

	/* We only need this once.  NFS mounts are so common that we cache
	   this module. */
	instance_mutex_lock();
	if (mount_nfs)
		init_ctr++;
	else {
		if ((mount_nfs = open_mount("nfs", MODPREFIX))) {
			init_ctr++;
		} else {
			kill_context(ctxt);
			*context = NULL;
			instance_mutex_unlock();
			return 1;
		}
	}
	instance_mutex_unlock();

	return 0;
}

int parse_reinit(int argc, const char *const *argv, void **context)
{
	return 0;
}

static struct substvar *add_lookup_vars(struct autofs_point *ap,
					const char *key, int key_len,
					struct map_source *source,
					struct substvar *sv)
{
	struct substvar *list = sv;
	struct thread_stdenv_vars *tsv;
	char lkp_key[PATH_MAX + 1];
	char path[PATH_MAX + 1];
	struct mapent *me;
	int len;

	len = ap->len + 1 + key_len + 1;
	if (len > PATH_MAX) {
		error(ap->logopt, MODPREFIX
		      "error: lookup key is greater than PATH_MAX");
		return NULL;
	}

	if (ap->pref) {
		if (snprintf(lkp_key, sizeof(lkp_key), "%s%s",
			     ap->pref, key) >= sizeof(lkp_key)) {
			error(ap->logopt, MODPREFIX "key too long");
			return NULL;
		}
	} else {
		if (snprintf(lkp_key, sizeof(lkp_key), "%s",
			     key) >= sizeof(lkp_key)) {
			error(ap->logopt, MODPREFIX "key too long");
			return NULL;
		}
	}

	if (*key == '/')
		strcpy(path, key);
	else {
		strcpy(path, ap->path);
		strcat(path, "/");
		strcat(path, key);
	}
	list = macro_addvar(list, "path", 4, path);

	me = cache_lookup_distinct(source->mc, lkp_key);
	if (me)
		list = macro_addvar(list, "key", 3, me->key);

	while (!me) {
		char match[PATH_MAX + 1];
		char *prefix;

		strcpy(match, lkp_key);
		while ((prefix = strrchr(match, '/'))) {
			*prefix = '\0';
			me = cache_partial_match_wild(source->mc, match);
			if (me) {
				list = macro_addvar(list, "key", 3, lkp_key);
				break;
			}
		}

		if (!me) {
			me = cache_lookup_distinct(source->mc, "*");
			if (me)
				list = macro_addvar(list, "key", 3, lkp_key);
		}

		break;
	}

	if (source->name)
		list = macro_addvar(list, "map", 3, source->name);
	else if (source->argv[0][0])
		list = macro_addvar(list, "map", 3, source->argv[0]);

	tsv = pthread_getspecific(key_thread_stdenv_vars);
	if (tsv) {
		char numbuf[16];
		long num;
		int ret;

		num = (long) tsv->uid;
		ret = sprintf(numbuf, "%ld", num);
		if (ret > 0)
			list = macro_addvar(list, "uid", 3, numbuf);
		num = (long) tsv->gid;
		ret = sprintf(numbuf, "%ld", num);
		if (ret > 0)
			list = macro_addvar(list, "gid", 3, numbuf);
	}

	list = macro_addvar(list, "fs", 2, "${autodir}/${rhost}${rfs}");
	list = macro_addvar(list, "rfs", 3, path);

	return list;
}

static int match_my_name(struct autofs_point *ap, const char *name, struct substvar *sv)
{
	struct addrinfo hints, *cni, *ni, *haddr;
	char host[NI_MAXHOST + 1], numeric[NI_MAXHOST + 1];
	unsigned int logopt = ap->logopt;
	const struct substvar *v;
	char *exp_name = NULL;
	int rv = 0, ret;

	if (!expand_selectors(ap, name, &exp_name, sv))
		exp_name = strdup(name);
	if (!exp_name) {
		error(logopt,
		      MODPREFIX "error: failed to alloc space for name");
		goto out;
	}

	v = macro_findvar(sv, "host", 4);
	if (v) {
		if (!strcmp(v->val, exp_name)) {
			rv = 1;
			goto out;
		}
	}

	if (!v || !v->val) {
		error(logopt, MODPREFIX "error: ${host} not set");
		goto out;
	}

	/* Check if comparison value is an alias */

	memset(&hints, 0, sizeof(hints));
	hints.ai_flags = AI_CANONNAME;
	hints.ai_family = AF_UNSPEC;
	hints.ai_socktype = SOCK_DGRAM;

	/* Get host canonical name */
	cni = NULL;
	ret = getaddrinfo(v->val, NULL, &hints, &cni);
	if (ret) {
		error(logopt, MODPREFIX
		      "hostname lookup for %s failed: %s\n",
		      v->val, gai_strerror(ret));
		goto out;
	}

	hints.ai_flags = AI_V4MAPPED | AI_ADDRCONFIG | AI_CANONNAME;

	/* Resolve comparison name to its names and compare */
	ni = NULL;
	ret = getaddrinfo(exp_name, NULL, &hints, &ni);
	if (ret) {
		error(logopt, MODPREFIX
		      "hostname lookup for %s failed: %s\n",
		      exp_name, gai_strerror(ret));
		freeaddrinfo(cni);
		goto out;
	}

	haddr = ni;
	while (haddr) {
		/* Translate the host address into a numeric string form */
		ret = getnameinfo(haddr->ai_addr, haddr->ai_addrlen,
				  numeric, sizeof(numeric), NULL, 0,
				  NI_NUMERICHOST);
		if (ret) {
			error(logopt, MODPREFIX
			      "host address info lookup failed: %s\n",
			      gai_strerror(ret));
			goto next;
		}

		/* Try to resolve back again to get the canonical name */
		ret = getnameinfo(haddr->ai_addr, haddr->ai_addrlen,
				  host, NI_MAXHOST, NULL, 0, 0);
		if (ret) {
			error(logopt, MODPREFIX
			      "host address info lookup failed: %s\n",
			      gai_strerror(ret));
			goto next;
		}

		if (!strcmp(host, cni->ai_canonname)) {
			rv = 1;
			break;
		}
next:
		haddr = haddr->ai_next;
	}
	freeaddrinfo(ni);
	freeaddrinfo(cni);
out:
	if (exp_name)
		free(exp_name);
	return rv;
}

static int sel_strcmp(struct autofs_point *ap,
		      const struct substvar *v, struct selector *s,
		      struct substvar *sv)
{
	char *expand = NULL;
	int ret = 0;
	int res;

	res = expand_selectors(ap, s->comp.value, &expand, sv);
	if (res)
		res = strcmp(v->val, expand);
	else
		res = strcmp(v->val, s->comp.value);

	if (s->compare & SEL_COMP_EQUAL && !res) {
		debug(ap->logopt, MODPREFIX
		      "matched selector %s(%s) == %s",
		      v->def, v->val, expand ? expand : s->comp.value);
		ret = 1;
	} else if (s->compare & SEL_COMP_NOTEQUAL && res) {
		debug(ap->logopt, MODPREFIX
		      "matched selector %s(%s) != %s",
		      v->def, v->val, expand ? expand : s->comp.value);
		ret = 1;
	} else
		debug(ap->logopt, MODPREFIX
		      "did not match selector %s(%s) %s %s",
		      v->def, v->val,
		      (s->compare & SEL_COMP_EQUAL ? "==" : "!="),
		      expand ? expand : s->comp.value);

	if (expand)
		free(expand);

	return ret;
}

static int sel_lstat(struct autofs_point *ap,
		     struct selector *s, struct substvar *sv)
{
	struct stat st;
	char *expand = NULL;
	int res, ret;

	/* Sould be OK to fail on any error here */
	res = expand_selectors(ap, s->func.arg1, &expand, sv);
	if (res)
		ret = !lstat(expand, &st);
	else
		ret = !lstat(s->func.arg1, &st);

	if (s->compare == SEL_COMP_NOT)
		ret = !ret;
	if (ret)
		debug(ap->logopt, MODPREFIX
		      "matched selector %s(%s)",
		      s->sel->name, expand ? expand : s->func.arg1);
	else
		debug(ap->logopt, MODPREFIX
		      "did not match selector %s(%s)",
		      s->sel->name, expand ? expand : s->func.arg1);
	if (expand)
		free(expand);

	return ret;
}

static int sel_in_network(struct autofs_point *ap,
			  struct selector *s, struct substvar *sv)
{
	char *expand = NULL;
	int res, ret;

	res = expand_selectors(ap, s->func.arg1, &expand, sv);
	if (!res)
		ret = in_network(s->func.arg1);
	else
		ret = in_network(expand);

	if (s->compare == SEL_COMP_NOT)
		ret = !ret;
	if (ret)
		debug(ap->logopt, MODPREFIX
		      "matched selector %s(%s)",
		      s->sel->name, expand ? expand : s->func.arg1);
	else
		debug(ap->logopt, MODPREFIX
		      "did not match selector %s(%s)",
		      s->sel->name, expand ? expand : s->func.arg1);

	if (expand)
		free(expand);

	return ret;
}

static int sel_netgrp(struct autofs_point *ap,
		      struct selector *s, struct substvar *sv)
{
	char *exp_arg1 = NULL, *exp_arg2 = NULL;
	const struct substvar *v;
	int res, ret = 0;
	char *host;

	if (s->func.arg2) {
		res = expand_selectors(ap, s->func.arg2, &exp_arg2, sv);
		if (res)
			host = exp_arg2;
		else
			host = s->func.arg2;
	} else {
		if (s->sel->selector == SEL_NETGRP)
			v = macro_findvar(sv, "host", 4);
		else
			v = macro_findvar(sv, "hostd", 5);
		if (!v || !*v->val) {
			error(ap->logopt, MODPREFIX
			     "failed to get value of ${host}");
			goto out;
		}
		host = v->val;
	}

	res = expand_selectors(ap, s->func.arg1, &exp_arg1, sv);
	if (res)
		ret = innetgr(exp_arg1, host, NULL, NULL);
	else
		ret = innetgr(s->func.arg1, host, NULL, NULL);

	if (s->compare == SEL_COMP_NOT)
		ret = !ret;
	if (ret) {
		if (!s->func.arg2)
			debug(ap->logopt, MODPREFIX
			      "matched selector %s(%s)",
			      s->sel->name, exp_arg1 ? exp_arg1 : s->func.arg1);
		else
			debug(ap->logopt, MODPREFIX
			      "matched selector %s(%s,%s)", s->sel->name,
			      exp_arg1 ? exp_arg1 : s->func.arg1,
			      exp_arg2 ? exp_arg2 : s->func.arg2);
	} else {
		if (!s->func.arg2)
			debug(ap->logopt, MODPREFIX
			      "did not match selector %s(%s)",
			      s->sel->name, exp_arg1 ? exp_arg1 : s->func.arg1);
		else
			debug(ap->logopt, MODPREFIX
			      "did not match selector %s(%s,%s)", s->sel->name,
			      exp_arg1 ? exp_arg1 : s->func.arg1,
			      exp_arg2 ? exp_arg2 : s->func.arg2);
	}
out:
	if (exp_arg1)
		free(exp_arg1);
	if (exp_arg2)
		free(exp_arg2);

	return ret;
}

static int eval_selector(struct autofs_point *ap,
			 struct amd_entry *this, struct substvar *sv)
{
	struct selector *s = this->selector;
	unsigned int logopt = ap->logopt;
	const struct substvar *v;
	unsigned int s_type;
	unsigned int v_type;
	int res, val, ret = 0;

	s_type = s->sel->flags & SEL_FLAGS_TYPE_MASK;

	switch (s_type) {
	case SEL_FLAG_MACRO:
		v = macro_findvar(sv, s->sel->name, strlen(s->sel->name));
		if (!v) {
			error(logopt, MODPREFIX
			      "failed to get selector %s", s->sel->name);
			return 0;
		}

		v_type = s->sel->flags & SEL_FLAGS_VALUE_MASK;

		switch (v_type) {
		case SEL_FLAG_STR:
			ret = sel_strcmp(ap, v, s, sv);
			break;

		case SEL_FLAG_NUM:
			if (!*s->comp.value) {
				res = 1;
				val = 0;
			} else {
				res = atoi(v->val);
				val = atoi(s->comp.value);
			}
			if (s->compare & SEL_COMP_EQUAL && res == val) {
				debug(logopt, MODPREFIX
				      "matched selector %s(%s) equal to %s",
				      v->def, v->val, s->comp.value);
				ret = 1;
				break;
			} else if (s->compare & SEL_COMP_NOTEQUAL && res != val) {
				debug(logopt, MODPREFIX
				      "matched selector %s(%s) not equal to %s",
				      v->def, v->val, s->comp.value);
				ret = 1;
				break;
			}

			debug(logopt, MODPREFIX
				      "did not match selector %s(%s) %s %s",
				      v->def, v->val,
				      (s->compare & SEL_COMP_EQUAL ? "==" : "!="),
				      s->comp.value);
			break;

		default:
			break;
		}
		break;

	case SEL_FLAG_FUNC1:
		if (s->sel->selector != SEL_TRUE &&
		    s->sel->selector != SEL_FALSE &&
		    !s->func.arg1) {
			error(logopt, MODPREFIX
			      "expected argument missing for selector %s",
			      s->sel->name);
			break;
		}

		switch (s->sel->selector) {
		case SEL_TRUE:
			ret = 1;
			if (s->compare == SEL_COMP_NOT)
				ret = !ret;
			if (ret)
				debug(logopt, MODPREFIX
				      "matched selector %s(%s)",
				      s->sel->name, s->func.arg1);
			else
				debug(logopt, MODPREFIX
				      "did not match selector %s(%s)",
				      s->sel->name, s->func.arg1);
			break;

		case SEL_FALSE:
			if (s->compare == SEL_COMP_NOT)
				ret = !ret;
			if (ret)
				debug(logopt, MODPREFIX
				      "matched selector %s(%s)",
				      s->sel->name, s->func.arg1);
			else
				debug(logopt, MODPREFIX
				      "did not match selector %s(%s)",
				      s->sel->name, s->func.arg1);
			break;

		case SEL_XHOST:
			ret = match_my_name(ap, s->func.arg1, sv);
			if (s->compare == SEL_COMP_NOT)
				ret = !ret;
			if (ret)
				debug(logopt, MODPREFIX
				      "matched selector %s(%s) to host name",
				      s->sel->name, s->func.arg1);
			else
				debug(logopt, MODPREFIX
				      "did not match selector %s(%s) to host name",
				      s->sel->name, s->func.arg1);
			break;

		case SEL_EXISTS:
			ret = sel_lstat(ap, s, sv);
			break;

		case SEL_IN_NETWORK:
			ret = sel_in_network(ap, s, sv);
			break;

		default:
			break;
		}
		break;

	case SEL_FLAG_FUNC2:
		if (!s->func.arg1) {
			error(logopt, MODPREFIX
			      "expected argument missing for selector %s",
			      s->sel->name);
			break;
		}

		switch (s->sel->selector) {
		case SEL_NETGRP:
		case SEL_NETGRPD:
			ret = sel_netgrp(ap, s, sv);
			break;

		default:
			break;
		}
		break;

	default:
		break;
	}

	return ret;
}

static void update_with_defaults(struct amd_entry *defaults,
				 struct amd_entry *entry,
				 struct substvar *sv)
{
	const struct substvar *v;
	unsigned long fstype = entry->flags & AMD_MOUNT_TYPE_MASK;
	char *tmp;

	if (fstype == AMD_MOUNT_TYPE_NONE) {
		unsigned long deftype = defaults->flags & AMD_MOUNT_TYPE_MASK;
		if (deftype != AMD_MOUNT_TYPE_NONE)
			entry->flags |= (defaults->flags & AMD_MOUNT_TYPE_MASK);
		else {
			entry->flags = AMD_MOUNT_TYPE_NFS;
			tmp = strdup("nfs");
			if (tmp)
				entry->type = tmp;
		}
	}

	if (!entry->type && defaults->type) {
		tmp = strdup(defaults->type);
		if (tmp)
			entry->type = tmp;
	}

	if (!entry->map_type && defaults->map_type) {
		tmp = strdup(defaults->map_type);
		if (tmp)
			entry->map_type = tmp;
	}

	if (!entry->pref && defaults->pref) {
		tmp = strdup(defaults->pref);
		if (tmp)
			entry->pref = tmp;
	}

	if (!entry->fs) {
		if (defaults->fs) {
			tmp = strdup(defaults->fs);
			if (tmp)
				entry->fs = tmp;
		} else {
			v = macro_findvar(sv, "fs", 2);
			if (v)
				entry->fs = strdup(v->val);
		}
	}

	if (!entry->rfs) {
		if (defaults->rfs) {
			tmp = strdup(defaults->rfs);
			if (tmp)
				entry->rfs = tmp;
		} else {
			v = macro_findvar(sv, "rfs", 3);
			if (v)
				entry->rfs = strdup(v->val);
		}
	}

	if (!entry->rhost) {
		if (defaults->rhost) {
			tmp = strdup(defaults->rhost);
			if (tmp)
				entry->rhost = tmp;
		} else {
			v = macro_findvar(sv, "host", 4);
			if (v)
				entry->rhost = strdup(v->val);
		}
	}

	if (!entry->dev && defaults->dev) {
		tmp = strdup(defaults->dev);
		if (tmp)
			entry->dev = tmp;
	}

	if (!entry->opts && defaults->opts) {
		tmp = merge_options(defaults->opts, entry->opts);
		if (tmp)
			entry->opts = tmp;
	}

	if (!entry->addopts && defaults->addopts) {
		tmp = merge_options(defaults->addopts, entry->addopts);
		if (tmp)
			entry->addopts = tmp;
	}

	if (!entry->remopts) {
		if (defaults->remopts) {
			tmp = strdup(defaults->remopts);
			if (tmp)
				entry->remopts = tmp;
		} else {
			v = macro_findvar(sv, "remopts", 7);
			if (v)
				entry->remopts = strdup(v->val);
		}
	}

	if (!entry->sublink) {
		if (defaults->sublink) {
			tmp = strdup(defaults->sublink);
			if (tmp)
				entry->sublink = tmp;
		} else {
			v = macro_findvar(sv, "sublink", 2);
			if (v)
				entry->sublink = strdup(v->val);
		}
	}

	return;
}

static char *normalize_hostname(unsigned int logopt, const char *host,
				unsigned int flags, struct substvar *sv)
{
	struct addrinfo hints, *ni;
	char *name;
	int ret;

	if (!(flags & CONF_NORMALIZE_HOSTNAMES))
		name = strdup(host);
	else {
		memset(&hints, 0, sizeof(hints));
		hints.ai_flags = AI_CANONNAME;
		hints.ai_family = AF_UNSPEC;
		hints.ai_socktype = SOCK_DGRAM;

		ni = NULL;
		ret = getaddrinfo(host, NULL, &hints, &ni);
		if (ret) {
			error(logopt, MODPREFIX
			      "hostname lookup for %s failed: %s",
			      host, gai_strerror(ret));
			return NULL;
		}
		name = strdup(ni->ai_canonname);
		freeaddrinfo(ni);
	}

	if (!name)
		return NULL;

	if (flags & CONF_DOMAIN_STRIP) {
		const struct substvar *v = macro_findvar(sv, "hostd", 5);
		if (v) {
			char *d1 = strchr(name, '.');
			if (d1) {
				char *d2 = strchr(v->val, '.');
				if (d2 && !strcmp(d1, d2))
					*d1 = '\0';
			}
		}
	}

	return name;
}

static struct substvar *expand_entry(struct autofs_point *ap,
				     struct amd_entry *entry,
				     unsigned int flags,
				     struct substvar *sv)
{
	unsigned int logopt = ap->logopt;
	char *expand;

	if (entry->rhost && *entry->rhost) {
		char *host = strdup(entry->rhost);
		char *nn;
		if (!host) {
			error(ap->logopt, MODPREFIX
			      "failed to allocate storage for rhost");
			goto next;
		}
		if (expand_selectors(ap, host, &expand, sv)) {
			free(host);
			host = expand;
		}
		nn = normalize_hostname(ap->logopt, host, flags, sv);
		if (!nn)
			sv = macro_addvar(sv, "rhost", 5, host);
		else {
			sv = macro_addvar(sv, "rhost", 5, nn);
			free(host);
			host = nn;
		}
		debug(logopt, MODPREFIX
		      "rhost expand(\"%s\") -> %s", entry->rhost, host);
		free(entry->rhost);
		entry->rhost = host;
	}
next:
	if (entry->pref) {
		if (expand_selectors(ap, entry->pref, &expand, sv)) {
			debug(logopt, MODPREFIX
			      "pref expand(\"%s\") -> %s",
			      entry->pref, expand);
			free(entry->pref);
			entry->pref = expand;
		}
		sv = macro_addvar(sv, "pref", 4, entry->pref);
	}

	if (entry->sublink) {
		if (expand_selectors(ap, entry->sublink, &expand, sv)) {
			debug(logopt, MODPREFIX
			      "sublink expand(\"%s\") -> %s",
			      entry->sublink, expand);
			free(entry->sublink);
			entry->sublink = expand;
		}
		sv = macro_addvar(sv, "sublink", 7, entry->sublink);
	}

	if (entry->rfs && *entry->rfs) {
		if (expand_selectors(ap, entry->rfs, &expand, sv)) {
			debug(logopt, MODPREFIX
			      "rfs expand(\"%s\") -> %s", entry->rfs, expand);
			free(entry->rfs);
			entry->rfs = expand;
		}
		sv = macro_addvar(sv, "rfs", 3, entry->rfs);
	}

	if (entry->fs && *entry->fs) {
		if (expand_selectors(ap, entry->fs, &expand, sv)) {
			debug(logopt, MODPREFIX
			      "fs expand(\"%s\") -> %s", entry->fs, expand);
			free(entry->fs);
			entry->fs = expand;
		}
		sv = macro_addvar(sv, "fs", 2, entry->fs);
	}

	if (entry->opts && *entry->opts) {
		if (expand_selectors(ap, entry->opts, &expand, sv)) {
			debug(logopt, MODPREFIX
			      "ops expand(\"%s\") -> %s", entry->opts, expand);
			free(entry->opts);
			entry->opts = expand;
		}
		sv = macro_addvar(sv, "opts", 4, entry->opts);
	}

	if (entry->addopts && *entry->addopts) {
		if (expand_selectors(ap, entry->addopts, &expand, sv)) {
			debug(logopt, MODPREFIX
			      "addopts expand(\"%s\") -> %s",
			      entry->addopts, expand);
			free(entry->addopts);
			entry->addopts = expand;
		}
		sv = macro_addvar(sv, "addopts", 7, entry->addopts);
	}

	if (entry->remopts && *entry->remopts) {
		if (expand_selectors(ap, entry->remopts, &expand, sv)) {
			debug(logopt, MODPREFIX
			      "remopts expand(\"%s\") -> %s",
			      entry->remopts, expand);
			free(entry->remopts);
			entry->remopts = expand;
		}
		sv = macro_addvar(sv, "remopts", 7, entry->remopts);
	}

	if (entry->mount) {
		if (!expand_selectors(ap, entry->mount, &expand, sv)) {
			free(entry->mount);
			if (entry->umount)
				free(entry->umount);
			entry->mount = NULL;
			entry->umount = NULL;
			goto done;
		}
		debug(logopt, MODPREFIX
		      "mount expand(\"%s\") -> %s", entry->mount, expand);
		free(entry->mount);
		entry->mount = expand;
		sv = macro_addvar(sv, "mount", 5, entry->mount);
	}

	if (entry->umount) {
		if (!expand_selectors(ap, entry->umount, &expand, sv)) {
			free(entry->umount);
			entry->umount = NULL;
			goto done;
		}
		debug(logopt, MODPREFIX
		      "umount expand(\"%s\") -> %s", entry->umount, expand);
		free(entry->umount);
		entry->umount = expand;
		sv = macro_addvar(sv, "umount", 5, entry->umount);
	}
done:
	return sv;
}

static void expand_merge_options(struct autofs_point *ap,
				 struct amd_entry *entry,
				 struct substvar *sv)
{
	char *tmp;

	if (entry->opts && *entry->opts) {
		if (!expand_selectors(ap, entry->opts, &tmp, sv))
			error(ap->logopt, MODPREFIX "failed to expand opts");
		else {
			free(entry->opts);
			entry->opts = tmp;
		}
	}

	if (entry->addopts && *entry->addopts) {
		if (!expand_selectors(ap, entry->addopts, &tmp, sv))
			error(ap->logopt, MODPREFIX "failed to expand addopts");
		else {
			free(entry->addopts);
			entry->addopts = tmp;
		}
	}

	if (entry->remopts && *entry->remopts) {
		if (!expand_selectors(ap, entry->remopts, &tmp, sv))
			error(ap->logopt, MODPREFIX "failed to expand remopts");
		else {
			free(entry->remopts);
			entry->remopts = tmp;
		}
	}

	return;
}

static struct substvar *merge_entry_options(struct autofs_point *ap,
					    struct amd_entry *entry,
				            struct substvar *sv)
{
	char *tmp;

	if (!entry->addopts)
		return sv;

	if (entry->opts && entry->remopts &&
	    !strcmp(entry->opts, entry->remopts)) {
		expand_merge_options(ap, entry, sv);
		tmp = merge_options(entry->opts, entry->addopts);
		if (tmp) {
			info(ap->logopt, MODPREFIX
			     "merge remopts \"%s\" addopts \"%s\" => \"%s\"",
			      entry->opts, entry->addopts, tmp);
			free(entry->opts);
			entry->opts = tmp;
			sv = macro_addvar(sv, "opts", 4, entry->opts);
		}
		if (*entry->opts) {
			tmp = strdup(entry->opts);
			if (tmp) {
				free(entry->remopts);
				entry->remopts = tmp;
				sv = macro_addvar(sv, "remopts", 7, entry->remopts);
			}
		}
		return sv;
	}

	expand_merge_options(ap, entry, sv);

	if (entry->opts && entry->addopts) {
		tmp = merge_options(entry->opts, entry->addopts);
		if (tmp) {
			info(ap->logopt, MODPREFIX
			     "merge opts \"%s\" addopts \"%s\" => \"%s\"",
			      entry->opts, entry->addopts, tmp);
			free(entry->opts);
			entry->opts = tmp;
			sv = macro_addvar(sv, "opts", 4, entry->opts);
		}
	} else if (entry->addopts && *entry->addopts) {
		tmp = strdup(entry->addopts);
		if (tmp) {
			info(ap->logopt, MODPREFIX
			     "opts add addopts \"%s\" => \"%s\"", entry->addopts, tmp);
			entry->opts = tmp;
			sv = macro_addvar(sv, "opts", 4, entry->opts);
		}
	}

	expand_merge_options(ap, entry, sv);

	if (entry->remopts && entry->addopts) {
		tmp = merge_options(entry->remopts, entry->addopts);
		if (tmp) {
			info(ap->logopt, MODPREFIX
			     "merge remopts \"%s\" addopts \"%s\" => \"%s\"",
			      entry->remopts, entry->addopts, tmp);
			free(entry->remopts);
			entry->remopts = tmp;
			sv = macro_addvar(sv, "remopts", 7, entry->remopts);
		}
	} else if (entry->addopts && *entry->addopts) {
		tmp = strdup(entry->addopts);
		if (tmp) {
			info(ap->logopt, MODPREFIX
			     "remopts add addopts \"%s\" => \"%s\"",
			     entry->addopts, tmp);
			entry->remopts = tmp;
			sv = macro_addvar(sv, "remopts", 7, entry->remopts);
		}
	}

	return sv;
}

static int do_auto_mount(struct autofs_point *ap, const char *name,
			 struct amd_entry *entry, unsigned int flags)
{
	char target[PATH_MAX + 1];

	if (!entry->map_type) {
		if (strlen(entry->fs) > PATH_MAX) {
			error(ap->logopt, MODPREFIX
			     "error: fs option length is too long");
			return 0;
		}
		strcpy(target, entry->fs);
	} else {
		if (strlen(entry->fs) +
		    strlen(entry->map_type) + 5 > PATH_MAX) {
			error(ap->logopt, MODPREFIX
			     "error: fs + maptype options length is too long");
			return 0;
		}
		strcpy(target, entry->map_type);
		strcat(target, ",amd:");
		strcat(target, entry->fs);
	}

	return do_mount(ap, ap->path,
			name, strlen(name), target, "autofs", entry->opts);
}

static int do_link_mount(struct autofs_point *ap, const char *name,
			 struct amd_entry *entry, unsigned int flags)
{
	const char *opts = (entry->opts && *entry->opts) ? entry->opts : NULL;
	char *target;
	int ret;

	if (entry->sublink) {
		if (strlen(entry->sublink) > PATH_MAX) {
			error(ap->logopt, MODPREFIX
			     "error: sublink option length is too long");
			return 0;
		}
		target = entry->sublink;
	} else {
		if (strlen(entry->fs) > PATH_MAX) {
			error(ap->logopt, MODPREFIX
			     "error: fs option length is too long");
			return 0;
		}
		target = entry->fs;
	}

	if (!(flags & CONF_AUTOFS_USE_LOFS))
		goto symlink;

	/* For a sublink this might cause an external mount */
	ret = do_mount(ap, ap->path,
		       name, strlen(name), target, "bind", opts);
	if (!ret)
		goto out;

	debug(ap->logopt, MODPREFIX "bind mount failed, symlinking");

symlink:
	ret = do_mount(ap, ap->path,
		       name, strlen(name), target, "bind", "symlink");
	if (!ret)
		goto out;

	error(ap->logopt, MODPREFIX
	      "failed to symlink %s to %s", entry->path, target);

	if (entry->sublink) {
		/* failed to complete sublink mount */
		umount_amd_ext_mount(ap, entry->fs);
	}
out:
	return ret;
}

static int do_linkx_mount(struct autofs_point *ap, const char *name,
			  struct amd_entry *entry, unsigned int flags)
{
	struct stat st;
	char *target;

	if (entry->sublink)
		target = entry->sublink;
	else
		target = entry->fs;

	if (lstat(target, &st) < 0)
		return errno;

	return do_link_mount(ap, name, entry, flags);
}

static int do_generic_mount(struct autofs_point *ap, const char *name,
			    struct amd_entry *entry, const char *target,
			    unsigned int flags)
{
	const char *opts = (entry->opts && *entry->opts) ? entry->opts : NULL;
	unsigned int umount = 0;
	int ret = 0;

	if (!entry->fs) {
		ret = do_mount(ap, ap->path, name,
			       strlen(name), target, entry->type, opts);
	} else {
		/*
		 * Careful, external mounts may get mounted
		 * multiple times since they are outside of
		 * the automount filesystem.
		 */
		if (!is_mounted(entry->fs, MNTS_REAL)) {
			ret = do_mount(ap, entry->fs,
				       entry->fs, strlen(entry->fs),
				       target, entry->type, opts);
			if (ret)
				goto out;
			umount = 1;
		}
		/* If we have an external mount add it to the list */
		if (umount && !ext_mount_add(entry->fs, entry->umount)) {
			umount_ent(ap, entry->fs);
			error(ap->logopt, MODPREFIX
			      "error: could not add external mount %s",
			      entry->fs);
			ret = 1;
			goto out;
		}
		ret = do_link_mount(ap, name, entry, flags);
	}
out:
	return ret;
}

static int do_nfs_mount(struct autofs_point *ap, const char *name,
			struct amd_entry *entry, unsigned int flags)
{
	char target[PATH_MAX + 1];
	unsigned int proximity;
	char *opts = (entry->opts && *entry->opts) ? entry->opts : NULL;
	unsigned int umount = 0;
	int ret = 0;

	if (strlen(entry->rhost) + strlen(entry->rfs) + 1 > PATH_MAX) {
		error(ap->logopt, MODPREFIX
		     "error: rhost + rfs options length is too long");
		return 1;
	}

	strcpy(target, entry->rhost);
	strcat(target, ":");
	strcat(target, entry->rfs);

	proximity = get_network_proximity(entry->rhost);
	if (proximity == PROXIMITY_OTHER && entry->remopts && *entry->remopts)
		opts = entry->remopts;

	if (!entry->fs) {
		ret = mount_nfs->mount_mount(ap, ap->path, name, strlen(name),
					     target, entry->type, opts,
					     mount_nfs->context);
	} else {
		if (!is_mounted(entry->fs, MNTS_REAL)) {
			ret = mount_nfs->mount_mount(ap, entry->fs,
						entry->fs, strlen(entry->fs),
						target, entry->type, opts,
						mount_nfs->context);
			if (ret)
				goto out;
			umount = 1;
		}
		/* We might be using an external mount */
		if (umount && !ext_mount_add(entry->fs, entry->umount)) {
			umount_ent(ap, entry->fs);
			error(ap->logopt, MODPREFIX
			      "error: could not add external mount %s", entry->fs);
			ret = 1;
			goto out;
		}
		ret = do_link_mount(ap, name, entry, flags);
	}
out:
	return ret;
}

static int do_nfsl_mount(struct autofs_point *ap, const char *name,
			 struct amd_entry *entry, struct substvar *sv,
			 unsigned int flags)
{
	const struct substvar *host, *hostd;
	struct stat st;
	char *target;

	host = macro_findvar(sv, "host", 4);
	if (!host)
		return do_nfs_mount(ap, name, entry, flags);
	hostd = macro_findvar(sv, "hostd", 5);
	if (!hostd || !*hostd->val)
		return do_nfs_mount(ap, name, entry, flags);

	if (entry->sublink)
		target = entry->sublink;
	else
		target = entry->fs;

	if (strcasecmp(host->val, entry->rhost) ||
	    strcasecmp(hostd->val, entry->rhost))
		return do_nfs_mount(ap, name, entry, flags);
	else if (lstat(target, &st) < 0)
		return do_nfs_mount(ap, name, entry, flags);

	return do_link_mount(ap, name, entry, flags);
}

static int wait_for_expire(struct autofs_point *ap)
{
	int ret = 0;

	st_wait_task(ap, ST_EXPIRE, 0);

	st_mutex_lock();
	if (ap->state != ST_SHUTDOWN &&
	    ap->state != ST_SHUTDOWN_PENDING &&
	    ap->state != ST_SHUTDOWN_FORCE) {
		ret = 1;
	}
	st_mutex_unlock();

	return ret;
}

static int do_host_mount(struct autofs_point *ap, const char *name,
			 struct amd_entry *entry, struct map_source *source,
			 unsigned int flags)
{
	struct lookup_mod *lookup;
	struct map_source *instance;
	struct mnt_list *mnt = NULL;
	struct mapent *me;
	const char *argv[2];
	const char **pargv = NULL;
	int status;
	int argc = 0;
	int ret = 1;

	/*
	 * If the mount point name isn't the same as the host name
	 * then we need to symlink to it after the mount. Attempt
	 * the allocation and set entry->path to the base location
	 * of the hosts mount tree so we can find it in
	 * lookup_nss_mount() later.
	 */
	if (strcmp(name, entry->rhost)) {
		char *target;
		size_t len;

		len = ap->len + strlen(entry->rhost) + 2;
		target = malloc(len);
		if (!target) {
			warn(ap->logopt, MODPREFIX
			     "failed to alloc target to hosts mount base");
			goto out;
		}
		strcpy(target, ap->path);
		strcat(target, "/");
		strcat(target, entry->rhost);
		if (entry->path)
			free(entry->path);
		entry->path = target;

		/* Add an mnt_list entry for the updated path. */
		mnt = mnts_add_amdmount(ap, entry);
		if (!mnt) {
			error(ap->logopt, MODPREFIX
			      "failed to update mount mnt_list entry");
			goto out;
		}

		/*
		 * Wait for any expire before racing to mount the
		 * export tree or bail out if we're shutting down.
		*/
		if (!wait_for_expire(ap))
			goto out;
	}

	if (entry->opts && *entry->opts) {
		argv[0] = entry->opts;
		argv[1] = NULL;
		pargv = argv;
		argc = 1;
	}

	instance_mutex_lock();
	status = open_lookup("hosts", MODPREFIX, NULL, argc, pargv, &lookup);
	if (status != NSS_STATUS_SUCCESS) {
		debug(ap->logopt, "open lookup module hosts failed");
		instance_mutex_unlock();
		goto out;
	}

	instance = master_find_source_instance(source,
					 "hosts", "sun", argc, pargv);
	if (!instance) {
		instance = master_add_source_instance(source,
				 "hosts", "sun", monotonic_time(NULL), argc, pargv);
		if (!instance) {
			error(ap->logopt, MODPREFIX
			     "failed to create source instance for hosts map");
			instance_mutex_unlock();
			close_lookup(lookup);
			goto out;
		}
	}
	instance->lookup = lookup;
	instance_mutex_unlock();

	cache_writelock(source->mc);
	me = cache_lookup_distinct(source->mc, name);
	if (me)
		cache_push_mapent(me, NULL);
	cache_unlock(source->mc);

	master_source_current_wait(ap->entry);
	ap->entry->current = source;

	ret = lookup->lookup_mount(ap, entry->rhost,
				   strlen(entry->rhost), lookup->context);

	if (!strcmp(name, entry->rhost))
		goto out;

	if (do_mount(ap, ap->path,
		     name, strlen(name), entry->path, "bind", "symlink"))
		warn(ap->logopt, MODPREFIX
		     "failed to create symlink to hosts mount base");
out:
	if (ret && mnt)
		mnts_remove_amdmount(mnt->mp);
	return ret;
}

static int do_program_mount(struct autofs_point *ap,
			    struct amd_entry *entry, const char *name)
{
	char *prog, *str;
	char **argv;
	int argc = -1;
	int rv = 1;

	str = strdup(entry->mount);
	if (!str)
		goto out;

	prog = NULL;
	argv = NULL;

	argc = construct_argv(str, &prog, &argv);
	if (argc == -1) {
		error(ap->logopt, MODPREFIX
		      "%s: error creating mount arguments", entry->type);
		free(str);
		goto out;
	}

	/* The am-utils documentation doesn't actually say that the
	 * mount (and umount, if given) command need to use ${fs} as
	 * the mount point in the command.
	 *
	 * For program mounts there's no way to know what the mount
	 * point is so ${fs} must be used in the mount (and umount,
	 * if given) in order to create the mount point directory
	 * before executing the mount command and removing it at
	 * umount.
	 */
	if (ext_mount_inuse(entry->fs)) {
		rv = 0;
		/* An external mount with path entry->fs exists
		 * so ext_mount_add() won't fail.
		 */
		ext_mount_add(entry->fs, entry->umount);
	} else {
		rv = mkdir_path(entry->fs, mp_mode);
		if (rv && errno != EEXIST) {
			char buf[MAX_ERR_BUF];
			char *estr;

			estr = strerror_r(errno, buf, MAX_ERR_BUF);
			error(ap->logopt,
			      MODPREFIX "%s: mkdir_path %s failed: %s",
			      entry->type, entry->fs, estr);
			goto do_free;
		}

		rv = spawnv(ap->logopt, prog, (const char * const *) argv);
		if (WIFEXITED(rv) && !WEXITSTATUS(rv)) {
			if (ext_mount_add(entry->fs, entry->umount)) {
				rv = 0;
				debug(ap->logopt, MODPREFIX
				     "%s: mounted %s", entry->type, entry->fs);
				goto do_free;
			}
			umount_ent(ap, entry->fs);
		}

		if (!ext_mount_inuse(entry->fs))
			rmdir_path(ap, entry->fs, ap->dev);
		error(ap->logopt, MODPREFIX
		   "%s: failed to mount using %s", entry->type, entry->mount);
	}
do_free:
	free_argv(argc, (const char **) argv);
	free(str);

	if (rv)
		goto out;

	rv = do_link_mount(ap, name, entry, 0);
	if (!rv)
		goto out;

	if (umount_amd_ext_mount(ap, entry->fs)) {
		if (!ext_mount_inuse(entry->fs))
			rmdir_path(ap, entry->fs, ap->dev);
		debug(ap->logopt, MODPREFIX
		      "%s: failed to umount external mount at %s",
		      entry->type, entry->fs);
	}
out:
	return rv;
}

static unsigned int validate_auto_options(unsigned int logopt,
					  struct amd_entry *entry)
{
	/*
	 * The amd manual implies all the mount type auto options
	 * are optional but I don't think there's much point if
	 * no map is given. If the option has been intentionally
	 * left blank the mount must be expected to fail so don't
	 * report the error.
	 */
	if (!entry->fs) {
		error(logopt, MODPREFIX
		      "%s: file system not given", entry->type);
		return 0;
	} else if (!*entry->fs)
		return 0;
	return 1;
}

static unsigned int validate_link_options(unsigned int logopt,
					  struct amd_entry *entry)
{
	/* fs is the destimation of the link */
	return validate_auto_options(logopt, entry);
}

static unsigned int validate_nfs_options(unsigned int logopt,
					 struct amd_entry *entry)
{
	/*
	 * Required option rhost will always have a value unless
	 * it has been intentionally left blank. It is set from
	 * ${host} if it is found to be NULL earlier in the parsing
	 * process. Don't report the error if it has been left blank
	 * or if the fs option has been left blank since the mount is
	 * expected to fail.
	 */
	if (!entry->rfs || !*entry->rfs) {
		if (entry->rfs && !*entry->rfs)
			return 0;
		/* Map option fs has been intentionally left blank */
		if (entry->fs && !*entry->fs)
			return 0;
		entry->rfs = strdup(entry->fs);
		if (!entry->rfs) {
			error(logopt, MODPREFIX
			      "%s: remote file system not given", entry->type);
			return 0;
		}
	}
	if (entry->sublink && !entry->fs) {
		error(logopt, MODPREFIX
		      "%s: sublink option requires option fs");
		return 0;
	}
	return 1;
}

static unsigned int validate_generic_options(unsigned int logopt,
					     unsigned long fstype,
					     struct amd_entry *entry)
{
	/*
	 * If dev or rfs are empty in the map entry the mount is
	 * expected to fail so don't report the error.
	 */
	if (fstype != AMD_MOUNT_TYPE_LOFS) {
		if (!entry->dev) {
			error(logopt, MODPREFIX
			      "%s: mount device not given", entry->type);
			return 0;
		} else if (!*entry->dev)
			return 0;
	} else {
		if (!entry->rfs) {
			/*
			 * Can't use entry->type as the mount type to reprot
			 * the error since entry->type == "bind" not "lofs".
			 */
			error(logopt, MODPREFIX "lofs: mount device not given");
			return 0;
		} else if (!*entry->rfs)
			return 0;
	}
	if (entry->sublink && !entry->fs) {
		error(logopt, MODPREFIX
		      "%s: sublink option requires option fs");
		return 0;
	}
	return 1;
}

static unsigned int validate_ufs_fstype(unsigned int logopt,
					struct amd_entry *entry)
{
	const char *type = (const char *) entry->type;

	if (strcmp(type, "ext") && strcmp(type, "ext2") &&
	    strcmp(type, "ext3") && strcmp(type, "ext4") &&
	    strcmp(type, "xfs") && strcmp(type, "jfs")) {
		error(logopt, MODPREFIX
		      "%s: mount type %s not valid as ufs mount type on Linux",
		      type);
		return 0;
	}
	return 1;
}

static unsigned int validate_host_options(unsigned int logopt,
					  struct amd_entry *entry)
{
	/*
	 * rhost is always non-null, unless it is intentionally left
	 * empty, because it will have the the value of the host name
	 * if it isn't given in the map entry. Don't report an error
	 * if it has been left empty since it's expected to fail.
	 */
	if (!entry->rhost) {
		error(logopt, MODPREFIX
		      "%s: remote host name not given", entry->type);
		return 0;
	} else if (!*entry->rhost)
		return 0;
	return 1;
}

static unsigned int validate_program_options(unsigned int logopt,
					     struct amd_entry *entry)
{
	/*
	 * entry->mount will be NULL if there is a problem expanding
	 * ${} macros in expandamdent().
	 */
	if (!entry->mount) {
		error(logopt, MODPREFIX
		      "%s: mount program invalid or not set", entry->type);
		return 0;
	}

	if (!entry->fs || !*entry->fs) {
		error(logopt, MODPREFIX
		    "%s: ${fs} must be used as the mount point but is not set",
		    entry->type);
		return 0;
	}

	return 1;
}

static int amd_mount(struct autofs_point *ap, const char *name,
		     struct amd_entry *entry, struct map_source *source,
		     struct substvar *sv, unsigned int flags,
		     struct parse_context *ctxt)
{
	unsigned long fstype = entry->flags & AMD_MOUNT_TYPE_MASK;
	int ret = 1;

	switch (fstype) {
	case AMD_MOUNT_TYPE_AUTO:
		if (!validate_auto_options(ap->logopt, entry))
			return 1;
		ret = do_auto_mount(ap, name, entry, flags);
		break;

	case AMD_MOUNT_TYPE_LOFS:
		if (!validate_generic_options(ap->logopt, fstype, entry))
			return 1;
		ret = do_generic_mount(ap, name, entry, entry->rfs, flags);
		break;

	case AMD_MOUNT_TYPE_UFS:
		if (!validate_ufs_fstype(ap->logopt, entry))
			return 1;
		/* fall through to validate generic options */

	case AMD_MOUNT_TYPE_EXT:
	case AMD_MOUNT_TYPE_XFS:
	case AMD_MOUNT_TYPE_CDFS:
		if (!validate_generic_options(ap->logopt, fstype, entry))
			return 1;
		ret = do_generic_mount(ap, name, entry, entry->dev, flags);
		break;

	case AMD_MOUNT_TYPE_NFS:
		if (!validate_nfs_options(ap->logopt, entry))
			return 1;
		ret = do_nfs_mount(ap, name, entry, flags);
		break;

	case AMD_MOUNT_TYPE_NFSL:
		if (!validate_nfs_options(ap->logopt, entry) ||
		    !validate_link_options(ap->logopt, entry))
			return 1;
		ret = do_nfsl_mount(ap, name, entry, sv, flags);
		break;

	case AMD_MOUNT_TYPE_LINK:
		if (!validate_link_options(ap->logopt, entry))
			return 1;
		ret = do_link_mount(ap, name, entry, flags);
		break;

	case AMD_MOUNT_TYPE_LINKX:
		if (!validate_link_options(ap->logopt, entry))
			return 1;
		ret = do_linkx_mount(ap, name, entry, flags);
		break;

	case AMD_MOUNT_TYPE_HOST:
		if (!validate_host_options(ap->logopt, entry))
			return 1;
		ret = do_host_mount(ap, name, entry, source, flags);
		break;

	case AMD_MOUNT_TYPE_PROGRAM:
		if (!validate_program_options(ap->logopt, entry))
			return 1;
		ret = do_program_mount(ap, entry, name);
		break;

	default:
		info(ap->logopt,
		     MODPREFIX "unknown file system type %x", fstype);
		break;
	}

	return ret;
}

void dequote_entry(struct autofs_point *ap, struct amd_entry *entry)
{
	char *res;

	if (entry->pref) {
		res = dequote(entry->pref, strlen(entry->pref), ap->logopt);
		if (res) {
			debug(ap->logopt,
			      MODPREFIX "pref dequote(\"%.*s\") -> %s",
			      strlen(entry->pref), entry->pref, res);
			free(entry->pref);
			entry->pref = res;
		}
	}

	if (entry->sublink) {
		res = dequote(entry->sublink, strlen(entry->sublink), ap->logopt);
		if (res) {
			debug(ap->logopt,
			      MODPREFIX "sublink dequote(\"%.*s\") -> %s",
			      strlen(entry->sublink), entry->sublink, res);
			free(entry->sublink);
			entry->sublink = res;
		}
	}

	if (entry->fs && *entry->fs) {
		res = dequote(entry->fs, strlen(entry->fs), ap->logopt);
		if (res) {
			debug(ap->logopt,
			      MODPREFIX "fs dequote(\"%.*s\") -> %s",
			      strlen(entry->fs), entry->fs, res);
			free(entry->fs);
			entry->fs = res;
		}
	}

	if (entry->rfs && *entry->rfs) {
		res = dequote(entry->rfs, strlen(entry->rfs), ap->logopt);
		if (res) {
			debug(ap->logopt,
			      MODPREFIX "rfs dequote(\"%.*s\") -> %s",
			      strlen(entry->rfs), entry->rfs, res);
			free(entry->rfs);
			entry->rfs = res;
		}
	}

	if (entry->opts && *entry->opts) {
		res = dequote(entry->opts, strlen(entry->opts), ap->logopt);
		if (res) {
			debug(ap->logopt,
			      MODPREFIX "ops dequote(\"%.*s\") -> %s",
			      strlen(entry->opts), entry->opts, res);
			free(entry->opts);
			entry->opts = res;
		}
	}

	if (entry->remopts && *entry->remopts) {
		res = dequote(entry->remopts, strlen(entry->remopts), ap->logopt);
		if (res) {
			debug(ap->logopt,
			      MODPREFIX "remopts dequote(\"%.*s\") -> %s",
			      strlen(entry->remopts), entry->remopts, res);
			free(entry->remopts);
			entry->remopts = res;
		}
	}

	if (entry->addopts && *entry->addopts) {
		res = dequote(entry->addopts, strlen(entry->addopts), ap->logopt);
		if (res) {
			debug(ap->logopt,
			      MODPREFIX "addopts dequote(\"%.*s\") -> %s",
			      strlen(entry->addopts), entry->addopts, res);
			free(entry->addopts);
			entry->addopts = res;
		}
	}

	return;
}

static void normalize_sublink(unsigned int logopt,
			      struct amd_entry *entry, struct substvar *sv)
{
	char *new;
	size_t len;

	/* Normalizing sublink requires a non-blank fs option */
	if (!*entry->fs)
		return;

	if (entry->sublink && *entry->sublink != '/') {
		len = strlen(entry->fs) + strlen(entry->sublink) + 2;
		new = malloc(len);
		if (!new) {
			error(logopt, MODPREFIX
			      "error: couldn't allocate storage for sublink");
			return;
		}
		strcpy(new, entry->fs);
		strcat(new, "/");
		strcat(new, entry->sublink);
		debug(logopt, MODPREFIX
		      "rfs dequote(\"%.*s\") -> %s",
		      strlen(entry->sublink), entry->sublink, new);
		free(entry->sublink);
		entry->sublink = new;
	}
	return;
}

/*
 * Set the prefix.
 *
 * This is done in a couple of places, here is as good a place as
 * any to describe it.
 *
 * If a prefix is present in the map entry then use it.
 *
 * A pref option with the value none is required to use no prefix,
 * otherwise the prefix of the parent map, if any, will be used.
 */
static void update_prefix(struct autofs_point *ap,
			  struct amd_entry *entry, const char *name)
{
	size_t len;
	char *new;

	if (!entry->pref && ap->pref) {
		len = strlen(ap->pref) + strlen(name) + 2;
		new = malloc(len);
		if (new) {
			strcpy(new, ap->pref);
			strcat(new, name);
			strcat(new, "/");
			entry->pref = new;
		}
	}
	return;
}

static int match_selectors(struct autofs_point *ap,
			   struct amd_entry *entry, struct substvar *sv)
{
	struct selector *s = entry->selector;
	int ret;

	/* No selectors, always match */
	if (!s) {
		debug(ap->logopt, MODPREFIX "no selectors found in location");
		return 1;
	}

	ret = 0;

	/* All selectors must match */
	while (s) {
		ret = eval_selector(ap, entry, sv);
		if (!ret)
			break;
		s = s->next;
	}
	if (!s)
		ret = 1;

	return ret;
}

static struct amd_entry *dup_defaults_entry(struct amd_entry *defaults)
{
	struct amd_entry *entry;
	char *tmp;

	entry = malloc(sizeof(struct amd_entry));
	if (!entry)
		return NULL;
	memset(entry, 0, sizeof(struct amd_entry));

	entry->flags = defaults->flags;

	if (defaults->type) {
		tmp = strdup(defaults->type);
		if (tmp)
			entry->type = tmp;
	}

	if (defaults->map_type) {
		tmp = strdup(defaults->map_type);
		if (tmp)
			entry->map_type = tmp;
	}

	if (defaults->pref) {
		tmp = strdup(defaults->pref);
		if (tmp)
			entry->pref = tmp;
	}

	if (defaults->fs) {
		tmp = strdup(defaults->fs);
		if (tmp)
			entry->fs = tmp;
	}

	/* These shouldn't be blank in a defaults entry but ... */

	if (defaults->rfs && *defaults->rfs) {
		tmp = strdup(defaults->rfs);
		if (tmp)
			entry->rfs = tmp;
	}

	if (defaults->rhost && *defaults->rhost) {
		tmp = strdup(defaults->rhost);
		if (tmp)
			entry->rhost = tmp;
	}

	if (defaults->dev && *defaults->dev) {
		tmp = strdup(defaults->dev);
		if (tmp)
			entry->dev = tmp;
	}

	if (defaults->opts && *defaults->opts) {
		tmp = strdup(defaults->opts);
		if (tmp)
			entry->opts = tmp;
	}

	if (defaults->addopts && *defaults->addopts) {
		tmp = strdup(defaults->addopts);
		if (tmp)
			entry->addopts = tmp;
	}

	if (defaults->remopts && *defaults->remopts) {
		tmp = strdup(defaults->remopts);
		if (tmp)
			entry->remopts = tmp;
	}

	INIT_LIST_HEAD(&entry->list);

	return entry;
}

struct amd_entry *make_default_entry(struct autofs_point *ap,
				     struct substvar *sv)
{
	char *defaults = "opts:=rw,defaults";
	struct amd_entry *defaults_entry;
	struct list_head dflts;
	char *map_type;

	INIT_LIST_HEAD(&dflts);
	if (amd_parse_list(ap, defaults, &dflts, &sv))
		return NULL;
	defaults_entry = list_entry(dflts.next, struct amd_entry, list);
	/*
	 * If map type isn't given try to inherit from
	 * parent. A NULL map type is valid and means
	 * use configured nss sources.
	 */
	map_type = conf_amd_get_map_type(ap->path);
	if (map_type) {
		defaults_entry->map_type = map_type;
#ifndef HAVE_HESIOD
		if (!strcmp(map_type, "hesiod")) {
			warn(ap->logopt, MODPREFIX
			     "hesiod support not built in, "
			     "defaults map entry not set");
			defaults_entry = NULL;
		}
#endif
	}
	if (defaults_entry)
		list_del_init(&defaults_entry->list);
	/* The list should now be empty .... */
	free_amd_entry_list(&dflts);
	return defaults_entry;
}

static struct amd_entry *select_default_entry(struct autofs_point *ap,
					      struct list_head *entries,
					      struct substvar *sv)
{
	unsigned long flags = conf_amd_get_flags(ap->path);
	struct amd_entry *defaults_entry = NULL;
	struct amd_entry *entry_default = NULL;
	struct list_head *p, *head;

	if (!(flags & CONF_SELECTORS_IN_DEFAULTS))
		goto no_sel;

	head = entries;
	p = head->next;
	while (p != head) {
		struct amd_entry *this = list_entry(p, struct amd_entry, list);

		p = p->next;

		if (this->flags & AMD_DEFAULTS_MERGE) {
			if (entry_default)
				free_amd_entry(entry_default);
			list_del_init(&this->list);
			entry_default = this;
			continue;
		} else if (this->flags & AMD_DEFAULTS_RESET) {
			struct amd_entry *new;
			new = dup_defaults_entry(defaults_entry);
			if (new) {
				free_amd_entry(entry_default);
				entry_default = new;
			}
			list_del_init(&this->list);
			free_amd_entry(this);
			continue;
		}

		/*
		 * This probably should be a fail since we expect
		 * selectors to pick the default entry.
		 */
		if (!this->selector)
			continue;

		if (match_selectors(ap, this, sv)) {
			if (entry_default) {
				/*update_with_defaults(entry_default, this, sv);*/
				free_amd_entry(entry_default);
			}
			list_del_init(&this->list);
			defaults_entry = this;
			break;
		}
	}

	/* Not strickly amd semantics but ... */
	if (!defaults_entry && entry_default) {
		defaults_entry = entry_default;
		goto done;
	}

	if (!defaults_entry) {
		debug(ap->logopt, MODPREFIX
		      "no matching selector(s) found in defaults, "
		      "using internal defaults");
		goto ret_default;
	}

	goto done;

no_sel:
	if (list_empty(entries))
		goto ret_default;

	defaults_entry = list_entry(entries->next, struct amd_entry, list);
	list_del_init(&defaults_entry->list);
	if (!list_empty(entries)) {
		free_amd_entry(defaults_entry);
		goto ret_default;
	}
done:
	/*merge_entry_options(ap, defaults_entry, sv);*/
	/*normalize_sublink(ap->logopt, defaults_entry, sv);*/
	return defaults_entry;

ret_default:
	return make_default_entry(ap, sv);
}

static struct amd_entry *get_defaults_entry(struct autofs_point *ap,
					    const char *defaults,
					    struct substvar *sv)
{
	struct amd_entry *entry;
	struct list_head dflts;

	INIT_LIST_HEAD(&dflts);

	entry = NULL;
	if (!defaults)
		goto out;
	else {
		char *expand;
		if (!expand_selectors(ap, defaults, &expand, sv))
			goto out;
		if (amd_parse_list(ap, expand, &dflts, &sv)) {
			error(ap->logopt, MODPREFIX
			     "failed to parse defaults entry, "
			     "attempting to use internal default");
			free(expand);
			goto out;
		}
		entry = select_default_entry(ap, &dflts, sv);
		if (entry && !entry->map_type) {
			/*
			 * If map type isn't given try to inherit from
			 * parent. A NULL map type is valid and means
			 * use configured nss sources.
			 */
			char *map_type = conf_amd_get_map_type(ap->path);
			if (map_type) {
				entry->map_type = map_type;
#ifndef HAVE_HESIOD
				if (!strcmp(map_type, "hesiod")) {
					warn(ap->logopt, MODPREFIX
					     "hesiod support not built in, "
					     "attempting to use internal "
					     "default");
					free_amd_entry(entry);
					free(expand);
					goto out;
				}
#endif
			}
		}
		free(expand);
	}

	return entry;
out:
	return make_default_entry(ap, sv);
}

static struct amd_entry *setup_defaults(struct autofs_point *ap,
					const char *name, int name_len,
					struct map_source *source,
					struct substvar **sv)
{
	struct amd_entry *defaults_entry;
	struct mapent_cache *mc;
	struct mapent *me;
	struct substvar *nsv;
	char *defaults;

	mc = source->mc;
	defaults = NULL;
	defaults_entry = NULL;

	nsv = add_lookup_vars(ap, name, name_len, source, NULL);
	if (!nsv)
		goto done;

	defaults = conf_amd_get_map_defaults(ap->path);
	if (defaults) {
		debug(ap->logopt, MODPREFIX
		      "using map_defaults %s for %s", defaults, ap->path);
	} else if ((me = cache_lookup_distinct(mc, "/defaults"))) {
		defaults = strdup(me->mapent);
		if (defaults)
			debug(ap->logopt, MODPREFIX
			      "using /defaults %s from map", defaults);
		else {
			char buf[MAX_ERR_BUF];
			char *estr = strerror_r(errno, buf, MAX_ERR_BUF);
			error(ap->logopt, MODPREFIX "malloc: %s", estr);
			macro_free_table(nsv);
			nsv = NULL;
			goto done;
		}
	}

	defaults_entry = get_defaults_entry(ap, defaults, nsv);
	if (!defaults_entry) {
		error(ap->logopt, MODPREFIX "failed to get a defaults entry");
		macro_free_table(nsv);
		nsv = NULL;
	}
done:
	if (defaults)
		free(defaults);
	if (*sv)
		macro_free_table(*sv);
	*sv = nsv;

	return defaults_entry;
}

int parse_mount(struct autofs_point *ap, const char *name,
		int name_len, const char *mapent, void *context)
{
	struct parse_context *ctxt = (struct parse_context *) context;
	unsigned int flags = conf_amd_get_flags(ap->path);
	struct substvar *sv = NULL;
	struct map_source *source;
	unsigned int at_least_one;
	struct list_head entries, *p, *head;
	struct amd_entry *defaults_entry;
	struct amd_entry *cur_defaults;
	struct mnt_list *mnt;
	int rv = 1;
	int cur_state;
	int ret;

	source = ap->entry->current;
	ap->entry->current = NULL;
	master_source_current_signal(ap->entry);

	if (!mapent) {
		warn(ap->logopt, MODPREFIX "error: empty map entry");
		return 1;
	}

	INIT_LIST_HEAD(&entries);

	pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &cur_state);

	defaults_entry = setup_defaults(ap, name, name_len, source, &sv);
	if (!defaults_entry) {
		error(ap->logopt, MODPREFIX
		      "failed to setup defaults entry");
		goto done;
	}

	ret = amd_parse_list(ap, mapent, &entries, &sv);
	if (ret) {
		error(ap->logopt,
		      MODPREFIX "failed to parse entry: %s", mapent);
		goto done;
	}

	if (list_empty(&entries)) {
		error(ap->logopt, MODPREFIX "no location found after parse");
		goto done;
	}

	cur_defaults = dup_defaults_entry(defaults_entry);
	if (!cur_defaults) {
		error(ap->logopt, MODPREFIX
		      "failed to duplicate defaults entry");
		goto done;
	}

	at_least_one = 0;
	head = &entries;
	p = head->next;
	while (p != head) {
		struct amd_entry *this = list_entry(p, struct amd_entry, list);
		p = p->next;

		if (this->flags & AMD_DEFAULTS_MERGE) {
			free_amd_entry(cur_defaults);
			list_del_init(&this->list);
			cur_defaults = this;
			update_with_defaults(defaults_entry, cur_defaults, sv);
			debug(ap->logopt, "merged /defaults entry with defaults");
			continue;
		} else if (this->flags & AMD_DEFAULTS_RESET) {
			struct amd_entry *nd, *new;
			struct substvar *nsv = NULL;

			nd = setup_defaults(ap, name, name_len, source, &nsv);
			if (nd) {
				free_amd_entry(defaults_entry);
				defaults_entry = nd;
				macro_free_table(sv);
				sv = nsv;
			}

			new = dup_defaults_entry(defaults_entry);
			if (new) {
				free_amd_entry(cur_defaults);
				cur_defaults = new;
			}
			list_del_init(&this->list);
			free_amd_entry(this);
			continue;
		}

		debug(ap->logopt, "expand defaults entry");
		sv = expand_entry(ap, cur_defaults, flags, sv);

		if (this->flags & AMD_ENTRY_CUT && at_least_one) {
			info(ap->logopt, MODPREFIX
			     "at least one entry tried before cut selector, "
			     "not continuing");
			break;
		}

		if (!match_selectors(ap, this, sv))
			continue;

		at_least_one = 1;

		debug(ap->logopt, "update mount entry with defaults");
		update_with_defaults(cur_defaults, this, sv);
		debug(ap->logopt, "expand mount entry");
		sv = expand_entry(ap, this, flags, sv);
		sv = merge_entry_options(ap, this, sv);
		normalize_sublink(ap->logopt, this, sv);
		update_prefix(ap, this, name);

		dequote_entry(ap, this);

		/*
		 * Type "auto" needs to set the prefix at mount time so
		 * add parsed entry to parent amd mount list and remove
		 * on mount fail.
		 */
		mnt = mnts_add_amdmount(ap, this);
		if (!mnt) {
			error(ap->logopt, MODPREFIX
			      "failed to add mount to mnt_list");
			break;
		}

		rv = amd_mount(ap, name, this, source, sv, flags, ctxt);
		if (!rv) {
			/*
			 * If entry->path doesn't match the mnt->mp then
			 * it's a "host" map and the mount point path is
			 * different to the lookup name. Add a new mnt_list
			 * entry so that both the symlinked name and the
			 * mount expire.
			 */
			if (strcmp(this->path, mnt->mp))
				mnts_add_mount(ap, this->rhost, MNTS_INDIRECT|MNTS_MOUNTED);
			break;
		}
		/* Not mounted, remove the mnt_list entry from amdmount list */
		mnts_remove_amdmount(this->path);
	}
	free_amd_entry(cur_defaults);

	if (rv)
		debug(ap->logopt, MODPREFIX
		      "no more locations to try, returning fail");
done:
	free_amd_entry_list(&entries);
	free_amd_entry(defaults_entry);
	macro_free_table(sv);

	pthread_setcancelstate(cur_state, NULL);

	return rv;
}

int parse_done(void *context)
{
	int rv = 0;
	struct parse_context *ctxt = (struct parse_context *) context;

	instance_mutex_lock();
	if (--init_ctr == 0) {
		rv = close_mount(mount_nfs);
		mount_nfs = NULL;
	}
	instance_mutex_unlock();
	if (ctxt)
		kill_context(ctxt);

	return rv;
}
