From 43d1332f4cc5f072de04db84f236ad01e9ab7425 Mon Sep 17 00:00:00 2001 From: Mark Wooding Date: Sun, 2 Oct 2011 13:41:44 +0100 Subject: [PATCH] hashsum.c: Optional progress indicator for large files. Hashing large files is very dull. Optionally provide some eyecandy and a completion time estimate (if the input is seekable) in order to keep the user happy. --- hashsum.1 | 6 +++- hashsum.c | 108 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 110 insertions(+), 4 deletions(-) diff --git a/hashsum.1 b/hashsum.1 index e6b1f39..f0bd831 100644 --- a/hashsum.1 +++ b/hashsum.1 @@ -12,7 +12,7 @@ hashsum \- compute and verify cryptographic checksums of files .SH SYNOPSIS .B hashsum -.RB [ \-f0ecbv ] +.RB [ \-f0ecbpv ] .RB [ \-a .IR algorithm ] .RB [ \-E @@ -122,6 +122,10 @@ Assume that the files to be hashed are binary files. This doesn't make any difference in Unix systems, although it might on other platforms which draw a distinction. .TP +.B "\-p, \-\-progress" +Display a progress indicator while hashing large files. The progress +indicator is written to standard error. +.TP .B "\-v, \-\-verbose" In conjunction with the .B \-c diff --git a/hashsum.c b/hashsum.c index 9721410..d0ac31e 100644 --- a/hashsum.c +++ b/hashsum.c @@ -29,6 +29,8 @@ /*----- Header files ------------------------------------------------------*/ +#define _FILE_OFFSET_BITS 64 + #include "config.h" #include @@ -53,6 +55,16 @@ #include "ghash.h" #include "cc.h" +#ifndef PATHSEP +# if defined(__riscos) +# define PATHSEP '.' +# elif defined(__unix) || defined(unix) +# define PATHSEP '/' +# else +# define PATHSEP '\\' +# endif +#endif + /*----- Static variables --------------------------------------------------*/ #define f_binary 1u @@ -64,6 +76,7 @@ #define f_oddhash 64u #define f_escape 128u #define f_oddenc 256u +#define f_progress 512u /*----- Encoding and decoding ---------------------------------------------*/ @@ -203,24 +216,109 @@ static const encodeops *getencoding(const char *ename) * Use: Hashes a file. */ +struct unit { + const char *name; + int m; +}; + +static void prhuman_time(FILE *fp, unsigned long n) +{ + const static struct unit utime[] = { + { "s", 60 }, { "m", 60 }, { "h", 24 }, { "d", 0 } + }; + + unsigned long m = 0; + const struct unit *u = utime; + + while (u[1].m && n > u[0].m*u[1].m) { n /= u->m; u++; } + m = n / u[1].m; n %= u[0].m; + if (m) fprintf(fp, "%3lu%s%02lu%s", m, u[1].name, n, u[0].name); + else fprintf(fp, " %2lu%s", n, u[0].name); +} + +static void prhuman_data(FILE *fp, off_t n) +{ + const static struct unit udata[] = { + { " ", 1024 }, { "k", 1024 }, { "M", 1024 }, { "G", 1024 }, + { "T", 1024 }, { "P", 1024 }, { "E", 1024 }, { "Z", 1024 }, + { "Y", 0 } + }; + + double x = n; + const struct unit *u = udata; + + while (u->m && x >= u->m) { x /= u->m; u++; } + fprintf(fp, "%6.1f%s", x, u->name); +} + static int fhash(const char *file, unsigned f, const gchash *gch, void *buf) { FILE *fp; - char fbuf[BUFSIZ]; + char fbuf[1024 * 128]; size_t sz; ghash *h; int e; + off_t fsz = -1, fo; + const char *p; + dstr d = DSTR_INIT; + static char baton[] = "-\\|/"; + char *bp = baton; + time_t now, last, start; + int i, pc; if (!file || strcmp(file, "-") == 0) fp = stdin; else if ((fp = fopen(file, f & f_binary ? "rb" : "r")) == 0) return (-1); + if (f & f_progress) { + if ((fo = ftello(fp)) >= 0 && + fseeko(fp, 0, SEEK_END) >= 0 && + (fsz = ftello(fp), + fseeko(fp, fo, SEEK_SET) < 0)) + return (-1); + if (fo != -1 && fsz != -1) fsz -= fo; + fo = 0; + sz = strlen(file); + if (sz < 24) + dstr_puts(&d, file); + else if ((p = strchr(file + sz - 20, PATHSEP)) != 0) { + dstr_puts(&d, "..."); dstr_puts(&d, p); + } else { + p = strrchr(file, PATHSEP); + if (!p) dstr_putf(&d, "%.20s...", file); + else dstr_putf(&d, "...%.17s...", p); + } + start = last = time(0); + } + h = GH_INIT(gch); - while ((sz = fread(fbuf, 1, sizeof(fbuf), fp)) > 0) + while ((sz = fread(fbuf, 1, sizeof(fbuf), fp)) > 0) { GH_HASH(h, fbuf, sz); + if (f & f_progress) { + fo += sz; + now = time(0); + if (difftime(now, last) < 1) continue; + last = now; + fprintf(stderr, "\r%-24s", d.buf); + fprintf(stderr, "%c ", *bp++); if (!*bp) bp = baton; + prhuman_data(stderr, fo); + if (fsz >= fo) { + fputc('/', stderr); + prhuman_data(stderr, fsz); + fputs(" [", stderr); + pc = (fo*16 + fsz/2)/fsz; + for (i = 0; i < pc; i++) fputc('.', stderr); + for (; i < 16; i++) fputc(' ', stderr); + fprintf(stderr, "] %3d%%", (int)((fo*100 + 50)/fsz)); + fprintf(stderr, " ETA "); + prhuman_time(stderr, difftime(now, start)*(fsz - fo)/fo); + } + } + } GH_DONE(h, buf); GH_DESTROY(h); + if (f & f_progress) fprintf(stderr, "\r%78s\r", ""); e = ferror(fp); if (file) fclose(fp); @@ -691,10 +789,11 @@ int main(int argc, char *argv[]) { "check", 0, 0, 'c' }, { "binary", 0, 0, 'b' }, { "verbose", 0, 0, 'v' }, + { "progress", 0, 0, 'p' }, { 0, 0, 0, 0 } }; - int i = mdwopt(argc, argv, "hVu a:E:l f0 ecbv", opts, 0, 0, 0); + int i = mdwopt(argc, argv, "hVu a:E:l f0 ecbvp", opts, 0, 0, 0); if (i < 0) break; @@ -738,6 +837,9 @@ int main(int argc, char *argv[]) case 'v': f |= f_verbose; break; + case 'p': + f |= f_progress; + break; default: f |= f_bogus; break; -- 2.11.0