Home | History | Annotate | Line # | Download | only in db
db.c revision 1.23
      1 /*	$NetBSD: db.c,v 1.23 2009/01/18 01:04:34 lukem Exp $	*/
      2 
      3 /*-
      4  * Copyright (c) 2002-2008 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.23 2009/01/18 01:04:34 lukem 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_NO_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 int	main(int, char *[]);
     75 void	db_print(DBT *, DBT *);
     76 int	db_dump(void);
     77 int	db_del(char *);
     78 int	db_get(char *);
     79 int	db_put(char *, char *);
     80 int	parseline(FILE *, const char *, char **, char **);
     81 int	encode_data(size_t, char *, char **);
     82 int	decode_data(char *, char **);
     83 void	parse_encode_decode_arg(const char *, int);
     84 int	parse_encode_option(char **);
     85 void	usage(void);
     86 
     87 flags_t	 	 flags = 0;
     88 DB		*db;
     89 const char	*outputsep = "\t";
     90 int		 visflags = 0;
     91 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	 	flags;
    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((int)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_NO_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.flags = O_RDWR | O_CREAT | O_EXLOCK;
    268 		if (flags & F_CREATENEW)
    269 			oi.flags |= 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.flags = 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.flags = 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.flags, 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
    349 			dbop = db_get;
    350 		for (ch = 0; ch < argc; ch++) {
    351 			if ((rv = dbop(argv[ch])))
    352 				goto cleanup;
    353 		}
    354 		if (infp) {
    355 			while (parseline(infp, fieldsep, &key, NULL)) {
    356 				if ((rv = dbop(key)))
    357 					goto cleanup;
    358 			}
    359 			if (ferror(infp)) {
    360 				warnx("Reading `%s'", infile);
    361 				goto cleanup;
    362 			}
    363 		}
    364 	}
    365 
    366 				/* close database */
    367  cleanup:
    368 	if (db->close(db) == -1)
    369 		err(1, "Closing database `%s'", oi.file);
    370 	if (infp)
    371 		fclose(infp);
    372 	return (rv);
    373 }
    374 
    375 void
    376 db_print(DBT *key, DBT *val)
    377 {
    378 	int	len;
    379 	char	*data;
    380 
    381 #define	MINUSNUL(x)	((x) > 0  ?  (x) - (flags & F_NO_NUL ? 0 : 1)  :  0)
    382 
    383 	if (flags & F_SHOW_KEY) {
    384 		if (flags & F_ENCODE_KEY) {
    385 			len = encode_data(MINUSNUL(key->size),
    386 			    (char *)key->data, &data);
    387 		} else {
    388 			len = (int)MINUSNUL(key->size);
    389 			data = (char *)key->data;
    390 		}
    391 		printf("%.*s", len, data);
    392 	}
    393 	if ((flags & F_SHOW_KEY) && (flags & F_SHOW_VALUE))
    394 		printf("%s", outputsep);
    395 	if (flags & F_SHOW_VALUE) {
    396 		if (flags & F_ENCODE_VAL) {
    397 			len = encode_data(MINUSNUL(val->size),
    398 			    (char *)val->data, &data);
    399 		} else {
    400 			len = (int)MINUSNUL(val->size);
    401 			data = (char *)val->data;
    402 		}
    403 		printf("%.*s", len, data);
    404 	}
    405 	printf("\n");
    406 }
    407 
    408 int
    409 db_dump(void)
    410 {
    411 	DBT	key, val;
    412 	int	rv;
    413 
    414 	while ((rv = db->seq(db, &key, &val, R_NEXT)) == 0)
    415 		db_print(&key, &val);
    416 	if (rv == -1)
    417 		warn("Error dumping database");
    418 	return (rv == 1 ? 0 : 1);
    419 }
    420 
    421 static void
    422 db_makekey(DBT *key, char *keystr, int downcase, int decode)
    423 {
    424 	char	*p, *ks;
    425 	int	klen;
    426 
    427 	memset(key, 0, sizeof(*key));
    428 	if (decode) {
    429 		if ((klen = decode_data(keystr, &ks)) == -1)
    430 			errx(1, "Invalid escape sequence in `%s'", keystr);
    431 	} else {
    432 		klen = strlen(keystr);
    433 		ks = keystr;
    434 	}
    435 	key->data = ks;
    436 	key->size = klen + (flags & F_NO_NUL ? 0 : 1);
    437 	if (downcase && (flags & F_IGNORECASE)) {
    438 		for (p = ks; *p; p++)
    439 			if (isupper((int)*p))
    440 				*p = tolower((int)*p);
    441 	}
    442 }
    443 
    444 int
    445 db_del(char *keystr)
    446 {
    447 	DBT	key;
    448 	int	r;
    449 
    450 	db_makekey(&key, keystr, 1, (flags & F_DECODE_KEY ? 1 : 0));
    451 	r = db->del(db, &key, 0);
    452 	switch (r) {
    453 	case -1:
    454 		if (! (flags & F_QUIET))
    455 			warn("Error deleting key `%s'", keystr);
    456 		r = 1;
    457 		break;
    458 	case 0:
    459 		if (! (flags & F_QUIET))
    460 			printf("Deleted key `%s'\n", keystr);
    461 		break;
    462 	case 1:
    463 		if (! (flags & F_QUIET))
    464 			warnx("Unknown key `%s'", keystr);
    465 		break;
    466 	default:
    467 		abort();
    468 	}
    469 	if (flags & F_DECODE_KEY)
    470 		free(key.data);
    471 	return (r);
    472 }
    473 
    474 int
    475 db_get(char *keystr)
    476 {
    477 	DBT	key, val;
    478 	char	*wantkey;
    479 	int	r, found;
    480 	u_int	seqflags;
    481 
    482 	db_makekey(&key, keystr, 1, (flags & F_DECODE_KEY ? 1 : 0));
    483 	wantkey = strdup(key.data);
    484 	if (wantkey == NULL)
    485 		err(1, "Cannot allocate key buffer");
    486 
    487 	found = 0;
    488 	seqflags = R_CURSOR;
    489 	while ((r = db->seq(db, &key, &val, seqflags)) == 0) {
    490 		if (strcmp((char *)key.data, wantkey) != 0) {
    491 			r = 1;
    492 			break;
    493 		}
    494 		seqflags = R_NEXT;
    495 		found++;
    496 		db_print(&key, &val);
    497 		if (! (flags & F_DUPLICATES))
    498 			break;
    499 	}
    500 
    501 	switch (r) {
    502 	case -1:
    503 		warn("Error reading key `%s'", keystr);
    504 		r = 1;
    505 		break;
    506 	case 0:
    507 		break;
    508 	case 1:
    509 		if (found) {
    510 			r = 0;
    511 			break;
    512 		}
    513 		if (! (flags & F_QUIET)) {
    514 			warnx("Unknown key `%s'", keystr);
    515 		}
    516 		break;
    517 	default:
    518 		abort();
    519 	}
    520 	if (flags & F_DECODE_KEY)
    521 		free(key.data);
    522 	free(wantkey);
    523 	return (r);
    524 }
    525 
    526 int
    527 db_put(char *keystr, char *valstr)
    528 {
    529 	DBT	key, val;
    530 	int	r = 0;
    531 
    532 	db_makekey(&key, keystr, 1, (flags & F_DECODE_KEY ? 1 : 0));
    533 	db_makekey(&val, valstr, 0, (flags & F_DECODE_VAL ? 1 : 0));
    534 	r = db->put(db, &key, &val, (flags & F_REPLACE) ? 0 : R_NOOVERWRITE);
    535 	switch (r) {
    536 	case -1:
    537 		warn("Error writing key `%s'", keystr);
    538 		r = 1;
    539 		break;
    540 	case 0:
    541 		if (! (flags & F_QUIET))
    542 			printf("Added key `%s'\n", keystr);
    543 		break;
    544 	case 1:
    545 		if (! (flags & F_QUIET))
    546 			warnx("Key `%s' already exists", keystr);
    547 		break;
    548 	default:
    549 		abort();
    550 	}
    551 	if (flags & F_DECODE_KEY)
    552 		free(key.data);
    553 	if (flags & F_DECODE_VAL)
    554 		free(val.data);
    555 	return (r);
    556 }
    557 
    558 int
    559 parseline(FILE *fp, const char *sep, char **kp, char **vp)
    560 {
    561 	size_t	len;
    562 	char	*key, *val;
    563 
    564 	key = fgetln(fp, &len);
    565 	if (key == NULL)		/* end of file, or error */
    566 		return (0);
    567 
    568 	if (key[len-1] == '\n')		/* check for \n at EOL */
    569 		key[--len] = '\0';
    570 	else
    571 		return (0);
    572 
    573 	*kp = key;
    574 	if (vp == NULL)			/* don't split if don't want value */
    575 		return (1);
    576 	if ((val = strstr(key, sep)) == NULL)
    577 		val = key + len;
    578 	else {
    579 		*val = '\0';
    580 		val += strlen(sep);
    581 	}
    582 	*vp = val;
    583 	return (1);
    584 }
    585 
    586 int
    587 encode_data(size_t len, char *data, char **edata)
    588 {
    589 	static char	*buf = NULL;
    590 	char		*nbuf;
    591 	static size_t	buflen = 0;
    592 	size_t		elen;
    593 
    594 	elen = 1 + (len * 4);
    595 	if (elen > buflen) {
    596 		if ((nbuf = realloc(buf, elen)) == NULL)
    597 			err(1, "Cannot allocate encoding buffer");
    598 		buf = nbuf;
    599 		buflen = elen;
    600 	}
    601 	*edata = buf;
    602 	if (extra_echars) {
    603 		return (strsvisx(buf, data, len, visflags, extra_echars));
    604 	} else {
    605 		return (strvisx(buf, data, len, visflags));
    606 	}
    607 }
    608 
    609 int
    610 decode_data(char *data, char **ddata)
    611 {
    612 	char	*buf;
    613 
    614 	if ((buf = malloc(strlen(data) + 1)) == NULL)
    615 		err(1, "Cannot allocate decoding buffer");
    616 	*ddata = buf;
    617 	return (strunvisx(buf, data, (visflags & VIS_HTTPSTYLE)));
    618 }
    619 
    620 void
    621 parse_encode_decode_arg(const char *arg, int encode)
    622 {
    623 	if (! arg[0] || arg[1])
    624 		return;
    625 	if (arg[0] == 'k' || arg[0] == 'b') {
    626 		if (encode)
    627 			flags |= F_ENCODE_KEY;
    628 		else
    629 			flags |= F_DECODE_KEY;
    630 	}
    631 	if (arg[0] == 'v' || arg[0] == 'b') {
    632 		if (encode)
    633 			flags |= F_ENCODE_VAL;
    634 		else
    635 			flags |= F_DECODE_VAL;
    636 	}
    637 	return;
    638 }
    639 
    640 int
    641 parse_encode_option(char **arg)
    642 {
    643 	int	r = 0;
    644 	int	encmask = ~(VIS_CSTYLE | VIS_HTTPSTYLE | VIS_OCTAL);
    645 
    646 	for(; **arg; (*arg)++) {
    647 		switch (**arg) {
    648 			case 'b':
    649 				r |= VIS_NOSLASH;
    650 				break;
    651 			case 'c':
    652 				r &= encmask;
    653 				r |= VIS_CSTYLE;
    654 				break;
    655 			case 'h':
    656 				r &= encmask;
    657 				r |= VIS_HTTPSTYLE;
    658 				break;
    659 			case 'o':
    660 				r &= encmask;
    661 				r |= VIS_OCTAL;
    662 				break;
    663 			case 's':
    664 				r |= VIS_SAFE;
    665 				break;
    666 			case 't':
    667 				r |= VIS_TAB;
    668 				break;
    669 			case 'w':
    670 				r |= VIS_WHITE;
    671 				break;
    672 			default:
    673 				return (0);
    674 				break;
    675 		}
    676 	}
    677 	return (r);
    678 }
    679 
    680 void
    681 usage(void)
    682 {
    683 	const char *p = getprogname();
    684 
    685 	fprintf(stderr,
    686 "usage: %s    [-KiNqV] [-E endian] [-f infile] [-O outsep] [-S visitem]\n"
    687 "             [-T visspec] [-X extravis] type dbfile [key [...]]\n"
    688 "       %s -d [-iNq] [-E endian] [-f infile] [-T visspec] [-U unvisitem]\n"
    689 "             type dbfile [key [...]]\n"
    690 "       %s -w [-CDiNqR] [-E endian] [-F isep] [-f infile] [-m mode]\n"
    691 "             [-P pagesize] [-T visspec] [-U unvisitem]\n"
    692 "             type dbfile [key value [...]]\n"
    693 	    ,p ,p ,p );
    694 	fprintf(stderr,
    695 "Supported modes:\n"
    696 "                read keys  [default]\n"
    697 "   -d           delete keys\n"
    698 "   -w           write (add) keys/values\n"
    699 "Supported options:\n"
    700 "   -C           create empty (truncated) database\n"
    701 "   -D           allow duplicates\n"
    702 "   -E endian    database endian: `B'ig, `L'ittle, `H'ost  [default: H]\n"
    703 "   -F isep      input field separator string  [default: a space]\n"
    704 "   -f infile    file of keys (read|delete) or keys/vals (write)\n"
    705 "   -i           ignore case of key by converting to lower case\n"
    706 "   -K           print key\n"
    707 "   -m mode      mode of created database  [default: 0644]\n"
    708 "   -N           don't NUL terminate key\n"
    709 "   -O outsep    output field separator string  [default: a tab]\n"
    710 "   -P pagesize  database page size  [default: 4096]\n"
    711 "   -q           quiet operation (missing keys aren't errors)\n"
    712 "   -R           replace existing keys\n"
    713 "   -S visitem   items to strvis(3) encode: 'k'ey, 'v'alue, 'b'oth\n"
    714 "   -T visspec   options to control -S and -U; like vis(1) options\n"
    715 "   -U unvisitem items to strunvis(3) decode: 'k'ey, 'v'alue, 'b'oth\n"
    716 "   -V           print value\n"
    717 "   -X extravis  extra characters to encode with -S\n"
    718 	    );
    719 	exit(1);
    720 }
    721