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