root/usr/src/cmd/svc/svcadm/svcadm.c
/*
 * 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.
 */

/*
 * Copyright 2017 RackTop Systems.
 * Copyright 2020, Joyent, Inc. All rights reserved.
 * Copyright 2023 Oxide Computer Company
 */

/*
 * svcadm - request adminstrative actions for service instances
 */

#include <locale.h>
#include <libintl.h>
#include <libscf.h>
#include <libscf_priv.h>
#include <libcontract.h>
#include <libcontract_priv.h>
#include <sys/contract/process.h>
#include <libuutil.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <procfs.h>
#include <assert.h>
#include <errno.h>
#include <zone.h>

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

/* Must be a power of two */
#define HT_BUCKETS      64

/*
 * Exit codes for enable and disable -s.
 */
#define EXIT_SVC_FAILURE        3
#define EXIT_DEP_FAILURE        4

#define WALK_FLAGS      (SCF_WALK_UNIPARTIAL | SCF_WALK_MULTIPLE)

/*
 * How long we will wait (in seconds) for a service to change state
 * before re-checking its dependencies.
 */
#define WAIT_INTERVAL           3

#define bad_error(func, err)                                            \
        uu_panic("%s:%d: %s() failed with unexpected error %d.\n",      \
            __FILE__, __LINE__, (func), (err));

struct ht_elt {
        struct ht_elt   *next;
        boolean_t       active;
        char            str[1];
};


/*
 * Callback data for enable/disable.
 */
#define SET_ENABLED     0x1
#define SET_TEMPORARY   0x2
#define SET_RECURSIVE   0x4

typedef struct {
        char ed_comment[SCF_COMMENT_MAX_LENGTH];
        int ed_flags;
} enable_data_t;

/*
 * Callback data for mark.
 */
#define MARK_IMMEDIATE  0x1
#define MARK_TEMPORARY  0x2

typedef struct {
        int md_flags;
} mark_data_t;

scf_handle_t *h;
ssize_t max_scf_fmri_sz;
static const char *emsg_permission_denied;
static const char *emsg_nomem;
static const char *emsg_create_pg_perm_denied;
static const char *emsg_pg_perm_denied;
static const char *emsg_prop_perm_denied;
static const char *emsg_no_service;

static int exit_status = 0;
static int verbose = 0;
static char *scratch_fmri;
static char *g_zonename = NULL;
static char svcstate[80];
static boolean_t svcsearch = B_FALSE;

static struct ht_elt **visited;

void do_scfdie(int lineno) __NORETURN;
static void usage_milestone(void) __NORETURN;
static void set_astring_prop(const char *, const char *, const char *,
    uint32_t, const char *, const char *);
static void pr_warn(const char *format, ...);

/*
 * Visitors from synch.c, needed for enable -s and disable -s.
 */
extern int is_enabled(scf_instance_t *);
extern int has_potential(scf_instance_t *, int);

void
do_scfdie(int lineno)
{
        scf_error_t err;

        switch (err = scf_error()) {
        case SCF_ERROR_CONNECTION_BROKEN:
                uu_die(gettext("Connection to repository server broken.  "
                    "Exiting.\n"));
                /* NOTREACHED */

        case SCF_ERROR_BACKEND_READONLY:
                uu_die(gettext("Repository is read-only.  Exiting.\n"));
                /* NOTREACHED */

        default:
#ifdef NDEBUG
                uu_die(gettext("Unexpected libscf error: %s.  Exiting.\n"),
                    scf_strerror(err));
#else
                uu_die("Unexpected libscf error on line %d: %s.\n", lineno,
                    scf_strerror(err));
#endif
        }
}

#define scfdie()        do_scfdie(__LINE__)

static void
usage()
{
        (void) fprintf(stderr, gettext(
        "Usage: %1$s [-S <state>] [-v] [-Z | -z zone] [cmd [args ... ]]\n\n"
        "\t%1$s enable [-rst] [<service> ...]\t- enable and online service(s)\n"
        "\t%1$s disable [-c comment] [-st] [<service> ...] - disable "
        "service(s)\n"
        "\t%1$s restart [-d] [<service> ...]\t- restart specified service(s)\n"
        "\t%1$s refresh [<service> ...]\t\t- re-read service configuration\n"
        "\t%1$s mark [-It] <state> [<service> ...] - set maintenance state\n"
        "\t%1$s clear [<service> ...]\t\t- clear maintenance state\n"
        "\t%1$s milestone [-d] <milestone>\t- advance to a service milestone\n"
        "\n\t"
        "Services can be specified using an FMRI, abbreviation, or fnmatch(7)\n"
        "\tpattern, as shown in these examples for svc:/network/smtp:sendmail\n"
        "\n"
        "\t%1$s <cmd> svc:/network/smtp:sendmail\n"
        "\t%1$s <cmd> network/smtp:sendmail\n"
        "\t%1$s <cmd> network/*mail\n"
        "\t%1$s <cmd> network/smtp\n"
        "\t%1$s <cmd> smtp:sendmail\n"
        "\t%1$s <cmd> smtp\n"
        "\t%1$s <cmd> sendmail\n"), uu_getpname());

        exit(UU_EXIT_USAGE);
}


/*
 * FMRI hash table for recursive enable.
 */

static uint32_t
hash_fmri(const char *str)
{
        uint32_t h = 0, g;
        const char *p;

        /* Generic hash function from uts/common/os/modhash.c . */
        for (p = str; *p != '\0'; ++p) {
                h = (h << 4) + *p;
                if ((g = (h & 0xf0000000)) != 0) {
                        h ^= (g >> 24);
                        h ^= g;
                }
        }

        return (h);
}

/*
 * Return 1 if str has been visited, 0 if it has not, and -1 if memory could not
 * be allocated.
 */
static int
visited_find_or_add(const char *str, struct ht_elt **hep)
{
        uint32_t h;
        uint_t i;
        struct ht_elt *he;

        h = hash_fmri(str);
        i = h & (HT_BUCKETS - 1);

        for (he = visited[i]; he != NULL; he = he->next) {
                if (strcmp(he->str, str) == 0) {
                        if (hep)
                                *hep = he;
                        return (1);
                }
        }

        he = malloc(offsetof(struct ht_elt, str) + strlen(str) + 1);
        if (he == NULL)
                return (-1);

        (void) strcpy(he->str, str);

        he->next = visited[i];
        visited[i] = he;

        if (hep)
                *hep = he;
        return (0);
}


/*
 * Returns 0, ECANCELED if pg is deleted, ENOENT if propname doesn't exist,
 * EINVAL if the property is not of boolean type or has no values, and E2BIG
 * if it has more than one value.  *bp is set if 0 or E2BIG is returned.
 */
int
get_bool_prop(scf_propertygroup_t *pg, const char *propname, uint8_t *bp)
{
        scf_property_t *prop;
        scf_value_t *val;
        int ret;

        if ((prop = scf_property_create(h)) == NULL ||
            (val = scf_value_create(h)) == NULL)
                scfdie();

        if (scf_pg_get_property(pg, propname, prop) != 0) {
                switch (scf_error()) {
                case SCF_ERROR_DELETED:
                        ret = ECANCELED;
                        goto out;

                case SCF_ERROR_NOT_FOUND:
                        ret = ENOENT;
                        goto out;

                case SCF_ERROR_NOT_SET:
                        assert(0);
                        abort();
                        /* NOTREACHED */

                default:
                        scfdie();
                }
        }

        if (scf_property_get_value(prop, val) == 0) {
                ret = 0;
        } else {
                switch (scf_error()) {
                case SCF_ERROR_DELETED:
                        ret = ENOENT;
                        goto out;

                case SCF_ERROR_NOT_FOUND:
                        ret = EINVAL;
                        goto out;

                case SCF_ERROR_CONSTRAINT_VIOLATED:
                        ret = E2BIG;
                        break;

                case SCF_ERROR_NOT_SET:
                        assert(0);
                        abort();
                        /* NOTREACHED */

                default:
                        scfdie();
                }
        }

        if (scf_value_get_boolean(val, bp) != 0) {
                if (scf_error() != SCF_ERROR_TYPE_MISMATCH)
                        scfdie();

                ret = EINVAL;
                goto out;
        }

out:
        scf_value_destroy(val);
        scf_property_destroy(prop);
        return (ret);
}

/*
 * Returns 0, EPERM, or EROFS.
 */
static int
set_bool_prop(scf_propertygroup_t *pg, const char *propname, boolean_t b)
{
        scf_value_t *v;
        scf_transaction_t *tx;
        scf_transaction_entry_t *ent;
        int ret = 0, r;

        if ((tx = scf_transaction_create(h)) == NULL ||
            (ent = scf_entry_create(h)) == NULL ||
            (v = scf_value_create(h)) == NULL)
                scfdie();

        scf_value_set_boolean(v, b);

        for (;;) {
                if (scf_transaction_start(tx, pg) == -1) {
                        switch (scf_error()) {
                        case SCF_ERROR_PERMISSION_DENIED:
                                ret = EPERM;
                                goto out;

                        case SCF_ERROR_BACKEND_READONLY:
                                ret = EROFS;
                                goto out;

                        default:
                                scfdie();
                        }
                }

                if (scf_transaction_property_change_type(tx, ent, propname,
                    SCF_TYPE_BOOLEAN) != 0) {
                        if (scf_error() != SCF_ERROR_NOT_FOUND)
                                scfdie();

                        if (scf_transaction_property_new(tx, ent, propname,
                            SCF_TYPE_BOOLEAN) != 0)
                                scfdie();
                }

                r = scf_entry_add_value(ent, v);
                assert(r == 0);

                r = scf_transaction_commit(tx);
                if (r == 1)
                        break;

                scf_transaction_reset(tx);

                if (r != 0) {
                        switch (scf_error()) {
                        case SCF_ERROR_PERMISSION_DENIED:
                                ret = EPERM;
                                goto out;

                        case SCF_ERROR_BACKEND_READONLY:
                                ret = EROFS;
                                goto out;

                        default:
                                scfdie();
                        }
                }

                if (scf_pg_update(pg) == -1)
                        scfdie();
        }

out:
        scf_transaction_destroy(tx);
        scf_entry_destroy(ent);
        scf_value_destroy(v);
        return (ret);
}

