/* Fstab filesystem frobbing

   Copyright (C) 1996, 1997, 1998, 1999 Free Software Foundation, Inc.

   Written by Miles Bader <miles@gnu.ai.mit.edu>

   This file is part of the GNU Hurd.

   The GNU Hurd 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; either version 2, or (at
   your option) any later version.

   The GNU Hurd is distributed in the hope that it will be useful, but
   WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
   General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program; if not, write to the Free Software
   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.  */

#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
#include <errno.h>
#include <fcntl.h>
#include <error.h>
#include <argz.h>
#include <argp.h>
#include <fnmatch.h>

#include <hurd/fsys.h>

#include "fstab.h"

extern error_t fsys_set_readonly (fsys_t fsys, int readonly);
extern error_t fsys_get_readonly (fsys_t fsys, int *readonly);
extern error_t fsys_update (fsys_t fsys);

extern file_t file_name_lookup_carefully (const char *file,
					  int flags, mode_t mode);

/* Return a new fstab in FSTAB.  */
error_t
fstab_create (struct fstypes *types, struct fstab **fstab)
{
  struct fstab *new = malloc (sizeof (struct fstab));
  if (new)
    {
      new->entries = 0;
      new->types = types;
      *fstab = new;
      return 0;
    }
  else
    return ENOMEM;
}

/* Free FSTAB and all of its entries.  */
void
fstab_free (struct fstab *fstab)
{
  while (fstab->entries)
    fs_free (fstab->entries);
  free (fstab);
}

/* Return a new fstypes structure in TYPES.  SEARCH_FMTS is copied.  */
error_t
fstypes_create (const char *search_fmts, size_t search_fmts_len,
		struct fstypes **types)
{
  struct fstypes *new = malloc (sizeof (struct fstypes));
  if (new)
    {
      new->entries = 0;
      new->program_search_fmts = malloc (search_fmts_len);
      new->program_search_fmts_len = search_fmts_len;
      if (! new->program_search_fmts)
	{
	  free (types);
	  return ENOMEM;
	}
      bcopy (search_fmts, new->program_search_fmts, search_fmts_len);
      *types = new;
      return 0;
    }
  else
    return ENOMEM;
}

/* Return an fstype entry in TYPES called NAME, in FSTYPE.  If there is no
   existing entry, an attempt to find a fsck program with the given type,
   using the alternatives in the FSCK_SEARCH_FMTS field in TYPES.  If
   one is found, it is added to TYPES, otherwise an new entry is created
   with a NULL PROGRAM field.  */
error_t
fstypes_get (struct fstypes *types, const char *name, struct fstype **fstype)
{
  char *fmts, *fmt;
  size_t fmts_len;
  struct fstype *type;
  char *program = 0;

  for (type = types->entries; type; type = type->next)
    if (strcasecmp (type->name, name) == 0)
      {
	*fstype = type;
	return 0;
      }

  /* No existing entry, make a new one.  */

  fmts = types->program_search_fmts;
  fmts_len = types->program_search_fmts_len;

  for (fmt = fmts; fmt; fmt = argz_next (fmts, fmts_len, fmt))
    {
      int fd;

      asprintf (&program, fmt, name);
      fd = open (program, O_EXEC);
      if (fd < 0)
	{
	  free (program);	/* Failed.  */
	  if (errno != ENOENT && errno != EACCES)
	    /* The program's there but something went wrong; fail.  */
	    return errno;
	}
      else
	/* We can open for exec, but check the stat info too (e.g. root can
	   open everything).  */
	{
	  struct stat stat;
	  int rv = fstat (fd, &stat);

	  close (fd);

	  if (rv < 0)
	    {
	      free (program);
	      return errno;
	    }

	  if (stat.st_mode & S_IXUSR)
	    /* Yup execute bit is set.  This must be a program...  */
	    break;

	  free (program);
	}

      program = 0;
    }

  type = malloc (sizeof (struct fstype));
  if (! type)
    {
      free (program);
      return ENOMEM;
    }

  type->name = strdup (name);
  if (type->name == 0)
    {
      free (type);
      return ENOMEM;
    }
  type->program = program;
  type->next = types->entries;
  types->entries = type;

  *fstype = type;

  return 0;
}

