/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright 2007 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #pragma ident "%Z%%M% %I% %E% SMI" /* * 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" /* 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 #ifndef TEXT_DOMAIN #define TEXT_DOMAIN "SUNW_OST_OSCMD" #endif /* TEXT_DOMAIN */ #define LEGACY_SCHEME "lrc:" #define LEGACY_UNKNOWN "unknown" /* Flags for pg_get_single_val() */ #define EMPTY_OK 0x01 #define MULTI_OK 0x02 /* * 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 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. */ /* * 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; /* 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); } static void sanitize_locale(char *locale) { for (; *locale != '\0'; locale++) if (!isalnum(*locale)) *locale = '_'; } /* * FMRI hashtable. For uniquifing listings. */ struct ht_elem { const char *fmri; struct ht_elem *next; }; static struct ht_elem **ht_buckets; static uint_t ht_buckets_num; static uint_t ht_num; static void ht_init() { 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; 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) > 0 ? SCF_SUCCESS : -1; 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: 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, NULL, 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); } static int get_psinfo(pid_t pid, psinfo_t *psip) { char path[100]; int fd; (void) snprintf(path, sizeof (path), "/proc/%lu/psinfo", pid); fd = open64(path, O_RDONLY); if (fd < 0) return (-1); if (read(fd, psip, sizeof (*psip)) < 0) uu_die(gettext("Could not read info for process %lu"), pid); (void) close(fd); return (0); } /* * 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 static void sprint_ctid(char **buf, scf_walkinfo_t *wip) { int r; uint64_t c; size_t newsize = (*buf ? strlen(*buf) : 0) + CTID_COLUMN_WIDTH + 2; 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, LEGACY_SCHEME); if (pg_get_single_val(wip->pg, SCF_LEGACY_PROPERTY_NAME, SCF_TYPE_ASTRING, fmri_buf + sizeof (LEGACY_SCHEME) - 1, max_scf_fmri_length + 1 - (sizeof (LEGACY_SCHEME) - 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 " 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, NULL, 0); } if (r != 0) { (void) snprintf(newbuf, newsize, "%s%-*s", *buf ? *buf : "", STIME_COLUMN_WIDTH + 1, "?"); return; } 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, 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, NULL, 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); } /* * 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 }, }; #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(4))"); 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; } assert(s != NULL); return (s); } static void print_usage(const char *progname, FILE *f, boolean_t do_exit) { (void) fprintf(f, gettext( "Usage: %1$s [-aHpv] [-o col[,col ... ]] [-R restarter] " "[-sS col] [ ...]\n" " %1$s -d | -D [-Hpv] [-o col[,col ... ]] [-sS col] " "[ ...]\n" " %1$s -l ...\n" " %1$s -x [-v] [ ...]\n" " %1$s -?\n"), progname); if (do_exit) exit(UU_EXIT_USAGE); } #define argserr(progname) print_usage(progname, stderr, B_TRUE) static void print_help(const char *progname) { int i; print_usage(progname, stdout, B_FALSE); (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-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" "\n\t" "Services can be specified using an FMRI, abbreviation, or fnmatch(5)\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 (get_psinfo(pids[i], &psi) == 0) (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_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); /* * 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")); } /* * 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 * he suspects 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, NULL, 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_snapshot_destroy(snap); scf_iter_destroy(pg_iter); if (opt_processes) detailed_list_processes(wip); 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; struct tm *tm; int len = 1 + 15 + 8 + 3 + 6 + 1 + PRFNSZ; if (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'); 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 < 24 * 60 * 60) (void) strftime(stime, sizeof (stime), gettext(FORMAT_TIME), tm); else if (now - psi.pr_start.tv_sec < 12 * 30 * 24 * 60 * 60) (void) strftime(stime, sizeof (stime), gettext(FORMAT_DATE), tm); else (void) strftime(stime, sizeof (stime), gettext(FORMAT_YEAR), tm); (void) snprintf(cp, len, "\n %-8s %6ld %.*s", stime, pids[i], PRFNSZ, psi.pr_fname); } 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); } int main(int argc, char **argv) { char opt, opt_mode; int i, n; char *columns_str = NULL; char *cp; const char *progname; int err; int show_all = 0; int show_header = 1; const char * const options = "aHpvo:R:s:S:dDl?x"; (void) setlocale(LC_ALL, ""); locale = setlocale(LC_MESSAGES, ""); if (locale) { locale = safe_strdup(locale); 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); if (max_scf_name_length == -1 || max_scf_value_length == -1 || max_scf_fmri_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': 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': if (opt_mode == 'l') argserr(progname); 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 'x': assert(opt_mode == optopt); break; case '?': argserr(progname); /* NOTREACHED */ 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")); h = scf_handle_create(SCF_VERSION); if (h == NULL) scfdie(); if (scf_handle_bind(h) == -1) 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(); argc -= optind; argv += optind; /* * 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, &exit_status, uu_warn)) != 0) { uu_warn(gettext("failed to iterate over " "instances: %s\n"), scf_strerror(err)); exit_status = UU_EXIT_FATAL; } return (exit_status); } if (opt_mode == 'x') { explain(opt_verbose, argc, argv); return (exit_status); } if (opt_snum == 0) { /* Default sort. */ add_sort_column("state", 0); add_sort_column("stime", 0); add_sort_column("fmri", 0); } if (columns_str == NULL) { if (!opt_verbose) columns_str = safe_strdup("state,stime,fmri"); else columns_str = safe_strdup("state,nstate,stime,ctid,fmri"); } /* Decode columns_str into opt_columns. */ line_sz = 0; opt_cnum = 1; for (cp = columns_str; *cp != '\0'; ++cp) if (*cp == ',') ++opt_cnum; 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. Exiting.\n"), uu_strerror(uu_error())); switch (opt_mode) { case 0: 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, &exit_status, uu_warn)) != 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, &exit_status, uu_warn)) != 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; default: assert(0); abort(); } if (show_header) print_header(); (void) uu_avl_walk(lines, print_line, NULL, 0); return (exit_status); }