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