view usr/src/cmd/svc/svccfg/svccfg_engine.c @ 12979:ab9ae749152f

PSARC/2009/617 Software Events Notification Parameters CLI PSARC/2009/618 snmp-notify: SNMP Notification Daemon for Software Events PSARC/2009/619 smtp-notify: Email Notification Daemon for Software Events PSARC/2010/225 fmd for non-global Solaris zones PSARC/2010/226 Solaris Instance UUID PSARC/2010/227 nvlist_nvflag(3NVPAIR) PSARC/2010/228 libfmevent additions PSARC/2010/257 sysevent_evc_setpropnvl and sysevent_evc_getpropnvl PSARC/2010/265 FMRI and FMA Event Stabilty, 'ireport' category 1 event class, and the 'sw' FMRI scheme PSARC/2010/278 FMA/SMF integration: instance state transitions PSARC/2010/279 Modelling panics within FMA PSARC/2010/290 logadm.conf upgrade 6392476 fmdump needs to pretty-print 6393375 userland ereport/ireport event generation interfaces 6445732 Add email notification agent for FMA and software events 6804168 RFE: Allow an efficient means to monitor SMF services status changes 6866661 scf_values_destroy(3SCF) will segfault if is passed NULL 6884709 Add snmp notification agent for FMA and software events 6884712 Add private interface to tap into libfmd_msg macro expansion capabilities 6897919 fmd to run in a non-global zone 6897937 fmd use of non-private doors is not safe 6900081 add a UUID to Solaris kernel image for use in crashdump identification 6914884 model panic events as a defect diagnosis in FMA 6944862 fmd_case_open_uuid, fmd_case_uuisresolved, fmd_nvl_create_defect 6944866 log legacy sysevents in fmd 6944867 enumerate svc scheme in topo 6944868 software-diagnosis and software-response fmd modules 6944870 model SMF maintenance state as a defect diagnosis in FMA 6944876 savecore runs in foreground for systems with zfs root and dedicated dump 6965796 Implement notification parameters for SMF state transitions and FMA events 6968287 SUN-FM-MIB.mib needs to be updated to reflect Oracle information 6972331 logadm.conf upgrade PSARC/2010/290
author Gavin Maltby <gavin.maltby@oracle.com>
date Fri, 30 Jul 2010 17:04:17 +1000
parents 62bc4887874f
children e0ee41527b6e
line wrap: on
line source

/*
 * 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 (c) 2004, 2010, Oracle and/or its affiliates. All rights reserved.
 */


/*
 * svccfg(1) interpreter and command execution engine.
 */

#include <sys/mman.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <assert.h>
#include <errno.h>
#include <fcntl.h>
#include <libintl.h>
#include <libtecla.h>
#include <md5.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>

#include "manifest_find.h"
#include "manifest_hash.h"
#include "svccfg.h"

#define	MS_PER_US		1000

engine_state_t *est;

/*
 * Replacement lex(1) character retrieval routines.
 */
int
engine_cmd_getc(engine_state_t *E)
{
	if (E->sc_cmd_file != NULL)
		return (getc(E->sc_cmd_file));

	if (E->sc_cmd_flags & SC_CMD_EOF)
		return (EOF);

	if (E->sc_cmd_bufoff < E->sc_cmd_bufsz)
		return (*(E->sc_cmd_buf + E->sc_cmd_bufoff++));

	if (!(E->sc_cmd_flags & SC_CMD_IACTIVE)) {
		E->sc_cmd_flags |= SC_CMD_EOF;

		return (EOF);
	} else {
#ifdef NATIVE_BUILD
		return (EOF);
#else
		extern int parens;

		if (parens <= 0) {
			E->sc_cmd_flags |= SC_CMD_EOF;
			return (EOF);
		}

		for (;;) {
			E->sc_cmd_buf = gl_get_line(E->sc_gl, "> ", NULL, -1);
			if (E->sc_cmd_buf != NULL)
				break;

			switch (gl_return_status(E->sc_gl)) {
			case GLR_SIGNAL:
				gl_abandon_line(E->sc_gl);
				continue;

			case GLR_EOF:
				E->sc_cmd_flags |= SC_CMD_EOF;
				return (EOF);

			case GLR_ERROR:
				uu_die(gettext("Error reading terminal: %s.\n"),
				    gl_error_message(E->sc_gl, NULL, 0));
				/* NOTREACHED */

			default:
#ifndef NDEBUG
				(void) fprintf(stderr, "%s:%d: gl_get_line() "
				    "returned unexpected value %d.\n", __FILE__,
				    __LINE__, gl_return_status(E->sc_gl));
#endif
				abort();
			}
		}

		E->sc_cmd_bufsz = strlen(E->sc_cmd_buf);
		E->sc_cmd_bufoff = 1;

		return (E->sc_cmd_buf[0]);
#endif	/* NATIVE_BUILD */
	}
}

