chiark / gitweb /
887409db55bb0d3ed43d8c675a65b3b61332d3bd
[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) {
291   static char *buf;
292   static int buf_allocd;
293   
294   struct dirent **namelist, *const *de;
295   char *subpathp;
296   int nentries, pathl, esave, buf_want, i;
297
298   pathl= strlen(path);
299   recurse_maxlen= 2;
300   nentries= scandir(path, &namelist, recurse_filter, recurse_compar);
301   esave= errno;
302   buf_want= pathl+1+recurse_maxlen+1;
303   if (buf_want > buf_allocd) {
304     buf= mrealloc(buf, buf_want);
305     buf_allocd= buf_want;
306   }
307   strcpy(buf,path);
308   buf[pathl]= '/';
309   subpathp= buf+pathl+1;
310   if (nentries < 0) {
311     strcpy(subpathp,"\\?");  errno= esave;
312     problem_e(buf,-1,"scandir failed");
313     return;
314   }
315   for (i=0, de=namelist; i<nentries; i++, de++) {
316     strcpy(subpathp, (*de)->d_name);
317     node(buf);
318     free(*de);
319   }
320   free(namelist);
321 }
322
323 static void from_stdin(void) {
324   char buf[MAXFN+2];
325   char *s;
326   int l;
327
328   if (!quiet)
329     fprintf(stderr, "summer: processing stdin lines as startpoints\n");
330   for (;;) {
331     s= fgets(buf,sizeof(buf),stdin);
332     if (ferror(stdin)) { perror("summer: stdin"); exit(12); }
333     if (!s) { if (feof(stdin)) return; else abort(); }
334     l= strlen(buf);
335     assert(l>0);
336     if (buf[l-1]!='\n') { fprintf(stderr,"summer: line too long\n"); exit(8); }
337     buf[l-1]= 0;
338     process(buf);
339   }
340 }
341
342 int main(int argc, const char *const *argv) {
343   const char *arg;
344   int c;
345
346   errfile= stderr;
347   
348   if ((arg=argv[1]) && *arg++=='-') {
349     while ((c=*arg++)) {
350       switch (c) {
351       case 'h':
352         fprintf(stderr,
353                 "summer: usage: summer startpoint... >data.list\n"
354                 "               cat startpoints.list | summer >data.list\n");
355         exit(8);
356       case 'q':
357         quiet= 1;
358         break;
359       case 't':
360         filenamefieldsep= '\t';
361         break;
362       case 'D':
363         hidedirsize= 1;
364         break;
365       case 'b':
366         hidelinkmtime= 1;
367         break;
368       case 'C':
369         hidectime= 1;
370         break;
371       case 'A':
372         hideatime= 1;
373         break;
374       case 'f':
375         errfile= stdout;
376         break;
377       default:
378         fprintf(stderr,"summer: bad usage, try -h\n");
379         exit(8);
380       }
381     }
382     argv++;
383   }
384
385   if (!argv[1]) {
386     from_stdin();
387   } else {
388     if (!quiet)
389       fprintf(stderr, "summer: processing command line args as startpoints\n");
390     while ((arg=*++argv)) {
391       process(arg);
392     }
393   }
394   if (ferror(stdout) || fclose(stdout)) {
395     perror("summer: stdout (at end)"); exit(12);
396   }
397   if (!quiet)
398     fputs("summer: done.\n", stderr);
399   return 0;
400 }