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