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 119 typedef 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 149 typedef struct service { 150 const char *svcname; 151 uu_list_t *instances; 152 struct service *next; 153 } svc_t; 154 155 struct svcptr { 156 inst_t *svcp; 157 inst_t *next_hop; 158 uu_list_node_t node; 159 }; 160 161 struct 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 168 struct 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 177 static svc_t **services; 178 179 static uu_list_pool_t *insts, *svcptrs, *depgroups, *deps; 180 static uu_list_t *g_causes; /* list of svcptrs */ 181 182 static scf_scope_t *g_local_scope; 183 static scf_service_t *g_svc; 184 static scf_instance_t *g_inst; 185 static scf_snapshot_t *g_snap; 186 static scf_propertygroup_t *g_pg; 187 static scf_property_t *g_prop; 188 static scf_value_t *g_val; 189 static scf_iter_t *g_iter, *g_viter; 190 static char *g_fmri, *g_value; 191 static size_t g_fmri_sz, g_value_sz; 192 static const char *g_msgbase = "http://illumos.org/msg/"; 193 194 static char *emsg_nomem; 195 static char *emsg_invalid_dep; 196 197 extern scf_handle_t *h; 198 extern char *g_zonename; 199 200 /* ARGSUSED */ 201 static int 202 svcptr_compare(struct svcptr *a, struct svcptr *b, void *data) 203 { 204 return (b->svcp - a->svcp); 205 } 206 207 static uint32_t 208 hash_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 224 static void 225 x_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 */ 281 static int 282 load_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 403 static void 404 add_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 545 static void 546 load_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 613 static void 614 add_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 634 static 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 */ 640 static int 641 add_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, ©->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 673 static int 674 inst_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 680 static int 681 inst_running_or_maint(inst_t *ip) 682 { 683 return (inst_running(ip) || 684 strcmp(ip->state, SCF_STATE_STRING_MAINT) == 0); 685 } 686 687 static svc_t * 688 get_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 */ 704 static inst_t * 705 get_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 719 static int 720 get_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 753 static int 754 process_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 826 static int 827 process_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 924 static int 925 process_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 987 static int 988 process_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 1044 static int 1045 process_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 */ 1077 static int 1078 eval_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 1102 static void 1103 process_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 */ 1184 static int 1185 determine_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 1302 static void 1303 determine_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 */ 1320 static int 1321 determine_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, ©->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 1373 static void 1374 check_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 1431 static void 1432 determine_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 1487 static void 1488 print_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 { 1543 fail: 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 1553 static void 1554 print_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 1672 static void 1673 print_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 1687 static void 1688 print_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 1701 static void 1702 print_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 1843 diagcode: 1844 if (g_msgbase != NULL) 1845 (void) printf(gettext(" See: %s%s\n"), g_msgbase, dc); 1846 } 1847 1848 static void 1849 print_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 1885 static void 1886 print_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 */ 1907 static int 1908 print_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 1971 static int first = 1; 1972 1973 /* 1974 * Explain why the given service is in the state it's in. 1975 */ 1976 static void 1977 print_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 2086 static int 2087 impact_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 2097 static int 2098 print_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 2118 void 2119 explain(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 }