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