OpenSolaris_b135/lib/mms/mgmt/common/mgmt_util.c

Compare this file to the similar file:
Show the results in this format:

/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License (the "License").
 * You may not use this file except in compliance with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */
/*
 * Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

static char *_SrcFile = __FILE__; /* Using __FILE__ makes duplicate strings */

/*
 * various utility functions
 */

#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/param.h>
#include <sys/wait.h>
#include <unistd.h>
#include <string.h>
#include <stdarg.h>
#include <time.h>
#include <sys/mnttab.h>
#include <libgen.h>
#include <pwd.h>
#include <grp.h>
#include <ctype.h>
#include <fcntl.h>
#include <unistd.h>
#include <procfs.h>
#include <dirent.h>
#include <auth_attr.h>
#include <secdb.h>

#include "mgmt_util.h"
#include "mmp_defs.h"
#include "mms_cfg.h"

/* forward declarations */
static int file_chown(char *file, struct stat64 *statbuf, char *user,
	char *group);
static int file_chown_id(char *file, struct stat64 *statbuf, uid_t uid,
	gid_t gid);
static void
filter_on_var(char *varname, char **varray, int count, nvlist_t *nvl);

/* error messages */
static mms_sym_t	mms_mgmt_errs[] = {
	"Internal error; missing argument",
	MMS_MGMT_NOARG,
	"Could not exec ACSLS ssi daemon",
	MMS_MGMT_ERR_EXEC_SSI,
	"Could not communicate with ACSLS server",
	MMS_MGMT_ERR_ACSLS_COMM,
	"Received invalid response from ACSLS server",
	MMS_MGMT_ERR_ACSLS_RSP,
	"Could not parse response from ACSLS server",
	MMS_MGMT_ERR_ACSLS_PARSE,
	"Missing required option",
	MMS_MGMT_ERR_REQUIRED,
	"Could not determine MM host",
	MMS_MGMT_NO_MMHOST,
	"Volume in use",
	MMS_MGMT_CARTRIDGE_INUSE,
	"Could not access database backup",
	MMS_MGMT_DBDUMP_MISSING,
	"Unknown response type",
	MMS_MGMT_RSP_UNKNOWN,
	"Request cancelled",
	MMS_MGMT_RSP_CANCELLED,
	"Request not accepted",
	MMS_MGMT_REQ_NOT_ACCEPTED,
	"Could not determine group",
	MMS_MGMT_ERR_GROUP,
	"Could not determine user",
	MMS_MGMT_ERR_USER,
	"Option applies to MM server host only",
	MMS_MGMT_ERR_SVRONLY,
	"Volume not unique",
	MMS_MGMT_ERR_CART_NOT_UNIQUE,
	"Partition not unique",
	MMS_MGMT_ERR_PARTITION_NOT_UNIQUE,
	"Volume not labeled",
	MMS_MGMT_VOL_NOT_INIT,
	"No usable volume found",
	MMS_MGMT_NO_USABLE_VOL,
	"Could not find ACSLS client libraries",
	MMS_MGMT_ACSLS_NOT_FOUND,
	"MMS is not initialized or not running",
	MMS_MGMT_MMS_NOT_INIT,
	"Drives on remote systems cannot be configured at this time",
	MMS_MGMT_REMOTE_NOT_SUPP,
	"Operation requires a password",
	MMS_MGMT_PASSWORD_REQUIRED,
	"Volume not mounted",
	MMS_MGMT_VOL_NOT_MOUNTED,
	"Not authorized.  Use the correct application/password combination.",
	MMS_MGMT_NOT_AUTHORIZED,
	"Password validation failed",
	MMS_MGMT_PASSWD_MISMATCH,
	"Failed to get the password",
	MMS_MGMT_GETPASS_FAILED,
	"Password must be 8 characters or longer.",
	MMS_MGMT_PASSTOOSHORT,
	"Internal error:  MMP parsing failed",
	MMS_MGMT_MMP_PARSE_ERR,
	"Application is still using one or more volumes.",
	MMS_MGMT_APP_VOLS_EXIST,
	"Not a valid MMS database backup file",
	MMS_MGMT_NOT_DBFILE,
	"Database Administrator user account not found",
	MMS_MGMT_DB_USER_NOTFOUND,
	"Default DISK lib path invalid",
	MMS_MGMT_INVALID_PATH,
	"Invalid hostpath format. Must be hostname@path",
	MMS_MGMT_INV_HOSTPATH,
	"Library already exists",
	MMS_MGMT_LIB_EXISTS,
	"Library does not exist",
	MMS_MGMT_LIB_NOT_EXIST,
	"Must specify hardware type",
	MMS_MGMT_NO_HWTYPE,
	"Must specify volume type",
	MMS_MGMT_NO_VOLTYPE,
	"Cannot get library DefaultPath",
	MMS_MGMT_DFLTPATH_ERR,
	"dpool does not exist. Create it first.",
	MMS_MGMT_DG_NOT_EXIST,
	"mpool does not exist. Create it first.",
	MMS_MGMT_CG_NOT_EXIST,
	"voltype does not exist. Create it first.",
	MMS_MGMT_SHOW_CT_ERR,
	"show voltype error",
	MMS_MGMT_CT_NOT_EXIST,
	"voltype does not exist. Create it first",
	MMS_MGMT_SN_ERR,
	"missing required arguement",
	MMS_MGMT_REQ_ARG,
	"create PARTITION error",
	MMS_MGMT_CREATE_PART_ERR,
	"create CARTRIDGE error",
	MMS_MGMT_CREATE_CART_ERR,
	"invalid readonly value",
	MMS_MGMT_INVALID_READONLY,
};

/*
 * mms_gen_taskid()
 *
 * Parameters:
 *      - tid           unique task identifier.
 *
 * This function returns a task identifier (TID). All responses to an MMP
 * command will include the TID of the initiating command. The TID will be
 * unique in the context of a session so that the client can determine which
 * responses go with which command.
 */
int
mms_gen_taskid(char *tid)
{
	if (!tid) {
		return (MMS_MGMT_NOARG);
	}

	(void) sprintf(tid, "%d-%ld", (int)getpid(), time(NULL));

	return (0);
}

/*
 * create the directory dir. This function will not fail if the directory
 * already exists.
 */