#if 0
/* XXX nice idea, but not that useful since scanf's %s always eats all
   non-ws, and it seems a bit overkill to convert it to a .+ regexp match */
error_t
fstypes_find_program (struct fstypes *types, const char *program,
		      struct fstype **fstype)
{
  char *fmts, *fmt;
  size_t fmts_len;
  struct fstype *type;
  char *typename;

  /* First see if a known type matches this program.  */
  for (type = types->entries; type; type = type->next)
    if (type->program && !strcmp (type->program, program))
      {
	*fstype = type;
	return 0;
      }

  /* No existing entry, see if we can make a new one.  */

  typename = alloca (strlen (program) + 1);

  fmts = types->program_search_fmts;
  fmts_len = types->program_search_fmts_len;
  for (fmt = fmts; fmt; fmt = argz_next (fmts, fmts_len, fmt))
    /* XXX this only works for trailing %s */
    if (sscanf (program, fmt, typename) == 1)
      {
	/* This format matches the program and yields the type name.
	   Create a new entry for this type.  */

	type = malloc (sizeof (struct fstype));
	if (! type)
	  return ENOMEM;
	type->name = strdup (typename);
	if (type->name == 0)
	  {
	    free (type);
	    return ENOMEM;
	  }
	type->program = strdup (program);
	if (type->program == 0)
	  {
	    free (type->name);
	    free (type);
	    return ENOMEM;
	  }
	type->next = types->entries;
	types->entries = type;

	*fstype = type;
	return 0;
      }

  /* We could find no program search format that could have yielded this
     program name.  */
  *fstype = 0;
  return 0;
}
#endif

/* Copy MNTENT into FS, copying component strings as well.  */
error_t
fs_set_mntent (struct fs *fs, const struct mntent *mntent)
{
  char *end;
  size_t needed = 0;

  if (fs->storage)
    free (fs->storage);

  /* Allocate space for all string mntent fields in FS.  */
#define COUNT(field) if (mntent->field) needed += strlen (mntent->field) + 1;
  COUNT (mnt_fsname);
  COUNT (mnt_dir);
  COUNT (mnt_type);
  COUNT (mnt_opts);
#undef COUNT

  fs->storage = malloc (needed);
  if (! fs->storage)
    return ENOMEM;

  if (!fs->mntent.mnt_dir || !mntent->mnt_dir
      || strcmp (fs->mntent.mnt_dir, mntent->mnt_dir) != 0)
    {
      fs->mounted = fs->readonly = -1;
      if (fs->fsys != MACH_PORT_NULL)
	mach_port_deallocate (mach_task_self (), fs->fsys);
      fs->fsys = MACH_PORT_NULL;
    }

  /* Copy MNTENT into FS; string-valued fields will be fixed up next.  */
  fs->mntent = *mntent;

  /* Copy each mntent field from MNTENT into FS's version.  */
  end = fs->storage;
#define STORE(field) \
  if (mntent->field)				\
    {						\
      fs->mntent.field = end;			\
      end = stpcpy (end, mntent->field) + 1;	\
    }						\
  else						\
    fs->mntent.field = 0;
  STORE (mnt_fsname);
  STORE (mnt_dir);
  STORE (mnt_type);
  STORE (mnt_opts);
#undef STORE

  if (fs->type
      && (!mntent->mnt_type
	  || strcasecmp (fs->type->name, mntent->mnt_type) != 0))
    fs->type = 0;		/* Type is different.  */

  return 0;
}

/* Returns an fstype for FS in TYPE, trying to fill in FS's type field if
   necessary.  */
