5025 import and use mandoc
Reviewed by: Hans Rosenfeld <hans.rosenfeld@nexenta.com>
Reviewed by: Igor Kozhukhov <ikozhukhov@gmail.com>
Reviewed by: Robert Mustacchi <rm@joyent.com>
Reviewed by: Albert Lee <trisk@nexenta.com>
Approved by: Dan McDonald <danmcd@omniti.com>
diff --git a/exception_lists/cstyle b/exception_lists/cstyle
index 1c17c99..c2c58b0 100644
--- a/exception_lists/cstyle
+++ b/exception_lists/cstyle
@@ -61,6 +61,59 @@
 usr/src/cmd/krb5/slave/kprop.c
 usr/src/cmd/krb5/slave/kprop.h
 usr/src/cmd/krb5/slave/kpropd.c
+usr/src/cmd/mandoc/arch.c
+usr/src/cmd/mandoc/att.c
+usr/src/cmd/mandoc/chars.c
+usr/src/cmd/mandoc/config.h
+usr/src/cmd/mandoc/eqn.c
+usr/src/cmd/mandoc/eqn_html.c
+usr/src/cmd/mandoc/eqn_term.c
+usr/src/cmd/mandoc/html.c
+usr/src/cmd/mandoc/html.h
+usr/src/cmd/mandoc/lib.c
+usr/src/cmd/mandoc/libman.h
+usr/src/cmd/mandoc/libmandoc.h
+usr/src/cmd/mandoc/libmdoc.h
+usr/src/cmd/mandoc/libroff.h
+usr/src/cmd/mandoc/main.c
+usr/src/cmd/mandoc/main.h
+usr/src/cmd/mandoc/man.c
+usr/src/cmd/mandoc/man.h
+usr/src/cmd/mandoc/man_hash.c
+usr/src/cmd/mandoc/man_html.c
+usr/src/cmd/mandoc/man_macro.c
+usr/src/cmd/mandoc/man_term.c
+usr/src/cmd/mandoc/man_validate.c
+usr/src/cmd/mandoc/mandoc.c
+usr/src/cmd/mandoc/mandoc.h
+usr/src/cmd/mandoc/mdoc.c
+usr/src/cmd/mandoc/mdoc.h
+usr/src/cmd/mandoc/mdoc_argv.c
+usr/src/cmd/mandoc/mdoc_hash.c
+usr/src/cmd/mandoc/mdoc_html.c
+usr/src/cmd/mandoc/mdoc_macro.c
+usr/src/cmd/mandoc/mdoc_man.c
+usr/src/cmd/mandoc/mdoc_term.c
+usr/src/cmd/mandoc/mdoc_validate.c
+usr/src/cmd/mandoc/msec.c
+usr/src/cmd/mandoc/out.c
+usr/src/cmd/mandoc/out.h
+usr/src/cmd/mandoc/preconv.c
+usr/src/cmd/mandoc/read.c
+usr/src/cmd/mandoc/roff.c
+usr/src/cmd/mandoc/st.c
+usr/src/cmd/mandoc/tbl.c
+usr/src/cmd/mandoc/tbl_data.c
+usr/src/cmd/mandoc/tbl_html.c
+usr/src/cmd/mandoc/tbl_layout.c
+usr/src/cmd/mandoc/tbl_opts.c
+usr/src/cmd/mandoc/tbl_term.c
+usr/src/cmd/mandoc/term.c
+usr/src/cmd/mandoc/term.h
+usr/src/cmd/mandoc/term_ascii.c
+usr/src/cmd/mandoc/term_ps.c
+usr/src/cmd/mandoc/tree.c
+usr/src/cmd/mandoc/vol.c
 usr/src/common/bzip2/bzlib.h
 usr/src/common/bzip2/crctable.c
 usr/src/common/bzip2/randtable.c
diff --git a/exception_lists/hdrchk b/exception_lists/hdrchk
index 7925ef1..9ff5a68 100644
--- a/exception_lists/hdrchk
+++ b/exception_lists/hdrchk
@@ -17,6 +17,18 @@
 usr/src/cmd/krb5/ldap_util/kdb5_ldap_util.h
 usr/src/cmd/krb5/slave/kprop.h
 usr/src/cmd/localedef/localedef.h
+usr/src/cmd/mandoc/config.h
+usr/src/cmd/mandoc/html.h
+usr/src/cmd/mandoc/libman.h
+usr/src/cmd/mandoc/libmandoc.h
+usr/src/cmd/mandoc/libmdoc.h
+usr/src/cmd/mandoc/libroff.h
+usr/src/cmd/mandoc/main.h
+usr/src/cmd/mandoc/man.h
+usr/src/cmd/mandoc/mandoc.h
+usr/src/cmd/mandoc/mdoc.h
+usr/src/cmd/mandoc/out.h
+usr/src/cmd/mandoc/term.h
 usr/src/common/openssl/crypto/krb5/krb5_asn.h
 usr/src/lib/gss_mechs/mech_krb5/et/error_table.h
 usr/src/lib/gss_mechs/mech_krb5/et/internal.h
diff --git a/exception_lists/packaging b/exception_lists/packaging
index a9eef93..5a93d23 100644
--- a/exception_lists/packaging
+++ b/exception_lists/packaging
@@ -946,6 +946,8 @@
 #
 opt/onbld/bin/i386/elfsign	i386
 opt/onbld/bin/sparc/elfsign	sparc
+opt/onbld/bin/i386/mandoc	i386
+opt/onbld/bin/sparc/mandoc	sparc
 
 #
 # Private libdwarf
diff --git a/usr/src/Makefile b/usr/src/Makefile
index ef522a4..b9be247 100644
--- a/usr/src/Makefile
+++ b/usr/src/Makefile
@@ -22,6 +22,7 @@
 #
 # Copyright (c) 1989, 2010, Oracle and/or its affiliates. All rights reserved.
 # Copyright (c) 2012 by Delphix. All rights reserved.
+# Copyright 2014 Garrett D'Amore <garrett@damore.org>
 #
 
 #
@@ -65,6 +66,9 @@
 # The check target also causes smf(5) service manifests to be validated.
 CHKMFSTSUBDIRS=	cmd
 
+# And man page formats
+CHKMANSUBDIRS = man
+
 MSGSUBDIRS=	cmd ucbcmd lib
 DOMAINS= \
 	SUNW_OST_ADMIN \
@@ -205,7 +209,7 @@
 	@cd cmd/fm; pwd; $(MAKE) install_h
 	@cd cmd/mdb; pwd; $(MAKE) install_h
 
-check:	$(CHKHDRSUBDIRS) $(CHKMFSTSUBDIRS)
+check:	$(CHKHDRSUBDIRS) $(CHKMFSTSUBDIRS) $(CHKMANSUBDIRS)
 
 #
 # Cross-reference customization: skip all of the subdirectories that
diff --git a/usr/src/cmd/Makefile b/usr/src/cmd/Makefile
index 79bce93..45a41f1 100644
--- a/usr/src/cmd/Makefile
+++ b/usr/src/cmd/Makefile
@@ -24,11 +24,12 @@
 # Copyright (c) 2012 Joyent, Inc.  All rights reserved.
 # Copyright (c) 2012 by Delphix. All rights reserved.
 # Copyright (c) 2013 DEY Storage Systems, Inc. All rights reserved.
+# Copyright 2014 Garrett D'Amore <garrett@damore.org>
 
 include ../Makefile.master
 
 #
-#	Note that the commands 'agents', 'lp', 'perl', and 'man' are first in
+#	Note that the commands 'lp', and 'perl' are first in
 #	the list, violating alphabetical order.  This is because they are very
 #	long-running and should be given the most wall-clock time for a
 #	parallel build.
@@ -52,7 +53,6 @@
 	availdevs	\
 	lp		\
 	perl		\
-	man		\
 	Adm		\
 	abi		\
 	adbgen		\
@@ -252,6 +252,8 @@
 	mail		\
 	mailx		\
 	makekey		\
+	man		\
+	mandoc		\
 	mdb		\
 	mesg		\
 	mkdir		\
@@ -498,12 +500,11 @@
 	vntsd
 
 #
-# Commands that are messaged.  Note that 'lp' and 'man' come first
-# (see previous comment about 'lp' and 'man').
+# Commands that are messaged.  Note that 'lp' comes first
+# (see previous comment about 'lp'.)
 #
 MSGSUBDIRS=		\
 	lp		\
-	man		\
 	abi		\
 	acctadm		\
 	allocate	\
@@ -617,6 +618,7 @@
 	luxadm		\
 	lvm		\
 	mailx		\
+	man		\
 	mesg		\
 	mkdir		\
 	mkpwdict	\
diff --git a/usr/src/cmd/man/Makefile b/usr/src/cmd/man/Makefile
index bf87071..911bae6 100644
--- a/usr/src/cmd/man/Makefile
+++ b/usr/src/cmd/man/Makefile
@@ -1,61 +1,47 @@
 #
-# CDDL HEADER START
+# This file and its contents are supplied under the terms of the
+# Common Development and Distribution License ("CDDL"), version 1.0.
+# You may only use this file in accordance with the terms of version
+# 1.0 of the CDDL.
 #
-# The contents of this file are subject to the terms of the
-# Common Development and Distribution License, Version 1.0 only
-# (the "License").  You may not use this file except in compliance
-# with the License.
+# A full copy of the text of the CDDL should have accompanied this
+# source.  A copy of the CDDL is also available via the Internet at
+# http://www.illumos.org/license/CDDL.
 #
-# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
-# or http://www.opensolaris.org/os/licensing.
-# See the License for the specific language governing permissions
-# and limitations under the License.
+
 #
-# When distributing Covered Code, include this CDDL HEADER in each
-# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
-# If applicable, add the following below this CDDL HEADER, with the
-# fields enclosed by brackets "[]" replaced with your own identifying
-# information: Portions Copyright [yyyy] [name of copyright owner]
+# Copyright 2012 Nexenta Systems, Inc. All rights reserved.
+# Copyright 2014 Garrett D'Amore <garrett@damore.org>
 #
-# CDDL HEADER END
-#
-#
-#ident	"%Z%%M%	%I%	%E% SMI"
-#
-# Copyright (c) 1990 by Sun Microsystems, Inc.
-#
-# cmd/man/Makefile
 
-include ../Makefile.cmd
-SUBDIRS = src
+PROG=		man
+LINKS=		apropos whatis catman
+LIBLINKS =	makewhatis
+OBJS=		makewhatis.o man.o stringlist.o
+SRCS=		$(OBJS:%.o=%.c)
 
-all :=		TARGET= all
-install :=	TARGET= install
-clean :=	TARGET= clean
-clobber :=	TARGET= clobber
-lint :=		TARGET= lint
-_msg :=         TARGET= catalog
+include		$(SRC)/cmd/Makefile.cmd
 
-#for message catalog files
-POFILE = man.po
-POFILES = src/src.po
+CFLAGS +=	$(CCVERBOSE)
 
-.KEEP_STATE:
+ROOTLINKS=	$(LINKS:%=$(ROOTBIN)/%) $(LIBLINKS:%=$(ROOTLIB)/%)
 
-all install clean lint: $(SUBDIRS)
+.KEEP_STATE :
 
-clobber: $(SUBDIRS)	local_clobber
+all:		$(PROG)
 
-local_clobber:
-	$(RM)	$(CLOBBERFILES)
+clean:
+		$(RM) $(OBJS)
 
-_msg:   $(SUBDIRS)
-	$(RM)	$(POFILE)
-	cat $(POFILES)      > $(POFILE)
-	$(RM)  $(MSGDOMAIN)/$(POFILE)
-	cp $(POFILE) $(MSGDOMAIN)
+install:	all $(ROOTPROG) $(ROOTLINKS)
 
-$(SUBDIRS): FRC
-	@cd $@; pwd; $(MAKE) $(TARGET)
+lint: 		lint_SRCS
 
-FRC:
+$(PROG):	$(OBJS)
+		$(LINK.c) $(OBJS) -o $@ $(LDLIBS)
+		$(POST_PROCESS)
+
+$(ROOTLINKS):	$(ROOTPROG)
+		$(RM) $@; $(LN) $(ROOTPROG) $@
+
+include		$(SRC)/cmd/Makefile.targ
diff --git a/usr/src/cmd/man/THIRDPARTYLICENSE b/usr/src/cmd/man/THIRDPARTYLICENSE
new file mode 100644
index 0000000..f6ba743
--- /dev/null
+++ b/usr/src/cmd/man/THIRDPARTYLICENSE
@@ -0,0 +1,92 @@
+man.c:
+
+Copyright (c) 1980 Regents of the University of California.  
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+    1. Redistributions of source code must retain the above copyright
+       notice, this list of conditions and the following disclaimer.
+    2. Redistributions in binary form must reproduce the above
+       copyright notice, this list of conditions and the following
+       disclaimer in the documentation and/or other materials provided
+       with the distribution.
+    3. All advertising materials mentioning features or use of this
+       software must display the following acknowledgement:
+           This product includes software developed by the University
+           of California, Berkeley and its contributors.
+    4. Neither the name of the University nor the names of its
+       contributors may be used to endorse or promote products derived
+       from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE REGENTS 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 COPYRIGHT OWNER 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.
+
+
+makewhatis.c:
+
+Copyright (c) 2002 John Rochester
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions
+are met:
+1. Redistributions of source code must retain the above copyright
+   notice, this list of conditions and the following disclaimer,
+   in this position and unchanged.
+2. Redistributions in binary form must reproduce the above copyright
+   notice, this list of conditions and the following disclaimer in the
+   documentation and/or other materials provided with the distribution.
+3. The name of the author may not be used to endorse or promote products
+   derived from this software without specific prior written permission
+
+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.
+
+
+stringlist.c, stringlist.h:
+
+Copyright (c) 1994 Christos Zoulas
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions
+are met:
+1. Redistributions of source code must retain the above copyright
+   notice, this list of conditions and the following disclaimer.
+2. Redistributions in binary form must reproduce the above copyright
+   notice, this list of conditions and the following disclaimer in the
+   documentation and/or other materials provided with the distribution.
+4. The name of the author may not be used to endorse or promote products
+   derived from this software without specific prior written permission.
+
+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.
diff --git a/usr/src/cmd/man/src/THIRDPARTYLICENSE.descrip b/usr/src/cmd/man/THIRDPARTYLICENSE.descrip
similarity index 100%
rename from usr/src/cmd/man/src/THIRDPARTYLICENSE.descrip
rename to usr/src/cmd/man/THIRDPARTYLICENSE.descrip
diff --git a/usr/src/cmd/man/makewhatis.c b/usr/src/cmd/man/makewhatis.c
new file mode 100644
index 0000000..c5428e4
--- /dev/null
+++ b/usr/src/cmd/man/makewhatis.c
@@ -0,0 +1,837 @@
+/*
+ * Copyright (c) 2002 John Rochester
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer,
+ *    in this position and unchanged.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ *    derived from this software without specific prior written permission
+ *
+ * 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.
+ */
+
+/*
+ * Copyright 2012 Nexenta Systems, Inc. All rights reserved.
+ * Copyright 2014 Garrett D'Amore <garrett@damore.org>
+ */
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/param.h>
+
+#include <ctype.h>
+#include <dirent.h>
+#include <err.h>
+#include <signal.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "man.h"
+#include "stringlist.h"
+
+
+/* Information collected about each man page in a section */
+struct page_info {
+	char	*filename;
+	char	*name;
+	char	*suffix;
+	ino_t	inode;
+};
+
+/* An expanding string */
+struct sbuf {
+	char	*content;	/* the start of the buffer */
+	char	*end;		/* just past the end of the content */
+	char	*last;		/* the last allocated character */
+};
+
+/* Remove the last amount characters from the sbuf */
+#define	sbuf_retract(sbuf, amount) ((sbuf)->end -= (amount))
+/* Return the length of the sbuf content */
+#define	sbuf_length(sbuf) ((sbuf)->end - (sbuf)->content)
+
+typedef char *edited_copy(char *from, char *to, int length);
+
+/*
+ * While the whatis line is being formed, it is stored in whatis_proto.
+ * When finished, it is reformatted into whatis_final and then appended
+ * to whatis_lines.
+ */
+static struct sbuf	*whatis_proto;
+static struct sbuf	*whatis_final;
+static stringlist	*whatis_lines;	/* collected output lines */
+
+static char tempfile[MAXPATHLEN];	/* path of temporary file, if any */
+
+#define	MDOC_COMMANDS	"ArDvErEvFlLiNmPa"
+
+
+/* Free a struct page_info and its content */
+static void
+free_page_info(struct page_info *info)
+{
+
+	free(info->filename);
+	free(info->name);
+	free(info->suffix);
+	free(info);
+}
+
+/*
+ * Allocate and fill in a new struct page_info given the
+ * name of the man section directory and the dirent of the file.
+ * If the file is not a man page, return NULL.
+ */
+static struct page_info *
+new_page_info(char *dir, struct dirent *dirent)
+{
+	struct page_info *info;
+	int		basename_length;
+	char		*suffix;
+	struct stat	st;
+
+	if ((info = malloc(sizeof (struct page_info))) == NULL)
+		err(1, "malloc");
+	basename_length = strlen(dirent->d_name);
+	suffix = &dirent->d_name[basename_length];
+	if (asprintf(&info->filename, "%s/%s", dir, dirent->d_name) == -1)
+		err(1, "asprintf");
+	for (;;) {
+		if (--suffix == dirent->d_name || !isalnum(*suffix)) {
+			if (*suffix == '.')
+				break;
+			free(info->filename);
+			free(info);
+			return (NULL);
+		}
+	}
+	*suffix++ = '\0';
+	info->name = strdup(dirent->d_name);
+	info->suffix = strdup(suffix);
+	if (stat(info->filename, &st) < 0) {
+		warn("%s", info->filename);
+		free_page_info(info);
+		return (NULL);
+	}
+	if (!S_ISREG(st.st_mode)) {
+		free_page_info(info);
+		return (NULL);
+	}
+	info->inode = st.st_ino;
+	return (info);
+}
+
+/*
+ * Reset sbuf length to 0.
+ */
+static void
+sbuf_clear(struct sbuf *sbuf)
+{
+
+	sbuf->end = sbuf->content;
+}
+
+/*
+ * Allocate a new sbuf.
+ */
+static struct sbuf *
+new_sbuf(void)
+{
+	struct sbuf	*sbuf;
+
+	if ((sbuf = malloc(sizeof (struct sbuf))) == NULL)
+		err(1, "malloc");
+	if ((sbuf->content = (char *)malloc(LINE_ALLOC)) == NULL)
+		err(1, "malloc");
+	sbuf->last = sbuf->content + LINE_ALLOC - 1;
+	sbuf_clear(sbuf);
+
+	return (sbuf);
+}
+
+/*
+ * Ensure that there is enough room in the sbuf
+ * for nchars more characters.
+ */
+static void
+sbuf_need(struct sbuf *sbuf, int nchars)
+{
+	char *new_content;
+	size_t size, cntsize;
+	size_t grow = 128;
+
+	while (grow < nchars) {
+		grow += 128;	/* we grow in chunks of 128 bytes */
+	}
+
+	/* Grow if the buffer isn't big enough */
+	if (sbuf->end + nchars > sbuf->last) {
+		size = sbuf->last + 1 - sbuf->content;
+		size += grow;
+		cntsize = sbuf->end - sbuf->content;
+
+		if ((new_content = realloc(sbuf->content, size)) == NULL) {
+			perror("realloc");
+			if (tempfile[0] != '\0')
+				(void) unlink(tempfile);
+			exit(1);
+		}
+		sbuf->content = new_content;
+		sbuf->end = new_content + cntsize;
+		sbuf->last = new_content + size - 1;
+	}
+}
+
+/*
+ * Append a string of a given length to the sbuf.
+ */
+static void
+sbuf_append(struct sbuf *sbuf, const char *text, int length)
+{
+	if (length > 0) {
+		sbuf_need(sbuf, length);
+		(void) memcpy(sbuf->end, text, length);
+		sbuf->end += length;
+	}
+}
+
+/*
+ * Append a null-terminated string to the sbuf.
+ */
+static void
+sbuf_append_str(struct sbuf *sbuf, char *text)
+{
+
+	sbuf_append(sbuf, text, strlen(text));
+}
+
+/*
+ * Append an edited null-terminated string to the sbuf.
+ */
+static void
+sbuf_append_edited(struct sbuf *sbuf, char *text, edited_copy copy)
+{
+	int	length;
+
+	if ((length = strlen(text)) > 0) {
+		sbuf_need(sbuf, length);
+		sbuf->end = copy(text, sbuf->end, length);
+	}
+}
+
+/*
+ * Strip any of a set of chars from the end of the sbuf.
+ */
+static void
+sbuf_strip(struct sbuf *sbuf, const char *set)
+{
+
+	while (sbuf->end > sbuf->content && strchr(set, sbuf->end[-1]) != NULL)
+		sbuf->end--;
+}
+
+/*
+ * Return the null-terminated string built by the sbuf.
+ */
+static char *
+sbuf_content(struct sbuf *sbuf)
+{
+
+	*sbuf->end = '\0';
+	return (sbuf->content);
+}
+
+/*
+ * Return true if no man page exists in the directory with
+ * any of the names in the stringlist.
+ */
+static int
+no_page_exists(char *dir, stringlist *names, char *suffix)
+{
+	char	path[MAXPATHLEN];
+	char	*suffixes[] = { "", ".gz", ".bz2", NULL };
+	size_t	i;
+	int	j;
+
+	for (i = 0; i < names->sl_cur; i++) {
+		for (j = 0; suffixes[j] != NULL; j++) {
+			(void) snprintf(path, MAXPATHLEN, "%s/%s.%s%s",
+			    dir, names->sl_str[i], suffix, suffixes[j]);
+			if (access(path, F_OK) == 0) {
+				return (0);
+			}
+		}
+	}
+	return (1);
+}
+
+/* ARGSUSED sig */
+static void
+trap_signal(int sig)
+{
+
+	if (tempfile[0] != '\0')
+		(void) unlink(tempfile);
+
+	exit(1);
+}
+
+/*
+ * Attempt to open an output file.
+ * Return NULL if unsuccessful.
+ */
+static FILE *
+open_output(char *name)
+{
+	FILE	*output;
+
+	whatis_lines = sl_init();
+	(void) snprintf(tempfile, MAXPATHLEN, "%s.tmp", name);
+	name = tempfile;
+	if ((output = fopen(name, "w")) == NULL) {
+		warn("%s", name);
+		return (NULL);
+	}
+	return (output);
+}
+
+static int
+linesort(const void *a, const void *b)
+{
+
+	return (strcmp((*(const char * const *)a), (*(const char * const *)b)));
+}
+
+/*
+ * Write the unique sorted lines to the output file.
+ */
+static void
+finish_output(FILE *output, char *name)
+{
+	size_t	i;
+	char	*prev = NULL;
+
+	qsort(whatis_lines->sl_str, whatis_lines->sl_cur, sizeof (char *),
+	    linesort);
+	for (i = 0; i < whatis_lines->sl_cur; i++) {
+		char *line = whatis_lines->sl_str[i];
+		if (i > 0 && strcmp(line, prev) == 0)
+			continue;
+		prev = line;
+		(void) fputs(line, output);
+		(void) putc('\n', output);
+	}
+	(void) fclose(output);
+	sl_free(whatis_lines, 1);
+	(void) rename(tempfile, name);
+	(void) unlink(tempfile);
+}
+
+static FILE *
+open_whatis(char *mandir)
+{
+	char	filename[MAXPATHLEN];
+
+	(void) snprintf(filename, MAXPATHLEN, "%s/%s", mandir, WHATIS);
+	return (open_output(filename));
+}
+
+static void
+finish_whatis(FILE *output, char *mandir)
+{
+	char	filename[MAXPATHLEN];
+
+	(void) snprintf(filename, MAXPATHLEN, "%s/%s", mandir, WHATIS);
+	finish_output(output, filename);
+}
+
+/*
+ * Remove trailing spaces from a string, returning a pointer to just
+ * beyond the new last character.
+ */
+static char *
+trim_rhs(char *str)
+{
+	char	*rhs;
+
+	rhs = &str[strlen(str)];
+	while (--rhs > str && isspace(*rhs))
+		;
+	*++rhs = '\0';
+	return (rhs);
+}
+
+/*
+ * Return a pointer to the next non-space character in the string.
+ */
+static char *
+skip_spaces(char *s)
+{
+
+	while (*s != '\0' && isspace(*s))
+		s++;
+
+	return (s);
+}
+
+/*
+ * Return whether the line is of one of the forms:
+ *	.Sh NAME
+ *	.Sh "NAME"
+ *	etc.
+ * assuming that section_start is ".Sh".
+ */
+static int
+name_section_line(char *line, const char *section_start)
+{
+	char		*rhs;
+
+	if (strncmp(line, section_start, 3) != 0)
+		return (0);
+	line = skip_spaces(line + 3);
+	rhs = trim_rhs(line);
+	if (*line == '"') {
+		line++;
+		if (*--rhs == '"')
+			*rhs = '\0';
+	}
+	if (strcmp(line, "NAME") == 0)
+		return (1);
+
+	return (0);
+}
+
+/*
+ * Copy characters while removing the most common nroff/troff markup:
+ *	\(em, \(mi, \s[+-N], \&
+ *	\fF, \f(fo, \f[font]
+ *	\*s, \*(st, \*[stringvar]
+ */
+static char *
+de_nroff_copy(char *from, char *to, int fromlen)
+{
+	char	*from_end = &from[fromlen];
+
+	while (from < from_end) {
+		switch (*from) {
+		case '\\':
+			switch (*++from) {
+			case '(':
+				if (strncmp(&from[1], "em", 2) == 0 ||
+				    strncmp(&from[1], "mi", 2) == 0) {
+					from += 3;
+					continue;
+				}
+				break;
+			case 's':
+				if (*++from == '-')
+					from++;
+				while (isdigit(*from))
+					from++;
+				continue;
+			case 'f':
+			case '*':
+				if (*++from == '(') {
+					from += 3;
+				} else if (*from == '[') {
+					while (*++from != ']' &&
+					    from < from_end)
+						;
+					from++;
+				} else {
+					from++;
+				}
+				continue;
+			case '&':
+				from++;
+				continue;
+			}
+			break;
+		}
+		*to++ = *from++;
+	}
+	return (to);
+}
+
+/*
+ * Append a string with the nroff formatting removed.
+ */
+static void
+add_nroff(char *text)
+{
+
+	sbuf_append_edited(whatis_proto, text, de_nroff_copy);
+}
+
+/*
+ * Appends "name(suffix), " to whatis_final
+ */
+static void
+add_whatis_name(char *name, char *suffix)
+{
+
+	if (*name != '\0') {
+		sbuf_append_str(whatis_final, name);
+		sbuf_append(whatis_final, "(", 1);
+		sbuf_append_str(whatis_final, suffix);
+		sbuf_append(whatis_final, "), ", 3);
+	}
+}
+
+/*
+ * Processes an old-style man(7) line. This ignores commands with only
+ * a single number argument.
+ */
+static void
+process_man_line(char *line)
+{
+	char	*p;
+
+	if (*line == '.') {
+		while (isalpha(*++line))
+			;
+		p = line = skip_spaces(line);
+		while (*p != '\0') {
+			if (!isdigit(*p))
+				break;
+			p++;
+		}
+		if (*p == '\0')
+			return;
+	} else
+		line = skip_spaces(line);
+	if (*line != '\0') {
+		add_nroff(line);
+		sbuf_append(whatis_proto, " ", 1);
+	}
+}
+
+/*
+ * Processes a new-style mdoc(7) line.
+ */
+static void
+process_mdoc_line(char *line)
+{
+	int	xref;
+	int	arg = 0;
+	char	*line_end = &line[strlen(line)];
+	int	orig_length = sbuf_length(whatis_proto);
+	char	*next;
+
+	if (*line == '\0')
+		return;
+	if (line[0] != '.' || !isupper(line[1]) || !islower(line[2])) {
+		add_nroff(skip_spaces(line));
+		sbuf_append(whatis_proto, " ", 1);
+		return;
+	}
+	xref = strncmp(line, ".Xr", 3) == 0;
+	line += 3;
+	while ((line = skip_spaces(line)) < line_end) {
+		if (*line == '"') {
+			next = ++line;
+			for (;;) {
+				next = strchr(next, '"');
+				if (next == NULL)
+					break;
+				(void) memmove(next, next + 1, strlen(next));
+				line_end--;
+				if (*next != '"')
+					break;
+				next++;
+			}
+		} else {
+			next = strpbrk(line, " \t");
+		}
+		if (next != NULL)
+			*next++ = '\0';
+		else
+			next = line_end;
+		if (isupper(*line) && islower(line[1]) && line[2] == '\0') {
+			if (strcmp(line, "Ns") == 0) {
+				arg = 0;
+				line = next;
+				continue;
+			}
+			if (strstr(line, MDOC_COMMANDS) != NULL) {
+				line = next;
+				continue;
+			}
+		}
+		if (arg > 0 && strchr(",.:;?!)]", *line) == 0) {
+			if (xref) {
+				sbuf_append(whatis_proto, "(", 1);
+				add_nroff(line);
+				sbuf_append(whatis_proto, ")", 1);
+				xref = 0;
+			} else {
+				sbuf_append(whatis_proto, " ", 1);
+			}
+		}
+		add_nroff(line);
+		arg++;
+		line = next;
+	}
+	if (sbuf_length(whatis_proto) > orig_length)
+		sbuf_append(whatis_proto, " ", 1);
+}
+
+/*
+ * Collect a list of comma-separated names from the text.
+ */
+static void
+collect_names(stringlist *names, char *text)
+{
+	char	*arg;
+
+	for (;;) {
+		arg = text;
+		text = strchr(text, ',');
+		if (text != NULL)
+			*text++ = '\0';
+		(void) sl_add(names, arg);
+		if (text == NULL)
+			return;
+		if (*text == ' ')
+			text++;
+	}
+}
+
+enum { STATE_UNKNOWN, STATE_MANSTYLE, STATE_MDOCNAME, STATE_MDOCDESC };
+
+/*
+ * Process a man page source into a single whatis line and add it
+ * to whatis_lines.
+ */
+static void
+process_page(struct page_info *page, char *section_dir)
+{
+	FILE		*fp;
+	stringlist	*names;
+	char		*descr;
+	int		state = STATE_UNKNOWN;
+	size_t		i;
+	char		*line = NULL;
+	size_t		linecap = 0;
+
+	sbuf_clear(whatis_proto);
+	if ((fp = fopen(page->filename, "r")) == NULL) {
+		warn("%s", page->filename);
+		return;
+	}
+	while (getline(&line, &linecap, fp) > 0) {
+		/* Skip comments */
+		if (strncmp(line, ".\\\"", 3) == 0)
+			continue;
+		switch (state) {
+		/* Haven't reached the NAME section yet */
+		case STATE_UNKNOWN:
+			if (name_section_line(line, ".SH"))
+				state = STATE_MANSTYLE;
+			else if (name_section_line(line, ".Sh"))
+				state = STATE_MDOCNAME;
+			continue;
+		/* Inside an old-style .SH NAME section */
+		case STATE_MANSTYLE:
+			if (strncmp(line, ".SH", 3) == 0 ||
+			    strncmp(line, ".SS", 3) == 0)
+				break;
+			(void) trim_rhs(line);
+			if (strcmp(line, ".") == 0)
+				continue;
+			if (strncmp(line, ".IX", 3) == 0) {
+				line += 3;
+				line = skip_spaces(line);
+			}
+			process_man_line(line);
+			continue;
+		/* Inside a new-style .Sh NAME section (the .Nm part) */
+		case STATE_MDOCNAME:
+			(void) trim_rhs(line);
+			if (strncmp(line, ".Nm", 3) == 0) {
+				process_mdoc_line(line);
+				continue;
+			} else {
+				if (strcmp(line, ".") == 0)
+					continue;
+				sbuf_append(whatis_proto, "- ", 2);
+				state = STATE_MDOCDESC;
+			}
+			/* FALLTHROUGH */
+		/* Inside a new-style .Sh NAME section (after the .Nm-s) */
+		case STATE_MDOCDESC:
+			if (strncmp(line, ".Sh", 3) == 0)
+				break;
+			(void) trim_rhs(line);
+			if (strcmp(line, ".") == 0)
+				continue;
+			process_mdoc_line(line);
+			continue;
+		}
+		break;
+	}
+	(void) fclose(fp);
+	sbuf_strip(whatis_proto, " \t.-");
+	line = sbuf_content(whatis_proto);
+	/*
+	 * Line now contains the appropriate data, but without the
+	 * proper indentation or the section appended to each name.
+	 */
+	descr = strstr(line, " - ");
+	if (descr == NULL) {
+		descr = strchr(line, ' ');
+		if (descr == NULL)
+			return;
+		*descr++ = '\0';
+	} else {
+		*descr = '\0';
+		descr += 3;
+	}
+	names = sl_init();
+	collect_names(names, line);
+	sbuf_clear(whatis_final);
+	if (!sl_find(names, page->name) &&
+	    no_page_exists(section_dir, names, page->suffix)) {
+		/*
+		 * Add the page name since that's the only
+		 * thing that man(1) will find.
+		 */
+		add_whatis_name(page->name, page->suffix);
+	}
+	for (i = 0; i < names->sl_cur; i++)
+		add_whatis_name(names->sl_str[i], page->suffix);
+	sl_free(names, 0);
+	/* Remove last ", " */
+	sbuf_retract(whatis_final, 2);
+	while (sbuf_length(whatis_final) < INDENT)
+		sbuf_append(whatis_final, " ", 1);
+	sbuf_append(whatis_final, " - ", 3);
+	sbuf_append_str(whatis_final, skip_spaces(descr));
+	(void) sl_add(whatis_lines, strdup(sbuf_content(whatis_final)));
+}
+
+/*
+ * Sort pages first by inode number, then by name.
+ */
+static int
+pagesort(const void *a, const void *b)
+{
+	const struct page_info *p1 = *(struct page_info * const *) a;
+	const struct page_info *p2 = *(struct page_info * const *) b;
+
+	if (p1->inode == p2->inode)
+		return (strcmp(p1->name, p2->name));
+
+	return (p1->inode - p2->inode);
+}
+
+/*
+ * Process a single man section.
+ */
+static void
+process_section(char *section_dir)
+{
+	struct dirent	**entries;
+	int		nentries;
+	struct page_info **pages;
+	int		npages = 0;
+	int		i;
+	ino_t		prev_inode = 0;
+
+	/* Scan the man section directory for pages */
+	nentries = scandir(section_dir, &entries, NULL, alphasort);
+
+	/* Collect information about man pages */
+	pages = (struct page_info **)calloc(nentries,
+	    sizeof (struct page_info *));
+	for (i = 0; i < nentries; i++) {
+		struct page_info *info = new_page_info(section_dir, entries[i]);
+		if (info != NULL)
+			pages[npages++] = info;
+		free(entries[i]);
+	}
+	free(entries);
+	qsort(pages, npages, sizeof (struct page_info *), pagesort);
+
+	/* Process each unique page */
+	for (i = 0; i < npages; i++) {
+		struct page_info *page = pages[i];
+		if (page->inode != prev_inode) {
+			prev_inode = page->inode;
+			process_page(page, section_dir);
+		}
+		free_page_info(page);
+	}
+	free(pages);
+}
+
+/*
+ * Return whether the directory entry is a man page section.
+ */
+static int
+select_sections(const struct dirent *entry)
+{
+	const char	*p = &entry->d_name[3];
+
+	if (strncmp(entry->d_name, "man", 3) != 0)
+		return (0);
+	while (*p != '\0') {
+		if (!isalnum(*p++))
+			return (0);
+	}
+	return (1);
+}
+
+/*
+ * Process a single top-level man directory by finding all the
+ * sub-directories named man* and processing each one in turn.
+ */
+void
+mwpath(char *path)
+{
+	FILE		*fp = NULL;
+	struct dirent	**entries;
+	int		nsections;
+	int		i;
+
+	(void) signal(SIGINT, trap_signal);
+	(void) signal(SIGHUP, trap_signal);
+	(void) signal(SIGQUIT, trap_signal);
+	(void) signal(SIGTERM, trap_signal);
+
+	whatis_proto = new_sbuf();
+	whatis_final = new_sbuf();
+
+	nsections = scandir(path, &entries, select_sections, alphasort);
+	if ((fp = open_whatis(path)) == NULL)
+		return;
+	for (i = 0; i < nsections; i++) {
+		char	section_dir[MAXPATHLEN];
+
+		(void) snprintf(section_dir, MAXPATHLEN, "%s/%s",
+		    path, entries[i]->d_name);
+		process_section(section_dir);
+		free(entries[i]);
+	}
+	free(entries);
+	finish_whatis(fp, path);
+}
diff --git a/usr/src/cmd/man/man.c b/usr/src/cmd/man/man.c
new file mode 100644
index 0000000..8038cab
--- /dev/null
+++ b/usr/src/cmd/man/man.c
@@ -0,0 +1,1622 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License (the "License").
+ * You may not use this file except in compliance with the License.
+ *
+ * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
+ * or http://www.opensolaris.org/os/licensing.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
+ * If applicable, add the following below this CDDL HEADER, with the
+ * fields enclosed by brackets "[]" replaced with your own identifying
+ * information: Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ */
+
+/*
+ * Copyright (c) 1990, 2010, Oracle and/or its affiliates. All rights reserved.
+ * Copyright 2012, Josef 'Jeff' Sipek <jeffpc@31bits.net>. All rights reserved.
+ * Copyright 2013 Nexenta Systems, Inc. All rights reserved.
+ * Copyright 2014 Garrett D'Amore <garrett@damore.org>
+ */
+
+/*	Copyright (c) 1983, 1984, 1985, 1986, 1987, 1988, 1989  AT&T.	*/
+/*		All rights reserved.					*/
+
+/*
+ * University Copyright- Copyright (c) 1982, 1986, 1988
+ * The Regents of the University of California
+ * All Rights Reserved
+ *
+ * University Acknowledgment- Portions of this document are derived from
+ * software developed by the University of California, Berkeley, and its
+ * contributors.
+ */
+
+/*
+ * Find and display reference manual pages. This version includes makewhatis
+ * functionality as well.
+ */
+
+#include <sys/param.h>
+#include <sys/stat.h>
+#include <sys/termios.h>
+#include <sys/types.h>
+
+#include <ctype.h>
+#include <dirent.h>
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <fnmatch.h>
+#include <limits.h>
+#include <locale.h>
+#include <malloc.h>
+#include <memory.h>
+#include <regex.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "man.h"
+
+
+/* Mapping of old directories to new directories */
+static const struct map_entry {
+	char	*old_name;
+	char	*new_name;
+} map[] = {
+	{ "3b",		"3ucb"		},
+	{ "3e",		"3elf"		},
+	{ "3g",		"3gen"		},
+	{ "3k",		"3kstat"	},
+	{ "3n",		"3socket"	},
+	{ "3r",		"3rt"		},
+	{ "3s",		"3c"		},
+	{ "3t",		"3thr"		},
+	{ "3x",		"3curses"	},
+	{ "3xc",	"3xcurses"	},
+	{ "3xn",	"3xnet"		},
+	{ NULL,		NULL		}
+};
+
+struct suffix {
+	char *ds;
+	char *fs;
+};
+
+/*
+ * Flags that control behavior of build_manpath()
+ *
+ *   BMP_ISPATH 		pathv is a vector constructed from PATH.
+ *                		Perform appropriate path translations for
+ * 				manpath.
+ *   BMP_APPEND_DEFMANDIR	Add DEFMANDIR to the end if it hasn't
+ *				already appeared earlier.
+ *   BMP_FALLBACK_DEFMANDIR	Append /usr/share/man only if no other
+ *				manpath (including derived from PATH)
+ * 				elements are valid.
+ */
+#define	BMP_ISPATH		1
+#define	BMP_APPEND_DEFMANDIR	2
+#define	BMP_FALLBACK_DEFMANDIR	4
+
+/*
+ * When doing equality comparisons of directories, device and inode
+ * comparisons are done.  The secnode and dupnode structures are used
+ * to form a list of lists for this processing.
+ */
+struct secnode {
+	char		*secp;
+	struct secnode	*next;
+};
+struct dupnode {
+	dev_t		dev;	/* from struct stat st_dev */
+	ino_t		ino;	/* from struct stat st_ino */
+	struct secnode	*secl;	/* sections already considered */
+	struct dupnode	*next;
+};
+
+/*
+ * Map directories that may appear in PATH to the corresponding
+ * man directory.
+ */
+static struct pathmap {
+	char	*bindir;
+	char	*mandir;
+	dev_t	dev;
+	ino_t	ino;
+} bintoman[] = {
+	{ "/sbin",		"/usr/share/man,1m",		0, 0 },
+	{ "/usr/sbin",		"/usr/share/man,1m",		0, 0 },
+	{ "/usr/ucb",		"/usr/share/man,1b",		0, 0 },
+	{ "/usr/bin",		"/usr/share/man,1,1m,1s,1t,1c", 0, 0 },
+	{ "/usr/xpg4/bin",	"/usr/share/man,1",		0, 0 },
+	{ "/usr/xpg6/bin",	"/usr/share/man,1",		0, 0 },
+	{ NULL,			NULL,				0, 0 }
+};
+
+struct man_node {
+	char		*path;		/* mandir path */
+	char		**secv;		/* submandir suffices */
+	int		defsrch;	/* hint for man -p */
+	int		frompath;	/* hint for man -d */
+	struct man_node *next;
+};
+
+static int	all = 0;
+static int	apropos = 0;
+static int	debug = 0;
+static int	found = 0;
+static int	list = 0;
+static int	makewhatis = 0;
+static int	printmp = 0;
+static int	sargs = 0;
+static int	psoutput = 0;
+static int	lintout = 0;
+static int	whatis = 0;
+static int	makewhatishere = 0;
+
+static char	*mansec;
+static char	*pager = NULL;
+
+static char	*addlocale(char *);
+static struct man_node *build_manpath(char **, int);
+static void	do_makewhatis(struct man_node *);
+static char	*check_config(char *);
+static int	cmp(const void *, const void *);
+static int	dupcheck(struct man_node *, struct dupnode **);
+static int	format(char *, char *, char *, char *);
+static void	free_dupnode(struct dupnode *);
+static void	free_manp(struct man_node *manp);
+static void	freev(char **);
+static void	fullpaths(struct man_node **);
+static void	get_all_sect(struct man_node *);
+static int	getdirs(char *, char ***, int);
+static void	getpath(struct man_node *, char **);
+static void	getsect(struct man_node *, char **);
+static void	init_bintoman(void);
+static void	lower(char *);
+static void	mandir(char **, char *, char *, int);
+static int	manual(struct man_node *, char *);
+static char	*map_section(char *, char *);
+static char	*path_to_manpath(char *);
+static void	print_manpath(struct man_node *);
+static void	search_whatis(char *, char *);
+static int	searchdir(char *, char *, char *);
+static void	sortdir(DIR *, char ***);
+static char	**split(char *, char);
+static void	usage_man(void);
+static void	usage_whatapro(void);
+static void	usage_catman(void);
+static void	usage_makewhatis(void);
+static void	whatapro(struct man_node *, char *);
+
+static char	language[MAXPATHLEN]; 	/* LC_MESSAGES */
+static char	localedir[MAXPATHLEN];	/* locale specific path component */
+
+static char	*newsection = NULL;
+
+static int	manwidth = 0;
+
+extern const char	*__progname;
+
+int
+main(int argc, char **argv)
+{
+	int		c, i;
+	char		**pathv;
+	char		*manpath = NULL;
+	static struct man_node *mandirs = NULL;
+	int		bmp_flags = 0;
+	int		ret = 0;
+	char		*opts;
+	char		*mwstr;
+	int		catman = 0;
+
+	(void) setlocale(LC_ALL, "");
+	(void) strcpy(language, setlocale(LC_MESSAGES, (char *)NULL));
+	if (strcmp("C", language) != 0)
+		(void) strlcpy(localedir, language, MAXPATHLEN);
+
+#if !defined(TEXT_DOMAIN)
+#define	TEXT_DOMAIN "SYS_TEST"
+#endif
+	(void) textdomain(TEXT_DOMAIN);
+
+	if (strcmp(__progname, "apropos") == 0) {
+		apropos++;
+		opts = "M:ds:";
+	} else if (strcmp(__progname, "whatis") == 0) {
+		apropos++;
+		whatis++;
+		opts = "M:ds:";
+	} else if (strcmp(__progname, "catman") == 0) {
+		catman++;
+		makewhatis++;
+		opts = "P:M:w";
+	} else if (strcmp(__progname, "makewhatis") == 0) {
+		makewhatis++;
+		makewhatishere++;
+		manpath = ".";
+		opts = "";
+	} else {
+		opts = "FM:P:T:adfklprs:tw";
+		if (argc > 1 && strcmp(argv[1], "-") == 0) {
+			pager = "cat";
+			optind++;
+		}
+	}
+
+	opterr = 0;
+	while ((c = getopt(argc, argv, opts)) != -1) {
+		switch (c) {
+		case 'M':	/* Respecify path for man pages */
+			manpath = optarg;
+			break;
+		case 'a':
+			all++;
+			break;
+		case 'd':
+			debug++;
+			break;
+		case 'f':
+			whatis++;
+			/*FALLTHROUGH*/
+		case 'k':
+			apropos++;
+			break;
+		case 'l':
+			list++;
+			all++;
+			break;
+		case 'p':
+			printmp++;
+			break;
+		case 's':
+			mansec = optarg;
+			sargs++;
+			break;
+		case 'r':
+			lintout++;
+			break;
+		case 't':
+			psoutput++;
+			break;
+		case 'T':
+		case 'P':
+		case 'F':
+			/* legacy options, compatibility only and ignored */
+			break;
+		case 'w':
+			makewhatis++;
+			break;
+		case '?':
+		default:
+			if (apropos)
+				usage_whatapro();
+			else if (catman)
+				usage_catman();
+			else if (makewhatishere)
+				usage_makewhatis();
+			else
+				usage_man();
+		}
+	}
+	argc -= optind;
+	argv += optind;
+
+	if (argc == 0) {
+		if (apropos) {
+			(void) fprintf(stderr, gettext("%s what?\n"),
+			    __progname);
+			exit(1);
+		} else if (!printmp && !makewhatis) {
+			(void) fprintf(stderr,
+			    gettext("What manual page do you want?\n"));
+			exit(1);
+		}
+	}
+
+	init_bintoman();
+	if (manpath == NULL && (manpath = getenv("MANPATH")) == NULL) {
+		if ((manpath = getenv("PATH")) != NULL)
+			bmp_flags = BMP_ISPATH | BMP_APPEND_DEFMANDIR;
+		else
+			manpath = DEFMANDIR;
+	}
+	pathv = split(manpath, ':');
+	mandirs = build_manpath(pathv, bmp_flags);
+	freev(pathv);
+	fullpaths(&mandirs);
+
+	if (makewhatis) {
+		do_makewhatis(mandirs);
+		exit(0);
+	}
+
+	if (printmp) {
+		print_manpath(mandirs);
+		exit(0);
+	}
+
+	/* Collect environment information */
+	if (isatty(STDOUT_FILENO) && (mwstr = getenv("MANWIDTH")) != NULL &&
+	    *mwstr != '\0') {
+		if (strcasecmp(mwstr, "tty") == 0) {
+			struct winsize	ws;
+
+			if (ioctl(0, TIOCGWINSZ, &ws) != 0)
+				warn("TIOCGWINSZ");
+			else
+				manwidth = ws.ws_col;
+		} else {
+			manwidth = (int)strtol(mwstr, (char **)NULL, 10);
+			if (manwidth < 0)
+				manwidth = 0;
+		}
+	}
+	if (manwidth != 0) {
+		DPRINTF("-- Using non-standard page width: %d\n", manwidth);
+	}
+
+	if (pager == NULL) {
+		if ((pager = getenv("PAGER")) == NULL || *pager == '\0')
+			pager = PAGER;
+	}
+	DPRINTF("-- Using pager: %s\n", pager);
+
+	for (i = 0; i < argc; i++) {
+		char		*cmd;
+		static struct man_node *mp;
+		char		*pv[2];
+
+		/*
+		 * If full path to command specified, customize
+		 * the manpath accordingly.
+		 */
+		if ((cmd = strrchr(argv[i], '/')) != NULL) {
+			*cmd = '\0';
+			if ((pv[0] = strdup(argv[i])) == NULL)
+				err(1, "strdup");
+			pv[1] = NULL;
+			*cmd = '/';
+			mp = build_manpath(pv,
+			    BMP_ISPATH | BMP_FALLBACK_DEFMANDIR);
+		} else {
+			mp = mandirs;
+		}
+
+		if (apropos)
+			whatapro(mp, argv[i]);
+		else
+			ret += manual(mp, argv[i]);
+
+		if (mp != NULL && mp != mandirs) {
+			free(pv[0]);
+			free_manp(mp);
+		}
+	}
+
+	return (ret == 0 ? 0 : 1);
+}
+
+/*
+ * This routine builds the manpage structure from MANPATH or PATH,
+ * depending on flags.  See BMP_* definitions above for valid
+ * flags.
+ */
+static struct man_node *
+build_manpath(char **pathv, int flags)
+{
+	struct man_node *manpage = NULL;
+	struct man_node *currp = NULL;
+	struct man_node *lastp = NULL;
+	char		**p;
+	char		**q;
+	char		*mand = NULL;
+	char		*mandir = DEFMANDIR;
+	int		s;
+	struct dupnode	*didup = NULL;
+	struct stat	sb;
+
+	s = sizeof (struct man_node);
+	for (p = pathv; *p != NULL; ) {
+		if (flags & BMP_ISPATH) {
+			if ((mand = path_to_manpath(*p)) == NULL)
+				goto next;
+			free(*p);
+			*p = mand;
+		}
+		q = split(*p, ',');
+		if (stat(q[0], &sb) != 0 || (sb.st_mode & S_IFDIR) == 0) {
+			freev(q);
+			goto next;
+		}
+
+		if (access(q[0], R_OK | X_OK) == 0) {
+			/*
+			 * Some element exists.  Do not append DEFMANDIR as a
+			 * fallback.
+			 */
+			flags &= ~BMP_FALLBACK_DEFMANDIR;
+
+			if ((currp = (struct man_node *)calloc(1, s)) == NULL)
+				err(1, "calloc");
+
+			currp->frompath = (flags & BMP_ISPATH);
+
+			if (manpage == NULL)
+				lastp = manpage = currp;
+
+			getpath(currp, p);
+			getsect(currp, p);
+
+			/*
+			 * If there are no new elements in this path,
+			 * do not add it to the manpage list.
+			 */
+			if (dupcheck(currp, &didup) != 0) {
+				freev(currp->secv);
+				free(currp);
+			} else {
+				currp->next = NULL;
+				if (currp != manpage)
+					lastp->next = currp;
+				lastp = currp;
+			}
+		}
+		freev(q);
+next:
+		/*
+		 * Special handling of appending DEFMANDIR. After all pathv
+		 * elements have been processed, append DEFMANDIR if needed.
+		 */
+		if (p == &mandir)
+			break;
+		p++;
+		if (*p != NULL)
+			continue;
+		if (flags & (BMP_APPEND_DEFMANDIR | BMP_FALLBACK_DEFMANDIR)) {
+			p = &mandir;
+			flags &= ~BMP_ISPATH;
+		}
+	}
+
+	free_dupnode(didup);
+
+	return (manpage);
+}
+
+/*
+ * Store the mandir path into the manp structure.
+ */
+static void
+getpath(struct man_node *manp, char **pv)
+{
+	char	*s = *pv;
+	int	i = 0;
+
+	while (*s != '\0' && *s != ',')
+		i++, s++;
+
+	if ((manp->path = (char *)malloc(i + 1)) == NULL)
+		err(1, "malloc");
+	(void) strlcpy(manp->path, *pv, i + 1);
+}
+
+/*
+ * Store the mandir's corresponding sections (submandir
+ * directories) into the manp structure.
+ */
+static void
+getsect(struct man_node *manp, char **pv)
+{
+	char	*sections;
+	char	**sectp;
+
+	/* Just store all sections when doing makewhatis or apropos/whatis */
+	if (makewhatis || apropos) {
+		manp->defsrch = 1;
+		DPRINTF("-- Adding %s\n", manp->path);
+		manp->secv = NULL;
+		get_all_sect(manp);
+	} else if (sargs) {
+		manp->secv = split(mansec, ',');
+		for (sectp = manp->secv; *sectp; sectp++)
+			lower(*sectp);
+	} else if ((sections = strchr(*pv, ',')) != NULL) {
+		DPRINTF("-- Adding %s: MANSECTS=%s\n", manp->path, sections);
+		manp->secv = split(++sections, ',');
+		for (sectp = manp->secv; *sectp; sectp++)
+			lower(*sectp);
+		if (*manp->secv == NULL)
+			get_all_sect(manp);
+	} else if ((sections = check_config(*pv)) != NULL) {
+		manp->defsrch = 1;
+		DPRINTF("-- Adding %s: from %s, MANSECTS=%s\n", manp->path,
+		    CONFIG, sections);
+		manp->secv = split(sections, ',');
+		for (sectp = manp->secv; *sectp; sectp++)
+			lower(*sectp);
+		if (*manp->secv == NULL)
+			get_all_sect(manp);
+	} else {
+		manp->defsrch = 1;
+		DPRINTF("-- Adding %s: default sort order\n", manp->path);
+		manp->secv = NULL;
+		get_all_sect(manp);
+	}
+}
+
+/*
+ * Get suffices of all sub-mandir directories in a mandir.
+ */
+static void
+get_all_sect(struct man_node *manp)
+{
+	DIR	*dp;
+	char	**dirv;
+	char	**dv;
+	char	**p;
+	char	*prev = NULL;
+	char 	*tmp = NULL;
+	int	maxentries = MAXTOKENS;
+	int	entries = 0;
+
+	if ((dp = opendir(manp->path)) == 0)
+		return;
+
+	sortdir(dp, &dirv);
+
+	(void) closedir(dp);
+
+	if (manp->secv == NULL) {
+		if ((manp->secv = malloc(maxentries * sizeof (char *))) == NULL)
+			err(1, "malloc");
+	}
+
+	for (dv = dirv, p = manp->secv; *dv; dv++) {
+		if (strcmp(*dv, CONFIG) == 0) {
+			free(*dv);
+			continue;
+		}
+
+		free(tmp);
+		if ((tmp = strdup(*dv + 3)) == NULL)
+			err(1, "strdup");
+
+		if (prev != NULL && strcmp(prev, tmp) == 0) {
+			free(*dv);
+			continue;
+		}
+
+		free(prev);
+		if ((prev = strdup(*dv + 3)) == NULL)
+			err(1, "strdup");
+
+		if ((*p = strdup(*dv + 3)) == NULL)
+			err(1, "strdup");
+
+		p++; entries++;
+
+		if (entries == maxentries) {
+			maxentries += MAXTOKENS;
+			if ((manp->secv = realloc(manp->secv,
+			    sizeof (char *) * maxentries)) == NULL)
+				err(1, "realloc");
+			p = manp->secv + entries;
+		}
+		free(*dv);
+	}
+	free(tmp);
+	free(prev);
+	*p = NULL;
+	free(dirv);
+}
+
+/*
+ * Build whatis databases.
+ */
+static void
+do_makewhatis(struct man_node *manp)
+{
+	struct man_node *p;
+	char		*ldir;
+
+	for (p = manp; p != NULL; p = p->next) {
+		ldir = addlocale(p->path);
+		if (*localedir != '\0' && getdirs(ldir, NULL, 0) > 0)
+			mwpath(ldir);
+		free(ldir);
+		mwpath(p->path);
+	}
+}
+
+/*
+ * Count mandirs under the given manpath
+ */
+static int
+getdirs(char *path, char ***dirv, int flag)
+{
+	DIR		*dp;
+	struct dirent	*d;
+	int		n = 0;
+	int		maxentries = MAXDIRS;
+	char		**dv = NULL;
+
+	if ((dp = opendir(path)) == NULL)
+		return (0);
+
+	if (flag) {
+		if ((*dirv = malloc(sizeof (char *) *
+		    maxentries)) == NULL)
+			err(1, "malloc");
+		dv = *dirv;
+	}
+	while ((d = readdir(dp))) {
+		if (strncmp(d->d_name, "man", 3) != 0)
+			continue;
+		n++;
+
+		if (flag) {
+			if ((*dv = strdup(d->d_name + 3)) == NULL)
+				err(1, "strdup");
+			dv++;
+			if ((dv - *dirv) == maxentries) {
+				int	entries = maxentries;
+
+				maxentries += MAXTOKENS;
+				if ((*dirv = realloc(*dirv,
+				    sizeof (char *) * maxentries)) == NULL)
+					err(1, "realloc");
+				dv = *dirv + entries;
+			}
+		}
+	}
+
+	(void) closedir(dp);
+	return (n);
+}
+
+
+/*
+ * Find matching whatis or apropos entries.
+ */
+static void
+whatapro(struct man_node *manp, char *word)
+{
+	char		whatpath[MAXPATHLEN];
+	struct man_node *b;
+	char		*ldir;
+
+	for (b = manp; b != NULL; b = b->next) {
+		if (*localedir != '\0') {
+			ldir = addlocale(b->path);
+			if (getdirs(ldir, NULL, 0) != 0) {
+				(void) snprintf(whatpath, sizeof (whatpath),
+				    "%s/%s", ldir, WHATIS);
+				search_whatis(whatpath, word);
+			}
+			free(ldir);
+		}
+		(void) snprintf(whatpath, sizeof (whatpath), "%s/%s", b->path,
+		    WHATIS);
+		search_whatis(whatpath, word);
+	}
+}
+
+static void
+search_whatis(char *whatpath, char *word)
+{
+	FILE		*fp;
+	char		*line = NULL;
+	size_t		linecap = 0;
+	char		*pkwd;
+	regex_t		preg;
+	char		**ss = NULL;
+	char		s[MAXNAMELEN];
+	int		i;
+
+	if ((fp = fopen(whatpath, "r")) == NULL) {
+		perror(whatpath);
+		return;
+	}
+
+	DPRINTF("-- Found %s: %s\n", WHATIS, whatpath);
+
+	/* Build keyword regex */
+	if (asprintf(&pkwd, "%s%s%s", (whatis) ? "\\<" : "",
+	    word, (whatis) ? "\\>" : "") == -1)
+		err(1, "asprintf");
+
+	if (regcomp(&preg, pkwd, REG_BASIC | REG_ICASE | REG_NOSUB) != 0)
+		err(1, "regcomp");
+
+	if (sargs)
+		ss = split(mansec, ',');
+
+	while (getline(&line, &linecap, fp) > 0) {
+		if (regexec(&preg, line, 0, NULL, 0) == 0) {
+			if (sargs) {
+				/* Section-restricted search */
+				for (i = 0; ss[i] != NULL; i++) {
+					(void) snprintf(s, sizeof (s), "(%s)",
+					    ss[i]);
+					if (strstr(line, s) != NULL) {
+						(void) printf("%s", line);
+						break;
+					}
+				}
+			} else {
+				(void) printf("%s", line);
+			}
+		}
+	}
+
+	if (ss != NULL)
+		freev(ss);
+	free(pkwd);
+	(void) fclose(fp);
+}
+
+
+/*
+ * Split a string by specified separator.
+ */
+static char **
+split(char *s1, char sep)
+{
+	char	**tokv, **vp;
+	char	*mp = s1, *tp;
+	int	maxentries = MAXTOKENS;
+	int	entries = 0;
+
+	if ((tokv = vp = malloc(maxentries * sizeof (char *))) == NULL)
+		err(1, "malloc");
+
+	for (; mp && *mp; mp = tp) {
+		tp = strchr(mp, sep);
+		if (mp == tp) {
+			tp++;
+			continue;
+		}
+		if (tp) {
+			size_t	len;
+
+			len = tp - mp;
+			if ((*vp = (char *)malloc(sizeof (char) *
+			    len + 1)) == NULL)
+				err(1, "malloc");
+			(void) strncpy(*vp, mp, len);
+			*(*vp + len) = '\0';
+			tp++;
+			vp++;
+		} else {
+			if ((*vp = strdup(mp)) == NULL)
+				err(1, "strdup");
+			vp++;
+		}
+		entries++;
+		if (entries == maxentries) {
+			maxentries += MAXTOKENS;
+			if ((tokv = realloc(tokv,
+			    maxentries * sizeof (char *))) == NULL)
+				err(1, "realloc");
+			vp = tokv + entries;
+		}
+	}
+	*vp = 0;
+
+	return (tokv);
+}
+
+/*
+ * Free a vector allocated by split()
+ */
+static void
+freev(char **v)
+{
+	int i;
+	if (v != NULL) {
+		for (i = 0; v[i] != NULL; i++) {
+			free(v[i]);
+		}
+		free(v);
+	}
+}
+
+/*
+ * Convert paths to full paths if necessary
+ */
+static void
+fullpaths(struct man_node **manp_head)
+{
+	char		*cwd = NULL;
+	char		*p;
+	int		cwd_gotten = 0;
+	struct man_node *manp = *manp_head;
+	struct man_node *b;
+	struct man_node *prev = NULL;
+
+	for (b = manp; b != NULL; b = b->next) {
+		if (*(b->path) == '/') {
+			prev = b;
+			continue;
+		}
+
+		if (!cwd_gotten) {
+			cwd = getcwd(NULL, MAXPATHLEN);
+			cwd_gotten = 1;
+		}
+
+		if (cwd) {
+			/* Relative manpath with cwd: make absolute */
+			if (asprintf(&p, "%s/%s", cwd, b->path) == -1)
+				err(1, "asprintf");
+			free(b->path);
+			b->path = p;
+		} else {
+			/* Relative manpath but no cwd: omit path entry */
+			if (prev)
+				prev->next = b->next;
+			else
+				*manp_head = b->next;
+
+			free_manp(b);
+		}
+	}
+	free(cwd);
+}
+
+/*
+ * Free a man_node structure and its contents
+ */
+static void
+free_manp(struct man_node *manp)
+{
+	char	**p;
+
+	free(manp->path);
+	p = manp->secv;
+	while ((p != NULL) && (*p != NULL)) {
+		free(*p);
+		p++;
+	}
+	free(manp->secv);
+	free(manp);
+}
+
+
+/*
+ * Map (in place) to lower case.
+ */
+static void
+lower(char *s)
+{
+
+	if (s == 0)
+		return;
+	while (*s) {
+		if (isupper(*s))
+			*s = tolower(*s);
+		s++;
+	}
+}
+
+
+/*
+ * Compare function for qsort().
+ * Sort first by section, then by prefix.
+ */
+static int
+cmp(const void *arg1, const void *arg2)
+{
+	int	n;
+	char	**p1 = (char **)arg1;
+	char	**p2 = (char **)arg2;
+
+	/* By section */
+	if ((n = strcmp(*p1 + 3, *p2 + 3)) != 0)
+		return (n);
+
+	/* By prefix reversed */
+	return (strncmp(*p2, *p1, 3));
+}
+
+
+/*
+ * Find a manpage.
+ */
+static int
+manual(struct man_node *manp, char *name)
+{
+	struct man_node *p;
+	struct man_node *local;
+	int		ndirs = 0;
+	char		*ldir;
+	char		*ldirs[2];
+	char		*fullname = name;
+	char		*slash;
+
+	if ((slash = strrchr(name, '/')) != NULL)
+		name = slash + 1;
+
+	/* For each path in MANPATH */
+	found = 0;
+
+	for (p = manp; p != NULL; p = p->next) {
+		DPRINTF("-- Searching mandir: %s\n", p->path);
+
+		if (*localedir != '\0') {
+			ldir = addlocale(p->path);
+			ndirs = getdirs(ldir, NULL, 0);
+			if (ndirs != 0) {
+				ldirs[0] = ldir;
+				ldirs[1] = NULL;
+				local = build_manpath(ldirs, 0);
+				DPRINTF("-- Locale specific subdir: %s\n",
+				    ldir);
+				mandir(local->secv, ldir, name, 1);
+				free_manp(local);
+			}
+			free(ldir);
+		}
+
+		/*
+		 * Locale mandir not valid, man page in locale
+		 * mandir not found, or -a option present
+		 */
+		if (ndirs == 0 || !found || all)
+			mandir(p->secv, p->path, name, 0);
+
+		if (found && !all)
+			break;
+	}
+
+	if (!found) {
+		if (sargs) {
+			(void) fprintf(stderr, gettext(
+			    "No manual entry for %s in section(s) %s\n"),
+			    fullname, mansec);
+		} else {
+			(void) fprintf(stderr,
+			    gettext("No manual entry for %s\n"), fullname);
+		}
+
+	}
+
+	return (!found);
+}
+
+
+/*
+ * For a specified manual directory, read, store and sort section subdirs.
+ * For each section specified, find and search matching subdirs.
+ */
+static void
+mandir(char **secv, char *path, char *name, int lspec)
+{
+	DIR	*dp;
+	char	**dirv;
+	char	**dv, **pdv;
+	int	len, dslen;
+
+	if ((dp = opendir(path)) == NULL)
+		return;
+
+	if (lspec)
+		DPRINTF("-- Searching mandir: %s\n", path);
+
+	sortdir(dp, &dirv);
+
+	/* Search in the order specified by MANSECTS */
+	for (; *secv; secv++) {
+		len = strlen(*secv);
+		for (dv = dirv; *dv; dv++) {
+			dslen = strlen(*dv + 3);
+			if (dslen > len)
+				len = dslen;
+			if (**secv == '\\') {
+				if (strcmp(*secv + 1, *dv + 3) != 0)
+					continue;
+			} else if (strncasecmp(*secv, *dv + 3, len) != 0) {
+				if (!all &&
+				    (newsection = map_section(*secv, path))
+				    == NULL) {
+					continue;
+				}
+				if (newsection == NULL)
+					newsection = "";
+				if (strncmp(newsection, *dv + 3, len) != 0) {
+					continue;
+				}
+			}
+
+			if (searchdir(path, *dv, name) == 0)
+				continue;
+
+			if (!all) {
+				pdv = dirv;
+				while (*pdv) {
+					free(*pdv);
+					pdv++;
+				}
+				(void) closedir(dp);
+				free(dirv);
+				return;
+			}
+
+			if (all && **dv == 'm' && *(dv + 1) &&
+			    strcmp(*(dv + 1) + 3, *dv + 3) == 0)
+					dv++;
+		}
+	}
+	pdv = dirv;
+	while (*pdv != NULL) {
+		free(*pdv);
+		pdv++;
+	}
+	free(dirv);
+	(void) closedir(dp);
+}
+
+/*
+ * Sort directories.
+ */
+static void
+sortdir(DIR *dp, char ***dirv)
+{
+	struct dirent	*d;
+	char		**dv;
+	int		maxentries = MAXDIRS;
+	int		entries = 0;
+
+	if ((dv = *dirv = malloc(sizeof (char *) *
+	    maxentries)) == NULL)
+		err(1, "malloc");
+	dv = *dirv;
+
+	while ((d = readdir(dp))) {
+		if (strcmp(d->d_name, ".") == 0 ||
+		    strcmp(d->d_name, "..") == 0)
+			continue;
+
+		if (strncmp(d->d_name, "man", 3) == 0 ||
+		    strncmp(d->d_name, "cat", 3) == 0) {
+			if ((*dv = strdup(d->d_name)) == NULL)
+				err(1, "strdup");
+			dv++;
+			entries++;
+			if (entries == maxentries) {
+				maxentries += MAXDIRS;
+				if ((*dirv = realloc(*dirv,
+				    sizeof (char *) * maxentries)) == NULL)
+					err(1, "realloc");
+				dv = *dirv + entries;
+			}
+		}
+	}
+	*dv = 0;
+
+	qsort((void *)*dirv, dv - *dirv, sizeof (char *), cmp);
+
+}
+
+
+/*
+ * Search a section subdir for a given manpage.
+ */
+static int
+searchdir(char *path, char *dir, char *name)
+{
+	DIR		*sdp;
+	struct dirent	*sd;
+	char		sectpath[MAXPATHLEN];
+	char		file[MAXNAMLEN];
+	char		dname[MAXPATHLEN];
+	char		*last;
+	int		nlen;
+
+	(void) snprintf(sectpath, sizeof (sectpath), "%s/%s", path, dir);
+	(void) snprintf(file, sizeof (file), "%s.", name);
+
+	if ((sdp = opendir(sectpath)) == NULL)
+		return (0);
+
+	while ((sd = readdir(sdp))) {
+		char	*pname;
+
+		if ((pname = strdup(sd->d_name)) == NULL)
+			err(1, "strdup");
+		if ((last = strrchr(pname, '.')) != NULL &&
+		    (strcmp(last, ".gz") == 0 || strcmp(last, ".bz2") == 0))
+			*last = '\0';
+		last = strrchr(pname, '.');
+		nlen = last - pname;
+		(void) snprintf(dname, sizeof (dname), "%.*s.", nlen, pname);
+		if (strcmp(dname, file) == 0 ||
+		    strcmp(pname, name) == 0) {
+			(void) format(path, dir, name, sd->d_name);
+			(void) closedir(sdp);
+			free(pname);
+			return (1);
+		}
+		free(pname);
+	}
+	(void) closedir(sdp);
+
+	return (0);
+}
+
+/*
+ * Check the hash table of old directory names to see if there is a
+ * new directory name.
+ */
+static char *
+map_section(char *section, char *path)
+{
+	int	i;
+	char	fullpath[MAXPATHLEN];
+
+	if (list)  /* -l option fall through */
+		return (NULL);
+
+	for (i = 0; map[i].new_name != NULL; i++) {
+		if (strcmp(section, map[i].old_name) == 0) {
+			(void) snprintf(fullpath, sizeof (fullpath),
+			    "%s/man%s", path, map[i].new_name);
+			if (!access(fullpath, R_OK | X_OK)) {
+				return (map[i].new_name);
+			} else {
+				return (NULL);
+			}
+		}
+	}
+
+	return (NULL);
+}
+
+/*
+ * Format the manpage.
+ */
+static int
+format(char *path, char *dir, char *name, char *pg)
+{
+	char		manpname[MAXPATHLEN], catpname[MAXPATHLEN];
+	char		cmdbuf[BUFSIZ], tmpbuf[BUFSIZ];
+	char		*cattool;
+	int		utf8 = 0;
+	struct stat	sbman, sbcat;
+
+	found++;
+
+	if (list) {
+		(void) printf(gettext("%s(%s)\t-M %s\n"), name, dir + 3, path);
+		return (-1);
+	}
+
+	(void) snprintf(manpname, sizeof (manpname), "%s/man%s/%s", path,
+	    dir + 3, pg);
+	(void) snprintf(catpname, sizeof (catpname), "%s/cat%s/%s", path,
+	    dir + 3, pg);
+
+	/* Can't do PS output if manpage doesn't exist */
+	if (stat(manpname, &sbman) != 0 && (psoutput|lintout))
+		return (-1);
+
+	/*
+	 * If both manpage and catpage do not exist, manpname is
+	 * broken symlink, most likely.
+	 */
+	if (stat(catpname, &sbcat) != 0 && stat(manpname, &sbman) != 0)
+		err(1, "%s", manpname);
+
+	/* Setup cattool */
+	if (fnmatch("*.gz", manpname, 0) == 0)
+		cattool = "gzcat";
+	else if (fnmatch("*.bz2", manpname, 0) == 0)
+		cattool = "bzcat";
+	else
+		cattool = "cat";
+
+	/* Preprocess UTF-8 input with preconv (could be smarter) */
+	if (strstr(path, "UTF-8") != NULL)
+		utf8 = 1;
+
+	if (psoutput) {
+		(void) snprintf(cmdbuf, BUFSIZ,
+		    "cd %s; %s %s%s | mandoc -Tps | lp -Tpostscript",
+		    path, cattool, manpname,
+		    utf8 ? " | " PRECONV " -e UTF-8" : "");
+		DPRINTF("-- Using manpage: %s\n", manpname);
+		goto cmd;
+	} else if (lintout) {
+		(void) snprintf(cmdbuf, BUFSIZ,
+		    "cd %s; %s %s%s | mandoc -Tlint",
+		    path, cattool, manpname,
+		    utf8 ? " | " PRECONV " -e UTF-8" : "");
+		DPRINTF("-- Linting manpage: %s\n", manpname);
+		goto cmd;
+	}
+
+	/*
+	 * Output catpage if:
+	 * - manpage doesn't exist
+	 * - output width is standard and catpage is recent enough
+	 */
+	if (stat(manpname, &sbman) != 0 || (manwidth == 0 &&
+	    stat(catpname, &sbcat) == 0 && sbcat.st_mtime >= sbman.st_mtime)) {
+		DPRINTF("-- Using catpage: %s\n", catpname);
+		(void) snprintf(cmdbuf, BUFSIZ, "%s %s", pager, catpname);
+		goto cmd;
+	}
+
+	DPRINTF("-- Using manpage: %s\n", manpname);
+	if (manwidth > 0)
+		(void) snprintf(tmpbuf, BUFSIZ, "-Owidth=%d ", manwidth);
+	(void) snprintf(cmdbuf, BUFSIZ, "cd %s; %s %s%s | mandoc -T%s %s| %s",
+	    path, cattool, manpname,
+	    utf8 ? " | " PRECONV " -e UTF-8 " : "",
+	    utf8 ? "utf8" : "ascii", (manwidth > 0) ? tmpbuf : "", pager);
+
+cmd:
+	DPRINTF("-- Command: %s\n", cmdbuf);
+
+	if (!debug)
+		return (system(cmdbuf) == 0);
+	else
+		return (0);
+}
+
+/*
+ * Add <localedir> to the path.
+ */
+static char *
+addlocale(char *path)
+{
+	char	*tmp;
+
+	if (asprintf(&tmp, "%s/%s", path, localedir) == -1)
+		err(1, "asprintf");
+
+	return (tmp);
+}
+
+/*
+ * Get the order of sections from man.cf.
+ */
+static char *
+check_config(char *path)
+{
+	FILE		*fp;
+	char		*rc = NULL;
+	char		*sect;
+	char		fname[MAXPATHLEN];
+	char		*line = NULL;
+	size_t		linecap = 0;
+
+	(void) snprintf(fname, MAXPATHLEN, "%s/%s", path, CONFIG);
+
+	if ((fp = fopen(fname, "r")) == NULL)
+		return (NULL);
+
+	while (getline(&line, &linecap, fp) > 0) {
+		if ((rc = strstr(line, "MANSECTS")) != NULL)
+			break;
+	}
+
+	(void) fclose(fp);
+
+	if (rc == NULL || (sect = strchr(line, '=')) == NULL)
+		return (NULL);
+	else
+		return (++sect);
+}
+
+
+/*
+ * Initialize the bintoman array with appropriate device and inode info.
+ */
+static void
+init_bintoman(void)
+{
+	int i;
+	struct stat sb;
+
+	for (i = 0; bintoman[i].bindir != NULL; i++) {
+		if (stat(bintoman[i].bindir, &sb) == 0) {
+			bintoman[i].dev = sb.st_dev;
+			bintoman[i].ino = sb.st_ino;
+		} else {
+			bintoman[i].dev = NODEV;
+		}
+	}
+}
+
+/*
+ * If a duplicate is found, return 1.
+ * If a duplicate is not found, add it to the dupnode list and return 0.
+ */
+static int
+dupcheck(struct man_node *mnp, struct dupnode **dnp)
+{
+	struct dupnode	*curdnp;
+	struct secnode	*cursnp;
+	struct stat 	sb;
+	int 		i;
+	int		rv = 1;
+	int		dupfound;
+
+	/* If the path doesn't exist, treat it as a duplicate */
+	if (stat(mnp->path, &sb) != 0)
+		return (1);
+
+	/* If no sections were found in the man dir, treat it as duplicate */
+	if (mnp->secv == NULL)
+		return (1);
+
+	/*
+	 * Find the dupnode structure for the previous time this directory
+	 * was looked at.  Device and inode numbers are compared so that
+	 * directories that are reached via different paths (e.g. /usr/man and
+	 * /usr/share/man) are treated as equivalent.
+	 */
+	for (curdnp = *dnp; curdnp != NULL; curdnp = curdnp->next) {
+		if (curdnp->dev == sb.st_dev && curdnp->ino == sb.st_ino)
+			break;
+	}
+
+	/*
+	 * First time this directory has been seen. Add a new node to the
+	 * head of the list. Since all entries are guaranteed to be unique
+	 * copy all sections to new node.
+	 */
+	if (curdnp == NULL) {
+		if ((curdnp = calloc(1, sizeof (struct dupnode))) == NULL)
+			err(1, "calloc");
+		for (i = 0; mnp->secv[i] != NULL; i++) {
+			if ((cursnp = calloc(1, sizeof (struct secnode)))
+			    == NULL)
+				err(1, "calloc");
+			cursnp->next = curdnp->secl;
+			curdnp->secl = cursnp;
+			if ((cursnp->secp = strdup(mnp->secv[i])) == NULL)
+				err(1, "strdup");
+		}
+		curdnp->dev = sb.st_dev;
+		curdnp->ino = sb.st_ino;
+		curdnp->next = *dnp;
+		*dnp = curdnp;
+		return (0);
+	}
+
+	/*
+	 * Traverse the section vector in the man_node and the section list
+	 * in dupnode cache to eliminate all duplicates from man_node.
+	 */
+	for (i = 0; mnp->secv[i] != NULL; i++) {
+		dupfound = 0;
+		for (cursnp = curdnp->secl; cursnp != NULL;
+		    cursnp = cursnp->next) {
+			if (strcmp(mnp->secv[i], cursnp->secp) == 0) {
+				dupfound = 1;
+				break;
+			}
+		}
+		if (dupfound) {
+			mnp->secv[i][0] = '\0';
+			continue;
+		}
+
+
+		/*
+		 * Update curdnp and set return value to indicate that this
+		 * was not all duplicates.
+		 */
+		if ((cursnp = calloc(1, sizeof (struct secnode))) == NULL)
+			err(1, "calloc");
+		cursnp->next = curdnp->secl;
+		curdnp->secl = cursnp;
+		if ((cursnp->secp = strdup(mnp->secv[i])) == NULL)
+			err(1, "strdup");
+		rv = 0;
+	}
+
+	return (rv);
+}
+
+/*
+ * Given a bindir, return corresponding mandir.
+ */
+static char *
+path_to_manpath(char *bindir)
+{
+	char		*mand, *p;
+	int		i;
+	struct stat	sb;
+
+	/* First look for known translations for specific bin paths */
+	if (stat(bindir, &sb) != 0) {
+		return (NULL);
+	}
+	for (i = 0; bintoman[i].bindir != NULL; i++) {
+		if (sb.st_dev == bintoman[i].dev &&
+		    sb.st_ino == bintoman[i].ino) {
+			if ((mand = strdup(bintoman[i].mandir)) == NULL)
+				err(1, "strdup");
+			if ((p = strchr(mand, ',')) != NULL)
+				*p = '\0';
+			if (stat(mand, &sb) != 0) {
+				free(mand);
+				return (NULL);
+			}
+			if (p != NULL)
+				*p = ',';
+			return (mand);
+		}
+	}
+
+	/*
+	 * No specific translation found.  Try `dirname $bindir`/share/man
+	 * and `dirname $bindir`/man
+	 */
+	if ((mand = malloc(MAXPATHLEN)) == NULL)
+		err(1, "malloc");
+	if (strlcpy(mand, bindir, MAXPATHLEN) >= MAXPATHLEN) {
+		free(mand);
+		return (NULL);
+	}
+
+	/*
+	 * Advance to end of buffer, strip trailing /'s then remove last
+	 * directory component.
+	 */
+	for (p = mand; *p != '\0'; p++)
+		;
+	for (; p > mand && *p == '/'; p--)
+		;
+	for (; p > mand && *p != '/'; p--)
+		;
+	if (p == mand && *p == '.') {
+		if (realpath("..", mand) == NULL) {
+			free(mand);
+			return (NULL);
+		}
+		for (; *p != '\0'; p++)
+			;
+	} else {
+		*p = '\0';
+	}
+
+	if (strlcat(mand, "/share/man", MAXPATHLEN) >= MAXPATHLEN) {
+		free(mand);
+		return (NULL);
+	}
+
+	if ((stat(mand, &sb) == 0) && S_ISDIR(sb.st_mode)) {
+		return (mand);
+	}
+
+	/*
+	 * Strip the /share/man off and try /man
+	 */
+	*p = '\0';
+	if (strlcat(mand, "/man", MAXPATHLEN) >= MAXPATHLEN) {
+		free(mand);
+		return (NULL);
+	}
+	if ((stat(mand, &sb) == 0) && S_ISDIR(sb.st_mode)) {
+		return (mand);
+	}
+
+	/*
+	 * No man or share/man directory found
+	 */
+	free(mand);
+	return (NULL);
+}
+
+/*
+ * Free a linked list of dupnode structs.
+ */
+void
+free_dupnode(struct dupnode *dnp) {
+	struct dupnode *dnp2;
+	struct secnode *snp;
+
+	while (dnp != NULL) {
+		dnp2 = dnp;
+		dnp = dnp->next;
+		while (dnp2->secl != NULL) {
+			snp = dnp2->secl;
+			dnp2->secl = dnp2->secl->next;
+			free(snp->secp);
+			free(snp);
+		}
+		free(dnp2);
+	}
+}
+
+/*
+ * Print manp linked list to stdout.
+ */
+void
+print_manpath(struct man_node *manp)
+{
+	char	colon[2] = "\0\0";
+	char	**secp;
+
+	for (; manp != NULL; manp = manp->next) {
+		(void) printf("%s%s", colon, manp->path);
+		colon[0] = ':';
+
+		/*
+		 * If man.cf or a directory scan was used to create section
+		 * list, do not print section list again.  If the output of
+		 * man -p is used to set MANPATH, subsequent runs of man
+		 * will re-read man.cf and/or scan man directories as
+		 * required.
+		 */
+		if (manp->defsrch != 0)
+			continue;
+
+		for (secp = manp->secv; *secp != NULL; secp++) {
+			/*
+			 * Section deduplication may have eliminated some
+			 * sections from the vector. Avoid displaying this
+			 * detail which would appear as ",," in output
+			 */
+			if ((*secp)[0] != '\0')
+				(void) printf(",%s", *secp);
+		}
+	}
+	(void) printf("\n");
+}
+
+static void
+usage_man(void)
+{
+
+	(void) fprintf(stderr, gettext(
+"usage: man [-alptw] [-M path] [-s section] name ...\n"
+"       man [-M path] [-s section] -k keyword ...\n"
+"       man [-M path] [-s section] -f keyword ...\n"));
+
+	exit(1);
+}
+
+static void
+usage_whatapro(void)
+{
+
+	(void) fprintf(stderr, gettext(
+"usage: %s [-M path] [-s section] keyword ...\n"),
+	    whatis ? "whatis" : "apropos");
+
+	exit(1);
+}
+
+static void
+usage_catman(void)
+{
+	(void) fprintf(stderr, gettext(
+"usage: catman [-M path] [-w]\n"));
+
+	exit(1);
+}
+
+static void
+usage_makewhatis(void)
+{
+	(void) fprintf(stderr, gettext("usage: makewhatis\n"));
+
+	exit(1);
+}
diff --git a/usr/src/cmd/man/man.h b/usr/src/cmd/man/man.h
new file mode 100644
index 0000000..e6803a7
--- /dev/null
+++ b/usr/src/cmd/man/man.h
@@ -0,0 +1,40 @@
+/*
+ * This file and its contents are supplied under the terms of the
+ * Common Development and Distribution License ("CDDL"), version 1.0.
+ * You may only use this file in accordance with the terms of version
+ * 1.0 of the CDDL.
+ *
+ * A full copy of the text of the CDDL should have accompanied this
+ * source.  A copy of the CDDL is also available via the Internet at
+ * http://www.illumos.org/license/CDDL.
+ */
+
+/*
+ * Copyright 2012 Nexenta Systems, Inc. All rights reserved.
+ * Copyright 2014 Garrett D'Amore <garrett@damore.org>
+ */
+
+/*
+ * Common definitions
+ */
+
+#ifndef _MAN_H_
+#define	_MAN_H_
+
+#define	CONFIG		"man.cf"
+#define	DEFMANDIR	"/usr/share/man"
+#define	INDENT		24
+#define	PAGER		"less -ins"
+#define	WHATIS		"whatis"
+#define	PRECONV		"/usr/lib/mandoc_preconv"
+
+#define	LINE_ALLOC	4096
+#define	MAXDIRS		128
+#define	MAXTOKENS	64
+
+#define	DPRINTF		if (debug) \
+				(void) printf
+
+void	mwpath(char *path);
+
+#endif	/* _MAN_H_ */
diff --git a/usr/src/cmd/man/src/Makefile b/usr/src/cmd/man/src/Makefile
deleted file mode 100644
index 32135e8..0000000
--- a/usr/src/cmd/man/src/Makefile
+++ /dev/null
@@ -1,88 +0,0 @@
-#
-# CDDL HEADER START
-#
-# The contents of this file are subject to the terms of the
-# Common Development and Distribution License (the "License").
-# You may not use this file except in compliance with the License.
-#
-# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
-# or http://www.opensolaris.org/os/licensing.
-# See the License for the specific language governing permissions
-# and limitations under the License.
-#
-# When distributing Covered Code, include this CDDL HEADER in each
-# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
-# If applicable, add the following below this CDDL HEADER, with the
-# fields enclosed by brackets "[]" replaced with your own identifying
-# information: Portions Copyright [yyyy] [name of copyright owner]
-#
-# CDDL HEADER END
-#
-# Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
-# Use is subject to license terms.
-#
-# Makefile for cmd/man/src
-#
-
-include		../../Makefile.cmd
-
-BINPROG =	man
-LIBPROG =	getNAME
-LIBSHELL =	makewhatis
-LNKPROG =	catman apropos whatis
-
-PROG =		$(BINPROG) $(LIBPROG)
-
-SRCS =		man.c getNAME.c
-
-SHSRCS =	makewhatis.sh
-
-OBJS =		$(SRCS:%.c=%.o)
-
-
-CLOBBERFILES +=	$(LNKPROG) $(LIBSHELL)
-
-ROOTPROG =	$(BINPROG:%=$(ROOTBIN)/%)
-ROOTLIBPROG =	$(LIBPROG:%=$(ROOTLIB)/%)
-ROOTLIBSHELL =	$(LIBSHELL:%=$(ROOTLIB)/%)
-ROOTLNKPROG =	$(LNKPROG:%=$(ROOTBIN)/%)
-
-CERRWARN += -_gcc=-Wno-implicit-function-declaration
-CERRWARN += -_gcc=-Wno-uninitialized
-CERRWARN += -_gcc=-Wno-unused-variable
-
-# for messaging catalog files
-POFILE= src.po
-POFILES= $(SRCS:%.c=%.po)
-
-.KEEP_STATE :
-
-all :		$(PROG) $(LIBSHELL) $(LNKPROG)
-
-makewhatis :	$$@.sh
-		cat $@.sh > $@
-
-install :	all $(ROOTPROG) $(ROOTLIBPROG)	\
-		$(ROOTLIBSHELL) $(ROOTLNKPROG)
-
-$(ROOTLNKPROG) :	$(ROOTBIN)/man
-		-$(RM) $@; $(LN) $(ROOTBIN)/man $@
-
-$(LNKPROG) :	man
-		-$(RM) $@; $(LN) man $@
-
-catalog:        $(POFILE)
-
-$(POFILE):      $(POFILES)
-	$(RM)	$@
-	cat     $(POFILES)      > $@
-
-
-clean :
-
-strip :
-		$(STRIP) $(PROG)
-
-lint : 		lint_SRCS
-
-include		../../Makefile.targ
diff --git a/usr/src/cmd/man/src/THIRDPARTYLICENSE b/usr/src/cmd/man/src/THIRDPARTYLICENSE
deleted file mode 100644
index 4dedd9d..0000000
--- a/usr/src/cmd/man/src/THIRDPARTYLICENSE
+++ /dev/null
@@ -1,32 +0,0 @@
-Copyright (c) 1980 Regents of the University of California.  
-All rights reserved.
-
-Redistribution and use in source and binary forms, with or without
-modification, are permitted provided that the following conditions are
-met:
-
-    1. Redistributions of source code must retain the above copyright
-       notice, this list of conditions and the following disclaimer.
-    2. Redistributions in binary form must reproduce the above
-       copyright notice, this list of conditions and the following
-       disclaimer in the documentation and/or other materials provided
-       with the distribution.
-    3. All advertising materials mentioning features or use of this
-       software must display the following acknowledgement:
-           This product includes software developed by the University
-           of California, Berkeley and its contributors.
-    4. Neither the name of the University nor the names of its
-       contributors may be used to endorse or promote products derived
-       from this software without specific prior written permission.
-
-THIS SOFTWARE IS PROVIDED BY THE REGENTS 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 COPYRIGHT OWNER 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.
diff --git a/usr/src/cmd/man/src/getNAME.c b/usr/src/cmd/man/src/getNAME.c
deleted file mode 100644
index 77d03dd..0000000
--- a/usr/src/cmd/man/src/getNAME.c
+++ /dev/null
@@ -1,789 +0,0 @@
-/*
- * Copyright 2005 Sun Microsystems, Inc.  All rights reserved.
- * Use is subject to license terms.
- */
-
-/*	Copyright 1984, 1986, 1987, 1988, 1989 AT&T */
-/*	All Rights Reserved   */
-
-/*
- * Copyright 1980 Regents of the University of California.
- * All rights reserved.  The Berkeley software License Agreement
- * specifies the terms and conditions for redistribution.
- */
-
-#pragma ident	"%Z%%M%	%I%	%E% SMI"
-
-/*
- * Get name sections from manual pages.
- *	-t	for building toc
- *	-i	for building intro entries
- *	other	apropos database
- */
-
-#include <stdlib.h>
-#include <stdio.h>
-#include <stdarg.h>
-#include <string.h>
-#include <unistd.h>
-#include <limits.h>
-#include <locale.h>
-#include <wchar.h>
-#include <errno.h>
-#include <sys/param.h>
-
-#define	PLEN	3	/* prefix length "man" */
-
-static char path[MAXPATHLEN+1];
-static int tocrc;
-static int intro;
-static char *progname;
-
-static void trimln(char *);
-static void roff_trim(char *cp);
-static void doname(char *);
-static void section(char *, char *);
-static void split(char *, char *);
-static void dorefname(char *);
-static void troffpage(char *);
-static void sgmlpage(char *);
-
-/*
- * Test to see if this is an SGML manpage or a regular manpage
- * Unless the first line begins with <!DOCTYPE, we assume it isn't.
- */
-static int
-issgml(FILE *fp)
-{
-	static const char magic[] = "<!DOCTYPE";
-	char buf[sizeof (magic)];
-	size_t n = sizeof (magic) - 1;
-
-	if (read(fileno(fp), buf, n) != n ||
-	    lseek(fileno(fp), 0, SEEK_SET) != 0)
-		return (0);
-	return (strncmp(magic, buf, n) == 0);
-}
-
-int
-main(int argc, char *argv[])
-{
-	int c;
-
-	(void) setlocale(LC_ALL, "");
-
-	progname = argv[0];
-
-	while ((c = getopt(argc, argv, "it")) != EOF)
-		switch (c) {
-		case 't':
-			tocrc++;
-			break;
-		case 'i':
-			intro++;
-			break;
-		case '?':
-		default:
-			(void) fprintf(stderr,
-			    "usage: %s [-i][-t] files..\n", progname);
-			exit(1);
-		}
-
-	if (getcwd(path, sizeof (path)) == NULL) {
-		(void) fprintf(stderr, "%s: getcwd: %s\n", progname, path);
-		exit(1);
-	}
-
-	for (; optind < argc; optind++) {
-		char *name = argv[optind];
-
-		if (freopen(name, "r", stdin) == 0) {
-			(void) fprintf(stderr,
-			    "%s: %s: %s\n", progname, name, strerror(errno));
-			continue;
-		}
-
-		/*
-		 * Most of the info we care about is in the first kbyte
-		 */
-		(void) setvbuf(stdin, NULL, _IOFBF, 1024);
-
-		if (issgml(stdin))
-			sgmlpage(name);
-		else
-			troffpage(name);
-	}
-
-	return (0);
-}
-
-/*
- * Parse a troff-format manpage
- */
-static void
-troffpage(char *name)
-{
-	char headbuf[BUFSIZ];
-	char linbuf[BUFSIZ];
-	char *strptr;
-	int i = 0;
-
-	for (;;) {
-		if (fgets(headbuf, sizeof (headbuf), stdin) == NULL)
-			return;
-		if (headbuf[0] != '.')
-			continue;
-		if (headbuf[1] == 'T' && headbuf[2] == 'H')
-			break;
-		if (headbuf[1] == 't' && headbuf[2] == 'h')
-			break;
-	}
-	for (;;) {
-		if (fgets(linbuf, sizeof (linbuf), stdin) == NULL)
-			return;
-		if (linbuf[0] != '.')
-			continue;
-		if (linbuf[1] == 'S' && linbuf[2] == 'H')
-			break;
-		if (linbuf[1] == 's' && linbuf[2] == 'h')
-			break;
-	}
-	trimln(headbuf);
-	if (tocrc)
-		doname(name);
-	if (!intro)
-		section(name, headbuf);
-	for (;;) {
-		if (fgets(linbuf, sizeof (linbuf), stdin) == NULL)
-			break;
-		if (linbuf[0] == '.') {
-			if (linbuf[1] == 'S' && linbuf[2] == 'H')
-				break;
-			if (linbuf[1] == 's' && linbuf[2] == 'h')
-				break;
-			if (linbuf[1] == '\\' && linbuf[2] == '"')
-				continue;
-		}
-		trimln(linbuf);
-		roff_trim(linbuf);
-		if (intro) {
-			split(linbuf, name);
-			continue;
-		}
-		if (i != 0)
-			(void) printf(" ");
-		i++;
-		(void) printf("%s", linbuf);
-	}
-	(void) printf("\n");
-}
-
-
-/*
- * Substitute section defined in page with new section spec
- * of the form xx/yy where xx is the section suffix of the
- * directory and yy is the filename extension (unless xx
- * and yy are equal, in which case xx is the section).
- * Pages should be placed in their proper directory with the
- * proper name to simplify things.
- *
- * For example take the following names:
- *    man1/ar.1v	(1/1V)
- *    man1/find.1	(1)
- *    man1/loco		(1/)
- *
- */
-static void
-section(char *name, char *buf)
-{
-	char scratch[MAXPATHLEN+1];
-	char *p = buf;
-	char *dir, *fname;
-	char *dp, *np;
-	int i;
-	int plen = PLEN;
-
-	/*
-	 * split dirname and filename
-	 */
-	(void) strcpy(scratch, name);
-	if ((fname = strrchr(scratch, '/')) == NULL) {
-		fname = name;
-		dir = path;
-	} else {
-		dir = scratch;
-		*fname = 0;
-		fname++;
-	}
-	dp = strrchr(dir, '/');
-
-	if (*(dp+1) == 's')
-		plen = PLEN + 1;
-
-	if (dp != NULL) {
-		dp = dp+plen+1;
-	} else {
-		dp = dir+plen;
-	}
-	np = strrchr(fname, '.');
-	if (np != NULL) {
-		++np;
-	} else {
-		np = "";
-	}
-	for (i = 0; i < 2; i++) {
-		while (*p && *p != ' ' && *p != '\t')
-			p++;
-		if (!*p)
-			break;
-		while (*p && (*p == ' ' || *p == '\t'))
-			p++;
-		if (!*p)
-			break;
-	}
-	*p++ = 0;
-	(void) printf("%s", buf);
-	if (strcmp(np, dp) == 0)
-		(void) printf("%s", dp);
-	else
-		(void) printf("%s/%s", dp, np);
-	while (*p && *p != ' ' && *p != '\t')
-		p++;
-	(void) printf("%s\t", p);
-}
-
-static void
-trimln(char *cp)
-{
-	while (*cp)
-		cp++;
-	if (*--cp == '\n')
-		*cp = 0;
-}
-
-static void
-roff_trim(char *cp)
-{
-	if (*cp == '.') {
-		while ((*cp != ' ') && (*cp != '\0')) {
-			strcpy(cp, cp+1);
-		}
-		strcpy(cp, cp+1);
-	}
-	while (*cp) {
-		if (strncmp(cp, "\\f", 2) == 0) {
-			if ((*(cp+2) >= 48) && (*(cp+2) <= 57)) {
-				strcpy(cp, cp+3);
-			}
-			if (*(cp+2) == '(') {
-				strcpy(cp, cp+5);
-			}
-		}
-		cp++;
-	}
-}
-
-static void
-doname(char *name)
-{
-	char *dp = name, *ep;
-
-again:
-	while (*dp && *dp != '.')
-		(void) putchar(*dp++);
-	if (*dp)
-		for (ep = dp+1; *ep; ep++)
-			if (*ep == '.') {
-				(void) putchar(*dp++);
-				goto again;
-			}
-	(void) putchar('(');
-	if (*dp)
-		dp++;
-	while (*dp)
-		(void) putchar(*dp++);
-	(void) putchar(')');
-	(void) putchar(' ');
-}
-
-static void
-split(char *line, char *name)
-{
-	char *cp, *dp;
-	char *sp, *sep;
-
-	cp = strchr(line, '-');
-	if (cp == 0)
-		return;
-	sp = cp + 1;
-	for (--cp; *cp == ' ' || *cp == '\t' || *cp == '\\'; cp--)
-		;
-	*++cp = '\0';
-	while (*sp && (*sp == ' ' || *sp == '\t'))
-		sp++;
-	for (sep = "", dp = line; dp && *dp; dp = cp, sep = "\n") {
-		cp = strchr(dp, ',');
-		if (cp) {
-			char *tp;
-
-			for (tp = cp - 1; *tp == ' ' || *tp == '\t'; tp--)
-				;
-			*++tp = '\0';
-			for (++cp; *cp == ' ' || *cp == '\t'; cp++)
-				;
-		}
-		(void) printf("%s%s\t", sep, dp);
-		dorefname(name);
-		(void) printf("\t%s", sp);
-	}
-}
-
-static void
-dorefname(char *name)
-{
-	char *dp = name, *ep;
-
-again:
-	while (*dp && *dp != '.')
-		(void) putchar(*dp++);
-	if (*dp)
-		for (ep = dp+1; *ep; ep++)
-			if (*ep == '.') {
-				(void) putchar(*dp++);
-				goto again;
-			}
-	(void) putchar('.');
-	if (*dp)
-		dp++;
-	while (*dp)
-		(void) putchar(*dp++);
-}
-
-/*
- * The rest of the routines in the file form a simplistic parser
- * for SGML manpages.  We assume the input is syntactically correct
- * SGML, and that the fields occur in the input file in order.
- */
-
-/*
- * Some utilities for constructing arbitrary length wide character strings
- */
-
-typedef struct {
-	wchar_t *str;
-	size_t size;
-	long index;
-} string_t;
-
-#define	DEF_STR_SIZE	16
-#define	DEF_STR_GROWTH	16
-
-static void
-outofspace(char *where)
-{
-	(void) fprintf(stderr, "%s: '%s' - out of memory\n", progname, where);
-	exit(1);
-}
-
-static string_t *
-newstring(size_t initial)
-{
-	string_t *s = malloc(sizeof (*s));
-
-	if (s == NULL)
-		outofspace("new s");
-
-	initial *= sizeof (wchar_t);
-	if (initial < DEF_STR_SIZE)
-		initial = DEF_STR_SIZE;
-
-	s->str = malloc(initial);
-	if (s->str == NULL)
-		outofspace("new str");
-
-	s->size = initial;
-	s->index = 0;
-	*s->str = L'\0';
-	return (s);
-}
-
-static void
-delstring(string_t **s)
-{
-	free((*s)->str);
-	(*s)->str = NULL;
-	free(*s);
-	*s = NULL;
-}
-
-static wchar_t *
-getwstring(string_t *s)
-{
-	static const wchar_t wnull = L'\0';
-
-	if (s)
-		return (s->str);
-	return ((wchar_t *)&wnull);
-}
-
-static char *
-getcstring(string_t *s)
-{
-	size_t len = (wcslen(s->str) + 1) * MB_CUR_MAX;
-	char *cstr = malloc(len);
-	char *p = cstr;
-	wchar_t *wp = s->str;
-
-	if (p == NULL)
-		outofspace("getc");
-	while (*wp)
-		p += wctomb(p, *wp++);
-	*p = '\0';
-	return (cstr);
-}
-
-static void
-appendwstring(string_t *s, const wchar_t *str)
-{
-	size_t len = wcslen(str) + 1;
-
-	s->size += sizeof (wchar_t) * len;
-	s->str = realloc(s->str, s->size);
-	if (s->str == NULL)
-		outofspace("appendw");
-	(void) wcscat(s->str, str);
-	s->index = wcslen(s->str) + 1;
-}
-
-static void
-putwstring(string_t *s, wchar_t wc)
-{
-	if ((s->index + 1) * sizeof (wchar_t) >= s->size) {
-		s->size += DEF_STR_GROWTH;
-		s->str = realloc(s->str, s->size);
-		if (s->str == NULL)
-			outofspace("put");
-	}
-	s->str[s->index++] = wc;
-}
-
-/*
- * Find the closing > of an SGML comment block
- * (allowing for multibyte, embedded, comments)
- */
-static void
-eatcomments(void)
-{
-	int pending = 1;
-
-	while (pending)
-		switch (getwchar()) {
-		default:
-			break;
-		case L'<':
-			pending++;
-			break;
-		case L'>':
-			pending--;
-			break;
-		case WEOF:
-			return;
-		}
-}
-
-/*
- * Find the next token on stdin.
- * Handles nested comment strings, and removes any trailing newlines
- * from the stream after the closing '>'.
- */
-static int
-find_token(char *tokbuf, size_t tokbuflen)
-{
-	int c;
-	wint_t wc;
-	char *tokp;
-
-top:
-	while ((wc = getwchar()) != WEOF)
-		if (wc == L'<')
-			break;
-
-	if (wc == WEOF && errno == EILSEQ)
-		return (0);
-
-	switch (c = getchar()) {
-	case EOF:
-		return (0);
-	default:
-		(void) ungetc(c, stdin);
-		break;
-	case '!':
-		eatcomments();
-		goto top;
-	}
-
-	tokp = tokbuf;
-
-	while ((c = getchar()) != EOF) {
-		if (c == '>') {
-			while ((c = getchar()) != EOF)
-				if (c != '\n') {
-					(void) ungetc(c, stdin);
-					break;
-				}
-			*tokp = '\0';
-			return (1);
-		}
-		if (tokp - tokbuf < tokbuflen)
-			*tokp++ = (char)c;
-	}
-
-	return (0);
-}
-
-/*
- * This structure is filled out during the parsing of each page we encounter
- */
-typedef struct {
-	char *name;
-	string_t *title;
-	string_t *volnum;
-	string_t *date;
-	string_t *names;
-	string_t *purpose;
-} manpage_t;
-
-static void
-warning(manpage_t *m, const char *fmt, ...)
-{
-	va_list ap;
-	va_start(ap, fmt);
-	(void) fprintf(stderr, "%s: %s - ", progname, m->name);
-	(void) vfprintf(stderr, fmt, ap);
-	va_end(ap);
-}
-
-/*
- * Fetch a string from stdin, terminated by the endtoken.
- * These strings may be localized, so do this with wide characters.
- * Hack: skip over (completely ignore) all other tokens
- * Hack: map all &blort; constructs to spaces.
- */
-static string_t *
-filestring(manpage_t *m, size_t initial, char *endtoken)
-{
-	char tokbuf[BUFSIZ * MB_LEN_MAX];
-	string_t *s = newstring(initial);
-	wint_t wc;
-
-	while ((wc = getwchar()) != WEOF)
-		switch (wc) {
-		case L'\n':
-			if ((wc = getwchar()) != WEOF)
-				(void) ungetwc(wc, stdin);
-			if (wc != L'<')
-				putwstring(s, L' ');
-			break;
-		case L'<':
-			(void) ungetwc(wc, stdin);
-			if (!find_token(tokbuf, sizeof (tokbuf)) ||
-			    strcasecmp(endtoken, tokbuf) == 0)
-				goto done;
-			break;
-		case L'&':
-			while ((wc = getwchar()) != WEOF)
-				if (wc == L';')
-					break;
-			wc = L' ';
-			/* FALLTHROUGH */
-		default:
-			putwstring(s, wc);
-			break;
-		}
-
-	if (errno == EILSEQ)
-		warning(m, "%s while parsing %s\n", strerror(errno), endtoken);
-done:
-	putwstring(s, L'\0');
-	return (s);
-}
-
-/*
- * <refentrytitle> TITLE </refentrytitle>
- */
-static int
-refentrytitle(manpage_t *m)
-{
-	if (m->title != NULL)
-		warning(m, "repeated refentrytitle\n");
-	m->title = filestring(m, 8, "/refentrytitle");
-	return (1);
-}
-
-/*
- * <manvolnum> MANVOLNUM </manvolnum>
- */
-static int
-manvolnum(manpage_t *m)
-{
-	if (m->volnum != NULL)
-		warning(m, "repeated manvolnum\n");
-	m->volnum = filestring(m, 3, "/manvolnum");
-	return (1);
-}
-
-/*
- * <refmiscinfo class="date"> DATE </refmiscinfo>
- */
-static int
-refmiscinfo_date(manpage_t *m)
-{
-	if (m->date != NULL)
-		warning(m, "repeated date\n");
-	m->date = filestring(m, 11, "/refmiscinfo");
-	return (1);
-}
-
-/*
- * .. </refmeta>
- */
-static int
-print_refmeta(manpage_t *m)
-{
-	char headbuf[BUFSIZ];
-
-	(void) snprintf(headbuf, sizeof (headbuf), ".TH %ws %ws \"%ws\"",
-	    getwstring(m->title), getwstring(m->volnum), getwstring(m->date));
-
-	trimln(headbuf);
-	if (tocrc)
-		doname(m->name);
-	if (!intro)
-		section(m->name, headbuf);
-
-	if (m->title)
-		delstring(&m->title);
-	if (m->volnum)
-		delstring(&m->volnum);
-	if (m->date)
-		delstring(&m->date);
-
-	return (1);
-}
-
-static int
-appendname(manpage_t *m, char *term)
-{
-	string_t *r = filestring(m, 0, term);
-
-	if (m->names) {
-		appendwstring(m->names, L", ");
-		appendwstring(m->names, getwstring(r));
-		delstring(&r);
-	} else
-		m->names = r;
-	return (1);
-}
-
-/*
- * <refdescriptor> REFDESCRIPTOR </refdescriptor>
- */
-static int
-refdescriptor(manpage_t *m)
-{
-	return (appendname(m, "/refdescriptor"));
-}
-
-/*
- * <refname> REFNAME </refname>
- */
-static int
-refname(manpage_t *m)
-{
-	return (appendname(m, "/refname"));
-}
-
-/*
- * <refpurpose> PURPOSE </refpurpose>
- */
-static int
-refpurpose(manpage_t *m)
-{
-	if (m->purpose != NULL)
-		warning(m, "repeated refpurpose\n");
-	m->purpose = filestring(m, 0, "/refpurpose");
-	return (1);
-}
-
-/*
- * .. </refnamediv> - this is our chance to bail out.
- */
-static int
-terminate(manpage_t *m)
-{
-	if (m->names) {
-		appendwstring(m->names, L" \\- ");
-		appendwstring(m->names, getwstring(m->purpose));
-		if (intro) {
-			char *buf = getcstring(m->names);
-			split(buf, m->name);
-			free(buf);
-		} else
-			(void) printf("%ws", getwstring(m->names));
-	}
-
-	if (m->names)
-		delstring(&m->names);
-	if (m->purpose)
-		delstring(&m->purpose);
-
-	(void) printf("\n");
-	return (0);
-}
-
-
-/*
- * Basic control structure of the SGML "parser".
- * It's very simplistic - when named tags are encountered in the
- * input stream, control is transferred to the corresponding routine.
- * No checking is done for correct pairing of tags.  A few other hacks
- * are sneaked into the lexical routines above.
- * Output is generated after seeing the /refmeta and /refnamediv
- * closing tags.
- */
-static const struct {
-	char *name;
-	int (*action)(manpage_t *);
-} acts[] = {
-	{ "refentrytitle",		refentrytitle },
-	{ "manvolnum",			manvolnum },
-	{ "refmiscinfo class=\"date\"",	refmiscinfo_date },
-	{ "/refmeta",			print_refmeta },
-	{ "refdescriptor",		refdescriptor },
-	{ "refname",			refname },
-	{ "refpurpose",			refpurpose },
-	{ "/refnamediv",		terminate },
-	{ 0 }
-};
-
-static void
-sgmlpage(char *name)
-{
-	int rc = 1, a;
-	char tokbuf[BUFSIZ];
-	manpage_t manpage, *m = &manpage;
-
-	(void) memset(m, 0, sizeof (*m));
-	m->name = name;
-
-	do {
-		if (!find_token(tokbuf, sizeof (tokbuf)))
-			break;
-		for (a = 0; acts[a].name; a++) {
-			if (strcasecmp(acts[a].name, tokbuf) != 0)
-				continue;
-			rc = acts[a].action(m);
-			break;
-		}
-	} while (rc);
-}
diff --git a/usr/src/cmd/man/src/makewhatis.sh b/usr/src/cmd/man/src/makewhatis.sh
deleted file mode 100644
index da5fbf8..0000000
--- a/usr/src/cmd/man/src/makewhatis.sh
+++ /dev/null
@@ -1,85 +0,0 @@
-#!/bin/sh -
-#
-# CDDL HEADER START
-#
-# The contents of this file are subject to the terms of the
-# Common Development and Distribution License (the "License").
-# You may not use this file except in compliance with the License.
-#
-# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
-# or http://www.opensolaris.org/os/licensing.
-# See the License for the specific language governing permissions
-# and limitations under the License.
-#
-# When distributing Covered Code, include this CDDL HEADER in each
-# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
-# If applicable, add the following below this CDDL HEADER, with the
-# fields enclosed by brackets "[]" replaced with your own identifying
-# information: Portions Copyright [yyyy] [name of copyright owner]
-#
-# CDDL HEADER END
-#
-#
-# Copyright (c) 1990, 2010, Oracle and/or its affiliates. All rights reserved.
-#
-#	Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T
-#	  All Rights Reserved
-#
-
-# University Copyright- Copyright (c) 1982, 1986, 1988
-# The Regents of the University of California
-# All Rights Reserved
-#
-# University Acknowledgment- Portions of this document are derived from
-# software developed by the University of California, Berkeley, and its
-# contributors
-
-PATH=/usr/xpg4/bin:$PATH
-
-tmpdir=/tmp/whatis.$$
-trap "rm -rf $tmpdir; exit 1" 1 2 13 15
-
-mkdir -m 700 $tmpdir || {
-	echo "${0}: could not create temporary directory" 1&>2
-	exit 1
-}
-
-[ -d $1 ] || exit 1
-
-cd $1
-top=`pwd`
-for i in man?* sman?*
-do
-	if [ -d $i ] ; then
-		cd $i
-		if test "`echo *`" != "*" ; then
-			/usr/lib/getNAME *
-		fi
-		cd $top
-	fi
-done >$tmpdir/whatisx
-sed  <$tmpdir/whatisx \
-	-e 's/\\-/-/' \
-	-e 's/\\\*-/-/' \
-	-e 's/ VAX-11//' \
-	-e 's/\\f[PRIB01234]//g' \
-	-e 's/\\s[-+0-9]*//g' \
-	-e 's/\\&//g' \
-	-e '/ - /!d' \
-	-e 's/.TH [^ ]* \([^ 	]*\).*	\(.*\) -/\2 (\1)	 -/' \
-	-e 's/	 /	/g' | \
-awk '{	title = substr($0, 1, index($0, "- ") - 1)
-	synop = substr($0, index($0, "- "))
-	count = split(title, n, " ")
-	for (i=1; i<count; i++) {
-		if ( (pos = index(n[i], ",")) || (pos = index(n[i], ":")) )
-			n[i] = substr(n[i], 1, pos-1)
-		printf("%s\t%s %s\t%s\n", n[i], n[1], n[count], synop)
-	}
-}' >$tmpdir/whatis
-/usr/bin/expand -16,32,36,40,44,48,52,56,60,64,68,72,76,80,84,88,92,96,100 \
-	$tmpdir/whatis | LC_CTYPE=C LC_COLLATE=C sort -u | \
-	/usr/bin/unexpand -a > windex
-chmod 644 windex >/dev/null 2>&1
-rm -rf $tmpdir
-exit 0
diff --git a/usr/src/cmd/man/src/man.c b/usr/src/cmd/man/src/man.c
deleted file mode 100644
index f272eca..0000000
--- a/usr/src/cmd/man/src/man.c
+++ /dev/null
@@ -1,3336 +0,0 @@
-/*
- * CDDL HEADER START
- *
- * The contents of this file are subject to the terms of the
- * Common Development and Distribution License (the "License").
- * You may not use this file except in compliance with the License.
- *
- * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
- * or http://www.opensolaris.org/os/licensing.
- * See the License for the specific language governing permissions
- * and limitations under the License.
- *
- * When distributing Covered Code, include this CDDL HEADER in each
- * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
- * If applicable, add the following below this CDDL HEADER, with the
- * fields enclosed by brackets "[]" replaced with your own identifying
- * information: Portions Copyright [yyyy] [name of copyright owner]
- *
- * CDDL HEADER END
- */
-/*
- * Copyright (c) 1990, 2010, Oracle and/or its affiliates. All rights reserved.
- * Copyright 2012, Josef 'Jeff' Sipek <jeffpc@31bits.net>. All rights reserved.
- */
-
-/*	Copyright (c) 1983, 1984, 1985, 1986, 1987, 1988, 1989  AT&T.	*/
-/*		All rights reserved.					*/
-
-/*
- * University Copyright- Copyright (c) 1982, 1986, 1988
- * The Regents of the University of California
- * All Rights Reserved
- *
- * University Acknowledgment- Portions of this document are derived from
- * software developed by the University of California, Berkeley, and its
- * contributors.
- */
-
-
-/*
- * man
- * links to apropos, whatis, and catman
- * This version uses more for underlining and paging.
- */
-
-#include <stdio.h>
-#include <ctype.h>
-#include <sgtty.h>
-#include <sys/param.h>
-#include <sys/types.h>
-#include <sys/stat.h>
-#include <signal.h>
-#include <string.h>
-#include <malloc.h>
-#include <dirent.h>
-#include <errno.h>
-#include <fcntl.h>
-#include <locale.h>
-#include <stdlib.h>
-#include <unistd.h>
-#include <memory.h>
-#include <limits.h>
-#include <wchar.h>
-
-#define	MACROF 	"tmac.an"		/* name of <locale> macro file */
-#define	TMAC_AN	"-man"		/* default macro file */
-
-/*
- * The default search path for man subtrees.
- */
-
-#define	MANDIR		"/usr/share/man" 	/* default mandir */
-#define	MAKEWHATIS	"/usr/lib/makewhatis"
-#define	WHATIS		"windex"
-#define	TEMPLATE	"/tmp/mpXXXXXX"
-#define	CONFIG		"man.cf"
-
-/*
- * Names for formatting and display programs.  The values given
- * below are reasonable defaults, but sites with source may
- * wish to modify them to match the local environment.  The
- * value for TCAT is particularly problematic as there's no
- * accepted standard value available for it.  (The definition
- * below assumes C.A.T. troff output and prints it).
- */
-
-#define	MORE	"more -s" 		/* default paging filter */
-#define	CAT_S	"/usr/bin/cat -s"	/* for '-' opt (no more) */
-#define	CAT_	"/usr/bin/cat"		/* for when output is not a tty */
-#define	TROFF	"troff"			/* local name for troff */
-#define	TCAT	"lp -c -T troff"	/* command to "display" troff output */
-
-#define	SOLIMIT		10	/* maximum allowed .so chain length */
-#define	MAXDIRS		128	/* max # of subdirs per manpath */
-#define	MAXPAGES	128	/* max # for multiple pages */
-#define	PLEN		3	/* prefix length {man, cat, fmt} */
-#define	TMPLEN		7	/* length of tmpfile prefix */
-#define	MAXTOKENS 	64
-
-#define	DOT_SO		".so "
-#define	PREPROC_SPEC	"'\\\" "
-
-#define	DPRINTF		if (debug && !catmando) \
-				(void) printf
-
-#define	sys(s)		(debug ? ((void)puts(s), 0) : system(s))
-#define	eq(a, b)	(strcmp(a, b) == 0)
-#define	match(a, b, c)	(strncmp(a, b, c) == 0)
-
-#define	ISDIR(A)	((A.st_mode & S_IFMT) == S_IFDIR)
-
-#define	SROFF_CMD	"/usr/lib/sgml/sgml2roff" /* sgml converter */
-#define	MANDIRNAME	"man"			  /* man directory */
-#define	SGMLDIR		"sman"			  /* sman directory */
-#define	SGML_SYMBOL	"<!DOCTYPE"	/* a sgml file should contain this */
-#define	SGML_SYMBOL_LEN		9	/* length of SGML_SYMBOL */
-
-/*
- * Directory mapping of old directories to new directories
- */
-
-typedef struct {
-	char *old_name;
-	char *new_name;
-} map_entry;
-
-static const map_entry map[] = {
-					{ "3b", "3ucb" },
-					{ "3e", "3elf" },
-					{ "3g", "3gen" },
-					{ "3k", "3kstat" },
-					{ "3n", "3socket" },
-					{ "3r", "3rt" },
-					{ "3s", "3c" },
-					{ "3t", "3thr" },
-					{ "3x", "3curses" },
-					{ "3xc", "3xcurses" },
-					{ "3xn", "3xnet" }
-};
-
-/*
- * A list of known preprocessors to precede the formatter itself
- * in the formatting pipeline.  Preprocessors are specified by
- * starting a manual page with a line of the form:
- *	'\" X
- * where X is a string consisting of letters from the p_tag fields
- * below.
- */
-static const struct preprocessor {
-	char	p_tag;
-	char	*p_nroff,
-		*p_troff,
-		*p_stdin_char;
-} preprocessors [] = {
-	{'c',	"cw",				"cw",		"-"},
-	{'e',	"neqn /usr/share/lib/pub/eqnchar",
-			"eqn /usr/share/lib/pub/eqnchar",	"-"},
-	{'p',	"gpic",				"gpic",		"-"},
-	{'r',	"refer",			"refer",	"-"},
-	{'t',	"tbl",				"tbl",		""},
-	{'v',	"vgrind -f",			"vgrind -f",	"-"},
-	{0,	0,				0,		0}
-};
-
-struct suffix {
-	char *ds;
-	char *fs;
-};
-
-/*
- * Flags that control behavior of build_manpath()
- *
- *   BMP_ISPATH 	pathv is a vector constructed from PATH.
- *                	Perform appropriate path translations for
- * 			manpath.
- *   BMP_APPEND_MANDIR	Add /usr/share/man to the end if it
- *			hasn't already appeared earlier.
- *   BMP_FALLBACK_MANDIR Append /usr/share/man only if no other
- *			manpath (including derived from PATH)
- * 			elements are valid.
- */
-#define	BMP_ISPATH		1
-#define	BMP_APPEND_MANDIR	2
-#define	BMP_FALLBACK_MANDIR	4
-
-/*
- * When doing equality comparisons of directories, device and inode
- * comparisons are done.  The dupsec and dupnode structures are used
- * to form a list of lists for this processing.
- */
-struct secnode {
-	char		*secp;
-	struct secnode	*next;
-};
-struct dupnode {
-	dev_t		dev;	/* from struct stat st_dev */
-	ino_t		ino;	/* from struct stat st_ino */
-	struct secnode	*secl;	/* sections already considered */
-	struct dupnode	*next;
-};
-
-/*
- * Map directories that may appear in PATH to the corresponding
- * man directory
- */
-static struct pathmap {
-	char	*bindir;
-	char	*mandir;
-	dev_t	dev;
-	ino_t	ino;
-} bintoman[] = {
-	{"/sbin",		"/usr/share/man,1m",			0, 0},
-	{"/usr/sbin",		"/usr/share/man,1m",			0, 0},
-	{"/usr/ucb",		"/usr/share/man,1b",			0, 0},
-	{"/usr/bin/X11",	"/usr/X11/share/man",			0, 0},
-	/*
-	 * Restrict to section 1 so that whatis /usr/{,xpg4,xpg6}/bin/ls
-	 * does not confuse users with section 1 and 1b
-	 */
-	{"/usr/bin",		"/usr/share/man,1,1m,1s,1t,1c", 	0, 0},
-	{"/usr/xpg4/bin",	"/usr/share/man,1",			0, 0},
-	{"/usr/xpg6/bin",	"/usr/share/man,1",			0, 0},
-	{NULL,			NULL,					0, 0}
-};
-
-/*
- * Subdirectories to search for unformatted/formatted man page
- * versions, in nroff and troff variations.  The searching
- * code in manual() is structured to expect there to be two
- * subdirectories apiece, the first for unformatted files
- * and the second for formatted ones.
- */
-static char	*nroffdirs[] = { "man", "cat", 0 };
-static char	*troffdirs[] = { "man", "fmt", 0 };
-
-#define	MAN_USAGE "\
-usage:\tman [-] [-adFlprt] [-M path] [-T macro-package ] [ -s section ] \
-name ...\n\
-\tman [-M path] -k keyword ...\n\tman [-M path] -f file ..."
-#define	CATMAN_USAGE "\
-usage:\tcatman [-p] [-c|-ntw] [-M path] [-T macro-package ] [sections]"
-
-static char *opts[] = {
-	"FfkrpP:M:T:ts:lad",	/* man */
-	"wpnP:M:T:tc"		/* catman */
-};
-
-struct man_node {
-	char *path;		/* mandir path */
-	char **secv;		/* submandir suffices */
-	int  defsrch;		/* hint for man -p to avoid section list */
-	int  frompath;		/* hint for man -d and catman -p */
-	struct man_node *next;
-};
-
-static char	*pages[MAXPAGES];
-static char	**endp = pages;
-
-/*
- * flags (options)
- */
-static int	nomore;
-static int	troffit;
-static int	debug;
-static int	Tflag;
-static int	sargs;
-static int	margs;
-static int	force;
-static int	found;
-static int	list;
-static int	all;
-static int	whatis;
-static int	apropos;
-static int	catmando;
-static int	nowhatis;
-static int	whatonly;
-static int	compargs;	/* -c option for catman */
-static int	printmp;
-
-static char	*CAT	= CAT_;
-static char	macros[MAXPATHLEN];
-static char	*mansec;
-static char	*pager;
-static char	*troffcmd;
-static char	*troffcat;
-static char	**subdirs;
-
-static char *check_config(char *);
-static struct man_node *build_manpath(char **, int);
-static void getpath(struct man_node *, char **);
-static void getsect(struct man_node *, char **);
-static void get_all_sect(struct man_node *);
-static void catman(struct man_node *, char **, int);
-static int makecat(char *, char **, int);
-static int getdirs(char *, char ***, short);
-static void whatapro(struct man_node *, char *, int);
-static void lookup_windex(char *, char *, char **);
-static int icmp(wchar_t *, wchar_t *);
-static void more(char **, int);
-static void cleanup(char **);
-static void bye(int);
-static char **split(char *, char);
-static void freev(char **);
-static void fullpaths(struct man_node **);
-static void lower(char *);
-static int cmp(const void *, const void *);
-static int manual(struct man_node *, char *);
-static void mandir(char **, char *, char *);
-static void sortdir(DIR *, char ***);
-static int searchdir(char *, char *, char *);
-static int windex(char **, char *, char *);
-static void section(struct suffix *, char *);
-static int bfsearch(FILE *, char **, char *, char **);
-static int compare(char *, char *, char **);
-static int format(char *, char *, char *, char *);
-static char *addlocale(char *);
-static int get_manconfig(FILE *, char *);
-static void	malloc_error(void);
-static int	sgmlcheck(const char *);
-static char *map_section(char *, char *);
-static void free_manp(struct man_node *manp);
-static void init_bintoman(void);
-static char *path_to_manpath(char *);
-static int dupcheck(struct man_node *, struct dupnode **);
-static void free_dupnode(struct dupnode *);
-static void print_manpath(struct man_node *, char *);
-
-/*
- * This flag is used when the SGML-to-troff converter
- * is absent - all the SGML searches are bypassed.
- */
-static int no_sroff = 0;
-
-/*
- * This flag is used to describe the case where we've found
- * an SGML formatted manpage in the sman directory, we haven't
- * found a troff formatted manpage, and we don't have the SGML to troff
- * conversion utility on the system.
- */
-static int sman_no_man_no_sroff;
-
-static char language[PATH_MAX + 1]; 	/* LC_MESSAGES */
-static char localedir[PATH_MAX + 1];	/* locale specific path component */
-
-static int	defaultmandir = 1;	/* if processing default mandir, 1 */
-
-static char *newsection = NULL;
-
-int
-main(int argc, char *argv[])
-{
-	int badopts = 0;
-	int c;
-	char **pathv;
-	char *cmdname;
-	char *manpath = NULL;
-	static struct man_node	*manpage = NULL;
-	int bmp_flags = 0;
-	int err = 0;
-
-	if (access(SROFF_CMD, F_OK | X_OK) != 0)
-		no_sroff = 1;
-
-	(void) setlocale(LC_ALL, "");
-	(void) strcpy(language, setlocale(LC_MESSAGES, (char *)0));
-	if (strcmp("C", language) != 0)
-		(void) sprintf(localedir, "%s", language);
-
-#if !defined(TEXT_DOMAIN)
-#define	TEXT_DOMAIN "SYS_TEST"
-#endif
-	(void) textdomain(TEXT_DOMAIN);
-
-	(void) strcpy(macros, TMAC_AN);
-
-	/*
-	 * get base part of command name
-	 */
-	if ((cmdname = strrchr(argv[0], '/')) != NULL)
-		cmdname++;
-	else
-		cmdname = argv[0];
-
-	if (eq(cmdname, "apropos") || eq(cmdname, "whatis")) {
-		whatis++;
-		apropos = (*cmdname == 'a');
-		if ((optind = 1) == argc) {
-			(void) fprintf(stderr, gettext("%s what?\n"), cmdname);
-			exit(2);
-		}
-		goto doargs;
-	} else if (eq(cmdname, "catman"))
-		catmando++;
-
-	opterr = 0;
-	while ((c = getopt(argc, argv, opts[catmando])) != -1)
-		switch (c) {
-
-		/*
-		 * man specific options
-		 */
-		case 'k':
-			apropos++;
-			/*FALLTHROUGH*/
-		case 'f':
-			whatis++;
-			break;
-		case 'F':
-			force++;	/* do lookups the hard way */
-			break;
-		case 's':
-			mansec = optarg;
-			sargs++;
-			break;
-		case 'r':
-			nomore++, troffit++;
-			break;
-		case 'l':
-			list++;		/* implies all */
-			/*FALLTHROUGH*/
-		case 'a':
-			all++;
-			break;
-		case 'd':
-			debug++;
-			break;
-		/*
-		 * man and catman use -p differently.  In catman it
-		 * enables debug mode and in man it prints the (possibly
-		 * derived from PATH or name operand) MANPATH.
-		 */
-		case 'p':
-			if (catmando == 0) {
-				printmp++;
-			} else {
-				debug++;
-			}
-			break;
-		case 'n':
-			nowhatis++;
-			break;
-		case 'w':
-			whatonly++;
-			break;
-		case 'c':	/* n|troff compatibility */
-			if (no_sroff)
-				(void) fprintf(stderr, gettext(
-				    "catman: SGML conversion not "
-				    "available -- -c flag ignored\n"));
-			else
-				compargs++;
-			continue;
-
-		/*
-		 * shared options
-		 */
-		case 'P':	/* Backwards compatibility */
-		case 'M':	/* Respecify path for man pages. */
-			manpath = optarg;
-			margs++;
-			break;
-		case 'T':	/* Respecify man macros */
-			(void) strcpy(macros, optarg);
-			Tflag++;
-			break;
-		case 't':
-			troffit++;
-			break;
-		case '?':
-			badopts++;
-		}
-
-	/*
-	 *  Bad options or no args?
-	 *	(man -p and catman don't need args)
-	 */
-	if (badopts || (!catmando && !printmp && optind == argc)) {
-		(void) fprintf(stderr, "%s\n", catmando ?
-		    gettext(CATMAN_USAGE) : gettext(MAN_USAGE));
-		exit(2);
-	}
-
-	if (compargs && (nowhatis || whatonly || troffit)) {
-		(void) fprintf(stderr, "%s\n", gettext(CATMAN_USAGE));
-		(void) fprintf(stderr, gettext(
-		    "-c option cannot be used with [-w][-n][-t]\n"));
-		exit(2);
-	}
-
-	if (sargs && margs && catmando) {
-		(void) fprintf(stderr, "%s\n", gettext(CATMAN_USAGE));
-		exit(2);
-	}
-
-	if (troffit == 0 && nomore == 0 && !isatty(fileno(stdout)))
-		nomore++;
-
-	/*
-	 * Collect environment information.
-	 */
-	if (troffit) {
-		if ((troffcmd = getenv("TROFF")) == NULL)
-			troffcmd = TROFF;
-		if ((troffcat = getenv("TCAT")) == NULL)
-			troffcat = TCAT;
-	} else {
-		if (((pager = getenv("PAGER")) == NULL) ||
-		    (*pager == NULL))
-			pager = MORE;
-	}
-
-doargs:
-	subdirs = troffit ? troffdirs : nroffdirs;
-
-	init_bintoman();
-
-	if (manpath == NULL && (manpath = getenv("MANPATH")) == NULL) {
-		if ((manpath = getenv("PATH")) != NULL) {
-			bmp_flags = BMP_ISPATH | BMP_APPEND_MANDIR;
-		} else {
-			manpath = MANDIR;
-		}
-	}
-
-	pathv = split(manpath, ':');
-
-	manpage = build_manpath(pathv, bmp_flags);
-
-	/* release pathv allocated by split() */
-	freev(pathv);
-
-	/*
-	 * Since we can't make use of GNU troff, set the path to ensure we
-	 * find the one in /usr/bin first.
-	 */
-	if (putenv("PATH=/usr/bin") != 0) {
-		perror("putenv");
-		exit(1);
-	}
-
-	fullpaths(&manpage);
-
-	if (catmando) {
-		catman(manpage, argv+optind, argc-optind);
-		exit(0);
-	}
-
-	/*
-	 * The manual routine contains windows during which
-	 * termination would leave a temp file behind.  Thus
-	 * we blanket the whole thing with a clean-up routine.
-	 */
-	if (signal(SIGINT, SIG_IGN) == SIG_DFL) {
-		(void) signal(SIGINT, bye);
-		(void) signal(SIGQUIT, bye);
-		(void) signal(SIGTERM, bye);
-	}
-
-	/*
-	 * "man -p" without operands
-	 */
-	if ((printmp != 0) && (optind == argc)) {
-		print_manpath(manpage, NULL);
-		exit(0);
-	}
-
-	for (; optind < argc; optind++) {
-		if (strcmp(argv[optind], "-") == 0) {
-			nomore++;
-			CAT = CAT_S;
-		} else {
-			char *cmd;
-			static struct man_node *mp;
-			char *pv[2];
-
-			/*
-			 * If full path to command specified, customize
-			 * manpath accordingly
-			 */
-			if ((cmd = strrchr(argv[optind], '/')) != NULL) {
-				*cmd = '\0';
-				if ((pv[0] = strdup(argv[optind])) == NULL) {
-					malloc_error();
-				}
-				pv[1] = NULL;
-				*cmd = '/';
-				mp = build_manpath(pv,
-				    BMP_ISPATH|BMP_FALLBACK_MANDIR);
-			} else {
-				mp = manpage;
-			}
-
-			if (whatis) {
-				whatapro(mp, argv[optind], apropos);
-			} else if (printmp != 0) {
-				print_manpath(mp, argv[optind]);
-			} else {
-				err += manual(mp, argv[optind]);
-			}
-
-			if (mp != NULL && mp != manpage) {
-				free(pv[0]);
-				free_manp(mp);
-			}
-		}
-	}
-	return (err == 0 ? 0 : 1);
-	/*NOTREACHED*/
-}
-
-/*
- * This routine builds the manpage structure from MANPATH or PATH,
- * depending on flags.  See BMP_* definitions above for valid
- * flags.
- *
- * Assumes pathv elements were malloc'd, as done by split().
- * Elements may be freed and reallocated to have different contents.
- */
-
-static struct man_node *
-build_manpath(char **pathv, int flags)
-{
-	struct man_node *manpage = NULL;
-	struct man_node *currp = NULL;
-	struct man_node *lastp = NULL;
-	char **p;
-	char **q;
-	char *mand = NULL;
-	char *mandir = MANDIR;
-	int s;
-	struct dupnode *didup = NULL;
-	struct stat sb;
-
-	s = sizeof (struct man_node);
-	for (p = pathv; *p; ) {
-
-		if (flags & BMP_ISPATH) {
-			if ((mand = path_to_manpath(*p)) == NULL) {
-				goto next;
-			}
-			free(*p);
-			*p = mand;
-		}
-		q = split(*p, ',');
-		if (stat(q[0], &sb) != 0 || (sb.st_mode & S_IFDIR) == 0) {
-			freev(q);
-			goto next;
-		}
-
-		if (access(q[0], R_OK|X_OK) != 0) {
-			if (catmando) {
-				(void) fprintf(stderr,
-				    gettext("%s is not accessible.\n"),
-				    q[0]);
-				(void) fflush(stderr);
-			}
-		} else {
-
-			/*
-			 * Some element exists.  Do not append MANDIR as a
-			 * fallback.
-			 */
-			flags &= ~BMP_FALLBACK_MANDIR;
-
-			if ((currp = (struct man_node *)calloc(1, s)) == NULL) {
-				malloc_error();
-			}
-
-			currp->frompath = (flags & BMP_ISPATH);
-
-			if (manpage == NULL) {
-				lastp = manpage = currp;
-			}
-
-			getpath(currp, p);
-			getsect(currp, p);
-
-			/*
-			 * If there are no new elements in this path,
-			 * do not add it to the manpage list
-			 */
-			if (dupcheck(currp, &didup) != 0) {
-				freev(currp->secv);
-				free(currp);
-			} else {
-				currp->next = NULL;
-				if (currp != manpage) {
-					lastp->next = currp;
-				}
-				lastp = currp;
-			}
-		}
-		freev(q);
-next:
-		/*
-		 * Special handling of appending MANDIR.
-		 * After all pathv elements have been processed, append MANDIR
-		 * if needed.
-		 */
-		if (p == &mandir) {
-			break;
-		}
-		p++;
-		if (*p != NULL) {
-			continue;
-		}
-		if (flags & (BMP_APPEND_MANDIR|BMP_FALLBACK_MANDIR)) {
-			p = &mandir;
-			flags &= ~BMP_ISPATH;
-		}
-	}
-
-	free_dupnode(didup);
-
-	return (manpage);
-}
-
-/*
- * Stores the mandir path into the manp structure.
- */
-
-static void
-getpath(struct man_node *manp, char **pv)
-{
-	char *s;
-	int i = 0;
-
-	s = *pv;
-
-	while (*s != NULL && *s != ',')
-		i++, s++;
-
-	manp->path = (char *)malloc(i+1);
-	if (manp->path == NULL)
-		malloc_error();
-	(void) strncpy(manp->path, *pv, i);
-	*(manp->path + i) = '\0';
-}
-
-/*
- * Stores the mandir's corresponding sections (submandir
- * directories) into the manp structure.
- */
-
-static void
-getsect(struct man_node *manp, char **pv)
-{
-	char *sections;
-	char **sectp;
-
-	if (sargs) {
-		manp->secv = split(mansec, ',');
-
-		for (sectp = manp->secv; *sectp; sectp++)
-			lower(*sectp);
-	} else if ((sections = strchr(*pv, ',')) != NULL) {
-		if (debug) {
-			if (manp->frompath != 0) {
-/*
- * TRANSLATION_NOTE - message for man -d or catman -p
- * ex. /usr/share/man: derived from PATH, MANSECTS=,1b
- */
-				(void) printf(gettext(
-				    "%s: derived from PATH, MANSECTS=%s\n"),
-				    manp->path, sections);
-			} else {
-/*
- * TRANSLATION_NOTE - message for man -d or catman -p
- * ex. /usr/share/man: from -M option, MANSECTS=,1,2,3c
- */
-				(void) fprintf(stdout, gettext(
-				    "%s: from -M option, MANSECTS=%s\n"),
-				    manp->path, sections);
-			}
-		}
-		manp->secv = split(++sections, ',');
-		for (sectp = manp->secv; *sectp; sectp++)
-			lower(*sectp);
-
-		if (*manp->secv == NULL)
-			get_all_sect(manp);
-	} else if ((sections = check_config(*pv)) != NULL) {
-		manp->defsrch = 1;
-/*
- * TRANSLATION_NOTE - message for man -d or catman -p
- * ex. /usr/share/man: from man.cf, MANSECTS=1,1m,1c
- */
-		if (debug)
-			(void) fprintf(stdout, gettext(
-			    "%s: from %s, MANSECTS=%s\n"),
-			    manp->path, CONFIG, sections);
-		manp->secv = split(sections, ',');
-
-		for (sectp = manp->secv; *sectp; sectp++)
-			lower(*sectp);
-
-		if (*manp->secv == NULL)
-			get_all_sect(manp);
-	} else {
-		manp->defsrch = 1;
-/*
- * TRANSLATION_NOTE - message for man -d or catman -p
- * if man.cf has not been found or sections has not been specified
- * man/catman searches the sections lexicographically.
- */
-		if (debug)
-			(void) fprintf(stdout, gettext(
-			    "%s: search the sections lexicographically\n"),
-			    manp->path);
-		manp->secv = NULL;
-		get_all_sect(manp);
-	}
-}
-
-/*
- * Get suffices of all sub-mandir directories in a mandir.
- */
-
-static void
-get_all_sect(struct man_node *manp)
-{
-	DIR *dp;
-	char **dirv;
-	char **dv;
-	char **p;
-	char *prev = NULL;
-	char *tmp = NULL;
-	int  plen;
-	int	maxentries = MAXTOKENS;
-	int	entries = 0;
-
-	if ((dp = opendir(manp->path)) == 0)
-		return;
-
-	/*
-	 * sortdir() allocates memory for dirv and dirv[].
-	 */
-	sortdir(dp, &dirv);
-
-	(void) closedir(dp);
-
-	if (manp->secv == NULL) {
-		/*
-		 * allocates memory for manp->secv only if it's NULL
-		 */
-		manp->secv = (char **)malloc(maxentries * sizeof (char *));
-		if (manp->secv == NULL)
-			malloc_error();
-	}
-
-	for (dv = dirv, p = manp->secv; *dv; dv++) {
-		plen = PLEN;
-		if (match(*dv, SGMLDIR, PLEN+1))
-			++plen;
-
-		if (strcmp(*dv, CONFIG) == 0) {
-			/* release memory allocated by sortdir */
-			free(*dv);
-			continue;
-		}
-
-		if (tmp != NULL)
-			free(tmp);
-		tmp = strdup(*dv + plen);
-		if (tmp == NULL)
-			malloc_error();
-		(void) sprintf(tmp, "%s", *dv + plen);
-
-		if (prev != NULL) {
-			if (strcmp(prev, tmp) == 0) {
-				/* release memory allocated by sortdir */
-				free(*dv);
-				continue;
-			}
-		}
-
-		if (prev != NULL)
-			free(prev);
-		prev = strdup(*dv + plen);
-		if (prev == NULL)
-			malloc_error();
-		(void) sprintf(prev, "%s", *dv + plen);
-		/*
-		 * copy the string in (*dv + plen) to *p
-		 */
-		*p = strdup(*dv + plen);
-		if (*p == NULL)
-			malloc_error();
-		p++;
-		entries++;
-		if (entries == maxentries) {
-			maxentries += MAXTOKENS;
-			manp->secv = (char **)realloc(manp->secv,
-			    sizeof (char *) * maxentries);
-			if (manp->secv == NULL)
-				malloc_error();
-			p = manp->secv + entries;
-		}
-		/* release memory allocated by sortdir */
-		free(*dv);
-	}
-	*p = 0;
-	/* release memory allocated by sortdir */
-	free(dirv);
-}
-
-/*
- * Format man pages (build cat pages); if no
- * sections are specified, build all of them.
- * When building cat pages:
- *	catman() tries to build cat pages for locale specific
- *	man dirs first.  Then, catman() tries to build cat pages
- *	for the default man dir (for C locale like /usr/share/man)
- *	regardless of the locale.
- * When building windex file:
- *	catman() tries to build windex file for locale specific
- *	man dirs first.  Then, catman() tries to build windex file
- *	for the default man dir (for C locale like /usr/share/man)
- *	regardless of the locale.
- */
-
-static void
-catman(struct man_node *manp, char **argv, int argc)
-{
-	char cmdbuf[BUFSIZ];
-	char **dv;
-	int changed;
-	struct man_node *p;
-	int ndirs = 0;
-	char *ldir;
-	int	i;
-	struct dupnode *dnp = NULL;
-	char   **realsecv;
-	/*
-	 * May be overwritten in dupcheck() so must be kept out of .rodata.
-	 */
-	char	fakename[] = " catman ";
-	char	*fakesecv[2];
-
-	fakesecv[0] = fakename;
-	fakesecv[1] = NULL;
-
-	for (p = manp; p != NULL; p = p->next) {
-		/*
-		 * prevent catman from doing very heavy lifting multiple
-		 * times on some directory
-		 */
-		realsecv = p->secv;
-		p->secv = fakesecv;
-		if (dupcheck(p, &dnp) != 0) {
-			p->secv = realsecv;
-			continue;
-		}
-
-/*
- * TRANSLATION_NOTE - message for catman -p
- * ex. mandir path = /usr/share/man
- */
-		if (debug)
-			(void) fprintf(stdout, gettext(
-			    "\nmandir path = %s\n"), p->path);
-		ndirs = 0;
-
-		/*
-		 * Build cat pages
-		 * addlocale() allocates memory and returns it
-		 */
-		ldir = addlocale(p->path);
-		if (!whatonly) {
-			if (*localedir != '\0') {
-				if (defaultmandir)
-					defaultmandir = 0;
-				/* getdirs allocate memory for dv */
-				ndirs = getdirs(ldir, &dv, 1);
-				if (ndirs != 0) {
-					changed = argc ?
-					    makecat(ldir, argv, argc) :
-					    makecat(ldir, dv, ndirs);
-					/* release memory by getdirs */
-					for (i = 0; i < ndirs; i++) {
-						free(dv[i]);
-					}
-					free(dv);
-				}
-			}
-
-			/* default man dir is always processed */
-			defaultmandir = 1;
-			ndirs = getdirs(p->path, &dv, 1);
-			changed = argc ?
-			    makecat(p->path, argv, argc) :
-			    makecat(p->path, dv, ndirs);
-			/* release memory allocated by getdirs */
-			for (i = 0; i < ndirs; i++) {
-				free(dv[i]);
-			}
-			free(dv);
-		}
-		/*
-		 * Build whatis database
-		 *  print error message if locale is set and man dir not found
-		 *  won't build it at all if -c option is on
-		 */
-		if (!compargs && (whatonly || (!nowhatis && changed))) {
-			if (*localedir != '\0') {
-				/* just count the number of ndirs */
-				if ((ndirs = getdirs(ldir, NULL, 0)) != 0) {
-					(void) sprintf(cmdbuf,
-					    "/usr/bin/sh %s %s",
-					    MAKEWHATIS, ldir);
-					(void) sys(cmdbuf);
-				}
-			}
-			/* whatis database of the default man dir */
-			/* will be always built in C locale. */
-			(void) sprintf(cmdbuf,
-			    "/usr/bin/sh %s %s",
-			    MAKEWHATIS, p->path);
-			(void) sys(cmdbuf);
-		}
-		/* release memory allocated by addlocale() */
-		free(ldir);
-	}
-	free_dupnode(dnp);
-}
-
-/*
- * Build cat pages for given sections
- */
-
-static int
-makecat(char *path, char **dv, int ndirs)
-{
-	DIR *dp, *sdp;
-	struct dirent *d;
-	struct stat sbuf;
-	char mandir[MAXPATHLEN+1];
-	char smandir[MAXPATHLEN+1];
-	char catdir[MAXPATHLEN+1];
-	char *dirp, *sdirp;
-	int i, fmt;
-	int manflag, smanflag;
-
-	for (i = fmt = 0; i < ndirs; i++) {
-		(void) snprintf(mandir, MAXPATHLEN, "%s/%s%s",
-		    path, MANDIRNAME, dv[i]);
-		(void) snprintf(smandir, MAXPATHLEN, "%s/%s%s",
-		    path, SGMLDIR, dv[i]);
-		(void) snprintf(catdir, MAXPATHLEN, "%s/%s%s",
-		    path, subdirs[1], dv[i]);
-		dirp = strrchr(mandir, '/') + 1;
-		sdirp = strrchr(smandir, '/') + 1;
-
-		manflag = smanflag = 0;
-
-		if ((dp = opendir(mandir)) != NULL)
-			manflag = 1;
-
-		if (!no_sroff && (sdp = opendir(smandir)) != NULL)
-			smanflag = 1;
-
-		if (dp == 0 && sdp == 0) {
-			if (strcmp(mandir, CONFIG) == 0)
-				perror(mandir);
-			continue;
-		}
-/*
- * TRANSLATION_NOTE - message for catman -p
- * ex. Building cat pages for mandir = /usr/share/man/ja
- */
-		if (debug)
-			(void) fprintf(stdout, gettext(
-			    "Building cat pages for mandir = %s\n"), path);
-
-		if (!compargs && stat(catdir, &sbuf) < 0) {
-			(void) umask(02);
-/*
- * TRANSLATION_NOTE - message for catman -p
- * ex. mkdir /usr/share/man/ja/cat3c
- */
-			if (debug)
-				(void) fprintf(stdout, gettext("mkdir %s\n"),
-				    catdir);
-			else {
-				if (mkdir(catdir, 0755) < 0) {
-					perror(catdir);
-					continue;
-				}
-				(void) chmod(catdir, 0755);
-			}
-		}
-
-		/*
-		 * if it is -c option of catman, if there is no
-		 * coresponding man dir for sman files to go to,
-		 * make the man dir
-		 */
-
-		if (compargs && !manflag) {
-			if (mkdir(mandir, 0755) < 0) {
-				perror(mandir);
-				continue;
-			}
-			(void) chmod(mandir, 0755);
-		}
-
-		if (smanflag) {
-			while ((d = readdir(sdp))) {
-				if (eq(".", d->d_name) || eq("..", d->d_name))
-					continue;
-
-				if (format(path, sdirp, (char *)0, d->d_name)
-				    > 0)
-					fmt++;
-			}
-		}
-
-		if (manflag && !compargs) {
-			while ((d = readdir(dp))) {
-				if (eq(".", d->d_name) || eq("..", d->d_name))
-					continue;
-
-				if (format(path, dirp, (char *)0, d->d_name)
-				    > 0)
-					fmt++;
-			}
-		}
-
-		if (manflag)
-			(void) closedir(dp);
-
-		if (smanflag)
-			(void) closedir(sdp);
-
-	}
-	return (fmt);
-}
-
-
-/*
- * Get all "man" and "sman" dirs under a given manpath
- * and return the number found
- * If -c option is on, only count sman dirs
- */
-
-static int
-getdirs(char *path, char ***dirv, short flag)
-{
-	DIR *dp;
-	struct dirent *d;
-	int n = 0;
-	int plen, sgml_flag, man_flag;
-	int i = 0;
-	int	maxentries = MAXDIRS;
-	char	**dv;
-
-	if ((dp = opendir(path)) == 0) {
-		if (debug) {
-			if (*localedir != '\0')
-				(void) printf(gettext("\
-locale is %s, search in %s\n"), localedir, path);
-			perror(path);
-		}
-		return (0);
-	}
-
-	if (flag) {
-		/* allocate memory for dirv */
-		*dirv = (char **)malloc(sizeof (char *) *
-		    maxentries);
-		if (*dirv == NULL)
-			malloc_error();
-		dv = *dirv;
-	}
-	while ((d = readdir(dp))) {
-		plen = PLEN;
-		man_flag = sgml_flag = 0;
-		if (match(d->d_name, SGMLDIR, PLEN+1)) {
-			plen = PLEN + 1;
-			sgml_flag = 1;
-			i++;
-		}
-
-		if (match(subdirs[0], d->d_name, PLEN))
-			man_flag = 1;
-
-		if (compargs && sgml_flag) {
-			if (flag) {
-				*dv = strdup(d->d_name+plen);
-				if (*dv == NULL)
-					malloc_error();
-				dv++;
-				n = i;
-			}
-		} else if (!compargs && (sgml_flag || man_flag)) {
-			if (flag) {
-				*dv = strdup(d->d_name+plen);
-				if (*dv == NULL)
-					malloc_error();
-				dv++;
-			}
-			n++;
-		}
-		if (flag) {
-			if ((dv - *dirv) == maxentries) {
-				int entries = maxentries;
-				maxentries += MAXTOKENS;
-				*dirv = (char **)realloc(*dirv,
-				    sizeof (char *) * maxentries);
-				if (*dirv == NULL)
-					malloc_error();
-				dv = *dirv + entries;
-			}
-		}
-	}
-
-	(void) closedir(dp);
-	return (n);
-}
-
-
-/*
- * Find matching whatis or apropos entries
- * whatapro() tries to handle the windex file of the locale specific
- * man dirs first, then tries to handle the windex file of the default
- * man dir (of C locale like /usr/share/man).
- */
-
-static void
-whatapro(struct man_node *manp, char *word, int apropos)
-{
-	char whatpath[MAXPATHLEN+1];
-	char *p;
-	struct man_node *b;
-	int ndirs = 0;
-	char *ldir;
-
-
-/*
- * TRANSLATION_NOTE - message for man -d
- * %s takes a parameter to -k option.
- */
-	DPRINTF(gettext("word = %s \n"), word);
-
-	/*
-	 * get base part of name
-	 */
-	if (!apropos) {
-		if ((p = strrchr(word, '/')) == NULL)
-			p = word;
-		else
-			p++;
-	} else {
-		p = word;
-	}
-
-	for (b = manp; b != NULL; b = b->next) {
-
-		if (*localedir != '\0') {
-			/* addlocale() allocates memory and returns it */
-			ldir = addlocale(b->path);
-			if (defaultmandir)
-				defaultmandir = 0;
-			ndirs = getdirs(ldir, NULL, 0);
-			if (ndirs != 0) {
-				(void) sprintf(whatpath, "%s/%s", ldir, WHATIS);
-/*
- * TRANSLATION_NOTE - message for man -d
- * ex. mandir path = /usr/share/man/ja
- */
-				DPRINTF(gettext("\nmandir path = %s\n"), ldir);
-				lookup_windex(whatpath, p, b->secv);
-			}
-			/* release memory allocated by addlocale() */
-			free(ldir);
-		}
-
-		defaultmandir = 1;
-		(void) sprintf(whatpath, "%s/%s", b->path, WHATIS);
-/*
- * TRANSLATION_NOTE - message for man -d
- * ex. mandir path = /usr/share/man
- */
-		DPRINTF(gettext("\nmandir path = %s\n"), b->path);
-
-		lookup_windex(whatpath, p, b->secv);
-	}
-}
-
-
-static void
-lookup_windex(char *whatpath, char *word, char **secv)
-{
-	FILE *fp;
-	char *matches[MAXPAGES];
-	char **pp;
-	wchar_t	wbuf[BUFSIZ];
-	wchar_t *word_wchar = NULL;
-	wchar_t	*ws;
-	size_t	word_len, ret;
-
-	if ((fp = fopen(whatpath, "r")) == NULL) {
-		perror(whatpath);
-		return;
-	}
-
-	if (apropos) {
-		word_len = strlen(word) + 1;
-		if ((word_wchar = (wchar_t *)malloc(sizeof (wchar_t) *
-		    word_len)) == NULL) {
-			malloc_error();
-		}
-		ret = mbstowcs(word_wchar, (const char *)word, word_len);
-		if (ret == (size_t)-1) {
-			(void) fprintf(stderr, gettext(
-			    "Invalid character in keyword\n"));
-			exit(1);
-		}
-		while (fgetws(wbuf, BUFSIZ, fp) != NULL)
-			for (ws = wbuf; *ws; ws++)
-				if (icmp(word_wchar, ws) == 0) {
-					(void) printf("%ws", wbuf);
-					break;
-				}
-	} else {
-		if (bfsearch(fp, matches, word, secv))
-			for (pp = matches; *pp; pp++) {
-				(void) printf("%s", *pp);
-				/*
-				 * release memory allocated by
-				 * strdup() in bfsearch()
-				 */
-				free(*pp);
-			}
-	}
-	(void) fclose(fp);
-	if (word_wchar)
-		free(word_wchar);
-
-}
-
-
-/*
- * case-insensitive compare unless upper case is used
- * ie)	"mount" matches mount, Mount, MOUNT
- *	"Mount" matches Mount, MOUNT
- *	"MOUNT" matches MOUNT only
- *	If matched return 0.  Otherwise, return 1.
- */
-
-static int
-icmp(wchar_t *ws, wchar_t *wt)
-{
-	for (; (*ws == 0) ||
-	    (*ws == (iswupper(*ws) ? *wt: towlower(*wt)));
-	    ws++, wt++)
-		if (*ws == 0)
-			return (0);
-
-	return (1);
-}
-
-
-/*
- * Invoke PAGER with all matching man pages
- */
-
-static void
-more(char **pages, int plain)
-{
-	char cmdbuf[BUFSIZ];
-	char **vp;
-
-	/*
-	 * Dont bother.
-	 */
-	if (list || (*pages == 0))
-		return;
-
-	if (plain && troffit) {
-		cleanup(pages);
-		return;
-	}
-	(void) sprintf(cmdbuf, "%s", troffit ? troffcat :
-	    plain ? CAT : pager);
-
-	/*
-	 * Build arg list
-	 */
-	for (vp = pages; vp < endp; vp++) {
-		(void) strcat(cmdbuf, " ");
-		(void) strcat(cmdbuf, *vp);
-	}
-	(void) sys(cmdbuf);
-	cleanup(pages);
-}
-
-
-/*
- * Get rid of dregs.
- */
-
-static void
-cleanup(char **pages)
-{
-	char **vp;
-
-	for (vp = pages; vp < endp; vp++) {
-		if (match(TEMPLATE, *vp, TMPLEN))
-			(void) unlink(*vp);
-		free(*vp);
-	}
-
-	endp = pages;	/* reset */
-}
-
-
-/*
- * Clean things up after receiving a signal.
- */
-
-/*ARGSUSED*/
-static void
-bye(int sig)
-{
-	cleanup(pages);
-	exit(1);
-	/*NOTREACHED*/
-}
-
-
-/*
- * Split a string by specified separator.
- *    ignore empty components/adjacent separators.
- *    returns vector to all tokens
- */
-
-static char **
-split(char *s1, char sep)
-{
-	char **tokv, **vp;
-	char *mp, *tp;
-	int maxentries = MAXTOKENS;
-	int entries = 0;
-
-	tokv = vp = (char **)malloc(maxentries * sizeof (char *));
-	if (tokv == NULL)
-		malloc_error();
-	mp = s1;
-	for (; mp && *mp; mp = tp) {
-		tp = strchr(mp, sep);
-		if (mp == tp) {		/* empty component */
-			tp++;			/* ignore */
-			continue;
-		}
-		if (tp) {
-			/* a component found */
-			size_t	len;
-
-			len = tp - mp;
-			*vp = (char *)malloc(sizeof (char) * len + 1);
-			if (*vp == NULL)
-				malloc_error();
-			(void) strncpy(*vp, mp, len);
-			*(*vp + len) = '\0';
-			tp++;
-			vp++;
-		} else {
-			/* the last component */
-			*vp = strdup(mp);
-			if (*vp == NULL)
-				malloc_error();
-			vp++;
-		}
-		entries++;
-		if (entries == maxentries) {
-			maxentries += MAXTOKENS;
-			tokv = (char **)realloc(tokv,
-			    maxentries * sizeof (char *));
-			if (tokv == NULL)
-				malloc_error();
-			vp = tokv + entries;
-		}
-	}
-	*vp = 0;
-	return (tokv);
-}
-
-/*
- * Free a vector allocated by split();
- */
-static void
-freev(char **v)
-{
-	int i;
-	if (v != NULL) {
-		for (i = 0; v[i] != NULL; i++) {
-			free(v[i]);
-		}
-		free(v);
-	}
-}
-
-/*
- * Convert paths to full paths if necessary
- *
- */
-
-static void
-fullpaths(struct man_node **manp_head)
-{
-	char *cwd = NULL;
-	char *p;
-	char cwd_gotten = 0;
-	struct man_node *manp = *manp_head;
-	struct man_node *b;
-	struct man_node *prev = NULL;
-
-	for (b = manp; b != NULL; b = b->next) {
-		if (*(b->path) == '/') {
-			prev = b;
-			continue;
-		}
-
-		/* try to get cwd if haven't already */
-		if (!cwd_gotten) {
-			cwd = getcwd(NULL, MAXPATHLEN+1);
-			cwd_gotten = 1;
-		}
-
-		if (cwd) {
-			/* case: relative manpath with cwd: make absolute */
-			if ((p = malloc(strlen(b->path)+strlen(cwd)+2)) ==
-			    NULL) {
-				malloc_error();
-			}
-			(void) sprintf(p, "%s/%s", cwd, b->path);
-			/*
-			 * resetting b->path
-			 */
-			free(b->path);
-			b->path = p;
-		} else {
-			/* case: relative manpath but no cwd: omit path entry */
-			if (prev)
-				prev->next = b->next;
-			else
-				*manp_head = b->next;
-
-			free_manp(b);
-		}
-	}
-	/*
-	 * release memory allocated by getcwd()
-	 */
-	free(cwd);
-}
-
-/*
- * Free a man_node structure and its contents
- */
-
-static void
-free_manp(struct man_node *manp)
-{
-	char **p;
-
-	free(manp->path);
-	p = manp->secv;
-	while ((p != NULL) && (*p != NULL)) {
-		free(*p);
-		p++;
-	}
-	free(manp->secv);
-	free(manp);
-}
-
-
-/*
- * Map (in place) to lower case
- */
-
-static void
-lower(char *s)
-{
-	if (s == 0)
-		return;
-	while (*s) {
-		if (isupper(*s))
-			*s = tolower(*s);
-		s++;
-	}
-}
-
-
-/*
- * compare for sort()
- * sort first by section-spec, then by prefix {sman, man, cat, fmt}
- *	note: prefix is reverse sorted so that "sman" and "man" always
- * 	comes before {cat, fmt}
- */
-
-static int
-cmp(const void *arg1, const void *arg2)
-{
-	int n;
-	char **p1 = (char **)arg1;
-	char **p2 = (char **)arg2;
-
-
-	/* by section; sman always before man dirs */
-	if ((n = strcmp(*p1 + PLEN + (**p1 == 's' ? 1 : 0),
-	    *p2 + PLEN + (**p2 == 's' ? 1 : 0))))
-		return (n);
-
-	/* by prefix reversed */
-	return (strncmp(*p2, *p1, PLEN));
-}
-
-
-/*
- * Find a man page ...
- *   Loop through each path specified,
- *   first try the lookup method (whatis database),
- *   and if it doesn't exist, do the hard way.
- */
-
-static int
-manual(struct man_node *manp, char *name)
-{
-	struct man_node *p;
-	struct man_node *local;
-	int ndirs = 0;
-	char *ldir;
-	char *ldirs[2];
-	char *fullname = name;
-	char *slash;
-
-	if ((slash = strrchr(name, '/')) != NULL) {
-		name = slash + 1;
-	}
-
-	/*
-	 *  for each path in MANPATH
-	 */
-	found = 0;
-
-	for (p = manp; p != NULL; p = p->next) {
-/*
- * TRANSLATION_NOTE - message for man -d
- * ex. mandir path = /usr/share/man
- */
-		DPRINTF(gettext("\nmandir path = %s\n"), p->path);
-
-		if (*localedir != '\0') {
-			/* addlocale() allocates memory and returns it */
-			ldir = addlocale(p->path);
-			if (defaultmandir)
-				defaultmandir = 0;
-/*
- * TRANSLATION_NOTE - message for man -d
- * ex. localedir = ja, ldir = /usr/share/man/ja
- */
-			if (debug)
-				(void) printf(gettext(
-				    "localedir = %s, ldir = %s\n"),
-				    localedir, ldir);
-			ndirs = getdirs(ldir, NULL, 0);
-			if (ndirs != 0) {
-				ldirs[0] = ldir;
-				ldirs[1] = NULL;
-				local = build_manpath(ldirs, 0);
-				if (force ||
-				    windex(local->secv, ldir, name) < 0)
-					mandir(local->secv, ldir, name);
-				free_manp(local);
-			}
-			/* release memory allocated by addlocale() */
-			free(ldir);
-		}
-
-		defaultmandir = 1;
-		/*
-		 * locale mandir not valid, man page in locale
-		 * mandir not found, or -a option present
-		 */
-		if (ndirs == 0 || !found || all) {
-			if (force || windex(p->secv, p->path, name) < 0)
-				mandir(p->secv, p->path, name);
-		}
-
-		if (found && !all)
-			break;
-	}
-
-	if (found) {
-		more(pages, nomore);
-	} else {
-		if (sargs) {
-			(void) fprintf(stderr, gettext("No entry for %s in "
-			    "section(s) %s of the manual.\n"),
-			    fullname, mansec);
-		} else {
-			(void) fprintf(stderr, gettext(
-			    "No manual entry for %s.\n"), fullname, mansec);
-		}
-
-		if (sman_no_man_no_sroff)
-			(void) fprintf(stderr, gettext("(An SGML manpage was "
-			    "found for '%s' but it cannot be displayed.)\n"),
-			    fullname, mansec);
-	}
-	sman_no_man_no_sroff = 0;
-	return (!found);
-}
-
-
-/*
- * For a specified manual directory,
- *	read, store, & sort section subdirs,
- *	for each section specified
- *		find and search matching subdirs
- */
-
-static void
-mandir(char **secv, char *path, char *name)
-{
-	DIR *dp;
-	char **dirv;
-	char **dv, **pdv;
-	int len, dslen, plen = PLEN;
-
-	if ((dp = opendir(path)) == 0) {
-/*
- * TRANSLATION_NOTE - message for man -d or catman -p
- * opendir(%s) returned 0
- */
-		if (debug)
-			(void) fprintf(stdout, gettext(
-			    " opendir on %s failed\n"), path);
-		return;
-	}
-
-/*
- * TRANSLATION_NOTE - message for man -d or catman -p
- * ex. mandir path = /usr/share/man/ja
- */
-	if (debug)
-		(void) printf(gettext("mandir path = %s\n"), path);
-
-	/*
-	 * sordir() allocates memory for dirv and dirv[].
-	 */
-	sortdir(dp, &dirv);
-	/*
-	 * Search in the order specified by MANSECTS
-	 */
-	for (; *secv; secv++) {
-/*
- * TRANSLATION_NOTE - message for man -d or catman -p
- * ex.  section = 3c
- */
-		DPRINTF(gettext("  section = %s\n"), *secv);
-		len = strlen(*secv);
-		for (dv = dirv; *dv; dv++) {
-			plen = PLEN;
-			if (*dv[0] == 's')
-				plen++;
-			dslen = strlen(*dv+plen);
-			if (dslen > len)
-				len = dslen;
-			if (**secv == '\\') {
-				if (!eq(*secv + 1, *dv+plen))
-					continue;
-			} else if (strncasecmp(*secv, *dv+plen, len) != 0) {
-				/* check to see if directory name changed */
-				if (!all &&
-				    (newsection = map_section(*secv, path))
-				    == NULL) {
-					continue;
-				}
-				if (newsection == NULL)
-					newsection = "";
-				if (!match(newsection, *dv+plen, len)) {
-					continue;
-				}
-			}
-
-			if (searchdir(path, *dv, name) == 0)
-				continue;
-
-			if (!all) {
-				/* release memory allocated by sortdir() */
-				pdv = dirv;
-				while (*pdv) {
-					free(*pdv);
-					pdv++;
-				}
-				(void) closedir(dp);
-				/* release memory allocated by sortdir() */
-				free(dirv);
-				return;
-			}
-			/*
-			 * if we found a match in the man dir skip
-			 * the corresponding cat dir if it exists
-			 */
-			if (all && **dv == 'm' && *(dv+1) &&
-			    eq(*(dv+1)+plen, *dv+plen))
-					dv++;
-		}
-	}
-	/* release memory allocated by sortdir() */
-	pdv = dirv;
-	while (*pdv) {
-		free(*pdv);
-		pdv++;
-	}
-	free(dirv);
-	(void) closedir(dp);
-}
-
-/*
- * Sort directories.
- */
-
-static void
-sortdir(DIR *dp, char ***dirv)
-{
-	struct dirent *d;
-	char **dv;
-	int	maxentries = MAXDIRS;
-	int	entries = 0;
-
-	*dirv = (char **)malloc(sizeof (char *) * maxentries);
-	dv = *dirv;
-	while ((d = readdir(dp))) {	/* store dirs */
-		if (eq(d->d_name, ".") || eq(d->d_name, ".."))	/* ignore */
-			continue;
-
-		/* check if it matches sman, man, cat format */
-		if (match(d->d_name, SGMLDIR, PLEN+1) ||
-		    match(d->d_name, subdirs[0], PLEN) ||
-		    match(d->d_name, subdirs[1], PLEN)) {
-			*dv = malloc(strlen(d->d_name) + 1);
-			if (*dv == NULL)
-				malloc_error();
-			(void) strcpy(*dv, d->d_name);
-			dv++;
-			entries++;
-			if (entries == maxentries) {
-				maxentries += MAXDIRS;
-				*dirv = (char **)realloc(*dirv,
-				    sizeof (char *) * maxentries);
-				if (*dirv == NULL)
-					malloc_error();
-				dv = *dirv + entries;
-			}
-		}
-	}
-	*dv = 0;
-
-	qsort((void *)*dirv, dv - *dirv, sizeof (char *), cmp);
-
-}
-
-
-/*
- * Search a section subdirectory for a
- * given man page, return 1 for success
- */
-
-static int
-searchdir(char *path, char *dir, char *name)
-{
-	DIR *sdp;
-	struct dirent *sd;
-	char sectpath[MAXPATHLEN+1];
-	char file[MAXNAMLEN+1];
-	char dname[MAXPATHLEN+1];
-	char *last;
-	int nlen;
-
-/*
- * TRANSLATION_NOTE - message for man -d or catman -p
- * ex.   scanning = man3c
- */
-	DPRINTF(gettext("    scanning = %s\n"), dir);
-	(void) sprintf(sectpath, "%s/%s", path, dir);
-	(void) snprintf(file, MAXPATHLEN, "%s.", name);
-
-	if ((sdp = opendir(sectpath)) == 0) {
-		if (errno != ENOTDIR)	/* ignore matching cruft */
-			perror(sectpath);
-		return (0);
-	}
-	while ((sd = readdir(sdp))) {
-		last = strrchr(sd->d_name, '.');
-		nlen = last - sd->d_name;
-		(void) sprintf(dname, "%.*s.", nlen, sd->d_name);
-		if (eq(dname, file) || eq(sd->d_name, name)) {
-			if (no_sroff && *dir == 's') {
-				sman_no_man_no_sroff = 1;
-				return (0);
-			}
-			(void) format(path, dir, name, sd->d_name);
-			(void) closedir(sdp);
-			return (1);
-		}
-	}
-	(void) closedir(sdp);
-	return (0);
-}
-
-/*
- * Check the hash table of old directory names to see if there is a
- * new directory name.
- * Returns new directory name if a match; after checking to be sure
- * directory exists.
- * Otherwise returns NULL
- */
-
-static char *
-map_section(char *section, char *path)
-{
-	int i;
-	int len;
-	char fullpath[MAXPATHLEN];
-
-	if (list)  /* -l option fall through */
-		return (NULL);
-
-	for (i = 0; i <= ((sizeof (map)/sizeof (map[0]) - 1)); i++) {
-		if (strlen(section) > strlen(map[i].new_name)) {
-			len = strlen(section);
-		} else {
-			len = strlen(map[i].new_name);
-		}
-		if (match(section, map[i].old_name, len)) {
-			(void) sprintf(fullpath,
-			    "%s/sman%s", path, map[i].new_name);
-			if (!access(fullpath, R_OK | X_OK)) {
-				return (map[i].new_name);
-			} else {
-				return (NULL);
-			}
-		}
-	}
-
-	return (NULL);
-}
-
-
-/*
- * Use windex database for quick lookup of man pages
- * instead of mandir() (brute force search)
- */
-
-static int
-windex(char **secv, char *path, char *name)
-{
-	FILE *fp;
-	struct stat sbuf;
-	struct suffix *sp;
-	struct suffix	psecs[MAXPAGES+1];
-	char whatfile[MAXPATHLEN+1];
-	char page[MAXPATHLEN+1];
-	char *matches[MAXPAGES];
-	char *file, *dir;
-	char **sv, **vp;
-	int len, dslen, exist, i;
-	int	found_in_windex = 0;
-	char *tmp[] = {0, 0, 0, 0};
-
-
-	(void) sprintf(whatfile, "%s/%s", path, WHATIS);
-	if ((fp = fopen(whatfile, "r")) == NULL) {
-		if (errno == ENOENT)
-			return (-1);
-		return (0);
-	}
-
-/*
- * TRANSLATION_NOTE - message for man -d or catman -p
- * ex. search in = /usr/share/man/ja/windex file
- */
-	if (debug)
-		(void) fprintf(stdout, gettext(
-		    " search in = %s file\n"), whatfile);
-
-	if (bfsearch(fp, matches, name, NULL) == 0) {
-		(void) fclose(fp);
-		return (-1); /* force search in mandir */
-	}
-
-	(void) fclose(fp);
-
-	/*
-	 * Save and split sections
-	 * section() allocates memory for sp->ds
-	 */
-	for (sp = psecs, vp = matches; *vp; vp++, sp++) {
-		if ((sp - psecs) < MAXPAGES) {
-			section(sp, *vp);
-		} else {
-			if (debug)
-				(void) fprintf(stderr, gettext(
-				    "too many sections in %s windex entry\n"),
-				    name);
-
-			/* Setting sp->ds to NULL signifies end-of-data. */
-			sp->ds = 0;
-			goto finish;
-		}
-	}
-
-	sp->ds = 0;
-
-	/*
-	 * Search in the order specified
-	 * by MANSECTS
-	 */
-	for (; *secv; secv++) {
-		len = strlen(*secv);
-
-/*
- * TRANSLATION_NOTE - message for man -d or catman -p
- * ex.  search an entry to match printf.3c
- */
-		if (debug)
-			(void) fprintf(stdout, gettext(
-			    "  search an entry to match %s.%s\n"), name, *secv);
-		/*
-		 * For every whatis entry that
-		 * was matched
-		 */
-		for (sp = psecs; sp->ds; sp++) {
-			dslen = strlen(sp->ds);
-			if (dslen > len)
-				len = dslen;
-			if (**secv == '\\') {
-				if (!eq(*secv + 1, sp->ds))
-					continue;
-			} else if (!match(*secv, sp->ds, len)) {
-				/* check to see if directory name changed */
-				if (!all &&
-				    (newsection = map_section(*secv, path))
-				    == NULL) {
-					continue;
-				}
-				if (newsection == NULL)
-					newsection = "";
-				if (!match(newsection, sp->ds, len)) {
-					continue;
-				}
-			}
-			/*
-			 * here to form "sman", "man", "cat"|"fmt" in
-			 * order
-			 */
-			if (!no_sroff) {
-				tmp[0] = SGMLDIR;
-				for (i = 1; i < 4; i++)
-					tmp[i] = subdirs[i-1];
-			} else {
-				for (i = 0; i < 3; i++)
-					tmp[i] = subdirs[i];
-			}
-
-			for (sv = tmp; *sv; sv++) {
-				(void) sprintf(page,
-				    "%s/%s%s/%s%s%s", path, *sv,
-				    sp->ds, name, *sp->fs ? "." : "",
-				    sp->fs);
-				exist = (stat(page, &sbuf) == 0);
-				if (exist)
-					break;
-			}
-			if (!exist) {
-				(void) fprintf(stderr, gettext(
-				    "%s entry incorrect:  %s(%s) not found.\n"),
-				    WHATIS, name, sp->ds);
-				continue;
-			}
-
-			file = strrchr(page, '/'), *file = 0;
-			dir = strrchr(page, '/');
-
-			/*
-			 * By now we have a match
-			 */
-			found_in_windex = 1;
-			(void) format(path, ++dir, name, ++file);
-
-			if (!all)
-				goto finish;
-		}
-	}
-finish:
-	/*
-	 * release memory allocated by section()
-	 */
-	sp = psecs;
-	while (sp->ds) {
-		free(sp->ds);
-		sp->ds = NULL;
-		sp++;
-	}
-
-	/*
-	 * If we didn't find a match, return failure as if we didn't find
-	 * the windex at all. Why? Well, if you create a windex, then upgrade
-	 * to a later release that contains new man pages, and forget to
-	 * recreate the windex (since we don't do that automatically), you
-	 * won't see any new man pages since they aren't in the windex.
-	 * Pretending we didn't see a windex at all if there are no matches
-	 * forces a search of the underlying directory. After all, the
-	 * goal of the windex is to enable searches (man -k) and speed things
-	 * up, not to _prevent_ you from seeing new man pages, so this seems
-	 * ok. The only problem is when there are multiple entries (different
-	 * sections), and some are in and some are out. Say you do 'man ls',
-	 * and ls(1) isn't in the windex, but ls(1B) is. In that case, we
-	 * will find a match in ls(1B), and you'll see that man page.
-	 * That doesn't seem bad since if you specify the section the search
-	 * will be restricted too. So in the example above, if you do
-	 * 'man -s 1 ls' you'll get ls(1).
-	 */
-	if (found_in_windex)
-		return (0);
-	else
-		return (-1);
-}
-
-
-/*
- * Return pointers to the section-spec
- * and file-suffix of a whatis entry
- */
-
-static void
-section(struct suffix *sp, char *s)
-{
-	char *lp, *p;
-
-	lp = strchr(s, '(');
-	p = strchr(s, ')');
-
-	if (++lp == 0 || p == 0 || lp == p) {
-		(void) fprintf(stderr,
-		    gettext("mangled windex entry:\n\t%s\n"), s);
-		return;
-	}
-	*p = 0;
-
-	/*
-	 * copy the string pointed to by lp
-	 */
-	lp = strdup(lp);
-	if (lp == NULL)
-		malloc_error();
-	/*
-	 * release memory in s
-	 * s has been allocated memory in bfsearch()
-	 */
-	free(s);
-
-	lower(lp);
-
-	/*
-	 * split section-specifier if file-name
-	 * suffix differs from section-suffix
-	 */
-	sp->ds = lp;
-	if ((p = strchr(lp, '/'))) {
-		*p++ = 0;
-		sp->fs = p;
-	} else
-		sp->fs = lp;
-}
-
-
-/*
- * Binary file search to find matching man
- *   pages in whatis database.
- */
-
-static int
-bfsearch(FILE *fp, char **matchv, char *key, char **secv)
-{
-	char entry[BUFSIZ];
-	char **vp;
-	long top, bot, mid;
-	int	c;
-
-	vp = matchv;
-	bot = 0;
-	(void) fseek(fp, 0L, 2);
-	top = ftell(fp);
-	for (;;) {
-		mid = (top+bot)/2;
-		(void) fseek(fp, mid, 0);
-		do {
-			c = getc(fp);
-			mid++;
-		} while (c != EOF && c != '\n');
-		if (fgets(entry, sizeof (entry), fp) == NULL)
-			break;
-		switch (compare(key, entry, secv)) {
-		case -2:
-		case -1:
-		case 0:
-			if (top <= mid)
-				break;
-			top = mid;
-			continue;
-		case 1:
-		case 2:
-			bot = mid;
-			continue;
-		}
-		break;
-	}
-	(void) fseek(fp, bot, 0);
-	while (ftell(fp) < top) {
-		if (fgets(entry, sizeof (entry), fp) == NULL) {
-			*matchv = 0;
-			return (matchv - vp);
-		}
-		switch (compare(key, entry, secv)) {
-		case -2:
-			*matchv = 0;
-			return (matchv - vp);
-		case -1:
-		case 0:
-			*matchv = strdup(entry);
-			if (*matchv == NULL)
-				malloc_error();
-			else
-				matchv++;
-			break;
-		case 1:
-		case 2:
-			continue;
-		}
-		break;
-	}
-	while (fgets(entry, sizeof (entry), fp)) {
-		switch (compare(key, entry, secv)) {
-		case -1:
-		case 0:
-			*matchv = strdup(entry);
-			if (*matchv == NULL)
-				malloc_error();
-			else
-				matchv++;
-			continue;
-		}
-		break;
-	}
-	*matchv = 0;
-	return (matchv - vp);
-}
-
-static int
-compare(char *key, char *entry, char **secv)
-{
-	char	*entbuf;
-	char	*s;
-	int	comp, mlen;
-	int	mbcurmax = MB_CUR_MAX;
-	char 	*secp = NULL;
-	int	rv;
-	int	eblen;
-
-	entbuf = strdup(entry);
-	if (entbuf == NULL) {
-		malloc_error();
-	}
-	eblen = strlen(entbuf);
-
-	s = entbuf;
-	while (*s) {
-		if (*s == '\t' || *s == ' ') {
-			*s = '\0';
-			break;
-		}
-		mlen = mblen(s, mbcurmax);
-		if (mlen == -1) {
-			(void) fprintf(stderr, gettext(
-			    "Invalid character in windex file.\n"));
-			exit(1);
-		}
-		s += mlen;
-	}
-	/*
-	 * Find the section within parantheses
-	 */
-	if (secv != NULL && (s - entbuf) < eblen) {
-		if ((secp = strchr(s + 1, ')')) != NULL) {
-			*secp = '\0';
-			if ((secp = strchr(s + 1, '(')) != NULL) {
-				secp++;
-			}
-		}
-	}
-
-	comp = strcmp(key, entbuf);
-	if (comp == 0) {
-		if (secp == NULL) {
-			rv = 0;
-		} else {
-			while (*secv != NULL) {
-				if ((strcmp(*secv, secp)) == 0) {
-					rv = 0;
-					break;
-				}
-				secv++;
-			}
-		}
-	} else if (comp < 0) {
-		rv = -2;
-	} else {
-		rv = 2;
-	}
-	free(entbuf);
-	return (rv);
-}
-
-
-/*
- * Format a man page and follow .so references
- * if necessary.
- */
-
-static int
-format(char *path, char *dir, char *name, char *pg)
-{
-	char manpname[MAXPATHLEN+1], catpname[MAXPATHLEN+1];
-	char manpname_sgml[MAXPATHLEN+1], smantmpname[MAXPATHLEN+1];
-	char soed[MAXPATHLEN+1], soref[MAXPATHLEN+1];
-	char manbuf[BUFSIZ], cmdbuf[BUFSIZ], tmpbuf[BUFSIZ];
-	char tmpdir[MAXPATHLEN+1];
-	int socount, updatedcat, regencat;
-	struct stat mansb, catsb, smansb;
-	char *tmpname;
-	int catonly = 0;
-	struct stat statb;
-	int plen = PLEN;
-	FILE *md;
-	int tempfd;
-	ssize_t	count;
-	int	temp, sgml_flag = 0, check_flag = 0;
-	char prntbuf[BUFSIZ + 1];
-	char *ptr;
-	char *new_m;
-	char	*tmpsubdir;
-
-	found++;
-
-	if (*dir != 'm' && *dir != 's')
-		catonly++;
-
-
-	if (*dir == 's') {
-		tmpsubdir = SGMLDIR;
-		++plen;
-		(void) sprintf(manpname_sgml, "%s/man%s/%s",
-		    path, dir+plen, pg);
-	} else
-		tmpsubdir = MANDIRNAME;
-
-	if (list) {
-		(void) printf(gettext("%s (%s)\t-M %s\n"),
-		    name, dir+plen, path);
-		return (-1);
-	}
-
-	(void) sprintf(manpname, "%s/%s%s/%s", path, tmpsubdir, dir+plen, pg);
-	(void) sprintf(catpname, "%s/%s%s/%s", path, subdirs[1], dir+plen, pg);
-
-	(void) sprintf(smantmpname, "%s/%s%s/%s", path, SGMLDIR, dir+plen, pg);
-
-/*
- * TRANSLATION_NOTE - message for man -d or catman -p
- * ex.  unformatted = /usr/share/man/ja/man3s/printf.3s
- */
-	DPRINTF(gettext(
-	    "      unformatted = %s\n"), catonly ? "" : manpname);
-/*
- * TRANSLATION_NOTE - message for man -d or catman -p
- * ex.  formatted = /usr/share/man/ja/cat3s/printf.3s
- */
-	DPRINTF(gettext(
-	    "      formatted = %s\n"), catpname);
-
-	/*
-	 * Take care of indirect references to other man pages;
-	 * i.e., resolve files containing only ".so manx/file.x".
-	 * We follow .so chains, replacing title with the .so'ed
-	 * file at each stage, and keeping track of how many times
-	 * we've done so, so that we can avoid looping.
-	 */
-	*soed = 0;
-	socount = 0;
-	for (;;) {
-		FILE *md;
-		char *cp;
-		char *s;
-		char *new_s;
-
-		if (catonly)
-			break;
-		/*
-		 * Grab manpname's first line, stashing it in manbuf.
-		 */
-
-
-		if ((md = fopen(manpname, "r")) == NULL) {
-			if (*soed && errno == ENOENT) {
-				(void) fprintf(stderr,
-				    gettext("Can't find referent of "
-				    ".so in %s\n"), soed);
-				(void) fflush(stderr);
-				return (-1);
-			}
-			perror(manpname);
-			return (-1);
-		}
-
-		/*
-		 * If this is a directory, just ignore it.
-		 */
-		if (fstat(fileno(md), &statb) == NULL) {
-			if (S_ISDIR(statb.st_mode)) {
-				if (debug) {
-					(void) fprintf(stderr,
-					    "\tignoring directory %s\n",
-					    manpname);
-					(void) fflush(stderr);
-				}
-				(void) fclose(md);
-				return (-1);
-			}
-		}
-
-		if (fgets(manbuf, BUFSIZ-1, md) == NULL) {
-			(void) fclose(md);
-			(void) fprintf(stderr, gettext("%s: null file\n"),
-			    manpname);
-			(void) fflush(stderr);
-			return (-1);
-		}
-		(void) fclose(md);
-
-		if (strncmp(manbuf, DOT_SO, sizeof (DOT_SO) - 1))
-			break;
-so_again:	if (++socount > SOLIMIT) {
-			(void) fprintf(stderr, gettext(".so chain too long\n"));
-			(void) fflush(stderr);
-			return (-1);
-		}
-		s = manbuf + sizeof (DOT_SO) - 1;
-		if ((check_flag == 1) && ((new_s = strrchr(s, '/')) != NULL)) {
-				new_s++;
-				(void) sprintf(s, "%s%s/%s",
-				    tmpsubdir, dir+plen, new_s);
-		}
-
-		cp = strrchr(s, '\n');
-		if (cp)
-			*cp = '\0';
-		/*
-		 * Compensate for sloppy typists by stripping
-		 * trailing white space.
-		 */
-		cp = s + strlen(s);
-		while (--cp >= s && (*cp == ' ' || *cp == '\t'))
-			*cp = '\0';
-
-		/*
-		 * Go off and find the next link in the chain.
-		 */
-		(void) strcpy(soed, manpname);
-		(void) strcpy(soref, s);
-		(void) sprintf(manpname, "%s/%s", path, s);
-/*
- * TRANSLATION_NOTE - message for man -d or catman -p
- * ex.  .so ref = man3c/string.3c
- */
-		DPRINTF(gettext(".so ref = %s\n"), s);
-	}
-
-	/*
-	 * Make symlinks if so'ed and cattin'
-	 */
-	if (socount && catmando) {
-		(void) sprintf(cmdbuf, "cd %s; rm -f %s; ln -s ../%s%s %s",
-		    path, catpname, subdirs[1], soref+plen, catpname);
-		(void) sys(cmdbuf);
-		return (1);
-	}
-
-	/*
-	 * Obtain the cat page that corresponds to the man page.
-	 * If it already exists, is up to date, and if we haven't
-	 * been told not to use it, use it as it stands.
-	 */
-	regencat = updatedcat = 0;
-	if (compargs || (!catonly && stat(manpname, &mansb) >= 0 &&
-	    (stat(catpname, &catsb) < 0 || catsb.st_mtime < mansb.st_mtime)) ||
-	    (access(catpname, R_OK) != 0)) {
-		/*
-		 * Construct a shell command line for formatting manpname.
-		 * The resulting file goes initially into /tmp.  If possible,
-		 * it will later be moved to catpname.
-		 */
-
-		int pipestage = 0;
-		int needcol = 0;
-		char *cbp = cmdbuf;
-
-		regencat = updatedcat = 1;
-
-		if (!catmando && !debug && !check_flag) {
-			(void) fprintf(stderr, gettext(
-			    "Reformatting page.  Please Wait..."));
-			if (sargs && (newsection != NULL) &&
-			    (*newsection != '\0')) {
-				(void) fprintf(stderr, gettext(
-				    "\nThe directory name has been changed "
-				    "to %s\n"), newsection);
-			}
-			(void) fflush(stderr);
-		}
-
-		/*
-		 * in catman command, if the file exists in sman dir already,
-		 * don't need to convert the file in man dir to cat dir
-		 */
-
-		if (!no_sroff && catmando &&
-		    match(tmpsubdir, MANDIRNAME, PLEN) &&
-		    stat(smantmpname, &smansb) >= 0)
-			return (1);
-
-		/*
-		 * cd to path so that relative .so commands will work
-		 * correctly
-		 */
-		(void) sprintf(cbp, "cd %s; ", path);
-		cbp += strlen(cbp);
-
-
-		/*
-		 * check to see whether it is a sgml file
-		 * assume sgml symbol(>!DOCTYPE) can be found in the first
-		 * BUFSIZ bytes
-		 */
-
-		if ((temp = open(manpname, 0)) == -1) {
-				perror(manpname);
-				return (-1);
-		}
-
-		if ((count = read(temp, prntbuf, BUFSIZ)) <= 0) {
-				perror(manpname);
-				return (-1);
-		}
-
-		prntbuf[count] = '\0';	/* null terminate */
-		ptr = prntbuf;
-		if (sgmlcheck((const char *)ptr) == 1) {
-			sgml_flag = 1;
-			if (defaultmandir && *localedir) {
-				(void) sprintf(cbp, "LC_MESSAGES=C %s %s ",
-				    SROFF_CMD, manpname);
-			} else {
-				(void) sprintf(cbp, "%s %s ",
-				    SROFF_CMD, manpname);
-			}
-			cbp += strlen(cbp);
-		} else if (*dir == 's') {
-			(void) close(temp);
-			return (-1);
-		}
-		(void) close(temp);
-
-		/*
-		 * Check for special formatting requirements by examining
-		 * manpname's first line preprocessor specifications.
-		 */
-
-		if (strncmp(manbuf, PREPROC_SPEC,
-		    sizeof (PREPROC_SPEC) - 1) == 0) {
-			char *ptp;
-
-			ptp = manbuf + sizeof (PREPROC_SPEC) - 1;
-			while (*ptp && *ptp != '\n') {
-				const struct preprocessor *pp;
-
-				/*
-				 * Check for a preprocessor we know about.
-				 */
-				for (pp = preprocessors; pp->p_tag; pp++) {
-					if (pp->p_tag == *ptp)
-						break;
-				}
-				if (pp->p_tag == 0) {
-					(void) fprintf(stderr,
-					    gettext("unknown preprocessor "
-					    "specifier %c\n"), *ptp);
-					(void) fflush(stderr);
-					return (-1);
-				}
-
-				/*
-				 * Add it to the pipeline.
-				 */
-				(void) sprintf(cbp, "%s %s |",
-				    troffit ? pp->p_troff : pp->p_nroff,
-				    pipestage++ == 0 ? manpname :
-				    pp->p_stdin_char);
-				cbp += strlen(cbp);
-
-				/*
-				 * Special treatment: if tbl is among the
-				 * preprocessors and we'll process with
-				 * nroff, we have to pass things through
-				 * col at the end of the pipeline.
-				 */
-				if (pp->p_tag == 't' && !troffit)
-					needcol++;
-
-				ptp++;
-			}
-		}
-
-		/*
-		 * if catman, use the cat page name
-		 * otherwise, dup template and create another
-		 * (needed for multiple pages)
-		 */
-		if (catmando)
-			tmpname = catpname;
-		else {
-			tmpname = strdup(TEMPLATE);
-			if (tmpname == NULL)
-				malloc_error();
-			(void) close(mkstemp(tmpname));
-		}
-
-		if (! Tflag) {
-			if (*localedir != '\0') {
-				(void) sprintf(macros, "%s/%s", path, MACROF);
-/*
- * TRANSLATION_NOTE - message for man -d or catman -p
- * ex.  locale macros = /usr/share/man/ja/tmac.an
- */
-				if (debug)
-					(void) printf(gettext(
-					    "\nlocale macros = %s "),
-					    macros);
-				if (stat(macros, &statb) < 0)
-					(void) strcpy(macros, TMAC_AN);
-/*
- * TRANSLATION_NOTE - message for man -d or catman -p
- * ex.  macros = /usr/share/man/ja/tman.an
- */
-				if (debug)
-					(void) printf(gettext(
-					    "\nmacros = %s\n"),
-					    macros);
-			}
-		}
-
-		tmpdir[0] = '\0';
-		if (sgml_flag == 1) {
-			if (check_flag == 0) {
-				strcpy(tmpdir, "/tmp/sman_XXXXXX");
-				if ((tempfd = mkstemp(tmpdir)) == -1) {
-					(void) fprintf(stderr, gettext(
-					    "%s: null file\n"), tmpdir);
-					(void) fflush(stderr);
-					return (-1);
-				}
-
-				if (debug)
-					close(tempfd);
-
-				(void) sprintf(tmpbuf, "%s > %s",
-				    cmdbuf, tmpdir);
-				if (sys(tmpbuf)) {
-/*
- * TRANSLATION_NOTE - message for man -d or catman -p
- * Error message if sys(%s) failed
- */
-					(void) fprintf(stderr, gettext(
-					    "sys(%s) fail!\n"), tmpbuf);
-					(void) fprintf(stderr,
-					    gettext(" aborted (sorry)\n"));
-					(void) fflush(stderr);
-					/* release memory for tmpname */
-					if (!catmando) {
-						(void) unlink(tmpdir);
-						(void) unlink(tmpname);
-						free(tmpname);
-					}
-					return (-1);
-				} else if (debug == 0) {
-					if ((md = fdopen(tempfd, "r"))
-					    == NULL) {
-						(void) fprintf(stderr, gettext(
-						    "%s: null file\n"), tmpdir);
-						(void) fflush(stderr);
-						close(tempfd);
-						/* release memory for tmpname */
-						if (!catmando)
-							free(tmpname);
-						return (-1);
-					}
-
-					/* if the file is empty, */
-					/* it's a fragment, do nothing */
-					if (fgets(manbuf, BUFSIZ-1, md)
-					    == NULL) {
-						(void) fclose(md);
-						/* release memory for tmpname */
-						if (!catmando)
-							free(tmpname);
-						return (1);
-					}
-					(void) fclose(md);
-
-					if (strncmp(manbuf, DOT_SO,
-					    sizeof (DOT_SO) - 1) == 0) {
-						if (!compargs) {
-						check_flag = 1;
-						(void) unlink(tmpdir);
-						(void) unlink(tmpname);
-						/* release memory for tmpname */
-						if (!catmando)
-							free(tmpname);
-						goto so_again;
-						} else {
-							(void) unlink(tmpdir);
-						strcpy(tmpdir,
-						    "/tmp/sman_XXXXXX");
-						tempfd = mkstemp(tmpdir);
-						if ((tempfd == -1) ||
-						    (md = fdopen(tempfd, "w"))
-						    == NULL) {
-							(void) fprintf(stderr,
-							    gettext(
-							    "%s: null file\n"),
-							    tmpdir);
-							(void) fflush(stderr);
-							if (tempfd != -1)
-								close(tempfd);
-						/* release memory for tmpname */
-							if (!catmando)
-								free(tmpname);
-							return (-1);
-						}
-				if ((new_m = strrchr(manbuf, '/')) != NULL) {
-		(void) fprintf(md, ".so man%s%s\n", dir+plen, new_m);
-							} else {
-/*
- * TRANSLATION_NOTE - message for catman -c
- * Error message if unable to get file name
- */
-				(void) fprintf(stderr,
-				    gettext("file not found\n"));
-				(void) fflush(stderr);
-				return (-1);
-				}
-							(void) fclose(md);
-						}
-					}
-				}
-				if (catmando && compargs)
-					(void) sprintf(cmdbuf, "cat %s > %s",
-					    tmpdir, manpname_sgml);
-				else
-	(void) sprintf(cmdbuf, " cat %s | tbl | eqn | %s %s - %s > %s",
-	    tmpdir, troffit ? troffcmd : "nroff -u0 -Tlp",
-	    macros, troffit ? "" : " | col -x", tmpname);
-			} else
-				if (catmando && compargs)
-					(void) sprintf(cbp, " > %s",
-					    manpname_sgml);
-				else
-	(void) sprintf(cbp, " | tbl | eqn | %s %s - %s > %s",
-	    troffit ? troffcmd : "nroff -u0 -Tlp",
-	    macros, troffit ? "" : " | col -x", tmpname);
-
-		} else
-	(void) sprintf(cbp, "%s %s %s%s > %s",
-	    troffit ? troffcmd : "nroff -u0 -Tlp",
-	    macros, pipestage == 0 ? manpname : "-",
-	    troffit ? "" : " | col -x", tmpname);
-
-		/* Reformat the page. */
-		if (sys(cmdbuf)) {
-/*
- * TRANSLATION_NOTE - message for man -d or catman -p
- * Error message if sys(%s) failed
- */
-			(void) fprintf(stderr, gettext(
-			    "sys(%s) fail!\n"), cmdbuf);
-			(void) fprintf(stderr, gettext(" aborted (sorry)\n"));
-			(void) fflush(stderr);
-			(void) unlink(tmpname);
-			/* release memory for tmpname */
-			if (!catmando)
-				free(tmpname);
-			return (-1);
-		}
-
-		if (tmpdir[0] != '\0')
-			(void) unlink(tmpdir);
-
-		if (catmando)
-			return (1);
-
-		/*
-		 * Attempt to move the cat page to its proper home.
-		 */
-		(void) sprintf(cmdbuf,
-		    "trap '' 1 15; /usr/bin/mv -f %s %s 2> /dev/null",
-		    tmpname,
-		    catpname);
-		if (sys(cmdbuf))
-			updatedcat = 0;
-		else if (debug == 0)
-			(void) chmod(catpname, 0644);
-
-		if (debug) {
-			/* release memory for tmpname */
-			if (!catmando)
-				free(tmpname);
-			(void) unlink(tmpname);
-			return (1);
-		}
-
-		(void) fprintf(stderr, gettext(" done\n"));
-		(void) fflush(stderr);
-	}
-
-	/*
-	 * Save file name (dup if necessary)
-	 * to view later
-	 * fix for 1123802 - don't save names if we are invoked as catman
-	 */
-	if (!catmando) {
-		char	**tmpp;
-		int	dup;
-		char	*newpage;
-
-		if (regencat && !updatedcat)
-			newpage = tmpname;
-		else {
-			newpage = strdup(catpname);
-			if (newpage == NULL)
-				malloc_error();
-		}
-		/* make sure we don't add a dup */
-		dup = 0;
-		for (tmpp = pages; tmpp < endp; tmpp++) {
-			if (strcmp(*tmpp, newpage) == 0) {
-				dup = 1;
-				break;
-			}
-		}
-		if (!dup)
-			*endp++ = newpage;
-		if (endp >= &pages[MAXPAGES]) {
-			fprintf(stderr,
-			    gettext("Internal pages array overflow!\n"));
-			exit(1);
-		}
-	}
-
-	return (regencat);
-}
-
-/*
- * Add <localedir> to the path.
- */
-
-static char *
-addlocale(char *path)
-{
-
-	char *tmp;
-
-	tmp = malloc(strlen(path) + strlen(localedir) + 2);
-	if (tmp == NULL)
-		malloc_error();
-	(void) sprintf(tmp, "%s/%s", path, localedir);
-	return (tmp);
-
-}
-
-/*
- * From the configuration file "man.cf", get the order of suffices of
- * sub-mandirs to be used in the search path for a given mandir.
- */
-
-static char *
-check_config(char *path)
-{
-	FILE *fp;
-	static char submandir[BUFSIZ];
-	char *sect;
-	char fname[MAXPATHLEN];
-
-	(void) sprintf(fname, "%s/%s", path, CONFIG);
-
-	if ((fp = fopen(fname, "r")) == NULL)
-		return (NULL);
-	else {
-		if (get_manconfig(fp, submandir) == -1) {
-			(void) fclose(fp);
-			return (NULL);
-		}
-
-		(void) fclose(fp);
-
-		sect = strchr(submandir, '=');
-		if (sect != NULL)
-			return (++sect);
-		else
-			return (NULL);
-	}
-}
-
-/*
- *  This routine is for getting the MANSECTS entry from man.cf.
- *  It sets submandir to the line in man.cf that contains
- *	MANSECTS=sections[,sections]...
- */
-
-static int
-get_manconfig(FILE *fp, char *submandir)
-{
-	char *s, *t, *rc;
-	char buf[BUFSIZ];
-
-	while ((rc = fgets(buf, sizeof (buf), fp)) != NULL) {
-
-		/*
-		 * skip leading blanks
-		 */
-		for (t = buf; *t != '\0'; t++) {
-			if (!isspace(*t))
-				break;
-		}
-		/*
-		 * skip line that starts with '#' or empty line
-		 */
-		if (*t == '#' || *t == '\0')
-			continue;
-
-		if (strstr(buf, "MANSECTS") != NULL)
-			break;
-	}
-
-	/*
-	 * the man.cf file doesn't have a MANSECTS entry
-	 */
-	if (rc == NULL)
-		return (-1);
-
-	s = strchr(buf, '\n');
-	*s = '\0';	/* replace '\n' with '\0' */
-
-	(void) strcpy(submandir, buf);
-	return (0);
-}
-
-static void
-malloc_error(void)
-{
-	(void) fprintf(stderr, gettext(
-	    "Memory allocation failed.\n"));
-	exit(1);
-}
-
-static int
-sgmlcheck(const char *s1)
-{
-	const char	*s2 = SGML_SYMBOL;
-	int	len;
-
-	while (*s1) {
-		/*
-		 * Assume the first character of SGML_SYMBOL(*s2) is '<'.
-		 * Therefore, not necessary to do toupper(*s1) here.
-		 */
-		if (*s1 == *s2) {
-			/*
-			 * *s1 is '<'.  Check the following substring matches
-			 * with "!DOCTYPE".
-			 */
-			s1++;
-			if (strncasecmp(s1, s2 + 1, SGML_SYMBOL_LEN - 1)
-			    == 0) {
-				/*
-				 * SGML_SYMBOL found
-				 */
-				return (1);
-			}
-			continue;
-		} else if (isascii(*s1)) {
-			/*
-			 * *s1 is an ASCII char
-			 * Skip one character
-			 */
-			s1++;
-			continue;
-		} else {
-			/*
-			 * *s1 is a non-ASCII char or
-			 * the first byte of the multibyte char.
-			 * Skip one character
-			 */
-			len = mblen(s1, MB_CUR_MAX);
-			if (len == -1)
-				len = 1;
-			s1 += len;
-			continue;
-		}
-	}
-	/*
-	 * SGML_SYMBOL not found
-	 */
-	return (0);
-}
-
-/*
- * Initializes the bintoman array with appropriate device and inode info
- */
-
-static void
-init_bintoman(void)
-{
-	int i;
-	struct stat sb;
-
-	for (i = 0; bintoman[i].bindir != NULL; i++) {
-		if (stat(bintoman[i].bindir, &sb) == 0) {
-			bintoman[i].dev = sb.st_dev;
-			bintoman[i].ino = sb.st_ino;
-		} else {
-			bintoman[i].dev = NODEV;
-		}
-	}
-}
-
-/*
- * If a duplicate is found, return 1
- * If a duplicate is not found, add it to the dupnode list and return 0
- */
-static int
-dupcheck(struct man_node *mnp, struct dupnode **dnp)
-{
-	struct dupnode	*curdnp;
-	struct secnode	*cursnp;
-	struct stat 	sb;
-	int 		i;
-	int		rv = 1;
-	int		dupfound;
-
-	/*
-	 * If the path doesn't exist, treat it as a duplicate
-	 */
-	if (stat(mnp->path, &sb) != 0) {
-		return (1);
-	}
-
-	/*
-	 * If no sections were found in the man dir, treat it as duplicate
-	 */
-	if (mnp->secv == NULL) {
-		return (1);
-	}
-
-	/*
-	 * Find the dupnode structure for the previous time this directory
-	 * was looked at.  Device and inode numbers are compared so that
-	 * directories that are reached via different paths (e.g. /usr/man vs.
-	 * /usr/share/man) are treated as equivalent.
-	 */
-	for (curdnp = *dnp; curdnp != NULL; curdnp = curdnp->next) {
-		if (curdnp->dev == sb.st_dev && curdnp->ino == sb.st_ino) {
-			break;
-		}
-	}
-
-	/*
-	 * First time this directory has been seen.  Add a new node to the
-	 * head of the list.  Since all entries are guaranteed to be unique
-	 * copy all sections to new node.
-	 */
-	if (curdnp == NULL) {
-		if ((curdnp = calloc(1, sizeof (struct dupnode))) == NULL) {
-			malloc_error();
-		}
-		for (i = 0; mnp->secv[i] != NULL; i++) {
-			if ((cursnp = calloc(1, sizeof (struct secnode)))
-			    == NULL) {
-				malloc_error();
-			}
-			cursnp->next = curdnp->secl;
-			curdnp->secl = cursnp;
-			if ((cursnp->secp = strdup(mnp->secv[i])) == NULL) {
-				malloc_error();
-			}
-		}
-		curdnp->dev = sb.st_dev;
-		curdnp->ino = sb.st_ino;
-		curdnp->next = *dnp;
-		*dnp = curdnp;
-		return (0);
-	}
-
-	/*
-	 * Traverse the section vector in the man_node and the section list
-	 * in dupnode cache to eliminate all duplicates from man_node
-	 */
-	for (i = 0; mnp->secv[i] != NULL; i++) {
-		dupfound = 0;
-		for (cursnp = curdnp->secl; cursnp != NULL;
-		    cursnp = cursnp->next) {
-			if (strcmp(mnp->secv[i], cursnp->secp) == 0) {
-				dupfound = 1;
-				break;
-			}
-		}
-		if (dupfound) {
-			mnp->secv[i][0] = '\0';
-			continue;
-		}
-
-
-		/*
-		 * Update curdnp and set return value to indicate that this
-		 * was not all duplicates.
-		 */
-		if ((cursnp = calloc(1, sizeof (struct secnode))) == NULL) {
-			malloc_error();
-		}
-		cursnp->next = curdnp->secl;
-		curdnp->secl = cursnp;
-		if ((cursnp->secp = strdup(mnp->secv[i])) == NULL) {
-			malloc_error();
-		}
-		rv = 0;
-	}
-
-	return (rv);
-}
-
-/*
- * Given a bin directory, return the corresponding man directory.
- * Return string must be free()d by the caller.
- *
- * NULL will be returned if no matching man directory can be found.
- */
-
-static char *
-path_to_manpath(char *bindir)
-{
-	char	*mand, *p;
-	int	i;
-	struct stat	sb;
-
-	/*
-	 * First look for known translations for specific bin paths
-	 */
-	if (stat(bindir, &sb) != 0) {
-		return (NULL);
-	}
-	for (i = 0; bintoman[i].bindir != NULL; i++) {
-		if (sb.st_dev == bintoman[i].dev &&
-		    sb.st_ino == bintoman[i].ino) {
-			if ((mand = strdup(bintoman[i].mandir)) == NULL) {
-				malloc_error();
-			}
-			if ((p = strchr(mand, ',')) != NULL) {
-				*p = '\0';
-			}
-			if (stat(mand, &sb) != 0) {
-				free(mand);
-				return (NULL);
-			}
-			if (p != NULL) {
-				*p = ',';
-			}
-			return (mand);
-		}
-	}
-
-	/*
-	 * No specific translation found.  Try `dirname $bindir`/man
-	 * and `dirname $bindir`/share/man
-	 */
-	if ((mand = malloc(PATH_MAX)) == NULL) {
-		malloc_error();
-	}
-
-	if (strlcpy(mand, bindir, PATH_MAX) >= PATH_MAX) {
-		free(mand);
-		return (NULL);
-	}
-
-	/*
-	 * Advance to end of buffer, strip trailing /'s then remove last
-	 * directory component.
-	 */
-	for (p = mand; *p != '\0'; p++)
-		;
-	for (; p > mand && *p == '/'; p--)
-		;
-	for (; p > mand && *p != '/'; p--)
-		;
-	if (p == mand && *p == '.') {
-		if (realpath("..", mand) == NULL) {
-			free(mand);
-			return (NULL);
-		}
-		for (; *p != '\0'; p++)
-			;
-	} else {
-		*p = '\0';
-	}
-
-	if (strlcat(mand, "/man", PATH_MAX) >= PATH_MAX) {
-		free(mand);
-		return (NULL);
-	}
-
-	if ((stat(mand, &sb) == 0) && S_ISDIR(sb.st_mode)) {
-		return (mand);
-	}
-
-	/*
-	 * Strip the /man off and try /share/man
-	 */
-	*p = '\0';
-	if (strlcat(mand, "/share/man", PATH_MAX) >= PATH_MAX) {
-		free(mand);
-		return (NULL);
-	}
-	if ((stat(mand, &sb) == 0) && S_ISDIR(sb.st_mode)) {
-		return (mand);
-	}
-
-	/*
-	 * No man or share/man directory found
-	 */
-	free(mand);
-	return (NULL);
-}
-
-/*
- * Free a linked list of dupnode structs
- */
-void
-free_dupnode(struct dupnode *dnp) {
-	struct dupnode *dnp2;
-	struct secnode *snp;
-
-	while (dnp != NULL) {
-		dnp2 = dnp;
-		dnp = dnp->next;
-		while (dnp2->secl != NULL) {
-			snp = dnp2->secl;
-			dnp2->secl = dnp2->secl->next;
-			free(snp->secp);
-			free(snp);
-		}
-		free(dnp2);
-	}
-}
-
-/*
- * prints manp linked list to stdout.
- *
- * If namep is NULL, output can be used for setting MANPATH.
- *
- * If namep is not NULL output is two columns.  First column is the string
- * pointed to by namep.  Second column is a MANPATH-compatible representation
- * of manp linked list.
- */
-void
-print_manpath(struct man_node *manp, char *namep)
-{
-	char colon[2];
-	char **secp;
-
-	if (namep != NULL) {
-		(void) printf("%s ", namep);
-	}
-
-	colon[0] = '\0';
-	colon[1] = '\0';
-
-	for (; manp != NULL; manp = manp->next) {
-		(void) printf("%s%s", colon, manp->path);
-		colon[0] = ':';
-
-		/*
-		 * If man.cf or a directory scan was used to create section
-		 * list, do not print section list again.  If the output of
-		 * man -p is used to set MANPATH, subsequent runs of man
-		 * will re-read man.cf and/or scan man directories as
-		 * required.
-		 */
-		if (manp->defsrch != 0) {
-			continue;
-		}
-
-		for (secp = manp->secv; *secp != NULL; secp++) {
-			/*
-			 * Section deduplication may have eliminated some
-			 * sections from the vector. Avoid displaying this
-			 * detail which would appear as ",," in output
-			 */
-			if ((*secp)[0] != '\0') {
-				(void) printf(",%s", *secp);
-			}
-		}
-	}
-	(void) printf("\n");
-}
diff --git a/usr/src/cmd/man/stringlist.c b/usr/src/cmd/man/stringlist.c
new file mode 100644
index 0000000..e9f6035
--- /dev/null
+++ b/usr/src/cmd/man/stringlist.c
@@ -0,0 +1,102 @@
+/*
+ * Copyright (c) 1994 Christos Zoulas
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 4. The name of the author may not be used to endorse or promote products
+ *    derived from this software without specific prior written permission.
+ *
+ * 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.
+ */
+
+/*
+ * Copyright 2012 Nexenta Systems, Inc. All rights reserved.
+ */
+
+#include <err.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "stringlist.h"
+
+#define	_SL_CHUNKSIZE	20
+
+stringlist *
+sl_init(void)
+{
+	stringlist	*sl;
+
+	if ((sl = malloc(sizeof (stringlist))) == NULL)
+		err(1, "malloc");
+
+	sl->sl_cur = 0;
+	sl->sl_max = _SL_CHUNKSIZE;
+	sl->sl_str = malloc(sl->sl_max * sizeof (char *));
+	if (sl->sl_str == NULL)
+		err(1, "malloc");
+
+	return (sl);
+}
+
+int
+sl_add(stringlist *sl, char *name)
+{
+
+	if (sl->sl_cur == sl->sl_max - 1) {
+		sl->sl_max += _SL_CHUNKSIZE;
+		sl->sl_str = realloc(sl->sl_str, sl->sl_max * sizeof (char *));
+		if (sl->sl_str == NULL)
+			return (-1);
+	}
+	sl->sl_str[sl->sl_cur++] = name;
+
+	return (0);
+}
+
+
+void
+sl_free(stringlist *sl, int all)
+{
+	size_t i;
+
+	if (sl == NULL)
+		return;
+	if (sl->sl_str) {
+		if (all)
+			for (i = 0; i < sl->sl_cur; i++)
+				free(sl->sl_str[i]);
+		free(sl->sl_str);
+	}
+	free(sl);
+}
+
+
+char *
+sl_find(stringlist *sl, char *name)
+{
+	size_t	i;
+
+	for (i = 0; i < sl->sl_cur; i++)
+		if (strcmp(sl->sl_str[i], name) == 0)
+			return (sl->sl_str[i]);
+
+	return (NULL);
+}
diff --git a/usr/src/cmd/man/stringlist.h b/usr/src/cmd/man/stringlist.h
new file mode 100644
index 0000000..2813a10
--- /dev/null
+++ b/usr/src/cmd/man/stringlist.h
@@ -0,0 +1,52 @@
+/*
+ * Copyright (c) 1994 Christos Zoulas
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ *    must display the following acknowledgement:
+ *	This product includes software developed by Christos Zoulas.
+ * 4. The name of the author may not be used to endorse or promote products
+ *    derived from this software without specific prior written permission.
+ *
+ * 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.
+ */
+
+/*
+ * Copyright 2012 Nexenta Systems, Inc. All rights reserved.
+ */
+
+#ifndef _STRINGLIST_H_
+#define	_STRINGLIST_H_
+
+#include <sys/types.h>
+
+typedef struct _stringlist {
+	char	**sl_str;
+	size_t	sl_max;
+	size_t	sl_cur;
+} stringlist;
+
+stringlist	*sl_init(void);
+int		sl_add(stringlist *, char *);
+void		sl_free(stringlist *, int);
+char		*sl_find(stringlist *, char *);
+
+#endif	/* _STRINGLIST_H_ */
diff --git a/usr/src/cmd/mandoc/Makefile b/usr/src/cmd/mandoc/Makefile
new file mode 100644
index 0000000..6d2e349
--- /dev/null
+++ b/usr/src/cmd/mandoc/Makefile
@@ -0,0 +1,47 @@
+#
+# This file and its contents are supplied under the terms of the
+# Common Development and Distribution License ("CDDL"), version 1.0.
+# You may only use this file in accordance with the terms of version
+# 1.0 of the CDDL.
+#
+# A full copy of the text of the CDDL should have accompanied this
+# source.  A copy of the CDDL is also available via the Internet at
+# http://www.illumos.org/license/CDDL.
+#
+
+#
+# Copyright 2014 Nexenta Systems, Inc.  All rights reserved.
+# Copyright 2014 Garrett D'Amore <garrett@damore.org>
+#
+
+PROGS=		mandoc mandoc_preconv
+
+# We place preconv in /usr/lib.  This is done to avoid conflicting with
+# GNU groff, which puts it into /usr/bin.  We also rename it so that it
+# will only be seen by mandoc -- it isn't intended for general end-user use.
+
+ROOTPROGS =	$(ROOTBIN)/mandoc $(ROOTLIB)/mandoc_preconv
+
+OBJS=		$(preconv_OBJS) $(mandoc_OBJS)
+
+include		$(SRC)/cmd/Makefile.cmd
+include		$(SRC)/cmd/mandoc/Makefile.common
+
+.KEEP_STATE:
+
+all:		$(PROGS)
+
+mandoc_preconv:	$(preconv_OBJS)
+		$(LINK.c) $(preconv_OBJS) -o $@ $(LDLIBS)
+		$(POST_PROCESS)
+
+mandoc:		$(mandoc_OBJS)
+		$(LINK.c) $(mandoc_OBJS) -o $@ $(LDLIBS)
+		$(POST_PROCESS)
+
+clean:
+		$(RM) $(OBJS)
+
+install:	all $(ROOTPROGS)
+
+include		$(SRC)/cmd/Makefile.targ
diff --git a/usr/src/cmd/mandoc/Makefile.common b/usr/src/cmd/mandoc/Makefile.common
new file mode 100644
index 0000000..0969995
--- /dev/null
+++ b/usr/src/cmd/mandoc/Makefile.common
@@ -0,0 +1,33 @@
+#
+# This file and its contents are supplied under the terms of the
+# Common Development and Distribution License ("CDDL"), version 1.0.
+# You may only use this file in accordance with the terms of version
+# 1.0 of the CDDL.
+#
+# A full copy of the text of the CDDL should have accompanied this
+# source.  A copy of the CDDL is also available via the Internet at
+# http://www.illumos.org/license/CDDL.
+#
+
+#
+# Copyright 2012 Nexenta Systems, Inc.  All rights reserved.
+# Copyright 2014 Garrett D'Amore <garrett@damore.org>
+#
+
+PROGS=		mandoc mandoc_preconv
+mandoc_OBJS =	arch.o att.o chars.o eqn.o eqn_html.o eqn_term.o 	\
+		html.o lib.o main.o man.o man_hash.o man_html.o		\
+		man_macro.o man_term.o man_validate.o mandoc.o mdoc.o	\
+		mdoc_argv.o mdoc_hash.o mdoc_html.o mdoc_macro.o	\
+		mdoc_man.o mdoc_term.o mdoc_validate.o msec.o out.o	\
+		read.o roff.o st.o tbl.o tbl_data.o tbl_html.o		\
+		tbl_layout.o tbl_opts.o tbl_term.o term.o term_ascii.o	\
+		term_ps.o tree.o vol.o
+
+preconv_OBJS =	preconv.o
+
+CFLAGS +=	$(CC_VERBOSE)
+
+CPPFLAGS +=	-DHAVE_CONFIG_H -DUSE_WCHAR	\
+		-DOSNAME="\"illumos\""	\
+		-DVERSION="\"1.12.1\""
diff --git a/usr/src/cmd/mandoc/THIRDPARTYLICENSE b/usr/src/cmd/mandoc/THIRDPARTYLICENSE
new file mode 100644
index 0000000..0fae1fd
--- /dev/null
+++ b/usr/src/cmd/mandoc/THIRDPARTYLICENSE
@@ -0,0 +1,14 @@
+Copyright (c) 2008, 2009, 2010, 2011 Kristaps Dzonsons <kristaps@bsd.lv>
+Copyright (c) 2011 Ingo Schwarze <schwarze@openbsd.org>
+
+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 AUTHORS DISCLAIM ALL WARRANTIES
+WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHORS 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.
diff --git a/usr/src/cmd/mandoc/THIRDPARTYLICENSE.descrip b/usr/src/cmd/mandoc/THIRDPARTYLICENSE.descrip
new file mode 100644
index 0000000..ecb8c67
--- /dev/null
+++ b/usr/src/cmd/mandoc/THIRDPARTYLICENSE.descrip
@@ -0,0 +1 @@
+MANDOC - FORMAT AND DISPLAY UNIX MANUALS
diff --git a/usr/src/cmd/mandoc/arch.c b/usr/src/cmd/mandoc/arch.c
new file mode 100644
index 0000000..e764bfe
--- /dev/null
+++ b/usr/src/cmd/mandoc/arch.c
@@ -0,0 +1,39 @@
+/*	$Id: arch.c,v 1.9 2011/03/22 14:33:05 kristaps Exp $ */
+/*
+ * Copyright (c) 2009 Kristaps Dzonsons <kristaps@bsd.lv>
+ *
+ * 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.
+ */
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+
+#include "mdoc.h"
+#include "mandoc.h"
+#include "libmdoc.h"
+
+#define LINE(x, y) \
+	if (0 == strcmp(p, x)) return(y);
+
+const char *
+mdoc_a2arch(const char *p)
+{
+
+#include "arch.in" 
+
+	return(NULL);
+}
diff --git a/usr/src/cmd/mandoc/arch.in b/usr/src/cmd/mandoc/arch.in
new file mode 100644
index 0000000..5113446
--- /dev/null
+++ b/usr/src/cmd/mandoc/arch.in
@@ -0,0 +1,111 @@
+/*	$Id: arch.in,v 1.12 2012/01/28 14:02:17 joerg Exp $ */
+/*
+ * Copyright (c) 2009 Kristaps Dzonsons <kristaps@bsd.lv>
+ *
+ * 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.
+ */
+
+/*
+ * This file defines the architecture token of the .Dt prologue macro.
+ * All architectures that your system supports (or the manuals of your
+ * system) should be included here.  The right-hand-side is the
+ * formatted output.
+ *
+ * Be sure to escape strings.
+ *
+ * REMEMBER TO ADD NEW ARCHITECTURES TO MDOC.7!
+ */
+
+LINE("acorn26",		"Acorn26")
+LINE("acorn32",		"Acorn32")
+LINE("algor",		"Algor")
+LINE("alpha",		"Alpha")
+LINE("amd64",		"AMD64")
+LINE("amiga",		"Amiga")
+LINE("amigappc",	"AmigaPPC")
+LINE("arc",		"ARC")
+LINE("arm",		"ARM")
+LINE("arm26",		"ARM26")
+LINE("arm32",		"ARM32")
+LINE("armish",		"ARMISH")
+LINE("aviion",		"AViiON")
+LINE("atari",		"ATARI")
+LINE("beagle",		"Beagle")
+LINE("bebox",		"BeBox")
+LINE("cats",		"cats")
+LINE("cesfic",		"CESFIC")
+LINE("cobalt",		"Cobalt")
+LINE("dreamcast",	"Dreamcast")
+LINE("emips",		"EMIPS")
+LINE("evbarm",		"evbARM")
+LINE("evbmips",		"evbMIPS")
+LINE("evbppc",		"evbPPC")
+LINE("evbsh3",		"evbSH3")
+LINE("ews4800mips",	"EWS4800MIPS")
+LINE("hp300",		"HP300")
+LINE("hp700",		"HP700")
+LINE("hpcarm",		"HPCARM")
+LINE("hpcmips",		"HPCMIPS")
+LINE("hpcsh",		"HPCSH")
+LINE("hppa",		"HPPA")
+LINE("hppa64",		"HPPA64")
+LINE("ia64",		"ia64")
+LINE("i386",		"i386")
+LINE("ibmnws",		"IBMNWS")
+LINE("iyonix",		"Iyonix")
+LINE("landisk",		"LANDISK")
+LINE("loongson",	"Loongson")
+LINE("luna68k",		"Luna68k")
+LINE("luna88k",		"Luna88k")
+LINE("m68k",		"m68k")
+LINE("mac68k",		"Mac68k")
+LINE("macppc",		"MacPPC")
+LINE("mips",		"MIPS")
+LINE("mips64",		"MIPS64")
+LINE("mipsco",		"MIPSCo")
+LINE("mmeye",		"mmEye")
+LINE("mvme68k",		"MVME68k")
+LINE("mvme88k",		"MVME88k")
+LINE("mvmeppc",		"MVMEPPC")
+LINE("netwinder",	"NetWinder")
+LINE("news68k",		"NeWS68k")
+LINE("newsmips",	"NeWSMIPS")
+LINE("next68k",		"NeXT68k")
+LINE("ofppc",		"OFPPC")
+LINE("palm",		"Palm")
+LINE("pc532",		"PC532")
+LINE("playstation2",	"PlayStation2")
+LINE("pmax",		"PMAX")
+LINE("pmppc",		"pmPPC")
+LINE("powerpc",		"PowerPC")
+LINE("prep",		"PReP")
+LINE("rs6000",		"RS6000")
+LINE("sandpoint",	"Sandpoint")
+LINE("sbmips",		"SBMIPS")
+LINE("sgi",		"SGI")
+LINE("sgimips",		"SGIMIPS")
+LINE("sh3",		"SH3")
+LINE("shark",		"Shark")
+LINE("socppc",		"SOCPPC")
+LINE("solbourne",	"Solbourne")
+LINE("sparc",		"SPARC")
+LINE("sparc64",		"SPARC64")
+LINE("sun2",		"Sun2")
+LINE("sun3",		"Sun3")
+LINE("tahoe",		"Tahoe")
+LINE("vax",		"VAX")
+LINE("x68k",		"X68k")
+LINE("x86",		"x86")
+LINE("x86_64",		"x86_64")
+LINE("xen",		"Xen")
+LINE("zaurus",		"Zaurus")
diff --git a/usr/src/cmd/mandoc/att.c b/usr/src/cmd/mandoc/att.c
new file mode 100644
index 0000000..24d757d
--- /dev/null
+++ b/usr/src/cmd/mandoc/att.c
@@ -0,0 +1,39 @@
+/*	$Id: att.c,v 1.9 2011/03/22 14:33:05 kristaps Exp $ */
+/*
+ * Copyright (c) 2009 Kristaps Dzonsons <kristaps@bsd.lv>
+ *
+ * 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.
+ */
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+
+#include "mdoc.h"
+#include "mandoc.h"
+#include "libmdoc.h"
+
+#define LINE(x, y) \
+	if (0 == strcmp(p, x)) return(y);
+
+const char *
+mdoc_a2att(const char *p)
+{
+
+#include "att.in" 
+
+	return(NULL);
+}
diff --git a/usr/src/cmd/mandoc/att.in b/usr/src/cmd/mandoc/att.in
new file mode 100644
index 0000000..b4ef822
--- /dev/null
+++ b/usr/src/cmd/mandoc/att.in
@@ -0,0 +1,40 @@
+/*	$Id: att.in,v 1.8 2011/07/31 17:30:33 schwarze Exp $ */
+/*
+ * Copyright (c) 2009 Kristaps Dzonsons <kristaps@bsd.lv>
+ *
+ * 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.
+ */
+
+/*
+ * This file defines the AT&T versions of the .At macro.  This probably
+ * isn't going to change.  The right-hand side is the formatted string.
+ *
+ * Be sure to escape strings.
+ * The non-breaking blanks prevent ending an output line right before
+ * a number.  Groff prevent line breaks at the same places.
+ */
+
+LINE("v1",		"Version\\~1 AT&T UNIX")
+LINE("v2",		"Version\\~2 AT&T UNIX")
+LINE("v3",		"Version\\~3 AT&T UNIX")
+LINE("v4",		"Version\\~4 AT&T UNIX")
+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("III",		"AT&T System\\~III UNIX")
+LINE("V",		"AT&T System\\~V UNIX")
+LINE("V.1",		"AT&T System\\~V Release\\~1 UNIX")
+LINE("V.2",		"AT&T System\\~V Release\\~2 UNIX")
+LINE("V.3",		"AT&T System\\~V Release\\~3 UNIX")
+LINE("V.4",		"AT&T System\\~V Release\\~4 UNIX")
diff --git a/usr/src/cmd/mandoc/chars.c b/usr/src/cmd/mandoc/chars.c
new file mode 100644
index 0000000..ce03347
--- /dev/null
+++ b/usr/src/cmd/mandoc/chars.c
@@ -0,0 +1,167 @@
+/*	$Id: chars.c,v 1.52 2011/11/08 00:15:23 kristaps Exp $ */
+/*
+ * Copyright (c) 2009, 2010, 2011 Kristaps Dzonsons <kristaps@bsd.lv>
+ * Copyright (c) 2011 Ingo Schwarze <schwarze@openbsd.org>
+ *
+ * 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.
+ */
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <assert.h>
+#include <ctype.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "mandoc.h"
+#include "libmandoc.h"
+
+#define	PRINT_HI	 126
+#define	PRINT_LO	 32
+
+struct	ln {
+	struct ln	 *next;
+	const char	 *code;
+	const char	 *ascii;
+	int		  unicode;
+};
+
+#define	LINES_MAX	  328
+
+#define CHAR(in, ch, code) \
+	{ NULL, (in), (ch), (code) },
+
+#define	CHAR_TBL_START	  static struct ln lines[LINES_MAX] = {
+#define	CHAR_TBL_END	  };
+
+#include "chars.in"
+
+struct	mchars {
+	struct ln	**htab;
+};
+
+static	const struct ln	 *find(const struct mchars *, 
+				const char *, size_t);
+
+void
+mchars_free(struct mchars *arg)
+{
+
+	free(arg->htab);
+	free(arg);
+}
+
+struct mchars *
+mchars_alloc(void)
+{
+	struct mchars	 *tab;
+	struct ln	**htab;
+	struct ln	 *pp;
+	int		  i, hash;
+
+	/*
+	 * Constructs a very basic chaining hashtable.  The hash routine
+	 * is simply the integral value of the first character.
+	 * Subsequent entries are chained in the order they're processed.
+	 */
+
+	tab = mandoc_malloc(sizeof(struct mchars));
+	htab = mandoc_calloc(PRINT_HI - PRINT_LO + 1, sizeof(struct ln **));
+
+	for (i = 0; i < LINES_MAX; i++) {
+		hash = (int)lines[i].code[0] - PRINT_LO;
+
+		if (NULL == (pp = htab[hash])) {
+			htab[hash] = &lines[i];
+			continue;
+		}
+
+		for ( ; pp->next; pp = pp->next)
+			/* Scan ahead. */ ;
+		pp->next = &lines[i];
+	}
+
+	tab->htab = htab;
+	return(tab);
+}
+
+int
+mchars_spec2cp(const struct mchars *arg, const char *p, size_t sz)
+{
+	const struct ln	*ln;
+
+	ln = find(arg, p, sz);
+	if (NULL == ln)
+		return(-1);
+	return(ln->unicode);
+}
+
+char
+mchars_num2char(const char *p, size_t sz)
+{
+	int		  i;
+
+	if ((i = mandoc_strntoi(p, sz, 10)) < 0)
+		return('\0');
+	return(i > 0 && i < 256 && isprint(i) ? 
+			/* LINTED */ i : '\0');
+}
+
+int
+mchars_num2uc(const char *p, size_t sz)
+{
+	int               i;
+
+	if ((i = mandoc_strntoi(p, sz, 16)) < 0)
+		return('\0');
+	/* FIXME: make sure we're not in a bogus range. */
+	return(i > 0x80 && i <= 0x10FFFF ? i : '\0');
+}
+
+const char *
+mchars_spec2str(const struct mchars *arg, 
+		const char *p, size_t sz, size_t *rsz)
+{
+	const struct ln	*ln;
+
+	ln = find(arg, p, sz);
+	if (NULL == ln) {
+		*rsz = 1;
+		return(NULL);
+	}
+
+	*rsz = strlen(ln->ascii);
+	return(ln->ascii);
+}
+
+static const struct ln *
+find(const struct mchars *tab, const char *p, size_t sz)
+{
+	const struct ln	 *pp;
+	int		  hash;
+
+	assert(p);
+
+	if (0 == sz || p[0] < PRINT_LO || p[0] > PRINT_HI)
+		return(NULL);
+
+	hash = (int)p[0] - PRINT_LO;
+
+	for (pp = tab->htab[hash]; pp; pp = pp->next)
+		if (0 == strncmp(pp->code, p, sz) && 
+				'\0' == pp->code[(int)sz])
+			return(pp);
+
+	return(NULL);
+}
diff --git a/usr/src/cmd/mandoc/chars.in b/usr/src/cmd/mandoc/chars.in
new file mode 100644
index 0000000..a4c45b3
--- /dev/null
+++ b/usr/src/cmd/mandoc/chars.in
@@ -0,0 +1,397 @@
+/*	$Id: chars.in,v 1.42 2011/10/02 10:02:26 kristaps Exp $ */
+/*
+ * Copyright (c) 2009, 2010, 2011 Kristaps Dzonsons <kristaps@bsd.lv>
+ *
+ * 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.
+ */
+
+/*
+ * The ASCII translation tables.  
+ *
+ * The left-hand side corresponds to the input sequence (\x, \(xx, \*(xx
+ * and so on) whose length is listed second element.  The right-hand
+ * side is what's produced by the front-end, with the fourth element
+ * being its length.
+ *
+ * XXX - C-escape strings!
+ * XXX - update LINES_MAX if adding more!
+ */
+
+/* Non-breaking, non-collapsing space uses unit separator. */
+static const char ascii_nbrsp[2] = { ASCII_NBRSP, '\0' };
+
+CHAR_TBL_START
+
+/* Spacing. */
+CHAR("c",			"",		0)
+CHAR("0",			" ",		8194)
+CHAR(" ",			ascii_nbrsp,	160)
+CHAR("~",			ascii_nbrsp,	160)
+CHAR("%",			"",		0)
+CHAR("&",			"",		0)
+CHAR("^",			"",		0)
+CHAR("|",			"",		0)
+CHAR("}",			"",		0)
+
+/* Accents. */
+CHAR("a\"",			"\"",		779)
+CHAR("a-",			"-",		175)
+CHAR("a.",			".",		729)
+CHAR("a^",			"^",		770)
+CHAR("\'",			"\'",		769)
+CHAR("aa",			"\'",		769)
+CHAR("ga",			"`",		768)
+CHAR("`",			"`",		768)
+CHAR("ab",			"`",		774)
+CHAR("ac",			",",		807)
+CHAR("ad",			"\"",		776)
+CHAR("ah",			"v",		711)
+CHAR("ao",			"o",		730)
+CHAR("a~",			"~",		771)
+CHAR("ho",			",",		808)
+CHAR("ha",			"^",		94)
+CHAR("ti",			"~",		126)
+
+/* Quotes. */
+CHAR("Bq",			",,",		8222)
+CHAR("bq",			",",		8218)
+CHAR("lq",			"``",		8220)
+CHAR("rq",			"\'\'",		8221)
+CHAR("oq",			"`",		8216)
+CHAR("cq",			"\'",		8217)
+CHAR("aq",			"\'",		39)
+CHAR("dq",			"\"",		34)
+CHAR("Fo",			"<<",		171)
+CHAR("Fc",			">>",		187)
+CHAR("fo",			"<",		8249)
+CHAR("fc",			">",		8250)
+
+/* Brackets. */
+CHAR("lB",			"[",		91)
+CHAR("rB",			"]",		93)
+CHAR("lC",			"{",		123)
+CHAR("rC",			"}",		125)
+CHAR("la",			"<",		60)
+CHAR("ra",			">",		62)
+CHAR("bv",			"|",		9130)
+CHAR("braceex",			"|",		9130)
+CHAR("bracketlefttp",		"|",		9121)
+CHAR("bracketleftbp",		"|",		9123)
+CHAR("bracketleftex",		"|",		9122)
+CHAR("bracketrighttp",		"|",		9124)
+CHAR("bracketrightbp",		"|",		9126)
+CHAR("bracketrightex",		"|",		9125)
+CHAR("lt",			",-",		9127)
+CHAR("bracelefttp",		",-",		9127)
+CHAR("lk",			"{",		9128)
+CHAR("braceleftmid",		"{",		9128)
+CHAR("lb",			",-",		9129)
+CHAR("braceleftbp",		"`-",		9129)
+CHAR("braceleftex",		"|",		9130)
+CHAR("rt",			"-.",		9131)
+CHAR("bracerighttp",		"-.",		9131)
+CHAR("rk",			"}",		9132)
+CHAR("bracerightmid",		"}",		9132)
+CHAR("rb",			"-\'",		9133)
+CHAR("bracerightbp",		"-\'",		9133)
+CHAR("bracerightex",		"|",		9130)
+CHAR("parenlefttp",		"/",		9115)
+CHAR("parenleftbp",		"\\",		9117)
+CHAR("parenleftex",		"|",		9116)
+CHAR("parenrighttp",		"\\",		9118)
+CHAR("parenrightbp",		"/",		9120)
+CHAR("parenrightex",		"|",		9119)
+
+/* Greek characters. */
+CHAR("*A",			"A",		913)
+CHAR("*B",			"B",		914)
+CHAR("*G",			"|",		915)
+CHAR("*D",			"/\\",		916)
+CHAR("*E",			"E",		917)
+CHAR("*Z",			"Z",		918)
+CHAR("*Y",			"H",		919)
+CHAR("*H",			"O",		920)
+CHAR("*I",			"I",		921)
+CHAR("*K",			"K",		922)
+CHAR("*L",			"/\\",		923)
+CHAR("*M",			"M",		924)
+CHAR("*N",			"N",		925)
+CHAR("*C",			"H",		926)
+CHAR("*O",			"O",		927)
+CHAR("*P",			"TT",		928)
+CHAR("*R",			"P",		929)
+CHAR("*S",			">",		931)
+CHAR("*T",			"T",		932)
+CHAR("*U",			"Y",		933)
+CHAR("*F",			"O_",		934)
+CHAR("*X",			"X",		935)
+CHAR("*Q",			"Y",		936)
+CHAR("*W",			"O",		937)
+CHAR("*a",			"a",		945)
+CHAR("*b",			"B",		946)
+CHAR("*g",			"y",		947)
+CHAR("*d",			"d",		948)
+CHAR("*e",			"e",		949)
+CHAR("*z",			"C",		950)
+CHAR("*y",			"n",		951)
+CHAR("*h",			"0",		952)
+CHAR("*i",			"i",		953)
+CHAR("*k",			"k",		954)
+CHAR("*l",			"\\",		955)
+CHAR("*m",			"u",		956)
+CHAR("*n",			"v",		957)
+CHAR("*c",			"E",		958)
+CHAR("*o",			"o",		959)
+CHAR("*p",			"n",		960)
+CHAR("*r",			"p",		961)
+CHAR("*s",			"o",		963)
+CHAR("*t",			"t",		964)
+CHAR("*u",			"u",		965)
+CHAR("*f",			"o",		981)
+CHAR("*x",			"x",		967)
+CHAR("*q",			"u",		968)
+CHAR("*w",			"w",		969)
+CHAR("+h",			"0",		977)
+CHAR("+f",			"o",		966)
+CHAR("+p",			"w",		982)
+CHAR("+e",			"e",		1013)
+CHAR("ts",			"s",		962)
+
+/* Accented letters. */
+CHAR(",C",			"C",		199)
+CHAR(",c",			"c",		231)
+CHAR("/L",			"L",		321)
+CHAR("/O",			"O",		216)
+CHAR("/l",			"l",		322)
+CHAR("/o",			"o",		248)
+CHAR("oA",			"A",		197)
+CHAR("oa",			"a",		229)
+CHAR(":A",			"A",		196)
+CHAR(":E",			"E",		203)
+CHAR(":I",			"I",		207)
+CHAR(":O",			"O",		214)
+CHAR(":U",			"U",		220)
+CHAR(":a",			"a",		228)
+CHAR(":e",			"e",		235)
+CHAR(":i",			"i",		239)
+CHAR(":o",			"o",		246)
+CHAR(":u",			"u",		252)
+CHAR(":y",			"y",		255)
+CHAR("\'A",			"A",		193)
+CHAR("\'E",			"E",		201)
+CHAR("\'I",			"I",		205)
+CHAR("\'O",			"O",		211)
+CHAR("\'U",			"U",		218)
+CHAR("\'a",			"a",		225)
+CHAR("\'e",			"e",		233)
+CHAR("\'i",			"i",		237)
+CHAR("\'o",			"o",		243)
+CHAR("\'u",			"u",		250)
+CHAR("^A",			"A",		194)
+CHAR("^E",			"E",		202)
+CHAR("^I",			"I",		206)
+CHAR("^O",			"O",		212)
+CHAR("^U",			"U",		219)
+CHAR("^a",			"a",		226)
+CHAR("^e",			"e",		234)
+CHAR("^i",			"i",		238)
+CHAR("^o",			"o",		244)
+CHAR("^u",			"u",		251)
+CHAR("`A",			"A",		192)
+CHAR("`E",			"E",		200)
+CHAR("`I",			"I",		204)
+CHAR("`O",			"O",		210)
+CHAR("`U",			"U",		217)
+CHAR("`a",			"a",		224)
+CHAR("`e",			"e",		232)
+CHAR("`i",			"i",		236)
+CHAR("`o",			"o",		242)
+CHAR("`u",			"u",		249)
+CHAR("~A",			"A",		195)
+CHAR("~N",			"N",		209)
+CHAR("~O",			"O",		213)
+CHAR("~a",			"a",		227)
+CHAR("~n",			"n",		241)
+CHAR("~o",			"o",		245)
+
+/* Arrows and lines. */
+CHAR("<-",			"<-",		8592)
+CHAR("->",			"->",		8594)
+CHAR("<>",			"<>",		8596)
+CHAR("da",			"v",		8595)
+CHAR("ua",			"^",		8593)
+CHAR("va",			"^v",		8597)
+CHAR("lA",			"<=",		8656)
+CHAR("rA",			"=>",		8658)
+CHAR("hA",			"<=>",		8660)
+CHAR("dA",			"v",		8659)
+CHAR("uA",			"^",		8657)
+CHAR("vA",			"^=v",		8661)
+
+/* Logic. */
+CHAR("AN",			"^",		8743)
+CHAR("OR",			"v",		8744)
+CHAR("no",			"~",		172)
+CHAR("tno",			"~",		172)
+CHAR("te",			"3",		8707)
+CHAR("fa",			"V",		8704)
+CHAR("st",			"-)",		8715)
+CHAR("tf",			".:.",		8756)
+CHAR("3d",			".:.",		8756)
+CHAR("or",			"|",		124)
+
+/* Mathematicals. */
+CHAR("pl",			"+",		43)
+CHAR("mi",			"-",		8722)
+CHAR("-",			"-",		45)
+CHAR("-+",			"-+",		8723)
+CHAR("+-",			"+-",		177)
+CHAR("t+-",			"+-",		177)
+CHAR("pc",			".",		183)
+CHAR("md",			".",		8901)
+CHAR("mu",			"x",		215)
+CHAR("tmu",			"x",		215)
+CHAR("c*",			"x",		8855)
+CHAR("c+",			"+",		8853)
+CHAR("di",			"-:-",		247)
+CHAR("tdi",			"-:-",		247)
+CHAR("f/",			"/",		8260)
+CHAR("**",			"*",		8727)
+CHAR("<=",			"<=",		8804)
+CHAR(">=",			">=",		8805)
+CHAR("<<",			"<<",		8810)
+CHAR(">>",			">>",		8811)
+CHAR("eq",			"=",		61)
+CHAR("!=",			"!=",		8800)
+CHAR("==",			"==",		8801)
+CHAR("ne",			"!==",		8802)
+CHAR("=~",			"=~",		8773)
+CHAR("-~",			"-~",		8771)
+CHAR("ap",			"~",		8764)
+CHAR("~~",			"~~",		8776)
+CHAR("~=",			"~=",		8780)
+CHAR("pt",			"oc",		8733)
+CHAR("es",			"{}",		8709)
+CHAR("mo",			"E",		8712)
+CHAR("nm",			"!E",		8713)
+CHAR("sb",			"(=",		8834)
+CHAR("nb",			"(!=",		8836)
+CHAR("sp",			"=)",		8835)
+CHAR("nc",			"!=)",		8837)
+CHAR("ib",			"(=",		8838)
+CHAR("ip",			"=)",		8839)
+CHAR("ca",			"(^)",		8745)
+CHAR("cu",			"U",		8746)
+CHAR("/_",			"/_",		8736)
+CHAR("pp",			"_|_",		8869)
+CHAR("is",			"I",		8747)
+CHAR("integral",		"I",		8747)
+CHAR("sum",			"E",		8721)
+CHAR("product",			"TT",		8719)
+CHAR("coproduct",		"U",		8720)
+CHAR("gr",			"V",		8711)
+CHAR("sr",			"\\/",		8730)
+CHAR("sqrt",			"\\/",		8730)
+CHAR("lc",			"|~",		8968)
+CHAR("rc",			"~|",		8969)
+CHAR("lf",			"|_",		8970)
+CHAR("rf",			"_|",		8971)
+CHAR("if",			"oo",		8734)
+CHAR("Ah",			"N",		8501)
+CHAR("Im",			"I",		8465)
+CHAR("Re",			"R",		8476)
+CHAR("pd",			"a",		8706)
+CHAR("-h",			"/h",		8463)
+CHAR("12",			"1/2",		189)
+CHAR("14",			"1/4",		188)
+CHAR("34",			"3/4",		190)
+
+/* Ligatures. */
+CHAR("ff",			"ff",		64256)
+CHAR("fi",			"fi",		64257)
+CHAR("fl",			"fl",		64258)
+CHAR("Fi",			"ffi",		64259)
+CHAR("Fl",			"ffl",		64260)
+CHAR("AE",			"AE",		198)
+CHAR("ae",			"ae",		230)
+CHAR("OE",			"OE",		338)
+CHAR("oe",			"oe",		339)
+CHAR("ss",			"ss",		223)
+CHAR("IJ",			"IJ",		306)
+CHAR("ij",			"ij",		307)
+
+/* Special letters. */
+CHAR("-D",			"D",		208)
+CHAR("Sd",			"o",		240)
+CHAR("TP",			"b",		222)
+CHAR("Tp",			"b",		254)
+CHAR(".i",			"i",		305)
+CHAR(".j",			"j",		567)
+
+/* Currency. */
+CHAR("Do",			"$",		36)
+CHAR("ct",			"c",		162)
+CHAR("Eu",			"EUR",		8364)
+CHAR("eu",			"EUR",		8364)
+CHAR("Ye",			"Y",		165)
+CHAR("Po",			"L",		163)
+CHAR("Cs",			"x",		164)
+CHAR("Fn",			"f",		402)
+
+/* Lines. */
+CHAR("ba",			"|",		124)
+CHAR("br",			"|",		9474)
+CHAR("ul",			"_",		95)
+CHAR("rl",			"-",		8254)
+CHAR("bb",			"|",		166)
+CHAR("sl",			"/",		47)
+CHAR("rs",			"\\",		92)
+
+/* Text markers. */
+CHAR("ci",			"o",		9675)
+CHAR("bu",			"o",		8226)
+CHAR("dd",			"=",		8225)
+CHAR("dg",			"-",		8224)
+CHAR("lz",			"<>",		9674)
+CHAR("sq",			"[]",		9633)
+CHAR("ps",			"9|",		182)
+CHAR("sc",			"S",		167)
+CHAR("lh",			"<=",		9756)
+CHAR("rh",			"=>",		9758)
+CHAR("at",			"@",		64)
+CHAR("sh",			"#",		35)
+CHAR("CR",			"_|",		8629)
+CHAR("OK",			"\\/",		10003)
+
+/* Legal symbols. */
+CHAR("co",			"(C)",		169)
+CHAR("rg",			"(R)",		174)
+CHAR("tm",			"tm",		8482)
+
+/* Punctuation. */
+CHAR(".",			".",		46)
+CHAR("r!",			"i",		161)
+CHAR("r?",			"c",		191)
+CHAR("em",			"--",		8212)
+CHAR("en",			"-",		8211)
+CHAR("hy",			"-",		8208)
+CHAR("e",			"\\",		92)
+
+/* Units. */
+CHAR("de",			"o",		176)
+CHAR("%0",			"%o",		8240)
+CHAR("fm",			"\'",		8242)
+CHAR("sd",			"\"",		8243)
+CHAR("mc",			"mu",		181)
+
+CHAR_TBL_END
diff --git a/usr/src/cmd/mandoc/config.h b/usr/src/cmd/mandoc/config.h
new file mode 100644
index 0000000..969e1b4
--- /dev/null
+++ b/usr/src/cmd/mandoc/config.h
@@ -0,0 +1,56 @@
+#ifndef	MANDOC_CONFIG_H
+#define	MANDOC_CONFIG_H
+
+#if defined(__linux__) || defined(__MINT__)
+# define _GNU_SOURCE /* strptime(), getsubopt() */
+#endif
+
+#include <stdio.h>
+
+#define HAVE_STRPTIME
+#define HAVE_GETSUBOPT
+#define HAVE_STRLCAT
+#define HAVE_STRLCPY
+
+#include <sys/types.h>
+
+#if !defined(__BEGIN_DECLS)
+#  ifdef __cplusplus
+#  define	__BEGIN_DECLS		extern "C" {
+#  else
+#  define	__BEGIN_DECLS
+#  endif
+#endif
+#if !defined(__END_DECLS)
+#  ifdef __cplusplus
+#  define	__END_DECLS		}
+#  else
+#  define	__END_DECLS
+#  endif
+#endif
+
+#if defined(__APPLE__)
+# define htobe32(x) OSSwapHostToBigInt32(x)
+# define betoh32(x) OSSwapBigToHostInt32(x)
+# define htobe64(x) OSSwapHostToBigInt64(x)
+# define betoh64(x) OSSwapBigToHostInt64(x)
+#elif defined(__linux__)
+# define betoh32(x) be32toh(x)
+# define betoh64(x) be64toh(x)
+#endif
+
+#ifndef HAVE_STRLCAT
+extern	size_t	  strlcat(char *, const char *, size_t);
+#endif
+#ifndef HAVE_STRLCPY
+extern	size_t	  strlcpy(char *, const char *, size_t);
+#endif
+#ifndef HAVE_GETSUBOPT
+extern	int	  getsubopt(char **, char * const *, char **);
+extern	char	 *suboptarg;
+#endif
+#ifndef HAVE_FGETLN
+extern	char	 *fgetln(FILE *, size_t *);
+#endif
+
+#endif /* MANDOC_CONFIG_H */
diff --git a/usr/src/cmd/mandoc/eqn.c b/usr/src/cmd/mandoc/eqn.c
new file mode 100644
index 0000000..37f01bc
--- /dev/null
+++ b/usr/src/cmd/mandoc/eqn.c
@@ -0,0 +1,949 @@
+/*	$Id: eqn.c,v 1.38 2011/07/25 15:37:00 kristaps Exp $ */
+/*
+ * Copyright (c) 2011 Kristaps Dzonsons <kristaps@bsd.lv>
+ *
+ * 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.
+ */
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <assert.h>
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+
+#include "mandoc.h"
+#include "libmandoc.h"
+#include "libroff.h"
+
+#define	EQN_NEST_MAX	 128 /* maximum nesting of defines */
+#define	EQN_MSG(t, x)	 mandoc_msg((t), (x)->parse, (x)->eqn.ln, (x)->eqn.pos, NULL)
+
+enum	eqn_rest {
+	EQN_DESCOPE,
+	EQN_ERR,
+	EQN_OK,
+	EQN_EOF
+};
+
+enum	eqn_symt {
+	EQNSYM_alpha,
+	EQNSYM_beta,
+	EQNSYM_chi,
+	EQNSYM_delta,
+	EQNSYM_epsilon,
+	EQNSYM_eta,
+	EQNSYM_gamma,
+	EQNSYM_iota,
+	EQNSYM_kappa,
+	EQNSYM_lambda,
+	EQNSYM_mu,
+	EQNSYM_nu,
+	EQNSYM_omega,
+	EQNSYM_omicron,
+	EQNSYM_phi,
+	EQNSYM_pi,
+	EQNSYM_ps,
+	EQNSYM_rho,
+	EQNSYM_sigma,
+	EQNSYM_tau,
+	EQNSYM_theta,
+	EQNSYM_upsilon,
+	EQNSYM_xi,
+	EQNSYM_zeta,
+	EQNSYM_DELTA,
+	EQNSYM_GAMMA,
+	EQNSYM_LAMBDA,
+	EQNSYM_OMEGA,
+	EQNSYM_PHI,
+	EQNSYM_PI,
+	EQNSYM_PSI,
+	EQNSYM_SIGMA,
+	EQNSYM_THETA,
+	EQNSYM_UPSILON,
+	EQNSYM_XI,
+	EQNSYM_inter,
+	EQNSYM_union,
+	EQNSYM_prod,
+	EQNSYM_int,
+	EQNSYM_sum,
+	EQNSYM_grad,
+	EQNSYM_del,
+	EQNSYM_times,
+	EQNSYM_cdot,
+	EQNSYM_nothing,
+	EQNSYM_approx,
+	EQNSYM_prime,
+	EQNSYM_half,
+	EQNSYM_partial,
+	EQNSYM_inf,
+	EQNSYM_muchgreat,
+	EQNSYM_muchless,
+	EQNSYM_larrow,
+	EQNSYM_rarrow,
+	EQNSYM_pm,
+	EQNSYM_nequal,
+	EQNSYM_equiv,
+	EQNSYM_lessequal,
+	EQNSYM_moreequal,
+	EQNSYM__MAX
+};
+
+enum	eqnpartt {
+	EQN_DEFINE = 0,
+	EQN_NDEFINE,
+	EQN_TDEFINE,
+	EQN_SET,
+	EQN_UNDEF,
+	EQN_GFONT,
+	EQN_GSIZE,
+	EQN_BACK,
+	EQN_FWD,
+	EQN_UP,
+	EQN_DOWN,
+	EQN__MAX
+};
+
+struct	eqnstr {
+	const char	*name;
+	size_t		 sz;
+};
+
+#define	STRNEQ(p1, sz1, p2, sz2) \
+	((sz1) == (sz2) && 0 == strncmp((p1), (p2), (sz1)))
+#define	EQNSTREQ(x, p, sz) \
+	STRNEQ((x)->name, (x)->sz, (p), (sz))
+
+struct	eqnpart {
+	struct eqnstr	 str;
+	int		(*fp)(struct eqn_node *);
+};
+
+struct	eqnsym {
+	struct eqnstr	 str;
+	const char	*sym;
+};
+
+
+static	enum eqn_rest	 eqn_box(struct eqn_node *, struct eqn_box *);
+static	struct eqn_box	*eqn_box_alloc(struct eqn_node *, 
+				struct eqn_box *);
+static	void		 eqn_box_free(struct eqn_box *);
+static	struct eqn_def	*eqn_def_find(struct eqn_node *, 
+				const char *, size_t);
+static	int		 eqn_do_gfont(struct eqn_node *);
+static	int		 eqn_do_gsize(struct eqn_node *);
+static	int		 eqn_do_define(struct eqn_node *);
+static	int		 eqn_do_ign1(struct eqn_node *);
+static	int		 eqn_do_ign2(struct eqn_node *);
+static	int		 eqn_do_tdefine(struct eqn_node *);
+static	int		 eqn_do_undef(struct eqn_node *);
+static	enum eqn_rest	 eqn_eqn(struct eqn_node *, struct eqn_box *);
+static	enum eqn_rest	 eqn_list(struct eqn_node *, struct eqn_box *);
+static	enum eqn_rest	 eqn_matrix(struct eqn_node *, struct eqn_box *);
+static	const char	*eqn_nexttok(struct eqn_node *, size_t *);
+static	const char	*eqn_nextrawtok(struct eqn_node *, size_t *);
+static	const char	*eqn_next(struct eqn_node *, 
+				char, size_t *, int);
+static	void		 eqn_rewind(struct eqn_node *);
+
+static	const struct eqnpart eqnparts[EQN__MAX] = {
+	{ { "define", 6 }, eqn_do_define }, /* EQN_DEFINE */
+	{ { "ndefine", 7 }, eqn_do_define }, /* EQN_NDEFINE */
+	{ { "tdefine", 7 }, eqn_do_tdefine }, /* EQN_TDEFINE */
+	{ { "set", 3 }, eqn_do_ign2 }, /* EQN_SET */
+	{ { "undef", 5 }, eqn_do_undef }, /* EQN_UNDEF */
+	{ { "gfont", 5 }, eqn_do_gfont }, /* EQN_GFONT */
+	{ { "gsize", 5 }, eqn_do_gsize }, /* EQN_GSIZE */
+	{ { "back", 4 }, eqn_do_ign1 }, /* EQN_BACK */
+	{ { "fwd", 3 }, eqn_do_ign1 }, /* EQN_FWD */
+	{ { "up", 2 }, eqn_do_ign1 }, /* EQN_UP */
+	{ { "down", 4 }, eqn_do_ign1 }, /* EQN_DOWN */
+};
+
+static	const struct eqnstr eqnmarks[EQNMARK__MAX] = {
+	{ "", 0 }, /* EQNMARK_NONE */
+	{ "dot", 3 }, /* EQNMARK_DOT */
+	{ "dotdot", 6 }, /* EQNMARK_DOTDOT */
+	{ "hat", 3 }, /* EQNMARK_HAT */
+	{ "tilde", 5 }, /* EQNMARK_TILDE */
+	{ "vec", 3 }, /* EQNMARK_VEC */
+	{ "dyad", 4 }, /* EQNMARK_DYAD */
+	{ "bar", 3 }, /* EQNMARK_BAR */
+	{ "under", 5 }, /* EQNMARK_UNDER */
+};
+
+static	const struct eqnstr eqnfonts[EQNFONT__MAX] = {
+	{ "", 0 }, /* EQNFONT_NONE */
+	{ "roman", 5 }, /* EQNFONT_ROMAN */
+	{ "bold", 4 }, /* EQNFONT_BOLD */
+	{ "fat", 3 }, /* EQNFONT_FAT */
+	{ "italic", 6 }, /* EQNFONT_ITALIC */
+};
+
+static	const struct eqnstr eqnposs[EQNPOS__MAX] = {
+	{ "", 0 }, /* EQNPOS_NONE */
+	{ "over", 4 }, /* EQNPOS_OVER */
+	{ "sup", 3 }, /* EQNPOS_SUP */
+	{ "sub", 3 }, /* EQNPOS_SUB */
+	{ "to", 2 }, /* EQNPOS_TO */
+	{ "from", 4 }, /* EQNPOS_FROM */
+};
+
+static	const struct eqnstr eqnpiles[EQNPILE__MAX] = {
+	{ "", 0 }, /* EQNPILE_NONE */
+	{ "pile", 4 }, /* EQNPILE_PILE */
+	{ "cpile", 5 }, /* EQNPILE_CPILE */
+	{ "rpile", 5 }, /* EQNPILE_RPILE */
+	{ "lpile", 5 }, /* EQNPILE_LPILE */
+	{ "col", 3 }, /* EQNPILE_COL */
+	{ "ccol", 4 }, /* EQNPILE_CCOL */
+	{ "rcol", 4 }, /* EQNPILE_RCOL */
+	{ "lcol", 4 }, /* EQNPILE_LCOL */
+};
+
+static	const struct eqnsym eqnsyms[EQNSYM__MAX] = {
+	{ { "alpha", 5 }, "*a" }, /* EQNSYM_alpha */
+	{ { "beta", 4 }, "*b" }, /* EQNSYM_beta */
+	{ { "chi", 3 }, "*x" }, /* EQNSYM_chi */
+	{ { "delta", 5 }, "*d" }, /* EQNSYM_delta */
+	{ { "epsilon", 7 }, "*e" }, /* EQNSYM_epsilon */
+	{ { "eta", 3 }, "*y" }, /* EQNSYM_eta */
+	{ { "gamma", 5 }, "*g" }, /* EQNSYM_gamma */
+	{ { "iota", 4 }, "*i" }, /* EQNSYM_iota */
+	{ { "kappa", 5 }, "*k" }, /* EQNSYM_kappa */
+	{ { "lambda", 6 }, "*l" }, /* EQNSYM_lambda */
+	{ { "mu", 2 }, "*m" }, /* EQNSYM_mu */
+	{ { "nu", 2 }, "*n" }, /* EQNSYM_nu */
+	{ { "omega", 5 }, "*w" }, /* EQNSYM_omega */
+	{ { "omicron", 7 }, "*o" }, /* EQNSYM_omicron */
+	{ { "phi", 3 }, "*f" }, /* EQNSYM_phi */
+	{ { "pi", 2 }, "*p" }, /* EQNSYM_pi */
+	{ { "psi", 2 }, "*q" }, /* EQNSYM_psi */
+	{ { "rho", 3 }, "*r" }, /* EQNSYM_rho */
+	{ { "sigma", 5 }, "*s" }, /* EQNSYM_sigma */
+	{ { "tau", 3 }, "*t" }, /* EQNSYM_tau */
+	{ { "theta", 5 }, "*h" }, /* EQNSYM_theta */
+	{ { "upsilon", 7 }, "*u" }, /* EQNSYM_upsilon */
+	{ { "xi", 2 }, "*c" }, /* EQNSYM_xi */
+	{ { "zeta", 4 }, "*z" }, /* EQNSYM_zeta */
+	{ { "DELTA", 5 }, "*D" }, /* EQNSYM_DELTA */
+	{ { "GAMMA", 5 }, "*G" }, /* EQNSYM_GAMMA */
+	{ { "LAMBDA", 6 }, "*L" }, /* EQNSYM_LAMBDA */
+	{ { "OMEGA", 5 }, "*W" }, /* EQNSYM_OMEGA */
+	{ { "PHI", 3 }, "*F" }, /* EQNSYM_PHI */
+	{ { "PI", 2 }, "*P" }, /* EQNSYM_PI */
+	{ { "PSI", 3 }, "*Q" }, /* EQNSYM_PSI */
+	{ { "SIGMA", 5 }, "*S" }, /* EQNSYM_SIGMA */
+	{ { "THETA", 5 }, "*H" }, /* EQNSYM_THETA */
+	{ { "UPSILON", 7 }, "*U" }, /* EQNSYM_UPSILON */
+	{ { "XI", 2 }, "*C" }, /* EQNSYM_XI */
+	{ { "inter", 5 }, "ca" }, /* EQNSYM_inter */
+	{ { "union", 5 }, "cu" }, /* EQNSYM_union */
+	{ { "prod", 4 }, "product" }, /* EQNSYM_prod */
+	{ { "int", 3 }, "integral" }, /* EQNSYM_int */
+	{ { "sum", 3 }, "sum" }, /* EQNSYM_sum */
+	{ { "grad", 4 }, "gr" }, /* EQNSYM_grad */
+	{ { "del", 3 }, "gr" }, /* EQNSYM_del */
+	{ { "times", 5 }, "mu" }, /* EQNSYM_times */
+	{ { "cdot", 4 }, "pc" }, /* EQNSYM_cdot */
+	{ { "nothing", 7 }, "&" }, /* EQNSYM_nothing */
+	{ { "approx", 6 }, "~~" }, /* EQNSYM_approx */
+	{ { "prime", 5 }, "aq" }, /* EQNSYM_prime */
+	{ { "half", 4 }, "12" }, /* EQNSYM_half */
+	{ { "partial", 7 }, "pd" }, /* EQNSYM_partial */
+	{ { "inf", 3 }, "if" }, /* EQNSYM_inf */
+	{ { ">>", 2 }, ">>" }, /* EQNSYM_muchgreat */
+	{ { "<<", 2 }, "<<" }, /* EQNSYM_muchless */
+	{ { "<-", 2 }, "<-" }, /* EQNSYM_larrow */
+	{ { "->", 2 }, "->" }, /* EQNSYM_rarrow */
+	{ { "+-", 2 }, "+-" }, /* EQNSYM_pm */
+	{ { "!=", 2 }, "!=" }, /* EQNSYM_nequal */
+	{ { "==", 2 }, "==" }, /* EQNSYM_equiv */
+	{ { "<=", 2 }, "<=" }, /* EQNSYM_lessequal */
+	{ { ">=", 2 }, ">=" }, /* EQNSYM_moreequal */
+};
+
+/* ARGSUSED */
+enum rofferr
+eqn_read(struct eqn_node **epp, int ln, 
+		const char *p, int pos, int *offs)
+{
+	size_t		 sz;
+	struct eqn_node	*ep;
+	enum rofferr	 er;
+
+	ep = *epp;
+
+	/*
+	 * If we're the terminating mark, unset our equation status and
+	 * validate the full equation.
+	 */
+
+	if (0 == strncmp(p, ".EN", 3)) {
+		er = eqn_end(epp);
+		p += 3;
+		while (' ' == *p || '\t' == *p)
+			p++;
+		if ('\0' == *p) 
+			return(er);
+		mandoc_msg(MANDOCERR_ARGSLOST, ep->parse, ln, pos, NULL);
+		return(er);
+	}
+
+	/*
+	 * Build up the full string, replacing all newlines with regular
+	 * whitespace.
+	 */
+
+	sz = strlen(p + pos) + 1;
+	ep->data = mandoc_realloc(ep->data, ep->sz + sz + 1);
+
+	/* First invocation: nil terminate the string. */
+
+	if (0 == ep->sz)
+		*ep->data = '\0';
+
+	ep->sz += sz;
+	strlcat(ep->data, p + pos, ep->sz + 1);
+	strlcat(ep->data, " ", ep->sz + 1);
+	return(ROFF_IGN);
+}
+
+struct eqn_node *
+eqn_alloc(const char *name, int pos, int line, struct mparse *parse)
+{
+	struct eqn_node	*p;
+	size_t		 sz;
+	const char	*end;
+
+	p = mandoc_calloc(1, sizeof(struct eqn_node));
+
+	if (name && '\0' != *name) {
+		sz = strlen(name);
+		assert(sz);
+		do {
+			sz--;
+			end = name + (int)sz;
+		} while (' ' == *end || '\t' == *end);
+		p->eqn.name = mandoc_strndup(name, sz + 1);
+	}
+
+	p->parse = parse;
+	p->eqn.ln = line;
+	p->eqn.pos = pos;
+	p->gsize = EQN_DEFSIZE;
+
+	return(p);
+}
+
+enum rofferr
+eqn_end(struct eqn_node **epp)
+{
+	struct eqn_node	*ep;
+	struct eqn_box	*root;
+	enum eqn_rest	 c;
+
+	ep = *epp;
+	*epp = NULL;
+
+	ep->eqn.root = mandoc_calloc(1, sizeof(struct eqn_box));
+
+	root = ep->eqn.root;
+	root->type = EQN_ROOT;
+
+	if (0 == ep->sz)
+		return(ROFF_IGN);
+
+	if (EQN_DESCOPE == (c = eqn_eqn(ep, root))) {
+		EQN_MSG(MANDOCERR_EQNNSCOPE, ep);
+		c = EQN_ERR;
+	}
+
+	return(EQN_EOF == c ? ROFF_EQN : ROFF_IGN);
+}
+
+static enum eqn_rest
+eqn_eqn(struct eqn_node *ep, struct eqn_box *last)
+{
+	struct eqn_box	*bp;
+	enum eqn_rest	 c;
+
+	bp = eqn_box_alloc(ep, last);
+	bp->type = EQN_SUBEXPR;
+
+	while (EQN_OK == (c = eqn_box(ep, bp)))
+		/* Spin! */ ;
+
+	return(c);
+}
+
+static enum eqn_rest
+eqn_matrix(struct eqn_node *ep, struct eqn_box *last)
+{
+	struct eqn_box	*bp;
+	const char	*start;
+	size_t		 sz;
+	enum eqn_rest	 c;
+
+	bp = eqn_box_alloc(ep, last);
+	bp->type = EQN_MATRIX;
+
+	if (NULL == (start = eqn_nexttok(ep, &sz))) {
+		EQN_MSG(MANDOCERR_EQNEOF, ep);
+		return(EQN_ERR);
+	}
+	if ( ! STRNEQ(start, sz, "{", 1)) {
+		EQN_MSG(MANDOCERR_EQNSYNT, ep);
+		return(EQN_ERR);
+	}
+
+	while (EQN_OK == (c = eqn_box(ep, bp)))
+		switch (bp->last->pile) {
+		case (EQNPILE_LCOL):
+			/* FALLTHROUGH */
+		case (EQNPILE_CCOL):
+			/* FALLTHROUGH */
+		case (EQNPILE_RCOL):
+			continue;
+		default:
+			EQN_MSG(MANDOCERR_EQNSYNT, ep);
+			return(EQN_ERR);
+		};
+
+	if (EQN_DESCOPE != c) {
+		if (EQN_EOF == c)
+			EQN_MSG(MANDOCERR_EQNEOF, ep);
+		return(EQN_ERR);
+	}
+
+	eqn_rewind(ep);
+	start = eqn_nexttok(ep, &sz);
+	assert(start);
+	if (STRNEQ(start, sz, "}", 1))
+		return(EQN_OK);
+
+	EQN_MSG(MANDOCERR_EQNBADSCOPE, ep);
+	return(EQN_ERR);
+}
+
+static enum eqn_rest
+eqn_list(struct eqn_node *ep, struct eqn_box *last)
+{
+	struct eqn_box	*bp;
+	const char	*start;
+	size_t		 sz;
+	enum eqn_rest	 c;
+
+	bp = eqn_box_alloc(ep, last);
+	bp->type = EQN_LIST;
+
+	if (NULL == (start = eqn_nexttok(ep, &sz))) {
+		EQN_MSG(MANDOCERR_EQNEOF, ep);
+		return(EQN_ERR);
+	}
+	if ( ! STRNEQ(start, sz, "{", 1)) {
+		EQN_MSG(MANDOCERR_EQNSYNT, ep);
+		return(EQN_ERR);
+	}
+
+	while (EQN_DESCOPE == (c = eqn_eqn(ep, bp))) {
+		eqn_rewind(ep);
+		start = eqn_nexttok(ep, &sz);
+		assert(start);
+		if ( ! STRNEQ(start, sz, "above", 5))
+			break;
+	}
+
+	if (EQN_DESCOPE != c) {
+		if (EQN_ERR != c)
+			EQN_MSG(MANDOCERR_EQNSCOPE, ep);
+		return(EQN_ERR);
+	}
+
+	eqn_rewind(ep);
+	start = eqn_nexttok(ep, &sz);
+	assert(start);
+	if (STRNEQ(start, sz, "}", 1))
+		return(EQN_OK);
+
+	EQN_MSG(MANDOCERR_EQNBADSCOPE, ep);
+	return(EQN_ERR);
+}
+
+static enum eqn_rest
+eqn_box(struct eqn_node *ep, struct eqn_box *last)
+{
+	size_t		 sz;
+	const char	*start;
+	char		*left;
+	char		 sym[64];
+	enum eqn_rest	 c;
+	int		 i, size;
+	struct eqn_box	*bp;
+
+	if (NULL == (start = eqn_nexttok(ep, &sz)))
+		return(EQN_EOF);
+
+	if (STRNEQ(start, sz, "}", 1))
+		return(EQN_DESCOPE);
+	else if (STRNEQ(start, sz, "right", 5))
+		return(EQN_DESCOPE);
+	else if (STRNEQ(start, sz, "above", 5))
+		return(EQN_DESCOPE);
+	else if (STRNEQ(start, sz, "mark", 4))
+		return(EQN_OK);
+	else if (STRNEQ(start, sz, "lineup", 6))
+		return(EQN_OK);
+
+	for (i = 0; i < (int)EQN__MAX; i++) {
+		if ( ! EQNSTREQ(&eqnparts[i].str, start, sz))
+			continue;
+		return((*eqnparts[i].fp)(ep) ? 
+				EQN_OK : EQN_ERR);
+	} 
+
+	if (STRNEQ(start, sz, "{", 1)) {
+		if (EQN_DESCOPE != (c = eqn_eqn(ep, last))) {
+			if (EQN_ERR != c)
+				EQN_MSG(MANDOCERR_EQNSCOPE, ep);
+			return(EQN_ERR);
+		}
+		eqn_rewind(ep);
+		start = eqn_nexttok(ep, &sz);
+		assert(start);
+		if (STRNEQ(start, sz, "}", 1))
+			return(EQN_OK);
+		EQN_MSG(MANDOCERR_EQNBADSCOPE, ep);
+		return(EQN_ERR);
+	} 
+
+	for (i = 0; i < (int)EQNPILE__MAX; i++) {
+		if ( ! EQNSTREQ(&eqnpiles[i], start, sz))
+			continue;
+		if (EQN_OK == (c = eqn_list(ep, last)))
+			last->last->pile = (enum eqn_pilet)i;
+		return(c);
+	}
+
+	if (STRNEQ(start, sz, "matrix", 6))
+		return(eqn_matrix(ep, last));
+
+	if (STRNEQ(start, sz, "left", 4)) {
+		if (NULL == (start = eqn_nexttok(ep, &sz))) {
+			EQN_MSG(MANDOCERR_EQNEOF, ep);
+			return(EQN_ERR);
+		}
+		left = mandoc_strndup(start, sz);
+		c = eqn_eqn(ep, last);
+		if (last->last)
+			last->last->left = left;
+		else
+			free(left);
+		if (EQN_DESCOPE != c)
+			return(c);
+		assert(last->last);
+		eqn_rewind(ep);
+		start = eqn_nexttok(ep, &sz);
+		assert(start);
+		if ( ! STRNEQ(start, sz, "right", 5))
+			return(EQN_DESCOPE);
+		if (NULL == (start = eqn_nexttok(ep, &sz))) {
+			EQN_MSG(MANDOCERR_EQNEOF, ep);
+			return(EQN_ERR);
+		}
+		last->last->right = mandoc_strndup(start, sz);
+		return(EQN_OK);
+	}
+
+	for (i = 0; i < (int)EQNPOS__MAX; i++) {
+		if ( ! EQNSTREQ(&eqnposs[i], start, sz))
+			continue;
+		if (NULL == last->last) {
+			EQN_MSG(MANDOCERR_EQNSYNT, ep);
+			return(EQN_ERR);
+		} 
+		last->last->pos = (enum eqn_post)i;
+		if (EQN_EOF == (c = eqn_box(ep, last))) {
+			EQN_MSG(MANDOCERR_EQNEOF, ep);
+			return(EQN_ERR);
+		}
+		return(c);
+	}
+
+	for (i = 0; i < (int)EQNMARK__MAX; i++) {
+		if ( ! EQNSTREQ(&eqnmarks[i], start, sz))
+			continue;
+		if (NULL == last->last) {
+			EQN_MSG(MANDOCERR_EQNSYNT, ep);
+			return(EQN_ERR);
+		} 
+		last->last->mark = (enum eqn_markt)i;
+		if (EQN_EOF == (c = eqn_box(ep, last))) {
+			EQN_MSG(MANDOCERR_EQNEOF, ep);
+			return(EQN_ERR);
+		}
+		return(c);
+	}
+
+	for (i = 0; i < (int)EQNFONT__MAX; i++) {
+		if ( ! EQNSTREQ(&eqnfonts[i], start, sz))
+			continue;
+		if (EQN_EOF == (c = eqn_box(ep, last))) {
+			EQN_MSG(MANDOCERR_EQNEOF, ep);
+			return(EQN_ERR);
+		} else if (EQN_OK == c)
+			last->last->font = (enum eqn_fontt)i;
+		return(c);
+	}
+
+	if (STRNEQ(start, sz, "size", 4)) {
+		if (NULL == (start = eqn_nexttok(ep, &sz))) {
+			EQN_MSG(MANDOCERR_EQNEOF, ep);
+			return(EQN_ERR);
+		}
+		size = mandoc_strntoi(start, sz, 10);
+		if (EQN_EOF == (c = eqn_box(ep, last))) {
+			EQN_MSG(MANDOCERR_EQNEOF, ep);
+			return(EQN_ERR);
+		} else if (EQN_OK != c)
+			return(c);
+		last->last->size = size;
+	}
+
+	bp = eqn_box_alloc(ep, last);
+	bp->type = EQN_TEXT;
+	for (i = 0; i < (int)EQNSYM__MAX; i++)
+		if (EQNSTREQ(&eqnsyms[i].str, start, sz)) {
+			sym[63] = '\0';
+			snprintf(sym, 62, "\\[%s]", eqnsyms[i].sym);
+			bp->text = mandoc_strdup(sym);
+			return(EQN_OK);
+		}
+
+	bp->text = mandoc_strndup(start, sz);
+	return(EQN_OK);
+}
+
+void
+eqn_free(struct eqn_node *p)
+{
+	int		 i;
+
+	eqn_box_free(p->eqn.root);
+
+	for (i = 0; i < (int)p->defsz; i++) {
+		free(p->defs[i].key);
+		free(p->defs[i].val);
+	}
+
+	free(p->eqn.name);
+	free(p->data);
+	free(p->defs);
+	free(p);
+}
+
+static struct eqn_box *
+eqn_box_alloc(struct eqn_node *ep, struct eqn_box *parent)
+{
+	struct eqn_box	*bp;
+
+	bp = mandoc_calloc(1, sizeof(struct eqn_box));
+	bp->parent = parent;
+	bp->size = ep->gsize;
+
+	if (NULL == parent->first)
+		parent->first = bp;
+	else
+		parent->last->next = bp;
+
+	parent->last = bp;
+	return(bp);
+}
+
+static void
+eqn_box_free(struct eqn_box *bp)
+{
+
+	if (bp->first)
+		eqn_box_free(bp->first);
+	if (bp->next)
+		eqn_box_free(bp->next);
+
+	free(bp->text);
+	free(bp->left);
+	free(bp->right);
+	free(bp);
+}
+
+static const char *
+eqn_nextrawtok(struct eqn_node *ep, size_t *sz)
+{
+
+	return(eqn_next(ep, '"', sz, 0));
+}
+
+static const char *
+eqn_nexttok(struct eqn_node *ep, size_t *sz)
+{
+
+	return(eqn_next(ep, '"', sz, 1));
+}
+
+static void
+eqn_rewind(struct eqn_node *ep)
+{
+
+	ep->cur = ep->rew;
+}
+
+static const char *
+eqn_next(struct eqn_node *ep, char quote, size_t *sz, int repl)
+{
+	char		*start, *next;
+	int		 q, diff, lim;
+	size_t		 ssz, dummy;
+	struct eqn_def	*def;
+
+	if (NULL == sz)
+		sz = &dummy;
+
+	lim = 0;
+	ep->rew = ep->cur;
+again:
+	/* Prevent self-definitions. */
+
+	if (lim >= EQN_NEST_MAX) {
+		EQN_MSG(MANDOCERR_ROFFLOOP, ep);
+		return(NULL);
+	}
+
+	ep->cur = ep->rew;
+	start = &ep->data[(int)ep->cur];
+	q = 0;
+
+	if ('\0' == *start)
+		return(NULL);
+
+	if (quote == *start) {
+		ep->cur++;
+		q = 1;
+	}
+
+	start = &ep->data[(int)ep->cur];
+
+	if ( ! q) {
+		if ('{' == *start || '}' == *start)
+			ssz = 1;
+		else
+			ssz = strcspn(start + 1, " ^~\"{}\t") + 1;
+		next = start + (int)ssz;
+		if ('\0' == *next)
+			next = NULL;
+	} else
+		next = strchr(start, quote);
+
+	if (NULL != next) {
+		*sz = (size_t)(next - start);
+		ep->cur += *sz;
+		if (q)
+			ep->cur++;
+		while (' ' == ep->data[(int)ep->cur] ||
+				'\t' == ep->data[(int)ep->cur] ||
+				'^' == ep->data[(int)ep->cur] ||
+				'~' == ep->data[(int)ep->cur])
+			ep->cur++;
+	} else {
+		if (q)
+			EQN_MSG(MANDOCERR_BADQUOTE, ep);
+		next = strchr(start, '\0');
+		*sz = (size_t)(next - start);
+		ep->cur += *sz;
+	}
+
+	/* Quotes aren't expanded for values. */
+
+	if (q || ! repl)
+		return(start);
+
+	if (NULL != (def = eqn_def_find(ep, start, *sz))) {
+		diff = def->valsz - *sz;
+
+		if (def->valsz > *sz) {
+			ep->sz += diff;
+			ep->data = mandoc_realloc(ep->data, ep->sz + 1);
+			ep->data[ep->sz] = '\0';
+			start = &ep->data[(int)ep->rew];
+		}
+
+		diff = def->valsz - *sz;
+		memmove(start + *sz + diff, start + *sz, 
+				(strlen(start) - *sz) + 1);
+		memcpy(start, def->val, def->valsz);
+		goto again;
+	}
+
+	return(start);
+}
+
+static int
+eqn_do_ign1(struct eqn_node *ep)
+{
+
+	if (NULL == eqn_nextrawtok(ep, NULL))
+		EQN_MSG(MANDOCERR_EQNEOF, ep);
+	else
+		return(1);
+
+	return(0);
+}
+
+static int
+eqn_do_ign2(struct eqn_node *ep)
+{
+
+	if (NULL == eqn_nextrawtok(ep, NULL))
+		EQN_MSG(MANDOCERR_EQNEOF, ep);
+	else if (NULL == eqn_nextrawtok(ep, NULL))
+		EQN_MSG(MANDOCERR_EQNEOF, ep);
+	else
+		return(1);
+
+	return(0);
+}
+
+static int
+eqn_do_tdefine(struct eqn_node *ep)
+{
+
+	if (NULL == eqn_nextrawtok(ep, NULL))
+		EQN_MSG(MANDOCERR_EQNEOF, ep);
+	else if (NULL == eqn_next(ep, ep->data[(int)ep->cur], NULL, 0))
+		EQN_MSG(MANDOCERR_EQNEOF, ep);
+	else
+		return(1);
+
+	return(0);
+}
+
+static int
+eqn_do_define(struct eqn_node *ep)
+{
+	const char	*start;
+	size_t		 sz;
+	struct eqn_def	*def;
+	int		 i;
+
+	if (NULL == (start = eqn_nextrawtok(ep, &sz))) {
+		EQN_MSG(MANDOCERR_EQNEOF, ep);
+		return(0);
+	}
+
+	/* 
+	 * Search for a key that already exists. 
+	 * Create a new key if none is found.
+	 */
+
+	if (NULL == (def = eqn_def_find(ep, start, sz))) {
+		/* Find holes in string array. */
+		for (i = 0; i < (int)ep->defsz; i++)
+			if (0 == ep->defs[i].keysz)
+				break;
+
+		if (i == (int)ep->defsz) {
+			ep->defsz++;
+			ep->defs = mandoc_realloc
+				(ep->defs, ep->defsz * 
+				 sizeof(struct eqn_def));
+			ep->defs[i].key = ep->defs[i].val = NULL;
+		}
+
+		ep->defs[i].keysz = sz;
+		ep->defs[i].key = mandoc_realloc
+			(ep->defs[i].key, sz + 1);
+
+		memcpy(ep->defs[i].key, start, sz);
+		ep->defs[i].key[(int)sz] = '\0';
+		def = &ep->defs[i];
+	}
+
+	start = eqn_next(ep, ep->data[(int)ep->cur], &sz, 0);
+
+	if (NULL == start) {
+		EQN_MSG(MANDOCERR_EQNEOF, ep);
+		return(0);
+	}
+
+	def->valsz = sz;
+	def->val = mandoc_realloc(def->val, sz + 1);
+	memcpy(def->val, start, sz);
+	def->val[(int)sz] = '\0';
+	return(1);
+}
+
+static int
+eqn_do_gfont(struct eqn_node *ep)
+{
+
+	if (NULL == eqn_nextrawtok(ep, NULL)) {
+		EQN_MSG(MANDOCERR_EQNEOF, ep);
+		return(0);
+	} 
+	return(1);
+}
+
+static int
+eqn_do_gsize(struct eqn_node *ep)
+{
+	const char	*start;
+	size_t		 sz;
+
+	if (NULL == (start = eqn_nextrawtok(ep, &sz))) {
+		EQN_MSG(MANDOCERR_EQNEOF, ep);
+		return(0);
+	} 
+	ep->gsize = mandoc_strntoi(start, sz, 10);
+	return(1);
+}
+
+static int
+eqn_do_undef(struct eqn_node *ep)
+{
+	const char	*start;
+	struct eqn_def	*def;
+	size_t		 sz;
+
+	if (NULL == (start = eqn_nextrawtok(ep, &sz))) {
+		EQN_MSG(MANDOCERR_EQNEOF, ep);
+		return(0);
+	} else if (NULL != (def = eqn_def_find(ep, start, sz)))
+		def->keysz = 0;
+
+	return(1);
+}
+
+static struct eqn_def *
+eqn_def_find(struct eqn_node *ep, const char *key, size_t sz)
+{
+	int		 i;
+
+	for (i = 0; i < (int)ep->defsz; i++) 
+		if (ep->defs[i].keysz && STRNEQ(ep->defs[i].key, 
+					ep->defs[i].keysz, key, sz))
+			return(&ep->defs[i]);
+
+	return(NULL);
+}
diff --git a/usr/src/cmd/mandoc/eqn_html.c b/usr/src/cmd/mandoc/eqn_html.c
new file mode 100644
index 0000000..80c82f1
--- /dev/null
+++ b/usr/src/cmd/mandoc/eqn_html.c
@@ -0,0 +1,81 @@
+/*	$Id: eqn_html.c,v 1.2 2011/07/24 10:09:03 kristaps Exp $ */
+/*
+ * Copyright (c) 2011 Kristaps Dzonsons <kristaps@bsd.lv>
+ *
+ * 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.
+ */
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <assert.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "mandoc.h"
+#include "out.h"
+#include "html.h"
+
+static	const enum htmltag fontmap[EQNFONT__MAX] = {
+	TAG_SPAN, /* EQNFONT_NONE */
+	TAG_SPAN, /* EQNFONT_ROMAN */
+	TAG_B, /* EQNFONT_BOLD */
+	TAG_B, /* EQNFONT_FAT */
+	TAG_I /* EQNFONT_ITALIC */
+};
+
+
+static void	eqn_box(struct html *, const struct eqn_box *);
+
+void
+print_eqn(struct html *p, const struct eqn *ep)
+{
+	struct htmlpair	 tag;
+	struct tag	*t;
+
+	PAIR_CLASS_INIT(&tag, "eqn");
+	t = print_otag(p, TAG_SPAN, 1, &tag);
+
+	p->flags |= HTML_NONOSPACE;
+	eqn_box(p, ep->root);
+	p->flags &= ~HTML_NONOSPACE;
+
+	print_tagq(p, t);
+}
+
+static void
+eqn_box(struct html *p, const struct eqn_box *bp)
+{
+	struct tag	*t;
+
+	t = EQNFONT_NONE == bp->font ? NULL : 
+		print_otag(p, fontmap[(int)bp->font], 0, NULL);
+
+	if (bp->left)
+		print_text(p, bp->left);
+	
+	if (bp->text)
+		print_text(p, bp->text);
+
+	if (bp->first)
+		eqn_box(p, bp->first);
+
+	if (NULL != t)
+		print_tagq(p, t);
+	if (bp->right)
+		print_text(p, bp->right);
+
+	if (bp->next)
+		eqn_box(p, bp->next);
+}
diff --git a/usr/src/cmd/mandoc/eqn_term.c b/usr/src/cmd/mandoc/eqn_term.c
new file mode 100644
index 0000000..cfbd8d4
--- /dev/null
+++ b/usr/src/cmd/mandoc/eqn_term.c
@@ -0,0 +1,76 @@
+/*	$Id: eqn_term.c,v 1.4 2011/07/24 10:09:03 kristaps Exp $ */
+/*
+ * Copyright (c) 2011 Kristaps Dzonsons <kristaps@bsd.lv>
+ *
+ * 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.
+ */
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <assert.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "mandoc.h"
+#include "out.h"
+#include "term.h"
+
+static	const enum termfont fontmap[EQNFONT__MAX] = {
+	TERMFONT_NONE, /* EQNFONT_NONE */
+	TERMFONT_NONE, /* EQNFONT_ROMAN */
+	TERMFONT_BOLD, /* EQNFONT_BOLD */
+	TERMFONT_BOLD, /* EQNFONT_FAT */
+	TERMFONT_UNDER /* EQNFONT_ITALIC */
+};
+
+static void	eqn_box(struct termp *, const struct eqn_box *);
+
+void
+term_eqn(struct termp *p, const struct eqn *ep)
+{
+
+	p->flags |= TERMP_NONOSPACE;
+	eqn_box(p, ep->root);
+	term_word(p, " ");
+	p->flags &= ~TERMP_NONOSPACE;
+}
+
+static void
+eqn_box(struct termp *p, const struct eqn_box *bp)
+{
+
+	if (EQNFONT_NONE != bp->font)
+		term_fontpush(p, fontmap[(int)bp->font]);
+	if (bp->left)
+		term_word(p, bp->left);
+	if (EQN_SUBEXPR == bp->type)
+		term_word(p, "(");
+
+	if (bp->text)
+		term_word(p, bp->text);
+
+	if (bp->first)
+		eqn_box(p, bp->first);
+
+	if (EQN_SUBEXPR == bp->type)
+		term_word(p, ")");
+	if (bp->right)
+		term_word(p, bp->right);
+	if (EQNFONT_NONE != bp->font) 
+		term_fontpop(p);
+
+	if (bp->next)
+		eqn_box(p, bp->next);
+}
diff --git a/usr/src/cmd/mandoc/html.c b/usr/src/cmd/mandoc/html.c
new file mode 100644
index 0000000..326df03
--- /dev/null
+++ b/usr/src/cmd/mandoc/html.c
@@ -0,0 +1,699 @@
+/*	$Id: html.c,v 1.150 2011/10/05 21:35:17 kristaps Exp $ */
+/*
+ * Copyright (c) 2008, 2009, 2010, 2011 Kristaps Dzonsons <kristaps@bsd.lv>
+ * Copyright (c) 2011 Ingo Schwarze <schwarze@openbsd.org>
+ *
+ * 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.
+ */
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <sys/types.h>
+
+#include <assert.h>
+#include <ctype.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "mandoc.h"
+#include "libmandoc.h"
+#include "out.h"
+#include "html.h"
+#include "main.h"
+
+struct	htmldata {
+	const char	 *name;
+	int		  flags;
+#define	HTML_CLRLINE	 (1 << 0)
+#define	HTML_NOSTACK	 (1 << 1)
+#define	HTML_AUTOCLOSE	 (1 << 2) /* Tag has auto-closure. */
+};
+
+static	const struct htmldata htmltags[TAG_MAX] = {
+	{"html",	HTML_CLRLINE}, /* TAG_HTML */
+	{"head",	HTML_CLRLINE}, /* TAG_HEAD */
+	{"body",	HTML_CLRLINE}, /* TAG_BODY */
+	{"meta",	HTML_CLRLINE | HTML_NOSTACK | HTML_AUTOCLOSE}, /* TAG_META */
+	{"title",	HTML_CLRLINE}, /* TAG_TITLE */
+	{"div",		HTML_CLRLINE}, /* TAG_DIV */
+	{"h1",		0}, /* TAG_H1 */
+	{"h2",		0}, /* TAG_H2 */
+	{"span",	0}, /* TAG_SPAN */
+	{"link",	HTML_CLRLINE | HTML_NOSTACK | HTML_AUTOCLOSE}, /* TAG_LINK */
+	{"br",		HTML_CLRLINE | HTML_NOSTACK | HTML_AUTOCLOSE}, /* TAG_BR */
+	{"a",		0}, /* TAG_A */
+	{"table",	HTML_CLRLINE}, /* TAG_TABLE */
+	{"tbody",	HTML_CLRLINE}, /* TAG_TBODY */
+	{"col",		HTML_CLRLINE | HTML_NOSTACK | HTML_AUTOCLOSE}, /* TAG_COL */
+	{"tr",		HTML_CLRLINE}, /* TAG_TR */
+	{"td",		HTML_CLRLINE}, /* TAG_TD */
+	{"li",		HTML_CLRLINE}, /* TAG_LI */
+	{"ul",		HTML_CLRLINE}, /* TAG_UL */
+	{"ol",		HTML_CLRLINE}, /* TAG_OL */
+	{"dl",		HTML_CLRLINE}, /* TAG_DL */
+	{"dt",		HTML_CLRLINE}, /* TAG_DT */
+	{"dd",		HTML_CLRLINE}, /* TAG_DD */
+	{"blockquote",	HTML_CLRLINE}, /* TAG_BLOCKQUOTE */
+	{"p",		HTML_CLRLINE | HTML_NOSTACK | HTML_AUTOCLOSE}, /* TAG_P */
+	{"pre",		HTML_CLRLINE }, /* TAG_PRE */
+	{"b",		0 }, /* TAG_B */
+	{"i",		0 }, /* TAG_I */
+	{"code",	0 }, /* TAG_CODE */
+	{"small",	0 }, /* TAG_SMALL */
+};
+
+static	const char	*const htmlattrs[ATTR_MAX] = {
+	"http-equiv", /* ATTR_HTTPEQUIV */
+	"content", /* ATTR_CONTENT */
+	"name", /* ATTR_NAME */
+	"rel", /* ATTR_REL */
+	"href", /* ATTR_HREF */
+	"type", /* ATTR_TYPE */
+	"media", /* ATTR_MEDIA */
+	"class", /* ATTR_CLASS */
+	"style", /* ATTR_STYLE */
+	"width", /* ATTR_WIDTH */
+	"id", /* ATTR_ID */
+	"summary", /* ATTR_SUMMARY */
+	"align", /* ATTR_ALIGN */
+	"colspan", /* ATTR_COLSPAN */
+};
+
+static	const char	*const roffscales[SCALE_MAX] = {
+	"cm", /* SCALE_CM */
+	"in", /* SCALE_IN */
+	"pc", /* SCALE_PC */
+	"pt", /* SCALE_PT */
+	"em", /* SCALE_EM */
+	"em", /* SCALE_MM */
+	"ex", /* SCALE_EN */
+	"ex", /* SCALE_BU */
+	"em", /* SCALE_VS */
+	"ex", /* SCALE_FS */
+};
+
+static	void	 bufncat(struct html *, const char *, size_t);
+static	void	 print_ctag(struct html *, enum htmltag);
+static	int	 print_encode(struct html *, const char *, int);
+static	void	 print_metaf(struct html *, enum mandoc_esc);
+static	void	 print_attr(struct html *, const char *, const char *);
+static	void	 *ml_alloc(char *, enum htmltype);
+
+static void *
+ml_alloc(char *outopts, enum htmltype type)
+{
+	struct html	*h;
+	const char	*toks[5];
+	char		*v;
+
+	toks[0] = "style";
+	toks[1] = "man";
+	toks[2] = "includes";
+	toks[3] = "fragment";
+	toks[4] = NULL;
+
+	h = mandoc_calloc(1, sizeof(struct html));
+
+	h->type = type;
+	h->tags.head = NULL;
+	h->symtab = mchars_alloc();
+
+	while (outopts && *outopts)
+		switch (getsubopt(&outopts, UNCONST(toks), &v)) {
+		case (0):
+			h->style = v;
+			break;
+		case (1):
+			h->base_man = v;
+			break;
+		case (2):
+			h->base_includes = v;
+			break;
+		case (3):
+			h->oflags |= HTML_FRAGMENT;
+			break;
+		default:
+			break;
+		}
+
+	return(h);
+}
+
+void *
+html_alloc(char *outopts)
+{
+
+	return(ml_alloc(outopts, HTML_HTML_4_01_STRICT));
+}
+
+
+void *
+xhtml_alloc(char *outopts)
+{
+
+	return(ml_alloc(outopts, HTML_XHTML_1_0_STRICT));
+}
+
+
+void
+html_free(void *p)
+{
+	struct tag	*tag;
+	struct html	*h;
+
+	h = (struct html *)p;
+
+	while ((tag = h->tags.head) != NULL) {
+		h->tags.head = tag->next;	
+		free(tag);
+	}
+	
+	if (h->symtab)
+		mchars_free(h->symtab);
+
+	free(h);
+}
+
+
+void
+print_gen_head(struct html *h)
+{
+	struct htmlpair	 tag[4];
+
+	tag[0].key = ATTR_HTTPEQUIV;
+	tag[0].val = "Content-Type";
+	tag[1].key = ATTR_CONTENT;
+	tag[1].val = "text/html; charset=utf-8";
+	print_otag(h, TAG_META, 2, tag);
+
+	tag[0].key = ATTR_NAME;
+	tag[0].val = "resource-type";
+	tag[1].key = ATTR_CONTENT;
+	tag[1].val = "document";
+	print_otag(h, TAG_META, 2, tag);
+
+	if (h->style) {
+		tag[0].key = ATTR_REL;
+		tag[0].val = "stylesheet";
+		tag[1].key = ATTR_HREF;
+		tag[1].val = h->style;
+		tag[2].key = ATTR_TYPE;
+		tag[2].val = "text/css";
+		tag[3].key = ATTR_MEDIA;
+		tag[3].val = "all";
+		print_otag(h, TAG_LINK, 4, tag);
+	}
+}
+
+static void
+print_metaf(struct html *h, enum mandoc_esc deco)
+{
+	enum htmlfont	 font;
+
+	switch (deco) {
+	case (ESCAPE_FONTPREV):
+		font = h->metal;
+		break;
+	case (ESCAPE_FONTITALIC):
+		font = HTMLFONT_ITALIC;
+		break;
+	case (ESCAPE_FONTBOLD):
+		font = HTMLFONT_BOLD;
+		break;
+	case (ESCAPE_FONT):
+		/* FALLTHROUGH */
+	case (ESCAPE_FONTROMAN):
+		font = HTMLFONT_NONE;
+		break;
+	default:
+		abort();
+		/* NOTREACHED */
+	}
+
+	if (h->metaf) {
+		print_tagq(h, h->metaf);
+		h->metaf = NULL;
+	}
+
+	h->metal = h->metac;
+	h->metac = font;
+
+	if (HTMLFONT_NONE != font)
+		h->metaf = HTMLFONT_BOLD == font ?
+			print_otag(h, TAG_B, 0, NULL) :
+			print_otag(h, TAG_I, 0, NULL);
+}
+
+int
+html_strlen(const char *cp)
+{
+	int		 ssz, sz;
+	const char	*seq, *p;
+
+	/*
+	 * Account for escaped sequences within string length
+	 * calculations.  This follows the logic in term_strlen() as we
+	 * must calculate the width of produced strings.
+	 * Assume that characters are always width of "1".  This is
+	 * hacky, but it gets the job done for approximation of widths.
+	 */
+
+	sz = 0;
+	while (NULL != (p = strchr(cp, '\\'))) {
+		sz += (int)(p - cp);
+		++cp;
+		switch (mandoc_escape(&cp, &seq, &ssz)) {
+		case (ESCAPE_ERROR):
+			return(sz);
+		case (ESCAPE_UNICODE):
+			/* FALLTHROUGH */
+		case (ESCAPE_NUMBERED):
+			/* FALLTHROUGH */
+		case (ESCAPE_SPECIAL):
+			sz++;
+			break;
+		default:
+			break;
+		}
+	}
+
+	assert(sz >= 0);
+	return(sz + strlen(cp));
+}
+
+static int
+print_encode(struct html *h, const char *p, int norecurse)
+{
+	size_t		 sz;
+	int		 c, len, nospace;
+	const char	*seq;
+	enum mandoc_esc	 esc;
+	static const char rejs[6] = { '\\', '<', '>', '&', ASCII_HYPH, '\0' };
+
+	nospace = 0;
+
+	while ('\0' != *p) {
+		sz = strcspn(p, rejs);
+
+		fwrite(p, 1, sz, stdout);
+		p += (int)sz;
+
+		if ('\0' == *p)
+			break;
+
+		switch (*p++) {
+		case ('<'):
+			printf("&lt;");
+			continue;
+		case ('>'):
+			printf("&gt;");
+			continue;
+		case ('&'):
+			printf("&amp;");
+			continue;
+		case (ASCII_HYPH):
+			putchar('-');
+			continue;
+		default:
+			break;
+		}
+
+		esc = mandoc_escape(&p, &seq, &len);
+		if (ESCAPE_ERROR == esc)
+			break;
+
+		switch (esc) {
+		case (ESCAPE_UNICODE):
+			/* Skip passed "u" header. */
+			c = mchars_num2uc(seq + 1, len - 1);
+			if ('\0' != c)
+				printf("&#x%x;", c);
+			break;
+		case (ESCAPE_NUMBERED):
+			c = mchars_num2char(seq, len);
+			if ('\0' != c)
+				putchar(c);
+			break;
+		case (ESCAPE_SPECIAL):
+			c = mchars_spec2cp(h->symtab, seq, len);
+			if (c > 0)
+				printf("&#%d;", c);
+			else if (-1 == c && 1 == len)
+				putchar((int)*seq);
+			break;
+		case (ESCAPE_FONT):
+			/* FALLTHROUGH */
+		case (ESCAPE_FONTPREV):
+			/* FALLTHROUGH */
+		case (ESCAPE_FONTBOLD):
+			/* FALLTHROUGH */
+		case (ESCAPE_FONTITALIC):
+			/* FALLTHROUGH */
+		case (ESCAPE_FONTROMAN):
+			if (norecurse)
+				break;
+			print_metaf(h, esc);
+			break;
+		case (ESCAPE_NOSPACE):
+			if ('\0' == *p)
+				nospace = 1;
+			break;
+		default:
+			break;
+		}
+	}
+
+	return(nospace);
+}
+
+
+static void
+print_attr(struct html *h, const char *key, const char *val)
+{
+	printf(" %s=\"", key);
+	(void)print_encode(h, val, 1);
+	putchar('\"');
+}
+
+
+struct tag *
+print_otag(struct html *h, enum htmltag tag, 
+		int sz, const struct htmlpair *p)
+{
+	int		 i;
+	struct tag	*t;
+
+	/* Push this tags onto the stack of open scopes. */
+
+	if ( ! (HTML_NOSTACK & htmltags[tag].flags)) {
+		t = mandoc_malloc(sizeof(struct tag));
+		t->tag = tag;
+		t->next = h->tags.head;
+		h->tags.head = t;
+	} else
+		t = NULL;
+
+	if ( ! (HTML_NOSPACE & h->flags))
+		if ( ! (HTML_CLRLINE & htmltags[tag].flags)) {
+			/* Manage keeps! */
+			if ( ! (HTML_KEEP & h->flags)) {
+				if (HTML_PREKEEP & h->flags)
+					h->flags |= HTML_KEEP;
+				putchar(' ');
+			} else
+				printf("&#160;");
+		}
+
+	if ( ! (h->flags & HTML_NONOSPACE))
+		h->flags &= ~HTML_NOSPACE;
+	else
+		h->flags |= HTML_NOSPACE;
+
+	/* Print out the tag name and attributes. */
+
+	printf("<%s", htmltags[tag].name);
+	for (i = 0; i < sz; i++)
+		print_attr(h, htmlattrs[p[i].key], p[i].val);
+
+	/* Add non-overridable attributes. */
+
+	if (TAG_HTML == tag && HTML_XHTML_1_0_STRICT == h->type) {
+		print_attr(h, "xmlns", "http://www.w3.org/1999/xhtml");
+		print_attr(h, "xml:lang", "en");
+		print_attr(h, "lang", "en");
+	}
+
+	/* Accommodate for XML "well-formed" singleton escaping. */
+
+	if (HTML_AUTOCLOSE & htmltags[tag].flags)
+		switch (h->type) {
+		case (HTML_XHTML_1_0_STRICT):
+			putchar('/');
+			break;
+		default:
+			break;
+		}
+
+	putchar('>');
+
+	h->flags |= HTML_NOSPACE;
+
+	if ((HTML_AUTOCLOSE | HTML_CLRLINE) & htmltags[tag].flags)
+		putchar('\n');
+
+	return(t);
+}
+
+
+static void
+print_ctag(struct html *h, enum htmltag tag)
+{
+	
+	printf("</%s>", htmltags[tag].name);
+	if (HTML_CLRLINE & htmltags[tag].flags) {
+		h->flags |= HTML_NOSPACE;
+		putchar('\n');
+	} 
+}
+
+void
+print_gen_decls(struct html *h)
+{
+	const char	*doctype;
+	const char	*dtd;
+	const char	*name;
+
+	switch (h->type) {
+	case (HTML_HTML_4_01_STRICT):
+		name = "HTML";
+		doctype = "-//W3C//DTD HTML 4.01//EN";
+		dtd = "http://www.w3.org/TR/html4/strict.dtd";
+		break;
+	default:
+		puts("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
+		name = "html";
+		doctype = "-//W3C//DTD XHTML 1.0 Strict//EN";
+		dtd = "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd";
+		break;
+	}
+
+	printf("<!DOCTYPE %s PUBLIC \"%s\" \"%s\">\n", 
+			name, doctype, dtd);
+}
+
+void
+print_text(struct html *h, const char *word)
+{
+
+	if ( ! (HTML_NOSPACE & h->flags)) {
+		/* Manage keeps! */
+		if ( ! (HTML_KEEP & h->flags)) {
+			if (HTML_PREKEEP & h->flags)
+				h->flags |= HTML_KEEP;
+			putchar(' ');
+		} else
+			printf("&#160;");
+	}
+
+	assert(NULL == h->metaf);
+	if (HTMLFONT_NONE != h->metac)
+		h->metaf = HTMLFONT_BOLD == h->metac ?
+			print_otag(h, TAG_B, 0, NULL) :
+			print_otag(h, TAG_I, 0, NULL);
+
+	assert(word);
+	if ( ! print_encode(h, word, 0)) {
+		if ( ! (h->flags & HTML_NONOSPACE))
+			h->flags &= ~HTML_NOSPACE;
+	} else
+		h->flags |= HTML_NOSPACE;
+
+	if (h->metaf) {
+		print_tagq(h, h->metaf);
+		h->metaf = NULL;
+	}
+
+	h->flags &= ~HTML_IGNDELIM;
+}
+
+
+void
+print_tagq(struct html *h, const struct tag *until)
+{
+	struct tag	*tag;
+
+	while ((tag = h->tags.head) != NULL) {
+		/* 
+		 * Remember to close out and nullify the current
+		 * meta-font and table, if applicable.
+		 */
+		if (tag == h->metaf)
+			h->metaf = NULL;
+		if (tag == h->tblt)
+			h->tblt = NULL;
+		print_ctag(h, tag->tag);
+		h->tags.head = tag->next;
+		free(tag);
+		if (until && tag == until)
+			return;
+	}
+}
+
+
+void
+print_stagq(struct html *h, const struct tag *suntil)
+{
+	struct tag	*tag;
+
+	while ((tag = h->tags.head) != NULL) {
+		if (suntil && tag == suntil)
+			return;
+		/* 
+		 * Remember to close out and nullify the current
+		 * meta-font and table, if applicable.
+		 */
+		if (tag == h->metaf)
+			h->metaf = NULL;
+		if (tag == h->tblt)
+			h->tblt = NULL;
+		print_ctag(h, tag->tag);
+		h->tags.head = tag->next;
+		free(tag);
+	}
+}
+
+void
+bufinit(struct html *h)
+{
+
+	h->buf[0] = '\0';
+	h->buflen = 0;
+}
+
+void
+bufcat_style(struct html *h, const char *key, const char *val)
+{
+
+	bufcat(h, key);
+	bufcat(h, ":");
+	bufcat(h, val);
+	bufcat(h, ";");
+}
+
+void
+bufcat(struct html *h, const char *p)
+{
+
+	h->buflen = strlcat(h->buf, p, BUFSIZ);
+	assert(h->buflen < BUFSIZ);
+}
+
+void
+bufcat_fmt(struct html *h, const char *fmt, ...)
+{
+	va_list		 ap;
+
+	va_start(ap, fmt);
+	(void)vsnprintf(h->buf + (int)h->buflen, 
+			BUFSIZ - h->buflen - 1, fmt, ap);
+	va_end(ap);
+	h->buflen = strlen(h->buf);
+}
+
+static void
+bufncat(struct html *h, const char *p, size_t sz)
+{
+
+	assert(h->buflen + sz + 1 < BUFSIZ);
+	strncat(h->buf, p, sz);
+	h->buflen += sz;
+}
+
+void
+buffmt_includes(struct html *h, const char *name)
+{
+	const char	*p, *pp;
+
+	pp = h->base_includes;
+	
+	bufinit(h);
+	while (NULL != (p = strchr(pp, '%'))) {
+		bufncat(h, pp, (size_t)(p - pp));
+		switch (*(p + 1)) {
+		case('I'):
+			bufcat(h, name);
+			break;
+		default:
+			bufncat(h, p, 2);
+			break;
+		}
+		pp = p + 2;
+	}
+	if (pp)
+		bufcat(h, pp);
+}
+
+void
+buffmt_man(struct html *h, 
+		const char *name, const char *sec)
+{
+	const char	*p, *pp;
+
+	pp = h->base_man;
+	
+	bufinit(h);
+	while (NULL != (p = strchr(pp, '%'))) {
+		bufncat(h, pp, (size_t)(p - pp));
+		switch (*(p + 1)) {
+		case('S'):
+			bufcat(h, sec ? sec : "1");
+			break;
+		case('N'):
+			bufcat_fmt(h, name);
+			break;
+		default:
+			bufncat(h, p, 2);
+			break;
+		}
+		pp = p + 2;
+	}
+	if (pp)
+		bufcat(h, pp);
+}
+
+void
+bufcat_su(struct html *h, const char *p, const struct roffsu *su)
+{
+	double		 v;
+
+	v = su->scale;
+	if (SCALE_MM == su->unit && 0.0 == (v /= 100.0))
+		v = 1.0;
+
+	bufcat_fmt(h, "%s: %.2f%s;", p, v, roffscales[su->unit]);
+}
+
+void
+bufcat_id(struct html *h, const char *src)
+{
+
+	/* Cf. <http://www.w3.org/TR/html4/types.html#h-6.2>. */
+
+	while ('\0' != *src)
+		bufcat_fmt(h, "%.2x", *src++);
+}
diff --git a/usr/src/cmd/mandoc/html.h b/usr/src/cmd/mandoc/html.h
new file mode 100644
index 0000000..6096070
--- /dev/null
+++ b/usr/src/cmd/mandoc/html.h
@@ -0,0 +1,164 @@
+/*	$Id: html.h,v 1.47 2011/10/05 21:35:17 kristaps Exp $ */
+/*
+ * Copyright (c) 2008, 2009, 2010, 2011 Kristaps Dzonsons <kristaps@bsd.lv>
+ *
+ * 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.
+ */
+#ifndef HTML_H
+#define HTML_H
+
+__BEGIN_DECLS
+
+enum	htmltag {
+	TAG_HTML,
+	TAG_HEAD,
+	TAG_BODY,
+	TAG_META,
+	TAG_TITLE,
+	TAG_DIV,
+	TAG_H1,
+	TAG_H2,
+	TAG_SPAN,
+	TAG_LINK,
+	TAG_BR,
+	TAG_A,
+	TAG_TABLE,
+	TAG_TBODY,
+	TAG_COL,
+	TAG_TR,
+	TAG_TD,
+	TAG_LI,
+	TAG_UL,
+	TAG_OL,
+	TAG_DL,
+	TAG_DT,
+	TAG_DD,
+	TAG_BLOCKQUOTE,
+	TAG_P,
+	TAG_PRE,
+	TAG_B,
+	TAG_I,
+	TAG_CODE,
+	TAG_SMALL,
+	TAG_MAX
+};
+
+enum	htmlattr {
+	ATTR_HTTPEQUIV,
+	ATTR_CONTENT,
+	ATTR_NAME,
+	ATTR_REL,
+	ATTR_HREF,
+	ATTR_TYPE,
+	ATTR_MEDIA,
+	ATTR_CLASS,
+	ATTR_STYLE,
+	ATTR_WIDTH,
+	ATTR_ID,
+	ATTR_SUMMARY,
+	ATTR_ALIGN,
+	ATTR_COLSPAN,
+	ATTR_MAX
+};
+
+enum	htmlfont {
+	HTMLFONT_NONE = 0,
+	HTMLFONT_BOLD,
+	HTMLFONT_ITALIC,
+	HTMLFONT_MAX
+};
+
+struct	tag {
+	struct tag	 *next;
+	enum htmltag	  tag;
+};
+
+struct tagq {
+	struct tag	 *head;
+};
+
+struct	htmlpair {
+	enum htmlattr	  key;
+	const char	 *val;
+};
+
+#define	PAIR_INIT(p, t, v) \
+	do { \
+		(p)->key = (t); \
+		(p)->val = (v); \
+	} while (/* CONSTCOND */ 0)
+
+#define	PAIR_ID_INIT(p, v)	PAIR_INIT(p, ATTR_ID, v)
+#define	PAIR_CLASS_INIT(p, v)	PAIR_INIT(p, ATTR_CLASS, v)
+#define	PAIR_HREF_INIT(p, v)	PAIR_INIT(p, ATTR_HREF, v)
+#define	PAIR_STYLE_INIT(p, h)	PAIR_INIT(p, ATTR_STYLE, (h)->buf)
+#define	PAIR_SUMMARY_INIT(p, v)	PAIR_INIT(p, ATTR_SUMMARY, v)
+
+enum	htmltype { 
+	HTML_HTML_4_01_STRICT,
+	HTML_XHTML_1_0_STRICT
+};
+
+struct	html {
+	int		  flags;
+#define	HTML_NOSPACE	 (1 << 0) /* suppress next space */
+#define	HTML_IGNDELIM	 (1 << 1)
+#define	HTML_KEEP	 (1 << 2)
+#define	HTML_PREKEEP	 (1 << 3)
+#define	HTML_NONOSPACE	 (1 << 4) /* never add spaces */
+#define	HTML_LITERAL	 (1 << 5) /* literal (e.g., <PRE>) context */
+	struct tagq	  tags; /* stack of open tags */
+	struct rofftbl	  tbl; /* current table */
+	struct tag	 *tblt; /* current open table scope */
+	struct mchars	 *symtab; /* character-escapes */
+	char		 *base_man; /* base for manpage href */
+	char		 *base_includes; /* base for include href */
+	char		 *style; /* style-sheet URI */
+	char		  buf[BUFSIZ]; /* see bufcat and friends */
+	size_t		  buflen; 
+	struct tag	 *metaf; /* current open font scope */
+	enum htmlfont	  metal; /* last used font */
+	enum htmlfont	  metac; /* current font mode */
+	enum htmltype	  type; /* output media type */
+	int		  oflags; /* output options */
+#define	HTML_FRAGMENT	 (1 << 0) /* don't emit HTML/HEAD/BODY */
+};
+
+void		  print_gen_decls(struct html *);
+void		  print_gen_head(struct html *);
+struct tag	 *print_otag(struct html *, enum htmltag, 
+				int, const struct htmlpair *);
+void		  print_tagq(struct html *, const struct tag *);
+void		  print_stagq(struct html *, const struct tag *);
+void		  print_text(struct html *, const char *);
+void		  print_tblclose(struct html *);
+void		  print_tbl(struct html *, const struct tbl_span *);
+void		  print_eqn(struct html *, const struct eqn *);
+
+void		  bufcat_fmt(struct html *, const char *, ...);
+void		  bufcat(struct html *, const char *);
+void		  bufcat_id(struct html *, const char *);
+void		  bufcat_style(struct html *, 
+			const char *, const char *);
+void		  bufcat_su(struct html *, const char *, 
+			const struct roffsu *);
+void		  bufinit(struct html *);
+void		  buffmt_man(struct html *, 
+			const char *, const char *);
+void		  buffmt_includes(struct html *, const char *);
+
+int		  html_strlen(const char *);
+
+__END_DECLS
+
+#endif /*!HTML_H*/
diff --git a/usr/src/cmd/mandoc/lib.c b/usr/src/cmd/mandoc/lib.c
new file mode 100644
index 0000000..7a18a5d
--- /dev/null
+++ b/usr/src/cmd/mandoc/lib.c
@@ -0,0 +1,39 @@
+/*	$Id: lib.c,v 1.9 2011/03/22 14:33:05 kristaps Exp $ */
+/*
+ * Copyright (c) 2009 Kristaps Dzonsons <kristaps@bsd.lv>
+ *
+ * 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.
+ */
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+
+#include "mdoc.h"
+#include "mandoc.h"
+#include "libmdoc.h"
+
+#define LINE(x, y) \
+	if (0 == strcmp(p, x)) return(y);
+
+const char *
+mdoc_a2lib(const char *p)
+{
+
+#include "lib.in"
+
+	return(NULL);
+}
diff --git a/usr/src/cmd/mandoc/lib.in b/usr/src/cmd/mandoc/lib.in
new file mode 100644
index 0000000..1e45677
--- /dev/null
+++ b/usr/src/cmd/mandoc/lib.in
@@ -0,0 +1,18 @@
+/*
+ * This file and its contents are supplied under the terms of the
+ * Common Development and Distribution License ("CDDL"), version 1.0.
+ * You may only use this file in accordance with the terms of version
+ * 1.0 of the CDDL.
+ *
+ * A full copy of the text of the CDDL should have accompanied this
+ * source.  A copy of the CDDL is also available via the Internet at
+ * http://www.illumos.org/license/CDDL.
+ */
+
+/*
+ * Copyright 2012 Nexenta Systems, Inc. All rights reserved.
+ */
+
+/*
+ * TBD
+ */
diff --git a/usr/src/cmd/mandoc/libman.h b/usr/src/cmd/mandoc/libman.h
new file mode 100644
index 0000000..4bc5128
--- /dev/null
+++ b/usr/src/cmd/mandoc/libman.h
@@ -0,0 +1,85 @@
+/*	$Id: libman.h,v 1.55 2011/11/07 01:24:40 schwarze Exp $ */
+/*
+ * Copyright (c) 2009, 2010, 2011 Kristaps Dzonsons <kristaps@bsd.lv>
+ *
+ * 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.
+ */
+#ifndef LIBMAN_H
+#define LIBMAN_H
+
+enum	man_next {
+	MAN_NEXT_SIBLING = 0,
+	MAN_NEXT_CHILD
+};
+
+struct	man {
+	struct mparse	*parse; /* parse pointer */
+	int		 flags; /* parse flags */
+#define	MAN_HALT	(1 << 0) /* badness happened: die */
+#define	MAN_ELINE	(1 << 1) /* Next-line element scope. */
+#define	MAN_BLINE	(1 << 2) /* Next-line block scope. */
+#define	MAN_ILINE	(1 << 3) /* Ignored in next-line scope. */
+#define	MAN_LITERAL	(1 << 4) /* Literal input. */
+#define	MAN_BPLINE	(1 << 5)
+#define	MAN_NEWLINE	(1 << 6) /* first macro/text in a line */
+	enum man_next	 next; /* where to put the next node */
+	struct man_node	*last; /* the last parsed node */
+	struct man_node	*first; /* the first parsed node */
+	struct man_meta	 meta; /* document meta-data */
+	struct roff	*roff;
+};
+
+#define	MACRO_PROT_ARGS	  struct man *m, \
+			  enum mant tok, \
+			  int line, \
+			  int ppos, \
+			  int *pos, \
+			  char *buf
+
+struct	man_macro {
+	int		(*fp)(MACRO_PROT_ARGS);
+	int		  flags;
+#define	MAN_SCOPED	 (1 << 0)
+#define	MAN_EXPLICIT	 (1 << 1)	/* See blk_imp(). */
+#define	MAN_FSCOPED	 (1 << 2)	/* See blk_imp(). */
+#define	MAN_NSCOPED	 (1 << 3)	/* See in_line_eoln(). */
+#define	MAN_NOCLOSE	 (1 << 4)	/* See blk_exp(). */
+#define	MAN_BSCOPE	 (1 << 5)	/* Break BLINE scope. */
+};
+
+extern	const struct man_macro *const man_macros;
+
+__BEGIN_DECLS
+
+#define		  man_pmsg(m, l, p, t) \
+		  mandoc_msg((t), (m)->parse, (l), (p), NULL)
+#define		  man_nmsg(m, n, t) \
+		  mandoc_msg((t), (m)->parse, (n)->line, (n)->pos, NULL)
+int		  man_word_alloc(struct man *, int, int, const char *);
+int		  man_block_alloc(struct man *, int, int, enum mant);
+int		  man_head_alloc(struct man *, int, int, enum mant);
+int		  man_tail_alloc(struct man *, int, int, enum mant);
+int		  man_body_alloc(struct man *, int, int, enum mant);
+int		  man_elem_alloc(struct man *, int, int, enum mant);
+void		  man_node_delete(struct man *, struct man_node *);
+void		  man_hash_init(void);
+enum mant	  man_hash_find(const char *);
+int		  man_macroend(struct man *);
+int		  man_valid_post(struct man *);
+int		  man_valid_pre(struct man *, struct man_node *);
+int		  man_unscope(struct man *, 
+			const struct man_node *, enum mandocerr);
+
+__END_DECLS
+
+#endif /*!LIBMAN_H*/
diff --git a/usr/src/cmd/mandoc/libmandoc.h b/usr/src/cmd/mandoc/libmandoc.h
new file mode 100644
index 0000000..de42288
--- /dev/null
+++ b/usr/src/cmd/mandoc/libmandoc.h
@@ -0,0 +1,92 @@
+/*	$Id: libmandoc.h,v 1.29 2011/12/02 01:37:14 schwarze Exp $ */
+/*
+ * Copyright (c) 2009, 2010, 2011 Kristaps Dzonsons <kristaps@bsd.lv>
+ *
+ * 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.
+ */
+#ifndef LIBMANDOC_H
+#define LIBMANDOC_H
+
+enum	rofferr {
+	ROFF_CONT, /* continue processing line */
+	ROFF_RERUN, /* re-run roff interpreter with offset */
+	ROFF_APPEND, /* re-run main parser, appending next line */
+	ROFF_REPARSE, /* re-run main parser on the result */
+	ROFF_SO, /* include another file */
+	ROFF_IGN, /* ignore current line */
+	ROFF_TBL, /* a table row was successfully parsed */
+	ROFF_EQN, /* an equation was successfully parsed */
+	ROFF_ERR /* badness: puke and stop */
+};
+
+enum	regs {
+	REG_nS = 0, /* nS register */
+	REG__MAX
+};
+
+__BEGIN_DECLS
+
+struct	roff;
+struct	mdoc;
+struct	man;
+
+void		 mandoc_msg(enum mandocerr, struct mparse *, 
+			int, int, const char *);
+void		 mandoc_vmsg(enum mandocerr, struct mparse *, 
+			int, int, const char *, ...);
+char		*mandoc_getarg(struct mparse *, char **, int, int *);
+char		*mandoc_normdate(struct mparse *, char *, int, int);
+int		 mandoc_eos(const char *, size_t, int);
+int		 mandoc_getcontrol(const char *, int *);
+int		 mandoc_strntoi(const char *, size_t, int);
+const char	*mandoc_a2msec(const char*);
+
+void	 	 mdoc_free(struct mdoc *);
+struct	mdoc	*mdoc_alloc(struct roff *, struct mparse *);
+void		 mdoc_reset(struct mdoc *);
+int	 	 mdoc_parseln(struct mdoc *, int, char *, int);
+int		 mdoc_endparse(struct mdoc *);
+int		 mdoc_addspan(struct mdoc *, const struct tbl_span *);
+int		 mdoc_addeqn(struct mdoc *, const struct eqn *);
+
+void	 	 man_free(struct man *);
+struct	man	*man_alloc(struct roff *, struct mparse *);
+void		 man_reset(struct man *);
+int	 	 man_parseln(struct man *, int, char *, int);
+int		 man_endparse(struct man *);
+int		 man_addspan(struct man *, const struct tbl_span *);
+int		 man_addeqn(struct man *, const struct eqn *);
+
+void	 	 roff_free(struct roff *);
+struct roff	*roff_alloc(struct mparse *);
+void		 roff_reset(struct roff *);
+enum rofferr	 roff_parseln(struct roff *, int, 
+			char **, size_t *, int, int *);
+void		 roff_endparse(struct roff *);
+int		 roff_regisset(const struct roff *, enum regs);
+unsigned int	 roff_regget(const struct roff *, enum regs);
+void		 roff_regunset(struct roff *, enum regs);
+char		*roff_strdup(const struct roff *, const char *);
+#if 0
+char		 roff_eqndelim(const struct roff *);
+void		 roff_openeqn(struct roff *, const char *, 
+			int, int, const char *);
+int		 roff_closeeqn(struct roff *);
+#endif
+
+const struct tbl_span	*roff_span(const struct roff *);
+const struct eqn	*roff_eqn(const struct roff *);
+
+__END_DECLS
+
+#endif /*!LIBMANDOC_H*/
diff --git a/usr/src/cmd/mandoc/libmdoc.h b/usr/src/cmd/mandoc/libmdoc.h
new file mode 100644
index 0000000..af17292
--- /dev/null
+++ b/usr/src/cmd/mandoc/libmdoc.h
@@ -0,0 +1,141 @@
+/*	$Id: libmdoc.h,v 1.78 2011/12/02 01:37:14 schwarze Exp $ */
+/*
+ * Copyright (c) 2008, 2009, 2010, 2011 Kristaps Dzonsons <kristaps@bsd.lv>
+ *
+ * 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.
+ */
+#ifndef LIBMDOC_H
+#define LIBMDOC_H
+
+enum	mdoc_next {
+	MDOC_NEXT_SIBLING = 0,
+	MDOC_NEXT_CHILD
+};
+
+struct	mdoc {
+	struct mparse	 *parse; /* parse pointer */
+	int		  flags; /* parse flags */
+#define	MDOC_HALT	 (1 << 0) /* error in parse: halt */
+#define	MDOC_LITERAL	 (1 << 1) /* in a literal scope */
+#define	MDOC_PBODY	 (1 << 2) /* in the document body */
+#define	MDOC_NEWLINE	 (1 << 3) /* first macro/text in a line */
+#define	MDOC_PHRASELIT	 (1 << 4) /* literal within a partila phrase */
+#define	MDOC_PPHRASE	 (1 << 5) /* within a partial phrase */
+#define	MDOC_FREECOL	 (1 << 6) /* `It' invocation should close */
+#define	MDOC_SYNOPSIS	 (1 << 7) /* SYNOPSIS-style formatting */
+	enum mdoc_next	  next; /* where to put the next node */
+	struct mdoc_node *last; /* the last node parsed */
+	struct mdoc_node *first; /* the first node parsed */
+	struct mdoc_meta  meta; /* document meta-data */
+	enum mdoc_sec	  lastnamed;
+	enum mdoc_sec	  lastsec;
+	struct roff	 *roff;
+};
+
+#define	MACRO_PROT_ARGS	struct mdoc *m, \
+			enum mdoct tok, \
+			int line, \
+			int ppos, \
+			int *pos, \
+			char *buf
+
+struct	mdoc_macro {
+	int		(*fp)(MACRO_PROT_ARGS);
+	int		  flags;
+#define	MDOC_CALLABLE	 (1 << 0)
+#define	MDOC_PARSED	 (1 << 1)
+#define	MDOC_EXPLICIT	 (1 << 2)
+#define	MDOC_PROLOGUE	 (1 << 3)
+#define	MDOC_IGNDELIM	 (1 << 4) 
+	/* Reserved words in arguments treated as text. */
+};
+
+enum	margserr {
+	ARGS_ERROR,
+	ARGS_EOLN, /* end-of-line */
+	ARGS_WORD, /* normal word */
+	ARGS_PUNCT, /* series of punctuation */
+	ARGS_QWORD, /* quoted word */
+	ARGS_PHRASE, /* Ta'd phrase (-column) */
+	ARGS_PPHRASE, /* tabbed phrase (-column) */
+	ARGS_PEND /* last phrase (-column) */
+};
+
+enum	margverr {
+	ARGV_ERROR,
+	ARGV_EOLN, /* end of line */
+	ARGV_ARG, /* valid argument */
+	ARGV_WORD /* normal word (or bad argument---same thing) */
+};
+
+/*
+ * A punctuation delimiter is opening, closing, or "middle mark"
+ * punctuation.  These govern spacing.
+ * Opening punctuation (e.g., the opening parenthesis) suppresses the
+ * following space; closing punctuation (e.g., the closing parenthesis)
+ * suppresses the leading space; middle punctuation (e.g., the vertical
+ * bar) can do either.  The middle punctuation delimiter bends the rules
+ * depending on usage.
+ */
+enum	mdelim {
+	DELIM_NONE = 0,
+	DELIM_OPEN,
+	DELIM_MIDDLE,
+	DELIM_CLOSE,
+	DELIM_MAX
+};
+
+extern	const struct mdoc_macro *const mdoc_macros;
+
+__BEGIN_DECLS
+
+#define		  mdoc_pmsg(m, l, p, t) \
+		  mandoc_msg((t), (m)->parse, (l), (p), NULL)
+#define		  mdoc_nmsg(m, n, t) \
+		  mandoc_msg((t), (m)->parse, (n)->line, (n)->pos, NULL)
+int		  mdoc_macro(MACRO_PROT_ARGS);
+int		  mdoc_word_alloc(struct mdoc *, 
+			int, int, const char *);
+int		  mdoc_elem_alloc(struct mdoc *, int, int, 
+			enum mdoct, struct mdoc_arg *);
+int		  mdoc_block_alloc(struct mdoc *, int, int, 
+			enum mdoct, struct mdoc_arg *);
+int		  mdoc_head_alloc(struct mdoc *, int, int, enum mdoct);
+int		  mdoc_tail_alloc(struct mdoc *, int, int, enum mdoct);
+int		  mdoc_body_alloc(struct mdoc *, int, int, enum mdoct);
+int		  mdoc_endbody_alloc(struct mdoc *m, int line, int pos,
+			enum mdoct tok, struct mdoc_node *body,
+			enum mdoc_endbody end);
+void		  mdoc_node_delete(struct mdoc *, struct mdoc_node *);
+void		  mdoc_hash_init(void);
+enum mdoct	  mdoc_hash_find(const char *);
+const char	 *mdoc_a2att(const char *);
+const char	 *mdoc_a2lib(const char *);
+const char	 *mdoc_a2st(const char *);
+const char	 *mdoc_a2arch(const char *);
+const char	 *mdoc_a2vol(const char *);
+int		  mdoc_valid_pre(struct mdoc *, struct mdoc_node *);
+int		  mdoc_valid_post(struct mdoc *);
+enum margverr	  mdoc_argv(struct mdoc *, int, enum mdoct,
+			struct mdoc_arg **, int *, char *);
+void		  mdoc_argv_free(struct mdoc_arg *);
+enum margserr	  mdoc_args(struct mdoc *, int,
+			int *, char *, enum mdoct, char **);
+enum margserr	  mdoc_zargs(struct mdoc *, int, 
+			int *, char *, char **);
+int		  mdoc_macroend(struct mdoc *);
+enum mdelim	  mdoc_isdelim(const char *);
+
+__END_DECLS
+
+#endif /*!LIBMDOC_H*/
diff --git a/usr/src/cmd/mandoc/libroff.h b/usr/src/cmd/mandoc/libroff.h
new file mode 100644
index 0000000..0bdd5a3
--- /dev/null
+++ b/usr/src/cmd/mandoc/libroff.h
@@ -0,0 +1,84 @@
+/*	$Id: libroff.h,v 1.27 2011/07/25 15:37:00 kristaps Exp $ */
+/*
+ * Copyright (c) 2009, 2010, 2011 Kristaps Dzonsons <kristaps@bsd.lv>
+ *
+ * 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.
+ */
+#ifndef LIBROFF_H
+#define LIBROFF_H
+
+__BEGIN_DECLS
+
+enum	tbl_part {
+	TBL_PART_OPTS, /* in options (first line) */
+	TBL_PART_LAYOUT, /* describing layout */
+	TBL_PART_DATA, /* creating data rows */
+	TBL_PART_CDATA /* continue previous row */
+};
+
+struct	tbl_node {
+	struct mparse	 *parse; /* parse point */
+	int		  pos; /* invocation column */
+	int		  line; /* invocation line */
+	enum tbl_part	  part;
+	struct tbl	  opts;
+	struct tbl_row	 *first_row;
+	struct tbl_row	 *last_row;
+	struct tbl_span	 *first_span;
+	struct tbl_span	 *current_span;
+	struct tbl_span	 *last_span;
+	struct tbl_head	 *first_head;
+	struct tbl_head	 *last_head;
+	struct tbl_node	 *next;
+};
+
+struct	eqn_node {
+	struct eqn_def	 *defs;
+	size_t		  defsz;
+	char		 *data;
+	size_t		  rew;
+	size_t		  cur;
+	size_t		  sz;
+	int		  gsize;
+	struct eqn	  eqn;
+	struct mparse	 *parse;
+	struct eqn_node  *next;
+};
+
+struct	eqn_def {
+	char		 *key;
+	size_t		  keysz;
+	char		 *val;
+	size_t		  valsz;
+};
+
+struct tbl_node	*tbl_alloc(int, int, struct mparse *);
+void		 tbl_restart(int, int, struct tbl_node *);
+void		 tbl_free(struct tbl_node *);
+void		 tbl_reset(struct tbl_node *);
+enum rofferr 	 tbl_read(struct tbl_node *, int, const char *, int);
+int		 tbl_option(struct tbl_node *, int, const char *);
+int		 tbl_layout(struct tbl_node *, int, const char *);
+int		 tbl_data(struct tbl_node *, int, const char *);
+int		 tbl_cdata(struct tbl_node *, int, const char *);
+const struct tbl_span	*tbl_span(struct tbl_node *);
+void		 tbl_end(struct tbl_node **);
+struct eqn_node	*eqn_alloc(const char *, int, int, struct mparse *);
+enum rofferr	 eqn_end(struct eqn_node **);
+void		 eqn_free(struct eqn_node *);
+enum rofferr 	 eqn_read(struct eqn_node **, int, 
+			const char *, int, int *);
+
+__END_DECLS
+
+#endif /*LIBROFF_H*/
diff --git a/usr/src/cmd/mandoc/main.c b/usr/src/cmd/mandoc/main.c
new file mode 100644
index 0000000..fec83fb
--- /dev/null
+++ b/usr/src/cmd/mandoc/main.c
@@ -0,0 +1,401 @@
+/*	$Id: main.c,v 1.165 2011/10/06 22:29:12 kristaps Exp $ */
+/*
+ * Copyright (c) 2008, 2009, 2010, 2011 Kristaps Dzonsons <kristaps@bsd.lv>
+ * Copyright (c) 2010, 2011 Ingo Schwarze <schwarze@openbsd.org>
+ *
+ * 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.
+ */
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <assert.h>
+#include <stdio.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "mandoc.h"
+#include "main.h"
+#include "mdoc.h"
+#include "man.h"
+
+#if !defined(__GNUC__) || (__GNUC__ < 2)
+# if !defined(lint)
+#  define __attribute__(x)
+# endif
+#endif /* !defined(__GNUC__) || (__GNUC__ < 2) */
+
+typedef	void		(*out_mdoc)(void *, const struct mdoc *);
+typedef	void		(*out_man)(void *, const struct man *);
+typedef	void		(*out_free)(void *);
+
+enum	outt {
+	OUTT_ASCII = 0,	/* -Tascii */
+	OUTT_LOCALE,	/* -Tlocale */
+	OUTT_UTF8,	/* -Tutf8 */
+	OUTT_TREE,	/* -Ttree */
+	OUTT_MAN,	/* -Tman */
+	OUTT_HTML,	/* -Thtml */
+	OUTT_XHTML,	/* -Txhtml */
+	OUTT_LINT,	/* -Tlint */
+	OUTT_PS,	/* -Tps */
+	OUTT_PDF	/* -Tpdf */
+};
+
+struct	curparse {
+	struct mparse	 *mp;
+	enum mandoclevel  wlevel;	/* ignore messages below this */
+	int		  wstop;	/* stop after a file with a warning */
+	enum outt	  outtype; 	/* which output to use */
+	out_mdoc	  outmdoc;	/* mdoc output ptr */
+	out_man	  	  outman;	/* man output ptr */
+	out_free	  outfree;	/* free output ptr */
+	void		 *outdata;	/* data for output */
+	char		  outopts[BUFSIZ]; /* buf of output opts */
+};
+
+static	int		  moptions(enum mparset *, char *);
+static	void		  mmsg(enum mandocerr, enum mandoclevel,
+				const char *, int, int, const char *);
+static	void		  parse(struct curparse *, int, 
+				const char *, enum mandoclevel *);
+static	int		  toptions(struct curparse *, char *);
+static	void		  usage(void) __attribute__((noreturn));
+static	void		  version(void) __attribute__((noreturn));
+static	int		  woptions(struct curparse *, char *);
+
+static	const char	 *progname;
+
+int
+main(int argc, char *argv[])
+{
+	int		 c;
+	struct curparse	 curp;
+	enum mparset	 type;
+	enum mandoclevel rc;
+
+	progname = strrchr(argv[0], '/');
+	if (progname == NULL)
+		progname = argv[0];
+	else
+		++progname;
+
+	memset(&curp, 0, sizeof(struct curparse));
+
+	type = MPARSE_AUTO;
+	curp.outtype = OUTT_ASCII;
+	curp.wlevel  = MANDOCLEVEL_FATAL;
+
+	/* LINTED */
+	while (-1 != (c = getopt(argc, argv, "m:O:T:VW:")))
+		switch (c) {
+		case ('m'):
+			if ( ! moptions(&type, optarg))
+				return((int)MANDOCLEVEL_BADARG);
+			break;
+		case ('O'):
+			(void)strlcat(curp.outopts, optarg, BUFSIZ);
+			(void)strlcat(curp.outopts, ",", BUFSIZ);
+			break;
+		case ('T'):
+			if ( ! toptions(&curp, optarg))
+				return((int)MANDOCLEVEL_BADARG);
+			break;
+		case ('W'):
+			if ( ! woptions(&curp, optarg))
+				return((int)MANDOCLEVEL_BADARG);
+			break;
+		case ('V'):
+			version();
+			/* NOTREACHED */
+		default:
+			usage();
+			/* NOTREACHED */
+		}
+
+	curp.mp = mparse_alloc(type, curp.wlevel, mmsg, &curp);
+
+	/*
+	 * Conditionally start up the lookaside buffer before parsing.
+	 */
+	if (OUTT_MAN == curp.outtype)
+		mparse_keep(curp.mp);
+
+	argc -= optind;
+	argv += optind;
+
+	rc = MANDOCLEVEL_OK;
+
+	if (NULL == *argv)
+		parse(&curp, STDIN_FILENO, "<stdin>", &rc);
+
+	while (*argv) {
+		parse(&curp, -1, *argv, &rc);
+		if (MANDOCLEVEL_OK != rc && curp.wstop)
+			break;
+		++argv;
+	}
+
+	if (curp.outfree)
+		(*curp.outfree)(curp.outdata);
+	if (curp.mp)
+		mparse_free(curp.mp);
+
+	return((int)rc);
+}
+
+static void
+version(void)
+{
+
+	printf("%s %s\n", progname, VERSION);
+	exit((int)MANDOCLEVEL_OK);
+}
+
+static void
+usage(void)
+{
+
+	fprintf(stderr, "usage: %s "
+			"[-V] "
+			"[-foption] "
+			"[-mformat] "
+			"[-Ooption] "
+			"[-Toutput] "
+			"[-Wlevel] "
+			"[file...]\n", 
+			progname);
+
+	exit((int)MANDOCLEVEL_BADARG);
+}
+
+static void
+parse(struct curparse *curp, int fd, 
+		const char *file, enum mandoclevel *level)
+{
+	enum mandoclevel  rc;
+	struct mdoc	 *mdoc;
+	struct man	 *man;
+
+	/* Begin by parsing the file itself. */
+
+	assert(file);
+	assert(fd >= -1);
+
+	rc = mparse_readfd(curp->mp, fd, file);
+
+	/* Stop immediately if the parse has failed. */
+
+	if (MANDOCLEVEL_FATAL <= rc)
+		goto cleanup;
+
+	/*
+	 * With -Wstop and warnings or errors of at least the requested
+	 * level, do not produce output.
+	 */
+
+	if (MANDOCLEVEL_OK != rc && curp->wstop)
+		goto cleanup;
+
+	/* If unset, allocate output dev now (if applicable). */
+
+	if ( ! (curp->outman && curp->outmdoc)) {
+		switch (curp->outtype) {
+		case (OUTT_XHTML):
+			curp->outdata = xhtml_alloc(curp->outopts);
+			curp->outfree = html_free;
+			break;
+		case (OUTT_HTML):
+			curp->outdata = html_alloc(curp->outopts);
+			curp->outfree = html_free;
+			break;
+		case (OUTT_UTF8):
+			curp->outdata = utf8_alloc(curp->outopts);
+			curp->outfree = ascii_free;
+			break;
+		case (OUTT_LOCALE):
+			curp->outdata = locale_alloc(curp->outopts);
+			curp->outfree = ascii_free;
+			break;
+		case (OUTT_ASCII):
+			curp->outdata = ascii_alloc(curp->outopts);
+			curp->outfree = ascii_free;
+			break;
+		case (OUTT_PDF):
+			curp->outdata = pdf_alloc(curp->outopts);
+			curp->outfree = pspdf_free;
+			break;
+		case (OUTT_PS):
+			curp->outdata = ps_alloc(curp->outopts);
+			curp->outfree = pspdf_free;
+			break;
+		default:
+			break;
+		}
+
+		switch (curp->outtype) {
+		case (OUTT_HTML):
+			/* FALLTHROUGH */
+		case (OUTT_XHTML):
+			curp->outman = html_man;
+			curp->outmdoc = html_mdoc;
+			break;
+		case (OUTT_TREE):
+			curp->outman = tree_man;
+			curp->outmdoc = tree_mdoc;
+			break;
+		case (OUTT_MAN):
+			curp->outmdoc = man_mdoc;
+			curp->outman = man_man;
+			break;
+		case (OUTT_PDF):
+			/* FALLTHROUGH */
+		case (OUTT_ASCII):
+			/* FALLTHROUGH */
+		case (OUTT_UTF8):
+			/* FALLTHROUGH */
+		case (OUTT_LOCALE):
+			/* FALLTHROUGH */
+		case (OUTT_PS):
+			curp->outman = terminal_man;
+			curp->outmdoc = terminal_mdoc;
+			break;
+		default:
+			break;
+		}
+	}
+
+	mparse_result(curp->mp, &mdoc, &man);
+
+	/* Execute the out device, if it exists. */
+
+	if (man && curp->outman)
+		(*curp->outman)(curp->outdata, man);
+	if (mdoc && curp->outmdoc)
+		(*curp->outmdoc)(curp->outdata, mdoc);
+
+ cleanup:
+
+	mparse_reset(curp->mp);
+
+	if (*level < rc)
+		*level = rc;
+}
+
+static int
+moptions(enum mparset *tflags, char *arg)
+{
+
+	if (0 == strcmp(arg, "doc"))
+		*tflags = MPARSE_MDOC;
+	else if (0 == strcmp(arg, "andoc"))
+		*tflags = MPARSE_AUTO;
+	else if (0 == strcmp(arg, "an"))
+		*tflags = MPARSE_MAN;
+	else {
+		fprintf(stderr, "%s: Bad argument\n", arg);
+		return(0);
+	}
+
+	return(1);
+}
+
+static int
+toptions(struct curparse *curp, char *arg)
+{
+
+	if (0 == strcmp(arg, "ascii"))
+		curp->outtype = OUTT_ASCII;
+	else if (0 == strcmp(arg, "lint")) {
+		curp->outtype = OUTT_LINT;
+		curp->wlevel  = MANDOCLEVEL_WARNING;
+	} else if (0 == strcmp(arg, "tree"))
+		curp->outtype = OUTT_TREE;
+	else if (0 == strcmp(arg, "man"))
+		curp->outtype = OUTT_MAN;
+	else if (0 == strcmp(arg, "html"))
+		curp->outtype = OUTT_HTML;
+	else if (0 == strcmp(arg, "utf8"))
+		curp->outtype = OUTT_UTF8;
+	else if (0 == strcmp(arg, "locale"))
+		curp->outtype = OUTT_LOCALE;
+	else if (0 == strcmp(arg, "xhtml"))
+		curp->outtype = OUTT_XHTML;
+	else if (0 == strcmp(arg, "ps"))
+		curp->outtype = OUTT_PS;
+	else if (0 == strcmp(arg, "pdf"))
+		curp->outtype = OUTT_PDF;
+	else {
+		fprintf(stderr, "%s: Bad argument\n", arg);
+		return(0);
+	}
+
+	return(1);
+}
+
+static int
+woptions(struct curparse *curp, char *arg)
+{
+	char		*v, *o;
+	const char	*toks[6]; 
+
+	toks[0] = "stop";
+	toks[1] = "all";
+	toks[2] = "warning";
+	toks[3] = "error";
+	toks[4] = "fatal";
+	toks[5] = NULL;
+
+	while (*arg) {
+		o = arg;
+		switch (getsubopt(&arg, UNCONST(toks), &v)) {
+		case (0):
+			curp->wstop = 1;
+			break;
+		case (1):
+			/* FALLTHROUGH */
+		case (2):
+			curp->wlevel = MANDOCLEVEL_WARNING;
+			break;
+		case (3):
+			curp->wlevel = MANDOCLEVEL_ERROR;
+			break;
+		case (4):
+			curp->wlevel = MANDOCLEVEL_FATAL;
+			break;
+		default:
+			fprintf(stderr, "-W%s: Bad argument\n", o);
+			return(0);
+		}
+	}
+
+	return(1);
+}
+
+static void
+mmsg(enum mandocerr t, enum mandoclevel lvl, 
+		const char *file, int line, int col, const char *msg)
+{
+
+	fprintf(stderr, "%s:%d:%d: %s: %s", 
+			file, line, col + 1, 
+			mparse_strlevel(lvl),
+			mparse_strerror(t));
+
+	if (msg)
+		fprintf(stderr, ": %s", msg);
+
+	fputc('\n', stderr);
+}
diff --git a/usr/src/cmd/mandoc/main.h b/usr/src/cmd/mandoc/main.h
new file mode 100644
index 0000000..79dcf48
--- /dev/null
+++ b/usr/src/cmd/mandoc/main.h
@@ -0,0 +1,61 @@
+/*	$Id: main.h,v 1.15 2011/10/06 22:29:12 kristaps Exp $ */
+/*
+ * Copyright (c) 2009, 2010, 2011 Kristaps Dzonsons <kristaps@bsd.lv>
+ *
+ * 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.
+ */
+#ifndef	MAIN_H
+#define	MAIN_H
+
+__BEGIN_DECLS
+
+struct	mdoc;
+struct	man;
+
+#define	UNCONST(a)	((void *)(uintptr_t)(const void *)(a))
+
+
+/* 
+ * Definitions for main.c-visible output device functions, e.g., -Thtml
+ * and -Tascii.  Note that ascii_alloc() is named as such in
+ * anticipation of latin1_alloc() and so on, all of which map into the
+ * terminal output routines with different character settings.
+ */
+
+void		 *html_alloc(char *);
+void		 *xhtml_alloc(char *);
+void		  html_mdoc(void *, const struct mdoc *);
+void		  html_man(void *, const struct man *);
+void		  html_free(void *);
+
+void		  tree_mdoc(void *, const struct mdoc *);
+void		  tree_man(void *, const struct man *);
+
+void		  man_mdoc(void *, const struct mdoc *);
+void		  man_man(void *, const struct man *);
+
+void		 *locale_alloc(char *);
+void		 *utf8_alloc(char *);
+void		 *ascii_alloc(char *);
+void		  ascii_free(void *);
+
+void		 *pdf_alloc(char *);
+void		 *ps_alloc(char *);
+void		  pspdf_free(void *);
+
+void		  terminal_mdoc(void *, const struct mdoc *);
+void		  terminal_man(void *, const struct man *);
+
+__END_DECLS
+
+#endif /*!MAIN_H*/
diff --git a/usr/src/cmd/mandoc/man.c b/usr/src/cmd/mandoc/man.c
new file mode 100644
index 0000000..1bea561
--- /dev/null
+++ b/usr/src/cmd/mandoc/man.c
@@ -0,0 +1,690 @@
+/*	$Id: man.c,v 1.115 2012/01/03 15:16:24 kristaps Exp $ */
+/*
+ * Copyright (c) 2008, 2009, 2010, 2011 Kristaps Dzonsons <kristaps@bsd.lv>
+ *
+ * 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.
+ */
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <sys/types.h>
+
+#include <assert.h>
+#include <stdarg.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+
+#include "man.h"
+#include "mandoc.h"
+#include "libman.h"
+#include "libmandoc.h"
+
+const	char *const __man_macronames[MAN_MAX] = {		 
+	"br",		"TH",		"SH",		"SS",
+	"TP", 		"LP",		"PP",		"P",
+	"IP",		"HP",		"SM",		"SB",
+	"BI",		"IB",		"BR",		"RB",
+	"R",		"B",		"I",		"IR",
+	"RI",		"na",		"sp",		"nf",
+	"fi",		"RE",		"RS",		"DT",
+	"UC",		"PD",		"AT",		"in",
+	"ft",		"OP"
+	};
+
+const	char * const *man_macronames = __man_macronames;
+
+static	struct man_node	*man_node_alloc(struct man *, int, int, 
+				enum man_type, enum mant);
+static	int		 man_node_append(struct man *, 
+				struct man_node *);
+static	void		 man_node_free(struct man_node *);
+static	void		 man_node_unlink(struct man *, 
+				struct man_node *);
+static	int		 man_ptext(struct man *, int, char *, int);
+static	int		 man_pmacro(struct man *, int, char *, int);
+static	void		 man_free1(struct man *);
+static	void		 man_alloc1(struct man *);
+static	int		 man_descope(struct man *, int, int);
+
+
+const struct man_node *
+man_node(const struct man *m)
+{
+
+	assert( ! (MAN_HALT & m->flags));
+	return(m->first);
+}
+
+
+const struct man_meta *
+man_meta(const struct man *m)
+{
+
+	assert( ! (MAN_HALT & m->flags));
+	return(&m->meta);
+}
+
+
+void
+man_reset(struct man *man)
+{
+
+	man_free1(man);
+	man_alloc1(man);
+}
+
+
+void
+man_free(struct man *man)
+{
+
+	man_free1(man);
+	free(man);
+}
+
+
+struct man *
+man_alloc(struct roff *roff, struct mparse *parse)
+{
+	struct man	*p;
+
+	p = mandoc_calloc(1, sizeof(struct man));
+
+	man_hash_init();
+	p->parse = parse;
+	p->roff = roff;
+
+	man_alloc1(p);
+	return(p);
+}
+
+
+int
+man_endparse(struct man *m)
+{
+
+	assert( ! (MAN_HALT & m->flags));
+	if (man_macroend(m))
+		return(1);
+	m->flags |= MAN_HALT;
+	return(0);
+}
+
+
+int
+man_parseln(struct man *m, int ln, char *buf, int offs)
+{
+
+	m->flags |= MAN_NEWLINE;
+
+	assert( ! (MAN_HALT & m->flags));
+
+	return (mandoc_getcontrol(buf, &offs) ?
+			man_pmacro(m, ln, buf, offs) : 
+			man_ptext(m, ln, buf, offs));
+}
+
+
+static void
+man_free1(struct man *man)
+{
+
+	if (man->first)
+		man_node_delete(man, man->first);
+	if (man->meta.title)
+		free(man->meta.title);
+	if (man->meta.source)
+		free(man->meta.source);
+	if (man->meta.date)
+		free(man->meta.date);
+	if (man->meta.vol)
+		free(man->meta.vol);
+	if (man->meta.msec)
+		free(man->meta.msec);
+}
+
+
+static void
+man_alloc1(struct man *m)
+{
+
+	memset(&m->meta, 0, sizeof(struct man_meta));
+	m->flags = 0;
+	m->last = mandoc_calloc(1, sizeof(struct man_node));
+	m->first = m->last;
+	m->last->type = MAN_ROOT;
+	m->last->tok = MAN_MAX;
+	m->next = MAN_NEXT_CHILD;
+}
+
+
+static int
+man_node_append(struct man *man, struct man_node *p)
+{
+
+	assert(man->last);
+	assert(man->first);
+	assert(MAN_ROOT != p->type);
+
+	switch (man->next) {
+	case (MAN_NEXT_SIBLING):
+		man->last->next = p;
+		p->prev = man->last;
+		p->parent = man->last->parent;
+		break;
+	case (MAN_NEXT_CHILD):
+		man->last->child = p;
+		p->parent = man->last;
+		break;
+	default:
+		abort();
+		/* NOTREACHED */
+	}
+	
+	assert(p->parent);
+	p->parent->nchild++;
+
+	if ( ! man_valid_pre(man, p))
+		return(0);
+
+	switch (p->type) {
+	case (MAN_HEAD):
+		assert(MAN_BLOCK == p->parent->type);
+		p->parent->head = p;
+		break;
+	case (MAN_TAIL):
+		assert(MAN_BLOCK == p->parent->type);
+		p->parent->tail = p;
+		break;
+	case (MAN_BODY):
+		assert(MAN_BLOCK == p->parent->type);
+		p->parent->body = p;
+		break;
+	default:
+		break;
+	}
+
+	man->last = p;
+
+	switch (p->type) {
+	case (MAN_TBL):
+		/* FALLTHROUGH */
+	case (MAN_TEXT):
+		if ( ! man_valid_post(man))
+			return(0);
+		break;
+	default:
+		break;
+	}
+
+	return(1);
+}
+
+
+static struct man_node *
+man_node_alloc(struct man *m, int line, int pos, 
+		enum man_type type, enum mant tok)
+{
+	struct man_node *p;
+
+	p = mandoc_calloc(1, sizeof(struct man_node));
+	p->line = line;
+	p->pos = pos;
+	p->type = type;
+	p->tok = tok;
+
+	if (MAN_NEWLINE & m->flags)
+		p->flags |= MAN_LINE;
+	m->flags &= ~MAN_NEWLINE;
+	return(p);
+}
+
+
+int
+man_elem_alloc(struct man *m, int line, int pos, enum mant tok)
+{
+	struct man_node *p;
+
+	p = man_node_alloc(m, line, pos, MAN_ELEM, tok);
+	if ( ! man_node_append(m, p))
+		return(0);
+	m->next = MAN_NEXT_CHILD;
+	return(1);
+}
+
+
+int
+man_tail_alloc(struct man *m, int line, int pos, enum mant tok)
+{
+	struct man_node *p;
+
+	p = man_node_alloc(m, line, pos, MAN_TAIL, tok);
+	if ( ! man_node_append(m, p))
+		return(0);
+	m->next = MAN_NEXT_CHILD;
+	return(1);
+}
+
+
+int
+man_head_alloc(struct man *m, int line, int pos, enum mant tok)
+{
+	struct man_node *p;
+
+	p = man_node_alloc(m, line, pos, MAN_HEAD, tok);
+	if ( ! man_node_append(m, p))
+		return(0);
+	m->next = MAN_NEXT_CHILD;
+	return(1);
+}
+
+
+int
+man_body_alloc(struct man *m, int line, int pos, enum mant tok)
+{
+	struct man_node *p;
+
+	p = man_node_alloc(m, line, pos, MAN_BODY, tok);
+	if ( ! man_node_append(m, p))
+		return(0);
+	m->next = MAN_NEXT_CHILD;
+	return(1);
+}
+
+
+int
+man_block_alloc(struct man *m, int line, int pos, enum mant tok)
+{
+	struct man_node *p;
+
+	p = man_node_alloc(m, line, pos, MAN_BLOCK, tok);
+	if ( ! man_node_append(m, p))
+		return(0);
+	m->next = MAN_NEXT_CHILD;
+	return(1);
+}
+
+int
+man_word_alloc(struct man *m, int line, int pos, const char *word)
+{
+	struct man_node	*n;
+
+	n = man_node_alloc(m, line, pos, MAN_TEXT, MAN_MAX);
+	n->string = roff_strdup(m->roff, word);
+
+	if ( ! man_node_append(m, n))
+		return(0);
+
+	m->next = MAN_NEXT_SIBLING;
+	return(1);
+}
+
+
+/*
+ * Free all of the resources held by a node.  This does NOT unlink a
+ * node from its context; for that, see man_node_unlink().
+ */
+static void
+man_node_free(struct man_node *p)
+{
+
+	if (p->string)
+		free(p->string);
+	free(p);
+}
+
+
+void
+man_node_delete(struct man *m, struct man_node *p)
+{
+
+	while (p->child)
+		man_node_delete(m, p->child);
+
+	man_node_unlink(m, p);
+	man_node_free(p);
+}
+
+int
+man_addeqn(struct man *m, const struct eqn *ep)
+{
+	struct man_node	*n;
+
+	assert( ! (MAN_HALT & m->flags));
+
+	n = man_node_alloc(m, ep->ln, ep->pos, MAN_EQN, MAN_MAX);
+	n->eqn = ep;
+
+	if ( ! man_node_append(m, n))
+		return(0);
+
+	m->next = MAN_NEXT_SIBLING;
+	return(man_descope(m, ep->ln, ep->pos));
+}
+
+int
+man_addspan(struct man *m, const struct tbl_span *sp)
+{
+	struct man_node	*n;
+
+	assert( ! (MAN_HALT & m->flags));
+
+	n = man_node_alloc(m, sp->line, 0, MAN_TBL, MAN_MAX);
+	n->span = sp;
+
+	if ( ! man_node_append(m, n))
+		return(0);
+
+	m->next = MAN_NEXT_SIBLING;
+	return(man_descope(m, sp->line, 0));
+}
+
+static int
+man_descope(struct man *m, int line, int offs)
+{
+	/*
+	 * Co-ordinate what happens with having a next-line scope open:
+	 * first close out the element scope (if applicable), then close
+	 * out the block scope (also if applicable).
+	 */
+
+	if (MAN_ELINE & m->flags) {
+		m->flags &= ~MAN_ELINE;
+		if ( ! man_unscope(m, m->last->parent, MANDOCERR_MAX))
+			return(0);
+	}
+
+	if ( ! (MAN_BLINE & m->flags))
+		return(1);
+	m->flags &= ~MAN_BLINE;
+
+	if ( ! man_unscope(m, m->last->parent, MANDOCERR_MAX))
+		return(0);
+	return(man_body_alloc(m, line, offs, m->last->tok));
+}
+
+static int
+man_ptext(struct man *m, int line, char *buf, int offs)
+{
+	int		 i;
+
+	/* Literal free-form text whitespace is preserved. */
+
+	if (MAN_LITERAL & m->flags) {
+		if ( ! man_word_alloc(m, line, offs, buf + offs))
+			return(0);
+		return(man_descope(m, line, offs));
+	}
+
+	/* Pump blank lines directly into the backend. */
+
+	for (i = offs; ' ' == buf[i]; i++)
+		/* Skip leading whitespace. */ ;
+
+	if ('\0' == buf[i]) {
+		/* Allocate a blank entry. */
+		if ( ! man_word_alloc(m, line, offs, ""))
+			return(0);
+		return(man_descope(m, line, offs));
+	}
+
+	/* 
+	 * Warn if the last un-escaped character is whitespace. Then
+	 * strip away the remaining spaces (tabs stay!).   
+	 */
+
+	i = (int)strlen(buf);
+	assert(i);
+
+	if (' ' == buf[i - 1] || '\t' == buf[i - 1]) {
+		if (i > 1 && '\\' != buf[i - 2])
+			man_pmsg(m, line, i - 1, MANDOCERR_EOLNSPACE);
+
+		for (--i; i && ' ' == buf[i]; i--)
+			/* Spin back to non-space. */ ;
+
+		/* Jump ahead of escaped whitespace. */
+		i += '\\' == buf[i] ? 2 : 1;
+
+		buf[i] = '\0';
+	}
+
+	if ( ! man_word_alloc(m, line, offs, buf + offs))
+		return(0);
+
+	/*
+	 * End-of-sentence check.  If the last character is an unescaped
+	 * EOS character, then flag the node as being the end of a
+	 * sentence.  The front-end will know how to interpret this.
+	 */
+
+	assert(i);
+	if (mandoc_eos(buf, (size_t)i, 0))
+		m->last->flags |= MAN_EOS;
+
+	return(man_descope(m, line, offs));
+}
+
+static int
+man_pmacro(struct man *m, int ln, char *buf, int offs)
+{
+	int		 i, ppos;
+	enum mant	 tok;
+	char		 mac[5];
+	struct man_node	*n;
+
+	if ('"' == buf[offs]) {
+		man_pmsg(m, ln, offs, MANDOCERR_BADCOMMENT);
+		return(1);
+	} else if ('\0' == buf[offs])
+		return(1);
+
+	ppos = offs;
+
+	/*
+	 * Copy the first word into a nil-terminated buffer.
+	 * Stop copying when a tab, space, or eoln is encountered.
+	 */
+
+	i = 0;
+	while (i < 4 && '\0' != buf[offs] && 
+			' ' != buf[offs] && '\t' != buf[offs])
+		mac[i++] = buf[offs++];
+
+	mac[i] = '\0';
+
+	tok = (i > 0 && i < 4) ? man_hash_find(mac) : MAN_MAX;
+
+	if (MAN_MAX == tok) {
+		mandoc_vmsg(MANDOCERR_MACRO, m->parse, ln, 
+				ppos, "%s", buf + ppos - 1);
+		return(1);
+	}
+
+	/* The macro is sane.  Jump to the next word. */
+
+	while (buf[offs] && ' ' == buf[offs])
+		offs++;
+
+	/* 
+	 * Trailing whitespace.  Note that tabs are allowed to be passed
+	 * into the parser as "text", so we only warn about spaces here.
+	 */
+
+	if ('\0' == buf[offs] && ' ' == buf[offs - 1])
+		man_pmsg(m, ln, offs - 1, MANDOCERR_EOLNSPACE);
+
+	/* 
+	 * Remove prior ELINE macro, as it's being clobbered by a new
+	 * macro.  Note that NSCOPED macros do not close out ELINE
+	 * macros---they don't print text---so we let those slip by.
+	 */
+
+	if ( ! (MAN_NSCOPED & man_macros[tok].flags) &&
+			m->flags & MAN_ELINE) {
+		n = m->last;
+		assert(MAN_TEXT != n->type);
+
+		/* Remove repeated NSCOPED macros causing ELINE. */
+
+		if (MAN_NSCOPED & man_macros[n->tok].flags)
+			n = n->parent;
+
+		mandoc_vmsg(MANDOCERR_LINESCOPE, m->parse, n->line, 
+		    n->pos, "%s breaks %s", man_macronames[tok],
+		    man_macronames[n->tok]);
+
+		man_node_delete(m, n);
+		m->flags &= ~MAN_ELINE;
+	}
+
+	/*
+	 * Remove prior BLINE macro that is being clobbered.
+	 */
+	if ((m->flags & MAN_BLINE) &&
+	    (MAN_BSCOPE & man_macros[tok].flags)) {
+		n = m->last;
+
+		/* Might be a text node like 8 in
+		 * .TP 8
+		 * .SH foo
+		 */
+		if (MAN_TEXT == n->type)
+			n = n->parent;
+
+		/* Remove element that didn't end BLINE, if any. */
+		if ( ! (MAN_BSCOPE & man_macros[n->tok].flags))
+			n = n->parent;
+
+		assert(MAN_HEAD == n->type);
+		n = n->parent;
+		assert(MAN_BLOCK == n->type);
+		assert(MAN_SCOPED & man_macros[n->tok].flags);
+
+		mandoc_vmsg(MANDOCERR_LINESCOPE, m->parse, n->line, 
+		    n->pos, "%s breaks %s", man_macronames[tok],
+		    man_macronames[n->tok]);
+
+		man_node_delete(m, n);
+		m->flags &= ~MAN_BLINE;
+	}
+
+	/*
+	 * Save the fact that we're in the next-line for a block.  In
+	 * this way, embedded roff instructions can "remember" state
+	 * when they exit.
+	 */
+
+	if (MAN_BLINE & m->flags)
+		m->flags |= MAN_BPLINE;
+
+	/* Call to handler... */
+
+	assert(man_macros[tok].fp);
+	if ( ! (*man_macros[tok].fp)(m, tok, ln, ppos, &offs, buf))
+		goto err;
+
+	/* 
+	 * We weren't in a block-line scope when entering the
+	 * above-parsed macro, so return.
+	 */
+
+	if ( ! (MAN_BPLINE & m->flags)) {
+		m->flags &= ~MAN_ILINE; 
+		return(1);
+	}
+	m->flags &= ~MAN_BPLINE;
+
+	/*
+	 * If we're in a block scope, then allow this macro to slip by
+	 * without closing scope around it.
+	 */
+
+	if (MAN_ILINE & m->flags) {
+		m->flags &= ~MAN_ILINE;
+		return(1);
+	}
+
+	/* 
+	 * If we've opened a new next-line element scope, then return
+	 * now, as the next line will close out the block scope.
+	 */
+
+	if (MAN_ELINE & m->flags)
+		return(1);
+
+	/* Close out the block scope opened in the prior line.  */
+
+	assert(MAN_BLINE & m->flags);
+	m->flags &= ~MAN_BLINE;
+
+	if ( ! man_unscope(m, m->last->parent, MANDOCERR_MAX))
+		return(0);
+	return(man_body_alloc(m, ln, ppos, m->last->tok));
+
+err:	/* Error out. */
+
+	m->flags |= MAN_HALT;
+	return(0);
+}
+
+/*
+ * Unlink a node from its context.  If "m" is provided, the last parse
+ * point will also be adjusted accordingly.
+ */
+static void
+man_node_unlink(struct man *m, struct man_node *n)
+{
+
+	/* Adjust siblings. */
+
+	if (n->prev)
+		n->prev->next = n->next;
+	if (n->next)
+		n->next->prev = n->prev;
+
+	/* Adjust parent. */
+
+	if (n->parent) {
+		n->parent->nchild--;
+		if (n->parent->child == n)
+			n->parent->child = n->prev ? n->prev : n->next;
+	}
+
+	/* Adjust parse point, if applicable. */
+
+	if (m && m->last == n) {
+		/*XXX: this can occur when bailing from validation. */
+		/*assert(NULL == n->next);*/
+		if (n->prev) {
+			m->last = n->prev;
+			m->next = MAN_NEXT_SIBLING;
+		} else {
+			m->last = n->parent;
+			m->next = MAN_NEXT_CHILD;
+		}
+	}
+
+	if (m && m->first == n)
+		m->first = NULL;
+}
+
+const struct mparse *
+man_mparse(const struct man *m)
+{
+
+	assert(m && m->parse);
+	return(m->parse);
+}
diff --git a/usr/src/cmd/mandoc/man.h b/usr/src/cmd/mandoc/man.h
new file mode 100644
index 0000000..4fc3934
--- /dev/null
+++ b/usr/src/cmd/mandoc/man.h
@@ -0,0 +1,113 @@
+/*	$Id: man.h,v 1.60 2012/01/03 15:16:24 kristaps Exp $ */
+/*
+ * Copyright (c) 2009, 2010, 2011 Kristaps Dzonsons <kristaps@bsd.lv>
+ *
+ * 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.
+ */
+#ifndef MAN_H
+#define MAN_H
+
+enum	mant {
+	MAN_br = 0,
+	MAN_TH,
+	MAN_SH,
+	MAN_SS,
+	MAN_TP,
+	MAN_LP,
+	MAN_PP,
+	MAN_P,
+	MAN_IP,
+	MAN_HP,
+	MAN_SM,
+	MAN_SB,
+	MAN_BI,
+	MAN_IB,
+	MAN_BR,
+	MAN_RB,
+	MAN_R,
+	MAN_B,
+	MAN_I,
+	MAN_IR,
+	MAN_RI,
+	MAN_na,
+	MAN_sp,
+	MAN_nf,
+	MAN_fi,
+	MAN_RE,
+	MAN_RS,
+	MAN_DT,
+	MAN_UC,
+	MAN_PD,
+	MAN_AT,
+	MAN_in,
+	MAN_ft,
+	MAN_OP,
+	MAN_MAX
+};
+
+enum	man_type {
+	MAN_TEXT,
+	MAN_ELEM,
+	MAN_ROOT,
+	MAN_BLOCK,
+	MAN_HEAD,
+	MAN_BODY,
+	MAN_TAIL,
+	MAN_TBL,
+	MAN_EQN
+};
+
+struct	man_meta {
+	char		*msec; /* `TH' section (1, 3p, etc.) */
+	char		*date; /* `TH' normalised date */
+	char		*vol; /* `TH' volume */
+	char		*title; /* `TH' title (e.g., FOO) */
+	char		*source; /* `TH' source (e.g., GNU) */
+};
+
+struct	man_node {
+	struct man_node	*parent; /* parent AST node */
+	struct man_node	*child; /* first child AST node */
+	struct man_node	*next; /* sibling AST node */
+	struct man_node	*prev; /* prior sibling AST node */
+	int		 nchild; /* number children */
+	int		 line;
+	int		 pos;
+	enum mant	 tok; /* tok or MAN__MAX if none */
+	int		 flags;
+#define	MAN_VALID	(1 << 0) /* has been validated */
+#define	MAN_EOS		(1 << 2) /* at sentence boundary */
+#define	MAN_LINE	(1 << 3) /* first macro/text on line */
+	enum man_type	 type; /* AST node type */
+	char		*string; /* TEXT node argument */
+	struct man_node	*head; /* BLOCK node HEAD ptr */
+	struct man_node *tail; /* BLOCK node TAIL ptr */
+	struct man_node	*body; /* BLOCK node BODY ptr */
+	const struct tbl_span *span; /* TBL */
+	const struct eqn *eqn; /* EQN */
+};
+
+/* Names of macros.  Index is enum mant. */
+extern	const char *const *man_macronames;
+
+__BEGIN_DECLS
+
+struct	man;
+
+const struct man_node *man_node(const struct man *);
+const struct man_meta *man_meta(const struct man *);
+const struct mparse   *man_mparse(const struct man *);
+
+__END_DECLS
+
+#endif /*!MAN_H*/
diff --git a/usr/src/cmd/mandoc/man_hash.c b/usr/src/cmd/mandoc/man_hash.c
new file mode 100644
index 0000000..86c5c40
--- /dev/null
+++ b/usr/src/cmd/mandoc/man_hash.c
@@ -0,0 +1,107 @@
+/*	$Id: man_hash.c,v 1.25 2011/07/24 18:15:14 kristaps Exp $ */
+/*
+ * Copyright (c) 2008, 2009, 2010 Kristaps Dzonsons <kristaps@bsd.lv>
+ *
+ * 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.
+ */
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <sys/types.h>
+
+#include <assert.h>
+#include <ctype.h>
+#include <limits.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "man.h"
+#include "mandoc.h"
+#include "libman.h"
+
+#define	HASH_DEPTH	 6
+
+#define	HASH_ROW(x) do { \
+		if (isupper((unsigned char)(x))) \
+			(x) -= 65; \
+		else \
+			(x) -= 97; \
+		(x) *= HASH_DEPTH; \
+	} while (/* CONSTCOND */ 0)
+
+/*
+ * Lookup table is indexed first by lower-case first letter (plus one
+ * for the period, which is stored in the last row), then by lower or
+ * uppercase second letter.  Buckets correspond to the index of the
+ * macro (the integer value of the enum stored as a char to save a bit
+ * of space).
+ */
+static	unsigned char	 table[26 * HASH_DEPTH];
+
+/*
+ * XXX - this hash has global scope, so if intended for use as a library
+ * with multiple callers, it will need re-invocation protection.
+ */
+void
+man_hash_init(void)
+{
+	int		 i, j, x;
+
+	memset(table, UCHAR_MAX, sizeof(table));
+
+	assert(/* LINTED */ 
+			MAN_MAX < UCHAR_MAX);
+
+	for (i = 0; i < (int)MAN_MAX; i++) {
+		x = man_macronames[i][0];
+
+		assert(isalpha((unsigned char)x));
+
+		HASH_ROW(x);
+
+		for (j = 0; j < HASH_DEPTH; j++)
+			if (UCHAR_MAX == table[x + j]) {
+				table[x + j] = (unsigned char)i;
+				break;
+			}
+
+		assert(j < HASH_DEPTH);
+	}
+}
+
+
+enum mant
+man_hash_find(const char *tmp)
+{
+	int		 x, y, i;
+	enum mant	 tok;
+
+	if ('\0' == (x = tmp[0]))
+		return(MAN_MAX);
+	if ( ! (isalpha((unsigned char)x)))
+		return(MAN_MAX);
+
+	HASH_ROW(x);
+
+	for (i = 0; i < HASH_DEPTH; i++) {
+		if (UCHAR_MAX == (y = table[x + i]))
+			return(MAN_MAX);
+
+		tok = (enum mant)y;
+		if (0 == strcmp(tmp, man_macronames[tok]))
+			return(tok);
+	}
+
+	return(MAN_MAX);
+}
diff --git a/usr/src/cmd/mandoc/man_html.c b/usr/src/cmd/mandoc/man_html.c
new file mode 100644
index 0000000..a76ea2d
--- /dev/null
+++ b/usr/src/cmd/mandoc/man_html.c
@@ -0,0 +1,688 @@
+/*	$Id: man_html.c,v 1.86 2012/01/03 15:16:24 kristaps Exp $ */
+/*
+ * Copyright (c) 2008, 2009, 2010, 2011 Kristaps Dzonsons <kristaps@bsd.lv>
+ *
+ * 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.
+ */
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <sys/types.h>
+
+#include <assert.h>
+#include <ctype.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "mandoc.h"
+#include "out.h"
+#include "html.h"
+#include "man.h"
+#include "main.h"
+
+/* TODO: preserve ident widths. */
+/* FIXME: have PD set the default vspace width. */
+
+#define	INDENT		  5
+
+#define	MAN_ARGS	  const struct man_meta *m, \
+			  const struct man_node *n, \
+			  struct mhtml *mh, \
+			  struct html *h
+
+struct	mhtml {
+	int		  fl;
+#define	MANH_LITERAL	 (1 << 0) /* literal context */
+};
+
+struct	htmlman {
+	int		(*pre)(MAN_ARGS);
+	int		(*post)(MAN_ARGS);
+};
+
+static	void		  print_bvspace(struct html *, 
+				const struct man_node *);
+static	void		  print_man(MAN_ARGS);
+static	void		  print_man_head(MAN_ARGS);
+static	void		  print_man_nodelist(MAN_ARGS);
+static	void		  print_man_node(MAN_ARGS);
+static	int		  a2width(const struct man_node *,
+				struct roffsu *);
+static	int		  man_B_pre(MAN_ARGS);
+static	int		  man_HP_pre(MAN_ARGS);
+static	int		  man_IP_pre(MAN_ARGS);
+static	int		  man_I_pre(MAN_ARGS);
+static	int		  man_OP_pre(MAN_ARGS);
+static	int		  man_PP_pre(MAN_ARGS);
+static	int		  man_RS_pre(MAN_ARGS);
+static	int		  man_SH_pre(MAN_ARGS);
+static	int		  man_SM_pre(MAN_ARGS);
+static	int		  man_SS_pre(MAN_ARGS);
+static	int		  man_alt_pre(MAN_ARGS);
+static	int		  man_br_pre(MAN_ARGS);
+static	int		  man_ign_pre(MAN_ARGS);
+static	int		  man_in_pre(MAN_ARGS);
+static	int		  man_literal_pre(MAN_ARGS);
+static	void		  man_root_post(MAN_ARGS);
+static	void		  man_root_pre(MAN_ARGS);
+
+static	const struct htmlman mans[MAN_MAX] = {
+	{ man_br_pre, NULL }, /* br */
+	{ NULL, NULL }, /* TH */
+	{ man_SH_pre, NULL }, /* SH */
+	{ man_SS_pre, NULL }, /* SS */
+	{ man_IP_pre, NULL }, /* TP */
+	{ man_PP_pre, NULL }, /* LP */
+	{ man_PP_pre, NULL }, /* PP */
+	{ man_PP_pre, NULL }, /* P */
+	{ man_IP_pre, NULL }, /* IP */
+	{ man_HP_pre, NULL }, /* HP */ 
+	{ man_SM_pre, NULL }, /* SM */
+	{ man_SM_pre, NULL }, /* SB */
+	{ man_alt_pre, NULL }, /* BI */
+	{ man_alt_pre, NULL }, /* IB */
+	{ man_alt_pre, NULL }, /* BR */
+	{ man_alt_pre, NULL }, /* RB */
+	{ NULL, NULL }, /* R */
+	{ man_B_pre, NULL }, /* B */
+	{ man_I_pre, NULL }, /* I */
+	{ man_alt_pre, NULL }, /* IR */
+	{ man_alt_pre, NULL }, /* RI */
+	{ man_ign_pre, NULL }, /* na */
+	{ man_br_pre, NULL }, /* sp */
+	{ man_literal_pre, NULL }, /* nf */
+	{ man_literal_pre, NULL }, /* fi */
+	{ NULL, NULL }, /* RE */
+	{ man_RS_pre, NULL }, /* RS */
+	{ man_ign_pre, NULL }, /* DT */
+	{ man_ign_pre, NULL }, /* UC */
+	{ man_ign_pre, NULL }, /* PD */
+	{ man_ign_pre, NULL }, /* AT */
+	{ man_in_pre, NULL }, /* in */
+	{ man_ign_pre, NULL }, /* ft */
+	{ man_OP_pre, NULL }, /* OP */
+};
+
+/*
+ * Printing leading vertical space before a block.
+ * This is used for the paragraph macros.
+ * The rules are pretty simple, since there's very little nesting going
+ * on here.  Basically, if we're the first within another block (SS/SH),
+ * then don't emit vertical space.  If we are (RS), then do.  If not the
+ * first, print it.
+ */
+static void
+print_bvspace(struct html *h, const struct man_node *n)
+{
+
+	if (n->body && n->body->child)
+		if (MAN_TBL == n->body->child->type)
+			return;
+
+	if (MAN_ROOT == n->parent->type || MAN_RS != n->parent->tok)
+		if (NULL == n->prev)
+			return;
+
+	print_otag(h, TAG_P, 0, NULL);
+}
+
+void
+html_man(void *arg, const struct man *m)
+{
+	struct mhtml	 mh;
+
+	memset(&mh, 0, sizeof(struct mhtml));
+	print_man(man_meta(m), man_node(m), &mh, (struct html *)arg);
+	putchar('\n');
+}
+
+static void
+print_man(MAN_ARGS) 
+{
+	struct tag	*t, *tt;
+	struct htmlpair	 tag;
+
+	PAIR_CLASS_INIT(&tag, "mandoc");
+
+	if ( ! (HTML_FRAGMENT & h->oflags)) {
+		print_gen_decls(h);
+		t = print_otag(h, TAG_HTML, 0, NULL);
+		tt = print_otag(h, TAG_HEAD, 0, NULL);
+		print_man_head(m, n, mh, h);
+		print_tagq(h, tt);
+		print_otag(h, TAG_BODY, 0, NULL);
+		print_otag(h, TAG_DIV, 1, &tag);
+	} else 
+		t = print_otag(h, TAG_DIV, 1, &tag);
+
+	print_man_nodelist(m, n, mh, h);
+	print_tagq(h, t);
+}
+
+
+/* ARGSUSED */
+static void
+print_man_head(MAN_ARGS)
+{
+
+	print_gen_head(h);
+	assert(m->title);
+	assert(m->msec);
+	bufcat_fmt(h, "%s(%s)", m->title, m->msec);
+	print_otag(h, TAG_TITLE, 0, NULL);
+	print_text(h, h->buf);
+}
+
+
+static void
+print_man_nodelist(MAN_ARGS)
+{
+
+	print_man_node(m, n, mh, h);
+	if (n->next)
+		print_man_nodelist(m, n->next, mh, h);
+}
+
+
+static void
+print_man_node(MAN_ARGS)
+{
+	int		 child;
+	struct tag	*t;
+
+	child = 1;
+	t = h->tags.head;
+
+	switch (n->type) {
+	case (MAN_ROOT):
+		man_root_pre(m, n, mh, h);
+		break;
+	case (MAN_TEXT):
+		/*
+		 * If we have a blank line, output a vertical space.
+		 * If we have a space as the first character, break
+		 * before printing the line's data.
+		 */
+		if ('\0' == *n->string) {
+			print_otag(h, TAG_P, 0, NULL);
+			return;
+		}
+
+		if (' ' == *n->string && MAN_LINE & n->flags)
+			print_otag(h, TAG_BR, 0, NULL);
+		else if (MANH_LITERAL & mh->fl && n->prev)
+			print_otag(h, TAG_BR, 0, NULL);
+
+		print_text(h, n->string);
+		return;
+	case (MAN_EQN):
+		print_eqn(h, n->eqn);
+		break;
+	case (MAN_TBL):
+		/*
+		 * This will take care of initialising all of the table
+		 * state data for the first table, then tearing it down
+		 * for the last one.
+		 */
+		print_tbl(h, n->span);
+		return;
+	default:
+		/* 
+		 * Close out scope of font prior to opening a macro
+		 * scope.
+		 */
+		if (HTMLFONT_NONE != h->metac) {
+			h->metal = h->metac;
+			h->metac = HTMLFONT_NONE;
+		}
+
+		/*
+		 * Close out the current table, if it's open, and unset
+		 * the "meta" table state.  This will be reopened on the
+		 * next table element.
+		 */
+		if (h->tblt) {
+			print_tblclose(h);
+			t = h->tags.head;
+		}
+		if (mans[n->tok].pre)
+			child = (*mans[n->tok].pre)(m, n, mh, h);
+		break;
+	}
+
+	if (child && n->child)
+		print_man_nodelist(m, n->child, mh, h);
+
+	/* This will automatically close out any font scope. */
+	print_stagq(h, t);
+
+	switch (n->type) {
+	case (MAN_ROOT):
+		man_root_post(m, n, mh, h);
+		break;
+	case (MAN_EQN):
+		break;
+	default:
+		if (mans[n->tok].post)
+			(*mans[n->tok].post)(m, n, mh, h);
+		break;
+	}
+}
+
+
+static int
+a2width(const struct man_node *n, struct roffsu *su)
+{
+
+	if (MAN_TEXT != n->type)
+		return(0);
+	if (a2roffsu(n->string, su, SCALE_BU))
+		return(1);
+
+	return(0);
+}
+
+
+/* ARGSUSED */
+static void
+man_root_pre(MAN_ARGS)
+{
+	struct htmlpair	 tag[3];
+	struct tag	*t, *tt;
+	char		 b[BUFSIZ], title[BUFSIZ];
+
+	b[0] = 0;
+	if (m->vol)
+		(void)strlcat(b, m->vol, BUFSIZ);
+
+	assert(m->title);
+	assert(m->msec);
+	snprintf(title, BUFSIZ - 1, "%s(%s)", m->title, m->msec);
+
+	PAIR_SUMMARY_INIT(&tag[0], "Document Header");
+	PAIR_CLASS_INIT(&tag[1], "head");
+	PAIR_INIT(&tag[2], ATTR_WIDTH, "100%");
+	t = print_otag(h, TAG_TABLE, 3, tag);
+	PAIR_INIT(&tag[0], ATTR_WIDTH, "30%");
+	print_otag(h, TAG_COL, 1, tag);
+	print_otag(h, TAG_COL, 1, tag);
+	print_otag(h, TAG_COL, 1, tag);
+
+	print_otag(h, TAG_TBODY, 0, NULL);
+
+	tt = print_otag(h, TAG_TR, 0, NULL);
+
+	PAIR_CLASS_INIT(&tag[0], "head-ltitle");
+	print_otag(h, TAG_TD, 1, tag);
+	print_text(h, title);
+	print_stagq(h, tt);
+
+	PAIR_CLASS_INIT(&tag[0], "head-vol");
+	PAIR_INIT(&tag[1], ATTR_ALIGN, "center");
+	print_otag(h, TAG_TD, 2, tag);
+	print_text(h, b);
+	print_stagq(h, tt);
+
+	PAIR_CLASS_INIT(&tag[0], "head-rtitle");
+	PAIR_INIT(&tag[1], ATTR_ALIGN, "right");
+	print_otag(h, TAG_TD, 2, tag);
+	print_text(h, title);
+	print_tagq(h, t);
+}
+
+
+/* ARGSUSED */
+static void
+man_root_post(MAN_ARGS)
+{
+	struct htmlpair	 tag[3];
+	struct tag	*t, *tt;
+
+	PAIR_SUMMARY_INIT(&tag[0], "Document Footer");
+	PAIR_CLASS_INIT(&tag[1], "foot");
+	PAIR_INIT(&tag[2], ATTR_WIDTH, "100%");
+	t = print_otag(h, TAG_TABLE, 3, tag);
+	PAIR_INIT(&tag[0], ATTR_WIDTH, "50%");
+	print_otag(h, TAG_COL, 1, tag);
+	print_otag(h, TAG_COL, 1, tag);
+
+	tt = print_otag(h, TAG_TR, 0, NULL);
+
+	PAIR_CLASS_INIT(&tag[0], "foot-date");
+	print_otag(h, TAG_TD, 1, tag);
+
+	assert(m->date);
+	print_text(h, m->date);
+	print_stagq(h, tt);
+
+	PAIR_CLASS_INIT(&tag[0], "foot-os");
+	PAIR_INIT(&tag[1], ATTR_ALIGN, "right");
+	print_otag(h, TAG_TD, 2, tag);
+
+	if (m->source)
+		print_text(h, m->source);
+	print_tagq(h, t);
+}
+
+
+/* ARGSUSED */
+static int
+man_br_pre(MAN_ARGS)
+{
+	struct roffsu	 su;
+	struct htmlpair	 tag;
+
+	SCALE_VS_INIT(&su, 1);
+
+	if (MAN_sp == n->tok) {
+		if (NULL != (n = n->child))
+			if ( ! a2roffsu(n->string, &su, SCALE_VS))
+				SCALE_VS_INIT(&su, atoi(n->string));
+	} else
+		su.scale = 0;
+
+	bufinit(h);
+	bufcat_su(h, "height", &su);
+	PAIR_STYLE_INIT(&tag, h);
+	print_otag(h, TAG_DIV, 1, &tag);
+
+	/* So the div isn't empty: */
+	print_text(h, "\\~");
+
+	return(0);
+}
+
+/* ARGSUSED */
+static int
+man_SH_pre(MAN_ARGS)
+{
+	struct htmlpair	 tag;
+
+	if (MAN_BLOCK == n->type) {
+		mh->fl &= ~MANH_LITERAL;
+		PAIR_CLASS_INIT(&tag, "section");
+		print_otag(h, TAG_DIV, 1, &tag);
+		return(1);
+	} else if (MAN_BODY == n->type)
+		return(1);
+
+	print_otag(h, TAG_H1, 0, NULL);
+	return(1);
+}
+
+/* ARGSUSED */
+static int
+man_alt_pre(MAN_ARGS)
+{
+	const struct man_node	*nn;
+	int		 i, savelit;
+	enum htmltag	 fp;
+	struct tag	*t;
+
+	if ((savelit = mh->fl & MANH_LITERAL)) 
+		print_otag(h, TAG_BR, 0, NULL);
+
+	mh->fl &= ~MANH_LITERAL;
+
+	for (i = 0, nn = n->child; nn; nn = nn->next, i++) {
+		t = NULL;
+		switch (n->tok) {
+		case (MAN_BI):
+			fp = i % 2 ? TAG_I : TAG_B;
+			break;
+		case (MAN_IB):
+			fp = i % 2 ? TAG_B : TAG_I;
+			break;
+		case (MAN_RI):
+			fp = i % 2 ? TAG_I : TAG_MAX;
+			break;
+		case (MAN_IR):
+			fp = i % 2 ? TAG_MAX : TAG_I;
+			break;
+		case (MAN_BR):
+			fp = i % 2 ? TAG_MAX : TAG_B;
+			break;
+		case (MAN_RB):
+			fp = i % 2 ? TAG_B : TAG_MAX;
+			break;
+		default:
+			abort();
+			/* NOTREACHED */
+		}
+
+		if (i)
+			h->flags |= HTML_NOSPACE;
+
+		if (TAG_MAX != fp)
+			t = print_otag(h, fp, 0, NULL);
+
+		print_man_node(m, nn, mh, h);
+
+		if (t)
+			print_tagq(h, t);
+	}
+
+	if (savelit)
+		mh->fl |= MANH_LITERAL;
+
+	return(0);
+}
+
+/* ARGSUSED */
+static int
+man_SM_pre(MAN_ARGS)
+{
+	
+	print_otag(h, TAG_SMALL, 0, NULL);
+	if (MAN_SB == n->tok)
+		print_otag(h, TAG_B, 0, NULL);
+	return(1);
+}
+
+/* ARGSUSED */
+static int
+man_SS_pre(MAN_ARGS)
+{
+	struct htmlpair	 tag;
+
+	if (MAN_BLOCK == n->type) {
+		mh->fl &= ~MANH_LITERAL;
+		PAIR_CLASS_INIT(&tag, "subsection");
+		print_otag(h, TAG_DIV, 1, &tag);
+		return(1);
+	} else if (MAN_BODY == n->type)
+		return(1);
+
+	print_otag(h, TAG_H2, 0, NULL);
+	return(1);
+}
+
+/* ARGSUSED */
+static int
+man_PP_pre(MAN_ARGS)
+{
+
+	if (MAN_HEAD == n->type)
+		return(0);
+	else if (MAN_BLOCK == n->type)
+		print_bvspace(h, n);
+
+	return(1);
+}
+
+/* ARGSUSED */
+static int
+man_IP_pre(MAN_ARGS)
+{
+	const struct man_node	*nn;
+
+	if (MAN_BODY == n->type) { 
+		print_otag(h, TAG_DD, 0, NULL);
+		return(1);
+	} else if (MAN_HEAD != n->type) {
+		print_otag(h, TAG_DL, 0, NULL);
+		return(1);
+	}
+
+	/* FIXME: width specification. */
+
+	print_otag(h, TAG_DT, 0, NULL);
+
+	/* For IP, only print the first header element. */
+
+	if (MAN_IP == n->tok && n->child)
+		print_man_node(m, n->child, mh, h);
+
+	/* For TP, only print next-line header elements. */
+
+	if (MAN_TP == n->tok)
+		for (nn = n->child; nn; nn = nn->next)
+			if (nn->line > n->line)
+				print_man_node(m, nn, mh, h);
+
+	return(0);
+}
+
+/* ARGSUSED */
+static int
+man_HP_pre(MAN_ARGS)
+{
+	struct htmlpair	 tag;
+	struct roffsu	 su;
+	const struct man_node *np;
+
+	if (MAN_HEAD == n->type)
+		return(0);
+	else if (MAN_BLOCK != n->type)
+		return(1);
+
+	np = n->head->child;
+
+	if (NULL == np || ! a2width(np, &su))
+		SCALE_HS_INIT(&su, INDENT);
+
+	bufinit(h);
+
+	print_bvspace(h, n);
+	bufcat_su(h, "margin-left", &su);
+	su.scale = -su.scale;
+	bufcat_su(h, "text-indent", &su);
+	PAIR_STYLE_INIT(&tag, h);
+	print_otag(h, TAG_P, 1, &tag);
+	return(1);
+}
+
+/* ARGSUSED */
+static int
+man_OP_pre(MAN_ARGS)
+{
+	struct tag	*tt;
+	struct htmlpair	 tag;
+
+	print_text(h, "[");
+	h->flags |= HTML_NOSPACE;
+	PAIR_CLASS_INIT(&tag, "opt");
+	tt = print_otag(h, TAG_SPAN, 1, &tag);
+
+	if (NULL != (n = n->child)) {
+		print_otag(h, TAG_B, 0, NULL);
+		print_text(h, n->string);
+	}
+
+	print_stagq(h, tt);
+
+	if (NULL != n && NULL != n->next) {
+		print_otag(h, TAG_I, 0, NULL);
+		print_text(h, n->next->string);
+	}
+
+	print_stagq(h, tt);
+	h->flags |= HTML_NOSPACE;
+	print_text(h, "]");
+	return(0);
+}
+
+
+/* ARGSUSED */
+static int
+man_B_pre(MAN_ARGS)
+{
+
+	print_otag(h, TAG_B, 0, NULL);
+	return(1);
+}
+
+/* ARGSUSED */
+static int
+man_I_pre(MAN_ARGS)
+{
+	
+	print_otag(h, TAG_I, 0, NULL);
+	return(1);
+}
+
+/* ARGSUSED */
+static int
+man_literal_pre(MAN_ARGS)
+{
+
+	if (MAN_nf != n->tok) {
+		print_otag(h, TAG_BR, 0, NULL);
+		mh->fl &= ~MANH_LITERAL;
+	} else
+		mh->fl |= MANH_LITERAL;
+
+	return(0);
+}
+
+/* ARGSUSED */
+static int
+man_in_pre(MAN_ARGS)
+{
+
+	print_otag(h, TAG_BR, 0, NULL);
+	return(0);
+}
+
+/* ARGSUSED */
+static int
+man_ign_pre(MAN_ARGS)
+{
+
+	return(0);
+}
+
+/* ARGSUSED */
+static int
+man_RS_pre(MAN_ARGS)
+{
+	struct htmlpair	 tag;
+	struct roffsu	 su;
+
+	if (MAN_HEAD == n->type)
+		return(0);
+	else if (MAN_BODY == n->type)
+		return(1);
+
+	SCALE_HS_INIT(&su, INDENT);
+	if (n->head->child)
+		a2width(n->head->child, &su);
+
+	bufinit(h);
+	bufcat_su(h, "margin-left", &su);
+	PAIR_STYLE_INIT(&tag, h);
+	print_otag(h, TAG_DIV, 1, &tag);
+	return(1);
+}
diff --git a/usr/src/cmd/mandoc/man_macro.c b/usr/src/cmd/mandoc/man_macro.c
new file mode 100644
index 0000000..4bbbc4f
--- /dev/null
+++ b/usr/src/cmd/mandoc/man_macro.c
@@ -0,0 +1,484 @@
+/*	$Id: man_macro.c,v 1.71 2012/01/03 15:16:24 kristaps Exp $ */
+/*
+ * Copyright (c) 2008, 2009, 2010, 2011 Kristaps Dzonsons <kristaps@bsd.lv>
+ *
+ * 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.
+ */
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <assert.h>
+#include <ctype.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "man.h"
+#include "mandoc.h"
+#include "libmandoc.h"
+#include "libman.h"
+
+enum	rew {
+	REW_REWIND,
+	REW_NOHALT,
+	REW_HALT
+};
+
+static	int		 blk_close(MACRO_PROT_ARGS);
+static	int		 blk_exp(MACRO_PROT_ARGS);
+static	int		 blk_imp(MACRO_PROT_ARGS);
+static	int		 in_line_eoln(MACRO_PROT_ARGS);
+static	int		 man_args(struct man *, int, 
+				int *, char *, char **);
+
+static	int		 rew_scope(enum man_type, 
+				struct man *, enum mant);
+static	enum rew	 rew_dohalt(enum mant, enum man_type, 
+				const struct man_node *);
+static	enum rew	 rew_block(enum mant, enum man_type, 
+				const struct man_node *);
+static	void		 rew_warn(struct man *, 
+				struct man_node *, enum mandocerr);
+
+const	struct man_macro __man_macros[MAN_MAX] = {
+	{ in_line_eoln, MAN_NSCOPED }, /* br */
+	{ in_line_eoln, MAN_BSCOPE }, /* TH */
+	{ blk_imp, MAN_BSCOPE | MAN_SCOPED }, /* SH */
+	{ blk_imp, MAN_BSCOPE | MAN_SCOPED }, /* SS */
+	{ blk_imp, MAN_BSCOPE | MAN_SCOPED | MAN_FSCOPED }, /* TP */
+	{ blk_imp, MAN_BSCOPE }, /* LP */
+	{ blk_imp, MAN_BSCOPE }, /* PP */
+	{ blk_imp, MAN_BSCOPE }, /* P */
+	{ blk_imp, MAN_BSCOPE }, /* IP */
+	{ blk_imp, MAN_BSCOPE }, /* HP */
+	{ in_line_eoln, MAN_SCOPED }, /* SM */
+	{ in_line_eoln, MAN_SCOPED }, /* SB */
+	{ in_line_eoln, 0 }, /* BI */
+	{ in_line_eoln, 0 }, /* IB */
+	{ in_line_eoln, 0 }, /* BR */
+	{ in_line_eoln, 0 }, /* RB */
+	{ in_line_eoln, MAN_SCOPED }, /* R */
+	{ in_line_eoln, MAN_SCOPED }, /* B */
+	{ in_line_eoln, MAN_SCOPED }, /* I */
+	{ in_line_eoln, 0 }, /* IR */
+	{ in_line_eoln, 0 }, /* RI */
+	{ in_line_eoln, MAN_NSCOPED }, /* na */
+	{ in_line_eoln, MAN_NSCOPED }, /* sp */
+	{ in_line_eoln, MAN_BSCOPE }, /* nf */
+	{ in_line_eoln, MAN_BSCOPE }, /* fi */
+	{ blk_close, 0 }, /* RE */
+	{ blk_exp, MAN_EXPLICIT }, /* RS */
+	{ in_line_eoln, 0 }, /* DT */
+	{ in_line_eoln, 0 }, /* UC */
+	{ in_line_eoln, 0 }, /* PD */
+	{ in_line_eoln, 0 }, /* AT */
+	{ in_line_eoln, 0 }, /* in */
+	{ in_line_eoln, 0 }, /* ft */
+	{ in_line_eoln, 0 }, /* OP */
+};
+
+const	struct man_macro * const man_macros = __man_macros;
+
+
+/*
+ * Warn when "n" is an explicit non-roff macro.
+ */
+static void
+rew_warn(struct man *m, struct man_node *n, enum mandocerr er)
+{
+
+	if (er == MANDOCERR_MAX || MAN_BLOCK != n->type)
+		return;
+	if (MAN_VALID & n->flags)
+		return;
+	if ( ! (MAN_EXPLICIT & man_macros[n->tok].flags))
+		return;
+
+	assert(er < MANDOCERR_FATAL);
+	man_nmsg(m, n, er);
+}
+
+
+/*
+ * Rewind scope.  If a code "er" != MANDOCERR_MAX has been provided, it
+ * will be used if an explicit block scope is being closed out.
+ */
+int
+man_unscope(struct man *m, const struct man_node *to, 
+		enum mandocerr er)
+{
+	struct man_node	*n;
+
+	assert(to);
+
+	m->next = MAN_NEXT_SIBLING;
+
+	/* LINTED */
+	while (m->last != to) {
+		/*
+		 * Save the parent here, because we may delete the
+		 * m->last node in the post-validation phase and reset
+		 * it to m->last->parent, causing a step in the closing
+		 * out to be lost.
+		 */
+		n = m->last->parent;
+		rew_warn(m, m->last, er);
+		if ( ! man_valid_post(m))
+			return(0);
+		m->last = n;
+		assert(m->last);
+	}
+
+	rew_warn(m, m->last, er);
+	if ( ! man_valid_post(m))
+		return(0);
+
+	return(1);
+}
+
+
+static enum rew
+rew_block(enum mant ntok, enum man_type type, const struct man_node *n)
+{
+
+	if (MAN_BLOCK == type && ntok == n->parent->tok && 
+			MAN_BODY == n->parent->type)
+		return(REW_REWIND);
+	return(ntok == n->tok ? REW_HALT : REW_NOHALT);
+}
+
+
+/*
+ * There are three scope levels: scoped to the root (all), scoped to the
+ * section (all less sections), and scoped to subsections (all less
+ * sections and subsections).
+ */
+static enum rew 
+rew_dohalt(enum mant tok, enum man_type type, const struct man_node *n)
+{
+	enum rew	 c;
+
+	/* We cannot progress beyond the root ever. */
+	if (MAN_ROOT == n->type)
+		return(REW_HALT);
+
+	assert(n->parent);
+
+	/* Normal nodes shouldn't go to the level of the root. */
+	if (MAN_ROOT == n->parent->type)
+		return(REW_REWIND);
+
+	/* Already-validated nodes should be closed out. */
+	if (MAN_VALID & n->flags)
+		return(REW_NOHALT);
+
+	/* First: rewind to ourselves. */
+	if (type == n->type && tok == n->tok)
+		return(REW_REWIND);
+
+	/* 
+	 * Next follow the implicit scope-smashings as defined by man.7:
+	 * section, sub-section, etc.
+	 */
+
+	switch (tok) {
+	case (MAN_SH):
+		break;
+	case (MAN_SS):
+		/* Rewind to a section, if a block. */
+		if (REW_NOHALT != (c = rew_block(MAN_SH, type, n)))
+			return(c);
+		break;
+	case (MAN_RS):
+		/* Rewind to a subsection, if a block. */
+		if (REW_NOHALT != (c = rew_block(MAN_SS, type, n)))
+			return(c);
+		/* Rewind to a section, if a block. */
+		if (REW_NOHALT != (c = rew_block(MAN_SH, type, n)))
+			return(c);
+		break;
+	default:
+		/* Rewind to an offsetter, if a block. */
+		if (REW_NOHALT != (c = rew_block(MAN_RS, type, n)))
+			return(c);
+		/* Rewind to a subsection, if a block. */
+		if (REW_NOHALT != (c = rew_block(MAN_SS, type, n)))
+			return(c);
+		/* Rewind to a section, if a block. */
+		if (REW_NOHALT != (c = rew_block(MAN_SH, type, n)))
+			return(c);
+		break;
+	}
+
+	return(REW_NOHALT);
+}
+
+
+/*
+ * Rewinding entails ascending the parse tree until a coherent point,
+ * for example, the `SH' macro will close out any intervening `SS'
+ * scopes.  When a scope is closed, it must be validated and actioned.
+ */
+static int
+rew_scope(enum man_type type, struct man *m, enum mant tok)
+{
+	struct man_node	*n;
+	enum rew	 c;
+
+	/* LINTED */
+	for (n = m->last; n; n = n->parent) {
+		/* 
+		 * Whether we should stop immediately (REW_HALT), stop
+		 * and rewind until this point (REW_REWIND), or keep
+		 * rewinding (REW_NOHALT).
+		 */
+		c = rew_dohalt(tok, type, n);
+		if (REW_HALT == c)
+			return(1);
+		if (REW_REWIND == c)
+			break;
+	}
+
+	/* 
+	 * Rewind until the current point.  Warn if we're a roff
+	 * instruction that's mowing over explicit scopes.
+	 */
+	assert(n);
+
+	return(man_unscope(m, n, MANDOCERR_MAX));
+}
+
+
+/*
+ * Close out a generic explicit macro.
+ */
+/* ARGSUSED */
+int
+blk_close(MACRO_PROT_ARGS)
+{
+	enum mant	 	 ntok;
+	const struct man_node	*nn;
+
+	switch (tok) {
+	case (MAN_RE):
+		ntok = MAN_RS;
+		break;
+	default:
+		abort();
+		/* NOTREACHED */
+	}
+
+	for (nn = m->last->parent; nn; nn = nn->parent)
+		if (ntok == nn->tok)
+			break;
+
+	if (NULL == nn)
+		man_pmsg(m, line, ppos, MANDOCERR_NOSCOPE);
+
+	if ( ! rew_scope(MAN_BODY, m, ntok))
+		return(0);
+	if ( ! rew_scope(MAN_BLOCK, m, ntok))
+		return(0);
+
+	return(1);
+}
+
+
+/* ARGSUSED */
+int
+blk_exp(MACRO_PROT_ARGS)
+{
+	int		 la;
+	char		*p;
+
+	/* 
+	 * Close out prior scopes.  "Regular" explicit macros cannot be
+	 * nested, but we allow roff macros to be placed just about
+	 * anywhere.
+	 */
+
+	if ( ! man_block_alloc(m, line, ppos, tok))
+		return(0);
+	if ( ! man_head_alloc(m, line, ppos, tok))
+		return(0);
+
+	for (;;) {
+		la = *pos;
+		if ( ! man_args(m, line, pos, buf, &p))
+			break;
+		if ( ! man_word_alloc(m, line, la, p))
+			return(0);
+	}
+
+	assert(m);
+	assert(tok != MAN_MAX);
+
+	if ( ! rew_scope(MAN_HEAD, m, tok))
+		return(0);
+	return(man_body_alloc(m, line, ppos, tok));
+}
+
+
+
+/*
+ * Parse an implicit-block macro.  These contain a MAN_HEAD and a
+ * MAN_BODY contained within a MAN_BLOCK.  Rules for closing out other
+ * scopes, such as `SH' closing out an `SS', are defined in the rew
+ * routines.
+ */
+/* ARGSUSED */
+int
+blk_imp(MACRO_PROT_ARGS)
+{
+	int		 la;
+	char		*p;
+	struct man_node	*n;
+
+	/* Close out prior scopes. */
+
+	if ( ! rew_scope(MAN_BODY, m, tok))
+		return(0);
+	if ( ! rew_scope(MAN_BLOCK, m, tok))
+		return(0);
+
+	/* Allocate new block & head scope. */
+
+	if ( ! man_block_alloc(m, line, ppos, tok))
+		return(0);
+	if ( ! man_head_alloc(m, line, ppos, tok))
+		return(0);
+
+	n = m->last;
+
+	/* Add line arguments. */
+
+	for (;;) {
+		la = *pos;
+		if ( ! man_args(m, line, pos, buf, &p))
+			break;
+		if ( ! man_word_alloc(m, line, la, p))
+			return(0);
+	}
+
+	/* Close out head and open body (unless MAN_SCOPE). */
+
+	if (MAN_SCOPED & man_macros[tok].flags) {
+		/* If we're forcing scope (`TP'), keep it open. */
+		if (MAN_FSCOPED & man_macros[tok].flags) {
+			m->flags |= MAN_BLINE;
+			return(1);
+		} else if (n == m->last) {
+			m->flags |= MAN_BLINE;
+			return(1);
+		}
+	}
+
+	if ( ! rew_scope(MAN_HEAD, m, tok))
+		return(0);
+	return(man_body_alloc(m, line, ppos, tok));
+}
+
+
+/* ARGSUSED */
+int
+in_line_eoln(MACRO_PROT_ARGS)
+{
+	int		 la;
+	char		*p;
+	struct man_node	*n;
+
+	if ( ! man_elem_alloc(m, line, ppos, tok))
+		return(0);
+
+	n = m->last;
+
+	for (;;) {
+		la = *pos;
+		if ( ! man_args(m, line, pos, buf, &p))
+			break;
+		if ( ! man_word_alloc(m, line, la, p))
+			return(0);
+	}
+
+	/*
+	 * If no arguments are specified and this is MAN_SCOPED (i.e.,
+	 * next-line scoped), then set our mode to indicate that we're
+	 * waiting for terms to load into our context.
+	 */
+
+	if (n == m->last && MAN_SCOPED & man_macros[tok].flags) {
+		assert( ! (MAN_NSCOPED & man_macros[tok].flags));
+		m->flags |= MAN_ELINE;
+		return(1);
+	} 
+
+	/* Set ignorable context, if applicable. */
+
+	if (MAN_NSCOPED & man_macros[tok].flags) {
+		assert( ! (MAN_SCOPED & man_macros[tok].flags));
+		m->flags |= MAN_ILINE;
+	}
+
+	assert(MAN_ROOT != m->last->type);
+	m->next = MAN_NEXT_SIBLING;
+	
+	/*
+	 * Rewind our element scope.  Note that when TH is pruned, we'll
+	 * be back at the root, so make sure that we don't clobber as
+	 * its sibling.
+	 */
+
+	for ( ; m->last; m->last = m->last->parent) {
+		if (m->last == n)
+			break;
+		if (m->last->type == MAN_ROOT)
+			break;
+		if ( ! man_valid_post(m))
+			return(0);
+	}
+
+	assert(m->last);
+
+	/*
+	 * Same here regarding whether we're back at the root. 
+	 */
+
+	if (m->last->type != MAN_ROOT && ! man_valid_post(m))
+		return(0);
+
+	return(1);
+}
+
+
+int
+man_macroend(struct man *m)
+{
+
+	return(man_unscope(m, m->first, MANDOCERR_SCOPEEXIT));
+}
+
+static int
+man_args(struct man *m, int line, int *pos, char *buf, char **v)
+{
+	char	 *start;
+
+	assert(*pos);
+	*v = start = buf + *pos;
+	assert(' ' != *start);
+
+	if ('\0' == *start)
+		return(0);
+
+	*v = mandoc_getarg(m->parse, v, line, pos);
+	return(1);
+}
diff --git a/usr/src/cmd/mandoc/man_term.c b/usr/src/cmd/mandoc/man_term.c
new file mode 100644
index 0000000..69c5c95
--- /dev/null
+++ b/usr/src/cmd/mandoc/man_term.c
@@ -0,0 +1,1117 @@
+/*	$Id: man_term.c,v 1.127 2012/01/03 15:16:24 kristaps Exp $ */
+/*
+ * Copyright (c) 2008, 2009, 2010, 2011 Kristaps Dzonsons <kristaps@bsd.lv>
+ * Copyright (c) 2010, 2011 Ingo Schwarze <schwarze@openbsd.org>
+ *
+ * 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.
+ */
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <sys/types.h>
+
+#include <assert.h>
+#include <ctype.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "mandoc.h"
+#include "out.h"
+#include "man.h"
+#include "term.h"
+#include "main.h"
+
+#define	MAXMARGINS	  64 /* maximum number of indented scopes */
+
+/* FIXME: have PD set the default vspace width. */
+
+struct	mtermp {
+	int		  fl;
+#define	MANT_LITERAL	 (1 << 0)
+	size_t		  lmargin[MAXMARGINS]; /* margins (incl. visible page) */
+	int		  lmargincur; /* index of current margin */
+	int		  lmarginsz; /* actual number of nested margins */
+	size_t		  offset; /* default offset to visible page */
+};
+
+#define	DECL_ARGS 	  struct termp *p, \
+			  struct mtermp *mt, \
+			  const struct man_node *n, \
+			  const struct man_meta *m
+
+struct	termact {
+	int		(*pre)(DECL_ARGS);
+	void		(*post)(DECL_ARGS);
+	int		  flags;
+#define	MAN_NOTEXT	 (1 << 0) /* Never has text children. */
+};
+
+static	int		  a2width(const struct termp *, const char *);
+static	size_t		  a2height(const struct termp *, const char *);
+
+static	void		  print_man_nodelist(DECL_ARGS);
+static	void		  print_man_node(DECL_ARGS);
+static	void		  print_man_head(struct termp *, const void *);
+static	void		  print_man_foot(struct termp *, const void *);
+static	void		  print_bvspace(struct termp *, 
+				const struct man_node *);
+
+static	int		  pre_B(DECL_ARGS);
+static	int		  pre_HP(DECL_ARGS);
+static	int		  pre_I(DECL_ARGS);
+static	int		  pre_IP(DECL_ARGS);
+static	int		  pre_OP(DECL_ARGS);
+static	int		  pre_PP(DECL_ARGS);
+static	int		  pre_RS(DECL_ARGS);
+static	int		  pre_SH(DECL_ARGS);
+static	int		  pre_SS(DECL_ARGS);
+static	int		  pre_TP(DECL_ARGS);
+static	int		  pre_alternate(DECL_ARGS);
+static	int		  pre_ft(DECL_ARGS);
+static	int		  pre_ign(DECL_ARGS);
+static	int		  pre_in(DECL_ARGS);
+static	int		  pre_literal(DECL_ARGS);
+static	int		  pre_sp(DECL_ARGS);
+
+static	void		  post_IP(DECL_ARGS);
+static	void		  post_HP(DECL_ARGS);
+static	void		  post_RS(DECL_ARGS);
+static	void		  post_SH(DECL_ARGS);
+static	void		  post_SS(DECL_ARGS);
+static	void		  post_TP(DECL_ARGS);
+
+static	const struct termact termacts[MAN_MAX] = {
+	{ pre_sp, NULL, MAN_NOTEXT }, /* br */
+	{ NULL, NULL, 0 }, /* TH */
+	{ pre_SH, post_SH, 0 }, /* SH */
+	{ pre_SS, post_SS, 0 }, /* SS */
+	{ pre_TP, post_TP, 0 }, /* TP */
+	{ pre_PP, NULL, 0 }, /* LP */
+	{ pre_PP, NULL, 0 }, /* PP */
+	{ pre_PP, NULL, 0 }, /* P */
+	{ pre_IP, post_IP, 0 }, /* IP */
+	{ pre_HP, post_HP, 0 }, /* HP */ 
+	{ NULL, NULL, 0 }, /* SM */
+	{ pre_B, NULL, 0 }, /* SB */
+	{ pre_alternate, NULL, 0 }, /* BI */
+	{ pre_alternate, NULL, 0 }, /* IB */
+	{ pre_alternate, NULL, 0 }, /* BR */
+	{ pre_alternate, NULL, 0 }, /* RB */
+	{ NULL, NULL, 0 }, /* R */
+	{ pre_B, NULL, 0 }, /* B */
+	{ pre_I, NULL, 0 }, /* I */
+	{ pre_alternate, NULL, 0 }, /* IR */
+	{ pre_alternate, NULL, 0 }, /* RI */
+	{ pre_ign, NULL, MAN_NOTEXT }, /* na */
+	{ pre_sp, NULL, MAN_NOTEXT }, /* sp */
+	{ pre_literal, NULL, 0 }, /* nf */
+	{ pre_literal, NULL, 0 }, /* fi */
+	{ NULL, NULL, 0 }, /* RE */
+	{ pre_RS, post_RS, 0 }, /* RS */
+	{ pre_ign, NULL, 0 }, /* DT */
+	{ pre_ign, NULL, 0 }, /* UC */
+	{ pre_ign, NULL, 0 }, /* PD */
+	{ pre_ign, NULL, 0 }, /* AT */
+	{ pre_in, NULL, MAN_NOTEXT }, /* in */
+	{ pre_ft, NULL, MAN_NOTEXT }, /* ft */
+	{ pre_OP, NULL, 0 }, /* OP */
+};
+
+
+
+void
+terminal_man(void *arg, const struct man *man)
+{
+	struct termp		*p;
+	const struct man_node	*n;
+	const struct man_meta	*m;
+	struct mtermp		 mt;
+
+	p = (struct termp *)arg;
+
+	if (0 == p->defindent)
+		p->defindent = 7;
+
+	p->overstep = 0;
+	p->maxrmargin = p->defrmargin;
+	p->tabwidth = term_len(p, 5);
+
+	if (NULL == p->symtab)
+		p->symtab = mchars_alloc();
+
+	n = man_node(man);
+	m = man_meta(man);
+
+	term_begin(p, print_man_head, print_man_foot, m);
+	p->flags |= TERMP_NOSPACE;
+
+	memset(&mt, 0, sizeof(struct mtermp));
+
+	mt.lmargin[mt.lmargincur] = term_len(p, p->defindent);
+	mt.offset = term_len(p, p->defindent);
+
+	if (n->child)
+		print_man_nodelist(p, &mt, n->child, m);
+
+	term_end(p);
+}
+
+
+static size_t
+a2height(const struct termp *p, const char *cp)
+{
+	struct roffsu	 su;
+
+	if ( ! a2roffsu(cp, &su, SCALE_VS))
+		SCALE_VS_INIT(&su, atoi(cp));
+
+	return(term_vspan(p, &su));
+}
+
+
+static int
+a2width(const struct termp *p, const char *cp)
+{
+	struct roffsu	 su;
+
+	if ( ! a2roffsu(cp, &su, SCALE_BU))
+		return(-1);
+
+	return((int)term_hspan(p, &su));
+}
+
+/*
+ * Printing leading vertical space before a block.
+ * This is used for the paragraph macros.
+ * The rules are pretty simple, since there's very little nesting going
+ * on here.  Basically, if we're the first within another block (SS/SH),
+ * then don't emit vertical space.  If we are (RS), then do.  If not the
+ * first, print it.
+ */
+static void
+print_bvspace(struct termp *p, const struct man_node *n)
+{
+
+	term_newln(p);
+
+	if (n->body && n->body->child)
+		if (MAN_TBL == n->body->child->type)
+			return;
+
+	if (MAN_ROOT == n->parent->type || MAN_RS != n->parent->tok)
+		if (NULL == n->prev)
+			return;
+
+	term_vspace(p);
+}
+
+/* ARGSUSED */
+static int
+pre_ign(DECL_ARGS)
+{
+
+	return(0);
+}
+
+
+/* ARGSUSED */
+static int
+pre_I(DECL_ARGS)
+{
+
+	term_fontrepl(p, TERMFONT_UNDER);
+	return(1);
+}
+
+
+/* ARGSUSED */
+static int
+pre_literal(DECL_ARGS)
+{
+
+	term_newln(p);
+
+	if (MAN_nf == n->tok)
+		mt->fl |= MANT_LITERAL;
+	else
+		mt->fl &= ~MANT_LITERAL;
+
+	/*
+	 * Unlike .IP and .TP, .HP does not have a HEAD.
+	 * So in case a second call to term_flushln() is needed,
+	 * indentation has to be set up explicitly.
+	 */
+	if (MAN_HP == n->parent->tok && p->rmargin < p->maxrmargin) {
+		p->offset = p->rmargin;
+		p->rmargin = p->maxrmargin;
+		p->flags &= ~(TERMP_NOBREAK | TERMP_TWOSPACE);
+		p->flags |= TERMP_NOSPACE;
+	}
+
+	return(0);
+}
+
+/* ARGSUSED */
+static int
+pre_alternate(DECL_ARGS)
+{
+	enum termfont		 font[2];
+	const struct man_node	*nn;
+	int			 savelit, i;
+
+	switch (n->tok) {
+	case (MAN_RB):
+		font[0] = TERMFONT_NONE;
+		font[1] = TERMFONT_BOLD;
+		break;
+	case (MAN_RI):
+		font[0] = TERMFONT_NONE;
+		font[1] = TERMFONT_UNDER;
+		break;
+	case (MAN_BR):
+		font[0] = TERMFONT_BOLD;
+		font[1] = TERMFONT_NONE;
+		break;
+	case (MAN_BI):
+		font[0] = TERMFONT_BOLD;
+		font[1] = TERMFONT_UNDER;
+		break;
+	case (MAN_IR):
+		font[0] = TERMFONT_UNDER;
+		font[1] = TERMFONT_NONE;
+		break;
+	case (MAN_IB):
+		font[0] = TERMFONT_UNDER;
+		font[1] = TERMFONT_BOLD;
+		break;
+	default:
+		abort();
+	}
+
+	savelit = MANT_LITERAL & mt->fl;
+	mt->fl &= ~MANT_LITERAL;
+
+	for (i = 0, nn = n->child; nn; nn = nn->next, i = 1 - i) {
+		term_fontrepl(p, font[i]);
+		if (savelit && NULL == nn->next)
+			mt->fl |= MANT_LITERAL;
+		print_man_node(p, mt, nn, m);
+		if (nn->next)
+			p->flags |= TERMP_NOSPACE;
+	}
+
+	return(0);
+}
+
+/* ARGSUSED */
+static int
+pre_B(DECL_ARGS)
+{
+
+	term_fontrepl(p, TERMFONT_BOLD);
+	return(1);
+}
+
+/* ARGSUSED */
+static int
+pre_OP(DECL_ARGS)
+{
+
+	term_word(p, "[");
+	p->flags |= TERMP_NOSPACE;
+
+	if (NULL != (n = n->child)) {
+		term_fontrepl(p, TERMFONT_BOLD);
+		term_word(p, n->string);
+	}
+	if (NULL != n && NULL != n->next) {
+		term_fontrepl(p, TERMFONT_UNDER);
+		term_word(p, n->next->string);
+	}
+
+	term_fontrepl(p, TERMFONT_NONE);
+	p->flags |= TERMP_NOSPACE;
+	term_word(p, "]");
+	return(0);
+}
+
+/* ARGSUSED */
+static int
+pre_ft(DECL_ARGS)
+{
+	const char	*cp;
+
+	if (NULL == n->child) {
+		term_fontlast(p);
+		return(0);
+	}
+
+	cp = n->child->string;
+	switch (*cp) {
+	case ('4'):
+		/* FALLTHROUGH */
+	case ('3'):
+		/* FALLTHROUGH */
+	case ('B'):
+		term_fontrepl(p, TERMFONT_BOLD);
+		break;
+	case ('2'):
+		/* FALLTHROUGH */
+	case ('I'):
+		term_fontrepl(p, TERMFONT_UNDER);
+		break;
+	case ('P'):
+		term_fontlast(p);
+		break;
+	case ('1'):
+		/* FALLTHROUGH */
+	case ('C'):
+		/* FALLTHROUGH */
+	case ('R'):
+		term_fontrepl(p, TERMFONT_NONE);
+		break;
+	default:
+		break;
+	}
+	return(0);
+}
+
+/* ARGSUSED */
+static int
+pre_in(DECL_ARGS)
+{
+	int		 len, less;
+	size_t		 v;
+	const char	*cp;
+
+	term_newln(p);
+
+	if (NULL == n->child) {
+		p->offset = mt->offset;
+		return(0);
+	}
+
+	cp = n->child->string;
+	less = 0;
+
+	if ('-' == *cp)
+		less = -1;
+	else if ('+' == *cp)
+		less = 1;
+	else
+		cp--;
+
+	if ((len = a2width(p, ++cp)) < 0)
+		return(0);
+
+	v = (size_t)len;
+
+	if (less < 0)
+		p->offset -= p->offset > v ? v : p->offset;
+	else if (less > 0)
+		p->offset += v;
+	else 
+		p->offset = v;
+
+	/* Don't let this creep beyond the right margin. */
+
+	if (p->offset > p->rmargin)
+		p->offset = p->rmargin;
+
+	return(0);
+}
+
+
+/* ARGSUSED */
+static int
+pre_sp(DECL_ARGS)
+{
+	size_t		 i, len;
+
+	if ((NULL == n->prev && n->parent)) {
+		if (MAN_SS == n->parent->tok)
+			return(0);
+		if (MAN_SH == n->parent->tok)
+			return(0);
+	}
+
+	switch (n->tok) {
+	case (MAN_br):
+		len = 0;
+		break;
+	default:
+		len = n->child ? a2height(p, n->child->string) : 1;
+		break;
+	}
+
+	if (0 == len)
+		term_newln(p);
+	for (i = 0; i < len; i++)
+		term_vspace(p);
+
+	return(0);
+}
+
+
+/* ARGSUSED */
+static int
+pre_HP(DECL_ARGS)
+{
+	size_t			 len, one;
+	int			 ival;
+	const struct man_node	*nn;
+
+	switch (n->type) {
+	case (MAN_BLOCK):
+		print_bvspace(p, n);
+		return(1);
+	case (MAN_BODY):
+		p->flags |= TERMP_NOBREAK;
+		p->flags |= TERMP_TWOSPACE;
+		break;
+	default:
+		return(0);
+	}
+
+	len = mt->lmargin[mt->lmargincur];
+	ival = -1;
+
+	/* Calculate offset. */
+
+	if (NULL != (nn = n->parent->head->child))
+		if ((ival = a2width(p, nn->string)) >= 0)
+			len = (size_t)ival;
+
+	one = term_len(p, 1);
+	if (len < one)
+		len = one;
+
+	p->offset = mt->offset;
+	p->rmargin = mt->offset + len;
+
+	if (ival >= 0)
+		mt->lmargin[mt->lmargincur] = (size_t)ival;
+
+	return(1);
+}
+
+
+/* ARGSUSED */
+static void
+post_HP(DECL_ARGS)
+{
+
+	switch (n->type) {
+	case (MAN_BLOCK):
+		term_flushln(p);
+		break;
+	case (MAN_BODY):
+		term_flushln(p);
+		p->flags &= ~TERMP_NOBREAK;
+		p->flags &= ~TERMP_TWOSPACE;
+		p->offset = mt->offset;
+		p->rmargin = p->maxrmargin;
+		break;
+	default:
+		break;
+	}
+}
+
+
+/* ARGSUSED */
+static int
+pre_PP(DECL_ARGS)
+{
+
+	switch (n->type) {
+	case (MAN_BLOCK):
+		mt->lmargin[mt->lmargincur] = term_len(p, p->defindent);
+		print_bvspace(p, n);
+		break;
+	default:
+		p->offset = mt->offset;
+		break;
+	}
+
+	return(MAN_HEAD != n->type);
+}
+
+
+/* ARGSUSED */
+static int
+pre_IP(DECL_ARGS)
+{
+	const struct man_node	*nn;
+	size_t			 len;
+	int			 savelit, ival;
+
+	switch (n->type) {
+	case (MAN_BODY):
+		p->flags |= TERMP_NOSPACE;
+		break;
+	case (MAN_HEAD):
+		p->flags |= TERMP_NOBREAK;
+		break;
+	case (MAN_BLOCK):
+		print_bvspace(p, n);
+		/* FALLTHROUGH */
+	default:
+		return(1);
+	}
+
+	len = mt->lmargin[mt->lmargincur];
+	ival = -1;
+
+	/* Calculate the offset from the optional second argument. */
+	if (NULL != (nn = n->parent->head->child))
+		if (NULL != (nn = nn->next))
+			if ((ival = a2width(p, nn->string)) >= 0)
+				len = (size_t)ival;
+
+	switch (n->type) {
+	case (MAN_HEAD):
+		/* Handle zero-width lengths. */
+		if (0 == len)
+			len = term_len(p, 1);
+
+		p->offset = mt->offset;
+		p->rmargin = mt->offset + len;
+		if (ival < 0)
+			break;
+
+		/* Set the saved left-margin. */
+		mt->lmargin[mt->lmargincur] = (size_t)ival;
+
+		savelit = MANT_LITERAL & mt->fl;
+		mt->fl &= ~MANT_LITERAL;
+
+		if (n->child)
+			print_man_node(p, mt, n->child, m);
+
+		if (savelit)
+			mt->fl |= MANT_LITERAL;
+
+		return(0);
+	case (MAN_BODY):
+		p->offset = mt->offset + len;
+		p->rmargin = p->maxrmargin;
+		break;
+	default:
+		break;
+	}
+
+	return(1);
+}
+
+
+/* ARGSUSED */
+static void
+post_IP(DECL_ARGS)
+{
+
+	switch (n->type) {
+	case (MAN_HEAD):
+		term_flushln(p);
+		p->flags &= ~TERMP_NOBREAK;
+		p->rmargin = p->maxrmargin;
+		break;
+	case (MAN_BODY):
+		term_newln(p);
+		break;
+	default:
+		break;
+	}
+}
+
+
+/* ARGSUSED */
+static int
+pre_TP(DECL_ARGS)
+{
+	const struct man_node	*nn;
+	size_t			 len;
+	int			 savelit, ival;
+
+	switch (n->type) {
+	case (MAN_HEAD):
+		p->flags |= TERMP_NOBREAK;
+		break;
+	case (MAN_BODY):
+		p->flags |= TERMP_NOSPACE;
+		break;
+	case (MAN_BLOCK):
+		print_bvspace(p, n);
+		/* FALLTHROUGH */
+	default:
+		return(1);
+	}
+
+	len = (size_t)mt->lmargin[mt->lmargincur];
+	ival = -1;
+
+	/* Calculate offset. */
+
+	if (NULL != (nn = n->parent->head->child))
+		if (nn->string && nn->parent->line == nn->line)
+			if ((ival = a2width(p, nn->string)) >= 0)
+				len = (size_t)ival;
+
+	switch (n->type) {
+	case (MAN_HEAD):
+		/* Handle zero-length properly. */
+		if (0 == len)
+			len = term_len(p, 1);
+
+		p->offset = mt->offset;
+		p->rmargin = mt->offset + len;
+
+		savelit = MANT_LITERAL & mt->fl;
+		mt->fl &= ~MANT_LITERAL;
+
+		/* Don't print same-line elements. */
+		for (nn = n->child; nn; nn = nn->next)
+			if (nn->line > n->line)
+				print_man_node(p, mt, nn, m);
+
+		if (savelit)
+			mt->fl |= MANT_LITERAL;
+		if (ival >= 0)
+			mt->lmargin[mt->lmargincur] = (size_t)ival;
+
+		return(0);
+	case (MAN_BODY):
+		p->offset = mt->offset + len;
+		p->rmargin = p->maxrmargin;
+		break;
+	default:
+		break;
+	}
+
+	return(1);
+}
+
+
+/* ARGSUSED */
+static void
+post_TP(DECL_ARGS)
+{
+
+	switch (n->type) {
+	case (MAN_HEAD):
+		term_flushln(p);
+		p->flags &= ~TERMP_NOBREAK;
+		p->flags &= ~TERMP_TWOSPACE;
+		p->rmargin = p->maxrmargin;
+		break;
+	case (MAN_BODY):
+		term_newln(p);
+		break;
+	default:
+		break;
+	}
+}
+
+
+/* ARGSUSED */
+static int
+pre_SS(DECL_ARGS)
+{
+
+	switch (n->type) {
+	case (MAN_BLOCK):
+		mt->fl &= ~MANT_LITERAL;
+		mt->lmargin[mt->lmargincur] = term_len(p, p->defindent);
+		mt->offset = term_len(p, p->defindent);
+		/* If following a prior empty `SS', no vspace. */
+		if (n->prev && MAN_SS == n->prev->tok)
+			if (NULL == n->prev->body->child)
+				break;
+		if (NULL == n->prev)
+			break;
+		term_vspace(p);
+		break;
+	case (MAN_HEAD):
+		term_fontrepl(p, TERMFONT_BOLD);
+		p->offset = term_len(p, p->defindent/2);
+		break;
+	case (MAN_BODY):
+		p->offset = mt->offset;
+		break;
+	default:
+		break;
+	}
+
+	return(1);
+}
+
+
+/* ARGSUSED */
+static void
+post_SS(DECL_ARGS)
+{
+	
+	switch (n->type) {
+	case (MAN_HEAD):
+		term_newln(p);
+		break;
+	case (MAN_BODY):
+		term_newln(p);
+		break;
+	default:
+		break;
+	}
+}
+
+
+/* ARGSUSED */
+static int
+pre_SH(DECL_ARGS)
+{
+
+	switch (n->type) {
+	case (MAN_BLOCK):
+		mt->fl &= ~MANT_LITERAL;
+		mt->lmargin[mt->lmargincur] = term_len(p, p->defindent);
+		mt->offset = term_len(p, p->defindent);
+		/* If following a prior empty `SH', no vspace. */
+		if (n->prev && MAN_SH == n->prev->tok)
+			if (NULL == n->prev->body->child)
+				break;
+		/* If the first macro, no vspae. */
+		if (NULL == n->prev)
+			break;
+		term_vspace(p);
+		break;
+	case (MAN_HEAD):
+		term_fontrepl(p, TERMFONT_BOLD);
+		p->offset = 0;
+		break;
+	case (MAN_BODY):
+		p->offset = mt->offset;
+		break;
+	default:
+		break;
+	}
+
+	return(1);
+}
+
+
+/* ARGSUSED */
+static void
+post_SH(DECL_ARGS)
+{
+	
+	switch (n->type) {
+	case (MAN_HEAD):
+		term_newln(p);
+		break;
+	case (MAN_BODY):
+		term_newln(p);
+		break;
+	default:
+		break;
+	}
+}
+
+/* ARGSUSED */
+static int
+pre_RS(DECL_ARGS)
+{
+	int		 ival;
+	size_t		 sz;
+
+	switch (n->type) {
+	case (MAN_BLOCK):
+		term_newln(p);
+		return(1);
+	case (MAN_HEAD):
+		return(0);
+	default:
+		break;
+	}
+
+	sz = term_len(p, p->defindent);
+
+	if (NULL != (n = n->parent->head->child))
+		if ((ival = a2width(p, n->string)) >= 0) 
+			sz = (size_t)ival;
+
+	mt->offset += sz;
+	p->rmargin = p->maxrmargin;
+	p->offset = mt->offset < p->rmargin ? mt->offset : p->rmargin;
+
+	if (++mt->lmarginsz < MAXMARGINS)
+		mt->lmargincur = mt->lmarginsz;
+
+	mt->lmargin[mt->lmargincur] = mt->lmargin[mt->lmargincur - 1];
+	return(1);
+}
+
+/* ARGSUSED */
+static void
+post_RS(DECL_ARGS)
+{
+	int		 ival;
+	size_t		 sz;
+
+	switch (n->type) {
+	case (MAN_BLOCK):
+		return;
+	case (MAN_HEAD):
+		return;
+	default:
+		term_newln(p);
+		break;
+	}
+
+	sz = term_len(p, p->defindent);
+
+	if (NULL != (n = n->parent->head->child)) 
+		if ((ival = a2width(p, n->string)) >= 0) 
+			sz = (size_t)ival;
+
+	mt->offset = mt->offset < sz ?  0 : mt->offset - sz;
+	p->offset = mt->offset;
+
+	if (--mt->lmarginsz < MAXMARGINS)
+		mt->lmargincur = mt->lmarginsz;
+}
+
+static void
+print_man_node(DECL_ARGS)
+{
+	size_t		 rm, rmax;
+	int		 c;
+
+	switch (n->type) {
+	case(MAN_TEXT):
+		/*
+		 * If we have a blank line, output a vertical space.
+		 * If we have a space as the first character, break
+		 * before printing the line's data.
+		 */
+		if ('\0' == *n->string) {
+			term_vspace(p);
+			return;
+		} else if (' ' == *n->string && MAN_LINE & n->flags)
+			term_newln(p);
+
+		term_word(p, n->string);
+
+		/*
+		 * If we're in a literal context, make sure that words
+		 * togehter on the same line stay together.  This is a
+		 * POST-printing call, so we check the NEXT word.  Since
+		 * -man doesn't have nested macros, we don't need to be
+		 * more specific than this.
+		 */
+		if (MANT_LITERAL & mt->fl && ! (TERMP_NOBREAK & p->flags) &&
+				(NULL == n->next || 
+				 n->next->line > n->line)) {
+			rm = p->rmargin;
+			rmax = p->maxrmargin;
+			p->rmargin = p->maxrmargin = TERM_MAXMARGIN;
+			p->flags |= TERMP_NOSPACE;
+			term_flushln(p);
+			p->rmargin = rm;
+			p->maxrmargin = rmax;
+		}
+
+		if (MAN_EOS & n->flags)
+			p->flags |= TERMP_SENTENCE;
+		return;
+	case (MAN_EQN):
+		term_eqn(p, n->eqn);
+		return;
+	case (MAN_TBL):
+		/*
+		 * Tables are preceded by a newline.  Then process a
+		 * table line, which will cause line termination,
+		 */
+		if (TBL_SPAN_FIRST & n->span->flags) 
+			term_newln(p);
+		term_tbl(p, n->span);
+		return;
+	default:
+		break;
+	}
+
+	if ( ! (MAN_NOTEXT & termacts[n->tok].flags))
+		term_fontrepl(p, TERMFONT_NONE);
+
+	c = 1;
+	if (termacts[n->tok].pre)
+		c = (*termacts[n->tok].pre)(p, mt, n, m);
+
+	if (c && n->child)
+		print_man_nodelist(p, mt, n->child, m);
+
+	if (termacts[n->tok].post)
+		(*termacts[n->tok].post)(p, mt, n, m);
+	if ( ! (MAN_NOTEXT & termacts[n->tok].flags))
+		term_fontrepl(p, TERMFONT_NONE);
+
+	if (MAN_EOS & n->flags)
+		p->flags |= TERMP_SENTENCE;
+}
+
+
+static void
+print_man_nodelist(DECL_ARGS)
+{
+
+	print_man_node(p, mt, n, m);
+	if ( ! n->next)
+		return;
+	print_man_nodelist(p, mt, n->next, m);
+}
+
+
+static void
+print_man_foot(struct termp *p, const void *arg)
+{
+	char		title[BUFSIZ];
+	size_t		datelen;
+	const struct man_meta *meta;
+
+	meta = (const struct man_meta *)arg;
+	assert(meta->title);
+	assert(meta->msec);
+	assert(meta->date);
+
+	term_fontrepl(p, TERMFONT_NONE);
+
+	term_vspace(p);
+
+	/*
+	 * Temporary, undocumented option to imitate mdoc(7) output.
+	 * In the bottom right corner, use the source instead of
+	 * the title.
+	 */
+
+	if ( ! p->mdocstyle) {
+		term_vspace(p);
+		term_vspace(p);
+		snprintf(title, BUFSIZ, "%s(%s)", meta->title, meta->msec);
+	} else if (meta->source) {
+		strlcpy(title, meta->source, BUFSIZ);
+	} else {
+		title[0] = '\0';
+	}
+	datelen = term_strlen(p, meta->date);
+
+	/* Bottom left corner: manual source. */
+
+	p->flags |= TERMP_NOSPACE | TERMP_NOBREAK;
+	p->offset = 0;
+	p->rmargin = (p->maxrmargin - datelen + term_len(p, 1)) / 2;
+
+	if (meta->source)
+		term_word(p, meta->source);
+	term_flushln(p);
+
+	/* At the bottom in the middle: manual date. */
+
+	p->flags |= TERMP_NOSPACE;
+	p->offset = p->rmargin;
+	p->rmargin = p->maxrmargin - term_strlen(p, title);
+	if (p->offset + datelen >= p->rmargin)
+		p->rmargin = p->offset + datelen;
+
+	term_word(p, meta->date);
+	term_flushln(p);
+
+	/* Bottom right corner: manual title and section. */
+
+	p->flags &= ~TERMP_NOBREAK;
+	p->flags |= TERMP_NOSPACE;
+	p->offset = p->rmargin;
+	p->rmargin = p->maxrmargin;
+
+	term_word(p, title);
+	term_flushln(p);
+}
+
+
+static void
+print_man_head(struct termp *p, const void *arg)
+{
+	char		buf[BUFSIZ], title[BUFSIZ];
+	size_t		buflen, titlen;
+	const struct man_meta *m;
+
+	m = (const struct man_meta *)arg;
+	assert(m->title);
+	assert(m->msec);
+
+	if (m->vol)
+		strlcpy(buf, m->vol, BUFSIZ);
+	else
+		buf[0] = '\0';
+	buflen = term_strlen(p, buf);
+
+	/* Top left corner: manual title and section. */
+
+	snprintf(title, BUFSIZ, "%s(%s)", m->title, m->msec);
+	titlen = term_strlen(p, title);
+
+	p->flags |= TERMP_NOBREAK | TERMP_NOSPACE;
+	p->offset = 0;
+	p->rmargin = 2 * (titlen+1) + buflen < p->maxrmargin ?
+	    (p->maxrmargin - 
+	     term_strlen(p, buf) + term_len(p, 1)) / 2 :
+	    p->maxrmargin - buflen;
+
+	term_word(p, title);
+	term_flushln(p);
+
+	/* At the top in the middle: manual volume. */
+
+	p->flags |= TERMP_NOSPACE;
+	p->offset = p->rmargin;
+	p->rmargin = p->offset + buflen + titlen < p->maxrmargin ?
+	    p->maxrmargin - titlen : p->maxrmargin;
+
+	term_word(p, buf);
+	term_flushln(p);
+
+	/* Top right corner: title and section, again. */
+
+	p->flags &= ~TERMP_NOBREAK;
+	if (p->rmargin + titlen <= p->maxrmargin) {
+		p->flags |= TERMP_NOSPACE;
+		p->offset = p->rmargin;
+		p->rmargin = p->maxrmargin;
+		term_word(p, title);
+		term_flushln(p);
+	}
+
+	p->flags &= ~TERMP_NOSPACE;
+	p->offset = 0;
+	p->rmargin = p->maxrmargin;
+
+	/* 
+	 * Groff prints three blank lines before the content.
+	 * Do the same, except in the temporary, undocumented
+	 * mode imitating mdoc(7) output.
+	 */
+
+	term_vspace(p);
+	if ( ! p->mdocstyle) {
+		term_vspace(p);
+		term_vspace(p);
+	}
+}
diff --git a/usr/src/cmd/mandoc/man_validate.c b/usr/src/cmd/mandoc/man_validate.c
new file mode 100644
index 0000000..e40b089
--- /dev/null
+++ b/usr/src/cmd/mandoc/man_validate.c
@@ -0,0 +1,550 @@
+/*	$Id: man_validate.c,v 1.80 2012/01/03 15:16:24 kristaps Exp $ */
+/*
+ * Copyright (c) 2008, 2009, 2010, 2011 Kristaps Dzonsons <kristaps@bsd.lv>
+ * Copyright (c) 2010 Ingo Schwarze <schwarze@openbsd.org>
+ *
+ * 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.
+ */
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <sys/types.h>
+
+#include <assert.h>
+#include <ctype.h>
+#include <errno.h>
+#include <limits.h>
+#include <stdarg.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+
+#include "man.h"
+#include "mandoc.h"
+#include "libman.h"
+#include "libmandoc.h"
+
+#define	CHKARGS	  struct man *m, struct man_node *n
+
+typedef	int	(*v_check)(CHKARGS);
+
+struct	man_valid {
+	v_check	 *pres;
+	v_check	 *posts;
+};
+
+static	int	  check_eq0(CHKARGS);
+static	int	  check_eq2(CHKARGS);
+static	int	  check_le1(CHKARGS);
+static	int	  check_ge2(CHKARGS);
+static	int	  check_le5(CHKARGS);
+static	int	  check_par(CHKARGS);
+static	int	  check_part(CHKARGS);
+static	int	  check_root(CHKARGS);
+static	void	  check_text(CHKARGS);
+
+static	int	  post_AT(CHKARGS);
+static	int	  post_vs(CHKARGS);
+static	int	  post_fi(CHKARGS);
+static	int	  post_ft(CHKARGS);
+static	int	  post_nf(CHKARGS);
+static	int	  post_sec(CHKARGS);
+static	int	  post_TH(CHKARGS);
+static	int	  post_UC(CHKARGS);
+static	int	  pre_sec(CHKARGS);
+
+static	v_check	  posts_at[] = { post_AT, NULL };
+static	v_check	  posts_br[] = { post_vs, check_eq0, NULL };
+static	v_check	  posts_eq0[] = { check_eq0, NULL };
+static	v_check	  posts_eq2[] = { check_eq2, NULL };
+static	v_check	  posts_fi[] = { check_eq0, post_fi, NULL };
+static	v_check	  posts_ft[] = { post_ft, NULL };
+static	v_check	  posts_nf[] = { check_eq0, post_nf, NULL };
+static	v_check	  posts_par[] = { check_par, NULL };
+static	v_check	  posts_part[] = { check_part, NULL };
+static	v_check	  posts_sec[] = { post_sec, NULL };
+static	v_check	  posts_sp[] = { post_vs, check_le1, NULL };
+static	v_check	  posts_th[] = { check_ge2, check_le5, post_TH, NULL };
+static	v_check	  posts_uc[] = { post_UC, NULL };
+static	v_check	  pres_sec[] = { pre_sec, NULL };
+
+static	const struct man_valid man_valids[MAN_MAX] = {
+	{ NULL, posts_br }, /* br */
+	{ NULL, posts_th }, /* TH */
+	{ pres_sec, posts_sec }, /* SH */
+	{ pres_sec, posts_sec }, /* SS */
+	{ NULL, NULL }, /* TP */
+	{ NULL, posts_par }, /* LP */
+	{ NULL, posts_par }, /* PP */
+	{ NULL, posts_par }, /* P */
+	{ NULL, NULL }, /* IP */
+	{ NULL, NULL }, /* HP */
+	{ NULL, NULL }, /* SM */
+	{ NULL, NULL }, /* SB */
+	{ NULL, NULL }, /* BI */
+	{ NULL, NULL }, /* IB */
+	{ NULL, NULL }, /* BR */
+	{ NULL, NULL }, /* RB */
+	{ NULL, NULL }, /* R */
+	{ NULL, NULL }, /* B */
+	{ NULL, NULL }, /* I */
+	{ NULL, NULL }, /* IR */
+	{ NULL, NULL }, /* RI */
+	{ NULL, posts_eq0 }, /* na */
+	{ NULL, posts_sp }, /* sp */
+	{ NULL, posts_nf }, /* nf */
+	{ NULL, posts_fi }, /* fi */
+	{ NULL, NULL }, /* RE */
+	{ NULL, posts_part }, /* RS */
+	{ NULL, NULL }, /* DT */
+	{ NULL, posts_uc }, /* UC */
+	{ NULL, NULL }, /* PD */
+	{ NULL, posts_at }, /* AT */
+	{ NULL, NULL }, /* in */
+	{ NULL, posts_ft }, /* ft */
+	{ NULL, posts_eq2 }, /* OP */
+};
+
+
+int
+man_valid_pre(struct man *m, struct man_node *n)
+{
+	v_check		*cp;
+
+	switch (n->type) {
+	case (MAN_TEXT):
+		/* FALLTHROUGH */
+	case (MAN_ROOT):
+		/* FALLTHROUGH */
+	case (MAN_EQN):
+		/* FALLTHROUGH */
+	case (MAN_TBL):
+		return(1);
+	default:
+		break;
+	}
+
+	if (NULL == (cp = man_valids[n->tok].pres))
+		return(1);
+	for ( ; *cp; cp++)
+		if ( ! (*cp)(m, n)) 
+			return(0);
+	return(1);
+}
+
+
+int
+man_valid_post(struct man *m)
+{
+	v_check		*cp;
+
+	if (MAN_VALID & m->last->flags)
+		return(1);
+	m->last->flags |= MAN_VALID;
+
+	switch (m->last->type) {
+	case (MAN_TEXT): 
+		check_text(m, m->last);
+		return(1);
+	case (MAN_ROOT):
+		return(check_root(m, m->last));
+	case (MAN_EQN):
+		/* FALLTHROUGH */
+	case (MAN_TBL):
+		return(1);
+	default:
+		break;
+	}
+
+	if (NULL == (cp = man_valids[m->last->tok].posts))
+		return(1);
+	for ( ; *cp; cp++)
+		if ( ! (*cp)(m, m->last))
+			return(0);
+
+	return(1);
+}
+
+
+static int
+check_root(CHKARGS) 
+{
+
+	if (MAN_BLINE & m->flags)
+		man_nmsg(m, n, MANDOCERR_SCOPEEXIT);
+	else if (MAN_ELINE & m->flags)
+		man_nmsg(m, n, MANDOCERR_SCOPEEXIT);
+
+	m->flags &= ~MAN_BLINE;
+	m->flags &= ~MAN_ELINE;
+
+	if (NULL == m->first->child) {
+		man_nmsg(m, n, MANDOCERR_NODOCBODY);
+		return(0);
+	} else if (NULL == m->meta.title) {
+		man_nmsg(m, n, MANDOCERR_NOTITLE);
+
+		/*
+		 * If a title hasn't been set, do so now (by
+		 * implication, date and section also aren't set).
+		 */
+
+	        m->meta.title = mandoc_strdup("unknown");
+		m->meta.msec = mandoc_strdup("1");
+		m->meta.date = mandoc_normdate
+			(m->parse, NULL, n->line, n->pos);
+	}
+
+	return(1);
+}
+
+static void
+check_text(CHKARGS)
+{
+	char		*cp, *p;
+
+	if (MAN_LITERAL & m->flags)
+		return;
+
+	cp = n->string;
+	for (p = cp; NULL != (p = strchr(p, '\t')); p++)
+		man_pmsg(m, n->line, (int)(p - cp), MANDOCERR_BADTAB);
+}
+
+#define	INEQ_DEFINE(x, ineq, name) \
+static int \
+check_##name(CHKARGS) \
+{ \
+	if (n->nchild ineq (x)) \
+		return(1); \
+	mandoc_vmsg(MANDOCERR_ARGCOUNT, m->parse, n->line, n->pos, \
+			"line arguments %s %d (have %d)", \
+			#ineq, (x), n->nchild); \
+	return(1); \
+}
+
+INEQ_DEFINE(0, ==, eq0)
+INEQ_DEFINE(2, ==, eq2)
+INEQ_DEFINE(1, <=, le1)
+INEQ_DEFINE(2, >=, ge2)
+INEQ_DEFINE(5, <=, le5)
+
+static int
+post_ft(CHKARGS)
+{
+	char	*cp;
+	int	 ok;
+
+	if (0 == n->nchild)
+		return(1);
+
+	ok = 0;
+	cp = n->child->string;
+	switch (*cp) {
+	case ('1'):
+		/* FALLTHROUGH */
+	case ('2'):
+		/* FALLTHROUGH */
+	case ('3'):
+		/* FALLTHROUGH */
+	case ('4'):
+		/* FALLTHROUGH */
+	case ('I'):
+		/* FALLTHROUGH */
+	case ('P'):
+		/* FALLTHROUGH */
+	case ('R'):
+		if ('\0' == cp[1])
+			ok = 1;
+		break;
+	case ('B'):
+		if ('\0' == cp[1] || ('I' == cp[1] && '\0' == cp[2]))
+			ok = 1;
+		break;
+	case ('C'):
+		if ('W' == cp[1] && '\0' == cp[2])
+			ok = 1;
+		break;
+	default:
+		break;
+	}
+
+	if (0 == ok) {
+		mandoc_vmsg
+			(MANDOCERR_BADFONT, m->parse,
+			 n->line, n->pos, "%s", cp);
+		*cp = '\0';
+	}
+
+	if (1 < n->nchild)
+		mandoc_vmsg
+			(MANDOCERR_ARGCOUNT, m->parse, n->line, 
+			 n->pos, "want one child (have %d)", 
+			 n->nchild);
+
+	return(1);
+}
+
+static int
+pre_sec(CHKARGS)
+{
+
+	if (MAN_BLOCK == n->type)
+		m->flags &= ~MAN_LITERAL;
+	return(1);
+}
+
+static int
+post_sec(CHKARGS)
+{
+
+	if ( ! (MAN_HEAD == n->type && 0 == n->nchild)) 
+		return(1);
+
+	man_nmsg(m, n, MANDOCERR_SYNTARGCOUNT);
+	return(0);
+}
+
+static int
+check_part(CHKARGS)
+{
+
+	if (MAN_BODY == n->type && 0 == n->nchild)
+		mandoc_msg(MANDOCERR_ARGCWARN, m->parse, n->line, 
+				n->pos, "want children (have none)");
+
+	return(1);
+}
+
+
+static int
+check_par(CHKARGS)
+{
+
+	switch (n->type) {
+	case (MAN_BLOCK):
+		if (0 == n->body->nchild)
+			man_node_delete(m, n);
+		break;
+	case (MAN_BODY):
+		if (0 == n->nchild)
+			man_nmsg(m, n, MANDOCERR_IGNPAR);
+		break;
+	case (MAN_HEAD):
+		if (n->nchild)
+			man_nmsg(m, n, MANDOCERR_ARGSLOST);
+		break;
+	default:
+		break;
+	}
+
+	return(1);
+}
+
+
+static int
+post_TH(CHKARGS)
+{
+	const char	*p;
+	int		 line, pos;
+
+	if (m->meta.title)
+		free(m->meta.title);
+	if (m->meta.vol)
+		free(m->meta.vol);
+	if (m->meta.source)
+		free(m->meta.source);
+	if (m->meta.msec)
+		free(m->meta.msec);
+	if (m->meta.date)
+		free(m->meta.date);
+
+	line = n->line;
+	pos = n->pos;
+	m->meta.title = m->meta.vol = m->meta.date =
+		m->meta.msec = m->meta.source = NULL;
+
+	/* ->TITLE<- MSEC DATE SOURCE VOL */
+
+	n = n->child;
+	if (n && n->string) {
+		for (p = n->string; '\0' != *p; p++) {
+			/* Only warn about this once... */
+			if (isalpha((unsigned char)*p) && 
+					! isupper((unsigned char)*p)) {
+				man_nmsg(m, n, MANDOCERR_UPPERCASE);
+				break;
+			}
+		}
+		m->meta.title = mandoc_strdup(n->string);
+	} else
+		m->meta.title = mandoc_strdup("");
+
+	/* TITLE ->MSEC<- DATE SOURCE VOL */
+
+	if (n)
+		n = n->next;
+	if (n && n->string)
+		m->meta.msec = mandoc_strdup(n->string);
+	else
+		m->meta.msec = mandoc_strdup("");
+
+	/* TITLE MSEC ->DATE<- SOURCE VOL */
+
+	if (n)
+		n = n->next;
+	if (n && n->string && '\0' != n->string[0]) {
+		pos = n->pos;
+		m->meta.date = mandoc_normdate
+		    (m->parse, n->string, line, pos);
+	} else
+		m->meta.date = mandoc_strdup("");
+
+	/* TITLE MSEC DATE ->SOURCE<- VOL */
+
+	if (n && (n = n->next))
+		m->meta.source = mandoc_strdup(n->string);
+
+	/* TITLE MSEC DATE SOURCE ->VOL<- */
+	/* If missing, use the default VOL name for MSEC. */
+
+	if (n && (n = n->next))
+		m->meta.vol = mandoc_strdup(n->string);
+	else if ('\0' != m->meta.msec[0] &&
+	    (NULL != (p = mandoc_a2msec(m->meta.msec))))
+		m->meta.vol = mandoc_strdup(p);
+
+	/*
+	 * Remove the `TH' node after we've processed it for our
+	 * meta-data.
+	 */
+	man_node_delete(m, m->last);
+	return(1);
+}
+
+static int
+post_nf(CHKARGS)
+{
+
+	if (MAN_LITERAL & m->flags)
+		man_nmsg(m, n, MANDOCERR_SCOPEREP);
+
+	m->flags |= MAN_LITERAL;
+	return(1);
+}
+
+static int
+post_fi(CHKARGS)
+{
+
+	if ( ! (MAN_LITERAL & m->flags))
+		man_nmsg(m, n, MANDOCERR_WNOSCOPE);
+
+	m->flags &= ~MAN_LITERAL;
+	return(1);
+}
+
+static int
+post_UC(CHKARGS)
+{
+	static const char * const bsd_versions[] = {
+	    "3rd Berkeley Distribution",
+	    "4th Berkeley Distribution",
+	    "4.2 Berkeley Distribution",
+	    "4.3 Berkeley Distribution",
+	    "4.4 Berkeley Distribution",
+	};
+
+	const char	*p, *s;
+
+	n = n->child;
+
+	if (NULL == n || MAN_TEXT != n->type)
+		p = bsd_versions[0];
+	else {
+		s = n->string;
+		if (0 == strcmp(s, "3"))
+			p = bsd_versions[0];
+		else if (0 == strcmp(s, "4"))
+			p = bsd_versions[1];
+		else if (0 == strcmp(s, "5"))
+			p = bsd_versions[2];
+		else if (0 == strcmp(s, "6"))
+			p = bsd_versions[3];
+		else if (0 == strcmp(s, "7"))
+			p = bsd_versions[4];
+		else
+			p = bsd_versions[0];
+	}
+
+	if (m->meta.source)
+		free(m->meta.source);
+
+	m->meta.source = mandoc_strdup(p);
+	return(1);
+}
+
+static int
+post_AT(CHKARGS)
+{
+	static const char * const unix_versions[] = {
+	    "7th Edition",
+	    "System III",
+	    "System V",
+	    "System V Release 2",
+	};
+
+	const char	*p, *s;
+	struct man_node	*nn;
+
+	n = n->child;
+
+	if (NULL == n || MAN_TEXT != n->type)
+		p = unix_versions[0];
+	else {
+		s = n->string;
+		if (0 == strcmp(s, "3"))
+			p = unix_versions[0];
+		else if (0 == strcmp(s, "4"))
+			p = unix_versions[1];
+		else if (0 == strcmp(s, "5")) {
+			nn = n->next;
+			if (nn && MAN_TEXT == nn->type && nn->string[0])
+				p = unix_versions[3];
+			else
+				p = unix_versions[2];
+		} else
+			p = unix_versions[0];
+	}
+
+	if (m->meta.source)
+		free(m->meta.source);
+
+	m->meta.source = mandoc_strdup(p);
+	return(1);
+}
+
+static int
+post_vs(CHKARGS)
+{
+
+	/* 
+	 * Don't warn about this because it occurs in pod2man and would
+	 * cause considerable (unfixable) warnage.
+	 */
+	if (NULL == n->prev && MAN_ROOT == n->parent->type)
+		man_node_delete(m, n);
+
+	return(1);
+}
diff --git a/usr/src/cmd/mandoc/mandoc.c b/usr/src/cmd/mandoc/mandoc.c
new file mode 100644
index 0000000..604bb67
--- /dev/null
+++ b/usr/src/cmd/mandoc/mandoc.c
@@ -0,0 +1,735 @@
+/*	$Id: mandoc.c,v 1.62 2011/12/03 16:08:51 schwarze Exp $ */
+/*
+ * Copyright (c) 2008, 2009, 2010, 2011 Kristaps Dzonsons <kristaps@bsd.lv>
+ * Copyright (c) 2011 Ingo Schwarze <schwarze@openbsd.org>
+ *
+ * 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 AUTHORS DISCLAIM ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHORS 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.
+ */
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <sys/types.h>
+
+#include <assert.h>
+#include <ctype.h>
+#include <errno.h>
+#include <limits.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <time.h>
+
+#include "mandoc.h"
+#include "libmandoc.h"
+
+#define DATESIZE 32
+
+static	int	 a2time(time_t *, const char *, const char *);
+static	char	*time2a(time_t);
+static	int	 numescape(const char *);
+
+/*
+ * Pass over recursive numerical expressions.  This context of this
+ * function is important: it's only called within character-terminating
+ * escapes (e.g., \s[xxxyyy]), so all we need to do is handle initial
+ * recursion: we don't care about what's in these blocks. 
+ * This returns the number of characters skipped or -1 if an error
+ * occurs (the caller should bail).
+ */
+static int
+numescape(const char *start)
+{
+	int		 i;
+	size_t		 sz;
+	const char	*cp;
+
+	i = 0;
+
+	/* The expression consists of a subexpression. */
+
+	if ('\\' == start[i]) {
+		cp = &start[++i];
+		/*
+		 * Read past the end of the subexpression.
+		 * Bail immediately on errors.
+		 */
+		if (ESCAPE_ERROR == mandoc_escape(&cp, NULL, NULL))
+			return(-1);
+		return(i + cp - &start[i]);
+	} 
+
+	if ('(' != start[i++])
+		return(0);
+
+	/*
+	 * A parenthesised subexpression.  Read until the closing
+	 * parenthesis, making sure to handle any nested subexpressions
+	 * that might ruin our parse.
+	 */
+
+	while (')' != start[i]) {
+		sz = strcspn(&start[i], ")\\");
+		i += (int)sz;
+
+		if ('\0' == start[i])
+			return(-1);
+		else if ('\\' != start[i])
+			continue;
+
+		cp = &start[++i];
+		if (ESCAPE_ERROR == mandoc_escape(&cp, NULL, NULL))
+			return(-1);
+		i += cp - &start[i];
+	}
+
+	/* Read past the terminating ')'. */
+	return(++i);
+}
+
+enum mandoc_esc
+mandoc_escape(const char **end, const char **start, int *sz)
+{
+	char		 c, term, numeric;
+	int		 i, lim, ssz, rlim;
+	const char	*cp, *rstart;
+	enum mandoc_esc	 gly; 
+
+	cp = *end;
+	rstart = cp;
+	if (start)
+		*start = rstart;
+	i = lim = 0;
+	gly = ESCAPE_ERROR;
+	term = numeric = '\0';
+
+	switch ((c = cp[i++])) {
+	/*
+	 * First the glyphs.  There are several different forms of
+	 * these, but each eventually returns a substring of the glyph
+	 * name.
+	 */
+	case ('('):
+		gly = ESCAPE_SPECIAL;
+		lim = 2;
+		break;
+	case ('['):
+		gly = ESCAPE_SPECIAL;
+		/*
+		 * Unicode escapes are defined in groff as \[uXXXX] to
+		 * \[u10FFFF], where the contained value must be a valid
+		 * Unicode codepoint.  Here, however, only check whether
+		 * it's not a zero-width escape.
+		 */
+		if ('u' == cp[i] && ']' != cp[i + 1])
+			gly = ESCAPE_UNICODE;
+		term = ']';
+		break;
+	case ('C'):
+		if ('\'' != cp[i])
+			return(ESCAPE_ERROR);
+		gly = ESCAPE_SPECIAL;
+		term = '\'';
+		break;
+
+	/*
+	 * Handle all triggers matching \X(xy, \Xx, and \X[xxxx], where
+	 * 'X' is the trigger.  These have opaque sub-strings.
+	 */
+	case ('F'):
+		/* FALLTHROUGH */
+	case ('g'):
+		/* FALLTHROUGH */
+	case ('k'):
+		/* FALLTHROUGH */
+	case ('M'):
+		/* FALLTHROUGH */
+	case ('m'):
+		/* FALLTHROUGH */
+	case ('n'):
+		/* FALLTHROUGH */
+	case ('V'):
+		/* FALLTHROUGH */
+	case ('Y'):
+		gly = ESCAPE_IGNORE;
+		/* FALLTHROUGH */
+	case ('f'):
+		if (ESCAPE_ERROR == gly)
+			gly = ESCAPE_FONT;
+
+		rstart= &cp[i];
+		if (start) 
+			*start = rstart;
+
+		switch (cp[i++]) {
+		case ('('):
+			lim = 2;
+			break;
+		case ('['):
+			term = ']';
+			break;
+		default:
+			lim = 1;
+			i--;
+			break;
+		}
+		break;
+
+	/*
+	 * These escapes are of the form \X'Y', where 'X' is the trigger
+	 * and 'Y' is any string.  These have opaque sub-strings.
+	 */
+	case ('A'):
+		/* FALLTHROUGH */
+	case ('b'):
+		/* FALLTHROUGH */
+	case ('D'):
+		/* FALLTHROUGH */
+	case ('o'):
+		/* FALLTHROUGH */
+	case ('R'):
+		/* FALLTHROUGH */
+	case ('X'):
+		/* FALLTHROUGH */
+	case ('Z'):
+		if ('\'' != cp[i++])
+			return(ESCAPE_ERROR);
+		gly = ESCAPE_IGNORE;
+		term = '\'';
+		break;
+
+	/*
+	 * These escapes are of the form \X'N', where 'X' is the trigger
+	 * and 'N' resolves to a numerical expression.
+	 */
+	case ('B'):
+		/* FALLTHROUGH */
+	case ('h'):
+		/* FALLTHROUGH */
+	case ('H'):
+		/* FALLTHROUGH */
+	case ('L'):
+		/* FALLTHROUGH */
+	case ('l'):
+		gly = ESCAPE_NUMBERED;
+		/* FALLTHROUGH */
+	case ('S'):
+		/* FALLTHROUGH */
+	case ('v'):
+		/* FALLTHROUGH */
+	case ('w'):
+		/* FALLTHROUGH */
+	case ('x'):
+		if (ESCAPE_ERROR == gly)
+			gly = ESCAPE_IGNORE;
+		if ('\'' != cp[i++])
+			return(ESCAPE_ERROR);
+		term = numeric = '\'';
+		break;
+
+	/*
+	 * Special handling for the numbered character escape.
+	 * XXX Do any other escapes need similar handling?
+	 */
+	case ('N'):
+		if ('\0' == cp[i])
+			return(ESCAPE_ERROR);
+		*end = &cp[++i];
+		if (isdigit((unsigned char)cp[i-1]))
+			return(ESCAPE_IGNORE);
+		while (isdigit((unsigned char)**end))
+			(*end)++;
+		if (start)
+			*start = &cp[i];
+		if (sz)
+			*sz = *end - &cp[i];
+		if ('\0' != **end)
+			(*end)++;
+		return(ESCAPE_NUMBERED);
+
+	/* 
+	 * Sizes get a special category of their own.
+	 */
+	case ('s'):
+		gly = ESCAPE_IGNORE;
+
+		rstart = &cp[i];
+		if (start) 
+			*start = rstart;
+
+		/* See +/- counts as a sign. */
+		c = cp[i];
+		if ('+' == c || '-' == c || ASCII_HYPH == c)
+			++i;
+
+		switch (cp[i++]) {
+		case ('('):
+			lim = 2;
+			break;
+		case ('['):
+			term = numeric = ']';
+			break;
+		case ('\''):
+			term = numeric = '\'';
+			break;
+		default:
+			lim = 1;
+			i--;
+			break;
+		}
+
+		/* See +/- counts as a sign. */
+		c = cp[i];
+		if ('+' == c || '-' == c || ASCII_HYPH == c)
+			++i;
+
+		break;
+
+	/*
+	 * Anything else is assumed to be a glyph.
+	 */
+	default:
+		gly = ESCAPE_SPECIAL;
+		lim = 1;
+		i--;
+		break;
+	}
+
+	assert(ESCAPE_ERROR != gly);
+
+	rstart = &cp[i];
+	if (start)
+		*start = rstart;
+
+	/*
+	 * If a terminating block has been specified, we need to
+	 * handle the case of recursion, which could have their
+	 * own terminating blocks that mess up our parse.  This, by the
+	 * way, means that the "start" and "size" values will be
+	 * effectively meaningless.
+	 */
+
+	ssz = 0;
+	if (numeric && -1 == (ssz = numescape(&cp[i])))
+		return(ESCAPE_ERROR);
+
+	i += ssz;
+	rlim = -1;
+
+	/*
+	 * We have a character terminator.  Try to read up to that
+	 * character.  If we can't (i.e., we hit the nil), then return
+	 * an error; if we can, calculate our length, read past the
+	 * terminating character, and exit.
+	 */
+
+	if ('\0' != term) {
+		*end = strchr(&cp[i], term);
+		if ('\0' == *end)
+			return(ESCAPE_ERROR);
+
+		rlim = *end - &cp[i];
+		if (sz)
+			*sz = rlim;
+		(*end)++;
+		goto out;
+	}
+
+	assert(lim > 0);
+
+	/*
+	 * We have a numeric limit.  If the string is shorter than that,
+	 * stop and return an error.  Else adjust our endpoint, length,
+	 * and return the current glyph.
+	 */
+
+	if ((size_t)lim > strlen(&cp[i]))
+		return(ESCAPE_ERROR);
+
+	rlim = lim;
+	if (sz)
+		*sz = rlim;
+
+	*end = &cp[i] + lim;
+
+out:
+	assert(rlim >= 0 && rstart);
+
+	/* Run post-processors. */
+
+	switch (gly) {
+	case (ESCAPE_FONT):
+		/*
+		 * Pretend that the constant-width font modes are the
+		 * same as the regular font modes.
+		 */
+		if (2 == rlim && 'C' == *rstart)
+			rstart++;
+		else if (1 != rlim)
+			break;
+
+		switch (*rstart) {
+		case ('3'):
+			/* FALLTHROUGH */
+		case ('B'):
+			gly = ESCAPE_FONTBOLD;
+			break;
+		case ('2'):
+			/* FALLTHROUGH */
+		case ('I'):
+			gly = ESCAPE_FONTITALIC;
+			break;
+		case ('P'):
+			gly = ESCAPE_FONTPREV;
+			break;
+		case ('1'):
+			/* FALLTHROUGH */
+		case ('R'):
+			gly = ESCAPE_FONTROMAN;
+			break;
+		}
+		break;
+	case (ESCAPE_SPECIAL):
+		if (1 != rlim)
+			break;
+		if ('c' == *rstart)
+			gly = ESCAPE_NOSPACE;
+		break;
+	default:
+		break;
+	}
+
+	return(gly);
+}
+
+void *
+mandoc_calloc(size_t num, size_t size)
+{
+	void		*ptr;
+
+	ptr = calloc(num, size);
+	if (NULL == ptr) {
+		perror(NULL);
+		exit((int)MANDOCLEVEL_SYSERR);
+	}
+
+	return(ptr);
+}
+
+
+void *
+mandoc_malloc(size_t size)
+{
+	void		*ptr;
+
+	ptr = malloc(size);
+	if (NULL == ptr) {
+		perror(NULL);
+		exit((int)MANDOCLEVEL_SYSERR);
+	}
+
+	return(ptr);
+}
+
+
+void *
+mandoc_realloc(void *ptr, size_t size)
+{
+
+	ptr = realloc(ptr, size);
+	if (NULL == ptr) {
+		perror(NULL);
+		exit((int)MANDOCLEVEL_SYSERR);
+	}
+
+	return(ptr);
+}
+
+char *
+mandoc_strndup(const char *ptr, size_t sz)
+{
+	char		*p;
+
+	p = mandoc_malloc(sz + 1);
+	memcpy(p, ptr, sz);
+	p[(int)sz] = '\0';
+	return(p);
+}
+
+char *
+mandoc_strdup(const char *ptr)
+{
+	char		*p;
+
+	p = strdup(ptr);
+	if (NULL == p) {
+		perror(NULL);
+		exit((int)MANDOCLEVEL_SYSERR);
+	}
+
+	return(p);
+}
+
+/*
+ * Parse a quoted or unquoted roff-style request or macro argument.
+ * Return a pointer to the parsed argument, which is either the original
+ * pointer or advanced by one byte in case the argument is quoted.
+ * Null-terminate the argument in place.
+ * Collapse pairs of quotes inside quoted arguments.
+ * Advance the argument pointer to the next argument,
+ * or to the null byte terminating the argument line.
+ */
+char *
+mandoc_getarg(struct mparse *parse, char **cpp, int ln, int *pos)
+{
+	char	 *start, *cp;
+	int	  quoted, pairs, white;
+
+	/* Quoting can only start with a new word. */
+	start = *cpp;
+	quoted = 0;
+	if ('"' == *start) {
+		quoted = 1;
+		start++;
+	} 
+
+	pairs = 0;
+	white = 0;
+	for (cp = start; '\0' != *cp; cp++) {
+		/* Move left after quoted quotes and escaped backslashes. */
+		if (pairs)
+			cp[-pairs] = cp[0];
+		if ('\\' == cp[0]) {
+			if ('\\' == cp[1]) {
+				/* Poor man's copy mode. */
+				pairs++;
+				cp++;
+			} else if (0 == quoted && ' ' == cp[1])
+				/* Skip escaped blanks. */
+				cp++;
+		} else if (0 == quoted) {
+			if (' ' == cp[0]) {
+				/* Unescaped blanks end unquoted args. */
+				white = 1;
+				break;
+			}
+		} else if ('"' == cp[0]) {
+			if ('"' == cp[1]) {
+				/* Quoted quotes collapse. */
+				pairs++;
+				cp++;
+			} else {
+				/* Unquoted quotes end quoted args. */
+				quoted = 2;
+				break;
+			}
+		}
+	}
+
+	/* Quoted argument without a closing quote. */
+	if (1 == quoted)
+		mandoc_msg(MANDOCERR_BADQUOTE, parse, ln, *pos, NULL);
+
+	/* Null-terminate this argument and move to the next one. */
+	if (pairs)
+		cp[-pairs] = '\0';
+	if ('\0' != *cp) {
+		*cp++ = '\0';
+		while (' ' == *cp)
+			cp++;
+	}
+	*pos += (int)(cp - start) + (quoted ? 1 : 0);
+	*cpp = cp;
+
+	if ('\0' == *cp && (white || ' ' == cp[-1]))
+		mandoc_msg(MANDOCERR_EOLNSPACE, parse, ln, *pos, NULL);
+
+	return(start);
+}
+
+static int
+a2time(time_t *t, const char *fmt, const char *p)
+{
+	struct tm	 tm;
+	char		*pp;
+
+	memset(&tm, 0, sizeof(struct tm));
+
+	pp = NULL;
+#ifdef	HAVE_STRPTIME
+	pp = strptime(p, fmt, &tm);
+#endif
+	if (NULL != pp && '\0' == *pp) {
+		*t = mktime(&tm);
+		return(1);
+	}
+
+	return(0);
+}
+
+static char *
+time2a(time_t t)
+{
+	struct tm	*tm;
+	char		*buf, *p;
+	size_t		 ssz;
+	int		 isz;
+
+	tm = localtime(&t);
+
+	/*
+	 * Reserve space:
+	 * up to 9 characters for the month (September) + blank
+	 * up to 2 characters for the day + comma + blank
+	 * 4 characters for the year and a terminating '\0'
+	 */
+	p = buf = mandoc_malloc(10 + 4 + 4 + 1);
+
+	if (0 == (ssz = strftime(p, 10 + 1, "%B ", tm)))
+		goto fail;
+	p += (int)ssz;
+
+	if (-1 == (isz = snprintf(p, 4 + 1, "%d, ", tm->tm_mday)))
+		goto fail;
+	p += isz;
+
+	if (0 == strftime(p, 4 + 1, "%Y", tm))
+		goto fail;
+	return(buf);
+
+fail:
+	free(buf);
+	return(NULL);
+}
+
+char *
+mandoc_normdate(struct mparse *parse, char *in, int ln, int pos)
+{
+	char		*out;
+	time_t		 t;
+
+	if (NULL == in || '\0' == *in ||
+	    0 == strcmp(in, "$" "Mdocdate$")) {
+		mandoc_msg(MANDOCERR_NODATE, parse, ln, pos, NULL);
+		time(&t);
+	}
+	else if (a2time(&t, "%Y-%m-%d", in))
+		t = 0;
+	else if (!a2time(&t, "$" "Mdocdate: %b %d %Y $", in) &&
+	    !a2time(&t, "%b %d, %Y", in)) {
+		mandoc_msg(MANDOCERR_BADDATE, parse, ln, pos, NULL);
+		t = 0;
+	}
+	out = t ? time2a(t) : NULL;
+	return(out ? out : mandoc_strdup(in));
+}
+
+int
+mandoc_eos(const char *p, size_t sz, int enclosed)
+{
+	const char *q;
+	int found;
+
+	if (0 == sz)
+		return(0);
+
+	/*
+	 * End-of-sentence recognition must include situations where
+	 * some symbols, such as `)', allow prior EOS punctuation to
+	 * propagate outward.
+	 */
+
+	found = 0;
+	for (q = p + (int)sz - 1; q >= p; q--) {
+		switch (*q) {
+		case ('\"'):
+			/* FALLTHROUGH */
+		case ('\''):
+			/* FALLTHROUGH */
+		case (']'):
+			/* FALLTHROUGH */
+		case (')'):
+			if (0 == found)
+				enclosed = 1;
+			break;
+		case ('.'):
+			/* FALLTHROUGH */
+		case ('!'):
+			/* FALLTHROUGH */
+		case ('?'):
+			found = 1;
+			break;
+		default:
+			return(found && (!enclosed || isalnum((unsigned char)*q)));
+		}
+	}
+
+	return(found && !enclosed);
+}
+
+/*
+ * Find out whether a line is a macro line or not.  If it is, adjust the
+ * current position and return one; if it isn't, return zero and don't
+ * change the current position.
+ */
+int
+mandoc_getcontrol(const char *cp, int *ppos)
+{
+	int		pos;
+
+	pos = *ppos;
+
+	if ('\\' == cp[pos] && '.' == cp[pos + 1])
+		pos += 2;
+	else if ('.' == cp[pos] || '\'' == cp[pos])
+		pos++;
+	else
+		return(0);
+
+	while (' ' == cp[pos] || '\t' == cp[pos])
+		pos++;
+
+	*ppos = pos;
+	return(1);
+}
+
+/*
+ * Convert a string to a long that may not be <0.
+ * If the string is invalid, or is less than 0, return -1.
+ */
+int
+mandoc_strntoi(const char *p, size_t sz, int base)
+{
+	char		 buf[32];
+	char		*ep;
+	long		 v;
+
+	if (sz > 31)
+		return(-1);
+
+	memcpy(buf, p, sz);
+	buf[(int)sz] = '\0';
+
+	errno = 0;
+	v = strtol(buf, &ep, base);
+
+	if (buf[0] == '\0' || *ep != '\0')
+		return(-1);
+
+	if (v > INT_MAX)
+		v = INT_MAX;
+	if (v < INT_MIN)
+		v = INT_MIN;
+
+	return((int)v);
+}
diff --git a/usr/src/cmd/mandoc/mandoc.h b/usr/src/cmd/mandoc/mandoc.h
new file mode 100644
index 0000000..a37effc
--- /dev/null
+++ b/usr/src/cmd/mandoc/mandoc.h
@@ -0,0 +1,432 @@
+/*	$Id: mandoc.h,v 1.99 2012/02/16 20:51:31 joerg Exp $ */
+/*
+ * Copyright (c) 2010, 2011 Kristaps Dzonsons <kristaps@bsd.lv>
+ *
+ * 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.
+ */
+#ifndef MANDOC_H
+#define MANDOC_H
+
+#define ASCII_NBRSP	 31  /* non-breaking space */
+#define	ASCII_HYPH	 30  /* breakable hyphen */
+
+/*
+ * Status level.  This refers to both internal status (i.e., whilst
+ * running, when warnings/errors are reported) and an indicator of a
+ * threshold of when to halt (when said internal state exceeds the
+ * threshold).
+ */
+enum	mandoclevel {
+	MANDOCLEVEL_OK = 0,
+	MANDOCLEVEL_RESERVED,
+	MANDOCLEVEL_WARNING, /* warnings: syntax, whitespace, etc. */
+	MANDOCLEVEL_ERROR, /* input has been thrown away */
+	MANDOCLEVEL_FATAL, /* input is borked */
+	MANDOCLEVEL_BADARG, /* bad argument in invocation */
+	MANDOCLEVEL_SYSERR, /* system error */
+	MANDOCLEVEL_MAX
+};
+
+/*
+ * All possible things that can go wrong within a parse, be it libroff,
+ * libmdoc, or libman.
+ */
+enum	mandocerr {
+	MANDOCERR_OK,
+
+	MANDOCERR_WARNING, /* ===== start of warnings ===== */
+
+	/* related to the prologue */
+	MANDOCERR_NOTITLE, /* no title in document */
+	MANDOCERR_UPPERCASE, /* document title should be all caps */
+	MANDOCERR_BADMSEC, /* unknown manual section */
+	MANDOCERR_NODATE, /* date missing, using today's date */
+	MANDOCERR_BADDATE, /* cannot parse date, using it verbatim */
+	MANDOCERR_PROLOGOOO, /* prologue macros out of order */
+	MANDOCERR_PROLOGREP, /* duplicate prologue macro */
+	MANDOCERR_BADPROLOG, /* macro not allowed in prologue */
+	MANDOCERR_BADBODY, /* macro not allowed in body */
+
+	/* related to document structure */
+	MANDOCERR_SO, /* .so is fragile, better use ln(1) */
+	MANDOCERR_NAMESECFIRST, /* NAME section must come first */
+	MANDOCERR_BADNAMESEC, /* bad NAME section contents */
+	MANDOCERR_NONAME, /* manual name not yet set */
+	MANDOCERR_SECOOO, /* sections out of conventional order */
+	MANDOCERR_SECREP, /* duplicate section name */
+	MANDOCERR_SECMSEC, /* section not in conventional manual section */
+
+	/* related to macros and nesting */
+	MANDOCERR_MACROOBS, /* skipping obsolete macro */
+	MANDOCERR_IGNPAR, /* skipping paragraph macro */
+	MANDOCERR_IGNNS, /* skipping no-space macro */
+	MANDOCERR_SCOPENEST, /* blocks badly nested */
+	MANDOCERR_CHILD, /* child violates parent syntax */
+	MANDOCERR_NESTEDDISP, /* nested displays are not portable */
+	MANDOCERR_SCOPEREP, /* already in literal mode */
+	MANDOCERR_LINESCOPE, /* line scope broken */
+
+	/* related to missing macro arguments */
+	MANDOCERR_MACROEMPTY, /* skipping empty macro */
+	MANDOCERR_ARGCWARN, /* argument count wrong */
+	MANDOCERR_DISPTYPE, /* missing display type */
+	MANDOCERR_LISTFIRST, /* list type must come first */
+	MANDOCERR_NOWIDTHARG, /* tag lists require a width argument */
+	MANDOCERR_FONTTYPE, /* missing font type */
+	MANDOCERR_WNOSCOPE, /* skipping end of block that is not open */
+
+	/* related to bad macro arguments */
+	MANDOCERR_IGNARGV, /* skipping argument */
+	MANDOCERR_ARGVREP, /* duplicate argument */
+	MANDOCERR_DISPREP, /* duplicate display type */
+	MANDOCERR_LISTREP, /* duplicate list type */
+	MANDOCERR_BADATT, /* unknown AT&T UNIX version */
+	MANDOCERR_BADBOOL, /* bad Boolean value */
+	MANDOCERR_BADFONT, /* unknown font */
+	MANDOCERR_BADSTANDARD, /* unknown standard specifier */
+	MANDOCERR_BADWIDTH, /* bad width argument */
+
+	/* related to plain text */
+	MANDOCERR_NOBLANKLN, /* blank line in non-literal context */
+	MANDOCERR_BADTAB, /* tab in non-literal context */
+	MANDOCERR_EOLNSPACE, /* end of line whitespace */
+	MANDOCERR_BADCOMMENT, /* bad comment style */
+	MANDOCERR_BADESCAPE, /* unknown escape sequence */
+	MANDOCERR_BADQUOTE, /* unterminated quoted string */
+
+	/* related to equations */
+	MANDOCERR_EQNQUOTE, /* unexpected literal in equation */
+
+	MANDOCERR_ERROR, /* ===== start of errors ===== */
+
+	/* related to equations */
+	MANDOCERR_EQNNSCOPE, /* unexpected equation scope closure*/
+	MANDOCERR_EQNSCOPE, /* equation scope open on exit */
+	MANDOCERR_EQNBADSCOPE, /* overlapping equation scopes */
+	MANDOCERR_EQNEOF, /* unexpected end of equation */
+	MANDOCERR_EQNSYNT, /* equation syntax error */
+
+	/* related to tables */
+	MANDOCERR_TBL, /* bad table syntax */
+	MANDOCERR_TBLOPT, /* bad table option */
+	MANDOCERR_TBLLAYOUT, /* bad table layout */
+	MANDOCERR_TBLNOLAYOUT, /* no table layout cells specified */
+	MANDOCERR_TBLNODATA, /* no table data cells specified */
+	MANDOCERR_TBLIGNDATA, /* ignore data in cell */
+	MANDOCERR_TBLBLOCK, /* data block still open */
+	MANDOCERR_TBLEXTRADAT, /* ignoring extra data cells */
+
+	MANDOCERR_ROFFLOOP, /* input stack limit exceeded, infinite loop? */
+	MANDOCERR_BADCHAR, /* skipping bad character */
+	MANDOCERR_NAMESC, /* escaped character not allowed in a name */
+	MANDOCERR_NOTEXT, /* skipping text before the first section header */
+	MANDOCERR_MACRO, /* skipping unknown macro */
+	MANDOCERR_REQUEST, /* NOT IMPLEMENTED: skipping request */
+	MANDOCERR_ARGCOUNT, /* argument count wrong */
+	MANDOCERR_NOSCOPE, /* skipping end of block that is not open */
+	MANDOCERR_SCOPEBROKEN, /* missing end of block */
+	MANDOCERR_SCOPEEXIT, /* scope open on exit */
+	MANDOCERR_UNAME, /* uname(3) system call failed */
+	/* FIXME: merge following with MANDOCERR_ARGCOUNT */
+	MANDOCERR_NOARGS, /* macro requires line argument(s) */
+	MANDOCERR_NOBODY, /* macro requires body argument(s) */
+	MANDOCERR_NOARGV, /* macro requires argument(s) */
+	MANDOCERR_LISTTYPE, /* missing list type */
+	MANDOCERR_ARGSLOST, /* line argument(s) will be lost */
+	MANDOCERR_BODYLOST, /* body argument(s) will be lost */
+
+	MANDOCERR_FATAL, /* ===== start of fatal errors ===== */
+
+	MANDOCERR_NOTMANUAL, /* manual isn't really a manual */
+	MANDOCERR_COLUMNS, /* column syntax is inconsistent */
+	MANDOCERR_BADDISP, /* NOT IMPLEMENTED: .Bd -file */
+	MANDOCERR_SYNTARGVCOUNT, /* argument count wrong, violates syntax */
+	MANDOCERR_SYNTCHILD, /* child violates parent syntax */
+	MANDOCERR_SYNTARGCOUNT, /* argument count wrong, violates syntax */
+	MANDOCERR_SOPATH, /* NOT IMPLEMENTED: .so with absolute path or ".." */
+	MANDOCERR_NODOCBODY, /* no document body */
+	MANDOCERR_NODOCPROLOG, /* no document prologue */
+	MANDOCERR_MEM, /* static buffer exhausted */
+	MANDOCERR_MAX
+};
+
+struct	tbl {
+	char		  tab; /* cell-separator */
+	char		  decimal; /* decimal point */
+	int		  linesize;
+	int		  opts;
+#define	TBL_OPT_CENTRE	 (1 << 0)
+#define	TBL_OPT_EXPAND	 (1 << 1)
+#define	TBL_OPT_BOX	 (1 << 2)
+#define	TBL_OPT_DBOX	 (1 << 3)
+#define	TBL_OPT_ALLBOX	 (1 << 4)
+#define	TBL_OPT_NOKEEP	 (1 << 5)
+#define	TBL_OPT_NOSPACE	 (1 << 6)
+	int		  cols; /* number of columns */
+};
+
+enum	tbl_headt {
+	TBL_HEAD_DATA, /* plug in data from tbl_dat */
+	TBL_HEAD_VERT, /* vertical spacer */
+	TBL_HEAD_DVERT  /* double-vertical spacer */
+};
+
+/*
+ * The head of a table specifies all of its columns.  When formatting a
+ * tbl_span, iterate over these and plug in data from the tbl_span when
+ * appropriate, using tbl_cell as a guide to placement.
+ */
+struct	tbl_head {
+	enum tbl_headt	  pos;
+	int		  ident; /* 0 <= unique id < cols */
+	struct tbl_head	 *next;
+	struct tbl_head	 *prev;
+};
+
+enum	tbl_cellt {
+	TBL_CELL_CENTRE, /* c, C */
+	TBL_CELL_RIGHT, /* r, R */
+	TBL_CELL_LEFT, /* l, L */
+	TBL_CELL_NUMBER, /* n, N */
+	TBL_CELL_SPAN, /* s, S */
+	TBL_CELL_LONG, /* a, A */
+	TBL_CELL_DOWN, /* ^ */
+	TBL_CELL_HORIZ, /* _, - */
+	TBL_CELL_DHORIZ, /* = */
+	TBL_CELL_VERT, /* | */
+	TBL_CELL_DVERT, /* || */
+	TBL_CELL_MAX
+};
+
+/*
+ * A cell in a layout row.
+ */
+struct	tbl_cell {
+	struct tbl_cell	 *next;
+	enum tbl_cellt	  pos;
+	size_t		  spacing;
+	int		  flags;
+#define	TBL_CELL_TALIGN	 (1 << 0) /* t, T */
+#define	TBL_CELL_BALIGN	 (1 << 1) /* d, D */
+#define	TBL_CELL_BOLD	 (1 << 2) /* fB, B, b */
+#define	TBL_CELL_ITALIC	 (1 << 3) /* fI, I, i */
+#define	TBL_CELL_EQUAL	 (1 << 4) /* e, E */
+#define	TBL_CELL_UP	 (1 << 5) /* u, U */
+#define	TBL_CELL_WIGN	 (1 << 6) /* z, Z */
+	struct tbl_head	 *head;
+};
+
+/*
+ * A layout row.
+ */
+struct	tbl_row {
+	struct tbl_row	 *next;
+	struct tbl_cell	 *first;
+	struct tbl_cell	 *last;
+};
+
+enum	tbl_datt {
+	TBL_DATA_NONE, /* has no data */
+	TBL_DATA_DATA, /* consists of data/string */
+	TBL_DATA_HORIZ, /* horizontal line */
+	TBL_DATA_DHORIZ, /* double-horizontal line */
+	TBL_DATA_NHORIZ, /* squeezed horizontal line */
+	TBL_DATA_NDHORIZ /* squeezed double-horizontal line */
+};
+
+/*
+ * A cell within a row of data.  The "string" field contains the actual
+ * string value that's in the cell.  The rest is layout.
+ */
+struct	tbl_dat {
+	struct tbl_cell	 *layout; /* layout cell */
+	int		  spans; /* how many spans follow */
+	struct tbl_dat	 *next;
+	char		 *string; /* data (NULL if not TBL_DATA_DATA) */
+	enum tbl_datt	  pos;
+};
+
+enum	tbl_spant {
+	TBL_SPAN_DATA, /* span consists of data */
+	TBL_SPAN_HORIZ, /* span is horizontal line */
+	TBL_SPAN_DHORIZ /* span is double horizontal line */
+};
+
+/*
+ * A row of data in a table.
+ */
+struct	tbl_span {
+	struct tbl	 *tbl;
+	struct tbl_head	 *head;
+	struct tbl_row	 *layout; /* layout row */
+	struct tbl_dat	 *first;
+	struct tbl_dat	 *last;
+	int		  line; /* parse line */
+	int		  flags;
+#define	TBL_SPAN_FIRST	 (1 << 0)
+#define	TBL_SPAN_LAST	 (1 << 1)
+	enum tbl_spant	  pos;
+	struct tbl_span	 *next;
+};
+
+enum	eqn_boxt {
+	EQN_ROOT, /* root of parse tree */
+	EQN_TEXT, /* text (number, variable, whatever) */
+	EQN_SUBEXPR, /* nested `eqn' subexpression */
+	EQN_LIST, /* subexpressions list */
+	EQN_MATRIX /* matrix subexpression */
+};
+
+enum	eqn_markt {
+	EQNMARK_NONE = 0,
+	EQNMARK_DOT,
+	EQNMARK_DOTDOT,
+	EQNMARK_HAT,
+	EQNMARK_TILDE,
+	EQNMARK_VEC,
+	EQNMARK_DYAD,
+	EQNMARK_BAR,
+	EQNMARK_UNDER,
+	EQNMARK__MAX
+};
+
+enum	eqn_fontt {
+	EQNFONT_NONE = 0,
+	EQNFONT_ROMAN,
+	EQNFONT_BOLD,
+	EQNFONT_FAT,
+	EQNFONT_ITALIC,
+	EQNFONT__MAX
+};
+
+enum	eqn_post {
+	EQNPOS_NONE = 0,
+	EQNPOS_OVER,
+	EQNPOS_SUP,
+	EQNPOS_SUB,
+	EQNPOS_TO,
+	EQNPOS_FROM,
+	EQNPOS__MAX
+};
+
+enum	eqn_pilet {
+	EQNPILE_NONE = 0,
+	EQNPILE_PILE,
+	EQNPILE_CPILE,
+	EQNPILE_RPILE,
+	EQNPILE_LPILE,
+	EQNPILE_COL,
+	EQNPILE_CCOL,
+	EQNPILE_RCOL,
+	EQNPILE_LCOL,
+	EQNPILE__MAX
+};
+
+ /*
+ * A "box" is a parsed mathematical expression as defined by the eqn.7
+ * grammar.
+ */
+struct	eqn_box {
+	int		  size; /* font size of expression */
+#define	EQN_DEFSIZE	  INT_MIN
+	enum eqn_boxt	  type; /* type of node */
+	struct eqn_box	 *first; /* first child node */
+	struct eqn_box	 *last; /* last child node */
+	struct eqn_box	 *next; /* node sibling */
+	struct eqn_box	 *parent; /* node sibling */
+	char		 *text; /* text (or NULL) */
+	char		 *left;
+	char		 *right;
+	enum eqn_post	  pos; /* position of next box */
+	enum eqn_markt	  mark; /* a mark about the box */
+	enum eqn_fontt	  font; /* font of box */
+	enum eqn_pilet	  pile; /* equation piling */
+};
+
+/*
+ * An equation consists of a tree of expressions starting at a given
+ * line and position. 
+ */
+struct	eqn {
+	char		 *name; /* identifier (or NULL) */
+	struct eqn_box	 *root; /* root mathematical expression */
+	int		  ln; /* invocation line */
+	int		  pos; /* invocation position */
+};
+
+/*
+ * The type of parse sequence.  This value is usually passed via the
+ * mandoc(1) command line of -man and -mdoc.  It's almost exclusively
+ * -mandoc but the others have been retained for compatibility.
+ */
+enum	mparset {
+	MPARSE_AUTO, /* magically determine the document type */
+	MPARSE_MDOC, /* assume -mdoc */
+	MPARSE_MAN /* assume -man */
+};
+
+enum	mandoc_esc {
+	ESCAPE_ERROR = 0, /* bail! unparsable escape */
+	ESCAPE_IGNORE, /* escape to be ignored */
+	ESCAPE_SPECIAL, /* a regular special character */
+	ESCAPE_FONT, /* a generic font mode */
+	ESCAPE_FONTBOLD, /* bold font mode */
+	ESCAPE_FONTITALIC, /* italic font mode */
+	ESCAPE_FONTROMAN, /* roman font mode */
+	ESCAPE_FONTPREV, /* previous font mode */
+	ESCAPE_NUMBERED, /* a numbered glyph */
+	ESCAPE_UNICODE, /* a unicode codepoint */
+	ESCAPE_NOSPACE /* suppress space if the last on a line */
+};
+
+typedef	void	(*mandocmsg)(enum mandocerr, enum mandoclevel,
+			const char *, int, int, const char *);
+
+struct	mparse;
+struct	mchars;
+struct	mdoc;
+struct	man;
+
+__BEGIN_DECLS
+
+void		 *mandoc_calloc(size_t, size_t);
+enum mandoc_esc	  mandoc_escape(const char **, const char **, int *);
+void		 *mandoc_malloc(size_t);
+void		 *mandoc_realloc(void *, size_t);
+char		 *mandoc_strdup(const char *);
+char		 *mandoc_strndup(const char *, size_t);
+struct mchars	 *mchars_alloc(void);
+void		  mchars_free(struct mchars *);
+char	 	  mchars_num2char(const char *, size_t);
+int		  mchars_num2uc(const char *, size_t);
+int		  mchars_spec2cp(const struct mchars *, 
+			const char *, size_t);
+const char	 *mchars_spec2str(const struct mchars *, 
+			const char *, size_t, size_t *);
+struct mparse	 *mparse_alloc(enum mparset, 
+			enum mandoclevel, mandocmsg, void *);
+void		  mparse_free(struct mparse *);
+void		  mparse_keep(struct mparse *);
+enum mandoclevel  mparse_readfd(struct mparse *, int, const char *);
+enum mandoclevel  mparse_readmem(struct mparse *, const void *, size_t,
+			const char *);
+void		  mparse_reset(struct mparse *);
+void		  mparse_result(struct mparse *, 
+			struct mdoc **, struct man **);
+const char	 *mparse_getkeep(const struct mparse *);
+const char	 *mparse_strerror(enum mandocerr);
+const char	 *mparse_strlevel(enum mandoclevel);
+
+__END_DECLS
+
+#endif /*!MANDOC_H*/
diff --git a/usr/src/cmd/mandoc/mdoc.c b/usr/src/cmd/mandoc/mdoc.c
new file mode 100644
index 0000000..81a4ffc
--- /dev/null
+++ b/usr/src/cmd/mandoc/mdoc.c
@@ -0,0 +1,987 @@
+/*	$Id: mdoc.c,v 1.196 2011/09/30 00:13:28 schwarze Exp $ */
+/*
+ * Copyright (c) 2008, 2009, 2010, 2011 Kristaps Dzonsons <kristaps@bsd.lv>
+ * Copyright (c) 2010 Ingo Schwarze <schwarze@openbsd.org>
+ *
+ * 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.
+ */
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <sys/types.h>
+
+#include <assert.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+
+#include "mdoc.h"
+#include "mandoc.h"
+#include "libmdoc.h"
+#include "libmandoc.h"
+
+const	char *const __mdoc_macronames[MDOC_MAX] = {		 
+	"Ap",		"Dd",		"Dt",		"Os",
+	"Sh",		"Ss",		"Pp",		"D1",
+	"Dl",		"Bd",		"Ed",		"Bl",
+	"El",		"It",		"Ad",		"An",
+	"Ar",		"Cd",		"Cm",		"Dv",
+	"Er",		"Ev",		"Ex",		"Fa",
+	"Fd",		"Fl",		"Fn",		"Ft",
+	"Ic",		"In",		"Li",		"Nd",
+	"Nm",		"Op",		"Ot",		"Pa",
+	"Rv",		"St",		"Va",		"Vt",
+	/* LINTED */
+	"Xr",		"%A",		"%B",		"%D",
+	/* LINTED */
+	"%I",		"%J",		"%N",		"%O",
+	/* LINTED */
+	"%P",		"%R",		"%T",		"%V",
+	"Ac",		"Ao",		"Aq",		"At",
+	"Bc",		"Bf",		"Bo",		"Bq",
+	"Bsx",		"Bx",		"Db",		"Dc",
+	"Do",		"Dq",		"Ec",		"Ef",
+	"Em",		"Eo",		"Fx",		"Ms",
+	"No",		"Ns",		"Nx",		"Ox",
+	"Pc",		"Pf",		"Po",		"Pq",
+	"Qc",		"Ql",		"Qo",		"Qq",
+	"Re",		"Rs",		"Sc",		"So",
+	"Sq",		"Sm",		"Sx",		"Sy",
+	"Tn",		"Ux",		"Xc",		"Xo",
+	"Fo",		"Fc",		"Oo",		"Oc",
+	"Bk",		"Ek",		"Bt",		"Hf",
+	"Fr",		"Ud",		"Lb",		"Lp",
+	"Lk",		"Mt",		"Brq",		"Bro",
+	/* LINTED */
+	"Brc",		"%C",		"Es",		"En",
+	/* LINTED */
+	"Dx",		"%Q",		"br",		"sp",
+	/* LINTED */
+	"%U",		"Ta"
+	};
+
+const	char *const __mdoc_argnames[MDOC_ARG_MAX] = {		 
+	"split",		"nosplit",		"ragged",
+	"unfilled",		"literal",		"file",		 
+	"offset",		"bullet",		"dash",		 
+	"hyphen",		"item",			"enum",		 
+	"tag",			"diag",			"hang",		 
+	"ohang",		"inset",		"column",	 
+	"width",		"compact",		"std",	 
+	"filled",		"words",		"emphasis",
+	"symbolic",		"nested",		"centered"
+	};
+
+const	char * const *mdoc_macronames = __mdoc_macronames;
+const	char * const *mdoc_argnames = __mdoc_argnames;
+
+static	void		  mdoc_node_free(struct mdoc_node *);
+static	void		  mdoc_node_unlink(struct mdoc *, 
+				struct mdoc_node *);
+static	void		  mdoc_free1(struct mdoc *);
+static	void		  mdoc_alloc1(struct mdoc *);
+static	struct mdoc_node *node_alloc(struct mdoc *, int, int, 
+				enum mdoct, enum mdoc_type);
+static	int		  node_append(struct mdoc *, 
+				struct mdoc_node *);
+#if 0
+static	int		  mdoc_preptext(struct mdoc *, int, char *, int);
+#endif
+static	int		  mdoc_ptext(struct mdoc *, int, char *, int);
+static	int		  mdoc_pmacro(struct mdoc *, int, char *, int);
+
+const struct mdoc_node *
+mdoc_node(const struct mdoc *m)
+{
+
+	assert( ! (MDOC_HALT & m->flags));
+	return(m->first);
+}
+
+
+const struct mdoc_meta *
+mdoc_meta(const struct mdoc *m)
+{
+
+	assert( ! (MDOC_HALT & m->flags));
+	return(&m->meta);
+}
+
+
+/*
+ * Frees volatile resources (parse tree, meta-data, fields).
+ */
+static void
+mdoc_free1(struct mdoc *mdoc)
+{
+
+	if (mdoc->first)
+		mdoc_node_delete(mdoc, mdoc->first);
+	if (mdoc->meta.title)
+		free(mdoc->meta.title);
+	if (mdoc->meta.os)
+		free(mdoc->meta.os);
+	if (mdoc->meta.name)
+		free(mdoc->meta.name);
+	if (mdoc->meta.arch)
+		free(mdoc->meta.arch);
+	if (mdoc->meta.vol)
+		free(mdoc->meta.vol);
+	if (mdoc->meta.msec)
+		free(mdoc->meta.msec);
+	if (mdoc->meta.date)
+		free(mdoc->meta.date);
+}
+
+
+/*
+ * Allocate all volatile resources (parse tree, meta-data, fields).
+ */
+static void
+mdoc_alloc1(struct mdoc *mdoc)
+{
+
+	memset(&mdoc->meta, 0, sizeof(struct mdoc_meta));
+	mdoc->flags = 0;
+	mdoc->lastnamed = mdoc->lastsec = SEC_NONE;
+	mdoc->last = mandoc_calloc(1, sizeof(struct mdoc_node));
+	mdoc->first = mdoc->last;
+	mdoc->last->type = MDOC_ROOT;
+	mdoc->last->tok = MDOC_MAX;
+	mdoc->next = MDOC_NEXT_CHILD;
+}
+
+
+/*
+ * Free up volatile resources (see mdoc_free1()) then re-initialises the
+ * data with mdoc_alloc1().  After invocation, parse data has been reset
+ * and the parser is ready for re-invocation on a new tree; however,
+ * cross-parse non-volatile data is kept intact.
+ */
+void
+mdoc_reset(struct mdoc *mdoc)
+{
+
+	mdoc_free1(mdoc);
+	mdoc_alloc1(mdoc);
+}
+
+
+/*
+ * Completely free up all volatile and non-volatile parse resources.
+ * After invocation, the pointer is no longer usable.
+ */
+void
+mdoc_free(struct mdoc *mdoc)
+{
+
+	mdoc_free1(mdoc);
+	free(mdoc);
+}
+
+
+/*
+ * Allocate volatile and non-volatile parse resources.  
+ */
+struct mdoc *
+mdoc_alloc(struct roff *roff, struct mparse *parse)
+{
+	struct mdoc	*p;
+
+	p = mandoc_calloc(1, sizeof(struct mdoc));
+
+	p->parse = parse;
+	p->roff = roff;
+
+	mdoc_hash_init();
+	mdoc_alloc1(p);
+	return(p);
+}
+
+
+/*
+ * Climb back up the parse tree, validating open scopes.  Mostly calls
+ * through to macro_end() in macro.c.
+ */
+int
+mdoc_endparse(struct mdoc *m)
+{
+
+	assert( ! (MDOC_HALT & m->flags));
+	if (mdoc_macroend(m))
+		return(1);
+	m->flags |= MDOC_HALT;
+	return(0);
+}
+
+int
+mdoc_addeqn(struct mdoc *m, const struct eqn *ep)
+{
+	struct mdoc_node *n;
+
+	assert( ! (MDOC_HALT & m->flags));
+
+	/* No text before an initial macro. */
+
+	if (SEC_NONE == m->lastnamed) {
+		mdoc_pmsg(m, ep->ln, ep->pos, MANDOCERR_NOTEXT);
+		return(1);
+	}
+
+	n = node_alloc(m, ep->ln, ep->pos, MDOC_MAX, MDOC_EQN);
+	n->eqn = ep;
+
+	if ( ! node_append(m, n))
+		return(0);
+
+	m->next = MDOC_NEXT_SIBLING;
+	return(1);
+}
+
+int
+mdoc_addspan(struct mdoc *m, const struct tbl_span *sp)
+{
+	struct mdoc_node *n;
+
+	assert( ! (MDOC_HALT & m->flags));
+
+	/* No text before an initial macro. */
+
+	if (SEC_NONE == m->lastnamed) {
+		mdoc_pmsg(m, sp->line, 0, MANDOCERR_NOTEXT);
+		return(1);
+	}
+
+	n = node_alloc(m, sp->line, 0, MDOC_MAX, MDOC_TBL);
+	n->span = sp;
+
+	if ( ! node_append(m, n))
+		return(0);
+
+	m->next = MDOC_NEXT_SIBLING;
+	return(1);
+}
+
+
+/*
+ * Main parse routine.  Parses a single line -- really just hands off to
+ * the macro (mdoc_pmacro()) or text parser (mdoc_ptext()).
+ */
+int
+mdoc_parseln(struct mdoc *m, int ln, char *buf, int offs)
+{
+
+	assert( ! (MDOC_HALT & m->flags));
+
+	m->flags |= MDOC_NEWLINE;
+
+	/*
+	 * Let the roff nS register switch SYNOPSIS mode early,
+	 * such that the parser knows at all times
+	 * whether this mode is on or off.
+	 * Note that this mode is also switched by the Sh macro.
+	 */
+	if (roff_regisset(m->roff, REG_nS)) {
+		if (roff_regget(m->roff, REG_nS))
+			m->flags |= MDOC_SYNOPSIS;
+		else
+			m->flags &= ~MDOC_SYNOPSIS;
+	}
+
+	return(mandoc_getcontrol(buf, &offs) ?
+			mdoc_pmacro(m, ln, buf, offs) :
+			mdoc_ptext(m, ln, buf, offs));
+}
+
+int
+mdoc_macro(MACRO_PROT_ARGS)
+{
+	assert(tok < MDOC_MAX);
+
+	/* If we're in the body, deny prologue calls. */
+
+	if (MDOC_PROLOGUE & mdoc_macros[tok].flags && 
+			MDOC_PBODY & m->flags) {
+		mdoc_pmsg(m, line, ppos, MANDOCERR_BADBODY);
+		return(1);
+	}
+
+	/* If we're in the prologue, deny "body" macros.  */
+
+	if ( ! (MDOC_PROLOGUE & mdoc_macros[tok].flags) && 
+			! (MDOC_PBODY & m->flags)) {
+		mdoc_pmsg(m, line, ppos, MANDOCERR_BADPROLOG);
+		if (NULL == m->meta.msec)
+			m->meta.msec = mandoc_strdup("1");
+		if (NULL == m->meta.title)
+			m->meta.title = mandoc_strdup("UNKNOWN");
+		if (NULL == m->meta.vol)
+			m->meta.vol = mandoc_strdup("LOCAL");
+		if (NULL == m->meta.os)
+			m->meta.os = mandoc_strdup("LOCAL");
+		if (NULL == m->meta.date)
+			m->meta.date = mandoc_normdate
+				(m->parse, NULL, line, ppos);
+		m->flags |= MDOC_PBODY;
+	}
+
+	return((*mdoc_macros[tok].fp)(m, tok, line, ppos, pos, buf));
+}
+
+
+static int
+node_append(struct mdoc *mdoc, struct mdoc_node *p)
+{
+
+	assert(mdoc->last);
+	assert(mdoc->first);
+	assert(MDOC_ROOT != p->type);
+
+	switch (mdoc->next) {
+	case (MDOC_NEXT_SIBLING):
+		mdoc->last->next = p;
+		p->prev = mdoc->last;
+		p->parent = mdoc->last->parent;
+		break;
+	case (MDOC_NEXT_CHILD):
+		mdoc->last->child = p;
+		p->parent = mdoc->last;
+		break;
+	default:
+		abort();
+		/* NOTREACHED */
+	}
+
+	p->parent->nchild++;
+
+	/*
+	 * Copy over the normalised-data pointer of our parent.  Not
+	 * everybody has one, but copying a null pointer is fine.
+	 */
+
+	switch (p->type) {
+	case (MDOC_BODY):
+		/* FALLTHROUGH */
+	case (MDOC_TAIL):
+		/* FALLTHROUGH */
+	case (MDOC_HEAD):
+		p->norm = p->parent->norm;
+		break;
+	default:
+		break;
+	}
+
+	if ( ! mdoc_valid_pre(mdoc, p))
+		return(0);
+
+	switch (p->type) {
+	case (MDOC_HEAD):
+		assert(MDOC_BLOCK == p->parent->type);
+		p->parent->head = p;
+		break;
+	case (MDOC_TAIL):
+		assert(MDOC_BLOCK == p->parent->type);
+		p->parent->tail = p;
+		break;
+	case (MDOC_BODY):
+		if (p->end)
+			break;
+		assert(MDOC_BLOCK == p->parent->type);
+		p->parent->body = p;
+		break;
+	default:
+		break;
+	}
+
+	mdoc->last = p;
+
+	switch (p->type) {
+	case (MDOC_TBL):
+		/* FALLTHROUGH */
+	case (MDOC_TEXT):
+		if ( ! mdoc_valid_post(mdoc))
+			return(0);
+		break;
+	default:
+		break;
+	}
+
+	return(1);
+}
+
+
+static struct mdoc_node *
+node_alloc(struct mdoc *m, int line, int pos, 
+		enum mdoct tok, enum mdoc_type type)
+{
+	struct mdoc_node *p;
+
+	p = mandoc_calloc(1, sizeof(struct mdoc_node));
+	p->sec = m->lastsec;
+	p->line = line;
+	p->pos = pos;
+	p->tok = tok;
+	p->type = type;
+
+	/* Flag analysis. */
+
+	if (MDOC_SYNOPSIS & m->flags)
+		p->flags |= MDOC_SYNPRETTY;
+	else
+		p->flags &= ~MDOC_SYNPRETTY;
+	if (MDOC_NEWLINE & m->flags)
+		p->flags |= MDOC_LINE;
+	m->flags &= ~MDOC_NEWLINE;
+
+	return(p);
+}
+
+
+int
+mdoc_tail_alloc(struct mdoc *m, int line, int pos, enum mdoct tok)
+{
+	struct mdoc_node *p;
+
+	p = node_alloc(m, line, pos, tok, MDOC_TAIL);
+	if ( ! node_append(m, p))
+		return(0);
+	m->next = MDOC_NEXT_CHILD;
+	return(1);
+}
+
+
+int
+mdoc_head_alloc(struct mdoc *m, int line, int pos, enum mdoct tok)
+{
+	struct mdoc_node *p;
+
+	assert(m->first);
+	assert(m->last);
+
+	p = node_alloc(m, line, pos, tok, MDOC_HEAD);
+	if ( ! node_append(m, p))
+		return(0);
+	m->next = MDOC_NEXT_CHILD;
+	return(1);
+}
+
+
+int
+mdoc_body_alloc(struct mdoc *m, int line, int pos, enum mdoct tok)
+{
+	struct mdoc_node *p;
+
+	p = node_alloc(m, line, pos, tok, MDOC_BODY);
+	if ( ! node_append(m, p))
+		return(0);
+	m->next = MDOC_NEXT_CHILD;
+	return(1);
+}
+
+
+int
+mdoc_endbody_alloc(struct mdoc *m, int line, int pos, enum mdoct tok,
+		struct mdoc_node *body, enum mdoc_endbody end)
+{
+	struct mdoc_node *p;
+
+	p = node_alloc(m, line, pos, tok, MDOC_BODY);
+	p->pending = body;
+	p->end = end;
+	if ( ! node_append(m, p))
+		return(0);
+	m->next = MDOC_NEXT_SIBLING;
+	return(1);
+}
+
+
+int
+mdoc_block_alloc(struct mdoc *m, int line, int pos, 
+		enum mdoct tok, struct mdoc_arg *args)
+{
+	struct mdoc_node *p;
+
+	p = node_alloc(m, line, pos, tok, MDOC_BLOCK);
+	p->args = args;
+	if (p->args)
+		(args->refcnt)++;
+
+	switch (tok) {
+	case (MDOC_Bd):
+		/* FALLTHROUGH */
+	case (MDOC_Bf):
+		/* FALLTHROUGH */
+	case (MDOC_Bl):
+		/* FALLTHROUGH */
+	case (MDOC_Rs):
+		p->norm = mandoc_calloc(1, sizeof(union mdoc_data));
+		break;
+	default:
+		break;
+	}
+
+	if ( ! node_append(m, p))
+		return(0);
+	m->next = MDOC_NEXT_CHILD;
+	return(1);
+}
+
+
+int
+mdoc_elem_alloc(struct mdoc *m, int line, int pos, 
+		enum mdoct tok, struct mdoc_arg *args)
+{
+	struct mdoc_node *p;
+
+	p = node_alloc(m, line, pos, tok, MDOC_ELEM);
+	p->args = args;
+	if (p->args)
+		(args->refcnt)++;
+
+	switch (tok) {
+	case (MDOC_An):
+		p->norm = mandoc_calloc(1, sizeof(union mdoc_data));
+		break;
+	default:
+		break;
+	}
+
+	if ( ! node_append(m, p))
+		return(0);
+	m->next = MDOC_NEXT_CHILD;
+	return(1);
+}
+
+int
+mdoc_word_alloc(struct mdoc *m, int line, int pos, const char *p)
+{
+	struct mdoc_node *n;
+
+	n = node_alloc(m, line, pos, MDOC_MAX, MDOC_TEXT);
+	n->string = roff_strdup(m->roff, p);
+
+	if ( ! node_append(m, n))
+		return(0);
+
+	m->next = MDOC_NEXT_SIBLING;
+	return(1);
+}
+
+
+static void
+mdoc_node_free(struct mdoc_node *p)
+{
+
+	if (MDOC_BLOCK == p->type || MDOC_ELEM == p->type)
+		free(p->norm);
+	if (p->string)
+		free(p->string);
+	if (p->args)
+		mdoc_argv_free(p->args);
+	free(p);
+}
+
+
+static void
+mdoc_node_unlink(struct mdoc *m, struct mdoc_node *n)
+{
+
+	/* Adjust siblings. */
+
+	if (n->prev)
+		n->prev->next = n->next;
+	if (n->next)
+		n->next->prev = n->prev;
+
+	/* Adjust parent. */
+
+	if (n->parent) {
+		n->parent->nchild--;
+		if (n->parent->child == n)
+			n->parent->child = n->prev ? n->prev : n->next;
+		if (n->parent->last == n)
+			n->parent->last = n->prev ? n->prev : NULL;
+	}
+
+	/* Adjust parse point, if applicable. */
+
+	if (m && m->last == n) {
+		if (n->prev) {
+			m->last = n->prev;
+			m->next = MDOC_NEXT_SIBLING;
+		} else {
+			m->last = n->parent;
+			m->next = MDOC_NEXT_CHILD;
+		}
+	}
+
+	if (m && m->first == n)
+		m->first = NULL;
+}
+
+
+void
+mdoc_node_delete(struct mdoc *m, struct mdoc_node *p)
+{
+
+	while (p->child) {
+		assert(p->nchild);
+		mdoc_node_delete(m, p->child);
+	}
+	assert(0 == p->nchild);
+
+	mdoc_node_unlink(m, p);
+	mdoc_node_free(p);
+}
+
+#if 0
+/*
+ * Pre-treat a text line.
+ * Text lines can consist of equations, which must be handled apart from
+ * the regular text.
+ * Thus, use this function to step through a line checking if it has any
+ * equations embedded in it.
+ * This must handle multiple equations AND equations that do not end at
+ * the end-of-line, i.e., will re-enter in the next roff parse.
+ */
+static int
+mdoc_preptext(struct mdoc *m, int line, char *buf, int offs)
+{
+	char		*start, *end;
+	char		 delim;
+
+	while ('\0' != buf[offs]) {
+		/* Mark starting position if eqn is set. */
+		start = NULL;
+		if ('\0' != (delim = roff_eqndelim(m->roff)))
+			if (NULL != (start = strchr(buf + offs, delim)))
+				*start++ = '\0';
+
+		/* Parse text as normal. */
+		if ( ! mdoc_ptext(m, line, buf, offs))
+			return(0);
+
+		/* Continue only if an equation exists. */
+		if (NULL == start)
+			break;
+
+		/* Read past the end of the equation. */
+		offs += start - (buf + offs);
+		assert(start == &buf[offs]);
+		if (NULL != (end = strchr(buf + offs, delim))) {
+			*end++ = '\0';
+			while (' ' == *end)
+				end++;
+		}
+
+		/* Parse the equation itself. */
+		roff_openeqn(m->roff, NULL, line, offs, buf);
+
+		/* Process a finished equation? */
+		if (roff_closeeqn(m->roff))
+			if ( ! mdoc_addeqn(m, roff_eqn(m->roff)))
+				return(0);
+		offs += (end - (buf + offs));
+	} 
+
+	return(1);
+}
+#endif
+
+/*
+ * Parse free-form text, that is, a line that does not begin with the
+ * control character.
+ */
+static int
+mdoc_ptext(struct mdoc *m, int line, char *buf, int offs)
+{
+	char		 *c, *ws, *end;
+	struct mdoc_node *n;
+
+	/* No text before an initial macro. */
+
+	if (SEC_NONE == m->lastnamed) {
+		mdoc_pmsg(m, line, offs, MANDOCERR_NOTEXT);
+		return(1);
+	}
+
+	assert(m->last);
+	n = m->last;
+
+	/*
+	 * Divert directly to list processing if we're encountering a
+	 * columnar MDOC_BLOCK with or without a prior MDOC_BLOCK entry
+	 * (a MDOC_BODY means it's already open, in which case we should
+	 * process within its context in the normal way).
+	 */
+
+	if (MDOC_Bl == n->tok && MDOC_BODY == n->type &&
+			LIST_column == n->norm->Bl.type) {
+		/* `Bl' is open without any children. */
+		m->flags |= MDOC_FREECOL;
+		return(mdoc_macro(m, MDOC_It, line, offs, &offs, buf));
+	}
+
+	if (MDOC_It == n->tok && MDOC_BLOCK == n->type &&
+			NULL != n->parent &&
+			MDOC_Bl == n->parent->tok &&
+			LIST_column == n->parent->norm->Bl.type) {
+		/* `Bl' has block-level `It' children. */
+		m->flags |= MDOC_FREECOL;
+		return(mdoc_macro(m, MDOC_It, line, offs, &offs, buf));
+	}
+
+	/*
+	 * Search for the beginning of unescaped trailing whitespace (ws)
+	 * and for the first character not to be output (end).
+	 */
+
+	/* FIXME: replace with strcspn(). */
+	ws = NULL;
+	for (c = end = buf + offs; *c; c++) {
+		switch (*c) {
+		case ' ':
+			if (NULL == ws)
+				ws = c;
+			continue;
+		case '\t':
+			/*
+			 * Always warn about trailing tabs,
+			 * even outside literal context,
+			 * where they should be put on the next line.
+			 */
+			if (NULL == ws)
+				ws = c;
+			/*
+			 * Strip trailing tabs in literal context only;
+			 * outside, they affect the next line.
+			 */
+			if (MDOC_LITERAL & m->flags)
+				continue;
+			break;
+		case '\\':
+			/* Skip the escaped character, too, if any. */
+			if (c[1])
+				c++;
+			/* FALLTHROUGH */
+		default:
+			ws = NULL;
+			break;
+		}
+		end = c + 1;
+	}
+	*end = '\0';
+
+	if (ws)
+		mdoc_pmsg(m, line, (int)(ws-buf), MANDOCERR_EOLNSPACE);
+
+	if ('\0' == buf[offs] && ! (MDOC_LITERAL & m->flags)) {
+		mdoc_pmsg(m, line, (int)(c-buf), MANDOCERR_NOBLANKLN);
+
+		/*
+		 * Insert a `sp' in the case of a blank line.  Technically,
+		 * blank lines aren't allowed, but enough manuals assume this
+		 * behaviour that we want to work around it.
+		 */
+		if ( ! mdoc_elem_alloc(m, line, offs, MDOC_sp, NULL))
+			return(0);
+
+		m->next = MDOC_NEXT_SIBLING;
+		return(1);
+	}
+
+	if ( ! mdoc_word_alloc(m, line, offs, buf+offs))
+		return(0);
+
+	if (MDOC_LITERAL & m->flags)
+		return(1);
+
+	/*
+	 * End-of-sentence check.  If the last character is an unescaped
+	 * EOS character, then flag the node as being the end of a
+	 * sentence.  The front-end will know how to interpret this.
+	 */
+
+	assert(buf < end);
+
+	if (mandoc_eos(buf+offs, (size_t)(end-buf-offs), 0))
+		m->last->flags |= MDOC_EOS;
+
+	return(1);
+}
+
+
+/*
+ * Parse a macro line, that is, a line beginning with the control
+ * character.
+ */
+static int
+mdoc_pmacro(struct mdoc *m, int ln, char *buf, int offs)
+{
+	enum mdoct	  tok;
+	int		  i, sv;
+	char		  mac[5];
+	struct mdoc_node *n;
+
+	/* Empty post-control lines are ignored. */
+
+	if ('"' == buf[offs]) {
+		mdoc_pmsg(m, ln, offs, MANDOCERR_BADCOMMENT);
+		return(1);
+	} else if ('\0' == buf[offs])
+		return(1);
+
+	sv = offs;
+
+	/* 
+	 * Copy the first word into a nil-terminated buffer.
+	 * Stop copying when a tab, space, or eoln is encountered.
+	 */
+
+	i = 0;
+	while (i < 4 && '\0' != buf[offs] && 
+			' ' != buf[offs] && '\t' != buf[offs])
+		mac[i++] = buf[offs++];
+
+	mac[i] = '\0';
+
+	tok = (i > 1 || i < 4) ? mdoc_hash_find(mac) : MDOC_MAX;
+
+	if (MDOC_MAX == tok) {
+		mandoc_vmsg(MANDOCERR_MACRO, m->parse, 
+				ln, sv, "%s", buf + sv - 1);
+		return(1);
+	}
+
+	/* Disregard the first trailing tab, if applicable. */
+
+	if ('\t' == buf[offs])
+		offs++;
+
+	/* Jump to the next non-whitespace word. */
+
+	while (buf[offs] && ' ' == buf[offs])
+		offs++;
+
+	/* 
+	 * Trailing whitespace.  Note that tabs are allowed to be passed
+	 * into the parser as "text", so we only warn about spaces here.
+	 */
+
+	if ('\0' == buf[offs] && ' ' == buf[offs - 1])
+		mdoc_pmsg(m, ln, offs - 1, MANDOCERR_EOLNSPACE);
+
+	/*
+	 * If an initial macro or a list invocation, divert directly
+	 * into macro processing.
+	 */
+
+	if (NULL == m->last || MDOC_It == tok || MDOC_El == tok) {
+		if ( ! mdoc_macro(m, tok, ln, sv, &offs, buf)) 
+			goto err;
+		return(1);
+	}
+
+	n = m->last;
+	assert(m->last);
+
+	/*
+	 * If the first macro of a `Bl -column', open an `It' block
+	 * context around the parsed macro.
+	 */
+
+	if (MDOC_Bl == n->tok && MDOC_BODY == n->type &&
+			LIST_column == n->norm->Bl.type) {
+		m->flags |= MDOC_FREECOL;
+		if ( ! mdoc_macro(m, MDOC_It, ln, sv, &sv, buf))
+			goto err;
+		return(1);
+	}
+
+	/*
+	 * If we're following a block-level `It' within a `Bl -column'
+	 * context (perhaps opened in the above block or in ptext()),
+	 * then open an `It' block context around the parsed macro.
+	 */
+
+	if (MDOC_It == n->tok && MDOC_BLOCK == n->type &&
+			NULL != n->parent &&
+			MDOC_Bl == n->parent->tok &&
+			LIST_column == n->parent->norm->Bl.type) {
+		m->flags |= MDOC_FREECOL;
+		if ( ! mdoc_macro(m, MDOC_It, ln, sv, &sv, buf)) 
+			goto err;
+		return(1);
+	}
+
+	/* Normal processing of a macro. */
+
+	if ( ! mdoc_macro(m, tok, ln, sv, &offs, buf)) 
+		goto err;
+
+	return(1);
+
+err:	/* Error out. */
+
+	m->flags |= MDOC_HALT;
+	return(0);
+}
+
+enum mdelim
+mdoc_isdelim(const char *p)
+{
+
+	if ('\0' == p[0])
+		return(DELIM_NONE);
+
+	if ('\0' == p[1])
+		switch (p[0]) {
+		case('('):
+			/* FALLTHROUGH */
+		case('['):
+			return(DELIM_OPEN);
+		case('|'):
+			return(DELIM_MIDDLE);
+		case('.'):
+			/* FALLTHROUGH */
+		case(','):
+			/* FALLTHROUGH */
+		case(';'):
+			/* FALLTHROUGH */
+		case(':'):
+			/* FALLTHROUGH */
+		case('?'):
+			/* FALLTHROUGH */
+		case('!'):
+			/* FALLTHROUGH */
+		case(')'):
+			/* FALLTHROUGH */
+		case(']'):
+			return(DELIM_CLOSE);
+		default:
+			return(DELIM_NONE);
+		}
+
+	if ('\\' != p[0])
+		return(DELIM_NONE);
+
+	if (0 == strcmp(p + 1, "."))
+		return(DELIM_CLOSE);
+	if (0 == strcmp(p + 1, "*(Ba"))
+		return(DELIM_MIDDLE);
+
+	return(DELIM_NONE);
+}
diff --git a/usr/src/cmd/mandoc/mdoc.h b/usr/src/cmd/mandoc/mdoc.h
new file mode 100644
index 0000000..9cee098
--- /dev/null
+++ b/usr/src/cmd/mandoc/mdoc.h
@@ -0,0 +1,392 @@
+/*	$Id: mdoc.h,v 1.122 2011/03/22 14:05:45 kristaps Exp $ */
+/*
+ * Copyright (c) 2008, 2009, 2010, 2011 Kristaps Dzonsons <kristaps@bsd.lv>
+ *
+ * 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.
+ */
+#ifndef MDOC_H
+#define MDOC_H
+
+enum	mdoct {
+	MDOC_Ap = 0,
+	MDOC_Dd,
+	MDOC_Dt,
+	MDOC_Os,
+	MDOC_Sh,
+	MDOC_Ss,
+	MDOC_Pp,
+	MDOC_D1,
+	MDOC_Dl,
+	MDOC_Bd,
+	MDOC_Ed,
+	MDOC_Bl,
+	MDOC_El,
+	MDOC_It,
+	MDOC_Ad,
+	MDOC_An,
+	MDOC_Ar,
+	MDOC_Cd,
+	MDOC_Cm,
+	MDOC_Dv,
+	MDOC_Er,
+	MDOC_Ev,
+	MDOC_Ex,
+	MDOC_Fa,
+	MDOC_Fd,
+	MDOC_Fl,
+	MDOC_Fn,
+	MDOC_Ft,
+	MDOC_Ic,
+	MDOC_In,
+	MDOC_Li,
+	MDOC_Nd,
+	MDOC_Nm,
+	MDOC_Op,
+	MDOC_Ot,
+	MDOC_Pa,
+	MDOC_Rv,
+	MDOC_St,
+	MDOC_Va,
+	MDOC_Vt,
+	MDOC_Xr,
+	MDOC__A,
+	MDOC__B,
+	MDOC__D,
+	MDOC__I,
+	MDOC__J,
+	MDOC__N,
+	MDOC__O,
+	MDOC__P,
+	MDOC__R,
+	MDOC__T,
+	MDOC__V,
+	MDOC_Ac,
+	MDOC_Ao,
+	MDOC_Aq,
+	MDOC_At,
+	MDOC_Bc,
+	MDOC_Bf,
+	MDOC_Bo,
+	MDOC_Bq,
+	MDOC_Bsx,
+	MDOC_Bx,
+	MDOC_Db,
+	MDOC_Dc,
+	MDOC_Do,
+	MDOC_Dq,
+	MDOC_Ec,
+	MDOC_Ef,
+	MDOC_Em,
+	MDOC_Eo,
+	MDOC_Fx,
+	MDOC_Ms,
+	MDOC_No,
+	MDOC_Ns,
+	MDOC_Nx,
+	MDOC_Ox,
+	MDOC_Pc,
+	MDOC_Pf,
+	MDOC_Po,
+	MDOC_Pq,
+	MDOC_Qc,
+	MDOC_Ql,
+	MDOC_Qo,
+	MDOC_Qq,
+	MDOC_Re,
+	MDOC_Rs,
+	MDOC_Sc,
+	MDOC_So,
+	MDOC_Sq,
+	MDOC_Sm,
+	MDOC_Sx,
+	MDOC_Sy,
+	MDOC_Tn,
+	MDOC_Ux,
+	MDOC_Xc,
+	MDOC_Xo,
+	MDOC_Fo,
+	MDOC_Fc,
+	MDOC_Oo,
+	MDOC_Oc,
+	MDOC_Bk,
+	MDOC_Ek,
+	MDOC_Bt,
+	MDOC_Hf,
+	MDOC_Fr,
+	MDOC_Ud,
+	MDOC_Lb,
+	MDOC_Lp,
+	MDOC_Lk,
+	MDOC_Mt,
+	MDOC_Brq,
+	MDOC_Bro,
+	MDOC_Brc,
+	MDOC__C,
+	MDOC_Es,
+	MDOC_En,
+	MDOC_Dx,
+	MDOC__Q,
+	MDOC_br,
+	MDOC_sp,
+	MDOC__U,
+	MDOC_Ta,
+	MDOC_MAX
+};
+
+enum	mdocargt {
+	MDOC_Split, /* -split */
+	MDOC_Nosplit, /* -nospli */
+	MDOC_Ragged, /* -ragged */
+	MDOC_Unfilled, /* -unfilled */
+	MDOC_Literal, /* -literal */
+	MDOC_File, /* -file */
+	MDOC_Offset, /* -offset */
+	MDOC_Bullet, /* -bullet */
+	MDOC_Dash, /* -dash */
+	MDOC_Hyphen, /* -hyphen */
+	MDOC_Item, /* -item */
+	MDOC_Enum, /* -enum */
+	MDOC_Tag, /* -tag */
+	MDOC_Diag, /* -diag */
+	MDOC_Hang, /* -hang */
+	MDOC_Ohang, /* -ohang */
+	MDOC_Inset, /* -inset */
+	MDOC_Column, /* -column */
+	MDOC_Width, /* -width */
+	MDOC_Compact, /* -compact */
+	MDOC_Std, /* -std */
+	MDOC_Filled, /* -filled */
+	MDOC_Words, /* -words */
+	MDOC_Emphasis, /* -emphasis */
+	MDOC_Symbolic, /* -symbolic */
+	MDOC_Nested, /* -nested */
+	MDOC_Centred, /* -centered */
+	MDOC_ARG_MAX
+};
+
+enum	mdoc_type {
+	MDOC_TEXT,
+	MDOC_ELEM,
+	MDOC_HEAD,
+	MDOC_TAIL,
+	MDOC_BODY,
+	MDOC_BLOCK,
+	MDOC_TBL,
+	MDOC_EQN,
+	MDOC_ROOT
+};
+
+/* 
+ * Section (named/unnamed) of `Sh'.   Note that these appear in the
+ * conventional order imposed by mdoc.7.  In the case of SEC_NONE, no
+ * section has been invoked (this shouldn't happen).  SEC_CUSTOM refers
+ * to other sections.
+ */
+enum	mdoc_sec {
+	SEC_NONE = 0,
+	SEC_NAME, /* NAME */
+	SEC_LIBRARY, /* LIBRARY */
+	SEC_SYNOPSIS, /* SYNOPSIS */
+	SEC_DESCRIPTION, /* DESCRIPTION */
+	SEC_IMPLEMENTATION, /* IMPLEMENTATION NOTES */
+	SEC_RETURN_VALUES, /* RETURN VALUES */
+	SEC_ENVIRONMENT,  /* ENVIRONMENT */
+	SEC_FILES, /* FILES */
+	SEC_EXIT_STATUS, /* EXIT STATUS */
+	SEC_EXAMPLES, /* EXAMPLES */
+	SEC_DIAGNOSTICS, /* DIAGNOSTICS */
+	SEC_COMPATIBILITY, /* COMPATIBILITY */
+	SEC_ERRORS, /* ERRORS */
+	SEC_SEE_ALSO, /* SEE ALSO */
+	SEC_STANDARDS, /* STANDARDS */
+	SEC_HISTORY, /* HISTORY */
+	SEC_AUTHORS, /* AUTHORS */
+	SEC_CAVEATS, /* CAVEATS */
+	SEC_BUGS, /* BUGS */
+	SEC_SECURITY, /* SECURITY */
+	SEC_CUSTOM, 
+	SEC__MAX
+};
+
+struct	mdoc_meta {
+	char		 *msec; /* `Dt' section (1, 3p, etc.) */
+	char		 *vol; /* `Dt' volume (implied) */
+	char		 *arch; /* `Dt' arch (i386, etc.) */
+	char		 *date; /* `Dd' normalised date */
+	char		 *title; /* `Dt' title (FOO, etc.) */
+	char		 *os; /* `Os' system (OpenBSD, etc.) */
+	char		 *name; /* leading `Nm' name */
+};
+
+/* 
+ * An argument to a macro (multiple values = `-column xxx yyy'). 
+ */
+struct	mdoc_argv {
+	enum mdocargt  	  arg; /* type of argument */
+	int		  line;
+	int		  pos;
+	size_t		  sz; /* elements in "value" */
+	char		**value; /* argument strings */
+};
+
+/*
+ * Reference-counted macro arguments.  These are refcounted because
+ * blocks have multiple instances of the same arguments spread across
+ * the HEAD, BODY, TAIL, and BLOCK node types.
+ */
+struct 	mdoc_arg {
+	size_t		  argc;
+	struct mdoc_argv *argv;
+	unsigned int	  refcnt;
+};
+
+/*
+ * Indicates that a BODY's formatting has ended, but the scope is still
+ * open.  Used for syntax-broken blocks.
+ */
+enum	mdoc_endbody {
+	ENDBODY_NOT = 0,
+	ENDBODY_SPACE, /* is broken: append a space */
+	ENDBODY_NOSPACE /* is broken: don't append a space */
+};
+
+enum	mdoc_list {
+	LIST__NONE = 0,
+	LIST_bullet, /* -bullet */
+	LIST_column, /* -column */
+	LIST_dash, /* -dash */
+	LIST_diag, /* -diag */
+	LIST_enum, /* -enum */
+	LIST_hang, /* -hang */
+	LIST_hyphen, /* -hyphen */
+	LIST_inset, /* -inset */
+	LIST_item, /* -item */
+	LIST_ohang, /* -ohang */
+	LIST_tag, /* -tag */
+	LIST_MAX
+};
+
+enum	mdoc_disp {
+	DISP__NONE = 0,
+	DISP_centred, /* -centered */
+	DISP_ragged, /* -ragged */
+	DISP_unfilled, /* -unfilled */
+	DISP_filled, /* -filled */
+	DISP_literal /* -literal */
+};
+
+enum	mdoc_auth {
+	AUTH__NONE = 0,
+	AUTH_split, /* -split */
+	AUTH_nosplit /* -nosplit */
+};
+
+enum	mdoc_font {
+	FONT__NONE = 0,
+	FONT_Em, /* Em, -emphasis */
+	FONT_Li, /* Li, -literal */
+	FONT_Sy /* Sy, -symbolic */
+};
+
+struct	mdoc_bd {
+	const char	 *offs; /* -offset */
+	enum mdoc_disp	  type; /* -ragged, etc. */
+	int		  comp; /* -compact */
+};
+
+struct	mdoc_bl {
+	const char	 *width; /* -width */
+	const char	 *offs; /* -offset */
+	enum mdoc_list	  type; /* -tag, -enum, etc. */
+	int		  comp; /* -compact */
+	size_t		  ncols; /* -column arg count */
+	const char	**cols; /* -column val ptr */
+};
+
+struct	mdoc_bf {
+	enum mdoc_font	  font; /* font */
+};
+
+struct	mdoc_an {
+	enum mdoc_auth	  auth; /* -split, etc. */
+};
+
+struct	mdoc_rs {
+	int		  quote_T; /* whether to quote %T */
+};
+
+/*
+ * Consists of normalised node arguments.  These should be used instead
+ * of iterating through the mdoc_arg pointers of a node: defaults are
+ * provided, etc.
+ */
+union	mdoc_data {
+	struct mdoc_an 	  An;
+	struct mdoc_bd	  Bd;
+	struct mdoc_bf	  Bf;
+	struct mdoc_bl	  Bl;
+	struct mdoc_rs	  Rs;
+};
+
+/* 
+ * Single node in tree-linked AST. 
+ */
+struct	mdoc_node {
+	struct mdoc_node *parent; /* parent AST node */
+	struct mdoc_node *child; /* first child AST node */
+	struct mdoc_node *last; /* last child AST node */
+	struct mdoc_node *next; /* sibling AST node */
+	struct mdoc_node *prev; /* prior sibling AST node */
+	int		  nchild; /* number children */
+	int		  line; /* parse line */
+	int		  pos; /* parse column */
+	enum mdoct	  tok; /* tok or MDOC__MAX if none */
+	int		  flags;
+#define	MDOC_VALID	 (1 << 0) /* has been validated */
+#define	MDOC_EOS	 (1 << 2) /* at sentence boundary */
+#define	MDOC_LINE	 (1 << 3) /* first macro/text on line */
+#define	MDOC_SYNPRETTY	 (1 << 4) /* SYNOPSIS-style formatting */
+#define	MDOC_ENDED	 (1 << 5) /* rendering has been ended */
+#define	MDOC_DELIMO	 (1 << 6)
+#define	MDOC_DELIMC	 (1 << 7)
+	enum mdoc_type	  type; /* AST node type */
+	enum mdoc_sec	  sec; /* current named section */
+	union mdoc_data	 *norm; /* normalised args */
+	/* FIXME: these can be union'd to shave a few bytes. */
+	struct mdoc_arg	 *args; /* BLOCK/ELEM */
+	struct mdoc_node *pending; /* BLOCK */
+	struct mdoc_node *head; /* BLOCK */
+	struct mdoc_node *body; /* BLOCK */
+	struct mdoc_node *tail; /* BLOCK */
+	char		 *string; /* TEXT */
+	const struct tbl_span *span; /* TBL */
+	const struct eqn *eqn; /* EQN */
+	enum mdoc_endbody end; /* BODY */
+};
+
+/* Names of macros.  Index is enum mdoct. */
+extern	const char *const *mdoc_macronames;
+
+/* Names of macro args.  Index is enum mdocargt. */
+extern	const char *const *mdoc_argnames;
+
+__BEGIN_DECLS
+
+struct	mdoc;
+
+const struct mdoc_node *mdoc_node(const struct mdoc *);
+const struct mdoc_meta *mdoc_meta(const struct mdoc *);
+
+__END_DECLS
+
+#endif /*!MDOC_H*/
diff --git a/usr/src/cmd/mandoc/mdoc_argv.c b/usr/src/cmd/mandoc/mdoc_argv.c
new file mode 100644
index 0000000..08386e0
--- /dev/null
+++ b/usr/src/cmd/mandoc/mdoc_argv.c
@@ -0,0 +1,716 @@
+/*	$Id: mdoc_argv.c,v 1.82 2012/03/23 05:50:24 kristaps Exp $ */
+/*
+ * Copyright (c) 2008, 2009, 2010, 2011 Kristaps Dzonsons <kristaps@bsd.lv>
+ *
+ * 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.
+ */
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <sys/types.h>
+
+#include <assert.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+
+#include "mdoc.h"
+#include "mandoc.h"
+#include "libmdoc.h"
+#include "libmandoc.h"
+
+#define	MULTI_STEP	 5 /* pre-allocate argument values */