int
create_dir(char *dir, mode_t perms, char *user, uid_t uid, char *group,
	gid_t gid)
{

	struct stat64	dir_stat;
	int		st = 0;

	if (!dir) {
		return (MMS_MGMT_NOARG);
	}

	if (perms == 0) {
		/* make the dir rwx by owner, and rx by other and group */
		perms = 0 | S_IRWXU | S_IROTH | S_IXOTH | S_IRGRP | S_IXGRP;
	}

	mms_trace(MMS_INFO, "creating directory %s", dir);
	errno = 0;
	if (stat64(dir, &dir_stat) == 0) {
		if (!S_ISDIR(dir_stat.st_mode)) {

			/* TBD: set errno and errmsg */
			return (ENOTDIR);
		}
	} else if (errno == ENOENT) {
		errno = 0;
		st = mkdirp(dir, perms);
		if (st != 0) {
			return (errno);
		}
		(void) stat64(dir, &dir_stat);
	}

	if (uid == 0) {
		st = file_chown(dir, &dir_stat, user, group);
	} else {
		st = file_chown_id(dir, &dir_stat, uid, gid);
	}

	return (st);
}

static int
file_chown(char *file, struct stat64 *statbuf, char *user, char *group)
{
	struct passwd	pwd;
	struct passwd	*pwdp;
	struct group	gr;
	struct group	*grp;
	char		buf[1024];
	uid_t		uid = 0;
	gid_t		gid = 0;
	int		st;

	if ((file == NULL) || (statbuf == NULL)) {
		return (MMS_MGMT_NOARG);
	}

	if (user == NULL) {
		/* nothing to do */
		return (0);
	}

	(void) getpwnam_r(user, &pwd, buf, sizeof (buf), &pwdp);
	if (pwdp == NULL) {
		return (MMS_MGMT_ERR_USER);
	}
	uid = pwdp->pw_uid;
	gid = pwdp->pw_gid;	/* default to user's default group */

	if (group != NULL) {
		(void) getgrnam_r(group, &gr, buf, sizeof (buf), &grp);
		if (grp == NULL) {
			return (MMS_MGMT_ERR_GROUP);
		}
		gid = grp->gr_gid;
	}

	st = file_chown_id(file, statbuf, uid, gid);

	return (st);
}

static int
file_chown_id(char *file, struct stat64 *statbuf, uid_t uid, gid_t gid)
{
	int	st;

	if ((file == NULL) || (statbuf == NULL)) {
		return (MMS_MGMT_NOARG);
	}

	if ((uid == 0) && (gid == 0)) {
		/* nothing to do */
		return (0);
	}

	if ((statbuf->st_uid == uid) && (statbuf->st_gid == gid)) {
		/* nothing to do */
		return (0);
	}

	st = chown(file, uid, gid);

	return (st);
}

int cp_file(
	const char *old,
	const char *new)
{
	int		oldfd = -1;
	int		newfd = -1;
	struct stat	oldstatbuf;
	struct stat	newstatbuf;
	int		res;
	int		saverr = 0;
	struct timeval	oldtimes[2];
	char		buf[8192];
	int		wlen;
	ssize_t		oldlen;

	mms_trace(MMS_DEBUG, "copying file %s to %s", old, new);

	/* make sure old exists */
	res = stat(old, &oldstatbuf);
	if (res != 0) {
		return (errno);
	}

	/* if the target exists, remove it */
	res = stat(new, &newstatbuf);
	if (res == 0) {
		mms_trace(MMS_DEBUG, "cp_file: removing %s", new);
		(void) unlink(new);
	}

	/* save the access & mod times so they can be reset */
	oldtimes[0].tv_sec = oldstatbuf.st_atim.tv_sec;
	oldtimes[0].tv_usec = oldstatbuf.st_atim.tv_nsec / 1000;
	oldtimes[1].tv_sec = oldstatbuf.st_mtim.tv_sec;
	oldtimes[1].tv_usec = oldstatbuf.st_mtim.tv_nsec / 1000;

	oldfd = open(old, O_RDONLY, oldstatbuf.st_mode);
	if (oldfd == -1) {
		res = errno;
		mms_trace(MMS_ERR, "Error opening %s, %d", old, res);
		return (res);
	}
	newfd = open(new, O_WRONLY|O_CREAT|O_EXCL, oldstatbuf.st_mode);
	if (newfd == -1) {
		res = errno;
		(void) close(oldfd);
		mms_trace(MMS_ERR, "Error opening %s, %d", new, res);
		return (res);
	}

	/* finally, copy the file */
	res = 0;
	oldlen = oldstatbuf.st_size;

	while (oldlen > 0) {
		if (oldlen < 8192) {
			wlen = oldlen;
		} else {
			wlen = 8192;
		}

		res = readbuf(oldfd, buf, wlen);
		if (res == -1) {
			saverr = errno;
			mms_trace(MMS_ERR, "Error reading file %s, %d",
			    old, saverr);
			break;
		}

		res = write_buf(newfd, buf, wlen);
		if (res == -1) {
			saverr = errno;
			mms_trace(MMS_ERR, "Error writing file %s, %d",
			    new, saverr);
			break;
		}

		oldlen -= wlen;
	}

	(void) close(newfd);
	(void) close(oldfd);

	/* set acccess & modify times to match original file */
	if (saverr == 0) {
		(void) utimes(new, oldtimes);
		(void) utimes(old, oldtimes);
		res = 0;
	} else {
		res = saverr;
	}

	return (res);
}

/* helper function to use read() correctly */
int
readbuf(int fd, void* buffer, int len)
{
	int	numread = 0;
	int	ret;
	char	*bufp;

	if ((buffer == NULL) || (len < 1) || (fd == -1)) {
		return (-1);
	}

	bufp = buffer;

	while (numread < len) {
		ret = read(fd, bufp, (len - numread));

		if (ret == 0) {
			/* reached EOF */
			break;
		} else if (ret == -1) {
			if (errno == EAGAIN) {
				continue;
			}
			numread = -1;
			break;
		}

		numread += ret;
		bufp += ret;
	}

	return (numread);
}

