OpenSolaris_b135/cmd/svc/svcprop/svcprop.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 2007 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

#pragma ident	"%Z%%M%	%I%	%E% SMI"

/*
 * svcprop - report service configuration properties
 */

#include <locale.h>
#include <libintl.h>
#include <libscf.h>
#include <libscf_priv.h>
#include <libuutil.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <strings.h>
#include <assert.h>

#ifndef TEXT_DOMAIN
#define	TEXT_DOMAIN	"SUNW_OST_OSCMD"
#endif /* TEXT_DOMAIN */

/*
 * Error functions.  These can change if the quiet (-q) option is used.
 */
static void (*warn)(const char *, ...) = uu_warn;
static void (*die)(const char *, ...) = uu_die;

/*
 * Entity encapsulation.  This allows me to treat services and instances
 * similarly, and avoid duplicating process_ent().
 */
typedef struct {
	char type;			/* !=0: service, 0: instance */
	union {
		scf_service_t *svc;
		scf_instance_t *inst;
	} u;
} scf_entityp_t;

#define	ENT_INSTANCE	0

#define	SCF_ENTITY_SET_TO_SERVICE(ent, s)	{ ent.type = 1; ent.u.svc = s; }

#define	SCF_ENTITY_SET_TO_INSTANCE(ent, i)	\
	{ ent.type = ENT_INSTANCE; ent.u.inst = i; }

#define	scf_entity_get_pg(ent, name, pg) \
	(ent.type ? scf_service_get_pg(ent.u.svc, name, pg) : \
	scf_instance_get_pg(ent.u.inst, name, pg))

#define	scf_entity_to_fmri(ent, buf, buf_sz) \
	(ent.type ? scf_service_to_fmri(ent.u.svc, buf, buf_sz) : \
	scf_instance_to_fmri(ent.u.inst, buf, buf_sz))

#define	SCF_ENTITY_TYPE_NAME(ent)	(ent.type ? "service" : "instance")

/*
 * Data structure for -p arguments.  Since they may be name or name/name, we
 * just track the components.
 */
typedef struct svcprop_prop_node {
	uu_list_node_t	spn_list_node;
	const char	*spn_comp1;
	const char	*spn_comp2;
} svcprop_prop_node_t;

static uu_list_pool_t	*prop_pool;
static uu_list_t	*prop_list;

static scf_handle_t *hndl;
static ssize_t max_scf_name_length;
static ssize_t max_scf_value_length;
static ssize_t max_scf_fmri_length;

/* Options */
static int quiet = 0;			/* No output. Nothing found, exit(1) */
static int types = 0;			/* Display types of properties. */
static int verbose = 0;			/* Print not found errors to stderr. */
static int fmris = 0;			/* Display full FMRIs for properties. */
static int wait = 0;			/* Wait mode. */
static char *snapshot = "running";	/* Snapshot to use. */
static int Cflag = 0;			/* C option supplied */
static int cflag = 0;			/* c option supplied */
static int sflag = 0;			/* s option supplied */
static int return_code;			/* main's return code */

#define	PRINT_NOPROP_ERRORS	(!quiet || verbose)

/*
 * For unexpected libscf errors.  The ending newline is necessary to keep
 * uu_die() from appending the errno error.
 */
static void
scfdie()
{
	die(gettext("Unexpected libscf error: %s.  Exiting.\n"),
	    scf_strerror(scf_error()));
}

static void *
safe_malloc(size_t sz)
{
	void *p;

	p = malloc(sz);
	if (p == NULL)
		die(gettext("Could not allocate memory"));

	return (p);
}

static void
usage()
{
	(void) fprintf(stderr, gettext("Usage: %1$s [-fqtv] "
	    "[-C | -c | -s snapshot] "
	    "[-p [name/]name]... \n"
	    "         {FMRI | pattern}...\n"
	    "       %1$s -w [-fqtv] [-p [name/]name] "
	    "{FMRI | pattern}\n"), uu_getpname());
	exit(UU_EXIT_USAGE);
}

/*
 * Return an allocated copy of str, with the Bourne shell's metacharacters
 * escaped by '\'.
 *
 * What about unicode?
 */
