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