/*
 * Gets the single astring value of the propname property of pg.  prop & v are
 * scratch space.  Returns the length of the string on success or
 *   -ENOENT - pg has no property named propname
 *   -E2BIG - property has no values or multiple values
 *   -EINVAL - property type is not compatible with astring
 */
ssize_t
get_astring_prop(const scf_propertygroup_t *pg, const char *propname,
    scf_property_t *prop, scf_value_t *v, char *buf, size_t bufsz)
{
        ssize_t sz;

        if (scf_pg_get_property(pg, propname, prop) != 0) {
                if (scf_error() != SCF_ERROR_NOT_FOUND)
                        scfdie();

                return (-ENOENT);
        }

        if (scf_property_get_value(prop, v) != 0) {
                switch (scf_error()) {
                case SCF_ERROR_NOT_FOUND:
                case SCF_ERROR_CONSTRAINT_VIOLATED:
                        return (-E2BIG);

                default:
                        scfdie();
                }
        }

        sz = scf_value_get_astring(v, buf, bufsz);
        if (sz < 0) {
                if (scf_error() != SCF_ERROR_TYPE_MISMATCH)
                        scfdie();

                return (-EINVAL);
        }

        return (sz);
}

/*
 * Returns 0 or EPERM.
 */
static int
pg_get_or_add(const scf_instance_t *inst, const char *pgname,
    const char *pgtype, uint32_t pgflags, scf_propertygroup_t *pg)
{
again:
        if (scf_instance_get_pg(inst, pgname, pg) == 0)
                return (0);

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

        if (scf_instance_add_pg(inst, pgname, pgtype, pgflags, pg) == 0)
                return (0);

        switch (scf_error()) {
        case SCF_ERROR_EXISTS:
                goto again;

        case SCF_ERROR_PERMISSION_DENIED:
                return (EPERM);

        default:
                scfdie();
                /* NOTREACHED */
        }
}

static int
my_ct_name(char *out, size_t len)
{
        ct_stathdl_t st;
        char *ct_fmri;
        ctid_t ct;
        int fd, errno, ret;

        if ((ct = getctid()) == -1)
                uu_die(gettext("Could not get contract id for process"));

        fd = contract_open(ct, "process", "status", O_RDONLY);

        if ((errno = ct_status_read(fd, CTD_ALL, &st)) != 0)
                uu_warn(gettext("Could not read status of contract "
                    "%ld: %s.\n"), ct, strerror(errno));

        if ((errno = ct_pr_status_get_svc_fmri(st, &ct_fmri)) != 0)
                uu_warn(gettext("Could not get svc_fmri for contract "
                    "%ld: %s.\n"), ct, strerror(errno));

        ret = strlcpy(out, ct_fmri, len);

        ct_status_free(st);
        (void) close(fd);

        return (ret);
}

/*
 * Set auxiliary_tty and auxiliary_fmri properties in restarter_actions pg to
 * communicate whether the action is requested from a tty and the fmri of the
 * responsible process.
 *
 * Returns 0, EPERM, or EROFS
 */
static int
restarter_setup(const char *fmri, const scf_instance_t *inst)
{
        boolean_t b = B_FALSE;
        scf_propertygroup_t *pg = NULL;
        int ret = 0;

        if ((pg = scf_pg_create(h)) == NULL)
                scfdie();

        if (pg_get_or_add(inst, SCF_PG_RESTARTER_ACTIONS,
            SCF_PG_RESTARTER_ACTIONS_TYPE, SCF_PG_RESTARTER_ACTIONS_FLAGS,
            pg) == EPERM) {
                if (!verbose)
                        uu_warn(emsg_permission_denied, fmri);
                else
                        uu_warn(emsg_create_pg_perm_denied, fmri,
                            SCF_PG_RESTARTER_ACTIONS);

                ret = EPERM;
                goto out;
        }

        /* Set auxiliary_tty property */
        if (isatty(STDIN_FILENO))
                b = B_TRUE;

        /* Create and set state to disabled */
        switch (set_bool_prop(pg, SCF_PROPERTY_AUX_TTY, b)) {
        case 0:
                break;

        case EPERM:
                if (!verbose)
                        uu_warn(emsg_permission_denied, fmri);
                else
                        uu_warn(emsg_prop_perm_denied, fmri,
                            SCF_PG_RESTARTER_ACTIONS, SCF_PROPERTY_AUX_TTY);

                ret = EPERM;
                goto out;
                /* NOTREACHED */

        case EROFS:
                /* Shouldn't happen, but it can. */
                if (!verbose)
                        uu_warn(gettext("%s: Repository read-only.\n"), fmri);
                else
                        uu_warn(gettext("%s: Could not set %s/%s "
                            "(repository read-only).\n"), fmri,
                            SCF_PG_RESTARTER_ACTIONS, SCF_PROPERTY_AUX_TTY);

                ret = EROFS;
                goto out;
                /* NOTREACHED */

        default:
                scfdie();
        }

        if (my_ct_name(scratch_fmri, max_scf_fmri_sz) > 0) {
                set_astring_prop(fmri, SCF_PG_RESTARTER_ACTIONS,
                    SCF_PG_RESTARTER_ACTIONS_TYPE,
                    SCF_PG_RESTARTER_ACTIONS_FLAGS,
                    SCF_PROPERTY_AUX_FMRI, scratch_fmri);
        } else {
                uu_warn(gettext("%s: Could not set %s/%s: "
                    "my_ct_name failed.\n"), fmri,
                    SCF_PG_RESTARTER_ACTIONS, SCF_PROPERTY_AUX_FMRI);
        }

out:
        scf_pg_destroy(pg);
        return (ret);
}

static int
delete_prop(const char *fmri, scf_instance_t *inst, const char *pgname,
    const char *propname)
{
        int r = scf_instance_delete_prop(inst, pgname, propname);

        switch (r) {
        case 0:
                break;

        case ECANCELED:
                uu_warn(emsg_no_service, fmri);
                break;

        case EACCES:
                uu_warn(gettext("Could not delete %s/%s "
                    "property of %s: backend access denied.\n"),
                    pgname, propname, fmri);
                break;

        case EROFS:
                uu_warn(gettext("Could not delete %s/%s "
                    "property of %s: backend is read-only.\n"),
                    pgname, propname, fmri);
                break;

        default:
                bad_error("scf_instance_delete_prop", r);
        }

        return (r);
}

/*
 * Returns 0, EPERM, or EROFS.
 */
static int
set_enabled_props(scf_propertygroup_t *pg, enable_data_t *ed)
{
        scf_transaction_entry_t *ent1;
        scf_transaction_entry_t *ent2;
        scf_transaction_t *tx;
        scf_value_t *v2;
        scf_value_t *v1;
        int ret = 0, r;

        if ((tx = scf_transaction_create(h)) == NULL ||
            (ent1 = scf_entry_create(h)) == NULL ||
            (ent2 = scf_entry_create(h)) == NULL ||
            (v1 = scf_value_create(h)) == NULL ||
            (v2 = scf_value_create(h)) == NULL)
                scfdie();

        for (;;) {
                if (scf_transaction_start(tx, pg) == -1) {
                        switch (scf_error()) {
                        case SCF_ERROR_PERMISSION_DENIED:
                                ret = EPERM;
                                goto out;

                        case SCF_ERROR_BACKEND_READONLY:
                                ret = EROFS;
                                goto out;

                        default:
                                scfdie();
                        }
                }

                if (scf_transaction_property_change_type(tx, ent1,
                    SCF_PROPERTY_ENABLED, SCF_TYPE_BOOLEAN) != 0) {
                        if (scf_error() != SCF_ERROR_NOT_FOUND)
                                scfdie();

                        if (scf_transaction_property_new(tx, ent1,
                            SCF_PROPERTY_ENABLED, SCF_TYPE_BOOLEAN) != 0)
                                scfdie();
                }

                scf_value_set_boolean(v1, !!(ed->ed_flags & SET_ENABLED));

                r = scf_entry_add_value(ent1, v1);
                assert(r == 0);

                if (scf_transaction_property_change_type(tx, ent2,
                    SCF_PROPERTY_COMMENT, SCF_TYPE_ASTRING) != 0) {
                        if (scf_error() != SCF_ERROR_NOT_FOUND)
                                scfdie();

                        if (scf_transaction_property_new(tx, ent2,
                            SCF_PROPERTY_COMMENT, SCF_TYPE_ASTRING) != 0)
                                scfdie();
                }

                if (scf_value_set_astring(v2, ed->ed_comment) != SCF_SUCCESS)
                        scfdie();

                if (scf_entry_add_value(ent2, v2) != SCF_SUCCESS)
                        scfdie();

                r = scf_transaction_commit(tx);
                if (r == 1)
                        break;

                scf_transaction_reset(tx);

                if (r != 0) {
                        switch (scf_error()) {
                        case SCF_ERROR_PERMISSION_DENIED:
                                ret = EPERM;
                                goto out;

                        case SCF_ERROR_BACKEND_READONLY:
                                ret = EROFS;
                                goto out;

                        default:
                                scfdie();
                        }
                }

                if (scf_pg_update(pg) == -1)
                        scfdie();
        }

out:
        scf_transaction_destroy(tx);
        scf_entry_destroy(ent1);
        scf_entry_destroy(ent2);
        scf_value_destroy(v1);
        scf_value_destroy(v2);
        return (ret);
}

/*
 * Enable or disable an instance.  SET_TEMPORARY modifications apply to
 * general_ovr/ property group.
 */
