svcs.c revision 7c478bd95313f5f23a4c958a745db2134aa03244
1/*
2 * CDDL HEADER START
3 *
4 * The contents of this file are subject to the terms of the
5 * Common Development and Distribution License, Version 1.0 only
6 * (the "License").  You may not use this file except in compliance
7 * with the License.
8 *
9 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
10 * or http://www.opensolaris.org/os/licensing.
11 * See the License for the specific language governing permissions
12 * and limitations under the License.
13 *
14 * When distributing Covered Code, include this CDDL HEADER in each
15 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
16 * If applicable, add the following below this CDDL HEADER, with the
17 * fields enclosed by brackets "[]" replaced with your own identifying
18 * information: Portions Copyright [yyyy] [name of copyright owner]
19 *
20 * CDDL HEADER END
21 *
22 * Copyright 2005 Sun Microsystems, Inc.  All rights reserved.
23 * Use is subject to license terms.
24 */
25
26#pragma ident	"%Z%%M%	%I%	%E% SMI"
27
28/*
29 * svcs - display attributes of service instances
30 *
31 * We have two output formats and six instance selection mechanisms.  The
32 * primary output format is a line of attributes (selected by -o), possibly
33 * followed by process description lines (if -p is specified), for each
34 * instance selected.  The columns available to display are described by the
35 * struct column columns array.  The columns to actually display are kept in
36 * the opt_columns array as indicies into the columns array.  The selection
37 * mechanisms available for this format are service FMRIs (selects all child
38 * instances), instance FMRIs, instance FMRI glob patterns, instances with
39 * a certain restarter (-R), dependencies of instances (-d), and dependents of
40 * instances (-D).  Since the lines must be sorted (per -sS), we'll just stick
41 * each into a data structure and print them in order when we're done.  To
42 * avoid listing the same instance twice (when -d and -D aren't given), we'll
43 * use a hash table of FMRIs to record that we've listed (added to the tree)
44 * an instance.
45 *
46 * The secondary output format (-l "long") is a paragraph of text for the
47 * services or instances selected.  Not needing to be sorted, it's implemented
48 * by just calling print_detailed() for each FMRI given.
49 */
50
51#include "svcs.h"
52
53/* Get the byteorder macros to ease sorting. */
54#include <sys/types.h>
55#include <netinet/in.h>
56#include <inttypes.h>
57
58#include <sys/contract.h>
59#include <sys/ctfs.h>
60#include <sys/stat.h>
61
62#include <assert.h>
63#include <ctype.h>
64#include <errno.h>
65#include <fcntl.h>
66#include <fnmatch.h>
67#include <libcontract.h>
68#include <libcontract_priv.h>
69#include <libintl.h>
70#include <libscf.h>
71#include <libscf_priv.h>
72#include <libuutil.h>
73#include <locale.h>
74#include <procfs.h>
75#include <stdarg.h>
76#include <stdio.h>
77#include <stdlib.h>
78#include <strings.h>
79#include <time.h>
80
81
82#ifndef TEXT_DOMAIN
83#define	TEXT_DOMAIN	"SUNW_OST_OSCMD"
84#endif /* TEXT_DOMAIN */
85
86#define	LEGACY_SCHEME	"lrc:"
87#define	LEGACY_UNKNOWN	"unknown"
88
89/* Flags for pg_get_single_val() */
90#define	EMPTY_OK	0x01
91#define	MULTI_OK	0x02
92
93
94/*
95 * An AVL-storable node for output lines and the keys to sort them by.
96 */
97struct avl_string {
98	uu_avl_node_t	node;
99	char		*key;
100	char		*str;
101};
102
103/*
104 * For lists of parsed restarter FMRIs.
105 */
106struct pfmri_list {
107	const char		*scope;
108	const char		*service;
109	const char		*instance;
110	struct pfmri_list	*next;
111};
112
113
114/*
115 * Globals
116 */
117scf_handle_t *h;
118static scf_propertygroup_t *g_pg;
119static scf_property_t *g_prop;
120static scf_value_t *g_val;
121
122static size_t line_sz;			/* Bytes in the header line. */
123static size_t sortkey_sz;		/* Bytes in sort keys. */
124static uu_avl_pool_t *lines_pool;
125static uu_avl_t *lines;			/* Output lines. */
126int exit_status;
127ssize_t max_scf_name_length;
128ssize_t max_scf_value_length;
129ssize_t max_scf_fmri_length;
130static time_t now;
131static struct pfmri_list *restarters = NULL;
132static int first_paragraph = 1;		/* For -l mode. */
133static char *common_name_buf;		/* Sized for maximal length value. */
134char *locale;				/* Current locale. */
135
136/* Options */
137static int *opt_columns = NULL;		/* Indices into columns to display. */
138static int opt_cnum = 0;
139static int opt_processes = 0;		/* Print processes? */
140static int *opt_sort = NULL;		/* Indices into columns to sort. */
141static int opt_snum = 0;
142static int opt_nstate_shown = 0;	/* Will nstate be shown? */
143static int opt_verbose = 0;
144
145/* Minimize string constants. */
146static const char * const scf_property_state = SCF_PROPERTY_STATE;
147static const char * const scf_property_next_state = SCF_PROPERTY_NEXT_STATE;
148static const char * const scf_property_contract = SCF_PROPERTY_CONTRACT;
149
150
151/*
152 * Utility functions
153 */
154
155/*
156 * For unexpected libscf errors.  The ending newline is necessary to keep
157 * uu_die() from appending the errno error.
158 */
159#ifndef NDEBUG
160void
161do_scfdie(const char *file, int line)
162{
163	uu_die(gettext("%s:%d: Unexpected libscf error: %s.  Exiting.\n"),
164	    file, line, scf_strerror(scf_error()));
165}
166#else
167void
168scfdie(void)
169{
170	uu_die(gettext("Unexpected libscf error: %s.  Exiting.\n"),
171	    scf_strerror(scf_error()));
172}
173#endif
174
175void *
176safe_malloc(size_t sz)
177{
178	void *ptr;
179
180	ptr = malloc(sz);
181	if (ptr == NULL)
182		uu_die(gettext("Out of memory"));
183
184	return (ptr);
185}
186
187char *
188safe_strdup(const char *str)
189{
190	char *cp;
191
192	cp = strdup(str);
193	if (cp == NULL)
194		uu_die(gettext("Out of memory.\n"));
195
196	return (cp);
197}
198
199static void
200sanitize_locale(char *locale)
201{
202	for (; *locale != '\0'; locale++)
203		if (!isalnum(*locale))
204			*locale = '_';
205}
206
207/*
208 * FMRI hashtable.  For uniquifing listings.
209 */
210
211struct ht_elem {
212	const char	*fmri;
213	struct ht_elem	*next;
214};
215
216static struct ht_elem	**ht_buckets;
217static uint_t		ht_buckets_num;
218static uint_t		ht_num;
219
220static void
221ht_init()
222{
223	ht_buckets_num = 8;
224	ht_buckets = safe_malloc(sizeof (*ht_buckets) * ht_buckets_num);
225	bzero(ht_buckets, sizeof (*ht_buckets) * ht_buckets_num);
226	ht_num = 0;
227}
228
229static uint_t
230ht_hash_fmri(const char *fmri)
231{
232	uint_t h = 0, g;
233	const char *p, *k;
234
235	/* All FMRIs begin with svc:/, so skip that part. */
236	assert(strncmp(fmri, "svc:/", sizeof ("svc:/") - 1) == 0);
237	k = fmri + sizeof ("svc:/") - 1;
238
239	/*
240	 * Generic hash function from uts/common/os/modhash.c.
241	 */
242	for (p = k; *p != '\0'; ++p) {
243		h = (h << 4) + *p;
244		if ((g = (h & 0xf0000000)) != 0) {
245			h ^= (g >> 24);
246			h ^= g;
247		}
248	}
249
250	return (h);
251}
252
253static void
254ht_grow()
255{
256	uint_t new_ht_buckets_num;
257	struct ht_elem **new_ht_buckets;
258	int i;
259
260	new_ht_buckets_num = ht_buckets_num * 2;
261	assert(new_ht_buckets_num > ht_buckets_num);
262	new_ht_buckets =
263	    safe_malloc(sizeof (*new_ht_buckets) * new_ht_buckets_num);
264	bzero(new_ht_buckets, sizeof (*new_ht_buckets) * new_ht_buckets_num);
265
266	for (i = 0; i < ht_buckets_num; ++i) {
267		struct ht_elem *elem, *next;
268
269		for (elem = ht_buckets[i]; elem != NULL; elem = next) {
270			uint_t h;
271
272			next = elem->next;
273
274			h = ht_hash_fmri(elem->fmri);
275
276			elem->next =
277			    new_ht_buckets[h & (new_ht_buckets_num - 1)];
278			new_ht_buckets[h & (new_ht_buckets_num - 1)] = elem;
279		}
280	}
281
282	free(ht_buckets);
283
284	ht_buckets = new_ht_buckets;
285	ht_buckets_num = new_ht_buckets_num;
286}
287
288/*
289 * Add an FMRI to the hash table.  Returns 1 if it was already there,
290 * 0 otherwise.
291 */
292static int
293ht_add(const char *fmri)
294{
295	uint_t h;
296	struct ht_elem *elem;
297
298	h = ht_hash_fmri(fmri);
299
300	elem = ht_buckets[h & (ht_buckets_num - 1)];
301
302	for (; elem != NULL; elem = elem->next) {
303		if (strcmp(elem->fmri, fmri) == 0)
304			return (1);
305	}
306
307	/* Grow when average chain length is over 3. */
308	if (ht_num > 3 * ht_buckets_num)
309		ht_grow();
310
311	++ht_num;
312
313	elem = safe_malloc(sizeof (*elem));
314	elem->fmri = strdup(fmri);
315	elem->next = ht_buckets[h & (ht_buckets_num - 1)];
316	ht_buckets[h & (ht_buckets_num - 1)] = elem;
317
318	return (0);
319}
320
321
322
323/*
324 * Convenience libscf wrapper functions.
325 */
326
327/*
328 * Get the single value of the named property in the given property group,
329 * which must have type ty, and put it in *vp.  If ty is SCF_TYPE_ASTRING, vp
330 * is taken to be a char **, and sz is the size of the buffer.  sz is unused
331 * otherwise.  Return 0 on success, -1 if the property doesn't exist, has the
332 * wrong type, or doesn't have a single value.  If flags has EMPTY_OK, don't
333 * complain if the property has no values (but return nonzero).  If flags has
334 * MULTI_OK and the property has multiple values, succeed with E2BIG.
335 */
336int
337pg_get_single_val(scf_propertygroup_t *pg, const char *propname, scf_type_t ty,
338    void *vp, size_t sz, uint_t flags)
339{
340	char *buf;
341	size_t buf_sz;
342	int ret = -1, r;
343	boolean_t multi = B_FALSE;
344
345	assert((flags & ~(EMPTY_OK | MULTI_OK)) == 0);
346
347	if (scf_pg_get_property(pg, propname, g_prop) == -1) {
348		if (scf_error() != SCF_ERROR_NOT_FOUND)
349			scfdie();
350
351		goto out;
352	}
353
354	if (scf_property_is_type(g_prop, ty) != SCF_SUCCESS) {
355		if (scf_error() == SCF_ERROR_TYPE_MISMATCH)
356			goto misconfigured;
357		scfdie();
358	}
359
360	if (scf_property_get_value(g_prop, g_val) != SCF_SUCCESS) {
361		switch (scf_error()) {
362		case SCF_ERROR_NOT_FOUND:
363			if (flags & EMPTY_OK)
364				goto out;
365			goto misconfigured;
366
367		case SCF_ERROR_CONSTRAINT_VIOLATED:
368			if (flags & MULTI_OK) {
369				multi = B_TRUE;
370				break;
371			}
372			goto misconfigured;
373
374		default:
375			scfdie();
376		}
377	}
378
379	switch (ty) {
380	case SCF_TYPE_ASTRING:
381		r = scf_value_get_astring(g_val, vp, sz) > 0 ? SCF_SUCCESS : -1;
382		break;
383
384	case SCF_TYPE_BOOLEAN:
385		r = scf_value_get_boolean(g_val, (uint8_t *)vp);
386		break;
387
388	case SCF_TYPE_COUNT:
389		r = scf_value_get_count(g_val, (uint64_t *)vp);
390		break;
391
392	case SCF_TYPE_INTEGER:
393		r = scf_value_get_integer(g_val, (int64_t *)vp);
394		break;
395
396	case SCF_TYPE_TIME: {
397		int64_t sec;
398		int32_t ns;
399		r = scf_value_get_time(g_val, &sec, &ns);
400		((struct timeval *)vp)->tv_sec = sec;
401		((struct timeval *)vp)->tv_usec = ns / 1000;
402		break;
403	}
404
405	case SCF_TYPE_USTRING:
406		r = scf_value_get_ustring(g_val, vp, sz) > 0 ? SCF_SUCCESS : -1;
407		break;
408
409	default:
410#ifndef NDEBUG
411		uu_warn("%s:%d: Unknown type %d.\n", __FILE__, __LINE__, ty);
412#endif
413		abort();
414	}
415	if (r != SCF_SUCCESS)
416		scfdie();
417
418	ret = multi ? E2BIG : 0;
419	goto out;
420
421misconfigured:
422	buf_sz = max_scf_fmri_length + 1;
423	buf = safe_malloc(buf_sz);
424	if (scf_property_to_fmri(g_prop, buf, buf_sz) == -1)
425		scfdie();
426
427	uu_warn(gettext("Property \"%s\" is misconfigured.\n"), buf);
428
429	free(buf);
430
431out:
432	return (ret);
433}
434
435static scf_snapshot_t *
436get_running_snapshot(scf_instance_t *inst)
437{
438	scf_snapshot_t *snap;
439
440	snap = scf_snapshot_create(h);
441	if (snap == NULL)
442		scfdie();
443
444	if (scf_instance_get_snapshot(inst, "running", snap) == 0)
445		return (snap);
446
447	if (scf_error() != SCF_ERROR_NOT_FOUND)
448		scfdie();
449
450	scf_snapshot_destroy(snap);
451	return (NULL);
452}
453
454/*
455 * As pg_get_single_val(), except look the property group up in an
456 * instance.  If "use_running" is set, and the running snapshot exists,
457 * do a composed lookup there.  Otherwise, do an (optionally composed)
458 * lookup on the current values.  Note that lookups using snapshots are
459 * always composed.
460 */
461int
462inst_get_single_val(scf_instance_t *inst, const char *pgname,
463    const char *propname, scf_type_t ty, void *vp, size_t sz, uint_t flags,
464    int use_running, int composed)
465{
466	scf_snapshot_t *snap = NULL;
467	int r;
468
469	if (use_running)
470		snap = get_running_snapshot(inst);
471	if (composed || use_running)
472		r = scf_instance_get_pg_composed(inst, snap, pgname, g_pg);
473	else
474		r = scf_instance_get_pg(inst, pgname, g_pg);
475	if (snap)
476		scf_snapshot_destroy(snap);
477	if (r == -1)
478		return (-1);
479
480	r = pg_get_single_val(g_pg, propname, ty, vp, sz, flags);
481
482	return (r);
483}
484
485static int
486instance_enabled(scf_instance_t *inst, boolean_t temp)
487{
488	uint8_t b;
489
490	if (inst_get_single_val(inst,
491	    temp ? SCF_PG_GENERAL_OVR : SCF_PG_GENERAL, SCF_PROPERTY_ENABLED,
492	    SCF_TYPE_BOOLEAN, &b, 0, 0, 0, 0) != 0)
493		return (-1);
494
495	return (b ? 1 : 0);
496}
497
498/*
499 * Get a string property from the restarter property group of the given
500 * instance.  Return an empty string on normal problems.
501 */
502static void
503get_restarter_string_prop(scf_instance_t *inst, const char *pname,
504    char *buf, size_t buf_sz)
505{
506	if (inst_get_single_val(inst, SCF_PG_RESTARTER, pname,
507	    SCF_TYPE_ASTRING, buf, buf_sz, 0, 0, 1) != 0)
508		*buf = '\0';
509}
510
511static int
512get_restarter_time_prop(scf_instance_t *inst, const char *pname,
513    struct timeval *tvp, int ok_if_empty)
514{
515	int r;
516
517	r = inst_get_single_val(inst, SCF_PG_RESTARTER, pname, SCF_TYPE_TIME,
518	    tvp, NULL, ok_if_empty ? EMPTY_OK : 0, 0, 1);
519
520	return (r == 0 ? 0 : -1);
521}
522
523static int
524get_restarter_count_prop(scf_instance_t *inst, const char *pname, uint64_t *cp,
525    uint_t flags)
526{
527	return (inst_get_single_val(inst, SCF_PG_RESTARTER, pname,
528	    SCF_TYPE_COUNT, cp, 0, flags, 0, 1));
529}
530
531
532/*
533 * Generic functions
534 */
535
536static int
537propvals_to_pids(scf_propertygroup_t *pg, const char *pname, pid_t **pidsp,
538    uint_t *np, scf_property_t *prop, scf_value_t *val, scf_iter_t *iter)
539{
540	scf_type_t ty;
541	int r, fd, err;
542	uint64_t c;
543	ct_stathdl_t ctst;
544	pid_t *pids;
545	uint_t m;
546
547	if (scf_pg_get_property(pg, pname, prop) != 0) {
548		if (scf_error() != SCF_ERROR_NOT_FOUND)
549			scfdie();
550
551		return (ENOENT);
552	}
553
554	if (scf_property_type(prop, &ty) != 0)
555		scfdie();
556
557	if (ty != SCF_TYPE_COUNT)
558		return (EINVAL);
559
560	if (scf_iter_property_values(iter, prop) != 0)
561		scfdie();
562
563	for (;;) {
564		r = scf_iter_next_value(iter, val);
565		if (r == -1)
566			scfdie();
567		if (r == 0)
568			break;
569
570		if (scf_value_get_count(val, &c) != 0)
571			scfdie();
572
573		fd = contract_open(c, NULL, "status", O_RDONLY);
574		if (fd < 0)
575			continue;
576
577		err = ct_status_read(fd, CTD_ALL, &ctst);
578		if (err != 0) {
579			uu_warn(gettext("Could not read status of contract "
580			    "%ld: %s.\n"), c, strerror(err));
581			(void) close(fd);
582			continue;
583		}
584
585		(void) close(fd);
586
587		r = ct_pr_status_get_members(ctst, &pids, &m);
588		assert(r == 0);
589
590		if (m == 0) {
591			ct_status_free(ctst);
592			continue;
593		}
594
595		*pidsp = realloc(*pidsp, (*np + m) * sizeof (*pidsp));
596		if (*pidsp == NULL)
597			uu_die(gettext("Out of memory"));
598
599		bcopy(pids, *pidsp + *np, m * sizeof (*pids));
600		*np += m;
601
602		ct_status_free(ctst);
603	}
604
605	return (0);
606}
607
608static int
609instance_processes(scf_instance_t *inst, pid_t **pids, uint_t *np)
610{
611	scf_iter_t *iter;
612	int ret;
613
614	if ((iter = scf_iter_create(h)) == NULL)
615		scfdie();
616
617	if (scf_instance_get_pg(inst, SCF_PG_RESTARTER, g_pg) == 0) {
618		*pids = NULL;
619		*np = 0;
620
621		(void) propvals_to_pids(g_pg, scf_property_contract, pids, np,
622		    g_prop, g_val, iter);
623
624		(void) propvals_to_pids(g_pg, SCF_PROPERTY_TRANSIENT_CONTRACT,
625		    pids, np, g_prop, g_val, iter);
626
627		ret = 0;
628	} else {
629		if (scf_error() != SCF_ERROR_NOT_FOUND)
630			scfdie();
631
632		ret = -1;
633	}
634
635	scf_iter_destroy(iter);
636
637	return (ret);
638}
639
640static int
641get_psinfo(pid_t pid, psinfo_t *psip)
642{
643	char path[100];
644	int fd;
645
646	(void) snprintf(path, sizeof (path), "/proc/%lu/psinfo", pid);
647
648	fd = open64(path, O_RDONLY);
649	if (fd < 0)
650		return (-1);
651
652	if (read(fd, psip, sizeof (*psip)) < 0)
653		uu_die(gettext("Could not read info for process %lu"), pid);
654
655	(void) close(fd);
656
657	return (0);
658}
659
660
661
662/*
663 * Column sprint and sortkey functions
664 */
665
666struct column {
667	const char *name;
668	int width;
669
670	/*
671	 * This function should write the value for the column into buf, and
672	 * grow or allocate buf accordingly.  It should always write at least
673	 * width bytes, blanking unused bytes with spaces.  If the field is
674	 * greater than the column width we allow it to overlap other columns.
675	 * In particular, it shouldn't write any null bytes.  (Though an extra
676	 * null byte past the end is currently tolerated.)  If the property
677	 * group is non-NULL, then we are dealing with a legacy service.
678	 */
679	void (*sprint)(char **, scf_walkinfo_t *);
680
681	int sortkey_width;
682
683	/*
684	 * This function should write sortkey_width bytes into buf which will
685	 * cause memcmp() to sort it properly.  (Unlike sprint() above,
686	 * however, an extra null byte may overrun the buffer.)  The second
687	 * argument controls whether the results are sorted in forward or
688	 * reverse order.
689	 */
690	void (*get_sortkey)(char *, int, scf_walkinfo_t *);
691};
692
693static void
694reverse_bytes(char *buf, size_t len)
695{
696	int i;
697
698	for (i = 0; i < len; ++i)
699		buf[i] = ~buf[i];
700}
701
702/* CTID */
703#define	CTID_COLUMN_WIDTH		6
704
705static void
706sprint_ctid(char **buf, scf_walkinfo_t *wip)
707{
708	int r;
709	uint64_t c;
710	size_t newsize = (*buf ? strlen(*buf) : 0) + CTID_COLUMN_WIDTH + 2;
711	char *newbuf = safe_malloc(newsize);
712
713	if (wip->pg != NULL)
714		r = pg_get_single_val(wip->pg, scf_property_contract,
715		    SCF_TYPE_COUNT, &c, 0, EMPTY_OK | MULTI_OK);
716	else
717		r = get_restarter_count_prop(wip->inst, scf_property_contract,
718		    &c, EMPTY_OK | MULTI_OK);
719
720	if (r == 0)
721		(void) snprintf(newbuf, newsize, "%s%*lu ",
722		    *buf ? *buf : "", CTID_COLUMN_WIDTH, (ctid_t)c);
723	else if (r == E2BIG)
724		(void) snprintf(newbuf, newsize, "%s%*lu* ",
725		    *buf ? *buf : "", CTID_COLUMN_WIDTH - 1, (ctid_t)c);
726	else
727		(void) snprintf(newbuf, newsize, "%s%*s ",
728		    *buf ? *buf : "", CTID_COLUMN_WIDTH, "-");
729	if (*buf)
730		free(*buf);
731	*buf = newbuf;
732}
733
734#define	CTID_SORTKEY_WIDTH		(sizeof (uint64_t))
735
736static void
737sortkey_ctid(char *buf, int reverse, scf_walkinfo_t *wip)
738{
739	int r;
740	uint64_t c;
741
742	if (wip->pg != NULL)
743		r = pg_get_single_val(wip->pg, scf_property_contract,
744		    SCF_TYPE_COUNT, &c, 0, EMPTY_OK);
745	else
746		r = get_restarter_count_prop(wip->inst, scf_property_contract,
747		    &c, EMPTY_OK);
748
749	if (r == 0) {
750		/*
751		 * Use the id itself, but it must be big-endian for this to
752		 * work.
753		 */
754		c = BE_64(c);
755
756		bcopy(&c, buf, CTID_SORTKEY_WIDTH);
757	} else {
758		bzero(buf, CTID_SORTKEY_WIDTH);
759	}
760
761	if (reverse)
762		reverse_bytes(buf, CTID_SORTKEY_WIDTH);
763}
764
765/* DESC */
766#define	DESC_COLUMN_WIDTH	100
767
768static void
769sprint_desc(char **buf, scf_walkinfo_t *wip)
770{
771	char *x;
772	size_t newsize;
773	char *newbuf;
774
775	if (common_name_buf == NULL)
776		common_name_buf = safe_malloc(max_scf_value_length + 1);
777
778	bzero(common_name_buf, max_scf_value_length + 1);
779
780	if (wip->pg != NULL) {
781		common_name_buf[0] = '-';
782	} else if (inst_get_single_val(wip->inst, SCF_PG_TM_COMMON_NAME, locale,
783	    SCF_TYPE_USTRING, common_name_buf, max_scf_value_length, 0,
784	    1, 1) == -1 &&
785	    inst_get_single_val(wip->inst, SCF_PG_TM_COMMON_NAME, "C",
786	    SCF_TYPE_USTRING, common_name_buf, max_scf_value_length, 0,
787	    1, 1) == -1) {
788		common_name_buf[0] = '-';
789	}
790
791	/*
792	 * Collapse multi-line tm_common_name values into a single line.
793	 */
794	for (x = common_name_buf; *x != '\0'; x++)
795		if (*x == '\n')
796			*x = ' ';
797
798	if (strlen(common_name_buf) > DESC_COLUMN_WIDTH)
799		newsize = (*buf ? strlen(*buf) : 0) +
800		    strlen(common_name_buf) + 1;
801	else
802		newsize = (*buf ? strlen(*buf) : 0) + DESC_COLUMN_WIDTH + 1;
803	newbuf = safe_malloc(newsize);
804	(void) snprintf(newbuf, newsize, "%s%-*s ", *buf ? *buf : "",
805			DESC_COLUMN_WIDTH, common_name_buf);
806	if (*buf)
807		free(*buf);
808	*buf = newbuf;
809}
810
811/* ARGSUSED */
812static void
813sortkey_desc(char *buf, int reverse, scf_walkinfo_t *wip)
814{
815	bzero(buf, DESC_COLUMN_WIDTH);
816}
817
818/* State columns (STATE, NSTATE, S, N, SN, STA, NSTA) */
819
820static char
821state_to_char(const char *state)
822{
823	if (strcmp(state, SCF_STATE_STRING_UNINIT) == 0)
824		return ('u');
825
826	if (strcmp(state, SCF_STATE_STRING_OFFLINE) == 0)
827		return ('0');
828
829	if (strcmp(state, SCF_STATE_STRING_ONLINE) == 0)
830		return ('1');
831
832	if (strcmp(state, SCF_STATE_STRING_MAINT) == 0)
833		return ('m');
834
835	if (strcmp(state, SCF_STATE_STRING_DISABLED) == 0)
836		return ('d');
837
838	if (strcmp(state, SCF_STATE_STRING_DEGRADED) == 0)
839		return ('D');
840
841	if (strcmp(state, SCF_STATE_STRING_LEGACY) == 0)
842		return ('L');
843
844	return ('?');
845}
846
847/* Return true if inst is transitioning. */
848static int
849transitioning(scf_instance_t *inst)
850{
851	char nstate_name[MAX_SCF_STATE_STRING_SZ];
852
853	get_restarter_string_prop(inst, scf_property_next_state, nstate_name,
854	    sizeof (nstate_name));
855
856	return (state_to_char(nstate_name) != '?');
857}
858
859/* ARGSUSED */
860static void
861sortkey_states(const char *pname, char *buf, int reverse, scf_walkinfo_t *wip)
862{
863	char state_name[MAX_SCF_STATE_STRING_SZ];
864
865	/*
866	 * Lower numbers are printed first, so these are arranged from least
867	 * interesting ("legacy run") to most interesting (unknown).
868	 */
869	if (wip->pg == NULL) {
870		get_restarter_string_prop(wip->inst, pname, state_name,
871		    sizeof (state_name));
872
873		if (strcmp(state_name, SCF_STATE_STRING_ONLINE) == 0)
874			*buf = 2;
875		else if (strcmp(state_name, SCF_STATE_STRING_DEGRADED) == 0)
876			*buf = 3;
877		else if (strcmp(state_name, SCF_STATE_STRING_OFFLINE) == 0)
878			*buf = 4;
879		else if (strcmp(state_name, SCF_STATE_STRING_MAINT) == 0)
880			*buf = 5;
881		else if (strcmp(state_name, SCF_STATE_STRING_DISABLED) == 0)
882			*buf = 1;
883		else if (strcmp(state_name, SCF_STATE_STRING_UNINIT) == 0)
884			*buf = 6;
885		else
886			*buf = 7;
887	} else
888		*buf = 0;
889
890	if (reverse)
891		*buf = 255 - *buf;
892}
893
894static void
895sprint_state(char **buf, scf_walkinfo_t *wip)
896{
897	char state_name[MAX_SCF_STATE_STRING_SZ + 1];
898	size_t newsize;
899	char *newbuf;
900
901	if (wip->pg == NULL) {
902		get_restarter_string_prop(wip->inst, scf_property_state,
903		    state_name, sizeof (state_name));
904
905		/* Don't print blank fields, to ease parsing. */
906		if (state_name[0] == '\0') {
907			state_name[0] = '-';
908			state_name[1] = '\0';
909		}
910
911		if (!opt_nstate_shown && transitioning(wip->inst)) {
912			/* Append an asterisk if nstate is valid. */
913			(void) strcat(state_name, "*");
914		}
915	} else
916		(void) strcpy(state_name, SCF_STATE_STRING_LEGACY);
917
918	newsize = (*buf ? strlen(*buf) : 0) + MAX_SCF_STATE_STRING_SZ + 2;
919	newbuf = safe_malloc(newsize);
920	(void) snprintf(newbuf, newsize, "%s%-*s ", *buf ? *buf : "",
921	    MAX_SCF_STATE_STRING_SZ + 1, state_name);
922
923	if (*buf)
924		free(*buf);
925	*buf = newbuf;
926}
927
928static void
929sortkey_state(char *buf, int reverse, scf_walkinfo_t *wip)
930{
931	sortkey_states(scf_property_state, buf, reverse, wip);
932}
933
934static void
935sprint_nstate(char **buf, scf_walkinfo_t *wip)
936{
937	char next_state_name[MAX_SCF_STATE_STRING_SZ];
938	boolean_t blank = 0;
939	size_t newsize;
940	char *newbuf;
941
942	if (wip->pg == NULL) {
943		get_restarter_string_prop(wip->inst, scf_property_next_state,
944		    next_state_name, sizeof (next_state_name));
945
946		/* Don't print blank fields, to ease parsing. */
947		if (next_state_name[0] == '\0' ||
948		    strcmp(next_state_name, SCF_STATE_STRING_NONE) == 0)
949			blank = 1;
950	} else
951		blank = 1;
952
953	if (blank) {
954		next_state_name[0] = '-';
955		next_state_name[1] = '\0';
956	}
957
958	newsize = (*buf ? strlen(*buf) : 0) + MAX_SCF_STATE_STRING_SZ + 1;
959	newbuf = safe_malloc(newsize);
960	(void) snprintf(newbuf, newsize, "%s%-*s ", *buf ? *buf : "",
961	    MAX_SCF_STATE_STRING_SZ - 1, next_state_name);
962	if (*buf)
963		free(*buf);
964	*buf = newbuf;
965}
966
967static void
968sortkey_nstate(char *buf, int reverse, scf_walkinfo_t *wip)
969{
970	sortkey_states(scf_property_next_state, buf, reverse, wip);
971}
972
973static void
974sprint_s(char **buf, scf_walkinfo_t *wip)
975{
976	char tmp[3];
977	char state_name[MAX_SCF_STATE_STRING_SZ];
978	size_t newsize = (*buf ? strlen(*buf) : 0) + 4;
979	char *newbuf = safe_malloc(newsize);
980
981	if (wip->pg == NULL) {
982		get_restarter_string_prop(wip->inst, scf_property_state,
983		    state_name, sizeof (state_name));
984		tmp[0] = state_to_char(state_name);
985
986		if (!opt_nstate_shown && transitioning(wip->inst))
987			tmp[1] = '*';
988		else
989			tmp[1] = ' ';
990	} else {
991		tmp[0] = 'L';
992		tmp[1] = ' ';
993	}
994	tmp[2] = ' ';
995	(void) snprintf(newbuf, newsize, "%s%-*s", *buf ? *buf : "",
996	    3, tmp);
997	if (*buf)
998		free(*buf);
999	*buf = newbuf;
1000}
1001
1002static void
1003sprint_n(char **buf, scf_walkinfo_t *wip)
1004{
1005	char tmp[2];
1006	size_t newsize = (*buf ? strlen(*buf) : 0) + 3;
1007	char *newbuf = safe_malloc(newsize);
1008	char nstate_name[MAX_SCF_STATE_STRING_SZ];
1009
1010	if (wip->pg == NULL) {
1011		get_restarter_string_prop(wip->inst, scf_property_next_state,
1012		    nstate_name, sizeof (nstate_name));
1013
1014		if (strcmp(nstate_name, SCF_STATE_STRING_NONE) == 0)
1015			tmp[0] = '-';
1016		else
1017			tmp[0] = state_to_char(nstate_name);
1018	} else
1019		tmp[0] = '-';
1020
1021	(void) snprintf(newbuf, newsize, "%s%-*s ", *buf ? *buf : "",
1022	    2, tmp);
1023	if (*buf)
1024		free(*buf);
1025	*buf = newbuf;
1026}
1027
1028static void
1029sprint_sn(char **buf, scf_walkinfo_t *wip)
1030{
1031	char tmp[3];
1032	size_t newsize = (*buf ? strlen(*buf) : 0) + 4;
1033	char *newbuf = safe_malloc(newsize);
1034	char nstate_name[MAX_SCF_STATE_STRING_SZ];
1035	char state_name[MAX_SCF_STATE_STRING_SZ];
1036
1037	if (wip->pg == NULL) {
1038		get_restarter_string_prop(wip->inst, scf_property_state,
1039		    state_name, sizeof (state_name));
1040		get_restarter_string_prop(wip->inst, scf_property_next_state,
1041		    nstate_name, sizeof (nstate_name));
1042		tmp[0] = state_to_char(state_name);
1043
1044		if (strcmp(nstate_name, SCF_STATE_STRING_NONE) == 0)
1045			tmp[1] = '-';
1046		else
1047			tmp[1] = state_to_char(nstate_name);
1048	} else {
1049		tmp[0] = 'L';
1050		tmp[1] = '-';
1051	}
1052
1053	tmp[2] = ' ';
1054	(void) snprintf(newbuf, newsize, "%s%-*s ", *buf ? *buf : "",
1055	    3, tmp);
1056	if (*buf)
1057		free(*buf);
1058	*buf = newbuf;
1059}
1060
1061/* ARGSUSED */
1062static void
1063sortkey_sn(char *buf, int reverse, scf_walkinfo_t *wip)
1064{
1065	sortkey_state(buf, reverse, wip);
1066	sortkey_nstate(buf + 1, reverse, wip);
1067}
1068
1069static const char *
1070state_abbrev(const char *state)
1071{
1072	if (strcmp(state, SCF_STATE_STRING_UNINIT) == 0)
1073		return ("UN");
1074	if (strcmp(state, SCF_STATE_STRING_OFFLINE) == 0)
1075		return ("OFF");
1076	if (strcmp(state, SCF_STATE_STRING_ONLINE) == 0)
1077		return ("ON");
1078	if (strcmp(state, SCF_STATE_STRING_MAINT) == 0)
1079		return ("MNT");
1080	if (strcmp(state, SCF_STATE_STRING_DISABLED) == 0)
1081		return ("DIS");
1082	if (strcmp(state, SCF_STATE_STRING_DEGRADED) == 0)
1083		return ("DGD");
1084	if (strcmp(state, SCF_STATE_STRING_LEGACY) == 0)
1085		return ("LRC");
1086
1087	return ("?");
1088}
1089
1090static void
1091sprint_sta(char **buf, scf_walkinfo_t *wip)
1092{
1093	char state_name[MAX_SCF_STATE_STRING_SZ];
1094	char sta[5];
1095	size_t newsize = (*buf ? strlen(*buf) : 0) + 6;
1096	char *newbuf = safe_malloc(newsize);
1097
1098	if (wip->pg == NULL)
1099		get_restarter_string_prop(wip->inst, scf_property_state,
1100		    state_name, sizeof (state_name));
1101	else
1102		(void) strcpy(state_name, SCF_STATE_STRING_LEGACY);
1103
1104	(void) strcpy(sta, state_abbrev(state_name));
1105
1106	if (wip->pg == NULL && !opt_nstate_shown && transitioning(wip->inst))
1107		(void) strcat(sta, "*");
1108
1109	(void) snprintf(newbuf, newsize, "%s%-4s ", *buf ? *buf : "", sta);
1110	if (*buf)
1111		free(*buf);
1112	*buf = newbuf;
1113}
1114
1115static void
1116sprint_nsta(char **buf, scf_walkinfo_t *wip)
1117{
1118	char state_name[MAX_SCF_STATE_STRING_SZ];
1119	size_t newsize = (*buf ? strlen(*buf) : 0) + 6;
1120	char *newbuf = safe_malloc(newsize);
1121
1122	if (wip->pg == NULL)
1123		get_restarter_string_prop(wip->inst, scf_property_next_state,
1124		    state_name, sizeof (state_name));
1125	else
1126		(void) strcpy(state_name, SCF_STATE_STRING_NONE);
1127
1128	if (strcmp(state_name, SCF_STATE_STRING_NONE) == 0)
1129		(void) snprintf(newbuf, newsize, "%s%-4s ", *buf ? *buf : "",
1130		    "-");
1131	else
1132		(void) snprintf(newbuf, newsize, "%s%-4s ", *buf ? *buf : "",
1133		    state_abbrev(state_name));
1134	if (*buf)
1135		free(*buf);
1136	*buf = newbuf;
1137}
1138
1139/* FMRI */
1140#define	FMRI_COLUMN_WIDTH	50
1141static void
1142sprint_fmri(char **buf, scf_walkinfo_t *wip)
1143{
1144	char *fmri_buf = safe_malloc(max_scf_fmri_length + 1);
1145	size_t newsize;
1146	char *newbuf;
1147
1148	if (wip->pg == NULL) {
1149		if (scf_instance_to_fmri(wip->inst, fmri_buf,
1150		    max_scf_fmri_length + 1) == -1)
1151			scfdie();
1152	} else {
1153		(void) strcpy(fmri_buf, LEGACY_SCHEME);
1154		if (pg_get_single_val(wip->pg, SCF_LEGACY_PROPERTY_NAME,
1155		    SCF_TYPE_ASTRING, fmri_buf + sizeof (LEGACY_SCHEME) - 1,
1156		    max_scf_fmri_length + 1 - (sizeof (LEGACY_SCHEME) - 1),
1157		    0) != 0)
1158			(void) strcat(fmri_buf, LEGACY_UNKNOWN);
1159	}
1160
1161	if (strlen(fmri_buf) > FMRI_COLUMN_WIDTH)
1162		newsize = (*buf ? strlen(*buf) : 0) + strlen(fmri_buf) + 2;
1163	else
1164		newsize = (*buf ? strlen(*buf) : 0) + FMRI_COLUMN_WIDTH + 2;
1165	newbuf = safe_malloc(newsize);
1166	(void) snprintf(newbuf, newsize, "%s%-*s ", *buf ? *buf : "",
1167	    FMRI_COLUMN_WIDTH, fmri_buf);
1168	free(fmri_buf);
1169	if (*buf)
1170		free(*buf);
1171	*buf = newbuf;
1172}
1173
1174static void
1175sortkey_fmri(char *buf, int reverse, scf_walkinfo_t *wip)
1176{
1177	char *tmp = NULL;
1178
1179	sprint_fmri(&tmp, wip);
1180	bcopy(tmp, buf, FMRI_COLUMN_WIDTH);
1181	free(tmp);
1182	if (reverse)
1183		reverse_bytes(buf, FMRI_COLUMN_WIDTH);
1184}
1185
1186/* Component columns */
1187#define	COMPONENT_COLUMN_WIDTH	20
1188static void
1189sprint_scope(char **buf, scf_walkinfo_t *wip)
1190{
1191	char *scope_buf = safe_malloc(max_scf_name_length + 1);
1192	size_t newsize = (*buf ? strlen(*buf) : 0) + COMPONENT_COLUMN_WIDTH + 2;
1193	char *newbuf = safe_malloc(newsize);
1194
1195	assert(wip->scope != NULL);
1196
1197	if (scf_scope_get_name(wip->scope, scope_buf, max_scf_name_length) < 0)
1198		scfdie();
1199
1200	(void) snprintf(newbuf, newsize, "%s%-*s ", *buf ? *buf : "",
1201	    COMPONENT_COLUMN_WIDTH, scope_buf);
1202	if (*buf)
1203		free(*buf);
1204	*buf = newbuf;
1205	free(scope_buf);
1206}
1207
1208static void
1209sortkey_scope(char *buf, int reverse, scf_walkinfo_t *wip)
1210{
1211	char *tmp = NULL;
1212
1213	sprint_scope(&tmp, wip);
1214	bcopy(tmp, buf, COMPONENT_COLUMN_WIDTH);
1215	free(tmp);
1216	if (reverse)
1217		reverse_bytes(buf, COMPONENT_COLUMN_WIDTH);
1218}
1219
1220static void
1221sprint_service(char **buf, scf_walkinfo_t *wip)
1222{
1223	char *svc_buf = safe_malloc(max_scf_name_length + 1);
1224	char *newbuf;
1225	size_t newsize;
1226
1227	if (wip->pg == NULL) {
1228		if (scf_service_get_name(wip->svc, svc_buf,
1229		    max_scf_name_length + 1) < 0)
1230			scfdie();
1231	} else {
1232		if (pg_get_single_val(wip->pg, "name", SCF_TYPE_ASTRING,
1233		    svc_buf, max_scf_name_length + 1, EMPTY_OK) != 0)
1234			(void) strcpy(svc_buf, LEGACY_UNKNOWN);
1235	}
1236
1237
1238	if (strlen(svc_buf) > COMPONENT_COLUMN_WIDTH)
1239		newsize = (*buf ? strlen(*buf) : 0) + strlen(svc_buf) + 2;
1240	else
1241		newsize = (*buf ? strlen(*buf) : 0) +
1242		    COMPONENT_COLUMN_WIDTH + 2;
1243	newbuf = safe_malloc(newsize);
1244	(void) snprintf(newbuf, newsize, "%s%-*s ", *buf ? *buf : "",
1245	    COMPONENT_COLUMN_WIDTH, svc_buf);
1246	free(svc_buf);
1247	if (*buf)
1248		free(*buf);
1249	*buf = newbuf;
1250}
1251
1252static void
1253sortkey_service(char *buf, int reverse, scf_walkinfo_t *wip)
1254{
1255	char *tmp = NULL;
1256
1257	sprint_service(&tmp, wip);
1258	bcopy(tmp, buf, COMPONENT_COLUMN_WIDTH);
1259	free(tmp);
1260	if (reverse)
1261		reverse_bytes(buf, COMPONENT_COLUMN_WIDTH);
1262}
1263
1264/* INST */
1265static void
1266sprint_instance(char **buf, scf_walkinfo_t *wip)
1267{
1268	char *tmp = safe_malloc(max_scf_name_length + 1);
1269	size_t newsize = (*buf ? strlen(*buf) : 0) + COMPONENT_COLUMN_WIDTH + 2;
1270	char *newbuf = safe_malloc(newsize);
1271
1272	if (wip->pg == NULL) {
1273		if (scf_instance_get_name(wip->inst, tmp,
1274		    max_scf_name_length + 1) < 0)
1275			scfdie();
1276	} else {
1277		tmp[0] = '-';
1278		tmp[1] = '\0';
1279	}
1280
1281	(void) snprintf(newbuf, newsize, "%s%-*s ", *buf ? *buf : "",
1282	    COMPONENT_COLUMN_WIDTH, tmp);
1283	if (*buf)
1284		free(*buf);
1285	*buf = newbuf;
1286	free(tmp);
1287}
1288
1289static void
1290sortkey_instance(char *buf, int reverse, scf_walkinfo_t *wip)
1291{
1292	char *tmp = NULL;
1293
1294	sprint_instance(&tmp, wip);
1295	bcopy(tmp, buf, COMPONENT_COLUMN_WIDTH);
1296	free(tmp);
1297	if (reverse)
1298		reverse_bytes(buf, COMPONENT_COLUMN_WIDTH);
1299}
1300
1301/* STIME */
1302#define	STIME_COLUMN_WIDTH		8
1303#define	FORMAT_TIME			"%k:%M:%S"
1304#define	FORMAT_DATE			"%b_%d  "
1305#define	FORMAT_YEAR			"%Y    "
1306
1307static void
1308sprint_stime(char **buf, scf_walkinfo_t *wip)
1309{
1310	int r;
1311	struct timeval tv;
1312	time_t then;
1313	struct tm *tm;
1314	char st_buf[STIME_COLUMN_WIDTH + 1];
1315	size_t newsize = (*buf ? strlen(*buf) : 0) + STIME_COLUMN_WIDTH + 2;
1316	char *newbuf = safe_malloc(newsize);
1317
1318	if (wip->pg == NULL) {
1319		r = get_restarter_time_prop(wip->inst,
1320		    SCF_PROPERTY_STATE_TIMESTAMP, &tv, 0);
1321	} else {
1322		r = pg_get_single_val(wip->pg, SCF_PROPERTY_STATE_TIMESTAMP,
1323		    SCF_TYPE_TIME, &tv, NULL, 0);
1324	}
1325
1326	if (r != 0) {
1327		(void) snprintf(newbuf, newsize, "%s%-*s", *buf ? *buf : "",
1328		    STIME_COLUMN_WIDTH + 1, "?");
1329		return;
1330	}
1331
1332	then = (time_t)tv.tv_sec;
1333
1334	tm = localtime(&then);
1335	/*
1336	 * Print time if started within the past 24 hours, print date
1337	 * if within the past 12 months, print year if started greater than
1338	 * 12 months ago.
1339	 */
1340	if (now - then < 24 * 60 * 60)
1341		(void) strftime(st_buf, sizeof (st_buf), gettext(FORMAT_TIME),
1342				tm);
1343	else if (now - then < 12 * 30 * 24 * 60 * 60)
1344		(void) strftime(st_buf, sizeof (st_buf), gettext(FORMAT_DATE),
1345				tm);
1346	else
1347		(void) strftime(st_buf, sizeof (st_buf), gettext(FORMAT_YEAR),
1348				tm);
1349
1350	(void) snprintf(newbuf, newsize, "%s%-*s ", *buf ? *buf : "",
1351	    STIME_COLUMN_WIDTH + 1, st_buf);
1352	if (*buf)
1353		free(*buf);
1354	*buf = newbuf;
1355}
1356
1357#define	STIME_SORTKEY_WIDTH		(sizeof (uint64_t) + sizeof (uint32_t))
1358
1359/* ARGSUSED */
1360static void
1361sortkey_stime(char *buf, int reverse, scf_walkinfo_t *wip)
1362{
1363	struct timeval tv;
1364	int r;
1365
1366	if (wip->pg == NULL)
1367		r = get_restarter_time_prop(wip->inst,
1368		    SCF_PROPERTY_STATE_TIMESTAMP, &tv, 0);
1369	else
1370		r = pg_get_single_val(wip->pg, SCF_PROPERTY_STATE_TIMESTAMP,
1371		    SCF_TYPE_TIME, &tv, NULL, 0);
1372
1373	if (r == 0) {
1374		int64_t sec;
1375		int32_t us;
1376
1377		/* Stick it straight into the buffer. */
1378		sec = tv.tv_sec;
1379		us = tv.tv_usec;
1380
1381		sec = BE_64(sec);
1382		us = BE_32(us);
1383		bcopy(&sec, buf, sizeof (sec));
1384		bcopy(&us, buf + sizeof (sec), sizeof (us));
1385	} else {
1386		bzero(buf, STIME_SORTKEY_WIDTH);
1387	}
1388
1389	if (reverse)
1390		reverse_bytes(buf, STIME_SORTKEY_WIDTH);
1391}
1392
1393
1394/*
1395 * Information about columns which can be displayed.  If you add something,
1396 * check MAX_COLUMN_NAME_LENGTH_STR & update description_of_column() below.
1397 */
1398static const struct column columns[] = {
1399	{ "CTID", CTID_COLUMN_WIDTH, sprint_ctid,
1400		CTID_SORTKEY_WIDTH, sortkey_ctid },
1401	{ "DESC", DESC_COLUMN_WIDTH, sprint_desc,
1402		DESC_COLUMN_WIDTH, sortkey_desc },
1403	{ "FMRI", FMRI_COLUMN_WIDTH, sprint_fmri,
1404		FMRI_COLUMN_WIDTH, sortkey_fmri },
1405	{ "INST", COMPONENT_COLUMN_WIDTH, sprint_instance,
1406		COMPONENT_COLUMN_WIDTH, sortkey_instance },
1407	{ "N", 1,  sprint_n, 1, sortkey_nstate },
1408	{ "NSTA", 4, sprint_nsta, 1, sortkey_nstate },
1409	{ "NSTATE", MAX_SCF_STATE_STRING_SZ - 1, sprint_nstate,
1410		1, sortkey_nstate },
1411	{ "S", 2, sprint_s, 1, sortkey_state },
1412	{ "SCOPE", COMPONENT_COLUMN_WIDTH, sprint_scope,
1413		COMPONENT_COLUMN_WIDTH, sortkey_scope },
1414	{ "SN", 2, sprint_sn, 2, sortkey_sn },
1415	{ "SVC", COMPONENT_COLUMN_WIDTH, sprint_service,
1416		COMPONENT_COLUMN_WIDTH, sortkey_service },
1417	{ "STA", 4, sprint_sta, 1, sortkey_state },
1418	{ "STATE", MAX_SCF_STATE_STRING_SZ - 1 + 1, sprint_state,
1419		1, sortkey_state },
1420	{ "STIME", STIME_COLUMN_WIDTH, sprint_stime,
1421		STIME_SORTKEY_WIDTH, sortkey_stime },
1422};
1423
1424#define	MAX_COLUMN_NAME_LENGTH_STR	"6"
1425
1426static const int ncolumns = sizeof (columns) / sizeof (columns[0]);
1427
1428/*
1429 * Necessary thanks to gettext() & xgettext.
1430 */
1431static const char *
1432description_of_column(int c)
1433{
1434	const char *s = NULL;
1435
1436	switch (c) {
1437	case 0:
1438		s = gettext("contract ID for service (see contract(4))");
1439		break;
1440	case 1:
1441		s = gettext("human-readable description of the service");
1442		break;
1443	case 2:
1444		s = gettext("Fault Managed Resource Identifier for service");
1445		break;
1446	case 3:
1447		s = gettext("portion of the FMRI indicating service instance");
1448		break;
1449	case 4:
1450		s = gettext("abbreviation for next state (if in transition)");
1451		break;
1452	case 5:
1453		s = gettext("abbreviation for next state (if in transition)");
1454		break;
1455	case 6:
1456		s = gettext("name for next state (if in transition)");
1457		break;
1458	case 7:
1459		s = gettext("abbreviation for current state");
1460		break;
1461	case 8:
1462		s = gettext("name for scope associated with service");
1463		break;
1464	case 9:
1465		s = gettext("abbreviation for current state and next state");
1466		break;
1467	case 10:
1468		s = gettext("portion of the FMRI representing service name");
1469		break;
1470	case 11:
1471		s = gettext("abbreviation for current state");
1472		break;
1473	case 12:
1474		s = gettext("name for current state");
1475		break;
1476	case 13:
1477		s = gettext("time of last state change");
1478		break;
1479	}
1480
1481	assert(s != NULL);
1482	return (s);
1483}
1484
1485
1486static void
1487print_usage(const char *progname, FILE *f, boolean_t do_exit)
1488{
1489	(void) fprintf(f, gettext(
1490	    "Usage: %1$s [-aHpv] [-o col[,col ... ]] [-R restarter] "
1491	    "[-sS col] [<service> ...]\n"
1492	    "       %1$s -d | -D [-Hpv] [-o col[,col ... ]] [-sS col] "
1493	    "[<service> ...]\n"
1494	    "       %1$s -l <service> ...\n"
1495	    "       %1$s -x [-v] [<service> ...]\n"
1496	    "       %1$s -?\n"), progname);
1497
1498	if (do_exit)
1499		exit(UU_EXIT_USAGE);
1500}
1501
1502#define	argserr(progname)	print_usage(progname, stderr, B_TRUE)
1503
1504static void
1505print_help(const char *progname)
1506{
1507	int i;
1508
1509	print_usage(progname, stdout, B_FALSE);
1510
1511	(void) printf(gettext("\n"
1512	"\t-a  list all service instances rather than "
1513	"only those that are enabled\n"
1514	"\t-d  list dependencies of the specified service(s)\n"
1515	"\t-D  list dependents of the specified service(s)\n"
1516	"\t-H  omit header line from output\n"
1517	"\t-l  list detailed information about the specified service(s)\n"
1518	"\t-o  list only the specified columns in the output\n"
1519	"\t-p  list process IDs and names associated with each service\n"
1520	"\t-R  list only those services with the specified restarter\n"
1521	"\t-s  sort output in ascending order by the specified column(s)\n"
1522	"\t-S  sort output in descending order by the specified column(s)\n"
1523	"\t-v  list verbose information appropriate to the type of output\n"
1524	"\t-x  explain the status of services that might require maintenance,\n"
1525	"\t    or explain the status of the specified service(s)\n"
1526	"\n\t"
1527	"Services can be specified using an FMRI, abbreviation, or fnmatch(5)\n"
1528	"\tpattern, as shown in these examples for svc:/network/smtp:sendmail\n"
1529	"\n"
1530	"\t%1$s [opts] svc:/network/smtp:sendmail\n"
1531	"\t%1$s [opts] network/smtp:sendmail\n"
1532	"\t%1$s [opts] network/*mail\n"
1533	"\t%1$s [opts] network/smtp\n"
1534	"\t%1$s [opts] smtp:sendmail\n"
1535	"\t%1$s [opts] smtp\n"
1536	"\t%1$s [opts] sendmail\n"
1537	"\n\t"
1538	"Columns for output or sorting can be specified using these names:\n"
1539	"\n"), progname);
1540
1541	for (i = 0; i < ncolumns; i++) {
1542		(void) printf("\t%-" MAX_COLUMN_NAME_LENGTH_STR "s  %s\n",
1543		    columns[i].name, description_of_column(i));
1544	}
1545}
1546
1547
1548/*
1549 * A getsubopt()-like function which returns an index into the columns table.
1550 * On success, *optionp is set to point to the next sub-option, or the
1551 * terminating null if there are none.
1552 */
1553static int
1554getcolumnopt(char **optionp)
1555{
1556	char *str = *optionp, *cp;
1557	int i;
1558
1559	assert(optionp != NULL);
1560	assert(*optionp != NULL);
1561
1562	cp = strchr(*optionp, ',');
1563	if (cp != NULL)
1564		*cp = '\0';
1565
1566	for (i = 0; i < ncolumns; ++i) {
1567		if (strcasecmp(str, columns[i].name) == 0) {
1568			if (cp != NULL)
1569				*optionp = cp + 1;
1570			else
1571				*optionp = strchr(*optionp, '\0');
1572
1573			return (i);
1574		}
1575	}
1576
1577	return (-1);
1578}
1579
1580static void
1581print_header()
1582{
1583	int i;
1584	char *line_buf, *cp;
1585
1586	line_buf = safe_malloc(line_sz);
1587	cp = line_buf;
1588	for (i = 0; i < opt_cnum; ++i) {
1589		const struct column * const colp = &columns[opt_columns[i]];
1590
1591		(void) snprintf(cp, colp->width + 1, "%-*s", colp->width,
1592		    colp->name);
1593		cp += colp->width;
1594		*cp++ = ' ';
1595	}
1596
1597	/* Trim the trailing whitespace */
1598	--cp;
1599	while (*cp == ' ')
1600		--cp;
1601	*(cp+1) = '\0';
1602	(void) puts(line_buf);
1603
1604	free(line_buf);
1605}
1606
1607
1608
1609/*
1610 * Long listing (-l) functions.
1611 */
1612
1613static int
1614pidcmp(const void *l, const void *r)
1615{
1616	pid_t lp = *(pid_t *)l, rp = *(pid_t *)r;
1617
1618	if (lp < rp)
1619		return (-1);
1620	if (lp > rp)
1621		return (1);
1622	return (0);
1623}
1624
1625/*
1626 * This is the strlen() of the longest label ("description"), plus intercolumn
1627 * space.
1628 */
1629#define	DETAILED_WIDTH	(11 + 2)
1630
1631static void
1632detailed_list_processes(scf_instance_t *inst)
1633{
1634	uint64_t c;
1635	pid_t *pids;
1636	uint_t i, n;
1637	psinfo_t psi;
1638
1639	if (get_restarter_count_prop(inst, scf_property_contract, &c,
1640	    EMPTY_OK) != 0)
1641		return;
1642
1643	if (instance_processes(inst, &pids, &n) != 0)
1644		return;
1645
1646	qsort(pids, n, sizeof (*pids), pidcmp);
1647
1648	for (i = 0; i < n; ++i) {
1649		(void) printf("%-*s%lu", DETAILED_WIDTH, gettext("process"),
1650		    pids[i]);
1651
1652		if (get_psinfo(pids[i], &psi) == 0)
1653			(void) printf(" %.*s", PRARGSZ, psi.pr_psargs);
1654
1655		(void) putchar('\n');
1656	}
1657
1658	free(pids);
1659}
1660
1661/*
1662 * Determines the state of a dependency.  If the FMRI specifies a file, then we
1663 * fake up a state based on whether we can access the file.
1664 */
1665static void
1666get_fmri_state(char *fmri, char *state, size_t state_sz)
1667{
1668	char *lfmri;
1669	const char *svc_name, *inst_name, *pg_name, *path;
1670	scf_service_t *svc;
1671	scf_instance_t *inst;
1672	scf_iter_t *iter;
1673
1674	lfmri = safe_strdup(fmri);
1675
1676	/*
1677	 * Check for file:// dependencies
1678	 */
1679	if (scf_parse_file_fmri(lfmri, NULL, &path) == SCF_SUCCESS) {
1680		struct stat64 statbuf;
1681		const char *msg;
1682
1683		if (stat64(path, &statbuf) == 0)
1684			msg = "online";
1685		else if (errno == ENOENT)
1686			msg = "absent";
1687		else
1688			msg = "unknown";
1689
1690		(void) strlcpy(state, msg, state_sz);
1691		return;
1692	}
1693
1694	/*
1695	 * scf_parse_file_fmri() may have overwritten part of the string, so
1696	 * copy it back.
1697	 */
1698	(void) strcpy(lfmri, fmri);
1699
1700	if (scf_parse_svc_fmri(lfmri, NULL, &svc_name, &inst_name,
1701	    &pg_name, NULL) != SCF_SUCCESS) {
1702		free(lfmri);
1703		(void) strlcpy(state, "invalid", state_sz);
1704		return;
1705	}
1706
1707	free(lfmri);
1708
1709	if (svc_name == NULL || pg_name != NULL) {
1710		(void) strlcpy(state, "invalid", state_sz);
1711		return;
1712	}
1713
1714	if (inst_name != NULL) {
1715		/* instance: get state */
1716		inst = scf_instance_create(h);
1717		if (inst == NULL)
1718			scfdie();
1719
1720		if (scf_handle_decode_fmri(h, fmri, NULL, NULL, inst, NULL,
1721		    NULL, SCF_DECODE_FMRI_EXACT) == SCF_SUCCESS)
1722			get_restarter_string_prop(inst, scf_property_state,
1723			    state, state_sz);
1724		else {
1725			switch (scf_error()) {
1726			case SCF_ERROR_INVALID_ARGUMENT:
1727				(void) strlcpy(state, "invalid", state_sz);
1728				break;
1729			case SCF_ERROR_NOT_FOUND:
1730				(void) strlcpy(state, "absent", state_sz);
1731				break;
1732
1733			default:
1734				scfdie();
1735			}
1736		}
1737
1738		scf_instance_destroy(inst);
1739		return;
1740	}
1741
1742	/*
1743	 * service: If only one instance, use that state.  Otherwise, say
1744	 * "multiple".
1745	 */
1746	if ((svc = scf_service_create(h)) == NULL ||
1747	    (inst = scf_instance_create(h)) == NULL ||
1748	    (iter = scf_iter_create(h)) == NULL)
1749		scfdie();
1750
1751	if (scf_handle_decode_fmri(h, fmri, NULL, svc, NULL, NULL, NULL,
1752	    SCF_DECODE_FMRI_EXACT) != SCF_SUCCESS) {
1753		switch (scf_error()) {
1754		case SCF_ERROR_INVALID_ARGUMENT:
1755			(void) strlcpy(state, "invalid", state_sz);
1756			goto out;
1757		case SCF_ERROR_NOT_FOUND:
1758			(void) strlcpy(state, "absent", state_sz);
1759			goto out;
1760
1761		default:
1762			scfdie();
1763		}
1764	}
1765
1766	if (scf_iter_service_instances(iter, svc) != SCF_SUCCESS)
1767		scfdie();
1768
1769	switch (scf_iter_next_instance(iter, inst)) {
1770	case 0:
1771		(void) strlcpy(state, "absent", state_sz);
1772		goto out;
1773
1774	case 1:
1775		break;
1776
1777	default:
1778		scfdie();
1779	}
1780
1781	/* Get the state in case this is the only instance. */
1782	get_restarter_string_prop(inst, scf_property_state, state, state_sz);
1783
1784	switch (scf_iter_next_instance(iter, inst)) {
1785	case 0:
1786		break;
1787
1788	case 1:
1789		/* Nope, multiple instances. */
1790		(void) strlcpy(state, "multiple", state_sz);
1791		goto out;
1792
1793	default:
1794		scfdie();
1795	}
1796
1797out:
1798	scf_iter_destroy(iter);
1799	scf_instance_destroy(inst);
1800	scf_service_destroy(svc);
1801}
1802
1803static void
1804print_detailed_dependency(scf_propertygroup_t *pg)
1805{
1806	scf_property_t *eprop;
1807	scf_iter_t *iter;
1808	scf_type_t ty;
1809	char *val_buf;
1810	int i;
1811
1812	if ((eprop = scf_property_create(h)) == NULL ||
1813	    (iter = scf_iter_create(h)) == NULL)
1814		scfdie();
1815
1816	val_buf = safe_malloc(max_scf_value_length + 1);
1817
1818	if (scf_pg_get_property(pg, SCF_PROPERTY_ENTITIES, eprop) !=
1819	    SCF_SUCCESS ||
1820	    scf_property_type(eprop, &ty) != SCF_SUCCESS ||
1821	    ty != SCF_TYPE_FMRI)
1822		return;
1823
1824	(void) printf("%-*s", DETAILED_WIDTH, gettext("dependency"));
1825
1826	/* Print the grouping */
1827	if (pg_get_single_val(pg, SCF_PROPERTY_GROUPING, SCF_TYPE_ASTRING,
1828	    val_buf, max_scf_value_length + 1, 0) == 0)
1829		(void) fputs(val_buf, stdout);
1830	else
1831		(void) putchar('?');
1832
1833	(void) putchar('/');
1834
1835	if (pg_get_single_val(pg, SCF_PROPERTY_RESTART_ON, SCF_TYPE_ASTRING,
1836	    val_buf, max_scf_value_length + 1, 0) == 0)
1837		(void) fputs(val_buf, stdout);
1838	else
1839		(void) putchar('?');
1840
1841	/* Print the dependency entities. */
1842	if (scf_iter_property_values(iter, eprop) == -1)
1843		scfdie();
1844
1845	while ((i = scf_iter_next_value(iter, g_val)) == 1) {
1846		char state[MAX_SCF_STATE_STRING_SZ];
1847
1848		if (scf_value_get_astring(g_val, val_buf,
1849		    max_scf_value_length + 1) < 0)
1850			scfdie();
1851
1852		(void) putchar(' ');
1853		(void) fputs(val_buf, stdout);
1854
1855		/* Print the state. */
1856		state[0] = '-';
1857		state[1] = '\0';
1858
1859		get_fmri_state(val_buf, state, sizeof (state));
1860
1861		(void) printf(" (%s)", state);
1862	}
1863	if (i == -1)
1864		scfdie();
1865
1866	(void) putchar('\n');
1867
1868	free(val_buf);
1869	scf_iter_destroy(iter);
1870	scf_property_destroy(eprop);
1871}
1872
1873/* ARGSUSED */
1874static int
1875print_detailed(void *unused, scf_walkinfo_t *wip)
1876{
1877	scf_snapshot_t *snap;
1878	scf_propertygroup_t *rpg;
1879	scf_iter_t *pg_iter;
1880
1881	char *buf;
1882	char *timebuf;
1883	size_t tbsz;
1884	int ret;
1885	uint64_t c;
1886	int temp, perm;
1887	struct timeval tv;
1888	time_t stime;
1889	struct tm *tmp;
1890
1891	const char * const fmt = "%-*s%s\n";
1892
1893	assert(wip->pg == NULL);
1894
1895	rpg = scf_pg_create(h);
1896	if (rpg == NULL)
1897		scfdie();
1898
1899	if (first_paragraph)
1900		first_paragraph = 0;
1901	else
1902		(void) putchar('\n');
1903
1904	buf = safe_malloc(max_scf_fmri_length + 1);
1905
1906	if (scf_instance_to_fmri(wip->inst, buf, max_scf_fmri_length + 1) != -1)
1907		(void) printf(fmt, DETAILED_WIDTH, "fmri", buf);
1908
1909	if (common_name_buf == NULL)
1910		common_name_buf = safe_malloc(max_scf_value_length + 1);
1911
1912	if (inst_get_single_val(wip->inst, SCF_PG_TM_COMMON_NAME, locale,
1913	    SCF_TYPE_USTRING, common_name_buf, max_scf_value_length, 0, 1, 1)
1914	    == 0)
1915		(void) printf(fmt, DETAILED_WIDTH, gettext("name"),
1916		    common_name_buf);
1917	else if (inst_get_single_val(wip->inst, SCF_PG_TM_COMMON_NAME, "C",
1918	    SCF_TYPE_USTRING, common_name_buf, max_scf_value_length, 0, 1, 1)
1919	    == 0)
1920		(void) printf(fmt, DETAILED_WIDTH, gettext("name"),
1921		    common_name_buf);
1922
1923	/*
1924	 * Synthesize an 'enabled' property that hides the enabled_ovr
1925	 * implementation from the user.  If the service has been temporarily
1926	 * set to a state other than its permanent value, alert the user with
1927	 * a '(temporary)' message.
1928	 */
1929	perm = instance_enabled(wip->inst, B_FALSE);
1930	temp = instance_enabled(wip->inst, B_TRUE);
1931	if (temp != -1) {
1932		if (temp != perm)
1933			(void) printf(gettext("%-*s%s (temporary)\n"),
1934			    DETAILED_WIDTH, gettext("enabled"),
1935			    temp ? gettext("true") : gettext("false"));
1936		else
1937			(void) printf(fmt, DETAILED_WIDTH,
1938			    gettext("enabled"), temp ? gettext("true") :
1939			    gettext("false"));
1940	} else if (perm != -1) {
1941		(void) printf(fmt, DETAILED_WIDTH, gettext("enabled"),
1942		    perm ? gettext("true") : gettext("false"));
1943	}
1944
1945	/*
1946	 * Property values may be longer than max_scf_fmri_length, but these
1947	 * shouldn't be, so we'll just reuse buf.  The user can use svcprop if
1948	 * he suspects something fishy.
1949	 */
1950	if (scf_instance_get_pg(wip->inst, SCF_PG_RESTARTER, rpg) != 0) {
1951		if (scf_error() != SCF_ERROR_NOT_FOUND)
1952			scfdie();
1953
1954		scf_pg_destroy(rpg);
1955		rpg = NULL;
1956	}
1957
1958	if (rpg) {
1959		if (pg_get_single_val(rpg, scf_property_state, SCF_TYPE_ASTRING,
1960		    buf, max_scf_fmri_length + 1, 0) == 0)
1961			(void) printf(fmt, DETAILED_WIDTH, gettext("state"),
1962			    buf);
1963
1964		if (pg_get_single_val(rpg, scf_property_next_state,
1965		    SCF_TYPE_ASTRING, buf, max_scf_fmri_length + 1, 0) == 0)
1966			(void) printf(fmt, DETAILED_WIDTH,
1967			    gettext("next_state"), buf);
1968
1969		if (pg_get_single_val(rpg, SCF_PROPERTY_STATE_TIMESTAMP,
1970		    SCF_TYPE_TIME, &tv, NULL, 0) == 0) {
1971			stime = tv.tv_sec;
1972			tmp = localtime(&stime);
1973			for (tbsz = 50; ; tbsz *= 2) {
1974				timebuf = safe_malloc(tbsz);
1975				if (strftime(timebuf, tbsz, NULL, tmp) != 0)
1976					break;
1977				free(timebuf);
1978			}
1979			(void) printf(fmt, DETAILED_WIDTH,
1980			    gettext("state_time"),
1981			    timebuf);
1982			free(timebuf);
1983		}
1984
1985	}
1986
1987	if (pg_get_single_val(rpg, SCF_PROPERTY_ALT_LOGFILE,
1988	    SCF_TYPE_ASTRING, buf, max_scf_fmri_length + 1, 0) == 0)
1989		(void) printf(fmt, DETAILED_WIDTH, gettext("alt_logfile"),
1990		    buf);
1991
1992	if (pg_get_single_val(rpg, SCF_PROPERTY_LOGFILE,
1993	    SCF_TYPE_ASTRING, buf, max_scf_fmri_length + 1, 0) == 0)
1994		(void) printf(fmt, DETAILED_WIDTH, gettext("logfile"), buf);
1995
1996	if (inst_get_single_val(wip->inst, SCF_PG_GENERAL,
1997	    SCF_PROPERTY_RESTARTER, SCF_TYPE_ASTRING, buf,
1998	    max_scf_fmri_length + 1, 0, 0, 1) == 0)
1999		(void) printf(fmt, DETAILED_WIDTH, gettext("restarter"), buf);
2000	else
2001		(void) printf(fmt, DETAILED_WIDTH, gettext("restarter"),
2002		    SCF_SERVICE_STARTD);
2003
2004	free(buf);
2005
2006	if (rpg) {
2007		scf_iter_t *iter;
2008
2009		if ((iter = scf_iter_create(h)) == NULL)
2010			scfdie();
2011
2012		if (scf_pg_get_property(rpg, scf_property_contract, g_prop) ==
2013		    0) {
2014			if (scf_property_is_type(g_prop, SCF_TYPE_COUNT) == 0) {
2015				(void) printf("%-*s", DETAILED_WIDTH,
2016				    "contract_id");
2017
2018				if (scf_iter_property_values(iter, g_prop) != 0)
2019					scfdie();
2020
2021				for (;;) {
2022					ret = scf_iter_next_value(iter, g_val);
2023					if (ret == -1)
2024						scfdie();
2025					if (ret == 0)
2026						break;
2027
2028					if (scf_value_get_count(g_val, &c) != 0)
2029						scfdie();
2030					(void) printf("%lu ", (ctid_t)c);
2031				}
2032
2033				(void) putchar('\n');
2034			} else {
2035				if (scf_error() != SCF_ERROR_TYPE_MISMATCH)
2036					scfdie();
2037			}
2038		} else {
2039			if (scf_error() != SCF_ERROR_NOT_FOUND)
2040				scfdie();
2041		}
2042
2043		scf_iter_destroy(iter);
2044	} else {
2045		if (scf_error() != SCF_ERROR_NOT_FOUND)
2046			scfdie();
2047	}
2048
2049	scf_pg_destroy(rpg);
2050
2051	/* Dependencies. */
2052	if ((pg_iter = scf_iter_create(h)) == NULL)
2053		scfdie();
2054
2055	snap = get_running_snapshot(wip->inst);
2056
2057	if (scf_iter_instance_pgs_typed_composed(pg_iter, wip->inst, snap,
2058	    SCF_GROUP_DEPENDENCY) != SCF_SUCCESS)
2059		scfdie();
2060
2061	while ((ret = scf_iter_next_pg(pg_iter, g_pg)) == 1)
2062		print_detailed_dependency(g_pg);
2063	if (ret == -1)
2064		scfdie();
2065
2066	scf_snapshot_destroy(snap);
2067	scf_iter_destroy(pg_iter);
2068
2069	if (opt_processes)
2070		detailed_list_processes(wip->inst);
2071
2072	return (0);
2073}
2074
2075/*
2076 * Append a one-lined description of each process in inst's contract(s) and
2077 * return the augmented string.
2078 */
2079static char *
2080add_processes(char *line, scf_instance_t *inst, scf_propertygroup_t *lpg)
2081{
2082	pid_t *pids = NULL;
2083	uint_t i, n = 0;
2084
2085	if (lpg == NULL) {
2086		if (instance_processes(inst, &pids, &n) != 0)
2087			return (line);
2088	} else {
2089		scf_iter_t *iter;
2090
2091		if ((iter = scf_iter_create(h)) == NULL)
2092			scfdie();
2093
2094		(void) propvals_to_pids(lpg, scf_property_contract, &pids, &n,
2095		    g_prop, g_val, iter);
2096
2097		scf_iter_destroy(iter);
2098	}
2099
2100	if (n == 0)
2101		return (line);
2102
2103	qsort(pids, n, sizeof (*pids), pidcmp);
2104
2105	for (i = 0; i < n; ++i) {
2106		char *cp, stime[9];
2107		psinfo_t psi;
2108		struct tm *tm;
2109		int len = 1 + 15 + 8 + 3 + 6 + 1 + PRFNSZ;
2110
2111		if (get_psinfo(pids[i], &psi) != 0)
2112			continue;
2113
2114		line = realloc(line, strlen(line) + len);
2115		if (line == NULL)
2116			uu_die(gettext("Out of memory.\n"));
2117
2118		cp = strchr(line, '\0');
2119
2120		tm = localtime(&psi.pr_start.tv_sec);
2121
2122		/*
2123		 * Print time if started within the past 24 hours, print date
2124		 * if within the past 12 months, print year if started greater
2125		 * than 12 months ago.
2126		 */
2127		if (now - psi.pr_start.tv_sec < 24 * 60 * 60)
2128		    (void) strftime(stime, sizeof (stime), gettext(FORMAT_TIME),
2129			tm);
2130		else if (now - psi.pr_start.tv_sec < 12 * 30 * 24 * 60 * 60)
2131		    (void) strftime(stime, sizeof (stime), gettext(FORMAT_DATE),
2132			tm);
2133		else
2134		    (void) strftime(stime, sizeof (stime), gettext(FORMAT_YEAR),
2135			tm);
2136
2137		(void) snprintf(cp, len, "\n               %-8s   %6ld %.*s",
2138		    stime, pids[i], PRFNSZ, psi.pr_fname);
2139	}
2140
2141	free(pids);
2142
2143	return (line);
2144}
2145
2146/*ARGSUSED*/
2147static int
2148list_instance(void *unused, scf_walkinfo_t *wip)
2149{
2150	struct avl_string *lp;
2151	char *cp;
2152	int i;
2153	uu_avl_index_t idx;
2154
2155	/*
2156	 * If the user has specified a restarter, check for a match first
2157	 */
2158	if (restarters != NULL) {
2159		struct pfmri_list *rest;
2160		int match;
2161		char *restarter_fmri;
2162		const char *scope_name, *svc_name, *inst_name, *pg_name;
2163
2164		/* legacy services don't have restarters */
2165		if (wip->pg != NULL)
2166			return (0);
2167
2168		restarter_fmri = safe_malloc(max_scf_fmri_length + 1);
2169
2170		if (inst_get_single_val(wip->inst, SCF_PG_GENERAL,
2171		    SCF_PROPERTY_RESTARTER, SCF_TYPE_ASTRING, restarter_fmri,
2172		    max_scf_fmri_length + 1, 0, 0, 1) != 0)
2173			(void) strcpy(restarter_fmri, SCF_SERVICE_STARTD);
2174
2175		if (scf_parse_svc_fmri(restarter_fmri, &scope_name, &svc_name,
2176		    &inst_name, &pg_name, NULL) != SCF_SUCCESS) {
2177			free(restarter_fmri);
2178			return (0);
2179		}
2180
2181		match = 0;
2182		for (rest = restarters; rest != NULL; rest = rest->next) {
2183			if (strcmp(rest->scope, scope_name) == 0 &&
2184			    strcmp(rest->service, svc_name) == 0 &&
2185			    strcmp(rest->instance, inst_name) == 0)
2186				match = 1;
2187		}
2188
2189		free(restarter_fmri);
2190
2191		if (!match)
2192			return (0);
2193	}
2194
2195	if (wip->pg == NULL && ht_buckets != NULL && ht_add(wip->fmri)) {
2196		/* It was already there. */
2197		return (0);
2198	}
2199
2200	lp = safe_malloc(sizeof (*lp));
2201
2202	lp->str = NULL;
2203	for (i = 0; i < opt_cnum; ++i) {
2204		columns[opt_columns[i]].sprint(&lp->str, wip);
2205	}
2206	cp = lp->str + strlen(lp->str);
2207	cp--;
2208	while (*cp == ' ')
2209		cp--;
2210	*(cp+1) = '\0';
2211
2212	/* If we're supposed to list the processes, too, do that now. */
2213	if (opt_processes)
2214		lp->str = add_processes(lp->str, wip->inst, wip->pg);
2215
2216	/* Create the sort key. */
2217	cp = lp->key = safe_malloc(sortkey_sz);
2218	for (i = 0; i < opt_snum; ++i) {
2219		int j = opt_sort[i] & 0xff;
2220
2221		assert(columns[j].get_sortkey != NULL);
2222		columns[j].get_sortkey(cp, opt_sort[i] & ~0xff, wip);
2223		cp += columns[j].sortkey_width;
2224	}
2225
2226	/* Insert into AVL tree. */
2227	uu_avl_node_init(lp, &lp->node, lines_pool);
2228	(void) uu_avl_find(lines, lp, NULL, &idx);
2229	uu_avl_insert(lines, lp, idx);
2230
2231	return (0);
2232}
2233
2234static int
2235list_if_enabled(void *unused, scf_walkinfo_t *wip)
2236{
2237	if (wip->pg != NULL ||
2238	    instance_enabled(wip->inst, B_FALSE) == 1 ||
2239	    instance_enabled(wip->inst, B_TRUE) == 1)
2240		return (list_instance(unused, wip));
2241
2242	return (0);
2243}
2244
2245/*
2246 * Service FMRI selection: Lookup and call list_instance() for the instances.
2247 * Instance FMRI selection: Lookup and call list_instance().
2248 *
2249 * Note: This is shoehorned into a walk_dependencies() callback prototype so
2250 * it can be used in list_dependencies.
2251 */
2252static int
2253list_svc_or_inst_fmri(void *complain, scf_walkinfo_t *wip)
2254{
2255	char *fmri;
2256	const char *svc_name, *inst_name, *pg_name, *save;
2257	scf_iter_t *iter;
2258	int ret;
2259
2260	fmri = safe_strdup(wip->fmri);
2261
2262	if (scf_parse_svc_fmri(fmri, NULL, &svc_name, &inst_name, &pg_name,
2263	    NULL) != SCF_SUCCESS) {
2264		if (complain)
2265			uu_warn(gettext("FMRI \"%s\" is invalid.\n"),
2266			    wip->fmri);
2267		exit_status = UU_EXIT_FATAL;
2268		free(fmri);
2269		return (0);
2270	}
2271
2272	/*
2273	 * Yes, this invalidates *_name, but we only care whether they're NULL
2274	 * or not.
2275	 */
2276	free(fmri);
2277
2278	if (svc_name == NULL || pg_name != NULL) {
2279		if (complain)
2280			uu_warn(gettext("FMRI \"%s\" does not designate a "
2281			    "service or instance.\n"), wip->fmri);
2282		return (0);
2283	}
2284
2285	if (inst_name != NULL) {
2286		/* instance */
2287		if (scf_handle_decode_fmri(h, wip->fmri, wip->scope, wip->svc,
2288		    wip->inst, NULL, NULL, 0) != SCF_SUCCESS) {
2289			if (scf_error() != SCF_ERROR_NOT_FOUND)
2290				scfdie();
2291
2292			if (complain)
2293				uu_warn(gettext(
2294				    "Instance \"%s\" does not exist.\n"),
2295				    wip->fmri);
2296			return (0);
2297		}
2298
2299		return (list_instance(NULL, wip));
2300	}
2301
2302	/* service: Walk the instances. */
2303	if (scf_handle_decode_fmri(h, wip->fmri, wip->scope, wip->svc, NULL,
2304	    NULL, NULL, 0) != SCF_SUCCESS) {
2305		if (scf_error() != SCF_ERROR_NOT_FOUND)
2306			scfdie();
2307
2308		if (complain)
2309			uu_warn(gettext("Service \"%s\" does not exist.\n"),
2310			    wip->fmri);
2311
2312		exit_status = UU_EXIT_FATAL;
2313
2314		return (0);
2315	}
2316
2317	iter = scf_iter_create(h);
2318	if (iter == NULL)
2319		scfdie();
2320
2321	if (scf_iter_service_instances(iter, wip->svc) != SCF_SUCCESS)
2322		scfdie();
2323
2324	if ((fmri = malloc(max_scf_fmri_length + 1)) == NULL) {
2325		scf_iter_destroy(iter);
2326		exit_status = UU_EXIT_FATAL;
2327		return (0);
2328	}
2329
2330	save = wip->fmri;
2331	wip->fmri = fmri;
2332	while ((ret = scf_iter_next_instance(iter, wip->inst)) == 1) {
2333		if (scf_instance_to_fmri(wip->inst, fmri,
2334		    max_scf_fmri_length + 1) <= 0)
2335			scfdie();
2336		(void) list_instance(NULL, wip);
2337	}
2338	free(fmri);
2339	wip->fmri = save;
2340	if (ret == -1)
2341		scfdie();
2342
2343	exit_status = UU_EXIT_OK;
2344
2345	scf_iter_destroy(iter);
2346
2347	return (0);
2348}
2349
2350/*
2351 * Dependency selection: Straightforward since each instance lists the
2352 * services it depends on.
2353 */
2354
2355static void
2356walk_dependencies(scf_walkinfo_t *wip, scf_walk_callback callback, void *data)
2357{
2358	scf_snapshot_t *snap;
2359	scf_iter_t *iter, *viter;
2360	int ret, vret;
2361	char *dep;
2362
2363	assert(wip->inst != NULL);
2364
2365	if ((iter = scf_iter_create(h)) == NULL ||
2366	    (viter = scf_iter_create(h)) == NULL)
2367		scfdie();
2368
2369	snap = get_running_snapshot(wip->inst);
2370
2371	if (scf_iter_instance_pgs_typed_composed(iter, wip->inst, snap,
2372	    SCF_GROUP_DEPENDENCY) != SCF_SUCCESS)
2373		scfdie();
2374
2375	dep = safe_malloc(max_scf_value_length + 1);
2376
2377	while ((ret = scf_iter_next_pg(iter, g_pg)) == 1) {
2378		scf_type_t ty;
2379
2380		/* Ignore exclude_any dependencies. */
2381		if (scf_pg_get_property(g_pg, SCF_PROPERTY_GROUPING, g_prop) !=
2382		    SCF_SUCCESS) {
2383			if (scf_error() != SCF_ERROR_NOT_FOUND)
2384				scfdie();
2385
2386			continue;
2387		}
2388
2389		if (scf_property_type(g_prop, &ty) != SCF_SUCCESS)
2390			scfdie();
2391
2392		if (ty != SCF_TYPE_ASTRING)
2393			continue;
2394
2395		if (scf_property_get_value(g_prop, g_val) != SCF_SUCCESS) {
2396			if (scf_error() != SCF_ERROR_CONSTRAINT_VIOLATED)
2397				scfdie();
2398
2399			continue;
2400		}
2401
2402		if (scf_value_get_astring(g_val, dep,
2403		    max_scf_value_length + 1) < 0)
2404			scfdie();
2405
2406		if (strcmp(dep, SCF_DEP_EXCLUDE_ALL) == 0)
2407			continue;
2408
2409		if (scf_pg_get_property(g_pg, SCF_PROPERTY_ENTITIES, g_prop) !=
2410		    SCF_SUCCESS) {
2411			if (scf_error() != SCF_ERROR_NOT_FOUND)
2412				scfdie();
2413
2414			continue;
2415		}
2416
2417		if (scf_iter_property_values(viter, g_prop) != SCF_SUCCESS)
2418			scfdie();
2419
2420		while ((vret = scf_iter_next_value(viter, g_val)) == 1) {
2421			if (scf_value_get_astring(g_val, dep,
2422			    max_scf_value_length + 1) < 0)
2423				scfdie();
2424
2425			wip->fmri = dep;
2426			if (callback(data, wip) != 0)
2427				goto out;
2428		}
2429		if (vret == -1)
2430			scfdie();
2431	}
2432	if (ret == -1)
2433		scfdie();
2434
2435out:
2436	scf_iter_destroy(viter);
2437	scf_iter_destroy(iter);
2438	scf_snapshot_destroy(snap);
2439}
2440
2441static int
2442list_dependencies(void *data, scf_walkinfo_t *wip)
2443{
2444	walk_dependencies(wip, list_svc_or_inst_fmri, data);
2445	return (0);
2446}
2447
2448
2449/*
2450 * Dependent selection: The "providing" service's or instance's FMRI is parsed
2451 * into the provider_* variables, the instances are walked, and any instance
2452 * which lists an FMRI which parses to these components is selected.  This is
2453 * inefficient in the face of multiple operands, but that should be uncommon.
2454 */
2455
2456static char *provider_scope;
2457static char *provider_svc;
2458static char *provider_inst;	/* NULL for services */
2459
2460/*ARGSUSED*/
2461static int
2462check_against_provider(void *arg, scf_walkinfo_t *wip)
2463{
2464	char *cfmri;
2465	const char *scope_name, *svc_name, *inst_name, *pg_name;
2466	int *matchp = arg;
2467
2468	cfmri = safe_strdup(wip->fmri);
2469
2470	if (scf_parse_svc_fmri(cfmri, &scope_name, &svc_name, &inst_name,
2471	    &pg_name, NULL) != SCF_SUCCESS) {
2472		free(cfmri);
2473		return (0);
2474	}
2475
2476	if (svc_name == NULL || pg_name != NULL) {
2477		free(cfmri);
2478		return (0);
2479	}
2480
2481	/*
2482	 * If the user has specified an instance, then also match dependencies
2483	 * on the service itself.
2484	 */
2485	*matchp = (strcmp(provider_scope, scope_name) == 0 &&
2486	    strcmp(provider_svc, svc_name) == 0 &&
2487	    (provider_inst == NULL ? (inst_name == NULL) :
2488	    (inst_name == NULL || strcmp(provider_inst, inst_name) == 0)));
2489
2490	free(cfmri);
2491
2492	/* Stop on matches. */
2493	return (*matchp);
2494}
2495
2496static int
2497list_if_dependent(void *unused, scf_walkinfo_t *wip)
2498{
2499	/* Only proceed if this instance depends on provider_*. */
2500	int match = 0;
2501
2502	(void) walk_dependencies(wip, check_against_provider, &match);
2503
2504	if (match)
2505		return (list_instance(unused, wip));
2506
2507	return (0);
2508}
2509
2510/*ARGSUSED*/
2511static int
2512list_dependents(void *unused, scf_walkinfo_t *wip)
2513{
2514	char *save;
2515	int ret;
2516
2517	if (scf_scope_get_name(wip->scope, provider_scope,
2518	    max_scf_fmri_length) <= 0 ||
2519	    scf_service_get_name(wip->svc, provider_svc,
2520	    max_scf_fmri_length) <= 0)
2521		scfdie();
2522
2523	save = provider_inst;
2524	if (wip->inst == NULL)
2525		provider_inst = NULL;
2526	else if (scf_instance_get_name(wip->inst, provider_inst,
2527	    max_scf_fmri_length) <= 0)
2528		scfdie();
2529
2530	ret = scf_walk_fmri(h, 0, NULL, 0, list_if_dependent, NULL, NULL,
2531	    uu_warn);
2532
2533	provider_inst = save;
2534
2535	return (ret);
2536}
2537
2538/*
2539 * main() & helpers
2540 */
2541
2542static void
2543add_sort_column(const char *col, int reverse)
2544{
2545	int i;
2546
2547	++opt_snum;
2548
2549	opt_sort = realloc(opt_sort, opt_snum * sizeof (*opt_sort));
2550	if (opt_sort == NULL)
2551		uu_die(gettext("Too many sort criteria: out of memory.\n"));
2552
2553	for (i = 0; i < ncolumns; ++i) {
2554		if (strcasecmp(col, columns[i].name) == 0)
2555			break;
2556	}
2557
2558	if (i < ncolumns)
2559		opt_sort[opt_snum - 1] = (reverse ? i | 0x100 : i);
2560	else
2561		uu_die(gettext("Unrecognized sort column \"%s\".\n"), col);
2562
2563	sortkey_sz += columns[i].sortkey_width;
2564}
2565
2566static void
2567add_restarter(const char *fmri)
2568{
2569	char *cfmri;
2570	const char *pg_name;
2571	struct pfmri_list *rest;
2572
2573	cfmri = safe_strdup(fmri);
2574	rest = safe_malloc(sizeof (*rest));
2575
2576	if (scf_parse_svc_fmri(cfmri, &rest->scope, &rest->service,
2577	    &rest->instance, &pg_name, NULL) != SCF_SUCCESS)
2578		uu_die(gettext("Restarter FMRI \"%s\" is invalid.\n"), fmri);
2579
2580	if (rest->instance == NULL || pg_name != NULL)
2581		uu_die(gettext("Restarter FMRI \"%s\" does not designate an "
2582		    "instance.\n"), fmri);
2583
2584	rest->next = restarters;
2585	restarters = rest;
2586	return;
2587
2588err:
2589	free(cfmri);
2590	free(rest);
2591}
2592
2593/* ARGSUSED */
2594static int
2595line_cmp(const void *l_arg, const void *r_arg, void *private)
2596{
2597	const struct avl_string *l = l_arg;
2598	const struct avl_string *r = r_arg;
2599
2600	return (memcmp(l->key, r->key, sortkey_sz));
2601}
2602
2603/* ARGSUSED */
2604static int
2605print_line(void *e, void *private)
2606{
2607	struct avl_string *lp = e;
2608
2609	(void) puts(lp->str);
2610
2611	return (UU_WALK_NEXT);
2612}
2613
2614int
2615main(int argc, char **argv)
2616{
2617	char opt, opt_mode;
2618	int i, n;
2619	char *columns_str = NULL;
2620	char *cp;
2621	const char *progname;
2622	int err;
2623
2624	int show_all = 0;
2625	int show_header = 1;
2626
2627	const char * const options = "aHpvo:R:s:S:dDl?x";
2628
2629	(void) setlocale(LC_ALL, "");
2630
2631	locale = setlocale(LC_MESSAGES, "");
2632	if (locale) {
2633		locale = safe_strdup(locale);
2634		sanitize_locale(locale);
2635	}
2636
2637	(void) textdomain(TEXT_DOMAIN);
2638	progname = uu_setpname(argv[0]);
2639
2640	exit_status = UU_EXIT_OK;
2641
2642	max_scf_name_length = scf_limit(SCF_LIMIT_MAX_NAME_LENGTH);
2643	max_scf_value_length = scf_limit(SCF_LIMIT_MAX_VALUE_LENGTH);
2644	max_scf_fmri_length = scf_limit(SCF_LIMIT_MAX_FMRI_LENGTH);
2645
2646	if (max_scf_name_length == -1 || max_scf_value_length == -1 ||
2647	    max_scf_fmri_length == -1)
2648		scfdie();
2649
2650	now = time(NULL);
2651	assert(now != -1);
2652
2653	/*
2654	 * opt_mode is the mode of operation.  0 for plain, 'd' for
2655	 * dependencies, 'D' for dependents, and 'l' for detailed (long).  We
2656	 * need to know now so we know which options are valid.
2657	 */
2658	opt_mode = 0;
2659	while ((opt = getopt(argc, argv, options)) != -1) {
2660		switch (opt) {
2661		case '?':
2662			if (optopt == '?') {
2663				print_help(progname);
2664				return (UU_EXIT_OK);
2665			} else {
2666				argserr(progname);
2667				/* NOTREACHED */
2668			}
2669
2670		case 'd':
2671		case 'D':
2672		case 'l':
2673			if (opt_mode != 0)
2674				argserr(progname);
2675
2676			opt_mode = opt;
2677			break;
2678
2679		case 'x':
2680			if (opt_mode != 0)
2681				argserr(progname);
2682
2683			opt_mode = opt;
2684			break;
2685
2686		default:
2687			break;
2688		}
2689	}
2690
2691	sortkey_sz = 0;
2692
2693	optind = 1;	/* Reset getopt() */
2694	while ((opt = getopt(argc, argv, options)) != -1) {
2695		switch (opt) {
2696		case 'a':
2697			if (opt_mode != 0)
2698				argserr(progname);
2699			show_all = 1;
2700			break;
2701
2702		case 'H':
2703			if (opt_mode == 'l' || opt_mode == 'x')
2704				argserr(progname);
2705			show_header = 0;
2706			break;
2707
2708		case 'p':
2709			if (opt_mode == 'x')
2710				argserr(progname);
2711			opt_processes = 1;
2712			break;
2713
2714		case 'v':
2715			if (opt_mode == 'l')
2716				argserr(progname);
2717			opt_verbose = 1;
2718			break;
2719
2720		case 'o':
2721			if (opt_mode == 'l' || opt_mode == 'x')
2722				argserr(progname);
2723			columns_str = optarg;
2724			break;
2725
2726		case 'R':
2727			if (opt_mode != 0 || opt_mode == 'x')
2728				argserr(progname);
2729
2730			add_restarter(optarg);
2731			break;
2732
2733		case 's':
2734		case 'S':
2735			if (opt_mode != 0)
2736				argserr(progname);
2737
2738			add_sort_column(optarg, optopt == 'S');
2739			break;
2740
2741		case 'd':
2742		case 'D':
2743		case 'l':
2744		case 'x':
2745			assert(opt_mode == optopt);
2746			break;
2747
2748		case '?':
2749			argserr(progname);
2750			/* NOTREACHED */
2751
2752		default:
2753			assert(0);
2754			abort();
2755		}
2756	}
2757
2758	/*
2759	 * -a is only meaningful when given no arguments
2760	 */
2761	if (show_all && optind != argc)
2762		uu_warn(gettext("-a ignored when used with arguments.\n"));
2763
2764	h = scf_handle_create(SCF_VERSION);
2765	if (h == NULL)
2766		scfdie();
2767
2768	if (scf_handle_bind(h) == -1)
2769		uu_die(gettext("Could not bind to repository server: %s.  "
2770		    "Exiting.\n"), scf_strerror(scf_error()));
2771
2772	if ((g_pg = scf_pg_create(h)) == NULL ||
2773	    (g_prop = scf_property_create(h)) == NULL ||
2774	    (g_val = scf_value_create(h)) == NULL)
2775		scfdie();
2776
2777	argc -= optind;
2778	argv += optind;
2779
2780	/*
2781	 * If we're in long mode, take care of it now before we deal with the
2782	 * sorting and the columns, since we won't use them anyway.
2783	 */
2784	if (opt_mode == 'l') {
2785		if (argc == 0)
2786			argserr(progname);
2787
2788		if ((err = scf_walk_fmri(h, argc, argv, SCF_WALK_MULTIPLE,
2789		    print_detailed, NULL, &exit_status, uu_warn)) != 0) {
2790			uu_warn(gettext("failed to iterate over "
2791			    "instances: %s\n"), scf_strerror(err));
2792			exit_status = UU_EXIT_FATAL;
2793		}
2794
2795		return (exit_status);
2796	}
2797
2798	if (opt_mode == 'x') {
2799		explain(opt_verbose, argc, argv);
2800
2801		return (exit_status);
2802	}
2803
2804
2805	if (opt_snum == 0) {
2806		/* Default sort. */
2807		add_sort_column("state", 0);
2808		add_sort_column("stime", 0);
2809		add_sort_column("fmri", 0);
2810	}
2811
2812	if (columns_str == NULL) {
2813		if (!opt_verbose)
2814			columns_str = safe_strdup("state,stime,fmri");
2815		else
2816			columns_str =
2817			    safe_strdup("state,nstate,stime,ctid,fmri");
2818	}
2819
2820	/* Decode columns_str into opt_columns. */
2821	line_sz = 0;
2822
2823	opt_cnum = 1;
2824	for (cp = columns_str; *cp != '\0'; ++cp)
2825		if (*cp == ',')
2826			++opt_cnum;
2827
2828	opt_columns = malloc(opt_cnum * sizeof (*opt_columns));
2829	if (opt_columns == NULL)
2830		uu_die(gettext("Too many columns.\n"));
2831
2832	for (n = 0; *columns_str != '\0'; ++n) {
2833		i = getcolumnopt(&columns_str);
2834		if (i == -1)
2835			uu_die(gettext("Unknown column \"%s\".\n"),
2836			    columns_str);
2837
2838		if (strcmp(columns[i].name, "N") == 0 ||
2839		    strcmp(columns[i].name, "SN") == 0 ||
2840		    strcmp(columns[i].name, "NSTA") == 0 ||
2841		    strcmp(columns[i].name, "NSTATE") == 0)
2842			opt_nstate_shown = 1;
2843
2844		opt_columns[n] = i;
2845		line_sz += columns[i].width + 1;
2846	}
2847
2848
2849	if ((lines_pool = uu_avl_pool_create("lines_pool",
2850	    sizeof (struct avl_string), offsetof(struct avl_string, node),
2851	    line_cmp, UU_AVL_DEBUG)) == NULL ||
2852	    (lines = uu_avl_create(lines_pool, NULL, 0)) == NULL)
2853		uu_die(gettext("Unexpected libuutil error: %s.  Exiting.\n"),
2854		    uu_strerror(uu_error()));
2855
2856	switch (opt_mode) {
2857	case 0:
2858		ht_init();
2859
2860		/* Always show all FMRIs when given arguments or restarters */
2861		if (argc != 0 || restarters != NULL)
2862			show_all =  1;
2863
2864		if ((err = scf_walk_fmri(h, argc, argv,
2865		    SCF_WALK_MULTIPLE | SCF_WALK_LEGACY,
2866		    show_all ? list_instance : list_if_enabled, NULL,
2867		    &exit_status, uu_warn)) != 0) {
2868			uu_warn(gettext("failed to iterate over "
2869			    "instances: %s\n"), scf_strerror(err));
2870			exit_status = UU_EXIT_FATAL;
2871		}
2872		break;
2873
2874	case 'd':
2875		if (argc == 0)
2876			argserr(progname);
2877
2878		if ((err = scf_walk_fmri(h, argc, argv,
2879		    SCF_WALK_MULTIPLE, list_dependencies, NULL,
2880		    &exit_status, uu_warn)) != 0) {
2881			uu_warn(gettext("failed to iterate over "
2882			    "instances: %s\n"), scf_strerror(err));
2883			exit_status = UU_EXIT_FATAL;
2884		}
2885		break;
2886
2887	case 'D':
2888		if (argc == 0)
2889			argserr(progname);
2890
2891		provider_scope = safe_malloc(max_scf_fmri_length);
2892		provider_svc = safe_malloc(max_scf_fmri_length);
2893		provider_inst = safe_malloc(max_scf_fmri_length);
2894
2895		if ((err = scf_walk_fmri(h, argc, argv,
2896		    SCF_WALK_MULTIPLE | SCF_WALK_SERVICE,
2897		    list_dependents, NULL, &exit_status, uu_warn)) != 0) {
2898			uu_warn(gettext("failed to iterate over "
2899			    "instances: %s\n"), scf_strerror(err));
2900			exit_status = UU_EXIT_FATAL;
2901		}
2902
2903		free(provider_scope);
2904		free(provider_svc);
2905		free(provider_inst);
2906		break;
2907
2908	default:
2909		assert(0);
2910		abort();
2911	}
2912
2913	if (show_header)
2914		print_header();
2915
2916	(void) uu_avl_walk(lines, print_line, NULL, 0);
2917
2918	return (exit_status);
2919}
2920