error_t
fs_type (struct fs *fs, struct fstype **type)
{
  error_t err = 0;
  if (! fs->type)
    err = fstypes_get (fs->fstab->types, fs->mntent.mnt_type, &fs->type);
  if (! err)
    *type = fs->type;
  return err;
}

/* Looks to see if FS is currently mounted, being very careful to avoid
   mounting anything that's not already, and fills in the fsys & mounted
   fields in FS.  */
static error_t
_fs_check_mounted (struct fs *fs)
{
  error_t err = 0;

  if (fs->mounted < 0)
    /* The mounted field in FS is -1 if we're not sure.  */
    {
      if (fs->fsys != MACH_PORT_NULL)
	mach_port_deallocate (mach_task_self (), fs->fsys);

      if (strcmp (fs->mntent.mnt_dir, "/") == 0)
	/* The root is always mounted.  Get its control port.  */
	{
	  file_t root = getcrdir ();
	  if (root == MACH_PORT_NULL)
	    err = errno;
	  else
	    {
	      err = file_getcontrol (root, &fs->fsys);
	      mach_port_deallocate (mach_task_self (), root);
	    }
	}
      else
	{
	  file_t mount_point =
	    file_name_lookup_carefully (fs->mntent.mnt_dir, O_NOTRANS, 0);

	  if (mount_point != MACH_PORT_NULL)
	    /* The node exists.  Is it the root of an active translator?
	       [Note that it could be a different translator than the one in
	       the mntent, but oh well, nothing we can do about that.]  */
	    {
	      err = file_get_translator_cntl (mount_point, &fs->fsys);
	      if (err ==  EINVAL || err == EOPNOTSUPP || err == ENXIO)
		/* Either the mount point doesn't exist, or wasn't mounted.  */
		{
		  fs->fsys = MACH_PORT_NULL;
		  err = 0;
		}
	    }
	  else if (errno == ENXIO)
	    /* Ran into an inactive passive translator.  FS can't be mounted.  */
	    {
	      fs->fsys = MACH_PORT_NULL;
	      err = 0;
	    }
	}

      if (! err)
	fs->mounted = (fs->fsys != MACH_PORT_NULL);
    }

  return err;
}

/* Looks to see if FS is currently mounted, being very careful to avoid
   mounting anything that's not already, and returns the control port for the
   mounted filesystem (MACH_PORT_NULL if not mounted).  */
error_t
fs_fsys (struct fs *fs, fsys_t *fsys)
{
  error_t err = _fs_check_mounted (fs);
  if (!err && fsys)
    *fsys = fs->fsys;
  return err;
}

/* Looks to see if FS is currently mounted, being very careful to avoid
   mounting anything that's not already, and returns the boolean MOUNTED.  */
error_t
fs_mounted (struct fs *fs, int *mounted)
{
  error_t err = _fs_check_mounted (fs);
  if (!err && mounted)
    *mounted = fs->mounted;
  return err;
}

/* Looks to see if FS is currently mounted readonly, being very careful to
   avoid mounting anything that's not already, and returns the boolean
   READONLY.  If FS isn't mounted at all, READONLY is set to 1 (it's not
   going to write anything!).  */
error_t
fs_readonly (struct fs *fs, int *readonly)
{
  error_t err = 0;

  if (fs->readonly < 0)
    /* Unknown. */
    {
      fsys_t fsys;

      err = fs_fsys (fs, &fsys);
      if (! err)
	{
	  if (fsys == MACH_PORT_NULL)
	    fs->readonly = 1;
	  else
	    err = fsys_get_readonly (fsys, &fs->readonly);
	}
    }

  if (!err && readonly)
    *readonly = fs->readonly;

  return err;
}

/* If FS is currently mounted writable, try to make it readonly.  XXX If FS
   is not mounted at all, then nothing is done.   */
