Home | History | Annotate | Line # | Download | only in db
      1 /*	$NetBSD: db.c,v 1.28 2022/08/10 00:28:00 charlotte Exp $	*/
      2 
      3 /*-
      4  * Copyright (c) 2002-2009 The NetBSD Foundation, Inc.
      5  * All rights reserved.
      6  *
      7  * This code is derived from software contributed to The NetBSD Foundation
      8  * by Luke Mewburn of Wasabi Systems.
      9  *
     10  * Redistribution and use in source and binary forms, with or without
     11  * modification, are permitted provided that the following conditions
     12  * are met:
     13  * 1. Redistributions of source code must retain the above copyright
     14  *    notice, this list of conditions and the following disclaimer.
     15  * 2. Redistributions in binary form must reproduce the above copyright
     16  *    notice, this list of conditions and the following disclaimer in the
     17  *    documentation and/or other materials provided with the distribution.
     18  *
     19  * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
     20  * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
     21  * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
     22  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
     23  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
     24  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
     25  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
     26  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
     27  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
     28  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
     29  * POSSIBILITY OF SUCH DAMAGE.
     30  */
     31 
     32 #if HAVE_NBTOOL_CONFIG_H
     33 #include "nbtool_config.h"
     34 #endif
     35 
     36 #include <sys/cdefs.h>
     37 #ifndef lint
     38 #ifdef __RCSID
     39 __RCSID("$NetBSD: db.c,v 1.28 2022/08/10 00:28:00 charlotte Exp $");
     40 #endif /* __RCSID */
     41 #endif /* not lint */
     42 
     43 #include <ctype.h>
     44 #include <db.h>
     45 #include <err.h>
     46 #include <fcntl.h>
     47 #include <limits.h>
     48 #include <stdio.h>
     49 #include <stdlib.h>
     50 #include <string.h>
     51 #include <unistd.h>
     52 #include <vis.h>
     53 
     54 
     55 typedef enum {
     56 	F_WRITE		= 1<<0,
     57 	F_DELETE	= 1<<1,
     58 	F_SHOW_KEY	= 1<<2,
     59 	F_SHOW_VALUE	= 1<<3,
     60 	F_QUIET		= 1<<10,
     61 	F_IGNORECASE	= 1<<11,
     62 	F_ENDIAN_BIG	= 1<<12,
     63 	F_ENDIAN_LITTLE	= 1<<13,
     64 	F_INCLUDE_NUL	= 1<<14,
     65 	F_CREATENEW	= 1<<20,
     66 	F_DUPLICATES	= 1<<21,
     67 	F_REPLACE	= 1<<22,
     68 	F_ENCODE_KEY	= 1<<23,
     69 	F_ENCODE_VAL	= 1<<24,
     70 	F_DECODE_KEY	= 1<<25,
     71 	F_DECODE_VAL	= 1<<26,
     72 } flags_t;
     73 
     74 static void	db_print(DBT *, DBT *);
     75 static int	db_dump(void);
     76 static int	db_del(char *);
     77 static int	db_get(char *);
     78 static int	db_seq(char *);
     79 static int	db_put(char *, char *);
     80 static int	parseline(FILE *, const char *, char **, char **);
     81 static int	encode_data(size_t, char *, char **);
     82 static int	decode_data(char *, char **);
     83 static void	parse_encode_decode_arg(const char *, int);
     84 static int	parse_encode_option(char **);
     85 __dead static void	usage(void);
     86 
     87 static flags_t	 	 flags = 0;
     88 static DB		*db;
     89 static const char	*outputsep = "\t";
     90 static int		 visflags = 0;
     91 static const char	*extra_echars = NULL;
     92 
     93 int
     94 main(int argc, char *argv[])
     95 {
     96 	struct {
     97 		char		*file;
     98 		char		*type;
     99 		DBTYPE	 	dbtype;
    100 		void		*info;
    101 		int	 	dbflags;
    102 		mode_t	 	mode;
    103 		unsigned int	pagesize;
    104 	} oi;
    105 	BTREEINFO	btreeinfo;
    106 	HASHINFO	hashinfo;
    107 	FILE		*infp;
    108 	const char	*infile, *fieldsep;
    109 	char		*p, *key, *val;
    110 	int		ch, rv;
    111 	long		lval;
    112 
    113 	setprogname(argv[0]);
    114 
    115 	infile = NULL;
    116 	fieldsep = " ";
    117 	infp = NULL;
    118 	memset(&oi, 0, sizeof(oi));
    119 	oi.mode = 0644;
    120 	oi.pagesize = 4096;
    121 
    122 				/* parse arguments */
    123 	while ( (ch = getopt(argc, argv,
    124 			     "CDdE:F:f:iKm:NO:P:qRS:T:U:VwX:")) != -1) {
    125 		switch (ch) {
    126 
    127 		case 'C':
    128 			flags |= F_CREATENEW;
    129 			break;
    130 
    131 		case 'D':
    132 			flags |= F_DUPLICATES;
    133 			break;
    134 
    135 		case 'd':
    136 			flags |= F_DELETE;
    137 			break;
    138 
    139 		case 'E':
    140 			if (! optarg[0] || optarg[1])
    141 				goto badendian;
    142 			switch (toupper((unsigned char)optarg[0])) {
    143 			case 'B':
    144 				flags |= F_ENDIAN_BIG;
    145 				break;
    146 			case 'L':
    147 				flags |= F_ENDIAN_LITTLE;
    148 				break;
    149 			case 'H':
    150 				flags &= ~(F_ENDIAN_BIG | F_ENDIAN_LITTLE);
    151 				break;
    152 			default:
    153  badendian:
    154 				errx(1, "Bad endian `%s'", optarg);
    155 			}
    156 			break;
    157 
    158 		case 'F':
    159 			if (! optarg[0])
    160 				errx(1, "Invalid field separator `%s'",
    161 				    optarg);
    162 			fieldsep = optarg;
    163 			break;
    164 
    165 		case 'f':
    166 			infile = optarg;
    167 			break;
    168 
    169 		case 'i':
    170 			flags |= F_IGNORECASE;
    171 			break;
    172 
    173 		case 'K':
    174 			flags |= F_SHOW_KEY;
    175 			break;
    176 
    177 		case 'm':
    178 			lval = strtol(optarg, &p, 8);
    179 			if (p == optarg || *p != '\0')
    180 				errx(1, "Invalid octal number `%s'", optarg);
    181 			if (lval < 0 || lval > 07777)
    182 				errx(1, "Invalid mode `%s'", optarg);
    183 			oi.mode = (mode_t)lval;
    184 			break;
    185 
    186 		case 'N':
    187 			flags |= F_INCLUDE_NUL;
    188 			break;
    189 
    190 		case 'O':
    191 			outputsep = optarg;
    192 			break;
    193 
    194 		case 'P':
    195 			lval = strtol(optarg, &p, 10);
    196 			if (p == optarg || *p != '\0')
    197 				errx(1, "Invalid pagesize `%s'", optarg);
    198 			if (lval < 0 || (unsigned int)lval >= UINT_MAX)
    199 				errx(1, "Pagesize `%s' out of range", optarg);
    200 			oi.pagesize = (unsigned int)lval;
    201 			break;
    202 
    203 		case 'q':
    204 			flags |= F_QUIET;
    205 			break;
    206 
    207 		case 'R':
    208 			flags |= F_REPLACE;
    209 			break;
    210 
    211 		case 'S':
    212 			parse_encode_decode_arg(optarg, 1 /* encode */);
    213 			if (! (flags & (F_ENCODE_KEY | F_ENCODE_VAL)))
    214 				errx(1, "Invalid encoding argument `%s'",
    215 				    optarg);
    216 			break;
    217 
    218 		case 'T':
    219 			visflags = parse_encode_option(&optarg);
    220 			if (! visflags)
    221 				errx(1, "Invalid encoding/decoding option `%s'",
    222 				    optarg);
    223 			break;
    224 
    225 		case 'U':
    226 			parse_encode_decode_arg(optarg, 0 /* decode */);
    227 			if (! (flags & (F_DECODE_KEY | F_DECODE_VAL)))
    228 				errx(1, "Invalid decoding argument `%s'",
    229 				    optarg);
    230 			break;
    231 
    232 		case 'V':
    233 			flags |= F_SHOW_VALUE;
    234 			break;
    235 
    236 		case 'w':
    237 			flags |= F_WRITE;
    238 			break;
    239 
    240 		case 'X':
    241 			extra_echars = optarg;
    242 			break;
    243 
    244 		default:
    245 			usage();
    246 
    247 		}
    248 	}
    249 	argc -= optind;
    250 	argv += optind;
    251 
    252 				/* validate arguments */
    253 	if (argc < 2)
    254 		usage();
    255 	oi.type = argv[0];
    256 	oi.file = argv[1];
    257 	argc -= 2;
    258 	argv += 2;
    259 
    260 	if (flags & F_WRITE) {
    261 		if (flags & (F_SHOW_KEY | F_SHOW_VALUE | F_DELETE))
    262 			usage();
    263 		if ((!infile && argc < 2) || (argc % 2))
    264 			usage();
    265 		if (0 != (visflags & ~(VIS_HTTPSTYLE)))
    266 			errx(1, "Unsupported decoding option provided to -T");
    267 		oi.dbflags = O_RDWR | O_CREAT | O_EXLOCK;
    268 		if (flags & F_CREATENEW)
    269 			oi.dbflags |= O_TRUNC;
    270 	} else if (flags & F_DELETE) {
    271 		if (flags & (F_SHOW_KEY | F_SHOW_VALUE | F_WRITE))
    272 			usage();
    273 		if (!infile && argc < 1)
    274 			usage();
    275 		if (0 != (visflags & ~(VIS_HTTPSTYLE)))
    276 			errx(1, "Unsupported decoding option provided to -T");
    277 		oi.dbflags = O_RDWR | O_CREAT | O_EXLOCK;
    278 	} else {
    279 		if (! (flags & (F_SHOW_KEY | F_SHOW_VALUE)))
    280 			flags |= (F_SHOW_KEY | F_SHOW_VALUE);
    281 		oi.dbflags = O_RDONLY | O_SHLOCK;
    282 	}
    283 
    284 				/* validate oi.type */
    285 	if (strcmp(oi.type, "btree") == 0) {
    286 		memset(&btreeinfo, 0, sizeof(btreeinfo));
    287 		if (flags & F_ENDIAN_BIG)
    288 			btreeinfo.lorder = 4321;
    289 		else if (flags & F_ENDIAN_LITTLE)
    290 			btreeinfo.lorder = 1234;
    291 		if (flags & F_DUPLICATES)
    292 			btreeinfo.flags = R_DUP;
    293 		btreeinfo.psize = oi.pagesize;
    294 		btreeinfo.cachesize = 1024 * 1024;
    295 		oi.info = &btreeinfo;
    296 		oi.dbtype = DB_BTREE;
    297 	} else if (strcmp(oi.type, "hash") == 0) {
    298 		memset(&hashinfo, 0, sizeof(hashinfo));
    299 		if (flags & F_ENDIAN_BIG)
    300 			hashinfo.lorder = 4321;
    301 		else if (flags & F_ENDIAN_LITTLE)
    302 			hashinfo.lorder = 1234;
    303 		hashinfo.bsize = oi.pagesize;
    304 		hashinfo.cachesize = 1024 * 1024;
    305 		oi.info = &hashinfo;
    306 		oi.dbtype = DB_HASH;
    307 	} else {
    308 		warnx("Unknown database type `%s'", oi.type);
    309 		usage();
    310 	}
    311 
    312 	if (infile) {
    313 		if (strcmp(infile, "-") == 0)
    314 			infp = stdin;
    315 		else if ((infp = fopen(infile, "r")) == NULL)
    316 			err(1, "Opening input file `%s'", infile);
    317 	}
    318 
    319 				/* open database */
    320 	db = dbopen(oi.file, oi.dbflags, oi.mode, oi.dbtype, oi.info);
    321 	if (db == NULL)
    322 		err(1, "Opening database `%s'", oi.file);
    323 
    324 
    325 				/* manipulate database */
    326 	rv = 0;
    327 	if (flags & F_WRITE) {			/* write entries */
    328 		for (ch = 0; ch < argc; ch += 2)
    329 			if ((rv = db_put(argv[ch], argv[ch+1])))
    330 				goto cleanup;
    331 		if (infp) {
    332 			while (parseline(infp, fieldsep, &key, &val)) {
    333 				if ((rv = db_put(key, val)))
    334 					goto cleanup;
    335 			}
    336 			if (ferror(infp)) {
    337 				warnx("Reading `%s'", infile);
    338 				goto cleanup;
    339 			}
    340 		}
    341 	} else if (!infp && argc == 0) {	/* read all */
    342 		db_dump();
    343 	} else {				/* read/delete specific */
    344 		int	(*dbop)(char *);
    345 
    346 		if (flags & F_DELETE)
    347 			dbop = db_del;
    348 		else if (DB_BTREE == oi.dbtype)
    349 			dbop = db_seq;
    350 		else if (DB_HASH == oi.dbtype)
    351 			dbop = db_get;
    352 		else
    353 			errx(5, "internal error: unsupported dbtype %d",
    354 			    oi.dbtype);
    355 		for (ch = 0; ch < argc; ch++) {
    356 			if ((rv = dbop(argv[ch])))
    357 				goto cleanup;
    358 		}
    359 		if (infp) {
    360 			while (parseline(infp, fieldsep, &key, NULL)) {
    361 				if ((rv = dbop(key)))
    362 					goto cleanup;
    363 			}
    364 			if (ferror(infp)) {
    365 				warnx("Reading `%s'", infile);
    366 				goto cleanup;
    367 			}
    368 		}
    369 	}
    370 
    371 				/* close database */
    372  cleanup:
    373 	if (db->close(db) == -1)
    374 		err(1, "Closing database `%s'", oi.file);
    375 	if (infp)
    376 		fclose(infp);
    377 	return (rv);
    378 }
    379 
    380 static void
    381 db_print(DBT *key, DBT *val)
    382 {
    383 	int	len;
    384 	char	*data;
    385 
    386 #define	MINUSNUL(x) ((x) > 0  ?  (x) - (flags & F_INCLUDE_NUL ? 0 : 1)  :  0)
    387 
    388 	if (flags & F_SHOW_KEY) {
    389 		if (flags & F_ENCODE_KEY) {
    390 			len = encode_data(MINUSNUL(key->size),
    391 			    (char *)key->data, &data);
    392 		} else {
    393 			len = (int)MINUSNUL(key->size);
    394 			data = (char *)key->data;
    395 		}
    396 		printf("%.*s", len, data);
    397 	}
    398 	if ((flags & F_SHOW_KEY) && (flags & F_SHOW_VALUE))
    399 		printf("%s", outputsep);
    400 	if (flags & F_SHOW_VALUE) {
    401 		if (flags & F_ENCODE_VAL) {
    402 			len = encode_data(MINUSNUL(val->size),
    403 			    (char *)val->data, &data);
    404 		} else {
    405 			len = (int)MINUSNUL(val->size);
    406 			data = (char *)val->data;
    407 		}
    408 		printf("%.*s", len, data);
    409 	}
    410 	printf("\n");
    411 }
    412 
    413 static int
    414 db_dump(void)
    415 {
    416 	DBT	key, val;
    417 	int	rv;
    418 
    419 	while ((rv = db->seq(db, &key, &val, R_NEXT)) == 0)
    420 		db_print(&key, &val);
    421 	if (rv == -1)
    422 		warn("Error dumping database");
    423 	return (rv == 1 ? 0 : 1);
    424 }
    425 
    426 static void
    427 db_makekey(DBT *key, char *keystr, int downcase, int decode)
    428 {
    429 	char	*p, *ks;
    430 	int	klen;
    431 
    432 	memset(key, 0, sizeof(*key));
    433 	if (decode) {
    434 		if ((klen = decode_data(keystr, &ks)) == -1)
    435 			errx(1, "Invalid escape sequence in `%s'", keystr);
    436 	} else {
    437 		klen = strlen(keystr);
    438 		ks = keystr;
    439 	}
    440 	key->data = ks;
    441 	key->size = klen + (flags & F_INCLUDE_NUL ? 0 : 1);
    442 	if (downcase && (flags & F_IGNORECASE)) {
    443 		for (p = ks; *p; p++)
    444 			if (isupper((unsigned char)*p))
    445 				*p = tolower((unsigned char)*p);
    446 	}
    447 }
    448 
    449 static int
    450 db_del(char *keystr)
    451 {
    452 	DBT	key;
    453 	int	r;
    454 
    455 	db_makekey(&key, keystr, 1, (flags & F_DECODE_KEY ? 1 : 0));
    456 	r = db->del(db, &key, 0);
    457 	switch (r) {
    458 	case -1:
    459 		if (! (flags & F_QUIET))
    460 			warn("Error deleting key `%s'", keystr);
    461 		r = 1;
    462 		break;
    463 	case 0:
    464 		if (! (flags & F_QUIET))
    465 			printf("Deleted key `%s'\n", keystr);
    466 		break;
    467 	case 1:
    468 		if (! (flags & F_QUIET))
    469 			warnx("Unknown key `%s'", keystr);
    470 		break;
    471 	default:
    472 		errx(5, "%s: unexpected result %d from db", __func__, r);
    473 	}
    474 	if (flags & F_DECODE_KEY)
    475 		free(key.data);
    476 	return (r);
    477 }
    478 
    479 static int
    480 db_get(char *keystr)
    481 {
    482 	DBT	key, val;
    483 	int	r;
    484 
    485 	db_makekey(&key, keystr, 1, (flags & F_DECODE_KEY ? 1 : 0));
    486 
    487 	r = db->get(db, &key, &val, 0);
    488 	switch (r) {
    489 	case -1:
    490 		warn("Error reading key `%s'", keystr);
    491 		r = 1;
    492 		break;
    493 	case 0:
    494 		db_print(&key, &val);
    495 		break;
    496 	case 1:
    497 		if (! (flags & F_QUIET)) {
    498 			warnx("Unknown key `%s'", keystr);
    499 		}
    500 		break;
    501 	default:
    502 		errx(5, "%s: unexpected result %d from db", __func__, r);
    503 	}
    504 	if (flags & F_DECODE_KEY)
    505 		free(key.data);
    506 	return (r);
    507 }
    508 
    509 static int
    510 db_seq(char *keystr)
    511 {
    512 	DBT	key, val, want;
    513 	int	r, found;
    514 	u_int	seqflags;
    515 
    516 	db_makekey(&key, keystr, 1, (flags & F_DECODE_KEY ? 1 : 0));
    517 		/* remember key in want, since db->seq() changes key */
    518 	want.data = key.data;
    519 	want.size = key.size;
    520 
    521 	found = 0;
    522 	seqflags = R_CURSOR;
    523 	while ((r = db->seq(db, &key, &val, seqflags)) == 0) {
    524 		if (key.size != want.size ||
    525 		    0 != strcmp((char *)key.data, (char *)want.data)) {
    526 			r = 1;
    527 			break;
    528 		}
    529 		seqflags = R_NEXT;
    530 		found++;
    531 		db_print(&key, &val);
    532 		if (! (flags & F_DUPLICATES))
    533 			break;
    534 	}
    535 
    536 	switch (r) {
    537 	case -1:
    538 		warn("Error reading key `%s'", keystr);
    539 		r = 1;
    540 		break;
    541 	case 0:
    542 		break;
    543 	case 1:
    544 		if (found) {
    545 			r = 0;
    546 			break;
    547 		}
    548 		if (! (flags & F_QUIET)) {
    549 			warnx("Unknown key `%s'", keystr);
    550 		}
    551 		break;
    552 	default:
    553 		errx(5, "%s: unexpected result %d from db", __func__, r);
    554 	}
    555 	if (flags & F_DECODE_KEY)
    556 		free(want.data);
    557 	return (r);
    558 }
    559 
    560 static int
    561 db_put(char *keystr, char *valstr)
    562 {
    563 	DBT	key, val;
    564 	int	r = 0;
    565 
    566 	db_makekey(&key, keystr, 1, (flags & F_DECODE_KEY ? 1 : 0));
    567 	db_makekey(&val, valstr, 0, (flags & F_DECODE_VAL ? 1 : 0));
    568 	r = db->put(db, &key, &val, (flags & F_REPLACE) ? 0 : R_NOOVERWRITE);
    569 	switch (r) {
    570 	case -1:
    571 		warn("Error writing key `%s'", keystr);
    572 		r = 1;
    573 		break;
    574 	case 0:
    575 		if (! (flags & F_QUIET))
    576 			printf("Added key `%s'\n", keystr);
    577 		break;
    578 	case 1:
    579 		if (! (flags & F_QUIET))
    580 			warnx("Key `%s' already exists", keystr);
    581 		break;
    582 	default:
    583 		errx(5, "Unexpected result %d in %s", r, __func__);
    584 	}
    585 	if (flags & F_DECODE_KEY)
    586 		free(key.data);
    587 	if (flags & F_DECODE_VAL)
    588 		free(val.data);
    589 	return (r);
    590 }
    591 
    592 static int
    593 parseline(FILE *fp, const char *sep, char **kp, char **vp)
    594 {
    595 	size_t	len;
    596 	char	*key, *val;
    597 
    598 	key = fgetln(fp, &len);
    599 	if (key == NULL)		/* end of file, or error */
    600 		return (0);
    601 
    602 	if (key[len-1] == '\n')		/* check for \n at EOL */
    603 		key[--len] = '\0';
    604 	else
    605 		return (0);
    606 
    607 	*kp = key;
    608 	if (vp == NULL)			/* don't split if don't want value */
    609 		return (1);
    610 	if ((val = strstr(key, sep)) == NULL)
    611 		val = key + len;
    612 	else {
    613 		*val = '\0';
    614 		val += strlen(sep);
    615 	}
    616 	*vp = val;
    617 	return (1);
    618 }
    619 
    620 static int
    621 encode_data(size_t len, char *data, char **edata)
    622 {
    623 	static char	*buf = NULL;
    624 	char		*nbuf;
    625 	static size_t	buflen = 0;
    626 	size_t		elen;
    627 
    628 	elen = 1 + (len * 4);
    629 	if (elen > buflen) {
    630 		if ((nbuf = realloc(buf, elen)) == NULL)
    631 			err(1, "Cannot allocate encoding buffer");
    632 		buf = nbuf;
    633 		buflen = elen;
    634 	}
    635 	*edata = buf;
    636 	if (extra_echars) {
    637 		return (strsvisx(buf, data, len, visflags, extra_echars));
    638 	} else {
    639 		return (strvisx(buf, data, len, visflags));
    640 	}
    641 }
    642 
    643 static int
    644 decode_data(char *data, char **ddata)
    645 {
    646 	char	*buf;
    647 
    648 	if ((buf = malloc(strlen(data) + 1)) == NULL)
    649 		err(1, "Cannot allocate decoding buffer");
    650 	*ddata = buf;
    651 	return (strunvisx(buf, data, (visflags & VIS_HTTPSTYLE)));
    652 }
    653 
    654 static void
    655 parse_encode_decode_arg(const char *arg, int encode)
    656 {
    657 	if (! arg[0] || arg[1])
    658 		return;
    659 	if (arg[0] == 'k' || arg[0] == 'b') {
    660 		if (encode)
    661 			flags |= F_ENCODE_KEY;
    662 		else
    663 			flags |= F_DECODE_KEY;
    664 	}
    665 	if (arg[0] == 'v' || arg[0] == 'b') {
    666 		if (encode)
    667 			flags |= F_ENCODE_VAL;
    668 		else
    669 			flags |= F_DECODE_VAL;
    670 	}
    671 	return;
    672 }
    673 
    674 static int
    675 parse_encode_option(char **arg)
    676 {
    677 	int	r = 0;
    678 	int	encmask = ~(VIS_CSTYLE | VIS_HTTPSTYLE | VIS_OCTAL);
    679 
    680 	for(; **arg; (*arg)++) {
    681 		switch (**arg) {
    682 			case 'b':
    683 				r |= VIS_NOSLASH;
    684 				break;
    685 			case 'c':
    686 				r &= encmask;
    687 				r |= VIS_CSTYLE;
    688 				break;
    689 			case 'h':
    690 				r &= encmask;
    691 				r |= VIS_HTTPSTYLE;
    692 				break;
    693 			case 'o':
    694 				r &= encmask;
    695 				r |= VIS_OCTAL;
    696 				break;
    697 			case 's':
    698 				r |= VIS_SAFE;
    699 				break;
    700 			case 't':
    701 				r |= VIS_TAB;
    702 				break;
    703 			case 'w':
    704 				r |= VIS_WHITE;
    705 				break;
    706 			default:
    707 				return (0);
    708 		}
    709 	}
    710 	return (r);
    711 }
    712 
    713 static void
    714 usage(void)
    715 {
    716 	const char *p = getprogname();
    717 
    718 	fprintf(stderr,
    719 "usage: %s    [-DKiNqV] [-E endian] [-f infile] [-O outsep] [-S visitem]\n"
    720 "             [-T visspec] [-U unvisitem] [-X extravis] type dbfile [key [...]]\n"
    721 "       %s -d [-iNq] [-E endian] [-f infile] [-T visspec] [-U unvisitem]\n"
    722 "             type dbfile [key [...]]\n"
    723 "       %s -w [-CDiNqR] [-E endian] [-F isep] [-f infile] [-m mode]\n"
    724 "             [-P pagesize] [-T visspec] [-U unvisitem]\n"
    725 "             type dbfile [key value [...]]\n"
    726 	    ,p ,p ,p );
    727 	fprintf(stderr,
    728 "Supported modes:\n"
    729 "                read keys  [default]\n"
    730 "   -d           delete keys\n"
    731 "   -w           write (add) keys/values\n"
    732 "Supported options:\n"
    733 "   -C           create empty (truncated) database\n"
    734 "   -D           allow duplicates\n"
    735 "   -E endian    database endian: `B'ig, `L'ittle, `H'ost  [default: H]\n"
    736 "   -F isep      input field separator string  [default: a space]\n"
    737 "   -f infile    file of keys (read|delete) or keys/vals (write)\n"
    738 "   -i           ignore case of key by converting to lower case\n"
    739 "   -K           print key\n"
    740 "   -m mode      mode of created database  [default: 0644]\n"
    741 "   -N           don't NUL terminate key\n"
    742 "   -O outsep    output field separator string  [default: a tab]\n"
    743 "   -P pagesize  database page size  [default: 4096]\n"
    744 "   -q           quiet operation\n"
    745 "   -R           replace existing keys\n"
    746 "   -S visitem   items to strvis(3) encode: 'k'ey, 'v'alue, 'b'oth\n"
    747 "   -T visspec   options to control -S and -U; like vis(1) options\n"
    748 "   -U unvisitem items to strunvis(3) decode: 'k'ey, 'v'alue, 'b'oth\n"
    749 "   -V           print value\n"
    750 "   -X extravis  extra characters to encode with -S\n"
    751 	    );
    752 	exit(1);
    753 }
    754