xref: /illumos-gate/usr/src/cmd/ctstat/ctstat.c (revision 2bbdd445a21f9d61f4a0ca0faf05d5ceb2bd91f3)
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
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
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
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
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
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
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
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
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, &param))
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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