1/*-
2 * SPDX-License-Identifier: BSD-4-Clause
3 *
4 * Copyright (c) 1994 Christopher G. Demetriou
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 * 1. Redistributions of source code must retain the above copyright
11 *    notice, this list of conditions and the following disclaimer.
12 * 2. Redistributions in binary form must reproduce the above copyright
13 *    notice, this list of conditions and the following disclaimer in the
14 *    documentation and/or other materials provided with the distribution.
15 * 3. All advertising materials mentioning features or use of this software
16 *    must display the following acknowledgement:
17 *      This product includes software developed by Christopher G. Demetriou.
18 * 4. The name of the author may not be used to endorse or promote products
19 *    derived from this software without specific prior written permission
20 *
21 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
22 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
23 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
24 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
25 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
26 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
27 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
28 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
29 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
30 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31 */
32
33#if 0
34#ifndef lint
35static const char copyright[] =
36"@(#) Copyright (c) 1994 Christopher G. Demetriou\n\
37 All rights reserved.\n";
38#endif
39#endif
40#include <sys/cdefs.h>
41__FBSDID("$FreeBSD: stable/11/usr.sbin/sa/main.c 330449 2018-03-05 07:26:05Z eadler $");
42
43/*
44 * sa:	system accounting
45 */
46
47#include <sys/types.h>
48#include <sys/acct.h>
49#include <ctype.h>
50#include <err.h>
51#include <errno.h>
52#include <fcntl.h>
53#include <signal.h>
54#include <stdint.h>
55#include <stdio.h>
56#include <stdlib.h>
57#include <string.h>
58#include <unistd.h>
59#include "extern.h"
60#include "pathnames.h"
61
62static FILE	*acct_load(const char *, int);
63static int	 cmp_comm(const char *, const char *);
64static int	 cmp_usrsys(const DBT *, const DBT *);
65static int	 cmp_avgusrsys(const DBT *, const DBT *);
66static int	 cmp_dkio(const DBT *, const DBT *);
67static int	 cmp_avgdkio(const DBT *, const DBT *);
68static int	 cmp_cpumem(const DBT *, const DBT *);
69static int	 cmp_avgcpumem(const DBT *, const DBT *);
70static int	 cmp_calls(const DBT *, const DBT *);
71static void	 usage(void);
72
73int aflag, bflag, cflag, dflag, Dflag, fflag, iflag, jflag, kflag;
74int Kflag, lflag, mflag, qflag, rflag, sflag, tflag, uflag, vflag;
75u_quad_t cutoff = 1;
76const char *pdb_file = _PATH_SAVACCT;
77const char *usrdb_file = _PATH_USRACCT;
78
79static char	*dfltargv[] = { NULL };
80static int	dfltargc = (sizeof dfltargv/sizeof(char *));
81
82/* default to comparing by sum of user + system time */
83cmpf_t   sa_cmp = cmp_usrsys;
84
85int
86main(int argc, char **argv)
87{
88	FILE *f;
89	char pathacct[] = _PATH_ACCT;
90	int ch, error = 0;
91
92	dfltargv[0] = pathacct;
93
94	while ((ch = getopt(argc, argv, "abcdDfijkKlmnP:qrstuU:v:")) != -1)
95		switch (ch) {
96			case 'a':
97				/* print all commands */
98				aflag = 1;
99				break;
100			case 'b':
101				/* sort by per-call user/system time average */
102				bflag = 1;
103				sa_cmp = cmp_avgusrsys;
104				break;
105			case 'c':
106				/* print percentage total time */
107				cflag = 1;
108				break;
109			case 'd':
110				/* sort by averge number of disk I/O ops */
111				dflag = 1;
112				sa_cmp = cmp_avgdkio;
113				break;
114			case 'D':
115				/* print and sort by total disk I/O ops */
116				Dflag = 1;
117				sa_cmp = cmp_dkio;
118				break;
119			case 'f':
120				/* force no interactive threshold comprison */
121				fflag = 1;
122				break;
123			case 'i':
124				/* do not read in summary file */
125				iflag = 1;
126				break;
127			case 'j':
128				/* instead of total minutes, give sec/call */
129				jflag = 1;
130				break;
131			case 'k':
132				/* sort by cpu-time average memory usage */
133				kflag = 1;
134				sa_cmp = cmp_avgcpumem;
135				break;
136			case 'K':
137				/* print and sort by cpu-storage integral */
138				sa_cmp = cmp_cpumem;
139				Kflag = 1;
140				break;
141			case 'l':
142				/* separate system and user time */
143				lflag = 1;
144				break;
145			case 'm':
146				/* print procs and time per-user */
147				mflag = 1;
148				break;
149			case 'n':
150				/* sort by number of calls */
151				sa_cmp = cmp_calls;
152				break;
153			case 'P':
154				/* specify program database summary file */
155				pdb_file = optarg;
156				break;
157			case 'q':
158				/* quiet; error messages only */
159				qflag = 1;
160				break;
161			case 'r':
162				/* reverse order of sort */
163				rflag = 1;
164				break;
165			case 's':
166				/* merge accounting file into summaries */
167				sflag = 1;
168				break;
169			case 't':
170				/* report ratio of user and system times */
171				tflag = 1;
172				break;
173			case 'u':
174				/* first, print uid and command name */
175				uflag = 1;
176				break;
177			case 'U':
178				/* specify user database summary file */
179				usrdb_file = optarg;
180				break;
181			case 'v':
182				/* cull junk */
183				vflag = 1;
184				cutoff = atoi(optarg);
185				break;
186			case '?':
187	                default:
188				usage();
189		}
190
191	argc -= optind;
192	argv += optind;
193
194	/* various argument checking */
195	if (fflag && !vflag)
196		errx(1, "only one of -f requires -v");
197	if (fflag && aflag)
198		errx(1, "only one of -a and -v may be specified");
199	/* XXX need more argument checking */
200
201	if (!uflag) {
202		/* initialize tables */
203		if ((sflag || (!mflag && !qflag)) && pacct_init() != 0)
204			errx(1, "process accounting initialization failed");
205		if ((sflag || (mflag && !qflag)) && usracct_init() != 0)
206			errx(1, "user accounting initialization failed");
207	}
208
209	if (argc == 0) {
210		argc = dfltargc;
211		argv = dfltargv;
212	}
213
214	/* for each file specified */
215	for (; argc > 0; argc--, argv++) {
216		/*
217		 * load the accounting data from the file.
218		 * if it fails, go on to the next file.
219		 */
220		f = acct_load(argv[0], sflag);
221		if (f == NULL)
222			continue;
223
224		if (!uflag && sflag) {
225#ifndef DEBUG
226			sigset_t nmask, omask;
227			int unmask = 1;
228
229			/*
230			 * block most signals so we aren't interrupted during
231			 * the update.
232			 */
233			if (sigfillset(&nmask) == -1) {
234				warn("sigfillset");
235				unmask = 0;
236				error = 1;
237			}
238			if (unmask &&
239			    (sigprocmask(SIG_BLOCK, &nmask, &omask) == -1)) {
240				warn("couldn't set signal mask");
241				unmask = 0;
242				error = 1;
243			}
244#endif /* DEBUG */
245
246			/*
247			 * truncate the accounting data file ASAP, to avoid
248			 * losing data.  don't worry about errors in updating
249			 * the saved stats; better to underbill than overbill,
250			 * but we want every accounting record intact.
251			 */
252			if (ftruncate(fileno(f), 0) == -1) {
253				warn("couldn't truncate %s", *argv);
254				error = 1;
255			}
256
257			/*
258			 * update saved user and process accounting data.
259			 * note errors for later.
260			 */
261			if (pacct_update() != 0 || usracct_update() != 0)
262				error = 1;
263
264#ifndef DEBUG
265			/*
266			 * restore signals
267			 */
268			if (unmask &&
269			    (sigprocmask(SIG_SETMASK, &omask, NULL) == -1)) {
270				warn("couldn't restore signal mask");
271				error = 1;
272			}
273#endif /* DEBUG */
274		}
275
276		/*
277		 * close the opened accounting file
278		 */
279		if (fclose(f) == EOF) {
280			warn("fclose %s", *argv);
281			error = 1;
282		}
283	}
284
285	if (!uflag && !qflag) {
286		/* print any results we may have obtained. */
287		if (!mflag)
288			pacct_print();
289		else
290			usracct_print();
291	}
292
293	if (!uflag) {
294		/* finally, deallocate databases */
295		if (sflag || (!mflag && !qflag))
296			pacct_destroy();
297		if (sflag || (mflag && !qflag))
298			usracct_destroy();
299	}
300
301	exit(error);
302}
303
304static void
305usage(void)
306{
307	(void)fprintf(stderr,
308		"usage: sa [-abcdDfijkKlmnqrstu] [-P file] [-U file] [-v cutoff] [file ...]\n");
309	exit(1);
310}
311
312static FILE *
313acct_load(const char *pn, int wr)
314{
315	struct acctv2 ac;
316	struct cmdinfo ci;
317	ssize_t rv;
318	FILE *f;
319	int i;
320
321	/*
322	 * open the file
323	 */
324	f = fopen(pn, wr ? "r+" : "r");
325	if (f == NULL) {
326		warn("open %s %s", pn, wr ? "for read/write" : "read-only");
327		return (NULL);
328	}
329
330	/*
331	 * read all we can; don't stat and open because more processes
332	 * could exit, and we'd miss them
333	 */
334	while (1) {
335		/* get one accounting entry and punt if there's an error */
336		rv = readrec_forward(f, &ac);
337		if (rv != 1) {
338			if (rv == EOF)
339				warn("error reading %s", pn);
340			break;
341		}
342
343		/* decode it */
344		ci.ci_calls = 1;
345		for (i = 0; i < (int)sizeof ac.ac_comm && ac.ac_comm[i] != '\0';
346		    i++) {
347			char c = ac.ac_comm[i];
348
349			if (!isascii(c) || iscntrl(c)) {
350				ci.ci_comm[i] = '?';
351				ci.ci_flags |= CI_UNPRINTABLE;
352			} else
353				ci.ci_comm[i] = c;
354		}
355		if (ac.ac_flagx & AFORK)
356			ci.ci_comm[i++] = '*';
357		ci.ci_comm[i++] = '\0';
358		ci.ci_etime = ac.ac_etime;
359		ci.ci_utime = ac.ac_utime;
360		ci.ci_stime = ac.ac_stime;
361		ci.ci_uid = ac.ac_uid;
362		ci.ci_mem = ac.ac_mem;
363		ci.ci_io = ac.ac_io;
364
365		if (!uflag) {
366			/* and enter it into the usracct and pacct databases */
367			if (sflag || (!mflag && !qflag))
368				pacct_add(&ci);
369			if (sflag || (mflag && !qflag))
370				usracct_add(&ci);
371		} else if (!qflag)
372			printf("%6u %12.3lf cpu %12.0lfk mem %12.0lf io %s\n",
373			    ci.ci_uid,
374			    (ci.ci_utime + ci.ci_stime) / 1000000,
375			    ci.ci_mem, ci.ci_io,
376			    ci.ci_comm);
377	}
378
379	/* Finally, return the file stream for possible truncation. */
380	return (f);
381}
382
383/* sort commands, doing the right thing in terms of reversals */
384static int
385cmp_comm(const char *s1, const char *s2)
386{
387	int rv;
388
389	rv = strcmp(s1, s2);
390	if (rv == 0)
391		rv = -1;
392	return (rflag ? rv : -rv);
393}
394
395/* sort by total user and system time */
396static int
397cmp_usrsys(const DBT *d1, const DBT *d2)
398{
399	struct cmdinfo c1, c2;
400	double t1, t2;
401
402	memcpy(&c1, d1->data, sizeof(c1));
403	memcpy(&c2, d2->data, sizeof(c2));
404
405	t1 = c1.ci_utime + c1.ci_stime;
406	t2 = c2.ci_utime + c2.ci_stime;
407
408	if (t1 < t2)
409		return -1;
410	else if (t1 == t2)
411		return (cmp_comm(c1.ci_comm, c2.ci_comm));
412	else
413		return 1;
414}
415
416/* sort by average user and system time */
417static int
418cmp_avgusrsys(const DBT *d1, const DBT *d2)
419{
420	struct cmdinfo c1, c2;
421	double t1, t2;
422
423	memcpy(&c1, d1->data, sizeof(c1));
424	memcpy(&c2, d2->data, sizeof(c2));
425
426	t1 = c1.ci_utime + c1.ci_stime;
427	t1 /= (double) (c1.ci_calls ? c1.ci_calls : 1);
428
429	t2 = c2.ci_utime + c2.ci_stime;
430	t2 /= (double) (c2.ci_calls ? c2.ci_calls : 1);
431
432	if (t1 < t2)
433		return -1;
434	else if (t1 == t2)
435		return (cmp_comm(c1.ci_comm, c2.ci_comm));
436	else
437		return 1;
438}
439
440/* sort by total number of disk I/O operations */
441static int
442cmp_dkio(const DBT *d1, const DBT *d2)
443{
444	struct cmdinfo c1, c2;
445
446	memcpy(&c1, d1->data, sizeof(c1));
447	memcpy(&c2, d2->data, sizeof(c2));
448
449	if (c1.ci_io < c2.ci_io)
450		return -1;
451	else if (c1.ci_io == c2.ci_io)
452		return (cmp_comm(c1.ci_comm, c2.ci_comm));
453	else
454		return 1;
455}
456
457/* sort by average number of disk I/O operations */
458static int
459cmp_avgdkio(const DBT *d1, const DBT *d2)
460{
461	struct cmdinfo c1, c2;
462	double n1, n2;
463
464	memcpy(&c1, d1->data, sizeof(c1));
465	memcpy(&c2, d2->data, sizeof(c2));
466
467	n1 = c1.ci_io / (double) (c1.ci_calls ? c1.ci_calls : 1);
468	n2 = c2.ci_io / (double) (c2.ci_calls ? c2.ci_calls : 1);
469
470	if (n1 < n2)
471		return -1;
472	else if (n1 == n2)
473		return (cmp_comm(c1.ci_comm, c2.ci_comm));
474	else
475		return 1;
476}
477
478/* sort by the cpu-storage integral */
479static int
480cmp_cpumem(const DBT *d1, const DBT *d2)
481{
482	struct cmdinfo c1, c2;
483
484	memcpy(&c1, d1->data, sizeof(c1));
485	memcpy(&c2, d2->data, sizeof(c2));
486
487	if (c1.ci_mem < c2.ci_mem)
488		return -1;
489	else if (c1.ci_mem == c2.ci_mem)
490		return (cmp_comm(c1.ci_comm, c2.ci_comm));
491	else
492		return 1;
493}
494
495/* sort by the cpu-time average memory usage */
496static int
497cmp_avgcpumem(const DBT *d1, const DBT *d2)
498{
499	struct cmdinfo c1, c2;
500	double t1, t2;
501	double n1, n2;
502
503	memcpy(&c1, d1->data, sizeof(c1));
504	memcpy(&c2, d2->data, sizeof(c2));
505
506	t1 = c1.ci_utime + c1.ci_stime;
507	t2 = c2.ci_utime + c2.ci_stime;
508
509	n1 = c1.ci_mem / (t1 ? t1 : 1);
510	n2 = c2.ci_mem / (t2 ? t2 : 1);
511
512	if (n1 < n2)
513		return -1;
514	else if (n1 == n2)
515		return (cmp_comm(c1.ci_comm, c2.ci_comm));
516	else
517		return 1;
518}
519
520/* sort by the number of invocations */
521static int
522cmp_calls(const DBT *d1, const DBT *d2)
523{
524	struct cmdinfo c1, c2;
525
526	memcpy(&c1, d1->data, sizeof(c1));
527	memcpy(&c2, d2->data, sizeof(c2));
528
529	if (c1.ci_calls < c2.ci_calls)
530		return -1;
531	else if (c1.ci_calls == c2.ci_calls)
532		return (cmp_comm(c1.ci_comm, c2.ci_comm));
533	else
534		return 1;
535}
536