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