chiark / gitweb /
Bugfixes from Peter Benie.
[chiark-utils.git] / cprogs / acctdump.c
1 /*
2  * acctdump.c - accounting data dump utility
3  *
4  * Copyright (C) 1998 Ian Jackson <ian@chiark.greenend.org.uk>
5  *
6  * This is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as
8  * published by the Free Software Foundation; either version 2,
9  * or (at your option) any later version.
10  *
11  * This is distributed in the hope that it will be useful, but
12  * WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public
17  * License along with this file; if not, write to the Free Software
18  * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
19  */
20
21 #include <stdio.h>
22 #include <stdlib.h>
23 #include <time.h>
24 #include <stdarg.h>
25 #include <wait.h>
26 #include <string.h>
27 #include <pwd.h>
28 #include <grp.h>
29 #include <dirent.h>
30 #include <ctype.h>
31 #include <sys/stat.h>
32 #include <sys/acct.h>
33 #include <errno.h>
34
35 #include "myopt.h"
36
37 static int forwards, nobanner, usestdin, raw, usages;
38
39 static int de_used, de_allocd;
40 static struct deventry {
41   const char *fn;
42   dev_t dev;
43 } *deventries;
44
45 static const struct cmdinfo cmdinfos[]= {
46   { "--forwards",  'f',  0, &forwards, 0, 0, 1, 0, 0 },
47   { "--no-banner", 'q',  0, &nobanner, 0, 0, 1, 0, 0 },
48   { "--stdin",     'p',  0, &usestdin, 0, 0, 1, 0, 0 },
49   { "--raw",       'r',  0, &raw,      0, 0, 1, 0, 0 },
50   { "--resource",  'u',  0, &usages,   0, 0, 1, 0, 0 },
51   {  0                                                 }
52 };
53
54 static const char *sigabbrev[]= {
55   "HUP", "INT", "QUIT", "ILL", "TRAP", "ABRT", "BUS", "FPE",
56   "KILL", "USR1", "SEGV", "USR2", "PIPE", "ALRM", "TERM", "STKFLT",
57   "CHLD", "CONT", "STOP", "TSTP", "TTIN", "TTOU", "URG", "XCPU",
58   "XFSZ", "VTALRM", "PROF", "WINCH", "IO"
59 };
60
61 static void usage(FILE *file) {
62   fputs("usage: acctdump [<options>] [<file> ...]\n"
63         "options: -f|--forwards -q|--no-banner -p|--stdin -r|--raw -u|--resource\n",
64         file);
65   if (ferror(file)) { perror("print usage"); exit(8); }
66 }
67
68 void badusage(const char *fmt, ...) {
69   va_list al;
70
71   fputs("usage error: ",stderr);
72   va_start(al,fmt);
73   vfprintf(stderr,fmt,al);
74   va_end(al);
75   fputs("\n",stderr);
76   usage(stderr);
77   exit(12);
78 }
79
80 static void checkstdout(void) {
81   if (ferror(stdout)) { perror("stdout"); exit(8); }
82 }
83
84 static void scandev(const char *basename, int levelsleft) {
85   /* We deliberately ignore most errors */
86   DIR *dir;
87   struct dirent *de;
88   struct stat stab;
89   int fnbufalloc, fnbufreq, r, basel, nallocd;
90   char *fnbuf, *nfnbuf;
91   struct deventry *ndeventries;
92   
93   if (levelsleft==0) return;
94
95   dir= opendir(basename); 
96   if (!dir) {
97     fprintf(stderr, "%s: opendir: %s\n", basename, strerror(errno));
98     return;
99   }
100   fnbufalloc= 0;
101   fnbuf= 0;
102   basel= strlen(basename);
103
104   while ((errno=0, de= readdir(dir))) {
105     fnbufreq= basel+1+strlen(de->d_name)+1;
106     if (fnbufalloc<fnbufreq) {
107       fnbufalloc= fnbufreq+10;
108       nfnbuf= realloc(fnbuf,fnbufalloc);
109       if (!nfnbuf) { free(fnbuf); fnbufalloc=0; continue; }
110       fnbuf= nfnbuf;
111     }
112     sprintf(fnbuf,"%s/%s",basename,de->d_name);
113     r= lstat(fnbuf,&stab);
114     if (r) {
115       fprintf(stderr, "%s: %s\n", fnbuf, strerror(errno));
116       continue;
117     }
118     if (S_ISCHR(stab.st_mode)) {
119       if (de_used >= de_allocd) {
120         nallocd= (de_allocd+10)<<1;
121         ndeventries= realloc(deventries,nallocd*sizeof(*deventries));
122         if (!ndeventries) continue;
123         de_allocd= nallocd;
124         deventries= ndeventries;
125       }
126       deventries[de_used].fn= strdup(fnbuf+5); /* remove /dev */
127       if (!deventries[de_used].fn) continue;
128       deventries[de_used].dev= stab.st_rdev;
129       de_used++;
130     } else if (S_ISDIR(stab.st_mode) && de->d_name[0] != '.') {
131       scandev(fnbuf,levelsleft-1);
132     }
133   }
134   if (errno)
135       fprintf(stderr, "%s: readdir: %s\n", basename, strerror(errno));
136   closedir(dir);
137   free(fnbuf);
138 }
139
140 static int walkdev_cptr(const void *av, const void *bv) {
141   const struct deventry *a= av;
142   const struct deventry *b= bv;
143   return a->dev - b->dev;
144 }
145   
146 static void printbanner(void) {
147   if (raw) {
148     fputs("begin date command          "
149           "uid      gid      tty dev  FSDX exit",
150           stdout);
151   } else {
152     fputs("begin date and time command          "
153           "user     group    tty dev    FSDX sigexit",
154           stdout);
155   }
156   if (usages) {
157     fputs("  user time   sys time  elap time   minflt   maxflt",
158           stdout);
159   }
160   putchar('\n');
161   checkstdout();
162 }
163
164 static void printrecord(const struct acct *as, const char *filename) {
165   static int walkeddev;
166
167   int i, dc, r;
168   const char *fp;
169   char buf[100];
170   struct tm *tm;
171   struct deventry *deve, devlookfor;
172   struct passwd *pw;
173   struct group *gr;
174
175   if (raw) {
176     printf("%10lu ",(unsigned long)as->ac_btime);
177   } else {
178     time_t btime=as->ac_btime;
179     tm= localtime(&btime);
180     strftime(buf,sizeof(buf),"%Y-%m-%d %H:%M:%S",tm); buf[sizeof(buf)-1]= 0;
181     printf("%19s ",buf);
182   }
183   
184   printf("%-16.16s ", as->ac_comm);
185   
186   pw= raw ? 0 : getpwuid(as->ac_uid);
187   if (pw) printf("%-8s ",pw->pw_name);
188   else printf("%-8ld ",(long)as->ac_uid);
189   
190   gr= raw ? 0 : getgrgid(as->ac_gid);
191   if (gr) printf("%-8s ",gr->gr_name);
192   else printf("%-8ld ",(long)as->ac_gid);
193
194   if (raw) {
195     if (as->ac_tty == (dev_t)-1) {
196       printf("-        ");
197     } else {
198       printf("%08lx ",(unsigned long)as->ac_tty);
199     }
200   } else {
201     if (as->ac_tty == (dev_t)-1) {
202       printf("-          ");
203     } else {
204       if (!walkeddev) {
205         scandev("/dev",4);
206         qsort(deventries,de_used,sizeof(*deventries),walkdev_cptr);
207         walkeddev= 1;
208       }
209       devlookfor.fn= 0;
210       devlookfor.dev= as->ac_tty;
211       deve= bsearch(&devlookfor,deventries,de_used,sizeof(*deventries),walkdev_cptr);
212       if (deve) {
213         printf("%-10s ",deve->fn);
214       } else {
215         printf("%08lx   ",(unsigned long)as->ac_tty);
216       }
217     }
218   }
219
220   r= as->ac_flag;
221   for (i=1, fp= "FSDX"; *fp; fp++, i<<=1) {
222     if (r&i) {
223       putchar(*fp);
224       r &= ~i;
225     } else {
226       putchar(' ');
227     }
228   }
229   if (r) {
230     printf("#%x",r);
231   }
232   putchar(' ');
233   
234   dc= WCOREDUMP(as->ac_exitcode) ? 'd' : 'k';
235   if (raw) {
236     if (WIFEXITED(as->ac_exitcode)) {
237       printf(" %3d",WEXITSTATUS(as->ac_exitcode));
238     } else if (WIFSIGNALED(as->ac_exitcode)) {
239       printf("%c%3d",
240              dc,
241              WTERMSIG(as->ac_exitcode));
242     } else {
243       printf("%04lx",(unsigned long)as->ac_exitcode);
244     }
245   } else {
246     if (WIFEXITED(as->ac_exitcode)) {
247       printf(" %6d",WEXITSTATUS(as->ac_exitcode));
248     } else if (WIFSIGNALED(as->ac_exitcode)) {
249       r= WTERMSIG(as->ac_exitcode);
250       if (r>0 && r<=sizeof(sigabbrev)/sizeof(*sigabbrev)) {
251         printf("%c%6s",
252                dc,
253                sigabbrev[r-1]);
254       } else {
255         printf("%cSIG%-3d",
256                dc,
257                r);
258       }
259     } else {
260       printf("#%04lx",(unsigned long)as->ac_exitcode);
261     }
262   }
263
264   if (usages) {
265     printf(" %10lu %10lu %10lu %8ld %8ld",
266            (unsigned long)as->ac_utime,
267            (unsigned long)as->ac_stime,
268            (unsigned long)as->ac_etime,
269            (unsigned long)as->ac_minflt,
270            (unsigned long)as->ac_majflt);
271   }
272   putchar('\n');
273
274   checkstdout();
275 }
276
277 static void processfile(FILE *file, const char *filename) {
278   long pos;
279   struct acct as;
280   int r;
281   
282   if (forwards) {
283     while ((r= fread(&as,1,sizeof(as),file)) == sizeof(as)) {
284       printrecord(&as,filename);
285     }
286   } else {
287     r= fseek(file,0,SEEK_END); if (r) { perror(filename); exit(8); }
288     pos= ftell(file); if (pos==-1) { perror(filename); exit(8); }
289     if (pos % sizeof(as)) { 
290       fprintf(stderr, "%s: File size is not an integral number "
291               "of accounting records", filename);
292       exit(8);
293     }
294     for (;;) {
295       if (pos<sizeof(as)) break;
296       pos -= sizeof(as);
297       r= fseek(file,pos,SEEK_SET); if (r==-1) { perror(filename); exit(8); }
298       r= fread(&as,1,sizeof(as),file); if (r!=sizeof(as)) { perror(filename); exit(8); }
299       printrecord(&as,filename);
300     }
301   }
302   if (ferror(file) || fclose(file)) { perror(filename); exit(8); }
303 }
304
305 static void processnamedfile(const char *filename) {
306   FILE *file;
307
308   file= fopen(filename,"rb"); if (!file) { perror(filename); exit(8); }
309   processfile(file,filename);
310 }
311
312 int main(int argc, const char *const *argv) {
313   myopt(&argv,cmdinfos);
314   if (!nobanner) printbanner();
315   if (usestdin) {
316     processfile(stdin,"<standard input>");
317   } else if (!*argv) {
318     processnamedfile("/var/account/pacct");
319   } else {
320     while (*argv) {
321       processnamedfile(*argv);
322       argv++;
323     }
324   }
325   checkstdout();
326   if (fflush(stdout)) { perror("flush stdout"); exit(8); }
327   return 0;
328 }