/* helper function to use write() correctly */
int
write_buf(int fd, void* buffer, int len)
{
	int	written = 0;
	int	ret;
	char	*bufp;

	if ((buffer == NULL) || (fd == -1)) {
		return (-1);
	}

	bufp = buffer;

	while (written < len) {
		ret = write(fd, bufp, (len - written));

		if (ret == -1) {
			if (errno == EAGAIN) {
				continue;
			}
			written = -1;
			break;
		}

		written += ret;
		bufp += written;
	}

	return (written);
}

/*
 * mk_wc_path()
 *
 * Function to generate a path name for working copies of
 * files and creates the file.
 */
int
mk_wc_path(
	char *original, 	/* IN - path to original file */
	char *tmppath, 		/* IN/OUT - buffer to hold new file path */
	size_t buflen)		/* IN - length of buffer */
{
	char		*copypath;
	char		template[MAXPATHLEN+1];
	char		buf[MAXPATHLEN+1];
	char		*fname;
	int		ret;
	struct stat64	statbuf;

	if (!original || !tmppath) {
		return (MMS_MGMT_NOARG);
	}

	/* make sure target directory exists */
	ret = create_dir(default_tmpfile_dir, 0, NULL, geteuid(),
	    NULL, getegid());
	if (ret != 0) {
		return (ret);
	}

	ret = stat64(original, &statbuf);

	/*
	 * not an error if the original doesn't exist.  In this
	 * case, dummy up a mode to be used for the later mknod.
	 */
	if (ret != 0) {
		statbuf.st_mode = S_IFREG;
		statbuf.st_mode |= S_IRWXU|S_IRGRP|S_IROTH;
	}

	/* create the template name */
	(void) strlcpy(buf, original, MAXPATHLEN+1);
	fname = basename(buf);
	(void) snprintf(template, MAXPATHLEN+1, "%s/%s_XXXXXX",
	    default_tmpfile_dir, fname);

	copypath = mktemp(template);

	if (copypath == NULL) {
		return (-1);
	} else {
		(void) strlcpy(tmppath, copypath, buflen);
	}

	/* make sure an old version isn't hanging around */
	(void) unlink(tmppath);

	/* create the target file */
	ret = mknod(tmppath, statbuf.st_mode, 0);

	return (ret);
}

/*
 * make_working_copy()
 *
 * Copies a file to the default temporary location and returns
 * the pathname of the copy.
 *
 */
int
make_working_copy(char *path, char *wc_path, int pathlen)
{
	int		ret;

	if (!path || !wc_path) {
		return (-1);
	}

	ret = mk_wc_path(path, wc_path, pathlen);
	if (ret != 0) {
		return (ret);
	}

	ret = cp_file(path, wc_path);

	return (ret);
}


typedef struct proclist proclist_t;
struct proclist {
	mms_list_node_t	next_proc;
	psinfo_t	*proc;
};

/*
 * The find_process() function reads through /proc and finds all processes with
 * the specified executable name.
 *
 * PARAM
 * exename	- INPUT - 	name of executable
 * procs	- OUTPUT - 	list of psinfo_t structs
 *
 * ERRORS
 */
int
find_process(char *exename, mms_list_t *procs)
{
	DIR		*dirp;
	dirent64_t	*dent;
	dirent64_t	*dentp;
	char		pname[MAXPATHLEN];
	char		*ptr;
	int		procfd;	/* filedescriptor for /proc/nnnnn/psinfo */
	psinfo_t 	info;	/* process information from /proc */
	int		ret = 0;
	int		len = sizeof (info);

	if (!exename || !procs) {
		return (MMS_MGMT_NOARG);
	}

	mms_trace(MMS_INFO, "finding all %s processes", exename);

	if ((dirp = opendir(PROCDIR)) == NULL) {
		ret = errno;
		mms_trace(MMS_ERR, "Could not open %s, %d", PROCDIR, ret);
		return (ret);
	}

	mms_list_create(procs, sizeof (proclist_t),
	    offsetof(proclist_t, next_proc));

	/* allocate the dirent structure */
	dent = malloc(MAXPATHLEN + sizeof (dirent64_t));
	if (dent == NULL) {
		(void) closedir(dirp);
		mms_list_destroy(procs);
		return (ENOMEM);
	}

	/* find each active process --- */
	while ((ret = readdir64_r(dirp, dent, &dentp)) == 0) {

		if (dentp == NULL) {
			break;
		}

		/* skip . and .. */
		if (dentp->d_name[0] == '.') {
			continue;
		}

		(void) snprintf(pname, MAXPATHLEN, "%s/%s/%s", PROCDIR,
		    dentp->d_name, "psinfo");

		procfd = open64(pname, O_RDONLY);
		if (procfd == -1) {
			/* process may have ended while we were processing */
			continue;
		}

		/*
		 * Get the info structure for the process and close quickly.
		 */
		ret = readbuf(procfd, &info, len);

		(void) close(procfd);

		if (ret == -1) {
			break;
		}

		if (info.pr_lwp.pr_state == 0)		/* can't happen? */
			continue;

		/* ensure cmd buffers properly terminated */
		info.pr_psargs[PRARGSZ-1] = '\0';
		info.pr_fname[PRFNSZ-1] = '\0';

		/* is it the proc we're looking for? */
		if (strncmp(info.pr_psargs, exename, strlen(exename)) != 0) {
			continue;
		}

		ptr = malloc(len);
		if (ptr == NULL) {
			ret = ENOMEM;
			break;
		}
		(void) memcpy(ptr, &info, len);
		mms_list_insert_tail(procs, ptr);
	}

	(void) closedir(dirp);
	free(dent);
	return (ret);
}

/*
 * exec_mgmt_cmd()
 *
 * Helper functino to exec an external program, optionally returning
 * messages written to stdout/stderr and exec()ing as a different UID.
 *
 * The 'cmd' array must have the executable as the first entry, and *must*
 * have a NULL as the last entry.
 */