int
engine_cmd_ungetc(engine_state_t *E, char c)
{
	if (E->sc_cmd_file != NULL)
		return (ungetc(c, E->sc_cmd_file));

	if (E->sc_cmd_buf != NULL)
		*(E->sc_cmd_buf + --E->sc_cmd_bufoff) = c;

	return (c);
}

/*ARGSUSED*/
void
engine_cmd_nputs(engine_state_t *E, char *c, size_t n)
{
	/* our lexer shouldn't need this state */
	exit(11);
}

int
engine_exec(char *cmd)
{
	est->sc_cmd_buf = cmd;
	est->sc_cmd_bufsz = strlen(cmd) + 1;
	est->sc_cmd_bufoff = 0;

	(void) yyparse();

	return (0);
}

#ifndef NATIVE_BUILD
/* ARGSUSED */
static
CPL_CHECK_FN(check_xml)
{
	const char *ext;

	if (strlen(pathname) < 4)
		return (0);

	ext = pathname + strlen(pathname) - 4;

	return (strcmp(ext, ".xml") == 0 ? 1 : 0);
}

static const char * const whitespace = " \t";

static
CPL_MATCH_FN(complete_single_xml_file_arg)
{
	const char *arg1 = data;
	int arg1end_i, ret;
	CplFileConf *cfc;

	arg1end_i = arg1 + strcspn(arg1, whitespace) - line;
	if (arg1end_i < word_end)
		return (0);

	cfc = new_CplFileConf();
	if (cfc == NULL) {
		cpl_record_error(cpl, "Out of memory.");
		return (1);
	}

	cfc_set_check_fn(cfc, check_xml, NULL);

	ret = cpl_file_completions(cpl, cfc, line, word_end);

	(void) del_CplFileConf(cfc);
	return (ret);
}

static struct cmd_info {
	const char	*name;
	uint32_t	flags;
	CplMatchFn	*complete_args_f;
} cmds[] = {
	{ "validate", CS_GLOBAL, complete_single_xml_file_arg },
	{ "import", CS_GLOBAL, complete_single_xml_file_arg },
	{ "cleanup", CS_GLOBAL, NULL},
	{ "export", CS_GLOBAL, NULL },
	{ "archive", CS_GLOBAL, NULL },
	{ "apply", CS_GLOBAL, complete_single_xml_file_arg },
	{ "extract", CS_GLOBAL, NULL },
	{ "repository", CS_GLOBAL, NULL },
	{ "inventory", CS_GLOBAL, complete_single_xml_file_arg },
	{ "set", CS_GLOBAL, NULL },
	{ "end", CS_GLOBAL, NULL },
	{ "exit", CS_GLOBAL, NULL },
	{ "quit", CS_GLOBAL, NULL },
	{ "help", CS_GLOBAL, NULL },
	{ "delete", CS_GLOBAL, NULL },
	{ "select", CS_GLOBAL, complete_select },
	{ "unselect", CS_SVC | CS_INST | CS_SNAP, NULL },
	{ "list", CS_SCOPE | CS_SVC | CS_SNAP, NULL },
	{ "add", CS_SCOPE | CS_SVC, NULL },
	{ "listpg", CS_SVC | CS_INST | CS_SNAP, NULL },
	{ "addpg", CS_SVC | CS_INST, NULL },
	{ "delpg", CS_SVC | CS_INST, NULL },
	{ "delhash", CS_GLOBAL, complete_single_xml_file_arg },
	{ "listprop", CS_SVC | CS_INST | CS_SNAP, NULL },
	{ "setprop", CS_SVC | CS_INST, NULL },
	{ "delprop", CS_SVC | CS_INST, NULL },
	{ "editprop", CS_SVC | CS_INST, NULL },
	{ "describe", CS_SVC | CS_INST | CS_SNAP, NULL },
	{ "listsnap", CS_INST | CS_SNAP, NULL },
	{ "selectsnap", CS_INST | CS_SNAP, NULL },
	{ "revert", CS_INST | CS_SNAP, NULL },
	{ "refresh", CS_INST, NULL },
	{ NULL }
};

