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}