error_t
fs_set_readonly (struct fs *fs, int readonly)
{
  int currently_readonly;
  error_t err = fs_readonly (fs, &currently_readonly);

  readonly = !!readonly;

  if (!err && readonly != currently_readonly)
    /* We have to try and change the readonly state.  */
    {
      fsys_t fsys;
      err = fs_fsys (fs, &fsys);
      if (!err && fsys != MACH_PORT_NULL) /* XXX What to do if not mounted? */
	err = fsys_set_readonly (fsys, readonly);
      if (! err)
	fs->readonly = readonly;
    }

  return err;
}

/* If FS is currently mounted tell it to remount the device.  XXX If FS is
   not mounted at all, then nothing is done.  */
error_t
fs_remount (struct fs *fs)
{
  fsys_t fsys;
  error_t err = fs_fsys (fs, &fsys);
  if (!err && fsys != MACH_PORT_NULL) /* XXX What to do if not mounted? */
    err = fsys_update (fsys);
  return err;
}

/* Returns the FS entry in FSTAB with the device field NAME (there can only
   be one such entry).  */
inline struct fs *
fstab_find_device (const struct fstab *fstab, const char *name)
{
  struct fs *fs;
  for (fs = fstab->entries; fs; fs = fs->next)
    if (strcmp (fs->mntent.mnt_fsname, name) == 0)
      return fs;
  return 0;
}

/* Returns the FS entry in FSTAB with the mount point NAME (there can only
   be one such entry).  */
inline struct fs *
fstab_find_mount (const struct fstab *fstab, const char *name)
{
  struct fs *fs;

  /* Don't count "none" or "-" as matching any other mount point.
     It is canonical to use "none" for swap partitions, and multiple
     such do not in fact conflict with each other.  Likewise, the
     special device name "ignore" is used for things that should not
     be processed automatically.  */
  if (!strcmp (name, "-")
      || !strcmp (name, "none")
      || !strcmp (name, "ignore"))
    return 0;

  for (fs = fstab->entries; fs; fs = fs->next)
    if (strcmp (fs->mntent.mnt_dir, name) == 0)
      return fs;
  return 0;
}

/* Returns the FS entry in FSTAB with the device or mount point NAME (there
   can only be one such entry).  */
inline struct fs *
fstab_find (const struct fstab *fstab, const char *name)
{
  return fstab_find_device (fstab, name) ?: fstab_find_mount (fstab, name);
}

/* Cons FS onto the beginning of FSTAB's entry list.  */
static void
_fstab_add (struct fstab *fstab, struct fs *fs)
{
  fs->fstab = fstab;
  fs->next = fstab->entries;
  fs->self = &fstab->entries;
  if (fstab->entries)
    fstab->entries->self = &fs->next;
  fstab->entries = fs;
}

/* Destroy FS, removing it from its containing FSTAB.  */
void
fs_free (struct fs *fs)
{
  *fs->self = fs->next;		/* unlink from chain */
  if (fs->storage)
    free (fs->storage);
  if (fs->fsys != MACH_PORT_NULL)
    mach_port_deallocate (mach_task_self (), fs->fsys);
  free (fs);
}

/* Add an entry for MNTENT to FSTAB, removing any existing entries that
   conflict (in either the device or mount point).  If RESULT is non-zero, the
   new entry is returne in it.  */
error_t
fstab_add_mntent (struct fstab *const fstab, const struct mntent *mntent,
		  struct fs **result)
{
  int new = 0;			/* True if we didn't overwrite an old entry.  */
  error_t err = 0;
  struct fs *fs = fstab_find_device (fstab, mntent->mnt_fsname);
  struct fs *mounted_fs = fstab_find_mount (fstab, mntent->mnt_dir);

  if (! fs)
    /* No old entry with the same device; see if there's one with the same
       mount point.  */
    {
      fs = mounted_fs;
      mounted_fs = 0;
    }

  if (! fs)
    /* No old entry, make a new one.  */
    {
      fs = malloc (sizeof (struct fs));
      if (fs)
	{
	  bzero (fs, sizeof (struct fs));
	  fs->mounted = fs->readonly = -1;
	  fs->fsys = MACH_PORT_NULL;
	  new = 1;
	}
      else
	err = ENOMEM;
    }

