Module Name:    othersrc
Committed By:   dholland
Date:           Mon Aug 29 05:36:31 UTC 2016

Added Files:
        othersrc/usr.bin/manxref: Makefile array.c array.h exceptions.c
            exceptions.h main.c manxref.1 mem.c mem.h page.c page.h pagename.h
            pathnames.h readpage.c readpage.h

Log Message:
Add manxref, a tool for analyzing man-page crossreferences across a
whole system's worth of man pages.

Pursuant to PR 9627, and a much simpler script therein, where it was
suggested that man page crossreferences mostly ought to be
bidirectional.

This does not appear to be the case. There are a lot of cases where a
reference in one direction does not imply a reference back; for
example, make(1) refers to chdir(2) for good reasons, but there's no
sensible reason for chdir(2) to refer to make(1). Likewise, lots of
driver pages refer to bus pages for buses they attach to, but the
reverse links aren't necessarily useful as there are a lot of them.

Note: this program is not related to the Isle of Man.


To generate a diff of this commit:
cvs rdiff -u -r0 -r1.1 othersrc/usr.bin/manxref/Makefile \
    othersrc/usr.bin/manxref/array.c othersrc/usr.bin/manxref/array.h \
    othersrc/usr.bin/manxref/exceptions.c \
    othersrc/usr.bin/manxref/exceptions.h othersrc/usr.bin/manxref/main.c \
    othersrc/usr.bin/manxref/manxref.1 othersrc/usr.bin/manxref/mem.c \
    othersrc/usr.bin/manxref/mem.h othersrc/usr.bin/manxref/page.c \
    othersrc/usr.bin/manxref/page.h othersrc/usr.bin/manxref/pagename.h \
    othersrc/usr.bin/manxref/pathnames.h othersrc/usr.bin/manxref/readpage.c \
    othersrc/usr.bin/manxref/readpage.h

Please note that diffs are not public domain; they are subject to the
copyright notices on the relevant files.

Added files:

