xref: /freebsd/contrib/mandoc/mandocdb.c (revision 4c07abdbacf49e4ba749b8720916d8cd340a50b8)
1*4c07abdbSAlexander Ziaee /* $Id: mandocdb.c,v 1.275 2025/06/05 12:33:41 schwarze Exp $ */
261d06d6bSBaptiste Daroussin /*
3*4c07abdbSAlexander Ziaee  * Copyright (c) 2011-2021, 2024, 2025 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 *);
154*4c07abdbSAlexander Ziaee static	int	 parse_mdoc_Lb(struct mpage *, const struct roff_meta *,
155*4c07abdbSAlexander Ziaee 			const struct roff_node *);
15661d06d6bSBaptiste Daroussin static	int	 parse_mdoc_Nd(struct mpage *, const struct roff_meta *,
15761d06d6bSBaptiste Daroussin 			const struct roff_node *);
15861d06d6bSBaptiste Daroussin static	int	 parse_mdoc_Nm(struct mpage *, const struct roff_meta *,
15961d06d6bSBaptiste Daroussin 			const struct roff_node *);
16061d06d6bSBaptiste Daroussin static	int	 parse_mdoc_Sh(struct mpage *, const struct roff_meta *,
16161d06d6bSBaptiste Daroussin 			const struct roff_node *);
16261d06d6bSBaptiste Daroussin static	int	 parse_mdoc_Va(struct mpage *, const struct roff_meta *,
16361d06d6bSBaptiste Daroussin 			const struct roff_node *);
16461d06d6bSBaptiste Daroussin static	int	 parse_mdoc_Xr(struct mpage *, const struct roff_meta *,
16561d06d6bSBaptiste Daroussin 			const struct roff_node *);
16661d06d6bSBaptiste Daroussin static	void	 putkey(const struct mpage *, char *, uint64_t);
16761d06d6bSBaptiste Daroussin static	void	 putkeys(const struct mpage *, char *, size_t, uint64_t);
16861d06d6bSBaptiste Daroussin static	void	 putmdockey(const struct mpage *,
16961d06d6bSBaptiste Daroussin 			const struct roff_node *, uint64_t, int);
1706d38604fSBaptiste Daroussin #ifdef READ_ALLOWED_PATH
1716d38604fSBaptiste Daroussin static	int	 read_allowed(const char *);
1726d38604fSBaptiste Daroussin #endif
17361d06d6bSBaptiste Daroussin static	int	 render_string(char **, size_t *);
17461d06d6bSBaptiste Daroussin static	void	 say(const char *, const char *, ...)
17561d06d6bSBaptiste Daroussin 			__attribute__((__format__ (__printf__, 2, 3)));
17661d06d6bSBaptiste Daroussin static	int	 set_basedir(const char *, int);
17761d06d6bSBaptiste Daroussin static	int	 treescan(void);
178c1c95addSBrooks Davis static	size_t	 utf8(unsigned int, char[5]);
17961d06d6bSBaptiste Daroussin 
18061d06d6bSBaptiste Daroussin static	int		 nodb; /* no database changes */
18161d06d6bSBaptiste Daroussin static	int		 mparse_options; /* abort the parse early */
18261d06d6bSBaptiste Daroussin static	int		 use_all; /* use all found files */
18361d06d6bSBaptiste Daroussin static	int		 debug; /* print what we're doing */
18461d06d6bSBaptiste Daroussin static	int		 warnings; /* warn about crap */
18561d06d6bSBaptiste Daroussin static	int		 write_utf8; /* write UTF-8 output; else ASCII */
18661d06d6bSBaptiste Daroussin static	int		 exitcode; /* to be returned by main */
18761d06d6bSBaptiste Daroussin static	enum op		 op; /* operational mode */
18861d06d6bSBaptiste Daroussin static	char		 basedir[PATH_MAX]; /* current base directory */
1896d38604fSBaptiste Daroussin static	size_t		 basedir_len; /* strlen(basedir) */
19061d06d6bSBaptiste Daroussin static	struct mpage	*mpage_head; /* list of distinct manual pages */
19161d06d6bSBaptiste Daroussin static	struct ohash	 mpages; /* table of distinct manual pages */
19261d06d6bSBaptiste Daroussin static	struct ohash	 mlinks; /* table of directory entries */
19361d06d6bSBaptiste Daroussin static	struct ohash	 names; /* table of all names */
19461d06d6bSBaptiste Daroussin static	struct ohash	 strings; /* table of all strings */
19561d06d6bSBaptiste Daroussin static	uint64_t	 name_mask;
19661d06d6bSBaptiste Daroussin 
1977295610fSBaptiste Daroussin static	const struct mdoc_handler mdoc_handlers[MDOC_MAX - MDOC_Dd] = {
19861d06d6bSBaptiste Daroussin 	{ NULL, 0, NODE_NOPRT },  /* Dd */
19961d06d6bSBaptiste Daroussin 	{ NULL, 0, NODE_NOPRT },  /* Dt */
20061d06d6bSBaptiste Daroussin 	{ NULL, 0, NODE_NOPRT },  /* Os */
20161d06d6bSBaptiste Daroussin 	{ parse_mdoc_Sh, TYPE_Sh, 0 }, /* Sh */
20261d06d6bSBaptiste Daroussin 	{ parse_mdoc_head, TYPE_Ss, 0 }, /* Ss */
20361d06d6bSBaptiste Daroussin 	{ NULL, 0, 0 },  /* Pp */
20461d06d6bSBaptiste Daroussin 	{ NULL, 0, 0 },  /* D1 */
20561d06d6bSBaptiste Daroussin 	{ NULL, 0, 0 },  /* Dl */
20661d06d6bSBaptiste Daroussin 	{ NULL, 0, 0 },  /* Bd */
20761d06d6bSBaptiste Daroussin 	{ NULL, 0, 0 },  /* Ed */
20861d06d6bSBaptiste Daroussin 	{ NULL, 0, 0 },  /* Bl */
20961d06d6bSBaptiste Daroussin 	{ NULL, 0, 0 },  /* El */
21061d06d6bSBaptiste Daroussin 	{ NULL, 0, 0 },  /* It */
21161d06d6bSBaptiste Daroussin 	{ NULL, 0, 0 },  /* Ad */
21261d06d6bSBaptiste Daroussin 	{ NULL, TYPE_An, 0 },  /* An */
21361d06d6bSBaptiste Daroussin 	{ NULL, 0, 0 },  /* Ap */
21461d06d6bSBaptiste Daroussin 	{ NULL, TYPE_Ar, 0 },  /* Ar */
21561d06d6bSBaptiste Daroussin 	{ NULL, TYPE_Cd, 0 },  /* Cd */
21661d06d6bSBaptiste Daroussin 	{ NULL, TYPE_Cm, 0 },  /* Cm */
21761d06d6bSBaptiste Daroussin 	{ NULL, TYPE_Dv, 0 },  /* Dv */
21861d06d6bSBaptiste Daroussin 	{ NULL, TYPE_Er, 0 },  /* Er */
21961d06d6bSBaptiste Daroussin 	{ NULL, TYPE_Ev, 0 },  /* Ev */
22061d06d6bSBaptiste Daroussin 	{ NULL, 0, 0 },  /* Ex */
22161d06d6bSBaptiste Daroussin 	{ parse_mdoc_Fa, 0, 0 },  /* Fa */
22261d06d6bSBaptiste Daroussin 	{ parse_mdoc_Fd, 0, 0 },  /* Fd */
22361d06d6bSBaptiste Daroussin 	{ NULL, TYPE_Fl, 0 },  /* Fl */
22461d06d6bSBaptiste Daroussin 	{ parse_mdoc_Fn, 0, 0 },  /* Fn */
22561d06d6bSBaptiste Daroussin 	{ NULL, TYPE_Ft | TYPE_Vt, 0 },  /* Ft */
22661d06d6bSBaptiste Daroussin 	{ NULL, TYPE_Ic, 0 },  /* Ic */
22761d06d6bSBaptiste Daroussin 	{ NULL, TYPE_In, 0 },  /* In */
22861d06d6bSBaptiste Daroussin 	{ NULL, TYPE_Li, 0 },  /* Li */
22961d06d6bSBaptiste Daroussin 	{ parse_mdoc_Nd, 0, 0 },  /* Nd */
23061d06d6bSBaptiste Daroussin 	{ parse_mdoc_Nm, 0, 0 },  /* Nm */
23161d06d6bSBaptiste Daroussin 	{ NULL, 0, 0 },  /* Op */
23261d06d6bSBaptiste Daroussin 	{ NULL, 0, 0 },  /* Ot */
23361d06d6bSBaptiste Daroussin 	{ NULL, TYPE_Pa, NODE_NOSRC },  /* Pa */
23461d06d6bSBaptiste Daroussin 	{ NULL, 0, 0 },  /* Rv */
23561d06d6bSBaptiste Daroussin 	{ NULL, TYPE_St, 0 },  /* St */
23661d06d6bSBaptiste Daroussin 	{ parse_mdoc_Va, TYPE_Va, 0 },  /* Va */
23761d06d6bSBaptiste Daroussin 	{ parse_mdoc_Va, TYPE_Vt, 0 },  /* Vt */
23861d06d6bSBaptiste Daroussin 	{ parse_mdoc_Xr, 0, 0 },  /* Xr */
23961d06d6bSBaptiste Daroussin 	{ NULL, 0, 0 },  /* %A */
24061d06d6bSBaptiste Daroussin 	{ NULL, 0, 0 },  /* %B */
24161d06d6bSBaptiste Daroussin 	{ NULL, 0, 0 },  /* %D */
24261d06d6bSBaptiste Daroussin 	{ NULL, 0, 0 },  /* %I */
24361d06d6bSBaptiste Daroussin 	{ NULL, 0, 0 },  /* %J */
24461d06d6bSBaptiste Daroussin 	{ NULL, 0, 0 },  /* %N */
24561d06d6bSBaptiste Daroussin 	{ NULL, 0, 0 },  /* %O */
24661d06d6bSBaptiste Daroussin 	{ NULL, 0, 0 },  /* %P */
24761d06d6bSBaptiste Daroussin 	{ NULL, 0, 0 },  /* %R */
24861d06d6bSBaptiste Daroussin 	{ NULL, 0, 0 },  /* %T */
24961d06d6bSBaptiste Daroussin 	{ NULL, 0, 0 },  /* %V */
25061d06d6bSBaptiste Daroussin 	{ NULL, 0, 0 },  /* Ac */
25161d06d6bSBaptiste Daroussin 	{ NULL, 0, 0 },  /* Ao */
25261d06d6bSBaptiste Daroussin 	{ NULL, 0, 0 },  /* Aq */
25361d06d6bSBaptiste Daroussin 	{ NULL, TYPE_At, 0 },  /* At */
25461d06d6bSBaptiste Daroussin 	{ NULL, 0, 0 },  /* Bc */
25561d06d6bSBaptiste Daroussin 	{ NULL, 0, 0 },  /* Bf */
25661d06d6bSBaptiste Daroussin 	{ NULL, 0, 0 },  /* Bo */
25761d06d6bSBaptiste Daroussin 	{ NULL, 0, 0 },  /* Bq */
25861d06d6bSBaptiste Daroussin 	{ NULL, TYPE_Bsx, NODE_NOSRC },  /* Bsx */
25961d06d6bSBaptiste Daroussin 	{ NULL, TYPE_Bx, NODE_NOSRC },  /* Bx */
26061d06d6bSBaptiste Daroussin 	{ NULL, 0, 0 },  /* Db */
26161d06d6bSBaptiste Daroussin 	{ NULL, 0, 0 },  /* Dc */
26261d06d6bSBaptiste Daroussin 	{ NULL, 0, 0 },  /* Do */
26361d06d6bSBaptiste Daroussin 	{ NULL, 0, 0 },  /* Dq */
26461d06d6bSBaptiste Daroussin 	{ NULL, 0, 0 },  /* Ec */
26561d06d6bSBaptiste Daroussin 	{ NULL, 0, 0 },  /* Ef */
26661d06d6bSBaptiste Daroussin 	{ NULL, TYPE_Em, 0 },  /* Em */
26761d06d6bSBaptiste Daroussin 	{ NULL, 0, 0 },  /* Eo */
26861d06d6bSBaptiste Daroussin 	{ NULL, TYPE_Fx, NODE_NOSRC },  /* Fx */
26961d06d6bSBaptiste Daroussin 	{ NULL, TYPE_Ms, 0 },  /* Ms */
27061d06d6bSBaptiste Daroussin 	{ NULL, 0, 0 },  /* No */
27161d06d6bSBaptiste Daroussin 	{ NULL, 0, 0 },  /* Ns */
27261d06d6bSBaptiste Daroussin 	{ NULL, TYPE_Nx, NODE_NOSRC },  /* Nx */
27361d06d6bSBaptiste Daroussin 	{ NULL, TYPE_Ox, NODE_NOSRC },  /* Ox */
27461d06d6bSBaptiste Daroussin 	{ NULL, 0, 0 },  /* Pc */
27561d06d6bSBaptiste Daroussin 	{ NULL, 0, 0 },  /* Pf */
27661d06d6bSBaptiste Daroussin 	{ NULL, 0, 0 },  /* Po */
27761d06d6bSBaptiste Daroussin 	{ NULL, 0, 0 },  /* Pq */
27861d06d6bSBaptiste Daroussin 	{ NULL, 0, 0 },  /* Qc */
27961d06d6bSBaptiste Daroussin 	{ NULL, 0, 0 },  /* Ql */
28061d06d6bSBaptiste Daroussin 	{ NULL, 0, 0 },  /* Qo */
28161d06d6bSBaptiste Daroussin 	{ NULL, 0, 0 },  /* Qq */
28261d06d6bSBaptiste Daroussin 	{ NULL, 0, 0 },  /* Re */
28361d06d6bSBaptiste Daroussin 	{ NULL, 0, 0 },  /* Rs */
28461d06d6bSBaptiste Daroussin 	{ NULL, 0, 0 },  /* Sc */
28561d06d6bSBaptiste Daroussin 	{ NULL, 0, 0 },  /* So */
28661d06d6bSBaptiste Daroussin 	{ NULL, 0, 0 },  /* Sq */
28761d06d6bSBaptiste Daroussin 	{ NULL, 0, 0 },  /* Sm */
28861d06d6bSBaptiste Daroussin 	{ NULL, 0, 0 },  /* Sx */
28961d06d6bSBaptiste Daroussin 	{ NULL, TYPE_Sy, 0 },  /* Sy */
29061d06d6bSBaptiste Daroussin 	{ NULL, TYPE_Tn, 0 },  /* Tn */
29161d06d6bSBaptiste Daroussin 	{ NULL, 0, NODE_NOSRC },  /* Ux */
29261d06d6bSBaptiste Daroussin 	{ NULL, 0, 0 },  /* Xc */
29361d06d6bSBaptiste Daroussin 	{ NULL, 0, 0 },  /* Xo */
29461d06d6bSBaptiste Daroussin 	{ parse_mdoc_Fo, 0, 0 },  /* Fo */
29561d06d6bSBaptiste Daroussin 	{ NULL, 0, 0 },  /* Fc */
29661d06d6bSBaptiste Daroussin 	{ NULL, 0, 0 },  /* Oo */
29761d06d6bSBaptiste Daroussin 	{ NULL, 0, 0 },  /* Oc */
29861d06d6bSBaptiste Daroussin 	{ NULL, 0, 0 },  /* Bk */
29961d06d6bSBaptiste Daroussin 	{ NULL, 0, 0 },  /* Ek */
30061d06d6bSBaptiste Daroussin 	{ NULL, 0, 0 },  /* Bt */
30161d06d6bSBaptiste Daroussin 	{ NULL, 0, 0 },  /* Hf */
30261d06d6bSBaptiste Daroussin 	{ NULL, 0, 0 },  /* Fr */
30361d06d6bSBaptiste Daroussin 	{ NULL, 0, 0 },  /* Ud */
304*4c07abdbSAlexander Ziaee 	{ parse_mdoc_Lb, 0, 0 },  /* Lb */
30561d06d6bSBaptiste Daroussin 	{ NULL, 0, 0 },  /* Lp */
30661d06d6bSBaptiste Daroussin 	{ NULL, TYPE_Lk, 0 },  /* Lk */
30761d06d6bSBaptiste Daroussin 	{ NULL, TYPE_Mt, NODE_NOSRC },  /* Mt */
30861d06d6bSBaptiste Daroussin 	{ NULL, 0, 0 },  /* Brq */
30961d06d6bSBaptiste Daroussin 	{ NULL, 0, 0 },  /* Bro */
31061d06d6bSBaptiste Daroussin 	{ NULL, 0, 0 },  /* Brc */
31161d06d6bSBaptiste Daroussin 	{ NULL, 0, 0 },  /* %C */
31261d06d6bSBaptiste Daroussin 	{ NULL, 0, 0 },  /* Es */
31361d06d6bSBaptiste Daroussin 	{ NULL, 0, 0 },  /* En */
31461d06d6bSBaptiste Daroussin 	{ NULL, TYPE_Dx, NODE_NOSRC },  /* Dx */
31561d06d6bSBaptiste Daroussin 	{ NULL, 0, 0 },  /* %Q */
31661d06d6bSBaptiste Daroussin 	{ NULL, 0, 0 },  /* %U */
31761d06d6bSBaptiste Daroussin 	{ NULL, 0, 0 },  /* Ta */
31861d06d6bSBaptiste Daroussin };
31961d06d6bSBaptiste Daroussin 
32061d06d6bSBaptiste Daroussin 
32161d06d6bSBaptiste Daroussin int
mandocdb(int argc,char * argv[])32261d06d6bSBaptiste Daroussin mandocdb(int argc, char *argv[])
32361d06d6bSBaptiste Daroussin {
32461d06d6bSBaptiste Daroussin 	struct manconf	  conf;
32561d06d6bSBaptiste Daroussin 	struct mparse	 *mp;
32661d06d6bSBaptiste Daroussin 	struct dba	 *dba;
32761d06d6bSBaptiste Daroussin 	const char	 *path_arg, *progname;
32861d06d6bSBaptiste Daroussin 	size_t		  j, sz;
32961d06d6bSBaptiste Daroussin 	int		  ch, i;
33061d06d6bSBaptiste Daroussin 
33161d06d6bSBaptiste Daroussin #if HAVE_PLEDGE
33261d06d6bSBaptiste Daroussin 	if (pledge("stdio rpath wpath cpath", NULL) == -1) {
33361d06d6bSBaptiste Daroussin 		warn("pledge");
33461d06d6bSBaptiste Daroussin 		return (int)MANDOCLEVEL_SYSERR;
33561d06d6bSBaptiste Daroussin 	}
33661d06d6bSBaptiste Daroussin #endif
33761d06d6bSBaptiste Daroussin 
33861d06d6bSBaptiste Daroussin #if HAVE_SANDBOX_INIT
33961d06d6bSBaptiste Daroussin 	if (sandbox_init(kSBXProfileNoInternet, SANDBOX_NAMED, NULL) == -1) {
34061d06d6bSBaptiste Daroussin 		warnx("sandbox_init");
34161d06d6bSBaptiste Daroussin 		return (int)MANDOCLEVEL_SYSERR;
34261d06d6bSBaptiste Daroussin 	}
34361d06d6bSBaptiste Daroussin #endif
34461d06d6bSBaptiste Daroussin 
34561d06d6bSBaptiste Daroussin 	memset(&conf, 0, sizeof(conf));
34661d06d6bSBaptiste Daroussin 
34761d06d6bSBaptiste Daroussin 	/*
34861d06d6bSBaptiste Daroussin 	 * We accept a few different invocations.
34961d06d6bSBaptiste Daroussin 	 * The CHECKOP macro makes sure that invocation styles don't
35061d06d6bSBaptiste Daroussin 	 * clobber each other.
35161d06d6bSBaptiste Daroussin 	 */
35261d06d6bSBaptiste Daroussin #define	CHECKOP(_op, _ch) do \
3536d38604fSBaptiste Daroussin 	if ((_op) != OP_DEFAULT) { \
35461d06d6bSBaptiste Daroussin 		warnx("-%c: Conflicting option", (_ch)); \
35561d06d6bSBaptiste Daroussin 		goto usage; \
35661d06d6bSBaptiste Daroussin 	} while (/*CONSTCOND*/0)
35761d06d6bSBaptiste Daroussin 
358c1c95addSBrooks Davis 	mparse_options = MPARSE_UTF8 | MPARSE_LATIN1 | MPARSE_VALIDATE;
35961d06d6bSBaptiste Daroussin 	path_arg = NULL;
36061d06d6bSBaptiste Daroussin 	op = OP_DEFAULT;
36161d06d6bSBaptiste Daroussin 
3626d38604fSBaptiste Daroussin 	while ((ch = getopt(argc, argv, "aC:Dd:npQT:tu:v")) != -1)
36361d06d6bSBaptiste Daroussin 		switch (ch) {
36461d06d6bSBaptiste Daroussin 		case 'a':
36561d06d6bSBaptiste Daroussin 			use_all = 1;
36661d06d6bSBaptiste Daroussin 			break;
36761d06d6bSBaptiste Daroussin 		case 'C':
36861d06d6bSBaptiste Daroussin 			CHECKOP(op, ch);
36961d06d6bSBaptiste Daroussin 			path_arg = optarg;
37061d06d6bSBaptiste Daroussin 			op = OP_CONFFILE;
37161d06d6bSBaptiste Daroussin 			break;
37261d06d6bSBaptiste Daroussin 		case 'D':
37361d06d6bSBaptiste Daroussin 			debug++;
37461d06d6bSBaptiste Daroussin 			break;
37561d06d6bSBaptiste Daroussin 		case 'd':
37661d06d6bSBaptiste Daroussin 			CHECKOP(op, ch);
37761d06d6bSBaptiste Daroussin 			path_arg = optarg;
37861d06d6bSBaptiste Daroussin 			op = OP_UPDATE;
37961d06d6bSBaptiste Daroussin 			break;
38061d06d6bSBaptiste Daroussin 		case 'n':
38161d06d6bSBaptiste Daroussin 			nodb = 1;
38261d06d6bSBaptiste Daroussin 			break;
38361d06d6bSBaptiste Daroussin 		case 'p':
38461d06d6bSBaptiste Daroussin 			warnings = 1;
38561d06d6bSBaptiste Daroussin 			break;
38661d06d6bSBaptiste Daroussin 		case 'Q':
38761d06d6bSBaptiste Daroussin 			mparse_options |= MPARSE_QUICK;
38861d06d6bSBaptiste Daroussin 			break;
38961d06d6bSBaptiste Daroussin 		case 'T':
3906d38604fSBaptiste Daroussin 			if (strcmp(optarg, "utf8") != 0) {
39161d06d6bSBaptiste Daroussin 				warnx("-T%s: Unsupported output format",
39261d06d6bSBaptiste Daroussin 				    optarg);
39361d06d6bSBaptiste Daroussin 				goto usage;
39461d06d6bSBaptiste Daroussin 			}
39561d06d6bSBaptiste Daroussin 			write_utf8 = 1;
39661d06d6bSBaptiste Daroussin 			break;
39761d06d6bSBaptiste Daroussin 		case 't':
39861d06d6bSBaptiste Daroussin 			CHECKOP(op, ch);
39961d06d6bSBaptiste Daroussin 			dup2(STDOUT_FILENO, STDERR_FILENO);
40061d06d6bSBaptiste Daroussin 			op = OP_TEST;
40161d06d6bSBaptiste Daroussin 			nodb = warnings = 1;
40261d06d6bSBaptiste Daroussin 			break;
40361d06d6bSBaptiste Daroussin 		case 'u':
40461d06d6bSBaptiste Daroussin 			CHECKOP(op, ch);
40561d06d6bSBaptiste Daroussin 			path_arg = optarg;
40661d06d6bSBaptiste Daroussin 			op = OP_DELETE;
40761d06d6bSBaptiste Daroussin 			break;
40861d06d6bSBaptiste Daroussin 		case 'v':
40961d06d6bSBaptiste Daroussin 			/* Compatibility with espie@'s makewhatis. */
41061d06d6bSBaptiste Daroussin 			break;
41161d06d6bSBaptiste Daroussin 		default:
41261d06d6bSBaptiste Daroussin 			goto usage;
41361d06d6bSBaptiste Daroussin 		}
41461d06d6bSBaptiste Daroussin 
41561d06d6bSBaptiste Daroussin 	argc -= optind;
41661d06d6bSBaptiste Daroussin 	argv += optind;
41761d06d6bSBaptiste Daroussin 
41861d06d6bSBaptiste Daroussin #if HAVE_PLEDGE
41961d06d6bSBaptiste Daroussin 	if (nodb) {
42061d06d6bSBaptiste Daroussin 		if (pledge("stdio rpath", NULL) == -1) {
42161d06d6bSBaptiste Daroussin 			warn("pledge");
42261d06d6bSBaptiste Daroussin 			return (int)MANDOCLEVEL_SYSERR;
42361d06d6bSBaptiste Daroussin 		}
42461d06d6bSBaptiste Daroussin 	}
42561d06d6bSBaptiste Daroussin #endif
42661d06d6bSBaptiste Daroussin 
4276d38604fSBaptiste Daroussin 	if (op == OP_CONFFILE && argc > 0) {
42861d06d6bSBaptiste Daroussin 		warnx("-C: Too many arguments");
42961d06d6bSBaptiste Daroussin 		goto usage;
43061d06d6bSBaptiste Daroussin 	}
43161d06d6bSBaptiste Daroussin 
43261d06d6bSBaptiste Daroussin 	exitcode = (int)MANDOCLEVEL_OK;
43361d06d6bSBaptiste Daroussin 	mchars_alloc();
4347295610fSBaptiste Daroussin 	mp = mparse_alloc(mparse_options, MANDOC_OS_OTHER, NULL);
43561d06d6bSBaptiste Daroussin 	mandoc_ohash_init(&mpages, 6, offsetof(struct mpage, inodev));
43661d06d6bSBaptiste Daroussin 	mandoc_ohash_init(&mlinks, 6, offsetof(struct mlink, file));
43761d06d6bSBaptiste Daroussin 
4386d38604fSBaptiste Daroussin 	if (op == OP_UPDATE || op == OP_DELETE || op == OP_TEST) {
43961d06d6bSBaptiste Daroussin 
44061d06d6bSBaptiste Daroussin 		/*
44161d06d6bSBaptiste Daroussin 		 * Most of these deal with a specific directory.
44261d06d6bSBaptiste Daroussin 		 * Jump into that directory first.
44361d06d6bSBaptiste Daroussin 		 */
4446d38604fSBaptiste Daroussin 		if (op != OP_TEST && set_basedir(path_arg, 1) == 0)
44561d06d6bSBaptiste Daroussin 			goto out;
44661d06d6bSBaptiste Daroussin 
44761d06d6bSBaptiste Daroussin 		dba = nodb ? dba_new(128) : dba_read(MANDOC_DB);
44861d06d6bSBaptiste Daroussin 		if (dba != NULL) {
44961d06d6bSBaptiste Daroussin 			/*
45061d06d6bSBaptiste Daroussin 			 * The existing database is usable.  Process
45161d06d6bSBaptiste Daroussin 			 * all files specified on the command-line.
45261d06d6bSBaptiste Daroussin 			 */
45361d06d6bSBaptiste Daroussin 			use_all = 1;
45461d06d6bSBaptiste Daroussin 			for (i = 0; i < argc; i++)
45561d06d6bSBaptiste Daroussin 				filescan(argv[i]);
45661d06d6bSBaptiste Daroussin 			if (nodb == 0)
45761d06d6bSBaptiste Daroussin 				dbprune(dba);
45861d06d6bSBaptiste Daroussin 		} else {
45961d06d6bSBaptiste Daroussin 			/* Database missing or corrupt. */
46061d06d6bSBaptiste Daroussin 			if (op != OP_UPDATE || errno != ENOENT)
46161d06d6bSBaptiste Daroussin 				say(MANDOC_DB, "%s: Automatically recreating"
46261d06d6bSBaptiste Daroussin 				    " from scratch", strerror(errno));
46361d06d6bSBaptiste Daroussin 			exitcode = (int)MANDOCLEVEL_OK;
46461d06d6bSBaptiste Daroussin 			op = OP_DEFAULT;
4656d38604fSBaptiste Daroussin 			if (treescan() == 0)
46661d06d6bSBaptiste Daroussin 				goto out;
46761d06d6bSBaptiste Daroussin 			dba = dba_new(128);
46861d06d6bSBaptiste Daroussin 		}
4696d38604fSBaptiste Daroussin 		if (op != OP_DELETE)
47061d06d6bSBaptiste Daroussin 			mpages_merge(dba, mp);
47161d06d6bSBaptiste Daroussin 		if (nodb == 0)
47261d06d6bSBaptiste Daroussin 			dbwrite(dba);
47361d06d6bSBaptiste Daroussin 		dba_free(dba);
47461d06d6bSBaptiste Daroussin 	} else {
47561d06d6bSBaptiste Daroussin 		/*
47661d06d6bSBaptiste Daroussin 		 * If we have arguments, use them as our manpaths.
47761d06d6bSBaptiste Daroussin 		 * If we don't, use man.conf(5).
47861d06d6bSBaptiste Daroussin 		 */
47961d06d6bSBaptiste Daroussin 		if (argc > 0) {
48061d06d6bSBaptiste Daroussin 			conf.manpath.paths = mandoc_reallocarray(NULL,
48161d06d6bSBaptiste Daroussin 			    argc, sizeof(char *));
48261d06d6bSBaptiste Daroussin 			conf.manpath.sz = (size_t)argc;
48361d06d6bSBaptiste Daroussin 			for (i = 0; i < argc; i++)
48461d06d6bSBaptiste Daroussin 				conf.manpath.paths[i] = mandoc_strdup(argv[i]);
48561d06d6bSBaptiste Daroussin 		} else
48661d06d6bSBaptiste Daroussin 			manconf_parse(&conf, path_arg, NULL, NULL);
48761d06d6bSBaptiste Daroussin 
48861d06d6bSBaptiste Daroussin 		if (conf.manpath.sz == 0) {
48961d06d6bSBaptiste Daroussin 			exitcode = (int)MANDOCLEVEL_BADARG;
49061d06d6bSBaptiste Daroussin 			say("", "Empty manpath");
49161d06d6bSBaptiste Daroussin 		}
49261d06d6bSBaptiste Daroussin 
49361d06d6bSBaptiste Daroussin 		/*
49461d06d6bSBaptiste Daroussin 		 * First scan the tree rooted at a base directory, then
49561d06d6bSBaptiste Daroussin 		 * build a new database and finally move it into place.
49661d06d6bSBaptiste Daroussin 		 * Ignore zero-length directories and strip trailing
49761d06d6bSBaptiste Daroussin 		 * slashes.
49861d06d6bSBaptiste Daroussin 		 */
49961d06d6bSBaptiste Daroussin 		for (j = 0; j < conf.manpath.sz; j++) {
50061d06d6bSBaptiste Daroussin 			sz = strlen(conf.manpath.paths[j]);
50161d06d6bSBaptiste Daroussin 			if (sz && conf.manpath.paths[j][sz - 1] == '/')
50261d06d6bSBaptiste Daroussin 				conf.manpath.paths[j][--sz] = '\0';
5036d38604fSBaptiste Daroussin 			if (sz == 0)
50461d06d6bSBaptiste Daroussin 				continue;
50561d06d6bSBaptiste Daroussin 
50661d06d6bSBaptiste Daroussin 			if (j) {
50761d06d6bSBaptiste Daroussin 				mandoc_ohash_init(&mpages, 6,
50861d06d6bSBaptiste Daroussin 				    offsetof(struct mpage, inodev));
50961d06d6bSBaptiste Daroussin 				mandoc_ohash_init(&mlinks, 6,
51061d06d6bSBaptiste Daroussin 				    offsetof(struct mlink, file));
51161d06d6bSBaptiste Daroussin 			}
51261d06d6bSBaptiste Daroussin 
5136d38604fSBaptiste Daroussin 			if (set_basedir(conf.manpath.paths[j], argc > 0) == 0)
51461d06d6bSBaptiste Daroussin 				continue;
5156d38604fSBaptiste Daroussin 			if (treescan() == 0)
51661d06d6bSBaptiste Daroussin 				continue;
51761d06d6bSBaptiste Daroussin 			dba = dba_new(128);
51861d06d6bSBaptiste Daroussin 			mpages_merge(dba, mp);
51961d06d6bSBaptiste Daroussin 			if (nodb == 0)
52061d06d6bSBaptiste Daroussin 				dbwrite(dba);
52161d06d6bSBaptiste Daroussin 			dba_free(dba);
52261d06d6bSBaptiste Daroussin 
52361d06d6bSBaptiste Daroussin 			if (j + 1 < conf.manpath.sz) {
52461d06d6bSBaptiste Daroussin 				mpages_free();
52561d06d6bSBaptiste Daroussin 				ohash_delete(&mpages);
52661d06d6bSBaptiste Daroussin 				ohash_delete(&mlinks);
52761d06d6bSBaptiste Daroussin 			}
52861d06d6bSBaptiste Daroussin 		}
52961d06d6bSBaptiste Daroussin 	}
53061d06d6bSBaptiste Daroussin out:
53161d06d6bSBaptiste Daroussin 	manconf_free(&conf);
53261d06d6bSBaptiste Daroussin 	mparse_free(mp);
53361d06d6bSBaptiste Daroussin 	mchars_free();
53461d06d6bSBaptiste Daroussin 	mpages_free();
53561d06d6bSBaptiste Daroussin 	ohash_delete(&mpages);
53661d06d6bSBaptiste Daroussin 	ohash_delete(&mlinks);
537c1c95addSBrooks Davis #if DEBUG_MEMORY
538c1c95addSBrooks Davis 	mandoc_dbg_finish();
539c1c95addSBrooks Davis #endif
54061d06d6bSBaptiste Daroussin 	return exitcode;
54161d06d6bSBaptiste Daroussin usage:
54261d06d6bSBaptiste Daroussin 	progname = getprogname();
54361d06d6bSBaptiste Daroussin 	fprintf(stderr, "usage: %s [-aDnpQ] [-C file] [-Tutf8]\n"
54461d06d6bSBaptiste Daroussin 			"       %s [-aDnpQ] [-Tutf8] dir ...\n"
54561d06d6bSBaptiste Daroussin 			"       %s [-DnpQ] [-Tutf8] -d dir [file ...]\n"
54661d06d6bSBaptiste Daroussin 			"       %s [-Dnp] -u dir [file ...]\n"
54761d06d6bSBaptiste Daroussin 			"       %s [-Q] -t file ...\n",
54861d06d6bSBaptiste Daroussin 		        progname, progname, progname, progname, progname);
54961d06d6bSBaptiste Daroussin 
55061d06d6bSBaptiste Daroussin 	return (int)MANDOCLEVEL_BADARG;
55161d06d6bSBaptiste Daroussin }
55261d06d6bSBaptiste Daroussin 
55361d06d6bSBaptiste Daroussin /*
55461d06d6bSBaptiste Daroussin  * To get a singly linked list in alpha order while inserting entries
55561d06d6bSBaptiste Daroussin  * at the beginning, process directory entries in reverse alpha order.
55661d06d6bSBaptiste Daroussin  */
55761d06d6bSBaptiste Daroussin static int
55861d06d6bSBaptiste Daroussin #if HAVE_FTS_COMPARE_CONST
fts_compare(const FTSENT * const * a,const FTSENT * const * b)55961d06d6bSBaptiste Daroussin fts_compare(const FTSENT *const *a, const FTSENT *const *b)
56061d06d6bSBaptiste Daroussin #else
56161d06d6bSBaptiste Daroussin fts_compare(const FTSENT **a, const FTSENT **b)
56261d06d6bSBaptiste Daroussin #endif
56361d06d6bSBaptiste Daroussin {
56461d06d6bSBaptiste Daroussin 	return -strcmp((*a)->fts_name, (*b)->fts_name);
56561d06d6bSBaptiste Daroussin }
56661d06d6bSBaptiste Daroussin 
56761d06d6bSBaptiste Daroussin /*
56861d06d6bSBaptiste Daroussin  * Scan a directory tree rooted at "basedir" for manpages.
56961d06d6bSBaptiste Daroussin  * We use fts(), scanning directory parts along the way for clues to our
57061d06d6bSBaptiste Daroussin  * section and architecture.
57161d06d6bSBaptiste Daroussin  *
57261d06d6bSBaptiste Daroussin  * If use_all has been specified, grok all files.
57361d06d6bSBaptiste Daroussin  * If not, sanitise paths to the following:
57461d06d6bSBaptiste Daroussin  *
57561d06d6bSBaptiste Daroussin  *   [./]man*[/<arch>]/<name>.<section>
57661d06d6bSBaptiste Daroussin  *   or
57761d06d6bSBaptiste Daroussin  *   [./]cat<section>[/<arch>]/<name>.0
57861d06d6bSBaptiste Daroussin  *
57961d06d6bSBaptiste Daroussin  * TODO: accommodate for multi-language directories.
58061d06d6bSBaptiste Daroussin  */
58161d06d6bSBaptiste Daroussin static int
treescan(void)58261d06d6bSBaptiste Daroussin treescan(void)
58361d06d6bSBaptiste Daroussin {
58461d06d6bSBaptiste Daroussin 	char		 buf[PATH_MAX];
58561d06d6bSBaptiste Daroussin 	FTS		*f;
58661d06d6bSBaptiste Daroussin 	FTSENT		*ff;
58761d06d6bSBaptiste Daroussin 	struct mlink	*mlink;
58861d06d6bSBaptiste Daroussin 	int		 gzip;
58961d06d6bSBaptiste Daroussin 	enum form	 dform;
59061d06d6bSBaptiste Daroussin 	char		*dsec, *arch, *fsec, *cp;
59161d06d6bSBaptiste Daroussin 	const char	*path;
59261d06d6bSBaptiste Daroussin 	const char	*argv[2];
59361d06d6bSBaptiste Daroussin 
59461d06d6bSBaptiste Daroussin 	argv[0] = ".";
59561d06d6bSBaptiste Daroussin 	argv[1] = NULL;
59661d06d6bSBaptiste Daroussin 
59761d06d6bSBaptiste Daroussin 	f = fts_open((char * const *)argv, FTS_PHYSICAL | FTS_NOCHDIR,
59861d06d6bSBaptiste Daroussin 	    fts_compare);
59961d06d6bSBaptiste Daroussin 	if (f == NULL) {
60061d06d6bSBaptiste Daroussin 		exitcode = (int)MANDOCLEVEL_SYSERR;
60161d06d6bSBaptiste Daroussin 		say("", "&fts_open");
60261d06d6bSBaptiste Daroussin 		return 0;
60361d06d6bSBaptiste Daroussin 	}
60461d06d6bSBaptiste Daroussin 
60561d06d6bSBaptiste Daroussin 	dsec = arch = NULL;
60661d06d6bSBaptiste Daroussin 	dform = FORM_NONE;
60761d06d6bSBaptiste Daroussin 
60861d06d6bSBaptiste Daroussin 	while ((ff = fts_read(f)) != NULL) {
60961d06d6bSBaptiste Daroussin 		path = ff->fts_path + 2;
61061d06d6bSBaptiste Daroussin 		switch (ff->fts_info) {
61161d06d6bSBaptiste Daroussin 
61261d06d6bSBaptiste Daroussin 		/*
61361d06d6bSBaptiste Daroussin 		 * Symbolic links require various sanity checks,
61461d06d6bSBaptiste Daroussin 		 * then get handled just like regular files.
61561d06d6bSBaptiste Daroussin 		 */
61661d06d6bSBaptiste Daroussin 		case FTS_SL:
61761d06d6bSBaptiste Daroussin 			if (realpath(path, buf) == NULL) {
61861d06d6bSBaptiste Daroussin 				if (warnings)
61961d06d6bSBaptiste Daroussin 					say(path, "&realpath");
62061d06d6bSBaptiste Daroussin 				continue;
62161d06d6bSBaptiste Daroussin 			}
6226d38604fSBaptiste Daroussin 			if (strncmp(buf, basedir, basedir_len) != 0
6236d38604fSBaptiste Daroussin #ifdef READ_ALLOWED_PATH
6246d38604fSBaptiste Daroussin 			    && !read_allowed(buf)
62561d06d6bSBaptiste Daroussin #endif
62661d06d6bSBaptiste Daroussin 			) {
62761d06d6bSBaptiste Daroussin 				if (warnings) say("",
62861d06d6bSBaptiste Daroussin 				    "%s: outside base directory", buf);
62961d06d6bSBaptiste Daroussin 				continue;
63061d06d6bSBaptiste Daroussin 			}
63161d06d6bSBaptiste Daroussin 			/* Use logical inode to avoid mpages dupe. */
63261d06d6bSBaptiste Daroussin 			if (stat(path, ff->fts_statp) == -1) {
63361d06d6bSBaptiste Daroussin 				if (warnings)
63461d06d6bSBaptiste Daroussin 					say(path, "&stat");
63561d06d6bSBaptiste Daroussin 				continue;
63661d06d6bSBaptiste Daroussin 			}
6376d38604fSBaptiste Daroussin 			if ((ff->fts_statp->st_mode & S_IFMT) != S_IFREG)
6386d38604fSBaptiste Daroussin 				continue;
63961d06d6bSBaptiste Daroussin 			/* FALLTHROUGH */
64061d06d6bSBaptiste Daroussin 
64161d06d6bSBaptiste Daroussin 		/*
64261d06d6bSBaptiste Daroussin 		 * If we're a regular file, add an mlink by using the
64361d06d6bSBaptiste Daroussin 		 * stored directory data and handling the filename.
64461d06d6bSBaptiste Daroussin 		 */
64561d06d6bSBaptiste Daroussin 		case FTS_F:
64661d06d6bSBaptiste Daroussin 			if ( ! strcmp(path, MANDOC_DB))
64761d06d6bSBaptiste Daroussin 				continue;
64861d06d6bSBaptiste Daroussin 			if ( ! use_all && ff->fts_level < 2) {
64961d06d6bSBaptiste Daroussin 				if (warnings)
65061d06d6bSBaptiste Daroussin 					say(path, "Extraneous file");
65161d06d6bSBaptiste Daroussin 				continue;
65261d06d6bSBaptiste Daroussin 			}
65361d06d6bSBaptiste Daroussin 			gzip = 0;
65461d06d6bSBaptiste Daroussin 			fsec = NULL;
65561d06d6bSBaptiste Daroussin 			while (fsec == NULL) {
65661d06d6bSBaptiste Daroussin 				fsec = strrchr(ff->fts_name, '.');
65761d06d6bSBaptiste Daroussin 				if (fsec == NULL || strcmp(fsec+1, "gz"))
65861d06d6bSBaptiste Daroussin 					break;
65961d06d6bSBaptiste Daroussin 				gzip = 1;
66061d06d6bSBaptiste Daroussin 				*fsec = '\0';
66161d06d6bSBaptiste Daroussin 				fsec = NULL;
66261d06d6bSBaptiste Daroussin 			}
66361d06d6bSBaptiste Daroussin 			if (fsec == NULL) {
66461d06d6bSBaptiste Daroussin 				if ( ! use_all) {
66561d06d6bSBaptiste Daroussin 					if (warnings)
66661d06d6bSBaptiste Daroussin 						say(path,
66761d06d6bSBaptiste Daroussin 						    "No filename suffix");
66861d06d6bSBaptiste Daroussin 					continue;
66961d06d6bSBaptiste Daroussin 				}
67061d06d6bSBaptiste Daroussin 			} else if ( ! strcmp(++fsec, "html")) {
67161d06d6bSBaptiste Daroussin 				if (warnings)
67261d06d6bSBaptiste Daroussin 					say(path, "Skip html");
67361d06d6bSBaptiste Daroussin 				continue;
67461d06d6bSBaptiste Daroussin 			} else if ( ! strcmp(fsec, "ps")) {
67561d06d6bSBaptiste Daroussin 				if (warnings)
67661d06d6bSBaptiste Daroussin 					say(path, "Skip ps");
67761d06d6bSBaptiste Daroussin 				continue;
67861d06d6bSBaptiste Daroussin 			} else if ( ! strcmp(fsec, "pdf")) {
67961d06d6bSBaptiste Daroussin 				if (warnings)
68061d06d6bSBaptiste Daroussin 					say(path, "Skip pdf");
68161d06d6bSBaptiste Daroussin 				continue;
68261d06d6bSBaptiste Daroussin 			} else if ( ! use_all &&
68361d06d6bSBaptiste Daroussin 			    ((dform == FORM_SRC &&
68461d06d6bSBaptiste Daroussin 			      strncmp(fsec, dsec, strlen(dsec))) ||
68561d06d6bSBaptiste Daroussin 			     (dform == FORM_CAT && strcmp(fsec, "0")))) {
68661d06d6bSBaptiste Daroussin 				if (warnings)
68761d06d6bSBaptiste Daroussin 					say(path, "Wrong filename suffix");
68861d06d6bSBaptiste Daroussin 				continue;
68961d06d6bSBaptiste Daroussin 			} else
69061d06d6bSBaptiste Daroussin 				fsec[-1] = '\0';
69161d06d6bSBaptiste Daroussin 
69261d06d6bSBaptiste Daroussin 			mlink = mandoc_calloc(1, sizeof(struct mlink));
69361d06d6bSBaptiste Daroussin 			if (strlcpy(mlink->file, path,
69461d06d6bSBaptiste Daroussin 			    sizeof(mlink->file)) >=
69561d06d6bSBaptiste Daroussin 			    sizeof(mlink->file)) {
69661d06d6bSBaptiste Daroussin 				say(path, "Filename too long");
69761d06d6bSBaptiste Daroussin 				free(mlink);
69861d06d6bSBaptiste Daroussin 				continue;
69961d06d6bSBaptiste Daroussin 			}
70061d06d6bSBaptiste Daroussin 			mlink->dform = dform;
70161d06d6bSBaptiste Daroussin 			mlink->dsec = dsec;
70261d06d6bSBaptiste Daroussin 			mlink->arch = arch;
70361d06d6bSBaptiste Daroussin 			mlink->name = ff->fts_name;
70461d06d6bSBaptiste Daroussin 			mlink->fsec = fsec;
70561d06d6bSBaptiste Daroussin 			mlink->gzip = gzip;
70661d06d6bSBaptiste Daroussin 			mlink_add(mlink, ff->fts_statp);
70761d06d6bSBaptiste Daroussin 			continue;
70861d06d6bSBaptiste Daroussin 
70961d06d6bSBaptiste Daroussin 		case FTS_D:
71061d06d6bSBaptiste Daroussin 		case FTS_DP:
71161d06d6bSBaptiste Daroussin 			break;
71261d06d6bSBaptiste Daroussin 
71361d06d6bSBaptiste Daroussin 		default:
71461d06d6bSBaptiste Daroussin 			if (warnings)
71561d06d6bSBaptiste Daroussin 				say(path, "Not a regular file");
71661d06d6bSBaptiste Daroussin 			continue;
71761d06d6bSBaptiste Daroussin 		}
71861d06d6bSBaptiste Daroussin 
71961d06d6bSBaptiste Daroussin 		switch (ff->fts_level) {
72061d06d6bSBaptiste Daroussin 		case 0:
72161d06d6bSBaptiste Daroussin 			/* Ignore the root directory. */
72261d06d6bSBaptiste Daroussin 			break;
72361d06d6bSBaptiste Daroussin 		case 1:
72461d06d6bSBaptiste Daroussin 			/*
72561d06d6bSBaptiste Daroussin 			 * This might contain manX/ or catX/.
72661d06d6bSBaptiste Daroussin 			 * Try to infer this from the name.
72761d06d6bSBaptiste Daroussin 			 * If we're not in use_all, enforce it.
72861d06d6bSBaptiste Daroussin 			 */
72961d06d6bSBaptiste Daroussin 			cp = ff->fts_name;
73061d06d6bSBaptiste Daroussin 			if (ff->fts_info == FTS_DP) {
73161d06d6bSBaptiste Daroussin 				dform = FORM_NONE;
73261d06d6bSBaptiste Daroussin 				dsec = NULL;
73361d06d6bSBaptiste Daroussin 				break;
73461d06d6bSBaptiste Daroussin 			}
73561d06d6bSBaptiste Daroussin 
73661d06d6bSBaptiste Daroussin 			if ( ! strncmp(cp, "man", 3)) {
73761d06d6bSBaptiste Daroussin 				dform = FORM_SRC;
73861d06d6bSBaptiste Daroussin 				dsec = cp + 3;
73961d06d6bSBaptiste Daroussin 			} else if ( ! strncmp(cp, "cat", 3)) {
74061d06d6bSBaptiste Daroussin 				dform = FORM_CAT;
74161d06d6bSBaptiste Daroussin 				dsec = cp + 3;
74261d06d6bSBaptiste Daroussin 			} else {
74361d06d6bSBaptiste Daroussin 				dform = FORM_NONE;
74461d06d6bSBaptiste Daroussin 				dsec = NULL;
74561d06d6bSBaptiste Daroussin 			}
74661d06d6bSBaptiste Daroussin 
74761d06d6bSBaptiste Daroussin 			if (dsec != NULL || use_all)
74861d06d6bSBaptiste Daroussin 				break;
74961d06d6bSBaptiste Daroussin 
75061d06d6bSBaptiste Daroussin 			if (warnings)
75161d06d6bSBaptiste Daroussin 				say(path, "Unknown directory part");
75261d06d6bSBaptiste Daroussin 			fts_set(f, ff, FTS_SKIP);
75361d06d6bSBaptiste Daroussin 			break;
75461d06d6bSBaptiste Daroussin 		case 2:
75561d06d6bSBaptiste Daroussin 			/*
75661d06d6bSBaptiste Daroussin 			 * Possibly our architecture.
75761d06d6bSBaptiste Daroussin 			 * If we're descending, keep tabs on it.
75861d06d6bSBaptiste Daroussin 			 */
75961d06d6bSBaptiste Daroussin 			if (ff->fts_info != FTS_DP && dsec != NULL)
76061d06d6bSBaptiste Daroussin 				arch = ff->fts_name;
76161d06d6bSBaptiste Daroussin 			else
76261d06d6bSBaptiste Daroussin 				arch = NULL;
76361d06d6bSBaptiste Daroussin 			break;
76461d06d6bSBaptiste Daroussin 		default:
76561d06d6bSBaptiste Daroussin 			if (ff->fts_info == FTS_DP || use_all)
76661d06d6bSBaptiste Daroussin 				break;
76761d06d6bSBaptiste Daroussin 			if (warnings)
76861d06d6bSBaptiste Daroussin 				say(path, "Extraneous directory part");
76961d06d6bSBaptiste Daroussin 			fts_set(f, ff, FTS_SKIP);
77061d06d6bSBaptiste Daroussin 			break;
77161d06d6bSBaptiste Daroussin 		}
77261d06d6bSBaptiste Daroussin 	}
77361d06d6bSBaptiste Daroussin 
77461d06d6bSBaptiste Daroussin 	fts_close(f);
77561d06d6bSBaptiste Daroussin 	return 1;
77661d06d6bSBaptiste Daroussin }
77761d06d6bSBaptiste Daroussin 
77861d06d6bSBaptiste Daroussin /*
77961d06d6bSBaptiste Daroussin  * Add a file to the mlinks table.
78061d06d6bSBaptiste Daroussin  * Do not verify that it's a "valid" looking manpage (we'll do that
78161d06d6bSBaptiste Daroussin  * later).
78261d06d6bSBaptiste Daroussin  *
78361d06d6bSBaptiste Daroussin  * Try to infer the manual section, architecture, and page name from the
78461d06d6bSBaptiste Daroussin  * path, assuming it looks like
78561d06d6bSBaptiste Daroussin  *
78661d06d6bSBaptiste Daroussin  *   [./]man*[/<arch>]/<name>.<section>
78761d06d6bSBaptiste Daroussin  *   or
78861d06d6bSBaptiste Daroussin  *   [./]cat<section>[/<arch>]/<name>.0
78961d06d6bSBaptiste Daroussin  *
79061d06d6bSBaptiste Daroussin  * See treescan() for the fts(3) version of this.
79161d06d6bSBaptiste Daroussin  */
79261d06d6bSBaptiste Daroussin static void
filescan(const char * infile)7936d38604fSBaptiste Daroussin filescan(const char *infile)
79461d06d6bSBaptiste Daroussin {
79561d06d6bSBaptiste Daroussin 	struct stat	 st;
79661d06d6bSBaptiste Daroussin 	struct mlink	*mlink;
7976d38604fSBaptiste Daroussin 	char		*linkfile, *p, *realdir, *start, *usefile;
7986d38604fSBaptiste Daroussin 	size_t		 realdir_len;
79961d06d6bSBaptiste Daroussin 
80061d06d6bSBaptiste Daroussin 	assert(use_all);
80161d06d6bSBaptiste Daroussin 
8026d38604fSBaptiste Daroussin 	if (strncmp(infile, "./", 2) == 0)
8036d38604fSBaptiste Daroussin 		infile += 2;
80461d06d6bSBaptiste Daroussin 
80561d06d6bSBaptiste Daroussin 	/*
80661d06d6bSBaptiste Daroussin 	 * We have to do lstat(2) before realpath(3) loses
80761d06d6bSBaptiste Daroussin 	 * the information whether this is a symbolic link.
80861d06d6bSBaptiste Daroussin 	 * We need to know that because for symbolic links,
809c1c95addSBrooks Davis 	 * we want to use the original file name, while for
81061d06d6bSBaptiste Daroussin 	 * regular files, we want to use the real path.
81161d06d6bSBaptiste Daroussin 	 */
8126d38604fSBaptiste Daroussin 	if (lstat(infile, &st) == -1) {
81361d06d6bSBaptiste Daroussin 		exitcode = (int)MANDOCLEVEL_BADARG;
8146d38604fSBaptiste Daroussin 		say(infile, "&lstat");
81561d06d6bSBaptiste Daroussin 		return;
8166d38604fSBaptiste Daroussin 	} else if (S_ISREG(st.st_mode) == 0 && S_ISLNK(st.st_mode) == 0) {
81761d06d6bSBaptiste Daroussin 		exitcode = (int)MANDOCLEVEL_BADARG;
8186d38604fSBaptiste Daroussin 		say(infile, "Not a regular file");
81961d06d6bSBaptiste Daroussin 		return;
82061d06d6bSBaptiste Daroussin 	}
82161d06d6bSBaptiste Daroussin 
82261d06d6bSBaptiste Daroussin 	/*
82361d06d6bSBaptiste Daroussin 	 * We have to resolve the file name to the real path
82461d06d6bSBaptiste Daroussin 	 * in any case for the base directory check.
82561d06d6bSBaptiste Daroussin 	 */
8266d38604fSBaptiste Daroussin 	if ((usefile = realpath(infile, NULL)) == NULL) {
82761d06d6bSBaptiste Daroussin 		exitcode = (int)MANDOCLEVEL_BADARG;
8286d38604fSBaptiste Daroussin 		say(infile, "&realpath");
82961d06d6bSBaptiste Daroussin 		return;
83061d06d6bSBaptiste Daroussin 	}
83161d06d6bSBaptiste Daroussin 
8326d38604fSBaptiste Daroussin 	if (op == OP_TEST)
8336d38604fSBaptiste Daroussin 		start = usefile;
8346d38604fSBaptiste Daroussin 	else if (strncmp(usefile, basedir, basedir_len) == 0)
8356d38604fSBaptiste Daroussin 		start = usefile + basedir_len;
8366d38604fSBaptiste Daroussin #ifdef READ_ALLOWED_PATH
8376d38604fSBaptiste Daroussin 	else if (read_allowed(usefile))
8386d38604fSBaptiste Daroussin 		start = usefile;
83961d06d6bSBaptiste Daroussin #endif
84061d06d6bSBaptiste Daroussin 	else {
84161d06d6bSBaptiste Daroussin 		exitcode = (int)MANDOCLEVEL_BADARG;
8426d38604fSBaptiste Daroussin 		say("", "%s: outside base directory", infile);
8436d38604fSBaptiste Daroussin 		free(usefile);
84461d06d6bSBaptiste Daroussin 		return;
84561d06d6bSBaptiste Daroussin 	}
84661d06d6bSBaptiste Daroussin 
84761d06d6bSBaptiste Daroussin 	/*
84861d06d6bSBaptiste Daroussin 	 * Now we are sure the file is inside our tree.
84961d06d6bSBaptiste Daroussin 	 * If it is a symbolic link, ignore the real path
85061d06d6bSBaptiste Daroussin 	 * and use the original name.
85161d06d6bSBaptiste Daroussin 	 */
8526d38604fSBaptiste Daroussin 	do {
8536d38604fSBaptiste Daroussin 		if (S_ISLNK(st.st_mode) == 0)
8546d38604fSBaptiste Daroussin 			break;
8556d38604fSBaptiste Daroussin 
8566d38604fSBaptiste Daroussin 		/*
8576d38604fSBaptiste Daroussin 		 * Some implementations of realpath(3) may succeed
8586d38604fSBaptiste Daroussin 		 * even if the target of the link does not exist,
8596d38604fSBaptiste Daroussin 		 * so check again for extra safety.
8606d38604fSBaptiste Daroussin 		 */
8616d38604fSBaptiste Daroussin 		if (stat(usefile, &st) == -1) {
86261d06d6bSBaptiste Daroussin 			exitcode = (int)MANDOCLEVEL_BADARG;
8636d38604fSBaptiste Daroussin 			say(infile, "&stat");
8646d38604fSBaptiste Daroussin 			free(usefile);
86561d06d6bSBaptiste Daroussin 			return;
86661d06d6bSBaptiste Daroussin 		}
8676d38604fSBaptiste Daroussin 		linkfile = mandoc_strdup(infile);
8686d38604fSBaptiste Daroussin 		if (op == OP_TEST) {
8696d38604fSBaptiste Daroussin 			free(usefile);
8706d38604fSBaptiste Daroussin 			start = usefile = linkfile;
8716d38604fSBaptiste Daroussin 			break;
8726d38604fSBaptiste Daroussin 		}
8736d38604fSBaptiste Daroussin 		if (strncmp(infile, basedir, basedir_len) == 0) {
8746d38604fSBaptiste Daroussin 			free(usefile);
8756d38604fSBaptiste Daroussin 			usefile = linkfile;
8766d38604fSBaptiste Daroussin 			start = usefile + basedir_len;
8776d38604fSBaptiste Daroussin 			break;
8786d38604fSBaptiste Daroussin 		}
8796d38604fSBaptiste Daroussin 
8806d38604fSBaptiste Daroussin 		/*
8816d38604fSBaptiste Daroussin 		 * This symbolic link points into the basedir
8826d38604fSBaptiste Daroussin 		 * from the outside.  Let's see whether any of
8836d38604fSBaptiste Daroussin 		 * the parent directories resolve to the basedir.
8846d38604fSBaptiste Daroussin 		 */
8856d38604fSBaptiste Daroussin 		p = strchr(linkfile, '\0');
8866d38604fSBaptiste Daroussin 		do {
8876d38604fSBaptiste Daroussin 			while (*--p != '/')
8886d38604fSBaptiste Daroussin 				continue;
8896d38604fSBaptiste Daroussin 			*p = '\0';
8906d38604fSBaptiste Daroussin 			if ((realdir = realpath(linkfile, NULL)) == NULL) {
8916d38604fSBaptiste Daroussin 				exitcode = (int)MANDOCLEVEL_BADARG;
8926d38604fSBaptiste Daroussin 				say(infile, "&realpath");
8936d38604fSBaptiste Daroussin 				free(linkfile);
8946d38604fSBaptiste Daroussin 				free(usefile);
89561d06d6bSBaptiste Daroussin 				return;
89661d06d6bSBaptiste Daroussin 			}
8976d38604fSBaptiste Daroussin 			realdir_len = strlen(realdir) + 1;
8986d38604fSBaptiste Daroussin 			free(realdir);
8996d38604fSBaptiste Daroussin 			*p = '/';
9006d38604fSBaptiste Daroussin 		} while (realdir_len > basedir_len);
9016d38604fSBaptiste Daroussin 
9026d38604fSBaptiste Daroussin 		/*
9036d38604fSBaptiste Daroussin 		 * If one of the directories resolves to the basedir,
9046d38604fSBaptiste Daroussin 		 * use the rest of the original name.
9056d38604fSBaptiste Daroussin 		 * Otherwise, the best we can do
9066d38604fSBaptiste Daroussin 		 * is to use the filename pointed to.
9076d38604fSBaptiste Daroussin 		 */
9086d38604fSBaptiste Daroussin 		if (realdir_len == basedir_len) {
9096d38604fSBaptiste Daroussin 			free(usefile);
9106d38604fSBaptiste Daroussin 			usefile = linkfile;
9116d38604fSBaptiste Daroussin 			start = p + 1;
9126d38604fSBaptiste Daroussin 		} else {
9136d38604fSBaptiste Daroussin 			free(linkfile);
9146d38604fSBaptiste Daroussin 			start = usefile + basedir_len;
91561d06d6bSBaptiste Daroussin 		}
9166d38604fSBaptiste Daroussin 	} while (/* CONSTCOND */ 0);
91761d06d6bSBaptiste Daroussin 
91861d06d6bSBaptiste Daroussin 	mlink = mandoc_calloc(1, sizeof(struct mlink));
91961d06d6bSBaptiste Daroussin 	mlink->dform = FORM_NONE;
92061d06d6bSBaptiste Daroussin 	if (strlcpy(mlink->file, start, sizeof(mlink->file)) >=
92161d06d6bSBaptiste Daroussin 	    sizeof(mlink->file)) {
92261d06d6bSBaptiste Daroussin 		say(start, "Filename too long");
92361d06d6bSBaptiste Daroussin 		free(mlink);
9246d38604fSBaptiste Daroussin 		free(usefile);
92561d06d6bSBaptiste Daroussin 		return;
92661d06d6bSBaptiste Daroussin 	}
92761d06d6bSBaptiste Daroussin 
92861d06d6bSBaptiste Daroussin 	/*
92961d06d6bSBaptiste Daroussin 	 * In test mode or when the original name is absolute
93061d06d6bSBaptiste Daroussin 	 * but outside our tree, guess the base directory.
93161d06d6bSBaptiste Daroussin 	 */
93261d06d6bSBaptiste Daroussin 
9336d38604fSBaptiste Daroussin 	if (op == OP_TEST || (start == usefile && *start == '/')) {
9346d38604fSBaptiste Daroussin 		if (strncmp(usefile, "man/", 4) == 0)
9356d38604fSBaptiste Daroussin 			start = usefile + 4;
9366d38604fSBaptiste Daroussin 		else if ((start = strstr(usefile, "/man/")) != NULL)
93761d06d6bSBaptiste Daroussin 			start += 5;
93861d06d6bSBaptiste Daroussin 		else
9396d38604fSBaptiste Daroussin 			start = usefile;
94061d06d6bSBaptiste Daroussin 	}
94161d06d6bSBaptiste Daroussin 
94261d06d6bSBaptiste Daroussin 	/*
94361d06d6bSBaptiste Daroussin 	 * First try to guess our directory structure.
94461d06d6bSBaptiste Daroussin 	 * If we find a separator, try to look for man* or cat*.
94561d06d6bSBaptiste Daroussin 	 * If we find one of these and what's underneath is a directory,
94661d06d6bSBaptiste Daroussin 	 * assume it's an architecture.
94761d06d6bSBaptiste Daroussin 	 */
9486d38604fSBaptiste Daroussin 	if ((p = strchr(start, '/')) != NULL) {
94961d06d6bSBaptiste Daroussin 		*p++ = '\0';
9506d38604fSBaptiste Daroussin 		if (strncmp(start, "man", 3) == 0) {
95161d06d6bSBaptiste Daroussin 			mlink->dform = FORM_SRC;
95261d06d6bSBaptiste Daroussin 			mlink->dsec = start + 3;
9536d38604fSBaptiste Daroussin 		} else if (strncmp(start, "cat", 3) == 0) {
95461d06d6bSBaptiste Daroussin 			mlink->dform = FORM_CAT;
95561d06d6bSBaptiste Daroussin 			mlink->dsec = start + 3;
95661d06d6bSBaptiste Daroussin 		}
95761d06d6bSBaptiste Daroussin 
95861d06d6bSBaptiste Daroussin 		start = p;
9596d38604fSBaptiste Daroussin 		if (mlink->dsec != NULL && (p = strchr(start, '/')) != NULL) {
96061d06d6bSBaptiste Daroussin 			*p++ = '\0';
96161d06d6bSBaptiste Daroussin 			mlink->arch = start;
96261d06d6bSBaptiste Daroussin 			start = p;
96361d06d6bSBaptiste Daroussin 		}
96461d06d6bSBaptiste Daroussin 	}
96561d06d6bSBaptiste Daroussin 
96661d06d6bSBaptiste Daroussin 	/*
96761d06d6bSBaptiste Daroussin 	 * Now check the file suffix.
96861d06d6bSBaptiste Daroussin 	 * Suffix of `.0' indicates a catpage, `.1-9' is a manpage.
96961d06d6bSBaptiste Daroussin 	 */
97061d06d6bSBaptiste Daroussin 	p = strrchr(start, '\0');
9716d38604fSBaptiste Daroussin 	while (p-- > start && *p != '/' && *p != '.')
9726d38604fSBaptiste Daroussin 		continue;
97361d06d6bSBaptiste Daroussin 
9746d38604fSBaptiste Daroussin 	if (*p == '.') {
97561d06d6bSBaptiste Daroussin 		*p++ = '\0';
97661d06d6bSBaptiste Daroussin 		mlink->fsec = p;
97761d06d6bSBaptiste Daroussin 	}
97861d06d6bSBaptiste Daroussin 
97961d06d6bSBaptiste Daroussin 	/*
98061d06d6bSBaptiste Daroussin 	 * Now try to parse the name.
98161d06d6bSBaptiste Daroussin 	 * Use the filename portion of the path.
98261d06d6bSBaptiste Daroussin 	 */
98361d06d6bSBaptiste Daroussin 	mlink->name = start;
9846d38604fSBaptiste Daroussin 	if ((p = strrchr(start, '/')) != NULL) {
98561d06d6bSBaptiste Daroussin 		mlink->name = p + 1;
98661d06d6bSBaptiste Daroussin 		*p = '\0';
98761d06d6bSBaptiste Daroussin 	}
98861d06d6bSBaptiste Daroussin 	mlink_add(mlink, &st);
9896d38604fSBaptiste Daroussin 	free(usefile);
99061d06d6bSBaptiste Daroussin }
99161d06d6bSBaptiste Daroussin 
99261d06d6bSBaptiste Daroussin static void
mlink_add(struct mlink * mlink,const struct stat * st)99361d06d6bSBaptiste Daroussin mlink_add(struct mlink *mlink, const struct stat *st)
99461d06d6bSBaptiste Daroussin {
99561d06d6bSBaptiste Daroussin 	struct inodev	 inodev;
99661d06d6bSBaptiste Daroussin 	struct mpage	*mpage;
99761d06d6bSBaptiste Daroussin 	unsigned int	 slot;
99861d06d6bSBaptiste Daroussin 
99961d06d6bSBaptiste Daroussin 	assert(NULL != mlink->file);
100061d06d6bSBaptiste Daroussin 
100161d06d6bSBaptiste Daroussin 	mlink->dsec = mandoc_strdup(mlink->dsec ? mlink->dsec : "");
100261d06d6bSBaptiste Daroussin 	mlink->arch = mandoc_strdup(mlink->arch ? mlink->arch : "");
100361d06d6bSBaptiste Daroussin 	mlink->name = mandoc_strdup(mlink->name ? mlink->name : "");
100461d06d6bSBaptiste Daroussin 	mlink->fsec = mandoc_strdup(mlink->fsec ? mlink->fsec : "");
100561d06d6bSBaptiste Daroussin 
100661d06d6bSBaptiste Daroussin 	if ('0' == *mlink->fsec) {
100761d06d6bSBaptiste Daroussin 		free(mlink->fsec);
100861d06d6bSBaptiste Daroussin 		mlink->fsec = mandoc_strdup(mlink->dsec);
100961d06d6bSBaptiste Daroussin 		mlink->fform = FORM_CAT;
101061d06d6bSBaptiste Daroussin 	} else if ('1' <= *mlink->fsec && '9' >= *mlink->fsec)
101161d06d6bSBaptiste Daroussin 		mlink->fform = FORM_SRC;
101261d06d6bSBaptiste Daroussin 	else
101361d06d6bSBaptiste Daroussin 		mlink->fform = FORM_NONE;
101461d06d6bSBaptiste Daroussin 
101561d06d6bSBaptiste Daroussin 	slot = ohash_qlookup(&mlinks, mlink->file);
101661d06d6bSBaptiste Daroussin 	assert(NULL == ohash_find(&mlinks, slot));
101761d06d6bSBaptiste Daroussin 	ohash_insert(&mlinks, slot, mlink);
101861d06d6bSBaptiste Daroussin 
101961d06d6bSBaptiste Daroussin 	memset(&inodev, 0, sizeof(inodev));  /* Clear padding. */
102061d06d6bSBaptiste Daroussin 	inodev.st_ino = st->st_ino;
102161d06d6bSBaptiste Daroussin 	inodev.st_dev = st->st_dev;
102261d06d6bSBaptiste Daroussin 	slot = ohash_lookup_memory(&mpages, (char *)&inodev,
102361d06d6bSBaptiste Daroussin 	    sizeof(struct inodev), inodev.st_ino);
102461d06d6bSBaptiste Daroussin 	mpage = ohash_find(&mpages, slot);
102561d06d6bSBaptiste Daroussin 	if (NULL == mpage) {
102661d06d6bSBaptiste Daroussin 		mpage = mandoc_calloc(1, sizeof(struct mpage));
102761d06d6bSBaptiste Daroussin 		mpage->inodev.st_ino = inodev.st_ino;
102861d06d6bSBaptiste Daroussin 		mpage->inodev.st_dev = inodev.st_dev;
102961d06d6bSBaptiste Daroussin 		mpage->form = FORM_NONE;
103061d06d6bSBaptiste Daroussin 		mpage->next = mpage_head;
103161d06d6bSBaptiste Daroussin 		mpage_head = mpage;
103261d06d6bSBaptiste Daroussin 		ohash_insert(&mpages, slot, mpage);
103361d06d6bSBaptiste Daroussin 	} else
103461d06d6bSBaptiste Daroussin 		mlink->next = mpage->mlinks;
103561d06d6bSBaptiste Daroussin 	mpage->mlinks = mlink;
103661d06d6bSBaptiste Daroussin 	mlink->mpage = mpage;
103761d06d6bSBaptiste Daroussin }
103861d06d6bSBaptiste Daroussin 
103961d06d6bSBaptiste Daroussin static void
mlink_free(struct mlink * mlink)104061d06d6bSBaptiste Daroussin mlink_free(struct mlink *mlink)
104161d06d6bSBaptiste Daroussin {
104261d06d6bSBaptiste Daroussin 
104361d06d6bSBaptiste Daroussin 	free(mlink->dsec);
104461d06d6bSBaptiste Daroussin 	free(mlink->arch);
104561d06d6bSBaptiste Daroussin 	free(mlink->name);
104661d06d6bSBaptiste Daroussin 	free(mlink->fsec);
104761d06d6bSBaptiste Daroussin 	free(mlink);
104861d06d6bSBaptiste Daroussin }
104961d06d6bSBaptiste Daroussin 
105061d06d6bSBaptiste Daroussin static void
mpages_free(void)105161d06d6bSBaptiste Daroussin mpages_free(void)
105261d06d6bSBaptiste Daroussin {
105361d06d6bSBaptiste Daroussin 	struct mpage	*mpage;
105461d06d6bSBaptiste Daroussin 	struct mlink	*mlink;
105561d06d6bSBaptiste Daroussin 
105661d06d6bSBaptiste Daroussin 	while ((mpage = mpage_head) != NULL) {
105761d06d6bSBaptiste Daroussin 		while ((mlink = mpage->mlinks) != NULL) {
105861d06d6bSBaptiste Daroussin 			mpage->mlinks = mlink->next;
105961d06d6bSBaptiste Daroussin 			mlink_free(mlink);
106061d06d6bSBaptiste Daroussin 		}
106161d06d6bSBaptiste Daroussin 		mpage_head = mpage->next;
106261d06d6bSBaptiste Daroussin 		free(mpage->sec);
106361d06d6bSBaptiste Daroussin 		free(mpage->arch);
106461d06d6bSBaptiste Daroussin 		free(mpage->title);
106561d06d6bSBaptiste Daroussin 		free(mpage->desc);
106661d06d6bSBaptiste Daroussin 		free(mpage);
106761d06d6bSBaptiste Daroussin 	}
106861d06d6bSBaptiste Daroussin }
106961d06d6bSBaptiste Daroussin 
107061d06d6bSBaptiste Daroussin /*
107161d06d6bSBaptiste Daroussin  * For each mlink to the mpage, check whether the path looks like
107261d06d6bSBaptiste Daroussin  * it is formatted, and if it does, check whether a source manual
107361d06d6bSBaptiste Daroussin  * exists by the same name, ignoring the suffix.
107461d06d6bSBaptiste Daroussin  * If both conditions hold, drop the mlink.
107561d06d6bSBaptiste Daroussin  */
107661d06d6bSBaptiste Daroussin static void
mlinks_undupe(struct mpage * mpage)107761d06d6bSBaptiste Daroussin mlinks_undupe(struct mpage *mpage)
107861d06d6bSBaptiste Daroussin {
107961d06d6bSBaptiste Daroussin 	char		  buf[PATH_MAX];
108061d06d6bSBaptiste Daroussin 	struct mlink	**prev;
108161d06d6bSBaptiste Daroussin 	struct mlink	 *mlink;
108261d06d6bSBaptiste Daroussin 	char		 *bufp;
108361d06d6bSBaptiste Daroussin 
108461d06d6bSBaptiste Daroussin 	mpage->form = FORM_CAT;
108561d06d6bSBaptiste Daroussin 	prev = &mpage->mlinks;
108661d06d6bSBaptiste Daroussin 	while (NULL != (mlink = *prev)) {
108761d06d6bSBaptiste Daroussin 		if (FORM_CAT != mlink->dform) {
108861d06d6bSBaptiste Daroussin 			mpage->form = FORM_NONE;
108961d06d6bSBaptiste Daroussin 			goto nextlink;
109061d06d6bSBaptiste Daroussin 		}
109161d06d6bSBaptiste Daroussin 		(void)strlcpy(buf, mlink->file, sizeof(buf));
109261d06d6bSBaptiste Daroussin 		bufp = strstr(buf, "cat");
109361d06d6bSBaptiste Daroussin 		assert(NULL != bufp);
109461d06d6bSBaptiste Daroussin 		memcpy(bufp, "man", 3);
109561d06d6bSBaptiste Daroussin 		if (NULL != (bufp = strrchr(buf, '.')))
109661d06d6bSBaptiste Daroussin 			*++bufp = '\0';
109761d06d6bSBaptiste Daroussin 		(void)strlcat(buf, mlink->dsec, sizeof(buf));
109861d06d6bSBaptiste Daroussin 		if (NULL == ohash_find(&mlinks,
109961d06d6bSBaptiste Daroussin 		    ohash_qlookup(&mlinks, buf)))
110061d06d6bSBaptiste Daroussin 			goto nextlink;
110161d06d6bSBaptiste Daroussin 		if (warnings)
110261d06d6bSBaptiste Daroussin 			say(mlink->file, "Man source exists: %s", buf);
110361d06d6bSBaptiste Daroussin 		if (use_all)
110461d06d6bSBaptiste Daroussin 			goto nextlink;
110561d06d6bSBaptiste Daroussin 		*prev = mlink->next;
110661d06d6bSBaptiste Daroussin 		mlink_free(mlink);
110761d06d6bSBaptiste Daroussin 		continue;
110861d06d6bSBaptiste Daroussin nextlink:
110961d06d6bSBaptiste Daroussin 		prev = &(*prev)->next;
111061d06d6bSBaptiste Daroussin 	}
111161d06d6bSBaptiste Daroussin }
111261d06d6bSBaptiste Daroussin 
111361d06d6bSBaptiste Daroussin static void
mlink_check(struct mpage * mpage,struct mlink * mlink)111461d06d6bSBaptiste Daroussin mlink_check(struct mpage *mpage, struct mlink *mlink)
111561d06d6bSBaptiste Daroussin {
111661d06d6bSBaptiste Daroussin 	struct str	*str;
111761d06d6bSBaptiste Daroussin 	unsigned int	 slot;
111861d06d6bSBaptiste Daroussin 
111961d06d6bSBaptiste Daroussin 	/*
112061d06d6bSBaptiste Daroussin 	 * Check whether the manual section given in a file
112161d06d6bSBaptiste Daroussin 	 * agrees with the directory where the file is located.
112261d06d6bSBaptiste Daroussin 	 * Some manuals have suffixes like (3p) on their
112361d06d6bSBaptiste Daroussin 	 * section number either inside the file or in the
112461d06d6bSBaptiste Daroussin 	 * directory name, some are linked into more than one
112561d06d6bSBaptiste Daroussin 	 * section, like encrypt(1) = makekey(8).
112661d06d6bSBaptiste Daroussin 	 */
112761d06d6bSBaptiste Daroussin 
112861d06d6bSBaptiste Daroussin 	if (FORM_SRC == mpage->form &&
112961d06d6bSBaptiste Daroussin 	    strcasecmp(mpage->sec, mlink->dsec))
113061d06d6bSBaptiste Daroussin 		say(mlink->file, "Section \"%s\" manual in %s directory",
113161d06d6bSBaptiste Daroussin 		    mpage->sec, mlink->dsec);
113261d06d6bSBaptiste Daroussin 
113361d06d6bSBaptiste Daroussin 	/*
113461d06d6bSBaptiste Daroussin 	 * Manual page directories exist for each kernel
113561d06d6bSBaptiste Daroussin 	 * architecture as returned by machine(1).
113661d06d6bSBaptiste Daroussin 	 * However, many manuals only depend on the
113761d06d6bSBaptiste Daroussin 	 * application architecture as returned by arch(1).
113861d06d6bSBaptiste Daroussin 	 * For example, some (2/ARM) manuals are shared
113961d06d6bSBaptiste Daroussin 	 * across the "armish" and "zaurus" kernel
114061d06d6bSBaptiste Daroussin 	 * architectures.
114161d06d6bSBaptiste Daroussin 	 * A few manuals are even shared across completely
114261d06d6bSBaptiste Daroussin 	 * different architectures, for example fdformat(1)
114361d06d6bSBaptiste Daroussin 	 * on amd64, i386, and sparc64.
114461d06d6bSBaptiste Daroussin 	 */
114561d06d6bSBaptiste Daroussin 
114661d06d6bSBaptiste Daroussin 	if (strcasecmp(mpage->arch, mlink->arch))
114761d06d6bSBaptiste Daroussin 		say(mlink->file, "Architecture \"%s\" manual in "
114861d06d6bSBaptiste Daroussin 		    "\"%s\" directory", mpage->arch, mlink->arch);
114961d06d6bSBaptiste Daroussin 
115061d06d6bSBaptiste Daroussin 	/*
115161d06d6bSBaptiste Daroussin 	 * XXX
115261d06d6bSBaptiste Daroussin 	 * parse_cat() doesn't set NAME_TITLE yet.
115361d06d6bSBaptiste Daroussin 	 */
115461d06d6bSBaptiste Daroussin 
115561d06d6bSBaptiste Daroussin 	if (FORM_CAT == mpage->form)
115661d06d6bSBaptiste Daroussin 		return;
115761d06d6bSBaptiste Daroussin 
115861d06d6bSBaptiste Daroussin 	/*
115961d06d6bSBaptiste Daroussin 	 * Check whether this mlink
116061d06d6bSBaptiste Daroussin 	 * appears as a name in the NAME section.
116161d06d6bSBaptiste Daroussin 	 */
116261d06d6bSBaptiste Daroussin 
116361d06d6bSBaptiste Daroussin 	slot = ohash_qlookup(&names, mlink->name);
116461d06d6bSBaptiste Daroussin 	str = ohash_find(&names, slot);
116561d06d6bSBaptiste Daroussin 	assert(NULL != str);
116661d06d6bSBaptiste Daroussin 	if ( ! (NAME_TITLE & str->mask))
116761d06d6bSBaptiste Daroussin 		say(mlink->file, "Name missing in NAME section");
116861d06d6bSBaptiste Daroussin }
116961d06d6bSBaptiste Daroussin 
117061d06d6bSBaptiste Daroussin /*
117161d06d6bSBaptiste Daroussin  * Run through the files in the global vector "mpages"
117261d06d6bSBaptiste Daroussin  * and add them to the database specified in "basedir".
117361d06d6bSBaptiste Daroussin  *
117461d06d6bSBaptiste Daroussin  * This handles the parsing scheme itself, using the cues of directory
117561d06d6bSBaptiste Daroussin  * and filename to determine whether the file is parsable or not.
117661d06d6bSBaptiste Daroussin  */
117761d06d6bSBaptiste Daroussin static void
mpages_merge(struct dba * dba,struct mparse * mp)117861d06d6bSBaptiste Daroussin mpages_merge(struct dba *dba, struct mparse *mp)
117961d06d6bSBaptiste Daroussin {
118061d06d6bSBaptiste Daroussin 	struct mpage		*mpage, *mpage_dest;
118161d06d6bSBaptiste Daroussin 	struct mlink		*mlink, *mlink_dest;
11827295610fSBaptiste Daroussin 	struct roff_meta	*meta;
118361d06d6bSBaptiste Daroussin 	char			*cp;
118461d06d6bSBaptiste Daroussin 	int			 fd;
118561d06d6bSBaptiste Daroussin 
118661d06d6bSBaptiste Daroussin 	for (mpage = mpage_head; mpage != NULL; mpage = mpage->next) {
118761d06d6bSBaptiste Daroussin 		mlinks_undupe(mpage);
118861d06d6bSBaptiste Daroussin 		if ((mlink = mpage->mlinks) == NULL)
118961d06d6bSBaptiste Daroussin 			continue;
119061d06d6bSBaptiste Daroussin 
119161d06d6bSBaptiste Daroussin 		name_mask = NAME_MASK;
119261d06d6bSBaptiste Daroussin 		mandoc_ohash_init(&names, 4, offsetof(struct str, key));
119361d06d6bSBaptiste Daroussin 		mandoc_ohash_init(&strings, 6, offsetof(struct str, key));
119461d06d6bSBaptiste Daroussin 		mparse_reset(mp);
11957295610fSBaptiste Daroussin 		meta = NULL;
119661d06d6bSBaptiste Daroussin 
119761d06d6bSBaptiste Daroussin 		if ((fd = mparse_open(mp, mlink->file)) == -1) {
119861d06d6bSBaptiste Daroussin 			say(mlink->file, "&open");
119961d06d6bSBaptiste Daroussin 			goto nextpage;
120061d06d6bSBaptiste Daroussin 		}
120161d06d6bSBaptiste Daroussin 
120261d06d6bSBaptiste Daroussin 		/*
120361d06d6bSBaptiste Daroussin 		 * Interpret the file as mdoc(7) or man(7) source
120461d06d6bSBaptiste Daroussin 		 * code, unless it is known to be formatted.
120561d06d6bSBaptiste Daroussin 		 */
120661d06d6bSBaptiste Daroussin 		if (mlink->dform != FORM_CAT || mlink->fform != FORM_CAT) {
120761d06d6bSBaptiste Daroussin 			mparse_readfd(mp, fd, mlink->file);
120861d06d6bSBaptiste Daroussin 			close(fd);
120961d06d6bSBaptiste Daroussin 			fd = -1;
12107295610fSBaptiste Daroussin 			meta = mparse_result(mp);
121161d06d6bSBaptiste Daroussin 		}
121261d06d6bSBaptiste Daroussin 
12137295610fSBaptiste Daroussin 		if (meta != NULL && meta->sodest != NULL) {
121461d06d6bSBaptiste Daroussin 			mlink_dest = ohash_find(&mlinks,
12157295610fSBaptiste Daroussin 			    ohash_qlookup(&mlinks, meta->sodest));
121661d06d6bSBaptiste Daroussin 			if (mlink_dest == NULL) {
12177295610fSBaptiste Daroussin 				mandoc_asprintf(&cp, "%s.gz", meta->sodest);
121861d06d6bSBaptiste Daroussin 				mlink_dest = ohash_find(&mlinks,
121961d06d6bSBaptiste Daroussin 				    ohash_qlookup(&mlinks, cp));
122061d06d6bSBaptiste Daroussin 				free(cp);
122161d06d6bSBaptiste Daroussin 			}
122261d06d6bSBaptiste Daroussin 			if (mlink_dest != NULL) {
122361d06d6bSBaptiste Daroussin 
122461d06d6bSBaptiste Daroussin 				/* The .so target exists. */
122561d06d6bSBaptiste Daroussin 
122661d06d6bSBaptiste Daroussin 				mpage_dest = mlink_dest->mpage;
122761d06d6bSBaptiste Daroussin 				while (1) {
122861d06d6bSBaptiste Daroussin 					mlink->mpage = mpage_dest;
122961d06d6bSBaptiste Daroussin 
123061d06d6bSBaptiste Daroussin 					/*
123161d06d6bSBaptiste Daroussin 					 * If the target was already
123261d06d6bSBaptiste Daroussin 					 * processed, add the links
123361d06d6bSBaptiste Daroussin 					 * to the database now.
123461d06d6bSBaptiste Daroussin 					 * Otherwise, this will
123561d06d6bSBaptiste Daroussin 					 * happen when we come
123661d06d6bSBaptiste Daroussin 					 * to the target.
123761d06d6bSBaptiste Daroussin 					 */
123861d06d6bSBaptiste Daroussin 
123961d06d6bSBaptiste Daroussin 					if (mpage_dest->dba != NULL)
124061d06d6bSBaptiste Daroussin 						dbadd_mlink(mlink);
124161d06d6bSBaptiste Daroussin 
124261d06d6bSBaptiste Daroussin 					if (mlink->next == NULL)
124361d06d6bSBaptiste Daroussin 						break;
124461d06d6bSBaptiste Daroussin 					mlink = mlink->next;
124561d06d6bSBaptiste Daroussin 				}
124661d06d6bSBaptiste Daroussin 
124761d06d6bSBaptiste Daroussin 				/* Move all links to the target. */
124861d06d6bSBaptiste Daroussin 
124961d06d6bSBaptiste Daroussin 				mlink->next = mlink_dest->next;
125061d06d6bSBaptiste Daroussin 				mlink_dest->next = mpage->mlinks;
125161d06d6bSBaptiste Daroussin 				mpage->mlinks = NULL;
125261d06d6bSBaptiste Daroussin 				goto nextpage;
125345a5aec3SBaptiste Daroussin 			}
125445a5aec3SBaptiste Daroussin 			meta->macroset = MACROSET_NONE;
125545a5aec3SBaptiste Daroussin 		}
125645a5aec3SBaptiste Daroussin 		if (meta != NULL && meta->macroset == MACROSET_MDOC) {
125761d06d6bSBaptiste Daroussin 			mpage->form = FORM_SRC;
12587295610fSBaptiste Daroussin 			mpage->sec = meta->msec;
125961d06d6bSBaptiste Daroussin 			mpage->sec = mandoc_strdup(
126061d06d6bSBaptiste Daroussin 			    mpage->sec == NULL ? "" : mpage->sec);
12617295610fSBaptiste Daroussin 			mpage->arch = meta->arch;
126261d06d6bSBaptiste Daroussin 			mpage->arch = mandoc_strdup(
126361d06d6bSBaptiste Daroussin 			    mpage->arch == NULL ? "" : mpage->arch);
12647295610fSBaptiste Daroussin 			mpage->title = mandoc_strdup(meta->title);
12657295610fSBaptiste Daroussin 		} else if (meta != NULL && meta->macroset == MACROSET_MAN) {
12667295610fSBaptiste Daroussin 			if (*meta->msec != '\0' || *meta->title != '\0') {
126761d06d6bSBaptiste Daroussin 				mpage->form = FORM_SRC;
12687295610fSBaptiste Daroussin 				mpage->sec = mandoc_strdup(meta->msec);
126961d06d6bSBaptiste Daroussin 				mpage->arch = mandoc_strdup(mlink->arch);
12707295610fSBaptiste Daroussin 				mpage->title = mandoc_strdup(meta->title);
127161d06d6bSBaptiste Daroussin 			} else
12727295610fSBaptiste Daroussin 				meta = NULL;
127361d06d6bSBaptiste Daroussin 		}
127461d06d6bSBaptiste Daroussin 
127561d06d6bSBaptiste Daroussin 		assert(mpage->desc == NULL);
127645a5aec3SBaptiste Daroussin 		if (meta == NULL || meta->sodest != NULL) {
127761d06d6bSBaptiste Daroussin 			mpage->sec = mandoc_strdup(mlink->dsec);
127861d06d6bSBaptiste Daroussin 			mpage->arch = mandoc_strdup(mlink->arch);
127961d06d6bSBaptiste Daroussin 			mpage->title = mandoc_strdup(mlink->name);
128045a5aec3SBaptiste Daroussin 			if (meta == NULL) {
128145a5aec3SBaptiste Daroussin 				mpage->form = FORM_CAT;
128261d06d6bSBaptiste Daroussin 				parse_cat(mpage, fd);
128345a5aec3SBaptiste Daroussin 			} else
128445a5aec3SBaptiste Daroussin 				mpage->form = FORM_SRC;
12857295610fSBaptiste Daroussin 		} else if (meta->macroset == MACROSET_MDOC)
12867295610fSBaptiste Daroussin 			parse_mdoc(mpage, meta, meta->first);
128761d06d6bSBaptiste Daroussin 		else
12887295610fSBaptiste Daroussin 			parse_man(mpage, meta, meta->first);
128961d06d6bSBaptiste Daroussin 		if (mpage->desc == NULL) {
129061d06d6bSBaptiste Daroussin 			mpage->desc = mandoc_strdup(mlink->name);
129161d06d6bSBaptiste Daroussin 			if (warnings)
129261d06d6bSBaptiste Daroussin 				say(mlink->file, "No one-line description, "
129361d06d6bSBaptiste Daroussin 				    "using filename \"%s\"", mlink->name);
129461d06d6bSBaptiste Daroussin 		}
129561d06d6bSBaptiste Daroussin 
129661d06d6bSBaptiste Daroussin 		for (mlink = mpage->mlinks;
129761d06d6bSBaptiste Daroussin 		     mlink != NULL;
129861d06d6bSBaptiste Daroussin 		     mlink = mlink->next) {
129961d06d6bSBaptiste Daroussin 			putkey(mpage, mlink->name, NAME_FILE);
130061d06d6bSBaptiste Daroussin 			if (warnings && !use_all)
130161d06d6bSBaptiste Daroussin 				mlink_check(mpage, mlink);
130261d06d6bSBaptiste Daroussin 		}
130361d06d6bSBaptiste Daroussin 
130461d06d6bSBaptiste Daroussin 		dbadd(dba, mpage);
130561d06d6bSBaptiste Daroussin 
130661d06d6bSBaptiste Daroussin nextpage:
130761d06d6bSBaptiste Daroussin 		ohash_delete(&strings);
130861d06d6bSBaptiste Daroussin 		ohash_delete(&names);
130961d06d6bSBaptiste Daroussin 	}
131061d06d6bSBaptiste Daroussin }
131161d06d6bSBaptiste Daroussin 
131261d06d6bSBaptiste Daroussin static void
parse_cat(struct mpage * mpage,int fd)131361d06d6bSBaptiste Daroussin parse_cat(struct mpage *mpage, int fd)
131461d06d6bSBaptiste Daroussin {
131561d06d6bSBaptiste Daroussin 	FILE		*stream;
131661d06d6bSBaptiste Daroussin 	struct mlink	*mlink;
131761d06d6bSBaptiste Daroussin 	char		*line, *p, *title, *sec;
131861d06d6bSBaptiste Daroussin 	size_t		 linesz, plen, titlesz;
131961d06d6bSBaptiste Daroussin 	ssize_t		 len;
132061d06d6bSBaptiste Daroussin 	int		 offs;
132161d06d6bSBaptiste Daroussin 
132261d06d6bSBaptiste Daroussin 	mlink = mpage->mlinks;
132361d06d6bSBaptiste Daroussin 	stream = fd == -1 ? fopen(mlink->file, "r") : fdopen(fd, "r");
132461d06d6bSBaptiste Daroussin 	if (stream == NULL) {
132561d06d6bSBaptiste Daroussin 		if (fd != -1)
132661d06d6bSBaptiste Daroussin 			close(fd);
132761d06d6bSBaptiste Daroussin 		if (warnings)
132861d06d6bSBaptiste Daroussin 			say(mlink->file, "&fopen");
132961d06d6bSBaptiste Daroussin 		return;
133061d06d6bSBaptiste Daroussin 	}
133161d06d6bSBaptiste Daroussin 
133261d06d6bSBaptiste Daroussin 	line = NULL;
133361d06d6bSBaptiste Daroussin 	linesz = 0;
133461d06d6bSBaptiste Daroussin 
133561d06d6bSBaptiste Daroussin 	/* Parse the section number from the header line. */
133661d06d6bSBaptiste Daroussin 
133761d06d6bSBaptiste Daroussin 	while (getline(&line, &linesz, stream) != -1) {
133861d06d6bSBaptiste Daroussin 		if (*line == '\n')
133961d06d6bSBaptiste Daroussin 			continue;
134061d06d6bSBaptiste Daroussin 		if ((sec = strchr(line, '(')) == NULL)
134161d06d6bSBaptiste Daroussin 			break;
134261d06d6bSBaptiste Daroussin 		if ((p = strchr(++sec, ')')) == NULL)
134361d06d6bSBaptiste Daroussin 			break;
134461d06d6bSBaptiste Daroussin 		free(mpage->sec);
134561d06d6bSBaptiste Daroussin 		mpage->sec = mandoc_strndup(sec, p - sec);
134661d06d6bSBaptiste Daroussin 		if (warnings && *mlink->dsec != '\0' &&
134761d06d6bSBaptiste Daroussin 		    strcasecmp(mpage->sec, mlink->dsec))
134861d06d6bSBaptiste Daroussin 			say(mlink->file,
134961d06d6bSBaptiste Daroussin 			    "Section \"%s\" manual in %s directory",
135061d06d6bSBaptiste Daroussin 			    mpage->sec, mlink->dsec);
135161d06d6bSBaptiste Daroussin 		break;
135261d06d6bSBaptiste Daroussin 	}
135361d06d6bSBaptiste Daroussin 
135461d06d6bSBaptiste Daroussin 	/* Skip to first blank line. */
135561d06d6bSBaptiste Daroussin 
135661d06d6bSBaptiste Daroussin 	while (line == NULL || *line != '\n')
135761d06d6bSBaptiste Daroussin 		if (getline(&line, &linesz, stream) == -1)
135861d06d6bSBaptiste Daroussin 			break;
135961d06d6bSBaptiste Daroussin 
136061d06d6bSBaptiste Daroussin 	/*
136161d06d6bSBaptiste Daroussin 	 * Assume the first line that is not indented
136261d06d6bSBaptiste Daroussin 	 * is the first section header.  Skip to it.
136361d06d6bSBaptiste Daroussin 	 */
136461d06d6bSBaptiste Daroussin 
136561d06d6bSBaptiste Daroussin 	while (getline(&line, &linesz, stream) != -1)
136661d06d6bSBaptiste Daroussin 		if (*line != '\n' && *line != ' ')
136761d06d6bSBaptiste Daroussin 			break;
136861d06d6bSBaptiste Daroussin 
136961d06d6bSBaptiste Daroussin 	/*
137061d06d6bSBaptiste Daroussin 	 * Read up until the next section into a buffer.
137161d06d6bSBaptiste Daroussin 	 * Strip the leading and trailing newline from each read line,
137261d06d6bSBaptiste Daroussin 	 * appending a trailing space.
137361d06d6bSBaptiste Daroussin 	 * Ignore empty (whitespace-only) lines.
137461d06d6bSBaptiste Daroussin 	 */
137561d06d6bSBaptiste Daroussin 
137661d06d6bSBaptiste Daroussin 	titlesz = 0;
137761d06d6bSBaptiste Daroussin 	title = NULL;
137861d06d6bSBaptiste Daroussin 
137961d06d6bSBaptiste Daroussin 	while ((len = getline(&line, &linesz, stream)) != -1) {
138061d06d6bSBaptiste Daroussin 		if (*line != ' ')
138161d06d6bSBaptiste Daroussin 			break;
138261d06d6bSBaptiste Daroussin 		offs = 0;
138361d06d6bSBaptiste Daroussin 		while (isspace((unsigned char)line[offs]))
138461d06d6bSBaptiste Daroussin 			offs++;
138561d06d6bSBaptiste Daroussin 		if (line[offs] == '\0')
138661d06d6bSBaptiste Daroussin 			continue;
138761d06d6bSBaptiste Daroussin 		title = mandoc_realloc(title, titlesz + len - offs);
138861d06d6bSBaptiste Daroussin 		memcpy(title + titlesz, line + offs, len - offs);
138961d06d6bSBaptiste Daroussin 		titlesz += len - offs;
139061d06d6bSBaptiste Daroussin 		title[titlesz - 1] = ' ';
139161d06d6bSBaptiste Daroussin 	}
139261d06d6bSBaptiste Daroussin 	free(line);
139361d06d6bSBaptiste Daroussin 
139461d06d6bSBaptiste Daroussin 	/*
139561d06d6bSBaptiste Daroussin 	 * If no page content can be found, or the input line
139661d06d6bSBaptiste Daroussin 	 * is already the next section header, or there is no
139761d06d6bSBaptiste Daroussin 	 * trailing newline, reuse the page title as the page
139861d06d6bSBaptiste Daroussin 	 * description.
139961d06d6bSBaptiste Daroussin 	 */
140061d06d6bSBaptiste Daroussin 
140161d06d6bSBaptiste Daroussin 	if (NULL == title || '\0' == *title) {
140261d06d6bSBaptiste Daroussin 		if (warnings)
140361d06d6bSBaptiste Daroussin 			say(mlink->file, "Cannot find NAME section");
140461d06d6bSBaptiste Daroussin 		fclose(stream);
140561d06d6bSBaptiste Daroussin 		free(title);
140661d06d6bSBaptiste Daroussin 		return;
140761d06d6bSBaptiste Daroussin 	}
140861d06d6bSBaptiste Daroussin 
140961d06d6bSBaptiste Daroussin 	title[titlesz - 1] = '\0';
141061d06d6bSBaptiste Daroussin 
141161d06d6bSBaptiste Daroussin 	/*
141261d06d6bSBaptiste Daroussin 	 * Skip to the first dash.
141361d06d6bSBaptiste Daroussin 	 * Use the remaining line as the description (no more than 70
141461d06d6bSBaptiste Daroussin 	 * bytes).
141561d06d6bSBaptiste Daroussin 	 */
141661d06d6bSBaptiste Daroussin 
141761d06d6bSBaptiste Daroussin 	if (NULL != (p = strstr(title, "- "))) {
141861d06d6bSBaptiste Daroussin 		for (p += 2; ' ' == *p || '\b' == *p; p++)
141961d06d6bSBaptiste Daroussin 			/* Skip to next word. */ ;
142061d06d6bSBaptiste Daroussin 	} else {
142161d06d6bSBaptiste Daroussin 		if (warnings)
142261d06d6bSBaptiste Daroussin 			say(mlink->file, "No dash in title line, "
142361d06d6bSBaptiste Daroussin 			    "reusing \"%s\" as one-line description", title);
142461d06d6bSBaptiste Daroussin 		p = title;
142561d06d6bSBaptiste Daroussin 	}
142661d06d6bSBaptiste Daroussin 
142761d06d6bSBaptiste Daroussin 	plen = strlen(p);
142861d06d6bSBaptiste Daroussin 
142961d06d6bSBaptiste Daroussin 	/* Strip backspace-encoding from line. */
143061d06d6bSBaptiste Daroussin 
143161d06d6bSBaptiste Daroussin 	while (NULL != (line = memchr(p, '\b', plen))) {
143261d06d6bSBaptiste Daroussin 		len = line - p;
143361d06d6bSBaptiste Daroussin 		if (0 == len) {
143461d06d6bSBaptiste Daroussin 			memmove(line, line + 1, plen--);
143561d06d6bSBaptiste Daroussin 			continue;
143661d06d6bSBaptiste Daroussin 		}
143761d06d6bSBaptiste Daroussin 		memmove(line - 1, line + 1, plen - len);
143861d06d6bSBaptiste Daroussin 		plen -= 2;
143961d06d6bSBaptiste Daroussin 	}
144061d06d6bSBaptiste Daroussin 
144161d06d6bSBaptiste Daroussin 	/*
144261d06d6bSBaptiste Daroussin 	 * Cut off excessive one-line descriptions.
144361d06d6bSBaptiste Daroussin 	 * Bad pages are not worth better heuristics.
144461d06d6bSBaptiste Daroussin 	 */
144561d06d6bSBaptiste Daroussin 
144661d06d6bSBaptiste Daroussin 	mpage->desc = mandoc_strndup(p, 150);
144761d06d6bSBaptiste Daroussin 	fclose(stream);
144861d06d6bSBaptiste Daroussin 	free(title);
144961d06d6bSBaptiste Daroussin }
145061d06d6bSBaptiste Daroussin 
145161d06d6bSBaptiste Daroussin /*
145261d06d6bSBaptiste Daroussin  * Put a type/word pair into the word database for this particular file.
145361d06d6bSBaptiste Daroussin  */
145461d06d6bSBaptiste Daroussin static void
putkey(const struct mpage * mpage,char * value,uint64_t type)145561d06d6bSBaptiste Daroussin putkey(const struct mpage *mpage, char *value, uint64_t type)
145661d06d6bSBaptiste Daroussin {
145761d06d6bSBaptiste Daroussin 	putkeys(mpage, value, strlen(value), type);
145861d06d6bSBaptiste Daroussin }
145961d06d6bSBaptiste Daroussin 
146061d06d6bSBaptiste Daroussin /*
146161d06d6bSBaptiste Daroussin  * Grok all nodes at or below a certain mdoc node into putkey().
146261d06d6bSBaptiste Daroussin  */
146361d06d6bSBaptiste Daroussin static void
putmdockey(const struct mpage * mpage,const struct roff_node * n,uint64_t m,int taboo)146461d06d6bSBaptiste Daroussin putmdockey(const struct mpage *mpage,
146561d06d6bSBaptiste Daroussin 	const struct roff_node *n, uint64_t m, int taboo)
146661d06d6bSBaptiste Daroussin {
146761d06d6bSBaptiste Daroussin 
146861d06d6bSBaptiste Daroussin 	for ( ; NULL != n; n = n->next) {
146961d06d6bSBaptiste Daroussin 		if (n->flags & taboo)
147061d06d6bSBaptiste Daroussin 			continue;
147161d06d6bSBaptiste Daroussin 		if (NULL != n->child)
147261d06d6bSBaptiste Daroussin 			putmdockey(mpage, n->child, m, taboo);
147361d06d6bSBaptiste Daroussin 		if (n->type == ROFFT_TEXT)
147461d06d6bSBaptiste Daroussin 			putkey(mpage, n->string, m);
147561d06d6bSBaptiste Daroussin 	}
147661d06d6bSBaptiste Daroussin }
147761d06d6bSBaptiste Daroussin 
147861d06d6bSBaptiste Daroussin static void
parse_man(struct mpage * mpage,const struct roff_meta * meta,const struct roff_node * n)147961d06d6bSBaptiste Daroussin parse_man(struct mpage *mpage, const struct roff_meta *meta,
148061d06d6bSBaptiste Daroussin 	const struct roff_node *n)
148161d06d6bSBaptiste Daroussin {
148261d06d6bSBaptiste Daroussin 	const struct roff_node *head, *body;
148361d06d6bSBaptiste Daroussin 	char		*start, *title;
148461d06d6bSBaptiste Daroussin 	char		 byte;
148561d06d6bSBaptiste Daroussin 	size_t		 sz;
148661d06d6bSBaptiste Daroussin 
148761d06d6bSBaptiste Daroussin 	if (n == NULL)
148861d06d6bSBaptiste Daroussin 		return;
148961d06d6bSBaptiste Daroussin 
149061d06d6bSBaptiste Daroussin 	/*
149161d06d6bSBaptiste Daroussin 	 * We're only searching for one thing: the first text child in
149261d06d6bSBaptiste Daroussin 	 * the BODY of a NAME section.  Since we don't keep track of
149361d06d6bSBaptiste Daroussin 	 * sections in -man, run some hoops to find out whether we're in
149461d06d6bSBaptiste Daroussin 	 * the correct section or not.
149561d06d6bSBaptiste Daroussin 	 */
149661d06d6bSBaptiste Daroussin 
149761d06d6bSBaptiste Daroussin 	if (n->type == ROFFT_BODY && n->tok == MAN_SH) {
149861d06d6bSBaptiste Daroussin 		body = n;
149961d06d6bSBaptiste Daroussin 		if ((head = body->parent->head) != NULL &&
150061d06d6bSBaptiste Daroussin 		    (head = head->child) != NULL &&
150161d06d6bSBaptiste Daroussin 		    head->next == NULL &&
150261d06d6bSBaptiste Daroussin 		    head->type == ROFFT_TEXT &&
150361d06d6bSBaptiste Daroussin 		    strcmp(head->string, "NAME") == 0 &&
150461d06d6bSBaptiste Daroussin 		    body->child != NULL) {
150561d06d6bSBaptiste Daroussin 
150661d06d6bSBaptiste Daroussin 			/*
150761d06d6bSBaptiste Daroussin 			 * Suck the entire NAME section into memory.
150861d06d6bSBaptiste Daroussin 			 * Yes, we might run away.
150961d06d6bSBaptiste Daroussin 			 * But too many manuals have big, spread-out
151061d06d6bSBaptiste Daroussin 			 * NAME sections over many lines.
151161d06d6bSBaptiste Daroussin 			 */
151261d06d6bSBaptiste Daroussin 
151361d06d6bSBaptiste Daroussin 			title = NULL;
151461d06d6bSBaptiste Daroussin 			deroff(&title, body);
151561d06d6bSBaptiste Daroussin 			if (NULL == title)
151661d06d6bSBaptiste Daroussin 				return;
151761d06d6bSBaptiste Daroussin 
151861d06d6bSBaptiste Daroussin 			/*
151961d06d6bSBaptiste Daroussin 			 * Go through a special heuristic dance here.
152061d06d6bSBaptiste Daroussin 			 * Conventionally, one or more manual names are
152161d06d6bSBaptiste Daroussin 			 * comma-specified prior to a whitespace, then a
152261d06d6bSBaptiste Daroussin 			 * dash, then a description.  Try to puzzle out
152361d06d6bSBaptiste Daroussin 			 * the name parts here.
152461d06d6bSBaptiste Daroussin 			 */
152561d06d6bSBaptiste Daroussin 
152661d06d6bSBaptiste Daroussin 			start = title;
152761d06d6bSBaptiste Daroussin 			for ( ;; ) {
152861d06d6bSBaptiste Daroussin 				sz = strcspn(start, " ,");
152961d06d6bSBaptiste Daroussin 				if ('\0' == start[sz])
153061d06d6bSBaptiste Daroussin 					break;
153161d06d6bSBaptiste Daroussin 
153261d06d6bSBaptiste Daroussin 				byte = start[sz];
153361d06d6bSBaptiste Daroussin 				start[sz] = '\0';
153461d06d6bSBaptiste Daroussin 
153561d06d6bSBaptiste Daroussin 				/*
153661d06d6bSBaptiste Daroussin 				 * Assume a stray trailing comma in the
153761d06d6bSBaptiste Daroussin 				 * name list if a name begins with a dash.
153861d06d6bSBaptiste Daroussin 				 */
153961d06d6bSBaptiste Daroussin 
154061d06d6bSBaptiste Daroussin 				if ('-' == start[0] ||
154161d06d6bSBaptiste Daroussin 				    ('\\' == start[0] && '-' == start[1]))
154261d06d6bSBaptiste Daroussin 					break;
154361d06d6bSBaptiste Daroussin 
154461d06d6bSBaptiste Daroussin 				putkey(mpage, start, NAME_TITLE);
154561d06d6bSBaptiste Daroussin 				if ( ! (mpage->name_head_done ||
154661d06d6bSBaptiste Daroussin 				    strcasecmp(start, meta->title))) {
154761d06d6bSBaptiste Daroussin 					putkey(mpage, start, NAME_HEAD);
154861d06d6bSBaptiste Daroussin 					mpage->name_head_done = 1;
154961d06d6bSBaptiste Daroussin 				}
155061d06d6bSBaptiste Daroussin 
155161d06d6bSBaptiste Daroussin 				if (' ' == byte) {
155261d06d6bSBaptiste Daroussin 					start += sz + 1;
155361d06d6bSBaptiste Daroussin 					break;
155461d06d6bSBaptiste Daroussin 				}
155561d06d6bSBaptiste Daroussin 
155661d06d6bSBaptiste Daroussin 				assert(',' == byte);
155761d06d6bSBaptiste Daroussin 				start += sz + 1;
155861d06d6bSBaptiste Daroussin 				while (' ' == *start)
155961d06d6bSBaptiste Daroussin 					start++;
156061d06d6bSBaptiste Daroussin 			}
156161d06d6bSBaptiste Daroussin 
156261d06d6bSBaptiste Daroussin 			if (start == title) {
156361d06d6bSBaptiste Daroussin 				putkey(mpage, start, NAME_TITLE);
156461d06d6bSBaptiste Daroussin 				if ( ! (mpage->name_head_done ||
156561d06d6bSBaptiste Daroussin 				    strcasecmp(start, meta->title))) {
156661d06d6bSBaptiste Daroussin 					putkey(mpage, start, NAME_HEAD);
156761d06d6bSBaptiste Daroussin 					mpage->name_head_done = 1;
156861d06d6bSBaptiste Daroussin 				}
156961d06d6bSBaptiste Daroussin 				free(title);
157061d06d6bSBaptiste Daroussin 				return;
157161d06d6bSBaptiste Daroussin 			}
157261d06d6bSBaptiste Daroussin 
157361d06d6bSBaptiste Daroussin 			while (isspace((unsigned char)*start))
157461d06d6bSBaptiste Daroussin 				start++;
157561d06d6bSBaptiste Daroussin 
157661d06d6bSBaptiste Daroussin 			if (0 == strncmp(start, "-", 1))
157761d06d6bSBaptiste Daroussin 				start += 1;
157861d06d6bSBaptiste Daroussin 			else if (0 == strncmp(start, "\\-\\-", 4))
157961d06d6bSBaptiste Daroussin 				start += 4;
158061d06d6bSBaptiste Daroussin 			else if (0 == strncmp(start, "\\-", 2))
158161d06d6bSBaptiste Daroussin 				start += 2;
158261d06d6bSBaptiste Daroussin 			else if (0 == strncmp(start, "\\(en", 4))
158361d06d6bSBaptiste Daroussin 				start += 4;
158461d06d6bSBaptiste Daroussin 			else if (0 == strncmp(start, "\\(em", 4))
158561d06d6bSBaptiste Daroussin 				start += 4;
158661d06d6bSBaptiste Daroussin 
158761d06d6bSBaptiste Daroussin 			while (' ' == *start)
158861d06d6bSBaptiste Daroussin 				start++;
158961d06d6bSBaptiste Daroussin 
159061d06d6bSBaptiste Daroussin 			/*
159161d06d6bSBaptiste Daroussin 			 * Cut off excessive one-line descriptions.
159261d06d6bSBaptiste Daroussin 			 * Bad pages are not worth better heuristics.
159361d06d6bSBaptiste Daroussin 			 */
159461d06d6bSBaptiste Daroussin 
159561d06d6bSBaptiste Daroussin 			mpage->desc = mandoc_strndup(start, 150);
159661d06d6bSBaptiste Daroussin 			free(title);
159761d06d6bSBaptiste Daroussin 			return;
159861d06d6bSBaptiste Daroussin 		}
159961d06d6bSBaptiste Daroussin 	}
160061d06d6bSBaptiste Daroussin 
160161d06d6bSBaptiste Daroussin 	for (n = n->child; n; n = n->next) {
160261d06d6bSBaptiste Daroussin 		if (NULL != mpage->desc)
160361d06d6bSBaptiste Daroussin 			break;
160461d06d6bSBaptiste Daroussin 		parse_man(mpage, meta, n);
160561d06d6bSBaptiste Daroussin 	}
160661d06d6bSBaptiste Daroussin }
160761d06d6bSBaptiste Daroussin 
160861d06d6bSBaptiste Daroussin static void
parse_mdoc(struct mpage * mpage,const struct roff_meta * meta,const struct roff_node * n)160961d06d6bSBaptiste Daroussin parse_mdoc(struct mpage *mpage, const struct roff_meta *meta,
161061d06d6bSBaptiste Daroussin 	const struct roff_node *n)
161161d06d6bSBaptiste Daroussin {
16127295610fSBaptiste Daroussin 	const struct mdoc_handler *handler;
161361d06d6bSBaptiste Daroussin 
161461d06d6bSBaptiste Daroussin 	for (n = n->child; n != NULL; n = n->next) {
16157295610fSBaptiste Daroussin 		if (n->tok == TOKEN_NONE || n->tok < ROFF_MAX)
161661d06d6bSBaptiste Daroussin 			continue;
161761d06d6bSBaptiste Daroussin 		assert(n->tok >= MDOC_Dd && n->tok < MDOC_MAX);
16187295610fSBaptiste Daroussin 		handler = mdoc_handlers + (n->tok - MDOC_Dd);
16197295610fSBaptiste Daroussin 		if (n->flags & handler->taboo)
16207295610fSBaptiste Daroussin 			continue;
16217295610fSBaptiste Daroussin 
162261d06d6bSBaptiste Daroussin 		switch (n->type) {
162361d06d6bSBaptiste Daroussin 		case ROFFT_ELEM:
162461d06d6bSBaptiste Daroussin 		case ROFFT_BLOCK:
162561d06d6bSBaptiste Daroussin 		case ROFFT_HEAD:
162661d06d6bSBaptiste Daroussin 		case ROFFT_BODY:
162761d06d6bSBaptiste Daroussin 		case ROFFT_TAIL:
16287295610fSBaptiste Daroussin 			if (handler->fp != NULL &&
16297295610fSBaptiste Daroussin 			    (*handler->fp)(mpage, meta, n) == 0)
163061d06d6bSBaptiste Daroussin 				break;
16317295610fSBaptiste Daroussin 			if (handler->mask)
163261d06d6bSBaptiste Daroussin 				putmdockey(mpage, n->child,
16337295610fSBaptiste Daroussin 				    handler->mask, handler->taboo);
163461d06d6bSBaptiste Daroussin 			break;
163561d06d6bSBaptiste Daroussin 		default:
163661d06d6bSBaptiste Daroussin 			continue;
163761d06d6bSBaptiste Daroussin 		}
163861d06d6bSBaptiste Daroussin 		if (NULL != n->child)
163961d06d6bSBaptiste Daroussin 			parse_mdoc(mpage, meta, n);
164061d06d6bSBaptiste Daroussin 	}
164161d06d6bSBaptiste Daroussin }
164261d06d6bSBaptiste Daroussin 
164361d06d6bSBaptiste Daroussin static int
parse_mdoc_Fa(struct mpage * mpage,const struct roff_meta * meta,const struct roff_node * n)164461d06d6bSBaptiste Daroussin parse_mdoc_Fa(struct mpage *mpage, const struct roff_meta *meta,
164561d06d6bSBaptiste Daroussin 	const struct roff_node *n)
164661d06d6bSBaptiste Daroussin {
164761d06d6bSBaptiste Daroussin 	uint64_t mask;
164861d06d6bSBaptiste Daroussin 
164961d06d6bSBaptiste Daroussin 	mask = TYPE_Fa;
165061d06d6bSBaptiste Daroussin 	if (n->sec == SEC_SYNOPSIS)
165161d06d6bSBaptiste Daroussin 		mask |= TYPE_Vt;
165261d06d6bSBaptiste Daroussin 
165361d06d6bSBaptiste Daroussin 	putmdockey(mpage, n->child, mask, 0);
165461d06d6bSBaptiste Daroussin 	return 0;
165561d06d6bSBaptiste Daroussin }
165661d06d6bSBaptiste Daroussin 
165761d06d6bSBaptiste Daroussin static int
parse_mdoc_Fd(struct mpage * mpage,const struct roff_meta * meta,const struct roff_node * n)165861d06d6bSBaptiste Daroussin parse_mdoc_Fd(struct mpage *mpage, const struct roff_meta *meta,
165961d06d6bSBaptiste Daroussin 	const struct roff_node *n)
166061d06d6bSBaptiste Daroussin {
166161d06d6bSBaptiste Daroussin 	char		*start, *end;
166261d06d6bSBaptiste Daroussin 	size_t		 sz;
166361d06d6bSBaptiste Daroussin 
166461d06d6bSBaptiste Daroussin 	if (SEC_SYNOPSIS != n->sec ||
166561d06d6bSBaptiste Daroussin 	    NULL == (n = n->child) ||
166661d06d6bSBaptiste Daroussin 	    n->type != ROFFT_TEXT)
166761d06d6bSBaptiste Daroussin 		return 0;
166861d06d6bSBaptiste Daroussin 
166961d06d6bSBaptiste Daroussin 	/*
167061d06d6bSBaptiste Daroussin 	 * Only consider those `Fd' macro fields that begin with an
167161d06d6bSBaptiste Daroussin 	 * "inclusion" token (versus, e.g., #define).
167261d06d6bSBaptiste Daroussin 	 */
167361d06d6bSBaptiste Daroussin 
167461d06d6bSBaptiste Daroussin 	if (strcmp("#include", n->string))
167561d06d6bSBaptiste Daroussin 		return 0;
167661d06d6bSBaptiste Daroussin 
167761d06d6bSBaptiste Daroussin 	if ((n = n->next) == NULL || n->type != ROFFT_TEXT)
167861d06d6bSBaptiste Daroussin 		return 0;
167961d06d6bSBaptiste Daroussin 
168061d06d6bSBaptiste Daroussin 	/*
168161d06d6bSBaptiste Daroussin 	 * Strip away the enclosing angle brackets and make sure we're
168261d06d6bSBaptiste Daroussin 	 * not zero-length.
168361d06d6bSBaptiste Daroussin 	 */
168461d06d6bSBaptiste Daroussin 
168561d06d6bSBaptiste Daroussin 	start = n->string;
168661d06d6bSBaptiste Daroussin 	if ('<' == *start || '"' == *start)
168761d06d6bSBaptiste Daroussin 		start++;
168861d06d6bSBaptiste Daroussin 
168961d06d6bSBaptiste Daroussin 	if (0 == (sz = strlen(start)))
169061d06d6bSBaptiste Daroussin 		return 0;
169161d06d6bSBaptiste Daroussin 
169261d06d6bSBaptiste Daroussin 	end = &start[(int)sz - 1];
169361d06d6bSBaptiste Daroussin 	if ('>' == *end || '"' == *end)
169461d06d6bSBaptiste Daroussin 		end--;
169561d06d6bSBaptiste Daroussin 
169661d06d6bSBaptiste Daroussin 	if (end > start)
169761d06d6bSBaptiste Daroussin 		putkeys(mpage, start, end - start + 1, TYPE_In);
169861d06d6bSBaptiste Daroussin 	return 0;
169961d06d6bSBaptiste Daroussin }
170061d06d6bSBaptiste Daroussin 
170161d06d6bSBaptiste Daroussin static void
parse_mdoc_fname(struct mpage * mpage,const struct roff_node * n)170261d06d6bSBaptiste Daroussin parse_mdoc_fname(struct mpage *mpage, const struct roff_node *n)
170361d06d6bSBaptiste Daroussin {
170461d06d6bSBaptiste Daroussin 	char	*cp;
170561d06d6bSBaptiste Daroussin 	size_t	 sz;
170661d06d6bSBaptiste Daroussin 
170761d06d6bSBaptiste Daroussin 	if (n->type != ROFFT_TEXT)
170861d06d6bSBaptiste Daroussin 		return;
170961d06d6bSBaptiste Daroussin 
171061d06d6bSBaptiste Daroussin 	/* Skip function pointer punctuation. */
171161d06d6bSBaptiste Daroussin 
171261d06d6bSBaptiste Daroussin 	cp = n->string;
171361d06d6bSBaptiste Daroussin 	while (*cp == '(' || *cp == '*')
171461d06d6bSBaptiste Daroussin 		cp++;
171561d06d6bSBaptiste Daroussin 	sz = strcspn(cp, "()");
171661d06d6bSBaptiste Daroussin 
171761d06d6bSBaptiste Daroussin 	putkeys(mpage, cp, sz, TYPE_Fn);
171861d06d6bSBaptiste Daroussin 	if (n->sec == SEC_SYNOPSIS)
171961d06d6bSBaptiste Daroussin 		putkeys(mpage, cp, sz, NAME_SYN);
172061d06d6bSBaptiste Daroussin }
172161d06d6bSBaptiste Daroussin 
172261d06d6bSBaptiste Daroussin static int
parse_mdoc_Fn(struct mpage * mpage,const struct roff_meta * meta,const struct roff_node * n)172361d06d6bSBaptiste Daroussin parse_mdoc_Fn(struct mpage *mpage, const struct roff_meta *meta,
172461d06d6bSBaptiste Daroussin 	const struct roff_node *n)
172561d06d6bSBaptiste Daroussin {
172661d06d6bSBaptiste Daroussin 	uint64_t mask;
172761d06d6bSBaptiste Daroussin 
172861d06d6bSBaptiste Daroussin 	if (n->child == NULL)
172961d06d6bSBaptiste Daroussin 		return 0;
173061d06d6bSBaptiste Daroussin 
173161d06d6bSBaptiste Daroussin 	parse_mdoc_fname(mpage, n->child);
173261d06d6bSBaptiste Daroussin 
173361d06d6bSBaptiste Daroussin 	n = n->child->next;
173461d06d6bSBaptiste Daroussin 	if (n != NULL && n->type == ROFFT_TEXT) {
173561d06d6bSBaptiste Daroussin 		mask = TYPE_Fa;
173661d06d6bSBaptiste Daroussin 		if (n->sec == SEC_SYNOPSIS)
173761d06d6bSBaptiste Daroussin 			mask |= TYPE_Vt;
173861d06d6bSBaptiste Daroussin 		putmdockey(mpage, n, mask, 0);
173961d06d6bSBaptiste Daroussin 	}
174061d06d6bSBaptiste Daroussin 
174161d06d6bSBaptiste Daroussin 	return 0;
174261d06d6bSBaptiste Daroussin }
174361d06d6bSBaptiste Daroussin 
174461d06d6bSBaptiste Daroussin static int
parse_mdoc_Fo(struct mpage * mpage,const struct roff_meta * meta,const struct roff_node * n)174561d06d6bSBaptiste Daroussin parse_mdoc_Fo(struct mpage *mpage, const struct roff_meta *meta,
174661d06d6bSBaptiste Daroussin 	const struct roff_node *n)
174761d06d6bSBaptiste Daroussin {
174861d06d6bSBaptiste Daroussin 
174961d06d6bSBaptiste Daroussin 	if (n->type != ROFFT_HEAD)
175061d06d6bSBaptiste Daroussin 		return 1;
175161d06d6bSBaptiste Daroussin 
175261d06d6bSBaptiste Daroussin 	if (n->child != NULL)
175361d06d6bSBaptiste Daroussin 		parse_mdoc_fname(mpage, n->child);
175461d06d6bSBaptiste Daroussin 
175561d06d6bSBaptiste Daroussin 	return 0;
175661d06d6bSBaptiste Daroussin }
175761d06d6bSBaptiste Daroussin 
175861d06d6bSBaptiste Daroussin static int
parse_mdoc_Lb(struct mpage * mpage,const struct roff_meta * meta,const struct roff_node * n)1759*4c07abdbSAlexander Ziaee parse_mdoc_Lb(struct mpage *mpage, const struct roff_meta *meta,
1760*4c07abdbSAlexander Ziaee 	const struct roff_node *n)
1761*4c07abdbSAlexander Ziaee {
1762*4c07abdbSAlexander Ziaee 	char *cp;
1763*4c07abdbSAlexander Ziaee 
1764*4c07abdbSAlexander Ziaee 	for (n = n->child; n != NULL; n = n->next) {
1765*4c07abdbSAlexander Ziaee 		if (n->flags & NODE_NOSRC)
1766*4c07abdbSAlexander Ziaee 			continue;
1767*4c07abdbSAlexander Ziaee 		cp = n->string;
1768*4c07abdbSAlexander Ziaee 		if (n->sec == SEC_SYNOPSIS)
1769*4c07abdbSAlexander Ziaee 			mandoc_asprintf(&cp, "lib%s", cp);
1770*4c07abdbSAlexander Ziaee 		putkey(mpage, cp, TYPE_Lb);
1771*4c07abdbSAlexander Ziaee 		if (n->sec == SEC_SYNOPSIS)
1772*4c07abdbSAlexander Ziaee 			free(cp);
1773*4c07abdbSAlexander Ziaee 	}
1774*4c07abdbSAlexander Ziaee 	return 0;
1775*4c07abdbSAlexander Ziaee }
1776*4c07abdbSAlexander Ziaee 
1777*4c07abdbSAlexander Ziaee static int
parse_mdoc_Va(struct mpage * mpage,const struct roff_meta * meta,const struct roff_node * n)177861d06d6bSBaptiste Daroussin parse_mdoc_Va(struct mpage *mpage, const struct roff_meta *meta,
177961d06d6bSBaptiste Daroussin 	const struct roff_node *n)
178061d06d6bSBaptiste Daroussin {
178161d06d6bSBaptiste Daroussin 	char *cp;
178261d06d6bSBaptiste Daroussin 
178361d06d6bSBaptiste Daroussin 	if (n->type != ROFFT_ELEM && n->type != ROFFT_BODY)
178461d06d6bSBaptiste Daroussin 		return 0;
178561d06d6bSBaptiste Daroussin 
178661d06d6bSBaptiste Daroussin 	if (n->child != NULL &&
178761d06d6bSBaptiste Daroussin 	    n->child->next == NULL &&
178861d06d6bSBaptiste Daroussin 	    n->child->type == ROFFT_TEXT)
178961d06d6bSBaptiste Daroussin 		return 1;
179061d06d6bSBaptiste Daroussin 
179161d06d6bSBaptiste Daroussin 	cp = NULL;
179261d06d6bSBaptiste Daroussin 	deroff(&cp, n);
179361d06d6bSBaptiste Daroussin 	if (cp != NULL) {
179461d06d6bSBaptiste Daroussin 		putkey(mpage, cp, TYPE_Vt | (n->tok == MDOC_Va ||
179561d06d6bSBaptiste Daroussin 		    n->type == ROFFT_BODY ? TYPE_Va : 0));
179661d06d6bSBaptiste Daroussin 		free(cp);
179761d06d6bSBaptiste Daroussin 	}
179861d06d6bSBaptiste Daroussin 
179961d06d6bSBaptiste Daroussin 	return 0;
180061d06d6bSBaptiste Daroussin }
180161d06d6bSBaptiste Daroussin 
180261d06d6bSBaptiste Daroussin static int
parse_mdoc_Xr(struct mpage * mpage,const struct roff_meta * meta,const struct roff_node * n)180361d06d6bSBaptiste Daroussin parse_mdoc_Xr(struct mpage *mpage, const struct roff_meta *meta,
180461d06d6bSBaptiste Daroussin 	const struct roff_node *n)
180561d06d6bSBaptiste Daroussin {
180661d06d6bSBaptiste Daroussin 	char	*cp;
180761d06d6bSBaptiste Daroussin 
180861d06d6bSBaptiste Daroussin 	if (NULL == (n = n->child))
180961d06d6bSBaptiste Daroussin 		return 0;
181061d06d6bSBaptiste Daroussin 
181161d06d6bSBaptiste Daroussin 	if (NULL == n->next) {
181261d06d6bSBaptiste Daroussin 		putkey(mpage, n->string, TYPE_Xr);
181361d06d6bSBaptiste Daroussin 		return 0;
181461d06d6bSBaptiste Daroussin 	}
181561d06d6bSBaptiste Daroussin 
181661d06d6bSBaptiste Daroussin 	mandoc_asprintf(&cp, "%s(%s)", n->string, n->next->string);
181761d06d6bSBaptiste Daroussin 	putkey(mpage, cp, TYPE_Xr);
181861d06d6bSBaptiste Daroussin 	free(cp);
181961d06d6bSBaptiste Daroussin 	return 0;
182061d06d6bSBaptiste Daroussin }
182161d06d6bSBaptiste Daroussin 
182261d06d6bSBaptiste Daroussin static int
parse_mdoc_Nd(struct mpage * mpage,const struct roff_meta * meta,const struct roff_node * n)182361d06d6bSBaptiste Daroussin parse_mdoc_Nd(struct mpage *mpage, const struct roff_meta *meta,
182461d06d6bSBaptiste Daroussin 	const struct roff_node *n)
182561d06d6bSBaptiste Daroussin {
182661d06d6bSBaptiste Daroussin 
182761d06d6bSBaptiste Daroussin 	if (n->type == ROFFT_BODY)
182861d06d6bSBaptiste Daroussin 		deroff(&mpage->desc, n);
182961d06d6bSBaptiste Daroussin 	return 0;
183061d06d6bSBaptiste Daroussin }
183161d06d6bSBaptiste Daroussin 
183261d06d6bSBaptiste Daroussin static int
parse_mdoc_Nm(struct mpage * mpage,const struct roff_meta * meta,const struct roff_node * n)183361d06d6bSBaptiste Daroussin parse_mdoc_Nm(struct mpage *mpage, const struct roff_meta *meta,
183461d06d6bSBaptiste Daroussin 	const struct roff_node *n)
183561d06d6bSBaptiste Daroussin {
183661d06d6bSBaptiste Daroussin 
183761d06d6bSBaptiste Daroussin 	if (SEC_NAME == n->sec)
183861d06d6bSBaptiste Daroussin 		putmdockey(mpage, n->child, NAME_TITLE, 0);
183961d06d6bSBaptiste Daroussin 	else if (n->sec == SEC_SYNOPSIS && n->type == ROFFT_HEAD) {
184061d06d6bSBaptiste Daroussin 		if (n->child == NULL)
184161d06d6bSBaptiste Daroussin 			putkey(mpage, meta->name, NAME_SYN);
184261d06d6bSBaptiste Daroussin 		else
184361d06d6bSBaptiste Daroussin 			putmdockey(mpage, n->child, NAME_SYN, 0);
184461d06d6bSBaptiste Daroussin 	}
184561d06d6bSBaptiste Daroussin 	if ( ! (mpage->name_head_done ||
184661d06d6bSBaptiste Daroussin 	    n->child == NULL || n->child->string == NULL ||
184761d06d6bSBaptiste Daroussin 	    strcasecmp(n->child->string, meta->title))) {
184861d06d6bSBaptiste Daroussin 		putkey(mpage, n->child->string, NAME_HEAD);
184961d06d6bSBaptiste Daroussin 		mpage->name_head_done = 1;
185061d06d6bSBaptiste Daroussin 	}
185161d06d6bSBaptiste Daroussin 	return 0;
185261d06d6bSBaptiste Daroussin }
185361d06d6bSBaptiste Daroussin 
185461d06d6bSBaptiste Daroussin static int
parse_mdoc_Sh(struct mpage * mpage,const struct roff_meta * meta,const struct roff_node * n)185561d06d6bSBaptiste Daroussin parse_mdoc_Sh(struct mpage *mpage, const struct roff_meta *meta,
185661d06d6bSBaptiste Daroussin 	const struct roff_node *n)
185761d06d6bSBaptiste Daroussin {
185861d06d6bSBaptiste Daroussin 
185961d06d6bSBaptiste Daroussin 	return n->sec == SEC_CUSTOM && n->type == ROFFT_HEAD;
186061d06d6bSBaptiste Daroussin }
186161d06d6bSBaptiste Daroussin 
186261d06d6bSBaptiste Daroussin static int
parse_mdoc_head(struct mpage * mpage,const struct roff_meta * meta,const struct roff_node * n)186361d06d6bSBaptiste Daroussin parse_mdoc_head(struct mpage *mpage, const struct roff_meta *meta,
186461d06d6bSBaptiste Daroussin 	const struct roff_node *n)
186561d06d6bSBaptiste Daroussin {
186661d06d6bSBaptiste Daroussin 
186761d06d6bSBaptiste Daroussin 	return n->type == ROFFT_HEAD;
186861d06d6bSBaptiste Daroussin }
186961d06d6bSBaptiste Daroussin 
187061d06d6bSBaptiste Daroussin /*
187161d06d6bSBaptiste Daroussin  * Add a string to the hash table for the current manual.
187261d06d6bSBaptiste Daroussin  * Each string has a bitmask telling which macros it belongs to.
187361d06d6bSBaptiste Daroussin  * When we finish the manual, we'll dump the table.
187461d06d6bSBaptiste Daroussin  */
187561d06d6bSBaptiste Daroussin static void
putkeys(const struct mpage * mpage,char * cp,size_t sz,uint64_t v)187661d06d6bSBaptiste Daroussin putkeys(const struct mpage *mpage, char *cp, size_t sz, uint64_t v)
187761d06d6bSBaptiste Daroussin {
187861d06d6bSBaptiste Daroussin 	struct ohash	*htab;
187961d06d6bSBaptiste Daroussin 	struct str	*s;
188061d06d6bSBaptiste Daroussin 	const char	*end;
188161d06d6bSBaptiste Daroussin 	unsigned int	 slot;
188261d06d6bSBaptiste Daroussin 	int		 i, mustfree;
188361d06d6bSBaptiste Daroussin 
188461d06d6bSBaptiste Daroussin 	if (0 == sz)
188561d06d6bSBaptiste Daroussin 		return;
188661d06d6bSBaptiste Daroussin 
188761d06d6bSBaptiste Daroussin 	mustfree = render_string(&cp, &sz);
188861d06d6bSBaptiste Daroussin 
188961d06d6bSBaptiste Daroussin 	if (TYPE_Nm & v) {
189061d06d6bSBaptiste Daroussin 		htab = &names;
189161d06d6bSBaptiste Daroussin 		v &= name_mask;
189261d06d6bSBaptiste Daroussin 		if (v & NAME_FIRST)
189361d06d6bSBaptiste Daroussin 			name_mask &= ~NAME_FIRST;
189461d06d6bSBaptiste Daroussin 		if (debug > 1)
189561d06d6bSBaptiste Daroussin 			say(mpage->mlinks->file,
189661d06d6bSBaptiste Daroussin 			    "Adding name %*s, bits=0x%llx", (int)sz, cp,
189761d06d6bSBaptiste Daroussin 			    (unsigned long long)v);
189861d06d6bSBaptiste Daroussin 	} else {
189961d06d6bSBaptiste Daroussin 		htab = &strings;
190061d06d6bSBaptiste Daroussin 		if (debug > 1)
190161d06d6bSBaptiste Daroussin 		    for (i = 0; i < KEY_MAX; i++)
190261d06d6bSBaptiste Daroussin 			if ((uint64_t)1 << i & v)
190361d06d6bSBaptiste Daroussin 			    say(mpage->mlinks->file,
190461d06d6bSBaptiste Daroussin 				"Adding key %s=%*s",
190561d06d6bSBaptiste Daroussin 				mansearch_keynames[i], (int)sz, cp);
190661d06d6bSBaptiste Daroussin 	}
190761d06d6bSBaptiste Daroussin 
190861d06d6bSBaptiste Daroussin 	end = cp + sz;
190961d06d6bSBaptiste Daroussin 	slot = ohash_qlookupi(htab, cp, &end);
191061d06d6bSBaptiste Daroussin 	s = ohash_find(htab, slot);
191161d06d6bSBaptiste Daroussin 
191261d06d6bSBaptiste Daroussin 	if (NULL != s && mpage == s->mpage) {
191361d06d6bSBaptiste Daroussin 		s->mask |= v;
191461d06d6bSBaptiste Daroussin 		return;
191561d06d6bSBaptiste Daroussin 	} else if (NULL == s) {
191661d06d6bSBaptiste Daroussin 		s = mandoc_calloc(1, sizeof(struct str) + sz + 1);
191761d06d6bSBaptiste Daroussin 		memcpy(s->key, cp, sz);
191861d06d6bSBaptiste Daroussin 		ohash_insert(htab, slot, s);
191961d06d6bSBaptiste Daroussin 	}
192061d06d6bSBaptiste Daroussin 	s->mpage = mpage;
192161d06d6bSBaptiste Daroussin 	s->mask = v;
192261d06d6bSBaptiste Daroussin 
192361d06d6bSBaptiste Daroussin 	if (mustfree)
192461d06d6bSBaptiste Daroussin 		free(cp);
192561d06d6bSBaptiste Daroussin }
192661d06d6bSBaptiste Daroussin 
192761d06d6bSBaptiste Daroussin /*
192861d06d6bSBaptiste Daroussin  * Take a Unicode codepoint and produce its UTF-8 encoding.
192961d06d6bSBaptiste Daroussin  * This isn't the best way to do this, but it works.
193061d06d6bSBaptiste Daroussin  * The magic numbers are from the UTF-8 packaging.
1931c1c95addSBrooks Davis  * Read the UTF-8 spec or the utf8(7) manual page for details.
193261d06d6bSBaptiste Daroussin  */
193361d06d6bSBaptiste Daroussin static size_t
utf8(unsigned int cp,char out[5])1934c1c95addSBrooks Davis utf8(unsigned int cp, char out[5])
193561d06d6bSBaptiste Daroussin {
193661d06d6bSBaptiste Daroussin 	size_t		 rc;
193761d06d6bSBaptiste Daroussin 
1938c1c95addSBrooks Davis 	if (cp <= 0x7f) {
193961d06d6bSBaptiste Daroussin 		rc = 1;
194061d06d6bSBaptiste Daroussin 		out[0] = (char)cp;
1941c1c95addSBrooks Davis 	} else if (cp <= 0x7ff) {
194261d06d6bSBaptiste Daroussin 		rc = 2;
194361d06d6bSBaptiste Daroussin 		out[0] = (cp >> 6  & 31) | 192;
194461d06d6bSBaptiste Daroussin 		out[1] = (cp       & 63) | 128;
1945c1c95addSBrooks Davis 	} else if (cp >= 0xd800 && cp <= 0xdfff) {
1946c1c95addSBrooks Davis 		rc = 0; /* reject UTF-16 surrogate */
1947c1c95addSBrooks Davis 	} else if (cp <= 0xffff) {
194861d06d6bSBaptiste Daroussin 		rc = 3;
194961d06d6bSBaptiste Daroussin 		out[0] = (cp >> 12 & 15) | 224;
195061d06d6bSBaptiste Daroussin 		out[1] = (cp >> 6  & 63) | 128;
195161d06d6bSBaptiste Daroussin 		out[2] = (cp       & 63) | 128;
1952c1c95addSBrooks Davis 	} else if (cp <= 0x10ffff) {
195361d06d6bSBaptiste Daroussin 		rc = 4;
195461d06d6bSBaptiste Daroussin 		out[0] = (cp >> 18 &  7) | 240;
195561d06d6bSBaptiste Daroussin 		out[1] = (cp >> 12 & 63) | 128;
195661d06d6bSBaptiste Daroussin 		out[2] = (cp >> 6  & 63) | 128;
195761d06d6bSBaptiste Daroussin 		out[3] = (cp       & 63) | 128;
195861d06d6bSBaptiste Daroussin 	} else
1959c1c95addSBrooks Davis 		rc = 0;
196061d06d6bSBaptiste Daroussin 
196161d06d6bSBaptiste Daroussin 	out[rc] = '\0';
196261d06d6bSBaptiste Daroussin 	return rc;
196361d06d6bSBaptiste Daroussin }
196461d06d6bSBaptiste Daroussin 
196561d06d6bSBaptiste Daroussin /*
196661d06d6bSBaptiste Daroussin  * If the string contains escape sequences,
196761d06d6bSBaptiste Daroussin  * replace it with an allocated rendering and return 1,
196861d06d6bSBaptiste Daroussin  * such that the caller can free it after use.
196961d06d6bSBaptiste Daroussin  * Otherwise, do nothing and return 0.
197061d06d6bSBaptiste Daroussin  */
197161d06d6bSBaptiste Daroussin static int
render_string(char ** public,size_t * psz)197261d06d6bSBaptiste Daroussin render_string(char **public, size_t *psz)
197361d06d6bSBaptiste Daroussin {
197461d06d6bSBaptiste Daroussin 	const char	*src, *scp, *addcp, *seq;
197561d06d6bSBaptiste Daroussin 	char		*dst;
197661d06d6bSBaptiste Daroussin 	size_t		 ssz, dsz, addsz;
197761d06d6bSBaptiste Daroussin 	char		 utfbuf[7], res[6];
197861d06d6bSBaptiste Daroussin 	int		 seqlen, unicode;
197961d06d6bSBaptiste Daroussin 
198061d06d6bSBaptiste Daroussin 	res[0] = '\\';
198161d06d6bSBaptiste Daroussin 	res[1] = '\t';
198261d06d6bSBaptiste Daroussin 	res[2] = ASCII_NBRSP;
198361d06d6bSBaptiste Daroussin 	res[3] = ASCII_HYPH;
198461d06d6bSBaptiste Daroussin 	res[4] = ASCII_BREAK;
198561d06d6bSBaptiste Daroussin 	res[5] = '\0';
198661d06d6bSBaptiste Daroussin 
198761d06d6bSBaptiste Daroussin 	src = scp = *public;
198861d06d6bSBaptiste Daroussin 	ssz = *psz;
198961d06d6bSBaptiste Daroussin 	dst = NULL;
199061d06d6bSBaptiste Daroussin 	dsz = 0;
199161d06d6bSBaptiste Daroussin 
199261d06d6bSBaptiste Daroussin 	while (scp < src + *psz) {
199361d06d6bSBaptiste Daroussin 
199461d06d6bSBaptiste Daroussin 		/* Leave normal characters unchanged. */
199561d06d6bSBaptiste Daroussin 
199661d06d6bSBaptiste Daroussin 		if (strchr(res, *scp) == NULL) {
199761d06d6bSBaptiste Daroussin 			if (dst != NULL)
199861d06d6bSBaptiste Daroussin 				dst[dsz++] = *scp;
199961d06d6bSBaptiste Daroussin 			scp++;
200061d06d6bSBaptiste Daroussin 			continue;
200161d06d6bSBaptiste Daroussin 		}
200261d06d6bSBaptiste Daroussin 
200361d06d6bSBaptiste Daroussin 		/*
200461d06d6bSBaptiste Daroussin 		 * Found something that requires replacing,
200561d06d6bSBaptiste Daroussin 		 * make sure we have a destination buffer.
200661d06d6bSBaptiste Daroussin 		 */
200761d06d6bSBaptiste Daroussin 
200861d06d6bSBaptiste Daroussin 		if (dst == NULL) {
200961d06d6bSBaptiste Daroussin 			dst = mandoc_malloc(ssz + 1);
201061d06d6bSBaptiste Daroussin 			dsz = scp - src;
201161d06d6bSBaptiste Daroussin 			memcpy(dst, src, dsz);
201261d06d6bSBaptiste Daroussin 		}
201361d06d6bSBaptiste Daroussin 
201461d06d6bSBaptiste Daroussin 		/* Handle single-char special characters. */
201561d06d6bSBaptiste Daroussin 
201661d06d6bSBaptiste Daroussin 		switch (*scp) {
201761d06d6bSBaptiste Daroussin 		case '\\':
201861d06d6bSBaptiste Daroussin 			break;
201961d06d6bSBaptiste Daroussin 		case '\t':
202061d06d6bSBaptiste Daroussin 		case ASCII_NBRSP:
202161d06d6bSBaptiste Daroussin 			dst[dsz++] = ' ';
202261d06d6bSBaptiste Daroussin 			scp++;
202361d06d6bSBaptiste Daroussin 			continue;
202461d06d6bSBaptiste Daroussin 		case ASCII_HYPH:
202561d06d6bSBaptiste Daroussin 			dst[dsz++] = '-';
202661d06d6bSBaptiste Daroussin 			/* FALLTHROUGH */
202761d06d6bSBaptiste Daroussin 		case ASCII_BREAK:
202861d06d6bSBaptiste Daroussin 			scp++;
202961d06d6bSBaptiste Daroussin 			continue;
203061d06d6bSBaptiste Daroussin 		default:
203161d06d6bSBaptiste Daroussin 			abort();
203261d06d6bSBaptiste Daroussin 		}
203361d06d6bSBaptiste Daroussin 
203461d06d6bSBaptiste Daroussin 		/*
203561d06d6bSBaptiste Daroussin 		 * Found an escape sequence.
203661d06d6bSBaptiste Daroussin 		 * Read past the slash, then parse it.
203761d06d6bSBaptiste Daroussin 		 * Ignore everything except characters.
203861d06d6bSBaptiste Daroussin 		 */
203961d06d6bSBaptiste Daroussin 
204061d06d6bSBaptiste Daroussin 		scp++;
2041c1c95addSBrooks Davis 		switch (mandoc_escape(&scp, &seq, &seqlen)) {
2042c1c95addSBrooks Davis 		case ESCAPE_UNICODE:
2043c1c95addSBrooks Davis 			unicode = mchars_num2uc(seq + 1, seqlen - 1);
2044c1c95addSBrooks Davis 			break;
2045c1c95addSBrooks Davis 		case ESCAPE_NUMBERED:
2046c1c95addSBrooks Davis 			unicode = mchars_num2char(seq, seqlen);
2047c1c95addSBrooks Davis 			break;
2048c1c95addSBrooks Davis 		case ESCAPE_SPECIAL:
2049c1c95addSBrooks Davis 			unicode = mchars_spec2cp(seq, seqlen);
2050c1c95addSBrooks Davis 			break;
2051c1c95addSBrooks Davis 		default:
2052c1c95addSBrooks Davis 			unicode = -1;
2053c1c95addSBrooks Davis 			break;
2054c1c95addSBrooks Davis 		}
2055c1c95addSBrooks Davis 		if (unicode <= 0)
205661d06d6bSBaptiste Daroussin 			continue;
205761d06d6bSBaptiste Daroussin 
205861d06d6bSBaptiste Daroussin 		/*
205961d06d6bSBaptiste Daroussin 		 * Render the special character
206061d06d6bSBaptiste Daroussin 		 * as either UTF-8 or ASCII.
206161d06d6bSBaptiste Daroussin 		 */
206261d06d6bSBaptiste Daroussin 
206361d06d6bSBaptiste Daroussin 		if (write_utf8) {
206461d06d6bSBaptiste Daroussin 			addsz = utf8(unicode, utfbuf);
206561d06d6bSBaptiste Daroussin 			if (addsz == 0)
206661d06d6bSBaptiste Daroussin 				continue;
206761d06d6bSBaptiste Daroussin 			addcp = utfbuf;
206861d06d6bSBaptiste Daroussin 		} else {
2069c1c95addSBrooks Davis 			addcp = mchars_uc2str(unicode);
207061d06d6bSBaptiste Daroussin 			if (addcp == NULL)
207161d06d6bSBaptiste Daroussin 				continue;
2072c1c95addSBrooks Davis 			if (*addcp == ASCII_NBRSP)
207361d06d6bSBaptiste Daroussin 				addcp = " ";
2074c1c95addSBrooks Davis 			addsz = strlen(addcp);
207561d06d6bSBaptiste Daroussin 		}
207661d06d6bSBaptiste Daroussin 
207761d06d6bSBaptiste Daroussin 		/* Copy the rendered glyph into the stream. */
207861d06d6bSBaptiste Daroussin 
207961d06d6bSBaptiste Daroussin 		ssz += addsz;
208061d06d6bSBaptiste Daroussin 		dst = mandoc_realloc(dst, ssz + 1);
208161d06d6bSBaptiste Daroussin 		memcpy(dst + dsz, addcp, addsz);
208261d06d6bSBaptiste Daroussin 		dsz += addsz;
208361d06d6bSBaptiste Daroussin 	}
208461d06d6bSBaptiste Daroussin 	if (dst != NULL) {
208561d06d6bSBaptiste Daroussin 		*public = dst;
208661d06d6bSBaptiste Daroussin 		*psz = dsz;
208761d06d6bSBaptiste Daroussin 	}
208861d06d6bSBaptiste Daroussin 
208961d06d6bSBaptiste Daroussin 	/* Trim trailing whitespace and NUL-terminate. */
209061d06d6bSBaptiste Daroussin 
209161d06d6bSBaptiste Daroussin 	while (*psz > 0 && (*public)[*psz - 1] == ' ')
209261d06d6bSBaptiste Daroussin 		--*psz;
209361d06d6bSBaptiste Daroussin 	if (dst != NULL) {
209461d06d6bSBaptiste Daroussin 		(*public)[*psz] = '\0';
209561d06d6bSBaptiste Daroussin 		return 1;
209661d06d6bSBaptiste Daroussin 	} else
209761d06d6bSBaptiste Daroussin 		return 0;
209861d06d6bSBaptiste Daroussin }
209961d06d6bSBaptiste Daroussin 
210061d06d6bSBaptiste Daroussin static void
dbadd_mlink(const struct mlink * mlink)210161d06d6bSBaptiste Daroussin dbadd_mlink(const struct mlink *mlink)
210261d06d6bSBaptiste Daroussin {
210361d06d6bSBaptiste Daroussin 	dba_page_alias(mlink->mpage->dba, mlink->name, NAME_FILE);
210461d06d6bSBaptiste Daroussin 	dba_page_add(mlink->mpage->dba, DBP_SECT, mlink->dsec);
210561d06d6bSBaptiste Daroussin 	dba_page_add(mlink->mpage->dba, DBP_SECT, mlink->fsec);
210661d06d6bSBaptiste Daroussin 	dba_page_add(mlink->mpage->dba, DBP_ARCH, mlink->arch);
210761d06d6bSBaptiste Daroussin 	dba_page_add(mlink->mpage->dba, DBP_FILE, mlink->file);
210861d06d6bSBaptiste Daroussin }
210961d06d6bSBaptiste Daroussin 
211061d06d6bSBaptiste Daroussin /*
211161d06d6bSBaptiste Daroussin  * Flush the current page's terms (and their bits) into the database.
211261d06d6bSBaptiste Daroussin  * Also, handle escape sequences at the last possible moment.
211361d06d6bSBaptiste Daroussin  */
211461d06d6bSBaptiste Daroussin static void
dbadd(struct dba * dba,struct mpage * mpage)211561d06d6bSBaptiste Daroussin dbadd(struct dba *dba, struct mpage *mpage)
211661d06d6bSBaptiste Daroussin {
211761d06d6bSBaptiste Daroussin 	struct mlink	*mlink;
211861d06d6bSBaptiste Daroussin 	struct str	*key;
211961d06d6bSBaptiste Daroussin 	char		*cp;
212061d06d6bSBaptiste Daroussin 	uint64_t	 mask;
212161d06d6bSBaptiste Daroussin 	size_t		 i;
212261d06d6bSBaptiste Daroussin 	unsigned int	 slot;
212361d06d6bSBaptiste Daroussin 	int		 mustfree;
212461d06d6bSBaptiste Daroussin 
212561d06d6bSBaptiste Daroussin 	mlink = mpage->mlinks;
212661d06d6bSBaptiste Daroussin 
212761d06d6bSBaptiste Daroussin 	if (nodb) {
212861d06d6bSBaptiste Daroussin 		for (key = ohash_first(&names, &slot); NULL != key;
212961d06d6bSBaptiste Daroussin 		     key = ohash_next(&names, &slot))
213061d06d6bSBaptiste Daroussin 			free(key);
213161d06d6bSBaptiste Daroussin 		for (key = ohash_first(&strings, &slot); NULL != key;
213261d06d6bSBaptiste Daroussin 		     key = ohash_next(&strings, &slot))
213361d06d6bSBaptiste Daroussin 			free(key);
213461d06d6bSBaptiste Daroussin 		if (0 == debug)
213561d06d6bSBaptiste Daroussin 			return;
213661d06d6bSBaptiste Daroussin 		while (NULL != mlink) {
213761d06d6bSBaptiste Daroussin 			fputs(mlink->name, stdout);
213861d06d6bSBaptiste Daroussin 			if (NULL == mlink->next ||
213961d06d6bSBaptiste Daroussin 			    strcmp(mlink->dsec, mlink->next->dsec) ||
214061d06d6bSBaptiste Daroussin 			    strcmp(mlink->fsec, mlink->next->fsec) ||
214161d06d6bSBaptiste Daroussin 			    strcmp(mlink->arch, mlink->next->arch)) {
214261d06d6bSBaptiste Daroussin 				putchar('(');
214361d06d6bSBaptiste Daroussin 				if ('\0' == *mlink->dsec)
214461d06d6bSBaptiste Daroussin 					fputs(mlink->fsec, stdout);
214561d06d6bSBaptiste Daroussin 				else
214661d06d6bSBaptiste Daroussin 					fputs(mlink->dsec, stdout);
214761d06d6bSBaptiste Daroussin 				if ('\0' != *mlink->arch)
214861d06d6bSBaptiste Daroussin 					printf("/%s", mlink->arch);
214961d06d6bSBaptiste Daroussin 				putchar(')');
215061d06d6bSBaptiste Daroussin 			}
215161d06d6bSBaptiste Daroussin 			mlink = mlink->next;
215261d06d6bSBaptiste Daroussin 			if (NULL != mlink)
215361d06d6bSBaptiste Daroussin 				fputs(", ", stdout);
215461d06d6bSBaptiste Daroussin 		}
215561d06d6bSBaptiste Daroussin 		printf(" - %s\n", mpage->desc);
215661d06d6bSBaptiste Daroussin 		return;
215761d06d6bSBaptiste Daroussin 	}
215861d06d6bSBaptiste Daroussin 
215961d06d6bSBaptiste Daroussin 	if (debug)
216061d06d6bSBaptiste Daroussin 		say(mlink->file, "Adding to database");
216161d06d6bSBaptiste Daroussin 
216261d06d6bSBaptiste Daroussin 	cp = mpage->desc;
216361d06d6bSBaptiste Daroussin 	i = strlen(cp);
216461d06d6bSBaptiste Daroussin 	mustfree = render_string(&cp, &i);
216561d06d6bSBaptiste Daroussin 	mpage->dba = dba_page_new(dba->pages,
216661d06d6bSBaptiste Daroussin 	    *mpage->arch == '\0' ? mlink->arch : mpage->arch,
216761d06d6bSBaptiste Daroussin 	    cp, mlink->file, mpage->form);
216861d06d6bSBaptiste Daroussin 	if (mustfree)
216961d06d6bSBaptiste Daroussin 		free(cp);
217061d06d6bSBaptiste Daroussin 	dba_page_add(mpage->dba, DBP_SECT, mpage->sec);
217161d06d6bSBaptiste Daroussin 
217261d06d6bSBaptiste Daroussin 	while (mlink != NULL) {
217361d06d6bSBaptiste Daroussin 		dbadd_mlink(mlink);
217461d06d6bSBaptiste Daroussin 		mlink = mlink->next;
217561d06d6bSBaptiste Daroussin 	}
217661d06d6bSBaptiste Daroussin 
217761d06d6bSBaptiste Daroussin 	for (key = ohash_first(&names, &slot); NULL != key;
217861d06d6bSBaptiste Daroussin 	     key = ohash_next(&names, &slot)) {
217961d06d6bSBaptiste Daroussin 		assert(key->mpage == mpage);
218061d06d6bSBaptiste Daroussin 		dba_page_alias(mpage->dba, key->key, key->mask);
218161d06d6bSBaptiste Daroussin 		free(key);
218261d06d6bSBaptiste Daroussin 	}
218361d06d6bSBaptiste Daroussin 	for (key = ohash_first(&strings, &slot); NULL != key;
218461d06d6bSBaptiste Daroussin 	     key = ohash_next(&strings, &slot)) {
218561d06d6bSBaptiste Daroussin 		assert(key->mpage == mpage);
218661d06d6bSBaptiste Daroussin 		i = 0;
218761d06d6bSBaptiste Daroussin 		for (mask = TYPE_Xr; mask <= TYPE_Lb; mask *= 2) {
218861d06d6bSBaptiste Daroussin 			if (key->mask & mask)
218961d06d6bSBaptiste Daroussin 				dba_macro_add(dba->macros, i,
219061d06d6bSBaptiste Daroussin 				    key->key, mpage->dba);
219161d06d6bSBaptiste Daroussin 			i++;
219261d06d6bSBaptiste Daroussin 		}
219361d06d6bSBaptiste Daroussin 		free(key);
219461d06d6bSBaptiste Daroussin 	}
219561d06d6bSBaptiste Daroussin }
219661d06d6bSBaptiste Daroussin 
219761d06d6bSBaptiste Daroussin static void
dbprune(struct dba * dba)219861d06d6bSBaptiste Daroussin dbprune(struct dba *dba)
219961d06d6bSBaptiste Daroussin {
220061d06d6bSBaptiste Daroussin 	struct dba_array	*page, *files;
220161d06d6bSBaptiste Daroussin 	char			*file;
220261d06d6bSBaptiste Daroussin 
220361d06d6bSBaptiste Daroussin 	dba_array_FOREACH(dba->pages, page) {
220461d06d6bSBaptiste Daroussin 		files = dba_array_get(page, DBP_FILE);
220561d06d6bSBaptiste Daroussin 		dba_array_FOREACH(files, file) {
220661d06d6bSBaptiste Daroussin 			if (*file < ' ')
220761d06d6bSBaptiste Daroussin 				file++;
220861d06d6bSBaptiste Daroussin 			if (ohash_find(&mlinks, ohash_qlookup(&mlinks,
220961d06d6bSBaptiste Daroussin 			    file)) != NULL) {
221061d06d6bSBaptiste Daroussin 				if (debug)
221161d06d6bSBaptiste Daroussin 					say(file, "Deleting from database");
221261d06d6bSBaptiste Daroussin 				dba_array_del(dba->pages);
221361d06d6bSBaptiste Daroussin 				break;
221461d06d6bSBaptiste Daroussin 			}
221561d06d6bSBaptiste Daroussin 		}
221661d06d6bSBaptiste Daroussin 	}
221761d06d6bSBaptiste Daroussin }
221861d06d6bSBaptiste Daroussin 
221961d06d6bSBaptiste Daroussin /*
222061d06d6bSBaptiste Daroussin  * Write the database from memory to disk.
222161d06d6bSBaptiste Daroussin  */
222261d06d6bSBaptiste Daroussin static void
dbwrite(struct dba * dba)222361d06d6bSBaptiste Daroussin dbwrite(struct dba *dba)
222461d06d6bSBaptiste Daroussin {
222561d06d6bSBaptiste Daroussin 	struct stat	 sb1, sb2;
222661d06d6bSBaptiste Daroussin 	char		 tfn[33], *cp1, *cp2;
222761d06d6bSBaptiste Daroussin 	off_t		 i;
222861d06d6bSBaptiste Daroussin 	int		 fd1, fd2;
222961d06d6bSBaptiste Daroussin 
223061d06d6bSBaptiste Daroussin 	/*
223161d06d6bSBaptiste Daroussin 	 * Do not write empty databases, and delete existing ones
223261d06d6bSBaptiste Daroussin 	 * when makewhatis -u causes them to become empty.
223361d06d6bSBaptiste Daroussin 	 */
223461d06d6bSBaptiste Daroussin 
223561d06d6bSBaptiste Daroussin 	dba_array_start(dba->pages);
223661d06d6bSBaptiste Daroussin 	if (dba_array_next(dba->pages) == NULL) {
223761d06d6bSBaptiste Daroussin 		if (unlink(MANDOC_DB) == -1 && errno != ENOENT)
223861d06d6bSBaptiste Daroussin 			say(MANDOC_DB, "&unlink");
223961d06d6bSBaptiste Daroussin 		return;
224061d06d6bSBaptiste Daroussin 	}
224161d06d6bSBaptiste Daroussin 
224261d06d6bSBaptiste Daroussin 	/*
224361d06d6bSBaptiste Daroussin 	 * Build the database in a temporary file,
224461d06d6bSBaptiste Daroussin 	 * then atomically move it into place.
224561d06d6bSBaptiste Daroussin 	 */
224661d06d6bSBaptiste Daroussin 
224761d06d6bSBaptiste Daroussin 	if (dba_write(MANDOC_DB "~", dba) != -1) {
224861d06d6bSBaptiste Daroussin 		if (rename(MANDOC_DB "~", MANDOC_DB) == -1) {
224961d06d6bSBaptiste Daroussin 			exitcode = (int)MANDOCLEVEL_SYSERR;
225061d06d6bSBaptiste Daroussin 			say(MANDOC_DB, "&rename");
225161d06d6bSBaptiste Daroussin 			unlink(MANDOC_DB "~");
225261d06d6bSBaptiste Daroussin 		}
225361d06d6bSBaptiste Daroussin 		return;
225461d06d6bSBaptiste Daroussin 	}
225561d06d6bSBaptiste Daroussin 
225661d06d6bSBaptiste Daroussin 	/*
225761d06d6bSBaptiste Daroussin 	 * We lack write permission and cannot replace the database
225861d06d6bSBaptiste Daroussin 	 * file, but let's at least check whether the data changed.
225961d06d6bSBaptiste Daroussin 	 */
226061d06d6bSBaptiste Daroussin 
226161d06d6bSBaptiste Daroussin 	(void)strlcpy(tfn, "/tmp/mandocdb.XXXXXXXX", sizeof(tfn));
226261d06d6bSBaptiste Daroussin 	if (mkdtemp(tfn) == NULL) {
226361d06d6bSBaptiste Daroussin 		exitcode = (int)MANDOCLEVEL_SYSERR;
226461d06d6bSBaptiste Daroussin 		say("", "&%s", tfn);
226561d06d6bSBaptiste Daroussin 		return;
226661d06d6bSBaptiste Daroussin 	}
226761d06d6bSBaptiste Daroussin 	cp1 = cp2 = MAP_FAILED;
226861d06d6bSBaptiste Daroussin 	fd1 = fd2 = -1;
226961d06d6bSBaptiste Daroussin 	(void)strlcat(tfn, "/" MANDOC_DB, sizeof(tfn));
227061d06d6bSBaptiste Daroussin 	if (dba_write(tfn, dba) == -1) {
227161d06d6bSBaptiste Daroussin 		say(tfn, "&dba_write");
227261d06d6bSBaptiste Daroussin 		goto err;
227361d06d6bSBaptiste Daroussin 	}
2274c1c95addSBrooks Davis 	if ((fd1 = open(MANDOC_DB, O_RDONLY)) == -1) {
227561d06d6bSBaptiste Daroussin 		say(MANDOC_DB, "&open");
227661d06d6bSBaptiste Daroussin 		goto err;
227761d06d6bSBaptiste Daroussin 	}
2278c1c95addSBrooks Davis 	if ((fd2 = open(tfn, O_RDONLY)) == -1) {
227961d06d6bSBaptiste Daroussin 		say(tfn, "&open");
228061d06d6bSBaptiste Daroussin 		goto err;
228161d06d6bSBaptiste Daroussin 	}
228261d06d6bSBaptiste Daroussin 	if (fstat(fd1, &sb1) == -1) {
228361d06d6bSBaptiste Daroussin 		say(MANDOC_DB, "&fstat");
228461d06d6bSBaptiste Daroussin 		goto err;
228561d06d6bSBaptiste Daroussin 	}
228661d06d6bSBaptiste Daroussin 	if (fstat(fd2, &sb2) == -1) {
228761d06d6bSBaptiste Daroussin 		say(tfn, "&fstat");
228861d06d6bSBaptiste Daroussin 		goto err;
228961d06d6bSBaptiste Daroussin 	}
229061d06d6bSBaptiste Daroussin 	if (sb1.st_size != sb2.st_size)
229161d06d6bSBaptiste Daroussin 		goto err;
229261d06d6bSBaptiste Daroussin 	if ((cp1 = mmap(NULL, sb1.st_size, PROT_READ, MAP_PRIVATE,
229361d06d6bSBaptiste Daroussin 	    fd1, 0)) == MAP_FAILED) {
229461d06d6bSBaptiste Daroussin 		say(MANDOC_DB, "&mmap");
229561d06d6bSBaptiste Daroussin 		goto err;
229661d06d6bSBaptiste Daroussin 	}
229761d06d6bSBaptiste Daroussin 	if ((cp2 = mmap(NULL, sb2.st_size, PROT_READ, MAP_PRIVATE,
229861d06d6bSBaptiste Daroussin 	    fd2, 0)) == MAP_FAILED) {
229961d06d6bSBaptiste Daroussin 		say(tfn, "&mmap");
230061d06d6bSBaptiste Daroussin 		goto err;
230161d06d6bSBaptiste Daroussin 	}
230261d06d6bSBaptiste Daroussin 	for (i = 0; i < sb1.st_size; i++)
230361d06d6bSBaptiste Daroussin 		if (cp1[i] != cp2[i])
230461d06d6bSBaptiste Daroussin 			goto err;
230561d06d6bSBaptiste Daroussin 	goto out;
230661d06d6bSBaptiste Daroussin 
230761d06d6bSBaptiste Daroussin err:
230861d06d6bSBaptiste Daroussin 	exitcode = (int)MANDOCLEVEL_SYSERR;
230961d06d6bSBaptiste Daroussin 	say(MANDOC_DB, "Data changed, but cannot replace database");
231061d06d6bSBaptiste Daroussin 
231161d06d6bSBaptiste Daroussin out:
231261d06d6bSBaptiste Daroussin 	if (cp1 != MAP_FAILED)
231361d06d6bSBaptiste Daroussin 		munmap(cp1, sb1.st_size);
231461d06d6bSBaptiste Daroussin 	if (cp2 != MAP_FAILED)
231561d06d6bSBaptiste Daroussin 		munmap(cp2, sb2.st_size);
231661d06d6bSBaptiste Daroussin 	if (fd1 != -1)
231761d06d6bSBaptiste Daroussin 		close(fd1);
231861d06d6bSBaptiste Daroussin 	if (fd2 != -1)
231961d06d6bSBaptiste Daroussin 		close(fd2);
232061d06d6bSBaptiste Daroussin 	unlink(tfn);
232161d06d6bSBaptiste Daroussin 	*strrchr(tfn, '/') = '\0';
232261d06d6bSBaptiste Daroussin 	rmdir(tfn);
232361d06d6bSBaptiste Daroussin }
232461d06d6bSBaptiste Daroussin 
232561d06d6bSBaptiste Daroussin static int
set_basedir(const char * targetdir,int report_baddir)232661d06d6bSBaptiste Daroussin set_basedir(const char *targetdir, int report_baddir)
232761d06d6bSBaptiste Daroussin {
232861d06d6bSBaptiste Daroussin 	static char	 startdir[PATH_MAX];
232961d06d6bSBaptiste Daroussin 	static int	 getcwd_status;  /* 1 = ok, 2 = failure */
233061d06d6bSBaptiste Daroussin 	static int	 chdir_status;  /* 1 = changed directory */
233161d06d6bSBaptiste Daroussin 
233261d06d6bSBaptiste Daroussin 	/*
233361d06d6bSBaptiste Daroussin 	 * Remember the original working directory, if possible.
233461d06d6bSBaptiste Daroussin 	 * This will be needed if the second or a later directory
233561d06d6bSBaptiste Daroussin 	 * on the command line is given as a relative path.
233661d06d6bSBaptiste Daroussin 	 * Do not error out if the current directory is not
233761d06d6bSBaptiste Daroussin 	 * searchable: Maybe it won't be needed after all.
233861d06d6bSBaptiste Daroussin 	 */
23396d38604fSBaptiste Daroussin 	if (getcwd_status == 0) {
23406d38604fSBaptiste Daroussin 		if (getcwd(startdir, sizeof(startdir)) == NULL) {
234161d06d6bSBaptiste Daroussin 			getcwd_status = 2;
234261d06d6bSBaptiste Daroussin 			(void)strlcpy(startdir, strerror(errno),
234361d06d6bSBaptiste Daroussin 			    sizeof(startdir));
234461d06d6bSBaptiste Daroussin 		} else
234561d06d6bSBaptiste Daroussin 			getcwd_status = 1;
234661d06d6bSBaptiste Daroussin 	}
234761d06d6bSBaptiste Daroussin 
234861d06d6bSBaptiste Daroussin 	/*
234961d06d6bSBaptiste Daroussin 	 * We are leaving the old base directory.
235061d06d6bSBaptiste Daroussin 	 * Do not use it any longer, not even for messages.
235161d06d6bSBaptiste Daroussin 	 */
235261d06d6bSBaptiste Daroussin 	*basedir = '\0';
23536d38604fSBaptiste Daroussin 	basedir_len = 0;
235461d06d6bSBaptiste Daroussin 
235561d06d6bSBaptiste Daroussin 	/*
235661d06d6bSBaptiste Daroussin 	 * If and only if the directory was changed earlier and
235761d06d6bSBaptiste Daroussin 	 * the next directory to process is given as a relative path,
235861d06d6bSBaptiste Daroussin 	 * first go back, or bail out if that is impossible.
235961d06d6bSBaptiste Daroussin 	 */
23606d38604fSBaptiste Daroussin 	if (chdir_status && *targetdir != '/') {
23616d38604fSBaptiste Daroussin 		if (getcwd_status == 2) {
236261d06d6bSBaptiste Daroussin 			exitcode = (int)MANDOCLEVEL_SYSERR;
236361d06d6bSBaptiste Daroussin 			say("", "getcwd: %s", startdir);
236461d06d6bSBaptiste Daroussin 			return 0;
236561d06d6bSBaptiste Daroussin 		}
23666d38604fSBaptiste Daroussin 		if (chdir(startdir) == -1) {
236761d06d6bSBaptiste Daroussin 			exitcode = (int)MANDOCLEVEL_SYSERR;
236861d06d6bSBaptiste Daroussin 			say("", "&chdir %s", startdir);
236961d06d6bSBaptiste Daroussin 			return 0;
237061d06d6bSBaptiste Daroussin 		}
237161d06d6bSBaptiste Daroussin 	}
237261d06d6bSBaptiste Daroussin 
237361d06d6bSBaptiste Daroussin 	/*
237461d06d6bSBaptiste Daroussin 	 * Always resolve basedir to the canonicalized absolute
237561d06d6bSBaptiste Daroussin 	 * pathname and append a trailing slash, such that
237661d06d6bSBaptiste Daroussin 	 * we can reliably check whether files are inside.
237761d06d6bSBaptiste Daroussin 	 */
23786d38604fSBaptiste Daroussin 	if (realpath(targetdir, basedir) == NULL) {
237961d06d6bSBaptiste Daroussin 		if (report_baddir || errno != ENOENT) {
238061d06d6bSBaptiste Daroussin 			exitcode = (int)MANDOCLEVEL_BADARG;
238161d06d6bSBaptiste Daroussin 			say("", "&%s: realpath", targetdir);
238261d06d6bSBaptiste Daroussin 		}
23836d38604fSBaptiste Daroussin 		*basedir = '\0';
238461d06d6bSBaptiste Daroussin 		return 0;
23856d38604fSBaptiste Daroussin 	} else if (chdir(basedir) == -1) {
238661d06d6bSBaptiste Daroussin 		if (report_baddir || errno != ENOENT) {
238761d06d6bSBaptiste Daroussin 			exitcode = (int)MANDOCLEVEL_BADARG;
238861d06d6bSBaptiste Daroussin 			say("", "&chdir");
238961d06d6bSBaptiste Daroussin 		}
23906d38604fSBaptiste Daroussin 		*basedir = '\0';
239161d06d6bSBaptiste Daroussin 		return 0;
239261d06d6bSBaptiste Daroussin 	}
239361d06d6bSBaptiste Daroussin 	chdir_status = 1;
23946d38604fSBaptiste Daroussin 	basedir_len = strlen(basedir);
23956d38604fSBaptiste Daroussin 	if (basedir[basedir_len - 1] != '/') {
23966d38604fSBaptiste Daroussin 		if (basedir_len >= PATH_MAX - 1) {
239761d06d6bSBaptiste Daroussin 			exitcode = (int)MANDOCLEVEL_SYSERR;
239861d06d6bSBaptiste Daroussin 			say("", "Filename too long");
23996d38604fSBaptiste Daroussin 			*basedir = '\0';
24006d38604fSBaptiste Daroussin 			basedir_len = 0;
240161d06d6bSBaptiste Daroussin 			return 0;
240261d06d6bSBaptiste Daroussin 		}
24036d38604fSBaptiste Daroussin 		basedir[basedir_len++] = '/';
24046d38604fSBaptiste Daroussin 		basedir[basedir_len] = '\0';
240561d06d6bSBaptiste Daroussin 	}
240661d06d6bSBaptiste Daroussin 	return 1;
240761d06d6bSBaptiste Daroussin }
240861d06d6bSBaptiste Daroussin 
24096d38604fSBaptiste Daroussin #ifdef READ_ALLOWED_PATH
24106d38604fSBaptiste Daroussin static int
read_allowed(const char * candidate)24116d38604fSBaptiste Daroussin read_allowed(const char *candidate)
24126d38604fSBaptiste Daroussin {
24136d38604fSBaptiste Daroussin 	const char	*cp;
24146d38604fSBaptiste Daroussin 	size_t		 len;
24156d38604fSBaptiste Daroussin 
24166d38604fSBaptiste Daroussin 	for (cp = READ_ALLOWED_PATH;; cp += len) {
24176d38604fSBaptiste Daroussin 		while (*cp == ':')
24186d38604fSBaptiste Daroussin 			cp++;
24196d38604fSBaptiste Daroussin 		if (*cp == '\0')
24206d38604fSBaptiste Daroussin 			return 0;
24216d38604fSBaptiste Daroussin 		len = strcspn(cp, ":");
24226d38604fSBaptiste Daroussin 		if (strncmp(candidate, cp, len) == 0)
24236d38604fSBaptiste Daroussin 			return 1;
24246d38604fSBaptiste Daroussin 	}
24256d38604fSBaptiste Daroussin }
24266d38604fSBaptiste Daroussin #endif
24276d38604fSBaptiste Daroussin 
242861d06d6bSBaptiste Daroussin static void
say(const char * file,const char * format,...)242961d06d6bSBaptiste Daroussin say(const char *file, const char *format, ...)
243061d06d6bSBaptiste Daroussin {
243161d06d6bSBaptiste Daroussin 	va_list		 ap;
243261d06d6bSBaptiste Daroussin 	int		 use_errno;
243361d06d6bSBaptiste Daroussin 
24346d38604fSBaptiste Daroussin 	if (*basedir != '\0')
243561d06d6bSBaptiste Daroussin 		fprintf(stderr, "%s", basedir);
24366d38604fSBaptiste Daroussin 	if (*basedir != '\0' && *file != '\0')
243761d06d6bSBaptiste Daroussin 		fputc('/', stderr);
24386d38604fSBaptiste Daroussin 	if (*file != '\0')
243961d06d6bSBaptiste Daroussin 		fprintf(stderr, "%s", file);
244061d06d6bSBaptiste Daroussin 
244161d06d6bSBaptiste Daroussin 	use_errno = 1;
24426d38604fSBaptiste Daroussin 	if (format != NULL) {
244361d06d6bSBaptiste Daroussin 		switch (*format) {
244461d06d6bSBaptiste Daroussin 		case '&':
244561d06d6bSBaptiste Daroussin 			format++;
244661d06d6bSBaptiste Daroussin 			break;
244761d06d6bSBaptiste Daroussin 		case '\0':
244861d06d6bSBaptiste Daroussin 			format = NULL;
244961d06d6bSBaptiste Daroussin 			break;
245061d06d6bSBaptiste Daroussin 		default:
245161d06d6bSBaptiste Daroussin 			use_errno = 0;
245261d06d6bSBaptiste Daroussin 			break;
245361d06d6bSBaptiste Daroussin 		}
245461d06d6bSBaptiste Daroussin 	}
24556d38604fSBaptiste Daroussin 	if (format != NULL) {
24566d38604fSBaptiste Daroussin 		if (*basedir != '\0' || *file != '\0')
245761d06d6bSBaptiste Daroussin 			fputs(": ", stderr);
245861d06d6bSBaptiste Daroussin 		va_start(ap, format);
245961d06d6bSBaptiste Daroussin 		vfprintf(stderr, format, ap);
246061d06d6bSBaptiste Daroussin 		va_end(ap);
246161d06d6bSBaptiste Daroussin 	}
246261d06d6bSBaptiste Daroussin 	if (use_errno) {
24636d38604fSBaptiste Daroussin 		if (*basedir != '\0' || *file != '\0' || format != NULL)
246461d06d6bSBaptiste Daroussin 			fputs(": ", stderr);
246561d06d6bSBaptiste Daroussin 		perror(NULL);
246661d06d6bSBaptiste Daroussin 	} else
246761d06d6bSBaptiste Daroussin 		fputc('\n', stderr);
246861d06d6bSBaptiste Daroussin }
2469