xref: /netbsd-src/external/bsd/mdocml/dist/dba.c (revision 3cae15996b212a3594075274fb5af48233a26016)
19508192eSchristos /*	Id: dba.c,v 1.10 2017/02/17 14:43:54 schwarze Exp  */
29508192eSchristos /*
39508192eSchristos  * Copyright (c) 2016, 2017 Ingo Schwarze <schwarze@openbsd.org>
49508192eSchristos  *
59508192eSchristos  * Permission to use, copy, modify, and distribute this software for any
69508192eSchristos  * purpose with or without fee is hereby granted, provided that the above
79508192eSchristos  * copyright notice and this permission notice appear in all copies.
89508192eSchristos  *
99508192eSchristos  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
109508192eSchristos  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
119508192eSchristos  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
129508192eSchristos  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
139508192eSchristos  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
149508192eSchristos  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
159508192eSchristos  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
169508192eSchristos  *
179508192eSchristos  * Allocation-based version of the mandoc database, for read-write access.
189508192eSchristos  * The interface is defined in "dba.h".
199508192eSchristos  */
209508192eSchristos #include "config.h"
219508192eSchristos 
229508192eSchristos #include <sys/types.h>
239508192eSchristos #if HAVE_ENDIAN
249508192eSchristos #include <endian.h>
259508192eSchristos #elif HAVE_SYS_ENDIAN
269508192eSchristos #include <sys/endian.h>
279508192eSchristos #elif HAVE_NTOHL
289508192eSchristos #include <arpa/inet.h>
299508192eSchristos #endif
309508192eSchristos #include <errno.h>
319508192eSchristos #include <stddef.h>
329508192eSchristos #include <stdint.h>
339508192eSchristos #include <stdlib.h>
349508192eSchristos #include <string.h>
359508192eSchristos #include <unistd.h>
369508192eSchristos 
379508192eSchristos #include "mandoc_aux.h"
389508192eSchristos #include "mandoc_ohash.h"
399508192eSchristos #include "mansearch.h"
409508192eSchristos #include "dba_write.h"
419508192eSchristos #include "dba_array.h"
429508192eSchristos #include "dba.h"
439508192eSchristos 
449508192eSchristos struct macro_entry {
459508192eSchristos 	struct dba_array	*pages;
469508192eSchristos 	char			 value[];
479508192eSchristos };
489508192eSchristos 
499508192eSchristos static void	*prepend(const char *, char);
509508192eSchristos static void	 dba_pages_write(struct dba_array *);
519508192eSchristos static int	 compare_names(const void *, const void *);
529508192eSchristos static int	 compare_strings(const void *, const void *);
539508192eSchristos 
549508192eSchristos static struct macro_entry
559508192eSchristos 		*get_macro_entry(struct ohash *, const char *, int32_t);
569508192eSchristos static void	 dba_macros_write(struct dba_array *);
579508192eSchristos static void	 dba_macro_write(struct ohash *);
589508192eSchristos static int	 compare_entries(const void *, const void *);
599508192eSchristos 
609508192eSchristos 
619508192eSchristos /*** top-level functions **********************************************/
629508192eSchristos 
639508192eSchristos struct dba *
dba_new(int32_t npages)649508192eSchristos dba_new(int32_t npages)
659508192eSchristos {
669508192eSchristos 	struct dba	*dba;
679508192eSchristos 	struct ohash	*macro;
689508192eSchristos 	int32_t		 im;
699508192eSchristos 
709508192eSchristos 	dba = mandoc_malloc(sizeof(*dba));
719508192eSchristos 	dba->pages = dba_array_new(npages, DBA_GROW);
729508192eSchristos 	dba->macros = dba_array_new(MACRO_MAX, 0);
739508192eSchristos 	for (im = 0; im < MACRO_MAX; im++) {
749508192eSchristos 		macro = mandoc_malloc(sizeof(*macro));
759508192eSchristos 		mandoc_ohash_init(macro, 4,
769508192eSchristos 		    offsetof(struct macro_entry, value));
779508192eSchristos 		dba_array_set(dba->macros, im, macro);
789508192eSchristos 	}
799508192eSchristos 	return dba;
809508192eSchristos }
819508192eSchristos 
829508192eSchristos void
dba_free(struct dba * dba)839508192eSchristos dba_free(struct dba *dba)
849508192eSchristos {
859508192eSchristos 	struct dba_array	*page;
869508192eSchristos 	struct ohash		*macro;
879508192eSchristos 	struct macro_entry	*entry;
889508192eSchristos 	unsigned int		 slot;
899508192eSchristos 
909508192eSchristos 	dba_array_FOREACH(dba->macros, macro) {
919508192eSchristos 		for (entry = ohash_first(macro, &slot); entry != NULL;
929508192eSchristos 		     entry = ohash_next(macro, &slot)) {
939508192eSchristos 			dba_array_free(entry->pages);
949508192eSchristos 			free(entry);
959508192eSchristos 		}
969508192eSchristos 		ohash_delete(macro);
979508192eSchristos 		free(macro);
989508192eSchristos 	}
999508192eSchristos 	dba_array_free(dba->macros);
1009508192eSchristos 
1019508192eSchristos 	dba_array_undel(dba->pages);
1029508192eSchristos 	dba_array_FOREACH(dba->pages, page) {
1039508192eSchristos 		dba_array_free(dba_array_get(page, DBP_NAME));
1049508192eSchristos 		dba_array_free(dba_array_get(page, DBP_SECT));
1059508192eSchristos 		dba_array_free(dba_array_get(page, DBP_ARCH));
1069508192eSchristos 		free(dba_array_get(page, DBP_DESC));
1079508192eSchristos 		dba_array_free(dba_array_get(page, DBP_FILE));
1089508192eSchristos 		dba_array_free(page);
1099508192eSchristos 	}
1109508192eSchristos 	dba_array_free(dba->pages);
1119508192eSchristos 
1129508192eSchristos 	free(dba);
1139508192eSchristos }
1149508192eSchristos 
1159508192eSchristos /*
1169508192eSchristos  * Write the complete mandoc database to disk; the format is:
1179508192eSchristos  * - One integer each for magic and version.
1189508192eSchristos  * - One pointer each to the macros table and to the final magic.
1199508192eSchristos  * - The pages table.
1209508192eSchristos  * - The macros table.
1219508192eSchristos  * - And at the very end, the magic integer again.
1229508192eSchristos  */
1239508192eSchristos int
dba_write(const char * fname,struct dba * dba)1249508192eSchristos dba_write(const char *fname, struct dba *dba)
1259508192eSchristos {
1269508192eSchristos 	int	 save_errno;
1279508192eSchristos 	int32_t	 pos_end, pos_macros, pos_macros_ptr;
1289508192eSchristos 
1299508192eSchristos 	if (dba_open(fname) == -1)
1309508192eSchristos 		return -1;
1319508192eSchristos 	dba_int_write(MANDOCDB_MAGIC);
1329508192eSchristos 	dba_int_write(MANDOCDB_VERSION);
1339508192eSchristos 	pos_macros_ptr = dba_skip(1, 2);
1349508192eSchristos 	dba_pages_write(dba->pages);
1359508192eSchristos 	pos_macros = dba_tell();
1369508192eSchristos 	dba_macros_write(dba->macros);
1379508192eSchristos 	pos_end = dba_tell();
1389508192eSchristos 	dba_int_write(MANDOCDB_MAGIC);
1399508192eSchristos 	dba_seek(pos_macros_ptr);
1409508192eSchristos 	dba_int_write(pos_macros);
1419508192eSchristos 	dba_int_write(pos_end);
1429508192eSchristos 	if (dba_close() == -1) {
1439508192eSchristos 		save_errno = errno;
1449508192eSchristos 		unlink(fname);
1459508192eSchristos 		errno = save_errno;
1469508192eSchristos 		return -1;
1479508192eSchristos 	}
1489508192eSchristos 	return 0;
1499508192eSchristos }
1509508192eSchristos 
1519508192eSchristos 
1529508192eSchristos /*** functions for handling pages *************************************/
1539508192eSchristos 
1549508192eSchristos /*
1559508192eSchristos  * Create a new page and append it to the pages table.
1569508192eSchristos  */
1579508192eSchristos struct dba_array *
dba_page_new(struct dba_array * pages,const char * arch,const char * desc,const char * file,enum form form)1589508192eSchristos dba_page_new(struct dba_array *pages, const char *arch,
1599508192eSchristos     const char *desc, const char *file, enum form form)
1609508192eSchristos {
1619508192eSchristos 	struct dba_array *page, *entry;
1629508192eSchristos 
1639508192eSchristos 	page = dba_array_new(DBP_MAX, 0);
1649508192eSchristos 	entry = dba_array_new(1, DBA_STR | DBA_GROW);
1659508192eSchristos 	dba_array_add(page, entry);
1669508192eSchristos 	entry = dba_array_new(1, DBA_STR | DBA_GROW);
1679508192eSchristos 	dba_array_add(page, entry);
1689508192eSchristos 	if (arch != NULL && *arch != '\0') {
1699508192eSchristos 		entry = dba_array_new(1, DBA_STR | DBA_GROW);
170*3cae1599Schristos 		dba_array_add(entry, __UNCONST(arch));
1719508192eSchristos 	} else
1729508192eSchristos 		entry = NULL;
1739508192eSchristos 	dba_array_add(page, entry);
1749508192eSchristos 	dba_array_add(page, mandoc_strdup(desc));
1759508192eSchristos 	entry = dba_array_new(1, DBA_STR | DBA_GROW);
1769508192eSchristos 	dba_array_add(entry, prepend(file, form));
1779508192eSchristos 	dba_array_add(page, entry);
1789508192eSchristos 	dba_array_add(pages, page);
1799508192eSchristos 	return page;
1809508192eSchristos }
1819508192eSchristos 
1829508192eSchristos /*
1839508192eSchristos  * Add a section, architecture, or file name to an existing page.
1849508192eSchristos  * Passing the NULL pointer for the architecture makes the page MI.
1859508192eSchristos  * In that case, any earlier or later architectures are ignored.
1869508192eSchristos  */
1879508192eSchristos void
dba_page_add(struct dba_array * page,int32_t ie,const char * str)1889508192eSchristos dba_page_add(struct dba_array *page, int32_t ie, const char *str)
1899508192eSchristos {
1909508192eSchristos 	struct dba_array	*entries;
1919508192eSchristos 	char			*entry;
1929508192eSchristos 
1939508192eSchristos 	entries = dba_array_get(page, ie);
1949508192eSchristos 	if (ie == DBP_ARCH) {
1959508192eSchristos 		if (entries == NULL)
1969508192eSchristos 			return;
1979508192eSchristos 		if (str == NULL || *str == '\0') {
1989508192eSchristos 			dba_array_free(entries);
1999508192eSchristos 			dba_array_set(page, DBP_ARCH, NULL);
2009508192eSchristos 			return;
2019508192eSchristos 		}
2029508192eSchristos 	}
2039508192eSchristos 	if (*str == '\0')
2049508192eSchristos 		return;
2059508192eSchristos 	dba_array_FOREACH(entries, entry) {
2069508192eSchristos 		if (ie == DBP_FILE && *entry < ' ')
2079508192eSchristos 			entry++;
2089508192eSchristos 		if (strcmp(entry, str) == 0)
2099508192eSchristos 			return;
2109508192eSchristos 	}
211*3cae1599Schristos 	dba_array_add(entries, __UNCONST(str));
2129508192eSchristos }
2139508192eSchristos 
2149508192eSchristos /*
2159508192eSchristos  * Add an additional name to an existing page.
2169508192eSchristos  */
2179508192eSchristos void
dba_page_alias(struct dba_array * page,const char * name,uint64_t mask)2189508192eSchristos dba_page_alias(struct dba_array *page, const char *name, uint64_t mask)
2199508192eSchristos {
2209508192eSchristos 	struct dba_array	*entries;
2219508192eSchristos 	char			*entry;
2229508192eSchristos 	char			 maskbyte;
2239508192eSchristos 
2249508192eSchristos 	if (*name == '\0')
2259508192eSchristos 		return;
2269508192eSchristos 	maskbyte = mask & NAME_MASK;
2279508192eSchristos 	entries = dba_array_get(page, DBP_NAME);
2289508192eSchristos 	dba_array_FOREACH(entries, entry) {
2299508192eSchristos 		if (strcmp(entry + 1, name) == 0) {
2309508192eSchristos 			*entry |= maskbyte;
2319508192eSchristos 			return;
2329508192eSchristos 		}
2339508192eSchristos 	}
2349508192eSchristos 	dba_array_add(entries, prepend(name, maskbyte));
2359508192eSchristos }
2369508192eSchristos 
2379508192eSchristos /*
2389508192eSchristos  * Return a pointer to a temporary copy of instr with inbyte prepended.
2399508192eSchristos  */
2409508192eSchristos static void *
prepend(const char * instr,char inbyte)2419508192eSchristos prepend(const char *instr, char inbyte)
2429508192eSchristos {
2439508192eSchristos 	static char	*outstr = NULL;
2449508192eSchristos 	static size_t	 outlen = 0;
2459508192eSchristos 	size_t		 newlen;
2469508192eSchristos 
2479508192eSchristos 	newlen = strlen(instr) + 1;
2489508192eSchristos 	if (newlen > outlen) {
2499508192eSchristos 		outstr = mandoc_realloc(outstr, newlen + 1);
2509508192eSchristos 		outlen = newlen;
2519508192eSchristos 	}
2529508192eSchristos 	*outstr = inbyte;
2539508192eSchristos 	memcpy(outstr + 1, instr, newlen);
2549508192eSchristos 	return outstr;
2559508192eSchristos }
2569508192eSchristos 
2579508192eSchristos /*
2589508192eSchristos  * Write the pages table to disk; the format is:
2599508192eSchristos  * - One integer containing the number of pages.
2609508192eSchristos  * - For each page, five pointers to the names, sections,
2619508192eSchristos  *   architectures, description, and file names of the page.
2629508192eSchristos  *   MI pages write 0 instead of the architecture pointer.
2639508192eSchristos  * - One list each for names, sections, architectures, descriptions and
2649508192eSchristos  *   file names.  The description for each page ends with a NUL byte.
2659508192eSchristos  *   For all the other lists, each string ends with a NUL byte,
2669508192eSchristos  *   and the last string for a page ends with two NUL bytes.
2679508192eSchristos  * - To assure alignment of following integers,
2689508192eSchristos  *   the end is padded with NUL bytes up to a multiple of four bytes.
2699508192eSchristos  */
2709508192eSchristos static void
dba_pages_write(struct dba_array * pages)2719508192eSchristos dba_pages_write(struct dba_array *pages)
2729508192eSchristos {
2739508192eSchristos 	struct dba_array	*page, *entry;
2749508192eSchristos 	int32_t			 pos_pages, pos_end;
2759508192eSchristos 
2769508192eSchristos 	pos_pages = dba_array_writelen(pages, 5);
2779508192eSchristos 	dba_array_FOREACH(pages, page) {
2789508192eSchristos 		dba_array_setpos(page, DBP_NAME, dba_tell());
2799508192eSchristos 		entry = dba_array_get(page, DBP_NAME);
2809508192eSchristos 		dba_array_sort(entry, compare_names);
2819508192eSchristos 		dba_array_writelst(entry);
2829508192eSchristos 	}
2839508192eSchristos 	dba_array_FOREACH(pages, page) {
2849508192eSchristos 		dba_array_setpos(page, DBP_SECT, dba_tell());
2859508192eSchristos 		entry = dba_array_get(page, DBP_SECT);
2869508192eSchristos 		dba_array_sort(entry, compare_strings);
2879508192eSchristos 		dba_array_writelst(entry);
2889508192eSchristos 	}
2899508192eSchristos 	dba_array_FOREACH(pages, page) {
2909508192eSchristos 		if ((entry = dba_array_get(page, DBP_ARCH)) != NULL) {
2919508192eSchristos 			dba_array_setpos(page, DBP_ARCH, dba_tell());
2929508192eSchristos 			dba_array_sort(entry, compare_strings);
2939508192eSchristos 			dba_array_writelst(entry);
2949508192eSchristos 		} else
2959508192eSchristos 			dba_array_setpos(page, DBP_ARCH, 0);
2969508192eSchristos 	}
2979508192eSchristos 	dba_array_FOREACH(pages, page) {
2989508192eSchristos 		dba_array_setpos(page, DBP_DESC, dba_tell());
2999508192eSchristos 		dba_str_write(dba_array_get(page, DBP_DESC));
3009508192eSchristos 	}
3019508192eSchristos 	dba_array_FOREACH(pages, page) {
3029508192eSchristos 		dba_array_setpos(page, DBP_FILE, dba_tell());
3039508192eSchristos 		dba_array_writelst(dba_array_get(page, DBP_FILE));
3049508192eSchristos 	}
3059508192eSchristos 	pos_end = dba_align();
3069508192eSchristos 	dba_seek(pos_pages);
3079508192eSchristos 	dba_array_FOREACH(pages, page)
3089508192eSchristos 		dba_array_writepos(page);
3099508192eSchristos 	dba_seek(pos_end);
3109508192eSchristos }
3119508192eSchristos 
3129508192eSchristos static int
compare_names(const void * vp1,const void * vp2)3139508192eSchristos compare_names(const void *vp1, const void *vp2)
3149508192eSchristos {
3159508192eSchristos 	const char	*cp1, *cp2;
3169508192eSchristos 	int		 diff;
3179508192eSchristos 
3189508192eSchristos 	cp1 = *(const char * const *)vp1;
3199508192eSchristos 	cp2 = *(const char * const *)vp2;
3209508192eSchristos 	return (diff = *cp2 - *cp1) ? diff :
3219508192eSchristos 	    strcasecmp(cp1 + 1, cp2 + 1);
3229508192eSchristos }
3239508192eSchristos 
3249508192eSchristos static int
compare_strings(const void * vp1,const void * vp2)3259508192eSchristos compare_strings(const void *vp1, const void *vp2)
3269508192eSchristos {
3279508192eSchristos 	const char	*cp1, *cp2;
3289508192eSchristos 
3299508192eSchristos 	cp1 = *(const char * const *)vp1;
3309508192eSchristos 	cp2 = *(const char * const *)vp2;
3319508192eSchristos 	return strcmp(cp1, cp2);
3329508192eSchristos }
3339508192eSchristos 
3349508192eSchristos /*** functions for handling macros ************************************/
3359508192eSchristos 
3369508192eSchristos /*
3379508192eSchristos  * In the hash table for a single macro, look up an entry by
3389508192eSchristos  * the macro value or add an empty one if it doesn't exist yet.
3399508192eSchristos  */
3409508192eSchristos static struct macro_entry *
get_macro_entry(struct ohash * macro,const char * value,int32_t np)3419508192eSchristos get_macro_entry(struct ohash *macro, const char *value, int32_t np)
3429508192eSchristos {
3439508192eSchristos 	struct macro_entry	*entry;
3449508192eSchristos 	size_t			 len;
3459508192eSchristos 	unsigned int		 slot;
3469508192eSchristos 
3479508192eSchristos 	slot = ohash_qlookup(macro, value);
3489508192eSchristos 	if ((entry = ohash_find(macro, slot)) == NULL) {
3499508192eSchristos 		len = strlen(value) + 1;
3509508192eSchristos 		entry = mandoc_malloc(sizeof(*entry) + len);
3519508192eSchristos 		memcpy(&entry->value, value, len);
3529508192eSchristos 		entry->pages = dba_array_new(np, DBA_GROW);
3539508192eSchristos 		ohash_insert(macro, slot, entry);
3549508192eSchristos 	}
3559508192eSchristos 	return entry;
3569508192eSchristos }
3579508192eSchristos 
3589508192eSchristos /*
3599508192eSchristos  * In addition to get_macro_entry(), add multiple page references,
3609508192eSchristos  * converting them from the on-disk format (byte offsets in the file)
3619508192eSchristos  * to page pointers in memory.
3629508192eSchristos  */
3639508192eSchristos void
dba_macro_new(struct dba * dba,int32_t im,const char * value,const int32_t * pp)3649508192eSchristos dba_macro_new(struct dba *dba, int32_t im, const char *value,
3659508192eSchristos     const int32_t *pp)
3669508192eSchristos {
3679508192eSchristos 	struct macro_entry	*entry;
3689508192eSchristos 	const int32_t		*ip;
3699508192eSchristos 	int32_t			 np;
3709508192eSchristos 
3719508192eSchristos 	np = 0;
3729508192eSchristos 	for (ip = pp; *ip; ip++)
3739508192eSchristos 		np++;
3749508192eSchristos 
3759508192eSchristos 	entry = get_macro_entry(dba_array_get(dba->macros, im), value, np);
3769508192eSchristos 	for (ip = pp; *ip; ip++)
3779508192eSchristos 		dba_array_add(entry->pages, dba_array_get(dba->pages,
3789508192eSchristos 		    be32toh(*ip) / 5 / sizeof(*ip) - 1));
3799508192eSchristos }
3809508192eSchristos 
3819508192eSchristos /*
3829508192eSchristos  * In addition to get_macro_entry(), add one page reference,
3839508192eSchristos  * directly taking the in-memory page pointer as an argument.
3849508192eSchristos  */
3859508192eSchristos void
dba_macro_add(struct dba_array * macros,int32_t im,const char * value,struct dba_array * page)3869508192eSchristos dba_macro_add(struct dba_array *macros, int32_t im, const char *value,
3879508192eSchristos     struct dba_array *page)
3889508192eSchristos {
3899508192eSchristos 	struct macro_entry	*entry;
3909508192eSchristos 
3919508192eSchristos 	if (*value == '\0')
3929508192eSchristos 		return;
3939508192eSchristos 	entry = get_macro_entry(dba_array_get(macros, im), value, 1);
3949508192eSchristos 	dba_array_add(entry->pages, page);
3959508192eSchristos }
3969508192eSchristos 
3979508192eSchristos /*
3989508192eSchristos  * Write the macros table to disk; the format is:
3999508192eSchristos  * - The number of macro tables (actually, MACRO_MAX).
4009508192eSchristos  * - That number of pointers to the individual macro tables.
4019508192eSchristos  * - The individual macro tables.
4029508192eSchristos  */
4039508192eSchristos static void
dba_macros_write(struct dba_array * macros)4049508192eSchristos dba_macros_write(struct dba_array *macros)
4059508192eSchristos {
4069508192eSchristos 	struct ohash		*macro;
4079508192eSchristos 	int32_t			 im, pos_macros, pos_end;
4089508192eSchristos 
4099508192eSchristos 	pos_macros = dba_array_writelen(macros, 1);
4109508192eSchristos 	im = 0;
4119508192eSchristos 	dba_array_FOREACH(macros, macro) {
4129508192eSchristos 		dba_array_setpos(macros, im++, dba_tell());
4139508192eSchristos 		dba_macro_write(macro);
4149508192eSchristos 	}
4159508192eSchristos 	pos_end = dba_tell();
4169508192eSchristos 	dba_seek(pos_macros);
4179508192eSchristos 	dba_array_writepos(macros);
4189508192eSchristos 	dba_seek(pos_end);
4199508192eSchristos }
4209508192eSchristos 
4219508192eSchristos /*
4229508192eSchristos  * Write one individual macro table to disk; the format is:
4239508192eSchristos  * - The number of entries in the table.
4249508192eSchristos  * - For each entry, two pointers, the first one to the value
4259508192eSchristos  *   and the second one to the list of pages.
4269508192eSchristos  * - A list of values, each ending in a NUL byte.
4279508192eSchristos  * - To assure alignment of following integers,
4289508192eSchristos  *   padding with NUL bytes up to a multiple of four bytes.
4299508192eSchristos  * - A list of pointers to pages, each list ending in a 0 integer.
4309508192eSchristos  */
4319508192eSchristos static void
dba_macro_write(struct ohash * macro)4329508192eSchristos dba_macro_write(struct ohash *macro)
4339508192eSchristos {
4349508192eSchristos 	struct macro_entry	**entries, *entry;
4359508192eSchristos 	struct dba_array	 *page;
4369508192eSchristos 	int32_t			 *kpos, *dpos;
4379508192eSchristos 	unsigned int		  ie, ne, slot;
4389508192eSchristos 	int			  use;
4399508192eSchristos 	int32_t			  addr, pos_macro, pos_end;
4409508192eSchristos 
4419508192eSchristos 	/* Temporary storage for filtering and sorting. */
4429508192eSchristos 
4439508192eSchristos 	ne = ohash_entries(macro);
4449508192eSchristos 	entries = mandoc_reallocarray(NULL, ne, sizeof(*entries));
4459508192eSchristos 	kpos = mandoc_reallocarray(NULL, ne, sizeof(*kpos));
4469508192eSchristos 	dpos = mandoc_reallocarray(NULL, ne, sizeof(*dpos));
4479508192eSchristos 
4489508192eSchristos 	/* Build a list of non-empty entries and sort it. */
4499508192eSchristos 
4509508192eSchristos 	ne = 0;
4519508192eSchristos 	for (entry = ohash_first(macro, &slot); entry != NULL;
4529508192eSchristos 	     entry = ohash_next(macro, &slot)) {
4539508192eSchristos 		use = 0;
4549508192eSchristos 		dba_array_FOREACH(entry->pages, page)
4559508192eSchristos 			if (dba_array_getpos(page))
4569508192eSchristos 				use = 1;
4579508192eSchristos 		if (use)
4589508192eSchristos 			entries[ne++] = entry;
4599508192eSchristos 	}
4609508192eSchristos 	qsort(entries, ne, sizeof(*entries), compare_entries);
4619508192eSchristos 
4629508192eSchristos 	/* Number of entries, and space for the pointer pairs. */
4639508192eSchristos 
4649508192eSchristos 	dba_int_write(ne);
4659508192eSchristos 	pos_macro = dba_skip(2, ne);
4669508192eSchristos 
4679508192eSchristos 	/* String table. */
4689508192eSchristos 
4699508192eSchristos 	for (ie = 0; ie < ne; ie++) {
4709508192eSchristos 		kpos[ie] = dba_tell();
4719508192eSchristos 		dba_str_write(entries[ie]->value);
4729508192eSchristos 	}
4739508192eSchristos 	dba_align();
4749508192eSchristos 
4759508192eSchristos 	/* Pages table. */
4769508192eSchristos 
4779508192eSchristos 	for (ie = 0; ie < ne; ie++) {
4789508192eSchristos 		dpos[ie] = dba_tell();
4799508192eSchristos 		dba_array_FOREACH(entries[ie]->pages, page)
4809508192eSchristos 			if ((addr = dba_array_getpos(page)))
4819508192eSchristos 				dba_int_write(addr);
4829508192eSchristos 		dba_int_write(0);
4839508192eSchristos 	}
4849508192eSchristos 	pos_end = dba_tell();
4859508192eSchristos 
4869508192eSchristos 	/* Fill in the pointer pairs. */
4879508192eSchristos 
4889508192eSchristos 	dba_seek(pos_macro);
4899508192eSchristos 	for (ie = 0; ie < ne; ie++) {
4909508192eSchristos 		dba_int_write(kpos[ie]);
4919508192eSchristos 		dba_int_write(dpos[ie]);
4929508192eSchristos 	}
4939508192eSchristos 	dba_seek(pos_end);
4949508192eSchristos 
4959508192eSchristos 	free(entries);
4969508192eSchristos 	free(kpos);
4979508192eSchristos 	free(dpos);
4989508192eSchristos }
4999508192eSchristos 
5009508192eSchristos static int
compare_entries(const void * vp1,const void * vp2)5019508192eSchristos compare_entries(const void *vp1, const void *vp2)
5029508192eSchristos {
5039508192eSchristos 	const struct macro_entry *ep1, *ep2;
5049508192eSchristos 
5059508192eSchristos 	ep1 = *(const struct macro_entry * const *)vp1;
5069508192eSchristos 	ep2 = *(const struct macro_entry * const *)vp2;
5079508192eSchristos 	return strcmp(ep1->value, ep2->value);
5089508192eSchristos }
509