master xplshn/aruu / cmd / extra / b3sum.c
  1/* see LICENSE file for copyright and license details */
  2#include "arg.h"
  3#include "util.h"
  4#include "../../shared/libblake3/blake3.h"
  5
  6#include <stdio.h>
  7#include <stdlib.h>
  8#include <string.h>
  9
 10static unsigned char *out;
 11static size_t         outlen = BLAKE3_OUT_LEN;
 12
 13static void
 14usage(void)
 15{
 16  fprintf(stderr, "usage: %s [-bct] [-l length] [file ...]\n", argv0);
 17  exit(1);
 18}
 19
 20static int
 21sumfile(const char *name, FILE *file, unsigned char *out_buf, size_t out_len)
 22{
 23  char                buf[16384];
 24  struct Blake3Hasher ctx;
 25  size_t              len;
 26
 27  blake3_hasher_init(&ctx);
 28  do {
 29    len = fread(buf, 1, sizeof(buf), file);
 30    if (len > 0)
 31      blake3_hasher_update(&ctx, buf, len);
 32  } while (len == sizeof(buf));
 33
 34  if (ferror(file)) {
 35    fprintf(stderr, "%s: read %s: ", argv0, name);
 36    perror(NULL);
 37    return 1;
 38  }
 39  blake3_hasher_finalize(&ctx, out_buf, out_len);
 40  return 0;
 41}
 42
 43static int
 44sum(const char *name, FILE *file)
 45{
 46  size_t i;
 47
 48  if (sumfile(name, file, out, outlen) != 0)
 49    return 1;
 50  for (i = 0; i < outlen; i++)
 51    printf("%02x", out[i]);
 52  printf("  %s\n", name);
 53  return 0;
 54}
 55
 56static int
 57checkfile(const char *name, const char *mode, const char *str, unsigned char *out_buf, size_t len)
 58{
 59  FILE  *file;
 60  int    c1, c2;
 61  size_t i;
 62
 63  file = fopen(name, mode);
 64  if (!file) {
 65    fprintf(stderr, "%s: open %s: ", argv0, name);
 66    perror(NULL);
 67    return 1;
 68  }
 69  sumfile(name, file, out_buf, len);
 70  fclose(file);
 71
 72  for (i = 0; i < len; i++) {
 73    c1 = hexval(str[i * 2]);
 74    c2 = hexval(str[i * 2 + 1]);
 75    if (c1 == -1 || c2 == -1) {
 76      fprintf(stderr, "%s: skipping invalid checksum line\n", argv0);
 77      return 1;
 78    }
 79    if (out_buf[i] != (c1 << 4 | c2)) {
 80      printf("%s: FAILED\n", name);
 81      return 1;
 82    }
 83  }
 84  printf("%s: OK\n", name);
 85  return 0;
 86}
 87
 88static int
 89check(const char *name, FILE *file)
 90{
 91  const char *mode;
 92  char        buf[8192], *pos, *end;
 93  size_t      len;
 94  int         ret = 0, skip = 0;
 95
 96  buf[sizeof(buf) - 2] = 0;
 97  while (fgets(buf, sizeof(buf), file)) {
 98    if (buf[sizeof(buf) - 2]) {
 99      fprintf(stderr, "%s: skipping line that is too long\n", argv0);
100      buf[sizeof(buf) - 2] = 0;
101      skip                 = 1;
102      ret                  = 1;
103      continue;
104    }
105    if (skip) {
106      skip = 0;
107      continue;
108    }
109    pos = strchr(buf, ' ');
110    if (!pos || pos == buf || (pos[1] != ' ' && pos[1] != '*') || (pos - buf) & 1) {
111      fprintf(stderr, "%s: skipping invalid checksum line\n", argv0);
112      ret = 1;
113      continue;
114    }
115    mode = pos[1] == ' ' ? "r" : "rb";
116    len  = (pos - buf) / 2;
117    if (len > outlen) {
118      outlen = len;
119      free(out);
120      out = malloc(len);
121      if (!out) {
122        perror(argv0);
123        return 1;
124      }
125    }
126    *pos = '\0';
127    pos += 2;
128    end = strchr(pos, '\n');
129    if (end)
130      *end = '\0';
131    ret |= checkfile(pos, mode, buf, out, len);
132  }
133  if (ferror(file)) {
134    fprintf(stderr, "%s: read %s: ", argv0, name);
135    perror(NULL);
136    ret = 1;
137  }
138  return ret;
139}
140
141// ?man b3sum: compute blake3 checksums
142// ?man arguments: file ...
143// ?man compute and check blake3 message digests
144int
145main(int argc, char *argv[])
146{
147  int (*func)(const char *, FILE *) = sum;
148  FILE       *file;
149  char       *end;
150  const char *name, *mode = NULL;
151  int         ret = 0;
152
153  ARGBEGIN
154  {
155    // ?man -b: read in binary mode
156    case 'b':
157      mode = "rb";
158      break;
159    // ?man -c: check blake3 sums from file
160    case 'c':
161      func = check;
162      break;
163    // ?man -l:str: -l length: output digest length in bytes
164    case 'l':
165      outlen = strtoul(EARGF(usage()), &end, 10);
166      if (*end)
167        usage();
168      break;
169    // ?man -t: read in text mode
170    case 't':
171      mode = "r";
172      break;
173    default:
174      usage();
175  }
176  ARGEND
177
178  out = malloc(outlen);
179  if (!out) {
180    perror(NULL);
181    return 1;
182  }
183
184  if (argc == 0) {
185    if (!mode || strcmp(mode, "r") == 0 || freopen(NULL, mode, stdin)) {
186      ret |= func("<stdin>", stdin);
187    } else {
188      fprintf(stderr, "%s: reopen stdin: ", argv0);
189      perror(NULL);
190      ret = 1;
191    }
192  } else {
193    if (!mode)
194      mode = "r";
195    for (; argc > 0; argc--, argv++) {
196      name = *argv;
197      file = fopen(name, mode);
198      if (file) {
199        ret |= func(name, file);
200        fclose(file);
201      } else {
202        fprintf(stderr, "%s: open %s: ", argv0, name);
203        perror(NULL);
204        ret = 1;
205      }
206    }
207  }
208
209  free(out);
210  if (fshut(stdin, "<stdin>") | fshut(stdout, "<stdout>"))
211    ret = 1;
212
213  return ret;
214}