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