int
exec_mgmt_cmd(
	FILE		**outstr,
	FILE		**errstr,
	uid_t		euid,
	gid_t		egid,
	boolean_t	daemon,
	char		*cmd[])
{
	int		fdo[2] = {-1, -1}; /* pipe for reading stdout */
	int		fde[2] = {-1, -1}; /* pipe for reading stderr */
	pid_t		pid;

	/* The path to the executable must be fully-qualified */
	if ((cmd == NULL) || (cmd[0] == NULL) || (cmd[0][0] != '/')) {
		mms_trace(MMS_DEBUG,
		    "validate error");
		return (-1);
	}

	if (outstr != NULL) {
		if (pipe(fdo) < 0) {
			mms_trace(MMS_DEBUG,
			    "pipe(fdo) error");
			return (-1);
		}
	}

	if (errstr != NULL) {
		if (pipe(fde) < 0) {
			mms_trace(MMS_DEBUG,
			    "pipe(fde) error");
			(void) close(fdo[0]);
			(void) close(fdo[1]);
			return (-1);
		}
	}

	if ((pid = fork()) < 0) {
		mms_trace(MMS_DEBUG,
		    "fork() error");
		(void) close(fdo[0]);
		(void) close(fdo[1]);
		(void) close(fde[0]);
		(void) close(fde[1]);
		return (-1);
	}

	if (pid == 0) {		/* child */
		/* redirect stdout and stderr */
		int	ret;

		if (!outstr) {
			fdo[1] = open("/dev/null", O_WRONLY);
		}

		if (!errstr) {
			fde[1] = open("/dev/null", O_WRONLY);
		}

		if ((fde[1] == -1) || (fdo[1] == -1)) {
			mms_trace(MMS_DEBUG,
			    "(fde[1] == -1) || (fdo[1] == -1) error");
			exit(9);
		}

		(void) dup2(fdo[1], STDOUT_FILENO);
		(void) dup2(fde[1], STDERR_FILENO);

		(void) close(fdo[0]);
		(void) close(fde[0]);

		(void) close(STDIN_FILENO);

		(void) closefrom(3);

		/* set UID if requested */
		if (euid != 0) {
			(void) setuid(euid);
		}

		if (egid != 0) {
			(void) setgid(egid);
		}

		if (daemon) {
			(void) setsid();	 /* become session leader */
			pid = fork();
			if (pid < 0) {
				exit(1);
			} else if (pid > 0) {
				/* parent */
				exit(0);
			}
		}

		ret = execv(cmd[0], cmd);

		if (0 != ret) {
			mms_trace(MMS_DEBUG,
			    "execv(cmd[0], cmd) error");
			return (ret);
		}
	}

	/* parent */
	if (outstr) {
		(void) close(fdo[1]);
		*outstr = fdopen(fdo[0], "r");
	}

	if (errstr) {
		(void) close(fde[1]);
		*errstr = fdopen(fde[0], "r");
	}

	return (pid);
}

/* configuration functions */
void
mgmt_unsetall_cfgvar(void)
{
	(void) mms_cfg_unsetvar(MMS_CFG_CONFIG_TYPE);
	(void) mms_cfg_unsetvar(MMS_CFG_MGR_HOST);
	(void) mms_cfg_unsetvar(MMS_CFG_MGR_PORT);
	(void) mms_cfg_unsetvar(MMS_CFG_SSL_ENABLED);
	(void) mms_cfg_unsetvar(MMS_CFG_SSL_CERT_FILE);
	(void) mms_cfg_unsetvar(MMS_CFG_SSL_PASS_FILE);
	(void) mms_cfg_unsetvar(MMS_CFG_SSL_DH_FILE);
	(void) mms_cfg_unsetvar(MMS_CFG_SSL_CRL_FILE);
	(void) mms_cfg_unsetvar(MMS_CFG_SSL_PEER_FILE);
	(void) mms_cfg_unsetvar(MMS_CFG_SSL_CIPHER);
	(void) mms_cfg_unsetvar(MMS_CFG_SSL_VERIFY);
	(void) mms_cfg_unsetvar(MMS_CFG_DB_DATA);
	(void) mms_cfg_unsetvar(MMS_CFG_DB_LOG);
	(void) mms_cfg_unsetvar(MMS_CFG_MM_DB_HOST);
	(void) mms_cfg_unsetvar(MMS_CFG_MM_DB_PORT);
	(void) mms_cfg_unsetvar(MMS_CFG_MM_DB_NAME);
	(void) mms_cfg_unsetvar(MMS_CFG_MM_DB_USER);
	(void) mms_cfg_unsetvar(MMS_CFG_SSI_PATH);
}

int
mgmt_set_svc_state(
	char		*fmri,
	mms_svcstate_t	targetState,
	char		**original)
{
	char		*startState = NULL;
	char		*endState = NULL;
	int		st = 0;
	const char	*cmpState;
	int		i;

	if (fmri == NULL) {
		mms_trace(MMS_ERR, "fmri is null");
		return (MMS_MGMT_NOARG);
	}

	startState = smf_get_state(fmri);
	if (startState == NULL) {
		st = scf_error();
		mms_trace(MMS_ERR, "get state %s - %s", fmri, scf_strerror(st));
		/*
		 * Not an error if request to disable or degrade a
		 * non-existent svc
		 */
		if ((targetState == DISABLE) || (targetState == DEGRADE)) {
			if (st == SCF_ERROR_NOT_FOUND) {
				st = 0;
			}
		}
		return (st);
	}

	if (original != NULL) {
		*original = startState;
	}

	switch (targetState) {
		case ENABLE:
			cmpState = SCF_STATE_STRING_ONLINE;
			if (strcmp(startState, cmpState) != 0) {
				st = smf_enable_instance(fmri, 0);
			}
			break;
		case DISABLE:
			cmpState = SCF_STATE_STRING_DISABLED;
			/*
			 * can't go directly from maintenance to disabled,
			 * though I can't see why.
			 */
			if (strcmp(startState, SCF_STATE_STRING_MAINT) == 0) {
				mms_trace(MMS_DEBUG,
				    "restore before disable %s",
				    fmri);
				st = mgmt_set_svc_state(fmri, RESTORE, NULL);
				if (st != 0) {
					mms_trace(MMS_ERR,
					    "failed to restore %s",
					    fmri);
				}
				st = smf_disable_instance(fmri, 0);
			}
			if (strcmp(startState, cmpState) != 0) {
				st = smf_disable_instance(fmri, 0);
			}
			break;
		case REFRESH:
			/* refresh shouldn't change the current state */
			cmpState = startState;
			st = smf_refresh_instance(fmri);
			break;
		case RESTART:
			cmpState = SCF_STATE_STRING_ONLINE;
			st = smf_restart_instance(fmri);
			break;
		case MAINTAIN:
			cmpState = SCF_STATE_STRING_MAINT;
			st = smf_maintain_instance(fmri, SMF_IMMEDIATE);
			break;
		case DEGRADE:
			/* only available if 'online' */
			if (strcmp(startState, SCF_STATE_STRING_ONLINE) == 0) {
				cmpState = SCF_STATE_STRING_DEGRADED;
				st = smf_degrade_instance(fmri, 0);
			} else {
				cmpState = startState;
			}
			break;
		case RESTORE:
			/*
			 * if disabled, returns to online.  If maintenance,
			 * returns to disabled.
			 */
			if (strcmp(startState, SCF_STATE_STRING_DISABLED)
			    == 0) {
				cmpState = SCF_STATE_STRING_ONLINE;
			} else if (strcmp(startState, SCF_STATE_STRING_MAINT)
			    == 0) {
				cmpState = SCF_STATE_STRING_DISABLED;
			} else {
				/* invalid operation */
				st = EINVAL;
				break;
			}
			st = smf_restore_instance(fmri);
			break;
		default:
			st = -1;
			mms_trace(MMS_ERR, "%s unknown action %d",
			    fmri, targetState);
			break;
	}

	if (st == 0) {
		/*
		 * Changing state sometimes takes a while and
		 * the scf service state functions do not wait.
		 * Check to see if the action was successul.
		 */
		st = 1;
		for (i = 0; i < 10; i++) {
			endState = smf_get_state(fmri);
			if (endState == NULL) {
				st = scf_error();
				mms_trace(MMS_ERR,
				    "wait for state change %s - %s",
				    fmri, scf_strerror(st));
				break;
			} else if (strcmp(endState, cmpState) == 0) {
				st = 0;
				break;
			}
			free(endState);
			endState = NULL;
			(void) sleep(1);
		}
	} else if (st != -1) {
		st = scf_error();
		mms_trace(MMS_ERR, "state change %s - %s",
		    fmri, scf_strerror(st));
	}

	if ((startState != NULL) && (original == NULL)) {
		free(startState);
	}
	if (endState != NULL) {
		free(endState);
	}

	return (st);
}

