xref: /openbsd/usr.bin/systat/main.c (revision 479c151d)
1 /* $OpenBSD: main.c,v 1.78 2024/09/20 02:00:46 jsg Exp $	 */
2 /*
3  * Copyright (c) 2001, 2007 Can Erkin Acar
4  * Copyright (c) 2001 Daniel Hartmeier
5  * All rights reserved.
6  *
7  * Redistribution and use in source and binary forms, with or without
8  * modification, are permitted provided that the following conditions
9  * are met:
10  *
11  *    - Redistributions of source code must retain the above copyright
12  *      notice, this list of conditions and the following disclaimer.
13  *    - Redistributions in binary form must reproduce the above
14  *      copyright notice, this list of conditions and the following
15  *      disclaimer in the documentation and/or other materials provided
16  *      with the distribution.
17  *
18  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
21  * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
22  * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
23  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
24  * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
25  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
26  * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
27  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
28  * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
29  * POSSIBILITY OF SUCH DAMAGE.
30  *
31  */
32 
33 #include <sys/types.h>
34 #include <sys/sysctl.h>
35 
36 
37 #include <ctype.h>
38 #include <curses.h>
39 #include <err.h>
40 #include <errno.h>
41 #include <fcntl.h>
42 #include <limits.h>
43 #include <math.h>
44 #include <netdb.h>
45 #include <signal.h>
46 #include <stdio.h>
47 #include <stdint.h>
48 #include <stdlib.h>
49 #include <string.h>
50 #include <stdarg.h>
51 #include <unistd.h>
52 #include <utmp.h>
53 
54 #include "engine.h"
55 #include "systat.h"
56 
57 #define TIMEPOS (80 - 8 - 20 - 1)
58 
59 double	dellave;
60 
61 kvm_t	*kd;
62 char	*nlistf = NULL;
63 char	*memf = NULL;
64 double	avenrun[3];
65 double	naptime = 5.0;
66 int	verbose = 1;		/* to report kvm read errs */
67 int	nflag = 1;
68 int	ut, hz;
69 char    hostname[HOST_NAME_MAX+1];
70 WINDOW  *wnd;
71 int	CMDLINE;
72 char	timebuf[26];
73 char	uloadbuf[TIMEPOS];
74 
75 
76 int  ucount(void);
77 void usage(void);
78 double strtodnum(const char *, double, double, const char **);
79 
80 /* command prompt */
81 
82 void cmd_delay(const char *);
83 void cmd_count(const char *);
84 void cmd_compat(const char *);
85 
86 struct command cm_compat = {"Command", cmd_compat};
87 struct command cm_delay = {"Seconds to delay", cmd_delay};
88 struct command cm_count = {"Number of lines to display", cmd_count};
89 
90 
91 /* display functions */
92 
93 int
print_header(void)94 print_header(void)
95 {
96 	time_t now;
97 	int start = dispstart + 1, end = dispstart + maxprint;
98 	char tmpbuf[TIMEPOS];
99 	char header[MAX_LINE_BUF];
100 
101 	if (end > num_disp)
102 		end = num_disp;
103 
104 	tb_start();
105 
106 	if (!paused) {
107 		char *ctim;
108 
109 		getloadavg(avenrun, sizeof(avenrun) / sizeof(avenrun[0]));
110 
111 		snprintf(uloadbuf, sizeof(uloadbuf),
112 		    "%4d users Load %.2f %.2f %.2f",
113 		    ucount(), avenrun[0], avenrun[1], avenrun[2]);
114 
115 		time(&now);
116 		ctim = ctime(&now);
117 		ctim[11+8] = '\0';
118 		strlcpy(timebuf, ctim + 11, sizeof(timebuf));
119 	}
120 
121 	if (num_disp && (start > 1 || end != num_disp))
122 		snprintf(tmpbuf, sizeof(tmpbuf),
123 		    "%s (%u-%u of %u) %s", uloadbuf, start, end, num_disp,
124 		    paused ? "PAUSED" : "");
125 	else
126 		snprintf(tmpbuf, sizeof(tmpbuf),
127 		    "%s %s", uloadbuf,
128 		    paused ? "PAUSED" : "");
129 
130 	snprintf(header, sizeof(header), "%-*s %19.19s %s", TIMEPOS - 1,
131 	    tmpbuf, hostname, timebuf);
132 
133 	if (rawmode)
134 		printf("\n\n%s\n", header);
135 	else
136 		mvprintw(0, 0, "%s", header);
137 
138 	return (1);
139 }
140 
141 /* compatibility functions, rearrange later */
142 void
error(const char * fmt,...)143 error(const char *fmt, ...)
144 {
145 	va_list ap;
146 	char buf[MAX_LINE_BUF];
147 
148 	va_start(ap, fmt);
149 	vsnprintf(buf, sizeof buf, fmt, ap);
150 	va_end(ap);
151 
152 	message_set(buf);
153 }
154 
155 void
nlisterr(struct nlist namelist[])156 nlisterr(struct nlist namelist[])
157 {
158 	int i, n;
159 
160 	n = 0;
161 	clear();
162 	mvprintw(2, 10, "systat: nlist: can't find following symbols:");
163 	for (i = 0;
164 	    namelist[i].n_name != NULL && *namelist[i].n_name != '\0'; i++)
165 		if (namelist[i].n_value == 0)
166 			mvprintw(2 + ++n, 10, "%s", namelist[i].n_name);
167 	move(CMDLINE, 0);
168 	clrtoeol();
169 	refresh();
170 	endwin();
171 	exit(1);
172 }
173 
174 void
die(void)175 die(void)
176 {
177 	if (!rawmode)
178 		endwin();
179 	exit(0);
180 }
181 
182 
183 int
prefix(char * s1,char * s2)184 prefix(char *s1, char *s2)
185 {
186 
187 	while (*s1 == *s2) {
188 		if (*s1 == '\0')
189 			return (1);
190 		s1++, s2++;
191 	}
192 	return (*s1 == '\0');
193 }
194 
195 /* calculate number of users on the system */
196 int
ucount(void)197 ucount(void)
198 {
199 	int nusers = 0;
200 	struct	utmp utmp;
201 
202 	if (ut < 0)
203 		return (0);
204 	lseek(ut, (off_t)0, SEEK_SET);
205 	while (read(ut, &utmp, sizeof(utmp)))
206 		if (utmp.ut_name[0] != '\0')
207 			nusers++;
208 
209 	return (nusers);
210 }
211 
212 /* main program functions */
213 
214 void
usage(void)215 usage(void)
216 {
217 	extern char *__progname;
218 	fprintf(stderr, "usage: %s [-aBbhiNn] [-d count] "
219 	    "[-s delay] [-w width] [view] [delay]\n", __progname);
220 	exit(1);
221 }
222 
223 void
show_view(void)224 show_view(void)
225 {
226 	if (rawmode)
227 		return;
228 
229 	tb_start();
230 	tbprintf("%s %g", curr_view->name, naptime);
231 	tb_end();
232 	message_set(tmp_buf);
233 }
234 
235 void
add_view_tb(field_view * v)236 add_view_tb(field_view *v)
237 {
238 	if (curr_view == v)
239 		tbprintf("[%s] ", v->name);
240 	else
241 		tbprintf("%s ", v->name);
242 }
243 
244 void
show_help(void)245 show_help(void)
246 {
247 	if (rawmode)
248 		return;
249 
250 	tb_start();
251 	foreach_view(add_view_tb);
252 	tb_end();
253 	message_set(tmp_buf);
254 }
255 
256 void
add_order_tb(order_type * o)257 add_order_tb(order_type *o)
258 {
259 	if (curr_view->mgr->order_curr == o)
260 		tbprintf("[%s%s(%c)] ", o->name,
261 		    o->func != NULL && sortdir == -1 ? "^" : "",
262 		    (char) o->hotkey);
263 	else
264 		tbprintf("%s(%c) ", o->name, (char) o->hotkey);
265 }
266 
267 void
show_order(void)268 show_order(void)
269 {
270 	if (rawmode)
271 		return;
272 
273 	tb_start();
274 	if (foreach_order(add_order_tb) == -1) {
275 		tbprintf("No orders available");
276 	}
277 	tb_end();
278 	message_set(tmp_buf);
279 }
280 
281 void
cmd_compat(const char * buf)282 cmd_compat(const char *buf)
283 {
284 	const char *s;
285 
286 	if (strcasecmp(buf, "help") == 0) {
287 		message_toggle(MESSAGE_HELP);
288 		return;
289 	}
290 	if (strcasecmp(buf, "quit") == 0 || strcasecmp(buf, "q") == 0) {
291 		gotsig_close = 1;
292 		return;
293 	}
294 	if (strcasecmp(buf, "stop") == 0) {
295 		paused = 1;
296 		gotsig_alarm = 1;
297 		return;
298 	}
299 	if (strncasecmp(buf, "start", 5) == 0) {
300 		paused = 0;
301 		gotsig_alarm = 1;
302 		cmd_delay(buf + 5);
303 		return;
304 	}
305 	if (strncasecmp(buf, "order", 5) == 0) {
306 		message_toggle(MESSAGE_ORDER);
307 		return;
308 	}
309 	if (strncasecmp(buf, "human", 5) == 0) {
310 		humanreadable = !humanreadable;
311 		return;
312 	}
313 
314 	for (s = buf; *s && strchr("0123456789+-.eE", *s) != NULL; s++)
315 		;
316 	if (*s) {
317 		if (set_view(buf))
318 			error("Invalid/ambiguous view: %s", buf);
319 	} else
320 		cmd_delay(buf);
321 }
322 
323 void
cmd_delay(const char * buf)324 cmd_delay(const char *buf)
325 {
326 	double del;
327 	const char *errstr;
328 
329 	if (buf[0] == '\0')
330 		return;
331 	del = strtodnum(buf, 0, UINT32_MAX / 1000000, &errstr);
332 	if (errstr != NULL)
333 		error("s: \"%s\": delay value is %s", buf, errstr);
334 	else {
335 		refresh_delay(del);
336 		gotsig_alarm = 1;
337 		naptime = del;
338 	}
339 }
340 
341 void
cmd_count(const char * buf)342 cmd_count(const char *buf)
343 {
344 	const char *errstr;
345 
346 	maxprint = strtonum(buf, 1, lines - HEADER_LINES, &errstr);
347 	if (errstr)
348 		maxprint = lines - HEADER_LINES;
349 }
350 
351 
352 int
keyboard_callback(int ch)353 keyboard_callback(int ch)
354 {
355 	switch (ch) {
356 	case '?':
357 		/* FALLTHROUGH */
358 	case 'h':
359 		message_toggle(MESSAGE_HELP);
360 		break;
361 	case CTRL_G:
362 		message_toggle(MESSAGE_VIEW);
363 		break;
364 	case 'l':
365 		command_set(&cm_count, NULL);
366 		break;
367 	case 's':
368 		command_set(&cm_delay, NULL);
369 		break;
370 	case ',':
371 		separate_thousands = !separate_thousands;
372 		gotsig_alarm = 1;
373 		break;
374 	case ':':
375 		command_set(&cm_compat, NULL);
376 		break;
377 	default:
378 		return 0;
379 	}
380 
381 	return 1;
382 }
383 
384 void
initialize(void)385 initialize(void)
386 {
387 	engine_initialize();
388 
389 	initvmstat();
390 	initpigs();
391 	initifstat();
392 	initiostat();
393 	initsensors();
394 	initmembufs();
395 	initnetstat();
396 	initswap();
397 	initpftop();
398 	initpf();
399 	initpool();
400 	initmalloc();
401 	initnfs();
402 	initcpu();
403 	inituvm();
404 }
405 
406 void
gethz(void)407 gethz(void)
408 {
409 	struct clockinfo cinf;
410 	size_t  size = sizeof(cinf);
411 	int	mib[2];
412 
413 	mib[0] = CTL_KERN;
414 	mib[1] = KERN_CLOCKRATE;
415 	if (sysctl(mib, 2, &cinf, &size, NULL, 0) == -1)
416 		return;
417 	hz = cinf.hz;
418 }
419 
420 #define	INVALID		1
421 #define	TOOSMALL	2
422 #define	TOOLARGE	3
423 
424 double
strtodnum(const char * nptr,double minval,double maxval,const char ** errstrp)425 strtodnum(const char *nptr, double minval, double maxval, const char **errstrp)
426 {
427 	double d = 0;
428 	int error = 0;
429 	char *ep;
430 	struct errval {
431 		const char *errstr;
432 		int err;
433 	} ev[4] = {
434 		{ NULL,		0 },
435 		{ "invalid",	EINVAL },
436 		{ "too small",	ERANGE },
437 		{ "too large",	ERANGE },
438 	};
439 
440 	ev[0].err = errno;
441 	errno = 0;
442 	if (minval > maxval) {
443 		error = INVALID;
444 	} else {
445 		d = strtod(nptr, &ep);
446 		if (nptr == ep || *ep != '\0')
447 			error = INVALID;
448 		else if ((d == -HUGE_VAL && errno == ERANGE) || d < minval)
449 			error = TOOSMALL;
450 		else if ((d == HUGE_VAL && errno == ERANGE) || d > maxval)
451 			error = TOOLARGE;
452 	}
453 	if (errstrp != NULL)
454 		*errstrp = ev[error].errstr;
455 	errno = ev[error].err;
456 	if (error)
457 		d = 0;
458 
459 	return (d);
460 }
461 
462 int
main(int argc,char * argv[])463 main(int argc, char *argv[])
464 {
465 	char errbuf[_POSIX2_LINE_MAX];
466 	const char *errstr;
467 	extern char *optarg;
468 	extern int optind;
469 	double delay = 5, del;
470 
471 	char *viewstr = NULL;
472 
473 	gid_t gid;
474 	int countmax = 0;
475 	int maxlines = 0;
476 
477 	int ch;
478 
479 	ut = open(_PATH_UTMP, O_RDONLY);
480 	if (ut == -1) {
481 		warn("No utmp");
482 	}
483 
484 	kd = kvm_openfiles(NULL, NULL, NULL, KVM_NO_FILES, errbuf);
485 
486 	gid = getgid();
487 	if (setresgid(gid, gid, gid) == -1)
488 		err(1, "setresgid");
489 
490 	while ((ch = getopt(argc, argv, "BNabd:hins:w:")) != -1) {
491 		switch (ch) {
492 		case 'a':
493 			maxlines = -1;
494 			break;
495 		case 'B':
496 			averageonly = 1;
497 			if (countmax < 2)
498 				countmax = 2;
499 			/* FALLTHROUGH */
500 		case 'b':
501 			rawmode = 1;
502 			interactive = 0;
503 			break;
504 		case 'd':
505 			countmax = strtonum(optarg, 1, INT_MAX, &errstr);
506 			if (errstr)
507 				errx(1, "-d %s: %s", optarg, errstr);
508 			break;
509 		case 'h':
510 			humanreadable = 1;
511 			break;
512 		case 'i':
513 			interactive = 1;
514 			break;
515 		case 'N':
516 			nflag = 0;
517 			break;
518 		case 'n':
519 			/* this is a noop, -n is the default */
520 			nflag = 1;
521 			break;
522 		case 's':
523 			delay = strtodnum(optarg, 0, UINT32_MAX / 1000000,
524 			    &errstr);
525 			if (errstr != NULL)
526 				errx(1, "-s \"%s\": delay value is %s", optarg,
527 				    errstr);
528 			break;
529 		case 'w':
530 			rawwidth = strtonum(optarg, 1, MAX_LINE_BUF-1, &errstr);
531 			if (errstr)
532 				errx(1, "-w %s: %s", optarg, errstr);
533 			break;
534 		default:
535 			usage();
536 			/* NOTREACHED */
537 		}
538 	}
539 
540 	if (kd == NULL)
541 		warnx("kvm_openfiles: %s", errbuf);
542 
543 	argc -= optind;
544 	argv += optind;
545 
546 	if (argc == 1) {
547 		del = strtodnum(argv[0], 0, UINT32_MAX / 1000000, &errstr);
548 		if (errstr != NULL)
549 			viewstr = argv[0];
550 		else
551 			delay = del;
552 	} else if (argc == 2) {
553 		viewstr = argv[0];
554 		delay = strtodnum(argv[1], 0, UINT32_MAX / 1000000, &errstr);
555 		if (errstr != NULL)
556 			errx(1, "\"%s\": delay value is %s", argv[1], errstr);
557 	}
558 
559 	refresh_delay(delay);
560 	naptime = delay;
561 
562 	gethostname(hostname, sizeof (hostname));
563 	gethz();
564 
565 	initialize();
566 
567 	set_order(NULL);
568 	if (viewstr && set_view(viewstr)) {
569 		fprintf(stderr, "Unknown/ambiguous view name: %s\n", viewstr);
570 		return 1;
571 	}
572 
573 	if (check_termcap()) {
574 		rawmode = 1;
575 		interactive = 0;
576 	}
577 
578 	setup_term(maxlines);
579 
580 	if (unveil("/", "r") == -1)
581 		err(1, "unveil /");
582 	if (unveil(NULL, NULL) == -1)
583 		err(1, "unveil");
584 
585 	if (rawmode && countmax == 0)
586 		countmax = 1;
587 
588 	gotsig_alarm = 1;
589 
590 	engine_loop(countmax);
591 
592 	return 0;
593 }
594