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