int
check_exit(pid_t pid, int *signo)
{
	pid_t	wpid;
	int	pst;
	int	st = EINVAL;

	if (pid == (pid_t)-1) {
		return (st);
	}

	wpid = waitpid(pid, &pst, 0);

	if (wpid != pid) {
		st = errno;
	} else {
		if (WIFEXITED(pst)) {
			st = WEXITSTATUS(pst);
		} else if (WIFSIGNALED(pst)) {
			st = EINTR;
			if (signo) {
				*signo = WTERMSIG(pst);
			}
		} else if (WCOREDUMP(pst)) {
			st = EINTR;
			if (signo) {
				*signo = SIGSEGV;
			}
		}
	}
	return (st);
}

/* Helper function for MMS lists */
void
mms_list_free_and_destroy(mms_list_t *list, void (*free_func)(void *))
{
	mms_list_node_t	*node;

	if (!list || !free_func || (list->list_size == 0)) {
		return;
	}

	while (! mms_list_empty(list)) {
		node = mms_list_head(list);

		mms_list_remove(list, node);
		free_func(node);
	}

	mms_list_destroy(list);
}

/* helper functions to validate option values */
int
val_numonly(char *val)
{
	int	st = 0;
	char	*bufp;

	if (!val) {
		return (MMS_MGMT_NOARG);
	}

	for (bufp = val; *bufp != '\0'; bufp++) {
		if (*bufp == '-') {
			/* negative number */
			continue;
		}
		if (!isdigit(*bufp)) {
			st = EINVAL;
			break;
		}
	}

	return (st);
}

int
val_passwd(char *val)
{
	if (!val) {
		return (MMS_MGMT_NOARG);
	} else if (strlen(val) < 8) {
		return (EINVAL);
	} else {
		return (0);
	}
}

int
val_objtype(char *val)
{
	if (!val) {
		return (MMS_MGMT_NOARG);
	}

	if ((strcmp(val, "client") == 0) ||
	    (strcmp(val, "server") == 0) ||
	    (strcmp(val, "library") == 0) ||
	    (strcmp(val, "drive") == 0) ||
	    (strcmp(val, "dpool") == 0) ||
	    (strcmp(val, "mpool") == 0) ||
	    (strcmp(val, "app") == 0) ||
	    (strcmp(val, "alarm") == 0) ||
	    (strcmp(val, "vol") == 0) ||
	    (strcmp(val, "voltype") == 0)) {
		return (0);
	} else {
		return (EINVAL);
	}
}

int
val_path(char *val)
{
	if (!val) {
		return (MMS_MGMT_NOARG);
	}

	if (*val != '/') {
		return (EINVAL);
	}

	return (0);
}

int
val_level(char *val)
{
	if (!val) {
		return (MMS_MGMT_NOARG);
	}

	if ((strcmp(val, "emergency") == 0) ||
	    (strcmp(val, "alert") == 0) ||
	    (strcmp(val, "critical") == 0) ||
	    (strcmp(val, "error") == 0) ||
	    (strcmp(val, "warning") == 0) ||
	    (strcmp(val, "notice") == 0) ||
	    (strcmp(val, "information") == 0) ||
	    (strcmp(val, "debug") == 0)) {
		return (0);
	} else {
		return (EINVAL);
	}
}

int
val_yesno(char *val)
{
	if (!val) {
		return (MMS_MGMT_NOARG);
	}

	if (*val == 'y' || *val == 'Y' || *val == 'n' || *val == 'N') {
		return (0);
	} else {
		return (EINVAL);
	}
}

int
val_truefalse(char *val)
{
	if (!val) {
		return (MMS_MGMT_NOARG);
	}

	if ((strcmp(val, "true") == 0) || (strcmp(val, "false") == 0)) {
		return (0);
	} else {
		return (EINVAL);
	}
}

int
val_mms_size(char *val)
{
	return (do_val_mms_size(val, NULL));
}

