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 * Copyright (c) 2004, 2010, Oracle and/or its affiliates. All rights reserved.
23 */
24
25 #include <sys/types.h>
26 #include <sys/ctfs.h>
27 #include <stdio.h>
28 #include <stdlib.h>
29 #include <unistd.h>
30 #include <fcntl.h>
31 #include <string.h>
32 #include <errno.h>
33 #include <libuutil.h>
34 #include <sys/contract/process.h>
35 #include <sys/contract/device.h>
36 #include <limits.h>
37 #include <libcontract.h>
38 #include <libcontract_priv.h>
39 #include <dirent.h>
40
41 #include <locale.h>
42 #include <langinfo.h>
43
44 #include "statcommon.h"
45
46 static uint_t timestamp_fmt = NODATE;
47
48 static int opt_verbose = 0;
49 static int opt_showall = 0;
50
51 /*
52 * usage
53 *
54 * Educate the user.
55 */
56 static void
usage(void)57 usage(void)
58 {
59 (void) fprintf(stderr, gettext("Usage: %s [-a] [-i ctidlist] "
60 "[-t typelist] [-T d|u] [-v] [interval [count]]\n"), uu_getpname());
61 exit(UU_EXIT_USAGE);
62 }
63
64 /*
65 * mystrtoul
66 *
67 * Convert a string into an int in [0, INT_MAX]. Exit if the argument
68 * doen't fit this description.
69 */
70 static int
mystrtoul(const char * arg)71 mystrtoul(const char *arg)
72 {
73 unsigned int result;
74
75 if (uu_strtoint(arg, &result, sizeof (result), 10, 0, INT_MAX) == -1) {
76 uu_warn(gettext("invalid numerical argument \"%s\"\n"), arg);
77 usage();
78 }
79
80 return (result);
81 }
82
83 /*
84 * int_compar
85 *
86 * A simple integer comparator. Also used for id_ts, since they're the
87 * same thing.
88 */
89 static int
int_compar(const void * a1,const void * a2)90 int_compar(const void *a1, const void *a2)
91 {
92 int id1 = *(int *)a1;
93 int id2 = *(int *)a2;
94
95 if (id1 > id2)
96 return (1);
97 if (id2 > id1)
98 return (-1);
99 return (0);
100 }
101
102 typedef struct optvect {
103 const char *option;
104 uint_t bit;
105 } optvect_t;
106
107 static optvect_t option_params[] = {
108 { "inherit", CT_PR_INHERIT },
109 { "noorphan", CT_PR_NOORPHAN },
110 { "pgrponly", CT_PR_PGRPONLY },
111 { "regent", CT_PR_REGENT },
112 { NULL }
113 };
114
115 static optvect_t option_events[] = {
116 { "core", CT_PR_EV_CORE },
117 { "signal", CT_PR_EV_SIGNAL },
118 { "hwerr", CT_PR_EV_HWERR },
119 { "empty", CT_PR_EV_EMPTY },
120 { "fork", CT_PR_EV_FORK },
121 { "exit", CT_PR_EV_EXIT },
122 { NULL }
123 };
124
125 /*
126 * print_bits
127 *
128 * Display a set whose membership is identified by a bitfield.
129 */
130 static void
print_bits(uint_t bits,optvect_t * desc)131 print_bits(uint_t bits, optvect_t *desc)
132 {
133 int i, printed = 0;
134
135 for (i = 0; desc[i].option; i++)
136 if (desc[i].bit & bits) {
137 if (printed)
138 (void) putchar(' ');
139 printed = 1;
140 (void) fputs(desc[i].option, stdout);
141 }
142 if (printed)
143 (void) putchar('\n');
144 else
145 (void) puts("none");
146 }
147
148 /*
149 * print_ids
150 *
151 * Display a list of ids, sorted.
152 */
153 static void
print_ids(id_t * ids,uint_t nids)154 print_ids(id_t *ids, uint_t nids)
155 {
156 int i;
157 int first = 1;
158
159 qsort(ids, nids, sizeof (int), int_compar);
160
161 for (i = 0; i < nids; i++) {
162 /*LINTED*/
163 (void) printf(" %d" + first, ids[i]);
164 first = 0;
165 }
166 if (first)
167 (void) puts("none");
168 else
169 (void) putchar('\n');
170 }
171
172 typedef void printfunc_t(ct_stathdl_t);
173
174 /*
175 * A structure defining a displayed field. Includes a label to be
176 * printed along side the field value, and a function which extracts
177 * the data from a status structure, formats it, and displays it on
178 * stdout.
179 */
180 typedef struct verbout {
181 const char *label; /* field label */
182 printfunc_t *func; /* field display function */
183 } verbout_t;
184
185 /*
186 * verb_cookie
187 *
188 * Used to display an error encountered when reading a contract status
189 * field.
190 */
191 static void
verb_error(int err)192 verb_error(int err)
193 {
194 (void) printf("(error: %s)\n", strerror(err));
195 }
196
197 /*
198 * verb_cookie
199 *
200 * Display the contract's cookie.
201 */
202 static void
verb_cookie(ct_stathdl_t hdl)203 verb_cookie(ct_stathdl_t hdl)
204 {
205 (void) printf("%#llx\n", ct_status_get_cookie(hdl));
206 }
207
208 /*
209 * verb_info
210 *
211 * Display the parameters in the parameter set.
212 */
213 static void
verb_param(ct_stathdl_t hdl)214 verb_param(ct_stathdl_t hdl)
215 {
216 uint_t param;
217 int err;
218
219 if (err = ct_pr_status_get_param(hdl, ¶m))
220 verb_error(err);
221 else
222 print_bits(param, option_params);
223 }
224
225 /*
226 * verb_info
227 *
228 * Display the events in the informative event set.
229 */
230 static void
verb_info(ct_stathdl_t hdl)231 verb_info(ct_stathdl_t hdl)
232 {
233 print_bits(ct_status_get_informative(hdl), option_events);
234 }
235
236 /*
237 * verb_crit
238 *
239 * Display the events in the critical event set.
240 */
241 static void
verb_crit(ct_stathdl_t hdl)242 verb_crit(ct_stathdl_t hdl)
243 {
244 print_bits(ct_status_get_critical(hdl), option_events);
245 }
246
247 /*
248 * verb_minor
249 *
250 * Display the minor device
251 */
252 static void
verb_minor(ct_stathdl_t hdl)253 verb_minor(ct_stathdl_t hdl)
254 {
255 int err;
256 char *buf;
257
258 if (err = ct_dev_status_get_minor(hdl, &buf))
259 verb_error(err);
260 else
261 (void) printf("%s\n", buf);
262 }
263
264 /*
265 * verb_state
266 *
267 * Display the state of the device
268 */
269 static void
verb_dev_state(ct_stathdl_t hdl)270 verb_dev_state(ct_stathdl_t hdl)
271 {
272 int err;
273 uint_t state;
274
275 if (err = ct_dev_status_get_dev_state(hdl, &state))
276 verb_error(err);
277 else
278 (void) printf("%s\n", state == CT_DEV_EV_ONLINE ? "online" :
279 state == CT_DEV_EV_DEGRADED ? "degraded" : "offline");
280 }
281
282 /*
283 * verb_fatal
284 *
285 * Display the events in the fatal event set.
286 */
287 static void
verb_fatal(ct_stathdl_t hdl)288 verb_fatal(ct_stathdl_t hdl)
289 {
290 uint_t event;
291 int err;
292
293 if (err = ct_pr_status_get_fatal(hdl, &event))
294 verb_error(err);
295 else
296 print_bits(event, option_events);
297 }
298
299 /*
300 * verb_members
301 *
302 * Display the list of member contracts.
303 */
304 static void
verb_members(ct_stathdl_t hdl)305 verb_members(ct_stathdl_t hdl)
306 {
307 pid_t *pids;
308 uint_t npids;
309 int err;
310
311 if (err = ct_pr_status_get_members(hdl, &pids, &npids)) {
312 verb_error(err);
313 return;
314 }
315
316 print_ids(pids, npids);
317 }
318
319 /*
320 * verb_inherit
321 *
322 * Display the list of inherited contracts.
323 */
324 static void
verb_inherit(ct_stathdl_t hdl)325 verb_inherit(ct_stathdl_t hdl)
326 {
327 ctid_t *ctids;
328 uint_t nctids;
329 int err;
330
331 if (err = ct_pr_status_get_contracts(hdl, &ctids, &nctids))
332 verb_error(err);
333 else
334 print_ids(ctids, nctids);
335 }
336
337 /*
338 * verb_svc_fmri
339 *
340 * Display the process contract service fmri
341 */
342 static void
verb_svc_fmri(ct_stathdl_t hdl)343 verb_svc_fmri(ct_stathdl_t hdl)
344 {
345 char *svc_fmri;
346 int err;
347 if (err = ct_pr_status_get_svc_fmri(hdl, &svc_fmri))
348 verb_error(err);
349 else
350 (void) printf("%s\n", svc_fmri);
351 }
352
353 /*
354 * verb_svc_aux
355 *
356 * Display the process contract service fmri auxiliar
357 */
358 static void
verb_svc_aux(ct_stathdl_t hdl)359 verb_svc_aux(ct_stathdl_t hdl)
360 {
361 char *svc_aux;
362 int err;
363 if (err = ct_pr_status_get_svc_aux(hdl, &svc_aux))
364 verb_error(err);
365 else
366 (void) printf("%s\n", svc_aux);
367 }
368
369 /*
370 * verb_svc_ctid
371 *
372 * Display the process contract service fmri ctid
373 */
374 static void
verb_svc_ctid(ct_stathdl_t hdl)375 verb_svc_ctid(ct_stathdl_t hdl)
376 {
377 ctid_t svc_ctid;
378 int err;
379 if (err = ct_pr_status_get_svc_ctid(hdl, &svc_ctid))
380 verb_error(err);
381 else
382 (void) printf("%ld\n", svc_ctid);
383 }
384
385 /*
386 * verb_svc_creator
387 *
388 * Display the process contract creator's execname
389 */
390 static void
verb_svc_creator(ct_stathdl_t hdl)391 verb_svc_creator(ct_stathdl_t hdl)
392 {
393 char *svc_creator;
394 int err;
395 if (err = ct_pr_status_get_svc_creator(hdl, &svc_creator))
396 verb_error(err);
397 else
398 (void) printf("%s\n", svc_creator);
399 }
400
401 /*
402 * Common contract status fields.
403 */
404 static verbout_t vcommon[] = {
405 "cookie", verb_cookie,
406 NULL,
407 };
408
409 /*
410 * Process contract-specific status fields.
411 * The critical and informative event sets are here because the event
412 * names are contract-specific. They are listed first, however, so
413 * they are displayed adjacent to the "normal" common output.
414 */
415 static verbout_t vprocess[] = {
416 "informative event set", verb_info,
417 "critical event set", verb_crit,
418 "fatal event set", verb_fatal,
419 "parameter set", verb_param,
420 "member processes", verb_members,
421 "inherited contracts", verb_inherit,
422 "service fmri", verb_svc_fmri,
423 "service fmri ctid", verb_svc_ctid,
424 "creator", verb_svc_creator,
425 "aux", verb_svc_aux,
426 NULL
427 };
428
429 static verbout_t vdevice[] = {
430 "device", verb_minor,
431 "dev_state", verb_dev_state,
432 NULL
433 };
434
435 /*
436 * print_verbose
437 *
438 * Displays a contract's verbose status, common fields first.
439 */
440 static void
print_verbose(ct_stathdl_t hdl,verbout_t * spec,verbout_t * common)441 print_verbose(ct_stathdl_t hdl, verbout_t *spec, verbout_t *common)
442 {
443 int i;
444 int tmp, maxwidth = 0;
445
446 /*
447 * Compute the width of all the fields.
448 */
449 for (i = 0; common[i].label; i++)
450 if ((tmp = strlen(common[i].label)) > maxwidth)
451 maxwidth = tmp;
452 if (spec)
453 for (i = 0; spec[i].label; i++)
454 if ((tmp = strlen(spec[i].label)) > maxwidth)
455 maxwidth = tmp;
456 maxwidth += 2;
457
458 /*
459 * Display the data.
460 */
461 for (i = 0; common[i].label; i++) {
462 tmp = printf("\t%s", common[i].label);
463 if (tmp < 0)
464 tmp = 0;
465 (void) printf("%-*s", maxwidth - tmp + 1, ":");
466 common[i].func(hdl);
467 }
468 if (spec)
469 for (i = 0; spec[i].label; i++) {
470 (void) printf("\t%s%n", spec[i].label, &tmp);
471 (void) printf("%-*s", maxwidth - tmp + 1, ":");
472 spec[i].func(hdl);
473 }
474 }
475
476 struct {
477 const char *name;
478 verbout_t *verbout;
479 } cttypes[] = {
480 { "process", vprocess },
481 { "device", vdevice },
482 { NULL }
483 };
484
485 /*
486 * get_type
487 *
488 * Given a type name, return an index into the above array of types.
489 */
490 static int
get_type(const char * typestr)491 get_type(const char *typestr)
492 {
493 int i;
494 for (i = 0; cttypes[i].name; i++)
495 if (strcmp(cttypes[i].name, typestr) == 0)
496 return (i);
497 uu_die(gettext("invalid contract type: %s\n"), typestr);
498 /* NOTREACHED */
499 }
500
501 /*
502 * print_header
503 *
504 * Display the status header.
505 */
506 static void
print_header(void)507 print_header(void)
508 {
509 (void) printf("%-8s%-8s%-8s%-8s%-8s%-8s%-8s%-8s\n", "CTID", "ZONEID",
510 "TYPE", "STATE", "HOLDER", "EVENTS", "QTIME", "NTIME");
511 }
512
513 /*
514 * print_contract
515 *
516 * Display status for contract ID 'id' from type directory 'dir'. If
517 * only contracts of a specific set of types should be displayed,
518 * 'types' will be a sorted list of type indices of length 'ntypes'.
519 */
520 static void
print_contract(const char * dir,ctid_t id,verbout_t * spec,int * types,int ntypes)521 print_contract(const char *dir, ctid_t id, verbout_t *spec,
522 int *types, int ntypes)
523 {
524 ct_stathdl_t status;
525 char hstr[100], qstr[20], nstr[20];
526 ctstate_t state;
527 int fd = 0;
528 int t;
529
530 /*
531 * Open and obtain status.
532 */
533 if ((fd = contract_open(id, dir, "status", O_RDONLY)) == -1) {
534 if (errno == ENOENT)
535 return;
536 uu_die(gettext("could not open contract status file"));
537 }
538
539 if (errno = ct_status_read(fd, opt_verbose ? CTD_ALL : CTD_COMMON,
540 &status))
541 uu_die(gettext("failed to get contract status for %d"), id);
542 (void) close(fd);
543
544 /*
545 * Unless otherwise directed, don't display dead contracts.
546 */
547 state = ct_status_get_state(status);
548 if (!opt_showall && state == CTS_DEAD) {
549 ct_status_free(status);
550 return;
551 }
552
553 /*
554 * If we are only allowed to display certain contract types,
555 * perform that filtering here. We stash a copy of spec so we
556 * don't have to recompute it later.
557 */
558 if (types) {
559 int key = get_type(ct_status_get_type(status));
560 spec = cttypes[key].verbout;
561 if (bsearch(&key, types, ntypes, sizeof (int), int_compar) ==
562 NULL) {
563 ct_status_free(status);
564 return;
565 }
566 }
567
568 /*
569 * Precompute those fields which have both textual and
570 * numerical values.
571 */
572 if ((state == CTS_OWNED) || (state == CTS_INHERITED))
573 (void) snprintf(hstr, sizeof (hstr), "%ld",
574 ct_status_get_holder(status));
575 else
576 (void) snprintf(hstr, sizeof (hstr), "%s", "-");
577
578 if ((t = ct_status_get_qtime(status)) == -1) {
579 qstr[0] = nstr[0] = '-';
580 qstr[1] = nstr[1] = '\0';
581 } else {
582 (void) snprintf(qstr, sizeof (qstr), "%d", t);
583 (void) snprintf(nstr, sizeof (nstr), "%d",
584 ct_status_get_ntime(status));
585 }
586
587 /*
588 * Emit the contract's status.
589 */
590 (void) printf("%-7ld %-7ld %-7s %-7s %-7s %-7d %-7s %-8s\n",
591 ct_status_get_id(status),
592 ct_status_get_zoneid(status),
593 ct_status_get_type(status),
594 (state == CTS_OWNED) ? "owned" :
595 (state == CTS_INHERITED) ? "inherit" :
596 (state == CTS_ORPHAN) ? "orphan" : "dead", hstr,
597 ct_status_get_nevents(status), qstr, nstr);
598
599 /*
600 * Emit verbose status information, if requested. If we
601 * weren't provided a verbose output spec or didn't compute it
602 * earlier, do it now.
603 */
604 if (opt_verbose) {
605 if (spec == NULL)
606 spec = cttypes[get_type(ct_status_get_type(status))].
607 verbout;
608 print_verbose(status, spec, vcommon);
609 }
610
611 ct_status_free(status);
612 }
613
614 /*
615 * scan_type
616 *
617 * Display all contracts of the requested type.
618 */
619 static void
scan_type(int typeno)620 scan_type(int typeno)
621 {
622 DIR *dir;
623 struct dirent64 *de;
624 char path[PATH_MAX];
625
626 verbout_t *vo = cttypes[typeno].verbout;
627 const char *type = cttypes[typeno].name;
628
629 if (snprintf(path, PATH_MAX, CTFS_ROOT "/%s", type) >= PATH_MAX ||
630 (dir = opendir(path)) == NULL)
631 uu_die(gettext("bad contract type: %s\n"), type);
632 while ((de = readdir64(dir)) != NULL) {
633 /*
634 * Eliminate special files (e.g. '.', '..').
635 */
636 if (de->d_name[0] < '0' || de->d_name[0] > '9')
637 continue;
638 print_contract(type, mystrtoul(de->d_name), vo, NULL, 0);
639 }
640 (void) closedir(dir);
641 }
642
643 /*
644 * scan_ids
645 *
646 * Display all contracts with the requested IDs.
647 */
648 static void
scan_ids(ctid_t * ids,int nids)649 scan_ids(ctid_t *ids, int nids)
650 {
651 int i;
652 for (i = 0; i < nids; i++)
653 print_contract("all", ids[i], NULL, NULL, 0);
654 }
655
656 /*
657 * scan_all
658 *
659 * Display the union of the requested IDs and types. So that the
660 * output is sorted by contract ID, it takes the slow road by testing
661 * each entry in /system/contract/all against its criteria. Used when
662 * the number of types is greater than 1, when we have a mixture of
663 * types and ids, or no lists were provided at all.
664 */
665 static void
scan_all(int * types,int ntypes,ctid_t * ids,int nids)666 scan_all(int *types, int ntypes, ctid_t *ids, int nids)
667 {
668 DIR *dir;
669 struct dirent64 *de;
670 const char *path = CTFS_ROOT "/all";
671 int key, test;
672
673 if ((dir = opendir(path)) == NULL)
674 uu_die(gettext("could not open %s"), path);
675 while ((de = readdir64(dir)) != NULL) {
676 /*
677 * Eliminate special files (e.g. '.', '..').
678 */
679 if (de->d_name[0] < '0' || de->d_name[0] > '9')
680 continue;
681 key = mystrtoul(de->d_name);
682
683 /*
684 * If we are given IDs to look at and this contract
685 * isn't in the ID list, or if we weren't given a list
686 * if IDs but were given a list of types, provide the
687 * list of acceptable types to print_contract.
688 */
689 test = nids ? (bsearch(&key, ids, nids, sizeof (int),
690 int_compar) == NULL) : (ntypes != 0);
691 print_contract("all", key, NULL, (test ? types : NULL), ntypes);
692 }
693 (void) closedir(dir);
694 }
695
696 /*
697 * walk_args
698 *
699 * Apply fp to each token in the comma- or space- separated argument
700 * string str and store the results in the array starting at results.
701 */
702 static int
walk_args(const char * str,int (* fp)(const char *),int * results)703 walk_args(const char *str, int (*fp)(const char *), int *results)
704 {
705 char *copy, *token;
706 int count = 0;
707
708 if ((copy = strdup(str)) == NULL)
709 uu_die(gettext("strdup() failed"));
710
711 token = strtok(copy, ", ");
712 if (token == NULL) {
713 free(copy);
714 return (0);
715 }
716
717 do {
718 if (fp)
719 *(results++) = fp(token);
720 count++;
721 } while (token = strtok(NULL, ", "));
722 free(copy);
723
724 return (count);
725 }
726
727 /*
728 * parse
729 *
730 * Parse the comma- or space- separated string str, using fp to covert
731 * the tokens to integers. Append the list of integers to the array
732 * pointed to by *idps, growing the array if necessary.
733 */
734 static int
parse(const char * str,int ** idsp,int nids,int (* fp)(const char * fp))735 parse(const char *str, int **idsp, int nids, int (*fp)(const char *fp))
736 {
737 int count;
738 int *array;
739
740 count = walk_args(str, NULL, NULL);
741 if (count == 0)
742 return (0);
743
744 if ((array = calloc(nids + count, sizeof (int))) == NULL)
745 uu_die(gettext("calloc() failed"));
746
747 if (*idsp) {
748 (void) memcpy(array, *idsp, nids * sizeof (int));
749 free(*idsp);
750 }
751
752 (void) walk_args(str, fp, array + nids);
753
754 *idsp = array;
755 return (count + nids);
756 }
757
758 /*
759 * parse_ids
760 *
761 * Extract a list of ids from the comma- or space- separated string str
762 * and append them to the array *idsp, growing it if necessary.
763 */
764 static int
parse_ids(const char * arg,int ** idsp,int nids)765 parse_ids(const char *arg, int **idsp, int nids)
766 {
767 return (parse(arg, idsp, nids, mystrtoul));
768 }
769
770 /*
771 * parse_types
772 *
773 * Extract a list of types from the comma- or space- separated string
774 * str and append them to the array *idsp, growing it if necessary.
775 */
776 static int
parse_types(const char * arg,int ** typesp,int ntypes)777 parse_types(const char *arg, int **typesp, int ntypes)
778 {
779 return (parse(arg, typesp, ntypes, get_type));
780 }
781
782 /*
783 * compact
784 *
785 * Sorts and removes duplicates from array. Initial size of array is
786 * in *size; final size is stored in *size.
787 */
788 static void
compact(int * array,int * size)789 compact(int *array, int *size)
790 {
791 int i, j, last = -1;
792
793 qsort(array, *size, sizeof (int), int_compar);
794 for (i = j = 0; i < *size; i++) {
795 if (array[i] != last) {
796 last = array[i];
797 array[j++] = array[i];
798 }
799 }
800 *size = j;
801 }
802
803 int
main(int argc,char ** argv)804 main(int argc, char **argv)
805 {
806 unsigned int interval = 0, count = 1;
807 ctid_t *ids = NULL;
808 int *types = NULL;
809 int nids = 0, ntypes = 0;
810 int i, s;
811
812 (void) setlocale(LC_ALL, "");
813 (void) textdomain(TEXT_DOMAIN);
814
815 (void) uu_setpname(argv[0]);
816
817 while ((s = getopt(argc, argv, "ai:T:t:v")) != EOF) {
818 switch (s) {
819 case 'a':
820 opt_showall = 1;
821 break;
822 case 'i':
823 nids = parse_ids(optarg, (int **)&ids, nids);
824 break;
825 case 'T':
826 if (optarg) {
827 if (*optarg == 'u')
828 timestamp_fmt = UDATE;
829 else if (*optarg == 'd')
830 timestamp_fmt = DDATE;
831 else
832 usage();
833 } else {
834 usage();
835 }
836 break;
837 case 't':
838 ntypes = parse_types(optarg, &types, ntypes);
839 break;
840 case 'v':
841 opt_verbose = 1;
842 break;
843 default:
844 usage();
845 }
846 }
847
848 argc -= optind;
849 argv += optind;
850
851 if (argc > 2 || argc < 0)
852 usage();
853
854 if (argc > 0) {
855 interval = mystrtoul(argv[0]);
856 count = 0;
857 }
858
859 if (argc > 1) {
860 count = mystrtoul(argv[1]);
861 if (count == 0)
862 return (0);
863 }
864
865 if (nids)
866 compact((int *)ids, &nids);
867 if (ntypes)
868 compact(types, &ntypes);
869
870 for (i = 0; count == 0 || i < count; i++) {
871 if (i)
872 (void) sleep(interval);
873 if (timestamp_fmt != NODATE)
874 print_timestamp(timestamp_fmt);
875 print_header();
876 if (nids && ntypes)
877 scan_all(types, ntypes, ids, nids);
878 else if (ntypes == 1)
879 scan_type(*types);
880 else if (nids)
881 scan_ids(ids, nids);
882 else
883 scan_all(types, ntypes, ids, nids);
884 }
885
886 return (0);
887 }
888