Index: othersrc/usr.bin/manxref/Makefile
diff -u /dev/null othersrc/usr.bin/manxref/Makefile:1.1
--- /dev/null	Mon Aug 29 05:36:31 2016
+++ othersrc/usr.bin/manxref/Makefile	Mon Aug 29 05:36:31 2016
@@ -0,0 +1,7 @@
+# $NetBSD: Makefile,v 1.1 2016/08/29 05:36:31 dholland Exp $
+
+PROG=manxref
+SRCS=main.c exceptions.c page.c readpage.c array.c mem.c
+WARNS=5
+
+.include <bsd.prog.mk>
Index: othersrc/usr.bin/manxref/array.c
diff -u /dev/null othersrc/usr.bin/manxref/array.c:1.1
--- /dev/null	Mon Aug 29 05:36:31 2016
+++ othersrc/usr.bin/manxref/array.c	Mon Aug 29 05:36:31 2016
@@ -0,0 +1,117 @@
+/*-
+ * Copyright (c) 2009 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to The NetBSD Foundation
+ * by David A. Holland.
+ *
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdlib.h>
+#include <string.h>
+
+#include "mem.h"
+
+#define ARRAYINLINE
+#include "array.h"
+
+struct array *
+array_create(void)
+{
+	struct array *a;
+
+	a = domalloc(sizeof(*a));
+	array_init(a);
+	return a;
+}
+
+void
+array_destroy(struct array *a)
+{
+	array_cleanup(a);
+	dofree(a, sizeof(*a));
+}
+
+void
+array_init(struct array *a)
+{
+	a->num = a->max = 0;
+	a->v = NULL;
+}
+
+void
+array_cleanup(struct array *a)
+{
+	arrayassert(a->num == 0);
+	dofree(a->v, a->max * sizeof(a->v[0]));
+#ifdef ARRAYS_CHECKED
+	a->v = NULL;
+#endif
+}
+
+void
+array_setsize(struct array *a, unsigned num)
+{
+	unsigned newmax;
+	void **newptr;
+
+	if (num > a->max) {
+		newmax = a->max;
+		while (num > newmax) {
+			newmax = newmax ? newmax*2 : 4;
+		}
+		newptr = dorealloc(a->v, a->max * sizeof(a->v[0]), 
+				   newmax * sizeof(a->v[0]));
+		a->v = newptr;
+		a->max = newmax;
+	}
+	a->num = num;
+}
+
+void
+array_insert(struct array *a, unsigned index_)
+{
+	unsigned movers;
+
+	arrayassert(a->num <= a->max);
+	arrayassert(index_ < a->num);
+
+	movers = a->num - index_;
+
+	array_setsize(a, a->num + 1);
+
+	memmove(a->v + index_+1, a->v + index_, movers*sizeof(*a->v));
+}
+
+void
+array_remove(struct array *a, unsigned index_)
+{
+	unsigned movers;
+
+	arrayassert(a->num <= a->max);
+	arrayassert(index_ < a->num);
+
+	movers = a->num - (index_ + 1);
+	memmove(a->v + index_, a->v + index_+1, movers*sizeof(*a->v));
+	a->num--;
+}
Index: othersrc/usr.bin/manxref/array.h
diff -u /dev/null othersrc/usr.bin/manxref/array.h:1.1
--- /dev/null	Mon Aug 29 05:36:31 2016
+++ othersrc/usr.bin/manxref/array.h	Mon Aug 29 05:36:31 2016
@@ -0,0 +1,247 @@
+/*-
+ * Copyright (c) 2009 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to The NetBSD Foundation
+ * by David A. Holland.
+ *
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef ARRAY_H
+#define ARRAY_H
+
+#define ARRAYS_CHECKED
+
+#ifdef ARRAYS_CHECKED
+#include <assert.h>
+#define arrayassert assert
+#else
+#define arrayassert(x) ((void)(x))
+#endif
+
+////////////////////////////////////////////////////////////
+// type and base operations
+
+struct array {
+	void **v;
+	unsigned num, max;
+};
+
+struct array *array_create(void);
+void array_destroy(struct array *);
+void array_init(struct array *);
+void array_cleanup(struct array *);
+unsigned array_num(const struct array *);
+void *array_get(const struct array *, unsigned index_);
+void array_set(const struct array *, unsigned index_, void *val);
+void array_setsize(struct array *, unsigned num);
+void array_add(struct array *, void *val, unsigned *index_ret);
+void array_insert(struct array *a, unsigned index_);
+void array_remove(struct array *a, unsigned index_);
+void **array_getdata(struct array *a);
+
+////////////////////////////////////////////////////////////
+// inlining for base operations
+
+#ifndef ARRAYINLINE
+#define ARRAYINLINE __c99inline
+#endif
+
+ARRAYINLINE unsigned
+array_num(const struct array *a)
+{
+	return a->num;
+}
+
+ARRAYINLINE void *
+array_get(const struct array *a, unsigned index_)
+{
+	arrayassert(index_ < a->num);
+	return a->v[index_];
+}
+
+ARRAYINLINE void
+array_set(const struct array *a, unsigned index_, void *val)
+{
+	arrayassert(index_ < a->num);
+	a->v[index_] = val;
+}
+
+ARRAYINLINE void
+array_add(struct array *a, void *val, unsigned *index_ret)
+{
+	unsigned index_ = a->num;
+	array_setsize(a, index_+1);
+	a->v[index_] = val;
+	if (index_ret != NULL) {
+		*index_ret = index_;
+	}
+}
+
+ARRAYINLINE void **
+array_getdata(struct array *a)
+{
+	return a->v;
+}
+
+////////////////////////////////////////////////////////////
+// bits for declaring and defining typed arrays
+
+/*
+ * Usage:
+ *
+ * DECLARRAY_BYTYPE(foo, bar) declares "struct foo", which is
+ * an array of pointers to "bar", plus the operations on it.
+ *
+ * DECLARRAY(foo) is equivalent to DECLARRAY_BYTYPE(fooarray, struct foo).
+ *
+ * DEFARRAY_BYTYPE and DEFARRAY are the same as DECLARRAY except that
+ * they define the operations, and both take an extra argument INLINE.
+ * For C99 this should be INLINE in header files and empty in the
+ * master source file, the same as the usage of ARRAYINLINE above and
+ * in array.c.
+ *
+ * Example usage in e.g. item.h of some game:
+ * 
+ * DECLARRAY_BYTYPE(stringarray, char);
+ * DECLARRAY(potion);
+ * DECLARRAY(sword);
+ *
+ * #ifndef ITEMINLINE
+ * #define ITEMINLINE INLINE
+ * #endif
+ *
+ * DEFARRAY_BYTYPE(stringarray, char, ITEMINLINE);
+ * DEFARRAY(potion, ITEMINLINE);
+ * DEFARRAY(sword, ITEMINLINE);
+ *
+ * Then item.c would do "#define ITEMINLINE" before including item.h.
+ */
+
+#define DECLARRAY_BYTYPE(ARRAY, T) \
+	struct ARRAY {						\
+		struct array arr;				\
+	};							\
+								\
+	struct ARRAY *ARRAY##_create(void);			\
+	void ARRAY##_destroy(struct ARRAY *a);			\
+	void ARRAY##_init(struct ARRAY *a);			\
+	void ARRAY##_cleanup(struct ARRAY *a);			\
+	unsigned ARRAY##_num(const struct ARRAY *a);		\
+	T *ARRAY##_get(const struct ARRAY *a, unsigned index_);	\
+	void ARRAY##_set(struct ARRAY *a, unsigned index_, T *val); \
+	void ARRAY##_setsize(struct ARRAY *a, unsigned num);	\
+	void ARRAY##_add(struct ARRAY *a, T *val, unsigned *index_ret); \
+	void ARRAY##_insert(struct ARRAY *a, unsigned index_);	\
+	void ARRAY##_remove(struct ARRAY *a, unsigned index_);	\
+	void **ARRAY##_getdata(struct ARRAY *a)
+
+
+#define DEFARRAY_BYTYPE(ARRAY, T, INLINE) \
+	INLINE void						\
+	ARRAY##_init(struct ARRAY *a)				\
+	{							\
+		array_init(&a->arr);				\
+	}							\
+								\
+	INLINE void						\
+	ARRAY##_cleanup(struct ARRAY *a)			\
+	{							\
+		array_cleanup(&a->arr);				\
+	}							\
+								\
+	INLINE struct						\
+	ARRAY *ARRAY##_create(void)				\
+	{							\
+		struct ARRAY *a;				\
+								\
+		a  = domalloc(sizeof(*a));			\
+		ARRAY##_init(a);				\
+		return a;					\
+	}							\
+								\
+	INLINE void						\
+	ARRAY##_destroy(struct ARRAY *a)			\
+	{							\
+		ARRAY##_cleanup(a);				\
+		dofree(a, sizeof(*a));				\
+	}							\
+								\
+	INLINE unsigned						\
+	ARRAY##_num(const struct ARRAY *a)			\
+	{							\
+		return array_num(&a->arr);			\
+	}							\
+								\
+	INLINE T *						\
+	ARRAY##_get(const struct ARRAY *a, unsigned index_)	\
+	{				 			\
+		return (T *)array_get(&a->arr, index_);		\
+	}							\
+								\
+	INLINE void						\
+	ARRAY##_set(struct ARRAY *a, unsigned index_, T *val)	\
+	{				 			\
+		array_set(&a->arr, index_, (void *)val);	\
+	}							\
+								\
+	INLINE void						\
+	ARRAY##_setsize(struct ARRAY *a, unsigned num)		\
+	{				 			\
+		array_setsize(&a->arr, num);			\
+	}							\
+								\
+	INLINE void						\
+	ARRAY##_add(struct ARRAY *a, T *val, unsigned *ret)	\
+	{				 			\
+		array_add(&a->arr, (void *)val, ret);		\
+	}							\
+								\
+	INLINE void						\
+	ARRAY##_insert(struct ARRAY *a, unsigned index_)	\
+	{				 			\
+		array_insert(&a->arr, index_);			\
+	}							\
+								\
+	INLINE void						\
+	ARRAY##_remove(struct ARRAY *a, unsigned index_)	\
+	{				 			\
+		return array_remove(&a->arr, index_);		\
+	}							\
+								\
+	INLINE void **						\
+	ARRAY##_getdata(struct ARRAY *a)			\
+	{				 			\
+		return array_getdata(&a->arr);			\
+	}
+
+#define DECLARRAY(T) DECLARRAY_BYTYPE(T##array, struct T)
+#define DEFARRAY(T, INLINE) DEFARRAY_BYTYPE(T##array, struct T, INLINE)
+
+////////////////////////////////////////////////////////////
+// basic array types
+
+DECLARRAY_BYTYPE(stringarray, char);
+DEFARRAY_BYTYPE(stringarray, char, ARRAYINLINE);
+
+#endif /* ARRAY_H */
Index: othersrc/usr.bin/manxref/exceptions.c
diff -u /dev/null othersrc/usr.bin/manxref/exceptions.c:1.1
--- /dev/null	Mon Aug 29 05:36:31 2016
+++ othersrc/usr.bin/manxref/exceptions.c	Mon Aug 29 05:36:31 2016
@@ -0,0 +1,489 @@
+/*-
+ * Copyright (c) 2016 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to The NetBSD Foundation
+ * by David A. Holland.
+ *
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <err.h>
+
+#include "mem.h"
+#include "array.h"
+#include "pagename.h"
+#include "page.h"
+#include "exceptions.h"
+
+struct namepair {
+	struct pagename from, to;
+};
+DECLARRAY(namepair);
+DEFARRAY(namepair, );
+
+static struct namepairarray silences;
+static struct pagenamearray silencealls;
+static struct pagenamearray dups;
+
+////////////////////////////////////////////////////////////
+// add stuff
+
+/*
+ * Copy the string S, stopping at the first ENDCH, which is guaranteed
+ * to be present.
+ */
+static char *
+getstring(const char *s, int endch)
+{
+	const char *t;
+
+	t = strchr(s, endch);
+	assert(t);
+	return dostrndup(s, t-s);
+}
+
+static void
+addsilence(struct constpagename *from, struct constpagename *to)
+{
+	struct namepair *np;
+
+	np = domalloc(sizeof(*np));
+	np->from.page = getstring(from->page, '(');
+	np->from.section = getstring(from->section, ')');
+	np->to.page = getstring(to->page, '(');
+	np->to.section = getstring(to->section, ')');
+	namepairarray_add(&silences, np, NULL);
+}
+
+static void
+addsilenceall(struct constpagename *from)
+{
+	struct pagename *pn;
+
+	pn = domalloc(sizeof(*pn));
+	pn->page = getstring(from->page, '(');
+	pn->section = getstring(from->section, ')');
+	pagenamearray_add(&silencealls, pn, NULL);
+}
+
+static void
+adddup(struct constpagename *from)
+{
+	struct pagename *pn;
+
+	pn = domalloc(sizeof(*pn));
+	pn->page = getstring(from->page, '(');
+	pn->section = getstring(from->section, ')');
+	pagenamearray_add(&dups, pn, NULL);
+}
+
+////////////////////////////////////////////////////////////
+// parsestate
+
+struct parsestate {
+	const char *file;
+	unsigned line;
+	bool inblock;
+	enum { B_SILENCE, B_DUPS } blocktype;
+	bool ok;
+};
+
+static void
+parsefail(struct parsestate *ps, const char *what, const char *where)
+{
+	warnx("%s:%u: %s %s", ps->file, ps->line, what, where);
+	ps->ok = false;
+}
+
+static void
+parsestate_init(struct parsestate *ps, const char *file)
+{
+	ps->file = file;
+	ps->line = 0;
+	ps->inblock = false;
+	ps->blocktype = B_SILENCE;
+	ps->ok = true;
+}
+
+static bool
+parsestate_cleanup(struct parsestate *ps)
+{
+	if (ps->inblock) {
+		parsefail(ps, "Unclosed block", "at end of input");
+		return false;
+	}
+	return ps->ok;
+}
+
+////////////////////////////////////////////////////////////
+// parse
+
+static const char *
+skipws(const char *s)
+{
+	while (*s == ' ' || *s == '\t') {
+		s++;
+	}
+	return s;
+}
+
+static bool
+is_alnum(int ch)
+{
+	/* XXX fix this if EBCDIC ever reappears */
+	return ch == '_' || ch == '-' ||
+		(ch >= '0' && ch <= '9') ||
+		(ch >= 'a' && ch <= 'z') ||
+		(ch >= 'A' && ch <= 'Z');
+}
+
+static const char *
+skipalnum(const char *s)
+{
+	while (is_alnum(*s)) {
+		s++;
+	}
+	return s;
+}
+
+static const char *
+getpagename(struct parsestate *ps, const char *text, const char *where,
+	    struct constpagename *ret)
+{
+	text = skipws(text);
+
+	ret->page = text;
+	text = skipalnum(text);
+	if (text == ret->page) {
+		parsefail(ps, "Page name expected", where);
+		goto fail;
+	} else if (*text != '(') {
+		parsefail(ps, "Missing section", "after page name");
+		goto fail;
+	}
+	text++;
+	ret->section = text;
+	text = skipalnum(text);
+	if (text == ret->section) {
+		parsefail(ps, "Section name expected", "after page name");
+		goto fail;
+	} else if (*text != ')') {
+		parsefail(ps, "Missing right paren", "after section name");
+		goto fail;
+	}
+	text++;
+	return text;
+
+ fail:
+	ret->page = ret->section = NULL;
+	return text;
+}
+
+static void
+dosilence(struct parsestate *ps, const char *text)
+{
+	struct constpagename pn;
+	struct constpagename pn2;
+
+	text = getpagename(ps, text, "in silence block", &pn);
+	text = skipws(text);
+	if (*text) {
+		if (!strncmp(text, "->", 2)) {
+			text += 2;
+		} else {
+			parsefail(ps, "Expected arrow", "after page name");
+		}
+		text = skipws(text);
+		text = getpagename(ps, text, "after arrow", &pn2);
+		text = skipws(text);
+		if (*text) {
+			parsefail(ps, "Unexpected text", "at end of line");
+		}
+		addsilence(&pn, &pn2);
+	} else {
+		addsilenceall(&pn);
+	}
+}
+
+static void
+dodup(struct parsestate *ps, const char *text)
+{
+	struct constpagename pn;
+
+	text = getpagename(ps, text, "in duplicates block", &pn);
+	text = skipws(text);
+	if (*text) {
+		parsefail(ps, "Unexpected text", "at end of line");
+	}
+	adddup(&pn);
+}
+
+static void
+parseline(struct parsestate *ps, const char *text)
+{
+	/*
+	 * things we allow:
+	 *    silence foo(3)
+	 *    silence foo(3) -> bar(2)
+	 *    silence {
+	 *       foo(3)
+	 *       foo(3) -> bar(2)
+         *    }
+         *    duplicate foo(3)
+	 *    duplicates {
+	 *       foo(3)
+         *    }
+	 */
+
+	text = skipws(text);
+
+	if (ps->inblock && *text == '}') {
+		text++;
+		text = skipws(text);
+		if (*text) {
+			parsefail(ps, "Unexpected text",
+				  "after close brace");
+		}
+		ps->inblock = false;
+		return;
+	}
+
+	if (ps->inblock && ps->blocktype == B_SILENCE) {
+		dosilence(ps, text);
+		return;
+	}
+
+	if (ps->inblock && ps->blocktype == B_DUPS) {
+		dodup(ps, text);
+		return;
+	}
+
+	assert(ps->inblock == false);
+
+	if (!strncmp(text, "silence ", 8)) {
+		text += 8;
+		text = skipws(text);
+		if (*text == '{') {
+			ps->inblock = true;
+			ps->blocktype = B_SILENCE;
+			text++;
+			text = skipws(text);
+			if (*text) {
+				parsefail(ps, "Unexpected text",
+					  "after open brace");
+			}
+			return;
+		}
+		dosilence(ps, text);
+		return;
+	}
+
+	if (!strncmp(text, "duplicate ", 10)) {
+		text += 10;
+		dodup(ps, text);
+		return;
+	}
+
+	if (!strncmp(text, "duplicates {", 12)) {
+		text += 12;
+		text = skipws(text);
+		if (*text) {
+			parsefail(ps, "Unexpected text",
+				  "after open brace");
+		}
+		ps->inblock = true;
+		ps->blocktype = B_DUPS;
+		return;
+	}
+
+	if (*text == '#') {
+		return;
+	}
+
+	parsefail(ps, "Unknown declaration", "");
+}
+
+////////////////////////////////////////////////////////////
+// load
+
+void
+exceptions_add(const char *text)
+{
+	struct parsestate state;
+
+	parsestate_init(&state, "<command line>");
+	parseline(&state, text);
+	if (!parsestate_cleanup(&state)) {
+		errx(EXIT_FAILURE, "Command line parse errors");
+	}
+}
+
+void
+exceptions_loadfile(const char *file)
+{
+	FILE *f;
+	char buf[256];
+	size_t len;
+	struct parsestate state;
+
+	f = fopen(file, "r");
+	if (!f) {
+		err(EXIT_FAILURE, "%s", file);
+	}
+	parsestate_init(&state, file);
+	while (!fgets(buf, sizeof(buf), f)) {
+		state.line++;
+		len = strlen(buf);
+		if (len == 0 || buf[len - 1] != '\n') {
+			errx(EXIT_FAILURE, "%s:%u: Line too long", file,
+			     state.line);
+		}
+		buf[len - 1] = 0;
+		len--;
+		parseline(&state, buf);
+	}
+	if (!parsestate_cleanup(&state)) {
+		errx(EXIT_FAILURE, "%s: Parse errors", file);
+	}
+	if (ferror(f)) {
+		errx(EXIT_FAILURE, "%s: Read error", file);
+	}
+	fclose(f);
+}
+
+////////////////////////////////////////////////////////////
+// push into page.c
+
+void
+exceptions_to_pages(void)
+{
+	unsigned num, i;
+	struct namepair *np;
+	struct pagename *pn;
+	struct page *pg;
+	unsigned ix;
+
+	/*
+	 * XXX these should silence by name, not by page,
+	 * so all pages with the same name get handled instead
+	 * of only the first one.
+	 */
+
+	num = namepairarray_num(&silences);
+	for (i=0; i<num; i++) {
+		np = namepairarray_get(&silences, i);
+		pg = page_getbyname(np->from.page, np->from.section);
+		if (pg == NULL) {
+			warnx("Exception page %s(%s) does not exist",
+			      np->from.page, np->from.section);
+			continue;
+		}
+		ix = page_getxrefto(pg, np->to.page, np->to.section);
+		if (ix == NO_XREF) {
+			warnx("Exception referece %s(%s) -> %s(%s) "
+			      "does not exist",
+			      np->from.page, np->from.section,
+			      np->to.page, np->to.section);
+			continue;
+		}
+		page_silencexref(pg, ix);
+	}
+
+	num = pagenamearray_num(&silencealls);
+	for (i=0; i<num; i++) {
+		pn = pagenamearray_get(&silencealls, i);
+		pg = page_getbyname(pn->page, pn->section);
+		if (pg == NULL) {
+			warnx("Exception page %s(%s) does not exist",
+			      pn->page, pn->section);
+			continue;
+		}
+		page_silence(pg);
+	}
+
+	num = pagenamearray_num(&dups);
+	for (i=0; i<num; i++) {
+		pn = pagenamearray_get(&dups, i);
+		if (page_expectdup(pn)) {
+			warnx("Exception page %s(%s) does not exist",
+			      pn->page, pn->section);
+			continue;
+		}
+	}
+}
+
+////////////////////////////////////////////////////////////
+// global init
+
+void
+exceptions_setup(void)
+{
+	namepairarray_init(&silences);
+	pagenamearray_init(&silencealls);
+	pagenamearray_init(&dups);
+}
+
+void
+exceptions_shutdown(void)
+{
+	unsigned i, num;
+	struct namepair *np;
+	struct pagename *pn;
+
+	num = pagenamearray_num(&dups);
+	for (i=0; i<num; i++) {
+		pn = pagenamearray_get(&dups, i);
+		dostrfree(pn->page);
+		dostrfree(pn->section);
+		dofree(pn, sizeof(*pn));
+	}
+	pagenamearray_setsize(&dups, 0);
+
+	num = pagenamearray_num(&silencealls);
+	for (i=0; i<num; i++) {
+		pn = pagenamearray_get(&silencealls, i);
+		dostrfree(pn->page);
+		dostrfree(pn->section);
+		dofree(pn, sizeof(*pn));
+	}
+	pagenamearray_setsize(&silencealls, 0);
+
+	num = namepairarray_num(&silences);
+	for (i=0; i<num; i++) {
+		np = namepairarray_get(&silences, i);
+		dostrfree(np->from.page);
+		dostrfree(np->from.section);
+		dostrfree(np->to.page);
+		dostrfree(np->to.section);
+		dofree(np, sizeof(*np));
+	}
+	namepairarray_setsize(&silences, 0);
+
+	pagenamearray_cleanup(&dups);
+	pagenamearray_cleanup(&silencealls);
+	namepairarray_cleanup(&silences);
+}
Index: othersrc/usr.bin/manxref/exceptions.h
diff -u /dev/null othersrc/usr.bin/manxref/exceptions.h:1.1
--- /dev/null	Mon Aug 29 05:36:31 2016
+++ othersrc/usr.bin/manxref/exceptions.h	Mon Aug 29 05:36:31 2016
@@ -0,0 +1,36 @@
+/*-
+ * Copyright (c) 2016 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to The NetBSD Foundation
+ * by David A. Holland.
+ *
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+void exceptions_add(const char *text);
+void exceptions_loadfile(const char *file);
+void exceptions_to_pages(void);
+
+void exceptions_setup(void);
+void exceptions_shutdown(void);
+
Index: othersrc/usr.bin/manxref/main.c
diff -u /dev/null othersrc/usr.bin/manxref/main.c:1.1
--- /dev/null	Mon Aug 29 05:36:31 2016
+++ othersrc/usr.bin/manxref/main.c	Mon Aug 29 05:36:31 2016
@@ -0,0 +1,330 @@
+/*-
+ * Copyright (c) 2016 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to The NetBSD Foundation
+ * by David A. Holland.
+ *
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <dirent.h>
+#include <err.h>
+
+#include "mem.h"
+#include "array.h"
+#include "pagename.h"
+#include "readpage.h"
+#include "page.h"
+#include "exceptions.h"
+
+////////////////////////////////////////////////////////////
+// reporting
+
+static void
+report_page_dups(struct constpagename *pn)
+{
+	unsigned dups;
+
+	dups = page_unexpecteddups(pn->page, pn->section);
+	if (dups > 0) {
+		printf("%s(%s) duplicated %u times\n",
+		       pn->page, pn->section, dups);
+	}
+}
+
+static void
+report_dups(void)
+{
+	struct constpagenamearray names;
+	unsigned i, num;
+
+	constpagenamearray_init(&names);
+	page_getdupnames(&names);
+	num = constpagenamearray_num(&names);
+	for (i=0; i<num; i++) {
+		report_page_dups(constpagenamearray_get(&names, i));
+	}
+	constpagenamearray_setsize(&names, 0);
+	constpagenamearray_cleanup(&names);
+}
+
+static void
+report_page_xrefs(struct page *pg)
+{
+	unsigned i, num, flags;
+	struct constpagename *myname;
+	struct pagename *refname;
+
+	myname = page_getname(pg);
+
+	if (page_issilenced(pg)) {
+		return;
+	}
+
+	num = page_getnumxrefs(pg);
+	for (i=0; i<num; i++) {
+		refname = page_getxref(pg, i, &flags);
+		if (flags & XF_SILENCED) {
+			continue;
+		}
+		if (flags & XF_COMESBACK) {
+			continue;
+		}
+
+		printf("%s(%s) -> %s(%s)", myname->page, myname->section,
+		       refname->page, refname->section);
+		if (flags & XF_DANGLING) {
+			printf(" dangles\n");
+		} else {
+			printf(" does not point back\n");
+		}
+	}
+}
+
+static void
+report_xrefs(void)
+{
+	struct pagearray pages;
+	unsigned i, num;
+
+	pagearray_init(&pages);
+	page_getallsorted(&pages);
+	num = pagearray_num(&pages);
+	for (i=0; i<num; i++) {
+		report_page_xrefs(pagearray_get(&pages, i));
+	}
+	pagearray_setsize(&pages, 0);
+	pagearray_cleanup(&pages);
+}
+
+////////////////////////////////////////////////////////////
+// directory scanning
+
+/*
+ * Scan a man page directory, e.g. /usr/share/man/man1. In that
+ * example case PATH contains "/usr/share/man", DIR contains "man1",
+ * and the current directory is /usr/share/man. TREENUM is the
+ * index number of the man tree we're looking at; this is used to
+ * preserve the command-line ordering of the trees.
+ */
+static void
+scanmandir(unsigned treenum, const char *path, const char *dir)
+{
+	const char *section, *s;
+	char *sectionstore;
+	size_t sectionlen;
+	DIR *dp;
+	struct dirent *d;
+	size_t len;
+	char *file, *page;
+	struct stat st;
+	struct page *pg;
+	struct pagenamearray *xrefnames;
+
+	//warnx("reached %s/%s", path, dir);
+
+	section = dir + 3;
+	s = strchr(section, '/');
+	if (s) {
+		sectionlen = s - section;
+		sectionstore = dostrndup(section, sectionlen);
+		section = sectionstore;
+	} else {
+		sectionlen = strlen(section);
+		sectionstore = NULL;
+	}
+
+	dp = opendir(dir);
+	if (dp == NULL) {
+		err(EXIT_FAILURE, "%s/%s: opendir", path, dir);
+	}
+	while ((d = readdir(dp)) != NULL) {
+		if (d->d_name[0] == '.') {
+			continue;
+		}
+
+		file = dostrdup3(dir, "/", d->d_name);
+		if (stat(file, &st) < 0) {
+			warnx("%s/%s: stat", path, file);
+			continue;
+		}
+
+		if (S_ISDIR(st.st_mode)) {
+			scanmandir(treenum, path, file);
+			dostrfree(file);
+			continue;
+		}
+
+		len = strlen(d->d_name);
+		if (len < sectionlen + 1 ||
+		    strcmp(d->d_name + len - sectionlen, section) != 0 ||
+		    d->d_name[len - sectionlen - 1] != '.') {
+			warnx("%s/%s/%s: Inappropriately named manual page",
+			      path, dir, d->d_name);
+			continue;
+		}
+
+		page = domalloc(len - sectionlen);
+		memcpy(page, d->d_name, len - sectionlen - 1);
+		page[len - sectionlen - 1] = 0;
+
+		pg = page_get(st.st_dev, st.st_ino,
+			      path, treenum,
+			      page, section);
+		assert(pg != NULL);
+		xrefnames = page_getloadarray(pg);
+		if (xrefnames != NULL) {
+			//warnx("reading %s", file);
+			readpage(file, xrefnames);
+		}
+
+		dostrfree(file);
+		dostrfree(page);
+	}
+	closedir(dp);
+	if (sectionstore) {
+		dostrfree(sectionstore);
+	}
+}
+
+/*
+ * Scan the top level dir of a man tree, e.g. /usr/share/man.
+ */
+static void
+scantree(const char *path, unsigned treenum)
+{
+	int herefd;
+	DIR *dp;
+	struct dirent *d;
+	struct stat st;
+
+	herefd = open(".", O_DIRECTORY|O_RDONLY);
+	if (chdir(path) < 0) {
+		err(EXIT_FAILURE, "%s: chdir", path);
+	}
+
+	dp = opendir(".");
+	if (dp == NULL) {
+		err(EXIT_FAILURE, "%s: opendir", path);
+	}
+
+	/*
+	 * The top level of the man tree contains directories
+	 * man1, man2, etc., which we want to look in, and also
+	 * likely directories cat1, cat2 or html1, html2 etc.
+	 * that we don't.
+	 */
+	while ((d = readdir(dp)) != NULL) {
+		if (!strncmp(d->d_name, "man", 3)) {
+			if (stat(d->d_name, &st) < 0) {
+				warn("%s/%s: stat", path, d->d_name);
+				continue;
+			}
+			if (!S_ISDIR(st.st_mode)) {
+				warnx("%s/%s: Not a directory",
+				      path, d->d_name);
+				continue;
+			}
+			scanmandir(treenum, path, d->d_name);
+		}
+	}
+	closedir(dp);
+
+	if (herefd >= 0) {
+		fchdir(herefd);
+		close(herefd);
+	}
+}
+
+////////////////////////////////////////////////////////////
+// main
+
+static __dead void
+usage(void)
+{
+	fprintf(stderr, "Usage: manxref [options] [man-trees]\n");
+	fprintf(stderr, "   -p mandocpath        Set path to mandoc\n");
+	fprintf(stderr, "   -X exceptionfile     Load file of exceptions\n");
+	fprintf(stderr, "   -x exception         Set single exception\n");
+	exit(EXIT_FAILURE);
+}
+
+int
+main(int argc, char *argv[])
+{
+	struct stringarray mantrees;
+	int ch;
+	unsigned i;
+
+	mem_setup();
+	stringarray_init(&mantrees);
+	exceptions_setup();
+	readpage_setup();
+	page_setup();
+
+	while ((ch = getopt(argc, argv, "p:X:x:")) != -1) {
+		switch (ch) {
+		    case 'p':
+			readpage_setmandoc(optarg);
+			break;
+		    case 'X':
+			exceptions_loadfile(optarg);
+			break;
+		    case 'x':
+			exceptions_add(optarg);
+			break;
+		    default:
+			usage();
+		}
+	}
+	while (optind < argc) {
+		stringarray_add(&mantrees, argv[optind++], NULL);
+	}
+
+	for (i=0; i<stringarray_num(&mantrees); i++) {
+		scantree(stringarray_get(&mantrees, i), i);
+	}
+
+	page_crossref();
+	exceptions_to_pages();
+
+	report_dups();
+	report_xrefs();
+
+	page_shutdown();
+	readpage_shutdown();
+	exceptions_shutdown();
+	stringarray_setsize(&mantrees, 0);
+	stringarray_cleanup(&mantrees);
+	mem_shutdown();
+	return 0;
+}
Index: othersrc/usr.bin/manxref/manxref.1
diff -u /dev/null othersrc/usr.bin/manxref/manxref.1:1.1
--- /dev/null	Mon Aug 29 05:36:31 2016
+++ othersrc/usr.bin/manxref/manxref.1	Mon Aug 29 05:36:31 2016
@@ -0,0 +1,118 @@
+.\"
+.\" Copyright (c) 2016 The NetBSD Foundation, Inc.
+.\" All rights reserved.
+.\"
+.\" This code is derived from software contributed to The NetBSD Foundation
+.\" by David A. Holland.
+.\"
+.\" 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.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
+.\" ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+.\" TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+.\" PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
+.\" BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+.\" CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+.\" SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+.\" INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+.\" CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+.\" ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+.\" POSSIBILITY OF SUCH DAMAGE.
+.\"
+.Dd August 29, 2016
+.Dt MANXREF 1
+.Os
+.Sh NAME
+.Nm manxref
+.Nd cross-check man page cross references
+.Sh SYNOPSIS
+.Nm
+.Op Fl p Ar path
+.Op Fl X Ar file
+.Op Fl x Ar string
+.Ar man-tree...
+.Sh DESCRIPTION
+The
+.Nm
+program reads one or more trees of manual pages and collects all the
+cross-references.
+At that point it prints a report listing (a) the duplicated page
+names; (b) all dangling cross-references; and (c) cross-references
+where the target page doesn't reference the source page back.
+.Pp
+The value of point (c) is questionable.
+This program was written with (c) in mind, pursuant to PR 9627, but
+the amount of noise produced is very large -- there are many cases
+where bidirectional references do not make sense.
+For example,
+.Xr make 1
+refers to
+.Xr chdir 2
+for good reasons but there is no conceivable reason for the reverse
+reference.
+Neither is there any reason for either of those pages to reference
+this one!
+While
+.Nm
+supports lists of crossreferences to not warn about, the size of the
+list required makes it unclear if the analysis is worthwhile in the
+absence of any way to deduce which backreferences are wanted.
+.Ss Options
+.Bl -tag -width aaa
+.It Fl p Ar path
+Set the path to the
+.Xr mandoc 1
+program used to read manual page source files.
+The default path is
+.Pa /usr/bin/mandoc .
+.It Fl X Ar file
+Read a list of exceptions from the specified
+.Ar file .
+.It Fl x Ar string
+Add an exception specified directly as
+.Ar string .
+.\" XXX
+The syntax for exceptions and exception lists is documented in a
+comment in
+.Pa exceptions.c .
+.El
+.Pp
+The
+.Ar man-tree
+arguments should be paths to the top of installed manual page trees,
+such as
+.Pa /usr/share/man .
+.Sh EXAMPLES
+.D1 Li "manxref /usr/share/man /usr/X11R7/man"
+.Sh SEE ALSO
+.Xr mandoc 1 ,
+.Xr mdoclint 1
+.Sh RESTRICTIONS
+The path given to the
+.Fl p
+option must be absolute, for no especially good reason.
+.Pp
+The exceptions handling code hasn't been tested yet and probably
+doesn't work.
+.Pp
+Some things related to duplicate page names vs. hard-linked pages
+haven't been sorted out yet internally.
+.Pp
+Because
+.Nm
+runs
+.Xr mandoc 1
+as a subprocess, errors therein are not handled well.
+.Pp
+Crossreferences within pages that are not written in
+.Xr mdoc 7
+are effectively invisible.
+.Pp
+The code leaks a lot of memory.
Index: othersrc/usr.bin/manxref/mem.c
diff -u /dev/null othersrc/usr.bin/manxref/mem.c:1.1
--- /dev/null	Mon Aug 29 05:36:31 2016
+++ othersrc/usr.bin/manxref/mem.c	Mon Aug 29 05:36:31 2016
@@ -0,0 +1,157 @@
+/*-
+ * Copyright (c) 2016 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to The NetBSD Foundation
+ * by David A. Holland.
+ *
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+#include <err.h>
+
+#include "mem.h"
+
+static size_t allocated;
+
+void *
+domalloc(size_t size)
+{
+	void *ptr;
+
+	ptr = malloc(size);
+	if (ptr == NULL) {
+		err(EXIT_FAILURE, "malloc");
+	}
+	allocated += size;
+	return ptr;
+}
+
+void *
+dorealloc(void *oldptr, size_t oldsize, size_t newsize)
+{
+	void *newptr;
+
+	newptr = realloc(oldptr, newsize);
+	if (newptr == NULL) {
+		err(EXIT_FAILURE, "realloc");
+	}
+	assert(allocated >= oldsize);
+	allocated -= oldsize;
+	allocated += newsize;
+	return newptr;
+}
+
+void
+dofree(void *ptr, size_t size)
+{
+	free(ptr);
+	assert(allocated >= size);
+	allocated -= size;
+}
+
+char *
+dostrdup(const char *s)
+{
+	size_t rlen, slen;
+	char *r;
+
+	slen = strlen(s);
+	rlen = slen;
+	r = domalloc(rlen + 1);
+	memcpy(r, s, slen);
+	r[rlen] = 0;
+	return r;
+}
+
+char *
+dostrdup2(const char *s, const char *t)
+{
+	size_t rlen, slen, tlen;
+	char *r;
+
+	slen = strlen(s);
+	tlen = strlen(t);
+	rlen = slen + tlen;
+	r = domalloc(rlen + 1);
+	memcpy(r, s, slen);
+	memcpy(r + slen, t, tlen);
+	r[rlen] = 0;
+	return r;
+}
+
+char *
+dostrdup3(const char *s, const char *t, const char *u)
+{
+	size_t rlen, slen, tlen, ulen;
+	char *r;
+
+	slen = strlen(s);
+	tlen = strlen(t);
+	ulen = strlen(u);
+	rlen = slen + tlen + ulen;
+	r = domalloc(rlen + 1);
+	memcpy(r, s, slen);
+	memcpy(r + slen, t, tlen);
+	memcpy(r + slen + tlen, u, ulen);
+	r[rlen] = 0;
+	return r;
+}
+
+char *
+dostrndup(const char *s, size_t slen)
+{
+	size_t rlen;
+	char *r;
+
+	rlen = slen;
+	r = domalloc(rlen + 1);
+	memcpy(r, s, slen);
+	r[rlen] = 0;
+	return r;
+}
+
+void
+dostrfree(char *s)
+{
+	size_t len;
+
+	len = strlen(s);
+	dofree(s, len + 1);
+}
+
+void
+mem_setup(void)
+{
+	/* nothing */
+}
+
+void
+mem_shutdown(void)
+{
+	if (allocated > 0) {
+		warnx("%zu bytes leaked", allocated);
+	}
+}
Index: othersrc/usr.bin/manxref/mem.h
diff -u /dev/null othersrc/usr.bin/manxref/mem.h:1.1
--- /dev/null	Mon Aug 29 05:36:31 2016
+++ othersrc/usr.bin/manxref/mem.h	Mon Aug 29 05:36:31 2016
@@ -0,0 +1,41 @@
+/*-
+ * Copyright (c) 2016 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to The NetBSD Foundation
+ * by David A. Holland.
+ *
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+void *domalloc(size_t);
+void *dorealloc(void *, size_t, size_t);
+void dofree(void *, size_t);
+
+char *dostrdup(const char *);
+char *dostrdup2(const char *, const char *);
+char *dostrdup3(const char *, const char *, const char *);
+char *dostrndup(const char *, size_t);
+void dostrfree(char *);
+
+void mem_setup(void);
+void mem_shutdown(void);
Index: othersrc/usr.bin/manxref/page.c
diff -u /dev/null othersrc/usr.bin/manxref/page.c:1.1
--- /dev/null	Mon Aug 29 05:36:31 2016
+++ othersrc/usr.bin/manxref/page.c	Mon Aug 29 05:36:31 2016
@@ -0,0 +1,830 @@
+/*-
+ * Copyright (c) 2016 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to The NetBSD Foundation
+ * by David A. Holland.
+ *
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <sys/types.h>
+#include <sys/rbtree.h>
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "mem.h"
+#include "array.h"
+#include "pagename.h"
+#include "page.h"
+
+DEFARRAY(pagename, );
+DEFARRAY(constpagename, );
+DEFARRAY(page, );
+
+////////////////////////////////////////////////////////////
+// struct page type
+
+struct fileid {
+	dev_t dev;
+	ino_t ino;
+};
+
+struct page {
+	/* file id */
+	struct fileid id;
+	char *mantree;
+	unsigned mantreenum;
+	rb_node_t filenode;
+
+	/* names for this page */
+	struct constpagename self;
+	struct pagenamearray mynames;
+
+	/* xref data */
+	struct pagenamearray xrefnames;
+	struct pagearray xrefpages;
+	unsigned *xrefflags;
+	unsigned numxrefs;
+
+	/* whole-page flags */
+	bool loaded;
+	bool silenced;
+};
+
+struct nameindexentry {
+	struct page *page;
+	struct pagearray allpages;
+
+	struct constpagename name;
+	rb_node_t namenode;
+
+	unsigned expecteddups;
+};
+DECLARRAY(nameindexentry);
+DEFARRAY(nameindexentry, );
+
+////////////////////////////////////////////////////////////
+// struct pagename operations
+
+static struct pagename *
+pagename_create(const char *page, const char *section)
+{
+	struct pagename *pn;
+
+	pn = domalloc(sizeof(*pn));
+	pn->page = dostrdup(page);
+	pn->section = dostrdup(section);
+	return pn;
+}
+
+static void
+pagename_destroy(struct pagename *pn)
+{
+	dostrfree(pn->page);
+	dostrfree(pn->section);
+	dofree(pn, sizeof(*pn));
+}
+
+////////////////////////////////////////////////////////////
+// struct page operations
+
+static struct page *
+page_create(dev_t dev, ino_t ino, const char *mantree, unsigned mantreenum,
+	    const char *page, const char *section)
+{
+	struct page *pg;
+	struct pagename *self;
+
+	self = pagename_create(page, section);
+
+	pg = domalloc(sizeof(*pg));
+
+	pg->id.dev = dev;
+	pg->id.ino = ino;
+	pg->mantree = dostrdup(mantree);
+	pg->mantreenum = mantreenum;
+
+	pg->self.page = self->page;
+	pg->self.section = self->section;
+	pagenamearray_init(&pg->mynames);
+	pagenamearray_add(&pg->mynames, self, NULL);
+
+	pagenamearray_init(&pg->xrefnames);
+	pagearray_init(&pg->xrefpages);
+	pg->xrefflags = NULL;
+	pg->numxrefs = 0;
+
+	pg->loaded = false;
+	pg->silenced = false;
+
+	return pg;
+}
+
+static void
+page_destroy(struct page *pg)
+{
+	unsigned i, num;
+	struct pagename *pn;
+	bool seen;
+
+	if (pg->xrefflags != NULL) {
+		assert(pg->numxrefs == pagenamearray_num(&pg->xrefnames));
+		assert(pg->numxrefs == pagearray_num(&pg->xrefpages));
+		dofree(pg->xrefflags, pg->numxrefs * sizeof(pg->xrefflags[0]));
+	} else {
+		assert(pg->numxrefs == 0);
+	}
+
+	/* we don't own these so just clear it */
+	pagearray_setsize(&pg->xrefpages, 0);
+	pagearray_cleanup(&pg->xrefpages);
+
+	/* we do own these so nuke 'em */
+	num = pagenamearray_num(&pg->xrefnames);
+	for (i=0; i<num; i++) {
+		pn = pagenamearray_get(&pg->xrefnames, i);
+		pagename_destroy(pn);
+	}
+	pagenamearray_setsize(&pg->xrefnames, 0);
+	pagenamearray_cleanup(&pg->xrefnames);
+
+	/* and we own these */
+	seen = false;
+	num = pagenamearray_num(&pg->mynames);
+	for (i=0; i<num; i++) {
+		pn = pagenamearray_get(&pg->mynames, i);
+		if (pn->page == pg->self.page &&
+		    pn->section == pg->self.section) {
+			assert(!seen);
+			seen = true;
+		} else {
+			assert(pn->page != pg->self.page);
+			assert(pn->section != pg->self.section);
+		}
+		pagename_destroy(pn);
+	}
+	assert(seen);
+	pagenamearray_setsize(&pg->mynames, 0);
+	pagenamearray_cleanup(&pg->mynames);
+	//pg->self.page = NULL;
+	//pg->self.section = NULL;
+
+	dostrfree(pg->mantree);
+	dofree(pg, sizeof(*pg));
+}
+
+static struct pagename *
+page_add_name(struct page *pg, const char *page, const char *section)
+{
+	struct pagename *pn;
+
+	pn = pagename_create(page, section);
+	pagenamearray_add(&pg->mynames, pn, NULL);
+	return pn;
+}
+
+////////////////////////////////////////////////////////////
+// struct nameindexentry functions
+
+static struct nameindexentry *
+nameindexentry_create(struct page *pg, struct pagename *name)
+{
+	struct nameindexentry *e;
+
+	e = domalloc(sizeof(*e));
+
+	e->page = pg;
+	pagearray_init(&e->allpages);
+	pagearray_add(&e->allpages, pg, NULL);
+
+	e->name.page = name->page;
+	e->name.section = name->section;
+
+	e->expecteddups = 0;
+	return e;
+}
+
+static void
+nameindexentry_destroy(struct nameindexentry *e)
+{
+	pagearray_setsize(&e->allpages, 0);
+	pagearray_cleanup(&e->allpages);
+	dofree(e, sizeof(*e));
+}
+
+////////////////////////////////////////////////////////////
+// utility functions
+
+/*
+ * sort-type compare function for (unsigned) integers
+ */
+static int
+uintcmp(uintmax_t a, uintmax_t b)
+{
+	if (a < b) {
+		return -1;
+	}
+	if (a > b) {
+		return 1;
+	}
+	return 0;
+}
+
+static int
+fileid_cmp(const struct fileid *a, const struct fileid *b)
+{
+	int r;
+
+	r = uintcmp(a->dev, b->dev);
+	if (r == 0) {
+		r = uintcmp(a->ino, b->ino);
+	}
+	return r;
+}
+
+static int
+constpagename_cmp(const struct constpagename *a, const struct constpagename *b)
+{
+	int r;
+
+	r = strcmp(a->section, b->section);
+	if (r == 0) {
+		r = strcmp(a->page, b->page);
+	}
+	return r;
+}
+
+////////////////////////////////////////////////////////////
+// table of pages
+
+/*
+ * The way this works is that there's an array of pages, which is not
+ * sorted at all, and two indexes: one by file identity (dev_t and
+ * ino_t) and one by page name.
+ */
+
+static struct pagearray allpages;
+static struct nameindexentryarray nameindexentries;
+static rb_tree_t fileindex;
+static rb_tree_t nameindex;
+
+////////////////////
+// fileindex
+
+static int
+fileindex_compare_node(void *null, const void *node1, const void *node2)
+{
+	const struct page *pg1 = node1;
+	const struct page *pg2 = node2;
+
+	(void)null;
+	return fileid_cmp(&pg1->id, &pg2->id);
+}
+
+static int
+fileindex_compare_key(void *null, const void *node1, const void *key2)
+{
+	const struct page *pg = node1;
+	const struct fileid *key = key2;
+
+	return fileid_cmp(&pg->id, key);
+}
+
+static void
+fileindex_init(void)
+{
+	static const rb_tree_ops_t fileindex_ops = {
+		.rbto_compare_nodes = fileindex_compare_node,
+		.rbto_compare_key = fileindex_compare_key,
+		.rbto_node_offset = offsetof(struct page, filenode),
+		.rbto_context = NULL,
+	};
+
+	rb_tree_init(&fileindex, &fileindex_ops);
+}
+
+static void
+fileindex_cleanup(void)
+{
+	/* nothing */
+}
+
+static struct page *
+fileindex_get(dev_t dev, ino_t ino)
+{
+	struct fileid id = { .dev = dev, .ino = ino };
+	void *node;
+
+	node = rb_tree_find_node(&fileindex, &id);
+	if (node == NULL) {
+		return NULL;
+	}
+	//return FILENODE_TO_PAGE(, node);
+	return node;
+}
+
+static void
+fileindex_insert(struct page *pg)
+{
+	void *node;
+
+	//node = rb_tree_insert_node(&fileindex, &pg->filenode);
+	//assert(node == &pg->filenode);
+	node = rb_tree_insert_node(&fileindex, pg);
+	assert(node == pg);
+}
+
+////////////////////
+// nameindex
+
+static int
+nameindex_compare_node(void *null, const void *node1, const void *node2)
+{
+	const struct nameindexentry *e1 = node1;
+	const struct nameindexentry *e2 = node2;
+
+	(void)null;
+	return constpagename_cmp(&e1->name, &e2->name);
+}
+
+static int
+nameindex_compare_key(void *null, const void *node1, const void *key2)
+{
+	const struct nameindexentry *e = node1;
+	const struct constpagename *key = key2;
+
+	(void)null;
+	return constpagename_cmp(&e->name, key);
+}
+
+static void
+nameindex_init(void)
+{
+	static const rb_tree_ops_t nameindex_ops = {
+		.rbto_compare_nodes = nameindex_compare_node,
+		.rbto_compare_key = nameindex_compare_key,
+		.rbto_node_offset = offsetof(struct nameindexentry, namenode),
+		.rbto_context = NULL,
+	};
+
+	rb_tree_init(&nameindex, &nameindex_ops);
+}
+
+static void
+nameindex_cleanup(void)
+{
+	/* nothing */
+}
+
+static struct nameindexentry *
+nameindex_getraw(const char *page, const char *section)
+{
+	struct constpagename name = {
+		.page = page, .section = section
+	};
+	void *node;
+	struct nameindexentry *e;
+
+	node = rb_tree_find_node(&nameindex, &name);
+	if (node == NULL) {
+		return NULL;
+	}
+	e = node;
+	return e;
+}
+
+static struct page *
+nameindex_get(const char *page, const char *section)
+{
+	struct nameindexentry *e;
+
+	e = nameindex_getraw(page, section);
+	if (e == NULL) {
+		return NULL;
+	}
+	return e->page;
+}
+
+static void
+nameindex_insert(struct page *pg, struct pagename *name)
+{
+	struct nameindexentry *e;
+	void *node;
+
+	e = nameindexentry_create(pg, name);
+	nameindexentryarray_add(&nameindexentries, e, NULL);
+
+	node = rb_tree_insert_node(&nameindex, e);
+	assert(node == e);
+}
+
+////////////////////////////////////////////////////////////
+// struct page external queries / actions
+
+struct constpagename *
+page_getname(struct page *pg)
+{
+	return &pg->self;
+}
+
+bool
+page_issilenced(struct page *pg)
+{
+	return pg->silenced;
+}
+
+void
+page_silence(struct page *pg)
+{
+	pg->silenced = true;
+}
+
+unsigned
+page_unexpecteddups(const char *page, const char *section)
+{
+	struct nameindexentry *e;
+	unsigned num;
+
+	e = nameindex_getraw(page, section);
+	if (e == NULL) {
+		/* ? */
+		return 0;
+	}
+
+	num = pagearray_num(&e->allpages);
+	assert(num > 0);
+	assert(e->expecteddups <= num - 1);
+	return num - 1 - e->expecteddups;
+}
+
+int
+page_expectdup(struct pagename *pn)
+{
+	struct nameindexentry *e;
+
+	e = nameindex_getraw(pn->page, pn->section);
+	if (e == NULL) {
+		return -1;
+	}
+	e->expecteddups++;
+	return 0;
+}
+
+unsigned
+page_getnumxrefs(struct page *pg)
+{
+	assert(pagenamearray_num(&pg->xrefnames) == pg->numxrefs);
+	assert(pagearray_num(&pg->xrefpages) == pg->numxrefs);
+
+	return pg->numxrefs;
+}
+
+struct pagename *
+page_getxref(struct page *pg, unsigned ix, unsigned *flags_ret)
+{
+	struct pagename *xref;
+
+	xref = pagenamearray_get(&pg->xrefnames, ix);
+	*flags_ret = pg->xrefflags[ix];
+	return xref;
+}
+
+unsigned
+page_getxrefto(struct page *pg, const char *page, const char *section)
+{
+	unsigned i, num;
+	struct pagename *pn;
+
+	num = pagenamearray_num(&pg->xrefnames);
+	for (i=0; i<num; i++) {
+		pn = pagenamearray_get(&pg->xrefnames, i);
+		if (!strcmp(pn->page, page) && !strcmp(pn->section, section)) {
+			return i;
+		}
+	}
+	return NO_XREF;
+}
+
+void
+page_silencexref(struct page *pg, unsigned ix)
+{
+	assert(pg->xrefflags != NULL);
+	assert(ix < pg->numxrefs);
+	pg->xrefflags[ix] |= XF_SILENCED;
+}
+
+struct pagenamearray *
+page_getloadarray(struct page *pg)
+{
+	if (pg->loaded) {
+		return NULL;
+	}
+	pg->loaded = true;
+	return &pg->xrefnames;
+}
+
+////////////////////////////////////////////////////////////
+// table access
+
+/*
+ * av/bv are pointers into the array data, so each points to
+ * a void pointer that's really a page pointer.
+ */
+static int
+pagecmp(const void *av, const void *bv)
+{
+	const struct page *a = *(const void *const *)av;
+	const struct page *b = *(const void *const *)bv;
+	int r;
+
+	r = constpagename_cmp(&a->self, &b->self);
+	if (r == 0) {
+		r = uintcmp(a->mantreenum, b->mantreenum);
+	}
+	if (r == 0) {
+		/*
+		 * Use the array index to make the sort stable
+		 * XXX: is this actually safe?
+		 */
+		r = uintcmp((uintptr_t)av, (uintptr_t)bv);
+	}
+	return r;
+}
+
+/*
+ * Retrieve all the pages, sorted in the order we'd like to display
+ * them.
+ */
+void
+page_getallsorted(struct pagearray *fill)
+{
+	unsigned i, num;
+	struct page *pg;
+
+	num = pagearray_num(&allpages);
+	pagearray_setsize(fill, num);
+	for (i=0; i<num; i++) {
+		pg = pagearray_get(&allpages, i);
+
+		assert(pagenamearray_num(&pg->xrefnames) == pg->numxrefs);
+		assert(pagearray_num(&pg->xrefpages) == pg->numxrefs);
+		assert(pg->xrefflags != NULL);
+
+		pagearray_set(fill, i, pg);
+	}
+
+	qsort(pagearray_getdata(fill), pagearray_num(fill),
+	      sizeof(void *), pagecmp);
+}
+
+/*
+ * retrieve names that are duplicated
+ */
+void
+page_getdupnames(struct constpagenamearray *fill)
+{
+	unsigned i, num;
+	struct nameindexentry *e;
+
+	num = nameindexentryarray_num(&nameindexentries);
+	for (i=0; i<num; i++) {
+		e = nameindexentryarray_get(&nameindexentries, i);
+		if (pagearray_num(&e->allpages) > 1) {
+			constpagenamearray_add(fill, &e->name, NULL);
+		}
+	}
+}
+
+/*
+ * Fetch the proper page structure for the man page file with the
+ * given identity, in the given man tree, with name page/section.
+ *
+ * If we have seen the same file before, use the same page structure.
+ * If we have a different file with the same page name, mark it up as
+ * a duplicate page.
+ */
+struct page *
+page_get(dev_t dev, ino_t ino, const char *mantree, unsigned treenum,
+	 const char *page, const char *section)
+{
+	struct page *pg;
+	struct pagename *name;
+	struct nameindexentry *e;
+
+	pg = fileindex_get(dev, ino);
+	if (pg == NULL) {
+		/* new page */
+		pg = page_create(dev, ino, mantree, treenum, page, section);
+		assert(pg != NULL);
+
+		pagearray_add(&allpages, pg, NULL);
+		fileindex_insert(pg);
+
+		assert(pagenamearray_num(&pg->mynames) == 1);
+		name = pagenamearray_get(&pg->mynames, 0);
+	} else {
+		name = page_add_name(pg, page, section);
+	}
+
+	e = nameindex_getraw(page, section);
+	if (e != NULL) {
+		pagearray_add(&e->allpages, pg, NULL);
+	} else {
+		nameindex_insert(pg, name);
+	}
+
+	return pg;
+}
+
+struct page *
+page_getbyname(const char *page, const char *section)
+{
+	return nameindex_get(page, section);
+}
+
+////////////////////////////////////////////////////////////
+// xref analysis
+
+/*
+ * Populate xrefpages[] for each xref. Also allocate xrefflags[].
+ */
+static void
+page_populate_xrefpages(struct page *pg)
+{
+	unsigned i, num;
+	struct pagename *pn;
+	struct page *otherpg;
+
+	num = pagenamearray_num(&pg->xrefnames);
+	pagearray_setsize(&pg->xrefpages, num);
+	pg->xrefflags = domalloc(num * sizeof(pg->xrefflags[0]));
+	pg->numxrefs = num;
+	for (i=0; i<num; i++) {
+		pn = pagenamearray_get(&pg->xrefnames, i);
+		otherpg = nameindex_get(pn->page, pn->section);
+		pagearray_set(&pg->xrefpages, i, otherpg);
+		if (otherpg == NULL) {
+			pg->xrefflags[i] = XF_DANGLING;
+		} else {
+			pg->xrefflags[i] = 0;
+		}
+	}
+}
+
+/*
+ * Check if PG itself references TARGET.
+ */
+static bool
+page_references_really(struct page *pg, struct page *target)
+{
+	unsigned i;
+	struct page *candidate;
+
+	assert(target != NULL);
+
+	for (i=0; i<pg->numxrefs; i++) {
+		candidate = pagearray_get(&pg->xrefpages, i);
+		if (candidate == NULL) {
+			continue;
+		}
+		if (candidate == target) {
+			return true;
+		}
+	}
+	return false;
+}
+
+
+/*
+ * Check if PG, or any of its duplicates, references TARGET.
+ */
+static bool
+page_references(struct page *pg, struct page *target)
+{
+	unsigned i, num, j, num2;
+	struct page *dup;
+	struct pagename *pn;
+	struct nameindexentry *e;
+
+	assert(target != NULL);
+
+	num = pagenamearray_num(&pg->mynames);
+	for (i=0; i<num; i++) {
+		pn = pagenamearray_get(&pg->mynames, i);
+		e = nameindex_getraw(pn->page, pn->section);
+		num2 = pagearray_num(&e->allpages);
+		for (j=0; j<num2; j++) {
+			dup = pagearray_get(&e->allpages, j);
+			if (page_references_really(dup, target)) {
+				return true;
+			}
+		}
+	}
+	return false;
+}
+
+/*
+ * For each xref, check if it comes back.
+ */
+static void
+page_check_comesback(struct page *pg)
+{
+	unsigned i;
+	struct page *otherpg;
+
+	for (i=0; i<pg->numxrefs; i++) {
+		otherpg = pagearray_get(&pg->xrefpages,  i);
+		if (otherpg == NULL) {
+			continue;
+		}
+		if (page_references(otherpg, pg)) {
+			pg->xrefflags[i] |= XF_COMESBACK;
+		}
+	}
+}
+
+/*
+ * This needs to:
+ *    - for every page, populate xrefpages[] and allocate xrefflags[]
+ *    - mark with XF_DANGLING if no xrefpages[] exists
+ *    - mark with XF_COMESBACK if the xref page points back here
+ *
+ * Note that because pages may have multiple names, the last step
+ * needs to search xrefpages[i]->xrefpages[] for object identity and
+ * not go by name. Also, if we point to a page with duplicates, we
+ * want to check all the duplicates for a link back. (Ideally, we'd
+ * diagnose it if some duplicates have different links back...)
+ */
+void
+page_crossref(void)
+{
+	unsigned i, num;
+
+	num = pagearray_num(&allpages);
+	for (i=0; i<num; i++) {
+		page_populate_xrefpages(pagearray_get(&allpages, i));
+	}
+	for (i=0; i<num; i++) {
+		page_check_comesback(pagearray_get(&allpages, i));
+	}
+}
+
+////////////////////////////////////////////////////////////
+// global init
+
+void
+page_setup(void)
+{
+	fileindex_init();
+	nameindex_init();
+	pagearray_init(&allpages);
+	nameindexentryarray_init(&nameindexentries);
+}
+
+void
+page_shutdown(void)
+{
+	unsigned i, num;
+	struct page *pg;
+	struct nameindexentry *e;
+
+	num = nameindexentryarray_num(&nameindexentries);
+	for (i=0; i<num; i++) {
+		e = nameindexentryarray_get(&nameindexentries, i);
+		nameindexentry_destroy(e);
+	}
+	nameindexentryarray_setsize(&nameindexentries, 0);
+	nameindexentryarray_cleanup(&nameindexentries);
+
+	num = pagearray_num(&allpages);
+	for (i=0; i<num; i++) {
+		pg = pagearray_get(&allpages, i);
+		page_destroy(pg);
+	}
+	pagearray_setsize(&allpages, 0);
+	pagearray_cleanup(&allpages);
+	nameindex_cleanup();
+	fileindex_cleanup();
+}
Index: othersrc/usr.bin/manxref/page.h
diff -u /dev/null othersrc/usr.bin/manxref/page.h:1.1
--- /dev/null	Mon Aug 29 05:36:31 2016
+++ othersrc/usr.bin/manxref/page.h	Mon Aug 29 05:36:31 2016
@@ -0,0 +1,82 @@
+/*-
+ * Copyright (c) 2016 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to The NetBSD Foundation
+ * by David A. Holland.
+ *
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+struct page;
+DECLARRAY(page);
+
+#define NO_XREF ((unsigned)-1)
+
+/* flags for xrefflags */
+#define XF_SILENCED 1
+#define XF_COMESBACK 2
+#define XF_DANGLING 4
+
+/*
+ * operations on page names
+ */
+unsigned page_unexpecteddups(const char *page, const char *section);
+int page_expectdup(struct pagename *);
+
+
+/*
+ * operations on pages
+ */
+struct constpagename *page_getname(struct page *);
+bool page_issilenced(struct page *);
+void page_silence(struct page *);
+
+unsigned page_getnumxrefs(struct page *);
+struct pagename *page_getxref(struct page *, unsigned, unsigned *flags_ret);
+unsigned page_getxrefto(struct page *, const char *page, const char *section);
+void page_silencexref(struct page *, unsigned);
+
+/*
+ * load-time retrievals
+ */
+struct page *page_get(dev_t, ino_t, const char *mantree, unsigned treenum,
+		      const char *page, const char *section);
+struct pagenamearray *page_getloadarray(struct page *);
+
+/*
+ * analysis phase
+ */
+void page_crossref(void);
+
+/*
+ * report-time retrievals
+ */
+void page_getdupnames(struct constpagenamearray *fill);
+void page_getallsorted(struct pagearray *fill);
+struct page *page_getbyname(const char *page, const char *section);
+
+/*
+ * global init
+ */
+void page_setup(void);
+void page_shutdown(void);
Index: othersrc/usr.bin/manxref/pagename.h
diff -u /dev/null othersrc/usr.bin/manxref/pagename.h:1.1
--- /dev/null	Mon Aug 29 05:36:31 2016
+++ othersrc/usr.bin/manxref/pagename.h	Mon Aug 29 05:36:31 2016
@@ -0,0 +1,40 @@
+/*-
+ * Copyright (c) 2016 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to The NetBSD Foundation
+ * by David A. Holland.
+ *
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+struct pagename {
+	char *page;
+	char *section;
+};
+DECLARRAY(pagename);
+
+struct constpagename {
+	const char *page;
+	const char *section;
+};
+DECLARRAY(constpagename);
Index: othersrc/usr.bin/manxref/pathnames.h
diff -u /dev/null othersrc/usr.bin/manxref/pathnames.h:1.1
--- /dev/null	Mon Aug 29 05:36:31 2016
+++ othersrc/usr.bin/manxref/pathnames.h	Mon Aug 29 05:36:31 2016
@@ -0,0 +1,30 @@
+/*-
+ * Copyright (c) 2016 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to The NetBSD Foundation
+ * by David A. Holland.
+ *
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#define _PATH_MANDOC "/usr/bin/mandoc"
Index: othersrc/usr.bin/manxref/readpage.c
diff -u /dev/null othersrc/usr.bin/manxref/readpage.c:1.1
--- /dev/null	Mon Aug 29 05:36:31 2016
+++ othersrc/usr.bin/manxref/readpage.c	Mon Aug 29 05:36:31 2016
@@ -0,0 +1,488 @@
+/*-
+ * Copyright (c) 2016 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to The NetBSD Foundation
+ * by David A. Holland.
+ *
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/*
+ * Scan a man page and produce a list of its crossreferences.
+ * Uses mandoc -Ttree to read the roff.
+ */
+
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <err.h>
+#include <ctype.h>
+
+#include "mem.h"
+#include "array.h"
+#include "pagename.h"
+#include "readpage.h"
+#include "pathnames.h"
+
+static const char *mandocpath = _PATH_MANDOC;
+
+////////////////////////////////////////////////////////////
+// inbuf
+
+static char *inbuf;
+static size_t inbufpos, inbuflen, inbufmax;
+static bool inbuf_eof;
+
+static void
+inbuf_init(void)
+{
+	inbufpos = inbuflen = 0;
+	inbufmax = 4096;
+	inbuf_eof = false;
+
+	inbuf = domalloc(inbufmax);
+}
+
+static void
+inbuf_cleanup(void)
+{
+	dofree(inbuf, inbufmax);
+	inbuf = NULL;
+	inbufmax = 0;
+}
+
+static void
+inbuf_reset(void)
+{
+	inbufpos = 0;
+	inbuflen = 0;
+	inbuf_eof = false;
+}
+
+static void
+inbuf_grow(void)
+{
+	assert(inbufmax > 0);
+	inbuf = dorealloc(inbuf, inbufmax, inbufmax*2);
+	inbufmax *= 2;
+}
+
+static void
+inbuf_read(int fd)
+{
+	ssize_t r;
+
+	if (inbuf_eof) {
+		/* buffer contents should be null-terminated */
+		assert(inbuflen < inbufmax);
+		assert(inbuf[inbuflen] == 0);
+		return;
+	}
+
+	if (inbufpos == inbuflen) {
+		inbufpos = inbuflen = 0;
+	}
+
+	if (inbufpos > inbufmax / 2) {
+		/* shift the whole thing down */
+		memmove(inbuf, inbuf + inbufpos, inbuflen - inbufpos);
+		inbuflen -= inbufpos;
+		inbufpos = 0;
+	}
+
+	if (inbuflen == inbufmax) {
+		/* long line; need more space */
+		inbuf_grow();
+	}
+
+	r = read(fd, inbuf + inbuflen,  inbufmax - inbuflen);
+	if (r < 0) {
+		err(EXIT_FAILURE, "read");
+	}
+	if (r == 0) {
+		inbuf_eof = true;
+		/* make sure there's a null terminator after the last byte */
+		if (inbuflen == inbufmax) {
+			inbuf_grow();
+		}
+		inbuf[inbuflen] = 0;
+		return;
+	}
+	inbuflen += r;
+}
+
+static const char *
+inbuf_getline(int fd)
+{
+	char *s;
+	const char *ret;
+	size_t amt;
+
+	if (inbufpos == inbuflen) {
+		/* nothing in the buffer; read more */
+		inbuf_read(fd);
+		if (inbufpos == inbuflen) {
+			/* read must have done nothing => we hit eof */
+			assert(inbuf_eof);
+			return NULL;
+		}
+	}
+
+	while (1) {
+
+		s = memchr(inbuf + inbufpos, '\n', inbuflen - inbufpos);
+		if (s != NULL) {
+			/* have a line to return */
+			ret = inbuf + inbufpos;
+			amt = s - ret;
+			*s = 0;
+			s++;
+			amt++;
+			break;
+		}
+
+		/* have a partial line in the buffer */
+
+		if (inbuf_eof) {
+			/* nothing more to read, return the partial line */
+			ret = inbuf + inbufpos;
+			amt = inbuflen - inbufpos;
+
+			/* inbuf_read() null-terminated it */
+			assert(inbuflen < inbufmax);
+			assert(inbuf[inbuflen] == 0);
+
+			break;
+		}
+
+		/* read more and try again */
+		inbuf_read(fd);
+	}
+
+	inbufpos += amt;
+	return ret;
+}
+
+////////////////////////////////////////////////////////////
+// popen
+
+static int
+mypopen(const char *cmd, char **argv, pid_t *retpid)
+{
+	int fds[2];
+	pid_t pid;
+
+	if (pipe(fds) < 0) {
+		err(EXIT_FAILURE, "pipe");
+	}
+
+	pid = fork();
+	if (pid < 0) {
+		err(EXIT_FAILURE, "fork");
+	}
+	if (pid == 0) {
+		/* child - close read end, make write end stdout */
+		assert(fds[1] != STDOUT_FILENO);
+		close(fds[0]);
+		if (dup2(fds[1], STDOUT_FILENO) < 0) {
+			err(EXIT_FAILURE, "dup2");
+		}
+		close(fds[1]);
+		/* exec the command */
+		execv(cmd, argv);
+		err(EXIT_FAILURE, "exec: %s", cmd);
+	}
+	/* parent - close write end, return read end */
+	close(fds[1]);
+	*retpid = pid;
+	return fds[0];
+}
+
+static void
+mypclose(int fd, pid_t pid)
+{
+	int status;
+
+	close(fd);
+	if (waitpid(pid, &status, 0) < 0) {
+		err(EXIT_FAILURE, "waitpid");
+	}
+	if (WIFSIGNALED(status)) {
+		warnx("Child process exited with signal %d", WTERMSIG(status));
+	} else if (WIFEXITED(status) && WEXITSTATUS(status) != 0) {
+		warnx("Child process exit %d", WEXITSTATUS(status));
+	}
+}
+
+////////////////////////////////////////////////////////////
+// primary scanning code
+
+/*
+ * Count the number of leading tabs.
+ */
+static unsigned
+count_tabs(const char *s)
+{
+	unsigned ret = 0;
+
+	while (*s == '\t') {
+		ret++;
+		s++;
+	}
+	/* This is not true; it appears possible to get empty text blocks */
+	//assert(*s != ' ');
+	return ret;
+}
+
+/*
+ * Check if S has the form "line:col"
+ */
+static bool
+islinecol(const char *s)
+{
+	unsigned n;
+
+	/* number (line number in roff source) */
+	n = 0;
+	while (isdigit((unsigned char)*s)) {
+		s++;
+		n++;
+	}
+	if (n == 0) {
+		return false;
+	}
+
+	/* colon */
+	if (*s != ':') {
+		return false;
+	}
+	s++;
+
+	/* number (column number in roff source) */
+	n = 0;
+	while (isdigit((unsigned char)*s)) {
+		s++;
+		n++;
+	}
+	if (n == 0) {
+		return false;
+	}
+
+	/* end */
+	if (*s) {
+		return false;
+	}
+	return true;
+}
+
+/*
+ * Check if S contains the output for an Xr node.
+ */
+static bool
+at_Xr(const char *s)
+{
+	if (strncmp(s, "Xr (elem) *", 11) != 0) {
+		return false;
+	}
+	s += 11;
+
+	if (!islinecol(s)) {
+		return false;
+	}
+	return true;
+}
+
+/*
+ * Check that s contains "stuff (text) line:col" and truncate after stuff.
+ */
+static bool
+checktext(char *s)
+{
+	char *t;
+
+	t = strrchr(s, ' ');
+	if (t == NULL) {
+		return false;
+	}
+	if (!islinecol(t + 1)) {
+		return false;
+	}
+	if (t - s < 7) {
+		return false;
+	}
+	t -= 7;
+	if (memcmp(t, " (text)", 7) != 0) {
+		return false;
+	}
+	*t = 0;
+	return true;
+}
+
+/*
+ * Post the .Xr data into the page name array.
+ */
+static int
+postxref(char *s1, char *s2, struct pagenamearray *fill)
+{
+	unsigned i, num;
+	struct pagename *pn;
+	struct pagename *xref;
+
+	/*
+	 * for .Xr ksh 1,
+	 * s1 contains: ksh (text) line:col
+	 * s2 contains: 1 (text) line:col
+	 */
+
+	if (!checktext(s1) || !checktext(s2)) {
+		return -1;
+	}
+
+	/* XXX should use something other than linear search for this */
+	num = pagenamearray_num(fill);
+	for (i=0; i<num; i++) {
+		pn = pagenamearray_get(fill, i);
+		if (!strcmp(s1, pn->page) && !strcmp(s2, pn->section)) {
+			goto out;
+		}
+	}
+
+	xref = domalloc(sizeof(*xref));
+	xref->page = dostrdup(s1);
+	xref->section = dostrdup(s2);
+	pagenamearray_add(fill, xref, NULL);
+
+ out:
+	dostrfree(s1);
+	dostrfree(s2);
+	return 0;
+}
+
+void
+readpage(const char *name, struct pagenamearray *fill)
+{
+	const char *args[4];
+	int fd;
+	pid_t pid;
+	bool in_Xr;
+	unsigned Xr_linenum, Xr_indent, Xr_contents_indent;
+	char *collect[2];
+	unsigned collected;
+	unsigned linenum;
+	const char *line;
+	unsigned indent;
+
+	args[0] = mandocpath;
+	args[1] = "-Ttree";
+	args[2] = name;
+	args[3] = NULL;
+
+	fd = mypopen(mandocpath, __UNCONST(args), &pid);
+	if (fd < 0) {
+		/* messages already printed */
+		return;
+	}
+
+	in_Xr = false;
+	Xr_linenum = 0;
+	Xr_indent = 0;
+	Xr_contents_indent = 0;
+	collect[0] = NULL;
+	collect[1] = NULL;
+	collected = 0;
+
+	linenum = 0;
+	inbuf_reset();
+	while ((line = inbuf_getline(fd)) != NULL) {
+		linenum++;
+
+		//printf(" >> %u %s\n", linenum, line);
+
+		indent = count_tabs(line);
+		if (!in_Xr && at_Xr(line + indent)) {
+			in_Xr = true;
+			Xr_linenum = linenum;
+			Xr_indent = indent;
+			Xr_contents_indent = indent + 1;
+			collect[0] = NULL;
+			collect[1] = NULL;
+			collected = 0;
+		} else if (in_Xr) {
+			if (indent == Xr_contents_indent && collected < 2) {
+				collect[collected] = dostrdup(line + indent);
+				collected++;
+			} else if (indent <= Xr_indent && collected == 2) {
+				if (postxref(collect[0], collect[1], fill)) {
+					warnx("%s: Invalid Xr data at line %u "
+					      "of mandoc output", name,
+					      Xr_linenum);
+					dostrfree(collect[0]);
+					dostrfree(collect[1]);
+				}
+				in_Xr = false;
+				collect[0] = NULL;
+				collect[1] = NULL;
+				collected = 0;
+			} else {
+				warnx("%s: Unexpected Xr contents at line %u "
+				      "of mandoc output", name, Xr_linenum);
+				in_Xr = false;
+				if (collect[0]) {
+					dostrfree(collect[0]);
+					collect[0] = NULL;
+				}
+				if (collect[1]) {
+					dostrfree(collect[1]);
+					collect[1] = NULL;
+				}
+				collected = 0;
+			}
+		}
+	}
+
+	mypclose(fd, pid);
+}
+
+////////////////////////////////////////////////////////////
+// global init
+
+void
+readpage_setmandoc(const char *path)
+{
+	mandocpath = path;
+}
+
+void
+readpage_setup(void)
+{
+	inbuf_init();
+}
+
+void
+readpage_shutdown(void)
+{
+	inbuf_cleanup();
+}
Index: othersrc/usr.bin/manxref/readpage.h
diff -u /dev/null othersrc/usr.bin/manxref/readpage.h:1.1
--- /dev/null	Mon Aug 29 05:36:31 2016
+++ othersrc/usr.bin/manxref/readpage.h	Mon Aug 29 05:36:31 2016
@@ -0,0 +1,36 @@
+/*-
+ * Copyright (c) 2016 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to The NetBSD Foundation
+ * by David A. Holland.
+ *
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+struct pagenamearray;
+
+void readpage_setmandoc(const char *path);
+void readpage(const char *name, struct pagenamearray *fill);
+
+void readpage_setup(void);
+void readpage_shutdown(void);

Reply via email to