  if (! err)
    /* Try and fill in FS's mntent.  */
    err = fs_set_mntent (fs, mntent);

  if (new)
    {
      if (! err)
	_fstab_add (fstab, fs);
      else if (fs)
	free (fs);
    }

  if (!err && mounted_fs)
    /* Get rid of the conflicting entry MOUNTED_FS.  */
    fs_free (mounted_fs);

  if (!err && result)
    *result = fs;

  return err;
}

/* Copy the entry FS (which should belong to another fstab than DST) into
   DST.  If DST & SRC have different TYPES fields, EINVAL is returned.  If
   COPY is non-zero, the copy is returned in it.  */
error_t
fstab_add_fs (struct fstab *dst, const struct fs *fs, struct fs **copy)
{
  error_t err;
  struct fs *new;
  struct fstab *src = fs->fstab;

  if (dst->types != src->types)
    return EINVAL;

  err = fstab_add_mntent (dst, &fs->mntent, &new);
  if (err)
    return err;

  new->type = fs->type;

  if (copy)
    *copy = new;

  return 0;
}

/* Merge SRC into DST, as if by calling fstab_add_fs on DST with every
   entry in SRC, and then deallocating SRC.  If DST & SRC have different
   TYPES fields, EINVAL is returned.  */
error_t
fstab_merge (struct fstab *dst, struct fstab *src)
{
  struct fs *fs;

  if (dst->types != src->types)
    return EINVAL;

  /* Remove entries in DST which conflict with those in SRC.  */
  for (fs = src->entries; fs; fs = fs->next)
    {
      struct fs *old_fs;

      old_fs = fstab_find_device (dst, fs->mntent.mnt_fsname);
      if (old_fs)
	fs_free (old_fs);
      old_fs = fstab_find_mount (dst, fs->mntent.mnt_dir);
      if (old_fs)
	fs_free (old_fs);
    }

  /* Now that we know there are no conflicts, steal all SRC's entries and
     cons them onto DST.  */
  for (fs = src->entries; fs; fs = fs->next)
    _fstab_add (dst, fs);

  /* Now all entries from SRC should be in DST, so just deallocate SRC.  */
  free (src);

  return 0;
}

/* Reads fstab-format entries into FSTAB from the file NAME.  Any entries
   duplicating one already in FS_LIST supersede the existing entry.  */
error_t
fstab_read (struct fstab *fstab, const char *name)
{
  error_t err;
  /* Used to hold entries from the file, before merging with FSTAB at the
     end.  */
  struct fstab *contents;
  FILE *stream = setmntent (name, "r");

  if (! stream)
    return errno;

  err = fstab_create (fstab->types, &contents);
  if (! err)
    {
      while (!err && !feof (stream))
	{
	  struct mntent *mntent = getmntent (stream);

	  if (! mntent)
	    err = errno;
	  else if (fstab_find_device (fstab, mntent->mnt_fsname))
	    error (0, 0, "%s: Warning: duplicate entry for device %s (%s)",
		   name, mntent->mnt_fsname, mntent->mnt_dir);
	  else if (fstab_find_mount (fstab, mntent->mnt_dir))
	    error (0, 0, "%s: Warning: duplicate entry for mount point %s (%s)",
		   name, mntent->mnt_dir, mntent->mnt_fsname);
	  else
	    err = fstab_add_mntent (fstab, mntent, 0);
	}

      if (! err)
	fstab_merge (fstab, contents);
      else
	fstab_free (contents);
    }

  endmntent (stream);

  return err;
}

/* Return the next pass number that applies to any filesystem in FSTAB that
   is greater than PASS, or -1 if there isn't any.  */
