xref: /illumos-gate/usr/src/cmd/acct/acctcom.c (revision 5661bb7641e85c46713da7a3002b29ecd2c3daf0)
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, Version 1.0 only
6  * (the "License").  You may not use this file except in compliance
7  * with the License.
8  *
9  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
10  * or http://www.opensolaris.org/os/licensing.
11  * See the License for the specific language governing permissions
12  * and limitations under the License.
13  *
14  * When distributing Covered Code, include this CDDL HEADER in each
15  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
16  * If applicable, add the following below this CDDL HEADER, with the
17  * fields enclosed by brackets "[]" replaced with your own identifying
18  * information: Portions Copyright [yyyy] [name of copyright owner]
19  *
20  * CDDL HEADER END
21  */
22 /*
23  * Copyright 2005 Sun Microsystems, Inc.  All rights reserved.
24  * Use is subject to license terms.
25  */
26 
27 /*	Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T	*/
28 /*	  All Rights Reserved  	*/
29 
30 
31 #pragma ident	"%Z%%M%	%I%	%E% SMI"
32 
33 #include <time.h>
34 #include <string.h>
35 #include <stdio.h>
36 #include <sys/types.h>
37 #include <sys/param.h>
38 #include "acctdef.h"
39 #include <grp.h>
40 #include <sys/acct.h>
41 #include <pwd.h>
42 #include <sys/stat.h>
43 #include <locale.h>
44 #include <stdlib.h>
45 #include <libgen.h>
46 
47 struct	acct ab;
48 char	command_name[16];
49 char	obuf[BUFSIZ];
50 static char	time_buf[50];
51 
52 double	cpucut,
53 	syscut,
54 	hogcut,
55 	iocut,
56 	realtot,
57 	cputot,
58 	usertot,
59 	systot,
60 	kcoretot,
61 	iotot,
62 	rwtot;
63 extern long	timezone;
64 extern int	daylight;	/* daylight savings time if set */
65 long	daydiff,
66 	offset = -2,
67 	cmdcount;
68 ulong_t	elapsed,
69 	sys,
70 	user,
71 	cpu,
72 	io,
73 	rw,
74 	mem,
75 	etime;
76 time_t	tstrt_b,
77 	tstrt_a,
78 	tend_b,
79 	tend_a;
80 int	backward,
81 	flag_field,
82 	average,
83 	quiet,
84 	option,
85 	verbose = 1,
86 	uidflag,
87 	gidflag,
88 	unkid,	/*user doesn't have login on this machine*/
89 	errflg,
90 	su_user,
91 	fileout = 0,
92 	stdinflg,
93 	nfiles;
94 static int	eflg = 0,
95 	Eflg = 0,
96 	sflg = 0,
97 	Sflg = 0;
98 #ifdef uts
99 dev_t   linedev = 0xffff;  /* changed from -1, as dev_t is now ushort */
100 #else
101 dev_t	linedev = (dev_t)-1;
102 #endif
103 uid_t	uidval;
104 gid_t	gidval;
105 char	*cname = NULL; /* command name pattern to match*/
106 
107 struct passwd *getpwnam(), *getpwuid(), *pw;
108 struct group *getgrnam(),*grp;
109 long	convtime();
110 
111 #ifdef uts
112 float   expand();
113 #else
114 ulong_t	expand();
115 #endif
116 
117 char	*ofile,
118 	*devtolin(),
119 	*uidtonam();
120 dev_t	lintodev();
121 
122 void dofile(char *);
123 void doexit(int) __NORETURN;
124 void usage(void);
125 void fatal(char *, char *);
126 void println(void);
127 void printhd(void);
128 char *cmset(char *);
129 
130 FILE	*ostrm;
131 
132 int
133 main(int argc, char **argv)
134 {
135 	int	c;
136 
137 	(void)setlocale(LC_ALL, "");
138 	setbuf(stdout,obuf);
139 	while((c = getopt(argc, argv,
140 		"C:E:H:I:O:S:abe:fg:hikl:mn:o:qrs:tu:v")) != EOF) {
141 		switch(c) {
142 		case 'C':
143 			sscanf(optarg,"%lf",&cpucut);
144 			continue;
145 		case 'O':
146 			sscanf(optarg,"%lf",&syscut);
147 			continue;
148 		case 'H':
149 			sscanf(optarg,"%lf",&hogcut);
150 			continue;
151 		case 'I':
152 			sscanf(optarg,"%lf",&iocut);
153 			continue;
154 		case 'a':
155 			average++;
156 			continue;
157 		case 'b':
158 			backward++;
159 			continue;
160 		case 'g':
161 			if(sscanf(optarg,"%ld",&gidval) == 1) {
162 				if (getgrgid(gidval) == NULL)
163 					fatal("Unknown group", optarg);
164 			} else if((grp=getgrnam(optarg)) == NULL)
165 				fatal("Unknown group", optarg);
166 			else
167 				gidval=grp->gr_gid;
168 			gidflag++;
169 			continue;
170 		case 'h':
171 			option |= HOGFACTOR;
172 			continue;
173 		case 'i':
174 			option |= IORW;
175 			continue;
176 		case 'k':
177 			option |= KCOREMIN;
178 			continue;
179 		case 'm':
180 			option |= MEANSIZE;
181 			continue;
182 		case 'n':
183 			cname=cmset(optarg);
184 			continue;
185 		case 't':
186 			option |= SEPTIME;
187 			continue;
188 		case 'r':
189 			option |= CPUFACTOR;
190 			continue;
191 		case 'v':
192 			verbose=0;
193 			continue;
194 		case 'l':
195 			linedev = lintodev(optarg);
196 			continue;
197 		case 'u':
198 			if(*optarg == '?') {
199 				unkid++;
200 				continue;
201 			}
202 			if(*optarg == '#') {
203 				su_user++;
204 				uidval = 0;
205 				uidflag++;
206 				continue;
207 			}
208 			if((pw = getpwnam(optarg)) == NULL) {
209 				uidval = (uid_t)atoi(optarg);
210 				/* atoi will return 0 in abnormal situation */
211 				if (uidval == 0 && strcmp(optarg, "0") != 0) {
212 					fprintf(stderr, "%s: Unknown user %s\n", argv[0], optarg);
213 					exit(1);
214 				}
215  				if ((pw = getpwuid(uidval)) == NULL) {
216 					fprintf(stderr, "%s: Unknown user %s\n", argv[0], optarg);
217 					exit(1);
218 				}
219 				uidflag++;
220 			} else {
221 				uidval = pw->pw_uid;
222 				uidflag++;
223 			}
224 			continue;
225 		case 'q':
226 			quiet++;
227 			verbose=0;
228 			average++;
229 			continue;
230 		case 's':
231 			sflg = 1;
232 			tend_a = convtime(optarg);
233 			continue;
234 		case 'S':
235 			Sflg = 1;
236 			tstrt_a = convtime(optarg);
237 			continue;
238 		case 'f':
239 			flag_field++;
240 			continue;
241 		case 'e':
242 			eflg = 1;
243 			tstrt_b = convtime(optarg);
244 			continue;
245 		case 'E':
246 			Eflg = 1;
247 			tend_b = convtime(optarg);
248 			continue;
249 		case 'o':
250 			ofile = optarg;
251 			fileout++;
252 			if((ostrm = fopen(ofile, "w")) == NULL) {
253 				perror("open error on output file");
254 				errflg++;
255 			}
256 			continue;
257 		case '?':
258 			errflg++;
259 			continue;
260 		}
261 	}
262 
263 	if(errflg) {
264 		usage();
265 		exit(1);
266 	}
267 
268 
269 	argv = &argv[optind];
270 	while(optind++ < argc) {
271 		dofile(*argv++);    /* change from *argv */
272 		nfiles++;
273 	}
274 
275 	if(nfiles==0) {
276 		if(isatty(0) || isdevnull())
277 			dofile(PACCT);
278 		else {
279 			stdinflg = 1;
280 			backward = offset = 0;
281 			dofile(NULL);
282 		}
283 	}
284 	doexit(0);
285 	/* NOTREACHED */
286 }
287 
288 void
289 dofile(char *fname)
290 {
291 	struct acct *a = &ab;
292 	struct tm *t;
293 	time_t curtime;
294 	time_t	ts_a = 0,
295 		ts_b = 0,
296 		te_a = 0,
297 		te_b = 0;
298 	long	daystart;
299 	long	nsize;
300 	int	ver;	/* version of acct structure */
301 	int	dst_secs;	/* number of seconds to adjust
302 				   for daylight savings time */
303 
304 	if(fname != NULL)
305 		if(freopen(fname, "r", stdin) == NULL) {
306 			fprintf(stderr, "acctcom: cannot open %s\n", fname);
307 			return;
308 		}
309 
310 	if (fread((char *)&ab, sizeof(struct acct), 1, stdin) != 1)
311 		return;
312 	else if (ab.ac_flag & AEXPND)
313 		ver = 2;	/* 4.0 acct structure */
314 	else
315 		ver = 1;	/* 3.x acct structure */
316 
317 	rewind(stdin);
318 
319 
320 	if(backward) {
321 		if (ver == 2)
322 			nsize = sizeof(struct acct);	/* make sure offset is signed */
323 		else
324 			nsize = sizeof(struct o_acct);	/* make sure offset is signed */
325 		fseek(stdin, (long)(-nsize), 2);
326 	}
327 	tzset();
328 	daydiff = a->ac_btime - (a->ac_btime % SECSINDAY);
329 	time(&curtime);
330 	t = localtime(&curtime);
331 	if (daydiff < (curtime - (curtime % SECSINDAY))) {
332 		time_t t;
333 		/*
334 		 * it is older than today
335 		 */
336 		t = (time_t)a->ac_btime;
337 		cftime(time_buf, DATE_FMT, &t);
338 		fprintf(stdout, "\nACCOUNTING RECORDS FROM:  %s", time_buf);
339 	}
340 
341 	/* adjust time by one hour for daylight savings time */
342 	if (daylight && t->tm_isdst != 0)
343 		dst_secs = 3600;
344 	else
345 		dst_secs = 0;
346 	daystart = (a->ac_btime - timezone + dst_secs) -
347 	    ((a->ac_btime - timezone + dst_secs) % SECSINDAY);
348 	if (Sflg) {
349 		ts_a = tstrt_a + daystart - dst_secs;
350 		cftime(time_buf, DATE_FMT, &ts_a);
351 		fprintf(stdout, "START AFT: %s", time_buf);
352 	}
353 	if (eflg) {
354 		ts_b = tstrt_b + daystart - dst_secs;
355 		cftime(time_buf, DATE_FMT, &ts_b);
356 		fprintf(stdout, "START BEF: %s", time_buf);
357 	}
358 	if (sflg) {
359 		te_a = tend_a + daystart - dst_secs;
360 		cftime(time_buf, DATE_FMT, &te_a);
361 		fprintf(stdout, "END AFTER: %s", time_buf);
362 	}
363 	if (Eflg) {
364 		te_b = tend_b + daystart - dst_secs;
365 		cftime(time_buf, DATE_FMT, &te_b);
366 		fprintf(stdout, "END BEFOR: %s", time_buf);
367 	}
368 	if(ts_a) {
369 		if (te_b && ts_a > te_b) te_b += SECSINDAY;
370 	}
371 
372 	while(aread(ver) != 0) {
373 		elapsed = expand(a->ac_etime);
374 		etime = (ulong_t)a->ac_btime + (ulong_t)SECS(elapsed);
375 		if(ts_a || ts_b || te_a || te_b) {
376 
377 			if(te_a && (etime < te_a)) {
378 				if(backward) return;
379 				else continue;
380 			}
381 			if(te_b && (etime > te_b)) {
382 				if(backward) continue;
383 				else return;
384 			}
385 			if(ts_a && (a->ac_btime < ts_a))
386 				continue;
387 			if(ts_b && (a->ac_btime > ts_b))
388 				continue;
389 		}
390 		if(!MYKIND(a->ac_flag))
391 			continue;
392 		if(su_user && !SU(a->ac_flag))
393 			continue;
394 		sys = expand(a->ac_stime);
395 		user = expand(a->ac_utime);
396 		cpu = sys + user;
397 		if(cpu == 0)
398 			cpu = 1;
399 		mem = expand(a->ac_mem);
400 		(void) strncpy(command_name, a->ac_comm, 8);
401 		io=expand(a->ac_io);
402 		rw=expand(a->ac_rw);
403 		if(cpucut && cpucut >= SECS(cpu))
404 			continue;
405 		if(syscut && syscut >= SECS(sys))
406 			continue;
407 #ifdef uts
408 		if(linedev != 0xffff && a->ac_tty != linedev)
409 			continue;
410 #else
411 		if(linedev != (dev_t)-1 && a->ac_tty != linedev)
412 			continue;
413 #endif
414 		if(uidflag && a->ac_uid != uidval)
415 			continue;
416 		if(gidflag && a->ac_gid != gidval)
417 			continue;
418 		if(cname && !cmatch(a->ac_comm,cname))
419 			continue;
420 		if(iocut && iocut > io)
421 			continue;
422 		if(unkid && uidtonam(a->ac_uid)[0] != '?')
423 			continue;
424 		if(verbose && (fileout == 0)) {
425 			printhd();
426 			verbose = 0;
427 		}
428 		if(elapsed == 0)
429 			elapsed++;
430 		if(hogcut && hogcut >= (double)cpu/(double)elapsed)
431 			continue;
432 		if(fileout)
433 			fwrite(&ab, sizeof(ab), 1, ostrm);
434 		else
435 			println();
436 		if(average) {
437 			cmdcount++;
438 			realtot += (double)elapsed;
439 			usertot += (double)user;
440 			systot += (double)sys;
441 			kcoretot += (double)mem;
442 			iotot += (double)io;
443 			rwtot += (double)rw;
444 		};
445 	}
446 }
447 
448 int
449 aread(int ver)
450 {
451 	static int ok = 1;
452 	struct o_acct oab;
453 	int ret;
454 
455 	if (ver != 2) {
456 		if ((ret = fread((char *)&oab, sizeof(struct o_acct), 1, stdin)) == 1){
457 			/* copy SVR3 acct struct to SVR4 acct struct */
458 			ab.ac_flag = oab.ac_flag | AEXPND;
459 			ab.ac_stat = oab.ac_stat;
460 			ab.ac_uid = (uid_t) oab.ac_uid;
461 			ab.ac_gid = (gid_t) oab.ac_gid;
462 			ab.ac_tty = (dev_t) oab.ac_tty;
463 			ab.ac_btime = oab.ac_btime;
464 			ab.ac_utime = oab.ac_utime;
465 			ab.ac_stime = oab.ac_stime;
466 			ab.ac_mem = oab.ac_mem;
467 			ab.ac_io = oab.ac_io;
468 			ab.ac_rw = oab.ac_rw;
469 			strcpy(ab.ac_comm, oab.ac_comm);
470 		}
471 	} else
472 		ret = fread((char *)&ab, sizeof(struct acct), 1, stdin);
473 
474 
475 	if(backward) {
476 		if(ok) {
477 			if(fseek(stdin,
478 				(long)(offset*(ver == 2 ? sizeof(struct acct) :
479 					sizeof(struct o_acct))), 1) != 0) {
480 
481 					rewind(stdin);	/* get 1st record */
482 					ok = 0;
483 			}
484 		} else
485 			ret = 0;
486 	}
487 	return(ret != 1 ? 0 : 1);
488 }
489 
490 void
491 printhd(void)
492 {
493 	fprintf(stdout, "COMMAND                           START    END          REAL");
494 	ps("CPU");
495 	if(option & SEPTIME)
496 		ps("(SECS)");
497 	if(option & IORW){
498 		ps("CHARS");
499 		ps("BLOCKS");
500 	}
501 	if(option & CPUFACTOR)
502 		ps("CPU");
503 	if(option & HOGFACTOR)
504 		ps("HOG");
505 	if(!option || (option & MEANSIZE))
506 		ps("MEAN");
507 	if(option & KCOREMIN)
508 		ps("KCORE");
509 	fprintf(stdout, "\n");
510 	fprintf(stdout, "NAME       USER     TTYNAME       TIME     TIME       (SECS)");
511 	if(option & SEPTIME) {
512 		ps("SYS");
513 		ps("USER");
514 	} else
515 		ps("(SECS)");
516 	if(option & IORW) {
517 		ps("TRNSFD");
518 		ps("READ");
519 	}
520 	if(option & CPUFACTOR)
521 		ps("FACTOR");
522 	if(option & HOGFACTOR)
523 		ps("FACTOR");
524 	if(!option || (option & MEANSIZE))
525 		ps("SIZE(K)");
526 	if(option & KCOREMIN)
527 		ps("MIN");
528 	if(flag_field)
529 		fprintf(stdout, "  F STAT");
530 	fprintf(stdout, "\n");
531 	fflush(stdout);
532 }
533 
534 void
535 println(void)
536 {
537 	char name[32];
538 	struct acct *a = &ab;
539 	time_t t;
540 
541 	if(quiet)
542 		return;
543 	if(!SU(a->ac_flag))
544 		strcpy(name,command_name);
545 	else {
546 		strcpy(name,"#");
547 		strcat(name,command_name);
548 	}
549 	fprintf(stdout, "%-*.*s", (OUTPUT_NSZ + 1),
550 	    (OUTPUT_NSZ + 1), name);
551 	strcpy(name,uidtonam(a->ac_uid));
552 	if(*name != '?')
553 		fprintf(stdout, "  %-*.*s", (OUTPUT_NSZ + 1),
554 		    (OUTPUT_NSZ + 1), name);
555 	else
556 		fprintf(stdout, "  %-9d",a->ac_uid);
557 #ifdef uts
558 	fprintf(stdout, " %-*.*s", OUTPUT_LSZ, OUTPUT_LSZ,
559 	    a->ac_tty != 0xffff? devtolin(a->ac_tty):"?");
560 #else
561 	fprintf(stdout, " %-*.*s", OUTPUT_LSZ, OUTPUT_LSZ,
562 	    a->ac_tty != (dev_t)-1? devtolin(a->ac_tty):"?");
563 #endif
564 	t = a->ac_btime;
565 	cftime(time_buf, DATE_FMT1, &t);
566 	fprintf(stdout, "%.9s", time_buf);
567 	cftime(time_buf, DATE_FMT1, (time_t *)&etime);
568 	fprintf(stdout, "%.9s ", time_buf);
569 	pf((double)SECS(elapsed));
570 	if(option & SEPTIME) {
571 		pf((double)sys / HZ);
572 		pf((double)user / HZ);
573 	} else
574 		pf((double)cpu / HZ);
575 	if(option & IORW)
576 		fprintf(stdout, io < 100000000 ? "%8ld%8ld" : "%12ld%8ld",io,rw);
577 	if(option & CPUFACTOR)
578 		pf((double)user / cpu);
579 	if(option & HOGFACTOR)
580 		pf((double)cpu / elapsed);
581 	if(!option || (option & MEANSIZE))
582 		pf(KCORE(mem / cpu));
583 	if(option & KCOREMIN)
584 		pf(MINT(KCORE(mem)));
585 	if(flag_field)
586 		fprintf(stdout, "  %1o %3o", (unsigned char) a->ac_flag,
587 						(unsigned char) a->ac_stat);
588 	fprintf(stdout, "\n");
589 }
590 
591 /*
592  * convtime converts time arg to internal value
593  * arg has form hr:min:sec, min or sec are assumed to be 0 if omitted
594  */
595 long
596 convtime(str)
597 char *str;
598 {
599 	long	hr, min, sec;
600 
601 	min = sec = 0;
602 
603 	if(sscanf(str, "%ld:%ld:%ld", &hr, &min, &sec) < 1) {
604 		fatal("acctcom: bad time:", str);
605 	}
606 	tzset();
607 	sec += (min*60);
608 	sec += (hr*3600);
609 	return(sec + timezone);
610 }
611 
612 int
613 cmatch(char *comm, char *cstr)
614 {
615 
616 	char	xcomm[9];
617 	int i;
618 
619 	for(i=0;i<8;i++){
620 		if(comm[i]==' '||comm[i]=='\0')
621 			break;
622 		xcomm[i] = comm[i];
623 	}
624 	xcomm[i] = '\0';
625 
626 	return (regex(cstr,xcomm) ? 1 : 0);
627 }
628 
629 char *
630 cmset(char *pattern)
631 {
632 
633 	if((pattern=(char *)regcmp(pattern,(char *)0))==NULL){
634 		fatal("pattern syntax", NULL);
635 	}
636 
637 	return (pattern);
638 }
639 
640 void
641 doexit(int status)
642 {
643 	if(!average)
644 		exit(status);
645 	if(cmdcount) {
646 		fprintf(stdout, "cmds=%ld ",cmdcount);
647 		fprintf(stdout, "Real=%-6.2f ",SECS(realtot)/cmdcount);
648 		cputot = systot + usertot;
649 		fprintf(stdout, "CPU=%-6.2f ",SECS(cputot)/cmdcount);
650 		fprintf(stdout, "USER=%-6.2f ",SECS(usertot)/cmdcount);
651 		fprintf(stdout, "SYS=%-6.2f ",SECS(systot)/cmdcount);
652 		fprintf(stdout, "CHAR=%-8.2f ",iotot/cmdcount);
653 		fprintf(stdout, "BLK=%-8.2f ",rwtot/cmdcount);
654 		fprintf(stdout, "USR/TOT=%-4.2f ",usertot/cputot);
655 		fprintf(stdout, "HOG=%-4.2f ",cputot/realtot);
656 		fprintf(stdout, "\n");
657 	}
658 	else
659 		fprintf(stdout, "\nNo commands matched\n");
660 	exit(status);
661 }
662 
663 int
664 isdevnull(void)
665 {
666 	struct stat	filearg;
667 	struct stat	devnull;
668 
669 	if(fstat(0,&filearg) == -1) {
670 		fprintf(stderr,"acctcom: cannot stat stdin\n");
671 		return (0);
672 	}
673 	if(stat("/dev/null",&devnull) == -1) {
674 		fprintf(stderr,"acctcom: cannot stat /dev/null\n");
675 		return (0);
676 	}
677 
678 	if (filearg.st_rdev == devnull.st_rdev)
679 		return (1);
680 	else
681 		return (0);
682 }
683 
684 void
685 fatal(char *s1, char *s2)
686 {
687 	fprintf(stderr,"acctcom: %s %s\n", s1, (s2 ? s2 : ""));
688 	exit(1);
689 }
690 
691 void
692 usage(void)
693 {
694 	fprintf(stderr, "Usage: acctcom [options] [files]\n");
695 	fprintf(stderr, "\nWhere options can be:\n");
696 	diag("-b	read backwards through file");
697 	diag("-f	print the fork/exec flag and exit status");
698 	diag("-h	print hog factor (total-CPU-time/elapsed-time)");
699 	diag("-i	print I/O counts");
700 	diag("-k	show total Kcore minutes instead of memory size");
701 	diag("-m	show mean memory size");
702 	diag("-r	show CPU factor (user-time/(sys-time + user-time))");
703 	diag("-t	show separate system and user CPU times");
704 	diag("-v	don't print column headings");
705 	diag("-a	print average statistics of selected commands");
706 	diag("-q	print average statistics only");
707 	diag("-l line	\tshow processes belonging to terminal /dev/line");
708 	diag("-u user	\tshow processes belonging to user name or user ID");
709 	diag("-u #	\tshow processes executed by super-user");
710 	diag("-u ?	\tshow processes executed by unknown UID's");
711 	diag("-g group	show processes belonging to group name of group ID");
712 	diag("-s time	\tshow processes ending after time (hh[:mm[:ss]])");
713 	diag("-e time	\tshow processes starting before time");
714 	diag("-S time	\tshow processes starting after time");
715 	diag("-E time	\tshow processes ending before time");
716 	diag("-n regex	select commands matching the ed(1) regular expression");
717 	diag("-o file	\tdo not print, put selected pacct records into file");
718 	diag("-H factor	show processes that exceed hog factor");
719 	diag("-O sec	\tshow processes that exceed CPU system time sec");
720 	diag("-C sec	\tshow processes that exceed total CPU time sec");
721 	diag("-I chars	show processes that transfer more than char chars");
722 }
723