int
do_val_mms_size(char *val, uint64_t *bytes)
{
	uint64_t	sz = 0;
	char		*unit = NULL;
	uint64_t	mult = 1;

	if (!val) {
		return (MMS_MGMT_NOARG);
	}

	sz = strtoll(val, &unit, 10);
	if ((sz == LONG_MAX) || (sz == LONG_MIN)) {
		return (EINVAL);
	}

	if (unit) {
		switch (*unit) {
			case 'b':
			case 'B':
				mult = 1;
				break;
			case 'k':
			case 'K':
				mult = KILO;
				break;
			case 'm':
			case 'M':
				mult = MEGA;
				break;
			case 'g':
			case 'G':
				mult = GIGA;
				break;
			case 't':
			case 'T':
				mult = TERA;
				break;
			case 'p':
			case 'P':
				mult = PETA;
				break;
			default:
				return (EINVAL);
				break;
		}
	}

	if (bytes) {
		*bytes = sz * mult;
	}

	return (0);
}

int
val_density(char *val)
{
	if (!val) {
		return (MMS_MGMT_NOARG);
	}

	if ((strcmp(val, "den_9840C") == 0) ||
	    (strcmp(val, "den_9840") == 0) ||
	    (strcmp(val, "den_LTO4") == 0) ||
	    (strcmp(val, "den_LTO3") == 0) ||
	    (strcmp(val, "den_LTO2") == 0) ||
	    (strcmp(val, "den_LTO1") == 0)) {
		return (0);
	}

	return (EINVAL);
}

int
mms_mgmt_get_pwd(char *pwfile, char *key, char *phrase[2], nvlist_t *nvl,
    nvlist_t *errs)
{
	int		st = 0;
	char		*mpwp;
	char		*chkpw;
	char		buf[512];
	int		fd;
	size_t		sz;

	if (!key || !nvl) {
		return (MMS_MGMT_NOARG);
	}

	if (pwfile != NULL) {
		fd = open(pwfile, O_RDONLY);
		if (fd == -1) {
			st = errno;
			if (errs) {
				(void) nvlist_add_int32(errs, pwfile, st);
			}
			return (st);
		}

		sz = readbuf(fd, buf, sizeof (buf));
		(void) close(fd);

		buf[sz] = '\0';

		while ((sz > 1) && (isspace(buf[sz - 1]))) {
			buf[sz -1] = '\0';
			sz--;
		}

		mpwp = buf;
		if (strlen(mpwp) < 8) {
			return (MMS_MGMT_PASSTOOSHORT);
		}
	} else {
		if ((!phrase) || (!phrase[0])) {
			return (MMS_MGMT_NOARG);
		}

		mpwp = getpassphrase(phrase[0]);
		if (mpwp == NULL) {
			return (MMS_MGMT_GETPASS_FAILED);
		} else if (strlen(mpwp) < 8) {
			return (MMS_MGMT_PASSTOOSHORT);
		}

		/* getpassphrase overwrites previous result, so save first */
		(void) strlcpy(buf, mpwp, sizeof (buf));
		mpwp = buf;

		/* verify entered password if required */
		if (phrase[1]) {
			chkpw = getpassphrase(phrase[1]);
			if ((chkpw == NULL) || (strcmp(mpwp, chkpw) != 0)) {
				return (MMS_MGMT_PASSWD_MISMATCH);
			}
		}
	}

	st = nvlist_add_string(nvl, key, mpwp);
	return (0);
}

/*
 *  Helper function to generate the MMP 'create' clause for the specified
 *  object.
 */
int
create_mmp_clause(char *objtype, mms_mgmt_setopt_t *opts, nvlist_t *inopts,
    nvlist_t *errs, char *cmd, size_t cmdlen)
{
	int		st = 0;
	int		ost = 0;
	char		tid[64];
	char		buf[1024];
	char		*val;
	int		i;

	if (!objtype || !opts || !buf || !inopts) {
		return (MMS_MGMT_NOARG);
	}

	(void) mms_gen_taskid(tid);

	(void) snprintf(cmd, cmdlen, "create task['%s'] type[%s]", tid,
	    objtype);

	for (i = 0; opts[i].name != NULL; i++) {
		if (opts[i].mmpopt == NULL) {
			continue;
		}
		ost = nvlist_lookup_string(inopts, opts[i].name, &val);
		if (ost == ENOENT) {
			if (opts[i].required) {
				if (opts[i].defval) {
					val = opts[i].defval;
					ost = 0;
				}
			} else {
				ost = 0;
				continue;
			}
		}
		if (ost != 0) {
			MGMT_ADD_OPTERR(errs, opts[i].name, ost);
			if (st == 0) {
				st = ost;
			}
			continue;
		}
		(void) snprintf(buf, sizeof (buf), " set[%s.'%s' '%s']",
		    objtype, opts[i].mmpopt, val);
		(void) strlcat(cmd, buf, cmdlen);
	}

	(void) strlcat(cmd, ";\n", cmdlen);

	return (st);
}

int
mms_add_object(void *session, char *objtype, mms_mgmt_setopt_t *objopts,
    nvlist_t *nvl, nvlist_t *errs)
{
	void	*response = NULL;
	int	st;
	char	tid[64];
	char	cmd[8192];
	void	*sess = NULL;
	void	*sessp = session;

	if (!objtype || !objopts || !nvl) {
		return (MMS_MGMT_NOARG);
	}

	if (session == NULL) {
		st = create_mm_clnt(NULL, NULL, NULL, NULL, &sess);
		if (st != 0) {
			return (st);
		}
		sessp = sess;
	}

	st = create_mmp_clause(objtype, objopts, nvl, errs, cmd, sizeof (cmd));
	if (st == 0) {
		st = mms_mgmt_send_cmd(sessp, tid, cmd, "mms_add_object()",
		    &response);

		mms_free_rsp(response);
	}

	if (sess) {
		(void) mms_goodbye(sess, 0);
	}

	return (st);
}

int
mms_mgmt_send_cmd(void *sess, char *tid, char *cmd, char *pfx, void **response)
{
	int		st = 0;

	if (!sess || !tid || !cmd || !pfx || !response) {
		return (MMS_MGMT_NOARG);
	}

	mms_trace(MMS_DEBUG, "%s request command: %s", pfx, cmd);

	if ((st = mms_send_cmd(sess, cmd, response)) != 0) {
		st = MMS_MGMT_MMP_PARSE_ERR;
		mms_trace(MMS_ERR, "%s send command failed with %d", pfx,
		    st);
	} else {
		mms_trace(MMS_DEBUG, "Response[%s]: %s",
		    tid, ((mms_rsp_ele_t *)(*response))->mms_rsp_str);

		if ((st = mms_client_handle_rsp(*response)) != MMS_API_OK) {
			mms_trace(MMS_ERR, "%s response failed", pfx);
		}
	}

	return (st);
}