static void
set_inst_enabled(const char *fmri, scf_instance_t *inst, enable_data_t *ed)
{
        scf_propertygroup_t *pg;
        uint8_t b;
        const char *pgname = NULL;      /* For emsg_pg_perm_denied */

        pg = scf_pg_create(h);
        if (pg == NULL)
                scfdie();

        if (restarter_setup(fmri, inst))
                goto out;

        /*
         * An instance's configuration is incomplete if general/enabled
         * doesn't exist. Create both the property group and property
         * here if they don't exist.
         */
        pgname = SCF_PG_GENERAL;
        if (pg_get_or_add(inst, pgname, SCF_PG_GENERAL_TYPE,
            SCF_PG_GENERAL_FLAGS, pg) != 0)
                goto eperm;

        if (get_bool_prop(pg, SCF_PROPERTY_ENABLED, &b) != 0) {
                /* Create and set state to disabled */
                switch (set_bool_prop(pg, SCF_PROPERTY_ENABLED, B_FALSE)) {
                case 0:
                        break;

                case EPERM:
                        goto eperm;

                case EROFS:
                        /* Shouldn't happen, but it can. */
                        if (!verbose)
                                uu_warn(gettext("%s: Repository read-only.\n"),
                                    fmri);
                        else
                                uu_warn(gettext("%s: Could not set %s/%s "
                                    "(repository read-only).\n"), fmri,
                                    SCF_PG_GENERAL, SCF_PROPERTY_ENABLED);
                        goto out;

                default:
                        assert(0);
                        abort();
                }
        }

        if (ed->ed_flags & SET_TEMPORARY) {
                pgname = SCF_PG_GENERAL_OVR;
                if (pg_get_or_add(inst, pgname, SCF_PG_GENERAL_OVR_TYPE,
                    SCF_PG_GENERAL_OVR_FLAGS, pg) != 0)
                        goto eperm;

                switch (set_enabled_props(pg, ed)) {
                case 0:
                        break;

                case EPERM:
                        goto eperm;

                case EROFS:
                        /* Shouldn't happen, but it can. */
                        if (!verbose)
                                uu_warn(gettext("%s: Repository read-only.\n"),
                                    fmri);
                        else
                                uu_warn(gettext("%s: Could not set %s/%s "
                                    "(repository read-only).\n"), fmri,
                                    SCF_PG_GENERAL_OVR, SCF_PROPERTY_ENABLED);
                        goto out;

                default:
                        assert(0);
                        abort();
                }

                if (verbose)
                        (void) printf((ed->ed_flags & SET_ENABLED) ?
                            gettext("%s temporarily enabled.\n") :
                            gettext("%s temporarily disabled.\n"), fmri);
        } else {
again:
                /*
                 * Both pg and property should exist since we created
                 * them earlier. However, there's still a chance that
                 * someone may have deleted the property out from under
                 * us.
                 */
                if (pg_get_or_add(inst, pgname, SCF_PG_GENERAL_TYPE,
                    SCF_PG_GENERAL_FLAGS, pg) != 0)
                        goto eperm;

                switch (set_enabled_props(pg, ed)) {
                case 0:
                        break;

                case EPERM:
                        goto eperm;

                case EROFS:
                        /*
                         * If general/enabled is already set the way we want,
                         * proceed.
                         */
                        switch (get_bool_prop(pg, SCF_PROPERTY_ENABLED, &b)) {
                        case 0:
                                if (!(b) == !(ed->ed_flags & SET_ENABLED))
                                        break;
                                /* FALLTHROUGH */

                        case ENOENT:
                        case EINVAL:
                        case E2BIG:
                                if (!verbose)
                                        uu_warn(gettext("%s: Repository "
                                            "read-only.\n"), fmri);
                                else
                                        uu_warn(gettext("%s: Could not set "
                                            "%s/%s (repository read-only).\n"),
                                            fmri, SCF_PG_GENERAL,
                                            SCF_PROPERTY_ENABLED);
                                goto out;

                        case ECANCELED:
                                goto again;

                        default:
                                assert(0);
                                abort();
                        }
                        break;

                default:
                        assert(0);
                        abort();
                }

                switch (delete_prop(fmri, inst, SCF_PG_GENERAL_OVR,
                    SCF_PROPERTY_ENABLED)) {
                case 0:
                        break;

                case EPERM:
                        goto eperm;

                default:
                        goto out;
                }

                switch (delete_prop(fmri, inst, SCF_PG_GENERAL_OVR,
                    SCF_PROPERTY_COMMENT)) {
                case 0:
                        break;

                case EPERM:
                        goto eperm;

                default:
                        goto out;
                }

                if (verbose) {
                        (void) printf((ed->ed_flags & SET_ENABLED) ?
                            gettext("%s enabled.\n") :
                            gettext("%s disabled.\n"), fmri);
                }
        }

        scf_pg_destroy(pg);
        return;

eperm:
        assert(pgname != NULL);
        if (!verbose)
                uu_warn(emsg_permission_denied, fmri);
        else
                uu_warn(emsg_pg_perm_denied, fmri, pgname);

out:
        scf_pg_destroy(pg);
        exit_status = 1;
}

/*
 * Set inst to the instance which corresponds to fmri.  If fmri identifies
 * a service with a single instance, get that instance.
 *
 * Fails with
 *   ENOTSUP - fmri has an unsupported scheme
 *   EINVAL - fmri is invalid
 *   ENOTDIR - fmri does not identify a service or instance
 *   ENOENT - could not locate instance
 *   E2BIG - fmri is a service with multiple instances (warning not printed)
 */
static int
get_inst_mult(const char *fmri, scf_instance_t *inst)
{
        char *cfmri;
        const char *svc_name, *inst_name, *pg_name;
        scf_service_t *svc;
        scf_instance_t *inst2;
        scf_iter_t *iter;
        int ret;

        if (strncmp(fmri, "lrc:", sizeof ("lrc:") - 1) == 0) {
                uu_warn(gettext("FMRI \"%s\" is a legacy service.\n"), fmri);
                exit_status = 1;
                return (ENOTSUP);
        }

        cfmri = strdup(fmri);
        if (cfmri == NULL)
                uu_die(emsg_nomem);

        if (scf_parse_svc_fmri(cfmri, NULL, &svc_name, &inst_name, &pg_name,
            NULL) != SCF_SUCCESS) {
                free(cfmri);
                uu_warn(gettext("FMRI \"%s\" is invalid.\n"), fmri);
                exit_status = 1;
                return (EINVAL);
        }

        free(cfmri);

        if (svc_name == NULL || pg_name != NULL) {
                uu_warn(gettext(
                    "FMRI \"%s\" does not designate a service or instance.\n"),
                    fmri);
                exit_status = 1;
                return (ENOTDIR);
        }

        if (inst_name != NULL) {
                if (scf_handle_decode_fmri(h, fmri, NULL, NULL, inst, NULL,
                    NULL, SCF_DECODE_FMRI_EXACT) == 0)
                        return (0);

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

                uu_warn(gettext("No such instance \"%s\".\n"), fmri);
                exit_status = 1;

                return (ENOENT);
        }

        if ((svc = scf_service_create(h)) == NULL ||
            (inst2 = scf_instance_create(h)) == NULL ||
            (iter = scf_iter_create(h)) == NULL)
                scfdie();

        if (scf_handle_decode_fmri(h, fmri, NULL, svc, NULL, NULL, NULL,
            SCF_DECODE_FMRI_EXACT) != SCF_SUCCESS) {
                if (scf_error() != SCF_ERROR_NOT_FOUND)
                        scfdie();

                uu_warn(emsg_no_service, fmri);
                exit_status = 1;

                ret = ENOENT;
                goto out;
        }

        /* If the service has only one child, use it. */
        if (scf_iter_service_instances(iter, svc) != SCF_SUCCESS)
                scfdie();

        ret = scf_iter_next_instance(iter, inst);
        if (ret < 0)
                scfdie();
        if (ret != 1) {
                uu_warn(gettext("Service \"%s\" has no instances.\n"),
                    fmri);
                exit_status = 1;
                ret = ENOENT;
                goto out;
        }

        ret = scf_iter_next_instance(iter, inst2);
        if (ret < 0)
                scfdie();

        if (ret != 0) {
                ret = E2BIG;
                goto out;
        }

        ret = 0;

out:
        scf_iter_destroy(iter);
        scf_instance_destroy(inst2);
        scf_service_destroy(svc);
        return (ret);
}

/*
 * Same as get_inst_mult(), but on E2BIG prints a warning and returns ENOENT.
 */
static int
get_inst(const char *fmri, scf_instance_t *inst)
{
        int r;

        r = get_inst_mult(fmri, inst);
        if (r != E2BIG)
                return (r);

        uu_warn(gettext("operation on service %s is ambiguous; "
            "instance specification needed.\n"), fmri);
        return (ENOENT);
}

static char *
inst_get_fmri(const scf_instance_t *inst)
{
        ssize_t sz;

        sz = scf_instance_to_fmri(inst, scratch_fmri, max_scf_fmri_sz);
        if (sz < 0)
                scfdie();
        if (sz >= max_scf_fmri_sz)
                uu_die(gettext("scf_instance_to_fmri() returned unexpectedly "
                    "long value.\n"));

        return (scratch_fmri);
}

static ssize_t
dep_get_astring(const char *fmri, const char *pgname,
    const scf_propertygroup_t *pg, const char *propname, scf_property_t *prop,
    scf_value_t *v, char *buf, size_t bufsz)
{
        ssize_t sz;

        sz = get_astring_prop(pg, propname, prop, v, buf, bufsz);
        if (sz >= 0)
                return (sz);

        switch (-sz) {
        case ENOENT:
                uu_warn(gettext("\"%s\" is misconfigured (\"%s\" dependency "
                    "lacks \"%s\" property.)\n"), fmri, pgname, propname);
                return (-1);

        case E2BIG:
                uu_warn(gettext("\"%s\" is misconfigured (\"%s/%s\" property "
                    "is not single-valued.)\n"), fmri, pgname, propname);
                return (-1);

        case EINVAL:
                uu_warn(gettext("\"%s\" is misconfigured (\"%s/%s\" property "
                    "is not of astring type.)\n"), fmri, pgname, propname);
                return (-1);

        default:
                assert(0);
                abort();
                /* NOTREACHED */
        }
}

