/* * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #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 ] [-v] [-Z | -z zone] [cmd [args ... ]]\n\n" "\t%1$s enable [-rst] [ ...]\t- enable and online service(s)\n" "\t%1$s disable [-c comment] [-st] [ ...] - disable " "service(s)\n" "\t%1$s restart [-d] [ ...]\t- restart specified service(s)\n" "\t%1$s refresh [ ...]\t\t- re-read service configuration\n" "\t%1$s mark [-It] [ ...] - set maintenance state\n" "\t%1$s clear [ ...]\t\t- clear maintenance state\n" "\t%1$s milestone [-d] \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 svc:/network/smtp:sendmail\n" "\t%1$s network/smtp:sendmail\n" "\t%1$s network/*mail\n" "\t%1$s network/smtp\n" "\t%1$s smtp:sendmail\n" "\t%1$s smtp\n" "\t%1$s 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] \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); }