/*
 * Copyright (c) 1997-2002 Erez Zadok
 * Copyright (c) 1990 Jan-Simon Pendry
 * Copyright (c) 1990 Imperial College of Science, Technology & Medicine
 * Copyright (c) 1990 The Regents of the University of California.
 * All rights reserved.
 *
 * This code is derived from software contributed to Berkeley by
 * Jan-Simon Pendry at Imperial College, London.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. All advertising materials mentioning features or use of this software
 *    must display the following acknowledgment:
 *      This product includes software developed by the University of
 *      California, Berkeley and its contributors.
 * 4. Neither the name of the University nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 *
 *
 * $Id: amfs_auto.c,v 1.1.1.1 2002/05/15 01:21:53 jkh Exp $
 *
 */

/*
 * Automount file system
 */

#ifdef HAVE_CONFIG_H
# include <config.h>
#endif /* HAVE_CONFIG_H */
#include <am_defs.h>
#include <amd.h>

/****************************************************************************
 *** MACROS                                                               ***
 ****************************************************************************/
#define	IN_PROGRESS(cp) ((cp)->mp->am_mnt->mf_flags & MFF_MOUNTING)

#define DOT_DOT_COOKIE	(u_int) 1

/****************************************************************************
 *** STRUCTURES                                                           ***
 ****************************************************************************/


/****************************************************************************
 *** FORWARD DEFINITIONS                                                  ***
 ****************************************************************************/
static int amfs_auto_bgmount(struct continuation *cp, int mpe);
static int amfs_auto_mount(am_node *mp, mntfs *mf);
static int amfs_auto_readdir_browsable(am_node *mp, nfscookie cookie, nfsdirlist *dp, nfsentry *ep, int count, int fully_browsable);


/****************************************************************************
 *** OPS STRUCTURES                                                       ***
 ****************************************************************************/
am_ops amfs_auto_ops =
{
  "auto",
  amfs_auto_match,
  0,				/* amfs_auto_init */
  amfs_auto_mount,
  amfs_auto_umount,
  amfs_auto_lookup_child,
  amfs_auto_mount_child,
  amfs_auto_readdir,
  0,				/* amfs_auto_readlink */
  amfs_auto_mounted,
  0,				/* amfs_auto_umounted */
  find_amfs_auto_srvr,
  FS_AMQINFO | FS_DIRECTORY | FS_AUTOFS,
  FS_AMQINFO | FS_DIRECTORY | FS_AUTOFS,
};


/****************************************************************************
 *** FUNCTIONS                                                             ***
 ****************************************************************************/
/*
 * AMFS_AUTO needs nothing in particular.
 */
char *
amfs_auto_match(am_opts *fo)
{
  char *p = fo->opt_rfs;

  if (!fo->opt_rfs) {
    plog(XLOG_USER, "auto: no mount point named (rfs:=)");
    return 0;
  }
  if (!fo->opt_fs) {
    plog(XLOG_USER, "auto: no map named (fs:=)");
    return 0;
  }

  /*
   * Swap round fs:= and rfs:= options
   * ... historical (jsp)
   */
  fo->opt_rfs = fo->opt_fs;
  fo->opt_fs = p;

  /*
   * mtab entry turns out to be the name of the mount map
   */
  return strdup(fo->opt_rfs ? fo->opt_rfs : ".");
}




/*
 * Build a new map cache for this node, or re-use
 * an existing cache for the same map.
 */
void
amfs_auto_mkcacheref(mntfs *mf)
{
  char *cache;

  if (mf->mf_fo && mf->mf_fo->opt_cache)
    cache = mf->mf_fo->opt_cache;
  else
    cache = "none";
  mf->mf_private = (voidp) mapc_find(mf->mf_info, cache,
				     mf->mf_fo->opt_maptype);
  mf->mf_prfree = mapc_free;
}


/*
 * Mount a sub-mount
 */
static int
amfs_auto_mount(am_node *mp, mntfs *mf)
{
  /*
   * Pseudo-directories are used to provide some structure
   * to the automounted directories instead
   * of putting them all in the top-level automount directory.
   *
   * Here, just increment the parent's link count.
   */
  mp->am_parent->am_fattr.na_nlink++;

  /*
   * Info field of . means use parent's info field.
   * Historical - not documented.
   */
  if (mf->mf_info[0] == '.' && mf->mf_info[1] == '\0')
    mf->mf_info = strealloc(mf->mf_info, mp->am_parent->am_mnt->mf_info);

  /*
   * Compute prefix:
   *
   * If there is an option prefix then use that else
   * If the parent had a prefix then use that with name
   *      of this node appended else
   * Use the name of this node.
   *
   * That means if you want no prefix you must say so
   * in the map.
   */
  if (mf->mf_fo->opt_pref) {
    /* allow pref:=null to set a real null prefix */
    if (STREQ(mf->mf_fo->opt_pref, "null")) {
      mp->am_pref = "";
    } else {
      /*
       * the prefix specified as an option
       */
      mp->am_pref = strdup(mf->mf_fo->opt_pref);
    }
  } else {
    /*
     * else the parent's prefix
     * followed by the name
     * followed by /
     */
    char *ppref = mp->am_parent->am_pref;
    if (ppref == 0)
      ppref = "";
    mp->am_pref = str3cat((char *) 0, ppref, mp->am_name, "/");
  }

#ifdef HAVE_FS_AUTOFS
  if (mf->mf_flags & MFF_AUTOFS) {
    char opts[256];
    int error;

    autofs_get_opts(opts, mf->mf_autofs_fh);

    /* now do the mount */
    error = mount_amfs_toplvl(mf, opts);
    if (error) {
      errno = error;
      plog(XLOG_FATAL, "amfs_auto_mount: mount_amfs_toplvl failed: %m");
      return error;
    }
  }
#endif /* HAVE_FS_AUTOFS */

  /*
   * Attach a map cache
   */
  amfs_auto_mkcacheref(mf);

  return 0;
}



void
amfs_auto_mounted(mntfs *mf)
{
  amfs_auto_mkcacheref(mf);
#ifdef HAVE_FS_AUTOFS
  if (mf->mf_flags & MFF_AUTOFS)
    autofs_mounted(mf);
#endif /* HAVE_FS_AUTOFS */
}