int
add_cmd_matches(WordCompletion *cpl, const char *line, int word_end,
    uint32_t scope)
{
	int word_start, err;
	size_t len;
	const char *bol;
	struct cmd_info *cip;

	word_start = strspn(line, whitespace);
	len = word_end - word_start;
	bol = line + word_end - len;

	for (cip = cmds; cip->name != NULL; ++cip) {
		if ((cip->flags & scope) == 0)
			continue;

		if (strncmp(cip->name, bol, len) == 0) {
			err = cpl_add_completion(cpl, line, word_start,
			    word_end, cip->name + len, "", " ");
			if (err != 0)
				return (err);
		}
	}

	return (0);
}

/*
 * Suggest completions.  We must first determine if the cursor is in command
 * position or in argument position.  If the former, complete_command() finds
 * matching commands.  If the latter, we tail-call the command-specific
 * argument-completion routine in the cmds table.
 */
/* ARGSUSED */
static
CPL_MATCH_FN(complete)
{
	const char *arg0, *arg1;
	size_t arg0len;
	struct cmd_info *cip;

	arg0 = line + strspn(line, whitespace);
	arg0len = strcspn(arg0, whitespace);
	if ((arg0 + arg0len) - line >= word_end ||
	    (arg0[arg0len] != ' ' && arg0[arg0len] != '\t'))
		return (complete_command(cpl, (void *)arg0, line, word_end));

	arg1 = arg0 + arg0len;
	arg1 += strspn(arg1, whitespace);

	for (cip = cmds; cip->name != NULL; ++cip) {
		if (strlen(cip->name) != arg0len)
			continue;

		if (strncmp(cip->name, arg0, arg0len) != 0)
			continue;

		if (cip->complete_args_f == NULL)
			break;

		return (cip->complete_args_f(cpl, (void *)arg1, line,
		    word_end));
	}

	return (0);
}
#endif	/* NATIVE_BUILD */

int
engine_interp()
{
#ifdef NATIVE_BUILD
	uu_die("native build does not support interactive mode.");
#else
	char *selfmri;
	size_t sfsz;
	int r;

	extern int parens;

	(void) sigset(SIGINT, SIG_IGN);

	est->sc_gl = new_GetLine(512, 8000);
	if (est->sc_gl == NULL)
		uu_die(gettext("Out of memory.\n"));

	/* The longest string is "[snapname]fmri[:instname]> ". */
	sfsz = 1 + max_scf_name_len + 1 + max_scf_fmri_len + 2 +
	    max_scf_name_len + 1 + 2 + 1;
	selfmri = safe_malloc(sfsz);

	r = gl_customize_completion(est->sc_gl, NULL, complete);
	assert(r == 0);

	for (;;) {
		lscf_get_selection_str(selfmri, sfsz - 2);
		(void) strcat(selfmri, "> ");
		est->sc_cmd_buf = gl_get_line(est->sc_gl, selfmri, NULL, -1);

		if (est->sc_cmd_buf == NULL) {
			switch (gl_return_status(est->sc_gl)) {
			case GLR_SIGNAL:
				gl_abandon_line(est->sc_gl);
				continue;

			case GLR_EOF:
				break;

			case GLR_ERROR:
				uu_die(gettext("Error reading terminal: %s.\n"),
				    gl_error_message(est->sc_gl, NULL, 0));
				/* NOTREACHED */

			default:
#ifndef NDEBUG
				(void) fprintf(stderr, "%s:%d: gl_get_line() "
				    "returned unexpected value %d.\n", __FILE__,
				    __LINE__, gl_return_status(est->sc_gl));
#endif
				abort();
			}

			break;
		}

		parens = 0;
		est->sc_cmd_bufsz = strlen(est->sc_cmd_buf);
		est->sc_cmd_bufoff = 0;
		est->sc_cmd_flags = SC_CMD_IACTIVE;

		(void) yyparse();
	}

	free(selfmri);
	est->sc_gl = del_GetLine(est->sc_gl);	/* returns NULL */

#endif	/* NATIVE_BUILD */
	return (0);
}

