#include <err.h>
#include <errno.h>
#include <limits.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <sysexits.h>
#include <unistd.h>
#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);
}