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