int
engine_source(const char *name, boolean_t dont_exit)
{
	engine_state_t *old = est;
	struct stat st;
	int ret;

	est = uu_zalloc(sizeof (engine_state_t));

	/* first, copy the stuff set up in engine_init */
	est->sc_repo_pid = old->sc_repo_pid;
	if (old->sc_repo_filename != NULL)
		est->sc_repo_filename = safe_strdup(old->sc_repo_filename);
	if (old->sc_repo_doordir != NULL)
		est->sc_repo_doordir = safe_strdup(old->sc_repo_doordir);
	if (old->sc_repo_doorname != NULL)
		est->sc_repo_doorname = safe_strdup(old->sc_repo_doorname);
	if (old->sc_repo_server != NULL)
		est->sc_repo_server = safe_strdup(old->sc_repo_server);

	/* set up the new guy */
	est->sc_cmd_lineno = 1;

	if (dont_exit)
		est->sc_cmd_flags |= SC_CMD_DONT_EXIT;

	if (strcmp(name, "-") == 0) {
		est->sc_cmd_file = stdin;
		est->sc_cmd_filename = "<stdin>";
	} else {
		errno = 0;
		est->sc_cmd_filename = name;
		est->sc_cmd_file = fopen(name, "r");
		if (est->sc_cmd_file == NULL) {
			if (errno == 0)
				semerr(gettext("No free stdio streams.\n"));
			else
				semerr(gettext("Could not open %s"), name);

			ret = -1;
			goto fail;
		}

		do {
			ret = fstat(fileno(est->sc_cmd_file), &st);
		} while (ret != 0 && errno == EINTR);
		if (ret != 0) {
			(void) fclose(est->sc_cmd_file);
			est->sc_cmd_file = NULL;	/* for semerr() */

			semerr(gettext("Could not stat %s"), name);

			ret = -1;
			goto fail;
		}

		if (!S_ISREG(st.st_mode)) {
			(void) fclose(est->sc_cmd_file);
			est->sc_cmd_file = NULL;	/* for semerr() */

			semerr(gettext("%s is not a regular file.\n"), name);

			ret = -1;
			goto fail;
		}
	}

	(void) yyparse();

	if (est->sc_cmd_file != stdin)
		(void) fclose(est->sc_cmd_file);

	ret = 0;

fail:
	if (est->sc_repo_pid != old->sc_repo_pid)
		lscf_cleanup();		/* clean up any new repository */

	if (est->sc_repo_filename != NULL)
		free((void *)est->sc_repo_filename);
	if (est->sc_repo_doordir != NULL)
		free((void *)est->sc_repo_doordir);
	if (est->sc_repo_doorname != NULL)
		free((void *)est->sc_repo_doorname);
	if (est->sc_repo_server != NULL)
		free((void *)est->sc_repo_server);
	free(est);

	est = old;

	return (ret);
}

/*
 * Initialize svccfg state.  We recognize four environment variables:
 *
 * SVCCFG_REPOSITORY	Create a private instance of svc.configd(1M) to answer
 *			requests for the specified repository file.
 * SVCCFG_DOOR_PATH	Directory for door creation.
 *
 * SVCCFG_DOOR		Rendezvous via an alternative repository door.
 *
 * SVCCFG_CONFIGD_PATH	Resolvable path to alternative svc.configd(1M) binary.
 */
