util  Check-in [0d4aa1c43a]

Overview
Comment:add soda
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA3-256: 0d4aa1c43a9a2a03d2241d2f04c6f793309e582c336cd8a8e199d0972ce3f49e
User & Date: lexi on 2022-10-27 20:04:19
Other Links: manifest | tags
Context
2022-10-31
21:27
add wgsync base check-in: 99704cfae0 user: lexi tags: trunk
2022-10-27
20:04
add soda check-in: 0d4aa1c43a user: lexi tags: trunk
2022-04-28
22:52
clean up rels mechanism, get parvan mostly working again check-in: bc37f02701 user: lexi tags: trunk
Changes

Modified clib/iaia.c from [1d0d9f385e] to [70661568bf].

    27     27   /* this is an expression that will be evaluated to
    28     28    * determine whether to compress ascii to a 7-bit
    29     29    * representation or to store it in 8-bit space.
    30     30    *  1 = 7-bit (compressed)
    31     31    *  0 = 8-bit (uncompressed) */
    32     32   #	define _IAIA_EXP_ASCFORM (0)
    33     33   #endif
           34  +#ifndef NULL
           35  +#	define NULL ((void*)0)
           36  +#endif
    34     37   
    35     38   enum /* constants */ {
    36     39   	base7bit = 128,
    37     40   
    38     41   	/* ascii address space */
    39     42   	numspace        = (0x39 - 0x30) + 1, /* 10 */
    40     43   	alphaspace      = (0x5a - 0x41) + 1, /* 26 */
................................................................................
    52     55   		iaia_e_overflow,
    53     56   	} iaia_error_type;
    54     57   	typedef unsigned long long iaia_word_type;
    55     58   #endif
    56     59   
    57     60   /* -- string to integer converters -- */
    58     61   
    59         -iaia_error_type _IAIA_FN_ASCTOI(const char* s, iaia_word_type* ret) {
           62  +iaia_error_type
           63  +_IAIA_FN_ASCTOI(const char* s, iaia_word_type* ret) {
    60     64   	iaia_word_type val = 0;
    61     65   
    62         -	for (;*s!=null;++s) {
           66  +	for (;*s!=0;++s) {
    63     67   		uint8_t v = *s;
    64     68   		if (v > base7bit) return iaia_e_domain;
    65     69   
    66     70   		if (_IAIA_EXP_ASCFORM)
    67     71   			val *= base7bit;
    68     72   			else val <<= 8;
    69     73   
    70     74   		val += v;
    71     75   	}
    72     76   
    73     77   	*ret = val;
    74         -	return ok;
           78  +	return iaia_e_ok;
    75     79   }
    76     80   
    77         -iaia_error_type _IAIA_FN_ATOI(iaia_word_type base, const char* s, iaia_word_type* ret) {
    78         -	/* s must be a null-terminated ASCII numeral string */
           81  +iaia_error_type
           82  +_IAIA_FN_ATOI(iaia_word_type base, const char* s, iaia_word_type* ret) {
           83  +	/* s must be a NUL-terminated ASCII numeral string */
    79     84   	if (base > maxbase) return iaia_e_base;
    80     85   
    81     86   	/* override the default base if it's a basèd literal */
    82     87   	if (s[0] == '@' || base == 0) return _IAIA_FN_ASCTOI(s + (s[0]=='@'),ret);
    83     88   	else if (s[0] == '0' && s[1] == 'x') base = 16, s += 2;
    84     89   	else if (s[0] == '0' && s[1] == 'd') base = 10, s += 2;
    85     90   	else if (s[0] == '0' && s[1] == 'b') base =  2, s += 2;
    86     91   	else if (s[0] == '0' && s[1] == 't') base =  3, s += 2;
    87     92   	else if (s[0] == '0')                base =  8, s += 1;
    88     93   
    89     94   	bool insens = (base <= imaxbase);
    90     95   	iaia_word_type val = 0;
    91     96   
    92         -	for (;*s!=null;++s) {
           97  +	for (;*s!=0;++s) {
    93     98   		uint8_t v = *s;
    94     99   		if(v >= 0x30 && v <= 0x39) v -= 0x30; else {
    95    100   			if(v >= 0x61 && v <= 0x7a) {
    96    101   				if (insens) v -= 0x20; else {
    97    102   					v = numspace + alphaspace + (v - 0x61);
    98    103   					goto checkval;
    99    104   				}
................................................................................
   112    117   }
   113    118   
   114    119   
   115    120   /* -- integer to string converters -- */
   116    121   
   117    122   /* needed for efficiency's sake, but really sucky -
   118    123    * this table needs to be kept in sync with the
   119         - * itoa algorithm by hand. unfortunately, given C's
          124  + * itoa algorithm manually. unfortunately, given C's
   120    125    * abject lack of metaprogramming, we have to do this
   121    126    * by hand. */
   122    127   const char iaia_ref_table[] = /* numerals[10] */ "0123456789"
   123    128   	/* bigalpha[26] */ "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
   124    129   	/* smallalpha[26] */ "abcdefghijklmnopqrstuvwxyz";
   125    130   _Static_assert (sizeof iaia_ref_table - 1 == maxbase, "tables out of sync");
   126    131   
   127         -iaia_error_type _IAIA_FN_ITOASC(iaia_word_type val, const char* buf_start, char* buf_end, char** newbuf) {
          132  +iaia_error_type
          133  +_IAIA_FN_ITOASC(iaia_word_type val, const char* buf_start, char* buf_end, char** newbuf) {
   128    134   	char* ptr = buf_end;
   129    135   
   130    136   	*ptr-- = 0;
   131    137   	while(val > 0) {
   132    138   		if (ptr < buf_start) return iaia_e_overflow;
   133    139   		iaia_word_type rem = val % 128;
   134    140   		if (_IAIA_EXP_ASCFORM)
   135    141   			val /= base7bit;
   136    142   			else val >>= 8;
   137    143   		*ptr-- = (char)rem;
   138    144   	}
   139    145   
   140         -	if (newbuf != null) *newbuf = ptr + 1;
   141         -	return ok;
          146  +	if (newbuf != NULL) *newbuf = ptr + 1;
          147  +	return iaia_e_ok;
   142    148   }
   143    149   
   144         -iaia_error_type _IAIA_FN_ITOA(iaia_word_type base, iaia_word_type val, const char* buf_start,
          150  +iaia_error_type
          151  +_IAIA_FN_ITOA(iaia_word_type base, iaia_word_type val, const char* buf_start,
   145    152   		char* buf_end, char** newbuf, bool lowercase) {
   146    153   
   147    154   	char* ptr = buf_end;
   148    155   
   149    156   	if (base > maxbase) return iaia_e_base;
   150    157   	if (base == 0) return _IAIA_FN_ITOASC(val, buf_start, buf_end, newbuf);
   151    158   
................................................................................
   158    165   		char out = iaia_ref_table[rem];
   159    166   		if (lowercase && base <= imaxbase)
   160    167   			if (out >= 'A' && out <= 'Z')
   161    168   				out += ('a' - 'A');
   162    169   		*ptr-- = out;
   163    170   	}
   164    171   
   165         -	if (newbuf != null) *newbuf = ptr + 1;
          172  +	if (newbuf != NULL) *newbuf = ptr + 1;
   166    173   	return iaia_e_ok;
   167    174   }
   168    175   

Modified makefile from [69ff088193] to [ca4a818574].

     3      3   # pass debug=yes for debugging symbols, and to disable
     4      4   # stripping and optimization of all binaries
     5      5   
     6      6   # to build project contained in their own directory, run 
     7      7   # $ make (dir).proj
     8      8   
     9      9   all: ctl conv xutil gen
           10  +clean:
           11  +	rm xpriv safekill mkpw kpw.bin rosshil ord nkvd.so bgrd
           12  +install:
           13  +	mkdir -p $(prefix)/bin $(prefix)/lib
           14  +	cp xpriv safekill mkpw rosshil ord bgrd ${out}/bin/
           15  +	cp kpw.bin ${out}/bin/kpw
           16  +	cp nkvd.so $(prefix)/lib
    10     17   
    11     18   xutil: xpriv safekill
    12     19     # X11 tools
    13         -gen: mkpw kpw rosshil
           20  +gen: mkpw kpw.bin rosshil
    14     21     # procedural generators
    15     22   conv: ord
    16     23     # converters
    17     24   ctl: nkvd.so bgrd
    18     25     # manipulating disobedient software
    19     26   
    20     27   nkvd.so: nkvd.c

Modified makerules from [a224223f8c] to [e7dd2b0bcc].

    14     14   cc-flags = $(if $(debug),-g,-O$(cc-opt))
    15     15   cc = $(CC) $(cc-flags)
    16     16   
    17     17   post = $(if $(debug),, && strip $@)
    18     18   cc-post = $(post)
    19     19   sc-post = $(post)
    20     20   mc-post = $(post)
           21  +
           22  +prefix = $(if ${out},${out},/usr)
    21     23   
    22     24   os = $(shell uname -o)
    23     25   
    24     26   %: %.c
    25     27   	$(cc) $< -o$@ $(cc-post)
    26     28   
    27     29   %: %.ml

Modified ord.c from [9504e2c85c] to [4549cc99f3].

     1      1   /* [ʞ] ord.c - integer converter
     2      2    *  ~ lexi hale <lexi@hale.su>
     3      3    *  © AGPLv3
     4      4    *  * ord has no dependencies except for libc.
     5      5    *  ? ord converts integers to ascii characters
     6      6    *    and back. written because the only fucking
     7      7    *    way to do this in shell is FUCKING PRINTF.
     8         - *  $ cc ord.c -o ord [-D_IO=(LIBC|POSIX)]
            8  + *  $ cc ord.c -o ord [-D_(POSIX|LIBC)_IO]
     9      9    *  	- the flag D_IO will instruct ord.c whether
    10     10    *  	  to use POSIX io primitives (write and read)
    11     11    *  	  instead of libc primitives (printf). if
    12     12    *  	  you're on a UNIX system, POSIX primitives
    13     13    *  	  will be used by default, but you can block
    14     14    *  	  them with LIBC or force them with POSIX.
    15     15    *  	  if you are on a POSIX- compliant system,
................................................................................
    16     16    *  	  you *should* use POSIX IO, for improved
    17     17    *  	  performance and safety.
    18     18   
    19     19    	TODO: take full advantage of write(2) by storing
    20     20   	      output in single string & making single
    21     21   		  write call */
    22     22   
    23         -#if (defined(__unix__) && _IO != LIBC) || (_IO == POSIX)
           23  +#if (defined(__unix__) && !defined(_POSIX_IO)) && !defined(_LIBC_IO)
    24     24   #	define _POSIX_IO
    25     25   #endif
    26     26   
    27     27   #ifdef _POSIX_IO
    28     28   #	include <unistd.h>
    29     29   #	define say(x) (write(2, (x), (sizeof (x))))
    30     30   #	define print(sz,x) (write(1, (x), (sz)))
    31     31   #	define forposix(x) x
    32     32   #	define forlibc(x)
    33     33   #else
    34     34   #	include <stdio.h>
    35     35   #	define say(x) (fprintf(stderr, (x)))
    36         -#	define print(x) (printf("%s",(x)))
           36  +#	define print(sz,x) (printf("%s",(x)))
    37     37   #	define forposix(x)
    38     38   #	define forlibc(x) x
    39     39   #endif
    40     40   #include <stddef.h>
    41     41   #include <stdint.h>
    42     42   #include <string.h>
    43     43   #include <limits.h>

Modified safekill.c from [59e57157c0] to [283d7b5c67].

   176    176   
   177    177   usage:
   178    178   	#define info  "\x1b[34m-- "
   179    179   	#define param "\x1b[32m"
   180    180   	#define eol   "\x1b[m\n"
   181    181   	#define bold  "\x1b[1m"
   182    182   	#define nl    "       "
   183         -	fprintf(stderr, bold "usage:\x1b[0m %s    "       "     " info "kill active window if non-vital" eol
          183  +	fprintf(stderr, bold "usage:\x1b[0m %1$s    "       "     " info "kill active window if non-vital" eol
   184    184   						 nl "%1$s -v " param "[id] " info "make [id] or active window vital" eol
   185    185   						 nl "%1$s -c " param "[id] " info "clear vital flag on [id] or active window" eol
   186    186   						 nl "%1$s -q " param "[id] " info "'emergency' kill w/o reference to vital flag" eol,
   187    187   				argv[0]);
   188    188   	return 1;
   189    189   }

Modified smake.c from [8f8960ef17] to [1bfb924710].

    17     17   
    18     18   #include <stdio.h>
    19     19   #include <stdlib.h>
    20     20   #include <string.h>
    21     21   #include <unistd.h>
    22     22   #include <fcntl.h>
    23     23   #include <sys/mman.h>
           24  +#include <assert.h>
    24     25   
    25     26   enum       e { ok, badname, nofile };
    26     27   enum    kind { css, sass, scss, bad };
    27     28   typedef enum { false, true } bool;
    28     29   
    29     30   #define corebufsz 1024
    30     31   char corebuf[corebufsz];
................................................................................
    47     48   				size_t rsz = bufptr-extbuf;
    48     49   				extbuf = realloc(extbuf, run);
    49     50   				bufptr = extbuf + rsz + sz;
    50     51   				return bufptr - sz;
    51     52   			}
    52     53   		}
    53     54   	}
    54         -	// this point should never be reached
           55  +	assert(false); /* this point should never be reached */
    55     56   	mkptr: {
    56     57   		void* ret = bufptr;
    57     58   		bufptr += sz;
    58     59   		return ret;
    59     60   	}
    60     61   }
    61     62   
................................................................................
   123    124   		*namecur = 0;
   124    125   		printf("found import to %s;\n", namebuf);
   125    126   		namecur = namebuf;
   126    127   	}
   127    128   	++cur; goto read_start;
   128    129   
   129    130   read_string:
   130         -	if (*cur == 0) goto read_done; //unterminated string!?
          131  +	if (*cur == 0) goto read_done; /* unterminated string!? */
   131    132   	if (*cur++ == strqt) goto read_start;
   132    133   	goto read_string;
   133    134   
   134    135   read_ml_comment:
   135         -	if (*cur == 0) goto read_done; //unterminated comment!
          136  +	if (*cur == 0) goto read_done; /* unterminated comment! */
   136    137   	if (*cur == '*' && cur[1] == '/') {
   137    138   		cur += 2;
   138    139   		goto read_start;
   139    140   	}
   140    141   	++cur; goto read_ml_comment;
   141    142   
   142    143   skip_line:

Added soda.c version [0502e2ccfd].

            1  +/* [ʞ] soda - libsodium front end
            2  + *  ~ lexi hale <lexi@hale.su>
            3  + *  © AGPLv3
            4  + *  @ vim: ft=c
            5  + */
            6  +
            7  +#define _POSIX_C_SOURCE 200809L
            8  +#include <unistd.h>
            9  +#include <stdio.h>
           10  +#include <errno.h> /* boo hiss */
           11  +#include <stdint.h>
           12  +#include <limits.h>
           13  +#include <stdbool.h>
           14  +#include <string.h>
           15  +#include <sodium.h>
           16  +#include <fcntl.h>
           17  +#define sz(x) (sizeof(x)/sizeof((x)[0]))
           18  +
           19  +#define _IAIA_FN_ITOA str_of_int
           20  +#define _IAIA_FN_ATOI int_of_str
           21  +#include "clib/iaia.c"
           22  +
           23  +#ifdef _WIN32
           24  +#	define open  _open
           25  +#	define close _close
           26  +#	define read  _read
           27  +#	define write _write
           28  +#endif
           29  +
           30  +#define _self_name "soda"
           31  +
           32  +typedef unsigned long long word;
           33  +
           34  +typedef enum atom {
           35  +	/* commands */
           36  +	atom_none,
           37  +	atom_key,
           38  +	atom_gen,
           39  +	atom_pub,
           40  +	atom_psk,
           41  +	atom_pair,
           42  +	atom_cert,
           43  +	atom_sign,
           44  +	atom_verify,
           45  +	atom_enc,
           46  +	atom_dec,
           47  +	atom_wrap,
           48  +	atom_unwrap,
           49  +	atom_help,
           50  +	_atom_cmd_n,
           51  +
           52  +	/* flags */
           53  +	atom_verbose,
           54  +	atom_quiet,
           55  +	atom_noise,
           56  +	atom_ascii_armor,
           57  +	atom_color_on,
           58  +	atom_color_off,
           59  +	atom_out, atom_outfd,
           60  +	atom_in, atom_infd,
           61  +	atom_pw, atom_pwfd, atom_pwtty,
           62  +	atom_sig, atom_sigfd,
           63  +	atom_symmetric,
           64  +	atom_raw,
           65  +	atom_fd,
           66  +	atom_fmtv,
           67  +	_atom_flag_n,
           68  +
           69  +	/* debug values */
           70  +	atom_debug,
           71  +	atom_info,
           72  +	atom_warn,
           73  +	atom_fatal,
           74  +	atom_silent,
           75  +
           76  +	_atom_n,
           77  +	atom_capture = _atom_n
           78  +} atom;
           79  +
           80  +enum noise {
           81  +	noise_debug,
           82  +	noise_info,
           83  +	noise_warn,
           84  +	noise_fatal,
           85  +	noise_silent,
           86  +
           87  +	_noise_n
           88  +};
           89  +struct { const char* label; unsigned char color; }
           90  +noise_level_text[] = {
           91  +	[noise_debug] = {"(debug",5},
           92  +	[noise_info ] = {" (info", 4},
           93  +	[noise_warn ] = {" (warn", 3},
           94  +	[noise_fatal] = {"(fatal",1},
           95  +};
           96  +
           97  +struct cmd_spec { const char* desc; const char** params; struct cmd_spec* sub; }
           98  +cmd_specs[_atom_cmd_n] = { {NULL,NULL,NULL},
           99  +	[atom_key] = { "create and manipulate keypairs for public-key crypto", NULL, (struct cmd_spec[_atom_cmd_n]){
          100  +		[atom_gen] = { "generate new keypair", NULL, NULL },
          101  +		[atom_pub] = { "extract the public key portion from a keypair", NULL, NULL},
          102  +		[atom_wrap] = { "convert a binary key to its ascii-armor representation", NULL, NULL},
          103  +	}},
          104  +	[atom_psk] = { "create and manipulate keys for secret-key crypto", NULL, (struct cmd_spec[_atom_cmd_n]){
          105  +		[atom_gen] = { "generate new PSK", NULL, NULL },
          106  +	}},
          107  +	[atom_help] = { "display usage listings for commands",
          108  +		(const char*[]){"commands…",NULL}, NULL },
          109  +	[atom_sign] = { "sign data using public-key crypto",
          110  +		(const char*[]){"keyfile",NULL}, NULL },
          111  +	[atom_verify] = { "verify signature on data",
          112  +		(const char*[]){"pubfile",NULL}, NULL },
          113  +	[atom_enc] = { "encrypt data", NULL, (struct cmd_spec[_atom_cmd_n]){
          114  +		[atom_pub] = { "encrypt against public key", (const char*[]){
          115  +			"pubfile", NULL
          116  +		}, NULL},
          117  +		[atom_psk] = { "encrypt against symmetric, pre-shared key", (const char*[]){
          118  +			"pskfile", NULL
          119  +		}, NULL},
          120  +	}},
          121  +	[atom_wrap] = {"wrap binary input with ascii armor so it can survive transmission mechanisms that are not 8-bit clean", NULL,NULL},
          122  +	[atom_unwrap] = {"translate ascii-armored input back to the original binary", NULL,NULL},
          123  +};
          124  +
          125  +enum say { say_stop, say_string, say_string_list, say_char, say_uint, say_sint, say_hex };
          126  +struct sayp {
          127  +	enum say kind; union {
          128  +		const char*  str;
          129  +		const char** strlist;
          130  +		char ch;
          131  +		long long unsigned int uint;
          132  +		long long signed   int sint;
          133  +	};
          134  +};
          135  +
          136  +enum feature {
          137  +	feature_off = 0,
          138  +	feature_on = 1,
          139  +	feature_auto = 2,
          140  +};
          141  +
          142  +struct ctx {
          143  +	size_t argc; const char** argv;
          144  +	size_t cmdc; atom* cmdv;
          145  +	const char* bin;
          146  +	struct {
          147  +		enum noise noise;
          148  +		enum feature
          149  +			ascii_armor,
          150  +			color;
          151  +		bool fd;
          152  +		bool pw_force_tty;
          153  +		const char* infile,* outfile,* pwfile,* sigfile;
          154  +		word infd, outfd, pwfd, sigfd;
          155  +		bool symcrypt;
          156  +		word fmtv;
          157  +	} flags;
          158  +};
          159  +
          160  +bool
          161  +isws(const char c) {
          162  +	/* TODO: maybe ignore all characters outside the ascii armory? */
          163  +	return (c == ' ' || c == '\t' || c == '\n');
          164  +}
          165  +
          166  +enum { _buffer_max = 512 };
          167  +struct buffer {
          168  +	int fd; /* where to flush */
          169  +	size_t sz;
          170  +	char bytes[_buffer_max];
          171  +};
          172  +void buffer_flush(struct buffer* b) { write(b->fd, b->bytes, b->sz); b->sz = 0; }
          173  +#define _buffer_write(s,b) buffer_write(s, b, sizeof(b))
          174  +void buffer_write(struct buffer* b, const char* str, size_t sz) {
          175  +	if (sz == 0) {
          176  +		size_t i;
          177  +		for (i = 0; str[i] != 0; ++i) {
          178  +			const size_t ofs = (b->sz + i) % _buffer_max;
          179  +			b->bytes[ofs] = str[i];
          180  +			if (ofs == 0 && i != 0) buffer_flush(b);
          181  +		}
          182  +		b -> sz = (b->sz + i) % _buffer_max;
          183  +	} else if (sz > _buffer_max) {
          184  +		buffer_flush(b);
          185  +		write(b->fd, str, sz);
          186  +		b->sz = sz;
          187  +	} else if (_buffer_max - b->sz < sz) {
          188  +		buffer_flush(b);
          189  +		memcpy(b->bytes, str, sz);
          190  +		b->sz = sz;
          191  +	} else {
          192  +		memcpy(b->bytes + b->sz, str, sz);
          193  +		b->sz += sz;
          194  +	}
          195  +}
          196  +void buffer_push(struct buffer* b, const char c) { buffer_write(b, &c, 1); }
          197  +
          198  +void say(struct ctx c, enum noise kind, struct sayp* s) {
          199  +	struct buffer o = {2,0};
          200  +	if (c.flags.noise > kind) return; 
          201  +	if (noise_level_text -> label != NULL)
          202  +	if (c.flags.color) {
          203  +		buffer_write(&o,"\x1b[1;3",0);
          204  +		buffer_push(&o, 0x30 + noise_level_text[kind].color);
          205  +		_buffer_write(&o,"m");
          206  +		buffer_write(&o, noise_level_text[kind].label, 0);
          207  +		_buffer_write(&o,")\x1b[m");
          208  +	} else {
          209  +		buffer_write(&o, noise_level_text[kind].label, 0);
          210  +		buffer_push(&o,')');
          211  +	}
          212  +
          213  +	bool bright = false;
          214  +	while(s -> kind != say_stop) {
          215  +		if (c.flags.color) {
          216  +			if (bright) _buffer_write(&o," \x1b[1m");
          217  +				   else _buffer_write(&o," \x1b[m");
          218  +		} else {
          219  +			buffer_push(&o,' ');
          220  +		}
          221  +		char intbuf[128];
          222  +		char* const intbuf_end = intbuf + sizeof intbuf;
          223  +		char* start;
          224  +
          225  +		explain: switch(s -> kind) {
          226  +			case say_string: buffer_write(&o, s -> str, 0);  break;
          227  +			case say_char:   buffer_push(&o, s -> ch);       break;
          228  +			case say_uint:
          229  +				str_of_int(10, s -> uint, intbuf, intbuf_end - 1, &start, true);
          230  +				buffer_write(&o, start, intbuf_end - start);
          231  +				break;
          232  +			case say_sint:
          233  +				/* TODO oops! iaia doesn't have signed int support yet 🙃 */
          234  +				/* str_of_int(10, s -> uint, intbuf, intbuf + sizeof intbuf, &end, true) */
          235  +				/* buffer_write(&o, intbuf, end - intbuf); */
          236  +				break;
          237  +			case say_hex:
          238  +				str_of_int(16, s -> uint, intbuf, intbuf_end - 1, &start, true);
          239  +				_buffer_write(&o, "0x");
          240  +				buffer_write(&o, start, intbuf_end - start);
          241  +				break;
          242  +			case say_string_list:
          243  +				buffer_push(&o,'[');
          244  +				for(size_t i = 0; s -> strlist[i] != NULL; ++i) {
          245  +					if (i != 0) dprintf(2,", ");
          246  +					_buffer_write(&o,"“");
          247  +					buffer_write(&o,s -> strlist[i],0);
          248  +					_buffer_write(&o,"”");
          249  +				}
          250  +				buffer_push(&o,']');
          251  +			break;
          252  +		}
          253  +
          254  +		++s; bright = !bright;
          255  +	}
          256  +
          257  +	if (c.flags.color) _buffer_write(&o, "\x1b[m\n");
          258  +		else buffer_push(&o, '\n');
          259  +	
          260  +	buffer_flush(&o);
          261  +}
          262  +
          263  +enum { _atom_max_str_len = 12 };
          264  +struct { atom val; const char* str;}
          265  +/* first name listed for an atom is treated as the canonical name
          266  + * by reconstitute() and displayed in usage listings */
          267  +atom_strings[] = {
          268  +	/* commands */
          269  +	{atom_key, "key"}, {atom_key, "privkey"}, {atom_key, "priv"},
          270  +	{atom_pub, "pub"}, {atom_pub, "pubkey"},
          271  +	{atom_psk, "psk"},
          272  +	{atom_cert, "cert"}, {atom_cert, "certificate"},
          273  +	{atom_sign, "sign"},
          274  +	{atom_verify, "verify"}, {atom_verify, "vfy"}, {atom_verify, "v"},
          275  +	{atom_enc, "enc"}, {atom_enc, "encipher"}, {atom_enc, "encrypt"},
          276  +	{atom_dec, "dec"}, {atom_dec, "decipher"}, {atom_dec, "decrypt"},
          277  +	{atom_wrap, "wrap"}, {atom_wrap, "armor"},
          278  +	{atom_unwrap, "unwrap"}, {atom_unwrap, "dearmor"}, {atom_unwrap, "disarmor"},
          279  +	{atom_gen, "gen"}, {atom_gen, "generate"}, {atom_gen, "create"}, {atom_gen, "new"},
          280  +	{atom_help, "help"},
          281  +
          282  +	/* flags */
          283  +	{atom_quiet, "quiet"},
          284  +	{atom_ascii_armor, "ascii-armor"},
          285  +	{atom_raw, "raw"},
          286  +	{atom_sig, "sig"}, {atom_sig, "sigfile"},
          287  +	{atom_sigfd, "sig-fd"}, {atom_sig, "sigfd"},
          288  +	{atom_verbose, "verbose"},
          289  +	{atom_noise, "noise"},
          290  +	{atom_color_on, "color"},
          291  +	{atom_color_off, "no-color"},
          292  +	{atom_out, "out"}, {atom_out, "output"}, {atom_out, "outfile"},
          293  +	{atom_outfd, "out-fd"}, {atom_outfd, "outfd"},
          294  +	{atom_in, "in"}, {atom_out, "input"}, {atom_out, "infile"},
          295  +	{atom_infd, "in-fd"}, {atom_infd, "infd"},
          296  +	{atom_pw, "pw"}, {atom_pw, "passphrase"}, {atom_pw, "password"},
          297  +	{atom_pwfd, "pw-fd"}, {atom_pwfd, "pwfd"},
          298  +	{atom_pwtty, "pw-tty"}, {atom_pwtty, "pwtty"},
          299  +	{atom_fd, "fd"},
          300  +	{atom_symmetric, "symmetric"}, {atom_symmetric, "sym"}, {atom_symmetric, "symcrypt"},
          301  +	{atom_fmtv, "format"}, {atom_fmtv, "fmt"}, {atom_fmtv, "format-version"}, 
          302  +
          303  +	/* debug levels */
          304  +	{atom_silent, "silent"},
          305  +	{atom_debug, "debug"}, {atom_debug, "dbg"},
          306  +	{atom_info, "info"},
          307  +	{atom_warn, "warn"}, {atom_warn, "warnings"},
          308  +	{atom_fatal, "fatal"}, {atom_fatal, "error"},
          309  +};
          310  +
          311  +atom
          312  +atomize(const char* str) {
          313  +	for(size_t i = 0; i < sz(atom_strings); ++i) {
          314  +		if (strncmp(atom_strings[i].str, str, _atom_max_str_len) == 0) {
          315  +			return atom_strings[i].val;
          316  +		}
          317  +	}
          318  +	return atom_none;
          319  +}
          320  +
          321  +const char*
          322  +reconstitute(atom a) {
          323  +	for(size_t i = 0; i < sz(atom_strings); ++i) {
          324  +		if (atom_strings[i].val == a) return atom_strings[i].str;
          325  +	}
          326  +	return NULL;
          327  +}
          328  +
          329  +struct flag {
          330  +	enum flag_kind {
          331  +		flag_none, 
          332  +		_flag_noparam,
          333  +			flag_on, flag_off, flag_switch,
          334  +			flag_inc, flag_dec, flag_sdec,
          335  +		_flag_param,
          336  +			flag_atom, flag_str,
          337  +			flag_uint, flag_sint,
          338  +			flag_raw_atom,
          339  +		flag_value
          340  +	} kind;
          341  +	char shortname;
          342  +	const char* desc;
          343  +	struct { atom first; atom last; } opts;
          344  +	size_t offset;
          345  +}
          346  +
          347  +#define _declflag_raw(a,k,s,o,rs,re,d) [atom_##a] = { \
          348  +	.kind = flag_##k, \
          349  +	.shortname = s, \
          350  +	.desc = d, \
          351  +	.opts = { rs,re }, \
          352  +	.offset = (size_t)&(((struct ctx*)0) -> flags.o) \
          353  +},
          354  +#define _declflag(a,k,s,o,d) _declflag_raw(a,k,s,o,0,0,d)
          355  +#define _declflaga(a,rs,re,s,o,d) _declflag_raw(a,atom,s,o,rs,re,d)
          356  +
          357  +flags[_atom_n] = {
          358  +	{flag_none},
          359  +	_declflag(ascii_armor, value + feature_on, 'a', ascii_armor,
          360  +			"emit ascii-encoded data instead of raw binary "
          361  +			"data (default if stdout is a TTY)")
          362  +	_declflag(raw, value + feature_off, 'r', ascii_armor,
          363  +			"emit binary data instead of raw ascii-armored "
          364  +			"data (default if stdout is not a TTY)")
          365  +	_declflag(in, str, 'i', infile,
          366  +			"read data from a file instead of stdin")
          367  +	_declflag(infd, uint, 'I', infd,
          368  +			"read data from a file instead of stdin")
          369  +	_declflag(out, str, 'o', outfile,
          370  +			"write data to a file instead of stdout")
          371  +	_declflag(outfd, uint, 'O', outfd,
          372  +			"write data to a file instead of stdout")
          373  +	_declflag(pw, str, 'p', pwfile,
          374  +			"load passphrase from file instead of stdin")
          375  +	_declflag(pwfd, uint, 'P', pwfd,
          376  +			"load passphrase from file instead of stdin")
          377  +	_declflag(pwtty, switch, 'W', pw_force_tty,
          378  +			"always read password from the input TTY unless a file is specified instead")
          379  +	_declflag(sig, str, 'g', sigfile,
          380  +			"read signature from specified file instead of extracting it from input data")
          381  +	_declflag(sigfd, uint, 'G', sigfd,
          382  +			"read signature from specified file descriptor instead of extracting it from input data")
          383  +	_declflag(symmetric, switch, 's', symcrypt,
          384  +			"secure sensitive key material with symmetric cryptography")
          385  +	_declflag(fd, switch, 'f', fd,
          386  +			"interpret filename arguments to commands as file descriptor numbers")
          387  +	_declflag(color_on, value + feature_on, 'c', color,
          388  +			"enable colorized output (default if stderr is a TTY)")
          389  +	_declflag(color_off, value + feature_off, 'C', color,
          390  +			"disable colorized output")
          391  +	_declflag(verbose, dec, 'v', noise,
          392  +			"increase noise level")
          393  +	_declflag(quiet, value + noise_silent, 'q', noise,
          394  +			"emit nothing to stderr")
          395  +	_declflaga(noise, atom_debug, atom_silent, 'n', noise,
          396  +			"set noise level")
          397  +	_declflag(fmtv, uint, 'F', fmtv,
          398  +			"use a specific data file format version instead of the native version (0)")
          399  +};
          400  +
          401  +const char** flag_enum_desc[_atom_n] = {
          402  +	[atom_noise] = (const char*[_noise_n]){
          403  +		[noise_silent] = "run in complete silence",
          404  +		[noise_debug] = "emit all notices, including those useful only for debugging",
          405  +		[noise_info] = "report on the progress of the operation",
          406  +		[noise_warn] = "emit non-fatal warnings",
          407  +		[noise_fatal] = "only emit fatal errors",
          408  +	},
          409  +};
          410  +
          411  +#undef _declflag
          412  +#undef _declflaga
          413  +#undef _declflag_raw
          414  +
          415  +/* the casts here are a bit wacky. this is unfortunately necessary as C
          416  + * insists on evaluating each branch of the generic expression regardless
          417  + * of which type is picked. why anyone thought this was a good idea is
          418  + * beyond me. */
          419  +#define _t(x) (_Generic((x), \
          420  +	      const char**: (struct sayp){say_string_list, .strlist  = (const char**)(size_t)(x)}, \
          421  +	              char: (struct sayp){say_char,   .ch   = (char)(size_t)(x)}, \
          422  +	             char*: (struct sayp){say_string, .str  = (const char*)(size_t)(x)}, \
          423  +	       const char*: (struct sayp){say_string, .str  = (const char*)(size_t)(x)}, \
          424  +				   int: (struct sayp){say_sint,   .sint = (long long)(x)}, \
          425  +	             short: (struct sayp){say_sint,   .sint = (long long)(x)}, \
          426  +	              long: (struct sayp){say_sint,   .sint = (long long)(x)}, \
          427  +	         long long: (struct sayp){say_sint,   .sint = (long long)(x)}, \
          428  +		      unsigned: (struct sayp){say_uint,   .uint = (long long unsigned)(x)}, \
          429  +	    unsigned short: (struct sayp){say_uint,   .uint = (long long unsigned)(x)}, \
          430  +	     long unsigned: (struct sayp){say_uint,   .uint = (long long unsigned)(x)}, \
          431  +	long long unsigned: (struct sayp){say_uint,   .uint = (long long unsigned)(x)}, \
          432  +	default: (struct sayp){say_hex, .uint = (size_t)(x)})),
          433  +#define _say(k,...)  say(c, noise_##k, ((struct sayp[]){__VA_ARGS__ {say_stop}}))
          434  +#define _report(k,s) _say(k, _t(s))
          435  +
          436  +void
          437  +usage(struct ctx c, const struct cmd_spec* tree) {
          438  +	const char* bin = c.bin;
          439  +	const char* fmt_flag,* fmt_usage,* fmt_enum,* head_start,* head_fin;
          440  +	if (c.flags.color) {
          441  +		fmt_usage = "\x1b[1musage:\x1b[m %s \x1b[32m[flags]\x1b[m%s \x1b[33mcommand\x1b[m\n";
          442  +		fmt_flag = "  \x1b[1;36m%c%c \x1b[;95m--%s\x1b[m: %s\n";
          443  +		fmt_enum = "      \x1b[94m%s\x1b[m: %s\n";
          444  +		head_start = "\x1b[1m"; head_fin = "\x1b[m";
          445  +	} else {
          446  +		fmt_usage = "usage: %s [flags]%s <command>\n";
          447  +		fmt_flag = "  %c%c --%s: %s\n";
          448  +		fmt_enum = "      %s: %s\n";
          449  +		head_start = head_fin = "";
          450  +	}
          451  +
          452  +	char ccmd[32]; char *cptr = ccmd; *cptr = 0;
          453  +	if (tree == NULL) tree = cmd_specs;
          454  +	else if(c.cmdc > 1) for (size_t i = 1; i<c.cmdc; ++i) {
          455  +		*(cptr++)=' ';
          456  +		cptr=stpcpy(cptr, reconstitute(c.cmdv[i]));
          457  +	}
          458  +
          459  +
          460  +	dprintf(2,fmt_usage,bin,ccmd);
          461  +
          462  +	dprintf(2,"%scommands:%s\n",head_start,head_fin);
          463  +	for (size_t i = 0; i<_atom_cmd_n; ++i) {
          464  +		if (tree[i].desc == NULL) continue;
          465  +		
          466  +		dprintf(2, "  %s", reconstitute(i));
          467  +		if (tree[i].params != NULL) {
          468  +			for (size_t j = 0; tree[i].params[j] != NULL; ++j) {
          469  +				dprintf(2," <%s>", tree[i].params[j]);
          470  +			}
          471  +		}
          472  +		dprintf(2,"%s: %s\n",
          473  +				tree[i].sub != NULL ? " …" : "",
          474  +				tree[i].desc);
          475  +	}
          476  +
          477  +	dprintf(2,"%sflags:%s\n",head_start,head_fin);
          478  +	for (size_t i = _atom_cmd_n + 1; i<_atom_flag_n; ++i) {
          479  +		/* if (flags[i].kind == flag_none) continue; */
          480  +		dprintf(2,fmt_flag,
          481  +				flags[i].shortname != 0 ? '-' : ' ',
          482  +				flags[i].shortname != 0 ? flags[i].shortname : ' ',
          483  +				reconstitute(i), flags[i].desc);
          484  +		if (flags[i].kind == flag_atom || flags[i].kind == flag_raw_atom) {
          485  +			for (atom a = flags[i].opts.first; a < flags[i].opts.last + 1; ++a) {
          486  +				dprintf(2,fmt_enum,reconstitute(a),flag_enum_desc[i][a - flags[i].opts.first]);
          487  +			}
          488  +		}
          489  +	}
          490  +}
          491  +
          492  +bool
          493  +atom_match(struct ctx c, size_t* match) {
          494  +	/* primitive pattern-matching function */
          495  +	for (size_t i = 0; i < c.cmdc; ++i) {
          496  +		if (match[i] == 0) return false;
          497  +
          498  +		if (match[i] < atom_capture) {
          499  +			if (match[i] != c.cmdv[i]) return false;
          500  +		} else if (c.cmdv[i] >= _atom_n) {
          501  +			const char** dest = (const char**)(match[i] - atom_capture);
          502  +			*dest = c.argv[c.cmdv[i] - _atom_n];
          503  +		} else {
          504  +			return false;
          505  +		}
          506  +	}
          507  +	return match[c.cmdc] == 0;
          508  +}
          509  +
          510  +/* the ascii armory is intentionally limited to characters that are
          511  + * not only printable but that are unlikely to have special meaning
          512  + * such that they would require escaping for any form of input.
          513  + * while this is a more limited set than base64, it's significantly
          514  + * stronger armor -- a user can, for instance, simply paste it into
          515  + * most shells without quoting. */
          516  +const char ascii_armory[] = "0123456789/abcdefghijklmopqrstuvwxyz."
          517  +                            "-ABCDEFGHIJKLMOPQRSTUVWXYZ_";
          518  +const char ascii_value[] = 
          519  +	"\x25\x24\x0a\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\00\00\00"
          520  +	"\00\00\00\00\x26\x27\x28\x29\x2a\x2b\x2c\x2d\x2e\x2f\x30"
          521  +	"\x31\x32\00\x33\x34\x35\x36\x37\x38\x39\x3a\x3b\x3c\x3d\x3e"
          522  +	"\00\00\00\00\x3f\00\x0b\x0c\x0d\x0e\x0f\x10\x11\x12\x13"
          523  +	"\x14\x15\x16\x17\00\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f\x20\x21"
          524  +	"\x22";
          525  +	/* generated by a script. do not touch. */
          526  +
          527  +enum { armor_split_every = 48 };
          528  +char*
          529  +armor(const unsigned char* src, char* dest, size_t sz, size_t brstart, bool format) {
          530  +	char* bufptr = dest;
          531  +	unsigned char carry = 0;
          532  +	for(size_t i = 0; i<sz; ++i) {
          533  +		/* insert line breaks every so many chars */
          534  +		*(bufptr++)=ascii_armory[src[i] % 64];
          535  +		if (format) if ((brstart + (bufptr - dest)) % armor_split_every == 0)
          536  +			*(bufptr++)='\n', *(bufptr++)=' ';
          537  +
          538  +		carry<<=2;
          539  +		carry |= (src[i] / 64);
          540  +		if (i && (i%3 == 0)) { /* :( */
          541  +			*(bufptr++)=ascii_armory[carry];
          542  +			carry = 0;
          543  +		}
          544  +		if (format) if ((brstart + (bufptr - dest)) % armor_split_every == 0)
          545  +			*(bufptr++)='\n', *(bufptr++)=' ';
          546  +	}
          547  +	/* if(carry != 0) */ *(bufptr++)=ascii_armory[carry];
          548  +	return bufptr;
          549  +}
          550  +
          551  +char* disarmor(const unsigned char* src, char* dest, size_t sz) {
          552  +	/* transforms ascii armor into binary. can transform in place. */
          553  +	for(size_t i = 0; i<sz; i += 3) {
          554  +		while (isws(src[i])) ++i;
          555  +		const char* s = src + i;
          556  +		unsigned char carry = ascii_value[s[2] - '-'],
          557  +			          b1    = ascii_value[s[0] - '-'] + (64 * ((carry & 12) >> 2)),
          558  +		              b2    = ascii_value[s[1] - '-'] + (64 * ( carry &  3));
          559  +		*(dest++)=b1; *(dest++)=b2;
          560  +	}
          561  +	return dest;
          562  +}
          563  +
          564  +struct pstr {
          565  +	size_t len;
          566  +	const char* str;
          567  +};
          568  +#define _p(x) {sizeof(x)-1,(x)}
          569  +struct encoding { struct pstr bin; struct pstr asc; const char* desc;};
          570  +enum doc {
          571  +	doc_none,
          572  +	doc_header,
          573  +	doc_footer,
          574  +
          575  +	_doc_versions,
          576  +		doc_version_1,
          577  +		doc_version_2,
          578  +		doc_version_3,
          579  +
          580  +	_doc_types,
          581  +		doc_type_priv /* private key */,
          582  +		doc_type_pub /* public key */,
          583  +		doc_type_psk /* shared secret key */,
          584  +		doc_type_asc /* arbitrary ascii-armored data */,
          585  +		doc_type_sig /* separate signature */,
          586  +		doc_type_signed /* document prefixed with signature */,
          587  +		doc_type_master /* master key used to derive keys deterministically */,
          588  +
          589  +	_doc_n
          590  +};
          591  +const struct encoding docbytes[_doc_n] = { {0,NULL},
          592  +	[doc_header]      = {_p("\x26\xe9\x11\x1a"), _p("[" _self_name)},
          593  +	[doc_footer]      = {_p(""), _p("]")},
          594  +	[doc_version_1]   = {_p("\01"), _p("1-")},
          595  +	[doc_version_2]   = {_p("\02"), _p("2-")},
          596  +	[doc_version_3]   = {_p("\03"), _p("3-")},
          597  +	[doc_type_priv]   = {_p("\01"), _p("priv-"),"private key material"},
          598  +	[doc_type_pub]    = {_p("\02"), _p("pub-"), "public key material"},
          599  +	[doc_type_psk]    = {_p("\03"), _p("psk-"), "shared key material"},
          600  +	[doc_type_asc]    = {_p("\04"), _p("asc-"), "ascii-armored binary data"},
          601  +	[doc_type_sig]    = {_p("\05"), _p("sig-"), "separate signature"},
          602  +	[doc_type_signed] = {_p("\06"), _p("signed-"), "data with signature"},
          603  +	[doc_type_master] = {_p("\07"), _p("master-"), "master key"},
          604  +};
          605  +
          606  +
          607  +char*
          608  +compare_wsi(const char* s, char* d, size_t sz) {
          609  +	for (size_t i=0; i<sz; ++i) {
          610  +		while(isws(*s)) ++s;
          611  +		while(isws(*d)) ++d;
          612  +		if (*s != *d) return NULL;
          613  +		++s; ++d;
          614  +	}
          615  +	return d;
          616  +}
          617  +
          618  +bool
          619  +file_cmp(enum doc fld, char** s, bool binary) {
          620  +	/* compares against a field and increments the callee's 
          621  +	 * pointer appropriately if a match is found */
          622  +	if (binary) {
          623  +		if (memcmp(*s, docbytes[fld].bin.str, docbytes[fld].bin.len) == 0) {
          624  +			*s += docbytes[fld].bin.len;
          625  +			return true;
          626  +		} else return false;
          627  +	} else {
          628  +		char* p = compare_wsi(docbytes[fld].asc.str, *s, docbytes[fld].asc.len);
          629  +		if (p != NULL) {
          630  +			*s = p;
          631  +			return true;
          632  +		} else return false;
          633  +	}
          634  +}
          635  +
          636  +struct file {
          637  +	enum doc version, type;
          638  +	union {
          639  +		struct file_signed {
          640  +			char* sig, *text;
          641  +		} signtext;
          642  +		struct file_key {
          643  +			char* priv, *pub;
          644  +		} key;
          645  +		char* pub;
          646  +	} data;
          647  +	char* datastart;
          648  +	size_t rawlen, datlen;
          649  +	char raw [];
          650  +}* load_file(struct ctx c, const int fd) {
          651  +	_say(debug, _t("reading in file from fd") _t(fd));
          652  +	size_t run = 512; size_t len = 0;
          653  +	struct file* buf = malloc(run + sizeof(struct file)); /* :( */
          654  +	int e; while(e = read(fd, buf->raw + len, run - len)) {
          655  +		if (e == -1) {
          656  +			switch(errno) {
          657  +				case EBADF: _say(fatal, _t("file descriptor") _t((unsigned)fd) _t("does not reference an open, readable file")); break;
          658  +				case EFAULT: _say(fatal, _t("file descriptor") _t((unsigned)fd) _t("references a file outside your accessible address space")); break;
          659  +				case EIO: _say(fatal, _t("file descriptor") _t((unsigned)fd) _t("could not be read due to an I/O error")); break;
          660  +				case EISDIR: _say(fatal, _t("file descriptor") _t((unsigned)fd) _t("is a directory, not a readable file")); break;
          661  +				default: _say(fatal, _t("file descriptor") _t((unsigned)fd) _t("could not be opened due to an unknown error - consult errno table for code") _t((unsigned)errno)); break;
          662  +			}
          663  +			goto err;
          664  +		}
          665  +
          666  +		len += e;
          667  +		if (len == run) {
          668  +			run *= 2; buf = realloc(buf, run + sizeof(struct file));
          669  +		} else if (len < run) continue;
          670  +	}
          671  +
          672  +	buf -> rawlen = len;
          673  +	buf -> type = doc_none;
          674  +	return buf;
          675  +
          676  +	err: free(buf);
          677  +		 return NULL;
          678  +}
          679  +
          680  +struct file* read_file(struct ctx c, const int fd) {
          681  +	struct file* buf = load_file(c, fd);
          682  +	bool binary; size_t len = buf -> rawlen;
          683  +	char* cur = buf -> raw;
          684  +
          685  +	if (file_cmp(doc_header,&cur,true)) {
          686  +		_report(debug, "parsing binary data");
          687  +		binary = true;
          688  +	} else if (file_cmp(doc_header,&cur,false)) {
          689  +		_report(debug, "parsing ascii-armored data");
          690  +		binary = false;
          691  +	} else {
          692  +		_say(fatal, _t("cannot parse data from fd") _t((unsigned)fd));
          693  +		goto err;
          694  +	}
          695  +
          696  +	/* write(2, cur, run - (cur - buf->raw)); */
          697  +
          698  +	for (enum doc i = _doc_versions + 1; i < _doc_types; ++ i) {
          699  +		if (file_cmp(i, &cur, binary)) {
          700  +			_say(debug, _t("file is format version") _t((unsigned) i - _doc_versions));
          701  +			buf -> version = i; goto found_version;
          702  +		}
          703  +	} /* else */ {
          704  +		_say(fatal, _t("unrecognized file format version; are you using the most recent release of " _self_name "?"));
          705  +		goto err;
          706  +	}
          707  +	found_version:;
          708  +	
          709  +	for (enum doc i = _doc_types + 1; i < _doc_n; ++ i) {
          710  +		if (file_cmp(i, &cur, binary)) {
          711  +			_say(debug, _t("file contains") _t(docbytes[i].desc));
          712  +			buf -> type = i; goto found_type;
          713  +		}
          714  +	} /* else */ {
          715  +		_say(fatal, _t("unrecognized file type; are you using the most recent release of " _self_name "?"));
          716  +		goto err;
          717  +	}
          718  +	found_type: if (!binary) {
          719  +		size_t s = (len - (cur - buf->raw)) - docbytes[doc_footer].asc.len;
          720  +		_say(debug, _t("disarmoring") _t(s) _t("bytes of encoded text"));
          721  +		char* end = disarmor(cur, buf -> raw, s);
          722  +		buf -> datlen = end - cur;
          723  +	} else {
          724  +		buf -> datlen = (len - (cur - buf->raw)) - docbytes[doc_footer].bin.len;
          725  +	}
          726  +
          727  +	buf -> datastart = cur;
          728  +	return buf;
          729  +
          730  +	err: free(buf);
          731  +		 return NULL;
          732  +}
          733  +
          734  +void
          735  +write_armored_v1(struct ctx c, const unsigned char* data, size_t sz) {
          736  +	struct buffer o = {c.flags.outfd,0};
          737  +#	define _wra_fld(f) buffer_write(&o, docbytes[doc_##f].asc.str,\
          738  +		docbytes[doc_##f].asc.len);
          739  +	_wra_fld(header);
          740  +	_wra_fld(version_1);
          741  +	_wra_fld(type_asc);
          742  +
          743  +	char buf[sz*2];
          744  +	char* end = armor(data,buf,sz,13, isatty(c.flags.outfd));
          745  +	buffer_write(&o, buf, end - buf);
          746  +
          747  +	_wra_fld(footer);
          748  +#	undef _wra_fld
          749  +
          750  +	if (isatty(c.flags.outfd)) buffer_push(&o, '\n');
          751  +	buffer_flush(&o);
          752  +}
          753  +
          754  +void
          755  +write_privkey_v1(struct ctx c, const unsigned char* material) {
          756  +	struct buffer o = {c.flags.outfd,0};
          757  +#	define _wrp_fld(f) buffer_write(&o, \
          758  +		(c.flags.ascii_armor ? docbytes[doc_##f].asc.str   \
          759  +		                     : docbytes[doc_##f].bin.str), \
          760  +		(c.flags.ascii_armor ? docbytes[doc_##f].asc.len   \
          761  +		                     : docbytes[doc_##f].bin.len))
          762  +	
          763  +	_wrp_fld(header);
          764  +	_wrp_fld(version_1);
          765  +	_wrp_fld(type_priv);
          766  +
          767  +	if (c.flags.ascii_armor) {
          768  +		char buf[crypto_sign_SECRETKEYBYTES * 2];
          769  +		char* end = armor(material,buf,crypto_sign_SECRETKEYBYTES,13,
          770  +				isatty(c.flags.outfd));
          771  +		buffer_write(&o, buf, end - buf);
          772  +	} else {
          773  +		buffer_write(&o, material, crypto_sign_SECRETKEYBYTES);
          774  +	}
          775  +
          776  +	_wrp_fld(footer);
          777  +	if (isatty(c.flags.outfd)) buffer_push(&o, '\n');
          778  +	buffer_flush(&o);
          779  +}
          780  +
          781  +typedef void (*write_privkey_t)(struct ctx, const unsigned char*);
          782  +typedef void (*write_armored_t)(struct ctx, const unsigned char*, size_t);
          783  +
          784  +write_privkey_t write_privkey[] = {
          785  +	[0] = write_privkey_v1, /* current version */
          786  +
          787  +	[1] = write_privkey_v1,
          788  +};
          789  +write_armored_t write_armored[] = {
          790  +	[0] = write_armored_v1, /* current version */
          791  +
          792  +	[1] = write_armored_v1,
          793  +};
          794  +
          795  +int
          796  +route(struct ctx c) {
          797  +	if (sodium_init() < 0) {
          798  +		_report(fatal, "could not initialize libsodium");
          799  +		return 2;
          800  +	}
          801  +
          802  +	switch (c.cmdv[0]) {
          803  +		case atom_key: {
          804  +			switch (c.cmdv[1]) {
          805  +				case atom_gen: {
          806  +					if (c.cmdc != 2) { usage(c,NULL); return 1; }
          807  +					_report(info, "generating new private key");
          808  +					if (c.flags.symcrypt == false) {
          809  +						_say(warn, _t("not using symmetric encryption; key will be emitted in") _t("plain text"));
          810  +					} else {
          811  +						/* read passphrase from stdin/file */
          812  +					}
          813  +					
          814  +					/* libsodium is a bit weird. it uses different key types for
          815  +					 * encrypting and signing. we emit a signing (ed25519) key
          816  +					 * because it can be converted to an encryption key, but not
          817  +					 * vice-versa. libsodium also does not have any means to
          818  +					 * derive a public key from a private key, even though this
          819  +					 * is trivial, but instead stores the public key in the second
          820  +					 * half of the private key (despite emitting it AGAIN into the
          821  +					 * second parameter). so we generate a signing keypair, discard
          822  +					 * the returned public key, and save the "private key" (including
          823  +					 * its copy of the public key). this is extremely awkward and
          824  +					 * hopefully a better solution will be found in the fullness of
          825  +					 * time. */
          826  +					unsigned char skey[crypto_sign_SECRETKEYBYTES];
          827  +					{ unsigned char _[crypto_sign_PUBLICKEYBYTES];
          828  +					  crypto_sign_keypair(_,skey); }
          829  +					(*write_privkey[c.flags.fmtv])(c, skey);
          830  +
          831  +				} break;
          832  +				case atom_pub: {
          833  +					if (c.cmdc != 2) { usage(c,NULL); return 1; }
          834  +					struct file* skey = read_file(c, c.flags.infd);
          835  +					if (skey == NULL) return 6;
          836  +
          837  +					free(skey);
          838  +				} break;
          839  +				case atom_wrap: {
          840  +					if (c.cmdc != 2) { usage(c,NULL); return 1; }
          841  +					struct file* key = read_file(c, c.flags.infd);
          842  +					c.flags.ascii_armor = feature_on;
          843  +
          844  +					if (key -> type == doc_type_priv) {
          845  +						(*write_privkey[c.flags.fmtv])(c, key -> datastart);
          846  +					} else if (key -> type == doc_type_pub) {
          847  +						/* (*write_pubkey[c.flags.fmtv])(c, key); */
          848  +					} else {
          849  +						_report(fatal, "file is not a " _self_name " key");
          850  +						free(key); return 8;
          851  +					}
          852  +					free(key); return 0;
          853  +				} break;
          854  +
          855  +				default: goto no_cmd;
          856  +			}
          857  +		} break;
          858  +		case atom_wrap: {
          859  +			if (c.cmdc != 1) { usage(c,NULL); return 1; }
          860  +
          861  +			struct file* data = load_file(c, c.flags.infd);
          862  +			if (data == NULL) return 6;
          863  +
          864  +			(*write_armored[c.flags.fmtv])(c, data->raw, data->rawlen);
          865  +
          866  +			free(data); return 0;
          867  +		} break;
          868  +		case atom_unwrap: {
          869  +			if (c.cmdc != 1) { usage(c,NULL); return 1; }
          870  +			struct file* data = read_file(c, c.flags.infd);
          871  +			if (data == NULL) return 6;
          872  +			if (isatty(c.flags.outfd)) _say(warn, _t("printing binary values to tty"));
          873  +			write(c.flags.outfd, data->datastart, data->datlen);
          874  +			free(data); return 0;
          875  +		} break;
          876  +		case atom_help: {
          877  +			if (c.cmdc == 1) usage(c,NULL);
          878  +			else {
          879  +				const struct cmd_spec* tree = cmd_specs;
          880  +				for (size_t i = 1; i < c.cmdc; ++i) {
          881  +					if (c.cmdv[i] < _atom_n) {
          882  +						if (tree[c.cmdv[i]].sub != NULL) {
          883  +							tree = tree[c.cmdv[i]].sub;
          884  +						} else {
          885  +							_say(fatal, _t("no help available for subcommand") _t(reconstitute(c.cmdv[i])));
          886  +							return 3;
          887  +						}
          888  +					} else {
          889  +						_say(fatal, _t("no help available;") _t(c.argv[c.cmdv[i] - _atom_n]) _t("is not a recognized command"));
          890  +						return 4;
          891  +					}
          892  +				}
          893  +				usage(c,tree);
          894  +			}
          895  +			return 0;
          896  +		} break;
          897  +		default: goto no_cmd;
          898  +	}
          899  +	return 0;
          900  +
          901  +	no_cmd: _say(fatal, _t("no such command"));
          902  +			usage(c,NULL);
          903  +			return 1;
          904  +}
          905  +
          906  +
          907  +int
          908  +open_file(struct ctx c, const char* path, int flags, word* retfd) {
          909  +	int fd = open(path,flags,0600);
          910  +	if (fd == -1) {
          911  +		switch (errno) {
          912  +			case EACCES: _say(fatal, _t("file") _t(path) _t("could not be opened because you do not have permission to read it")); break;
          913  +			case EFAULT: _say(fatal, _t("file") _t(path) _t("could not be opened because the path is outside your accessible address space")); break;
          914  +			case EISDIR: _say(fatal, _t("file") _t(path) _t("could not be opened because it is a directory, not a regular file")); break;
          915  +			case ELOOP: _say(fatal, _t("file") _t(path) _t("could not be opened because too many symbolic links were encountered")); break;
          916  +			case EMFILE: _say(fatal, _t("file") _t(path) _t("could not be opened because the per-process file limit is too low")); break;
          917  +			case ENOENT: _say(fatal, _t("file") _t(path) _t("could not be opened because it does not exist")); break;
          918  +			case ENOMEM: _say(fatal, _t("file") _t(path) _t("could not be opened, either because not enough memory remains to make use of a FIFO or because the kernel is running out of memory (!)")); break;
          919  +			case ENOTDIR: _say(fatal, _t("file") _t(path) _t("could not be opened because a file was specified in place of a directory in the path")); break;
          920  +			case ENXIO: _say(fatal, _t("file") _t(path) _t("could not be opened because it is not a regular file or FIFO")); break;
          921  +			case EPERM: _say(fatal, _t("file") _t(path) _t("could not be opened because it is sealed against access")); break;
          922  +			case EROFS: _say(fatal, _t("file") _t(path) _t("could not be opened for writing because it is located on a read-only filesystem")); break;
          923  +			default: _say(fatal, _t("file") _t(path) _t("could not be opened for unclear reasons - consult errno entry for code") _t((unsigned)errno)); break;
          924  +		}
          925  +		return errno;
          926  +	}
          927  +
          928  +	*retfd = fd; return 0;
          929  +}
          930  +
          931  +int
          932  +main(unsigned int argc, const char** argv) {
          933  +	atom cmdlist[argc];
          934  +	const char* paramlist[argc];
          935  +	size_t paramcount = 0;
          936  +
          937  +	struct ctx c = {
          938  +		.argv = paramlist,
          939  +		.cmdc = 0,
          940  +		.cmdv = cmdlist,
          941  +		.bin = (argc > 0 ? argv[0] : NULL),
          942  +		.flags = {
          943  +			.ascii_armor = feature_auto,
          944  +			.color = feature_auto,
          945  +			.noise = noise_fatal,
          946  +			.infile = NULL, .infd = 0,
          947  +			.outfile = NULL, .outfd = 1,
          948  +			.pwfile = NULL, .pwfd = 0,
          949  +			.pw_force_tty = false,
          950  +			.sigfile = NULL, .sigfd = 0,
          951  +				/* if sigfd == infd, extract sig from input stream */
          952  +			.fd = false,
          953  +			.symcrypt = false,
          954  +			.fmtv = 0, /* 0 = native version */
          955  +		},
          956  +	};
          957  +
          958  +	if (argc == 0) {
          959  +		_report(fatal, "invoked incorrectly or your environment is not sane; the 0th value of the argument array should be the name of or path to the program you are attempting to execute");
          960  +		return -1;
          961  +	}
          962  +
          963  +	struct flag* paramstack[argc];
          964  +	size_t stackptr = 0;
          965  +	enum {
          966  +		reading_params,
          967  +		reading_commands,
          968  +		reading_flags
          969  +	} reading_mode = reading_flags;
          970  +	_say(debug, _t("parsing command line") _t(argv));
          971  +	for (size_t i = 1; i < argc; ++i) {
          972  +		const char* const p = argv[i];
          973  +		
          974  +		if (reading_mode >= reading_flags && p[0] == '-') { /* flag */
          975  +			bool longflag;
          976  +			if (p[1] == '-') {
          977  +				if (p[2] == 0) {
          978  +					/* "--" has been passed; all further arguments must be
          979  +					 * interpreted as parameters or commands, not flags */
          980  +					reading_mode = reading_commands;
          981  +					continue;
          982  +				} else if (p[2] == '-' && p[3] == 0) {
          983  +					/* "---" has been passed; all further arguments must be
          984  +					 * interpreted as parameters, not flags or commands */
          985  +					reading_mode = reading_params;
          986  +					continue;
          987  +				} else longflag = true;
          988  +			} else longflag = false;
          989  +			_say(debug, _t("parsing flag") _t(p));
          990  +			for (size_t o = 1; p[o] != 0; ++o) {
          991  +				atom option = atom_none;
          992  +
          993  +				if (longflag) option = atomize(p+2);
          994  +				else { /* short flag */
          995  +					_say(debug, _t("parsing short flag") _t(p[o]));
          996  +					for (size_t j = 0; j < sz(flags); ++ j)
          997  +						if (flags[j].kind != flag_none && flags[j].shortname == p[o]) {
          998  +							option = (atom)j; break;
          999  +						}
         1000  +				}
         1001  +
         1002  +				if (option == atom_none) {
         1003  +					if (!longflag && p[o] == '-') {
         1004  +						if (p[o+1] == '-' && p[o+2] == 0) {
         1005  +							/* shorthand for --- */
         1006  +							reading_mode = reading_params;
         1007  +							continue;
         1008  +						} else if (p[o+1] == 0) {
         1009  +							/* shorthand for -- */
         1010  +							reading_mode = reading_commands;
         1011  +							continue;
         1012  +						}
         1013  +					}
         1014  +					char ftext[3] = {'-',p[o],0};
         1015  +					_say(fatal, _t("flag") _t(longflag ? p+2 : ftext) _t("not meaningful"));
         1016  +					goto parse_fail;
         1017  +				}
         1018  +
         1019  +				handle_flag:;
         1020  +					struct flag* f = flags + option;
         1021  +					void* fld = ((unsigned char*)&c) + (unsigned char)(f -> offset);
         1022  +					if (f -> kind > _flag_param) {
         1023  +						if (f->kind>=flag_value) {
         1024  +							*((unsigned*)fld) = f->kind - flag_value;
         1025  +						} else paramstack[stackptr++] = f;
         1026  +					} else if (f -> kind > _flag_noparam) {
         1027  +						switch(f -> kind) {
         1028  +							case flag_on:  *((bool*)fld) = true; break;
         1029  +							case flag_off: *((bool*)fld) = false; break;
         1030  +							case flag_switch: *((bool*)fld) = !*((bool*)fld); break;
         1031  +							/* in order to avoid creepy behavior in pathological cases,
         1032  +							 * we enforce saturation arithmetic for the increment/decrement
         1033  +							 * flag types */
         1034  +							case flag_inc: {
         1035  +								unsigned* v = fld;
         1036  +								if (*v < UINT_MAX) ++*v;
         1037  +							} break;
         1038  +							case flag_dec: {
         1039  +								unsigned* v = fld;
         1040  +								if (*v > 0) --*v;
         1041  +							} break;
         1042  +							case flag_sdec: {
         1043  +								signed* v = fld;
         1044  +								if (*v > INT_MIN) --*v;
         1045  +							} break;
         1046  +						}
         1047  +					}
         1048  +
         1049  +				if (p[1] == '-') break;
         1050  +			}
         1051  +			continue;
         1052  +		}
         1053  +
         1054  +		if (stackptr > 0) {
         1055  +			struct flag* f = paramstack[--stackptr];
         1056  +			_say(debug, _t("handling argument for flag") _t(reconstitute(f - flags)));
         1057  +			void* fld = ((unsigned char*)&c) + (unsigned char)(f -> offset);
         1058  +			switch(f -> kind) {
         1059  +				case flag_str: *((const char**)fld) = p; break;
         1060  +				case flag_uint: {
         1061  +					iaia_error_type e = int_of_str(10, p, (unsigned long long*)fld);
         1062  +					switch (e) {
         1063  +						case iaia_e_ok: continue;
         1064  +						case iaia_e_domain: 
         1065  +							_say(fatal, _t("argument") _t(p) _t("outside domain for base"));
         1066  +					}
         1067  +					return 5;
         1068  +				} break;
         1069  +				case flag_atom: case flag_raw_atom: {
         1070  +					atom a = atomize(p);
         1071  +					if (a == atom_none || a < f -> opts.first || a > f -> opts.last) {
         1072  +						_say(fatal, _t("invalid argument") _t(p) _t("for flag")
         1073  +								_t(reconstitute(f - flags)));
         1074  +						goto parse_fail;
         1075  +					}
         1076  +					*((atom*)fld) = f->kind == flag_raw_atom ? a : a - f -> opts.first;
         1077  +				} break;
         1078  +
         1079  +			}
         1080  +			continue;
         1081  +		}
         1082  +
         1083  +		if (reading_mode >= reading_commands) {
         1084  +			if (p[0] == '=') { /* parameter even if atomizable */
         1085  +				paramlist[paramcount] = p + 1;
         1086  +				cmdlist[c.cmdc] = _atom_n + paramcount;
         1087  +				++c.cmdc; ++ paramcount;
         1088  +				continue;
         1089  +			} else {
         1090  +				atom a = atomize(p);
         1091  +				if (a != atom_none) {
         1092  +					cmdlist[c.cmdc] = a;
         1093  +					++ c.cmdc;
         1094  +					continue;
         1095  +				}
         1096  +			}
         1097  +		}
         1098  +
         1099  +		/* only parameters remain to be read */
         1100  +		paramlist[paramcount] = p;
         1101  +		cmdlist[c.cmdc] = _atom_n + paramcount;
         1102  +		++ c.cmdc; ++ paramcount;
         1103  +	}
         1104  +	
         1105  +	parse_done: _say(debug, _t("stored") _t(c.cmdc) _t("arguments and") _t(paramcount) _t("parameters"));
         1106  +	
         1107  +	if (c.flags.noise <= noise_debug) for (size_t i = 0; i<c.cmdc; ++i) {
         1108  +		if (cmdlist[i] < _atom_n) {
         1109  +			dprintf(2, "\t%llu. atom %u (normal form “%s”)\n", i, cmdlist[i], reconstitute(cmdlist[i]));
         1110  +		} else {
         1111  +			dprintf(2,"\t%llu. parameter “%s”\n", i, paramlist[cmdlist[i] - _atom_n]);
         1112  +		}
         1113  +	}
         1114  +
         1115  +	if (c.flags.fmtv >= sz(write_privkey)) {
         1116  +		_say(fatal, _t("data format version") _t(c.flags.fmtv) _t("is not known. the highest available version is") _t(sz(write_privkey) - 1) _t("- are you sure you're using the latest release of " _self_name "?"));
         1117  +		return -2;
         1118  +	}
         1119  +	int err;
         1120  +	if (c.flags.pwfile != NULL) {
         1121  +		err = open_file(c,c.flags.pwfile, O_RDONLY, &c.flags.pwfd);
         1122  +		if(err) return err;
         1123  +	}
         1124  +	if (c.flags.infile != NULL) {
         1125  +		err = open_file(c,c.flags.infile, O_RDONLY, &c.flags.infd);
         1126  +		if(err) return err;
         1127  +	}
         1128  +	if (c.flags.outfile != NULL) {
         1129  +		err = open_file(c,c.flags.outfile, O_WRONLY | O_TRUNC | O_CREAT, &c.flags.outfd);
         1130  +		if(err) return err;
         1131  +	}
         1132  +	if (c.flags.sigfile != NULL) {
         1133  +		err = open_file(c,c.flags.sigfile, O_RDONLY, &c.flags.sigfd);
         1134  +		if(err) return err;
         1135  +	}
         1136  +
         1137  +
         1138  +	if (c.flags.color == feature_auto)
         1139  +		c.flags.color = isatty(2);
         1140  +	if (c.flags.ascii_armor == feature_auto)
         1141  +		c.flags.ascii_armor = isatty(c.flags.outfd);
         1142  +
         1143  +	/* this is hateful but i can't figure out a better way */
         1144  +	if (c.flags.pw_force_tty && !isatty(c.flags.pwfd)) {
         1145  +		_say(debug, _t("passphrases cannot be read from fd") _t(c.flags.pwfd) _t("as it is not a tty and -W was passed;") _t("/dev/tty") _t("will be used instead"));
         1146  +	} else if (c.flags.pwfd == c.flags.infd) {
         1147  +		_say(debug, _t("passphrases cannot be read from fd") _t(c.flags.pwfd) _t("as input data is already being read from that descriptor;") _t("/dev/tty") _t("will be used instead"));
         1148  +	} else if (c.flags.pwfd == c.flags.sigfd) {
         1149  +		_say(warn, _t("passphrases cannot be read from fd") _t(c.flags.pwfd) _t("as the signature is already being read from that descriptor;") _t("/dev/tty") _t("will be used instead"));
         1150  +	} else goto skip_pwfd_change;
         1151  +
         1152  +	pwfd_change: {
         1153  +		err = open_file(c,"/dev/tty", O_WRONLY, &c.flags.pwfd);
         1154  +		if(err) return err;
         1155  +	}
         1156  +
         1157  +	skip_pwfd_change:
         1158  +
         1159  +	_say(debug, _t("input fd =") _t(c.flags.infd));
         1160  +	_say(debug, _t("output fd =") _t(c.flags.outfd));
         1161  +	_say(debug, _t("pw fd =") _t(c.flags.pwfd));
         1162  +
         1163  +	if (c.cmdc > 0) return route(c);
         1164  +
         1165  +	parse_fail: if (c.flags.noise < noise_silent) usage(c,NULL);
         1166  +	            return 1;
         1167  +}

Modified tenki/tenki.c from [b2cd504148] to [be25cce95d].

    72     72   #else
    73     73   #	define _http "?"
    74     74   #endif
    75     75   #define http _http "lang=" ds_lang " HTTP/1.1\r\nHost: api.darksky.net\r\n\r\n"
    76     76   #define start "GET /forecast/" ds_apikey "/"
    77     77   
    78     78   #define min(x,y) (x>y?y:x)
    79         -#define len(x) (sizeof(x)/sizeof(*x))
           79  +#define len(x) (sizeof(x)/sizeof(x)[0])
    80     80   
    81     81   typedef enum bool { false, true } bool;
    82     82   
    83     83   SSL_CTX* sc;
    84     84   bool head(size_t* len, char* txt, char** body) {
    85     85   	char* hend = strstr(txt, "\r\n\r\n");
    86     86   	if (hend == NULL) return false;
................................................................................
   186    186   	size_t len = *key++;
   187    187   	for (size_t i = 0; i<len; ++i) {
   188    188   		printf("/%.*s",*key,key+1);
   189    189   		key += *key + 1;
   190    190   	};
   191    191   	printf("\n");
   192    192   } */
   193         -void pplt(const char* str, const char* const end, const char** keys, const char** const keyend, jsv* values) {
          193  +void pplt
          194  +(	const char*        str,
          195  +	const char*  const end,
          196  +	const char**       keys,
          197  +	const char** const keyend,
          198  +	jsv* values
          199  +) {
   194    200   	// initialize parser state
   195    201   	size_t depth = 0;
   196    202   	const char* fld = NULL;
   197    203   	const char* path[32];
   198    204   	path[0] = NULL;
   199    205   	
   200    206   	while(*str++!='{'); //cheat
................................................................................
   365    371   			
   366    372   			pstr summary = values[0].s;
   367    373   			float pcpint = values[1].f,
   368    374   				temp = values[2].f,
   369    375   				windspd = values[3].f;
   370    376   
   371    377   			printf("%.1f°  %%{F#ff9bbb}%.*s\n",
   372         -					summary.sz,
   373         -					summary.a,
   374         -					temp);
          378  +					temp,
          379  +					(int /*throwing table emoji*/)summary.sz,
          380  +					summary.a);
   375    381   		}
   376    382   		// dark sky's API allows us to make 1000 free requests a day.
   377    383   		// let's try not to get too near that.
   378    384   		// hours in day 24 - minutes in day 24 * 60 = 1440
   379    385   		// so once a minute is almost half again too many
   380    386   		// 1440 / 2 = 720 leaving reasonable refresh time without
   381    387   		// going too near the limit. so we aim for one update
   382    388   		// every two minutes.
   383    389   	//	sleep(60 * 2);
   384    390   //	}
   385    391   
   386    392   	return 0;
   387    393   }