/*
 * Unmount an automount sub-node
 */
int
amfs_auto_umount(am_node *mp, mntfs *mf)
{
  int error = 0;

#ifdef HAVE_FS_AUTOFS
  if (mf->mf_flags & MFF_AUTOFS) {
    error = UMOUNT_FS(mp->am_path, mnttab_file_name);
    /*
     * autofs mounts are in place, so it is possible
     * that we can't just unmount our mount points and go away.
     * If that's the case, just give up.
     */
    if (error == EBUSY)
      goto out;

    autofs_release_fh(mf->mf_autofs_fh);
    mf->mf_autofs_fh = 0;
  }
 out:
#endif /* HAVE_FS_AUTOFS */

  return error;
}


/*
 * Discard an old continuation
 */
void
free_continuation(struct continuation *cp)
{
  mntfs **mf;

  if (cp->callout)
    untimeout(cp->callout);
  /*
   * we must free the mntfs's in the list.
   * so free all of them if there was an error,
   * or free all but the used one, if the mount succeeded.
   */
  for (mf = cp->mp->am_mfarray; *mf; mf++)
    if (cp->mp->am_error || cp->mp->am_mnt != *mf)
      free_mntfs(*mf);
  XFREE(cp->mp->am_mfarray);
  cp->mp->am_mfarray = 0;
  XFREE(cp);
}


/*
 * Discard the underlying mount point and replace
 * with a reference to an error filesystem.
 */
void
assign_error_mntfs(am_node *mp)
{
  if (mp->am_error > 0) {
    /*
     * Save the old error code
     */
    int error = mp->am_error;
    if (error <= 0)
      error = mp->am_mnt->mf_error;
    /*
     * Discard the old filesystem
     */
    free_mntfs(mp->am_mnt);
    /*
     * Allocate a new error reference
     */
    mp->am_mnt = new_mntfs();
    /*
     * Put back the error code
     */
    mp->am_mnt->mf_error = error;
    mp->am_mnt->mf_flags |= MFF_ERROR;
    /*
     * Zero the error in the mount point
     */
    mp->am_error = 0;
  }
}


/*
 * The continuation function.  This is called by
 * the task notifier when a background mount attempt
 * completes.
 */
void
amfs_auto_cont(int rc, int term, voidp closure)
{
  struct continuation *cp = (struct continuation *) closure;
  mntfs *mf = cp->mp->am_mnt;

  /*
   * Definitely not trying to mount at the moment
   */
  mf->mf_flags &= ~MFF_MOUNTING;

  /*
   * While we are mounting - try to avoid race conditions
   */
  new_ttl(cp->mp);

  /*
   * Wakeup anything waiting for this mount
   */
  wakeup((voidp) mf);

  /*
   * Check for termination signal or exit status...
   */
  if (rc || term) {
    am_node *xmp;

    if (term) {
      /*
       * Not sure what to do for an error code.
       */
      mf->mf_error = EIO;	/* XXX ? */
      mf->mf_flags |= MFF_ERROR;
      plog(XLOG_ERROR, "mount for %s got signal %d", cp->mp->am_path, term);
    } else {
      /*
       * Check for exit status...
       */
#ifdef __linux__
      /*
       * HACK ALERT!
       *
       * On Linux (and maybe not only) it's possible to run
       * an amd which "knows" how to mount certain combinations
       * of nfs_proto/nfs_version which the kernel doesn't grok.
       * So if we got an EINVAL and we have a server that's not
       * using NFSv2/UDP, try again with NFSv2/UDP.
       */
      if (rc == EINVAL &&
	  mf->mf_server &&
	  (mf->mf_server->fs_version != 2 ||
	   !STREQ(mf->mf_server->fs_proto, "udp")))
	mf->mf_flags |= MFF_NFS_SCALEDOWN;
      else
#endif /* __linux__ */
      {
	mf->mf_error = rc;
	mf->mf_flags |= MFF_ERROR;
	errno = rc;		/* XXX */
	if (!STREQ(cp->mp->am_mnt->mf_ops->fs_type, "linkx"))
	  plog(XLOG_ERROR, "%s: mount (amfs_auto_cont): %m", cp->mp->am_path);
      }
    }

    if (mf->mf_flags & MFF_NFS_SCALEDOWN)
      amfs_auto_bgmount(cp, 0);
    else {
      /*
       * If we get here then that attempt didn't work, so
       * move the info vector pointer along by one and
       * call the background mount routine again
       */
      amd_stats.d_merr++;
      cp->mf++;
      xmp = cp->mp;
      amfs_auto_bgmount(cp, 0);
      assign_error_mntfs(xmp);
    }
  } else {
    /*
     * The mount worked.
     */
    am_mounted(cp->mp);
    free_continuation(cp);
  }

  reschedule_timeout_mp();
}


/*
 * Retry a mount
 */
void
amfs_auto_retry(int rc, int term, voidp closure)
{
  struct continuation *cp = (struct continuation *) closure;
  int error = 0;

  dlog("Commencing retry for mount of %s", cp->mp->am_path);

  new_ttl(cp->mp);

  if ((cp->start + ALLOWED_MOUNT_TIME) < clocktime()) {
    /*
     * The entire mount has timed out.  Set the error code and skip past all
     * the mntfs's so that amfs_auto_bgmount will not have any more
     * ways to try the mount, thus causing an error.
     */
    plog(XLOG_INFO, "mount of \"%s\" has timed out", cp->mp->am_path);
    error = ETIMEDOUT;
    while (*cp->mf)
      cp->mf++;
    /* explicitly forbid further retries after timeout */
    cp->retry = FALSE;
  }
  if (error || !IN_PROGRESS(cp)) {
    (void) amfs_auto_bgmount(cp, error);
  }
  reschedule_timeout_mp();
}


/*
 * Try to mount a file system.  Can be called
 * directly or in a sub-process by run_task.
 */