static boolean_t
multiple_instances(scf_iter_t *iter, scf_value_t *v, char *buf)
{
        int count = 0, r;
        boolean_t ret;
        scf_instance_t *inst;

        inst = scf_instance_create(h);
        if (inst == NULL)
                scfdie();

        for (;;) {
                r = scf_iter_next_value(iter, v);
                if (r == 0) {
                        ret = B_FALSE;
                        goto out;
                }
                if (r != 1)
                        scfdie();

                if (scf_value_get_astring(v, buf, max_scf_fmri_sz) < 0)
                        scfdie();

                switch (get_inst_mult(buf, inst)) {
                case 0:
                        ++count;
                        if (count > 1) {
                                ret = B_TRUE;
                                goto out;
                        }
                        break;

                case ENOTSUP:
                case EINVAL:
                case ENOTDIR:
                case ENOENT:
                        continue;

                case E2BIG:
                        ret = B_TRUE;
                        goto out;

                default:
                        assert(0);
                        abort();
                }
        }

out:
        scf_instance_destroy(inst);
        return (ret);
}

/*
 * Enable the service or instance identified by fmri and its dependencies,
 * recursively.  Specifically, call get_inst(fmri), enable the result, and
 * recurse on its restarter and the dependencies.  To avoid duplication of
 * effort or looping around a dependency cycle, each FMRI is entered into the
 * "visited" hash table.  While recursing, the hash table entry is marked
 * "active", so that if we come upon it again, we know we've hit a cycle.
 * exclude_all and optional_all dependencies are ignored.  require_any
 * dependencies are followed only if they comprise a single service; otherwise
 * the user is warned.
 *
 * fmri must point to a writable max_scf_fmri_sz buffer.  Returns EINVAL if fmri
 * is invalid, E2BIG if fmri identifies a service with multiple instances, ELOOP
 * on cycle detection, or 0 on success.
 */
static int
enable_fmri_rec(char *fmri, enable_data_t *ed)
{
        scf_instance_t *inst;
        scf_snapshot_t *snap;
        scf_propertygroup_t *pg;
        scf_property_t *prop;
        scf_value_t *v;
        scf_iter_t *pg_iter, *val_iter;
        scf_type_t ty;
        char *buf, *pgname;
        ssize_t name_sz, len, sz;
        int ret;
        struct ht_elt *he;

        len = scf_canonify_fmri(fmri, fmri, max_scf_fmri_sz);
        if (len < 0) {
                assert(scf_error() == SCF_ERROR_INVALID_ARGUMENT);
                return (EINVAL);
        }
        assert(len < max_scf_fmri_sz);

        switch (visited_find_or_add(fmri, &he)) {
        case 0:
                he->active = B_TRUE;
                break;

        case 1:
                return (he->active ? ELOOP : 0);

        case -1:
                uu_die(emsg_nomem);

        default:
                assert(0);
                abort();
        }

        inst = scf_instance_create(h);
        if (inst == NULL)
                scfdie();

        switch (get_inst_mult(fmri, inst)) {
        case 0:
                break;

        case E2BIG:
                he->active = B_FALSE;
                return (E2BIG);

        default:
                he->active = B_FALSE;
                return (0);
        }

        set_inst_enabled(fmri, inst, ed);

        if ((snap = scf_snapshot_create(h)) == NULL ||
            (pg = scf_pg_create(h)) == NULL ||
            (prop = scf_property_create(h)) == NULL ||
            (v = scf_value_create(h)) == NULL ||
            (pg_iter = scf_iter_create(h)) == NULL ||
            (val_iter = scf_iter_create(h)) == NULL)
                scfdie();

        buf = malloc(max_scf_fmri_sz);
        if (buf == NULL)
                uu_die(emsg_nomem);

        name_sz = scf_limit(SCF_LIMIT_MAX_NAME_LENGTH);
        if (name_sz < 0)
                scfdie();
        ++name_sz;
        pgname = malloc(name_sz);
        if (pgname == NULL)
                uu_die(emsg_nomem);

        if (scf_instance_get_snapshot(inst, "running", snap) != 0) {
                if (scf_error() != SCF_ERROR_NOT_FOUND)
                        scfdie();

                scf_snapshot_destroy(snap);
                snap = NULL;
        }

        /* Enable restarter */
        if (scf_instance_get_pg_composed(inst, snap, SCF_PG_GENERAL, pg) != 0) {
                if (scf_error() != SCF_ERROR_NOT_FOUND)
                        scfdie();

                uu_warn(gettext("\"%s\" is misconfigured (lacks \"%s\" "
                    "property group).\n"), fmri, SCF_PG_GENERAL);
                ret = 0;
                goto out;
        }

        sz = get_astring_prop(pg, SCF_PROPERTY_RESTARTER, prop, v, buf,
            max_scf_fmri_sz);
        if (sz > max_scf_fmri_sz) {
                uu_warn(gettext("\"%s\" is misconfigured (the value of "
                    "\"%s/%s\" is too long).\n"), fmri, SCF_PG_GENERAL,
                    SCF_PROPERTY_RESTARTER);
                ret = 0;
                goto out;
        } else if (sz >= 0) {
                switch (enable_fmri_rec(buf, ed)) {
                case 0:
                        break;

                case EINVAL:
                        uu_warn(gettext("Restarter FMRI for \"%s\" is "
                            "invalid.\n"), fmri);
                        break;

                case E2BIG:
                        uu_warn(gettext("Restarter FMRI for \"%s\" identifies "
                            "a service with multiple instances.\n"), fmri);
                        break;

                case ELOOP:
                        ret = ELOOP;
                        goto out;

                default:
                        assert(0);
                        abort();
                }
        } else if (sz < 0) {
                switch (-sz) {
                case ENOENT:
                        break;

                case E2BIG:
                        uu_warn(gettext("\"%s\" is misconfigured (\"%s/%s\" "
                            "property is not single-valued).\n"), fmri,
                            SCF_PG_GENERAL, SCF_PROPERTY_RESTARTER);
                        ret = 0;
                        goto out;

                case EINVAL:
                        uu_warn(gettext("\"%s\" is misconfigured (\"%s/%s\" "
                            "property is not of astring type).\n"), fmri,
                            SCF_PG_GENERAL, SCF_PROPERTY_RESTARTER);
                        ret = 0;
                        goto out;

                default:
                        assert(0);
                        abort();
                }
        }

        if (scf_iter_instance_pgs_typed_composed(pg_iter, inst, snap,
            SCF_GROUP_DEPENDENCY) == -1)
                scfdie();

        while (scf_iter_next_pg(pg_iter, pg) > 0) {
                len = scf_pg_get_name(pg, pgname, name_sz);
                if (len < 0)
                        scfdie();
                assert(len < name_sz);

                if (dep_get_astring(fmri, pgname, pg, SCF_PROPERTY_TYPE, prop,
                    v, buf, max_scf_fmri_sz) < 0)
                        continue;

                if (strcmp(buf, "service") != 0)
                        continue;

                if (dep_get_astring(fmri, pgname, pg, SCF_PROPERTY_GROUPING,
                    prop, v, buf, max_scf_fmri_sz) < 0)
                        continue;

                if (strcmp(buf, SCF_DEP_EXCLUDE_ALL) == 0 ||
                    strcmp(buf, SCF_DEP_OPTIONAL_ALL) == 0)
                        continue;

                if (strcmp(buf, SCF_DEP_REQUIRE_ALL) != 0 &&
                    strcmp(buf, SCF_DEP_REQUIRE_ANY) != 0) {
                        uu_warn(gettext("Dependency \"%s\" of \"%s\" has "
                            "unknown type \"%s\".\n"), pgname, fmri, buf);
                        continue;
                }

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

                        uu_warn(gettext("\"%s\" is misconfigured (\"%s\" "
                            "dependency lacks \"%s\" property.)\n"), fmri,
                            pgname, SCF_PROPERTY_ENTITIES);
                        continue;
                }

                if (scf_property_type(prop, &ty) != SCF_SUCCESS)
                        scfdie();

                if (ty != SCF_TYPE_FMRI) {
                        uu_warn(gettext("\"%s\" is misconfigured (property "
                            "\"%s/%s\" is not of fmri type).\n"), fmri, pgname,
                            SCF_PROPERTY_ENTITIES);
                        continue;
                }

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

                if (strcmp(buf, SCF_DEP_REQUIRE_ANY) == 0) {
                        if (multiple_instances(val_iter, v, buf)) {
                                (void) printf(gettext("%s requires one of:\n"),
                                    fmri);

                                if (scf_iter_property_values(val_iter, prop) !=
                                    0)
                                        scfdie();

                                for (;;) {
                                        int r;

                                        r = scf_iter_next_value(val_iter, v);
                                        if (r == 0)
                                                break;
                                        if (r != 1)
                                                scfdie();

                                        if (scf_value_get_astring(v, buf,
                                            max_scf_fmri_sz) < 0)
                                                scfdie();

                                        (void) fputs("  ", stdout);
                                        (void) puts(buf);
                                }

                                continue;
                        }

                        /*
                         * Since there's only one instance, we can enable it.
                         * Reset val_iter and continue.
                         */
                        if (scf_iter_property_values(val_iter, prop) != 0)
                                scfdie();
                }

                for (;;) {
                        ret = scf_iter_next_value(val_iter, v);
                        if (ret == 0)
                                break;
                        if (ret != 1)
                                scfdie();

                        if (scf_value_get_astring(v, buf, max_scf_fmri_sz) ==
                            -1)
                                scfdie();

                        switch (enable_fmri_rec(buf, ed)) {
                        case 0:
                                break;

                        case EINVAL:
                                uu_warn(gettext("\"%s\" dependency of \"%s\" "
                                    "has invalid FMRI \"%s\".\n"), pgname,
                                    fmri, buf);
                                break;

                        case E2BIG:
                                uu_warn(gettext("%s depends on %s, which has "
                                    "multiple instances.\n"), fmri, buf);
                                break;

                        case ELOOP:
                                ret = ELOOP;
                                goto out;

                        default:
                                assert(0);
                                abort();
                        }
                }
        }

        ret = 0;