void
engine_init()
{
	const char *cp;

	est = uu_zalloc(sizeof (engine_state_t));

	est->sc_cmd_lineno = 1;
	est->sc_repo_pid = -1;

	cp = getenv("SVCCFG_REPOSITORY");
	est->sc_repo_filename = cp ? safe_strdup(cp) : NULL;

	cp = getenv("SVCCFG_DOOR_PATH");
	est->sc_repo_doordir = cp ? cp : "/var/run";

	cp = getenv("SVCCFG_DOOR");
	if (cp != NULL) {
		if (est->sc_repo_filename != NULL) {
			uu_warn(gettext("SVCCFG_DOOR unused when "
			    "SVCCFG_REPOSITORY specified\n"));
		} else {
			est->sc_repo_doorname = safe_strdup(cp);
		}
	}

	cp = getenv("SVCCFG_CONFIGD_PATH");
	est->sc_repo_server = cp ? cp : "/lib/svc/bin/svc.configd";

	est->sc_miss_type = B_FALSE;
	est->sc_in_emi = 0;
	cp = getenv("SMF_FMRI");
	if ((cp != NULL) && (strcmp(cp, SCF_INSTANCE_EMI) == 0))
		est->sc_in_emi = 1;

	cp = smf_get_state(SCF_INSTANCE_FS_MINIMAL);
	if (cp && (strcmp(cp, SCF_STATE_STRING_ONLINE) == 0))
		est->sc_fs_minimal = B_TRUE;
	free((void *) cp);
}

static int
import_manifest_file(manifest_info_t *info, boolean_t validate, FILE *pout,
    uint_t flags)
{
	bundle_t *b;
	tmpl_errors_t *errs;
	const char *file;
	tmpl_validate_status_t vr;

	file = info->mi_path;

	/* Load the manifest */
	b = internal_bundle_new();

	if (lxml_get_bundle_file(b, file, SVCCFG_OP_IMPORT) != 0) {
		internal_bundle_free(b);
		return (-1);
	}

	/* Validate */
	if ((vr = tmpl_validate_bundle(b, &errs)) != TVS_SUCCESS) {
		char *prefix;

		if ((validate == 0) || (vr == TVS_WARN)) {
			prefix = gettext("Warning: ");
		} else {
			prefix = "";
		}
		tmpl_errors_print(stderr, errs, prefix);
		if (validate && (vr != TVS_WARN)) {
			tmpl_errors_destroy(errs);
			semerr(gettext("Import of %s failed.\n"),
			    info->mi_path);
			if (pout != NULL) {
				(void) fprintf(pout, gettext("WARNING: svccfg "
				    "import of %s failed.\n"), info->mi_path);
			}

			return (-1);
		}
	}
	tmpl_errors_destroy(errs);

	/* Import */
	if (lscf_bundle_import(b, file, flags) != 0) {
		internal_bundle_free(b);
		semerr(gettext("Import of %s failed.\n"), info->mi_path);
		if (pout != NULL) {
			(void) fprintf(pout, gettext("WARNING: svccfg import "
			    "of %s failed.\n"), info->mi_path);
		}
		return (-1);
	}

	internal_bundle_free(b);

	if (info->mi_prop) {
		char *errstr;

		if (mhash_store_entry(g_hndl, info->mi_prop, file,
		    info->mi_hash, APPLY_NONE, &errstr)) {
			if (errstr)
				semerr(gettext("Could not store hash for %s. "
				    "%s\n"), info->mi_path, errstr);
			else
				semerr(gettext("Unknown error from "
				    "mhash_store_entry() for %s\n"),
				    info->mi_path);
		}

	}

	return (0);
}

/*
 * Return values:
 *	1	No manifests need to be imported.
 *	0	Success
 *	-1	Error
 *	-2	Syntax error
 */
