/*
+ * summer - program for summarising (with md5 checksums) filesystem trees
+ *
* usage:
* cat startpoints.list | summer >data.list
* summer startpoints... >data.list
* prints md5sum of data-list to stderr
*/
+/*
+ * Copyright (C) 2003,2006-2007 Ian Jackson <ian@davenant.greenend.org.uk>
+ *
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 3,
+ * or (at your option) any later version.
+ *
+ * This is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this file; if not, consult the Free Software
+ * Foundation's website at www.fsf.org, or the GNU Project website at
+ * www.gnu.org.
+ */
#define _GNU_SOURCE
-#include <ftw.h>
+#include <search.h>
+#include <dirent.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <stdarg.h>
#include <limits.h>
#include <assert.h>
+#include <stdlib.h>
-#include "md5.h"
+#include "nettle/md5-compat.h"
#define MAXFN 2048
#define MAXDEPTH 1024
#define CSUMXL 32
+static int quiet=0, hidectime=0, hideatime=0, hidemtime=0;
+static int hidedirsize=0, hidelinkmtime=0, hidextime=0, onefilesystem=0;
+static int filenamefieldsep=' ';
+static FILE *errfile;
+
+#define nodeflag_fsvalid 1u
+
+static void malloc_fail(void) { perror("summer: alloc failed"); exit(12); }
+
+static void *mmalloc(size_t sz) {
+ void *r;
+ r= malloc(sz); if (!r) malloc_fail();
+ return r;
+}
+
+static void *mrealloc(void *p, size_t sz) {
+ void *r;
+ r= realloc(p, sz); if (!r && sz) malloc_fail();
+ return r;
+}
+
static void fn_escaped(FILE *f, const char *fn) {
int c;
while ((c= *fn++)) {
*pr += printf_ret;
}
-static void vproblemx(int padto, int per, const char *fmt, va_list al) {
+static void vproblemx(const char *path, int padto, int per,
+ const char *fmt, va_list al) {
int e=errno, pr=0;
- add_pr(&pr, printf("\\["));
- add_pr(&pr, vprintf(fmt,al));
- if (per) add_pr(&pr, printf(": %s",strerror(e)));
+
+ if (errfile==stderr) fputs("summer: error: ",stderr);
+ else add_pr(&pr, fprintf(errfile,"\\["));
+
+ add_pr(&pr, vfprintf(errfile,fmt,al));
+ if (per) add_pr(&pr, fprintf(errfile,": %s",strerror(e)));
+
+ if (errfile==stderr) {
+ fputs(": ",stderr);
+ fn_escaped(stderr,path);
+ fputc('\n',stderr);
+ exit(2);
+ }
+
add_pr(&pr, printf("]"));
+
while (pr++ < padto)
putchar(' ');
}
-static void problem_e(int padto, const char *fmt, ...) {
+static void problem_e(const char *path, int padto, const char *fmt, ...) {
va_list(al);
va_start(al,fmt);
- vproblemx(padto,1,fmt,al);
+ vproblemx(path,padto,1,fmt,al);
va_end(al);
}
-static void problem(int padto, const char *fmt, ...) {
+static void problem(const char *path, int padto, const char *fmt, ...) {
va_list(al);
va_start(al,fmt);
- vproblemx(padto,0,fmt,al);
+ vproblemx(path,padto,0,fmt,al);
va_end(al);
}
static void csum_file(const char *path) {
FILE *f;
- struct MD5Context mc;
+ MD5_CTX mc;
char db[65536];
unsigned char digest[16];
size_t r;
int i;
f= fopen(path,"rb");
- if (!f) { problem_e(sizeof(digest)*2,"open"); return; }
+ if (!f) { problem_e(path,sizeof(digest)*2,"open"); return; }
MD5Init(&mc);
for (;;) {
r= fread(db,1,sizeof(db),f);
- if (ferror(f)) { problem_e(sizeof(digest)*2,"read"); fclose(f); return; }
+ if (ferror(f)) {
+ problem_e(path,sizeof(digest)*2,"read");
+ fclose(f); return;
+ }
if (!r) { assert(feof(f)); break; }
MD5Update(&mc,db,r);
}
MD5Final(digest,&mc);
- if (fclose(f)) { problem_e(sizeof(digest)*2,"close"); return; }
+ if (fclose(f)) { problem_e(path,sizeof(digest)*2,"close"); return; }
for (i=0; i<sizeof(digest); i++)
printf("%02x", digest[i]);
printf("%-*s", CSUMXL, s);
}
-struct FTW;
+static void linktargpath(const char *linktarg) {
+ printf(" -> ");
+ fn_escaped(stdout, linktarg);
+}
+
+static void pu10(void) { printf(" %10s", "?"); }
+
+#define PTIME(stab, memb) ((stab) ? ptime((stab), (stab)->memb) : pu10())
+
+static void ptime(const struct stat *stab, unsigned long val) {
+ const char *instead;
+
+ if (!hidextime) goto justprint;
+ else if (S_ISCHR(stab->st_mode)) instead= "char";
+ else if (S_ISBLK(stab->st_mode)) instead= "block";
+ else if (S_ISLNK(stab->st_mode)) instead= "link";
+ else if (S_ISSOCK(stab->st_mode)) instead= "sock";
+ else if (S_ISFIFO(stab->st_mode)) instead= "pipe";
+ else {
+ justprint:
+ printf(" %10lu", val);
+ return;
+ }
+
+ printf(" %10s",instead);
+}
-static int item(const char *path, const struct stat *stab,
- int flag, struct FTW *ftws) {
+struct hardlink {
+ dev_t dev;
+ ino_t ino;
+ char path[1];
+};
+static void *hardlinks;
+
+static int hardlink_compar(const void *av, const void *bv) {
+ const struct hardlink *a=av, *b=bv;
+ if (a->ino != b->ino) return b->ino - a->ino;
+ return b->dev - a->dev;
+}
+
+static void recurse(const char *path, unsigned nodeflags, dev_t fs);
+
+static void node(const char *path, unsigned nodeflags, dev_t fs) {
char linktarg[MAXFN+1];
+ struct hardlink *foundhl;
+ const struct stat *stab;
+ struct stat stabuf;
+ int r, mountpoint=0;
- switch (flag) {
- case FTW_D:
- case FTW_F:
- case FTW_SL:
- if (S_ISREG(stab->st_mode)) csum_file(path);
- else if (S_ISDIR(stab->st_mode)) csum_str("dir");
- else if (S_ISCHR(stab->st_mode)) csum_dev('c',stab);
- else if (S_ISBLK(stab->st_mode)) csum_dev('b',stab);
- else if (S_ISFIFO(stab->st_mode)) csum_str("pipe");
- else if (S_ISLNK(stab->st_mode)) csum_str("link");
- else if (S_ISSOCK(stab->st_mode)) csum_str("sock");
- else problem(CSUMXL,"badobj: 0x%lx", (unsigned long)stab->st_mode);
- break;
+ r= lstat(path, &stabuf);
+ stab= r ? 0 : &stabuf;
- case FTW_NS:
- case FTW_DNR:
- problem_e(CSUMXL,"inaccessible");
- break;
+ foundhl= 0;
+ if (stab && stab->st_nlink>1) {
+ struct hardlink *newhl, **foundhl_node;
+ newhl= mmalloc(sizeof(*newhl) + strlen(path));
+ newhl->dev= stab->st_dev;
+ newhl->ino= stab->st_ino;
+ foundhl_node= tsearch(newhl, &hardlinks, hardlink_compar);
+ if (!foundhl_node) malloc_fail();
+ foundhl= *foundhl_node;
+ if (foundhl!=newhl) {
+ free(newhl); /* hardlink to an earlier object */
+ } else {
+ foundhl= 0; /* new object with link count>1 */
+ strcpy(newhl->path, path);
+ }
+ }
- default:
- problem(CSUMXL,"ftw flag 0x%x: %s",flag);
+ if (stab) {
+ if ((nodeflags & nodeflag_fsvalid) && stab->st_dev != fs)
+ mountpoint= 1;
+ fs= stab->st_dev;
+ nodeflags |= nodeflag_fsvalid;
}
- if (S_ISLNK(stab->st_mode)) {
- int r;
+ if (!stab) problem_e(path,CSUMXL,"inaccessible");
+ else if (foundhl) csum_str("hardlink");
+ else if (S_ISREG(stab->st_mode)) csum_file(path);
+ else if (S_ISCHR(stab->st_mode)) csum_dev('c',stab);
+ else if (S_ISBLK(stab->st_mode)) csum_dev('b',stab);
+ else if (S_ISFIFO(stab->st_mode)) csum_str("pipe");
+ else if (S_ISLNK(stab->st_mode)) csum_str("symlink");
+ else if (S_ISSOCK(stab->st_mode)) csum_str("sock");
+ else if (S_ISDIR(stab->st_mode)) csum_str(mountpoint ? "mountpoint" : "dir");
+ else problem(path,CSUMXL,"badobj: 0x%lx", (unsigned long)stab->st_mode);
+ if (stab && S_ISLNK(stab->st_mode)) {
r= readlink(path, linktarg, sizeof(linktarg)-1);
- if (r==sizeof(linktarg)) { problem(-1,"readlink too big"); r=-1; }
- else if (r<0) { problem_e(-1,"readlink"); }
+ if (r==sizeof(linktarg)) { problem(path,-1,"readlink too big"); r=-1; }
+ else if (r<0) { problem_e(path,-1,"readlink"); }
else assert(r<sizeof(linktarg));
if (r<0) strcpy(linktarg,"\\?");
else linktarg[r]= 0;
}
- printf(" %10lu %4d %4o %10ld %10ld %10lu %10lu %10lu ",
- (unsigned long)stab->st_size,
- (int)stab->st_nlink,
- (unsigned)stab->st_mode & 07777U,
- (unsigned long)stab->st_uid,
- (unsigned long)stab->st_gid,
- (unsigned long)stab->st_atime,
- (unsigned long)stab->st_mtime,
- (unsigned long)stab->st_ctime);
- fn_escaped(stdout, path);
+ if (stab) {
+ if (S_ISDIR(stab->st_mode) && hidedirsize)
+ printf(" %10s","dir");
+ else
+ printf(" %10lu",
+ (unsigned long)stab->st_size);
+
+ printf(" %4o %10ld %10ld",
+ (unsigned)stab->st_mode & 07777U,
+ (unsigned long)stab->st_uid,
+ (unsigned long)stab->st_gid);
+ } else {
+ printf(" %10s %4s %10s %10s", "?","?","?","?");
+ }
- if (S_ISLNK(stab->st_mode)) {
- printf(" -> ");
- fn_escaped(stdout, linktarg);
+ if (!hideatime)
+ PTIME(stab, st_atime);
+
+ if (!hidemtime) {
+ if (stab && S_ISLNK(stab->st_mode) && hidelinkmtime)
+ printf(" %10s","link");
+ else
+ PTIME(stab, st_mtime);
}
+
+ if (!hidectime)
+ PTIME(stab, st_ctime);
+
+ putchar(filenamefieldsep);
+ fn_escaped(stdout, path);
+
+ if (foundhl) linktargpath(foundhl->path);
+ if (stab && S_ISLNK(stab->st_mode)) linktargpath(linktarg);
+
putchar('\n');
if (ferror(stdout)) { perror("summer: stdout"); exit(12); }
- return 0;
+
+ if (stab && S_ISDIR(stab->st_mode) && !(mountpoint && onefilesystem))
+ recurse(path, nodeflags, fs);
}
static void process(const char *startpoint) {
- int r;
- fprintf(stderr,"summer: processing: %s\n",startpoint);
- r= nftw(startpoint, item, MAXDEPTH, FTW_MOUNT|FTW_PHYS);
- if (r) { fprintf(stderr, "summer: nftw failed: %s: %s\n",
- strerror(errno), startpoint); exit(4); }
+ if (!quiet)
+ fprintf(stderr,"summer: processing: %s\n",startpoint);
+ node(startpoint, 0,0);
+ tdestroy(hardlinks,free);
+ hardlinks= 0;
+}
+
+static int recurse_maxlen;
+
+static int recurse_filter(const struct dirent *de) {
+ int l;
+ if (de->d_name[0]=='.' &&
+ (de->d_name[1]==0 ||
+ (de->d_name[1]=='.' &&
+ de->d_name[2]==0)))
+ return 0;
+ l= strlen(de->d_name);
+ if (l > recurse_maxlen) recurse_maxlen= l;
+ return 1;
+}
+
+static int recurse_compar(const void *av, const void *bv) {
+ const struct dirent *const *a=av, *const *b=bv;
+ return strcmp((*a)->d_name, (*b)->d_name);
+}
+
+static void recurse(const char *path_or_buf, unsigned nodeflags, dev_t fs) {
+ static char *buf;
+ static int buf_allocd;
+
+ struct dirent **namelist, *const *de;
+ const char *path_or_0= path_or_buf==buf ? 0 : path_or_buf;
+ int nentries, pathl, esave, buf_want, i;
+
+ pathl= strlen(path_or_buf);
+ recurse_maxlen= 2;
+ nentries= scandir(path_or_buf, &namelist, recurse_filter, recurse_compar);
+ esave= errno;
+
+ buf_want= pathl+1+recurse_maxlen+1;
+ if (buf_want > buf_allocd) {
+ buf= mrealloc(buf, buf_want);
+ buf_allocd= buf_want;
+ }
+ /* NOTE that path_or_buf is invalid after this point because
+ * it might have been realloc'd ! */
+ if (path_or_0) strcpy(buf,path_or_0);
+
+ buf[pathl]= '/';
+ pathl++;
+ if (nentries < 0) {
+ buf[pathl]= 0; errno= esave;
+ problem_e(buf,CSUMXL+72,"scandir failed");
+ fn_escaped(stdout,buf); putchar('\n');
+ return;
+ }
+ for (i=0, de=namelist; i<nentries; i++, de++) {
+ strcpy(buf+pathl, (*de)->d_name);
+ node(buf, nodeflags, fs);
+ free(*de);
+ }
+ free(namelist);
}
static void from_stdin(void) {
char buf[MAXFN+2];
char *s;
int l;
-
- fprintf(stderr, "summer: processing stdin lines as startpoints\n");
+
+ if (!quiet)
+ fprintf(stderr, "summer: processing stdin lines as startpoints\n");
for (;;) {
s= fgets(buf,sizeof(buf),stdin);
if (ferror(stdin)) { perror("summer: stdin"); exit(12); }
int main(int argc, const char *const *argv) {
const char *arg;
+ int c;
+
+ errfile= stderr;
+ while ((arg=argv[1]) && *arg++=='-') {
+ while ((c=*arg++)) {
+ switch (c) {
+ case 'h':
+ fprintf(stderr,
+ "summer: usage: summer startpoint... >data.list\n"
+ " cat startpoints.list | summer >data.list\n");
+ exit(8);
+ case 'q':
+ quiet= 1;
+ break;
+ case 't':
+ filenamefieldsep= '\t';
+ break;
+ case 'D':
+ hidedirsize= 1;
+ break;
+ case 'b':
+ hidelinkmtime= 1;
+ break;
+ case 'B':
+ hidextime= 1;
+ break;
+ case 'x':
+ onefilesystem= 1;
+ break;
+ case 'C':
+ hidectime= 1;
+ break;
+ case 'A':
+ hideatime= 1;
+ break;
+ case 'M':
+ hidemtime= 1;
+ break;
+ case 'f':
+ errfile= stdout;
+ break;
+ default:
+ fprintf(stderr,"summer: bad usage, try -h\n");
+ exit(8);
+ }
+ }
+ argv++;
+ }
+
if (!argv[1]) {
from_stdin();
- } else if (argv[1][0]=='h') {
- fprintf(stderr,
- "summer: usage: summer startpoint... >data.list\n"
- " cat startpoints.list | summer >data.list\n");
- return 8;
} else {
- fprintf(stderr, "summer: processing command line args as startpoints\n");
+ if (!quiet)
+ fprintf(stderr, "summer: processing command line args as startpoints\n");
while ((arg=*++argv)) {
process(arg);
}
if (ferror(stdout) || fclose(stdout)) {
perror("summer: stdout (at end)"); exit(12);
}
- fputs("summer: done.\n", stderr);
+ if (!quiet)
+ fputs("summer: done.\n", stderr);
return 0;
}