int
try_mount(voidp mvp)
{
  int error = 0;
  am_node *mp = (am_node *) mvp;
  mntfs *mf = mp->am_mnt;

  /*
   * If the directory is not yet made and it needs to be made, then make it!
   * This may be run in a background process in which case the flag setting
   * won't be noticed later - but it is set anyway just after run_task is
   * called.  It should probably go away totally...
   */
  if (!(mf->mf_flags & MFF_MKMNT) && mf->mf_fsflags & FS_MKMNT) {
    error = mkdirs(mf->mf_mount, 0555);
    if (!error)
      mf->mf_flags |= MFF_MKMNT;
  }

  /*
   * Mount it!
   */
  error = mount_node(mp);

  if (error > 0)
    dlog("amfs_auto: call to mount_node(%s) failed: %s",
	 mp->am_path, strerror(error));

  return error;
}


/*
 * Pick a file system to try mounting and
 * do that in the background if necessary
 *
For each location:
	discard previous mount location if required
	fetch next mount location
	if the filesystem failed to be mounted then
		this_error = error from filesystem
	elif the filesystem is mounting or unmounting then
		this_error = -1
	elif the fileserver is down then
		this_error = -1
	elif the filesystem is already mounted
		this_error = 0
		break
	fi
	if no error on this mount then
		this_error = initialize mount point
	fi
	if file server is down then
		this_error = -1
	fi
	if no error on this mount and mount is delayed then
		this_error = -1
	fi
	if this_error < 0 then
		retry = true
	fi
	if no error on this mount then
		if mount in background then
			run mount in background
			return -1
		else
			this_error = mount in foreground
		fi
	fi
	if an error occurred on this mount then
		update stats
		save error in mount point
	fi
endfor
 */
static int
amfs_auto_bgmount(struct continuation *cp, int mp_error)
{
  am_node *mp = cp->mp;
  mntfs *mf = mp->am_mnt;	/* Current mntfs */
  mntfs *mf_retry = 0;		/* First mntfs which needed retrying */
  int this_error = -1;		/* Per-mount error */
  int hard_error = -1;

  /*
   * Try to mount each location.
   * At the end:
   * hard_error == 0 indicates something was mounted.
   * hard_error > 0 indicates everything failed with a hard error
   * hard_error < 0 indicates nothing could be mounted now
   */
  for (; this_error && *cp->mf; cp->mf++) {
    am_ops *p;
    int retry;

    mf = mp->am_mnt = *cp->mf;
    p = mf->mf_ops;

    if (hard_error < 0)
      hard_error = this_error;

    if (mf->mf_fo->fs_mtab) {
      plog(XLOG_MAP, "Trying mount of %s on %s fstype %s mount_type %s",
	   mf->mf_fo->fs_mtab, mf->mf_fo->opt_fs,
	   p->fs_type, mf->mf_fo->opt_mount_type);
    }

    this_error = 0;
    retry = TRUE;

    if (mp->am_link) {
      XFREE(mp->am_link);
      mp->am_link = 0;
    }
    if (mf->mf_fo->opt_sublink)
      mp->am_link = strdup(mf->mf_fo->opt_sublink);

    if (mf->mf_error > 0) {
      this_error = mf->mf_error;
    } else if (mf->mf_flags & (MFF_MOUNTING | MFF_UNMOUNTING)) {
      /*
       * Still mounting - retry later
       */
      dlog("Duplicate pending mount fstype %s", p->fs_type);
      this_error = -1;
    } else if (FSRV_ISDOWN(mf->mf_server)) {
      /*
       * Would just mount from the same place
       * as a hung mount - so give up
       */
      dlog("%s is already hung - giving up", mf->mf_mount);
      mp_error = EWOULDBLOCK;
      retry = FALSE;
      this_error = -1;
    } else if (mf->mf_flags & MFF_MOUNTED) {
      dlog("duplicate mount of \"%s\" ...", mf->mf_info);

      /*
       * Just call am_mounted()
       */
      am_mounted(mp);

      this_error = 0;
      break;
    }

    /*
     * Will usually need to play around with the mount nodes
     * file attribute structure.  This must be done here.
     * Try and get things initialized, even if the fileserver
     * is not known to be up.  In the common case this will
     * progress things faster.
     */
    if (!this_error) {
      /*
       * Fill in attribute fields.
       */
      if (mf->mf_fsflags & FS_DIRECTORY)
	mk_fattr(mp, NFDIR);
      else
	mk_fattr(mp, NFLNK);

      if (p->fs_init)
	this_error = (*p->fs_init) (mf);
    }

    /*
     * Make sure the fileserver is UP before doing any more work
     */
    if (!FSRV_ISUP(mf->mf_server)) {
      dlog("waiting for server %s to become available", mf->mf_server->fs_host);
      this_error = -1;
    }

    if (!this_error && mf->mf_fo->opt_delay) {
      /*
       * If there is a delay timer on the mount
       * then don't try to mount if the timer
       * has not expired.
       */
      int i = atoi(mf->mf_fo->opt_delay);
      if (i > 0 && clocktime() < (cp->start + i)) {
	dlog("Mount of %s delayed by %lds", mf->mf_mount, (long) (i - clocktime() + cp->start));
	this_error = -1;
      }
    }

    if (this_error < 0 && retry) {
      if (!mf_retry)
	mf_retry = dup_mntfs(mf);
      cp->retry = TRUE;
      dlog("will retry ...\n");
      break;
    }

    if (!this_error) {
#ifdef HAVE_FS_AUTOFS
      if (mf->mf_flags & MFF_AUTOFS) {
	mf->mf_autofs_fh = autofs_get_fh(mp);
      }
#endif /* HAVE_FS_AUTOFS */

      if (mf->mf_fsflags & FS_MBACKGROUND) {
	mf->mf_flags |= MFF_MOUNTING;	/* XXX */
	dlog("backgrounding mount of \"%s\"", mf->mf_mount);
	if (cp->callout) {
	  untimeout(cp->callout);
	  cp->callout = 0;
	}

	/* actually run the task, backgrounding as necessary */
	run_task(try_mount, (voidp) mp, amfs_auto_cont, (voidp) cp);

	if (mf_retry)
	  free_mntfs(mf_retry);
	return -1;
      } else {
	dlog("foreground mount of \"%s\" ...", mf->mf_info);
	this_error = try_mount((voidp) mp);
	if (this_error < 0) {
	  if (!mf_retry)
	    mf_retry = dup_mntfs(mf);
	  cp->retry = TRUE;
	}
      }
    }
  }

  if (this_error >= 0) {
    if (this_error > 0) {
      amd_stats.d_merr++;
      if (mf != mf_retry) {
	mf->mf_error = this_error;
	mf->mf_flags |= MFF_ERROR;
      }
    }

    /*
     * Wakeup anything waiting for this mount
     */
    wakeup((voidp) mf);
  }


  if (this_error) {
    if (cp->retry) {
      free_mntfs(mf);
      mf = mp->am_mnt = mf_retry;
      /*
       * Not retrying again (so far)
       */
      cp->retry = FALSE;
#if 0
      /*
       * Start at the beginning.
       * Rewind the location vector and
       * reset the default options.
       *
       * DISABLED.
       */
      dlog("(skipping rewind)\n");
#endif
      /*
       * Arrange that amfs_auto_bgmount is called
       * after anything else happens.
       */
      dlog("Arranging to retry mount of %s", mp->am_path);
      sched_task(amfs_auto_retry, (voidp) cp, (voidp) mf);
      if (cp->callout)
	untimeout(cp->callout);
      cp->callout = timeout(RETRY_INTERVAL, wakeup, (voidp) mf);

      mp->am_ttl = clocktime() + RETRY_INTERVAL;

      /*
       * Not done yet - so don't return anything
       */
      return -1;
    }
#ifdef HAVE_FS_AUTOFS
    else {
      if (mp->am_flags & AMF_AUTOFS)
	autofs_mount_failed(mp);
    }
#endif /* HAVE_FS_AUTOFS */
  }

  if (hard_error < 0 || this_error == 0)
    hard_error = this_error;

  /*
   * Discard handle on duff filesystem.
   * This should never happen since it
   * should be caught by the case above.
   */
  if (mf_retry) {
    if (hard_error)
      plog(XLOG_ERROR, "discarding a retry mntfs for %s", mf_retry->mf_mount);
    free_mntfs(mf_retry);
  }

  /*
   * If we get here, then either the mount succeeded or
   * there is no more mount information available.
   */
  if (hard_error < 0 && mp_error)
    hard_error = mp->am_error = mp_error;
  if (hard_error > 0) {
    /*
     * Set a small(ish) timeout on an error node if
     * the error was not a time out.
     */
    switch (hard_error) {
    case ETIMEDOUT:
    case EWOULDBLOCK:
      mp->am_timeo = 17;
      break;
    default:
      mp->am_timeo = 5;
      break;
    }
    new_ttl(mp);
  }

  /*
   * Make sure that the error value in the mntfs has a
   * reasonable value.
   */
  if (mf->mf_error < 0) {
    mf->mf_error = hard_error;
    if (hard_error)
      mf->mf_flags |= MFF_ERROR;
  }

  /*
   * In any case we don't need the continuation any more
   */
  free_continuation(cp);

  return hard_error;
}