int
mgmt_find_changed_attrs(char *objtype, mms_mgmt_setopt_t *opts,
    nvlist_t *nvl, char **carray, int *count, nvlist_t *errs)
{
	int		st = 0;
	int		ost = 0;
	int		i;

	if (!objtype || !opts || !nvl || !carray || !count) {
		return (MMS_MGMT_NOARG);
	}

	*count = 0;

	for (i = 0; opts[i].name != NULL; i++) {
		if (strcmp(opts[i].name, "name") == 0) {
			continue;
		}
		ost = nvlist_lookup_string(nvl, opts[i].name, &carray[i]);
		if (ost == 0) {
			if (opts[i].validate_func) {
				ost = (opts[i].validate_func)(carray[i]);
				if (ost != 0) {
					if (st == 0) {
						st = ost;
					}
					MGMT_ADD_OPTERR(errs, opts[i].name,
					    ost);
					carray[i] = NULL;
					continue;
				}
			}
			(*count)++;
		}
	}

	return (st);
}

void
cmp_mmp_opts(mms_mgmt_setopt_t *opts, char **carray, nvlist_t *nva, int *count)
{
	int	i;
	int	ost;
	char	*val;

	if (!opts || !carray || !nva || !count) {
		return;
	}

	for (i = 0; opts[i].name != NULL; i++) {
		if (carray[i] == NULL) {
			continue;
		}
		ost = nvlist_lookup_string(nva, opts[i].mmpopt, &val);
		if (ost != 0) {
			continue;
		}
		if (strcmp(val, carray[i]) == 0) {
			/* value identical */
			carray[i] = NULL;
			(*count)--;
		}
	}
}

void
mk_set_clause(char *objtype, mms_mgmt_setopt_t *opts, char **carray,
    char *buf, int buflen)
{
	int	i;
	char	phrase[1024];

	if (!objtype || !opts || !carray || !buf) {
		return;
	}

	for (i = 0; opts[i].name != NULL; i++) {
		if (carray[i] == NULL) {
			continue;
		}
		(void) snprintf(phrase, sizeof (phrase),
		    " set[%s.'%s' '%s']",
		    objtype, opts[i].mmpopt, carray[i]);
		(void) strlcat(buf, phrase, buflen);
	}
}

char **
mgmt_var_to_array(nvlist_t *nvl, char *optname, int *count)
{
	int		st;
	data_type_t	nvt;
	nvpair_t	*nvp = NULL;
	char		**arr = NULL;
	char		*val = NULL;
	char		**aval = NULL;
	int		i;

	if (!nvl || !optname || !count) {
		return (NULL);
	}

	*count = 0;

	st = nvlist_lookup_nvpair(nvl, optname, &nvp);
	if (nvp == NULL) {
		return (NULL);
	}

	nvt = nvpair_type(nvp);

	if (nvt == DATA_TYPE_STRING) {
		st = nvpair_value_string(nvp, &val);
		if (st == 0) {
			*count = 1;
			arr = malloc(sizeof (char *));
			if (arr != NULL) {
				arr[0] = strdup(val);
			}
		}
	} else if (nvt == DATA_TYPE_STRING_ARRAY) {
		st = nvpair_value_string_array(nvp, &aval, (uint_t *)count);
		if ((st == 0) && (*count > 0)) {
			arr = malloc(sizeof (char *) * *count);
			if (arr != NULL) {
				for (i = 0; i < *count; i++) {
					arr[i] = strdup(aval[i]);
				}
			}
		}
	}

	return (arr);
}

void
mgmt_free_str_arr(char **inarr, int count)
{
	int	i;

	if (!inarr) {
		return;
	}

	for (i = 0; i < count; i++) {
		if (inarr[i]) {
			free(inarr[i]);
		}
	}

	free(inarr);
}

int
mgmt_opt_to_var(char *in_str, boolean_t allow_empty, nvlist_t *nvl)
{
	int	st;
	char	*bufp;
	char	*wstr;

	if (!in_str || !nvl) {
		return (MMS_MGMT_NOARG);
	}

	wstr = strdup(in_str);
	if (!wstr) {
		return (ENOMEM);
	}

	bufp = strchr(wstr, '=');
	if (!bufp) {
		return (MMS_MGMT_NOARG);
	}
	*bufp++ = '\0';

	st = mgmt_set_str_or_arr(bufp, wstr, nvl);
	if (st != 0) {
		if (st == ENOENT) {
			if (allow_empty) {
				/* common for 'list' options */
				st = 0;
				(void) nvlist_add_string(nvl, wstr, "");
			}
		}
	}

	free(wstr);

	return (st);
}

int
mgmt_set_str_or_arr(char *inargs, char *key, nvlist_t *nvl)
{
	int		st;
	char		*bufp;
	int		count;
	char		**tmparr;

	if (!inargs || !key || !nvl) {
		return (MMS_MGMT_NOARG);
	}

	bufp = inargs;
	count = 1;

	for (;;) {
		bufp = strchr(bufp, ',');
		if (bufp == NULL) {
			break;
		}
		bufp++;
		count++;
	}

	if (count == 1) {
		st = nvlist_add_string(nvl, key, inargs);
	} else {
		tmparr = calloc(count, sizeof (char *));
		if (tmparr == NULL) {
			return (ENOMEM);
		}
		bufp = inargs;
		/* set delimiter to comma */
		(void) bufsplit(",", 0, NULL);

		(void) bufsplit(bufp, count, tmparr);
		st = nvlist_add_string_array(nvl, key, tmparr, count);
		free(tmparr);
	}

	return (st);
}

int
mgmt_xlate_cfgerr(scf_error_t in_err)
{
	int		st;

	switch (in_err) {
		case SCF_ERROR_NOT_SET:
		case SCF_ERROR_NOT_FOUND:
		case SCF_ERROR_DELETED:
			st = ENOENT;
			break;
		case SCF_ERROR_NO_MEMORY:
			st = ENOMEM;
			break;
		case SCF_ERROR_TYPE_MISMATCH:
			st = EINVAL;
			break;
		default:
			st = in_err;
			break;
	}

	return (st);
}

