chiark / gitweb /
19c806987c18bf4d190382927acced0aa21c1e02
[chiark-utils.git] / cprogs / summer.c
1 /*
2  * usage:
3  *    cat startpoints.list | summer >data.list
4  *    summer startpoints... >data.list
5  *  prints md5sum of data-list to stderr
6  */
7
8 #define _GNU_SOURCE
9
10 #include <search.h>
11 #include <dirent.h>
12 #include <sys/types.h>
13 #include <sys/stat.h>
14 #include <unistd.h>
15 #include <stdio.h>
16 #include <string.h>
17 #include <errno.h>
18 #include <stdarg.h>
19 #include <limits.h>
20 #include <assert.h>
21 #include <stdlib.h>
22
23 #include "nettle/md5-compat.h"
24
25 #define MAXFN 2048
26 #define MAXDEPTH 1024
27 #define CSUMXL 32
28
29 static int quiet=0, hidectime=0, hideatime=0;
30 static int hidedirsize=0, hidelinkmtime=0;
31 static int filenamefieldsep=' ';
32 static FILE *errfile;
33
34 static void malloc_fail(void) { perror("summer: alloc failed"); exit(12); }
35
36 static void *mmalloc(size_t sz) {
37   void *r;
38   r= malloc(sz);  if (!r) malloc_fail();
39   return r;
40 }
41
42 static void *mrealloc(void *p, size_t sz) {
43   void *r;
44   r= realloc(p, sz);  if (!r && sz) malloc_fail();
45   return r;
46 }
47
48 static void fn_escaped(FILE *f, const char *fn) {
49   int c;
50   while ((c= *fn++)) {
51     if (c>=33 && c<=126 && c!='\\') putc(c,f);
52     else fprintf(f,"\\x%02x",(int)(unsigned char)c);
53   }
54 }
55
56 static void add_pr(int *pr, int printf_ret) {
57   if (printf_ret == EOF) return;
58   *pr += printf_ret;
59 }
60
61 static void vproblemx(const char *path, int padto, int per,
62                       const char *fmt, va_list al) {
63   int e=errno, pr=0;
64   
65   if (errfile==stderr) fputs("summer: error: ",stderr);
66   else add_pr(&pr, fprintf(errfile,"\\["));
67   
68   add_pr(&pr, vfprintf(errfile,fmt,al));
69   if (per) add_pr(&pr, fprintf(errfile,": %s",strerror(e)));
70
71   if (errfile==stderr) {
72     fputs(": ",stderr);
73     fn_escaped(stderr,path);
74     fputc('\n',stderr);
75     exit(2);
76   }
77
78   add_pr(&pr, printf("]"));
79
80   while (pr++ < padto)
81     putchar(' ');
82 }  
83
84 static void problem_e(const char *path, int padto, const char *fmt, ...) {
85   va_list(al);
86   va_start(al,fmt);
87   vproblemx(path,padto,1,fmt,al);
88   va_end(al);
89 }
90
91 static void problem(const char *path, int padto, const char *fmt, ...) {
92   va_list(al);
93   va_start(al,fmt);
94   vproblemx(path,padto,0,fmt,al);
95   va_end(al);
96 }
97
98 static void csum_file(const char *path) {
99   FILE *f;
100   MD5_CTX mc;
101   char db[65536];
102   unsigned char digest[16];
103   size_t r;
104   int i;
105
106   f= fopen(path,"rb");
107   if (!f) { problem_e(path,sizeof(digest)*2,"open"); return; }
108   
109   MD5Init(&mc);
110   for (;;) {
111     r= fread(db,1,sizeof(db),f);
112     if (ferror(f)) {
113       problem_e(path,sizeof(digest)*2,"read");
114       fclose(f); return;
115     }
116     if (!r) { assert(feof(f)); break; }
117     MD5Update(&mc,db,r);
118   }
119   MD5Final(digest,&mc);
120   if (fclose(f)) { problem_e(path,sizeof(digest)*2,"close"); return; }
121
122   for (i=0; i<sizeof(digest); i++)
123     printf("%02x", digest[i]);
124 }
125
126 static void csum_dev(int cb, const struct stat *stab) {
127   printf("%c 0x%08lx %3lu %3lu %3lu %3lu    ", cb,
128          (unsigned long)stab->st_rdev,
129          ((unsigned long)stab->st_rdev & 0x0ff000000U) >> 24,
130          ((unsigned long)stab->st_rdev & 0x000ff0000U) >> 16,
131          ((unsigned long)stab->st_rdev & 0x00000ff00U) >> 8,
132          ((unsigned long)stab->st_rdev & 0x0000000ffU) >> 0);
133 }
134
135 static void csum_str(const char *s) {
136   printf("%-*s", CSUMXL, s);
137 }
138
139 static void linktargpath(const char *linktarg) {
140   printf(" -> ");
141   fn_escaped(stdout, linktarg);
142 }
143
144 struct hardlink {
145   dev_t dev;
146   ino_t ino;
147   char path[1];
148 };
149 static void *hardlinks;
150
151 static void pu10(void) { printf(" %10s", "?"); }
152
153 static int hardlink_compar(const void *av, const void *bv) {
154   const struct hardlink *a=av, *b=bv;
155   if (a->ino != b->ino) return b->ino - a->ino;
156   return b->dev - a->dev;
157 }
158
159 static void recurse(const char *path);
160
161 static void node(const char *path) {
162   char linktarg[MAXFN+1];
163   struct hardlink *foundhl;
164   const struct stat *stab;
165   struct stat stabuf;
166   int r;
167
168   r= lstat(path, &stabuf);
169   stab= r ? 0 : &stabuf;
170
171   foundhl= 0;
172   if (stab && stab->st_nlink>1) {
173     struct hardlink *newhl, **foundhl_node;
174     newhl= mmalloc(sizeof(*newhl) + strlen(path));
175     newhl->dev= stab->st_dev;
176     newhl->ino= stab->st_ino;
177     foundhl_node= tsearch(newhl, &hardlinks, hardlink_compar);
178     if (!foundhl_node) malloc_fail();
179     foundhl= *foundhl_node;
180     if (foundhl!=newhl) {
181       free(newhl); /* hardlink to an earlier object */
182     } else {
183       foundhl= 0; /* new object with link count>1 */
184       strcpy(newhl->path, path);
185     }
186   }
187
188   if (!stab) problem_e(path,CSUMXL,"inaccessible");
189   else if (foundhl) csum_str("hardlink");
190   else if (S_ISREG(stab->st_mode)) csum_file(path);
191   else if (S_ISDIR(stab->st_mode)) csum_str("dir");
192   else if (S_ISCHR(stab->st_mode)) csum_dev('c',stab);
193   else if (S_ISBLK(stab->st_mode)) csum_dev('b',stab);
194   else if (S_ISFIFO(stab->st_mode)) csum_str("pipe");
195   else if (S_ISLNK(stab->st_mode)) csum_str("symlink");
196   else if (S_ISSOCK(stab->st_mode)) csum_str("sock");
197   else problem(path,CSUMXL,"badobj: 0x%lx", (unsigned long)stab->st_mode);
198
199   if (stab && S_ISLNK(stab->st_mode)) {
200     r= readlink(path, linktarg, sizeof(linktarg)-1);
201     if (r==sizeof(linktarg)) { problem(path,-1,"readlink too big"); r=-1; }
202     else if (r<0) { problem_e(path,-1,"readlink"); }
203     else assert(r<sizeof(linktarg));
204
205     if (r<0) strcpy(linktarg,"\\?");
206     else linktarg[r]= 0;
207   }
208
209   if (stab) {
210     if (S_ISDIR(stab->st_mode) && hidedirsize)
211       printf(" %10s","dir");
212     else
213       printf(" %10lu", 
214              (unsigned long)stab->st_size);
215
216     printf(" %4o %10ld %10ld",
217            (unsigned)stab->st_mode & 07777U,
218            (unsigned long)stab->st_uid,
219            (unsigned long)stab->st_gid);
220   } else {
221     printf(" %10s %4s %10s %10s", "?","?","?","?");
222   }
223
224   if (!hideatime) {
225     if (stab)
226       printf(" %10lu",
227              (unsigned long)stab->st_atime);
228     else
229       pu10();
230   }
231
232   if (stab)
233     if (S_ISLNK(stab->st_mode) && hidelinkmtime)
234       printf(" %10s","link");
235     else
236       printf(" %10lu",
237              (unsigned long)stab->st_mtime);
238   else
239     pu10();
240
241   if (!hidectime) {
242     if (stab)
243       printf(" %10lu",
244              (unsigned long)stab->st_ctime);
245     else
246       pu10();
247   }
248
249   putchar(filenamefieldsep);
250   fn_escaped(stdout, path);
251
252   if (foundhl) linktargpath(foundhl->path);
253   if (stab && S_ISLNK(stab->st_mode)) linktargpath(linktarg);
254
255   putchar('\n');
256
257   if (ferror(stdout)) { perror("summer: stdout"); exit(12); }
258
259   if (stab && S_ISDIR(stab->st_mode))
260     recurse(path);
261 }
262
263 static void process(const char *startpoint) {
264   if (!quiet)
265     fprintf(stderr,"summer: processing: %s\n",startpoint);
266   node(startpoint);
267   tdestroy(hardlinks,free);
268   hardlinks= 0;
269 }
270
271 static int recurse_maxlen;
272
273 static int recurse_filter(const struct dirent *de) {
274   int l;
275   if (de->d_name[0]=='.' &&
276       (de->d_name[1]==0 ||
277        (de->d_name[1]=='.' &&
278         de->d_name[2]==0)))
279     return 0;
280   l= strlen(de->d_name);
281   if (l > recurse_maxlen) recurse_maxlen= l;
282   return 1;
283 }
284
285 static int recurse_compar(const void *av, const void *bv) {
286   const struct dirent *const *a=av, *const *b=bv;
287   return strcmp((*a)->d_name, (*b)->d_name);
288 }
289
290 static void recurse(const char *path_or_buf) {
291   static char *buf;
292   static int buf_allocd;
293   
294   struct dirent **namelist, *const *de;
295   char *subpathp;
296   const char *path_or_0= path_or_buf==buf ? 0 : path_or_buf;
297   int nentries, pathl, esave, buf_want, i;
298
299   pathl= strlen(path_or_buf);
300   recurse_maxlen= 2;
301   nentries= scandir(path_or_buf, &namelist, recurse_filter, recurse_compar);
302   esave= errno;
303   
304   buf_want= pathl+1+recurse_maxlen+1;
305   if (buf_want > buf_allocd) {
306     buf= mrealloc(buf, buf_want);
307     buf_allocd= buf_want;
308   }
309   /* NOTE that path_or_buf is invalid after this point because
310    * it might have been realloc'd ! */
311   if (path_or_0) strcpy(buf,path_or_0);
312
313   buf[pathl]= '/';
314   subpathp= buf+pathl+1;
315   if (nentries < 0) {
316     strcpy(subpathp,"\\?");  errno= esave;
317     problem_e(buf,-1,"scandir failed");
318     return;
319   }
320   for (i=0, de=namelist; i<nentries; i++, de++) {
321     strcpy(subpathp, (*de)->d_name);
322     node(buf);
323     free(*de);
324   }
325   free(namelist);
326 }
327
328 static void from_stdin(void) {
329   char buf[MAXFN+2];
330   char *s;
331   int l;
332
333   if (!quiet)
334     fprintf(stderr, "summer: processing stdin lines as startpoints\n");
335   for (;;) {
336     s= fgets(buf,sizeof(buf),stdin);
337     if (ferror(stdin)) { perror("summer: stdin"); exit(12); }
338     if (!s) { if (feof(stdin)) return; else abort(); }
339     l= strlen(buf);
340     assert(l>0);
341     if (buf[l-1]!='\n') { fprintf(stderr,"summer: line too long\n"); exit(8); }
342     buf[l-1]= 0;
343     process(buf);
344   }
345 }
346
347 int main(int argc, const char *const *argv) {
348   const char *arg;
349   int c;
350
351   errfile= stderr;
352   
353   if ((arg=argv[1]) && *arg++=='-') {
354     while ((c=*arg++)) {
355       switch (c) {
356       case 'h':
357         fprintf(stderr,
358                 "summer: usage: summer startpoint... >data.list\n"
359                 "               cat startpoints.list | summer >data.list\n");
360         exit(8);
361       case 'q':
362         quiet= 1;
363         break;
364       case 't':
365         filenamefieldsep= '\t';
366         break;
367       case 'D':
368         hidedirsize= 1;
369         break;
370       case 'b':
371         hidelinkmtime= 1;
372         break;
373       case 'C':
374         hidectime= 1;
375         break;
376       case 'A':
377         hideatime= 1;
378         break;
379       case 'f':
380         errfile= stdout;
381         break;
382       default:
383         fprintf(stderr,"summer: bad usage, try -h\n");
384         exit(8);
385       }
386     }
387     argv++;
388   }
389
390   if (!argv[1]) {
391     from_stdin();
392   } else {
393     if (!quiet)
394       fprintf(stderr, "summer: processing command line args as startpoints\n");
395     while ((arg=*++argv)) {
396       process(arg);
397     }
398   }
399   if (ferror(stdout) || fclose(stdout)) {
400     perror("summer: stdout (at end)"); exit(12);
401   }
402   if (!quiet)
403     fputs("summer: done.\n", stderr);
404   return 0;
405 }