out:
        he->active = B_FALSE;

        free(buf);
        free(pgname);

        (void) scf_value_destroy(v);
        scf_property_destroy(prop);
        scf_pg_destroy(pg);
        scf_snapshot_destroy(snap);
        scf_iter_destroy(pg_iter);
        scf_iter_destroy(val_iter);

        return (ret);
}

/*
 * fmri here is only used for verbose messages.
 */
static void
set_inst_action(const char *fmri, const scf_instance_t *inst,
    const char *action)
{
        scf_transaction_t *tx;
        scf_transaction_entry_t *ent;
        scf_propertygroup_t *pg;
        scf_property_t *prop;
        scf_value_t *v;
        int ret;
        int64_t t;
        hrtime_t timestamp;

        const char * const scf_pg_restarter_actions = SCF_PG_RESTARTER_ACTIONS;

        if ((pg = scf_pg_create(h)) == NULL ||
            (prop = scf_property_create(h)) == NULL ||
            (v = scf_value_create(h)) == NULL ||
            (tx = scf_transaction_create(h)) == NULL ||
            (ent = scf_entry_create(h)) == NULL)
                scfdie();

        if (restarter_setup(fmri, inst)) {
                exit_status = 1;
                goto out;
        }

        if (scf_instance_get_pg(inst, scf_pg_restarter_actions, pg) == -1) {
                if (scf_error() != SCF_ERROR_NOT_FOUND)
                        scfdie();

                /* Try creating the restarter_actions property group. */
                if (scf_instance_add_pg(inst, scf_pg_restarter_actions,
                    SCF_PG_RESTARTER_ACTIONS_TYPE,
                    SCF_PG_RESTARTER_ACTIONS_FLAGS, pg) == -1) {
                        switch (scf_error()) {
                        case SCF_ERROR_EXISTS:
                                /* Someone must have added it. */
                                break;

                        case SCF_ERROR_PERMISSION_DENIED:
                                if (!verbose)
                                        uu_warn(emsg_permission_denied, fmri);
                                else
                                        uu_warn(emsg_create_pg_perm_denied,
                                            fmri, scf_pg_restarter_actions);
                                goto out;

                        default:
                                scfdie();
                        }
                }
        }

        /*
         * If we lose the transaction race and need to retry, there are 2
         * potential other winners:
         *      - another process setting actions
         *      - the restarter marking the action complete
         * Therefore, re-read the property every time through the loop before
         * making any decisions based on their values.
         */
        do {
                timestamp = gethrtime();

                if (scf_transaction_start(tx, pg) == -1) {
                        if (scf_error() != SCF_ERROR_PERMISSION_DENIED)
                                scfdie();

                        if (!verbose)
                                uu_warn(emsg_permission_denied, fmri);
                        else
                                uu_warn(emsg_pg_perm_denied, fmri,
                                    scf_pg_restarter_actions);
                        goto out;
                }

                if (scf_pg_get_property(pg, action, prop) == -1) {
                        if (scf_error() != SCF_ERROR_NOT_FOUND)
                                scfdie();
                        if (scf_transaction_property_new(tx, ent,
                            action, SCF_TYPE_INTEGER) == -1)
                                scfdie();
                        goto action_set;
                } else {
                        if (scf_transaction_property_change_type(tx, ent,
                            action, SCF_TYPE_INTEGER) == -1)
                                scfdie();
                }

                if (scf_property_get_value(prop, v) == -1) {
                        switch (scf_error()) {
                        case SCF_ERROR_CONSTRAINT_VIOLATED:
                        case SCF_ERROR_NOT_FOUND:
                                /* Misconfigured, so set anyway. */
                                goto action_set;

                        default:
                                scfdie();
                        }
                } else {
                        if (scf_value_get_integer(v, &t) == -1) {
                                assert(scf_error() == SCF_ERROR_TYPE_MISMATCH);
                                goto action_set;
                        }
                        if (t > timestamp)
                                break;
                }

action_set:
                scf_value_set_integer(v, timestamp);
                if (scf_entry_add_value(ent, v) == -1)
                        scfdie();

                ret = scf_transaction_commit(tx);
                if (ret == -1) {
                        if (scf_error() != SCF_ERROR_PERMISSION_DENIED)
                                scfdie();

                        if (!verbose)
                                uu_warn(emsg_permission_denied, fmri);
                        else
                                uu_warn(emsg_prop_perm_denied, fmri,
                                    scf_pg_restarter_actions, action);
                        scf_transaction_reset(tx);
                        goto out;
                }

                scf_transaction_reset(tx);

                if (ret == 0) {
                        if (scf_pg_update(pg) == -1)
                                scfdie();
                }
        } while (ret == 0);

        if (verbose)
                (void) printf(gettext("Action %s set for %s.\n"), action, fmri);

out:
        scf_value_destroy(v);
        scf_entry_destroy(ent);
        scf_transaction_destroy(tx);
        scf_property_destroy(prop);
        scf_pg_destroy(pg);
}

/*
 * Get the state of inst.  state should point to a buffer of
 * MAX_SCF_STATE_STRING_SZ bytes.  Returns 0 on success or -1 if
 *   no restarter property group
 *   no state property
 *   state property is misconfigured (wrong type, not single-valued)
 *   state value is too long
 * In these cases, fmri is used to print a warning.
 *
 * If pgp is non-NULL, a successful call to inst_get_state will store
 * the SCF_PG_RESTARTER property group in *pgp, and the caller will be
 * responsible for calling scf_pg_destroy on the property group.
 */
int
inst_get_state(scf_instance_t *inst, char *state, const char *fmri,
    scf_propertygroup_t **pgp)
{
        scf_propertygroup_t *pg;
        scf_property_t *prop;
        scf_value_t *val;
        int ret = -1;
        ssize_t szret;

        if ((pg = scf_pg_create(h)) == NULL ||
            (prop = scf_property_create(h)) == NULL ||
            (val = scf_value_create(h)) == NULL)
                scfdie();

        if (scf_instance_get_pg(inst, SCF_PG_RESTARTER, pg) != SCF_SUCCESS) {
                if (scf_error() != SCF_ERROR_NOT_FOUND)
                        scfdie();

                uu_warn(gettext("%s is misconfigured (lacks \"%s\" property "
                    "group).\n"), fmri ? fmri : inst_get_fmri(inst),
                    SCF_PG_RESTARTER);
                goto out;
        }

        szret = get_astring_prop(pg, SCF_PROPERTY_STATE, prop, val, state,
            MAX_SCF_STATE_STRING_SZ);
        if (szret < 0) {
                switch (-szret) {
                case ENOENT:
                        uu_warn(gettext("%s is misconfigured (\"%s\" property "
                            "group lacks \"%s\" property).\n"),
                            fmri ? fmri : inst_get_fmri(inst), SCF_PG_RESTARTER,
                            SCF_PROPERTY_STATE);
                        goto out;

                case E2BIG:
                        uu_warn(gettext("%s is misconfigured (\"%s/%s\" "
                            "property is not single-valued).\n"),
                            fmri ? fmri : inst_get_fmri(inst), SCF_PG_RESTARTER,
                            SCF_PROPERTY_STATE);
                        goto out;

                case EINVAL:
                        uu_warn(gettext("%s is misconfigured (\"%s/%s\" "
                            "property is not of type astring).\n"),
                            fmri ? fmri : inst_get_fmri(inst), SCF_PG_RESTARTER,
                            SCF_PROPERTY_STATE);
                        goto out;

                default:
                        assert(0);
                        abort();
                }
        }
        if (szret >= MAX_SCF_STATE_STRING_SZ) {
                uu_warn(gettext("%s is misconfigured (\"%s/%s\" property value "
                    "is too long).\n"), fmri ? fmri : inst_get_fmri(inst),
                    SCF_PG_RESTARTER, SCF_PROPERTY_STATE);
                goto out;
        }

        ret = 0;
        if (pgp)
                *pgp = pg;

out:
        (void) scf_value_destroy(val);
        scf_property_destroy(prop);
        if (ret || pgp == NULL)
                scf_pg_destroy(pg);
        return (ret);
}

static void
set_astring_prop(const char *fmri, const char *pgname, const char *pgtype,
    uint32_t pgflags, const char *propname, const char *str)
{
        scf_instance_t *inst;
        scf_propertygroup_t *pg;
        scf_property_t *prop;
        scf_value_t *val;
        scf_transaction_t *tx;
        scf_transaction_entry_t *txent;
        int ret;

        inst = scf_instance_create(h);
        if (inst == NULL)
                scfdie();

        if (get_inst(fmri, inst) != 0)
                return;

        if ((pg = scf_pg_create(h)) == NULL ||
            (prop = scf_property_create(h)) == NULL ||
            (val = scf_value_create(h)) == NULL ||
            (tx = scf_transaction_create(h)) == NULL ||
            (txent = scf_entry_create(h)) == NULL)
                scfdie();

        if (scf_instance_get_pg(inst, pgname, pg) != SCF_SUCCESS) {
                if (scf_error() != SCF_ERROR_NOT_FOUND)
                        scfdie();

                if (scf_instance_add_pg(inst, pgname, pgtype, pgflags, pg) !=
                    SCF_SUCCESS) {
                        switch (scf_error()) {
                        case SCF_ERROR_EXISTS:
                                if (scf_instance_get_pg(inst, pgname, pg) !=
                                    SCF_SUCCESS) {
                                        if (scf_error() != SCF_ERROR_NOT_FOUND)
                                                scfdie();

                                        uu_warn(gettext("Repository write "
                                            "contention.\n"));
                                        goto out;
                                }
                                break;

                        case SCF_ERROR_PERMISSION_DENIED:
                                if (!verbose)
                                        uu_warn(emsg_permission_denied, fmri);
                                else
                                        uu_warn(emsg_create_pg_perm_denied,
                                            fmri, pgname);
                                goto out;

                        default:
                                scfdie();
                        }
                }
        }

        do {
                if (scf_transaction_start(tx, pg) != SCF_SUCCESS) {
                        if (scf_error() != SCF_ERROR_PERMISSION_DENIED)
                                scfdie();

                        if (!verbose)
                                uu_warn(emsg_permission_denied, fmri);
                        else
                                uu_warn(emsg_pg_perm_denied, fmri, pgname);
                        goto out;
                }

                if (scf_transaction_property_change_type(tx, txent, propname,
                    SCF_TYPE_ASTRING) != 0) {
                        if (scf_error() != SCF_ERROR_NOT_FOUND)
                                scfdie();

                        if (scf_transaction_property_new(tx, txent, propname,
                            SCF_TYPE_ASTRING) != 0)
                                scfdie();
                }

                if (scf_value_set_astring(val, str) != SCF_SUCCESS)
                        scfdie();

                if (scf_entry_add_value(txent, val) != SCF_SUCCESS)
                        scfdie();

                ret = scf_transaction_commit(tx);
                if (ret == -1) {
                        if (scf_error() != SCF_ERROR_PERMISSION_DENIED)
                                scfdie();

                        if (!verbose)
                                uu_warn(emsg_permission_denied, fmri);
                        else
                                uu_warn(emsg_prop_perm_denied, fmri, pgname,
                                    propname);
                        goto out;
                }

                if (ret == 0) {
                        scf_transaction_reset(tx);

                        if (scf_pg_update(pg) == -1)
                                scfdie();
                }
        } while (ret == 0);

out:
        scf_transaction_destroy(tx);
        scf_entry_destroy(txent);
        scf_value_destroy(val);
        scf_property_destroy(prop);
        scf_pg_destroy(pg);
        scf_instance_destroy(inst);
}