static char *
quote_for_shell(const char *str)
{
	const char *sp;
	char *dst, *dp;
	size_t dst_len;

	const char * const metachars = ";&()|^<>\n \t\\\"\'`";

	if (str[0] == '\0')
		return (strdup("\"\""));

	dst_len = 0;
	for (sp = str; *sp != '\0'; ++sp) {
		++dst_len;

		if (strchr(metachars, *sp) != NULL)
			++dst_len;
	}

	if (sp - str == dst_len)
		return (strdup(str));

	dst = safe_malloc(dst_len + 1);

	for (dp = dst, sp = str; *sp != '\0'; ++dp, ++sp) {
		if (strchr(metachars, *sp) != NULL)
			*dp++ = '\\';

		*dp = *sp;
	}
	*dp = '\0';

	return (dst);
}

static void
print_value(scf_value_t *val)
{
	char *buf, *qbuf;
	ssize_t bufsz, r;

	bufsz = scf_value_get_as_string(val, NULL, 0) + 1;
	if (bufsz - 1 < 0)
		scfdie();

	buf = safe_malloc(bufsz);

	r = scf_value_get_as_string(val, buf, bufsz);
	assert(r + 1 == bufsz);

	qbuf = quote_for_shell(buf);
	(void) fputs(qbuf, stdout);

	free(qbuf);
	free(buf);
}

/*
 * Display a property's values on a line.  If types is true, prepend
 * identification (the FMRI if fmris is true, pg/prop otherwise) and the type
 * of the property.
 */
static void
display_prop(scf_propertygroup_t *pg, scf_property_t *prop)
{
	scf_value_t *val;
	scf_iter_t *iter;
	int ret, first, err;

	const char * const permission_denied_emsg =
	    gettext("Permission denied.\n");

	if (types) {
		scf_type_t ty;
		char *buf;
		size_t buf_sz;

		if (fmris) {
			buf_sz = max_scf_fmri_length + 1;
			buf = safe_malloc(buf_sz);

			if (scf_property_to_fmri(prop, buf, buf_sz) == -1)
				scfdie();
			(void) fputs(buf, stdout);

			free(buf);
		} else {
			buf_sz = max_scf_name_length + 1;
			buf = safe_malloc(buf_sz);

			if (scf_pg_get_name(pg, buf, buf_sz) < 0)
				scfdie();
			(void) fputs(buf, stdout);
			(void) putchar('/');

			if (scf_property_get_name(prop, buf, buf_sz) < 0)
				scfdie();
			(void) fputs(buf, stdout);

			free(buf);
		}

		(void) putchar(' ');

		if (scf_property_type(prop, &ty) == -1)
			scfdie();
		(void) fputs(scf_type_to_string(ty), stdout);
		(void) putchar(' ');
	}

	if ((iter = scf_iter_create(hndl)) == NULL ||
	    (val = scf_value_create(hndl)) == NULL)
		scfdie();

	if (scf_iter_property_values(iter, prop) == -1)
		scfdie();

	first = 1;
	while ((ret = scf_iter_next_value(iter, val)) == 1) {
		if (first)
			first = 0;
		else
			(void) putchar(' ');
		print_value(val);
	}
	if (ret == -1) {
		err = scf_error();
		if (err == SCF_ERROR_PERMISSION_DENIED) {
			if (uu_list_numnodes(prop_list) > 0)
				die(permission_denied_emsg);
		} else {
			scfdie();
		}
	}

	(void) putchar('\n');

	scf_iter_destroy(iter);
	(void) scf_value_destroy(val);
}

/*
 * display_prop() all of the properties in the given property group.  Force
 * types to true so identification will be displayed.
 */
static void
display_pg(scf_propertygroup_t *pg)
{
	scf_property_t *prop;
	scf_iter_t *iter;
	int ret;

	types = 1;	/* Always display types for whole propertygroups. */

	if ((prop = scf_property_create(hndl)) == NULL ||
	    (iter = scf_iter_create(hndl)) == NULL)
		scfdie();

	if (scf_iter_pg_properties(iter, pg) == -1)
		scfdie();

	while ((ret = scf_iter_next_property(iter, prop)) == 1)
		display_prop(pg, prop);
	if (ret == -1)
		scfdie();

	scf_iter_destroy(iter);
	scf_property_destroy(prop);
}

/*
 * Common code to execute when a nonexistant property is encountered.
 */
static void
noprop_common_action()
{
	if (!PRINT_NOPROP_ERRORS)
		/* We're not printing errors, so we can cut out early. */
		exit(UU_EXIT_FATAL);

	return_code = UU_EXIT_FATAL;
}