static char *
amfs_parse_defaults(am_node *mp, mntfs *mf, char *def_opts)
{
  char *dflts;
  char *dfl;
  char **rvec;

  dlog("searching for /defaults entry");
  if (mapc_search((mnt_map *) mf->mf_private, "/defaults", &dflts) == 0) {
    dlog("/defaults gave %s", dflts);
    if (*dflts == '-')
      dfl = dflts + 1;
    else
      dfl = dflts;

    /*
     * Chop the defaults up
     */
    rvec = strsplit(dfl, ' ', '\"');

    if (gopt.flags & CFM_SELECTORS_IN_DEFAULTS) {
      /*
       * Pick whichever first entry matched the list of selectors.
       * Strip the selectors from the string, and assign to dfl the
       * rest of the string.
       */
      if (rvec) {
	am_opts ap;
	am_ops *pt;
	char **sp = rvec;
	while (*sp) {		/* loop until you find something, if any */
	  memset((char *) &ap, 0, sizeof(am_opts));
	  /*
	   * This next routine cause many spurious "expansion of ... is"
	   * messages, which are ignored, b/c all we need out of this
	   * routine is to match selectors.  These spurious messages may
	   * be wrong, esp. if they try to expand ${key} b/c it will
	   * get expanded to "/defaults"
	   */
	  pt = ops_match(&ap, *sp, "", mp->am_path, "/defaults",
			 mp->am_parent->am_mnt->mf_info);
	  free_opts(&ap);	/* don't leak */
	  if (pt == &amfs_error_ops) {
	    plog(XLOG_MAP, "did not match defaults for \"%s\"", *sp);
	  } else {
	    dfl = strip_selectors(*sp, "/defaults");
	    plog(XLOG_MAP, "matched default selectors \"%s\"", dfl);
	    break;
	  }
	  ++sp;
	}
      }
    } else {			/* not selectors_in_defaults */
      /*
       * Extract first value
       */
      dfl = rvec[0];
    }

    /*
     * If there were any values at all...
     */
    if (dfl) {
      /*
       * Log error if there were other values
       */
      if (!(gopt.flags & CFM_SELECTORS_IN_DEFAULTS) && rvec[1]) {
	dlog("/defaults chopped into %s", dfl);
	plog(XLOG_USER, "More than a single value for /defaults in %s", mf->mf_info);
      }

      /*
       * Prepend to existing defaults if they exist,
       * otherwise just use these defaults.
       */
      if (*def_opts && *dfl) {
	char *nopts = (char *) xmalloc(strlen(def_opts) + strlen(dfl) + 2);
	sprintf(nopts, "%s;%s", dfl, def_opts);
	XFREE(def_opts);
	def_opts = nopts;
      } else if (*dfl) {
	def_opts = strealloc(def_opts, dfl);
      }
    }
    XFREE(dflts);
    /*
     * Don't need info vector any more
     */
    XFREE(rvec);
  }

  return def_opts;
}



