kit

kit
git clone https://git.ryansepassi.com/git/kit.git
Log | Files | Refs | README

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 }