/*
 * Iterate the properties of a service or an instance when no snapshot
 * is specified.
 */
static int
scf_iter_entity_pgs(scf_iter_t *iter, scf_entityp_t ent)
{
	int ret = 0;

	if (ent.type) {
		/*
		 * If we are displaying properties for a service,
		 * treat it as though it were a composed, current
		 * lookup. (implicit cflag) However, if a snapshot
		 * was specified, fail.
		 */
		if (sflag)
			die(gettext("Only instances have "
			    "snapshots.\n"));
		ret = scf_iter_service_pgs(iter, ent.u.svc);
	} else {
		if (Cflag)
			ret = scf_iter_instance_pgs(iter, ent.u.inst);
		else
			ret = scf_iter_instance_pgs_composed(iter, ent.u.inst,
			    NULL);
	}
	return (ret);
}

/*
 * Return a snapshot for the supplied instance and snapshot name.
 */
static scf_snapshot_t *
get_snapshot(const scf_instance_t *inst, const char *snapshot)
{
	scf_snapshot_t *snap = scf_snapshot_create(hndl);

	if (snap == NULL)
		scfdie();

	if (scf_instance_get_snapshot(inst, snapshot, snap) == -1) {
		switch (scf_error()) {
		case SCF_ERROR_INVALID_ARGUMENT:
			die(gettext("Invalid snapshot name.\n"));
			/* NOTREACHED */

		case SCF_ERROR_NOT_FOUND:
			if (sflag == 0) {
				scf_snapshot_destroy(snap);
				snap = NULL;
			} else
				die(gettext("No such snapshot.\n"));
			break;

		default:
			scfdie();
		}
	}

	return (snap);
}

/*
 * Entity (service or instance): If there are -p options,
 * display_{pg,prop}() the named property groups and/or properties.  Otherwise
 * display_pg() all property groups.
 */
