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