xref: /freebsd-src/contrib/mandoc/mandocdb.c (revision c1c95add8c80843ba15d784f95c361d795b1f593)
1*c1c95addSBrooks Davis /* $Id: mandocdb.c,v 1.274 2024/05/14 21:19:12 schwarze Exp $ */
261d06d6bSBaptiste Daroussin /*
3*c1c95addSBrooks Davis  * Copyright (c) 2011-2021, 2024 Ingo Schwarze <schwarze@openbsd.org>
461d06d6bSBaptiste Daroussin  * Copyright (c) 2011, 2012 Kristaps Dzonsons <kristaps@bsd.lv>
561d06d6bSBaptiste Daroussin  * Copyright (c) 2016 Ed Maste <emaste@freebsd.org>
661d06d6bSBaptiste Daroussin  *
761d06d6bSBaptiste Daroussin  * Permission to use, copy, modify, and distribute this software for any
861d06d6bSBaptiste Daroussin  * purpose with or without fee is hereby granted, provided that the above
961d06d6bSBaptiste Daroussin  * copyright notice and this permission notice appear in all copies.
1061d06d6bSBaptiste Daroussin  *
1161d06d6bSBaptiste Daroussin  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHORS DISCLAIM ALL WARRANTIES
1261d06d6bSBaptiste Daroussin  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
1361d06d6bSBaptiste Daroussin  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR
1461d06d6bSBaptiste Daroussin  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
1561d06d6bSBaptiste Daroussin  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
1661d06d6bSBaptiste Daroussin  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
1761d06d6bSBaptiste Daroussin  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
186d38604fSBaptiste Daroussin  *
196d38604fSBaptiste Daroussin  * Implementation of the makewhatis(8) program.
2061d06d6bSBaptiste Daroussin  */
2161d06d6bSBaptiste Daroussin #include "config.h"
2261d06d6bSBaptiste Daroussin 
2361d06d6bSBaptiste Daroussin #include <sys/types.h>
2461d06d6bSBaptiste Daroussin #include <sys/mman.h>
2561d06d6bSBaptiste Daroussin #include <sys/stat.h>
2661d06d6bSBaptiste Daroussin 
2761d06d6bSBaptiste Daroussin #include <assert.h>
2861d06d6bSBaptiste Daroussin #include <ctype.h>
2961d06d6bSBaptiste Daroussin #if HAVE_ERR
3061d06d6bSBaptiste Daroussin #include <err.h>
3161d06d6bSBaptiste Daroussin #endif
3261d06d6bSBaptiste Daroussin #include <errno.h>
3361d06d6bSBaptiste Daroussin #include <fcntl.h>
3461d06d6bSBaptiste Daroussin #if HAVE_FTS
3561d06d6bSBaptiste Daroussin #include <fts.h>
3661d06d6bSBaptiste Daroussin #else
3761d06d6bSBaptiste Daroussin #include "compat_fts.h"
3861d06d6bSBaptiste Daroussin #endif
3961d06d6bSBaptiste Daroussin #include <limits.h>
4061d06d6bSBaptiste Daroussin #if HAVE_SANDBOX_INIT
4161d06d6bSBaptiste Daroussin #include <sandbox.h>
4261d06d6bSBaptiste Daroussin #endif
4361d06d6bSBaptiste Daroussin #include <stdarg.h>
4461d06d6bSBaptiste Daroussin #include <stddef.h>
4561d06d6bSBaptiste Daroussin #include <stdio.h>
4661d06d6bSBaptiste Daroussin #include <stdint.h>
4761d06d6bSBaptiste Daroussin #include <stdlib.h>
4861d06d6bSBaptiste Daroussin #include <string.h>
4961d06d6bSBaptiste Daroussin #include <unistd.h>
5061d06d6bSBaptiste Daroussin 
5161d06d6bSBaptiste Daroussin #include "mandoc_aux.h"
5261d06d6bSBaptiste Daroussin #include "mandoc_ohash.h"
5361d06d6bSBaptiste Daroussin #include "mandoc.h"
5461d06d6bSBaptiste Daroussin #include "roff.h"
5561d06d6bSBaptiste Daroussin #include "mdoc.h"
5661d06d6bSBaptiste Daroussin #include "man.h"
577295610fSBaptiste Daroussin #include "mandoc_parse.h"
5861d06d6bSBaptiste Daroussin #include "manconf.h"
5961d06d6bSBaptiste Daroussin #include "mansearch.h"
6061d06d6bSBaptiste Daroussin #include "dba_array.h"
6161d06d6bSBaptiste Daroussin #include "dba.h"
6261d06d6bSBaptiste Daroussin 
6361d06d6bSBaptiste Daroussin extern const char *const mansearch_keynames[];
6461d06d6bSBaptiste Daroussin 
6561d06d6bSBaptiste Daroussin enum	op {
6661d06d6bSBaptiste Daroussin 	OP_DEFAULT = 0, /* new dbs from dir list or default config */
6761d06d6bSBaptiste Daroussin 	OP_CONFFILE, /* new databases from custom config file */
6861d06d6bSBaptiste Daroussin 	OP_UPDATE, /* delete/add entries in existing database */
6961d06d6bSBaptiste Daroussin 	OP_DELETE, /* delete entries from existing database */
7061d06d6bSBaptiste Daroussin 	OP_TEST /* change no databases, report potential problems */
7161d06d6bSBaptiste Daroussin };
7261d06d6bSBaptiste Daroussin 
7361d06d6bSBaptiste Daroussin struct	str {
7461d06d6bSBaptiste Daroussin 	const struct mpage *mpage; /* if set, the owning parse */
7561d06d6bSBaptiste Daroussin 	uint64_t	 mask; /* bitmask in sequence */
7661d06d6bSBaptiste Daroussin 	char		 key[]; /* rendered text */
7761d06d6bSBaptiste Daroussin };
7861d06d6bSBaptiste Daroussin 
7961d06d6bSBaptiste Daroussin struct	inodev {
8061d06d6bSBaptiste Daroussin 	ino_t		 st_ino;
8161d06d6bSBaptiste Daroussin 	dev_t		 st_dev;
8261d06d6bSBaptiste Daroussin };
8361d06d6bSBaptiste Daroussin 
8461d06d6bSBaptiste Daroussin struct	mpage {
8561d06d6bSBaptiste Daroussin 	struct inodev	 inodev;  /* used for hashing routine */
8661d06d6bSBaptiste Daroussin 	struct dba_array *dba;
8761d06d6bSBaptiste Daroussin 	char		*sec;     /* section from file content */
8861d06d6bSBaptiste Daroussin 	char		*arch;    /* architecture from file content */
8961d06d6bSBaptiste Daroussin 	char		*title;   /* title from file content */
9061d06d6bSBaptiste Daroussin 	char		*desc;    /* description from file content */
9161d06d6bSBaptiste Daroussin 	struct mpage	*next;    /* singly linked list */
9261d06d6bSBaptiste Daroussin 	struct mlink	*mlinks;  /* singly linked list */
9361d06d6bSBaptiste Daroussin 	int		 name_head_done;
9461d06d6bSBaptiste Daroussin 	enum form	 form;    /* format from file content */
9561d06d6bSBaptiste Daroussin };
9661d06d6bSBaptiste Daroussin 
9761d06d6bSBaptiste Daroussin struct	mlink {
9861d06d6bSBaptiste Daroussin 	char		 file[PATH_MAX]; /* filename rel. to manpath */
9961d06d6bSBaptiste Daroussin 	char		*dsec;    /* section from directory */
10061d06d6bSBaptiste Daroussin 	char		*arch;    /* architecture from directory */
10161d06d6bSBaptiste Daroussin 	char		*name;    /* name from file name (not empty) */
10261d06d6bSBaptiste Daroussin 	char		*fsec;    /* section from file name suffix */
10361d06d6bSBaptiste Daroussin 	struct mlink	*next;    /* singly linked list */
10461d06d6bSBaptiste Daroussin 	struct mpage	*mpage;   /* parent */
10561d06d6bSBaptiste Daroussin 	int		 gzip;	  /* filename has a .gz suffix */
10661d06d6bSBaptiste Daroussin 	enum form	 dform;   /* format from directory */
10761d06d6bSBaptiste Daroussin 	enum form	 fform;   /* format from file name suffix */
10861d06d6bSBaptiste Daroussin };
10961d06d6bSBaptiste Daroussin 
11061d06d6bSBaptiste Daroussin typedef	int (*mdoc_fp)(struct mpage *, const struct roff_meta *,
11161d06d6bSBaptiste Daroussin 			const struct roff_node *);
11261d06d6bSBaptiste Daroussin 
11361d06d6bSBaptiste Daroussin struct	mdoc_handler {
11461d06d6bSBaptiste Daroussin 	mdoc_fp		 fp; /* optional handler */
11561d06d6bSBaptiste Daroussin 	uint64_t	 mask;  /* set unless handler returns 0 */
11661d06d6bSBaptiste Daroussin 	int		 taboo;  /* node flags that must not be set */
11761d06d6bSBaptiste Daroussin };
11861d06d6bSBaptiste Daroussin 
11961d06d6bSBaptiste Daroussin 
12061d06d6bSBaptiste Daroussin int		 mandocdb(int, char *[]);
12161d06d6bSBaptiste Daroussin 
12261d06d6bSBaptiste Daroussin static	void	 dbadd(struct dba *, struct mpage *);
1236d38604fSBaptiste Daroussin static	void	 dbadd_mlink(const struct mlink *);
12461d06d6bSBaptiste Daroussin static	void	 dbprune(struct dba *);
12561d06d6bSBaptiste Daroussin static	void	 dbwrite(struct dba *);
12661d06d6bSBaptiste Daroussin static	void	 filescan(const char *);
12761d06d6bSBaptiste Daroussin #if HAVE_FTS_COMPARE_CONST
12861d06d6bSBaptiste Daroussin static	int	 fts_compare(const FTSENT *const *, const FTSENT *const *);
12961d06d6bSBaptiste Daroussin #else
13061d06d6bSBaptiste Daroussin static	int	 fts_compare(const FTSENT **, const FTSENT **);
13161d06d6bSBaptiste Daroussin #endif
13261d06d6bSBaptiste Daroussin static	void	 mlink_add(struct mlink *, const struct stat *);
13361d06d6bSBaptiste Daroussin static	void	 mlink_check(struct mpage *, struct mlink *);
13461d06d6bSBaptiste Daroussin static	void	 mlink_free(struct mlink *);
13561d06d6bSBaptiste Daroussin static	void	 mlinks_undupe(struct mpage *);
13661d06d6bSBaptiste Daroussin static	void	 mpages_free(void);
13761d06d6bSBaptiste Daroussin static	void	 mpages_merge(struct dba *, struct mparse *);
13861d06d6bSBaptiste Daroussin static	void	 parse_cat(struct mpage *, int);
13961d06d6bSBaptiste Daroussin static	void	 parse_man(struct mpage *, const struct roff_meta *,
14061d06d6bSBaptiste Daroussin 			const struct roff_node *);
14161d06d6bSBaptiste Daroussin static	void	 parse_mdoc(struct mpage *, const struct roff_meta *,
14261d06d6bSBaptiste Daroussin 			const struct roff_node *);
14361d06d6bSBaptiste Daroussin static	int	 parse_mdoc_head(struct mpage *, const struct roff_meta *,
14461d06d6bSBaptiste Daroussin 			const struct roff_node *);
14561d06d6bSBaptiste Daroussin static	int	 parse_mdoc_Fa(struct mpage *, const struct roff_meta *,
14661d06d6bSBaptiste Daroussin 			const struct roff_node *);
14761d06d6bSBaptiste Daroussin static	int	 parse_mdoc_Fd(struct mpage *, const struct roff_meta *,
14861d06d6bSBaptiste Daroussin 			const struct roff_node *);
14961d06d6bSBaptiste Daroussin static	void	 parse_mdoc_fname(struct mpage *, const struct roff_node *);
15061d06d6bSBaptiste Daroussin static	int	 parse_mdoc_Fn(struct mpage *, const struct roff_meta *,
15161d06d6bSBaptiste Daroussin 			const struct roff_node *);
15261d06d6bSBaptiste Daroussin static	int	 parse_mdoc_Fo(struct mpage *, const struct roff_meta *,
15361d06d6bSBaptiste Daroussin 			const struct roff_node *);
15461d06d6bSBaptiste Daroussin static	int	 parse_mdoc_Nd(struct mpage *, const struct roff_meta *,
15561d06d6bSBaptiste Daroussin 			const struct roff_node *);
15661d06d6bSBaptiste Daroussin static	int	 parse_mdoc_Nm(struct mpage *, const struct roff_meta *,
15761d06d6bSBaptiste Daroussin 			const struct roff_node *);
15861d06d6bSBaptiste Daroussin static	int	 parse_mdoc_Sh(struct mpage *, const struct roff_meta *,
15961d06d6bSBaptiste Daroussin 			const struct roff_node *);
16061d06d6bSBaptiste Daroussin static	int	 parse_mdoc_Va(struct mpage *, const struct roff_meta *,
16161d06d6bSBaptiste Daroussin 			const struct roff_node *);
16261d06d6bSBaptiste Daroussin static	int	 parse_mdoc_Xr(struct mpage *, const struct roff_meta *,
16361d06d6bSBaptiste Daroussin 			const struct roff_node *);
16461d06d6bSBaptiste Daroussin static	void	 putkey(const struct mpage *, char *, uint64_t);
16561d06d6bSBaptiste Daroussin static	void	 putkeys(const struct mpage *, char *, size_t, uint64_t);
16661d06d6bSBaptiste Daroussin static	void	 putmdockey(const struct mpage *,
16761d06d6bSBaptiste Daroussin 			const struct roff_node *, uint64_t, int);
1686d38604fSBaptiste Daroussin #ifdef READ_ALLOWED_PATH
1696d38604fSBaptiste Daroussin static	int	 read_allowed(const char *);
1706d38604fSBaptiste Daroussin #endif
17161d06d6bSBaptiste Daroussin static	int	 render_string(char **, size_t *);
17261d06d6bSBaptiste Daroussin static	void	 say(const char *, const char *, ...)
17361d06d6bSBaptiste Daroussin 			__attribute__((__format__ (__printf__, 2, 3)));
17461d06d6bSBaptiste Daroussin static	int	 set_basedir(const char *, int);
17561d06d6bSBaptiste Daroussin static	int	 treescan(void);
176*c1c95addSBrooks Davis static	size_t	 utf8(unsigned int, char[5]);
17761d06d6bSBaptiste Daroussin 
17861d06d6bSBaptiste Daroussin static	int		 nodb; /* no database changes */
17961d06d6bSBaptiste Daroussin static	int		 mparse_options; /* abort the parse early */
18061d06d6bSBaptiste Daroussin static	int		 use_all; /* use all found files */
18161d06d6bSBaptiste Daroussin static	int		 debug; /* print what we're doing */
18261d06d6bSBaptiste Daroussin static	int		 warnings; /* warn about crap */
18361d06d6bSBaptiste Daroussin static	int		 write_utf8; /* write UTF-8 output; else ASCII */
18461d06d6bSBaptiste Daroussin static	int		 exitcode; /* to be returned by main */
18561d06d6bSBaptiste Daroussin static	enum op		 op; /* operational mode */
18661d06d6bSBaptiste Daroussin static	char		 basedir[PATH_MAX]; /* current base directory */
1876d38604fSBaptiste Daroussin static	size_t		 basedir_len; /* strlen(basedir) */
18861d06d6bSBaptiste Daroussin static	struct mpage	*mpage_head; /* list of distinct manual pages */
18961d06d6bSBaptiste Daroussin static	struct ohash	 mpages; /* table of distinct manual pages */
19061d06d6bSBaptiste Daroussin static	struct ohash	 mlinks; /* table of directory entries */
19161d06d6bSBaptiste Daroussin static	struct ohash	 names; /* table of all names */
19261d06d6bSBaptiste Daroussin static	struct ohash	 strings; /* table of all strings */
19361d06d6bSBaptiste Daroussin static	uint64_t	 name_mask;
19461d06d6bSBaptiste Daroussin 
1957295610fSBaptiste Daroussin static	const struct mdoc_handler mdoc_handlers[MDOC_MAX - MDOC_Dd] = {
19661d06d6bSBaptiste Daroussin 	{ NULL, 0, NODE_NOPRT },  /* Dd */
19761d06d6bSBaptiste Daroussin 	{ NULL, 0, NODE_NOPRT },  /* Dt */
19861d06d6bSBaptiste Daroussin 	{ NULL, 0, NODE_NOPRT },  /* Os */
19961d06d6bSBaptiste Daroussin 	{ parse_mdoc_Sh, TYPE_Sh, 0 }, /* Sh */
20061d06d6bSBaptiste Daroussin 	{ parse_mdoc_head, TYPE_Ss, 0 }, /* Ss */
20161d06d6bSBaptiste Daroussin 	{ NULL, 0, 0 },  /* Pp */
20261d06d6bSBaptiste Daroussin 	{ NULL, 0, 0 },  /* D1 */
20361d06d6bSBaptiste Daroussin 	{ NULL, 0, 0 },  /* Dl */
20461d06d6bSBaptiste Daroussin 	{ NULL, 0, 0 },  /* Bd */
20561d06d6bSBaptiste Daroussin 	{ NULL, 0, 0 },  /* Ed */
20661d06d6bSBaptiste Daroussin 	{ NULL, 0, 0 },  /* Bl */
20761d06d6bSBaptiste Daroussin 	{ NULL, 0, 0 },  /* El */
20861d06d6bSBaptiste Daroussin 	{ NULL, 0, 0 },  /* It */
20961d06d6bSBaptiste Daroussin 	{ NULL, 0, 0 },  /* Ad */
21061d06d6bSBaptiste Daroussin 	{ NULL, TYPE_An, 0 },  /* An */
21161d06d6bSBaptiste Daroussin 	{ NULL, 0, 0 },  /* Ap */
21261d06d6bSBaptiste Daroussin 	{ NULL, TYPE_Ar, 0 },  /* Ar */
21361d06d6bSBaptiste Daroussin 	{ NULL, TYPE_Cd, 0 },  /* Cd */
21461d06d6bSBaptiste Daroussin 	{ NULL, TYPE_Cm, 0 },  /* Cm */
21561d06d6bSBaptiste Daroussin 	{ NULL, TYPE_Dv, 0 },  /* Dv */
21661d06d6bSBaptiste Daroussin 	{ NULL, TYPE_Er, 0 },  /* Er */
21761d06d6bSBaptiste Daroussin 	{ NULL, TYPE_Ev, 0 },  /* Ev */
21861d06d6bSBaptiste Daroussin 	{ NULL, 0, 0 },  /* Ex */
21961d06d6bSBaptiste Daroussin 	{ parse_mdoc_Fa, 0, 0 },  /* Fa */
22061d06d6bSBaptiste Daroussin 	{ parse_mdoc_Fd, 0, 0 },  /* Fd */
22161d06d6bSBaptiste Daroussin 	{ NULL, TYPE_Fl, 0 },  /* Fl */
22261d06d6bSBaptiste Daroussin 	{ parse_mdoc_Fn, 0, 0 },  /* Fn */
22361d06d6bSBaptiste Daroussin 	{ NULL, TYPE_Ft | TYPE_Vt, 0 },  /* Ft */
22461d06d6bSBaptiste Daroussin 	{ NULL, TYPE_Ic, 0 },  /* Ic */
22561d06d6bSBaptiste Daroussin 	{ NULL, TYPE_In, 0 },  /* In */
22661d06d6bSBaptiste Daroussin 	{ NULL, TYPE_Li, 0 },  /* Li */
22761d06d6bSBaptiste Daroussin 	{ parse_mdoc_Nd, 0, 0 },  /* Nd */
22861d06d6bSBaptiste Daroussin 	{ parse_mdoc_Nm, 0, 0 },  /* Nm */
22961d06d6bSBaptiste Daroussin 	{ NULL, 0, 0 },  /* Op */
23061d06d6bSBaptiste Daroussin 	{ NULL, 0, 0 },  /* Ot */
23161d06d6bSBaptiste Daroussin 	{ NULL, TYPE_Pa, NODE_NOSRC },  /* Pa */
23261d06d6bSBaptiste Daroussin 	{ NULL, 0, 0 },  /* Rv */
23361d06d6bSBaptiste Daroussin 	{ NULL, TYPE_St, 0 },  /* St */
23461d06d6bSBaptiste Daroussin 	{ parse_mdoc_Va, TYPE_Va, 0 },  /* Va */
23561d06d6bSBaptiste Daroussin 	{ parse_mdoc_Va, TYPE_Vt, 0 },  /* Vt */
23661d06d6bSBaptiste Daroussin 	{ parse_mdoc_Xr, 0, 0 },  /* Xr */
23761d06d6bSBaptiste Daroussin 	{ NULL, 0, 0 },  /* %A */
23861d06d6bSBaptiste Daroussin 	{ NULL, 0, 0 },  /* %B */
23961d06d6bSBaptiste Daroussin 	{ NULL, 0, 0 },  /* %D */
24061d06d6bSBaptiste Daroussin 	{ NULL, 0, 0 },  /* %I */
24161d06d6bSBaptiste Daroussin 	{ NULL, 0, 0 },  /* %J */
24261d06d6bSBaptiste Daroussin 	{ NULL, 0, 0 },  /* %N */
24361d06d6bSBaptiste Daroussin 	{ NULL, 0, 0 },  /* %O */
24461d06d6bSBaptiste Daroussin 	{ NULL, 0, 0 },  /* %P */
24561d06d6bSBaptiste Daroussin 	{ NULL, 0, 0 },  /* %R */
24661d06d6bSBaptiste Daroussin 	{ NULL, 0, 0 },  /* %T */
24761d06d6bSBaptiste Daroussin 	{ NULL, 0, 0 },  /* %V */
24861d06d6bSBaptiste Daroussin 	{ NULL, 0, 0 },  /* Ac */
24961d06d6bSBaptiste Daroussin 	{ NULL, 0, 0 },  /* Ao */
25061d06d6bSBaptiste Daroussin 	{ NULL, 0, 0 },  /* Aq */
25161d06d6bSBaptiste Daroussin 	{ NULL, TYPE_At, 0 },  /* At */
25261d06d6bSBaptiste Daroussin 	{ NULL, 0, 0 },  /* Bc */
25361d06d6bSBaptiste Daroussin 	{ NULL, 0, 0 },  /* Bf */
25461d06d6bSBaptiste Daroussin 	{ NULL, 0, 0 },  /* Bo */
25561d06d6bSBaptiste Daroussin 	{ NULL, 0, 0 },  /* Bq */
25661d06d6bSBaptiste Daroussin 	{ NULL, TYPE_Bsx, NODE_NOSRC },  /* Bsx */
25761d06d6bSBaptiste Daroussin 	{ NULL, TYPE_Bx, NODE_NOSRC },  /* Bx */
25861d06d6bSBaptiste Daroussin 	{ NULL, 0, 0 },  /* Db */
25961d06d6bSBaptiste Daroussin 	{ NULL, 0, 0 },  /* Dc */
26061d06d6bSBaptiste Daroussin 	{ NULL, 0, 0 },  /* Do */
26161d06d6bSBaptiste Daroussin 	{ NULL, 0, 0 },  /* Dq */
26261d06d6bSBaptiste Daroussin 	{ NULL, 0, 0 },  /* Ec */
26361d06d6bSBaptiste Daroussin 	{ NULL, 0, 0 },  /* Ef */
26461d06d6bSBaptiste Daroussin 	{ NULL, TYPE_Em, 0 },  /* Em */
26561d06d6bSBaptiste Daroussin 	{ NULL, 0, 0 },  /* Eo */
26661d06d6bSBaptiste Daroussin 	{ NULL, TYPE_Fx, NODE_NOSRC },  /* Fx */
26761d06d6bSBaptiste Daroussin 	{ NULL, TYPE_Ms, 0 },  /* Ms */
26861d06d6bSBaptiste Daroussin 	{ NULL, 0, 0 },  /* No */
26961d06d6bSBaptiste Daroussin 	{ NULL, 0, 0 },  /* Ns */
27061d06d6bSBaptiste Daroussin 	{ NULL, TYPE_Nx, NODE_NOSRC },  /* Nx */
27161d06d6bSBaptiste Daroussin 	{ NULL, TYPE_Ox, NODE_NOSRC },  /* Ox */
27261d06d6bSBaptiste Daroussin 	{ NULL, 0, 0 },  /* Pc */
27361d06d6bSBaptiste Daroussin 	{ NULL, 0, 0 },  /* Pf */
27461d06d6bSBaptiste Daroussin 	{ NULL, 0, 0 },  /* Po */
27561d06d6bSBaptiste Daroussin 	{ NULL, 0, 0 },  /* Pq */
27661d06d6bSBaptiste Daroussin 	{ NULL, 0, 0 },  /* Qc */
27761d06d6bSBaptiste Daroussin 	{ NULL, 0, 0 },  /* Ql */
27861d06d6bSBaptiste Daroussin 	{ NULL, 0, 0 },  /* Qo */
27961d06d6bSBaptiste Daroussin 	{ NULL, 0, 0 },  /* Qq */
28061d06d6bSBaptiste Daroussin 	{ NULL, 0, 0 },  /* Re */
28161d06d6bSBaptiste Daroussin 	{ NULL, 0, 0 },  /* Rs */
28261d06d6bSBaptiste Daroussin 	{ NULL, 0, 0 },  /* Sc */
28361d06d6bSBaptiste Daroussin 	{ NULL, 0, 0 },  /* So */
28461d06d6bSBaptiste Daroussin 	{ NULL, 0, 0 },  /* Sq */
28561d06d6bSBaptiste Daroussin 	{ NULL, 0, 0 },  /* Sm */
28661d06d6bSBaptiste Daroussin 	{ NULL, 0, 0 },  /* Sx */
28761d06d6bSBaptiste Daroussin 	{ NULL, TYPE_Sy, 0 },  /* Sy */
28861d06d6bSBaptiste Daroussin 	{ NULL, TYPE_Tn, 0 },  /* Tn */
28961d06d6bSBaptiste Daroussin 	{ NULL, 0, NODE_NOSRC },  /* Ux */
29061d06d6bSBaptiste Daroussin 	{ NULL, 0, 0 },  /* Xc */
29161d06d6bSBaptiste Daroussin 	{ NULL, 0, 0 },  /* Xo */
29261d06d6bSBaptiste Daroussin 	{ parse_mdoc_Fo, 0, 0 },  /* Fo */
29361d06d6bSBaptiste Daroussin 	{ NULL, 0, 0 },  /* Fc */
29461d06d6bSBaptiste Daroussin 	{ NULL, 0, 0 },  /* Oo */
29561d06d6bSBaptiste Daroussin 	{ NULL, 0, 0 },  /* Oc */
29661d06d6bSBaptiste Daroussin 	{ NULL, 0, 0 },  /* Bk */
29761d06d6bSBaptiste Daroussin 	{ NULL, 0, 0 },  /* Ek */
29861d06d6bSBaptiste Daroussin 	{ NULL, 0, 0 },  /* Bt */
29961d06d6bSBaptiste Daroussin 	{ NULL, 0, 0 },  /* Hf */
30061d06d6bSBaptiste Daroussin 	{ NULL, 0, 0 },  /* Fr */
30161d06d6bSBaptiste Daroussin 	{ NULL, 0, 0 },  /* Ud */
30261d06d6bSBaptiste Daroussin 	{ NULL, TYPE_Lb, NODE_NOSRC },  /* Lb */
30361d06d6bSBaptiste Daroussin 	{ NULL, 0, 0 },  /* Lp */
30461d06d6bSBaptiste Daroussin 	{ NULL, TYPE_Lk, 0 },  /* Lk */
30561d06d6bSBaptiste Daroussin 	{ NULL, TYPE_Mt, NODE_NOSRC },  /* Mt */
30661d06d6bSBaptiste Daroussin 	{ NULL, 0, 0 },  /* Brq */
30761d06d6bSBaptiste Daroussin 	{ NULL, 0, 0 },  /* Bro */
30861d06d6bSBaptiste Daroussin 	{ NULL, 0, 0 },  /* Brc */
30961d06d6bSBaptiste Daroussin 	{ NULL, 0, 0 },  /* %C */
31061d06d6bSBaptiste Daroussin 	{ NULL, 0, 0 },  /* Es */
31161d06d6bSBaptiste Daroussin 	{ NULL, 0, 0 },  /* En */
31261d06d6bSBaptiste Daroussin 	{ NULL, TYPE_Dx, NODE_NOSRC },  /* Dx */
31361d06d6bSBaptiste Daroussin 	{ NULL, 0, 0 },  /* %Q */
31461d06d6bSBaptiste Daroussin 	{ NULL, 0, 0 },  /* %U */
31561d06d6bSBaptiste Daroussin 	{ NULL, 0, 0 },  /* Ta */
31661d06d6bSBaptiste Daroussin };
31761d06d6bSBaptiste Daroussin 
31861d06d6bSBaptiste Daroussin 
31961d06d6bSBaptiste Daroussin int
32061d06d6bSBaptiste Daroussin mandocdb(int argc, char *argv[])
32161d06d6bSBaptiste Daroussin {
32261d06d6bSBaptiste Daroussin 	struct manconf	  conf;
32361d06d6bSBaptiste Daroussin 	struct mparse	 *mp;
32461d06d6bSBaptiste Daroussin 	struct dba	 *dba;
32561d06d6bSBaptiste Daroussin 	const char	 *path_arg, *progname;
32661d06d6bSBaptiste Daroussin 	size_t		  j, sz;
32761d06d6bSBaptiste Daroussin 	int		  ch, i;
32861d06d6bSBaptiste Daroussin 
32961d06d6bSBaptiste Daroussin #if HAVE_PLEDGE
33061d06d6bSBaptiste Daroussin 	if (pledge("stdio rpath wpath cpath", NULL) == -1) {
33161d06d6bSBaptiste Daroussin 		warn("pledge");
33261d06d6bSBaptiste Daroussin 		return (int)MANDOCLEVEL_SYSERR;
33361d06d6bSBaptiste Daroussin 	}
33461d06d6bSBaptiste Daroussin #endif
33561d06d6bSBaptiste Daroussin 
33661d06d6bSBaptiste Daroussin #if HAVE_SANDBOX_INIT
33761d06d6bSBaptiste Daroussin 	if (sandbox_init(kSBXProfileNoInternet, SANDBOX_NAMED, NULL) == -1) {
33861d06d6bSBaptiste Daroussin 		warnx("sandbox_init");
33961d06d6bSBaptiste Daroussin 		return (int)MANDOCLEVEL_SYSERR;
34061d06d6bSBaptiste Daroussin 	}
34161d06d6bSBaptiste Daroussin #endif
34261d06d6bSBaptiste Daroussin 
34361d06d6bSBaptiste Daroussin 	memset(&conf, 0, sizeof(conf));
34461d06d6bSBaptiste Daroussin 
34561d06d6bSBaptiste Daroussin 	/*
34661d06d6bSBaptiste Daroussin 	 * We accept a few different invocations.
34761d06d6bSBaptiste Daroussin 	 * The CHECKOP macro makes sure that invocation styles don't
34861d06d6bSBaptiste Daroussin 	 * clobber each other.
34961d06d6bSBaptiste Daroussin 	 */
35061d06d6bSBaptiste Daroussin #define	CHECKOP(_op, _ch) do \
3516d38604fSBaptiste Daroussin 	if ((_op) != OP_DEFAULT) { \
35261d06d6bSBaptiste Daroussin 		warnx("-%c: Conflicting option", (_ch)); \
35361d06d6bSBaptiste Daroussin 		goto usage; \
35461d06d6bSBaptiste Daroussin 	} while (/*CONSTCOND*/0)
35561d06d6bSBaptiste Daroussin 
356*c1c95addSBrooks Davis 	mparse_options = MPARSE_UTF8 | MPARSE_LATIN1 | MPARSE_VALIDATE;
35761d06d6bSBaptiste Daroussin 	path_arg = NULL;
35861d06d6bSBaptiste Daroussin 	op = OP_DEFAULT;
35961d06d6bSBaptiste Daroussin 
3606d38604fSBaptiste Daroussin 	while ((ch = getopt(argc, argv, "aC:Dd:npQT:tu:v")) != -1)
36161d06d6bSBaptiste Daroussin 		switch (ch) {
36261d06d6bSBaptiste Daroussin 		case 'a':
36361d06d6bSBaptiste Daroussin 			use_all = 1;
36461d06d6bSBaptiste Daroussin 			break;
36561d06d6bSBaptiste Daroussin 		case 'C':
36661d06d6bSBaptiste Daroussin 			CHECKOP(op, ch);
36761d06d6bSBaptiste Daroussin 			path_arg = optarg;
36861d06d6bSBaptiste Daroussin 			op = OP_CONFFILE;
36961d06d6bSBaptiste Daroussin 			break;
37061d06d6bSBaptiste Daroussin 		case 'D':
37161d06d6bSBaptiste Daroussin 			debug++;
37261d06d6bSBaptiste Daroussin 			break;
37361d06d6bSBaptiste Daroussin 		case 'd':
37461d06d6bSBaptiste Daroussin 			CHECKOP(op, ch);
37561d06d6bSBaptiste Daroussin 			path_arg = optarg;
37661d06d6bSBaptiste Daroussin 			op = OP_UPDATE;
37761d06d6bSBaptiste Daroussin 			break;
37861d06d6bSBaptiste Daroussin 		case 'n':
37961d06d6bSBaptiste Daroussin 			nodb = 1;
38061d06d6bSBaptiste Daroussin 			break;
38161d06d6bSBaptiste Daroussin 		case 'p':
38261d06d6bSBaptiste Daroussin 			warnings = 1;
38361d06d6bSBaptiste Daroussin 			break;
38461d06d6bSBaptiste Daroussin 		case 'Q':
38561d06d6bSBaptiste Daroussin 			mparse_options |= MPARSE_QUICK;
38661d06d6bSBaptiste Daroussin 			break;
38761d06d6bSBaptiste Daroussin 		case 'T':
3886d38604fSBaptiste Daroussin 			if (strcmp(optarg, "utf8") != 0) {
38961d06d6bSBaptiste Daroussin 				warnx("-T%s: Unsupported output format",
39061d06d6bSBaptiste Daroussin 				    optarg);
39161d06d6bSBaptiste Daroussin 				goto usage;
39261d06d6bSBaptiste Daroussin 			}
39361d06d6bSBaptiste Daroussin 			write_utf8 = 1;
39461d06d6bSBaptiste Daroussin 			break;
39561d06d6bSBaptiste Daroussin 		case 't':
39661d06d6bSBaptiste Daroussin 			CHECKOP(op, ch);
39761d06d6bSBaptiste Daroussin 			dup2(STDOUT_FILENO, STDERR_FILENO);
39861d06d6bSBaptiste Daroussin 			op = OP_TEST;
39961d06d6bSBaptiste Daroussin 			nodb = warnings = 1;
40061d06d6bSBaptiste Daroussin 			break;
40161d06d6bSBaptiste Daroussin 		case 'u':
40261d06d6bSBaptiste Daroussin 			CHECKOP(op, ch);
40361d06d6bSBaptiste Daroussin 			path_arg = optarg;
40461d06d6bSBaptiste Daroussin 			op = OP_DELETE;
40561d06d6bSBaptiste Daroussin 			break;
40661d06d6bSBaptiste Daroussin 		case 'v':
40761d06d6bSBaptiste Daroussin 			/* Compatibility with espie@'s makewhatis. */
40861d06d6bSBaptiste Daroussin 			break;
40961d06d6bSBaptiste Daroussin 		default:
41061d06d6bSBaptiste Daroussin 			goto usage;
41161d06d6bSBaptiste Daroussin 		}
41261d06d6bSBaptiste Daroussin 
41361d06d6bSBaptiste Daroussin 	argc -= optind;
41461d06d6bSBaptiste Daroussin 	argv += optind;
41561d06d6bSBaptiste Daroussin 
41661d06d6bSBaptiste Daroussin #if HAVE_PLEDGE
41761d06d6bSBaptiste Daroussin 	if (nodb) {
41861d06d6bSBaptiste Daroussin 		if (pledge("stdio rpath", NULL) == -1) {
41961d06d6bSBaptiste Daroussin 			warn("pledge");
42061d06d6bSBaptiste Daroussin 			return (int)MANDOCLEVEL_SYSERR;
42161d06d6bSBaptiste Daroussin 		}
42261d06d6bSBaptiste Daroussin 	}
42361d06d6bSBaptiste Daroussin #endif
42461d06d6bSBaptiste Daroussin 
4256d38604fSBaptiste Daroussin 	if (op == OP_CONFFILE && argc > 0) {
42661d06d6bSBaptiste Daroussin 		warnx("-C: Too many arguments");
42761d06d6bSBaptiste Daroussin 		goto usage;
42861d06d6bSBaptiste Daroussin 	}
42961d06d6bSBaptiste Daroussin 
43061d06d6bSBaptiste Daroussin 	exitcode = (int)MANDOCLEVEL_OK;
43161d06d6bSBaptiste Daroussin 	mchars_alloc();
4327295610fSBaptiste Daroussin 	mp = mparse_alloc(mparse_options, MANDOC_OS_OTHER, NULL);
43361d06d6bSBaptiste Daroussin 	mandoc_ohash_init(&mpages, 6, offsetof(struct mpage, inodev));
43461d06d6bSBaptiste Daroussin 	mandoc_ohash_init(&mlinks, 6, offsetof(struct mlink, file));
43561d06d6bSBaptiste Daroussin 
4366d38604fSBaptiste Daroussin 	if (op == OP_UPDATE || op == OP_DELETE || op == OP_TEST) {
43761d06d6bSBaptiste Daroussin 
43861d06d6bSBaptiste Daroussin 		/*
43961d06d6bSBaptiste Daroussin 		 * Most of these deal with a specific directory.
44061d06d6bSBaptiste Daroussin 		 * Jump into that directory first.
44161d06d6bSBaptiste Daroussin 		 */
4426d38604fSBaptiste Daroussin 		if (op != OP_TEST && set_basedir(path_arg, 1) == 0)
44361d06d6bSBaptiste Daroussin 			goto out;
44461d06d6bSBaptiste Daroussin 
44561d06d6bSBaptiste Daroussin 		dba = nodb ? dba_new(128) : dba_read(MANDOC_DB);
44661d06d6bSBaptiste Daroussin 		if (dba != NULL) {
44761d06d6bSBaptiste Daroussin 			/*
44861d06d6bSBaptiste Daroussin 			 * The existing database is usable.  Process
44961d06d6bSBaptiste Daroussin 			 * all files specified on the command-line.
45061d06d6bSBaptiste Daroussin 			 */
45161d06d6bSBaptiste Daroussin 			use_all = 1;
45261d06d6bSBaptiste Daroussin 			for (i = 0; i < argc; i++)
45361d06d6bSBaptiste Daroussin 				filescan(argv[i]);
45461d06d6bSBaptiste Daroussin 			if (nodb == 0)
45561d06d6bSBaptiste Daroussin 				dbprune(dba);
45661d06d6bSBaptiste Daroussin 		} else {
45761d06d6bSBaptiste Daroussin 			/* Database missing or corrupt. */
45861d06d6bSBaptiste Daroussin 			if (op != OP_UPDATE || errno != ENOENT)
45961d06d6bSBaptiste Daroussin 				say(MANDOC_DB, "%s: Automatically recreating"
46061d06d6bSBaptiste Daroussin 				    " from scratch", strerror(errno));
46161d06d6bSBaptiste Daroussin 			exitcode = (int)MANDOCLEVEL_OK;
46261d06d6bSBaptiste Daroussin 			op = OP_DEFAULT;
4636d38604fSBaptiste Daroussin 			if (treescan() == 0)
46461d06d6bSBaptiste Daroussin 				goto out;
46561d06d6bSBaptiste Daroussin 			dba = dba_new(128);
46661d06d6bSBaptiste Daroussin 		}
4676d38604fSBaptiste Daroussin 		if (op != OP_DELETE)
46861d06d6bSBaptiste Daroussin 			mpages_merge(dba, mp);
46961d06d6bSBaptiste Daroussin 		if (nodb == 0)
47061d06d6bSBaptiste Daroussin 			dbwrite(dba);
47161d06d6bSBaptiste Daroussin 		dba_free(dba);
47261d06d6bSBaptiste Daroussin 	} else {
47361d06d6bSBaptiste Daroussin 		/*
47461d06d6bSBaptiste Daroussin 		 * If we have arguments, use them as our manpaths.
47561d06d6bSBaptiste Daroussin 		 * If we don't, use man.conf(5).
47661d06d6bSBaptiste Daroussin 		 */
47761d06d6bSBaptiste Daroussin 		if (argc > 0) {
47861d06d6bSBaptiste Daroussin 			conf.manpath.paths = mandoc_reallocarray(NULL,
47961d06d6bSBaptiste Daroussin 			    argc, sizeof(char *));
48061d06d6bSBaptiste Daroussin 			conf.manpath.sz = (size_t)argc;
48161d06d6bSBaptiste Daroussin 			for (i = 0; i < argc; i++)
48261d06d6bSBaptiste Daroussin 				conf.manpath.paths[i] = mandoc_strdup(argv[i]);
48361d06d6bSBaptiste Daroussin 		} else
48461d06d6bSBaptiste Daroussin 			manconf_parse(&conf, path_arg, NULL, NULL);
48561d06d6bSBaptiste Daroussin 
48661d06d6bSBaptiste Daroussin 		if (conf.manpath.sz == 0) {
48761d06d6bSBaptiste Daroussin 			exitcode = (int)MANDOCLEVEL_BADARG;
48861d06d6bSBaptiste Daroussin 			say("", "Empty manpath");
48961d06d6bSBaptiste Daroussin 		}
49061d06d6bSBaptiste Daroussin 
49161d06d6bSBaptiste Daroussin 		/*
49261d06d6bSBaptiste Daroussin 		 * First scan the tree rooted at a base directory, then
49361d06d6bSBaptiste Daroussin 		 * build a new database and finally move it into place.
49461d06d6bSBaptiste Daroussin 		 * Ignore zero-length directories and strip trailing
49561d06d6bSBaptiste Daroussin 		 * slashes.
49661d06d6bSBaptiste Daroussin 		 */
49761d06d6bSBaptiste Daroussin 		for (j = 0; j < conf.manpath.sz; j++) {
49861d06d6bSBaptiste Daroussin 			sz = strlen(conf.manpath.paths[j]);
49961d06d6bSBaptiste Daroussin 			if (sz && conf.manpath.paths[j][sz - 1] == '/')
50061d06d6bSBaptiste Daroussin 				conf.manpath.paths[j][--sz] = '\0';
5016d38604fSBaptiste Daroussin 			if (sz == 0)
50261d06d6bSBaptiste Daroussin 				continue;
50361d06d6bSBaptiste Daroussin 
50461d06d6bSBaptiste Daroussin 			if (j) {
50561d06d6bSBaptiste Daroussin 				mandoc_ohash_init(&mpages, 6,
50661d06d6bSBaptiste Daroussin 				    offsetof(struct mpage, inodev));
50761d06d6bSBaptiste Daroussin 				mandoc_ohash_init(&mlinks, 6,
50861d06d6bSBaptiste Daroussin 				    offsetof(struct mlink, file));
50961d06d6bSBaptiste Daroussin 			}
51061d06d6bSBaptiste Daroussin 
5116d38604fSBaptiste Daroussin 			if (set_basedir(conf.manpath.paths[j], argc > 0) == 0)
51261d06d6bSBaptiste Daroussin 				continue;
5136d38604fSBaptiste Daroussin 			if (treescan() == 0)
51461d06d6bSBaptiste Daroussin 				continue;
51561d06d6bSBaptiste Daroussin 			dba = dba_new(128);
51661d06d6bSBaptiste Daroussin 			mpages_merge(dba, mp);
51761d06d6bSBaptiste Daroussin 			if (nodb == 0)
51861d06d6bSBaptiste Daroussin 				dbwrite(dba);
51961d06d6bSBaptiste Daroussin 			dba_free(dba);
52061d06d6bSBaptiste Daroussin 
52161d06d6bSBaptiste Daroussin 			if (j + 1 < conf.manpath.sz) {
52261d06d6bSBaptiste Daroussin 				mpages_free();
52361d06d6bSBaptiste Daroussin 				ohash_delete(&mpages);
52461d06d6bSBaptiste Daroussin 				ohash_delete(&mlinks);
52561d06d6bSBaptiste Daroussin 			}
52661d06d6bSBaptiste Daroussin 		}
52761d06d6bSBaptiste Daroussin 	}
52861d06d6bSBaptiste Daroussin out:
52961d06d6bSBaptiste Daroussin 	manconf_free(&conf);
53061d06d6bSBaptiste Daroussin 	mparse_free(mp);
53161d06d6bSBaptiste Daroussin 	mchars_free();
53261d06d6bSBaptiste Daroussin 	mpages_free();
53361d06d6bSBaptiste Daroussin 	ohash_delete(&mpages);
53461d06d6bSBaptiste Daroussin 	ohash_delete(&mlinks);
535*c1c95addSBrooks Davis #if DEBUG_MEMORY
536*c1c95addSBrooks Davis 	mandoc_dbg_finish();
537*c1c95addSBrooks Davis #endif
53861d06d6bSBaptiste Daroussin 	return exitcode;
53961d06d6bSBaptiste Daroussin usage:
54061d06d6bSBaptiste Daroussin 	progname = getprogname();
54161d06d6bSBaptiste Daroussin 	fprintf(stderr, "usage: %s [-aDnpQ] [-C file] [-Tutf8]\n"
54261d06d6bSBaptiste Daroussin 			"       %s [-aDnpQ] [-Tutf8] dir ...\n"
54361d06d6bSBaptiste Daroussin 			"       %s [-DnpQ] [-Tutf8] -d dir [file ...]\n"
54461d06d6bSBaptiste Daroussin 			"       %s [-Dnp] -u dir [file ...]\n"
54561d06d6bSBaptiste Daroussin 			"       %s [-Q] -t file ...\n",
54661d06d6bSBaptiste Daroussin 		        progname, progname, progname, progname, progname);
54761d06d6bSBaptiste Daroussin 
54861d06d6bSBaptiste Daroussin 	return (int)MANDOCLEVEL_BADARG;
54961d06d6bSBaptiste Daroussin }
55061d06d6bSBaptiste Daroussin 
55161d06d6bSBaptiste Daroussin /*
55261d06d6bSBaptiste Daroussin  * To get a singly linked list in alpha order while inserting entries
55361d06d6bSBaptiste Daroussin  * at the beginning, process directory entries in reverse alpha order.
55461d06d6bSBaptiste Daroussin  */
55561d06d6bSBaptiste Daroussin static int
55661d06d6bSBaptiste Daroussin #if HAVE_FTS_COMPARE_CONST
55761d06d6bSBaptiste Daroussin fts_compare(const FTSENT *const *a, const FTSENT *const *b)
55861d06d6bSBaptiste Daroussin #else
55961d06d6bSBaptiste Daroussin fts_compare(const FTSENT **a, const FTSENT **b)
56061d06d6bSBaptiste Daroussin #endif
56161d06d6bSBaptiste Daroussin {
56261d06d6bSBaptiste Daroussin 	return -strcmp((*a)->fts_name, (*b)->fts_name);
56361d06d6bSBaptiste Daroussin }
56461d06d6bSBaptiste Daroussin 
56561d06d6bSBaptiste Daroussin /*
56661d06d6bSBaptiste Daroussin  * Scan a directory tree rooted at "basedir" for manpages.
56761d06d6bSBaptiste Daroussin  * We use fts(), scanning directory parts along the way for clues to our
56861d06d6bSBaptiste Daroussin  * section and architecture.
56961d06d6bSBaptiste Daroussin  *
57061d06d6bSBaptiste Daroussin  * If use_all has been specified, grok all files.
57161d06d6bSBaptiste Daroussin  * If not, sanitise paths to the following:
57261d06d6bSBaptiste Daroussin  *
57361d06d6bSBaptiste Daroussin  *   [./]man*[/<arch>]/<name>.<section>
57461d06d6bSBaptiste Daroussin  *   or
57561d06d6bSBaptiste Daroussin  *   [./]cat<section>[/<arch>]/<name>.0
57661d06d6bSBaptiste Daroussin  *
57761d06d6bSBaptiste Daroussin  * TODO: accommodate for multi-language directories.
57861d06d6bSBaptiste Daroussin  */
57961d06d6bSBaptiste Daroussin static int
58061d06d6bSBaptiste Daroussin treescan(void)
58161d06d6bSBaptiste Daroussin {
58261d06d6bSBaptiste Daroussin 	char		 buf[PATH_MAX];
58361d06d6bSBaptiste Daroussin 	FTS		*f;
58461d06d6bSBaptiste Daroussin 	FTSENT		*ff;
58561d06d6bSBaptiste Daroussin 	struct mlink	*mlink;
58661d06d6bSBaptiste Daroussin 	int		 gzip;
58761d06d6bSBaptiste Daroussin 	enum form	 dform;
58861d06d6bSBaptiste Daroussin 	char		*dsec, *arch, *fsec, *cp;
58961d06d6bSBaptiste Daroussin 	const char	*path;
59061d06d6bSBaptiste Daroussin 	const char	*argv[2];
59161d06d6bSBaptiste Daroussin 
59261d06d6bSBaptiste Daroussin 	argv[0] = ".";
59361d06d6bSBaptiste Daroussin 	argv[1] = NULL;
59461d06d6bSBaptiste Daroussin 
59561d06d6bSBaptiste Daroussin 	f = fts_open((char * const *)argv, FTS_PHYSICAL | FTS_NOCHDIR,
59661d06d6bSBaptiste Daroussin 	    fts_compare);
59761d06d6bSBaptiste Daroussin 	if (f == NULL) {
59861d06d6bSBaptiste Daroussin 		exitcode = (int)MANDOCLEVEL_SYSERR;
59961d06d6bSBaptiste Daroussin 		say("", "&fts_open");
60061d06d6bSBaptiste Daroussin 		return 0;
60161d06d6bSBaptiste Daroussin 	}
60261d06d6bSBaptiste Daroussin 
60361d06d6bSBaptiste Daroussin 	dsec = arch = NULL;
60461d06d6bSBaptiste Daroussin 	dform = FORM_NONE;
60561d06d6bSBaptiste Daroussin 
60661d06d6bSBaptiste Daroussin 	while ((ff = fts_read(f)) != NULL) {
60761d06d6bSBaptiste Daroussin 		path = ff->fts_path + 2;
60861d06d6bSBaptiste Daroussin 		switch (ff->fts_info) {
60961d06d6bSBaptiste Daroussin 
61061d06d6bSBaptiste Daroussin 		/*
61161d06d6bSBaptiste Daroussin 		 * Symbolic links require various sanity checks,
61261d06d6bSBaptiste Daroussin 		 * then get handled just like regular files.
61361d06d6bSBaptiste Daroussin 		 */
61461d06d6bSBaptiste Daroussin 		case FTS_SL:
61561d06d6bSBaptiste Daroussin 			if (realpath(path, buf) == NULL) {
61661d06d6bSBaptiste Daroussin 				if (warnings)
61761d06d6bSBaptiste Daroussin 					say(path, "&realpath");
61861d06d6bSBaptiste Daroussin 				continue;
61961d06d6bSBaptiste Daroussin 			}
6206d38604fSBaptiste Daroussin 			if (strncmp(buf, basedir, basedir_len) != 0
6216d38604fSBaptiste Daroussin #ifdef READ_ALLOWED_PATH
6226d38604fSBaptiste Daroussin 			    && !read_allowed(buf)
62361d06d6bSBaptiste Daroussin #endif
62461d06d6bSBaptiste Daroussin 			) {
62561d06d6bSBaptiste Daroussin 				if (warnings) say("",
62661d06d6bSBaptiste Daroussin 				    "%s: outside base directory", buf);
62761d06d6bSBaptiste Daroussin 				continue;
62861d06d6bSBaptiste Daroussin 			}
62961d06d6bSBaptiste Daroussin 			/* Use logical inode to avoid mpages dupe. */
63061d06d6bSBaptiste Daroussin 			if (stat(path, ff->fts_statp) == -1) {
63161d06d6bSBaptiste Daroussin 				if (warnings)
63261d06d6bSBaptiste Daroussin 					say(path, "&stat");
63361d06d6bSBaptiste Daroussin 				continue;
63461d06d6bSBaptiste Daroussin 			}
6356d38604fSBaptiste Daroussin 			if ((ff->fts_statp->st_mode & S_IFMT) != S_IFREG)
6366d38604fSBaptiste Daroussin 				continue;
63761d06d6bSBaptiste Daroussin 			/* FALLTHROUGH */
63861d06d6bSBaptiste Daroussin 
63961d06d6bSBaptiste Daroussin 		/*
64061d06d6bSBaptiste Daroussin 		 * If we're a regular file, add an mlink by using the
64161d06d6bSBaptiste Daroussin 		 * stored directory data and handling the filename.
64261d06d6bSBaptiste Daroussin 		 */
64361d06d6bSBaptiste Daroussin 		case FTS_F:
64461d06d6bSBaptiste Daroussin 			if ( ! strcmp(path, MANDOC_DB))
64561d06d6bSBaptiste Daroussin 				continue;
64661d06d6bSBaptiste Daroussin 			if ( ! use_all && ff->fts_level < 2) {
64761d06d6bSBaptiste Daroussin 				if (warnings)
64861d06d6bSBaptiste Daroussin 					say(path, "Extraneous file");
64961d06d6bSBaptiste Daroussin 				continue;
65061d06d6bSBaptiste Daroussin 			}
65161d06d6bSBaptiste Daroussin 			gzip = 0;
65261d06d6bSBaptiste Daroussin 			fsec = NULL;
65361d06d6bSBaptiste Daroussin 			while (fsec == NULL) {
65461d06d6bSBaptiste Daroussin 				fsec = strrchr(ff->fts_name, '.');
65561d06d6bSBaptiste Daroussin 				if (fsec == NULL || strcmp(fsec+1, "gz"))
65661d06d6bSBaptiste Daroussin 					break;
65761d06d6bSBaptiste Daroussin 				gzip = 1;
65861d06d6bSBaptiste Daroussin 				*fsec = '\0';
65961d06d6bSBaptiste Daroussin 				fsec = NULL;
66061d06d6bSBaptiste Daroussin 			}
66161d06d6bSBaptiste Daroussin 			if (fsec == NULL) {
66261d06d6bSBaptiste Daroussin 				if ( ! use_all) {
66361d06d6bSBaptiste Daroussin 					if (warnings)
66461d06d6bSBaptiste Daroussin 						say(path,
66561d06d6bSBaptiste Daroussin 						    "No filename suffix");
66661d06d6bSBaptiste Daroussin 					continue;
66761d06d6bSBaptiste Daroussin 				}
66861d06d6bSBaptiste Daroussin 			} else if ( ! strcmp(++fsec, "html")) {
66961d06d6bSBaptiste Daroussin 				if (warnings)
67061d06d6bSBaptiste Daroussin 					say(path, "Skip html");
67161d06d6bSBaptiste Daroussin 				continue;
67261d06d6bSBaptiste Daroussin 			} else if ( ! strcmp(fsec, "ps")) {
67361d06d6bSBaptiste Daroussin 				if (warnings)
67461d06d6bSBaptiste Daroussin 					say(path, "Skip ps");
67561d06d6bSBaptiste Daroussin 				continue;
67661d06d6bSBaptiste Daroussin 			} else if ( ! strcmp(fsec, "pdf")) {
67761d06d6bSBaptiste Daroussin 				if (warnings)
67861d06d6bSBaptiste Daroussin 					say(path, "Skip pdf");
67961d06d6bSBaptiste Daroussin 				continue;
68061d06d6bSBaptiste Daroussin 			} else if ( ! use_all &&
68161d06d6bSBaptiste Daroussin 			    ((dform == FORM_SRC &&
68261d06d6bSBaptiste Daroussin 			      strncmp(fsec, dsec, strlen(dsec))) ||
68361d06d6bSBaptiste Daroussin 			     (dform == FORM_CAT && strcmp(fsec, "0")))) {
68461d06d6bSBaptiste Daroussin 				if (warnings)
68561d06d6bSBaptiste Daroussin 					say(path, "Wrong filename suffix");
68661d06d6bSBaptiste Daroussin 				continue;
68761d06d6bSBaptiste Daroussin 			} else
68861d06d6bSBaptiste Daroussin 				fsec[-1] = '\0';
68961d06d6bSBaptiste Daroussin 
69061d06d6bSBaptiste Daroussin 			mlink = mandoc_calloc(1, sizeof(struct mlink));
69161d06d6bSBaptiste Daroussin 			if (strlcpy(mlink->file, path,
69261d06d6bSBaptiste Daroussin 			    sizeof(mlink->file)) >=
69361d06d6bSBaptiste Daroussin 			    sizeof(mlink->file)) {
69461d06d6bSBaptiste Daroussin 				say(path, "Filename too long");
69561d06d6bSBaptiste Daroussin 				free(mlink);
69661d06d6bSBaptiste Daroussin 				continue;
69761d06d6bSBaptiste Daroussin 			}
69861d06d6bSBaptiste Daroussin 			mlink->dform = dform;
69961d06d6bSBaptiste Daroussin 			mlink->dsec = dsec;
70061d06d6bSBaptiste Daroussin 			mlink->arch = arch;
70161d06d6bSBaptiste Daroussin 			mlink->name = ff->fts_name;
70261d06d6bSBaptiste Daroussin 			mlink->fsec = fsec;
70361d06d6bSBaptiste Daroussin 			mlink->gzip = gzip;
70461d06d6bSBaptiste Daroussin 			mlink_add(mlink, ff->fts_statp);
70561d06d6bSBaptiste Daroussin 			continue;
70661d06d6bSBaptiste Daroussin 
70761d06d6bSBaptiste Daroussin 		case FTS_D:
70861d06d6bSBaptiste Daroussin 		case FTS_DP:
70961d06d6bSBaptiste Daroussin 			break;
71061d06d6bSBaptiste Daroussin 
71161d06d6bSBaptiste Daroussin 		default:
71261d06d6bSBaptiste Daroussin 			if (warnings)
71361d06d6bSBaptiste Daroussin 				say(path, "Not a regular file");
71461d06d6bSBaptiste Daroussin 			continue;
71561d06d6bSBaptiste Daroussin 		}
71661d06d6bSBaptiste Daroussin 
71761d06d6bSBaptiste Daroussin 		switch (ff->fts_level) {
71861d06d6bSBaptiste Daroussin 		case 0:
71961d06d6bSBaptiste Daroussin 			/* Ignore the root directory. */
72061d06d6bSBaptiste Daroussin 			break;
72161d06d6bSBaptiste Daroussin 		case 1:
72261d06d6bSBaptiste Daroussin 			/*
72361d06d6bSBaptiste Daroussin 			 * This might contain manX/ or catX/.
72461d06d6bSBaptiste Daroussin 			 * Try to infer this from the name.
72561d06d6bSBaptiste Daroussin 			 * If we're not in use_all, enforce it.
72661d06d6bSBaptiste Daroussin 			 */
72761d06d6bSBaptiste Daroussin 			cp = ff->fts_name;
72861d06d6bSBaptiste Daroussin 			if (ff->fts_info == FTS_DP) {
72961d06d6bSBaptiste Daroussin 				dform = FORM_NONE;
73061d06d6bSBaptiste Daroussin 				dsec = NULL;
73161d06d6bSBaptiste Daroussin 				break;
73261d06d6bSBaptiste Daroussin 			}
73361d06d6bSBaptiste Daroussin 
73461d06d6bSBaptiste Daroussin 			if ( ! strncmp(cp, "man", 3)) {
73561d06d6bSBaptiste Daroussin 				dform = FORM_SRC;
73661d06d6bSBaptiste Daroussin 				dsec = cp + 3;
73761d06d6bSBaptiste Daroussin 			} else if ( ! strncmp(cp, "cat", 3)) {
73861d06d6bSBaptiste Daroussin 				dform = FORM_CAT;
73961d06d6bSBaptiste Daroussin 				dsec = cp + 3;
74061d06d6bSBaptiste Daroussin 			} else {
74161d06d6bSBaptiste Daroussin 				dform = FORM_NONE;
74261d06d6bSBaptiste Daroussin 				dsec = NULL;
74361d06d6bSBaptiste Daroussin 			}
74461d06d6bSBaptiste Daroussin 
74561d06d6bSBaptiste Daroussin 			if (dsec != NULL || use_all)
74661d06d6bSBaptiste Daroussin 				break;
74761d06d6bSBaptiste Daroussin 
74861d06d6bSBaptiste Daroussin 			if (warnings)
74961d06d6bSBaptiste Daroussin 				say(path, "Unknown directory part");
75061d06d6bSBaptiste Daroussin 			fts_set(f, ff, FTS_SKIP);
75161d06d6bSBaptiste Daroussin 			break;
75261d06d6bSBaptiste Daroussin 		case 2:
75361d06d6bSBaptiste Daroussin 			/*
75461d06d6bSBaptiste Daroussin 			 * Possibly our architecture.
75561d06d6bSBaptiste Daroussin 			 * If we're descending, keep tabs on it.
75661d06d6bSBaptiste Daroussin 			 */
75761d06d6bSBaptiste Daroussin 			if (ff->fts_info != FTS_DP && dsec != NULL)
75861d06d6bSBaptiste Daroussin 				arch = ff->fts_name;
75961d06d6bSBaptiste Daroussin 			else
76061d06d6bSBaptiste Daroussin 				arch = NULL;
76161d06d6bSBaptiste Daroussin 			break;
76261d06d6bSBaptiste Daroussin 		default:
76361d06d6bSBaptiste Daroussin 			if (ff->fts_info == FTS_DP || use_all)
76461d06d6bSBaptiste Daroussin 				break;
76561d06d6bSBaptiste Daroussin 			if (warnings)
76661d06d6bSBaptiste Daroussin 				say(path, "Extraneous directory part");
76761d06d6bSBaptiste Daroussin 			fts_set(f, ff, FTS_SKIP);
76861d06d6bSBaptiste Daroussin 			break;
76961d06d6bSBaptiste Daroussin 		}
77061d06d6bSBaptiste Daroussin 	}
77161d06d6bSBaptiste Daroussin 
77261d06d6bSBaptiste Daroussin 	fts_close(f);
77361d06d6bSBaptiste Daroussin 	return 1;
77461d06d6bSBaptiste Daroussin }
77561d06d6bSBaptiste Daroussin 
77661d06d6bSBaptiste Daroussin /*
77761d06d6bSBaptiste Daroussin  * Add a file to the mlinks table.
77861d06d6bSBaptiste Daroussin  * Do not verify that it's a "valid" looking manpage (we'll do that
77961d06d6bSBaptiste Daroussin  * later).
78061d06d6bSBaptiste Daroussin  *
78161d06d6bSBaptiste Daroussin  * Try to infer the manual section, architecture, and page name from the
78261d06d6bSBaptiste Daroussin  * path, assuming it looks like
78361d06d6bSBaptiste Daroussin  *
78461d06d6bSBaptiste Daroussin  *   [./]man*[/<arch>]/<name>.<section>
78561d06d6bSBaptiste Daroussin  *   or
78661d06d6bSBaptiste Daroussin  *   [./]cat<section>[/<arch>]/<name>.0
78761d06d6bSBaptiste Daroussin  *
78861d06d6bSBaptiste Daroussin  * See treescan() for the fts(3) version of this.
78961d06d6bSBaptiste Daroussin  */
79061d06d6bSBaptiste Daroussin static void
7916d38604fSBaptiste Daroussin filescan(const char *infile)
79261d06d6bSBaptiste Daroussin {
79361d06d6bSBaptiste Daroussin 	struct stat	 st;
79461d06d6bSBaptiste Daroussin 	struct mlink	*mlink;
7956d38604fSBaptiste Daroussin 	char		*linkfile, *p, *realdir, *start, *usefile;
7966d38604fSBaptiste Daroussin 	size_t		 realdir_len;
79761d06d6bSBaptiste Daroussin 
79861d06d6bSBaptiste Daroussin 	assert(use_all);
79961d06d6bSBaptiste Daroussin 
8006d38604fSBaptiste Daroussin 	if (strncmp(infile, "./", 2) == 0)
8016d38604fSBaptiste Daroussin 		infile += 2;
80261d06d6bSBaptiste Daroussin 
80361d06d6bSBaptiste Daroussin 	/*
80461d06d6bSBaptiste Daroussin 	 * We have to do lstat(2) before realpath(3) loses
80561d06d6bSBaptiste Daroussin 	 * the information whether this is a symbolic link.
80661d06d6bSBaptiste Daroussin 	 * We need to know that because for symbolic links,
807*c1c95addSBrooks Davis 	 * we want to use the original file name, while for
80861d06d6bSBaptiste Daroussin 	 * regular files, we want to use the real path.
80961d06d6bSBaptiste Daroussin 	 */
8106d38604fSBaptiste Daroussin 	if (lstat(infile, &st) == -1) {
81161d06d6bSBaptiste Daroussin 		exitcode = (int)MANDOCLEVEL_BADARG;
8126d38604fSBaptiste Daroussin 		say(infile, "&lstat");
81361d06d6bSBaptiste Daroussin 		return;
8146d38604fSBaptiste Daroussin 	} else if (S_ISREG(st.st_mode) == 0 && S_ISLNK(st.st_mode) == 0) {
81561d06d6bSBaptiste Daroussin 		exitcode = (int)MANDOCLEVEL_BADARG;
8166d38604fSBaptiste Daroussin 		say(infile, "Not a regular file");
81761d06d6bSBaptiste Daroussin 		return;
81861d06d6bSBaptiste Daroussin 	}
81961d06d6bSBaptiste Daroussin 
82061d06d6bSBaptiste Daroussin 	/*
82161d06d6bSBaptiste Daroussin 	 * We have to resolve the file name to the real path
82261d06d6bSBaptiste Daroussin 	 * in any case for the base directory check.
82361d06d6bSBaptiste Daroussin 	 */
8246d38604fSBaptiste Daroussin 	if ((usefile = realpath(infile, NULL)) == NULL) {
82561d06d6bSBaptiste Daroussin 		exitcode = (int)MANDOCLEVEL_BADARG;
8266d38604fSBaptiste Daroussin 		say(infile, "&realpath");
82761d06d6bSBaptiste Daroussin 		return;
82861d06d6bSBaptiste Daroussin 	}
82961d06d6bSBaptiste Daroussin 
8306d38604fSBaptiste Daroussin 	if (op == OP_TEST)
8316d38604fSBaptiste Daroussin 		start = usefile;
8326d38604fSBaptiste Daroussin 	else if (strncmp(usefile, basedir, basedir_len) == 0)
8336d38604fSBaptiste Daroussin 		start = usefile + basedir_len;
8346d38604fSBaptiste Daroussin #ifdef READ_ALLOWED_PATH
8356d38604fSBaptiste Daroussin 	else if (read_allowed(usefile))
8366d38604fSBaptiste Daroussin 		start = usefile;
83761d06d6bSBaptiste Daroussin #endif
83861d06d6bSBaptiste Daroussin 	else {
83961d06d6bSBaptiste Daroussin 		exitcode = (int)MANDOCLEVEL_BADARG;
8406d38604fSBaptiste Daroussin 		say("", "%s: outside base directory", infile);
8416d38604fSBaptiste Daroussin 		free(usefile);
84261d06d6bSBaptiste Daroussin 		return;
84361d06d6bSBaptiste Daroussin 	}
84461d06d6bSBaptiste Daroussin 
84561d06d6bSBaptiste Daroussin 	/*
84661d06d6bSBaptiste Daroussin 	 * Now we are sure the file is inside our tree.
84761d06d6bSBaptiste Daroussin 	 * If it is a symbolic link, ignore the real path
84861d06d6bSBaptiste Daroussin 	 * and use the original name.
84961d06d6bSBaptiste Daroussin 	 */
8506d38604fSBaptiste Daroussin 	do {
8516d38604fSBaptiste Daroussin 		if (S_ISLNK(st.st_mode) == 0)
8526d38604fSBaptiste Daroussin 			break;
8536d38604fSBaptiste Daroussin 
8546d38604fSBaptiste Daroussin 		/*
8556d38604fSBaptiste Daroussin 		 * Some implementations of realpath(3) may succeed
8566d38604fSBaptiste Daroussin 		 * even if the target of the link does not exist,
8576d38604fSBaptiste Daroussin 		 * so check again for extra safety.
8586d38604fSBaptiste Daroussin 		 */
8596d38604fSBaptiste Daroussin 		if (stat(usefile, &st) == -1) {
86061d06d6bSBaptiste Daroussin 			exitcode = (int)MANDOCLEVEL_BADARG;
8616d38604fSBaptiste Daroussin 			say(infile, "&stat");
8626d38604fSBaptiste Daroussin 			free(usefile);
86361d06d6bSBaptiste Daroussin 			return;
86461d06d6bSBaptiste Daroussin 		}
8656d38604fSBaptiste Daroussin 		linkfile = mandoc_strdup(infile);
8666d38604fSBaptiste Daroussin 		if (op == OP_TEST) {
8676d38604fSBaptiste Daroussin 			free(usefile);
8686d38604fSBaptiste Daroussin 			start = usefile = linkfile;
8696d38604fSBaptiste Daroussin 			break;
8706d38604fSBaptiste Daroussin 		}
8716d38604fSBaptiste Daroussin 		if (strncmp(infile, basedir, basedir_len) == 0) {
8726d38604fSBaptiste Daroussin 			free(usefile);
8736d38604fSBaptiste Daroussin 			usefile = linkfile;
8746d38604fSBaptiste Daroussin 			start = usefile + basedir_len;
8756d38604fSBaptiste Daroussin 			break;
8766d38604fSBaptiste Daroussin 		}
8776d38604fSBaptiste Daroussin 
8786d38604fSBaptiste Daroussin 		/*
8796d38604fSBaptiste Daroussin 		 * This symbolic link points into the basedir
8806d38604fSBaptiste Daroussin 		 * from the outside.  Let's see whether any of
8816d38604fSBaptiste Daroussin 		 * the parent directories resolve to the basedir.
8826d38604fSBaptiste Daroussin 		 */
8836d38604fSBaptiste Daroussin 		p = strchr(linkfile, '\0');
8846d38604fSBaptiste Daroussin 		do {
8856d38604fSBaptiste Daroussin 			while (*--p != '/')
8866d38604fSBaptiste Daroussin 				continue;
8876d38604fSBaptiste Daroussin 			*p = '\0';
8886d38604fSBaptiste Daroussin 			if ((realdir = realpath(linkfile, NULL)) == NULL) {
8896d38604fSBaptiste Daroussin 				exitcode = (int)MANDOCLEVEL_BADARG;
8906d38604fSBaptiste Daroussin 				say(infile, "&realpath");
8916d38604fSBaptiste Daroussin 				free(linkfile);
8926d38604fSBaptiste Daroussin 				free(usefile);
89361d06d6bSBaptiste Daroussin 				return;
89461d06d6bSBaptiste Daroussin 			}
8956d38604fSBaptiste Daroussin 			realdir_len = strlen(realdir) + 1;
8966d38604fSBaptiste Daroussin 			free(realdir);
8976d38604fSBaptiste Daroussin 			*p = '/';
8986d38604fSBaptiste Daroussin 		} while (realdir_len > basedir_len);
8996d38604fSBaptiste Daroussin 
9006d38604fSBaptiste Daroussin 		/*
9016d38604fSBaptiste Daroussin 		 * If one of the directories resolves to the basedir,
9026d38604fSBaptiste Daroussin 		 * use the rest of the original name.
9036d38604fSBaptiste Daroussin 		 * Otherwise, the best we can do
9046d38604fSBaptiste Daroussin 		 * is to use the filename pointed to.
9056d38604fSBaptiste Daroussin 		 */
9066d38604fSBaptiste Daroussin 		if (realdir_len == basedir_len) {
9076d38604fSBaptiste Daroussin 			free(usefile);
9086d38604fSBaptiste Daroussin 			usefile = linkfile;
9096d38604fSBaptiste Daroussin 			start = p + 1;
9106d38604fSBaptiste Daroussin 		} else {
9116d38604fSBaptiste Daroussin 			free(linkfile);
9126d38604fSBaptiste Daroussin 			start = usefile + basedir_len;
91361d06d6bSBaptiste Daroussin 		}
9146d38604fSBaptiste Daroussin 	} while (/* CONSTCOND */ 0);
91561d06d6bSBaptiste Daroussin 
91661d06d6bSBaptiste Daroussin 	mlink = mandoc_calloc(1, sizeof(struct mlink));
91761d06d6bSBaptiste Daroussin 	mlink->dform = FORM_NONE;
91861d06d6bSBaptiste Daroussin 	if (strlcpy(mlink->file, start, sizeof(mlink->file)) >=
91961d06d6bSBaptiste Daroussin 	    sizeof(mlink->file)) {
92061d06d6bSBaptiste Daroussin 		say(start, "Filename too long");
92161d06d6bSBaptiste Daroussin 		free(mlink);
9226d38604fSBaptiste Daroussin 		free(usefile);
92361d06d6bSBaptiste Daroussin 		return;
92461d06d6bSBaptiste Daroussin 	}
92561d06d6bSBaptiste Daroussin 
92661d06d6bSBaptiste Daroussin 	/*
92761d06d6bSBaptiste Daroussin 	 * In test mode or when the original name is absolute
92861d06d6bSBaptiste Daroussin 	 * but outside our tree, guess the base directory.
92961d06d6bSBaptiste Daroussin 	 */
93061d06d6bSBaptiste Daroussin 
9316d38604fSBaptiste Daroussin 	if (op == OP_TEST || (start == usefile && *start == '/')) {
9326d38604fSBaptiste Daroussin 		if (strncmp(usefile, "man/", 4) == 0)
9336d38604fSBaptiste Daroussin 			start = usefile + 4;
9346d38604fSBaptiste Daroussin 		else if ((start = strstr(usefile, "/man/")) != NULL)
93561d06d6bSBaptiste Daroussin 			start += 5;
93661d06d6bSBaptiste Daroussin 		else
9376d38604fSBaptiste Daroussin 			start = usefile;
93861d06d6bSBaptiste Daroussin 	}
93961d06d6bSBaptiste Daroussin 
94061d06d6bSBaptiste Daroussin 	/*
94161d06d6bSBaptiste Daroussin 	 * First try to guess our directory structure.
94261d06d6bSBaptiste Daroussin 	 * If we find a separator, try to look for man* or cat*.
94361d06d6bSBaptiste Daroussin 	 * If we find one of these and what's underneath is a directory,
94461d06d6bSBaptiste Daroussin 	 * assume it's an architecture.
94561d06d6bSBaptiste Daroussin 	 */
9466d38604fSBaptiste Daroussin 	if ((p = strchr(start, '/')) != NULL) {
94761d06d6bSBaptiste Daroussin 		*p++ = '\0';
9486d38604fSBaptiste Daroussin 		if (strncmp(start, "man", 3) == 0) {
94961d06d6bSBaptiste Daroussin 			mlink->dform = FORM_SRC;
95061d06d6bSBaptiste Daroussin 			mlink->dsec = start + 3;
9516d38604fSBaptiste Daroussin 		} else if (strncmp(start, "cat", 3) == 0) {
95261d06d6bSBaptiste Daroussin 			mlink->dform = FORM_CAT;
95361d06d6bSBaptiste Daroussin 			mlink->dsec = start + 3;
95461d06d6bSBaptiste Daroussin 		}
95561d06d6bSBaptiste Daroussin 
95661d06d6bSBaptiste Daroussin 		start = p;
9576d38604fSBaptiste Daroussin 		if (mlink->dsec != NULL && (p = strchr(start, '/')) != NULL) {
95861d06d6bSBaptiste Daroussin 			*p++ = '\0';
95961d06d6bSBaptiste Daroussin 			mlink->arch = start;
96061d06d6bSBaptiste Daroussin 			start = p;
96161d06d6bSBaptiste Daroussin 		}
96261d06d6bSBaptiste Daroussin 	}
96361d06d6bSBaptiste Daroussin 
96461d06d6bSBaptiste Daroussin 	/*
96561d06d6bSBaptiste Daroussin 	 * Now check the file suffix.
96661d06d6bSBaptiste Daroussin 	 * Suffix of `.0' indicates a catpage, `.1-9' is a manpage.
96761d06d6bSBaptiste Daroussin 	 */
96861d06d6bSBaptiste Daroussin 	p = strrchr(start, '\0');
9696d38604fSBaptiste Daroussin 	while (p-- > start && *p != '/' && *p != '.')
9706d38604fSBaptiste Daroussin 		continue;
97161d06d6bSBaptiste Daroussin 
9726d38604fSBaptiste Daroussin 	if (*p == '.') {
97361d06d6bSBaptiste Daroussin 		*p++ = '\0';
97461d06d6bSBaptiste Daroussin 		mlink->fsec = p;
97561d06d6bSBaptiste Daroussin 	}
97661d06d6bSBaptiste Daroussin 
97761d06d6bSBaptiste Daroussin 	/*
97861d06d6bSBaptiste Daroussin 	 * Now try to parse the name.
97961d06d6bSBaptiste Daroussin 	 * Use the filename portion of the path.
98061d06d6bSBaptiste Daroussin 	 */
98161d06d6bSBaptiste Daroussin 	mlink->name = start;
9826d38604fSBaptiste Daroussin 	if ((p = strrchr(start, '/')) != NULL) {
98361d06d6bSBaptiste Daroussin 		mlink->name = p + 1;
98461d06d6bSBaptiste Daroussin 		*p = '\0';
98561d06d6bSBaptiste Daroussin 	}
98661d06d6bSBaptiste Daroussin 	mlink_add(mlink, &st);
9876d38604fSBaptiste Daroussin 	free(usefile);
98861d06d6bSBaptiste Daroussin }
98961d06d6bSBaptiste Daroussin 
99061d06d6bSBaptiste Daroussin static void
99161d06d6bSBaptiste Daroussin mlink_add(struct mlink *mlink, const struct stat *st)
99261d06d6bSBaptiste Daroussin {
99361d06d6bSBaptiste Daroussin 	struct inodev	 inodev;
99461d06d6bSBaptiste Daroussin 	struct mpage	*mpage;
99561d06d6bSBaptiste Daroussin 	unsigned int	 slot;
99661d06d6bSBaptiste Daroussin 
99761d06d6bSBaptiste Daroussin 	assert(NULL != mlink->file);
99861d06d6bSBaptiste Daroussin 
99961d06d6bSBaptiste Daroussin 	mlink->dsec = mandoc_strdup(mlink->dsec ? mlink->dsec : "");
100061d06d6bSBaptiste Daroussin 	mlink->arch = mandoc_strdup(mlink->arch ? mlink->arch : "");
100161d06d6bSBaptiste Daroussin 	mlink->name = mandoc_strdup(mlink->name ? mlink->name : "");
100261d06d6bSBaptiste Daroussin 	mlink->fsec = mandoc_strdup(mlink->fsec ? mlink->fsec : "");
100361d06d6bSBaptiste Daroussin 
100461d06d6bSBaptiste Daroussin 	if ('0' == *mlink->fsec) {
100561d06d6bSBaptiste Daroussin 		free(mlink->fsec);
100661d06d6bSBaptiste Daroussin 		mlink->fsec = mandoc_strdup(mlink->dsec);
100761d06d6bSBaptiste Daroussin 		mlink->fform = FORM_CAT;
100861d06d6bSBaptiste Daroussin 	} else if ('1' <= *mlink->fsec && '9' >= *mlink->fsec)
100961d06d6bSBaptiste Daroussin 		mlink->fform = FORM_SRC;
101061d06d6bSBaptiste Daroussin 	else
101161d06d6bSBaptiste Daroussin 		mlink->fform = FORM_NONE;
101261d06d6bSBaptiste Daroussin 
101361d06d6bSBaptiste Daroussin 	slot = ohash_qlookup(&mlinks, mlink->file);
101461d06d6bSBaptiste Daroussin 	assert(NULL == ohash_find(&mlinks, slot));
101561d06d6bSBaptiste Daroussin 	ohash_insert(&mlinks, slot, mlink);
101661d06d6bSBaptiste Daroussin 
101761d06d6bSBaptiste Daroussin 	memset(&inodev, 0, sizeof(inodev));  /* Clear padding. */
101861d06d6bSBaptiste Daroussin 	inodev.st_ino = st->st_ino;
101961d06d6bSBaptiste Daroussin 	inodev.st_dev = st->st_dev;
102061d06d6bSBaptiste Daroussin 	slot = ohash_lookup_memory(&mpages, (char *)&inodev,
102161d06d6bSBaptiste Daroussin 	    sizeof(struct inodev), inodev.st_ino);
102261d06d6bSBaptiste Daroussin 	mpage = ohash_find(&mpages, slot);
102361d06d6bSBaptiste Daroussin 	if (NULL == mpage) {
102461d06d6bSBaptiste Daroussin 		mpage = mandoc_calloc(1, sizeof(struct mpage));
102561d06d6bSBaptiste Daroussin 		mpage->inodev.st_ino = inodev.st_ino;
102661d06d6bSBaptiste Daroussin 		mpage->inodev.st_dev = inodev.st_dev;
102761d06d6bSBaptiste Daroussin 		mpage->form = FORM_NONE;
102861d06d6bSBaptiste Daroussin 		mpage->next = mpage_head;
102961d06d6bSBaptiste Daroussin 		mpage_head = mpage;
103061d06d6bSBaptiste Daroussin 		ohash_insert(&mpages, slot, mpage);
103161d06d6bSBaptiste Daroussin 	} else
103261d06d6bSBaptiste Daroussin 		mlink->next = mpage->mlinks;
103361d06d6bSBaptiste Daroussin 	mpage->mlinks = mlink;
103461d06d6bSBaptiste Daroussin 	mlink->mpage = mpage;
103561d06d6bSBaptiste Daroussin }
103661d06d6bSBaptiste Daroussin 
103761d06d6bSBaptiste Daroussin static void
103861d06d6bSBaptiste Daroussin mlink_free(struct mlink *mlink)
103961d06d6bSBaptiste Daroussin {
104061d06d6bSBaptiste Daroussin 
104161d06d6bSBaptiste Daroussin 	free(mlink->dsec);
104261d06d6bSBaptiste Daroussin 	free(mlink->arch);
104361d06d6bSBaptiste Daroussin 	free(mlink->name);
104461d06d6bSBaptiste Daroussin 	free(mlink->fsec);
104561d06d6bSBaptiste Daroussin 	free(mlink);
104661d06d6bSBaptiste Daroussin }
104761d06d6bSBaptiste Daroussin 
104861d06d6bSBaptiste Daroussin static void
104961d06d6bSBaptiste Daroussin mpages_free(void)
105061d06d6bSBaptiste Daroussin {
105161d06d6bSBaptiste Daroussin 	struct mpage	*mpage;
105261d06d6bSBaptiste Daroussin 	struct mlink	*mlink;
105361d06d6bSBaptiste Daroussin 
105461d06d6bSBaptiste Daroussin 	while ((mpage = mpage_head) != NULL) {
105561d06d6bSBaptiste Daroussin 		while ((mlink = mpage->mlinks) != NULL) {
105661d06d6bSBaptiste Daroussin 			mpage->mlinks = mlink->next;
105761d06d6bSBaptiste Daroussin 			mlink_free(mlink);
105861d06d6bSBaptiste Daroussin 		}
105961d06d6bSBaptiste Daroussin 		mpage_head = mpage->next;
106061d06d6bSBaptiste Daroussin 		free(mpage->sec);
106161d06d6bSBaptiste Daroussin 		free(mpage->arch);
106261d06d6bSBaptiste Daroussin 		free(mpage->title);
106361d06d6bSBaptiste Daroussin 		free(mpage->desc);
106461d06d6bSBaptiste Daroussin 		free(mpage);
106561d06d6bSBaptiste Daroussin 	}
106661d06d6bSBaptiste Daroussin }
106761d06d6bSBaptiste Daroussin 
106861d06d6bSBaptiste Daroussin /*
106961d06d6bSBaptiste Daroussin  * For each mlink to the mpage, check whether the path looks like
107061d06d6bSBaptiste Daroussin  * it is formatted, and if it does, check whether a source manual
107161d06d6bSBaptiste Daroussin  * exists by the same name, ignoring the suffix.
107261d06d6bSBaptiste Daroussin  * If both conditions hold, drop the mlink.
107361d06d6bSBaptiste Daroussin  */
107461d06d6bSBaptiste Daroussin static void
107561d06d6bSBaptiste Daroussin mlinks_undupe(struct mpage *mpage)
107661d06d6bSBaptiste Daroussin {
107761d06d6bSBaptiste Daroussin 	char		  buf[PATH_MAX];
107861d06d6bSBaptiste Daroussin 	struct mlink	**prev;
107961d06d6bSBaptiste Daroussin 	struct mlink	 *mlink;
108061d06d6bSBaptiste Daroussin 	char		 *bufp;
108161d06d6bSBaptiste Daroussin 
108261d06d6bSBaptiste Daroussin 	mpage->form = FORM_CAT;
108361d06d6bSBaptiste Daroussin 	prev = &mpage->mlinks;
108461d06d6bSBaptiste Daroussin 	while (NULL != (mlink = *prev)) {
108561d06d6bSBaptiste Daroussin 		if (FORM_CAT != mlink->dform) {
108661d06d6bSBaptiste Daroussin 			mpage->form = FORM_NONE;
108761d06d6bSBaptiste Daroussin 			goto nextlink;
108861d06d6bSBaptiste Daroussin 		}
108961d06d6bSBaptiste Daroussin 		(void)strlcpy(buf, mlink->file, sizeof(buf));
109061d06d6bSBaptiste Daroussin 		bufp = strstr(buf, "cat");
109161d06d6bSBaptiste Daroussin 		assert(NULL != bufp);
109261d06d6bSBaptiste Daroussin 		memcpy(bufp, "man", 3);
109361d06d6bSBaptiste Daroussin 		if (NULL != (bufp = strrchr(buf, '.')))
109461d06d6bSBaptiste Daroussin 			*++bufp = '\0';
109561d06d6bSBaptiste Daroussin 		(void)strlcat(buf, mlink->dsec, sizeof(buf));
109661d06d6bSBaptiste Daroussin 		if (NULL == ohash_find(&mlinks,
109761d06d6bSBaptiste Daroussin 		    ohash_qlookup(&mlinks, buf)))
109861d06d6bSBaptiste Daroussin 			goto nextlink;
109961d06d6bSBaptiste Daroussin 		if (warnings)
110061d06d6bSBaptiste Daroussin 			say(mlink->file, "Man source exists: %s", buf);
110161d06d6bSBaptiste Daroussin 		if (use_all)
110261d06d6bSBaptiste Daroussin 			goto nextlink;
110361d06d6bSBaptiste Daroussin 		*prev = mlink->next;
110461d06d6bSBaptiste Daroussin 		mlink_free(mlink);
110561d06d6bSBaptiste Daroussin 		continue;
110661d06d6bSBaptiste Daroussin nextlink:
110761d06d6bSBaptiste Daroussin 		prev = &(*prev)->next;
110861d06d6bSBaptiste Daroussin 	}
110961d06d6bSBaptiste Daroussin }
111061d06d6bSBaptiste Daroussin 
111161d06d6bSBaptiste Daroussin static void
111261d06d6bSBaptiste Daroussin mlink_check(struct mpage *mpage, struct mlink *mlink)
111361d06d6bSBaptiste Daroussin {
111461d06d6bSBaptiste Daroussin 	struct str	*str;
111561d06d6bSBaptiste Daroussin 	unsigned int	 slot;
111661d06d6bSBaptiste Daroussin 
111761d06d6bSBaptiste Daroussin 	/*
111861d06d6bSBaptiste Daroussin 	 * Check whether the manual section given in a file
111961d06d6bSBaptiste Daroussin 	 * agrees with the directory where the file is located.
112061d06d6bSBaptiste Daroussin 	 * Some manuals have suffixes like (3p) on their
112161d06d6bSBaptiste Daroussin 	 * section number either inside the file or in the
112261d06d6bSBaptiste Daroussin 	 * directory name, some are linked into more than one
112361d06d6bSBaptiste Daroussin 	 * section, like encrypt(1) = makekey(8).
112461d06d6bSBaptiste Daroussin 	 */
112561d06d6bSBaptiste Daroussin 
112661d06d6bSBaptiste Daroussin 	if (FORM_SRC == mpage->form &&
112761d06d6bSBaptiste Daroussin 	    strcasecmp(mpage->sec, mlink->dsec))
112861d06d6bSBaptiste Daroussin 		say(mlink->file, "Section \"%s\" manual in %s directory",
112961d06d6bSBaptiste Daroussin 		    mpage->sec, mlink->dsec);
113061d06d6bSBaptiste Daroussin 
113161d06d6bSBaptiste Daroussin 	/*
113261d06d6bSBaptiste Daroussin 	 * Manual page directories exist for each kernel
113361d06d6bSBaptiste Daroussin 	 * architecture as returned by machine(1).
113461d06d6bSBaptiste Daroussin 	 * However, many manuals only depend on the
113561d06d6bSBaptiste Daroussin 	 * application architecture as returned by arch(1).
113661d06d6bSBaptiste Daroussin 	 * For example, some (2/ARM) manuals are shared
113761d06d6bSBaptiste Daroussin 	 * across the "armish" and "zaurus" kernel
113861d06d6bSBaptiste Daroussin 	 * architectures.
113961d06d6bSBaptiste Daroussin 	 * A few manuals are even shared across completely
114061d06d6bSBaptiste Daroussin 	 * different architectures, for example fdformat(1)
114161d06d6bSBaptiste Daroussin 	 * on amd64, i386, and sparc64.
114261d06d6bSBaptiste Daroussin 	 */
114361d06d6bSBaptiste Daroussin 
114461d06d6bSBaptiste Daroussin 	if (strcasecmp(mpage->arch, mlink->arch))
114561d06d6bSBaptiste Daroussin 		say(mlink->file, "Architecture \"%s\" manual in "
114661d06d6bSBaptiste Daroussin 		    "\"%s\" directory", mpage->arch, mlink->arch);
114761d06d6bSBaptiste Daroussin 
114861d06d6bSBaptiste Daroussin 	/*
114961d06d6bSBaptiste Daroussin 	 * XXX
115061d06d6bSBaptiste Daroussin 	 * parse_cat() doesn't set NAME_TITLE yet.
115161d06d6bSBaptiste Daroussin 	 */
115261d06d6bSBaptiste Daroussin 
115361d06d6bSBaptiste Daroussin 	if (FORM_CAT == mpage->form)
115461d06d6bSBaptiste Daroussin 		return;
115561d06d6bSBaptiste Daroussin 
115661d06d6bSBaptiste Daroussin 	/*
115761d06d6bSBaptiste Daroussin 	 * Check whether this mlink
115861d06d6bSBaptiste Daroussin 	 * appears as a name in the NAME section.
115961d06d6bSBaptiste Daroussin 	 */
116061d06d6bSBaptiste Daroussin 
116161d06d6bSBaptiste Daroussin 	slot = ohash_qlookup(&names, mlink->name);
116261d06d6bSBaptiste Daroussin 	str = ohash_find(&names, slot);
116361d06d6bSBaptiste Daroussin 	assert(NULL != str);
116461d06d6bSBaptiste Daroussin 	if ( ! (NAME_TITLE & str->mask))
116561d06d6bSBaptiste Daroussin 		say(mlink->file, "Name missing in NAME section");
116661d06d6bSBaptiste Daroussin }
116761d06d6bSBaptiste Daroussin 
116861d06d6bSBaptiste Daroussin /*
116961d06d6bSBaptiste Daroussin  * Run through the files in the global vector "mpages"
117061d06d6bSBaptiste Daroussin  * and add them to the database specified in "basedir".
117161d06d6bSBaptiste Daroussin  *
117261d06d6bSBaptiste Daroussin  * This handles the parsing scheme itself, using the cues of directory
117361d06d6bSBaptiste Daroussin  * and filename to determine whether the file is parsable or not.
117461d06d6bSBaptiste Daroussin  */
117561d06d6bSBaptiste Daroussin static void
117661d06d6bSBaptiste Daroussin mpages_merge(struct dba *dba, struct mparse *mp)
117761d06d6bSBaptiste Daroussin {
117861d06d6bSBaptiste Daroussin 	struct mpage		*mpage, *mpage_dest;
117961d06d6bSBaptiste Daroussin 	struct mlink		*mlink, *mlink_dest;
11807295610fSBaptiste Daroussin 	struct roff_meta	*meta;
118161d06d6bSBaptiste Daroussin 	char			*cp;
118261d06d6bSBaptiste Daroussin 	int			 fd;
118361d06d6bSBaptiste Daroussin 
118461d06d6bSBaptiste Daroussin 	for (mpage = mpage_head; mpage != NULL; mpage = mpage->next) {
118561d06d6bSBaptiste Daroussin 		mlinks_undupe(mpage);
118661d06d6bSBaptiste Daroussin 		if ((mlink = mpage->mlinks) == NULL)
118761d06d6bSBaptiste Daroussin 			continue;
118861d06d6bSBaptiste Daroussin 
118961d06d6bSBaptiste Daroussin 		name_mask = NAME_MASK;
119061d06d6bSBaptiste Daroussin 		mandoc_ohash_init(&names, 4, offsetof(struct str, key));
119161d06d6bSBaptiste Daroussin 		mandoc_ohash_init(&strings, 6, offsetof(struct str, key));
119261d06d6bSBaptiste Daroussin 		mparse_reset(mp);
11937295610fSBaptiste Daroussin 		meta = NULL;
119461d06d6bSBaptiste Daroussin 
119561d06d6bSBaptiste Daroussin 		if ((fd = mparse_open(mp, mlink->file)) == -1) {
119661d06d6bSBaptiste Daroussin 			say(mlink->file, "&open");
119761d06d6bSBaptiste Daroussin 			goto nextpage;
119861d06d6bSBaptiste Daroussin 		}
119961d06d6bSBaptiste Daroussin 
120061d06d6bSBaptiste Daroussin 		/*
120161d06d6bSBaptiste Daroussin 		 * Interpret the file as mdoc(7) or man(7) source
120261d06d6bSBaptiste Daroussin 		 * code, unless it is known to be formatted.
120361d06d6bSBaptiste Daroussin 		 */
120461d06d6bSBaptiste Daroussin 		if (mlink->dform != FORM_CAT || mlink->fform != FORM_CAT) {
120561d06d6bSBaptiste Daroussin 			mparse_readfd(mp, fd, mlink->file);
120661d06d6bSBaptiste Daroussin 			close(fd);
120761d06d6bSBaptiste Daroussin 			fd = -1;
12087295610fSBaptiste Daroussin 			meta = mparse_result(mp);
120961d06d6bSBaptiste Daroussin 		}
121061d06d6bSBaptiste Daroussin 
12117295610fSBaptiste Daroussin 		if (meta != NULL && meta->sodest != NULL) {
121261d06d6bSBaptiste Daroussin 			mlink_dest = ohash_find(&mlinks,
12137295610fSBaptiste Daroussin 			    ohash_qlookup(&mlinks, meta->sodest));
121461d06d6bSBaptiste Daroussin 			if (mlink_dest == NULL) {
12157295610fSBaptiste Daroussin 				mandoc_asprintf(&cp, "%s.gz", meta->sodest);
121661d06d6bSBaptiste Daroussin 				mlink_dest = ohash_find(&mlinks,
121761d06d6bSBaptiste Daroussin 				    ohash_qlookup(&mlinks, cp));
121861d06d6bSBaptiste Daroussin 				free(cp);
121961d06d6bSBaptiste Daroussin 			}
122061d06d6bSBaptiste Daroussin 			if (mlink_dest != NULL) {
122161d06d6bSBaptiste Daroussin 
122261d06d6bSBaptiste Daroussin 				/* The .so target exists. */
122361d06d6bSBaptiste Daroussin 
122461d06d6bSBaptiste Daroussin 				mpage_dest = mlink_dest->mpage;
122561d06d6bSBaptiste Daroussin 				while (1) {
122661d06d6bSBaptiste Daroussin 					mlink->mpage = mpage_dest;
122761d06d6bSBaptiste Daroussin 
122861d06d6bSBaptiste Daroussin 					/*
122961d06d6bSBaptiste Daroussin 					 * If the target was already
123061d06d6bSBaptiste Daroussin 					 * processed, add the links
123161d06d6bSBaptiste Daroussin 					 * to the database now.
123261d06d6bSBaptiste Daroussin 					 * Otherwise, this will
123361d06d6bSBaptiste Daroussin 					 * happen when we come
123461d06d6bSBaptiste Daroussin 					 * to the target.
123561d06d6bSBaptiste Daroussin 					 */
123661d06d6bSBaptiste Daroussin 
123761d06d6bSBaptiste Daroussin 					if (mpage_dest->dba != NULL)
123861d06d6bSBaptiste Daroussin 						dbadd_mlink(mlink);
123961d06d6bSBaptiste Daroussin 
124061d06d6bSBaptiste Daroussin 					if (mlink->next == NULL)
124161d06d6bSBaptiste Daroussin 						break;
124261d06d6bSBaptiste Daroussin 					mlink = mlink->next;
124361d06d6bSBaptiste Daroussin 				}
124461d06d6bSBaptiste Daroussin 
124561d06d6bSBaptiste Daroussin 				/* Move all links to the target. */
124661d06d6bSBaptiste Daroussin 
124761d06d6bSBaptiste Daroussin 				mlink->next = mlink_dest->next;
124861d06d6bSBaptiste Daroussin 				mlink_dest->next = mpage->mlinks;
124961d06d6bSBaptiste Daroussin 				mpage->mlinks = NULL;
125061d06d6bSBaptiste Daroussin 				goto nextpage;
125145a5aec3SBaptiste Daroussin 			}
125245a5aec3SBaptiste Daroussin 			meta->macroset = MACROSET_NONE;
125345a5aec3SBaptiste Daroussin 		}
125445a5aec3SBaptiste Daroussin 		if (meta != NULL && meta->macroset == MACROSET_MDOC) {
125561d06d6bSBaptiste Daroussin 			mpage->form = FORM_SRC;
12567295610fSBaptiste Daroussin 			mpage->sec = meta->msec;
125761d06d6bSBaptiste Daroussin 			mpage->sec = mandoc_strdup(
125861d06d6bSBaptiste Daroussin 			    mpage->sec == NULL ? "" : mpage->sec);
12597295610fSBaptiste Daroussin 			mpage->arch = meta->arch;
126061d06d6bSBaptiste Daroussin 			mpage->arch = mandoc_strdup(
126161d06d6bSBaptiste Daroussin 			    mpage->arch == NULL ? "" : mpage->arch);
12627295610fSBaptiste Daroussin 			mpage->title = mandoc_strdup(meta->title);
12637295610fSBaptiste Daroussin 		} else if (meta != NULL && meta->macroset == MACROSET_MAN) {
12647295610fSBaptiste Daroussin 			if (*meta->msec != '\0' || *meta->title != '\0') {
126561d06d6bSBaptiste Daroussin 				mpage->form = FORM_SRC;
12667295610fSBaptiste Daroussin 				mpage->sec = mandoc_strdup(meta->msec);
126761d06d6bSBaptiste Daroussin 				mpage->arch = mandoc_strdup(mlink->arch);
12687295610fSBaptiste Daroussin 				mpage->title = mandoc_strdup(meta->title);
126961d06d6bSBaptiste Daroussin 			} else
12707295610fSBaptiste Daroussin 				meta = NULL;
127161d06d6bSBaptiste Daroussin 		}
127261d06d6bSBaptiste Daroussin 
127361d06d6bSBaptiste Daroussin 		assert(mpage->desc == NULL);
127445a5aec3SBaptiste Daroussin 		if (meta == NULL || meta->sodest != NULL) {
127561d06d6bSBaptiste Daroussin 			mpage->sec = mandoc_strdup(mlink->dsec);
127661d06d6bSBaptiste Daroussin 			mpage->arch = mandoc_strdup(mlink->arch);
127761d06d6bSBaptiste Daroussin 			mpage->title = mandoc_strdup(mlink->name);
127845a5aec3SBaptiste Daroussin 			if (meta == NULL) {
127945a5aec3SBaptiste Daroussin 				mpage->form = FORM_CAT;
128061d06d6bSBaptiste Daroussin 				parse_cat(mpage, fd);
128145a5aec3SBaptiste Daroussin 			} else
128245a5aec3SBaptiste Daroussin 				mpage->form = FORM_SRC;
12837295610fSBaptiste Daroussin 		} else if (meta->macroset == MACROSET_MDOC)
12847295610fSBaptiste Daroussin 			parse_mdoc(mpage, meta, meta->first);
128561d06d6bSBaptiste Daroussin 		else
12867295610fSBaptiste Daroussin 			parse_man(mpage, meta, meta->first);
128761d06d6bSBaptiste Daroussin 		if (mpage->desc == NULL) {
128861d06d6bSBaptiste Daroussin 			mpage->desc = mandoc_strdup(mlink->name);
128961d06d6bSBaptiste Daroussin 			if (warnings)
129061d06d6bSBaptiste Daroussin 				say(mlink->file, "No one-line description, "
129161d06d6bSBaptiste Daroussin 				    "using filename \"%s\"", mlink->name);
129261d06d6bSBaptiste Daroussin 		}
129361d06d6bSBaptiste Daroussin 
129461d06d6bSBaptiste Daroussin 		for (mlink = mpage->mlinks;
129561d06d6bSBaptiste Daroussin 		     mlink != NULL;
129661d06d6bSBaptiste Daroussin 		     mlink = mlink->next) {
129761d06d6bSBaptiste Daroussin 			putkey(mpage, mlink->name, NAME_FILE);
129861d06d6bSBaptiste Daroussin 			if (warnings && !use_all)
129961d06d6bSBaptiste Daroussin 				mlink_check(mpage, mlink);
130061d06d6bSBaptiste Daroussin 		}
130161d06d6bSBaptiste Daroussin 
130261d06d6bSBaptiste Daroussin 		dbadd(dba, mpage);
130361d06d6bSBaptiste Daroussin 
130461d06d6bSBaptiste Daroussin nextpage:
130561d06d6bSBaptiste Daroussin 		ohash_delete(&strings);
130661d06d6bSBaptiste Daroussin 		ohash_delete(&names);
130761d06d6bSBaptiste Daroussin 	}
130861d06d6bSBaptiste Daroussin }
130961d06d6bSBaptiste Daroussin 
131061d06d6bSBaptiste Daroussin static void
131161d06d6bSBaptiste Daroussin parse_cat(struct mpage *mpage, int fd)
131261d06d6bSBaptiste Daroussin {
131361d06d6bSBaptiste Daroussin 	FILE		*stream;
131461d06d6bSBaptiste Daroussin 	struct mlink	*mlink;
131561d06d6bSBaptiste Daroussin 	char		*line, *p, *title, *sec;
131661d06d6bSBaptiste Daroussin 	size_t		 linesz, plen, titlesz;
131761d06d6bSBaptiste Daroussin 	ssize_t		 len;
131861d06d6bSBaptiste Daroussin 	int		 offs;
131961d06d6bSBaptiste Daroussin 
132061d06d6bSBaptiste Daroussin 	mlink = mpage->mlinks;
132161d06d6bSBaptiste Daroussin 	stream = fd == -1 ? fopen(mlink->file, "r") : fdopen(fd, "r");
132261d06d6bSBaptiste Daroussin 	if (stream == NULL) {
132361d06d6bSBaptiste Daroussin 		if (fd != -1)
132461d06d6bSBaptiste Daroussin 			close(fd);
132561d06d6bSBaptiste Daroussin 		if (warnings)
132661d06d6bSBaptiste Daroussin 			say(mlink->file, "&fopen");
132761d06d6bSBaptiste Daroussin 		return;
132861d06d6bSBaptiste Daroussin 	}
132961d06d6bSBaptiste Daroussin 
133061d06d6bSBaptiste Daroussin 	line = NULL;
133161d06d6bSBaptiste Daroussin 	linesz = 0;
133261d06d6bSBaptiste Daroussin 
133361d06d6bSBaptiste Daroussin 	/* Parse the section number from the header line. */
133461d06d6bSBaptiste Daroussin 
133561d06d6bSBaptiste Daroussin 	while (getline(&line, &linesz, stream) != -1) {
133661d06d6bSBaptiste Daroussin 		if (*line == '\n')
133761d06d6bSBaptiste Daroussin 			continue;
133861d06d6bSBaptiste Daroussin 		if ((sec = strchr(line, '(')) == NULL)
133961d06d6bSBaptiste Daroussin 			break;
134061d06d6bSBaptiste Daroussin 		if ((p = strchr(++sec, ')')) == NULL)
134161d06d6bSBaptiste Daroussin 			break;
134261d06d6bSBaptiste Daroussin 		free(mpage->sec);
134361d06d6bSBaptiste Daroussin 		mpage->sec = mandoc_strndup(sec, p - sec);
134461d06d6bSBaptiste Daroussin 		if (warnings && *mlink->dsec != '\0' &&
134561d06d6bSBaptiste Daroussin 		    strcasecmp(mpage->sec, mlink->dsec))
134661d06d6bSBaptiste Daroussin 			say(mlink->file,
134761d06d6bSBaptiste Daroussin 			    "Section \"%s\" manual in %s directory",
134861d06d6bSBaptiste Daroussin 			    mpage->sec, mlink->dsec);
134961d06d6bSBaptiste Daroussin 		break;
135061d06d6bSBaptiste Daroussin 	}
135161d06d6bSBaptiste Daroussin 
135261d06d6bSBaptiste Daroussin 	/* Skip to first blank line. */
135361d06d6bSBaptiste Daroussin 
135461d06d6bSBaptiste Daroussin 	while (line == NULL || *line != '\n')
135561d06d6bSBaptiste Daroussin 		if (getline(&line, &linesz, stream) == -1)
135661d06d6bSBaptiste Daroussin 			break;
135761d06d6bSBaptiste Daroussin 
135861d06d6bSBaptiste Daroussin 	/*
135961d06d6bSBaptiste Daroussin 	 * Assume the first line that is not indented
136061d06d6bSBaptiste Daroussin 	 * is the first section header.  Skip to it.
136161d06d6bSBaptiste Daroussin 	 */
136261d06d6bSBaptiste Daroussin 
136361d06d6bSBaptiste Daroussin 	while (getline(&line, &linesz, stream) != -1)
136461d06d6bSBaptiste Daroussin 		if (*line != '\n' && *line != ' ')
136561d06d6bSBaptiste Daroussin 			break;
136661d06d6bSBaptiste Daroussin 
136761d06d6bSBaptiste Daroussin 	/*
136861d06d6bSBaptiste Daroussin 	 * Read up until the next section into a buffer.
136961d06d6bSBaptiste Daroussin 	 * Strip the leading and trailing newline from each read line,
137061d06d6bSBaptiste Daroussin 	 * appending a trailing space.
137161d06d6bSBaptiste Daroussin 	 * Ignore empty (whitespace-only) lines.
137261d06d6bSBaptiste Daroussin 	 */
137361d06d6bSBaptiste Daroussin 
137461d06d6bSBaptiste Daroussin 	titlesz = 0;
137561d06d6bSBaptiste Daroussin 	title = NULL;
137661d06d6bSBaptiste Daroussin 
137761d06d6bSBaptiste Daroussin 	while ((len = getline(&line, &linesz, stream)) != -1) {
137861d06d6bSBaptiste Daroussin 		if (*line != ' ')
137961d06d6bSBaptiste Daroussin 			break;
138061d06d6bSBaptiste Daroussin 		offs = 0;
138161d06d6bSBaptiste Daroussin 		while (isspace((unsigned char)line[offs]))
138261d06d6bSBaptiste Daroussin 			offs++;
138361d06d6bSBaptiste Daroussin 		if (line[offs] == '\0')
138461d06d6bSBaptiste Daroussin 			continue;
138561d06d6bSBaptiste Daroussin 		title = mandoc_realloc(title, titlesz + len - offs);
138661d06d6bSBaptiste Daroussin 		memcpy(title + titlesz, line + offs, len - offs);
138761d06d6bSBaptiste Daroussin 		titlesz += len - offs;
138861d06d6bSBaptiste Daroussin 		title[titlesz - 1] = ' ';
138961d06d6bSBaptiste Daroussin 	}
139061d06d6bSBaptiste Daroussin 	free(line);
139161d06d6bSBaptiste Daroussin 
139261d06d6bSBaptiste Daroussin 	/*
139361d06d6bSBaptiste Daroussin 	 * If no page content can be found, or the input line
139461d06d6bSBaptiste Daroussin 	 * is already the next section header, or there is no
139561d06d6bSBaptiste Daroussin 	 * trailing newline, reuse the page title as the page
139661d06d6bSBaptiste Daroussin 	 * description.
139761d06d6bSBaptiste Daroussin 	 */
139861d06d6bSBaptiste Daroussin 
139961d06d6bSBaptiste Daroussin 	if (NULL == title || '\0' == *title) {
140061d06d6bSBaptiste Daroussin 		if (warnings)
140161d06d6bSBaptiste Daroussin 			say(mlink->file, "Cannot find NAME section");
140261d06d6bSBaptiste Daroussin 		fclose(stream);
140361d06d6bSBaptiste Daroussin 		free(title);
140461d06d6bSBaptiste Daroussin 		return;
140561d06d6bSBaptiste Daroussin 	}
140661d06d6bSBaptiste Daroussin 
140761d06d6bSBaptiste Daroussin 	title[titlesz - 1] = '\0';
140861d06d6bSBaptiste Daroussin 
140961d06d6bSBaptiste Daroussin 	/*
141061d06d6bSBaptiste Daroussin 	 * Skip to the first dash.
141161d06d6bSBaptiste Daroussin 	 * Use the remaining line as the description (no more than 70
141261d06d6bSBaptiste Daroussin 	 * bytes).
141361d06d6bSBaptiste Daroussin 	 */
141461d06d6bSBaptiste Daroussin 
141561d06d6bSBaptiste Daroussin 	if (NULL != (p = strstr(title, "- "))) {
141661d06d6bSBaptiste Daroussin 		for (p += 2; ' ' == *p || '\b' == *p; p++)
141761d06d6bSBaptiste Daroussin 			/* Skip to next word. */ ;
141861d06d6bSBaptiste Daroussin 	} else {
141961d06d6bSBaptiste Daroussin 		if (warnings)
142061d06d6bSBaptiste Daroussin 			say(mlink->file, "No dash in title line, "
142161d06d6bSBaptiste Daroussin 			    "reusing \"%s\" as one-line description", title);
142261d06d6bSBaptiste Daroussin 		p = title;
142361d06d6bSBaptiste Daroussin 	}
142461d06d6bSBaptiste Daroussin 
142561d06d6bSBaptiste Daroussin 	plen = strlen(p);
142661d06d6bSBaptiste Daroussin 
142761d06d6bSBaptiste Daroussin 	/* Strip backspace-encoding from line. */
142861d06d6bSBaptiste Daroussin 
142961d06d6bSBaptiste Daroussin 	while (NULL != (line = memchr(p, '\b', plen))) {
143061d06d6bSBaptiste Daroussin 		len = line - p;
143161d06d6bSBaptiste Daroussin 		if (0 == len) {
143261d06d6bSBaptiste Daroussin 			memmove(line, line + 1, plen--);
143361d06d6bSBaptiste Daroussin 			continue;
143461d06d6bSBaptiste Daroussin 		}
143561d06d6bSBaptiste Daroussin 		memmove(line - 1, line + 1, plen - len);
143661d06d6bSBaptiste Daroussin 		plen -= 2;
143761d06d6bSBaptiste Daroussin 	}
143861d06d6bSBaptiste Daroussin 
143961d06d6bSBaptiste Daroussin 	/*
144061d06d6bSBaptiste Daroussin 	 * Cut off excessive one-line descriptions.
144161d06d6bSBaptiste Daroussin 	 * Bad pages are not worth better heuristics.
144261d06d6bSBaptiste Daroussin 	 */
144361d06d6bSBaptiste Daroussin 
144461d06d6bSBaptiste Daroussin 	mpage->desc = mandoc_strndup(p, 150);
144561d06d6bSBaptiste Daroussin 	fclose(stream);
144661d06d6bSBaptiste Daroussin 	free(title);
144761d06d6bSBaptiste Daroussin }
144861d06d6bSBaptiste Daroussin 
144961d06d6bSBaptiste Daroussin /*
145061d06d6bSBaptiste Daroussin  * Put a type/word pair into the word database for this particular file.
145161d06d6bSBaptiste Daroussin  */
145261d06d6bSBaptiste Daroussin static void
145361d06d6bSBaptiste Daroussin putkey(const struct mpage *mpage, char *value, uint64_t type)
145461d06d6bSBaptiste Daroussin {
145561d06d6bSBaptiste Daroussin 	putkeys(mpage, value, strlen(value), type);
145661d06d6bSBaptiste Daroussin }
145761d06d6bSBaptiste Daroussin 
145861d06d6bSBaptiste Daroussin /*
145961d06d6bSBaptiste Daroussin  * Grok all nodes at or below a certain mdoc node into putkey().
146061d06d6bSBaptiste Daroussin  */
146161d06d6bSBaptiste Daroussin static void
146261d06d6bSBaptiste Daroussin putmdockey(const struct mpage *mpage,
146361d06d6bSBaptiste Daroussin 	const struct roff_node *n, uint64_t m, int taboo)
146461d06d6bSBaptiste Daroussin {
146561d06d6bSBaptiste Daroussin 
146661d06d6bSBaptiste Daroussin 	for ( ; NULL != n; n = n->next) {
146761d06d6bSBaptiste Daroussin 		if (n->flags & taboo)
146861d06d6bSBaptiste Daroussin 			continue;
146961d06d6bSBaptiste Daroussin 		if (NULL != n->child)
147061d06d6bSBaptiste Daroussin 			putmdockey(mpage, n->child, m, taboo);
147161d06d6bSBaptiste Daroussin 		if (n->type == ROFFT_TEXT)
147261d06d6bSBaptiste Daroussin 			putkey(mpage, n->string, m);
147361d06d6bSBaptiste Daroussin 	}
147461d06d6bSBaptiste Daroussin }
147561d06d6bSBaptiste Daroussin 
147661d06d6bSBaptiste Daroussin static void
147761d06d6bSBaptiste Daroussin parse_man(struct mpage *mpage, const struct roff_meta *meta,
147861d06d6bSBaptiste Daroussin 	const struct roff_node *n)
147961d06d6bSBaptiste Daroussin {
148061d06d6bSBaptiste Daroussin 	const struct roff_node *head, *body;
148161d06d6bSBaptiste Daroussin 	char		*start, *title;
148261d06d6bSBaptiste Daroussin 	char		 byte;
148361d06d6bSBaptiste Daroussin 	size_t		 sz;
148461d06d6bSBaptiste Daroussin 
148561d06d6bSBaptiste Daroussin 	if (n == NULL)
148661d06d6bSBaptiste Daroussin 		return;
148761d06d6bSBaptiste Daroussin 
148861d06d6bSBaptiste Daroussin 	/*
148961d06d6bSBaptiste Daroussin 	 * We're only searching for one thing: the first text child in
149061d06d6bSBaptiste Daroussin 	 * the BODY of a NAME section.  Since we don't keep track of
149161d06d6bSBaptiste Daroussin 	 * sections in -man, run some hoops to find out whether we're in
149261d06d6bSBaptiste Daroussin 	 * the correct section or not.
149361d06d6bSBaptiste Daroussin 	 */
149461d06d6bSBaptiste Daroussin 
149561d06d6bSBaptiste Daroussin 	if (n->type == ROFFT_BODY && n->tok == MAN_SH) {
149661d06d6bSBaptiste Daroussin 		body = n;
149761d06d6bSBaptiste Daroussin 		if ((head = body->parent->head) != NULL &&
149861d06d6bSBaptiste Daroussin 		    (head = head->child) != NULL &&
149961d06d6bSBaptiste Daroussin 		    head->next == NULL &&
150061d06d6bSBaptiste Daroussin 		    head->type == ROFFT_TEXT &&
150161d06d6bSBaptiste Daroussin 		    strcmp(head->string, "NAME") == 0 &&
150261d06d6bSBaptiste Daroussin 		    body->child != NULL) {
150361d06d6bSBaptiste Daroussin 
150461d06d6bSBaptiste Daroussin 			/*
150561d06d6bSBaptiste Daroussin 			 * Suck the entire NAME section into memory.
150661d06d6bSBaptiste Daroussin 			 * Yes, we might run away.
150761d06d6bSBaptiste Daroussin 			 * But too many manuals have big, spread-out
150861d06d6bSBaptiste Daroussin 			 * NAME sections over many lines.
150961d06d6bSBaptiste Daroussin 			 */
151061d06d6bSBaptiste Daroussin 
151161d06d6bSBaptiste Daroussin 			title = NULL;
151261d06d6bSBaptiste Daroussin 			deroff(&title, body);
151361d06d6bSBaptiste Daroussin 			if (NULL == title)
151461d06d6bSBaptiste Daroussin 				return;
151561d06d6bSBaptiste Daroussin 
151661d06d6bSBaptiste Daroussin 			/*
151761d06d6bSBaptiste Daroussin 			 * Go through a special heuristic dance here.
151861d06d6bSBaptiste Daroussin 			 * Conventionally, one or more manual names are
151961d06d6bSBaptiste Daroussin 			 * comma-specified prior to a whitespace, then a
152061d06d6bSBaptiste Daroussin 			 * dash, then a description.  Try to puzzle out
152161d06d6bSBaptiste Daroussin 			 * the name parts here.
152261d06d6bSBaptiste Daroussin 			 */
152361d06d6bSBaptiste Daroussin 
152461d06d6bSBaptiste Daroussin 			start = title;
152561d06d6bSBaptiste Daroussin 			for ( ;; ) {
152661d06d6bSBaptiste Daroussin 				sz = strcspn(start, " ,");
152761d06d6bSBaptiste Daroussin 				if ('\0' == start[sz])
152861d06d6bSBaptiste Daroussin 					break;
152961d06d6bSBaptiste Daroussin 
153061d06d6bSBaptiste Daroussin 				byte = start[sz];
153161d06d6bSBaptiste Daroussin 				start[sz] = '\0';
153261d06d6bSBaptiste Daroussin 
153361d06d6bSBaptiste Daroussin 				/*
153461d06d6bSBaptiste Daroussin 				 * Assume a stray trailing comma in the
153561d06d6bSBaptiste Daroussin 				 * name list if a name begins with a dash.
153661d06d6bSBaptiste Daroussin 				 */
153761d06d6bSBaptiste Daroussin 
153861d06d6bSBaptiste Daroussin 				if ('-' == start[0] ||
153961d06d6bSBaptiste Daroussin 				    ('\\' == start[0] && '-' == start[1]))
154061d06d6bSBaptiste Daroussin 					break;
154161d06d6bSBaptiste Daroussin 
154261d06d6bSBaptiste Daroussin 				putkey(mpage, start, NAME_TITLE);
154361d06d6bSBaptiste Daroussin 				if ( ! (mpage->name_head_done ||
154461d06d6bSBaptiste Daroussin 				    strcasecmp(start, meta->title))) {
154561d06d6bSBaptiste Daroussin 					putkey(mpage, start, NAME_HEAD);
154661d06d6bSBaptiste Daroussin 					mpage->name_head_done = 1;
154761d06d6bSBaptiste Daroussin 				}
154861d06d6bSBaptiste Daroussin 
154961d06d6bSBaptiste Daroussin 				if (' ' == byte) {
155061d06d6bSBaptiste Daroussin 					start += sz + 1;
155161d06d6bSBaptiste Daroussin 					break;
155261d06d6bSBaptiste Daroussin 				}
155361d06d6bSBaptiste Daroussin 
155461d06d6bSBaptiste Daroussin 				assert(',' == byte);
155561d06d6bSBaptiste Daroussin 				start += sz + 1;
155661d06d6bSBaptiste Daroussin 				while (' ' == *start)
155761d06d6bSBaptiste Daroussin 					start++;
155861d06d6bSBaptiste Daroussin 			}
155961d06d6bSBaptiste Daroussin 
156061d06d6bSBaptiste Daroussin 			if (start == title) {
156161d06d6bSBaptiste Daroussin 				putkey(mpage, start, NAME_TITLE);
156261d06d6bSBaptiste Daroussin 				if ( ! (mpage->name_head_done ||
156361d06d6bSBaptiste Daroussin 				    strcasecmp(start, meta->title))) {
156461d06d6bSBaptiste Daroussin 					putkey(mpage, start, NAME_HEAD);
156561d06d6bSBaptiste Daroussin 					mpage->name_head_done = 1;
156661d06d6bSBaptiste Daroussin 				}
156761d06d6bSBaptiste Daroussin 				free(title);
156861d06d6bSBaptiste Daroussin 				return;
156961d06d6bSBaptiste Daroussin 			}
157061d06d6bSBaptiste Daroussin 
157161d06d6bSBaptiste Daroussin 			while (isspace((unsigned char)*start))
157261d06d6bSBaptiste Daroussin 				start++;
157361d06d6bSBaptiste Daroussin 
157461d06d6bSBaptiste Daroussin 			if (0 == strncmp(start, "-", 1))
157561d06d6bSBaptiste Daroussin 				start += 1;
157661d06d6bSBaptiste Daroussin 			else if (0 == strncmp(start, "\\-\\-", 4))
157761d06d6bSBaptiste Daroussin 				start += 4;
157861d06d6bSBaptiste Daroussin 			else if (0 == strncmp(start, "\\-", 2))
157961d06d6bSBaptiste Daroussin 				start += 2;
158061d06d6bSBaptiste Daroussin 			else if (0 == strncmp(start, "\\(en", 4))
158161d06d6bSBaptiste Daroussin 				start += 4;
158261d06d6bSBaptiste Daroussin 			else if (0 == strncmp(start, "\\(em", 4))
158361d06d6bSBaptiste Daroussin 				start += 4;
158461d06d6bSBaptiste Daroussin 
158561d06d6bSBaptiste Daroussin 			while (' ' == *start)
158661d06d6bSBaptiste Daroussin 				start++;
158761d06d6bSBaptiste Daroussin 
158861d06d6bSBaptiste Daroussin 			/*
158961d06d6bSBaptiste Daroussin 			 * Cut off excessive one-line descriptions.
159061d06d6bSBaptiste Daroussin 			 * Bad pages are not worth better heuristics.
159161d06d6bSBaptiste Daroussin 			 */
159261d06d6bSBaptiste Daroussin 
159361d06d6bSBaptiste Daroussin 			mpage->desc = mandoc_strndup(start, 150);
159461d06d6bSBaptiste Daroussin 			free(title);
159561d06d6bSBaptiste Daroussin 			return;
159661d06d6bSBaptiste Daroussin 		}
159761d06d6bSBaptiste Daroussin 	}
159861d06d6bSBaptiste Daroussin 
159961d06d6bSBaptiste Daroussin 	for (n = n->child; n; n = n->next) {
160061d06d6bSBaptiste Daroussin 		if (NULL != mpage->desc)
160161d06d6bSBaptiste Daroussin 			break;
160261d06d6bSBaptiste Daroussin 		parse_man(mpage, meta, n);
160361d06d6bSBaptiste Daroussin 	}
160461d06d6bSBaptiste Daroussin }
160561d06d6bSBaptiste Daroussin 
160661d06d6bSBaptiste Daroussin static void
160761d06d6bSBaptiste Daroussin parse_mdoc(struct mpage *mpage, const struct roff_meta *meta,
160861d06d6bSBaptiste Daroussin 	const struct roff_node *n)
160961d06d6bSBaptiste Daroussin {
16107295610fSBaptiste Daroussin 	const struct mdoc_handler *handler;
161161d06d6bSBaptiste Daroussin 
161261d06d6bSBaptiste Daroussin 	for (n = n->child; n != NULL; n = n->next) {
16137295610fSBaptiste Daroussin 		if (n->tok == TOKEN_NONE || n->tok < ROFF_MAX)
161461d06d6bSBaptiste Daroussin 			continue;
161561d06d6bSBaptiste Daroussin 		assert(n->tok >= MDOC_Dd && n->tok < MDOC_MAX);
16167295610fSBaptiste Daroussin 		handler = mdoc_handlers + (n->tok - MDOC_Dd);
16177295610fSBaptiste Daroussin 		if (n->flags & handler->taboo)
16187295610fSBaptiste Daroussin 			continue;
16197295610fSBaptiste Daroussin 
162061d06d6bSBaptiste Daroussin 		switch (n->type) {
162161d06d6bSBaptiste Daroussin 		case ROFFT_ELEM:
162261d06d6bSBaptiste Daroussin 		case ROFFT_BLOCK:
162361d06d6bSBaptiste Daroussin 		case ROFFT_HEAD:
162461d06d6bSBaptiste Daroussin 		case ROFFT_BODY:
162561d06d6bSBaptiste Daroussin 		case ROFFT_TAIL:
16267295610fSBaptiste Daroussin 			if (handler->fp != NULL &&
16277295610fSBaptiste Daroussin 			    (*handler->fp)(mpage, meta, n) == 0)
162861d06d6bSBaptiste Daroussin 				break;
16297295610fSBaptiste Daroussin 			if (handler->mask)
163061d06d6bSBaptiste Daroussin 				putmdockey(mpage, n->child,
16317295610fSBaptiste Daroussin 				    handler->mask, handler->taboo);
163261d06d6bSBaptiste Daroussin 			break;
163361d06d6bSBaptiste Daroussin 		default:
163461d06d6bSBaptiste Daroussin 			continue;
163561d06d6bSBaptiste Daroussin 		}
163661d06d6bSBaptiste Daroussin 		if (NULL != n->child)
163761d06d6bSBaptiste Daroussin 			parse_mdoc(mpage, meta, n);
163861d06d6bSBaptiste Daroussin 	}
163961d06d6bSBaptiste Daroussin }
164061d06d6bSBaptiste Daroussin 
164161d06d6bSBaptiste Daroussin static int
164261d06d6bSBaptiste Daroussin parse_mdoc_Fa(struct mpage *mpage, const struct roff_meta *meta,
164361d06d6bSBaptiste Daroussin 	const struct roff_node *n)
164461d06d6bSBaptiste Daroussin {
164561d06d6bSBaptiste Daroussin 	uint64_t mask;
164661d06d6bSBaptiste Daroussin 
164761d06d6bSBaptiste Daroussin 	mask = TYPE_Fa;
164861d06d6bSBaptiste Daroussin 	if (n->sec == SEC_SYNOPSIS)
164961d06d6bSBaptiste Daroussin 		mask |= TYPE_Vt;
165061d06d6bSBaptiste Daroussin 
165161d06d6bSBaptiste Daroussin 	putmdockey(mpage, n->child, mask, 0);
165261d06d6bSBaptiste Daroussin 	return 0;
165361d06d6bSBaptiste Daroussin }
165461d06d6bSBaptiste Daroussin 
165561d06d6bSBaptiste Daroussin static int
165661d06d6bSBaptiste Daroussin parse_mdoc_Fd(struct mpage *mpage, const struct roff_meta *meta,
165761d06d6bSBaptiste Daroussin 	const struct roff_node *n)
165861d06d6bSBaptiste Daroussin {
165961d06d6bSBaptiste Daroussin 	char		*start, *end;
166061d06d6bSBaptiste Daroussin 	size_t		 sz;
166161d06d6bSBaptiste Daroussin 
166261d06d6bSBaptiste Daroussin 	if (SEC_SYNOPSIS != n->sec ||
166361d06d6bSBaptiste Daroussin 	    NULL == (n = n->child) ||
166461d06d6bSBaptiste Daroussin 	    n->type != ROFFT_TEXT)
166561d06d6bSBaptiste Daroussin 		return 0;
166661d06d6bSBaptiste Daroussin 
166761d06d6bSBaptiste Daroussin 	/*
166861d06d6bSBaptiste Daroussin 	 * Only consider those `Fd' macro fields that begin with an
166961d06d6bSBaptiste Daroussin 	 * "inclusion" token (versus, e.g., #define).
167061d06d6bSBaptiste Daroussin 	 */
167161d06d6bSBaptiste Daroussin 
167261d06d6bSBaptiste Daroussin 	if (strcmp("#include", n->string))
167361d06d6bSBaptiste Daroussin 		return 0;
167461d06d6bSBaptiste Daroussin 
167561d06d6bSBaptiste Daroussin 	if ((n = n->next) == NULL || n->type != ROFFT_TEXT)
167661d06d6bSBaptiste Daroussin 		return 0;
167761d06d6bSBaptiste Daroussin 
167861d06d6bSBaptiste Daroussin 	/*
167961d06d6bSBaptiste Daroussin 	 * Strip away the enclosing angle brackets and make sure we're
168061d06d6bSBaptiste Daroussin 	 * not zero-length.
168161d06d6bSBaptiste Daroussin 	 */
168261d06d6bSBaptiste Daroussin 
168361d06d6bSBaptiste Daroussin 	start = n->string;
168461d06d6bSBaptiste Daroussin 	if ('<' == *start || '"' == *start)
168561d06d6bSBaptiste Daroussin 		start++;
168661d06d6bSBaptiste Daroussin 
168761d06d6bSBaptiste Daroussin 	if (0 == (sz = strlen(start)))
168861d06d6bSBaptiste Daroussin 		return 0;
168961d06d6bSBaptiste Daroussin 
169061d06d6bSBaptiste Daroussin 	end = &start[(int)sz - 1];
169161d06d6bSBaptiste Daroussin 	if ('>' == *end || '"' == *end)
169261d06d6bSBaptiste Daroussin 		end--;
169361d06d6bSBaptiste Daroussin 
169461d06d6bSBaptiste Daroussin 	if (end > start)
169561d06d6bSBaptiste Daroussin 		putkeys(mpage, start, end - start + 1, TYPE_In);
169661d06d6bSBaptiste Daroussin 	return 0;
169761d06d6bSBaptiste Daroussin }
169861d06d6bSBaptiste Daroussin 
169961d06d6bSBaptiste Daroussin static void
170061d06d6bSBaptiste Daroussin parse_mdoc_fname(struct mpage *mpage, const struct roff_node *n)
170161d06d6bSBaptiste Daroussin {
170261d06d6bSBaptiste Daroussin 	char	*cp;
170361d06d6bSBaptiste Daroussin 	size_t	 sz;
170461d06d6bSBaptiste Daroussin 
170561d06d6bSBaptiste Daroussin 	if (n->type != ROFFT_TEXT)
170661d06d6bSBaptiste Daroussin 		return;
170761d06d6bSBaptiste Daroussin 
170861d06d6bSBaptiste Daroussin 	/* Skip function pointer punctuation. */
170961d06d6bSBaptiste Daroussin 
171061d06d6bSBaptiste Daroussin 	cp = n->string;
171161d06d6bSBaptiste Daroussin 	while (*cp == '(' || *cp == '*')
171261d06d6bSBaptiste Daroussin 		cp++;
171361d06d6bSBaptiste Daroussin 	sz = strcspn(cp, "()");
171461d06d6bSBaptiste Daroussin 
171561d06d6bSBaptiste Daroussin 	putkeys(mpage, cp, sz, TYPE_Fn);
171661d06d6bSBaptiste Daroussin 	if (n->sec == SEC_SYNOPSIS)
171761d06d6bSBaptiste Daroussin 		putkeys(mpage, cp, sz, NAME_SYN);
171861d06d6bSBaptiste Daroussin }
171961d06d6bSBaptiste Daroussin 
172061d06d6bSBaptiste Daroussin static int
172161d06d6bSBaptiste Daroussin parse_mdoc_Fn(struct mpage *mpage, const struct roff_meta *meta,
172261d06d6bSBaptiste Daroussin 	const struct roff_node *n)
172361d06d6bSBaptiste Daroussin {
172461d06d6bSBaptiste Daroussin 	uint64_t mask;
172561d06d6bSBaptiste Daroussin 
172661d06d6bSBaptiste Daroussin 	if (n->child == NULL)
172761d06d6bSBaptiste Daroussin 		return 0;
172861d06d6bSBaptiste Daroussin 
172961d06d6bSBaptiste Daroussin 	parse_mdoc_fname(mpage, n->child);
173061d06d6bSBaptiste Daroussin 
173161d06d6bSBaptiste Daroussin 	n = n->child->next;
173261d06d6bSBaptiste Daroussin 	if (n != NULL && n->type == ROFFT_TEXT) {
173361d06d6bSBaptiste Daroussin 		mask = TYPE_Fa;
173461d06d6bSBaptiste Daroussin 		if (n->sec == SEC_SYNOPSIS)
173561d06d6bSBaptiste Daroussin 			mask |= TYPE_Vt;
173661d06d6bSBaptiste Daroussin 		putmdockey(mpage, n, mask, 0);
173761d06d6bSBaptiste Daroussin 	}
173861d06d6bSBaptiste Daroussin 
173961d06d6bSBaptiste Daroussin 	return 0;
174061d06d6bSBaptiste Daroussin }
174161d06d6bSBaptiste Daroussin 
174261d06d6bSBaptiste Daroussin static int
174361d06d6bSBaptiste Daroussin parse_mdoc_Fo(struct mpage *mpage, const struct roff_meta *meta,
174461d06d6bSBaptiste Daroussin 	const struct roff_node *n)
174561d06d6bSBaptiste Daroussin {
174661d06d6bSBaptiste Daroussin 
174761d06d6bSBaptiste Daroussin 	if (n->type != ROFFT_HEAD)
174861d06d6bSBaptiste Daroussin 		return 1;
174961d06d6bSBaptiste Daroussin 
175061d06d6bSBaptiste Daroussin 	if (n->child != NULL)
175161d06d6bSBaptiste Daroussin 		parse_mdoc_fname(mpage, n->child);
175261d06d6bSBaptiste Daroussin 
175361d06d6bSBaptiste Daroussin 	return 0;
175461d06d6bSBaptiste Daroussin }
175561d06d6bSBaptiste Daroussin 
175661d06d6bSBaptiste Daroussin static int
175761d06d6bSBaptiste Daroussin parse_mdoc_Va(struct mpage *mpage, const struct roff_meta *meta,
175861d06d6bSBaptiste Daroussin 	const struct roff_node *n)
175961d06d6bSBaptiste Daroussin {
176061d06d6bSBaptiste Daroussin 	char *cp;
176161d06d6bSBaptiste Daroussin 
176261d06d6bSBaptiste Daroussin 	if (n->type != ROFFT_ELEM && n->type != ROFFT_BODY)
176361d06d6bSBaptiste Daroussin 		return 0;
176461d06d6bSBaptiste Daroussin 
176561d06d6bSBaptiste Daroussin 	if (n->child != NULL &&
176661d06d6bSBaptiste Daroussin 	    n->child->next == NULL &&
176761d06d6bSBaptiste Daroussin 	    n->child->type == ROFFT_TEXT)
176861d06d6bSBaptiste Daroussin 		return 1;
176961d06d6bSBaptiste Daroussin 
177061d06d6bSBaptiste Daroussin 	cp = NULL;
177161d06d6bSBaptiste Daroussin 	deroff(&cp, n);
177261d06d6bSBaptiste Daroussin 	if (cp != NULL) {
177361d06d6bSBaptiste Daroussin 		putkey(mpage, cp, TYPE_Vt | (n->tok == MDOC_Va ||
177461d06d6bSBaptiste Daroussin 		    n->type == ROFFT_BODY ? TYPE_Va : 0));
177561d06d6bSBaptiste Daroussin 		free(cp);
177661d06d6bSBaptiste Daroussin 	}
177761d06d6bSBaptiste Daroussin 
177861d06d6bSBaptiste Daroussin 	return 0;
177961d06d6bSBaptiste Daroussin }
178061d06d6bSBaptiste Daroussin 
178161d06d6bSBaptiste Daroussin static int
178261d06d6bSBaptiste Daroussin parse_mdoc_Xr(struct mpage *mpage, const struct roff_meta *meta,
178361d06d6bSBaptiste Daroussin 	const struct roff_node *n)
178461d06d6bSBaptiste Daroussin {
178561d06d6bSBaptiste Daroussin 	char	*cp;
178661d06d6bSBaptiste Daroussin 
178761d06d6bSBaptiste Daroussin 	if (NULL == (n = n->child))
178861d06d6bSBaptiste Daroussin 		return 0;
178961d06d6bSBaptiste Daroussin 
179061d06d6bSBaptiste Daroussin 	if (NULL == n->next) {
179161d06d6bSBaptiste Daroussin 		putkey(mpage, n->string, TYPE_Xr);
179261d06d6bSBaptiste Daroussin 		return 0;
179361d06d6bSBaptiste Daroussin 	}
179461d06d6bSBaptiste Daroussin 
179561d06d6bSBaptiste Daroussin 	mandoc_asprintf(&cp, "%s(%s)", n->string, n->next->string);
179661d06d6bSBaptiste Daroussin 	putkey(mpage, cp, TYPE_Xr);
179761d06d6bSBaptiste Daroussin 	free(cp);
179861d06d6bSBaptiste Daroussin 	return 0;
179961d06d6bSBaptiste Daroussin }
180061d06d6bSBaptiste Daroussin 
180161d06d6bSBaptiste Daroussin static int
180261d06d6bSBaptiste Daroussin parse_mdoc_Nd(struct mpage *mpage, const struct roff_meta *meta,
180361d06d6bSBaptiste Daroussin 	const struct roff_node *n)
180461d06d6bSBaptiste Daroussin {
180561d06d6bSBaptiste Daroussin 
180661d06d6bSBaptiste Daroussin 	if (n->type == ROFFT_BODY)
180761d06d6bSBaptiste Daroussin 		deroff(&mpage->desc, n);
180861d06d6bSBaptiste Daroussin 	return 0;
180961d06d6bSBaptiste Daroussin }
181061d06d6bSBaptiste Daroussin 
181161d06d6bSBaptiste Daroussin static int
181261d06d6bSBaptiste Daroussin parse_mdoc_Nm(struct mpage *mpage, const struct roff_meta *meta,
181361d06d6bSBaptiste Daroussin 	const struct roff_node *n)
181461d06d6bSBaptiste Daroussin {
181561d06d6bSBaptiste Daroussin 
181661d06d6bSBaptiste Daroussin 	if (SEC_NAME == n->sec)
181761d06d6bSBaptiste Daroussin 		putmdockey(mpage, n->child, NAME_TITLE, 0);
181861d06d6bSBaptiste Daroussin 	else if (n->sec == SEC_SYNOPSIS && n->type == ROFFT_HEAD) {
181961d06d6bSBaptiste Daroussin 		if (n->child == NULL)
182061d06d6bSBaptiste Daroussin 			putkey(mpage, meta->name, NAME_SYN);
182161d06d6bSBaptiste Daroussin 		else
182261d06d6bSBaptiste Daroussin 			putmdockey(mpage, n->child, NAME_SYN, 0);
182361d06d6bSBaptiste Daroussin 	}
182461d06d6bSBaptiste Daroussin 	if ( ! (mpage->name_head_done ||
182561d06d6bSBaptiste Daroussin 	    n->child == NULL || n->child->string == NULL ||
182661d06d6bSBaptiste Daroussin 	    strcasecmp(n->child->string, meta->title))) {
182761d06d6bSBaptiste Daroussin 		putkey(mpage, n->child->string, NAME_HEAD);
182861d06d6bSBaptiste Daroussin 		mpage->name_head_done = 1;
182961d06d6bSBaptiste Daroussin 	}
183061d06d6bSBaptiste Daroussin 	return 0;
183161d06d6bSBaptiste Daroussin }
183261d06d6bSBaptiste Daroussin 
183361d06d6bSBaptiste Daroussin static int
183461d06d6bSBaptiste Daroussin parse_mdoc_Sh(struct mpage *mpage, const struct roff_meta *meta,
183561d06d6bSBaptiste Daroussin 	const struct roff_node *n)
183661d06d6bSBaptiste Daroussin {
183761d06d6bSBaptiste Daroussin 
183861d06d6bSBaptiste Daroussin 	return n->sec == SEC_CUSTOM && n->type == ROFFT_HEAD;
183961d06d6bSBaptiste Daroussin }
184061d06d6bSBaptiste Daroussin 
184161d06d6bSBaptiste Daroussin static int
184261d06d6bSBaptiste Daroussin parse_mdoc_head(struct mpage *mpage, const struct roff_meta *meta,
184361d06d6bSBaptiste Daroussin 	const struct roff_node *n)
184461d06d6bSBaptiste Daroussin {
184561d06d6bSBaptiste Daroussin 
184661d06d6bSBaptiste Daroussin 	return n->type == ROFFT_HEAD;
184761d06d6bSBaptiste Daroussin }
184861d06d6bSBaptiste Daroussin 
184961d06d6bSBaptiste Daroussin /*
185061d06d6bSBaptiste Daroussin  * Add a string to the hash table for the current manual.
185161d06d6bSBaptiste Daroussin  * Each string has a bitmask telling which macros it belongs to.
185261d06d6bSBaptiste Daroussin  * When we finish the manual, we'll dump the table.
185361d06d6bSBaptiste Daroussin  */
185461d06d6bSBaptiste Daroussin static void
185561d06d6bSBaptiste Daroussin putkeys(const struct mpage *mpage, char *cp, size_t sz, uint64_t v)
185661d06d6bSBaptiste Daroussin {
185761d06d6bSBaptiste Daroussin 	struct ohash	*htab;
185861d06d6bSBaptiste Daroussin 	struct str	*s;
185961d06d6bSBaptiste Daroussin 	const char	*end;
186061d06d6bSBaptiste Daroussin 	unsigned int	 slot;
186161d06d6bSBaptiste Daroussin 	int		 i, mustfree;
186261d06d6bSBaptiste Daroussin 
186361d06d6bSBaptiste Daroussin 	if (0 == sz)
186461d06d6bSBaptiste Daroussin 		return;
186561d06d6bSBaptiste Daroussin 
186661d06d6bSBaptiste Daroussin 	mustfree = render_string(&cp, &sz);
186761d06d6bSBaptiste Daroussin 
186861d06d6bSBaptiste Daroussin 	if (TYPE_Nm & v) {
186961d06d6bSBaptiste Daroussin 		htab = &names;
187061d06d6bSBaptiste Daroussin 		v &= name_mask;
187161d06d6bSBaptiste Daroussin 		if (v & NAME_FIRST)
187261d06d6bSBaptiste Daroussin 			name_mask &= ~NAME_FIRST;
187361d06d6bSBaptiste Daroussin 		if (debug > 1)
187461d06d6bSBaptiste Daroussin 			say(mpage->mlinks->file,
187561d06d6bSBaptiste Daroussin 			    "Adding name %*s, bits=0x%llx", (int)sz, cp,
187661d06d6bSBaptiste Daroussin 			    (unsigned long long)v);
187761d06d6bSBaptiste Daroussin 	} else {
187861d06d6bSBaptiste Daroussin 		htab = &strings;
187961d06d6bSBaptiste Daroussin 		if (debug > 1)
188061d06d6bSBaptiste Daroussin 		    for (i = 0; i < KEY_MAX; i++)
188161d06d6bSBaptiste Daroussin 			if ((uint64_t)1 << i & v)
188261d06d6bSBaptiste Daroussin 			    say(mpage->mlinks->file,
188361d06d6bSBaptiste Daroussin 				"Adding key %s=%*s",
188461d06d6bSBaptiste Daroussin 				mansearch_keynames[i], (int)sz, cp);
188561d06d6bSBaptiste Daroussin 	}
188661d06d6bSBaptiste Daroussin 
188761d06d6bSBaptiste Daroussin 	end = cp + sz;
188861d06d6bSBaptiste Daroussin 	slot = ohash_qlookupi(htab, cp, &end);
188961d06d6bSBaptiste Daroussin 	s = ohash_find(htab, slot);
189061d06d6bSBaptiste Daroussin 
189161d06d6bSBaptiste Daroussin 	if (NULL != s && mpage == s->mpage) {
189261d06d6bSBaptiste Daroussin 		s->mask |= v;
189361d06d6bSBaptiste Daroussin 		return;
189461d06d6bSBaptiste Daroussin 	} else if (NULL == s) {
189561d06d6bSBaptiste Daroussin 		s = mandoc_calloc(1, sizeof(struct str) + sz + 1);
189661d06d6bSBaptiste Daroussin 		memcpy(s->key, cp, sz);
189761d06d6bSBaptiste Daroussin 		ohash_insert(htab, slot, s);
189861d06d6bSBaptiste Daroussin 	}
189961d06d6bSBaptiste Daroussin 	s->mpage = mpage;
190061d06d6bSBaptiste Daroussin 	s->mask = v;
190161d06d6bSBaptiste Daroussin 
190261d06d6bSBaptiste Daroussin 	if (mustfree)
190361d06d6bSBaptiste Daroussin 		free(cp);
190461d06d6bSBaptiste Daroussin }
190561d06d6bSBaptiste Daroussin 
190661d06d6bSBaptiste Daroussin /*
190761d06d6bSBaptiste Daroussin  * Take a Unicode codepoint and produce its UTF-8 encoding.
190861d06d6bSBaptiste Daroussin  * This isn't the best way to do this, but it works.
190961d06d6bSBaptiste Daroussin  * The magic numbers are from the UTF-8 packaging.
1910*c1c95addSBrooks Davis  * Read the UTF-8 spec or the utf8(7) manual page for details.
191161d06d6bSBaptiste Daroussin  */
191261d06d6bSBaptiste Daroussin static size_t
1913*c1c95addSBrooks Davis utf8(unsigned int cp, char out[5])
191461d06d6bSBaptiste Daroussin {
191561d06d6bSBaptiste Daroussin 	size_t		 rc;
191661d06d6bSBaptiste Daroussin 
1917*c1c95addSBrooks Davis 	if (cp <= 0x7f) {
191861d06d6bSBaptiste Daroussin 		rc = 1;
191961d06d6bSBaptiste Daroussin 		out[0] = (char)cp;
1920*c1c95addSBrooks Davis 	} else if (cp <= 0x7ff) {
192161d06d6bSBaptiste Daroussin 		rc = 2;
192261d06d6bSBaptiste Daroussin 		out[0] = (cp >> 6  & 31) | 192;
192361d06d6bSBaptiste Daroussin 		out[1] = (cp       & 63) | 128;
1924*c1c95addSBrooks Davis 	} else if (cp >= 0xd800 && cp <= 0xdfff) {
1925*c1c95addSBrooks Davis 		rc = 0; /* reject UTF-16 surrogate */
1926*c1c95addSBrooks Davis 	} else if (cp <= 0xffff) {
192761d06d6bSBaptiste Daroussin 		rc = 3;
192861d06d6bSBaptiste Daroussin 		out[0] = (cp >> 12 & 15) | 224;
192961d06d6bSBaptiste Daroussin 		out[1] = (cp >> 6  & 63) | 128;
193061d06d6bSBaptiste Daroussin 		out[2] = (cp       & 63) | 128;
1931*c1c95addSBrooks Davis 	} else if (cp <= 0x10ffff) {
193261d06d6bSBaptiste Daroussin 		rc = 4;
193361d06d6bSBaptiste Daroussin 		out[0] = (cp >> 18 &  7) | 240;
193461d06d6bSBaptiste Daroussin 		out[1] = (cp >> 12 & 63) | 128;
193561d06d6bSBaptiste Daroussin 		out[2] = (cp >> 6  & 63) | 128;
193661d06d6bSBaptiste Daroussin 		out[3] = (cp       & 63) | 128;
193761d06d6bSBaptiste Daroussin 	} else
1938*c1c95addSBrooks Davis 		rc = 0;
193961d06d6bSBaptiste Daroussin 
194061d06d6bSBaptiste Daroussin 	out[rc] = '\0';
194161d06d6bSBaptiste Daroussin 	return rc;
194261d06d6bSBaptiste Daroussin }
194361d06d6bSBaptiste Daroussin 
194461d06d6bSBaptiste Daroussin /*
194561d06d6bSBaptiste Daroussin  * If the string contains escape sequences,
194661d06d6bSBaptiste Daroussin  * replace it with an allocated rendering and return 1,
194761d06d6bSBaptiste Daroussin  * such that the caller can free it after use.
194861d06d6bSBaptiste Daroussin  * Otherwise, do nothing and return 0.
194961d06d6bSBaptiste Daroussin  */
195061d06d6bSBaptiste Daroussin static int
195161d06d6bSBaptiste Daroussin render_string(char **public, size_t *psz)
195261d06d6bSBaptiste Daroussin {
195361d06d6bSBaptiste Daroussin 	const char	*src, *scp, *addcp, *seq;
195461d06d6bSBaptiste Daroussin 	char		*dst;
195561d06d6bSBaptiste Daroussin 	size_t		 ssz, dsz, addsz;
195661d06d6bSBaptiste Daroussin 	char		 utfbuf[7], res[6];
195761d06d6bSBaptiste Daroussin 	int		 seqlen, unicode;
195861d06d6bSBaptiste Daroussin 
195961d06d6bSBaptiste Daroussin 	res[0] = '\\';
196061d06d6bSBaptiste Daroussin 	res[1] = '\t';
196161d06d6bSBaptiste Daroussin 	res[2] = ASCII_NBRSP;
196261d06d6bSBaptiste Daroussin 	res[3] = ASCII_HYPH;
196361d06d6bSBaptiste Daroussin 	res[4] = ASCII_BREAK;
196461d06d6bSBaptiste Daroussin 	res[5] = '\0';
196561d06d6bSBaptiste Daroussin 
196661d06d6bSBaptiste Daroussin 	src = scp = *public;
196761d06d6bSBaptiste Daroussin 	ssz = *psz;
196861d06d6bSBaptiste Daroussin 	dst = NULL;
196961d06d6bSBaptiste Daroussin 	dsz = 0;
197061d06d6bSBaptiste Daroussin 
197161d06d6bSBaptiste Daroussin 	while (scp < src + *psz) {
197261d06d6bSBaptiste Daroussin 
197361d06d6bSBaptiste Daroussin 		/* Leave normal characters unchanged. */
197461d06d6bSBaptiste Daroussin 
197561d06d6bSBaptiste Daroussin 		if (strchr(res, *scp) == NULL) {
197661d06d6bSBaptiste Daroussin 			if (dst != NULL)
197761d06d6bSBaptiste Daroussin 				dst[dsz++] = *scp;
197861d06d6bSBaptiste Daroussin 			scp++;
197961d06d6bSBaptiste Daroussin 			continue;
198061d06d6bSBaptiste Daroussin 		}
198161d06d6bSBaptiste Daroussin 
198261d06d6bSBaptiste Daroussin 		/*
198361d06d6bSBaptiste Daroussin 		 * Found something that requires replacing,
198461d06d6bSBaptiste Daroussin 		 * make sure we have a destination buffer.
198561d06d6bSBaptiste Daroussin 		 */
198661d06d6bSBaptiste Daroussin 
198761d06d6bSBaptiste Daroussin 		if (dst == NULL) {
198861d06d6bSBaptiste Daroussin 			dst = mandoc_malloc(ssz + 1);
198961d06d6bSBaptiste Daroussin 			dsz = scp - src;
199061d06d6bSBaptiste Daroussin 			memcpy(dst, src, dsz);
199161d06d6bSBaptiste Daroussin 		}
199261d06d6bSBaptiste Daroussin 
199361d06d6bSBaptiste Daroussin 		/* Handle single-char special characters. */
199461d06d6bSBaptiste Daroussin 
199561d06d6bSBaptiste Daroussin 		switch (*scp) {
199661d06d6bSBaptiste Daroussin 		case '\\':
199761d06d6bSBaptiste Daroussin 			break;
199861d06d6bSBaptiste Daroussin 		case '\t':
199961d06d6bSBaptiste Daroussin 		case ASCII_NBRSP:
200061d06d6bSBaptiste Daroussin 			dst[dsz++] = ' ';
200161d06d6bSBaptiste Daroussin 			scp++;
200261d06d6bSBaptiste Daroussin 			continue;
200361d06d6bSBaptiste Daroussin 		case ASCII_HYPH:
200461d06d6bSBaptiste Daroussin 			dst[dsz++] = '-';
200561d06d6bSBaptiste Daroussin 			/* FALLTHROUGH */
200661d06d6bSBaptiste Daroussin 		case ASCII_BREAK:
200761d06d6bSBaptiste Daroussin 			scp++;
200861d06d6bSBaptiste Daroussin 			continue;
200961d06d6bSBaptiste Daroussin 		default:
201061d06d6bSBaptiste Daroussin 			abort();
201161d06d6bSBaptiste Daroussin 		}
201261d06d6bSBaptiste Daroussin 
201361d06d6bSBaptiste Daroussin 		/*
201461d06d6bSBaptiste Daroussin 		 * Found an escape sequence.
201561d06d6bSBaptiste Daroussin 		 * Read past the slash, then parse it.
201661d06d6bSBaptiste Daroussin 		 * Ignore everything except characters.
201761d06d6bSBaptiste Daroussin 		 */
201861d06d6bSBaptiste Daroussin 
201961d06d6bSBaptiste Daroussin 		scp++;
2020*c1c95addSBrooks Davis 		switch (mandoc_escape(&scp, &seq, &seqlen)) {
2021*c1c95addSBrooks Davis 		case ESCAPE_UNICODE:
2022*c1c95addSBrooks Davis 			unicode = mchars_num2uc(seq + 1, seqlen - 1);
2023*c1c95addSBrooks Davis 			break;
2024*c1c95addSBrooks Davis 		case ESCAPE_NUMBERED:
2025*c1c95addSBrooks Davis 			unicode = mchars_num2char(seq, seqlen);
2026*c1c95addSBrooks Davis 			break;
2027*c1c95addSBrooks Davis 		case ESCAPE_SPECIAL:
2028*c1c95addSBrooks Davis 			unicode = mchars_spec2cp(seq, seqlen);
2029*c1c95addSBrooks Davis 			break;
2030*c1c95addSBrooks Davis 		default:
2031*c1c95addSBrooks Davis 			unicode = -1;
2032*c1c95addSBrooks Davis 			break;
2033*c1c95addSBrooks Davis 		}
2034*c1c95addSBrooks Davis 		if (unicode <= 0)
203561d06d6bSBaptiste Daroussin 			continue;
203661d06d6bSBaptiste Daroussin 
203761d06d6bSBaptiste Daroussin 		/*
203861d06d6bSBaptiste Daroussin 		 * Render the special character
203961d06d6bSBaptiste Daroussin 		 * as either UTF-8 or ASCII.
204061d06d6bSBaptiste Daroussin 		 */
204161d06d6bSBaptiste Daroussin 
204261d06d6bSBaptiste Daroussin 		if (write_utf8) {
204361d06d6bSBaptiste Daroussin 			addsz = utf8(unicode, utfbuf);
204461d06d6bSBaptiste Daroussin 			if (addsz == 0)
204561d06d6bSBaptiste Daroussin 				continue;
204661d06d6bSBaptiste Daroussin 			addcp = utfbuf;
204761d06d6bSBaptiste Daroussin 		} else {
2048*c1c95addSBrooks Davis 			addcp = mchars_uc2str(unicode);
204961d06d6bSBaptiste Daroussin 			if (addcp == NULL)
205061d06d6bSBaptiste Daroussin 				continue;
2051*c1c95addSBrooks Davis 			if (*addcp == ASCII_NBRSP)
205261d06d6bSBaptiste Daroussin 				addcp = " ";
2053*c1c95addSBrooks Davis 			addsz = strlen(addcp);
205461d06d6bSBaptiste Daroussin 		}
205561d06d6bSBaptiste Daroussin 
205661d06d6bSBaptiste Daroussin 		/* Copy the rendered glyph into the stream. */
205761d06d6bSBaptiste Daroussin 
205861d06d6bSBaptiste Daroussin 		ssz += addsz;
205961d06d6bSBaptiste Daroussin 		dst = mandoc_realloc(dst, ssz + 1);
206061d06d6bSBaptiste Daroussin 		memcpy(dst + dsz, addcp, addsz);
206161d06d6bSBaptiste Daroussin 		dsz += addsz;
206261d06d6bSBaptiste Daroussin 	}
206361d06d6bSBaptiste Daroussin 	if (dst != NULL) {
206461d06d6bSBaptiste Daroussin 		*public = dst;
206561d06d6bSBaptiste Daroussin 		*psz = dsz;
206661d06d6bSBaptiste Daroussin 	}
206761d06d6bSBaptiste Daroussin 
206861d06d6bSBaptiste Daroussin 	/* Trim trailing whitespace and NUL-terminate. */
206961d06d6bSBaptiste Daroussin 
207061d06d6bSBaptiste Daroussin 	while (*psz > 0 && (*public)[*psz - 1] == ' ')
207161d06d6bSBaptiste Daroussin 		--*psz;
207261d06d6bSBaptiste Daroussin 	if (dst != NULL) {
207361d06d6bSBaptiste Daroussin 		(*public)[*psz] = '\0';
207461d06d6bSBaptiste Daroussin 		return 1;
207561d06d6bSBaptiste Daroussin 	} else
207661d06d6bSBaptiste Daroussin 		return 0;
207761d06d6bSBaptiste Daroussin }
207861d06d6bSBaptiste Daroussin 
207961d06d6bSBaptiste Daroussin static void
208061d06d6bSBaptiste Daroussin dbadd_mlink(const struct mlink *mlink)
208161d06d6bSBaptiste Daroussin {
208261d06d6bSBaptiste Daroussin 	dba_page_alias(mlink->mpage->dba, mlink->name, NAME_FILE);
208361d06d6bSBaptiste Daroussin 	dba_page_add(mlink->mpage->dba, DBP_SECT, mlink->dsec);
208461d06d6bSBaptiste Daroussin 	dba_page_add(mlink->mpage->dba, DBP_SECT, mlink->fsec);
208561d06d6bSBaptiste Daroussin 	dba_page_add(mlink->mpage->dba, DBP_ARCH, mlink->arch);
208661d06d6bSBaptiste Daroussin 	dba_page_add(mlink->mpage->dba, DBP_FILE, mlink->file);
208761d06d6bSBaptiste Daroussin }
208861d06d6bSBaptiste Daroussin 
208961d06d6bSBaptiste Daroussin /*
209061d06d6bSBaptiste Daroussin  * Flush the current page's terms (and their bits) into the database.
209161d06d6bSBaptiste Daroussin  * Also, handle escape sequences at the last possible moment.
209261d06d6bSBaptiste Daroussin  */
209361d06d6bSBaptiste Daroussin static void
209461d06d6bSBaptiste Daroussin dbadd(struct dba *dba, struct mpage *mpage)
209561d06d6bSBaptiste Daroussin {
209661d06d6bSBaptiste Daroussin 	struct mlink	*mlink;
209761d06d6bSBaptiste Daroussin 	struct str	*key;
209861d06d6bSBaptiste Daroussin 	char		*cp;
209961d06d6bSBaptiste Daroussin 	uint64_t	 mask;
210061d06d6bSBaptiste Daroussin 	size_t		 i;
210161d06d6bSBaptiste Daroussin 	unsigned int	 slot;
210261d06d6bSBaptiste Daroussin 	int		 mustfree;
210361d06d6bSBaptiste Daroussin 
210461d06d6bSBaptiste Daroussin 	mlink = mpage->mlinks;
210561d06d6bSBaptiste Daroussin 
210661d06d6bSBaptiste Daroussin 	if (nodb) {
210761d06d6bSBaptiste Daroussin 		for (key = ohash_first(&names, &slot); NULL != key;
210861d06d6bSBaptiste Daroussin 		     key = ohash_next(&names, &slot))
210961d06d6bSBaptiste Daroussin 			free(key);
211061d06d6bSBaptiste Daroussin 		for (key = ohash_first(&strings, &slot); NULL != key;
211161d06d6bSBaptiste Daroussin 		     key = ohash_next(&strings, &slot))
211261d06d6bSBaptiste Daroussin 			free(key);
211361d06d6bSBaptiste Daroussin 		if (0 == debug)
211461d06d6bSBaptiste Daroussin 			return;
211561d06d6bSBaptiste Daroussin 		while (NULL != mlink) {
211661d06d6bSBaptiste Daroussin 			fputs(mlink->name, stdout);
211761d06d6bSBaptiste Daroussin 			if (NULL == mlink->next ||
211861d06d6bSBaptiste Daroussin 			    strcmp(mlink->dsec, mlink->next->dsec) ||
211961d06d6bSBaptiste Daroussin 			    strcmp(mlink->fsec, mlink->next->fsec) ||
212061d06d6bSBaptiste Daroussin 			    strcmp(mlink->arch, mlink->next->arch)) {
212161d06d6bSBaptiste Daroussin 				putchar('(');
212261d06d6bSBaptiste Daroussin 				if ('\0' == *mlink->dsec)
212361d06d6bSBaptiste Daroussin 					fputs(mlink->fsec, stdout);
212461d06d6bSBaptiste Daroussin 				else
212561d06d6bSBaptiste Daroussin 					fputs(mlink->dsec, stdout);
212661d06d6bSBaptiste Daroussin 				if ('\0' != *mlink->arch)
212761d06d6bSBaptiste Daroussin 					printf("/%s", mlink->arch);
212861d06d6bSBaptiste Daroussin 				putchar(')');
212961d06d6bSBaptiste Daroussin 			}
213061d06d6bSBaptiste Daroussin 			mlink = mlink->next;
213161d06d6bSBaptiste Daroussin 			if (NULL != mlink)
213261d06d6bSBaptiste Daroussin 				fputs(", ", stdout);
213361d06d6bSBaptiste Daroussin 		}
213461d06d6bSBaptiste Daroussin 		printf(" - %s\n", mpage->desc);
213561d06d6bSBaptiste Daroussin 		return;
213661d06d6bSBaptiste Daroussin 	}
213761d06d6bSBaptiste Daroussin 
213861d06d6bSBaptiste Daroussin 	if (debug)
213961d06d6bSBaptiste Daroussin 		say(mlink->file, "Adding to database");
214061d06d6bSBaptiste Daroussin 
214161d06d6bSBaptiste Daroussin 	cp = mpage->desc;
214261d06d6bSBaptiste Daroussin 	i = strlen(cp);
214361d06d6bSBaptiste Daroussin 	mustfree = render_string(&cp, &i);
214461d06d6bSBaptiste Daroussin 	mpage->dba = dba_page_new(dba->pages,
214561d06d6bSBaptiste Daroussin 	    *mpage->arch == '\0' ? mlink->arch : mpage->arch,
214661d06d6bSBaptiste Daroussin 	    cp, mlink->file, mpage->form);
214761d06d6bSBaptiste Daroussin 	if (mustfree)
214861d06d6bSBaptiste Daroussin 		free(cp);
214961d06d6bSBaptiste Daroussin 	dba_page_add(mpage->dba, DBP_SECT, mpage->sec);
215061d06d6bSBaptiste Daroussin 
215161d06d6bSBaptiste Daroussin 	while (mlink != NULL) {
215261d06d6bSBaptiste Daroussin 		dbadd_mlink(mlink);
215361d06d6bSBaptiste Daroussin 		mlink = mlink->next;
215461d06d6bSBaptiste Daroussin 	}
215561d06d6bSBaptiste Daroussin 
215661d06d6bSBaptiste Daroussin 	for (key = ohash_first(&names, &slot); NULL != key;
215761d06d6bSBaptiste Daroussin 	     key = ohash_next(&names, &slot)) {
215861d06d6bSBaptiste Daroussin 		assert(key->mpage == mpage);
215961d06d6bSBaptiste Daroussin 		dba_page_alias(mpage->dba, key->key, key->mask);
216061d06d6bSBaptiste Daroussin 		free(key);
216161d06d6bSBaptiste Daroussin 	}
216261d06d6bSBaptiste Daroussin 	for (key = ohash_first(&strings, &slot); NULL != key;
216361d06d6bSBaptiste Daroussin 	     key = ohash_next(&strings, &slot)) {
216461d06d6bSBaptiste Daroussin 		assert(key->mpage == mpage);
216561d06d6bSBaptiste Daroussin 		i = 0;
216661d06d6bSBaptiste Daroussin 		for (mask = TYPE_Xr; mask <= TYPE_Lb; mask *= 2) {
216761d06d6bSBaptiste Daroussin 			if (key->mask & mask)
216861d06d6bSBaptiste Daroussin 				dba_macro_add(dba->macros, i,
216961d06d6bSBaptiste Daroussin 				    key->key, mpage->dba);
217061d06d6bSBaptiste Daroussin 			i++;
217161d06d6bSBaptiste Daroussin 		}
217261d06d6bSBaptiste Daroussin 		free(key);
217361d06d6bSBaptiste Daroussin 	}
217461d06d6bSBaptiste Daroussin }
217561d06d6bSBaptiste Daroussin 
217661d06d6bSBaptiste Daroussin static void
217761d06d6bSBaptiste Daroussin dbprune(struct dba *dba)
217861d06d6bSBaptiste Daroussin {
217961d06d6bSBaptiste Daroussin 	struct dba_array	*page, *files;
218061d06d6bSBaptiste Daroussin 	char			*file;
218161d06d6bSBaptiste Daroussin 
218261d06d6bSBaptiste Daroussin 	dba_array_FOREACH(dba->pages, page) {
218361d06d6bSBaptiste Daroussin 		files = dba_array_get(page, DBP_FILE);
218461d06d6bSBaptiste Daroussin 		dba_array_FOREACH(files, file) {
218561d06d6bSBaptiste Daroussin 			if (*file < ' ')
218661d06d6bSBaptiste Daroussin 				file++;
218761d06d6bSBaptiste Daroussin 			if (ohash_find(&mlinks, ohash_qlookup(&mlinks,
218861d06d6bSBaptiste Daroussin 			    file)) != NULL) {
218961d06d6bSBaptiste Daroussin 				if (debug)
219061d06d6bSBaptiste Daroussin 					say(file, "Deleting from database");
219161d06d6bSBaptiste Daroussin 				dba_array_del(dba->pages);
219261d06d6bSBaptiste Daroussin 				break;
219361d06d6bSBaptiste Daroussin 			}
219461d06d6bSBaptiste Daroussin 		}
219561d06d6bSBaptiste Daroussin 	}
219661d06d6bSBaptiste Daroussin }
219761d06d6bSBaptiste Daroussin 
219861d06d6bSBaptiste Daroussin /*
219961d06d6bSBaptiste Daroussin  * Write the database from memory to disk.
220061d06d6bSBaptiste Daroussin  */
220161d06d6bSBaptiste Daroussin static void
220261d06d6bSBaptiste Daroussin dbwrite(struct dba *dba)
220361d06d6bSBaptiste Daroussin {
220461d06d6bSBaptiste Daroussin 	struct stat	 sb1, sb2;
220561d06d6bSBaptiste Daroussin 	char		 tfn[33], *cp1, *cp2;
220661d06d6bSBaptiste Daroussin 	off_t		 i;
220761d06d6bSBaptiste Daroussin 	int		 fd1, fd2;
220861d06d6bSBaptiste Daroussin 
220961d06d6bSBaptiste Daroussin 	/*
221061d06d6bSBaptiste Daroussin 	 * Do not write empty databases, and delete existing ones
221161d06d6bSBaptiste Daroussin 	 * when makewhatis -u causes them to become empty.
221261d06d6bSBaptiste Daroussin 	 */
221361d06d6bSBaptiste Daroussin 
221461d06d6bSBaptiste Daroussin 	dba_array_start(dba->pages);
221561d06d6bSBaptiste Daroussin 	if (dba_array_next(dba->pages) == NULL) {
221661d06d6bSBaptiste Daroussin 		if (unlink(MANDOC_DB) == -1 && errno != ENOENT)
221761d06d6bSBaptiste Daroussin 			say(MANDOC_DB, "&unlink");
221861d06d6bSBaptiste Daroussin 		return;
221961d06d6bSBaptiste Daroussin 	}
222061d06d6bSBaptiste Daroussin 
222161d06d6bSBaptiste Daroussin 	/*
222261d06d6bSBaptiste Daroussin 	 * Build the database in a temporary file,
222361d06d6bSBaptiste Daroussin 	 * then atomically move it into place.
222461d06d6bSBaptiste Daroussin 	 */
222561d06d6bSBaptiste Daroussin 
222661d06d6bSBaptiste Daroussin 	if (dba_write(MANDOC_DB "~", dba) != -1) {
222761d06d6bSBaptiste Daroussin 		if (rename(MANDOC_DB "~", MANDOC_DB) == -1) {
222861d06d6bSBaptiste Daroussin 			exitcode = (int)MANDOCLEVEL_SYSERR;
222961d06d6bSBaptiste Daroussin 			say(MANDOC_DB, "&rename");
223061d06d6bSBaptiste Daroussin 			unlink(MANDOC_DB "~");
223161d06d6bSBaptiste Daroussin 		}
223261d06d6bSBaptiste Daroussin 		return;
223361d06d6bSBaptiste Daroussin 	}
223461d06d6bSBaptiste Daroussin 
223561d06d6bSBaptiste Daroussin 	/*
223661d06d6bSBaptiste Daroussin 	 * We lack write permission and cannot replace the database
223761d06d6bSBaptiste Daroussin 	 * file, but let's at least check whether the data changed.
223861d06d6bSBaptiste Daroussin 	 */
223961d06d6bSBaptiste Daroussin 
224061d06d6bSBaptiste Daroussin 	(void)strlcpy(tfn, "/tmp/mandocdb.XXXXXXXX", sizeof(tfn));
224161d06d6bSBaptiste Daroussin 	if (mkdtemp(tfn) == NULL) {
224261d06d6bSBaptiste Daroussin 		exitcode = (int)MANDOCLEVEL_SYSERR;
224361d06d6bSBaptiste Daroussin 		say("", "&%s", tfn);
224461d06d6bSBaptiste Daroussin 		return;
224561d06d6bSBaptiste Daroussin 	}
224661d06d6bSBaptiste Daroussin 	cp1 = cp2 = MAP_FAILED;
224761d06d6bSBaptiste Daroussin 	fd1 = fd2 = -1;
224861d06d6bSBaptiste Daroussin 	(void)strlcat(tfn, "/" MANDOC_DB, sizeof(tfn));
224961d06d6bSBaptiste Daroussin 	if (dba_write(tfn, dba) == -1) {
225061d06d6bSBaptiste Daroussin 		say(tfn, "&dba_write");
225161d06d6bSBaptiste Daroussin 		goto err;
225261d06d6bSBaptiste Daroussin 	}
2253*c1c95addSBrooks Davis 	if ((fd1 = open(MANDOC_DB, O_RDONLY)) == -1) {
225461d06d6bSBaptiste Daroussin 		say(MANDOC_DB, "&open");
225561d06d6bSBaptiste Daroussin 		goto err;
225661d06d6bSBaptiste Daroussin 	}
2257*c1c95addSBrooks Davis 	if ((fd2 = open(tfn, O_RDONLY)) == -1) {
225861d06d6bSBaptiste Daroussin 		say(tfn, "&open");
225961d06d6bSBaptiste Daroussin 		goto err;
226061d06d6bSBaptiste Daroussin 	}
226161d06d6bSBaptiste Daroussin 	if (fstat(fd1, &sb1) == -1) {
226261d06d6bSBaptiste Daroussin 		say(MANDOC_DB, "&fstat");
226361d06d6bSBaptiste Daroussin 		goto err;
226461d06d6bSBaptiste Daroussin 	}
226561d06d6bSBaptiste Daroussin 	if (fstat(fd2, &sb2) == -1) {
226661d06d6bSBaptiste Daroussin 		say(tfn, "&fstat");
226761d06d6bSBaptiste Daroussin 		goto err;
226861d06d6bSBaptiste Daroussin 	}
226961d06d6bSBaptiste Daroussin 	if (sb1.st_size != sb2.st_size)
227061d06d6bSBaptiste Daroussin 		goto err;
227161d06d6bSBaptiste Daroussin 	if ((cp1 = mmap(NULL, sb1.st_size, PROT_READ, MAP_PRIVATE,
227261d06d6bSBaptiste Daroussin 	    fd1, 0)) == MAP_FAILED) {
227361d06d6bSBaptiste Daroussin 		say(MANDOC_DB, "&mmap");
227461d06d6bSBaptiste Daroussin 		goto err;
227561d06d6bSBaptiste Daroussin 	}
227661d06d6bSBaptiste Daroussin 	if ((cp2 = mmap(NULL, sb2.st_size, PROT_READ, MAP_PRIVATE,
227761d06d6bSBaptiste Daroussin 	    fd2, 0)) == MAP_FAILED) {
227861d06d6bSBaptiste Daroussin 		say(tfn, "&mmap");
227961d06d6bSBaptiste Daroussin 		goto err;
228061d06d6bSBaptiste Daroussin 	}
228161d06d6bSBaptiste Daroussin 	for (i = 0; i < sb1.st_size; i++)
228261d06d6bSBaptiste Daroussin 		if (cp1[i] != cp2[i])
228361d06d6bSBaptiste Daroussin 			goto err;
228461d06d6bSBaptiste Daroussin 	goto out;
228561d06d6bSBaptiste Daroussin 
228661d06d6bSBaptiste Daroussin err:
228761d06d6bSBaptiste Daroussin 	exitcode = (int)MANDOCLEVEL_SYSERR;
228861d06d6bSBaptiste Daroussin 	say(MANDOC_DB, "Data changed, but cannot replace database");
228961d06d6bSBaptiste Daroussin 
229061d06d6bSBaptiste Daroussin out:
229161d06d6bSBaptiste Daroussin 	if (cp1 != MAP_FAILED)
229261d06d6bSBaptiste Daroussin 		munmap(cp1, sb1.st_size);
229361d06d6bSBaptiste Daroussin 	if (cp2 != MAP_FAILED)
229461d06d6bSBaptiste Daroussin 		munmap(cp2, sb2.st_size);
229561d06d6bSBaptiste Daroussin 	if (fd1 != -1)
229661d06d6bSBaptiste Daroussin 		close(fd1);
229761d06d6bSBaptiste Daroussin 	if (fd2 != -1)
229861d06d6bSBaptiste Daroussin 		close(fd2);
229961d06d6bSBaptiste Daroussin 	unlink(tfn);
230061d06d6bSBaptiste Daroussin 	*strrchr(tfn, '/') = '\0';
230161d06d6bSBaptiste Daroussin 	rmdir(tfn);
230261d06d6bSBaptiste Daroussin }
230361d06d6bSBaptiste Daroussin 
230461d06d6bSBaptiste Daroussin static int
230561d06d6bSBaptiste Daroussin set_basedir(const char *targetdir, int report_baddir)
230661d06d6bSBaptiste Daroussin {
230761d06d6bSBaptiste Daroussin 	static char	 startdir[PATH_MAX];
230861d06d6bSBaptiste Daroussin 	static int	 getcwd_status;  /* 1 = ok, 2 = failure */
230961d06d6bSBaptiste Daroussin 	static int	 chdir_status;  /* 1 = changed directory */
231061d06d6bSBaptiste Daroussin 
231161d06d6bSBaptiste Daroussin 	/*
231261d06d6bSBaptiste Daroussin 	 * Remember the original working directory, if possible.
231361d06d6bSBaptiste Daroussin 	 * This will be needed if the second or a later directory
231461d06d6bSBaptiste Daroussin 	 * on the command line is given as a relative path.
231561d06d6bSBaptiste Daroussin 	 * Do not error out if the current directory is not
231661d06d6bSBaptiste Daroussin 	 * searchable: Maybe it won't be needed after all.
231761d06d6bSBaptiste Daroussin 	 */
23186d38604fSBaptiste Daroussin 	if (getcwd_status == 0) {
23196d38604fSBaptiste Daroussin 		if (getcwd(startdir, sizeof(startdir)) == NULL) {
232061d06d6bSBaptiste Daroussin 			getcwd_status = 2;
232161d06d6bSBaptiste Daroussin 			(void)strlcpy(startdir, strerror(errno),
232261d06d6bSBaptiste Daroussin 			    sizeof(startdir));
232361d06d6bSBaptiste Daroussin 		} else
232461d06d6bSBaptiste Daroussin 			getcwd_status = 1;
232561d06d6bSBaptiste Daroussin 	}
232661d06d6bSBaptiste Daroussin 
232761d06d6bSBaptiste Daroussin 	/*
232861d06d6bSBaptiste Daroussin 	 * We are leaving the old base directory.
232961d06d6bSBaptiste Daroussin 	 * Do not use it any longer, not even for messages.
233061d06d6bSBaptiste Daroussin 	 */
233161d06d6bSBaptiste Daroussin 	*basedir = '\0';
23326d38604fSBaptiste Daroussin 	basedir_len = 0;
233361d06d6bSBaptiste Daroussin 
233461d06d6bSBaptiste Daroussin 	/*
233561d06d6bSBaptiste Daroussin 	 * If and only if the directory was changed earlier and
233661d06d6bSBaptiste Daroussin 	 * the next directory to process is given as a relative path,
233761d06d6bSBaptiste Daroussin 	 * first go back, or bail out if that is impossible.
233861d06d6bSBaptiste Daroussin 	 */
23396d38604fSBaptiste Daroussin 	if (chdir_status && *targetdir != '/') {
23406d38604fSBaptiste Daroussin 		if (getcwd_status == 2) {
234161d06d6bSBaptiste Daroussin 			exitcode = (int)MANDOCLEVEL_SYSERR;
234261d06d6bSBaptiste Daroussin 			say("", "getcwd: %s", startdir);
234361d06d6bSBaptiste Daroussin 			return 0;
234461d06d6bSBaptiste Daroussin 		}
23456d38604fSBaptiste Daroussin 		if (chdir(startdir) == -1) {
234661d06d6bSBaptiste Daroussin 			exitcode = (int)MANDOCLEVEL_SYSERR;
234761d06d6bSBaptiste Daroussin 			say("", "&chdir %s", startdir);
234861d06d6bSBaptiste Daroussin 			return 0;
234961d06d6bSBaptiste Daroussin 		}
235061d06d6bSBaptiste Daroussin 	}
235161d06d6bSBaptiste Daroussin 
235261d06d6bSBaptiste Daroussin 	/*
235361d06d6bSBaptiste Daroussin 	 * Always resolve basedir to the canonicalized absolute
235461d06d6bSBaptiste Daroussin 	 * pathname and append a trailing slash, such that
235561d06d6bSBaptiste Daroussin 	 * we can reliably check whether files are inside.
235661d06d6bSBaptiste Daroussin 	 */
23576d38604fSBaptiste Daroussin 	if (realpath(targetdir, basedir) == NULL) {
235861d06d6bSBaptiste Daroussin 		if (report_baddir || errno != ENOENT) {
235961d06d6bSBaptiste Daroussin 			exitcode = (int)MANDOCLEVEL_BADARG;
236061d06d6bSBaptiste Daroussin 			say("", "&%s: realpath", targetdir);
236161d06d6bSBaptiste Daroussin 		}
23626d38604fSBaptiste Daroussin 		*basedir = '\0';
236361d06d6bSBaptiste Daroussin 		return 0;
23646d38604fSBaptiste Daroussin 	} else if (chdir(basedir) == -1) {
236561d06d6bSBaptiste Daroussin 		if (report_baddir || errno != ENOENT) {
236661d06d6bSBaptiste Daroussin 			exitcode = (int)MANDOCLEVEL_BADARG;
236761d06d6bSBaptiste Daroussin 			say("", "&chdir");
236861d06d6bSBaptiste Daroussin 		}
23696d38604fSBaptiste Daroussin 		*basedir = '\0';
237061d06d6bSBaptiste Daroussin 		return 0;
237161d06d6bSBaptiste Daroussin 	}
237261d06d6bSBaptiste Daroussin 	chdir_status = 1;
23736d38604fSBaptiste Daroussin 	basedir_len = strlen(basedir);
23746d38604fSBaptiste Daroussin 	if (basedir[basedir_len - 1] != '/') {
23756d38604fSBaptiste Daroussin 		if (basedir_len >= PATH_MAX - 1) {
237661d06d6bSBaptiste Daroussin 			exitcode = (int)MANDOCLEVEL_SYSERR;
237761d06d6bSBaptiste Daroussin 			say("", "Filename too long");
23786d38604fSBaptiste Daroussin 			*basedir = '\0';
23796d38604fSBaptiste Daroussin 			basedir_len = 0;
238061d06d6bSBaptiste Daroussin 			return 0;
238161d06d6bSBaptiste Daroussin 		}
23826d38604fSBaptiste Daroussin 		basedir[basedir_len++] = '/';
23836d38604fSBaptiste Daroussin 		basedir[basedir_len] = '\0';
238461d06d6bSBaptiste Daroussin 	}
238561d06d6bSBaptiste Daroussin 	return 1;
238661d06d6bSBaptiste Daroussin }
238761d06d6bSBaptiste Daroussin 
23886d38604fSBaptiste Daroussin #ifdef READ_ALLOWED_PATH
23896d38604fSBaptiste Daroussin static int
23906d38604fSBaptiste Daroussin read_allowed(const char *candidate)
23916d38604fSBaptiste Daroussin {
23926d38604fSBaptiste Daroussin 	const char	*cp;
23936d38604fSBaptiste Daroussin 	size_t		 len;
23946d38604fSBaptiste Daroussin 
23956d38604fSBaptiste Daroussin 	for (cp = READ_ALLOWED_PATH;; cp += len) {
23966d38604fSBaptiste Daroussin 		while (*cp == ':')
23976d38604fSBaptiste Daroussin 			cp++;
23986d38604fSBaptiste Daroussin 		if (*cp == '\0')
23996d38604fSBaptiste Daroussin 			return 0;
24006d38604fSBaptiste Daroussin 		len = strcspn(cp, ":");
24016d38604fSBaptiste Daroussin 		if (strncmp(candidate, cp, len) == 0)
24026d38604fSBaptiste Daroussin 			return 1;
24036d38604fSBaptiste Daroussin 	}
24046d38604fSBaptiste Daroussin }
24056d38604fSBaptiste Daroussin #endif
24066d38604fSBaptiste Daroussin 
240761d06d6bSBaptiste Daroussin static void
240861d06d6bSBaptiste Daroussin say(const char *file, const char *format, ...)
240961d06d6bSBaptiste Daroussin {
241061d06d6bSBaptiste Daroussin 	va_list		 ap;
241161d06d6bSBaptiste Daroussin 	int		 use_errno;
241261d06d6bSBaptiste Daroussin 
24136d38604fSBaptiste Daroussin 	if (*basedir != '\0')
241461d06d6bSBaptiste Daroussin 		fprintf(stderr, "%s", basedir);
24156d38604fSBaptiste Daroussin 	if (*basedir != '\0' && *file != '\0')
241661d06d6bSBaptiste Daroussin 		fputc('/', stderr);
24176d38604fSBaptiste Daroussin 	if (*file != '\0')
241861d06d6bSBaptiste Daroussin 		fprintf(stderr, "%s", file);
241961d06d6bSBaptiste Daroussin 
242061d06d6bSBaptiste Daroussin 	use_errno = 1;
24216d38604fSBaptiste Daroussin 	if (format != NULL) {
242261d06d6bSBaptiste Daroussin 		switch (*format) {
242361d06d6bSBaptiste Daroussin 		case '&':
242461d06d6bSBaptiste Daroussin 			format++;
242561d06d6bSBaptiste Daroussin 			break;
242661d06d6bSBaptiste Daroussin 		case '\0':
242761d06d6bSBaptiste Daroussin 			format = NULL;
242861d06d6bSBaptiste Daroussin 			break;
242961d06d6bSBaptiste Daroussin 		default:
243061d06d6bSBaptiste Daroussin 			use_errno = 0;
243161d06d6bSBaptiste Daroussin 			break;
243261d06d6bSBaptiste Daroussin 		}
243361d06d6bSBaptiste Daroussin 	}
24346d38604fSBaptiste Daroussin 	if (format != NULL) {
24356d38604fSBaptiste Daroussin 		if (*basedir != '\0' || *file != '\0')
243661d06d6bSBaptiste Daroussin 			fputs(": ", stderr);
243761d06d6bSBaptiste Daroussin 		va_start(ap, format);
243861d06d6bSBaptiste Daroussin 		vfprintf(stderr, format, ap);
243961d06d6bSBaptiste Daroussin 		va_end(ap);
244061d06d6bSBaptiste Daroussin 	}
244161d06d6bSBaptiste Daroussin 	if (use_errno) {
24426d38604fSBaptiste Daroussin 		if (*basedir != '\0' || *file != '\0' || format != NULL)
244361d06d6bSBaptiste Daroussin 			fputs(": ", stderr);
244461d06d6bSBaptiste Daroussin 		perror(NULL);
244561d06d6bSBaptiste Daroussin 	} else
244661d06d6bSBaptiste Daroussin 		fputc('\n', stderr);
244761d06d6bSBaptiste Daroussin }
2448