static void
process_ent(scf_entityp_t ent)
{
	scf_snapshot_t *snap = NULL;
	scf_propertygroup_t *pg;
	scf_property_t *prop;
	scf_iter_t *iter;
	svcprop_prop_node_t *spn;
	int ret, err;

	if (uu_list_numnodes(prop_list) == 0) {
		if (quiet)
			return;

		if ((pg = scf_pg_create(hndl)) == NULL ||
		    (iter = scf_iter_create(hndl)) == NULL)
			scfdie();

		if (cflag || Cflag || ent.type != ENT_INSTANCE) {
			if (scf_iter_entity_pgs(iter, ent) == -1)
				scfdie();
		} else {
			if (snapshot != NULL)
				snap = get_snapshot(ent.u.inst, snapshot);

			if (scf_iter_instance_pgs_composed(iter, ent.u.inst,
			    snap) == -1)
				scfdie();
			if (snap)
				scf_snapshot_destroy(snap);
		}

		while ((ret = scf_iter_next_pg(iter, pg)) == 1)
			display_pg(pg);
		if (ret == -1)
			scfdie();

		/*
		 * In normal usage, i.e. against the running snapshot,
		 * we must iterate over the current non-persistent
		 * pg's.
		 */
		if (sflag == 0 && snap != NULL) {
			scf_iter_reset(iter);
			if (scf_iter_instance_pgs_composed(iter, ent.u.inst,
			    NULL) == -1)
				scfdie();
			while ((ret = scf_iter_next_pg(iter, pg)) == 1) {
				uint32_t flags;

				if (scf_pg_get_flags(pg, &flags) == -1)
					scfdie();
				if (flags & SCF_PG_FLAG_NONPERSISTENT)
					display_pg(pg);
			}
		}
		if (ret == -1)
			scfdie();

		scf_iter_destroy(iter);
		scf_pg_destroy(pg);

		return;
	}

	if ((pg = scf_pg_create(hndl)) == NULL ||
	    (prop = scf_property_create(hndl)) == NULL)
		scfdie();

	if (ent.type == ENT_INSTANCE && snapshot != NULL)
		snap = get_snapshot(ent.u.inst, snapshot);

	for (spn = uu_list_first(prop_list);
	    spn != NULL;
	    spn = uu_list_next(prop_list, spn)) {
		if (ent.type == ENT_INSTANCE) {
			if (Cflag)
				ret = scf_instance_get_pg(ent.u.inst,
				    spn->spn_comp1, pg);
			else
				ret = scf_instance_get_pg_composed(ent.u.inst,
				    snap, spn->spn_comp1, pg);
			err = scf_error();

			/*
			 * If we didn't find it in the specified snapshot, use
			 * the current values if the pg is nonpersistent.
			 */
			if (ret == -1 && !Cflag &&snap != NULL && err ==
			    SCF_ERROR_NOT_FOUND) {
				ret = scf_instance_get_pg_composed(
				    ent.u.inst, NULL, spn->spn_comp1,
				    pg);

				if (ret == 0) {
					uint32_t flags;

					if (scf_pg_get_flags(pg, &flags) == -1)
						scfdie();
					if ((flags & SCF_PG_FLAG_NONPERSISTENT)
					    == 0) {
						ret = -1;
					}
				}
			}
		} else {
			/*
			 * If we are displaying properties for a service,
			 * treat it as though it were a composed, current
			 * lookup. (implicit cflag) However, if a snapshot
			 * was specified, fail.
			 */
			if (sflag)
				die(gettext("Only instances have "
				    "snapshots.\n"));
			ret = scf_entity_get_pg(ent, spn->spn_comp1, pg);
			err = scf_error();
		}
		if (ret == -1) {
			if (err != SCF_ERROR_NOT_FOUND)
				scfdie();

			if (PRINT_NOPROP_ERRORS) {
				char *buf;

				buf = safe_malloc(max_scf_fmri_length + 1);
				if (scf_entity_to_fmri(ent, buf,
				    max_scf_fmri_length + 1) == -1)
					scfdie();

				uu_warn(gettext("Couldn't find property group "
				    "`%s' for %s `%s'.\n"), spn->spn_comp1,
				    SCF_ENTITY_TYPE_NAME(ent), buf);

				free(buf);
			}

			noprop_common_action();

			continue;
		}

		if (spn->spn_comp2 == NULL) {
			if (!quiet)
				display_pg(pg);
			continue;
		}

		if (scf_pg_get_property(pg, spn->spn_comp2, prop) == -1) {
			if (scf_error() != SCF_ERROR_NOT_FOUND)
				scfdie();

			if (PRINT_NOPROP_ERRORS) {
				char *buf;

				buf = safe_malloc(max_scf_fmri_length + 1);
				if (scf_entity_to_fmri(ent, buf,
				    max_scf_fmri_length + 1) == -1)
					scfdie();

				/* FMRI syntax knowledge */
				uu_warn(gettext("Couldn't find property "
				    "`%s/%s' for %s `%s'.\n"), spn->spn_comp1,
				    spn->spn_comp2, SCF_ENTITY_TYPE_NAME(ent),
				    buf);

				free(buf);
			}

			noprop_common_action();

			continue;
		}

		if (!quiet)
			display_prop(pg, prop);
	}

	scf_property_destroy(prop);
	scf_pg_destroy(pg);
	if (snap)
		scf_snapshot_destroy(snap);
}

/*
 * Without -p options, just call display_pg().  Otherwise display_prop() the
 * named properties of the property group.
 */
static void
process_pg(scf_propertygroup_t *pg)
{
	scf_property_t *prop;
	svcprop_prop_node_t *spn;

	if (uu_list_first(prop_list) == NULL) {
		if (quiet)
			return;

		display_pg(pg);
		return;
	}

	prop = scf_property_create(hndl);
	if (prop == NULL)
		scfdie();

	for (spn = uu_list_first(prop_list);
	    spn != NULL;
	    spn = uu_list_next(prop_list, spn)) {
		if (spn->spn_comp2 != NULL) {
			char *buf;

			buf = safe_malloc(max_scf_fmri_length + 1);
			if (scf_pg_to_fmri(pg, buf, max_scf_fmri_length + 1) ==
			    -1)
				scfdie();

			uu_xdie(UU_EXIT_USAGE, gettext("-p argument `%s/%s' "
			    "has too many components for property "
			    "group `%s'.\n"), spn->spn_comp1, spn->spn_comp2,
			    buf);

			free(buf);
		}

		if (scf_pg_get_property(pg, spn->spn_comp1, prop) == 0) {
			if (!quiet)
				display_prop(pg, prop);
			continue;
		}

		if (scf_error() != SCF_ERROR_NOT_FOUND)
			scfdie();

		if (PRINT_NOPROP_ERRORS) {
			char *buf;

			buf = safe_malloc(max_scf_fmri_length + 1);
			if (scf_pg_to_fmri(pg, buf, max_scf_fmri_length + 1) ==
			    -1)
				scfdie();

			uu_warn(gettext("Couldn't find property `%s' in "
			    "property group `%s'.\n"), spn->spn_comp1, buf);

			free(buf);
		}

		noprop_common_action();
	}
}