int fstab_next_pass (const struct fstab *fstab, int pass)
{
  int next_pass = -1;
  struct fs *fs;
  for (fs = fstab->entries; fs; fs = fs->next)
    if (fs->mntent.mnt_passno > pass)
      if (next_pass < 0 || fs->mntent.mnt_passno < next_pass)
	{
	  next_pass = fs->mntent.mnt_passno;
	  if (next_pass == pass + 1)
	    break;		/* Only possible answer.  */
	}
  return next_pass;
}


static const struct argp_option options[] =
{
  {"all",	 'a', 0,      0, "Do all filesystems in " _PATH_MNTTAB},
  {0,		 'A', 0,      OPTION_ALIAS },
  {"fstab",	 'F', "FILE", 0, "File to use instead of " _PATH_MNTTAB},
  {"fstype",	 't', "TYPE", 0, "Do only filesystems of given type(s)"},
  {"exclude-root",'R',0,      0,
     "Exclude root (/) filesystem from " _PATH_MNTTAB " list"},
  {"exclude",	 'X', "PATTERN", 0, "Exclude directories matching PATTERN"},

  {"search-fmts",'S', "FMTS", 0,
     "`:' separated list of formats to use for finding"
     " filesystem-specific programs"},

  {0, 0}
};

static error_t
parse_opt (int key, char *arg, struct argp_state *state)
{
  error_t err;
  struct fstab_argp_params *params = state->input;

  switch (key)
    {
    case ARGP_KEY_INIT:
      /* Initialize our parsing state.  */
      if (! params)
	return EINVAL;	/* Need at least a way to return a result.  */
      bzero (params, sizeof *params);
      break;

    case 'A':
    case 'a':
      params->do_all = 1;
      break;

    case 'F':
      params->fstab_path = arg;
      break;

    case 'S':
      argz_create_sep (arg, ':',
		       &params->program_search_fmts,
		       &params->program_search_fmts_len);
      break;

    case 'R':
      arg = "/";
      /* FALLTHROUGH */
    case 'X':
      err = argz_add (&params->exclude, &params->exclude_len, arg);
      if (err)
	argp_failure (state, 100, ENOMEM, "%s", arg);
      break;
    case 't':
      err = argz_add_sep (&params->types, &params->types_len, arg, ',');
      if (err)
	argp_failure (state, 100, ENOMEM, "%s", arg);
      break;

    case ARGP_KEY_ARG:
      err = argz_add (&params->names, &params->names_len, arg);
      if (err)
	argp_failure (state, 100, ENOMEM, "%s", arg);
      break;

    case ARGP_KEY_END:
      /* Check for bogus combinations of arguments.  */
      if (params->names)
	{
	  if (params->do_all)
	    argp_error (state, "filesystem arguments not allowed with --all");
	  if (params->exclude)
	    argp_error (state,
			"--exclude not allowed with filesystem arguments");
	  if (params->types)
	    argp_error (state,
			"--fstype not allowed with filesystem arguments");
	}
      break;

    default:
      return ARGP_ERR_UNKNOWN;
    }

  return 0;
}

const struct argp fstab_argp = {options, parse_opt, 0, 0};