int
engine_import(uu_list_t *args)
{
	int argc, i, o;
	int dont_exit;
	int failed_manifests;
	int total_manifests;
	char *file;
	char **argv = NULL;
	string_list_t *slp;
	boolean_t validate = B_FALSE;
	uint_t flags = SCI_GENERALLAST;
	int dirarg = 0;
	int isdir;
	int rc = -1;
	struct stat sb;
	char **paths;
	manifest_info_t ***manifest_sets = NULL;
	manifest_info_t **manifests;
	char *progress_file = NULL;
	FILE *progress_out = NULL;
	int progress_count;
	int back_count;
	int count;
	int fm_flags;

	argc = uu_list_numnodes(args);
	if (argc < 1)
		return (-2);

	argv = calloc(argc + 1, sizeof (char *));
	if (argv == NULL)
		uu_die(gettext("Out of memory.\n"));

	for (slp = uu_list_first(args), i = 0;
	    slp != NULL;
	    slp = uu_list_next(args, slp), ++i)
		argv[i] = slp->str;

	argv[i] = NULL;

	opterr = 0;
	optind = 0;				/* Remember, no argv[0]. */
	for (;;) {
		o = getopt(argc, argv, "np:V");
		if (o == -1)
			break;

		switch (o) {
		case 'n':
			flags |= SCI_NOREFRESH;
			break;

		case 'p':
			progress_file = optarg;
			break;

		case 'V':
			validate = B_TRUE;
			break;

		case '?':
			free(argv);
			return (-2);

		default:
			bad_error("getopt", o);
		}
	}

	argc -= optind;
	if (argc < 1) {
		free(argv);
		return (-2);
	}

	/* Open device for progress messages */
	if (progress_file != NULL) {
		if (strcmp(progress_file, "-") == 0) {
			progress_out = stdout;
		} else {
			progress_out = fopen(progress_file, "w");
			if (progress_out == NULL) {
				semerr(gettext("Unable to open %s for "
				    "progress reporting.  %s\n"),
				    progress_file, strerror(errno));
				goto out;
			}
			setbuf(progress_out, NULL);
		}
	}

	paths = argv+optind;
	manifest_sets = safe_malloc(argc * sizeof (*manifest_sets));

	/* If we're in interactive mode, force strict validation. */
	if (est->sc_cmd_flags & SC_CMD_IACTIVE)
		validate = B_TRUE;

	lscf_prep_hndl();

	/* Determine which manifests must be imported. */

	total_manifests = 0;
	for (i = 0; i < argc; i++) {
		file = *(paths + i);
		fm_flags = CHECKHASH;

		/* Determine if argument is a directory or file. */
		if (stat(file, &sb) == -1) {
			semerr(gettext("Unable to stat file %s.  %s\n"), file,
			    strerror(errno));
			goto out;
		}
		if (sb.st_mode & S_IFDIR) {
			fm_flags |= CHECKEXT;
			dirarg = 1;
			isdir = 1;
		} else if (sb.st_mode & S_IFREG) {
			isdir = 0;
		} else {
			semerr(gettext("%s is not a directory or regular "
			    "file\n"), file);
			goto out;
		}

		/* Get list of manifests that we should import for this path. */
		if ((count = find_manifests(file, &manifests, fm_flags)) < 0) {
			if (isdir) {
				semerr(gettext("Could not hash directory %s\n"),
				    file);
			} else {
				semerr(gettext("Could not hash file %s\n"),
				    file);
			}
			free_manifest_array(manifests);
			goto out;
		}
		total_manifests += count;
		manifest_sets[i] = manifests;
	}

	if (total_manifests == 0) {
		/* No manifests to process. */
		if (g_verbose) {
			warn(gettext("No changes were necessary\n"));
		}
		rc = 1;
		goto out;
	}

	/*
	 * If we're processing more than one file, we don't want to exit if
	 * we encounter an error.  We should go ahead and process all of
	 * the manifests.
	 */
	dont_exit = est->sc_cmd_flags & SC_CMD_DONT_EXIT;
	if (total_manifests > 1)
		est->sc_cmd_flags |= SC_CMD_DONT_EXIT;

	if (progress_out != NULL)
		(void) fprintf(progress_out,
		    "Loading smf(5) service descriptions: ");

	failed_manifests = 0;
	progress_count = 0;
	for (i = 0; i < argc; i++) {
		manifests = manifest_sets[i];
		if (manifests == NULL)
			continue;
		for (; *manifests != NULL; manifests++) {
			progress_count++;
			if (progress_out != NULL) {
				back_count = fprintf(progress_out, "%d/%d",
				    progress_count, total_manifests);
				while (back_count-- > 0) {
					(void) fputc('\b', progress_out);
				}
			}
			if (import_manifest_file(*manifests, validate,
			    progress_out, flags) != 0) {
				failed_manifests++;
			}
		}
	}
	if (progress_out != NULL)
		(void) fputc('\n', progress_out);

	if ((total_manifests > 1) && (dont_exit == 0))
		est->sc_cmd_flags &= ~SC_CMD_DONT_EXIT;

	if (dirarg && total_manifests > 0) {
		char *msg;

		msg = "Loaded %d smf(5) service descriptions\n";
		warn(gettext(msg), progress_count);

		if (failed_manifests) {
			msg = "%d smf(5) service descriptions failed to load\n";
			warn(gettext(msg), failed_manifests);
		}
	}

	if (failed_manifests > 0)
		goto out;

	if (g_verbose)
		warn(gettext("Successful import.\n"));
	rc = 0;

out:
	if ((progress_out != NULL) && (progress_out != stdout))
		(void) fclose(progress_out);
	free(argv);
	if (manifest_sets != NULL) {
		for (i = 0; i < argc; i++) {
			free_manifest_array(manifest_sets[i]);
		}
		free(manifest_sets);
	}
	return (rc);
}

