hash.c (6939B)
1 #include <kit/core.h> 2 #include <kit/hash.h> 3 #include <stddef.h> 4 #include <stdint.h> 5 #include <string.h> 6 7 #include "driver.h" 8 #include "env.h" 9 10 /* `kit hash` — print the SHA-256, BLAKE2b-256, or CRC-32 digest of each 11 * input. Output is coreutils-style ("<hex> <name>"), so it diffs cleanly 12 * against sha256sum / b2sum / cksum -a output. With no FILE, or with `-`, 13 * reads stdin. Drives the streaming kit_hasher_* API (the one-shot 14 * kit_hash stays for library callers). 15 * 16 * The same core also backs the standard-named aliases, each of which pins the 17 * algorithm so the tool is a drop-in for the matching command: 18 * sha256sum -> SHA-256 b2sum -> BLAKE2b-256 crc32 -> CRC-32 19 * (Note: GNU b2sum defaults to BLAKE2b-512; kit's BLAKE2b is 256-bit, so the 20 * digests differ in width.) The aliases reject -a, since their algorithm is 21 * fixed; the generic `hash` tool keeps -a for selecting any of the three. */ 22 23 static const char HASH_HEX[] = "0123456789abcdef"; 24 25 /* Invocation personality: which algorithm, and whether the name pins it. */ 26 typedef struct HashPersona { 27 const char* name; /* tool name for diagnostics: hash/sha256sum/... */ 28 KitHashAlgo algo; /* default (and, when locked, the only) algorithm */ 29 int locked; /* alias pins the algorithm: -a is rejected */ 30 } HashPersona; 31 32 static const HashPersona HASH_GENERIC = {"hash", KIT_HASH_SHA256, 0}; 33 static const HashPersona HASH_SHA256SUM = {"sha256sum", KIT_HASH_SHA256, 1}; 34 static const HashPersona HASH_B2SUM = {"b2sum", KIT_HASH_BLAKE2B, 1}; 35 static const HashPersona HASH_CRC32 = {"crc32", KIT_HASH_CRC32, 1}; 36 37 typedef struct HashOpts { 38 KitHashAlgo algo; 39 } HashOpts; 40 41 static const char* hash_algo_name(KitHashAlgo algo) { 42 switch (algo) { 43 case KIT_HASH_SHA256: 44 return "sha256"; 45 case KIT_HASH_BLAKE2B: 46 return "blake2b"; 47 case KIT_HASH_CRC32: 48 return "crc32"; 49 } 50 return "?"; 51 } 52 53 static int hash_parse_algo(const char* s, KitHashAlgo* out) { 54 if (driver_streq(s, "sha256")) { 55 *out = KIT_HASH_SHA256; 56 return 0; 57 } 58 if (driver_streq(s, "blake2b")) { 59 *out = KIT_HASH_BLAKE2B; 60 return 0; 61 } 62 if (driver_streq(s, "crc32")) { 63 *out = KIT_HASH_CRC32; 64 return 0; 65 } 66 return 1; 67 } 68 69 void driver_help_hash(void) { 70 driver_printf( 71 "%.*s", 72 KIT_SLICE_ARG(KIT_SLICE_LIT( 73 "kit hash — hash files with SHA-256, BLAKE2b, or CRC-32\n" 74 "\n" 75 "USAGE\n" 76 " kit hash [-a ALGO] [FILE...]\n" 77 "\n" 78 "DESCRIPTION\n" 79 " Prints one line per input: the lowercase-hex digest, two spaces,\n" 80 " then the file name (`-` for stdin). With no FILE, reads stdin.\n" 81 "\n" 82 "OPTIONS\n" 83 " -a ALGO sha256 (default) | blake2b | crc32\n" 84 " -h, --help show this help\n" 85 "\n" 86 "ALIASES\n" 87 " Invoked as sha256sum, b2sum, or crc32 the algorithm is fixed to\n" 88 " SHA-256, BLAKE2b-256, or CRC-32 respectively and -a is rejected.\n" 89 " (GNU b2sum defaults to BLAKE2b-512; this BLAKE2b is 256-bit.)\n" 90 "\n" 91 "EXIT CODES\n" 92 " 0 success 1 I/O error 2 bad usage\n"))); 93 } 94 95 /* Hash data[0..len) with opts->algo and print "<hex> <name>". Returns 0 on 96 * success, 1 on failure (error already reported under `tool`). */ 97 static int hash_one(const KitContext* ctx, const HashOpts* opts, 98 const char* tool, const uint8_t* data, size_t len, 99 const char* name) { 100 KitHasher* h = NULL; 101 uint8_t digest[KIT_HASH_MAX_LEN]; 102 char hex[KIT_HASH_MAX_LEN * 2 + 1]; 103 size_t dlen = 0, i; 104 105 if (kit_hasher_new(ctx, opts->algo, &h) != KIT_OK) { 106 driver_errf(tool, "failed to start hasher"); 107 return 1; 108 } 109 kit_hasher_update(h, data, len); 110 kit_hasher_final(h, digest, &dlen); 111 kit_hasher_free(h); 112 113 for (i = 0; i < dlen; ++i) { 114 hex[i * 2] = HASH_HEX[digest[i] >> 4]; 115 hex[i * 2 + 1] = HASH_HEX[digest[i] & 0x0f]; 116 } 117 hex[dlen * 2] = '\0'; 118 driver_printf("%s %s\n", hex, name); 119 return 0; 120 } 121 122 static int hash_main(int argc, char** argv, const HashPersona* persona) { 123 DriverEnv env; 124 KitContext ctx; 125 HashOpts opts; 126 int i, rc = 1, any_input = 0; 127 128 if (driver_argv_wants_help(argc, argv, 1)) { 129 driver_help_hash(); 130 return 0; 131 } 132 133 memset(&opts, 0, sizeof opts); 134 opts.algo = persona->algo; 135 driver_env_init(&env); 136 ctx = driver_env_to_context(&env); 137 138 /* First pass: options. */ 139 for (i = 1; i < argc; ++i) { 140 const char* a = argv[i]; 141 if (driver_streq(a, "-a")) { 142 if (persona->locked) { 143 driver_errf(persona->name, "-a is not accepted; %s always uses %s", 144 persona->name, hash_algo_name(persona->algo)); 145 rc = 2; 146 goto done; 147 } 148 if (i + 1 >= argc || hash_parse_algo(argv[++i], &opts.algo) != 0) { 149 driver_errf(persona->name, "-a requires sha256, blake2b, or crc32"); 150 rc = 2; 151 goto done; 152 } 153 continue; 154 } 155 if (driver_streq(a, "-")) { 156 any_input = 1; 157 continue; 158 } 159 if (a[0] == '-' && a[1] != '\0') { 160 driver_errf(persona->name, "unknown option: %s", a); 161 rc = 2; 162 goto done; 163 } 164 any_input = 1; 165 } 166 167 /* No file operands: hash stdin. */ 168 if (!any_input) { 169 uint8_t* buf = NULL; 170 size_t n = 0; 171 if (!driver_read_stdin(&env, &buf, &n)) { 172 driver_errf(persona->name, "failed to read stdin"); 173 rc = 1; 174 goto done; 175 } 176 rc = hash_one(&ctx, &opts, persona->name, buf, n, "-"); 177 driver_free(&env, buf, n); 178 goto done; 179 } 180 181 /* Second pass: inputs, in argv order. */ 182 rc = 0; 183 for (i = 1; i < argc; ++i) { 184 const char* a = argv[i]; 185 if (driver_streq(a, "-a")) { 186 ++i; /* skip its value (only reachable for the generic, unlocked tool) */ 187 continue; 188 } 189 if (driver_streq(a, "-")) { 190 uint8_t* buf = NULL; 191 size_t n = 0; 192 if (!driver_read_stdin(&env, &buf, &n)) { 193 driver_errf(persona->name, "failed to read stdin"); 194 rc = 1; 195 continue; 196 } 197 if (hash_one(&ctx, &opts, persona->name, buf, n, "-") != 0) rc = 1; 198 driver_free(&env, buf, n); 199 continue; 200 } 201 { 202 DriverLoad ld = {0}; 203 KitSlice input; 204 if (driver_load_bytes(&env.file_io, persona->name, a, &ld, &input) != 0) { 205 rc = 1; 206 continue; 207 } 208 if (hash_one(&ctx, &opts, persona->name, input.data, input.len, a) != 0) 209 rc = 1; 210 driver_release_bytes(&env.file_io, &ld); 211 } 212 } 213 214 done: 215 driver_env_fini(&env); 216 return rc; 217 } 218 219 int driver_hash(int argc, char** argv) { 220 return hash_main(argc, argv, &HASH_GENERIC); 221 } 222 223 int driver_sha256sum(int argc, char** argv) { 224 return hash_main(argc, argv, &HASH_SHA256SUM); 225 } 226 227 int driver_b2sum(int argc, char** argv) { 228 return hash_main(argc, argv, &HASH_B2SUM); 229 } 230 231 int driver_crc32(int argc, char** argv) { 232 return hash_main(argc, argv, &HASH_CRC32); 233 }