chiark / gitweb /
566840294c954ca3c2a6e3d8816c470d512f1905
[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 <errno.h>
32 #include <sys/stat.h>
33
34 typedef unsigned long long u64;
35
36
37 /* Sadly this thing is not very portable */
38
39 #if defined(__linux__)
40
41 #include <sys/types.h>
42 #include <sys/acct.h>
43
44 typedef struct acct_v3 struct_acct;
45
46 #elif defined(__FreeBSD__) || defined(__FreeBSD_kernel__)
47
48 #include <sys/param.h>
49 #include <sys/types.h>
50 #include <sys/acct.h>
51
52 typedef struct acctv2 struct_acct;
53
54 #else
55
56 #error Do not know what struct_acct to use on this platform
57
58 #endif
59
60
61 #include "myopt.h"
62
63 static int forwards, nobanner, usestdin, raw, usages;
64
65 static int de_used, de_allocd;
66 static struct deventry {
67   const char *fn;
68   dev_t dev;
69 } *deventries;
70
71 static const struct cmdinfo cmdinfos[]= {
72   { "--forwards",  'f',  0, &forwards, 0, 0, 1, 0, 0 },
73   { "--no-banner", 'q',  0, &nobanner, 0, 0, 1, 0, 0 },
74   { "--stdin",     'p',  0, &usestdin, 0, 0, 1, 0, 0 },
75   { "--raw",       'r',  0, &raw,      0, 0, 1, 0, 0 },
76   { "--resource",  'u',  0, &usages,   0, 0, 1, 0, 0 },
77   {  0                                                 }
78 };
79
80 static const char *sigabbrev[]= {
81   "HUP", "INT", "QUIT", "ILL", "TRAP", "ABRT", "BUS", "FPE",
82   "KILL", "USR1", "SEGV", "USR2", "PIPE", "ALRM", "TERM", "STKFLT",
83   "CHLD", "CONT", "STOP", "TSTP", "TTIN", "TTOU", "URG", "XCPU",
84   "XFSZ", "VTALRM", "PROF", "WINCH", "IO"
85 };
86
87 void usagemessage(void) {
88   fputs("usage: acctdump [<options>] [<file> ...]\n"
89         "options: -f|--forwards -q|--no-banner -p|--stdin -r|--raw -u|--resource\n",
90         stderr);
91   if (ferror(stderr)) { perror("print usage"); exit(8); }
92 }
93
94 static void checkstdout(void) {
95   if (ferror(stdout)) { perror("stdout"); exit(8); }
96 }
97
98 static void scandev(const char *basename, int levelsleft) {
99   /* We deliberately ignore most errors */
100   DIR *dir;
101   struct dirent *de;
102   struct stat stab;
103   int fnbufalloc, fnbufreq, r, basel, nallocd;
104   char *fnbuf, *nfnbuf;
105   struct deventry *ndeventries;
106   
107   if (levelsleft==0) return;
108
109   dir= opendir(basename); 
110   if (!dir) {
111     fprintf(stderr, "%s: opendir: %s\n", basename, strerror(errno));
112     return;
113   }
114   fnbufalloc= 0;
115   fnbuf= 0;
116   basel= strlen(basename);
117
118   while ((errno=0, de= readdir(dir))) {
119     fnbufreq= basel+1+strlen(de->d_name)+1;
120     if (fnbufalloc<fnbufreq) {
121       fnbufalloc= fnbufreq+10;
122       nfnbuf= realloc(fnbuf,fnbufalloc);
123       if (!nfnbuf) { free(fnbuf); fnbufalloc=0; continue; }
124       fnbuf= nfnbuf;
125     }
126     sprintf(fnbuf,"%s/%s",basename,de->d_name);
127     r= lstat(fnbuf,&stab);
128     if (r) {
129       fprintf(stderr, "%s: %s\n", fnbuf, strerror(errno));
130       continue;
131     }
132     if (S_ISCHR(stab.st_mode)) {
133       if (de_used >= de_allocd) {
134         nallocd= (de_allocd+10)<<1;
135         ndeventries= realloc(deventries,nallocd*sizeof(*deventries));
136         if (!ndeventries) continue;
137         de_allocd= nallocd;
138         deventries= ndeventries;
139       }
140       deventries[de_used].fn= strdup(fnbuf+5); /* remove /dev */
141       if (!deventries[de_used].fn) continue;
142       deventries[de_used].dev= stab.st_rdev;
143       de_used++;
144     } else if (S_ISDIR(stab.st_mode) && de->d_name[0] != '.') {
145       scandev(fnbuf,levelsleft-1);
146     }
147   }
148   if (errno)
149       fprintf(stderr, "%s: readdir: %s\n", basename, strerror(errno));
150   closedir(dir);
151   free(fnbuf);
152 }
153
154 static int walkdev_cptr(const void *av, const void *bv) {
155   const struct deventry *a= av;
156   const struct deventry *b= bv;
157   return a->dev - b->dev;
158 }
159   
160 static void printbanner(void) {
161   if (raw) {
162     fputs("begin date command          "
163           "uid      gid      tty dev  FSDX exit",
164           stdout);
165   } else {
166     fputs("begin date and time command          "
167           "user     group    tty dev    FSDX sigexit",
168           stdout);
169   }
170   if (usages) {
171     fputs("  user time   sys time  elap time   minflt   maxflt",
172           stdout);
173   }
174   putchar('\n');
175   checkstdout();
176 }
177
178 static void printrecord(const struct_acct *as, const char *filename) {
179   static int walkeddev;
180
181   int i, dc, r;
182   const char *fp;
183   char buf[100];
184   struct tm *tm;
185   struct deventry *deve, devlookfor;
186   struct passwd *pw;
187   struct group *gr;
188   time_t btime;
189
190   if (raw) {
191     printf("%10lu ",(unsigned long)as->ac_btime);
192   } else {
193     btime= as->ac_btime;
194     tm= localtime(&btime);
195     strftime(buf,sizeof(buf),"%Y-%m-%d %H:%M:%S",tm); buf[sizeof(buf)-1]= 0;
196     printf("%19s ",buf);
197   }
198   
199   printf("%-16.16s ", as->ac_comm);
200   
201   pw= raw ? 0 : getpwuid(as->ac_uid);
202   if (pw) printf("%-8s ",pw->pw_name);
203   else printf("%-8ld ",(long)as->ac_uid);
204   
205   gr= raw ? 0 : getgrgid(as->ac_gid);
206   if (gr) printf("%-8s ",gr->gr_name);
207   else printf("%-8ld ",(long)as->ac_gid);
208
209   if (raw) {
210     if (!(as->ac_tty + 1) /* check for -1 without knowing type */) {
211       printf("-        ");
212     } else {
213       printf("%08lx ",(unsigned long)as->ac_tty);
214     }
215   } else {
216     if (!(as->ac_tty + 1)) {
217       printf("-          ");
218     } else {
219       if (!walkeddev) {
220         scandev("/dev",4);
221         qsort(deventries,de_used,sizeof(*deventries),walkdev_cptr);
222         walkeddev= 1;
223       }
224       devlookfor.fn= 0;
225       devlookfor.dev= as->ac_tty;
226       deve= bsearch(&devlookfor,deventries,de_used,sizeof(*deventries),walkdev_cptr);
227       if (deve) {
228         printf("%-10s ",deve->fn);
229       } else {
230         printf("%08lx   ",(unsigned long)as->ac_tty);
231       }
232     }
233   }
234
235   r= as->ac_flag;
236   for (i=1, fp= "FS4DX"; *fp; fp++, i<<=1) {
237     if (r&i) {
238       putchar(*fp);
239       r &= ~i;
240     } else if (!isdigit(*fp)) {
241       putchar(' ');
242     }
243   }
244   if (r) {
245     printf("#%x",r);
246   }
247   putchar(' ');
248   
249   dc= WCOREDUMP(as->ac_exitcode) ? 'd' : 'k';
250   if (raw) {
251     if (WIFEXITED(as->ac_exitcode)) {
252       printf(" %3d",WEXITSTATUS(as->ac_exitcode));
253     } else if (WIFSIGNALED(as->ac_exitcode)) {
254       printf("%c%3d",
255              dc,
256              WTERMSIG(as->ac_exitcode));
257     } else {
258       printf("%04lx",(unsigned long)as->ac_exitcode);
259     }
260   } else {
261     if (WIFEXITED(as->ac_exitcode)) {
262       printf(" %6d",WEXITSTATUS(as->ac_exitcode));
263     } else if (WIFSIGNALED(as->ac_exitcode)) {
264       r= WTERMSIG(as->ac_exitcode);
265       if (r>0 && r<=sizeof(sigabbrev)/sizeof(*sigabbrev)) {
266         printf("%c%6s",
267                dc,
268                sigabbrev[r-1]);
269       } else {
270         printf("%cSIG%-3d",
271                dc,
272                r);
273       }
274     } else {
275       printf("#%04lx",(unsigned long)as->ac_exitcode);
276     }
277   }
278
279   if (usages) {
280     printf(" %10lu %10lu %10lu %8ld %8ld",
281            (unsigned long)as->ac_utime,
282            (unsigned long)as->ac_stime,
283            (unsigned long)as->ac_etime,
284            (unsigned long)as->ac_minflt,
285            (unsigned long)as->ac_majflt);
286   }
287   putchar('\n');
288
289   checkstdout();
290 }
291
292 static void processfile(FILE *file, const char *filename) {
293   struct_acct as;
294   long pos;
295   int r;
296   
297   if (forwards) {
298     while ((r= fread(&as,1,sizeof(as),file)) == sizeof(as)) {
299       printrecord(&as,filename);
300     }
301   } else {
302     r= fseek(file,0,SEEK_END); if (r) { perror(filename); exit(8); }
303     pos= ftell(file); if (pos==-1) { perror(filename); exit(8); }
304     if (pos % sizeof(as)) { 
305       fprintf(stderr, "%s: File size is not an integral number "
306               "of accounting records\n", filename);
307       exit(8);
308     }
309     for (;;) {
310       if (pos<sizeof(as)) break;
311       pos -= sizeof(as);
312       r= fseek(file,pos,SEEK_SET); if (r==-1) { perror(filename); exit(8); }
313       r= fread(&as,1,sizeof(as),file); if (r!=sizeof(as)) { perror(filename); exit(8); }
314       printrecord(&as,filename);
315     }
316   }
317   if (ferror(file) || fclose(file)) { perror(filename); exit(8); }
318 }
319
320 static void processnamedfile(const char *filename) {
321   FILE *file;
322
323   file= fopen(filename,"rb"); if (!file) { perror(filename); exit(8); }
324   processfile(file,filename);
325 }
326
327 int main(int argc, const char *const *argv) {
328   myopt(&argv,cmdinfos);
329   if (!nobanner) printbanner();
330   if (usestdin) {
331     processfile(stdin,"<standard input>");
332   } else if (!*argv) {
333     processnamedfile("/var/log/account/pacct");
334   } else {
335     while (*argv) {
336       processnamedfile(*argv);
337       argv++;
338     }
339   }
340   checkstdout();
341   if (fflush(stdout)) { perror("flush stdout"); exit(8); }
342   return 0;
343 }