am_node *
amfs_auto_lookup_node(am_node *mp, char *fname, int *error_return)
{
  am_node *new_mp;
  int error = 0;		/* Error so far */
  int in_progress = 0;		/* # of (un)mount in progress */
  mntfs *mf;
  char *expanded_fname = 0;

  dlog("in amfs_auto_lookup_node");

  /*
   * If the server is shutting down
   * then don't return information
   * about the mount point.
   */
  if (amd_state == Finishing) {
    if (mp->am_mnt == 0 || mp->am_mnt->mf_fsflags & FS_DIRECT) {
      dlog("%s mount ignored - going down", fname);
    } else {
      dlog("%s/%s mount ignored - going down", mp->am_path, fname);
    }
    ereturn(ENOENT);
  }

  /*
   * Handle special case of "." and ".."
   */
  if (fname[0] == '.') {
    if (fname[1] == '\0')
      return mp;		/* "." is the current node */
    if (fname[1] == '.' && fname[2] == '\0') {
      if (mp->am_parent) {
	dlog(".. in %s gives %s", mp->am_path, mp->am_parent->am_path);
	return mp->am_parent;	/* ".." is the parent node */
      }
      ereturn(ESTALE);
    }
  }

  /*
   * Check for valid key name.
   * If it is invalid then pretend it doesn't exist.
   */
  if (!valid_key(fname)) {
    plog(XLOG_WARNING, "Key \"%s\" contains a disallowed character", fname);
    ereturn(ENOENT);
  }

  /*
   * Expand key name.
   * expanded_fname is now a private copy.
   */
  expanded_fname = expand_selectors(fname);

  /*
   * Search children of this node
   */
  for (new_mp = mp->am_child; new_mp; new_mp = new_mp->am_osib) {
    if (FSTREQ(new_mp->am_name, expanded_fname)) {
      if (new_mp->am_error) {
	error = new_mp->am_error;
	continue;
      }
      /*
       * If the error code is undefined then it must be
       * in progress.
       */
      mf = new_mp->am_mnt;
      if (mf->mf_error < 0)
	goto in_progrss;

      /*
       * Check for a hung node
       */
      if (FSRV_ISDOWN(mf->mf_server)) {
	dlog("server hung");
	error = new_mp->am_error;
	continue;
      }
      /*
       * If there was a previous error with this node
       * then return that error code.
       */
      if (mf->mf_flags & MFF_ERROR) {
	error = mf->mf_error;
	continue;
      }
      if (!(mf->mf_flags & MFF_MOUNTED) || (mf->mf_flags & MFF_UNMOUNTING)) {
      in_progrss:
	/*
	 * If the fs is not mounted or it is unmounting then there
	 * is a background (un)mount in progress.  In this case
	 * we just drop the RPC request (return nil) and
	 * wait for a retry, by which time the (un)mount may
	 * have completed.
	 */
	dlog("ignoring mount of %s in %s -- flags (%x) in progress",
	     expanded_fname, mf->mf_mount, mf->mf_flags);
	in_progress++;
	new_mp->am_flags |= AMF_REMOUNT;
	continue;
      }

      /*
       * Otherwise we have a hit: return the current mount point.
       */
      dlog("matched %s in %s", expanded_fname, new_mp->am_path);
      XFREE(expanded_fname);
      return new_mp;
    }
  }

  if (in_progress) {
    dlog("Waiting while %d mount(s) in progress", in_progress);
    XFREE(expanded_fname);
    ereturn(-1);
  }

  /*
   * If an error occurred then return it.
   */
  if (error) {
    dlog("Returning error: %s", strerror(error));
    XFREE(expanded_fname);
    ereturn(error);
  }

  /*
   * If the server is going down then just return,
   * don't try to mount any more file systems
   */
  if ((int) amd_state >= (int) Finishing) {
    dlog("not found - server going down anyway");
    ereturn(ENOENT);
  }

  /*
   * Allocate a new map
   */
  new_mp = get_ap_child(mp, expanded_fname);
  XFREE(expanded_fname);
  if (new_mp == 0)
    ereturn(ENOSPC);

  *error_return = -1;
  return new_mp;
}