/*
 * Walk each service and get its manifest file.
 *
 * If the file exists check instance support, and cleanup any
 * stale instances.
 *
 * If the file doesn't exist tear down the service and/or instances
 * that are no longer supported by files.
 */
int
engine_cleanup(int flags)
{
	boolean_t		activity = B_TRUE;
	int			r = -1;

	lscf_prep_hndl();

	if (flags == 1) {
		activity = B_FALSE;
	}

	if (scf_walk_fmri(g_hndl, 0, NULL, SCF_WALK_SERVICE|SCF_WALK_NOINSTANCE,
	    lscf_service_cleanup, (void *)activity, NULL,
	    uu_warn) == SCF_SUCCESS)
		r = 0;

	(void) lscf_hash_cleanup();

	return (r);
}

static int
apply_profile(manifest_info_t *info, int apply_changes)
{
	bundle_t *b = internal_bundle_new();

	if (lxml_get_bundle_file(b, info->mi_path, SVCCFG_OP_APPLY) != 0) {
		internal_bundle_free(b);
		return (-1);
	}

	if (!apply_changes) {	/* we don't want to apply, just test */
		internal_bundle_free(b);
		return (0);
	}

	if (lscf_bundle_apply(b, info->mi_path) != 0) {
		internal_bundle_free(b);
		return (-1);
	}

	internal_bundle_free(b);

	if (info->mi_prop) {
		apply_action_t apply;
		char *errstr;

		apply = (est->sc_in_emi == 1) ? APPLY_LATE : APPLY_NONE;
		if (mhash_store_entry(g_hndl, info->mi_prop, info->mi_path,
		    info->mi_hash, apply, &errstr)) {
			semerr(errstr);
		}
	}

	return (0);
}