/*
 * If there are -p options, show the error.  Otherwise just call
 * display_prop().
 */
static void
process_prop(scf_propertygroup_t *pg, scf_property_t *prop)
{
	if (uu_list_first(prop_list) != NULL) {
		uu_warn(gettext("The -p option cannot be used with property "
		    "operands.\n"));
		usage();
	}

	if (quiet)
		return;

	display_prop(pg, prop);
}

/* Decode an operand & dispatch. */
/* ARGSUSED */
static int
process_fmri(void *unused, scf_walkinfo_t *wip)
{
	scf_entityp_t ent;

	/* Multiple matches imply multiple entities. */
	if (wip->count > 1)
		types = fmris = 1;

	if (wip->prop != NULL) {
		process_prop(wip->pg, wip->prop);
	} else if (wip->pg != NULL) {
		process_pg(wip->pg);
	} else if (wip->inst != NULL) {
		SCF_ENTITY_SET_TO_INSTANCE(ent, wip->inst);
		process_ent(ent);
	} else {
		/* scf_walk_fmri() won't let this happen */
		assert(wip->svc != NULL);
		SCF_ENTITY_SET_TO_SERVICE(ent, wip->svc);
		process_ent(ent);
	}

	return (0);
}

static void
add_prop(char *property)
{
	svcprop_prop_node_t *p, *last;
	char *slash;

	const char * const invalid_component_emsg =
	    gettext("Invalid component name `%s'.\n");

	/* FMRI syntax knowledge. */
	slash = strchr(property, '/');
	if (slash != NULL) {
		if (strchr(slash + 1, '/') != NULL) {
			uu_warn(gettext("-p argument `%s' has too many "
			    "components.\n"), property);
			usage();
		}
	}

	if (slash != NULL)
		*slash = '\0';

	p = safe_malloc(sizeof (svcprop_prop_node_t));
	uu_list_node_init(p, &p->spn_list_node, prop_pool);

	p->spn_comp1 = property;
	p->spn_comp2 = (slash == NULL) ? NULL : slash + 1;

	if (uu_check_name(p->spn_comp1, UU_NAME_DOMAIN) == -1)
		uu_xdie(UU_EXIT_USAGE, invalid_component_emsg, p->spn_comp1);
	if (p->spn_comp2 != NULL &&
	    uu_check_name(p->spn_comp2, UU_NAME_DOMAIN) == -1)
		uu_xdie(UU_EXIT_USAGE, invalid_component_emsg, p->spn_comp2);

	last = uu_list_last(prop_list);
	if (last != NULL) {
		if ((last->spn_comp2 == NULL) ^ (p->spn_comp2 == NULL)) {
			/*
			 * The -p options have mixed numbers of components.
			 * If they both turn out to be valid, then the
			 * single-component ones will specify property groups,
			 * so we need to turn on types to keep the output of
			 * display_prop() consistent with display_pg().
			 */
			types = 1;
		}
	}

	(void) uu_list_insert_after(prop_list, NULL, p);
}


/*
 * Wait for a property group or property change.
 *
 * Extract a pg and optionally a property name from fmri & prop_list.
 * _scf_pg_wait() for the pg, and display_pg(pg) or display_prop(pg, prop)
 * when it returns.
 */
