X-Git-Url: https://git.distorted.org.uk/u/mdw/catacomb/blobdiff_plain/18b3351a3791f19a63c0af05640719db8eb5d184..95ccefe3ce59a0773f915aca6948d5b6f1f7c882:/cc-hash.c diff --git a/cc-hash.c b/cc-hash.c index 81a06a2..64fea0e 100644 --- a/cc-hash.c +++ b/cc-hash.c @@ -32,11 +32,18 @@ #include "config.h" #include +#include #include #include #include +#include +#include + +#include #include +#include +#include #include #include @@ -165,10 +172,106 @@ const encodeops *getencoding(const char *ename) /*----- File hashing ------------------------------------------------------*/ +enum { + FHETY_DIR, + FHETY_FILE +}; + +typedef struct fhent { + struct fhent *next; + unsigned ty; + struct fhent *sub; + char name[1]; +} fhdir; + +/* --- @gethash@ --- * + * + * Arguments: @const char *name@ = pointer to name string + * + * Returns: Pointer to appropriate hash class. + * + * Use: Chooses a hash function by name. + */ + +const gchash *gethash(const char *name) +{ + const gchash *const *g, *gg = 0; + size_t sz = strlen(name); + for (g = ghashtab; *g; g++) { + if (strncmp(name, (*g)->name, sz) == 0) { + if ((*g)->name[sz] == 0) { + gg = *g; + break; + } else if (gg) + return (0); + else + gg = *g; + } + } + return (gg); +} + +/* --- @describefile@ --- * + * + * Arguments: @const struct stat *st@ = pointer to file state + * + * Returns: A snappy one-word description of the file. + */ + +const char *describefile(const struct stat *st) +{ + switch (st->st_mode & S_IFMT) { + case S_IFBLK: return ("block-special"); + case S_IFCHR: return ("char-special"); + case S_IFIFO: return ("fifo"); + case S_IFREG: return ("file"); + case S_IFLNK: return ("symlink"); + case S_IFDIR: return ("directory"); + case S_IFSOCK: return ("socket"); + default: return ("unknown"); + } +} + +/* --- @fhash_init@ ---* + * + * Arguments: @fhashstate *fh@ = pointer to fhash state to initialize + * @const gchash *gch@ = hash class to set + * @unsigned f@ initial flags to set + * + * Returns: --- + * + * Use: Initializes an @fhashstate@ structure. + */ + +void fhash_init(fhashstate *fh, const gchash *gch, unsigned f) + { fh->f = f; fh->gch = gch; fh->ents = 0; } + +/* --- @fhash_free@ --- * + * + * Arguments: @fhashstate *fh@ = pointer to fhash state to free + * + * Returns: --- + * + * Use: Frees an fhash state. + */ + +static void freefhents(struct fhent *fhe) +{ + struct fhent *ffhe; + + for (; fhe; fhe = ffhe) { + ffhe = fhe->next; + freefhents(fhe->sub); + xfree(fhe); + } +} + +void fhash_free(fhashstate *fh) + { freefhents(fh->ents); } + /* --- @fhash@ --- * * - * Arguments: @const gchash *gch@ = pointer to hash function to use - * @unsigned f@ = flags to set + * Arguments: @fhashstate *fh@ = pointer to fhash state * @const char *file@ = file name to be hashed (null for stdin) * @void *buf@ = pointer to hash output buffer * @@ -177,37 +280,233 @@ const encodeops *getencoding(const char *ename) * Use: Hashes a file. */ -int fhash(const gchash *gch, unsigned f, const char *file, void *buf) +int fhash(fhashstate *fh, const char *file, void *buf) { FILE *fp; char fbuf[1024 * 128]; size_t sz; ghash *h; int rc = 0; + struct fhent *fhe, **ffhe; + const char *p, *q; + size_t n; fprogress ff; if (!file || strcmp(file, "-") == 0) fp = stdin; - else if ((fp = fopen(file, f & FHF_BINARY ? "rb" : "r")) == 0) + else if ((fp = fopen(file, fh->f & FHF_BINARY ? "rb" : "r")) == 0) return (-1); - if (f & FHF_PROGRESS) { - if (fprogress_init(&ff, file, fp)) return (-1); + if (fh->f & FHF_PROGRESS) { + if (fprogress_init(&ff, file ? file : "", fp)) return (-1); } - h = GH_INIT(gch); + if (fh->f & FHF_JUNK) { + p = file; + if (strncmp(p, "./", 2) == 0) p += 2; + q = p; + ffhe = &fh->ents; + for (;;) { + if (*q == '/' || *q == 0) { + n = q - p; + for (; *ffhe; ffhe = &(*ffhe)->next) { + fhe = *ffhe; + if (strncmp(p, fhe->name, n) == 0 && fhe->name[n] == 0) + goto found; + } + fhe = xmalloc(offsetof(struct fhent, name) + n + 1); + fhe->next = 0; + fhe->ty = *q == '/' ? FHETY_DIR : FHETY_FILE; + fhe->sub = 0; + *ffhe = fhe; + memcpy(fhe->name, p, n); fhe->name[n] = 0; + found: + if (!*q) break; + while (*++q == '/'); + p = q; + ffhe = &fhe->sub; + } else + q++; + } + } + + h = GH_INIT(fh->gch); while ((sz = fread(fbuf, 1, sizeof(fbuf), fp)) > 0) { GH_HASH(h, fbuf, sz); - if (f & FHF_PROGRESS) fprogress_update(&ff, sz); + if (fh->f & FHF_PROGRESS) fprogress_update(&ff, sz); } if (ferror(fp)) rc = -1; if (fp != stdin) fclose(fp); - if (f & FHF_PROGRESS) fprogress_done(&ff); + if (fh->f & FHF_PROGRESS) fprogress_done(&ff); GH_DONE(h, buf); GH_DESTROY(h); return (rc); } +/* --- @fhash_junk@ --- * + * + * Arguments: @fhashstate *fh@ = pointer to fhash state + * @void (*func)(const char *, const struct stat *, void *)@ + * @void *p@ = pointer to pass to function + * + * Returns: Positive if any junk was found, negative on error, zero if + * everything was fine. + * + * Use: Reports junk files in any directories covered by the hash + * state. + */ + +struct fhjunk { + int (*func)(const char *, const struct stat *, void *); + void *p; + dstr *d; +}; + +static int fhjunk(struct fhjunk *fhj, struct fhent *ents) +{ + DIR *dp; + int rc = 0, rrc; + struct stat st; + struct dirent *d; + const char *dname; + size_t n = fhj->d->len; + struct fhent *fhe; + + dname = n ? fhj->d->buf : "."; + if ((dp = opendir(dname)) == 0) { + moan("failed to open directory `%s': %s", dname, strerror(errno)); + rc = -1; + goto subs; + } + if (n) { + dstr_putc(fhj->d, '/'); + n++; + } + while (errno = 0, (d = readdir(dp)) != 0) { + if (strcmp(d->d_name, ".") == 0 || strcmp(d->d_name, "..") == 0) + continue; + for (fhe = ents; fhe; fhe = fhe->next) { + if (strcmp(d->d_name, fhe->name) == 0) goto found; + } + fhj->d->len = n; + dstr_puts(fhj->d, d->d_name); + if (!lstat(fhj->d->buf, &st)) { + if (!rc) rc = 1; + rrc = fhj->func(fhj->d->buf, &st, fhj->p); + } else { + rc = -1; + rrc = fhj->func(fhj->d->buf, 0, fhj->p); + } + if (rrc < 0) rc = -1; + found:; + } + closedir(dp); + if (errno) { + moan("failed to read directory `%s': %s", dname, strerror(errno)); + rc = -1; + } + +subs: + for (fhe = ents; fhe; fhe = fhe->next) { + if (fhe->ty == FHETY_DIR) { + fhj->d->len = n; + dstr_puts(fhj->d, fhe->name); + rrc = fhjunk(fhj, fhe->sub); + if (rrc < 0) rc = -1; + else if (!rc) rc = rrc; + } + } + + return (rc); +} + +int fhash_junk(fhashstate *fh, + int (*func)(const char *, const struct stat *, void *), + void *p) +{ + dstr d = DSTR_INIT; + struct fhjunk fhj; + int rc; + + fhj.func = func; + fhj.p = p; + fhj.d = &d; + rc = fhjunk(&fhj, fh->ents); + dstr_destroy(&d); + return (rc); +} + +/* --- @hfparse@ --- * + * + * Arguments: @hfpctx *hfp@ = pointer to the context structure + * + * Returns: A code indicating what happened. + * + * Use: Parses a line from the input file. + */ + +int hfparse(hfpctx *hfp) +{ + char *p, *q; + const gchash *gch; + const encodeops *ee; + dstr *d = hfp->dline; + size_t hsz; + + /* --- Fetch the input line and get ready to parse --- */ + + DRESET(d); + if (dstr_putline(d, hfp->fp) == EOF) return (HF_EOF); + p = d->buf; + + /* --- Parse magic comments --- */ + + if (*p == '#') { + p++; + if ((q = str_getword(&p)) == 0) return (HF_BAD); + if (strcmp(q, "hash") == 0) { + if ((q = str_getword(&p)) == 0) return (HF_BAD); + if ((gch = gethash(q)) == 0) return (HF_BAD); + hfp->gch = gch; + return (HF_HASH); + } else if (strcmp(q, "encoding") == 0) { + if ((q = str_getword(&p)) == 0) return (HF_BAD); + if ((ee = getencoding(q)) == 0) return (HF_BAD); + hfp->ee = ee; + return (HF_ENC); + } else if (strcmp(q, "escape") == 0) { + hfp->f |= HFF_ESCAPE; + return (HF_ESC); + } + return (HF_BAD); + } + + /* --- Otherwise it's a file line --- */ + + q = p; + while (*p && *p != ' ') p++; + if (!*p) return (HF_BAD); + *p++ = 0; + hsz = hfp->gch->hashsz; + if (hfp->ee->get(q, hfp->hbuf, hsz, 0) < hsz) return (HF_BAD); + switch (*p) { + case '*': hfp->f |= FHF_BINARY; break; + case ' ': hfp->f &= ~FHF_BINARY; break; + default: return (HF_BAD); + } + p++; + + DRESET(hfp->dfile); + if (hfp->f & HFF_ESCAPE) + getstring(&p, hfp->dfile, GSF_STRING); + else { + dstr_putm(hfp->dfile, p, d->len - (p - d->buf)); + dstr_putz(hfp->dfile); + } + + return (HF_FILE); +} + /*----- String I/O --------------------------------------------------------*/ /* --- @getstring@ --- *