int
engine_apply(const char *file, int apply_changes)
{
	int rc = 0;
	int isdir;
	int dont_exit;
	int profile_count;
	int fm_flags;
	struct stat sb;
	manifest_info_t **profiles = NULL;
	manifest_info_t **entry;
	manifest_info_t *pfile;

	lscf_prep_hndl();

	/* Determine which profile(s) must be applied. */

	profile_count = 0;
	fm_flags = BUNDLE_PROF | CHECKHASH;

	/* Determine if argument is a directory or file. */
	if (stat(file, &sb) == -1) {
		semerr(gettext("Unable to stat file %s.  %s\n"), file,
		    strerror(errno));
		rc = -1;
		goto out;
	}

	if (sb.st_mode & S_IFDIR) {
		fm_flags |= CHECKEXT;
		isdir = 1;
	} else if (sb.st_mode & S_IFREG) {
		isdir = 0;
	} else {
		semerr(gettext("%s is not a directory or regular "
		    "file\n"), file);
		rc = -1;
		goto out;
	}

	/* Get list of profiles to be applied. */
	if ((profile_count = find_manifests(file, &profiles, fm_flags)) < 0) {
		if (isdir) {
			semerr(gettext("Could not hash directory %s\n"), file);
		} else {
			semerr(gettext("Could not hash file %s\n"), file);
		}
		rc = -1;
		goto out;
	}

	if (profile_count == 0) {
		/* No profiles to process. */
		if (g_verbose) {
			warn(gettext("No changes were necessary\n"));
		}
		goto out;
	}

	/*
	 * We don't want to exit if we encounter an error.  We should go ahead
	 * and process all of the profiles.
	 */
	dont_exit = est->sc_cmd_flags & SC_CMD_DONT_EXIT;
	est->sc_cmd_flags |= SC_CMD_DONT_EXIT;

	for (entry = profiles; *entry != NULL; entry++) {
		pfile = *entry;

		if (apply_profile(pfile, apply_changes) == 0) {
			if (g_verbose) {
				warn(gettext("Successfully applied: %s\n"),
				    pfile->mi_path);
			}
		} else {
			warn(gettext("WARNING: Failed to apply %s\n"),
			    pfile->mi_path);
			rc = -1;
		}
	}

	if (dont_exit == 0)
		est->sc_cmd_flags &= ~SC_CMD_DONT_EXIT;

	/* exit(1) appropriately if any profile failed to be applied. */
	if ((rc == -1) &&
	    (est->sc_cmd_flags & (SC_CMD_IACTIVE | SC_CMD_DONT_EXIT)) == 0) {
		free_manifest_array(profiles);
		exit(1);
	}

out:
	free_manifest_array(profiles);
	return (rc);
}

int
engine_restore(const char *file)
{
	bundle_t *b;

	lscf_prep_hndl();

	b = internal_bundle_new();

	if (lxml_get_bundle_file(b, file, SVCCFG_OP_RESTORE) != 0) {
		internal_bundle_free(b);
		return (-1);
	}

	if (lscf_bundle_import(b, file, SCI_NOSNAP) != 0) {
		internal_bundle_free(b);
		return (-1);
	}

	internal_bundle_free(b);

	return (0);
}

int
engine_set(uu_list_t *args)
{
	uu_list_walk_t *walk;
	string_list_t *slp;

	if (uu_list_first(args) == NULL) {
		/* Display current options. */
		if (!g_verbose)
			(void) fputs("no", stdout);
		(void) puts("verbose");

		return (0);
	}

	walk = uu_list_walk_start(args, UU_DEFAULT);
	if (walk == NULL)
		uu_die(gettext("Couldn't read arguments"));

	/* Use getopt? */
	for (slp = uu_list_walk_next(walk);
	    slp != NULL;
	    slp = uu_list_walk_next(walk)) {
		if (slp->str[0] == '-') {
			char *op;

			for (op = &slp->str[1]; *op != '\0'; ++op) {
				switch (*op) {
				case 'v':
					g_verbose = 1;
					break;

				case 'V':
					g_verbose = 0;
					break;

				default:
					warn(gettext("Unknown option -%c.\n"),
					    *op);
				}
			}
		} else {
			warn(gettext("No non-flag arguments defined.\n"));
		}
	}

	return (0);
}

void
help(int com)
{
	int i;

	if (com == 0) {
		warn(gettext("General commands:	 help set repository end\n"
		    "Manifest commands:	 inventory validate import export "
		    "archive\n"
		    "Profile commands:	 apply extract\n"
		    "Entity commands:	 list select unselect add delete "
		    "describe\n"
		    "Snapshot commands:	 listsnap selectsnap revert\n"
		    "Instance commands:	 refresh\n"
		    "Property group commands: listpg addpg delpg\n"
		    "Property commands:	 listprop setprop delprop editprop\n"
		    "Property value commands: addpropvalue delpropvalue "
		    "setenv unsetenv\n"
		    "Notification parameters: "
		    "listnotify setnotify delnotify\n"));
		return;
	}

	for (i = 0; help_messages[i].message != NULL; ++i) {
		if (help_messages[i].token == com) {
			warn(gettext("Usage: %s\n"),
			    gettext(help_messages[i].message));
			return;
		}
	}

	warn(gettext("Unknown command.\n"));
}