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