xref: /openbsd-src/usr.bin/mandoc/dba.c (revision f2da64fbbbf1b03f09f390ab01267c93dfd77c4c)
1 /*	$OpenBSD: dba.c,v 1.5 2016/08/17 20:46:06 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  * Allocation-based version of the mandoc database, for read-write access.
18  * The interface is defined in "dba.h".
19  */
20 #include <sys/types.h>
21 #include <endian.h>
22 #include <errno.h>
23 #include <stdint.h>
24 #include <stdlib.h>
25 #include <string.h>
26 #include <unistd.h>
27 
28 #include "mandoc_aux.h"
29 #include "mansearch.h"
30 #include "dba_write.h"
31 #include "dba_array.h"
32 #include "dba.h"
33 
34 static void	*prepend(const char *, char);
35 static void	 dba_pages_write(struct dba_array *);
36 static int	 compare_names(const void *, const void *);
37 static int	 compare_strings(const void *, const void *);
38 static void	 dba_macros_write(struct dba_array *);
39 static void	 dba_macro_write(struct dba_array *);
40 
41 
42 /*** top-level functions **********************************************/
43 
44 struct dba *
45 dba_new(int32_t npages)
46 {
47 	struct dba	*dba;
48 	int32_t		 im;
49 
50 	dba = mandoc_malloc(sizeof(*dba));
51 	dba->pages = dba_array_new(npages, DBA_GROW);
52 	dba->macros = dba_array_new(MACRO_MAX, 0);
53 	for (im = 0; im < MACRO_MAX; im++)
54 		dba_array_set(dba->macros, im, dba_array_new(128, DBA_GROW));
55 	return dba;
56 }
57 
58 void
59 dba_free(struct dba *dba)
60 {
61 	struct dba_array	*page, *macro, *entry;
62 
63 	dba_array_FOREACH(dba->macros, macro) {
64 		dba_array_undel(macro);
65 		dba_array_FOREACH(macro, entry) {
66 			free(dba_array_get(entry, 0));
67 			dba_array_free(dba_array_get(entry, 1));
68 			dba_array_free(entry);
69 		}
70 		dba_array_free(macro);
71 	}
72 	dba_array_free(dba->macros);
73 
74 	dba_array_undel(dba->pages);
75 	dba_array_FOREACH(dba->pages, page) {
76 		dba_array_free(dba_array_get(page, DBP_NAME));
77 		dba_array_free(dba_array_get(page, DBP_SECT));
78 		dba_array_free(dba_array_get(page, DBP_ARCH));
79 		free(dba_array_get(page, DBP_DESC));
80 		dba_array_free(dba_array_get(page, DBP_FILE));
81 		dba_array_free(page);
82 	}
83 	dba_array_free(dba->pages);
84 
85 	free(dba);
86 }
87 
88 /*
89  * Write the complete mandoc database to disk; the format is:
90  * - One integer each for magic and version.
91  * - One pointer each to the macros table and to the final magic.
92  * - The pages table.
93  * - The macros table.
94  * - And at the very end, the magic integer again.
95  */
96 int
97 dba_write(const char *fname, struct dba *dba)
98 {
99 	int	 save_errno;
100 	int32_t	 pos_end, pos_macros, pos_macros_ptr;
101 
102 	if (dba_open(fname) == -1)
103 		return -1;
104 	dba_int_write(MANDOCDB_MAGIC);
105 	dba_int_write(MANDOCDB_VERSION);
106 	pos_macros_ptr = dba_skip(1, 2);
107 	dba_pages_write(dba->pages);
108 	pos_macros = dba_tell();
109 	dba_macros_write(dba->macros);
110 	pos_end = dba_tell();
111 	dba_int_write(MANDOCDB_MAGIC);
112 	dba_seek(pos_macros_ptr);
113 	dba_int_write(pos_macros);
114 	dba_int_write(pos_end);
115 	if (dba_close() == -1) {
116 		save_errno = errno;
117 		unlink(fname);
118 		errno = save_errno;
119 		return -1;
120 	}
121 	return 0;
122 }
123 
124 
125 /*** functions for handling pages *************************************/
126 
127 /*
128  * Create a new page and append it to the pages table.
129  */
130 struct dba_array *
131 dba_page_new(struct dba_array *pages, const char *arch,
132     const char *desc, const char *file, enum form form)
133 {
134 	struct dba_array *page, *entry;
135 
136 	page = dba_array_new(DBP_MAX, 0);
137 	entry = dba_array_new(1, DBA_STR | DBA_GROW);
138 	dba_array_add(page, entry);
139 	entry = dba_array_new(1, DBA_STR | DBA_GROW);
140 	dba_array_add(page, entry);
141 	if (arch != NULL && *arch != '\0') {
142 		entry = dba_array_new(1, DBA_STR | DBA_GROW);
143 		dba_array_add(entry, (void *)arch);
144 	} else
145 		entry = NULL;
146 	dba_array_add(page, entry);
147 	dba_array_add(page, mandoc_strdup(desc));
148 	entry = dba_array_new(1, DBA_STR | DBA_GROW);
149 	dba_array_add(entry, prepend(file, form));
150 	dba_array_add(page, entry);
151 	dba_array_add(pages, page);
152 	return page;
153 }
154 
155 /*
156  * Add a section, architecture, or file name to an existing page.
157  * Passing the NULL pointer for the architecture makes the page MI.
158  * In that case, any earlier or later architectures are ignored.
159  */
160 void
161 dba_page_add(struct dba_array *page, int32_t ie, const char *str)
162 {
163 	struct dba_array	*entries;
164 	char			*entry;
165 
166 	entries = dba_array_get(page, ie);
167 	if (ie == DBP_ARCH) {
168 		if (entries == NULL)
169 			return;
170 		if (str == NULL || *str == '\0') {
171 			dba_array_free(entries);
172 			dba_array_set(page, DBP_ARCH, NULL);
173 			return;
174 		}
175 	}
176 	if (*str == '\0')
177 		return;
178 	dba_array_FOREACH(entries, entry) {
179 		if (ie == DBP_FILE && *entry < ' ')
180 			entry++;
181 		if (strcmp(entry, str) == 0)
182 			return;
183 	}
184 	dba_array_add(entries, (void *)str);
185 }
186 
187 /*
188  * Add an additional name to an existing page.
189  */
190 void
191 dba_page_alias(struct dba_array *page, const char *name, uint64_t mask)
192 {
193 	struct dba_array	*entries;
194 	char			*entry;
195 	char			 maskbyte;
196 
197 	if (*name == '\0')
198 		return;
199 	maskbyte = mask & NAME_MASK;
200 	entries = dba_array_get(page, DBP_NAME);
201 	dba_array_FOREACH(entries, entry) {
202 		if (strcmp(entry + 1, name) == 0) {
203 			*entry |= maskbyte;
204 			return;
205 		}
206 	}
207 	dba_array_add(entries, prepend(name, maskbyte));
208 }
209 
210 /*
211  * Return a pointer to a temporary copy of instr with inbyte prepended.
212  */
213 static void *
214 prepend(const char *instr, char inbyte)
215 {
216 	static char	*outstr = NULL;
217 	static size_t	 outlen = 0;
218 	size_t		 newlen;
219 
220 	newlen = strlen(instr) + 1;
221 	if (newlen > outlen) {
222 		outstr = mandoc_realloc(outstr, newlen + 1);
223 		outlen = newlen;
224 	}
225 	*outstr = inbyte;
226 	memcpy(outstr + 1, instr, newlen);
227 	return outstr;
228 }
229 
230 /*
231  * Write the pages table to disk; the format is:
232  * - One integer containing the number of pages.
233  * - For each page, five pointers to the names, sections,
234  *   architectures, description, and file names of the page.
235  *   MI pages write 0 instead of the architecture pointer.
236  * - One list each for names, sections, architectures, descriptions and
237  *   file names.  The description for each page ends with a NUL byte.
238  *   For all the other lists, each string ends with a NUL byte,
239  *   and the last string for a page ends with two NUL bytes.
240  * - To assure alignment of following integers,
241  *   the end is padded with NUL bytes up to a multiple of four bytes.
242  */
243 static void
244 dba_pages_write(struct dba_array *pages)
245 {
246 	struct dba_array	*page, *entry;
247 	int32_t			 pos_pages, pos_end;
248 
249 	pos_pages = dba_array_writelen(pages, 5);
250 	dba_array_FOREACH(pages, page) {
251 		dba_array_setpos(page, DBP_NAME, dba_tell());
252 		entry = dba_array_get(page, DBP_NAME);
253 		dba_array_sort(entry, compare_names);
254 		dba_array_writelst(entry);
255 	}
256 	dba_array_FOREACH(pages, page) {
257 		dba_array_setpos(page, DBP_SECT, dba_tell());
258 		entry = dba_array_get(page, DBP_SECT);
259 		dba_array_sort(entry, compare_strings);
260 		dba_array_writelst(entry);
261 	}
262 	dba_array_FOREACH(pages, page) {
263 		if ((entry = dba_array_get(page, DBP_ARCH)) != NULL) {
264 			dba_array_setpos(page, DBP_ARCH, dba_tell());
265 			dba_array_sort(entry, compare_strings);
266 			dba_array_writelst(entry);
267 		} else
268 			dba_array_setpos(page, DBP_ARCH, 0);
269 	}
270 	dba_array_FOREACH(pages, page) {
271 		dba_array_setpos(page, DBP_DESC, dba_tell());
272 		dba_str_write(dba_array_get(page, DBP_DESC));
273 	}
274 	dba_array_FOREACH(pages, page) {
275 		dba_array_setpos(page, DBP_FILE, dba_tell());
276 		dba_array_writelst(dba_array_get(page, DBP_FILE));
277 	}
278 	pos_end = dba_align();
279 	dba_seek(pos_pages);
280 	dba_array_FOREACH(pages, page)
281 		dba_array_writepos(page);
282 	dba_seek(pos_end);
283 }
284 
285 static int
286 compare_names(const void *vp1, const void *vp2)
287 {
288 	const char	*cp1, *cp2;
289 	int		 diff;
290 
291 	cp1 = *(char **)vp1;
292 	cp2 = *(char **)vp2;
293 	return (diff = *cp2 - *cp1) ? diff :
294 	    strcasecmp(cp1 + 1, cp2 + 1);
295 }
296 
297 static int
298 compare_strings(const void *vp1, const void *vp2)
299 {
300 	const char	*cp1, *cp2;
301 
302 	cp1 = *(char **)vp1;
303 	cp2 = *(char **)vp2;
304 	return strcmp(cp1, cp2);
305 }
306 
307 /*** functions for handling macros ************************************/
308 
309 /*
310  * Create a new macro entry and append it to one of the macro tables.
311  */
312 void
313 dba_macro_new(struct dba *dba, int32_t im, const char *value,
314     const int32_t *pp)
315 {
316 	struct dba_array	*entry, *pages;
317 	const int32_t		*ip;
318 	int32_t			 np;
319 
320 	np = 0;
321 	for (ip = pp; *ip; ip++)
322 		np++;
323 	pages = dba_array_new(np, DBA_GROW);
324 	for (ip = pp; *ip; ip++)
325 		dba_array_add(pages, dba_array_get(dba->pages,
326 		    be32toh(*ip) / 5 / sizeof(*ip) - 1));
327 
328 	entry = dba_array_new(2, 0);
329 	dba_array_add(entry, mandoc_strdup(value));
330 	dba_array_add(entry, pages);
331 
332 	dba_array_add(dba_array_get(dba->macros, im), entry);
333 }
334 
335 /*
336  * Look up a macro entry by value and add a reference to a new page to it.
337  * If the value does not yet exist, create a new macro entry
338  * and add it to the macro table in question.
339  */
340 void
341 dba_macro_add(struct dba_array *macros, int32_t im, const char *value,
342     struct dba_array *page)
343 {
344 	struct dba_array	*macro, *entry, *pages;
345 
346 	if (*value == '\0')
347 		return;
348 	macro = dba_array_get(macros, im);
349 	dba_array_FOREACH(macro, entry)
350 		if (strcmp(value, dba_array_get(entry, 0)) == 0)
351 			break;
352 	if (entry == NULL) {
353 		entry = dba_array_new(2, 0);
354 		dba_array_add(entry, mandoc_strdup(value));
355 		pages = dba_array_new(1, DBA_GROW);
356 		dba_array_add(entry, pages);
357 		dba_array_add(macro, entry);
358 	} else
359 		pages = dba_array_get(entry, 1);
360 	dba_array_add(pages, page);
361 }
362 
363 /*
364  * Write the macros table to disk; the format is:
365  * - The number of macro tables (actually, MACRO_MAX).
366  * - That number of pointers to the individual macro tables.
367  * - The individual macro tables.
368  */
369 static void
370 dba_macros_write(struct dba_array *macros)
371 {
372 	struct dba_array	*macro;
373 	int32_t			 im, pos_macros, pos_end;
374 
375 	pos_macros = dba_array_writelen(macros, 1);
376 	im = 0;
377 	dba_array_FOREACH(macros, macro) {
378 		dba_array_setpos(macros, im++, dba_tell());
379 		dba_macro_write(macro);
380 	}
381 	pos_end = dba_tell();
382 	dba_seek(pos_macros);
383 	dba_array_writepos(macros);
384 	dba_seek(pos_end);
385 }
386 
387 /*
388  * Write one individual macro table to disk; the format is:
389  * - The number of entries in the table.
390  * - For each entry, two pointers, the first one to the value
391  *   and the second one to the list of pages.
392  * - A list of values, each ending in a NUL byte.
393  * - To assure alignment of following integers,
394  *   padding with NUL bytes up to a multiple of four bytes.
395  * - A list of pointers to pages, each list ending in a 0 integer.
396  */
397 static void
398 dba_macro_write(struct dba_array *macro)
399 {
400 	struct dba_array	*entry, *pages, *page;
401 	int			 empty;
402 	int32_t			 addr, pos_macro, pos_end;
403 
404 	dba_array_FOREACH(macro, entry) {
405 		pages = dba_array_get(entry, 1);
406 		empty = 1;
407 		dba_array_FOREACH(pages, page)
408 			if (dba_array_getpos(page))
409 				empty = 0;
410 		if (empty)
411 			dba_array_del(macro);
412 	}
413 	pos_macro = dba_array_writelen(macro, 2);
414 	dba_array_FOREACH(macro, entry) {
415 		dba_array_setpos(entry, 0, dba_tell());
416 		dba_str_write(dba_array_get(entry, 0));
417 	}
418 	dba_align();
419 	dba_array_FOREACH(macro, entry) {
420 		dba_array_setpos(entry, 1, dba_tell());
421 		pages = dba_array_get(entry, 1);
422 		dba_array_FOREACH(pages, page)
423 			if ((addr = dba_array_getpos(page)))
424 				dba_int_write(addr);
425 		dba_int_write(0);
426 	}
427 	pos_end = dba_tell();
428 	dba_seek(pos_macro);
429 	dba_array_FOREACH(macro, entry)
430 		dba_array_writepos(entry);
431 	dba_seek(pos_end);
432 }
433