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