1 /* 2 * This file and its contents are supplied under the terms of the 3 * Common Development and Distribution License ("CDDL"), version 1.0. 4 * You may only use this file in accordance with the terms of version 5 * 1.0 of the CDDL. 6 * 7 * A full copy of the text of the CDDL should have accompanied this 8 * source. A copy of the CDDL is also available via the Internet at 9 * http://www.illumos.org/license/CDDL. 10 */ 11 12 /* 13 * Copyright (c) 2012 DEY Storage Systems, Inc. All rights reserved. 14 * Copyright 2012 Nexenta Systems, Inc. All rights reserved. 15 * Copyright 2019 Joyent, Inc. 16 */ 17 18 /* 19 * This implements psrinfo(8), a utility to report various information 20 * about processors, cores, and threads (virtual cpus). This is mostly 21 * intended for human consumption - this utility doesn't do much more than 22 * simply process kstats for human readability. 23 * 24 * All the relevant kstats are in the cpu_info kstat module. 25 */ 26 27 #include <sys/sysmacros.h> 28 29 #include <stdbool.h> 30 #include <stdio.h> 31 #include <stdlib.h> 32 #include <unistd.h> 33 #include <string.h> 34 #include <kstat.h> 35 #include <libintl.h> 36 #include <locale.h> 37 #include <libgen.h> 38 #include <ctype.h> 39 #include <errno.h> 40 #include <err.h> 41 42 #include <libdevinfo.h> 43 44 #define _(x) gettext(x) 45 #if XGETTEXT 46 /* These CPU states are here for benefit of xgettext */ 47 _("on-line") 48 _("off-line") 49 _("faulted") 50 _("powered-off") 51 _("no-intr") 52 _("spare") 53 _("unknown") 54 _("disabled") 55 #endif 56 57 /* 58 * We deal with sorted linked lists, where the sort key is usually the 59 * cpu id, core id, or chip id. We generalize this with simple node. 60 */ 61 struct link { 62 long l_id; 63 struct link *l_next; 64 void *l_ptr; 65 }; 66 67 /* 68 * A physical chip. A chip can contain multiple cores and virtual cpus. 69 */ 70 struct pchip { 71 struct link p_link; 72 int p_ncore; 73 int p_nvcpu; 74 struct link *p_cores; 75 struct link *p_vcpus; 76 int p_doit; 77 }; 78 79 struct core { 80 struct link c_link; 81 struct link c_link_pchip; 82 83 int c_nvcpu; 84 int c_doit; 85 86 struct pchip *c_pchip; 87 struct link *c_vcpus; 88 }; 89 90 struct vcpu { 91 struct link v_link; 92 93 struct link v_link_core; 94 struct link v_link_pchip; 95 96 int v_doit; 97 98 struct pchip *v_pchip; 99 struct core *v_core; 100 101 char *v_state; 102 long v_state_begin; 103 char *v_cpu_type; 104 char *v_fpu_type; 105 long v_clock_mhz; 106 long v_pchip_id; /* 1 per socket */ 107 char *v_impl; 108 char *v_brand; 109 char *v_socket; 110 long v_core_id; /* n per chip_id */ 111 }; 112 113 static struct link *pchips = NULL; 114 static struct link *cores = NULL; 115 static struct link *vcpus = NULL; 116 117 static uint_t nr_cpus; 118 static uint_t nr_cores; 119 static uint_t nr_chips; 120 121 static const char *cmdname; 122 123 static void 124 usage(char *msg) 125 { 126 if (msg != NULL) 127 (void) fprintf(stderr, "%s: %s\n", cmdname, msg); 128 (void) fprintf(stderr, _("usage: \n" 129 "\t%s -r propname\n" 130 "\t%s [-v] [-p] [processor_id ...]\n" 131 "\t%s -s [-p] processor_id\n" 132 "\t%s -t [-S <state> | -c | -p]\n"), 133 cmdname, cmdname, cmdname, cmdname); 134 exit(2); 135 } 136 137 /* like perror, but includes the command name */ 138 static void 139 die(const char *msg) 140 { 141 (void) fprintf(stderr, "%s: %s: %s\n", cmdname, msg, strerror(errno)); 142 exit(2); 143 } 144 145 static char * 146 mystrdup(const char *src) 147 { 148 char *dst; 149 150 if ((dst = strdup(src)) == NULL) 151 die(_("strdup() failed")); 152 return (dst); 153 } 154 155 static void * 156 zalloc(size_t size) 157 { 158 void *ptr; 159 160 if ((ptr = calloc(1, size)) == NULL) 161 die(_("calloc() failed")); 162 return (ptr); 163 } 164 165 /* 166 * Insert a new node on a list, at the insertion point given. 167 */ 168 static void 169 ins_link(struct link **ins, struct link *item) 170 { 171 item->l_next = *ins; 172 *ins = item; 173 } 174 175 /* 176 * Find an id on a sorted list. If the requested id is not found, 177 * then the insertpt will be set (if not null) to the location where 178 * a new node should be inserted with ins_link (see above). 179 */ 180 static void * 181 find_link(void *list, int id, struct link ***insertpt) 182 { 183 struct link **ins = list; 184 struct link *l; 185 186 while ((l = *ins) != NULL) { 187 if (l->l_id == id) 188 return (l->l_ptr); 189 if (l->l_id > id) 190 break; 191 ins = &l->l_next; 192 } 193 if (insertpt != NULL) 194 *insertpt = ins; 195 return (NULL); 196 } 197 198 /* 199 * Print the linked list of ids in parens, taking care to collapse 200 * ranges, so instead of (0 1 2 3) it should print (0-3). 201 */ 202 static void 203 print_links(struct link *l) 204 { 205 int start = -1; 206 int end = 0; 207 208 (void) printf(" ("); 209 while (l != NULL) { 210 if (start < 0) { 211 start = l->l_id; 212 } 213 end = l->l_id; 214 if ((l->l_next == NULL) || 215 (l->l_next->l_id > (l->l_id + 1))) { 216 /* end of the contiguous group */ 217 if (start == end) { 218 (void) printf("%d", start); 219 } else { 220 (void) printf("%d-%d", start, end); 221 } 222 if (l->l_next) 223 (void) printf(" "); 224 start = -1; 225 } 226 l = l->l_next; 227 } 228 (void) printf(")"); 229 } 230 231 static const char * 232 timestr(long t) 233 { 234 static char buffer[256]; 235 (void) strftime(buffer, sizeof (buffer), _("%m/%d/%Y %T"), 236 localtime(&t)); 237 return (buffer); 238 } 239 240 static void 241 print_vp(int nspec) 242 { 243 struct pchip *chip; 244 struct core *core; 245 struct vcpu *vcpu; 246 struct link *l1, *l2; 247 int len; 248 for (l1 = pchips; l1; l1 = l1->l_next) { 249 250 chip = l1->l_ptr; 251 252 if ((nspec != 0) && (chip->p_doit == 0)) 253 continue; 254 255 vcpu = chip->p_vcpus->l_ptr; 256 257 /* 258 * Note that some of the way these strings are broken up are 259 * to accommodate the legacy translations so that we won't 260 * have to retranslate for this utility. 261 */ 262 if ((chip->p_ncore == 1) || (chip->p_ncore == chip->p_nvcpu)) { 263 (void) printf(_("%s has %d virtual %s"), 264 _("The physical processor"), 265 chip->p_nvcpu, 266 chip->p_nvcpu > 1 ? 267 _("processors") : 268 _("processor")); 269 } else { 270 (void) printf(_("%s has %d %s and %d virtual %s"), 271 _("The physical processor"), 272 chip->p_ncore, _("cores"), 273 chip->p_nvcpu, 274 chip->p_nvcpu > 1 ? 275 _("processors") : _("processor")); 276 } 277 278 print_links(chip->p_vcpus); 279 (void) putchar('\n'); 280 281 if ((chip->p_ncore == 1) || (chip->p_ncore == chip->p_nvcpu)) { 282 if (strlen(vcpu->v_impl)) { 283 (void) printf(" %s\n", vcpu->v_impl); 284 } 285 if (((len = strlen(vcpu->v_brand)) != 0) && 286 (strncmp(vcpu->v_brand, vcpu->v_impl, len) != 0)) 287 (void) printf("\t%s", vcpu->v_brand); 288 if (strcmp(vcpu->v_socket, "Unknown") != 0) 289 (void) printf("\t[ %s: %s ]", _("Socket"), 290 vcpu->v_socket); 291 (void) putchar('\n'); 292 } else { 293 for (l2 = chip->p_cores; l2; l2 = l2->l_next) { 294 core = l2->l_ptr; 295 (void) printf(_(" %s has %d virtual %s"), 296 _("The core"), 297 core->c_nvcpu, 298 chip->p_nvcpu > 1 ? 299 _("processors") : _("processor")); 300 print_links(core->c_vcpus); 301 (void) putchar('\n'); 302 } 303 if (strlen(vcpu->v_impl)) { 304 (void) printf(" %s\n", vcpu->v_impl); 305 } 306 if (((len = strlen(vcpu->v_brand)) != 0) && 307 (strncmp(vcpu->v_brand, vcpu->v_impl, len) != 0)) 308 (void) printf(" %s\n", vcpu->v_brand); 309 } 310 } 311 } 312 313 static void 314 print_ps(void) 315 { 316 int online = 1; 317 struct pchip *p = NULL; 318 struct vcpu *v; 319 struct link *l; 320 321 /* 322 * Report "1" if all cpus colocated on the same chip are online. 323 */ 324 for (l = pchips; l != NULL; l = l->l_next) { 325 p = l->l_ptr; 326 if (p->p_doit) 327 break; 328 } 329 if (p == NULL) 330 return; /* should never happen! */ 331 for (l = p->p_vcpus; l != NULL; l = l->l_next) { 332 v = l->l_ptr; 333 if (strcmp(v->v_state, "on-line") != 0) { 334 online = 0; 335 break; 336 } 337 } 338 339 (void) printf("%d\n", online); 340 } 341 342 static void 343 print_s(void) 344 { 345 struct link *l; 346 347 /* 348 * Find the processor (there will be only one) that we selected, 349 * and report whether or not it is online. 350 */ 351 for (l = vcpus; l != NULL; l = l->l_next) { 352 struct vcpu *v = l->l_ptr; 353 if (v->v_doit) { 354 (void) printf("%d\n", 355 strcmp(v->v_state, "on-line") == 0 ? 1 : 0); 356 return; 357 } 358 } 359 } 360 361 static void 362 print_p(int nspec) 363 { 364 struct link *l1, *l2; 365 int online = 0; 366 367 /* 368 * Print the number of physical packages with at least one processor 369 * online. 370 */ 371 for (l1 = pchips; l1 != NULL; l1 = l1->l_next) { 372 struct pchip *p = l1->l_ptr; 373 if ((nspec == 0) || (p->p_doit)) { 374 375 for (l2 = p->p_vcpus; l2 != NULL; l2 = l2->l_next) { 376 struct vcpu *v = l2->l_ptr; 377 if (strcmp(v->v_state, "on-line") == 0) { 378 online++; 379 break; 380 } 381 } 382 } 383 } 384 (void) printf("%d\n", online); 385 } 386 387 static void 388 print_v(int nspec) 389 { 390 struct link *l; 391 392 for (l = vcpus; l != NULL; l = l->l_next) { 393 struct vcpu *v = l->l_ptr; 394 395 if ((nspec != 0) && (!v->v_doit)) 396 continue; 397 (void) printf(_("Status of virtual processor %d as of: "), 398 l->l_id); 399 (void) printf("%s\n", timestr(time(NULL))); 400 (void) printf(_(" %s since %s.\n"), 401 _(v->v_state), timestr(v->v_state_begin)); 402 if (v->v_clock_mhz) { 403 (void) printf( 404 _(" The %s processor operates at %llu MHz,\n"), 405 v->v_cpu_type, (unsigned long long)v->v_clock_mhz); 406 } else { 407 (void) printf( 408 _(" The %s processor operates at " \ 409 "an unknown frequency,\n"), v->v_cpu_type); 410 } 411 switch (*v->v_fpu_type) { 412 case '\0': 413 (void) printf( 414 _("\tand has no floating point processor.\n")); 415 break; 416 case 'a': case 'A': 417 case 'e': case 'E': 418 case 'i': case 'I': 419 case 'o': case 'O': 420 case 'u': case 'U': 421 case 'y': case 'Y': 422 (void) printf( 423 _("\tand has an %s floating point processor.\n"), 424 v->v_fpu_type); 425 break; 426 default: 427 (void) printf( 428 _("\tand has a %s floating point processor.\n"), 429 v->v_fpu_type); 430 break; 431 } 432 } 433 } 434 435 static void 436 print_normal(int nspec) 437 { 438 struct link *l; 439 struct vcpu *v; 440 441 for (l = vcpus; l != NULL; l = l->l_next) { 442 v = l->l_ptr; 443 if ((nspec == 0) || (v->v_doit)) { 444 (void) printf(_("%d\t%-8s since %s\n"), 445 l->l_id, _(v->v_state), timestr(v->v_state_begin)); 446 } 447 } 448 } 449 450 static bool 451 valid_propname(const char *propname) 452 { 453 size_t i; 454 455 const char *props[] = { 456 "smt_enabled", 457 }; 458 459 for (i = 0; i < ARRAY_SIZE(props); i++) { 460 if (strcmp(propname, props[i]) == 0) 461 break; 462 } 463 464 return (i != ARRAY_SIZE(props)); 465 } 466 467 static void 468 read_property(const char *propname) 469 { 470 di_prop_t prop = DI_PROP_NIL; 471 di_node_t root_node; 472 bool show_all = strcmp(propname, "all") == 0; 473 474 if (!show_all && !valid_propname(propname)) 475 errx(EXIT_FAILURE, _("unknown CPU property %s"), propname); 476 477 if ((root_node = di_init("/", DINFOPROP)) == NULL) 478 err(EXIT_FAILURE, _("failed to read root node")); 479 480 while ((prop = di_prop_sys_next(root_node, prop)) != DI_PROP_NIL) { 481 const char *name = di_prop_name(prop); 482 char *val; 483 int nr_vals; 484 485 if (!valid_propname(name)) 486 continue; 487 488 if (!show_all && strcmp(di_prop_name(prop), propname) != 0) 489 continue; 490 491 if ((nr_vals = di_prop_strings(prop, &val)) < 1) { 492 err(EXIT_FAILURE, 493 _("error reading property %s"), name); 494 } else if (nr_vals != 1) { 495 errx(EXIT_FAILURE, _("invalid property %s"), name); 496 } 497 498 printf("%s=%s\n", name, val); 499 500 if (!show_all) 501 exit(EXIT_SUCCESS); 502 } 503 504 if (!show_all) 505 errx(EXIT_FAILURE, _("property %s was not found"), propname); 506 507 di_fini(root_node); 508 } 509 510 static void 511 print_total(int opt_c, int opt_p, const char *opt_S) 512 { 513 uint_t count = 0; 514 515 if (opt_c) { 516 printf("%u\n", nr_cores); 517 return; 518 } else if (opt_p) { 519 printf("%u\n", nr_chips); 520 return; 521 } else if (opt_S == NULL || strcmp(opt_S, "all") == 0) { 522 printf("%u\n", nr_cpus); 523 return; 524 } 525 526 527 for (struct link *l = vcpus; l != NULL; l = l->l_next) { 528 struct vcpu *v = l->l_ptr; 529 if (strcmp(opt_S, v->v_state) == 0) 530 count++; 531 } 532 533 printf("%u\n", count); 534 } 535 536 int 537 main(int argc, char **argv) 538 { 539 kstat_ctl_t *kc; 540 kstat_t *ksp; 541 kstat_named_t *knp; 542 struct vcpu *vc; 543 struct core *core; 544 struct pchip *chip; 545 struct link **ins; 546 char *s; 547 int nspec; 548 int optc; 549 int opt_c = 0; 550 int opt_p = 0; 551 const char *opt_r = NULL; 552 const char *opt_S = NULL; 553 int opt_s = 0; 554 int opt_t = 0; 555 int opt_v = 0; 556 int ex = 0; 557 558 cmdname = basename(argv[0]); 559 560 561 (void) setlocale(LC_ALL, ""); 562 #if !defined(TEXT_DOMAIN) 563 #define TEXT_DOMAIN "SYS_TEST" 564 #endif 565 (void) textdomain(TEXT_DOMAIN); 566 567 /* collect the kstats */ 568 if ((kc = kstat_open()) == NULL) 569 die(_("kstat_open() failed")); 570 571 if ((ksp = kstat_lookup(kc, "cpu_info", -1, NULL)) == NULL) 572 die(_("kstat_lookup() failed")); 573 574 for (ksp = kc->kc_chain; ksp; ksp = ksp->ks_next) { 575 576 if (strcmp(ksp->ks_module, "cpu_info") != 0) 577 continue; 578 if (kstat_read(kc, ksp, NULL) == -1) 579 die(_("kstat_read() failed")); 580 581 vc = find_link(&vcpus, ksp->ks_instance, &ins); 582 if (vc == NULL) { 583 vc = zalloc(sizeof (struct vcpu)); 584 vc->v_link.l_id = ksp->ks_instance; 585 vc->v_link_core.l_id = ksp->ks_instance; 586 vc->v_link_pchip.l_id = ksp->ks_instance; 587 vc->v_link.l_ptr = vc; 588 vc->v_link_core.l_ptr = vc; 589 vc->v_link_pchip.l_ptr = vc; 590 ins_link(ins, &vc->v_link); 591 nr_cpus++; 592 } 593 594 if ((knp = kstat_data_lookup(ksp, "state")) != NULL) { 595 vc->v_state = mystrdup(knp->value.c); 596 } else { 597 vc->v_state = "unknown"; 598 } 599 600 if ((knp = kstat_data_lookup(ksp, "cpu_type")) != NULL) { 601 vc->v_cpu_type = mystrdup(knp->value.c); 602 } 603 if ((knp = kstat_data_lookup(ksp, "fpu_type")) != NULL) { 604 vc->v_fpu_type = mystrdup(knp->value.c); 605 } 606 607 if ((knp = kstat_data_lookup(ksp, "state_begin")) != NULL) { 608 vc->v_state_begin = knp->value.l; 609 } 610 611 if ((knp = kstat_data_lookup(ksp, "clock_MHz")) != NULL) { 612 vc->v_clock_mhz = knp->value.l; 613 } 614 615 if ((knp = kstat_data_lookup(ksp, "brand")) == NULL) { 616 vc->v_brand = _("(unknown)"); 617 } else { 618 vc->v_brand = mystrdup(knp->value.str.addr.ptr); 619 } 620 621 if ((knp = kstat_data_lookup(ksp, "socket_type")) == NULL) { 622 vc->v_socket = "Unknown"; 623 } else { 624 vc->v_socket = mystrdup(knp->value.str.addr.ptr); 625 } 626 627 if ((knp = kstat_data_lookup(ksp, "implementation")) == NULL) { 628 vc->v_impl = _("(unknown)"); 629 } else { 630 vc->v_impl = mystrdup(knp->value.str.addr.ptr); 631 } 632 /* 633 * Legacy code removed the chipid and cpuid fields... we 634 * do the same for compatibility. Note that the original 635 * pattern is a bit strange, and we have to emulate this because 636 * on SPARC we *do* emit these. The original pattern we are 637 * emulating is: $impl =~ s/(cpuid|chipid)\s*\w+\s+//; 638 */ 639 if ((s = strstr(vc->v_impl, "chipid")) != NULL) { 640 char *x = s + strlen("chipid"); 641 while (isspace(*x)) 642 x++; 643 if ((!isalnum(*x)) && (*x != '_')) 644 goto nochipid; 645 while (isalnum(*x) || (*x == '_')) 646 x++; 647 if (!isspace(*x)) 648 goto nochipid; 649 while (isspace(*x)) 650 x++; 651 (void) strcpy(s, x); 652 } 653 nochipid: 654 if ((s = strstr(vc->v_impl, "cpuid")) != NULL) { 655 char *x = s + strlen("cpuid"); 656 while (isspace(*x)) 657 x++; 658 if ((!isalnum(*x)) && (*x != '_')) 659 goto nocpuid; 660 while (isalnum(*x) || (*x == '_')) 661 x++; 662 if (!isspace(*x)) 663 goto nocpuid; 664 while (isspace(*x)) 665 x++; 666 (void) strcpy(s, x); 667 } 668 nocpuid: 669 670 if ((knp = kstat_data_lookup(ksp, "chip_id")) != NULL) 671 vc->v_pchip_id = knp->value.l; 672 chip = find_link(&pchips, vc->v_pchip_id, &ins); 673 if (chip == NULL) { 674 chip = zalloc(sizeof (struct pchip)); 675 chip->p_link.l_id = vc->v_pchip_id; 676 chip->p_link.l_ptr = chip; 677 ins_link(ins, &chip->p_link); 678 nr_chips++; 679 } 680 vc->v_pchip = chip; 681 682 if ((knp = kstat_data_lookup(ksp, "core_id")) != NULL) 683 vc->v_core_id = knp->value.l; 684 core = find_link(&cores, vc->v_core_id, &ins); 685 if (core == NULL) { 686 core = zalloc(sizeof (struct core)); 687 core->c_link.l_id = vc->v_core_id; 688 core->c_link.l_ptr = core; 689 core->c_link_pchip.l_id = vc->v_core_id; 690 core->c_link_pchip.l_ptr = core; 691 core->c_pchip = chip; 692 ins_link(ins, &core->c_link); 693 chip->p_ncore++; 694 (void) find_link(&chip->p_cores, core->c_link.l_id, 695 &ins); 696 ins_link(ins, &core->c_link_pchip); 697 nr_cores++; 698 } 699 vc->v_core = core; 700 701 702 703 /* now put other linkages in place */ 704 (void) find_link(&chip->p_vcpus, vc->v_link.l_id, &ins); 705 ins_link(ins, &vc->v_link_pchip); 706 chip->p_nvcpu++; 707 708 (void) find_link(&core->c_vcpus, vc->v_link.l_id, &ins); 709 ins_link(ins, &vc->v_link_core); 710 core->c_nvcpu++; 711 } 712 713 (void) kstat_close(kc); 714 715 nspec = 0; 716 717 while ((optc = getopt(argc, argv, "cpr:S:stv")) != EOF) { 718 switch (optc) { 719 case 'c': 720 opt_c = 1; 721 break; 722 case 'p': 723 opt_p = 1; 724 break; 725 case 'r': 726 opt_r = optarg; 727 break; 728 case 'S': 729 opt_S = optarg; 730 break; 731 case 's': 732 opt_s = 1; 733 break; 734 case 't': 735 opt_t = 1; 736 break; 737 case 'v': 738 opt_v = 1; 739 break; 740 default: 741 usage(NULL); 742 } 743 } 744 745 if (opt_r != NULL) { 746 if (optind != argc) 747 usage(_("cannot specify CPUs with -r")); 748 if (opt_c || opt_p || opt_S != NULL || opt_s || opt_t || opt_v) 749 usage(_("cannot specify other arguments with -r")); 750 751 read_property(opt_r); 752 return (EXIT_SUCCESS); 753 } 754 755 if (opt_t != 0) { 756 if (optind != argc) 757 usage(_("cannot specify CPUs with -t")); 758 if (opt_s || opt_v) 759 usage(_("cannot specify -s or -v with -t")); 760 if (opt_S != NULL && (opt_c || opt_p)) 761 usage(_("cannot specify CPU state with -c or -p")); 762 if (opt_c && opt_p) 763 usage(_("cannot specify -c and -p")); 764 765 print_total(opt_c, opt_p, opt_S); 766 return (EXIT_SUCCESS); 767 } 768 769 if (opt_S != NULL || opt_c) 770 usage(_("cannot specify -S or -c without -t")); 771 772 while (optind < argc) { 773 long id; 774 char *eptr; 775 struct link *l; 776 id = strtol(argv[optind], &eptr, 10); 777 l = find_link(&vcpus, id, NULL); 778 if ((*eptr != '\0') || (l == NULL)) { 779 (void) fprintf(stderr, 780 _("%s: processor %s: Invalid argument\n"), 781 cmdname, argv[optind]); 782 ex = 2; 783 } else { 784 ((struct vcpu *)l->l_ptr)->v_doit = 1; 785 ((struct vcpu *)l->l_ptr)->v_pchip->p_doit = 1; 786 ((struct vcpu *)l->l_ptr)->v_core->c_doit = 1; 787 } 788 nspec++; 789 optind++; 790 } 791 792 if (opt_s && opt_v) { 793 usage(_("options -s and -v are mutually exclusive")); 794 } 795 if (opt_s && nspec != 1) { 796 usage(_("must specify exactly one processor if -s used")); 797 } 798 if (opt_v && opt_p) { 799 print_vp(nspec); 800 } else if (opt_s && opt_p) { 801 print_ps(); 802 } else if (opt_p) { 803 print_p(nspec); 804 } else if (opt_v) { 805 print_v(nspec); 806 } else if (opt_s) { 807 print_s(); 808 } else { 809 print_normal(nspec); 810 } 811 812 return (ex); 813 } 814