unfwd: Just ship it as a script.
[misc] / gorp.c
diff --git a/gorp.c b/gorp.c
index 933460c..d5ce481 100644 (file)
--- a/gorp.c
+++ b/gorp.c
+#include <ctype.h>
+#include <errno.h>
+#include <limits.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
 
 #include <mLib/alloc.h>
 #include <mLib/base64.h>
+#include <mLib/base32.h>
 #include <mLib/dstr.h>
+#include <mLib/hex.h>
+#include <mLib/mdwopt.h>
 #include <mLib/quis.h>
 #include <mLib/report.h>
 
 #include <catacomb/rand.h>
 #include <catacomb/noise.h>
 
-int main(int argc, char *argv[])
+struct format {
+  const char *name;
+  void (*out)(size_t, unsigned);
+};
+
+#define CHUNK 1024
+
+static void do_format(size_t n, unsigned line, void *ctx,
+                     void (*encode)(void *, char *, size_t, dstr *),
+                     void (*fix)(char *, size_t))
 {
+  char buf[CHUNK];
   dstr d = DSTR_INIT;
+
+  while (n) {
+    size_t nn = CHUNK;
+    if (nn > n) nn = n;
+    rand_get(RAND_GLOBAL, buf, nn);
+    encode(ctx, buf, nn, &d);
+    if (fix) fix(d.buf, d.len);
+    DWRITE(&d, stdout);
+    DRESET(&d);
+    n -= nn;
+  }
+  encode(ctx, 0, 0, &d);
+  if (!line) {
+    while (d.len && d.buf[d.len - 1] == '=')
+      d.len--;
+  }
+  if (fix) fix(d.buf, d.len);
+  DPUTC(&d, '\n');
+  DWRITE(&d, stdout);
+}
+
+static void do_base64(void *ctx, char *p, size_t sz, dstr *d)
+  { base64_encode(ctx, p, sz, d); }
+static void do_format_base64(size_t n, unsigned line,
+                            void (*fix)(char *, size_t))
+{
   base64_ctx b;
-  char *p;
+
+  base64_init(&b);
+  if (line) { b.indent = "\n"; b.maxline = line; }
+  else { b.indent = ""; b.maxline = 0; }
+  do_format(n, line, &b, do_base64, fix);
+}
+static void format_base64(size_t n, unsigned line)
+  { do_format_base64(n, line, 0); }
+
+static void do_base32(void *ctx, char *p, size_t sz, dstr *d)
+  { base32_encode(ctx, p, sz, d); }
+static void format_base32(size_t n, unsigned line)
+{
+  base32_ctx b;
+
+  base32_init(&b);
+  if (line) { b.indent = "\n"; b.maxline = line; }
+  else { b.indent = ""; b.maxline = 0; }
+  do_format(n, line, &b, do_base32, 0);
+}
+
+static void fix_file64(char *p, size_t n)
+{
+  while (n) {
+    if (*p == '/') *p = '%';
+    p++; n--;
+  }
+}
+static void format_file64(size_t n, unsigned line)
+  { do_format_base64(n, line, fix_file64); }
+
+static void fix_safe64(char *p, size_t n)
+{
+  while (n) {
+    if (*p == '+') *p = '-';
+    else if (*p == '/') *p = '_';
+    p++; n--;
+  }
+}
+static void format_safe64(size_t n, unsigned line)
+  { do_format_base64(n, line, fix_safe64); }
+
+static void do_hex(void *ctx, char *p, size_t sz, dstr *d)
+  { hex_encode(ctx, p, sz, d); }
+static void format_hex(size_t n, unsigned line)
+{
+  hex_ctx b;
+
+  hex_init(&b);
+  if (line) { b.indent = "\n"; b.maxline = line; }
+  else { b.indent = ""; b.maxline = 0; }
+  do_format(n, line, &b, do_hex, 0);
+}
+
+static void format_raw(size_t n, unsigned line)
+{
+  unsigned char buf[CHUNK];
+
+  while (n) {
+    size_t nn = CHUNK;
+    if (nn > n) nn = n;
+    rand_get(RAND_GLOBAL, buf, nn);
+    fwrite(buf, 1, nn, stdout);
+    n -= nn;
+  }
+}
+
+static const struct format fmt[] = {
+  { "base64",  format_base64 },
+  { "file64",  format_file64 },
+  { "safe64",  format_safe64 },
+  { "base32",  format_base32 },
+  { "hex",     format_hex },
+  { "raw",     format_raw },
+  { 0,         0 }
+};
+
+static int uarg(const char *p, const char *what)
+{
+  unsigned long x;
+  char *q;
+  errno = 0;
+  x = strtoul(p, &q, 0);
+  if (*q || errno || x > INT_MAX) die(EXIT_FAILURE, "bad %s", what);
+  return (x);
+}
+
+static void version(FILE *fp)
+{
+  pquis(stderr, "$, version " VERSION "\n");
+}
+
+static void usage(FILE *fp)
+{
+  pquis(stderr, "Usage: $ [-y] [-l LEN] [-f FORMAT] [BITS]\n");
+}
+
+static void help(FILE *fp)
+{
+  version(fp);
+  putc('\n', fp);
+  usage(fp);
+  fputs("\n\
+Generates a random string, and prints it to standard output.\n\
+\n\
+Options:\n\
+\n\
+-h, --help             Print this help message.\n\
+-v, --version          Print program version number.\n\
+-u, --usage            Print short usage summary.\n\
+\n\
+-y, --bytes            Output length is bytes, not bits.\n\
+-l, --line=LENGTH      For textual output, limit line length to LENGTH.\n\
+-f, --format=FORMAT    Select output format:\n\
+                         base64, file64, safe64, base32, hex, raw.\n\
+", stdout);
+}
+
+int main(int argc, char *argv[])
+{
   size_t n;
+  unsigned f = 0;
+  unsigned len = 0;
+  unsigned mul = 8;
+  const struct format *ff = &fmt[0];
+
+#define f_bogus 1u
 
   ego(argv[0]);
-  if (argc > 2) {
-    pquis(stderr, "Usage: $ [BITS]\n");
+  for (;;) {
+    static const struct option opt[] = {
+      { "help",                0,              0,      'h' },
+      { "version",     0,              0,      'v' },
+      { "usage",       0,              0,      'u' },
+
+      { "format",      OPTF_ARGREQ,    0,      'f' },
+      { "line",                OPTF_ARGREQ,    0,      'l' },
+      { "bytes",       0,              0,      'y' },
+      { 0,             0,              0,      0 }
+    };
+    int i;
+
+    i = mdwopt(argc, argv, "hvuf:l:y", opt, 0, 0, 0);
+    if (i < 0)
+      break;
+    switch (i) {
+      case 'h':
+       help(stdout);
+       exit(0);
+      case 'v':
+       version(stdout);
+       exit(0);
+      case 'u':
+       usage(stdout);
+       exit(0);
+      case 'y':
+       mul = 1;
+       break;
+      case 'l':
+       len = uarg(optarg, "line length");
+       break;
+      case 'f':
+       ff = 0;
+       n = strlen(optarg);
+       for (i = 0; fmt[i].name; i++) {
+         if (strncmp(fmt[i].name, optarg, n) != 0)
+           continue;
+         if (!fmt[i].name[n]) {
+           ff = &fmt[i];
+           break;
+         }
+         if (ff)
+           die(EXIT_FAILURE, "ambiguous format name `%s'", optarg);
+         ff = &fmt[i];
+       }
+       if (!ff)
+         die(EXIT_FAILURE, "unknown format name `%s'", optarg);
+       break;
+      default:
+       f |= f_bogus;
+       break;
+    }
+  }
+
+  argc -= optind;
+  argv += optind;
+  if (f & f_bogus && argc > 1) {
+    usage(stderr);
     exit(EXIT_FAILURE);
   }
-  n = argc == 2 ? atoi(argv[1]) : 128;
-  if (!n || n % 8) die(EXIT_FAILURE, "bad bit count");
-  n >>= 3;
-  p = xmalloc(n);
+  n = argc == 1 ? uarg(argv[0], "bit count") : 128;
+  if (!n || n % mul) die(EXIT_FAILURE, "bad bit count");
+  n /= mul;
   rand_noisesrc(RAND_GLOBAL, &noise_source);
   rand_seed(RAND_GLOBAL, 160);
-  rand_get(RAND_GLOBAL, p, n);
-  base64_init(&b);
-  b.maxline = 0;
-  b.indent = "";
-  base64_encode(&b, p, n, &d);
-  base64_encode(&b, 0, 0, &d);
-  printf("%s\n", d.buf);
+  ff->out(n, len);
+  if (ferror(stdout))
+    die(EXIT_FAILURE, "output error: %s", strerror(errno));
   return (0);
 }