struct fstab *
fstab_argp_create (struct fstab_argp_params *params,
		   const char *default_search_fmts,
		   size_t default_search_fmts_len)
{
  error_t err;
  struct fstab *fstab, *check;
  struct fstypes *types;

  if (params->fstab_path == 0)
    params->fstab_path = _PATH_MNTTAB;
  if (params->program_search_fmts == 0)
    {
      params->program_search_fmts = (char *) default_search_fmts;
      params->program_search_fmts_len = default_search_fmts_len;
    }

  err = fstypes_create (params->program_search_fmts,
			params->program_search_fmts_len,
			&types);
  if (err)
    error (102, err, "fstypes_create");

  err = fstab_create (types, &fstab);
  if (err)
    error (101, err, "fstab_create");

  err = fstab_read (fstab, params->fstab_path);
  if (err)
    error (103, err, "%s", params->fstab_path);

  if (params->names)
    {
      /* Process specified filesystems; also look at /var/run/mtab.  */
      const char *name;

      err = fstab_read (fstab, _PATH_MOUNTED);
      if (err && err != ENOENT)
	error (104, err, "%s", _PATH_MOUNTED);

      err = fstab_create (types, &check);
      if (err)
	error (105, err, "fstab_create");

      for (name = params->names; name; name = argz_next (params->names,
							 params->names_len,
							 name))
	{
	  struct fs *fs = fstab_find (fstab, name);
	  if (! fs)
	    error (106, 0, "%s: Unknown device or filesystem", name);
	  fstab_add_fs (check, fs, 0);
	}

      /*      fstab_free (fstab); XXX */
    }
  else
    {
      /* Process everything in /etc/fstab.  */

      if (params->exclude == 0 && params->types == 0)
	check = fstab;
      else
	{
	  struct fs *fs;
	  const char *tn;
	  unsigned int nonexclude_types;

	  err = fstab_create (types, &check);
	  if (err)
	    error (105, err, "fstab_create");

	  /* For each excluded type (i.e. `-t notype'), clobber the
	     fstype entry's program with an empty string to mark it.  */
	  nonexclude_types = 0;
	  for (tn = params->types; tn;
	       tn = argz_next (params->types, params->types_len, tn))
	    {
	      if (!strncasecmp (tn, "no", 2))
		{
		  struct fstype *type;
		  err = fstypes_get (types, &tn[2], &type);
		  if (err)
		    error (106, err, "fstypes_get");
		  free (type->program);
		  type->program = strdup ("");
		}
	      else
		++nonexclude_types;
	    }

	  if (nonexclude_types != 0)
	    {
	      const char *tn;
	      struct fstypes *wanttypes;

	      /* We will copy the types we want to include into a fresh
		 list in WANTTYPES.  Since we specify no search formats,
		 `fstypes_get' applied to WANTTYPES can only create
		 elements with a null `program' field.  */
	      err = fstypes_create (0, 0, &wanttypes);
	      if (err)
		error (102, err, "fstypes_create");

	      for (tn = params->types; tn;
		   tn = argz_next (params->types, params->types_len, tn))
		if (strncasecmp (tn, "no", 2))
		  {
		    struct fstype *type;
		    err = fstypes_get (types, tn, &type);
		    if (err)
		      error (106, err, "fstypes_get");
		    if (type->program == 0)
		      error (0, 0,
			     "requested filesystem type `%s' unknown", tn);
		    else
		      {
			struct fstype *newtype = malloc (sizeof *newtype);
			newtype->name = strdup (type->name);
			newtype->program = strdup (type->program);
			newtype->next = wanttypes->entries;
			wanttypes->entries = newtype;
		      }
		  }

	      /* fstypes_free (types); */
	      types = wanttypes;
	    }

	  for (fs = fstab->entries; fs; fs = fs->next)
	    {
	      const char *ptn;
	      struct fstype *type;

	      err = fs_type (fs, &type);
	      if (err || nonexclude_types)
		{
		  err = fstypes_get (types, fs->mntent.mnt_type, &type);
		  if (err)
		    error (106, err, "fstypes_get");
		  if (params->types != 0)
		    continue;
		}
	      if (nonexclude_types && type->program == 0)
		continue;	/* Freshly created, was not in WANTTYPES.  */
	      if (type->program != 0 && type->program[0] == '\0')
		continue;	/* This type is marked as excluded.  */

	      for (ptn = params->exclude; ptn;
		   ptn = argz_next (params->exclude, params->exclude_len, ptn))
		if (fnmatch (ptn, fs->mntent.mnt_dir, 0) == 0)
		  break;
	      if (ptn)	/* An exclude pattern matched.  */
		continue;

	      err = fstab_add_fs (check, fs, 0);
	      if (err)
		error (107, err, "fstab_add_fs");
	    }

	  /*	  fstab_free (fstab); XXX */
	}
    }

  return check;
}