static int
set_fmri_enabled(void *data, scf_walkinfo_t *wip)
{
        enable_data_t *ed = data;

        assert(wip->inst != NULL);
        assert(wip->pg == NULL);

        if (svcsearch) {
                char state[MAX_SCF_STATE_STRING_SZ];

                if (inst_get_state(wip->inst, state, wip->fmri, NULL) != 0)
                        return (0);
                if (strcmp(state, svcstate) != 0)
                        return (0);
        }

        if (ed->ed_flags & SET_RECURSIVE) {
                char *fmri_buf = malloc(max_scf_fmri_sz);
                if (fmri_buf == NULL)
                        uu_die(emsg_nomem);

                visited = calloc(HT_BUCKETS, sizeof (*visited));
                if (visited == NULL)
                        uu_die(emsg_nomem);

                /* scf_walk_fmri() guarantees that fmri isn't too long */
                assert(strlen(wip->fmri) <= max_scf_fmri_sz);
                (void) strlcpy(fmri_buf, wip->fmri, max_scf_fmri_sz);

                switch (enable_fmri_rec(fmri_buf, ed)) {
                case E2BIG:
                        uu_warn(gettext("operation on service %s is ambiguous; "
                            "instance specification needed.\n"), fmri_buf);
                        break;

                case ELOOP:
                        uu_warn(gettext("%s: Dependency cycle detected.\n"),
                            fmri_buf);
                }

                free(visited);
                free(fmri_buf);

        } else {
                set_inst_enabled(wip->fmri, wip->inst, ed);
        }

        return (0);
}

/* ARGSUSED */
static int
wait_fmri_enabled(void *data, scf_walkinfo_t *wip)
{
        scf_propertygroup_t *pg = NULL;
        char state[MAX_SCF_STATE_STRING_SZ];

        assert(wip->inst != NULL);
        assert(wip->pg == NULL);

        do {
                if (pg)
                        scf_pg_destroy(pg);
                if (inst_get_state(wip->inst, state, wip->fmri, &pg) != 0) {
                        exit_status = EXIT_SVC_FAILURE;
                        return (0);
                }

                if (strcmp(state, SCF_STATE_STRING_ONLINE) == 0 ||
                    strcmp(state, SCF_STATE_STRING_DEGRADED) == 0) {
                        /*
                         * We're done.
                         */
                        goto out;
                }

                if (strcmp(state, SCF_STATE_STRING_MAINT) == 0) {
                        /*
                         * The service is ill.
                         */
                        uu_warn(gettext("Instance \"%s\" is in maintenance"
                            " state.\n"), wip->fmri);
                        exit_status = EXIT_SVC_FAILURE;
                        goto out;
                }

                if (!is_enabled(wip->inst)) {
                        /*
                         * Someone stepped in and disabled the service.
                         */
                        uu_warn(gettext("Instance \"%s\" has been disabled"
                            " by another entity.\n"), wip->fmri);
                        exit_status = EXIT_SVC_FAILURE;
                        goto out;
                }

                if (!has_potential(wip->inst, B_FALSE)) {
                        /*
                         * Our dependencies aren't met.  We'll never
                         * amount to anything.
                         */
                        uu_warn(gettext("Instance \"%s\" has unsatisfied"
                            " dependencies.\n"), wip->fmri);
                        /*
                         * EXIT_SVC_FAILURE takes precedence over
                         * EXIT_DEP_FAILURE
                         */
                        if (exit_status == 0)
                                exit_status = EXIT_DEP_FAILURE;
                        goto out;
                }
        } while (_scf_pg_wait(pg, WAIT_INTERVAL) >= 0);
        scfdie();
        /* NOTREACHED */

out:
        scf_pg_destroy(pg);
        return (0);
}

/* ARGSUSED */
static int
wait_fmri_disabled(void *data, scf_walkinfo_t *wip)
{
        scf_propertygroup_t *pg = NULL;
        char state[MAX_SCF_STATE_STRING_SZ];

        assert(wip->inst != NULL);
        assert(wip->pg == NULL);

        do {
                if (pg)
                        scf_pg_destroy(pg);
                if (inst_get_state(wip->inst, state, wip->fmri, &pg) != 0) {
                        exit_status = EXIT_SVC_FAILURE;
                        return (0);
                }

                if (strcmp(state, SCF_STATE_STRING_DISABLED) == 0) {
                        /*
                         * We're done.
                         */
                        goto out;
                }

                if (is_enabled(wip->inst)) {
                        /*
                         * Someone stepped in and enabled the service.
                         */
                        uu_warn(gettext("Instance \"%s\" has been enabled"
                            " by another entity.\n"), wip->fmri);
                        exit_status = EXIT_SVC_FAILURE;
                        goto out;
                }

                if (!has_potential(wip->inst, B_TRUE)) {
                        /*
                         * Our restarter is hopeless.
                         */
                        uu_warn(gettext("Restarter for instance \"%s\" is"
                            " unavailable.\n"), wip->fmri);
                        /*
                         * EXIT_SVC_FAILURE takes precedence over
                         * EXIT_DEP_FAILURE
                         */
                        if (exit_status == 0)
                                exit_status = EXIT_DEP_FAILURE;
                        goto out;
                }

        } while (_scf_pg_wait(pg, WAIT_INTERVAL) >= 0);
        scfdie();
        /* NOTREACHED */

out:
        scf_pg_destroy(pg);
        return (0);
}

/* ARGSUSED */
static int
clear_instance(void *data, scf_walkinfo_t *wip)
{
        char state[MAX_SCF_STATE_STRING_SZ];

        assert(wip->inst != NULL);
        assert(wip->pg == NULL);

        if (inst_get_state(wip->inst, state, wip->fmri, NULL) != 0)
                return (0);

        if (svcsearch && strcmp(state, svcstate) != 0)
                return (0);

        if (strcmp(state, SCF_STATE_STRING_MAINT) == 0) {
                set_inst_action(wip->fmri, wip->inst, SCF_PROPERTY_MAINT_OFF);
        } else if (strcmp(state, SCF_STATE_STRING_DEGRADED) == 0) {
                set_inst_action(wip->fmri, wip->inst, SCF_PROPERTY_RESTORE);
        } else {
                uu_warn(gettext("Instance \"%s\" is not in a "
                    "maintenance or degraded state.\n"), wip->fmri);

                exit_status = 1;
        }

        return (0);
}

static int
set_fmri_action(void *action, scf_walkinfo_t *wip)
{
        assert(wip->inst != NULL && wip->pg == NULL);

        if (svcsearch) {
                char state[MAX_SCF_STATE_STRING_SZ];

                if (inst_get_state(wip->inst, state, wip->fmri, NULL) != 0)
                        return (0);
                if (strcmp(state, svcstate) != 0)
                        return (0);
        }

        set_inst_action(wip->fmri, wip->inst, action);

        return (0);
}

static int
force_degraded(void *data, scf_walkinfo_t *wip)
{
        mark_data_t *md = data;
        char state[MAX_SCF_STATE_STRING_SZ];

        if (inst_get_state(wip->inst, state, wip->fmri, NULL) != 0) {
                exit_status = 1;
                return (0);
        }

        if (svcsearch && strcmp(state, svcstate) != 0)
                return (0);

        if (strcmp(state, SCF_STATE_STRING_ONLINE) != 0) {
                uu_warn(gettext("Instance \"%s\" is not online.\n"), wip->fmri);
                exit_status = 1;
                return (0);
        }

        set_inst_action(wip->fmri, wip->inst, (md->md_flags & MARK_IMMEDIATE) ?
            SCF_PROPERTY_DEGRADE_IMMEDIATE : SCF_PROPERTY_DEGRADED);

        return (0);
}

static int
force_maintenance(void *data, scf_walkinfo_t *wip)
{
        mark_data_t *md = data;
        const char *prop;

        if (svcsearch) {
                char state[MAX_SCF_STATE_STRING_SZ];

                if (inst_get_state(wip->inst, state, wip->fmri, NULL) != 0)
                        return (0);
                if (strcmp(state, svcstate) != 0)
                        return (0);
        }

        if (md->md_flags & MARK_IMMEDIATE) {
                prop = (md->md_flags & MARK_TEMPORARY) ?
                    SCF_PROPERTY_MAINT_ON_IMMTEMP :
                    SCF_PROPERTY_MAINT_ON_IMMEDIATE;
        } else {
                prop = (md->md_flags & MARK_TEMPORARY) ?
                    SCF_PROPERTY_MAINT_ON_TEMPORARY :
                    SCF_PROPERTY_MAINT_ON;
        }

        set_inst_action(wip->fmri, wip->inst, prop);

        return (0);
}

