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