text: /*
   * CDDL HEADER START
   *
   * The contents of this file are subject to the terms of the
   * Common Development and Distribution License (the "License").
   * You may not use this file except in compliance with the License.
   *
   * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
   * or http://www.opensolaris.org/os/licensing.
   * See the License for the specific language governing permissions
   * and limitations under the License.
   *
   * When distributing Covered Code, include this CDDL HEADER in each
   * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
   * If applicable, add the following below this CDDL HEADER, with the
   * fields enclosed by brackets "[]" replaced with your own identifying
   * information: Portions Copyright [yyyy] [name of copyright owner]
   *
   * CDDL HEADER END
   */
  /*
   * Copyright 2008 Sun Microsystems, Inc.  All rights reserved.
   * Use is subject to license terms.
   */
  
  #pragma ident	"%Z%%M%	%I%	%E% SMI"
  
  /*
   * This module adds support to the RCM framework for mounted filesystems.
   *
   * The module provides this functionality:
   * 	1) reports device usage for mounted filesystems
   *	2) prevents offline operations for mounted resources
   *	3) prevents suspend operations (unless forced) of those filesystems
   *	   deemed critical for the continued operation of the OS
   *	4) propagates RCM operations from mounted resources to the consumers
   *	   of files within the mounted filesystems
   */
  
  #include <stdio.h>
  #include <assert.h>
  #include <string.h>
  #include <synch.h>
  #include <libintl.h>
  #include <errno.h>
  #include <sys/mnttab.h>
  #include <sys/param.h>
  #include <sys/stat.h>
  #include <sys/utssys.h>
  #include <unistd.h>
  #include <limits.h>
  
  #include "rcm_module.h"
  
  /* Definitions */
  
  #define	HASH_DEFAULT		4
  #define	HASH_THRESHOLD		256
  
  #define	OPT_IGNORE		"ignore"
  
  #define	MSG_HDR_STD		gettext("mounted filesystem")
  #define	MSG_HDR_STD_MULTI	gettext("mounted filesystems")
  #define	MSG_HDR_CRIT		gettext("cannot suspend filesystem")
  #define	MSG_HDR_CRIT_MULTI	gettext("cannot suspend filesystems")
  #define	MSG_SEPARATOR		gettext(", ")
  #define	MSG_FAIL_USAGE		gettext("failed to construct usage string.")
  #define	MSG_FAIL_DEPENDENTS	gettext("failed while calling dependents.")
  #define	MSG_FAIL_REMOVE		gettext("filesystems cannot be removed.")
  #define	MSG_FAIL_INTERNAL	gettext("internal processing failure.")
  
  typedef struct hashentry {
  	int n_mounts;
  	char *special;
  	char *fstype;
  	char **mountps;
  	struct hashentry *next;
  } hashentry_t;
  
  typedef struct {
  	time_t timestamp;
  	uint32_t hash_size;
  	hashentry_t **mounts;
  } cache_t;
  
  /* Forward Declarations */
  
  /* module interface routines */
  static int mnt_register(rcm_handle_t *);
  static int mnt_unregister(rcm_handle_t *);
  static int mnt_getinfo(rcm_handle_t *, char *, id_t, uint_t, char **, char **,
      nvlist_t *, rcm_info_t **);
  static int mnt_suspend(rcm_handle_t *, char *, id_t, timespec_t *,
      uint_t, char **, rcm_info_t **);
  static int mnt_resume(rcm_handle_t *, char *, id_t, uint_t, char **,
      rcm_info_t **);
  static int mnt_offline(rcm_handle_t *, char *, id_t, uint_t, char **,
      rcm_info_t **);
  static int mnt_online(rcm_handle_t *, char *, id_t, uint_t, char **,
      rcm_info_t **);
  static int mnt_remove(rcm_handle_t *, char *, id_t, uint_t, char **,
      rcm_info_t **);
  
  /* cache functions */
  static cache_t *cache_create();
  static int cache_insert(cache_t *, struct mnttab *);
  static int cache_sync(rcm_handle_t *, cache_t **);
  static hashentry_t *cache_lookup(cache_t *, char *);
  static void free_cache(cache_t **);
  static void free_entry(hashentry_t **);
  static void free_list(char **);
  
  /* miscellaneous functions */
  static uint32_t hash(uint32_t, char *);
  static void register_rsrc(rcm_handle_t *, char *);
  static void unregister_rsrc(rcm_handle_t *, char *);
  static char *create_message(char *, char *, char **);
  static int detect_critical_failure(char **, uint_t, char **);
  static int is_critical(char *);
  static int use_cache(char *, char **, char ***);
  static void prune_dependents(char **, char *);
  static char **create_dependents(hashentry_t *);
  
  /* Module-Private data */
  
  static struct rcm_mod_ops mnt_ops =
  {
  	RCM_MOD_OPS_VERSION,
  	mnt_register,
  	mnt_unregister,
  	mnt_getinfo,
  	mnt_suspend,
  	mnt_resume,
  	mnt_offline,
  	mnt_online,
  	mnt_remove
  };
  
  static cache_t *mnt_cache;
  static mutex_t cache_lock;
  
  /* Module Interface Routines */
  
  /*
   * rcm_mod_init()
   *
   *	Called when module is loaded.  Returns the ops vector.
   */
  struct rcm_mod_ops *
  rcm_mod_init()
  {
  	return (&mnt_ops);
  }
  
  /*
   * rcm_mod_info()
   *
   *	Returns a string identifying this module.
   */
  const char *
  rcm_mod_info()
  {
  	return ("File system module 1.9");
  }
  
  /*
   * rcm_mod_fini()
   *
   *	Called when module is unloaded.  Frees up all used memory.
   *
   *	Locking: the cache is locked for the duration of this function.
   */
  int
  rcm_mod_fini()
  {
  	(void) mutex_lock(&cache_lock);
  	free_cache(&mnt_cache);
  	(void) mutex_unlock(&cache_lock);
  
  	return (RCM_SUCCESS);
  }
  
  /*
   * mnt_register()
   *
   *	Called to synchronize the module's registrations.  Results in the
   *	construction of a new cache, destruction of any old cache data,
   *	and a full synchronization of the module's registrations.
   *
   *	Locking: the cache is locked for the duration of this function.
   */
  int
  mnt_register(rcm_handle_t *hd)
  {
  	assert(hd != NULL);
  
  	rcm_log_message(RCM_TRACE1, "FILESYS: register()\n");
  
  	(void) mutex_lock(&cache_lock);
  
  	/* cache_sync() does all of the necessary work */
  	if (cache_sync(hd, &mnt_cache) < 0) {
  		rcm_log_message(RCM_ERROR,
  		    "FILESYS: failed to synchronize cache (%s).\n",
  		    strerror(errno));
  		(void) mutex_unlock(&cache_lock);
  		return (RCM_FAILURE);
  	}
  
  	(void) mutex_unlock(&cache_lock);
  
  	return (RCM_SUCCESS);
  }
  
  /*
   * mnt_unregister()
   *
   *	Manually walk through the cache, unregistering all the special
   *	files and mount points.
   *
   *	Locking: the cache is locked throughout the execution of this
   *	routine because it reads and modifies cache links continuously.
   */
  int
  mnt_unregister(rcm_handle_t *hd)
  {
  	uint32_t index;
  	hashentry_t *entry;
  
  	assert(hd != NULL);
  
  	rcm_log_message(RCM_TRACE1, "FILESYS: unregister()\n");
  
  	(void) mutex_lock(&cache_lock);
  
  	/* Unregister everything in the cache */
  	if (mnt_cache) {
  		for (index = 0; index < mnt_cache->hash_size; index++) {
  			for (entry = mnt_cache->mounts[index]; entry != NULL;
  			    entry = entry->next) {
  				unregister_rsrc(hd, entry->special);
  			}
  		}
  	}
  
  	/* Destroy the cache */
  	free_cache(&mnt_cache);
  
  	(void) mutex_unlock(&cache_lock);
  
  	return (RCM_SUCCESS);
  }
  
  /*
   * mnt_offline()
   *
   *	Filesystem resources cannot be offlined. They can however be retired
   *	if they don't provide a critical service. The offline entry point
   *	checks if this is a retire operation and if it is and the filesystem
   *	doesn't provide a critical service, the entry point returns success
   *	For all other cases, failure is returned.
   *	Since no real action is taken, QUERY or not doesn't matter.
   */
  int
  mnt_offline(rcm_handle_t *hd, char *rsrc, id_t id, uint_t flags,
      char **errorp, rcm_info_t **dependent_info)
  {
  	char **dependents;
  	hashentry_t *entry;
  	int retval;
  	int i;
  
  	assert(hd != NULL);
  	assert(rsrc != NULL);
  	assert(id == (id_t)0);
  	assert(errorp != NULL);
  
  	*errorp = NULL;
  
  	rcm_log_message(RCM_TRACE1, "FILESYS: offline(%s)\n", rsrc);
  
  	/* Retrieve necessary info from the cache */
  	if (use_cache(rsrc, errorp, &dependents) < 0) {
  		if (flags & RCM_RETIRE_REQUEST)
  			return (RCM_NO_CONSTRAINT);
  		else
  			return (RCM_FAILURE);
  	}
  
  	if (flags & RCM_RETIRE_REQUEST) {
  		(void) mutex_lock(&cache_lock);
  		if ((entry = cache_lookup(mnt_cache, rsrc)) == NULL) {
  			rcm_log_message(RCM_ERROR, "FILESYS: "
  			    "failed to look up \"%s\" in cache (%s).\n",
  			    rsrc, strerror(errno));
  			(void) mutex_unlock(&cache_lock);
  			retval = RCM_NO_CONSTRAINT;
  			goto out;
  		}
  
  		if (strcmp(entry->fstype, "zfs") == 0) {
  			retval = RCM_NO_CONSTRAINT;
  			rcm_log_message(RCM_TRACE1,
  			    "FILESYS: zfs: NO_CONSTRAINT: %s\n", rsrc);
  		} else {
  			retval = RCM_SUCCESS;
  			for (i = 0; dependents[i] != NULL; i++) {
  				if (is_critical(dependents[i])) {
  					retval = RCM_FAILURE;
  					rcm_log_message(RCM_TRACE1, "FILESYS: "
  					    "CRITICAL %s\n", rsrc);
  					break;
  				}
  			}
  		}
  		(void) mutex_unlock(&cache_lock);
  		goto out;
  	}
  
  	retval = RCM_FAILURE;
  
  	/* Convert the gathered dependents into an error message */
  	*errorp = create_message(MSG_HDR_STD, MSG_HDR_STD_MULTI, dependents);
  	if (*errorp == NULL) {
  		rcm_log_message(RCM_ERROR,
  		    "FILESYS: failed to construct offline message (%s).\n",
  		    strerror(errno));
  	}
  
  out:
  	free_list(dependents);
  	return (retval);
  }
  
  /*
   * mnt_online()
   *
   *	Filesystem resources aren't offlined, so there's really nothing to do
   *	here.
   */
  int
  mnt_online(rcm_handle_t *hd, char *rsrc, id_t id, uint_t flag, char **errorp,
      rcm_info_t **dependent_reason)
  {
  	assert(hd != NULL);
  	assert(rsrc != NULL);
  	assert(id == (id_t)0);
  	assert(errorp != NULL);
  
  	rcm_log_message(RCM_TRACE1, "FILESYS: online(%s)\n", rsrc);
  
  	return (RCM_SUCCESS);
  }
  
  /*
   * mnt_getinfo()
   *
   *	Report how a given resource is in use by this module.  And also
   *	possibly include dependent consumers of the mounted filesystems.
   */
  int
  mnt_getinfo(rcm_handle_t *hd, char *rsrc, id_t id, uint_t flag, char **usagep,
      char **errorp, nvlist_t *props, rcm_info_t **depend_info)
  {
  	int rv = RCM_SUCCESS;
  	char **dependents;
  
  	assert(hd != NULL);
  	assert(rsrc != NULL);
  	assert(id == (id_t)0);
  	assert(usagep != NULL);
  	assert(errorp != NULL);
  	assert(props != NULL);
  
  	rcm_log_message(RCM_TRACE1, "FILESYS: getinfo(%s)\n", rsrc);
  
  	/* Retrieve necessary info from the cache */
  	if (use_cache(rsrc, errorp, &dependents) < 0)
  		return (RCM_FAILURE);
  
  	/* Convert the gathered dependents into a usage message */
  	*usagep = create_message(MSG_HDR_STD, MSG_HDR_STD_MULTI, dependents);
  	if (*usagep == NULL) {
  		rcm_log_message(RCM_ERROR,
  		    "FILESYS: failed to construct usage message (%s).\n",
  		    strerror(errno));
  		*errorp = strdup(MSG_FAIL_USAGE);
  		free_list(dependents);
  		return (RCM_FAILURE);
  	}
  
  	/* Recurse on dependents if necessary */
  	if ((flag & RCM_INCLUDE_DEPENDENT) && (dependents != NULL)) {
  		prune_dependents(dependents, rsrc);
  		if (dependents[0] != NULL) {
  			if ((rv = rcm_get_info_list(hd, dependents, flag,
  			    depend_info)) != RCM_SUCCESS) {
  				*errorp = strdup(MSG_FAIL_DEPENDENTS);
  			}
  		}
  	}
  
  	/* Free up info retrieved from the cache */
  	free_list(dependents);
  
  	return (rv);
  }
  
  /*
   * mnt_suspend()
   *
   *	Notify all dependents that the resource is being suspended.
   *	Since no real action is taken, QUERY or not doesn't matter.
   */
  int
  mnt_suspend(rcm_handle_t *hd, char *rsrc, id_t id, timespec_t *interval,
      uint_t flag, char **errorp, rcm_info_t **depend_info)
  {
  	int rv = RCM_SUCCESS;
  	char **dependents;
  
  	assert(hd != NULL);
  	assert(rsrc != NULL);
  	assert(id == (id_t)0);
  	assert(interval != NULL);
  	assert(errorp != NULL);
  
  	rcm_log_message(RCM_TRACE1, "FILESYS: suspend(%s)\n", rsrc);
  
  	/* Retrieve necessary info from the cache */
  	if (use_cache(rsrc, errorp, &dependents) < 0)
  		return (RCM_FAILURE);
  
  	/* Unforced suspensions fail if any of the dependents are critical */
  	if (detect_critical_failure(errorp, flag, dependents)) {
  		free_list(dependents);
  		return (RCM_FAILURE);
  	}
  
  	/* Recurse on dependents if necessary */
  	if ((flag & RCM_INCLUDE_DEPENDENT) && (dependents != NULL)) {
  		prune_dependents(dependents, rsrc);
  		if (dependents[0] != NULL)
  			if ((rv = rcm_request_suspend_list(hd, dependents, flag,
  			    interval, depend_info)) != RCM_SUCCESS) {
  				*errorp = strdup(MSG_FAIL_DEPENDENTS);
  			}
  	}
  	free_list(dependents);
  
  	return (rv);
  }
  
  /*
   * mnt_resume()
   *
   *	Resume all the dependents of a suspended filesystem.
   */
  int
  mnt_resume(rcm_handle_t *hd, char *rsrc, id_t id, uint_t flag, char **errorp,
      rcm_info_t **depend_info)
  {
  	int rv = RCM_SUCCESS;
  	char **dependents;
  
  	assert(hd != NULL);
  	assert(rsrc != NULL);
  	assert(id == (id_t)0);
  	assert(errorp != NULL);
  
  	rcm_log_message(RCM_TRACE1, "FILESYS: resume(%s)\n", rsrc);
  
  	/* Retrieve necessary info from the cache */
  	if (use_cache(rsrc, errorp, &dependents) < 0)
  		return (RCM_FAILURE);
  
  	/* Recurse on dependents if necessary */
  	if ((flag & RCM_INCLUDE_DEPENDENT) && (dependents != NULL)) {
  		prune_dependents(dependents, rsrc);
  		if (dependents[0] != NULL) {
  			if ((rv = rcm_notify_resume_list(hd, dependents, flag,
  			    depend_info)) != RCM_SUCCESS) {
  				*errorp = strdup(MSG_FAIL_DEPENDENTS);
  			}
  		}
  	}
  	free_list(dependents);
  
  	return (rv);
  }
  
  static int
  get_spec(char *line, char *spec, size_t ssz)
  {
  	char	*cp;
  	char	*start;
  
  	if (strlcpy(spec, line, ssz) >= ssz) {
  		rcm_log_message(RCM_ERROR, "FILESYS: get_spec() failed: "
  		    "line: %s\n", line);
  		return (-1);
  	}
  
  	cp = spec;
  	while (*cp == ' ' || *cp == '\t')
  		cp++;
  
  	if (*cp == '#')
  		return (-1);
  
  	start = cp;
  
  	while (*cp != ' ' && *cp != '\t' && *cp != '\0')
  		cp++;
  	*cp = '\0';
  
  	(void) memmove(spec, start, strlen(start) + 1);
  
  	return (0);
  }
  
  static int
  path_match(char *rsrc, char *spec)
  {
  	char r[PATH_MAX];
  	char s[PATH_MAX];
  	size_t len;
  
  	if (realpath(rsrc, r) == NULL)
  		goto error;
  
  	if (realpath(spec, s) == NULL)
  		goto error;
  
  	len = strlen("/devices/");
  
  	if (strncmp(r, "/devices/", len) != 0) {
  		errno = ENXIO;
  		goto error;
  	}
  
  	if (strncmp(s, "/devices/", len) != 0) {
  		errno = ENXIO;
  		goto error;
  	}
  
  	len = strlen(r);
  	if (strncmp(r, s, len) == 0 && (s[len] == '\0' || s[len] == ':'))
  		return (0);
  	else
  		return (1);
  
  error:
  	rcm_log_message(RCM_DEBUG, "FILESYS: path_match() failed "
  	    "rsrc=%s spec=%s: %s\n", rsrc, spec, strerror(errno));
  	return (-1);
  }
  
  #define	VFSTAB		"/etc/vfstab"
  #define	RETIRED_PREFIX	"## RETIRED ##"
  
  static int
  disable_vfstab_entry(char *rsrc)
  {
  	FILE	*vfp;
  	FILE	*tfp;
  	int	retval;
  	int	update;
  	char	tmp[PATH_MAX];
  	char	line[MNT_LINE_MAX + 1];
  
  	vfp = fopen(VFSTAB, "r");
  	if (vfp == NULL) {
  		rcm_log_message(RCM_ERROR, "FILESYS: failed to open /etc/vfstab"
  		    " for reading: %s\n", strerror(errno));
  		return (RCM_FAILURE);
  	}
  
  	(void) snprintf(tmp, sizeof (tmp), "/etc/vfstab.retire.%lu", getpid());
  
  	tfp = fopen(tmp, "w");
  	if (tfp == NULL) {
  		rcm_log_message(RCM_ERROR, "FILESYS: failed to open "
  		    "/etc/vfstab.retire for writing: %s\n", strerror(errno));
  		(void) fclose(vfp);
  		return (RCM_FAILURE);
  	}
  
  	retval = RCM_SUCCESS;
  	update = 0;
  	while (fgets(line, sizeof (line), vfp)) {
  
  		char	spec[MNT_LINE_MAX + 1];
  		char	newline[MNT_LINE_MAX + 1];
  		char	*l;
  
  		if (get_spec(line, spec, sizeof (spec)) == -1) {
  			l = line;
  			goto foot;
  		}
  
  		if (path_match(rsrc, spec) != 0) {
  			l = line;
  			goto foot;
  		}
  
  		update = 1;
  
  		/* Paths match. Disable this entry */
  		(void) snprintf(newline, sizeof (newline), "%s %s",
  		    RETIRED_PREFIX, line);
  
  		rcm_log_message(RCM_TRACE1, "FILESYS: disabling line\n\t%s\n",
  		    line);
  
  		l = newline;
  foot:
  		if (fputs(l, tfp) == EOF) {
  			rcm_log_message(RCM_ERROR, "FILESYS: failed to write "
  			    "new vfstab: %s\n", strerror(errno));
  			update = 0;
  			retval = RCM_FAILURE;
  			break;
  		}
  	}
  
  	if (vfp)
  		(void) fclose(vfp);
  	if (tfp)
  		(void) fclose(tfp);
  
  	if (update) {
  		if (rename(tmp, VFSTAB) != 0) {
  			rcm_log_message(RCM_ERROR, "FILESYS: vfstab rename "
  			    "failed: %s\n", strerror(errno));
  			retval = RCM_FAILURE;
  		}
  	}
  
  	(void) unlink(tmp);
  
  	return (retval);
  }
  
  /*
   * mnt_remove()
   *
   *	Remove will only be called in the retire case i.e. if RCM_RETIRE_NOTIFY
   *	flag is set.
   *
   *	If the flag is not set, then return failure and log the mistake if a
   *	remove is ever received for a mounted filesystem resource.
   */
  int
  mnt_remove(rcm_handle_t *hd, char *rsrc, id_t id, uint_t flag, char **errorp,
      rcm_info_t **depend_info)
  {
  	assert(hd != NULL);
  	assert(rsrc != NULL);
  	assert(id == (id_t)0);
  	assert(errorp != NULL);
  
  	rcm_log_message(RCM_TRACE1, "FILESYS: remove(%s)\n", rsrc);
  
  	if (!(flag & RCM_RETIRE_NOTIFY)) {
  		/* Log the mistake */
  		rcm_log_message(RCM_ERROR, "FILESYS: invalid remove of "
  		    "\"%s\"\n", rsrc);
  		*errorp = strdup(MSG_FAIL_REMOVE);
  		return (RCM_FAILURE);
  	}
  
  	return (disable_vfstab_entry(rsrc));
  }
  
  /*
   * Cache management routines
   */
  
  /*
   * cache_create()
   *
   *	This routine constructs a new cache of the current mnttab file.
   *
   *	Locking: the cache must be locked prior to calling this function.
   *
   *	Return Values: NULL with errno set on failure, new cache point on
   *	success.
   */
  static cache_t *
  cache_create()
  {
  	FILE *fp;
  	cache_t *cache;
  	int i;
  	uint32_t size;
  	struct stat st;
  	struct mnttab mt;
  
  	/*
  	 * To keep the hash table relatively sparse, default values are
  	 * used for smaller mnttab files and these values are scaled up
  	 * as a fraction of the total mnttab file size for larger ones.
  	 */
  	if (stat(MNTTAB, &st) < 0) {
  		rcm_log_message(RCM_ERROR,
  		    "FILESYS: failed to stat \"%s\" (%s).\n", MNTTAB,
  		    strerror(errno));
  		errno = EBADF;
  		return (NULL);
  	}
  	if (st.st_size > HASH_THRESHOLD) {
  		size = st.st_size / HASH_THRESHOLD;
  		for (i = 0; size > 1; i++, size >>= 1);
  		for (; i > -1; i--, size <<= 1);
  	} else {
  		size = HASH_DEFAULT;
  	}
  
  	/* Allocate a new empty cache */
  	if ((cache = (cache_t *)calloc(1, sizeof (cache_t))) == NULL) {
  		rcm_log_message(RCM_ERROR,
  		    "FILESYS: failed to allocate cache (%s).\n",
  		    strerror(errno));
  		errno = ENOMEM;
  		return (NULL);
  	}
  	cache->hash_size = size;
  	cache->timestamp = st.st_mtime;
  
  	/* Allocate an empty hash table for the registered special devices */
  	cache->mounts = (hashentry_t **)calloc(size, sizeof (hashentry_t *));
  	if (cache->mounts == NULL) {
  		rcm_log_message(RCM_ERROR,
  		    "FILESYS: failed to allocate mount table (%s).\n",
  		    strerror(errno));
  		free_cache(&cache);
  		errno = ENOMEM;
  		return (NULL);
  	}
  
  	/* Open the mnttab file */
  	if ((fp = fopen(MNTTAB, "r")) == NULL) {
  		rcm_log_message(RCM_ERROR,
  		    "FILESYS: failed to open \"%s\" (%s).\n", MNTTAB,
  		    strerror(errno));
  		free_cache(&cache);
  		errno = EIO;
  		return (NULL);
  	}
  
  	/* Insert each mnttab entry into the cache */
  	while (getmntent(fp, &mt) == 0) {
  
  		/* Well, not each entry... some are meant to be ignored */
  		if ((mt.mnt_mntopts != NULL) &&
  		    (hasmntopt(&mt, OPT_IGNORE) != NULL))
  			continue;
  
  		if (cache_insert(cache, &mt) < 0) {
  			rcm_log_message(RCM_ERROR,
  			    "FILESYS: cache insertion failure (%s).\n",
  			    strerror(errno));
  			free_cache(&cache);
  			(void) fclose(fp);
  			errno = EFAULT;
  			return (NULL);
  		}
  	}
  
  	/* Close the mnttab file */
  	(void) fclose(fp);
  
  	return (cache);
  }
  
  /*
   * free_cache()
   *
   *	Free up all the memory associated with a cache.
   *
   *	Locking: the cache must be locked before calling this function.
   */
  static void
  free_cache(cache_t **cachep)
  {
  	uint32_t index;
  	hashentry_t *entry;
  	hashentry_t *entry_tmp;
  
  	/* Do nothing with empty caches */
  	if ((cachep == NULL) || (*cachep == NULL))
  		return;
  
  	if ((*cachep)->mounts) {
  		/* Walk through the hashtable, emptying it */
  		for (index = 0; index < (*cachep)->hash_size; index++) {
  			entry = (*cachep)->mounts[index];
  			while (entry) {
  				entry_tmp = entry->next;
  				free_entry(&entry);
  				entry = entry_tmp;
  			}
  		}
  		free((*cachep)->mounts);
  	}
  
  	free(*cachep);
  	*cachep = NULL;
  }
  
  /*
   * free_entry()
   *
   *	Free up memory associated with a hashtable entry.
   *
   *	Locking: the cache must be locked before calling this function.
   */
  static void
  free_entry(hashentry_t **entryp)
  {
  	if (entryp) {
  		if (*entryp) {
  			if ((*entryp)->special)
  				free((*entryp)->special);
  			if ((*entryp)->fstype)
  				free((*entryp)->fstype);
  			free_list((*entryp)->mountps);
  			free(*entryp);
  		}
  		*entryp = NULL;
  	}
  }
  
  /*
   * free_list()
   *
   *	Free up memory associated with a null terminated list of names.
   */
  static void
  free_list(char **list)
  {
  	int i;
  
  	if (list) {
  		for (i = 0; list[i] != NULL; i++)
  			free(list[i]);
  		free(list);
  	}
  }
  
  /*
   * cache_sync()
   *
   *	Resynchronize the mnttab cache with the mnttab file.
   *
   *	Locking: the cache must be locked before calling this function.
   *
   *	Return Values: -1 with errno set on failure, 0 on success.
   */
  static int
  cache_sync(rcm_handle_t *hd, cache_t **cachep)
  {
  	uint32_t index;
  	cache_t *new_cache;
  	cache_t *old_cache;
  	hashentry_t *entry;
  	struct stat st;
  
  	/* Only accept valid arguments */
  	if ((hd == NULL) || (cachep == NULL)) {
  		rcm_log_message(RCM_ERROR,
  		    "FILESYS: invalid arguments to cache_sync().\n");
  		errno = EINVAL;
  		return (-1);
  	}
  
  	/* Do nothing if there's already an up-to-date cache */
  	old_cache = *cachep;
  	if (old_cache) {
  		if (stat(MNTTAB, &st) == 0) {
  			if (old_cache->timestamp >= st.st_mtime) {
  				return (0);
  			}
  		} else {
  			rcm_log_message(RCM_WARNING,
  			    "FILESYS: failed to stat \"%s\", cache is stale "
  			    "(%s).\n", MNTTAB, strerror(errno));
  			errno = EIO;
  			return (-1);
  		}
  	}
  
  	/* Create a new cache based on the new mnttab file.  */
  	if ((new_cache = cache_create()) == NULL) {
  		rcm_log_message(RCM_WARNING,
  		    "FILESYS: failed creating cache, cache is stale (%s).\n",
  		    strerror(errno));
  		errno = EIO;
  		return (-1);
  	}
  
  	/* Register any specials found in the new cache but not the old one */
  	for (index = 0; index < new_cache->hash_size; index++) {
  		for (entry = new_cache->mounts[index]; entry != NULL;
  		    entry = entry->next) {
  			if (cache_lookup(old_cache, entry->special) == NULL) {
  				register_rsrc(hd, entry->special);
  			}
  		}
  	}
  
  	/* Pass the new cache pointer to the calling function */
  	*cachep = new_cache;
  
  	/* If there wasn't an old cache, return successfully now */
  	if (old_cache == NULL)
  		return (0);
  
  	/*
  	 * If there was an old cache, then unregister whatever specials it
  	 * contains that aren't in the new cache.  And then destroy the old
  	 * cache.
  	 */
  	for (index = 0; index < old_cache->hash_size; index++) {
  		for (entry = old_cache->mounts[index]; entry != NULL;
  		    entry = entry->next) {
  			if (cache_lookup(new_cache, entry->special) == NULL) {
  				unregister_rsrc(hd, entry->special);
  			}
  		}
  	}
  	free_cache(&old_cache);
  
  	return (0);
  }
  
  /*
   * cache_insert()
   *
   *	Given a cache and a mnttab entry, this routine inserts that entry in
   *	the cache.  The mnttab entry's special device and filesystem type
   *	is added to the 'mounts' hashtable of the cache, and the entry's
   *	mountp value is added to the list of associated mountpoints for the
   *	corresponding hashtable entry.
   *
   *	Locking: the cache must be locked before calling this function.
   *
   *	Return Values: -1 with errno set on failure, 0 on success.
   */
  static int
  cache_insert(cache_t *cache, struct mnttab *mt)
  {
  	uint32_t index;
  	hashentry_t *entry;
  	char **mountps;
  
  	/* Only accept valid arguments */
  	if ((cache == NULL) ||
  	    (cache->mounts == NULL) ||
  	    (mt == NULL) ||
  	    (mt->mnt_special == NULL) ||
  	    (mt->mnt_mountp == NULL) ||
  	    (mt->mnt_fstype == NULL)) {
  		errno = EINVAL;
  		return (-1);
  	}
  
  	/*
  	 * Disregard any non-loopback mounts whose special device names
  	 * don't begin with "/dev".
  	 */
  	if ((strncmp(mt->mnt_special, "/dev", strlen("/dev")) != 0) &&
  	    (strcmp(mt->mnt_fstype, "lofs") != 0))
  		return (0);
  
  	/*
  	 * Find the special device's entry in the mounts hashtable, allocating
  	 * a new entry if necessary.
  	 */
  	index = hash(cache->hash_size, mt->mnt_special);
  	for (entry = cache->mounts[index]; entry != NULL; entry = entry->next) {
  		if (strcmp(entry->special, mt->mnt_special) == 0)
  			break;
  	}
  	if (entry == NULL) {
  		entry = (hashentry_t *)calloc(1, sizeof (hashentry_t));
  		if ((entry == NULL) ||
  		    ((entry->special = strdup(mt->mnt_special)) == NULL) ||
  		    ((entry->fstype = strdup(mt->mnt_fstype)) == NULL)) {
  			rcm_log_message(RCM_ERROR,
  			    "FILESYS: failed to allocate special device name "
  			    "or filesystem type: (%s).\n", strerror(errno));
  			free_entry(&entry);
  			errno = ENOMEM;
  			return (-1);
  		}
  		entry->next = cache->mounts[index];
  		cache->mounts[index] = entry;
  	}
  
  	/*
  	 * Keep entries in the list of mounts unique, so exit early if the
  	 * mount is already in the list.
  	 */
  	for (index = 0; index < entry->n_mounts; index++) {
  		if (strcmp(entry->mountps[index], mt->mnt_mountp) == 0)
  			return (0);
  	}
  
  	/*
  	 * Add this mountpoint to the list of mounts associated with the
  	 * special device.
  	 */
  	mountps = (char **)realloc(entry->mountps,
  	    (entry->n_mounts + 2) * sizeof (char *));
  	if ((mountps == NULL) ||
  	    ((mountps[entry->n_mounts] = strdup(mt->mnt_mountp)) == NULL)) {
  		rcm_log_message(RCM_ERROR,
  		    "FILESYS: failed to allocate mountpoint name (%s).\n",
  		    strerror(errno));
  		if (entry->n_mounts == 0) {
  			cache->mounts[index] = entry->next;
  			free_entry(&entry);
  		}
  		errno = ENOMEM;
  		return (-1);
  	}
  	mountps[entry->n_mounts + 1] = NULL;
  	entry->n_mounts++;
  	entry->mountps = mountps;
  
  	return (0);
  }
  
  /*
   * cache_lookup()
   *
   *	Searches the cached table of mounts for a special device entry.
   *
   *	Locking: the cache must be locked before calling this function.
   *
   *	Return Value: NULL with errno set if failure, pointer to existing
   *	cache entry when successful.
   */
  static hashentry_t *
  cache_lookup(cache_t *cache, char *rsrc)
  {
  	uint32_t index;
  	hashentry_t *entry;
  
  	/* Only accept valid arguments */
  	if ((cache == NULL) || (cache->mounts == NULL) || (rsrc == NULL)) {
  		errno = EINVAL;
  		return (NULL);
  	}
  
  	/* Search the cached mounts table for the resource's entry */
  	index = hash(cache->hash_size, rsrc);
  	if (cache->mounts[index]) {
  		for (entry = cache->mounts[index]; entry != NULL;
  		    entry = entry->next) {
  			if (strcmp(entry->special, rsrc) == 0)
  				return (entry);
  		}
  	}
  
  	errno = ENOENT;
  	return (NULL);
  }
  
  /*
   * Miscellaneous Functions
   */
  
  /*
   * hash()
   *
   *	A naive hashing function that converts a string 's' to an index in a
   * 	hash table of size 'h'.  It seems to spread entries around well enough.
   */
  static uint32_t
  hash(uint32_t h, char *s)
  {
  	uint32_t sum = 0;
  	unsigned char *byte;
  
  	if ((byte = (unsigned char *)s) != NULL) {
  		while (*byte) {
  			sum += 0x3F & (uint32_t)*byte;
  			byte++;
  		}
  	}
  
  	return (sum % h);
  }
  
  /*
   * register_rsrc()
   *
   *	Registers for any given resource, unless it's "/".
   */
  static void
  register_rsrc(rcm_handle_t *hd, char *rsrc)
  {
  	/* Only accept valid arguments */
  	if ((hd == NULL) || (rsrc == NULL))
  		return;
  
  	/*
  	 * Register any resource other than "/" or "/devices"
  	 */
  	if ((strcmp(rsrc, "/") != 0) && (strcmp(rsrc, "/devices") != 0)) {
  		rcm_log_message(RCM_DEBUG, "FILESYS: registering %s\n", rsrc);
  		if (rcm_register_interest(hd, rsrc, 0, NULL) != RCM_SUCCESS) {
  			rcm_log_message(RCM_WARNING,
  			    "FILESYS: failed to register %s\n", rsrc);
  		}
  	}
  
  }
  
  /*
   * unregister_rsrc()
   *
   *	Unregister a resource.  This does a little filtering since we know
   *	"/" can't be registered, so we never bother unregistering for it.
   */
  static void
  unregister_rsrc(rcm_handle_t *hd, char *rsrc)
  {
  	assert(hd != NULL);
  	assert(rsrc != NULL);
  
  	/* Unregister any resource other than "/" */
  	if (strcmp(rsrc, "/") != 0) {
  		rcm_log_message(RCM_DEBUG, "FILESYS: unregistering %s\n", rsrc);
  		(void) rcm_unregister_interest(hd, rsrc, 0);
  	}
  }
  
  /*
   * create_message()
   *
   *	Given some header strings and a list of dependent names, this
   *	constructs a single string.  If there's only one dependent, the
   *	string consists of the first header and the only dependent appended
   *	to the end of the string enclosed in quotemarks.  If there are
   *	multiple dependents, then the string uses the second header and the
   *	full list of dependents is appended at the end as a comma separated
   *	list of names enclosed in quotemarks.
   */
  static char *
  create_message(char *header, char *header_multi, char **dependents)
  {
  	int i;
  	size_t len;
  	int ndependents;
  	char *msg_buf;
  	char *msg_header;
  	char *separator = MSG_SEPARATOR;
  
  	assert(header != NULL);
  	assert(header_multi != NULL);
  	assert(dependents != NULL);
  
  	/* Count the number of dependents */
  	for (ndependents = 0; dependents[ndependents] != NULL; ndependents++);
  
  	/* If there are no dependents, fail */
  	if (ndependents == 0) {
  		errno = ENOENT;
  		return (NULL);
  	}
  
  	/* Pick the appropriate header to use based on amount of dependents */
  	if (ndependents == 1) {
  		msg_header = header;
  	} else {
  		msg_header = header_multi;
  	}
  
  	/* Compute the size required for the message buffer */
  	len = strlen(msg_header) + 2;	/* +2 for the space and a NULL */
  	for (i = 0; dependents[i] != NULL; i++)
  		len += strlen(dependents[i]) + 2;	/* +2 for quotemarks */
  	len += strlen(separator) * (ndependents - 1);
  
  	/* Allocate the message buffer */
  	if ((msg_buf = (char *)calloc(len, sizeof (char))) == NULL) {
  		rcm_log_message(RCM_ERROR,
  		    "FILESYS: failed to allocate message buffer (%s).\n",
  		    strerror(errno));
  		errno = ENOMEM;
  		return (NULL);
  	}
  
  	/* Fill in the message buffer */
  	(void) snprintf(msg_buf, len, "%s ", msg_header);
  	for (i = 0; dependents[i] != NULL; i++) {
  		(void) strlcat(msg_buf, "\"", len);
  		(void) strlcat(msg_buf, dependents[i], len);
  		(void) strlcat(msg_buf, "\"", len);
  		if ((i + 1) < ndependents)
  			(void) strlcat(msg_buf, separator, len);
  	}
  
  	return (msg_buf);
  }
  
  /*
   * create_dependents()
   *
   *	Creates a copy of the list of dependent mounts associated with a
   *	given hashtable entry from the cache.
   *
   *	Return Values: NULL with errno set on failure, the resulting list of
   *	dependent resources when successful.
   */
  static char **
  create_dependents(hashentry_t *entry)
  {
  	int i;
  	char **dependents;
  
  	if (entry == NULL) {
  		errno = EINVAL;
  		return (NULL);
  	}
  
  	if (entry->n_mounts == 0) {
  		errno = ENOENT;
  		return (NULL);
  	}
  
  	/* Allocate space for the full dependency list */
  	dependents = (char **)calloc(entry->n_mounts + 1, sizeof (char *));
  	if (dependents == NULL) {
  		rcm_log_message(RCM_ERROR,
  		    "FILESYS: failed to allocate dependents (%s).\n",
  		    strerror(errno));
  		errno = ENOMEM;
  		return (NULL);
  	}
  
  	/* Copy all the dependent names into the new list of dependents */
  	for (i = 0; i < entry->n_mounts; i++) {
  		if ((dependents[i] = strdup(entry->mountps[i])) == NULL) {
  			rcm_log_message(RCM_ERROR,
  			    "FILESYS: failed to allocate dependent \"%s\" "
  			    "(%s).\n", entry->mountps[i], strerror(errno));
  			free_list(dependents);
  			errno = ENOMEM;
  			return (NULL);
  		}
  	}
  
  	return (dependents);
  }
  
  /*
   * detect_critical_failure()
   *
   *	Given a list of dependents, a place to store an error message, and
   *	the flags associated with an operation, this function detects whether
   *	or not the operation should fail due to the presence of any critical
   *	filesystem resources.  When a failure is detected, an appropriate
   *	error message is constructed and passed back to the caller.  This is
   *	called during a suspend request operation.
   *
   *	Return Values: 0 when a critical resource failure shouldn't prevent
   *	the operation, and 1 when such a failure condition does exist.
   */
  static int
  detect_critical_failure(char **errorp, uint_t flags, char **dependents)
  {
  	int i;
  	int n_critical;
  	char *tmp;
  
  	/* Do nothing if the operation is forced or there are no dependents */
  	if ((errorp == NULL) || (flags & RCM_FORCE) || (dependents == NULL))
  		return (0);
  
  	/*
  	 * Count how many of the dependents are critical, and shift the
  	 * critical resources to the head of the list.
  	 */
  	if (dependents) {
  		for (i = 0, n_critical = 0; dependents[i] != NULL; i++) {
  			if (is_critical(dependents[i])) {
  				if (n_critical != i) {
  					tmp = dependents[n_critical];
  					dependents[n_critical] = dependents[i];
  					dependents[i] = tmp;
  				}
  				n_critical++;
  			}
  		}
  	}
  
  	/* If no criticals were found, do nothing and return */
  	if (n_critical == 0)
  		return (0);
  
  	/*
  	 * Criticals were found.  Prune the list appropriately and construct
  	 * an error message.
  	 */
  
  	/* Prune non-criticals out of the list */
  	for (i = n_critical; dependents[i] != NULL; i++) {
  		free(dependents[i]);
  		dependents[i] = NULL;
  	}
  
  	/* Construct the critical resource error message */
  	*errorp = create_message(MSG_HDR_CRIT, MSG_HDR_CRIT_MULTI, dependents);
  
  	return (1);
  }
  
  /*
   * is_critical()
   *
   *	Test a resource to determine if it's critical to the system and thus
   *	cannot be suspended.
   *
   *	Return Values: 1 if the named resource is critical, 0 if not.
   */
  static int
  is_critical(char *rsrc)
  {
  	assert(rsrc != NULL);
  
  	if ((strcmp(rsrc, "/") == 0) ||
  	    (strcmp(rsrc, "/usr") == 0) ||
  	    (strcmp(rsrc, "/lib") == 0) ||
  	    (strcmp(rsrc, "/usr/lib") == 0) ||
  	    (strcmp(rsrc, "/bin") == 0) ||
  	    (strcmp(rsrc, "/usr/bin") == 0) ||
  	    (strcmp(rsrc, "/tmp") == 0) ||
  	    (strcmp(rsrc, "/var") == 0) ||
  	    (strcmp(rsrc, "/var/run") == 0) ||
  	    (strcmp(rsrc, "/etc") == 0) ||
  	    (strcmp(rsrc, "/etc/mnttab") == 0) ||
  	    (strcmp(rsrc, "/platform") == 0) ||
  	    (strcmp(rsrc, "/usr/platform") == 0) ||
  	    (strcmp(rsrc, "/sbin") == 0) ||
  	    (strcmp(rsrc, "/usr/sbin") == 0))
  		return (1);
  
  	return (0);
  }
  
  
  /*
   * use_cache()
   *
   *	This routine handles all the tasks necessary to lookup a resource
   *	in the cache and extract a separate list of dependents for that
   *	entry.  If an error occurs while doing this, an appropriate error
   *	message is passed back to the caller.
   *
   *	Locking: the cache is locked for the whole duration of this function.
   */
  static int
  use_cache(char *rsrc, char **errorp, char ***dependentsp)
  {
  	hashentry_t *entry;
  
  	(void) mutex_lock(&cache_lock);
  	if ((entry = cache_lookup(mnt_cache, rsrc)) == NULL) {
  		rcm_log_message(RCM_ERROR,
  		    "FILESYS: failed looking up \"%s\" in cache (%s).\n",
  		    rsrc, strerror(errno));
  		*errorp = strdup(MSG_FAIL_INTERNAL);
  		(void) mutex_unlock(&cache_lock);
  		return (-1);
  	}
  	*dependentsp = create_dependents(entry);
  	(void) mutex_unlock(&cache_lock);
  
  	return (0);
  }
  
  /*
   * prune_dependents()
   *
   *	Before calling back into RCM with a list of dependents, the list
   *	must be cleaned up a little.  To avoid infinite recursion, "/" and
   *	the named resource must be pruned out of the list.
   */
  static void
  prune_dependents(char **dependents, char *rsrc)
  {
  	int i;
  	int n;
  
  	if (dependents) {
  
  		/* Set 'n' to the total length of the list */
  		for (n = 0; dependents[n] != NULL; n++);
  
  		/*
  		 * Move offending dependents to the tail of the list and
  		 * then truncate the list.
  		 */
  		for (i = 0; dependents[i] != NULL; i++) {
  			if ((strcmp(dependents[i], rsrc) == 0) ||
  			    (strcmp(dependents[i], "/") == 0)) {
  				free(dependents[i]);
  				dependents[i] = dependents[n - 1];
  				dependents[n] = NULL;
  				i--;
  				n--;
  			}
  		}
  	}
  }
  