xref: /openbsd/usr.bin/mandoc/dbm_map.c (revision 79a81166)
1 /*	$OpenBSD: dbm_map.c,v 1.6 2017/02/09 18:26:17 schwarze Exp $ */
2 /*
3  * Copyright (c) 2016 Ingo Schwarze <schwarze@openbsd.org>
4  *
5  * Permission to use, copy, modify, and distribute this software for any
6  * purpose with or without fee is hereby granted, provided that the above
7  * copyright notice and this permission notice appear in all copies.
8  *
9  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
16  *
17  * Low-level routines for the map-based version
18  * of the mandoc database, for read-only access.
19  * The interface is defined in "dbm_map.h".
20  */
21 #include <sys/mman.h>
22 #include <sys/stat.h>
23 #include <sys/types.h>
24 
25 #include <endian.h>
26 #include <err.h>
27 #include <errno.h>
28 #include <fcntl.h>
29 #include <regex.h>
30 #include <stdint.h>
31 #include <stdlib.h>
32 #include <string.h>
33 #include <unistd.h>
34 
35 #include "mansearch.h"
36 #include "dbm_map.h"
37 #include "dbm.h"
38 
39 static struct stat	 st;
40 static char		*dbm_base;
41 static int		 ifd;
42 static int32_t		 max_offset;
43 
44 /*
45  * Open a disk-based database for read-only access.
46  * Validate the file format as far as it is not mandoc-specific.
47  * Return 0 on success.  Return -1 and set errno on failure.
48  */
49 int
dbm_map(const char * fname)50 dbm_map(const char *fname)
51 {
52 	int		 save_errno;
53 	const int32_t	*magic;
54 
55 	if ((ifd = open(fname, O_RDONLY)) == -1)
56 		return -1;
57 	if (fstat(ifd, &st) == -1)
58 		goto fail;
59 	if (st.st_size < 5) {
60 		warnx("dbm_map(%s): File too short", fname);
61 		errno = EFTYPE;
62 		goto fail;
63 	}
64 	if (st.st_size > INT32_MAX) {
65 		errno = EFBIG;
66 		goto fail;
67 	}
68 	if ((dbm_base = mmap(NULL, st.st_size, PROT_READ, MAP_SHARED,
69 	    ifd, 0)) == MAP_FAILED)
70 		goto fail;
71 	magic = dbm_getint(0);
72 	if (be32toh(*magic) != MANDOCDB_MAGIC) {
73 		if (strncmp(dbm_base, "SQLite format 3", 15))
74 			warnx("dbm_map(%s): "
75 			    "Bad initial magic %x (expected %x)",
76 			    fname, be32toh(*magic), MANDOCDB_MAGIC);
77 		else
78 			warnx("dbm_map(%s): "
79 			    "Obsolete format based on SQLite 3",
80 			    fname);
81 		errno = EFTYPE;
82 		goto fail;
83 	}
84 	magic = dbm_getint(1);
85 	if (be32toh(*magic) != MANDOCDB_VERSION) {
86 		warnx("dbm_map(%s): Bad version number %d (expected %d)",
87 		    fname, be32toh(*magic), MANDOCDB_VERSION);
88 		errno = EFTYPE;
89 		goto fail;
90 	}
91 	max_offset = be32toh(*dbm_getint(3)) + sizeof(int32_t);
92 	if (st.st_size != max_offset) {
93 		warnx("dbm_map(%s): Inconsistent file size %lld (expected %d)",
94 		    fname, (long long)st.st_size, max_offset);
95 		errno = EFTYPE;
96 		goto fail;
97 	}
98 	if ((magic = dbm_get(*dbm_getint(3))) == NULL) {
99 		errno = EFTYPE;
100 		goto fail;
101 	}
102 	if (be32toh(*magic) != MANDOCDB_MAGIC) {
103 		warnx("dbm_map(%s): Bad final magic %x (expected %x)",
104 		    fname, be32toh(*magic), MANDOCDB_MAGIC);
105 		errno = EFTYPE;
106 		goto fail;
107 	}
108 	return 0;
109 
110 fail:
111 	save_errno = errno;
112 	close(ifd);
113 	errno = save_errno;
114 	return -1;
115 }
116 
117 void
dbm_unmap(void)118 dbm_unmap(void)
119 {
120 	if (munmap(dbm_base, st.st_size) == -1)
121 		warn("dbm_unmap: munmap");
122 	if (close(ifd) == -1)
123 		warn("dbm_unmap: close");
124 	dbm_base = (char *)-1;
125 }
126 
127 /*
128  * Take a raw integer as it was read from the database.
129  * Interpret it as an offset into the database file
130  * and return a pointer to that place in the file.
131  */
132 void *
dbm_get(int32_t offset)133 dbm_get(int32_t offset)
134 {
135 	offset = be32toh(offset);
136 	if (offset < 0) {
137 		warnx("dbm_get: Database corrupt: offset %d", offset);
138 		return NULL;
139 	}
140 	if (offset >= max_offset) {
141 		warnx("dbm_get: Database corrupt: offset %d > %d",
142 		    offset, max_offset);
143 		return NULL;
144 	}
145 	return dbm_base + offset;
146 }
147 
148 /*
149  * Assume the database starts with some integers.
150  * Assume they are numbered starting from 0, increasing.
151  * Get a pointer to one with the number "offset".
152  */
153 int32_t *
dbm_getint(int32_t offset)154 dbm_getint(int32_t offset)
155 {
156 	return (int32_t *)dbm_base + offset;
157 }
158 
159 /*
160  * The reverse of dbm_get().
161  * Take pointer into the database file
162  * and convert it to the raw integer
163  * that would be used to refer to that place in the file.
164  */
165 int32_t
dbm_addr(const void * p)166 dbm_addr(const void *p)
167 {
168 	return htobe32((const char *)p - dbm_base);
169 }
170 
171 int
dbm_match(const struct dbm_match * match,const char * str)172 dbm_match(const struct dbm_match *match, const char *str)
173 {
174 	switch (match->type) {
175 	case DBM_EXACT:
176 		return strcmp(str, match->str) == 0;
177 	case DBM_SUB:
178 		return strcasestr(str, match->str) != NULL;
179 	case DBM_REGEX:
180 		return regexec(match->re, str, 0, NULL, 0) == 0;
181 	default:
182 		abort();
183 	}
184 }
185