diff --git a/INSTALL b/INSTALL index 58bef6a7358c..115d16006432 100644 --- a/INSTALL +++ b/INSTALL @@ -1,4 +1,4 @@ -$Id: INSTALL,v 1.15 2016/07/14 11:09:06 schwarze Exp $ +$Id: INSTALL,v 1.17 2016/07/19 22:40:33 schwarze Exp $ About mdocml, the portable mandoc distribution ---------------------------------------------- @@ -35,7 +35,11 @@ To install mandoc manually, the following steps are needed: command "echo BUILD_CGI=1 > configure.local". Then run "cp cgi.h.examples cgi.h" and edit cgi.h as desired. -2. Run "./configure". +2. Define MANPATH_DEFAULT in configure.local +if /usr/share/man:/usr/X11R6/man:/usr/local/man is not appropriate +for your operating system. + +3. Run "./configure". This script attempts autoconfiguration of mandoc for your system. Read both its standard output and the file "Makefile.local" it generates. If anything looks wrong or different from what you @@ -45,28 +49,21 @@ result seems right to you. On Solaris 10 and earlier, you may have to run "ksh ./configure" because the native /bin/sh lacks some POSIX features. -3. Run "make". +4. Run "make". Any POSIX-compatible make, in particular both BSD make and GNU make, should work. If the build fails, look at "configure.local.example" and go back to step 2. -4. Run "make -n install" and check whether everything will be +5. Run "make -n install" and check whether everything will be installed to the intended places. Otherwise, put some *DIR or *NM* -variables into "configure.local" and go back to step 2. +variables into "configure.local" and go back to step 3. -5. Run "sudo make install". If you intend to build a binary +6. Run "sudo make install". If you intend to build a binary package using some kind of fake root mechanism, you may need a command like "make DESTDIR=... install". Read the *-install targets in the "Makefile" to understand how DESTDIR is used. -6. If you want to use the integrated man(1) and your system uses -manpath(1), make sure it is configured correctly, in particular, -it returns all directory trees where manual pages are installed. -Otherwise, if your system uses man.conf(5), make sure it contains -a "manpath" line for each directory tree, and the order of these -lines meets your wishes. - -7. If you compiled with database support, run the command "sudo +7. Run the command "sudo makewhatis" to build mandoc.db(5) databases in all the directory trees configured in step 6. Whenever installing new manual pages, re-run makewhatis(8) to update the databases, or apropos(1) will @@ -84,20 +81,9 @@ manual page source. Understanding mandoc dependencies --------------------------------- -The mandoc(1), man(1), and demandoc(1) utilities only depend -on the zlib library for decompressing gzipped manual pages, -but makewhatis(8) and apropos(1) depend on the following -additional software: +The following libraries are required: -1. The SQLite database system, see . -The recommended version of SQLite is 3.8.4.3 or newer. The mandoc -toolset is known to work with version 3.7.5 or newer. Versions -older than 3.8.3 may not achieve full performance due to the -missing SQLITE_DETERMINISTIC optimization flag. Versions older -than 3.8.0 may not show full error information if opening a database -fails due to the missing sqlite3_errstr() API. Both are very minor -problems, apropos(1) is fully usable with SQLite 3.7.5. Versions -older than 3.7.5 may or may not work, they have not been tested. +1. zlib for decompressing gzipped manual pages. 2. The fts(3) directory traversion functions. If your system does not have them, the bundled compatibility version diff --git a/LICENSE b/LICENSE index 57e5656ba73f..1dec04b9f4a6 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,4 @@ -$Id: LICENSE,v 1.12 2016/07/07 23:46:36 schwarze Exp $ +$Id: LICENSE,v 1.13 2016/10/18 14:15:33 schwarze Exp $ With the exceptions noted below, all code and documentation contained in the mdocml toolkit is protected by the Copyright @@ -8,7 +8,8 @@ Copyright (c) 2008-2012, 2014 Kristaps Dzonsons Copyright (c) 2010-2016 Ingo Schwarze Copyright (c) 2009, 2010, 2011, 2012 Joerg Sonnenberger Copyright (c) 2013 Franco Fichtner -Copyright (c) 2014 Baptiste Daroussin +Copyright (c) 2014 Baptiste Daroussin +Copyright (c) 2016 Ed Maste Copyright (c) 1999, 2004 Marc Espie Copyright (c) 1998, 2004, 2010 Todd C. Miller Copyright (c) 2008 Otto Moerbeek @@ -41,7 +42,7 @@ other people's Copyright and are distributed under various 2-clause and 3-clause BSD licenses; see these individual files for details. soelim.c, soelim.1: -Copyright (c) 2014 Baptiste Daroussin +Copyright (c) 2014 Baptiste Daroussin compat_err.c, compat_fts.c, compat_fts.h, compat_getsubopt.c, compat_strcasestr.c, compat_strsep.c, diff --git a/Makefile b/Makefile index f76b13763136..5c2657c39a95 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -# $Id: Makefile,v 1.488 2016/07/12 05:18:38 kristaps Exp $ +# $Id: Makefile,v 1.493 2016/11/19 15:24:51 schwarze Exp $ # # Copyright (c) 2010, 2011, 2012 Kristaps Dzonsons # Copyright (c) 2011, 2013-2016 Ingo Schwarze @@ -15,27 +15,29 @@ # ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF # OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -VERSION = 1.13.4 +VERSION = 1.14.0 # === LIST OF FILES ==================================================== -TESTSRCS = test-dirent-namlen.c \ +TESTSRCS = test-be32toh.c \ + test-dirent-namlen.c \ + test-EFTYPE.c \ test-err.c \ test-fts.c \ test-getline.c \ test-getsubopt.c \ test-isblank.c \ test-mkdtemp.c \ - test-mmap.c \ + test-nanosleep.c \ + test-ntohl.c \ test-ohash.c \ + test-PATH_MAX.c \ test-pledge.c \ test-progname.c \ test-reallocarray.c \ test-rewb-bsd.c \ test-rewb-sysv.c \ test-sandbox_init.c \ - test-sqlite3.c \ - test-sqlite3_errstr.c \ test-strcasestr.c \ test-stringlist.c \ test-strlcat.c \ @@ -58,7 +60,6 @@ SRCS = att.c \ compat_ohash.c \ compat_progname.c \ compat_reallocarray.c \ - compat_sqlite3_errstr.c \ compat_strcasestr.c \ compat_stringlist.c \ compat_strlcat.c \ @@ -66,6 +67,12 @@ SRCS = att.c \ compat_strsep.c \ compat_strtonum.c \ compat_vasprintf.c \ + dba.c \ + dba_array.c \ + dba_read.c \ + dba_write.c \ + dbm.c \ + dbm_map.c \ demandoc.c \ eqn.c \ eqn_html.c \ @@ -86,7 +93,6 @@ SRCS = att.c \ manpage.c \ manpath.c \ mansearch.c \ - mansearch_const.c \ mdoc.c \ mdoc_argv.c \ mdoc_hash.c \ @@ -128,6 +134,11 @@ DISTFILES = INSTALL \ compat_stringlist.h \ configure \ configure.local.example \ + dba.h \ + dba_array.h \ + dba_write.h \ + dbm.h \ + dbm_map.h \ demandoc.1 \ eqn.7 \ gmdiff \ @@ -220,7 +231,6 @@ COMPAT_OBJS = compat_err.o \ compat_ohash.o \ compat_progname.o \ compat_reallocarray.o \ - compat_sqlite3_errstr.o \ compat_strcasestr.o \ compat_strlcat.o \ compat_strlcpy.o \ @@ -244,28 +254,35 @@ MANDOC_TERM_OBJS = eqn_term.o \ term_ps.o \ tbl_term.o -BASE_OBJS = $(MANDOC_HTML_OBJS) \ +DBM_OBJS = dbm.o \ + dbm_map.o \ + mansearch.o + +DBA_OBJS = dba.o \ + dba_array.o \ + dba_read.o \ + dba_write.o \ + mandocdb.o + +MAIN_OBJS = $(MANDOC_HTML_OBJS) \ $(MANDOC_MAN_OBJS) \ $(MANDOC_TERM_OBJS) \ + $(DBM_OBJS) \ + $(DBA_OBJS) \ main.o \ manpath.o \ out.o \ tag.o \ tree.o -MAIN_OBJS = $(BASE_OBJS) - -DB_OBJS = mandocdb.o \ - mansearch.o \ - mansearch_const.o - CGI_OBJS = $(MANDOC_HTML_OBJS) \ + $(DBM_OBJS) \ cgi.o \ - mansearch.o \ - mansearch_const.o \ out.o -MANPAGE_OBJS = manpage.o mansearch.o mansearch_const.o manpath.o +MANPAGE_OBJS = $(DBM_OBJS) \ + manpage.o \ + manpath.o DEMANDOC_OBJS = demandoc.o @@ -329,7 +346,7 @@ www: $(WWW_OBJS) $(WWW_MANS) $(WWW_MANS): mandoc -.PHONY: base-install cgi-install db-install install www-install +.PHONY: base-install cgi-install install www-install .PHONY: clean distclean depend include Makefile.depend @@ -341,7 +358,7 @@ distclean: clean clean: rm -f libmandoc.a $(LIBMANDOC_OBJS) $(COMPAT_OBJS) - rm -f mandoc $(BASE_OBJS) $(DB_OBJS) + rm -f mandoc $(MAIN_OBJS) rm -f man.cgi $(CGI_OBJS) rm -f manpage $(MANPAGE_OBJS) rm -f demandoc $(DEMANDOC_OBJS) @@ -351,50 +368,45 @@ clean: base-install: base-build mkdir -p $(DESTDIR)$(BINDIR) - mkdir -p $(DESTDIR)$(LIBDIR) - mkdir -p $(DESTDIR)$(INCLUDEDIR) + mkdir -p $(DESTDIR)$(SBINDIR) mkdir -p $(DESTDIR)$(MANDIR)/man1 - mkdir -p $(DESTDIR)$(MANDIR)/man3 mkdir -p $(DESTDIR)$(MANDIR)/man5 mkdir -p $(DESTDIR)$(MANDIR)/man7 + mkdir -p $(DESTDIR)$(MANDIR)/man8 $(INSTALL_PROGRAM) mandoc demandoc $(DESTDIR)$(BINDIR) $(INSTALL_PROGRAM) soelim $(DESTDIR)$(BINDIR)/$(BINM_SOELIM) ln -f $(DESTDIR)$(BINDIR)/mandoc $(DESTDIR)$(BINDIR)/$(BINM_MAN) - $(INSTALL_LIB) libmandoc.a $(DESTDIR)$(LIBDIR) - $(INSTALL_LIB) man.h mandoc.h mandoc_aux.h mdoc.h roff.h \ - $(DESTDIR)$(INCLUDEDIR) + ln -f $(DESTDIR)$(BINDIR)/mandoc $(DESTDIR)$(BINDIR)/$(BINM_APROPOS) + ln -f $(DESTDIR)$(BINDIR)/mandoc $(DESTDIR)$(BINDIR)/$(BINM_WHATIS) + ln -f $(DESTDIR)$(BINDIR)/mandoc \ + $(DESTDIR)$(SBINDIR)/$(BINM_MAKEWHATIS) $(INSTALL_MAN) mandoc.1 demandoc.1 $(DESTDIR)$(MANDIR)/man1 $(INSTALL_MAN) soelim.1 $(DESTDIR)$(MANDIR)/man1/$(BINM_SOELIM).1 $(INSTALL_MAN) man.1 $(DESTDIR)$(MANDIR)/man1/$(BINM_MAN).1 - $(INSTALL_MAN) mandoc.3 mandoc_escape.3 mandoc_malloc.3 \ - mchars_alloc.3 tbl.3 $(DESTDIR)$(MANDIR)/man3 + $(INSTALL_MAN) apropos.1 $(DESTDIR)$(MANDIR)/man1/$(BINM_APROPOS).1 + ln -f $(DESTDIR)$(MANDIR)/man1/$(BINM_APROPOS).1 \ + $(DESTDIR)$(MANDIR)/man1/$(BINM_WHATIS).1 $(INSTALL_MAN) man.conf.5 $(DESTDIR)$(MANDIR)/man5/${MANM_MANCONF}.5 + $(INSTALL_MAN) mandoc.db.5 $(DESTDIR)$(MANDIR)/man5 $(INSTALL_MAN) man.7 $(DESTDIR)$(MANDIR)/man7/${MANM_MAN}.7 $(INSTALL_MAN) mdoc.7 $(DESTDIR)$(MANDIR)/man7/${MANM_MDOC}.7 $(INSTALL_MAN) roff.7 $(DESTDIR)$(MANDIR)/man7/${MANM_ROFF}.7 $(INSTALL_MAN) eqn.7 $(DESTDIR)$(MANDIR)/man7/${MANM_EQN}.7 $(INSTALL_MAN) tbl.7 $(DESTDIR)$(MANDIR)/man7/${MANM_TBL}.7 $(INSTALL_MAN) mandoc_char.7 $(DESTDIR)$(MANDIR)/man7 - -db-install: base-build - mkdir -p $(DESTDIR)$(BINDIR) - mkdir -p $(DESTDIR)$(SBINDIR) - mkdir -p $(DESTDIR)$(MANDIR)/man1 - mkdir -p $(DESTDIR)$(MANDIR)/man3 - mkdir -p $(DESTDIR)$(MANDIR)/man5 - mkdir -p $(DESTDIR)$(MANDIR)/man8 - ln -f $(DESTDIR)$(BINDIR)/mandoc $(DESTDIR)$(BINDIR)/$(BINM_APROPOS) - ln -f $(DESTDIR)$(BINDIR)/mandoc $(DESTDIR)$(BINDIR)/$(BINM_WHATIS) - ln -f $(DESTDIR)$(BINDIR)/mandoc \ - $(DESTDIR)$(SBINDIR)/$(BINM_MAKEWHATIS) - $(INSTALL_MAN) apropos.1 $(DESTDIR)$(MANDIR)/man1/$(BINM_APROPOS).1 - ln -f $(DESTDIR)$(MANDIR)/man1/$(BINM_APROPOS).1 \ - $(DESTDIR)$(MANDIR)/man1/$(BINM_WHATIS).1 - $(INSTALL_MAN) mansearch.3 $(DESTDIR)$(MANDIR)/man3 - $(INSTALL_MAN) mandoc.db.5 $(DESTDIR)$(MANDIR)/man5 $(INSTALL_MAN) makewhatis.8 \ $(DESTDIR)$(MANDIR)/man8/$(BINM_MAKEWHATIS).8 +lib-install: base-build + mkdir -p $(DESTDIR)$(LIBDIR) + mkdir -p $(DESTDIR)$(INCLUDEDIR) + mkdir -p $(DESTDIR)$(MANDIR)/man3 + $(INSTALL_LIB) libmandoc.a $(DESTDIR)$(LIBDIR) + $(INSTALL_LIB) man.h mandoc.h mandoc_aux.h mdoc.h roff.h \ + $(DESTDIR)$(INCLUDEDIR) + $(INSTALL_MAN) mandoc.3 mandoc_escape.3 mandoc_malloc.3 \ + mansearch.3 mchars_alloc.3 tbl.3 $(DESTDIR)$(MANDIR)/man3 + cgi-install: cgi-build mkdir -p $(DESTDIR)$(CGIBINDIR) mkdir -p $(DESTDIR)$(HTDOCDIR) diff --git a/Makefile.depend b/Makefile.depend index 98a1928b7a98..af1255defdf5 100644 --- a/Makefile.depend +++ b/Makefile.depend @@ -10,7 +10,6 @@ compat_mkdtemp.o: compat_mkdtemp.c config.h compat_ohash.o: compat_ohash.c config.h compat_ohash.h compat_progname.o: compat_progname.c config.h compat_reallocarray.o: compat_reallocarray.c config.h -compat_sqlite3_errstr.o: compat_sqlite3_errstr.c config.h compat_strcasestr.o: compat_strcasestr.c config.h compat_stringlist.o: compat_stringlist.c config.h compat_stringlist.h compat_strlcat.o: compat_strlcat.c config.h @@ -18,6 +17,12 @@ compat_strlcpy.o: compat_strlcpy.c config.h compat_strsep.o: compat_strsep.c config.h compat_strtonum.o: compat_strtonum.c config.h compat_vasprintf.o: compat_vasprintf.c config.h +dba.o: dba.c config.h mandoc_aux.h mandoc_ohash.h compat_ohash.h mansearch.h dba_write.h dba_array.h dba.h +dba_array.o: dba_array.c mandoc_aux.h dba_write.h dba_array.h +dba_read.o: dba_read.c mandoc_aux.h mansearch.h dba_array.h dba.h dbm.h +dba_write.o: dba_write.c config.h dba_write.h +dbm.o: dbm.c config.h mansearch.h dbm_map.h dbm.h +dbm_map.o: dbm_map.c config.h mansearch.h dbm_map.h dbm.h demandoc.o: demandoc.c config.h roff.h man.h mdoc.h mandoc.h eqn.o: eqn.c config.h mandoc.h mandoc_aux.h libmandoc.h libroff.h eqn_html.o: eqn_html.c config.h mandoc.h out.h html.h @@ -26,7 +31,7 @@ html.o: html.c config.h mandoc.h mandoc_aux.h out.h html.h manconf.h main.h lib.o: lib.c config.h roff.h mdoc.h libmdoc.h lib.in main.o: main.c config.h mandoc_aux.h mandoc.h roff.h mdoc.h man.h tag.h main.h manconf.h mansearch.h man.o: man.c config.h mandoc_aux.h mandoc.h roff.h man.h libmandoc.h roff_int.h libman.h -man_hash.o: man_hash.c config.h roff.h man.h libman.h +man_hash.o: man_hash.c config.h mandoc.h roff.h man.h libmandoc.h libman.h man_html.o: man_html.c config.h mandoc_aux.h roff.h man.h out.h html.h main.h man_macro.o: man_macro.c config.h mandoc.h roff.h man.h libmandoc.h roff_int.h libman.h man_term.o: man_term.c config.h mandoc_aux.h mandoc.h roff.h man.h out.h term.h main.h @@ -34,14 +39,13 @@ man_validate.o: man_validate.c config.h mandoc_aux.h mandoc.h roff.h man.h libma mandoc.o: mandoc.c config.h mandoc.h mandoc_aux.h libmandoc.h mandoc_aux.o: mandoc_aux.c config.h mandoc.h mandoc_aux.h mandoc_ohash.o: mandoc_ohash.c mandoc_aux.h mandoc_ohash.h compat_ohash.h -mandocdb.o: mandocdb.c config.h compat_fts.h mandoc_aux.h mandoc_ohash.h compat_ohash.h mandoc.h roff.h mdoc.h man.h manconf.h mansearch.h +mandocdb.o: mandocdb.c config.h compat_fts.h mandoc_aux.h mandoc_ohash.h compat_ohash.h mandoc.h roff.h mdoc.h man.h manconf.h mansearch.h dba_array.h dba.h manpage.o: manpage.c config.h manconf.h mansearch.h manpath.o: manpath.c config.h mandoc_aux.h manconf.h -mansearch.o: mansearch.c config.h mandoc.h mandoc_aux.h mandoc_ohash.h compat_ohash.h manconf.h mansearch.h -mansearch_const.o: mansearch_const.c config.h mansearch.h +mansearch.o: mansearch.c config.h mandoc.h mandoc_aux.h mandoc_ohash.h compat_ohash.h manconf.h mansearch.h dbm.h mdoc.o: mdoc.c config.h mandoc_aux.h mandoc.h roff.h mdoc.h libmandoc.h roff_int.h libmdoc.h -mdoc_argv.o: mdoc_argv.c config.h mandoc_aux.h mandoc.h roff.h mdoc.h libmandoc.h libmdoc.h -mdoc_hash.o: mdoc_hash.c config.h roff.h mdoc.h libmdoc.h +mdoc_argv.o: mdoc_argv.c config.h mandoc_aux.h mandoc.h roff.h mdoc.h libmandoc.h roff_int.h libmdoc.h +mdoc_hash.o: mdoc_hash.c config.h mandoc.h roff.h mdoc.h libmandoc.h libmdoc.h mdoc_html.o: mdoc_html.c config.h mandoc_aux.h roff.h mdoc.h out.h html.h main.h mdoc_macro.o: mdoc_macro.c config.h mandoc.h roff.h mdoc.h libmandoc.h roff_int.h libmdoc.h mdoc_man.o: mdoc_man.c config.h mandoc_aux.h mandoc.h roff.h mdoc.h man.h out.h main.h diff --git a/TODO b/TODO index 99a16a9e62a4..181c43100ab2 100644 --- a/TODO +++ b/TODO @@ -1,6 +1,6 @@ ************************************************************************ * Official mandoc TODO. -* $Id: TODO,v 1.218 2016/06/05 21:06:04 schwarze Exp $ +* $Id: TODO,v 1.223 2017/01/17 15:32:43 schwarze Exp $ ************************************************************************ Many issues are annotated for difficulty as follows: @@ -32,15 +32,6 @@ Many issues are annotated for difficulty as follows: Obviously, as the issues have not been solved yet, these annotations are mere guesses, and some may be wrong. -************************************************************************ -* crashes -************************************************************************ - -- The abort() in bufcat(), html.c, can be triggered via buffmt_includes() - by running -Thtml -Oincludes on a file containing a long .In argument. - Fixing this will probably require reworking the whole bufcat() concept. - loc ** exist * algo * size ** imp ** - ************************************************************************ * missing features ************************************************************************ @@ -213,6 +204,13 @@ are mere guesses, and some may be wrong. synaptics(4) found by tedu@ Mon, 17 Aug 2015 21:17:42 -0400 loc ** exist ** algo ** size ** imp *** +- break long text into lines inside cells + net/lftp(1) from jirib via bentley@ Sep 13, 2016 + +- layout l1 for a column of max text width 3 reduces the following + inter-column spacing for groff, but not for mandoc + net/lftp(1) from jirib via bentley@ Sep 13, 2016 + - the "w" layout option is ignored synaptics(4) found by tedu@ Mon, 17 Aug 2015 21:17:42 -0400 loc * exist * algo * size * imp ** @@ -528,16 +526,6 @@ are mere guesses, and some may be wrong. in dig(1). loc ** exist ** algo ** size * imp ** -************************************************************************ -* portability -************************************************************************ - -- systems having UTF-8 but not en_US.UTF-8 - call locale(1) from ./configure, select a UTF-8-locale, - and use that for test-wchar.c and term_ascii.c - to Markus Waldeck Sat, 18 Jul 2015 01:55:37 +0200 - loc * exist * algo * size * imp * - ************************************************************************ * warning issues ************************************************************************ @@ -612,7 +600,6 @@ are mere guesses, and some may be wrong. ************************************************************************ - Why are we using MAP_SHARED, not MAP_PRIVATE for mmap(2)? - How does SQLITE_CONFIG_PAGECACHE actually work? Document it! from kristaps@ Sat, 09 Aug 2014 13:51:36 +0200 Several areas can be cleaned up to make mandoc even faster. These are @@ -631,6 +618,13 @@ Several areas can be cleaned up to make mandoc even faster. These are * structural issues ************************************************************************ +- POSIX says in the documentation of sysconf(3) that PATH_MAX + is allowed to be so large that it is a bad idea to use it + for sizing static buffers. So use dynamic buffers throughout. + See the file test-PATH_MAX.c for details. + Found by Aaron M. Ucko in the GNU Hurd via Bdale Garbee, + https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=829624 + - We use the input line number at several places to distinguish same-line from different-line input. That plainly doesn't work with user-defined macros, leading to random breakage. @@ -646,11 +640,6 @@ Several areas can be cleaned up to make mandoc even faster. These are - struct mparse refactoring Steffen Nurpmeso Thu, 04 Sep 2014 12:50:00 +0200 -- Consider creating some views that will make the database more - readable from the sqlite3 shell. Consider using them to - abstract from the database structure, too. - suggested by espie@ Sat, 19 Apr 2014 14:52:57 +0200 - ************************************************************************ * CGI issues ************************************************************************ diff --git a/cgi.c b/cgi.c index f8497b588d95..06beca3fce5a 100644 --- a/cgi.c +++ b/cgi.c @@ -1,7 +1,7 @@ -/* $Id: cgi.c,v 1.135 2016/07/11 22:48:37 schwarze Exp $ */ +/* $Id: cgi.c,v 1.144 2017/01/21 01:20:31 schwarze Exp $ */ /* * Copyright (c) 2011, 2012 Kristaps Dzonsons - * Copyright (c) 2014, 2015, 2016 Ingo Schwarze + * Copyright (c) 2014, 2015, 2016, 2017 Ingo Schwarze * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -113,17 +113,18 @@ static const char *const sec_names[] = { static const int sec_MAX = sizeof(sec_names) / sizeof(char *); static const char *const arch_names[] = { - "amd64", "alpha", "armish", "armv7", - "hppa", "hppa64", "i386", "landisk", + "amd64", "alpha", "armv7", + "hppa", "i386", "landisk", "loongson", "luna88k", "macppc", "mips64", - "octeon", "sgi", "socppc", "sparc", - "sparc64", "zaurus", - "amiga", "arc", "arm32", "atari", - "aviion", "beagle", "cats", "hp300", + "octeon", "sgi", "socppc", "sparc64", + "amiga", "arc", "armish", "arm32", + "atari", "aviion", "beagle", "cats", + "hppa64", "hp300", "ia64", "mac68k", "mvme68k", "mvme88k", "mvmeppc", "palm", "pc532", "pegasos", - "pmax", "powerpc", "solbourne", "sun3", - "vax", "wgrisc", "x68k" + "pmax", "powerpc", "solbourne", "sparc", + "sun3", "vax", "wgrisc", "x68k", + "zaurus" }; static const int arch_MAX = sizeof(arch_names) / sizeof(char *); @@ -137,7 +138,7 @@ html_putchar(char c) switch (c) { case ('"'): - printf(""e;"); + printf("""); break; case ('&'): printf("&"); @@ -337,6 +338,7 @@ resp_copy(const char *filename) fflush(stdout); while ((sz = read(fd, buf, sizeof(buf))) > 0) write(STDOUT_FILENO, buf, sz); + close(fd); } } @@ -349,13 +351,12 @@ resp_begin_html(int code, const char *msg) printf("\n" "\n" "\n" - "\n" - "\n" + " \n" - "%s\n" + " %s\n" "\n" - "\n" - "\n", + "\n", CSS_DIR, CUSTOMIZE_TITLE); resp_copy(MAN_DIR "/header.html"); @@ -376,16 +377,14 @@ resp_searchform(const struct req *req, enum focus focus) { int i; - puts(""); - printf("
\n" - "
\n" - "
\n" - "Manual Page Search Parameters\n", + printf("\n" + "
\n" + " Manual Page Search Parameters\n", scriptname); /* Write query input box. */ - printf("q.query != NULL) html_print(req->q.query); printf( "\" size=\"40\""); @@ -395,45 +394,46 @@ resp_searchform(const struct req *req, enum focus focus) /* Write submission buttons. */ - printf( "\n" - "\n
\n"); + " \n" + "
\n"); /* Write section selector. */ - puts(""); for (i = 0; i < sec_MAX; i++) { - printf("\n", sec_names[i]); } - puts(""); + puts(" "); /* Write architecture selector. */ - printf( ""); + puts(" "); /* Write manpath selector. */ if (req->psz > 1) { - puts(""); for (i = 0; i < (int)req->psz; i++) { - printf(""); } - puts(""); + puts(" "); } - puts("
\n" - "\n" - "
"); - puts(""); + puts(" \n" + ""); } static int @@ -496,9 +494,9 @@ pg_index(const struct req *req) resp_searchform(req, FOCUS_QUERY); printf("

\n" "This web interface is documented in the\n" - "man.cgi(8)\n" + "man.cgi(8)\n" "manual, and the\n" - "apropos(1)\n" + "apropos(1)\n" "manual explains the query syntax.\n" "

\n", scriptname, *scriptname == '\0' ? "" : "/", @@ -578,27 +576,21 @@ pg_searchres(const struct req *req, struct manpage *r, size_t sz) req->q.equal || sz == 1 ? FOCUS_NONE : FOCUS_QUERY); if (sz > 1) { - puts("
"); - puts(""); - + puts("
"); for (i = 0; i < sz; i++) { - printf("\n" - "\n" - "\n" + " \n" - ""); + puts("\n" + " "); } - - puts("
\n" - "\n" + " " + "", scriptname, *scriptname == '\0' ? "" : "/", req->q.manpath, r[i].file); - printf("\">"); html_print(r[i].names); - printf("\n" - ""); + printf(""); html_print(r[i].output); - puts("
\n" - "
"); + puts(""); } /* @@ -800,7 +792,8 @@ resp_format(const struct req *req, const char *file) } mchars_alloc(); - mp = mparse_alloc(MPARSE_SO, MANDOCLEVEL_BADARG, NULL, req->q.manpath); + mp = mparse_alloc(MPARSE_SO | MPARSE_UTF8 | MPARSE_LATIN1, + MANDOCLEVEL_BADARG, NULL, req->q.manpath); mparse_readfd(mp, fd, file); close(fd); diff --git a/compat_fts.c b/compat_fts.c index ed95854666a0..c2cc9570c545 100644 --- a/compat_fts.c +++ b/compat_fts.c @@ -6,8 +6,8 @@ int dummy; #else -/* $Id: compat_fts.c,v 1.9 2015/03/18 19:29:48 schwarze Exp $ */ -/* $OpenBSD: fts.c,v 1.50 2015/01/16 16:48:51 deraadt Exp $ */ +/* $Id: compat_fts.c,v 1.12 2016/10/18 23:58:12 schwarze Exp $ */ +/* $OpenBSD: fts.c,v 1.56 2016/09/21 04:38:56 guenther Exp $ */ /*- * Copyright (c) 1990, 1993, 1994 @@ -59,6 +59,7 @@ static void fts_load(FTS *, FTSENT *); static size_t fts_maxarglen(char * const *); static void fts_padjust(FTS *, FTSENT *); static int fts_palloc(FTS *, size_t); +static FTSENT *fts_sort(FTS *, FTSENT *, int); static unsigned short fts_stat(FTS *, FTSENT *); #define ISDOT(a) (a[0] == '.' && (!a[1] || (a[1] == '.' && !a[2]))) @@ -68,19 +69,22 @@ static unsigned short fts_stat(FTS *, FTSENT *); #ifndef O_CLOEXEC #define O_CLOEXEC 0 #endif +#ifndef PATH_MAX +#define PATH_MAX 4096 +#endif #define CLR(opt) (sp->fts_options &= ~(opt)) #define ISSET(opt) (sp->fts_options & (opt)) #define SET(opt) (sp->fts_options |= (opt)) FTS * -fts_open(char * const *argv, int options, void *dummy) +fts_open(char * const *argv, int options, + int (*compar)(const FTSENT **, const FTSENT **)) { FTS *sp; FTSENT *p, *root; int nitems; FTSENT *parent, *tmp; - size_t len; /* Options check. */ if (options & ~FTS_OPTIONMASK) { @@ -88,9 +92,16 @@ fts_open(char * const *argv, int options, void *dummy) return (NULL); } + /* At least one path must be specified. */ + if (*argv == NULL) { + errno = EINVAL; + return (NULL); + } + /* Allocate/initialize the stream */ if ((sp = calloc(1, sizeof(FTS))) == NULL) return (NULL); + sp->fts_compar = compar; sp->fts_options = options; /* @@ -107,13 +118,7 @@ fts_open(char * const *argv, int options, void *dummy) /* Allocate/initialize root(s). */ for (root = NULL, nitems = 0; *argv; ++argv, ++nitems) { - /* Don't allow zero-length paths. */ - if ((len = strlen(*argv)) == 0) { - errno = ENOENT; - goto mem3; - } - - if ((p = fts_alloc(sp, *argv, len)) == NULL) + if ((p = fts_alloc(sp, *argv, strlen(*argv))) == NULL) goto mem3; p->fts_level = FTS_ROOTLEVEL; p->fts_parent = parent; @@ -124,14 +129,25 @@ fts_open(char * const *argv, int options, void *dummy) if (p->fts_info == FTS_DOT) p->fts_info = FTS_D; - p->fts_link = NULL; - if (root == NULL) - tmp = root = p; - else { - tmp->fts_link = p; - tmp = p; + /* + * If comparison routine supplied, traverse in sorted + * order; otherwise traverse in the order specified. + */ + if (compar) { + p->fts_link = root; + root = p; + } else { + p->fts_link = NULL; + if (root == NULL) + tmp = root = p; + else { + tmp->fts_link = p; + tmp = p; + } } } + if (compar && nitems > 1) + root = fts_sort(sp, root, nitems); /* * Allocate a dummy pointer and make fts_read think that we've just @@ -201,6 +217,7 @@ fts_close(FTS *sp) /* Free up child linked list, sort array, path buffer, stream ptr.*/ if (sp->fts_child) fts_lfree(sp->fts_child); + free(sp->fts_array); free(sp->fts_path); free(sp); @@ -317,7 +334,6 @@ name: t = sp->fts_path + NAPPEND(p->fts_parent); * semantics to fts using fts_set. An error return is allowed for similar * reasons. */ -/* ARGSUSED */ int fts_set(FTS *sp, FTSENT *p, int instr) { @@ -416,8 +432,7 @@ fts_build(FTS *sp) * structures already allocated. */ mem1: saved_errno = errno; - if (p) - free(p); + free(p); fts_lfree(head); (void)closedir(dirp); cur->fts_info = FTS_ERR; @@ -490,6 +505,10 @@ mem1: saved_errno = errno; cur->fts_info = FTS_DP; return (NULL); } + + /* Sort the entries. */ + if (sp->fts_compar && nitems > 1) + head = fts_sort(sp, head, nitems); return (head); } @@ -546,6 +565,40 @@ fts_stat(FTS *sp, FTSENT *p) return (FTS_DEFAULT); } +static FTSENT * +fts_sort(FTS *sp, FTSENT *head, int nitems) +{ + FTSENT **ap, *p; + + /* + * Construct an array of pointers to the structures and call qsort(3). + * Reassemble the array in the order returned by qsort. If unable to + * sort for memory reasons, return the directory entries in their + * current order. Allocate enough space for the current needs plus + * 40 so don't realloc one entry at a time. + */ + if (nitems > sp->fts_nitems) { + struct _ftsent **a; + + sp->fts_nitems = nitems + 40; + if ((a = reallocarray(sp->fts_array, + sp->fts_nitems, sizeof(FTSENT *))) == NULL) { + free(sp->fts_array); + sp->fts_array = NULL; + sp->fts_nitems = 0; + return (head); + } + sp->fts_array = a; + } + for (ap = sp->fts_array, p = head; p; p = p->fts_link) + *ap++ = p; + qsort(sp->fts_array, nitems, sizeof(FTSENT *), sp->fts_compar); + for (head = *(ap = sp->fts_array); --nitems; ++ap) + ap[0]->fts_link = ap[1]; + ap[0]->fts_link = NULL; + return (head); +} + static FTSENT * fts_alloc(FTS *sp, const char *name, size_t namelen) { @@ -597,8 +650,7 @@ fts_palloc(FTS *sp, size_t more) */ more += 256; if (sp->fts_pathlen + more < sp->fts_pathlen) { - if (sp->fts_path) - free(sp->fts_path); + free(sp->fts_path); sp->fts_path = NULL; errno = ENAMETOOLONG; return (1); @@ -606,8 +658,7 @@ fts_palloc(FTS *sp, size_t more) sp->fts_pathlen += more; p = realloc(sp->fts_path, sp->fts_pathlen); if (p == NULL) { - if (sp->fts_path) - free(sp->fts_path); + free(sp->fts_path); sp->fts_path = NULL; return (1); } diff --git a/compat_fts.h b/compat_fts.h index 1eed2ae380fd..f4a97a4c3a56 100644 --- a/compat_fts.h +++ b/compat_fts.h @@ -38,9 +38,12 @@ typedef struct { struct _ftsent *fts_cur; /* current node */ struct _ftsent *fts_child; /* linked list of children */ + struct _ftsent **fts_array; /* sort array */ dev_t fts_dev; /* starting device # */ char *fts_path; /* path for this descent */ size_t fts_pathlen; /* sizeof(path) */ + int fts_nitems; /* elements in the sort array */ + int (*fts_compar)(); /* compare function */ #define FTS_NOCHDIR 0x0004 /* don't change directories */ #define FTS_PHYSICAL 0x0010 /* physical walk */ @@ -94,7 +97,8 @@ typedef struct _ftsent { int fts_close(FTS *); -FTS *fts_open(char * const *, int, void *); +FTS *fts_open(char * const *, int, + int (*)(const FTSENT **, const FTSENT **)); FTSENT *fts_read(FTS *); int fts_set(FTS *, FTSENT *, int); diff --git a/compat_sqlite3_errstr.c b/compat_sqlite3_errstr.c deleted file mode 100644 index 8a6ace28e8cd..000000000000 --- a/compat_sqlite3_errstr.c +++ /dev/null @@ -1,16 +0,0 @@ -#include "config.h" - -#if HAVE_SQLITE3_ERRSTR - -int dummy; - -#else - -const char * -sqlite3_errstr(int rc) -{ - - return rc ? "unknown error" : "not an error"; -} - -#endif diff --git a/config.log b/config.log deleted file mode 100644 index 1f7e11600d30..000000000000 --- a/config.log +++ /dev/null @@ -1,210 +0,0 @@ -configure.local: no (fully automatic configuration) - -dirent-namlen: testing... -cc -g -W -Wall -Wstrict-prototypes -Wno-unused-parameter -Wwrite-strings -Wno-unused -Werror -o test-dirent-namlen test-dirent-namlen.c -dirent-namlen: cc succeeded -dirent-namlen: yes - -err: testing... -cc -g -W -Wall -Wstrict-prototypes -Wno-unused-parameter -Wwrite-strings -Wno-unused -Werror -o test-err test-err.c -err: cc succeeded -test-err: 1. warnx -test-err: 2. warn: No error: 0 -test-err: 3. err: No error: 0 -err: yes - -fts: testing... -cc -g -W -Wall -Wstrict-prototypes -Wno-unused-parameter -Wwrite-strings -Wno-unused -Werror -o test-fts test-fts.c -fts: cc succeeded -fts: yes - -getline: testing... -cc -g -W -Wall -Wstrict-prototypes -Wno-unused-parameter -Wwrite-strings -Wno-unused -Werror -o test-getline test-getline.c -test-getline.c:12:9: error: implicit declaration of function 'getline' is invalid in C99 [-Werror,-Wimplicit-function-declaration] - return getline(&line, &linesz, stdin) != -1; - ^ -1 error generated. -getline: cc failed with 1 - -getsubopt: testing... -cc -g -W -Wall -Wstrict-prototypes -Wno-unused-parameter -Wwrite-strings -Wno-unused -Werror -o test-getsubopt test-getsubopt.c -getsubopt: cc succeeded -getsubopt: yes - -isblank: testing... -cc -g -W -Wall -Wstrict-prototypes -Wno-unused-parameter -Wwrite-strings -Wno-unused -Werror -o test-isblank test-isblank.c -isblank: cc succeeded -isblank: yes - -mkdtemp: testing... -cc -g -W -Wall -Wstrict-prototypes -Wno-unused-parameter -Wwrite-strings -Wno-unused -Werror -o test-mkdtemp test-mkdtemp.c -mkdtemp: cc succeeded -mkdtemp: yes - -mmap: testing... -cc -g -W -Wall -Wstrict-prototypes -Wno-unused-parameter -Wwrite-strings -Wno-unused -Werror -o test-mmap test-mmap.c -mmap: cc succeeded -mmap: yes - -pledge: testing... -cc -g -W -Wall -Wstrict-prototypes -Wno-unused-parameter -Wwrite-strings -Wno-unused -Werror -o test-pledge test-pledge.c -test-pledge.c:6:11: error: implicit declaration of function 'pledge' is invalid in C99 [-Werror,-Wimplicit-function-declaration] - return !!pledge("stdio", NULL); - ^ -1 error generated. -pledge: cc failed with 1 - -progname: testing... -cc -g -W -Wall -Wstrict-prototypes -Wno-unused-parameter -Wwrite-strings -Wno-unused -Werror -o test-progname test-progname.c -progname: cc succeeded -progname: yes - -reallocarray: testing... -cc -g -W -Wall -Wstrict-prototypes -Wno-unused-parameter -Wwrite-strings -Wno-unused -Werror -o test-reallocarray test-reallocarray.c -reallocarray: cc succeeded -reallocarray: yes - -rewb-bsd: testing... -cc -g -W -Wall -Wstrict-prototypes -Wno-unused-parameter -Wwrite-strings -Wno-unused -Werror -o test-rewb-bsd test-rewb-bsd.c -test-rewb-bsd.c:11:42: error: use of undeclared identifier 'NULL' - if (regexec(&re, "the word is here", 0, NULL, 0)) - ^ -test-rewb-bsd.c:13:35: error: use of undeclared identifier 'NULL' - if (regexec(&re, "same word", 0, NULL, 0)) - ^ -test-rewb-bsd.c:15:36: error: use of undeclared identifier 'NULL' - if (regexec(&re, "word again", 0, NULL, 0)) - ^ -test-rewb-bsd.c:17:30: error: use of undeclared identifier 'NULL' - if (regexec(&re, "word", 0, NULL, 0)) - ^ -test-rewb-bsd.c:19:31: error: use of undeclared identifier 'NULL' - if (regexec(&re, "wordy", 0, NULL, 0) != REG_NOMATCH) - ^ -test-rewb-bsd.c:21:31: error: use of undeclared identifier 'NULL' - if (regexec(&re, "sword", 0, NULL, 0) != REG_NOMATCH) - ^ -test-rewb-bsd.c:23:34: error: use of undeclared identifier 'NULL' - if (regexec(&re, "reworded", 0, NULL, 0) != REG_NOMATCH) - ^ -7 errors generated. -rewb-bsd: cc failed with 1 - -rewb-sysv: testing... -cc -g -W -Wall -Wstrict-prototypes -Wno-unused-parameter -Wwrite-strings -Wno-unused -Werror -o test-rewb-sysv test-rewb-sysv.c -test-rewb-sysv.c:11:42: error: use of undeclared identifier 'NULL' - if (regexec(&re, "the word is here", 0, NULL, 0)) - ^ -test-rewb-sysv.c:13:35: error: use of undeclared identifier 'NULL' - if (regexec(&re, "same word", 0, NULL, 0)) - ^ -test-rewb-sysv.c:15:36: error: use of undeclared identifier 'NULL' - if (regexec(&re, "word again", 0, NULL, 0)) - ^ -test-rewb-sysv.c:17:30: error: use of undeclared identifier 'NULL' - if (regexec(&re, "word", 0, NULL, 0)) - ^ -test-rewb-sysv.c:19:31: error: use of undeclared identifier 'NULL' - if (regexec(&re, "wordy", 0, NULL, 0) != REG_NOMATCH) - ^ -test-rewb-sysv.c:21:31: error: use of undeclared identifier 'NULL' - if (regexec(&re, "sword", 0, NULL, 0) != REG_NOMATCH) - ^ -test-rewb-sysv.c:23:34: error: use of undeclared identifier 'NULL' - if (regexec(&re, "reworded", 0, NULL, 0) != REG_NOMATCH) - ^ -7 errors generated. -rewb-sysv: cc failed with 1 - -strcasestr: testing... -cc -g -W -Wall -Wstrict-prototypes -Wno-unused-parameter -Wwrite-strings -Wno-unused -Werror -o test-strcasestr test-strcasestr.c -strcasestr: cc succeeded -strcasestr: yes - -stringlist: testing... -cc -g -W -Wall -Wstrict-prototypes -Wno-unused-parameter -Wwrite-strings -Wno-unused -Werror -o test-stringlist test-stringlist.c -test-stringlist.c:26:26: error: use of undeclared identifier 'NULL' - if ((sl = sl_init()) == NULL) - ^ -1 error generated. -stringlist: cc failed with 1 - -strlcat: testing... -cc -g -W -Wall -Wstrict-prototypes -Wno-unused-parameter -Wwrite-strings -Wno-unused -Werror -o test-strlcat test-strlcat.c -strlcat: cc succeeded -strlcat: yes - -strlcpy: testing... -cc -g -W -Wall -Wstrict-prototypes -Wno-unused-parameter -Wwrite-strings -Wno-unused -Werror -o test-strlcpy test-strlcpy.c -strlcpy: cc succeeded -strlcpy: yes - -strptime: testing... -cc -g -W -Wall -Wstrict-prototypes -Wno-unused-parameter -Wwrite-strings -Wno-unused -Werror -o test-strptime test-strptime.c -strptime: cc succeeded -strptime: yes - -strsep: testing... -cc -g -W -Wall -Wstrict-prototypes -Wno-unused-parameter -Wwrite-strings -Wno-unused -Werror -o test-strsep test-strsep.c -strsep: cc succeeded -strsep: yes - -strtonum: testing... -cc -g -W -Wall -Wstrict-prototypes -Wno-unused-parameter -Wwrite-strings -Wno-unused -Werror -o test-strtonum test-strtonum.c -strtonum: cc succeeded -strtonum: yes - -vasprintf: testing... -cc -g -W -Wall -Wstrict-prototypes -Wno-unused-parameter -Wwrite-strings -Wno-unused -Werror -o test-vasprintf test-vasprintf.c -vasprintf: cc succeeded -vasprintf: yes - -wchar: testing... -cc -g -W -Wall -Wstrict-prototypes -Wno-unused-parameter -Wwrite-strings -Wno-unused -Werror -o test-wchar test-wchar.c -wchar: cc succeeded -*wchar: yes - -sqlite3: testing... -cc -g -W -Wall -Wstrict-prototypes -Wno-unused-parameter -Wwrite-strings -Wno-unused -Werror -lsqlite3 -o test-sqlite3 test-sqlite3.c -test-sqlite3.c:20:10: fatal error: 'sqlite3.h' file not found -#include - ^ -1 error generated. -sqlite3: cc failed with 1 - -sqlite3: testing... -cc -g -W -Wall -Wstrict-prototypes -Wno-unused-parameter -Wwrite-strings -Wno-unused -Werror -I/usr/local/include -L/usr/local/lib -lsqlite3 -o test-sqlite3 test-sqlite3.c -sqlite3: cc succeeded -sqlite3: yes - -sqlite3_errstr: testing... -cc -g -W -Wall -Wstrict-prototypes -Wno-unused-parameter -Wwrite-strings -Wno-unused -Werror -L/usr/local/lib -lsqlite3 -o test-sqlite3_errstr test-sqlite3_errstr.c -test-sqlite3_errstr.c:2:10: fatal error: 'sqlite3.h' file not found -#include - ^ -1 error generated. -sqlite3_errstr: cc failed with 1 - -ohash: testing... -cc -g -W -Wall -Wstrict-prototypes -Wno-unused-parameter -Wwrite-strings -Wno-unused -Werror -o test-ohash test-ohash.c -test-ohash.c:4:10: fatal error: 'ohash.h' file not found -#include - ^ -1 error generated. -ohash: cc failed with 1 - -ohash: testing... -cc -g -W -Wall -Wstrict-prototypes -Wno-unused-parameter -Wwrite-strings -Wno-unused -Werror -lutil -o test-ohash test-ohash.c -test-ohash.c:4:10: fatal error: 'ohash.h' file not found -#include - ^ -1 error generated. -ohash: cc failed with 1 - -DBLIB="-L/usr/local/lib -lsqlite3 -lz" - -/usr/share/man:/usr/local/man:/usr/share/openssl/man:/usr/local/lib/perl5/site_perl/man:/usr/local/lib/perl5/5.20/perl/man:/usr/local/share/xpdf/man -manpath: yes - -config.h: written -Makefile.local: written diff --git a/configure b/configure index 6f2c4116bf30..1b8101582822 100755 --- a/configure +++ b/configure @@ -1,5 +1,7 @@ #!/bin/sh # +# $Id: configure,v 1.55 2017/01/12 15:45:05 schwarze Exp $ +# # Copyright (c) 2014, 2015, 2016 Ingo Schwarze # # Permission to use, copy, modify, and distribute this software for any @@ -33,26 +35,34 @@ echo "config.log: writing..." MANPATH_DEFAULT="/usr/share/man:/usr/X11R6/man:/usr/local/man" OSNAME= +UTF8_LOCALE= -CC=`printf "all:\\n\\t@echo \\\$(CC)\\n" | make -f -` -CFLAGS="-g -W -Wall -Wstrict-prototypes -Wno-unused-parameter -Wwrite-strings" +CC=`printf "all:\\n\\t@echo \\\$(CC)\\n" | env -i make -sf -` +CFLAGS="-g -W -Wall -Wmissing-prototypes -Wstrict-prototypes -Wwrite-strings" +CFLAGS="${CFLAGS} -Wno-unused-parameter" LDADD= LDFLAGS= +LD_NANOSLEEP= LD_OHASH= -LD_SQLITE3= STATIC="-static" -BUILD_DB=1 BUILD_CGI=0 +INSTALL_LIBMANDOC=0 HAVE_DIRENT_NAMLEN= +HAVE_EFTYPE= +HAVE_ENDIAN= HAVE_ERR= HAVE_FTS= +HAVE_FTS_COMPARE_CONST= HAVE_GETLINE= HAVE_GETSUBOPT= HAVE_ISBLANK= HAVE_MKDTEMP= -HAVE_MMAP= +HAVE_NANOSLEEP= +HAVE_NTOHL= +HAVE_OHASH= +HAVE_PATH_MAX= HAVE_PLEDGE= HAVE_PROGNAME= HAVE_REALLOCARRAY= @@ -66,14 +76,10 @@ HAVE_STRLCPY= HAVE_STRPTIME= HAVE_STRSEP= HAVE_STRTONUM= +HAVE_SYS_ENDIAN= HAVE_VASPRINTF= HAVE_WCHAR= -HAVE_SQLITE3= -HAVE_SQLITE3_ERRSTR= -HAVE_OHASH= -HAVE_MANPATH= - PREFIX="/usr/local" BINDIR= SBINDIR= @@ -125,9 +131,9 @@ COMP="${CC} ${CFLAGS} -Wno-unused -Werror" # If yes, use the override, if no, do not decide anything yet. # Arguments: lower-case test name, manual value ismanual() { - [ -z "${2}" ] && return 1 - echo "${1}: manual (${2})" 1>&2 - echo "${1}: manual (${2})" 1>&3 + [ -z "${3}" ] && return 1 + echo "${1}: manual (HAVE_${2}=${3})" 1>&2 + echo "${1}: manual (HAVE_${2}=${3})" 1>&3 echo 1>&3 return 0 } @@ -138,27 +144,27 @@ ismanual() { # Arguments: lower-case test name, upper-case test name, additional CFLAGS singletest() { cat 1>&3 << __HEREDOC__ -${1}: testing... -${COMP} ${3} -o test-${1} test-${1}.c +${1}${3}: testing... +${COMP} -o test-${1} test-${1}.c ${3} __HEREDOC__ - if ${COMP} ${3} -o "test-${1}" "test-${1}.c" 1>&3 2>&3; then - echo "${1}: ${CC} succeeded" 1>&3 + if ${COMP} -o "test-${1}" "test-${1}.c" ${3} 1>&3 2>&3; then + echo "${1}${3}: ${CC} succeeded" 1>&3 else - echo "${1}: ${CC} failed with $?" 1>&3 + echo "${1}${3}: ${CC} failed with $?" 1>&3 echo 1>&3 return 1 fi if ./test-${1} 1>&3 2>&3; then - echo "${1}: yes" 1>&2 - echo "${1}: yes" 1>&3 + echo "${1}${3}: yes" 1>&2 + echo "${1}${3}: yes" 1>&3 echo 1>&3 eval HAVE_${2}=1 rm "test-${1}" return 0 else - echo "${1}: execution failed with $?" 1>&3 + echo "${1}${3}: execution failed with $?" 1>&3 echo 1>&3 rm "test-${1}" return 1 @@ -170,22 +176,42 @@ __HEREDOC__ # Arguments: lower case name, upper case name, additional CFLAGS runtest() { eval _manual=\${HAVE_${2}} - ismanual "${1}" "${_manual}" && return 0 + ismanual "${1}" "${2}" "${_manual}" && return 0 singletest "${1}" "${2}" "${3}" && return 0 - echo "${1}: no" 1>&2 + echo "${1}${3}: no" 1>&2 eval HAVE_${2}=0 return 1 } +# Select a UTF-8 locale. +get_locale() { + [ -n "${HAVE_WCHAR}" ] && [ "${HAVE_WCHAR}" -eq 0 ] && return 0 + ismanual UTF8_LOCALE UTF8_LOCALE "$UTF8_LOCALE" && return 0 + echo "UTF8_LOCALE: testing..." 1>&3 + UTF8_LOCALE=`locale -a | grep -i '^en_US\.UTF-*8$' | head -n 1` + if [ -z "${UTF8_LOCALE}" ]; then + UTF8_LOCALE=`locale -a | grep -i '\.UTF-*8' | head -n 1` + [ -n "${UTF8_LOCALE}" ] || return 1 + fi + echo "UTF8_LOCALE=${UTF8_LOCALE}" 1>&2 + echo "UTF8_LOCALE=${UTF8_LOCALE}" 1>&3 + echo 1>&3 + return 0; +} + + # --- library functions --- runtest dirent-namlen DIRENT_NAMLEN || true +runtest be32toh ENDIAN || true +runtest be32toh SYS_ENDIAN -DSYS_ENDIAN || true +runtest EFTYPE EFTYPE || true runtest err ERR || true -runtest fts FTS || true runtest getline GETLINE || true runtest getsubopt GETSUBOPT || true runtest isblank ISBLANK || true runtest mkdtemp MKDTEMP || true -runtest mmap MMAP || true +runtest ntohl NTOHL || true +runtest PATH_MAX PATH_MAX || true runtest pledge PLEDGE || true runtest sandbox_init SANDBOX_INIT || true runtest progname PROGNAME || true @@ -200,48 +226,48 @@ runtest strptime STRPTIME || true runtest strsep STRSEP || true runtest strtonum STRTONUM || true runtest vasprintf VASPRINTF || true -runtest wchar WCHAR || true -# --- sqlite3 --- -if [ ${BUILD_DB} -eq 0 ]; then - echo "BUILD_DB=0 (manual)" 1>&2 - echo "BUILD_DB=0 (manual)" 1>&3 - echo 1>&3 - HAVE_SQLITE3=0 -elif ismanual sqlite3 "${HAVE_SQLITE3}"; then - if [ -z "${LD_SQLITE3}" ]; then - LD_SQLITE3="-lsqlite3" - fi -elif [ -n "${LD_SQLITE3}" ]; then - runtest sqlite3 SQLITE3 "${LD_SQLITE3}" || true -elif singletest sqlite3 SQLITE3 "-lsqlite3"; then - LD_SQLITE3="-lsqlite3" -elif runtest sqlite3 SQLITE3 \ - "-I/usr/local/include -L/usr/local/lib -lsqlite3"; then - LD_SQLITE3="-L/usr/local/lib -lsqlite3" - CFLAGS="${CFLAGS} -I/usr/local/include" -fi -if [ ${HAVE_SQLITE3} -eq 0 ]; then - LD_SQLITE3= - if [ ${BUILD_DB} -gt 0 ]; then - echo "BUILD_DB=0 (no sqlite3)" 1>&2 - echo "BUILD_DB=0 (no sqlite3)" 1>&3 - echo 1>&3 - BUILD_DB=0 - fi +if [ ${HAVE_ENDIAN} -eq 0 -a \ + ${HAVE_SYS_ENDIAN} -eq 0 -a \ + ${HAVE_NTOHL} -eq 0 ]; then + echo "FATAL: no endian conversion functions found" 1>&2 + echo "FATAL: no endian conversion functions found" 1>&3 + exit 1 fi -# --- sqlite3_errstr --- -if [ ${BUILD_DB} -eq 0 ]; then - HAVE_SQLITE3_ERRSTR=1 -elif ismanual sqlite3_errstr "${HAVE_SQLITE3_ERRSTR}"; then - : +if ismanual fts FTS ${HAVE_FTS}; then + HAVE_FTS_COMPARE_CONST=0 +elif runtest fts FTS_COMPARE_CONST -DFTS_COMPARE_CONST; then + HAVE_FTS=1 else - runtest sqlite3_errstr SQLITE3_ERRSTR "${LD_SQLITE3}" || true + runtest fts FTS || true +fi + +# --- wide character and locale support --- +if get_locale; then + runtest wchar WCHAR -DUTF8_LOCALE=\"${UTF8_LOCALE}\" || true +else + HAVE_WCHAR=0 + echo "wchar: no (no UTF8_LOCALE)" 1>&2 + echo "wchar: no (no UTF8_LOCALE)" 1>&3 +fi + +# --- nanosleep --- +if [ -n "${LD_NANOSLEEP}" ]; then + runtest nanosleep NANOSLEEP "${LD_NANOSLEEP}" || true +elif singletest nanosleep NANOSLEEP; then + : +elif runtest nanosleep NANOSLEEP "-lrt"; then + LD_NANOSLEEP="-lrt" +fi +if [ "${HAVE_NANOSLEEP}" -eq 0 ]; then + echo "FATAL: nanosleep: no" 1>&2 + echo "FATAL: nanosleep: no" 1>&3 + exit 1 fi # --- ohash --- -if ismanual ohash "${HAVE_OHASH}"; then +if ismanual ohash OHASH "${HAVE_OHASH}"; then : elif [ -n "${LD_OHASH}" ]; then runtest ohash OHASH "${LD_OHASH}" || true @@ -255,26 +281,11 @@ if [ "${HAVE_OHASH}" -eq 0 ]; then fi # --- LDADD --- -LDADD="${LDADD} ${LD_SQLITE3} ${LD_OHASH} -lz" +LDADD="${LDADD} ${LD_NANOSLEEP} ${LD_OHASH} -lz" echo "LDADD=\"${LDADD}\"" 1>&2 echo "LDADD=\"${LDADD}\"" 1>&3 echo 1>&3 -# --- manpath --- -if ismanual manpath "${HAVE_MANPATH}"; then - : -elif manpath 1>&3 2>&3; then - echo "manpath: yes" 1>&2 - echo "manpath: yes" 1>&3 - echo 1>&3 - HAVE_MANPATH=1 -else - echo "manpath: no" 1>&2 - echo "manpath: no" 1>&3 - echo 1>&3 - HAVE_MANPATH=0 -fi - # --- write config.h --- exec > config.h @@ -284,8 +295,9 @@ cat << __HEREDOC__ #error "Do not use C++. See the INSTALL file." #endif -#ifndef MANDOC_CONFIG_H -#define MANDOC_CONFIG_H +#if !defined(__GNUC__) || (__GNUC__ < 4) +#define __attribute__(x) +#endif #if defined(__linux__) || defined(__MINT__) #define _GNU_SOURCE /* See test-*.c what needs this. */ @@ -303,17 +315,26 @@ echo echo "#define MAN_CONF_FILE \"/etc/${MANM_MANCONF}\"" echo "#define MANPATH_DEFAULT \"${MANPATH_DEFAULT}\"" [ -n "${OSNAME}" ] && echo "#define OSNAME \"${OSNAME}\"" +[ -n "${UTF8_LOCALE}" ] && echo "#define UTF8_LOCALE \"${UTF8_LOCALE}\"" [ -n "${HOMEBREWDIR}" ] && echo "#define HOMEBREWDIR \"${HOMEBREWDIR}\"" +[ ${HAVE_EFTYPE} -eq 0 ] && echo "#define EFTYPE EINVAL" +[ ${HAVE_PATH_MAX} -eq 0 ] && echo "#define PATH_MAX 4096" +if [ ${HAVE_ENDIAN} -eq 0 -a ${HAVE_SYS_ENDIAN} -eq 0 ]; then + echo "#define be32toh ntohl" + echo "#define htobe32 htonl" +fi cat << __HEREDOC__ #define HAVE_DIRENT_NAMLEN ${HAVE_DIRENT_NAMLEN} +#define HAVE_ENDIAN ${HAVE_ENDIAN} #define HAVE_ERR ${HAVE_ERR} #define HAVE_FTS ${HAVE_FTS} +#define HAVE_FTS_COMPARE_CONST ${HAVE_FTS_COMPARE_CONST} #define HAVE_GETLINE ${HAVE_GETLINE} #define HAVE_GETSUBOPT ${HAVE_GETSUBOPT} #define HAVE_ISBLANK ${HAVE_ISBLANK} #define HAVE_MKDTEMP ${HAVE_MKDTEMP} -#define HAVE_MMAP ${HAVE_MMAP} +#define HAVE_NTOHL ${HAVE_NTOHL} #define HAVE_PLEDGE ${HAVE_PLEDGE} #define HAVE_PROGNAME ${HAVE_PROGNAME} #define HAVE_REALLOCARRAY ${HAVE_REALLOCARRAY} @@ -327,12 +348,10 @@ cat << __HEREDOC__ #define HAVE_STRPTIME ${HAVE_STRPTIME} #define HAVE_STRSEP ${HAVE_STRSEP} #define HAVE_STRTONUM ${HAVE_STRTONUM} +#define HAVE_SYS_ENDIAN ${HAVE_SYS_ENDIAN} #define HAVE_VASPRINTF ${HAVE_VASPRINTF} #define HAVE_WCHAR ${HAVE_WCHAR} -#define HAVE_SQLITE3 ${HAVE_SQLITE3} -#define HAVE_SQLITE3_ERRSTR ${HAVE_SQLITE3_ERRSTR} #define HAVE_OHASH ${HAVE_OHASH} -#define HAVE_MANPATH ${HAVE_MANPATH} #define BINM_APROPOS "${BINM_APROPOS}" #define BINM_MAKEWHATIS "${BINM_MAKEWHATIS}" @@ -369,9 +388,6 @@ fi [ ${HAVE_REALLOCARRAY} -eq 0 ] && \ echo "extern void *reallocarray(void *, size_t, size_t);" -[ ${BUILD_DB} -gt 0 -a ${HAVE_SQLITE3_ERRSTR} -eq 0 ] && - echo "extern const char *sqlite3_errstr(int);" - [ ${HAVE_STRCASESTR} -eq 0 ] && \ echo "extern char *strcasestr(const char *, const char *);" @@ -390,9 +406,6 @@ fi [ ${HAVE_VASPRINTF} -eq 0 ] && \ echo "extern int vasprintf(char **, const char *, va_list);" -echo -echo "#endif /* MANDOC_CONFIG_H */" - echo "config.h: written" 1>&2 echo "config.h: written" 1>&3 @@ -414,16 +427,10 @@ exec > Makefile.local [ -z "${INSTALL_MAN}" ] && INSTALL_MAN="${INSTALL} -m 0444" [ -z "${INSTALL_DATA}" ] && INSTALL_DATA="${INSTALL} -m 0444" -if [ ${BUILD_DB} -eq 0 -a ${BUILD_CGI} -gt 0 ]; then - echo "BUILD_CGI=0 (no BUILD_DB)" 1>&2 - echo "BUILD_CGI=0 (no BUILD_DB)" 1>&3 - BUILD_CGI=0 -fi - -BUILD_TARGETS="base-build" -[ ${BUILD_CGI} -gt 0 ] && BUILD_TARGETS="${BUILD_TARGETS} cgi-build" -INSTALL_TARGETS="base-install" -[ ${BUILD_DB} -gt 0 ] && INSTALL_TARGETS="${INSTALL_TARGETS} db-install" +BUILD_TARGETS= +[ ${BUILD_CGI} -gt 0 ] && BUILD_TARGETS="cgi-build" +INSTALL_TARGETS= +[ ${INSTALL_LIBMANDOC} -gt 0 ] && INSTALL_TARGETS="lib-install" [ ${BUILD_CGI} -gt 0 ] && INSTALL_TARGETS="${INSTALL_TARGETS} cgi-install" cat << __HEREDOC__ @@ -461,9 +468,6 @@ INSTALL_MAN = ${INSTALL_MAN} INSTALL_DATA = ${INSTALL_DATA} __HEREDOC__ -[ ${BUILD_DB} -gt 0 ] && \ - echo "MAIN_OBJS = \$(BASE_OBJS) \$(DB_OBJS)" - echo "Makefile.local: written" 1>&2 echo "Makefile.local: written" 1>&3 diff --git a/configure.local.example b/configure.local.example index c955a80a3869..d5799a5a9e5c 100644 --- a/configure.local.example +++ b/configure.local.example @@ -1,4 +1,4 @@ -# $Id: configure.local.example,v 1.13 2016/07/14 11:09:06 schwarze Exp $ +# $Id: configure.local.example,v 1.22 2016/11/19 15:24:51 schwarze Exp $ # # Copyright (c) 2014, 2015, 2016 Ingo Schwarze # @@ -48,10 +48,17 @@ HAVE_WCHAR=1 HAVE_WCHAR=0 +# For -Tutf8 mode, mandoc needs to set an arbitrary locale having +# a UTF-8 character set. If autodetection of a suitable locale +# fails or selects an undesirable locale, you can manually choose +# the locale for -Tutf8 mode: + +UTF8_LOCALE=en_US.UTF-8 + # When man(1) or apropos(1) is called without -m and -M options, -# MANPATH is not set in the environment, man.conf(5) is not available -# and manpath(1) not used, manuals are searched for in the following -# directory trees by default. +# MANPATH is not set in the environment, and man.conf(5) is not +# available, manuals are searched for in the following directory +# trees by default. MANPATH_DEFAULT="/usr/share/man:/usr/X11R6/man:/usr/local/man" @@ -65,7 +72,7 @@ MANPATH_DEFAULT="/usr/share/man:/usr/X11R6/man:/usr/local/man" # If you do not want uname(3) to be called but instead want a fixed # string to be used, use the following line: -OSNAME="OpenBSD 5.9" +OSNAME="OpenBSD 6.0" # The following installation directories are used. # It is possible to set only one or a few of these variables, @@ -76,25 +83,8 @@ OSNAME="OpenBSD 5.9" PREFIX="/usr/local" BINDIR="${PREFIX}/bin" SBINDIR="${PREFIX}/sbin" -INCLUDEDIR="${PREFIX}/include/mandoc" -LIBDIR="${PREFIX}/lib/mandoc" MANDIR="${PREFIX}/man" -# The man(1) utility needs to know where the manuals reside. -# We know of two ways to tell it: via manpath(1) or man.conf(5). -# The latter is used by OpenBSD and NetBSD, the former by most -# other systems. - -# Force usage of manpath(1). -# If it is not installed or not operational, -# man(1), makewhatis(8), and apropos(1) will not work properly. -HAVE_MANPATH=1 - -# Force usage of man.conf(5). -# If it does not exist or contains no valid configuration, -# man(1), makewhatis(8), and apropos(1) will not work properly. -HAVE_MANPATH=0 - # Some distributions may want to avoid naming conflicts # with the configuration files of other man(1) implementations. # This changes the name of the installed section 5 manual page as well. @@ -113,14 +103,20 @@ MANM_ROFF="mandoc_roff" # default is "roff" MANM_EQN="mandoc_eqn" # default is "eqn" MANM_TBL="mandoc_tbl" # default is "tbl" -# Some distributions may want to avoid naming conflicts -# with other man(1) and soelim(1) utilities. +# Some distributions may want to avoid naming conflicts with +# other man(1), apropos(1), makewhatis(8), or soelim(1) utilities. # If you want to change the names of binary programs, # the following alternative names are suggested. # Using different names is possible as well. -# This changes the names of the installed section 1 manual pages as well. +# This changes the names of the installed section 1 and section 8 +# manual pages as well. +# It is possible to set only one or two of these variables, +# there is no need to copy the whole block. BINM_MAN=mman # default is "man" +BINM_APROPOS=mapropos # default is "apropos" +BINM_WHATIS=mwhatis # default is "whatis" +BINM_MAKEWHATIS=mandocdb # default is "makewhatis" BINM_SOELIM=msoelim # default is "soelim" # Before falling back to the bundled version of the ohash(3) hashing @@ -131,11 +127,24 @@ BINM_SOELIM=msoelim # default is "soelim" LD_OHASH="-lutil" -# Some platforms may need additional linker flags to link against libmandoc -# that are not autodetected. -# For example, Solaris 9 and 10 need -lrt for nanosleep(2). +# When library autodetection decides to use -L/usr/local/lib, +# -I/usr/local/include is automatically added to CFLAGS. +# If you manually set LD_OHASH to something including -L/usr/local/lib, +# chances are you will also need the following line: -LDADD="-lrt" +CFLAGS="${CFLAGS} -I/usr/local/include" + +# Some platforms may need an additional linker flag for nanosleep(2). +# If none is needed or it is -lrt, it is autodetected. +# Otherwise, set the following variable. + +LD_NANOSLEEP="-lrt" + +# Some platforms might need additional linker flags to link against +# libmandoc that are not autodetected, though no such cases are +# currently known. + +LDADD="-lm" # Some systems may want to set additional linker flags for all the # binaries, not only for those using libmandoc, for example for @@ -152,43 +161,6 @@ INSTALL_LIB="${INSTALL} -m 0444" INSTALL_MAN="${INSTALL} -m 0444" INSTALL_DATA="${INSTALL} -m 0444" -# --- user settings related to database support ------------------------ - -# By default, building makewhatis(8) and apropos(1) is enabled. -# To disable it, for example to avoid the dependency on SQLite3, -# use the following line. It that case, the remaining settings -# in this section are irrelevant. - -BUILD_DB=0 - -# Autoconfiguration tries the following linker flags to find the -# SQLite3 library installed on your system. If none of these work, -# set the following variable to specify the required linker flags. - -LD_SQLITE3="-lsqlite3" -LD_SQLITE3="-L/usr/local/lib -lsqlite3" - -# When library autodetection decides to use -L/usr/local/lib, -# -I/usr/local/include is automatically added to CFLAGS. -# If you manually set LD_SQLITE3 to something including -L/usr/local/lib, -# chances are you will also need the following line: - -CFLAGS="${CFLAGS} -I/usr/local/include" - -# Some distributions may want to avoid naming conflicts -# with another implementation of apropos(1) and makewhatis(8). -# If you want to change the names of the binary programs, -# the following alternative names are suggested. -# Using other names is possible as well. -# This changes the names of the installed section 1 and section 8 -# manual pages as well. -# It is possible to set only one or two of these variables, -# there is no need to copy the whole block. - -BINM_APROPOS=mapropos # default is "apropos" -BINM_WHATIS=mwhatis # default is "whatis" -BINM_MAKEWHATIS=mandocdb # default is "makewhatis" - # When using the "homebrew" package manager on Mac OS X, the actual # manuals are located in a so-called "cellar" and only symlinked # into the manual trees. To allow mandoc to follow such symlinks, @@ -198,11 +170,25 @@ BINM_MAKEWHATIS=mandocdb # default is "makewhatis" PREFIX="/usr/local" HOMEBREWDIR="${PREFIX}/Cellar" -# --- user settings related man.cgi ------------------------------------ +# --- user settings for the mandoc(3) library -------------------------- + +# By default, libmandoc.a is not installed. It is almost never needed +# because there is almost no non-mandoc software out there using this +# library. The one notable exception is NetBSD apropos(1). +# So, when building for the NetBSD base system - but not for NetBSD +# ports nor for pkgsrc! - you may want the following: + +INSTALL_LIBMANDOC=1 + +# The following settings are only used when INSTALL_LIBMANDOC is set. + +INCLUDEDIR="${PREFIX}/include/mandoc" +LIBDIR="${PREFIX}/lib/mandoc" + +# --- user settings related to man.cgi --------------------------------- # By default, building man.cgi(8) is disabled. To enable it, copy # cgi.h.example to cgi.h, edit it, and use the following line. -# Obviously, this requires that BUILD_DB is enabled, too. BUILD_CGI=1 @@ -254,13 +240,18 @@ CFLAGS="-g" # be regarded as successful). HAVE_DIRENT_NAMLEN=0 +HAVE_ENDIAN=0 +HAVE_EFTYPE=0 HAVE_ERR=0 -HAVE_FTS=0 +HAVE_FTS=0 # Setting this implies HAVE_FTS_COMPARE_CONST=0. +HAVE_FTS_COMPARE_CONST=0 # Setting this implies HAVE_FTS=1. HAVE_GETLINE=0 HAVE_GETSUBOPT=0 HAVE_ISBLANK=0 HAVE_MKDTEMP=0 -HAVE_MMAP=0 +HAVE_NTOHL=0 +HAVE_OHASH=0 +HAVE_PATH_MAX=0 HAVE_PLEDGE=0 HAVE_PROGNAME=0 HAVE_REALLOCARRAY=0 @@ -273,9 +264,6 @@ HAVE_STRLCPY=0 HAVE_STRPTIME=0 HAVE_STRSEP=0 HAVE_STRTONUM=0 +HAVE_SYS_ENDIAN=0 HAVE_VASPRINTF=0 HAVE_WCHAR=0 - -HAVE_SQLITE3=0 -HAVE_SQLITE3_ERRSTR=0 -HAVE_OHASH=0 diff --git a/dba.c b/dba.c new file mode 100644 index 000000000000..bb1539b741e9 --- /dev/null +++ b/dba.c @@ -0,0 +1,508 @@ +/* $Id: dba.c,v 1.9 2017/01/15 15:28:55 schwarze Exp $ */ +/* + * Copyright (c) 2016, 2017 Ingo Schwarze + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * Allocation-based version of the mandoc database, for read-write access. + * The interface is defined in "dba.h". + */ +#include "config.h" + +#include +#if HAVE_ENDIAN +#include +#elif HAVE_SYS_ENDIAN +#include +#elif HAVE_NTOHL +#include +#endif +#include +#include +#include +#include +#include +#include + +#include "mandoc_aux.h" +#include "mandoc_ohash.h" +#include "mansearch.h" +#include "dba_write.h" +#include "dba_array.h" +#include "dba.h" + +struct macro_entry { + struct dba_array *pages; + char value[]; +}; + +static void *prepend(const char *, char); +static void dba_pages_write(struct dba_array *); +static int compare_names(const void *, const void *); +static int compare_strings(const void *, const void *); + +static struct macro_entry + *get_macro_entry(struct ohash *, const char *, int32_t); +static void dba_macros_write(struct dba_array *); +static void dba_macro_write(struct ohash *); +static int compare_entries(const void *, const void *); + + +/*** top-level functions **********************************************/ + +struct dba * +dba_new(int32_t npages) +{ + struct dba *dba; + struct ohash *macro; + int32_t im; + + dba = mandoc_malloc(sizeof(*dba)); + dba->pages = dba_array_new(npages, DBA_GROW); + dba->macros = dba_array_new(MACRO_MAX, 0); + for (im = 0; im < MACRO_MAX; im++) { + macro = mandoc_malloc(sizeof(*macro)); + mandoc_ohash_init(macro, 4, + offsetof(struct macro_entry, value)); + dba_array_set(dba->macros, im, macro); + } + return dba; +} + +void +dba_free(struct dba *dba) +{ + struct dba_array *page; + struct ohash *macro; + struct macro_entry *entry; + unsigned int slot; + + dba_array_FOREACH(dba->macros, macro) { + for (entry = ohash_first(macro, &slot); entry != NULL; + entry = ohash_next(macro, &slot)) { + dba_array_free(entry->pages); + free(entry); + } + ohash_delete(macro); + free(macro); + } + dba_array_free(dba->macros); + + dba_array_undel(dba->pages); + dba_array_FOREACH(dba->pages, page) { + dba_array_free(dba_array_get(page, DBP_NAME)); + dba_array_free(dba_array_get(page, DBP_SECT)); + dba_array_free(dba_array_get(page, DBP_ARCH)); + free(dba_array_get(page, DBP_DESC)); + dba_array_free(dba_array_get(page, DBP_FILE)); + dba_array_free(page); + } + dba_array_free(dba->pages); + + free(dba); +} + +/* + * Write the complete mandoc database to disk; the format is: + * - One integer each for magic and version. + * - One pointer each to the macros table and to the final magic. + * - The pages table. + * - The macros table. + * - And at the very end, the magic integer again. + */ +int +dba_write(const char *fname, struct dba *dba) +{ + int save_errno; + int32_t pos_end, pos_macros, pos_macros_ptr; + + if (dba_open(fname) == -1) + return -1; + dba_int_write(MANDOCDB_MAGIC); + dba_int_write(MANDOCDB_VERSION); + pos_macros_ptr = dba_skip(1, 2); + dba_pages_write(dba->pages); + pos_macros = dba_tell(); + dba_macros_write(dba->macros); + pos_end = dba_tell(); + dba_int_write(MANDOCDB_MAGIC); + dba_seek(pos_macros_ptr); + dba_int_write(pos_macros); + dba_int_write(pos_end); + if (dba_close() == -1) { + save_errno = errno; + unlink(fname); + errno = save_errno; + return -1; + } + return 0; +} + + +/*** functions for handling pages *************************************/ + +/* + * Create a new page and append it to the pages table. + */ +struct dba_array * +dba_page_new(struct dba_array *pages, const char *arch, + const char *desc, const char *file, enum form form) +{ + struct dba_array *page, *entry; + + page = dba_array_new(DBP_MAX, 0); + entry = dba_array_new(1, DBA_STR | DBA_GROW); + dba_array_add(page, entry); + entry = dba_array_new(1, DBA_STR | DBA_GROW); + dba_array_add(page, entry); + if (arch != NULL && *arch != '\0') { + entry = dba_array_new(1, DBA_STR | DBA_GROW); + dba_array_add(entry, (void *)arch); + } else + entry = NULL; + dba_array_add(page, entry); + dba_array_add(page, mandoc_strdup(desc)); + entry = dba_array_new(1, DBA_STR | DBA_GROW); + dba_array_add(entry, prepend(file, form)); + dba_array_add(page, entry); + dba_array_add(pages, page); + return page; +} + +/* + * Add a section, architecture, or file name to an existing page. + * Passing the NULL pointer for the architecture makes the page MI. + * In that case, any earlier or later architectures are ignored. + */ +void +dba_page_add(struct dba_array *page, int32_t ie, const char *str) +{ + struct dba_array *entries; + char *entry; + + entries = dba_array_get(page, ie); + if (ie == DBP_ARCH) { + if (entries == NULL) + return; + if (str == NULL || *str == '\0') { + dba_array_free(entries); + dba_array_set(page, DBP_ARCH, NULL); + return; + } + } + if (*str == '\0') + return; + dba_array_FOREACH(entries, entry) { + if (ie == DBP_FILE && *entry < ' ') + entry++; + if (strcmp(entry, str) == 0) + return; + } + dba_array_add(entries, (void *)str); +} + +/* + * Add an additional name to an existing page. + */ +void +dba_page_alias(struct dba_array *page, const char *name, uint64_t mask) +{ + struct dba_array *entries; + char *entry; + char maskbyte; + + if (*name == '\0') + return; + maskbyte = mask & NAME_MASK; + entries = dba_array_get(page, DBP_NAME); + dba_array_FOREACH(entries, entry) { + if (strcmp(entry + 1, name) == 0) { + *entry |= maskbyte; + return; + } + } + dba_array_add(entries, prepend(name, maskbyte)); +} + +/* + * Return a pointer to a temporary copy of instr with inbyte prepended. + */ +static void * +prepend(const char *instr, char inbyte) +{ + static char *outstr = NULL; + static size_t outlen = 0; + size_t newlen; + + newlen = strlen(instr) + 1; + if (newlen > outlen) { + outstr = mandoc_realloc(outstr, newlen + 1); + outlen = newlen; + } + *outstr = inbyte; + memcpy(outstr + 1, instr, newlen); + return outstr; +} + +/* + * Write the pages table to disk; the format is: + * - One integer containing the number of pages. + * - For each page, five pointers to the names, sections, + * architectures, description, and file names of the page. + * MI pages write 0 instead of the architecture pointer. + * - One list each for names, sections, architectures, descriptions and + * file names. The description for each page ends with a NUL byte. + * For all the other lists, each string ends with a NUL byte, + * and the last string for a page ends with two NUL bytes. + * - To assure alignment of following integers, + * the end is padded with NUL bytes up to a multiple of four bytes. + */ +static void +dba_pages_write(struct dba_array *pages) +{ + struct dba_array *page, *entry; + int32_t pos_pages, pos_end; + + pos_pages = dba_array_writelen(pages, 5); + dba_array_FOREACH(pages, page) { + dba_array_setpos(page, DBP_NAME, dba_tell()); + entry = dba_array_get(page, DBP_NAME); + dba_array_sort(entry, compare_names); + dba_array_writelst(entry); + } + dba_array_FOREACH(pages, page) { + dba_array_setpos(page, DBP_SECT, dba_tell()); + entry = dba_array_get(page, DBP_SECT); + dba_array_sort(entry, compare_strings); + dba_array_writelst(entry); + } + dba_array_FOREACH(pages, page) { + if ((entry = dba_array_get(page, DBP_ARCH)) != NULL) { + dba_array_setpos(page, DBP_ARCH, dba_tell()); + dba_array_sort(entry, compare_strings); + dba_array_writelst(entry); + } else + dba_array_setpos(page, DBP_ARCH, 0); + } + dba_array_FOREACH(pages, page) { + dba_array_setpos(page, DBP_DESC, dba_tell()); + dba_str_write(dba_array_get(page, DBP_DESC)); + } + dba_array_FOREACH(pages, page) { + dba_array_setpos(page, DBP_FILE, dba_tell()); + dba_array_writelst(dba_array_get(page, DBP_FILE)); + } + pos_end = dba_align(); + dba_seek(pos_pages); + dba_array_FOREACH(pages, page) + dba_array_writepos(page); + dba_seek(pos_end); +} + +static int +compare_names(const void *vp1, const void *vp2) +{ + const char *cp1, *cp2; + int diff; + + cp1 = *(char **)vp1; + cp2 = *(char **)vp2; + return (diff = *cp2 - *cp1) ? diff : + strcasecmp(cp1 + 1, cp2 + 1); +} + +static int +compare_strings(const void *vp1, const void *vp2) +{ + const char *cp1, *cp2; + + cp1 = *(char **)vp1; + cp2 = *(char **)vp2; + return strcmp(cp1, cp2); +} + +/*** functions for handling macros ************************************/ + +/* + * In the hash table for a single macro, look up an entry by + * the macro value or add an empty one if it doesn't exist yet. + */ +static struct macro_entry * +get_macro_entry(struct ohash *macro, const char *value, int32_t np) +{ + struct macro_entry *entry; + size_t len; + unsigned int slot; + + slot = ohash_qlookup(macro, value); + if ((entry = ohash_find(macro, slot)) == NULL) { + len = strlen(value) + 1; + entry = mandoc_malloc(sizeof(*entry) + len); + memcpy(&entry->value, value, len); + entry->pages = dba_array_new(np, DBA_GROW); + ohash_insert(macro, slot, entry); + } + return entry; +} + +/* + * In addition to get_macro_entry(), add multiple page references, + * converting them from the on-disk format (byte offsets in the file) + * to page pointers in memory. + */ +void +dba_macro_new(struct dba *dba, int32_t im, const char *value, + const int32_t *pp) +{ + struct macro_entry *entry; + const int32_t *ip; + int32_t np; + + np = 0; + for (ip = pp; *ip; ip++) + np++; + + entry = get_macro_entry(dba_array_get(dba->macros, im), value, np); + for (ip = pp; *ip; ip++) + dba_array_add(entry->pages, dba_array_get(dba->pages, + be32toh(*ip) / 5 / sizeof(*ip) - 1)); +} + +/* + * In addition to get_macro_entry(), add one page reference, + * directly taking the in-memory page pointer as an argument. + */ +void +dba_macro_add(struct dba_array *macros, int32_t im, const char *value, + struct dba_array *page) +{ + struct macro_entry *entry; + + if (*value == '\0') + return; + entry = get_macro_entry(dba_array_get(macros, im), value, 1); + dba_array_add(entry->pages, page); +} + +/* + * Write the macros table to disk; the format is: + * - The number of macro tables (actually, MACRO_MAX). + * - That number of pointers to the individual macro tables. + * - The individual macro tables. + */ +static void +dba_macros_write(struct dba_array *macros) +{ + struct ohash *macro; + int32_t im, pos_macros, pos_end; + + pos_macros = dba_array_writelen(macros, 1); + im = 0; + dba_array_FOREACH(macros, macro) { + dba_array_setpos(macros, im++, dba_tell()); + dba_macro_write(macro); + } + pos_end = dba_tell(); + dba_seek(pos_macros); + dba_array_writepos(macros); + dba_seek(pos_end); +} + +/* + * Write one individual macro table to disk; the format is: + * - The number of entries in the table. + * - For each entry, two pointers, the first one to the value + * and the second one to the list of pages. + * - A list of values, each ending in a NUL byte. + * - To assure alignment of following integers, + * padding with NUL bytes up to a multiple of four bytes. + * - A list of pointers to pages, each list ending in a 0 integer. + */ +static void +dba_macro_write(struct ohash *macro) +{ + struct macro_entry **entries, *entry; + struct dba_array *page; + int32_t *kpos, *dpos; + unsigned int ie, ne, slot; + int use; + int32_t addr, pos_macro, pos_end; + + /* Temporary storage for filtering and sorting. */ + + ne = ohash_entries(macro); + entries = mandoc_reallocarray(NULL, ne, sizeof(*entries)); + kpos = mandoc_reallocarray(NULL, ne, sizeof(*kpos)); + dpos = mandoc_reallocarray(NULL, ne, sizeof(*dpos)); + + /* Build a list of non-empty entries and sort it. */ + + ne = 0; + for (entry = ohash_first(macro, &slot); entry != NULL; + entry = ohash_next(macro, &slot)) { + use = 0; + dba_array_FOREACH(entry->pages, page) + if (dba_array_getpos(page)) + use = 1; + if (use) + entries[ne++] = entry; + } + qsort(entries, ne, sizeof(*entries), compare_entries); + + /* Number of entries, and space for the pointer pairs. */ + + dba_int_write(ne); + pos_macro = dba_skip(2, ne); + + /* String table. */ + + for (ie = 0; ie < ne; ie++) { + kpos[ie] = dba_tell(); + dba_str_write(entries[ie]->value); + } + dba_align(); + + /* Pages table. */ + + for (ie = 0; ie < ne; ie++) { + dpos[ie] = dba_tell(); + dba_array_FOREACH(entries[ie]->pages, page) + if ((addr = dba_array_getpos(page))) + dba_int_write(addr); + dba_int_write(0); + } + pos_end = dba_tell(); + + /* Fill in the pointer pairs. */ + + dba_seek(pos_macro); + for (ie = 0; ie < ne; ie++) { + dba_int_write(kpos[ie]); + dba_int_write(dpos[ie]); + } + dba_seek(pos_end); + + free(entries); + free(kpos); + free(dpos); +} + +static int +compare_entries(const void *vp1, const void *vp2) +{ + const struct macro_entry *ep1, *ep2; + + ep1 = *(struct macro_entry **)vp1; + ep2 = *(struct macro_entry **)vp2; + return strcmp(ep1->value, ep2->value); +} diff --git a/dba.h b/dba.h new file mode 100644 index 000000000000..67a2759a2071 --- /dev/null +++ b/dba.h @@ -0,0 +1,50 @@ +/* $Id: dba.h,v 1.2 2016/08/17 20:46:56 schwarze Exp $ */ +/* + * Copyright (c) 2016 Ingo Schwarze + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * Public interface of the allocation-based version + * of the mandoc database, for read-write access. + * To be used by dba.c, dba_read.c, and makewhatis(8). + */ + +#define DBP_NAME 0 +#define DBP_SECT 1 +#define DBP_ARCH 2 +#define DBP_DESC 3 +#define DBP_FILE 4 +#define DBP_MAX 5 + +struct dba_array; + +struct dba { + struct dba_array *pages; + struct dba_array *macros; +}; + + +struct dba *dba_new(int32_t); +void dba_free(struct dba *); +struct dba *dba_read(const char *); +int dba_write(const char *, struct dba *); + +struct dba_array *dba_page_new(struct dba_array *, const char *, + const char *, const char *, enum form); +void dba_page_add(struct dba_array *, int32_t, const char *); +void dba_page_alias(struct dba_array *, const char *, uint64_t); + +void dba_macro_new(struct dba *, int32_t, + const char *, const int32_t *); +void dba_macro_add(struct dba_array *, int32_t, + const char *, struct dba_array *); diff --git a/dba_array.c b/dba_array.c new file mode 100644 index 000000000000..18c9f09f1a21 --- /dev/null +++ b/dba_array.c @@ -0,0 +1,188 @@ +/* $Id: dba_array.c,v 1.1 2016/07/19 21:31:55 schwarze Exp $ */ +/* + * Copyright (c) 2016 Ingo Schwarze + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * Allocation-based arrays for the mandoc database, for read-write access. + * The interface is defined in "dba_array.h". + */ +#include +#include +#include +#include + +#include "mandoc_aux.h" +#include "dba_write.h" +#include "dba_array.h" + +struct dba_array { + void **ep; /* Array of entries. */ + int32_t *em; /* Array of map positions. */ + int flags; + int32_t ea; /* Entries allocated. */ + int32_t eu; /* Entries used (including deleted). */ + int32_t ed; /* Entries deleted. */ + int32_t ec; /* Currently active entry. */ + int32_t pos; /* Map position of this array. */ +}; + + +struct dba_array * +dba_array_new(int32_t ea, int flags) +{ + struct dba_array *array; + + assert(ea > 0); + array = mandoc_malloc(sizeof(*array)); + array->ep = mandoc_reallocarray(NULL, ea, sizeof(*array->ep)); + array->em = mandoc_reallocarray(NULL, ea, sizeof(*array->em)); + array->ea = ea; + array->eu = 0; + array->ed = 0; + array->ec = 0; + array->flags = flags; + array->pos = 0; + return array; +} + +void +dba_array_free(struct dba_array *array) +{ + int32_t ie; + + if (array == NULL) + return; + if (array->flags & DBA_STR) + for (ie = 0; ie < array->eu; ie++) + free(array->ep[ie]); + free(array->ep); + free(array->em); + free(array); +} + +void +dba_array_set(struct dba_array *array, int32_t ie, void *entry) +{ + assert(ie >= 0); + assert(ie < array->ea); + assert(ie <= array->eu); + if (ie == array->eu) + array->eu++; + if (array->flags & DBA_STR) + entry = mandoc_strdup(entry); + array->ep[ie] = entry; + array->em[ie] = 0; +} + +void +dba_array_add(struct dba_array *array, void *entry) +{ + if (array->eu == array->ea) { + assert(array->flags & DBA_GROW); + array->ep = mandoc_reallocarray(array->ep, + 2, sizeof(*array->ep) * array->ea); + array->em = mandoc_reallocarray(array->em, + 2, sizeof(*array->em) * array->ea); + array->ea *= 2; + } + dba_array_set(array, array->eu, entry); +} + +void * +dba_array_get(struct dba_array *array, int32_t ie) +{ + if (ie < 0 || ie >= array->eu || array->em[ie] == -1) + return NULL; + return array->ep[ie]; +} + +void +dba_array_start(struct dba_array *array) +{ + array->ec = array->eu; +} + +void * +dba_array_next(struct dba_array *array) +{ + if (array->ec < array->eu) + array->ec++; + else + array->ec = 0; + while (array->ec < array->eu && array->em[array->ec] == -1) + array->ec++; + return array->ec < array->eu ? array->ep[array->ec] : NULL; +} + +void +dba_array_del(struct dba_array *array) +{ + if (array->ec < array->eu && array->em[array->ec] != -1) { + array->em[array->ec] = -1; + array->ed++; + } +} + +void +dba_array_undel(struct dba_array *array) +{ + memset(array->em, 0, sizeof(*array->em) * array->eu); +} + +void +dba_array_setpos(struct dba_array *array, int32_t ie, int32_t pos) +{ + array->em[ie] = pos; +} + +int32_t +dba_array_getpos(struct dba_array *array) +{ + return array->pos; +} + +void +dba_array_sort(struct dba_array *array, dba_compare_func func) +{ + assert(array->ed == 0); + qsort(array->ep, array->eu, sizeof(*array->ep), func); +} + +int32_t +dba_array_writelen(struct dba_array *array, int32_t nmemb) +{ + dba_int_write(array->eu - array->ed); + return dba_skip(nmemb, array->eu - array->ed); +} + +void +dba_array_writepos(struct dba_array *array) +{ + int32_t ie; + + array->pos = dba_tell(); + for (ie = 0; ie < array->eu; ie++) + if (array->em[ie] != -1) + dba_int_write(array->em[ie]); +} + +void +dba_array_writelst(struct dba_array *array) +{ + const char *str; + + dba_array_FOREACH(array, str) + dba_str_write(str); + dba_char_write('\0'); +} diff --git a/dba_array.h b/dba_array.h new file mode 100644 index 000000000000..d9a6ee6b1a43 --- /dev/null +++ b/dba_array.h @@ -0,0 +1,47 @@ +/* $Id: dba_array.h,v 1.1 2016/07/19 21:31:55 schwarze Exp $ */ +/* + * Copyright (c) 2016 Ingo Schwarze + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * Public interface for allocation-based arrays + * for the mandoc database, for read-write access. + * To be used by dba*.c and by makewhatis(8). + */ + +struct dba_array; + +#define DBA_STR 0x01 /* Map contains strings, not pointers. */ +#define DBA_GROW 0x02 /* Allow the array to grow. */ + +#define dba_array_FOREACH(a, e) \ + dba_array_start(a); \ + while (((e) = dba_array_next(a)) != NULL) + +typedef int dba_compare_func(const void *, const void *); + +struct dba_array *dba_array_new(int32_t, int); +void dba_array_free(struct dba_array *); +void dba_array_set(struct dba_array *, int32_t, void *); +void dba_array_add(struct dba_array *, void *); +void *dba_array_get(struct dba_array *, int32_t); +void dba_array_start(struct dba_array *); +void *dba_array_next(struct dba_array *); +void dba_array_del(struct dba_array *); +void dba_array_undel(struct dba_array *); +void dba_array_setpos(struct dba_array *, int32_t, int32_t); +int32_t dba_array_getpos(struct dba_array *); +void dba_array_sort(struct dba_array *, dba_compare_func); +int32_t dba_array_writelen(struct dba_array *, int32_t); +void dba_array_writepos(struct dba_array *); +void dba_array_writelst(struct dba_array *); diff --git a/dba_read.c b/dba_read.c new file mode 100644 index 000000000000..e976057064ab --- /dev/null +++ b/dba_read.c @@ -0,0 +1,72 @@ +/* $Id: dba_read.c,v 1.4 2016/08/17 20:46:56 schwarze Exp $ */ +/* + * Copyright (c) 2016 Ingo Schwarze + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * Function to read the mandoc database from disk into RAM, + * such that data can be added or removed. + * The interface is defined in "dba.h". + * This file is seperate from dba.c because this also uses "dbm.h". + */ +#include +#include +#include +#include +#include + +#include "mandoc_aux.h" +#include "mansearch.h" +#include "dba_array.h" +#include "dba.h" +#include "dbm.h" + + +struct dba * +dba_read(const char *fname) +{ + struct dba *dba; + struct dba_array *page; + struct dbm_page *pdata; + struct dbm_macro *mdata; + const char *cp; + int32_t im, ip, iv, npages; + + if (dbm_open(fname) == -1) + return NULL; + npages = dbm_page_count(); + dba = dba_new(npages < 128 ? 128 : npages); + for (ip = 0; ip < npages; ip++) { + pdata = dbm_page_get(ip); + page = dba_page_new(dba->pages, pdata->arch, + pdata->desc, pdata->file + 1, *pdata->file); + for (cp = pdata->name; *cp != '\0'; cp = strchr(cp, '\0') + 1) + dba_page_add(page, DBP_NAME, cp); + for (cp = pdata->sect; *cp != '\0'; cp = strchr(cp, '\0') + 1) + dba_page_add(page, DBP_SECT, cp); + if ((cp = pdata->arch) != NULL) + while (*(cp = strchr(cp, '\0') + 1) != '\0') + dba_page_add(page, DBP_ARCH, cp); + cp = pdata->file; + while (*(cp = strchr(cp, '\0') + 1) != '\0') + dba_page_add(page, DBP_FILE, cp); + } + for (im = 0; im < MACRO_MAX; im++) { + for (iv = 0; iv < dbm_macro_count(im); iv++) { + mdata = dbm_macro_get(im, iv); + dba_macro_new(dba, im, mdata->value, mdata->pp); + } + } + dbm_close(); + return dba; +} diff --git a/dba_write.c b/dba_write.c new file mode 100644 index 000000000000..89883b87c1e7 --- /dev/null +++ b/dba_write.c @@ -0,0 +1,127 @@ +/* $Id: dba_write.c,v 1.3 2016/08/05 23:15:08 schwarze Exp $ */ +/* + * Copyright (c) 2016 Ingo Schwarze + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * Low-level functions for serializing allocation-based data to disk. + * The interface is defined in "dba_write.h". + */ +#include "config.h" + +#include +#if HAVE_ENDIAN +#include +#elif HAVE_SYS_ENDIAN +#include +#elif HAVE_NTOHL +#include +#endif +#if HAVE_ERR +#include +#endif +#include +#include +#include +#include + +#include "dba_write.h" + +static FILE *ofp; + + +int +dba_open(const char *fname) +{ + ofp = fopen(fname, "w"); + return ofp == NULL ? -1 : 0; +} + +int +dba_close(void) +{ + return fclose(ofp) == EOF ? -1 : 0; +} + +int32_t +dba_tell(void) +{ + long pos; + + if ((pos = ftell(ofp)) == -1) + err(1, "ftell"); + if (pos >= INT32_MAX) { + errno = EOVERFLOW; + err(1, "ftell = %ld", pos); + } + return pos; +} + +void +dba_seek(int32_t pos) +{ + if (fseek(ofp, pos, SEEK_SET) == -1) + err(1, "fseek(%d)", pos); +} + +int32_t +dba_align(void) +{ + int32_t pos; + + pos = dba_tell(); + while (pos & 3) { + dba_char_write('\0'); + pos++; + } + return pos; +} + +int32_t +dba_skip(int32_t nmemb, int32_t sz) +{ + const int32_t out[5] = {0, 0, 0, 0, 0}; + int32_t i, pos; + + assert(sz >= 0); + assert(nmemb > 0); + assert(nmemb <= 5); + pos = dba_tell(); + for (i = 0; i < sz; i++) + if (nmemb - fwrite(&out, sizeof(out[0]), nmemb, ofp)) + err(1, "fwrite"); + return pos; +} + +void +dba_char_write(int c) +{ + if (putc(c, ofp) == EOF) + err(1, "fputc"); +} + +void +dba_str_write(const char *str) +{ + if (fputs(str, ofp) == EOF) + err(1, "fputs"); + dba_char_write('\0'); +} + +void +dba_int_write(int32_t i) +{ + i = htobe32(i); + if (fwrite(&i, sizeof(i), 1, ofp) != 1) + err(1, "fwrite"); +} diff --git a/dba_write.h b/dba_write.h new file mode 100644 index 000000000000..6adda05b6506 --- /dev/null +++ b/dba_write.h @@ -0,0 +1,30 @@ +/* $Id: dba_write.h,v 1.1 2016/07/19 21:31:55 schwarze Exp $ */ +/* + * Copyright (c) 2016 Ingo Schwarze + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * Internal interface to low-level functions + * for serializing allocation-based data to disk. + * For use by dba_array.c and dba.c only. + */ + +int dba_open(const char *); +int dba_close(void); +int32_t dba_tell(void); +void dba_seek(int32_t); +int32_t dba_align(void); +int32_t dba_skip(int32_t, int32_t); +void dba_char_write(int); +void dba_str_write(const char *); +void dba_int_write(int32_t); diff --git a/dbm.c b/dbm.c new file mode 100644 index 000000000000..4aedf66d136f --- /dev/null +++ b/dbm.c @@ -0,0 +1,480 @@ +/* $Id: dbm.c,v 1.5 2016/10/18 22:27:25 schwarze Exp $ */ +/* + * Copyright (c) 2016 Ingo Schwarze + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * Map-based version of the mandoc database, for read-only access. + * The interface is defined in "dbm.h". + */ +#include "config.h" + +#include +#if HAVE_ENDIAN +#include +#elif HAVE_SYS_ENDIAN +#include +#elif HAVE_NTOHL +#include +#endif +#if HAVE_ERR +#include +#endif +#include +#include +#include +#include +#include +#include + +#include "mansearch.h" +#include "dbm_map.h" +#include "dbm.h" + +struct macro { + int32_t value; + int32_t pages; +}; + +struct page { + int32_t name; + int32_t sect; + int32_t arch; + int32_t desc; + int32_t file; +}; + +enum iter { + ITER_NONE = 0, + ITER_NAME, + ITER_SECT, + ITER_ARCH, + ITER_DESC, + ITER_MACRO +}; + +static struct macro *macros[MACRO_MAX]; +static int32_t nvals[MACRO_MAX]; +static struct page *pages; +static int32_t npages; +static enum iter iteration; + +static struct dbm_res page_bytitle(enum iter, const struct dbm_match *); +static struct dbm_res page_byarch(const struct dbm_match *); +static struct dbm_res page_bymacro(int32_t, const struct dbm_match *); +static char *macro_bypage(int32_t, int32_t); + + +/*** top level functions **********************************************/ + +/* + * Open a disk-based mandoc database for read-only access. + * Map the pages and macros[] arrays. + * Return 0 on success. Return -1 and set errno on failure. + */ +int +dbm_open(const char *fname) +{ + const int32_t *mp, *ep; + int32_t im; + + if (dbm_map(fname) == -1) + return -1; + + if ((npages = be32toh(*dbm_getint(4))) < 0) { + warnx("dbm_open(%s): Invalid number of pages: %d", + fname, npages); + goto fail; + } + pages = (struct page *)dbm_getint(5); + + if ((mp = dbm_get(*dbm_getint(2))) == NULL) { + warnx("dbm_open(%s): Invalid offset of macros array", fname); + goto fail; + } + if (be32toh(*mp) != MACRO_MAX) { + warnx("dbm_open(%s): Invalid number of macros: %d", + fname, be32toh(*mp)); + goto fail; + } + for (im = 0; im < MACRO_MAX; im++) { + if ((ep = dbm_get(*++mp)) == NULL) { + warnx("dbm_open(%s): Invalid offset of macro %d", + fname, im); + goto fail; + } + nvals[im] = be32toh(*ep); + macros[im] = (struct macro *)++ep; + } + return 0; + +fail: + dbm_unmap(); + errno = EFTYPE; + return -1; +} + +void +dbm_close(void) +{ + dbm_unmap(); +} + + +/*** functions for handling pages *************************************/ + +int32_t +dbm_page_count(void) +{ + return npages; +} + +/* + * Give the caller pointers to the data for one manual page. + */ +struct dbm_page * +dbm_page_get(int32_t ip) +{ + static struct dbm_page res; + + assert(ip >= 0); + assert(ip < npages); + res.name = dbm_get(pages[ip].name); + if (res.name == NULL) + res.name = "(NULL)"; + res.sect = dbm_get(pages[ip].sect); + if (res.sect == NULL) + res.sect = "(NULL)"; + res.arch = pages[ip].arch ? dbm_get(pages[ip].arch) : NULL; + res.desc = dbm_get(pages[ip].desc); + if (res.desc == NULL) + res.desc = "(NULL)"; + res.file = dbm_get(pages[ip].file); + if (res.file == NULL) + res.file = " (NULL)"; + res.addr = dbm_addr(pages + ip); + return &res; +} + +/* + * Functions to start filtered iterations over manual pages. + */ +void +dbm_page_byname(const struct dbm_match *match) +{ + assert(match != NULL); + page_bytitle(ITER_NAME, match); +} + +void +dbm_page_bysect(const struct dbm_match *match) +{ + assert(match != NULL); + page_bytitle(ITER_SECT, match); +} + +void +dbm_page_byarch(const struct dbm_match *match) +{ + assert(match != NULL); + page_byarch(match); +} + +void +dbm_page_bydesc(const struct dbm_match *match) +{ + assert(match != NULL); + page_bytitle(ITER_DESC, match); +} + +void +dbm_page_bymacro(int32_t im, const struct dbm_match *match) +{ + assert(im >= 0); + assert(im < MACRO_MAX); + assert(match != NULL); + page_bymacro(im, match); +} + +/* + * Return the number of the next manual page in the current iteration. + */ +struct dbm_res +dbm_page_next(void) +{ + struct dbm_res res = {-1, 0}; + + switch(iteration) { + case ITER_NONE: + return res; + case ITER_ARCH: + return page_byarch(NULL); + case ITER_MACRO: + return page_bymacro(0, NULL); + default: + return page_bytitle(iteration, NULL); + } +} + +/* + * Functions implementing the iteration over manual pages. + */ +static struct dbm_res +page_bytitle(enum iter arg_iter, const struct dbm_match *arg_match) +{ + static const struct dbm_match *match; + static const char *cp; + static int32_t ip; + struct dbm_res res = {-1, 0}; + + assert(arg_iter == ITER_NAME || arg_iter == ITER_DESC || + arg_iter == ITER_SECT); + + /* Initialize for a new iteration. */ + + if (arg_match != NULL) { + iteration = arg_iter; + match = arg_match; + switch (iteration) { + case ITER_NAME: + cp = dbm_get(pages[0].name); + break; + case ITER_SECT: + cp = dbm_get(pages[0].sect); + break; + case ITER_DESC: + cp = dbm_get(pages[0].desc); + break; + default: + abort(); + } + if (cp == NULL) { + iteration = ITER_NONE; + match = NULL; + cp = NULL; + ip = npages; + } else + ip = 0; + return res; + } + + /* Search for a name. */ + + while (ip < npages) { + if (iteration == ITER_NAME) + cp++; + if (dbm_match(match, cp)) + break; + cp = strchr(cp, '\0') + 1; + if (iteration == ITER_DESC) + ip++; + else if (*cp == '\0') { + cp++; + ip++; + } + } + + /* Reached the end without a match. */ + + if (ip == npages) { + iteration = ITER_NONE; + match = NULL; + cp = NULL; + return res; + } + + /* Found a match; save the quality for later retrieval. */ + + res.page = ip; + res.bits = iteration == ITER_NAME ? cp[-1] : 0; + + /* Skip the remaining names of this page. */ + + if (++ip < npages) { + do { + cp++; + } while (cp[-1] != '\0' || + (iteration != ITER_DESC && cp[-2] != '\0')); + } + return res; +} + +static struct dbm_res +page_byarch(const struct dbm_match *arg_match) +{ + static const struct dbm_match *match; + struct dbm_res res = {-1, 0}; + static int32_t ip; + const char *cp; + + /* Initialize for a new iteration. */ + + if (arg_match != NULL) { + iteration = ITER_ARCH; + match = arg_match; + ip = 0; + return res; + } + + /* Search for an architecture. */ + + for ( ; ip < npages; ip++) + if (pages[ip].arch) + for (cp = dbm_get(pages[ip].arch); + *cp != '\0'; + cp = strchr(cp, '\0') + 1) + if (dbm_match(match, cp)) { + res.page = ip++; + return res; + } + + /* Reached the end without a match. */ + + iteration = ITER_NONE; + match = NULL; + return res; +} + +static struct dbm_res +page_bymacro(int32_t arg_im, const struct dbm_match *arg_match) +{ + static const struct dbm_match *match; + static const int32_t *pp; + static const char *cp; + static int32_t im, iv; + struct dbm_res res = {-1, 0}; + + assert(im >= 0); + assert(im < MACRO_MAX); + + /* Initialize for a new iteration. */ + + if (arg_match != NULL) { + iteration = ITER_MACRO; + match = arg_match; + im = arg_im; + cp = nvals[im] ? dbm_get(macros[im]->value) : NULL; + pp = NULL; + iv = -1; + return res; + } + if (iteration != ITER_MACRO) + return res; + + /* Find the next matching macro value. */ + + while (pp == NULL || *pp == 0) { + if (++iv == nvals[im]) { + iteration = ITER_NONE; + return res; + } + if (iv) + cp = strchr(cp, '\0') + 1; + if (dbm_match(match, cp)) + pp = dbm_get(macros[im][iv].pages); + } + + /* Found a matching page. */ + + res.page = (struct page *)dbm_get(*pp++) - pages; + return res; +} + + +/*** functions for handling macros ************************************/ + +int32_t +dbm_macro_count(int32_t im) +{ + assert(im >= 0); + assert(im < MACRO_MAX); + return nvals[im]; +} + +struct dbm_macro * +dbm_macro_get(int32_t im, int32_t iv) +{ + static struct dbm_macro macro; + + assert(im >= 0); + assert(im < MACRO_MAX); + assert(iv >= 0); + assert(iv < nvals[im]); + macro.value = dbm_get(macros[im][iv].value); + macro.pp = dbm_get(macros[im][iv].pages); + return ¯o; +} + +/* + * Filtered iteration over macro entries. + */ +void +dbm_macro_bypage(int32_t im, int32_t ip) +{ + assert(im >= 0); + assert(im < MACRO_MAX); + assert(ip != 0); + macro_bypage(im, ip); +} + +char * +dbm_macro_next(void) +{ + return macro_bypage(MACRO_MAX, 0); +} + +static char * +macro_bypage(int32_t arg_im, int32_t arg_ip) +{ + static const int32_t *pp; + static int32_t im, ip, iv; + + /* Initialize for a new iteration. */ + + if (arg_im < MACRO_MAX && arg_ip != 0) { + im = arg_im; + ip = arg_ip; + pp = dbm_get(macros[im]->pages); + iv = 0; + return NULL; + } + if (im >= MACRO_MAX) + return NULL; + + /* Search for the next value. */ + + while (iv < nvals[im]) { + if (*pp == ip) + break; + if (*pp == 0) + iv++; + pp++; + } + + /* Reached the end without a match. */ + + if (iv == nvals[im]) { + im = MACRO_MAX; + ip = 0; + pp = NULL; + return NULL; + } + + /* Found a match; skip the remaining pages of this entry. */ + + if (++iv < nvals[im]) + while (*pp++ != 0) + continue; + + return dbm_get(macros[im][iv - 1].value); +} diff --git a/dbm.h b/dbm.h new file mode 100644 index 000000000000..ec2cd479a739 --- /dev/null +++ b/dbm.h @@ -0,0 +1,68 @@ +/* $Id: dbm.h,v 1.1 2016/07/19 21:31:55 schwarze Exp $ */ +/* + * Copyright (c) 2016 Ingo Schwarze + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * Public interface for the map-based version + * of the mandoc database, for read-only access. + * To be used by dbm*.c, dba_read.c, and man(1) and apropos(1). + */ + +enum dbm_mtype { + DBM_EXACT = 0, + DBM_SUB, + DBM_REGEX +}; + +struct dbm_match { + regex_t *re; + const char *str; + enum dbm_mtype type; +}; + +struct dbm_res { + int32_t page; + int32_t bits; +}; + +struct dbm_page { + const char *name; + const char *sect; + const char *arch; + const char *desc; + const char *file; + int32_t addr; +}; + +struct dbm_macro { + const char *value; + const int32_t *pp; +}; + +int dbm_open(const char *); +void dbm_close(void); + +int32_t dbm_page_count(void); +struct dbm_page *dbm_page_get(int32_t); +void dbm_page_byname(const struct dbm_match *); +void dbm_page_bysect(const struct dbm_match *); +void dbm_page_byarch(const struct dbm_match *); +void dbm_page_bydesc(const struct dbm_match *); +void dbm_page_bymacro(int32_t, const struct dbm_match *); +struct dbm_res dbm_page_next(void); + +int32_t dbm_macro_count(int32_t); +struct dbm_macro *dbm_macro_get(int32_t, int32_t); +void dbm_macro_bypage(int32_t, int32_t); +char *dbm_macro_next(void); diff --git a/dbm_map.c b/dbm_map.c new file mode 100644 index 000000000000..d158302bad66 --- /dev/null +++ b/dbm_map.c @@ -0,0 +1,194 @@ +/* $Id: dbm_map.c,v 1.7 2016/10/22 10:09:27 schwarze Exp $ */ +/* + * Copyright (c) 2016 Ingo Schwarze + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * Low-level routines for the map-based version + * of the mandoc database, for read-only access. + * The interface is defined in "dbm_map.h". + */ +#include "config.h" + +#include +#include +#include + +#if HAVE_ENDIAN +#include +#elif HAVE_SYS_ENDIAN +#include +#elif HAVE_NTOHL +#include +#endif +#if HAVE_ERR +#include +#endif +#include +#include +#include +#include +#include +#include +#include + +#include "mansearch.h" +#include "dbm_map.h" +#include "dbm.h" + +static struct stat st; +static char *dbm_base; +static int ifd; +static int32_t max_offset; + +/* + * Open a disk-based database for read-only access. + * Validate the file format as far as it is not mandoc-specific. + * Return 0 on success. Return -1 and set errno on failure. + */ +int +dbm_map(const char *fname) +{ + int save_errno; + const int32_t *magic; + + if ((ifd = open(fname, O_RDONLY)) == -1) + return -1; + if (fstat(ifd, &st) == -1) + goto fail; + if (st.st_size < 5) { + warnx("dbm_map(%s): File too short", fname); + errno = EFTYPE; + goto fail; + } + if (st.st_size > INT32_MAX) { + errno = EFBIG; + goto fail; + } + if ((dbm_base = mmap(NULL, st.st_size, PROT_READ, MAP_SHARED, + ifd, 0)) == MAP_FAILED) + goto fail; + magic = dbm_getint(0); + if (be32toh(*magic) != MANDOCDB_MAGIC) { + if (strncmp(dbm_base, "SQLite format 3", 15)) + warnx("dbm_map(%s): " + "Bad initial magic %x (expected %x)", + fname, be32toh(*magic), MANDOCDB_MAGIC); + else + warnx("dbm_map(%s): " + "Obsolete format based on SQLite 3", + fname); + errno = EFTYPE; + goto fail; + } + magic = dbm_getint(1); + if (be32toh(*magic) != MANDOCDB_VERSION) { + warnx("dbm_map(%s): Bad version number %d (expected %d)", + fname, be32toh(*magic), MANDOCDB_VERSION); + errno = EFTYPE; + goto fail; + } + max_offset = be32toh(*dbm_getint(3)) + sizeof(int32_t); + if (st.st_size != max_offset) { + warnx("dbm_map(%s): Inconsistent file size %lld (expected %d)", + fname, (long long)st.st_size, max_offset); + errno = EFTYPE; + goto fail; + } + if ((magic = dbm_get(*dbm_getint(3))) == NULL) { + errno = EFTYPE; + goto fail; + } + if (be32toh(*magic) != MANDOCDB_MAGIC) { + warnx("dbm_map(%s): Bad final magic %x (expected %x)", + fname, be32toh(*magic), MANDOCDB_MAGIC); + errno = EFTYPE; + goto fail; + } + return 0; + +fail: + save_errno = errno; + close(ifd); + errno = save_errno; + return -1; +} + +void +dbm_unmap(void) +{ + if (munmap(dbm_base, st.st_size) == -1) + warn("dbm_unmap: munmap"); + if (close(ifd) == -1) + warn("dbm_unmap: close"); + dbm_base = (char *)-1; +} + +/* + * Take a raw integer as it was read from the database. + * Interpret it as an offset into the database file + * and return a pointer to that place in the file. + */ +void * +dbm_get(int32_t offset) +{ + offset = be32toh(offset); + if (offset < 0) { + warnx("dbm_get: Database corrupt: offset %d", offset); + return NULL; + } + if (offset >= max_offset) { + warnx("dbm_get: Database corrupt: offset %d > %d", + offset, max_offset); + return NULL; + } + return dbm_base + offset; +} + +/* + * Assume the database starts with some integers. + * Assume they are numbered starting from 0, increasing. + * Get a pointer to one with the number "offset". + */ +int32_t * +dbm_getint(int32_t offset) +{ + return (int32_t *)dbm_base + offset; +} + +/* + * The reverse of dbm_get(). + * Take pointer into the database file + * and convert it to the raw integer + * that would be used to refer to that place in the file. + */ +int32_t +dbm_addr(const void *p) +{ + return htobe32((char *)p - dbm_base); +} + +int +dbm_match(const struct dbm_match *match, const char *str) +{ + switch (match->type) { + case DBM_EXACT: + return strcmp(str, match->str) == 0; + case DBM_SUB: + return strcasestr(str, match->str) != NULL; + case DBM_REGEX: + return regexec(match->re, str, 0, NULL, 0) == 0; + default: + abort(); + } +} diff --git a/mansearch_const.c b/dbm_map.h similarity index 57% rename from mansearch_const.c rename to dbm_map.h index 61351c3c2a47..9768fc5f2dcc 100644 --- a/mansearch_const.c +++ b/dbm_map.h @@ -1,6 +1,6 @@ -/* $Id: mansearch_const.c,v 1.7 2014/12/01 08:05:52 schwarze Exp $ */ +/* $Id: dbm_map.h,v 1.1 2016/07/19 21:31:55 schwarze Exp $ */ /* - * Copyright (c) 2014 Ingo Schwarze + * Copyright (c) 2016 Ingo Schwarze * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -13,21 +13,17 @@ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * Private interface for low-level routines for the map-based version + * of the mandoc database, for read-only access. + * To be used by dbm*.c only. */ -#include "config.h" -#include +struct dbm_match; -#include - -#include "mansearch.h" - -const int mansearch_keymax = 40; - -const char *const mansearch_keynames[40] = { - "arch", "sec", "Xr", "Ar", "Fa", "Fl", "Dv", "Fn", - "Ic", "Pa", "Cm", "Li", "Em", "Cd", "Va", "Ft", - "Tn", "Er", "Ev", "Sy", "Sh", "In", "Ss", "Ox", - "An", "Mt", "St", "Bx", "At", "Nx", "Fx", "Lk", - "Ms", "Bsx", "Dx", "Rs", "Vt", "Lb", "Nm", "Nd" -}; +int dbm_map(const char *); +void dbm_unmap(void); +void *dbm_get(int32_t); +int32_t *dbm_getint(int32_t); +int32_t dbm_addr(const void *); +int dbm_match(const struct dbm_match *, const char *); diff --git a/demandoc.c b/demandoc.c index c33fd89169a7..acddf5a19ffc 100644 --- a/demandoc.c +++ b/demandoc.c @@ -1,4 +1,4 @@ -/* $Id: demandoc.c,v 1.27 2016/07/09 15:24:19 schwarze Exp $ */ +/* $Id: demandoc.c,v 1.28 2017/01/10 13:47:00 schwarze Exp $ */ /* * Copyright (c) 2011 Kristaps Dzonsons * @@ -239,7 +239,7 @@ pmdoc(const struct roff_node *p, int *line, int *col, int list) { for ( ; p; p = p->next) { - if (MDOC_LINE & p->flags) + if (NODE_LINE & p->flags) pline(p->line, line, col, list); if (ROFFT_TEXT == p->type) pstring(p->string, p->pos, col, list); @@ -253,7 +253,7 @@ pman(const struct roff_node *p, int *line, int *col, int list) { for ( ; p; p = p->next) { - if (MAN_LINE & p->flags) + if (NODE_LINE & p->flags) pline(p->line, line, col, list); if (ROFFT_TEXT == p->type) pstring(p->string, p->pos, col, list); diff --git a/eqn_html.c b/eqn_html.c index f29733613bb9..b6e7d914b86e 100644 --- a/eqn_html.c +++ b/eqn_html.c @@ -1,6 +1,7 @@ -/* $Id: eqn_html.c,v 1.10 2014/10/12 19:31:41 schwarze Exp $ */ +/* $Id: eqn_html.c,v 1.11 2017/01/17 01:47:51 schwarze Exp $ */ /* * Copyright (c) 2011, 2014 Kristaps Dzonsons + * Copyright (c) 2017 Ingo Schwarze * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -31,7 +32,6 @@ static void eqn_box(struct html *p, const struct eqn_box *bp) { struct tag *post, *row, *cell, *t; - struct htmlpair tag[2]; const struct eqn_box *child, *parent; size_t i, j, rows; @@ -59,10 +59,10 @@ eqn_box(struct html *p, const struct eqn_box *bp) for (rows = 0; NULL != child; rows++) child = child->next; /* Print row-by-row. */ - post = print_otag(p, TAG_MTABLE, 0, NULL); + post = print_otag(p, TAG_MTABLE, ""); for (i = 0; i < rows; i++) { parent = bp->first->first; - row = print_otag(p, TAG_MTR, 0, NULL); + row = print_otag(p, TAG_MTR, ""); while (NULL != parent) { child = parent->first; for (j = 0; j < i; j++) { @@ -70,8 +70,7 @@ eqn_box(struct html *p, const struct eqn_box *bp) break; child = child->next; } - cell = print_otag - (p, TAG_MTD, 0, NULL); + cell = print_otag(p, TAG_MTD, ""); /* * If we have no data for this * particular cell, then print a @@ -89,28 +88,28 @@ eqn_box(struct html *p, const struct eqn_box *bp) switch (bp->pos) { case (EQNPOS_TO): - post = print_otag(p, TAG_MOVER, 0, NULL); + post = print_otag(p, TAG_MOVER, ""); break; case (EQNPOS_SUP): - post = print_otag(p, TAG_MSUP, 0, NULL); + post = print_otag(p, TAG_MSUP, ""); break; case (EQNPOS_FROM): - post = print_otag(p, TAG_MUNDER, 0, NULL); + post = print_otag(p, TAG_MUNDER, ""); break; case (EQNPOS_SUB): - post = print_otag(p, TAG_MSUB, 0, NULL); + post = print_otag(p, TAG_MSUB, ""); break; case (EQNPOS_OVER): - post = print_otag(p, TAG_MFRAC, 0, NULL); + post = print_otag(p, TAG_MFRAC, ""); break; case (EQNPOS_FROMTO): - post = print_otag(p, TAG_MUNDEROVER, 0, NULL); + post = print_otag(p, TAG_MUNDEROVER, ""); break; case (EQNPOS_SUBSUP): - post = print_otag(p, TAG_MSUBSUP, 0, NULL); + post = print_otag(p, TAG_MSUBSUP, ""); break; case (EQNPOS_SQRT): - post = print_otag(p, TAG_MSQRT, 0, NULL); + post = print_otag(p, TAG_MSQRT, ""); break; default: break; @@ -119,52 +118,49 @@ eqn_box(struct html *p, const struct eqn_box *bp) if (bp->top || bp->bottom) { assert(NULL == post); if (bp->top && NULL == bp->bottom) - post = print_otag(p, TAG_MOVER, 0, NULL); + post = print_otag(p, TAG_MOVER, ""); else if (bp->top && bp->bottom) - post = print_otag(p, TAG_MUNDEROVER, 0, NULL); + post = print_otag(p, TAG_MUNDEROVER, ""); else if (bp->bottom) - post = print_otag(p, TAG_MUNDER, 0, NULL); + post = print_otag(p, TAG_MUNDER, ""); } if (EQN_PILE == bp->type) { assert(NULL == post); if (bp->first != NULL && bp->first->type == EQN_LIST) - post = print_otag(p, TAG_MTABLE, 0, NULL); + post = print_otag(p, TAG_MTABLE, ""); } else if (bp->type == EQN_LIST && bp->parent && bp->parent->type == EQN_PILE) { assert(NULL == post); - post = print_otag(p, TAG_MTR, 0, NULL); - print_otag(p, TAG_MTD, 0, NULL); + post = print_otag(p, TAG_MTR, ""); + print_otag(p, TAG_MTD, ""); } if (NULL != bp->text) { assert(NULL == post); - post = print_otag(p, TAG_MI, 0, NULL); + post = print_otag(p, TAG_MI, ""); print_text(p, bp->text); } else if (NULL == post) { - if (NULL != bp->left || NULL != bp->right) { - PAIR_INIT(&tag[0], ATTR_OPEN, - NULL == bp->left ? "" : bp->left); - PAIR_INIT(&tag[1], ATTR_CLOSE, - NULL == bp->right ? "" : bp->right); - post = print_otag(p, TAG_MFENCED, 2, tag); - } + if (NULL != bp->left || NULL != bp->right) + post = print_otag(p, TAG_MFENCED, "??", + "open", bp->left == NULL ? "" : bp->left, + "close", bp->right == NULL ? "" : bp->right); if (NULL == post) - post = print_otag(p, TAG_MROW, 0, NULL); + post = print_otag(p, TAG_MROW, ""); else - print_otag(p, TAG_MROW, 0, NULL); + print_otag(p, TAG_MROW, ""); } eqn_box(p, bp->first); out: if (NULL != bp->bottom) { - t = print_otag(p, TAG_MO, 0, NULL); + t = print_otag(p, TAG_MO, ""); print_text(p, bp->bottom); print_tagq(p, t); } if (NULL != bp->top) { - t = print_otag(p, TAG_MO, 0, NULL); + t = print_otag(p, TAG_MO, ""); print_text(p, bp->top); print_tagq(p, t); } @@ -178,11 +174,9 @@ eqn_box(struct html *p, const struct eqn_box *bp) void print_eqn(struct html *p, const struct eqn *ep) { - struct htmlpair tag; struct tag *t; - PAIR_CLASS_INIT(&tag, "eqn"); - t = print_otag(p, TAG_MATH, 1, &tag); + t = print_otag(p, TAG_MATH, "c", "eqn"); p->flags |= HTML_NONOSPACE; eqn_box(p, ep->root); diff --git a/html.c b/html.c index adff053c1a08..24fd6f471f6e 100644 --- a/html.c +++ b/html.c @@ -1,7 +1,7 @@ -/* $Id: html.c,v 1.192 2016/01/04 12:45:29 schwarze Exp $ */ +/* $Id: html.c,v 1.200 2017/01/21 02:29:57 schwarze Exp $ */ /* * Copyright (c) 2008-2011, 2014 Kristaps Dzonsons - * Copyright (c) 2011-2015 Ingo Schwarze + * Copyright (c) 2011-2015, 2017 Ingo Schwarze * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -38,74 +38,65 @@ struct htmldata { const char *name; int flags; -#define HTML_CLRLINE (1 << 0) -#define HTML_NOSTACK (1 << 1) -#define HTML_AUTOCLOSE (1 << 2) /* Tag has auto-closure. */ +#define HTML_NOSTACK (1 << 0) +#define HTML_AUTOCLOSE (1 << 1) +#define HTML_NLBEFORE (1 << 2) +#define HTML_NLBEGIN (1 << 3) +#define HTML_NLEND (1 << 4) +#define HTML_NLAFTER (1 << 5) +#define HTML_NLAROUND (HTML_NLBEFORE | HTML_NLAFTER) +#define HTML_NLINSIDE (HTML_NLBEGIN | HTML_NLEND) +#define HTML_NLALL (HTML_NLAROUND | HTML_NLINSIDE) +#define HTML_INDENT (1 << 6) +#define HTML_NOINDENT (1 << 7) }; static const struct htmldata htmltags[TAG_MAX] = { - {"html", HTML_CLRLINE}, /* TAG_HTML */ - {"head", HTML_CLRLINE}, /* TAG_HEAD */ - {"body", HTML_CLRLINE}, /* TAG_BODY */ - {"meta", HTML_CLRLINE | HTML_NOSTACK | HTML_AUTOCLOSE}, /* TAG_META */ - {"title", HTML_CLRLINE}, /* TAG_TITLE */ - {"div", HTML_CLRLINE}, /* TAG_DIV */ - {"h1", 0}, /* TAG_H1 */ - {"h2", 0}, /* TAG_H2 */ - {"span", 0}, /* TAG_SPAN */ - {"link", HTML_CLRLINE | HTML_NOSTACK | HTML_AUTOCLOSE}, /* TAG_LINK */ - {"br", HTML_CLRLINE | HTML_NOSTACK | HTML_AUTOCLOSE}, /* TAG_BR */ - {"a", 0}, /* TAG_A */ - {"table", HTML_CLRLINE}, /* TAG_TABLE */ - {"tbody", HTML_CLRLINE}, /* TAG_TBODY */ - {"col", HTML_CLRLINE | HTML_NOSTACK | HTML_AUTOCLOSE}, /* TAG_COL */ - {"tr", HTML_CLRLINE}, /* TAG_TR */ - {"td", HTML_CLRLINE}, /* TAG_TD */ - {"li", HTML_CLRLINE}, /* TAG_LI */ - {"ul", HTML_CLRLINE}, /* TAG_UL */ - {"ol", HTML_CLRLINE}, /* TAG_OL */ - {"dl", HTML_CLRLINE}, /* TAG_DL */ - {"dt", HTML_CLRLINE}, /* TAG_DT */ - {"dd", HTML_CLRLINE}, /* TAG_DD */ - {"blockquote", HTML_CLRLINE}, /* TAG_BLOCKQUOTE */ - {"pre", HTML_CLRLINE }, /* TAG_PRE */ - {"b", 0 }, /* TAG_B */ - {"i", 0 }, /* TAG_I */ - {"code", 0 }, /* TAG_CODE */ - {"small", 0 }, /* TAG_SMALL */ - {"style", HTML_CLRLINE}, /* TAG_STYLE */ - {"math", HTML_CLRLINE}, /* TAG_MATH */ - {"mrow", 0}, /* TAG_MROW */ - {"mi", 0}, /* TAG_MI */ - {"mo", 0}, /* TAG_MO */ - {"msup", 0}, /* TAG_MSUP */ - {"msub", 0}, /* TAG_MSUB */ - {"msubsup", 0}, /* TAG_MSUBSUP */ - {"mfrac", 0}, /* TAG_MFRAC */ - {"msqrt", 0}, /* TAG_MSQRT */ - {"mfenced", 0}, /* TAG_MFENCED */ - {"mtable", 0}, /* TAG_MTABLE */ - {"mtr", 0}, /* TAG_MTR */ - {"mtd", 0}, /* TAG_MTD */ - {"munderover", 0}, /* TAG_MUNDEROVER */ - {"munder", 0}, /* TAG_MUNDER*/ - {"mover", 0}, /* TAG_MOVER*/ -}; - -static const char *const htmlattrs[ATTR_MAX] = { - "name", /* ATTR_NAME */ - "rel", /* ATTR_REL */ - "href", /* ATTR_HREF */ - "type", /* ATTR_TYPE */ - "media", /* ATTR_MEDIA */ - "class", /* ATTR_CLASS */ - "style", /* ATTR_STYLE */ - "id", /* ATTR_ID */ - "colspan", /* ATTR_COLSPAN */ - "charset", /* ATTR_CHARSET */ - "open", /* ATTR_OPEN */ - "close", /* ATTR_CLOSE */ - "mathvariant", /* ATTR_MATHVARIANT */ + {"html", HTML_NLALL}, + {"head", HTML_NLALL | HTML_INDENT}, + {"body", HTML_NLALL}, + {"meta", HTML_NOSTACK | HTML_AUTOCLOSE | HTML_NLALL}, + {"title", HTML_NLAROUND}, + {"div", HTML_NLAROUND}, + {"h1", HTML_NLAROUND}, + {"h2", HTML_NLAROUND}, + {"span", 0}, + {"link", HTML_NOSTACK | HTML_AUTOCLOSE | HTML_NLALL}, + {"br", HTML_NOSTACK | HTML_AUTOCLOSE | HTML_NLALL}, + {"a", 0}, + {"table", HTML_NLALL | HTML_INDENT}, + {"tbody", HTML_NLALL | HTML_INDENT}, + {"col", HTML_NOSTACK | HTML_AUTOCLOSE | HTML_NLALL}, + {"tr", HTML_NLALL | HTML_INDENT}, + {"td", HTML_NLAROUND}, + {"li", HTML_NLAROUND | HTML_INDENT}, + {"ul", HTML_NLALL | HTML_INDENT}, + {"ol", HTML_NLALL | HTML_INDENT}, + {"dl", HTML_NLALL | HTML_INDENT}, + {"dt", HTML_NLAROUND}, + {"dd", HTML_NLAROUND | HTML_INDENT}, + {"pre", HTML_NLALL | HTML_NOINDENT}, + {"b", 0}, + {"i", 0}, + {"code", 0}, + {"small", 0}, + {"style", HTML_NLALL | HTML_INDENT}, + {"math", HTML_NLALL | HTML_INDENT}, + {"mrow", 0}, + {"mi", 0}, + {"mo", 0}, + {"msup", 0}, + {"msub", 0}, + {"msubsup", 0}, + {"mfrac", 0}, + {"msqrt", 0}, + {"mfenced", 0}, + {"mtable", 0}, + {"mtr", 0}, + {"mtd", 0}, + {"munderover", 0}, + {"munder", 0}, + {"mover", 0}, }; static const char *const roffscales[SCALE_MAX] = { @@ -121,12 +112,18 @@ static const char *const roffscales[SCALE_MAX] = { "ex", /* SCALE_FS */ }; -static void bufncat(struct html *, const char *, size_t); +static void a2width(const char *, struct roffsu *); +static void print_byte(struct html *, char); +static void print_endline(struct html *); +static void print_endword(struct html *); +static void print_indent(struct html *); +static void print_word(struct html *, const char *); + static void print_ctag(struct html *, struct tag *); -static int print_escape(char); -static int print_encode(struct html *, const char *, int); +static int print_escape(struct html *, char); +static int print_encode(struct html *, const char *, const char *, int); +static void print_href(struct html *, const char *, const char *, int); static void print_metaf(struct html *, enum mandoc_esc); -static void print_attr(struct html *, const char *, const char *); void * @@ -165,36 +162,27 @@ html_free(void *p) void print_gen_head(struct html *h) { - struct htmlpair tag[4]; struct tag *t; - tag[0].key = ATTR_CHARSET; - tag[0].val = "utf-8"; - print_otag(h, TAG_META, 1, tag); + print_otag(h, TAG_META, "?", "charset", "utf-8"); /* * Print a default style-sheet. */ - t = print_otag(h, TAG_STYLE, 0, NULL); - print_text(h, "table.head, table.foot { width: 100%; }\n" - "td.head-rtitle, td.foot-os { text-align: right; }\n" - "td.head-vol { text-align: center; }\n" - "table.foot td { width: 50%; }\n" - "table.head td { width: 33%; }\n" - "div.spacer { margin: 1em 0; }\n"); + + t = print_otag(h, TAG_STYLE, ""); + print_text(h, "table.head, table.foot { width: 100%; }"); + print_endline(h); + print_text(h, "td.head-rtitle, td.foot-os { text-align: right; }"); + print_endline(h); + print_text(h, "td.head-vol { text-align: center; }"); + print_endline(h); + print_text(h, "div.Pp { margin: 1ex 0ex; }"); print_tagq(h, t); - if (h->style) { - tag[0].key = ATTR_REL; - tag[0].val = "stylesheet"; - tag[1].key = ATTR_HREF; - tag[1].val = h->style; - tag[2].key = ATTR_TYPE; - tag[2].val = "text/css"; - tag[3].key = ATTR_MEDIA; - tag[3].val = "all"; - print_otag(h, TAG_LINK, 4, tag); - } + if (h->style) + print_otag(h, TAG_LINK, "?h??", "rel", "stylesheet", + h->style, "type", "text/css", "media", "all"); } static void @@ -233,14 +221,14 @@ print_metaf(struct html *h, enum mandoc_esc deco) switch (font) { case HTMLFONT_ITALIC: - h->metaf = print_otag(h, TAG_I, 0, NULL); + h->metaf = print_otag(h, TAG_I, ""); break; case HTMLFONT_BOLD: - h->metaf = print_otag(h, TAG_B, 0, NULL); + h->metaf = print_otag(h, TAG_B, ""); break; case HTMLFONT_BI: - h->metaf = print_otag(h, TAG_B, 0, NULL); - print_otag(h, TAG_I, 0, NULL); + h->metaf = print_otag(h, TAG_B, ""); + print_otag(h, TAG_I, ""); break; default: break; @@ -299,27 +287,27 @@ html_strlen(const char *cp) } static int -print_escape(char c) +print_escape(struct html *h, char c) { switch (c) { case '<': - printf("<"); + print_word(h, "<"); break; case '>': - printf(">"); + print_word(h, ">"); break; case '&': - printf("&"); + print_word(h, "&"); break; case '"': - printf("""); + print_word(h, """); break; case ASCII_NBRSP: - printf(" "); + print_word(h, " "); break; case ASCII_HYPH: - putchar('-'); + print_byte(h, '-'); break; case ASCII_BREAK: break; @@ -330,8 +318,9 @@ print_escape(char c) } static int -print_encode(struct html *h, const char *p, int norecurse) +print_encode(struct html *h, const char *p, const char *pend, int norecurse) { + char numbuf[16]; size_t sz; int c, len, nospace; const char *seq; @@ -339,24 +328,28 @@ print_encode(struct html *h, const char *p, int norecurse) static const char rejs[9] = { '\\', '<', '>', '&', '"', ASCII_NBRSP, ASCII_HYPH, ASCII_BREAK, '\0' }; + if (pend == NULL) + pend = strchr(p, '\0'); + nospace = 0; - while ('\0' != *p) { + while (p < pend) { if (HTML_SKIPCHAR & h->flags && '\\' != *p) { h->flags &= ~HTML_SKIPCHAR; p++; continue; } - sz = strcspn(p, rejs); + for (sz = strcspn(p, rejs); sz-- && p < pend; p++) + if (*p == ' ') + print_endword(h); + else + print_byte(h, *p); - fwrite(p, 1, sz, stdout); - p += (int)sz; - - if ('\0' == *p) + if (p >= pend) break; - if (print_escape(*p++)) + if (print_escape(h, *p++)) continue; esc = mandoc_escape(&p, &seq, &len); @@ -415,33 +408,57 @@ print_encode(struct html *h, const char *p, int norecurse) if ((c < 0x20 && c != 0x09) || (c > 0x7E && c < 0xA0)) c = 0xFFFD; - if (c > 0x7E) - printf("&#%d;", c); - else if ( ! print_escape(c)) - putchar(c); + if (c > 0x7E) { + (void)snprintf(numbuf, sizeof(numbuf), "&#%d;", c); + print_word(h, numbuf); + } else if (print_escape(h, c) == 0) + print_byte(h, c); } return nospace; } static void -print_attr(struct html *h, const char *key, const char *val) +print_href(struct html *h, const char *name, const char *sec, int man) { - printf(" %s=\"", key); - (void)print_encode(h, val, 1); - putchar('\"'); + const char *p, *pp; + + pp = man ? h->base_man : h->base_includes; + while ((p = strchr(pp, '%')) != NULL) { + print_encode(h, pp, p, 1); + if (man && p[1] == 'S') { + if (sec == NULL) + print_byte(h, '1'); + else + print_encode(h, sec, NULL, 1); + } else if ((man && p[1] == 'N') || + (man == 0 && p[1] == 'I')) + print_encode(h, name, NULL, 1); + else + print_encode(h, p, p + 2, 1); + pp = p + 2; + } + if (*pp != '\0') + print_encode(h, pp, NULL, 1); } struct tag * -print_otag(struct html *h, enum htmltag tag, - int sz, const struct htmlpair *p) +print_otag(struct html *h, enum htmltag tag, const char *fmt, ...) { - int i; + va_list ap; + struct roffsu mysu, *su; + char numbuf[16]; struct tag *t; + const char *attr; + char *s; + double v; + int i, have_style, tflags; + + tflags = htmltags[tag].flags; /* Push this tags onto the stack of open scopes. */ - if ( ! (HTML_NOSTACK & htmltags[tag].flags)) { + if ((tflags & HTML_NOSTACK) == 0) { t = mandoc_malloc(sizeof(struct tag)); t->tag = tag; t->next = h->tags.head; @@ -449,16 +466,19 @@ print_otag(struct html *h, enum htmltag tag, } else t = NULL; - if ( ! (HTML_NOSPACE & h->flags)) - if ( ! (HTML_CLRLINE & htmltags[tag].flags)) { - /* Manage keeps! */ - if ( ! (HTML_KEEP & h->flags)) { - if (HTML_PREKEEP & h->flags) - h->flags |= HTML_KEEP; - putchar(' '); - } else - printf(" "); + if (tflags & HTML_NLBEFORE) + print_endline(h); + if (h->col == 0) + print_indent(h); + else if ((h->flags & HTML_NOSPACE) == 0) { + if (h->flags & HTML_KEEP) + print_word(h, " "); + else { + if (h->flags & HTML_PREKEEP) + h->flags |= HTML_KEEP; + print_endword(h); } + } if ( ! (h->flags & HTML_NONOSPACE)) h->flags &= ~HTML_NOSPACE; @@ -467,21 +487,164 @@ print_otag(struct html *h, enum htmltag tag, /* Print out the tag name and attributes. */ - printf("<%s", htmltags[tag].name); - for (i = 0; i < sz; i++) - print_attr(h, htmlattrs[p[i].key], p[i].val); + print_byte(h, '<'); + print_word(h, htmltags[tag].name); + + va_start(ap, fmt); + + have_style = 0; + while (*fmt != '\0') { + if (*fmt == 's') { + print_word(h, " style=\""); + have_style = 1; + fmt++; + break; + } + s = va_arg(ap, char *); + switch (*fmt++) { + case 'c': + attr = "class"; + break; + case 'h': + attr = "href"; + break; + case 'i': + attr = "id"; + break; + case '?': + attr = s; + s = va_arg(ap, char *); + break; + default: + abort(); + } + print_byte(h, ' '); + print_word(h, attr); + print_byte(h, '='); + print_byte(h, '"'); + switch (*fmt) { + case 'M': + print_href(h, s, va_arg(ap, char *), 1); + fmt++; + break; + case 'I': + print_href(h, s, NULL, 0); + fmt++; + break; + case 'R': + print_byte(h, '#'); + fmt++; + /* FALLTHROUGH */ + default: + print_encode(h, s, NULL, 1); + break; + } + print_byte(h, '"'); + } + + /* Print out styles. */ + + s = NULL; + su = &mysu; + while (*fmt != '\0') { + + /* First letter: input argument type. */ + + switch (*fmt++) { + case 'h': + i = va_arg(ap, int); + SCALE_HS_INIT(su, i); + break; + case 's': + s = va_arg(ap, char *); + break; + case 'u': + su = va_arg(ap, struct roffsu *); + break; + case 'v': + i = va_arg(ap, int); + SCALE_VS_INIT(su, i); + break; + case 'w': + s = va_arg(ap, char *); + a2width(s, su); + break; + default: + abort(); + } + + /* Second letter: style name. */ + + switch (*fmt++) { + case 'b': + attr = "margin-bottom"; + break; + case 'h': + attr = "height"; + break; + case 'i': + attr = "text-indent"; + break; + case 'l': + attr = "margin-left"; + break; + case 't': + attr = "margin-top"; + break; + case 'w': + attr = "width"; + break; + case 'W': + attr = "min-width"; + break; + case '?': + print_word(h, s); + print_byte(h, ':'); + print_byte(h, ' '); + print_word(h, va_arg(ap, char *)); + print_byte(h, ';'); + if (*fmt != '\0') + print_byte(h, ' '); + continue; + default: + abort(); + } + v = su->scale; + if (su->unit == SCALE_MM && (v /= 100.0) == 0.0) + v = 1.0; + else if (su->unit == SCALE_BU) + v /= 24.0; + print_word(h, attr); + print_byte(h, ':'); + print_byte(h, ' '); + (void)snprintf(numbuf, sizeof(numbuf), "%.2f", v); + print_word(h, numbuf); + print_word(h, roffscales[su->unit]); + print_byte(h, ';'); + if (*fmt != '\0') + print_byte(h, ' '); + } + if (have_style) + print_byte(h, '"'); + + va_end(ap); /* Accommodate for "well-formed" singleton escaping. */ if (HTML_AUTOCLOSE & htmltags[tag].flags) - putchar('/'); + print_byte(h, '/'); - putchar('>'); + print_byte(h, '>'); - h->flags |= HTML_NOSPACE; + if (tflags & HTML_NLBEGIN) + print_endline(h); + else + h->flags |= HTML_NOSPACE; - if ((HTML_AUTOCLOSE | HTML_CLRLINE) & htmltags[tag].flags) - putchar('\n'); + if (tflags & HTML_INDENT) + h->indent++; + if (tflags & HTML_NOINDENT) + h->noindent++; return t; } @@ -489,6 +652,7 @@ print_otag(struct html *h, enum htmltag tag, static void print_ctag(struct html *h, struct tag *tag) { + int tflags; /* * Remember to close out and nullify the current @@ -499,11 +663,21 @@ print_ctag(struct html *h, struct tag *tag) if (tag == h->tblt) h->tblt = NULL; - printf("", htmltags[tag->tag].name); - if (HTML_CLRLINE & htmltags[tag->tag].flags) { - h->flags |= HTML_NOSPACE; - putchar('\n'); - } + tflags = htmltags[tag->tag].flags; + + if (tflags & HTML_INDENT) + h->indent--; + if (tflags & HTML_NOINDENT) + h->noindent--; + if (tflags & HTML_NLEND) + print_endline(h); + print_indent(h); + print_byte(h, '<'); + print_byte(h, '/'); + print_word(h, htmltags[tag->tag].name); + print_byte(h, '>'); + if (tflags & HTML_NLAFTER) + print_endline(h); h->tags.head = tag->next; free(tag); @@ -512,42 +686,41 @@ print_ctag(struct html *h, struct tag *tag) void print_gen_decls(struct html *h) { - - puts(""); + print_word(h, ""); + print_endline(h); } void print_text(struct html *h, const char *word) { - - if ( ! (HTML_NOSPACE & h->flags)) { - /* Manage keeps! */ + if (h->col && (h->flags & HTML_NOSPACE) == 0) { if ( ! (HTML_KEEP & h->flags)) { if (HTML_PREKEEP & h->flags) h->flags |= HTML_KEEP; - putchar(' '); + print_endword(h); } else - printf(" "); + print_word(h, " "); } assert(NULL == h->metaf); switch (h->metac) { case HTMLFONT_ITALIC: - h->metaf = print_otag(h, TAG_I, 0, NULL); + h->metaf = print_otag(h, TAG_I, ""); break; case HTMLFONT_BOLD: - h->metaf = print_otag(h, TAG_B, 0, NULL); + h->metaf = print_otag(h, TAG_B, ""); break; case HTMLFONT_BI: - h->metaf = print_otag(h, TAG_B, 0, NULL); - print_otag(h, TAG_I, 0, NULL); + h->metaf = print_otag(h, TAG_B, ""); + print_otag(h, TAG_I, ""); break; default: + print_indent(h); break; } assert(word); - if ( ! print_encode(h, word, 0)) { + if ( ! print_encode(h, word, NULL, 0)) { if ( ! (h->flags & HTML_NONOSPACE)) h->flags &= ~HTML_NOSPACE; h->flags &= ~HTML_NONEWLINE; @@ -590,138 +763,136 @@ void print_paragraph(struct html *h) { struct tag *t; - struct htmlpair tag; - PAIR_CLASS_INIT(&tag, "spacer"); - t = print_otag(h, TAG_DIV, 1, &tag); + t = print_otag(h, TAG_DIV, "c", "Pp"); print_tagq(h, t); } -void -bufinit(struct html *h) -{ - - h->buf[0] = '\0'; - h->buflen = 0; -} - -void -bufcat_style(struct html *h, const char *key, const char *val) -{ - - bufcat(h, key); - bufcat(h, ":"); - bufcat(h, val); - bufcat(h, ";"); -} - -void -bufcat(struct html *h, const char *p) -{ - - /* - * XXX This is broken and not easy to fix. - * When using the -Oincludes option, buffmt_includes() - * may pass in strings overrunning BUFSIZ, causing a crash. - */ - - h->buflen = strlcat(h->buf, p, BUFSIZ); - assert(h->buflen < BUFSIZ); -} - -void -bufcat_fmt(struct html *h, const char *fmt, ...) -{ - va_list ap; - - va_start(ap, fmt); - (void)vsnprintf(h->buf + (int)h->buflen, - BUFSIZ - h->buflen - 1, fmt, ap); - va_end(ap); - h->buflen = strlen(h->buf); -} +/*********************************************************************** + * Low level output functions. + * They implement line breaking using a short static buffer. + ***********************************************************************/ +/* + * Buffer one HTML output byte. + * If the buffer is full, flush and deactivate it and start a new line. + * If the buffer is inactive, print directly. + */ static void -bufncat(struct html *h, const char *p, size_t sz) +print_byte(struct html *h, char c) { - - assert(h->buflen + sz + 1 < BUFSIZ); - strncat(h->buf, p, sz); - h->buflen += sz; -} - -void -buffmt_includes(struct html *h, const char *name) -{ - const char *p, *pp; - - pp = h->base_includes; - - bufinit(h); - while (NULL != (p = strchr(pp, '%'))) { - bufncat(h, pp, (size_t)(p - pp)); - switch (*(p + 1)) { - case'I': - bufcat(h, name); - break; - default: - bufncat(h, p, 2); - break; - } - pp = p + 2; + if ((h->flags & HTML_BUFFER) == 0) { + putchar(c); + h->col++; + return; } - if (pp) - bufcat(h, pp); -} -void -buffmt_man(struct html *h, const char *name, const char *sec) -{ - const char *p, *pp; - - pp = h->base_man; - - bufinit(h); - while (NULL != (p = strchr(pp, '%'))) { - bufncat(h, pp, (size_t)(p - pp)); - switch (*(p + 1)) { - case 'S': - bufcat(h, sec ? sec : "1"); - break; - case 'N': - bufcat_fmt(h, "%s", name); - break; - default: - bufncat(h, p, 2); - break; - } - pp = p + 2; + if (h->col + h->bufcol < sizeof(h->buf)) { + h->buf[h->bufcol++] = c; + return; } - if (pp) - bufcat(h, pp); + + putchar('\n'); + h->col = 0; + print_indent(h); + putchar(' '); + putchar(' '); + fwrite(h->buf, h->bufcol, 1, stdout); + putchar(c); + h->col = (h->indent + 1) * 2 + h->bufcol + 1; + h->bufcol = 0; + h->flags &= ~HTML_BUFFER; } -void -bufcat_su(struct html *h, const char *p, const struct roffsu *su) +/* + * If something was printed on the current output line, end it. + * Not to be called right after print_indent(). + */ +static void +print_endline(struct html *h) { - double v; + if (h->col == 0) + return; - v = su->scale; - if (SCALE_MM == su->unit && 0.0 == (v /= 100.0)) - v = 1.0; - else if (SCALE_BU == su->unit) - v /= 24.0; - - bufcat_fmt(h, "%s: %.2f%s;", p, v, roffscales[su->unit]); + if (h->bufcol) { + putchar(' '); + fwrite(h->buf, h->bufcol, 1, stdout); + h->bufcol = 0; + } + putchar('\n'); + h->col = 0; + h->flags |= HTML_NOSPACE; + h->flags &= ~HTML_BUFFER; } -void -bufcat_id(struct html *h, const char *src) +/* + * Flush the HTML output buffer. + * If it is inactive, activate it. + */ +static void +print_endword(struct html *h) { + if (h->noindent) { + print_byte(h, ' '); + return; + } - /* Cf. . */ - - for (; '\0' != *src; src++) - bufncat(h, *src == ' ' ? "_" : src, 1); + if ((h->flags & HTML_BUFFER) == 0) { + h->col++; + h->flags |= HTML_BUFFER; + } else if (h->bufcol) { + putchar(' '); + fwrite(h->buf, h->bufcol, 1, stdout); + h->col += h->bufcol + 1; + } + h->bufcol = 0; +} + +/* + * If at the beginning of a new output line, + * perform indentation and mark the line as containing output. + * Make sure to really produce some output right afterwards, + * but do not use print_otag() for producing it. + */ +static void +print_indent(struct html *h) +{ + size_t i; + + if (h->col) + return; + + if (h->noindent == 0) { + h->col = h->indent * 2; + for (i = 0; i < h->col; i++) + putchar(' '); + } + h->flags &= ~HTML_NOSPACE; +} + +/* + * Print or buffer some characters + * depending on the current HTML output buffer state. + */ +static void +print_word(struct html *h, const char *cp) +{ + while (*cp != '\0') + print_byte(h, *cp++); +} + +/* + * Calculate the scaling unit passed in a `-width' argument. This uses + * either a native scaling unit (e.g., 1i, 2m) or the string length of + * the value. + */ +static void +a2width(const char *p, struct roffsu *su) +{ + if (a2roffsu(p, su, SCALE_MAX) < 2) { + su->unit = SCALE_EN; + su->scale = html_strlen(p); + } else if (su->scale < 0.0) + su->scale = 0.0; } diff --git a/html.h b/html.h index 27dc140185da..19532c49d93e 100644 --- a/html.h +++ b/html.h @@ -1,6 +1,7 @@ -/* $Id: html.h,v 1.72 2015/11/07 14:01:16 schwarze Exp $ */ +/* $Id: html.h,v 1.78 2017/01/19 16:59:30 schwarze Exp $ */ /* * Copyright (c) 2008-2011, 2014 Kristaps Dzonsons + * Copyright (c) 2017 Ingo Schwarze * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -39,7 +40,6 @@ enum htmltag { TAG_DL, TAG_DT, TAG_DD, - TAG_BLOCKQUOTE, TAG_PRE, TAG_B, TAG_I, @@ -65,23 +65,6 @@ enum htmltag { TAG_MAX }; -enum htmlattr { - ATTR_NAME, - ATTR_REL, - ATTR_HREF, - ATTR_TYPE, - ATTR_MEDIA, - ATTR_CLASS, - ATTR_STYLE, - ATTR_ID, - ATTR_COLSPAN, - ATTR_CHARSET, - ATTR_OPEN, - ATTR_CLOSE, - ATTR_MATHVARIANT, - ATTR_MAX -}; - enum htmlfont { HTMLFONT_NONE = 0, HTMLFONT_BOLD, @@ -99,22 +82,6 @@ struct tagq { struct tag *head; }; -struct htmlpair { - enum htmlattr key; - const char *val; -}; - -#define PAIR_INIT(p, t, v) \ - do { \ - (p)->key = (t); \ - (p)->val = (v); \ - } while (/* CONSTCOND */ 0) - -#define PAIR_ID_INIT(p, v) PAIR_INIT(p, ATTR_ID, v) -#define PAIR_CLASS_INIT(p, v) PAIR_INIT(p, ATTR_CLASS, v) -#define PAIR_HREF_INIT(p, v) PAIR_INIT(p, ATTR_HREF, v) -#define PAIR_STYLE_INIT(p, h) PAIR_INIT(p, ATTR_STYLE, (h)->buf) - struct html { int flags; #define HTML_NOSPACE (1 << 0) /* suppress next space */ @@ -127,14 +94,18 @@ struct html { #define HTML_NOSPLIT (1 << 7) /* do not break line before .An */ #define HTML_SPLIT (1 << 8) /* break line before .An */ #define HTML_NONEWLINE (1 << 9) /* No line break in nofill mode. */ +#define HTML_BUFFER (1 << 10) /* Collect a word to see if it fits. */ + size_t indent; /* current output indentation level */ + int noindent; /* indent disabled by
 */
+	size_t		  col; /* current output byte position */
+	size_t		  bufcol; /* current buf byte position */
+	char		  buf[80]; /* output buffer */
 	struct tagq	  tags; /* stack of open tags */
 	struct rofftbl	  tbl; /* current table */
 	struct tag	 *tblt; /* current open table scope */
 	char		 *base_man; /* base for manpage href */
 	char		 *base_includes; /* base for include href */
 	char		 *style; /* style-sheet URI */
-	char		  buf[BUFSIZ]; /* see bufcat and friends */
-	size_t		  buflen;
 	struct tag	 *metaf; /* current open font scope */
 	enum htmlfont	  metal; /* last used font */
 	enum htmlfont	  metac; /* current font mode */
@@ -148,8 +119,7 @@ struct	eqn;
 
 void		  print_gen_decls(struct html *);
 void		  print_gen_head(struct html *);
-struct tag	 *print_otag(struct html *, enum htmltag,
-				int, const struct htmlpair *);
+struct tag	 *print_otag(struct html *, enum htmltag, const char *, ...);
 void		  print_tagq(struct html *, const struct tag *);
 void		  print_stagq(struct html *, const struct tag *);
 void		  print_text(struct html *, const char *);
@@ -158,19 +128,4 @@ void		  print_tbl(struct html *, const struct tbl_span *);
 void		  print_eqn(struct html *, const struct eqn *);
 void		  print_paragraph(struct html *);
 
-#if __GNUC__ - 0 >= 4
-__attribute__((__format__ (__printf__, 2, 3)))
-#endif
-void		  bufcat_fmt(struct html *, const char *, ...);
-void		  bufcat(struct html *, const char *);
-void		  bufcat_id(struct html *, const char *);
-void		  bufcat_style(struct html *,
-			const char *, const char *);
-void		  bufcat_su(struct html *, const char *,
-			const struct roffsu *);
-void		  bufinit(struct html *);
-void		  buffmt_man(struct html *,
-			const char *, const char *);
-void		  buffmt_includes(struct html *, const char *);
-
 int		  html_strlen(const char *);
diff --git a/lib.in b/lib.in
index f65c9c2f69ed..8b03df2ed3ec 100644
--- a/lib.in
+++ b/lib.in
@@ -1,4 +1,4 @@
-/*	$Id: lib.in,v 1.18 2014/01/06 00:53:33 schwarze Exp $ */
+/*	$Id: lib.in,v 1.19 2016/11/23 20:22:13 schwarze Exp $ */
 /*
  * Copyright (c) 2009 Kristaps Dzonsons 
  * Copyright (c) 2009, 2012 Joerg Sonnenberger 
@@ -83,6 +83,7 @@ LINE("libnetpgpverify",	"Netpgp Verification (libnetpgpverify, \\-lnetpgpverify)
 LINE("libnpf",		"NPF Packet Filter Library (libnpf, \\-lnpf)")
 LINE("libossaudio",	"OSS Audio Emulation Library (libossaudio, \\-lossaudio)")
 LINE("libpam",		"Pluggable Authentication Module Library (libpam, \\-lpam)")
+LINE("libpanel",	"Z-order for curses windows (libpanel, \\-lpanel)")
 LINE("libpcap",		"Capture Library (libpcap, \\-lpcap)")
 LINE("libpci",		"PCI Bus Access Library (libpci, \\-lpci)")
 LINE("libpmc",		"Performance Counters Library (libpmc, \\-lpmc)")
@@ -90,8 +91,10 @@ LINE("libppath",	"Property-List Paths Library (libppath, \\-lppath)")
 LINE("libposix",	"POSIX Compatibility Library (libposix, \\-lposix)")
 LINE("libposix1e",	"POSIX.1e Security API Library (libposix1e, \\-lposix1e)")
 LINE("libppath",	"Property-List Paths Library (libppath, \\-lppath)")
+LINE("libproc",		"Process Manipulation Library (libproc, \\-lproc)")
 LINE("libprop",		"Property Container Object Library (libprop, \\-lprop)")
 LINE("libpthread",	"POSIX Threads Library (libpthread, \\-lpthread)")
+LINE("libpthread_dbg",	"POSIX Debug Threads Library (libpthread_dbg, \\-lpthread_dbg)")
 LINE("libpuffs",	"puffs Convenience Library (libpuffs, \\-lpuffs)")
 LINE("libquota",	"Disk Quota Access and Control Library (libquota, \\-lquota)")
 LINE("libradius",	"RADIUS Client Library (libradius, \\-lradius)")
@@ -100,6 +103,7 @@ LINE("libresolv",	"DNS Resolver Library (libresolv, \\-lresolv)")
 LINE("librpcsec_gss",	"RPC GSS-API Authentication Library (librpcsec_gss, \\-lrpcsec_gss)")
 LINE("librpcsvc",	"RPC Service Library (librpcsvc, \\-lrpcsvc)")
 LINE("librt",		"POSIX Real\\-time Library (librt, \\-lrt)")
+LINE("librtld_db",	"Debugging interface to the runtime linker Library (librtld_db, \\-lrtld_db)")
 LINE("librumpclient",	"Clientside Stubs for rump Kernel Remote Protocols (librumpclient, \\-lrumpclient)")
 LINE("libsaslc",	"Simple Authentication and Security Layer client library (libsaslc, \\-lsaslc)")
 LINE("libsdp",		"Bluetooth Service Discovery Protocol User Library (libsdp, \\-lsdp)")
diff --git a/libmandoc.h b/libmandoc.h
index 9ed8f15049f6..96e726cbce74 100644
--- a/libmandoc.h
+++ b/libmandoc.h
@@ -1,4 +1,4 @@
-/*	$Id: libmandoc.h,v 1.63 2016/07/07 19:19:01 schwarze Exp $ */
+/*	$Id: libmandoc.h,v 1.64 2016/07/19 13:36:13 schwarze Exp $ */
 /*
  * Copyright (c) 2009, 2010, 2011, 2012 Kristaps Dzonsons 
  * Copyright (c) 2013, 2014, 2015 Ingo Schwarze 
@@ -41,11 +41,9 @@ struct	roff_man;
 
 void		 mandoc_msg(enum mandocerr, struct mparse *,
 			int, int, const char *);
-#if __GNUC__ - 0 >= 4
-__attribute__((__format__ (__printf__, 5, 6)))
-#endif
 void		 mandoc_vmsg(enum mandocerr, struct mparse *,
-			int, int, const char *, ...);
+			int, int, const char *, ...)
+			__attribute__((__format__ (printf, 5, 6)));
 char		*mandoc_getarg(struct mparse *, char **, int, int *);
 char		*mandoc_normdate(struct mparse *, char *, int, int);
 int		 mandoc_eos(const char *, size_t);
diff --git a/main.c b/main.c
index 527db244283e..b64b3be11501 100644
--- a/main.c
+++ b/main.c
@@ -1,7 +1,7 @@
-/*	$Id: main.c,v 1.269 2016/07/12 05:18:38 kristaps Exp $ */
+/*	$Id: main.c,v 1.279 2017/01/09 17:49:57 schwarze Exp $ */
 /*
  * Copyright (c) 2008-2012 Kristaps Dzonsons 
- * Copyright (c) 2010-2012, 2014-2016 Ingo Schwarze 
+ * Copyright (c) 2010-2012, 2014-2017 Ingo Schwarze 
  * Copyright (c) 2010 Joerg Sonnenberger 
  *
  * Permission to use, copy, modify, and distribute this software for any
@@ -51,12 +51,6 @@
 #include "manconf.h"
 #include "mansearch.h"
 
-#if !defined(__GNUC__) || (__GNUC__ < 2)
-# if !defined(lint)
-#  define __attribute__(x)
-# endif
-#endif /* !defined(__GNUC__) || (__GNUC__ < 2) */
-
 enum	outmode {
 	OUTMODE_DEF = 0,
 	OUTMODE_FLN,
@@ -87,6 +81,9 @@ struct	curparse {
 	struct manoutput *outopts;	/* output options */
 };
 
+
+int			  mandocdb(int, char *[]);
+
 static	int		  fs_lookup(const struct manpaths *,
 				size_t ipath, const char *,
 				const char *, const char *,
@@ -95,12 +92,10 @@ static	void		  fs_search(const struct mansearch *,
 				const struct manpaths *, int, char**,
 				struct manpage **, size_t *);
 static	int		  koptions(int *, char *);
-#if HAVE_SQLITE3
-int			  mandocdb(int, char**);
-#endif
 static	int		  moptions(int *, char *);
 static	void		  mmsg(enum mandocerr, enum mandoclevel,
 				const char *, int, int, const char *);
+static	void		  outdata_alloc(struct curparse *);
 static	void		  parse(struct curparse *, int, const char *);
 static	void		  passthrough(const char *, int, int);
 static	pid_t		  spawn_pager(struct tag_files *);
@@ -151,11 +146,9 @@ main(int argc, char *argv[])
 	setprogname(progname);
 #endif
 
-#if HAVE_SQLITE3
 	if (strncmp(progname, "mandocdb", 8) == 0 ||
 	    strcmp(progname, BINM_MAKEWHATIS) == 0)
 		return mandocdb(argc, argv);
-#endif
 
 #if HAVE_PLEDGE
 	if (pledge("stdio rpath tmppath tty proc exec flock", NULL) == -1)
@@ -353,9 +346,6 @@ main(int argc, char *argv[])
 	/* man(1), whatis(1), apropos(1) */
 
 	if (search.argmode != ARG_FILE) {
-		if (argc == 0)
-			usage(search.argmode);
-
 		if (search.argmode == ARG_NAME &&
 		    outmode == OUTMODE_ONE)
 			search.firstmatch = 1;
@@ -363,19 +353,9 @@ main(int argc, char *argv[])
 		/* Access the mandoc database. */
 
 		manconf_parse(&conf, conf_file, defpaths, auxpaths);
-#if HAVE_SQLITE3
-		mansearch_setup(1);
 		if ( ! mansearch(&search, &conf.manpath,
 		    argc, argv, &res, &sz))
 			usage(search.argmode);
-#else
-		if (search.argmode != ARG_NAME) {
-			fputs("mandoc: database support not compiled in\n",
-			    stderr);
-			return (int)MANDOCLEVEL_BADARG;
-		}
-		sz = 0;
-#endif
 
 		if (sz == 0) {
 			if (search.argmode == ARG_NAME)
@@ -478,7 +458,7 @@ main(int argc, char *argv[])
 
 			if (resp == NULL)
 				parse(&curp, fd, *argv);
-			else if (resp->form & FORM_SRC) {
+			else if (resp->form == FORM_SRC) {
 				/* For .so only; ignore failure. */
 				chdir(conf.manpath.paths[resp->ipath]);
 				parse(&curp, fd, resp->file);
@@ -486,8 +466,11 @@ main(int argc, char *argv[])
 				passthrough(resp->file, fd,
 				    conf.output.synopsisonly);
 
-			if (argc > 1 && curp.outtype <= OUTT_UTF8)
+			if (argc > 1 && curp.outtype <= OUTT_UTF8) {
+				if (curp.outdata == NULL)
+					outdata_alloc(&curp);
 				terminal_sepline(curp.outdata);
+			}
 		} else if (rc < MANDOCLEVEL_ERROR)
 			rc = MANDOCLEVEL_ERROR;
 
@@ -526,10 +509,7 @@ main(int argc, char *argv[])
 out:
 	if (search.argmode != ARG_FILE) {
 		manconf_free(&conf);
-#if HAVE_SQLITE3
 		mansearch_free(res, sz);
-		mansearch_setup(0);
-#endif
 	}
 
 	free(defos);
@@ -551,10 +531,10 @@ main(int argc, char *argv[])
 
 			/* Stop here until moved to the foreground. */
 
-			tc_pgid = tcgetpgrp(STDIN_FILENO);
+			tc_pgid = tcgetpgrp(tag_files->ofd);
 			if (tc_pgid != man_pgid) {
 				if (tc_pgid == pager_pid) {
-					(void)tcsetpgrp(STDIN_FILENO,
+					(void)tcsetpgrp(tag_files->ofd,
 					    man_pgid);
 					if (signum == SIGTTIN)
 						continue;
@@ -567,7 +547,7 @@ main(int argc, char *argv[])
 			/* Once in the foreground, activate the pager. */
 
 			if (pager_pid) {
-				(void)tcsetpgrp(STDIN_FILENO, pager_pid);
+				(void)tcsetpgrp(tag_files->ofd, pager_pid);
 				kill(pager_pid, SIGCONT);
 			} else
 				pager_pid = spawn_pager(tag_files);
@@ -633,7 +613,8 @@ fs_lookup(const struct manpaths *paths, size_t ipath,
 	glob_t		 globinfo;
 	struct manpage	*page;
 	char		*file;
-	int		 form, globres;
+	int		 globres;
+	enum form	 form;
 
 	form = FORM_SRC;
 	mandoc_asprintf(&file, "%s/man%s/%s.%s",
@@ -671,10 +652,8 @@ fs_lookup(const struct manpaths *paths, size_t ipath,
 		return 0;
 
 found:
-#if HAVE_SQLITE3
 	warnx("outdated mandoc.db lacks %s(%s) entry, run %s %s",
 	    name, sec, BINM_MAKEWHATIS, paths->paths[ipath]);
-#endif
 	*res = mandoc_reallocarray(*res, ++*ressz, sizeof(struct manpage));
 	page = *res + (*ressz - 1);
 	page->file = file;
@@ -747,32 +726,8 @@ parse(struct curparse *curp, int fd, const char *file)
 	if (rctmp != MANDOCLEVEL_OK && curp->wstop)
 		return;
 
-	/* If unset, allocate output dev now (if applicable). */
-
-	if (curp->outdata == NULL) {
-		switch (curp->outtype) {
-		case OUTT_HTML:
-			curp->outdata = html_alloc(curp->outopts);
-			break;
-		case OUTT_UTF8:
-			curp->outdata = utf8_alloc(curp->outopts);
-			break;
-		case OUTT_LOCALE:
-			curp->outdata = locale_alloc(curp->outopts);
-			break;
-		case OUTT_ASCII:
-			curp->outdata = ascii_alloc(curp->outopts);
-			break;
-		case OUTT_PDF:
-			curp->outdata = pdf_alloc(curp->outopts);
-			break;
-		case OUTT_PS:
-			curp->outdata = ps_alloc(curp->outopts);
-			break;
-		default:
-			break;
-		}
-	}
+	if (curp->outdata == NULL)
+		outdata_alloc(curp);
 
 	mparse_result(curp->mp, &man, NULL);
 
@@ -826,6 +781,34 @@ parse(struct curparse *curp, int fd, const char *file)
 			break;
 		}
 	}
+	mparse_updaterc(curp->mp, &rc);
+}
+
+static void
+outdata_alloc(struct curparse *curp)
+{
+	switch (curp->outtype) {
+	case OUTT_HTML:
+		curp->outdata = html_alloc(curp->outopts);
+		break;
+	case OUTT_UTF8:
+		curp->outdata = utf8_alloc(curp->outopts);
+		break;
+	case OUTT_LOCALE:
+		curp->outdata = locale_alloc(curp->outopts);
+		break;
+	case OUTT_ASCII:
+		curp->outdata = ascii_alloc(curp->outopts);
+		break;
+	case OUTT_PDF:
+		curp->outdata = pdf_alloc(curp->outopts);
+		break;
+	case OUTT_PS:
+		curp->outdata = ps_alloc(curp->outopts);
+		break;
+	default:
+		break;
+	}
 }
 
 static void
@@ -838,11 +821,17 @@ passthrough(const char *file, int fd, int synopsis_only)
 	const char	*syscall;
 	char		*line, *cp;
 	size_t		 linesz;
+	ssize_t		 len, written;
 	int		 print;
 
 	line = NULL;
 	linesz = 0;
 
+	if (fflush(stdout) == EOF) {
+		syscall = "fflush";
+		goto fail;
+	}
+
 	if ((stream = fdopen(fd, "r")) == NULL) {
 		close(fd);
 		syscall = "fdopen";
@@ -850,14 +839,16 @@ passthrough(const char *file, int fd, int synopsis_only)
 	}
 
 	print = 0;
-	while (getline(&line, &linesz, stream) != -1) {
+	while ((len = getline(&line, &linesz, stream)) != -1) {
 		cp = line;
 		if (synopsis_only) {
 			if (print) {
 				if ( ! isspace((unsigned char)*cp))
 					goto done;
-				while (isspace((unsigned char)*cp))
+				while (isspace((unsigned char)*cp)) {
 					cp++;
+					len--;
+				}
 			} else {
 				if (strcmp(cp, synb) == 0 ||
 				    strcmp(cp, synr) == 0)
@@ -865,9 +856,11 @@ passthrough(const char *file, int fd, int synopsis_only)
 				continue;
 			}
 		}
-		if (fputs(cp, stdout)) {
+		for (; len > 0; len -= written) {
+			if ((written = write(STDOUT_FILENO, cp, len)) != -1)
+				continue;
 			fclose(stream);
-			syscall = "fputs";
+			syscall = "write";
 			goto fail;
 		}
 	}
@@ -978,7 +971,7 @@ woptions(struct curparse *curp, char *arg)
 
 	while (*arg) {
 		o = arg;
-		switch (getsubopt(&arg, UNCONST(toks), &v)) {
+		switch (getsubopt(&arg, (char * const *)toks, &v)) {
 		case 0:
 			curp->wstop = 1;
 			break;
@@ -1010,7 +1003,8 @@ mmsg(enum mandocerr t, enum mandoclevel lvl,
 {
 	const char	*mparse_msg;
 
-	fprintf(stderr, "%s: %s:", getprogname(), file);
+	fprintf(stderr, "%s: %s:", getprogname(),
+	    file == NULL ? "" : file);
 
 	if (line)
 		fprintf(stderr, "%d:%d:", line, col + 1);
@@ -1082,7 +1076,7 @@ spawn_pager(struct tag_files *tag_files)
 		break;
 	default:
 		(void)setpgid(pager_pid, 0);
-		(void)tcsetpgrp(STDIN_FILENO, pager_pid);
+		(void)tcsetpgrp(tag_files->ofd, pager_pid);
 #if HAVE_PLEDGE
 		if (pledge("stdio rpath tmppath tty proc", NULL) == -1)
 			err((int)MANDOCLEVEL_SYSERR, "pledge");
@@ -1100,7 +1094,7 @@ spawn_pager(struct tag_files *tag_files)
 
 	/* Do not start the pager before controlling the terminal. */
 
-	while (tcgetpgrp(STDIN_FILENO) != getpid())
+	while (tcgetpgrp(STDOUT_FILENO) != getpid())
 		nanosleep(&timeout, NULL);
 
 	execvp(argv[0], argv);
diff --git a/main.h b/main.h
index a53df93c38f3..f12f3e4c3a05 100644
--- a/main.h
+++ b/main.h
@@ -1,4 +1,4 @@
-/*	$Id: main.h,v 1.25 2016/07/08 22:29:05 schwarze Exp $ */
+/*	$Id: main.h,v 1.26 2016/07/15 19:33:01 schwarze Exp $ */
 /*
  * Copyright (c) 2009, 2010, 2011 Kristaps Dzonsons 
  * Copyright (c) 2014, 2015 Ingo Schwarze 
@@ -16,8 +16,6 @@
  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  */
 
-#define	UNCONST(a)	((void *)(uintptr_t)(const void *)(a))
-
 struct	roff_man;
 struct	manoutput;
 
diff --git a/makewhatis.8 b/makewhatis.8
index 8a5de938fd73..945c05361b39 100644
--- a/makewhatis.8
+++ b/makewhatis.8
@@ -1,4 +1,4 @@
-.\"	$Id: makewhatis.8,v 1.3 2014/08/17 21:03:06 schwarze Exp $
+.\"	$Id: makewhatis.8,v 1.4 2016/07/19 22:40:33 schwarze Exp $
 .\"
 .\" Copyright (c) 2011, 2012 Kristaps Dzonsons 
 .\" Copyright (c) 2011, 2012 Ingo Schwarze 
@@ -15,7 +15,7 @@
 .\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 .\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 .\"
-.Dd $Mdocdate: August 17 2014 $
+.Dd $Mdocdate: July 19 2016 $
 .Dt MAKEWHATIS 8
 .Os
 .Sh NAME
@@ -79,8 +79,6 @@ If
 is not provided,
 .Nm
 uses the default paths stipulated by
-.Xr manpath 1 ,
-or
 .Xr man.conf 5 .
 .Pp
 The arguments are as follows:
diff --git a/man.1 b/man.1
index 0a7ae6d904fc..191bc9594cc1 100644
--- a/man.1
+++ b/man.1
@@ -1,4 +1,4 @@
-.\"	$Id: man.1,v 1.17 2016/07/01 20:24:04 schwarze Exp $
+.\"	$Id: man.1,v 1.20 2017/01/06 01:34:57 schwarze Exp $
 .\"
 .\" Copyright (c) 1989, 1990, 1993
 .\"	The Regents of the University of California.  All rights reserved.
@@ -31,7 +31,7 @@
 .\"
 .\"     @(#)man.1	8.2 (Berkeley) 1/2/94
 .\"
-.Dd $Mdocdate: July 1 2016 $
+.Dd $Mdocdate: January 6 2017 $
 .Dt MAN 1
 .Os
 .Sh NAME
@@ -69,12 +69,8 @@ machine architecture
 The options are as follows:
 .Bl -tag -width Ds
 .It Fl a
-Display all of the manual pages for a specified
-.Ar section
-and
-.Ar name
-combination.
-Normally, only the first manual page found is displayed.
+Display all matching manual pages.
+Normally, only the first page found is displayed.
 .It Fl C Ar file
 Use the specified
 .Ar file
@@ -100,6 +96,12 @@ This overrides any earlier
 and
 .Fl l
 options.
+.It Fl h
+Display only the SYNOPSIS lines of the requested manual pages.
+Implies
+.Fl a
+and
+.Fl c .
 .It Fl I Cm os Ns = Ns Ar name
 Override the default operating system
 .Ar name
@@ -110,12 +112,6 @@ and for the
 .Xr man 7
 .Ic \&TH
 macro.
-.It Fl h
-Display only the SYNOPSIS lines of the requested manual pages.
-Implies
-.Fl a
-and
-.Fl c .
 .It Fl K Ar encoding
 Specify the input encoding.
 The supported
@@ -330,7 +326,19 @@ is used, the interactive
 .Ic :t
 command can be used to go to the definitions of various terms, for
 example command line options, command modifiers, internal commands,
-and environment variables.
+environment variables, function names, preprocessor macros,
+.Xr errno 2
+values, and some other emphasized words.
+Some terms may have defining text at more than one place.
+In that case, the
+.Xr less 1
+interactive commands
+.Ic t
+and
+.Ic T
+can be used to move to the next and to the previous place providing
+information about the term last searched for with
+.Ic :t .
 .It Ev MANPATH
 The standard search path used by
 .Nm
diff --git a/man.c b/man.c
index 31c094e8d62c..a2db05fbaa3b 100644
--- a/man.c
+++ b/man.c
@@ -1,4 +1,4 @@
-/*	$Id: man.c,v 1.166 2015/10/22 21:54:23 schwarze Exp $ */
+/*	$Id: man.c,v 1.167 2017/01/10 13:47:00 schwarze Exp $ */
 /*
  * Copyright (c) 2008, 2009, 2010, 2011 Kristaps Dzonsons 
  * Copyright (c) 2013, 2014, 2015 Ingo Schwarze 
@@ -149,7 +149,7 @@ man_ptext(struct roff_man *man, int line, char *buf, int offs)
 
 	assert(i);
 	if (mandoc_eos(buf, (size_t)i))
-		man->last->flags |= MAN_EOS;
+		man->last->flags |= NODE_EOS;
 
 	man_descope(man, line, offs);
 	return 1;
@@ -340,7 +340,7 @@ man_state(struct roff_man *man, struct roff_node *n)
 	switch(n->tok) {
 	case MAN_nf:
 	case MAN_EX:
-		if (man->flags & MAN_LITERAL && ! (n->flags & MAN_VALID))
+		if (man->flags & MAN_LITERAL && ! (n->flags & NODE_VALID))
 			mandoc_msg(MANDOCERR_NF_SKIP, man->parse,
 			    n->line, n->pos, "nf");
 		man->flags |= MAN_LITERAL;
@@ -348,7 +348,7 @@ man_state(struct roff_man *man, struct roff_node *n)
 	case MAN_fi:
 	case MAN_EE:
 		if ( ! (man->flags & MAN_LITERAL) &&
-		     ! (n->flags & MAN_VALID))
+		     ! (n->flags & NODE_VALID))
 			mandoc_msg(MANDOCERR_FI_SKIP, man->parse,
 			    n->line, n->pos, "fi");
 		man->flags &= ~MAN_LITERAL;
@@ -356,7 +356,7 @@ man_state(struct roff_man *man, struct roff_node *n)
 	default:
 		break;
 	}
-	man->last->flags |= MAN_VALID;
+	man->last->flags |= NODE_VALID;
 }
 
 void
diff --git a/man.conf.5 b/man.conf.5
index 9cfeca761d3b..425895c1dc8a 100644
--- a/man.conf.5
+++ b/man.conf.5
@@ -1,4 +1,4 @@
-.\"	$Id: man.conf.5,v 1.3 2015/03/27 21:33:20 schwarze Exp $
+.\"	$Id: man.conf.5,v 1.4 2016/12/28 22:52:17 schwarze Exp $
 .\"
 .\" Copyright (c) 2015 Ingo Schwarze 
 .\"
@@ -14,7 +14,7 @@
 .\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 .\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 .\"
-.Dd $Mdocdate: March 27 2015 $
+.Dd $Mdocdate: December 28 2016 $
 .Dt MAN.CONF 5
 .Os
 .Sh NAME
@@ -91,7 +91,7 @@ manual.
 .It Ic fragment Ta none     Ta Cm html Ta print only body
 .It Ic includes Ta string   Ta Cm html Ta path to header files
 .It Ic indent   Ta integer  Ta Cm ascii , utf8 Ta left margin
-.It Ic man      Ta string   Ta Cm html Ta path for Xr links
+.It Ic man      Ta string   Ta Cm html Ta path for \&Xr links
 .It Ic paper    Ta string   Ta Cm ps , pdf Ta paper size
 .It Ic style    Ta string   Ta Cm html Ta CSS file
 .It Ic width    Ta integer  Ta Cm ascii , utf8 Ta right margin
diff --git a/man_hash.c b/man_hash.c
index 8573994e5d53..bb7b4665b348 100644
--- a/man_hash.c
+++ b/man_hash.c
@@ -1,4 +1,4 @@
-/*	$Id: man_hash.c,v 1.34 2015/10/06 18:32:19 schwarze Exp $ */
+/*	$Id: man_hash.c,v 1.35 2016/07/15 18:03:45 schwarze Exp $ */
 /*
  * Copyright (c) 2008, 2009, 2010 Kristaps Dzonsons 
  * Copyright (c) 2015 Ingo Schwarze 
@@ -24,8 +24,10 @@
 #include 
 #include 
 
+#include "mandoc.h"
 #include "roff.h"
 #include "man.h"
+#include "libmandoc.h"
 #include "libman.h"
 
 #define	HASH_DEPTH	 6
diff --git a/man_html.c b/man_html.c
index d71eb3823798..641e0e336eaf 100644
--- a/man_html.c
+++ b/man_html.c
@@ -1,7 +1,7 @@
-/*	$Id: man_html.c,v 1.120 2016/01/08 17:48:09 schwarze Exp $ */
+/*	$Id: man_html.c,v 1.129 2017/01/21 01:20:32 schwarze Exp $ */
 /*
  * Copyright (c) 2008-2012, 2014 Kristaps Dzonsons 
- * Copyright (c) 2013, 2014, 2015 Ingo Schwarze 
+ * Copyright (c) 2013, 2014, 2015, 2017 Ingo Schwarze 
  *
  * Permission to use, copy, modify, and distribute this software for any
  * purpose with or without fee is hereby granted, provided that the above
@@ -147,40 +147,39 @@ void
 html_man(void *arg, const struct roff_man *man)
 {
 	struct mhtml	 mh;
-	struct htmlpair	 tag;
 	struct html	*h;
-	struct tag	*t, *tt;
+	struct tag	*t;
 
 	memset(&mh, 0, sizeof(mh));
-	PAIR_CLASS_INIT(&tag, "mandoc");
 	h = (struct html *)arg;
 
-	if ( ! (HTML_FRAGMENT & h->oflags)) {
+	if ((h->oflags & HTML_FRAGMENT) == 0) {
 		print_gen_decls(h);
-		t = print_otag(h, TAG_HTML, 0, NULL);
-		tt = print_otag(h, TAG_HEAD, 0, NULL);
+		print_otag(h, TAG_HTML, "");
+		t = print_otag(h, TAG_HEAD, "");
 		print_man_head(&man->meta, man->first, &mh, h);
-		print_tagq(h, tt);
-		print_otag(h, TAG_BODY, 0, NULL);
-		print_otag(h, TAG_DIV, 1, &tag);
-	} else
-		t = print_otag(h, TAG_DIV, 1, &tag);
+		print_tagq(h, t);
+		print_otag(h, TAG_BODY, "");
+	}
 
-	print_man_nodelist(&man->meta, man->first, &mh, h);
+	man_root_pre(&man->meta, man->first, &mh, h);
+	t = print_otag(h, TAG_DIV, "c", "manual-text");
+	print_man_nodelist(&man->meta, man->first->child, &mh, h);
 	print_tagq(h, t);
-	putchar('\n');
+	man_root_post(&man->meta, man->first, &mh, h);
+	print_tagq(h, NULL);
 }
 
 static void
 print_man_head(MAN_ARGS)
 {
+	char	*cp;
 
 	print_gen_head(h);
-	assert(man->title);
-	assert(man->msec);
-	bufcat_fmt(h, "%s(%s)", man->title, man->msec);
-	print_otag(h, TAG_TITLE, 0, NULL);
-	print_text(h, h->buf);
+	mandoc_asprintf(&cp, "%s(%s)", man->title, man->msec);
+	print_otag(h, TAG_TITLE, "");
+	print_text(h, cp);
+	free(cp);
 }
 
 static void
@@ -203,23 +202,18 @@ print_man_node(MAN_ARGS)
 	t = h->tags.head;
 
 	switch (n->type) {
-	case ROFFT_ROOT:
-		man_root_pre(man, n, mh, h);
-		break;
 	case ROFFT_TEXT:
 		if ('\0' == *n->string) {
 			print_paragraph(h);
 			return;
 		}
-		if (n->flags & MAN_LINE && (*n->string == ' ' ||
+		if (n->flags & NODE_LINE && (*n->string == ' ' ||
 		    (n->prev != NULL && mh->fl & MANH_LITERAL &&
 		     ! (h->flags & HTML_NONEWLINE))))
-			print_otag(h, TAG_BR, 0, NULL);
+			print_otag(h, TAG_BR, "");
 		print_text(h, n->string);
 		return;
 	case ROFFT_EQN:
-		if (n->flags & MAN_LINE)
-			putchar('\n');
 		print_eqn(h, n->eqn);
 		break;
 	case ROFFT_TBL:
@@ -261,9 +255,6 @@ print_man_node(MAN_ARGS)
 	print_stagq(h, t);
 
 	switch (n->type) {
-	case ROFFT_ROOT:
-		man_root_post(man, n, mh, h);
-		break;
 	case ROFFT_EQN:
 		break;
 	default:
@@ -288,7 +279,6 @@ a2width(const struct roff_node *n, struct roffsu *su)
 static void
 man_root_pre(MAN_ARGS)
 {
-	struct htmlpair	 tag;
 	struct tag	*t, *tt;
 	char		*title;
 
@@ -296,26 +286,20 @@ man_root_pre(MAN_ARGS)
 	assert(man->msec);
 	mandoc_asprintf(&title, "%s(%s)", man->title, man->msec);
 
-	PAIR_CLASS_INIT(&tag, "head");
-	t = print_otag(h, TAG_TABLE, 1, &tag);
+	t = print_otag(h, TAG_TABLE, "c", "head");
+	print_otag(h, TAG_TBODY, "");
+	tt = print_otag(h, TAG_TR, "");
 
-	print_otag(h, TAG_TBODY, 0, NULL);
-
-	tt = print_otag(h, TAG_TR, 0, NULL);
-
-	PAIR_CLASS_INIT(&tag, "head-ltitle");
-	print_otag(h, TAG_TD, 1, &tag);
+	print_otag(h, TAG_TD, "c", "head-ltitle");
 	print_text(h, title);
 	print_stagq(h, tt);
 
-	PAIR_CLASS_INIT(&tag, "head-vol");
-	print_otag(h, TAG_TD, 1, &tag);
+	print_otag(h, TAG_TD, "c", "head-vol");
 	if (NULL != man->vol)
 		print_text(h, man->vol);
 	print_stagq(h, tt);
 
-	PAIR_CLASS_INIT(&tag, "head-rtitle");
-	print_otag(h, TAG_TD, 1, &tag);
+	print_otag(h, TAG_TD, "c", "head-rtitle");
 	print_text(h, title);
 	print_tagq(h, t);
 	free(title);
@@ -324,24 +308,16 @@ man_root_pre(MAN_ARGS)
 static void
 man_root_post(MAN_ARGS)
 {
-	struct htmlpair	 tag;
 	struct tag	*t, *tt;
 
-	PAIR_CLASS_INIT(&tag, "foot");
-	t = print_otag(h, TAG_TABLE, 1, &tag);
+	t = print_otag(h, TAG_TABLE, "c", "foot");
+	tt = print_otag(h, TAG_TR, "");
 
-	tt = print_otag(h, TAG_TR, 0, NULL);
-
-	PAIR_CLASS_INIT(&tag, "foot-date");
-	print_otag(h, TAG_TD, 1, &tag);
-
-	assert(man->date);
+	print_otag(h, TAG_TD, "c", "foot-date");
 	print_text(h, man->date);
 	print_stagq(h, tt);
 
-	PAIR_CLASS_INIT(&tag, "foot-os");
-	print_otag(h, TAG_TD, 1, &tag);
-
+	print_otag(h, TAG_TD, "c", "foot-os");
 	if (man->os)
 		print_text(h, man->os);
 	print_tagq(h, t);
@@ -352,7 +328,6 @@ static int
 man_br_pre(MAN_ARGS)
 {
 	struct roffsu	 su;
-	struct htmlpair	 tag;
 
 	SCALE_VS_INIT(&su, 1);
 
@@ -363,10 +338,7 @@ man_br_pre(MAN_ARGS)
 	} else
 		su.scale = 0.0;
 
-	bufinit(h);
-	bufcat_su(h, "height", &su);
-	PAIR_STYLE_INIT(&tag, h);
-	print_otag(h, TAG_DIV, 1, &tag);
+	print_otag(h, TAG_DIV, "suh", &su);
 
 	/* So the div isn't empty: */
 	print_text(h, "\\~");
@@ -377,17 +349,13 @@ man_br_pre(MAN_ARGS)
 static int
 man_SH_pre(MAN_ARGS)
 {
-	struct htmlpair	 tag;
-
 	if (n->type == ROFFT_BLOCK) {
 		mh->fl &= ~MANH_LITERAL;
-		PAIR_CLASS_INIT(&tag, "section");
-		print_otag(h, TAG_DIV, 1, &tag);
 		return 1;
 	} else if (n->type == ROFFT_BODY)
 		return 1;
 
-	print_otag(h, TAG_H1, 0, NULL);
+	print_otag(h, TAG_H1, "c", "Sh");
 	return 1;
 }
 
@@ -400,7 +368,7 @@ man_alt_pre(MAN_ARGS)
 	struct tag	*t;
 
 	if ((savelit = mh->fl & MANH_LITERAL))
-		print_otag(h, TAG_BR, 0, NULL);
+		print_otag(h, TAG_BR, "");
 
 	mh->fl &= ~MANH_LITERAL;
 
@@ -433,7 +401,7 @@ man_alt_pre(MAN_ARGS)
 			h->flags |= HTML_NOSPACE;
 
 		if (TAG_MAX != fp)
-			t = print_otag(h, fp, 0, NULL);
+			t = print_otag(h, fp, "");
 
 		print_man_node(man, nn, mh, h);
 
@@ -450,27 +418,22 @@ man_alt_pre(MAN_ARGS)
 static int
 man_SM_pre(MAN_ARGS)
 {
-
-	print_otag(h, TAG_SMALL, 0, NULL);
+	print_otag(h, TAG_SMALL, "");
 	if (MAN_SB == n->tok)
-		print_otag(h, TAG_B, 0, NULL);
+		print_otag(h, TAG_B, "");
 	return 1;
 }
 
 static int
 man_SS_pre(MAN_ARGS)
 {
-	struct htmlpair	 tag;
-
 	if (n->type == ROFFT_BLOCK) {
 		mh->fl &= ~MANH_LITERAL;
-		PAIR_CLASS_INIT(&tag, "subsection");
-		print_otag(h, TAG_DIV, 1, &tag);
 		return 1;
 	} else if (n->type == ROFFT_BODY)
 		return 1;
 
-	print_otag(h, TAG_H2, 0, NULL);
+	print_otag(h, TAG_H2, "c", "Ss");
 	return 1;
 }
 
@@ -492,16 +455,16 @@ man_IP_pre(MAN_ARGS)
 	const struct roff_node	*nn;
 
 	if (n->type == ROFFT_BODY) {
-		print_otag(h, TAG_DD, 0, NULL);
+		print_otag(h, TAG_DD, "c", "It-tag");
 		return 1;
 	} else if (n->type != ROFFT_HEAD) {
-		print_otag(h, TAG_DL, 0, NULL);
+		print_otag(h, TAG_DL, "c", "Bl-tag");
 		return 1;
 	}
 
 	/* FIXME: width specification. */
 
-	print_otag(h, TAG_DT, 0, NULL);
+	print_otag(h, TAG_DT, "c", "It-tag");
 
 	/* For IP, only print the first header element. */
 
@@ -512,7 +475,7 @@ man_IP_pre(MAN_ARGS)
 
 	if (MAN_TP == n->tok) {
 		nn = n->child;
-		while (NULL != nn && 0 == (MAN_LINE & nn->flags))
+		while (NULL != nn && 0 == (NODE_LINE & nn->flags))
 			nn = nn->next;
 		while (NULL != nn) {
 			print_man_node(man, nn, mh, h);
@@ -526,8 +489,7 @@ man_IP_pre(MAN_ARGS)
 static int
 man_HP_pre(MAN_ARGS)
 {
-	struct htmlpair	 tag[2];
-	struct roffsu	 su;
+	struct roffsu	 sum, sui;
 	const struct roff_node *np;
 
 	if (n->type == ROFFT_HEAD)
@@ -537,18 +499,14 @@ man_HP_pre(MAN_ARGS)
 
 	np = n->head->child;
 
-	if (NULL == np || ! a2width(np, &su))
-		SCALE_HS_INIT(&su, INDENT);
+	if (np == NULL || !a2width(np, &sum))
+		SCALE_HS_INIT(&sum, INDENT);
 
-	bufinit(h);
+	sui.unit = sum.unit;
+	sui.scale = -sum.scale;
 
 	print_bvspace(h, n);
-	bufcat_su(h, "margin-left", &su);
-	su.scale = -su.scale;
-	bufcat_su(h, "text-indent", &su);
-	PAIR_STYLE_INIT(&tag[0], h);
-	PAIR_CLASS_INIT(&tag[1], "spacer");
-	print_otag(h, TAG_DIV, 2, tag);
+	print_otag(h, TAG_DIV, "csului", "Pp", &sum, &sui);
 	return 1;
 }
 
@@ -556,22 +514,20 @@ static int
 man_OP_pre(MAN_ARGS)
 {
 	struct tag	*tt;
-	struct htmlpair	 tag;
 
 	print_text(h, "[");
 	h->flags |= HTML_NOSPACE;
-	PAIR_CLASS_INIT(&tag, "opt");
-	tt = print_otag(h, TAG_SPAN, 1, &tag);
+	tt = print_otag(h, TAG_SPAN, "c", "Op");
 
 	if (NULL != (n = n->child)) {
-		print_otag(h, TAG_B, 0, NULL);
+		print_otag(h, TAG_B, "");
 		print_text(h, n->string);
 	}
 
 	print_stagq(h, tt);
 
 	if (NULL != n && NULL != n->next) {
-		print_otag(h, TAG_I, 0, NULL);
+		print_otag(h, TAG_I, "");
 		print_text(h, n->next->string);
 	}
 
@@ -584,16 +540,14 @@ man_OP_pre(MAN_ARGS)
 static int
 man_B_pre(MAN_ARGS)
 {
-
-	print_otag(h, TAG_B, 0, NULL);
+	print_otag(h, TAG_B, "");
 	return 1;
 }
 
 static int
 man_I_pre(MAN_ARGS)
 {
-
-	print_otag(h, TAG_I, 0, NULL);
+	print_otag(h, TAG_I, "");
 	return 1;
 }
 
@@ -602,7 +556,7 @@ man_literal_pre(MAN_ARGS)
 {
 
 	if (MAN_fi == n->tok || MAN_EE == n->tok) {
-		print_otag(h, TAG_BR, 0, NULL);
+		print_otag(h, TAG_BR, "");
 		mh->fl &= ~MANH_LITERAL;
 	} else
 		mh->fl |= MANH_LITERAL;
@@ -613,8 +567,7 @@ man_literal_pre(MAN_ARGS)
 static int
 man_in_pre(MAN_ARGS)
 {
-
-	print_otag(h, TAG_BR, 0, NULL);
+	print_otag(h, TAG_BR, "");
 	return 0;
 }
 
@@ -628,7 +581,6 @@ man_ign_pre(MAN_ARGS)
 static int
 man_RS_pre(MAN_ARGS)
 {
-	struct htmlpair	 tag;
 	struct roffsu	 su;
 
 	if (n->type == ROFFT_HEAD)
@@ -640,25 +592,18 @@ man_RS_pre(MAN_ARGS)
 	if (n->head->child)
 		a2width(n->head->child, &su);
 
-	bufinit(h);
-	bufcat_su(h, "margin-left", &su);
-	PAIR_STYLE_INIT(&tag, h);
-	print_otag(h, TAG_DIV, 1, &tag);
+	print_otag(h, TAG_DIV, "sul", &su);
 	return 1;
 }
 
 static int
 man_UR_pre(MAN_ARGS)
 {
-	struct htmlpair		 tag[2];
-
 	n = n->child;
 	assert(n->type == ROFFT_HEAD);
 	if (n->child != NULL) {
 		assert(n->child->type == ROFFT_TEXT);
-		PAIR_CLASS_INIT(&tag[0], "link-ext");
-		PAIR_HREF_INIT(&tag[1], n->child->string);
-		print_otag(h, TAG_A, 2, tag);
+		print_otag(h, TAG_A, "ch", "Lk", n->child->string);
 	}
 
 	assert(n->next->type == ROFFT_BODY);
diff --git a/man_macro.c b/man_macro.c
index d15335709eca..7fd17c53481b 100644
--- a/man_macro.c
+++ b/man_macro.c
@@ -1,4 +1,4 @@
-/*	$Id: man_macro.c,v 1.114 2016/01/08 17:48:09 schwarze Exp $ */
+/*	$Id: man_macro.c,v 1.115 2017/01/10 13:47:00 schwarze Exp $ */
 /*
  * Copyright (c) 2008, 2009, 2010, 2011 Kristaps Dzonsons 
  * Copyright (c) 2012, 2013, 2014, 2015 Ingo Schwarze 
@@ -95,7 +95,7 @@ man_unscope(struct roff_man *man, const struct roff_node *to)
 
 		/* Reached the end of the document? */
 
-		if (to == NULL && ! (n->flags & MAN_VALID)) {
+		if (to == NULL && ! (n->flags & NODE_VALID)) {
 			if (man->flags & (MAN_BLINE | MAN_ELINE) &&
 			    man_macros[n->tok].flags & MAN_SCOPED) {
 				mandoc_vmsg(MANDOCERR_BLK_LINE,
@@ -130,7 +130,7 @@ man_unscope(struct roff_man *man, const struct roff_node *to)
 
 		man->last = n;
 		n = n->parent;
-		man->last->flags |= MAN_VALID;
+		man->last->flags |= NODE_VALID;
 	}
 
 	/*
@@ -164,7 +164,7 @@ rew_scope(struct roff_man *man, int tok)
 	for (;;) {
 		if (n->type == ROFFT_ROOT)
 			return;
-		if (n->flags & MAN_VALID) {
+		if (n->flags & NODE_VALID) {
 			n = n->parent;
 			continue;
 		}
@@ -356,13 +356,13 @@ in_line_eoln(MACRO_PROT_ARGS)
 	}
 
 	/*
-	 * Append MAN_EOS in case the last snipped argument
+	 * Append NODE_EOS in case the last snipped argument
 	 * ends with a dot, e.g. `.IR syslog (3).'
 	 */
 
 	if (n != man->last &&
 	    mandoc_eos(man->last->string, strlen(man->last->string)))
-		man->last->flags |= MAN_EOS;
+		man->last->flags |= NODE_EOS;
 
 	/*
 	 * If no arguments are specified and this is MAN_SCOPED (i.e.,
diff --git a/man_term.c b/man_term.c
index f45e24afea4a..672ab4163228 100644
--- a/man_term.c
+++ b/man_term.c
@@ -1,4 +1,4 @@
-/*	$Id: man_term.c,v 1.187 2016/01/08 17:48:09 schwarze Exp $ */
+/*	$Id: man_term.c,v 1.188 2017/01/10 13:47:00 schwarze Exp $ */
 /*
  * Copyright (c) 2008-2012 Kristaps Dzonsons 
  * Copyright (c) 2010-2015 Ingo Schwarze 
@@ -319,7 +319,7 @@ pre_alternate(DECL_ARGS)
 			mt->fl |= MANT_LITERAL;
 		assert(nn->type == ROFFT_TEXT);
 		term_word(p, nn->string);
-		if (nn->flags & MAN_EOS)
+		if (nn->flags & NODE_EOS)
                 	p->flags |= TERMP_SENTENCE;
 		if (nn->next)
 			p->flags |= TERMP_NOSPACE;
@@ -677,7 +677,7 @@ pre_TP(DECL_ARGS)
 	/* Calculate offset. */
 
 	if ((nn = n->parent->head->child) != NULL &&
-	    nn->string != NULL && ! (MAN_LINE & nn->flags) &&
+	    nn->string != NULL && ! (NODE_LINE & nn->flags) &&
 	    a2roffsu(nn->string, &su, SCALE_EN)) {
 		len = term_hspan(p, &su) / 24;
 		if (len < 0 && (size_t)(-len) > mt->offset)
@@ -698,7 +698,7 @@ pre_TP(DECL_ARGS)
 
 		/* Don't print same-line elements. */
 		nn = n->child;
-		while (NULL != nn && 0 == (MAN_LINE & nn->flags))
+		while (NULL != nn && 0 == (NODE_LINE & nn->flags))
 			nn = nn->next;
 
 		while (NULL != nn) {
@@ -960,17 +960,17 @@ print_man_node(DECL_ARGS)
 		if ('\0' == *n->string) {
 			term_vspace(p);
 			return;
-		} else if (' ' == *n->string && MAN_LINE & n->flags)
+		} else if (' ' == *n->string && NODE_LINE & n->flags)
 			term_newln(p);
 
 		term_word(p, n->string);
 		goto out;
 
 	case ROFFT_EQN:
-		if ( ! (n->flags & MAN_LINE))
+		if ( ! (n->flags & NODE_LINE))
 			p->flags |= TERMP_NOSPACE;
 		term_eqn(p, n->eqn);
-		if (n->next != NULL && ! (n->next->flags & MAN_LINE))
+		if (n->next != NULL && ! (n->next->flags & NODE_LINE))
 			p->flags |= TERMP_NOSPACE;
 		return;
 	case ROFFT_TBL:
@@ -1007,7 +1007,7 @@ print_man_node(DECL_ARGS)
 	 */
 	if (mt->fl & MANT_LITERAL &&
 	    ! (p->flags & (TERMP_NOBREAK | TERMP_NONEWLINE)) &&
-	    (n->next == NULL || n->next->flags & MAN_LINE)) {
+	    (n->next == NULL || n->next->flags & NODE_LINE)) {
 		rm = p->rmargin;
 		rmax = p->maxrmargin;
 		p->rmargin = p->maxrmargin = TERM_MAXMARGIN;
@@ -1023,7 +1023,7 @@ print_man_node(DECL_ARGS)
 			p->rmargin = rm;
 		p->maxrmargin = rmax;
 	}
-	if (MAN_EOS & n->flags)
+	if (NODE_EOS & n->flags)
 		p->flags |= TERMP_SENTENCE;
 }
 
diff --git a/mandoc.1 b/mandoc.1
index f4707aa28b04..946955a64c54 100644
--- a/mandoc.1
+++ b/mandoc.1
@@ -1,7 +1,7 @@
-.\"	$Id: mandoc.1,v 1.164 2015/11/05 17:47:51 schwarze Exp $
+.\"	$Id: mandoc.1,v 1.171 2017/01/21 02:32:39 schwarze Exp $
 .\"
 .\" Copyright (c) 2009, 2010, 2011 Kristaps Dzonsons 
-.\" Copyright (c) 2012, 2014, 2015 Ingo Schwarze 
+.\" Copyright (c) 2012, 2014-2017 Ingo Schwarze 
 .\"
 .\" Permission to use, copy, modify, and distribute this software for any
 .\" purpose with or without fee is hereby granted, provided that the above
@@ -15,7 +15,7 @@
 .\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 .\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 .\"
-.Dd $Mdocdate: November 5 2015 $
+.Dd $Mdocdate: January 21 2017 $
 .Dt MANDOC 1
 .Os
 .Sh NAME
@@ -75,6 +75,10 @@ This overrides any earlier
 and
 .Fl l
 options.
+.It Fl h
+Display only the SYNOPSIS lines.
+Implies
+.Fl c .
 .It Fl I Cm os Ns = Ns Ar name
 Override the default operating system
 .Ar name
@@ -85,10 +89,6 @@ and for the
 .Xr man 7
 .Sq \&TH
 macro.
-.It Fl h
-Display only the SYNOPSIS lines.
-Implies
-.Fl c .
 .It Fl K Ar encoding
 Specify the input encoding.
 The supported
@@ -498,7 +498,15 @@ Use
 to show a human readable representation of the syntax tree.
 It is useful for debugging the source code of manual pages.
 The exact format is subject to change, so don't write parsers for it.
-Each output line shows one syntax tree node.
+.Pp
+The first paragraph shows meta data found in the
+.Xr mdoc 7
+prologue, on the
+.Xr man 7
+.Ic \&TH
+line, or the fallbacks used.
+.Pp
+In the tree dump, each output line shows one syntax tree node.
 Child nodes are indented with respect to their parent node.
 The columns are:
 .Pp
@@ -529,6 +537,12 @@ The input column number (starting at one).
 A closing parenthesis if the node is a closing delimiter.
 .It
 A full stop if the node ends a sentence.
+.It
+NOSRC if the node is not in the input file,
+but automatically generated from macros.
+.It
+NOPRT if the node is not supposed to generate output
+for any output format.
 .El
 .El
 .Sh ENVIRONMENT
@@ -809,11 +823,13 @@ This may confuse
 .Xr makewhatis 8
 and
 .Xr apropos 1 .
-.It Sy "NAME section without name"
+.It Sy "NAME section without Nm before Nd"
 .Pq mdoc
 The NAME section does not contain any
 .Ic \&Nm
-child macro.
+child macro before the first
+.Ic \&Nd
+macro.
 .It Sy "NAME section without description"
 .Pq mdoc
 The NAME section lacks the mandatory
@@ -830,6 +846,11 @@ The NAME section contains plain text or macros other than
 .Ic \&Nm
 and
 .Ic \&Nd .
+.It Sy "missing comma before name"
+.Pq mdoc
+The NAME section contains an
+.Ic \&Nm
+macro that is neither the first one nor preceded by a comma.
 .It Sy "missing description line, using \(dq\(dq"
 .Pq mdoc
 The
@@ -1147,6 +1168,13 @@ macro is immediately followed by an
 .Ic \&Re
 macro on the next input line.
 Such an empty block does not produce any output.
+.It Sy "missing section argument"
+.Pq mdoc
+An
+.Ic \&Xr
+macro lacks its second, section number argument.
+The first argument, i.e. the name, is printed, but without subsequent
+parentheses.
 .It Sy "missing -std argument, adding it"
 .Pq mdoc
 An
@@ -1615,8 +1643,8 @@ macro fails to specify the list type.
 .It Sy "missing manual name, using \(dq\(dq"
 .Pq mdoc
 The first call to
-.Ic \&Nm
-lacks the required argument.
+.Ic \&Nm ,
+or any call in the NAME section, lacks the required argument.
 .It Sy "uname(3) system call failed, using UNKNOWN"
 .Pq mdoc
 The
@@ -1812,12 +1840,3 @@ utility was written by
 .An Kristaps Dzonsons Aq Mt kristaps@bsd.lv
 and is maintained by
 .An Ingo Schwarze Aq Mt schwarze@openbsd.org .
-.Sh BUGS
-In
-.Fl T Cm html ,
-the maximum size of an element attribute is determined by
-.Dv BUFSIZ ,
-which is usually 1024 bytes.
-Be aware of this when setting long link
-formats such as
-.Fl O Cm style Ns = Ns Ar really/long/link .
diff --git a/mandoc.3 b/mandoc.3
index 18b5707c492e..6f7c3eb09a18 100644
--- a/mandoc.3
+++ b/mandoc.3
@@ -1,7 +1,7 @@
-.\"	$Id: mandoc.3,v 1.37 2016/07/07 19:19:01 schwarze Exp $
+.\"	$Id: mandoc.3,v 1.38 2017/01/09 01:37:03 schwarze Exp $
 .\"
 .\" Copyright (c) 2009, 2010, 2011 Kristaps Dzonsons 
-.\" Copyright (c) 2010-2016 Ingo Schwarze 
+.\" Copyright (c) 2010-2017 Ingo Schwarze 
 .\"
 .\" Permission to use, copy, modify, and distribute this software for any
 .\" purpose with or without fee is hereby granted, provided that the above
@@ -15,7 +15,7 @@
 .\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 .\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 .\"
-.Dd $Mdocdate: July 7 2016 $
+.Dd $Mdocdate: January 9 2017 $
 .Dt MANDOC 3
 .Os
 .Sh NAME
@@ -34,7 +34,8 @@
 .Nm mparse_reset ,
 .Nm mparse_result ,
 .Nm mparse_strerror ,
-.Nm mparse_strlevel
+.Nm mparse_strlevel ,
+.Nm mparse_updaterc 
 .Nd mandoc macro compiler library
 .Sh SYNOPSIS
 .In sys/types.h
@@ -100,6 +101,11 @@
 .Fo mparse_strlevel
 .Fa "enum mandoclevel"
 .Fc
+.Ft void
+.Fo mparse_updaterc
+.Fa "struct mparse *parse"
+.Fa "enum mandoclevel *rc"
+.Fc
 .In roff.h
 .Ft void
 .Fo deroff
@@ -181,6 +187,9 @@ or
 .Fn man_validate ,
 respectively;
 .It
+if information about the validity of the input is needed, fetch it with
+.Fn mparse_updaterc ;
+.It
 iterate over parse nodes with starting from the
 .Fa first
 member of the returned
@@ -416,6 +425,22 @@ Declared in
 .In mandoc.h ,
 implemented in
 .Pa read.c .
+.It Fn mparse_updaterc
+If the highest warning or error level that occurred during the current
+.Fa parse
+is higher than
+.Pf * Fa rc ,
+update
+.Pf * Fa rc
+accordingly.
+This is useful after calling
+.Fn mdoc_validate
+or
+.Fn man_validate .
+Declared in
+.In mandoc.h ,
+implemented in
+.Pa read.c .
 .El
 .Ss Variables
 .Bl -ohang
diff --git a/mandoc.css b/mandoc.css
index 90085061fc08..162b730749ed 100644
--- a/mandoc.css
+++ b/mandoc.css
@@ -1,159 +1,173 @@
-/* $Id: mandoc.css,v 1.2 2016/04/13 10:19:23 schwarze Exp $ */
-
+/* $Id: mandoc.css,v 1.13 2017/01/21 02:29:57 schwarze Exp $ */
 /*
- * This is an example style-sheet provided for mandoc(1) and the -Thtml
- * or -Txhtml output mode.
- *
- * It mimics the appearance of the traditional cvsweb output.
- *
- * See mdoc(7) and man(7) for macro explanations.
+ * Standard style sheet for mandoc(1) -Thtml and man.cgi(8).
  */
 
-html		{ max-width: 880px; margin-left: 1em; }
-body		{ font-size: smaller; font-family: Helvetica,Arial,sans-serif; }
-body > div			{ padding-left: 2em;
-				  padding-top: 1em; }
-body > div.mandoc,
-body > div#mancgi		{ padding-left: 0em;
-				  padding-top: 0em; }
-body > div.results		{ font-size: smaller; }
-#mancgi fieldset		{ text-align: center;
-				  border: thin solid silver;
-				  border-radius: 1em;
-			  	  font-size: small; }
-#mancgi input[name=expr] 	{ width: 25%; }
-.results td.title		{ vertical-align: top;
-				  padding-right: 1em; }
-h1		{ margin-bottom: 1ex; font-size: 110% } 
-div.section > h1 { margin-left: -4ex; } /* Section header (Sh, SH). */
-h2		{ margin-bottom: 1ex; font-size: 105%; margin-left: -2ex; } /* Sub-section header (Ss, SS). */
-table		{ width: 100%; margin-top: 0ex; margin-bottom: 0ex; } /* All tables. */
-td		{ vertical-align: top; } /* All table cells. */
-p		{ } /* Paragraph: Pp, Lp. */
-blockquote	{ margin-left: 5ex; margin-top: 0ex; margin-bottom: 0ex; } /* D1. */
-div.section	{ margin-bottom: 2ex; margin-left: 5ex; } /* Sections (Sh, SH). */
-div.subsection	{ } /* Sub-sections (Ss, SS). */
-table.synopsis	{ } /* SYNOPSIS section table. */
-div.spacer	{ margin: 1em 0; }
+/* Global defaults. */
 
-/* Preamble structure. */
+html {		max-width: 100ex; }
+body {		font-family: Helvetica,Arial,sans-serif; }
+table {		margin-top: 0em;
+		margin-bottom: 0em; }
+td {		vertical-align: top; }
+ul, ol, dl {	margin-top: 0em;
+		margin-bottom: 0em; }
+li, dt {	margin-top: 1em; }
 
-table.foot	{ font-size: smaller; margin-top: 1em; border-top: 1px dotted #dddddd; } /* Document footer. */
-td.foot-date	{ width: 50%; } /* Document footer: date. */
-td.foot-os	{ width: 50%; } /* Document footer: OS/source. */
-table.head	{ font-size: smaller; margin-bottom: 1em; border-bottom: 1px dotted #dddddd; } /* Document header. */
-td.head-ltitle	{ width: 10%; } /* Document header: left-title. */
-td.head-vol	{ width: 80%; } /* Document header: volume. */
-td.head-rtitle	{ width: 10%; } /* Document header: right-title. */
+/* Search form and search results. */
 
-/* General font modes. */
+fieldset {	border: thin solid silver;
+		border-radius: 1em;
+		text-align: center; }
+input[name=expr] {
+		width: 25%; }
 
-i		{ } /* Italic: BI, IB, I, (implicit). */
-.emph		{ font-style: italic; font-weight: normal; } /* Emphasis: Em, Bl -emphasis. */
-b		{ } /* Bold: SB, BI, IB, BR, RB, B, (implicit). */
-.symb		{ font-style: normal; font-weight: bold; } /* Symbolic: Sy, Ms, Bf -symbolic. */
-small		{ } /* Small: SB, SM. */
-.lit		{ font-style: normal; font-weight: normal; font-family: monospace; } /* Literal: Dl, Li, Ql, Bf -literal, Bl -literal, Bl -unfilled. */
+table.results {	margin-top: 1em;
+		margin-left: 2em;
+		font-size: smaller; }
 
-/* Block modes. */
+/* Header and footer lines. */
 
-.display	{ } /* Top of all Bd, D1, Dl. */
-.list		{ } /* Top of all Bl. */
+table.head {	width: 100%;
+		border-bottom: 1px dotted #808080;
+		margin-bottom: 1em;
+		font-size: smaller; }
+td.head-vol {	text-align: center; }
+td.head-rtitle {
+		text-align: right; }
+span.Nd { }
 
-/* Context-specific modes. */
+table.foot {	width: 100%;
+		border-top: 1px dotted #808080;
+		margin-top: 1em;
+		font-size: smaller; }
+td.foot-os {	text-align: right; }
 
-i.addr		{ font-weight: normal; } /* Address (Ad). */
-i.arg		{ font-weight: normal; } /* Command argument (Ar). */
-span.author	{ } /* Author name (An). */
-b.cmd		{ font-style: normal; } /* Command (Cm). */
-b.config	{ font-style: normal; } /* Config statement (Cd). */
-span.define	{ } /* Defines (Dv). */
-span.desc	{ } /* Nd.  After em-dash. */
-b.diag		{ font-style: normal; } /* Diagnostic (Bl -diag). */
-span.env	{ } /* Environment variables (Ev). */
-span.errno	{ } /* Error string (Er). */
-i.farg		{ font-weight: normal; } /* Function argument (Fa, Fn). */
-i.file		{ font-weight: normal; } /* File (Pa). */
-b.flag		{ font-style: normal; } /* Flag (Fl, Cm). */
-b.fname		{ font-style: normal; } /* Function name (Fa, Fn, Rv). */
-i.ftype		{ font-weight: normal; } /* Function types (Ft, Fn). */
-b.includes	{ font-style: normal; } /* Header includes (In). */
-span.lib	{ } /* Library (Lb). */
-i.link-sec	{ font-weight: normal; } /* Section links (Sx). */
-b.macro		{ font-style: normal; } /* Macro-ish thing (Fd). */
-b.name		{ font-style: normal; } /* Name of utility (Nm). */
-span.opt	{ } /* Options (Op, Oo/Oc). */
-span.ref	{ } /* Citations (Rs). */
-span.ref-auth	{ } /* Reference author (%A). */
-i.ref-book	{ font-weight: normal; } /* Reference book (%B). */
-span.ref-city	{ } /* Reference city (%C). */
-span.ref-date	{ } /* Reference date (%D). */
-i.ref-issue	{ font-weight: normal; } /* Reference issuer/publisher (%I). */
-i.ref-jrnl	{ font-weight: normal; } /* Reference journal (%J). */
-span.ref-num	{ } /* Reference number (%N). */
-span.ref-opt	{ } /* Reference optionals (%O). */
-span.ref-page	{ } /* Reference page (%P). */
-span.ref-corp	{ } /* Reference corporate/foreign author (%Q). */
-span.ref-rep	{ } /* Reference report (%R). */
-span.ref-title	{ text-decoration: underline; } /* Reference title (%T). */
-span.ref-vol	{ } /* Reference volume (%V). */
-span.type	{ font-style: italic; font-weight: normal; } /* Variable types (Vt). */
-span.unix	{ } /* Unices (Ux, Ox, Nx, Fx, Bx, Bsx, Dx). */
-b.utility	{ font-style: normal; } /* Name of utility (Ex). */
-b.var		{ font-style: normal; } /* Variables (Rv). */
+/* Sections and paragraphs. */
 
-a.link-ext	{ } /* Off-site link (Lk). */
-a.link-includes	{ } /* Include-file link (In). */
-a.link-mail	{ } /* Mailto links (Mt). */
-a.link-man	{ } /* Manual links (Xr). */
-a.link-ref	{ } /* Reference section links (%Q). */
-a.link-sec	{ } /* Section links (Sx). */
+div.manual-text {
+		margin-left: 5ex; }
+h1.Sh {		margin-top: 2ex;
+		margin-bottom: 1ex;
+		margin-left: -4ex;
+		font-size: 110%; }
+h2.Ss {		margin-top: 2ex;
+		margin-bottom: 1ex;
+		margin-left: -2ex;
+		font-size: 105%; }
+div.Pp {	margin: 1ex 0ex; }
+a.Sx { }
+a.Xr { }
 
-/* Formatting for lists.  See mdoc(7). */
+/* Displays and lists. */
 
-dl.list-diag	{ }
-dt.list-diag	{ }
-dd.list-diag	{ }
+div.Bd { }
+div.D1 {	margin-left: 5ex; }
 
-dl.list-hang	{ }
-dt.list-hang	{ }
-dd.list-hang	{ }
+ul.Bl-bullet {	list-style-type: disc;
+		padding-left: 1em; }
+li.It-bullet { }
+ul.Bl-dash {	list-style-type: none;
+		padding-left: 0em; }
+li.It-dash:before {
+		content: "\2014  "; }
+ul.Bl-item {	list-style-type: none;
+		padding-left: 0em; }
+li.It-item { }
 
-dl.list-inset	{ }
-dt.list-inset	{ }
-dd.list-inset	{ }
+ol.Bl-enum {	padding-left: 2em; }
+li.It-enum { }
 
-dl.list-ohang	{ }
-dt.list-ohang	{ }
-dd.list-ohang	{ margin-left: 0ex; }
+dl.Bl-diag { }
+dt.It-diag { }
+dd.It-diag { }
+b.It-diag {	font-style: normal; }
+dl.Bl-hang { }
+dt.It-hang { }
+dd.It-hang { }
+dl.Bl-inset { }
+dt.It-inset { }
+dd.It-inset { }
+dl.Bl-ohang { }
+dt.It-ohang { }
+dd.It-ohang {	margin-left: 0ex; }
+dl.Bl-tag { }
+dt.It-tag { }
+dd.It-tag { }
 
-dl.list-tag	{ }
-dt.list-tag	{ }
-dd.list-tag	{ }
+table.Bl-column { }
+tr.It-column { }
+td.It-column {	margin-top: 1em; }
 
-table.list-col	{ }
-tr.list-col	{ }
-td.list-col	{ }
+span.Rs	{ }
+span.RsA { }
+i.RsB {		font-weight: normal; }
+span.RsC { }
+span.RsD { }
+i.RsI {		font-weight: normal; }
+i.RsJ {		font-weight: normal; }
+span.RsN { }
+span.RsO { }
+span.RsP { }
+span.RsQ { }
+span.RsR { }
+span.RsT {	text-decoration: underline; }
+a.RsU { }
+span.RsV { }
 
-ul.list-bul	{ list-style-type: disc; padding-left: 1em; }
-li.list-bul	{ }
+span.eqn { }
+table.tbl { }
 
-ul.list-dash	{ list-style-type: none; padding-left: 0em; }
-li.list-dash:before { content: "\2014  "; }
+/* Semantic markup for command line utilities. */
 
-ul.list-hyph	{ list-style-type: none; padding-left: 0em; }
-li.list-hyph:before { content: "\2013  "; }
+table.Nm { }
+b.Nm {		font-style: normal; }
+b.Fl {		font-style: normal; }
+b.Cm {		font-style: normal; }
+i.Ar {		font-weight: normal; }
+span.Op { }
+b.Ic {		font-style: normal; }
+code.Ev {	font-style: normal;
+		font-weight: normal;
+		font-family: monospace; }
+i.Pa {		font-weight: normal; }
 
-ul.list-item	{ list-style-type: none; padding-left: 0em; }
-li.list-item	{ }
+/* Semantic markup for function libraries. */
 
-ol.list-enum	{ padding-left: 2em; }
-li.list-enum	{ }
+span.Lb { }
+b.In {		font-style: normal; }
+a.In { }
+b.Fd {		font-style: normal; }
+i.Ft {		font-weight: normal; }
+b.Fn {		font-style: normal; }
+i.Fa {		font-weight: normal; }
+i.Vt {		font-weight: normal; }
+i.Va {		font-weight: normal; }
+code.Dv {	font-style: normal;
+		font-weight: normal;
+		font-family: monospace; }
+code.Er {	font-style: normal;
+		font-weight: normal;
+		font-family: monospace; }
 
-/* Equation modes.  See eqn(7). */
+/* Various semantic markup. */
 
-span.eqn	{ }
+span.An { }
+a.Lk { }
+a.Mt { }
+b.Cd {		font-style: normal; }
+i.Ad {		font-weight: normal; }
+b.Ms {		font-style: normal; }
+a.Ux { }
 
-/* Table modes.  See tbl(7). */
+/* Physical markup. */
 
-table.tbl	{ }
+.No {		font-style: normal;
+		font-weight: normal; }
+.Em {		font-style: italic;
+		font-weight: normal; }
+.Sy {		font-style: normal;
+		font-weight: bold; }
+.Li {		font-style: normal;
+		font-weight: normal;
+		font-family: monospace; }
diff --git a/mandoc.db.5 b/mandoc.db.5
index de6ea11bd0df..26907d139f41 100644
--- a/mandoc.db.5
+++ b/mandoc.db.5
@@ -1,6 +1,6 @@
-.\"	$Id: mandoc.db.5,v 1.4 2016/07/07 14:35:48 schwarze Exp $
+.\"	$Id: mandoc.db.5,v 1.5 2016/08/01 12:27:15 schwarze Exp $
 .\"
-.\" Copyright (c) 2014 Ingo Schwarze 
+.\" Copyright (c) 2014, 2016 Ingo Schwarze 
 .\"
 .\" Permission to use, copy, modify, and distribute this software for any
 .\" purpose with or without fee is hereby granted, provided that the above
@@ -14,7 +14,7 @@
 .\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 .\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 .\"
-.Dd $Mdocdate: July 7 2016 $
+.Dd $Mdocdate: August 1 2016 $
 .Dt MANDOC.DB 5
 .Os
 .Sh NAME
@@ -23,7 +23,7 @@
 .Sh DESCRIPTION
 The
 .Nm
-SQLite3 file format is used to store information about installed manual
+file format is used to store information about installed manual
 pages to facilitate semantic searching for manuals.
 Each manual page tree contains its own
 .Nm
@@ -34,87 +34,156 @@ for examples.
 Such database files are generated by
 .Xr makewhatis 8
 and used by
+.Xr man 1 ,
 .Xr apropos 1
 and
 .Xr whatis 1 .
 .Pp
-One line in the following tables describes:
-.Bl -tag -width Ds
-.It Sy mpages
-One physical manual page file, no matter how many times and under which
-names it may appear in the file system.
-.It Sy mlinks
-One entry in the file system, no matter which content it points to.
-.It Sy names
-One manual page name, no matter whether it appears in a page header,
-in a NAME or SYNOPSIS section, or as a file name.
-.It Sy keys
-One chunk of text from some macro invocation.
+The file format uses three datatypes:
+.Pp
+.Bl -dash -compact -offset 2n -width 1n
+.It
+32-bit signed integer numbers in big endian (network) byte ordering
+.It
+NUL-terminated strings
+.It
+lists of NUL-terminated strings, terminated by a second NUL character
 .El
 .Pp
-Each record in the latter three tables uses its
-.Va pageid
-column to point to a record in the
-.Sy mpages
-table.
+Numbers are aligned to four-byte boundaries; where they follow
+strings or lists of strings, padding with additional NUL characters
+occurs.
+Some, but not all, numbers point to positions in the file.
+These pointers are measured in bytes, and the first byte of the
+file is considered to be byte 0.
 .Pp
-The other columns are as follows; unless stated otherwise, they are
-of type
-.Vt TEXT .
-.Bl -tag -width mpages.desc
-.It Sy mpages.desc
-The description line
-.Pq Sq \&Nd
-of the page.
-.It Sy mpages.form
-An
-.Vt INTEGER
-bit field.
-If bit
-.Dv FORM_GZ
-is set, the page is compressed and requires
-.Xr gunzip 1
-for display.
-If bit
-.Dv FORM_SRC
-is set, the page is unformatted, that is in
+Each file consists of:
+.Pp
+.Bl -dash -compact -offset 2n -width 1n
+.It
+One magic number, 0x3a7d0cdb.
+.It
+One version number, currently 1.
+.It
+One pointer to the macros table.
+.It
+One pointer to the final magic number.
+.It
+The pages table (variable length).
+.It
+The macros table (variable length).
+.It
+The magic number once again, 0x3a7d0cdb.
+.El
+.Pp
+The pages table contains one entry for each physical manual page
+file, no matter how many hard and soft links it may have in the
+file system.
+The pages table consists of:
+.Pp
+.Bl -dash -compact -offset 2n -width 1n
+.It
+The number of pages in the database.
+.It
+For each page:
+.Bl -dash -compact -offset 2n -width 1n
+.It
+One pointer to the list of names.
+.It
+One pointer to the list of sections.
+.It
+One pointer to the list of architectures
+or 0 if the page is machine-independent.
+.It
+One pointer to the one-line description string.
+.It
+One pointer to the list of filenames.
+.El
+.It
+For each page, the list of names.
+Each name is preceded by a single byte indicating the sources of the name.
+The meaning of the bits is:
+.Bl -dash -compact -offset 2n -width 1n
+.It
+0x10: The name appears in a filename.
+.It
+0x08: The name appears in a header line, i.e. in a .Dt or .TH macro.
+.It
+0x04: The name is the first one in the title line, i.e. it appears
+in the first .Nm macro in the NAME section.
+.It
+0x02: The name appears in any .Nm macro in the NAME section.
+.It
+0x01: The name appears in an .Nm block in the SYNOPSIS section.
+.El
+.It
+For each page, the list of sections.
+Each section is given as a string, not as a number.
+.It
+For each architecture-dependent page, the list of architectures.
+.It
+For each page, the one-line description string taken from the .Nd macro.
+.It
+For each page, the list of filenames relative to the root of the
+respective manpath.
+This list includes hard links, soft links, and links simulated
+with .so
+.Xr roff 7
+requests.
+The first filename is preceded by a single byte
+having the following significance:
+.Bl -dash -compact -offset 2n -width 1n
+.It
+.Dv FORM_SRC No = 0x01 :
+The file format is
 .Xr mdoc 7
 or
-.Xr man 7
-format, and requires
-.Xr mandoc 1
-for display.
-If bit
-.Dv FORM_SRC
-is not set, the page is formatted, i.e. a
-.Sq cat
-page.
-.It Sy mlinks.sec
-The manual section as found in the subdirectory name.
-.It Sy mlinks.arch
-The manual architecture as found in the subdirectory name, or
-.Qq any .
-.It Sy mlinks.name
-The manual name as found in the file name.
-.It Sy names.bits
-An
-.Vt INTEGER
-bit mask telling whether the name came from a header line, from the
-NAME or SYNOPSIS section, or from a file name.
-Bits are defined in
-.In mansearch.h .
-.It Sy names.name
-The name itself.
-.It Sy keys.bits
-An
-.Vt INTEGER
-bit mask telling which semantic contexts the key was found in;
-defined in
-.In mansearch.h ,
-documented in
+.Xr man 7 .
+.It
+.Dv FORM_CAT No = 0x02 :
+The manual page is preformatted.
+.El
+.It
+Zero to three NUL bytes for padding.
+.El
+.Pp
+The macros table consists of:
+.Pp
+.Bl -dash -compact -offset 2n -width 1n
+.It
+The number of different macro keys, currently 36.
+The ordering of macros is defined in
+.In mansearch.h
+and the significance of the macro keys is documented in
 .Xr apropos 1 .
-.It Sy keys.key
-The string found in those contexts.
+.It
+For each macro key, one pointer to the respective macro table.
+.It
+For each macro key, the macro table (variable length).
+.El
+.Pp
+Each macro table consists of:
+.Pp
+.Bl -dash -compact -offset 2n -width 1n
+.It
+The number of entries in the table.
+.It
+For each entry:
+.Bl -dash -compact -offset 2n -width 1n
+.It
+One pointer to the value of the macro key.
+Each value is a string of text taken from some macro invocation.
+.It
+One pointer to the list of pages.
+.El
+.It
+For each entry, the value of the macro key.
+.It
+Zero to three NUL bytes for padding.
+.It
+For each entry, one or more pointers to pages in the pages table,
+pointing to the pointer to the list of names,
+followed by the number 0.
 .El
 .Sh FILES
 .Bl -tag -width /usr/share/man/mandoc.db -compact
@@ -128,10 +197,16 @@ Window System.
 The same for
 .Xr packages 7 .
 .El
+.Pp
+A program to dump
+.Nm
+files in a human-readable format suitable for
+.Xr diff 1
+is provided in the directory
+.Pa /usr/src/regress/usr.bin/mandoc/db/dbm_dump/ .
 .Sh SEE ALSO
 .Xr apropos 1 ,
 .Xr man 1 ,
-.Xr sqlite3 1 ,
 .Xr whatis 1 ,
 .Xr makewhatis 8
 .Sh HISTORY
@@ -140,7 +215,7 @@ A manual page database
 first appeared in
 .Bx 2 .
 The present format first appeared in
-.Ox 5.6 .
+.Ox 6.1 .
 .Sh AUTHORS
 .An -nosplit
 The original version of
@@ -148,9 +223,6 @@ The original version of
 was written by
 .An Bill Joy
 in 1979.
-An SQLite3 version was first implemented by
-.An Kristaps Dzonsons Aq Mt kristaps@bsd.lv
-in 2012.
 The present database format was designed by
 .An Ingo Schwarze Aq Mt schwarze@openbsd.org
-in 2014.
+in 2016.
diff --git a/mandoc.h b/mandoc.h
index d63814c6deaa..2ea64ea0aea0 100644
--- a/mandoc.h
+++ b/mandoc.h
@@ -1,7 +1,7 @@
-/*	$Id: mandoc.h,v 1.209 2016/01/08 02:53:13 schwarze Exp $ */
+/*	$Id: mandoc.h,v 1.213 2017/01/09 01:37:03 schwarze Exp $ */
 /*
  * Copyright (c) 2010, 2011, 2014 Kristaps Dzonsons 
- * Copyright (c) 2010-2016 Ingo Schwarze 
+ * Copyright (c) 2010-2017 Ingo Schwarze 
  *
  * Permission to use, copy, modify, and distribute this software for any
  * purpose with or without fee is hereby granted, provided that the above
@@ -65,10 +65,11 @@ enum	mandocerr {
 	MANDOCERR_DOC_EMPTY, /* no document body */
 	MANDOCERR_SEC_BEFORE, /* content before first section header: macro */
 	MANDOCERR_NAMESEC_FIRST, /* first section is not NAME: Sh title */
-	MANDOCERR_NAMESEC_NONM, /* NAME section without name */
+	MANDOCERR_NAMESEC_NONM, /* NAME section without Nm before Nd */
 	MANDOCERR_NAMESEC_NOND, /* NAME section without description */
 	MANDOCERR_NAMESEC_ND, /* description not at the end of NAME */
 	MANDOCERR_NAMESEC_BAD, /* bad NAME section content: macro */
+	MANDOCERR_NAMESEC_PUNCT, /* missing comma before name: Nm name */
 	MANDOCERR_ND_EMPTY, /* missing description line, using "" */
 	MANDOCERR_SEC_ORDER, /* sections out of conventional order: Sh title */
 	MANDOCERR_SEC_REP, /* duplicate section title: Sh title */
@@ -98,7 +99,7 @@ enum	mandocerr {
 	MANDOCERR_ARG_EMPTY, /* empty argument, using 0n: macro arg */
 	MANDOCERR_BD_NOTYPE, /* missing display type, using -ragged: Bd */
 	MANDOCERR_BL_LATETYPE, /* list type is not the first argument: Bl arg */
-	MANDOCERR_BL_NOWIDTH, /* missing -width in -tag list, using 8n */
+	MANDOCERR_BL_NOWIDTH, /* missing -width in -tag list, using 6n */
 	MANDOCERR_EX_NONAME, /* missing utility name, using "": Ex */
 	MANDOCERR_FO_NOHEAD, /* missing function name, using "": Fo */
 	MANDOCERR_IT_NOHEAD, /* empty head in list item: Bl -type It */
@@ -107,6 +108,7 @@ enum	mandocerr {
 	MANDOCERR_BF_BADFONT, /* unknown font type, using \fR: Bf font */
 	MANDOCERR_PF_SKIP, /* nothing follows prefix: Pf arg */
 	MANDOCERR_RS_EMPTY, /* empty reference block: Rs */
+	MANDOCERR_XR_NOSEC, /* missing section argument: Xr arg */
 	MANDOCERR_ARG_STD, /* missing -std argument, adding it: macro */
 	MANDOCERR_OP_EMPTY, /* missing option string, using "": OP */
 	MANDOCERR_UR_NOHEAD, /* missing resource identifier, using "": UR */
@@ -433,3 +435,4 @@ void		  mparse_result(struct mparse *,
 const char	 *mparse_getkeep(const struct mparse *);
 const char	 *mparse_strerror(enum mandocerr);
 const char	 *mparse_strlevel(enum mandoclevel);
+void		  mparse_updaterc(struct mparse *, enum mandoclevel *);
diff --git a/mandoc_aux.h b/mandoc_aux.h
index 2ae3a0cd2def..603cc5a7257c 100644
--- a/mandoc_aux.h
+++ b/mandoc_aux.h
@@ -1,4 +1,4 @@
-/*	$Id: mandoc_aux.h,v 1.4 2015/11/07 14:01:16 schwarze Exp $ */
+/*	$Id: mandoc_aux.h,v 1.5 2016/07/19 13:36:13 schwarze Exp $ */
 /*
  * Copyright (c) 2009, 2011 Kristaps Dzonsons 
  * Copyright (c) 2014 Ingo Schwarze 
@@ -16,7 +16,8 @@
  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  */
 
-int		  mandoc_asprintf(char **, const char *, ...);
+int		  mandoc_asprintf(char **, const char *, ...)
+			__attribute__((__format__ (printf, 2, 3)));
 void		 *mandoc_calloc(size_t, size_t);
 void		 *mandoc_malloc(size_t);
 void		 *mandoc_realloc(void *, size_t);
diff --git a/mandoc_html.3 b/mandoc_html.3
index 994eb3a288e7..80b1fe69f7cf 100644
--- a/mandoc_html.3
+++ b/mandoc_html.3
@@ -1,6 +1,6 @@
-.\"	$Id: mandoc_html.3,v 1.1 2014/07/23 18:13:09 schwarze Exp $
+.\"	$Id: mandoc_html.3,v 1.3 2017/01/17 15:32:44 schwarze Exp $
 .\"
-.\" Copyright (c) 2014 Ingo Schwarze 
+.\" Copyright (c) 2014, 2017 Ingo Schwarze 
 .\"
 .\" Permission to use, copy, modify, and distribute this software for any
 .\" purpose with or without fee is hereby granted, provided that the above
@@ -14,7 +14,7 @@
 .\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 .\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 .\"
-.Dd $Mdocdate: July 23 2014 $
+.Dd $Mdocdate: January 17 2017 $
 .Dt MANDOC_HTML 3
 .Os
 .Sh NAME
@@ -30,8 +30,8 @@
 .Fo print_otag
 .Fa "struct html *h"
 .Fa "enum htmltag tag"
-.Fa "int sz"
-.Fa "const struct htmlpair *p"
+.Fa "const char *fmt"
+.Fa ...
 .Fc
 .Ft void
 .Fo print_tagq
@@ -84,15 +84,6 @@ These structures are declared in
 .Bl -tag -width Ds
 .It Vt struct html
 Internal state of the HTML formatter.
-.It Vt struct htmlpair
-Holds one HTML attribute.
-Members are
-.Fa "enum htmlattr key"
-and
-.Fa "const char *val" .
-Helper macros
-.Fn PAIR_*
-are provided to support initialization of such structures.
 .It Vt struct tag
 One entry for the LIFO stack of HTML elements.
 Members are
@@ -134,14 +125,136 @@ The function
 .Fn print_otag
 prints the start tag of an HTML element with the name
 .Fa tag ,
-including the
-.Fa sz
-attributes that can optionally be provided in the
-.Fa p
-array.
-It uses the private function
-.Fn print_attr
-which in turn uses the private function
+optionally including the attributes specified by
+.Fa fmt .
+If
+.Fa fmt
+is the empty string, no attributes are written.
+Each letter of
+.Fa fmt
+specifies one attribute to write.
+Most attributes require one
+.Va char *
+argument which becomes the value of the attribute.
+The arguments have to be given in the same order as the attribute letters.
+.Bl -tag -width 1n -offset indent
+.It Cm c
+Print a
+.Cm class
+attribute.
+.It Cm h
+Print a
+.Cm href
+attribute.
+This attribute letter can optionally be followed by a modifier letter.
+If followed by
+.Cm R ,
+it formats the link as a local one by prefixing a
+.Sq #
+character.
+If followed by
+.Cm I ,
+it interpretes the argument as a header file name
+and generates a link using the
+.Xr mandoc 1
+.Fl O Cm includes
+option.
+If followed by
+.Cm M ,
+it takes two arguments instead of one, a manual page name and
+section, and formats them as a link to a manual page using the
+.Xr mandoc 1
+.Fl O Cm man
+option.
+.It Cm i
+Print an
+.Cm id
+attribute.
+.It Cm \&?
+Print an arbitrary attribute.
+This format letter requires two
+.Vt char *
+arguments, the attribute name and the value.
+.It Cm s
+Print a
+.Cm style
+attribute.
+If present, it must be the last format letter.
+In contrast to the other format letters, this one does not yet
+print the value and does not require an argument.
+Instead, the rest of the format string consists of pairs of
+argument type letters and style name letters.
+.El
+.Pp
+Argument type letters each require on argument as follows:
+.Bl -tag -width 1n -offset indent
+.It Cm h
+Requires one
+.Vt int
+argument, interpreted as a horizontal length in units of
+.Dv SCALE_EN .
+.It Cm s
+Requires one
+.Vt char *
+argument, used as a style value.
+.It Cm u
+Requires one
+.Vt struct roffsu *
+argument, used as a length.
+.It Cm v
+Requires one
+.Vt int
+argument, interpreted as a vertical length in units of
+.Dv SCALE_VS .
+.It Cm w
+Requires one
+.Vt char *
+argument, interpreted as an
+.Xr mdoc 7 Ns -style
+width specifier.
+.El
+.Pp
+Style name letters decide what to do with the preceding argument:
+.Bl -tag -width 1n -offset indent
+.It Cm b
+Set
+.Cm margin-bottom
+to the given length.
+.It Cm h
+Set
+.Cm height
+to the given length.
+.It Cm i
+Set
+.Cm text-indent
+to the given length.
+.It Cm l
+Set
+.Cm margin-left
+to the given length.
+.It Cm t
+Set
+.Cm margin-top
+to the given length.
+.It Cm w
+Set
+.Cm width
+to the given length.
+.It Cm W
+Set
+.Cm min-width
+to the given length.
+.It Cm \&?
+The special pair
+.Cm s?
+requires two
+.Vt char *
+arguments.
+The first is the style name, the second its value.
+.El
+.Pp
+.Fn print_otag
+uses the private function
 .Fn print_encode
 to take care of HTML encoding.
 If required by the element type, it remembers in
@@ -175,28 +288,6 @@ and
 functions.
 .Pp
 The functions
-.Fn bufinit ,
-.Fn bufcat* ,
-and
-.Fn buffmt*
-do not directly produce output but buffer text in the
-.Fa buf
-member of
-.Fa h .
-They are not used internally by
-.Pa html.c
-but intended for use by the language-specific formatters
-to ease preparation of strings for the
-.Fa p
-argument of
-.Fn print_otag
-and for the
-.Fa word
-argument of
-.Fn print_text .
-Consequently, these functions do not do any HTML encoding.
-.Pp
-The functions
 .Fn html_strlen ,
 .Fn print_eqn ,
 .Fn print_tbl ,
diff --git a/mandocdb.c b/mandocdb.c
index 6c04cb05256c..133e73670c10 100644
--- a/mandocdb.c
+++ b/mandocdb.c
@@ -1,7 +1,8 @@
-/*	$Id: mandocdb.c,v 1.218 2016/07/12 05:18:38 kristaps Exp $ */
+/*	$Id: mandocdb.c,v 1.237 2017/01/11 17:39:53 schwarze Exp $ */
 /*
  * Copyright (c) 2011, 2012 Kristaps Dzonsons 
- * Copyright (c) 2011-2016 Ingo Schwarze 
+ * Copyright (c) 2011-2017 Ingo Schwarze 
+ * Copyright (c) 2016 Ed Maste 
  *
  * Permission to use, copy, modify, and distribute this software for any
  * purpose with or without fee is hereby granted, provided that the above
@@ -37,6 +38,7 @@
 #if HAVE_SANDBOX_INIT
 #include 
 #endif
+#include 
 #include 
 #include 
 #include 
@@ -44,8 +46,6 @@
 #include 
 #include 
 
-#include 
-
 #include "mandoc_aux.h"
 #include "mandoc_ohash.h"
 #include "mandoc.h"
@@ -54,29 +54,11 @@
 #include "man.h"
 #include "manconf.h"
 #include "mansearch.h"
+#include "dba_array.h"
+#include "dba.h"
 
-extern int mansearch_keymax;
 extern const char *const mansearch_keynames[];
 
-#define	SQL_EXEC(_v) \
-	if (SQLITE_OK != sqlite3_exec(db, (_v), NULL, NULL, NULL)) \
-		say("", "%s: %s", (_v), sqlite3_errmsg(db))
-#define	SQL_BIND_TEXT(_s, _i, _v) \
-	if (SQLITE_OK != sqlite3_bind_text \
-		((_s), (_i)++, (_v), -1, SQLITE_STATIC)) \
-		say(mlink->file, "%s", sqlite3_errmsg(db))
-#define	SQL_BIND_INT(_s, _i, _v) \
-	if (SQLITE_OK != sqlite3_bind_int \
-		((_s), (_i)++, (_v))) \
-		say(mlink->file, "%s", sqlite3_errmsg(db))
-#define	SQL_BIND_INT64(_s, _i, _v) \
-	if (SQLITE_OK != sqlite3_bind_int64 \
-		((_s), (_i)++, (_v))) \
-		say(mlink->file, "%s", sqlite3_errmsg(db))
-#define SQL_STEP(_s) \
-	if (SQLITE_DONE != sqlite3_step((_s))) \
-		say(mlink->file, "%s", sqlite3_errmsg(db))
-
 enum	op {
 	OP_DEFAULT = 0, /* new dbs from dir list or default config */
 	OP_CONFFILE, /* new databases from custom config file */
@@ -98,14 +80,15 @@ struct	inodev {
 
 struct	mpage {
 	struct inodev	 inodev;  /* used for hashing routine */
-	int64_t		 pageid;  /* pageid in mpages SQL table */
+	struct dba_array *dba;
 	char		*sec;     /* section from file content */
 	char		*arch;    /* architecture from file content */
 	char		*title;   /* title from file content */
 	char		*desc;    /* description from file content */
+	struct mpage	*next;    /* singly linked list */
 	struct mlink	*mlinks;  /* singly linked list */
-	int		 form;    /* format from file content */
 	int		 name_head_done;
+	enum form	 form;    /* format from file content */
 };
 
 struct	mlink {
@@ -116,19 +99,9 @@ struct	mlink {
 	char		*fsec;    /* section from file name suffix */
 	struct mlink	*next;    /* singly linked list */
 	struct mpage	*mpage;   /* parent */
-	int		 dform;   /* format from directory */
-	int		 fform;   /* format from file name suffix */
 	int		 gzip;	  /* filename has a .gz suffix */
-};
-
-enum	stmt {
-	STMT_DELETE_PAGE = 0,	/* delete mpage */
-	STMT_INSERT_PAGE,	/* insert mpage */
-	STMT_INSERT_LINK,	/* insert mlink */
-	STMT_INSERT_NAME,	/* insert name */
-	STMT_SELECT_NAME,	/* retrieve existing name flags */
-	STMT_INSERT_KEY,	/* insert parsed key */
-	STMT__MAX
+	enum form	 dform;   /* format from directory */
+	enum form	 fform;   /* format from file name suffix */
 };
 
 typedef	int (*mdoc_fp)(struct mpage *, const struct roff_meta *,
@@ -137,22 +110,28 @@ typedef	int (*mdoc_fp)(struct mpage *, const struct roff_meta *,
 struct	mdoc_handler {
 	mdoc_fp		 fp; /* optional handler */
 	uint64_t	 mask;  /* set unless handler returns 0 */
+	int		 taboo;  /* node flags that must not be set */
 };
 
-static	void	 dbclose(int);
-static	void	 dbadd(struct mpage *);
+
+int		 mandocdb(int, char *[]);
+
+static	void	 dbadd(struct dba *, struct mpage *);
 static	void	 dbadd_mlink(const struct mlink *mlink);
-static	void	 dbadd_mlink_name(const struct mlink *mlink);
-static	int	 dbopen(int);
-static	void	 dbprune(void);
+static	void	 dbprune(struct dba *);
+static	void	 dbwrite(struct dba *);
 static	void	 filescan(const char *);
+#if HAVE_FTS_COMPARE_CONST
+static	int	 fts_compare(const FTSENT *const *, const FTSENT *const *);
+#else
+static	int	 fts_compare(const FTSENT **, const FTSENT **);
+#endif
 static	void	 mlink_add(struct mlink *, const struct stat *);
 static	void	 mlink_check(struct mpage *, struct mlink *);
 static	void	 mlink_free(struct mlink *);
 static	void	 mlinks_undupe(struct mpage *);
 static	void	 mpages_free(void);
-static	void	 mpages_merge(struct mparse *);
-static	void	 names_check(void);
+static	void	 mpages_merge(struct dba *, struct mparse *);
 static	void	 parse_cat(struct mpage *, int);
 static	void	 parse_man(struct mpage *, const struct roff_meta *,
 			const struct roff_node *);
@@ -180,14 +159,14 @@ static	int	 parse_mdoc_Xr(struct mpage *, const struct roff_meta *,
 static	void	 putkey(const struct mpage *, char *, uint64_t);
 static	void	 putkeys(const struct mpage *, char *, size_t, uint64_t);
 static	void	 putmdockey(const struct mpage *,
-			const struct roff_node *, uint64_t);
+			const struct roff_node *, uint64_t, int);
 static	int	 render_string(char **, size_t *);
-static	void	 say(const char *, const char *, ...);
+static	void	 say(const char *, const char *, ...)
+			__attribute__((__format__ (printf, 2, 3)));
 static	int	 set_basedir(const char *, int);
 static	int	 treescan(void);
 static	size_t	 utf8(unsigned int, char [7]);
 
-static	char		 tempfilename[32];
 static	int		 nodb; /* no database changes */
 static	int		 mparse_options; /* abort the parse early */
 static	int		 use_all; /* use all found files */
@@ -197,138 +176,137 @@ static	int		 write_utf8; /* write UTF-8 output; else ASCII */
 static	int		 exitcode; /* to be returned by main */
 static	enum op		 op; /* operational mode */
 static	char		 basedir[PATH_MAX]; /* current base directory */
+static	struct mpage	*mpage_head; /* list of distinct manual pages */
 static	struct ohash	 mpages; /* table of distinct manual pages */
 static	struct ohash	 mlinks; /* table of directory entries */
 static	struct ohash	 names; /* table of all names */
 static	struct ohash	 strings; /* table of all strings */
-static	sqlite3		*db = NULL; /* current database */
-static	sqlite3_stmt	*stmts[STMT__MAX]; /* current statements */
 static	uint64_t	 name_mask;
 
 static	const struct mdoc_handler mdocs[MDOC_MAX] = {
-	{ NULL, 0 },  /* Ap */
-	{ NULL, 0 },  /* Dd */
-	{ NULL, 0 },  /* Dt */
-	{ NULL, 0 },  /* Os */
-	{ parse_mdoc_Sh, TYPE_Sh }, /* Sh */
-	{ parse_mdoc_head, TYPE_Ss }, /* Ss */
-	{ NULL, 0 },  /* Pp */
-	{ NULL, 0 },  /* D1 */
-	{ NULL, 0 },  /* Dl */
-	{ NULL, 0 },  /* Bd */
-	{ NULL, 0 },  /* Ed */
-	{ NULL, 0 },  /* Bl */
-	{ NULL, 0 },  /* El */
-	{ NULL, 0 },  /* It */
-	{ NULL, 0 },  /* Ad */
-	{ NULL, TYPE_An },  /* An */
-	{ NULL, TYPE_Ar },  /* Ar */
-	{ NULL, TYPE_Cd },  /* Cd */
-	{ NULL, TYPE_Cm },  /* Cm */
-	{ NULL, TYPE_Dv },  /* Dv */
-	{ NULL, TYPE_Er },  /* Er */
-	{ NULL, TYPE_Ev },  /* Ev */
-	{ NULL, 0 },  /* Ex */
-	{ NULL, TYPE_Fa },  /* Fa */
-	{ parse_mdoc_Fd, 0 },  /* Fd */
-	{ NULL, TYPE_Fl },  /* Fl */
-	{ parse_mdoc_Fn, 0 },  /* Fn */
-	{ NULL, TYPE_Ft },  /* Ft */
-	{ NULL, TYPE_Ic },  /* Ic */
-	{ NULL, TYPE_In },  /* In */
-	{ NULL, TYPE_Li },  /* Li */
-	{ parse_mdoc_Nd, 0 },  /* Nd */
-	{ parse_mdoc_Nm, 0 },  /* Nm */
-	{ NULL, 0 },  /* Op */
-	{ NULL, 0 },  /* Ot */
-	{ NULL, TYPE_Pa },  /* Pa */
-	{ NULL, 0 },  /* Rv */
-	{ NULL, TYPE_St },  /* St */
-	{ parse_mdoc_Va, TYPE_Va },  /* Va */
-	{ parse_mdoc_Va, TYPE_Vt },  /* Vt */
-	{ parse_mdoc_Xr, 0 },  /* Xr */
-	{ NULL, 0 },  /* %A */
-	{ NULL, 0 },  /* %B */
-	{ NULL, 0 },  /* %D */
-	{ NULL, 0 },  /* %I */
-	{ NULL, 0 },  /* %J */
-	{ NULL, 0 },  /* %N */
-	{ NULL, 0 },  /* %O */
-	{ NULL, 0 },  /* %P */
-	{ NULL, 0 },  /* %R */
-	{ NULL, 0 },  /* %T */
-	{ NULL, 0 },  /* %V */
-	{ NULL, 0 },  /* Ac */
-	{ NULL, 0 },  /* Ao */
-	{ NULL, 0 },  /* Aq */
-	{ NULL, TYPE_At },  /* At */
-	{ NULL, 0 },  /* Bc */
-	{ NULL, 0 },  /* Bf */
-	{ NULL, 0 },  /* Bo */
-	{ NULL, 0 },  /* Bq */
-	{ NULL, TYPE_Bsx },  /* Bsx */
-	{ NULL, TYPE_Bx },  /* Bx */
-	{ NULL, 0 },  /* Db */
-	{ NULL, 0 },  /* Dc */
-	{ NULL, 0 },  /* Do */
-	{ NULL, 0 },  /* Dq */
-	{ NULL, 0 },  /* Ec */
-	{ NULL, 0 },  /* Ef */
-	{ NULL, TYPE_Em },  /* Em */
-	{ NULL, 0 },  /* Eo */
-	{ NULL, TYPE_Fx },  /* Fx */
-	{ NULL, TYPE_Ms },  /* Ms */
-	{ NULL, 0 },  /* No */
-	{ NULL, 0 },  /* Ns */
-	{ NULL, TYPE_Nx },  /* Nx */
-	{ NULL, TYPE_Ox },  /* Ox */
-	{ NULL, 0 },  /* Pc */
-	{ NULL, 0 },  /* Pf */
-	{ NULL, 0 },  /* Po */
-	{ NULL, 0 },  /* Pq */
-	{ NULL, 0 },  /* Qc */
-	{ NULL, 0 },  /* Ql */
-	{ NULL, 0 },  /* Qo */
-	{ NULL, 0 },  /* Qq */
-	{ NULL, 0 },  /* Re */
-	{ NULL, 0 },  /* Rs */
-	{ NULL, 0 },  /* Sc */
-	{ NULL, 0 },  /* So */
-	{ NULL, 0 },  /* Sq */
-	{ NULL, 0 },  /* Sm */
-	{ NULL, 0 },  /* Sx */
-	{ NULL, TYPE_Sy },  /* Sy */
-	{ NULL, TYPE_Tn },  /* Tn */
-	{ NULL, 0 },  /* Ux */
-	{ NULL, 0 },  /* Xc */
-	{ NULL, 0 },  /* Xo */
-	{ parse_mdoc_Fo, 0 },  /* Fo */
-	{ NULL, 0 },  /* Fc */
-	{ NULL, 0 },  /* Oo */
-	{ NULL, 0 },  /* Oc */
-	{ NULL, 0 },  /* Bk */
-	{ NULL, 0 },  /* Ek */
-	{ NULL, 0 },  /* Bt */
-	{ NULL, 0 },  /* Hf */
-	{ NULL, 0 },  /* Fr */
-	{ NULL, 0 },  /* Ud */
-	{ NULL, TYPE_Lb },  /* Lb */
-	{ NULL, 0 },  /* Lp */
-	{ NULL, TYPE_Lk },  /* Lk */
-	{ NULL, TYPE_Mt },  /* Mt */
-	{ NULL, 0 },  /* Brq */
-	{ NULL, 0 },  /* Bro */
-	{ NULL, 0 },  /* Brc */
-	{ NULL, 0 },  /* %C */
-	{ NULL, 0 },  /* Es */
-	{ NULL, 0 },  /* En */
-	{ NULL, TYPE_Dx },  /* Dx */
-	{ NULL, 0 },  /* %Q */
-	{ NULL, 0 },  /* br */
-	{ NULL, 0 },  /* sp */
-	{ NULL, 0 },  /* %U */
-	{ NULL, 0 },  /* Ta */
-	{ NULL, 0 },  /* ll */
+	{ NULL, 0, 0 },  /* Ap */
+	{ NULL, 0, NODE_NOPRT },  /* Dd */
+	{ NULL, 0, NODE_NOPRT },  /* Dt */
+	{ NULL, 0, NODE_NOPRT },  /* Os */
+	{ parse_mdoc_Sh, TYPE_Sh, 0 }, /* Sh */
+	{ parse_mdoc_head, TYPE_Ss, 0 }, /* Ss */
+	{ NULL, 0, 0 },  /* Pp */
+	{ NULL, 0, 0 },  /* D1 */
+	{ NULL, 0, 0 },  /* Dl */
+	{ NULL, 0, 0 },  /* Bd */
+	{ NULL, 0, 0 },  /* Ed */
+	{ NULL, 0, 0 },  /* Bl */
+	{ NULL, 0, 0 },  /* El */
+	{ NULL, 0, 0 },  /* It */
+	{ NULL, 0, 0 },  /* Ad */
+	{ NULL, TYPE_An, 0 },  /* An */
+	{ NULL, TYPE_Ar, 0 },  /* Ar */
+	{ NULL, TYPE_Cd, 0 },  /* Cd */
+	{ NULL, TYPE_Cm, 0 },  /* Cm */
+	{ NULL, TYPE_Dv, 0 },  /* Dv */
+	{ NULL, TYPE_Er, 0 },  /* Er */
+	{ NULL, TYPE_Ev, 0 },  /* Ev */
+	{ NULL, 0, 0 },  /* Ex */
+	{ NULL, TYPE_Fa, 0 },  /* Fa */
+	{ parse_mdoc_Fd, 0, 0 },  /* Fd */
+	{ NULL, TYPE_Fl, 0 },  /* Fl */
+	{ parse_mdoc_Fn, 0, 0 },  /* Fn */
+	{ NULL, TYPE_Ft, 0 },  /* Ft */
+	{ NULL, TYPE_Ic, 0 },  /* Ic */
+	{ NULL, TYPE_In, 0 },  /* In */
+	{ NULL, TYPE_Li, 0 },  /* Li */
+	{ parse_mdoc_Nd, 0, 0 },  /* Nd */
+	{ parse_mdoc_Nm, 0, 0 },  /* Nm */
+	{ NULL, 0, 0 },  /* Op */
+	{ NULL, 0, 0 },  /* Ot */
+	{ NULL, TYPE_Pa, NODE_NOSRC },  /* Pa */
+	{ NULL, 0, 0 },  /* Rv */
+	{ NULL, TYPE_St, 0 },  /* St */
+	{ parse_mdoc_Va, TYPE_Va, 0 },  /* Va */
+	{ parse_mdoc_Va, TYPE_Vt, 0 },  /* Vt */
+	{ parse_mdoc_Xr, 0, 0 },  /* Xr */
+	{ NULL, 0, 0 },  /* %A */
+	{ NULL, 0, 0 },  /* %B */
+	{ NULL, 0, 0 },  /* %D */
+	{ NULL, 0, 0 },  /* %I */
+	{ NULL, 0, 0 },  /* %J */
+	{ NULL, 0, 0 },  /* %N */
+	{ NULL, 0, 0 },  /* %O */
+	{ NULL, 0, 0 },  /* %P */
+	{ NULL, 0, 0 },  /* %R */
+	{ NULL, 0, 0 },  /* %T */
+	{ NULL, 0, 0 },  /* %V */
+	{ NULL, 0, 0 },  /* Ac */
+	{ NULL, 0, 0 },  /* Ao */
+	{ NULL, 0, 0 },  /* Aq */
+	{ NULL, TYPE_At, 0 },  /* At */
+	{ NULL, 0, 0 },  /* Bc */
+	{ NULL, 0, 0 },  /* Bf */
+	{ NULL, 0, 0 },  /* Bo */
+	{ NULL, 0, 0 },  /* Bq */
+	{ NULL, TYPE_Bsx, NODE_NOSRC },  /* Bsx */
+	{ NULL, TYPE_Bx, NODE_NOSRC },  /* Bx */
+	{ NULL, 0, 0 },  /* Db */
+	{ NULL, 0, 0 },  /* Dc */
+	{ NULL, 0, 0 },  /* Do */
+	{ NULL, 0, 0 },  /* Dq */
+	{ NULL, 0, 0 },  /* Ec */
+	{ NULL, 0, 0 },  /* Ef */
+	{ NULL, TYPE_Em, 0 },  /* Em */
+	{ NULL, 0, 0 },  /* Eo */
+	{ NULL, TYPE_Fx, NODE_NOSRC },  /* Fx */
+	{ NULL, TYPE_Ms, 0 },  /* Ms */
+	{ NULL, 0, 0 },  /* No */
+	{ NULL, 0, 0 },  /* Ns */
+	{ NULL, TYPE_Nx, NODE_NOSRC },  /* Nx */
+	{ NULL, TYPE_Ox, NODE_NOSRC },  /* Ox */
+	{ NULL, 0, 0 },  /* Pc */
+	{ NULL, 0, 0 },  /* Pf */
+	{ NULL, 0, 0 },  /* Po */
+	{ NULL, 0, 0 },  /* Pq */
+	{ NULL, 0, 0 },  /* Qc */
+	{ NULL, 0, 0 },  /* Ql */
+	{ NULL, 0, 0 },  /* Qo */
+	{ NULL, 0, 0 },  /* Qq */
+	{ NULL, 0, 0 },  /* Re */
+	{ NULL, 0, 0 },  /* Rs */
+	{ NULL, 0, 0 },  /* Sc */
+	{ NULL, 0, 0 },  /* So */
+	{ NULL, 0, 0 },  /* Sq */
+	{ NULL, 0, 0 },  /* Sm */
+	{ NULL, 0, 0 },  /* Sx */
+	{ NULL, TYPE_Sy, 0 },  /* Sy */
+	{ NULL, TYPE_Tn, 0 },  /* Tn */
+	{ NULL, 0, NODE_NOSRC },  /* Ux */
+	{ NULL, 0, 0 },  /* Xc */
+	{ NULL, 0, 0 },  /* Xo */
+	{ parse_mdoc_Fo, 0, 0 },  /* Fo */
+	{ NULL, 0, 0 },  /* Fc */
+	{ NULL, 0, 0 },  /* Oo */
+	{ NULL, 0, 0 },  /* Oc */
+	{ NULL, 0, 0 },  /* Bk */
+	{ NULL, 0, 0 },  /* Ek */
+	{ NULL, 0, 0 },  /* Bt */
+	{ NULL, 0, 0 },  /* Hf */
+	{ NULL, 0, 0 },  /* Fr */
+	{ NULL, 0, 0 },  /* Ud */
+	{ NULL, TYPE_Lb, NODE_NOSRC },  /* Lb */
+	{ NULL, 0, 0 },  /* Lp */
+	{ NULL, TYPE_Lk, 0 },  /* Lk */
+	{ NULL, TYPE_Mt, NODE_NOSRC },  /* Mt */
+	{ NULL, 0, 0 },  /* Brq */
+	{ NULL, 0, 0 },  /* Bro */
+	{ NULL, 0, 0 },  /* Brc */
+	{ NULL, 0, 0 },  /* %C */
+	{ NULL, 0, 0 },  /* Es */
+	{ NULL, 0, 0 },  /* En */
+	{ NULL, TYPE_Dx, NODE_NOSRC },  /* Dx */
+	{ NULL, 0, 0 },  /* %Q */
+	{ NULL, 0, 0 },  /* br */
+	{ NULL, 0, 0 },  /* sp */
+	{ NULL, 0, 0 },  /* %U */
+	{ NULL, 0, 0 },  /* Ta */
+	{ NULL, 0, 0 },  /* ll */
 };
 
 
@@ -337,6 +315,7 @@ mandocdb(int argc, char *argv[])
 {
 	struct manconf	  conf;
 	struct mparse	 *mp;
+	struct dba	 *dba;
 	const char	 *path_arg, *progname;
 	size_t		  j, sz;
 	int		  ch, i;
@@ -356,7 +335,6 @@ mandocdb(int argc, char *argv[])
 #endif
 
 	memset(&conf, 0, sizeof(conf));
-	memset(stmts, 0, STMT__MAX * sizeof(sqlite3_stmt *));
 
 	/*
 	 * We accept a few different invocations.
@@ -457,7 +435,8 @@ mandocdb(int argc, char *argv[])
 		if (OP_TEST != op && 0 == set_basedir(path_arg, 1))
 			goto out;
 
-		if (dbopen(1)) {
+		dba = nodb ? dba_new(128) : dba_read(MANDOC_DB);
+		if (dba != NULL) {
 			/*
 			 * The existing database is usable.  Process
 			 * all files specified on the command-line.
@@ -474,28 +453,28 @@ mandocdb(int argc, char *argv[])
 			use_all = 1;
 			for (i = 0; i < argc; i++)
 				filescan(argv[i]);
-			if (OP_TEST != op)
-				dbprune();
+			if (nodb == 0)
+				dbprune(dba);
 		} else {
-			/*
-			 * Database missing or corrupt.
-			 * Recreate from scratch.
-			 */
+			/* Database missing or corrupt. */
+			if (op != OP_UPDATE || errno != ENOENT)
+				say(MANDOC_DB, "%s: Automatically recreating"
+				    " from scratch", strerror(errno));
 			exitcode = (int)MANDOCLEVEL_OK;
 			op = OP_DEFAULT;
 			if (0 == treescan())
 				goto out;
-			if (0 == dbopen(0))
-				goto out;
+			dba = dba_new(128);
 		}
 		if (OP_DELETE != op)
-			mpages_merge(mp);
-		dbclose(OP_DEFAULT == op ? 0 : 1);
+			mpages_merge(dba, mp);
+		if (nodb == 0)
+			dbwrite(dba);
+		dba_free(dba);
 	} else {
 		/*
 		 * If we have arguments, use them as our manpaths.
-		 * If we don't, grok from manpath(1) or however else
-		 * manconf_parse() wants to do it.
+		 * If we don't, use man.conf(5).
 		 */
 		if (argc > 0) {
 			conf.manpath.paths = mandoc_reallocarray(NULL,
@@ -535,14 +514,11 @@ mandocdb(int argc, char *argv[])
 				continue;
 			if (0 == treescan())
 				continue;
-			if (0 == dbopen(0))
-				continue;
-
-			mpages_merge(mp);
-			if (warnings && !nodb &&
-			    ! (MPARSE_QUICK & mparse_options))
-				names_check();
-			dbclose(0);
+			dba = dba_new(128);
+			mpages_merge(dba, mp);
+			if (nodb == 0)
+				dbwrite(dba);
+			dba_free(dba);
 
 			if (j + 1 < conf.manpath.sz) {
 				mpages_free();
@@ -571,6 +547,20 @@ mandocdb(int argc, char *argv[])
 	return (int)MANDOCLEVEL_BADARG;
 }
 
+/*
+ * To get a singly linked list in alpha order while inserting entries
+ * at the beginning, process directory entries in reverse alpha order.
+ */
+static int
+#if HAVE_FTS_COMPARE_CONST
+fts_compare(const FTSENT *const *a, const FTSENT *const *b)
+#else
+fts_compare(const FTSENT **a, const FTSENT **b)
+#endif
+{
+	return -strcmp((*a)->fts_name, (*b)->fts_name);
+}
+
 /*
  * Scan a directory tree rooted at "basedir" for manpages.
  * We use fts(), scanning directory parts along the way for clues to our
@@ -592,7 +582,8 @@ treescan(void)
 	FTS		*f;
 	FTSENT		*ff;
 	struct mlink	*mlink;
-	int		 dform, gzip;
+	int		 gzip;
+	enum form	 dform;
 	char		*dsec, *arch, *fsec, *cp;
 	const char	*path;
 	const char	*argv[2];
@@ -600,8 +591,8 @@ treescan(void)
 	argv[0] = ".";
 	argv[1] = (char *)NULL;
 
-	f = fts_open((char * const *)argv,
-	    FTS_PHYSICAL | FTS_NOCHDIR, NULL);
+	f = fts_open((char * const *)argv, FTS_PHYSICAL | FTS_NOCHDIR,
+	    fts_compare);
 	if (f == NULL) {
 		exitcode = (int)MANDOCLEVEL_SYSERR;
 		say("", "&fts_open");
@@ -966,6 +957,9 @@ mlink_add(struct mlink *mlink, const struct stat *st)
 		mpage = mandoc_calloc(1, sizeof(struct mpage));
 		mpage->inodev.st_ino = inodev.st_ino;
 		mpage->inodev.st_dev = inodev.st_dev;
+		mpage->form = FORM_NONE;
+		mpage->next = mpage_head;
+		mpage_head = mpage;
 		ohash_insert(&mpages, slot, mpage);
 	} else
 		mlink->next = mpage->mlinks;
@@ -989,20 +983,18 @@ mpages_free(void)
 {
 	struct mpage	*mpage;
 	struct mlink	*mlink;
-	unsigned int	 slot;
 
-	mpage = ohash_first(&mpages, &slot);
-	while (NULL != mpage) {
-		while (NULL != (mlink = mpage->mlinks)) {
+	while ((mpage = mpage_head) != NULL) {
+		while ((mlink = mpage->mlinks) != NULL) {
 			mpage->mlinks = mlink->next;
 			mlink_free(mlink);
 		}
+		mpage_head = mpage->next;
 		free(mpage->sec);
 		free(mpage->arch);
 		free(mpage->title);
 		free(mpage->desc);
 		free(mpage);
-		mpage = ohash_next(&mpages, &slot);
 	}
 }
 
@@ -1079,7 +1071,7 @@ mlink_check(struct mpage *mpage, struct mlink *mlink)
 	 * architectures.
 	 * A few manuals are even shared across completely
 	 * different architectures, for example fdformat(1)
-	 * on amd64, i386, sparc, and sparc64.
+	 * on amd64, i386, and sparc64.
 	 */
 
 	if (strcasecmp(mpage->arch, mlink->arch))
@@ -1114,27 +1106,19 @@ mlink_check(struct mpage *mpage, struct mlink *mlink)
  * and filename to determine whether the file is parsable or not.
  */
 static void
-mpages_merge(struct mparse *mp)
+mpages_merge(struct dba *dba, struct mparse *mp)
 {
-	char			 any[] = "any";
 	struct mpage		*mpage, *mpage_dest;
 	struct mlink		*mlink, *mlink_dest;
 	struct roff_man		*man;
 	char			*sodest;
 	char			*cp;
 	int			 fd;
-	unsigned int		 pslot;
 
-	if ( ! nodb)
-		SQL_EXEC("BEGIN TRANSACTION");
-
-	mpage = ohash_first(&mpages, &pslot);
-	while (mpage != NULL) {
+	for (mpage = mpage_head; mpage != NULL; mpage = mpage->next) {
 		mlinks_undupe(mpage);
-		if ((mlink = mpage->mlinks) == NULL) {
-			mpage = ohash_next(&mpages, &pslot);
+		if ((mlink = mpage->mlinks) == NULL)
 			continue;
-		}
 
 		name_mask = NAME_MASK;
 		mandoc_ohash_init(&names, 4, offsetof(struct str, key));
@@ -1184,8 +1168,8 @@ mpages_merge(struct mparse *mp)
 					 * to the target.
 					 */
 
-					if (mpage_dest->pageid)
-						dbadd_mlink_name(mlink);
+					if (mpage_dest->dba != NULL)
+						dbadd_mlink(mlink);
 
 					if (mlink->next == NULL)
 						break;
@@ -1221,19 +1205,6 @@ mpages_merge(struct mparse *mp)
 			mpage->arch = mandoc_strdup(mlink->arch);
 			mpage->title = mandoc_strdup(mlink->name);
 		}
-		putkey(mpage, mpage->sec, TYPE_sec);
-		if (*mpage->arch != '\0')
-			putkey(mpage, mpage->arch, TYPE_arch);
-
-		for ( ; mlink != NULL; mlink = mlink->next) {
-			if ('\0' != *mlink->dsec)
-				putkey(mpage, mlink->dsec, TYPE_sec);
-			if ('\0' != *mlink->fsec)
-				putkey(mpage, mlink->fsec, TYPE_sec);
-			putkey(mpage, '\0' == *mlink->arch ?
-			    any : mlink->arch, TYPE_arch);
-			putkey(mpage, mlink->name, NAME_FILE);
-		}
 
 		assert(mpage->desc == NULL);
 		if (man != NULL && man->macroset == MACROSET_MDOC)
@@ -1250,52 +1221,13 @@ mpages_merge(struct mparse *mp)
 			     mlink = mlink->next)
 				mlink_check(mpage, mlink);
 
-		dbadd(mpage);
+		dbadd(dba, mpage);
 		mlink = mpage->mlinks;
 
 nextpage:
 		ohash_delete(&strings);
 		ohash_delete(&names);
-		mpage = ohash_next(&mpages, &pslot);
 	}
-
-	if (0 == nodb)
-		SQL_EXEC("END TRANSACTION");
-}
-
-static void
-names_check(void)
-{
-	sqlite3_stmt	*stmt;
-	const char	*name, *sec, *arch, *key;
-
-	sqlite3_prepare_v2(db,
-	  "SELECT name, sec, arch, key FROM ("
-	    "SELECT name AS key, pageid FROM names "
-	    "WHERE bits & ? AND NOT EXISTS ("
-	      "SELECT pageid FROM mlinks "
-	      "WHERE mlinks.pageid == names.pageid "
-	      "AND mlinks.name == names.name"
-	    ")"
-	  ") JOIN ("
-	    "SELECT sec, arch, name, pageid FROM mlinks "
-	    "GROUP BY pageid"
-	  ") USING (pageid);",
-	  -1, &stmt, NULL);
-
-	if (sqlite3_bind_int64(stmt, 1, NAME_TITLE) != SQLITE_OK)
-		say("", "%s", sqlite3_errmsg(db));
-
-	while (sqlite3_step(stmt) == SQLITE_ROW) {
-		name = (const char *)sqlite3_column_text(stmt, 0);
-		sec  = (const char *)sqlite3_column_text(stmt, 1);
-		arch = (const char *)sqlite3_column_text(stmt, 2);
-		key  = (const char *)sqlite3_column_text(stmt, 3);
-		say("", "%s(%s%s%s) lacks mlink \"%s\"", name, sec,
-		    '\0' == *arch ? "" : "/",
-		    '\0' == *arch ? "" : arch, key);
-	}
-	sqlite3_finalize(stmt);
 }
 
 static void
@@ -1420,13 +1352,6 @@ parse_cat(struct mpage *mpage, int fd)
 static void
 putkey(const struct mpage *mpage, char *value, uint64_t type)
 {
-	char	 *cp;
-
-	assert(NULL != value);
-	if (TYPE_arch == type)
-		for (cp = value; *cp; cp++)
-			if (isupper((unsigned char)*cp))
-				*cp = _tolower((unsigned char)*cp);
 	putkeys(mpage, value, strlen(value), type);
 }
 
@@ -1435,12 +1360,14 @@ putkey(const struct mpage *mpage, char *value, uint64_t type)
  */
 static void
 putmdockey(const struct mpage *mpage,
-	const struct roff_node *n, uint64_t m)
+	const struct roff_node *n, uint64_t m, int taboo)
 {
 
 	for ( ; NULL != n; n = n->next) {
+		if (n->flags & taboo)
+			continue;
 		if (NULL != n->child)
-			putmdockey(mpage, n->child, m);
+			putmdockey(mpage, n->child, m, taboo);
 		if (n->type == ROFFT_TEXT)
 			putkey(mpage, n->string, m);
 	}
@@ -1578,6 +1505,8 @@ parse_mdoc(struct mpage *mpage, const struct roff_meta *meta,
 
 	assert(NULL != n);
 	for (n = n->child; NULL != n; n = n->next) {
+		if (n->flags & mdocs[n->tok].taboo)
+			continue;
 		switch (n->type) {
 		case ROFFT_ELEM:
 		case ROFFT_BLOCK:
@@ -1589,7 +1518,7 @@ parse_mdoc(struct mpage *mpage, const struct roff_meta *meta,
 				       break;
 			if (mdocs[n->tok].mask)
 				putmdockey(mpage, n->child,
-				    mdocs[n->tok].mask);
+				    mdocs[n->tok].mask, mdocs[n->tok].taboo);
 			break;
 		default:
 			assert(n->type != ROFFT_ROOT);
@@ -1757,17 +1686,17 @@ parse_mdoc_Nm(struct mpage *mpage, const struct roff_meta *meta,
 {
 
 	if (SEC_NAME == n->sec)
-		putmdockey(mpage, n->child, NAME_TITLE);
+		putmdockey(mpage, n->child, NAME_TITLE, 0);
 	else if (n->sec == SEC_SYNOPSIS && n->type == ROFFT_HEAD) {
 		if (n->child == NULL)
 			putkey(mpage, meta->name, NAME_SYN);
 		else
-			putmdockey(mpage, n->child, NAME_SYN);
+			putmdockey(mpage, n->child, NAME_SYN, 0);
 	}
 	if ( ! (mpage->name_head_done ||
 	    n->child == NULL || n->child->string == NULL ||
 	    strcasecmp(n->child->string, meta->title))) {
-		putkey(mpage, n->child->string, ROFFT_HEAD);
+		putkey(mpage, n->child->string, NAME_HEAD);
 		mpage->name_head_done = 1;
 	}
 	return 0;
@@ -1815,15 +1744,16 @@ putkeys(const struct mpage *mpage, char *cp, size_t sz, uint64_t v)
 			name_mask &= ~NAME_FIRST;
 		if (debug > 1)
 			say(mpage->mlinks->file,
-			    "Adding name %*s, bits=%d", sz, cp, v);
+			    "Adding name %*s, bits=0x%llx", (int)sz, cp,
+			    (unsigned long long)v);
 	} else {
 		htab = &strings;
 		if (debug > 1)
-		    for (i = 0; i < mansearch_keymax; i++)
+		    for (i = 0; i < KEY_MAX; i++)
 			if ((uint64_t)1 << i & v)
 			    say(mpage->mlinks->file,
 				"Adding key %s=%*s",
-				mansearch_keynames[i], sz, cp);
+				mansearch_keynames[i], (int)sz, cp);
 	}
 
 	end = cp + sz;
@@ -2025,53 +1955,24 @@ render_string(char **public, size_t *psz)
 static void
 dbadd_mlink(const struct mlink *mlink)
 {
-	size_t		 i;
-
-	i = 1;
-	SQL_BIND_TEXT(stmts[STMT_INSERT_LINK], i, mlink->dsec);
-	SQL_BIND_TEXT(stmts[STMT_INSERT_LINK], i, mlink->arch);
-	SQL_BIND_TEXT(stmts[STMT_INSERT_LINK], i, mlink->name);
-	SQL_BIND_INT64(stmts[STMT_INSERT_LINK], i, mlink->mpage->pageid);
-	SQL_STEP(stmts[STMT_INSERT_LINK]);
-	sqlite3_reset(stmts[STMT_INSERT_LINK]);
-}
-
-static void
-dbadd_mlink_name(const struct mlink *mlink)
-{
-	uint64_t	 bits;
-	size_t		 i;
-
-	dbadd_mlink(mlink);
-
-	i = 1;
-	SQL_BIND_INT64(stmts[STMT_SELECT_NAME], i, mlink->mpage->pageid);
-	bits = NAME_FILE & NAME_MASK;
-	if (sqlite3_step(stmts[STMT_SELECT_NAME]) == SQLITE_ROW) {
-		bits |= sqlite3_column_int64(stmts[STMT_SELECT_NAME], 0);
-		sqlite3_reset(stmts[STMT_SELECT_NAME]);
-	}
-
-	i = 1;
-	SQL_BIND_INT64(stmts[STMT_INSERT_NAME], i, bits);
-	SQL_BIND_TEXT(stmts[STMT_INSERT_NAME], i, mlink->name);
-	SQL_BIND_INT64(stmts[STMT_INSERT_NAME], i, mlink->mpage->pageid);
-	SQL_STEP(stmts[STMT_INSERT_NAME]);
-	sqlite3_reset(stmts[STMT_INSERT_NAME]);
+	dba_page_alias(mlink->mpage->dba, mlink->name, NAME_FILE);
+	dba_page_add(mlink->mpage->dba, DBP_SECT, mlink->dsec);
+	dba_page_add(mlink->mpage->dba, DBP_SECT, mlink->fsec);
+	dba_page_add(mlink->mpage->dba, DBP_ARCH, mlink->arch);
+	dba_page_add(mlink->mpage->dba, DBP_FILE, mlink->file);
 }
 
 /*
  * Flush the current page's terms (and their bits) into the database.
- * Wrap the entire set of additions in a transaction to make sqlite be a
- * little faster.
  * Also, handle escape sequences at the last possible moment.
  */
 static void
-dbadd(struct mpage *mpage)
+dbadd(struct dba *dba, struct mpage *mpage)
 {
 	struct mlink	*mlink;
 	struct str	*key;
 	char		*cp;
+	uint64_t	 mask;
 	size_t		 i;
 	unsigned int	 slot;
 	int		 mustfree;
@@ -2116,127 +2017,106 @@ dbadd(struct mpage *mpage)
 	cp = mpage->desc;
 	i = strlen(cp);
 	mustfree = render_string(&cp, &i);
-	i = 1;
-	SQL_BIND_TEXT(stmts[STMT_INSERT_PAGE], i, cp);
-	SQL_BIND_INT(stmts[STMT_INSERT_PAGE], i, mpage->form);
-	SQL_STEP(stmts[STMT_INSERT_PAGE]);
-	mpage->pageid = sqlite3_last_insert_rowid(db);
-	sqlite3_reset(stmts[STMT_INSERT_PAGE]);
+	mpage->dba = dba_page_new(dba->pages,
+	    *mpage->arch == '\0' ? mlink->arch : mpage->arch,
+	    cp, mlink->file, mpage->form);
 	if (mustfree)
 		free(cp);
+	dba_page_add(mpage->dba, DBP_SECT, mpage->sec);
 
-	while (NULL != mlink) {
+	while (mlink != NULL) {
 		dbadd_mlink(mlink);
 		mlink = mlink->next;
 	}
-	mlink = mpage->mlinks;
 
 	for (key = ohash_first(&names, &slot); NULL != key;
 	     key = ohash_next(&names, &slot)) {
 		assert(key->mpage == mpage);
-		i = 1;
-		SQL_BIND_INT64(stmts[STMT_INSERT_NAME], i, key->mask);
-		SQL_BIND_TEXT(stmts[STMT_INSERT_NAME], i, key->key);
-		SQL_BIND_INT64(stmts[STMT_INSERT_NAME], i, mpage->pageid);
-		SQL_STEP(stmts[STMT_INSERT_NAME]);
-		sqlite3_reset(stmts[STMT_INSERT_NAME]);
+		dba_page_alias(mpage->dba, key->key, key->mask);
 		free(key);
 	}
 	for (key = ohash_first(&strings, &slot); NULL != key;
 	     key = ohash_next(&strings, &slot)) {
 		assert(key->mpage == mpage);
-		i = 1;
-		SQL_BIND_INT64(stmts[STMT_INSERT_KEY], i, key->mask);
-		SQL_BIND_TEXT(stmts[STMT_INSERT_KEY], i, key->key);
-		SQL_BIND_INT64(stmts[STMT_INSERT_KEY], i, mpage->pageid);
-		SQL_STEP(stmts[STMT_INSERT_KEY]);
-		sqlite3_reset(stmts[STMT_INSERT_KEY]);
+		i = 0;
+		for (mask = TYPE_Xr; mask <= TYPE_Lb; mask *= 2) {
+			if (key->mask & mask)
+				dba_macro_add(dba->macros, i,
+				    key->key, mpage->dba);
+			i++;
+		}
 		free(key);
 	}
 }
 
 static void
-dbprune(void)
+dbprune(struct dba *dba)
 {
-	struct mpage	*mpage;
-	struct mlink	*mlink;
-	size_t		 i;
-	unsigned int	 slot;
+	struct dba_array	*page, *files;
+	char			*file;
 
-	if (0 == nodb)
-		SQL_EXEC("BEGIN TRANSACTION");
-
-	for (mpage = ohash_first(&mpages, &slot); NULL != mpage;
-	     mpage = ohash_next(&mpages, &slot)) {
-		mlink = mpage->mlinks;
-		if (debug)
-			say(mlink->file, "Deleting from database");
-		if (nodb)
-			continue;
-		for ( ; NULL != mlink; mlink = mlink->next) {
-			i = 1;
-			SQL_BIND_TEXT(stmts[STMT_DELETE_PAGE],
-			    i, mlink->dsec);
-			SQL_BIND_TEXT(stmts[STMT_DELETE_PAGE],
-			    i, mlink->arch);
-			SQL_BIND_TEXT(stmts[STMT_DELETE_PAGE],
-			    i, mlink->name);
-			SQL_STEP(stmts[STMT_DELETE_PAGE]);
-			sqlite3_reset(stmts[STMT_DELETE_PAGE]);
+	dba_array_FOREACH(dba->pages, page) {
+		files = dba_array_get(page, DBP_FILE);
+		dba_array_FOREACH(files, file) {
+			if (*file < ' ')
+				file++;
+			if (ohash_find(&mlinks, ohash_qlookup(&mlinks,
+			    file)) != NULL) {
+				if (debug)
+					say(file, "Deleting from database");
+				dba_array_del(dba->pages);
+				break;
+			}
 		}
 	}
-
-	if (0 == nodb)
-		SQL_EXEC("END TRANSACTION");
 }
 
 /*
- * Close an existing database and its prepared statements.
- * If "real" is not set, rename the temporary file into the real one.
+ * Write the database from memory to disk.
  */
 static void
-dbclose(int real)
+dbwrite(struct dba *dba)
 {
-	size_t		 i;
+	char		 tfn[32];
 	int		 status;
 	pid_t		 child;
 
-	if (nodb)
-		return;
-
-	for (i = 0; i < STMT__MAX; i++) {
-		sqlite3_finalize(stmts[i]);
-		stmts[i] = NULL;
-	}
-
-	sqlite3_close(db);
-	db = NULL;
-
-	if (real)
-		return;
-
-	if ('\0' == *tempfilename) {
-		if (-1 == rename(MANDOC_DB "~", MANDOC_DB)) {
+	if (dba_write(MANDOC_DB "~", dba) != -1) {
+		if (rename(MANDOC_DB "~", MANDOC_DB) == -1) {
 			exitcode = (int)MANDOCLEVEL_SYSERR;
 			say(MANDOC_DB, "&rename");
+			unlink(MANDOC_DB "~");
 		}
 		return;
 	}
 
+	(void)strlcpy(tfn, "/tmp/mandocdb.XXXXXXXX", sizeof(tfn));
+	if (mkdtemp(tfn) == NULL) {
+		exitcode = (int)MANDOCLEVEL_SYSERR;
+		say("", "&%s", tfn);
+		return;
+	}
+
+	(void)strlcat(tfn, "/" MANDOC_DB, sizeof(tfn));
+	if (dba_write(tfn, dba) == -1) {
+		exitcode = (int)MANDOCLEVEL_SYSERR;
+		say(tfn, "&dba_write");
+		goto out;
+	}
+
 	switch (child = fork()) {
 	case -1:
 		exitcode = (int)MANDOCLEVEL_SYSERR;
 		say("", "&fork cmp");
 		return;
 	case 0:
-		execlp("cmp", "cmp", "-s",
-		    tempfilename, MANDOC_DB, (char *)NULL);
+		execlp("cmp", "cmp", "-s", tfn, MANDOC_DB, (char *)NULL);
 		say("", "&exec cmp");
 		exit(0);
 	default:
 		break;
 	}
-	if (-1 == waitpid(child, &status, 0)) {
+	if (waitpid(child, &status, 0) == -1) {
 		exitcode = (int)MANDOCLEVEL_SYSERR;
 		say("", "&wait cmp");
 	} else if (WIFSIGNALED(status)) {
@@ -2248,175 +2128,29 @@ dbclose(int real)
 		    "Data changed, but cannot replace database");
 	}
 
-	*strrchr(tempfilename, '/') = '\0';
+out:
+	*strrchr(tfn, '/') = '\0';
 	switch (child = fork()) {
 	case -1:
 		exitcode = (int)MANDOCLEVEL_SYSERR;
 		say("", "&fork rm");
 		return;
 	case 0:
-		execlp("rm", "rm", "-rf", tempfilename, (char *)NULL);
+		execlp("rm", "rm", "-rf", tfn, (char *)NULL);
 		say("", "&exec rm");
 		exit((int)MANDOCLEVEL_SYSERR);
 	default:
 		break;
 	}
-	if (-1 == waitpid(child, &status, 0)) {
+	if (waitpid(child, &status, 0) == -1) {
 		exitcode = (int)MANDOCLEVEL_SYSERR;
 		say("", "&wait rm");
 	} else if (WIFSIGNALED(status) || WEXITSTATUS(status)) {
 		exitcode = (int)MANDOCLEVEL_SYSERR;
-		say("", "%s: Cannot remove temporary directory",
-		    tempfilename);
+		say("", "%s: Cannot remove temporary directory", tfn);
 	}
 }
 
-/*
- * This is straightforward stuff.
- * Open a database connection to a "temporary" database, then open a set
- * of prepared statements we'll use over and over again.
- * If "real" is set, we use the existing database; if not, we truncate a
- * temporary one.
- * Must be matched by dbclose().
- */
-static int
-dbopen(int real)
-{
-	const char	*sql;
-	int		 rc, ofl;
-
-	if (nodb)
-		return 1;
-
-	*tempfilename = '\0';
-	ofl = SQLITE_OPEN_READWRITE;
-
-	if (real) {
-		rc = sqlite3_open_v2(MANDOC_DB, &db, ofl, NULL);
-		if (SQLITE_OK != rc) {
-			exitcode = (int)MANDOCLEVEL_SYSERR;
-			if (SQLITE_CANTOPEN != rc)
-				say(MANDOC_DB, "%s", sqlite3_errstr(rc));
-			return 0;
-		}
-		goto prepare_statements;
-	}
-
-	ofl |= SQLITE_OPEN_CREATE | SQLITE_OPEN_EXCLUSIVE;
-
-	remove(MANDOC_DB "~");
-	rc = sqlite3_open_v2(MANDOC_DB "~", &db, ofl, NULL);
-	if (SQLITE_OK == rc)
-		goto create_tables;
-	if (MPARSE_QUICK & mparse_options) {
-		exitcode = (int)MANDOCLEVEL_SYSERR;
-		say(MANDOC_DB "~", "%s", sqlite3_errstr(rc));
-		return 0;
-	}
-
-	(void)strlcpy(tempfilename, "/tmp/mandocdb.XXXXXX",
-	    sizeof(tempfilename));
-	if (NULL == mkdtemp(tempfilename)) {
-		exitcode = (int)MANDOCLEVEL_SYSERR;
-		say("", "&%s", tempfilename);
-		return 0;
-	}
-	(void)strlcat(tempfilename, "/" MANDOC_DB,
-	    sizeof(tempfilename));
-	rc = sqlite3_open_v2(tempfilename, &db, ofl, NULL);
-	if (SQLITE_OK != rc) {
-		exitcode = (int)MANDOCLEVEL_SYSERR;
-		say("", "%s: %s", tempfilename, sqlite3_errstr(rc));
-		return 0;
-	}
-
-create_tables:
-	sql = "CREATE TABLE \"mpages\" (\n"
-	      " \"desc\" TEXT NOT NULL,\n"
-	      " \"form\" INTEGER NOT NULL,\n"
-	      " \"pageid\" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL\n"
-	      ");\n"
-	      "\n"
-	      "CREATE TABLE \"mlinks\" (\n"
-	      " \"sec\" TEXT NOT NULL,\n"
-	      " \"arch\" TEXT NOT NULL,\n"
-	      " \"name\" TEXT NOT NULL,\n"
-	      " \"pageid\" INTEGER NOT NULL REFERENCES mpages(pageid) "
-		"ON DELETE CASCADE\n"
-	      ");\n"
-	      "CREATE INDEX mlinks_pageid_idx ON mlinks (pageid);\n"
-	      "\n"
-	      "CREATE TABLE \"names\" (\n"
-	      " \"bits\" INTEGER NOT NULL,\n"
-	      " \"name\" TEXT NOT NULL,\n"
-	      " \"pageid\" INTEGER NOT NULL REFERENCES mpages(pageid) "
-		"ON DELETE CASCADE,\n"
-	      " UNIQUE (\"name\", \"pageid\") ON CONFLICT REPLACE\n"
-	      ");\n"
-	      "\n"
-	      "CREATE TABLE \"keys\" (\n"
-	      " \"bits\" INTEGER NOT NULL,\n"
-	      " \"key\" TEXT NOT NULL,\n"
-	      " \"pageid\" INTEGER NOT NULL REFERENCES mpages(pageid) "
-		"ON DELETE CASCADE\n"
-	      ");\n"
-	      "CREATE INDEX keys_pageid_idx ON keys (pageid);\n";
-
-	if (SQLITE_OK != sqlite3_exec(db, sql, NULL, NULL, NULL)) {
-		exitcode = (int)MANDOCLEVEL_SYSERR;
-		say(MANDOC_DB, "%s", sqlite3_errmsg(db));
-		sqlite3_close(db);
-		return 0;
-	}
-
-prepare_statements:
-	if (SQLITE_OK != sqlite3_exec(db,
-	    "PRAGMA foreign_keys = ON", NULL, NULL, NULL)) {
-		exitcode = (int)MANDOCLEVEL_SYSERR;
-		say(MANDOC_DB, "PRAGMA foreign_keys: %s",
-		    sqlite3_errmsg(db));
-		sqlite3_close(db);
-		return 0;
-	}
-
-	sql = "DELETE FROM mpages WHERE pageid IN "
-		"(SELECT pageid FROM mlinks WHERE "
-		"sec=? AND arch=? AND name=?)";
-	sqlite3_prepare_v2(db, sql, -1, &stmts[STMT_DELETE_PAGE], NULL);
-	sql = "INSERT INTO mpages "
-		"(desc,form) VALUES (?,?)";
-	sqlite3_prepare_v2(db, sql, -1, &stmts[STMT_INSERT_PAGE], NULL);
-	sql = "INSERT INTO mlinks "
-		"(sec,arch,name,pageid) VALUES (?,?,?,?)";
-	sqlite3_prepare_v2(db, sql, -1, &stmts[STMT_INSERT_LINK], NULL);
-	sql = "SELECT bits FROM names where pageid = ?";
-	sqlite3_prepare_v2(db, sql, -1, &stmts[STMT_SELECT_NAME], NULL);
-	sql = "INSERT INTO names "
-		"(bits,name,pageid) VALUES (?,?,?)";
-	sqlite3_prepare_v2(db, sql, -1, &stmts[STMT_INSERT_NAME], NULL);
-	sql = "INSERT INTO keys "
-		"(bits,key,pageid) VALUES (?,?,?)";
-	sqlite3_prepare_v2(db, sql, -1, &stmts[STMT_INSERT_KEY], NULL);
-
-#ifndef __APPLE__
-	/*
-	 * When opening a new database, we can turn off
-	 * synchronous mode for much better performance.
-	 */
-
-	if (real && SQLITE_OK != sqlite3_exec(db,
-	    "PRAGMA synchronous = OFF", NULL, NULL, NULL)) {
-		exitcode = (int)MANDOCLEVEL_SYSERR;
-		say(MANDOC_DB, "PRAGMA synchronous: %s",
-		    sqlite3_errmsg(db));
-		sqlite3_close(db);
-		return 0;
-	}
-#endif
-
-	return 1;
-}
-
 static int
 set_basedir(const char *targetdir, int report_baddir)
 {
diff --git a/manpath.c b/manpath.c
index 83c329ec9bbb..008c59392379 100644
--- a/manpath.c
+++ b/manpath.c
@@ -1,4 +1,4 @@
-/*	$Id: manpath.c,v 1.30 2016/05/28 13:44:13 schwarze Exp $	*/
+/*	$Id: manpath.c,v 1.31 2016/07/19 22:40:33 schwarze Exp $	*/
 /*
  * Copyright (c) 2011, 2014, 2015 Ingo Schwarze 
  * Copyright (c) 2011 Kristaps Dzonsons 
@@ -32,9 +32,7 @@
 #include "mandoc_aux.h"
 #include "manconf.h"
 
-#if !HAVE_MANPATH
 static	void	 manconf_file(struct manconf *, const char *);
-#endif
 static	void	 manpath_add(struct manpaths *, const char *, int);
 static	void	 manpath_parseline(struct manpaths *, char *, int);
 
@@ -43,52 +41,6 @@ void
 manconf_parse(struct manconf *conf, const char *file,
 		char *defp, char *auxp)
 {
-#if HAVE_MANPATH
-	char		 cmd[(PATH_MAX * 3) + 20];
-	FILE		*stream;
-	char		*buf;
-	size_t		 sz, bsz;
-
-	strlcpy(cmd, "manpath", sizeof(cmd));
-	if (file) {
-		strlcat(cmd, " -C ", sizeof(cmd));
-		strlcat(cmd, file, sizeof(cmd));
-	}
-	if (auxp) {
-		strlcat(cmd, " -m ", sizeof(cmd));
-		strlcat(cmd, auxp, sizeof(cmd));
-	}
-	if (defp) {
-		strlcat(cmd, " -M ", sizeof(cmd));
-		strlcat(cmd, defp, sizeof(cmd));
-	}
-
-	/* Open manpath(1).  Ignore errors. */
-
-	stream = popen(cmd, "r");
-	if (NULL == stream)
-		return;
-
-	buf = NULL;
-	bsz = 0;
-
-	/* Read in as much output as we can. */
-
-	do {
-		buf = mandoc_realloc(buf, bsz + 1024);
-		sz = fread(buf + bsz, 1, 1024, stream);
-		bsz += sz;
-	} while (sz > 0);
-
-	if ( ! ferror(stream) && feof(stream) &&
-			bsz && '\n' == buf[bsz - 1]) {
-		buf[bsz - 1] = '\0';
-		manpath_parseline(&conf->manpath, buf, 1);
-	}
-
-	free(buf);
-	pclose(stream);
-#else
 	char		*insert;
 
 	/* Always prepend -m. */
@@ -137,7 +89,6 @@ manconf_parse(struct manconf *conf, const char *file,
 
 	/* MANPATH overrides man.conf(5) completely. */
 	manpath_parseline(&conf->manpath, defp, 0);
-#endif
 }
 
 /*
@@ -204,7 +155,6 @@ manconf_free(struct manconf *conf)
 	free(conf->output.style);
 }
 
-#if !HAVE_MANPATH
 static void
 manconf_file(struct manconf *conf, const char *file)
 {
@@ -270,7 +220,6 @@ manconf_file(struct manconf *conf, const char *file)
 	if (*manpath_default != '\0')
 		manpath_parseline(&conf->manpath, manpath_default, 0);
 }
-#endif
 
 void
 manconf_output(struct manoutput *conf, const char *cp)
diff --git a/mansearch.c b/mansearch.c
index 1ab879d7f211..6e689bd35862 100644
--- a/mansearch.c
+++ b/mansearch.c
@@ -1,7 +1,7 @@
-/*	$Id: mansearch.c,v 1.65 2016/07/09 15:24:19 schwarze Exp $ */
+/*	$OpenBSD: mansearch.c,v 1.50 2016/07/09 15:23:36 schwarze Exp $ */
 /*
  * Copyright (c) 2012 Kristaps Dzonsons 
- * Copyright (c) 2013, 2014, 2015 Ingo Schwarze 
+ * Copyright (c) 2013, 2014, 2015, 2016 Ingo Schwarze 
  *
  * Permission to use, copy, modify, and distribute this software for any
  * purpose with or without fee is hereby granted, provided that the above
@@ -36,143 +36,69 @@
 #include 
 #include 
 
-#include 
-#ifndef SQLITE_DETERMINISTIC
-#define SQLITE_DETERMINISTIC 0
-#endif
-
 #include "mandoc.h"
 #include "mandoc_aux.h"
 #include "mandoc_ohash.h"
 #include "manconf.h"
 #include "mansearch.h"
-
-extern int mansearch_keymax;
-extern const char *const mansearch_keynames[];
-
-#define	SQL_BIND_TEXT(_db, _s, _i, _v) \
-	do { if (SQLITE_OK != sqlite3_bind_text \
-		((_s), (_i)++, (_v), -1, SQLITE_STATIC)) \
-		errx((int)MANDOCLEVEL_SYSERR, "%s", sqlite3_errmsg((_db))); \
-	} while (0)
-#define	SQL_BIND_INT64(_db, _s, _i, _v) \
-	do { if (SQLITE_OK != sqlite3_bind_int64 \
-		((_s), (_i)++, (_v))) \
-		errx((int)MANDOCLEVEL_SYSERR, "%s", sqlite3_errmsg((_db))); \
-	} while (0)
-#define	SQL_BIND_BLOB(_db, _s, _i, _v) \
-	do { if (SQLITE_OK != sqlite3_bind_blob \
-		((_s), (_i)++, (&_v), sizeof(_v), SQLITE_STATIC)) \
-		errx((int)MANDOCLEVEL_SYSERR, "%s", sqlite3_errmsg((_db))); \
-	} while (0)
+#include "dbm.h"
 
 struct	expr {
-	regex_t		 regexp;  /* compiled regexp, if applicable */
-	const char	*substr;  /* to search for, if applicable */
-	struct expr	*next;    /* next in sequence */
-	uint64_t	 bits;    /* type-mask */
-	int		 equal;   /* equality, not subsring match */
-	int		 open;    /* opening parentheses before */
-	int		 and;	  /* logical AND before */
-	int		 close;   /* closing parentheses after */
+	/* Used for terms: */
+	struct dbm_match match;   /* Match type and expression. */
+	uint64_t	 bits;    /* Type mask. */
+	/* Used for OR and AND groups: */
+	struct expr	*next;    /* Next child in the parent group. */
+	struct expr	*child;   /* First child in this group. */
+	enum { EXPR_TERM, EXPR_OR, EXPR_AND } type;
 };
 
-struct	match {
-	uint64_t	 pageid; /* identifier in database */
-	uint64_t	 bits; /* name type mask */
-	char		*desc; /* manual page description */
-	int		 form; /* bit field: formatted, zipped? */
+const char *const mansearch_keynames[KEY_MAX] = {
+	"arch",	"sec",	"Xr",	"Ar",	"Fa",	"Fl",	"Dv",	"Fn",
+	"Ic",	"Pa",	"Cm",	"Li",	"Em",	"Cd",	"Va",	"Ft",
+	"Tn",	"Er",	"Ev",	"Sy",	"Sh",	"In",	"Ss",	"Ox",
+	"An",	"Mt",	"St",	"Bx",	"At",	"Nx",	"Fx",	"Lk",
+	"Ms",	"Bsx",	"Dx",	"Rs",	"Vt",	"Lb",	"Nm",	"Nd"
 };
 
-static	void		 buildnames(const struct mansearch *,
-				struct manpage *, sqlite3 *,
-				sqlite3_stmt *, uint64_t,
-				const char *, int form);
-static	char		*buildoutput(sqlite3 *, sqlite3_stmt *,
-				 uint64_t, uint64_t);
+
+static	struct ohash	*manmerge(struct expr *, struct ohash *);
+static	struct ohash	*manmerge_term(struct expr *, struct ohash *);
+static	struct ohash	*manmerge_or(struct expr *, struct ohash *);
+static	struct ohash	*manmerge_and(struct expr *, struct ohash *);
+static	char		*buildnames(const struct dbm_page *);
+static	char		*buildoutput(size_t, int32_t);
+static	size_t		 lstlen(const char *);
+static	void		 lstcat(char *, size_t *, const char *);
+static	int		 lstmatch(const char *, const char *);
 static	struct expr	*exprcomp(const struct mansearch *,
-				int, char *[]);
+				int, char *[], int *);
+static	struct expr	*expr_and(const struct mansearch *,
+				int, char *[], int *);
+static	struct expr	*exprterm(const struct mansearch *,
+				int, char *[], int *);
 static	void		 exprfree(struct expr *);
-static	struct expr	*exprterm(const struct mansearch *, char *, int);
 static	int		 manpage_compare(const void *, const void *);
-static	void		 sql_append(char **sql, size_t *sz,
-				const char *newstr, int count);
-static	void		 sql_match(sqlite3_context *context,
-				int argc, sqlite3_value **argv);
-static	void		 sql_regexp(sqlite3_context *context,
-				int argc, sqlite3_value **argv);
-static	char		*sql_statement(const struct expr *);
 
 
-int
-mansearch_setup(int start)
-{
-	static void	*pagecache;
-	int		 c;
-
-#define	PC_PAGESIZE	1280
-#define	PC_NUMPAGES	256
-
-	if (start) {
-		if (NULL != pagecache) {
-			warnx("pagecache already enabled");
-			return (int)MANDOCLEVEL_BADARG;
-		}
-
-		pagecache = mmap(NULL, PC_PAGESIZE * PC_NUMPAGES,
-		    PROT_READ | PROT_WRITE,
-		    MAP_SHARED | MAP_ANON, -1, 0);
-
-		if (MAP_FAILED == pagecache) {
-			warn("mmap");
-			pagecache = NULL;
-			return (int)MANDOCLEVEL_SYSERR;
-		}
-
-		c = sqlite3_config(SQLITE_CONFIG_PAGECACHE,
-		    pagecache, PC_PAGESIZE, PC_NUMPAGES);
-
-		if (SQLITE_OK == c)
-			return (int)MANDOCLEVEL_OK;
-
-		warnx("pagecache: %s", sqlite3_errstr(c));
-
-	} else if (NULL == pagecache) {
-		warnx("pagecache missing");
-		return (int)MANDOCLEVEL_BADARG;
-	}
-
-	if (-1 == munmap(pagecache, PC_PAGESIZE * PC_NUMPAGES)) {
-		warn("munmap");
-		pagecache = NULL;
-		return (int)MANDOCLEVEL_SYSERR;
-	}
-
-	pagecache = NULL;
-	return (int)MANDOCLEVEL_OK;
-}
-
 int
 mansearch(const struct mansearch *search,
 		const struct manpaths *paths,
 		int argc, char *argv[],
 		struct manpage **res, size_t *sz)
 {
-	int64_t		 pageid;
-	uint64_t	 outbit, iterbit;
 	char		 buf[PATH_MAX];
-	char		*sql;
+	struct dbm_res	*rp;
+	struct expr	*e;
+	struct dbm_page	*page;
 	struct manpage	*mpage;
-	struct expr	*e, *ep;
-	sqlite3		*db;
-	sqlite3_stmt	*s, *s2;
-	struct match	*mp;
-	struct ohash	 htab;
-	unsigned int	 idx;
-	size_t		 i, j, cur, maxres;
-	int		 c, chdir_status, getcwd_status, indexbit;
+	struct ohash	*htab;
+	size_t		 cur, i, maxres, outkey;
+	unsigned int	 slot;
+	int		 argi, chdir_status, getcwd_status, im;
 
-	if (argc == 0 || (e = exprcomp(search, argc, argv)) == NULL) {
+	argi = 0;
+	if ((e = exprcomp(search, argc, argv, &argi)) == NULL) {
 		*sz = 0;
 		return 0;
 	}
@@ -180,19 +106,14 @@ mansearch(const struct mansearch *search,
 	cur = maxres = 0;
 	*res = NULL;
 
-	if (NULL != search->outkey) {
-		outbit = TYPE_Nd;
-		for (indexbit = 0, iterbit = 1;
-		     indexbit < mansearch_keymax;
-		     indexbit++, iterbit <<= 1) {
+	outkey = KEY_Nd;
+	if (search->outkey != NULL)
+		for (im = 0; im < KEY_MAX; im++)
 			if (0 == strcasecmp(search->outkey,
-			    mansearch_keynames[indexbit])) {
-				outbit = iterbit;
+			    mansearch_keynames[im])) {
+				outkey = im;
 				break;
 			}
-		}
-	} else
-		outbit = 0;
 
 	/*
 	 * Remember the original working directory, if possible.
@@ -208,8 +129,6 @@ mansearch(const struct mansearch *search,
 	} else
 		getcwd_status = 1;
 
-	sql = sql_statement(e);
-
 	/*
 	 * Loop over the directories (containing databases) for us to
 	 * search.
@@ -235,123 +154,48 @@ mansearch(const struct mansearch *search,
 		}
 		chdir_status = 1;
 
-		c = sqlite3_open_v2(MANDOC_DB, &db,
-		    SQLITE_OPEN_READONLY, NULL);
-
-		if (SQLITE_OK != c) {
+		if (dbm_open(MANDOC_DB) == -1) {
 			warn("%s/%s", paths->paths[i], MANDOC_DB);
-			sqlite3_close(db);
 			continue;
 		}
 
-		/*
-		 * Define the SQL functions for substring
-		 * and regular expression matching.
-		 */
-
-		c = sqlite3_create_function(db, "match", 2,
-		    SQLITE_UTF8 | SQLITE_DETERMINISTIC,
-		    NULL, sql_match, NULL, NULL);
-		assert(SQLITE_OK == c);
-		c = sqlite3_create_function(db, "regexp", 2,
-		    SQLITE_UTF8 | SQLITE_DETERMINISTIC,
-		    NULL, sql_regexp, NULL, NULL);
-		assert(SQLITE_OK == c);
-
-		j = 1;
-		c = sqlite3_prepare_v2(db, sql, -1, &s, NULL);
-		if (SQLITE_OK != c)
-			errx((int)MANDOCLEVEL_SYSERR,
-			    "%s", sqlite3_errmsg(db));
-
-		for (ep = e; NULL != ep; ep = ep->next) {
-			if (NULL == ep->substr) {
-				SQL_BIND_BLOB(db, s, j, ep->regexp);
-			} else
-				SQL_BIND_TEXT(db, s, j, ep->substr);
-			if (0 == ((TYPE_Nd | TYPE_Nm) & ep->bits))
-				SQL_BIND_INT64(db, s, j, ep->bits);
+		if ((htab = manmerge(e, NULL)) == NULL) {
+			dbm_close();
+			continue;
 		}
 
-		mandoc_ohash_init(&htab, 4, offsetof(struct match, pageid));
+		for (rp = ohash_first(htab, &slot); rp != NULL;
+		    rp = ohash_next(htab, &slot)) {
+			page = dbm_page_get(rp->page);
 
-		/*
-		 * Hash each entry on its [unique] document identifier.
-		 * This is a uint64_t.
-		 * Instead of using a hash function, simply convert the
-		 * uint64_t to a uint32_t, the hash value's type.
-		 * This gives good performance and preserves the
-		 * distribution of buckets in the table.
-		 */
-		while (SQLITE_ROW == (c = sqlite3_step(s))) {
-			pageid = sqlite3_column_int64(s, 2);
-			idx = ohash_lookup_memory(&htab,
-			    (char *)&pageid, sizeof(uint64_t),
-			    (uint32_t)pageid);
-
-			if (NULL != ohash_find(&htab, idx))
+			if (lstmatch(search->sec, page->sect) == 0 ||
+			    lstmatch(search->arch, page->arch) == 0)
 				continue;
 
-			mp = mandoc_calloc(1, sizeof(struct match));
-			mp->pageid = pageid;
-			mp->form = sqlite3_column_int(s, 1);
-			mp->bits = sqlite3_column_int64(s, 3);
-			if (TYPE_Nd == outbit)
-				mp->desc = mandoc_strdup((const char *)
-				    sqlite3_column_text(s, 0));
-			ohash_insert(&htab, idx, mp);
-		}
-
-		if (SQLITE_DONE != c)
-			warnx("%s", sqlite3_errmsg(db));
-
-		sqlite3_finalize(s);
-
-		c = sqlite3_prepare_v2(db,
-		    "SELECT sec, arch, name, pageid FROM mlinks "
-		    "WHERE pageid=? ORDER BY sec, arch, name",
-		    -1, &s, NULL);
-		if (SQLITE_OK != c)
-			errx((int)MANDOCLEVEL_SYSERR,
-			    "%s", sqlite3_errmsg(db));
-
-		c = sqlite3_prepare_v2(db,
-		    "SELECT bits, key, pageid FROM keys "
-		    "WHERE pageid=? AND bits & ?",
-		    -1, &s2, NULL);
-		if (SQLITE_OK != c)
-			errx((int)MANDOCLEVEL_SYSERR,
-			    "%s", sqlite3_errmsg(db));
-
-		for (mp = ohash_first(&htab, &idx);
-				NULL != mp;
-				mp = ohash_next(&htab, &idx)) {
 			if (cur + 1 > maxres) {
 				maxres += 1024;
 				*res = mandoc_reallocarray(*res,
-				    maxres, sizeof(struct manpage));
+				    maxres, sizeof(**res));
 			}
 			mpage = *res + cur;
+			mandoc_asprintf(&mpage->file, "%s/%s",
+			    paths->paths[i], page->file + 1);
+			mpage->names = buildnames(page);
+			mpage->output = (int)outkey == KEY_Nd ?
+			    mandoc_strdup(page->desc) :
+			    buildoutput(outkey, page->addr);
 			mpage->ipath = i;
-			mpage->bits = mp->bits;
-			mpage->sec = 10;
-			mpage->form = mp->form;
-			buildnames(search, mpage, db, s, mp->pageid,
-			    paths->paths[i], mp->form);
-			if (mpage->names != NULL) {
-				mpage->output = TYPE_Nd & outbit ?
-				    mp->desc : outbit ?
-				    buildoutput(db, s2, mp->pageid, outbit) :
-				    NULL;
-				cur++;
-			}
-			free(mp);
+			mpage->bits = rp->bits;
+			mpage->sec = *page->sect - '0';
+			if (mpage->sec < 0 || mpage->sec > 9)
+				mpage->sec = 10;
+			mpage->form = *page->file;
+			free(rp);
+			cur++;
 		}
-
-		sqlite3_finalize(s);
-		sqlite3_finalize(s2);
-		sqlite3_close(db);
-		ohash_delete(&htab);
+		ohash_delete(htab);
+		free(htab);
+		dbm_close();
 
 		/*
 		 * In man(1) mode, prefer matches in earlier trees
@@ -365,11 +209,169 @@ mansearch(const struct mansearch *search,
 	if (chdir_status && getcwd_status && chdir(buf) == -1)
 		warn("%s", buf);
 	exprfree(e);
-	free(sql);
 	*sz = cur;
 	return 1;
 }
 
+/*
+ * Merge the results for the expression tree rooted at e
+ * into the the result list htab.
+ */
+static struct ohash *
+manmerge(struct expr *e, struct ohash *htab)
+{
+	switch (e->type) {
+	case EXPR_TERM:
+		return manmerge_term(e, htab);
+	case EXPR_OR:
+		return manmerge_or(e->child, htab);
+	case EXPR_AND:
+		return manmerge_and(e->child, htab);
+	default:
+		abort();
+	}
+}
+
+static struct ohash *
+manmerge_term(struct expr *e, struct ohash *htab)
+{
+	struct dbm_res	 res, *rp;
+	uint64_t	 ib;
+	unsigned int	 slot;
+	int		 im;
+
+	if (htab == NULL) {
+		htab = mandoc_malloc(sizeof(*htab));
+		mandoc_ohash_init(htab, 4, offsetof(struct dbm_res, page));
+	}
+
+	for (im = 0, ib = 1; im < KEY_MAX; im++, ib <<= 1) {
+		if ((e->bits & ib) == 0)
+			continue;
+
+		switch (ib) {
+		case TYPE_arch:
+			dbm_page_byarch(&e->match);
+			break;
+		case TYPE_sec:
+			dbm_page_bysect(&e->match);
+			break;
+		case TYPE_Nm:
+			dbm_page_byname(&e->match);
+			break;
+		case TYPE_Nd:
+			dbm_page_bydesc(&e->match);
+			break;
+		default:
+			dbm_page_bymacro(im - 2, &e->match);
+			break;
+		}
+
+		/*
+		 * When hashing for deduplication, use the unique
+		 * page ID itself instead of a hash function;
+		 * that is quite efficient.
+		 */
+
+		for (;;) {
+			res = dbm_page_next();
+			if (res.page == -1)
+				break;
+			slot = ohash_lookup_memory(htab,
+			    (char *)&res, sizeof(res.page), res.page);
+			if ((rp = ohash_find(htab, slot)) != NULL) {
+				rp->bits |= res.bits;
+				continue;
+			}
+			rp = mandoc_malloc(sizeof(*rp));
+			*rp = res;
+			ohash_insert(htab, slot, rp);
+		}
+	}
+	return htab;
+}
+
+static struct ohash *
+manmerge_or(struct expr *e, struct ohash *htab)
+{
+	while (e != NULL) {
+		htab = manmerge(e, htab);
+		e = e->next;
+	}
+	return htab;
+}
+
+static struct ohash *
+manmerge_and(struct expr *e, struct ohash *htab)
+{
+	struct ohash	*hand, *h1, *h2;
+	struct dbm_res	*res;
+	unsigned int	 slot1, slot2;
+
+	/* Evaluate the first term of the AND clause. */
+
+	hand = manmerge(e, NULL);
+
+	while ((e = e->next) != NULL) {
+
+		/* Evaluate the next term and prepare for ANDing. */
+
+		h2 = manmerge(e, NULL);
+		if (ohash_entries(h2) < ohash_entries(hand)) {
+			h1 = h2;
+			h2 = hand;
+		} else
+			h1 = hand;
+		hand = mandoc_malloc(sizeof(*hand));
+		mandoc_ohash_init(hand, 4, offsetof(struct dbm_res, page));
+
+		/* Keep all pages that are in both result sets. */
+
+		for (res = ohash_first(h1, &slot1); res != NULL;
+		    res = ohash_next(h1, &slot1)) {
+			if (ohash_find(h2, ohash_lookup_memory(h2,
+			    (char *)res, sizeof(res->page),
+			    res->page)) == NULL)
+				free(res);
+			else
+				ohash_insert(hand, ohash_lookup_memory(hand,
+				    (char *)res, sizeof(res->page),
+				    res->page), res);
+		}
+
+		/* Discard the merged results. */
+
+		for (res = ohash_first(h2, &slot2); res != NULL;
+		    res = ohash_next(h2, &slot2))
+			free(res);
+		ohash_delete(h2);
+		free(h2);
+		ohash_delete(h1);
+		free(h1);
+	}
+
+	/* Merge the result of the AND into htab. */
+
+	if (htab == NULL)
+		return hand;
+
+	for (res = ohash_first(hand, &slot1); res != NULL;
+	    res = ohash_next(hand, &slot1)) {
+		slot2 = ohash_lookup_memory(htab,
+		    (char *)res, sizeof(res->page), res->page);
+		if (ohash_find(htab, slot2) == NULL)
+			ohash_insert(htab, slot2, res);
+		else
+			free(res);
+	}
+
+	/* Discard the merged result. */
+
+	ohash_delete(hand);
+	free(hand);
+	return htab;
+}
+
 void
 mansearch_free(struct manpage *res, size_t sz)
 {
@@ -396,457 +398,354 @@ manpage_compare(const void *vp1, const void *vp2)
 	    strcasecmp(mp1->names, mp2->names);
 }
 
-static void
-buildnames(const struct mansearch *search, struct manpage *mpage,
-		sqlite3 *db, sqlite3_stmt *s,
-		uint64_t pageid, const char *path, int form)
+static char *
+buildnames(const struct dbm_page *page)
 {
-	glob_t		 globinfo;
-	char		*firstname, *newnames, *prevsec, *prevarch;
-	const char	*oldnames, *sep1, *name, *sec, *sep2, *arch, *fsec;
-	size_t		 i;
-	int		 c, globres;
+	char	*buf;
+	size_t	 i, sz;
 
-	mpage->file = NULL;
-	mpage->names = NULL;
-	firstname = prevsec = prevarch = NULL;
-	i = 1;
-	SQL_BIND_INT64(db, s, i, pageid);
-	while (SQLITE_ROW == (c = sqlite3_step(s))) {
-
-		/* Decide whether we already have some names. */
-
-		if (NULL == mpage->names) {
-			oldnames = "";
-			sep1 = "";
-		} else {
-			oldnames = mpage->names;
-			sep1 = ", ";
-		}
-
-		/* Fetch the next name, rejecting sec/arch mismatches. */
-
-		sec = (const char *)sqlite3_column_text(s, 0);
-		if (search->sec != NULL && strcasecmp(sec, search->sec))
-			continue;
-		arch = (const char *)sqlite3_column_text(s, 1);
-		if (search->arch != NULL && *arch != '\0' &&
-		    strcasecmp(arch, search->arch))
-			continue;
-		name = (const char *)sqlite3_column_text(s, 2);
-
-		/* Remember the first section found. */
-
-		if (9 < mpage->sec && '1' <= *sec && '9' >= *sec)
-			mpage->sec = (*sec - '1') + 1;
-
-		/* If the section changed, append the old one. */
-
-		if (NULL != prevsec &&
-		    (strcmp(sec, prevsec) ||
-		     strcmp(arch, prevarch))) {
-			sep2 = '\0' == *prevarch ? "" : "/";
-			mandoc_asprintf(&newnames, "%s(%s%s%s)",
-			    oldnames, prevsec, sep2, prevarch);
-			free(mpage->names);
-			oldnames = mpage->names = newnames;
-			free(prevsec);
-			free(prevarch);
-			prevsec = prevarch = NULL;
-		}
-
-		/* Save the new section, to append it later. */
-
-		if (NULL == prevsec) {
-			prevsec = mandoc_strdup(sec);
-			prevarch = mandoc_strdup(arch);
-		}
-
-		/* Append the new name. */
-
-		mandoc_asprintf(&newnames, "%s%s%s",
-		    oldnames, sep1, name);
-		free(mpage->names);
-		mpage->names = newnames;
-
-		/* Also save the first file name encountered. */
-
-		if (mpage->file != NULL)
-			continue;
-
-		if (form & FORM_SRC) {
-			sep1 = "man";
-			fsec = sec;
-		} else {
-			sep1 = "cat";
-			fsec = "0";
-		}
-		sep2 = *arch == '\0' ? "" : "/";
-		mandoc_asprintf(&mpage->file, "%s/%s%s%s%s/%s.%s",
-		    path, sep1, sec, sep2, arch, name, fsec);
-		if (access(mpage->file, R_OK) != -1)
-			continue;
-
-		/* Handle unusual file name extensions. */
-
-		if (firstname == NULL)
-			firstname = mpage->file;
-		else
-			free(mpage->file);
-		mandoc_asprintf(&mpage->file, "%s/%s%s%s%s/%s.*",
-		    path, sep1, sec, sep2, arch, name);
-		globres = glob(mpage->file, 0, NULL, &globinfo);
-		free(mpage->file);
-		mpage->file = globres ? NULL :
-		    mandoc_strdup(*globinfo.gl_pathv);
-		globfree(&globinfo);
+	sz = lstlen(page->name) + 1 + lstlen(page->sect) +
+	    (page->arch == NULL ? 0 : 1 + lstlen(page->arch)) + 2;
+	buf = mandoc_malloc(sz);
+	i = 0;
+	lstcat(buf, &i, page->name);
+	buf[i++] = '(';
+	lstcat(buf, &i, page->sect);
+	if (page->arch != NULL) {
+		buf[i++] = '/';
+		lstcat(buf, &i, page->arch);
 	}
-	if (c != SQLITE_DONE)
-		warnx("%s", sqlite3_errmsg(db));
-	sqlite3_reset(s);
+	buf[i++] = ')';
+	buf[i++] = '\0';
+	assert(i == sz);
+	return buf;
+}
 
-	/* If none of the files is usable, use the first name. */
+/*
+ * Count the buffer space needed to print the NUL-terminated
+ * list of NUL-terminated strings, when printing two separator
+ * characters between strings.
+ */
+static size_t
+lstlen(const char *cp)
+{
+	size_t	 sz;
 
-	if (mpage->file == NULL)
-		mpage->file = firstname;
-	else if (mpage->file != firstname)
-		free(firstname);
+	for (sz = 0;; sz++) {
+		if (cp[0] == '\0') {
+			if (cp[1] == '\0')
+				break;
+			sz++;
+		} else if (cp[0] < ' ')
+			sz--;
+		cp++;
+	}
+	return sz;
+}
 
-	/* Append one final section to the names. */
-
-	if (prevsec != NULL) {
-		sep2 = *prevarch == '\0' ? "" : "/";
-		mandoc_asprintf(&newnames, "%s(%s%s%s)",
-		    mpage->names, prevsec, sep2, prevarch);
-		free(mpage->names);
-		mpage->names = newnames;
-		free(prevsec);
-		free(prevarch);
+/*
+ * Print the NUL-terminated list of NUL-terminated strings
+ * into the buffer, seperating strings with a comma and a blank.
+ */
+static void
+lstcat(char *buf, size_t *i, const char *cp)
+{
+	for (;;) {
+		if (cp[0] == '\0') {
+			if (cp[1] == '\0')
+				break;
+			buf[(*i)++] = ',';
+			buf[(*i)++] = ' ';
+		} else if (cp[0] >= ' ')
+			buf[(*i)++] = cp[0];
+		cp++;
 	}
 }
 
-static char *
-buildoutput(sqlite3 *db, sqlite3_stmt *s, uint64_t pageid, uint64_t outbit)
+/*
+ * Return 1 if the string *want occurs in any of the strings
+ * in the NUL-terminated string list *have, or 0 otherwise.
+ * If either argument is NULL or empty, assume no filtering
+ * is desired and return 1.
+ */
+static int
+lstmatch(const char *want, const char *have)
 {
-	char		*output, *newoutput;
-	const char	*oldoutput, *sep1, *data;
-	size_t		 i;
-	int		 c;
+        if (want == NULL || have == NULL || *have == '\0')
+                return 1;
+        while (*have != '\0') {
+                if (strcasestr(have, want) != NULL)
+                        return 1;
+                have = strchr(have, '\0') + 1;
+        }
+        return 0;
+}
+
+/*
+ * Build a list of values taken by the macro im
+ * in the manual page with big-endian address addr.
+ */
+static char *
+buildoutput(size_t im, int32_t addr)
+{
+	const char	*oldoutput, *sep;
+	char		*output, *newoutput, *value;
 
 	output = NULL;
-	i = 1;
-	SQL_BIND_INT64(db, s, i, pageid);
-	SQL_BIND_INT64(db, s, i, outbit);
-	while (SQLITE_ROW == (c = sqlite3_step(s))) {
-		if (NULL == output) {
+	dbm_macro_bypage(im - 2, addr);
+	while ((value = dbm_macro_next()) != NULL) {
+		if (output == NULL) {
 			oldoutput = "";
-			sep1 = "";
+			sep = "";
 		} else {
 			oldoutput = output;
-			sep1 = " # ";
+			sep = " # ";
 		}
-		data = (const char *)sqlite3_column_text(s, 1);
-		mandoc_asprintf(&newoutput, "%s%s%s",
-		    oldoutput, sep1, data);
+		mandoc_asprintf(&newoutput, "%s%s%s", oldoutput, sep, value);
 		free(output);
 		output = newoutput;
 	}
-	if (SQLITE_DONE != c)
-		warnx("%s", sqlite3_errmsg(db));
-	sqlite3_reset(s);
 	return output;
 }
 
-/*
- * Implement substring match as an application-defined SQL function.
- * Using the SQL LIKE or GLOB operators instead would be a bad idea
- * because that would require escaping metacharacters in the string
- * being searched for.
- */
-static void
-sql_match(sqlite3_context *context, int argc, sqlite3_value **argv)
-{
-
-	assert(2 == argc);
-	sqlite3_result_int(context, NULL != strcasestr(
-	    (const char *)sqlite3_value_text(argv[1]),
-	    (const char *)sqlite3_value_text(argv[0])));
-}
-
-/*
- * Implement regular expression match
- * as an application-defined SQL function.
- */
-static void
-sql_regexp(sqlite3_context *context, int argc, sqlite3_value **argv)
-{
-
-	assert(2 == argc);
-	sqlite3_result_int(context, !regexec(
-	    (regex_t *)sqlite3_value_blob(argv[0]),
-	    (const char *)sqlite3_value_text(argv[1]),
-	    0, NULL, 0));
-}
-
-static void
-sql_append(char **sql, size_t *sz, const char *newstr, int count)
-{
-	size_t		 newsz;
-
-	newsz = 1 < count ? (size_t)count : strlen(newstr);
-	*sql = mandoc_realloc(*sql, *sz + newsz + 1);
-	if (1 < count)
-		memset(*sql + *sz, *newstr, (size_t)count);
-	else
-		memcpy(*sql + *sz, newstr, newsz);
-	*sz += newsz;
-	(*sql)[*sz] = '\0';
-}
-
-/*
- * Prepare the search SQL statement.
- */
-static char *
-sql_statement(const struct expr *e)
-{
-	char		*sql;
-	size_t		 sz;
-	int		 needop;
-
-	sql = mandoc_strdup(e->equal ?
-	    "SELECT desc, form, pageid, bits "
-		"FROM mpages NATURAL JOIN names WHERE " :
-	    "SELECT desc, form, pageid, 0 FROM mpages WHERE ");
-	sz = strlen(sql);
-
-	for (needop = 0; NULL != e; e = e->next) {
-		if (e->and)
-			sql_append(&sql, &sz, " AND ", 1);
-		else if (needop)
-			sql_append(&sql, &sz, " OR ", 1);
-		if (e->open)
-			sql_append(&sql, &sz, "(", e->open);
-		sql_append(&sql, &sz,
-		    TYPE_Nd & e->bits
-		    ? (NULL == e->substr
-			? "desc REGEXP ?"
-			: "desc MATCH ?")
-		    : TYPE_Nm == e->bits
-		    ? (NULL == e->substr
-			? "pageid IN (SELECT pageid FROM names "
-			  "WHERE name REGEXP ?)"
-			: e->equal
-			? "name = ? "
-			: "pageid IN (SELECT pageid FROM names "
-			  "WHERE name MATCH ?)")
-		    : (NULL == e->substr
-			? "pageid IN (SELECT pageid FROM keys "
-			  "WHERE key REGEXP ? AND bits & ?)"
-			: "pageid IN (SELECT pageid FROM keys "
-			  "WHERE key MATCH ? AND bits & ?)"), 1);
-		if (e->close)
-			sql_append(&sql, &sz, ")", e->close);
-		needop = 1;
-	}
-
-	return sql;
-}
-
 /*
  * Compile a set of string tokens into an expression.
  * Tokens in "argv" are assumed to be individual expression atoms (e.g.,
  * "(", "foo=bar", etc.).
  */
 static struct expr *
-exprcomp(const struct mansearch *search, int argc, char *argv[])
+exprcomp(const struct mansearch *search, int argc, char *argv[], int *argi)
 {
-	uint64_t	 mask;
-	int		 i, toopen, logic, igncase, toclose;
-	struct expr	*first, *prev, *cur, *next;
+	struct expr	*parent, *child;
+	int		 needterm, nested;
 
-	first = cur = NULL;
-	logic = igncase = toopen = toclose = 0;
-
-	for (i = 0; i < argc; i++) {
-		if (0 == strcmp("(", argv[i])) {
-			if (igncase)
-				goto fail;
-			toopen++;
-			toclose++;
-			continue;
-		} else if (0 == strcmp(")", argv[i])) {
-			if (toopen || logic || igncase || NULL == cur)
-				goto fail;
-			cur->close++;
-			if (0 > --toclose)
-				goto fail;
-			continue;
-		} else if (0 == strcmp("-a", argv[i])) {
-			if (toopen || logic || igncase || NULL == cur)
-				goto fail;
-			logic = 1;
-			continue;
-		} else if (0 == strcmp("-o", argv[i])) {
-			if (toopen || logic || igncase || NULL == cur)
-				goto fail;
-			logic = 2;
-			continue;
-		} else if (0 == strcmp("-i", argv[i])) {
-			if (igncase)
-				goto fail;
-			igncase = 1;
+	if ((nested = *argi) == argc)
+		return NULL;
+	needterm = 1;
+	parent = child = NULL;
+	while (*argi < argc) {
+		if (strcmp(")", argv[*argi]) == 0) {
+			if (needterm)
+				warnx("missing term "
+				    "before closing parenthesis");
+			needterm = 0;
+			if (nested)
+				break;
+			warnx("ignoring unmatched right parenthesis");
+			++*argi;
 			continue;
 		}
-		next = exprterm(search, argv[i], !igncase);
-		if (NULL == next)
-			goto fail;
-		if (NULL == first)
-			first = next;
-		else
-			cur->next = next;
-		prev = cur = next;
-
-		/*
-		 * Searching for descriptions must be split out
-		 * because they are stored in the mpages table,
-		 * not in the keys table.
-		 */
-
-		for (mask = TYPE_Nm; mask <= TYPE_Nd; mask <<= 1) {
-			if (mask & cur->bits && ~mask & cur->bits) {
-				next = mandoc_calloc(1,
-				    sizeof(struct expr));
-				memcpy(next, cur, sizeof(struct expr));
-				prev->open = 1;
-				cur->bits = mask;
-				cur->next = next;
-				cur = next;
-				cur->bits &= ~mask;
+		if (strcmp("-o", argv[*argi]) == 0) {
+			if (needterm) {
+				if (*argi > 0)
+					warnx("ignoring -o after %s",
+					    argv[*argi - 1]);
+				else
+					warnx("ignoring initial -o");
 			}
+			needterm = 1;
+			++*argi;
+			continue;
 		}
-		prev->and = (1 == logic);
-		prev->open += toopen;
-		if (cur != prev)
-			cur->close = 1;
-
-		toopen = logic = igncase = 0;
+		needterm = 0;
+		if (child == NULL) {
+			child = expr_and(search, argc, argv, argi);
+			continue;
+		}
+		if (parent == NULL) {
+			parent = mandoc_calloc(1, sizeof(*parent));
+			parent->type = EXPR_OR;
+			parent->next = NULL;
+			parent->child = child;
+		}
+		child->next = expr_and(search, argc, argv, argi);
+		child = child->next;
 	}
-	if ( ! (toopen || logic || igncase || toclose))
-		return first;
-
-fail:
-	if (NULL != first)
-		exprfree(first);
-	return NULL;
+	if (needterm && *argi)
+		warnx("ignoring trailing %s", argv[*argi - 1]);
+	return parent == NULL ? child : parent;
 }
 
 static struct expr *
-exprterm(const struct mansearch *search, char *buf, int cs)
+expr_and(const struct mansearch *search, int argc, char *argv[], int *argi)
+{
+	struct expr	*parent, *child;
+	int		 needterm;
+
+	needterm = 1;
+	parent = child = NULL;
+	while (*argi < argc) {
+		if (strcmp(")", argv[*argi]) == 0) {
+			if (needterm)
+				warnx("missing term "
+				    "before closing parenthesis");
+			needterm = 0;
+			break;
+		}
+		if (strcmp("-o", argv[*argi]) == 0)
+			break;
+		if (strcmp("-a", argv[*argi]) == 0) {
+			if (needterm) {
+				if (*argi > 0)
+					warnx("ignoring -a after %s",
+					    argv[*argi - 1]);
+				else
+					warnx("ignoring initial -a");
+			}
+			needterm = 1;
+			++*argi;
+			continue;
+		}
+		if (needterm == 0)
+			break;
+		if (child == NULL) {
+			child = exprterm(search, argc, argv, argi);
+			if (child != NULL)
+				needterm = 0;
+			continue;
+		}
+		needterm = 0;
+		if (parent == NULL) {
+			parent = mandoc_calloc(1, sizeof(*parent));
+			parent->type = EXPR_AND;
+			parent->next = NULL;
+			parent->child = child;
+		}
+		child->next = exprterm(search, argc, argv, argi);
+		if (child->next != NULL) {
+			child = child->next;
+			needterm = 0;
+		}
+	}
+	if (needterm && *argi)
+		warnx("ignoring trailing %s", argv[*argi - 1]);
+	return parent == NULL ? child : parent;
+}
+
+static struct expr *
+exprterm(const struct mansearch *search, int argc, char *argv[], int *argi)
 {
 	char		 errbuf[BUFSIZ];
 	struct expr	*e;
 	char		*key, *val;
 	uint64_t	 iterbit;
-	int		 i, irc;
+	int		 cs, i, irc;
 
-	if ('\0' == *buf)
-		return NULL;
+	if (strcmp("(", argv[*argi]) == 0) {
+		++*argi;
+		e = exprcomp(search, argc, argv, argi);
+		if (*argi < argc) {
+			assert(strcmp(")", argv[*argi]) == 0);
+			++*argi;
+		} else
+			warnx("unclosed parenthesis");
+		return e;
+	}
 
-	e = mandoc_calloc(1, sizeof(struct expr));
+	e = mandoc_calloc(1, sizeof(*e));
+	e->type = EXPR_TERM;
+	e->bits = 0;
+	e->next = NULL;
+	e->child = NULL;
 
 	if (search->argmode == ARG_NAME) {
 		e->bits = TYPE_Nm;
-		e->substr = buf;
-		e->equal = 1;
+		e->match.type = DBM_EXACT;
+		e->match.str = argv[(*argi)++];
 		return e;
 	}
 
 	/*
 	 * Separate macro keys from search string.
-	 * If needed, request regular expression handling
-	 * by setting e->substr to NULL.
+	 * If needed, request regular expression handling.
 	 */
 
 	if (search->argmode == ARG_WORD) {
 		e->bits = TYPE_Nm;
-		e->substr = NULL;
+		e->match.type = DBM_REGEX;
 #if HAVE_REWB_BSD
-		mandoc_asprintf(&val, "[[:<:]]%s[[:>:]]", buf);
+		mandoc_asprintf(&val, "[[:<:]]%s[[:>:]]", argv[*argi]);
 #elif HAVE_REWB_SYSV
-		mandoc_asprintf(&val, "\\<%s\\>", buf);
+		mandoc_asprintf(&val, "\\<%s\\>", argv[*argi]);
 #else
 		mandoc_asprintf(&val,
-		    "(^|[^a-zA-Z01-9_])%s([^a-zA-Z01-9_]|$)", buf);
+		    "(^|[^a-zA-Z01-9_])%s([^a-zA-Z01-9_]|$)", argv[*argi]);
 #endif
 		cs = 0;
-	} else if ((val = strpbrk(buf, "=~")) == NULL) {
+	} else if ((val = strpbrk(argv[*argi], "=~")) == NULL) {
 		e->bits = TYPE_Nm | TYPE_Nd;
-		e->substr = buf;
+		e->match.type = DBM_SUB;
+		e->match.str = argv[*argi];
 	} else {
-		if (val == buf)
+		if (val == argv[*argi])
 			e->bits = TYPE_Nm | TYPE_Nd;
-		if ('=' == *val)
-			e->substr = val + 1;
+		if (*val == '=') {
+			e->match.type = DBM_SUB;
+			e->match.str = val + 1;
+		} else
+			e->match.type = DBM_REGEX;
 		*val++ = '\0';
-		if (NULL != strstr(buf, "arch"))
+		if (strstr(argv[*argi], "arch") != NULL)
 			cs = 0;
 	}
 
 	/* Compile regular expressions. */
 
-	if (NULL == e->substr) {
-		irc = regcomp(&e->regexp, val,
+	if (e->match.type == DBM_REGEX) {
+		e->match.re = mandoc_malloc(sizeof(*e->match.re));
+		irc = regcomp(e->match.re, val,
 		    REG_EXTENDED | REG_NOSUB | (cs ? 0 : REG_ICASE));
+		if (irc) {
+			regerror(irc, e->match.re, errbuf, sizeof(errbuf));
+			warnx("regcomp /%s/: %s", val, errbuf);
+		}
 		if (search->argmode == ARG_WORD)
 			free(val);
 		if (irc) {
-			regerror(irc, &e->regexp, errbuf, sizeof(errbuf));
-			warnx("regcomp: %s", errbuf);
+			free(e->match.re);
 			free(e);
+			++*argi;
 			return NULL;
 		}
 	}
 
-	if (e->bits)
+	if (e->bits) {
+		++*argi;
 		return e;
+	}
 
 	/*
 	 * Parse out all possible fields.
 	 * If the field doesn't resolve, bail.
 	 */
 
-	while (NULL != (key = strsep(&buf, ","))) {
+	while (NULL != (key = strsep(&argv[*argi], ","))) {
 		if ('\0' == *key)
 			continue;
-		for (i = 0, iterbit = 1;
-		     i < mansearch_keymax;
-		     i++, iterbit <<= 1) {
-			if (0 == strcasecmp(key,
-			    mansearch_keynames[i])) {
+		for (i = 0, iterbit = 1; i < KEY_MAX; i++, iterbit <<= 1) {
+			if (0 == strcasecmp(key, mansearch_keynames[i])) {
 				e->bits |= iterbit;
 				break;
 			}
 		}
-		if (i == mansearch_keymax) {
-			if (strcasecmp(key, "any")) {
-				free(e);
-				return NULL;
-			}
+		if (i == KEY_MAX) {
+			if (strcasecmp(key, "any"))
+				warnx("treating unknown key "
+				    "\"%s\" as \"any\"", key);
 			e->bits |= ~0ULL;
 		}
 	}
 
+	++*argi;
 	return e;
 }
 
 static void
-exprfree(struct expr *p)
+exprfree(struct expr *e)
 {
-	struct expr	*pp;
-
-	while (NULL != p) {
-		pp = p->next;
-		free(p);
-		p = pp;
-	}
+	if (e->next != NULL)
+		exprfree(e->next);
+	if (e->child != NULL)
+		exprfree(e->child);
+	free(e);
 }
diff --git a/mansearch.h b/mansearch.h
index 7f68ff676750..892c6e1e8a56 100644
--- a/mansearch.h
+++ b/mansearch.h
@@ -1,7 +1,7 @@
-/*	$Id: mansearch.h,v 1.24 2015/11/07 14:01:16 schwarze Exp $ */
+/*	$Id: mansearch.h,v 1.27 2016/08/01 12:31:00 schwarze Exp $ */
 /*
  * Copyright (c) 2012 Kristaps Dzonsons 
- * Copyright (c) 2013, 2014 Ingo Schwarze 
+ * Copyright (c) 2013, 2014, 2016 Ingo Schwarze 
  *
  * Permission to use, copy, modify, and distribute this software for any
  * purpose with or without fee is hereby granted, provided that the above
@@ -17,6 +17,12 @@
  */
 
 #define	MANDOC_DB	 "mandoc.db"
+#define	MANDOCDB_MAGIC	 0x3a7d0cdb
+#define	MANDOCDB_VERSION 1
+
+#define	MACRO_MAX	 36
+#define	KEY_Nd		 39
+#define	KEY_MAX		 40
 
 #define	TYPE_arch	 0x0000000000000001ULL
 #define	TYPE_sec	 0x0000000000000002ULL
@@ -66,9 +72,11 @@
 #define	NAME_FILE	 0x0000004000000010ULL
 #define	NAME_MASK	 0x000000000000001fULL
 
-#define	FORM_CAT	 0  /* manual page is preformatted */
-#define	FORM_SRC	 1  /* format is mdoc(7) or man(7) */
-#define	FORM_NONE	 4  /* format is unknown */
+enum	form {
+	FORM_SRC = 1,	/* Format is mdoc(7) or man(7). */
+	FORM_CAT,	/* Manual page is preformatted. */
+	FORM_NONE	/* Format is unknown. */
+};
 
 enum	argmode {
 	ARG_FILE = 0,
@@ -84,7 +92,7 @@ struct	manpage {
 	size_t		 ipath; /* number of the manpath */
 	uint64_t	 bits; /* name type mask */
 	int		 sec; /* section number, 10 means invalid */
-	int		 form; /* 0 == catpage */
+	enum form	 form;
 };
 
 struct	mansearch {
@@ -98,7 +106,6 @@ struct	mansearch {
 
 struct	manpaths;
 
-int	mansearch_setup(int);
 int	mansearch(const struct mansearch *cfg, /* options */
 		const struct manpaths *paths, /* manpaths */
 		int argc, /* size of argv */
diff --git a/mdoc.7 b/mdoc.7
index 198a46a9636f..e28db482db0f 100644
--- a/mdoc.7
+++ b/mdoc.7
@@ -1,7 +1,7 @@
-.\"	$Id: mdoc.7,v 1.257 2015/11/05 12:06:45 schwarze Exp $
+.\"	$Id: mdoc.7,v 1.260 2017/01/09 14:10:53 schwarze Exp $
 .\"
 .\" Copyright (c) 2009, 2010, 2011 Kristaps Dzonsons 
-.\" Copyright (c) 2010, 2011, 2013 Ingo Schwarze 
+.\" Copyright (c) 2010, 2011, 2013-2017 Ingo Schwarze 
 .\"
 .\" Permission to use, copy, modify, and distribute this software for any
 .\" purpose with or without fee is hereby granted, provided that the above
@@ -15,7 +15,7 @@
 .\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 .\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 .\"
-.Dd $Mdocdate: November 5 2015 $
+.Dd $Mdocdate: January 9 2017 $
 .Dt MDOC 7
 .Os
 .Sh NAME
@@ -1831,14 +1831,25 @@ The
 list is the most complicated.
 Its syntax is as follows:
 .Pp
-.D1 Pf \. Sx \&It Ar cell Op  Ar cell ...
 .D1 Pf \. Sx \&It Ar cell Op Sx \&Ta Ar cell ...
+.D1 Pf \. Sx \&It Ar cell Op  Ar cell ...
 .Pp
 The arguments consist of one or more lines of text and macros
 representing a complete table line.
-Cells within the line are delimited by tabs or by the special
+Cells within the line are delimited by the special
 .Sx \&Ta
-block macro.
+block macro or by literal tab characters.
+.Pp
+Using literal tabs is strongly discouraged because they are very
+hard to use correctly and
+.Nm
+code using them is very hard to read.
+In particular, a blank character is syntactically significant
+before and after the literal tab character.
+If a word precedes or follows the tab without an intervening blank,
+that word is never interpreted as a macro call, but always output
+literally.
+.Pp
 The tab cell delimiter may only be used within the
 .Sx \&It
 line itself; on following lines, only the
@@ -1853,9 +1864,10 @@ Note that quoted strings may span tab-delimited cells on an
 line.
 For example,
 .Pp
-.Dl .It \(dqcol1 ;  col2 ;\(dq \&;
+.Dl .It \(dqcol1 ,\&  col2 ,\(dq \&;
 .Pp
-will preserve the semicolon whitespace except for the last.
+will preserve the whitespace before both commas,
+but not the whitespace before the semicolon.
 .Pp
 See also
 .Sx \&Bl .
@@ -2714,14 +2726,13 @@ Link to another manual
 .Pq Qq cross-reference .
 Its syntax is as follows:
 .Pp
-.D1 Pf \. Sx \&Xr Ar name Op section
+.D1 Pf \. Sx \&Xr Ar name section
 .Pp
 Cross reference the
 .Ar name
 and
 .Ar section
-number of another man page;
-omitting the section number is rarely useful.
+number of another man page.
 .Pp
 Examples:
 .Dl \&.Xr mandoc 1
@@ -3033,7 +3044,7 @@ then the macro accepts an arbitrary number of arguments.
 .It Sx \&Ux  Ta    Yes      Ta    Yes      Ta    n
 .It Sx \&Va  Ta    Yes      Ta    Yes      Ta    n
 .It Sx \&Vt  Ta    Yes      Ta    Yes      Ta    >0
-.It Sx \&Xr  Ta    Yes      Ta    Yes      Ta    >0
+.It Sx \&Xr  Ta    Yes      Ta    Yes      Ta    2
 .It Sx \&br  Ta    \&No     Ta    \&No     Ta    0
 .It Sx \&sp  Ta    \&No     Ta    \&No     Ta    1
 .El
@@ -3217,6 +3228,12 @@ but produces large indentations.
 .Xr mandoc_char 7 ,
 .Xr roff 7 ,
 .Xr tbl 7
+.Pp
+The web page
+.Lk http://mdocml.bsd.lv/mdoc/ "extended documentation for the mdoc language"
+provides a few tutorial-style pages for beginners, an extensive style
+guide for advanced authors, and an alphabetic index helping to choose
+the best macros for various kinds of content.
 .Sh HISTORY
 The
 .Nm
diff --git a/mdoc.c b/mdoc.c
index 724d45c652cd..009496184ab4 100644
--- a/mdoc.c
+++ b/mdoc.c
@@ -1,7 +1,7 @@
-/*	$Id: mdoc.c,v 1.256 2015/10/30 19:04:16 schwarze Exp $ */
+/*	$Id: mdoc.c,v 1.258 2017/01/10 13:47:00 schwarze Exp $ */
 /*
  * Copyright (c) 2008, 2009, 2010, 2011 Kristaps Dzonsons 
- * Copyright (c) 2010, 2012-2015 Ingo Schwarze 
+ * Copyright (c) 2010, 2012-2016 Ingo Schwarze 
  *
  * Permission to use, copy, modify, and distribute this software for any
  * purpose with or without fee is hereby granted, provided that the above
@@ -140,8 +140,8 @@ mdoc_endbody_alloc(struct roff_man *mdoc, int line, int pos, int tok,
 {
 	struct roff_node *p;
 
-	body->flags |= MDOC_ENDED;
-	body->parent->flags |= MDOC_ENDED;
+	body->flags |= NODE_ENDED;
+	body->parent->flags |= NODE_ENDED;
 	p = roff_node_alloc(mdoc, line, pos, ROFFT_BODY, tok);
 	p->body = body;
 	p->norm = body->norm;
@@ -219,29 +219,19 @@ mdoc_ptext(struct roff_man *mdoc, int line, char *buf, int offs)
 	struct roff_node *n;
 	char		 *c, *ws, *end;
 
-	assert(mdoc->last);
 	n = mdoc->last;
 
 	/*
-	 * Divert directly to list processing if we're encountering a
-	 * columnar ROFFT_BLOCK with or without a prior ROFFT_BLOCK entry
-	 * (a ROFFT_BODY means it's already open, in which case we should
-	 * process within its context in the normal way).
+	 * If a column list contains plain text, assume an implicit item
+	 * macro.  This can happen one or more times at the beginning
+	 * of such a list, intermixed with non-It mdoc macros and with
+	 * nodes generated on the roff level, for example by tbl.
 	 */
 
-	if (n->tok == MDOC_Bl && n->type == ROFFT_BODY &&
-	    n->end == ENDBODY_NOT && n->norm->Bl.type == LIST_column) {
-		/* `Bl' is open without any children. */
-		mdoc->flags |= MDOC_FREECOL;
-		mdoc_macro(mdoc, MDOC_It, line, offs, &offs, buf);
-		return 1;
-	}
-
-	if (n->tok == MDOC_It && n->type == ROFFT_BLOCK &&
-	    NULL != n->parent &&
-	    MDOC_Bl == n->parent->tok &&
-	    LIST_column == n->parent->norm->Bl.type) {
-		/* `Bl' has block-level `It' children. */
+	if ((n->tok == MDOC_Bl && n->type == ROFFT_BODY &&
+	     n->end == ENDBODY_NOT && n->norm->Bl.type == LIST_column) ||
+	    (n->parent != NULL && n->parent->tok == MDOC_Bl &&
+	     n->parent->norm->Bl.type == LIST_column)) {
 		mdoc->flags |= MDOC_FREECOL;
 		mdoc_macro(mdoc, MDOC_It, line, offs, &offs, buf);
 		return 1;
@@ -302,7 +292,7 @@ mdoc_ptext(struct roff_man *mdoc, int line, char *buf, int offs)
 		 * behaviour that we want to work around it.
 		 */
 		roff_elem_alloc(mdoc, line, offs, MDOC_sp);
-		mdoc->last->flags |= MDOC_VALID | MDOC_ENDED;
+		mdoc->last->flags |= NODE_VALID | NODE_ENDED;
 		mdoc->next = ROFF_NEXT_SIBLING;
 		return 1;
 	}
@@ -321,7 +311,7 @@ mdoc_ptext(struct roff_man *mdoc, int line, char *buf, int offs)
 	assert(buf < end);
 
 	if (mandoc_eos(buf+offs, (size_t)(end-buf-offs)))
-		mdoc->last->flags |= MDOC_EOS;
+		mdoc->last->flags |= NODE_EOS;
 	return 1;
 }
 
@@ -393,36 +383,23 @@ mdoc_pmacro(struct roff_man *mdoc, int ln, char *buf, int offs)
 	 * into macro processing.
 	 */
 
-	if (NULL == mdoc->last || MDOC_It == tok || MDOC_El == tok) {
+	n = mdoc->last;
+	if (n == NULL || tok == MDOC_It || tok == MDOC_El) {
 		mdoc_macro(mdoc, tok, ln, sv, &offs, buf);
 		return 1;
 	}
 
-	n = mdoc->last;
-	assert(mdoc->last);
-
 	/*
-	 * If the first macro of a `Bl -column', open an `It' block
-	 * context around the parsed macro.
+	 * If a column list contains a non-It macro, assume an implicit
+	 * item macro.  This can happen one or more times at the
+	 * beginning of such a list, intermixed with text lines and
+	 * with nodes generated on the roff level, for example by tbl.
 	 */
 
-	if (n->tok == MDOC_Bl && n->type == ROFFT_BODY &&
-	    n->end == ENDBODY_NOT && n->norm->Bl.type == LIST_column) {
-		mdoc->flags |= MDOC_FREECOL;
-		mdoc_macro(mdoc, MDOC_It, ln, sv, &sv, buf);
-		return 1;
-	}
-
-	/*
-	 * If we're following a block-level `It' within a `Bl -column'
-	 * context (perhaps opened in the above block or in ptext()),
-	 * then open an `It' block context around the parsed macro.
-	 */
-
-	if (n->tok == MDOC_It && n->type == ROFFT_BLOCK &&
-	    NULL != n->parent &&
-	    MDOC_Bl == n->parent->tok &&
-	    LIST_column == n->parent->norm->Bl.type) {
+	if ((n->tok == MDOC_Bl && n->type == ROFFT_BODY &&
+	     n->end == ENDBODY_NOT && n->norm->Bl.type == LIST_column) ||
+	    (n->parent != NULL && n->parent->tok == MDOC_Bl &&
+	     n->parent->norm->Bl.type == LIST_column)) {
 		mdoc->flags |= MDOC_FREECOL;
 		mdoc_macro(mdoc, MDOC_It, ln, sv, &sv, buf);
 		return 1;
diff --git a/mdoc_argv.c b/mdoc_argv.c
index 8675bdb2db56..b47c7dbdea34 100644
--- a/mdoc_argv.c
+++ b/mdoc_argv.c
@@ -1,4 +1,4 @@
-/*	$Id: mdoc_argv.c,v 1.107 2015/10/17 00:21:07 schwarze Exp $ */
+/*	$Id: mdoc_argv.c,v 1.109 2016/08/28 16:15:12 schwarze Exp $ */
 /*
  * Copyright (c) 2008, 2009, 2010, 2011 Kristaps Dzonsons 
  * Copyright (c) 2012, 2014, 2015 Ingo Schwarze 
@@ -29,6 +29,7 @@
 #include "roff.h"
 #include "mdoc.h"
 #include "libmandoc.h"
+#include "roff_int.h"
 #include "libmdoc.h"
 
 #define	MULTI_STEP	 5 /* pre-allocate argument values */
@@ -479,7 +480,7 @@ args(struct roff_man *mdoc, int line, int *pos,
 			 * unless there is a blank in between.
 			 */
 
-			if (p[-1] != ' ')
+			if (p > buf && p[-1] != ' ')
 				mdoc->flags |= MDOC_PHRASEQL;
 			if (p[1] != ' ')
 				mdoc->flags |= MDOC_PHRASEQN;
diff --git a/mdoc_hash.c b/mdoc_hash.c
index 476116d792ab..cad3c2db1afb 100644
--- a/mdoc_hash.c
+++ b/mdoc_hash.c
@@ -1,4 +1,4 @@
-/*	$Id: mdoc_hash.c,v 1.26 2015/10/06 18:32:19 schwarze Exp $ */
+/*	$Id: mdoc_hash.c,v 1.27 2016/07/15 18:03:45 schwarze Exp $ */
 /*
  * Copyright (c) 2008, 2009 Kristaps Dzonsons 
  * Copyright (c) 2015 Ingo Schwarze 
@@ -26,8 +26,10 @@
 #include 
 #include 
 
+#include "mandoc.h"
 #include "roff.h"
 #include "mdoc.h"
+#include "libmandoc.h"
 #include "libmdoc.h"
 
 static	unsigned char	 table[27 * 12];
diff --git a/mdoc_html.c b/mdoc_html.c
index 8e21bc79630d..3f757d20eb3d 100644
--- a/mdoc_html.c
+++ b/mdoc_html.c
@@ -1,7 +1,7 @@
-/*	$Id: mdoc_html.c,v 1.240 2016/01/08 17:48:09 schwarze Exp $ */
+/*	$Id: mdoc_html.c,v 1.260 2017/01/21 02:09:51 schwarze Exp $ */
 /*
  * Copyright (c) 2008-2011, 2014 Kristaps Dzonsons 
- * Copyright (c) 2014, 2015, 2016 Ingo Schwarze 
+ * Copyright (c) 2014, 2015, 2016, 2017 Ingo Schwarze 
  *
  * Permission to use, copy, modify, and distribute this software for any
  * purpose with or without fee is hereby granted, provided that the above
@@ -48,14 +48,13 @@ struct	htmlmdoc {
 	void		(*post)(MDOC_ARGS);
 };
 
+static	char		 *make_id(const struct roff_node *);
 static	void		  print_mdoc_head(MDOC_ARGS);
 static	void		  print_mdoc_node(MDOC_ARGS);
 static	void		  print_mdoc_nodelist(MDOC_ARGS);
 static	void		  synopsis_pre(struct html *,
 				const struct roff_node *);
 
-static	void		  a2width(const char *, struct roffsu *);
-
 static	void		  mdoc_root_post(MDOC_ARGS);
 static	int		  mdoc_root_pre(MDOC_ARGS);
 
@@ -70,9 +69,8 @@ static	int		  mdoc_bf_pre(MDOC_ARGS);
 static	void		  mdoc_bk_post(MDOC_ARGS);
 static	int		  mdoc_bk_pre(MDOC_ARGS);
 static	int		  mdoc_bl_pre(MDOC_ARGS);
-static	int		  mdoc_bt_pre(MDOC_ARGS);
-static	int		  mdoc_bx_pre(MDOC_ARGS);
 static	int		  mdoc_cd_pre(MDOC_ARGS);
+static	int		  mdoc_cm_pre(MDOC_ARGS);
 static	int		  mdoc_d1_pre(MDOC_ARGS);
 static	int		  mdoc_dv_pre(MDOC_ARGS);
 static	int		  mdoc_fa_pre(MDOC_ARGS);
@@ -107,7 +105,6 @@ static	int		  mdoc_pp_pre(MDOC_ARGS);
 static	void		  mdoc_quote_post(MDOC_ARGS);
 static	int		  mdoc_quote_pre(MDOC_ARGS);
 static	int		  mdoc_rs_pre(MDOC_ARGS);
-static	int		  mdoc_rv_pre(MDOC_ARGS);
 static	int		  mdoc_sh_pre(MDOC_ARGS);
 static	int		  mdoc_skip_pre(MDOC_ARGS);
 static	int		  mdoc_sm_pre(MDOC_ARGS);
@@ -115,7 +112,6 @@ static	int		  mdoc_sp_pre(MDOC_ARGS);
 static	int		  mdoc_ss_pre(MDOC_ARGS);
 static	int		  mdoc_sx_pre(MDOC_ARGS);
 static	int		  mdoc_sy_pre(MDOC_ARGS);
-static	int		  mdoc_ud_pre(MDOC_ARGS);
 static	int		  mdoc_va_pre(MDOC_ARGS);
 static	int		  mdoc_vt_pre(MDOC_ARGS);
 static	int		  mdoc_xr_pre(MDOC_ARGS);
@@ -140,7 +136,7 @@ static	const struct htmlmdoc mdocs[MDOC_MAX] = {
 	{mdoc_an_pre, NULL}, /* An */
 	{mdoc_ar_pre, NULL}, /* Ar */
 	{mdoc_cd_pre, NULL}, /* Cd */
-	{mdoc_fl_pre, NULL}, /* Cm */
+	{mdoc_cm_pre, NULL}, /* Cm */
 	{mdoc_dv_pre, NULL}, /* Dv */
 	{mdoc_er_pre, NULL}, /* Er */
 	{mdoc_ev_pre, NULL}, /* Ev */
@@ -158,7 +154,7 @@ static	const struct htmlmdoc mdocs[MDOC_MAX] = {
 	{mdoc_quote_pre, mdoc_quote_post}, /* Op */
 	{mdoc_ft_pre, NULL}, /* Ot */
 	{mdoc_pa_pre, NULL}, /* Pa */
-	{mdoc_rv_pre, NULL}, /* Rv */
+	{mdoc_ex_pre, NULL}, /* Rv */
 	{NULL, NULL}, /* St */
 	{mdoc_va_pre, NULL}, /* Va */
 	{mdoc_vt_pre, NULL}, /* Vt */
@@ -183,7 +179,7 @@ static	const struct htmlmdoc mdocs[MDOC_MAX] = {
 	{mdoc_quote_pre, mdoc_quote_post}, /* Bo */
 	{mdoc_quote_pre, mdoc_quote_post}, /* Bq */
 	{mdoc_xx_pre, NULL}, /* Bsx */
-	{mdoc_bx_pre, NULL}, /* Bx */
+	{mdoc_xx_pre, NULL}, /* Bx */
 	{mdoc_skip_pre, NULL}, /* Db */
 	{NULL, NULL}, /* Dc */
 	{mdoc_quote_pre, mdoc_quote_post}, /* Do */
@@ -224,10 +220,10 @@ static	const struct htmlmdoc mdocs[MDOC_MAX] = {
 	{NULL, NULL}, /* Oc */
 	{mdoc_bk_pre, mdoc_bk_post}, /* Bk */
 	{NULL, NULL}, /* Ek */
-	{mdoc_bt_pre, NULL}, /* Bt */
+	{NULL, NULL}, /* Bt */
 	{NULL, NULL}, /* Hf */
 	{mdoc_em_pre, NULL}, /* Fr */
-	{mdoc_ud_pre, NULL}, /* Ud */
+	{NULL, NULL}, /* Ud */
 	{mdoc_lb_pre, NULL}, /* Lb */
 	{mdoc_pp_pre, NULL}, /* Lp */
 	{mdoc_lk_pre, NULL}, /* Lk */
@@ -247,37 +243,6 @@ static	const struct htmlmdoc mdocs[MDOC_MAX] = {
 	{mdoc_skip_pre, NULL}, /* ll */
 };
 
-static	const char * const lists[LIST_MAX] = {
-	NULL,
-	"list-bul",
-	"list-col",
-	"list-dash",
-	"list-diag",
-	"list-enum",
-	"list-hang",
-	"list-hyph",
-	"list-inset",
-	"list-item",
-	"list-ohang",
-	"list-tag"
-};
-
-
-/*
- * Calculate the scaling unit passed in a `-width' argument.  This uses
- * either a native scaling unit (e.g., 1i, 2m) or the string length of
- * the value.
- */
-static void
-a2width(const char *p, struct roffsu *su)
-{
-
-	if (a2roffsu(p, su, SCALE_MAX) < 2) {
-		su->unit = SCALE_EN;
-		su->scale = html_strlen(p);
-	} else if (su->scale < 0.0)
-		su->scale = 0.0;
-}
 
 /*
  * See the same function in mdoc_term.c for documentation.
@@ -286,14 +251,14 @@ static void
 synopsis_pre(struct html *h, const struct roff_node *n)
 {
 
-	if (NULL == n->prev || ! (MDOC_SYNPRETTY & n->flags))
+	if (NULL == n->prev || ! (NODE_SYNPRETTY & n->flags))
 		return;
 
 	if (n->prev->tok == n->tok &&
 	    MDOC_Fo != n->tok &&
 	    MDOC_Ft != n->tok &&
 	    MDOC_Fn != n->tok) {
-		print_otag(h, TAG_BR, 0, NULL);
+		print_otag(h, TAG_BR, "");
 		return;
 	}
 
@@ -312,7 +277,7 @@ synopsis_pre(struct html *h, const struct roff_node *n)
 		}
 		/* FALLTHROUGH */
 	default:
-		print_otag(h, TAG_BR, 0, NULL);
+		print_otag(h, TAG_BR, "");
 		break;
 	}
 }
@@ -320,45 +285,48 @@ synopsis_pre(struct html *h, const struct roff_node *n)
 void
 html_mdoc(void *arg, const struct roff_man *mdoc)
 {
-	struct htmlpair	 tag;
 	struct html	*h;
-	struct tag	*t, *tt;
+	struct tag	*t;
 
-	PAIR_CLASS_INIT(&tag, "mandoc");
 	h = (struct html *)arg;
 
-	if ( ! (HTML_FRAGMENT & h->oflags)) {
+	if ((h->oflags & HTML_FRAGMENT) == 0) {
 		print_gen_decls(h);
-		t = print_otag(h, TAG_HTML, 0, NULL);
-		tt = print_otag(h, TAG_HEAD, 0, NULL);
+		print_otag(h, TAG_HTML, "");
+		t = print_otag(h, TAG_HEAD, "");
 		print_mdoc_head(&mdoc->meta, mdoc->first->child, h);
-		print_tagq(h, tt);
-		print_otag(h, TAG_BODY, 0, NULL);
-		print_otag(h, TAG_DIV, 1, &tag);
-	} else
-		t = print_otag(h, TAG_DIV, 1, &tag);
+		print_tagq(h, t);
+		print_otag(h, TAG_BODY, "");
+	}
 
 	mdoc_root_pre(&mdoc->meta, mdoc->first->child, h);
+	t = print_otag(h, TAG_DIV, "c", "manual-text");
 	print_mdoc_nodelist(&mdoc->meta, mdoc->first->child, h);
-	mdoc_root_post(&mdoc->meta, mdoc->first->child, h);
 	print_tagq(h, t);
-	putchar('\n');
+	mdoc_root_post(&mdoc->meta, mdoc->first->child, h);
+	print_tagq(h, NULL);
 }
 
 static void
 print_mdoc_head(MDOC_ARGS)
 {
+	char	*cp;
 
 	print_gen_head(h);
-	bufinit(h);
-	bufcat(h, meta->title);
-	if (meta->msec)
-		bufcat_fmt(h, "(%s)", meta->msec);
-	if (meta->arch)
-		bufcat_fmt(h, " (%s)", meta->arch);
 
-	print_otag(h, TAG_TITLE, 0, NULL);
-	print_text(h, h->buf);
+	if (meta->arch != NULL && meta->msec != NULL)
+		mandoc_asprintf(&cp, "%s(%s) (%s)", meta->title,
+		    meta->msec, meta->arch);
+	else if (meta->msec != NULL)
+		mandoc_asprintf(&cp, "%s(%s)", meta->title, meta->msec);
+	else if (meta->arch != NULL)
+		mandoc_asprintf(&cp, "%s (%s)", meta->title, meta->arch);
+	else
+		cp = mandoc_strdup(meta->title);
+
+	print_otag(h, TAG_TITLE, "");
+	print_text(h, cp);
+	free(cp);
 }
 
 static void
@@ -377,9 +345,12 @@ print_mdoc_node(MDOC_ARGS)
 	int		 child;
 	struct tag	*t;
 
+	if (n->flags & NODE_NOPRT)
+		return;
+
 	child = 1;
 	t = h->tags.head;
-	n->flags &= ~MDOC_ENDED;
+	n->flags &= ~NODE_ENDED;
 
 	switch (n->type) {
 	case ROFFT_TEXT:
@@ -390,18 +361,16 @@ print_mdoc_node(MDOC_ARGS)
 		 * Make sure that if we're in a literal mode already
 		 * (i.e., within a 
) don't print the newline.
 		 */
-		if (' ' == *n->string && MDOC_LINE & n->flags)
+		if (' ' == *n->string && NODE_LINE & n->flags)
 			if ( ! (HTML_LITERAL & h->flags))
-				print_otag(h, TAG_BR, 0, NULL);
-		if (MDOC_DELIMC & n->flags)
+				print_otag(h, TAG_BR, "");
+		if (NODE_DELIMC & n->flags)
 			h->flags |= HTML_NOSPACE;
 		print_text(h, n->string);
-		if (MDOC_DELIMO & n->flags)
+		if (NODE_DELIMO & n->flags)
 			h->flags |= HTML_NOSPACE;
 		return;
 	case ROFFT_EQN:
-		if (n->flags & MDOC_LINE)
-			putchar('\n');
 		print_eqn(h, n->eqn);
 		break;
 	case ROFFT_TBL:
@@ -428,7 +397,7 @@ print_mdoc_node(MDOC_ARGS)
 		break;
 	}
 
-	if (h->flags & HTML_KEEP && n->flags & MDOC_LINE) {
+	if (h->flags & HTML_KEEP && n->flags & NODE_LINE) {
 		h->flags &= ~HTML_KEEP;
 		h->flags |= HTML_PREKEEP;
 	}
@@ -442,11 +411,11 @@ print_mdoc_node(MDOC_ARGS)
 	case ROFFT_EQN:
 		break;
 	default:
-		if ( ! mdocs[n->tok].post || n->flags & MDOC_ENDED)
+		if ( ! mdocs[n->tok].post || n->flags & NODE_ENDED)
 			break;
 		(*mdocs[n->tok].post)(meta, n, h);
 		if (n->end != ENDBODY_NOT)
-			n->body->flags |= MDOC_ENDED;
+			n->body->flags |= NODE_ENDED;
 		if (n->end == ENDBODY_NOSPACE)
 			h->flags |= HTML_NOSPACE;
 		break;
@@ -456,23 +425,17 @@ print_mdoc_node(MDOC_ARGS)
 static void
 mdoc_root_post(MDOC_ARGS)
 {
-	struct htmlpair	 tag;
 	struct tag	*t, *tt;
 
-	PAIR_CLASS_INIT(&tag, "foot");
-	t = print_otag(h, TAG_TABLE, 1, &tag);
+	t = print_otag(h, TAG_TABLE, "c", "foot");
+	print_otag(h, TAG_TBODY, "");
+	tt = print_otag(h, TAG_TR, "");
 
-	print_otag(h, TAG_TBODY, 0, NULL);
-
-	tt = print_otag(h, TAG_TR, 0, NULL);
-
-	PAIR_CLASS_INIT(&tag, "foot-date");
-	print_otag(h, TAG_TD, 1, &tag);
+	print_otag(h, TAG_TD, "c", "foot-date");
 	print_text(h, meta->date);
 	print_stagq(h, tt);
 
-	PAIR_CLASS_INIT(&tag, "foot-os");
-	print_otag(h, TAG_TD, 1, &tag);
+	print_otag(h, TAG_TD, "c", "foot-os");
 	print_text(h, meta->os);
 	print_tagq(h, t);
 }
@@ -480,7 +443,6 @@ mdoc_root_post(MDOC_ARGS)
 static int
 mdoc_root_pre(MDOC_ARGS)
 {
-	struct htmlpair	 tag;
 	struct tag	*t, *tt;
 	char		*volume, *title;
 
@@ -496,25 +458,19 @@ mdoc_root_pre(MDOC_ARGS)
 		mandoc_asprintf(&title, "%s(%s)",
 		    meta->title, meta->msec);
 
-	PAIR_CLASS_INIT(&tag, "head");
-	t = print_otag(h, TAG_TABLE, 1, &tag);
+	t = print_otag(h, TAG_TABLE, "c", "head");
+	print_otag(h, TAG_TBODY, "");
+	tt = print_otag(h, TAG_TR, "");
 
-	print_otag(h, TAG_TBODY, 0, NULL);
-
-	tt = print_otag(h, TAG_TR, 0, NULL);
-
-	PAIR_CLASS_INIT(&tag, "head-ltitle");
-	print_otag(h, TAG_TD, 1, &tag);
+	print_otag(h, TAG_TD, "c", "head-ltitle");
 	print_text(h, title);
 	print_stagq(h, tt);
 
-	PAIR_CLASS_INIT(&tag, "head-vol");
-	print_otag(h, TAG_TD, 1, &tag);
+	print_otag(h, TAG_TD, "c", "head-vol");
 	print_text(h, volume);
 	print_stagq(h, tt);
 
-	PAIR_CLASS_INIT(&tag, "head-rtitle");
-	print_otag(h, TAG_TD, 1, &tag);
+	print_otag(h, TAG_TD, "c", "head-rtitle");
 	print_text(h, title);
 	print_tagq(h, t);
 
@@ -523,15 +479,35 @@ mdoc_root_pre(MDOC_ARGS)
 	return 1;
 }
 
+static char *
+make_id(const struct roff_node *n)
+{
+	const struct roff_node	*nch;
+	char			*buf, *cp;
+
+	for (nch = n->child; nch != NULL; nch = nch->next)
+		if (nch->type != ROFFT_TEXT)
+			return NULL;
+
+	buf = NULL;
+	deroff(&buf, n);
+
+	/* http://www.w3.org/TR/html5/dom.html#the-id-attribute */
+
+	for (cp = buf; *cp != '\0'; cp++)
+		if (*cp == ' ')
+			*cp = '_';
+
+	return buf;
+}
+
 static int
 mdoc_sh_pre(MDOC_ARGS)
 {
-	struct htmlpair	 tag;
+	char	*id;
 
 	switch (n->type) {
 	case ROFFT_BLOCK:
-		PAIR_CLASS_INIT(&tag, "section");
-		print_otag(h, TAG_DIV, 1, &tag);
 		return 1;
 	case ROFFT_BODY:
 		if (n->sec == SEC_AUTHORS)
@@ -541,19 +517,11 @@ mdoc_sh_pre(MDOC_ARGS)
 		break;
 	}
 
-	bufinit(h);
-
-	for (n = n->child; n != NULL && n->type == ROFFT_TEXT; ) {
-		bufcat_id(h, n->string);
-		if (NULL != (n = n->next))
-			bufcat_id(h, " ");
-	}
-
-	if (NULL == n) {
-		PAIR_ID_INIT(&tag, h->buf);
-		print_otag(h, TAG_H1, 1, &tag);
+	if ((id = make_id(n)) != NULL) {
+		print_otag(h, TAG_H1, "ci", "Sh", id);
+		free(id);
 	} else
-		print_otag(h, TAG_H1, 0, NULL);
+		print_otag(h, TAG_H1, "c", "Sh");
 
 	return 1;
 }
@@ -561,28 +529,16 @@ mdoc_sh_pre(MDOC_ARGS)
 static int
 mdoc_ss_pre(MDOC_ARGS)
 {
-	struct htmlpair	 tag;
+	char	*id;
 
-	if (n->type == ROFFT_BLOCK) {
-		PAIR_CLASS_INIT(&tag, "subsection");
-		print_otag(h, TAG_DIV, 1, &tag);
-		return 1;
-	} else if (n->type == ROFFT_BODY)
+	if (n->type != ROFFT_HEAD)
 		return 1;
 
-	bufinit(h);
-
-	for (n = n->child; n != NULL && n->type == ROFFT_TEXT; ) {
-		bufcat_id(h, n->string);
-		if (NULL != (n = n->next))
-			bufcat_id(h, " ");
-	}
-
-	if (NULL == n) {
-		PAIR_ID_INIT(&tag, h->buf);
-		print_otag(h, TAG_H2, 1, &tag);
+	if ((id = make_id(n)) != NULL) {
+		print_otag(h, TAG_H2, "ci", "Ss", id);
+		free(id);
 	} else
-		print_otag(h, TAG_H2, 0, NULL);
+		print_otag(h, TAG_H2, "c", "Ss");
 
 	return 1;
 }
@@ -590,70 +546,61 @@ mdoc_ss_pre(MDOC_ARGS)
 static int
 mdoc_fl_pre(MDOC_ARGS)
 {
-	struct htmlpair	 tag;
-
-	PAIR_CLASS_INIT(&tag, "flag");
-	print_otag(h, TAG_B, 1, &tag);
-
-	/* `Cm' has no leading hyphen. */
-
-	if (MDOC_Cm == n->tok)
-		return 1;
-
+	print_otag(h, TAG_B, "c", "Fl");
 	print_text(h, "\\-");
 
 	if (!(n->child == NULL &&
 	    (n->next == NULL ||
 	     n->next->type == ROFFT_TEXT ||
-	     n->next->flags & MDOC_LINE)))
+	     n->next->flags & NODE_LINE)))
 		h->flags |= HTML_NOSPACE;
 
 	return 1;
 }
 
+static int
+mdoc_cm_pre(MDOC_ARGS)
+{
+	print_otag(h, TAG_B, "c", "Cm");
+	return 1;
+}
+
 static int
 mdoc_nd_pre(MDOC_ARGS)
 {
-	struct htmlpair	 tag;
-
 	if (n->type != ROFFT_BODY)
 		return 1;
 
 	/* XXX: this tag in theory can contain block elements. */
 
 	print_text(h, "\\(em");
-	PAIR_CLASS_INIT(&tag, "desc");
-	print_otag(h, TAG_SPAN, 1, &tag);
+	print_otag(h, TAG_SPAN, "c", "Nd");
 	return 1;
 }
 
 static int
 mdoc_nm_pre(MDOC_ARGS)
 {
-	struct htmlpair	 tag;
-	struct roffsu	 su;
 	int		 len;
 
 	switch (n->type) {
 	case ROFFT_HEAD:
-		print_otag(h, TAG_TD, 0, NULL);
+		print_otag(h, TAG_TD, "");
 		/* FALLTHROUGH */
 	case ROFFT_ELEM:
-		PAIR_CLASS_INIT(&tag, "name");
-		print_otag(h, TAG_B, 1, &tag);
+		print_otag(h, TAG_B, "c", "Nm");
 		if (n->child == NULL && meta->name != NULL)
 			print_text(h, meta->name);
 		return 1;
 	case ROFFT_BODY:
-		print_otag(h, TAG_TD, 0, NULL);
+		print_otag(h, TAG_TD, "");
 		return 1;
 	default:
 		break;
 	}
 
 	synopsis_pre(h, n);
-	PAIR_CLASS_INIT(&tag, "synopsis");
-	print_otag(h, TAG_TABLE, 1, &tag);
+	print_otag(h, TAG_TABLE, "c", "Nm");
 
 	for (len = 0, n = n->head->child; n; n = n->next)
 		if (n->type == ROFFT_TEXT)
@@ -662,35 +609,25 @@ mdoc_nm_pre(MDOC_ARGS)
 	if (len == 0 && meta->name != NULL)
 		len = html_strlen(meta->name);
 
-	SCALE_HS_INIT(&su, len);
-	bufinit(h);
-	bufcat_su(h, "width", &su);
-	PAIR_STYLE_INIT(&tag, h);
-	print_otag(h, TAG_COL, 1, &tag);
-	print_otag(h, TAG_COL, 0, NULL);
-	print_otag(h, TAG_TBODY, 0, NULL);
-	print_otag(h, TAG_TR, 0, NULL);
+	print_otag(h, TAG_COL, "shw", len);
+	print_otag(h, TAG_COL, "");
+	print_otag(h, TAG_TBODY, "");
+	print_otag(h, TAG_TR, "");
 	return 1;
 }
 
 static int
 mdoc_xr_pre(MDOC_ARGS)
 {
-	struct htmlpair	 tag[2];
-
 	if (NULL == n->child)
 		return 0;
 
-	PAIR_CLASS_INIT(&tag[0], "link-man");
-
-	if (h->base_man) {
-		buffmt_man(h, n->child->string,
-		    n->child->next ?
-		    n->child->next->string : NULL);
-		PAIR_HREF_INIT(&tag[1], h->buf);
-		print_otag(h, TAG_A, 2, tag);
-	} else
-		print_otag(h, TAG_A, 1, tag);
+	if (h->base_man)
+		print_otag(h, TAG_A, "chM", "Xr",
+		    n->child->string, n->child->next == NULL ?
+		    NULL : n->child->next->string);
+	else
+		print_otag(h, TAG_A, "c", "Xr");
 
 	n = n->child;
 	print_text(h, n->string);
@@ -711,7 +648,7 @@ static int
 mdoc_ns_pre(MDOC_ARGS)
 {
 
-	if ( ! (MDOC_LINE & n->flags))
+	if ( ! (NODE_LINE & n->flags))
 		h->flags |= HTML_NOSPACE;
 	return 1;
 }
@@ -719,174 +656,124 @@ mdoc_ns_pre(MDOC_ARGS)
 static int
 mdoc_ar_pre(MDOC_ARGS)
 {
-	struct htmlpair tag;
-
-	PAIR_CLASS_INIT(&tag, "arg");
-	print_otag(h, TAG_I, 1, &tag);
+	print_otag(h, TAG_I, "c", "Ar");
 	return 1;
 }
 
 static int
 mdoc_xx_pre(MDOC_ARGS)
 {
-	const char	*pp;
-	struct htmlpair	 tag;
-	int		 flags;
-
-	switch (n->tok) {
-	case MDOC_Bsx:
-		pp = "BSD/OS";
-		break;
-	case MDOC_Dx:
-		pp = "DragonFly";
-		break;
-	case MDOC_Fx:
-		pp = "FreeBSD";
-		break;
-	case MDOC_Nx:
-		pp = "NetBSD";
-		break;
-	case MDOC_Ox:
-		pp = "OpenBSD";
-		break;
-	case MDOC_Ux:
-		pp = "UNIX";
-		break;
-	default:
-		return 1;
-	}
-
-	PAIR_CLASS_INIT(&tag, "unix");
-	print_otag(h, TAG_SPAN, 1, &tag);
-
-	print_text(h, pp);
-	if (n->child) {
-		flags = h->flags;
-		h->flags |= HTML_KEEP;
-		print_text(h, n->child->string);
-		h->flags = flags;
-	}
-	return 0;
-}
-
-static int
-mdoc_bx_pre(MDOC_ARGS)
-{
-	struct htmlpair	 tag;
-
-	PAIR_CLASS_INIT(&tag, "unix");
-	print_otag(h, TAG_SPAN, 1, &tag);
-
-	if (NULL != (n = n->child)) {
-		print_text(h, n->string);
-		h->flags |= HTML_NOSPACE;
-		print_text(h, "BSD");
-	} else {
-		print_text(h, "BSD");
-		return 0;
-	}
-
-	if (NULL != (n = n->next)) {
-		h->flags |= HTML_NOSPACE;
-		print_text(h, "-");
-		h->flags |= HTML_NOSPACE;
-		print_text(h, n->string);
-	}
-
-	return 0;
+	print_otag(h, TAG_SPAN, "c", "Ux");
+	return 1;
 }
 
 static int
 mdoc_it_pre(MDOC_ARGS)
 {
-	struct roffsu	 su;
-	enum mdoc_list	 type;
-	struct htmlpair	 tag[2];
-	const struct roff_node *bl;
+	const struct roff_node	*bl;
+	const char		*cattr;
+	enum mdoc_list		 type;
 
 	bl = n->parent;
-	while (bl && MDOC_Bl != bl->tok)
+	while (bl != NULL && bl->tok != MDOC_Bl)
 		bl = bl->parent;
-
-	assert(bl);
-
 	type = bl->norm->Bl.type;
 
-	assert(lists[type]);
-	PAIR_CLASS_INIT(&tag[0], lists[type]);
+	switch (type) {
+	case LIST_bullet:
+		cattr = "It-bullet";
+		break;
+	case LIST_dash:
+	case LIST_hyphen:
+		cattr = "It-dash";
+		break;
+	case LIST_item:
+		cattr = "It-item";
+		break;
+	case LIST_enum:
+		cattr = "It-enum";
+		break;
+	case LIST_diag:
+		cattr = "It-diag";
+		break;
+	case LIST_hang:
+		cattr = "It-hang";
+		break;
+	case LIST_inset:
+		cattr = "It-inset";
+		break;
+	case LIST_ohang:
+		cattr = "It-ohang";
+		break;
+	case LIST_tag:
+		cattr = "It-tag";
+		break;
+	case LIST_column:
+		cattr = "It-column";
+		break;
+	default:
+		break;
+	}
 
-	bufinit(h);
-
-	if (n->type == ROFFT_HEAD) {
-		switch (type) {
-		case LIST_bullet:
-		case LIST_dash:
-		case LIST_item:
-		case LIST_hyphen:
-		case LIST_enum:
+	switch (type) {
+	case LIST_bullet:
+	case LIST_dash:
+	case LIST_hyphen:
+	case LIST_item:
+	case LIST_enum:
+		switch (n->type) {
+		case ROFFT_HEAD:
 			return 0;
-		case LIST_diag:
-		case LIST_hang:
-		case LIST_inset:
-		case LIST_ohang:
-		case LIST_tag:
-			SCALE_VS_INIT(&su, ! bl->norm->Bl.comp);
-			bufcat_su(h, "margin-top", &su);
-			PAIR_STYLE_INIT(&tag[1], h);
-			print_otag(h, TAG_DT, 2, tag);
-			if (LIST_diag != type)
-				break;
-			PAIR_CLASS_INIT(&tag[0], "diag");
-			print_otag(h, TAG_B, 1, tag);
-			break;
-		case LIST_column:
+		case ROFFT_BODY:
+			if (bl->norm->Bl.comp)
+				print_otag(h, TAG_LI, "csvt", cattr, 0);
+			else
+				print_otag(h, TAG_LI, "c", cattr);
 			break;
 		default:
 			break;
 		}
-	} else if (n->type == ROFFT_BODY) {
-		switch (type) {
-		case LIST_bullet:
-		case LIST_hyphen:
-		case LIST_dash:
-		case LIST_enum:
-		case LIST_item:
-			SCALE_VS_INIT(&su, ! bl->norm->Bl.comp);
-			bufcat_su(h, "margin-top", &su);
-			PAIR_STYLE_INIT(&tag[1], h);
-			print_otag(h, TAG_LI, 2, tag);
+		break;
+	case LIST_diag:
+	case LIST_hang:
+	case LIST_inset:
+	case LIST_ohang:
+	case LIST_tag:
+		switch (n->type) {
+		case ROFFT_HEAD:
+			if (bl->norm->Bl.comp)
+				print_otag(h, TAG_DT, "csvt", cattr, 0);
+			else
+				print_otag(h, TAG_DT, "c", cattr);
+			if (type == LIST_diag)
+				print_otag(h, TAG_B, "c", cattr);
 			break;
-		case LIST_diag:
-		case LIST_hang:
-		case LIST_inset:
-		case LIST_ohang:
-		case LIST_tag:
-			if (NULL == bl->norm->Bl.width) {
-				print_otag(h, TAG_DD, 1, tag);
-				break;
-			}
-			a2width(bl->norm->Bl.width, &su);
-			bufcat_su(h, "margin-left", &su);
-			PAIR_STYLE_INIT(&tag[1], h);
-			print_otag(h, TAG_DD, 2, tag);
-			break;
-		case LIST_column:
-			SCALE_VS_INIT(&su, ! bl->norm->Bl.comp);
-			bufcat_su(h, "margin-top", &su);
-			PAIR_STYLE_INIT(&tag[1], h);
-			print_otag(h, TAG_TD, 2, tag);
+		case ROFFT_BODY:
+			if (bl->norm->Bl.width == NULL)
+				print_otag(h, TAG_DD, "c", cattr);
+			else
+				print_otag(h, TAG_DD, "cswl", cattr,
+				    bl->norm->Bl.width);
 			break;
 		default:
 			break;
 		}
-	} else {
-		switch (type) {
-		case LIST_column:
-			print_otag(h, TAG_TR, 1, tag);
+		break;
+	case LIST_column:
+		switch (n->type) {
+		case ROFFT_HEAD:
+			break;
+		case ROFFT_BODY:
+			if (bl->norm->Bl.comp)
+				print_otag(h, TAG_TD, "csvt", cattr, 0);
+			else
+				print_otag(h, TAG_TD, "c", cattr);
 			break;
 		default:
-			break;
+			print_otag(h, TAG_TR, "c", cattr);
 		}
+	default:
+		break;
 	}
 
 	return 1;
@@ -895,14 +782,13 @@ mdoc_it_pre(MDOC_ARGS)
 static int
 mdoc_bl_pre(MDOC_ARGS)
 {
+	const char	*cattr;
 	int		 i;
-	struct htmlpair	 tag[3];
-	struct roffsu	 su;
-	char		 buf[BUFSIZ];
+	enum htmltag	 elemtype;
 
 	if (n->type == ROFFT_BODY) {
 		if (LIST_column == n->norm->Bl.type)
-			print_otag(h, TAG_TBODY, 0, NULL);
+			print_otag(h, TAG_TBODY, "");
 		return 1;
 	}
 
@@ -917,142 +803,92 @@ mdoc_bl_pre(MDOC_ARGS)
 		 * screen and we want to preserve that behaviour.
 		 */
 
-		for (i = 0; i < (int)n->norm->Bl.ncols; i++) {
-			bufinit(h);
-			a2width(n->norm->Bl.cols[i], &su);
-			if (i < (int)n->norm->Bl.ncols - 1)
-				bufcat_su(h, "width", &su);
-			else
-				bufcat_su(h, "min-width", &su);
-			PAIR_STYLE_INIT(&tag[0], h);
-			print_otag(h, TAG_COL, 1, tag);
-		}
+		for (i = 0; i < (int)n->norm->Bl.ncols - 1; i++)
+			print_otag(h, TAG_COL, "sww", n->norm->Bl.cols[i]);
+		print_otag(h, TAG_COL, "swW", n->norm->Bl.cols[i]);
 
 		return 0;
 	}
 
-	SCALE_VS_INIT(&su, 0);
-	bufinit(h);
-	bufcat_su(h, "margin-top", &su);
-	bufcat_su(h, "margin-bottom", &su);
-	PAIR_STYLE_INIT(&tag[0], h);
-
-	assert(lists[n->norm->Bl.type]);
-	(void)strlcpy(buf, "list ", BUFSIZ);
-	(void)strlcat(buf, lists[n->norm->Bl.type], BUFSIZ);
-	PAIR_INIT(&tag[1], ATTR_CLASS, buf);
-
-	/* Set the block's left-hand margin. */
-
-	if (n->norm->Bl.offs) {
-		a2width(n->norm->Bl.offs, &su);
-		bufcat_su(h, "margin-left", &su);
-	}
-
 	switch (n->norm->Bl.type) {
 	case LIST_bullet:
+		elemtype = TAG_UL;
+		cattr = "Bl-bullet";
+		break;
 	case LIST_dash:
 	case LIST_hyphen:
+		elemtype = TAG_UL;
+		cattr = "Bl-dash";
+		break;
 	case LIST_item:
-		print_otag(h, TAG_UL, 2, tag);
+		elemtype = TAG_UL;
+		cattr = "Bl-item";
 		break;
 	case LIST_enum:
-		print_otag(h, TAG_OL, 2, tag);
+		elemtype = TAG_OL;
+		cattr = "Bl-enum";
 		break;
 	case LIST_diag:
+		elemtype = TAG_DL;
+		cattr = "Bl-diag";
+		break;
 	case LIST_hang:
+		elemtype = TAG_DL;
+		cattr = "Bl-hang";
+		break;
 	case LIST_inset:
+		elemtype = TAG_DL;
+		cattr = "Bl-inset";
+		break;
 	case LIST_ohang:
+		elemtype = TAG_DL;
+		cattr = "Bl-ohang";
+		break;
 	case LIST_tag:
-		print_otag(h, TAG_DL, 2, tag);
+		elemtype = TAG_DL;
+		cattr = "Bl-tag";
 		break;
 	case LIST_column:
-		print_otag(h, TAG_TABLE, 2, tag);
+		elemtype = TAG_TABLE;
+		cattr = "Bl-column";
 		break;
 	default:
 		abort();
 	}
 
+	if (n->norm->Bl.offs)
+		print_otag(h, elemtype, "cswl", cattr, n->norm->Bl.offs);
+	else
+		print_otag(h, elemtype, "c", cattr);
+
 	return 1;
 }
 
 static int
 mdoc_ex_pre(MDOC_ARGS)
 {
-	struct htmlpair	  tag;
-	struct tag	 *t;
-	struct roff_node *nch;
-
 	if (n->prev)
-		print_otag(h, TAG_BR, 0, NULL);
-
-	PAIR_CLASS_INIT(&tag, "utility");
-
-	print_text(h, "The");
-
-	for (nch = n->child; nch != NULL; nch = nch->next) {
-		assert(nch->type == ROFFT_TEXT);
-
-		t = print_otag(h, TAG_B, 1, &tag);
-		print_text(h, nch->string);
-		print_tagq(h, t);
-
-		if (nch->next == NULL)
-			continue;
-
-		if (nch->prev != NULL || nch->next->next != NULL) {
-			h->flags |= HTML_NOSPACE;
-			print_text(h, ",");
-		}
-
-		if (nch->next->next == NULL)
-			print_text(h, "and");
-	}
-
-	if (n->child != NULL && n->child->next != NULL)
-		print_text(h, "utilities exit\\~0");
-	else
-		print_text(h, "utility exits\\~0");
-
-	print_text(h, "on success, and\\~>0 if an error occurs.");
-	return 0;
+		print_otag(h, TAG_BR, "");
+	return 1;
 }
 
 static int
 mdoc_em_pre(MDOC_ARGS)
 {
-	struct htmlpair	tag;
-
-	PAIR_CLASS_INIT(&tag, "emph");
-	print_otag(h, TAG_SPAN, 1, &tag);
+	print_otag(h, TAG_I, "c", "Em");
 	return 1;
 }
 
 static int
 mdoc_d1_pre(MDOC_ARGS)
 {
-	struct htmlpair	 tag[2];
-	struct roffsu	 su;
-
 	if (n->type != ROFFT_BLOCK)
 		return 1;
 
-	SCALE_VS_INIT(&su, 0);
-	bufinit(h);
-	bufcat_su(h, "margin-top", &su);
-	bufcat_su(h, "margin-bottom", &su);
-	PAIR_STYLE_INIT(&tag[0], h);
-	print_otag(h, TAG_BLOCKQUOTE, 1, tag);
+	print_otag(h, TAG_DIV, "c", "D1");
 
-	/* BLOCKQUOTE needs a block body. */
-
-	PAIR_CLASS_INIT(&tag[0], "display");
-	print_otag(h, TAG_DIV, 1, tag);
-
-	if (MDOC_Dl == n->tok) {
-		PAIR_CLASS_INIT(&tag[0], "lit");
-		print_otag(h, TAG_CODE, 1, tag);
-	}
+	if (n->tok == MDOC_Dl)
+		print_otag(h, TAG_CODE, "c", "Li");
 
 	return 1;
 }
@@ -1060,32 +896,22 @@ mdoc_d1_pre(MDOC_ARGS)
 static int
 mdoc_sx_pre(MDOC_ARGS)
 {
-	struct htmlpair	 tag[2];
+	char	*id;
 
-	bufinit(h);
-	bufcat(h, "#");
+	if ((id = make_id(n)) != NULL) {
+		print_otag(h, TAG_A, "chR", "Sx", id);
+		free(id);
+	} else
+		print_otag(h, TAG_A, "c", "Sx");
 
-	for (n = n->child; n; ) {
-		bufcat_id(h, n->string);
-		if (NULL != (n = n->next))
-			bufcat_id(h, " ");
-	}
-
-	PAIR_CLASS_INIT(&tag[0], "link-sec");
-	PAIR_HREF_INIT(&tag[1], h->buf);
-
-	print_otag(h, TAG_I, 1, tag);
-	print_otag(h, TAG_A, 2, tag);
 	return 1;
 }
 
 static int
 mdoc_bd_pre(MDOC_ARGS)
 {
-	struct htmlpair		 tag[2];
-	int			 comp, sv;
+	int			 comp, offs, sv;
 	struct roff_node	*nn;
-	struct roffsu		 su;
 
 	if (n->type == ROFFT_HEAD)
 		return 0;
@@ -1109,27 +935,24 @@ mdoc_bd_pre(MDOC_ARGS)
 
 	if (n->norm->Bd.offs == NULL ||
 	    ! strcmp(n->norm->Bd.offs, "left"))
-		SCALE_HS_INIT(&su, 0);
+		offs = 0;
 	else if ( ! strcmp(n->norm->Bd.offs, "indent"))
-		SCALE_HS_INIT(&su, INDENT);
+		offs = INDENT;
 	else if ( ! strcmp(n->norm->Bd.offs, "indent-two"))
-		SCALE_HS_INIT(&su, INDENT * 2);
+		offs = INDENT * 2;
 	else
-		a2width(n->norm->Bd.offs, &su);
+		offs = -1;
 
-	bufinit(h);
-	bufcat_su(h, "margin-left", &su);
-	PAIR_STYLE_INIT(&tag[0], h);
+	if (offs == -1)
+		print_otag(h, TAG_DIV, "cswl", "Bd", n->norm->Bd.offs);
+	else
+		print_otag(h, TAG_DIV, "cshl", "Bd", offs);
 
-	if (DISP_unfilled != n->norm->Bd.type &&
-	    DISP_literal != n->norm->Bd.type) {
-		PAIR_CLASS_INIT(&tag[1], "display");
-		print_otag(h, TAG_DIV, 2, tag);
+	if (n->norm->Bd.type != DISP_unfilled &&
+	    n->norm->Bd.type != DISP_literal)
 		return 1;
-	}
 
-	PAIR_CLASS_INIT(&tag[1], "lit display");
-	print_otag(h, TAG_PRE, 2, tag);
+	print_otag(h, TAG_PRE, "c", "Li");
 
 	/* This can be recursive: save & set our literal state. */
 
@@ -1158,7 +981,7 @@ mdoc_bd_pre(MDOC_ARGS)
 			break;
 		}
 		if (h->flags & HTML_NONEWLINE ||
-		    (nn->next && ! (nn->next->flags & MDOC_LINE)))
+		    (nn->next && ! (nn->next->flags & NODE_LINE)))
 			continue;
 		else if (nn->next)
 			print_text(h, "\n");
@@ -1175,28 +998,20 @@ mdoc_bd_pre(MDOC_ARGS)
 static int
 mdoc_pa_pre(MDOC_ARGS)
 {
-	struct htmlpair	tag;
-
-	PAIR_CLASS_INIT(&tag, "file");
-	print_otag(h, TAG_I, 1, &tag);
+	print_otag(h, TAG_I, "c", "Pa");
 	return 1;
 }
 
 static int
 mdoc_ad_pre(MDOC_ARGS)
 {
-	struct htmlpair	tag;
-
-	PAIR_CLASS_INIT(&tag, "addr");
-	print_otag(h, TAG_I, 1, &tag);
+	print_otag(h, TAG_I, "c", "Ad");
 	return 1;
 }
 
 static int
 mdoc_an_pre(MDOC_ARGS)
 {
-	struct htmlpair	tag;
-
 	if (n->norm->An.auth == AUTH_split) {
 		h->flags &= ~HTML_NOSPLIT;
 		h->flags |= HTML_SPLIT;
@@ -1209,54 +1024,41 @@ mdoc_an_pre(MDOC_ARGS)
 	}
 
 	if (h->flags & HTML_SPLIT)
-		print_otag(h, TAG_BR, 0, NULL);
+		print_otag(h, TAG_BR, "");
 
 	if (n->sec == SEC_AUTHORS && ! (h->flags & HTML_NOSPLIT))
 		h->flags |= HTML_SPLIT;
 
-	PAIR_CLASS_INIT(&tag, "author");
-	print_otag(h, TAG_SPAN, 1, &tag);
+	print_otag(h, TAG_SPAN, "c", "An");
 	return 1;
 }
 
 static int
 mdoc_cd_pre(MDOC_ARGS)
 {
-	struct htmlpair	tag;
-
 	synopsis_pre(h, n);
-	PAIR_CLASS_INIT(&tag, "config");
-	print_otag(h, TAG_B, 1, &tag);
+	print_otag(h, TAG_B, "c", "Cd");
 	return 1;
 }
 
 static int
 mdoc_dv_pre(MDOC_ARGS)
 {
-	struct htmlpair	tag;
-
-	PAIR_CLASS_INIT(&tag, "define");
-	print_otag(h, TAG_SPAN, 1, &tag);
+	print_otag(h, TAG_CODE, "c", "Dv");
 	return 1;
 }
 
 static int
 mdoc_ev_pre(MDOC_ARGS)
 {
-	struct htmlpair	tag;
-
-	PAIR_CLASS_INIT(&tag, "env");
-	print_otag(h, TAG_SPAN, 1, &tag);
+	print_otag(h, TAG_CODE, "c", "Ev");
 	return 1;
 }
 
 static int
 mdoc_er_pre(MDOC_ARGS)
 {
-	struct htmlpair	tag;
-
-	PAIR_CLASS_INIT(&tag, "errno");
-	print_otag(h, TAG_SPAN, 1, &tag);
+	print_otag(h, TAG_CODE, "c", "Er");
 	return 1;
 }
 
@@ -1264,17 +1066,15 @@ static int
 mdoc_fa_pre(MDOC_ARGS)
 {
 	const struct roff_node	*nn;
-	struct htmlpair		 tag;
 	struct tag		*t;
 
-	PAIR_CLASS_INIT(&tag, "farg");
 	if (n->parent->tok != MDOC_Fo) {
-		print_otag(h, TAG_I, 1, &tag);
+		print_otag(h, TAG_I, "c", "Fa");
 		return 1;
 	}
 
 	for (nn = n->child; nn; nn = nn->next) {
-		t = print_otag(h, TAG_I, 1, &tag);
+		t = print_otag(h, TAG_I, "c", "Fa");
 		print_text(h, nn->string);
 		print_tagq(h, t);
 		if (nn->next) {
@@ -1294,11 +1094,8 @@ mdoc_fa_pre(MDOC_ARGS)
 static int
 mdoc_fd_pre(MDOC_ARGS)
 {
-	struct htmlpair	 tag[2];
-	char		 buf[BUFSIZ];
-	size_t		 sz;
-	int		 i;
 	struct tag	*t;
+	char		*buf, *cp;
 
 	synopsis_pre(h, n);
 
@@ -1308,43 +1105,29 @@ mdoc_fd_pre(MDOC_ARGS)
 	assert(n->type == ROFFT_TEXT);
 
 	if (strcmp(n->string, "#include")) {
-		PAIR_CLASS_INIT(&tag[0], "macro");
-		print_otag(h, TAG_B, 1, tag);
+		print_otag(h, TAG_B, "c", "Fd");
 		return 1;
 	}
 
-	PAIR_CLASS_INIT(&tag[0], "includes");
-	print_otag(h, TAG_B, 1, tag);
+	print_otag(h, TAG_B, "c", "In");
 	print_text(h, n->string);
 
 	if (NULL != (n = n->next)) {
 		assert(n->type == ROFFT_TEXT);
 
-		/*
-		 * XXX This is broken and not easy to fix.
-		 * When using -Oincludes, truncation may occur.
-		 * Dynamic allocation wouldn't help because
-		 * passing long strings to buffmt_includes()
-		 * does not work either.
-		 */
-
-		strlcpy(buf, '<' == *n->string || '"' == *n->string ?
-		    n->string + 1 : n->string, BUFSIZ);
-
-		sz = strlen(buf);
-		if (sz && ('>' == buf[sz - 1] || '"' == buf[sz - 1]))
-			buf[sz - 1] = '\0';
-
-		PAIR_CLASS_INIT(&tag[0], "link-includes");
-
-		i = 1;
 		if (h->base_includes) {
-			buffmt_includes(h, buf);
-			PAIR_HREF_INIT(&tag[i], h->buf);
-			i++;
-		}
+			cp = n->string;
+			if (*cp == '<' || *cp == '"')
+				cp++;
+			buf = mandoc_strdup(cp);
+			cp = strchr(buf, '\0') - 1;
+			if (cp >= buf && (*cp == '>' || *cp == '"'))
+				*cp = '\0';
+			t = print_otag(h, TAG_A, "chI", "In", buf);
+			free(buf);
+		} else
+			t = print_otag(h, TAG_A, "c", "In");
 
-		t = print_otag(h, TAG_A, i, tag);
 		print_text(h, n->string);
 		print_tagq(h, t);
 
@@ -1362,8 +1145,6 @@ mdoc_fd_pre(MDOC_ARGS)
 static int
 mdoc_vt_pre(MDOC_ARGS)
 {
-	struct htmlpair	 tag;
-
 	if (n->type == ROFFT_BLOCK) {
 		synopsis_pre(h, n);
 		return 1;
@@ -1372,19 +1153,15 @@ mdoc_vt_pre(MDOC_ARGS)
 	} else if (n->type == ROFFT_HEAD)
 		return 0;
 
-	PAIR_CLASS_INIT(&tag, "type");
-	print_otag(h, TAG_SPAN, 1, &tag);
+	print_otag(h, TAG_I, "c", "Vt");
 	return 1;
 }
 
 static int
 mdoc_ft_pre(MDOC_ARGS)
 {
-	struct htmlpair	 tag;
-
 	synopsis_pre(h, n);
-	PAIR_CLASS_INIT(&tag, "ftype");
-	print_otag(h, TAG_I, 1, &tag);
+	print_otag(h, TAG_I, "c", "Ft");
 	return 1;
 }
 
@@ -1392,12 +1169,11 @@ static int
 mdoc_fn_pre(MDOC_ARGS)
 {
 	struct tag	*t;
-	struct htmlpair	 tag[2];
 	char		 nbuf[BUFSIZ];
 	const char	*sp, *ep;
-	int		 sz, i, pretty;
+	int		 sz, pretty;
 
-	pretty = MDOC_SYNPRETTY & n->flags;
+	pretty = NODE_SYNPRETTY & n->flags;
 	synopsis_pre(h, n);
 
 	/* Split apart into type and name. */
@@ -1406,8 +1182,7 @@ mdoc_fn_pre(MDOC_ARGS)
 
 	ep = strchr(sp, ' ');
 	if (NULL != ep) {
-		PAIR_CLASS_INIT(&tag[0], "ftype");
-		t = print_otag(h, TAG_I, 1, tag);
+		t = print_otag(h, TAG_I, "c", "Ft");
 
 		while (ep) {
 			sz = MIN((int)(ep - sp), BUFSIZ - 1);
@@ -1420,25 +1195,7 @@ mdoc_fn_pre(MDOC_ARGS)
 		print_tagq(h, t);
 	}
 
-	PAIR_CLASS_INIT(&tag[0], "fname");
-
-	/*
-	 * FIXME: only refer to IDs that we know exist.
-	 */
-
-#if 0
-	if (MDOC_SYNPRETTY & n->flags) {
-		nbuf[0] = '\0';
-		html_idcat(nbuf, sp, BUFSIZ);
-		PAIR_ID_INIT(&tag[1], nbuf);
-	} else {
-		strlcpy(nbuf, "#", BUFSIZ);
-		html_idcat(nbuf, sp, BUFSIZ);
-		PAIR_HREF_INIT(&tag[1], nbuf);
-	}
-#endif
-
-	t = print_otag(h, TAG_B, 1, tag);
+	t = print_otag(h, TAG_B, "c", "Fn");
 
 	if (sp)
 		print_text(h, sp);
@@ -1449,16 +1206,12 @@ mdoc_fn_pre(MDOC_ARGS)
 	print_text(h, "(");
 	h->flags |= HTML_NOSPACE;
 
-	PAIR_CLASS_INIT(&tag[0], "farg");
-	bufinit(h);
-	bufcat_style(h, "white-space", "nowrap");
-	PAIR_STYLE_INIT(&tag[1], h);
-
 	for (n = n->child->next; n; n = n->next) {
-		i = 1;
-		if (MDOC_SYNPRETTY & n->flags)
-			i = 2;
-		t = print_otag(h, TAG_I, i, tag);
+		if (NODE_SYNPRETTY & n->flags)
+			t = print_otag(h, TAG_I, "css?", "Fa",
+			    "white-space", "nowrap");
+		else
+			t = print_otag(h, TAG_I, "c", "Fa");
 		print_text(h, n->string);
 		print_tagq(h, t);
 		if (n->next) {
@@ -1514,7 +1267,6 @@ static int
 mdoc_sp_pre(MDOC_ARGS)
 {
 	struct roffsu	 su;
-	struct htmlpair	 tag;
 
 	SCALE_VS_INIT(&su, 1);
 
@@ -1528,10 +1280,7 @@ mdoc_sp_pre(MDOC_ARGS)
 	} else
 		su.scale = 0.0;
 
-	bufinit(h);
-	bufcat_su(h, "height", &su);
-	PAIR_STYLE_INIT(&tag, h);
-	print_otag(h, TAG_DIV, 1, &tag);
+	print_otag(h, TAG_DIV, "suh", &su);
 
 	/* So the div isn't empty: */
 	print_text(h, "\\~");
@@ -1543,17 +1292,12 @@ mdoc_sp_pre(MDOC_ARGS)
 static int
 mdoc_lk_pre(MDOC_ARGS)
 {
-	struct htmlpair	 tag[2];
-
 	if (NULL == (n = n->child))
 		return 0;
 
 	assert(n->type == ROFFT_TEXT);
 
-	PAIR_CLASS_INIT(&tag[0], "link-ext");
-	PAIR_HREF_INIT(&tag[1], n->string);
-
-	print_otag(h, TAG_A, 2, tag);
+	print_otag(h, TAG_A, "ch", "Lk", n->string);
 
 	if (NULL == n->next)
 		print_text(h, n->string);
@@ -1567,22 +1311,17 @@ mdoc_lk_pre(MDOC_ARGS)
 static int
 mdoc_mt_pre(MDOC_ARGS)
 {
-	struct htmlpair	 tag[2];
 	struct tag	*t;
-
-	PAIR_CLASS_INIT(&tag[0], "link-mail");
+	char		*cp;
 
 	for (n = n->child; n; n = n->next) {
 		assert(n->type == ROFFT_TEXT);
 
-		bufinit(h);
-		bufcat(h, "mailto:");
-		bufcat(h, n->string);
-
-		PAIR_HREF_INIT(&tag[1], h->buf);
-		t = print_otag(h, TAG_A, 2, tag);
+		mandoc_asprintf(&cp, "mailto:%s", n->string);
+		t = print_otag(h, TAG_A, "ch", "Mt", cp);
 		print_text(h, n->string);
 		print_tagq(h, t);
+		free(cp);
 	}
 
 	return 0;
@@ -1591,7 +1330,6 @@ mdoc_mt_pre(MDOC_ARGS)
 static int
 mdoc_fo_pre(MDOC_ARGS)
 {
-	struct htmlpair	 tag;
 	struct tag	*t;
 
 	if (n->type == ROFFT_BODY) {
@@ -1608,8 +1346,7 @@ mdoc_fo_pre(MDOC_ARGS)
 		return 0;
 
 	assert(n->child->string);
-	PAIR_CLASS_INIT(&tag, "fname");
-	t = print_otag(h, TAG_B, 1, &tag);
+	t = print_otag(h, TAG_B, "c", "Fn");
 	print_text(h, n->child->string);
 	print_tagq(h, t);
 	return 0;
@@ -1631,13 +1368,9 @@ static int
 mdoc_in_pre(MDOC_ARGS)
 {
 	struct tag	*t;
-	struct htmlpair	 tag[2];
-	int		 i;
 
 	synopsis_pre(h, n);
-
-	PAIR_CLASS_INIT(&tag[0], "includes");
-	print_otag(h, TAG_B, 1, tag);
+	print_otag(h, TAG_B, "c", "In");
 
 	/*
 	 * The first argument of the `In' gets special treatment as
@@ -1646,7 +1379,7 @@ mdoc_in_pre(MDOC_ARGS)
 	 * of no children.
 	 */
 
-	if (MDOC_SYNPRETTY & n->flags && MDOC_LINE & n->flags)
+	if (NODE_SYNPRETTY & n->flags && NODE_LINE & n->flags)
 		print_text(h, "#include");
 
 	print_text(h, "<");
@@ -1655,16 +1388,10 @@ mdoc_in_pre(MDOC_ARGS)
 	if (NULL != (n = n->child)) {
 		assert(n->type == ROFFT_TEXT);
 
-		PAIR_CLASS_INIT(&tag[0], "link-includes");
-
-		i = 1;
-		if (h->base_includes) {
-			buffmt_includes(h, n->string);
-			PAIR_HREF_INIT(&tag[i], h->buf);
-			i++;
-		}
-
-		t = print_otag(h, TAG_A, i, tag);
+		if (h->base_includes)
+			t = print_otag(h, TAG_A, "chI", "In", n->string);
+		else
+			t = print_otag(h, TAG_A, "c", "In");
 		print_text(h, n->string);
 		print_tagq(h, t);
 
@@ -1685,75 +1412,14 @@ mdoc_in_pre(MDOC_ARGS)
 static int
 mdoc_ic_pre(MDOC_ARGS)
 {
-	struct htmlpair	tag;
-
-	PAIR_CLASS_INIT(&tag, "cmd");
-	print_otag(h, TAG_B, 1, &tag);
+	print_otag(h, TAG_B, "c", "Ic");
 	return 1;
 }
 
-static int
-mdoc_rv_pre(MDOC_ARGS)
-{
-	struct htmlpair	 tag;
-	struct tag	*t;
-	struct roff_node *nch;
-
-	if (n->prev)
-		print_otag(h, TAG_BR, 0, NULL);
-
-	PAIR_CLASS_INIT(&tag, "fname");
-
-	if (n->child != NULL) {
-		print_text(h, "The");
-
-		for (nch = n->child; nch != NULL; nch = nch->next) {
-			t = print_otag(h, TAG_B, 1, &tag);
-			print_text(h, nch->string);
-			print_tagq(h, t);
-
-			h->flags |= HTML_NOSPACE;
-			print_text(h, "()");
-
-			if (nch->next == NULL)
-				continue;
-
-			if (nch->prev != NULL || nch->next->next != NULL) {
-				h->flags |= HTML_NOSPACE;
-				print_text(h, ",");
-			}
-			if (nch->next->next == NULL)
-				print_text(h, "and");
-		}
-
-		if (n->child != NULL && n->child->next != NULL)
-			print_text(h, "functions return");
-		else
-			print_text(h, "function returns");
-
-		print_text(h, "the value\\~0 if successful;");
-	} else
-		print_text(h, "Upon successful completion,"
-                    " the value\\~0 is returned;");
-
-	print_text(h, "otherwise the value\\~\\-1 is returned"
-	   " and the global variable");
-
-	PAIR_CLASS_INIT(&tag, "var");
-	t = print_otag(h, TAG_B, 1, &tag);
-	print_text(h, "errno");
-	print_tagq(h, t);
-	print_text(h, "is set to indicate the error.");
-	return 0;
-}
-
 static int
 mdoc_va_pre(MDOC_ARGS)
 {
-	struct htmlpair	tag;
-
-	PAIR_CLASS_INIT(&tag, "var");
-	print_otag(h, TAG_B, 1, &tag);
+	print_otag(h, TAG_I, "c", "Va");
 	return 1;
 }
 
@@ -1770,8 +1436,7 @@ mdoc_ap_pre(MDOC_ARGS)
 static int
 mdoc_bf_pre(MDOC_ARGS)
 {
-	struct htmlpair	 tag[2];
-	struct roffsu	 su;
+	const char	*cattr;
 
 	if (n->type == ROFFT_HEAD)
 		return 0;
@@ -1779,35 +1444,27 @@ mdoc_bf_pre(MDOC_ARGS)
 		return 1;
 
 	if (FONT_Em == n->norm->Bf.font)
-		PAIR_CLASS_INIT(&tag[0], "emph");
+		cattr = "Em";
 	else if (FONT_Sy == n->norm->Bf.font)
-		PAIR_CLASS_INIT(&tag[0], "symb");
+		cattr = "Sy";
 	else if (FONT_Li == n->norm->Bf.font)
-		PAIR_CLASS_INIT(&tag[0], "lit");
+		cattr = "Li";
 	else
-		PAIR_CLASS_INIT(&tag[0], "none");
+		cattr = "none";
 
 	/*
 	 * We want this to be inline-formatted, but needs to be div to
 	 * accept block children.
 	 */
-	bufinit(h);
-	bufcat_style(h, "display", "inline");
-	SCALE_HS_INIT(&su, 1);
-	/* Needs a left-margin for spacing. */
-	bufcat_su(h, "margin-left", &su);
-	PAIR_STYLE_INIT(&tag[1], h);
-	print_otag(h, TAG_DIV, 2, tag);
+
+	print_otag(h, TAG_DIV, "css?hl", cattr, "display", "inline", 1);
 	return 1;
 }
 
 static int
 mdoc_ms_pre(MDOC_ARGS)
 {
-	struct htmlpair	tag;
-
-	PAIR_CLASS_INIT(&tag, "symb");
-	print_otag(h, TAG_SPAN, 1, &tag);
+	print_otag(h, TAG_B, "c", "Ms");
 	return 1;
 }
 
@@ -1823,154 +1480,116 @@ static void
 mdoc_pf_post(MDOC_ARGS)
 {
 
-	if ( ! (n->next == NULL || n->next->flags & MDOC_LINE))
+	if ( ! (n->next == NULL || n->next->flags & NODE_LINE))
 		h->flags |= HTML_NOSPACE;
 }
 
 static int
 mdoc_rs_pre(MDOC_ARGS)
 {
-	struct htmlpair	 tag;
-
 	if (n->type != ROFFT_BLOCK)
 		return 1;
 
 	if (n->prev && SEC_SEE_ALSO == n->sec)
 		print_paragraph(h);
 
-	PAIR_CLASS_INIT(&tag, "ref");
-	print_otag(h, TAG_SPAN, 1, &tag);
+	print_otag(h, TAG_SPAN, "c", "Rs");
 	return 1;
 }
 
 static int
 mdoc_no_pre(MDOC_ARGS)
 {
-	struct htmlpair	tag;
-
-	PAIR_CLASS_INIT(&tag, "none");
-	print_otag(h, TAG_CODE, 1, &tag);
+	print_otag(h, TAG_SPAN, "c", "No");
 	return 1;
 }
 
 static int
 mdoc_li_pre(MDOC_ARGS)
 {
-	struct htmlpair	tag;
-
-	PAIR_CLASS_INIT(&tag, "lit");
-	print_otag(h, TAG_CODE, 1, &tag);
+	print_otag(h, TAG_CODE, "c", "Li");
 	return 1;
 }
 
 static int
 mdoc_sy_pre(MDOC_ARGS)
 {
-	struct htmlpair	tag;
-
-	PAIR_CLASS_INIT(&tag, "symb");
-	print_otag(h, TAG_SPAN, 1, &tag);
+	print_otag(h, TAG_B, "c", "Sy");
 	return 1;
 }
 
-static int
-mdoc_bt_pre(MDOC_ARGS)
-{
-
-	print_text(h, "is currently in beta test.");
-	return 0;
-}
-
-static int
-mdoc_ud_pre(MDOC_ARGS)
-{
-
-	print_text(h, "currently under development.");
-	return 0;
-}
-
 static int
 mdoc_lb_pre(MDOC_ARGS)
 {
-	struct htmlpair	tag;
+	if (SEC_LIBRARY == n->sec && NODE_LINE & n->flags && n->prev)
+		print_otag(h, TAG_BR, "");
 
-	if (SEC_LIBRARY == n->sec && MDOC_LINE & n->flags && n->prev)
-		print_otag(h, TAG_BR, 0, NULL);
-
-	PAIR_CLASS_INIT(&tag, "lib");
-	print_otag(h, TAG_SPAN, 1, &tag);
+	print_otag(h, TAG_SPAN, "c", "Lb");
 	return 1;
 }
 
 static int
 mdoc__x_pre(MDOC_ARGS)
 {
-	struct htmlpair	tag[2];
-	enum htmltag	t;
+	const char	*cattr;
+	enum htmltag	 t;
 
 	t = TAG_SPAN;
 
 	switch (n->tok) {
 	case MDOC__A:
-		PAIR_CLASS_INIT(&tag[0], "ref-auth");
+		cattr = "RsA";
 		if (n->prev && MDOC__A == n->prev->tok)
 			if (NULL == n->next || MDOC__A != n->next->tok)
 				print_text(h, "and");
 		break;
 	case MDOC__B:
-		PAIR_CLASS_INIT(&tag[0], "ref-book");
 		t = TAG_I;
+		cattr = "RsB";
 		break;
 	case MDOC__C:
-		PAIR_CLASS_INIT(&tag[0], "ref-city");
+		cattr = "RsC";
 		break;
 	case MDOC__D:
-		PAIR_CLASS_INIT(&tag[0], "ref-date");
+		cattr = "RsD";
 		break;
 	case MDOC__I:
-		PAIR_CLASS_INIT(&tag[0], "ref-issue");
 		t = TAG_I;
+		cattr = "RsI";
 		break;
 	case MDOC__J:
-		PAIR_CLASS_INIT(&tag[0], "ref-jrnl");
 		t = TAG_I;
+		cattr = "RsJ";
 		break;
 	case MDOC__N:
-		PAIR_CLASS_INIT(&tag[0], "ref-num");
+		cattr = "RsN";
 		break;
 	case MDOC__O:
-		PAIR_CLASS_INIT(&tag[0], "ref-opt");
+		cattr = "RsO";
 		break;
 	case MDOC__P:
-		PAIR_CLASS_INIT(&tag[0], "ref-page");
+		cattr = "RsP";
 		break;
 	case MDOC__Q:
-		PAIR_CLASS_INIT(&tag[0], "ref-corp");
+		cattr = "RsQ";
 		break;
 	case MDOC__R:
-		PAIR_CLASS_INIT(&tag[0], "ref-rep");
+		cattr = "RsR";
 		break;
 	case MDOC__T:
-		PAIR_CLASS_INIT(&tag[0], "ref-title");
+		cattr = "RsT";
 		break;
 	case MDOC__U:
-		PAIR_CLASS_INIT(&tag[0], "link-ref");
-		break;
+		print_otag(h, TAG_A, "ch", "RsU", n->child->string);
+		return 1;
 	case MDOC__V:
-		PAIR_CLASS_INIT(&tag[0], "ref-vol");
+		cattr = "RsV";
 		break;
 	default:
 		abort();
 	}
 
-	if (MDOC__U != n->tok) {
-		print_otag(h, t, 1, tag);
-		return 1;
-	}
-
-	PAIR_HREF_INIT(&tag[1], n->child->string);
-	print_otag(h, TAG_A, 2, tag);
-
+	print_otag(h, t, "c", cattr);
 	return 1;
 }
 
@@ -2023,8 +1642,6 @@ mdoc_bk_post(MDOC_ARGS)
 static int
 mdoc_quote_pre(MDOC_ARGS)
 {
-	struct htmlpair	tag;
-
 	if (n->type != ROFFT_BODY)
 		return 1;
 
@@ -2046,8 +1663,7 @@ mdoc_quote_pre(MDOC_ARGS)
 	case MDOC_Op:
 		print_text(h, "\\(lB");
 		h->flags |= HTML_NOSPACE;
-		PAIR_CLASS_INIT(&tag, "opt");
-		print_otag(h, TAG_SPAN, 1, &tag);
+		print_otag(h, TAG_SPAN, "c", "Op");
 		break;
 	case MDOC_En:
 		if (NULL == n->norm->Es ||
@@ -2068,8 +1684,7 @@ mdoc_quote_pre(MDOC_ARGS)
 	case MDOC_Ql:
 		print_text(h, "\\(oq");
 		h->flags |= HTML_NOSPACE;
-		PAIR_CLASS_INIT(&tag, "lit");
-		print_otag(h, TAG_CODE, 1, &tag);
+		print_otag(h, TAG_CODE, "c", "Li");
 		break;
 	case MDOC_So:
 	case MDOC_Sq:
diff --git a/mdoc_macro.c b/mdoc_macro.c
index ca959589acd6..d4edcea14162 100644
--- a/mdoc_macro.c
+++ b/mdoc_macro.c
@@ -1,7 +1,7 @@
-/*	$Id: mdoc_macro.c,v 1.206 2015/10/20 02:01:32 schwarze Exp $ */
+/*	$Id: mdoc_macro.c,v 1.210 2017/01/10 13:47:00 schwarze Exp $ */
 /*
  * Copyright (c) 2008-2012 Kristaps Dzonsons 
- * Copyright (c) 2010, 2012-2015 Ingo Schwarze 
+ * Copyright (c) 2010, 2012-2016 Ingo Schwarze 
  *
  * Permission to use, copy, modify, and distribute this software for any
  * purpose with or without fee is hereby granted, provided that the above
@@ -216,7 +216,7 @@ mdoc_endparse(struct roff_man *mdoc)
 
 	/* Scan for open explicit scopes. */
 
-	n = mdoc->last->flags & MDOC_VALID ?
+	n = mdoc->last->flags & NODE_VALID ?
 	    mdoc->last->parent : mdoc->last;
 
 	for ( ; n; n = n->parent)
@@ -264,16 +264,16 @@ static void
 rew_last(struct roff_man *mdoc, const struct roff_node *to)
 {
 
-	if (to->flags & MDOC_VALID)
+	if (to->flags & NODE_VALID)
 		return;
 
 	while (mdoc->last != to) {
 		mdoc_state(mdoc, mdoc->last);
-		mdoc->last->flags |= MDOC_VALID | MDOC_ENDED;
+		mdoc->last->flags |= NODE_VALID | NODE_ENDED;
 		mdoc->last = mdoc->last->parent;
 	}
 	mdoc_state(mdoc, mdoc->last);
-	mdoc->last->flags |= MDOC_VALID | MDOC_ENDED;
+	mdoc->last->flags |= NODE_VALID | NODE_ENDED;
 	mdoc->next = ROFF_NEXT_SIBLING;
 }
 
@@ -292,13 +292,13 @@ rew_pending(struct roff_man *mdoc, const struct roff_node *n)
 			case ROFFT_HEAD:
 				roff_body_alloc(mdoc, n->line, n->pos,
 				    n->tok);
-				return;
+				break;
 			case ROFFT_BLOCK:
 				break;
 			default:
 				return;
 			}
-			if ( ! (n->flags & MDOC_BROKEN))
+			if ( ! (n->flags & NODE_BROKEN))
 				return;
 		} else
 			n = mdoc->last;
@@ -309,7 +309,7 @@ rew_pending(struct roff_man *mdoc, const struct roff_node *n)
 
 			if (n->type == ROFFT_BLOCK ||
 			    n->type == ROFFT_HEAD) {
-				if (n->flags & MDOC_ENDED)
+				if (n->flags & NODE_ENDED)
 					break;
 				else
 					return;
@@ -390,18 +390,18 @@ find_pending(struct roff_man *mdoc, int tok, int line, int ppos,
 
 	irc = 0;
 	for (n = mdoc->last; n != NULL && n != target; n = n->parent) {
-		if (n->flags & MDOC_ENDED) {
-			if ( ! (n->flags & MDOC_VALID))
-				n->flags |= MDOC_BROKEN;
+		if (n->flags & NODE_ENDED) {
+			if ( ! (n->flags & NODE_VALID))
+				n->flags |= NODE_BROKEN;
 			continue;
 		}
 		if (n->type == ROFFT_BLOCK &&
 		    mdoc_macros[n->tok].flags & MDOC_EXPLICIT) {
 			irc = 1;
-			n->flags = MDOC_BROKEN;
+			n->flags = NODE_BROKEN;
 			if (target->type == ROFFT_HEAD)
-				target->flags = MDOC_ENDED;
-			else if ( ! (target->flags & MDOC_ENDED)) {
+				target->flags = NODE_ENDED;
+			else if ( ! (target->flags & NODE_ENDED)) {
 				mandoc_vmsg(MANDOCERR_BLK_NEST,
 				    mdoc->parse, line, ppos,
 				    "%s breaks %s", mdoc_macronames[tok],
@@ -444,11 +444,11 @@ dword(struct roff_man *mdoc, int line, int col, const char *p,
 	 */
 
 	if (d == DELIM_OPEN)
-		mdoc->last->flags |= MDOC_DELIMO;
+		mdoc->last->flags |= NODE_DELIMO;
 	else if (d == DELIM_CLOSE &&
 	    ! (mdoc->flags & MDOC_NODELIMC) &&
 	    mdoc->last->parent->tok != MDOC_Fd)
-		mdoc->last->flags |= MDOC_DELIMC;
+		mdoc->last->flags |= NODE_DELIMC;
 	mdoc->flags &= ~MDOC_NODELIMC;
 }
 
@@ -481,7 +481,7 @@ append_delims(struct roff_man *mdoc, int line, int *pos, char *buf)
 		 */
 
 		if (mandoc_eos(p, strlen(p)))
-			mdoc->last->flags |= MDOC_EOS;
+			mdoc->last->flags |= NODE_EOS;
 	}
 }
 
@@ -549,29 +549,40 @@ blk_exp_close(MACRO_PROT_ARGS)
 		break;
 	}
 
+	/* Search backwards for the beginning of our own body. */
+
+	atok = rew_alt(tok);
+	body = NULL;
+	for (n = mdoc->last; n; n = n->parent) {
+		if (n->flags & NODE_ENDED || n->tok != atok ||
+		    n->type != ROFFT_BODY || n->end != ENDBODY_NOT)
+			continue;
+		body = n;
+		break;
+	}
+
 	/*
 	 * Search backwards for beginnings of blocks,
 	 * both of our own and of pending sub-blocks.
 	 */
 
-	atok = rew_alt(tok);
-	body = endbody = itblk = later = NULL;
+	endbody = itblk = later = NULL;
 	for (n = mdoc->last; n; n = n->parent) {
-		if (n->flags & MDOC_ENDED) {
-			if ( ! (n->flags & MDOC_VALID))
-				n->flags |= MDOC_BROKEN;
+		if (n->flags & NODE_ENDED) {
+			if ( ! (n->flags & NODE_VALID))
+				n->flags |= NODE_BROKEN;
 			continue;
 		}
 
-		/* Remember the start of our own body. */
+		/*
+		 * Mismatching end macros can never break anything,
+		 * SYNOPSIS name blocks can never be broken,
+		 * and we only care about the breaking of BLOCKs.
+		 */
 
-		if (n->type == ROFFT_BODY && atok == n->tok) {
-			if (n->end == ENDBODY_NOT)
-				body = n;
-			continue;
-		}
-
-		if (n->type != ROFFT_BLOCK || n->tok == MDOC_Nm)
+		if (body == NULL ||
+		    n->tok == MDOC_Nm ||
+		    n->type != ROFFT_BLOCK)
 			continue;
 
 		if (n->tok == MDOC_It) {
@@ -609,7 +620,7 @@ blk_exp_close(MACRO_PROT_ARGS)
 			    atok, body, ENDBODY_SPACE);
 
 			if (tok == MDOC_El)
-				itblk->flags |= MDOC_ENDED | MDOC_BROKEN;
+				itblk->flags |= NODE_ENDED | NODE_BROKEN;
 
 			/*
 			 * If a block closing macro taking arguments
@@ -631,7 +642,7 @@ blk_exp_close(MACRO_PROT_ARGS)
 
 		/* Breaking an open sub block. */
 
-		n->flags |= MDOC_BROKEN;
+		n->flags |= NODE_BROKEN;
 		if (later == NULL)
 			later = n;
 	}
@@ -639,8 +650,6 @@ blk_exp_close(MACRO_PROT_ARGS)
 	if (body == NULL) {
 		mandoc_msg(MANDOCERR_BLK_NOTOPEN, mdoc->parse,
 		    line, ppos, mdoc_macronames[tok]);
-		if (later != NULL)
-			later->flags &= ~MDOC_BROKEN;
 		if (maxargs && endbody == NULL) {
 			/*
 			 * Stray .Ec without previous .Eo:
@@ -697,11 +706,11 @@ blk_exp_close(MACRO_PROT_ARGS)
 	}
 
 	if (n != NULL) {
-		if (ntok != TOKEN_NONE && n->flags & MDOC_BROKEN) {
+		if (ntok != TOKEN_NONE && n->flags & NODE_BROKEN) {
 			target = n;
 			do
 				target = target->parent;
-			while ( ! (target->flags & MDOC_ENDED));
+			while ( ! (target->flags & NODE_ENDED));
 			pending = find_pending(mdoc, ntok, line, ppos,
 			    target);
 		} else
@@ -760,7 +769,7 @@ in_line(MACRO_PROT_ARGS)
 
 		if (ac == ARGS_EOLN) {
 			if (d == DELIM_OPEN)
-				mdoc->last->flags &= ~MDOC_DELIMO;
+				mdoc->last->flags &= ~NODE_DELIMO;
 			break;
 		}
 
@@ -854,7 +863,7 @@ in_line(MACRO_PROT_ARGS)
 		 */
 
 		if (firstarg && d == DELIM_CLOSE && !nc)
-			mdoc->last->flags &= ~MDOC_DELIMC;
+			mdoc->last->flags &= ~NODE_DELIMC;
 		firstarg = 0;
 
 		/*
@@ -917,9 +926,9 @@ blk_full(MACRO_PROT_ARGS)
 
 		blk = NULL;
 		for (n = mdoc->last; n != NULL; n = n->parent) {
-			if (n->flags & MDOC_ENDED) {
-				if ( ! (n->flags & MDOC_VALID))
-					n->flags |= MDOC_BROKEN;
+			if (n->flags & NODE_ENDED) {
+				if ( ! (n->flags & NODE_VALID))
+					n->flags |= NODE_BROKEN;
 				continue;
 			}
 			if (n->type != ROFFT_BLOCK)
@@ -1120,7 +1129,7 @@ blk_full(MACRO_PROT_ARGS)
 			break;
 	}
 
-	if (blk->flags & MDOC_VALID)
+	if (blk->flags & NODE_VALID)
 		return;
 	if (head == NULL)
 		head = roff_head_alloc(mdoc, line, ppos, tok);
@@ -1455,11 +1464,11 @@ phrase_ta(MACRO_PROT_ARGS)
 
 	body = NULL;
 	for (n = mdoc->last; n != NULL; n = n->parent) {
-		if (n->flags & MDOC_ENDED)
+		if (n->flags & NODE_ENDED)
 			continue;
 		if (n->tok == MDOC_It && n->type == ROFFT_BODY)
 			body = n;
-		if (n->tok == MDOC_Bl)
+		if (n->tok == MDOC_Bl && n->end == ENDBODY_NOT)
 			break;
 	}
 
diff --git a/mdoc_man.c b/mdoc_man.c
index ab245313492a..2c19e15fb8ba 100644
--- a/mdoc_man.c
+++ b/mdoc_man.c
@@ -1,6 +1,6 @@
-/*	$Id: mdoc_man.c,v 1.96 2016/01/08 17:48:09 schwarze Exp $ */
+/*	$Id: mdoc_man.c,v 1.101 2017/01/11 17:39:53 schwarze Exp $ */
 /*
- * Copyright (c) 2011-2016 Ingo Schwarze 
+ * Copyright (c) 2011-2017 Ingo Schwarze 
  *
  * Permission to use, copy, modify, and distribute this software for any
  * purpose with or without fee is hereby granted, provided that the above
@@ -79,7 +79,6 @@ static	int	  pre_bf(DECL_ARGS);
 static	int	  pre_bk(DECL_ARGS);
 static	int	  pre_bl(DECL_ARGS);
 static	int	  pre_br(DECL_ARGS);
-static	int	  pre_bx(DECL_ARGS);
 static	int	  pre_dl(DECL_ARGS);
 static	int	  pre_en(DECL_ARGS);
 static	int	  pre_enc(DECL_ARGS);
@@ -103,14 +102,12 @@ static	int	  pre_no(DECL_ARGS);
 static	int	  pre_ns(DECL_ARGS);
 static	int	  pre_pp(DECL_ARGS);
 static	int	  pre_rs(DECL_ARGS);
-static	int	  pre_rv(DECL_ARGS);
 static	int	  pre_sm(DECL_ARGS);
 static	int	  pre_sp(DECL_ARGS);
 static	int	  pre_sect(DECL_ARGS);
 static	int	  pre_sy(DECL_ARGS);
 static	void	  pre_syn(const struct roff_node *);
 static	int	  pre_vt(DECL_ARGS);
-static	int	  pre_ux(DECL_ARGS);
 static	int	  pre_xr(DECL_ARGS);
 static	void	  print_word(const char *);
 static	void	  print_line(const char *, int);
@@ -158,7 +155,7 @@ static	const struct manact manacts[MDOC_MAX + 1] = {
 	{ cond_body, pre_enc, post_enc, "[", "]" }, /* Op */
 	{ NULL, pre_ft, post_font, NULL, NULL }, /* Ot */
 	{ NULL, pre_em, post_font, NULL, NULL }, /* Pa */
-	{ NULL, pre_rv, NULL, NULL, NULL }, /* Rv */
+	{ NULL, pre_ex, NULL, NULL, NULL }, /* Rv */
 	{ NULL, NULL, NULL, NULL, NULL }, /* St */
 	{ NULL, pre_em, post_font, NULL, NULL }, /* Va */
 	{ NULL, pre_vt, post_vt, NULL, NULL }, /* Vt */
@@ -182,8 +179,8 @@ static	const struct manact manacts[MDOC_MAX + 1] = {
 	{ NULL, pre_bf, post_bf, NULL, NULL }, /* Bf */
 	{ cond_body, pre_enc, post_enc, "[", "]" }, /* Bo */
 	{ cond_body, pre_enc, post_enc, "[", "]" }, /* Bq */
-	{ NULL, pre_ux, NULL, "BSD/OS", NULL }, /* Bsx */
-	{ NULL, pre_bx, NULL, NULL, NULL }, /* Bx */
+	{ NULL, NULL, NULL, NULL, NULL }, /* Bsx */
+	{ NULL, NULL, NULL, NULL, NULL }, /* Bx */
 	{ NULL, pre_skip, NULL, NULL, NULL }, /* Db */
 	{ NULL, NULL, NULL, NULL, NULL }, /* Dc */
 	{ cond_body, pre_enc, post_enc, "\\(Lq", "\\(Rq" }, /* Do */
@@ -192,12 +189,12 @@ static	const struct manact manacts[MDOC_MAX + 1] = {
 	{ NULL, NULL, NULL, NULL, NULL }, /* Ef */
 	{ NULL, pre_em, post_font, NULL, NULL }, /* Em */
 	{ cond_body, pre_eo, post_eo, NULL, NULL }, /* Eo */
-	{ NULL, pre_ux, NULL, "FreeBSD", NULL }, /* Fx */
+	{ NULL, NULL, NULL, NULL, NULL }, /* Fx */
 	{ NULL, pre_sy, post_font, NULL, NULL }, /* Ms */
 	{ NULL, pre_no, NULL, NULL, NULL }, /* No */
 	{ NULL, pre_ns, NULL, NULL, NULL }, /* Ns */
-	{ NULL, pre_ux, NULL, "NetBSD", NULL }, /* Nx */
-	{ NULL, pre_ux, NULL, "OpenBSD", NULL }, /* Ox */
+	{ NULL, NULL, NULL, NULL, NULL }, /* Nx */
+	{ NULL, NULL, NULL, NULL, NULL }, /* Ox */
 	{ NULL, NULL, NULL, NULL, NULL }, /* Pc */
 	{ NULL, NULL, post_pf, NULL, NULL }, /* Pf */
 	{ cond_body, pre_enc, post_enc, "(", ")" }, /* Po */
@@ -215,7 +212,7 @@ static	const struct manact manacts[MDOC_MAX + 1] = {
 	{ NULL, pre_em, post_font, NULL, NULL }, /* Sx */
 	{ NULL, pre_sy, post_font, NULL, NULL }, /* Sy */
 	{ NULL, pre_li, post_font, NULL, NULL }, /* Tn */
-	{ NULL, pre_ux, NULL, "UNIX", NULL }, /* Ux */
+	{ NULL, NULL, NULL, NULL, NULL }, /* Ux */
 	{ NULL, NULL, NULL, NULL, NULL }, /* Xc */
 	{ NULL, NULL, NULL, NULL, NULL }, /* Xo */
 	{ NULL, pre_fo, post_fo, NULL, NULL }, /* Fo */
@@ -224,10 +221,10 @@ static	const struct manact manacts[MDOC_MAX + 1] = {
 	{ NULL, NULL, NULL, NULL, NULL }, /* Oc */
 	{ NULL, pre_bk, post_bk, NULL, NULL }, /* Bk */
 	{ NULL, NULL, NULL, NULL, NULL }, /* Ek */
-	{ NULL, pre_ux, NULL, "is currently in beta test.", NULL }, /* Bt */
+	{ NULL, NULL, NULL, NULL, NULL }, /* Bt */
 	{ NULL, NULL, NULL, NULL, NULL }, /* Hf */
 	{ NULL, pre_em, post_font, NULL, NULL }, /* Fr */
-	{ NULL, pre_ux, NULL, "currently under development.", NULL }, /* Ud */
+	{ NULL, NULL, NULL, NULL, NULL }, /* Ud */
 	{ NULL, NULL, post_lb, NULL, NULL }, /* Lb */
 	{ NULL, pre_pp, NULL, NULL, NULL }, /* Lp */
 	{ NULL, pre_lk, NULL, NULL, NULL }, /* Lk */
@@ -238,7 +235,7 @@ static	const struct manact manacts[MDOC_MAX + 1] = {
 	{ NULL, NULL, post_percent, NULL, NULL }, /* %C */
 	{ NULL, pre_skip, NULL, NULL, NULL }, /* Es */
 	{ cond_body, pre_en, post_en, NULL, NULL }, /* En */
-	{ NULL, pre_ux, NULL, "DragonFly", NULL }, /* Dx */
+	{ NULL, NULL, NULL, NULL, NULL }, /* Dx */
 	{ NULL, NULL, post_percent, NULL, NULL }, /* %Q */
 	{ NULL, pre_br, NULL, NULL, NULL }, /* br */
 	{ NULL, pre_sp, post_sp, NULL, NULL }, /* sp */
@@ -575,17 +572,20 @@ print_node(DECL_ARGS)
 	struct roff_node	*sub;
 	int			 cond, do_sub;
 
+	if (n->flags & NODE_NOPRT)
+		return;
+
 	/*
 	 * Break the line if we were parsed subsequent the current node.
 	 * This makes the page structure be more consistent.
 	 */
-	if (MMAN_spc & outflags && MDOC_LINE & n->flags)
+	if (MMAN_spc & outflags && NODE_LINE & n->flags)
 		outflags |= MMAN_nl;
 
 	act = NULL;
 	cond = 0;
 	do_sub = 1;
-	n->flags &= ~MDOC_ENDED;
+	n->flags &= ~NODE_ENDED;
 
 	if (n->type == ROFFT_TEXT) {
 		/*
@@ -598,10 +598,14 @@ print_node(DECL_ARGS)
 			printf("\\&");
 			outflags &= ~MMAN_spc;
 		}
-		if (outflags & MMAN_Sm && ! (n->flags & MDOC_DELIMC))
+		if (n->flags & NODE_DELIMC)
+			outflags &= ~(MMAN_spc | MMAN_spc_force);
+		else if (outflags & MMAN_Sm)
 			outflags |= MMAN_spc_force;
 		print_word(n->string);
-		if (outflags & MMAN_Sm && ! (n->flags & MDOC_DELIMO))
+		if (n->flags & NODE_DELIMO)
+			outflags &= ~(MMAN_spc | MMAN_spc_force);
+		else if (outflags & MMAN_Sm)
 			outflags |= MMAN_spc;
 	} else {
 		/*
@@ -627,14 +631,14 @@ print_node(DECL_ARGS)
 	/*
 	 * Lastly, conditionally run the post-node handler.
 	 */
-	if (MDOC_ENDED & n->flags)
+	if (NODE_ENDED & n->flags)
 		return;
 
 	if (cond && act->post)
 		(*act->post)(meta, n);
 
 	if (ENDBODY_NOT != n->end)
-		n->body->flags |= MDOC_ENDED;
+		n->body->flags |= NODE_ENDED;
 
 	if (ENDBODY_NOSPACE == n->end)
 		outflags &= ~(MMAN_spc | MMAN_nl);
@@ -682,36 +686,8 @@ post_enc(DECL_ARGS)
 static int
 pre_ex(DECL_ARGS)
 {
-	struct roff_node *nch;
-
 	outflags |= MMAN_br | MMAN_nl;
-
-	print_word("The");
-
-	for (nch = n->child; nch != NULL; nch = nch->next) {
-		font_push('B');
-		print_word(nch->string);
-		font_pop();
-
-		if (nch->next == NULL)
-			continue;
-
-		if (nch->prev != NULL || nch->next->next != NULL) {
-			outflags &= ~MMAN_spc;
-			print_word(",");
-		}
-		if (nch->next->next == NULL)
-			print_word("and");
-	}
-
-	if (n->child != NULL && n->child->next != NULL)
-		print_word("utilities exit\\~0");
-	else
-		print_word("utility exits\\~0");
-
-	print_word("on success, and\\~>0 if an error occurs.");
-	outflags |= MMAN_nl;
-	return 0;
+	return 1;
 }
 
 static void
@@ -805,7 +781,7 @@ static void
 pre_syn(const struct roff_node *n)
 {
 
-	if (NULL == n->prev || ! (MDOC_SYNPRETTY & n->flags))
+	if (NULL == n->prev || ! (NODE_SYNPRETTY & n->flags))
 		return;
 
 	if (n->prev->tok == n->tok &&
@@ -1050,26 +1026,6 @@ pre_br(DECL_ARGS)
 	return 0;
 }
 
-static int
-pre_bx(DECL_ARGS)
-{
-
-	n = n->child;
-	if (n) {
-		print_word(n->string);
-		outflags &= ~MMAN_spc;
-		n = n->next;
-	}
-	print_word("BSD");
-	if (NULL == n)
-		return 0;
-	outflags &= ~MMAN_spc;
-	print_word("-");
-	outflags &= ~MMAN_spc;
-	print_word(n->string);
-	return 0;
-}
-
 static int
 pre_dl(DECL_ARGS)
 {
@@ -1173,7 +1129,7 @@ pre_fa(DECL_ARGS)
 
 	while (NULL != n) {
 		font_push('I');
-		if (am_Fa || MDOC_SYNPRETTY & n->flags)
+		if (am_Fa || NODE_SYNPRETTY & n->flags)
 			outflags |= MMAN_nbrword;
 		print_node(meta, n);
 		font_pop();
@@ -1227,7 +1183,7 @@ post_fl(DECL_ARGS)
 	if (!(n->child != NULL ||
 	    n->next == NULL ||
 	    n->next->type == ROFFT_TEXT ||
-	    n->next->flags & MDOC_LINE))
+	    n->next->flags & NODE_LINE))
 		outflags &= ~MMAN_spc;
 }
 
@@ -1241,7 +1197,7 @@ pre_fn(DECL_ARGS)
 	if (NULL == n)
 		return 0;
 
-	if (MDOC_SYNPRETTY & n->flags)
+	if (NODE_SYNPRETTY & n->flags)
 		print_block(".HP 4n", MMAN_nl);
 
 	font_push('B');
@@ -1262,7 +1218,7 @@ post_fn(DECL_ARGS)
 {
 
 	print_word(")");
-	if (MDOC_SYNPRETTY & n->flags) {
+	if (NODE_SYNPRETTY & n->flags) {
 		print_word(";");
 		outflags |= MMAN_PP;
 	}
@@ -1279,7 +1235,7 @@ pre_fo(DECL_ARGS)
 	case ROFFT_HEAD:
 		if (n->child == NULL)
 			return 0;
-		if (MDOC_SYNPRETTY & n->flags)
+		if (NODE_SYNPRETTY & n->flags)
 			print_block(".HP 4n", MMAN_nl);
 		font_push('B');
 		break;
@@ -1324,7 +1280,7 @@ static int
 pre_in(DECL_ARGS)
 {
 
-	if (MDOC_SYNPRETTY & n->flags) {
+	if (NODE_SYNPRETTY & n->flags) {
 		pre_syn(n);
 		font_push('B');
 		print_word("#include <");
@@ -1341,7 +1297,7 @@ static void
 post_in(DECL_ARGS)
 {
 
-	if (MDOC_SYNPRETTY & n->flags) {
+	if (NODE_SYNPRETTY & n->flags) {
 		outflags &= ~MMAN_spc;
 		print_word(">");
 		font_pop();
@@ -1616,7 +1572,7 @@ static void
 post_pf(DECL_ARGS)
 {
 
-	if ( ! (n->next == NULL || n->next->flags & MDOC_LINE))
+	if ( ! (n->next == NULL || n->next->flags & NODE_LINE))
 		outflags &= ~MMAN_spc;
 }
 
@@ -1642,57 +1598,6 @@ pre_rs(DECL_ARGS)
 	return 1;
 }
 
-static int
-pre_rv(DECL_ARGS)
-{
-	struct roff_node *nch;
-
-	outflags |= MMAN_br | MMAN_nl;
-
-	if (n->child != NULL) {
-		print_word("The");
-
-		for (nch = n->child; nch != NULL; nch = nch->next) {
-			font_push('B');
-			print_word(nch->string);
-			font_pop();
-
-			outflags &= ~MMAN_spc;
-			print_word("()");
-
-			if (nch->next == NULL)
-				continue;
-
-			if (nch->prev != NULL || nch->next->next != NULL) {
-				outflags &= ~MMAN_spc;
-				print_word(",");
-			}
-			if (nch->next->next == NULL)
-				print_word("and");
-		}
-
-		if (n->child != NULL && n->child->next != NULL)
-			print_word("functions return");
-		else
-			print_word("function returns");
-
-		print_word("the value\\~0 if successful;");
-	} else
-		print_word("Upon successful completion, "
-		    "the value\\~0 is returned;");
-
-	print_word("otherwise the value\\~\\-1 is returned"
-	    " and the global variable");
-
-	font_push('I');
-	print_word("errno");
-	font_pop();
-
-	print_word("is set to indicate the error.");
-	outflags |= MMAN_nl;
-	return 0;
-}
-
 static int
 pre_skip(DECL_ARGS)
 {
@@ -1748,7 +1653,7 @@ static int
 pre_vt(DECL_ARGS)
 {
 
-	if (MDOC_SYNPRETTY & n->flags) {
+	if (NODE_SYNPRETTY & n->flags) {
 		switch (n->type) {
 		case ROFFT_BLOCK:
 			pre_syn(n);
@@ -1767,7 +1672,7 @@ static void
 post_vt(DECL_ARGS)
 {
 
-	if (n->flags & MDOC_SYNPRETTY && n->type != ROFFT_BODY)
+	if (n->flags & NODE_SYNPRETTY && n->type != ROFFT_BODY)
 		return;
 	font_pop();
 }
@@ -1789,16 +1694,3 @@ pre_xr(DECL_ARGS)
 	print_word(")");
 	return 0;
 }
-
-static int
-pre_ux(DECL_ARGS)
-{
-
-	print_word(manacts[n->tok].prefix);
-	if (NULL == n->child)
-		return 0;
-	outflags &= ~MMAN_spc;
-	print_word("\\ ");
-	outflags &= ~MMAN_spc;
-	return 1;
-}
diff --git a/mdoc_state.c b/mdoc_state.c
index cbd7376309e8..4e376ef07347 100644
--- a/mdoc_state.c
+++ b/mdoc_state.c
@@ -1,4 +1,4 @@
-/*	$Id: mdoc_state.c,v 1.3 2015/10/30 18:53:54 schwarze Exp $ */
+/*	$Id: mdoc_state.c,v 1.4 2017/01/10 13:47:00 schwarze Exp $ */
 /*
  * Copyright (c) 2014, 2015 Ingo Schwarze 
  *
@@ -249,7 +249,7 @@ state_sh(STATE_ARGS)
 	if (n->type != ROFFT_HEAD)
 		return;
 
-	if ( ! (n->flags & MDOC_VALID)) {
+	if ( ! (n->flags & NODE_VALID)) {
 		secname = NULL;
 		deroff(&secname, n);
 
diff --git a/mdoc_term.c b/mdoc_term.c
index e846436273da..dad54c8c44f9 100644
--- a/mdoc_term.c
+++ b/mdoc_term.c
@@ -1,7 +1,7 @@
-/*	$Id: mdoc_term.c,v 1.331 2016/01/08 17:48:09 schwarze Exp $ */
+/*	$Id: mdoc_term.c,v 1.341 2017/01/11 17:39:53 schwarze Exp $ */
 /*
  * Copyright (c) 2008, 2009, 2010, 2011 Kristaps Dzonsons 
- * Copyright (c) 2010, 2012-2016 Ingo Schwarze 
+ * Copyright (c) 2010, 2012-2017 Ingo Schwarze 
  * Copyright (c) 2013 Franco Fichtner 
  *
  * Permission to use, copy, modify, and distribute this software for any
@@ -80,6 +80,7 @@ static	void	  termp_pf_post(DECL_ARGS);
 static	void	  termp_quote_post(DECL_ARGS);
 static	void	  termp_sh_post(DECL_ARGS);
 static	void	  termp_ss_post(DECL_ARGS);
+static	void	  termp_xx_post(DECL_ARGS);
 
 static	int	  termp__a_pre(DECL_ARGS);
 static	int	  termp__t_pre(DECL_ARGS);
@@ -90,11 +91,10 @@ static	int	  termp_bf_pre(DECL_ARGS);
 static	int	  termp_bk_pre(DECL_ARGS);
 static	int	  termp_bl_pre(DECL_ARGS);
 static	int	  termp_bold_pre(DECL_ARGS);
-static	int	  termp_bt_pre(DECL_ARGS);
-static	int	  termp_bx_pre(DECL_ARGS);
 static	int	  termp_cd_pre(DECL_ARGS);
 static	int	  termp_d1_pre(DECL_ARGS);
 static	int	  termp_eo_pre(DECL_ARGS);
+static	int	  termp_em_pre(DECL_ARGS);
 static	int	  termp_er_pre(DECL_ARGS);
 static	int	  termp_ex_pre(DECL_ARGS);
 static	int	  termp_fa_pre(DECL_ARGS);
@@ -113,15 +113,14 @@ static	int	  termp_nm_pre(DECL_ARGS);
 static	int	  termp_ns_pre(DECL_ARGS);
 static	int	  termp_quote_pre(DECL_ARGS);
 static	int	  termp_rs_pre(DECL_ARGS);
-static	int	  termp_rv_pre(DECL_ARGS);
 static	int	  termp_sh_pre(DECL_ARGS);
 static	int	  termp_skip_pre(DECL_ARGS);
 static	int	  termp_sm_pre(DECL_ARGS);
 static	int	  termp_sp_pre(DECL_ARGS);
 static	int	  termp_ss_pre(DECL_ARGS);
+static	int	  termp_sy_pre(DECL_ARGS);
 static	int	  termp_tag_pre(DECL_ARGS);
 static	int	  termp_under_pre(DECL_ARGS);
-static	int	  termp_ud_pre(DECL_ARGS);
 static	int	  termp_vt_pre(DECL_ARGS);
 static	int	  termp_xr_pre(DECL_ARGS);
 static	int	  termp_xx_pre(DECL_ARGS);
@@ -163,7 +162,7 @@ static	const struct termact termacts[MDOC_MAX] = {
 	{ termp_quote_pre, termp_quote_post }, /* Op */
 	{ termp_ft_pre, NULL }, /* Ot */
 	{ termp_under_pre, NULL }, /* Pa */
-	{ termp_rv_pre, NULL }, /* Rv */
+	{ termp_ex_pre, NULL }, /* Rv */
 	{ NULL, NULL }, /* St */
 	{ termp_under_pre, NULL }, /* Va */
 	{ termp_vt_pre, NULL }, /* Vt */
@@ -187,22 +186,22 @@ static	const struct termact termacts[MDOC_MAX] = {
 	{ termp_bf_pre, NULL }, /* Bf */
 	{ termp_quote_pre, termp_quote_post }, /* Bo */
 	{ termp_quote_pre, termp_quote_post }, /* Bq */
-	{ termp_xx_pre, NULL }, /* Bsx */
-	{ termp_bx_pre, NULL }, /* Bx */
+	{ termp_xx_pre, termp_xx_post }, /* Bsx */
+	{ NULL, NULL }, /* Bx */
 	{ termp_skip_pre, NULL }, /* Db */
 	{ NULL, NULL }, /* Dc */
 	{ termp_quote_pre, termp_quote_post }, /* Do */
 	{ termp_quote_pre, termp_quote_post }, /* Dq */
 	{ NULL, NULL }, /* Ec */ /* FIXME: no space */
 	{ NULL, NULL }, /* Ef */
-	{ termp_under_pre, NULL }, /* Em */
+	{ termp_em_pre, NULL }, /* Em */
 	{ termp_eo_pre, termp_eo_post }, /* Eo */
-	{ termp_xx_pre, NULL }, /* Fx */
+	{ termp_xx_pre, termp_xx_post }, /* Fx */
 	{ termp_bold_pre, NULL }, /* Ms */
 	{ termp_li_pre, NULL }, /* No */
 	{ termp_ns_pre, NULL }, /* Ns */
-	{ termp_xx_pre, NULL }, /* Nx */
-	{ termp_xx_pre, NULL }, /* Ox */
+	{ termp_xx_pre, termp_xx_post }, /* Nx */
+	{ termp_xx_pre, termp_xx_post }, /* Ox */
 	{ NULL, NULL }, /* Pc */
 	{ NULL, termp_pf_post }, /* Pf */
 	{ termp_quote_pre, termp_quote_post }, /* Po */
@@ -218,9 +217,9 @@ static	const struct termact termacts[MDOC_MAX] = {
 	{ termp_quote_pre, termp_quote_post }, /* Sq */
 	{ termp_sm_pre, NULL }, /* Sm */
 	{ termp_under_pre, NULL }, /* Sx */
-	{ termp_bold_pre, NULL }, /* Sy */
+	{ termp_sy_pre, NULL }, /* Sy */
 	{ NULL, NULL }, /* Tn */
-	{ termp_xx_pre, NULL }, /* Ux */
+	{ termp_xx_pre, termp_xx_post }, /* Ux */
 	{ NULL, NULL }, /* Xc */
 	{ NULL, NULL }, /* Xo */
 	{ termp_fo_pre, termp_fo_post }, /* Fo */
@@ -229,10 +228,10 @@ static	const struct termact termacts[MDOC_MAX] = {
 	{ NULL, NULL }, /* Oc */
 	{ termp_bk_pre, termp_bk_post }, /* Bk */
 	{ NULL, NULL }, /* Ek */
-	{ termp_bt_pre, NULL }, /* Bt */
+	{ NULL, NULL }, /* Bt */
 	{ NULL, NULL }, /* Hf */
 	{ termp_under_pre, NULL }, /* Fr */
-	{ termp_ud_pre, NULL }, /* Ud */
+	{ NULL, NULL }, /* Ud */
 	{ NULL, termp_lb_post }, /* Lb */
 	{ termp_sp_pre, NULL }, /* Lp */
 	{ termp_lk_pre, NULL }, /* Lk */
@@ -243,7 +242,7 @@ static	const struct termact termacts[MDOC_MAX] = {
 	{ NULL, termp____post }, /* %C */
 	{ termp_skip_pre, NULL }, /* Es */
 	{ termp_quote_pre, termp_quote_post }, /* En */
-	{ termp_xx_pre, NULL }, /* Dx */
+	{ termp_xx_pre, termp_xx_post }, /* Dx */
 	{ NULL, termp____post }, /* %Q */
 	{ termp_sp_pre, NULL }, /* br */
 	{ termp_sp_pre, NULL }, /* sp */
@@ -283,6 +282,8 @@ terminal_mdoc(void *arg, const struct roff_man *mdoc)
 			p->defindent = 5;
 		term_begin(p, print_mdoc_head, print_mdoc_foot,
 		    &mdoc->meta);
+		while (n != NULL && n->flags & NODE_NOPRT)
+			n = n->next;
 		if (n != NULL) {
 			if (n->tok != MDOC_Sh)
 				term_vspace(p);
@@ -309,10 +310,13 @@ print_mdoc_node(DECL_ARGS)
 	struct termpair	 npair;
 	size_t		 offset, rmargin;
 
+	if (n->flags & NODE_NOPRT)
+		return;
+
 	chld = 1;
 	offset = p->offset;
 	rmargin = p->rmargin;
-	n->flags &= ~MDOC_ENDED;
+	n->flags &= ~NODE_ENDED;
 	n->prev_font = p->fonti;
 
 	memset(&npair, 0, sizeof(struct termpair));
@@ -323,7 +327,7 @@ print_mdoc_node(DECL_ARGS)
 	 * invoked in a prior line, revert it to PREKEEP.
 	 */
 
-	if (p->flags & TERMP_KEEP && n->flags & MDOC_LINE) {
+	if (p->flags & TERMP_KEEP && n->flags & NODE_LINE) {
 		p->flags &= ~TERMP_KEEP;
 		p->flags |= TERMP_PREKEEP;
 	}
@@ -335,19 +339,19 @@ print_mdoc_node(DECL_ARGS)
 
 	switch (n->type) {
 	case ROFFT_TEXT:
-		if (' ' == *n->string && MDOC_LINE & n->flags)
+		if (' ' == *n->string && NODE_LINE & n->flags)
 			term_newln(p);
-		if (MDOC_DELIMC & n->flags)
+		if (NODE_DELIMC & n->flags)
 			p->flags |= TERMP_NOSPACE;
 		term_word(p, n->string);
-		if (MDOC_DELIMO & n->flags)
+		if (NODE_DELIMO & n->flags)
 			p->flags |= TERMP_NOSPACE;
 		break;
 	case ROFFT_EQN:
-		if ( ! (n->flags & MDOC_LINE))
+		if ( ! (n->flags & NODE_LINE))
 			p->flags |= TERMP_NOSPACE;
 		term_eqn(p, n->eqn);
-		if (n->next != NULL && ! (n->next->flags & MDOC_LINE))
+		if (n->next != NULL && ! (n->next->flags & NODE_LINE))
 			p->flags |= TERMP_NOSPACE;
 		break;
 	case ROFFT_TBL:
@@ -377,7 +381,7 @@ print_mdoc_node(DECL_ARGS)
 	case ROFFT_EQN:
 		break;
 	default:
-		if ( ! termacts[n->tok].post || MDOC_ENDED & n->flags)
+		if ( ! termacts[n->tok].post || NODE_ENDED & n->flags)
 			break;
 		(void)(*termacts[n->tok].post)(p, &npair, meta, n);
 
@@ -387,7 +391,7 @@ print_mdoc_node(DECL_ARGS)
 		 * that it must not call the post handler again.
 		 */
 		if (ENDBODY_NOT != n->end)
-			n->body->flags |= MDOC_ENDED;
+			n->body->flags |= NODE_ENDED;
 
 		/*
 		 * End of line terminating an implicit block
@@ -399,7 +403,7 @@ print_mdoc_node(DECL_ARGS)
 		break;
 	}
 
-	if (MDOC_EOS & n->flags)
+	if (NODE_EOS & n->flags)
 		p->flags |= TERMP_SENTENCE;
 
 	if (MDOC_ll != n->tok) {
@@ -562,6 +566,8 @@ print_bvspace(struct termp *p,
 	/* Do not vspace directly after Ss/Sh. */
 
 	nn = n;
+	while (nn->prev != NULL && nn->prev->flags & NODE_NOPRT)
+		nn = nn->prev;
 	while (nn->prev == NULL) {
 		do {
 			nn = nn->parent;
@@ -631,10 +637,10 @@ termp_it_pre(DECL_ARGS)
 		width = term_len(p, 2);
 		break;
 	case LIST_hang:
+	case LIST_tag:
 		width = term_len(p, 8);
 		break;
 	case LIST_column:
-	case LIST_tag:
 		width = term_len(p, 10);
 		break;
 	default:
@@ -1042,7 +1048,7 @@ termp_fl_pre(DECL_ARGS)
 	if (!(n->child == NULL &&
 	    (n->next == NULL ||
 	     n->next->type == ROFFT_TEXT ||
-	     n->next->flags & MDOC_LINE)))
+	     n->next->flags & NODE_LINE)))
 		p->flags |= TERMP_NOSPACE;
 
 	return 1;
@@ -1087,7 +1093,7 @@ static int
 termp_ns_pre(DECL_ARGS)
 {
 
-	if ( ! (MDOC_LINE & n->flags))
+	if ( ! (NODE_LINE & n->flags))
 		p->flags |= TERMP_NOSPACE;
 	return 1;
 }
@@ -1103,92 +1109,11 @@ termp_rs_pre(DECL_ARGS)
 	return 1;
 }
 
-static int
-termp_rv_pre(DECL_ARGS)
-{
-	struct roff_node *nch;
-
-	term_newln(p);
-
-	if (n->child != NULL) {
-		term_word(p, "The");
-
-		for (nch = n->child; nch != NULL; nch = nch->next) {
-			term_fontpush(p, TERMFONT_BOLD);
-			term_word(p, nch->string);
-			term_fontpop(p);
-
-			p->flags |= TERMP_NOSPACE;
-			term_word(p, "()");
-
-			if (nch->next == NULL)
-				continue;
-
-			if (nch->prev != NULL || nch->next->next != NULL) {
-				p->flags |= TERMP_NOSPACE;
-				term_word(p, ",");
-			}
-			if (nch->next->next == NULL)
-				term_word(p, "and");
-		}
-
-		if (n->child != NULL && n->child->next != NULL)
-			term_word(p, "functions return");
-		else
-			term_word(p, "function returns");
-
-		term_word(p, "the value\\~0 if successful;");
-	} else
-		term_word(p, "Upon successful completion,"
-		    " the value\\~0 is returned;");
-
-	term_word(p, "otherwise the value\\~\\-1 is returned"
-	    " and the global variable");
-
-	term_fontpush(p, TERMFONT_UNDER);
-	term_word(p, "errno");
-	term_fontpop(p);
-
-	term_word(p, "is set to indicate the error.");
-	p->flags |= TERMP_SENTENCE;
-
-	return 0;
-}
-
 static int
 termp_ex_pre(DECL_ARGS)
 {
-	struct roff_node *nch;
-
 	term_newln(p);
-	term_word(p, "The");
-
-	for (nch = n->child; nch != NULL; nch = nch->next) {
-		term_fontpush(p, TERMFONT_BOLD);
-		term_word(p, nch->string);
-		term_fontpop(p);
-
-		if (nch->next == NULL)
-			continue;
-
-		if (nch->prev != NULL || nch->next->next != NULL) {
-			p->flags |= TERMP_NOSPACE;
-			term_word(p, ",");
-		}
-
-		if (nch->next->next == NULL)
-			term_word(p, "and");
-	}
-
-	if (n->child != NULL && n->child->next != NULL)
-		term_word(p, "utilities exit\\~0");
-	else
-		term_word(p, "utility exits\\~0");
-
-	term_word(p, "on success, and\\~>0 if an error occurs.");
-
-	p->flags |= TERMP_SENTENCE;
-	return 0;
+	return 1;
 }
 
 static int
@@ -1253,7 +1178,7 @@ synopsis_pre(struct termp *p, const struct roff_node *n)
 	 * Obviously, if we're not in a SYNOPSIS or no prior macros
 	 * exist, do nothing.
 	 */
-	if (NULL == n->prev || ! (MDOC_SYNPRETTY & n->flags))
+	if (NULL == n->prev || ! (NODE_SYNPRETTY & n->flags))
 		return;
 
 	/*
@@ -1389,32 +1314,14 @@ termp_sh_post(DECL_ARGS)
 	}
 }
 
-static int
-termp_bt_pre(DECL_ARGS)
-{
-
-	term_word(p, "is currently in beta test.");
-	p->flags |= TERMP_SENTENCE;
-	return 0;
-}
-
 static void
 termp_lb_post(DECL_ARGS)
 {
 
-	if (SEC_LIBRARY == n->sec && MDOC_LINE & n->flags)
+	if (SEC_LIBRARY == n->sec && NODE_LINE & n->flags)
 		term_newln(p);
 }
 
-static int
-termp_ud_pre(DECL_ARGS)
-{
-
-	term_word(p, "currently under development.");
-	p->flags |= TERMP_SENTENCE;
-	return 0;
-}
-
 static int
 termp_d1_pre(DECL_ARGS)
 {
@@ -1430,7 +1337,7 @@ static int
 termp_ft_pre(DECL_ARGS)
 {
 
-	/* NB: MDOC_LINE does not effect this! */
+	/* NB: NODE_LINE does not effect this! */
 	synopsis_pre(p, n);
 	term_fontpush(p, TERMFONT_UNDER);
 	return 1;
@@ -1442,7 +1349,7 @@ termp_fn_pre(DECL_ARGS)
 	size_t		 rmargin = 0;
 	int		 pretty;
 
-	pretty = MDOC_SYNPRETTY & n->flags;
+	pretty = NODE_SYNPRETTY & n->flags;
 
 	synopsis_pre(p, n);
 
@@ -1460,7 +1367,7 @@ termp_fn_pre(DECL_ARGS)
 	term_word(p, n->string);
 	term_fontpop(p);
 
-	if (n->sec == SEC_DESCRIPTION)
+	if (n->sec == SEC_DESCRIPTION || n->sec == SEC_CUSTOM)
 		tag_put(n->string, ++fn_prio, p->line);
 
 	if (pretty) {
@@ -1608,7 +1515,7 @@ termp_bd_pre(DECL_ARGS)
 			break;
 		}
 		if (p->flags & TERMP_NONEWLINE ||
-		    (nn->next && ! (nn->next->flags & MDOC_LINE)))
+		    (nn->next && ! (nn->next->flags & NODE_LINE)))
 			continue;
 		term_flushln(p);
 		p->flags |= TERMP_NOSPACE;
@@ -1642,85 +1549,41 @@ termp_bd_post(DECL_ARGS)
 	p->maxrmargin = rmax;
 }
 
-static int
-termp_bx_pre(DECL_ARGS)
-{
-
-	if (NULL != (n = n->child)) {
-		term_word(p, n->string);
-		p->flags |= TERMP_NOSPACE;
-		term_word(p, "BSD");
-	} else {
-		term_word(p, "BSD");
-		return 0;
-	}
-
-	if (NULL != (n = n->next)) {
-		p->flags |= TERMP_NOSPACE;
-		term_word(p, "-");
-		p->flags |= TERMP_NOSPACE;
-		term_word(p, n->string);
-	}
-
-	return 0;
-}
-
 static int
 termp_xx_pre(DECL_ARGS)
 {
-	const char	*pp;
-	int		 flags;
+	if ((n->aux = p->flags & TERMP_PREKEEP) == 0)
+		p->flags |= TERMP_PREKEEP;
+	return 1;
+}
 
-	pp = NULL;
-	switch (n->tok) {
-	case MDOC_Bsx:
-		pp = "BSD/OS";
-		break;
-	case MDOC_Dx:
-		pp = "DragonFly";
-		break;
-	case MDOC_Fx:
-		pp = "FreeBSD";
-		break;
-	case MDOC_Nx:
-		pp = "NetBSD";
-		break;
-	case MDOC_Ox:
-		pp = "OpenBSD";
-		break;
-	case MDOC_Ux:
-		pp = "UNIX";
-		break;
-	default:
-		abort();
-	}
-
-	term_word(p, pp);
-	if (n->child) {
-		flags = p->flags;
-		p->flags |= TERMP_KEEP;
-		term_word(p, n->child->string);
-		p->flags = flags;
-	}
-	return 0;
+static void
+termp_xx_post(DECL_ARGS)
+{
+	if (n->aux == 0)
+		p->flags &= ~(TERMP_KEEP | TERMP_PREKEEP);
 }
 
 static void
 termp_pf_post(DECL_ARGS)
 {
 
-	if ( ! (n->next == NULL || n->next->flags & MDOC_LINE))
+	if ( ! (n->next == NULL || n->next->flags & NODE_LINE))
 		p->flags |= TERMP_NOSPACE;
 }
 
 static int
 termp_ss_pre(DECL_ARGS)
 {
+	struct roff_node *nn;
 
 	switch (n->type) {
 	case ROFFT_BLOCK:
 		term_newln(p);
-		if (n->prev)
+		for (nn = n->prev; nn != NULL; nn = nn->prev)
+			if ((nn->flags & NODE_NOPRT) == 0)
+				break;
+		if (nn != NULL)
 			term_vspace(p);
 		break;
 	case ROFFT_HEAD:
@@ -1760,7 +1623,7 @@ termp_in_pre(DECL_ARGS)
 
 	synopsis_pre(p, n);
 
-	if (MDOC_SYNPRETTY & n->flags && MDOC_LINE & n->flags) {
+	if (NODE_SYNPRETTY & n->flags && NODE_LINE & n->flags) {
 		term_fontpush(p, TERMFONT_BOLD);
 		term_word(p, "#include");
 		term_word(p, "<");
@@ -1777,13 +1640,13 @@ static void
 termp_in_post(DECL_ARGS)
 {
 
-	if (MDOC_SYNPRETTY & n->flags)
+	if (NODE_SYNPRETTY & n->flags)
 		term_fontpush(p, TERMFONT_BOLD);
 
 	p->flags |= TERMP_NOSPACE;
 	term_word(p, ">");
 
-	if (MDOC_SYNPRETTY & n->flags)
+	if (NODE_SYNPRETTY & n->flags)
 		term_fontpop(p);
 }
 
@@ -1990,7 +1853,7 @@ termp_fo_pre(DECL_ARGS)
 	size_t		 rmargin = 0;
 	int		 pretty;
 
-	pretty = MDOC_SYNPRETTY & n->flags;
+	pretty = NODE_SYNPRETTY & n->flags;
 
 	if (n->type == ROFFT_BLOCK) {
 		synopsis_pre(p, n);
@@ -2036,7 +1899,7 @@ termp_fo_post(DECL_ARGS)
 	p->flags |= TERMP_NOSPACE;
 	term_word(p, ")");
 
-	if (MDOC_SYNPRETTY & n->flags) {
+	if (NODE_SYNPRETTY & n->flags) {
 		p->flags |= TERMP_NOSPACE;
 		term_word(p, ";");
 		term_flushln(p);
@@ -2120,6 +1983,7 @@ static int
 termp_li_pre(DECL_ARGS)
 {
 
+	termp_tag_pre(p, pair, meta, n);
 	term_fontpush(p, TERMFONT_NONE);
 	return 1;
 }
@@ -2217,6 +2081,26 @@ termp_under_pre(DECL_ARGS)
 	return 1;
 }
 
+static int
+termp_em_pre(DECL_ARGS)
+{
+	if (n->child != NULL &&
+	    n->child->type == ROFFT_TEXT)
+		tag_put(n->child->string, 0, p->line);
+	term_fontpush(p, TERMFONT_UNDER);
+	return 1;
+}
+
+static int
+termp_sy_pre(DECL_ARGS)
+{
+	if (n->child != NULL &&
+	    n->child->type == ROFFT_TEXT)
+		tag_put(n->child->string, 0, p->line);
+	term_fontpush(p, TERMFONT_BOLD);
+	return 1;
+}
+
 static int
 termp_er_pre(DECL_ARGS)
 {
@@ -2235,7 +2119,9 @@ termp_tag_pre(DECL_ARGS)
 
 	if (n->child != NULL &&
 	    n->child->type == ROFFT_TEXT &&
-	    n->prev == NULL &&
+	    (n->prev == NULL ||
+	     (n->prev->type == ROFFT_TEXT &&
+	      strcmp(n->prev->string, "|") == 0)) &&
 	    (n->parent->tok == MDOC_It ||
 	     (n->parent->tok == MDOC_Xo &&
 	      n->parent->parent->prev == NULL &&
diff --git a/mdoc_validate.c b/mdoc_validate.c
index e369349c8a35..043145ae1ccc 100644
--- a/mdoc_validate.c
+++ b/mdoc_validate.c
@@ -1,7 +1,7 @@
-/*	$Id: mdoc_validate.c,v 1.301 2016/01/08 17:48:09 schwarze Exp $ */
+/*	$Id: mdoc_validate.c,v 1.317 2017/01/11 17:39:53 schwarze Exp $ */
 /*
  * Copyright (c) 2008-2012 Kristaps Dzonsons 
- * Copyright (c) 2010-2016 Ingo Schwarze 
+ * Copyright (c) 2010-2017 Ingo Schwarze 
  * Copyright (c) 2010 Joerg Sonnenberger 
  *
  * Permission to use, copy, modify, and distribute this software for any
@@ -51,6 +51,7 @@ enum	check_ineq {
 
 typedef	void	(*v_post)(POST_ARGS);
 
+static	int	 build_list(struct roff_man *, int);
 static	void	 check_text(struct roff_man *, int, int, char *);
 static	void	 check_argv(struct roff_man *,
 			struct roff_node *, struct mdoc_argv *);
@@ -67,7 +68,6 @@ static	void	 post_bf(POST_ARGS);
 static	void	 post_bk(POST_ARGS);
 static	void	 post_bl(POST_ARGS);
 static	void	 post_bl_block(POST_ARGS);
-static	void	 post_bl_block_tag(POST_ARGS);
 static	void	 post_bl_head(POST_ARGS);
 static	void	 post_bl_norm(POST_ARGS);
 static	void	 post_bx(POST_ARGS);
@@ -96,6 +96,7 @@ static	void	 post_par(POST_ARGS);
 static	void	 post_prevpar(POST_ARGS);
 static	void	 post_root(POST_ARGS);
 static	void	 post_rs(POST_ARGS);
+static	void	 post_rv(POST_ARGS);
 static	void	 post_sh(POST_ARGS);
 static	void	 post_sh_head(POST_ARGS);
 static	void	 post_sh_name(POST_ARGS);
@@ -104,6 +105,8 @@ static	void	 post_sh_authors(POST_ARGS);
 static	void	 post_sm(POST_ARGS);
 static	void	 post_st(POST_ARGS);
 static	void	 post_std(POST_ARGS);
+static	void	 post_xr(POST_ARGS);
+static	void	 post_xx(POST_ARGS);
 
 static	v_post mdoc_valids[MDOC_MAX] = {
 	NULL,		/* Ap */
@@ -142,11 +145,11 @@ static	v_post mdoc_valids[MDOC_MAX] = {
 	NULL,		/* Op */
 	post_obsolete,	/* Ot */
 	post_defaults,	/* Pa */
-	post_std,	/* Rv */
+	post_rv,	/* Rv */
 	post_st,	/* St */
 	NULL,		/* Va */
 	NULL,		/* Vt */
-	NULL,		/* Xr */
+	post_xr,	/* Xr */
 	NULL,		/* %A */
 	post_hyph,	/* %B */ /* FIXME: can be used outside Rs/Re. */
 	NULL,		/* %D */
@@ -166,7 +169,7 @@ static	v_post mdoc_valids[MDOC_MAX] = {
 	post_bf,	/* Bf */
 	NULL,		/* Bo */
 	NULL,		/* Bq */
-	NULL,		/* Bsx */
+	post_xx,	/* Bsx */
 	post_bx,	/* Bx */
 	post_obsolete,	/* Db */
 	NULL,		/* Dc */
@@ -176,12 +179,12 @@ static	v_post mdoc_valids[MDOC_MAX] = {
 	NULL,		/* Ef */
 	NULL,		/* Em */
 	NULL,		/* Eo */
-	NULL,		/* Fx */
+	post_xx,	/* Fx */
 	NULL,		/* Ms */
 	NULL,		/* No */
 	post_ns,	/* Ns */
-	NULL,		/* Nx */
-	NULL,		/* Ox */
+	post_xx,	/* Nx */
+	post_xx,	/* Ox */
 	NULL,		/* Pc */
 	NULL,		/* Pf */
 	NULL,		/* Po */
@@ -199,7 +202,7 @@ static	v_post mdoc_valids[MDOC_MAX] = {
 	post_hyph,	/* Sx */
 	NULL,		/* Sy */
 	NULL,		/* Tn */
-	NULL,		/* Ux */
+	post_xx,	/* Ux */
 	NULL,		/* Xc */
 	NULL,		/* Xo */
 	post_fo,	/* Fo */
@@ -222,7 +225,7 @@ static	v_post mdoc_valids[MDOC_MAX] = {
 	NULL,		/* %C */
 	post_es,	/* Es */
 	post_en,	/* En */
-	NULL,		/* Dx */
+	post_xx,	/* Dx */
 	NULL,		/* %Q */
 	post_par,	/* br */
 	post_par,	/* sp */
@@ -297,7 +300,8 @@ mdoc_node_validate(struct roff_man *mdoc)
 	mdoc->next = ROFF_NEXT_SIBLING;
 	switch (n->type) {
 	case ROFFT_TEXT:
-		if (n->sec != SEC_SYNOPSIS || n->parent->tok != MDOC_Fd)
+		if (n->sec != SEC_SYNOPSIS ||
+		    (n->parent->tok != MDOC_Cd && n->parent->tok != MDOC_Fd))
 			check_text(mdoc, n->line, n->pos, n->string);
 		break;
 	case ROFFT_EQN:
@@ -316,9 +320,9 @@ mdoc_node_validate(struct roff_man *mdoc)
 		 */
 
 		if (n->child != NULL)
-			n->child->flags &= ~MDOC_DELIMC;
+			n->child->flags &= ~NODE_DELIMC;
 		if (n->last != NULL)
-			n->last->flags &= ~MDOC_DELIMO;
+			n->last->flags &= ~NODE_DELIMO;
 
 		/* Call the macro's postprocessor. */
 
@@ -502,6 +506,7 @@ post_bl_norm(POST_ARGS)
 		mandoc_msg(MANDOCERR_BL_NOTYPE, mdoc->parse,
 		    n->line, n->pos, "Bl");
 		n->norm->Bl.type = LIST_item;
+		mdoclt = MDOC_Item;
 	}
 
 	/*
@@ -618,6 +623,10 @@ post_bd(POST_ARGS)
 	}
 }
 
+/*
+ * Stand-alone line macros.
+ */
+
 static void
 post_an_norm(POST_ARGS)
 {
@@ -645,6 +654,158 @@ post_an_norm(POST_ARGS)
 		abort();
 }
 
+static void
+post_eoln(POST_ARGS)
+{
+	struct roff_node	*n;
+
+	n = mdoc->last;
+	if (n->child != NULL)
+		mandoc_vmsg(MANDOCERR_ARG_SKIP, mdoc->parse,
+		    n->line, n->pos, "%s %s",
+		    mdoc_macronames[n->tok], n->child->string);
+
+	while (n->child != NULL)
+		roff_node_delete(mdoc, n->child);
+
+	roff_word_alloc(mdoc, n->line, n->pos, n->tok == MDOC_Bt ?
+	    "is currently in beta test." : "currently under development.");
+	mdoc->last->flags |= NODE_EOS | NODE_NOSRC;
+	mdoc->last = n;
+}
+
+static int
+build_list(struct roff_man *mdoc, int tok)
+{
+	struct roff_node	*n;
+	int			 ic;
+
+	n = mdoc->last->next;
+	for (ic = 1;; ic++) {
+		roff_elem_alloc(mdoc, n->line, n->pos, tok);
+		mdoc->last->flags |= NODE_NOSRC;
+		mdoc_node_relink(mdoc, n);
+		n = mdoc->last = mdoc->last->parent;
+		mdoc->next = ROFF_NEXT_SIBLING;
+		if (n->next == NULL)
+			return ic;
+		if (ic > 1 || n->next->next != NULL) {
+			roff_word_alloc(mdoc, n->line, n->pos, ",");
+			mdoc->last->flags |= NODE_DELIMC | NODE_NOSRC;
+		}
+		n = mdoc->last->next;
+		if (n->next == NULL) {
+			roff_word_alloc(mdoc, n->line, n->pos, "and");
+			mdoc->last->flags |= NODE_NOSRC;
+		}
+	}
+}
+
+static void
+post_ex(POST_ARGS)
+{
+	struct roff_node	*n;
+	int			 ic;
+
+	post_std(mdoc);
+
+	n = mdoc->last;
+	mdoc->next = ROFF_NEXT_CHILD;
+	roff_word_alloc(mdoc, n->line, n->pos, "The");
+	mdoc->last->flags |= NODE_NOSRC;
+
+	if (mdoc->last->next != NULL)
+		ic = build_list(mdoc, MDOC_Nm);
+	else if (mdoc->meta.name != NULL) {
+		roff_elem_alloc(mdoc, n->line, n->pos, MDOC_Nm);
+		mdoc->last->flags |= NODE_NOSRC;
+		roff_word_alloc(mdoc, n->line, n->pos, mdoc->meta.name);
+		mdoc->last->flags |= NODE_NOSRC;
+		mdoc->last = mdoc->last->parent;
+		mdoc->next = ROFF_NEXT_SIBLING;
+		ic = 1;
+	} else {
+		mandoc_msg(MANDOCERR_EX_NONAME, mdoc->parse,
+		    n->line, n->pos, "Ex");
+		ic = 0;
+	}
+
+	roff_word_alloc(mdoc, n->line, n->pos,
+	    ic > 1 ? "utilities exit\\~0" : "utility exits\\~0");
+	mdoc->last->flags |= NODE_NOSRC;
+	roff_word_alloc(mdoc, n->line, n->pos,
+	    "on success, and\\~>0 if an error occurs.");
+	mdoc->last->flags |= NODE_EOS | NODE_NOSRC;
+	mdoc->last = n;
+}
+
+static void
+post_lb(POST_ARGS)
+{
+	struct roff_node	*n;
+	const char		*p;
+
+	n = mdoc->last;
+	assert(n->child->type == ROFFT_TEXT);
+	mdoc->next = ROFF_NEXT_CHILD;
+
+	if ((p = mdoc_a2lib(n->child->string)) != NULL) {
+		n->child->flags |= NODE_NOPRT;
+		roff_word_alloc(mdoc, n->line, n->pos, p);
+		mdoc->last->flags = NODE_NOSRC;
+		mdoc->last = n;
+		return;
+	}
+
+	roff_word_alloc(mdoc, n->line, n->pos, "library");
+	mdoc->last->flags = NODE_NOSRC;
+	roff_word_alloc(mdoc, n->line, n->pos, "\\(Lq");
+	mdoc->last->flags = NODE_DELIMO | NODE_NOSRC;
+	mdoc->last = mdoc->last->next;
+	roff_word_alloc(mdoc, n->line, n->pos, "\\(Rq");
+	mdoc->last->flags = NODE_DELIMC | NODE_NOSRC;
+	mdoc->last = n;
+}
+
+static void
+post_rv(POST_ARGS)
+{
+	struct roff_node	*n;
+	int			 ic;
+
+	post_std(mdoc);
+
+	n = mdoc->last;
+	mdoc->next = ROFF_NEXT_CHILD;
+	if (n->child != NULL) {
+		roff_word_alloc(mdoc, n->line, n->pos, "The");
+		mdoc->last->flags |= NODE_NOSRC;
+		ic = build_list(mdoc, MDOC_Fn);
+		roff_word_alloc(mdoc, n->line, n->pos,
+		    ic > 1 ? "functions return" : "function returns");
+		mdoc->last->flags |= NODE_NOSRC;
+		roff_word_alloc(mdoc, n->line, n->pos,
+		    "the value\\~0 if successful;");
+	} else
+		roff_word_alloc(mdoc, n->line, n->pos, "Upon successful "
+		    "completion, the value\\~0 is returned;");
+	mdoc->last->flags |= NODE_NOSRC;
+
+	roff_word_alloc(mdoc, n->line, n->pos, "otherwise "
+	    "the value\\~\\-1 is returned and the global variable");
+	mdoc->last->flags |= NODE_NOSRC;
+	roff_elem_alloc(mdoc, n->line, n->pos, MDOC_Va);
+	mdoc->last->flags |= NODE_NOSRC;
+	roff_word_alloc(mdoc, n->line, n->pos, "errno");
+	mdoc->last->flags |= NODE_NOSRC;
+	mdoc->last = mdoc->last->parent;
+	mdoc->next = ROFF_NEXT_SIBLING;
+	roff_word_alloc(mdoc, n->line, n->pos,
+	    "is set to indicate the error.");
+	mdoc->last->flags |= NODE_EOS | NODE_NOSRC;
+	mdoc->last = n;
+}
+
 static void
 post_std(POST_ARGS)
 {
@@ -659,6 +820,30 @@ post_std(POST_ARGS)
 	    n->line, n->pos, mdoc_macronames[n->tok]);
 }
 
+static void
+post_st(POST_ARGS)
+{
+	struct roff_node	 *n, *nch;
+	const char		 *p;
+
+	n = mdoc->last;
+	nch = n->child;
+	assert(nch->type == ROFFT_TEXT);
+
+	if ((p = mdoc_a2st(nch->string)) == NULL) {
+		mandoc_vmsg(MANDOCERR_ST_BAD, mdoc->parse,
+		    nch->line, nch->pos, "St %s", nch->string);
+		roff_node_delete(mdoc, n);
+		return;
+	}
+
+	nch->flags |= NODE_NOPRT;
+	mdoc->next = ROFF_NEXT_CHILD;
+	roff_word_alloc(mdoc, nch->line, nch->pos, p);
+	mdoc->last->flags |= NODE_NOSRC;
+	mdoc->last= n;
+}
+
 static void
 post_obsolete(POST_ARGS)
 {
@@ -670,6 +855,10 @@ post_obsolete(POST_ARGS)
 		    n->line, n->pos, mdoc_macronames[n->tok]);
 }
 
+/*
+ * Block macros.
+ */
+
 static void
 post_bf(POST_ARGS)
 {
@@ -735,39 +924,6 @@ post_bf(POST_ARGS)
 		    "Bf %s", np->child->string);
 }
 
-static void
-post_lb(POST_ARGS)
-{
-	struct roff_node	*n;
-	const char		*stdlibname;
-	char			*libname;
-
-	n = mdoc->last->child;
-	assert(n->type == ROFFT_TEXT);
-
-	if (NULL == (stdlibname = mdoc_a2lib(n->string)))
-		mandoc_asprintf(&libname,
-		    "library \\(Lq%s\\(Rq", n->string);
-	else
-		libname = mandoc_strdup(stdlibname);
-
-	free(n->string);
-	n->string = libname;
-}
-
-static void
-post_eoln(POST_ARGS)
-{
-	const struct roff_node *n;
-
-	n = mdoc->last;
-	if (n->child != NULL)
-		mandoc_vmsg(MANDOCERR_ARG_SKIP,
-		    mdoc->parse, n->line, n->pos,
-		    "%s %s", mdoc_macronames[n->tok],
-		    n->child->string);
-}
-
 static void
 post_fname(POST_ARGS)
 {
@@ -850,12 +1006,11 @@ post_nm(POST_ARGS)
 	     n->last->tok == MDOC_Lp))
 		mdoc_node_relink(mdoc, n->last);
 
-	if (mdoc->meta.name != NULL)
-		return;
-
-	deroff(&mdoc->meta.name, n);
-
 	if (mdoc->meta.name == NULL)
+		deroff(&mdoc->meta.name, n);
+
+	if (mdoc->meta.name == NULL ||
+	    (mdoc->lastsec == SEC_NAME && n->child == NULL))
 		mandoc_msg(MANDOCERR_NM_NONAME, mdoc->parse,
 		    n->line, n->pos, "Nm");
 }
@@ -885,9 +1040,11 @@ post_display(POST_ARGS)
 	n = mdoc->last;
 	switch (n->type) {
 	case ROFFT_BODY:
-		if (n->end != ENDBODY_NOT)
-			break;
-		if (n->child == NULL)
+		if (n->end != ENDBODY_NOT) {
+			if (n->tok == MDOC_Bd &&
+			    n->body->parent->args == NULL)
+				roff_node_delete(mdoc, n);
+		} else if (n->child == NULL)
 			mandoc_msg(MANDOCERR_BLK_EMPTY, mdoc->parse,
 			    n->line, n->pos, mdoc_macronames[n->tok]);
 		else if (n->tok == MDOC_D1)
@@ -942,12 +1099,15 @@ post_defaults(POST_ARGS)
 	case MDOC_Ar:
 		mdoc->next = ROFF_NEXT_CHILD;
 		roff_word_alloc(mdoc, nn->line, nn->pos, "file");
+		mdoc->last->flags |= NODE_NOSRC;
 		roff_word_alloc(mdoc, nn->line, nn->pos, "...");
+		mdoc->last->flags |= NODE_NOSRC;
 		break;
 	case MDOC_Pa:
 	case MDOC_Mt:
 		mdoc->next = ROFF_NEXT_CHILD;
 		roff_word_alloc(mdoc, nn->line, nn->pos, "~");
+		mdoc->last->flags |= NODE_NOSRC;
 		break;
 	default:
 		abort();
@@ -958,17 +1118,11 @@ post_defaults(POST_ARGS)
 static void
 post_at(POST_ARGS)
 {
-	struct roff_node	*n;
-	const char		*std_att;
-	char			*att;
+	struct roff_node	*n, *nch;
+	const char		*att;
 
 	n = mdoc->last;
-	if (n->child == NULL) {
-		mdoc->next = ROFF_NEXT_CHILD;
-		roff_word_alloc(mdoc, n->line, n->pos, "AT&T UNIX");
-		mdoc->last = n;
-		return;
-	}
+	nch = n->child;
 
 	/*
 	 * If we have a child, look it up in the standard keys.  If a
@@ -976,17 +1130,19 @@ post_at(POST_ARGS)
 	 * prefix "AT&T UNIX " to the existing data.
 	 */
 
-	n = n->child;
-	assert(n->type == ROFFT_TEXT);
-	if ((std_att = mdoc_a2att(n->string)) == NULL) {
+	att = NULL;
+	if (nch != NULL && ((att = mdoc_a2att(nch->string)) == NULL))
 		mandoc_vmsg(MANDOCERR_AT_BAD, mdoc->parse,
-		    n->line, n->pos, "At %s", n->string);
-		mandoc_asprintf(&att, "AT&T UNIX %s", n->string);
-	} else
-		att = mandoc_strdup(std_att);
+		    nch->line, nch->pos, "At %s", nch->string);
 
-	free(n->string);
-	n->string = att;
+	mdoc->next = ROFF_NEXT_CHILD;
+	if (att != NULL) {
+		roff_word_alloc(mdoc, nch->line, nch->pos, att);
+		nch->flags |= NODE_NOPRT;
+	} else
+		roff_word_alloc(mdoc, n->line, n->pos, "AT&T UNIX");
+	mdoc->last->flags |= NODE_NOSRC;
+	mdoc->last = n;
 }
 
 static void
@@ -1024,6 +1180,41 @@ post_es(POST_ARGS)
 	mdoc->last_es = mdoc->last;
 }
 
+static void
+post_xx(POST_ARGS)
+{
+	struct roff_node	*n;
+	const char		*os;
+
+	n = mdoc->last;
+	switch (n->tok) {
+	case MDOC_Bsx:
+		os = "BSD/OS";
+		break;
+	case MDOC_Dx:
+		os = "DragonFly";
+		break;
+	case MDOC_Fx:
+		os = "FreeBSD";
+		break;
+	case MDOC_Nx:
+		os = "NetBSD";
+		break;
+	case MDOC_Ox:
+		os = "OpenBSD";
+		break;
+	case MDOC_Ux:
+		os = "UNIX";
+		break;
+	default:
+		abort();
+	}
+	mdoc->next = ROFF_NEXT_CHILD;
+	roff_word_alloc(mdoc, n->line, n->pos, os);
+	mdoc->last->flags |= NODE_NOSRC;
+	mdoc->last = n;
+}
+
 static void
 post_it(POST_ARGS)
 {
@@ -1063,10 +1254,11 @@ post_it(POST_ARGS)
 			    mdoc_argnames[nbl->args->argv[0].arg]);
 		/* FALLTHROUGH */
 	case LIST_item:
-		if (nit->head->child != NULL)
+		if ((nch = nit->head->child) != NULL)
 			mandoc_vmsg(MANDOCERR_ARG_SKIP,
 			    mdoc->parse, nit->line, nit->pos,
-			    "It %s", nit->head->child->string);
+			    "It %s", nch->string == NULL ?
+			    mdoc_macronames[nch->tok] : nch->string);
 		break;
 	case LIST_column:
 		cols = (int)nbl->norm->Bl.ncols;
@@ -1095,22 +1287,7 @@ post_bl_block(POST_ARGS)
 
 	post_prevpar(mdoc);
 
-	/*
-	 * These are fairly complicated, so we've broken them into two
-	 * functions.  post_bl_block_tag() is called when a -tag is
-	 * specified, but no -width (it must be guessed).  The second
-	 * when a -width is specified (macro indicators must be
-	 * rewritten into real lengths).
-	 */
-
 	n = mdoc->last;
-
-	if (n->norm->Bl.type == LIST_tag &&
-	    n->norm->Bl.width == NULL) {
-		post_bl_block_tag(mdoc);
-		assert(n->norm->Bl.width != NULL);
-	}
-
 	for (ni = n->body->child; ni != NULL; ni = ni->next) {
 		if (ni->body == NULL)
 			continue;
@@ -1167,71 +1344,6 @@ rewrite_macro2len(char **arg)
 	mandoc_asprintf(arg, "%zun", width);
 }
 
-static void
-post_bl_block_tag(POST_ARGS)
-{
-	struct roff_node *n, *nn;
-	size_t		  sz, ssz;
-	int		  i;
-	char		  buf[24];
-
-	/*
-	 * Calculate the -width for a `Bl -tag' list if it hasn't been
-	 * provided.  Uses the first head macro.  NOTE AGAIN: this is
-	 * ONLY if the -width argument has NOT been provided.  See
-	 * rewrite_macro2len() for converting the -width string.
-	 */
-
-	sz = 10;
-	n = mdoc->last;
-
-	for (nn = n->body->child; nn != NULL; nn = nn->next) {
-		if (nn->tok != MDOC_It)
-			continue;
-
-		assert(nn->type == ROFFT_BLOCK);
-		nn = nn->head->child;
-
-		if (nn == NULL)
-			break;
-
-		if (nn->type == ROFFT_TEXT) {
-			sz = strlen(nn->string) + 1;
-			break;
-		}
-
-		if (0 != (ssz = macro2len(nn->tok)))
-			sz = ssz;
-
-		break;
-	}
-
-	/* Defaults to ten ens. */
-
-	(void)snprintf(buf, sizeof(buf), "%un", (unsigned int)sz);
-
-	/*
-	 * We have to dynamically add this to the macro's argument list.
-	 * We're guaranteed that a MDOC_Width doesn't already exist.
-	 */
-
-	assert(n->args != NULL);
-	i = (int)(n->args->argc)++;
-
-	n->args->argv = mandoc_reallocarray(n->args->argv,
-	    n->args->argc, sizeof(struct mdoc_argv));
-
-	n->args->argv[i].arg = MDOC_Width;
-	n->args->argv[i].line = n->line;
-	n->args->argv[i].pos = n->pos;
-	n->args->argv[i].sz = 1;
-	n->args->argv[i].value = mandoc_malloc(sizeof(char *));
-	n->args->argv[i].value[0] = mandoc_strdup(buf);
-
-	/* Set our width! */
-	n->norm->Bl.width = n->args->argv[i].value[0];
-}
-
 static void
 post_bl_head(POST_ARGS)
 {
@@ -1325,11 +1437,41 @@ post_bl(POST_ARGS)
 		return;
 	}
 	while (nchild != NULL) {
+		nnext = nchild->next;
 		if (nchild->tok == MDOC_It ||
 		    (nchild->tok == MDOC_Sm &&
-		     nchild->next != NULL &&
-		     nchild->next->tok == MDOC_It)) {
-			nchild = nchild->next;
+		     nnext != NULL && nnext->tok == MDOC_It)) {
+			nchild = nnext;
+			continue;
+		}
+
+		/*
+		 * In .Bl -column, the first rows may be implicit,
+		 * that is, they may not start with .It macros.
+		 * Such rows may be followed by nodes generated on the
+		 * roff level, for example .TS, which cannot be moved
+		 * out of the list.  In that case, wrap such roff nodes
+		 * into an implicit row.
+		 */
+
+		if (nchild->prev != NULL) {
+			mdoc->last = nchild;
+			mdoc->next = ROFF_NEXT_SIBLING;
+			roff_block_alloc(mdoc, nchild->line,
+			    nchild->pos, MDOC_It);
+			roff_head_alloc(mdoc, nchild->line,
+			    nchild->pos, MDOC_It);
+			mdoc->next = ROFF_NEXT_SIBLING;
+			roff_body_alloc(mdoc, nchild->line,
+			    nchild->pos, MDOC_It);
+			while (nchild->tok != MDOC_It) {
+				mdoc_node_relink(mdoc, nchild);
+				if ((nchild = nnext) == NULL)
+					break;
+				nnext = nchild->next;
+				mdoc->next = ROFF_NEXT_SIBLING;
+			}
+			mdoc->last = nbody;
 			continue;
 		}
 
@@ -1345,13 +1487,11 @@ post_bl(POST_ARGS)
 		nblock  = nbody->parent;
 		nprev   = nblock->prev;
 		nparent = nblock->parent;
-		nnext   = nchild->next;
 
 		/*
 		 * Unlink this child.
 		 */
 
-		assert(nchild->prev == NULL);
 		nbody->child = nnext;
 		if (nnext == NULL)
 			nbody->last  = NULL;
@@ -1461,27 +1601,6 @@ post_root(POST_ARGS)
 		    n->line, n->pos, mdoc_macronames[n->tok]);
 }
 
-static void
-post_st(POST_ARGS)
-{
-	struct roff_node	 *n, *nch;
-	const char		 *p;
-
-	n = mdoc->last;
-	nch = n->child;
-
-	assert(nch->type == ROFFT_TEXT);
-
-	if ((p = mdoc_a2st(nch->string)) == NULL) {
-		mandoc_vmsg(MANDOCERR_ST_BAD, mdoc->parse,
-		    nch->line, nch->pos, "St %s", nch->string);
-		roff_node_delete(mdoc, n);
-	} else {
-		free(nch->string);
-		nch->string = mandoc_strdup(p);
-	}
-}
-
 static void
 post_rs(POST_ARGS)
 {
@@ -1600,7 +1719,7 @@ static void
 post_ns(POST_ARGS)
 {
 
-	if (mdoc->last->flags & MDOC_LINE)
+	if (mdoc->last->flags & NODE_LINE)
 		mandoc_msg(MANDOCERR_NS_SKIP, mdoc->parse,
 		    mdoc->last->line, mdoc->last->pos, NULL);
 }
@@ -1646,8 +1765,12 @@ post_sh_name(POST_ARGS)
 	for (n = mdoc->last->child; n != NULL; n = n->next) {
 		switch (n->tok) {
 		case MDOC_Nm:
+			if (hasnm && n->child != NULL)
+				mandoc_vmsg(MANDOCERR_NAMESEC_PUNCT,
+				    mdoc->parse, n->line, n->pos,
+				    "Nm %s", n->child->string);
 			hasnm = 1;
-			break;
+			continue;
 		case MDOC_Nd:
 			hasnd = 1;
 			if (n->next != NULL)
@@ -1655,14 +1778,19 @@ post_sh_name(POST_ARGS)
 				    mdoc->parse, n->line, n->pos, NULL);
 			break;
 		case TOKEN_NONE:
-			if (hasnm)
-				break;
+			if (n->type == ROFFT_TEXT &&
+			    n->string[0] == ',' && n->string[1] == '\0' &&
+			    n->next != NULL && n->next->tok == MDOC_Nm) {
+				n = n->next;
+				continue;
+			}
 			/* FALLTHROUGH */
 		default:
 			mandoc_msg(MANDOCERR_NAMESEC_BAD, mdoc->parse,
 			    n->line, n->pos, mdoc_macronames[n->tok]);
-			break;
+			continue;
 		}
+		break;
 	}
 
 	if ( ! hasnm)
@@ -1759,8 +1887,9 @@ post_sh_authors(POST_ARGS)
 static void
 post_sh_head(POST_ARGS)
 {
-	const char	*goodsec;
-	enum roff_sec	 sec;
+	struct roff_node	*nch;
+	const char		*goodsec;
+	enum roff_sec		 sec;
 
 	/*
 	 * Process a new section.  Sections are either "named" or
@@ -1773,10 +1902,13 @@ post_sh_head(POST_ARGS)
 
 	/* The NAME should be first. */
 
-	if (SEC_NAME != sec && SEC_NONE == mdoc->lastnamed)
+	if (sec != SEC_NAME && mdoc->lastnamed == SEC_NONE)
 		mandoc_vmsg(MANDOCERR_NAMESEC_FIRST, mdoc->parse,
-		    mdoc->last->line, mdoc->last->pos,
-		    "Sh %s", secnames[sec]);
+		    mdoc->last->line, mdoc->last->pos, "Sh %s",
+		    sec != SEC_CUSTOM ? secnames[sec] :
+		    (nch = mdoc->last->child) == NULL ? "" :
+		    nch->type == ROFFT_TEXT ? nch->string :
+		    mdoc_macronames[nch->tok]);
 
 	/* The SYNOPSIS gets special attention in other areas. */
 
@@ -1851,6 +1983,21 @@ post_sh_head(POST_ARGS)
 	}
 }
 
+static void
+post_xr(POST_ARGS)
+{
+	struct roff_node *n, *nch;
+
+	n = mdoc->last;
+	nch = n->child;
+	if (nch->next == NULL) {
+		mandoc_vmsg(MANDOCERR_XR_NOSEC, mdoc->parse,
+		    n->line, n->pos, "Xr %s", nch->string);
+		return;
+	}
+	assert(nch->next == n->last);
+}
+
 static void
 post_ignpar(POST_ARGS)
 {
@@ -1961,6 +2108,8 @@ post_dd(POST_ARGS)
 	char		 *datestr;
 
 	n = mdoc->last;
+	n->flags |= NODE_NOPRT;
+
 	if (mdoc->meta.date != NULL) {
 		mandoc_msg(MANDOCERR_PROLOG_REP, mdoc->parse,
 		    n->line, n->pos, "Dd");
@@ -1978,7 +2127,7 @@ post_dd(POST_ARGS)
 	if (n->child == NULL || n->child->string[0] == '\0') {
 		mdoc->meta.date = mdoc->quick ? mandoc_strdup("") :
 		    mandoc_normdate(mdoc->parse, NULL, n->line, n->pos);
-		goto out;
+		return;
 	}
 
 	datestr = NULL;
@@ -1990,8 +2139,6 @@ post_dd(POST_ARGS)
 		    datestr, n->line, n->pos);
 		free(datestr);
 	}
-out:
-	roff_node_delete(mdoc, n);
 }
 
 static void
@@ -2002,10 +2149,12 @@ post_dt(POST_ARGS)
 	char		 *p;
 
 	n = mdoc->last;
+	n->flags |= NODE_NOPRT;
+
 	if (mdoc->flags & MDOC_PBODY) {
 		mandoc_msg(MANDOCERR_DT_LATE, mdoc->parse,
 		    n->line, n->pos, "Dt");
-		goto out;
+		return;
 	}
 
 	if (mdoc->meta.title != NULL)
@@ -2047,7 +2196,7 @@ post_dt(POST_ARGS)
 			}
 	}
 
-	/* Mandatory second argument: section. */
+	/* Mandatory second argument: section. */
 
 	if (nn != NULL)
 		nn = nn->next;
@@ -2057,7 +2206,7 @@ post_dt(POST_ARGS)
 		    mdoc->parse, n->line, n->pos,
 		    "Dt %s", mdoc->meta.title);
 		mdoc->meta.vol = mandoc_strdup("LOCAL");
-		goto out;  /* msec and arch remain NULL. */
+		return;  /* msec and arch remain NULL. */
 	}
 
 	mdoc->meta.msec = mandoc_strdup(nn->string);
@@ -2075,7 +2224,7 @@ post_dt(POST_ARGS)
 	/* Optional third argument: architecture. */
 
 	if ((nn = nn->next) == NULL)
-		goto out;
+		return;
 
 	for (p = nn->string; *p != '\0'; p++)
 		*p = tolower((unsigned char)*p);
@@ -2086,15 +2235,41 @@ post_dt(POST_ARGS)
 	if ((nn = nn->next) != NULL)
 		mandoc_vmsg(MANDOCERR_ARG_EXCESS, mdoc->parse,
 		    nn->line, nn->pos, "Dt ... %s", nn->string);
-
-out:
-	roff_node_delete(mdoc, n);
 }
 
 static void
 post_bx(POST_ARGS)
 {
-	struct roff_node	*n;
+	struct roff_node	*n, *nch;
+
+	n = mdoc->last;
+	nch = n->child;
+
+	if (nch != NULL) {
+		mdoc->last = nch;
+		nch = nch->next;
+		mdoc->next = ROFF_NEXT_SIBLING;
+		roff_elem_alloc(mdoc, n->line, n->pos, MDOC_Ns);
+		mdoc->last->flags |= NODE_NOSRC;
+		mdoc->next = ROFF_NEXT_SIBLING;
+	} else
+		mdoc->next = ROFF_NEXT_CHILD;
+	roff_word_alloc(mdoc, n->line, n->pos, "BSD");
+	mdoc->last->flags |= NODE_NOSRC;
+
+	if (nch == NULL) {
+		mdoc->last = n;
+		return;
+	}
+
+	roff_elem_alloc(mdoc, n->line, n->pos, MDOC_Ns);
+	mdoc->last->flags |= NODE_NOSRC;
+	mdoc->next = ROFF_NEXT_SIBLING;
+	roff_word_alloc(mdoc, n->line, n->pos, "-");
+	mdoc->last->flags |= NODE_NOSRC;
+	roff_elem_alloc(mdoc, n->line, n->pos, MDOC_Ns);
+	mdoc->last->flags |= NODE_NOSRC;
+	mdoc->last = n;
 
 	/*
 	 * Make `Bx's second argument always start with an uppercase
@@ -2102,8 +2277,7 @@ post_bx(POST_ARGS)
 	 * uppercase blindly.
 	 */
 
-	if ((n = mdoc->last->child) != NULL && (n = n->next) != NULL)
-		*n->string = (char)toupper((unsigned char)*n->string);
+	*nch->string = (char)toupper((unsigned char)*nch->string);
 }
 
 static void
@@ -2116,6 +2290,8 @@ post_os(POST_ARGS)
 	struct roff_node *n;
 
 	n = mdoc->last;
+	n->flags |= NODE_NOPRT;
+
 	if (mdoc->meta.os != NULL)
 		mandoc_msg(MANDOCERR_PROLOG_REP, mdoc->parse,
 		    n->line, n->pos, "Os");
@@ -2136,11 +2312,11 @@ post_os(POST_ARGS)
 	mdoc->meta.os = NULL;
 	deroff(&mdoc->meta.os, n);
 	if (mdoc->meta.os)
-		goto out;
+		return;
 
 	if (mdoc->defos) {
 		mdoc->meta.os = mandoc_strdup(mdoc->defos);
-		goto out;
+		return;
 	}
 
 #ifdef OSNAME
@@ -2157,35 +2333,6 @@ post_os(POST_ARGS)
 	}
 	mdoc->meta.os = mandoc_strdup(defbuf);
 #endif /*!OSNAME*/
-
-out:
-	roff_node_delete(mdoc, n);
-}
-
-/*
- * If no argument is provided,
- * fill in the name of the current manual page.
- */
-static void
-post_ex(POST_ARGS)
-{
-	struct roff_node *n;
-
-	post_std(mdoc);
-
-	n = mdoc->last;
-	if (n->child != NULL)
-		return;
-
-	if (mdoc->meta.name == NULL) {
-		mandoc_msg(MANDOCERR_EX_NONAME, mdoc->parse,
-		    n->line, n->pos, "Ex");
-		return;
-	}
-
-	mdoc->next = ROFF_NEXT_CHILD;
-	roff_word_alloc(mdoc, n->line, n->pos, mdoc->meta.name);
-	mdoc->last = n;
 }
 
 enum roff_sec
diff --git a/read.c b/read.c
index 6cbcd2df0079..d20a6098e9e4 100644
--- a/read.c
+++ b/read.c
@@ -1,7 +1,7 @@
-/*	$Id: read.c,v 1.149 2016/07/10 13:34:30 schwarze Exp $ */
+/*	$Id: read.c,v 1.157 2017/01/09 01:37:03 schwarze Exp $ */
 /*
  * Copyright (c) 2008, 2009, 2010, 2011 Kristaps Dzonsons 
- * Copyright (c) 2010-2016 Ingo Schwarze 
+ * Copyright (c) 2010-2017 Ingo Schwarze 
  * Copyright (c) 2010, 2012 Joerg Sonnenberger 
  *
  * Permission to use, copy, modify, and distribute this software for any
@@ -19,10 +19,8 @@
 #include "config.h"
 
 #include 
-#if HAVE_MMAP
 #include 
 #include 
-#endif
 
 #include 
 #include 
@@ -110,10 +108,11 @@ static	const char * const	mandocerrs[MANDOCERR_MAX] = {
 	"no document body",
 	"content before first section header",
 	"first section is not \"NAME\"",
-	"NAME section without name",
+	"NAME section without Nm before Nd",
 	"NAME section without description",
 	"description not at the end of NAME",
 	"bad NAME section content",
+	"missing comma before name",
 	"missing description line, using \"\"",
 	"sections out of conventional order",
 	"duplicate section title",
@@ -143,7 +142,7 @@ static	const char * const	mandocerrs[MANDOCERR_MAX] = {
 	"empty argument, using 0n",
 	"missing display type, using -ragged",
 	"list type is not the first argument",
-	"missing -width in -tag list, using 8n",
+	"missing -width in -tag list, using 6n",
 	"missing utility name, using \"\"",
 	"missing function name, using \"\"",
 	"empty head in list item",
@@ -152,6 +151,7 @@ static	const char * const	mandocerrs[MANDOCERR_MAX] = {
 	"unknown font type, using \\fR",
 	"nothing follows prefix",
 	"empty reference block",
+	"missing section argument",
 	"missing -std argument, adding it",
 	"missing option string, using \"\"",
 	"missing resource identifier, using \"\"",
@@ -291,13 +291,6 @@ choose_parser(struct mparse *curp)
 		}
 	}
 
-	if (curp->man == NULL) {
-		curp->man = roff_man_alloc(curp->roff, curp, curp->defos,
-		    curp->options & MPARSE_QUICK ? 1 : 0);
-		curp->man->macroset = MACROSET_MAN;
-		curp->man->first->tok = TOKEN_NONE;
-	}
-
 	if (format == MPARSE_MDOC) {
 		mdoc_hash_init();
 		curp->man->macroset = MACROSET_MDOC;
@@ -324,6 +317,7 @@ mparse_buf_r(struct mparse *curp, struct buf blk, size_t i, int start)
 	const char	*save_file;
 	char		*cp;
 	size_t		 pos; /* byte number in the ln buffer */
+	size_t		 j;  /* auxiliary byte number in the blk buffer */
 	enum rofferr	 rr;
 	int		 of;
 	int		 lnn; /* line number in the real file */
@@ -429,14 +423,21 @@ mparse_buf_r(struct mparse *curp, struct buf blk, size_t i, int start)
 			}
 
 			if ('"' == blk.buf[i + 1] || '#' == blk.buf[i + 1]) {
+				j = i;
 				i += 2;
 				/* Comment, skip to end of line */
 				for (; i < blk.sz; ++i) {
-					if ('\n' == blk.buf[i]) {
-						++i;
-						++lnn;
-						break;
-					}
+					if (blk.buf[i] != '\n')
+						continue;
+					if (blk.buf[i - 1] == ' ' ||
+					    blk.buf[i - 1] == '\t')
+						mandoc_msg(
+						    MANDOCERR_SPACE_EOL,
+						    curp, curp->line,
+						    pos + i-1 - j, NULL);
+					++i;
+					++lnn;
+					break;
 				}
 
 				/* Backout trailing whitespaces */
@@ -562,15 +563,7 @@ mparse_buf_r(struct mparse *curp, struct buf blk, size_t i, int start)
 			break;
 		}
 
-		/*
-		 * If input parsers have not been allocated, do so now.
-		 * We keep these instanced between parsers, but set them
-		 * locally per parse routine since we can use different
-		 * parsers with each one.
-		 */
-
-		if (curp->man == NULL ||
-		    curp->man->macroset == MACROSET_NONE)
+		if (curp->man->macroset == MACROSET_NONE)
 			choose_parser(curp);
 
 		/*
@@ -613,7 +606,6 @@ read_whole_file(struct mparse *curp, const char *file, int fd,
 	size_t		 off;
 	ssize_t		 ssz;
 
-#if HAVE_MMAP
 	struct stat	 st;
 
 	if (fstat(fd, &st) == -1)
@@ -637,7 +629,6 @@ read_whole_file(struct mparse *curp, const char *file, int fd,
 		if (fb->buf != MAP_FAILED)
 			return 1;
 	}
-#endif
 
 	if (curp->gzip) {
 		if ((gz = gzdopen(fd, "rb")) == NULL)
@@ -683,10 +674,6 @@ read_whole_file(struct mparse *curp, const char *file, int fd,
 static void
 mparse_end(struct mparse *curp)
 {
-
-	if (curp->man == NULL && curp->sodest == NULL)
-		curp->man = roff_man_alloc(curp->roff, curp, curp->defos,
-		    curp->options & MPARSE_QUICK ? 1 : 0);
 	if (curp->man->macroset == MACROSET_NONE)
 		curp->man->macroset = MACROSET_MAN;
 	if (curp->man->macroset == MACROSET_MDOC)
@@ -766,11 +753,9 @@ mparse_readfd(struct mparse *curp, int fd, const char *file)
 		    (MPARSE_UTF8 | MPARSE_LATIN1);
 		mparse_parse_buffer(curp, blk, file);
 		curp->filenc = save_filenc;
-#if HAVE_MMAP
 		if (with_mmap)
 			munmap(blk.buf, blk.sz);
 		else
-#endif
 			free(blk.buf);
 	}
 	return curp->file_status;
@@ -842,11 +827,8 @@ mparse_alloc(int options, enum mandoclevel wlevel, mandocmsg mmsg,
 void
 mparse_reset(struct mparse *curp)
 {
-
 	roff_reset(curp->roff);
-
-	if (curp->man != NULL)
-		roff_man_reset(curp->man);
+	roff_man_reset(curp->man);
 	if (curp->secondary)
 		curp->secondary->sz = 0;
 
@@ -884,6 +866,13 @@ mparse_result(struct mparse *curp, struct roff_man **man,
 		*man = curp->man;
 }
 
+void
+mparse_updaterc(struct mparse *curp, enum mandoclevel *rc)
+{
+	if (curp->file_status > *rc)
+		*rc = curp->file_status;
+}
+
 void
 mandoc_vmsg(enum mandocerr t, struct mparse *m,
 		int ln, int pos, const char *fmt, ...)
diff --git a/roff.c b/roff.c
index 13b9439e4df9..966ed9d53dd9 100644
--- a/roff.c
+++ b/roff.c
@@ -1,7 +1,7 @@
-/*	$Id: roff.c,v 1.284 2016/01/08 17:48:10 schwarze Exp $ */
+/*	$Id: roff.c,v 1.288 2017/01/12 18:02:20 schwarze Exp $ */
 /*
  * Copyright (c) 2008-2012, 2014 Kristaps Dzonsons 
- * Copyright (c) 2010-2015 Ingo Schwarze 
+ * Copyright (c) 2010-2015, 2017 Ingo Schwarze 
  *
  * Permission to use, copy, modify, and distribute this software for any
  * purpose with or without fee is hereby granted, provided that the above
@@ -991,11 +991,11 @@ roff_node_alloc(struct roff_man *man, int line, int pos,
 	n->sec = man->lastsec;
 
 	if (man->flags & MDOC_SYNOPSIS)
-		n->flags |= MDOC_SYNPRETTY;
+		n->flags |= NODE_SYNPRETTY;
 	else
-		n->flags &= ~MDOC_SYNPRETTY;
+		n->flags &= ~NODE_SYNPRETTY;
 	if (man->flags & MDOC_NEWLINE)
-		n->flags |= MDOC_LINE;
+		n->flags |= NODE_LINE;
 	man->flags &= ~MDOC_NEWLINE;
 
 	return n;
@@ -1017,9 +1017,13 @@ roff_node_append(struct roff_man *man, struct roff_node *n)
 		n->parent = man->last->parent;
 		break;
 	case ROFF_NEXT_CHILD:
+		if (man->last->child != NULL) {
+			n->next = man->last->child;
+			man->last->child->prev = n;
+		} else
+			man->last->last = n;
 		man->last->child = n;
 		n->parent = man->last;
-		n->parent->last = n;
 		break;
 	default:
 		abort();
@@ -1059,10 +1063,7 @@ roff_word_alloc(struct roff_man *man, int line, int pos, const char *word)
 	n = roff_node_alloc(man, line, pos, ROFFT_TEXT, TOKEN_NONE);
 	n->string = roff_strdup(man->roff, word);
 	roff_node_append(man, n);
-	if (man->macroset == MACROSET_MDOC)
-		n->flags |= MDOC_VALID | MDOC_ENDED;
-	else
-		n->flags |= MAN_VALID;
+	n->flags |= NODE_VALID | NODE_ENDED;
 	man->next = ROFF_NEXT_SIBLING;
 }
 
@@ -1132,7 +1133,7 @@ roff_addeqn(struct roff_man *man, const struct eqn *eqn)
 	n = roff_node_alloc(man, eqn->ln, eqn->pos, ROFFT_EQN, TOKEN_NONE);
 	n->eqn = eqn;
 	if (eqn->ln > man->last->line)
-		n->flags |= MDOC_LINE;
+		n->flags |= NODE_LINE;
 	roff_node_append(man, n);
 	man->next = ROFF_NEXT_SIBLING;
 }
@@ -1147,10 +1148,7 @@ roff_addtbl(struct roff_man *man, const struct tbl_span *tbl)
 	n = roff_node_alloc(man, tbl->line, 0, ROFFT_TBL, TOKEN_NONE);
 	n->span = tbl;
 	roff_node_append(man, n);
-	if (man->macroset == MACROSET_MDOC)
-		n->flags |= MDOC_VALID | MDOC_ENDED;
-	else
-		n->flags |= MAN_VALID;
+	n->flags |= NODE_VALID | NODE_ENDED;
 	man->next = ROFF_NEXT_SIBLING;
 }
 
@@ -1225,16 +1223,12 @@ deroff(char **dest, const struct roff_node *n)
 		return;
 	}
 
-	/* Skip leading whitespace and escape sequences. */
+	/* Skip leading whitespace. */
 
-	cp = n->string;
-	while (*cp != '\0') {
-		if ('\\' == *cp) {
+	for (cp = n->string; *cp != '\0'; cp++) {
+		if (cp[0] == '\\' && strchr(" %&0^|~", cp[1]) != NULL)
 			cp++;
-			mandoc_escape((const char **)&cp, NULL, NULL);
-		} else if (isspace((unsigned char)*cp))
-			cp++;
-		else
+		else if ( ! isspace((unsigned char)*cp))
 			break;
 	}
 
diff --git a/roff.h b/roff.h
index 19ec50f42e5c..3039d4e9f536 100644
--- a/roff.h
+++ b/roff.h
@@ -1,7 +1,7 @@
-/*	$OpenBSD$	*/
+/*	$Id: roff.h,v 1.39 2017/01/10 13:47:00 schwarze Exp $	*/
 /*
  * Copyright (c) 2008, 2009, 2010, 2011 Kristaps Dzonsons 
- * Copyright (c) 2013, 2014, 2015 Ingo Schwarze 
+ * Copyright (c) 2013, 2014, 2015, 2017 Ingo Schwarze 
  *
  * Permission to use, copy, modify, and distribute this software for any
  * purpose with or without fee is hereby granted, provided that the above
@@ -98,17 +98,16 @@ struct	roff_node {
 	int		  tok;     /* Request or macro ID. */
 #define	TOKEN_NONE	 (-1)	   /* No request or macro. */
 	int		  flags;
-#define	MDOC_VALID	 (1 << 0)  /* Has been validated. */
-#define	MDOC_ENDED	 (1 << 1)  /* Gone past body end mark. */
-#define MDOC_EOS	 (1 << 2)  /* At sentence boundary. */
-#define	MDOC_LINE	 (1 << 3)  /* First macro/text on line. */
-#define MDOC_SYNPRETTY	 (1 << 4)  /* SYNOPSIS-style formatting. */
-#define MDOC_BROKEN	 (1 << 5)  /* Must validate parent when ending. */
-#define	MDOC_DELIMO	 (1 << 6)
-#define	MDOC_DELIMC	 (1 << 7)
-#define	MAN_VALID	  MDOC_VALID
-#define	MAN_EOS		  MDOC_EOS
-#define	MAN_LINE	  MDOC_LINE
+#define	NODE_VALID	 (1 << 0)  /* Has been validated. */
+#define	NODE_ENDED	 (1 << 1)  /* Gone past body end mark. */
+#define	NODE_EOS	 (1 << 2)  /* At sentence boundary. */
+#define	NODE_LINE	 (1 << 3)  /* First macro/text on line. */
+#define	NODE_SYNPRETTY	 (1 << 4)  /* SYNOPSIS-style formatting. */
+#define	NODE_BROKEN	 (1 << 5)  /* Must validate parent when ending. */
+#define	NODE_DELIMO	 (1 << 6)
+#define	NODE_DELIMC	 (1 << 7)
+#define	NODE_NOSRC	 (1 << 8)  /* Generated node, not in input file. */
+#define	NODE_NOPRT	 (1 << 9)  /* Shall not print anything. */
 	int		  prev_font; /* Before entering this node. */
 	int		  aux;     /* Decoded node data, type-dependent. */
 	enum roff_type	  type;    /* AST node type. */
diff --git a/tag.c b/tag.c
index baedf15ad9e4..0fbd2e105978 100644
--- a/tag.c
+++ b/tag.c
@@ -1,6 +1,6 @@
-/*      $Id: tag.c,v 1.12 2016/07/08 20:42:15 schwarze Exp $    */
+/*	$Id: tag.c,v 1.17 2017/01/09 17:49:58 schwarze Exp $ */
 /*
- * Copyright (c) 2015 Ingo Schwarze 
+ * Copyright (c) 2015, 2016 Ingo Schwarze 
  *
  * Permission to use, copy, modify, and distribute this software for any
  * purpose with or without fee is hereby granted, provided that the above
@@ -31,12 +31,14 @@
 #include "tag.h"
 
 struct tag_entry {
-	size_t	 line;
+	size_t	*lines;
+	size_t	 maxlines;
+	size_t	 nlines;
 	int	 prio;
 	char	 s[];
 };
 
-static	void	 tag_signal(int);
+static	void	 tag_signal(int) __attribute__((noreturn));
 
 static struct ohash	 tag_data;
 static struct tag_files	 tag_files;
@@ -128,18 +130,58 @@ tag_put(const char *s, int prio, size_t line)
 	size_t			 len;
 	unsigned int		 slot;
 
-	if (tag_files.tfd <= 0 || strchr(s, ' ') != NULL)
+	/* Sanity checks. */
+
+	if (tag_files.tfd <= 0)
 		return;
+	if (s[0] == '\\' && (s[1] == '&' || s[1] == 'e'))
+		s += 2;
+	if (*s == '\0' || strchr(s, ' ') != NULL)
+		return;
+
 	slot = ohash_qlookup(&tag_data, s);
 	entry = ohash_find(&tag_data, slot);
+
 	if (entry == NULL) {
+
+		/* Build a new entry. */
+
 		len = strlen(s) + 1;
 		entry = mandoc_malloc(sizeof(*entry) + len);
 		memcpy(entry->s, s, len);
+		entry->lines = NULL;
+		entry->maxlines = entry->nlines = 0;
 		ohash_insert(&tag_data, slot, entry);
-	} else if (entry->prio <= prio)
-		return;
-	entry->line = line;
+
+	} else {
+
+		/* Handle priority 0 entries. */
+
+		if (prio == 0) {
+			if (entry->prio == 0)
+				entry->prio = -1;
+			return;
+		}
+
+		/* A better entry is already present, ignore the new one. */
+
+		if (entry->prio > 0 && entry->prio < prio)
+			return;
+
+		/* The existing entry is worse, clear it. */
+
+		if (entry->prio < 1 || entry->prio > prio)
+			entry->nlines = 0;
+	}
+
+	/* Remember the new line. */
+
+	if (entry->maxlines == entry->nlines) {
+		entry->maxlines += 4;
+		entry->lines = mandoc_reallocarray(entry->lines,
+		    entry->maxlines, sizeof(*entry->lines));
+	}
+	entry->lines[entry->nlines++] = line;
 	entry->prio = prio;
 }
 
@@ -152,6 +194,7 @@ tag_write(void)
 {
 	FILE			*stream;
 	struct tag_entry	*entry;
+	size_t			 i;
 	unsigned int		 slot;
 
 	if (tag_files.tfd <= 0)
@@ -159,9 +202,11 @@ tag_write(void)
 	stream = fdopen(tag_files.tfd, "w");
 	entry = ohash_first(&tag_data, &slot);
 	while (entry != NULL) {
-		if (stream != NULL)
-			fprintf(stream, "%s %s %zu\n",
-			    entry->s, tag_files.ofn, entry->line);
+		if (stream != NULL && entry->prio >= 0)
+			for (i = 0; i < entry->nlines; i++)
+				fprintf(stream, "%s %s %zu\n",
+				    entry->s, tag_files.ofn, entry->lines[i]);
+		free(entry->lines);
 		free(entry);
 		entry = ohash_next(&tag_data, &slot);
 	}
@@ -176,11 +221,11 @@ tag_unlink(void)
 	pid_t	 tc_pgid;
 
 	if (tag_files.tcpgid != -1) {
-		tc_pgid = tcgetpgrp(STDIN_FILENO);
+		tc_pgid = tcgetpgrp(tag_files.ofd);
 		if (tc_pgid == tag_files.pager_pid ||
 		    tc_pgid == getpgid(0) ||
 		    getpgid(tc_pgid) == -1)
-			(void)tcsetpgrp(STDIN_FILENO, tag_files.tcpgid);
+			(void)tcsetpgrp(tag_files.ofd, tag_files.tcpgid);
 	}
 	if (*tag_files.ofn != '\0')
 		unlink(tag_files.ofn);
diff --git a/tbl_html.c b/tbl_html.c
index 51c43286cbeb..962d900e3682 100644
--- a/tbl_html.c
+++ b/tbl_html.c
@@ -1,7 +1,7 @@
-/*	$Id: tbl_html.c,v 1.18 2015/10/12 00:08:16 schwarze Exp $ */
+/*	$Id: tbl_html.c,v 1.19 2017/01/17 01:47:51 schwarze Exp $ */
 /*
  * Copyright (c) 2011 Kristaps Dzonsons 
- * Copyright (c) 2014, 2015 Ingo Schwarze 
+ * Copyright (c) 2014, 2015, 2017 Ingo Schwarze 
  *
  * Permission to use, copy, modify, and distribute this software for any
  * purpose with or without fee is hereby granted, provided that the above
@@ -50,9 +50,6 @@ html_tbl_strlen(const char *p, void *arg)
 static void
 html_tblopen(struct html *h, const struct tbl_span *sp)
 {
-	struct htmlpair	 tag;
-	struct roffsu	 su;
-	struct roffcol	*col;
 	int		 ic;
 
 	if (h->tbl.cols == NULL) {
@@ -62,19 +59,12 @@ html_tblopen(struct html *h, const struct tbl_span *sp)
 	}
 
 	assert(NULL == h->tblt);
-	PAIR_CLASS_INIT(&tag, "tbl");
-	h->tblt = print_otag(h, TAG_TABLE, 1, &tag);
+	h->tblt = print_otag(h, TAG_TABLE, "c", "tbl");
 
-	for (ic = 0; ic < sp->opts->cols; ic++) {
-		bufinit(h);
-		col = h->tbl.cols + ic;
-		SCALE_HS_INIT(&su, col->width);
-		bufcat_su(h, "width", &su);
-		PAIR_STYLE_INIT(&tag, h);
-		print_otag(h, TAG_COL, 1, &tag);
-	}
+	for (ic = 0; ic < sp->opts->cols; ic++)
+		print_otag(h, TAG_COL, "shw", h->tbl.cols[ic].width);
 
-	print_otag(h, TAG_TBODY, 0, NULL);
+	print_otag(h, TAG_TBODY, "");
 }
 
 void
@@ -90,7 +80,6 @@ void
 print_tbl(struct html *h, const struct tbl_span *sp)
 {
 	const struct tbl_dat *dp;
-	struct htmlpair	 tag;
 	struct tag	*tt;
 	int		 ic;
 
@@ -104,19 +93,18 @@ print_tbl(struct html *h, const struct tbl_span *sp)
 	h->flags |= HTML_NONOSPACE;
 	h->flags |= HTML_NOSPACE;
 
-	tt = print_otag(h, TAG_TR, 0, NULL);
+	tt = print_otag(h, TAG_TR, "");
 
 	switch (sp->pos) {
 	case TBL_SPAN_HORIZ:
 	case TBL_SPAN_DHORIZ:
-		PAIR_INIT(&tag, ATTR_COLSPAN, "0");
-		print_otag(h, TAG_TD, 1, &tag);
+		print_otag(h, TAG_TD, "?", "colspan", "0");
 		break;
 	default:
 		dp = sp->first;
 		for (ic = 0; ic < sp->opts->cols; ic++) {
 			print_stagq(h, tt);
-			print_otag(h, TAG_TD, 0, NULL);
+			print_otag(h, TAG_TD, "");
 
 			if (dp == NULL || dp->layout->col > ic)
 				continue;
diff --git a/term.c b/term.c
index 0fd8f7c01974..1217d473cadd 100644
--- a/term.c
+++ b/term.c
@@ -1,7 +1,7 @@
-/*	$Id: term.c,v 1.257 2016/04/12 15:30:00 schwarze Exp $ */
+/*	$Id: term.c,v 1.259 2017/01/08 18:16:58 schwarze Exp $ */
 /*
  * Copyright (c) 2008, 2009, 2010, 2011 Kristaps Dzonsons 
- * Copyright (c) 2010-2015 Ingo Schwarze 
+ * Copyright (c) 2010-2017 Ingo Schwarze 
  *
  * Permission to use, copy, modify, and distribute this software for any
  * purpose with or without fee is hereby granted, provided that the above
@@ -504,7 +504,9 @@ term_word(struct termp *p, const char *word)
 				}
 			}
 			/* Trim trailing backspace/blank pair. */
-			if (p->col > 2 && p->buf[p->col - 1] == ' ')
+			if (p->col > 2 &&
+			    (p->buf[p->col - 1] == ' ' ||
+			     p->buf[p->col - 1] == '\t'))
 				p->col -= 2;
 			continue;
 		default:
@@ -568,7 +570,7 @@ encode1(struct termp *p, int c)
 	    p->fontq[p->fonti] : TERMFONT_NONE;
 
 	if (p->flags & TERMP_BACKBEFORE) {
-		if (p->buf[p->col - 1] == ' ')
+		if (p->buf[p->col - 1] == ' ' || p->buf[p->col - 1] == '\t')
 			p->col--;
 		else
 			p->buf[p->col++] = 8;
@@ -604,8 +606,20 @@ encode(struct termp *p, const char *word, size_t sz)
 		if (ASCII_HYPH == word[i] ||
 		    isgraph((unsigned char)word[i]))
 			encode1(p, word[i]);
-		else
+		else {
 			p->buf[p->col++] = word[i];
+
+			/*
+			 * Postpone the effect of \z while handling
+			 * an overstrike sequence from ascii_uc2str().
+			 */
+
+			if (word[i] == '\b' &&
+			    (p->flags & TERMP_BACKBEFORE)) {
+				p->flags &= ~TERMP_BACKBEFORE;
+				p->flags |= TERMP_BACKAFTER;
+			}
+		}
 	}
 }
 
diff --git a/term_ascii.c b/term_ascii.c
index fecdb0a964dc..df5ff13901c2 100644
--- a/term_ascii.c
+++ b/term_ascii.c
@@ -1,4 +1,4 @@
-/*	$Id: term_ascii.c,v 1.53 2016/07/08 22:29:05 schwarze Exp $ */
+/*	$Id: term_ascii.c,v 1.54 2016/07/31 09:29:13 schwarze Exp $ */
 /*
  * Copyright (c) 2010, 2011 Kristaps Dzonsons 
  * Copyright (c) 2014, 2015 Ingo Schwarze 
@@ -98,7 +98,7 @@ ascii_init(enum termenc enc, const struct manoutput *outopts)
 
 		v = TERMENC_LOCALE == enc ?
 		    setlocale(LC_CTYPE, "") :
-		    setlocale(LC_CTYPE, "en_US.UTF-8");
+		    setlocale(LC_CTYPE, UTF8_LOCALE);
 		if (NULL != v && MB_CUR_MAX > 1) {
 			p->enc = enc;
 			p->advance = locale_advance;
diff --git a/term_ps.c b/term_ps.c
index 6105d5589fc4..286a89f91a61 100644
--- a/term_ps.c
+++ b/term_ps.c
@@ -1,7 +1,7 @@
-/*	$Id: term_ps.c,v 1.80 2015/12/23 20:50:13 schwarze Exp $ */
+/*	$Id: term_ps.c,v 1.82 2016/08/10 11:03:43 schwarze Exp $ */
 /*
  * Copyright (c) 2010, 2011 Kristaps Dzonsons 
- * Copyright (c) 2014, 2015 Ingo Schwarze 
+ * Copyright (c) 2014, 2015, 2016 Ingo Schwarze 
  *
  * Permission to use, copy, modify, and distribute this software for any
  * purpose with or without fee is hereby granted, provided that the above
@@ -98,15 +98,13 @@ static	void		  ps_begin(struct termp *);
 static	void		  ps_closepage(struct termp *);
 static	void		  ps_end(struct termp *);
 static	void		  ps_endline(struct termp *);
-static	void		  ps_fclose(struct termp *);
 static	void		  ps_growbuf(struct termp *, size_t);
 static	void		  ps_letter(struct termp *, int);
 static	void		  ps_pclose(struct termp *);
+static	void		  ps_plast(struct termp *);
 static	void		  ps_pletter(struct termp *, int);
-#if __GNUC__ - 0 >= 4
-__attribute__((__format__ (__printf__, 2, 3)))
-#endif
-static	void		  ps_printf(struct termp *, const char *, ...);
+static	void		  ps_printf(struct termp *, const char *, ...)
+				__attribute__((__format__ (printf, 2, 3)));
 static	void		  ps_putchar(struct termp *, char);
 static	void		  ps_setfont(struct termp *, enum termfont);
 static	void		  ps_setwidth(struct termp *, int, int);
@@ -782,6 +780,9 @@ ps_end(struct termp *p)
 {
 	size_t		 i, xref, base;
 
+	ps_plast(p);
+	ps_pclose(p);
+
 	/*
 	 * At the end of the file, do one last showpage.  This is the
 	 * same behaviour as groff(1) and works for multiple pages as
@@ -1025,39 +1026,53 @@ ps_pclose(struct termp *p)
 	p->ps->flags &= ~PS_INLINE;
 }
 
+/* If we have a `last' char that wasn't printed yet, print it now. */
 static void
-ps_fclose(struct termp *p)
+ps_plast(struct termp *p)
 {
+	size_t	 wx;
 
-	/*
-	 * Strong closure: if we have a last-char, spit it out after
-	 * checking that we're in the right font mode.  This will of
-	 * course open a new scope, if applicable.
-	 *
-	 * Following this, close out any scope that's open.
-	 */
-
-	if (p->ps->last != '\0') {
-		assert( ! (p->ps->flags & PS_BACKSP));
-		if (p->ps->nextf != p->ps->lastf) {
-			ps_pclose(p);
-			ps_setfont(p, p->ps->nextf);
-		}
-		p->ps->nextf = TERMFONT_NONE;
-		ps_pletter(p, p->ps->last);
-		p->ps->last = '\0';
-	}
-
-	if ( ! (PS_INLINE & p->ps->flags))
+	if (p->ps->last == '\0')
 		return;
 
-	ps_pclose(p);
+	/* Check the font mode; open a new scope if it doesn't match. */
+
+	if (p->ps->nextf != p->ps->lastf) {
+		ps_pclose(p);
+		ps_setfont(p, p->ps->nextf);
+	}
+	p->ps->nextf = TERMFONT_NONE;
+
+	/*
+	 * For an overstrike, if a previous character
+	 * was wider, advance to center the new one.
+	 */
+
+	if (p->ps->pscolnext) {
+		wx = fonts[p->ps->lastf].gly[(int)p->ps->last-32].wx;
+		if (p->ps->pscol + wx < p->ps->pscolnext)
+			p->ps->pscol = (p->ps->pscol +
+			    p->ps->pscolnext - wx) / 2;
+	}
+
+	ps_pletter(p, p->ps->last);
+	p->ps->last = '\0';
+
+	/*
+	 * For an overstrike, if a previous character
+	 * was wider, advance to the end of the old one.
+	 */
+
+	if (p->ps->pscol < p->ps->pscolnext) {
+		ps_pclose(p);
+		p->ps->pscol = p->ps->pscolnext;
+	}
 }
 
 static void
 ps_letter(struct termp *p, int arg)
 {
-	size_t		savecol, wx;
+	size_t		savecol;
 	char		c;
 
 	c = arg >= 128 || arg <= 0 ? '?' : arg;
@@ -1123,43 +1138,12 @@ ps_letter(struct termp *p, int arg)
 	 * Use them and print it.
 	 */
 
-	if (p->ps->last != '\0') {
-		if (p->ps->nextf != p->ps->lastf) {
-			ps_pclose(p);
-			ps_setfont(p, p->ps->nextf);
-		}
-		p->ps->nextf = TERMFONT_NONE;
-
-		/*
-		 * For an overstrike, if a previous character
-		 * was wider, advance to center the new one.
-		 */
-
-		if (p->ps->pscolnext) {
-			wx = fonts[p->ps->lastf].gly[(int)p->ps->last-32].wx;
-			if (p->ps->pscol + wx < p->ps->pscolnext)
-				p->ps->pscol = (p->ps->pscol +
-				    p->ps->pscolnext - wx) / 2;
-		}
-
-		ps_pletter(p, p->ps->last);
-
-		/*
-		 * For an overstrike, if a previous character
-		 * was wider, advance to the end of the old one.
-		 */
-
-		if (p->ps->pscol < p->ps->pscolnext) {
-			ps_pclose(p);
-			p->ps->pscol = p->ps->pscolnext;
-		}
-	}
+	ps_plast(p);
 
 	/*
 	 * Do not print the current character yet because font
-	 * instructions might follow; only remember it.
-	 * For the first character, nothing else is done.
-	 * The final character will get printed from ps_fclose().
+	 * instructions might follow; only remember the character.
+	 * It will get printed later from ps_plast().
 	 */
 
 	p->ps->last = c;
@@ -1192,7 +1176,8 @@ ps_advance(struct termp *p, size_t len)
 	 * and readjust our column settings.
 	 */
 
-	ps_fclose(p);
+	ps_plast(p);
+	ps_pclose(p);
 	p->ps->pscol += len;
 }
 
@@ -1202,7 +1187,8 @@ ps_endline(struct termp *p)
 
 	/* Close out any scopes we have open: we're at eoln. */
 
-	ps_fclose(p);
+	ps_plast(p);
+	ps_pclose(p);
 
 	/*
 	 * If we're in the margin, don't try to recalculate our current
diff --git a/test-EFTYPE.c b/test-EFTYPE.c
new file mode 100644
index 000000000000..148f54f769ed
--- /dev/null
+++ b/test-EFTYPE.c
@@ -0,0 +1,7 @@
+#include 
+
+int
+main(void)
+{
+	return !EFTYPE;
+}
diff --git a/test-PATH_MAX.c b/test-PATH_MAX.c
new file mode 100644
index 000000000000..99bcc0b48695
--- /dev/null
+++ b/test-PATH_MAX.c
@@ -0,0 +1,30 @@
+/*
+ * POSIX allows PATH_MAX to not be defined, see
+ * http://pubs.opengroup.org/onlinepubs/9699919799/functions/sysconf.html;
+ * the GNU Hurd is an example of a system not having it.
+ *
+ * Arguably, it would be better to test sysconf(_SC_PATH_MAX),
+ * but since the individual *.c files include "config.h" before
+ * , overriding an excessive value of PATH_MAX from
+ * "config.h" is impossible anyway, so for now, the simplest
+ * fix is to provide a value only on systems not having any.
+ * So far, we encountered no system defining PATH_MAX to an
+ * impractically large value, even though POSIX explicitly
+ * allows that.
+ *
+ * The real fix would be to replace all static buffers of size
+ * PATH_MAX by dynamically allocated buffers.  But that is
+ * somewhat intrusive because it touches several files and
+ * because it requires changing struct mlink in mandocdb.c.
+ * So i'm postponing that for now.
+ */
+
+#include 
+#include 
+
+int
+main(void)
+{
+	printf("PATH_MAX is defined to be %ld\n", (long)PATH_MAX);
+	return 0;
+}
diff --git a/test-be32toh.c b/test-be32toh.c
new file mode 100644
index 000000000000..471e85ea5e43
--- /dev/null
+++ b/test-be32toh.c
@@ -0,0 +1,11 @@
+#ifdef SYS_ENDIAN
+#include 
+#else
+#include 
+#endif
+
+int
+main(void)
+{
+	return htobe32(be32toh(0x3a7d0cdb)) != 0x3a7d0cdb;
+}
diff --git a/test-fts.c b/test-fts.c
index dbee52926d4a..23e441525760 100644
--- a/test-fts.c
+++ b/test-fts.c
@@ -2,6 +2,13 @@
 #include 
 #include 
 #include 
+#include 
+
+#ifdef FTS_COMPARE_CONST
+int fts_compare(const FTSENT *const *, const FTSENT *const *);
+#else
+int fts_compare(const FTSENT **, const FTSENT **);
+#endif
 
 int
 main(void)
@@ -14,7 +21,7 @@ main(void)
 	argv[1] = (char *)NULL;
 
 	ftsp = fts_open((char * const *)argv,
-	    FTS_PHYSICAL | FTS_NOCHDIR, NULL);
+	    FTS_PHYSICAL | FTS_NOCHDIR, fts_compare);
 
 	if (ftsp == NULL) {
 		perror("fts_open");
@@ -40,3 +47,13 @@ main(void)
 
 	return 0;
 }
+
+int
+#ifdef FTS_COMPARE_CONST
+fts_compare(const FTSENT *const *a, const FTSENT *const *b)
+#else
+fts_compare(const FTSENT **a, const FTSENT **b)
+#endif
+{
+	return strcmp((*a)->fts_name, (*b)->fts_name);
+}
diff --git a/test-mmap.c b/test-mmap.c
deleted file mode 100644
index 3a6232d9ce91..000000000000
--- a/test-mmap.c
+++ /dev/null
@@ -1,9 +0,0 @@
-#include 
-#include 
-#include 
-
-int
-main(void)
-{
-	return mmap(NULL, 1, PROT_READ, MAP_SHARED, -1, 0) != MAP_FAILED;
-}
diff --git a/test-nanosleep.c b/test-nanosleep.c
new file mode 100644
index 000000000000..4b25ca4cfbf9
--- /dev/null
+++ b/test-nanosleep.c
@@ -0,0 +1,17 @@
+#include 
+#include 
+
+int
+main(void)
+{
+	struct timespec	 timeout;
+
+	timeout.tv_sec = 0;
+	timeout.tv_nsec = 100000000;	/* 0.1 seconds */
+	
+	if (nanosleep(&timeout, NULL)) {
+		perror("nanosleep");
+		return 1;
+	}
+	return 0;
+}
diff --git a/test-ntohl.c b/test-ntohl.c
new file mode 100644
index 000000000000..52dcc256ff56
--- /dev/null
+++ b/test-ntohl.c
@@ -0,0 +1,7 @@
+#include 
+
+int
+main(void)
+{
+	return htonl(ntohl(0x3a7d0cdb)) != 0x3a7d0cdb;
+}
diff --git a/test-ohash.c b/test-ohash.c
index 138d520d1c4b..1844fe56ad8a 100644
--- a/test-ohash.c
+++ b/test-ohash.c
@@ -3,9 +3,27 @@
 #include 
 #include 
 
-void *xmalloc(size_t sz, void *arg) { return calloc(1,sz); }
-void *xcalloc(size_t nmemb, size_t sz, void *arg) { return calloc(nmemb,sz); }
-void xfree(void *p, void *arg) { free(p); }
+static void	*xmalloc(size_t, void *);
+static void	*xcalloc(size_t, size_t, void *);
+static void	 xfree(void *, void *);
+
+
+static void *
+xmalloc(size_t sz, void *arg) {
+	return calloc(1,sz);
+}
+
+static void *
+xcalloc(size_t nmemb, size_t sz, void *arg)
+{
+	return calloc(nmemb,sz);
+}
+
+static void
+xfree(void *p, void *arg)
+{
+	free(p);
+}
 
 int
 main(void)
diff --git a/test-sandbox_init.c b/test-sandbox_init.c
new file mode 100644
index 000000000000..a4902ee61669
--- /dev/null
+++ b/test-sandbox_init.c
@@ -0,0 +1,13 @@
+#include 
+
+int
+main(void)
+{
+	char	*ep;
+	int	 rc;
+
+	rc = sandbox_init(kSBXProfileNoInternet, SANDBOX_NAMED, &ep);
+	if (-1 == rc)
+		sandbox_free_error(ep);
+	return(-1 == rc);
+}
diff --git a/test-sqlite3.c b/test-sqlite3.c
deleted file mode 100644
index 11f17adcbbb3..000000000000
--- a/test-sqlite3.c
+++ /dev/null
@@ -1,47 +0,0 @@
-/*	$Id: test-sqlite3.c,v 1.2 2015/10/06 18:32:20 schwarze Exp $	*/
-/*
- * Copyright (c) 2014 Ingo Schwarze 
- *
- * Permission to use, copy, modify, and distribute this software for any
- * purpose with or without fee is hereby granted, provided that the above
- * copyright notice and this permission notice appear in all copies.
- *
- * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
- * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
- * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
- * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
- * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
- * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
- * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
- */
-
-#include 
-#include 
-#include 
-
-int
-main(void)
-{
-	sqlite3	*db;
-
-	if (sqlite3_open_v2("test.db", &db,
-	    SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE,
-	    NULL) != SQLITE_OK) {
-		perror("test.db");
-		fprintf(stderr, "sqlite3_open_v2: %s", sqlite3_errmsg(db));
-		return 1;
-	}
-	unlink("test.db");
-
-	if (sqlite3_exec(db, "PRAGMA foreign_keys = ON",
-	    NULL, NULL, NULL) != SQLITE_OK) {
-		fprintf(stderr, "sqlite3_exec: %s", sqlite3_errmsg(db));
-		return 1;
-	}
-
-	if (sqlite3_close(db) != SQLITE_OK) {
-		fprintf(stderr, "sqlite3_close: %s", sqlite3_errmsg(db));
-		return 1;
-	}
-	return 0;
-}
diff --git a/test-sqlite3_errstr.c b/test-sqlite3_errstr.c
deleted file mode 100644
index 4d3c7c54a5e3..000000000000
--- a/test-sqlite3_errstr.c
+++ /dev/null
@@ -1,8 +0,0 @@
-#include 
-#include 
-
-int
-main(void)
-{
-	return strcmp(sqlite3_errstr(SQLITE_OK), "not an error");
-}
diff --git a/test-vasprintf.c b/test-vasprintf.c
index bdb4408e300b..ee6980a28955 100644
--- a/test-vasprintf.c
+++ b/test-vasprintf.c
@@ -1,4 +1,4 @@
-/*	$Id: test-vasprintf.c,v 1.3 2015/10/06 18:32:20 schwarze Exp $	*/
+/*	$Id: test-vasprintf.c,v 1.4 2016/07/18 18:35:05 schwarze Exp $	*/
 /*
  * Copyright (c) 2015 Ingo Schwarze 
  *
@@ -23,7 +23,10 @@
 #include 
 #include 
 
-int
+static int	 testfunc(char **, const char *, ...);
+
+
+static int
 testfunc(char **ret, const char *format, ...)
 {
 	va_list	 ap;
diff --git a/test-wchar.c b/test-wchar.c
index a096705ebcf1..32962d9fe665 100644
--- a/test-wchar.c
+++ b/test-wchar.c
@@ -1,4 +1,4 @@
-/*	$Id: test-wchar.c,v 1.3 2015/10/06 18:32:20 schwarze Exp $	*/
+/*	$Id: test-wchar.c,v 1.4 2016/07/31 09:29:13 schwarze Exp $	*/
 /*
  * Copyright (c) 2014 Ingo Schwarze 
  *
@@ -35,9 +35,9 @@ main(void)
 		return 1;
 	}
 
-	if (setlocale(LC_CTYPE, "en_US.UTF-8") == NULL) {
-		fputs("setlocale(LC_CTYPE, \"en_US.UTF-8\") failed\n",
-		    stderr);
+	if (setlocale(LC_CTYPE, UTF8_LOCALE) == NULL) {
+		fprintf(stderr, "setlocale(LC_CTYPE, \"%s\") failed\n",
+		    UTF8_LOCALE);
 		return 1;
 	}
 
diff --git a/tree.c b/tree.c
index 52ca7547f443..9e68b69e5fb0 100644
--- a/tree.c
+++ b/tree.c
@@ -1,7 +1,7 @@
-/*	$Id: tree.c,v 1.69 2015/10/12 00:08:16 schwarze Exp $ */
+/*	$Id: tree.c,v 1.72 2017/01/12 17:29:33 schwarze Exp $ */
 /*
  * Copyright (c) 2008, 2009, 2011, 2014 Kristaps Dzonsons 
- * Copyright (c) 2013, 2014, 2015 Ingo Schwarze 
+ * Copyright (c) 2013, 2014, 2015, 2017 Ingo Schwarze 
  *
  * Permission to use, copy, modify, and distribute this software for any
  * purpose with or without fee is hereby granted, provided that the above
@@ -33,6 +33,7 @@
 
 static	void	print_box(const struct eqn_box *, int);
 static	void	print_man(const struct roff_node *, int);
+static	void	print_meta(const struct roff_meta *);
 static	void	print_mdoc(const struct roff_node *, int);
 static	void	print_span(const struct tbl_span *, int);
 
@@ -40,17 +41,40 @@ static	void	print_span(const struct tbl_span *, int);
 void
 tree_mdoc(void *arg, const struct roff_man *mdoc)
 {
-
+	print_meta(&mdoc->meta);
+	putchar('\n');
 	print_mdoc(mdoc->first->child, 0);
 }
 
 void
 tree_man(void *arg, const struct roff_man *man)
 {
-
+	print_meta(&man->meta);
+	if (man->meta.hasbody == 0)
+		puts("body  = empty");
+	putchar('\n');
 	print_man(man->first->child, 0);
 }
 
+static void
+print_meta(const struct roff_meta *meta)
+{
+	if (meta->title != NULL)
+		printf("title = \"%s\"\n", meta->title);
+	if (meta->name != NULL)
+		printf("name  = \"%s\"\n", meta->name);
+	if (meta->msec != NULL)
+		printf("sec   = \"%s\"\n", meta->msec);
+	if (meta->vol != NULL)
+		printf("vol   = \"%s\"\n", meta->vol);
+	if (meta->arch != NULL)
+		printf("arch  = \"%s\"\n", meta->arch);
+	if (meta->os != NULL)
+		printf("os    = \"%s\"\n", meta->os);
+	if (meta->date != NULL)
+		printf("date  = \"%s\"\n", meta->date);
+}
+
 static void
 print_mdoc(const struct roff_node *n, int indent)
 {
@@ -159,15 +183,19 @@ print_mdoc(const struct roff_node *n, int indent)
 		}
 
 		putchar(' ');
-		if (MDOC_DELIMO & n->flags)
+		if (NODE_DELIMO & n->flags)
 			putchar('(');
-		if (MDOC_LINE & n->flags)
+		if (NODE_LINE & n->flags)
 			putchar('*');
 		printf("%d:%d", n->line, n->pos + 1);
-		if (MDOC_DELIMC & n->flags)
+		if (NODE_DELIMC & n->flags)
 			putchar(')');
-		if (MDOC_EOS & n->flags)
+		if (NODE_EOS & n->flags)
 			putchar('.');
+		if (NODE_NOSRC & n->flags)
+			printf(" NOSRC");
+		if (NODE_NOPRT & n->flags)
+			printf(" NOPRT");
 		putchar('\n');
 	}
 
@@ -248,10 +276,10 @@ print_man(const struct roff_node *n, int indent)
 		for (i = 0; i < indent; i++)
 			putchar(' ');
 		printf("%s (%s) ", p, t);
-		if (MAN_LINE & n->flags)
+		if (NODE_LINE & n->flags)
 			putchar('*');
 		printf("%d:%d", n->line, n->pos + 1);
-		if (MAN_EOS & n->flags)
+		if (NODE_EOS & n->flags)
 			putchar('.');
 		putchar('\n');
 	}