mntfs **
amfs_auto_lookup_mntfs(am_node *new_mp, int *error_return)
{
  am_node *mp;
  char *info;			/* Mount info - where to get the file system */
  char **ivec, **cur_ivec;	/* Split version of info */
  int num_ivecs;
  char *def_opts;	       	/* Automount options */
  int error = 0;		/* Error so far */
  char path_name[MAXPATHLEN];	/* General path name buffer */
  char *pfname;			/* Path for database lookup */
  mntfs *mf, *new_mf, **mf_array;
  int i;

  dlog("in amfs_auto_lookup_mntfs");

  mp = new_mp->am_parent;

  /*
   * If we get here then this is a reference to an,
   * as yet, unknown name so we need to search the mount
   * map for it.
   */
  if (mp->am_pref) {
    sprintf(path_name, "%s%s", mp->am_pref, new_mp->am_name);
    pfname = path_name;
  } else {
    pfname = new_mp->am_name;
  }

  mf = mp->am_mnt;

  dlog("will search map info in %s to find %s", mf->mf_info, pfname);
  /*
   * Consult the oracle for some mount information.
   * info is malloc'ed and belongs to this routine.
   * It ends up being free'd in free_continuation().
   *
   * Note that this may return -1 indicating that information
   * is not yet available.
   */
  error = mapc_search((mnt_map *) mf->mf_private, pfname, &info);
  if (error) {
    if (error > 0)
      plog(XLOG_MAP, "No map entry for %s", pfname);
    else
      plog(XLOG_MAP, "Waiting on map entry for %s", pfname);
    ereturn(error);
  }
  dlog("mount info is %s", info);

  /*
   * Split info into an argument vector.
   * The vector is malloc'ed and belongs to
   * this routine.  It is free'd further down.
   *
   * Note: the vector pointers point into info, so don't free it!
   */
  ivec = strsplit(info, ' ', '\"');

  /*
   * Default error code...
   */
  error = ENOENT;

  if (mf->mf_auto)
    def_opts = mf->mf_auto;
  else
    def_opts = "";

  def_opts = amfs_parse_defaults(mp, mf, strdup(def_opts));

  /* first build our defaults */
  num_ivecs = 0;
  for (cur_ivec = ivec; *cur_ivec; cur_ivec++) {
    if (**cur_ivec == '-') {
      /*
       * Pick up new defaults
       */
      def_opts = str3cat(def_opts, def_opts, ";", *cur_ivec + 1);
      dlog("Setting def_opts to \"%s\"", def_opts);
      continue;
    } else
      num_ivecs++;
  }

  mf_array = calloc(num_ivecs + 1, sizeof(mntfs *));

  /* construct the array of struct mntfs for this mount point */
  for (i = 0, cur_ivec = ivec; *cur_ivec; cur_ivec++) {
    am_ops *p;
    am_opts *fs_opts;
    char *link_dir;

    /* we've dealt with the defaults already */
    if (**cur_ivec == '-')
      continue;

    /*
     * If a mount has been attempted, and we find
     * a cut then don't try any more locations.
     */
    if (STREQ(*cur_ivec, "/") || STREQ(*cur_ivec, "||")) {
      if (i > 0) {
	dlog("Cut: not trying any more locations for %s", mp->am_path);
	break;
      }
      continue;
    }

    /* match the operators */
    fs_opts = calloc(1, sizeof(am_opts));
    p = ops_match(fs_opts, *cur_ivec, def_opts, new_mp->am_path,
		  pfname, mf->mf_info);
#ifdef HAVE_FS_AUTOFS
    if (new_mp->am_flags & AMF_AUTOFS) {
      /* ignore user-provided fs if we're using autofs */
      if (fs_opts->opt_sublink) {
	if (fs_opts->opt_sublink[0] == '/') {
	  XFREE(fs_opts->opt_fs);
	  fs_opts->opt_fs = strdup(fs_opts->opt_rfs);
	}
      } else {
	XFREE(fs_opts->opt_fs);
	fs_opts->opt_fs = strdup(new_mp->am_path);
      }
    }
#endif /* HAVE_FS_AUTOFS */

    /*
     * Find or allocate a filesystem for this node.
     */
    new_mf = find_mntfs(p, fs_opts,
			fs_opts->opt_fs,
			fs_opts->fs_mtab,
			def_opts,
			fs_opts->opt_opts,
			fs_opts->opt_remopts);

    /*
     * See whether this is a real filesystem
     */
    p = new_mf->mf_ops;
    if (p == &amfs_error_ops) {
      plog(XLOG_MAP, "Map entry %s for %s did not match", *cur_ivec, new_mp->am_path);
      free_mntfs(new_mf);
      continue;
    }

    dlog("Got a hit with %s", p->fs_type);

#ifdef HAVE_FS_AUTOFS
    if (new_mp->am_flags & AMF_AUTOFS)
      new_mf->mf_fsflags = new_mf->mf_ops->autofs_fs_flags;
    if (new_mf->mf_fsflags & FS_AUTOFS &&
	mf->mf_flags & MFF_AUTOFS)
      new_mf->mf_flags |= MFF_AUTOFS;
#endif /* HAVE_FS_AUTOFS */

    link_dir = new_mf->mf_fo->opt_sublink;
    if (link_dir && link_dir[0] && link_dir[0] != '/') {
      link_dir = str3cat((char *) 0,
			 new_mf->mf_fo->opt_fs, "/", link_dir);
      normalize_slash(link_dir);
      XFREE(new_mf->mf_fo->opt_sublink);
      new_mf->mf_fo->opt_sublink = link_dir;
    }

    mf_array[i++] = new_mf;
  }

  /* We're done with ivec */
  XFREE(ivec);
  XFREE(info);

  return mf_array;
}


am_node *
amfs_auto_mount_child(am_node *new_mp, int *error_return)
{
  int error;
  struct continuation *cp;	/* Continuation structure if need to mount */

  dlog("in amfs_auto_mount_child");

  *error_return = error = 0;	/* Error so far */

  /* we have an errorfs attached to the am_node, free it */
  free_mntfs(new_mp->am_mnt);
  new_mp->am_mnt = 0;

  /*
   * Construct a continuation
   */
  cp = ALLOC(struct continuation);
  cp->callout = 0;
  cp->mp = new_mp;
  cp->retry = FALSE;
  cp->start = clocktime();
  cp->mf = new_mp->am_mfarray;

  /*
   * Try and mount the file system.  If this succeeds immediately (possible
   * for a ufs file system) then return the attributes, otherwise just
   * return an error.
   */
  error = amfs_auto_bgmount(cp, error);
  reschedule_timeout_mp();
  if (!error)
    return new_mp;

  /*
   * Code for quick reply.  If current_transp is set, then it's the
   * transp that's been passed down from nfs_program_2() or from
   * autofs_program_[123]().
   * If new_mp->am_transp is not already set, set it by copying in
   * current_transp.  Once am_transp is set, nfs_quick_reply() and
   * autofs_mount_succeeded() can use it to send a reply to the
   * client that requested this mount.
   */
  if (current_transp && !new_mp->am_transp) {
    new_mp->am_transp = (SVCXPRT *) xmalloc(sizeof(SVCXPRT));
    *(new_mp->am_transp) = *current_transp;
  }
  if (error && (new_mp->am_mnt->mf_ops == &amfs_error_ops))
    new_mp->am_error = error;

  assign_error_mntfs(new_mp);

  ereturn(error);
}


/*
 * Automount interface to RPC lookup routine
 * Find the corresponding entry and return
 * the file handle for it.
 */
am_node *
amfs_auto_lookup_child(am_node *mp, char *fname, int *error_return, int op)
{
  am_node *new_mp;
  mntfs **mf_array;

  dlog("in amfs_auto_lookup_child");

  *error_return = 0;
  new_mp = amfs_auto_lookup_node(mp, fname, error_return);

  /* return if it was already mounted or if we got an error */
  if (!new_mp || *error_return >= 0)
    return new_mp;

  if (op == VLOOK_DELETE)
    /*
     * If doing a delete then don't create again!
     */
    ereturn(ENOENT);

  mf_array = amfs_auto_lookup_mntfs(new_mp, error_return);
  if (!mf_array) {
    /* don't touch *error_return, it's already set */
    new_mp->am_error = new_mp->am_mnt->mf_error = *error_return;
    free_map(new_mp);
    return 0;
  }

  /* store the array inside the am_node */
  new_mp->am_mfarray = mf_array;

  /*
   * we need to sort the mntfs's we got.
   * the order should be something like:
   * 1. link
   * 2. anything else (XXX -- any other preferences?)
   */
  return new_mp;
}


