From e9bf778aefc154b6bdefd2772f7fd85819f465b1 Mon Sep 17 00:00:00 2001 From: Baptiste Daroussin Date: Mon, 15 Nov 2021 16:35:39 +0100 Subject: mandoc: import v1.14.6 --- INSTALL | 17 +- LICENSE | 6 +- Makefile | 64 ++-- Makefile.depend | 38 +- NEWS | 201 +++++++++- TODO | 142 ++++++- apropos.1 | 15 +- arch.c | 4 +- att.c | 4 +- catman.c | 4 +- cgi.c | 72 ++-- chars.c | 7 +- compat_err.c | 13 +- compat_fts.c | 29 +- compat_fts.h | 3 +- compat_getline.c | 13 +- compat_getsubopt.c | 13 +- compat_isblank.c | 14 +- compat_mkdtemp.c | 23 +- compat_mkstemps.c | 63 ++++ compat_ohash.c | 13 +- compat_progname.c | 15 +- compat_reallocarray.c | 15 +- compat_recallocarray.c | 25 +- compat_strcasestr.c | 13 +- compat_stringlist.c | 82 +++-- compat_stringlist.h | 43 ++- compat_strlcat.c | 62 ++-- compat_strlcpy.c | 49 +-- compat_strndup.c | 16 +- compat_strsep.c | 15 +- compat_strtonum.c | 15 +- compat_vasprintf.c | 13 +- configure | 313 +++++++++------- configure.local.example | 71 ++-- dba_array.c | 4 +- dba_read.c | 4 +- eqn.7 | 55 +-- eqn.c | 14 +- html.c | 337 ++++++++++++----- html.h | 36 +- libmandoc.h | 14 +- main.c | 956 +++++++++++++++++++++++++++--------------------- man.1 | 98 ++--- man.7 | 55 ++- man.conf.5 | 14 +- man_html.c | 43 +-- man_macro.c | 12 +- man_term.c | 132 ++----- man_validate.c | 157 +++++++- manconf.h | 10 +- mandoc.1 | 141 +++++-- mandoc.c | 85 +++-- mandoc.css | 7 +- mandoc.h | 22 +- mandoc_char.7 | 21 +- mandoc_headers.3 | 79 +++- mandoc_html.3 | 270 ++++++++++++-- mandoc_malloc.3 | 41 ++- mandoc_msg.c | 17 +- mandoc_ohash.c | 4 +- mandoc_parse.h | 3 +- mandoc_xr.c | 4 +- mandocd.c | 4 +- mandocdb.c | 247 ++++++++----- manpath.c | 48 ++- mdoc.7 | 183 +++++++-- mdoc.c | 13 +- mdoc_html.c | 385 ++++++++----------- mdoc_macro.c | 7 +- mdoc_man.c | 124 ++++--- mdoc_markdown.c | 55 ++- mdoc_state.c | 5 +- mdoc_term.c | 440 ++++++++-------------- mdoc_validate.c | 527 +++++++++++++++++--------- out.c | 75 ++-- out.h | 6 +- read.c | 31 +- roff.7 | 8 +- roff.c | 233 ++++++++---- roff.h | 17 +- roff_html.c | 4 +- roff_int.h | 5 +- roff_term.c | 56 ++- roff_validate.c | 10 +- soelim.c | 8 +- tag.c | 415 +++++++++++---------- tag.h | 35 +- tbl.7 | 13 +- tbl.h | 7 +- tbl_data.c | 53 ++- tbl_html.c | 42 ++- tbl_layout.c | 63 ++-- tbl_term.c | 60 +-- term.c | 25 +- term_ascii.c | 32 +- term_ps.c | 9 +- term_tab.c | 4 +- term_tag.c | 227 ++++++++++++ term_tag.h | 34 ++ test-attribute.c | 48 +++ test-mkstemps.c | 12 + tree.c | 201 +++++++--- 103 files changed, 5023 insertions(+), 2888 deletions(-) create mode 100644 compat_mkstemps.c create mode 100644 term_tag.c create mode 100644 term_tag.h create mode 100644 test-attribute.c create mode 100644 test-mkstemps.c diff --git a/INSTALL b/INSTALL index 3c09f3f214cb..e79674a16aa4 100644 --- a/INSTALL +++ b/INSTALL @@ -1,4 +1,4 @@ -$Id: INSTALL,v 1.23 2019/03/06 15:58:10 schwarze Exp $ +$Id: INSTALL,v 1.24 2021/09/20 13:25:42 schwarze Exp $ About the portable mandoc distribution -------------------------------------- @@ -18,7 +18,7 @@ tech@ mailing list, too. Enjoy using the mandoc toolset! -Ingo Schwarze, Karlsruhe, March 2019 +Ingo Schwarze, Karlsruhe, September 2021 Installation @@ -65,10 +65,15 @@ installed to the intended places. Otherwise, put some *DIR or *NM* variables into "configure.local" and go back to step 4. 7. Optionally run the regression suite. -Basically, that amounts to "cd regress && ./regress.pl". -But you should probably look at "./mandoc -l regress/regress.pl.1" -first. In particular, regarding Solaris systems, look at the BUGS -section of that manual page. +Basically, that amounts to "make regress" to do a standard regression +run, running all tests. For more fine-grained control, +read "./mandoc -l regress/regress.pl.1", +then run "cd regress && ./regress.pl" with optional arguments. +The regression suite requires a reasonably modern Perl interpreter. +Examples of systems that are too old to run the regression suite +include Solaris 9, Solaris 10, and Mac OS X 10.4 Tiger. +On Solaris 11, the suite does run, but some tests fail; +look at the BUGS section of that manual page. 8. Run "sudo make install". If you intend to build a binary package using some kind of fake root mechanism, you may need a diff --git a/LICENSE b/LICENSE index 81eada0b3657..0a0fc1acd2ac 100644 --- a/LICENSE +++ b/LICENSE @@ -1,11 +1,11 @@ -$Id: LICENSE,v 1.21 2018/11/26 17:11:11 schwarze Exp $ +$Id: LICENSE,v 1.22 2021/09/19 11:02:09 schwarze Exp $ With the exceptions noted below, all non-trivial files contained in the mandoc toolkit are protected by the Copyright of the following developers: Copyright (c) 2008-2012, 2014 Kristaps Dzonsons -Copyright (c) 2010-2018 Ingo Schwarze +Copyright (c) 2010-2021 Ingo Schwarze Copyright (c) 1999, 2004, 2017 Marc Espie Copyright (c) 2009, 2010, 2011, 2012 Joerg Sonnenberger Copyright (c) 2013 Franco Fichtner @@ -13,7 +13,7 @@ Copyright (c) 2014 Baptiste Daroussin Copyright (c) 2016 Ed Maste Copyright (c) 2017 Michael Stapelberg Copyright (c) 2017 Anthony Bentley -Copyright (c) 1998, 2004, 2010 Todd C. Miller +Copyright (c) 1998, 2004, 2010, 2015 Todd C. Miller Copyright (c) 2008, 2017 Otto Moerbeek Copyright (c) 2004 Ted Unangst Copyright (c) 1994 Christos Zoulas diff --git a/Makefile b/Makefile index f4e29540df00..48c4741812b6 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,7 @@ -# $Id: Makefile,v 1.530 2019/03/06 16:08:41 schwarze Exp $ +# $Id: Makefile,v 1.540 2021/09/21 11:04:40 schwarze Exp $ # +# Copyright (c) 2011, 2013-2021 Ingo Schwarze # Copyright (c) 2010, 2011, 2012 Kristaps Dzonsons -# Copyright (c) 2011, 2013-2019 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,11 +15,12 @@ # ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF # OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -VERSION = 1.14.5 +VERSION = 1.14.6 # === LIST OF FILES ==================================================== -TESTSRCS = test-be32toh.c \ +TESTSRCS = test-attribute.c \ + test-be32toh.c \ test-cmsg.c \ test-dirent-namlen.c \ test-EFTYPE.c \ @@ -29,6 +30,7 @@ TESTSRCS = test-be32toh.c \ test-getsubopt.c \ test-isblank.c \ test-mkdtemp.c \ + test-mkstemps.c \ test-nanosleep.c \ test-noop.c \ test-ntohl.c \ @@ -65,6 +67,7 @@ SRCS = arch.c \ compat_getsubopt.c \ compat_isblank.c \ compat_mkdtemp.c \ + compat_mkstemps.c \ compat_ohash.c \ compat_progname.c \ compat_reallocarray.c \ @@ -134,6 +137,7 @@ SRCS = arch.c \ term_ascii.c \ term_ps.c \ term_tab.c \ + term_tag.c \ tree.c DISTFILES = INSTALL \ @@ -209,6 +213,7 @@ DISTFILES = INSTALL \ tbl_int.h \ tbl_parse.h \ term.h \ + term_tag.h \ $(SRCS) \ $(TESTSRCS) @@ -245,19 +250,22 @@ LIBMANDOC_OBJS = $(LIBMAN_OBJS) \ mandoc_xr.o \ msec.o \ preconv.o \ - read.o + read.o \ + tag.o -COMPAT_OBJS = compat_err.o \ +ALL_COBJS = compat_err.o \ compat_fts.o \ compat_getline.o \ compat_getsubopt.o \ compat_isblank.o \ compat_mkdtemp.o \ + compat_mkstemps.o \ compat_ohash.o \ compat_progname.o \ compat_reallocarray.o \ compat_recallocarray.o \ compat_strcasestr.o \ + compat_stringlist.o \ compat_strlcat.o \ compat_strlcpy.o \ compat_strndup.o \ @@ -280,6 +288,7 @@ MANDOC_TERM_OBJS = eqn_term.o \ term_ascii.o \ term_ps.o \ term_tab.o \ + term_tag.o \ tbl_term.o DBM_OBJS = dbm.o \ @@ -302,7 +311,6 @@ MAIN_OBJS = $(MANDOC_HTML_OBJS) \ mdoc_man.o \ mdoc_markdown.o \ out.o \ - tag.o \ tree.o CGI_OBJS = $(MANDOC_HTML_OBJS) \ @@ -313,18 +321,10 @@ CGI_OBJS = $(MANDOC_HTML_OBJS) \ MANDOCD_OBJS = $(MANDOC_HTML_OBJS) \ $(MANDOC_TERM_OBJS) \ mandocd.o \ - out.o \ - tag.o + out.o DEMANDOC_OBJS = demandoc.o -SOELIM_OBJS = soelim.o \ - compat_err.o \ - compat_getline.o \ - compat_progname.o \ - compat_reallocarray.o \ - compat_stringlist.o - WWW_MANS = apropos.1.html \ demandoc.1.html \ man.1.html \ @@ -373,7 +373,7 @@ include Makefile.local # === DEPENDENCY HANDLING ============================================== -all: mandoc demandoc soelim $(BUILD_TARGETS) Makefile.local +all: mandoc man demandoc soelim $(BUILD_TARGETS) Makefile.local install: base-install $(INSTALL_TARGETS) @@ -392,13 +392,14 @@ distclean: clean rm -f Makefile.local config.h config.h.old config.log config.log.old clean: - rm -f libmandoc.a $(LIBMANDOC_OBJS) $(COMPAT_OBJS) - rm -f mandoc $(MAIN_OBJS) + rm -f libmandoc.a $(LIBMANDOC_OBJS) $(ALL_COBJS) + rm -f mandoc man $(MAIN_OBJS) rm -f man.cgi $(CGI_OBJS) rm -f mandocd catman catman.o $(MANDOCD_OBJS) rm -f demandoc $(DEMANDOC_OBJS) - rm -f soelim $(SOELIM_OBJS) + rm -f soelim soelim.o rm -f $(WWW_MANS) $(WWW_INCS) mandoc*.tar.gz mandoc*.sha256 + rm -f Makefile.tmp1 Makefile.tmp2 rm -rf *.dSYM base-install: mandoc demandoc soelim @@ -511,12 +512,15 @@ Makefile.local config.h: configure $(TESTSRCS) @echo "$@ is out of date; please run ./configure" @exit 1 -libmandoc.a: $(COMPAT_OBJS) $(LIBMANDOC_OBJS) - ar rs $@ $(COMPAT_OBJS) $(LIBMANDOC_OBJS) +libmandoc.a: $(MANDOC_COBJS) $(LIBMANDOC_OBJS) + $(AR) rs $@ $(MANDOC_COBJS) $(LIBMANDOC_OBJS) mandoc: $(MAIN_OBJS) libmandoc.a $(CC) -o $@ $(LDFLAGS) $(MAIN_OBJS) libmandoc.a $(LDADD) +man: mandoc + $(LN) mandoc man + man.cgi: $(CGI_OBJS) libmandoc.a $(CC) $(STATIC) -o $@ $(LDFLAGS) $(CGI_OBJS) libmandoc.a $(LDADD) @@ -529,8 +533,8 @@ catman: catman.o libmandoc.a demandoc: $(DEMANDOC_OBJS) libmandoc.a $(CC) -o $@ $(LDFLAGS) $(DEMANDOC_OBJS) libmandoc.a $(LDADD) -soelim: $(SOELIM_OBJS) - $(CC) -o $@ $(LDFLAGS) $(SOELIM_OBJS) +soelim: $(SOELIM_COBJS) soelim.o + $(CC) -o $@ $(LDFLAGS) $(SOELIM_COBJS) soelim.o # --- maintainer targets --- @@ -540,11 +544,13 @@ www-install: www $(INSTALL_DATA) $(WWW_INCS) $(HTDOCDIR)/includes depend: config.h - mkdep -f Makefile.depend $(CFLAGS) $(SRCS) + ./configure -depend + mkdep -f Makefile.tmp1 $(CFLAGS) $(SRCS) perl -e 'undef $$/; $$_ = <>; s|/usr/include/\S+||g; \ s|\\\n||g; s| +| |g; s| $$||mg; print;' \ - Makefile.depend > Makefile.tmp - mv Makefile.tmp Makefile.depend + Makefile.tmp1 > Makefile.tmp2 + rm Makefile.tmp1 + mv Makefile.tmp2 Makefile.depend regress-distclean: @find regress \ @@ -597,7 +603,7 @@ dist-install: dist .h.h.html: highlight -I $< > $@ -.1.1.html .3.3.html .5.5.html .7.7.html .8.8.html: mandoc - mandoc -Thtml -Wwarning,stop \ +.1.1.html .3.3.html .5.5.html .7.7.html .8.8.html: + ./mandoc -Thtml -Wwarning,stop \ -O 'style=/mandoc.css,man=/man/%N.%S.html;https://man.openbsd.org/%N.%S,includes=/includes/%I.html' \ $< > $@ diff --git a/Makefile.depend b/Makefile.depend index 3540aeda822c..d5f6556c3e7e 100644 --- a/Makefile.depend +++ b/Makefile.depend @@ -9,6 +9,7 @@ compat_getline.o: compat_getline.c config.h compat_getsubopt.o: compat_getsubopt.c config.h compat_isblank.o: compat_isblank.c config.h compat_mkdtemp.o: compat_mkdtemp.c config.h +compat_mkstemps.o: compat_mkstemps.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 @@ -22,8 +23,8 @@ 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_array.o: dba_array.c config.h mandoc_aux.h dba_write.h dba_array.h +dba_read.o: dba_read.c config.h 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 @@ -33,17 +34,17 @@ eqn_html.o: eqn_html.c config.h mandoc.h roff.h eqn.h out.h html.h eqn_term.o: eqn_term.c config.h eqn.h out.h term.h html.o: html.c config.h mandoc_aux.h mandoc_ohash.h compat_ohash.h mandoc.h roff.h out.h html.h manconf.h main.h lib.o: lib.c config.h roff.h libmdoc.h lib.in -main.o: main.c config.h mandoc_aux.h mandoc.h mandoc_xr.h roff.h mdoc.h man.h mandoc_parse.h tag.h main.h manconf.h mansearch.h +main.o: main.c config.h mandoc_aux.h mandoc.h mandoc_xr.h roff.h mdoc.h man.h mandoc_parse.h tag.h term_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_html.o: man_html.c config.h mandoc_aux.h mandoc.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 tag.h main.h -man_validate.o: man_validate.c config.h mandoc_aux.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 term_tag.h main.h +man_validate.o: man_validate.c config.h mandoc_aux.h mandoc.h roff.h man.h libmandoc.h roff_int.h libman.h tag.h mandoc.o: mandoc.c config.h mandoc_aux.h mandoc.h roff.h libmandoc.h roff_int.h mandoc_aux.o: mandoc_aux.c config.h mandoc.h mandoc_aux.h mandoc_msg.o: mandoc_msg.c config.h mandoc.h -mandoc_ohash.o: mandoc_ohash.c mandoc_aux.h mandoc_ohash.h compat_ohash.h -mandoc_xr.o: mandoc_xr.c mandoc_aux.h mandoc_ohash.h compat_ohash.h mandoc_xr.h +mandoc_ohash.o: mandoc_ohash.c config.h mandoc_aux.h mandoc_ohash.h compat_ohash.h +mandoc_xr.o: mandoc_xr.c config.h mandoc_aux.h mandoc_ohash.h compat_ohash.h mandoc_xr.h mandocd.o: mandocd.c config.h mandoc.h roff.h mdoc.h man.h mandoc_parse.h main.h manconf.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 mandoc_parse.h manconf.h mansearch.h dba_array.h dba.h manpath.o: manpath.c config.h mandoc_aux.h mandoc.h manconf.h @@ -53,21 +54,21 @@ mdoc_argv.o: mdoc_argv.c config.h mandoc_aux.h mandoc.h roff.h mdoc.h libmandoc. mdoc_html.o: mdoc_html.c config.h mandoc_aux.h mandoc.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 -mdoc_markdown.o: mdoc_markdown.c mandoc_aux.h mandoc.h roff.h mdoc.h main.h -mdoc_state.o: mdoc_state.c mandoc.h roff.h mdoc.h libmandoc.h roff_int.h libmdoc.h -mdoc_term.o: mdoc_term.c config.h mandoc_aux.h roff.h mdoc.h out.h term.h tag.h main.h -mdoc_validate.o: mdoc_validate.c config.h mandoc_aux.h mandoc.h mandoc_xr.h roff.h mdoc.h libmandoc.h roff_int.h libmdoc.h +mdoc_markdown.o: mdoc_markdown.c config.h mandoc_aux.h mandoc.h roff.h mdoc.h main.h +mdoc_state.o: mdoc_state.c config.h mandoc.h roff.h mdoc.h libmandoc.h roff_int.h libmdoc.h +mdoc_term.o: mdoc_term.c config.h mandoc_aux.h roff.h mdoc.h out.h term.h term_tag.h main.h +mdoc_validate.o: mdoc_validate.c config.h mandoc_aux.h mandoc.h mandoc_xr.h roff.h mdoc.h libmandoc.h roff_int.h libmdoc.h tag.h msec.o: msec.c config.h mandoc.h libmandoc.h msec.in -out.o: out.c config.h mandoc_aux.h tbl.h out.h +out.o: out.c config.h mandoc_aux.h mandoc.h tbl.h out.h preconv.o: preconv.c config.h mandoc.h roff.h mandoc_parse.h libmandoc.h -read.o: read.c config.h mandoc_aux.h mandoc.h roff.h mdoc.h man.h mandoc_parse.h libmandoc.h roff_int.h +read.o: read.c config.h mandoc_aux.h mandoc.h roff.h mdoc.h man.h mandoc_parse.h libmandoc.h roff_int.h tag.h roff.o: roff.c config.h mandoc_aux.h mandoc_ohash.h compat_ohash.h mandoc.h roff.h mandoc_parse.h libmandoc.h roff_int.h tbl_parse.h eqn_parse.h predefs.in -roff_html.o: roff_html.c mandoc.h roff.h out.h html.h -roff_term.o: roff_term.c mandoc.h roff.h out.h term.h -roff_validate.o: roff_validate.c mandoc.h roff.h libmandoc.h roff_int.h +roff_html.o: roff_html.c config.h mandoc.h roff.h out.h html.h +roff_term.o: roff_term.c config.h mandoc.h roff.h out.h term.h +roff_validate.o: roff_validate.c config.h mandoc.h roff.h libmandoc.h roff_int.h soelim.o: soelim.c config.h compat_stringlist.h st.o: st.c config.h mandoc.h roff.h libmdoc.h -tag.o: tag.c config.h mandoc_aux.h mandoc_ohash.h compat_ohash.h mandoc.h tag.h +tag.o: tag.c config.h mandoc_aux.h mandoc_ohash.h compat_ohash.h roff.h mdoc.h roff_int.h tag.h tbl.o: tbl.c config.h mandoc_aux.h mandoc.h tbl.h libmandoc.h tbl_parse.h tbl_int.h tbl_data.o: tbl_data.c config.h mandoc_aux.h mandoc.h tbl.h libmandoc.h tbl_int.h tbl_html.o: tbl_html.c config.h mandoc.h roff.h tbl.h out.h html.h @@ -77,5 +78,6 @@ tbl_term.o: tbl_term.c config.h mandoc.h tbl.h out.h term.h term.o: term.c config.h mandoc.h mandoc_aux.h out.h term.h main.h term_ascii.o: term_ascii.c config.h mandoc.h mandoc_aux.h out.h term.h manconf.h main.h term_ps.o: term_ps.c config.h mandoc_aux.h out.h term.h manconf.h main.h -term_tab.o: term_tab.c mandoc_aux.h out.h term.h +term_tab.o: term_tab.c config.h mandoc_aux.h out.h term.h +term_tag.o: term_tag.c config.h mandoc.h roff.h roff_int.h tag.h term_tag.h tree.o: tree.c config.h mandoc.h roff.h mdoc.h man.h tbl.h eqn.h main.h diff --git a/NEWS b/NEWS index 89eb3f6fb629..634ffaf6ccfa 100644 --- a/NEWS +++ b/NEWS @@ -1,7 +1,206 @@ -$Id: NEWS,v 1.34 2019/03/10 09:32:00 schwarze Exp $ +$Id: NEWS,v 1.40 2021/09/23 18:03:00 schwarze Exp $ This file lists the most important changes in the mandoc.bsd.lv distribution. +Changes in version 1.14.6, released on September 23, 2021 + + --- MAJOR NEW FEATURES --- + * mdoc(7): automatic tagging improved in many respects + * mdoc(7): new .Tg (tag) macro to explicitly mark a place as defining a term + * man(7): implement some automatic tagging support + * man(1): let -w without argument show the manpath, like in man-db and man-1.6 + * -T html: wrap text and phrasing elements in paragraphs unless already + contained in flow containers; never put them directly into sections. + This helps to format paragraphs with the CSS class selector .Pp. + * man.conf(5): remove support for the "_whatdb" configuration directive + that was deprecated in 2015; please use "manpath" instead + --- MINOR NEW FEATURES --- + * man(1): switch the default pager from "more -s" to "less" + * man(1): in the fallback code to look for manual pages without using + mandoc.db(5), accept files "man/." + in addition to the already supported "man/name.[01-9]*" + * if messages are shown and output is printed without a pager, display + a heads-up on stderr at the end because otherwise, users may easily + miss the messages + * man.cgi(8): add a Content-Security-Policy HTTP header + * man.cgi(8): switch off autocomplete and autocapitalize + * mandoc.css: support prefers-color-scheme: dark + * -T html: add meta viewport element to help mobile devices + * -T html -O tag: let this pass a file:// URI to the pager + * tbl(7): implement the "nospaces" option + * tbl(7) -T html: implement the "a" (em indent) layout specification + * tbl(7) -T html: implement the "b" (bold) and "i" (italic) layout modifiers + * tbl(7): support two-character font names in the layout font modifier + * tbl(7) -T html: support horinzontal rulers in individual cells + * tbl(7) -T tree: print more details about columns, options, rows, and cells + * roff(7): implement the .break request (break out of a .while loop) + * roff(7): support the CB and CI fonts in \f and .ft + * -T lint: new STYLE message if a file name extension contradicts .Dt/.TH + * -T lint: new STYLE message about overlong text lines + * -W style: check .Xr links along the full manpath + --- RELIABILITY BUGFIXES --- + * man(1): do not segfault if /tmp/ is not writeable + * man(1): do not access a NULL pointer when both -l and -w are given + * makewhatis(8): do not crash when a manpath directory contains + a symbolic link that points to a directory + * man(7): fix an assertion failure caused by doubly nested next-line scopes + * tbl(7): fix a crash when the last column is only reached by spans + * tbl(7): fix a NULL pointer access in some cases of two spans on one row + * tbl(7) -T ascii: fix a NULL pointer access on empty data cells + * tbl(7) -T ascii: fix a NULL pointer access on a line next to a short row + * tbl(7): fix an assertion failure caused by excessive spacing modifiers + * tbl(7): fix an infinite loop for some overlapping horizontal spans + * roff(7): fix a rare case of writing one byte past the end of the input buffer + * roff(7): do not call abort(3) when \*[.T] is encountered + * roff(7): fix an assertion failure caused by a macro inside .ce .if + * roff(7): fix assertion failures for .ti and .po with excessive arguments + * roff(7): avoid near-infinte output for .ce inside explicit no-fill mode + * -T ascii/utf8: fix assertion failures caused by excessive spacing + * -T html: fix an assertion failure caused by .ft in rare situations + * -T man: fix an assertion failure caused by tbl(7) and eqn(7) input + --- PORTABILITY IMPROVEMENTS --- + * rename HOMEBREWDIR to READ_ALLOWED_PATH, allow it to contain more than + one directory, and explain how to use that for NixOS and GNU Guix Linux + * configure: stop trying to ask make(1) what the default compiler is + because that test was too fragile; just use "cc" by default + * configure: various simplifications and improved robustness + * configure: only compile compat_*.c implementations that are needed + * configure: provide feature tests for __attribute__(()) and mkstemps(3) + * compat_*: sync with upstreams for security, functionality, and style + * in regress.pl, avoid the non-portable options sed(1) -i and echo(1) -n + * in the regression suite, avoid file names that differ only by case + --- MINOR FUNCTIONAL IMPROVEMENTS --- + * man(1) -h: for pages lacking a SYNOPSIS, show the NAME section + * man(1): when the first argument starts with a digit, optionally + followed by a letter, and at least one more argument follows, + interpret the first argument as a section name even when additional + characters follow after the digit and letter + * man(1): with a specific section requested, try harder to find + the best match; use this order of preference: + 1. The section in both the directory name and the file name matches exactly. + 2. The section in the file name matches exactly. + 3. The section in the directory name matches exactly. + 4. Neither of them matches exactly. + * man(1): if no tags were generated at all, unlink(2) the empty tags file + as soon as the condition can be detected and do not pass it to less(1) + * makewhatis(8): handle both dangling symlinks and .so links + in manual page directories more gracefully + * man.cgi(8): for invalid queries and for valid queries returning + no result, return the appropriate 40x status code rather than 200 + * mdoc(7): let .Dd concatenate all arguments and default to the empty string + * mdoc(7): convert ".Fl Fl" to ".Fl \-" during validation, improving -T html + * mdoc(7): improve output of .At 32v + * man(7): no longer print multiple blank lines before NAME and page footer + * tbl(7) -T utf8: improved rendering of horizontal lines + * tbl(7) -T html: in "n" cells, align by padding numbers on the right + * tbl(7): no longer leak tabulator settings to subsequent roff(7) code + * mdoc(7) -T html: for .Bl -tag, use "column-count: 1" rather + than "overflow: auto" to avoid the ugly side effects + * mdoc(7) -T html: render .Bd -unfilled in proportionally-spaced font + * mdoc(7) -T html: format .Nd with rather than
+ * mdoc(7) -T lint: do not warn about Mdocdate without an actual date + * mdoc(7) -T lint: do not complain about function types of the + form "ret_type (fname)(args)", but otherwise check names more strictly + * -T html: append .html suffix to temporary files to please browsers + * -T markdown: print a BAGARG message if called on man(7) input + --- MINOR BUGFIXES --- + * man(1): do the search for each name independently, and + show the results in the order of the command line argument + * man(1): escape shell wildcard characters in name arguments before glob(3) + * man(1): when asking for a single manual page by name, prefer file name + matches over .Dt/.TH matches over first NAME matches over later NAME + matches, but do not change the ordering for apropos(1) nor for man -a + * man(1): correctly extract the section name from the file name extension + of gzipped manual page files + * makewhatis(8): fix file type tests putting wrong data into mandoc.db(5) + * man.cgi(8): fix section number in the element for preformatted pages + * tbl(7): correct handling of T& after horizontal rulers in the layout + * tbl(7): correct column widths if rows have different numbers of cells + * tbl(7): empty columns are 1n wide rather than 0n + * tbl(7): correctly calculate required column widths for tables containing + cells that horizontally span columns which contains "n" (number) formatted + cells on other rows + * tbl(7): skip escape sequences when looking for column separators + * eqn(7): skip whitespace before tokens + * roff(7): when calling an empty macro, do not clobber existing arguments + * roff(7): recognize \} on lines closing a macro definition request + * roff(7): do not throw a bogus warning for "'br\}" and similar lines + * roff(7): stop generating comment nodes when encountering the first content + * mandoc_char(7): make \0 (digit-width space) non-breaking + * mdoc(7) .Bl -column: parse Macro in .It "word<tab>word" Ta word Macro<eol> + * mdoc(7) -T html: display straight quotes, not curly quotes, for .Qq/.Qo + * -T html: remove some spurious line breaks, in particular inside <pre> + * -T html: use <br/> for a space character at the beginning of an input line + * -T html: use ~%d for ordinal fragment suffixes, reserve '~' for that purpose + --- STRUCTURAL IMPROVEMENTS --- + * introduce the concept of semantically transparent syntax tree nodes, + allowing improved decisions in various validators and formatters + * move some code out of the giant main() into separate functions + doing one well-defined task each + * clearly separate parser state (struct curparse) and formatter state + (struct outstate), don't mix them in the same struct + * in the HTML formatter, assert(3) that no HTML nesting violation occurs + * let html_close_paragraph() close any phrasing context + --- THANKS TO --- + * Anthony Bentley and Klemens Nanni (OpenBSD) for many patches and bug + reports, for useful discussions, and for checking patches + * Anton Lindqvist (OpenBSD) for two patches and a bug report + * Marc Espie (OpenBSD) for a patch, many bug reports, and useful discussions + * Lukas Epple (NixOS) for a patch, bug reports, suggesting a minor + portability feature, checking patches, and extensive release testing + * Abel Romero Perez for a patch, a bug report, and suggesting a new feature + * nabijaczleweli for a patch and for suggesting feature improvements + * Jonathan Gray (OpenBSD) for a patch and for bug reports + * Otto Moerbeek (OpenBSD) and Alexander Gromnitsky for a patch + * Armin Besirovic for a contribution to mandoc.css + * Jason McIntyre (OpenBSD) for manual page patches, suggesting a new feature, + checking many patches, and useful discussions + * Martin Vahlensieck for a manual page patch and reporting a code style issue + * Frederic Cambus and Ian Sutton (OpenBSD) for a manual page patch + * Jan Schreiber for many bug reports found with afl(1) + * G. Branden Robinson (GNU troff) for several bug reports, feature + suggestions, and for checking many groff patches + * Michael Stapelberg (Debian) for several bug reports and feature + suggestions, and for extensive release testing + * Ian Ropers, Lorenzo Beretta, and Oliver Corff for several bug reports + and feature suggestions + * Stephen Gregoratto for several bug reports + * Theo de Raadt (OpenBSD) for two bug reports, checking a patch, + and a useful discussion + * Thomas Klausner (NetBSD) for two bug reports and for release testing + * Andreas Kahari and Jason A. Donenfeld for two bug reports + * Soeren Tempel (Alpine Linux) for a bug report, suggesting a feature + improvement, and checking two patches + * Aman Verma, Jan Stary, and John Gardner for a bug report + and for suggesting a feature impovement + * Todd Miller (OpenBSD) for a bug report, checking a patch, + and a useful discussion + * Andrew Fresh, Brian Callahan, Christian Weisgerber, Paul de Weerd (OpenBSD), + Havard Eidnes, Jason Thorpe (NetBSD), Yuri Pankov (FreeBSD), + Bjarni Ingi Gislason, Chris Bennett, Edgar Pettijohn, Eldred Habert, + Jamie Landeg-Jones, Kazuo KUROI, and Wynn Wolf Arbor for a bug report + * Theo Buehler (OpenBSD) for suggesting two feature impovements + and for checking a patch + * Leah Neukirchen (Void Linux) for suggesting a feature impovement + and for release testing + * Colin Watson (Debian) for suggesting a feature impovement + and for checking groff patches + * Matej Cepl (SUSE Linux), Matthew Martin, Steffen Nurpmeso, + and Tim Baumgard for suggesting a feature impovement + * Christos Zoulas (NetBSD) for a report regarding portability + * Daniel Dickman (OpenBSD) for suggesting a portability improvement + * Werner Lemberg (GNU troff) and Douglas McIlroy + for reporting bugs in manual pages + * Baptiste Daroussin and Eygene Ryabinkin (FreeBSD) + for an additional regression test + * Michal Nowak for reporting several code style issues + * TJ Townsend (OpenBSD) for help with CSS + * Sevan Janiyan (NetBSD) and Robert Mustacchi (Illumos) + for extensive release testing + * Job Snijders, Kinichiro INOGUCHI, and Martijn van Duren (OpenBSD) + for checking patches + * Bertrand Garrigues and Ralph Corderoy (GNU troff) for checking groff patches + Changes in version 1.14.5, released on March 10, 2019 --- MAJOR NEW FEATURES --- diff --git a/TODO b/TODO index 049e6a4f2beb..fe2059c9e5d5 100644 --- a/TODO +++ b/TODO @@ -1,6 +1,6 @@ ************************************************************************ * Official mandoc TODO. -* $Id: TODO,v 1.295 2019/06/11 16:04:36 schwarze Exp $ +* $Id: TODO,v 1.319 2021/09/21 17:58:13 schwarze Exp $ ************************************************************************ Many issues are annotated for difficulty as follows: @@ -32,6 +32,43 @@ 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. +************************************************************************ +* assertion failures +************************************************************************ + +- .if n .ce in the middle of .TS data + afl case f1/id:000103,sig:06,src:009024+009105,op:splice,rep:2 (jes@) + While roff_parseln() prevents .ce and similar requests in the middle + of a tbl, the guard is no longer effective when the .ce is wrapped + in a roff block, for example a conditional. The resulting assertion + has never been seen in any real-world manual page. + This is too dangerous to fix before release because it requires + reorganizing the very delicate internals of roff_parseln(), + which risks causing more severe bugs. + loc * exist *** algo *** size * imp * + + +************************************************************************ +* bugs: invalid output +************************************************************************ + +- wrong number of layout columns in tbl(7) code generated by -T man + https://savannah.gnu.org/bugs/?57720 + The reason likely is that tbl(7) does not support the -Bl -column + feature of not explicitly specifying the last table column. + loc ** exist * algo ** size * imp *** + +- eqn(7) delimiters cause conditional lines to misbehave + nabijaczleweli 8 Sep 2021 15:24:48 +0200 + loc * exist *** algo *** size * imp * + +- roff.c, roff_expand() should not remove blanks before comments + to Oliver Corff, Sep 7, 2021 + loc * exist * algo * size * imp * + but watch out for regressions in the high-level parsers + maybe it should not even remove comments? - consider T{\" + + ************************************************************************ * missing features ************************************************************************ @@ -83,8 +120,39 @@ are mere guesses, and some may be wrong. Jan Stary 20 Apr 2019 20:16:54 +0200 loc * exist *** algo *** size ** imp * +- mandoc replaces all ASCII control characters except tab and line feed + with '?' during input. It would be better to replace them with + Unicode escapes in preconv_encode() or somewhere in the vicinity, + such that the already existing better replacement strings show + up in the output. Emulating groff is not desirable: groff replaces + 0x00, 0x0b, and 0x0d to 0x1f with the empty string (bad because + that's easy to overlook for the document author), 0x01 with '.' + (very confusing), and passes through 0x02 to 0x08, 0x0c, and 0x7f + raw (bad because that is insecure output). Remember that 0x07 may + need special handling because it is sometimes used for certain + delimiters, so it may need handling *after* roff.c rather than before. + reminded by John Gardner 16 Jun 2020 14:26:28 +1000 + Actually, more ASCII control characters than just 0x07 may need + later handling because they can for example be used in macro names. + So they may need handling after roff(7) processing. + pointed out by John Gardner 23 Jun 2020 18:28:08 +1000 + more info from John Gardner 29 Jun 2020 19:54:04 +1000 + loc ** exist ** algo ** size ** imp * + +- many missing features used in old groff_char(7), + some can possibly be supported + kamil at netbsd 12 Nov 2020 17:27:09 +0100 + reply + +- \s with arbitrary arg delimiters as already supported for other escapes + found following jmc@'s mail 28 Apr 2021 18:31:41 +0100 + loc * exist * algo * size * imp * + --- missing mdoc features ---------------------------------------------- +- .Sh and .Ss should be parsed and partially callable, see groff_mdoc(7) + reed at reedmedia dot net Sat, 21 Dec 2019 17:13:07 -0600 + loc ** exist ** algo ** size ** imp * + - .Bl -column .Xo support is missing ultimate goal: restore .Xr and .Dv to @@ -153,6 +221,13 @@ are mere guesses, and some may be wrong. --- missing man features ----------------------------------------------- +- MANWIDTH + Markus Waldeck <waldeck at gmx dot de> 9 Jun 2015 05:49:56 +0200 + Laura Morales <lauretas at mail dot com> 26 Apr 2018 08:15:55 +0200 + Kamil Rytarowski <kamil at netbsd> 13 Nov 2020 00:19:36 +0100 + patch from Kamil 13 Nov 2020 22:37:07 +0100 + loc * exist * algo * size * imp * + - groff_www(7) .MTO and .URL These macros were used by the GNU grep(1) man page. The groff_www(7) manual page itself uses them, too. @@ -217,6 +292,21 @@ are mere guesses, and some may be wrong. --- missing misc features ---------------------------------------------- +- conisder whether man(1) fallback code in main.c/fs_*() can find files + like man3c/fopen.3c (illumos, Solaris) and man3p/fopen.3p (POSIX) + discussed with Robert Mustacchi 21 Sep 2021 10:39:40 -0700 + loc * exist * algo ** size * imp ** + +- let makewhatis(8) follow symbolic links to dirs below READ_ALLOWED_PATH + this may be feasible using fts_set(FTS_FOLLOW) + mail to sternenseemann 19 Aug 2021 19:11:50 +0200 + loc * exist ** algo ** size * imp ** + +- -T man does not handle eqn(7) and tbl(7) + Stephen Gregoratto 16 Feb 2020 01:28:07 +1100 + also https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=901636 + loc ** exist ** algo ** size *** imp ** + - man -ks 1,8 route; kn@ Jul 13, 2018 orally - italic correction (\/) in PostScript mode @@ -291,6 +381,10 @@ are mere guesses, and some may be wrong. - check features of the Slackware man.conf(5) format Carsten Kunze Wed, 11 Mar 2015 17:57:24 +0100 +- look at http://www.snake.net/software/troffcvt/ (troff to HTML) + mentioned by Oliver Corff 22 Jan 2021 01:36:49 +0100 + + ************************************************************************ * formatting issues: ugly output ************************************************************************ @@ -370,10 +464,29 @@ are mere guesses, and some may be wrong. add a new <</Type /Font>> block to the PDF files with /BaseFont /Courier and change the /Name from /F0 to the new font (/F5 (?)). re-reported by tb@ Mon, 16 Mar 2015 16:47:21 +0100 - loc * exist ** algo ** size * imp ** + loc ** exist ** algo ** size * imp ** --- HTML issues -------------------------------------------------------- +- make the HTML scaffolding customozable with -O skip=... + mail to Oliver Corff 3 Jun 2021 17:28:02 +0200 + more feedback from Oliver 3 Jun 2021 18:27:56 +0200 + more feedback from Oliver 3 Jun 2021 23:37:18 +0200 + +- .Bd -unfilled should not use monospaced font + anton@ 4 Mar 2021 08:19:35 +0100 + loc ** exist * algo * size * imp ** + +- HTML formatting of .nf should avoid <br/> + and not close and re-open <pre> on .P + my mail to ports@ 27 Jun 2021 16:09:20 +0200 + loc ** exist ** algo * size * imp ** + +- get rid of the last handful of style= attributes such that + Content-Security-Policy: can be enabled without unsafe-inline + suggested by bentley@ Nov 10, 2019 at 06:02:49AM -0700 + loc * exist * algo * size * imp ** + - .Bf at the beginning of a paragraph inserts a bogus 1ex horizontal space, see for example random(3). Introduced in http://mdocml.bsd.lv/cgi-bin/cvsweb/mdoc_html.c.diff?r1=1.91&r2=1.92 @@ -386,6 +499,11 @@ are mere guesses, and some may be wrong. https://github.com/Debian/debiman/issues/15 loc * exist * algo ** size ** imp ** +- space characters can end up in href= attributes, for example coming + from the first .Xr argument (where they make no sense, but still); + does this affect other characters, other source macros...? + Jackson Pauls 29 Aug 2017 16:56:27 +0100 + - The tables used to render the three-part page headers actually force the width of the <body> to the max-width given for <html>. Not yet sure how to fix that... @@ -470,6 +588,10 @@ are mere guesses, and some may be wrong. * warning issues ************************************************************************ +- shorten/simplify error messages for usage errors + To: deraadt@ 25 Oct 2020 23:37:01 +0100 + loc ** exist * algo * size ** imp *** + - warn about duplicate .Sh/.Ss heads gre(4): Rename duplicate sections 20 Apr 2018 15:27:33 +0200 loc * exist * algo * size * imp ** @@ -505,6 +627,10 @@ are mere guesses, and some may be wrong. output without intervening whitespace, in particular after a macro line (from the mdoclint TODO) +- report double .TH in man(7) as an ERROR and let the first win + kristaps@ 28 Mar 2021 13:30:41 +0200 + loc * exist * algo * size * imp * + - makewhatis -p complains about language subdirectories: /usr/local/man//ru: Unknown directory part @@ -553,6 +679,9 @@ are mere guesses, and some may be wrong. * CGI issues ************************************************************************ + - Inspect httpd(8) logs on man.openbsd.org and consider + whether logging can be improved, where bad syntax comes from, + and what needs to be done to get rid of COMPAT_OLDURI. - Enable HTTP compression by detecting gzip encoding and filtering output through libz. - Privilege separation (see OpenSSH). @@ -562,6 +691,15 @@ are mere guesses, and some may be wrong. * to improve in the groff_mdoc(7) macros ************************************************************************ +- delete OS release verification from .Dx, .Fx, .Nx, .Ox etc. + https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=629161 + also Branden Robinson 18 Dec 2019 00:59:52 +1100 + +- Can the distinction between .Vt and .Va be made stricter, + recommending .Vt extern char * Ns Va optarg ; ? + What about the block macro properties of .Vt in the SYNOPSIS? + zeurkous 25 Dec 2019 08:48:36 +0100 + - .Cd # arch1, arch2 in section 4 pages: find better way to indicate multiple architectures, maybe: allow .Dt vgafb 4 "macppc sparc64" diff --git a/apropos.1 b/apropos.1 index 01d2a6bfa4f4..9129ae31f126 100644 --- a/apropos.1 +++ b/apropos.1 @@ -1,4 +1,4 @@ -.\" $Id: apropos.1,v 1.49 2018/11/22 12:33:52 schwarze Exp $ +.\" $Id: apropos.1,v 1.51 2020/10/01 22:50:00 schwarze Exp $ .\" .\" Copyright (c) 2011, 2012 Kristaps Dzonsons <kristaps@bsd.lv> .\" Copyright (c) 2011,2012,2014,2017,2018 Ingo Schwarze <schwarze@openbsd.org> @@ -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 22 2018 $ +.Dd $Mdocdate: October 1 2020 $ .Dt APROPOS 1 .Os .Sh NAME @@ -73,7 +73,7 @@ would. If the standard output is a terminal device and .Fl c is not specified, use -.Xr more 1 +.Xr less 1 to paginate them. In .Fl a @@ -340,7 +340,7 @@ types appearing in function arguments in the SYNOPSIS Any non-empty value of the environment variable .Ev MANPAGER is used instead of the standard pagination program, -.Xr more 1 ; +.Xr less 1 ; see .Xr man 1 for details. @@ -363,8 +363,7 @@ Specifies the pagination program to use when .Ev MANPAGER is not defined. If neither PAGER nor MANPAGER is defined, -.Xr more 1 -.Fl s +.Xr less 1 is used. Only used if .Fl a @@ -404,6 +403,10 @@ Search in names and descriptions using a case-sensitive regular expression: .Pp .Dl $ apropos \(aq\(tiset.?[ug]id\(aq .Pp +Search for all manual pages in a given section: +.Pp +.Dl $ apropos \-s 9 \&. +.Pp Search for manuals in the library section mentioning both the .Qq optind and the diff --git a/arch.c b/arch.c index 551c86796a21..41a23cbbc32b 100644 --- a/arch.c +++ b/arch.c @@ -1,4 +1,4 @@ -/* $Id: arch.c,v 1.15 2019/05/21 07:52:00 schwarze Exp $ */ +/* $Id: arch.c,v 1.17 2021/05/13 13:33:11 schwarze Exp $ */ /* * Copyright (c) 2017, 2019 Ingo Schwarze <schwarze@openbsd.org> * @@ -26,7 +26,7 @@ arch_valid(const char *arch, enum mandoc_os os) const char *openbsd_arch[] = { "alpha", "amd64", "arm64", "armv7", "hppa", "i386", "landisk", "loongson", "luna88k", "macppc", "mips64", - "octeon", "sgi", "sparc64", NULL + "octeon", "powerpc64", "riscv64", "sparc64", NULL }; const char *netbsd_arch[] = { "acorn26", "acorn32", "algor", "alpha", "amiga", diff --git a/att.c b/att.c index 5575bed54a4f..abde95d7397b 100644 --- a/att.c +++ b/att.c @@ -1,4 +1,4 @@ -/* $Id: att.c,v 1.18 2018/12/13 11:55:46 schwarze Exp $ */ +/* $Id: att.c,v 1.19 2021/09/04 20:26:43 schwarze Exp $ */ /* * Copyright (c) 2009 Kristaps Dzonsons <kristaps@bsd.lv> * @@ -37,7 +37,7 @@ mdoc_a2att(const char *p) LINE("v5", "Version\\~5 AT&T UNIX"); LINE("v6", "Version\\~6 AT&T UNIX"); LINE("v7", "Version\\~7 AT&T UNIX"); - LINE("32v", "Version\\~32V AT&T UNIX"); + LINE("32v", "Version\\~7 AT&T UNIX/32V"); LINE("III", "AT&T System\\~III UNIX"); LINE("V", "AT&T System\\~V UNIX"); LINE("V.1", "AT&T System\\~V Release\\~1 UNIX"); diff --git a/catman.c b/catman.c index 7d62c0e74add..b1bab0f68c4b 100644 --- a/catman.c +++ b/catman.c @@ -1,4 +1,4 @@ -/* $Id: catman.c,v 1.21 2017/02/18 12:24:24 schwarze Exp $ */ +/* $Id: catman.c,v 1.22 2020/06/14 23:40:31 schwarze Exp $ */ /* * Copyright (c) 2017 Michael Stapelberg <stapelberg@debian.org> * Copyright (c) 2017 Ingo Schwarze <schwarze@openbsd.org> @@ -17,7 +17,7 @@ */ #include "config.h" -#if HAVE_CMSG_XPG42 +#if NEED_XPG4_2 #define _XPG4_2 #endif diff --git a/cgi.c b/cgi.c index 4762f819d57d..91310ce404b4 100644 --- a/cgi.c +++ b/cgi.c @@ -1,7 +1,7 @@ -/* $Id: cgi.c,v 1.167 2019/07/10 12:49:20 schwarze Exp $ */ +/* $Id: cgi.c,v 1.175 2021/08/19 15:23:36 schwarze Exp $ */ /* + * Copyright (c) 2014-2019, 2021 Ingo Schwarze <schwarze@usta.de> * Copyright (c) 2011, 2012 Kristaps Dzonsons <kristaps@bsd.lv> - * Copyright (c) 2014, 2015, 2016, 2017, 2018 Ingo Schwarze <schwarze@usta.de> * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -14,6 +14,8 @@ * 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. + * + * Implementation of the man.cgi(8) program. */ #include "config.h" @@ -70,14 +72,15 @@ enum focus { static void html_print(const char *); static void html_putchar(char); static int http_decode(char *); -static void http_encode(const char *p); +static void http_encode(const char *); static void parse_manpath_conf(struct req *); -static void parse_path_info(struct req *req, const char *path); +static void parse_path_info(struct req *, const char *); static void parse_query_string(struct req *, const char *); static void pg_error_badrequest(const char *); static void pg_error_internal(void); static void pg_index(const struct req *); -static void pg_noresult(const struct req *, const char *); +static void pg_noresult(const struct req *, int, const char *, + const char *); static void pg_redirect(const struct req *, const char *); static void pg_search(const struct req *); static void pg_searchres(const struct req *, @@ -119,16 +122,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", "armv7", "arm64", - "hppa", "i386", "landisk", - "loongson", "luna88k", "macppc", "mips64", - "octeon", "sgi", "socppc", "sparc64", + "amd64", "alpha", "armv7", "arm64", + "hppa", "i386", "landisk", "loongson", + "luna88k", "macppc", "mips64", "octeon", + "powerpc64", "riscv64", "sparc64", + "amiga", "arc", "armish", "arm32", "atari", "aviion", "beagle", "cats", "hppa64", "hp300", "ia64", "mac68k", "mvme68k", "mvme88k", "mvmeppc", "palm", "pc532", "pegasos", - "pmax", "powerpc", "solbourne", "sparc", + "pmax", "powerpc", "sgi", "socppc", + "solbourne", "sparc", "sun3", "vax", "wgrisc", "x68k", "zaurus" }; @@ -339,6 +344,8 @@ resp_begin_http(int code, const char *msg) printf("Content-Type: text/html; charset=utf-8\r\n" "Cache-Control: no-cache\r\n" + "Content-Security-Policy: default-src 'none'; " + "style-src 'self' 'unsafe-inline'\r\n" "Pragma: no-cache\r\n" "\r\n"); @@ -363,7 +370,8 @@ resp_copy(const char *filename) static void resp_begin_html(int code, const char *msg, const char *file) { - char *cp; + const char *name, *sec, *cp; + int namesz, secsz; resp_begin_http(code, msg); @@ -378,12 +386,27 @@ resp_begin_html(int code, const char *msg, const char *file) " <title>", CSS_DIR); if (file != NULL) { - if ((cp = strrchr(file, '/')) != NULL) - file = cp + 1; - if ((cp = strrchr(file, '.')) != NULL) { - printf("%.*s(%s) - ", (int)(cp - file), file, cp + 1); - } else - printf("%s - ", file); + cp = strrchr(file, '/'); + name = cp == NULL ? file : cp + 1; + cp = strrchr(name, '.'); + namesz = cp == NULL ? strlen(name) : cp - name; + sec = NULL; + if (cp != NULL && cp[1] != '0') { + sec = cp + 1; + secsz = strlen(sec); + } else if (name - file > 1) { + for (cp = name - 2; cp >= file; cp--) { + if (*cp < '1' || *cp > '9') + continue; + sec = cp; + secsz = name - cp - 1; + break; + } + } + printf("%.*s", namesz, name); + if (sec != NULL) + printf("(%.*s)", secsz, sec); + fputs(" - ", stdout); } printf("%s\n" "\n" @@ -408,7 +431,8 @@ resp_searchform(const struct req *req, enum focus focus) { int i; - printf("
\n" + printf("\n" "
\n" " Manual Page Search Parameters\n", scriptname); @@ -546,12 +570,13 @@ pg_index(const struct req *req) } static void -pg_noresult(const struct req *req, const char *msg) +pg_noresult(const struct req *req, int code, const char *http_msg, + const char *user_msg) { - resp_begin_html(200, NULL, NULL); + resp_begin_html(code, http_msg, NULL); resp_searchform(req, FOCUS_QUERY); puts("

"); - puts(msg); + puts(user_msg); puts("

"); resp_end_html(); } @@ -1016,9 +1041,10 @@ pg_search(const struct req *req) if (req->isquery && req->q.equal && argc == 1) pg_redirect(req, argv[0]); else if (mansearch(&search, &paths, argc, argv, &res, &ressz) == 0) - pg_noresult(req, "You entered an invalid query."); + pg_noresult(req, 400, "Bad Request", + "You entered an invalid query."); else if (ressz == 0) - pg_noresult(req, "No results found."); + pg_noresult(req, 404, "Not Found", "No results found."); else pg_searchres(req, res, ressz); diff --git a/chars.c b/chars.c index 24166dbd9f8f..d54fc458aea2 100644 --- a/chars.c +++ b/chars.c @@ -1,7 +1,8 @@ -/* $Id: chars.c,v 1.78 2018/12/15 19:30:26 schwarze Exp $ */ +/* $Id: chars.c,v 1.79 2020/02/13 16:18:29 schwarze Exp $ */ /* * Copyright (c) 2009, 2010, 2011 Kristaps Dzonsons - * Copyright (c) 2011,2014,2015,2017,2018 Ingo Schwarze + * Copyright (c) 2011, 2014, 2015, 2017, 2018, 2020 + * 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 @@ -47,7 +48,7 @@ static struct ln lines[] = { /* Spacing. */ { " ", ascii_nbrsp, 0x00a0 }, { "~", ascii_nbrsp, 0x00a0 }, - { "0", " ", 0x2002 }, + { "0", ascii_nbrsp, 0x00a0 }, { ":", ascii_break, 0 }, /* Lines. */ diff --git a/compat_err.c b/compat_err.c index d8b09cb2a307..31994cbfaee7 100644 --- a/compat_err.c +++ b/compat_err.c @@ -1,12 +1,4 @@ -#include "config.h" - -#if HAVE_ERR - -int dummy; - -#else - -/* $Id: compat_err.c,v 1.4 2015/11/26 07:42:11 schwarze Exp $ */ +/* $Id: compat_err.c,v 1.5 2020/06/15 01:37:14 schwarze Exp $ */ /* * Copyright (c) 1993 * The Regents of the University of California. All rights reserved. @@ -35,6 +27,7 @@ int dummy; * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ +#include "config.h" #include #include @@ -108,5 +101,3 @@ warnx(const char *fmt, ...) va_end(ap); fputc('\n', stderr); } - -#endif diff --git a/compat_fts.c b/compat_fts.c index 3859111a52f8..e44298df02b8 100644 --- a/compat_fts.c +++ b/compat_fts.c @@ -1,13 +1,5 @@ -#include "config.h" - -#if HAVE_FTS - -int dummy; - -#else - -/* $Id: compat_fts.c,v 1.14 2017/02/18 12:24:24 schwarze Exp $ */ -/* $OpenBSD: fts.c,v 1.56 2016/09/21 04:38:56 guenther Exp $ */ +/* $Id: compat_fts.c,v 1.17 2020/06/15 01:37:14 schwarze Exp $ */ +/* $OpenBSD: fts.c,v 1.59 2019/06/28 13:32:41 deraadt Exp $ */ /*- * Copyright (c) 1990, 1993, 1994 @@ -37,6 +29,7 @@ int dummy; * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ +#include "config.h" #include #include @@ -62,6 +55,8 @@ static int fts_palloc(FTS *, size_t); static FTSENT *fts_sort(FTS *, FTSENT *, int); static unsigned short fts_stat(FTS *, FTSENT *); +typedef int (*qsort_compar_proto)(const void *, const void *); + #define ISDOT(a) (a[0] == '.' && (!a[1] || (a[1] == '.' && !a[2]))) #ifndef O_CLOEXEC #define O_CLOEXEC 0 @@ -573,19 +568,20 @@ fts_sort(FTS *sp, FTSENT *head, int nitems) 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) { + nitems + 40, sizeof(FTSENT *))) == NULL) { free(sp->fts_array); sp->fts_array = NULL; sp->fts_nitems = 0; return (head); } + sp->fts_nitems = nitems + 40; 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); + qsort(sp->fts_array, nitems, sizeof(FTSENT *), + (qsort_compar_proto)sp->fts_compar); for (head = *(ap = sp->fts_array); --nitems; ++ap) ap[0]->fts_link = ap[1]; ap[0]->fts_link = NULL; @@ -648,13 +644,14 @@ fts_palloc(FTS *sp, size_t more) errno = ENAMETOOLONG; return (1); } - sp->fts_pathlen += more; - p = realloc(sp->fts_path, sp->fts_pathlen); + p = recallocarray(sp->fts_path, sp->fts_pathlen, + sp->fts_pathlen + more, 1); if (p == NULL) { free(sp->fts_path); sp->fts_path = NULL; return (1); } + sp->fts_pathlen += more; sp->fts_path = p; return (0); } @@ -697,5 +694,3 @@ fts_maxarglen(char * const *argv) max = len; return (max + 1); } - -#endif diff --git a/compat_fts.h b/compat_fts.h index f4a97a4c3a56..ca3f3a7ed804 100644 --- a/compat_fts.h +++ b/compat_fts.h @@ -43,7 +43,8 @@ typedef struct { 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 */ + int (*fts_compar)(const struct _ftsent **, const struct _ftsent **); + /* compare function */ #define FTS_NOCHDIR 0x0004 /* don't change directories */ #define FTS_PHYSICAL 0x0010 /* physical walk */ diff --git a/compat_getline.c b/compat_getline.c index aed4754ffdae..843a94a6bb93 100644 --- a/compat_getline.c +++ b/compat_getline.c @@ -1,12 +1,4 @@ -#include "config.h" - -#if HAVE_GETLINE - -int dummy; - -#else - -/* $Id: compat_getline.c,v 1.1 2015/11/07 20:52:52 schwarze Exp $ */ +/* $Id: compat_getline.c,v 1.2 2020/06/15 01:37:14 schwarze Exp $ */ /* * Copyright (c) 2015 Ingo Schwarze * @@ -22,6 +14,7 @@ int dummy; * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ +#include "config.h" #include #include @@ -64,5 +57,3 @@ getline(char **buf, size_t *bufsz, FILE *fp) return pos; } } - -#endif diff --git a/compat_getsubopt.c b/compat_getsubopt.c index 880f2f789d4a..c442f87ecab7 100644 --- a/compat_getsubopt.c +++ b/compat_getsubopt.c @@ -1,12 +1,4 @@ -#include "config.h" - -#if HAVE_GETSUBOPT - -int dummy; - -#else - -/* $Id: compat_getsubopt.c,v 1.5 2014/08/17 20:53:50 schwarze Exp $ */ +/* $Id: compat_getsubopt.c,v 1.6 2020/06/15 01:37:15 schwarze Exp $ */ /* $OpenBSD: getsubopt.c,v 1.4 2005/08/08 08:05:36 espie Exp $ */ /*- @@ -37,6 +29,7 @@ int dummy; * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ +#include "config.h" #include #include @@ -92,5 +85,3 @@ getsubopt(char **optionp, char * const *tokens, char **valuep) return(cnt); return(-1); } - -#endif diff --git a/compat_isblank.c b/compat_isblank.c index 9e3c74706c3d..38d1d2839eb8 100644 --- a/compat_isblank.c +++ b/compat_isblank.c @@ -1,12 +1,4 @@ -#include "config.h" - -#if HAVE_ISBLANK - -int dummy; - -#else - -/* $Id: compat_isblank.c,v 1.2 2015/10/06 18:32:19 schwarze Exp $ */ +/* $Id: compat_isblank.c,v 1.3 2020/06/15 01:37:15 schwarze Exp $ */ /* * Copyright (c) 2015 Ingo Schwarze * @@ -22,12 +14,10 @@ int dummy; * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ +#include "config.h" int isblank(int c) { - return c == ' ' || c == '\t'; } - -#endif diff --git a/compat_mkdtemp.c b/compat_mkdtemp.c index 1fcb325f4f9a..c2edfcb1927b 100644 --- a/compat_mkdtemp.c +++ b/compat_mkdtemp.c @@ -1,14 +1,6 @@ -#include "config.h" - -#if HAVE_MKDTEMP - -int dummy; - -#else - -/* $Id: compat_mkdtemp.c,v 1.2 2015/10/06 18:32:19 schwarze Exp $ */ +/* $Id: compat_mkdtemp.c,v 1.4 2021/09/19 15:02:55 schwarze Exp $ */ /* - * Copyright (c) 2015 Ingo Schwarze + * Copyright (c) 2015, 2021 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 @@ -25,6 +17,7 @@ int dummy; * The algorithm of this function is inspired by OpenBSD mkdtemp(3) * by Theo de Raadt and Todd Miller, but the code differs. */ +#include "config.h" #include #include @@ -43,19 +36,15 @@ mkdtemp(char *path) start--; for (tries = INT_MAX; tries; tries--) { - if (mktemp(path) == NULL) { - errno = EEXIST; + if (mktemp(path) == NULL) return NULL; - } if (mkdir(path, S_IRUSR | S_IWUSR | S_IXUSR) == 0) return path; - if (errno != EEXIST) - return NULL; for (cp = start; *cp != '\0'; cp++) *cp = 'X'; + if (errno != EEXIST) + return NULL; } errno = EEXIST; return NULL; } - -#endif diff --git a/compat_mkstemps.c b/compat_mkstemps.c new file mode 100644 index 000000000000..32394ffa682e --- /dev/null +++ b/compat_mkstemps.c @@ -0,0 +1,63 @@ +/* $Id: compat_mkstemps.c,v 1.1 2021/09/19 15:05:39 schwarze Exp $ */ +/* + * Copyright (c) 2015, 2021 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. + * + * Parts of the algorithm of this function are inspired by OpenBSD + * mkdtemp(3) by Theo de Raadt and Todd Miller, but the code differs. + */ +#include "config.h" + +#include +#include +#include +#include +#include +#include + +int +mkstemps(char *path, int suffixlen) +{ + char *start, *end, *cp; + int fd, tries; + char backup; + + end = strchr(path, '\0'); + if (suffixlen < 0 || suffixlen > end - path - 6) { + errno = EINVAL; + return -1; + } + end -= suffixlen; + for (start = end; start > path; start--) + if (start[-1] != 'X') + break; + + backup = *end; + for (tries = INT_MAX; tries; tries--) { + *end = '\0'; + cp = mktemp(path); + *end = backup; + if (cp == NULL) + return -1; + fd = open(path, O_CREAT | O_EXCL | O_RDWR, S_IRUSR | S_IWUSR); + if (fd != -1) + return fd; + for (cp = start; cp < end; cp++) + *cp = 'X'; + if (errno != EEXIST) + return -1; + } + errno = EEXIST; + return -1; +} diff --git a/compat_ohash.c b/compat_ohash.c index cbd6052182f2..f29c086c6d7d 100644 --- a/compat_ohash.c +++ b/compat_ohash.c @@ -1,11 +1,4 @@ -#include "config.h" - -#if HAVE_OHASH - -int dummy; - -#else - +/* $Id: compat_ohash.c,v 1.7 2020/06/15 01:37:15 schwarze Exp $ */ /* $OpenBSD: ohash.c,v 1.1 2014/06/02 18:52:03 deraadt Exp $ */ /* Copyright (c) 1999, 2004 Marc Espie @@ -22,9 +15,9 @@ int dummy; * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ +#include "config.h" #include - #include #include #include @@ -335,5 +328,3 @@ ohash_qlookupi(struct ohash *h, const char *s, const char **e) hv = ohash_interval(s, e); return ohash_lookup_interval(h, s, *e, hv); } - -#endif /*!HAVE_OHASH*/ diff --git a/compat_progname.c b/compat_progname.c index 9840cc7c6c61..e067e489c6b7 100644 --- a/compat_progname.c +++ b/compat_progname.c @@ -1,12 +1,4 @@ -#include "config.h" - -#if HAVE_PROGNAME - -int dummy; - -#else - -/* $Id: compat_progname.c,v 1.1 2015/11/06 16:30:33 schwarze Exp $ */ +/* $Id: compat_progname.c,v 1.2 2020/06/15 01:37:15 schwarze Exp $ */ /* * Copyright (c) 2015 Ingo Schwarze * @@ -22,21 +14,18 @@ int dummy; * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ +#include "config.h" static const char *progname; void setprogname(const char *name) { - progname = name; } const char * getprogname(void) { - return progname; } - -#endif diff --git a/compat_reallocarray.c b/compat_reallocarray.c index 66151904257d..a12cec1344cb 100644 --- a/compat_reallocarray.c +++ b/compat_reallocarray.c @@ -1,13 +1,5 @@ -#include "config.h" - -#if HAVE_REALLOCARRAY - -int dummy; - -#else - -/* $Id: compat_reallocarray.c,v 1.4 2014/12/11 09:05:01 schwarze Exp $ */ -/* $OpenBSD: reallocarray.c,v 1.2 2014/12/08 03:45:00 bcook Exp $ */ +/* $Id: compat_reallocarray.c,v 1.5 2020/06/15 01:37:15 schwarze Exp $ */ +/* $OpenBSD: reallocarray.c,v 1.3 2015/09/13 08:31:47 guenther Exp $ */ /* * Copyright (c) 2008 Otto Moerbeek * @@ -23,6 +15,7 @@ int dummy; * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ +#include "config.h" #include #include @@ -45,5 +38,3 @@ reallocarray(void *optr, size_t nmemb, size_t size) } return realloc(optr, size * nmemb); } - -#endif /*!HAVE_REALLOCARRAY*/ diff --git a/compat_recallocarray.c b/compat_recallocarray.c index d86dc5091a0a..723e145c0446 100644 --- a/compat_recallocarray.c +++ b/compat_recallocarray.c @@ -1,15 +1,7 @@ -#include "config.h" - -#if HAVE_RECALLOCARRAY - -int dummy; - -#else - -/* $Id: compat_recallocarray.c,v 1.1 2017/06/12 19:05:47 schwarze Exp $ */ -/* $OpenBSD: malloc.c,v 1.225 2017/05/13 07:11:29 otto Exp $ */ +/* $Id: compat_recallocarray.c,v 1.2 2020/06/15 01:37:15 schwarze Exp $ */ +/* $OpenBSD: recallocarray.c,v 1.1 2017/03/06 18:44:21 otto Exp $ */ /* - * Copyright (c) 2017 Otto Moerbeek + * Copyright (c) 2008, 2017 Otto Moerbeek * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -23,18 +15,19 @@ int dummy; * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ +#include "config.h" #include #include -#include #include +#include #include /* * This is sqrt(SIZE_MAX+1), as s1*s2 <= SIZE_MAX * if both s1 < MUL_NO_OVERFLOW and s2 < MUL_NO_OVERFLOW */ -#define MUL_NO_OVERFLOW ((size_t)1 << (sizeof(size_t) * 4)) +#define MUL_NO_OVERFLOW ((size_t)1 << (sizeof(size_t) * 4)) /* * Even though specified in POSIX, the PAGESIZE and PAGE_SIZE @@ -42,7 +35,7 @@ int dummy; * to avoid free() overhead for small shrinking, simply pick * an arbitrary number. */ -#define MALLOC_PAGESIZE (1UL << 12) +#define getpagesize() (1UL << 12) void * @@ -75,7 +68,7 @@ recallocarray(void *ptr, size_t oldnmemb, size_t newnmemb, size_t size) if (newsize <= oldsize) { size_t d = oldsize - newsize; - if (d < oldsize / 2 && d < MALLOC_PAGESIZE) { + if (d < oldsize / 2 && d < getpagesize()) { memset((char *)ptr + newsize, 0, d); return ptr; } @@ -104,5 +97,3 @@ recallocarray(void *ptr, size_t oldnmemb, size_t newnmemb, size_t size) return newptr; } - -#endif /* !HAVE_RECALLOCARRAY */ diff --git a/compat_strcasestr.c b/compat_strcasestr.c index 62c0ff740c81..5eba8257fdb0 100644 --- a/compat_strcasestr.c +++ b/compat_strcasestr.c @@ -1,12 +1,4 @@ -#include "config.h" - -#if HAVE_STRCASESTR - -int dummy; - -#else - -/* $Id: compat_strcasestr.c,v 1.4 2014/12/11 09:19:32 schwarze Exp $ */ +/* $Id: compat_strcasestr.c,v 1.5 2020/06/15 01:37:15 schwarze Exp $ */ /* $NetBSD: strcasestr.c,v 1.3 2005/11/29 03:12:00 christos Exp $ */ /*- @@ -40,6 +32,7 @@ int dummy; * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ +#include "config.h" #include #include @@ -69,5 +62,3 @@ strcasestr(const char *s, const char *find) } return __UNCONST(s); } - -#endif diff --git a/compat_stringlist.c b/compat_stringlist.c index 17eba7724af5..fb8e56aebaa4 100644 --- a/compat_stringlist.c +++ b/compat_stringlist.c @@ -1,16 +1,13 @@ -#include "config.h" - -#if HAVE_STRINGLIST - -int dummy; - -#else +/* $Id: compat_stringlist.c,v 1.8 2020/06/15 21:48:09 schwarze Exp $ */ +/* $NetBSD: stringlist.c,v 1.14 2015/05/21 01:29:13 christos Exp $ */ -/* $Id: compat_stringlist.c,v 1.6 2015/11/07 14:22:29 schwarze Exp $ */ -/* - * Copyright (c) 1994 Christos Zoulas +/*- + * Copyright (c) 1994, 1999 The NetBSD Foundation, Inc. * All rights reserved. * + * This code is derived from software contributed to The NetBSD Foundation + * by Christos Zoulas. + * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: @@ -20,22 +17,20 @@ int dummy; * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * - * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS - * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY - * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS - * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT - * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY - * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF - * SUCH DAMAGE. + * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. */ +#include "config.h" -#if HAVE_ERR -#include -#endif #include #include #include "compat_stringlist.h" @@ -52,13 +47,15 @@ sl_init(void) sl = malloc(sizeof(StringList)); if (sl == NULL) - err(1, "stringlist"); + return NULL; sl->sl_cur = 0; sl->sl_max = _SL_CHUNKSIZE; sl->sl_str = reallocarray(NULL, sl->sl_max, sizeof(char *)); - if (sl->sl_str == NULL) - err(1, "stringlist"); + if (sl->sl_str == NULL) { + free(sl); + sl = NULL; + } return sl; } @@ -70,14 +67,17 @@ int sl_add(StringList *sl, char *name) { if (sl->sl_cur == sl->sl_max - 1) { + char **new; + + new = reallocarray(sl->sl_str, (sl->sl_max + _SL_CHUNKSIZE), + sizeof(char *)); + if (new == NULL) + return -1; sl->sl_max += _SL_CHUNKSIZE; - sl->sl_str = reallocarray(sl->sl_str, - sl->sl_max, sizeof(char *)); - if (sl->sl_str == NULL) - return (-1); + sl->sl_str = new; } sl->sl_str[sl->sl_cur++] = name; - return (0); + return 0; } @@ -116,4 +116,20 @@ sl_find(StringList *sl, const char *name) return NULL; } -#endif +int +sl_delete(StringList *sl, const char *name, int all) +{ + size_t i, j; + + for (i = 0; i < sl->sl_cur; i++) + if (strcmp(sl->sl_str[i], name) == 0) { + if (all) + free(sl->sl_str[i]); + for (j = i + 1; j < sl->sl_cur; j++) + sl->sl_str[j - 1] = sl->sl_str[j]; + sl->sl_str[--sl->sl_cur] = NULL; + return 0; + } + return -1; +} + diff --git a/compat_stringlist.h b/compat_stringlist.h index f04e8435047d..5777b1cb2871 100644 --- a/compat_stringlist.h +++ b/compat_stringlist.h @@ -1,10 +1,13 @@ -/* $Id: compat_stringlist.h,v 1.4 2015/11/07 14:01:16 schwarze Exp $ */ -/* $NetBSD: stringlist.h,v 1.2 1997/01/17 06:11:36 lukem Exp $ */ +/* $Id: compat_stringlist.h,v 1.5 2020/06/15 21:48:09 schwarze Exp $ */ +/* $NetBSD: stringlist.h,v 1.7 2008/04/28 20:22:54 martin Exp $ */ -/* - * Copyright (c) 1994 Christos Zoulas +/*- + * Copyright (c) 1994 The NetBSD Foundation, Inc. * All rights reserved. * + * This code is derived from software contributed to The NetBSD Foundation + * by Christos Zoulas. + * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: @@ -14,17 +17,17 @@ * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * - * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS - * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY - * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS - * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT - * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY - * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF - * SUCH DAMAGE. + * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. */ #include @@ -38,8 +41,8 @@ typedef struct _stringlist { size_t sl_cur; } StringList; - -StringList *sl_init(void); -int sl_add(StringList *, char *); -void sl_free(StringList *, int); -char *sl_find(StringList *, const char *); +StringList *sl_init(void); +int sl_add(StringList *, char *); +void sl_free(StringList *, int); +char *sl_find(StringList *, const char *); +int sl_delete(StringList *, const char *, int); diff --git a/compat_strlcat.c b/compat_strlcat.c index acaae4fb1506..dc0ec5e79064 100644 --- a/compat_strlcat.c +++ b/compat_strlcat.c @@ -1,15 +1,8 @@ -#include "config.h" - -#if HAVE_STRLCAT - -int dummy; - -#else - -/* $OpenBSD: strlcat.c,v 1.13 2005/08/08 08:05:37 espie Exp $ */ +/* $Id: compat_strlcat.c,v 1.6 2020/06/15 20:49:57 schwarze Exp $ */ +/* $OpenBSD: strlcat.c,v 1.19 2019/01/25 00:19:25 millert Exp $ */ /* - * Copyright (c) 1998 Todd C. Miller + * Copyright (c) 1998, 2015 Todd C. Miller * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -23,43 +16,42 @@ int dummy; * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ +#include "config.h" #include #include /* - * Appends src to string dst of size siz (unlike strncat, siz is the - * full size of dst, not space left). At most siz-1 characters - * will be copied. Always NUL terminates (unless siz <= strlen(dst)). - * Returns strlen(src) + MIN(siz, strlen(initial dst)). - * If retval >= siz, truncation occurred. + * Appends src to string dst of size dsize (unlike strncat, dsize is the + * full size of dst, not space left). At most dsize-1 characters + * will be copied. Always NUL terminates (unless dsize <= strlen(dst)). + * Returns strlen(src) + MIN(dsize, strlen(initial dst)). + * If retval >= dsize, truncation occurred. */ size_t -strlcat(char *dst, const char *src, size_t siz) +strlcat(char *dst, const char *src, size_t dsize) { - char *d = dst; - const char *s = src; - size_t n = siz; + const char *odst = dst; + const char *osrc = src; + size_t n = dsize; size_t dlen; - /* Find the end of dst and adjust bytes left but don't go past end */ - while (n-- != 0 && *d != '\0') - d++; - dlen = d - dst; - n = siz - dlen; - - if (n == 0) - return(dlen + strlen(s)); - while (*s != '\0') { - if (n != 1) { - *d++ = *s; + /* Find the end of dst and adjust bytes left but don't go past end. */ + while (n-- != 0 && *dst != '\0') + dst++; + dlen = dst - odst; + n = dsize - dlen; + + if (n-- == 0) + return(dlen + strlen(src)); + while (*src != '\0') { + if (n != 0) { + *dst++ = *src; n--; } - s++; + src++; } - *d = '\0'; + *dst = '\0'; - return(dlen + (s - src)); /* count does not include NUL */ + return(dlen + (src - osrc)); /* count does not include NUL */ } - -#endif diff --git a/compat_strlcpy.c b/compat_strlcpy.c index a00d511817a3..2e7201a64953 100644 --- a/compat_strlcpy.c +++ b/compat_strlcpy.c @@ -1,15 +1,8 @@ -#include "config.h" - -#if HAVE_STRLCPY - -int dummy; - -#else - -/* $OpenBSD: strlcpy.c,v 1.11 2006/05/05 15:27:38 millert Exp $ */ +/* $Id: compat_strlcpy.c,v 1.6 2020/06/15 20:49:57 schwarze Exp $ */ +/* $OpenBSD: strlcpy.c,v 1.16 2019/01/25 00:19:25 millert Exp $ */ /* - * Copyright (c) 1998 Todd C. Miller + * Copyright (c) 1998, 2015 Todd C. Miller * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -23,39 +16,37 @@ int dummy; * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ +#include "config.h" #include #include /* - * Copy src to string dst of size siz. At most siz-1 characters - * will be copied. Always NUL terminates (unless siz == 0). - * Returns strlen(src); if retval >= siz, truncation occurred. + * Copy string src to buffer dst of size dsize. At most dsize-1 + * chars will be copied. Always NUL terminates (unless dsize == 0). + * Returns strlen(src); if retval >= dsize, truncation occurred. */ size_t -strlcpy(char *dst, const char *src, size_t siz) +strlcpy(char *dst, const char *src, size_t dsize) { - char *d = dst; - const char *s = src; - size_t n = siz; + const char *osrc = src; + size_t nleft = dsize; - /* Copy as many bytes as will fit */ - if (n != 0) { - while (--n != 0) { - if ((*d++ = *s++) == '\0') + /* Copy as many bytes as will fit. */ + if (nleft != 0) { + while (--nleft != 0) { + if ((*dst++ = *src++) == '\0') break; } } - /* Not enough room in dst, add NUL and traverse rest of src */ - if (n == 0) { - if (siz != 0) - *d = '\0'; /* NUL-terminate dst */ - while (*s++) + /* Not enough room in dst, add NUL and traverse rest of src. */ + if (nleft == 0) { + if (dsize != 0) + *dst = '\0'; /* NUL-terminate dst */ + while (*src++) ; } - return(s - src - 1); /* count does not include NUL */ + return(src - osrc - 1); /* count does not include NUL */ } - -#endif diff --git a/compat_strndup.c b/compat_strndup.c index 5e127364f5c8..89693e795ce9 100644 --- a/compat_strndup.c +++ b/compat_strndup.c @@ -1,15 +1,8 @@ -#include "config.h" - -#if HAVE_STRNDUP - -int dummy; +/* $Id: compat_strndup.c,v 1.3 2020/06/15 20:19:39 schwarze Exp $ */ +/* $OpenBSD: strndup.c,v 1.3 2019/01/25 00:19:25 millert Exp $ */ -#else - -/* $Id: compat_strndup.c,v 1.1 2018/02/27 11:16:23 schwarze Exp $ */ -/* OpenBSD: strndup.c,v 1.2 2015/08/31 02:53:57 guenther Exp */ /* - * Copyright (c) 2010 Todd C. Miller + * Copyright (c) 2010 Todd C. Miller * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -23,6 +16,7 @@ int dummy; * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ +#include "config.h" #include @@ -46,5 +40,3 @@ strndup(const char *str, size_t maxlen) return copy; } - -#endif diff --git a/compat_strsep.c b/compat_strsep.c index 1df57582802b..9765ac823eeb 100644 --- a/compat_strsep.c +++ b/compat_strsep.c @@ -1,13 +1,5 @@ -#include "config.h" - -#if HAVE_STRSEP - -int dummy; - -#else - -/* $Id: compat_strsep.c,v 1.4 2014/12/11 09:05:01 schwarze Exp $ */ -/* $OpenBSD: strsep.c,v 1.7 2014/02/05 20:42:32 stsp Exp $ */ +/* $Id: compat_strsep.c,v 1.5 2020/06/15 01:37:15 schwarze Exp $ */ +/* $OpenBSD: strsep.c,v 1.8 2015/08/31 02:53:57 guenther Exp $ */ /*- * Copyright (c) 1990, 1993 @@ -37,6 +29,7 @@ int dummy; * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ +#include "config.h" /* * Get next token from string *stringp, where tokens are possibly-empty @@ -75,5 +68,3 @@ strsep(char **stringp, const char *delim) } /* NOTREACHED */ } - -#endif diff --git a/compat_strtonum.c b/compat_strtonum.c index 628e5d51b86b..ce0459fa8825 100644 --- a/compat_strtonum.c +++ b/compat_strtonum.c @@ -1,13 +1,5 @@ -#include "config.h" - -#if HAVE_STRTONUM - -int dummy; - -#else - -/* $Id: compat_strtonum.c,v 1.1 2015/02/16 14:56:22 schwarze Exp $ */ -/* $OpenBSD: strtonum.c,v 1.7 2013/04/17 18:40:58 tedu Exp $ */ +/* $Id: compat_strtonum.c,v 1.2 2020/06/15 01:37:15 schwarze Exp $ */ +/* $OpenBSD: strtonum.c,v 1.8 2015/09/13 08:31:48 guenther Exp $ */ /* * Copyright (c) 2004 Ted Unangst and Todd Miller @@ -25,6 +17,7 @@ int dummy; * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ +#include "config.h" #include #include @@ -72,5 +65,3 @@ strtonum(const char *numstr, long long minval, long long maxval, return (ll); } - -#endif /* !HAVE_STRTONUM */ diff --git a/compat_vasprintf.c b/compat_vasprintf.c index 9040822b0097..11556c0155b8 100644 --- a/compat_vasprintf.c +++ b/compat_vasprintf.c @@ -1,12 +1,4 @@ -#include "config.h" - -#if HAVE_VASPRINTF - -int dummy; - -#else - -/* $Id: compat_vasprintf.c,v 1.3 2015/10/06 18:32:19 schwarze Exp $ */ +/* $Id: compat_vasprintf.c,v 1.4 2020/06/15 01:37:15 schwarze Exp $ */ /* * Copyright (c) 2015 Ingo Schwarze * @@ -28,6 +20,7 @@ int dummy; * printf(3) or completely reimplementing printf(3), i can't think * of another portable solution. */ +#include "config.h" #include #include @@ -52,5 +45,3 @@ vasprintf(char **ret, const char *format, va_list ap) *ret = NULL; return -1; } - -#endif diff --git a/configure b/configure index 2df028da0510..5cf4e081c2cb 100755 --- a/configure +++ b/configure @@ -1,8 +1,8 @@ #!/bin/sh # -# $Id: configure,v 1.71 2019/07/01 22:56:24 schwarze Exp $ +# $Id: configure,v 1.81 2021/09/20 10:19:51 schwarze Exp $ # -# Copyright (c) 2014-2019 Ingo Schwarze +# Copyright (c) 2014-2021 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,7 +33,7 @@ echo "file config.log: writing..." # Initialize all variables here, # such that nothing can leak in from the environment. -SOURCEDIR=`dirname "$0"` +SOURCEDIR=`dirname "${0}"` MANPATH_BASE="/usr/share/man:/usr/X11R6/man" MANPATH_DEFAULT="/usr/share/man:/usr/X11R6/man:/usr/local/man" @@ -41,8 +41,10 @@ OSENUM= OSNAME= UTF8_LOCALE= -CC=`printf "all:\\n\\t@echo \\\$(CC)\\n" | env -i make -sf -` +AR=ar +CC=cc CFLAGS= +FATAL=0 LDADD= LDFLAGS= LD_NANOSLEEP= @@ -54,8 +56,8 @@ BUILD_CGI=0 BUILD_CATMAN=0 INSTALL_LIBMANDOC=0 +HAVE_ATTRIBUTE= HAVE_CMSG= -HAVE_CMSG_XPG42=0 HAVE_DIRENT_NAMLEN= HAVE_EFTYPE= HAVE_ENDIAN= @@ -67,6 +69,7 @@ HAVE_GETSUBOPT= HAVE_ISBLANK= HAVE_LESS_T= HAVE_MKDTEMP= +HAVE_MKSTEMPS= HAVE_NANOSLEEP= HAVE_NTOHL= HAVE_O_DIRECTORY= @@ -94,6 +97,10 @@ HAVE_WCHAR= NEED_GNU_SOURCE=0 NEED_OPENBSD_SOURCE=0 +NEED_XPG4_2=0 + +MANDOC_COBJS= +SOELIM_COBJS= PREFIX="/usr/local" BINDIR= @@ -102,7 +109,7 @@ BIN_FROM_SBIN= INCLUDEDIR= LIBDIR= MANDIR= -HOMEBREWDIR= +READ_ALLOWED_PATH= WWWPREFIX="/var/www" HTDOCDIR= @@ -114,6 +121,7 @@ BINM_MAKEWHATIS="makewhatis" BINM_MAN="man" BINM_SOELIM="soelim" BINM_WHATIS="whatis" +BINM_PAGER= MANM_MAN="man" MANM_MANCONF="man.conf" MANM_MDOC="mdoc" @@ -159,16 +167,17 @@ ismanual() { # In case of failure, do not decide anything yet. # Arguments: test file name, test var name, additional CFLAGS singletest() { - n=${1}${3}${4} + n=${1}${3} cat 1>&3 << __HEREDOC__ testing ${n} ... -${COMP} -o test-${1} test-${1}.c ${3} ${4} +${COMP} -o test-${1} test-${1}.c ${3} __HEREDOC__ - if ${COMP} -o "test-${1}" "${SOURCEDIR}/test-${1}.c" ${3} ${4} 1>&3 2>&3 + if ${COMP} -o "test-${1}" "${SOURCEDIR}/test-${1}.c" ${3} 1>&3 2>&3 then echo "partial result of ${n}: ${CC} succeeded" 1>&3 else + echo "tested ${n}: no (compilation failed)" 1>&2 echo "result of ${n}: ${CC} failed with exit status $?" 1>&3 echo "result of compiling ${n}: no" 1>&3 echo 1>&3 @@ -180,11 +189,16 @@ __HEREDOC__ echo "result of running ${n}: yes" 1>&3 echo 1>&3 eval HAVE_${2}=1 - [ "X$3" = "X-D_GNU_SOURCE" ] && NEED_GNU_SOURCE=1 - [ "X$3" = "X-D_OPENBSD_SOURCE" ] && NEED_OPENBSD_SOURCE=1 + [ "${3}" = "-D_GNU_SOURCE" ] && NEED_GNU_SOURCE=1 + [ "${3}" = "-D_OPENBSD_SOURCE" ] && NEED_OPENBSD_SOURCE=1 + [ "${3}" = "-D_XPG4_2" ] && NEED_XPG4_2=1 + [ "${3}" = "-lrt" ] && LD_NANOSLEEP="-lrt" + [ "${3}" = "-lsocket" ] && LD_RECVMSG="-lsocket" + [ "${3}" = "-lutil" ] && LD_OHASH="-lutil" rm "test-${1}" return 0 else + echo "tested ${n}: no (execution failed)" 1>&2 echo "result of ${n}: execution failed with exit status $?" 1>&3 echo "result of running ${n}: no" 1>&3 echo 1>&3 @@ -196,11 +210,12 @@ __HEREDOC__ # Run a complete autoconfiguration test, including the check for # a manual override and disabling the feature on failure. # Arguments: test file name, test var name, additional CFLAGS +# The final argument can optionally be repeated a second time. runtest() { eval _manual=\${HAVE_${2}} ismanual "${1}" "${2}" "${_manual}" && return 0 - singletest "${1}" "${2}" "${3}" "${4}" && return 0 - echo "tested ${1}${3}${4}: no" 1>&2 + singletest "${1}" "${2}" "${3}" && return 0 + [ -n "${4}" ] && singletest "${1}" "${2}" "${4}" && return 0 eval HAVE_${2}=0 return 1 } @@ -208,7 +223,7 @@ runtest() { # 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 + ismanual UTF8_LOCALE UTF8_LOCALE "${UTF8_LOCALE}" && return 0 echo "testing UTF8_LOCALE ..." 1>&3 UTF8_LOCALE=`locale -a | grep -i '^en_US\.UTF-*8$' | head -n 1` if [ -z "${UTF8_LOCALE}" ]; then @@ -228,9 +243,9 @@ if [ -n "${OSENUM}" ]; then echo "OSENUM specified manually: ${OSENUM}" 1>&3 else OSDETECT=`uname` - if [ "X${OSDETECT}" = "XNetBSD" ]; then + if [ "${OSDETECT}" = "NetBSD" ]; then OSENUM=MANDOC_OS_NETBSD - elif [ "X${OSDETECT}" = "XOpenBSD" ]; then + elif [ "${OSDETECT}" = "OpenBSD" ]; then OSENUM=MANDOC_OS_OPENBSD else OSENUM=MANDOC_OS_OTHER @@ -250,8 +265,8 @@ if [ -n "${CFLAGS}" ]; then else COMP="${CC} ${DEFCFLAGS} -Wno-unused -Werror" fi -echo -n "tested ${CC} -W: " 1>&2 -echo -n "testing ${CC} -W: " 1>&3 +printf "%s" "tested ${CC} -W: " 1>&2 +printf "%s" "testing ${CC} -W: " 1>&3 runtest noop WFLAG || true if [ -n "${CFLAGS}" ]; then @@ -283,51 +298,48 @@ fi # --- tests for config.h ---------------------------------------------- # --- library functions --- +runtest attribute ATTRIBUTE || true +runtest cmsg CMSG "" "-D_XPG4_2" || true 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 getline GETLINE || true -singletest getsubopt GETSUBOPT || \ - runtest getsubopt GETSUBOPT -D_GNU_SOURCE || true +runtest getsubopt GETSUBOPT "" -D_GNU_SOURCE || true runtest isblank ISBLANK || true runtest mkdtemp MKDTEMP || true +runtest mkstemps MKSTEMPS || true +runtest nanosleep NANOSLEEP "${LD_NANOSLEEP}" "-lrt" || true runtest ntohl NTOHL || true runtest O_DIRECTORY O_DIRECTORY || true runtest PATH_MAX PATH_MAX || true runtest pledge PLEDGE || true runtest sandbox_init SANDBOX_INIT || true runtest progname PROGNAME || true -singletest reallocarray REALLOCARRAY || \ - runtest reallocarray REALLOCARRAY -D_OPENBSD_SOURCE || true -singletest recallocarray RECALLOCARRAY || \ - runtest recallocarray RECALLOCARRAY -D_OPENBSD_SOURCE || true +runtest reallocarray REALLOCARRAY "" -D_OPENBSD_SOURCE || true +runtest recallocarray RECALLOCARRAY "" -D_OPENBSD_SOURCE || true +runtest recvmsg RECVMSG "${LD_RECVMSG}" "-lsocket" || true runtest rewb-bsd REWB_BSD || true runtest rewb-sysv REWB_SYSV || true -singletest strcasestr STRCASESTR || \ - runtest strcasestr STRCASESTR -D_GNU_SOURCE || true +runtest strcasestr STRCASESTR "" -D_GNU_SOURCE || true runtest stringlist STRINGLIST || true runtest strlcat STRLCAT || true runtest strlcpy STRLCPY || true runtest strndup STRNDUP || true -singletest strptime STRPTIME || \ - runtest strptime STRPTIME -D_GNU_SOURCE || true +runtest strptime STRPTIME "" -D_GNU_SOURCE || true runtest strsep STRSEP || true -singletest strtonum STRTONUM || \ - runtest strtonum STRTONUM -D_OPENBSD_SOURCE || true -singletest vasprintf VASPRINTF || \ - runtest vasprintf VASPRINTF -D_GNU_SOURCE || true - -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 +runtest strtonum STRTONUM "" -D_OPENBSD_SOURCE || true +runtest vasprintf VASPRINTF "" -D_GNU_SOURCE || true -if ismanual fts FTS ${HAVE_FTS}; then +# --- fts --- +if [ "${1}" = "-depend" ]; then + HAVE_FTS=0 + HAVE_FTS_COMPARE_CONST=0 + echo "tested fts: HAVE_FTS=0 (for make depend)" 1>&2 + echo "tested fts: HAVE_FTS=0 (for make depend)" 1>&3 + echo 1>&3 +elif ismanual fts FTS ${HAVE_FTS}; then HAVE_FTS_COMPARE_CONST=0 elif runtest fts FTS_COMPARE_CONST -DFTS_COMPARE_CONST; then HAVE_FTS=1 @@ -335,25 +347,41 @@ else runtest fts FTS || true fi -if ismanual "less -T" LESS_T ${HAVE_LESS_T}; then +# --- pager --- +manual= +if [ -n "${BINM_PAGER}" ]; then + manual=" (manual)" +elif less test-noop.c 1>/dev/null 2>&3; then + BINM_PAGER=less + echo "tested less: yes" 1>&2 + echo "tested less: yes" 1>&3 +else + BINM_PAGER=more + echo "tested less: no" 1>&2 + echo "tested less: no" 1>&3 +fi +echo "selected BINM_PAGER=${BINM_PAGER}${manual}" 1>&2 +echo "selected BINM_PAGER=${BINM_PAGER}${manual}" 1>&3 + +# --- tagging support in the pager --- +if ismanual "${BINM_PAGER} -T" LESS_T ${HAVE_LESS_T}; then : -elif less -ET /dev/null test-noop.c 1>/dev/null 2>&3; then +elif ${BINM_PAGER} -T /dev/null test-noop.c 1>/dev/null 2>&3; then HAVE_LESS_T=1 - echo "tested less -T: yes" 1>&2 - echo "tested less -T: yes" 1>&3 + echo "tested ${BINM_PAGER} -T: yes" 1>&2 + echo "tested ${BINM_PAGER} -T: yes" 1>&3 echo 1>&3 else HAVE_LESS_T=0 - echo "tested less -T: no" 1>&2 - echo "tested less -T: no" 1>&3 + echo "tested ${BINM_PAGER} -T: no" 1>&2 + echo "tested ${BINM_PAGER} -T: no" 1>&3 echo 1>&3 fi # --- wide character and locale support --- if get_locale; then - singletest wchar WCHAR -DUTF8_LOCALE=\"${UTF8_LOCALE}\" || \ - runtest wchar WCHAR -D_GNU_SOURCE \ - -DUTF8_LOCALE=\"${UTF8_LOCALE}\" || true + runtest wchar WCHAR "-DUTF8_LOCALE=\"${UTF8_LOCALE}\"" \ + "-D_GNU_SOURCE -DUTF8_LOCALE=\"${UTF8_LOCALE}\"" || true else HAVE_WCHAR=0 echo "tested wchar: no (no UTF8_LOCALE)" 1>&2 @@ -361,63 +389,45 @@ else echo 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" +# --- ohash --- +if [ "${1}" = "-depend" ]; then + HAVE_OHASH=0 + echo "tested ohash: HAVE_OHASH=0 (for make depend)" 1>&2 + echo "tested ohash: HAVE_OHASH=0 (for make depend)" 1>&3 + echo 1>&3 +else + runtest ohash OHASH "${LD_OHASH}" "-lutil" || true +fi +if [ "${HAVE_OHASH}" -eq 0 ]; then + LD_OHASH= +fi + +# --- required functions --- +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 + FATAL=1 fi if [ "${HAVE_NANOSLEEP}" -eq 0 ]; then echo "FATAL: nanosleep: no" 1>&2 echo "FATAL: nanosleep: no" 1>&3 - exit 1 + FATAL=1 fi - -if [ ${BUILD_CATMAN} -gt 0 ]; then - # --- recvmsg --- - if [ -n "${LD_RECVMSG}" ]; then - runtest recvmsg RECVMSG "${LD_RECVMSG}" || true - elif singletest recvmsg RECVMSG; then - : - elif runtest recvmsg RECVMSG "-lsocket"; then - LD_RECVMSG="-lsocket" - fi - if [ "${HAVE_RECVMSG}" -eq 0 ]; then - echo "FATAL: recvmsg: no" 1>&2 - echo "FATAL: recvmsg: no" 1>&3 - echo "Without recvmsg(2), you cannot BUILD_CATMAN." 1>&2 - exit 1 - fi - - # --- cmsg --- - if singletest cmsg CMSG; then - : - elif runtest cmsg CMSG "-D_XPG4_2"; then - HAVE_CMSG_XPG42=1 - fi - if [ "${HAVE_CMSG}" -eq 0 ]; then - echo "FATAL: cmsg: no" 1>&2 - echo "FATAL: cmsg: no" 1>&3 - echo "Without CMSG_FIRSTHDR(3), you cannot BUILD_CATMAN." 1>&2 - exit 1 - fi -fi - -# --- ohash --- -if ismanual ohash OHASH "${HAVE_OHASH}"; then - : -elif [ -n "${LD_OHASH}" ]; then - runtest ohash OHASH "${LD_OHASH}" || true -elif singletest ohash OHASH; then - : -elif runtest ohash OHASH "-lutil"; then - LD_OHASH="-lutil" +if [ ${BUILD_CATMAN} -gt 0 -a "${HAVE_RECVMSG}" -eq 0 ]; then + echo "FATAL: recvmsg: no" 1>&2 + echo "FATAL: recvmsg: no" 1>&3 + echo "Without recvmsg(2), you cannot BUILD_CATMAN." 1>&2 + FATAL=1 fi -if [ "${HAVE_OHASH}" -eq 0 ]; then - LD_OHASH= +if [ ${BUILD_CATMAN} -gt 0 -a "${HAVE_CMSG}" -eq 0 ]; then + echo "FATAL: cmsg: no" 1>&2 + echo "FATAL: cmsg: no" 1>&3 + echo "Without CMSG_FIRSTHDR(3), you cannot BUILD_CATMAN." 1>&2 + FATAL=1 fi +[ "${FATAL}" -eq 0 ] || exit 1 # --- LDADD --- LDADD="${LDADD} ${LD_NANOSLEEP} ${LD_RECVMSG} ${LD_OHASH} -lz" @@ -434,10 +444,6 @@ cat << __HEREDOC__ #error "Do not use C++. See the INSTALL file." #endif -#if !defined(__GNUC__) || (__GNUC__ < 4) -#define __attribute__(x) -#endif - __HEREDOC__ [ ${NEED_GNU_SOURCE} -eq 0 ] || echo "#define _GNU_SOURCE" @@ -458,7 +464,9 @@ echo "#define MANPATH_DEFAULT \"${MANPATH_DEFAULT}\"" echo "#define OSENUM ${OSENUM}" [ -n "${OSNAME}" ] && echo "#define OSNAME \"${OSNAME}\"" [ -n "${UTF8_LOCALE}" ] && echo "#define UTF8_LOCALE \"${UTF8_LOCALE}\"" -[ -n "${HOMEBREWDIR}" ] && echo "#define HOMEBREWDIR \"${HOMEBREWDIR}\"" +[ -n "${READ_ALLOWED_PATH}" ] \ + && echo "#define READ_ALLOWED_PATH \"${READ_ALLOWED_PATH}\"" +[ ${HAVE_ATTRIBUTE} -eq 0 ] && echo "#define __attribute__(x)" [ ${HAVE_EFTYPE} -eq 0 ] && echo "#define EFTYPE EINVAL" [ ${HAVE_O_DIRECTORY} -eq 0 ] && echo "#define O_DIRECTORY 0" [ ${HAVE_PATH_MAX} -eq 0 ] && echo "#define PATH_MAX 4096" @@ -466,9 +474,8 @@ 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_CMSG_XPG42 ${HAVE_CMSG_XPG42} + #define HAVE_DIRENT_NAMLEN ${HAVE_DIRENT_NAMLEN} #define HAVE_ENDIAN ${HAVE_ENDIAN} #define HAVE_ERR ${HAVE_ERR} @@ -479,6 +486,7 @@ cat << __HEREDOC__ #define HAVE_ISBLANK ${HAVE_ISBLANK} #define HAVE_LESS_T ${HAVE_LESS_T} #define HAVE_MKDTEMP ${HAVE_MKDTEMP} +#define HAVE_MKSTEMPS ${HAVE_MKSTEMPS} #define HAVE_NTOHL ${HAVE_NTOHL} #define HAVE_PLEDGE ${HAVE_PLEDGE} #define HAVE_PROGNAME ${HAVE_PROGNAME} @@ -499,6 +507,7 @@ cat << __HEREDOC__ #define HAVE_VASPRINTF ${HAVE_VASPRINTF} #define HAVE_WCHAR ${HAVE_WCHAR} #define HAVE_OHASH ${HAVE_OHASH} +#define NEED_XPG4_2 ${NEED_XPG4_2} #define BINM_APROPOS "${BINM_APROPOS}" #define BINM_CATMAN "${BINM_CATMAN}" @@ -506,6 +515,7 @@ cat << __HEREDOC__ #define BINM_MAN "${BINM_MAN}" #define BINM_SOELIM "${BINM_SOELIM}" #define BINM_WHATIS "${BINM_WHATIS}" +#define BINM_PAGER "${BINM_PAGER}" __HEREDOC__ @@ -514,52 +524,82 @@ if [ ${HAVE_ERR} -eq 0 ]; then echo "extern void errx(int, const char *, ...);" echo "extern void warn(const char *, ...);" echo "extern void warnx(const char *, ...);" + MANDOC_COBJS="${MANDOC_COBJS} compat_err.o" + SOELIM_COBJS="${SOELIM_COBJS} compat_err.o" fi - -[ ${HAVE_GETLINE} -eq 0 ] && \ +if [ ${HAVE_FTS} -eq 0 ]; then + MANDOC_COBJS="${MANDOC_COBJS} compat_fts.o" +fi +if [ ${HAVE_GETLINE} -eq 0 ]; then echo "extern ssize_t getline(char **, size_t *, FILE *);" - -[ ${HAVE_GETSUBOPT} -eq 0 ] && \ + MANDOC_COBJS="${MANDOC_COBJS} compat_getline.o" + SOELIM_COBJS="${SOELIM_COBJS} compat_getline.o" +fi +if [ ${HAVE_GETSUBOPT} -eq 0 ]; then echo "extern int getsubopt(char **, char * const *, char **);" - -[ ${HAVE_ISBLANK} -eq 0 ] && \ + MANDOC_COBJS="${MANDOC_COBJS} compat_getsubopt.o" +fi +if [ ${HAVE_ISBLANK} -eq 0 ]; then echo "extern int isblank(int);" - -[ ${HAVE_MKDTEMP} -eq 0 ] && \ + MANDOC_COBJS="${MANDOC_COBJS} compat_isblank.o" +fi +if [ ${HAVE_MKDTEMP} -eq 0 ]; then echo "extern char *mkdtemp(char *);" - + MANDOC_COBJS="${MANDOC_COBJS} compat_mkdtemp.o" +fi +if [ ${HAVE_MKSTEMPS} -eq 0 ]; then + echo "extern int mkstemps(char *, int);" + MANDOC_COBJS="${MANDOC_COBJS} compat_mkstemps.o" +fi +if [ ${HAVE_OHASH} -eq 0 ]; then + MANDOC_COBJS="${MANDOC_COBJS} compat_ohash.o" +fi if [ ${HAVE_PROGNAME} -eq 0 ]; then echo "extern const char *getprogname(void);" echo "extern void setprogname(const char *);" + MANDOC_COBJS="${MANDOC_COBJS} compat_progname.o" + SOELIM_COBJS="${SOELIM_COBJS} compat_progname.o" fi - -[ ${HAVE_REALLOCARRAY} -eq 0 ] && \ +if [ ${HAVE_REALLOCARRAY} -eq 0 ]; then echo "extern void *reallocarray(void *, size_t, size_t);" - -[ ${HAVE_RECALLOCARRAY} -eq 0 ] && \ + MANDOC_COBJS="${MANDOC_COBJS} compat_reallocarray.o" + SOELIM_COBJS="${SOELIM_COBJS} compat_reallocarray.o" +fi +if [ ${HAVE_RECALLOCARRAY} -eq 0 ]; then echo "extern void *recallocarray(void *, size_t, size_t, size_t);" - -[ ${HAVE_STRCASESTR} -eq 0 ] && \ + MANDOC_COBJS="${MANDOC_COBJS} compat_recallocarray.o" +fi +if [ ${HAVE_STRCASESTR} -eq 0 ]; then echo "extern char *strcasestr(const char *, const char *);" - -[ ${HAVE_STRLCAT} -eq 0 ] && \ + MANDOC_COBJS="${MANDOC_COBJS} compat_strcasestr.o" +fi +if [ ${HAVE_STRINGLIST} -eq 0 ]; then + SOELIM_COBJS="${SOELIM_COBJS} compat_stringlist.o" +fi +if [ ${HAVE_STRLCAT} -eq 0 ]; then echo "extern size_t strlcat(char *, const char *, size_t);" - -[ ${HAVE_STRLCPY} -eq 0 ] && \ + MANDOC_COBJS="${MANDOC_COBJS} compat_strlcat.o" +fi +if [ ${HAVE_STRLCPY} -eq 0 ]; then echo "extern size_t strlcpy(char *, const char *, size_t);" - -[ ${HAVE_STRNDUP} -eq 0 ] && \ + MANDOC_COBJS="${MANDOC_COBJS} compat_strlcpy.o" +fi +if [ ${HAVE_STRNDUP} -eq 0 ]; then echo "extern char *strndup(const char *, size_t);" - -[ ${HAVE_STRSEP} -eq 0 ] && \ + MANDOC_COBJS="${MANDOC_COBJS} compat_strndup.o" +fi +if [ ${HAVE_STRSEP} -eq 0 ]; then echo "extern char *strsep(char **, const char *);" - -[ ${HAVE_STRTONUM} -eq 0 ] && \ + MANDOC_COBJS="${MANDOC_COBJS} compat_strsep.o" +fi +if [ ${HAVE_STRTONUM} -eq 0 ]; then echo "extern long long strtonum(const char *, long long, long long, const char **);" - -[ ${HAVE_VASPRINTF} -eq 0 ] && \ + MANDOC_COBJS="${MANDOC_COBJS} compat_strtonum.o" +fi +if [ ${HAVE_VASPRINTF} -eq 0 ]; then echo "extern int vasprintf(char **, const char *, va_list);" - + MANDOC_COBJS="${MANDOC_COBJS} compat_vasprintf.o" +fi echo "file config.h: written" 1>&2 echo "file config.h: written" 1>&3 @@ -595,10 +635,13 @@ INSTALL_TARGETS= cat << __HEREDOC__ BUILD_TARGETS = ${BUILD_TARGETS} INSTALL_TARGETS = ${INSTALL_TARGETS} +AR = ${AR} CC = ${CC} CFLAGS = ${CFLAGS} LDADD = ${LDADD} LDFLAGS = ${LDFLAGS} +MANDOC_COBJS = ${MANDOC_COBJS} +SOELIM_COBJS = ${SOELIM_COBJS} STATIC = ${STATIC} PREFIX = ${PREFIX} BINDIR = ${BINDIR} diff --git a/configure.local.example b/configure.local.example index 4a456aade7c3..1050f4a1d9ab 100644 --- a/configure.local.example +++ b/configure.local.example @@ -1,6 +1,6 @@ -# $Id: configure.local.example,v 1.36 2019/03/06 10:18:58 schwarze Exp $ +# $Id: configure.local.example,v 1.43 2021/09/20 13:25:42 schwarze Exp $ # -# Copyright (c) 2014-2019 Ingo Schwarze +# Copyright (c) 2014-2021 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 @@ -28,6 +28,19 @@ # --- user settings relevant for all builds ---------------------------- +# By default, "cc" is used as the C compiler, but it can be overridden. +# For example, the system compiler in SunOS 5.9 may not provide , +# which may require this line: +CC=gcc + +# IBM AIX may need: +CC=xlc + +# By default, "ar" is used as the library archive builder, but it +# can be overridden. For example, NixOS may not have ar(1) in the +# PATH, but may want to specify an absolute path instead. +AR=ar + # For -Tutf8 and -Tlocale operation, mandoc(1) requires # providing setlocale(3) and providing wcwidth(3) and # putwchar(3) with a wchar_t storing UCS-4 values. Theoretically, @@ -88,7 +101,7 @@ OSENUM=MANDOC_OS_OTHER # 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 6.5" +OSNAME="OpenBSD 7.0" # The following installation directories are used. # It is possible to set only one or a few of these variables, @@ -143,6 +156,14 @@ BINM_WHATIS=mwhatis # default is "whatis" BINM_MAKEWHATIS=mandocdb # default is "makewhatis" BINM_SOELIM=msoelim # default is "soelim" +# If less(1) is available, it is used as the default manual pager. +# Otherwise, more(1) is used: its existence is required by POSIX. +# It is possible to force using a different default pager, either +# by giving the name of a program found in the PATH, or by giving +# an absolute path. + +BINM_PAGER=pg # default is "less" or "more" + # Some distributions do not want hardlinks # between installed binary programs. # Set the following variable to use symbolic links instead. @@ -193,14 +214,28 @@ INSTALL_LIB="${INSTALL} -m 0444" INSTALL_MAN="${INSTALL} -m 0444" INSTALL_DATA="${INSTALL} -m 0444" -# 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, -# you have to specify the physical location of the cellar as returned -# by realpath(3), for example: +# By default, makewhatis(8) can only read from the paths passed on the +# command line or configured in man.conf(5). +# But some package managers on some operating systems store manual pages +# in separate "cellar" or "store" directories and only symlink them +# into the manual trees. +# To support one or more such package managers, give makewhatis(8) +# read access to the cellars and stores on your system, in the form +# of a colon-separated path: +# Homebrow package manager on Mac OS X: PREFIX="/usr/local" -HOMEBREWDIR="${PREFIX}/Cellar" +READ_ALLOWED_PATH="${PREFIX}/Cellar" + +# Nix package manager and/or NixOS Linux distribution: +READ_ALLOWED_PATH="/nix/store" + +# GNU Guix package manager and/or GNU Guix Linux distribution: +READ_ALLOWED_PATH="/gnu/store" + +# If multiple package managers are used concurrently: +PREFIX="/usr/local" +READ_ALLOWED_PATH="/nix/store:${PREFIX}/Cellar" # --- user settings for the mandoc(3) library -------------------------- @@ -256,6 +291,8 @@ CGIBINDIR="${WWWPREFIX}/cgi-bin" # To enable it, use the following line. # It does not work on SunOS 5.10 because there is no mkdirat(2) # nor on SunOS 5.9 which also lacks CMSG_LEN(3) and CMSG_SPACE(3). +# It may not work on old releases of Mac OS X either. For example, +# Mac OS X 10.4 Tiger provides neither mkdirat(2) nor openat(2). BUILD_CATMAN=1 @@ -268,21 +305,6 @@ BINM_CATMAN=mcatman # default is "catman" # Do not set these variables unless you really need to. -# You can manually override the compiler to be used. -# But that's rarely useful because ./configure asks your make(1) -# which compiler to use, and that answer will hardly be wrong. - -CC=cc - -# Because the system compiler may not provide , -# SunOS 5.9 may need: - -CC=gcc - -# IBM AIX may need: - -CC=xlc - # Normally, leave CFLAGS unset. In that case, -g will automatically # be used, and various -W options will be added if the compiler # supports them. If you define CFLAGS manually, it will be used @@ -295,6 +317,7 @@ CFLAGS="-g" # and will be regarded as failed) or 1 (test will not be run and will # be regarded as successful). +HAVE_ATTRIBUTE=0 HAVE_DIRENT_NAMLEN=0 HAVE_ENDIAN=0 HAVE_EFTYPE=0 diff --git a/dba_array.c b/dba_array.c index 18c9f09f1a21..aaf02baa7c39 100644 --- a/dba_array.c +++ b/dba_array.c @@ -1,4 +1,4 @@ -/* $Id: dba_array.c,v 1.1 2016/07/19 21:31:55 schwarze Exp $ */ +/* $Id: dba_array.c,v 1.2 2020/06/22 19:20:40 schwarze Exp $ */ /* * Copyright (c) 2016 Ingo Schwarze * @@ -17,6 +17,8 @@ * Allocation-based arrays for the mandoc database, for read-write access. * The interface is defined in "dba_array.h". */ +#include "config.h" + #include #include #include diff --git a/dba_read.c b/dba_read.c index e976057064ab..4fc3ee5eddcb 100644 --- a/dba_read.c +++ b/dba_read.c @@ -1,4 +1,4 @@ -/* $Id: dba_read.c,v 1.4 2016/08/17 20:46:56 schwarze Exp $ */ +/* $Id: dba_read.c,v 1.5 2020/06/22 19:20:40 schwarze Exp $ */ /* * Copyright (c) 2016 Ingo Schwarze * @@ -19,6 +19,8 @@ * The interface is defined in "dba.h". * This file is seperate from dba.c because this also uses "dbm.h". */ +#include "config.h" + #include #include #include diff --git a/eqn.7 b/eqn.7 index c9fe483b1719..e164e8fb651c 100644 --- a/eqn.7 +++ b/eqn.7 @@ -1,4 +1,4 @@ -.\" $Id: eqn.7,v 1.38 2019/04/23 17:57:49 schwarze Exp $ +.\" $Id: eqn.7,v 1.39 2020/01/10 11:55:04 schwarze Exp $ .\" .\" Copyright (c) 2011 Kristaps Dzonsons .\" Copyright (c) 2014 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: April 23 2019 $ +.Dd $Mdocdate: January 10 2020 $ .Dt EQN 7 .Os .Sh NAME @@ -44,28 +44,16 @@ specification (see .Sx SEE ALSO for references). .Pp -Equations within -.Xr mdoc 7 -or -.Xr man 7 -documents are enclosed by the standalone -.Sq \&.EQ -and -.Sq \&.EN -tags. -Equations are multi-line blocks consisting of formulas and control -statements. -.Sh EQUATION STRUCTURE -Each equation is bracketed by -.Sq \&.EQ -and -.Sq \&.EN -strings. -.Em Note : -these are not the same as -.Xr roff 7 -macros, and may only be invoked as -.Sq \&.EQ . +An equation starts with an input line containing exactly the characters +.Sq \&.EQ , +may contain multiple input lines, and ends with an input line +containing exactly the characters +.Sq \&.EN . +Equivalently, an equation can be given in the middle of a single +text input line by surrounding it with the equation delimiters +defined with the +.Cm delim +statement. .Pp The equation grammar is as follows, where quoted strings are case-sensitive literals in the input: @@ -178,6 +166,25 @@ statement is a synonym for while .Cm tdefine is discarded. +.It Cm delim +This statement takes a string argument consisting of two bytes, +to be used as the opening and closing delimiters for equations +in the middle of text input lines. +Conventionally, the dollar sign is used for both delimiters, +as follows: +.Bd -literal -offset indent +\&.EQ +delim $$ +\&.EN +An equation like $sin pi = 0$ can now be entered +in the middle of a text input line. +.Ed +.Pp +The special statement +.Cm delim off +temporarily disables previously declared delimiters and +.Cm delim on +reenables them. .It Cm gfont Set the default font of subsequent output. Its syntax is as follows: diff --git a/eqn.c b/eqn.c index 3d63382ab31b..27f5cac396cb 100644 --- a/eqn.c +++ b/eqn.c @@ -1,7 +1,7 @@ -/* $Id: eqn.c,v 1.83 2018/12/14 06:33:14 schwarze Exp $ */ +/* $Id: eqn.c,v 1.84 2020/01/08 12:16:24 schwarze Exp $ */ /* * Copyright (c) 2011, 2014 Kristaps Dzonsons - * Copyright (c) 2014, 2015, 2017, 2018 Ingo Schwarze + * Copyright (c) 2014,2015,2017,2018,2020 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 @@ -399,6 +399,14 @@ eqn_next(struct eqn_node *ep, enum parse_mode mode) case '"': quoted = 1; break; + case ' ': + case '\t': + case '~': + case '^': + if (quoted) + break; + ep->start++; + continue; default: break; } @@ -669,7 +677,7 @@ eqn_parse(struct eqn_node *ep) if (ep->data == NULL) return; - ep->start = ep->end = ep->data + strspn(ep->data, " ^~"); + ep->start = ep->end = ep->data; next_tok: tok = eqn_next(ep, MODE_TOK); diff --git a/html.c b/html.c index 9c45d3162eaf..71c9c711e825 100644 --- a/html.c +++ b/html.c @@ -1,7 +1,7 @@ -/* $Id: html.c,v 1.255 2019/04/30 15:53:00 schwarze Exp $ */ +/* $Id: html.c,v 1.275 2021/09/09 14:47:24 schwarze Exp $ */ /* * Copyright (c) 2008-2011, 2014 Kristaps Dzonsons - * Copyright (c) 2011-2015, 2017-2019 Ingo Schwarze + * Copyright (c) 2011-2015, 2017-2021 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,6 +14,9 @@ * 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. + * + * Common functions for mandoc(1) HTML formatters. + * For use by individual formatters and by the main program. */ #include "config.h" @@ -42,34 +45,30 @@ struct htmldata { const char *name; int flags; -#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_INPHRASE (1 << 0) /* Can appear in phrasing context. */ +#define HTML_TOPHRASE (1 << 1) /* Establishes phrasing context. */ +#define HTML_NOSTACK (1 << 2) /* Does not have an end tag. */ +#define HTML_NLBEFORE (1 << 3) /* Output line break before opening. */ +#define HTML_NLBEGIN (1 << 4) /* Output line break after opening. */ +#define HTML_NLEND (1 << 5) /* Output line break before closing. */ +#define HTML_NLAFTER (1 << 6) /* Output line break after closing. */ #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) +#define HTML_INDENT (1 << 7) /* Indent content by two spaces. */ +#define HTML_NOINDENT (1 << 8) /* Exception: never indent content. */ }; static const struct htmldata htmltags[TAG_MAX] = { {"html", HTML_NLALL}, {"head", HTML_NLALL | HTML_INDENT}, - {"body", HTML_NLALL}, - {"meta", HTML_NOSTACK | HTML_AUTOCLOSE | HTML_NLALL}, + {"meta", HTML_NOSTACK | HTML_NLALL}, + {"link", HTML_NOSTACK | HTML_NLALL}, + {"style", HTML_NLALL | HTML_INDENT}, {"title", HTML_NLAROUND}, + {"body", HTML_NLALL}, {"div", HTML_NLAROUND}, - {"div", 0}, {"section", HTML_NLALL}, - {"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}, {"tr", HTML_NLALL | HTML_INDENT}, {"td", HTML_NLAROUND}, @@ -79,16 +78,22 @@ static const struct htmldata htmltags[TAG_MAX] = { {"dl", HTML_NLALL | HTML_INDENT}, {"dt", HTML_NLAROUND}, {"dd", HTML_NLAROUND | HTML_INDENT}, - {"p", HTML_NLAROUND | HTML_INDENT}, - {"pre", HTML_NLALL | HTML_NOINDENT}, - {"var", 0}, - {"cite", 0}, - {"b", 0}, - {"i", 0}, - {"code", 0}, - {"small", 0}, - {"style", HTML_NLALL | HTML_INDENT}, - {"math", HTML_NLALL | HTML_INDENT}, + {"h1", HTML_TOPHRASE | HTML_NLAROUND}, + {"h2", HTML_TOPHRASE | HTML_NLAROUND}, + {"p", HTML_TOPHRASE | HTML_NLAROUND | HTML_INDENT}, + {"pre", HTML_TOPHRASE | HTML_NLAROUND | HTML_NOINDENT}, + {"a", HTML_INPHRASE | HTML_TOPHRASE}, + {"b", HTML_INPHRASE | HTML_TOPHRASE}, + {"cite", HTML_INPHRASE | HTML_TOPHRASE}, + {"code", HTML_INPHRASE | HTML_TOPHRASE}, + {"i", HTML_INPHRASE | HTML_TOPHRASE}, + {"small", HTML_INPHRASE | HTML_TOPHRASE}, + {"span", HTML_INPHRASE | HTML_TOPHRASE}, + {"var", HTML_INPHRASE | HTML_TOPHRASE}, + {"br", HTML_INPHRASE | HTML_NOSTACK | HTML_NLALL}, + {"hr", HTML_INPHRASE | HTML_NOSTACK}, + {"mark", HTML_INPHRASE }, + {"math", HTML_INPHRASE | HTML_NLALL | HTML_INDENT}, {"mrow", 0}, {"mi", 0}, {"mn", 0}, @@ -108,6 +113,11 @@ static const struct htmldata htmltags[TAG_MAX] = { }; /* Avoid duplicate HTML id= attributes. */ + +struct id_entry { + int ord; /* Ordinal number of the latest occurrence. */ + char id[]; /* The id= attribute without any ordinal suffix. */ +}; static struct ohash id_unique; static void html_reset_internal(struct html *); @@ -131,6 +141,7 @@ html_alloc(const struct manoutput *outopts) h = mandoc_calloc(1, sizeof(struct html)); h->tag = NULL; + h->metac = h->metal = ESCAPE_FONTROMAN; h->style = outopts->style; if ((h->base_man1 = outopts->man) == NULL) h->base_man2 = NULL; @@ -142,7 +153,7 @@ html_alloc(const struct manoutput *outopts) if (outopts->toc) h->oflags |= HTML_TOC; - mandoc_ohash_init(&id_unique, 4, 0); + mandoc_ohash_init(&id_unique, 4, offsetof(struct id_entry, id)); return h; } @@ -151,17 +162,17 @@ static void html_reset_internal(struct html *h) { struct tag *tag; - char *cp; + struct id_entry *entry; unsigned int slot; while ((tag = h->tag) != NULL) { h->tag = tag->next; free(tag); } - cp = ohash_first(&id_unique, &slot); - while (cp != NULL) { - free(cp); - cp = ohash_next(&id_unique, &slot); + entry = ohash_first(&id_unique, &slot); + while (entry != NULL) { + free(entry); + entry = ohash_next(&id_unique, &slot); } ohash_delete(&id_unique); } @@ -170,7 +181,7 @@ void html_reset(void *p) { html_reset_internal(p); - mandoc_ohash_init(&id_unique, 4, 0); + mandoc_ohash_init(&id_unique, 4, offsetof(struct id_entry, id)); } void @@ -186,6 +197,8 @@ print_gen_head(struct html *h) struct tag *t; print_otag(h, TAG_META, "?", "charset", "utf-8"); + print_otag(h, TAG_META, "??", "name", "viewport", + "content", "width=device-width, initial-scale=1.0"); if (h->style != NULL) { print_otag(h, TAG_LINK, "?h??", "rel", "stylesheet", h->style, "type", "text/css", "media", "all"); @@ -203,23 +216,18 @@ print_gen_head(struct html *h) print_endline(h); print_text(h, "td.head-vol { text-align: center; }"); print_endline(h); - print_text(h, "div.Pp { margin: 1ex 0ex; }"); + print_text(h, ".Nd, .Bf, .Op { display: inline; }"); print_endline(h); - print_text(h, "div.Nd, div.Bf, div.Op { display: inline; }"); + print_text(h, ".Pa, .Ad { font-style: italic; }"); print_endline(h); - print_text(h, "span.Pa, span.Ad { font-style: italic; }"); + print_text(h, ".Ms { font-weight: bold; }"); print_endline(h); - print_text(h, "span.Ms { font-weight: bold; }"); - print_endline(h); - print_text(h, "dl.Bl-diag "); + print_text(h, ".Bl-diag "); print_byte(h, '>'); print_text(h, " dt { font-weight: bold; }"); print_endline(h); - print_text(h, "code.Nm, code.Fl, code.Cm, code.Ic, " - "code.In, code.Fd, code.Fn,"); - print_endline(h); - print_text(h, "code.Cd { font-weight: bold; " - "font-family: inherit; }"); + print_text(h, "code.Nm, .Fl, .Cm, .Ic, code.In, .Fd, .Fn, .Cd " + "{ font-weight: bold; font-family: inherit; }"); print_tagq(h, t); } @@ -233,8 +241,10 @@ html_setfont(struct html *h, enum mandoc_esc font) case ESCAPE_FONTITALIC: case ESCAPE_FONTBOLD: case ESCAPE_FONTBI: - case ESCAPE_FONTCW: case ESCAPE_FONTROMAN: + case ESCAPE_FONTCR: + case ESCAPE_FONTCB: + case ESCAPE_FONTCI: break; case ESCAPE_FONT: font = ESCAPE_FONTROMAN; @@ -265,9 +275,17 @@ print_metaf(struct html *h) h->metaf = print_otag(h, TAG_B, ""); print_otag(h, TAG_I, ""); break; - case ESCAPE_FONTCW: + case ESCAPE_FONTCR: h->metaf = print_otag(h, TAG_SPAN, "c", "Li"); break; + case ESCAPE_FONTCB: + h->metaf = print_otag(h, TAG_SPAN, "c", "Li"); + print_otag(h, TAG_B, ""); + break; + case ESCAPE_FONTCI: + h->metaf = print_otag(h, TAG_SPAN, "c", "Li"); + print_otag(h, TAG_I, ""); + break; default: break; } @@ -276,21 +294,18 @@ print_metaf(struct html *h) void html_close_paragraph(struct html *h) { - struct tag *t; + struct tag *this, *next; + int flags; - for (t = h->tag; t != NULL && t->closed == 0; t = t->next) { - switch(t->tag) { - case TAG_P: - case TAG_PRE: - print_tagq(h, t); + this = h->tag; + for (;;) { + next = this->next; + flags = htmltags[this->tag].flags; + if (flags & (HTML_INPHRASE | HTML_TOPHRASE)) + print_ctag(h, this); + if ((flags & HTML_INPHRASE) == 0) break; - case TAG_A: - print_tagq(h, t); - continue; - default: - continue; - } - break; + this = next; } } @@ -328,33 +343,66 @@ html_fillmode(struct html *h, enum roff_tok want) return had; } +/* + * Allocate a string to be used for the "id=" attribute of an HTML + * element and/or as a segment identifier for a URI in an element. + * The function may fail and return NULL if the node lacks text data + * to create the attribute from. + * The caller is responsible for free(3)ing the returned string. + * + * If the "unique" argument is non-zero, the "id_unique" ohash table + * is used for de-duplication. If the "unique" argument is 1, + * it is the first time the function is called for this tag and + * location, so if an ordinal suffix is needed, it is incremented. + * If the "unique" argument is 2, it is the second time the function + * is called for this tag and location, so the ordinal suffix + * remains unchanged. + */ char * html_make_id(const struct roff_node *n, int unique) { const struct roff_node *nch; - char *buf, *bufs, *cp; + struct id_entry *entry; + char *buf, *cp; + size_t len; unsigned int slot; - int suffix; - for (nch = n->child; nch != NULL; nch = nch->next) - if (nch->type != ROFFT_TEXT) - return NULL; - - buf = NULL; - deroff(&buf, n); - if (buf == NULL) - return NULL; + if (n->tag != NULL) + buf = mandoc_strdup(n->tag); + else { + switch (n->tok) { + case MDOC_Sh: + case MDOC_Ss: + case MDOC_Sx: + case MAN_SH: + case MAN_SS: + for (nch = n->child; nch != NULL; nch = nch->next) + if (nch->type != ROFFT_TEXT) + return NULL; + buf = NULL; + deroff(&buf, n); + if (buf == NULL) + return NULL; + break; + default: + if (n->child == NULL || n->child->type != ROFFT_TEXT) + return NULL; + buf = mandoc_strdup(n->child->string); + break; + } + } /* * In ID attributes, only use ASCII characters that are * permitted in URL-fragment strings according to the * explicit list at: * https://url.spec.whatwg.org/#url-fragment-string + * In addition, reserve '~' for ordinal suffixes. */ for (cp = buf; *cp != '\0'; cp++) if (isalnum((unsigned char)*cp) == 0 && - strchr("!$&'()*+,-./:;=?@_~", *cp) == NULL) + strchr("!$&'()*+,-./:;=?@_", *cp) == NULL) *cp = '_'; if (unique == 0) @@ -362,25 +410,21 @@ html_make_id(const struct roff_node *n, int unique) /* Avoid duplicate HTML id= attributes. */ - bufs = NULL; - suffix = 1; slot = ohash_qlookup(&id_unique, buf); - cp = ohash_find(&id_unique, slot); - if (cp != NULL) { - while (cp != NULL) { - free(bufs); - if (++suffix > 127) { - free(buf); - return NULL; - } - mandoc_asprintf(&bufs, "%s_%d", buf, suffix); - slot = ohash_qlookup(&id_unique, bufs); - cp = ohash_find(&id_unique, slot); - } - free(buf); - buf = bufs; + if ((entry = ohash_find(&id_unique, slot)) == NULL) { + len = strlen(buf) + 1; + entry = mandoc_malloc(sizeof(*entry) + len); + entry->ord = 1; + memcpy(entry->id, buf, len); + ohash_insert(&id_unique, slot, entry); + } else if (unique == 1) + entry->ord++; + + if (entry->ord > 1) { + cp = buf; + mandoc_asprintf(&buf, "%s~%d", cp, entry->ord); + free(cp); } - ohash_insert(&id_unique, slot, buf); return buf; } @@ -470,8 +514,10 @@ print_encode(struct html *h, const char *p, const char *pend, int norecurse) case ESCAPE_FONTBOLD: case ESCAPE_FONTITALIC: case ESCAPE_FONTBI: - case ESCAPE_FONTCW: case ESCAPE_FONTROMAN: + case ESCAPE_FONTCR: + case ESCAPE_FONTCB: + case ESCAPE_FONTCI: if (0 == norecurse) { h->flags |= HTML_NOSPACE; if (html_setfont(h, esc)) @@ -589,6 +635,25 @@ print_otag(struct html *h, enum htmltag tag, const char *fmt, ...) tflags = htmltags[tag].flags; + /* Flow content is not allowed in phrasing context. */ + + if ((tflags & HTML_INPHRASE) == 0) { + for (t = h->tag; t != NULL; t = t->next) { + if (t->closed) + continue; + assert((htmltags[t->tag].flags & HTML_TOPHRASE) == 0); + break; + } + + /* + * Always wrap phrasing elements in a paragraph + * unless already contained in some flow container; + * never put them directly into a section. + */ + + } else if (tflags & HTML_TOPHRASE && h->tag->tag == TAG_SECTION) + print_otag(h, TAG_P, "c", "Pp"); + /* Push this tag onto the stack of open scopes. */ if ((tflags & HTML_NOSTACK) == 0) { @@ -706,7 +771,7 @@ print_otag(struct html *h, enum htmltag tag, const char *fmt, ...) /* Accommodate for "well-formed" singleton escaping. */ - if (HTML_AUTOCLOSE & htmltags[tag].flags) + if (htmltags[tag].flags & HTML_NOSTACK) print_byte(h, '/'); print_byte(h, '>'); @@ -724,6 +789,49 @@ print_otag(struct html *h, enum htmltag tag, const char *fmt, ...) return t; } +/* + * Print an element with an optional "id=" attribute. + * If the element has phrasing content and an "id=" attribute, + * also add a permalink: outside if it can be in phrasing context, + * inside otherwise. + */ +struct tag * +print_otag_id(struct html *h, enum htmltag elemtype, const char *cattr, + struct roff_node *n) +{ + struct roff_node *nch; + struct tag *ret, *t; + char *id, *href; + + ret = NULL; + id = href = NULL; + if (n->flags & NODE_ID) + id = html_make_id(n, 1); + if (n->flags & NODE_HREF) + href = id == NULL ? html_make_id(n, 2) : id; + if (href != NULL && htmltags[elemtype].flags & HTML_INPHRASE) + ret = print_otag(h, TAG_A, "chR", "permalink", href); + t = print_otag(h, elemtype, "ci", cattr, id); + if (ret == NULL) { + ret = t; + if (href != NULL && (nch = n->child) != NULL) { + /* man(7) is safe, it tags phrasing content only. */ + if (n->tok > MDOC_MAX || + htmltags[elemtype].flags & HTML_TOPHRASE) + nch = NULL; + else /* For mdoc(7), beware of nested blocks. */ + while (nch != NULL && nch->type == ROFFT_TEXT) + nch = nch->next; + if (nch == NULL) + print_otag(h, TAG_A, "chR", "permalink", href); + } + } + free(id); + if (id == NULL) + free(href); + return ret; +} + static void print_ctag(struct html *h, struct tag *tag) { @@ -793,6 +901,25 @@ print_gen_comment(struct html *h, struct roff_node *n) void print_text(struct html *h, const char *word) { + print_tagged_text(h, word, NULL); +} + +void +print_tagged_text(struct html *h, const char *word, struct roff_node *n) +{ + struct tag *t; + char *href; + + /* + * Always wrap text in a paragraph unless already contained in + * some flow container; never put it directly into a section. + */ + + if (h->tag->tag == TAG_SECTION) + print_otag(h, TAG_P, "c", "Pp"); + + /* Output whitespace before this text? */ + if (h->col && (h->flags & HTML_NOSPACE) == 0) { if ( ! (HTML_KEEP & h->flags)) { if (HTML_PREKEEP & h->flags) @@ -802,9 +929,21 @@ print_text(struct html *h, const char *word) print_word(h, " "); } + /* + * Optionally switch fonts, optionally write a permalink, then + * print the text, optionally surrounded by HTML whitespace. + */ + assert(h->metaf == NULL); print_metaf(h); print_indent(h); + + if (n != NULL && (href = html_make_id(n, 2)) != NULL) { + t = print_otag(h, TAG_A, "chR", "permalink", href); + free(href); + } else + t = NULL; + if ( ! print_encode(h, word, NULL, 0)) { if ( ! (h->flags & HTML_NONOSPACE)) h->flags &= ~HTML_NOSPACE; @@ -815,7 +954,8 @@ print_text(struct html *h, const char *word) if (h->metaf != NULL) { print_tagq(h, h->metaf); h->metaf = NULL; - } + } else if (t != NULL) + print_tagq(h, t); h->flags &= ~HTML_IGNDELIM; } @@ -942,15 +1082,12 @@ print_indent(struct html *h) { size_t i; - if (h->col) + if (h->col || h->noindent) return; - if (h->noindent == 0) { - h->col = h->indent * 2; - for (i = 0; i < h->col; i++) - putchar(' '); - } - h->flags &= ~HTML_NOSPACE; + h->col = h->indent * 2; + for (i = 0; i < h->col; i++) + putchar(' '); } /* diff --git a/html.h b/html.h index 242a63a8d624..3d201403342d 100644 --- a/html.h +++ b/html.h @@ -1,7 +1,7 @@ -/* $Id: html.h,v 1.103 2019/04/30 15:53:00 schwarze Exp $ */ +/* $Id: html.h,v 1.109 2021/09/09 14:47:24 schwarze Exp $ */ /* + * Copyright (c) 2017, 2018, 2019, 2020 Ingo Schwarze * Copyright (c) 2008-2011, 2014 Kristaps Dzonsons - * Copyright (c) 2017, 2018, 2019 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,23 +14,21 @@ * 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 interfaces for mandoc(1) HTML formatters. + * For use by the individual HTML formatters only. */ enum htmltag { TAG_HTML, TAG_HEAD, - TAG_BODY, TAG_META, + TAG_LINK, + TAG_STYLE, TAG_TITLE, + TAG_BODY, TAG_DIV, - TAG_IDIV, TAG_SECTION, - TAG_H1, - TAG_H2, - TAG_SPAN, - TAG_LINK, - TAG_BR, - TAG_A, TAG_TABLE, TAG_TR, TAG_TD, @@ -40,15 +38,21 @@ enum htmltag { TAG_DL, TAG_DT, TAG_DD, + TAG_H1, + TAG_H2, TAG_P, TAG_PRE, - TAG_VAR, - TAG_CITE, + TAG_A, TAG_B, - TAG_I, + TAG_CITE, TAG_CODE, + TAG_I, TAG_SMALL, - TAG_STYLE, + TAG_SPAN, + TAG_VAR, + TAG_BR, + TAG_HR, + TAG_MARK, TAG_MATH, TAG_MROW, TAG_MI, @@ -120,8 +124,12 @@ void print_gen_comment(struct html *, struct roff_node *); void print_gen_decls(struct html *); void print_gen_head(struct html *); struct tag *print_otag(struct html *, enum htmltag, const char *, ...); +struct tag *print_otag_id(struct html *, enum htmltag, const char *, + struct roff_node *); void print_tagq(struct html *, const struct tag *); void print_stagq(struct html *, const struct tag *); +void print_tagged_text(struct html *, const char *, + struct roff_node *); void print_text(struct html *, const char *); void print_tblclose(struct html *); void print_tbl(struct html *, const struct tbl_span *); diff --git a/libmandoc.h b/libmandoc.h index ff6f4692f062..ab7c29be510f 100644 --- a/libmandoc.h +++ b/libmandoc.h @@ -1,7 +1,7 @@ -/* $Id: libmandoc.h,v 1.77 2018/12/21 17:15:18 schwarze Exp $ */ +/* $Id: libmandoc.h,v 1.80 2021/06/27 17:57:54 schwarze Exp $ */ /* + * Copyright (c) 2013-2015,2017,2018,2020 Ingo Schwarze * Copyright (c) 2009, 2010, 2011, 2012 Kristaps Dzonsons - * Copyright (c) 2013,2014,2015,2017,2018 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,6 +14,9 @@ * 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 interfaces for parser utilities needed by multiple parsers + * and the top-level functions to call the mdoc, man, and roff parsers. */ /* @@ -47,8 +50,9 @@ struct buf { struct roff; struct roff_man; +struct roff_node; -char *mandoc_normdate(struct roff_man *, char *, int, int); +char *mandoc_normdate(struct roff_node *, struct roff_node *); int mandoc_eos(const char *, size_t); int mandoc_strntoi(const char *, size_t, int); const char *mandoc_a2msec(const char*); @@ -69,10 +73,10 @@ void roff_reset(struct roff *); void roff_man_free(struct roff_man *); struct roff_man *roff_man_alloc(struct roff *, const char *, int); void roff_man_reset(struct roff_man *); -int roff_parseln(struct roff *, int, struct buf *, int *); +int roff_parseln(struct roff *, int, struct buf *, int *, size_t); void roff_userret(struct roff *); void roff_endparse(struct roff *); -void roff_setreg(struct roff *, const char *, int, char sign); +void roff_setreg(struct roff *, const char *, int, char); int roff_getreg(struct roff *, const char *); char *roff_strdup(const struct roff *, const char *); char *roff_getarg(struct roff *, char **, int, int *); diff --git a/main.c b/main.c index b11f7b51c164..c5a7cff918ab 100644 --- a/main.c +++ b/main.c @@ -1,7 +1,7 @@ -/* $Id: main.c,v 1.332 2019/07/19 20:27:25 schwarze Exp $ */ +/* $Id: main.c,v 1.358 2021/09/04 22:38:46 schwarze Exp $ */ /* + * Copyright (c) 2010-2012, 2014-2021 Ingo Schwarze * Copyright (c) 2008-2012 Kristaps Dzonsons - * Copyright (c) 2010-2012, 2014-2019 Ingo Schwarze * Copyright (c) 2010 Joerg Sonnenberger * * Permission to use, copy, modify, and distribute this software for any @@ -15,6 +15,8 @@ * 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. + * + * Main program for mandoc(1), man(1), apropos(1), whatis(1), and help(1). */ #include "config.h" @@ -32,6 +34,7 @@ #include #include #include +#include #if HAVE_SANDBOX_INIT #include #endif @@ -52,6 +55,7 @@ #include "man.h" #include "mandoc_parse.h" #include "tag.h" +#include "term_tag.h" #include "main.h" #include "manconf.h" #include "mansearch.h" @@ -77,33 +81,39 @@ enum outt { OUTT_PDF /* -Tpdf */ }; -struct curparse { - struct mparse *mp; - struct manoutput *outopts; /* output options */ +struct outstate { + struct tag_files *tag_files; /* Tagging state variables. */ void *outdata; /* data for output */ - char *os_s; /* operating system for display */ + int use_pager; int wstop; /* stop after a file with a warning */ - enum mandoc_os os_e; /* check base system conventions */ + int had_output; /* Some output was generated. */ enum outt outtype; /* which output to use */ }; int mandocdb(int, char *[]); -static void check_xr(void); -static int fs_lookup(const struct manpaths *, - size_t ipath, const char *, - const char *, const char *, +static void check_xr(struct manpaths *); +static void fs_append(char **, size_t, int, + size_t, const char *, enum form, + struct manpage **, size_t *); +static int fs_lookup(const struct manpaths *, size_t, + const char *, const char *, const char *, struct manpage **, size_t *); static int fs_search(const struct mansearch *, - const struct manpaths *, int, char**, + const struct manpaths *, const char *, struct manpage **, size_t *); -static void outdata_alloc(struct curparse *); -static void parse(struct curparse *, int, const char *); +static void glob_esc(char **, const char *, const char *); +static void outdata_alloc(struct outstate *, struct manoutput *); +static void parse(struct mparse *, int, const char *, + struct outstate *, struct manconf *); static void passthrough(int, int); -static pid_t spawn_pager(struct tag_files *); +static void process_onefile(struct mparse *, struct manpage *, + int, struct outstate *, struct manconf *); +static void run_pager(struct outstate *, char *); +static pid_t spawn_pager(struct outstate *, char *); static void usage(enum argmode) __attribute__((__noreturn__)); -static int woptions(struct curparse *, char *); +static int woptions(char *, enum mandoc_os *, int *); static const int sec_prios[] = {1, 4, 5, 8, 6, 3, 7, 2, 9}; static char help_arg[] = "help"; @@ -113,26 +123,31 @@ static char *help_argv[] = {help_arg, NULL}; int main(int argc, char *argv[]) { - struct manconf conf; - struct mansearch search; - struct curparse curp; - struct winsize ws; - struct tag_files *tag_files; - struct manpage *res, *resp; - const char *progname, *sec, *thisarg; - char *conf_file, *defpaths, *auxpaths; - char *oarg, *tagarg; + struct manconf conf; /* Manpaths and output options. */ + struct outstate outst; /* Output state. */ + struct winsize ws; /* Result of ioctl(TIOCGWINSZ). */ + struct mansearch search; /* Search options. */ + struct manpage *res; /* Complete list of search results. */ + struct manpage *resn; /* Search results for one name. */ + struct mparse *mp; /* Opaque parser object. */ + const char *conf_file; /* -C: alternate config file. */ + const char *os_s; /* -I: Operating system for display. */ + const char *progname, *sec, *ep; + char *defpaths; /* -M: override manpaths. */ + char *auxpaths; /* -m: additional manpaths. */ + char *oarg; /* -O: output option string. */ + char *tagarg; /* -O tag: default value. */ unsigned char *uc; - size_t i, sz, ssz; + size_t ressz; /* Number of elements in res[]. */ + size_t resnsz; /* Number of elements in resn[]. */ + size_t i, ib, ssz; + int options; /* Parser options. */ + int show_usage; /* Invalid argument: give up. */ int prio, best_prio; - enum outmode outmode; - int fd, startdir; - int show_usage; - int options; - int use_pager; - int status, signum; + int startdir; int c; - pid_t pager_pid, tc_pgid, man_pgid, pid; + enum mandoc_os os_e; /* Check base system conventions. */ + enum outmode outmode; /* According to command line. */ #if HAVE_PROGNAME progname = getprogname(); @@ -152,7 +167,7 @@ main(int argc, char *argv[]) return mandocdb(argc, argv); #if HAVE_PLEDGE - if (pledge("stdio rpath tmppath tty proc exec", NULL) == -1) { + if (pledge("stdio rpath wpath cpath tmppath tty proc exec", NULL) == -1) { mandoc_msg(MANDOCERR_PLEDGE, 0, 0, "%s", strerror(errno)); return mandoc_msg_getrc(); } @@ -165,8 +180,8 @@ main(int argc, char *argv[]) /* Search options. */ memset(&conf, 0, sizeof(conf)); - conf_file = defpaths = NULL; - auxpaths = NULL; + conf_file = NULL; + defpaths = auxpaths = NULL; memset(&search, 0, sizeof(struct mansearch)); search.outkey = "Nd"; @@ -183,15 +198,19 @@ main(int argc, char *argv[]) else search.argmode = ARG_FILE; - /* Parser and formatter options. */ + /* Parser options. */ - memset(&curp, 0, sizeof(struct curparse)); - curp.outtype = OUTT_LOCALE; - curp.outopts = &conf.output; options = MPARSE_SO | MPARSE_UTF8 | MPARSE_LATIN1; + os_e = MANDOC_OS_OTHER; + os_s = NULL; + + /* Formatter options. */ + + memset(&outst, 0, sizeof(outst)); + outst.tag_files = NULL; + outst.outtype = OUTT_LOCALE; + outst.use_pager = 1; - use_pager = 1; - tag_files = NULL; show_usage = 0; outmode = OUTMODE_DEF; @@ -209,14 +228,14 @@ main(int argc, char *argv[]) conf_file = optarg; break; case 'c': - use_pager = 0; + outst.use_pager = 0; break; case 'f': search.argmode = ARG_WORD; break; case 'h': conf.output.synopsisonly = 1; - use_pager = 0; + outst.use_pager = 0; outmode = OUTMODE_ALL; break; case 'I': @@ -225,12 +244,12 @@ main(int argc, char *argv[]) "-I %s", optarg); return mandoc_msg_getrc(); } - if (curp.os_s != NULL) { + if (os_s != NULL) { mandoc_msg(MANDOCERR_BADARG_DUPE, 0, 0, "-I %s", optarg); return mandoc_msg_getrc(); } - curp.os_s = mandoc_strdup(optarg + 3); + os_s = optarg + 3; break; case 'K': options &= ~(MPARSE_UTF8 | MPARSE_LATIN1); @@ -268,27 +287,27 @@ main(int argc, char *argv[]) break; case 'T': if (strcmp(optarg, "ascii") == 0) - curp.outtype = OUTT_ASCII; + outst.outtype = OUTT_ASCII; else if (strcmp(optarg, "lint") == 0) { - curp.outtype = OUTT_LINT; + outst.outtype = OUTT_LINT; mandoc_msg_setoutfile(stdout); mandoc_msg_setmin(MANDOCERR_BASE); } else if (strcmp(optarg, "tree") == 0) - curp.outtype = OUTT_TREE; + outst.outtype = OUTT_TREE; else if (strcmp(optarg, "man") == 0) - curp.outtype = OUTT_MAN; + outst.outtype = OUTT_MAN; else if (strcmp(optarg, "html") == 0) - curp.outtype = OUTT_HTML; + outst.outtype = OUTT_HTML; else if (strcmp(optarg, "markdown") == 0) - curp.outtype = OUTT_MARKDOWN; + outst.outtype = OUTT_MARKDOWN; else if (strcmp(optarg, "utf8") == 0) - curp.outtype = OUTT_UTF8; + outst.outtype = OUTT_UTF8; else if (strcmp(optarg, "locale") == 0) - curp.outtype = OUTT_LOCALE; + outst.outtype = OUTT_LOCALE; else if (strcmp(optarg, "ps") == 0) - curp.outtype = OUTT_PS; + outst.outtype = OUTT_PS; else if (strcmp(optarg, "pdf") == 0) - curp.outtype = OUTT_PDF; + outst.outtype = OUTT_PDF; else { mandoc_msg(MANDOCERR_BADARG_BAD, 0, 0, "-T %s", optarg); @@ -296,7 +315,7 @@ main(int argc, char *argv[]) } break; case 'W': - if (woptions(&curp, optarg) == -1) + if (woptions(optarg, &os_e, &outst.wstop) == -1) return mandoc_msg_getrc(); break; case 'w': @@ -313,11 +332,12 @@ main(int argc, char *argv[]) /* Postprocess options. */ - if (outmode == OUTMODE_DEF) { + switch (outmode) { + case OUTMODE_DEF: switch (search.argmode) { case ARG_FILE: outmode = OUTMODE_ALL; - use_pager = 0; + outst.use_pager = 0; break; case ARG_NAME: outmode = OUTMODE_ONE; @@ -326,6 +346,16 @@ main(int argc, char *argv[]) outmode = OUTMODE_LST; break; } + break; + case OUTMODE_FLN: + if (search.argmode == ARG_FILE) + outmode = OUTMODE_ALL; + break; + case OUTMODE_ALL: + break; + case OUTMODE_LST: + case OUTMODE_ONE: + abort(); } if (oarg != NULL) { @@ -340,15 +370,17 @@ main(int argc, char *argv[]) } } - if (curp.outtype != OUTT_TREE || !curp.outopts->noval) + if (outst.outtype != OUTT_TREE || conf.output.noval == 0) options |= MPARSE_VALIDATE; if (outmode == OUTMODE_FLN || outmode == OUTMODE_LST || - !isatty(STDOUT_FILENO)) - use_pager = 0; + (conf.output.outfilename == NULL && + conf.output.tagfilename == NULL && + isatty(STDOUT_FILENO) == 0)) + outst.use_pager = 0; - if (use_pager && + if (outst.use_pager && (conf.output.width == 0 || conf.output.indent == 0) && ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) != -1 && ws.ws_col > 1) { @@ -359,12 +391,16 @@ main(int argc, char *argv[]) } #if HAVE_PLEDGE - if (use_pager == 0) { - if (pledge("stdio rpath", NULL) == -1) { - mandoc_msg(MANDOCERR_PLEDGE, 0, 0, - "%s", strerror(errno)); - return mandoc_msg_getrc(); - } + if (outst.use_pager == 0) + c = pledge("stdio rpath", NULL); + else if (conf.output.outfilename != NULL || + conf.output.tagfilename != NULL) + c = pledge("stdio rpath wpath cpath", NULL); + else + c = pledge("stdio rpath tmppath tty proc exec", NULL); + if (c == -1) { + mandoc_msg(MANDOCERR_PLEDGE, 0, 0, "%s", strerror(errno)); + return mandoc_msg_getrc(); } #endif @@ -374,11 +410,10 @@ main(int argc, char *argv[]) argc -= optind; argv += optind; } - resp = NULL; /* - * Quirks for help(1) - * and for a man(1) section argument without -s. + * Quirks for help(1) and man(1), + * in particular for a section argument without -s. */ if (search.argmode == ARG_NAME) { @@ -402,6 +437,8 @@ main(int argc, char *argv[]) if (search.arch == NULL) search.arch = MACHINE; #endif + if (outmode == OUTMODE_ONE) + search.firstmatch = 1; } /* @@ -415,80 +452,78 @@ main(int argc, char *argv[]) conf.output.tag = tagarg == NULL ? *argv : tagarg + 1; } - /* man(1), whatis(1), apropos(1) */ - - if (search.argmode != ARG_FILE) { - if (search.argmode == ARG_NAME && - outmode == OUTMODE_ONE) - search.firstmatch = 1; - - /* Access the mandoc database. */ + /* Read the configuration file. */ + if (search.argmode != ARG_FILE || + mandoc_msg_getmin() == MANDOCERR_STYLE) manconf_parse(&conf, conf_file, defpaths, auxpaths); - if ( ! mansearch(&search, &conf.manpath, - argc, argv, &res, &sz)) - usage(search.argmode); - if (sz == 0 && search.argmode == ARG_NAME) - (void)fs_search(&search, &conf.manpath, - argc, argv, &res, &sz); + /* man(1): Resolve each name individually. */ - if (search.argmode == ARG_NAME) { - for (c = 0; c < argc; c++) { - if (strchr(argv[c], '/') == NULL) - continue; - if (access(argv[c], R_OK) == -1) { - mandoc_msg_setinfilename(argv[c]); + if (search.argmode == ARG_NAME) { + if (argc < 1) { + if (outmode != OUTMODE_FLN) + usage(ARG_NAME); + if (conf.manpath.sz == 0) { + warnx("The manpath is empty."); + mandoc_msg_setrc(MANDOCLEVEL_BADARG); + } else { + for (i = 0; i + 1 < conf.manpath.sz; i++) + printf("%s:", conf.manpath.paths[i]); + printf("%s\n", conf.manpath.paths[i]); + } + manconf_free(&conf); + return (int)mandoc_msg_getrc(); + } + for (res = NULL, ressz = 0; argc > 0; argc--, argv++) { + (void)mansearch(&search, &conf.manpath, + 1, argv, &resn, &resnsz); + if (resnsz == 0) + (void)fs_search(&search, &conf.manpath, + *argv, &resn, &resnsz); + if (resnsz == 0 && strchr(*argv, '/') == NULL) { + if (search.arch != NULL && + arch_valid(search.arch, OSENUM) == 0) + warnx("Unknown architecture \"%s\".", + search.arch); + else if (search.sec != NULL) + warnx("No entry for %s in " + "section %s of the manual.", + *argv, search.sec); + else + warnx("No entry for %s in " + "the manual.", *argv); + mandoc_msg_setrc(MANDOCLEVEL_BADARG); + continue; + } + if (resnsz == 0) { + if (access(*argv, R_OK) == -1) { + mandoc_msg_setinfilename(*argv); mandoc_msg(MANDOCERR_BADARG_BAD, 0, 0, "%s", strerror(errno)); mandoc_msg_setinfilename(NULL); continue; } + resnsz = 1; + resn = mandoc_calloc(resnsz, sizeof(*res)); + resn->file = mandoc_strdup(*argv); + resn->ipath = SIZE_MAX; + resn->form = FORM_SRC; + } + if (outmode != OUTMODE_ONE || resnsz == 1) { res = mandoc_reallocarray(res, - sz + 1, sizeof(*res)); - res[sz].file = mandoc_strdup(argv[c]); - res[sz].names = NULL; - res[sz].output = NULL; - res[sz].bits = 0; - res[sz].ipath = SIZE_MAX; - res[sz].sec = 10; - res[sz].form = FORM_SRC; - sz++; + ressz + resnsz, sizeof(*res)); + memcpy(res + ressz, resn, + sizeof(*resn) * resnsz); + ressz += resnsz; + continue; } - } - if (sz == 0) { - if (search.argmode != ARG_NAME) - warnx("nothing appropriate"); - mandoc_msg_setrc(MANDOCLEVEL_BADARG); - goto out; - } + /* Search for the best section. */ - /* - * For standard man(1) and -a output mode, - * prepare for copying filename pointers - * into the program parameter array. - */ - - if (outmode == OUTMODE_ONE) { - argc = 1; best_prio = 40; - } else if (outmode == OUTMODE_ALL) - argc = (int)sz; - - /* Iterate all matching manuals. */ - - resp = res; - for (i = 0; i < sz; i++) { - if (outmode == OUTMODE_FLN) - puts(res[i].file); - else if (outmode == OUTMODE_LST) - printf("%s - %s\n", res[i].names, - res[i].output == NULL ? "" : - res[i].output); - else if (outmode == OUTMODE_ONE) { - /* Search for the best section. */ - sec = res[i].file; + for (ib = i = 0; i < resnsz; i++) { + sec = resn[i].file; sec += strcspn(sec, "123456789"); if (sec[0] == '\0') continue; /* No section at all. */ @@ -501,45 +536,66 @@ main(int argc, char *argv[]) sec++; /* Prefer without suffix. */ if (*sec != '/') prio += 10; /* Wrong dir name. */ - if (search.sec != NULL && - (strlen(sec) <= ssz + 3 || - strcmp(sec + strlen(sec) - ssz, - search.sec) != 0)) - prio += 20; /* Wrong file ext. */ + if (search.sec != NULL) { + ep = strchr(sec, '\0'); + if (ep - sec > 3 && + strncmp(ep - 3, ".gz", 3) == 0) + ep -= 3; + if ((size_t)(ep - sec) < ssz + 3 || + strncmp(ep - ssz, search.sec, + ssz) != 0) /* Wrong file */ + prio += 20; /* extension. */ + } if (prio >= best_prio) continue; best_prio = prio; - resp = res + i; + ib = i; } + res = mandoc_reallocarray(res, ressz + 1, + sizeof(*res)); + memcpy(res + ressz++, resn + ib, sizeof(*resn)); } - /* - * For man(1), -a and -i output mode, fall through - * to the main mandoc(1) code iterating files - * and running the parsers on each of them. - */ + /* apropos(1), whatis(1): Process the full search expression. */ + + } else if (search.argmode != ARG_FILE) { + if (mansearch(&search, &conf.manpath, + argc, argv, &res, &ressz) == 0) + usage(search.argmode); - if (outmode == OUTMODE_FLN || outmode == OUTMODE_LST) + if (ressz == 0) { + warnx("nothing appropriate"); + mandoc_msg_setrc(MANDOCLEVEL_BADARG); goto out; - } + } - /* mandoc(1) */ + /* mandoc(1): Take command line arguments as file names. */ -#if HAVE_PLEDGE - if (use_pager) { - if (pledge("stdio rpath tmppath tty proc exec", NULL) == -1) { - mandoc_msg(MANDOCERR_PLEDGE, 0, 0, - "%s", strerror(errno)); - return mandoc_msg_getrc(); - } } else { - if (pledge("stdio rpath", NULL) == -1) { - mandoc_msg(MANDOCERR_PLEDGE, 0, 0, - "%s", strerror(errno)); - return mandoc_msg_getrc(); + ressz = argc > 0 ? argc : 1; + res = mandoc_calloc(ressz, sizeof(*res)); + for (i = 0; i < ressz; i++) { + if (argc > 0) + res[i].file = mandoc_strdup(argv[i]); + res[i].ipath = SIZE_MAX; + res[i].form = FORM_SRC; } } -#endif + + switch (outmode) { + case OUTMODE_FLN: + for (i = 0; i < ressz; i++) + puts(res[i].file); + goto out; + case OUTMODE_LST: + for (i = 0; i < ressz; i++) + printf("%s - %s\n", res[i].names, + res[i].output == NULL ? "" : + res[i].output); + goto out; + default: + break; + } if (search.argmode == ARG_FILE && auxpaths != NULL) { if (strcmp(auxpaths, "doc") == 0) @@ -549,19 +605,7 @@ main(int argc, char *argv[]) } mchars_alloc(); - curp.mp = mparse_alloc(options, curp.os_e, curp.os_s); - - if (argc < 1) { - if (use_pager) { - tag_files = tag_init(); - if (tag_files != NULL) - tag_files->tagname = conf.output.tag; - } - thisarg = ""; - mandoc_msg_setinfilename(thisarg); - parse(&curp, STDIN_FILENO, thisarg); - mandoc_msg_setinfilename(NULL); - } + mp = mparse_alloc(options, os_e, os_s); /* * Remember the original working directory, if possible. @@ -571,164 +615,53 @@ main(int argc, char *argv[]) * readable: Maybe it won't be needed after all. */ startdir = open(".", O_RDONLY | O_DIRECTORY); - - while (argc > 0) { - - /* - * Changing directories is not needed in ARG_FILE mode. - * Do it on a best-effort basis. Even in case of - * failure, some functionality may still work. - */ - if (resp != NULL) { - if (resp->ipath != SIZE_MAX) - (void)chdir(conf.manpath.paths[resp->ipath]); - else if (startdir != -1) - (void)fchdir(startdir); - thisarg = resp->file; - } else - thisarg = *argv; - - mandoc_msg_setinfilename(thisarg); - fd = mparse_open(curp.mp, thisarg); - if (fd != -1) { - if (use_pager) { - use_pager = 0; - tag_files = tag_init(); - if (tag_files != NULL) - tag_files->tagname = conf.output.tag; - } - - if (resp == NULL || resp->form == FORM_SRC) - parse(&curp, fd, thisarg); - else - passthrough(fd, conf.output.synopsisonly); - - if (ferror(stdout)) { - if (tag_files != NULL) { - mandoc_msg(MANDOCERR_WRITE, 0, 0, - "%s: %s", tag_files->ofn, - strerror(errno)); - tag_unlink(); - tag_files = NULL; - } else - mandoc_msg(MANDOCERR_WRITE, 0, 0, - "%s", strerror(errno)); - break; - } - - if (argc > 1 && curp.outtype <= OUTT_UTF8) { - if (curp.outdata == NULL) - outdata_alloc(&curp); - terminal_sepline(curp.outdata); - } - } else - mandoc_msg(resp == NULL ? MANDOCERR_BADARG_BAD : - MANDOCERR_OPEN, 0, 0, "%s", strerror(errno)); - - mandoc_msg_setinfilename(NULL); - - if (curp.wstop && mandoc_msg_getrc() != MANDOCLEVEL_OK) + for (i = 0; i < ressz; i++) { + process_onefile(mp, res + i, startdir, &outst, &conf); + if (outst.wstop && mandoc_msg_getrc() != MANDOCLEVEL_OK) break; - - if (resp != NULL) - resp++; - else - argv++; - if (--argc) - mparse_reset(curp.mp); } if (startdir != -1) { (void)fchdir(startdir); close(startdir); } - - if (curp.outdata != NULL) { - switch (curp.outtype) { + if (conf.output.tag != NULL && conf.output.tag_found == 0) { + mandoc_msg(MANDOCERR_TAG, 0, 0, "%s", conf.output.tag); + conf.output.tag = NULL; + } + if (outst.outdata != NULL) { + switch (outst.outtype) { case OUTT_HTML: - html_free(curp.outdata); + html_free(outst.outdata); break; case OUTT_UTF8: case OUTT_LOCALE: case OUTT_ASCII: - ascii_free(curp.outdata); + ascii_free(outst.outdata); break; case OUTT_PDF: case OUTT_PS: - pspdf_free(curp.outdata); + pspdf_free(outst.outdata); break; default: break; } } mandoc_xr_free(); - mparse_free(curp.mp); + mparse_free(mp); mchars_free(); out: - if (search.argmode != ARG_FILE) { + mansearch_free(res, ressz); + if (search.argmode != ARG_FILE) manconf_free(&conf); - mansearch_free(res, sz); - } - - free(curp.os_s); - - /* - * When using a pager, finish writing both temporary files, - * fork it, wait for the user to close it, and clean up. - */ - - if (tag_files != NULL) { - fclose(stdout); - tag_write(); - man_pgid = getpgid(0); - tag_files->tcpgid = man_pgid == getpid() ? - getpgid(getppid()) : man_pgid; - pager_pid = 0; - signum = SIGSTOP; - for (;;) { - - /* Stop here until moved to the foreground. */ - - tc_pgid = tcgetpgrp(tag_files->ofd); - if (tc_pgid != man_pgid) { - if (tc_pgid == pager_pid) { - (void)tcsetpgrp(tag_files->ofd, - man_pgid); - if (signum == SIGTTIN) - continue; - } else - tag_files->tcpgid = tc_pgid; - kill(0, signum); - continue; - } - - /* Once in the foreground, activate the pager. */ - - if (pager_pid) { - (void)tcsetpgrp(tag_files->ofd, pager_pid); - kill(pager_pid, SIGCONT); - } else - pager_pid = spawn_pager(tag_files); - - /* Wait for the pager to stop or exit. */ - while ((pid = waitpid(pager_pid, &status, - WUNTRACED)) == -1 && errno == EINTR) - continue; - - if (pid == -1) { - mandoc_msg(MANDOCERR_WAIT, 0, 0, - "%s", strerror(errno)); - break; - } - if (!WIFSTOPPED(status)) - break; - - signum = WSTOPSIG(status); - } - tag_unlink(); - } else if (curp.outtype != OUTT_LINT && - (search.argmode == ARG_FILE || sz > 0)) + if (outst.tag_files != NULL) { + if (term_tag_close() != -1 && + conf.output.outfilename == NULL && + conf.output.tagfilename == NULL) + run_pager(&outst, conf.output.tag); + term_tag_unlink(); + } else if (outst.had_output && outst.outtype != OUTT_LINT) mandoc_msg_summary(); return (int)mandoc_msg_getrc(); @@ -762,6 +695,42 @@ usage(enum argmode argmode) exit((int)MANDOCLEVEL_BADARG); } +static void +glob_esc(char **dst, const char *src, const char *suffix) +{ + while (*src != '\0') { + if (strchr("*?[", *src) != NULL) + *(*dst)++ = '\\'; + *(*dst)++ = *src++; + } + while (*suffix != '\0') + *(*dst)++ = *suffix++; +} + +static void +fs_append(char **file, size_t filesz, int copy, size_t ipath, + const char *sec, enum form form, struct manpage **res, size_t *ressz) +{ + struct manpage *page; + + *res = mandoc_reallocarray(*res, *ressz + filesz, sizeof(**res)); + page = *res + *ressz; + *ressz += filesz; + for (;;) { + page->file = copy ? mandoc_strdup(*file) : *file; + page->names = NULL; + page->output = NULL; + page->bits = NAME_FILE & NAME_MASK; + page->ipath = ipath; + page->sec = (*sec >= '1' && *sec <= '9') ? *sec - '1' + 1 : 10; + page->form = form; + if (--filesz == 0) + break; + file++; + page++; + } +} + static int fs_lookup(const struct manpaths *paths, size_t ipath, const char *sec, const char *arch, const char *name, @@ -769,12 +738,19 @@ fs_lookup(const struct manpaths *paths, size_t ipath, { struct stat sb; glob_t globinfo; - struct manpage *page; - char *file; + char *file, *cp, secnum[2]; int globres; enum form form; + const char *const slman = "/man"; + const char *const slash = "/"; + const char *const sglob = ".[01-9]*"; + const char *const dot = "."; + const char *const aster = "*"; + + memset(&globinfo, 0, sizeof(globinfo)); form = FORM_SRC; + mandoc_asprintf(&file, "%s/man%s/%s.%s", paths->paths[ipath], sec, name, sec); if (stat(file, &sb) != -1) @@ -797,21 +773,46 @@ fs_lookup(const struct manpaths *paths, size_t ipath, free(file); } - mandoc_asprintf(&file, "%s/man%s/%s.[01-9]*", - paths->paths[ipath], sec, name); + cp = file = mandoc_malloc(strlen(paths->paths[ipath]) * 2 + + strlen(slman) + strlen(sec) * 2 + strlen(slash) + + strlen(name) * 2 + strlen(sglob) + 1); + glob_esc(&cp, paths->paths[ipath], slman); + glob_esc(&cp, sec, slash); + glob_esc(&cp, name, sglob); + *cp = '\0'; globres = glob(file, 0, NULL, &globinfo); if (globres != 0 && globres != GLOB_NOMATCH) mandoc_msg(MANDOCERR_GLOB, 0, 0, "%s: %s", file, strerror(errno)); free(file); + file = NULL; if (globres == 0) - file = mandoc_strdup(*globinfo.gl_pathv); + goto found; globfree(&globinfo); - if (globres == 0) { - if (stat(file, &sb) != -1) - goto found; + + if (sec[1] != '\0' && *ressz == 0) { + secnum[0] = sec[0]; + secnum[1] = '\0'; + cp = file = mandoc_malloc(strlen(paths->paths[ipath]) * 2 + + strlen(slman) + strlen(secnum) * 2 + strlen(slash) + + strlen(name) * 2 + strlen(dot) + + strlen(sec) * 2 + strlen(aster) + 1); + glob_esc(&cp, paths->paths[ipath], slman); + glob_esc(&cp, secnum, slash); + glob_esc(&cp, name, dot); + glob_esc(&cp, sec, aster); + *cp = '\0'; + globres = glob(file, 0, NULL, &globinfo); + if (globres != 0 && globres != GLOB_NOMATCH) + mandoc_msg(MANDOCERR_GLOB, 0, 0, + "%s: %s", file, strerror(errno)); free(file); + file = NULL; + if (globres == 0) + goto found; + globfree(&globinfo); } + if (res != NULL || ipath + 1 != paths->sz) return -1; @@ -823,81 +824,134 @@ fs_lookup(const struct manpaths *paths, size_t ipath, found: warnx("outdated mandoc.db lacks %s(%s) entry, run %s %s", name, sec, BINM_MAKEWHATIS, paths->paths[ipath]); - if (res == NULL) { + if (res == NULL) free(file); - return 0; - } - *res = mandoc_reallocarray(*res, ++*ressz, sizeof(**res)); - page = *res + (*ressz - 1); - page->file = file; - page->names = NULL; - page->output = NULL; - page->bits = NAME_FILE & NAME_MASK; - page->ipath = ipath; - page->sec = (*sec >= '1' && *sec <= '9') ? *sec - '1' + 1 : 10; - page->form = form; + else if (file == NULL) + fs_append(globinfo.gl_pathv, globinfo.gl_pathc, 1, + ipath, sec, form, res, ressz); + else + fs_append(&file, 1, 0, ipath, sec, form, res, ressz); + globfree(&globinfo); return 0; } static int fs_search(const struct mansearch *cfg, const struct manpaths *paths, - int argc, char **argv, struct manpage **res, size_t *ressz) + const char *name, struct manpage **res, size_t *ressz) { const char *const sections[] = {"1", "8", "6", "2", "3", "5", "7", "4", "9", "3p"}; const size_t nsec = sizeof(sections)/sizeof(sections[0]); - size_t ipath, isec, lastsz; + size_t ipath, isec; assert(cfg->argmode == ARG_NAME); - if (res != NULL) *res = NULL; - *ressz = lastsz = 0; - while (argc) { - for (ipath = 0; ipath < paths->sz; ipath++) { - if (cfg->sec != NULL) { - if (fs_lookup(paths, ipath, cfg->sec, - cfg->arch, *argv, res, ressz) != -1 && - cfg->firstmatch) - return 0; - } else for (isec = 0; isec < nsec; isec++) + *ressz = 0; + for (ipath = 0; ipath < paths->sz; ipath++) { + if (cfg->sec != NULL) { + if (fs_lookup(paths, ipath, cfg->sec, cfg->arch, + name, res, ressz) != -1 && cfg->firstmatch) + return 0; + } else { + for (isec = 0; isec < nsec; isec++) if (fs_lookup(paths, ipath, sections[isec], - cfg->arch, *argv, res, ressz) != -1 && + cfg->arch, name, res, ressz) != -1 && cfg->firstmatch) return 0; } - if (res != NULL && *ressz == lastsz && - strchr(*argv, '/') == NULL) { - if (cfg->arch != NULL && - arch_valid(cfg->arch, OSENUM) == 0) - warnx("Unknown architecture \"%s\".", - cfg->arch); - else if (cfg->sec == NULL) - warnx("No entry for %s in the manual.", - *argv); - else - warnx("No entry for %s in section %s " - "of the manual.", *argv, cfg->sec); - } - lastsz = *ressz; - argv++; - argc--; } return -1; } static void -parse(struct curparse *curp, int fd, const char *file) +process_onefile(struct mparse *mp, struct manpage *resp, int startdir, + struct outstate *outst, struct manconf *conf) { - struct roff_meta *meta; + int fd; + + /* + * Changing directories is not needed in ARG_FILE mode. + * Do it on a best-effort basis. Even in case of + * failure, some functionality may still work. + */ + if (resp->ipath != SIZE_MAX) + (void)chdir(conf->manpath.paths[resp->ipath]); + else if (startdir != -1) + (void)fchdir(startdir); + + mandoc_msg_setinfilename(resp->file); + if (resp->file != NULL) { + if ((fd = mparse_open(mp, resp->file)) == -1) { + mandoc_msg(resp->ipath == SIZE_MAX ? + MANDOCERR_BADARG_BAD : MANDOCERR_OPEN, + 0, 0, "%s", strerror(errno)); + mandoc_msg_setinfilename(NULL); + return; + } + } else + fd = STDIN_FILENO; + + if (outst->use_pager) { + outst->use_pager = 0; + outst->tag_files = term_tag_init(conf->output.outfilename, + outst->outtype == OUTT_HTML ? ".html" : "", + conf->output.tagfilename); +#if HAVE_PLEDGE + if ((conf->output.outfilename != NULL || + conf->output.tagfilename != NULL) && + pledge("stdio rpath cpath", NULL) == -1) { + mandoc_msg(MANDOCERR_PLEDGE, 0, 0, + "%s", strerror(errno)); + exit(mandoc_msg_getrc()); + } +#endif + } + if (outst->had_output && outst->outtype <= OUTT_UTF8) { + if (outst->outdata == NULL) + outdata_alloc(outst, &conf->output); + terminal_sepline(outst->outdata); + } - /* Begin by parsing the file itself. */ + if (resp->form == FORM_SRC) + parse(mp, fd, resp->file, outst, conf); + else { + passthrough(fd, conf->output.synopsisonly); + outst->had_output = 1; + } + + if (ferror(stdout)) { + if (outst->tag_files != NULL) { + mandoc_msg(MANDOCERR_WRITE, 0, 0, "%s: %s", + outst->tag_files->ofn, strerror(errno)); + term_tag_unlink(); + outst->tag_files = NULL; + } else + mandoc_msg(MANDOCERR_WRITE, 0, 0, "%s", + strerror(errno)); + } + mandoc_msg_setinfilename(NULL); +} + +static void +parse(struct mparse *mp, int fd, const char *file, + struct outstate *outst, struct manconf *conf) +{ + static struct manpaths basepaths; + static int previous; + struct roff_meta *meta; - assert(file); assert(fd >= 0); + if (file == NULL) + file = ""; - mparse_readfd(curp->mp, fd, file); + if (previous) + mparse_reset(mp); + else + previous = 1; + + mparse_readfd(mp, fd, file); if (fd != STDIN_FILENO) close(fd); @@ -906,81 +960,89 @@ parse(struct curparse *curp, int fd, const char *file) * level, do not produce output. */ - if (curp->wstop && mandoc_msg_getrc() != MANDOCLEVEL_OK) + if (outst->wstop && mandoc_msg_getrc() != MANDOCLEVEL_OK) return; - if (curp->outdata == NULL) - outdata_alloc(curp); - else if (curp->outtype == OUTT_HTML) - html_reset(curp); + if (outst->outdata == NULL) + outdata_alloc(outst, &conf->output); + else if (outst->outtype == OUTT_HTML) + html_reset(outst->outdata); mandoc_xr_reset(); - meta = mparse_result(curp->mp); + meta = mparse_result(mp); /* Execute the out device, if it exists. */ + outst->had_output = 1; if (meta->macroset == MACROSET_MDOC) { - switch (curp->outtype) { + switch (outst->outtype) { case OUTT_HTML: - html_mdoc(curp->outdata, meta); + html_mdoc(outst->outdata, meta); break; case OUTT_TREE: - tree_mdoc(curp->outdata, meta); + tree_mdoc(outst->outdata, meta); break; case OUTT_MAN: - man_mdoc(curp->outdata, meta); + man_mdoc(outst->outdata, meta); break; case OUTT_PDF: case OUTT_ASCII: case OUTT_UTF8: case OUTT_LOCALE: case OUTT_PS: - terminal_mdoc(curp->outdata, meta); + terminal_mdoc(outst->outdata, meta); break; case OUTT_MARKDOWN: - markdown_mdoc(curp->outdata, meta); + markdown_mdoc(outst->outdata, meta); break; default: break; } } if (meta->macroset == MACROSET_MAN) { - switch (curp->outtype) { + switch (outst->outtype) { case OUTT_HTML: - html_man(curp->outdata, meta); + html_man(outst->outdata, meta); break; case OUTT_TREE: - tree_man(curp->outdata, meta); + tree_man(outst->outdata, meta); break; case OUTT_MAN: - mparse_copy(curp->mp); + mparse_copy(mp); break; case OUTT_PDF: case OUTT_ASCII: case OUTT_UTF8: case OUTT_LOCALE: case OUTT_PS: - terminal_man(curp->outdata, meta); + terminal_man(outst->outdata, meta); + break; + case OUTT_MARKDOWN: + mandoc_msg(MANDOCERR_MAN_TMARKDOWN, 0, 0, NULL); break; default: break; } } - if (mandoc_msg_getmin() < MANDOCERR_STYLE) - check_xr(); + if (conf->output.tag != NULL && conf->output.tag_found == 0 && + tag_exists(conf->output.tag)) + conf->output.tag_found = 1; + + if (mandoc_msg_getmin() < MANDOCERR_STYLE) { + if (basepaths.sz == 0) + manpath_base(&basepaths); + check_xr(&basepaths); + } else if (mandoc_msg_getmin() < MANDOCERR_WARNING) + check_xr(&conf->manpath); } static void -check_xr(void) +check_xr(struct manpaths *paths) { - static struct manpaths paths; struct mansearch search; struct mandoc_xr *xr; size_t sz; - if (paths.sz == 0) - manpath_base(&paths); - for (xr = mandoc_xr_get(); xr != NULL; xr = xr->next) { if (xr->line == -1) continue; @@ -989,9 +1051,9 @@ check_xr(void) search.outkey = NULL; search.argmode = ARG_NAME; search.firstmatch = 1; - if (mansearch(&search, &paths, 1, &xr->name, NULL, &sz)) + if (mansearch(&search, paths, 1, &xr->name, NULL, &sz)) continue; - if (fs_search(&search, &paths, 1, &xr->name, NULL, &sz) != -1) + if (fs_search(&search, paths, xr->name, NULL, &sz) != -1) continue; if (xr->count == 1) mandoc_msg(MANDOCERR_XR_BAD, xr->line, @@ -1004,26 +1066,26 @@ check_xr(void) } static void -outdata_alloc(struct curparse *curp) +outdata_alloc(struct outstate *outst, struct manoutput *outconf) { - switch (curp->outtype) { + switch (outst->outtype) { case OUTT_HTML: - curp->outdata = html_alloc(curp->outopts); + outst->outdata = html_alloc(outconf); break; case OUTT_UTF8: - curp->outdata = utf8_alloc(curp->outopts); + outst->outdata = utf8_alloc(outconf); break; case OUTT_LOCALE: - curp->outdata = locale_alloc(curp->outopts); + outst->outdata = locale_alloc(outconf); break; case OUTT_ASCII: - curp->outdata = ascii_alloc(curp->outopts); + outst->outdata = ascii_alloc(outconf); break; case OUTT_PDF: - curp->outdata = pdf_alloc(curp->outopts); + outst->outdata = pdf_alloc(outconf); break; case OUTT_PS: - curp->outdata = ps_alloc(curp->outopts); + outst->outdata = ps_alloc(outconf); break; default: break; @@ -1093,7 +1155,7 @@ done: } static int -woptions(struct curparse *curp, char *arg) +woptions(char *arg, enum mandoc_os *os_e, int *wstop) { char *v, *o; const char *toks[11]; @@ -1114,7 +1176,7 @@ woptions(struct curparse *curp, char *arg) o = arg; switch (getsubopt(&arg, (char * const *)toks, &v)) { case 0: - curp->wstop = 1; + *wstop = 1; break; case 1: case 2: @@ -1137,11 +1199,11 @@ woptions(struct curparse *curp, char *arg) break; case 8: mandoc_msg_setmin(MANDOCERR_BASE); - curp->os_e = MANDOC_OS_OPENBSD; + *os_e = MANDOC_OS_OPENBSD; break; case 9: mandoc_msg_setmin(MANDOCERR_BASE); - curp->os_e = MANDOC_OS_NETBSD; + *os_e = MANDOC_OS_NETBSD; break; default: mandoc_msg(MANDOCERR_BADARG_BAD, 0, 0, "-W %s", o); @@ -1151,8 +1213,66 @@ woptions(struct curparse *curp, char *arg) return 0; } +/* + * Wait until moved to the foreground, + * then fork the pager and wait for the user to close it. + */ +static void +run_pager(struct outstate *outst, char *tag_target) +{ + int signum, status; + pid_t man_pgid, tc_pgid; + pid_t pager_pid, wait_pid; + + man_pgid = getpgid(0); + outst->tag_files->tcpgid = + man_pgid == getpid() ? getpgid(getppid()) : man_pgid; + pager_pid = 0; + signum = SIGSTOP; + + for (;;) { + /* Stop here until moved to the foreground. */ + + tc_pgid = tcgetpgrp(STDOUT_FILENO); + if (tc_pgid != man_pgid) { + if (tc_pgid == pager_pid) { + (void)tcsetpgrp(STDOUT_FILENO, man_pgid); + if (signum == SIGTTIN) + continue; + } else + outst->tag_files->tcpgid = tc_pgid; + kill(0, signum); + continue; + } + + /* Once in the foreground, activate the pager. */ + + if (pager_pid) { + (void)tcsetpgrp(STDOUT_FILENO, pager_pid); + kill(pager_pid, SIGCONT); + } else + pager_pid = spawn_pager(outst, tag_target); + + /* Wait for the pager to stop or exit. */ + + while ((wait_pid = waitpid(pager_pid, &status, + WUNTRACED)) == -1 && errno == EINTR) + continue; + + if (wait_pid == -1) { + mandoc_msg(MANDOCERR_WAIT, 0, 0, + "%s", strerror(errno)); + break; + } + if (!WIFSTOPPED(status)) + break; + + signum = WSTOPSIG(status); + } +} + static pid_t -spawn_pager(struct tag_files *tag_files) +spawn_pager(struct outstate *outst, char *tag_target) { const struct timespec timeout = { 0, 100000000 }; /* 0.1s */ #define MAX_PAGER_ARGS 16 @@ -1165,11 +1285,14 @@ spawn_pager(struct tag_files *tag_files) int argc, use_ofn; pid_t pager_pid; + assert(outst->tag_files->ofd == -1); + assert(outst->tag_files->tfs == NULL); + pager = getenv("MANPAGER"); if (pager == NULL || *pager == '\0') pager = getenv("PAGER"); if (pager == NULL || *pager == '\0') - pager = "more -s"; + pager = BINM_PAGER; cp = mandoc_strdup(pager); /* @@ -1194,21 +1317,28 @@ spawn_pager(struct tag_files *tag_files) use_ofn = 1; #if HAVE_LESS_T - if (*tag_files->tfn != '\0' && (cmdlen = strlen(argv[0])) >= 4) { + if (*outst->tag_files->tfn != '\0' && + (cmdlen = strlen(argv[0])) >= 4) { cp = argv[0] + cmdlen - 4; if (strcmp(cp, "less") == 0) { argv[argc++] = mandoc_strdup("-T"); - argv[argc++] = tag_files->tfn; - if (tag_files->tagname != NULL) { + argv[argc++] = outst->tag_files->tfn; + if (tag_target != NULL) { argv[argc++] = mandoc_strdup("-t"); - argv[argc++] = tag_files->tagname; + argv[argc++] = tag_target; use_ofn = 0; } } } #endif - if (use_ofn) - argv[argc++] = tag_files->ofn; + if (use_ofn) { + if (outst->outtype == OUTT_HTML && tag_target != NULL) + mandoc_asprintf(&argv[argc], "file://%s#%s", + outst->tag_files->ofn, tag_target); + else + argv[argc] = outst->tag_files->ofn; + argc++; + } argv[argc] = NULL; switch (pager_pid = fork()) { @@ -1219,7 +1349,7 @@ spawn_pager(struct tag_files *tag_files) break; default: (void)setpgid(pager_pid, 0); - (void)tcsetpgrp(tag_files->ofd, pager_pid); + (void)tcsetpgrp(STDOUT_FILENO, pager_pid); #if HAVE_PLEDGE if (pledge("stdio rpath tmppath tty proc", NULL) == -1) { mandoc_msg(MANDOCERR_PLEDGE, 0, 0, @@ -1227,20 +1357,14 @@ spawn_pager(struct tag_files *tag_files) exit(mandoc_msg_getrc()); } #endif - tag_files->pager_pid = pager_pid; + outst->tag_files->pager_pid = pager_pid; return pager_pid; } - /* The child process becomes the pager. */ - - if (dup2(tag_files->ofd, STDOUT_FILENO) == -1) { - mandoc_msg(MANDOCERR_DUP, 0, 0, "%s", strerror(errno)); - _exit(mandoc_msg_getrc()); - } - close(tag_files->ofd); - assert(tag_files->tfd == -1); - - /* Do not start the pager before controlling the terminal. */ + /* + * The child process becomes the pager. + * Do not start it before controlling the terminal. + */ while (tcgetpgrp(STDOUT_FILENO) != getpid()) nanosleep(&timeout, NULL); diff --git a/man.1 b/man.1 index 0d2976bab795..d3a54c6a235c 100644 --- a/man.1 +++ b/man.1 @@ -1,9 +1,9 @@ -.\" $Id: man.1,v 1.35 2019/03/09 15:55:01 schwarze Exp $ +.\" $Id: man.1,v 1.40 2020/07/20 16:57:30 schwarze Exp $ .\" .\" Copyright (c) 1989, 1990, 1993 .\" The Regents of the University of California. All rights reserved. .\" Copyright (c) 2003, 2007, 2008, 2014 Jason McIntyre -.\" Copyright (c) 2010, 2011, 2014-2018 Ingo Schwarze +.\" Copyright (c) 2010, 2011, 2014-2020 Ingo Schwarze .\" .\" Redistribution and use in source and binary forms, with or without .\" modification, are permitted provided that the following conditions @@ -31,7 +31,7 @@ .\" .\" @(#)man.1 8.2 (Berkeley) 1/2/94 .\" -.Dd $Mdocdate: March 9 2019 $ +.Dd $Mdocdate: July 20 2020 $ .Dt MAN 1 .Os .Sh NAME @@ -51,7 +51,7 @@ The .Nm utility displays the -manual pages entitled +manual page entitled .Ar name . Pages may be selected according to a specific category @@ -64,7 +64,6 @@ The options are as follows: .Bl -tag -width Ds .It Fl a Display all matching manual pages. -Normally, only the first page found is displayed. .It Fl C Ar file Use the specified .Ar file @@ -75,7 +74,7 @@ See for a description of the contents of this file. .It Fl c Copy the manual page to the standard output instead of using -.Xr more 1 +.Xr less 1 to paginate it. This is done by default if the standard output is not a terminal device. .Pp @@ -129,31 +128,31 @@ are ignored. This option implies .Fl a . .It Fl M Ar path -Override the list of standard directories which -.Nm -searches for manual pages. +Override the list of directories to search for manual pages. The supplied .Ar path must be a colon .Pq Ql \&: separated list of directories. -This search path may also be set using the environment variable -.Ev MANPATH . +This option also overrides the environment variable +.Ev MANPATH +and any directories specified in the +.Xr man.conf 5 +file. .It Fl m Ar path -Augment the list of standard directories which -.Nm -searches for manual pages. +Augment the list of directories to search for manual pages. The supplied .Ar path must be a colon .Pq Ql \&: separated list of directories. -These directories will be searched before the standard directories or -the directories specified using the +These directories will be searched before those specified using the .Fl M -option or the +option, the .Ev MANPATH -environment variable. +environment variable, the +.Xr man.conf 5 +file, or the default directories. .It Fl S Ar subsection Only show pages for the specified .Xr machine 1 @@ -168,6 +167,7 @@ architecture whilst using another. This option overrides the .Ev MACHINE environment variable. +.Tg s .It Oo Fl s Oc Ar section Only select manuals from the specified .Ar section . @@ -197,13 +197,12 @@ System maintenance and operation commands. .It 9 Kernel internals. .El -.Pp -If not specified and a match is found in more than one section, -the first match is selected from the following list: -1, 8, 6, 2, 3, 5, 7, 4, 9, 3p. .It Fl w List the pathnames of all matching manual pages instead of displaying any of them. +If no +.Ar name +is given, list the directories that would be searched. .El .Pp The options @@ -214,9 +213,23 @@ The options .Fl fkl are mutually exclusive and override each other. .Pp -Guidelines for writing -man pages can be found in -.Xr mdoc 7 . +The search starts with the +.Fl m +argument if provided, then continues with the +.Fl M +argument, the +.Ev MANPATH +variable, the +.Ic manpath +entries in the +.Xr man.conf 5 +file, or with +.Pa /usr/share/man : Ns Pa /usr/X11R6/man : Ns Pa /usr/local/man +by default. +Within each of these, directories are searched in the order provided. +Within each directory, the search proceeds according to the following +list of sections: 1, 8, 6, 2, 3, 5, 7, 4, 9, 3p. +The first match found is shown. .Pp The .Xr mandoc.db 5 @@ -236,6 +249,10 @@ The database is kept up to date with which is run by the .Xr weekly 8 maintenance script. +.Pp +Guidelines for writing +man pages can be found in +.Xr mdoc 7 . .Sh ENVIRONMENT .Bl -tag -width MANPATHX .It Ev MACHINE @@ -258,7 +275,7 @@ is case insensitive. Any non-empty value of the environment variable .Ev MANPAGER is used instead of the standard pagination program, -.Xr more 1 . +.Xr less 1 . If .Xr less 1 is used, the interactive @@ -286,15 +303,15 @@ manual opens a manual page at the definition of a specific .Ar term rather than at the beginning. .It Ev MANPATH -The standard search path used by -.Nm -may be changed by specifying a path in the +Override the standard search path which is either specified in +.Xr man.conf 5 +or the default path. +The format of .Ev MANPATH -environment variable. -The format of the path is a colon +is a colon .Pq Ql \&: separated list of directories. -Invalid paths are ignored. +Invalid directories are ignored. Overridden by .Fl M , ignored if @@ -303,25 +320,24 @@ is specified. .Pp If .Ev MANPATH -begins with a colon, it is appended to the default list; -if it ends with a colon, it is prepended to the default list; +begins with a colon, it is appended to the standard path; +if it ends with a colon, it is prepended to the standard path; or if it contains two adjacent colons, -the standard search path is inserted between the colons. -If none of these conditions are met, it overrides the -standard search path. +the standard path is inserted between the colons. .It Ev PAGER Specifies the pagination program to use when .Ev MANPAGER is not defined. If neither PAGER nor MANPAGER is defined, -.Xr more 1 -.Fl s +.Xr less 1 is used. .El .Sh FILES .Bl -tag -width /etc/man.conf -compact .It Pa /etc/man.conf -default man configuration file +default +.Nm +configuration file .El .Sh EXIT STATUS .Ex -std man @@ -365,7 +381,7 @@ are extensions to that specification. A .Nm command first appeared in -.At v3 . +.At v2 . .Pp The .Fl w diff --git a/man.7 b/man.7 index 6b4bece71433..cca9c1fe3520 100644 --- a/man.7 +++ b/man.7 @@ -1,7 +1,7 @@ -.\" $Id: man.7,v 1.144 2019/07/09 03:46:59 schwarze Exp $ +.\" $Id: man.7,v 1.148 2021/08/05 14:31:14 schwarze Exp $ .\" .\" Copyright (c) 2009, 2010, 2011, 2012 Kristaps Dzonsons -.\" Copyright (c) 2011-2015,2017,2018,2019 Ingo Schwarze +.\" Copyright (c) 2011-2015, 2017-2020 Ingo Schwarze .\" Copyright (c) 2017 Anthony Bentley .\" Copyright (c) 2010 Joerg Sonnenberger .\" @@ -17,7 +17,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 9 2019 $ +.Dd $Mdocdate: August 5 2021 $ .Dt MAN 7 .Os .Sh NAME @@ -127,6 +127,8 @@ Sets the volume for the footer for compatibility with man pages from .At releases. The optional arguments specify which release it is from. +This macro is an extension that first appeared in +.Bx 4.3 . .It Ic B Text is rendered in bold face. .It Ic BI @@ -244,7 +246,7 @@ link description to be shown .Ed .It Ic OP Optional command-line argument. -This is a non-standard GNU extension. +This is a non-standard DWB extension. It has the following syntax: .Pp .D1 Pf . Ic OP Ar key Op Ar value @@ -255,8 +257,12 @@ is usually a command-line flag and .Ar value its argument. .It Ic P -A synonym for -.Ic PP . +This synonym for +.Ic PP +is an +.At III +extension later adopted by +.Bx 4.3 . .It Ic PD Specify the vertical space to be inserted before each new paragraph. .br @@ -343,6 +349,9 @@ See also .It Ic SB Text is rendered in small size (one point smaller than the default font) bold face. +This macro is an extension that probably first appeared in SunOS 4.0 +and was later adopted by GNU and by +.Bx 4.4 . .It Ic SH Begin a section. The scope of a section is only closed by another section or the end of @@ -435,6 +444,8 @@ Sets the volume for the footer for compatibility with man pages from .Bx releases. The optional first argument specifies which release it is from. +This macro is an extension that first appeared in +.Bx 3 . .It Ic UE End a uniform resource identifier block started with .Ic UR . @@ -505,7 +516,7 @@ The syntax is as follows: .It Ic I Ta n Ta next-line Ta \& .It Ic IB Ta n Ta current Ta \& .It Ic IR Ta n Ta current Ta \& -.It Ic OP Ta >=1 Ta current Ta GNU +.It Ic OP Ta >=1 Ta current Ta DWB .It Ic PD Ta 1 Ta current Ta \& .It Ic RB Ta n Ta current Ta \& .It Ic RI Ta n Ta current Ta \& @@ -601,16 +612,32 @@ The language first appeared as a macro package for the roff typesetting system in .At v7 . -It was later rewritten by James Clark as a macro package for groff. -Eric S. Raymond wrote the extended -.Nm -macros for groff in 2007. +.Pp The stand-alone implementation that is part of the .Xr mandoc 1 -utility written by Kristaps Dzonsons appeared in +utility first appeared in .Ox 4.6 . .Sh AUTHORS -This +.An -nosplit +.An Douglas McIlroy Aq Mt m.douglas.mcilroy@dartmouth.edu +designed and implemented the original version of these macros, +wrote the original version of this manual page, +and was the first to use them when he edited volume 1 of the +.At v7 +manual pages. +.Pp +.An James Clark +later rewrote the macros for groff. +.An Eric S. Raymond Aq Mt esr@thyrsus.com +and +.An Werner Lemberg Aq Mt wl@gnu.org +added the extended +.Nm +macros to groff in 2007. +.Pp +The +.Xr mandoc 1 +program and this .Nm -reference was written by +reference were written by .An Kristaps Dzonsons Aq Mt kristaps@bsd.lv . diff --git a/man.conf.5 b/man.conf.5 index 0ffe868ad764..ffe9fcc07058 100644 --- a/man.conf.5 +++ b/man.conf.5 @@ -1,4 +1,4 @@ -.\" $Id: man.conf.5,v 1.6 2018/10/02 14:56:47 schwarze Exp $ +.\" $Id: man.conf.5,v 1.8 2020/02/10 14:42:10 schwarze Exp $ .\" .\" Copyright (c) 2015, 2017 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: October 2 2018 $ +.Dd $Mdocdate: February 10 2020 $ .Dt MAN.CONF 5 .Os .Sh NAME @@ -101,15 +101,11 @@ manual. .It Ic toc Ta none Ta Cm html Ta print table of contents .It Ic width Ta integer Ta Cm ascii , utf8 Ta right margin .El -.It Ic _whatdb Ar path Ns Cm /whatis.db -This directive provides the same functionality as -.Ic manpath , -but using a historic and misleading syntax. -It is kept for backward compatibility for now, -but will eventually be removed. .El .Sh FILES -.Pa /etc/man.conf +.Bl -tag -width /etc/examples/man.conf -compact +.It Pa /etc/man.conf +.El .Sh EXAMPLES The following configuration file reproduces the defaults: installing it is equivalent to not having a diff --git a/man_html.c b/man_html.c index c0a429c5d6c9..147c20e46443 100644 --- a/man_html.c +++ b/man_html.c @@ -1,7 +1,7 @@ -/* $Id: man_html.c,v 1.174 2019/04/30 15:53:00 schwarze Exp $ */ +/* $Id: man_html.c,v 1.179 2020/10/16 17:22:43 schwarze Exp $ */ /* + * Copyright (c) 2013-2015, 2017-2020 Ingo Schwarze * Copyright (c) 2008-2012, 2014 Kristaps Dzonsons - * Copyright (c) 2013-2015, 2017-2019 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,6 +14,8 @@ * 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. + * + * HTML formatter for man(7) used by mandoc(1). */ #include "config.h" @@ -34,7 +36,7 @@ #include "main.h" #define MAN_ARGS const struct roff_meta *man, \ - const struct roff_node *n, \ + struct roff_node *n, \ struct html *h struct man_html_act { @@ -167,7 +169,12 @@ print_man_node(MAN_ARGS) if (n->type == ROFFT_COMMENT || n->flags & NODE_NOPRT) return; - html_fillmode(h, n->flags & NODE_NOFILL ? ROFF_nf : ROFF_fi); + if ((n->flags & NODE_NOFILL) == 0) + html_fillmode(h, ROFF_fi); + else if (html_fillmode(h, ROFF_nf) == ROFF_nf && + n->tok != ROFF_fi && n->flags & NODE_LINE && + (n->prev == NULL || n->prev->tok != MAN_YS)) + print_endline(h); child = 1; switch (n->type) { @@ -178,7 +185,7 @@ print_man_node(MAN_ARGS) } if (*n->string == ' ' && n->flags & NODE_LINE && (h->flags & HTML_NONEWLINE) == 0) - print_endline(h); + print_otag(h, TAG_BR, ""); else if (n->flags & NODE_DELIMC) h->flags |= HTML_NOSPACE; t = h->tag; @@ -244,20 +251,13 @@ print_man_node(MAN_ARGS) * Close the list if no further item of the same type * follows; otherwise, close the item only. */ - if (list_continues(n, n->next) == '\0') { + if (list_continues(n, roff_node_next(n)) == '\0') { print_tagq(h, t); t = NULL; } } if (t != NULL) print_stagq(h, t); - - if (n->flags & NODE_NOFILL && n->tok != MAN_YS && - (n->next != NULL && n->next->flags & NODE_LINE)) { - /* In .nf =
, print even empty lines. */
-		h->col++;
-		print_endline(h);
-	}
 }
 
 static void
@@ -310,7 +310,6 @@ static int
 man_SH_pre(MAN_ARGS)
 {
 	const char	*class;
-	char		*id;
 	enum htmltag	 tag;
 
 	if (n->tok == MAN_SH) {
@@ -326,10 +325,7 @@ man_SH_pre(MAN_ARGS)
 		print_otag(h, TAG_SECTION, "c", class);
 		break;
 	case ROFFT_HEAD:
-		id = html_make_id(n, 1);
-		print_otag(h, tag, "ci", class, id);
-		if (id != NULL)
-			print_otag(h, TAG_A, "chR", "permalink", id);
+		print_otag_id(h, tag, class, n);
 		break;
 	case ROFFT_BODY:
 		break;
@@ -445,15 +441,17 @@ list_continues(const struct roff_node *n1, const struct roff_node *n2)
 static int
 man_IP_pre(MAN_ARGS)
 {
-	const struct roff_node	*nn;
+	struct roff_node	*nn;
 	const char		*list_class;
 	enum htmltag		 list_elem, body_elem;
 	char			 list_type;
 
 	nn = n->type == ROFFT_BLOCK ? n : n->parent;
-	if ((list_type = list_continues(nn->prev, nn)) == '\0') {
+	list_type = list_continues(roff_node_prev(nn), nn);
+	if (list_type == '\0') {
 		/* Start a new list. */
-		if ((list_type = list_continues(nn, nn->next)) == '\0')
+		list_type = list_continues(nn, roff_node_next(nn));
+		if (list_type == '\0')
 			list_type = ' ';
 		switch (list_type) {
 		case ' ':
@@ -487,7 +485,7 @@ man_IP_pre(MAN_ARGS)
 	case ROFFT_HEAD:
 		if (body_elem == TAG_LI)
 			return 0;
-		print_otag(h, TAG_DT, "");
+		print_otag_id(h, TAG_DT, NULL, n);
 		break;
 	case ROFFT_BODY:
 		print_otag(h, body_elem, "");
@@ -495,7 +493,6 @@ man_IP_pre(MAN_ARGS)
 	default:
 		abort();
 	}
-
 	switch(n->tok) {
 	case MAN_IP:  /* Only print the first header element. */
 		if (n->child != NULL)
diff --git a/man_macro.c b/man_macro.c
index d195576dee28..b3c3a3cb488b 100644
--- a/man_macro.c
+++ b/man_macro.c
@@ -1,7 +1,7 @@
-/*	$Id: man_macro.c,v 1.144 2019/01/05 18:59:46 schwarze Exp $ */
+/* $Id: man_macro.c,v 1.145 2020/09/09 17:01:10 schwarze Exp $ */
 /*
  * Copyright (c) 2008, 2009, 2010, 2011 Kristaps Dzonsons 
- * Copyright (c) 2012-2015, 2017-2019 Ingo Schwarze 
+ * Copyright (c) 2012-2015, 2017-2020 Ingo Schwarze 
  * Copyright (c) 2013 Franco Fichtner 
  *
  * Permission to use, copy, modify, and distribute this software for any
@@ -107,9 +107,11 @@ man_unscope(struct roff_man *man, const struct roff_node *to)
 				mandoc_msg(MANDOCERR_BLK_LINE,
 				    n->line, n->pos,
 				    "EOF breaks %s", roff_name[n->tok]);
-				if (man->flags & MAN_ELINE)
-					man->flags &= ~MAN_ELINE;
-				else {
+				if (man->flags & MAN_ELINE) {
+					if ((man_macro(n->parent->tok)->flags &
+					    MAN_ESCOPED) == 0)
+						man->flags &= ~MAN_ELINE;
+				} else {
 					assert(n->type == ROFFT_HEAD);
 					n = n->parent;
 					man->flags &= ~MAN_BLINE;
diff --git a/man_term.c b/man_term.c
index d9ee14ad22ba..d289f2d12bfb 100644
--- a/man_term.c
+++ b/man_term.c
@@ -1,7 +1,7 @@
-/*	$Id: man_term.c,v 1.232 2019/07/23 17:53:35 schwarze Exp $ */
+/* $Id: man_term.c,v 1.236 2021/06/28 19:50:15 schwarze Exp $ */
 /*
+ * Copyright (c) 2010-2015, 2017-2020 Ingo Schwarze 
  * Copyright (c) 2008-2012 Kristaps Dzonsons 
- * Copyright (c) 2010-2015, 2017-2019 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,6 +14,9 @@
  * 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.
+ *
+ * Plain text formatter for man(7), used by mandoc(1)
+ * for ASCII, UTF-8, PostScript, and PDF output.
  */
 #include "config.h"
 
@@ -32,7 +35,7 @@
 #include "man.h"
 #include "out.h"
 #include "term.h"
-#include "tag.h"
+#include "term_tag.h"
 #include "main.h"
 
 #define	MAXMARGINS	  64 /* maximum number of indented scopes */
@@ -64,7 +67,7 @@ static	void		  print_man_head(struct termp *,
 static	void		  print_man_foot(struct termp *,
 				const struct roff_meta *);
 static	void		  print_bvspace(struct termp *,
-				const struct roff_node *, int);
+				struct roff_node *, int);
 
 static	int		  pre_B(DECL_ARGS);
 static	int		  pre_DT(DECL_ARGS);
@@ -94,8 +97,6 @@ static	void		  post_SY(DECL_ARGS);
 static	void		  post_TP(DECL_ARGS);
 static	void		  post_UR(DECL_ARGS);
 
-static	void		  tag_man(struct termp *, struct roff_node *);
-
 static const struct man_term_act man_term_acts[MAN_MAX - MAN_TH] = {
 	{ NULL, NULL, 0 }, /* TH */
 	{ pre_SH, post_SH, 0 }, /* SH */
@@ -205,19 +206,20 @@ terminal_man(void *arg, const struct roff_meta *man)
  * first, print it.
  */
 static void
-print_bvspace(struct termp *p, const struct roff_node *n, int pardist)
+print_bvspace(struct termp *p, struct roff_node *n, int pardist)
 {
-	int	 i;
+	struct roff_node	*nch;
+	int			 i;
 
 	term_newln(p);
 
-	if (n->body != NULL && n->body->child != NULL)
-		if (n->body->child->type == ROFFT_TBL)
-			return;
+	if (n->body != NULL &&
+	    (nch = roff_node_child(n->body)) != NULL &&
+	    nch->type == ROFFT_TBL)
+		return;
 
-	if (n->parent->type == ROFFT_ROOT || n->parent->tok != MAN_RS)
-		if (n->prev == NULL)
-			return;
+	if (n->parent->tok != MAN_RS && roff_node_prev(n) == NULL)
+		return;
 
 	for (i = 0; i < pardist; i++)
 		term_vspace(p);
@@ -538,10 +540,8 @@ pre_IP(DECL_ARGS)
 	case ROFFT_HEAD:
 		p->tcol->offset = mt->offset;
 		p->tcol->rmargin = mt->offset + len;
-		if (n->child != NULL) {
+		if (n->child != NULL)
 			print_man_node(p, mt, n->child, meta);
-			tag_man(p, n->child);
-		}
 		return 0;
 	case ROFFT_BODY:
 		p->tcol->offset = mt->offset + len;
@@ -621,18 +621,6 @@ pre_TP(DECL_ARGS)
 		while (nn != NULL && (nn->flags & NODE_LINE) == 0)
 			nn = nn->next;
 
-		if (nn == NULL)
-			return 0;
-
-		if (nn->type == ROFFT_TEXT)
-			tag_man(p, nn);
-		else if (nn->child != NULL &&
-		    nn->child->type == ROFFT_TEXT &&
-		    (nn->tok == MAN_B || nn->tok == MAN_BI ||
-		     nn->tok == MAN_BR || nn->tok == MAN_I ||
-		     nn->tok == MAN_IB || nn->tok == MAN_IR))
-			tag_man(p, nn->child);
-
 		while (nn != NULL) {
 			print_man_node(p, mt, nn, meta);
 			nn = nn->next;
@@ -683,12 +671,8 @@ pre_SS(DECL_ARGS)
 		 * and after an empty subsection.
 		 */
 
-		do {
-			n = n->prev;
-		} while (n != NULL && n->tok >= MAN_TH &&
-		    man_term_act(n->tok)->flags & MAN_NOTEXT);
-		if (n == NULL || n->type == ROFFT_COMMENT ||
-		    (n->tok == MAN_SS && n->body->child == NULL))
+		if ((n = roff_node_prev(n)) == NULL ||
+		    (n->tok == MAN_SS && roff_node_child(n->body) == NULL))
 			break;
 
 		for (i = 0; i < mt->pardist; i++)
@@ -728,12 +712,8 @@ pre_SH(DECL_ARGS)
 		 * and after an empty section.
 		 */
 
-		do {
-			n = n->prev;
-		} while (n != NULL && n->tok >= MAN_TH &&
-		    man_term_act(n->tok)->flags & MAN_NOTEXT);
-		if (n == NULL || n->type == ROFFT_COMMENT ||
-		    (n->tok == MAN_SH && n->body->child == NULL))
+		if ((n = roff_node_prev(n)) == NULL ||
+		    (n->tok == MAN_SH && roff_node_child(n->body) == NULL))
 			break;
 
 		for (i = 0; i < mt->pardist; i++)
@@ -839,7 +819,7 @@ pre_SY(DECL_ARGS)
 
 	switch (n->type) {
 	case ROFFT_BLOCK:
-		if (n->prev == NULL || n->prev->tok != MAN_SY)
+		if ((nn = roff_node_prev(n)) == NULL || nn->tok != MAN_SY)
 			print_bvspace(p, n, mt->pardist);
 		return 1;
 	case ROFFT_HEAD:
@@ -920,6 +900,9 @@ print_man_node(DECL_ARGS)
 	const struct man_term_act *act;
 	int c;
 
+	if (n->flags & NODE_ID)
+		term_tag_write(n, p->line);
+
 	switch (n->type) {
 	case ROFFT_TEXT:
 		/*
@@ -1038,10 +1021,6 @@ print_man_foot(struct termp *p, const struct roff_meta *meta)
 	 */
 
 	if ( ! p->mdocstyle) {
-		if (meta->hasbody) {
-			term_vspace(p);
-			term_vspace(p);
-		}
 		mandoc_asprintf(&title, "%s(%s)",
 		    meta->title, meta->msec);
 	} else if (meta->os != NULL) {
@@ -1160,66 +1139,5 @@ print_man_head(struct termp *p, const struct roff_meta *meta)
 	 */
 
 	term_vspace(p);
-	if ( ! p->mdocstyle) {
-		term_vspace(p);
-		term_vspace(p);
-	}
 	free(title);
 }
-
-/*
- * Skip leading whitespace, dashes, backslashes, and font escapes,
- * then create a tag if the first following byte is a letter.
- * Priority is high unless whitespace is present.
- */
-static void
-tag_man(struct termp *p, struct roff_node *n)
-{
-	const char	*cp, *arg;
-	int		 prio, sz;
-
-	assert(n->type == ROFFT_TEXT);
-	cp = n->string;
-	prio = 1;
-	for (;;) {
-		switch (*cp) {
-		case ' ':
-		case '\t':
-			prio = INT_MAX;
-			/* FALLTHROUGH */
-		case '-':
-			cp++;
-			break;
-		case '\\':
-			cp++;
-			switch (mandoc_escape(&cp, &arg, &sz)) {
-			case ESCAPE_FONT:
-			case ESCAPE_FONTROMAN:
-			case ESCAPE_FONTITALIC:
-			case ESCAPE_FONTBOLD:
-			case ESCAPE_FONTPREV:
-			case ESCAPE_FONTBI:
-				break;
-			case ESCAPE_SPECIAL:
-				if (sz != 1)
-					return;
-				switch (*arg) {
-				case '&':
-				case '-':
-				case 'e':
-					break;
-				default:
-					return;
-				}
-				break;
-			default:
-				return;
-			}
-			break;
-		default:
-			if (isalpha((unsigned char)*cp))
-				tag_put(cp, prio, p->line);
-			return;
-		}
-	}
-}
diff --git a/man_validate.c b/man_validate.c
index 0aa550bd08d9..404b223f2b54 100644
--- a/man_validate.c
+++ b/man_validate.c
@@ -1,7 +1,7 @@
-/*	$Id: man_validate.c,v 1.149 2019/06/27 15:07:30 schwarze Exp $ */
+/* $Id: man_validate.c,v 1.156 2021/08/10 12:55:03 schwarze Exp $ */
 /*
+ * Copyright (c) 2010, 2012-2020 Ingo Schwarze 
  * Copyright (c) 2008, 2009, 2010, 2011 Kristaps Dzonsons 
- * Copyright (c) 2010, 2012-2018 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,6 +14,8 @@
  * 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.
+ *
+ * Validation module for man(7) syntax trees used by mandoc(1).
  */
 #include "config.h"
 
@@ -36,6 +38,7 @@
 #include "libmandoc.h"
 #include "roff_int.h"
 #include "libman.h"
+#include "tag.h"
 
 #define	CHKARGS	  struct roff_man *man, struct roff_node *n
 
@@ -45,6 +48,7 @@ static	void	  check_abort(CHKARGS) __attribute__((__noreturn__));
 static	void	  check_par(CHKARGS);
 static	void	  check_part(CHKARGS);
 static	void	  check_root(CHKARGS);
+static	void	  check_tag(struct roff_node *, struct roff_node *);
 static	void	  check_text(CHKARGS);
 
 static	void	  post_AT(CHKARGS);
@@ -54,6 +58,7 @@ static	void	  post_IP(CHKARGS);
 static	void	  post_OP(CHKARGS);
 static	void	  post_SH(CHKARGS);
 static	void	  post_TH(CHKARGS);
+static	void	  post_TP(CHKARGS);
 static	void	  post_UC(CHKARGS);
 static	void	  post_UR(CHKARGS);
 static	void	  post_in(CHKARGS);
@@ -62,8 +67,8 @@ static	const v_check man_valids[MAN_MAX - MAN_TH] = {
 	post_TH,    /* TH */
 	post_SH,    /* SH */
 	post_SH,    /* SS */
-	NULL,       /* TP */
-	NULL,       /* TQ */
+	post_TP,    /* TP */
+	post_TP,    /* TQ */
 	check_abort,/* LP */
 	check_par,  /* PP */
 	check_abort,/* P */
@@ -185,7 +190,7 @@ check_root(CHKARGS)
 
 		man->meta.title = mandoc_strdup("");
 		man->meta.msec = mandoc_strdup("");
-		man->meta.date = mandoc_normdate(man, NULL, n->line, n->pos);
+		man->meta.date = mandoc_normdate(NULL, NULL);
 	}
 
 	if (man->meta.os_e &&
@@ -201,6 +206,68 @@ check_abort(CHKARGS)
 	abort();
 }
 
+/*
+ * Skip leading whitespace, dashes, backslashes, and font escapes,
+ * then create a tag if the first following byte is a letter.
+ * Priority is high unless whitespace is present.
+ */
+static void
+check_tag(struct roff_node *n, struct roff_node *nt)
+{
+	const char	*cp, *arg;
+	int		 prio, sz;
+
+	if (nt == NULL || nt->type != ROFFT_TEXT)
+		return;
+
+	cp = nt->string;
+	prio = TAG_STRONG;
+	for (;;) {
+		switch (*cp) {
+		case ' ':
+		case '\t':
+			prio = TAG_WEAK;
+			/* FALLTHROUGH */
+		case '-':
+			cp++;
+			break;
+		case '\\':
+			cp++;
+			switch (mandoc_escape(&cp, &arg, &sz)) {
+			case ESCAPE_FONT:
+			case ESCAPE_FONTBOLD:
+			case ESCAPE_FONTITALIC:
+			case ESCAPE_FONTBI:
+			case ESCAPE_FONTROMAN:
+			case ESCAPE_FONTCR:
+			case ESCAPE_FONTCB:
+			case ESCAPE_FONTCI:
+			case ESCAPE_FONTPREV:
+			case ESCAPE_IGNORE:
+				break;
+			case ESCAPE_SPECIAL:
+				if (sz != 1)
+					return;
+				switch (*arg) {
+				case '-':
+				case 'e':
+					break;
+				default:
+					return;
+				}
+				break;
+			default:
+				return;
+			}
+			break;
+		default:
+			if (isalpha((unsigned char)*cp))
+				tag_put(cp, prio, n);
+			return;
+		}
+	}
+}
+
 static void
 check_text(CHKARGS)
 {
@@ -246,9 +313,32 @@ static void
 post_SH(CHKARGS)
 {
 	struct roff_node	*nc;
+	char			*cp, *tag;
 
-	if (n->type != ROFFT_BODY || (nc = n->child) == NULL)
+	nc = n->child;
+	switch (n->type) {
+	case ROFFT_HEAD:
+		tag = NULL;
+		deroff(&tag, n);
+		if (tag != NULL) {
+			for (cp = tag; *cp != '\0'; cp++)
+				if (*cp == ' ')
+					*cp = '_';
+			if (nc != NULL && nc->type == ROFFT_TEXT &&
+			    strcmp(nc->string, tag) == 0)
+				tag_put(NULL, TAG_STRONG, n);
+			else
+				tag_put(tag, TAG_FALLBACK, n);
+			free(tag);
+		}
+		return;
+	case ROFFT_BODY:
+		if (nc != NULL)
+			break;
 		return;
+	default:
+		return;
+	}
 
 	if (nc->tok == MAN_PP && nc->body->child != NULL) {
 		while (nc->body->last != NULL) {
@@ -332,12 +422,14 @@ check_par(CHKARGS)
 static void
 post_IP(CHKARGS)
 {
-
 	switch (n->type) {
 	case ROFFT_BLOCK:
 		if (n->head->child == NULL && n->body->child == NULL)
 			roff_node_delete(man, n);
 		break;
+	case ROFFT_HEAD:
+		check_tag(n, n->child);
+		break;
 	case ROFFT_BODY:
 		if (n->parent->head->child == NULL && n->child == NULL)
 			mandoc_msg(MANDOCERR_PAR_SKIP, n->line, n->pos,
@@ -348,6 +440,37 @@ post_IP(CHKARGS)
 	}
 }
 
+/*
+ * The first next-line element in the head is the tag.
+ * If that's a font macro, use its first child instead.
+ */
+static void
+post_TP(CHKARGS)
+{
+	struct roff_node *nt;
+
+	if (n->type != ROFFT_HEAD || (nt = n->child) == NULL)
+		return;
+
+	while ((nt->flags & NODE_LINE) == 0)
+		if ((nt = nt->next) == NULL)
+			return;
+
+	switch (nt->tok) {
+	case MAN_B:
+	case MAN_BI:
+	case MAN_BR:
+	case MAN_I:
+	case MAN_IB:
+	case MAN_IR:
+		nt = nt->child;
+		break;
+	default:
+		break;
+	}
+	check_tag(n, nt);
+}
+
 static void
 post_TH(CHKARGS)
 {
@@ -389,9 +512,14 @@ post_TH(CHKARGS)
 
 	if (n != NULL)
 		n = n->next;
-	if (n != NULL && n->string != NULL)
+	if (n != NULL && n->string != NULL) {
 		man->meta.msec = mandoc_strdup(n->string);
-	else {
+		if (man->filesec != '\0' &&
+		    man->filesec != *n->string &&
+		    *n->string >= '1' && *n->string <= '9')
+			mandoc_msg(MANDOCERR_MSEC_FILE, n->line, n->pos,
+			    "*.%c vs TH ... %c", man->filesec, *n->string);
+	} else {
 		man->meta.msec = mandoc_strdup("");
 		mandoc_msg(MANDOCERR_MSEC_MISSING,
 		    nb->line, nb->pos, "TH %s", man->meta.title);
@@ -401,15 +529,10 @@ post_TH(CHKARGS)
 
 	if (n != NULL)
 		n = n->next;
-	if (n != NULL && n->string != NULL && n->string[0] != '\0')
-		man->meta.date = mandoc_normdate(man,
-		    n->string, n->line, n->pos);
-	else {
+	if (man->quick && n != NULL)
 		man->meta.date = mandoc_strdup("");
-		mandoc_msg(MANDOCERR_DATE_MISSING,
-		    n == NULL ? nb->line : n->line,
-		    n == NULL ? nb->pos : n->pos, "TH");
-	}
+	else
+		man->meta.date = mandoc_normdate(n, nb);
 
 	/* TITLE MSEC DATE ->OS<- VOL */
 
diff --git a/manconf.h b/manconf.h
index bb3761998c88..c84409d7b570 100644
--- a/manconf.h
+++ b/manconf.h
@@ -1,6 +1,6 @@
-/*	$Id: manconf.h,v 1.7 2018/11/22 11:30:23 schwarze Exp $ */
+/* $OpenBSD: manconf.h,v 1.7 2018/11/22 11:30:15 schwarze Exp $ */
 /*
- * Copyright (c) 2011, 2015, 2017, 2018 Ingo Schwarze 
+ * Copyright (c) 2011,2015,2017,2018,2020 Ingo Schwarze 
  * Copyright (c) 2011 Kristaps Dzonsons 
  *
  * Permission to use, copy, modify, and distribute this software for any
@@ -14,6 +14,9 @@
  * 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 to man(1) configuration management.
+ * For use by the main program and by the formatters.
  */
 
 /* List of unique, absolute paths to manual trees. */
@@ -28,15 +31,18 @@ struct	manpaths {
 struct	manoutput {
 	char	 *includes;
 	char	 *man;
+	char	 *outfilename;
 	char	 *paper;
 	char	 *style;
 	char	 *tag;
+	char	 *tagfilename;
 	size_t	  indent;
 	size_t	  width;
 	int	  fragment;
 	int	  mdoc;
 	int	  noval;
 	int	  synopsisonly;
+	int	  tag_found;
 	int	  toc;
 };
 
diff --git a/mandoc.1 b/mandoc.1
index 298d5dc70610..f7490963b4bd 100644
--- a/mandoc.1
+++ b/mandoc.1
@@ -1,7 +1,7 @@
-.\"	$Id: mandoc.1,v 1.240 2019/07/10 19:39:01 schwarze Exp $
+.\" $OpenBSD: mandoc.1,v 1.166 2020/02/15 15:28:01 schwarze Exp $
 .\"
+.\" Copyright (c) 2012, 2014-2021 Ingo Schwarze 
 .\" Copyright (c) 2009, 2010, 2011 Kristaps Dzonsons 
-.\" Copyright (c) 2012, 2014-2019 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 10 2019 $
+.Dd $Mdocdate: August 14 2021 $
 .Dt MANDOC 1
 .Os
 .Sh NAME
@@ -52,13 +52,13 @@ The options are as follows:
 If the standard output is a terminal device and
 .Fl c
 is not specified, use
-.Xr more 1
+.Xr less 1
 to paginate the output, just like
 .Xr man 1
 would.
 .It Fl c
 Copy the formatted manual pages to the standard output without using
-.Xr more 1
+.Xr less 1
 to paginate them.
 This is the default.
 It can be specified to override
@@ -301,8 +301,8 @@ Format
 input files in
 .Xr mdoc 7
 output style.
-Specifically, this suppresses the two additional blank lines near the
-top and the bottom of each page, and it implies
+This prints the operating system name rather than the page title
+on the right side of the footer line, and it implies
 .Fl O Cm indent Ns =5 .
 One useful application is for checking that
 .Fl T Cm man
@@ -410,6 +410,30 @@ The file
 is used for an external style-sheet.
 This must be a valid absolute or
 relative URI.
+.It Cm tag Ns Op = Ns Ar term
+Same syntax and semantics as for
+.Sx ASCII Output .
+This is implemented by passing a
+.Ic file://
+URI ending in a fragment identifier to the pager
+rather than passing merely a file name.
+When using this argument, use a pager supporting such URIs, for example
+.Bd -literal -offset 3n
+MANPAGER='lynx -force_html' man -T html -O tag=MANPAGER man
+MANPAGER='w3m -T text/html' man -T html -O tag=toc mandoc
+.Ed
+.Pp
+Consequently, for HTML output, this argument does not work with
+.Xr more 1
+or
+.Xr less 1 .
+For example,
+.Ql MANPAGER=less man -T html -O tag=toc mandoc
+does not work because
+.Xr less 1
+does not support
+.Ic file://
+URIs.
 .It Cm toc
 If an input file contains at least two non-standard sections,
 print a table of contents near the beginning of the output.
@@ -443,13 +467,15 @@ This is useful for distributing manual sources to legacy systems
 lacking
 .Xr mdoc 7
 formatters.
+Embedded
+.Xr eqn 7
+and
+.Xr tbl 7
+code is not supported.
 .Pp
 If the input format of a file is
 .Xr man 7 ,
-the input is copied to the output, expanding any
-.Xr roff 7
-.Ic so
-requests.
+the input is copied to the output.
 The parser is also run, and as usual, the
 .Fl W
 level controls which
@@ -628,7 +654,7 @@ It never affects the interpretation of input files.
 Any non-empty value of the environment variable
 .Ev MANPAGER
 is used instead of the standard pagination program,
-.Xr more 1 ;
+.Xr less 1 ;
 see
 .Xr man 1
 for details.
@@ -642,8 +668,7 @@ Specifies the pagination program to use when
 .Ev MANPAGER
 is not defined.
 If neither PAGER nor MANPAGER is defined,
-.Xr more 1
-.Fl s
+.Xr less 1
 is used.
 Only used if
 .Fl a
@@ -897,14 +922,6 @@ generated by CVS
 or
 .Ic NetBSD
 keyword substitution as conventionally used in these operating systems.
-.It Sy "referenced manual not found"
-.Pq mdoc
-An
-.Ic \&Xr
-macro references a manual page that is not found in the base system.
-The path to look for base system manuals is configurable at compile
-time and defaults to
-.Pa /usr/share/man : /usr/X11R6/man .
 .El
 .Ss Style suggestions
 .Bl -ohang
@@ -991,6 +1008,35 @@ list contains two consecutive
 entries describing the same
 .Ic \&Er
 number.
+.It Sy "referenced manual not found"
+.Pq mdoc
+An
+.Ic \&Xr
+macro references a manual page that was not found.
+When running with
+.Fl W Cm base ,
+the search is restricted to the base system, by default to
+.Pa /usr/share/man : Ns Pa /usr/X11R6/man .
+This path can be configured at compile time using the
+.Dv MANPATH_BASE
+preprocessor macro.
+When running with
+.Fl W Cm style ,
+the search is done along the full search path as described in the
+.Xr man 1
+manual page, respecting the
+.Fl m
+and
+.Fl M
+command line options, the
+.Ev MANPATH
+environment variable, the
+.Xr man.conf 5
+file and falling back to the default of
+.Pa /usr/share/man : Ns Pa /usr/X11R6/man : Ns Pa /usr/local/man ,
+also configurable at compile time using the
+.Dv MANPATH_DEFAULT
+preprocessor macro.
 .It Sy "trailing delimiter"
 .Pq mdoc
 The last argument of an
@@ -1020,6 +1066,9 @@ An
 request occurs even though the document already switched to no-fill mode
 and did not switch back to fill mode yet.
 It has no effect.
+.It Sy "input text line longer than 80 bytes"
+Consider breaking the input text line
+at one of the blank characters before column 80.
 .It Sy "verbatim \(dq--\(dq, maybe consider using \e(em"
 .Pq mdoc
 Even though the ASCII output device renders an em-dash as
@@ -1073,7 +1122,21 @@ macro lacks the mandatory section argument.
 The section number in a
 .Ic \&Dt
 line is invalid, but still used.
-.It Sy "missing date, using today's date"
+.It Sy "filename/section mismatch"
+.Pq mdoc , man
+The name of the input file being processed is known and its file
+name extension starts with a non-zero digit, but the
+.Ic \&Dt
+or
+.Ic \&TH
+macro contains a
+.Ar section
+argument that starts with a different non-zero digit.
+The
+.Ar section
+argument is used as provided anyway.
+Consider checking whether the file name or the argument need a correction.
+.It Sy "missing date, using \(dq\(dq"
 .Pq mdoc, man
 The document was parsed as
 .Xr mdoc 7
@@ -1811,6 +1874,10 @@ The invalid character is discarded.
 A table layout specification contains an opening parenthesis,
 but no matching closing parenthesis.
 The rest of the input line, starting from the parenthesis, has no effect.
+.It Sy "ignoring excessive spacing in tbl layout"
+.Pq tbl
+A spacing modifier in a table layout is unreasonably large.
+The default spacing of 3n is used instead.
 .It Sy "tbl without any data cells"
 .Pq tbl
 A table does not contain any data cells.
@@ -2247,6 +2314,26 @@ or
 macro or of an undefined macro.
 The macro is ignored, and its arguments are handled
 as if they were a text line.
+.It Sy "skipping tbl in -Tman mode"
+.Pq mdoc , tbl
+An input file contains the
+.Ic \&TS
+macro.
+This message is only generated in
+.Fl T Cm man
+output mode, where
+.Xr tbl 7
+input is not supported.
+.It Sy "skipping eqn in -Tman mode"
+.Pq mdoc , eqn
+An input file contains the
+.Ic \&EQ
+macro.
+This message is only generated in
+.Fl T Cm man
+output mode, where
+.Xr eqn 7
+input is not supported.
 .El
 .Ss Bad command line arguments
 .Bl -ohang
@@ -2284,6 +2371,14 @@ The
 .Fl O Cm tag
 option was specified but the tag was not found in any of the displayed
 manual pages.
+.It Sy "\-Tmarkdown unsupported for man(7) input"
+.Pq man
+The
+.Fl T Cm markdown
+option was specified but an input file uses the
+.Xr man 7
+language.
+No output is produced for that input file.
 .El
 .Sh SEE ALSO
 .Xr apropos 1 ,
diff --git a/mandoc.c b/mandoc.c
index 5a2be88e40df..6adf1a4318b2 100644
--- a/mandoc.c
+++ b/mandoc.c
@@ -1,7 +1,7 @@
-/*	$Id: mandoc.c,v 1.116 2019/06/27 15:07:30 schwarze Exp $ */
+/*	$Id: mandoc.c,v 1.119 2021/08/10 12:55:03 schwarze Exp $ */
 /*
  * Copyright (c) 2008-2011, 2014 Kristaps Dzonsons 
- * Copyright (c) 2011-2015, 2017, 2018 Ingo Schwarze 
+ * Copyright (c) 2011-2015, 2017-2021 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
@@ -74,12 +74,12 @@ mandoc_font(const char *cp, int sz)
 		case 'C':
 			switch (cp[1]) {
 			case 'B':
-				return ESCAPE_FONTBOLD;
+				return ESCAPE_FONTCB;
 			case 'I':
-				return ESCAPE_FONTITALIC;
+				return ESCAPE_FONTCI;
 			case 'R':
 			case 'W':
-				return ESCAPE_FONTCW;
+				return ESCAPE_FONTCR;
 			default:
 				return ESCAPE_ERROR;
 			}
@@ -203,7 +203,18 @@ mandoc_escape(const char **end, const char **start, int *sz)
 	case 'O':
 	case 'V':
 	case 'Y':
-		gly = (*start)[-1] == 'f' ? ESCAPE_FONT : ESCAPE_IGNORE;
+	case '*':
+		switch ((*start)[-1]) {
+		case 'f':
+			gly = ESCAPE_FONT;
+			break;
+		case '*':
+			gly = ESCAPE_DEVICE;
+			break;
+		default:
+			gly = ESCAPE_IGNORE;
+			break;
+		}
 		switch (**start) {
 		case '(':
 			if ((*start)[-1] == 'O')
@@ -238,13 +249,6 @@ mandoc_escape(const char **end, const char **start, int *sz)
 			break;
 		}
 		break;
-	case '*':
-		if (strncmp(*start, "(.T", 3) != 0)
-			abort();
-		gly = ESCAPE_DEVICE;
-		*start = ++*end;
-		*sz = 2;
-		break;
 
 	/*
 	 * These escapes are of the form \X'Y', where 'X' is the trigger
@@ -459,6 +463,9 @@ mandoc_escape(const char **end, const char **start, int *sz)
 		    + 1 == *sz)
 			gly = ESCAPE_UNICODE;
 		break;
+	case ESCAPE_DEVICE:
+		assert(*sz == 2 && (*start)[0] == '.' && (*start)[1] == 'T');
+		break;
 	default:
 		break;
 	}
@@ -536,45 +543,59 @@ fail:
 }
 
 char *
-mandoc_normdate(struct roff_man *man, char *in, int ln, int pos)
+mandoc_normdate(struct roff_node *nch, struct roff_node *nbl)
 {
 	char		*cp;
 	time_t		 t;
 
-	if (man->quick)
-		return mandoc_strdup(in == NULL ? "" : in);
-
-	/* No date specified: use today's date. */
+	/* No date specified. */
 
-	if (in == NULL || *in == '\0')
-		mandoc_msg(MANDOCERR_DATE_MISSING, ln, pos, NULL);
-	if (in == NULL || *in == '\0' || strcmp(in, "$" "Mdocdate$") == 0)
+	if (nch == NULL) {
+		if (nbl == NULL)
+			mandoc_msg(MANDOCERR_DATE_MISSING, 0, 0, NULL);
+		else
+			mandoc_msg(MANDOCERR_DATE_MISSING, nbl->line,
+			    nbl->pos, "%s", roff_name[nbl->tok]);
+		return mandoc_strdup("");
+	}
+	if (*nch->string == '\0') {
+		mandoc_msg(MANDOCERR_DATE_MISSING, nch->line,
+		    nch->pos, "%s", roff_name[nbl->tok]);
+		return mandoc_strdup("");
+	}
+	if (strcmp(nch->string, "$" "Mdocdate$") == 0)
 		return time2a(time(NULL));
 
 	/* Valid mdoc(7) date format. */
 
-	if (a2time(&t, "$" "Mdocdate: %b %d %Y $", in) ||
-	    a2time(&t, "%b %d, %Y", in)) {
+	if (a2time(&t, "$" "Mdocdate: %b %d %Y $", nch->string) ||
+	    a2time(&t, "%b %d, %Y", nch->string)) {
 		cp = time2a(t);
 		if (t > time(NULL) + 86400)
-			mandoc_msg(MANDOCERR_DATE_FUTURE, ln, pos, "%s", cp);
-		else if (*in != '$' && strcmp(in, cp) != 0)
-			mandoc_msg(MANDOCERR_DATE_NORM, ln, pos, "%s", cp);
+			mandoc_msg(MANDOCERR_DATE_FUTURE, nch->line,
+			    nch->pos, "%s %s", roff_name[nbl->tok], cp);
+		else if (*nch->string != '$' &&
+		    strcmp(nch->string, cp) != 0)
+			mandoc_msg(MANDOCERR_DATE_NORM, nch->line,
+			    nch->pos, "%s %s", roff_name[nbl->tok], cp);
 		return cp;
 	}
 
 	/* In man(7), do not warn about the legacy format. */
 
-	if (a2time(&t, "%Y-%m-%d", in) == 0)
-		mandoc_msg(MANDOCERR_DATE_BAD, ln, pos, "%s", in);
+	if (a2time(&t, "%Y-%m-%d", nch->string) == 0)
+		mandoc_msg(MANDOCERR_DATE_BAD, nch->line, nch->pos,
+		    "%s %s", roff_name[nbl->tok], nch->string);
 	else if (t > time(NULL) + 86400)
-		mandoc_msg(MANDOCERR_DATE_FUTURE, ln, pos, "%s", in);
-	else if (man->meta.macroset == MACROSET_MDOC)
-		mandoc_msg(MANDOCERR_DATE_LEGACY, ln, pos, "Dd %s", in);
+		mandoc_msg(MANDOCERR_DATE_FUTURE, nch->line, nch->pos,
+		    "%s %s", roff_name[nbl->tok], nch->string);
+	else if (nbl->tok == MDOC_Dd)
+		mandoc_msg(MANDOCERR_DATE_LEGACY, nch->line, nch->pos,
+		    "Dd %s", nch->string);
 
 	/* Use any non-mdoc(7) date verbatim. */
 
-	return mandoc_strdup(in);
+	return mandoc_strdup(nch->string);
 }
 
 int
diff --git a/mandoc.css b/mandoc.css
index d75700a28ba7..ceac503a79f6 100644
--- a/mandoc.css
+++ b/mandoc.css
@@ -1,4 +1,4 @@
-/* $Id: mandoc.css,v 1.46 2019/06/02 16:57:13 schwarze Exp $ */
+/* $Id: mandoc.css,v 1.48 2021/03/30 19:26:20 schwarze Exp $ */
 /*
  * Standard style sheet for mandoc(1) -Thtml and man.cgi(8).
  *
@@ -31,6 +31,7 @@ td {		vertical-align: top;
 ul, ol, dl {	margin-top: 0em;
 		margin-bottom: 0em; }
 li, dt {	margin-top: 1em; }
+pre {		font-family: inherit; }
 
 .permalink {	border-bottom: thin dotted;
 		color: inherit;
@@ -135,12 +136,12 @@ h2.Ss {		margin-top: 1.2em;
 		vertical-align: top; }
 .Bl-tag > dd {
 		clear: right;
+		column-count: 1;  /* Force block formatting context. */
 		width: 100%;
 		margin-top: 0em;
 		margin-left: 0em;
 		margin-bottom: 0.6em;
-		vertical-align: top;
-		overflow: auto; }
+		vertical-align: top; }
 .Bl-compact {	margin-top: 0em; }
 .Bl-compact > dd {
 		margin-bottom: 0em; }
diff --git a/mandoc.h b/mandoc.h
index 4a7e7aa749ad..9837ff2ae9df 100644
--- a/mandoc.h
+++ b/mandoc.h
@@ -1,7 +1,7 @@
-/*	$Id: mandoc.h,v 1.264 2019/07/14 18:16:13 schwarze Exp $ */
+/* $Id: mandoc.h,v 1.274 2021/08/14 13:53:08 schwarze Exp $ */
 /*
+ * Copyright (c) 2012-2021 Ingo Schwarze 
  * Copyright (c) 2010, 2011, 2014 Kristaps Dzonsons 
- * Copyright (c) 2012-2019 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
@@ -16,6 +16,7 @@
  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  *
  * Error handling, escape sequence, and character utilities.
+ * Can be used by all code in the mandoc package.
  */
 
 #define ASCII_NBRSP	 31  /* non-breaking space */
@@ -53,7 +54,6 @@ enum	mandocerr {
 	MANDOCERR_ARCH_BAD,  /* unknown architecture: Dt ... arch */
 	MANDOCERR_OS_ARG,  /* operating system explicitly specified: Os ... */
 	MANDOCERR_RCS_MISSING, /* RCS id missing */
-	MANDOCERR_XR_BAD,  /* referenced manual not found: Xr name sec */
 
 	MANDOCERR_STYLE, /* ===== start of style suggestions ===== */
 
@@ -67,10 +67,12 @@ enum	mandocerr {
 	MANDOCERR_BX, /* consider using OS macro: macro */
 	MANDOCERR_ER_ORDER, /* errnos out of order: Er ... */
 	MANDOCERR_ER_REP, /* duplicate errno: Er ... */
+	MANDOCERR_XR_BAD,  /* referenced manual not found: Xr name sec */
 	MANDOCERR_DELIM, /* trailing delimiter: macro ... */
 	MANDOCERR_DELIM_NB, /* no blank before trailing delimiter: macro ... */
 	MANDOCERR_FI_SKIP, /* fill mode already enabled, skipping: fi */
 	MANDOCERR_NF_SKIP, /* fill mode already disabled, skipping: nf */
+	MANDOCERR_TEXT_LONG, /* input text line longer than 80 bytes */
 	MANDOCERR_DASHDASH, /* verbatim "--", maybe consider using \(em */
 	MANDOCERR_FUNC, /* function name without markup: name() */
 	MANDOCERR_SPACE_EOL, /* whitespace at end of input line */
@@ -83,7 +85,8 @@ enum	mandocerr {
 	MANDOCERR_TH_NOTITLE, /* missing manual title, using "": [macro] */
 	MANDOCERR_MSEC_MISSING, /* missing manual section, using "": macro */
 	MANDOCERR_MSEC_BAD, /* unknown manual section: Dt ... section */
-	MANDOCERR_DATE_MISSING, /* missing date, using today's date */
+	MANDOCERR_MSEC_FILE, /* filename/section mismatch: ... */
+	MANDOCERR_DATE_MISSING, /* missing date, using "": [macro] */
 	MANDOCERR_DATE_BAD, /* cannot parse date, using it verbatim: date */
 	MANDOCERR_DATE_FUTURE, /* date in the future, using it anyway: date */
 	MANDOCERR_OS_MISSING, /* missing Os macro, using "" */
@@ -187,6 +190,7 @@ enum	mandocerr {
 	MANDOCERR_TBLLAYOUT_NONE, /* empty tbl layout */
 	MANDOCERR_TBLLAYOUT_CHAR, /* invalid character in tbl layout: char */
 	MANDOCERR_TBLLAYOUT_PAR, /* unmatched parenthesis in tbl layout */
+	MANDOCERR_TBLLAYOUT_SPC, /* ignoring excessive spacing in tbl layout */
 	MANDOCERR_TBLDATA_NONE, /* tbl without any data cells */
 	MANDOCERR_TBLDATA_SPAN, /* ignoring data in spanned tbl cell: data */
 	MANDOCERR_TBLDATA_EXTRA, /* ignoring extra tbl data cells: data */
@@ -223,6 +227,7 @@ enum	mandocerr {
 	MANDOCERR_SHIFT, /* excessive shift: ..., but max is ... */
 	MANDOCERR_SO_PATH, /* NOT IMPLEMENTED: .so with absolute path or ".." */
 	MANDOCERR_SO_FAIL, /* .so request failed */
+	MANDOCERR_TG_SPC, /* skipping tag containing whitespace: tag */
 	MANDOCERR_ARG_SKIP, /* skipping all arguments: macro args */
 	MANDOCERR_ARG_EXCESS, /* skipping excess arguments: macro ... args */
 	MANDOCERR_DIVZERO, /* divide by zero */
@@ -240,6 +245,8 @@ enum	mandocerr {
 	MANDOCERR_TBLOPT_EQN, /* eqn delim option in tbl: arg */
 	MANDOCERR_TBLLAYOUT_MOD, /* unsupported tbl layout modifier: m */
 	MANDOCERR_TBLMACRO, /* ignoring macro in table: macro */
+	MANDOCERR_TBL_TMAN, /* skipping tbl in -Tman mode */
+	MANDOCERR_EQN_TMAN, /* skipping eqn in -Tman mode */
 
 	MANDOCERR_BADARG, /* ===== start of bad invocations ===== */
 
@@ -250,6 +257,7 @@ enum	mandocerr {
 	MANDOCERR_BADVAL_BAD, /* bad argument value */
 	MANDOCERR_BADVAL_DUPE, /* duplicate argument value */
 	MANDOCERR_TAG, /* no such tag */
+	MANDOCERR_MAN_TMARKDOWN, /* -Tmarkdown unsupported for man(7) input */
 
 	MANDOCERR_SYSERR, /* ===== start of system errors ===== */
 
@@ -284,7 +292,9 @@ enum	mandoc_esc {
 	ESCAPE_FONTITALIC, /* italic font mode */
 	ESCAPE_FONTBI, /* bold italic font mode */
 	ESCAPE_FONTROMAN, /* roman font mode */
-	ESCAPE_FONTCW, /* constant width font mode */
+	ESCAPE_FONTCR, /* constant width font mode */
+	ESCAPE_FONTCB, /* constant width bold font mode */
+	ESCAPE_FONTCI, /* constant width italic font mode */
 	ESCAPE_FONTPREV, /* previous font mode */
 	ESCAPE_NUMBERED, /* a numbered glyph */
 	ESCAPE_UNICODE, /* a unicode codepoint */
@@ -298,7 +308,7 @@ enum	mandoc_esc {
 };
 
 
-enum mandoc_esc	  mandoc_font(const char *, int sz);
+enum mandoc_esc	  mandoc_font(const char *, int);
 enum mandoc_esc	  mandoc_escape(const char **, const char **, int *);
 void		  mandoc_msg_setoutfile(FILE *);
 const char	 *mandoc_msg_getinfilename(void);
diff --git a/mandoc_char.7 b/mandoc_char.7
index b46575d4d250..eb9e65acfc86 100644
--- a/mandoc_char.7
+++ b/mandoc_char.7
@@ -1,8 +1,8 @@
-.\"	$Id: mandoc_char.7,v 1.76 2019/03/31 19:17:26 schwarze Exp $
+.\"	$Id: mandoc_char.7,v 1.78 2020/10/31 11:45:16 schwarze Exp $
 .\"
 .\" Copyright (c) 2003 Jason McIntyre 
 .\" Copyright (c) 2009, 2010, 2011 Kristaps Dzonsons 
-.\" Copyright (c) 2011,2013,2015,2017,2018 Ingo Schwarze 
+.\" Copyright (c) 2011,2013,2015,2017-2020 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
@@ -16,7 +16,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 31 2019 $
+.Dd $Mdocdate: October 31 2020 $
 .Dt MANDOC_CHAR 7
 .Os
 .Sh NAME
@@ -261,15 +261,15 @@ subsection of the
 .Xr roff 7
 manual.
 .Pp
-Spacing:
+Spaces, non-breaking unless stated otherwise:
 .Bl -column "Input" "Description" -offset indent -compact
 .It Em Input Ta Em Description
-.It Sq \e\ \& Ta unpaddable non-breaking space
-.It \e\(ti   Ta paddable non-breaking space
-.It \e0      Ta digit-width space allowing line break
+.It Sq \e\ \& Ta unpaddable space
+.It \e\(ti   Ta paddable space
+.It \e0      Ta digit-width space
 .It \e|      Ta one-sixth \e(em narrow space, zero width in nroff mode
 .It \e^      Ta one-twelfth \e(em half-narrow space, zero width in nroff
-.It \e&      Ta zero-width non-breaking space
+.It \e&      Ta zero-width space
 .It \e)      Ta zero-width space transparent to end-of-sentence detection
 .It \e%      Ta zero-width space allowing hyphenation
 .It \e:      Ta zero-width space allowing line break
@@ -709,11 +709,6 @@ Their syntax is similar to special characters, using
 and
 .Sq \e*[N]
 .Pq N-character .
-For details, see the
-.Em Predefined Strings
-subsection of the
-.Xr roff 7
-manual.
 .Bl -column "Input" "Rendered" "Description" -offset indent
 .It Em Input Ta Em Rendered Ta Em Description
 .It \e*(Ba   Ta \*(Ba       Ta vertical bar
diff --git a/mandoc_headers.3 b/mandoc_headers.3
index 2cda75cba8b3..7fe6d379f864 100644
--- a/mandoc_headers.3
+++ b/mandoc_headers.3
@@ -1,6 +1,6 @@
-.\"	$Id: mandoc_headers.3,v 1.31 2019/03/17 18:21:45 schwarze Exp $
+.\"	$Id: mandoc_headers.3,v 1.34 2021/08/10 12:55:03 schwarze Exp $
 .\"
-.\" Copyright (c) 2014-2019 Ingo Schwarze 
+.\" Copyright (c) 2014-2021 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: March 17 2019 $
+.Dd $Mdocdate: August 10 2021 $
 .Dt MANDOC_HEADERS 3
 .Os
 .Sh NAME
@@ -167,7 +167,11 @@ parse tree; can be used everywhere.
 Requires
 .In sys/types.h
 for
-.Vt size_t .
+.Vt size_t
+and
+.Qq Pa mandoc.h
+for
+.Vt enum mandoc_esc .
 .Pp
 Provides
 .Vt enum tbl_cellt ,
@@ -232,6 +236,30 @@ and the functions
 .Fn mandoc_xr_get ,
 and
 .Fn mandoc_xr_free .
+.It Qq Pa tag.h
+Internal interfaces to tag syntax tree nodes,
+for use by validation modules only.
+.Pp
+Requires
+.In limits.h
+for
+.Dv INT_MAX .
+.Pp
+Provides the functions
+.Fn tag_alloc ,
+.Fn tag_put ,
+.Fn tag_check ,
+and
+.Fn tag_free
+and some
+.Dv TAG_*
+constants.
+.Pp
+Uses the type
+.Vt struct roff_node
+from
+.Qq Pa roff.h
+as an opaque type for function prototypes.
 .El
 .Pp
 The following two require
@@ -587,6 +615,33 @@ When this header is included, the same file should not include
 .Qq Pa html.h
 or
 .Qq Pa mansearch.h .
+.It Qq Pa tag_term.h
+Requires
+.In sys/types.h
+for
+.Vt size_t
+and
+.In stdio.h
+for
+.Vt FILE .
+.Pp
+Provides an interface to generate
+.Xr ctags 1
+files for the
+.Ic :t
+functionality mentioned in
+.Xr man 1 .
+.Pp
+Uses the type
+.Vt struct roff_node
+from
+.Qq Pa roff.h
+as an opaque type for function prototypes.
+.Pp
+When this header is included, the same file should not include
+.Qq Pa html.h
+or
+.Qq Pa mansearch.h .
 .It Qq Pa html.h
 Requires
 .In sys/types.h
@@ -629,21 +684,10 @@ from
 as opaque types for function prototypes.
 .Pp
 When this header is included, the same file should not include
-.Qq Pa term.h
+.Qq Pa term.h ,
+.Qq Pa tab_term.h ,
 or
 .Qq Pa mansearch.h .
-.It Qq Pa tag.h
-Requires
-.In sys/types.h
-for
-.Vt size_t .
-.Pp
-Provides an interface to generate
-.Xr ctags 1
-files for the
-.Ic :t
-functionality mentioned in
-.Xr man 1 .
 .It Qq Pa main.h
 Provides the top level steering functions for all formatters.
 .Pp
@@ -696,6 +740,7 @@ as an opaque type for function prototypes.
 When this header is included, the same file should not include
 .Qq Pa out.h ,
 .Qq Pa term.h ,
+.Qq Pa tab_term.h ,
 or
 .Qq Pa html.h .
 .El
diff --git a/mandoc_html.3 b/mandoc_html.3
index 32407574a814..a77d0e04fce4 100644
--- a/mandoc_html.3
+++ b/mandoc_html.3
@@ -1,4 +1,4 @@
-.\"	$Id: mandoc_html.3,v 1.19 2019/01/11 12:56:43 schwarze Exp $
+.\"	$Id: mandoc_html.3,v 1.23 2020/04/24 13:13:06 schwarze Exp $
 .\"
 .\" Copyright (c) 2014, 2017, 2018 Ingo Schwarze 
 .\"
@@ -14,14 +14,18 @@
 .\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 .\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 .\"
-.Dd $Mdocdate: January 11 2019 $
+.Dd $Mdocdate: April 24 2020 $
 .Dt MANDOC_HTML 3
 .Os
 .Sh NAME
 .Nm mandoc_html
 .Nd internals of the mandoc HTML formatter
 .Sh SYNOPSIS
-.In "html.h"
+.In sys/types.h
+.Fd #include """mandoc.h"""
+.Fd #include """roff.h"""
+.Fd #include """out.h"""
+.Fd #include """html.h"""
 .Ft void
 .Fn print_gen_decls "struct html *h"
 .Ft void
@@ -46,18 +50,42 @@
 .Fa "const struct tag *suntil"
 .Fc
 .Ft void
+.Fn html_close_paragraph "struct html *h"
+.Ft enum roff_tok
+.Fo html_fillmode
+.Fa "struct html *h"
+.Fa "enum roff_tok tok"
+.Fc
+.Ft int
+.Fo html_setfont
+.Fa "struct html *h"
+.Fa "enum mandoc_esc font"
+.Fc
+.Ft void
 .Fo print_text
 .Fa "struct html *h"
 .Fa "const char *word"
 .Fc
+.Ft void
+.Fo print_tagged_text
+.Fa "struct html *h"
+.Fa "const char *word"
+.Fa "struct roff_node *n"
+.Fc
 .Ft char *
 .Fo html_make_id
 .Fa "const struct roff_node *n"
+.Fa "int unique"
 .Fc
-.Ft int
-.Fo html_strlen
-.Fa "const char *cp"
+.Ft struct tag *
+.Fo print_otag_id
+.Fa "struct html *h"
+.Fa "enum htmltag tag"
+.Fa "const char *cattr"
+.Fa "struct roff_node *n"
 .Fc
+.Ft void
+.Fn print_endline "struct html *h"
 .Sh DESCRIPTION
 The mandoc HTML formatter is not a formal library.
 However, as it is compiled into more than one program, in particular
@@ -96,7 +124,7 @@ These structures are declared in
 Internal state of the HTML formatter.
 .It Vt struct tag
 One entry for the LIFO stack of HTML elements.
-Members are
+Members include
 .Fa "enum htmltag tag"
 and
 .Fa "struct tag *next" .
@@ -105,10 +133,8 @@ and
 The function
 .Fn print_gen_decls
 prints the opening
-.Ao Pf \&? Ic xml ? Ac
-and
 .Aq Pf \&! Ic DOCTYPE
-declarations required for the current document type.
+declaration.
 .Pp
 The function
 .Fn print_gen_comment
@@ -235,6 +261,59 @@ is used to close out all open elements up to and including
 .Fn print_stagq
 is a variant to close out all open elements up to but excluding
 .Fa suntil .
+The function
+.Fn html_close_paragraph
+closes all open elements that establish phrasing context,
+thus returning to the innermost flow context.
+.Pp
+The function
+.Fn html_fillmode
+switches to fill mode if
+.Fa want
+is
+.Dv ROFF_fi
+or to no-fill mode if
+.Fa want
+is
+.Dv ROFF_nf .
+Switching from fill mode to no-fill mode closes the current paragraph
+and opens a
+.Aq Ic PRE
+element.
+Switching in the opposite direction closes the
+.Aq Ic PRE
+element, but does not open a new paragraph.
+If
+.Fa want
+matches the mode that is already active, no elements are closed nor opened.
+If
+.Fa want
+is
+.Dv TOKEN_NONE ,
+the mode remains as it is.
+.Pp
+The function
+.Fn html_setfont
+selects the
+.Fa font ,
+which can be
+.Dv ESCAPE_FONTROMAN ,
+.Dv ESCAPE_FONTBOLD ,
+.Dv ESCAPE_FONTITALIC ,
+.Dv ESCAPE_FONTBI ,
+or
+.Dv ESCAPE_FONTCW ,
+for future text output and internally remembers
+the font that was active before the change.
+If the
+.Fa font
+argument is
+.Dv ESCAPE_FONTPREV ,
+the current and the previous font are exchanged.
+This function only changes the internal state of the
+.Fa h
+object; no HTML elements are written yet.
+Subsequent text output will write font elements when needed.
 .Pp
 The function
 .Fn print_text
@@ -256,24 +335,118 @@ and
 functions.
 .Pp
 The function
-.Fn html_make_id
-takes a node containing one or more text children
-and returns a newly allocated string containing the concatenation
-of the child strings, with blanks replaced by underscores.
-If the node
+.Fn print_tagged_text
+is a variant of
+.Fn print_text
+that wraps
+.Fa word
+in an
+.Aq Ic A
+element of class
+.Qq permalink
+if
 .Fa n
-contains any non-text child node,
+is not
+.Dv NULL
+and yields a segment identifier when passed to
+.Fn html_make_id .
+.Pp
+The function
 .Fn html_make_id
-returns
+allocates a string to be used for the
+.Cm id
+attribute of an HTML element and/or as a segment identifier for a URI in an
+.Aq Ic A
+element.
+If
+.Fa n
+contains a
+.Fa tag
+attribute, it is used; otherwise, child nodes are used.
+If
+.Fa n
+is an
+.Ic \&Sh ,
+.Ic \&Ss ,
+.Ic \&Sx ,
+.Ic SH ,
+or
+.Ic SS
+node, the resulting string is the concatenation of the child strings;
+for other node types, only the first child is used.
+Bytes not permitted in URI-fragment strings are replaced by underscores.
+If any of the children to be used is not a text node,
+no string is generated and
 .Dv NULL
-instead.
-The caller is responsible for freeing the returned string.
+is returned instead.
+If the
+.Fa unique
+argument is non-zero, deduplication is performed by appending an
+underscore and a decimal integer, if necessary.
+If the
+.Fa unique
+argument is 1, this is assumed to be the first call for this tag
+at this location, typically for use by
+.Dv NODE_ID ,
+so the integer is incremented before use.
+If the
+.Fa unique
+argument is 2, this is ssumed to be the second call for this tag
+at this location, typically for use by
+.Dv NODE_HREF ,
+so the existing integer, if any, is used without incrementing it.
 .Pp
 The function
-.Fn html_strlen
-counts the number of characters in
-.Fa cp .
-It is used as a crude estimate of the width needed to display a string.
+.Fn print_otag_id
+opens a
+.Fa tag
+element of class
+.Fa cattr
+for the node
+.Fa n .
+If the flag
+.Dv NODE_ID
+is set in
+.Fa n ,
+it attempts to generate an
+.Cm id
+attribute with
+.Fn html_make_id .
+If the flag
+.Dv NODE_HREF
+is set in
+.Fa n ,
+an
+.Aq Ic A
+element of class
+.Qq permalink
+is added:
+outside if
+.Fa n
+generates an element that can only occur in phrasing context,
+or inside otherwise.
+This function is a wrapper around
+.Fn html_make_id
+and
+.Fn print_otag ,
+automatically chosing the
+.Fa unique
+argument appropriately and setting the
+.Fa fmt
+arguments to
+.Qq chR
+and
+.Qq ci ,
+respectively.
+.Pp
+The function
+.Fn print_endline
+makes sure subsequent output starts on a new HTML output line.
+If nothing was printed on the current output line yet, it has no effect.
+Otherwise, it appends any buffered text to the current output line,
+ends the line, and updates the internal state of the
+.Fa h
+object.
 .Pp
 The functions
 .Fn print_eqn ,
@@ -281,6 +454,46 @@ The functions
 and
 .Fn print_tblclose
 are not yet documented.
+.Sh RETURN VALUES
+The functions
+.Fn print_otag
+and
+.Fn print_otag_id
+return a pointer to a new element on the stack of HTML elements.
+When
+.Fn print_otag_id
+opens two elements, a pointer to the outer one is returned.
+The memory pointed to is owned by the library and is automatically
+.Xr free 3 Ns d
+when
+.Fn print_tagq
+is called on it or when
+.Fn print_stagq
+is called on a parent element.
+.Pp
+The function
+.Fn html_fillmode
+returns
+.Dv ROFF_fi
+if fill mode was active before the call or
+.Dv ROFF_nf
+otherwise.
+.Pp
+The function
+.Fn html_make_id
+returns a newly allocated string or
+.Dv NULL
+if
+.Fa n
+lacks text data to create the attribute from.
+The caller is responsible for
+.Xr free 3 Ns ing
+the returned string after using it.
+.Pp
+In case of
+.Xr malloc 3
+failure, these functions do not return but call
+.Xr err 3 .
 .Sh FILES
 .Bl -tag -width mandoc_aux.c -compact
 .It Pa main.h
@@ -303,6 +516,17 @@ HTML formatter
 .It Pa eqn_html.c
 .Xr eqn 7
 HTML formatter
+.It Pa roff_html.c
+.Xr roff 7
+HTML formatter, handling requests like
+.Ic br ,
+.Ic ce ,
+.Ic fi ,
+.Ic ft ,
+.Ic nf ,
+.Ic rj ,
+and
+.Ic sp .
 .It Pa out.h
 declarations of data types and private functions
 for shared use by all mandoc formatters,
diff --git a/mandoc_malloc.3 b/mandoc_malloc.3
index ae3ca16aa4a8..d73438575065 100644
--- a/mandoc_malloc.3
+++ b/mandoc_malloc.3
@@ -1,4 +1,4 @@
-.\"	$Id: mandoc_malloc.3,v 1.2 2016/07/07 19:19:01 schwarze Exp $
+.\"	$Id: mandoc_malloc.3,v 1.3 2021/09/17 18:50:21 schwarze Exp $
 .\"
 .\" Copyright (c) 2014 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: July 7 2016 $
+.Dd $Mdocdate: September 17 2021 $
 .Dt MANDOC_MALLOC 3
 .Os
 .Sh NAME
@@ -22,6 +22,7 @@
 .Nm mandoc_realloc ,
 .Nm mandoc_reallocarray ,
 .Nm mandoc_calloc ,
+.Nm mandoc_recallocarray ,
 .Nm mandoc_strdup ,
 .Nm mandoc_strndup ,
 .Nm mandoc_asprintf
@@ -49,6 +50,13 @@
 .Fa "size_t nmemb"
 .Fa "size_t size"
 .Fc
+.Ft "void *"
+.Fo mandoc_recallocarray
+.Fa "void *ptr"
+.Fa "size_t oldnmemb"
+.Fa "size_t nmemb"
+.Fa "size_t size"
+.Fc
 .Ft "char *"
 .Fo mandoc_strdup
 .Fa "const char *s"
@@ -82,12 +90,15 @@ The function
 .Fn mandoc_malloc
 allocates one new object, leaving the memory uninitialized.
 The functions
-.Fn mandoc_realloc
+.Fn mandoc_realloc ,
+.Fn mandoc_reallocarray ,
 and
-.Fn mandoc_reallocarray
+.Fn mandoc_recallocarray
 change the size of an existing object or array, possibly moving it.
 When shrinking the size, existing data is truncated; when growing,
-the additional memory is not initialized.
+only
+.Fn mandoc_recallocarray
+initializes the new elements to zero.
 The function
 .Fn mandoc_calloc
 allocates a new array, initializing it to zero.
@@ -99,6 +110,9 @@ The argument
 .Fa nmemb
 is the new number of objects in the array.
 The argument
+.Fa oldnmemb
+is the number of objects in the array before the call.
+The argument
 .Fa ptr
 is a pointer to the existing object or array to be resized; if it is
 .Dv NULL ,
@@ -168,9 +182,13 @@ is a widespread extension that first appeared in the GNU C library.
 The function
 .Fn reallocarray
 is an extension that first appeared in
-.Ox 5.6 .
-If it is not provided by the operating system, the mandoc build system
-uses a bundled portable implementation.
+.Ox 5.6 ,
+and
+.Fn recallocarray
+in
+.Ox 6.1 .
+If these two are not provided by the operating system,
+the mandoc build system uses bundled portable implementations.
 .Sh HISTORY
 The functions
 .Fn mandoc_malloc ,
@@ -181,11 +199,12 @@ and
 have been available since mandoc 1.9.12,
 .Fn mandoc_strndup
 since 1.11.5,
-and
 .Fn mandoc_asprintf
-and
+since 1.12.4,
 .Fn mandoc_reallocarray
-since 1.12.4 and 1.13.0.
+since 1.13.0, and
+.Fn mandoc_recallocarray
+since 1.14.2.
 .Sh AUTHORS
 .An Kristaps Dzonsons Aq Mt kristaps@bsd.lv
 .An Ingo Schwarze Aq Mt schwarze@openbsd.org
diff --git a/mandoc_msg.c b/mandoc_msg.c
index e73dcfd830ff..beec5059a28c 100644
--- a/mandoc_msg.c
+++ b/mandoc_msg.c
@@ -1,7 +1,7 @@
-/*	$Id: mandoc_msg.c,v 1.8 2019/07/14 18:16:13 schwarze Exp $ */
+/* $OpenBSD: mandoc_msg.c,v 1.8 2020/01/19 17:59:01 schwarze Exp $ */
 /*
+ * Copyright (c) 2014-2021 Ingo Schwarze 
  * Copyright (c) 2010, 2011 Kristaps Dzonsons 
- * Copyright (c) 2014-2019 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,6 +14,8 @@
  * 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.
+ *
+ * Implementation of warning and error messages for mandoc(1).
  */
 #include "config.h"
 
@@ -53,7 +55,6 @@ static	const char *const type_message[MANDOCERR_MAX] = {
 	"unknown architecture",
 	"operating system explicitly specified",
 	"RCS id missing",
-	"referenced manual not found",
 
 	"generic style suggestion",
 
@@ -67,10 +68,12 @@ static	const char *const type_message[MANDOCERR_MAX] = {
 	"consider using OS macro",
 	"errnos out of order",
 	"duplicate errno",
+	"referenced manual not found",
 	"trailing delimiter",
 	"no blank before trailing delimiter",
 	"fill mode already enabled, skipping",
 	"fill mode already disabled, skipping",
+	"input text line longer than 80 bytes",
 	"verbatim \"--\", maybe consider using \\(em",
 	"function name without markup",
 	"whitespace at end of input line",
@@ -83,7 +86,8 @@ static	const char *const type_message[MANDOCERR_MAX] = {
 	"missing manual title, using \"\"",
 	"missing manual section, using \"\"",
 	"unknown manual section",
-	"missing date, using today's date",
+	"filename/section mismatch",
+	"missing date, using \"\"",
 	"cannot parse date, using it verbatim",
 	"date in the future, using it anyway",
 	"missing Os macro, using \"\"",
@@ -187,6 +191,7 @@ static	const char *const type_message[MANDOCERR_MAX] = {
 	"empty tbl layout",
 	"invalid character in tbl layout",
 	"unmatched parenthesis in tbl layout",
+	"ignoring excessive spacing in tbl layout",
 	"tbl without any data cells",
 	"ignoring data in spanned tbl cell",
 	"ignoring extra tbl data cells",
@@ -223,6 +228,7 @@ static	const char *const type_message[MANDOCERR_MAX] = {
 	"excessive shift",
 	"NOT IMPLEMENTED: .so with absolute path or \"..\"",
 	".so request failed",
+	"skipping tag containing whitespace",
 	"skipping all arguments",
 	"skipping excess arguments",
 	"divide by zero",
@@ -239,6 +245,8 @@ static	const char *const type_message[MANDOCERR_MAX] = {
 	"eqn delim option in tbl",
 	"unsupported tbl layout modifier",
 	"ignoring macro in table",
+	"skipping tbl in -Tman mode",
+	"skipping eqn in -Tman mode",
 
 	/* bad command line arguments */
 	NULL,
@@ -249,6 +257,7 @@ static	const char *const type_message[MANDOCERR_MAX] = {
 	"bad option value",
 	"duplicate option value",
 	"no such tag",
+	"-Tmarkdown unsupported for man(7) input",
 
 	/* system errors */
 	NULL,
diff --git a/mandoc_ohash.c b/mandoc_ohash.c
index 0627b469c60b..c4ebfcd428ed 100644
--- a/mandoc_ohash.c
+++ b/mandoc_ohash.c
@@ -1,4 +1,4 @@
-/*	$Id: mandoc_ohash.c,v 1.2 2015/10/19 18:58:47 schwarze Exp $	*/
+/* $Id: mandoc_ohash.c,v 1.3 2020/06/22 19:20:40 schwarze Exp $	*/
 /*
  * Copyright (c) 2014, 2015 Ingo Schwarze 
  *
@@ -14,6 +14,8 @@
  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  */
+#include "config.h"
+
 #include 
 #include 
 #include 
diff --git a/mandoc_parse.h b/mandoc_parse.h
index 61341f0d7f39..b8b29dd931c9 100644
--- a/mandoc_parse.h
+++ b/mandoc_parse.h
@@ -1,4 +1,4 @@
-/*	$Id: mandoc_parse.h,v 1.4 2018/12/30 00:49:55 schwarze Exp $ */
+/*	$Id: mandoc_parse.h,v 1.5 2019/11/09 14:39:49 schwarze Exp $ */
 /*
  * Copyright (c) 2010, 2011 Kristaps Dzonsons 
  * Copyright (c) 2014,2015,2016,2017,2018 Ingo Schwarze 
@@ -29,6 +29,7 @@
 #define	MPARSE_UTF8	(1 << 4)  /* accept UTF-8 input */
 #define	MPARSE_LATIN1	(1 << 5)  /* accept ISO-LATIN-1 input */
 #define	MPARSE_VALIDATE	(1 << 6)  /* call validation functions */
+#define	MPARSE_COMMENT	(1 << 7)  /* save comments in the tree */
 
 
 struct	roff_meta;
diff --git a/mandoc_xr.c b/mandoc_xr.c
index da0a7f0cf23b..6d1b1a8d8ad2 100644
--- a/mandoc_xr.c
+++ b/mandoc_xr.c
@@ -1,4 +1,4 @@
-/*	$Id: mandoc_xr.c,v 1.3 2017/07/02 21:18:29 schwarze Exp $ */
+/* $Id: mandoc_xr.c,v 1.4 2020/06/22 19:20:40 schwarze Exp $ */
 /*
  * Copyright (c) 2017 Ingo Schwarze 
  *
@@ -14,6 +14,8 @@
  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  */
+#include "config.h"
+
 #include 
 
 #include 
diff --git a/mandocd.c b/mandocd.c
index 1a597b3867bc..60e40fed8023 100644
--- a/mandocd.c
+++ b/mandocd.c
@@ -1,4 +1,4 @@
-/*	$Id: mandocd.c,v 1.11 2019/03/03 13:02:11 schwarze Exp $ */
+/*	$Id: mandocd.c,v 1.12 2020/06/14 23:40:31 schwarze Exp $ */
 /*
  * Copyright (c) 2017 Michael Stapelberg 
  * Copyright (c) 2017, 2019 Ingo Schwarze 
@@ -17,7 +17,7 @@
  */
 #include "config.h"
 
-#if HAVE_CMSG_XPG42
+#if NEED_XPG4_2
 #define _XPG4_2
 #endif
 
diff --git a/mandocdb.c b/mandocdb.c
index 64ac10115877..a3360fe44d01 100644
--- a/mandocdb.c
+++ b/mandocdb.c
@@ -1,7 +1,7 @@
-/*	$Id: mandocdb.c,v 1.263 2019/05/03 18:17:12 schwarze Exp $ */
+/* $Id: mandocdb.c,v 1.269 2021/08/19 16:55:31 schwarze Exp $ */
 /*
+ * Copyright (c) 2011-2020 Ingo Schwarze 
  * Copyright (c) 2011, 2012 Kristaps Dzonsons 
- * Copyright (c) 2011-2019 Ingo Schwarze 
  * Copyright (c) 2016 Ed Maste 
  *
  * Permission to use, copy, modify, and distribute this software for any
@@ -15,6 +15,8 @@
  * 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.
+ *
+ * Implementation of the makewhatis(8) program.
  */
 #include "config.h"
 
@@ -118,7 +120,7 @@ struct	mdoc_handler {
 int		 mandocdb(int, char *[]);
 
 static	void	 dbadd(struct dba *, struct mpage *);
-static	void	 dbadd_mlink(const struct mlink *mlink);
+static	void	 dbadd_mlink(const struct mlink *);
 static	void	 dbprune(struct dba *);
 static	void	 dbwrite(struct dba *);
 static	void	 filescan(const char *);
@@ -163,6 +165,9 @@ 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, int);
+#ifdef READ_ALLOWED_PATH
+static	int	 read_allowed(const char *);
+#endif
 static	int	 render_string(char **, size_t *);
 static	void	 say(const char *, const char *, ...)
 			__attribute__((__format__ (__printf__, 2, 3)));
@@ -179,6 +184,7 @@ 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	size_t		 basedir_len; /* strlen(basedir) */
 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 */
@@ -342,7 +348,7 @@ mandocdb(int argc, char *argv[])
 	 * clobber each other.
 	 */
 #define	CHECKOP(_op, _ch) do \
-	if (OP_DEFAULT != (_op)) { \
+	if ((_op) != OP_DEFAULT) { \
 		warnx("-%c: Conflicting option", (_ch)); \
 		goto usage; \
 	} while (/*CONSTCOND*/0)
@@ -351,7 +357,7 @@ mandocdb(int argc, char *argv[])
 	path_arg = NULL;
 	op = OP_DEFAULT;
 
-	while (-1 != (ch = getopt(argc, argv, "aC:Dd:npQT:tu:v")))
+	while ((ch = getopt(argc, argv, "aC:Dd:npQT:tu:v")) != -1)
 		switch (ch) {
 		case 'a':
 			use_all = 1;
@@ -379,7 +385,7 @@ mandocdb(int argc, char *argv[])
 			mparse_options |= MPARSE_QUICK;
 			break;
 		case 'T':
-			if (strcmp(optarg, "utf8")) {
+			if (strcmp(optarg, "utf8") != 0) {
 				warnx("-T%s: Unsupported output format",
 				    optarg);
 				goto usage;
@@ -416,7 +422,7 @@ mandocdb(int argc, char *argv[])
 	}
 #endif
 
-	if (OP_CONFFILE == op && argc > 0) {
+	if (op == OP_CONFFILE && argc > 0) {
 		warnx("-C: Too many arguments");
 		goto usage;
 	}
@@ -427,13 +433,13 @@ mandocdb(int argc, char *argv[])
 	mandoc_ohash_init(&mpages, 6, offsetof(struct mpage, inodev));
 	mandoc_ohash_init(&mlinks, 6, offsetof(struct mlink, file));
 
-	if (OP_UPDATE == op || OP_DELETE == op || OP_TEST == op) {
+	if (op == OP_UPDATE || op == OP_DELETE || op == OP_TEST) {
 
 		/*
 		 * Most of these deal with a specific directory.
 		 * Jump into that directory first.
 		 */
-		if (OP_TEST != op && 0 == set_basedir(path_arg, 1))
+		if (op != OP_TEST && set_basedir(path_arg, 1) == 0)
 			goto out;
 
 		dba = nodb ? dba_new(128) : dba_read(MANDOC_DB);
@@ -454,11 +460,11 @@ mandocdb(int argc, char *argv[])
 				    " from scratch", strerror(errno));
 			exitcode = (int)MANDOCLEVEL_OK;
 			op = OP_DEFAULT;
-			if (0 == treescan())
+			if (treescan() == 0)
 				goto out;
 			dba = dba_new(128);
 		}
-		if (OP_DELETE != op)
+		if (op != OP_DELETE)
 			mpages_merge(dba, mp);
 		if (nodb == 0)
 			dbwrite(dba);
@@ -492,7 +498,7 @@ mandocdb(int argc, char *argv[])
 			sz = strlen(conf.manpath.paths[j]);
 			if (sz && conf.manpath.paths[j][sz - 1] == '/')
 				conf.manpath.paths[j][--sz] = '\0';
-			if (0 == sz)
+			if (sz == 0)
 				continue;
 
 			if (j) {
@@ -502,9 +508,9 @@ mandocdb(int argc, char *argv[])
 				    offsetof(struct mlink, file));
 			}
 
-			if ( ! set_basedir(conf.manpath.paths[j], argc > 0))
+			if (set_basedir(conf.manpath.paths[j], argc > 0) == 0)
 				continue;
-			if (0 == treescan())
+			if (treescan() == 0)
 				continue;
 			dba = dba_new(128);
 			mpages_merge(dba, mp);
@@ -608,9 +614,9 @@ treescan(void)
 					say(path, "&realpath");
 				continue;
 			}
-			if (strstr(buf, basedir) != buf
-#ifdef HOMEBREWDIR
-			    && strstr(buf, HOMEBREWDIR) != buf
+			if (strncmp(buf, basedir, basedir_len) != 0
+#ifdef READ_ALLOWED_PATH
+			    && !read_allowed(buf)
 #endif
 			) {
 				if (warnings) say("",
@@ -623,6 +629,8 @@ treescan(void)
 					say(path, "&stat");
 				continue;
 			}
+			if ((ff->fts_statp->st_mode & S_IFMT) != S_IFREG)
+				continue;
 			/* FALLTHROUGH */
 
 		/*
@@ -777,17 +785,17 @@ treescan(void)
  * See treescan() for the fts(3) version of this.
  */
 static void
-filescan(const char *file)
+filescan(const char *infile)
 {
-	char		 buf[PATH_MAX];
 	struct stat	 st;
 	struct mlink	*mlink;
-	char		*p, *start;
+	char		*linkfile, *p, *realdir, *start, *usefile;
+	size_t		 realdir_len;
 
 	assert(use_all);
 
-	if (0 == strncmp(file, "./", 2))
-		file += 2;
+	if (strncmp(infile, "./", 2) == 0)
+		infile += 2;
 
 	/*
 	 * We have to do lstat(2) before realpath(3) loses
@@ -796,13 +804,13 @@ filescan(const char *file)
 	 * we want to use the orginal file name, while for
 	 * regular files, we want to use the real path.
 	 */
-	if (-1 == lstat(file, &st)) {
+	if (lstat(infile, &st) == -1) {
 		exitcode = (int)MANDOCLEVEL_BADARG;
-		say(file, "&lstat");
+		say(infile, "&lstat");
 		return;
-	} else if (0 == ((S_IFREG | S_IFLNK) & st.st_mode)) {
+	} else if (S_ISREG(st.st_mode) == 0 && S_ISLNK(st.st_mode) == 0) {
 		exitcode = (int)MANDOCLEVEL_BADARG;
-		say(file, "Not a regular file");
+		say(infile, "Not a regular file");
 		return;
 	}
 
@@ -810,23 +818,24 @@ filescan(const char *file)
 	 * We have to resolve the file name to the real path
 	 * in any case for the base directory check.
 	 */
-	if (NULL == realpath(file, buf)) {
+	if ((usefile = realpath(infile, NULL)) == NULL) {
 		exitcode = (int)MANDOCLEVEL_BADARG;
-		say(file, "&realpath");
+		say(infile, "&realpath");
 		return;
 	}
 
-	if (OP_TEST == op)
-		start = buf;
-	else if (strstr(buf, basedir) == buf)
-		start = buf + strlen(basedir);
-#ifdef HOMEBREWDIR
-	else if (strstr(buf, HOMEBREWDIR) == buf)
-		start = buf;
+	if (op == OP_TEST)
+		start = usefile;
+	else if (strncmp(usefile, basedir, basedir_len) == 0)
+		start = usefile + basedir_len;
+#ifdef READ_ALLOWED_PATH
+	else if (read_allowed(usefile))
+		start = usefile;
 #endif
 	else {
 		exitcode = (int)MANDOCLEVEL_BADARG;
-		say("", "%s: outside base directory", buf);
+		say("", "%s: outside base directory", infile);
+		free(usefile);
 		return;
 	}
 
@@ -834,25 +843,72 @@ filescan(const char *file)
 	 * Now we are sure the file is inside our tree.
 	 * If it is a symbolic link, ignore the real path
 	 * and use the original name.
-	 * This implies passing stuff like "cat1/../man1/foo.1"
-	 * on the command line won't work.  So don't do that.
-	 * Note the stat(2) can still fail if the link target
-	 * doesn't exist.
 	 */
-	if (S_IFLNK & st.st_mode) {
-		if (-1 == stat(buf, &st)) {
+	do {
+		if (S_ISLNK(st.st_mode) == 0)
+			break;
+
+		/*
+		 * Some implementations of realpath(3) may succeed
+		 * even if the target of the link does not exist,
+		 * so check again for extra safety.
+		 */
+		if (stat(usefile, &st) == -1) {
 			exitcode = (int)MANDOCLEVEL_BADARG;
-			say(file, "&stat");
+			say(infile, "&stat");
+			free(usefile);
 			return;
 		}
-		if (strlcpy(buf, file, sizeof(buf)) >= sizeof(buf)) {
-			say(file, "Filename too long");
-			return;
+		linkfile = mandoc_strdup(infile);
+		if (op == OP_TEST) {
+			free(usefile);
+			start = usefile = linkfile;
+			break;
 		}
-		start = buf;
-		if (OP_TEST != op && strstr(buf, basedir) == buf)
-			start += strlen(basedir);
-	}
+		if (strncmp(infile, basedir, basedir_len) == 0) {
+			free(usefile);
+			usefile = linkfile;
+			start = usefile + basedir_len;
+			break;
+		}
+
+		/*
+		 * This symbolic link points into the basedir
+		 * from the outside.  Let's see whether any of
+		 * the parent directories resolve to the basedir.
+		 */
+		p = strchr(linkfile, '\0');
+		do {
+			while (*--p != '/')
+				continue;
+			*p = '\0';
+			if ((realdir = realpath(linkfile, NULL)) == NULL) {
+				exitcode = (int)MANDOCLEVEL_BADARG;
+				say(infile, "&realpath");
+				free(linkfile);
+				free(usefile);
+				return;
+			}
+			realdir_len = strlen(realdir) + 1;
+			free(realdir);
+			*p = '/';
+		} while (realdir_len > basedir_len);
+
+		/*
+		 * If one of the directories resolves to the basedir,
+		 * use the rest of the original name.
+		 * Otherwise, the best we can do
+		 * is to use the filename pointed to.
+		 */
+		if (realdir_len == basedir_len) {
+			free(usefile);
+			usefile = linkfile;
+			start = p + 1;
+		} else {
+			free(linkfile);
+			start = usefile + basedir_len;
+		}
+	} while (/* CONSTCOND */ 0);
 
 	mlink = mandoc_calloc(1, sizeof(struct mlink));
 	mlink->dform = FORM_NONE;
@@ -860,6 +916,7 @@ filescan(const char *file)
 	    sizeof(mlink->file)) {
 		say(start, "Filename too long");
 		free(mlink);
+		free(usefile);
 		return;
 	}
 
@@ -868,13 +925,13 @@ filescan(const char *file)
 	 * but outside our tree, guess the base directory.
 	 */
 
-	if (op == OP_TEST || (start == buf && *start == '/')) {
-		if (strncmp(buf, "man/", 4) == 0)
-			start = buf + 4;
-		else if ((start = strstr(buf, "/man/")) != NULL)
+	if (op == OP_TEST || (start == usefile && *start == '/')) {
+		if (strncmp(usefile, "man/", 4) == 0)
+			start = usefile + 4;
+		else if ((start = strstr(usefile, "/man/")) != NULL)
 			start += 5;
 		else
-			start = buf;
+			start = usefile;
 	}
 
 	/*
@@ -883,18 +940,18 @@ filescan(const char *file)
 	 * If we find one of these and what's underneath is a directory,
 	 * assume it's an architecture.
 	 */
-	if (NULL != (p = strchr(start, '/'))) {
+	if ((p = strchr(start, '/')) != NULL) {
 		*p++ = '\0';
-		if (0 == strncmp(start, "man", 3)) {
+		if (strncmp(start, "man", 3) == 0) {
 			mlink->dform = FORM_SRC;
 			mlink->dsec = start + 3;
-		} else if (0 == strncmp(start, "cat", 3)) {
+		} else if (strncmp(start, "cat", 3) == 0) {
 			mlink->dform = FORM_CAT;
 			mlink->dsec = start + 3;
 		}
 
 		start = p;
-		if (NULL != mlink->dsec && NULL != (p = strchr(start, '/'))) {
+		if (mlink->dsec != NULL && (p = strchr(start, '/')) != NULL) {
 			*p++ = '\0';
 			mlink->arch = start;
 			start = p;
@@ -906,10 +963,10 @@ filescan(const char *file)
 	 * Suffix of `.0' indicates a catpage, `.1-9' is a manpage.
 	 */
 	p = strrchr(start, '\0');
-	while (p-- > start && '/' != *p && '.' != *p)
-		/* Loop. */ ;
+	while (p-- > start && *p != '/' && *p != '.')
+		continue;
 
-	if ('.' == *p) {
+	if (*p == '.') {
 		*p++ = '\0';
 		mlink->fsec = p;
 	}
@@ -919,11 +976,12 @@ filescan(const char *file)
 	 * Use the filename portion of the path.
 	 */
 	mlink->name = start;
-	if (NULL != (p = strrchr(start, '/'))) {
+	if ((p = strrchr(start, '/')) != NULL) {
 		mlink->name = p + 1;
 		*p = '\0';
 	}
 	mlink_add(mlink, &st);
+	free(usefile);
 }
 
 static void
@@ -2250,7 +2308,6 @@ set_basedir(const char *targetdir, int report_baddir)
 	static char	 startdir[PATH_MAX];
 	static int	 getcwd_status;  /* 1 = ok, 2 = failure */
 	static int	 chdir_status;  /* 1 = changed directory */
-	char		*cp;
 
 	/*
 	 * Remember the original working directory, if possible.
@@ -2259,8 +2316,8 @@ set_basedir(const char *targetdir, int report_baddir)
 	 * Do not error out if the current directory is not
 	 * searchable: Maybe it won't be needed after all.
 	 */
-	if (0 == getcwd_status) {
-		if (NULL == getcwd(startdir, sizeof(startdir))) {
+	if (getcwd_status == 0) {
+		if (getcwd(startdir, sizeof(startdir)) == NULL) {
 			getcwd_status = 2;
 			(void)strlcpy(startdir, strerror(errno),
 			    sizeof(startdir));
@@ -2273,19 +2330,20 @@ set_basedir(const char *targetdir, int report_baddir)
 	 * Do not use it any longer, not even for messages.
 	 */
 	*basedir = '\0';
+	basedir_len = 0;
 
 	/*
 	 * If and only if the directory was changed earlier and
 	 * the next directory to process is given as a relative path,
 	 * first go back, or bail out if that is impossible.
 	 */
-	if (chdir_status && '/' != *targetdir) {
-		if (2 == getcwd_status) {
+	if (chdir_status && *targetdir != '/') {
+		if (getcwd_status == 2) {
 			exitcode = (int)MANDOCLEVEL_SYSERR;
 			say("", "getcwd: %s", startdir);
 			return 0;
 		}
-		if (-1 == chdir(startdir)) {
+		if (chdir(startdir) == -1) {
 			exitcode = (int)MANDOCLEVEL_SYSERR;
 			say("", "&chdir %s", startdir);
 			return 0;
@@ -2297,48 +2355,71 @@ set_basedir(const char *targetdir, int report_baddir)
 	 * pathname and append a trailing slash, such that
 	 * we can reliably check whether files are inside.
 	 */
-	if (NULL == realpath(targetdir, basedir)) {
+	if (realpath(targetdir, basedir) == NULL) {
 		if (report_baddir || errno != ENOENT) {
 			exitcode = (int)MANDOCLEVEL_BADARG;
 			say("", "&%s: realpath", targetdir);
 		}
+		*basedir = '\0';
 		return 0;
-	} else if (-1 == chdir(basedir)) {
+	} else if (chdir(basedir) == -1) {
 		if (report_baddir || errno != ENOENT) {
 			exitcode = (int)MANDOCLEVEL_BADARG;
 			say("", "&chdir");
 		}
+		*basedir = '\0';
 		return 0;
 	}
 	chdir_status = 1;
-	cp = strchr(basedir, '\0');
-	if ('/' != cp[-1]) {
-		if (cp - basedir >= PATH_MAX - 1) {
+	basedir_len = strlen(basedir);
+	if (basedir[basedir_len - 1] != '/') {
+		if (basedir_len >= PATH_MAX - 1) {
 			exitcode = (int)MANDOCLEVEL_SYSERR;
 			say("", "Filename too long");
+			*basedir = '\0';
+			basedir_len = 0;
 			return 0;
 		}
-		*cp++ = '/';
-		*cp = '\0';
+		basedir[basedir_len++] = '/';
+		basedir[basedir_len] = '\0';
 	}
 	return 1;
 }
 
+#ifdef READ_ALLOWED_PATH
+static int
+read_allowed(const char *candidate)
+{
+	const char	*cp;
+	size_t		 len;
+
+	for (cp = READ_ALLOWED_PATH;; cp += len) {
+		while (*cp == ':')
+			cp++;
+		if (*cp == '\0')
+			return 0;
+		len = strcspn(cp, ":");
+		if (strncmp(candidate, cp, len) == 0)
+			return 1;
+	}
+}
+#endif
+
 static void
 say(const char *file, const char *format, ...)
 {
 	va_list		 ap;
 	int		 use_errno;
 
-	if ('\0' != *basedir)
+	if (*basedir != '\0')
 		fprintf(stderr, "%s", basedir);
-	if ('\0' != *basedir && '\0' != *file)
+	if (*basedir != '\0' && *file != '\0')
 		fputc('/', stderr);
-	if ('\0' != *file)
+	if (*file != '\0')
 		fprintf(stderr, "%s", file);
 
 	use_errno = 1;
-	if (NULL != format) {
+	if (format != NULL) {
 		switch (*format) {
 		case '&':
 			format++;
@@ -2351,15 +2432,15 @@ say(const char *file, const char *format, ...)
 			break;
 		}
 	}
-	if (NULL != format) {
-		if ('\0' != *basedir || '\0' != *file)
+	if (format != NULL) {
+		if (*basedir != '\0' || *file != '\0')
 			fputs(": ", stderr);
 		va_start(ap, format);
 		vfprintf(stderr, format, ap);
 		va_end(ap);
 	}
 	if (use_errno) {
-		if ('\0' != *basedir || '\0' != *file || NULL != format)
+		if (*basedir != '\0' || *file != '\0' || format != NULL)
 			fputs(": ", stderr);
 		perror(NULL);
 	} else
diff --git a/manpath.c b/manpath.c
index e4578ccec364..255d748246d6 100644
--- a/manpath.c
+++ b/manpath.c
@@ -1,4 +1,4 @@
-/*	$Id: manpath.c,v 1.40 2019/07/10 19:39:01 schwarze Exp $ */
+/*	$Id: manpath.c,v 1.43 2020/08/27 14:59:47 schwarze Exp $ */
 /*
  * Copyright (c) 2011,2014,2015,2017-2019 Ingo Schwarze 
  * Copyright (c) 2011 Kristaps Dzonsons 
@@ -163,7 +163,7 @@ manconf_free(struct manconf *conf)
 static void
 manconf_file(struct manconf *conf, const char *file)
 {
-	const char *const toks[] = { "manpath", "output", "_whatdb" };
+	const char *const toks[] = { "manpath", "output" };
 	char manpath_default[] = MANPATH_DEFAULT;
 
 	FILE		*stream;
@@ -200,13 +200,6 @@ manconf_file(struct manconf *conf, const char *file)
 		}
 
 		switch (tok) {
-		case 2:  /* _whatdb */
-			while (ep > cp && ep[-1] != '/')
-				ep--;
-			if (ep == cp)
-				continue;
-			*ep = '\0';
-			/* FALLTHROUGH */
 		case 0:  /* manpath */
 			manpath_add(&conf->manpath, cp, '\0');
 			*manpath_default = '\0';
@@ -230,8 +223,13 @@ int
 manconf_output(struct manoutput *conf, const char *cp, int fromfile)
 {
 	const char *const toks[] = {
+	    /* Tokens requiring an argument. */
 	    "includes", "man", "paper", "style", "indent", "width",
-	    "tag", "fragment", "mdoc", "noval", "toc"
+	    "outfilename", "tagfilename",
+	    /* Token taking an optional argument. */
+	    "tag",
+	    /* Tokens not taking arguments. */
+	    "fragment", "mdoc", "noval", "toc"
 	};
 	const size_t ntoks = sizeof(toks) / sizeof(toks[0]);
 
@@ -252,11 +250,11 @@ manconf_output(struct manoutput *conf, const char *cp, int fromfile)
 		}
 	}
 
-	if (tok < 6 && *cp == '\0') {
+	if (tok < 8 && *cp == '\0') {
 		mandoc_msg(MANDOCERR_BADVAL_MISS, 0, 0, "-O %s=?", toks[tok]);
 		return -1;
 	}
-	if (tok > 6 && tok < ntoks && *cp != '\0') {
+	if (tok > 8 && tok < ntoks && *cp != '\0') {
 		mandoc_msg(MANDOCERR_BADVAL, 0, 0, "-O %s=%s", toks[tok], cp);
 		return -1;
 	}
@@ -313,22 +311,40 @@ manconf_output(struct manoutput *conf, const char *cp, int fromfile)
 		    "-O width=%s is %s", cp, errstr);
 		return -1;
 	case 6:
+		if (conf->outfilename != NULL) {
+			oldval = mandoc_strdup(conf->outfilename);
+			break;
+		}
+		conf->outfilename = mandoc_strdup(cp);
+		return 0;
+	case 7:
+		if (conf->tagfilename != NULL) {
+			oldval = mandoc_strdup(conf->tagfilename);
+			break;
+		}
+		conf->tagfilename = mandoc_strdup(cp);
+		return 0;
+	/*
+	 * If the index of the following token changes,
+	 * do not forget to adjust the range check above the switch.
+	 */
+	case 8:
 		if (conf->tag != NULL) {
 			oldval = mandoc_strdup(conf->tag);
 			break;
 		}
 		conf->tag = mandoc_strdup(cp);
 		return 0;
-	case 7:
+	case 9:
 		conf->fragment = 1;
 		return 0;
-	case 8:
+	case 10:
 		conf->mdoc = 1;
 		return 0;
-	case 9:
+	case 11:
 		conf->noval = 1;
 		return 0;
-	case 10:
+	case 12:
 		conf->toc = 1;
 		return 0;
 	default:
diff --git a/mdoc.7 b/mdoc.7
index deca4ba88c05..7ac6ff11c00d 100644
--- a/mdoc.7
+++ b/mdoc.7
@@ -1,7 +1,7 @@
-.\"	$Id: mdoc.7,v 1.279 2019/07/15 19:20:30 schwarze Exp $
+.\"	$Id: mdoc.7,v 1.287 2021/07/29 17:32:01 schwarze Exp $
 .\"
 .\" Copyright (c) 2009, 2010, 2011 Kristaps Dzonsons 
-.\" Copyright (c) 2010, 2011, 2013-2018 Ingo Schwarze 
+.\" Copyright (c) 2010, 2011, 2013-2020 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 15 2019 $
+.Dd $Mdocdate: July 29 2021 $
 .Dt MDOC 7
 .Os
 .Sh NAME
@@ -297,7 +297,7 @@ utility does this, that, and the other.
 It usually follows with a breakdown of the options (if documenting a
 command), such as:
 .Bd -literal -offset indent
-The arguments are as follows:
+The options are as follows:
 \&.Bl \-tag \-width Ds
 \&.It Fl v
 Print verbose information.
@@ -449,6 +449,7 @@ in the alphabetical
 .It Ic \&Ss Ta subsection header (one line)
 .It Ic \&Sx Ta internal cross reference to a section or subsection
 .It Ic \&Xr Ta cross reference to another manual page: Ar name section
+.It Ic \&Tg Ta tag the definition of a Ar term Pq <= 1 arguments
 .It Ic \&Pp Ta start a text paragraph (no arguments)
 .El
 .Ss Displays and lists
@@ -631,6 +632,7 @@ Close an
 .Ic \&Ao
 block.
 Does not have any tail arguments.
+.Tg Ad
 .It Ic \&Ad Ar address
 Memory address.
 Do not use this for postal addresses.
@@ -638,6 +640,7 @@ Do not use this for postal addresses.
 Examples:
 .Dl \&.Ad [0,$]
 .Dl \&.Ad 0x00000000
+.Tg An
 .It Ic \&An Fl split | nosplit | Ar first_name ... last_name
 Author name.
 Can be used both for the authors of the program, function, or driver
@@ -678,6 +681,7 @@ This macro is almost never useful.
 See
 .Ic \&Aq
 for more details.
+.Tg Ap
 .It Ic \&Ap
 Inserts an apostrophe without any surrounding whitespace.
 This is generally used as a grammatical device when referring to the verb
@@ -685,6 +689,7 @@ form of a function.
 .Pp
 Examples:
 .Dl \&.Fn execve \&Ap d
+.Tg Aq
 .It Ic \&Aq Ar line
 Enclose the rest of the input line in angle brackets.
 The only important use case is for email addresses.
@@ -729,6 +734,7 @@ as needed.
 .Pp
 See also
 .Ic \&Ao .
+.Tg Ar
 .It Ic \&Ar Op Ar placeholder ...
 Command arguments.
 If an argument is not provided, the string
@@ -747,6 +753,7 @@ for fixed strings to be passed verbatim as arguments, use
 .Ic \&Fl
 or
 .Ic \&Cm .
+.Tg At
 .It Ic \&At Op Ar version
 Formats an
 .At
@@ -784,6 +791,7 @@ Close a
 .Ic \&Bo
 block.
 Does not have any tail arguments.
+.Tg Bd
 .It Ic \&Bd Fl Ns Ar type Oo Fl offset Ar width Oc Op Fl compact
 Begin a display block.
 Display blocks are used to select a different indentation and
@@ -874,6 +882,7 @@ See also
 .Ic \&D1
 and
 .Ic \&Dl .
+.Tg Bf
 .It Ic \&Bf Fl emphasis | literal | symbolic | Cm \&Em | \&Li | \&Sy
 Change the font mode for a scoped block of text.
 The
@@ -900,6 +909,7 @@ See also
 .Ic \&Em ,
 and
 .Ic \&Sy .
+.Tg Bk
 .It Ic \&Bk Fl words
 For each macro, keep its output together on the same output line,
 until the end of the macro or the end of the input line is reached,
@@ -922,6 +932,7 @@ macro line:
 .Pp
 Be careful in using over-long lines within a keep block!
 Doing so will clobber the right margin.
+.Tg Bl
 .It Xo
 .Ic \&Bl
 .Fl Ns Ar type
@@ -1064,6 +1075,7 @@ Examples:
 .Pp
 See also
 .Ic \&Bq .
+.Tg Bq
 .It Ic \&Bq Ar line
 Encloses its arguments in square brackets.
 .Pp
@@ -1097,6 +1109,7 @@ Examples:
 .Pp
 See also
 .Ic \&Brq .
+.Tg Brq
 .It Ic \&Brq Ar line
 Encloses its arguments in curly braces.
 .Pp
@@ -1105,6 +1118,7 @@ Examples:
 .Pp
 See also
 .Ic \&Bro .
+.Tg Bsx
 .It Ic \&Bsx Op Ar version
 Format the
 .Bsx
@@ -1127,6 +1141,7 @@ and
 Supported only for compatibility, do not use this in new manuals.
 Prints
 .Dq is currently in beta test.
+.Tg Bx
 .It Ic \&Bx Op Ar version Op Ar variant
 Format the
 .Bx
@@ -1146,6 +1161,7 @@ See also
 .Ic \&Nx ,
 and
 .Ic \&Ox .
+.Tg Cd
 .It Ic \&Cd Ar line
 Kernel configuration declaration.
 This denotes strings accepted by
@@ -1161,6 +1177,7 @@ whitespace and align consecutive
 .Ic \&Cd
 declarations.
 This practise is discouraged.
+.Tg Cm
 .It Ic \&Cm Ar keyword ...
 Command modifiers.
 Typically used for fixed strings passed as arguments to interactive
@@ -1176,6 +1193,7 @@ Examples:
 .Dl ".Ic set Fl o Cm vi"
 .Dl ".Ic lookup Cm file bind"
 .Dl ".Ic permit Ar identity Op Cm as Ar target"
+.Tg D1
 .It Ic \&D1 Ar line
 One-line indented display.
 This is formatted by the default rules and is useful for simple indented
@@ -1201,8 +1219,10 @@ Close a
 .Ic \&Do
 block.
 Does not have any tail arguments.
+.Tg Dd
 .It Ic \&Dd Cm $\&Mdocdate$ | Ar month day , year
-Document date for display in the page footer.
+Document date for display in the page footer,
+by convention the date of the last change.
 This is the mandatory first macro of any
 .Nm
 manual.
@@ -1248,6 +1268,7 @@ See also
 .Ic \&Dt
 and
 .Ic \&Os .
+.Tg Dl
 .It Ic \&Dl Ar line
 One-line indented display.
 This is formatted as literal text and is useful for commands and
@@ -1276,6 +1297,7 @@ April is the cruellest month
 .Pp
 See also
 .Ic \&Dq .
+.Tg Dq
 .It Ic \&Dq Ar line
 Encloses its arguments in
 .Dq typographic
@@ -1292,6 +1314,7 @@ See also
 .Ic \&Sq ,
 and
 .Ic \&Do .
+.Tg Dt
 .It Ic \&Dt Ar TITLE section Op Ar arch
 Document title for display in the page header.
 This is the mandatory second macro of any
@@ -1351,6 +1374,7 @@ See also
 .Ic \&Dd
 and
 .Ic \&Os .
+.Tg Dv
 .It Ic \&Dv Ar identifier ...
 Defined variables such as preprocessor constants, constant symbols,
 enumeration values, and so on.
@@ -1370,6 +1394,7 @@ for variable symbols, and
 .Ic \&Fd
 for listing preprocessor variable definitions in the
 .Em SYNOPSIS .
+.Tg Dx
 .It Ic \&Dx Op Ar version
 Format the
 .Dx
@@ -1411,6 +1436,7 @@ End a list context started by
 .Ic \&Bl .
 See also
 .Ic \&It .
+.Tg Em
 .It Ic \&Em Ar word ...
 Request an italic font.
 If the output device does not provide that, underline.
@@ -1450,6 +1476,7 @@ or any of the other enclosure macros.
 It encloses its argument in the delimiters specified by the last
 .Ic \&Es
 macro.
+.Tg Eo
 .It Ic \&Eo Op Ar opening_delimiter
 An arbitrary enclosure.
 The
@@ -1457,6 +1484,7 @@ The
 argument is used as the enclosure head, for example, specifying \e(lq
 will emulate
 .Ic \&Do .
+.Tg Er
 .It Ic \&Er Ar identifier ...
 Error constants for definitions of the
 .Va errno
@@ -1479,6 +1507,7 @@ or any of the other enclosure macros.
 It takes two arguments, defining the delimiters to be used by subsequent
 .Ic \&En
 macros.
+.Tg Ev
 .It Ic \&Ev Ar identifier ...
 Environmental variables such as those specified in
 .Xr environ 7 .
@@ -1490,6 +1519,7 @@ Examples:
 See also
 .Ic \&Dv
 for general constants.
+.Tg Ex
 .It Ic \&Ex Fl std Op Ar utility ...
 Insert a standard sentence regarding command exit values of 0 on success
 and >0 on failure.
@@ -1506,6 +1536,7 @@ arguments are treated as separate utilities.
 .Pp
 See also
 .Ic \&Rv .
+.Tg Fa
 .It Ic \&Fa Ar argument ...
 Function argument or parameter.
 Each argument may be a name and a type (recommended for the
@@ -1543,6 +1574,7 @@ See also
 .It Ic \&Fc
 End a function context started by
 .Ic \&Fo .
+.Tg Fd
 .It Ic \&Fd Pf # Ar directive Op Ar argument ...
 Preprocessor directive, in particular for listing it in the
 .Em SYNOPSIS .
@@ -1563,25 +1595,33 @@ See also
 .Ic \&In ,
 and
 .Ic \&Dv .
+.Tg Fl
 .It Ic \&Fl Op Ar word ...
 Command-line flag or option.
 Used when listing arguments to command-line utilities.
-Prints a fixed-width hyphen
-.Sq \-
-directly followed by each argument.
-If no arguments are provided, a hyphen is printed followed by a space.
-If the argument is a macro, a hyphen is prefixed to the subsequent macro
-output.
+For each argument, prints an ASCII hyphen-minus character
+.Sq \- ,
+immediately followed by the argument.
+If no arguments are provided, a hyphen-minus is printed followed by a space.
+If the argument is a macro, a hyphen-minus is prefixed
+to the subsequent macro output.
 .Pp
 Examples:
-.Dl ".Fl R Op Fl H | L | P"
-.Dl ".Op Fl 1AaCcdFfgHhikLlmnopqRrSsTtux"
-.Dl ".Fl type Cm d Fl name Pa CVS"
-.Dl ".Fl Ar signal_number"
-.Dl ".Fl o Fl"
+.Dl ".Nm du Op Fl H | L | P"
+.Dl ".Nm ls Op Fl 1AaCcdFfgHhikLlmnopqRrSsTtux"
+.Dl ".Nm route Cm add Fl inet Ar destination gateway"
+.Dl ".Nm locate.updatedb Op Fl \e-fcodes Ns = Ns Ar dbfile"
+.Dl ".Nm aucat Fl o Fl"
+.Dl ".Nm kill Fl Ar signal_number"
+.Pp
+For GNU-sytle long options, escaping the additional hyphen-minus is not
+strictly required, but may be safer with future versions of GNU troff; see
+.Xr mandoc_char 7
+for details.
 .Pp
 See also
 .Ic \&Cm .
+.Tg Fn
 .It Ic \&Fn Ar funcname Op Ar argument ...
 A function name.
 .Pp
@@ -1610,6 +1650,7 @@ See also
 .Ic \&Fo ,
 and
 .Ic \&Ft .
+.Tg Fo
 .It Ic \&Fo Ar funcname
 Begin a function block.
 This is a multi-line version of
@@ -1644,6 +1685,7 @@ This macro is obsolete.
 No replacement markup is needed.
 .Pp
 It was used to show numerical function return values in an italic font.
+.Tg Ft
 .It Ic \&Ft Ar functype
 A function type.
 .Pp
@@ -1663,6 +1705,7 @@ See also
 .Ic \&Fn ,
 and
 .Ic \&Fo .
+.Tg Fx
 .It Ic \&Fx Op Ar version
 Format the
 .Fx
@@ -1685,6 +1728,7 @@ and
 This macro is not implemented in
 .Xr mandoc 1 .
 It was used to include the contents of a (header) file literally.
+.Tg Ic
 .It Ic \&Ic Ar keyword ...
 Internal or interactive command, or configuration instruction
 in a configuration file.
@@ -1704,6 +1748,7 @@ or
 is preferred for displaying code samples; the
 .Ic \&Ic
 macro is used when referring to an individual command name.
+.Tg In
 .It Ic \&In Ar filename
 The name of an include file.
 This macro is most often used in section 2, 3, and 9 manual pages.
@@ -1723,6 +1768,7 @@ Examples:
 .Pp
 See also
 .Sx MANUAL STRUCTURE .
+.Tg It
 .It Ic \&It Op Ar head
 A list item.
 The syntax of this macro depends on the list type.
@@ -1813,6 +1859,7 @@ but not the whitespace before the semicolon.
 .Pp
 See also
 .Ic \&Bl .
+.Tg Lb
 .It Ic \&Lb Cm lib Ns Ar name
 Specify a library.
 .Pp
@@ -1833,6 +1880,7 @@ section as described in
 Examples:
 .Dl \&.Lb libz
 .Dl \&.Lb libmandoc
+.Tg Li
 .It Ic \&Li Ar word ...
 Request a typewriter (literal) font.
 Deprecated because on terminal output devices, this is usually
@@ -1843,24 +1891,27 @@ For literal displays, use
 or
 .Ic \&Bd Fl literal Pq multi-line
 instead.
+.Tg Lk
 .It Ic \&Lk Ar uri Op Ar display_name
 Format a hyperlink.
 .Pp
 Examples:
-.Dl \&.Lk http://bsd.lv \(dqThe BSD.lv Project\(dq
-.Dl \&.Lk http://bsd.lv
+.Dl \&.Lk https://bsd.lv \(dqThe BSD.lv Project\(dq
+.Dl \&.Lk https://bsd.lv
 .Pp
 See also
 .Ic \&Mt .
 .It Ic \&Lp
 Deprecated synonym for
 .Ic \&Pp .
+.Tg Ms
 .It Ic \&Ms Ar name
 Display a mathematical symbol.
 .Pp
 Examples:
 .Dl \&.Ms sigma
 .Dl \&.Ms aleph
+.Tg Mt
 .It Ic \&Mt Ar localpart Ns @ Ns Ar domain
 Format a
 .Dq mailto:
@@ -1869,6 +1920,7 @@ hyperlink.
 Examples:
 .Dl \&.Mt discuss@manpages.bsd.lv
 .Dl \&.An Kristaps Dzonsons \&Aq \&Mt kristaps@bsd.lv
+.Tg Nd
 .It Ic \&Nd Ar line
 A one line description of the manual's content.
 This is the mandatory last macro of the
@@ -1891,6 +1943,7 @@ arguments and will display macros verbatim.
 .Pp
 See also
 .Ic \&Nm .
+.Tg Nm
 .It Ic \&Nm Op Ar name
 The name of the manual page, or \(em in particular in section 1, 6,
 and 8 pages \(em of an additional command or feature documented in
@@ -1928,6 +1981,7 @@ of section 2, 3 and 9 manual pages, use the
 macro rather than
 .Ic \&Nm
 to mark up the name of the manual page.
+.Tg No
 .It Ic \&No Ar word ...
 Normal text.
 Closes the scope of any preceding in-line macro.
@@ -1952,6 +2006,7 @@ See also
 .Ic \&Ql ,
 and
 .Ic \&Sy .
+.Tg Ns
 .It Ic \&Ns
 Suppress a space between the output of the preceding macro
 and the following text or macro.
@@ -1971,6 +2026,7 @@ See also
 .Ic \&No
 and
 .Ic \&Sm .
+.Tg Nx
 .It Ic \&Nx Op Ar version
 Format the
 .Nx
@@ -2003,6 +2059,7 @@ Examples:
 \&.Op Fl flag Ns Ar value
 \&.Oc
 .Ed
+.Tg Op
 .It Ic \&Op Ar line
 Optional part of a command line.
 Prints the argument(s) in brackets.
@@ -2016,6 +2073,7 @@ Examples:
 .Pp
 See also
 .Ic \&Oo .
+.Tg Os
 .It Ic \&Os Op Ar system Op Ar version
 Operating system version for display in the page footer.
 This is the mandatory third macro of
@@ -2058,6 +2116,7 @@ Historical
 .Nm
 packages described it as
 .Dq "old function type (FORTRAN)" .
+.Tg Ox
 .It Ic \&Ox Op Ar version
 Format the
 .Ox
@@ -2076,6 +2135,7 @@ See also
 .Ic \&Fx ,
 and
 .Ic \&Nx .
+.Tg Pa
 .It Ic \&Pa Ar name ...
 An absolute or relative file system path, or a file or directory name.
 If an argument is not provided, the character
@@ -2091,6 +2151,7 @@ See also
 .It Ic \&Pc
 Close parenthesised context opened by
 .Ic \&Po .
+.Tg Pf
 .It Ic \&Pf Ar prefix macro Op Ar argument ...
 Removes the space between its argument and the following macro.
 It is equivalent to:
@@ -2114,6 +2175,7 @@ and
 .It Ic \&Po Ar block
 Multi-line version of
 .Ic \&Pq .
+.Tg Pp
 .It Ic \&Pp
 Break a paragraph.
 This will assert vertical space between prior and subsequent macros
@@ -2130,6 +2192,7 @@ or lists
 unless the
 .Fl compact
 flag is given.
+.Tg Pq
 .It Ic \&Pq Ar line
 Parenthesised enclosure.
 .Pp
@@ -2138,6 +2201,7 @@ See also
 .It Ic \&Qc
 Close quoted context opened by
 .Ic \&Qo .
+.Tg Ql
 .It Ic \&Ql Ar line
 In-line literal display.
 This can be used for complete command invocations and for multi-word
@@ -2151,6 +2215,7 @@ and
 .It Ic \&Qo Ar block
 Multi-line version of
 .Ic \&Qq .
+.Tg Qq
 .It Ic \&Qq Ar line
 Encloses its arguments in
 .Qq typewriter
@@ -2168,6 +2233,7 @@ Close an
 .Ic \&Rs
 block.
 Does not have any tail arguments.
+.Tg Rs
 .It Ic \&Rs
 Begin a bibliographic
 .Pq Dq reference
@@ -2208,6 +2274,7 @@ If an
 block is used within a SEE ALSO section, a vertical space is asserted
 before the rendered output, else the block continues on the current
 line.
+.Tg Rv
 .It Ic \&Rv Fl std Op Ar function ...
 Insert a standard sentence regarding a function call's return value of 0
 on success and \-1 on error, with the
@@ -2228,6 +2295,7 @@ See also
 .It Ic \&Sc
 Close single-quoted context opened by
 .Ic \&So .
+.Tg Sh
 .It Ic \&Sh Ar TITLE LINE
 Begin a new section.
 For a list of conventional manual sections, see
@@ -2246,6 +2314,7 @@ See also
 .Ic \&Ss ,
 and
 .Ic \&Sx .
+.Tg Sm
 .It Ic \&Sm Op Cm on | off
 Switches the spacing mode for output generated from macros.
 .Pp
@@ -2264,6 +2333,7 @@ Using this is not recommended because it makes the code harder to read.
 .It Ic \&So Ar block
 Multi-line version of
 .Ic \&Sq .
+.Tg Sq
 .It Ic \&Sq Ar line
 Encloses its arguments in
 .Sq typewriter
@@ -2274,6 +2344,7 @@ See also
 .Ic \&Qq ,
 and
 .Ic \&So .
+.Tg Ss
 .It Ic \&Ss Ar Title line
 Begin a new subsection.
 Unlike with
@@ -2296,6 +2367,7 @@ See also
 .Ic \&Sh ,
 and
 .Ic \&Sx .
+.Tg St
 .It Ic \&St Fl Ns Ar abbreviation
 Replace an abbreviation for a standard with the full form.
 The following standards are recognised.
@@ -2506,6 +2578,7 @@ Ethernet local area networks.
 .St -ieee1275-94
 .El
 .El
+.Tg Sx
 .It Ic \&Sx Ar Title line
 Reference a section or subsection in the same manual page.
 The referenced section or subsection name must be identical to the
@@ -2518,6 +2591,7 @@ See also
 .Ic \&Sh
 and
 .Ic \&Ss .
+.Tg Sy
 .It Ic \&Sy Ar word ...
 Request a boldface font.
 .Pp
@@ -2543,11 +2617,56 @@ See also
 .Ic \&No ,
 and
 .Ic \&Ql .
+.Tg Ta
 .It Ic \&Ta
 Table cell separator in
 .Ic \&Bl Fl column
 lists; can only be used below
 .Ic \&It .
+.Tg Tg
+.It Ic \&Tg Op Ar term
+Announce that the next input line starts a definition of the
+.Ar term .
+This macro must appear alone on its own input line.
+The argument defaults to the first argument of the first macro
+on the next line.
+The argument may not contain whitespace characters, not even when it is quoted.
+This macro is a
+.Xr mandoc 1
+extension and is typically ignored by other formatters.
+.Pp
+When viewing terminal output with
+.Xr less 1 ,
+the interactive
+.Ic :t
+command can be used to go to the definition of the
+.Ar term
+as described for the
+.Ev MANPAGER
+variable in
+.Xr man 1 ;
+when producing HTML output, a fragment identifier
+.Pq Ic id No attribute
+is generated, to be used for deep linking to this place of the document.
+.Pp
+In most cases, adding a
+.Ic \&Tg
+macro would be redundant because
+.Xr mandoc 1
+is able to automatically tag most definitions.
+This macro is intended for cases where automatic tagging of a
+.Ar term
+is unsatisfactory, for example if a definition is not tagged
+automatically (false negative) or if places are tagged that do
+not define the
+.Ar term
+(false positives).
+When there is at least one
+.Ic \&Tg
+macro for a
+.Ar term ,
+no other places are automatically marked as definitions of that
+.Ar term .
 .It Ic \&Tn Ar word ...
 Supported only for compatibility, do not use this in new manuals.
 Even though the macro name
@@ -2562,6 +2681,7 @@ Prints out
 Supported only for compatibility, do not use this in new manuals.
 Prints out
 .Dq Ux .
+.Tg Va
 .It Ic \&Va Oo Ar type Oc Ar identifier ...
 A variable name.
 .Pp
@@ -2576,6 +2696,7 @@ For declarations of global variables in the
 .Em SYNOPSIS
 section, use
 .Ic \&Vt .
+.Tg Vt
 .It Ic \&Vt Ar type Op Ar identifier
 A variable type.
 .Pp
@@ -2619,6 +2740,7 @@ beyond the end of the input line.
 This macro originally existed to work around the 9-argument limit
 of historic
 .Xr roff 7 .
+.Tg Xr
 .It Ic \&Xr Ar name section
 Link to another manual
 .Pq Qq cross-reference .
@@ -2681,7 +2803,7 @@ column, if applicable, describes closure rules.
 .Ss Block full-explicit
 Multi-line scope closed by an explicit closing macro.
 All macros contains bodies; only
-.Ic \s&Bf
+.Ic \&Bf
 and
 .Pq optionally
 .Ic \&Bl
@@ -2912,6 +3034,7 @@ then the macro accepts an arbitrary number of arguments.
 .It Ic \&St  Ta    \&No     Ta    Yes      Ta    1
 .It Ic \&Sx  Ta    Yes      Ta    Yes      Ta    >0
 .It Ic \&Sy  Ta    Yes      Ta    Yes      Ta    >0
+.It Ic \&Tg  Ta    \&No     Ta    \&No     Ta    <2
 .It Ic \&Tn  Ta    Yes      Ta    Yes      Ta    >0
 .It Ic \&Ud  Ta    \&No     Ta    \&No     Ta    0
 .It Ic \&Ux  Ta    Yes      Ta    Yes      Ta    n
@@ -3039,17 +3162,6 @@ The following problematic behaviour is found in groff:
 .Pp
 .Bl -dash -compact
 .It
-.Ic \&Dd
-with non-standard arguments behaves very strangely.
-When there are three arguments, they are printed verbatim.
-Any other number of arguments is replaced by the current date,
-but without any arguments the string
-.Dq Epoch
-is printed.
-.It
-.Ic \&Lk
-only accepts a single link-name argument; the remainder is misformatted.
-.It
 .Ic \&Pa
 does not format its arguments when used in the FILES section under
 certain list types.
@@ -3057,9 +3169,6 @@ certain list types.
 .Ic \&Ta
 can only be called by other macros, but not at the beginning of a line.
 .It
-.Ic \&%C
-is not implemented (up to and including groff-1.22.2).
-.It
 .Sq \ef
 .Pq font face
 and
@@ -3109,10 +3218,16 @@ but produces large indentations.
 .Xr tbl 7
 .Pp
 The web page
-.Lk http://mandoc.bsd.lv/mdoc/ "extended documentation for the mdoc language"
+.Lk https://mandoc.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.
+.Pp
+The manual page
+.Lk https://man.voidlinux.org/groff_mdoc "groff_mdoc(7)"
+contained in the
+.Dq groff
+package documents exactly the same language in a somewhat different style.
 .Sh HISTORY
 The
 .Nm
diff --git a/mdoc.c b/mdoc.c
index bb3ec58bd0f7..475a6aa0ddb3 100644
--- a/mdoc.c
+++ b/mdoc.c
@@ -1,7 +1,7 @@
-/*	$Id: mdoc.c,v 1.274 2018/12/31 07:46:07 schwarze Exp $ */
+/* $Id: mdoc.c,v 1.275 2020/04/06 10:16:17 schwarze Exp $ */
 /*
+ * Copyright (c) 2010, 2012-2018, 2020 Ingo Schwarze 
  * Copyright (c) 2008, 2009, 2010, 2011 Kristaps Dzonsons 
- * Copyright (c) 2010, 2012-2018 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,6 +14,8 @@
  * 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.
+ *
+ * Top level and utility functions of the mdoc(7) parser for mandoc(1).
  */
 #include "config.h"
 
@@ -352,12 +354,13 @@ mdoc_pmacro(struct roff_man *mdoc, int ln, char *buf, int offs)
 		mandoc_msg(MANDOCERR_SPACE_EOL, ln, offs - 1, NULL);
 
 	/*
-	 * If an initial macro or a list invocation, divert directly
-	 * into macro processing.
+	 * If an initial or transparent macro or a list invocation,
+	 * divert directly into macro processing.
 	 */
 
 	n = mdoc->last;
-	if (n == NULL || tok == MDOC_It || tok == MDOC_El) {
+	if (n == NULL || tok == MDOC_It || tok == MDOC_El ||
+	    roff_tok_transparent(tok)) {
 		(*mdoc_macro(tok)->fp)(mdoc, tok, ln, sv, &offs, buf);
 		return 1;
 	}
diff --git a/mdoc_html.c b/mdoc_html.c
index 87bf42a72ba0..c0a0a6a56978 100644
--- a/mdoc_html.c
+++ b/mdoc_html.c
@@ -1,7 +1,7 @@
-/*	$Id: mdoc_html.c,v 1.328 2019/03/01 10:57:18 schwarze Exp $ */
+/* $Id: mdoc_html.c,v 1.342 2021/03/30 19:26:20 schwarze Exp $ */
 /*
+ * Copyright (c) 2014-2021 Ingo Schwarze 
  * Copyright (c) 2008-2011, 2014 Kristaps Dzonsons 
- * Copyright (c) 2014-2019 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,6 +14,8 @@
  * 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.
+ *
+ * HTML formatter for mdoc(7) used by mandoc(1).
  */
 #include "config.h"
 
@@ -47,13 +49,11 @@ struct	mdoc_html_act {
 	void		(*post)(MDOC_ARGS);
 };
 
-static	char		 *cond_id(const struct roff_node *);
 static	void		  print_mdoc_head(const struct roff_meta *,
 				struct html *);
 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		  synopsis_pre(struct html *, struct roff_node *);
 
 static	void		  mdoc_root_post(const struct roff_meta *,
 				struct html *);
@@ -73,9 +73,8 @@ static	void		  mdoc_bk_post(MDOC_ARGS);
 static	int		  mdoc_bk_pre(MDOC_ARGS);
 static	int		  mdoc_bl_pre(MDOC_ARGS);
 static	int		  mdoc_cd_pre(MDOC_ARGS);
-static	int		  mdoc_cm_pre(MDOC_ARGS);
+static	int		  mdoc_code_pre(MDOC_ARGS);
 static	int		  mdoc_d1_pre(MDOC_ARGS);
-static	int		  mdoc_dv_pre(MDOC_ARGS);
 static	int		  mdoc_fa_pre(MDOC_ARGS);
 static	int		  mdoc_fd_pre(MDOC_ARGS);
 static	int		  mdoc_fl_pre(MDOC_ARGS);
@@ -84,20 +83,15 @@ static	int		  mdoc_ft_pre(MDOC_ARGS);
 static	int		  mdoc_em_pre(MDOC_ARGS);
 static	void		  mdoc_eo_post(MDOC_ARGS);
 static	int		  mdoc_eo_pre(MDOC_ARGS);
-static	int		  mdoc_er_pre(MDOC_ARGS);
-static	int		  mdoc_ev_pre(MDOC_ARGS);
 static	int		  mdoc_ex_pre(MDOC_ARGS);
 static	void		  mdoc_fo_post(MDOC_ARGS);
 static	int		  mdoc_fo_pre(MDOC_ARGS);
-static	int		  mdoc_ic_pre(MDOC_ARGS);
 static	int		  mdoc_igndelim_pre(MDOC_ARGS);
 static	int		  mdoc_in_pre(MDOC_ARGS);
 static	int		  mdoc_it_pre(MDOC_ARGS);
 static	int		  mdoc_lb_pre(MDOC_ARGS);
-static	int		  mdoc_li_pre(MDOC_ARGS);
 static	int		  mdoc_lk_pre(MDOC_ARGS);
 static	int		  mdoc_mt_pre(MDOC_ARGS);
-static	int		  mdoc_ms_pre(MDOC_ARGS);
 static	int		  mdoc_nd_pre(MDOC_ARGS);
 static	int		  mdoc_nm_pre(MDOC_ARGS);
 static	int		  mdoc_no_pre(MDOC_ARGS);
@@ -115,6 +109,7 @@ static	int		  mdoc_ss_pre(MDOC_ARGS);
 static	int		  mdoc_st_pre(MDOC_ARGS);
 static	int		  mdoc_sx_pre(MDOC_ARGS);
 static	int		  mdoc_sy_pre(MDOC_ARGS);
+static	int		  mdoc_tg_pre(MDOC_ARGS);
 static	int		  mdoc_va_pre(MDOC_ARGS);
 static	int		  mdoc_vt_pre(MDOC_ARGS);
 static	int		  mdoc_xr_pre(MDOC_ARGS);
@@ -139,19 +134,19 @@ static const struct mdoc_html_act mdoc_html_acts[MDOC_MAX - MDOC_Dd] = {
 	{mdoc_ap_pre, NULL}, /* Ap */
 	{mdoc_ar_pre, NULL}, /* Ar */
 	{mdoc_cd_pre, NULL}, /* Cd */
-	{mdoc_cm_pre, NULL}, /* Cm */
-	{mdoc_dv_pre, NULL}, /* Dv */
-	{mdoc_er_pre, NULL}, /* Er */
-	{mdoc_ev_pre, NULL}, /* Ev */
+	{mdoc_code_pre, NULL}, /* Cm */
+	{mdoc_code_pre, NULL}, /* Dv */
+	{mdoc_code_pre, NULL}, /* Er */
+	{mdoc_code_pre, NULL}, /* Ev */
 	{mdoc_ex_pre, NULL}, /* Ex */
 	{mdoc_fa_pre, NULL}, /* Fa */
 	{mdoc_fd_pre, NULL}, /* Fd */
 	{mdoc_fl_pre, NULL}, /* Fl */
 	{mdoc_fn_pre, NULL}, /* Fn */
 	{mdoc_ft_pre, NULL}, /* Ft */
-	{mdoc_ic_pre, NULL}, /* Ic */
+	{mdoc_code_pre, NULL}, /* Ic */
 	{mdoc_in_pre, NULL}, /* In */
-	{mdoc_li_pre, NULL}, /* Li */
+	{mdoc_code_pre, NULL}, /* Li */
 	{mdoc_nd_pre, NULL}, /* Nd */
 	{mdoc_nm_pre, NULL}, /* Nm */
 	{mdoc_quote_pre, mdoc_quote_post}, /* Op */
@@ -192,7 +187,7 @@ static const struct mdoc_html_act mdoc_html_acts[MDOC_MAX - MDOC_Dd] = {
 	{mdoc_em_pre, NULL}, /* Em */
 	{mdoc_eo_pre, mdoc_eo_post}, /* Eo */
 	{mdoc_xx_pre, NULL}, /* Fx */
-	{mdoc_ms_pre, NULL}, /* Ms */
+	{mdoc_no_pre, NULL}, /* Ms */
 	{mdoc_no_pre, NULL}, /* No */
 	{mdoc_ns_pre, NULL}, /* Ns */
 	{mdoc_xx_pre, NULL}, /* Nx */
@@ -241,6 +236,7 @@ static const struct mdoc_html_act mdoc_html_acts[MDOC_MAX - MDOC_Dd] = {
 	{mdoc__x_pre, mdoc__x_post}, /* %Q */
 	{mdoc__x_pre, mdoc__x_post}, /* %U */
 	{NULL, NULL}, /* Ta */
+	{mdoc_tg_pre, NULL}, /* Tg */
 };
 
 
@@ -248,13 +244,15 @@ static const struct mdoc_html_act mdoc_html_acts[MDOC_MAX - MDOC_Dd] = {
  * See the same function in mdoc_term.c for documentation.
  */
 static void
-synopsis_pre(struct html *h, const struct roff_node *n)
+synopsis_pre(struct html *h, struct roff_node *n)
 {
+	struct roff_node *np;
 
-	if (NULL == n->prev || ! (NODE_SYNPRETTY & n->flags))
+	if ((n->flags & NODE_SYNPRETTY) == 0 ||
+	    (np = roff_node_prev(n)) == NULL)
 		return;
 
-	if (n->prev->tok == n->tok &&
+	if (np->tok == n->tok &&
 	    MDOC_Fo != n->tok &&
 	    MDOC_Ft != n->tok &&
 	    MDOC_Fn != n->tok) {
@@ -262,7 +260,7 @@ synopsis_pre(struct html *h, const struct roff_node *n)
 		return;
 	}
 
-	switch (n->prev->tok) {
+	switch (np->tok) {
 	case MDOC_Fd:
 	case MDOC_Fn:
 	case MDOC_Fo:
@@ -351,30 +349,40 @@ print_mdoc_node(MDOC_ARGS)
 	if (n->type == ROFFT_COMMENT || n->flags & NODE_NOPRT)
 		return;
 
-	html_fillmode(h, n->flags & NODE_NOFILL ? ROFF_nf : ROFF_fi);
+	if ((n->flags & NODE_NOFILL) == 0)
+		html_fillmode(h, ROFF_fi);
+	else if (html_fillmode(h, ROFF_nf) == ROFF_nf &&
+	    n->tok != ROFF_fi && n->flags & NODE_LINE)
+		print_endline(h);
 
 	child = 1;
 	n->flags &= ~NODE_ENDED;
 	switch (n->type) {
 	case ROFFT_TEXT:
+		if (n->flags & NODE_LINE) {
+			switch (*n->string) {
+			case '\0':
+				h->col = 1;
+				print_endline(h);
+				return;
+			case ' ':
+				if ((h->flags & HTML_NONEWLINE) == 0 &&
+				    (n->flags & NODE_NOFILL) == 0)
+					print_otag(h, TAG_BR, "");
+				break;
+			default:
+				break;
+			}
+		}
 		t = h->tag;
 		t->refcnt++;
-
-		/* No tables in this mode... */
-		assert(NULL == h->tblt);
-
-		/*
-		 * Make sure that if we're in a literal mode already
-		 * (i.e., within a 
) don't print the newline.
-		 */
-		if (*n->string == ' ' && n->flags & NODE_LINE &&
-		    (h->flags & HTML_NONEWLINE) == 0 &&
-		    (n->flags & NODE_NOFILL) == 0)
-			print_otag(h, TAG_BR, "");
-		if (NODE_DELIMC & n->flags)
+		if (n->flags & NODE_DELIMC)
 			h->flags |= HTML_NOSPACE;
-		print_text(h, n->string);
-		if (NODE_DELIMO & n->flags)
+		if (n->flags & NODE_HREF)
+			print_tagged_text(h, n->string, n);
+		else
+			print_text(h, n->string);
+		if (n->flags & NODE_DELIMO)
 			h->flags |= HTML_NOSPACE;
 		break;
 	case ROFFT_EQN:
@@ -439,12 +447,6 @@ print_mdoc_node(MDOC_ARGS)
 			n->body->flags |= NODE_ENDED;
 		break;
 	}
-
-	if (n->flags & NODE_NOFILL &&
-	    (n->next == NULL || n->next->flags & NODE_LINE)) {
-		h->col++;
-		print_endline(h);
-	}
 }
 
 static void
@@ -502,20 +504,11 @@ mdoc_root_pre(const struct roff_meta *meta, struct html *h)
 	return 1;
 }
 
-static char *
-cond_id(const struct roff_node *n)
+static int
+mdoc_code_pre(MDOC_ARGS)
 {
-	if (n->child != NULL &&
-	    n->child->type == ROFFT_TEXT &&
-	    (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 &&
-	      n->parent->parent->parent->tok == MDOC_It)))
-		return html_make_id(n, 1);
-	return NULL;
+	print_otag_id(h, TAG_CODE, roff_name[n->tok], n);
+	return 1;
 }
 
 static int
@@ -578,10 +571,7 @@ mdoc_sh_pre(MDOC_ARGS)
 		print_otag(h, TAG_SECTION, "c", "Sh");
 		break;
 	case ROFFT_HEAD:
-		id = html_make_id(n, 1);
-		print_otag(h, TAG_H1, "ci", "Sh", id);
-		if (id != NULL)
-			print_otag(h, TAG_A, "chR", "permalink", id);
+		print_otag_id(h, TAG_H1, "Sh", n);
 		break;
 	case ROFFT_BODY:
 		if (n->sec == SEC_AUTHORS)
@@ -596,64 +586,43 @@ mdoc_sh_pre(MDOC_ARGS)
 static int
 mdoc_ss_pre(MDOC_ARGS)
 {
-	char	*id;
-
 	switch (n->type) {
 	case ROFFT_BLOCK:
 		html_close_paragraph(h);
 		print_otag(h, TAG_SECTION, "c", "Ss");
-		return 1;
+		break;
 	case ROFFT_HEAD:
+		print_otag_id(h, TAG_H2, "Ss", n);
 		break;
 	case ROFFT_BODY:
-		return 1;
+		break;
 	default:
 		abort();
 	}
-
-	id = html_make_id(n, 1);
-	print_otag(h, TAG_H2, "ci", "Ss", id);
-	if (id != NULL)
-		print_otag(h, TAG_A, "chR", "permalink", id);
 	return 1;
 }
 
 static int
 mdoc_fl_pre(MDOC_ARGS)
 {
-	char	*id;
-
-	if ((id = cond_id(n)) != NULL)
-		print_otag(h, TAG_A, "chR", "permalink", id);
-	print_otag(h, TAG_CODE, "ci", "Fl", id);
+	struct roff_node	*nn;
 
+	print_otag_id(h, TAG_CODE, "Fl", n);
 	print_text(h, "\\-");
-	if (!(n->child == NULL &&
-	    (n->next == NULL ||
-	     n->next->type == ROFFT_TEXT ||
-	     n->next->flags & NODE_LINE)))
+	if (n->child != NULL ||
+	    ((nn = roff_node_next(n)) != NULL &&
+	     nn->type != ROFFT_TEXT &&
+	     (nn->flags & NODE_LINE) == 0))
 		h->flags |= HTML_NOSPACE;
 
 	return 1;
 }
 
-static int
-mdoc_cm_pre(MDOC_ARGS)
-{
-	char	*id;
-
-	if ((id = cond_id(n)) != NULL)
-		print_otag(h, TAG_A, "chR", "permalink", id);
-	print_otag(h, TAG_CODE, "ci", "Cm", id);
-	return 1;
-}
-
 static int
 mdoc_nd_pre(MDOC_ARGS)
 {
 	switch (n->type) {
 	case ROFFT_BLOCK:
-		html_close_paragraph(h);
 		return 1;
 	case ROFFT_HEAD:
 		return 0;
@@ -663,8 +632,7 @@ mdoc_nd_pre(MDOC_ARGS)
 		abort();
 	}
 	print_text(h, "\\(em");
-	/* Cannot use TAG_SPAN because it may contain blocks. */
-	print_otag(h, TAG_DIV, "c", "Nd");
+	print_otag(h, TAG_SPAN, "c", "Nd");
 	return 1;
 }
 
@@ -721,6 +689,18 @@ mdoc_xr_pre(MDOC_ARGS)
 	return 0;
 }
 
+static int
+mdoc_tg_pre(MDOC_ARGS)
+{
+	char	*id;
+
+	if ((id = html_make_id(n, 1)) != NULL) {
+		print_tagq(h, print_otag(h, TAG_MARK, "i", id));
+		free(id);
+	}
+	return 0;
+}
+
 static int
 mdoc_ns_pre(MDOC_ARGS)
 {
@@ -765,7 +745,7 @@ mdoc_it_pre(MDOC_ARGS)
 		case ROFFT_HEAD:
 			return 0;
 		case ROFFT_BODY:
-			print_otag(h, TAG_LI, "");
+			print_otag_id(h, TAG_LI, NULL, n);
 			break;
 		default:
 			break;
@@ -777,7 +757,7 @@ mdoc_it_pre(MDOC_ARGS)
 	case LIST_ohang:
 		switch (n->type) {
 		case ROFFT_HEAD:
-			print_otag(h, TAG_DT, "");
+			print_otag_id(h, TAG_DT, NULL, n);
 			break;
 		case ROFFT_BODY:
 			print_otag(h, TAG_DD, "");
@@ -789,7 +769,7 @@ mdoc_it_pre(MDOC_ARGS)
 	case LIST_tag:
 		switch (n->type) {
 		case ROFFT_HEAD:
-			print_otag(h, TAG_DT, "");
+			print_otag_id(h, TAG_DT, NULL, n);
 			break;
 		case ROFFT_BODY:
 			if (n->child == NULL) {
@@ -810,7 +790,7 @@ mdoc_it_pre(MDOC_ARGS)
 			print_otag(h, TAG_TD, "");
 			break;
 		default:
-			print_otag(h, TAG_TR, "");
+			print_otag_id(h, TAG_TR, NULL, n);
 		}
 	default:
 		break;
@@ -876,8 +856,8 @@ mdoc_bl_pre(MDOC_ARGS)
 	case LIST_tag:
 		if (bl->offs)
 			print_otag(h, TAG_DIV, "c", "Bd-indent");
-		print_otag(h, TAG_DL, "c", bl->comp ?
-		    "Bl-tag Bl-compact" : "Bl-tag");
+		print_otag_id(h, TAG_DL,
+		    bl->comp ? "Bl-tag Bl-compact" : "Bl-tag", n->body);
 		return 1;
 	case LIST_column:
 		elemtype = TAG_TABLE;
@@ -890,14 +870,14 @@ mdoc_bl_pre(MDOC_ARGS)
 		(void)strlcat(cattr, " Bd-indent", sizeof(cattr));
 	if (bl->comp)
 		(void)strlcat(cattr, " Bl-compact", sizeof(cattr));
-	print_otag(h, elemtype, "c", cattr);
+	print_otag_id(h, elemtype, cattr, n->body);
 	return 1;
 }
 
 static int
 mdoc_ex_pre(MDOC_ARGS)
 {
-	if (n->prev)
+	if (roff_node_prev(n) != NULL)
 		print_otag(h, TAG_BR, "");
 	return 1;
 }
@@ -912,7 +892,7 @@ mdoc_st_pre(MDOC_ARGS)
 static int
 mdoc_em_pre(MDOC_ARGS)
 {
-	print_otag(h, TAG_I, "c", "Em");
+	print_otag_id(h, TAG_I, "Em", n);
 	return 1;
 }
 
@@ -922,15 +902,15 @@ mdoc_d1_pre(MDOC_ARGS)
 	switch (n->type) {
 	case ROFFT_BLOCK:
 		html_close_paragraph(h);
-		break;
+		return 1;
 	case ROFFT_HEAD:
 		return 0;
 	case ROFFT_BODY:
-		return 1;
+		break;
 	default:
 		abort();
 	}
-	print_otag(h, TAG_DIV, "c", "Bd Bd-indent");
+	print_otag_id(h, TAG_DIV, "Bd Bd-indent", n);
 	if (n->tok == MDOC_Dl)
 		print_otag(h, TAG_CODE, "c", "Li");
 	return 1;
@@ -950,7 +930,7 @@ mdoc_sx_pre(MDOC_ARGS)
 static int
 mdoc_bd_pre(MDOC_ARGS)
 {
-	char			 buf[16];
+	char			 buf[20];
 	struct roff_node	*nn;
 	int			 comp;
 
@@ -974,7 +954,7 @@ mdoc_bd_pre(MDOC_ARGS)
 			continue;
 		if (nn->tok == MDOC_Sh || nn->tok == MDOC_Ss)
 			comp = 1;
-		if (nn->prev != NULL)
+		if (roff_node_prev(nn) != NULL)
 			break;
 	}
 	(void)strlcpy(buf, "Bd", sizeof(buf));
@@ -987,7 +967,10 @@ mdoc_bd_pre(MDOC_ARGS)
 	    strcmp(n->norm->Bd.offs, "left") != 0)
 		(void)strlcat(buf, " Bd-indent", sizeof(buf));
 
-	print_otag(h, TAG_DIV, "c", buf);
+	if (n->norm->Bd.type == DISP_literal)
+		(void)strlcat(buf, " Li", sizeof(buf));
+
+	print_otag_id(h, TAG_DIV, buf, n);
 	return 1;
 }
 
@@ -1037,45 +1020,6 @@ mdoc_cd_pre(MDOC_ARGS)
 	return 1;
 }
 
-static int
-mdoc_dv_pre(MDOC_ARGS)
-{
-	char	*id;
-
-	if ((id = cond_id(n)) != NULL)
-		print_otag(h, TAG_A, "chR", "permalink", id);
-	print_otag(h, TAG_CODE, "ci", "Dv", id);
-	return 1;
-}
-
-static int
-mdoc_ev_pre(MDOC_ARGS)
-{
-	char	*id;
-
-	if ((id = cond_id(n)) != NULL)
-		print_otag(h, TAG_A, "chR", "permalink", id);
-	print_otag(h, TAG_CODE, "ci", "Ev", id);
-	return 1;
-}
-
-static int
-mdoc_er_pre(MDOC_ARGS)
-{
-	char	*id;
-
-	id = n->sec == SEC_ERRORS &&
-	    (n->parent->tok == MDOC_It ||
-	     (n->parent->tok == MDOC_Bq &&
-	      n->parent->parent->parent->tok == MDOC_It)) ?
-	    html_make_id(n, 1) : NULL;
-
-	if (id != NULL)
-		print_otag(h, TAG_A, "chR", "permalink", id);
-	print_otag(h, TAG_CODE, "ci", "Er", id);
-	return 1;
-}
-
 static int
 mdoc_fa_pre(MDOC_ARGS)
 {
@@ -1086,22 +1030,21 @@ mdoc_fa_pre(MDOC_ARGS)
 		print_otag(h, TAG_VAR, "c", "Fa");
 		return 1;
 	}
-
-	for (nn = n->child; nn; nn = nn->next) {
+	for (nn = n->child; nn != NULL; nn = nn->next) {
 		t = print_otag(h, TAG_VAR, "c", "Fa");
 		print_text(h, nn->string);
 		print_tagq(h, t);
-		if (nn->next) {
+		if (nn->next != NULL) {
 			h->flags |= HTML_NOSPACE;
 			print_text(h, ",");
 		}
 	}
-
-	if (n->child && n->next && n->next->tok == MDOC_Fa) {
+	if (n->child != NULL &&
+	    (nn = roff_node_next(n)) != NULL &&
+	    nn->tok == MDOC_Fa) {
 		h->flags |= HTML_NOSPACE;
 		print_text(h, ",");
 	}
-
 	return 0;
 }
 
@@ -1209,7 +1152,7 @@ mdoc_fn_pre(MDOC_ARGS)
 		print_tagq(h, t);
 	}
 
-	t = print_otag(h, TAG_CODE, "c", "Fn");
+	t = print_otag_id(h, TAG_CODE, "Fn", n);
 
 	if (sp)
 		print_text(h, sp);
@@ -1272,9 +1215,21 @@ mdoc_skip_pre(MDOC_ARGS)
 static int
 mdoc_pp_pre(MDOC_ARGS)
 {
-	if ((n->flags & NODE_NOFILL) == 0) {
+	char	*id;
+
+	if (n->flags & NODE_NOFILL) {
+		print_endline(h);
+		if (n->flags & NODE_ID)
+			mdoc_tg_pre(meta, n, h);
+		else {
+			h->col = 1;
+			print_endline(h);
+		}
+	} else {
 		html_close_paragraph(h);
-		print_otag(h, TAG_P, "c", "Pp");
+		id = n->flags & NODE_ID ? html_make_id(n, 1) : NULL;
+		print_otag(h, TAG_P, "ci", "Pp", id);
+		free(id);
 	}
 	return 0;
 }
@@ -1324,14 +1279,12 @@ mdoc_mt_pre(MDOC_ARGS)
 
 	for (n = n->child; n; n = n->next) {
 		assert(n->type == ROFFT_TEXT);
-
 		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;
 }
 
@@ -1340,30 +1293,30 @@ mdoc_fo_pre(MDOC_ARGS)
 {
 	struct tag	*t;
 
-	if (n->type == ROFFT_BODY) {
+	switch (n->type) {
+	case ROFFT_BLOCK:
+		synopsis_pre(h, n);
+		return 1;
+	case ROFFT_HEAD:
+		if (n->child != NULL) {
+			t = print_otag_id(h, TAG_CODE, "Fn", n);
+			print_text(h, n->child->string);
+			print_tagq(h, t);
+		}
+		return 0;
+	case ROFFT_BODY:
 		h->flags |= HTML_NOSPACE;
 		print_text(h, "(");
 		h->flags |= HTML_NOSPACE;
 		return 1;
-	} else if (n->type == ROFFT_BLOCK) {
-		synopsis_pre(h, n);
-		return 1;
+	default:
+		abort();
 	}
-
-	if (n->child == NULL)
-		return 0;
-
-	assert(n->child->string);
-	t = print_otag(h, TAG_CODE, "c", "Fn");
-	print_text(h, n->child->string);
-	print_tagq(h, t);
-	return 0;
 }
 
 static void
 mdoc_fo_post(MDOC_ARGS)
 {
-
 	if (n->type != ROFFT_BODY)
 		return;
 	h->flags |= HTML_NOSPACE;
@@ -1413,21 +1366,9 @@ mdoc_in_pre(MDOC_ARGS)
 		assert(n->type == ROFFT_TEXT);
 		print_text(h, n->string);
 	}
-
 	return 0;
 }
 
-static int
-mdoc_ic_pre(MDOC_ARGS)
-{
-	char	*id;
-
-	if ((id = cond_id(n)) != NULL)
-		print_otag(h, TAG_A, "chR", "permalink", id);
-	print_otag(h, TAG_CODE, "ci", "Ic", id);
-	return 1;
-}
-
 static int
 mdoc_va_pre(MDOC_ARGS)
 {
@@ -1438,7 +1379,6 @@ mdoc_va_pre(MDOC_ARGS)
 static int
 mdoc_ap_pre(MDOC_ARGS)
 {
-
 	h->flags |= HTML_NOSPACE;
 	print_text(h, "\\(aq");
 	h->flags |= HTML_NOSPACE;
@@ -1476,21 +1416,9 @@ mdoc_bf_pre(MDOC_ARGS)
 	return 1;
 }
 
-static int
-mdoc_ms_pre(MDOC_ARGS)
-{
-	char *id;
-
-	if ((id = cond_id(n)) != NULL)
-		print_otag(h, TAG_A, "chR", "permalink", id);
-	print_otag(h, TAG_SPAN, "ci", "Ms", id);
-	return 1;
-}
-
 static int
 mdoc_igndelim_pre(MDOC_ARGS)
 {
-
 	h->flags |= HTML_IGNDELIM;
 	return 1;
 }
@@ -1498,7 +1426,6 @@ mdoc_igndelim_pre(MDOC_ARGS)
 static void
 mdoc_pf_post(MDOC_ARGS)
 {
-
 	if ( ! (n->next == NULL || n->next->flags & NODE_LINE))
 		h->flags |= HTML_NOSPACE;
 }
@@ -1527,36 +1454,23 @@ mdoc_rs_pre(MDOC_ARGS)
 static int
 mdoc_no_pre(MDOC_ARGS)
 {
-	char *id;
-
-	if ((id = cond_id(n)) != NULL)
-		print_otag(h, TAG_A, "chR", "permalink", id);
-	print_otag(h, TAG_SPAN, "ci", "No", id);
-	return 1;
-}
-
-static int
-mdoc_li_pre(MDOC_ARGS)
-{
-	char	*id;
-
-	if ((id = cond_id(n)) != NULL)
-		print_otag(h, TAG_A, "chR", "permalink", id);
-	print_otag(h, TAG_CODE, "ci", "Li", id);
+	print_otag_id(h, TAG_SPAN, roff_name[n->tok], n);
 	return 1;
 }
 
 static int
 mdoc_sy_pre(MDOC_ARGS)
 {
-	print_otag(h, TAG_B, "c", "Sy");
+	print_otag_id(h, TAG_B, "Sy", n);
 	return 1;
 }
 
 static int
 mdoc_lb_pre(MDOC_ARGS)
 {
-	if (SEC_LIBRARY == n->sec && NODE_LINE & n->flags && n->prev)
+	if (n->sec == SEC_LIBRARY &&
+	    n->flags & NODE_LINE &&
+	    roff_node_prev(n) != NULL)
 		print_otag(h, TAG_BR, "");
 
 	print_otag(h, TAG_SPAN, "c", "Lb");
@@ -1566,17 +1480,18 @@ mdoc_lb_pre(MDOC_ARGS)
 static int
 mdoc__x_pre(MDOC_ARGS)
 {
-	const char	*cattr;
-	enum htmltag	 t;
+	struct roff_node	*nn;
+	const char		*cattr;
+	enum htmltag		 t;
 
 	t = TAG_SPAN;
 
 	switch (n->tok) {
 	case MDOC__A:
 		cattr = "RsA";
-		if (n->prev && MDOC__A == n->prev->tok)
-			if (NULL == n->next || MDOC__A != n->next->tok)
-				print_text(h, "and");
+		if ((nn = roff_node_prev(n)) != NULL && nn->tok == MDOC__A &&
+		    ((nn = roff_node_next(n)) == NULL || nn->tok != MDOC__A))
+			print_text(h, "and");
 		break;
 	case MDOC__B:
 		t = TAG_I;
@@ -1631,19 +1546,21 @@ mdoc__x_pre(MDOC_ARGS)
 static void
 mdoc__x_post(MDOC_ARGS)
 {
+	struct roff_node *nn;
 
-	if (MDOC__A == n->tok && n->next && MDOC__A == n->next->tok)
-		if (NULL == n->next->next || MDOC__A != n->next->next->tok)
-			if (NULL == n->prev || MDOC__A != n->prev->tok)
-				return;
+	if (n->tok == MDOC__A &&
+	    (nn = roff_node_next(n)) != NULL && nn->tok == MDOC__A &&
+	    ((nn = roff_node_next(nn)) == NULL || nn->tok != MDOC__A) &&
+	    ((nn = roff_node_prev(n)) == NULL || nn->tok != MDOC__A))
+		return;
 
 	/* TODO: %U */
 
-	if (NULL == n->parent || MDOC_Rs != n->parent->tok)
+	if (n->parent == NULL || n->parent->tok != MDOC_Rs)
 		return;
 
 	h->flags |= HTML_NOSPACE;
-	print_text(h, n->next ? "," : ".");
+	print_text(h, roff_node_next(n) ? "," : ".");
 }
 
 static int
@@ -1700,7 +1617,7 @@ mdoc_quote_pre(MDOC_ARGS)
 		/*
 		 * Give up on semantic markup for now.
 		 * We cannot use TAG_SPAN because .Oo may contain blocks.
-		 * We cannot use TAG_IDIV because we might be in a
+		 * We cannot use TAG_DIV because we might be in a
 		 * phrasing context (like .Dl or .Pp); we cannot
 		 * close out a .Pp at this point either because
 		 * that would break the line.
@@ -1715,9 +1632,11 @@ mdoc_quote_pre(MDOC_ARGS)
 		break;
 	case MDOC_Do:
 	case MDOC_Dq:
+		print_text(h, "\\(lq");
+		break;
 	case MDOC_Qo:
 	case MDOC_Qq:
-		print_text(h, "\\(lq");
+		print_text(h, "\"");
 		break;
 	case MDOC_Po:
 	case MDOC_Pq:
@@ -1773,12 +1692,14 @@ mdoc_quote_post(MDOC_ARGS)
 		else
 			print_text(h, n->norm->Es->child->next->string);
 		break;
-	case MDOC_Qo:
-	case MDOC_Qq:
 	case MDOC_Do:
 	case MDOC_Dq:
 		print_text(h, "\\(rq");
 		break;
+	case MDOC_Qo:
+	case MDOC_Qq:
+		print_text(h, "\"");
+		break;
 	case MDOC_Po:
 	case MDOC_Pq:
 		print_text(h, ")");
diff --git a/mdoc_macro.c b/mdoc_macro.c
index 3422945814f0..dd3885c702b7 100644
--- a/mdoc_macro.c
+++ b/mdoc_macro.c
@@ -1,7 +1,7 @@
-/*	$Id: mdoc_macro.c,v 1.232 2019/01/07 07:26:29 schwarze Exp $ */
+/*	$Id: mdoc_macro.c,v 1.234 2020/01/19 18:02:00 schwarze Exp $ */
 /*
  * Copyright (c) 2008-2012 Kristaps Dzonsons 
- * Copyright (c) 2010, 2012-2019 Ingo Schwarze 
+ * Copyright (c) 2010, 2012-2020 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
@@ -61,7 +61,7 @@ static	void		rew_pending(struct roff_man *,
 				const struct roff_node *);
 
 static const struct mdoc_macro mdoc_macros[MDOC_MAX - MDOC_Dd] = {
-	{ in_line_eoln, MDOC_PROLOGUE }, /* Dd */
+	{ in_line_eoln, MDOC_PROLOGUE | MDOC_JOIN }, /* Dd */
 	{ in_line_eoln, MDOC_PROLOGUE }, /* Dt */
 	{ in_line_eoln, MDOC_PROLOGUE }, /* Os */
 	{ blk_full, MDOC_PARSED | MDOC_JOIN }, /* Sh */
@@ -200,6 +200,7 @@ static const struct mdoc_macro mdoc_macros[MDOC_MAX - MDOC_Dd] = {
 	{ in_line_eoln, MDOC_JOIN }, /* %Q */
 	{ in_line_eoln, 0 }, /* %U */
 	{ phrase_ta, MDOC_CALLABLE | MDOC_PARSED | MDOC_JOIN }, /* Ta */
+	{ in_line_eoln, 0 }, /* Tg */
 };
 
 
diff --git a/mdoc_man.c b/mdoc_man.c
index 2e8f02ae56f2..0964cc6160a1 100644
--- a/mdoc_man.c
+++ b/mdoc_man.c
@@ -1,6 +1,6 @@
-/*	$Id: mdoc_man.c,v 1.132 2019/01/04 03:17:36 schwarze Exp $ */
+/*	$Id: mdoc_man.c,v 1.137 2021/07/04 15:38:26 schwarze Exp $ */
 /*
- * Copyright (c) 2011-2019 Ingo Schwarze 
+ * Copyright (c) 2011-2021 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,7 +113,7 @@ static	int	  pre_sm(DECL_ARGS);
 static	void	  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	void	  pre_syn(struct roff_node *);
 static	void	  pre_ta(DECL_ARGS);
 static	int	  pre_vt(DECL_ARGS);
 static	int	  pre_xr(DECL_ARGS);
@@ -262,6 +262,7 @@ static const struct mdoc_man_act mdoc_man_acts[MDOC_MAX - MDOC_Dd] = {
 	{ NULL, NULL, post_percent, NULL, NULL }, /* %Q */
 	{ NULL, NULL, post_percent, NULL, NULL }, /* %U */
 	{ NULL, NULL, NULL, NULL, NULL }, /* Ta */
+	{ NULL, pre_skip, NULL, NULL, NULL }, /* Tg */
 };
 static const struct mdoc_man_act *mdoc_man_act(enum roff_tok);
 
@@ -582,9 +583,9 @@ print_width(const struct mdoc_bl *bl, const struct roff_node *child)
 
 	/* Set up the current list. */
 	if (chsz > sz && bl->type != LIST_tag)
-		print_block(".HP", 0);
+		print_block(".HP", MMAN_spc);
 	else {
-		print_block(".TP", 0);
+		print_block(".TP", MMAN_spc);
 		remain = sz + 2;
 	}
 	if (numeric) {
@@ -649,7 +650,9 @@ print_node(DECL_ARGS)
 	 * Break the line if we were parsed subsequent the current node.
 	 * This makes the page structure be more consistent.
 	 */
-	if (MMAN_spc & outflags && NODE_LINE & n->flags)
+	if (outflags & MMAN_spc &&
+	    n->flags & NODE_LINE &&
+	    !roff_node_transparent(n))
 		outflags |= MMAN_nl;
 
 	act = NULL;
@@ -657,7 +660,20 @@ print_node(DECL_ARGS)
 	do_sub = 1;
 	n->flags &= ~NODE_ENDED;
 
-	if (n->type == ROFFT_TEXT) {
+	switch (n->type) {
+	case ROFFT_EQN:
+	case ROFFT_TBL:
+		mandoc_msg(n->type == ROFFT_EQN ? MANDOCERR_EQN_TMAN :
+		    MANDOCERR_TBL_TMAN, n->line, n->pos, NULL);
+		outflags |= MMAN_PP | MMAN_sp | MMAN_nl;
+		print_word("The");
+		print_line(".B \\-T man", MMAN_nl);
+		print_word("output mode does not support");
+		print_word(n->type == ROFFT_EQN ? "eqn(7)" : "tbl(7)");
+		print_word("input.");
+		outflags |= MMAN_PP | MMAN_sp | MMAN_nl;
+		return;
+	case ROFFT_TEXT:
 		/*
 		 * Make sure that we don't happen to start with a
 		 * control character at the start of a line.
@@ -677,19 +693,18 @@ print_node(DECL_ARGS)
 			outflags &= ~(MMAN_spc | MMAN_spc_force);
 		else if (outflags & MMAN_Sm)
 			outflags |= MMAN_spc;
-	} else if (n->tok < ROFF_MAX) {
-		(*roff_man_acts[n->tok])(meta, n);
-		return;
-	} else {
-		/*
-		 * Conditionally run the pre-node action handler for a
-		 * node.
-		 */
+		break;
+	default:
+		if (n->tok < ROFF_MAX) {
+			(*roff_man_acts[n->tok])(meta, n);
+			return;
+		}
 		act = mdoc_man_act(n->tok);
 		cond = act->cond == NULL || (*act->cond)(meta, n);
 		if (cond && act->pre != NULL &&
 		    (n->end == ENDBODY_NOT || n->child != NULL))
 			do_sub = (*act->pre)(meta, n);
+		break;
 	}
 
 	/*
@@ -776,13 +791,20 @@ post_font(DECL_ARGS)
 static void
 post_percent(DECL_ARGS)
 {
+	struct roff_node *np, *nn, *nnn;
 
 	if (mdoc_man_act(n->tok)->pre == pre_em)
 		font_pop();
-	if (n->next) {
-		print_word(",");
-		if (n->prev &&	n->prev->tok == n->tok &&
-				n->next->tok == n->tok)
+
+	if ((nn = roff_node_next(n)) != NULL) {
+		np = roff_node_prev(n);
+		nnn = nn == NULL ? NULL : roff_node_next(nn);
+		if (nn->tok != n->tok ||
+		    (np != NULL && np->tok == n->tok) ||
+		    (nnn != NULL && nnn->tok == n->tok))
+			print_word(",");
+		if (nn->tok == n->tok &&
+		    (nnn == NULL || nnn->tok != n->tok))
 			print_word("and");
 	} else {
 		print_word(".");
@@ -850,13 +872,15 @@ post_sect(DECL_ARGS)
 
 /* See mdoc_term.c, synopsis_pre() for comments. */
 static void
-pre_syn(const struct roff_node *n)
+pre_syn(struct roff_node *n)
 {
+	struct roff_node *np;
 
-	if (NULL == n->prev || ! (NODE_SYNPRETTY & n->flags))
+	if ((n->flags & NODE_SYNPRETTY) == 0 ||
+	    (np = roff_node_prev(n)) == NULL)
 		return;
 
-	if (n->prev->tok == n->tok &&
+	if (np->tok == n->tok &&
 	    MDOC_Ft != n->tok &&
 	    MDOC_Fo != n->tok &&
 	    MDOC_Fn != n->tok) {
@@ -864,7 +888,7 @@ pre_syn(const struct roff_node *n)
 		return;
 	}
 
-	switch (n->prev->tok) {
+	switch (np->tok) {
 	case MDOC_Fd:
 	case MDOC_Fn:
 	case MDOC_Fo:
@@ -940,11 +964,10 @@ static int
 pre_bd(DECL_ARGS)
 {
 	outflags &= ~(MMAN_PP | MMAN_sp | MMAN_br);
-
-	if (DISP_unfilled == n->norm->Bd.type ||
-	    DISP_literal  == n->norm->Bd.type)
+	if (n->norm->Bd.type == DISP_unfilled ||
+	    n->norm->Bd.type == DISP_literal)
 		print_line(".nf", 0);
-	if (0 == n->norm->Bd.comp && NULL != n->parent->prev)
+	if (n->norm->Bd.comp == 0 && roff_node_prev(n->parent) != NULL)
 		outflags |= MMAN_sp;
 	print_offs(n->norm->Bd.offs, 1);
 	return 1;
@@ -976,7 +999,7 @@ post_bd(DECL_ARGS)
 	}
 
 	/* Maybe we are inside an enclosing list? */
-	if (NULL != n->parent->next)
+	if (roff_node_next(n->parent) != NULL)
 		mid_it();
 }
 
@@ -1101,16 +1124,15 @@ post_bl(DECL_ARGS)
 		print_line(".RE", MMAN_nl);
 		assert(Bl_stack_len);
 		Bl_stack_len--;
-		assert(0 == Bl_stack[Bl_stack_len]);
+		assert(Bl_stack[Bl_stack_len] == 0);
 	} else {
 		outflags |= MMAN_PP | MMAN_nl;
 		outflags &= ~(MMAN_sp | MMAN_br);
 	}
 
 	/* Maybe we are inside an enclosing list? */
-	if (NULL != n->parent->next)
+	if (roff_node_next(n->parent) != NULL)
 		mid_it();
-
 }
 
 static void
@@ -1122,7 +1144,6 @@ pre_br(DECL_ARGS)
 static int
 pre_dl(DECL_ARGS)
 {
-
 	print_offs("6n", 0);
 	return 1;
 }
@@ -1130,11 +1151,10 @@ pre_dl(DECL_ARGS)
 static void
 post_dl(DECL_ARGS)
 {
-
 	print_line(".RE", MMAN_nl);
 
 	/* Maybe we are inside an enclosing list? */
-	if (NULL != n->parent->next)
+	if (roff_node_next(n->parent) != NULL)
 		mid_it();
 }
 
@@ -1235,15 +1255,15 @@ pre_fa(DECL_ARGS)
 static void
 post_fa(DECL_ARGS)
 {
+	struct roff_node *nn;
 
-	if (NULL != n->next && MDOC_Fa == n->next->tok)
+	if ((nn = roff_node_next(n)) != NULL && nn->tok == MDOC_Fa)
 		print_word(",");
 }
 
 static int
 pre_fd(DECL_ARGS)
 {
-
 	pre_syn(n);
 	font_push('B');
 	return 1;
@@ -1252,7 +1272,6 @@ pre_fd(DECL_ARGS)
 static void
 post_fd(DECL_ARGS)
 {
-
 	font_pop();
 	outflags |= MMAN_br;
 }
@@ -1260,7 +1279,6 @@ post_fd(DECL_ARGS)
 static int
 pre_fl(DECL_ARGS)
 {
-
 	font_push('B');
 	print_word("\\-");
 	if (n->child != NULL)
@@ -1271,12 +1289,13 @@ pre_fl(DECL_ARGS)
 static void
 post_fl(DECL_ARGS)
 {
+	struct roff_node *nn;
 
 	font_pop();
-	if (!(n->child != NULL ||
-	    n->next == NULL ||
-	    n->next->type == ROFFT_TEXT ||
-	    n->next->flags & NODE_LINE))
+	if (n->child == NULL &&
+	    ((nn = roff_node_next(n)) != NULL &&
+	    nn->type != ROFFT_TEXT &&
+	    (nn->flags & NODE_LINE) == 0))
 		outflags &= ~MMAN_spc;
 }
 
@@ -1419,9 +1438,9 @@ pre_it(DECL_ARGS)
 	case ROFFT_HEAD:
 		outflags |= MMAN_PP | MMAN_nl;
 		bln = n->parent->parent;
-		if (0 == bln->norm->Bl.comp ||
-		    (NULL == n->parent->prev &&
-		     NULL == bln->parent->prev))
+		if (bln->norm->Bl.comp == 0 ||
+		    (n->parent->prev == NULL &&
+		     roff_node_prev(bln->parent) == NULL))
 			outflags |= MMAN_sp;
 		outflags &= ~MMAN_br;
 		switch (bln->norm->Bl.type) {
@@ -1633,17 +1652,22 @@ pre_nm(DECL_ARGS)
 {
 	char	*name;
 
-	if (n->type == ROFFT_BLOCK) {
+	switch (n->type) {
+	case ROFFT_BLOCK:
 		outflags |= MMAN_Bk;
 		pre_syn(n);
-	}
-	if (n->type != ROFFT_ELEM && n->type != ROFFT_HEAD)
 		return 1;
+	case ROFFT_HEAD:
+	case ROFFT_ELEM:
+		break;
+	default:
+		return 1;
+	}
 	name = n->child == NULL ? NULL : n->child->string;
-	if (NULL == name)
+	if (name == NULL)
 		return 0;
 	if (n->type == ROFFT_HEAD) {
-		if (NULL == n->parent->prev)
+		if (roff_node_prev(n->parent) == NULL)
 			outflags |= MMAN_sp;
 		print_block(".HP", 0);
 		printf(" %dn", man_strlen(name) + 1);
diff --git a/mdoc_markdown.c b/mdoc_markdown.c
index 88e37c0b188b..63d8e1705580 100644
--- a/mdoc_markdown.c
+++ b/mdoc_markdown.c
@@ -1,6 +1,6 @@
-/*	$Id: mdoc_markdown.c,v 1.31 2019/07/01 22:56:24 schwarze Exp $ */
+/* $Id: mdoc_markdown.c,v 1.37 2021/08/10 12:55:03 schwarze Exp $ */
 /*
- * Copyright (c) 2017, 2018 Ingo Schwarze 
+ * Copyright (c) 2017, 2018, 2020 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,7 +13,11 @@
  * 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.
+ *
+ * Markdown formatter for mdoc(7) used by mandoc(1).
  */
+#include "config.h"
+
 #include 
 
 #include 
@@ -29,16 +33,16 @@
 #include "main.h"
 
 struct	md_act {
-	int		(*cond)(struct roff_node *n);
-	int		(*pre)(struct roff_node *n);
-	void		(*post)(struct roff_node *n);
+	int		(*cond)(struct roff_node *);
+	int		(*pre)(struct roff_node *);
+	void		(*post)(struct roff_node *);
 	const char	 *prefix; /* pre-node string constant */
 	const char	 *suffix; /* post-node string constant */
 };
 
 static	void	 md_nodelist(struct roff_node *);
 static	void	 md_node(struct roff_node *);
-static	const char *md_stack(char c);
+static	const char *md_stack(char);
 static	void	 md_preword(void);
 static	void	 md_rawword(const char *);
 static	void	 md_word(const char *);
@@ -226,6 +230,7 @@ static	const struct md_act md_acts[MDOC_MAX - MDOC_Dd] = {
 	{ NULL, NULL, md_post_pc, NULL, NULL }, /* %Q */
 	{ NULL, md_pre_Lk, md_post_pc, NULL, NULL }, /* %U */
 	{ NULL, NULL, NULL, NULL, NULL }, /* Ta */
+	{ NULL, md_pre_skip, NULL, NULL, NULL }, /* Tg */
 };
 static const struct md_act *md_act(enum roff_tok);
 
@@ -309,7 +314,9 @@ md_node(struct roff_node *n)
 
 	if (outflags & MD_nonl)
 		outflags &= ~(MD_nl | MD_sp);
-	else if (outflags & MD_spc && n->flags & NODE_LINE)
+	else if (outflags & MD_spc &&
+	     n->flags & NODE_LINE &&
+	     !roff_node_transparent(n))
 		outflags |= MD_nl;
 
 	act = NULL;
@@ -596,16 +603,18 @@ md_word(const char *s)
 				md_rawword("markdown");
 				continue;
 			case ESCAPE_FONTBOLD:
+			case ESCAPE_FONTCB:
 				nextfont = "**";
 				break;
 			case ESCAPE_FONTITALIC:
+			case ESCAPE_FONTCI:
 				nextfont = "*";
 				break;
 			case ESCAPE_FONTBI:
 				nextfont = "***";
 				break;
 			case ESCAPE_FONT:
-			case ESCAPE_FONTCW:
+			case ESCAPE_FONTCR:
 			case ESCAPE_FONTROMAN:
 				nextfont = "";
 				break;
@@ -786,14 +795,17 @@ md_post_word(struct roff_node *n)
 static void
 md_post_pc(struct roff_node *n)
 {
+	struct roff_node *nn;
+
 	md_post_raw(n);
 	if (n->parent->tok != MDOC_Rs)
 		return;
-	if (n->next != NULL) {
+
+	if ((nn = roff_node_next(n)) != NULL) {
 		md_word(",");
-		if (n->prev != NULL &&
-		    n->prev->tok == n->tok &&
-		    n->next->tok == n->tok)
+		if (nn->tok == n->tok &&
+		    (nn = roff_node_prev(n)) != NULL &&
+		    nn->tok == n->tok)
 			md_word("and");
 	} else {
 		md_word(".");
@@ -810,10 +822,13 @@ md_pre_skip(struct roff_node *n)
 static void
 md_pre_syn(struct roff_node *n)
 {
-	if (n->prev == NULL || ! (n->flags & NODE_SYNPRETTY))
+	struct roff_node *np;
+
+	if ((n->flags & NODE_SYNPRETTY) == 0 ||
+	    (np = roff_node_prev(n)) == NULL)
 		return;
 
-	if (n->prev->tok == n->tok &&
+	if (np->tok == n->tok &&
 	    n->tok != MDOC_Ft &&
 	    n->tok != MDOC_Fo &&
 	    n->tok != MDOC_Fn) {
@@ -821,7 +836,7 @@ md_pre_syn(struct roff_node *n)
 		return;
 	}
 
-	switch (n->prev->tok) {
+	switch (np->tok) {
 	case MDOC_Fd:
 	case MDOC_Fn:
 	case MDOC_Fo:
@@ -1052,7 +1067,9 @@ md_pre_Fa(struct roff_node *n)
 static void
 md_post_Fa(struct roff_node *n)
 {
-	if (n->next != NULL && n->next->tok == MDOC_Fa)
+	struct roff_node *nn;
+
+	if ((nn = roff_node_next(n)) != NULL && nn->tok == MDOC_Fa)
 		md_word(",");
 }
 
@@ -1074,9 +1091,11 @@ md_post_Fd(struct roff_node *n)
 static void
 md_post_Fl(struct roff_node *n)
 {
+	struct roff_node *nn;
+
 	md_post_raw(n);
-	if (n->child == NULL && n->next != NULL &&
-	    n->next->type != ROFFT_TEXT && !(n->next->flags & NODE_LINE))
+	if (n->child == NULL && (nn = roff_node_next(n)) != NULL &&
+	    nn->type != ROFFT_TEXT && (nn->flags & NODE_LINE) == 0)
 		outflags &= ~MD_spc;
 }
 
diff --git a/mdoc_state.c b/mdoc_state.c
index f9a585e73623..d696ff27e06c 100644
--- a/mdoc_state.c
+++ b/mdoc_state.c
@@ -1,4 +1,4 @@
-/*	$Id: mdoc_state.c,v 1.15 2019/01/01 07:42:04 schwarze Exp $ */
+/* $Id: mdoc_state.c,v 1.17 2020/06/22 19:20:40 schwarze Exp $ */
 /*
  * Copyright (c) 2014, 2015, 2017 Ingo Schwarze 
  *
@@ -14,6 +14,8 @@
  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  */
+#include "config.h"
+
 #include 
 
 #include 
@@ -157,6 +159,7 @@ static	const state_handler state_handlers[MDOC_MAX - MDOC_Dd] = {
 	NULL,		/* %Q */
 	NULL,		/* %U */
 	NULL,		/* Ta */
+	NULL,		/* Tg */
 };
 
 
diff --git a/mdoc_term.c b/mdoc_term.c
index 18f0ff09e2c7..42392c7c0a59 100644
--- a/mdoc_term.c
+++ b/mdoc_term.c
@@ -1,7 +1,7 @@
-/*	$Id: mdoc_term.c,v 1.374 2019/06/27 12:20:18 schwarze Exp $ */
+/* $Id: mdoc_term.c,v 1.380 2020/04/06 10:16:17 schwarze Exp $ */
 /*
+ * Copyright (c) 2010, 2012-2020 Ingo Schwarze 
  * Copyright (c) 2008, 2009, 2010, 2011 Kristaps Dzonsons 
- * Copyright (c) 2010, 2012-2019 Ingo Schwarze 
  * Copyright (c) 2013 Franco Fichtner 
  *
  * Permission to use, copy, modify, and distribute this software for any
@@ -15,6 +15,9 @@
  * 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.
+ *
+ * Plain text formatter for mdoc(7), used by mandoc(1)
+ * for ASCII, UTF-8, PostScript, and PDF output.
  */
 #include "config.h"
 
@@ -33,7 +36,7 @@
 #include "mdoc.h"
 #include "out.h"
 #include "term.h"
-#include "tag.h"
+#include "term_tag.h"
 #include "main.h"
 
 struct	termpair {
@@ -54,14 +57,12 @@ struct	mdoc_term_act {
 static	int	  a2width(const struct termp *, const char *);
 
 static	void	  print_bvspace(struct termp *,
-			const struct roff_node *,
-			const struct roff_node *);
+			struct roff_node *, struct roff_node *);
 static	void	  print_mdoc_node(DECL_ARGS);
 static	void	  print_mdoc_nodelist(DECL_ARGS);
 static	void	  print_mdoc_head(struct termp *, const struct roff_meta *);
 static	void	  print_mdoc_foot(struct termp *, const struct roff_meta *);
-static	void	  synopsis_pre(struct termp *,
-			const struct roff_node *);
+static	void	  synopsis_pre(struct termp *, struct roff_node *);
 
 static	void	  termp____post(DECL_ARGS);
 static	void	  termp__t_post(DECL_ARGS);
@@ -91,11 +92,8 @@ 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_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);
 static	int	  termp_fd_pre(DECL_ARGS);
@@ -117,8 +115,6 @@ static	int	  termp_skip_pre(DECL_ARGS);
 static	int	  termp_sm_pre(DECL_ARGS);
 static	int	  termp_pp_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_vt_pre(DECL_ARGS);
 static	int	  termp_xr_pre(DECL_ARGS);
@@ -142,11 +138,11 @@ static const struct mdoc_term_act mdoc_term_acts[MDOC_MAX - MDOC_Dd] = {
 	{ termp_an_pre, NULL }, /* An */
 	{ termp_ap_pre, NULL }, /* Ap */
 	{ termp_under_pre, NULL }, /* Ar */
-	{ termp_cd_pre, NULL }, /* Cd */
+	{ termp_fd_pre, NULL }, /* Cd */
 	{ termp_bold_pre, NULL }, /* Cm */
 	{ termp_li_pre, NULL }, /* Dv */
-	{ termp_er_pre, NULL }, /* Er */
-	{ termp_tag_pre, NULL }, /* Ev */
+	{ NULL, NULL }, /* Er */
+	{ NULL, NULL }, /* Ev */
 	{ termp_ex_pre, NULL }, /* Ex */
 	{ termp_fa_pre, NULL }, /* Fa */
 	{ termp_fd_pre, termp_fd_post }, /* Fd */
@@ -193,7 +189,7 @@ static const struct mdoc_term_act mdoc_term_acts[MDOC_MAX - MDOC_Dd] = {
 	{ termp_quote_pre, termp_quote_post }, /* Dq */
 	{ NULL, NULL }, /* Ec */ /* FIXME: no space */
 	{ NULL, NULL }, /* Ef */
-	{ termp_em_pre, NULL }, /* Em */
+	{ termp_under_pre, NULL }, /* Em */
 	{ termp_eo_pre, termp_eo_post }, /* Eo */
 	{ termp_xx_pre, termp_xx_post }, /* Fx */
 	{ termp_bold_pre, NULL }, /* Ms */
@@ -216,7 +212,7 @@ static const struct mdoc_term_act mdoc_term_acts[MDOC_MAX - MDOC_Dd] = {
 	{ termp_quote_pre, termp_quote_post }, /* Sq */
 	{ termp_sm_pre, NULL }, /* Sm */
 	{ termp_under_pre, NULL }, /* Sx */
-	{ termp_sy_pre, NULL }, /* Sy */
+	{ termp_bold_pre, NULL }, /* Sy */
 	{ NULL, NULL }, /* Tn */
 	{ termp_xx_pre, termp_xx_post }, /* Ux */
 	{ NULL, NULL }, /* Xc */
@@ -245,10 +241,9 @@ static const struct mdoc_term_act mdoc_term_acts[MDOC_MAX - MDOC_Dd] = {
 	{ NULL, termp____post }, /* %Q */
 	{ NULL, termp____post }, /* %U */
 	{ NULL, NULL }, /* Ta */
+	{ termp_skip_pre, NULL }, /* Tg */
 };
 
-static	int	 fn_prio;
-
 
 void
 terminal_mdoc(void *arg, const struct roff_meta *mdoc)
@@ -301,7 +296,6 @@ terminal_mdoc(void *arg, const struct roff_meta *mdoc)
 static void
 print_mdoc_nodelist(DECL_ARGS)
 {
-
 	while (n != NULL) {
 		print_mdoc_node(p, pair, meta, n);
 		n = n->next;
@@ -341,6 +335,10 @@ print_mdoc_node(DECL_ARGS)
 	memset(&npair, 0, sizeof(struct termpair));
 	npair.ppair = pair;
 
+	if (n->flags & NODE_ID && n->tok != MDOC_Pp &&
+	    (n->tok != MDOC_It || n->type != ROFFT_BLOCK))
+		term_tag_write(n, p->line);
+
 	/*
 	 * Keeps only work until the end of a line.  If a keep was
 	 * invoked in a prior line, revert it to PREKEEP.
@@ -580,29 +578,20 @@ a2width(const struct termp *p, const char *v)
  * too.
  */
 static void
-print_bvspace(struct termp *p,
-	const struct roff_node *bl,
-	const struct roff_node *n)
+print_bvspace(struct termp *p, struct roff_node *bl, struct roff_node *n)
 {
-	const struct roff_node	*nn;
-
-	assert(n);
+	struct roff_node *nn;
 
 	term_newln(p);
 
-	if (MDOC_Bd == bl->tok && bl->norm->Bd.comp)
-		return;
-	if (MDOC_Bl == bl->tok && bl->norm->Bl.comp)
+	if ((bl->tok == MDOC_Bd && bl->norm->Bd.comp) ||
+	    (bl->tok == MDOC_Bl && bl->norm->Bl.comp))
 		return;
 
 	/* Do not vspace directly after Ss/Sh. */
 
 	nn = n;
-	while (nn->prev != NULL &&
-	    (nn->prev->type == ROFFT_COMMENT ||
-	     nn->prev->flags & NODE_NOPRT))
-		nn = nn->prev;
-	while (nn->prev == NULL) {
+	while (roff_node_prev(nn) == NULL) {
 		do {
 			nn = nn->parent;
 			if (nn->type == ROFFT_ROOT)
@@ -615,22 +604,18 @@ print_bvspace(struct termp *p,
 			break;
 	}
 
-	/* A `-column' does not assert vspace within the list. */
-
-	if (MDOC_Bl == bl->tok && LIST_column == bl->norm->Bl.type)
-		if (n->prev && MDOC_It == n->prev->tok)
-			return;
-
-	/* A `-diag' without body does not vspace. */
-
-	if (MDOC_Bl == bl->tok && LIST_diag == bl->norm->Bl.type)
-		if (n->prev && MDOC_It == n->prev->tok) {
-			assert(n->prev->body);
-			if (NULL == n->prev->body->child)
-				return;
-		}
+	/*
+	 * No vertical space after:
+	 * items in .Bl -column
+	 * items without a body in .Bl -diag
+	 */
 
-	term_vspace(p);
+	if (bl->tok != MDOC_Bl ||
+	    n->prev == NULL || n->prev->tok != MDOC_It ||
+	    (bl->norm->Bl.type != LIST_column &&
+	     (bl->norm->Bl.type != LIST_diag ||
+	      n->prev->body->child != NULL)))
+		term_vspace(p);
 }
 
 
@@ -646,6 +631,8 @@ termp_it_pre(DECL_ARGS)
 
 	if (n->type == ROFFT_BLOCK) {
 		print_bvspace(p, n->parent->parent, n);
+		if (n->flags & NODE_ID)
+			term_tag_write(n, p->line);
 		return 1;
 	}
 
@@ -1018,38 +1005,44 @@ termp_nm_pre(DECL_ARGS)
 			p->flags |= TERMP_HANG;
 		}
 	}
-
-	term_fontpush(p, TERMFONT_BOLD);
-	return 1;
+	return termp_bold_pre(p, pair, meta, n);
 }
 
 static void
 termp_nm_post(DECL_ARGS)
 {
-
-	if (n->type == ROFFT_BLOCK) {
+	switch (n->type) {
+	case ROFFT_BLOCK:
 		p->flags &= ~(TERMP_KEEP | TERMP_PREKEEP);
-	} else if (n->type == ROFFT_HEAD &&
-	    NULL != n->next && NULL != n->next->child) {
+		break;
+	case ROFFT_HEAD:
+		if (n->next == NULL || n->next->child == NULL)
+			break;
 		term_flushln(p);
 		p->flags &= ~(TERMP_NOBREAK | TERMP_BRIND | TERMP_HANG);
 		p->trailspace = 0;
-	} else if (n->type == ROFFT_BODY && n->child != NULL)
-		term_flushln(p);
+		break;
+	case ROFFT_BODY:
+		if (n->child != NULL)
+			term_flushln(p);
+		break;
+	default:
+		break;
+	}
 }
 
 static int
 termp_fl_pre(DECL_ARGS)
 {
+	struct roff_node *nn;
 
-	termp_tag_pre(p, pair, meta, n);
 	term_fontpush(p, TERMFONT_BOLD);
 	term_word(p, "\\-");
 
-	if (!(n->child == NULL &&
-	    (n->next == NULL ||
-	     n->next->type == ROFFT_TEXT ||
-	     n->next->flags & NODE_LINE)))
+	if (n->child != NULL ||
+	    ((nn = roff_node_next(n)) != NULL &&
+	     nn->type != ROFFT_TEXT &&
+	     (nn->flags & NODE_LINE) == 0))
 		p->flags |= TERMP_NOSPACE;
 
 	return 1;
@@ -1058,10 +1051,11 @@ termp_fl_pre(DECL_ARGS)
 static int
 termp__a_pre(DECL_ARGS)
 {
+	struct roff_node *nn;
 
-	if (n->prev && MDOC__A == n->prev->tok)
-		if (NULL == n->next || MDOC__A != n->next->tok)
-			term_word(p, "and");
+	if ((nn = roff_node_prev(n)) != NULL && nn->tok == MDOC__A &&
+	    ((nn = roff_node_next(n)) == NULL || nn->tok != MDOC__A))
+		term_word(p, "and");
 
 	return 1;
 }
@@ -1102,10 +1096,9 @@ termp_ns_pre(DECL_ARGS)
 static int
 termp_rs_pre(DECL_ARGS)
 {
-
 	if (SEC_SEE_ALSO != n->sec)
 		return 1;
-	if (n->type == ROFFT_BLOCK && n->prev != NULL)
+	if (n->type == ROFFT_BLOCK && roff_node_prev(n) != NULL)
 		term_vspace(p);
 	return 1;
 }
@@ -1120,7 +1113,6 @@ termp_ex_pre(DECL_ARGS)
 static int
 termp_nd_pre(DECL_ARGS)
 {
-
 	if (n->type == ROFFT_BODY)
 		term_word(p, "\\(en");
 	return 1;
@@ -1129,14 +1121,20 @@ termp_nd_pre(DECL_ARGS)
 static int
 termp_bl_pre(DECL_ARGS)
 {
-
-	return n->type != ROFFT_HEAD;
+	switch (n->type) {
+	case ROFFT_BLOCK:
+		term_newln(p);
+		return 1;
+	case ROFFT_HEAD:
+		return 0;
+	default:
+		return 1;
+	}
 }
 
 static void
 termp_bl_post(DECL_ARGS)
 {
-
 	if (n->type != ROFFT_BLOCK)
 		return;
 	term_newln(p);
@@ -1150,7 +1148,6 @@ termp_bl_post(DECL_ARGS)
 static int
 termp_xr_pre(DECL_ARGS)
 {
-
 	if (NULL == (n = n->child))
 		return 0;
 
@@ -1179,13 +1176,12 @@ termp_xr_pre(DECL_ARGS)
  * macro combos).
  */
 static void
-synopsis_pre(struct termp *p, const struct roff_node *n)
+synopsis_pre(struct termp *p, struct roff_node *n)
 {
-	/*
-	 * Obviously, if we're not in a SYNOPSIS or no prior macros
-	 * exist, do nothing.
-	 */
-	if (NULL == n->prev || ! (NODE_SYNPRETTY & n->flags))
+	struct roff_node	*np;
+
+	if ((n->flags & NODE_SYNPRETTY) == 0 ||
+	    (np = roff_node_prev(n)) == NULL)
 		return;
 
 	/*
@@ -1193,7 +1189,7 @@ synopsis_pre(struct termp *p, const struct roff_node *n)
 	 * newline and return.  UNLESS we're `Fo', `Fn', `Fn', in which
 	 * case we soldier on.
 	 */
-	if (n->prev->tok == n->tok &&
+	if (np->tok == n->tok &&
 	    MDOC_Ft != n->tok &&
 	    MDOC_Fo != n->tok &&
 	    MDOC_Fn != n->tok) {
@@ -1206,7 +1202,7 @@ synopsis_pre(struct termp *p, const struct roff_node *n)
 	 * another (or Fn/Fo, which we've let slip through) then assert
 	 * vertical space, else only newline and move on.
 	 */
-	switch (n->prev->tok) {
+	switch (np->tok) {
 	case MDOC_Fd:
 	case MDOC_Fn:
 	case MDOC_Fo:
@@ -1215,7 +1211,7 @@ synopsis_pre(struct termp *p, const struct roff_node *n)
 		term_vspace(p);
 		break;
 	case MDOC_Ft:
-		if (MDOC_Fn != n->tok && MDOC_Fo != n->tok) {
+		if (n->tok != MDOC_Fn && n->tok != MDOC_Fo) {
 			term_vspace(p);
 			break;
 		}
@@ -1229,24 +1225,22 @@ synopsis_pre(struct termp *p, const struct roff_node *n)
 static int
 termp_vt_pre(DECL_ARGS)
 {
-
-	if (n->type == ROFFT_ELEM) {
-		synopsis_pre(p, n);
-		return termp_under_pre(p, pair, meta, n);
-	} else if (n->type == ROFFT_BLOCK) {
+	switch (n->type) {
+	case ROFFT_ELEM:
+		return termp_ft_pre(p, pair, meta, n);
+	case ROFFT_BLOCK:
 		synopsis_pre(p, n);
 		return 1;
-	} else if (n->type == ROFFT_HEAD)
+	case ROFFT_HEAD:
 		return 0;
-
-	return termp_under_pre(p, pair, meta, n);
+	default:
+		return termp_under_pre(p, pair, meta, n);
+	}
 }
 
 static int
 termp_bold_pre(DECL_ARGS)
 {
-
-	termp_tag_pre(p, pair, meta, n);
 	term_fontpush(p, TERMFONT_BOLD);
 	return 1;
 }
@@ -1254,7 +1248,6 @@ termp_bold_pre(DECL_ARGS)
 static int
 termp_fd_pre(DECL_ARGS)
 {
-
 	synopsis_pre(p, n);
 	return termp_bold_pre(p, pair, meta, n);
 }
@@ -1262,13 +1255,13 @@ termp_fd_pre(DECL_ARGS)
 static void
 termp_fd_post(DECL_ARGS)
 {
-
 	term_newln(p);
 }
 
 static int
 termp_sh_pre(DECL_ARGS)
 {
+	struct roff_node	*np;
 
 	switch (n->type) {
 	case ROFFT_BLOCK:
@@ -1276,30 +1269,20 @@ termp_sh_pre(DECL_ARGS)
 		 * Vertical space before sections, except
 		 * when the previous section was empty.
 		 */
-		if (n->prev == NULL ||
-		    n->prev->tok != MDOC_Sh ||
-		    (n->prev->body != NULL &&
-		     n->prev->body->child != NULL))
+		if ((np = roff_node_prev(n)) == NULL ||
+		    np->tok != MDOC_Sh ||
+		    (np->body != NULL && np->body->child != NULL))
 			term_vspace(p);
 		break;
 	case ROFFT_HEAD:
-		term_fontpush(p, TERMFONT_BOLD);
-		break;
+		return termp_bold_pre(p, pair, meta, n);
 	case ROFFT_BODY:
 		p->tcol->offset = term_len(p, p->defindent);
 		term_tab_set(p, NULL);
 		term_tab_set(p, "T");
 		term_tab_set(p, ".5i");
-		switch (n->sec) {
-		case SEC_DESCRIPTION:
-			fn_prio = 0;
-			break;
-		case SEC_AUTHORS:
+		if (n->sec == SEC_AUTHORS)
 			p->flags &= ~(TERMP_SPLIT|TERMP_NOSPLIT);
-			break;
-		default:
-			break;
-		}
 		break;
 	default:
 		break;
@@ -1310,7 +1293,6 @@ termp_sh_pre(DECL_ARGS)
 static void
 termp_sh_post(DECL_ARGS)
 {
-
 	switch (n->type) {
 	case ROFFT_HEAD:
 		term_newln(p);
@@ -1327,15 +1309,13 @@ termp_sh_post(DECL_ARGS)
 static void
 termp_lb_post(DECL_ARGS)
 {
-
-	if (SEC_LIBRARY == n->sec && NODE_LINE & n->flags)
+	if (n->sec == SEC_LIBRARY && n->flags & NODE_LINE)
 		term_newln(p);
 }
 
 static int
 termp_d1_pre(DECL_ARGS)
 {
-
 	if (n->type != ROFFT_BLOCK)
 		return 1;
 	term_newln(p);
@@ -1349,11 +1329,8 @@ termp_d1_pre(DECL_ARGS)
 static int
 termp_ft_pre(DECL_ARGS)
 {
-
-	/* NB: NODE_LINE does not effect this! */
 	synopsis_pre(p, n);
-	term_fontpush(p, TERMFONT_UNDER);
-	return 1;
+	return termp_under_pre(p, pair, meta, n);
 }
 
 static int
@@ -1362,11 +1339,9 @@ termp_fn_pre(DECL_ARGS)
 	size_t		 rmargin = 0;
 	int		 pretty;
 
-	pretty = NODE_SYNPRETTY & n->flags;
-
 	synopsis_pre(p, n);
-
-	if (NULL == (n = n->child))
+	pretty = n->flags & NODE_SYNPRETTY;
+	if ((n = n->child) == NULL)
 		return 0;
 
 	if (pretty) {
@@ -1380,9 +1355,6 @@ termp_fn_pre(DECL_ARGS)
 	term_word(p, n->string);
 	term_fontpop(p);
 
-	if (n->sec == SEC_DESCRIPTION || n->sec == SEC_CUSTOM)
-		tag_put(n->string, ++fn_prio, p->line);
-
 	if (pretty) {
 		term_flushln(p);
 		p->flags &= ~(TERMP_NOBREAK | TERMP_BRIND | TERMP_HANG);
@@ -1417,7 +1389,6 @@ termp_fn_pre(DECL_ARGS)
 		term_word(p, ";");
 		term_flushln(p);
 	}
-
 	return 0;
 }
 
@@ -1426,23 +1397,25 @@ termp_fa_pre(DECL_ARGS)
 {
 	const struct roff_node	*nn;
 
-	if (n->parent->tok != MDOC_Fo) {
-		term_fontpush(p, TERMFONT_UNDER);
-		return 1;
-	}
+	if (n->parent->tok != MDOC_Fo)
+		return termp_under_pre(p, pair, meta, n);
 
-	for (nn = n->child; nn; nn = nn->next) {
+	for (nn = n->child; nn != NULL; nn = nn->next) {
 		term_fontpush(p, TERMFONT_UNDER);
 		p->flags |= TERMP_NBRWORD;
 		term_word(p, nn->string);
 		term_fontpop(p);
-
-		if (nn->next || (n->next && n->next->tok == MDOC_Fa)) {
+		if (nn->next != NULL) {
 			p->flags |= TERMP_NOSPACE;
 			term_word(p, ",");
 		}
 	}
-
+	if (n->child != NULL &&
+	    (nn = roff_node_next(n)) != NULL &&
+	    nn->tok == MDOC_Fa) {
+		p->flags |= TERMP_NOSPACE;
+		term_word(p, ",");
+	}
 	return 0;
 }
 
@@ -1522,30 +1495,23 @@ termp_xx_post(DECL_ARGS)
 static void
 termp_pf_post(DECL_ARGS)
 {
-
-	if ( ! (n->next == NULL || n->next->flags & NODE_LINE))
+	if (n->next != NULL && (n->next->flags & NODE_LINE) == 0)
 		p->flags |= TERMP_NOSPACE;
 }
 
 static int
 termp_ss_pre(DECL_ARGS)
 {
-	struct roff_node *nn;
-
 	switch (n->type) {
 	case ROFFT_BLOCK:
-		term_newln(p);
-		for (nn = n->prev; nn != NULL; nn = nn->prev)
-			if (nn->type != ROFFT_COMMENT &&
-			    (nn->flags & NODE_NOPRT) == 0)
-				break;
-		if (nn != NULL)
+		if (roff_node_prev(n) == NULL)
+			term_newln(p);
+		else
 			term_vspace(p);
 		break;
 	case ROFFT_HEAD:
-		term_fontpush(p, TERMFONT_BOLD);
 		p->tcol->offset = term_len(p, (p->defindent+1)/2);
-		break;
+		return termp_bold_pre(p, pair, meta, n);
 	case ROFFT_BODY:
 		p->tcol->offset = term_len(p, p->defindent);
 		term_tab_set(p, NULL);
@@ -1555,34 +1521,21 @@ termp_ss_pre(DECL_ARGS)
 	default:
 		break;
 	}
-
 	return 1;
 }
 
 static void
 termp_ss_post(DECL_ARGS)
 {
-
 	if (n->type == ROFFT_HEAD || n->type == ROFFT_BODY)
 		term_newln(p);
 }
 
-static int
-termp_cd_pre(DECL_ARGS)
-{
-
-	synopsis_pre(p, n);
-	term_fontpush(p, TERMFONT_BOLD);
-	return 1;
-}
-
 static int
 termp_in_pre(DECL_ARGS)
 {
-
 	synopsis_pre(p, n);
-
-	if (NODE_SYNPRETTY & n->flags && NODE_LINE & n->flags) {
+	if (n->flags & NODE_SYNPRETTY && n->flags & NODE_LINE) {
 		term_fontpush(p, TERMFONT_BOLD);
 		term_word(p, "#include");
 		term_word(p, "<");
@@ -1590,7 +1543,6 @@ termp_in_pre(DECL_ARGS)
 		term_word(p, "<");
 		term_fontpush(p, TERMFONT_UNDER);
 	}
-
 	p->flags |= TERMP_NOSPACE;
 	return 1;
 }
@@ -1598,36 +1550,32 @@ termp_in_pre(DECL_ARGS)
 static void
 termp_in_post(DECL_ARGS)
 {
-
-	if (NODE_SYNPRETTY & n->flags)
+	if (n->flags & NODE_SYNPRETTY)
 		term_fontpush(p, TERMFONT_BOLD);
-
 	p->flags |= TERMP_NOSPACE;
 	term_word(p, ">");
-
-	if (NODE_SYNPRETTY & n->flags)
+	if (n->flags & NODE_SYNPRETTY)
 		term_fontpop(p);
 }
 
 static int
 termp_pp_pre(DECL_ARGS)
 {
-	fn_prio = 0;
 	term_vspace(p);
+	if (n->flags & NODE_ID)
+		term_tag_write(n, p->line);
 	return 0;
 }
 
 static int
 termp_skip_pre(DECL_ARGS)
 {
-
 	return 0;
 }
 
 static int
 termp_quote_pre(DECL_ARGS)
 {
-
 	if (n->type != ROFFT_BODY && n->type != ROFFT_ELEM)
 		return 1;
 
@@ -1784,17 +1732,15 @@ termp_eo_post(DECL_ARGS)
 static int
 termp_fo_pre(DECL_ARGS)
 {
-	size_t		 rmargin = 0;
-	int		 pretty;
-
-	pretty = NODE_SYNPRETTY & n->flags;
+	size_t rmargin;
 
-	if (n->type == ROFFT_BLOCK) {
+	switch (n->type) {
+	case ROFFT_BLOCK:
 		synopsis_pre(p, n);
 		return 1;
-	} else if (n->type == ROFFT_BODY) {
-		if (pretty) {
-			rmargin = p->tcol->rmargin;
+	case ROFFT_BODY:
+		rmargin = p->tcol->rmargin;
+		if (n->flags & NODE_SYNPRETTY) {
 			p->tcol->rmargin = p->tcol->offset + term_len(p, 4);
 			p->flags |= TERMP_NOBREAK | TERMP_BRIND |
 					TERMP_HANG;
@@ -1802,7 +1748,7 @@ termp_fo_pre(DECL_ARGS)
 		p->flags |= TERMP_NOSPACE;
 		term_word(p, "(");
 		p->flags |= TERMP_NOSPACE;
-		if (pretty) {
+		if (n->flags & NODE_SYNPRETTY) {
 			term_flushln(p);
 			p->flags &= ~(TERMP_NOBREAK | TERMP_BRIND |
 					TERMP_HANG);
@@ -1811,30 +1757,21 @@ termp_fo_pre(DECL_ARGS)
 			p->tcol->rmargin = rmargin;
 		}
 		return 1;
+	default:
+		return termp_bold_pre(p, pair, meta, n);
 	}
-
-	if (NULL == n->child)
-		return 0;
-
-	/* XXX: we drop non-initial arguments as per groff. */
-
-	assert(n->child->string);
-	term_fontpush(p, TERMFONT_BOLD);
-	term_word(p, n->child->string);
-	return 0;
 }
 
 static void
 termp_fo_post(DECL_ARGS)
 {
-
 	if (n->type != ROFFT_BODY)
 		return;
 
 	p->flags |= TERMP_NOSPACE;
 	term_word(p, ")");
 
-	if (NODE_SYNPRETTY & n->flags) {
+	if (n->flags & NODE_SYNPRETTY) {
 		p->flags |= TERMP_NOSPACE;
 		term_word(p, ";");
 		term_flushln(p);
@@ -1844,29 +1781,30 @@ termp_fo_post(DECL_ARGS)
 static int
 termp_bf_pre(DECL_ARGS)
 {
-
-	if (n->type == ROFFT_HEAD)
+	switch (n->type) {
+	case ROFFT_HEAD:
 		return 0;
-	else if (n->type != ROFFT_BODY)
+	case ROFFT_BODY:
+		break;
+	default:
 		return 1;
-
-	if (FONT_Em == n->norm->Bf.font)
-		term_fontpush(p, TERMFONT_UNDER);
-	else if (FONT_Sy == n->norm->Bf.font)
-		term_fontpush(p, TERMFONT_BOLD);
-	else
-		term_fontpush(p, TERMFONT_NONE);
-
-	return 1;
+	}
+	switch (n->norm->Bf.font) {
+	case FONT_Em:
+		return termp_under_pre(p, pair, meta, n);
+	case FONT_Sy:
+		return termp_bold_pre(p, pair, meta, n);
+	default:
+		return termp_li_pre(p, pair, meta, n);
+	}
 }
 
 static int
 termp_sm_pre(DECL_ARGS)
 {
-
-	if (NULL == n->child)
+	if (n->child == NULL)
 		p->flags ^= TERMP_NONOSPACE;
-	else if (0 == strcmp("on", n->child->string))
+	else if (strcmp(n->child->string, "on") == 0)
 		p->flags &= ~TERMP_NONOSPACE;
 	else
 		p->flags |= TERMP_NONOSPACE;
@@ -1880,7 +1818,6 @@ termp_sm_pre(DECL_ARGS)
 static int
 termp_ap_pre(DECL_ARGS)
 {
-
 	p->flags |= TERMP_NOSPACE;
 	term_word(p, "'");
 	p->flags |= TERMP_NOSPACE;
@@ -1890,24 +1827,26 @@ termp_ap_pre(DECL_ARGS)
 static void
 termp____post(DECL_ARGS)
 {
+	struct roff_node *nn;
 
 	/*
 	 * Handle lists of authors.  In general, print each followed by
 	 * a comma.  Don't print the comma if there are only two
 	 * authors.
 	 */
-	if (MDOC__A == n->tok && n->next && MDOC__A == n->next->tok)
-		if (NULL == n->next->next || MDOC__A != n->next->next->tok)
-			if (NULL == n->prev || MDOC__A != n->prev->tok)
-				return;
+	if (n->tok == MDOC__A &&
+	    (nn = roff_node_next(n)) != NULL && nn->tok == MDOC__A &&
+	    ((nn = roff_node_next(nn)) == NULL || nn->tok != MDOC__A) &&
+	    ((nn = roff_node_prev(n)) == NULL || nn->tok != MDOC__A))
+		return;
 
 	/* TODO: %U. */
 
-	if (NULL == n->parent || MDOC_Rs != n->parent->tok)
+	if (n->parent == NULL || n->parent->tok != MDOC_Rs)
 		return;
 
 	p->flags |= TERMP_NOSPACE;
-	if (NULL == n->next) {
+	if (roff_node_next(n) == NULL) {
 		term_word(p, ".");
 		p->flags |= TERMP_SENTENCE;
 	} else
@@ -1917,8 +1856,6 @@ termp____post(DECL_ARGS)
 static int
 termp_li_pre(DECL_ARGS)
 {
-
-	termp_tag_pre(p, pair, meta, n);
 	term_fontpush(p, TERMFONT_NONE);
 	return 1;
 }
@@ -1968,7 +1905,6 @@ termp_lk_pre(DECL_ARGS)
 static int
 termp_bk_pre(DECL_ARGS)
 {
-
 	switch (n->type) {
 	case ROFFT_BLOCK:
 		break;
@@ -1981,106 +1917,46 @@ termp_bk_pre(DECL_ARGS)
 	default:
 		abort();
 	}
-
 	return 1;
 }
 
 static void
 termp_bk_post(DECL_ARGS)
 {
-
 	if (n->type == ROFFT_BODY)
 		p->flags &= ~(TERMP_KEEP | TERMP_PREKEEP);
 }
 
+/*
+ * If we are in an `Rs' and there is a journal present,
+ * then quote us instead of underlining us (for disambiguation).
+ */
 static void
 termp__t_post(DECL_ARGS)
 {
-
-	/*
-	 * If we're in an `Rs' and there's a journal present, then quote
-	 * us instead of underlining us (for disambiguation).
-	 */
-	if (n->parent && MDOC_Rs == n->parent->tok &&
+	if (n->parent != NULL && n->parent->tok == MDOC_Rs &&
 	    n->parent->norm->Rs.quote_T)
 		termp_quote_post(p, pair, meta, n);
-
 	termp____post(p, pair, meta, n);
 }
 
 static int
 termp__t_pre(DECL_ARGS)
 {
-
-	/*
-	 * If we're in an `Rs' and there's a journal present, then quote
-	 * us instead of underlining us (for disambiguation).
-	 */
-	if (n->parent && MDOC_Rs == n->parent->tok &&
+	if (n->parent != NULL && n->parent->tok == MDOC_Rs &&
 	    n->parent->norm->Rs.quote_T)
 		return termp_quote_pre(p, pair, meta, n);
-
-	term_fontpush(p, TERMFONT_UNDER);
-	return 1;
+	else
+		return termp_under_pre(p, pair, meta, n);
 }
 
 static int
 termp_under_pre(DECL_ARGS)
 {
-
 	term_fontpush(p, TERMFONT_UNDER);
 	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)
-{
-
-	if (n->sec == SEC_ERRORS &&
-	    (n->parent->tok == MDOC_It ||
-	     (n->parent->tok == MDOC_Bq &&
-	      n->parent->parent->parent->tok == MDOC_It)))
-		tag_put(n->child->string, 1, p->line);
-	return 1;
-}
-
-static int
-termp_tag_pre(DECL_ARGS)
-{
-
-	if (n->child != NULL &&
-	    n->child->type == ROFFT_TEXT &&
-	    (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 &&
-	      n->parent->parent->parent->tok == MDOC_It)))
-		tag_put(n->child->string, 1, p->line);
-	return 1;
-}
-
 static int
 termp_abort_pre(DECL_ARGS)
 {
diff --git a/mdoc_validate.c b/mdoc_validate.c
index 11cdf00b56cb..e1cd3ae1edcb 100644
--- a/mdoc_validate.c
+++ b/mdoc_validate.c
@@ -1,7 +1,7 @@
-/*	$Id: mdoc_validate.c,v 1.374 2019/06/27 15:07:30 schwarze Exp $ */
+/* $Id: mdoc_validate.c,v 1.389 2021/07/18 11:41:23 schwarze Exp $ */
 /*
+ * Copyright (c) 2010-2020 Ingo Schwarze 
  * Copyright (c) 2008-2012 Kristaps Dzonsons 
- * Copyright (c) 2010-2019 Ingo Schwarze 
  * Copyright (c) 2010 Joerg Sonnenberger 
  *
  * Permission to use, copy, modify, and distribute this software for any
@@ -15,6 +15,8 @@
  * 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.
+ *
+ * Validation module for mdoc(7) syntax trees used by mandoc(1).
  */
 #include "config.h"
 
@@ -39,6 +41,7 @@
 #include "libmandoc.h"
 #include "roff_int.h"
 #include "libmdoc.h"
+#include "tag.h"
 
 /* FIXME: .Bl -diag can't have non-text children in HEAD. */
 
@@ -82,16 +85,18 @@ static	void	 post_dd(POST_ARGS);
 static	void	 post_delim(POST_ARGS);
 static	void	 post_delim_nb(POST_ARGS);
 static	void	 post_dt(POST_ARGS);
+static	void	 post_em(POST_ARGS);
 static	void	 post_en(POST_ARGS);
+static	void	 post_er(POST_ARGS);
 static	void	 post_es(POST_ARGS);
 static	void	 post_eoln(POST_ARGS);
 static	void	 post_ex(POST_ARGS);
 static	void	 post_fa(POST_ARGS);
+static	void	 post_fl(POST_ARGS);
 static	void	 post_fn(POST_ARGS);
 static	void	 post_fname(POST_ARGS);
 static	void	 post_fo(POST_ARGS);
 static	void	 post_hyph(POST_ARGS);
-static	void	 post_ignpar(POST_ARGS);
 static	void	 post_it(POST_ARGS);
 static	void	 post_lb(POST_ARGS);
 static	void	 post_nd(POST_ARGS);
@@ -104,6 +109,7 @@ 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_section(POST_ARGS);
 static	void	 post_sh(POST_ARGS);
 static	void	 post_sh_head(POST_ARGS);
 static	void	 post_sh_name(POST_ARGS);
@@ -113,6 +119,8 @@ static	void	 post_sm(POST_ARGS);
 static	void	 post_st(POST_ARGS);
 static	void	 post_std(POST_ARGS);
 static	void	 post_sx(POST_ARGS);
+static	void	 post_tag(POST_ARGS);
+static	void	 post_tg(POST_ARGS);
 static	void	 post_useless(POST_ARGS);
 static	void	 post_xr(POST_ARGS);
 static	void	 post_xx(POST_ARGS);
@@ -122,7 +130,7 @@ static	const v_post mdoc_valids[MDOC_MAX - MDOC_Dd] = {
 	post_dt,	/* Dt */
 	post_os,	/* Os */
 	post_sh,	/* Sh */
-	post_ignpar,	/* Ss */
+	post_section,	/* Ss */
 	post_par,	/* Pp */
 	post_display,	/* D1 */
 	post_display,	/* Dl */
@@ -136,19 +144,19 @@ static	const v_post mdoc_valids[MDOC_MAX - MDOC_Dd] = {
 	NULL,		/* Ap */
 	post_defaults,	/* Ar */
 	NULL,		/* Cd */
-	post_delim_nb,	/* Cm */
-	post_delim_nb,	/* Dv */
-	post_delim_nb,	/* Er */
-	post_delim_nb,	/* Ev */
+	post_tag,	/* Cm */
+	post_tag,	/* Dv */
+	post_er,	/* Er */
+	post_tag,	/* Ev */
 	post_ex,	/* Ex */
 	post_fa,	/* Fa */
 	NULL,		/* Fd */
-	post_delim_nb,	/* Fl */
+	post_fl,	/* Fl */
 	post_fn,	/* Fn */
 	post_delim_nb,	/* Ft */
-	post_delim_nb,	/* Ic */
+	post_tag,	/* Ic */
 	post_delim_nb,	/* In */
-	post_defaults,	/* Li */
+	post_tag,	/* Li */
 	post_nd,	/* Nd */
 	post_nm,	/* Nm */
 	post_delim_nb,	/* Op */
@@ -156,7 +164,7 @@ static	const v_post mdoc_valids[MDOC_MAX - MDOC_Dd] = {
 	post_defaults,	/* Pa */
 	post_rv,	/* Rv */
 	post_st,	/* St */
-	post_delim_nb,	/* Va */
+	post_tag,	/* Va */
 	post_delim_nb,	/* Vt */
 	post_xr,	/* Xr */
 	NULL,		/* %A */
@@ -186,11 +194,11 @@ static	const v_post mdoc_valids[MDOC_MAX - MDOC_Dd] = {
 	NULL,		/* Dq */
 	NULL,		/* Ec */
 	NULL,		/* Ef */
-	post_delim_nb,	/* Em */
+	post_em,	/* Em */
 	NULL,		/* Eo */
 	post_xx,	/* Fx */
-	post_delim_nb,	/* Ms */
-	NULL,		/* No */
+	post_tag,	/* Ms */
+	post_tag,	/* No */
 	post_ns,	/* Ns */
 	post_xx,	/* Nx */
 	post_xx,	/* Ox */
@@ -209,7 +217,7 @@ static	const v_post mdoc_valids[MDOC_MAX - MDOC_Dd] = {
 	post_delim_nb,	/* Sq */
 	post_sm,	/* Sm */
 	post_sx,	/* Sx */
-	post_delim_nb,	/* Sy */
+	post_em,	/* Sy */
 	post_useless,	/* Tn */
 	post_xx,	/* Ux */
 	NULL,		/* Xc */
@@ -238,6 +246,7 @@ static	const v_post mdoc_valids[MDOC_MAX - MDOC_Dd] = {
 	NULL,		/* %Q */
 	NULL,		/* %U */
 	NULL,		/* Ta */
+	post_tg,	/* Tg */
 };
 
 #define	RSORD_MAX 14 /* Number of `Rs' blocks. */
@@ -285,6 +294,8 @@ static	const char * const secnames[SEC__MAX] = {
 	NULL
 };
 
+static	int	  fn_prio = TAG_STRONG;
+
 
 /* Validate the subtree rooted at mdoc->last. */
 void
@@ -1089,6 +1100,125 @@ post_st(POST_ARGS)
 	mdoc->last= n;
 }
 
+static void
+post_tg(POST_ARGS)
+{
+	struct roff_node *n;	/* The .Tg node. */
+	struct roff_node *nch;	/* The first child of the .Tg node. */
+	struct roff_node *nn;   /* The next node after the .Tg node. */
+	struct roff_node *np;	/* The parent of the next node. */
+	struct roff_node *nt;	/* The TEXT node containing the tag. */
+	size_t		  len;	/* The number of bytes in the tag. */
+
+	/* Find the next node. */
+	n = mdoc->last;
+	for (nn = n; nn != NULL; nn = nn->parent) {
+		if (nn->next != NULL) {
+			nn = nn->next;
+			break;
+		}
+	}
+
+	/* Find the tag. */
+	nt = nch = n->child;
+	if (nch == NULL && nn != NULL && nn->child != NULL &&
+	    nn->child->type == ROFFT_TEXT)
+		nt = nn->child;
+
+	/* Validate the tag. */
+	if (nt == NULL || *nt->string == '\0')
+		mandoc_msg(MANDOCERR_MACRO_EMPTY, n->line, n->pos, "Tg");
+	if (nt == NULL) {
+		roff_node_delete(mdoc, n);
+		return;
+	}
+	len = strcspn(nt->string, " \t\\");
+	if (nt->string[len] != '\0')
+		mandoc_msg(MANDOCERR_TG_SPC, nt->line,
+		    nt->pos + len, "Tg %s", nt->string);
+
+	/* Keep only the first argument. */
+	if (nch != NULL && nch->next != NULL) {
+		mandoc_msg(MANDOCERR_ARG_EXCESS, nch->next->line,
+		    nch->next->pos, "Tg ... %s", nch->next->string);
+		while (nch->next != NULL)
+			roff_node_delete(mdoc, nch->next);
+	}
+
+	/* Drop the macro if the first argument is invalid. */
+	if (len == 0 || nt->string[len] != '\0') {
+		roff_node_delete(mdoc, n);
+		return;
+	}
+
+	/* By default, tag the .Tg node itself. */
+	if (nn == NULL || nn->flags & NODE_ID)
+		nn = n;
+
+	/* Explicit tagging of specific macros. */
+	switch (nn->tok) {
+	case MDOC_Sh:
+	case MDOC_Ss:
+	case MDOC_Fo:
+		nn = nn->head->child == NULL ? n : nn->head;
+		break;
+	case MDOC_It:
+		np = nn->parent;
+		while (np->tok != MDOC_Bl)
+			np = np->parent;
+		switch (np->norm->Bl.type) {
+		case LIST_column:
+			break;
+		case LIST_diag:
+		case LIST_hang:
+		case LIST_inset:
+		case LIST_ohang:
+		case LIST_tag:
+			nn = nn->head;
+			break;
+		case LIST_bullet:
+		case LIST_dash:
+		case LIST_enum:
+		case LIST_hyphen:
+		case LIST_item:
+			nn = nn->body->child == NULL ? n : nn->body;
+			break;
+		default:
+			abort();
+		}
+		break;
+	case MDOC_Bd:
+	case MDOC_Bl:
+	case MDOC_D1:
+	case MDOC_Dl:
+		nn = nn->body->child == NULL ? n : nn->body;
+		break;
+	case MDOC_Pp:
+		break;
+	case MDOC_Cm:
+	case MDOC_Dv:
+	case MDOC_Em:
+	case MDOC_Er:
+	case MDOC_Ev:
+	case MDOC_Fl:
+	case MDOC_Fn:
+	case MDOC_Ic:
+	case MDOC_Li:
+	case MDOC_Ms:
+	case MDOC_No:
+	case MDOC_Sy:
+		if (nn->child == NULL)
+			nn = n;
+		break;
+	default:
+		nn = n;
+		break;
+	}
+	tag_put(nt->string, TAG_MANUAL, nn);
+	if (nn != n)
+		n->flags |= NODE_NOPRT;
+}
+
 static void
 post_obsolete(POST_ARGS)
 {
@@ -1181,22 +1311,32 @@ post_bf(POST_ARGS)
 static void
 post_fname(POST_ARGS)
 {
-	const struct roff_node	*n;
+	struct roff_node	*n, *nch;
 	const char		*cp;
 	size_t			 pos;
 
-	n = mdoc->last->child;
-	pos = strcspn(n->string, "()");
-	cp = n->string + pos;
-	if ( ! (cp[0] == '\0' || (cp[0] == '(' && cp[1] == '*')))
-		mandoc_msg(MANDOCERR_FN_PAREN, n->line, n->pos + pos,
-		    "%s", n->string);
+	n = mdoc->last;
+	nch = n->child;
+	cp = nch->string;
+	if (*cp == '(') {
+		if (cp[strlen(cp + 1)] == ')')
+			return;
+		pos = 0;
+	} else {
+		pos = strcspn(cp, "()");
+		if (cp[pos] == '\0') {
+			if (n->sec == SEC_DESCRIPTION ||
+			    n->sec == SEC_CUSTOM)
+				tag_put(NULL, fn_prio++, n);
+			return;
+		}
+	}
+	mandoc_msg(MANDOCERR_FN_PAREN, nch->line, nch->pos + pos, "%s", cp);
 }
 
 static void
 post_fn(POST_ARGS)
 {
-
 	post_fname(mdoc);
 	post_fa(mdoc);
 }
@@ -1360,38 +1500,29 @@ post_display(POST_ARGS)
 static void
 post_defaults(POST_ARGS)
 {
-	struct roff_node *nn;
+	struct roff_node *n;
 
-	if (mdoc->last->child != NULL) {
+	n = mdoc->last;
+	if (n->child != NULL) {
 		post_delim_nb(mdoc);
 		return;
 	}
-
-	/*
-	 * The `Ar' defaults to "file ..." if no value is provided as an
-	 * argument; the `Mt' and `Pa' macros use "~"; the `Li' just
-	 * gets an empty string.
-	 */
-
-	nn = mdoc->last;
-	switch (nn->tok) {
+	mdoc->next = ROFF_NEXT_CHILD;
+	switch (n->tok) {
 	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, "...");
+		roff_word_alloc(mdoc, n->line, n->pos, "file");
 		mdoc->last->flags |= NODE_NOSRC;
+		roff_word_alloc(mdoc, n->line, n->pos, "...");
 		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;
+		roff_word_alloc(mdoc, n->line, n->pos, "~");
 		break;
 	default:
 		abort();
 	}
-	mdoc->last = nn;
+	mdoc->last->flags |= NODE_NOSRC;
+	mdoc->last = n;
 }
 
 static void
@@ -1445,22 +1576,81 @@ post_an(POST_ARGS)
 }
 
 static void
-post_en(POST_ARGS)
+post_em(POST_ARGS)
 {
+	post_tag(mdoc);
+	tag_put(NULL, TAG_FALLBACK, mdoc->last);
+}
 
+static void
+post_en(POST_ARGS)
+{
 	post_obsolete(mdoc);
 	if (mdoc->last->type == ROFFT_BLOCK)
 		mdoc->last->norm->Es = mdoc->last_es;
 }
 
 static void
-post_es(POST_ARGS)
+post_er(POST_ARGS)
 {
+	struct roff_node *n;
+
+	n = mdoc->last;
+	if (n->sec == SEC_ERRORS &&
+	    (n->parent->tok == MDOC_It ||
+	     (n->parent->tok == MDOC_Bq &&
+	      n->parent->parent->parent->tok == MDOC_It)))
+		tag_put(NULL, TAG_STRONG, n);
+	post_delim_nb(mdoc);
+}
+
+static void
+post_tag(POST_ARGS)
+{
+	struct roff_node *n;
+
+	n = mdoc->last;
+	if ((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 &&
+	      n->parent->parent->parent->tok == MDOC_It)))
+		tag_put(NULL, TAG_STRONG, n);
+	post_delim_nb(mdoc);
+}
 
+static void
+post_es(POST_ARGS)
+{
 	post_obsolete(mdoc);
 	mdoc->last_es = mdoc->last;
 }
 
+static void
+post_fl(POST_ARGS)
+{
+	struct roff_node	*n;
+	char			*cp;
+
+	/*
+	 * Transform ".Fl Fl long" to ".Fl \-long",
+	 * resulting for example in better HTML output.
+	 */
+
+	n = mdoc->last;
+	if (n->prev != NULL && n->prev->tok == MDOC_Fl &&
+	    n->prev->child == NULL && n->child != NULL &&
+	    (n->flags & NODE_LINE) == 0) {
+		mandoc_asprintf(&cp, "\\-%s", n->child->string);
+		free(n->child->string);
+		n->child->string = cp;
+		roff_node_delete(mdoc, n->prev);
+	}
+	post_tag(mdoc);
+}
+
 static void
 post_xx(POST_ARGS)
 {
@@ -1553,8 +1743,8 @@ post_it(POST_ARGS)
 		if ((nch = nit->head->child) != NULL)
 			mandoc_msg(MANDOCERR_ARG_SKIP,
 			    nit->line, nit->pos, "It %s",
-			    nch->string == NULL ? roff_name[nch->tok] :
-			    nch->string);
+			    nch->type == ROFFT_TEXT ? nch->string :
+			    roff_name[nch->tok]);
 		break;
 	case LIST_column:
 		cols = (int)nbl->norm->Bl.ncols;
@@ -1717,8 +1907,7 @@ post_bl_head(POST_ARGS)
 static void
 post_bl(POST_ARGS)
 {
-	struct roff_node	*nparent, *nprev; /* of the Bl block */
-	struct roff_node	*nblock, *nbody;  /* of the Bl */
+	struct roff_node	*nbody;           /* of the Bl */
 	struct roff_node	*nchild, *nnext;  /* of the Bl body */
 	const char		*prev_Er;
 	int			 order;
@@ -1739,88 +1928,73 @@ post_bl(POST_ARGS)
 	if (nbody->end != ENDBODY_NOT)
 		return;
 
-	nchild = nbody->child;
-	if (nchild == NULL) {
-		mandoc_msg(MANDOCERR_BLK_EMPTY,
-		    nbody->line, nbody->pos, "Bl");
-		return;
+	/*
+	 * Up to the first item, move nodes before the list,
+	 * but leave transparent nodes where they are
+	 * if they precede an item.
+	 * The next non-transparent node is kept in nchild.
+	 * It only needs to be updated after a non-transparent
+	 * node was moved out, and at the very beginning
+	 * when no node at all was moved yet.
+	 */
+
+	nchild = mdoc->last;
+	for (;;) {
+		if (nchild == mdoc->last)
+			nchild = roff_node_child(nbody);
+		if (nchild == NULL) {
+			mdoc->last = nbody;
+			mandoc_msg(MANDOCERR_BLK_EMPTY,
+			    nbody->line, nbody->pos, "Bl");
+			return;
+		}
+		if (nchild->tok == MDOC_It) {
+			mdoc->last = nbody;
+			break;
+		}
+		mandoc_msg(MANDOCERR_BL_MOVE, nbody->child->line,
+		    nbody->child->pos, "%s", roff_name[nbody->child->tok]);
+		if (nbody->parent->prev == NULL) {
+			mdoc->last = nbody->parent->parent;
+			mdoc->next = ROFF_NEXT_CHILD;
+		} else {
+			mdoc->last = nbody->parent->prev;
+			mdoc->next = ROFF_NEXT_SIBLING;
+		}
+		roff_node_relink(mdoc, nbody->child);
 	}
+
+	/*
+	 * We have reached the first item,
+	 * so moving nodes out is no longer possible.
+	 * But 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.
+	 * Wrap such roff nodes into an implicit row.
+	 */
+
 	while (nchild != NULL) {
-		nnext = nchild->next;
-		if (nchild->tok == MDOC_It ||
-		    (nchild->tok == MDOC_Sm &&
-		     nnext != NULL && nnext->tok == MDOC_It)) {
-			nchild = nnext;
+		if (nchild->tok == MDOC_It) {
+			nchild = roff_node_next(nchild);
 			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);
+		nnext = nchild->next;
+		mdoc->last = nchild->prev;
+		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) {
+			roff_node_relink(mdoc, nchild);
+			if (nnext == NULL)
+				break;
+			nchild = nnext;
+			nnext = nchild->next;
 			mdoc->next = ROFF_NEXT_SIBLING;
-			roff_body_alloc(mdoc, nchild->line,
-			    nchild->pos, MDOC_It);
-			while (nchild->tok != MDOC_It) {
-				roff_node_relink(mdoc, nchild);
-				if ((nchild = nnext) == NULL)
-					break;
-				nnext = nchild->next;
-				mdoc->next = ROFF_NEXT_SIBLING;
-			}
-			mdoc->last = nbody;
-			continue;
 		}
-
-		mandoc_msg(MANDOCERR_BL_MOVE, nchild->line, nchild->pos,
-		    "%s", roff_name[nchild->tok]);
-
-		/*
-		 * Move the node out of the Bl block.
-		 * First, collect all required node pointers.
-		 */
-
-		nblock  = nbody->parent;
-		nprev   = nblock->prev;
-		nparent = nblock->parent;
-
-		/*
-		 * Unlink this child.
-		 */
-
-		nbody->child = nnext;
-		if (nnext == NULL)
-			nbody->last  = NULL;
-		else
-			nnext->prev = NULL;
-
-		/*
-		 * Relink this child.
-		 */
-
-		nchild->parent = nparent;
-		nchild->prev   = nprev;
-		nchild->next   = nblock;
-
-		nblock->prev = nchild;
-		if (nprev == NULL)
-			nparent->child = nchild;
-		else
-			nprev->next = nchild;
-
-		nchild = nnext;
+		mdoc->last = nbody;
 	}
 
 	if (mdoc->meta.os_e != MANDOC_OS_NETBSD)
@@ -1903,7 +2077,7 @@ post_root(POST_ARGS)
 	/* Add missing prologue data. */
 
 	if (mdoc->meta.date == NULL)
-		mdoc->meta.date = mandoc_normdate(mdoc, NULL, 0, 0);
+		mdoc->meta.date = mandoc_normdate(NULL, NULL);
 
 	if (mdoc->meta.title == NULL) {
 		mandoc_msg(MANDOCERR_DT_NOTITLE, 0, 0, "EOF");
@@ -2048,10 +2222,11 @@ post_rs(POST_ARGS)
 static void
 post_hyph(POST_ARGS)
 {
-	struct roff_node	*nch;
+	struct roff_node	*n, *nch;
 	char			*cp;
 
-	for (nch = mdoc->last->child; nch != NULL; nch = nch->next) {
+	n = mdoc->last;
+	for (nch = n->child; nch != NULL; nch = nch->next) {
 		if (nch->type != ROFFT_TEXT)
 			continue;
 		cp = nch->string;
@@ -2060,8 +2235,11 @@ post_hyph(POST_ARGS)
 		while (*(++cp) != '\0')
 			if (*cp == '-' &&
 			    isalpha((unsigned char)cp[-1]) &&
-			    isalpha((unsigned char)cp[1]))
+			    isalpha((unsigned char)cp[1])) {
+				if (n->tag == NULL && n->flags & NODE_ID)
+					n->tag = mandoc_strdup(nch->string);
 				*cp = ASCII_HYPH;
+			}
 	}
 }
 
@@ -2086,8 +2264,7 @@ post_sx(POST_ARGS)
 static void
 post_sh(POST_ARGS)
 {
-
-	post_ignpar(mdoc);
+	post_section(mdoc);
 
 	switch (mdoc->last->type) {
 	case ROFFT_HEAD:
@@ -2318,6 +2495,8 @@ post_sh_head(POST_ARGS)
 		roff_setreg(mdoc->roff, "nS", 0, '=');
 		mdoc->flags &= ~MDOC_SYNOPSIS;
 	}
+	if (sec == SEC_DESCRIPTION)
+		fn_prio = TAG_STRONG;
 
 	/* Mark our last section. */
 
@@ -2418,15 +2597,31 @@ post_xr(POST_ARGS)
 }
 
 static void
-post_ignpar(POST_ARGS)
+post_section(POST_ARGS)
 {
-	struct roff_node *np;
+	struct roff_node *n, *nch;
+	char		 *cp, *tag;
 
-	switch (mdoc->last->type) {
+	n = mdoc->last;
+	switch (n->type) {
 	case ROFFT_BLOCK:
 		post_prevpar(mdoc);
 		return;
 	case ROFFT_HEAD:
+		tag = NULL;
+		deroff(&tag, n);
+		if (tag != NULL) {
+			for (cp = tag; *cp != '\0'; cp++)
+				if (*cp == ' ')
+					*cp = '_';
+			if ((nch = n->child) != NULL &&
+			    nch->type == ROFFT_TEXT &&
+			    strcmp(nch->string, tag) == 0)
+				tag_put(NULL, TAG_STRONG, n);
+			else
+				tag_put(tag, TAG_FALLBACK, n);
+			free(tag);
+		}
 		post_delim(mdoc);
 		post_hyph(mdoc);
 		return;
@@ -2435,42 +2630,40 @@ post_ignpar(POST_ARGS)
 	default:
 		return;
 	}
-
-	if ((np = mdoc->last->child) != NULL)
-		if (np->tok == MDOC_Pp ||
-		    np->tok == ROFF_br || np->tok == ROFF_sp) {
-			mandoc_msg(MANDOCERR_PAR_SKIP, np->line, np->pos,
-			    "%s after %s", roff_name[np->tok],
-			    roff_name[mdoc->last->tok]);
-			roff_node_delete(mdoc, np);
-		}
-
-	if ((np = mdoc->last->last) != NULL)
-		if (np->tok == MDOC_Pp || np->tok == ROFF_br) {
-			mandoc_msg(MANDOCERR_PAR_SKIP, np->line, np->pos,
-			    "%s at the end of %s", roff_name[np->tok],
-			    roff_name[mdoc->last->tok]);
-			roff_node_delete(mdoc, np);
-		}
+	if ((nch = n->child) != NULL &&
+	    (nch->tok == MDOC_Pp || nch->tok == ROFF_br ||
+	     nch->tok == ROFF_sp)) {
+		mandoc_msg(MANDOCERR_PAR_SKIP, nch->line, nch->pos,
+		    "%s after %s", roff_name[nch->tok],
+		    roff_name[n->tok]);
+		roff_node_delete(mdoc, nch);
+	}
+	if ((nch = n->last) != NULL &&
+	    (nch->tok == MDOC_Pp || nch->tok == ROFF_br)) {
+		mandoc_msg(MANDOCERR_PAR_SKIP, nch->line, nch->pos,
+		    "%s at the end of %s", roff_name[nch->tok],
+		    roff_name[n->tok]);
+		roff_node_delete(mdoc, nch);
+	}
 }
 
 static void
 post_prevpar(POST_ARGS)
 {
-	struct roff_node *n;
+	struct roff_node *n, *np;
 
 	n = mdoc->last;
-	if (NULL == n->prev)
-		return;
 	if (n->type != ROFFT_ELEM && n->type != ROFFT_BLOCK)
 		return;
+	if ((np = roff_node_prev(n)) == NULL)
+		return;
 
 	/*
 	 * Don't allow `Pp' prior to a paragraph-type
 	 * block: `Pp' or non-compact `Bd' or `Bl'.
 	 */
 
-	if (n->prev->tok != MDOC_Pp && n->prev->tok != ROFF_br)
+	if (np->tok != MDOC_Pp && np->tok != ROFF_br)
 		return;
 	if (n->tok == MDOC_Bl && n->norm->Bl.comp)
 		return;
@@ -2479,9 +2672,9 @@ post_prevpar(POST_ARGS)
 	if (n->tok == MDOC_It && n->parent->norm->Bl.comp)
 		return;
 
-	mandoc_msg(MANDOCERR_PAR_SKIP, n->prev->line, n->prev->pos,
-	    "%s before %s", roff_name[n->prev->tok], roff_name[n->tok]);
-	roff_node_delete(mdoc, n->prev);
+	mandoc_msg(MANDOCERR_PAR_SKIP, np->line, np->pos,
+	    "%s before %s", roff_name[np->tok], roff_name[n->tok]);
+	roff_node_delete(mdoc, np);
 }
 
 static void
@@ -2489,6 +2682,7 @@ post_par(POST_ARGS)
 {
 	struct roff_node *np;
 
+	fn_prio = TAG_STRONG;
 	post_prevpar(mdoc);
 
 	np = mdoc->last;
@@ -2501,7 +2695,6 @@ static void
 post_dd(POST_ARGS)
 {
 	struct roff_node *n;
-	char		 *datestr;
 
 	n = mdoc->last;
 	n->flags |= NODE_NOPRT;
@@ -2518,10 +2711,10 @@ post_dd(POST_ARGS)
 		mandoc_msg(MANDOCERR_PROLOG_ORDER,
 		    n->line, n->pos, "Dd after Os");
 
-	datestr = NULL;
-	deroff(&datestr, n);
-	mdoc->meta.date = mandoc_normdate(mdoc, datestr, n->line, n->pos);
-	free(datestr);
+	if (mdoc->quick && n != NULL)
+		mdoc->meta.date = mandoc_strdup("");
+	else
+		mdoc->meta.date = mandoc_normdate(n->child, n);
 }
 
 static void
@@ -2596,8 +2789,14 @@ post_dt(POST_ARGS)
 		mandoc_msg(MANDOCERR_MSEC_BAD,
 		    nn->line, nn->pos, "Dt ... %s", nn->string);
 		mdoc->meta.vol = mandoc_strdup(nn->string);
-	} else
+	} else {
 		mdoc->meta.vol = mandoc_strdup(cp);
+		if (mdoc->filesec != '\0' &&
+		    mdoc->filesec != *nn->string &&
+		    *nn->string >= '1' && *nn->string <= '9')
+			mandoc_msg(MANDOCERR_MSEC_FILE, nn->line, nn->pos,
+			    "*.%c vs Dt ... %c", mdoc->filesec, *nn->string);
+	}
 
 	/* Optional third argument: architecture. */
 
diff --git a/out.c b/out.c
index d0b0d0a2ace3..12333e38ffde 100644
--- a/out.c
+++ b/out.c
@@ -1,7 +1,8 @@
-/*	$Id: out.c,v 1.78 2019/03/29 21:27:06 schwarze Exp $ */
+/*	$Id: out.c,v 1.82 2021/09/07 17:07:58 schwarze Exp $ */
 /*
  * Copyright (c) 2009, 2010, 2011 Kristaps Dzonsons 
- * Copyright (c) 2011,2014,2015,2017,2018 Ingo Schwarze 
+ * Copyright (c) 2011, 2014, 2015, 2017, 2018, 2019, 2021
+ *               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
@@ -22,11 +23,13 @@
 #include 
 #include 
 #include 
+#include 
 #include 
 #include 
 #include 
 
 #include "mandoc_aux.h"
+#include "mandoc.h"
 #include "tbl.h"
 #include "out.h"
 
@@ -120,7 +123,6 @@ tblcalc(struct rofftbl *tbl, const struct tbl_span *sp_first,
 	const struct tbl_dat	*dp;
 	struct roffcol		*col;
 	struct tbl_colgroup	*first_group, **gp, *g;
-	size_t			*colwidth;
 	size_t			 ewidth, min1, min2, wanted, width, xwidth;
 	int			 done, icol, maxcol, necol, nxcol, quirkcol;
 
@@ -209,13 +211,25 @@ tblcalc(struct rofftbl *tbl, const struct tbl_span *sp_first,
 	}
 
 	/*
-	 * Column spacings are needed for span width calculations,
-	 * so set the default values now.
+	 * The minimum width of columns explicitly specified
+	 * in the layout is 1n.
 	 */
 
-	for (icol = 0; icol <= maxcol; icol++)
-		if (tbl->cols[icol].spacing == SIZE_MAX || icol == maxcol)
-			tbl->cols[icol].spacing = 3;
+	if (maxcol < sp_first->opts->cols - 1)
+		maxcol = sp_first->opts->cols - 1;
+	for (icol = 0; icol <= maxcol; icol++) {
+		col = tbl->cols + icol;
+		if (col->width < 1)
+			col->width = 1;
+
+		/*
+		 * Column spacings are needed for span width
+		 * calculations, so set the default values now.
+		 */
+
+		if (col->spacing == SIZE_MAX || icol == maxcol)
+			col->spacing = 3;
+	}
 
 	/*
 	 * Replace the minimum widths with the missing widths,
@@ -242,20 +256,8 @@ tblcalc(struct rofftbl *tbl, const struct tbl_span *sp_first,
 			gp = &(*gp)->next;
 	}
 
-	colwidth = mandoc_reallocarray(NULL, maxcol + 1, sizeof(*colwidth));
 	while (first_group != NULL) {
 
-		/*
-		 * Rebuild the array of the widths of all columns
-		 * participating in spans that require expansion.
-		 */
-
-		for (icol = 0; icol <= maxcol; icol++)
-			colwidth[icol] = SIZE_MAX;
-		for (g = first_group; g != NULL; g = g->next)
-			for (icol = g->startcol; icol <= g->endcol; icol++)
-				colwidth[icol] = tbl->cols[icol].width;
-
 		/*
 		 * Find the smallest and second smallest column width
 		 * among the columns which may need expamsion.
@@ -263,12 +265,12 @@ tblcalc(struct rofftbl *tbl, const struct tbl_span *sp_first,
 
 		min1 = min2 = SIZE_MAX;
 		for (icol = 0; icol <= maxcol; icol++) {
-			if (min1 > colwidth[icol]) {
+			width = tbl->cols[icol].width;
+			if (min1 > width) {
 				min2 = min1;
-				min1 = colwidth[icol];
-			} else if (min1 < colwidth[icol] &&
-			    min2 > colwidth[icol])
-				min2 = colwidth[icol];
+				min1 = width;
+			} else if (min1 < width && min2 > width)
+				min2 = width;
 		}
 
 		/*
@@ -290,26 +292,22 @@ tblcalc(struct rofftbl *tbl, const struct tbl_span *sp_first,
 				width = min2;
 			if (wanted > width)
 				wanted = width;
-			for (icol = g->startcol; icol <= g->endcol; icol++)
-				if (colwidth[icol] == min1 ||
-				    (colwidth[icol] < min2 &&
-				     colwidth[icol] > width))
-					colwidth[icol] = width;
 		}
 
-		/* Record the effect of the widening on the group list. */
+		/* Record the effect of the widening. */
 
 		gp = &first_group;
 		while ((g = *gp) != NULL) {
 			done = 0;
 			for (icol = g->startcol; icol <= g->endcol; icol++) {
-				if (colwidth[icol] != wanted ||
-				    tbl->cols[icol].width == wanted)
+				if (tbl->cols[icol].width != min1)
 					continue;
 				if (g->wanted <= wanted - min1) {
+					tbl->cols[icol].width += g->wanted;
 					done = 1;
 					break;
 				}
+				tbl->cols[icol].width = wanted;
 				g->wanted -= wanted - min1;
 			}
 			if (done) {
@@ -318,14 +316,7 @@ tblcalc(struct rofftbl *tbl, const struct tbl_span *sp_first,
 			} else
 				gp = &(*gp)->next;
 		}
-
-		/* Record the effect of the widening on the columns. */
-
-		for (icol = 0; icol <= maxcol; icol++)
-			if (colwidth[icol] == wanted)
-				tbl->cols[icol].width = wanted;
 	}
-	free(colwidth);
 
 	/*
 	 * Align numbers with text.
@@ -340,8 +331,6 @@ tblcalc(struct rofftbl *tbl, const struct tbl_span *sp_first,
 		col = tbl->cols + icol;
 		if (col->width > col->nwidth)
 			col->decimal += (col->width - col->nwidth) / 2;
-		else
-			col->width = col->nwidth;
 		if (col->flags & TBL_CELL_EQUAL) {
 			necol++;
 			if (ewidth < col->width)
@@ -549,5 +538,7 @@ tblcalc_number(struct rofftbl *tbl, struct roffcol *col,
 
 	if (totsz > col->nwidth)
 		col->nwidth = totsz;
+	if (col->nwidth > col->width)
+		col->width = col->nwidth;
 	return totsz;
 }
diff --git a/out.h b/out.h
index dec6a8f87324..e621cbb7b92e 100644
--- a/out.h
+++ b/out.h
@@ -1,4 +1,4 @@
-/*	$Id: out.h,v 1.33 2018/08/18 20:18:14 schwarze Exp $ */
+/* $Id: out.h,v 1.34 2020/04/03 11:35:01 schwarze Exp $ */
 /*
  * Copyright (c) 2009, 2010, 2011 Kristaps Dzonsons 
  * Copyright (c) 2014, 2017, 2018 Ingo Schwarze 
@@ -14,6 +14,8 @@
  * 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.
+ *
+ * Utilities for use by multiple mandoc(1) formatters.
  */
 
 enum	roffscale {
@@ -64,5 +66,5 @@ struct	rofftbl {
 struct	tbl_span;
 
 const char	 *a2roffsu(const char *, struct roffsu *, enum roffscale);
-void		  tblcalc(struct rofftbl *tbl,
+void		  tblcalc(struct rofftbl *,
 			const struct tbl_span *, size_t, size_t);
diff --git a/read.c b/read.c
index b3858061e710..5b33edbe9cef 100644
--- a/read.c
+++ b/read.c
@@ -1,7 +1,7 @@
-/*	$Id: read.c,v 1.214 2019/07/10 19:39:01 schwarze Exp $ */
+/* $Id: read.c,v 1.220 2021/06/27 17:57:54 schwarze Exp $ */
 /*
+ * Copyright (c) 2010-2020 Ingo Schwarze 
  * Copyright (c) 2008, 2009, 2010, 2011 Kristaps Dzonsons 
- * Copyright (c) 2010-2019 Ingo Schwarze 
  * Copyright (c) 2010, 2012 Joerg Sonnenberger 
  *
  * Permission to use, copy, modify, and distribute this software for any
@@ -15,6 +15,12 @@
  * 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.
+ *
+ * Top-level functions of the mandoc(3) parser:
+ * Parser and input encoding selection, decompression,
+ * handling of input bytes, characters, lines, and files,
+ * handling of roff(7) loops and file inclusion,
+ * and steering of the various parsers.
  */
 #include "config.h"
 
@@ -41,6 +47,7 @@
 #include "mandoc_parse.h"
 #include "libmandoc.h"
 #include "roff_int.h"
+#include "tag.h"
 
 #define	REPARSE_LIMIT	1000
 
@@ -147,6 +154,7 @@ mparse_buf_r(struct mparse *curp, struct buf blk, size_t i, int start)
 	struct buf	*firstln, *lastln, *thisln, *loop;
 	char		*cp;
 	size_t		 pos; /* byte number in the ln buffer */
+	size_t		 spos; /* at the start of the current line parse */
 	int		 line_result, result;
 	int		 of;
 	int		 lnn; /* line number in the real file */
@@ -173,6 +181,7 @@ mparse_buf_r(struct mparse *curp, struct buf blk, size_t i, int start)
 			    curp->filenc & MPARSE_LATIN1)
 				curp->filenc = preconv_cue(&blk, i);
 		}
+		spos = pos;
 
 		while (i < blk.sz && (start || blk.buf[i] != '\0')) {
 
@@ -272,7 +281,8 @@ mparse_buf_r(struct mparse *curp, struct buf blk, size_t i, int start)
 
 		of = 0;
 rerun:
-		line_result = roff_parseln(curp->roff, curp->line, &ln, &of);
+		line_result = roff_parseln(curp->roff, curp->line,
+		    &ln, &of, start && spos == 0 ? pos : 0);
 
 		/* Process options. */
 
@@ -547,7 +557,7 @@ mparse_readfd(struct mparse *curp, int fd, const char *filename)
 
 	struct buf	 blk;
 	struct buf	*save_primary;
-	const char	*save_filename;
+	const char	*save_filename, *cp;
 	size_t		 offset;
 	int		 save_filenc, save_lineno;
 	int		 with_mmap;
@@ -555,7 +565,13 @@ mparse_readfd(struct mparse *curp, int fd, const char *filename)
 	if (recursion_depth > 64) {
 		mandoc_msg(MANDOCERR_ROFFLOOP, curp->line, 0, NULL);
 		return;
-	}
+	} else if (recursion_depth == 0 &&
+	    (cp = strrchr(filename, '.')) != NULL &&
+            cp[1] >= '1' && cp[1] <= '9')
+                curp->man->filesec = cp[1];
+        else
+                curp->man->filesec = '\0';
+
 	if (read_whole_file(curp, fd, &blk, &with_mmap) == -1)
 		return;
 
@@ -664,22 +680,26 @@ mparse_alloc(int options, enum mandoc_os os_e, const char *os_s)
 	}
 	curp->man->meta.first->tok = TOKEN_NONE;
 	curp->man->meta.os_e = os_e;
+	tag_alloc();
 	return curp;
 }
 
 void
 mparse_reset(struct mparse *curp)
 {
+	tag_free();
 	roff_reset(curp->roff);
 	roff_man_reset(curp->man);
 	free_buf_list(curp->secondary);
 	curp->secondary = NULL;
 	curp->gzip = 0;
+	tag_alloc();
 }
 
 void
 mparse_free(struct mparse *curp)
 {
+	tag_free();
 	roffhash_free(curp->man->mdocmac);
 	roffhash_free(curp->man->manmac);
 	roff_man_free(curp->man);
@@ -697,6 +717,7 @@ mparse_result(struct mparse *curp)
 			mdoc_validate(curp->man);
 		else
 			man_validate(curp->man);
+		tag_postprocess(curp->man, curp->man->meta.first);
 	}
 	return &curp->man->meta;
 }
diff --git a/roff.7 b/roff.7
index f129750b1b0e..6c2e3583f69b 100644
--- a/roff.7
+++ b/roff.7
@@ -1,4 +1,4 @@
-.\"	$Id: roff.7,v 1.114 2019/07/15 19:20:30 schwarze Exp $
+.\"	$Id: roff.7,v 1.116 2021/09/18 12:23:06 schwarze Exp $
 .\"
 .\" Copyright (c) 2010, 2011, 2012 Kristaps Dzonsons 
 .\" Copyright (c) 2010-2019 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: July 15 2019 $
+.Dd $Mdocdate: September 18 2021 $
 .Dt ROFF 7
 .Os
 .Sh NAME
@@ -624,7 +624,7 @@ Its syntax can be either
 .Pp
 or
 .Bd -literal -offset indent
-.Pf . Ic \&de Ar macroname Ar endmacro
+.Pf . Ic \&de Ar macroname endmacro
 .Ar definition
 .Pf . Ar endmacro
 .Ed
@@ -2331,7 +2331,7 @@ for
 .At v2 ,
 then ported nroff to C as troff, which Brian W. Kernighan released with
 .At v7 .
-In 1989, James Clarke re-implemented troff in C++, naming it groff.
+In 1989, James Clark re-implemented troff in C++, naming it groff.
 .Sh AUTHORS
 .An -nosplit
 This
diff --git a/roff.c b/roff.c
index f26d9f01f226..de75a260f5a7 100644
--- a/roff.c
+++ b/roff.c
@@ -1,7 +1,7 @@
-/*	$Id: roff.c,v 1.366 2019/07/01 22:56:24 schwarze Exp $ */
+/* $Id: roff.c,v 1.378 2021/08/10 12:55:04 schwarze Exp $ */
 /*
+ * Copyright (c) 2010-2015, 2017-2020 Ingo Schwarze 
  * Copyright (c) 2008-2012, 2014 Kristaps Dzonsons 
- * Copyright (c) 2010-2015, 2017-2019 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,6 +14,8 @@
  * 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.
+ *
+ * Implementation of the roff(7) parser for mandoc(1).
  */
 #include "config.h"
 
@@ -190,13 +192,14 @@ static	int		 roff_cc(ROFF_ARGS);
 static	int		 roff_ccond(struct roff *, int, int);
 static	int		 roff_char(ROFF_ARGS);
 static	int		 roff_cond(ROFF_ARGS);
+static	int		 roff_cond_checkend(ROFF_ARGS);
 static	int		 roff_cond_text(ROFF_ARGS);
 static	int		 roff_cond_sub(ROFF_ARGS);
 static	int		 roff_ds(ROFF_ARGS);
 static	int		 roff_ec(ROFF_ARGS);
 static	int		 roff_eo(ROFF_ARGS);
 static	int		 roff_eqndelim(struct roff *, struct buf *, int);
-static	int		 roff_evalcond(struct roff *r, int, char *, int *);
+static	int		 roff_evalcond(struct roff *, int, char *, int *);
 static	int		 roff_evalnum(struct roff *, int,
 				const char *, int *, int *, int);
 static	int		 roff_evalpar(struct roff *, int,
@@ -355,7 +358,7 @@ const char *__roff_name[MAN_MAX + 1] = {
 	"Lk",		"Mt",		"Brq",		"Bro",
 	"Brc",		"%C",		"Es",		"En",
 	"Dx",		"%Q",		"%U",		"Ta",
-	NULL,
+	"Tg",		NULL,
 	"TH",		"SH",		"SS",		"TP",
 	"TQ",
 	"LP",		"PP",		"P",		"IP",
@@ -771,6 +774,7 @@ void
 roff_reset(struct roff *r)
 {
 	roff_free1(r);
+	r->options |= MPARSE_COMMENT;
 	r->format = r->options & (MPARSE_MDOC | MPARSE_MAN);
 	r->control = '\0';
 	r->escape = '\\';
@@ -800,7 +804,7 @@ roff_alloc(int options)
 
 	r = mandoc_calloc(1, sizeof(struct roff));
 	r->reqtab = roffhash_alloc(0, ROFF_RENAMED);
-	r->options = options;
+	r->options = options | MPARSE_COMMENT;
 	r->format = options & (MPARSE_MDOC | MPARSE_MAN);
 	r->mstackpos = -1;
 	r->rstackpos = -1;
@@ -1100,6 +1104,7 @@ roff_node_free(struct roff_node *n)
 		free(n->norm);
 	eqn_box_free(n->eqn);
 	free(n->string);
+	free(n->tag);
 	free(n);
 }
 
@@ -1113,13 +1118,72 @@ roff_node_delete(struct roff_man *man, struct roff_node *n)
 	roff_node_free(n);
 }
 
+int
+roff_node_transparent(struct roff_node *n)
+{
+	if (n == NULL)
+		return 0;
+	if (n->type == ROFFT_COMMENT || n->flags & NODE_NOPRT)
+		return 1;
+	return roff_tok_transparent(n->tok);
+}
+
+int
+roff_tok_transparent(enum roff_tok tok)
+{
+	switch (tok) {
+	case ROFF_ft:
+	case ROFF_ll:
+	case ROFF_mc:
+	case ROFF_po:
+	case ROFF_ta:
+	case MDOC_Db:
+	case MDOC_Es:
+	case MDOC_Sm:
+	case MDOC_Tg:
+	case MAN_DT:
+	case MAN_UC:
+	case MAN_PD:
+	case MAN_AT:
+		return 1;
+	default:
+		return 0;
+	}
+}
+
+struct roff_node *
+roff_node_child(struct roff_node *n)
+{
+	for (n = n->child; roff_node_transparent(n); n = n->next)
+		continue;
+	return n;
+}
+
+struct roff_node *
+roff_node_prev(struct roff_node *n)
+{
+	do {
+		n = n->prev;
+	} while (roff_node_transparent(n));
+	return n;
+}
+
+struct roff_node *
+roff_node_next(struct roff_node *n)
+{
+	do {
+		n = n->next;
+	} while (roff_node_transparent(n));
+	return n;
+}
+
 void
 deroff(char **dest, const struct roff_node *n)
 {
 	char	*cp;
 	size_t	 sz;
 
-	if (n->type != ROFFT_TEXT) {
+	if (n->string == NULL) {
 		for (n = n->child; n != NULL; n = n->next)
 			deroff(dest, n);
 		return;
@@ -1246,7 +1310,7 @@ roff_expand(struct roff *r, struct buf *buf, int ln, int pos, char newesc)
 		 * in the syntax tree.
 		 */
 
-		if (newesc != ASCII_ESC && r->format == 0) {
+		if (newesc != ASCII_ESC && r->options & MPARSE_COMMENT) {
 			while (*ep == ' ' || *ep == '\t')
 				ep--;
 			ep[1] = '\0';
@@ -1759,7 +1823,7 @@ roff_parsetext(struct roff *r, struct buf *buf, int pos, int *offs)
 }
 
 int
-roff_parseln(struct roff *r, int ln, struct buf *buf, int *offs)
+roff_parseln(struct roff *r, int ln, struct buf *buf, int *offs, size_t len)
 {
 	enum roff_tok	 t;
 	int		 e;
@@ -1770,6 +1834,14 @@ roff_parseln(struct roff *r, int ln, struct buf *buf, int *offs)
 
 	ppos = pos = *offs;
 
+	if (len > 80 && r->tbl == NULL && r->eqn == NULL &&
+	    (r->man->flags & ROFF_NOFILL) == 0 &&
+	    strchr(" .\\", buf->buf[pos]) == NULL &&
+	    buf->buf[pos] != r->control &&
+	    strcspn(buf->buf, " ") < 80)
+		mandoc_msg(MANDOCERR_TEXT_LONG, ln, (int)len - 1,
+		    "%.20s...", buf->buf + pos);
+
 	/* Handle in-line equation delimiters. */
 
 	if (r->tbl == NULL &&
@@ -1815,8 +1887,10 @@ roff_parseln(struct roff *r, int ln, struct buf *buf, int *offs)
 		roff_addtbl(r->man, ln, r->tbl);
 		return e;
 	}
-	if ( ! ctl)
+	if ( ! ctl) {
+		r->options &= ~MPARSE_COMMENT;
 		return roff_parsetext(r, buf, pos, offs) | e;
+	}
 
 	/* Skip empty request lines. */
 
@@ -1839,6 +1913,7 @@ roff_parseln(struct roff *r, int ln, struct buf *buf, int *offs)
 
 	/* No scope is open.  This is a new request or macro. */
 
+	r->options &= ~MPARSE_COMMENT;
 	spos = pos;
 	t = roff_parse(r, buf->buf, &pos, ln, ppos);
 
@@ -1968,14 +2043,13 @@ roff_parse(struct roff *r, char *buf, int *pos, int ln, int ppos)
 
 /* --- handling of request blocks ----------------------------------------- */
 
+/*
+ * Close a macro definition block or an "ignore" block.
+ */
 static int
 roff_cblock(ROFF_ARGS)
 {
-
-	/*
-	 * A block-close `..' should only be invoked as a child of an
-	 * ignore macro, otherwise raise a warning and just ignore it.
-	 */
+	int	 rr;
 
 	if (r->last == NULL) {
 		mandoc_msg(MANDOCERR_BLK_NOTOPEN, ln, ppos, "..");
@@ -1984,26 +2058,38 @@ roff_cblock(ROFF_ARGS)
 
 	switch (r->last->tok) {
 	case ROFF_am:
-		/* ROFF_am1 is remapped to ROFF_am in roff_block(). */
 	case ROFF_ami:
 	case ROFF_de:
-		/* ROFF_de1 is remapped to ROFF_de in roff_block(). */
 	case ROFF_dei:
 	case ROFF_ig:
 		break;
+	case ROFF_am1:
+	case ROFF_de1:
+		/* Remapped in roff_block(). */
+		abort();
 	default:
 		mandoc_msg(MANDOCERR_BLK_NOTOPEN, ln, ppos, "..");
 		return ROFF_IGN;
 	}
 
+	roffnode_pop(r);
+	roffnode_cleanscope(r);
+
+	/*
+	 * If a conditional block with braces is still open,
+	 * check for "\}" block end markers.
+	 */
+
+	if (r->last != NULL && r->last->endspan < 0) {
+		rr = 1;  /* If arguments follow "\}", warn about them. */
+		roff_cond_checkend(r, tok, buf, ln, ppos, pos, &rr);
+	}
+
 	if (buf->buf[pos] != '\0')
 		mandoc_msg(MANDOCERR_ARG_SKIP, ln, pos,
 		    ".. %s", buf->buf + pos);
 
-	roffnode_pop(r);
-	roffnode_cleanscope(r);
 	return ROFF_IGN;
-
 }
 
 /*
@@ -2016,7 +2102,7 @@ roffnode_cleanscope(struct roff *r)
 	int inloop;
 
 	inloop = 0;
-	while (r->last != NULL) {
+	while (r->last != NULL && r->last->endspan > 0) {
 		if (--r->last->endspan != 0)
 			break;
 		inloop += roffnode_pop(r);
@@ -2025,7 +2111,7 @@ roffnode_cleanscope(struct roff *r)
 }
 
 /*
- * Handle the closing \} of a conditional block.
+ * Handle the closing "\}" of a conditional block.
  * Apart from generating warnings, this only pops nodes.
  * Return the number of loops ended.
  */
@@ -2245,13 +2331,20 @@ roff_block_text(ROFF_ARGS)
 	return ROFF_IGN;
 }
 
+/*
+ * Check for a closing "\}" and handle it.
+ * In this function, the final "int *offs" argument is used for
+ * different purposes than elsewhere:
+ * Input: *offs == 0: caller wants to discard arguments following \}
+ *        *offs == 1: caller wants to preserve text following \}
+ * Output: *offs = 0: tell caller to discard input line
+ *         *offs = 1: tell caller to use input line
+ */
 static int
-roff_cond_sub(ROFF_ARGS)
+roff_cond_checkend(ROFF_ARGS)
 {
-	struct roffnode	*bl;
 	char		*ep;
 	int		 endloop, irc, rr;
-	enum roff_tok	 t;
 
 	irc = ROFF_IGN;
 	rr = r->last->rule;
@@ -2261,23 +2354,28 @@ roff_cond_sub(ROFF_ARGS)
 		irc |= endloop;
 
 	/*
-	 * If `\}' occurs on a macro line without a preceding macro,
-	 * drop the line completely.
+	 * If "\}" occurs on a macro line without a preceding macro or
+	 * a text line contains nothing else, drop the line completely.
 	 */
 
 	ep = buf->buf + pos;
-	if (ep[0] == '\\' && ep[1] == '}')
+	if (ep[0] == '\\' && ep[1] == '}' && (ep[2] == '\0' || *offs == 0))
 		rr = 0;
 
 	/*
-	 * The closing delimiter `\}' rewinds the conditional scope
+	 * The closing delimiter "\}" rewinds the conditional scope
 	 * but is otherwise ignored when interpreting the line.
 	 */
 
 	while ((ep = strchr(ep, '\\')) != NULL) {
 		switch (ep[1]) {
 		case '}':
-			memmove(ep, ep + 2, strlen(ep + 2) + 1);
+			if (ep[2] == '\0')
+				ep[0] = '\0';
+			else if (rr)
+				ep[1] = '&';
+			else
+				memmove(ep, ep + 2, strlen(ep + 2) + 1);
 			if (roff_ccond(r, ln, ep - buf->buf))
 				irc |= endloop;
 			break;
@@ -2289,13 +2387,40 @@ roff_cond_sub(ROFF_ARGS)
 			break;
 		}
 	}
+	*offs = rr;
+	return irc;
+}
+
+/*
+ * Parse and process a request or macro line in conditional scope.
+ */
+static int
+roff_cond_sub(ROFF_ARGS)
+{
+	struct roffnode	*bl;
+	int		 irc, rr;
+	enum roff_tok	 t;
+
+	rr = 0;  /* If arguments follow "\}", skip them. */
+	irc = roff_cond_checkend(r, tok, buf, ln, ppos, pos, &rr);
+	t = roff_parse(r, buf->buf, &pos, ln, ppos);
+
+	/* For now, let high level macros abort .ce mode. */
+
+	if (roffce_node != NULL &&
+	    (t == TOKEN_NONE || t == ROFF_Dd || t == ROFF_EQ ||
+             t == ROFF_TH || t == ROFF_TS)) {
+		r->man->last = roffce_node;
+		r->man->next = ROFF_NEXT_SIBLING;
+		roffce_lines = 0;
+		roffce_node = NULL;
+	}
 
 	/*
 	 * Fully handle known macros when they are structurally
 	 * required or when the conditional evaluated to true.
 	 */
 
-	t = roff_parse(r, buf->buf, &pos, ln, ppos);
 	if (t == ROFF_break) {
 		if (irc & ROFF_LOOPMASK)
 			irc = ROFF_IGN | ROFF_LOOPEXIT;
@@ -2314,48 +2439,16 @@ roff_cond_sub(ROFF_ARGS)
 	return irc;
 }
 
+/*
+ * Parse and process a text line in conditional scope.
+ */
 static int
 roff_cond_text(ROFF_ARGS)
 {
-	char		*ep;
-	int		 endloop, irc, rr;
+	int	 irc, rr;
 
-	irc = ROFF_IGN;
-	rr = r->last->rule;
-	endloop = tok != ROFF_while ? ROFF_IGN :
-	    rr ? ROFF_LOOPCONT : ROFF_LOOPEXIT;
-	if (roffnode_cleanscope(r))
-		irc |= endloop;
-
-	/*
-	 * If `\}' occurs on a text line with neither preceding
-	 * nor following characters, drop the line completely.
-	 */
-
-	ep = buf->buf + pos;
-	if (strcmp(ep, "\\}") == 0)
-		rr = 0;
-
-	/*
-	 * The closing delimiter `\}' rewinds the conditional scope
-	 * but is otherwise ignored when interpreting the line.
-	 */
-
-	while ((ep = strchr(ep, '\\')) != NULL) {
-		switch (ep[1]) {
-		case '}':
-			memmove(ep, ep + 2, strlen(ep + 2) + 1);
-			if (roff_ccond(r, ln, ep - buf->buf))
-				irc |= endloop;
-			break;
-		case '\0':
-			++ep;
-			break;
-		default:
-			ep += 2;
-			break;
-		}
-	}
+	rr = 1;  /* If arguments follow "\}", preserve them. */
+	irc = roff_cond_checkend(r, tok, buf, ln, ppos, pos, &rr);
 	if (rr)
 		irc |= ROFF_CONT;
 	return irc;
@@ -3574,7 +3667,9 @@ roff_char(ROFF_ARGS)
 		case ESCAPE_FONTITALIC:
 		case ESCAPE_FONTBOLD:
 		case ESCAPE_FONTBI:
-		case ESCAPE_FONTCW:
+		case ESCAPE_FONTCR:
+		case ESCAPE_FONTCB:
+		case ESCAPE_FONTCI:
 		case ESCAPE_FONTPREV:
 			font++;
 			break;
diff --git a/roff.h b/roff.h
index 49b0927513d4..2933eb9c0bf8 100644
--- a/roff.h
+++ b/roff.h
@@ -1,7 +1,7 @@
-/*	$Id: roff.h,v 1.69 2019/03/04 13:01:57 schwarze Exp $	*/
+/* $Id: roff.h,v 1.74 2020/04/08 11:56:03 schwarze Exp $	*/
 /*
+ * Copyright (c) 2013-2015, 2017-2020 Ingo Schwarze 
  * Copyright (c) 2008, 2009, 2010, 2011 Kristaps Dzonsons 
- * Copyright (c) 2013-2015, 2017-2019 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
@@ -437,6 +437,7 @@ enum	roff_tok {
 	MDOC__Q,
 	MDOC__U,
 	MDOC_Ta,
+	MDOC_Tg,
 	MDOC_MAX,
 	MAN_TH,
 	MAN_SH,
@@ -505,6 +506,7 @@ struct	roff_node {
 	struct mdoc_arg	 *args;    /* BLOCK/ELEM */
 	union mdoc_data	 *norm;    /* Normalized arguments. */
 	char		 *string;  /* TEXT */
+	char		 *tag;     /* For less(1) :t and HTML id=. */
 	struct tbl_span	 *span;    /* TBL */
 	struct eqn_box	 *eqn;     /* EQN */
 	int		  line;    /* Input file line number. */
@@ -521,6 +523,8 @@ struct	roff_node {
 #define	NODE_NOFILL	 (1 << 8)  /* Fill mode switched off. */
 #define	NODE_NOSRC	 (1 << 9)  /* Generated node, not in input file. */
 #define	NODE_NOPRT	 (1 << 10) /* Shall not print anything. */
+#define	NODE_ID		 (1 << 11) /* Target for deep linking. */
+#define	NODE_HREF	 (1 << 12) /* Link to another place in this page. */
 	int		  prev_font; /* Before entering this node. */
 	int		  aux;     /* Decoded node data, type-dependent. */
 	enum roff_tok	  tok;     /* Request or macro ID. */
@@ -548,5 +552,10 @@ struct	roff_meta {
 extern	const char *const *roff_name;
 
 
-int		 arch_valid(const char *, enum mandoc_os);
-void		 deroff(char **, const struct roff_node *);
+int		  arch_valid(const char *, enum mandoc_os);
+void		  deroff(char **, const struct roff_node *);
+struct roff_node *roff_node_child(struct roff_node *);
+struct roff_node *roff_node_next(struct roff_node *);
+struct roff_node *roff_node_prev(struct roff_node *);
+int		  roff_node_transparent(struct roff_node *);
+int		  roff_tok_transparent(enum roff_tok);
diff --git a/roff_html.c b/roff_html.c
index e525aa2ede68..3cc7c19a8a51 100644
--- a/roff_html.c
+++ b/roff_html.c
@@ -1,4 +1,4 @@
-/*	$Id: roff_html.c,v 1.20 2019/04/30 15:53:01 schwarze Exp $ */
+/* $Id: roff_html.c,v 1.21 2020/06/22 19:20:40 schwarze Exp $ */
 /*
  * Copyright (c) 2010 Kristaps Dzonsons 
  * Copyright (c) 2014, 2017, 2018, 2019 Ingo Schwarze 
@@ -15,6 +15,8 @@
  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  */
+#include "config.h"
+
 #include 
 
 #include 
diff --git a/roff_int.h b/roff_int.h
index d033f86aceec..e0700a742910 100644
--- a/roff_int.h
+++ b/roff_int.h
@@ -1,7 +1,7 @@
-/*	$Id: roff_int.h,v 1.16 2019/01/05 00:36:50 schwarze Exp $	*/
+/* $OpenBSD: roff_int.h,v 1.16 2019/01/05 00:36:46 schwarze Exp $	*/
 /*
+ * Copyright (c) 2013-2015, 2017-2020 Ingo Schwarze 
  * Copyright (c) 2009, 2010, 2011 Kristaps Dzonsons 
- * Copyright (c) 2013-2015, 2017-2019 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
@@ -59,6 +59,7 @@ struct	roff_man {
 	enum roff_sec	  lastsec; /* Last section seen. */
 	enum roff_sec	  lastnamed; /* Last standard section seen. */
 	enum roff_next	  next;    /* Where to put the next node. */
+	char		  filesec; /* Section digit in the file name. */
 };
 
 
diff --git a/roff_term.c b/roff_term.c
index f10bb61d2cdd..115d850fb261 100644
--- a/roff_term.c
+++ b/roff_term.c
@@ -1,6 +1,6 @@
-/*	$Id: roff_term.c,v 1.19 2019/01/04 03:24:33 schwarze Exp $ */
+/* $OpenBSD: roff_term.c,v 1.20 2020/09/03 17:37:06 schwarze Exp $ */
 /*
- * Copyright (c) 2010,2014,2015,2017-2019 Ingo Schwarze 
+ * Copyright (c) 2010,2014,2015,2017-2020 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,6 +14,8 @@
  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  */
+#include "config.h"
+
 #include 
 
 #include 
@@ -110,9 +112,11 @@ roff_term_pre_ft(ROFF_TERM_ARGS)
 	cp = n->child->string;
 	switch (mandoc_font(cp, (int)strlen(cp))) {
 	case ESCAPE_FONTBOLD:
+	case ESCAPE_FONTCB:
 		term_fontrepl(p, TERMFONT_BOLD);
 		break;
 	case ESCAPE_FONTITALIC:
+	case ESCAPE_FONTCI:
 		term_fontrepl(p, TERMFONT_UNDER);
 		break;
 	case ESCAPE_FONTBI:
@@ -122,7 +126,7 @@ roff_term_pre_ft(ROFF_TERM_ARGS)
 		term_fontlast(p);
 		break;
 	case ESCAPE_FONTROMAN:
-	case ESCAPE_FONTCW:
+	case ESCAPE_FONTCR:
 		term_fontrepl(p, TERMFONT_NONE);
 		break;
 	default:
@@ -155,9 +159,13 @@ static void
 roff_term_pre_po(ROFF_TERM_ARGS)
 {
 	struct roffsu	 su;
-	static int	 po, polast;
+	static int	 po, pouse, polast;
 	int		 ponew;
 
+	/* Revert the currently active page offset. */
+	p->tcol->offset -= pouse;
+
+	/* Determine the requested page offset. */
 	if (n->child != NULL &&
 	    a2roffsu(n->child->string, &su, SCALE_EM) != NULL) {
 		ponew = term_hen(p, &su);
@@ -166,11 +174,15 @@ roff_term_pre_po(ROFF_TERM_ARGS)
 			ponew += po;
 	} else
 		ponew = polast;
+
+	/* Remeber both the previous and the newly requested offset. */
 	polast = po;
 	po = ponew;
 
-	ponew = po - polast + (int)p->tcol->offset;
-	p->tcol->offset = ponew > 0 ? ponew : 0;
+	/* Truncate to the range [-offset, 60], remember, and apply it. */
+	pouse = po >= 60 ? 60 :
+	    po < -(int)p->tcol->offset ? -(int)p->tcol->offset : po;
+	p->tcol->offset += pouse;
 }
 
 static void
@@ -208,6 +220,7 @@ roff_term_pre_ti(ROFF_TERM_ARGS)
 {
 	struct roffsu	 su;
 	const char	*cp;
+	const size_t	 maxoff = 72;
 	int		 len, sign;
 
 	roff_term_pre_br(p, n);
@@ -228,17 +241,26 @@ roff_term_pre_ti(ROFF_TERM_ARGS)
 		return;
 	len = term_hen(p, &su);
 
-	if (sign == 0) {
+	switch (sign) {
+	case 1:
+		if (p->tcol->offset + len <= maxoff)
+			p->ti = len;
+		else if (p->tcol->offset < maxoff)
+			p->ti = maxoff - p->tcol->offset;
+		else
+			p->ti = 0;
+		break;
+	case -1:
+		if ((size_t)len < p->tcol->offset)
+			p->ti = -len;
+		else
+			p->ti = -p->tcol->offset;
+		break;
+	default:
+		if ((size_t)len > maxoff)
+			len = maxoff;
 		p->ti = len - p->tcol->offset;
-		p->tcol->offset = len;
-	} else if (sign == 1) {
-		p->ti = len;
-		p->tcol->offset += len;
-	} else if ((size_t)len < p->tcol->offset) {
-		p->ti = -len;
-		p->tcol->offset -= len;
-	} else {
-		p->ti = -p->tcol->offset;
-		p->tcol->offset = 0;
+		break;
 	}
+	p->tcol->offset += p->ti;
 }
diff --git a/roff_validate.c b/roff_validate.c
index 9080f28797af..74eedafb9581 100644
--- a/roff_validate.c
+++ b/roff_validate.c
@@ -1,6 +1,6 @@
-/*	$Id: roff_validate.c,v 1.18 2018/12/31 09:02:37 schwarze Exp $ */
+/* $Id: roff_validate.c,v 1.20 2020/06/22 19:20:40 schwarze Exp $ */
 /*
- * Copyright (c) 2010, 2017, 2018 Ingo Schwarze 
+ * Copyright (c) 2010, 2017, 2018, 2020 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,6 +14,8 @@
  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  */
+#include "config.h"
+
 #include 
 
 #include 
@@ -75,7 +77,7 @@ roff_valid_br(ROFF_VALID_ARGS)
 		return;
 	}
 
-	if ((np = n->prev) == NULL)
+	if ((np = roff_node_prev(n)) == NULL)
 		return;
 
 	switch (np->tok) {
@@ -129,7 +131,7 @@ roff_valid_sp(ROFF_VALID_ARGS)
 {
 	struct roff_node	*np;
 
-	if ((np = n->prev) == NULL)
+	if ((np = roff_node_prev(n)) == NULL)
 		return;
 
 	switch (np->tok) {
diff --git a/soelim.c b/soelim.c
index 3ef30820e318..33d60e458826 100644
--- a/soelim.c
+++ b/soelim.c
@@ -1,4 +1,4 @@
-/*	$Id: soelim.c,v 1.5 2015/11/07 14:22:29 schwarze Exp $	*/
+/*	$Id: soelim.c,v 1.6 2021/09/19 18:14:24 schwarze Exp $	*/
 /*
  * Copyright (c) 2014 Baptiste Daroussin 
  * All rights reserved.
@@ -104,16 +104,16 @@ soelim_file(FILE *f, int flag)
 		}
 
 		walk = line + 3;
-		if (!isspace(*walk) && ((flag & C_OPTION) == 0)) {
+		if (!isspace((unsigned char)*walk) && (flag & C_OPTION) == 0) {
 			printf("%s", line);
 			continue;
 		}
 
-		while (isspace(*walk))
+		while (isspace((unsigned char)*walk))
 			walk++;
 
 		cp = walk;
-		while (*cp != '\0' && !isspace(*cp))
+		while (*cp != '\0' && !isspace((unsigned char)*cp))
 			cp++;
 		*cp = 0;
 		if (cp < line + linelen)
diff --git a/tag.c b/tag.c
index d5e7f89c1a50..fcaad99f2e2e 100644
--- a/tag.c
+++ b/tag.c
@@ -1,6 +1,6 @@
-/*	$Id: tag.c,v 1.24 2019/07/22 03:21:50 schwarze Exp $ */
+/* $Id: tag.c,v 1.36 2020/04/19 16:36:16 schwarze Exp $ */
 /*
- * Copyright (c) 2015, 2016, 2018, 2019 Ingo Schwarze 
+ * Copyright (c) 2015,2016,2018,2019,2020 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,142 +13,109 @@
  * 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.
+ *
+ * Functions to tag syntax tree nodes.
+ * For internal use by mandoc(1) validation modules only.
  */
 #include "config.h"
 
 #include 
 
-#include 
+#include 
 #include 
-#include 
 #include 
 #include 
-#include 
 #include 
 #include 
-#include 
 
 #include "mandoc_aux.h"
 #include "mandoc_ohash.h"
-#include "mandoc.h"
+#include "roff.h"
+#include "mdoc.h"
+#include "roff_int.h"
 #include "tag.h"
 
 struct tag_entry {
-	size_t	*lines;
-	size_t	 maxlines;
-	size_t	 nlines;
+	struct roff_node **nodes;
+	size_t	 maxnodes;
+	size_t	 nnodes;
 	int	 prio;
 	char	 s[];
 };
 
-static	void	 tag_signal(int) __attribute__((__noreturn__));
+static void		 tag_move_href(struct roff_man *,
+				struct roff_node *, const char *);
+static void		 tag_move_id(struct roff_node *);
 
 static struct ohash	 tag_data;
-static struct tag_files	 tag_files;
 
 
 /*
- * Prepare for using a pager.
- * Not all pagers are capable of using a tag file,
- * but for simplicity, create it anyway.
+ * Set up the ohash table to collect nodes
+ * where various marked-up terms are documented.
  */
-struct tag_files *
-tag_init(void)
+void
+tag_alloc(void)
 {
-	struct sigaction	 sa;
-	int			 ofd;
-
-	ofd = -1;
-	tag_files.tfd = -1;
-	tag_files.tcpgid = -1;
-
-	/* Clean up when dying from a signal. */
-
-	memset(&sa, 0, sizeof(sa));
-	sigfillset(&sa.sa_mask);
-	sa.sa_handler = tag_signal;
-	sigaction(SIGHUP, &sa, NULL);
-	sigaction(SIGINT, &sa, NULL);
-	sigaction(SIGTERM, &sa, NULL);
-
-	/*
-	 * POSIX requires that a process calling tcsetpgrp(3)
-	 * from the background gets a SIGTTOU signal.
-	 * In that case, do not stop.
-	 */
-
-	sa.sa_handler = SIG_IGN;
-	sigaction(SIGTTOU, &sa, NULL);
-
-	/* Save the original standard output for use by the pager. */
-
-	if ((tag_files.ofd = dup(STDOUT_FILENO)) == -1) {
-		mandoc_msg(MANDOCERR_DUP, 0, 0, "%s", strerror(errno));
-		goto fail;
-	}
+	mandoc_ohash_init(&tag_data, 4, offsetof(struct tag_entry, s));
+}
 
-	/* Create both temporary output files. */
+void
+tag_free(void)
+{
+	struct tag_entry	*entry;
+	unsigned int		 slot;
 
-	(void)strlcpy(tag_files.ofn, "/tmp/man.XXXXXXXXXX",
-	    sizeof(tag_files.ofn));
-	(void)strlcpy(tag_files.tfn, "/tmp/man.XXXXXXXXXX",
-	    sizeof(tag_files.tfn));
-	if ((ofd = mkstemp(tag_files.ofn)) == -1) {
-		mandoc_msg(MANDOCERR_MKSTEMP, 0, 0,
-		    "%s: %s", tag_files.ofn, strerror(errno));
-		goto fail;
-	}
-	if ((tag_files.tfd = mkstemp(tag_files.tfn)) == -1) {
-		mandoc_msg(MANDOCERR_MKSTEMP, 0, 0,
-		    "%s: %s", tag_files.tfn, strerror(errno));
-		goto fail;
-	}
-	if (dup2(ofd, STDOUT_FILENO) == -1) {
-		mandoc_msg(MANDOCERR_DUP, 0, 0, "%s", strerror(errno));
-		goto fail;
+	if (tag_data.info.free == NULL)
+		return;
+	entry = ohash_first(&tag_data, &slot);
+	while (entry != NULL) {
+		free(entry->nodes);
+		free(entry);
+		entry = ohash_next(&tag_data, &slot);
 	}
-	close(ofd);
-
-	/*
-	 * Set up the ohash table to collect output line numbers
-	 * where various marked-up terms are documented.
-	 */
-
-	mandoc_ohash_init(&tag_data, 4, offsetof(struct tag_entry, s));
-	return &tag_files;
-
-fail:
-	tag_unlink();
-	if (ofd != -1)
-		close(ofd);
-	if (tag_files.ofd != -1)
-		close(tag_files.ofd);
-	if (tag_files.tfd != -1)
-		close(tag_files.tfd);
-	*tag_files.ofn = '\0';
-	*tag_files.tfn = '\0';
-	tag_files.ofd = -1;
-	tag_files.tfd = -1;
-	return NULL;
+	ohash_delete(&tag_data);
+	tag_data.info.free = NULL;
 }
 
 /*
- * Set the line number where a term is defined,
+ * Set a node where a term is defined,
  * unless it is already defined at a lower priority.
  */
 void
-tag_put(const char *s, int prio, size_t line)
+tag_put(const char *s, int prio, struct roff_node *n)
 {
 	struct tag_entry	*entry;
+	struct roff_node	*nold;
 	const char		*se;
 	size_t			 len;
 	unsigned int		 slot;
 
-	if (tag_files.tfd <= 0)
-		return;
+	assert(prio <= TAG_FALLBACK);
 
-	if (s[0] == '\\' && (s[1] == '&' || s[1] == 'e'))
-		s += 2;
+	if (s == NULL) {
+		if (n->child == NULL || n->child->type != ROFFT_TEXT)
+			return;
+		s = n->child->string;
+		switch (s[0]) {
+		case '-':
+			s++;
+			break;
+		case '\\':
+			switch (s[1]) {
+			case '&':
+			case '-':
+			case 'e':
+				s += 2;
+				break;
+			default:
+				break;
+			}
+			break;
+		default:
+			break;
+		}
+	}
 
 	/*
 	 * Skip whitespace and escapes and whatever follows,
@@ -160,137 +127,201 @@ tag_put(const char *s, int prio, size_t line)
 		return;
 
 	se = s + len;
-	if (*se != '\0')
-		prio = INT_MAX;
+	if (*se != '\0' && prio < TAG_WEAK)
+		prio = TAG_WEAK;
 
 	slot = ohash_qlookupi(&tag_data, s, &se);
 	entry = ohash_find(&tag_data, slot);
 
-	if (entry == NULL) {
-
-		/* Build a new entry. */
+	/* Build a new entry. */
 
+	if (entry == NULL) {
 		entry = mandoc_malloc(sizeof(*entry) + len + 1);
 		memcpy(entry->s, s, len);
 		entry->s[len] = '\0';
-		entry->lines = NULL;
-		entry->maxlines = entry->nlines = 0;
+		entry->nodes = NULL;
+		entry->maxnodes = entry->nnodes = 0;
 		ohash_insert(&tag_data, slot, entry);
+	}
 
-	} else {
-
-		/*
-		 * Lower priority numbers take precedence,
-		 * but 0 is special.
-		 * A tag with priority 0 is only used
-		 * if the tag occurs exactly once.
-		 */
+	/*
+	 * Lower priority numbers take precedence.
+	 * If a better entry is already present, ignore the new one.
+	 */
 
-		if (prio == 0) {
-			if (entry->prio == 0)
-				entry->prio = -1;
+	else if (entry->prio < prio)
 			return;
-		}
 
-		/* A better entry is already present, ignore the new one. */
+	/*
+	 * If the existing entry is worse, clear it.
+	 * In addition, a tag with priority TAG_FALLBACK
+	 * is only used if the tag occurs exactly once.
+	 */
 
-		if (entry->prio > 0 && entry->prio < prio)
+	else if (entry->prio > prio || prio == TAG_FALLBACK) {
+		while (entry->nnodes > 0) {
+			nold = entry->nodes[--entry->nnodes];
+			nold->flags &= ~NODE_ID;
+			free(nold->tag);
+			nold->tag = NULL;
+		}
+		if (prio == TAG_FALLBACK) {
+			entry->prio = TAG_DELETE;
 			return;
-
-		/* The existing entry is worse, clear it. */
-
-		if (entry->prio < 1 || entry->prio > prio)
-			entry->nlines = 0;
+		}
 	}
 
-	/* Remember the new line. */
+	/* Remember the new node. */
 
-	if (entry->maxlines == entry->nlines) {
-		entry->maxlines += 4;
-		entry->lines = mandoc_reallocarray(entry->lines,
-		    entry->maxlines, sizeof(*entry->lines));
+	if (entry->maxnodes == entry->nnodes) {
+		entry->maxnodes += 4;
+		entry->nodes = mandoc_reallocarray(entry->nodes,
+		    entry->maxnodes, sizeof(*entry->nodes));
 	}
-	entry->lines[entry->nlines++] = line;
+	entry->nodes[entry->nnodes++] = n;
 	entry->prio = prio;
+	n->flags |= NODE_ID;
+	if (n->child == NULL || n->child->string != s || *se != '\0') {
+		assert(n->tag == NULL);
+		n->tag = mandoc_strndup(s, len);
+	}
+}
+
+int
+tag_exists(const char *tag)
+{
+	return ohash_find(&tag_data, ohash_qlookup(&tag_data, tag)) != NULL;
 }
 
 /*
- * Write out the tags file using the previously collected
- * information and clear the ohash table while going along.
+ * For in-line elements, move the link target
+ * to the enclosing paragraph when appropriate.
  */
-void
-tag_write(void)
+static void
+tag_move_id(struct roff_node *n)
 {
-	FILE			*stream;
-	struct tag_entry	*entry;
-	size_t			 i;
-	unsigned int		 slot;
-	int			 empty;
+	struct roff_node *np;
 
-	if (tag_files.tfd <= 0)
-		return;
-	if (tag_files.tagname != NULL && ohash_find(&tag_data,
-            ohash_qlookup(&tag_data, tag_files.tagname)) == NULL) {
-		mandoc_msg(MANDOCERR_TAG, 0, 0, "%s", tag_files.tagname);
-		tag_files.tagname = NULL;
-	}
-	if ((stream = fdopen(tag_files.tfd, "w")) == NULL)
-		mandoc_msg(MANDOCERR_FDOPEN, 0, 0, "%s", strerror(errno));
-	empty = 1;
-	entry = ohash_first(&tag_data, &slot);
-	while (entry != NULL) {
-		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]);
-				empty = 0;
+	np = n;
+	for (;;) {
+		if (np->prev != NULL)
+			np = np->prev;
+		else if ((np = np->parent) == NULL)
+			return;
+		switch (np->tok) {
+		case MDOC_It:
+			switch (np->parent->parent->norm->Bl.type) {
+			case LIST_column:
+				/* Target the ROFFT_BLOCK = . */
+				np = np->parent;
+				break;
+			case LIST_diag:
+			case LIST_hang:
+			case LIST_inset:
+			case LIST_ohang:
+			case LIST_tag:
+				/* Target the ROFFT_HEAD = 
. */ + np = np->parent->head; + break; + default: + /* Target the ROFF_BODY =
  • . */ + break; + } + /* FALLTHROUGH */ + case MDOC_Pp: /* Target the ROFFT_ELEM =

    . */ + if (np->tag == NULL) { + np->tag = mandoc_strdup(n->tag == NULL ? + n->child->string : n->tag); + np->flags |= NODE_ID; + n->flags &= ~NODE_ID; } + return; + case MDOC_Sh: + case MDOC_Ss: + case MDOC_Bd: + case MDOC_Bl: + case MDOC_D1: + case MDOC_Dl: + case MDOC_Rs: + /* Do not move past major blocks. */ + return; + default: + /* + * Move past in-line content and partial + * blocks, for example .It Xo or .It Bq Er. + */ + break; } - free(entry->lines); - free(entry); - entry = ohash_next(&tag_data, &slot); - } - ohash_delete(&tag_data); - if (stream != NULL) - fclose(stream); - else - close(tag_files.tfd); - tag_files.tfd = -1; - if (empty) { - unlink(tag_files.tfn); - *tag_files.tfn = '\0'; } } -void -tag_unlink(void) +/* + * When a paragraph is tagged and starts with text, + * move the permalink to the first few words. + */ +static void +tag_move_href(struct roff_man *man, struct roff_node *n, const char *tag) { - pid_t tc_pgid; - - if (tag_files.tcpgid != -1) { - tc_pgid = tcgetpgrp(tag_files.ofd); - if (tc_pgid == tag_files.pager_pid || - tc_pgid == getpgid(0) || - getpgid(tc_pgid) == -1) - (void)tcsetpgrp(tag_files.ofd, tag_files.tcpgid); + char *cp; + + if (n == NULL || n->type != ROFFT_TEXT || + *n->string == '\0' || *n->string == ' ') + return; + + cp = n->string; + while (cp != NULL && cp - n->string < 5) + cp = strchr(cp + 1, ' '); + + /* If the first text node is longer, split it. */ + + if (cp != NULL && cp[1] != '\0') { + man->last = n; + man->next = ROFF_NEXT_SIBLING; + roff_word_alloc(man, n->line, + n->pos + (cp - n->string), cp + 1); + man->last->flags = n->flags & ~NODE_LINE; + *cp = '\0'; } - if (*tag_files.ofn != '\0') - unlink(tag_files.ofn); - if (*tag_files.tfn != '\0') - unlink(tag_files.tfn); + + assert(n->tag == NULL); + n->tag = mandoc_strdup(tag); + n->flags |= NODE_HREF; } -static void -tag_signal(int signum) +/* + * When all tags have been set, decide where to put + * the associated permalinks, and maybe move some tags + * to the beginning of the respective paragraphs. + */ +void +tag_postprocess(struct roff_man *man, struct roff_node *n) { - struct sigaction sa; - - tag_unlink(); - memset(&sa, 0, sizeof(sa)); - sigemptyset(&sa.sa_mask); - sa.sa_handler = SIG_DFL; - sigaction(signum, &sa, NULL); - kill(getpid(), signum); - /* NOTREACHED */ - _exit(1); + if (n->flags & NODE_ID) { + switch (n->tok) { + case MDOC_Pp: + tag_move_href(man, n->next, n->tag); + break; + case MDOC_Bd: + case MDOC_D1: + case MDOC_Dl: + tag_move_href(man, n->child, n->tag); + break; + case MDOC_Bl: + /* XXX No permalink for now. */ + break; + default: + if (n->type == ROFFT_ELEM || n->tok == MDOC_Fo) + tag_move_id(n); + if (n->tok != MDOC_Tg) + n->flags |= NODE_HREF; + else if ((n->flags & NODE_ID) == 0) { + n->flags |= NODE_NOPRT; + free(n->tag); + n->tag = NULL; + } + break; + } + } + for (n = n->child; n != NULL; n = n->next) + tag_postprocess(man, n); } diff --git a/tag.h b/tag.h index bbd40b737e11..1eace6fd515e 100644 --- a/tag.h +++ b/tag.h @@ -1,6 +1,6 @@ -/* $Id: tag.h,v 1.8 2018/11/22 11:30:23 schwarze Exp $ */ +/* $Id: tag.h,v 1.14 2020/04/18 20:40:10 schwarze Exp $ */ /* - * Copyright (c) 2015 Ingo Schwarze + * Copyright (c) 2015, 2018, 2019, 2020 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,20 +13,23 @@ * 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 interfaces to tag syntax tree nodes. + * For use by mandoc(1) validation modules only. */ -struct tag_files { - char ofn[20]; - char tfn[20]; - char *tagname; - int ofd; - int tfd; - pid_t tcpgid; - pid_t pager_pid; -}; - +/* + * Tagging priorities. + * Lower numbers indicate higher importance. + */ +#define TAG_MANUAL 1 /* Set with a .Tg macro. */ +#define TAG_STRONG 2 /* Good automatic tagging. */ +#define TAG_WEAK (INT_MAX - 2) /* Dubious automatic tagging. */ +#define TAG_FALLBACK (INT_MAX - 1) /* Tag only used if unique. */ +#define TAG_DELETE (INT_MAX) /* Tag not used at all. */ -struct tag_files *tag_init(void); -void tag_put(const char *, int, size_t); -void tag_write(void); -void tag_unlink(void); +void tag_alloc(void); +int tag_exists(const char *); +void tag_put(const char *, int, struct roff_node *); +void tag_postprocess(struct roff_man *, struct roff_node *); +void tag_free(void); diff --git a/tbl.7 b/tbl.7 index e7953ea0a4e8..4ecc354c4ea3 100644 --- a/tbl.7 +++ b/tbl.7 @@ -1,4 +1,4 @@ -.\" $Id: tbl.7,v 1.34 2019/03/02 21:03:02 schwarze Exp $ +.\" $Id: tbl.7,v 1.37 2021/09/18 12:34:27 schwarze Exp $ .\" .\" Copyright (c) 2010, 2011 Kristaps Dzonsons .\" Copyright (c) 2014,2015,2017,2018,2019 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: March 2 2019 $ +.Dd $Mdocdate: September 18 2021 $ .Dt TBL 7 .Os .Sh NAME @@ -94,7 +94,7 @@ Allow page breaks within the table. This is a GNU extension and currently ignored. .It Cm nospaces Ignore leading and trailing spaces in data cells. -This is a GNU extension and currently ignored. +This is a GNU extension. .It Cm nowarn Suppress warnings about tables exceeding the current line length. This is a GNU extension and currently ignored. @@ -178,10 +178,11 @@ of any other column also having the .Cm e modifier. .It Cm f -The next character selects the font to use for this cell. +The next one or two characters select the font to use for this cell. +One-character font names must be followed by a blank or period. See the .Xr roff 7 -manual for supported one-character font names. +manual for supported font names. .It Cm i Use an italic font for the contents of this cell. .It Cm m @@ -416,7 +417,7 @@ equations inside tables. .Xr roff 7 .Rs .%A M. E. Lesk -.%T Tbl\(emA Program to Format Tables +.%T Tbl \(em A Program to Format Tables .%D June 11, 1976 .Re .Sh HISTORY diff --git a/tbl.h b/tbl.h index 365ae929edf4..0fccb440551c 100644 --- a/tbl.h +++ b/tbl.h @@ -1,7 +1,7 @@ -/* $Id: tbl.h,v 1.1 2018/12/12 21:54:35 schwarze Exp $ */ +/* $Id: tbl.h,v 1.2 2021/08/10 12:55:04 schwarze Exp $ */ /* * Copyright (c) 2010, 2011 Kristaps Dzonsons - * Copyright (c) 2014, 2015, 2017, 2018 Ingo Schwarze + * Copyright (c) 2014,2015,2017,2018,2021 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 @@ -57,14 +57,13 @@ struct tbl_cell { int vert; /* Width of subsequent vertical line. */ int col; /* Column number, starting from 0. */ int flags; -#define TBL_CELL_BOLD (1 << 0) /* b, B, fB */ -#define TBL_CELL_ITALIC (1 << 1) /* i, I, fI */ #define TBL_CELL_TALIGN (1 << 2) /* t, T */ #define TBL_CELL_UP (1 << 3) /* u, U */ #define TBL_CELL_BALIGN (1 << 4) /* d, D */ #define TBL_CELL_WIGN (1 << 5) /* z, Z */ #define TBL_CELL_EQUAL (1 << 6) /* e, E */ #define TBL_CELL_WMAX (1 << 7) /* x, X */ + enum mandoc_esc font; enum tbl_cellt pos; }; diff --git a/tbl_data.c b/tbl_data.c index 9a58d3256239..fe0259f9227a 100644 --- a/tbl_data.c +++ b/tbl_data.c @@ -1,7 +1,7 @@ -/* $Id: tbl_data.c,v 1.52 2019/02/09 16:00:39 schwarze Exp $ */ +/* $Id: tbl_data.c,v 1.59 2021/09/10 13:24:38 schwarze Exp $ */ /* * Copyright (c) 2009, 2010, 2011 Kristaps Dzonsons - * Copyright (c) 2011,2015,2017,2018,2019 Ingo Schwarze + * Copyright (c) 2011,2015,2017-2019,2021 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 @@ -21,6 +21,7 @@ #include #include +#include #include #include #include @@ -45,16 +46,20 @@ getdata(struct tbl_node *tbl, struct tbl_span *dp, struct tbl_dat *dat, *pdat; struct tbl_cell *cp; struct tbl_span *pdp; - int sv; + const char *ccp; + int startpos, endpos; /* * Determine the length of the string in the cell * and advance the parse point to the end of the cell. */ - sv = *pos; - while (p[*pos] != '\0' && p[*pos] != tbl->opts.tab) - (*pos)++; + startpos = *pos; + ccp = p + startpos; + while (*ccp != '\0' && *ccp != tbl->opts.tab) + if (*ccp++ == '\\') + mandoc_escape(&ccp, NULL, NULL); + *pos = ccp - p; /* Advance to the next layout cell, skipping spanners. */ @@ -73,12 +78,14 @@ getdata(struct tbl_node *tbl, struct tbl_span *dp, if (dp->layout->last->col + 1 < dp->opts->cols) { cp = mandoc_calloc(1, sizeof(*cp)); cp->pos = TBL_CELL_LEFT; + cp->font = ESCAPE_FONTROMAN; + cp->spacing = SIZE_MAX; dp->layout->last->next = cp; cp->col = dp->layout->last->col + 1; dp->layout->last = cp; } else { mandoc_msg(MANDOCERR_TBLDATA_EXTRA, - ln, sv, "%s", p + sv); + ln, startpos, "%s", p + startpos); while (p[*pos] != '\0') (*pos)++; return; @@ -103,7 +110,8 @@ getdata(struct tbl_node *tbl, struct tbl_span *dp, */ if (cp->pos == TBL_CELL_DOWN || - (*pos - sv == 2 && p[sv] == '\\' && p[sv + 1] == '^')) { + (*pos - startpos == 2 && + p[startpos] == '\\' && p[startpos + 1] == '^')) { pdp = dp; while ((pdp = pdp->prev) != NULL) { pdat = pdp->first; @@ -139,18 +147,29 @@ getdata(struct tbl_node *tbl, struct tbl_span *dp, dp->last->next = dat; dp->last = dat; + /* Strip leading and trailing spaces, if requested. */ + + endpos = *pos; + if (dp->opts->opts & TBL_OPT_NOSPACE) { + while (p[startpos] == ' ') + startpos++; + while (endpos > startpos && p[endpos - 1] == ' ') + endpos--; + } + /* * Check for a continued-data scope opening. This consists of a * trailing `T{' at the end of the line. Subsequent lines, * until a standalone `T}', are included in our cell. */ - if (*pos - sv == 2 && p[sv] == 'T' && p[sv + 1] == '{') { + if (endpos - startpos == 2 && + p[startpos] == 'T' && p[startpos + 1] == '{') { tbl->part = TBL_PART_CDATA; return; } - dat->string = mandoc_strndup(p + sv, *pos - sv); + dat->string = mandoc_strndup(p + startpos, endpos - startpos); if (p[*pos] != '\0') (*pos)++; @@ -171,7 +190,7 @@ getdata(struct tbl_node *tbl, struct tbl_span *dp, dat->layout->pos == TBL_CELL_DOWN) && dat->pos == TBL_DATA_DATA && *dat->string != '\0') mandoc_msg(MANDOCERR_TBLDATA_SPAN, - ln, sv, "%s", dat->string); + ln, startpos, "%s", dat->string); } void @@ -184,6 +203,9 @@ tbl_cdata(struct tbl_node *tbl, int ln, const char *p, int pos) if (p[pos] == 'T' && p[pos + 1] == '}') { pos += 2; + if (tbl->opts.opts & TBL_OPT_NOSPACE) + while (p[pos] == ' ') + pos++; if (p[pos] == tbl->opts.tab) { tbl->part = TBL_PART_DATA; pos++; @@ -242,10 +264,11 @@ tbl_data(struct tbl_node *tbl, int ln, const char *p, int pos) struct tbl_cell *cp; struct tbl_span *sp; - rp = (sp = tbl->last_span) == NULL ? tbl->first_row : - sp->pos == TBL_SPAN_DATA && sp->layout->next != NULL ? - sp->layout->next : sp->layout; - + for (sp = tbl->last_span; sp != NULL; sp = sp->prev) + if (sp->pos == TBL_SPAN_DATA) + break; + rp = sp == NULL ? tbl->first_row : + sp->layout->next == NULL ? sp->layout : sp->layout->next; assert(rp != NULL); if (p[1] == '\0') { diff --git a/tbl_html.c b/tbl_html.c index e137757dc16c..65c8ae8f887c 100644 --- a/tbl_html.c +++ b/tbl_html.c @@ -1,7 +1,7 @@ -/* $Id: tbl_html.c,v 1.33 2019/03/17 18:21:45 schwarze Exp $ */ +/* $Id: tbl_html.c,v 1.38 2021/09/09 16:52:52 schwarze Exp $ */ /* * Copyright (c) 2011 Kristaps Dzonsons - * Copyright (c) 2014, 2015, 2017, 2018 Ingo Schwarze + * Copyright (c) 2014,2015,2017,2018,2021 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 @@ -115,10 +115,14 @@ print_tbl(struct html *h, const struct tbl_span *sp) const struct tbl_dat *dp; const struct tbl_cell *cp; const struct tbl_span *psp; + const struct roffcol *col; struct tag *tt; const char *hspans, *vspans, *halign, *valign; const char *bborder, *lborder, *rborder; + const char *ccp; char hbuf[4], vbuf[4]; + size_t sz; + enum mandoc_esc save_font; int i; if (h->tblt == NULL) @@ -240,8 +244,40 @@ print_tbl(struct html *h, const struct tbl_span *sp) "vertical-align", valign, "text-align", halign, "border-right-style", rborder); - if (dp->string != NULL) + if (dp->layout->pos == TBL_CELL_HORIZ || + dp->layout->pos == TBL_CELL_DHORIZ || + dp->pos == TBL_DATA_HORIZ || + dp->pos == TBL_DATA_DHORIZ) + print_otag(h, TAG_HR, ""); + else if (dp->string != NULL) { + save_font = h->metac; + html_setfont(h, dp->layout->font); + if (dp->layout->pos == TBL_CELL_LONG) + print_text(h, "\\[u2003]"); /* em space */ print_text(h, dp->string); + if (dp->layout->pos == TBL_CELL_NUMBER) { + col = h->tbl.cols + dp->layout->col; + if (col->decimal < col->nwidth) { + if ((ccp = strrchr(dp->string, + sp->opts->decimal)) == NULL) { + /* Punctuation space. */ + print_text(h, "\\[u2008]"); + ccp = strchr(dp->string, '\0'); + } else + ccp++; + sz = col->nwidth - col->decimal; + while (--sz > 0) { + if (*ccp == '\0') + /* Figure space. */ + print_text(h, + "\\[u2007]"); + else + ccp++; + } + } + } + html_setfont(h, save_font); + } } print_tagq(h, tt); diff --git a/tbl_layout.c b/tbl_layout.c index 58599705c18c..171a8dbb1b49 100644 --- a/tbl_layout.c +++ b/tbl_layout.c @@ -1,7 +1,8 @@ -/* $Id: tbl_layout.c,v 1.48 2018/12/14 05:18:03 schwarze Exp $ */ +/* $Id: tbl_layout.c,v 1.50 2021/08/10 12:55:04 schwarze Exp $ */ /* * Copyright (c) 2009, 2010, 2011 Kristaps Dzonsons - * Copyright (c) 2012, 2014, 2015, 2017 Ingo Schwarze + * Copyright (c) 2012, 2014, 2015, 2017, 2020, 2021 + * 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,7 +66,10 @@ mods(struct tbl_node *tbl, struct tbl_cell *cp, int ln, const char *p, int *pos) { char *endptr; + unsigned long spacing; size_t sz; + int isz; + enum mandoc_esc fontesc; mod: while (p[*pos] == ' ' || p[*pos] == '\t') @@ -93,14 +97,18 @@ mod: /* Parse numerical spacing from modifier string. */ if (isdigit((unsigned char)p[*pos])) { - cp->spacing = strtoull(p + *pos, &endptr, 10); + if ((spacing = strtoul(p + *pos, &endptr, 10)) > 9) + mandoc_msg(MANDOCERR_TBLLAYOUT_SPC, ln, *pos, + "%lu", spacing); + else + cp->spacing = spacing; *pos = endptr - p; goto mod; } switch (tolower((unsigned char)p[(*pos)++])) { case 'b': - cp->flags |= TBL_CELL_BOLD; + cp->font = ESCAPE_FONTBOLD; goto mod; case 'd': cp->flags |= TBL_CELL_BALIGN; @@ -111,7 +119,7 @@ mod: case 'f': break; case 'i': - cp->flags |= TBL_CELL_ITALIC; + cp->font = ESCAPE_FONTITALIC; goto mod; case 'm': mandoc_msg(MANDOCERR_TBLLAYOUT_MOD, ln, *pos, "m"); @@ -165,40 +173,34 @@ mod: goto mod; } + while (p[*pos] == ' ' || p[*pos] == '\t') + (*pos)++; + /* Ignore parenthised font names for now. */ if (p[*pos] == '(') goto mod; - /* Support only one-character font-names for now. */ - - if (p[*pos] == '\0' || (p[*pos + 1] != ' ' && p[*pos + 1] != '.')) { + isz = 0; + if (p[*pos] != '\0') + isz++; + if (strchr(" \t.", p[*pos + isz]) == NULL) + isz++; + + fontesc = mandoc_font(p + *pos, isz); + + switch (fontesc) { + case ESCAPE_FONTPREV: + case ESCAPE_ERROR: mandoc_msg(MANDOCERR_FT_BAD, ln, *pos, "TS %s", p + *pos - 1); - if (p[*pos] != '\0') - (*pos)++; - if (p[*pos] != '\0') - (*pos)++; - goto mod; - } - - switch (p[(*pos)++]) { - case '3': - case 'B': - cp->flags |= TBL_CELL_BOLD; - goto mod; - case '2': - case 'I': - cp->flags |= TBL_CELL_ITALIC; - goto mod; - case '1': - case 'R': - goto mod; + break; default: - mandoc_msg(MANDOCERR_FT_BAD, - ln, *pos - 1, "TS f%c", p[*pos - 1]); - goto mod; + cp->font = fontesc; + break; } + *pos += isz; + goto mod; } static void @@ -357,6 +359,7 @@ cell_alloc(struct tbl_node *tbl, struct tbl_row *rp, enum tbl_cellt pos) p = mandoc_calloc(1, sizeof(*p)); p->spacing = SIZE_MAX; + p->font = ESCAPE_FONTROMAN; p->pos = pos; if ((pp = rp->last) != NULL) { diff --git a/tbl_term.c b/tbl_term.c index de88d61c15e1..eac125586c4d 100644 --- a/tbl_term.c +++ b/tbl_term.c @@ -1,7 +1,7 @@ -/* $Id: tbl_term.c,v 1.72 2019/07/01 22:56:24 schwarze Exp $ */ +/* $Id: tbl_term.c,v 1.75 2021/08/10 12:55:04 schwarze Exp $ */ /* * Copyright (c) 2009, 2011 Kristaps Dzonsons - * Copyright (c) 2011-2019 Ingo Schwarze + * Copyright (c) 2011-2021 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 @@ -190,17 +190,6 @@ term_tbl(struct termp *tp, const struct tbl_span *sp) tblcalc(&tp->tbl, sp, tp->tcol->offset, tp->tcol->rmargin); - /* Tables leak .ta settings to subsequent text. */ - - term_tab_set(tp, NULL); - coloff = sp->opts->opts & (TBL_OPT_BOX | TBL_OPT_DBOX) || - sp->opts->lvert; - for (ic = 0; ic < sp->opts->cols; ic++) { - coloff += tp->tbl.cols[ic].width; - term_tab_iset(coloff); - coloff += tp->tbl.cols[ic].spacing; - } - /* Center the table as a whole. */ offset = tp->tcol->offset; @@ -267,11 +256,11 @@ term_tbl(struct termp *tp, const struct tbl_span *sp) hspans--; continue; } - if (dp == NULL) - continue; - hspans = dp->hspans; - if (ic || sp->layout->first->pos != TBL_CELL_SPAN) + if (dp != NULL && + (ic || sp->layout->first->pos != TBL_CELL_SPAN)) { + hspans = dp->hspans; dp = dp->next; + } } /* Set up a column for a right vertical frame. */ @@ -302,11 +291,11 @@ term_tbl(struct termp *tp, const struct tbl_span *sp) tp->tcol++; tp->col = 0; tbl_data(tp, sp->opts, cp, dp, tp->tbl.cols + ic); - if (dp == NULL) - continue; - hspans = dp->hspans; - if (cp->pos != TBL_CELL_SPAN) + if (dp != NULL && + (ic || sp->layout->first->pos != TBL_CELL_SPAN)) { + hspans = dp->hspans; dp = dp->next; + } } break; } @@ -425,11 +414,10 @@ term_tbl(struct termp *tp, const struct tbl_span *sp) cp = cp->next; continue; } - if (dp != NULL) { + if (dp != NULL && (ic || + sp->layout->first->pos != TBL_CELL_SPAN)) { hspans = dp->hspans; - if (ic || sp->layout->first->pos - != TBL_CELL_SPAN) - dp = dp->next; + dp = dp->next; } /* @@ -935,10 +923,24 @@ tbl_word(struct termp *tp, const struct tbl_dat *dp) int prev_font; prev_font = tp->fonti; - if (dp->layout->flags & TBL_CELL_BOLD) - term_fontpush(tp, TERMFONT_BOLD); - else if (dp->layout->flags & TBL_CELL_ITALIC) - term_fontpush(tp, TERMFONT_UNDER); + switch (dp->layout->font) { + case ESCAPE_FONTBI: + term_fontpush(tp, TERMFONT_BI); + break; + case ESCAPE_FONTBOLD: + case ESCAPE_FONTCB: + term_fontpush(tp, TERMFONT_BOLD); + break; + case ESCAPE_FONTITALIC: + case ESCAPE_FONTCI: + term_fontpush(tp, TERMFONT_UNDER); + break; + case ESCAPE_FONTROMAN: + case ESCAPE_FONTCR: + break; + default: + abort(); + } term_word(tp, dp->string); diff --git a/term.c b/term.c index 3b9277aaabbc..fb0351d91f1d 100644 --- a/term.c +++ b/term.c @@ -1,7 +1,7 @@ -/* $Id: term.c,v 1.281 2019/06/03 20:23:41 schwarze Exp $ */ +/* $Id: term.c,v 1.283 2021/08/10 12:55:04 schwarze Exp $ */ /* * Copyright (c) 2008, 2009, 2010, 2011 Kristaps Dzonsons - * Copyright (c) 2010-2019 Ingo Schwarze + * Copyright (c) 2010-2020 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,8 +38,7 @@ static void bufferc(struct termp *, char); static void encode(struct termp *, const char *, size_t); static void encode1(struct termp *, int); static void endline(struct termp *); -static void term_field(struct termp *, size_t, size_t, - size_t, size_t); +static void term_field(struct termp *, size_t, size_t); static void term_fill(struct termp *, size_t *, size_t *, size_t); @@ -127,8 +126,7 @@ term_flushln(struct termp *p) * and with the BRNEVER flag, never break it at all. */ - vtarget = p->flags & TERMP_BRNEVER ? SIZE_MAX : - (p->flags & TERMP_NOBREAK) == 0 ? vfield : + vtarget = (p->flags & TERMP_NOBREAK) == 0 ? vfield : p->maxrmargin > p->viscol + vbl ? p->maxrmargin - p->viscol - vbl : 0; @@ -137,7 +135,8 @@ term_flushln(struct termp *p) * If there is whitespace only, print nothing. */ - term_fill(p, &nbr, &vbr, vtarget); + term_fill(p, &nbr, &vbr, + p->flags & TERMP_BRNEVER ? SIZE_MAX : vtarget); if (nbr == 0) break; @@ -156,7 +155,7 @@ term_flushln(struct termp *p) /* Finally, print the field content. */ - term_field(p, vbl, nbr, vbr, vtarget); + term_field(p, vbl, nbr); /* * If there is no text left in the field, exit the loop. @@ -345,12 +344,10 @@ term_fill(struct termp *p, size_t *nbr, size_t *vbr, size_t vtarget) /* * Print the contents of one field * with an indentation of vbl visual columns, - * an input string length of nbr characters, - * an output width of vbr visual columns, - * and a desired field width of vtarget visual columns. + * and an input string length of nbr characters. */ static void -term_field(struct termp *p, size_t vbl, size_t nbr, size_t vbr, size_t vtarget) +term_field(struct termp *p, size_t vbl, size_t nbr) { size_t ic; /* Character position in the input buffer. */ size_t vis; /* Visual position of the current character. */ @@ -592,16 +589,18 @@ term_word(struct termp *p, const char *word) uc = *seq; break; case ESCAPE_FONTBOLD: + case ESCAPE_FONTCB: term_fontrepl(p, TERMFONT_BOLD); continue; case ESCAPE_FONTITALIC: + case ESCAPE_FONTCI: term_fontrepl(p, TERMFONT_UNDER); continue; case ESCAPE_FONTBI: term_fontrepl(p, TERMFONT_BI); continue; case ESCAPE_FONT: - case ESCAPE_FONTCW: + case ESCAPE_FONTCR: case ESCAPE_FONTROMAN: term_fontrepl(p, TERMFONT_NONE); continue; diff --git a/term_ascii.c b/term_ascii.c index 368623cac105..bf7e9b639e04 100644 --- a/term_ascii.c +++ b/term_ascii.c @@ -1,7 +1,7 @@ -/* $Id: term_ascii.c,v 1.64 2018/11/28 14:23:06 schwarze Exp $ */ +/* $Id: term_ascii.c,v 1.66 2020/09/09 13:45:05 schwarze Exp $ */ /* * Copyright (c) 2010, 2011 Kristaps Dzonsons - * Copyright (c) 2014, 2015, 2017, 2018 Ingo Schwarze + * Copyright (c) 2014,2015,2017,2018,2020 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 @@ -232,7 +232,10 @@ ascii_endline(struct termp *p) { p->line++; - p->tcol->offset -= p->ti; + if ((int)p->tcol->offset > p->ti) + p->tcol->offset -= p->ti; + else + p->tcol->offset = 0; p->ti = 0; putchar('\n'); } @@ -242,7 +245,14 @@ ascii_advance(struct termp *p, size_t len) { size_t i; - assert(len < UINT16_MAX); + /* + * XXX We used to have "assert(len < UINT16_MAX)" here. + * that is not quite right because the input document + * can trigger that by merely providing large input. + * For now, simply truncate. + */ + if (len > 256) + len = 256; for (i = 0; i < len; i++) putchar(' '); } @@ -380,7 +390,14 @@ locale_advance(struct termp *p, size_t len) { size_t i; - assert(len < UINT16_MAX); + /* + * XXX We used to have "assert(len < UINT16_MAX)" here. + * that is not quite right because the input document + * can trigger that by merely providing large input. + * For now, simply truncate. + */ + if (len > 256) + len = 256; for (i = 0; i < len; i++) putwchar(L' '); } @@ -390,7 +407,10 @@ locale_endline(struct termp *p) { p->line++; - p->tcol->offset -= p->ti; + if ((int)p->tcol->offset > p->ti) + p->tcol->offset -= p->ti; + else + p->tcol->offset = 0; p->ti = 0; putwchar(L'\n'); } diff --git a/term_ps.c b/term_ps.c index 2cd94c923156..374d3d9a6abd 100644 --- a/term_ps.c +++ b/term_ps.c @@ -1,7 +1,7 @@ -/* $Id: term_ps.c,v 1.91 2017/11/10 23:42:52 schwarze Exp $ */ +/* $Id: term_ps.c,v 1.92 2020/09/06 14:45:22 schwarze Exp $ */ /* * Copyright (c) 2010, 2011 Kristaps Dzonsons - * Copyright (c) 2014, 2015, 2016, 2017 Ingo Schwarze + * Copyright (c) 2014,2015,2016,2017,2020 Ingo Schwarze * Copyright (c) 2017 Marc Espie * * Permission to use, copy, modify, and distribute this software for any @@ -1252,7 +1252,10 @@ ps_endline(struct termp *p) ps_closepage(p); - p->tcol->offset -= p->ti; + if ((int)p->tcol->offset > p->ti) + p->tcol->offset -= p->ti; + else + p->tcol->offset = 0; p->ti = 0; } diff --git a/term_tab.c b/term_tab.c index 3343244f3c8f..84b4c00c6e65 100644 --- a/term_tab.c +++ b/term_tab.c @@ -1,4 +1,4 @@ -/* $Id: term_tab.c,v 1.5 2018/12/16 00:21:05 schwarze Exp $ */ +/* $Id: term_tab.c,v 1.6 2020/06/22 19:20:40 schwarze Exp $ */ /* * Copyright (c) 2017 Ingo Schwarze * @@ -14,6 +14,8 @@ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ +#include "config.h" + #include #include diff --git a/term_tag.c b/term_tag.c new file mode 100644 index 000000000000..c26b942531fc --- /dev/null +++ b/term_tag.c @@ -0,0 +1,227 @@ +/* $Id: term_tag.c,v 1.6 2021/03/30 17:16:55 schwarze Exp $ */ +/* + * Copyright (c) 2015,2016,2018,2019,2020 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. + * + * Functions to write a ctags(1) file. + * For use by the mandoc(1) ASCII and UTF-8 formatters only. + */ +#include "config.h" + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "mandoc.h" +#include "roff.h" +#include "roff_int.h" +#include "tag.h" +#include "term_tag.h" + +static void tag_signal(int) __attribute__((__noreturn__)); + +static struct tag_files tag_files; + + +/* + * Prepare for using a pager. + * Not all pagers are capable of using a tag file, + * but for simplicity, create it anyway. + */ +struct tag_files * +term_tag_init(const char *outfilename, const char *suffix, + const char *tagfilename) +{ + struct sigaction sa; + int ofd; /* In /tmp/, dup(2)ed to stdout. */ + int tfd; + + ofd = tfd = -1; + tag_files.tfs = NULL; + tag_files.tcpgid = -1; + + /* Clean up when dying from a signal. */ + + memset(&sa, 0, sizeof(sa)); + sigfillset(&sa.sa_mask); + sa.sa_handler = tag_signal; + sigaction(SIGHUP, &sa, NULL); + sigaction(SIGINT, &sa, NULL); + sigaction(SIGTERM, &sa, NULL); + + /* + * POSIX requires that a process calling tcsetpgrp(3) + * from the background gets a SIGTTOU signal. + * In that case, do not stop. + */ + + sa.sa_handler = SIG_IGN; + sigaction(SIGTTOU, &sa, NULL); + + /* Save the original standard output for use by the pager. */ + + if ((tag_files.ofd = dup(STDOUT_FILENO)) == -1) { + mandoc_msg(MANDOCERR_DUP, 0, 0, "%s", strerror(errno)); + goto fail; + } + + /* Create both temporary output files. */ + + if (outfilename == NULL) { + (void)snprintf(tag_files.ofn, sizeof(tag_files.ofn), + "/tmp/man.XXXXXXXXXX%s", suffix); + if ((ofd = mkstemps(tag_files.ofn, strlen(suffix))) == -1) { + mandoc_msg(MANDOCERR_MKSTEMP, 0, 0, + "%s: %s", tag_files.ofn, strerror(errno)); + goto fail; + } + } else { + (void)strlcpy(tag_files.ofn, outfilename, + sizeof(tag_files.ofn)); + unlink(outfilename); + ofd = open(outfilename, O_WRONLY | O_CREAT | O_EXCL, 0644); + if (ofd == -1) { + mandoc_msg(MANDOCERR_OPEN, 0, 0, + "%s: %s", outfilename, strerror(errno)); + goto fail; + } + } + if (tagfilename == NULL) { + (void)strlcpy(tag_files.tfn, "/tmp/man.XXXXXXXXXX", + sizeof(tag_files.tfn)); + if ((tfd = mkstemp(tag_files.tfn)) == -1) { + mandoc_msg(MANDOCERR_MKSTEMP, 0, 0, + "%s: %s", tag_files.tfn, strerror(errno)); + goto fail; + } + } else { + (void)strlcpy(tag_files.tfn, tagfilename, + sizeof(tag_files.tfn)); + unlink(tagfilename); + tfd = open(tagfilename, O_WRONLY | O_CREAT | O_EXCL, 0644); + if (tfd == -1) { + mandoc_msg(MANDOCERR_OPEN, 0, 0, + "%s: %s", tagfilename, strerror(errno)); + goto fail; + } + } + if ((tag_files.tfs = fdopen(tfd, "w")) == NULL) { + mandoc_msg(MANDOCERR_FDOPEN, 0, 0, "%s", strerror(errno)); + goto fail; + } + tfd = -1; + if (dup2(ofd, STDOUT_FILENO) == -1) { + mandoc_msg(MANDOCERR_DUP, 0, 0, "%s", strerror(errno)); + goto fail; + } + close(ofd); + return &tag_files; + +fail: + term_tag_unlink(); + if (ofd != -1) + close(ofd); + if (tfd != -1) + close(tfd); + if (tag_files.ofd != -1) { + close(tag_files.ofd); + tag_files.ofd = -1; + } + return NULL; +} + +void +term_tag_write(struct roff_node *n, size_t line) +{ + const char *cp; + int len; + + if (tag_files.tfs == NULL) + return; + cp = n->tag == NULL ? n->child->string : n->tag; + if (cp[0] == '\\' && (cp[1] == '&' || cp[1] == 'e')) + cp += 2; + len = strcspn(cp, " \t\\"); + fprintf(tag_files.tfs, "%.*s %s %zu\n", + len, cp, tag_files.ofn, line); +} + +/* + * Close both output files and restore the original standard output + * to the terminal. In the unlikely case that the latter fails, + * trying to start a pager would be useless, so report the failure + * to the main program. + */ +int +term_tag_close(void) +{ + int irc = 0; + + if (tag_files.tfs != NULL) { + fclose(tag_files.tfs); + tag_files.tfs = NULL; + } + if (tag_files.ofd != -1) { + fflush(stdout); + if ((irc = dup2(tag_files.ofd, STDOUT_FILENO)) == -1) + mandoc_msg(MANDOCERR_DUP, 0, 0, "%s", strerror(errno)); + close(tag_files.ofd); + tag_files.ofd = -1; + } + return irc; +} + +void +term_tag_unlink(void) +{ + pid_t tc_pgid; + + if (tag_files.tcpgid != -1) { + tc_pgid = tcgetpgrp(STDOUT_FILENO); + if (tc_pgid == tag_files.pager_pid || + tc_pgid == getpgid(0) || + getpgid(tc_pgid) == -1) + (void)tcsetpgrp(STDOUT_FILENO, tag_files.tcpgid); + } + if (strncmp(tag_files.ofn, "/tmp/man.", 9) == 0) { + unlink(tag_files.ofn); + *tag_files.ofn = '\0'; + } + if (strncmp(tag_files.tfn, "/tmp/man.", 9) == 0) { + unlink(tag_files.tfn); + *tag_files.tfn = '\0'; + } +} + +static void +tag_signal(int signum) +{ + struct sigaction sa; + + term_tag_unlink(); + memset(&sa, 0, sizeof(sa)); + sigemptyset(&sa.sa_mask); + sa.sa_handler = SIG_DFL; + sigaction(signum, &sa, NULL); + kill(getpid(), signum); + /* NOTREACHED */ + _exit(1); +} diff --git a/term_tag.h b/term_tag.h new file mode 100644 index 000000000000..f82b1a6e9223 --- /dev/null +++ b/term_tag.h @@ -0,0 +1,34 @@ +/* $Id: term_tag.h,v 1.4 2021/03/30 17:16:55 schwarze Exp $ */ +/* + * Copyright (c) 2015, 2018, 2019, 2020 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 interfaces to write a ctags(1) file. + * For use by the mandoc(1) ASCII and UTF-8 formatters only. + */ + +struct tag_files { + char ofn[80]; /* Output file name. */ + char tfn[80]; /* Tag file name. */ + FILE *tfs; /* Tag file object. */ + int ofd; /* Original output file descriptor. */ + pid_t tcpgid; /* Process group controlling the terminal. */ + pid_t pager_pid; /* Process ID of the pager. */ +}; + + +struct tag_files *term_tag_init(const char *, const char *, const char *); +void term_tag_write(struct roff_node *, size_t); +int term_tag_close(void); +void term_tag_unlink(void); diff --git a/test-attribute.c b/test-attribute.c new file mode 100644 index 000000000000..adbcc9268252 --- /dev/null +++ b/test-attribute.c @@ -0,0 +1,48 @@ +/* $Id: test-attribute.c,v 1.1 2020/06/22 20:00:38 schwarze Exp $ */ +/* + * Copyright (c) 2020 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 + +void var_arg(const char *, ...) + __attribute__((__format__ (__printf__, 1, 2))); +void no_ret(int) + __attribute__((__noreturn__)); + +void +var_arg(const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + vprintf(fmt, ap); + va_end(ap); +} + +void +no_ret(int i) +{ + exit(i); +} + +int +main(void) +{ + var_arg("Test output: %d\n", 42); + no_ret(0); +} diff --git a/test-mkstemps.c b/test-mkstemps.c new file mode 100644 index 000000000000..31460dcae428 --- /dev/null +++ b/test-mkstemps.c @@ -0,0 +1,12 @@ +#include +#include + +int +main(void) +{ + char filename[] = "/tmp/temp.XXXXXX.suffix"; + + if (mkstemps(filename, 7) == -1) + return 1; + return unlink(filename) == -1; +} diff --git a/tree.c b/tree.c index 649c0804c8b9..12e841a50b78 100644 --- a/tree.c +++ b/tree.c @@ -1,7 +1,7 @@ -/* $Id: tree.c,v 1.84 2019/01/01 05:56:34 schwarze Exp $ */ +/* $Id: tree.c,v 1.91 2021/09/07 10:59:18 schwarze Exp $ */ /* * Copyright (c) 2008, 2009, 2011, 2014 Kristaps Dzonsons - * Copyright (c) 2013-2015, 2017-2019 Ingo Schwarze + * Copyright (c) 2013-2015, 2017-2021 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,6 +14,9 @@ * 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. + * + * Formatting module to let mandoc(1) show + * a human readable representation of the syntax tree. */ #include "config.h" @@ -33,7 +36,9 @@ #include "eqn.h" #include "main.h" +static void print_attr(const struct roff_node *); static void print_box(const struct eqn_box *, int); +static void print_cellt(enum tbl_cellt); 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); @@ -187,28 +192,8 @@ print_mdoc(const struct roff_node *n, int indent) if (argv[i].sz > 0) printf(" ]"); } - - putchar(' '); - if (n->flags & NODE_DELIMO) - putchar('('); - if (n->flags & NODE_LINE) - putchar('*'); - printf("%d:%d", n->line, n->pos + 1); - if (n->flags & NODE_DELIMC) - putchar(')'); - if (n->flags & NODE_EOS) - putchar('.'); - if (n->flags & NODE_BROKEN) - printf(" BROKEN"); - if (n->flags & NODE_NOFILL) - printf(" NOFILL"); - if (n->flags & NODE_NOSRC) - printf(" NOSRC"); - if (n->flags & NODE_NOPRT) - printf(" NOPRT"); - putchar('\n'); + print_attr(n); } - if (n->eqn) print_box(n->eqn->first, indent + 4); if (n->child) @@ -289,19 +274,9 @@ print_man(const struct roff_node *n, int indent) } else { for (i = 0; i < indent; i++) putchar(' '); - printf("%s (%s) ", p, t); - if (n->flags & NODE_LINE) - putchar('*'); - printf("%d:%d", n->line, n->pos + 1); - if (n->flags & NODE_DELIMC) - putchar(')'); - if (n->flags & NODE_EOS) - putchar('.'); - if (n->flags & NODE_NOFILL) - printf(" NOFILL"); - putchar('\n'); + printf("%s (%s)", p, t); + print_attr(n); } - if (n->eqn) print_box(n->eqn->first, indent + 4); if (n->child) @@ -311,6 +286,40 @@ print_man(const struct roff_node *n, int indent) print_man(n->next, indent); } +static void +print_attr(const struct roff_node *n) +{ + putchar(' '); + if (n->flags & NODE_DELIMO) + putchar('('); + if (n->flags & NODE_LINE) + putchar('*'); + printf("%d:%d", n->line, n->pos + 1); + if (n->flags & NODE_DELIMC) + putchar(')'); + if (n->flags & NODE_EOS) + putchar('.'); + if (n->flags & NODE_ID) { + printf(" ID"); + if (n->flags & NODE_HREF) + printf("=HREF"); + } else if (n->flags & NODE_HREF) + printf(" HREF"); + else if (n->tag != NULL) + printf(" STRAYTAG"); + if (n->tag != NULL) + printf("=%s", n->tag); + if (n->flags & NODE_BROKEN) + printf(" BROKEN"); + if (n->flags & NODE_NOFILL) + printf(" NOFILL"); + if (n->flags & NODE_NOSRC) + printf(" NOSRC"); + if (n->flags & NODE_NOPRT) + printf(" NOPRT"); + putchar('\n'); +} + static void print_box(const struct eqn_box *ep, int indent) { @@ -373,12 +382,73 @@ print_box(const struct eqn_box *ep, int indent) print_box(ep->next, indent); } +static void +print_cellt(enum tbl_cellt pos) +{ + switch(pos) { + case TBL_CELL_LEFT: + putchar('L'); + break; + case TBL_CELL_LONG: + putchar('a'); + break; + case TBL_CELL_CENTRE: + putchar('c'); + break; + case TBL_CELL_RIGHT: + putchar('r'); + break; + case TBL_CELL_NUMBER: + putchar('n'); + break; + case TBL_CELL_SPAN: + putchar('s'); + break; + case TBL_CELL_DOWN: + putchar('^'); + break; + case TBL_CELL_HORIZ: + putchar('-'); + break; + case TBL_CELL_DHORIZ: + putchar('='); + break; + case TBL_CELL_MAX: + putchar('#'); + break; + } +} + static void print_span(const struct tbl_span *sp, int indent) { const struct tbl_dat *dp; + const struct tbl_cell *cp; int i; + if (sp->prev == NULL) { + for (i = 0; i < indent; i++) + putchar(' '); + printf("%d", sp->opts->cols); + if (sp->opts->opts & TBL_OPT_CENTRE) + fputs(" center", stdout); + if (sp->opts->opts & TBL_OPT_EXPAND) + fputs(" expand", stdout); + if (sp->opts->opts & TBL_OPT_ALLBOX) + fputs(" allbox", stdout); + if (sp->opts->opts & TBL_OPT_BOX) + fputs(" box", stdout); + if (sp->opts->opts & TBL_OPT_DBOX) + fputs(" doublebox", stdout); + if (sp->opts->opts & TBL_OPT_NOKEEP) + fputs(" nokeep", stdout); + if (sp->opts->opts & TBL_OPT_NOSPACE) + fputs(" nospaces", stdout); + if (sp->opts->opts & TBL_OPT_NOWARN) + fputs(" nowarn", stdout); + printf(" (tbl options) %d:1\n", sp->line); + } + for (i = 0; i < indent; i++) putchar(' '); @@ -392,31 +462,72 @@ print_span(const struct tbl_span *sp, int indent) putchar(' '); break; default: + for (cp = sp->layout->first; cp != NULL; cp = cp->next) + print_cellt(cp->pos); + putchar(' '); for (dp = sp->first; dp; dp = dp->next) { + if ((cp = dp->layout) == NULL) + putchar('*'); + else { + printf("%d", cp->col); + print_cellt(dp->layout->pos); + switch (cp->font) { + case ESCAPE_FONTROMAN: + break; + case ESCAPE_FONTBOLD: + putchar('b'); + break; + case ESCAPE_FONTITALIC: + putchar('i'); + break; + case ESCAPE_FONTBI: + fputs("bi", stdout); + break; + case ESCAPE_FONTCR: + putchar('c'); + break; + case ESCAPE_FONTCB: + fputs("cb", stdout); + break; + case ESCAPE_FONTCI: + fputs("ci", stdout); + break; + default: + abort(); + } + if (cp->flags & TBL_CELL_TALIGN) + putchar('t'); + if (cp->flags & TBL_CELL_UP) + putchar('u'); + if (cp->flags & TBL_CELL_BALIGN) + putchar('d'); + if (cp->flags & TBL_CELL_WIGN) + putchar('z'); + if (cp->flags & TBL_CELL_EQUAL) + putchar('e'); + if (cp->flags & TBL_CELL_WMAX) + putchar('x'); + } switch (dp->pos) { case TBL_DATA_HORIZ: case TBL_DATA_NHORIZ: putchar('-'); - putchar(' '); - continue; + break; case TBL_DATA_DHORIZ: case TBL_DATA_NDHORIZ: putchar('='); - putchar(' '); - continue; + break; default: + putchar(dp->block ? '{' : '['); + if (dp->string != NULL) + fputs(dp->string, stdout); + putchar(dp->block ? '}' : ']'); break; } - printf("[\"%s\"", dp->string ? dp->string : ""); if (dp->hspans) printf(">%d", dp->hspans); if (dp->vspans) printf("v%d", dp->vspans); - if (dp->layout == NULL) - putchar('*'); - else if (dp->layout->pos == TBL_CELL_DOWN) - putchar('^'); - putchar(']'); putchar(' '); } break; -- cgit v1.2.3