xref: /illumos-gate/usr/src/cmd/rpcsvc/rstat_proc.c (revision 79033acb)
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, Version 1.0 only
6  * (the "License").  You may not use this file except in compliance
7  * with the License.
8  *
9  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
10  * or http://www.opensolaris.org/os/licensing.
11  * See the License for the specific language governing permissions
12  * and limitations under the License.
13  *
14  * When distributing Covered Code, include this CDDL HEADER in each
15  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
16  * If applicable, add the following below this CDDL HEADER, with the
17  * fields enclosed by brackets "[]" replaced with your own identifying
18  * information: Portions Copyright [yyyy] [name of copyright owner]
19  *
20  * CDDL HEADER END
21  */
22 /*
23  * Copyright 2005 Sun Microsystems, Inc.  All rights reserved.
24  * Use is subject to license terms.
25  */
26 
27 #pragma ident	"%Z%%M%	%I%	%E% SMI"
28 
29 /*
30  * rstat service:  built with rstat.x
31  */
32 
33 #include <stdio.h>
34 #include <stdlib.h>
35 #include <stdarg.h>
36 #include <string.h>
37 #include <signal.h>
38 #include <utmpx.h>
39 #include <nlist.h>
40 #include <fcntl.h>
41 #include <syslog.h>
42 #include <kstat.h>
43 
44 #include <rpc/rpc.h>
45 
46 #include <sys/socket.h>
47 #include <sys/cpuvar.h>
48 #include <sys/sysinfo.h>
49 #include <sys/systm.h>
50 #include <errno.h>
51 #include <sys/stropts.h>
52 #include <sys/tihdr.h>
53 #include <sys/sysmacros.h>
54 
55 #include <net/if.h>
56 #include <inet/mib2.h>
57 
58 #include "rstat.h"
59 #include "rstat_v2.h"
60 
61 typedef struct {
62 	kstat_t	sys;
63 	kstat_t	vm;
64 } _cpu_stats_t;
65 
66 /*
67  *	system and cpu stats
68  */
69 static	kstat_ctl_t	*kc;		/* libkstat cookie */
70 static	int	ncpus;
71 static	_cpu_stats_t	*cpu_stats_list = NULL;
72 static	kstat_t	*system_misc_ksp;
73 static	kstat_named_t *boot_time_knp;
74 static	kstat_named_t *avenrun_1min_knp, *avenrun_5min_knp, *avenrun_15min_knp;
75 static	int	hz;
76 static	struct	timeval btm;		/* boottime */
77 
78 /*
79  *	network interface stats
80  */
81 
82 typedef struct mib_item_s {
83 	struct mib_item_s	*next_item;
84 	long			group;
85 	long			mib_id;
86 	long			length;
87 	char			*valp;
88 } mib_item_t;
89 
90 mib_item_t	*netstat_item;
91 
92 /*
93  * disk stats
94  */
95 
96 struct diskinfo {
97 	struct diskinfo *next;
98 	kstat_t *ks;
99 	kstat_io_t kios;
100 };
101 
102 #define	NULLDISK (struct diskinfo *)0
103 static	struct diskinfo zerodisk = { NULL, NULL };
104 static	struct diskinfo *firstdisk = NULLDISK;
105 static	struct diskinfo *lastdisk = NULLDISK;
106 static	struct diskinfo *snip = NULLDISK;
107 static	int ndisks;
108 
109 /*
110  * net stats
111  */
112 
113 struct netinfo {
114 	struct netinfo *next;
115 	kstat_t	*ks;
116 	kstat_named_t *ipackets;
117 	kstat_named_t *opackets;
118 	kstat_named_t *ierrors;
119 	kstat_named_t *oerrors;
120 	kstat_named_t *collisions;
121 };
122 
123 #define	NULLNET (struct netinfo *)0
124 static	struct netinfo zeronet = { NULL, NULL, NULL, NULL, NULL, NULL, NULL };
125 static	struct netinfo *firstnet = NULLNET;
126 static	struct netinfo *lastnet = NULLNET;
127 static	struct netinfo *netsnip = NULLNET;
128 static	int nnets;
129 
130 /*
131  *  Define EXIT_WHEN_IDLE if you are able to have this program invoked
132  *  automatically on demand (as from inetd).  When defined, the service
133  *  will terminated after being idle for 120 seconds.
134  */
135 
136 #define	EXIT_WHEN_IDLE	1
137 
138 int sincelastreq = 0;		/* number of alarms since last request */
139 #ifdef EXIT_WHEN_IDLE
140 #define	CLOSEDOWN 120		/* how long to wait before exiting */
141 #endif /* def EXIT_WHEN_IDLE */
142 
143 statstime stats_s3;
144 statsvar stats_s4;
145 /* V2 support for backwards compatibility to pre-5.0 systems */
146 statsswtch stats_s2;
147 
148 static int stat_is_init = 0;
149 
150 static	void	fail(int, char *, ...);
151 static	void	safe_zalloc(void **, int, int);
152 static	kid_t	safe_kstat_read(kstat_ctl_t *, kstat_t *, void *);
153 static	kstat_t	*safe_kstat_lookup(kstat_ctl_t *, char *, int, char *);
154 static	void	*safe_kstat_data_lookup(kstat_t *, char *);
155 static	void	system_stat_init(void);
156 static	int	system_stat_load(void);
157 static	void	init_disks(void);
158 static	int	diskinfo_load(void);
159 static	void	init_net(void);
160 static	int	netinfo_load(void);
161 
162 static	void	updatestat(int);
163 
164 static	mib_item_t	*mibget(int sd);
165 static	int	mibopen(void);
166 static  char	*octetstr(char *buf, Octet_t *op, int code);
167 
168 static	void	kstat_copy(kstat_t *, kstat_t *, int);
169 
170 static	char	*cmdname = "rpc.rstatd";
171 
172 #define	CPU_STAT(ksp, name)	(((kstat_named_t *)safe_kstat_data_lookup( \
173 				    (ksp), (name)))->value.ui64)
174 static	_cpu_stats_t	cpu_stats_all = { 0 };
175 
176 static void
177 stat_init(void)
178 {
179 	struct utmpx *utmpx, utmpx_id;
180 
181 	stat_is_init = 1;
182 
183 	if ((kc = kstat_open()) == NULL)
184 		fail(1, "kstat_open(): can't open /dev/kstat");
185 
186 	/*
187 	 * Preallocate minimal set of drive entries.
188 	 */
189 
190 	if (stats_s4.dk_xfer.dk_xfer_val == NULL) {
191 		stats_s4.dk_xfer.dk_xfer_len = RSTAT_DK_NDRIVE;
192 		stats_s4.dk_xfer.dk_xfer_val =
193 		    (int *)calloc(RSTAT_DK_NDRIVE, sizeof (int));
194 	}
195 
196 	system_stat_init();
197 	init_disks();
198 	init_net();
199 
200 	/*
201 	 * To get the boot time, use utmpx, which is per-zone, but fall back
202 	 * to the system-wide kstat if utmpx is hosed for any reason.
203 	 */
204 	utmpx_id.ut_type = BOOT_TIME;
205 	if ((utmpx = getutxid(&utmpx_id)) != NULL)
206 		btm = utmpx->ut_tv;
207 	else {
208 		btm.tv_sec = boot_time_knp->value.ul;
209 		btm.tv_usec = 0; /* don't bother with usecs for boot time */
210 	}
211 	endutxent();
212 	stats_s4.boottime.tv_sec =
213 		stats_s2.boottime.tv_sec =
214 		stats_s3.boottime.tv_sec = btm.tv_sec;
215 	stats_s4.boottime.tv_usec =
216 		stats_s2.boottime.tv_usec =
217 		stats_s3.boottime.tv_usec = btm.tv_usec;
218 
219 	updatestat(0);
220 	alarm(1);
221 	signal(SIGALRM, updatestat);
222 	sleep(2);		/* allow for one wake-up */
223 }
224 
225 statsvar *
226 rstatproc_stats_4_svc(argp, svcrq)
227 void *argp;
228 struct svc_req *svcrq;
229 {
230 	if (! stat_is_init)
231 		stat_init();
232 #ifdef EXIT_WHEN_IDLE
233 	sincelastreq = 0;
234 #endif
235 	return (&stats_s4);
236 }
237 
238 statstime *
239 rstatproc_stats_3_svc(argp, svcrq)
240 void *argp;
241 struct svc_req *svcrq;
242 {
243 	if (! stat_is_init)
244 		stat_init();
245 #ifdef EXIT_WHEN_IDLE
246 	sincelastreq = 0;
247 #endif
248 	return (&stats_s3);
249 }
250 
251 statsswtch *
252 rstatproc_stats_2_svc(argp, svcrq)
253 void *argp;
254 struct svc_req *svcrq;
255 {
256 	if (! stat_is_init)
257 		stat_init();
258 #ifdef EXIT_WHEN_IDLE
259 	sincelastreq = 0;
260 #endif
261 	return (&stats_s2);
262 }
263 
264 
265 uint_t *
266 rstatproc_havedisk_4_svc(argp, svcrq)
267 void *argp;
268 struct svc_req *svcrq;
269 {
270 	return (rstatproc_havedisk_3_svc(argp, svcrq));
271 }
272 
273 uint_t *
274 rstatproc_havedisk_3_svc(argp, svcrq)
275 void *argp;
276 struct svc_req *svcrq;
277 {
278 	static uint_t have;
279 
280 	if (! stat_is_init)
281 		stat_init();
282 #ifdef EXIT_WHEN_IDLE
283 	sincelastreq = 0;
284 #endif
285 	have = (ndisks != 0);
286 	return (&have);
287 }
288 
289 uint_t *
290 rstatproc_havedisk_2_svc(argp, svcrq)
291 void *argp;
292 struct svc_req *svcrq;
293 {
294 	return (rstatproc_havedisk_3_svc(argp, svcrq));
295 }
296 
297 void
298 updatestat(int ignored)
299 {
300 extern int _rpcpmstart;		 /* Started by a port monitor ? */
301 extern int _rpcsvcdirty;	 /* Still serving ? */
302 
303 #ifdef DEBUG
304 	fprintf(stderr, "entering updatestat\n");
305 #endif
306 #ifdef EXIT_WHEN_IDLE
307 	if (_rpcpmstart && sincelastreq >= CLOSEDOWN && !_rpcsvcdirty) {
308 #ifdef DEBUG
309 		fprintf(stderr, "about to closedown\n");
310 #endif
311 		exit(0);
312 	}
313 	sincelastreq++;
314 #endif /* def EXIT_WHEN_IDLE */
315 
316 	(void) alarm(0);
317 #ifdef DEBUG
318 	fprintf(stderr, "boottime: %d %d\n", stats_s3.boottime.tv_sec,
319 		stats_s3.boottime.tv_usec);
320 #endif
321 	while (system_stat_load() || diskinfo_load() || netinfo_load()) {
322 		(void) kstat_chain_update(kc);
323 		system_stat_init();
324 		init_disks();
325 		init_net();
326 	}
327 	stats_s4.cp_time.cp_time_len = CPU_STATES;
328 	if (stats_s4.cp_time.cp_time_val == NULL)
329 		stats_s4.cp_time.cp_time_val =
330 		malloc(stats_s4.cp_time.cp_time_len * sizeof (int));
331 	stats_s2.cp_time[RSTAT_CPU_USER] =
332 	stats_s3.cp_time[RSTAT_CPU_USER] =
333 	stats_s4.cp_time.cp_time_val[RSTAT_CPU_USER] =
334 		CPU_STAT(&cpu_stats_all.sys, "cpu_ticks_user");
335 	stats_s2.cp_time[RSTAT_CPU_NICE] =
336 	stats_s3.cp_time[RSTAT_CPU_NICE] =
337 	stats_s4.cp_time.cp_time_val[RSTAT_CPU_NICE] =
338 		CPU_STAT(&cpu_stats_all.sys, "cpu_ticks_wait");
339 	stats_s2.cp_time[RSTAT_CPU_SYS] =
340 	stats_s3.cp_time[RSTAT_CPU_SYS] =
341 	stats_s4.cp_time.cp_time_val[RSTAT_CPU_SYS] =
342 		CPU_STAT(&cpu_stats_all.sys, "cpu_ticks_kernel");
343 	stats_s2.cp_time[RSTAT_CPU_IDLE] =
344 	stats_s3.cp_time[RSTAT_CPU_IDLE] =
345 	stats_s4.cp_time.cp_time_val[RSTAT_CPU_IDLE] =
346 		CPU_STAT(&cpu_stats_all.sys, "cpu_ticks_idle");
347 
348 #ifdef DEBUG
349 	fprintf(stderr, "cpu: %d %d %d %d\n",
350 		CPU_STAT(&cpu_stats_all.sys, "cpu_ticks_user"),
351 		CPU_STAT(&cpu_stats_all.sys, "cpu_ticks_wait"),
352 		CPU_STAT(&cpu_stats_all.sys, "cpu_ticks_kernel"),
353 		CPU_STAT(&cpu_stats_all.sys, "cpu_ticks_idle"));
354 	fprintf(stderr, "cp_time: %d %d %d %d\n",
355 		stats_s3.cp_time[RSTAT_CPU_USER],
356 		stats_s3.cp_time[RSTAT_CPU_NICE],
357 		stats_s3.cp_time[RSTAT_CPU_SYS],
358 		stats_s3.cp_time[RSTAT_CPU_IDLE]);
359 #endif
360 
361 	/* current time */
362 	gettimeofday((struct timeval *)&stats_s3.curtime, NULL);
363 	stats_s4.curtime = stats_s3.curtime;
364 
365 	stats_s2.v_pgpgin =
366 	stats_s3.v_pgpgin =
367 	stats_s4.v_pgpgin = CPU_STAT(&cpu_stats_all.vm, "pgpgin");
368 	stats_s2.v_pgpgout =
369 	stats_s3.v_pgpgout =
370 	stats_s4.v_pgpgout = CPU_STAT(&cpu_stats_all.vm, "pgpgout");
371 	stats_s2.v_pswpin =
372 	stats_s3.v_pswpin =
373 	stats_s4.v_pswpin = CPU_STAT(&cpu_stats_all.vm, "pgswapin");
374 	stats_s2.v_pswpout =
375 	stats_s3.v_pswpout =
376 	stats_s4.v_pswpout = CPU_STAT(&cpu_stats_all.vm, "pgswapout");
377 	stats_s3.v_intr = CPU_STAT(&cpu_stats_all.sys, "intr");
378 	stats_s3.v_intr -= hz*(stats_s3.curtime.tv_sec - btm.tv_sec) +
379 		hz*(stats_s3.curtime.tv_usec - btm.tv_usec)/1000000;
380 	stats_s2.v_intr =
381 	stats_s4.v_intr = stats_s3.v_intr;
382 	/* swtch not in V1 */
383 	stats_s2.v_swtch =
384 	stats_s3.v_swtch =
385 	stats_s4.v_swtch = CPU_STAT(&cpu_stats_all.sys, "pswitch");
386 
387 #ifdef DEBUG
388 	fprintf(stderr,
389 		"pgin: %d pgout: %d swpin: %d swpout: %d intr: %d swtch: %d\n",
390 		stats_s3.v_pgpgin,
391 		stats_s3.v_pgpgout,
392 		stats_s3.v_pswpin,
393 		stats_s3.v_pswpout,
394 		stats_s3.v_intr,
395 		stats_s3.v_swtch);
396 #endif
397 	/*
398 	 * V2 and V3 of rstat are limited to RSTAT_DK_NDRIVE drives
399 	 */
400 	memcpy(stats_s3.dk_xfer, stats_s4.dk_xfer.dk_xfer_val,
401 		RSTAT_DK_NDRIVE * sizeof (int));
402 	memcpy(stats_s2.dk_xfer, stats_s4.dk_xfer.dk_xfer_val,
403 		RSTAT_DK_NDRIVE * sizeof (int));
404 #ifdef DEBUG
405 	fprintf(stderr, "dk_xfer: %d %d %d %d\n",
406 		stats_s4.dk_xfer.dk_xfer_val[0],
407 		stats_s4.dk_xfer.dk_xfer_val[1],
408 		stats_s4.dk_xfer.dk_xfer_val[2],
409 		stats_s4.dk_xfer.dk_xfer_val[3]);
410 #endif
411 
412 	stats_s2.if_ipackets =
413 	stats_s3.if_ipackets = stats_s4.if_ipackets;
414 	/* no s2 opackets */
415 	stats_s3.if_opackets = stats_s4.if_opackets;
416 	stats_s2.if_ierrors =
417 	stats_s3.if_ierrors = stats_s4.if_ierrors;
418 	stats_s2.if_oerrors =
419 	stats_s3.if_oerrors = stats_s4.if_oerrors;
420 	stats_s2.if_collisions =
421 	stats_s3.if_collisions = stats_s4.if_collisions;
422 
423 	stats_s2.avenrun[0] =
424 	stats_s3.avenrun[0] =
425 	stats_s4.avenrun[0] = avenrun_1min_knp->value.ul;
426 	stats_s2.avenrun[1] =
427 	stats_s3.avenrun[1] =
428 	stats_s4.avenrun[1] = avenrun_5min_knp->value.ul;
429 	stats_s2.avenrun[2] =
430 	stats_s3.avenrun[2] =
431 	stats_s4.avenrun[2] = avenrun_15min_knp->value.ul;
432 #ifdef DEBUG
433 	fprintf(stderr, "avenrun: %d %d %d\n", stats_s3.avenrun[0],
434 		stats_s3.avenrun[1], stats_s3.avenrun[2]);
435 #endif
436 	signal(SIGALRM, updatestat);
437 	alarm(1);
438 }
439 
440 /* --------------------------------- MIBGET -------------------------------- */
441 
442 static mib_item_t *
443 mibget(int sd)
444 {
445 	int			flags;
446 	int			j, getcode;
447 	struct strbuf		ctlbuf, databuf;
448 	char			buf[512];
449 	struct T_optmgmt_req	*tor = (struct T_optmgmt_req *)buf;
450 	struct T_optmgmt_ack	*toa = (struct T_optmgmt_ack *)buf;
451 	struct T_error_ack	*tea = (struct T_error_ack *)buf;
452 	struct opthdr		*req;
453 	mib_item_t		*first_item = NULL;
454 	mib_item_t		*last_item  = NULL;
455 	mib_item_t		*temp;
456 
457 	tor->PRIM_type = T_SVR4_OPTMGMT_REQ;
458 	tor->OPT_offset = sizeof (struct T_optmgmt_req);
459 	tor->OPT_length = sizeof (struct opthdr);
460 	tor->MGMT_flags = T_CURRENT;
461 	req = (struct opthdr *)&tor[1];
462 	req->level = MIB2_IP;		/* any MIB2_xxx value ok here */
463 	req->name  = 0;
464 	req->len   = 0;
465 
466 	ctlbuf.buf = buf;
467 	ctlbuf.len = tor->OPT_length + tor->OPT_offset;
468 	flags = 0;
469 	if (putmsg(sd, &ctlbuf, NULL, flags) == -1) {
470 		perror("mibget: putmsg(ctl) failed");
471 		goto error_exit;
472 	}
473 	/*
474 	 * each reply consists of a ctl part for one fixed structure
475 	 * or table, as defined in mib2.h.  The format is a T_OPTMGMT_ACK,
476 	 * containing an opthdr structure.  level/name identify the entry,
477 	 * len is the size of the data part of the message.
478 	 */
479 	req = (struct opthdr *)&toa[1];
480 	ctlbuf.maxlen = sizeof (buf);
481 	/*CSTYLED*/
482 	for (j = 1; ; j++) {
483 		flags = 0;
484 		getcode = getmsg(sd, &ctlbuf, NULL, &flags);
485 		if (getcode == -1) {
486 #ifdef DEBUG_MIB
487 			perror("mibget getmsg(ctl) failed");
488 			fprintf(stderr, "#   level   name    len\n");
489 			i = 0;
490 			for (last_item = first_item; last_item;
491 				last_item = last_item->next_item)
492 				fprintf(stderr, "%d  %4d   %5d   %d\n", ++i,
493 					last_item->group,
494 					last_item->mib_id,
495 					last_item->length);
496 #endif /* DEBUG_MIB */
497 			goto error_exit;
498 		}
499 		if (getcode == 0 &&
500 			(ctlbuf.len >= sizeof (struct T_optmgmt_ack)) &&
501 			(toa->PRIM_type == T_OPTMGMT_ACK) &&
502 			(toa->MGMT_flags == T_SUCCESS) &&
503 			req->len == 0) {
504 #ifdef DEBUG_MIB
505 			fprintf(stderr,
506 		"mibget getmsg() %d returned EOD (level %d, name %d)\n",
507 				j, req->level, req->name);
508 #endif /* DEBUG_MIB */
509 			return (first_item);		/* this is EOD msg */
510 		}
511 
512 		if (ctlbuf.len >= sizeof (struct T_error_ack) &&
513 			(tea->PRIM_type == T_ERROR_ACK)) {
514 #ifdef DEBUG_MIB
515 			fprintf(stderr,
516 	"mibget %d gives T_ERROR_ACK: TLI_error = 0x%x, UNIX_error = 0x%x\n",
517 				j, getcode, tea->TLI_error, tea->UNIX_error);
518 #endif /* DEBUG_MIB */
519 			errno = (tea->TLI_error == TSYSERR)
520 				? tea->UNIX_error : EPROTO;
521 			goto error_exit;
522 		}
523 
524 		if (getcode != MOREDATA ||
525 			(ctlbuf.len < sizeof (struct T_optmgmt_ack)) ||
526 			(toa->PRIM_type != T_OPTMGMT_ACK) ||
527 			(toa->MGMT_flags != T_SUCCESS)) {
528 #ifdef DEBUG_MIB
529 			fprintf(stderr,
530 	"mibget getmsg(ctl) %d returned %d, ctlbuf.len = %d, PRIM_type = %d\n",
531 				j, getcode, ctlbuf.len, toa->PRIM_type);
532 			if (toa->PRIM_type == T_OPTMGMT_ACK)
533 				fprintf(stderr,
534 	"T_OPTMGMT_ACK: MGMT_flags = 0x%x, req->len = %d\n",
535 					toa->MGMT_flags, req->len);
536 #endif /* DEBUG_MIB */
537 			errno = ENOMSG;
538 			goto error_exit;
539 		}
540 
541 		temp = malloc(sizeof (mib_item_t));
542 		if (!temp) {
543 			perror("mibget malloc failed");
544 			goto error_exit;
545 		}
546 		if (last_item)
547 			last_item->next_item = temp;
548 		else
549 			first_item = temp;
550 		last_item = temp;
551 		last_item->next_item = NULL;
552 		last_item->group = req->level;
553 		last_item->mib_id = req->name;
554 		last_item->length = req->len;
555 		last_item->valp = malloc(req->len);
556 #ifdef DEBUG_MIB
557 		fprintf(stderr,
558 			"msg %d:  group = %4d   mib_id = %5d   length = %d\n",
559 			j, last_item->group, last_item->mib_id,
560 			last_item->length);
561 #endif /* DEBUG_MIB */
562 		databuf.maxlen = last_item->length;
563 		databuf.buf    = last_item->valp;
564 		databuf.len    = 0;
565 		flags = 0;
566 		getcode = getmsg(sd, NULL, &databuf, &flags);
567 		if (getcode == -1) {
568 			perror("mibget getmsg(data) failed");
569 			goto error_exit;
570 		} else if (getcode != 0) {
571 			fprintf(stderr,
572 "mibget getmsg(data) returned %d, databuf.maxlen = %d, databuf.len = %d\n",
573 				getcode, databuf.maxlen, databuf.len);
574 			goto error_exit;
575 		}
576 	}
577 
578 error_exit:
579 	while (first_item) {
580 		last_item = first_item;
581 		first_item = first_item->next_item;
582 		if (last_item->valp) {
583 			free(last_item->valp);
584 		}
585 		free(last_item);
586 	}
587 	return (first_item);
588 }
589 
590 static int
591 mibopen(void)
592 {
593 	int	sd;
594 
595 	/* gives us ip w/ arp on top */
596 	sd = open("/dev/arp", O_RDWR);
597 	if (sd == -1) {
598 		perror("arp open");
599 		close(sd);
600 		return (-1);
601 	}
602 	if (ioctl(sd, I_PUSH, "tcp") == -1) {
603 		perror("tcp I_PUSH");
604 		close(sd);
605 		return (-1);
606 	}
607 	if (ioctl(sd, I_PUSH, "udp") == -1) {
608 		perror("udp I_PUSH");
609 		close(sd);
610 		return (-1);
611 	}
612 	return (sd);
613 }
614 
615 static char *
616 octetstr(char *buf, Octet_t *op, int code)
617 {
618 	int	i;
619 	char	*cp;
620 
621 	cp = buf;
622 	if (op)
623 		for (i = 0; i < op->o_length; i++)
624 			switch (code) {
625 			case 'd':
626 				sprintf(cp, "%d.", 0xff & op->o_bytes[i]);
627 				cp = strchr(cp, '\0');
628 				break;
629 			case 'a':
630 				*cp++ = op->o_bytes[i];
631 				break;
632 			case 'h':
633 			default:
634 				sprintf(cp, "%02x:", 0xff & op->o_bytes[i]);
635 				cp += 3;
636 				break;
637 			}
638 	if (code != 'a' && cp != buf)
639 		cp--;
640 	*cp = '\0';
641 	return (buf);
642 }
643 
644 static void
645 fail(int do_perror, char *message, ...)
646 {
647 	va_list args;
648 
649 	va_start(args, message);
650 	fprintf(stderr, "%s: ", cmdname);
651 	vfprintf(stderr, message, args);
652 	va_end(args);
653 	if (do_perror)
654 		fprintf(stderr, ": %s", strerror(errno));
655 	fprintf(stderr, "\n");
656 	exit(2);
657 }
658 
659 static void
660 safe_zalloc(void **ptr, int size, int free_first)
661 {
662 	if (free_first && *ptr != NULL)
663 		free(*ptr);
664 	if ((*ptr = malloc(size)) == NULL)
665 		fail(1, "malloc failed");
666 	memset(*ptr, 0, size);
667 }
668 
669 kid_t
670 safe_kstat_read(kstat_ctl_t *kctl, kstat_t *ksp, void *data)
671 {
672 	kid_t kstat_chain_id = kstat_read(kctl, ksp, data);
673 
674 	if (kstat_chain_id == -1)
675 		fail(1, "kstat_read(%x, '%s') failed", kctl, ksp->ks_name);
676 	return (kstat_chain_id);
677 }
678 
679 kstat_t *
680 safe_kstat_lookup(kstat_ctl_t *kctl, char *ks_module, int ks_instance,
681 	char *ks_name)
682 {
683 	kstat_t *ksp = kstat_lookup(kctl, ks_module, ks_instance, ks_name);
684 
685 	if (ksp == NULL)
686 		fail(0, "kstat_lookup('%s', %d, '%s') failed",
687 			ks_module == NULL ? "" : ks_module,
688 			ks_instance,
689 			ks_name == NULL ? "" : ks_name);
690 	return (ksp);
691 }
692 
693 void *
694 safe_kstat_data_lookup(kstat_t *ksp, char *name)
695 {
696 	void *fp = kstat_data_lookup(ksp, name);
697 
698 	if (fp == NULL) {
699 		fail(0, "kstat_data_lookup('%s', '%s') failed",
700 			ksp->ks_name, name);
701 	}
702 	return (fp);
703 }
704 
705 /*
706  * Get various KIDs for subsequent system_stat_load operations.
707  */
708 
709 static void
710 system_stat_init(void)
711 {
712 	kstat_t *ksp;
713 	int i, nvmks;
714 
715 	/*
716 	 * Global statistics
717 	 */
718 
719 	system_misc_ksp	= safe_kstat_lookup(kc, "unix", 0, "system_misc");
720 
721 	safe_kstat_read(kc, system_misc_ksp, NULL);
722 	boot_time_knp = safe_kstat_data_lookup(system_misc_ksp, "boot_time");
723 	avenrun_1min_knp = safe_kstat_data_lookup(system_misc_ksp,
724 		"avenrun_1min");
725 	avenrun_5min_knp = safe_kstat_data_lookup(system_misc_ksp,
726 		"avenrun_5min");
727 	avenrun_15min_knp = safe_kstat_data_lookup(system_misc_ksp,
728 		"avenrun_15min");
729 
730 	/*
731 	 * Per-CPU statistics
732 	 */
733 
734 	ncpus = 0;
735 	for (ksp = kc->kc_chain; ksp; ksp = ksp->ks_next)
736 		if (strcmp(ksp->ks_module, "cpu") == 0 &&
737 		    strcmp(ksp->ks_name, "sys") == 0)
738 			ncpus++;
739 
740 	safe_zalloc((void **)&cpu_stats_list, ncpus * sizeof (*cpu_stats_list),
741 	    1);
742 
743 	ncpus = 0;
744 	for (ksp = kc->kc_chain; ksp; ksp = ksp->ks_next)
745 		if (strcmp(ksp->ks_module, "cpu") == 0 &&
746 		    strcmp(ksp->ks_name, "sys") == 0 &&
747 		    kstat_read(kc, ksp, NULL) != -1) {
748 			kstat_copy(ksp, &cpu_stats_list[ncpus].sys,
749 			    1);
750 			if ((ksp = kstat_lookup(kc, "cpu", ksp->ks_instance,
751 			    "vm")) != NULL && kstat_read(kc, ksp, NULL) != -1)
752 				kstat_copy(ksp, &cpu_stats_list[ncpus].vm, 1);
753 			else
754 				fail(0, "couldn't find per-CPU VM statistics");
755 			ncpus++;
756 		    }
757 
758 	if (ncpus == 0)
759 		fail(0, "couldn't find per-CPU statistics");
760 }
761 
762 /*
763  * load statistics, summing across CPUs where needed
764  */
765 
766 static int
767 system_stat_load(void)
768 {
769 	int i, j;
770 	_cpu_stats_t cs;
771 	ulong_t *np, *tp;
772 
773 	/*
774 	 * Global statistics
775 	 */
776 
777 	safe_kstat_read(kc, system_misc_ksp, NULL);
778 
779 	/*
780 	 * Per-CPU statistics.
781 	 */
782 
783 	for (i = 0; i < ncpus; i++) {
784 		if (kstat_read(kc, &cpu_stats_list[i].sys, NULL) == -1 ||
785 		    kstat_read(kc, &cpu_stats_list[i].vm, NULL) == -1)
786 			return (1);
787 		if (i == 0) {
788 			kstat_copy(&cpu_stats_list[0].sys, &cpu_stats_all.sys,
789 			    1);
790 			kstat_copy(&cpu_stats_list[0].vm, &cpu_stats_all.vm, 1);
791 		} else {
792 			kstat_named_t *nkp;
793 			kstat_named_t *tkp;
794 
795 			/*
796 			 * Other CPUs' statistics are accumulated in
797 			 * cpu_stats_all, initialized at the first iteration of
798 			 * the loop.
799 			 */
800 			nkp = (kstat_named_t *)cpu_stats_all.sys.ks_data;
801 			tkp = (kstat_named_t *)cpu_stats_list[i].sys.ks_data;
802 			for (j = 0; j < cpu_stats_list[i].sys.ks_ndata; j++)
803 				(nkp++)->value.ui64 += (tkp++)->value.ui64;
804 			nkp = (kstat_named_t *)cpu_stats_all.vm.ks_data;
805 			tkp = (kstat_named_t *)cpu_stats_list[i].vm.ks_data;
806 			for (j = 0; j < cpu_stats_list[i].vm.ks_ndata; j++)
807 				(nkp++)->value.ui64 += (tkp++)->value.ui64;
808 		}
809 	}
810 	return (0);
811 }
812 
813 static int
814 kscmp(kstat_t *ks1, kstat_t *ks2)
815 {
816 	int cmp;
817 
818 	cmp = strcmp(ks1->ks_module, ks2->ks_module);
819 	if (cmp != 0)
820 		return (cmp);
821 	cmp = ks1->ks_instance - ks2->ks_instance;
822 	if (cmp != 0)
823 		return (cmp);
824 	return (strcmp(ks1->ks_name, ks2->ks_name));
825 }
826 
827 static void
828 init_disks(void)
829 {
830 	struct diskinfo *disk, *prevdisk, *comp;
831 	kstat_t *ksp;
832 
833 	ndisks = 0;
834 	disk = &zerodisk;
835 
836 	/*
837 	 * Patch the snip in the diskinfo list (see below)
838 	 */
839 	if (snip)
840 		lastdisk->next = snip;
841 
842 	for (ksp = kc->kc_chain; ksp; ksp = ksp->ks_next) {
843 
844 		if (ksp->ks_type != KSTAT_TYPE_IO ||
845 		    strcmp(ksp->ks_class, "disk") != 0)
846 			continue;
847 		prevdisk = disk;
848 		if (disk->next)
849 			disk = disk->next;
850 		else {
851 			safe_zalloc((void **)&disk->next,
852 			    sizeof (struct diskinfo), 0);
853 			disk = disk->next;
854 			disk->next = NULLDISK;
855 		}
856 		disk->ks = ksp;
857 		memset((void *)&disk->kios, 0, sizeof (kstat_io_t));
858 		disk->kios.wlastupdate = disk->ks->ks_crtime;
859 		disk->kios.rlastupdate = disk->ks->ks_crtime;
860 
861 		/*
862 		 * Insertion sort on (ks_module, ks_instance, ks_name)
863 		 */
864 		comp = &zerodisk;
865 		while (kscmp(disk->ks, comp->next->ks) > 0)
866 			comp = comp->next;
867 		if (prevdisk != comp) {
868 			prevdisk->next = disk->next;
869 			disk->next = comp->next;
870 			comp->next = disk;
871 			disk = prevdisk;
872 		}
873 		ndisks++;
874 	}
875 	/*
876 	 * Put a snip in the linked list of diskinfos.  The idea:
877 	 * If there was a state change such that now there are fewer
878 	 * disks, we snip the list and retain the tail, rather than
879 	 * freeing it.  At the next state change, we clip the tail back on.
880 	 * This prevents a lot of malloc/free activity, and it's simpler.
881 	 */
882 	lastdisk = disk;
883 	snip = disk->next;
884 	disk->next = NULLDISK;
885 
886 	firstdisk = zerodisk.next;
887 
888 	if (ndisks > stats_s4.dk_xfer.dk_xfer_len) {
889 		stats_s4.dk_xfer.dk_xfer_len = ndisks;
890 		safe_zalloc((void **)&stats_s4.dk_xfer.dk_xfer_val,
891 			ndisks * sizeof (int), 1);
892 	}
893 }
894 
895 static int
896 diskinfo_load(void)
897 {
898 	struct diskinfo *disk;
899 	int i;
900 
901 	for (disk = firstdisk, i = 0; disk; disk = disk->next, i++) {
902 		if (kstat_read(kc, disk->ks, (void *)&disk->kios) == -1)
903 			return (1);
904 		stats_s4.dk_xfer.dk_xfer_val[i] = disk->kios.reads +
905 			disk->kios.writes;
906 	}
907 	return (0);
908 }
909 
910 static void
911 init_net(void)
912 {
913 	static int sd;
914 	mib_item_t *item;
915 	mib2_ipAddrEntry_t *ap;
916 	char namebuf[KSTAT_STRLEN];
917 	struct netinfo *net, *prevnet, *comp;
918 	kstat_t *ksp;
919 
920 	if (sd) {
921 		close(sd);
922 	}
923 	while (netstat_item) {
924 		item = netstat_item;
925 		netstat_item = netstat_item->next_item;
926 		if (item->valp) {
927 			free(item->valp);
928 		}
929 		free(item);
930 	}
931 	sd = mibopen();
932 	if (sd == -1) {
933 #ifdef DEBUG
934 		fprintf(stderr, "mibopen() failed\n");
935 #endif
936 		sd = 0;
937 	} else {
938 		if ((netstat_item = mibget(sd)) == NULL) {
939 #ifdef DEBUG
940 			fprintf(stderr, "mibget() failed\n");
941 #endif
942 			close(sd);
943 			sd = 0;
944 		}
945 	}
946 #ifdef DEBUG
947 	fprintf(stderr, "mibget returned item: %x\n", netstat_item);
948 #endif
949 
950 	nnets = 0;
951 	net = &zeronet;
952 
953 	if (netsnip)
954 		lastnet->next = netsnip;
955 
956 	for (item = netstat_item; item; item = item->next_item) {
957 #ifdef DEBUG_MIB
958 		fprintf(stderr, "\n--- Item %x ---\n", item);
959 		fprintf(stderr,
960 		"Group = %d, mib_id = %d, length = %d, valp = 0x%x\n",
961 		item->group, item->mib_id, item->length,
962 		item->valp);
963 #endif
964 		if (item->group != MIB2_IP || item->mib_id != MIB2_IP_20)
965 			continue;
966 		ap = (mib2_ipAddrEntry_t *)item->valp;
967 		for (; (char *)ap < item->valp + item->length; ap++) {
968 
969 			octetstr(namebuf, &ap->ipAdEntIfIndex, 'a');
970 #ifdef DEBUG
971 			fprintf(stderr, "%s ", namebuf);
972 #endif
973 			if (strlen(namebuf) == 0)
974 				continue;
975 			/*
976 			 * We found a device of interest.
977 			 * Now, let's see if there's a kstat for it.
978 			 */
979 			if ((ksp = kstat_lookup(kc, NULL, -1, namebuf)) == NULL)
980 				continue;
981 			if (ksp->ks_type != KSTAT_TYPE_NAMED)
982 				continue;
983 			if (kstat_read(kc, ksp, NULL) == -1)
984 				continue;
985 			prevnet = net;
986 			if (net->next)
987 				net = net->next;
988 			else {
989 				safe_zalloc((void **)&net->next,
990 					sizeof (struct netinfo), 0);
991 				net = net->next;
992 				net->next = NULLNET;
993 			}
994 			net->ks = ksp;
995 			net->ipackets	= kstat_data_lookup(net->ks,
996 				"ipackets");
997 			net->opackets	= kstat_data_lookup(net->ks,
998 				"opackets");
999 			net->ierrors	= kstat_data_lookup(net->ks,
1000 				"ierrors");
1001 			net->oerrors	= kstat_data_lookup(net->ks,
1002 				"oerrors");
1003 			net->collisions	= kstat_data_lookup(net->ks,
1004 				"collisions");
1005 			/*
1006 			 * Insertion sort on the name
1007 			 */
1008 			comp = &zeronet;
1009 			while (strcmp(net->ks->ks_name,
1010 			    comp->next->ks->ks_name) > 0)
1011 				comp = comp->next;
1012 			if (prevnet != comp) {
1013 				prevnet->next = net->next;
1014 				net->next = comp->next;
1015 				comp->next = net;
1016 				net = prevnet;
1017 			}
1018 			nnets++;
1019 		}
1020 #ifdef DEBUG
1021 		fprintf(stderr, "\n");
1022 #endif
1023 	}
1024 	/*
1025 	 * Put a snip in the linked list of netinfos.  The idea:
1026 	 * If there was a state change such that now there are fewer
1027 	 * nets, we snip the list and retain the tail, rather than
1028 	 * freeing it.  At the next state change, we clip the tail back on.
1029 	 * This prevents a lot of malloc/free activity, and it's simpler.
1030 	 */
1031 	lastnet = net;
1032 	netsnip = net->next;
1033 	net->next = NULLNET;
1034 
1035 	firstnet = zeronet.next;
1036 }
1037 
1038 static int
1039 netinfo_load(void)
1040 {
1041 	struct netinfo *net;
1042 
1043 	if (netstat_item == NULL) {
1044 #ifdef DEBUG
1045 		fprintf(stderr, "No net stats\n");
1046 #endif
1047 		return (0);
1048 	}
1049 
1050 	stats_s4.if_ipackets =
1051 	stats_s4.if_opackets =
1052 	stats_s4.if_ierrors =
1053 	stats_s4.if_oerrors =
1054 	stats_s4.if_collisions = 0;
1055 
1056 	for (net = firstnet; net; net = net->next) {
1057 		if (kstat_read(kc, net->ks, NULL) == -1)
1058 			return (1);
1059 		if (net->ipackets)
1060 			stats_s4.if_ipackets	+= net->ipackets->value.ul;
1061 		if (net->opackets)
1062 			stats_s4.if_opackets	+= net->opackets->value.ul;
1063 		if (net->ierrors)
1064 			stats_s4.if_ierrors	+= net->ierrors->value.ul;
1065 		if (net->oerrors)
1066 			stats_s4.if_oerrors	+= net->oerrors->value.ul;
1067 		if (net->collisions)
1068 			stats_s4.if_collisions	+= net->collisions->value.ul;
1069 	}
1070 #ifdef DEBUG
1071 	fprintf(stderr,
1072 	    "ipackets: %d opackets: %d ierrors: %d oerrors: %d colls: %d\n",
1073 		stats_s4.if_ipackets,
1074 		stats_s4.if_opackets,
1075 		stats_s4.if_ierrors,
1076 		stats_s4.if_oerrors,
1077 		stats_s4.if_collisions);
1078 #endif
1079 	return (0);
1080 }
1081 
1082 static void
1083 kstat_copy(kstat_t *src, kstat_t *dst, int fr)
1084 {
1085 	if (fr)
1086 		free(dst->ks_data);
1087 	*dst = *src;
1088 	if (src->ks_data != NULL) {
1089 		safe_zalloc(&dst->ks_data, src->ks_data_size, 0);
1090 		(void) memcpy(dst->ks_data, src->ks_data, src->ks_data_size);
1091 	} else {
1092 		dst->ks_data = NULL;
1093 		dst->ks_data_size = 0;
1094 	}
1095 }
1096