/*
 * Locate next node in sibling list which is mounted
 * and is not an error node.
 */
am_node *
next_nonerror_node(am_node *xp)
{
  mntfs *mf;

  /*
   * Bug report (7/12/89) from Rein Tollevik <rein@ifi.uio.no>
   * Fixes a race condition when mounting direct automounts.
   * Also fixes a problem when doing a readdir on a directory
   * containing hung automounts.
   */
  while (xp &&
	 (!(mf = xp->am_mnt) ||	/* No mounted filesystem */
	  mf->mf_error != 0 ||	/* There was a mntfs error */
	  xp->am_error != 0 ||	/* There was a mount error */
	  !(mf->mf_flags & MFF_MOUNTED) ||	/* The fs is not mounted */
	  (mf->mf_server->fs_flags & FSF_DOWN))	/* The fs may be down */
	 )
    xp = xp->am_osib;

  return xp;
}


/*
 * This readdir function which call a special version of it that allows
 * browsing if browsable_dirs=yes was set on the map.
 */
int
amfs_auto_readdir(am_node *mp, nfscookie cookie, nfsdirlist *dp, nfsentry *ep, int count)
{
  u_int gen = *(u_int *) cookie;
  am_node *xp;
  mntent_t mnt;

  dp->dl_eof = FALSE;		/* assume readdir not done */

  /* check if map is browsable */
  if (mp->am_mnt && mp->am_mnt->mf_mopts) {
    mnt.mnt_opts = mp->am_mnt->mf_mopts;
    if (hasmntopt(&mnt, "fullybrowsable"))
      return amfs_auto_readdir_browsable(mp, cookie, dp, ep, count, TRUE);
    if (hasmntopt(&mnt, "browsable"))
      return amfs_auto_readdir_browsable(mp, cookie, dp, ep, count, FALSE);
  }

  /* when gen is 0, we start reading from the beginning of the directory */
  if (gen == 0) {
    /*
     * In the default instance (which is used to start a search) we return
     * "." and "..".
     *
     * This assumes that the count is big enough to allow both "." and ".."
     * to be returned in a single packet.  If it isn't (which would be
     * fairly unbelievable) then tough.
     */
    dlog("amfs_auto_readdir: default search");
    /*
     * Check for enough room.  This is extremely approximate but is more
     * than enough space.  Really need 2 times:
     *      4byte fileid
     *      4byte cookie
     *      4byte name length
     *      4byte name
     * plus the dirlist structure */
    if (count < (2 * (2 * (sizeof(*ep) + sizeof("..") + 4) + sizeof(*dp))))
      return EINVAL;

    xp = next_nonerror_node(mp->am_child);
    dp->dl_entries = ep;

    /* construct "." */
    ep[0].ne_fileid = mp->am_gen;
    ep[0].ne_name = ".";
    ep[0].ne_nextentry = &ep[1];
    *(u_int *) ep[0].ne_cookie = 0;

    /* construct ".." */
    if (mp->am_parent)
      ep[1].ne_fileid = mp->am_parent->am_gen;
    else
      ep[1].ne_fileid = mp->am_gen;
    ep[1].ne_name = "..";
    ep[1].ne_nextentry = 0;
    *(u_int *) ep[1].ne_cookie = (xp ? xp->am_gen : DOT_DOT_COOKIE);

    if (!xp)
      dp->dl_eof = TRUE;	/* by default assume readdir done */

    amuDebug(D_READDIR) {
      nfsentry *ne;
      int j;
      for (j = 0, ne = ep; ne; ne = ne->ne_nextentry)
	plog(XLOG_DEBUG, "gen1 key %4d \"%s\" fi=%d ck=%d",
	     j++, ne->ne_name, ne->ne_fileid, *(u_int *)ne->ne_cookie);
    }
    return 0;
  }
  dlog("amfs_auto_readdir: real child");

  if (gen == DOT_DOT_COOKIE) {
    dlog("amfs_auto_readdir: End of readdir in %s", mp->am_path);
    dp->dl_eof = TRUE;
    dp->dl_entries = 0;
    amuDebug(D_READDIR)
      plog(XLOG_DEBUG, "end of readdir eof=TRUE, dl_entries=0\n");
    return 0;
  }

  /* non-browsable directories code */
  xp = mp->am_child;
  while (xp && xp->am_gen != gen)
    xp = xp->am_osib;

  if (xp) {
    int nbytes = count / 2;	/* conservative */
    int todo = MAX_READDIR_ENTRIES;

    dp->dl_entries = ep;
    do {
      am_node *xp_next = next_nonerror_node(xp->am_osib);

      if (xp_next) {
	*(u_int *) ep->ne_cookie = xp_next->am_gen;
      } else {
	*(u_int *) ep->ne_cookie = DOT_DOT_COOKIE;
	dp->dl_eof = TRUE;
      }

      ep->ne_fileid = xp->am_gen;
      ep->ne_name = xp->am_name;
      nbytes -= sizeof(*ep) + 1;
      if (xp->am_name)
	nbytes -= strlen(xp->am_name);

      xp = xp_next;

      if (nbytes > 0 && !dp->dl_eof && todo > 1) {
	ep->ne_nextentry = ep + 1;
	ep++;
	--todo;
      } else {
	todo = 0;
      }
    } while (todo > 0);

    ep->ne_nextentry = 0;

    amuDebug(D_READDIR) {
      nfsentry *ne;
      int j;
      for (j=0,ne=ep; ne; ne=ne->ne_nextentry)
	plog(XLOG_DEBUG, "gen2 key %4d \"%s\" fi=%d ck=%d",
	     j++, ne->ne_name, ne->ne_fileid, *(u_int *)ne->ne_cookie);
    }
    return 0;
  }
  return ESTALE;
}


