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 (the "License").
6 * You may not use this file except in compliance with the License.
7 *
8 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9 * or http://www.opensolaris.org/os/licensing.
10 * See the License for the specific language governing permissions
11 * and limitations under the License.
12 *
13 * When distributing Covered Code, include this CDDL HEADER in each
14 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15 * If applicable, add the following below this CDDL HEADER, with the
16 * fields enclosed by brackets "[]" replaced with your own identifying
17 * information: Portions Copyright [yyyy] [name of copyright owner]
18 *
19 * CDDL HEADER END
20 */
21
22/*
23 * Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
24 * Use is subject to license terms.
25 * Copyright 2020 Joyent, Inc.
26 */
27
28/*
29 * Service state explanation.  For select services, display a description, the
30 * state, and possibly why the service is in that state, what's causing it to
31 * be in that state, and what other services it is keeping offline (impact).
32 *
33 * Explaining states other than offline is easy.  For maintenance and
34 * degraded, we just use the auxiliary state.  For offline, we must determine
35 * which dependencies are unsatisfied and recurse.  If a causal service is not
36 * offline, then a svcptr to it is added to the offline service's causes list.
37 * If a causal service is offline, then we recurse to determine its causes and
38 * merge them into the causes list of the service in question (see
39 * add_causes()).  Note that by adding a self-pointing svcptr to the causes
40 * lists of services which are not offline or are offline for unknown reasons,
41 * we can always merge the unsatisfied dependency's causes into the
42 * dependent's list.
43 *
44 * Computing an impact list is more involved because the dependencies in the
45 * repository are unidirectional; it requires determining the causes of all
46 * offline services.  For each unsatisfied dependency of an offline service,
47 * a svcptr to the dependent is added to the dependency's impact_dependents
48 * list (see add_causes()).  determine_impact() uses the lists to build an
49 * impact list.  The direct dependency is used so that a path from the
50 * affected service to the causal service can be constructed (see
51 * print_dependency_reasons()).
52 *
53 * Because we always need at least impact counts, we always run
54 * determine_causes() on all services.
55 *
56 * If no arguments are given, we must select the services which are causing
57 * other services to be offline.  We do so by adding services which are not
58 * running for any reason other than another service to the g_causes list in
59 * determine_causes().
60 *
61 * Since all services must be examined, and their states may be consulted
62 * a lot, it is important that we only read volatile data (like states) from
63 * the repository once.  add_instance() reads data for an instance from the
64 * repository into an inst_t and puts it into the "services" cache, which is
65 * organized as a hash table of svc_t's, each of which has a list of inst_t's.
66 */
67
68#include "svcs.h"
69
70#include <sys/stat.h>
71#include <sys/wait.h>
72
73#include <assert.h>
74#include <errno.h>
75#include <libintl.h>
76#include <libuutil.h>
77#include <libscf.h>
78#include <libscf_priv.h>
79#include <string.h>
80#include <stdio.h>
81#include <stdlib.h>
82#include <time.h>
83
84
85#define	DC_DISABLED	"SMF-8000-05"
86#define	DC_TEMPDISABLED	"SMF-8000-1S"
87#define	DC_RSTRINVALID	"SMF-8000-2A"
88#define	DC_RSTRABSENT	"SMF-8000-3P"
89#define	DC_UNINIT	"SMF-8000-4D"
90#define	DC_RSTRDEAD	"SMF-8000-5H"
91#define	DC_ADMINMAINT	"SMF-8000-63"
92#define	DC_SVCREQMAINT	"SMF-8000-R4"
93#define	DC_REPTFAIL	"SMF-8000-7Y"
94#define	DC_METHFAIL	"SMF-8000-8Q"
95#define	DC_NONE		"SMF-8000-9C"
96#define	DC_UNKNOWN	"SMF-8000-AR"
97#define	DC_STARTING	"SMF-8000-C4"
98#define	DC_ADMINDEGR	"SMF-8000-DX"
99#define	DC_DEPABSENT	"SMF-8000-E2"
100#define	DC_DEPRUNNING	"SMF-8000-FJ"
101#define	DC_DEPOTHER	"SMF-8000-GE"
102#define	DC_DEPCYCLE	"SMF-8000-HP"
103#define	DC_INVALIDDEP	"SMF-8000-JA"
104#define	DC_STARTFAIL	"SMF-8000-KS"
105#define	DC_TOOQUICKLY	"SMF-8000-L5"
106#define	DC_INVALIDSTATE	"SMF-8000-N3"
107#define	DC_TRANSITION	"SMF-8000-PH"
108
109#define	DEFAULT_MAN_PATH	"/usr/share/man"
110
111#define	AUX_STATE_INVALID	"invalid_aux_state"
112
113#define	uu_list_append(lst, e)	uu_list_insert_before(lst, NULL, e)
114
115#define	bad_error(func, err)						\
116	uu_panic("%s:%d: %s() failed with unknown error %d.\n",		\
117	    __FILE__, __LINE__, func, err);
118
119typedef struct {
120	const char *svcname;
121	const char *instname;
122
123	/* restarter pg properties */
124	char state[MAX_SCF_STATE_STRING_SZ];
125	char next_state[MAX_SCF_STATE_STRING_SZ];
126	struct timeval stime;
127	const char *aux_state;
128	const char *aux_fmri;
129	int64_t start_method_waitstatus;
130
131	uint8_t enabled;
132	int temporary;
133	const char *restarter;
134	uu_list_t *dependencies;	/* list of dependency_group's */
135
136	char comment[SCF_COMMENT_MAX_LENGTH];
137
138	int active;			/* In use?  (cycle detection) */
139	int restarter_bad;
140	const char *summary;
141	uu_list_t *baddeps;		/* list of dependency's */
142	uu_list_t *causes;		/* list of svcptrs */
143	uu_list_t *impact_dependents;	/* list of svcptrs */
144	uu_list_t *impact;		/* list of svcptrs */
145
146	uu_list_node_t node;
147} inst_t;
148
149typedef struct service {
150	const char *svcname;
151	uu_list_t *instances;
152	struct service *next;
153} svc_t;
154
155struct svcptr {
156	inst_t *svcp;
157	inst_t *next_hop;
158	uu_list_node_t node;
159};
160
161struct dependency_group {
162	enum { DGG_REQALL, DGG_REQANY, DGG_OPTALL, DGG_EXCALL } grouping;
163	const char *type;
164	uu_list_t *entities;		/* List of struct dependency's */
165	uu_list_node_t node;
166};
167
168struct dependency {
169	const char *fmri;
170	uu_list_node_t node;
171};
172
173/* Hash table of service names -> svc_t's */
174#define	SVC_HASH_NBUCKETS	256
175#define	SVC_HASH_MASK		(SVC_HASH_NBUCKETS - 1)
176
177static svc_t **services;
178
179static uu_list_pool_t *insts, *svcptrs, *depgroups, *deps;
180static uu_list_t *g_causes;		/* list of svcptrs */
181
182static scf_scope_t *g_local_scope;
183static scf_service_t *g_svc;
184static scf_instance_t *g_inst;
185static scf_snapshot_t *g_snap;
186static scf_propertygroup_t *g_pg;
187static scf_property_t *g_prop;
188static scf_value_t *g_val;
189static scf_iter_t *g_iter, *g_viter;
190static char *g_fmri, *g_value;
191static size_t g_fmri_sz, g_value_sz;
192static const char *g_msgbase = "http://illumos.org/msg/";
193
194static char *emsg_nomem;
195static char *emsg_invalid_dep;
196
197extern scf_handle_t *h;
198extern char *g_zonename;
199
200/* ARGSUSED */
201static int
202svcptr_compare(struct svcptr *a, struct svcptr *b, void *data)
203{
204	return (b->svcp - a->svcp);
205}
206
207static uint32_t
208hash_name(const char *name)
209{
210	uint32_t h = 0, g;
211	const char *p;
212
213	for (p = name; *p != '\0'; ++p) {
214		h = (h << 4) + *p;
215		if ((g = (h & 0xf0000000)) != 0) {
216			h ^= (g >> 24);
217			h ^= g;
218		}
219	}
220
221	return (h);
222}
223
224static void
225x_init(void)
226{
227	emsg_nomem = gettext("Out of memory.\n");
228	emsg_invalid_dep =
229	    gettext("svc:/%s:%s has invalid dependency \"%s\".\n");
230
231	services = calloc(SVC_HASH_NBUCKETS, sizeof (*services));
232	if (services == NULL)
233		uu_die(emsg_nomem);
234
235	insts = uu_list_pool_create("insts", sizeof (inst_t),
236	    offsetof(inst_t, node), NULL, UU_LIST_POOL_DEBUG);
237	svcptrs = uu_list_pool_create("svcptrs", sizeof (struct svcptr),
238	    offsetof(struct svcptr, node), (uu_compare_fn_t *)svcptr_compare,
239	    UU_LIST_POOL_DEBUG);
240	depgroups = uu_list_pool_create("depgroups",
241	    sizeof (struct dependency_group),
242	    offsetof(struct dependency_group, node), NULL, UU_LIST_POOL_DEBUG);
243	deps = uu_list_pool_create("deps", sizeof (struct dependency),
244	    offsetof(struct dependency, node), NULL, UU_LIST_POOL_DEBUG);
245	g_causes = uu_list_create(svcptrs, NULL, UU_LIST_DEBUG);
246	if (insts == NULL || svcptrs == NULL || depgroups == NULL ||
247	    deps == NULL || g_causes == NULL)
248		uu_die(emsg_nomem);
249
250	if ((g_local_scope = scf_scope_create(h)) == NULL ||
251	    (g_svc = scf_service_create(h)) == NULL ||
252	    (g_inst = scf_instance_create(h)) == NULL ||
253	    (g_snap = scf_snapshot_create(h)) == NULL ||
254	    (g_pg = scf_pg_create(h)) == NULL ||
255	    (g_prop = scf_property_create(h)) == NULL ||
256	    (g_val = scf_value_create(h)) == NULL ||
257	    (g_iter = scf_iter_create(h)) == NULL ||
258	    (g_viter = scf_iter_create(h)) == NULL)
259		scfdie();
260
261	if (scf_handle_get_scope(h, SCF_SCOPE_LOCAL, g_local_scope) != 0)
262		scfdie();
263
264	g_fmri_sz = max_scf_fmri_length + 1;
265	g_fmri = safe_malloc(g_fmri_sz);
266
267	g_value_sz = max_scf_value_length + 1;
268	g_value = safe_malloc(g_value_sz);
269}
270
271/*
272 * Repository loading routines.
273 */
274
275/*
276 * Returns
277 *   0 - success
278 *   ECANCELED - inst was deleted
279 *   EINVAL - inst is invalid
280 */
281static int
282load_dependencies(inst_t *svcp, scf_instance_t *inst)
283{
284	scf_snapshot_t *snap;
285	struct dependency_group *dg;
286	struct dependency *d;
287	int r;
288
289	assert(svcp->dependencies == NULL);
290	svcp->dependencies = uu_list_create(depgroups, svcp, UU_LIST_DEBUG);
291	if (svcp->dependencies == NULL)
292		uu_die(emsg_nomem);
293
294	if (scf_instance_get_snapshot(inst, "running", g_snap) == 0) {
295		snap = g_snap;
296	} else {
297		if (scf_error() != SCF_ERROR_NOT_FOUND)
298			scfdie();
299
300		snap = NULL;
301	}
302
303	if (scf_iter_instance_pgs_typed_composed(g_iter, inst, snap,
304	    SCF_GROUP_DEPENDENCY) != 0) {
305		if (scf_error() != SCF_ERROR_DELETED)
306			scfdie();
307		return (ECANCELED);
308	}
309
310	for (;;) {
311		r = scf_iter_next_pg(g_iter, g_pg);
312		if (r == 0)
313			break;
314		if (r != 1) {
315			if (scf_error() != SCF_ERROR_DELETED)
316				scfdie();
317			return (ECANCELED);
318		}
319
320		dg = safe_malloc(sizeof (*dg));
321		(void) memset(dg, 0, sizeof (*dg));
322		dg->entities = uu_list_create(deps, dg, UU_LIST_DEBUG);
323		if (dg->entities == NULL)
324			uu_die(emsg_nomem);
325
326		if (pg_get_single_val(g_pg, SCF_PROPERTY_GROUPING,
327		    SCF_TYPE_ASTRING, g_value, g_value_sz, 0) != 0)
328			return (EINVAL);
329
330		if (strcmp(g_value, "require_all") == 0)
331			dg->grouping = DGG_REQALL;
332		else if (strcmp(g_value, "require_any") == 0)
333			dg->grouping = DGG_REQANY;
334		else if (strcmp(g_value, "optional_all") == 0)
335			dg->grouping = DGG_OPTALL;
336		else if (strcmp(g_value, "exclude_all") == 0)
337			dg->grouping = DGG_EXCALL;
338		else {
339			(void) fprintf(stderr, gettext("svc:/%s:%s has "
340			    "dependency with unknown type \"%s\".\n"),
341			    svcp->svcname, svcp->instname, g_value);
342			return (EINVAL);
343		}
344
345		if (pg_get_single_val(g_pg, SCF_PROPERTY_TYPE, SCF_TYPE_ASTRING,
346		    g_value, g_value_sz, 0) != 0)
347			return (EINVAL);
348		dg->type = safe_strdup(g_value);
349
350		if (scf_pg_get_property(g_pg, SCF_PROPERTY_ENTITIES, g_prop) !=
351		    0) {
352			switch (scf_error()) {
353			case SCF_ERROR_NOT_FOUND:
354				(void) fprintf(stderr, gettext("svc:/%s:%s has "
355				    "dependency without an entities "
356				    "property.\n"), svcp->svcname,
357				    svcp->instname);
358				return (EINVAL);
359
360			case SCF_ERROR_DELETED:
361				return (ECANCELED);
362
363			default:
364				scfdie();
365			}
366		}
367
368		if (scf_iter_property_values(g_viter, g_prop) != 0) {
369			if (scf_error() != SCF_ERROR_DELETED)
370				scfdie();
371			return (ECANCELED);
372		}
373
374		for (;;) {
375			r = scf_iter_next_value(g_viter, g_val);
376			if (r == 0)
377				break;
378			if (r != 1) {
379				if (scf_error() != SCF_ERROR_DELETED)
380					scfdie();
381				return (ECANCELED);
382			}
383
384			d = safe_malloc(sizeof (*d));
385			d->fmri = safe_malloc(max_scf_fmri_length + 1);
386
387			if (scf_value_get_astring(g_val, (char *)d->fmri,
388			    max_scf_fmri_length + 1) < 0)
389				scfdie();
390
391			uu_list_node_init(d, &d->node, deps);
392			(void) uu_list_append(dg->entities, d);
393		}
394
395		uu_list_node_init(dg, &dg->node, depgroups);
396		r = uu_list_append(svcp->dependencies, dg);
397		assert(r == 0);
398	}
399
400	return (0);
401}
402
403static void
404add_instance(const char *svcname, const char *instname, scf_instance_t *inst)
405{
406	inst_t *instp;
407	svc_t *svcp;
408	int ovr_set = 0;
409	uint8_t i;
410	uint32_t h;
411	int r;
412
413	h = hash_name(svcname) & SVC_HASH_MASK;
414	for (svcp = services[h]; svcp != NULL; svcp = svcp->next) {
415		if (strcmp(svcp->svcname, svcname) == 0)
416			break;
417	}
418
419	if (svcp == NULL) {
420		svcp = safe_malloc(sizeof (*svcp));
421		svcp->svcname = safe_strdup(svcname);
422		svcp->instances = uu_list_create(insts, svcp, UU_LIST_DEBUG);
423		if (svcp->instances == NULL)
424			uu_die(emsg_nomem);
425		svcp->next = services[h];
426		services[h] = svcp;
427	}
428
429	instp = safe_malloc(sizeof (*instp));
430	(void) memset(instp, 0, sizeof (*instp));
431	instp->svcname = svcp->svcname;
432	instp->instname = safe_strdup(instname);
433	instp->impact_dependents =
434	    uu_list_create(svcptrs, instp, UU_LIST_DEBUG);
435	if (instp->impact_dependents == NULL)
436		uu_die(emsg_nomem);
437
438	if (scf_instance_get_pg(inst, SCF_PG_RESTARTER, g_pg) != 0) {
439		switch (scf_error()) {
440		case SCF_ERROR_DELETED:
441			return;
442
443		case SCF_ERROR_NOT_FOUND:
444			(void) fprintf(stderr, gettext("svc:/%s:%s has no "
445			    "\"%s\" property group; ignoring.\n"),
446			    instp->svcname, instp->instname, SCF_PG_RESTARTER);
447			return;
448
449		default:
450			scfdie();
451		}
452	}
453
454	if (pg_get_single_val(g_pg, SCF_PROPERTY_STATE, SCF_TYPE_ASTRING,
455	    (void *)instp->state, sizeof (instp->state), 0) != 0)
456		return;
457
458	if (pg_get_single_val(g_pg, SCF_PROPERTY_NEXT_STATE, SCF_TYPE_ASTRING,
459	    (void *)instp->next_state, sizeof (instp->next_state), 0) != 0)
460		return;
461
462	if (pg_get_single_val(g_pg, SCF_PROPERTY_STATE_TIMESTAMP,
463	    SCF_TYPE_TIME, &instp->stime, 0, 0) != 0)
464		return;
465
466	/* restarter may not set aux_state, allow to continue in that case */
467	if (pg_get_single_val(g_pg, SCF_PROPERTY_AUX_STATE, SCF_TYPE_ASTRING,
468	    g_fmri, g_fmri_sz, 0) == 0)
469		instp->aux_state = safe_strdup(g_fmri);
470	else
471		instp->aux_state = safe_strdup(AUX_STATE_INVALID);
472
473	(void) pg_get_single_val(g_pg, SCF_PROPERTY_START_METHOD_WAITSTATUS,
474	    SCF_TYPE_INTEGER, &instp->start_method_waitstatus, 0, 0);
475
476	/* Get the optional auxiliary_fmri */
477	if (pg_get_single_val(g_pg, SCF_PROPERTY_AUX_FMRI, SCF_TYPE_ASTRING,
478	    g_fmri, g_fmri_sz, 0) == 0)
479		instp->aux_fmri = safe_strdup(g_fmri);
480
481	if (scf_instance_get_pg(inst, SCF_PG_GENERAL_OVR, g_pg) == 0) {
482		if (pg_get_single_val(g_pg, SCF_PROPERTY_ENABLED,
483		    SCF_TYPE_BOOLEAN, &instp->enabled, 0, 0) == 0)
484			ovr_set = 1;
485		(void) pg_get_single_val(g_pg, SCF_PROPERTY_COMMENT,
486		    SCF_TYPE_ASTRING, instp->comment,
487		    sizeof (instp->comment), EMPTY_OK);
488	} else {
489		switch (scf_error()) {
490		case SCF_ERROR_NOT_FOUND:
491			break;
492
493		case SCF_ERROR_DELETED:
494			return;
495
496		default:
497			scfdie();
498		}
499	}
500
501	if (scf_instance_get_pg_composed(inst, NULL, SCF_PG_GENERAL, g_pg) !=
502	    0) {
503		switch (scf_error()) {
504		case SCF_ERROR_DELETED:
505		case SCF_ERROR_NOT_FOUND:
506			return;
507
508		default:
509			scfdie();
510		}
511	}
512
513	if (pg_get_single_val(g_pg, SCF_PROPERTY_ENABLED, SCF_TYPE_BOOLEAN,
514	    &i, 0, 0) != 0)
515		return;
516
517	if (ovr_set) {
518		instp->temporary = (instp->enabled != i);
519	} else {
520		instp->enabled = i;
521		instp->temporary = 0;
522	}
523
524	if (!instp->temporary) {
525		(void) pg_get_single_val(g_pg, SCF_PROPERTY_COMMENT,
526		    SCF_TYPE_ASTRING, instp->comment,
527		    sizeof (instp->comment), EMPTY_OK);
528	}
529
530	if (pg_get_single_val(g_pg, SCF_PROPERTY_RESTARTER, SCF_TYPE_ASTRING,
531	    g_fmri, g_fmri_sz, 0) == 0)
532		instp->restarter = safe_strdup(g_fmri);
533	else
534		instp->restarter = SCF_SERVICE_STARTD;
535
536	if (strcmp(instp->state, SCF_STATE_STRING_OFFLINE) == 0 &&
537	    load_dependencies(instp, inst) != 0)
538		return;
539
540	uu_list_node_init(instp, &instp->node, insts);
541	r = uu_list_append(svcp->instances, instp);
542	assert(r == 0);
543}
544
545static void
546load_services(void)
547{
548	scf_iter_t *siter, *iiter;
549	int r;
550	char *svcname, *instname;
551
552	if ((siter = scf_iter_create(h)) == NULL ||
553	    (iiter = scf_iter_create(h)) == NULL)
554		scfdie();
555
556	svcname = safe_malloc(max_scf_name_length + 1);
557	instname = safe_malloc(max_scf_name_length + 1);
558
559	if (scf_iter_scope_services(siter, g_local_scope) != 0)
560		scfdie();
561
562	for (;;) {
563		r = scf_iter_next_service(siter, g_svc);
564		if (r == 0)
565			break;
566		if (r != 1)
567			scfdie();
568
569		if (scf_service_get_name(g_svc, svcname,
570		    max_scf_name_length + 1) < 0) {
571			if (scf_error() != SCF_ERROR_DELETED)
572				scfdie();
573			continue;
574		}
575
576		if (scf_iter_service_instances(iiter, g_svc) != 0) {
577			if (scf_error() != SCF_ERROR_DELETED)
578				scfdie();
579			continue;
580		}
581
582		for (;;) {
583			r = scf_iter_next_instance(iiter, g_inst);
584			if (r == 0)
585				break;
586			if (r != 1) {
587				if (scf_error() != SCF_ERROR_DELETED)
588					scfdie();
589				break;
590			}
591
592			if (scf_instance_get_name(g_inst, instname,
593			    max_scf_name_length + 1) < 0) {
594				if (scf_error() != SCF_ERROR_DELETED)
595					scfdie();
596				continue;
597			}
598
599			add_instance(svcname, instname, g_inst);
600		}
601	}
602
603	free(svcname);
604	free(instname);
605	scf_iter_destroy(siter);
606	scf_iter_destroy(iiter);
607}
608
609/*
610 * Dependency analysis routines.
611 */
612
613static void
614add_svcptr(uu_list_t *lst, inst_t *svcp)
615{
616	struct svcptr *spp;
617	uu_list_index_t idx;
618	int r;
619
620	spp = safe_malloc(sizeof (*spp));
621	spp->svcp = svcp;
622	spp->next_hop = NULL;
623
624	if (uu_list_find(lst, spp, NULL, &idx) != NULL) {
625		free(spp);
626		return;
627	}
628
629	uu_list_node_init(spp, &spp->node, svcptrs);
630	r = uu_list_append(lst, spp);
631	assert(r == 0);
632}
633
634static int determine_causes(inst_t *, void *);
635
636/*
637 * Determine the causes of src and add them to the causes list of dst.
638 * Returns ELOOP if src is active, and 0 otherwise.
639 */
640static int
641add_causes(inst_t *dst, inst_t *src)
642{
643	struct svcptr *spp, *copy;
644	uu_list_index_t idx;
645
646	if (determine_causes(src, (void *)1) != UU_WALK_NEXT) {
647		/* Dependency cycle. */
648		(void) fprintf(stderr, "  svc:/%s:%s\n", dst->svcname,
649		    dst->instname);
650		return (ELOOP);
651	}
652
653	add_svcptr(src->impact_dependents, dst);
654
655	for (spp = uu_list_first(src->causes);
656	    spp != NULL;
657	    spp = uu_list_next(src->causes, spp)) {
658		if (uu_list_find(dst->causes, spp, NULL, &idx) != NULL)
659			continue;
660
661		copy = safe_malloc(sizeof (*copy));
662		copy->svcp = spp->svcp;
663		copy->next_hop = src;
664		uu_list_node_init(copy, &copy->node, svcptrs);
665		uu_list_insert(dst->causes, copy, idx);
666
667		add_svcptr(g_causes, spp->svcp);
668	}
669
670	return (0);
671}
672
673static int
674inst_running(inst_t *ip)
675{
676	return (strcmp(ip->state, SCF_STATE_STRING_ONLINE) == 0 ||
677	    strcmp(ip->state, SCF_STATE_STRING_DEGRADED) == 0);
678}
679
680static int
681inst_running_or_maint(inst_t *ip)
682{
683	return (inst_running(ip) ||
684	    strcmp(ip->state, SCF_STATE_STRING_MAINT) == 0);
685}
686
687static svc_t *
688get_svc(const char *sn)
689{
690	uint32_t h;
691	svc_t *svcp;
692
693	h = hash_name(sn) & SVC_HASH_MASK;
694
695	for (svcp = services[h]; svcp != NULL; svcp = svcp->next) {
696		if (strcmp(svcp->svcname, sn) == 0)
697			break;
698	}
699
700	return (svcp);
701}
702
703/* ARGSUSED */
704static inst_t *
705get_inst(svc_t *svcp, const char *in)
706{
707	inst_t *instp;
708
709	for (instp = uu_list_first(svcp->instances);
710	    instp != NULL;
711	    instp = uu_list_next(svcp->instances, instp)) {
712		if (strcmp(instp->instname, in) == 0)
713			return (instp);
714	}
715
716	return (NULL);
717}
718
719static int
720get_fmri(const char *fmri, svc_t **spp, inst_t **ipp)
721{
722	const char *sn, *in;
723	svc_t *sp;
724	inst_t *ip;
725
726	if (strlcpy(g_fmri, fmri, g_fmri_sz) >= g_fmri_sz)
727		return (EINVAL);
728
729	if (scf_parse_svc_fmri(g_fmri, NULL, &sn, &in, NULL, NULL) != 0)
730		return (EINVAL);
731
732	if (sn == NULL)
733		return (EINVAL);
734
735	sp = get_svc(sn);
736	if (sp == NULL)
737		return (ENOENT);
738
739	if (in != NULL) {
740		ip = get_inst(sp, in);
741		if (ip == NULL)
742			return (ENOENT);
743	}
744
745	if (spp != NULL)
746		*spp = sp;
747	if (ipp != NULL)
748		*ipp = ((in == NULL) ? NULL : ip);
749
750	return (0);
751}
752
753static int
754process_reqall(inst_t *svcp, struct dependency_group *dg)
755{
756	uu_list_walk_t *walk;
757	struct dependency *d;
758	int r, svcrunning;
759	svc_t *sp;
760	inst_t *ip;
761
762	walk = uu_list_walk_start(dg->entities, UU_WALK_ROBUST);
763	if (walk == NULL)
764		uu_die(emsg_nomem);
765
766	while ((d = uu_list_walk_next(walk)) != NULL) {
767		r = get_fmri(d->fmri, &sp, &ip);
768		switch (r) {
769		case EINVAL:
770			/* LINTED */
771			(void) fprintf(stderr, emsg_invalid_dep, svcp->svcname,
772			    svcp->instname, d->fmri);
773			continue;
774
775		case ENOENT:
776			uu_list_remove(dg->entities, d);
777			r = uu_list_append(svcp->baddeps, d);
778			assert(r == 0);
779			continue;
780
781		case 0:
782			break;
783
784		default:
785			bad_error("get_fmri", r);
786		}
787
788		if (ip != NULL) {
789			if (inst_running(ip))
790				continue;
791			r = add_causes(svcp, ip);
792			if (r != 0) {
793				assert(r == ELOOP);
794				return (r);
795			}
796			continue;
797		}
798
799		svcrunning = 0;
800
801		for (ip = uu_list_first(sp->instances);
802		    ip != NULL;
803		    ip = uu_list_next(sp->instances, ip)) {
804			if (inst_running(ip))
805				svcrunning = 1;
806		}
807
808		if (!svcrunning) {
809			for (ip = uu_list_first(sp->instances);
810			    ip != NULL;
811			    ip = uu_list_next(sp->instances, ip)) {
812				r = add_causes(svcp, ip);
813				if (r != 0) {
814					assert(r == ELOOP);
815					uu_list_walk_end(walk);
816					return (r);
817				}
818			}
819		}
820	}
821
822	uu_list_walk_end(walk);
823	return (0);
824}
825
826static int
827process_reqany(inst_t *svcp, struct dependency_group *dg)
828{
829	svc_t *sp;
830	inst_t *ip;
831	struct dependency *d;
832	int r;
833	uu_list_walk_t *walk;
834
835	for (d = uu_list_first(dg->entities);
836	    d != NULL;
837	    d = uu_list_next(dg->entities, d)) {
838		r = get_fmri(d->fmri, &sp, &ip);
839		switch (r) {
840		case 0:
841			break;
842
843		case EINVAL:
844			/* LINTED */
845			(void) fprintf(stderr, emsg_invalid_dep, svcp->svcname,
846			    svcp->instname, d->fmri);
847			continue;
848
849		case ENOENT:
850			continue;
851
852		default:
853			bad_error("eval_svc_dep", r);
854		}
855
856		if (ip != NULL) {
857			if (inst_running(ip))
858				return (0);
859			continue;
860		}
861
862		for (ip = uu_list_first(sp->instances);
863		    ip != NULL;
864		    ip = uu_list_next(sp->instances, ip)) {
865			if (inst_running(ip))
866				return (0);
867		}
868	}
869
870	/*
871	 * The dependency group is not satisfied.  Add all unsatisfied members
872	 * to the cause list.
873	 */
874
875	walk = uu_list_walk_start(dg->entities, UU_WALK_ROBUST);
876	if (walk == NULL)
877		uu_die(emsg_nomem);
878
879	while ((d = uu_list_walk_next(walk)) != NULL) {
880		r = get_fmri(d->fmri, &sp, &ip);
881		switch (r) {
882		case 0:
883			break;
884
885		case ENOENT:
886			uu_list_remove(dg->entities, d);
887			r = uu_list_append(svcp->baddeps, d);
888			assert(r == 0);
889			continue;
890
891		case EINVAL:
892			/* Should have caught above. */
893		default:
894			bad_error("eval_svc_dep", r);
895		}
896
897		if (ip != NULL) {
898			if (inst_running(ip))
899				continue;
900			r = add_causes(svcp, ip);
901			if (r != 0) {
902				assert(r == ELOOP);
903				return (r);
904			}
905			continue;
906		}
907
908		for (ip = uu_list_first(sp->instances);
909		    ip != NULL;
910		    ip = uu_list_next(sp->instances, ip)) {
911			if (inst_running(ip))
912				continue;
913			r = add_causes(svcp, ip);
914			if (r != 0) {
915				assert(r == ELOOP);
916				return (r);
917			}
918		}
919	}
920
921	return (0);
922}
923
924static int
925process_optall(inst_t *svcp, struct dependency_group *dg)
926{
927	uu_list_walk_t *walk;
928	struct dependency *d;
929	int r;
930	inst_t *ip;
931	svc_t *sp;
932
933	walk = uu_list_walk_start(dg->entities, UU_WALK_ROBUST);
934	if (walk == NULL)
935		uu_die(emsg_nomem);
936
937	while ((d = uu_list_walk_next(walk)) != NULL) {
938		r = get_fmri(d->fmri, &sp, &ip);
939
940		switch (r) {
941		case 0:
942			break;
943
944		case EINVAL:
945			/* LINTED */
946			(void) fprintf(stderr, emsg_invalid_dep, svcp->svcname,
947			    svcp->instname, d->fmri);
948			continue;
949
950		case ENOENT:
951			continue;
952
953		default:
954			bad_error("get_fmri", r);
955		}
956
957		if (ip != NULL) {
958			if ((ip->enabled != 0) && !inst_running_or_maint(ip)) {
959				r = add_causes(svcp, ip);
960				if (r != 0) {
961					assert(r == ELOOP);
962					uu_list_walk_end(walk);
963					return (r);
964				}
965			}
966			continue;
967		}
968
969		for (ip = uu_list_first(sp->instances);
970		    ip != NULL;
971		    ip = uu_list_next(sp->instances, ip)) {
972			if ((ip->enabled != 0) && !inst_running_or_maint(ip)) {
973				r = add_causes(svcp, ip);
974				if (r != 0) {
975					assert(r == ELOOP);
976					uu_list_walk_end(walk);
977					return (r);
978				}
979			}
980		}
981	}
982
983	uu_list_walk_end(walk);
984	return (0);
985}
986
987static int
988process_excall(inst_t *svcp, struct dependency_group *dg)
989{
990	struct dependency *d;
991	int r;
992	svc_t *sp;
993	inst_t *ip;
994
995	for (d = uu_list_first(dg->entities);
996	    d != NULL;
997	    d = uu_list_next(dg->entities, d)) {
998		r = get_fmri(d->fmri, &sp, &ip);
999
1000		switch (r) {
1001		case 0:
1002			break;
1003
1004		case EINVAL:
1005			/* LINTED */
1006			(void) fprintf(stderr, emsg_invalid_dep, svcp->svcname,
1007			    svcp->instname, d->fmri);
1008			continue;
1009
1010		case ENOENT:
1011			continue;
1012
1013		default:
1014			bad_error("eval_svc_dep", r);
1015		}
1016
1017		if (ip != NULL) {
1018			if (inst_running(ip)) {
1019				r = add_causes(svcp, ip);
1020				if (r != 0) {
1021					assert(r == ELOOP);
1022					return (r);
1023				}
1024			}
1025			continue;
1026		}
1027
1028		for (ip = uu_list_first(sp->instances);
1029		    ip != NULL;
1030		    ip = uu_list_next(sp->instances, ip)) {
1031			if (inst_running(ip)) {
1032				r = add_causes(svcp, ip);
1033				if (r != 0) {
1034					assert(r == ELOOP);
1035					return (r);
1036				}
1037			}
1038		}
1039	}
1040
1041	return (0);
1042}
1043
1044static int
1045process_svc_dg(inst_t *svcp, struct dependency_group *dg)
1046{
1047	switch (dg->grouping) {
1048	case DGG_REQALL:
1049		return (process_reqall(svcp, dg));
1050
1051	case DGG_REQANY:
1052		return (process_reqany(svcp, dg));
1053
1054	case DGG_OPTALL:
1055		return (process_optall(svcp, dg));
1056
1057	case DGG_EXCALL:
1058		return (process_excall(svcp, dg));
1059
1060	default:
1061#ifndef NDEBUG
1062		(void) fprintf(stderr,
1063		    "%s:%d: Unknown dependency grouping %d.\n", __FILE__,
1064		    __LINE__, dg->grouping);
1065#endif
1066		abort();
1067		/* NOTREACHED */
1068	}
1069}
1070
1071/*
1072 * Returns
1073 *   EINVAL - fmri is not a valid FMRI
1074 *   0 - the file indicated by fmri is missing
1075 *   1 - the file indicated by fmri is present
1076 */
1077static int
1078eval_file_dep(const char *fmri)
1079{
1080	const char *path;
1081	struct stat st;
1082
1083	if (strncmp(fmri, "file:", sizeof ("file:") - 1) != 0)
1084		return (EINVAL);
1085
1086	path = fmri + (sizeof ("file:") - 1);
1087
1088	if (path[0] != '/')
1089		return (EINVAL);
1090
1091	if (path[1] == '/') {
1092		path += 2;
1093		if (strncmp(path, "localhost/", sizeof ("localhost/") - 1) == 0)
1094			path += sizeof ("localhost") - 1;
1095		else if (path[0] != '/')
1096			return (EINVAL);
1097	}
1098
1099	return (stat(path, &st) == 0 ? 1 : 0);
1100}
1101
1102static void
1103process_file_dg(inst_t *svcp, struct dependency_group *dg)
1104{
1105	uu_list_walk_t *walk;
1106	struct dependency *d, **deps;
1107	int r, i = 0, any_satisfied = 0;
1108
1109	if (dg->grouping == DGG_REQANY) {
1110		deps = calloc(uu_list_numnodes(dg->entities), sizeof (*deps));
1111		if (deps == NULL)
1112			uu_die(emsg_nomem);
1113	}
1114
1115	walk = uu_list_walk_start(dg->entities, UU_WALK_ROBUST);
1116	if (walk == NULL)
1117		uu_die(emsg_nomem);
1118
1119	while ((d = uu_list_walk_next(walk)) != NULL) {
1120		r = eval_file_dep(d->fmri);
1121		if (r == EINVAL) {
1122			/* LINTED */
1123			(void) fprintf(stderr, emsg_invalid_dep, svcp->svcname,
1124			    svcp->instname, d->fmri);
1125			continue;
1126		}
1127
1128		assert(r == 0 || r == 1);
1129
1130		switch (dg->grouping) {
1131		case DGG_REQALL:
1132		case DGG_OPTALL:
1133			if (r == 0) {
1134				uu_list_remove(dg->entities, d);
1135				r = uu_list_append(svcp->baddeps, d);
1136				assert(r == 0);
1137			}
1138			break;
1139
1140		case DGG_REQANY:
1141			if (r == 1)
1142				any_satisfied = 1;
1143			else
1144				deps[i++] = d;
1145			break;
1146
1147		case DGG_EXCALL:
1148			if (r == 1) {
1149				uu_list_remove(dg->entities, d);
1150				r = uu_list_append(svcp->baddeps, d);
1151				assert(r == 0);
1152			}
1153			break;
1154
1155		default:
1156#ifndef NDEBUG
1157			(void) fprintf(stderr, "%s:%d: Unknown grouping %d.\n",
1158			    __FILE__, __LINE__, dg->grouping);
1159#endif
1160			abort();
1161		}
1162	}
1163
1164	uu_list_walk_end(walk);
1165
1166	if (dg->grouping != DGG_REQANY)
1167		return;
1168
1169	if (!any_satisfied) {
1170		while (--i >= 0) {
1171			uu_list_remove(dg->entities, deps[i]);
1172			r = uu_list_append(svcp->baddeps, deps[i]);
1173			assert(r == 0);
1174		}
1175	}
1176
1177	free(deps);
1178}
1179
1180/*
1181 * Populate the causes list of svcp.  This function should not return with
1182 * causes empty.
1183 */
1184static int
1185determine_causes(inst_t *svcp, void *canfailp)
1186{
1187	struct dependency_group *dg;
1188	int r;
1189
1190	if (svcp->active) {
1191		(void) fprintf(stderr, gettext("Dependency cycle detected:\n"
1192		    "  svc:/%s:%s\n"), svcp->svcname, svcp->instname);
1193		return ((int)canfailp != 0 ? UU_WALK_ERROR : UU_WALK_NEXT);
1194	}
1195
1196	if (svcp->causes != NULL)
1197		return (UU_WALK_NEXT);
1198
1199	svcp->causes = uu_list_create(svcptrs, svcp, UU_LIST_DEBUG);
1200	svcp->baddeps = uu_list_create(deps, svcp, UU_LIST_DEBUG);
1201	if (svcp->causes == NULL || svcp->baddeps == NULL)
1202		uu_die(emsg_nomem);
1203
1204	if (inst_running(svcp) ||
1205	    strcmp(svcp->state, SCF_STATE_STRING_UNINIT) == 0) {
1206		/*
1207		 * If we're running, add a self-pointer in case we're
1208		 * excluding another service.
1209		 */
1210		add_svcptr(svcp->causes, svcp);
1211		return (UU_WALK_NEXT);
1212	}
1213
1214	if (strcmp(svcp->state, SCF_STATE_STRING_MAINT) == 0) {
1215		add_svcptr(svcp->causes, svcp);
1216		add_svcptr(g_causes, svcp);
1217		return (UU_WALK_NEXT);
1218	}
1219
1220	if (strcmp(svcp->state, SCF_STATE_STRING_DISABLED) == 0) {
1221		add_svcptr(svcp->causes, svcp);
1222		if (svcp->enabled != 0)
1223			add_svcptr(g_causes, svcp);
1224
1225		return (UU_WALK_NEXT);
1226	}
1227
1228	if (strcmp(svcp->state, SCF_STATE_STRING_OFFLINE) != 0) {
1229		(void) fprintf(stderr,
1230		    gettext("svc:/%s:%s has invalid state \"%s\".\n"),
1231		    svcp->svcname, svcp->instname, svcp->state);
1232		add_svcptr(svcp->causes, svcp);
1233		add_svcptr(g_causes, svcp);
1234		return (UU_WALK_NEXT);
1235	}
1236
1237	if (strcmp(svcp->next_state, SCF_STATE_STRING_NONE) != 0) {
1238		add_svcptr(svcp->causes, svcp);
1239		add_svcptr(g_causes, svcp);
1240		return (UU_WALK_NEXT);
1241	}
1242
1243	svcp->active = 1;
1244
1245	/*
1246	 * Dependency analysis can add elements to our baddeps list (absent
1247	 * dependency, unsatisfied file dependency), or to our cause list
1248	 * (unsatisfied dependency).
1249	 */
1250	for (dg = uu_list_first(svcp->dependencies);
1251	    dg != NULL;
1252	    dg = uu_list_next(svcp->dependencies, dg)) {
1253		if (strcmp(dg->type, "path") == 0) {
1254			process_file_dg(svcp, dg);
1255		} else if (strcmp(dg->type, "service") == 0) {
1256			int r;
1257
1258			r = process_svc_dg(svcp, dg);
1259			if (r != 0) {
1260				assert(r == ELOOP);
1261				svcp->active = 0;
1262				return ((int)canfailp != 0 ?
1263				    UU_WALK_ERROR : UU_WALK_NEXT);
1264			}
1265		} else {
1266			(void) fprintf(stderr, gettext("svc:/%s:%s has "
1267			    "dependency group with invalid type \"%s\".\n"),
1268			    svcp->svcname, svcp->instname, dg->type);
1269		}
1270	}
1271
1272	if (uu_list_numnodes(svcp->causes) == 0) {
1273		if (uu_list_numnodes(svcp->baddeps) > 0) {
1274			add_svcptr(g_causes, svcp);
1275			add_svcptr(svcp->causes, svcp);
1276		} else {
1277			inst_t *restarter;
1278
1279			r = get_fmri(svcp->restarter, NULL, &restarter);
1280			if (r == 0 && !inst_running(restarter)) {
1281				r = add_causes(svcp, restarter);
1282				if (r != 0) {
1283					assert(r == ELOOP);
1284					svcp->active = 0;
1285					return ((int)canfailp != 0 ?
1286					    UU_WALK_ERROR : UU_WALK_NEXT);
1287				}
1288			} else {
1289				svcp->restarter_bad = r;
1290				add_svcptr(svcp->causes, svcp);
1291				add_svcptr(g_causes, svcp);
1292			}
1293		}
1294	}
1295
1296	assert(uu_list_numnodes(svcp->causes) > 0);
1297
1298	svcp->active = 0;
1299	return (UU_WALK_NEXT);
1300}
1301
1302static void
1303determine_all_causes(void)
1304{
1305	svc_t *svcp;
1306	int i;
1307
1308	for (i = 0; i < SVC_HASH_NBUCKETS; ++i) {
1309		for (svcp = services[i]; svcp != NULL; svcp = svcp->next)
1310			(void) uu_list_walk(svcp->instances,
1311			    (uu_walk_fn_t *)determine_causes, 0, 0);
1312	}
1313}
1314
1315/*
1316 * Returns
1317 *   0 - success
1318 *   ELOOP - dependency cycle detected
1319 */
1320static int
1321determine_impact(inst_t *ip)
1322{
1323	struct svcptr *idsp, *spp, *copy;
1324	uu_list_index_t idx;
1325
1326	if (ip->active) {
1327		(void) fprintf(stderr, gettext("Dependency cycle detected:\n"
1328		    "  svc:/%s:%s\n"), ip->svcname, ip->instname);
1329		return (ELOOP);
1330	}
1331
1332	if (ip->impact != NULL)
1333		return (0);
1334
1335	ip->impact = uu_list_create(svcptrs, ip, UU_LIST_DEBUG);
1336	if (ip->impact == NULL)
1337		uu_die(emsg_nomem);
1338	ip->active = 1;
1339
1340	for (idsp = uu_list_first(ip->impact_dependents);
1341	    idsp != NULL;
1342	    idsp = uu_list_next(ip->impact_dependents, idsp)) {
1343		if (determine_impact(idsp->svcp) != 0) {
1344			(void) fprintf(stderr, "  svc:/%s:%s\n",
1345			    ip->svcname, ip->instname);
1346			return (ELOOP);
1347		}
1348
1349		add_svcptr(ip->impact, idsp->svcp);
1350
1351		for (spp = uu_list_first(idsp->svcp->impact);
1352		    spp != NULL;
1353		    spp = uu_list_next(idsp->svcp->impact, spp)) {
1354			if (uu_list_find(ip->impact, spp, NULL, &idx) != NULL)
1355				continue;
1356
1357			copy = safe_malloc(sizeof (*copy));
1358			copy->svcp = spp->svcp;
1359			copy->next_hop = NULL;
1360			uu_list_node_init(copy, &copy->node, svcptrs);
1361			uu_list_insert(ip->impact, copy, idx);
1362		}
1363	}
1364
1365	ip->active = 0;
1366	return (0);
1367}
1368
1369/*
1370 * Printing routines.
1371 */
1372
1373static void
1374check_msgbase(void)
1375{
1376	if (scf_handle_decode_fmri(h, SCF_SERVICE_STARTD, NULL, NULL, g_inst,
1377	    NULL, NULL, SCF_DECODE_FMRI_EXACT) != 0) {
1378		if (scf_error() != SCF_ERROR_NOT_FOUND)
1379			scfdie();
1380
1381		return;
1382	}
1383
1384	if (scf_instance_get_pg_composed(g_inst, NULL, "msg", g_pg) != 0) {
1385		switch (scf_error()) {
1386		case SCF_ERROR_NOT_FOUND:
1387		case SCF_ERROR_DELETED:
1388			return;
1389
1390		default:
1391			scfdie();
1392		}
1393	}
1394
1395	if (scf_pg_get_property(g_pg, "base", g_prop) != 0) {
1396		switch (scf_error()) {
1397		case SCF_ERROR_NOT_FOUND:
1398		case SCF_ERROR_DELETED:
1399			return;
1400
1401		default:
1402			scfdie();
1403		}
1404	}
1405
1406	if (scf_property_get_value(g_prop, g_val) != 0) {
1407		switch (scf_error()) {
1408		case SCF_ERROR_NOT_FOUND:
1409		case SCF_ERROR_CONSTRAINT_VIOLATED:
1410		case SCF_ERROR_PERMISSION_DENIED:
1411			g_msgbase = NULL;
1412			return;
1413
1414		case SCF_ERROR_DELETED:
1415			return;
1416
1417		default:
1418			scfdie();
1419		}
1420	}
1421
1422	if (scf_value_get_astring(g_val, g_value, g_value_sz) < 0) {
1423		if (scf_error() != SCF_ERROR_TYPE_MISMATCH)
1424			scfdie();
1425		return;
1426	}
1427
1428	g_msgbase = safe_strdup(g_value);
1429}
1430
1431static void
1432determine_summary(inst_t *ip)
1433{
1434	if (ip->summary != NULL)
1435		return;
1436
1437	if (inst_running(ip)) {
1438		ip->summary = gettext("is running.");
1439		return;
1440	}
1441
1442	if (strcmp(ip->state, SCF_STATE_STRING_UNINIT) == 0) {
1443		ip->summary = gettext("is uninitialized.");
1444	} else if (strcmp(ip->state, SCF_STATE_STRING_DISABLED) == 0) {
1445		if (!ip->temporary)
1446			ip->summary = gettext("is disabled.");
1447		else
1448			ip->summary = gettext("is temporarily disabled.");
1449	} else if (strcmp(ip->state, SCF_STATE_STRING_OFFLINE) == 0) {
1450		if (uu_list_numnodes(ip->baddeps) != 0)
1451			ip->summary = gettext("has missing dependencies.");
1452		else if (strcmp(ip->next_state, SCF_STATE_STRING_ONLINE) == 0)
1453			ip->summary = gettext("is starting.");
1454		else
1455			ip->summary = gettext("is offline.");
1456	} else if (strcmp(ip->state, SCF_STATE_STRING_MAINT) == 0) {
1457		if (strcmp(ip->aux_state, "administrative_request") == 0) {
1458			ip->summary = gettext("was taken down for maintenace "
1459			    "by an administrator.");
1460		} else if (strcmp(ip->aux_state, "dependency_cycle") == 0) {
1461			ip->summary = gettext("completed a dependency cycle.");
1462		} else if (strcmp(ip->aux_state, "fault_threshold_reached") ==
1463		    0) {
1464			ip->summary = gettext("is not running because "
1465			    "a method failed repeatedly.");
1466		} else if (strcmp(ip->aux_state, "invalid_dependency") == 0) {
1467			ip->summary = gettext("has an invalid dependency.");
1468		} else if (strcmp(ip->aux_state, "invalid_restarter") == 0) {
1469			ip->summary = gettext("has an invalid restarter.");
1470		} else if (strcmp(ip->aux_state, "method_failed") == 0) {
1471			ip->summary = gettext("is not running because "
1472			    "a method failed.");
1473		} else if (strcmp(ip->aux_state, "none") == 0) {
1474			ip->summary =
1475			    gettext("is not running for an unknown reason.");
1476		} else if (strcmp(ip->aux_state, "restarting_too_quickly") ==
1477		    0) {
1478			ip->summary = gettext("was restarting too quickly.");
1479		} else {
1480			ip->summary = gettext("requires maintenance.");
1481		}
1482	} else {
1483		ip->summary = gettext("is in an invalid state.");
1484	}
1485}
1486
1487static void
1488print_method_failure(const inst_t *ip, const char **dcp)
1489{
1490	char buf[50];
1491	int stat = ip->start_method_waitstatus;
1492
1493	if (stat != 0) {
1494		if (WIFEXITED(stat)) {
1495			if (WEXITSTATUS(stat) == SMF_EXIT_ERR_CONFIG) {
1496				(void) strlcpy(buf, gettext(
1497				    "exited with $SMF_EXIT_ERR_CONFIG"),
1498				    sizeof (buf));
1499			} else if (WEXITSTATUS(stat) == SMF_EXIT_ERR_FATAL) {
1500				(void) strlcpy(buf, gettext(
1501				    "exited with $SMF_EXIT_ERR_FATAL"),
1502				    sizeof (buf));
1503			} else {
1504				(void) snprintf(buf, sizeof (buf),
1505				    gettext("exited with status %d"),
1506				    WEXITSTATUS(stat));
1507			}
1508		} else if (WIFSIGNALED(stat)) {
1509			if (WCOREDUMP(stat)) {
1510				if (strsignal(WTERMSIG(stat)) != NULL)
1511					(void) snprintf(buf, sizeof (buf),
1512					    gettext("dumped core on %s (%d)"),
1513					    strsignal(WTERMSIG(stat)),
1514					    WTERMSIG(stat));
1515				else
1516					(void) snprintf(buf, sizeof (buf),
1517					    gettext("dumped core signal %d"),
1518					    WTERMSIG(stat));
1519			} else {
1520				if (strsignal(WTERMSIG(stat)) != NULL) {
1521					(void) snprintf(buf, sizeof (buf),
1522					    gettext("died on %s (%d)"),
1523					    strsignal(WTERMSIG(stat)),
1524					    WTERMSIG(stat));
1525				} else {
1526					(void) snprintf(buf, sizeof (buf),
1527					    gettext("died on signal %d"),
1528					    WTERMSIG(stat));
1529				}
1530			}
1531		} else {
1532			goto fail;
1533		}
1534
1535		if (strcmp(ip->aux_state, "fault_threshold_reached") != 0)
1536			(void) printf(gettext("Reason: Start method %s.\n"),
1537			    buf);
1538		else
1539			(void) printf(gettext("Reason: "
1540			    "Start method failed repeatedly, last %s.\n"), buf);
1541		*dcp = DC_STARTFAIL;
1542	} else {
1543fail:
1544		if (strcmp(ip->aux_state, "fault_threshold_reached") == 0)
1545			(void) puts(gettext(
1546			    "Reason: Method failed repeatedly."));
1547		else
1548			(void) puts(gettext("Reason: Method failed."));
1549		*dcp = DC_METHFAIL;
1550	}
1551}
1552
1553static void
1554print_dependency_reasons(const inst_t *svcp, int verbose)
1555{
1556	struct dependency *d;
1557	struct svcptr *spp;
1558	const char *dc;
1559
1560	/*
1561	 * If we couldn't determine why the service is offline, then baddeps
1562	 * will be empty and causes will have a pointer to self.
1563	 */
1564	if (uu_list_numnodes(svcp->baddeps) == 0 &&
1565	    uu_list_numnodes(svcp->causes) == 1) {
1566		spp = uu_list_first(svcp->causes);
1567		if (spp->svcp == svcp) {
1568			switch (svcp->restarter_bad) {
1569			case 0:
1570				(void) puts(gettext("Reason: Unknown."));
1571				dc = DC_UNKNOWN;
1572				break;
1573
1574			case EINVAL:
1575				(void) printf(gettext("Reason: "
1576				    "Restarter \"%s\" is invalid.\n"),
1577				    svcp->restarter);
1578				dc = DC_RSTRINVALID;
1579				break;
1580
1581			case ENOENT:
1582				(void) printf(gettext("Reason: "
1583				    "Restarter \"%s\" does not exist.\n"),
1584				    svcp->restarter);
1585				dc = DC_RSTRABSENT;
1586				break;
1587
1588			default:
1589#ifndef NDEBUG
1590				(void) fprintf(stderr, "%s:%d: Bad "
1591				    "restarter_bad value %d.  Aborting.\n",
1592				    __FILE__, __LINE__, svcp->restarter_bad);
1593#endif
1594				abort();
1595			}
1596
1597			if (g_msgbase)
1598				(void) printf(gettext("   See: %s%s\n"),
1599				    g_msgbase, dc);
1600			return;
1601		}
1602	}
1603
1604	for (d = uu_list_first(svcp->baddeps);
1605	    d != NULL;
1606	    d = uu_list_next(svcp->baddeps, d)) {
1607		(void) printf(gettext("Reason: Dependency %s is absent.\n"),
1608		    d->fmri);
1609		if (g_msgbase)
1610			(void) printf(gettext("   See: %s%s\n"), g_msgbase,
1611			    DC_DEPABSENT);
1612	}
1613
1614	for (spp = uu_list_first(svcp->causes);
1615	    spp != NULL && spp->svcp != svcp;
1616	    spp = uu_list_next(svcp->causes, spp)) {
1617		determine_summary(spp->svcp);
1618
1619		if (inst_running(spp->svcp)) {
1620			(void) printf(gettext("Reason: "
1621			    "Service svc:/%s:%s is running.\n"),
1622			    spp->svcp->svcname, spp->svcp->instname);
1623			dc = DC_DEPRUNNING;
1624		} else {
1625			if (snprintf(NULL, 0,
1626			    gettext("Reason: Service svc:/%s:%s %s"),
1627			    spp->svcp->svcname, spp->svcp->instname,
1628			    spp->svcp->summary) <= 80) {
1629				(void) printf(gettext(
1630				    "Reason: Service svc:/%s:%s %s\n"),
1631				    spp->svcp->svcname, spp->svcp->instname,
1632				    spp->svcp->summary);
1633			} else {
1634				(void) printf(gettext(
1635				    "Reason: Service svc:/%s:%s\n"
1636				    "        %s\n"), spp->svcp->svcname,
1637				    spp->svcp->instname, spp->svcp->summary);
1638			}
1639
1640			dc = DC_DEPOTHER;
1641		}
1642
1643		if (g_msgbase != NULL)
1644			(void) printf(gettext("   See: %s%s\n"), g_msgbase, dc);
1645
1646		if (verbose) {
1647			inst_t *pp;
1648			int indent;
1649
1650			(void) printf(gettext("  Path: svc:/%s:%s\n"),
1651			    svcp->svcname, svcp->instname);
1652
1653			indent = 1;
1654			for (pp = spp->next_hop; ; ) {
1655				struct svcptr *tmp;
1656
1657				(void) printf(gettext("%6s  %*ssvc:/%s:%s\n"),
1658				    "", indent++ * 2, "", pp->svcname,
1659				    pp->instname);
1660
1661				if (pp == spp->svcp)
1662					break;
1663
1664				/* set pp to next_hop of cause with same svcp */
1665				tmp = uu_list_find(pp->causes, spp, NULL, NULL);
1666				pp = tmp->next_hop;
1667			}
1668		}
1669	}
1670}
1671
1672static void
1673print_logs(scf_instance_t *inst)
1674{
1675	if (scf_instance_get_pg(inst, SCF_PG_RESTARTER, g_pg) != 0)
1676		return;
1677
1678	if (pg_get_single_val(g_pg, SCF_PROPERTY_ALT_LOGFILE,
1679	    SCF_TYPE_ASTRING, (void *)g_value, g_value_sz, 0) == 0)
1680		(void) printf(gettext("   See: %s\n"), g_value);
1681
1682	if (pg_get_single_val(g_pg, SCF_PROPERTY_LOGFILE,
1683	    SCF_TYPE_ASTRING, (void *)g_value, g_value_sz, 0) == 0)
1684		(void) printf(gettext("   See: %s\n"), g_value);
1685}
1686
1687static void
1688print_aux_fmri_logs(const char *fmri)
1689{
1690	scf_instance_t *scf_inst = scf_instance_create(h);
1691	if (scf_inst == NULL)
1692		return;
1693
1694	if (scf_handle_decode_fmri(h, fmri, NULL, NULL, scf_inst,
1695	    NULL, NULL, SCF_DECODE_FMRI_EXACT) == 0)
1696		print_logs(scf_inst);
1697
1698	scf_instance_destroy(scf_inst);
1699}
1700
1701static void
1702print_reasons(const inst_t *svcp, int verbose)
1703{
1704	int r;
1705	const char *dc = NULL;
1706
1707	if (strcmp(svcp->state, SCF_STATE_STRING_ONLINE) == 0)
1708		return;
1709
1710	if (strcmp(svcp->state, SCF_STATE_STRING_UNINIT) == 0) {
1711		inst_t *rsp;
1712
1713		r = get_fmri(svcp->restarter, NULL, &rsp);
1714		switch (r) {
1715		case 0:
1716			if (rsp != NULL)
1717				break;
1718			/* FALLTHROUGH */
1719
1720		case EINVAL:
1721			(void) printf(gettext("Reason: "
1722			    "Restarter \"%s\" is invalid.\n"), svcp->restarter);
1723			dc = DC_RSTRINVALID;
1724			goto diagcode;
1725
1726		case ENOENT:
1727			(void) printf(gettext("Reason: "
1728			    "Restarter \"%s\" does not exist.\n"),
1729			    svcp->restarter);
1730			dc = DC_RSTRABSENT;
1731			goto diagcode;
1732
1733		default:
1734			bad_error("get_fmri", r);
1735		}
1736
1737		if (inst_running(rsp)) {
1738			(void) printf(gettext("Reason: Restarter %s "
1739			    "has not initialized service state.\n"),
1740			    svcp->restarter);
1741			dc = DC_UNINIT;
1742		} else {
1743			(void) printf(gettext(
1744			    "Reason: Restarter %s is not running.\n"),
1745			    svcp->restarter);
1746			dc = DC_RSTRDEAD;
1747		}
1748
1749	} else if (strcmp(svcp->state, SCF_STATE_STRING_DISABLED) == 0) {
1750		if (!svcp->temporary) {
1751			if (svcp->comment[0] != '\0') {
1752				(void) printf(gettext("Reason: Disabled by "
1753				    "an administrator: %s\n"), svcp->comment);
1754			} else {
1755				(void) printf(gettext("Reason: Disabled by "
1756				    "an administrator.\n"));
1757			}
1758			dc = DC_DISABLED;
1759		} else {
1760			if (svcp->comment[0] != '\0') {
1761				(void) printf(gettext("Reason: Temporarily "
1762				    "disabled by an administrator: %s\n"),
1763				    svcp->comment);
1764			} else {
1765				(void) printf(gettext("Reason: Temporarily "
1766				    "disabled by an administrator.\n"));
1767			}
1768			dc = DC_TEMPDISABLED;
1769		}
1770
1771	} else if (strcmp(svcp->state, SCF_STATE_STRING_MAINT) == 0) {
1772		if (strcmp(svcp->aux_state, "administrative_request") == 0) {
1773			(void) puts(gettext("Reason: "
1774			    "Maintenance requested by an administrator."));
1775			dc = DC_ADMINMAINT;
1776		} else if (strcmp(svcp->aux_state, "dependency_cycle") == 0) {
1777			(void) puts(gettext(
1778			    "Reason: Completes a dependency cycle."));
1779			dc = DC_DEPCYCLE;
1780		} else if (strcmp(svcp->aux_state, "fault_threshold_reached") ==
1781		    0) {
1782			print_method_failure(svcp, &dc);
1783		} else if (strcmp(svcp->aux_state, "service_request") == 0) {
1784			if (svcp->aux_fmri) {
1785				(void) printf(gettext("Reason: Maintenance "
1786				    "requested by \"%s\"\n"), svcp->aux_fmri);
1787				print_aux_fmri_logs(svcp->aux_fmri);
1788			} else {
1789				(void) puts(gettext("Reason: Maintenance "
1790				    "requested by another service."));
1791			}
1792			dc = DC_SVCREQMAINT;
1793		} else if (strcmp(svcp->aux_state, "invalid_dependency") == 0) {
1794			(void) puts(gettext("Reason: Has invalid dependency."));
1795			dc = DC_INVALIDDEP;
1796		} else if (strcmp(svcp->aux_state, "invalid_restarter") == 0) {
1797			(void) printf(gettext("Reason: Restarter \"%s\" is "
1798			    "invalid.\n"), svcp->restarter);
1799			dc = DC_RSTRINVALID;
1800		} else if (strcmp(svcp->aux_state, "method_failed") == 0) {
1801			print_method_failure(svcp, &dc);
1802		} else if (strcmp(svcp->aux_state, "restarting_too_quickly") ==
1803		    0) {
1804			(void) puts(gettext("Reason: Restarting too quickly."));
1805			dc = DC_TOOQUICKLY;
1806		} else if (strcmp(svcp->aux_state, "none") == 0) {
1807			(void) printf(gettext(
1808			    "Reason: Restarter %s gave no explanation.\n"),
1809			    svcp->restarter);
1810			dc = DC_NONE;
1811		} else {
1812			(void) puts(gettext("Reason: Unknown."));
1813			dc = DC_UNKNOWN;
1814		}
1815
1816	} else if (strcmp(svcp->state, SCF_STATE_STRING_OFFLINE) == 0) {
1817		if (strcmp(svcp->next_state, SCF_STATE_STRING_ONLINE) == 0) {
1818			(void) puts(gettext(
1819			    "Reason: Start method is running."));
1820			dc = DC_STARTING;
1821		} else if (strcmp(svcp->next_state, SCF_STATE_STRING_NONE) ==
1822		    0) {
1823			print_dependency_reasons(svcp, verbose);
1824			/* Function prints diagcodes. */
1825			return;
1826		} else {
1827			(void) printf(gettext(
1828			    "Reason: Transitioning to state %s.\n"),
1829			    svcp->next_state);
1830			dc = DC_TRANSITION;
1831		}
1832
1833	} else if (strcmp(svcp->state, SCF_STATE_STRING_DEGRADED) == 0) {
1834		(void) puts(gettext("Reason: Degraded by an administrator."));
1835		dc = DC_ADMINDEGR;
1836
1837	} else {
1838		(void) printf(gettext("Reason: Not in valid state (%s).\n"),
1839		    svcp->state);
1840		dc = DC_INVALIDSTATE;
1841	}
1842
1843diagcode:
1844	if (g_msgbase != NULL)
1845		(void) printf(gettext("   See: %s%s\n"), g_msgbase, dc);
1846}
1847
1848static void
1849print_manpage(int verbose)
1850{
1851	static char *title = NULL;
1852	static char *section = NULL;
1853
1854	if (title == NULL) {
1855		title = safe_malloc(g_value_sz);
1856		section = safe_malloc(g_value_sz);
1857	}
1858
1859	if (pg_get_single_val(g_pg, SCF_PROPERTY_TM_TITLE, SCF_TYPE_ASTRING,
1860	    (void *)title, g_value_sz, 0) != 0)
1861		return;
1862
1863	if (pg_get_single_val(g_pg, SCF_PROPERTY_TM_SECTION,
1864	    SCF_TYPE_ASTRING, (void *)section, g_value_sz, 0) != 0)
1865		return;
1866
1867	if (!verbose) {
1868		(void) printf(gettext("   See: %s(%s)\n"), title, section);
1869		return;
1870	}
1871
1872	if (pg_get_single_val(g_pg, SCF_PROPERTY_TM_MANPATH, SCF_TYPE_ASTRING,
1873	    (void *)g_value, g_value_sz, 0) != 0)
1874		return;
1875
1876	if (strcmp(g_value, ":default") == 0) {
1877		assert(sizeof (DEFAULT_MAN_PATH) < g_value_sz);
1878		(void) strcpy(g_value, DEFAULT_MAN_PATH);
1879	}
1880
1881	(void) printf(gettext("   See: man -M %s -s %s %s\n"), g_value,
1882	    section, title);
1883}
1884
1885static void
1886print_doclink()
1887{
1888	static char *uri = NULL;
1889
1890	if (uri == NULL) {
1891		uri = safe_malloc(g_value_sz);
1892	}
1893
1894	if (pg_get_single_val(g_pg, SCF_PROPERTY_TM_URI, SCF_TYPE_ASTRING,
1895	    (void *)uri, g_value_sz, 0) != 0)
1896		return;
1897
1898	(void) printf(gettext("   See: %s\n"), uri);
1899}
1900
1901
1902/*
1903 * Returns
1904 *   0 - success
1905 *   1 - inst was deleted
1906 */
1907static int
1908print_docs(scf_instance_t *inst, int verbose)
1909{
1910	scf_snapshot_t *snap;
1911	int r;
1912
1913	if (scf_instance_get_snapshot(inst, "running", g_snap) != 0) {
1914		switch (scf_error()) {
1915		case SCF_ERROR_NOT_FOUND:
1916			break;
1917
1918		case SCF_ERROR_DELETED:
1919			return (1);
1920
1921		default:
1922			scfdie();
1923		}
1924
1925		snap = NULL;
1926	} else {
1927		snap = g_snap;
1928	}
1929
1930	if (scf_iter_instance_pgs_typed_composed(g_iter, inst, snap,
1931	    SCF_GROUP_TEMPLATE) != 0) {
1932		if (scf_error() != SCF_ERROR_DELETED)
1933			scfdie();
1934
1935		return (1);
1936	}
1937
1938	for (;;) {
1939		r = scf_iter_next_pg(g_iter, g_pg);
1940		if (r == 0)
1941			break;
1942		if (r != 1) {
1943			if (scf_error() != SCF_ERROR_DELETED)
1944				scfdie();
1945
1946			return (1);
1947		}
1948
1949		if (scf_pg_get_name(g_pg, g_fmri, g_fmri_sz) < 0) {
1950			if (scf_error() != SCF_ERROR_DELETED)
1951				scfdie();
1952
1953			continue;
1954		}
1955
1956		if (strncmp(g_fmri, SCF_PG_TM_MAN_PREFIX,
1957		    strlen(SCF_PG_TM_MAN_PREFIX)) == 0) {
1958			print_manpage(verbose);
1959			continue;
1960		}
1961
1962		if (strncmp(g_fmri, SCF_PG_TM_DOC_PREFIX,
1963		    strlen(SCF_PG_TM_DOC_PREFIX)) == 0) {
1964			print_doclink();
1965			continue;
1966		}
1967	}
1968	return (0);
1969}
1970
1971static int first = 1;
1972
1973/*
1974 * Explain why the given service is in the state it's in.
1975 */
1976static void
1977print_service(inst_t *svcp, int verbose)
1978{
1979	struct svcptr *spp;
1980	time_t stime;
1981	char *timebuf;
1982	size_t tbsz;
1983	struct tm *tmp;
1984	int deleted = 0;
1985
1986	if (first)
1987		first = 0;
1988	else
1989		(void) putchar('\n');
1990
1991	(void) printf(gettext("svc:/%s:%s"), svcp->svcname, svcp->instname);
1992
1993	if (scf_scope_get_service(g_local_scope, svcp->svcname, g_svc) != 0) {
1994		if (scf_error() != SCF_ERROR_NOT_FOUND)
1995			scfdie();
1996		deleted = 1;
1997	} else if (scf_service_get_instance(g_svc, svcp->instname, g_inst) !=
1998	    0) {
1999		if (scf_error() != SCF_ERROR_NOT_FOUND)
2000			scfdie();
2001		deleted = 1;
2002	}
2003
2004	if (!deleted) {
2005		if (inst_get_single_val(g_inst, SCF_PG_TM_COMMON_NAME, locale,
2006		    SCF_TYPE_USTRING, g_value, g_value_sz, 0, 0, 1) == 0)
2007			/* EMPTY */;
2008		else if (inst_get_single_val(g_inst, SCF_PG_TM_COMMON_NAME, "C",
2009		    SCF_TYPE_USTRING, g_value, g_value_sz, 0, 0, 1) != 0)
2010			(void) strcpy(g_value, "?");
2011
2012		(void) printf(gettext(" (%s)\n"), g_value);
2013	} else {
2014		(void) putchar('\n');
2015	}
2016
2017	if (g_zonename != NULL)
2018		(void) printf(gettext("  Zone: %s\n"), g_zonename);
2019
2020	stime = svcp->stime.tv_sec;
2021	tmp = localtime(&stime);
2022
2023	for (tbsz = 50; ; tbsz *= 2) {
2024		timebuf = safe_malloc(tbsz);
2025		if (strftime(timebuf, tbsz, NULL, tmp) != 0)
2026			break;
2027		free(timebuf);
2028	}
2029
2030	(void) printf(gettext(" State: %s since %s\n"), svcp->state, timebuf);
2031
2032	free(timebuf);
2033
2034	/* Reasons */
2035	print_reasons(svcp, verbose);
2036
2037	if (!deleted)
2038		deleted = print_docs(g_inst, verbose);
2039	if (!deleted)
2040		print_logs(g_inst);
2041
2042	(void) determine_impact(svcp);
2043
2044	switch (uu_list_numnodes(svcp->impact)) {
2045	case 0:
2046		if (inst_running(svcp))
2047			(void) puts(gettext("Impact: None."));
2048		else
2049			(void) puts(gettext(
2050			    "Impact: This service is not running."));
2051		break;
2052
2053	case 1:
2054		if (!verbose)
2055			(void) puts(gettext("Impact: 1 dependent service "
2056			    "is not running.  (Use -v for list.)"));
2057		else
2058			(void) puts(gettext(
2059			    "Impact: 1 dependent service is not running:"));
2060		break;
2061
2062	default:
2063		if (!verbose)
2064			(void) printf(gettext("Impact: %d dependent services "
2065			    "are not running.  (Use -v for list.)\n"),
2066			    uu_list_numnodes(svcp->impact));
2067		else
2068			(void) printf(gettext(
2069			    "Impact: %d dependent services are not running:\n"),
2070			    uu_list_numnodes(svcp->impact));
2071	}
2072
2073	if (verbose) {
2074		for (spp = uu_list_first(svcp->impact);
2075		    spp != NULL;
2076		    spp = uu_list_next(svcp->impact, spp))
2077			(void) printf(gettext("        svc:/%s:%s\n"),
2078			    spp->svcp->svcname, spp->svcp->instname);
2079	}
2080}
2081
2082/*
2083 * Top level routine.
2084 */
2085
2086static int
2087impact_compar(const void *a, const void *b)
2088{
2089	int n, m;
2090
2091	n = uu_list_numnodes((*(inst_t **)a)->impact);
2092	m = uu_list_numnodes((*(inst_t **)b)->impact);
2093
2094	return (m - n);
2095}
2096
2097static int
2098print_service_cb(void *verbose, scf_walkinfo_t *wip)
2099{
2100	int r;
2101	inst_t *ip;
2102
2103	assert(wip->pg == NULL);
2104
2105	r = get_fmri(wip->fmri, NULL, &ip);
2106	assert(r != EINVAL);
2107	if (r == ENOENT)
2108		return (0);
2109
2110	assert(r == 0);
2111	assert(ip != NULL);
2112
2113	print_service(ip, (int)verbose);
2114
2115	return (0);
2116}
2117
2118void
2119explain(int verbose, int argc, char **argv)
2120{
2121	/*
2122	 * Initialize globals.  If we have been called before (e.g., for a
2123	 * different zone), this will clobber the previous globals -- keeping
2124	 * with the proud svcs(1) tradition of not bothering to ever clean
2125	 * anything up.
2126	 */
2127	x_init();
2128
2129	/* Walk the graph and populate services with inst_t's */
2130	load_services();
2131
2132	/* Populate causes for services. */
2133	determine_all_causes();
2134
2135	if (argc > 0) {
2136		scf_error_t err;
2137
2138		check_msgbase();
2139
2140		/* Call print_service() for each operand. */
2141
2142		err = scf_walk_fmri(h, argc, argv, SCF_WALK_MULTIPLE,
2143		    print_service_cb, (void *)verbose, &exit_status, uu_warn);
2144		if (err != 0) {
2145			uu_warn(gettext(
2146			    "failed to iterate over instances: %s\n"),
2147			    scf_strerror(err));
2148			exit_status = UU_EXIT_FATAL;
2149		}
2150	} else {
2151		struct svcptr *spp;
2152		int n, i;
2153		inst_t **ary;
2154
2155		/* Sort g_causes. */
2156
2157		n = uu_list_numnodes(g_causes);
2158		if (n == 0)
2159			return;
2160
2161		check_msgbase();
2162
2163		ary = calloc(n, sizeof (*ary));
2164		if (ary == NULL)
2165			uu_die(emsg_nomem);
2166
2167		i = 0;
2168		for (spp = uu_list_first(g_causes);
2169		    spp != NULL;
2170		    spp = uu_list_next(g_causes, spp)) {
2171			(void) determine_impact(spp->svcp);
2172			ary[i++] = spp->svcp;
2173		}
2174
2175		qsort(ary, n, sizeof (*ary), impact_compar);
2176
2177		/* Call print_service() for each service. */
2178
2179		for (i = 0; i < n; ++i)
2180			print_service(ary[i], verbose);
2181	}
2182}
2183