static void
set_milestone(const char *fmri, boolean_t temporary)
{
        scf_instance_t *inst;
        scf_propertygroup_t *pg;

        if (temporary) {
                set_astring_prop(SCF_SERVICE_STARTD, SCF_PG_OPTIONS_OVR,
                    SCF_PG_OPTIONS_OVR_TYPE, SCF_PG_OPTIONS_OVR_FLAGS,
                    SCF_PROPERTY_MILESTONE, fmri);
                return;
        }

        if ((inst = scf_instance_create(h)) == NULL ||
            (pg = scf_pg_create(h)) == NULL)
                scfdie();

        if (get_inst(SCF_SERVICE_STARTD, inst) != 0) {
                scf_instance_destroy(inst);
                return;
        }

        /*
         * Set the persistent milestone before deleting the override so we don't
         * glitch.
         */
        set_astring_prop(SCF_SERVICE_STARTD, SCF_PG_OPTIONS,
            SCF_PG_OPTIONS_TYPE, SCF_PG_OPTIONS_FLAGS, SCF_PROPERTY_MILESTONE,
            fmri);

        if (delete_prop(SCF_SERVICE_STARTD, inst, SCF_PG_OPTIONS_OVR,
            SCF_PROPERTY_MILESTONE) != 0)
                exit_status = 1;

        scf_pg_destroy(pg);
        scf_instance_destroy(inst);
}

static char const *milestones[] = {
        SCF_MILESTONE_SINGLE_USER,
        SCF_MILESTONE_MULTI_USER,
        SCF_MILESTONE_MULTI_USER_SERVER,
        NULL
};

static void
usage_milestone(void)
{
        const char **ms;

        (void) fprintf(stderr, gettext(
        "Usage: svcadm milestone [-d] <milestone>\n\n"
        "\t-d\tmake the specified milestone the default for system boot\n\n"
        "\tMilestones can be specified using an FMRI or abbreviation.\n"
        "\tThe major milestones are as follows:\n\n"
        "\tall\n"
        "\tnone\n"));

        for (ms = milestones; *ms != NULL; ms++)
                (void) fprintf(stderr, "\t%s\n", *ms);

        exit(UU_EXIT_USAGE);
}

static const char *
validate_milestone(const char *milestone)
{
        const char **ms;
        const char *tmp;
        size_t len;

        if (strcmp(milestone, "all") == 0)
                return (milestone);

        if (strcmp(milestone, "none") == 0)
                return (milestone);

        /*
         * Determine if this is a full or partial milestone
         */
        for (ms = milestones; *ms != NULL; ms++) {
                if ((tmp = strstr(*ms, milestone)) != NULL) {
                        len = strlen(milestone);

                        /*
                         * The beginning of the string must align with the start
                         * of a milestone fmri, or on the boundary between
                         * elements.  The end of the string must align with the
                         * end of the milestone, or at the instance boundary.
                         */
                        if ((tmp == *ms || tmp[-1] == '/') &&
                            (tmp[len] == '\0' || tmp[len] == ':'))
                                return (*ms);
                }
        }

        (void) fprintf(stderr,
            gettext("\"%s\" is not a valid major milestone.\n"), milestone);

        usage_milestone();
        /* NOTREACHED */
}

/*PRINTFLIKE1*/
static void
pr_warn(const char *format, ...)
{
        const char *pname = uu_getpname();
        va_list alist;

        va_start(alist, format);

        if (pname != NULL)
                (void) fprintf(stderr, "%s", pname);

        if (g_zonename != NULL)
                (void) fprintf(stderr, " (%s)", g_zonename);

        (void) fprintf(stderr, ": ");

        (void) vfprintf(stderr, format, alist);

        if (strrchr(format, '\n') == NULL)
                (void) fprintf(stderr, ": %s\n", strerror(errno));

        va_end(alist);
}

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

