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
usage(char * msg)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
die(const char * msg)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 *
mystrdup(const char * src)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 *
zalloc(size_t size)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
ins_link(struct link ** ins,struct link * item)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 *
find_link(void * list,int id,struct link *** insertpt)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
print_links(struct link * l)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 *
timestr(long t)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
print_vp(int nspec)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
print_ps(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
print_s(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
print_p(int nspec)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
print_v(int nspec)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
print_normal(int nspec)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
valid_propname(const char * propname)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
read_property(const char * propname)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
print_total(int opt_c,int opt_p,const char * opt_S)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
main(int argc,char ** argv)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