/* This one is called only if map is browsable */
static int
amfs_auto_readdir_browsable(am_node *mp, nfscookie cookie, nfsdirlist *dp, nfsentry *ep, int count, int fully_browsable)
{
  u_int gen = *(u_int *) cookie;
  int chain_length, i;
  static nfsentry *te, *te_next;
  static int j;

  dp->dl_eof = FALSE;		/* assume readdir not done */

  amuDebug(D_READDIR)
    plog(XLOG_DEBUG, "amfs_auto_readdir_browsable gen=%u, count=%d",
	 gen, count);

  if (gen == 0) {
    /*
     * In the default instance (which is used to start a search) we return
     * "." and "..".
     *
     * This assumes that the count is big enough to allow both "." and ".."
     * to be returned in a single packet.  If it isn't (which would be
     * fairly unbelievable) then tough.
     */
    dlog("amfs_auto_readdir_browsable: default search");
    /*
     * Check for enough room.  This is extremely approximate but is more
     * than enough space.  Really need 2 times:
     *      4byte fileid
     *      4byte cookie
     *      4byte name length
     *      4byte name
     * plus the dirlist structure */
    if (count < (2 * (2 * (sizeof(*ep) + sizeof("..") + 4) + sizeof(*dp))))
      return EINVAL;

    /*
     * compute # of entries to send in this chain.
     * heuristics: 128 bytes per entry.
     * This is too much probably, but it seems to work better because
     * of the re-entrant nature of nfs_readdir, and esp. on systems
     * like OpenBSD 2.2.
     */
    chain_length = count / 128;

    /* reset static state counters */
    te = te_next = NULL;

    dp->dl_entries = ep;

    /* construct "." */
    ep[0].ne_fileid = mp->am_gen;
    ep[0].ne_name = ".";
    ep[0].ne_nextentry = &ep[1];
    *(u_int *) ep[0].ne_cookie = 0;

    /* construct ".." */
    if (mp->am_parent)
      ep[1].ne_fileid = mp->am_parent->am_gen;
    else
      ep[1].ne_fileid = mp->am_gen;

    ep[1].ne_name = "..";
    ep[1].ne_nextentry = 0;
    *(u_int *) ep[1].ne_cookie = DOT_DOT_COOKIE;

    /*
     * If map is browsable, call a function make_entry_chain() to construct
     * a linked list of unmounted keys, and return it.  Then link the chain
     * to the regular list.  Get the chain only once, but return
     * chunks of it each time.
     */
    te = make_entry_chain(mp, dp->dl_entries, fully_browsable);
    if (!te)
      return 0;
    amuDebug(D_READDIR) {
      nfsentry *ne;
      for (j = 0, ne = te; ne; ne = ne->ne_nextentry)
	plog(XLOG_DEBUG, "gen1 key %4d \"%s\"", j++, ne->ne_name);
    }

    /* return only "chain_length" entries */
    te_next = te;
    for (i=1; i<chain_length; ++i) {
      te_next = te_next->ne_nextentry;
      if (!te_next)
	break;
    }
    if (te_next) {
      nfsentry *te_saved = te_next->ne_nextentry;
      te_next->ne_nextentry = NULL; /* terminate "te" chain */
      te_next = te_saved;	/* save rest of "te" for next iteration */
      dp->dl_eof = FALSE;	/* tell readdir there's more */
    } else {
      dp->dl_eof = TRUE;	/* tell readdir that's it */
    }
    ep[1].ne_nextentry = te;	/* append this chunk of "te" chain */
#ifdef DEBUG
    amuDebug(D_READDIR) {
      nfsentry *ne;
      for (j = 0, ne = te; ne; ne = ne->ne_nextentry)
	plog(XLOG_DEBUG, "gen2 key %4d \"%s\"", j++, ne->ne_name);
      for (j = 0, ne = ep; ne; ne = ne->ne_nextentry)
	plog(XLOG_DEBUG, "gen2+ key %4d \"%s\" fi=%d ck=%d",
	     j++, ne->ne_name, ne->ne_fileid, *(u_int *)ne->ne_cookie);
      plog(XLOG_DEBUG, "EOF is %d", dp->dl_eof);
    }
#endif /* DEBUG */
    return 0;
  } /* end of "if (gen == 0)" statement */

  dlog("amfs_auto_readdir_browsable: real child");

  if (gen == DOT_DOT_COOKIE) {
    dlog("amfs_auto_readdir_browsable: End of readdir in %s", mp->am_path);
    dp->dl_eof = TRUE;
    dp->dl_entries = 0;
    return 0;
  }

  /*
   * If browsable directories, then continue serving readdir() with another
   * chunk of entries, starting from where we left off (when gen was equal
   * to 0).  Once again, assume last chunk served to readdir.
   */
  dp->dl_eof = TRUE;
  dp->dl_entries = ep;

  te = te_next;			/* reset 'te' from last saved te_next */
  if (!te) {			/* another indicator of end of readdir */
    dp->dl_entries = 0;
    return 0;
  }
  /*
   * compute # of entries to send in this chain.
   * heuristics: 128 bytes per entry.
   */
  chain_length = count / 128;

  /* return only "chain_length" entries */
  for (i = 1; i < chain_length; ++i) {
    te_next = te_next->ne_nextentry;
    if (!te_next)
      break;
  }
  if (te_next) {
    nfsentry *te_saved = te_next->ne_nextentry;
    te_next->ne_nextentry = NULL; /* terminate "te" chain */
    te_next = te_saved;		/* save rest of "te" for next iteration */
    dp->dl_eof = FALSE;		/* tell readdir there's more */
  }
  ep = te;			/* send next chunk of "te" chain */
  dp->dl_entries = ep;
#ifdef DEBUG
  amuDebug(D_READDIR) {
    nfsentry *ne;
    plog(XLOG_DEBUG, "dl_entries=0x%lx, te_next=0x%lx, dl_eof=%d",
	 (u_long) dp->dl_entries,
	 (u_long) te_next,
	 dp->dl_eof);
    for (ne = te; ne; ne = ne->ne_nextentry)
      plog(XLOG_DEBUG, "gen3 key %4d \"%s\"", j++, ne->ne_name);
  }
#endif /* DEBUG */
  return 0;
}