int
main(int argc, char *argv[])
{
        int o;
        int err;
        int sw_back;
        boolean_t do_zones = B_FALSE;
        boolean_t do_a_zone = B_FALSE;
        char zonename[ZONENAME_MAX];
        uint_t nzents = 0, zent = 0;
        zoneid_t *zids = NULL;
        int orig_optind, orig_argc;
        char **orig_argv;

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

        (void) uu_setpname(argv[0]);

        if (argc < 2)
                usage();

        max_scf_fmri_sz = scf_limit(SCF_LIMIT_MAX_FMRI_LENGTH);
        if (max_scf_fmri_sz < 0)
                scfdie();
        ++max_scf_fmri_sz;

        scratch_fmri = malloc(max_scf_fmri_sz);
        if (scratch_fmri == NULL)
                uu_die(emsg_nomem);

        while ((o = getopt(argc, argv, "S:vZz:")) != -1) {
                switch (o) {
                case 'S':
                        (void) strlcpy(svcstate, optarg, sizeof (svcstate));
                        svcsearch = B_TRUE;
                        break;

                case 'v':
                        verbose = 1;
                        break;

                case 'z':
                        if (getzoneid() != GLOBAL_ZONEID)
                                uu_die(gettext("svcadm -z may only be used "
                                    "from the global zone\n"));
                        if (do_zones)
                                usage();

                        (void) strlcpy(zonename, optarg, sizeof (zonename));
                        do_a_zone = B_TRUE;
                        break;

                case 'Z':
                        if (getzoneid() != GLOBAL_ZONEID)
                                uu_die(gettext("svcadm -Z may only be used "
                                    "from the global zone\n"));
                        if (do_a_zone)
                                usage();

                        do_zones = B_TRUE;
                        break;

                default:
                        usage();
                }
        }

        while (do_zones) {
                uint_t found;

                if (zone_list(NULL, &nzents) != 0)
                        uu_die(gettext("could not get number of zones"));

                if ((zids = malloc(nzents * sizeof (zoneid_t))) == NULL) {
                        uu_die(gettext("could not allocate array for "
                            "%d zone IDs"), nzents);
                }

                found = nzents;

                if (zone_list(zids, &found) != 0)
                        uu_die(gettext("could not get zone list"));

                /*
                 * If the number of zones has not changed between our calls to
                 * zone_list(), we're done -- otherwise, we must free our array
                 * of zone IDs and take another lap.
                 */
                if (found == nzents)
                        break;

                free(zids);
        }

        emsg_permission_denied = gettext("%s: Permission denied.\n");
        emsg_nomem = gettext("Out of memory.\n");
        emsg_create_pg_perm_denied = gettext("%s: Couldn't create \"%s\" "
            "property group (permission denied).\n");
        emsg_pg_perm_denied = gettext("%s: Couldn't modify \"%s\" property "
            "group (permission denied).\n");
        emsg_prop_perm_denied = gettext("%s: Couldn't modify \"%s/%s\" "
            "property (permission denied).\n");
        emsg_no_service = gettext("No such service \"%s\".\n");

        orig_optind = optind;
        orig_argc = argc;
        orig_argv = argv;

again:
        h = scf_handle_create(SCF_VERSION);
        if (h == NULL)
                scfdie();

        if (do_zones) {
                zone_status_t status;

                if (zone_getattr(zids[zent], ZONE_ATTR_STATUS, &status,
                    sizeof (status)) < 0 || status != ZONE_IS_RUNNING) {
                        /*
                         * If this zone is not running or we cannot
                         * get its status, we do not want to attempt
                         * to bind an SCF handle to it, lest we
                         * accidentally interfere with a zone that
                         * is not yet running by looking up a door
                         * to its svc.configd (which could potentially
                         * block a mount with an EBUSY).
                         */
                        zent++;
                        goto nextzone;
                }

                if (getzonenamebyid(zids[zent++], zonename,
                    sizeof (zonename)) < 0) {
                        uu_warn(gettext("could not get name for "
                            "zone %d; ignoring"), zids[zent - 1]);
                        goto nextzone;
                }

                g_zonename = zonename;
        }

        if (do_a_zone || do_zones) {
                scf_value_t *zone;

                if ((zone = scf_value_create(h)) == NULL)
                        scfdie();

                if (scf_value_set_astring(zone, zonename) != SCF_SUCCESS)
                        scfdie();

                if (scf_handle_decorate(h, "zone", zone) != SCF_SUCCESS) {
                        if (do_a_zone) {
                                uu_die(gettext("zone '%s': %s\n"),
                                    zonename, scf_strerror(scf_error()));
                        } else {
                                scf_value_destroy(zone);
                                goto nextzone;
                        }
                }

                scf_value_destroy(zone);
        }

        if (scf_handle_bind(h) == -1) {
                if (do_zones)
                        goto nextzone;

                uu_die(gettext("Couldn't bind to configuration repository: "
                    "%s.\n"), scf_strerror(scf_error()));
        }

        optind = orig_optind;
        argc = orig_argc;
        argv = orig_argv;

        if (optind >= argc)
                usage();

        if (strcmp(argv[optind], "enable") == 0) {
                enable_data_t ed = {
                        .ed_flags = SET_ENABLED,
                        .ed_comment = ""
                };
                int wait = 0;
                int error = 0;

                ++optind;

                while ((o = getopt(argc, argv, "rst")) != -1) {
                        if (o == 'r')
                                ed.ed_flags |= SET_RECURSIVE;
                        else if (o == 't')
                                ed.ed_flags |= SET_TEMPORARY;
                        else if (o == 's')
                                wait = 1;
                        else if (o == '?')
                                usage();
                        else {
                                assert(0);
                                abort();
                        }
                }
                argc -= optind;
                argv += optind;

                if (argc == 0 && !svcsearch)
                        usage();

                if (argc > 0 && svcsearch)
                        usage();

                /*
                 * We want to continue with -s processing if we had
                 * invalid options, but not if an enable failed.  We
                 * squelch output the second time we walk fmris; we saw
                 * the errors the first time.
                 */
                if ((err = scf_walk_fmri(h, argc, argv, WALK_FLAGS,
                    set_fmri_enabled, &ed, &error, pr_warn)) != 0) {

                        pr_warn(gettext("failed to iterate over "
                            "instances: %s\n"), scf_strerror(err));
                        exit_status = UU_EXIT_FATAL;

                } else if (wait && exit_status == 0 &&
                    (err = scf_walk_fmri(h, argc, argv, WALK_FLAGS,
                    wait_fmri_enabled, NULL, &error, quiet)) != 0) {

                        pr_warn(gettext("failed to iterate over "
                            "instances: %s\n"), scf_strerror(err));
                        exit_status = UU_EXIT_FATAL;
                }

                if (error > 0)
                        exit_status = error;

        } else if (strcmp(argv[optind], "disable") == 0) {
                enable_data_t ed = {
                        .ed_flags = 0,
                        .ed_comment = ""
                };
                int wait = 0;
                int error = 0;

                ++optind;

                while ((o = getopt(argc, argv, "c:st")) != -1) {
                        if (o == 'c') {
                                if (strlcpy(ed.ed_comment, optarg,
                                    sizeof (ed.ed_comment)) >=
                                    sizeof (ed.ed_comment)) {
                                        uu_die(gettext("disable -c comment "
                                            "too long.\n"));
                                }
                        } else if (o == 't')
                                ed.ed_flags |= SET_TEMPORARY;
                        else if (o == 's')
                                wait = 1;
                        else if (o == '?')
                                usage();
                        else {
                                assert(0);
                                abort();
                        }
                }
                argc -= optind;
                argv += optind;

                if (argc == 0 && !svcsearch)
                        usage();

                if (argc > 0 && svcsearch)
                        usage();

                /*
                 * We want to continue with -s processing if we had
                 * invalid options, but not if a disable failed.  We
                 * squelch output the second time we walk fmris; we saw
                 * the errors the first time.
                 */
                if ((err = scf_walk_fmri(h, argc, argv, WALK_FLAGS,
                    set_fmri_enabled, &ed, &exit_status,
                    pr_warn)) != 0) {

                        pr_warn(gettext("failed to iterate over "
                            "instances: %s\n"), scf_strerror(err));
                        exit_status = UU_EXIT_FATAL;

                } else if (wait && exit_status == 0 &&
                    (err = scf_walk_fmri(h, argc, argv, WALK_FLAGS,
                    wait_fmri_disabled, NULL, &error, quiet)) != 0) {

                        pr_warn(gettext("failed to iterate over "
                            "instances: %s\n"), scf_strerror(err));
                        exit_status = UU_EXIT_FATAL;
                }

                if (error > 0)
                        exit_status = error;

        } else if (strcmp(argv[optind], "restart") == 0) {
                boolean_t do_dump = B_FALSE;

                ++optind;

                while ((o = getopt(argc, argv, "d")) != -1) {
                        if (o == 'd')
                                do_dump = B_TRUE;
                        else if (o == '?')
                                usage();
                        else {
                                assert(0);
                                abort();
                        }
                }
                argc -= optind;
                argv += optind;

                if (argc == 0 && !svcsearch)
                        usage();

                if (argc > 0 && svcsearch)
                        usage();

                if (do_dump) {
                        if ((err = scf_walk_fmri(h, argc, argv, WALK_FLAGS,
                            set_fmri_action, (void *)SCF_PROPERTY_DODUMP,
                            &exit_status, pr_warn)) != 0) {
                                pr_warn(gettext("failed to iterate over "
                                    "instances: %s\n"), scf_strerror(err));
                                exit_status = UU_EXIT_FATAL;
                        }
                }

                if ((err = scf_walk_fmri(h, argc, argv, WALK_FLAGS,
                    set_fmri_action, (void *)SCF_PROPERTY_RESTART, &exit_status,
                    pr_warn)) != 0) {
                        pr_warn(gettext("failed to iterate over "
                            "instances: %s\n"), scf_strerror(err));
                        exit_status = UU_EXIT_FATAL;
                }

        } else if (strcmp(argv[optind], "refresh") == 0) {
                ++optind;
                argc -= optind;
                argv += optind;

                if (argc == 0 && !svcsearch)
                        usage();

                if (argc > 0 && svcsearch)
                        usage();

                if ((err = scf_walk_fmri(h, argc, argv, WALK_FLAGS,
                    set_fmri_action, (void *)SCF_PROPERTY_REFRESH, &exit_status,
                    pr_warn)) != 0) {
                        pr_warn(gettext("failed to iterate over "
                            "instances: %s\n"), scf_strerror(scf_error()));
                        exit_status = UU_EXIT_FATAL;
                }

        } else if (strcmp(argv[optind], "mark") == 0) {
                scf_walk_callback callback;
                mark_data_t md = {
                        .md_flags = 0
                };

                ++optind;

                while ((o = getopt(argc, argv, "It")) != -1) {
                        if (o == 'I')
                                md.md_flags |= MARK_IMMEDIATE;
                        else if (o == 't')
                                md.md_flags |= MARK_TEMPORARY;
                        else if (o == '?')
                                usage();
                        else {
                                assert(0);
                                abort();
                        }
                }

                if (argc - optind < 2)
                        usage();

                if (strcmp(argv[optind], "degraded") == 0) {
                        if ((md.md_flags & MARK_TEMPORARY) != 0) {
                                uu_xdie(UU_EXIT_USAGE, gettext(
                                    "-t may not be used with degraded.\n"));
                        }
                        callback = force_degraded;
                } else if (strcmp(argv[optind], "maintenance") == 0) {
                        callback = force_maintenance;
                } else {
                        usage();
                }

                optind++;
                argc -= optind;
                argv += optind;

                if (argc == 0 && !svcsearch)
                        usage();

                if (argc > 0 && svcsearch)
                        usage();

                if ((err = scf_walk_fmri(h, argc, argv, WALK_FLAGS, callback,
                    (void *)&md, &exit_status, pr_warn)) != 0) {
                        pr_warn(gettext(
                            "failed to iterate over instances: %s\n"),
                            scf_strerror(err));
                        exit_status = UU_EXIT_FATAL;
                }

        } else if (strcmp(argv[optind], "clear") == 0) {
                ++optind;
                argc -= optind;
                argv += optind;

                if (argc == 0 && !svcsearch)
                        usage();

                if (svcsearch) {
                        if (argc > 0)
                                usage();
                        if (strcmp(svcstate, SCF_STATE_STRING_MAINT) != 0 &&
                            strcmp(svcstate, SCF_STATE_STRING_DEGRADED) != 0)
                                uu_die(gettext("State must be '%s' or '%s'\n"),
                                    SCF_STATE_STRING_MAINT,
                                    SCF_STATE_STRING_DEGRADED);
                }

                if ((err = scf_walk_fmri(h, argc, argv, WALK_FLAGS,
                    clear_instance, NULL, &exit_status, pr_warn)) != 0) {
                        pr_warn(gettext("failed to iterate over "
                            "instances: %s\n"), scf_strerror(err));
                        exit_status = UU_EXIT_FATAL;
                }

        } else if (strcmp(argv[optind], "milestone") == 0) {
                boolean_t temporary = B_TRUE;
                const char *milestone;

                ++optind;

                while ((o = getopt(argc, argv, "d")) != -1) {
                        if (o == 'd')
                                temporary = B_FALSE;
                        else if (o == '?')
                                usage_milestone();
                        else {
                                assert(0);
                                abort();
                        }
                }

                if (optind >= argc)
                        usage_milestone();

                milestone = validate_milestone(argv[optind]);

                set_milestone(milestone, temporary);
        } else if (strcmp(argv[optind], "_smf_backup") == 0) {
                const char *reason = NULL;

                ++optind;

                if (optind != argc - 1)
                        usage();

                if ((err = _scf_request_backup(h, argv[optind])) !=
                    SCF_SUCCESS) {
                        switch (scf_error()) {
                        case SCF_ERROR_NOT_BOUND:
                        case SCF_ERROR_CONNECTION_BROKEN:
                        case SCF_ERROR_BACKEND_READONLY:
                                scfdie();
                                break;

                        case SCF_ERROR_PERMISSION_DENIED:
                        case SCF_ERROR_INVALID_ARGUMENT:
                                reason = scf_strerror(scf_error());
                                break;

                        case SCF_ERROR_INTERNAL:
                                reason =
                                    "unknown error (see console for details)";
                                break;
                        }

                        pr_warn("failed to backup repository: %s\n", reason);
                        exit_status = UU_EXIT_FATAL;
                }
        } else if (strcmp(argv[optind], "_smf_repository_switch") == 0) {
                const char *reason = NULL;

                ++optind;

                /*
                 * Check argument and setup scf_switch structure
                 */
                if (optind != argc - 1)
                        exit(1);

                if (strcmp(argv[optind], "fast") == 0) {
                        sw_back = 0;
                } else if (strcmp(argv[optind], "perm") == 0) {
                        sw_back = 1;
                } else {
                        exit(UU_EXIT_USAGE);
                }

                /*
                 * Call into switch primitive
                 */
                if ((err = _scf_repository_switch(h, sw_back)) !=
                    SCF_SUCCESS) {
                        /*
                         * Retrieve per thread SCF error code
                         */
                        switch (scf_error()) {
                        case SCF_ERROR_NOT_BOUND:
                                abort();
                                /* NOTREACHED */

                        case SCF_ERROR_CONNECTION_BROKEN:
                        case SCF_ERROR_BACKEND_READONLY:
                                scfdie();
                                /* NOTREACHED */

                        case SCF_ERROR_PERMISSION_DENIED:
                        case SCF_ERROR_INVALID_ARGUMENT:
                                reason = scf_strerror(scf_error());
                                break;

                        case SCF_ERROR_INTERNAL:
                                reason = "File operation error: (see console)";
                                break;

                        default:
                                abort();
                                /* NOTREACHED */
                        }

                        pr_warn("failed to switch repository: %s\n", reason);
                        exit_status = UU_EXIT_FATAL;
                }
        } else {
                usage();
        }

        if (scf_handle_unbind(h) == -1)
                scfdie();
nextzone:
        scf_handle_destroy(h);
        if (do_zones && zent < nzents)
                goto again;

        return (exit_status);
}