/* ARGSUSED */
static int
do_wait(void *unused, scf_walkinfo_t *wip)
{
	scf_property_t *prop;
	scf_propertygroup_t *lpg, *pg;
	const char *propname;
	svcprop_prop_node_t *p;

	const char *emsg_not_found = gettext("Not found.\n");

	if ((lpg = scf_pg_create(hndl)) == NULL ||
	    (prop = scf_property_create(hndl)) == NULL)
		scfdie();

	if (wip->prop != NULL) {
		if (uu_list_numnodes(prop_list) > 0)
			uu_xdie(UU_EXIT_USAGE, gettext("-p cannot be used with "
			    "property FMRIs.\n"));
		pg = wip->pg;

		assert(strrchr(wip->fmri, '/') != NULL);
		propname = strrchr(wip->fmri, '/') + 1;

	} else if (wip->pg != NULL) {
		p = uu_list_first(prop_list);

		if (p != NULL) {
			if (p->spn_comp2 != NULL)
				uu_xdie(UU_EXIT_USAGE, gettext("-p argument "
				    "\"%s/%s\" has too many components for "
				    "property group %s.\n"),
				    p->spn_comp1, p->spn_comp2, wip->fmri);

			propname = p->spn_comp1;

			if (scf_pg_get_property(wip->pg, propname, prop) !=
			    SCF_SUCCESS) {
				switch (scf_error()) {
				case SCF_ERROR_INVALID_ARGUMENT:
					uu_xdie(UU_EXIT_USAGE,
					    gettext("Invalid property name "
					    "\"%s\".\n"), propname);

					/* NOTREACHED */

				case SCF_ERROR_NOT_FOUND:
					die(emsg_not_found);

					/* NOTREACHED */

				default:
					scfdie();
				}
			}
		} else {
			propname = NULL;
		}

		pg = wip->pg;

	} else if (wip->inst != NULL) {

		p = uu_list_first(prop_list);
		if (p == NULL)
			uu_xdie(UU_EXIT_USAGE,
			    gettext("Cannot wait for an instance.\n"));

		if (scf_instance_get_pg(wip->inst, p->spn_comp1, lpg) !=
		    SCF_SUCCESS) {
			switch (scf_error()) {
			case SCF_ERROR_INVALID_ARGUMENT:
				uu_xdie(UU_EXIT_USAGE, gettext("Invalid "
				    "property group name \"%s\".\n"),
				    p->spn_comp1);

			case SCF_ERROR_NOT_FOUND:
				die(emsg_not_found);

				/* NOTREACHED */

			default:
				scfdie();
			}
		}

		propname = p->spn_comp2;

		if (propname != NULL) {
			if (scf_pg_get_property(lpg, propname, prop) !=
			    SCF_SUCCESS) {
				switch (scf_error()) {
				case SCF_ERROR_INVALID_ARGUMENT:
					uu_xdie(UU_EXIT_USAGE,
					    gettext("Invalid property name "
					    "\"%s\".\n"), propname);

				case SCF_ERROR_NOT_FOUND:
					die(emsg_not_found);

					/* NOTREACHED */

				default:
					scfdie();
				}
			}
		}

		pg = lpg;

	} else if (wip->svc != NULL) {

		p = uu_list_first(prop_list);
		if (p == NULL)
			uu_xdie(UU_EXIT_USAGE,
			    gettext("Cannot wait for a service.\n"));

		if (scf_service_get_pg(wip->svc, p->spn_comp1, lpg) !=
		    SCF_SUCCESS) {
			switch (scf_error()) {
			case SCF_ERROR_INVALID_ARGUMENT:
				uu_xdie(UU_EXIT_USAGE, gettext("Invalid "
				    "property group name \"%s\".\n"),
				    p->spn_comp1);

			case SCF_ERROR_NOT_FOUND:
				die(emsg_not_found);

			default:
				scfdie();
			}
		}

		propname = p->spn_comp2;

		if (propname != NULL) {
			if (scf_pg_get_property(lpg, propname, prop) !=
			    SCF_SUCCESS) {
				switch (scf_error()) {
				case SCF_ERROR_INVALID_ARGUMENT:
					uu_xdie(UU_EXIT_USAGE,
					    gettext("Invalid property name "
					    "\"%s\".\n"), propname);

					/* NOTREACHED */

				case SCF_ERROR_NOT_FOUND:
					die(emsg_not_found);

					/* NOTREACHED */

				default:
					scfdie();
				}
			}
		}

		pg = lpg;

	} else {
		uu_xdie(UU_EXIT_USAGE, gettext("FMRI must specify an entity, "
		    "property group, or property.\n"));
	}

	for (;;) {
		int ret;

		ret = _scf_pg_wait(pg, -1);
		if (ret != SCF_SUCCESS)
			scfdie();

		ret = scf_pg_update(pg);
		if (ret < 0) {
			if (scf_error() != SCF_ERROR_DELETED)
				scfdie();

			die(emsg_not_found);
		}
		if (ret == SCF_COMPLETE)
			break;
	}

	if (propname != NULL) {
		if (scf_pg_get_property(pg, propname, prop) == SCF_SUCCESS) {
			if (!quiet)
				display_prop(pg, prop);
		} else {
			if (scf_error() != SCF_ERROR_NOT_FOUND)
				scfdie();

			if (PRINT_NOPROP_ERRORS)
				uu_warn(emsg_not_found);

			return_code = UU_EXIT_FATAL;
		}
	} else {
		if (!quiet)
			display_pg(pg);
	}

	scf_property_destroy(prop);
	scf_pg_destroy(lpg);

	return (0);
}

