/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright (c) 2004, 2010, Oracle and/or its affiliates. All rights reserved. * Copyright 2020 Joyent, Inc. * Copyright (c) 2015, 2016 by Delphix. All rights reserved. */ /* * svcs - display attributes of service instances * * We have two output formats and six instance selection mechanisms. The * primary output format is a line of attributes (selected by -o), possibly * followed by process description lines (if -p is specified), for each * instance selected. The columns available to display are described by the * struct column columns array. The columns to actually display are kept in * the opt_columns array as indicies into the columns array. The selection * mechanisms available for this format are service FMRIs (selects all child * instances), instance FMRIs, instance FMRI glob patterns, instances with * a certain restarter (-R), dependencies of instances (-d), and dependents of * instances (-D). Since the lines must be sorted (per -sS), we'll just stick * each into a data structure and print them in order when we're done. To * avoid listing the same instance twice (when -d and -D aren't given), we'll * use a hash table of FMRIs to record that we've listed (added to the tree) * an instance. * * The secondary output format (-l "long") is a paragraph of text for the * services or instances selected. Not needing to be sorted, it's implemented * by just calling print_detailed() for each FMRI given. */ #include "svcs.h" #include "notify_params.h" /* Get the byteorder macros to ease sorting. */ #include #include #include #include #include #include #include #include #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 */ #define LEGACY_UNKNOWN "unknown" /* * Per proc(5) when pr_nlwp, pr_nzomb, and pr_lwp.pr_lwpid are all 0, * the process is a zombie. */ #define IS_ZOMBIE(_psip) \ ((_psip)->pr_nlwp == 0 && (_psip)->pr_nzomb == 0 && \ (_psip)->pr_lwp.pr_lwpid == 0) /* * An AVL-storable node for output lines and the keys to sort them by. */ struct avl_string { uu_avl_node_t node; char *key; char *str; }; /* * For lists of parsed restarter FMRIs. */ struct pfmri_list { const char *scope; const char *service; const char *instance; struct pfmri_list *next; }; /* * Globals */ scf_handle_t *h; static scf_propertygroup_t *g_pg; static scf_property_t *g_prop; static scf_value_t *g_val; static size_t line_sz; /* Bytes in the header line. */ static size_t sortkey_sz; /* Bytes in sort keys. */ static uu_avl_pool_t *lines_pool; static uu_avl_t *lines; /* Output lines. */ int exit_status; ssize_t max_scf_name_length; ssize_t max_scf_value_length; ssize_t max_scf_fmri_length; static ssize_t max_scf_type_length; static time_t now; static struct pfmri_list *restarters = NULL; static int first_paragraph = 1; /* For -l mode. */ static char *common_name_buf; /* Sized for maximal length value. */ char *locale; /* Current locale. */ char *g_zonename; /* zone being operated upon */ /* * Pathname storage for path generated from the fmri. * Used for reading the ctid and (start) pid files for an inetd service. */ static char genfmri_filename[MAXPATHLEN] = ""; /* Options */ static int *opt_columns = NULL; /* Indices into columns to display. */ static int opt_cnum = 0; static int opt_processes = 0; /* Print processes? */ static int *opt_sort = NULL; /* Indices into columns to sort. */ static int opt_snum = 0; static int opt_nstate_shown = 0; /* Will nstate be shown? */ static int opt_verbose = 0; static char *opt_zone; /* zone selected, if any */ /* Minimize string constants. */ static const char * const scf_property_state = SCF_PROPERTY_STATE; static const char * const scf_property_next_state = SCF_PROPERTY_NEXT_STATE; static const char * const scf_property_contract = SCF_PROPERTY_CONTRACT; /* * Utility functions */ /* * For unexpected libscf errors. The ending newline is necessary to keep * uu_die() from appending the errno error. */ #ifndef NDEBUG void do_scfdie(const char *file, int line) { uu_die(gettext("%s:%d: Unexpected libscf error: %s. Exiting.\n"), file, line, scf_strerror(scf_error())); } #else void scfdie(void) { uu_die(gettext("Unexpected libscf error: %s. Exiting.\n"), scf_strerror(scf_error())); } #endif void * safe_malloc(size_t sz) { void *ptr; ptr = malloc(sz); if (ptr == NULL) uu_die(gettext("Out of memory")); return (ptr); } char * safe_strdup(const char *str) { char *cp; cp = strdup(str); if (cp == NULL) uu_die(gettext("Out of memory.\n")); return (cp); } /* * FMRI hashtable. For uniquifing listings. */ struct ht_elem { const char *fmri; struct ht_elem *next; }; static struct ht_elem **ht_buckets = NULL; static uint_t ht_buckets_num = 0; static uint_t ht_num; static void ht_free(void) { struct ht_elem *elem, *next; int i; for (i = 0; i < ht_buckets_num; i++) { for (elem = ht_buckets[i]; elem != NULL; elem = next) { next = elem->next; free((char *)elem->fmri); free(elem); } } free(ht_buckets); ht_buckets_num = 0; ht_buckets = NULL; } static void ht_init(void) { assert(ht_buckets == NULL); ht_buckets_num = 8; ht_buckets = safe_malloc(sizeof (*ht_buckets) * ht_buckets_num); bzero(ht_buckets, sizeof (*ht_buckets) * ht_buckets_num); ht_num = 0; } static uint_t ht_hash_fmri(const char *fmri) { uint_t h = 0, g; const char *p, *k; /* All FMRIs begin with svc:/, so skip that part. */ assert(strncmp(fmri, "svc:/", sizeof ("svc:/") - 1) == 0); k = fmri + sizeof ("svc:/") - 1; /* * Generic hash function from uts/common/os/modhash.c. */ for (p = k; *p != '\0'; ++p) { h = (h << 4) + *p; if ((g = (h & 0xf0000000)) != 0) { h ^= (g >> 24); h ^= g; } } return (h); } static void ht_grow() { uint_t new_ht_buckets_num; struct ht_elem **new_ht_buckets; int i; new_ht_buckets_num = ht_buckets_num * 2; assert(new_ht_buckets_num > ht_buckets_num); new_ht_buckets = safe_malloc(sizeof (*new_ht_buckets) * new_ht_buckets_num); bzero(new_ht_buckets, sizeof (*new_ht_buckets) * new_ht_buckets_num); for (i = 0; i < ht_buckets_num; ++i) { struct ht_elem *elem, *next; for (elem = ht_buckets[i]; elem != NULL; elem = next) { uint_t h; next = elem->next; h = ht_hash_fmri(elem->fmri); elem->next = new_ht_buckets[h & (new_ht_buckets_num - 1)]; new_ht_buckets[h & (new_ht_buckets_num - 1)] = elem; } } free(ht_buckets); ht_buckets = new_ht_buckets; ht_buckets_num = new_ht_buckets_num; } /* * Add an FMRI to the hash table. Returns 1 if it was already there, * 0 otherwise. */ static int ht_add(const char *fmri) { uint_t h; struct ht_elem *elem; h = ht_hash_fmri(fmri); elem = ht_buckets[h & (ht_buckets_num - 1)]; for (; elem != NULL; elem = elem->next) { if (strcmp(elem->fmri, fmri) == 0) return (1); } /* Grow when average chain length is over 3. */ if (ht_num > 3 * ht_buckets_num) ht_grow(); ++ht_num; elem = safe_malloc(sizeof (*elem)); elem->fmri = strdup(fmri); elem->next = ht_buckets[h & (ht_buckets_num - 1)]; ht_buckets[h & (ht_buckets_num - 1)] = elem; return (0); } /* * Convenience libscf wrapper functions. */ /* * Get the single value of the named property in the given property group, * which must have type ty, and put it in *vp. If ty is SCF_TYPE_ASTRING, vp * is taken to be a char **, and sz is the size of the buffer. sz is unused * otherwise. Return 0 on success, -1 if the property doesn't exist, has the * wrong type, or doesn't have a single value. If flags has EMPTY_OK, don't * complain if the property has no values (but return nonzero). If flags has * MULTI_OK and the property has multiple values, succeed with E2BIG. */ int pg_get_single_val(scf_propertygroup_t *pg, const char *propname, scf_type_t ty, void *vp, size_t sz, uint_t flags) { char *buf, root[MAXPATHLEN]; size_t buf_sz; int ret = -1, r; boolean_t multi = B_FALSE; assert((flags & ~(EMPTY_OK | MULTI_OK)) == 0); if (scf_pg_get_property(pg, propname, g_prop) == -1) { if (scf_error() != SCF_ERROR_NOT_FOUND) scfdie(); goto out; } if (scf_property_is_type(g_prop, ty) != SCF_SUCCESS) { if (scf_error() == SCF_ERROR_TYPE_MISMATCH) goto misconfigured; scfdie(); } if (scf_property_get_value(g_prop, g_val) != SCF_SUCCESS) { switch (scf_error()) { case SCF_ERROR_NOT_FOUND: if (flags & EMPTY_OK) goto out; goto misconfigured; case SCF_ERROR_CONSTRAINT_VIOLATED: if (flags & MULTI_OK) { multi = B_TRUE; break; } goto misconfigured; case SCF_ERROR_PERMISSION_DENIED: default: scfdie(); } } switch (ty) { case SCF_TYPE_ASTRING: r = scf_value_get_astring(g_val, vp, sz); if (r == 0 && !(flags & EMPTY_OK)) { uu_die(gettext("Unexpected empty string for property " "%s. Exiting.\n"), propname); } if (r >= 0) r = SCF_SUCCESS; break; case SCF_TYPE_BOOLEAN: r = scf_value_get_boolean(g_val, (uint8_t *)vp); break; case SCF_TYPE_COUNT: r = scf_value_get_count(g_val, (uint64_t *)vp); break; case SCF_TYPE_INTEGER: r = scf_value_get_integer(g_val, (int64_t *)vp); break; case SCF_TYPE_TIME: { int64_t sec; int32_t ns; r = scf_value_get_time(g_val, &sec, &ns); ((struct timeval *)vp)->tv_sec = sec; ((struct timeval *)vp)->tv_usec = ns / 1000; break; } case SCF_TYPE_USTRING: r = scf_value_get_ustring(g_val, vp, sz) > 0 ? SCF_SUCCESS : -1; break; default: #ifndef NDEBUG uu_warn("%s:%d: Unknown type %d.\n", __FILE__, __LINE__, ty); #endif abort(); } if (r != SCF_SUCCESS) scfdie(); ret = multi ? E2BIG : 0; goto out; misconfigured: buf_sz = max_scf_fmri_length + 1; buf = safe_malloc(buf_sz); if (scf_property_to_fmri(g_prop, buf, buf_sz) == -1) scfdie(); uu_warn(gettext("Property \"%s\" is misconfigured.\n"), buf); free(buf); out: if (ret != 0 || g_zonename == NULL || (strcmp(propname, SCF_PROPERTY_LOGFILE) != 0 && strcmp(propname, SCF_PROPERTY_ALT_LOGFILE) != 0)) return (ret); /* * If we're here, we have a log file and we have specified a zone. * As a convenience, we're going to prepend the zone path to the * name of the log file. */ root[0] = '\0'; (void) zone_get_rootpath(g_zonename, root, sizeof (root)); (void) strlcat(root, vp, sizeof (root)); (void) snprintf(vp, sz, "%s", root); return (ret); } static scf_snapshot_t * get_running_snapshot(scf_instance_t *inst) { scf_snapshot_t *snap; snap = scf_snapshot_create(h); if (snap == NULL) scfdie(); if (scf_instance_get_snapshot(inst, "running", snap) == 0) return (snap); if (scf_error() != SCF_ERROR_NOT_FOUND) scfdie(); scf_snapshot_destroy(snap); return (NULL); } /* * As pg_get_single_val(), except look the property group up in an * instance. If "use_running" is set, and the running snapshot exists, * do a composed lookup there. Otherwise, do an (optionally composed) * lookup on the current values. Note that lookups using snapshots are * always composed. */ int inst_get_single_val(scf_instance_t *inst, const char *pgname, const char *propname, scf_type_t ty, void *vp, size_t sz, uint_t flags, int use_running, int composed) { scf_snapshot_t *snap = NULL; int r; if (use_running) snap = get_running_snapshot(inst); if (composed || use_running) r = scf_instance_get_pg_composed(inst, snap, pgname, g_pg); else r = scf_instance_get_pg(inst, pgname, g_pg); if (snap) scf_snapshot_destroy(snap); if (r == -1) return (-1); r = pg_get_single_val(g_pg, propname, ty, vp, sz, flags); return (r); } static int instance_enabled(scf_instance_t *inst, boolean_t temp) { uint8_t b; if (inst_get_single_val(inst, temp ? SCF_PG_GENERAL_OVR : SCF_PG_GENERAL, SCF_PROPERTY_ENABLED, SCF_TYPE_BOOLEAN, &b, 0, 0, 0, 0) != 0) return (-1); return (b ? 1 : 0); } /* * Get a string property from the restarter property group of the given * instance. Return an empty string on normal problems. */ static void get_restarter_string_prop(scf_instance_t *inst, const char *pname, char *buf, size_t buf_sz) { if (inst_get_single_val(inst, SCF_PG_RESTARTER, pname, SCF_TYPE_ASTRING, buf, buf_sz, 0, 0, 1) != 0) *buf = '\0'; } static int get_restarter_time_prop(scf_instance_t *inst, const char *pname, struct timeval *tvp, int ok_if_empty) { int r; r = inst_get_single_val(inst, SCF_PG_RESTARTER, pname, SCF_TYPE_TIME, tvp, 0, ok_if_empty ? EMPTY_OK : 0, 0, 1); return (r == 0 ? 0 : -1); } static int get_restarter_count_prop(scf_instance_t *inst, const char *pname, uint64_t *cp, uint_t flags) { return (inst_get_single_val(inst, SCF_PG_RESTARTER, pname, SCF_TYPE_COUNT, cp, 0, flags, 0, 1)); } /* * Generic functions */ /* * Return an array of pids associated with the given contract id. * Returned pids are added to the end of the pidsp array. */ static void ctid_to_pids(uint64_t c, pid_t **pidsp, uint_t *np) { ct_stathdl_t ctst; uint_t m; int fd; int r, err; pid_t *pids; fd = contract_open(c, NULL, "status", O_RDONLY); if (fd < 0) return; err = ct_status_read(fd, CTD_ALL, &ctst); if (err != 0) { uu_warn(gettext("Could not read status of contract " "%ld: %s.\n"), c, strerror(err)); (void) close(fd); return; } (void) close(fd); r = ct_pr_status_get_members(ctst, &pids, &m); assert(r == 0); if (m == 0) { ct_status_free(ctst); return; } *pidsp = realloc(*pidsp, (*np + m) * sizeof (*pidsp)); if (*pidsp == NULL) uu_die(gettext("Out of memory")); bcopy(pids, *pidsp + *np, m * sizeof (*pids)); *np += m; ct_status_free(ctst); } static int propvals_to_pids(scf_propertygroup_t *pg, const char *pname, pid_t **pidsp, uint_t *np, scf_property_t *prop, scf_value_t *val, scf_iter_t *iter) { scf_type_t ty; uint64_t c; int r; if (scf_pg_get_property(pg, pname, prop) != 0) { if (scf_error() != SCF_ERROR_NOT_FOUND) scfdie(); return (ENOENT); } if (scf_property_type(prop, &ty) != 0) scfdie(); if (ty != SCF_TYPE_COUNT) return (EINVAL); if (scf_iter_property_values(iter, prop) != 0) scfdie(); for (;;) { r = scf_iter_next_value(iter, val); if (r == -1) scfdie(); if (r == 0) break; if (scf_value_get_count(val, &c) != 0) scfdie(); ctid_to_pids(c, pidsp, np); } return (0); } /* * Check if instance has general/restarter property that matches * given string. Restarter string must be in canonified form. * Returns 0 for success; -1 otherwise. */ static int check_for_restarter(scf_instance_t *inst, const char *restarter) { char *fmri_buf; char *fmri_buf_canonified = NULL; int ret = -1; if (inst == NULL) return (-1); /* Get restarter */ fmri_buf = safe_malloc(max_scf_fmri_length + 1); if (inst_get_single_val(inst, SCF_PG_GENERAL, SCF_PROPERTY_RESTARTER, SCF_TYPE_ASTRING, fmri_buf, max_scf_fmri_length + 1, 0, 0, 1) != 0) goto out; fmri_buf_canonified = safe_malloc(max_scf_fmri_length + 1); if (scf_canonify_fmri(fmri_buf, fmri_buf_canonified, (max_scf_fmri_length + 1)) < 0) goto out; if (strcmp(fmri_buf, restarter) == 0) ret = 0; out: free(fmri_buf); if (fmri_buf_canonified) free(fmri_buf_canonified); return (ret); } /* * Common code that is used by ctids_by_restarter and pids_by_restarter. * Checks for a common restarter and if one is available, it generates * the appropriate filename using wip->fmri and stores that in the * global genfmri_filename. * * Restarters currently supported are: svc:/network/inetd:default * If a restarter specific action is available, then restarter_spec * is set to 1. If a restarter specific action is not available, then * restarter_spec is set to 0 and a -1 is returned. * * Returns: * 0 if success: restarter specific action found and filename generated * -1 if restarter specific action not found, * if restarter specific action found but an error was encountered * during the generation of the wip->fmri based filename */ static int common_by_restarter(scf_instance_t *inst, const char *fmri, int *restarter_specp) { int ret = -1; int r; /* Check for inetd specific restarter */ if (check_for_restarter(inst, "svc:/network/inetd:default") != 0) { *restarter_specp = 0; return (ret); } *restarter_specp = 1; /* Get the ctid filename associated with this instance */ r = gen_filenms_from_fmri(fmri, "ctid", genfmri_filename, NULL); switch (r) { case 0: break; case -1: /* * Unable to get filename from fmri. Print warning * and return failure with no ctids. */ uu_warn(gettext("Unable to read contract ids for %s -- " "FMRI is too long\n"), fmri); return (ret); case -2: /* * The directory didn't exist, so no contracts. * Return failure with no ctids. */ return (ret); default: uu_warn(gettext("%s:%d: gen_filenms_from_fmri() failed with " "unknown error %d\n"), __FILE__, __LINE__, r); abort(); } return (0); } /* * Get or print a contract id using a restarter specific action. * * If the print_flag is not set, this routine gets the single contract * id associated with this instance. * If the print flag is set, then print each contract id found. * * Returns: * 0 if success: restarter specific action found and used with no error * -1 if restarter specific action not found * -1 if restarter specific action found, but there was a failure * -1 if print flag is not set and no contract id is found or multiple * contract ids were found * E2BIG if print flag is not set, MULTI_OK bit in flag is set and multiple * contract ids were found */ static int ctids_by_restarter(scf_walkinfo_t *wip, uint64_t *cp, int print_flag, uint_t flags, int *restarter_specp, void (*callback_header)(), void (*callback_ctid)(uint64_t)) { FILE *fp; int ret = -1; int fscanf_ret; uint64_t cp2; int rest_ret; /* Check if callbacks are needed and were passed in */ if (print_flag) { if ((callback_header == NULL) || (callback_ctid == NULL)) return (ret); } /* Check for restarter specific action and generation of filename */ rest_ret = common_by_restarter(wip->inst, wip->fmri, restarter_specp); if (rest_ret != 0) return (rest_ret); /* * If fopen fails, then ctid file hasn't been created yet. * If print_flag is set, this is ok; otherwise fail. */ if ((fp = fopen(genfmri_filename, "r")) == NULL) { if (print_flag) return (0); goto out; } if (print_flag) { /* * Print all contract ids that are found. * First callback to print ctid header. */ callback_header(); /* fscanf may not set errno, so be sure to clear it first */ errno = 0; while ((fscanf_ret = fscanf(fp, "%llu", cp)) == 1) { /* Callback to print contract id */ callback_ctid(*cp); errno = 0; } /* EOF is not a failure when no errno. */ if ((fscanf_ret != EOF) || (errno != 0)) { uu_die(gettext("Unable to read ctid file for %s"), wip->fmri); } (void) putchar('\n'); ret = 0; } else { /* Must find 1 ctid or fail */ if (fscanf(fp, "%llu", cp) == 1) { /* If 2nd ctid found - fail */ if (fscanf(fp, "%llu", &cp2) == 1) { if (flags & MULTI_OK) ret = E2BIG; } else { /* Success - found only 1 ctid */ ret = 0; } } } (void) fclose(fp); out: return (ret); } /* * Get the process ids associated with an instance using a restarter * specific action. * * Returns: * 0 if success: restarter specific action found and used with no error * -1 restarter specific action not found or if failure */ static int pids_by_restarter(scf_instance_t *inst, const char *fmri, pid_t **pids, uint_t *np, int *restarter_specp) { uint64_t c; FILE *fp; int fscanf_ret; int rest_ret; /* Check for restarter specific action and generation of filename */ rest_ret = common_by_restarter(inst, fmri, restarter_specp); if (rest_ret != 0) return (rest_ret); /* * If fopen fails with ENOENT then the ctid file hasn't been * created yet so return success. * For all other errors - fail with uu_die. */ if ((fp = fopen(genfmri_filename, "r")) == NULL) { if (errno == ENOENT) return (0); uu_die(gettext("Unable to open ctid file for %s"), fmri); } /* fscanf may not set errno, so be sure to clear it first */ errno = 0; while ((fscanf_ret = fscanf(fp, "%llu", &c)) == 1) { if (c == 0) { (void) fclose(fp); uu_die(gettext("ctid file for %s has corrupt data"), fmri); } ctid_to_pids(c, pids, np); errno = 0; } /* EOF is not a failure when no errno. */ if ((fscanf_ret != EOF) || (errno != 0)) { uu_die(gettext("Unable to read ctid file for %s"), fmri); } (void) fclose(fp); return (0); } static int instance_processes(scf_instance_t *inst, const char *fmri, pid_t **pids, uint_t *np) { scf_iter_t *iter; int ret; int restarter_spec; /* Use the restarter specific get pids routine, if available. */ ret = pids_by_restarter(inst, fmri, pids, np, &restarter_spec); if (restarter_spec == 1) return (ret); if ((iter = scf_iter_create(h)) == NULL) scfdie(); if (scf_instance_get_pg(inst, SCF_PG_RESTARTER, g_pg) == 0) { *pids = NULL; *np = 0; (void) propvals_to_pids(g_pg, scf_property_contract, pids, np, g_prop, g_val, iter); (void) propvals_to_pids(g_pg, SCF_PROPERTY_TRANSIENT_CONTRACT, pids, np, g_prop, g_val, iter); ret = 0; } else { if (scf_error() != SCF_ERROR_NOT_FOUND) scfdie(); ret = -1; } scf_iter_destroy(iter); return (ret); } /* * Column sprint and sortkey functions */ struct column { const char *name; int width; /* * This function should write the value for the column into buf, and * grow or allocate buf accordingly. It should always write at least * width bytes, blanking unused bytes with spaces. If the field is * greater than the column width we allow it to overlap other columns. * In particular, it shouldn't write any null bytes. (Though an extra * null byte past the end is currently tolerated.) If the property * group is non-NULL, then we are dealing with a legacy service. */ void (*sprint)(char **, scf_walkinfo_t *); int sortkey_width; /* * This function should write sortkey_width bytes into buf which will * cause memcmp() to sort it properly. (Unlike sprint() above, * however, an extra null byte may overrun the buffer.) The second * argument controls whether the results are sorted in forward or * reverse order. */ void (*get_sortkey)(char *, int, scf_walkinfo_t *); }; static void reverse_bytes(char *buf, size_t len) { int i; for (i = 0; i < len; ++i) buf[i] = ~buf[i]; } /* CTID */ #define CTID_COLUMN_WIDTH 6 #define CTID_COLUMN_BUFSIZE 20 /* max ctid_t + space + \0 */ static void sprint_ctid(char **buf, scf_walkinfo_t *wip) { int r; uint64_t c; size_t newsize = (*buf ? strlen(*buf) : 0) + CTID_COLUMN_BUFSIZE; char *newbuf = safe_malloc(newsize); int restarter_spec; /* * Use the restarter specific get pids routine, if available. * Only check for non-legacy services (wip->pg == 0). */ if (wip->pg != NULL) { r = pg_get_single_val(wip->pg, scf_property_contract, SCF_TYPE_COUNT, &c, 0, EMPTY_OK | MULTI_OK); } else { r = ctids_by_restarter(wip, &c, 0, MULTI_OK, &restarter_spec, NULL, NULL); if (restarter_spec == 0) { /* No restarter specific routine */ r = get_restarter_count_prop(wip->inst, scf_property_contract, &c, EMPTY_OK | MULTI_OK); } } if (r == 0) (void) snprintf(newbuf, newsize, "%s%*lu ", *buf ? *buf : "", CTID_COLUMN_WIDTH, (ctid_t)c); else if (r == E2BIG) (void) snprintf(newbuf, newsize, "%s%*lu* ", *buf ? *buf : "", CTID_COLUMN_WIDTH - 1, (ctid_t)c); else (void) snprintf(newbuf, newsize, "%s%*s ", *buf ? *buf : "", CTID_COLUMN_WIDTH, "-"); if (*buf) free(*buf); *buf = newbuf; } #define CTID_SORTKEY_WIDTH (sizeof (uint64_t)) static void sortkey_ctid(char *buf, int reverse, scf_walkinfo_t *wip) { int r; uint64_t c; int restarter_spec; /* * Use the restarter specific get pids routine, if available. * Only check for non-legacy services (wip->pg == 0). */ if (wip->pg != NULL) { r = pg_get_single_val(wip->pg, scf_property_contract, SCF_TYPE_COUNT, &c, 0, EMPTY_OK); } else { r = ctids_by_restarter(wip, &c, 0, MULTI_OK, &restarter_spec, NULL, NULL); if (restarter_spec == 0) { /* No restarter specific routine */ r = get_restarter_count_prop(wip->inst, scf_property_contract, &c, EMPTY_OK); } } if (r == 0) { /* * Use the id itself, but it must be big-endian for this to * work. */ c = BE_64(c); bcopy(&c, buf, CTID_SORTKEY_WIDTH); } else { bzero(buf, CTID_SORTKEY_WIDTH); } if (reverse) reverse_bytes(buf, CTID_SORTKEY_WIDTH); } /* DESC */ #define DESC_COLUMN_WIDTH 100 static void sprint_desc(char **buf, scf_walkinfo_t *wip) { char *x; size_t newsize; char *newbuf; if (common_name_buf == NULL) common_name_buf = safe_malloc(max_scf_value_length + 1); bzero(common_name_buf, max_scf_value_length + 1); if (wip->pg != NULL) { common_name_buf[0] = '-'; } else if (inst_get_single_val(wip->inst, SCF_PG_TM_COMMON_NAME, locale, SCF_TYPE_USTRING, common_name_buf, max_scf_value_length, 0, 1, 1) == -1 && inst_get_single_val(wip->inst, SCF_PG_TM_COMMON_NAME, "C", SCF_TYPE_USTRING, common_name_buf, max_scf_value_length, 0, 1, 1) == -1) { common_name_buf[0] = '-'; } /* * Collapse multi-line tm_common_name values into a single line. */ for (x = common_name_buf; *x != '\0'; x++) if (*x == '\n') *x = ' '; if (strlen(common_name_buf) > DESC_COLUMN_WIDTH) newsize = (*buf ? strlen(*buf) : 0) + strlen(common_name_buf) + 1; else newsize = (*buf ? strlen(*buf) : 0) + DESC_COLUMN_WIDTH + 1; newbuf = safe_malloc(newsize); (void) snprintf(newbuf, newsize, "%s%-*s ", *buf ? *buf : "", DESC_COLUMN_WIDTH, common_name_buf); if (*buf) free(*buf); *buf = newbuf; } /* ARGSUSED */ static void sortkey_desc(char *buf, int reverse, scf_walkinfo_t *wip) { bzero(buf, DESC_COLUMN_WIDTH); } /* State columns (STATE, NSTATE, S, N, SN, STA, NSTA) */ static char state_to_char(const char *state) { if (strcmp(state, SCF_STATE_STRING_UNINIT) == 0) return ('u'); if (strcmp(state, SCF_STATE_STRING_OFFLINE) == 0) return ('0'); if (strcmp(state, SCF_STATE_STRING_ONLINE) == 0) return ('1'); if (strcmp(state, SCF_STATE_STRING_MAINT) == 0) return ('m'); if (strcmp(state, SCF_STATE_STRING_DISABLED) == 0) return ('d'); if (strcmp(state, SCF_STATE_STRING_DEGRADED) == 0) return ('D'); if (strcmp(state, SCF_STATE_STRING_LEGACY) == 0) return ('L'); return ('?'); } /* Return true if inst is transitioning. */ static int transitioning(scf_instance_t *inst) { char nstate_name[MAX_SCF_STATE_STRING_SZ]; get_restarter_string_prop(inst, scf_property_next_state, nstate_name, sizeof (nstate_name)); return (state_to_char(nstate_name) != '?'); } /* ARGSUSED */ static void sortkey_states(const char *pname, char *buf, int reverse, scf_walkinfo_t *wip) { char state_name[MAX_SCF_STATE_STRING_SZ]; /* * Lower numbers are printed first, so these are arranged from least * interesting ("legacy run") to most interesting (unknown). */ if (wip->pg == NULL) { get_restarter_string_prop(wip->inst, pname, state_name, sizeof (state_name)); if (strcmp(state_name, SCF_STATE_STRING_ONLINE) == 0) *buf = 2; else if (strcmp(state_name, SCF_STATE_STRING_DEGRADED) == 0) *buf = 3; else if (strcmp(state_name, SCF_STATE_STRING_OFFLINE) == 0) *buf = 4; else if (strcmp(state_name, SCF_STATE_STRING_MAINT) == 0) *buf = 5; else if (strcmp(state_name, SCF_STATE_STRING_DISABLED) == 0) *buf = 1; else if (strcmp(state_name, SCF_STATE_STRING_UNINIT) == 0) *buf = 6; else *buf = 7; } else *buf = 0; if (reverse) *buf = 255 - *buf; } static void sprint_state(char **buf, scf_walkinfo_t *wip) { char state_name[MAX_SCF_STATE_STRING_SZ + 1]; size_t newsize; char *newbuf; if (wip->pg == NULL) { get_restarter_string_prop(wip->inst, scf_property_state, state_name, sizeof (state_name)); /* Don't print blank fields, to ease parsing. */ if (state_name[0] == '\0') { state_name[0] = '-'; state_name[1] = '\0'; } if (!opt_nstate_shown && transitioning(wip->inst)) { /* Append an asterisk if nstate is valid. */ (void) strcat(state_name, "*"); } } else (void) strcpy(state_name, SCF_STATE_STRING_LEGACY); newsize = (*buf ? strlen(*buf) : 0) + MAX_SCF_STATE_STRING_SZ + 2; newbuf = safe_malloc(newsize); (void) snprintf(newbuf, newsize, "%s%-*s ", *buf ? *buf : "", MAX_SCF_STATE_STRING_SZ + 1, state_name); if (*buf) free(*buf); *buf = newbuf; } static void sortkey_state(char *buf, int reverse, scf_walkinfo_t *wip) { sortkey_states(scf_property_state, buf, reverse, wip); } static void sprint_nstate(char **buf, scf_walkinfo_t *wip) { char next_state_name[MAX_SCF_STATE_STRING_SZ]; boolean_t blank = 0; size_t newsize; char *newbuf; if (wip->pg == NULL) { get_restarter_string_prop(wip->inst, scf_property_next_state, next_state_name, sizeof (next_state_name)); /* Don't print blank fields, to ease parsing. */ if (next_state_name[0] == '\0' || strcmp(next_state_name, SCF_STATE_STRING_NONE) == 0) blank = 1; } else blank = 1; if (blank) { next_state_name[0] = '-'; next_state_name[1] = '\0'; } newsize = (*buf ? strlen(*buf) : 0) + MAX_SCF_STATE_STRING_SZ + 1; newbuf = safe_malloc(newsize); (void) snprintf(newbuf, newsize, "%s%-*s ", *buf ? *buf : "", MAX_SCF_STATE_STRING_SZ - 1, next_state_name); if (*buf) free(*buf); *buf = newbuf; } static void sortkey_nstate(char *buf, int reverse, scf_walkinfo_t *wip) { sortkey_states(scf_property_next_state, buf, reverse, wip); } static void sprint_s(char **buf, scf_walkinfo_t *wip) { char tmp[3]; char state_name[MAX_SCF_STATE_STRING_SZ]; size_t newsize = (*buf ? strlen(*buf) : 0) + 4; char *newbuf = safe_malloc(newsize); if (wip->pg == NULL) { get_restarter_string_prop(wip->inst, scf_property_state, state_name, sizeof (state_name)); tmp[0] = state_to_char(state_name); if (!opt_nstate_shown && transitioning(wip->inst)) tmp[1] = '*'; else tmp[1] = ' '; } else { tmp[0] = 'L'; tmp[1] = ' '; } tmp[2] = ' '; (void) snprintf(newbuf, newsize, "%s%-*s", *buf ? *buf : "", 3, tmp); if (*buf) free(*buf); *buf = newbuf; } static void sprint_n(char **buf, scf_walkinfo_t *wip) { char tmp[2]; size_t newsize = (*buf ? strlen(*buf) : 0) + 3; char *newbuf = safe_malloc(newsize); char nstate_name[MAX_SCF_STATE_STRING_SZ]; if (wip->pg == NULL) { get_restarter_string_prop(wip->inst, scf_property_next_state, nstate_name, sizeof (nstate_name)); if (strcmp(nstate_name, SCF_STATE_STRING_NONE) == 0) tmp[0] = '-'; else tmp[0] = state_to_char(nstate_name); } else tmp[0] = '-'; (void) snprintf(newbuf, newsize, "%s%-*s ", *buf ? *buf : "", 2, tmp); if (*buf) free(*buf); *buf = newbuf; } static void sprint_sn(char **buf, scf_walkinfo_t *wip) { char tmp[3]; size_t newsize = (*buf ? strlen(*buf) : 0) + 4; char *newbuf = safe_malloc(newsize); char nstate_name[MAX_SCF_STATE_STRING_SZ]; char state_name[MAX_SCF_STATE_STRING_SZ]; if (wip->pg == NULL) { get_restarter_string_prop(wip->inst, scf_property_state, state_name, sizeof (state_name)); get_restarter_string_prop(wip->inst, scf_property_next_state, nstate_name, sizeof (nstate_name)); tmp[0] = state_to_char(state_name); if (strcmp(nstate_name, SCF_STATE_STRING_NONE) == 0) tmp[1] = '-'; else tmp[1] = state_to_char(nstate_name); } else { tmp[0] = 'L'; tmp[1] = '-'; } tmp[2] = ' '; (void) snprintf(newbuf, newsize, "%s%-*s ", *buf ? *buf : "", 3, tmp); if (*buf) free(*buf); *buf = newbuf; } /* ARGSUSED */ static void sortkey_sn(char *buf, int reverse, scf_walkinfo_t *wip) { sortkey_state(buf, reverse, wip); sortkey_nstate(buf + 1, reverse, wip); } static const char * state_abbrev(const char *state) { if (strcmp(state, SCF_STATE_STRING_UNINIT) == 0) return ("UN"); if (strcmp(state, SCF_STATE_STRING_OFFLINE) == 0) return ("OFF"); if (strcmp(state, SCF_STATE_STRING_ONLINE) == 0) return ("ON"); if (strcmp(state, SCF_STATE_STRING_MAINT) == 0) return ("MNT"); if (strcmp(state, SCF_STATE_STRING_DISABLED) == 0) return ("DIS"); if (strcmp(state, SCF_STATE_STRING_DEGRADED) == 0) return ("DGD"); if (strcmp(state, SCF_STATE_STRING_LEGACY) == 0) return ("LRC"); return ("?"); } static void sprint_sta(char **buf, scf_walkinfo_t *wip) { char state_name[MAX_SCF_STATE_STRING_SZ]; char sta[5]; size_t newsize = (*buf ? strlen(*buf) : 0) + 6; char *newbuf = safe_malloc(newsize); if (wip->pg == NULL) get_restarter_string_prop(wip->inst, scf_property_state, state_name, sizeof (state_name)); else (void) strcpy(state_name, SCF_STATE_STRING_LEGACY); (void) strcpy(sta, state_abbrev(state_name)); if (wip->pg == NULL && !opt_nstate_shown && transitioning(wip->inst)) (void) strcat(sta, "*"); (void) snprintf(newbuf, newsize, "%s%-4s ", *buf ? *buf : "", sta); if (*buf) free(*buf); *buf = newbuf; } static void sprint_nsta(char **buf, scf_walkinfo_t *wip) { char state_name[MAX_SCF_STATE_STRING_SZ]; size_t newsize = (*buf ? strlen(*buf) : 0) + 6; char *newbuf = safe_malloc(newsize); if (wip->pg == NULL) get_restarter_string_prop(wip->inst, scf_property_next_state, state_name, sizeof (state_name)); else (void) strcpy(state_name, SCF_STATE_STRING_NONE); if (strcmp(state_name, SCF_STATE_STRING_NONE) == 0) (void) snprintf(newbuf, newsize, "%s%-4s ", *buf ? *buf : "", "-"); else (void) snprintf(newbuf, newsize, "%s%-4s ", *buf ? *buf : "", state_abbrev(state_name)); if (*buf) free(*buf); *buf = newbuf; } /* FMRI */ #define FMRI_COLUMN_WIDTH 50 static void sprint_fmri(char **buf, scf_walkinfo_t *wip) { char *fmri_buf = safe_malloc(max_scf_fmri_length + 1); size_t newsize; char *newbuf; if (wip->pg == NULL) { if (scf_instance_to_fmri(wip->inst, fmri_buf, max_scf_fmri_length + 1) == -1) scfdie(); } else { (void) strcpy(fmri_buf, SCF_FMRI_LEGACY_PREFIX); if (pg_get_single_val(wip->pg, SCF_LEGACY_PROPERTY_NAME, SCF_TYPE_ASTRING, fmri_buf + sizeof (SCF_FMRI_LEGACY_PREFIX) - 1, max_scf_fmri_length + 1 - (sizeof (SCF_FMRI_LEGACY_PREFIX) - 1), 0) != 0) (void) strcat(fmri_buf, LEGACY_UNKNOWN); } if (strlen(fmri_buf) > FMRI_COLUMN_WIDTH) newsize = (*buf ? strlen(*buf) : 0) + strlen(fmri_buf) + 2; else newsize = (*buf ? strlen(*buf) : 0) + FMRI_COLUMN_WIDTH + 2; newbuf = safe_malloc(newsize); (void) snprintf(newbuf, newsize, "%s%-*s ", *buf ? *buf : "", FMRI_COLUMN_WIDTH, fmri_buf); free(fmri_buf); if (*buf) free(*buf); *buf = newbuf; } static void sortkey_fmri(char *buf, int reverse, scf_walkinfo_t *wip) { char *tmp = NULL; sprint_fmri(&tmp, wip); bcopy(tmp, buf, FMRI_COLUMN_WIDTH); free(tmp); if (reverse) reverse_bytes(buf, FMRI_COLUMN_WIDTH); } /* Component columns */ #define COMPONENT_COLUMN_WIDTH 20 static void sprint_scope(char **buf, scf_walkinfo_t *wip) { char *scope_buf = safe_malloc(max_scf_name_length + 1); size_t newsize = (*buf ? strlen(*buf) : 0) + COMPONENT_COLUMN_WIDTH + 2; char *newbuf = safe_malloc(newsize); assert(wip->scope != NULL); if (scf_scope_get_name(wip->scope, scope_buf, max_scf_name_length) < 0) scfdie(); (void) snprintf(newbuf, newsize, "%s%-*s ", *buf ? *buf : "", COMPONENT_COLUMN_WIDTH, scope_buf); if (*buf) free(*buf); *buf = newbuf; free(scope_buf); } static void sortkey_scope(char *buf, int reverse, scf_walkinfo_t *wip) { char *tmp = NULL; sprint_scope(&tmp, wip); bcopy(tmp, buf, COMPONENT_COLUMN_WIDTH); free(tmp); if (reverse) reverse_bytes(buf, COMPONENT_COLUMN_WIDTH); } static void sprint_service(char **buf, scf_walkinfo_t *wip) { char *svc_buf = safe_malloc(max_scf_name_length + 1); char *newbuf; size_t newsize; if (wip->pg == NULL) { if (scf_service_get_name(wip->svc, svc_buf, max_scf_name_length + 1) < 0) scfdie(); } else { if (pg_get_single_val(wip->pg, "name", SCF_TYPE_ASTRING, svc_buf, max_scf_name_length + 1, EMPTY_OK) != 0) (void) strcpy(svc_buf, LEGACY_UNKNOWN); } if (strlen(svc_buf) > COMPONENT_COLUMN_WIDTH) newsize = (*buf ? strlen(*buf) : 0) + strlen(svc_buf) + 2; else newsize = (*buf ? strlen(*buf) : 0) + COMPONENT_COLUMN_WIDTH + 2; newbuf = safe_malloc(newsize); (void) snprintf(newbuf, newsize, "%s%-*s ", *buf ? *buf : "", COMPONENT_COLUMN_WIDTH, svc_buf); free(svc_buf); if (*buf) free(*buf); *buf = newbuf; } static void sortkey_service(char *buf, int reverse, scf_walkinfo_t *wip) { char *tmp = NULL; sprint_service(&tmp, wip); bcopy(tmp, buf, COMPONENT_COLUMN_WIDTH); free(tmp); if (reverse) reverse_bytes(buf, COMPONENT_COLUMN_WIDTH); } /* INST */ static void sprint_instance(char **buf, scf_walkinfo_t *wip) { char *tmp = safe_malloc(max_scf_name_length + 1); size_t newsize = (*buf ? strlen(*buf) : 0) + COMPONENT_COLUMN_WIDTH + 2; char *newbuf = safe_malloc(newsize); if (wip->pg == NULL) { if (scf_instance_get_name(wip->inst, tmp, max_scf_name_length + 1) < 0) scfdie(); } else { tmp[0] = '-'; tmp[1] = '\0'; } (void) snprintf(newbuf, newsize, "%s%-*s ", *buf ? *buf : "", COMPONENT_COLUMN_WIDTH, tmp); if (*buf) free(*buf); *buf = newbuf; free(tmp); } static void sortkey_instance(char *buf, int reverse, scf_walkinfo_t *wip) { char *tmp = NULL; sprint_instance(&tmp, wip); bcopy(tmp, buf, COMPONENT_COLUMN_WIDTH); free(tmp); if (reverse) reverse_bytes(buf, COMPONENT_COLUMN_WIDTH); } /* STIME */ #define STIME_COLUMN_WIDTH 8 #define FORMAT_TIME "%k:%M:%S" #define FORMAT_DATE "%b_%d " #define FORMAT_YEAR "%Y " /* * sprint_stime() will allocate a new buffer and snprintf the services's * state timestamp. If the timestamp is unavailable for some reason * a '-' is given instead. */ static void sprint_stime(char **buf, scf_walkinfo_t *wip) { int r; struct timeval tv; time_t then; struct tm *tm; char st_buf[STIME_COLUMN_WIDTH + 1]; size_t newsize = (*buf ? strlen(*buf) : 0) + STIME_COLUMN_WIDTH + 2; char *newbuf = safe_malloc(newsize); if (wip->pg == NULL) { r = get_restarter_time_prop(wip->inst, SCF_PROPERTY_STATE_TIMESTAMP, &tv, 0); } else { r = pg_get_single_val(wip->pg, SCF_PROPERTY_STATE_TIMESTAMP, SCF_TYPE_TIME, &tv, 0, 0); } if (r != 0) { /* * There's something amiss with our service * so we'll print a '-' for STIME. */ (void) snprintf(newbuf, newsize, "%s%-*s", *buf ? *buf : "", STIME_COLUMN_WIDTH + 1, "-"); } else { /* tv should be valid so we'll format it */ then = (time_t)tv.tv_sec; tm = localtime(&then); /* * Print time if started within the past 24 hours, print date * if within the past 12 months or, finally, print year if * started greater than 12 months ago. */ if (now - then < 24 * 60 * 60) { (void) strftime(st_buf, sizeof (st_buf), gettext(FORMAT_TIME), tm); } else if (now - then < 12 * 30 * 24 * 60 * 60) { (void) strftime(st_buf, sizeof (st_buf), gettext(FORMAT_DATE), tm); } else { (void) strftime(st_buf, sizeof (st_buf), gettext(FORMAT_YEAR), tm); } (void) snprintf(newbuf, newsize, "%s%-*s ", *buf ? *buf : "", STIME_COLUMN_WIDTH + 1, st_buf); } if (*buf) free(*buf); *buf = newbuf; } #define STIME_SORTKEY_WIDTH (sizeof (uint64_t) + sizeof (uint32_t)) /* ARGSUSED */ static void sortkey_stime(char *buf, int reverse, scf_walkinfo_t *wip) { struct timeval tv; int r; if (wip->pg == NULL) r = get_restarter_time_prop(wip->inst, SCF_PROPERTY_STATE_TIMESTAMP, &tv, 0); else r = pg_get_single_val(wip->pg, SCF_PROPERTY_STATE_TIMESTAMP, SCF_TYPE_TIME, &tv, 0, 0); if (r == 0) { int64_t sec; int32_t us; /* Stick it straight into the buffer. */ sec = tv.tv_sec; us = tv.tv_usec; sec = BE_64(sec); us = BE_32(us); bcopy(&sec, buf, sizeof (sec)); bcopy(&us, buf + sizeof (sec), sizeof (us)); } else { bzero(buf, STIME_SORTKEY_WIDTH); } if (reverse) reverse_bytes(buf, STIME_SORTKEY_WIDTH); } /* ZONE */ #define ZONE_COLUMN_WIDTH 16 /*ARGSUSED*/ static void sprint_zone(char **buf, scf_walkinfo_t *wip) { size_t newsize; char *newbuf, *zonename = g_zonename, b[ZONENAME_MAX]; if (zonename == NULL) { zoneid_t zoneid = getzoneid(); if (getzonenamebyid(zoneid, b, sizeof (b)) < 0) uu_die(gettext("could not determine zone name")); zonename = b; } if (strlen(zonename) > ZONE_COLUMN_WIDTH) newsize = (*buf ? strlen(*buf) : 0) + strlen(zonename) + 2; else newsize = (*buf ? strlen(*buf) : 0) + ZONE_COLUMN_WIDTH + 2; newbuf = safe_malloc(newsize); (void) snprintf(newbuf, newsize, "%s%-*s ", *buf ? *buf : "", ZONE_COLUMN_WIDTH, zonename); if (*buf) free(*buf); *buf = newbuf; } static void sortkey_zone(char *buf, int reverse, scf_walkinfo_t *wip) { char *tmp = NULL; sprint_zone(&tmp, wip); bcopy(tmp, buf, ZONE_COLUMN_WIDTH); free(tmp); if (reverse) reverse_bytes(buf, ZONE_COLUMN_WIDTH); } /* * Information about columns which can be displayed. If you add something, * check MAX_COLUMN_NAME_LENGTH_STR & update description_of_column() below. */ static const struct column columns[] = { { "CTID", CTID_COLUMN_WIDTH, sprint_ctid, CTID_SORTKEY_WIDTH, sortkey_ctid }, { "DESC", DESC_COLUMN_WIDTH, sprint_desc, DESC_COLUMN_WIDTH, sortkey_desc }, { "FMRI", FMRI_COLUMN_WIDTH, sprint_fmri, FMRI_COLUMN_WIDTH, sortkey_fmri }, { "INST", COMPONENT_COLUMN_WIDTH, sprint_instance, COMPONENT_COLUMN_WIDTH, sortkey_instance }, { "N", 1, sprint_n, 1, sortkey_nstate }, { "NSTA", 4, sprint_nsta, 1, sortkey_nstate }, { "NSTATE", MAX_SCF_STATE_STRING_SZ - 1, sprint_nstate, 1, sortkey_nstate }, { "S", 2, sprint_s, 1, sortkey_state }, { "SCOPE", COMPONENT_COLUMN_WIDTH, sprint_scope, COMPONENT_COLUMN_WIDTH, sortkey_scope }, { "SN", 2, sprint_sn, 2, sortkey_sn }, { "SVC", COMPONENT_COLUMN_WIDTH, sprint_service, COMPONENT_COLUMN_WIDTH, sortkey_service }, { "STA", 4, sprint_sta, 1, sortkey_state }, { "STATE", MAX_SCF_STATE_STRING_SZ - 1 + 1, sprint_state, 1, sortkey_state }, { "STIME", STIME_COLUMN_WIDTH, sprint_stime, STIME_SORTKEY_WIDTH, sortkey_stime }, { "ZONE", ZONE_COLUMN_WIDTH, sprint_zone, ZONE_COLUMN_WIDTH, sortkey_zone }, }; #define MAX_COLUMN_NAME_LENGTH_STR "6" static const int ncolumns = sizeof (columns) / sizeof (columns[0]); /* * Necessary thanks to gettext() & xgettext. */ static const char * description_of_column(int c) { const char *s = NULL; switch (c) { case 0: s = gettext("contract ID for service (see contract(5))"); break; case 1: s = gettext("human-readable description of the service"); break; case 2: s = gettext("Fault Managed Resource Identifier for service"); break; case 3: s = gettext("portion of the FMRI indicating service instance"); break; case 4: s = gettext("abbreviation for next state (if in transition)"); break; case 5: s = gettext("abbreviation for next state (if in transition)"); break; case 6: s = gettext("name for next state (if in transition)"); break; case 7: s = gettext("abbreviation for current state"); break; case 8: s = gettext("name for scope associated with service"); break; case 9: s = gettext("abbreviation for current state and next state"); break; case 10: s = gettext("portion of the FMRI representing service name"); break; case 11: s = gettext("abbreviation for current state"); break; case 12: s = gettext("name for current state"); break; case 13: s = gettext("time of last state change"); break; case 14: s = gettext("name of zone"); break; } assert(s != NULL); return (s); } static void print_usage(const char *progname, FILE *f) { (void) fprintf(f, gettext( "Usage: %1$s [-aHpv] [-o col[,col ... ]] [-R restarter] " "[-sS col] [-Z | -z zone ]\n [ ...]\n" " %1$s -d | -D [-Hpv] [-o col[,col ... ]] [-sS col] " "[-Z | -z zone ]\n [ ...]\n" " %1$s [-l | -L] [-Z | -z zone] ...\n" " %1$s -x [-v] [-Z | -z zone] [ ...]\n" " %1$s -?\n"), progname); } static __NORETURN void argserr(const char *progname) { print_usage(progname, stderr); exit(UU_EXIT_USAGE); } static void print_help(const char *progname) { int i; print_usage(progname, stdout); (void) printf(gettext("\n" "\t-a list all service instances rather than " "only those that are enabled\n" "\t-d list dependencies of the specified service(s)\n" "\t-D list dependents of the specified service(s)\n" "\t-H omit header line from output\n" "\t-l list detailed information about the specified service(s)\n" "\t-L list the log file associated with the specified service(s)\n" "\t-o list only the specified columns in the output\n" "\t-p list process IDs and names associated with each service\n" "\t-R list only those services with the specified restarter\n" "\t-s sort output in ascending order by the specified column(s)\n" "\t-S sort output in descending order by the specified column(s)\n" "\t-v list verbose information appropriate to the type of output\n" "\t-x explain the status of services that might require maintenance,\n" "\t or explain the status of the specified service(s)\n" "\t-z from global zone, show services in a specified zone\n" "\t-Z from global zone, show services in all zones\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 [opts] svc:/network/smtp:sendmail\n" "\t%1$s [opts] network/smtp:sendmail\n" "\t%1$s [opts] network/*mail\n" "\t%1$s [opts] network/smtp\n" "\t%1$s [opts] smtp:sendmail\n" "\t%1$s [opts] smtp\n" "\t%1$s [opts] sendmail\n" "\n\t" "Columns for output or sorting can be specified using these names:\n" "\n"), progname); for (i = 0; i < ncolumns; i++) { (void) printf("\t%-" MAX_COLUMN_NAME_LENGTH_STR "s %s\n", columns[i].name, description_of_column(i)); } } /* * A getsubopt()-like function which returns an index into the columns table. * On success, *optionp is set to point to the next sub-option, or the * terminating null if there are none. */ static int getcolumnopt(char **optionp) { char *str = *optionp, *cp; int i; assert(optionp != NULL); assert(*optionp != NULL); cp = strchr(*optionp, ','); if (cp != NULL) *cp = '\0'; for (i = 0; i < ncolumns; ++i) { if (strcasecmp(str, columns[i].name) == 0) { if (cp != NULL) *optionp = cp + 1; else *optionp = strchr(*optionp, '\0'); return (i); } } return (-1); } static void print_header() { int i; char *line_buf, *cp; line_buf = safe_malloc(line_sz); cp = line_buf; for (i = 0; i < opt_cnum; ++i) { const struct column * const colp = &columns[opt_columns[i]]; (void) snprintf(cp, colp->width + 1, "%-*s", colp->width, colp->name); cp += colp->width; *cp++ = ' '; } /* Trim the trailing whitespace */ --cp; while (*cp == ' ') --cp; *(cp+1) = '\0'; (void) puts(line_buf); free(line_buf); } /* * Long listing (-l) functions. */ static int pidcmp(const void *l, const void *r) { pid_t lp = *(pid_t *)l, rp = *(pid_t *)r; if (lp < rp) return (-1); if (lp > rp) return (1); return (0); } /* * This is the strlen() of the longest label ("description"), plus intercolumn * space. */ #define DETAILED_WIDTH (11 + 2) /* * Callback routine to print header for contract id. * Called by ctids_by_restarter and print_detailed. */ static void print_ctid_header() { (void) printf("%-*s", DETAILED_WIDTH, "contract_id"); } /* * Callback routine to print a contract id. * Called by ctids_by_restarter and print_detailed. */ static void print_ctid_detailed(uint64_t c) { (void) printf("%lu ", (ctid_t)c); } static void detailed_list_processes(scf_walkinfo_t *wip) { uint64_t c; pid_t *pids; uint_t i, n; psinfo_t psi; if (get_restarter_count_prop(wip->inst, scf_property_contract, &c, EMPTY_OK) != 0) return; if (instance_processes(wip->inst, wip->fmri, &pids, &n) != 0) return; qsort(pids, n, sizeof (*pids), pidcmp); for (i = 0; i < n; ++i) { (void) printf("%-*s%lu", DETAILED_WIDTH, gettext("process"), pids[i]); if (proc_get_psinfo(pids[i], &psi) == 0 && !IS_ZOMBIE(&psi)) (void) printf(" %.*s", PRARGSZ, psi.pr_psargs); (void) putchar('\n'); } free(pids); } /* * Determines the state of a dependency. If the FMRI specifies a file, then we * fake up a state based on whether we can access the file. */ static void get_fmri_state(char *fmri, char *state, size_t state_sz) { char *lfmri; const char *svc_name, *inst_name, *pg_name, *path; scf_service_t *svc; scf_instance_t *inst; scf_iter_t *iter; lfmri = safe_strdup(fmri); /* * Check for file:// dependencies */ if (scf_parse_file_fmri(lfmri, NULL, &path) == SCF_SUCCESS) { struct stat64 statbuf; const char *msg; if (stat64(path, &statbuf) == 0) msg = "online"; else if (errno == ENOENT) msg = "absent"; else msg = "unknown"; (void) strlcpy(state, msg, state_sz); return; } /* * scf_parse_file_fmri() may have overwritten part of the string, so * copy it back. */ (void) strcpy(lfmri, fmri); if (scf_parse_svc_fmri(lfmri, NULL, &svc_name, &inst_name, &pg_name, NULL) != SCF_SUCCESS) { free(lfmri); (void) strlcpy(state, "invalid", state_sz); return; } free(lfmri); if (svc_name == NULL || pg_name != NULL) { (void) strlcpy(state, "invalid", state_sz); return; } if (inst_name != NULL) { /* instance: get state */ inst = scf_instance_create(h); if (inst == NULL) scfdie(); if (scf_handle_decode_fmri(h, fmri, NULL, NULL, inst, NULL, NULL, SCF_DECODE_FMRI_EXACT) == SCF_SUCCESS) get_restarter_string_prop(inst, scf_property_state, state, state_sz); else { switch (scf_error()) { case SCF_ERROR_INVALID_ARGUMENT: (void) strlcpy(state, "invalid", state_sz); break; case SCF_ERROR_NOT_FOUND: (void) strlcpy(state, "absent", state_sz); break; default: scfdie(); } } scf_instance_destroy(inst); return; } /* * service: If only one instance, use that state. Otherwise, say * "multiple". */ if ((svc = scf_service_create(h)) == NULL || (inst = 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) { switch (scf_error()) { case SCF_ERROR_INVALID_ARGUMENT: (void) strlcpy(state, "invalid", state_sz); goto out; case SCF_ERROR_NOT_FOUND: (void) strlcpy(state, "absent", state_sz); goto out; default: scfdie(); } } if (scf_iter_service_instances(iter, svc) != SCF_SUCCESS) scfdie(); switch (scf_iter_next_instance(iter, inst)) { case 0: (void) strlcpy(state, "absent", state_sz); goto out; case 1: break; default: scfdie(); } /* Get the state in case this is the only instance. */ get_restarter_string_prop(inst, scf_property_state, state, state_sz); switch (scf_iter_next_instance(iter, inst)) { case 0: break; case 1: /* Nope, multiple instances. */ (void) strlcpy(state, "multiple", state_sz); goto out; default: scfdie(); } out: scf_iter_destroy(iter); scf_instance_destroy(inst); scf_service_destroy(svc); } static void print_application_properties(scf_walkinfo_t *wip, scf_snapshot_t *snap) { scf_iter_t *pg_iter, *prop_iter, *val_iter; scf_propertygroup_t *pg; scf_property_t *prop; scf_value_t *val; scf_pg_tmpl_t *pt; scf_prop_tmpl_t *prt; char *pg_name_buf = safe_malloc(max_scf_name_length + 1); char *prop_name_buf = safe_malloc(max_scf_name_length + 1); char *snap_name = safe_malloc(max_scf_name_length + 1); char *val_buf = safe_malloc(max_scf_value_length + 1); char *desc, *cp; scf_type_t type; int i, j, k; uint8_t vis; if ((pg_iter = scf_iter_create(h)) == NULL || (prop_iter = scf_iter_create(h)) == NULL || (val_iter = scf_iter_create(h)) == NULL || (val = scf_value_create(h)) == NULL || (prop = scf_property_create(h)) == NULL || (pt = scf_tmpl_pg_create(h)) == NULL || (prt = scf_tmpl_prop_create(h)) == NULL || (pg = scf_pg_create(h)) == NULL) scfdie(); if (scf_iter_instance_pgs_typed_composed(pg_iter, wip->inst, snap, SCF_PG_APP_DEFAULT) == -1) scfdie(); /* * Format for output: * pg (pgtype) * description * pg/prop (proptype) = * description */ while ((i = scf_iter_next_pg(pg_iter, pg)) == 1) { int tmpl = 0; if (scf_pg_get_name(pg, pg_name_buf, max_scf_name_length) < 0) scfdie(); if (scf_snapshot_get_name(snap, snap_name, max_scf_name_length) < 0) scfdie(); if (scf_tmpl_get_by_pg_name(wip->fmri, snap_name, pg_name_buf, SCF_PG_APP_DEFAULT, pt, 0) == 0) tmpl = 1; else tmpl = 0; (void) printf("%s (%s)\n", pg_name_buf, SCF_PG_APP_DEFAULT); if (tmpl == 1 && scf_tmpl_pg_description(pt, NULL, &desc) > 0) { (void) printf(" %s\n", desc); free(desc); } if (scf_iter_pg_properties(prop_iter, pg) == -1) scfdie(); while ((j = scf_iter_next_property(prop_iter, prop)) == 1) { if (scf_property_get_name(prop, prop_name_buf, max_scf_name_length) < 0) scfdie(); if (scf_property_type(prop, &type) == -1) scfdie(); if ((tmpl == 1) && (scf_tmpl_get_by_prop(pt, prop_name_buf, prt, 0) != 0)) tmpl = 0; if (tmpl == 1 && scf_tmpl_prop_visibility(prt, &vis) != -1 && vis == SCF_TMPL_VISIBILITY_HIDDEN) continue; (void) printf("%s/%s (%s) = ", pg_name_buf, prop_name_buf, scf_type_to_string(type)); if (scf_iter_property_values(val_iter, prop) == -1) scfdie(); while ((k = scf_iter_next_value(val_iter, val)) == 1) { if (scf_value_get_as_string(val, val_buf, max_scf_value_length + 1) < 0) scfdie(); if (strpbrk(val_buf, " \t\n\"()") != NULL) { (void) printf("\""); for (cp = val_buf; *cp != '\0'; ++cp) { if (*cp == '"' || *cp == '\\') (void) putc('\\', stdout); (void) putc(*cp, stdout); } (void) printf("\""); } else { (void) printf("%s ", val_buf); } } (void) printf("\n"); if (k == -1) scfdie(); if (tmpl == 1 && scf_tmpl_prop_description(prt, NULL, &desc) > 0) { (void) printf(" %s\n", desc); free(desc); } } if (j == -1) scfdie(); } if (i == -1) scfdie(); scf_iter_destroy(pg_iter); scf_iter_destroy(prop_iter); scf_iter_destroy(val_iter); scf_value_destroy(val); scf_property_destroy(prop); scf_tmpl_pg_destroy(pt); scf_tmpl_prop_destroy(prt); scf_pg_destroy(pg); free(pg_name_buf); free(prop_name_buf); free(snap_name); free(val_buf); } static void print_detailed_dependency(scf_propertygroup_t *pg) { scf_property_t *eprop; scf_iter_t *iter; scf_type_t ty; char *val_buf; int i; if ((eprop = scf_property_create(h)) == NULL || (iter = scf_iter_create(h)) == NULL) scfdie(); val_buf = safe_malloc(max_scf_value_length + 1); if (scf_pg_get_property(pg, SCF_PROPERTY_ENTITIES, eprop) != SCF_SUCCESS || scf_property_type(eprop, &ty) != SCF_SUCCESS || ty != SCF_TYPE_FMRI) return; (void) printf("%-*s", DETAILED_WIDTH, gettext("dependency")); /* Print the grouping */ if (pg_get_single_val(pg, SCF_PROPERTY_GROUPING, SCF_TYPE_ASTRING, val_buf, max_scf_value_length + 1, 0) == 0) (void) fputs(val_buf, stdout); else (void) putchar('?'); (void) putchar('/'); if (pg_get_single_val(pg, SCF_PROPERTY_RESTART_ON, SCF_TYPE_ASTRING, val_buf, max_scf_value_length + 1, 0) == 0) (void) fputs(val_buf, stdout); else (void) putchar('?'); /* Print the dependency entities. */ if (scf_iter_property_values(iter, eprop) == -1) scfdie(); while ((i = scf_iter_next_value(iter, g_val)) == 1) { char state[MAX_SCF_STATE_STRING_SZ]; if (scf_value_get_astring(g_val, val_buf, max_scf_value_length + 1) < 0) scfdie(); (void) putchar(' '); (void) fputs(val_buf, stdout); /* Print the state. */ state[0] = '-'; state[1] = '\0'; get_fmri_state(val_buf, state, sizeof (state)); (void) printf(" (%s)", state); } if (i == -1) scfdie(); (void) putchar('\n'); free(val_buf); scf_iter_destroy(iter); scf_property_destroy(eprop); } /* ARGSUSED */ static int print_detailed(void *unused, scf_walkinfo_t *wip) { scf_snapshot_t *snap; scf_propertygroup_t *rpg; scf_iter_t *pg_iter; char *buf; char *timebuf; size_t tbsz; int ret; uint64_t c; int temp, perm; struct timeval tv; time_t stime; struct tm *tmp; int restarter_spec; int restarter_ret; const char * const fmt = "%-*s%s\n"; assert(wip->pg == NULL); rpg = scf_pg_create(h); if (rpg == NULL) scfdie(); if (first_paragraph) first_paragraph = 0; else (void) putchar('\n'); buf = safe_malloc(max_scf_fmri_length + 1); if (scf_instance_to_fmri(wip->inst, buf, max_scf_fmri_length + 1) != -1) (void) printf(fmt, DETAILED_WIDTH, "fmri", buf); if (common_name_buf == NULL) common_name_buf = safe_malloc(max_scf_value_length + 1); if (inst_get_single_val(wip->inst, SCF_PG_TM_COMMON_NAME, locale, SCF_TYPE_USTRING, common_name_buf, max_scf_value_length, 0, 1, 1) == 0) (void) printf(fmt, DETAILED_WIDTH, gettext("name"), common_name_buf); else if (inst_get_single_val(wip->inst, SCF_PG_TM_COMMON_NAME, "C", SCF_TYPE_USTRING, common_name_buf, max_scf_value_length, 0, 1, 1) == 0) (void) printf(fmt, DETAILED_WIDTH, gettext("name"), common_name_buf); if (g_zonename != NULL) (void) printf(fmt, DETAILED_WIDTH, gettext("zone"), g_zonename); /* * Synthesize an 'enabled' property that hides the enabled_ovr * implementation from the user. If the service has been temporarily * set to a state other than its permanent value, alert the user with * a '(temporary)' message. */ perm = instance_enabled(wip->inst, B_FALSE); temp = instance_enabled(wip->inst, B_TRUE); if (temp != -1) { if (temp != perm) (void) printf(gettext("%-*s%s (temporary)\n"), DETAILED_WIDTH, gettext("enabled"), temp ? gettext("true") : gettext("false")); else (void) printf(fmt, DETAILED_WIDTH, gettext("enabled"), temp ? gettext("true") : gettext("false")); } else if (perm != -1) { (void) printf(fmt, DETAILED_WIDTH, gettext("enabled"), perm ? gettext("true") : gettext("false")); } if (temp == 0 || (temp == -1 && perm == 0)) { char comment[SCF_COMMENT_MAX_LENGTH] = ""; const char *pg = (temp != -1 && temp != perm) ? SCF_PG_GENERAL_OVR : SCF_PG_GENERAL; (void) inst_get_single_val(wip->inst, pg, SCF_PROPERTY_COMMENT, SCF_TYPE_ASTRING, &comment, sizeof (comment), EMPTY_OK, 0, 0); if (comment[0] != '\0') { printf(fmt, DETAILED_WIDTH, gettext("comment"), comment); } } /* * Property values may be longer than max_scf_fmri_length, but these * shouldn't be, so we'll just reuse buf. The user can use svcprop if * they suspect something fishy. */ if (scf_instance_get_pg(wip->inst, SCF_PG_RESTARTER, rpg) != 0) { if (scf_error() != SCF_ERROR_NOT_FOUND) scfdie(); scf_pg_destroy(rpg); rpg = NULL; } if (rpg) { if (pg_get_single_val(rpg, scf_property_state, SCF_TYPE_ASTRING, buf, max_scf_fmri_length + 1, 0) == 0) (void) printf(fmt, DETAILED_WIDTH, gettext("state"), buf); if (pg_get_single_val(rpg, scf_property_next_state, SCF_TYPE_ASTRING, buf, max_scf_fmri_length + 1, 0) == 0) (void) printf(fmt, DETAILED_WIDTH, gettext("next_state"), buf); if (pg_get_single_val(rpg, SCF_PROPERTY_STATE_TIMESTAMP, SCF_TYPE_TIME, &tv, 0, 0) == 0) { stime = tv.tv_sec; tmp = localtime(&stime); for (tbsz = 50; ; tbsz *= 2) { timebuf = safe_malloc(tbsz); if (strftime(timebuf, tbsz, NULL, tmp) != 0) break; free(timebuf); } (void) printf(fmt, DETAILED_WIDTH, gettext("state_time"), timebuf); free(timebuf); } if (pg_get_single_val(rpg, SCF_PROPERTY_ALT_LOGFILE, SCF_TYPE_ASTRING, buf, max_scf_fmri_length + 1, 0) == 0) (void) printf(fmt, DETAILED_WIDTH, gettext("alt_logfile"), buf); if (pg_get_single_val(rpg, SCF_PROPERTY_LOGFILE, SCF_TYPE_ASTRING, buf, max_scf_fmri_length + 1, 0) == 0) (void) printf(fmt, DETAILED_WIDTH, gettext("logfile"), buf); } if (inst_get_single_val(wip->inst, SCF_PG_GENERAL, SCF_PROPERTY_RESTARTER, SCF_TYPE_ASTRING, buf, max_scf_fmri_length + 1, 0, 0, 1) == 0) (void) printf(fmt, DETAILED_WIDTH, gettext("restarter"), buf); else (void) printf(fmt, DETAILED_WIDTH, gettext("restarter"), SCF_SERVICE_STARTD); free(buf); /* * Use the restarter specific routine to print the ctids, if available. * If restarter specific action is available and it fails, then die. */ restarter_ret = ctids_by_restarter(wip, &c, 1, 0, &restarter_spec, print_ctid_header, print_ctid_detailed); if (restarter_spec == 1) { if (restarter_ret != 0) uu_die(gettext("Unable to get restarter for %s"), wip->fmri); goto restarter_common; } if (rpg) { scf_iter_t *iter; if ((iter = scf_iter_create(h)) == NULL) scfdie(); if (scf_pg_get_property(rpg, scf_property_contract, g_prop) == 0) { if (scf_property_is_type(g_prop, SCF_TYPE_COUNT) == 0) { /* Callback to print ctid header */ print_ctid_header(); if (scf_iter_property_values(iter, g_prop) != 0) scfdie(); for (;;) { ret = scf_iter_next_value(iter, g_val); if (ret == -1) scfdie(); if (ret == 0) break; if (scf_value_get_count(g_val, &c) != 0) scfdie(); /* Callback to print contract id. */ print_ctid_detailed(c); } (void) putchar('\n'); } else { if (scf_error() != SCF_ERROR_TYPE_MISMATCH) scfdie(); } } else { if (scf_error() != SCF_ERROR_NOT_FOUND) scfdie(); } scf_iter_destroy(iter); } else { if (scf_error() != SCF_ERROR_NOT_FOUND) scfdie(); } restarter_common: scf_pg_destroy(rpg); /* Dependencies. */ if ((pg_iter = scf_iter_create(h)) == NULL) scfdie(); snap = get_running_snapshot(wip->inst); if (scf_iter_instance_pgs_typed_composed(pg_iter, wip->inst, snap, SCF_GROUP_DEPENDENCY) != SCF_SUCCESS) scfdie(); while ((ret = scf_iter_next_pg(pg_iter, g_pg)) == 1) print_detailed_dependency(g_pg); if (ret == -1) scfdie(); scf_iter_destroy(pg_iter); if (opt_processes) detailed_list_processes(wip); /* "application" type property groups */ if (opt_verbose == 1) print_application_properties(wip, snap); scf_snapshot_destroy(snap); return (0); } static int print_log(void *unused __unused, scf_walkinfo_t *wip) { scf_propertygroup_t *rpg; char buf[MAXPATHLEN]; if ((rpg = scf_pg_create(h)) == NULL) scfdie(); if (scf_instance_get_pg(wip->inst, SCF_PG_RESTARTER, rpg) != 0) { if (scf_error() != SCF_ERROR_NOT_FOUND) scfdie(); goto out; } if (pg_get_single_val(rpg, SCF_PROPERTY_LOGFILE, SCF_TYPE_ASTRING, buf, sizeof (buf), 0) == 0) { (void) printf("%s\n", buf); } else if (pg_get_single_val(rpg, SCF_PROPERTY_ALT_LOGFILE, SCF_TYPE_ASTRING, buf, sizeof (buf), 0) == 0) { (void) printf("%s\n", buf); } out: scf_pg_destroy(rpg); return (0); } int qsort_str_compare(const void *p1, const void *p2) { return (strcmp((const char *)p1, (const char *)p2)); } /* * get_notify_param_classes() * return the fma classes that don't have a tag in fma_tags[], otherwise NULL */ static char ** get_notify_param_classes() { scf_handle_t *h = _scf_handle_create_and_bind(SCF_VERSION); scf_instance_t *inst = scf_instance_create(h); scf_snapshot_t *snap = scf_snapshot_create(h); scf_snaplevel_t *slvl = scf_snaplevel_create(h); scf_propertygroup_t *pg = scf_pg_create(h); scf_iter_t *iter = scf_iter_create(h); int size = 4; int n = 0; size_t sz = scf_limit(SCF_LIMIT_MAX_NAME_LENGTH) + 1; int err; char *pgname = safe_malloc(sz); char **buf = safe_malloc(size * sizeof (char *)); if (h == NULL || inst == NULL || snap == NULL || slvl == NULL || pg == NULL || iter == NULL) { uu_die(gettext("Failed object creation: %s\n"), scf_strerror(scf_error())); } if (scf_handle_decode_fmri(h, SCF_NOTIFY_PARAMS_INST, NULL, NULL, inst, NULL, NULL, SCF_DECODE_FMRI_EXACT) != 0) uu_die(gettext("Failed to decode %s: %s\n"), SCF_NOTIFY_PARAMS_INST, scf_strerror(scf_error())); if (scf_instance_get_snapshot(inst, "running", snap) != 0) uu_die(gettext("Failed to get snapshot: %s\n"), scf_strerror(scf_error())); if (scf_snapshot_get_base_snaplevel(snap, slvl) != 0) uu_die(gettext("Failed to get base snaplevel: %s\n"), scf_strerror(scf_error())); if (scf_iter_snaplevel_pgs_typed(iter, slvl, SCF_NOTIFY_PARAMS_PG_TYPE) != 0) uu_die(gettext("Failed to get iterator: %s\n"), scf_strerror(scf_error())); while ((err = scf_iter_next_pg(iter, pg)) == 1) { char *c; if (scf_pg_get_name(pg, pgname, sz) == -1) uu_die(gettext("Failed to get pg name: %s\n"), scf_strerror(scf_error())); if ((c = strrchr(pgname, ',')) != NULL) *c = '\0'; if (has_fma_tag(pgname)) continue; if (!is_fma_token(pgname)) /* * We don't emmit a warning here so that we don't * pollute the output */ continue; if (n + 1 >= size) { size *= 2; buf = realloc(buf, size * sizeof (char *)); if (buf == NULL) uu_die(gettext("Out of memory.\n")); } buf[n] = safe_strdup(pgname); ++n; } /* * NULL terminate buf */ buf[n] = NULL; if (err == -1) uu_die(gettext("Failed to iterate pgs: %s\n"), scf_strerror(scf_error())); /* sort the classes */ qsort((void *)buf, n, sizeof (char *), qsort_str_compare); free(pgname); scf_iter_destroy(iter); scf_pg_destroy(pg); scf_snaplevel_destroy(slvl); scf_snapshot_destroy(snap); scf_instance_destroy(inst); scf_handle_destroy(h); return (buf); } /* * get_fma_notify_params() * populates an nvlist_t with notifycation parameters for a given FMA class * returns 0 if the nvlist is populated, 1 otherwise; */ int get_fma_notify_params(nvlist_t *nvl, const char *class) { if (_scf_get_fma_notify_params(class, nvl, 0) != 0) { /* * if the preferences have just been deleted * or does not exist, just skip. */ if (scf_error() != SCF_ERROR_NOT_FOUND && scf_error() != SCF_ERROR_DELETED) uu_warn(gettext( "Failed get_fma_notify_params %s\n"), scf_strerror(scf_error())); return (1); } return (0); } /* * print_notify_fma() * outputs the notification paramets of FMA events. * It first outputs classes in fma_tags[], then outputs the other classes * sorted alphabetically */ static void print_notify_fma(void) { nvlist_t *nvl; char **tmp = NULL; char **classes, *p; const char *class; uint32_t i; if (nvlist_alloc(&nvl, NV_UNIQUE_NAME, 0) != 0) uu_die(gettext("Out of memory.\n")); for (i = 0; (class = get_fma_class(i)) != NULL; ++i) { if (get_fma_notify_params(nvl, class) == 0) listnotify_print(nvl, get_fma_tag(i)); } if ((classes = get_notify_param_classes()) == NULL) goto cleanup; tmp = classes; for (p = *tmp; p; ++tmp, p = *tmp) { if (get_fma_notify_params(nvl, p) == 0) listnotify_print(nvl, re_tag(p)); free(p); } free(classes); cleanup: nvlist_free(nvl); } /* * print_notify_fmri() * prints notifycation parameters for an SMF instance. */ static void print_notify_fmri(const char *fmri) { nvlist_t *nvl; if (nvlist_alloc(&nvl, NV_UNIQUE_NAME, 0) != 0) uu_die(gettext("Out of memory.\n")); if (_scf_get_svc_notify_params(fmri, nvl, SCF_TRANSITION_ALL, 0, 0) != SCF_SUCCESS) { if (scf_error() != SCF_ERROR_NOT_FOUND && scf_error() != SCF_ERROR_DELETED) uu_warn(gettext( "Failed _scf_get_svc_notify_params: %s\n"), scf_strerror(scf_error())); } else { if (strcmp(SCF_INSTANCE_GLOBAL, fmri) == 0) safe_printf( gettext("System wide notification parameters:\n")); safe_printf("%s:\n", fmri); listnotify_print(nvl, NULL); } nvlist_free(nvl); } /* * print_notify_special() * prints notification parameters for FMA events and system wide SMF state * transitions parameters */ static void print_notify_special() { safe_printf("Notification parameters for FMA Events\n"); print_notify_fma(); print_notify_fmri(SCF_INSTANCE_GLOBAL); } /* * print_notify() * callback function to print notification parameters for SMF state transition * instances. It skips global and notify-params instances as they should be * printed by print_notify_special() */ /* ARGSUSED */ static int print_notify(void *unused, scf_walkinfo_t *wip) { if (strcmp(SCF_INSTANCE_GLOBAL, wip->fmri) == 0 || strcmp(SCF_NOTIFY_PARAMS_INST, wip->fmri) == 0) return (0); print_notify_fmri(wip->fmri); return (0); } /* * Append a one-lined description of each process in inst's contract(s) and * return the augmented string. */ static char * add_processes(scf_walkinfo_t *wip, char *line, scf_propertygroup_t *lpg) { pid_t *pids = NULL; uint_t i, n = 0; if (lpg == NULL) { if (instance_processes(wip->inst, wip->fmri, &pids, &n) != 0) return (line); } else { /* Legacy services */ scf_iter_t *iter; if ((iter = scf_iter_create(h)) == NULL) scfdie(); (void) propvals_to_pids(lpg, scf_property_contract, &pids, &n, g_prop, g_val, iter); scf_iter_destroy(iter); } if (n == 0) return (line); qsort(pids, n, sizeof (*pids), pidcmp); for (i = 0; i < n; ++i) { char *cp, stime[9]; psinfo_t psi; const char *name = NULL; int len = 1 + 15 + 8 + 3 + 6 + 1 + PRFNSZ; if (proc_get_psinfo(pids[i], &psi) != 0) continue; line = realloc(line, strlen(line) + len); if (line == NULL) uu_die(gettext("Out of memory.\n")); cp = strchr(line, '\0'); if (!IS_ZOMBIE(&psi)) { #define DAY (24 * 60 * 60) #define YEAR (12 * 30 * 24 * 60 * 60) struct tm *tm; tm = localtime(&psi.pr_start.tv_sec); /* * Print time if started within the past 24 hours, * print date if within the past 12 months, print year * if started greater than 12 months ago. */ if (now - psi.pr_start.tv_sec < DAY) { (void) strftime(stime, sizeof (stime), gettext(FORMAT_TIME), tm); } else if (now - psi.pr_start.tv_sec < YEAR) { (void) strftime(stime, sizeof (stime), gettext(FORMAT_DATE), tm); } else { (void) strftime(stime, sizeof (stime), gettext(FORMAT_YEAR), tm); } name = psi.pr_fname; #undef DAY #undef YEAR } else { (void) snprintf(stime, sizeof (stime), "-"); name = ""; } (void) snprintf(cp, len, "\n %-8s %6ld %.*s", stime, pids[i], PRFNSZ, name); } free(pids); return (line); } /*ARGSUSED*/ static int list_instance(void *unused, scf_walkinfo_t *wip) { struct avl_string *lp; char *cp; int i; uu_avl_index_t idx; /* * If the user has specified a restarter, check for a match first */ if (restarters != NULL) { struct pfmri_list *rest; int match; char *restarter_fmri; const char *scope_name, *svc_name, *inst_name, *pg_name; /* legacy services don't have restarters */ if (wip->pg != NULL) return (0); restarter_fmri = safe_malloc(max_scf_fmri_length + 1); if (inst_get_single_val(wip->inst, SCF_PG_GENERAL, SCF_PROPERTY_RESTARTER, SCF_TYPE_ASTRING, restarter_fmri, max_scf_fmri_length + 1, 0, 0, 1) != 0) (void) strcpy(restarter_fmri, SCF_SERVICE_STARTD); if (scf_parse_svc_fmri(restarter_fmri, &scope_name, &svc_name, &inst_name, &pg_name, NULL) != SCF_SUCCESS) { free(restarter_fmri); return (0); } match = 0; for (rest = restarters; rest != NULL; rest = rest->next) { if (strcmp(rest->scope, scope_name) == 0 && strcmp(rest->service, svc_name) == 0 && strcmp(rest->instance, inst_name) == 0) match = 1; } free(restarter_fmri); if (!match) return (0); } if (wip->pg == NULL && ht_buckets != NULL && ht_add(wip->fmri)) { /* It was already there. */ return (0); } lp = safe_malloc(sizeof (*lp)); lp->str = NULL; for (i = 0; i < opt_cnum; ++i) { columns[opt_columns[i]].sprint(&lp->str, wip); } cp = lp->str + strlen(lp->str); cp--; while (*cp == ' ') cp--; *(cp+1) = '\0'; /* If we're supposed to list the processes, too, do that now. */ if (opt_processes) lp->str = add_processes(wip, lp->str, wip->pg); /* Create the sort key. */ cp = lp->key = safe_malloc(sortkey_sz); for (i = 0; i < opt_snum; ++i) { int j = opt_sort[i] & 0xff; assert(columns[j].get_sortkey != NULL); columns[j].get_sortkey(cp, opt_sort[i] & ~0xff, wip); cp += columns[j].sortkey_width; } /* Insert into AVL tree. */ uu_avl_node_init(lp, &lp->node, lines_pool); (void) uu_avl_find(lines, lp, NULL, &idx); uu_avl_insert(lines, lp, idx); return (0); } static int list_if_enabled(void *unused, scf_walkinfo_t *wip) { if (wip->pg != NULL || instance_enabled(wip->inst, B_FALSE) == 1 || instance_enabled(wip->inst, B_TRUE) == 1) return (list_instance(unused, wip)); return (0); } /* * Service FMRI selection: Lookup and call list_instance() for the instances. * Instance FMRI selection: Lookup and call list_instance(). * * Note: This is shoehorned into a walk_dependencies() callback prototype so * it can be used in list_dependencies. */ static int list_svc_or_inst_fmri(void *complain, scf_walkinfo_t *wip) { char *fmri; const char *svc_name, *inst_name, *pg_name, *save; scf_iter_t *iter; int ret; fmri = safe_strdup(wip->fmri); if (scf_parse_svc_fmri(fmri, NULL, &svc_name, &inst_name, &pg_name, NULL) != SCF_SUCCESS) { if (complain) uu_warn(gettext("FMRI \"%s\" is invalid.\n"), wip->fmri); exit_status = UU_EXIT_FATAL; free(fmri); return (0); } /* * Yes, this invalidates *_name, but we only care whether they're NULL * or not. */ free(fmri); if (svc_name == NULL || pg_name != NULL) { if (complain) uu_warn(gettext("FMRI \"%s\" does not designate a " "service or instance.\n"), wip->fmri); return (0); } if (inst_name != NULL) { /* instance */ if (scf_handle_decode_fmri(h, wip->fmri, wip->scope, wip->svc, wip->inst, NULL, NULL, 0) != SCF_SUCCESS) { if (scf_error() != SCF_ERROR_NOT_FOUND) scfdie(); if (complain) uu_warn(gettext( "Instance \"%s\" does not exist.\n"), wip->fmri); return (0); } return (list_instance(NULL, wip)); } /* service: Walk the instances. */ if (scf_handle_decode_fmri(h, wip->fmri, wip->scope, wip->svc, NULL, NULL, NULL, 0) != SCF_SUCCESS) { if (scf_error() != SCF_ERROR_NOT_FOUND) scfdie(); if (complain) uu_warn(gettext("Service \"%s\" does not exist.\n"), wip->fmri); exit_status = UU_EXIT_FATAL; return (0); } iter = scf_iter_create(h); if (iter == NULL) scfdie(); if (scf_iter_service_instances(iter, wip->svc) != SCF_SUCCESS) scfdie(); if ((fmri = malloc(max_scf_fmri_length + 1)) == NULL) { scf_iter_destroy(iter); exit_status = UU_EXIT_FATAL; return (0); } save = wip->fmri; wip->fmri = fmri; while ((ret = scf_iter_next_instance(iter, wip->inst)) == 1) { if (scf_instance_to_fmri(wip->inst, fmri, max_scf_fmri_length + 1) <= 0) scfdie(); (void) list_instance(NULL, wip); } free(fmri); wip->fmri = save; if (ret == -1) scfdie(); exit_status = UU_EXIT_OK; scf_iter_destroy(iter); return (0); } /* * Dependency selection: Straightforward since each instance lists the * services it depends on. */ static void walk_dependencies(scf_walkinfo_t *wip, scf_walk_callback callback, void *data) { scf_snapshot_t *snap; scf_iter_t *iter, *viter; int ret, vret; char *dep; assert(wip->inst != NULL); if ((iter = scf_iter_create(h)) == NULL || (viter = scf_iter_create(h)) == NULL) scfdie(); snap = get_running_snapshot(wip->inst); if (scf_iter_instance_pgs_typed_composed(iter, wip->inst, snap, SCF_GROUP_DEPENDENCY) != SCF_SUCCESS) scfdie(); dep = safe_malloc(max_scf_value_length + 1); while ((ret = scf_iter_next_pg(iter, g_pg)) == 1) { scf_type_t ty; /* Ignore exclude_any dependencies. */ if (scf_pg_get_property(g_pg, SCF_PROPERTY_GROUPING, g_prop) != SCF_SUCCESS) { if (scf_error() != SCF_ERROR_NOT_FOUND) scfdie(); continue; } if (scf_property_type(g_prop, &ty) != SCF_SUCCESS) scfdie(); if (ty != SCF_TYPE_ASTRING) continue; if (scf_property_get_value(g_prop, g_val) != SCF_SUCCESS) { if (scf_error() != SCF_ERROR_CONSTRAINT_VIOLATED) scfdie(); continue; } if (scf_value_get_astring(g_val, dep, max_scf_value_length + 1) < 0) scfdie(); if (strcmp(dep, SCF_DEP_EXCLUDE_ALL) == 0) continue; if (scf_pg_get_property(g_pg, SCF_PROPERTY_ENTITIES, g_prop) != SCF_SUCCESS) { if (scf_error() != SCF_ERROR_NOT_FOUND) scfdie(); continue; } if (scf_iter_property_values(viter, g_prop) != SCF_SUCCESS) scfdie(); while ((vret = scf_iter_next_value(viter, g_val)) == 1) { if (scf_value_get_astring(g_val, dep, max_scf_value_length + 1) < 0) scfdie(); wip->fmri = dep; if (callback(data, wip) != 0) goto out; } if (vret == -1) scfdie(); } if (ret == -1) scfdie(); out: scf_iter_destroy(viter); scf_iter_destroy(iter); scf_snapshot_destroy(snap); } static int list_dependencies(void *data, scf_walkinfo_t *wip) { walk_dependencies(wip, list_svc_or_inst_fmri, data); return (0); } /* * Dependent selection: The "providing" service's or instance's FMRI is parsed * into the provider_* variables, the instances are walked, and any instance * which lists an FMRI which parses to these components is selected. This is * inefficient in the face of multiple operands, but that should be uncommon. */ static char *provider_scope; static char *provider_svc; static char *provider_inst; /* NULL for services */ /*ARGSUSED*/ static int check_against_provider(void *arg, scf_walkinfo_t *wip) { char *cfmri; const char *scope_name, *svc_name, *inst_name, *pg_name; int *matchp = arg; cfmri = safe_strdup(wip->fmri); if (scf_parse_svc_fmri(cfmri, &scope_name, &svc_name, &inst_name, &pg_name, NULL) != SCF_SUCCESS) { free(cfmri); return (0); } if (svc_name == NULL || pg_name != NULL) { free(cfmri); return (0); } /* * If the user has specified an instance, then also match dependencies * on the service itself. */ *matchp = (strcmp(provider_scope, scope_name) == 0 && strcmp(provider_svc, svc_name) == 0 && (provider_inst == NULL ? (inst_name == NULL) : (inst_name == NULL || strcmp(provider_inst, inst_name) == 0))); free(cfmri); /* Stop on matches. */ return (*matchp); } static int list_if_dependent(void *unused, scf_walkinfo_t *wip) { /* Only proceed if this instance depends on provider_*. */ int match = 0; (void) walk_dependencies(wip, check_against_provider, &match); if (match) return (list_instance(unused, wip)); return (0); } /*ARGSUSED*/ static int list_dependents(void *unused, scf_walkinfo_t *wip) { char *save; int ret; if (scf_scope_get_name(wip->scope, provider_scope, max_scf_fmri_length) <= 0 || scf_service_get_name(wip->svc, provider_svc, max_scf_fmri_length) <= 0) scfdie(); save = provider_inst; if (wip->inst == NULL) provider_inst = NULL; else if (scf_instance_get_name(wip->inst, provider_inst, max_scf_fmri_length) <= 0) scfdie(); ret = scf_walk_fmri(h, 0, NULL, 0, list_if_dependent, NULL, NULL, uu_warn); provider_inst = save; return (ret); } /* * main() & helpers */ static void add_sort_column(const char *col, int reverse) { int i; ++opt_snum; opt_sort = realloc(opt_sort, opt_snum * sizeof (*opt_sort)); if (opt_sort == NULL) uu_die(gettext("Too many sort criteria: out of memory.\n")); for (i = 0; i < ncolumns; ++i) { if (strcasecmp(col, columns[i].name) == 0) break; } if (i < ncolumns) opt_sort[opt_snum - 1] = (reverse ? i | 0x100 : i); else uu_die(gettext("Unrecognized sort column \"%s\".\n"), col); sortkey_sz += columns[i].sortkey_width; } static void add_restarter(const char *fmri) { char *cfmri; const char *pg_name; struct pfmri_list *rest; cfmri = safe_strdup(fmri); rest = safe_malloc(sizeof (*rest)); if (scf_parse_svc_fmri(cfmri, &rest->scope, &rest->service, &rest->instance, &pg_name, NULL) != SCF_SUCCESS) uu_die(gettext("Restarter FMRI \"%s\" is invalid.\n"), fmri); if (rest->instance == NULL || pg_name != NULL) uu_die(gettext("Restarter FMRI \"%s\" does not designate an " "instance.\n"), fmri); rest->next = restarters; restarters = rest; return; err: free(cfmri); free(rest); } /* ARGSUSED */ static int line_cmp(const void *l_arg, const void *r_arg, void *private) { const struct avl_string *l = l_arg; const struct avl_string *r = r_arg; return (memcmp(l->key, r->key, sortkey_sz)); } /* ARGSUSED */ static int print_line(void *e, void *private) { struct avl_string *lp = e; (void) puts(lp->str); return (UU_WALK_NEXT); } /* ARGSUSED */ static void errignore(const char *str, ...) {} int main(int argc, char **argv) { char opt, opt_mode; int i, n; char *columns_str = NULL; char *cp; const char *progname; int err, missing = 1, ignored, *errarg; uint_t nzents = 0, zent = 0; zoneid_t *zids = NULL; char zonename[ZONENAME_MAX]; void (*errfunc)(const char *, ...); int show_all = 0; int show_header = 1; int show_zones = 0; const char * const options = "aHpvno:R:s:S:dDlL?xZz:"; (void) setlocale(LC_ALL, ""); locale = setlocale(LC_MESSAGES, NULL); if (locale) { locale = safe_strdup(locale); _scf_sanitize_locale(locale); } (void) textdomain(TEXT_DOMAIN); progname = uu_setpname(argv[0]); exit_status = UU_EXIT_OK; max_scf_name_length = scf_limit(SCF_LIMIT_MAX_NAME_LENGTH); max_scf_value_length = scf_limit(SCF_LIMIT_MAX_VALUE_LENGTH); max_scf_fmri_length = scf_limit(SCF_LIMIT_MAX_FMRI_LENGTH); max_scf_type_length = scf_limit(SCF_LIMIT_MAX_PG_TYPE_LENGTH); if (max_scf_name_length == -1 || max_scf_value_length == -1 || max_scf_fmri_length == -1 || max_scf_type_length == -1) scfdie(); now = time(NULL); assert(now != -1); /* * opt_mode is the mode of operation. 0 for plain, 'd' for * dependencies, 'D' for dependents, and 'l' for detailed (long). We * need to know now so we know which options are valid. */ opt_mode = 0; while ((opt = getopt(argc, argv, options)) != -1) { switch (opt) { case '?': if (optopt == '?') { print_help(progname); return (UU_EXIT_OK); } else { argserr(progname); /* NOTREACHED */ } case 'd': case 'D': case 'l': case 'L': if (opt_mode != 0) argserr(progname); opt_mode = opt; break; case 'n': if (opt_mode != 0) argserr(progname); opt_mode = opt; break; case 'x': if (opt_mode != 0) argserr(progname); opt_mode = opt; break; default: break; } } sortkey_sz = 0; optind = 1; /* Reset getopt() */ while ((opt = getopt(argc, argv, options)) != -1) { switch (opt) { case 'a': if (opt_mode != 0) argserr(progname); show_all = 1; break; case 'H': if (opt_mode == 'l' || opt_mode == 'x') argserr(progname); show_header = 0; break; case 'p': if (opt_mode == 'x') argserr(progname); opt_processes = 1; break; case 'v': opt_verbose = 1; break; case 'o': if (opt_mode == 'l' || opt_mode == 'x') argserr(progname); columns_str = optarg; break; case 'R': if (opt_mode != 0 || opt_mode == 'x') argserr(progname); add_restarter(optarg); break; case 's': case 'S': if (opt_mode != 0) argserr(progname); add_sort_column(optarg, optopt == 'S'); break; case 'd': case 'D': case 'l': case 'L': case 'n': case 'x': assert(opt_mode == optopt); break; case 'z': if (getzoneid() != GLOBAL_ZONEID) uu_die(gettext("svcs -z may only be used from " "the global zone\n")); if (show_zones) argserr(progname); opt_zone = optarg; break; case 'Z': if (getzoneid() != GLOBAL_ZONEID) uu_die(gettext("svcs -Z may only be used from " "the global zone\n")); if (opt_zone != NULL) argserr(progname); show_zones = 1; break; case '?': argserr(progname); default: assert(0); abort(); } } /* * -a is only meaningful when given no arguments */ if (show_all && optind != argc) uu_warn(gettext("-a ignored when used with arguments.\n")); while (show_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); } argc -= optind; argv += optind; again: h = scf_handle_create(SCF_VERSION); if (h == NULL) scfdie(); if (opt_zone != NULL || zids != NULL) { scf_value_t *zone; assert(opt_zone == NULL || zids == NULL); if (opt_zone == NULL) { 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; } else { g_zonename = opt_zone; } if ((zone = scf_value_create(h)) == NULL) scfdie(); if (scf_value_set_astring(zone, g_zonename) != SCF_SUCCESS) scfdie(); if (scf_handle_decorate(h, "zone", zone) != SCF_SUCCESS) uu_die(gettext("invalid zone '%s'\n"), g_zonename); scf_value_destroy(zone); } if (scf_handle_bind(h) == -1) { if (g_zonename != NULL) { uu_warn(gettext("Could not bind to repository " "server for zone %s: %s\n"), g_zonename, scf_strerror(scf_error())); if (!show_zones) return (UU_EXIT_FATAL); goto nextzone; } uu_die(gettext("Could not bind to repository server: %s. " "Exiting.\n"), scf_strerror(scf_error())); } if ((g_pg = scf_pg_create(h)) == NULL || (g_prop = scf_property_create(h)) == NULL || (g_val = scf_value_create(h)) == NULL) scfdie(); if (show_zones) { /* * It's hard to avoid editorializing here, but suffice it to * say that scf_walk_fmri() takes an error handler, the * interface to which has been regrettably misdesigned: the * handler itself takes exclusively a string -- even though * scf_walk_fmri() has detailed, programmatic knowledge * of the error condition at the time it calls its errfunc. * That is, only the error message and not the error semantics * are given to the handler. This is poor interface at best, * but it is particularly problematic when we are talking to * multiple repository servers (as when we are iterating over * all zones) as we do not want to treat failure to find a * match in one zone as overall failure. Ideally, we would * simply ignore SCF_MSG_PATTERN_NOINSTANCE and correctly * process the others, but alas, no such interface exists -- * and we must settle for instead ignoring all errfunc-called * errors in the case that we are iterating over all zones... */ errfunc = errignore; errarg = missing ? &missing : &ignored; missing = 0; } else { errfunc = uu_warn; errarg = &exit_status; } /* * If we're in long mode, take care of it now before we deal with the * sorting and the columns, since we won't use them anyway. */ if (opt_mode == 'l') { if (argc == 0) argserr(progname); if ((err = scf_walk_fmri(h, argc, argv, SCF_WALK_MULTIPLE, print_detailed, NULL, errarg, errfunc)) != 0) { uu_warn(gettext("failed to iterate over " "instances: %s\n"), scf_strerror(err)); exit_status = UU_EXIT_FATAL; } goto nextzone; } if (opt_mode == 'L') { if ((err = scf_walk_fmri(h, argc, argv, SCF_WALK_MULTIPLE, print_log, NULL, &exit_status, uu_warn)) != 0) { uu_warn(gettext("failed to iterate over " "instances: %s\n"), scf_strerror(err)); exit_status = UU_EXIT_FATAL; } goto nextzone; } if (opt_mode == 'n') { print_notify_special(); if ((err = scf_walk_fmri(h, argc, argv, SCF_WALK_MULTIPLE, print_notify, NULL, errarg, errfunc)) != 0) { uu_warn(gettext("failed to iterate over " "instances: %s\n"), scf_strerror(err)); exit_status = UU_EXIT_FATAL; } goto nextzone; } if (opt_mode == 'x') { explain(opt_verbose, argc, argv); goto nextzone; } if (columns_str == NULL) { if (opt_snum == 0) { if (show_zones) add_sort_column("zone", 0); /* Default sort. */ add_sort_column("state", 0); add_sort_column("stime", 0); add_sort_column("fmri", 0); } if (!opt_verbose) { columns_str = safe_strdup(show_zones ? "zone,state,stime,fmri" : "state,stime,fmri"); } else { columns_str = safe_strdup(show_zones ? "zone,state,nstate,stime,ctid,fmri" : "state,nstate,stime,ctid,fmri"); } } if (opt_columns == NULL) { /* Decode columns_str into opt_columns. */ line_sz = 0; opt_cnum = 1; for (cp = columns_str; *cp != '\0'; ++cp) if (*cp == ',') ++opt_cnum; if (*columns_str == '\0') uu_die(gettext("No columns specified.\n")); opt_columns = malloc(opt_cnum * sizeof (*opt_columns)); if (opt_columns == NULL) uu_die(gettext("Too many columns.\n")); for (n = 0; *columns_str != '\0'; ++n) { i = getcolumnopt(&columns_str); if (i == -1) uu_die(gettext("Unknown column \"%s\".\n"), columns_str); if (strcmp(columns[i].name, "N") == 0 || strcmp(columns[i].name, "SN") == 0 || strcmp(columns[i].name, "NSTA") == 0 || strcmp(columns[i].name, "NSTATE") == 0) opt_nstate_shown = 1; opt_columns[n] = i; line_sz += columns[i].width + 1; } if ((lines_pool = uu_avl_pool_create("lines_pool", sizeof (struct avl_string), offsetof(struct avl_string, node), line_cmp, UU_AVL_DEBUG)) == NULL || (lines = uu_avl_create(lines_pool, NULL, 0)) == NULL) uu_die(gettext("Unexpected libuutil error: %s\n"), uu_strerror(uu_error())); } switch (opt_mode) { case 0: /* * If we already have a hash table (e.g., because we are * processing multiple zones), destroy it before creating * a new one. */ if (ht_buckets != NULL) ht_free(); ht_init(); /* Always show all FMRIs when given arguments or restarters */ if (argc != 0 || restarters != NULL) show_all = 1; if ((err = scf_walk_fmri(h, argc, argv, SCF_WALK_MULTIPLE | SCF_WALK_LEGACY, show_all ? list_instance : list_if_enabled, NULL, errarg, errfunc)) != 0) { uu_warn(gettext("failed to iterate over " "instances: %s\n"), scf_strerror(err)); exit_status = UU_EXIT_FATAL; } break; case 'd': if (argc == 0) argserr(progname); if ((err = scf_walk_fmri(h, argc, argv, SCF_WALK_MULTIPLE, list_dependencies, NULL, errarg, errfunc)) != 0) { uu_warn(gettext("failed to iterate over " "instances: %s\n"), scf_strerror(err)); exit_status = UU_EXIT_FATAL; } break; case 'D': if (argc == 0) argserr(progname); provider_scope = safe_malloc(max_scf_fmri_length); provider_svc = safe_malloc(max_scf_fmri_length); provider_inst = safe_malloc(max_scf_fmri_length); if ((err = scf_walk_fmri(h, argc, argv, SCF_WALK_MULTIPLE | SCF_WALK_SERVICE, list_dependents, NULL, &exit_status, uu_warn)) != 0) { uu_warn(gettext("failed to iterate over " "instances: %s\n"), scf_strerror(err)); exit_status = UU_EXIT_FATAL; } free(provider_scope); free(provider_svc); free(provider_inst); break; case 'n': break; default: assert(0); abort(); } nextzone: if (show_zones && zent < nzents && exit_status == 0) { scf_handle_destroy(h); goto again; } if (show_zones && exit_status == 0) exit_status = missing; if (opt_columns == NULL) return (exit_status); if (show_header) print_header(); (void) uu_avl_walk(lines, print_line, NULL, 0); return (exit_status); }