chiark / gitweb /
@@ -1,3 +1,12 @@
[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, 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 (stab)
242     if (S_ISLNK(stab->st_mode) && hidelinkmtime)
243       printf(" %10s","link");
244     else
245       printf(" %10lu",
246              (unsigned long)stab->st_mtime);
247   else
248     pu10();
249
250   if (!hidectime) {
251     if (stab)
252       printf(" %10lu",
253              (unsigned long)stab->st_ctime);
254     else
255       pu10();
256   }
257
258   putchar(filenamefieldsep);
259   fn_escaped(stdout, path);
260
261   if (foundhl) linktargpath(foundhl->path);
262   if (stab && S_ISLNK(stab->st_mode)) linktargpath(linktarg);
263
264   putchar('\n');
265
266   if (ferror(stdout)) { perror("summer: stdout"); exit(12); }
267
268   if (stab && S_ISDIR(stab->st_mode) && !(mountpoint && onefilesystem))
269     recurse(path, nodeflags, fs);
270 }
271
272 static void process(const char *startpoint) {
273   if (!quiet)
274     fprintf(stderr,"summer: processing: %s\n",startpoint);
275   node(startpoint, 0,0);
276   tdestroy(hardlinks,free);
277   hardlinks= 0;
278 }
279
280 static int recurse_maxlen;
281
282 static int recurse_filter(const struct dirent *de) {
283   int l;
284   if (de->d_name[0]=='.' &&
285       (de->d_name[1]==0 ||
286        (de->d_name[1]=='.' &&
287         de->d_name[2]==0)))
288     return 0;
289   l= strlen(de->d_name);
290   if (l > recurse_maxlen) recurse_maxlen= l;
291   return 1;
292 }
293
294 static int recurse_compar(const void *av, const void *bv) {
295   const struct dirent *const *a=av, *const *b=bv;
296   return strcmp((*a)->d_name, (*b)->d_name);
297 }
298
299 static void recurse(const char *path_or_buf, unsigned nodeflags, dev_t fs) {
300   static char *buf;
301   static int buf_allocd;
302   
303   struct dirent **namelist, *const *de;
304   const char *path_or_0= path_or_buf==buf ? 0 : path_or_buf;
305   int nentries, pathl, esave, buf_want, i;
306
307   pathl= strlen(path_or_buf);
308   recurse_maxlen= 2;
309   nentries= scandir(path_or_buf, &namelist, recurse_filter, recurse_compar);
310   esave= errno;
311   
312   buf_want= pathl+1+recurse_maxlen+1;
313   if (buf_want > buf_allocd) {
314     buf= mrealloc(buf, buf_want);
315     buf_allocd= buf_want;
316   }
317   /* NOTE that path_or_buf is invalid after this point because
318    * it might have been realloc'd ! */
319   if (path_or_0) strcpy(buf,path_or_0);
320
321   buf[pathl]= '/';
322   pathl++;
323   if (nentries < 0) {
324     buf[pathl]= 0;  errno= esave;
325     problem_e(buf,CSUMXL+72,"scandir failed");
326     fn_escaped(stdout,buf);  putchar('\n');
327     return;
328   }
329   for (i=0, de=namelist; i<nentries; i++, de++) {
330     strcpy(buf+pathl, (*de)->d_name);
331     node(buf, nodeflags, fs);
332     free(*de);
333   }
334   free(namelist);
335 }
336
337 static void from_stdin(void) {
338   char buf[MAXFN+2];
339   char *s;
340   int l;
341
342   if (!quiet)
343     fprintf(stderr, "summer: processing stdin lines as startpoints\n");
344   for (;;) {
345     s= fgets(buf,sizeof(buf),stdin);
346     if (ferror(stdin)) { perror("summer: stdin"); exit(12); }
347     if (!s) { if (feof(stdin)) return; else abort(); }
348     l= strlen(buf);
349     assert(l>0);
350     if (buf[l-1]!='\n') { fprintf(stderr,"summer: line too long\n"); exit(8); }
351     buf[l-1]= 0;
352     process(buf);
353   }
354 }
355
356 int main(int argc, const char *const *argv) {
357   const char *arg;
358   int c;
359
360   errfile= stderr;
361   
362   if ((arg=argv[1]) && *arg++=='-') {
363     while ((c=*arg++)) {
364       switch (c) {
365       case 'h':
366         fprintf(stderr,
367                 "summer: usage: summer startpoint... >data.list\n"
368                 "               cat startpoints.list | summer >data.list\n");
369         exit(8);
370       case 'q':
371         quiet= 1;
372         break;
373       case 't':
374         filenamefieldsep= '\t';
375         break;
376       case 'D':
377         hidedirsize= 1;
378         break;
379       case 'b':
380         hidelinkmtime= 1;
381         break;
382       case 'x':
383         onefilesystem= 1;
384         break;
385       case 'C':
386         hidectime= 1;
387         break;
388       case 'A':
389         hideatime= 1;
390         break;
391       case 'f':
392         errfile= stdout;
393         break;
394       default:
395         fprintf(stderr,"summer: bad usage, try -h\n");
396         exit(8);
397       }
398     }
399     argv++;
400   }
401
402   if (!argv[1]) {
403     from_stdin();
404   } else {
405     if (!quiet)
406       fprintf(stderr, "summer: processing command line args as startpoints\n");
407     while ((arg=*++argv)) {
408       process(arg);
409     }
410   }
411   if (ferror(stdout) || fclose(stdout)) {
412     perror("summer: stdout (at end)"); exit(12);
413   }
414   if (!quiet)
415     fputs("summer: done.\n", stderr);
416   return 0;
417 }