#include #include #include #include #include #include #include #include #include "filehash.h" #include "macros.h" #define optstr "0h:L:r:" typedef struct { const char *hash_program; const char *repository; unsigned max_per_invocation; char **files; unsigned n_files; char input_sep; } options_t; static void usage(const char *prgname) { errx(EX_USAGE, "usage %s ...", prgname); } static int str_to_uint(const char *str, unsigned *out) { unsigned long result; char *end; errno = 0; result = strtoul(str, &end, 10); if (errno) goto fail; if (*end != '\0') { errno = EINVAL; goto fail; } if (result > UINT_MAX) { errno = ERANGE; goto fail; } *out = result; return 0; fail: warn("str_to_uint \"%s\"", str); return -1; } static options_t read_opts(int argc, char **argv) { int o; const char *prgname; options_t options = { .hash_program = "md5sum", .repository = ".", .max_per_invocation = 4096, .input_sep = '\n', }; prgname = argc < 1 ? "keep" : argv[0]; while (o = getopt(argc, argv, optstr), o != -1) { switch (o) { case '0': options.input_sep = '\0'; break; case 'h': options.hash_program = optarg; break; case 'L': if (str_to_uint(optarg, &options.max_per_invocation)) goto fail; break; case 'r': options.repository = optarg; break; case '?': default: usage(prgname); } } options.files = argv + optind; options.n_files = argc - optind; return options; fail: err(EX_CONFIG, "bad options"); } static const char * iter_files(const options_t *opts, void **aux) { struct ctx { enum { from_files_list, from_stdin, } from; union { struct { int next; } files_list; struct { char *line; size_t len; } stdin; }; }; struct ctx *ctx = *aux; ssize_t nread; if (!ctx) { /* initialization */ *aux = ctx = malloc(sizeof(struct ctx)); if (!ctx) err(EX_OSERR, "malloc"); *ctx = (struct ctx){ opts->n_files ? from_files_list : from_stdin }; } switch (ctx->from) { case from_stdin: errno = 0; nread = getdelim(&ctx->stdin.line, &ctx->stdin.len, opts->input_sep, stdin); if (nread == -1) { if (!errno) { free(ctx->stdin.line); goto done; } err(EX_OSERR, "getdelim(..., stdin)"); } if (opts->input_sep && ctx->stdin.line[nread - 1] == opts->input_sep) ctx->stdin.line[nread - 1] = '\0'; return ctx->stdin.line; case from_files_list: if (ctx->files_list.next >= opts->n_files) goto done; return opts->files[ctx->files_list.next++]; } bug_abort; done: free(*aux); *aux = NULL; return NULL; } static int run(const options_t *opts, filehash_t *fh) { int ret = 0; void *aux = NULL; const char *file_name; while (file_name = iter_files(opts, &aux), file_name) { switch (filehash_send(fh, file_name)) { case fhs_accepts: continue; case fhs_failure: warnx("handle failure!"); ret = 1; goto exit; case fhs_full: warnx("handle full!"); filehash_free(fh); fh = filehash_new(opts->hash_program, opts->max_per_invocation); break; case fhs_rejected: bug_abort; } } exit: filehash_free(fh); return ret; } int main(int argc, char **argv) { options_t opts; filehash_t *fh; opts = read_opts(argc, argv); fh = filehash_new(opts.hash_program, opts.max_per_invocation); if (!fh) err(1, "filehash_new"); return run(&opts, fh); }