/*
 * These functions replace uu_warn() and uu_die() when the quiet (-q) option is
 * used, and silently ignore any output.
 */

/*ARGSUSED*/
static void
quiet_warn(const char *fmt, ...)
{
	/* Do nothing */
}

/*ARGSUSED*/
static void
quiet_die(const char *fmt, ...)
{
	exit(UU_EXIT_FATAL);
}

int
main(int argc, char *argv[])
{
	int c;
	scf_walk_callback callback;
	int flags;
	int err;

	(void) setlocale(LC_ALL, "");
	(void) textdomain(TEXT_DOMAIN);

	return_code = UU_EXIT_OK;

	(void) uu_setpname(argv[0]);

	prop_pool = uu_list_pool_create("properties",
	    sizeof (svcprop_prop_node_t),
	    offsetof(svcprop_prop_node_t, spn_list_node), NULL, 0);
	if (prop_pool == NULL)
		uu_die("%s\n", uu_strerror(uu_error()));

	prop_list = uu_list_create(prop_pool, NULL, 0);

	while ((c = getopt(argc, argv, "Ccfp:qs:tvw")) != -1) {
		switch (c) {
		case 'C':
			if (cflag || sflag || wait)
				usage();	/* Not with -c, -s or -w */
			Cflag++;
			snapshot = NULL;
			break;

		case 'c':
			if (Cflag || sflag || wait)
				usage();	/* Not with -C, -s or -w */
			cflag++;
			snapshot = NULL;
			break;

		case 'f':
			types = 1;
			fmris = 1;
			break;

		case 'p':
			add_prop(optarg);
			break;

		case 'q':
			quiet = 1;
			warn = quiet_warn;
			die = quiet_die;
			break;

		case 's':
			if (Cflag || cflag || wait)
				usage();	/* Not with -C, -c or -w */
			snapshot = optarg;
			sflag++;
			break;

		case 't':
			types = 1;
			break;

		case 'v':
			verbose = 1;
			break;

		case 'w':
			if (Cflag || cflag || sflag)
				usage();	/* Not with -C, -c or -s */
			wait = 1;
			break;

		case '?':
			switch (optopt) {
			case 'p':
				usage();

			default:
				break;
			}

			/* FALLTHROUGH */

		default:
			usage();
		}
	}

	if (optind == argc)
		usage();

	max_scf_name_length = scf_limit(SCF_LIMIT_MAX_NAME_LENGTH);
	max_scf_value_length = scf_limit(SCF_LIMIT_MAX_VALUE_LENGTH);
	max_scf_fmri_length = scf_limit(SCF_LIMIT_MAX_FMRI_LENGTH);
	if (max_scf_name_length == -1 || max_scf_value_length == -1 ||
	    max_scf_fmri_length == -1)
		scfdie();

	hndl = scf_handle_create(SCF_VERSION);
	if (hndl == NULL)
		scfdie();
	if (scf_handle_bind(hndl) == -1)
		die(gettext("Could not connect to configuration repository: "
		    "%s.\n"), scf_strerror(scf_error()));

	flags = SCF_WALK_PROPERTY | SCF_WALK_SERVICE | SCF_WALK_EXPLICIT;

	if (wait) {
		if (uu_list_numnodes(prop_list) > 1)
			usage();

		if (argc - optind > 1)
			usage();

		callback = do_wait;

	} else {
		callback = process_fmri;

		flags |= SCF_WALK_MULTIPLE;
	}

	if ((err = scf_walk_fmri(hndl, argc - optind, argv + optind, flags,
	    callback, NULL, &return_code, warn)) != 0) {
		warn(gettext("failed to iterate over instances: %s\n"),
		    scf_strerror(err));
		return_code = UU_EXIT_FATAL;
	}

	scf_handle_destroy(hndl);

	return (return_code);
}