int
mgmt_compare_hosts(char *host1, char *host2)
{
	int			st;
	struct addrinfo		*res1 = NULL;
	struct addrinfo		*res2 = NULL;
	struct addrinfo		*p1 = NULL;
	struct addrinfo		*p2 = NULL;
	boolean_t		match = B_FALSE;
	int			a;

	if (!host1 || !host2) {
		return (MMS_MGMT_NOARG);
	}

	st = getaddrinfo(host1, NULL, NULL, &res1);
	if (st != 0) {
		return (st);
	}

	st = getaddrinfo(host2, NULL, NULL, &res2);
	if (st != 0) {
		freeaddrinfo(res1);
		return (st);
	}

	for (p1 = res1; p1 != NULL; p1 = p1->ai_next) {
		for (p2 = res2; p2 != NULL; p2 = p2->ai_next) {
			a = memcmp(p1->ai_addr, p2->ai_addr,
			    sizeof (struct sockaddr));
			if (a == 0) {
				match = B_TRUE;
				break;
			}
			if (memcmp(p1->ai_addr, p2->ai_addr,
			    sizeof (struct sockaddr)) == 0) {
				match = B_TRUE;
				break;
			}
		}
	}

	if (res1) {
		freeaddrinfo(res1);
	}
	if (res2) {
		freeaddrinfo(res2);
	}

	if (match) {
		return (0);
	}

	return (1);
}

const char *
mms_mgmt_get_errstr(int errcode)
{
	int		i;
	int max_err = sizeof (mms_mgmt_errs) / sizeof (mms_sym_t);
	static	char	msg[1024];

	/* standard errors */
	if (errcode < 256) {
		return (strerror(errcode));
	}

	/* SCF errors */
	if ((errcode >= 1000) && (errcode < 1020)) {
		return (scf_strerror(errcode));
	}

	if ((errcode > MMS_MGMT_ERR_OFFSET) &&
	    (errcode < MMS_MGMT_LAST_ERR_CODE)) {
		for (i = 0; i < max_err; i++) {
			if (mms_mgmt_errs[i].sym_code == errcode) {
				return (mms_mgmt_errs[i].sym_token);
			}
		}
		(void) snprintf(msg, sizeof (msg), "Unknown err code %d",
		    errcode);
		return (msg);
	}

	if (errcode >= MMS_ERR_BIAS) {
		return (mms_sym_code_to_str(errcode));
	}

	return (NULL);
}

int
mgmt_chk_auth(char *authname)
{
	int		st;
	struct passwd	pwd;
	struct passwd	*pwdp;
	char		buf[1024];

	if (!authname) {
		return (1);
	}

	st = getpwuid_r(getuid(), &pwd, buf, sizeof (buf), &pwdp);

	if (st != 0) {
		/* fail if we can't determine the username */
		return (0);
	}

	st = chkauthattr(authname, pwdp->pw_name);

	return (st);
}

void
mgmt_filter_results(nvlist_t *filter, nvlist_t *nvl)
{
	int		st;
	int		count;
	char		**varray;
	nvpair_t	*nvp;
	char		*key;
	data_type_t	nvt;
	boolean_t	do_filter = B_FALSE;

	if (!filter || !nvl) {
		/* not a failure if nothing to do */
		return;
	}

	st = nvlist_lookup_boolean_value(filter, "filter", &do_filter);
	if ((st != 0) || !do_filter) {
		return;
	}

	nvp = NULL;
	while ((nvp = nvlist_next_nvpair(filter, nvp)) != NULL) {
		key = nvpair_name(nvp);

		if ((strcmp(key, "printopts") == 0) ||
		    (strcmp(key, "name") == 0) ||
		    (strcmp(key, "objtype") == 0)) {
			continue;
		}

		nvt = nvpair_type(nvp);
		if ((nvt != DATA_TYPE_STRING_ARRAY) &&
		    (nvt != DATA_TYPE_STRING)) {
			continue;
		}

		varray = mgmt_var_to_array(filter, key, &count);
		filter_on_var(key, varray, count, nvl);
		mgmt_free_str_arr(varray, count);
	}
}

static void
filter_on_var(char *varname, char **varray, int count, nvlist_t *nvl)
{
	int		st;
	uint_t		vcount;
	char		**arr;
	nvpair_t	*nvp = NULL;
	nvlist_t	*attrs;
	char		*val;
	data_type_t	nvt;
	int		i;
	int		j;
	nvpair_t	*fnvp;
	char		*attrname;
	nvpair_t	*lastnvp = NULL;
	boolean_t	keep;

	if (!varname || (count == 0) || !nvl) {
		return;
	}


	while ((nvp = nvlist_next_nvpair(nvl, nvp)) != NULL) {
		st = nvpair_value_nvlist(nvp, &attrs);
		if (st != 0) {
			continue;
		}

		attrname = nvpair_name(nvp);

		st = nvlist_lookup_nvpair(attrs, varname, &fnvp);
		if (st != 0) {
			/* no match, remove it */
			(void) nvlist_remove_all(nvl, attrname);
			nvp = lastnvp;
			continue;
		}

		keep = B_FALSE;

		nvt = nvpair_type(fnvp);

		val = NULL;
		if (nvt == DATA_TYPE_STRING) {
			(void) nvpair_value_string(fnvp, &val);
			if (val) {
				for (i = 0; i < count; i++) {
					if (strcmp(val, varray[i]) == 0) {
						/* a keeper */
						keep = B_TRUE;
						break;
					}
				}
			}
		} else if (nvt == DATA_TYPE_STRING_ARRAY) {
			st = nvpair_value_string_array(fnvp, &arr, &vcount);
			if (st == 0) {
				for (j = 0; j < vcount; j++) {
					for (i = 0; i < count; i++) {
						if (strcmp(val, varray[i])
						    == 0) {
							/* a keeper */
							keep = B_TRUE;
							break;
						}
					}
					if (keep) {
						break;
					}
				}
			}
		}

		if (keep) {
			lastnvp = nvp;
		} else {
			(void) nvlist_remove_all(nvl, attrname);
			nvp = lastnvp;
		}
	}
}