Return to mandocdb.c CVS log | Up to [cvsweb.bsd.lv] / mandoc |
version 1.112, 2014/01/19 22:41:25 | version 1.229, 2016/09/01 15:08:04 | ||
---|---|---|---|
|
|
||
/* $Id$ */ | /* $Id$ */ | ||
/* | /* | ||
* Copyright (c) 2011, 2012 Kristaps Dzonsons <kristaps@bsd.lv> | * Copyright (c) 2011, 2012 Kristaps Dzonsons <kristaps@bsd.lv> | ||
* Copyright (c) 2011, 2012, 2013, 2014 Ingo Schwarze <schwarze@openbsd.org> | * Copyright (c) 2011-2016 Ingo Schwarze <schwarze@openbsd.org> | ||
* | * | ||
* Permission to use, copy, modify, and distribute this software for any | * Permission to use, copy, modify, and distribute this software for any | ||
* purpose with or without fee is hereby granted, provided that the above | * purpose with or without fee is hereby granted, provided that the above | ||
* copyright notice and this permission notice appear in all copies. | * copyright notice and this permission notice appear in all copies. | ||
* | * | ||
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHORS DISCLAIM ALL WARRANTIES | ||
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF | ||
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR | ||
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES | ||
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN | ||
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF | ||
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. | ||
*/ | */ | ||
#ifdef HAVE_CONFIG_H | |||
#include "config.h" | #include "config.h" | ||
#endif | |||
#include <sys/types.h> | |||
#include <sys/stat.h> | #include <sys/stat.h> | ||
#include <sys/wait.h> | |||
#include <assert.h> | #include <assert.h> | ||
#include <ctype.h> | #include <ctype.h> | ||
#if HAVE_ERR | |||
#include <err.h> | |||
#endif | |||
#include <errno.h> | #include <errno.h> | ||
#include <fcntl.h> | #include <fcntl.h> | ||
#if HAVE_FTS | |||
#include <fts.h> | #include <fts.h> | ||
#include <getopt.h> | #else | ||
#include "compat_fts.h" | |||
#endif | |||
#include <limits.h> | #include <limits.h> | ||
#if HAVE_SANDBOX_INIT | |||
#include <sandbox.h> | |||
#endif | |||
#include <stdarg.h> | |||
#include <stddef.h> | #include <stddef.h> | ||
#include <stdio.h> | #include <stdio.h> | ||
#include <stdint.h> | #include <stdint.h> | ||
|
|
||
#include <string.h> | #include <string.h> | ||
#include <unistd.h> | #include <unistd.h> | ||
#ifdef HAVE_OHASH | #include "mandoc_aux.h" | ||
#include <ohash.h> | #include "mandoc_ohash.h" | ||
#else | #include "mandoc.h" | ||
#include "compat_ohash.h" | #include "roff.h" | ||
#endif | |||
#include <sqlite3.h> | |||
#include "mdoc.h" | #include "mdoc.h" | ||
#include "man.h" | #include "man.h" | ||
#include "mandoc.h" | #include "manconf.h" | ||
#include "manpath.h" | |||
#include "mansearch.h" | #include "mansearch.h" | ||
#include "dba_array.h" | |||
#include "dba.h" | |||
extern int mansearch_keymax; | |||
extern const char *const mansearch_keynames[]; | extern const char *const mansearch_keynames[]; | ||
#define SQL_EXEC(_v) \ | |||
if (SQLITE_OK != sqlite3_exec(db, (_v), NULL, NULL, NULL)) \ | |||
fprintf(stderr, "%s\n", sqlite3_errmsg(db)) | |||
#define SQL_BIND_TEXT(_s, _i, _v) \ | |||
if (SQLITE_OK != sqlite3_bind_text \ | |||
((_s), (_i)++, (_v), -1, SQLITE_STATIC)) \ | |||
fprintf(stderr, "%s\n", sqlite3_errmsg(db)) | |||
#define SQL_BIND_INT(_s, _i, _v) \ | |||
if (SQLITE_OK != sqlite3_bind_int \ | |||
((_s), (_i)++, (_v))) \ | |||
fprintf(stderr, "%s\n", sqlite3_errmsg(db)) | |||
#define SQL_BIND_INT64(_s, _i, _v) \ | |||
if (SQLITE_OK != sqlite3_bind_int64 \ | |||
((_s), (_i)++, (_v))) \ | |||
fprintf(stderr, "%s\n", sqlite3_errmsg(db)) | |||
#define SQL_STEP(_s) \ | |||
if (SQLITE_DONE != sqlite3_step((_s))) \ | |||
fprintf(stderr, "%s\n", sqlite3_errmsg(db)) | |||
enum op { | enum op { | ||
OP_DEFAULT = 0, /* new dbs from dir list or default config */ | OP_DEFAULT = 0, /* new dbs from dir list or default config */ | ||
OP_CONFFILE, /* new databases from custom config file */ | OP_CONFFILE, /* new databases from custom config file */ | ||
|
|
||
OP_TEST /* change no databases, report potential problems */ | OP_TEST /* change no databases, report potential problems */ | ||
}; | }; | ||
enum form { | |||
FORM_NONE, /* format is unknown */ | |||
FORM_SRC, /* format is -man or -mdoc */ | |||
FORM_CAT /* format is cat */ | |||
}; | |||
struct str { | struct str { | ||
char *rendered; /* key in UTF-8 or ASCII form */ | |||
const struct mpage *mpage; /* if set, the owning parse */ | const struct mpage *mpage; /* if set, the owning parse */ | ||
uint64_t mask; /* bitmask in sequence */ | uint64_t mask; /* bitmask in sequence */ | ||
char key[]; /* may contain escape sequences */ | char key[]; /* rendered text */ | ||
}; | }; | ||
struct inodev { | struct inodev { | ||
|
|
||
struct mpage { | struct mpage { | ||
struct inodev inodev; /* used for hashing routine */ | struct inodev inodev; /* used for hashing routine */ | ||
enum form form; /* format from file content */ | struct dba_array *dba; | ||
char *sec; /* section from file content */ | char *sec; /* section from file content */ | ||
char *arch; /* architecture from file content */ | char *arch; /* architecture from file content */ | ||
char *title; /* title from file content */ | char *title; /* title from file content */ | ||
char *desc; /* description from file content */ | char *desc; /* description from file content */ | ||
struct mlink *mlinks; /* singly linked list */ | struct mlink *mlinks; /* singly linked list */ | ||
int name_head_done; | |||
enum form form; /* format from file content */ | |||
}; | }; | ||
struct mlink { | struct mlink { | ||
char file[PATH_MAX]; /* filename rel. to manpath */ | char file[PATH_MAX]; /* filename rel. to manpath */ | ||
enum form dform; /* format from directory */ | |||
enum form fform; /* format from file name suffix */ | |||
char *dsec; /* section from directory */ | char *dsec; /* section from directory */ | ||
char *arch; /* architecture from directory */ | char *arch; /* architecture from directory */ | ||
char *name; /* name from file name (not empty) */ | char *name; /* name from file name (not empty) */ | ||
char *fsec; /* section from file name suffix */ | char *fsec; /* section from file name suffix */ | ||
struct mlink *next; /* singly linked list */ | struct mlink *next; /* singly linked list */ | ||
struct mpage *mpage; /* parent */ | |||
int gzip; /* filename has a .gz suffix */ | |||
enum form dform; /* format from directory */ | |||
enum form fform; /* format from file name suffix */ | |||
}; | }; | ||
enum stmt { | typedef int (*mdoc_fp)(struct mpage *, const struct roff_meta *, | ||
STMT_DELETE_PAGE = 0, /* delete mpage */ | const struct roff_node *); | ||
STMT_INSERT_PAGE, /* insert mpage */ | |||
STMT_INSERT_LINK, /* insert mlink */ | |||
STMT_INSERT_KEY, /* insert parsed key */ | |||
STMT__MAX | |||
}; | |||
typedef int (*mdoc_fp)(struct mpage *, const struct mdoc_node *); | |||
struct mdoc_handler { | struct mdoc_handler { | ||
mdoc_fp fp; /* optional handler */ | mdoc_fp fp; /* optional handler */ | ||
uint64_t mask; /* set unless handler returns 0 */ | uint64_t mask; /* set unless handler returns 0 */ | ||
}; | }; | ||
static void dbclose(int); | |||
static void dbadd(const struct mpage *, struct mchars *); | int mandocdb(int, char *[]); | ||
static int dbopen(int); | |||
static void dbprune(void); | static void dbadd(struct dba *, struct mpage *); | ||
static void dbadd_mlink(const struct mlink *mlink); | |||
static void dbprune(struct dba *); | |||
static void dbwrite(struct dba *); | |||
static void filescan(const char *); | static void filescan(const char *); | ||
static void *hash_alloc(size_t, void *); | |||
static void hash_free(void *, size_t, void *); | |||
static void *hash_halloc(size_t, void *); | |||
static void mlink_add(struct mlink *, const struct stat *); | static void mlink_add(struct mlink *, const struct stat *); | ||
static int mlink_check(struct mpage *, struct mlink *); | static void mlink_check(struct mpage *, struct mlink *); | ||
static void mlink_free(struct mlink *); | static void mlink_free(struct mlink *); | ||
static void mlinks_undupe(struct mpage *); | static void mlinks_undupe(struct mpage *); | ||
int mpages_compare(const void *, const void *); | |||
static void mpages_free(void); | static void mpages_free(void); | ||
static void mpages_merge(struct mchars *, struct mparse *); | static void mpages_merge(struct dba *, struct mparse *); | ||
static void parse_cat(struct mpage *); | static void parse_cat(struct mpage *, int); | ||
static void parse_man(struct mpage *, const struct man_node *); | static void parse_man(struct mpage *, const struct roff_meta *, | ||
static void parse_mdoc(struct mpage *, const struct mdoc_node *); | const struct roff_node *); | ||
static int parse_mdoc_body(struct mpage *, const struct mdoc_node *); | static void parse_mdoc(struct mpage *, const struct roff_meta *, | ||
static int parse_mdoc_head(struct mpage *, const struct mdoc_node *); | const struct roff_node *); | ||
static int parse_mdoc_Fd(struct mpage *, const struct mdoc_node *); | static int parse_mdoc_head(struct mpage *, const struct roff_meta *, | ||
static int parse_mdoc_Fn(struct mpage *, const struct mdoc_node *); | const struct roff_node *); | ||
static int parse_mdoc_Nd(struct mpage *, const struct mdoc_node *); | static int parse_mdoc_Fd(struct mpage *, const struct roff_meta *, | ||
static int parse_mdoc_Nm(struct mpage *, const struct mdoc_node *); | const struct roff_node *); | ||
static int parse_mdoc_Sh(struct mpage *, const struct mdoc_node *); | static void parse_mdoc_fname(struct mpage *, const struct roff_node *); | ||
static int parse_mdoc_Xr(struct mpage *, const struct mdoc_node *); | static int parse_mdoc_Fn(struct mpage *, const struct roff_meta *, | ||
const struct roff_node *); | |||
static int parse_mdoc_Fo(struct mpage *, const struct roff_meta *, | |||
const struct roff_node *); | |||
static int parse_mdoc_Nd(struct mpage *, const struct roff_meta *, | |||
const struct roff_node *); | |||
static int parse_mdoc_Nm(struct mpage *, const struct roff_meta *, | |||
const struct roff_node *); | |||
static int parse_mdoc_Sh(struct mpage *, const struct roff_meta *, | |||
const struct roff_node *); | |||
static int parse_mdoc_Va(struct mpage *, const struct roff_meta *, | |||
const struct roff_node *); | |||
static int parse_mdoc_Xr(struct mpage *, const struct roff_meta *, | |||
const struct roff_node *); | |||
static void putkey(const struct mpage *, char *, uint64_t); | static void putkey(const struct mpage *, char *, uint64_t); | ||
static void putkeys(const struct mpage *, | static void putkeys(const struct mpage *, char *, size_t, uint64_t); | ||
const char *, size_t, uint64_t); | |||
static void putmdockey(const struct mpage *, | static void putmdockey(const struct mpage *, | ||
const struct mdoc_node *, uint64_t); | const struct roff_node *, uint64_t); | ||
static void render_key(struct mchars *, struct str *); | static int render_string(char **, size_t *); | ||
static void say(const char *, const char *, ...); | static void say(const char *, const char *, ...) | ||
static int set_basedir(const char *); | __attribute__((__format__ (printf, 2, 3))); | ||
static int set_basedir(const char *, int); | |||
static int treescan(void); | static int treescan(void); | ||
static size_t utf8(unsigned int, char [7]); | static size_t utf8(unsigned int, char [7]); | ||
static char *progname; | |||
static int nodb; /* no database changes */ | static int nodb; /* no database changes */ | ||
static int quick; /* abort the parse early */ | static int mparse_options; /* abort the parse early */ | ||
static int use_all; /* use all found files */ | static int use_all; /* use all found files */ | ||
static int verb; /* print what we're doing */ | static int debug; /* print what we're doing */ | ||
static int warnings; /* warn about crap */ | static int warnings; /* warn about crap */ | ||
static int write_utf8; /* write UTF-8 output; else ASCII */ | static int write_utf8; /* write UTF-8 output; else ASCII */ | ||
static int exitcode; /* to be returned by main */ | static int exitcode; /* to be returned by main */ | ||
static enum op op; /* operational mode */ | static enum op op; /* operational mode */ | ||
static char basedir[PATH_MAX]; /* current base directory */ | static char basedir[PATH_MAX]; /* current base directory */ | ||
static struct ohash mpages; /* table of distinct manual pages */ | static struct ohash mpages; /* table of distinct manual pages */ | ||
static struct ohash mlinks; /* table of directory entries */ | static struct ohash mlinks; /* table of directory entries */ | ||
static struct ohash names; /* table of all names */ | |||
static struct ohash strings; /* table of all strings */ | static struct ohash strings; /* table of all strings */ | ||
static sqlite3 *db = NULL; /* current database */ | static uint64_t name_mask; | ||
static sqlite3_stmt *stmts[STMT__MAX]; /* current statements */ | |||
static const struct mdoc_handler mdocs[MDOC_MAX] = { | static const struct mdoc_handler mdocs[MDOC_MAX] = { | ||
{ NULL, 0 }, /* Ap */ | { NULL, 0 }, /* Ap */ | ||
|
|
||
{ NULL, TYPE_Ic }, /* Ic */ | { NULL, TYPE_Ic }, /* Ic */ | ||
{ NULL, TYPE_In }, /* In */ | { NULL, TYPE_In }, /* In */ | ||
{ NULL, TYPE_Li }, /* Li */ | { NULL, TYPE_Li }, /* Li */ | ||
{ parse_mdoc_Nd, TYPE_Nd }, /* Nd */ | { parse_mdoc_Nd, 0 }, /* Nd */ | ||
{ parse_mdoc_Nm, TYPE_Nm }, /* Nm */ | { parse_mdoc_Nm, 0 }, /* Nm */ | ||
{ NULL, 0 }, /* Op */ | { NULL, 0 }, /* Op */ | ||
{ NULL, 0 }, /* Ot */ | { NULL, 0 }, /* Ot */ | ||
{ NULL, TYPE_Pa }, /* Pa */ | { NULL, TYPE_Pa }, /* Pa */ | ||
{ NULL, 0 }, /* Rv */ | { NULL, 0 }, /* Rv */ | ||
{ NULL, TYPE_St }, /* St */ | { NULL, TYPE_St }, /* St */ | ||
{ NULL, TYPE_Va }, /* Va */ | { parse_mdoc_Va, TYPE_Va }, /* Va */ | ||
{ parse_mdoc_body, TYPE_Va }, /* Vt */ | { parse_mdoc_Va, TYPE_Vt }, /* Vt */ | ||
{ parse_mdoc_Xr, 0 }, /* Xr */ | { parse_mdoc_Xr, 0 }, /* Xr */ | ||
{ NULL, 0 }, /* %A */ | { NULL, 0 }, /* %A */ | ||
{ NULL, 0 }, /* %B */ | { NULL, 0 }, /* %B */ | ||
|
|
||
{ NULL, 0 }, /* Ux */ | { NULL, 0 }, /* Ux */ | ||
{ NULL, 0 }, /* Xc */ | { NULL, 0 }, /* Xc */ | ||
{ NULL, 0 }, /* Xo */ | { NULL, 0 }, /* Xo */ | ||
{ parse_mdoc_head, 0 }, /* Fo */ | { parse_mdoc_Fo, 0 }, /* Fo */ | ||
{ NULL, 0 }, /* Fc */ | { NULL, 0 }, /* Fc */ | ||
{ NULL, 0 }, /* Oo */ | { NULL, 0 }, /* Oo */ | ||
{ NULL, 0 }, /* Oc */ | { NULL, 0 }, /* Oc */ | ||
|
|
||
{ NULL, 0 }, /* sp */ | { NULL, 0 }, /* sp */ | ||
{ NULL, 0 }, /* %U */ | { NULL, 0 }, /* %U */ | ||
{ NULL, 0 }, /* Ta */ | { NULL, 0 }, /* Ta */ | ||
{ NULL, 0 }, /* ll */ | |||
}; | }; | ||
int | int | ||
main(int argc, char *argv[]) | mandocdb(int argc, char *argv[]) | ||
{ | { | ||
int ch, i; | struct manconf conf; | ||
size_t j, sz; | |||
const char *path_arg; | |||
struct mchars *mc; | |||
struct manpaths dirs; | |||
struct mparse *mp; | struct mparse *mp; | ||
struct ohash_info mpages_info, mlinks_info; | struct dba *dba; | ||
const char *path_arg, *progname; | |||
size_t j, sz; | |||
int ch, i; | |||
memset(stmts, 0, STMT__MAX * sizeof(sqlite3_stmt *)); | #if HAVE_PLEDGE | ||
memset(&dirs, 0, sizeof(struct manpaths)); | if (pledge("stdio rpath wpath cpath fattr flock proc exec", NULL) == -1) { | ||
warn("pledge"); | |||
return (int)MANDOCLEVEL_SYSERR; | |||
} | |||
#endif | |||
mpages_info.alloc = mlinks_info.alloc = hash_alloc; | #if HAVE_SANDBOX_INIT | ||
mpages_info.halloc = mlinks_info.halloc = hash_halloc; | if (sandbox_init(kSBXProfileNoInternet, SANDBOX_NAMED, NULL) == -1) { | ||
mpages_info.hfree = mlinks_info.hfree = hash_free; | warnx("sandbox_init"); | ||
return (int)MANDOCLEVEL_SYSERR; | |||
} | |||
#endif | |||
mpages_info.key_offset = offsetof(struct mpage, inodev); | memset(&conf, 0, sizeof(conf)); | ||
mlinks_info.key_offset = offsetof(struct mlink, file); | |||
progname = strrchr(argv[0], '/'); | |||
if (progname == NULL) | |||
progname = argv[0]; | |||
else | |||
++progname; | |||
/* | /* | ||
* We accept a few different invocations. | * We accept a few different invocations. | ||
* The CHECKOP macro makes sure that invocation styles don't | * The CHECKOP macro makes sure that invocation styles don't | ||
* clobber each other. | * clobber each other. | ||
*/ | */ | ||
#define CHECKOP(_op, _ch) do \ | #define CHECKOP(_op, _ch) do \ | ||
if (OP_DEFAULT != (_op)) { \ | if (OP_DEFAULT != (_op)) { \ | ||
fprintf(stderr, "-%c: Conflicting option\n", (_ch)); \ | warnx("-%c: Conflicting option", (_ch)); \ | ||
goto usage; \ | goto usage; \ | ||
} while (/*CONSTCOND*/0) | } while (/*CONSTCOND*/0) | ||
path_arg = NULL; | path_arg = NULL; | ||
op = OP_DEFAULT; | op = OP_DEFAULT; | ||
while (-1 != (ch = getopt(argc, argv, "aC:d:nQT:tu:vW"))) | while (-1 != (ch = getopt(argc, argv, "aC:Dd:npQT:tu:v"))) | ||
switch (ch) { | switch (ch) { | ||
case ('a'): | case 'a': | ||
use_all = 1; | use_all = 1; | ||
break; | break; | ||
case ('C'): | case 'C': | ||
CHECKOP(op, ch); | CHECKOP(op, ch); | ||
path_arg = optarg; | path_arg = optarg; | ||
op = OP_CONFFILE; | op = OP_CONFFILE; | ||
break; | break; | ||
case ('d'): | case 'D': | ||
debug++; | |||
break; | |||
case 'd': | |||
CHECKOP(op, ch); | CHECKOP(op, ch); | ||
path_arg = optarg; | path_arg = optarg; | ||
op = OP_UPDATE; | op = OP_UPDATE; | ||
break; | break; | ||
case ('n'): | case 'n': | ||
nodb = 1; | nodb = 1; | ||
break; | break; | ||
case ('Q'): | case 'p': | ||
quick = 1; | warnings = 1; | ||
break; | break; | ||
case ('T'): | case 'Q': | ||
mparse_options |= MPARSE_QUICK; | |||
break; | |||
case 'T': | |||
if (strcmp(optarg, "utf8")) { | if (strcmp(optarg, "utf8")) { | ||
fprintf(stderr, "-T%s: Unsupported " | warnx("-T%s: Unsupported output format", | ||
"output format\n", optarg); | optarg); | ||
goto usage; | goto usage; | ||
} | } | ||
write_utf8 = 1; | write_utf8 = 1; | ||
break; | break; | ||
case ('t'): | case 't': | ||
CHECKOP(op, ch); | CHECKOP(op, ch); | ||
dup2(STDOUT_FILENO, STDERR_FILENO); | dup2(STDOUT_FILENO, STDERR_FILENO); | ||
op = OP_TEST; | op = OP_TEST; | ||
nodb = warnings = 1; | nodb = warnings = 1; | ||
break; | break; | ||
case ('u'): | case 'u': | ||
CHECKOP(op, ch); | CHECKOP(op, ch); | ||
path_arg = optarg; | path_arg = optarg; | ||
op = OP_DELETE; | op = OP_DELETE; | ||
break; | break; | ||
case ('v'): | case 'v': | ||
verb++; | /* Compatibility with espie@'s makewhatis. */ | ||
break; | break; | ||
case ('W'): | |||
warnings = 1; | |||
break; | |||
default: | default: | ||
goto usage; | goto usage; | ||
} | } | ||
|
|
||
argc -= optind; | argc -= optind; | ||
argv += optind; | argv += optind; | ||
#if HAVE_PLEDGE | |||
if (nodb) { | |||
if (pledge("stdio rpath", NULL) == -1) { | |||
warn("pledge"); | |||
return (int)MANDOCLEVEL_SYSERR; | |||
} | |||
} | |||
#endif | |||
if (OP_CONFFILE == op && argc > 0) { | if (OP_CONFFILE == op && argc > 0) { | ||
fprintf(stderr, "-C: Too many arguments\n"); | warnx("-C: Too many arguments"); | ||
goto usage; | goto usage; | ||
} | } | ||
exitcode = (int)MANDOCLEVEL_OK; | exitcode = (int)MANDOCLEVEL_OK; | ||
mp = mparse_alloc(MPARSE_AUTO, | mchars_alloc(); | ||
MANDOCLEVEL_FATAL, NULL, NULL, quick); | mp = mparse_alloc(mparse_options, MANDOCLEVEL_BADARG, NULL, NULL); | ||
mc = mchars_alloc(); | mandoc_ohash_init(&mpages, 6, offsetof(struct mpage, inodev)); | ||
mandoc_ohash_init(&mlinks, 6, offsetof(struct mlink, file)); | |||
ohash_init(&mpages, 6, &mpages_info); | |||
ohash_init(&mlinks, 6, &mlinks_info); | |||
if (OP_UPDATE == op || OP_DELETE == op || OP_TEST == op) { | if (OP_UPDATE == op || OP_DELETE == op || OP_TEST == op) { | ||
/* | |||
* Force processing all files. | |||
*/ | |||
use_all = 1; | |||
/* | /* | ||
* All of these deal with a specific directory. | * Most of these deal with a specific directory. | ||
* Jump into that directory then collect files specified | * Jump into that directory first. | ||
* on the command-line. | |||
*/ | */ | ||
if (0 == set_basedir(path_arg)) | if (OP_TEST != op && 0 == set_basedir(path_arg, 1)) | ||
goto out; | goto out; | ||
for (i = 0; i < argc; i++) | |||
filescan(argv[i]); | dba = nodb ? dba_new(128) : dba_read(MANDOC_DB); | ||
if (0 == dbopen(1)) | if (dba != NULL) { | ||
goto out; | /* | ||
if (OP_TEST != op) | * The existing database is usable. Process | ||
dbprune(); | * all files specified on the command-line. | ||
*/ | |||
#if HAVE_PLEDGE | |||
if (!nodb) { | |||
if (pledge("stdio rpath wpath cpath fattr flock", NULL) == -1) { | |||
warn("pledge"); | |||
exitcode = (int)MANDOCLEVEL_SYSERR; | |||
goto out; | |||
} | |||
} | |||
#endif | |||
use_all = 1; | |||
for (i = 0; i < argc; i++) | |||
filescan(argv[i]); | |||
if (nodb == 0) | |||
dbprune(dba); | |||
} else { | |||
/* Database missing or corrupt. */ | |||
say(MANDOC_DB, | |||
"%s: Automatically recreating from scratch", | |||
strerror(errno)); | |||
exitcode = (int)MANDOCLEVEL_OK; | |||
op = OP_DEFAULT; | |||
if (0 == treescan()) | |||
goto out; | |||
dba = dba_new(128); | |||
} | |||
if (OP_DELETE != op) | if (OP_DELETE != op) | ||
mpages_merge(mc, mp); | mpages_merge(dba, mp); | ||
dbclose(1); | if (nodb == 0) | ||
dbwrite(dba); | |||
dba_free(dba); | |||
} else { | } else { | ||
/* | /* | ||
* If we have arguments, use them as our manpaths. | * If we have arguments, use them as our manpaths. | ||
* If we don't, grok from manpath(1) or however else | * If we don't, use man.conf(5). | ||
* manpath_parse() wants to do it. | |||
*/ | */ | ||
if (argc > 0) { | if (argc > 0) { | ||
dirs.paths = mandoc_calloc | conf.manpath.paths = mandoc_reallocarray(NULL, | ||
(argc, sizeof(char *)); | argc, sizeof(char *)); | ||
dirs.sz = (size_t)argc; | conf.manpath.sz = (size_t)argc; | ||
for (i = 0; i < argc; i++) | for (i = 0; i < argc; i++) | ||
dirs.paths[i] = mandoc_strdup(argv[i]); | conf.manpath.paths[i] = mandoc_strdup(argv[i]); | ||
} else | } else | ||
manpath_parse(&dirs, path_arg, NULL, NULL); | manconf_parse(&conf, path_arg, NULL, NULL); | ||
if (conf.manpath.sz == 0) { | |||
exitcode = (int)MANDOCLEVEL_BADARG; | |||
say("", "Empty manpath"); | |||
} | |||
/* | /* | ||
* First scan the tree rooted at a base directory, then | * First scan the tree rooted at a base directory, then | ||
* build a new database and finally move it into place. | * build a new database and finally move it into place. | ||
* Ignore zero-length directories and strip trailing | * Ignore zero-length directories and strip trailing | ||
* slashes. | * slashes. | ||
*/ | */ | ||
for (j = 0; j < dirs.sz; j++) { | for (j = 0; j < conf.manpath.sz; j++) { | ||
sz = strlen(dirs.paths[j]); | sz = strlen(conf.manpath.paths[j]); | ||
if (sz && '/' == dirs.paths[j][sz - 1]) | if (sz && conf.manpath.paths[j][sz - 1] == '/') | ||
dirs.paths[j][--sz] = '\0'; | conf.manpath.paths[j][--sz] = '\0'; | ||
if (0 == sz) | if (0 == sz) | ||
continue; | continue; | ||
if (j) { | if (j) { | ||
ohash_init(&mpages, 6, &mpages_info); | mandoc_ohash_init(&mpages, 6, | ||
ohash_init(&mlinks, 6, &mlinks_info); | offsetof(struct mpage, inodev)); | ||
mandoc_ohash_init(&mlinks, 6, | |||
offsetof(struct mlink, file)); | |||
} | } | ||
if (0 == set_basedir(dirs.paths[j])) | if ( ! set_basedir(conf.manpath.paths[j], argc > 0)) | ||
goto out; | continue; | ||
if (0 == treescan()) | if (0 == treescan()) | ||
goto out; | continue; | ||
if (0 == set_basedir(dirs.paths[j])) | dba = dba_new(128); | ||
goto out; | mpages_merge(dba, mp); | ||
if (0 == dbopen(0)) | if (nodb == 0) | ||
goto out; | dbwrite(dba); | ||
dba_free(dba); | |||
mpages_merge(mc, mp); | if (j + 1 < conf.manpath.sz) { | ||
dbclose(0); | |||
if (j + 1 < dirs.sz) { | |||
mpages_free(); | mpages_free(); | ||
ohash_delete(&mpages); | ohash_delete(&mpages); | ||
ohash_delete(&mlinks); | ohash_delete(&mlinks); | ||
|
|
||
} | } | ||
} | } | ||
out: | out: | ||
set_basedir(NULL); | manconf_free(&conf); | ||
manpath_free(&dirs); | |||
mchars_free(mc); | |||
mparse_free(mp); | mparse_free(mp); | ||
mchars_free(); | |||
mpages_free(); | mpages_free(); | ||
ohash_delete(&mpages); | ohash_delete(&mpages); | ||
ohash_delete(&mlinks); | ohash_delete(&mlinks); | ||
return(exitcode); | return exitcode; | ||
usage: | usage: | ||
fprintf(stderr, "usage: %s [-anQvW] [-C file] [-Tutf8]\n" | progname = getprogname(); | ||
" %s [-anQvW] [-Tutf8] dir ...\n" | fprintf(stderr, "usage: %s [-aDnpQ] [-C file] [-Tutf8]\n" | ||
" %s [-nQvW] [-Tutf8] -d dir [file ...]\n" | " %s [-aDnpQ] [-Tutf8] dir ...\n" | ||
" %s [-nvW] -u dir [file ...]\n" | " %s [-DnpQ] [-Tutf8] -d dir [file ...]\n" | ||
" %s [-Dnp] -u dir [file ...]\n" | |||
" %s [-Q] -t file ...\n", | " %s [-Q] -t file ...\n", | ||
progname, progname, progname, | progname, progname, progname, progname, progname); | ||
progname, progname); | |||
return((int)MANDOCLEVEL_BADARG); | return (int)MANDOCLEVEL_BADARG; | ||
} | } | ||
/* | /* | ||
|
|
||
* If use_all has been specified, grok all files. | * If use_all has been specified, grok all files. | ||
* If not, sanitise paths to the following: | * If not, sanitise paths to the following: | ||
* | * | ||
* [./]man*[/<arch>]/<name>.<section> | * [./]man*[/<arch>]/<name>.<section> | ||
* or | * or | ||
* [./]cat<section>[/<arch>]/<name>.0 | * [./]cat<section>[/<arch>]/<name>.0 | ||
* | * | ||
* TODO: accomodate for multi-language directories. | * TODO: accommodate for multi-language directories. | ||
*/ | */ | ||
static int | static int | ||
treescan(void) | treescan(void) | ||
{ | { | ||
char buf[PATH_MAX]; | |||
FTS *f; | FTS *f; | ||
FTSENT *ff; | FTSENT *ff; | ||
struct mlink *mlink; | struct mlink *mlink; | ||
int dform; | int gzip; | ||
enum form dform; | |||
char *dsec, *arch, *fsec, *cp; | char *dsec, *arch, *fsec, *cp; | ||
const char *path; | const char *path; | ||
const char *argv[2]; | const char *argv[2]; | ||
|
|
||
argv[0] = "."; | argv[0] = "."; | ||
argv[1] = (char *)NULL; | argv[1] = (char *)NULL; | ||
/* | f = fts_open((char * const *)argv, | ||
* Walk through all components under the directory, using the | FTS_PHYSICAL | FTS_NOCHDIR, NULL); | ||
* logical descent of files. | if (f == NULL) { | ||
*/ | |||
f = fts_open((char * const *)argv, FTS_LOGICAL, NULL); | |||
if (NULL == f) { | |||
exitcode = (int)MANDOCLEVEL_SYSERR; | exitcode = (int)MANDOCLEVEL_SYSERR; | ||
say("", NULL); | say("", "&fts_open"); | ||
return(0); | return 0; | ||
} | } | ||
dsec = arch = NULL; | dsec = arch = NULL; | ||
dform = FORM_NONE; | dform = FORM_NONE; | ||
while (NULL != (ff = fts_read(f))) { | while ((ff = fts_read(f)) != NULL) { | ||
path = ff->fts_path + 2; | path = ff->fts_path + 2; | ||
switch (ff->fts_info) { | |||
/* | /* | ||
* Symbolic links require various sanity checks, | |||
* then get handled just like regular files. | |||
*/ | |||
case FTS_SL: | |||
if (realpath(path, buf) == NULL) { | |||
if (warnings) | |||
say(path, "&realpath"); | |||
continue; | |||
} | |||
if (strstr(buf, basedir) != buf | |||
#ifdef HOMEBREWDIR | |||
&& strstr(buf, HOMEBREWDIR) != buf | |||
#endif | |||
) { | |||
if (warnings) say("", | |||
"%s: outside base directory", buf); | |||
continue; | |||
} | |||
/* Use logical inode to avoid mpages dupe. */ | |||
if (stat(path, ff->fts_statp) == -1) { | |||
if (warnings) | |||
say(path, "&stat"); | |||
continue; | |||
} | |||
/* FALLTHROUGH */ | |||
/* | |||
* If we're a regular file, add an mlink by using the | * If we're a regular file, add an mlink by using the | ||
* stored directory data and handling the filename. | * stored directory data and handling the filename. | ||
*/ | */ | ||
if (FTS_F == ff->fts_info) { | case FTS_F: | ||
if (0 == strcmp(path, MANDOC_DB)) | if ( ! strcmp(path, MANDOC_DB)) | ||
continue; | continue; | ||
if ( ! use_all && ff->fts_level < 2) { | if ( ! use_all && ff->fts_level < 2) { | ||
if (warnings) | if (warnings) | ||
say(path, "Extraneous file"); | say(path, "Extraneous file"); | ||
continue; | continue; | ||
} else if (NULL == (fsec = | } | ||
strrchr(ff->fts_name, '.'))) { | gzip = 0; | ||
fsec = NULL; | |||
while (fsec == NULL) { | |||
fsec = strrchr(ff->fts_name, '.'); | |||
if (fsec == NULL || strcmp(fsec+1, "gz")) | |||
break; | |||
gzip = 1; | |||
*fsec = '\0'; | |||
fsec = NULL; | |||
} | |||
if (fsec == NULL) { | |||
if ( ! use_all) { | if ( ! use_all) { | ||
if (warnings) | if (warnings) | ||
say(path, | say(path, | ||
"No filename suffix"); | "No filename suffix"); | ||
continue; | continue; | ||
} | } | ||
} else if (0 == strcmp(++fsec, "html")) { | } else if ( ! strcmp(++fsec, "html")) { | ||
if (warnings) | if (warnings) | ||
say(path, "Skip html"); | say(path, "Skip html"); | ||
continue; | continue; | ||
} else if (0 == strcmp(fsec, "gz")) { | } else if ( ! strcmp(fsec, "ps")) { | ||
if (warnings) | if (warnings) | ||
say(path, "Skip gz"); | |||
continue; | |||
} else if (0 == strcmp(fsec, "ps")) { | |||
if (warnings) | |||
say(path, "Skip ps"); | say(path, "Skip ps"); | ||
continue; | continue; | ||
} else if (0 == strcmp(fsec, "pdf")) { | } else if ( ! strcmp(fsec, "pdf")) { | ||
if (warnings) | if (warnings) | ||
say(path, "Skip pdf"); | say(path, "Skip pdf"); | ||
continue; | continue; | ||
} else if ( ! use_all && | } else if ( ! use_all && | ||
((FORM_SRC == dform && strcmp(fsec, dsec)) || | ((dform == FORM_SRC && | ||
(FORM_CAT == dform && strcmp(fsec, "0")))) { | strncmp(fsec, dsec, strlen(dsec))) || | ||
(dform == FORM_CAT && strcmp(fsec, "0")))) { | |||
if (warnings) | if (warnings) | ||
say(path, "Wrong filename suffix"); | say(path, "Wrong filename suffix"); | ||
continue; | continue; | ||
|
|
||
fsec[-1] = '\0'; | fsec[-1] = '\0'; | ||
mlink = mandoc_calloc(1, sizeof(struct mlink)); | mlink = mandoc_calloc(1, sizeof(struct mlink)); | ||
strlcpy(mlink->file, path, sizeof(mlink->file)); | if (strlcpy(mlink->file, path, | ||
sizeof(mlink->file)) >= | |||
sizeof(mlink->file)) { | |||
say(path, "Filename too long"); | |||
free(mlink); | |||
continue; | |||
} | |||
mlink->dform = dform; | mlink->dform = dform; | ||
mlink->dsec = dsec; | mlink->dsec = dsec; | ||
mlink->arch = arch; | mlink->arch = arch; | ||
mlink->name = ff->fts_name; | mlink->name = ff->fts_name; | ||
mlink->fsec = fsec; | mlink->fsec = fsec; | ||
mlink->gzip = gzip; | |||
mlink_add(mlink, ff->fts_statp); | mlink_add(mlink, ff->fts_statp); | ||
continue; | continue; | ||
} else if (FTS_D != ff->fts_info && | |||
FTS_DP != ff->fts_info) { | case FTS_D: | ||
case FTS_DP: | |||
break; | |||
default: | |||
if (warnings) | if (warnings) | ||
say(path, "Not a regular file"); | say(path, "Not a regular file"); | ||
continue; | continue; | ||
} | } | ||
switch (ff->fts_level) { | switch (ff->fts_level) { | ||
case (0): | case 0: | ||
/* Ignore the root directory. */ | /* Ignore the root directory. */ | ||
break; | break; | ||
case (1): | case 1: | ||
/* | /* | ||
* This might contain manX/ or catX/. | * This might contain manX/ or catX/. | ||
* Try to infer this from the name. | * Try to infer this from the name. | ||
* If we're not in use_all, enforce it. | * If we're not in use_all, enforce it. | ||
*/ | */ | ||
cp = ff->fts_name; | cp = ff->fts_name; | ||
if (FTS_DP == ff->fts_info) | if (ff->fts_info == FTS_DP) { | ||
dform = FORM_NONE; | |||
dsec = NULL; | |||
break; | break; | ||
} | |||
if (0 == strncmp(cp, "man", 3)) { | if ( ! strncmp(cp, "man", 3)) { | ||
dform = FORM_SRC; | dform = FORM_SRC; | ||
dsec = cp + 3; | dsec = cp + 3; | ||
} else if (0 == strncmp(cp, "cat", 3)) { | } else if ( ! strncmp(cp, "cat", 3)) { | ||
dform = FORM_CAT; | dform = FORM_CAT; | ||
dsec = cp + 3; | dsec = cp + 3; | ||
} else { | } else { | ||
|
|
||
dsec = NULL; | dsec = NULL; | ||
} | } | ||
if (NULL != dsec || use_all) | if (dsec != NULL || use_all) | ||
break; | break; | ||
if (warnings) | if (warnings) | ||
say(path, "Unknown directory part"); | say(path, "Unknown directory part"); | ||
fts_set(f, ff, FTS_SKIP); | fts_set(f, ff, FTS_SKIP); | ||
break; | break; | ||
case (2): | case 2: | ||
/* | /* | ||
* Possibly our architecture. | * Possibly our architecture. | ||
* If we're descending, keep tabs on it. | * If we're descending, keep tabs on it. | ||
*/ | */ | ||
if (FTS_DP != ff->fts_info && NULL != dsec) | if (ff->fts_info != FTS_DP && dsec != NULL) | ||
arch = ff->fts_name; | arch = ff->fts_name; | ||
else | else | ||
arch = NULL; | arch = NULL; | ||
break; | break; | ||
default: | default: | ||
if (FTS_DP == ff->fts_info || use_all) | if (ff->fts_info == FTS_DP || use_all) | ||
break; | break; | ||
if (warnings) | if (warnings) | ||
say(path, "Extraneous directory part"); | say(path, "Extraneous directory part"); | ||
|
|
||
} | } | ||
fts_close(f); | fts_close(f); | ||
return(1); | return 1; | ||
} | } | ||
/* | /* | ||
|
|
||
* Try to infer the manual section, architecture, and page name from the | * Try to infer the manual section, architecture, and page name from the | ||
* path, assuming it looks like | * path, assuming it looks like | ||
* | * | ||
* [./]man*[/<arch>]/<name>.<section> | * [./]man*[/<arch>]/<name>.<section> | ||
* or | * or | ||
* [./]cat<section>[/<arch>]/<name>.0 | * [./]cat<section>[/<arch>]/<name>.0 | ||
* | * | ||
|
|
||
if (0 == strncmp(file, "./", 2)) | if (0 == strncmp(file, "./", 2)) | ||
file += 2; | file += 2; | ||
/* | |||
* We have to do lstat(2) before realpath(3) loses | |||
* the information whether this is a symbolic link. | |||
* We need to know that because for symbolic links, | |||
* we want to use the orginal file name, while for | |||
* regular files, we want to use the real path. | |||
*/ | |||
if (-1 == lstat(file, &st)) { | |||
exitcode = (int)MANDOCLEVEL_BADARG; | |||
say(file, "&lstat"); | |||
return; | |||
} else if (0 == ((S_IFREG | S_IFLNK) & st.st_mode)) { | |||
exitcode = (int)MANDOCLEVEL_BADARG; | |||
say(file, "Not a regular file"); | |||
return; | |||
} | |||
/* | |||
* We have to resolve the file name to the real path | |||
* in any case for the base directory check. | |||
*/ | |||
if (NULL == realpath(file, buf)) { | if (NULL == realpath(file, buf)) { | ||
exitcode = (int)MANDOCLEVEL_BADARG; | exitcode = (int)MANDOCLEVEL_BADARG; | ||
say(file, NULL); | say(file, "&realpath"); | ||
return; | return; | ||
} | } | ||
if (strstr(buf, basedir) == buf) | if (OP_TEST == op) | ||
start = buf + strlen(basedir) + 1; | |||
else if (OP_TEST == op) | |||
start = buf; | start = buf; | ||
else if (strstr(buf, basedir) == buf) | |||
start = buf + strlen(basedir); | |||
#ifdef HOMEBREWDIR | |||
else if (strstr(buf, HOMEBREWDIR) == buf) | |||
start = buf; | |||
#endif | |||
else { | else { | ||
exitcode = (int)MANDOCLEVEL_BADARG; | exitcode = (int)MANDOCLEVEL_BADARG; | ||
say("", "%s: outside base directory", buf); | say("", "%s: outside base directory", buf); | ||
return; | return; | ||
} | } | ||
if (-1 == stat(buf, &st)) { | /* | ||
exitcode = (int)MANDOCLEVEL_BADARG; | * Now we are sure the file is inside our tree. | ||
say(file, NULL); | * If it is a symbolic link, ignore the real path | ||
return; | * and use the original name. | ||
} else if ( ! (S_IFREG & st.st_mode)) { | * This implies passing stuff like "cat1/../man1/foo.1" | ||
exitcode = (int)MANDOCLEVEL_BADARG; | * on the command line won't work. So don't do that. | ||
say(file, "Not a regular file"); | * Note the stat(2) can still fail if the link target | ||
return; | * doesn't exist. | ||
*/ | |||
if (S_IFLNK & st.st_mode) { | |||
if (-1 == stat(buf, &st)) { | |||
exitcode = (int)MANDOCLEVEL_BADARG; | |||
say(file, "&stat"); | |||
return; | |||
} | |||
if (strlcpy(buf, file, sizeof(buf)) >= sizeof(buf)) { | |||
say(file, "Filename too long"); | |||
return; | |||
} | |||
start = buf; | |||
if (OP_TEST != op && strstr(buf, basedir) == buf) | |||
start += strlen(basedir); | |||
} | } | ||
mlink = mandoc_calloc(1, sizeof(struct mlink)); | mlink = mandoc_calloc(1, sizeof(struct mlink)); | ||
strlcpy(mlink->file, start, sizeof(mlink->file)); | mlink->dform = FORM_NONE; | ||
if (strlcpy(mlink->file, start, sizeof(mlink->file)) >= | |||
sizeof(mlink->file)) { | |||
say(start, "Filename too long"); | |||
free(mlink); | |||
return; | |||
} | |||
/* | /* | ||
* First try to guess our directory structure. | * First try to guess our directory structure. | ||
|
|
||
assert(NULL == ohash_find(&mlinks, slot)); | assert(NULL == ohash_find(&mlinks, slot)); | ||
ohash_insert(&mlinks, slot, mlink); | ohash_insert(&mlinks, slot, mlink); | ||
memset(&inodev, 0, sizeof(inodev)); /* Clear padding. */ | |||
inodev.st_ino = st->st_ino; | inodev.st_ino = st->st_ino; | ||
inodev.st_dev = st->st_dev; | inodev.st_dev = st->st_dev; | ||
slot = ohash_lookup_memory(&mpages, (char *)&inodev, | slot = ohash_lookup_memory(&mpages, (char *)&inodev, | ||
|
|
||
mpage = mandoc_calloc(1, sizeof(struct mpage)); | mpage = mandoc_calloc(1, sizeof(struct mpage)); | ||
mpage->inodev.st_ino = inodev.st_ino; | mpage->inodev.st_ino = inodev.st_ino; | ||
mpage->inodev.st_dev = inodev.st_dev; | mpage->inodev.st_dev = inodev.st_dev; | ||
mpage->form = FORM_NONE; | |||
ohash_insert(&mpages, slot, mpage); | ohash_insert(&mpages, slot, mpage); | ||
} else | } else | ||
mlink->next = mpage->mlinks; | mlink->next = mpage->mlinks; | ||
mpage->mlinks = mlink; | mpage->mlinks = mlink; | ||
mlink->mpage = mpage; | |||
} | } | ||
static void | static void | ||
|
|
||
mpage->form = FORM_NONE; | mpage->form = FORM_NONE; | ||
goto nextlink; | goto nextlink; | ||
} | } | ||
if (strlcpy(buf, mlink->file, PATH_MAX) >= PATH_MAX) { | (void)strlcpy(buf, mlink->file, sizeof(buf)); | ||
if (warnings) | |||
say(mlink->file, "Filename too long"); | |||
goto nextlink; | |||
} | |||
bufp = strstr(buf, "cat"); | bufp = strstr(buf, "cat"); | ||
assert(NULL != bufp); | assert(NULL != bufp); | ||
memcpy(bufp, "man", 3); | memcpy(bufp, "man", 3); | ||
if (NULL != (bufp = strrchr(buf, '.'))) | if (NULL != (bufp = strrchr(buf, '.'))) | ||
*++bufp = '\0'; | *++bufp = '\0'; | ||
strlcat(buf, mlink->dsec, PATH_MAX); | (void)strlcat(buf, mlink->dsec, sizeof(buf)); | ||
if (NULL == ohash_find(&mlinks, | if (NULL == ohash_find(&mlinks, | ||
ohash_qlookup(&mlinks, buf))) | ohash_qlookup(&mlinks, buf))) | ||
goto nextlink; | goto nextlink; | ||
if (warnings) | if (warnings) | ||
say(mlink->file, "Man source exists: %s", buf); | say(mlink->file, "Man source exists: %s", buf); | ||
|
|
||
} | } | ||
} | } | ||
static int | static void | ||
mlink_check(struct mpage *mpage, struct mlink *mlink) | mlink_check(struct mpage *mpage, struct mlink *mlink) | ||
{ | { | ||
int match; | struct str *str; | ||
unsigned int slot; | |||
match = 1; | |||
/* | /* | ||
* Check whether the manual section given in a file | * Check whether the manual section given in a file | ||
* agrees with the directory where the file is located. | * agrees with the directory where the file is located. | ||
|
|
||
*/ | */ | ||
if (FORM_SRC == mpage->form && | if (FORM_SRC == mpage->form && | ||
strcasecmp(mpage->sec, mlink->dsec)) { | strcasecmp(mpage->sec, mlink->dsec)) | ||
match = 0; | |||
say(mlink->file, "Section \"%s\" manual in %s directory", | say(mlink->file, "Section \"%s\" manual in %s directory", | ||
mpage->sec, mlink->dsec); | mpage->sec, mlink->dsec); | ||
} | |||
/* | /* | ||
* Manual page directories exist for each kernel | * Manual page directories exist for each kernel | ||
|
|
||
* architectures. | * architectures. | ||
* A few manuals are even shared across completely | * A few manuals are even shared across completely | ||
* different architectures, for example fdformat(1) | * different architectures, for example fdformat(1) | ||
* on amd64, i386, sparc, and sparc64. | * on amd64, i386, and sparc64. | ||
*/ | */ | ||
if (strcasecmp(mpage->arch, mlink->arch)) { | if (strcasecmp(mpage->arch, mlink->arch)) | ||
match = 0; | |||
say(mlink->file, "Architecture \"%s\" manual in " | say(mlink->file, "Architecture \"%s\" manual in " | ||
"\"%s\" directory", mpage->arch, mlink->arch); | "\"%s\" directory", mpage->arch, mlink->arch); | ||
} | |||
if (strcasecmp(mpage->title, mlink->name)) | /* | ||
match = 0; | * XXX | ||
* parse_cat() doesn't set NAME_TITLE yet. | |||
*/ | |||
return(match); | if (FORM_CAT == mpage->form) | ||
return; | |||
/* | |||
* Check whether this mlink | |||
* appears as a name in the NAME section. | |||
*/ | |||
slot = ohash_qlookup(&names, mlink->name); | |||
str = ohash_find(&names, slot); | |||
assert(NULL != str); | |||
if ( ! (NAME_TITLE & str->mask)) | |||
say(mlink->file, "Name missing in NAME section"); | |||
} | } | ||
/* | /* | ||
|
|
||
* and filename to determine whether the file is parsable or not. | * and filename to determine whether the file is parsable or not. | ||
*/ | */ | ||
static void | static void | ||
mpages_merge(struct mchars *mc, struct mparse *mp) | mpages_merge(struct dba *dba, struct mparse *mp) | ||
{ | { | ||
struct ohash_info str_info; | struct mpage **mplist, *mpage, *mpage_dest; | ||
struct mpage *mpage; | struct mlink *mlink, *mlink_dest; | ||
struct mlink *mlink; | struct roff_man *man; | ||
struct mdoc *mdoc; | char *sodest; | ||
struct man *man; | |||
char *cp; | char *cp; | ||
int match; | int fd; | ||
unsigned int pslot; | unsigned int ip, npages, pslot; | ||
enum mandoclevel lvl; | |||
str_info.alloc = hash_alloc; | npages = ohash_entries(&mpages); | ||
str_info.halloc = hash_halloc; | mplist = mandoc_reallocarray(NULL, npages, sizeof(*mplist)); | ||
str_info.hfree = hash_free; | ip = 0; | ||
str_info.key_offset = offsetof(struct str, key); | |||
if (0 == nodb) | |||
SQL_EXEC("BEGIN TRANSACTION"); | |||
mpage = ohash_first(&mpages, &pslot); | mpage = ohash_first(&mpages, &pslot); | ||
while (NULL != mpage) { | while (mpage != NULL) { | ||
mlinks_undupe(mpage); | mlinks_undupe(mpage); | ||
if (NULL == mpage->mlinks) { | if (mpage->mlinks != NULL) | ||
mpage = ohash_next(&mpages, &pslot); | mplist[ip++] = mpage; | ||
continue; | mpage = ohash_next(&mpages, &pslot); | ||
} | } | ||
npages = ip; | |||
qsort(mplist, npages, sizeof(*mplist), mpages_compare); | |||
ohash_init(&strings, 6, &str_info); | for (ip = 0; ip < npages; ip++) { | ||
mpage = mplist[ip]; | |||
mlink = mpage->mlinks; | |||
name_mask = NAME_MASK; | |||
mandoc_ohash_init(&names, 4, offsetof(struct str, key)); | |||
mandoc_ohash_init(&strings, 6, offsetof(struct str, key)); | |||
mparse_reset(mp); | mparse_reset(mp); | ||
mdoc = NULL; | |||
man = NULL; | man = NULL; | ||
sodest = NULL; | |||
if ((fd = mparse_open(mp, mlink->file)) == -1) { | |||
say(mlink->file, "&open"); | |||
goto nextpage; | |||
} | |||
/* | /* | ||
* Try interpreting the file as mdoc(7) or man(7) | * Interpret the file as mdoc(7) or man(7) source | ||
* source code, unless it is already known to be | * code, unless it is known to be formatted. | ||
* formatted. Fall back to formatted mode. | |||
*/ | */ | ||
if (FORM_CAT != mpage->mlinks->dform || | if (mlink->dform != FORM_CAT || mlink->fform != FORM_CAT) { | ||
FORM_CAT != mpage->mlinks->fform) { | mparse_readfd(mp, fd, mlink->file); | ||
lvl = mparse_readfd(mp, -1, mpage->mlinks->file); | close(fd); | ||
if (lvl < MANDOCLEVEL_FATAL) | mparse_result(mp, &man, &sodest); | ||
mparse_result(mp, &mdoc, &man); | |||
} | } | ||
if (NULL != mdoc) { | if (sodest != NULL) { | ||
mlink_dest = ohash_find(&mlinks, | |||
ohash_qlookup(&mlinks, sodest)); | |||
if (mlink_dest == NULL) { | |||
mandoc_asprintf(&cp, "%s.gz", sodest); | |||
mlink_dest = ohash_find(&mlinks, | |||
ohash_qlookup(&mlinks, cp)); | |||
free(cp); | |||
} | |||
if (mlink_dest != NULL) { | |||
/* The .so target exists. */ | |||
mpage_dest = mlink_dest->mpage; | |||
while (1) { | |||
mlink->mpage = mpage_dest; | |||
/* | |||
* If the target was already | |||
* processed, add the links | |||
* to the database now. | |||
* Otherwise, this will | |||
* happen when we come | |||
* to the target. | |||
*/ | |||
if (mpage_dest->dba != NULL) | |||
dbadd_mlink(mlink); | |||
if (mlink->next == NULL) | |||
break; | |||
mlink = mlink->next; | |||
} | |||
/* Move all links to the target. */ | |||
mlink->next = mlink_dest->next; | |||
mlink_dest->next = mpage->mlinks; | |||
mpage->mlinks = NULL; | |||
} | |||
goto nextpage; | |||
} else if (man != NULL && man->macroset == MACROSET_MDOC) { | |||
mdoc_validate(man); | |||
mpage->form = FORM_SRC; | mpage->form = FORM_SRC; | ||
mpage->sec = | mpage->sec = man->meta.msec; | ||
mandoc_strdup(mdoc_meta(mdoc)->msec); | mpage->sec = mandoc_strdup( | ||
mpage->arch = mdoc_meta(mdoc)->arch; | mpage->sec == NULL ? "" : mpage->sec); | ||
mpage->arch = man->meta.arch; | |||
mpage->arch = mandoc_strdup( | mpage->arch = mandoc_strdup( | ||
NULL == mpage->arch ? "" : mpage->arch); | mpage->arch == NULL ? "" : mpage->arch); | ||
mpage->title = | mpage->title = mandoc_strdup(man->meta.title); | ||
mandoc_strdup(mdoc_meta(mdoc)->title); | } else if (man != NULL && man->macroset == MACROSET_MAN) { | ||
} else if (NULL != man) { | man_validate(man); | ||
mpage->form = FORM_SRC; | mpage->form = FORM_SRC; | ||
mpage->sec = | mpage->sec = mandoc_strdup(man->meta.msec); | ||
mandoc_strdup(man_meta(man)->msec); | mpage->arch = mandoc_strdup(mlink->arch); | ||
mpage->arch = | mpage->title = mandoc_strdup(man->meta.title); | ||
mandoc_strdup(mpage->mlinks->arch); | |||
mpage->title = | |||
mandoc_strdup(man_meta(man)->title); | |||
} else { | } else { | ||
mpage->form = FORM_CAT; | mpage->form = FORM_CAT; | ||
mpage->sec = | mpage->sec = mandoc_strdup(mlink->dsec); | ||
mandoc_strdup(mpage->mlinks->dsec); | mpage->arch = mandoc_strdup(mlink->arch); | ||
mpage->arch = | mpage->title = mandoc_strdup(mlink->name); | ||
mandoc_strdup(mpage->mlinks->arch); | |||
mpage->title = | |||
mandoc_strdup(mpage->mlinks->name); | |||
} | } | ||
putkey(mpage, mpage->sec, TYPE_sec); | |||
putkey(mpage, '\0' == *mpage->arch ? | |||
"any" : mpage->arch, TYPE_arch); | |||
for (mlink = mpage->mlinks; mlink; mlink = mlink->next) { | assert(mpage->desc == NULL); | ||
if ('\0' != *mlink->dsec) | if (man != NULL && man->macroset == MACROSET_MDOC) | ||
putkey(mpage, mlink->dsec, TYPE_sec); | parse_mdoc(mpage, &man->meta, man->first); | ||
if ('\0' != *mlink->fsec) | else if (man != NULL) | ||
putkey(mpage, mlink->fsec, TYPE_sec); | parse_man(mpage, &man->meta, man->first); | ||
putkey(mpage, '\0' == *mlink->arch ? | else | ||
"any" : mlink->arch, TYPE_arch); | parse_cat(mpage, fd); | ||
putkey(mpage, mlink->name, TYPE_Nm); | if (mpage->desc == NULL) | ||
} | mpage->desc = mandoc_strdup(mpage->mlinks->name); | ||
if (warnings && !use_all) { | if (warnings && !use_all) | ||
match = 0; | |||
for (mlink = mpage->mlinks; mlink; | for (mlink = mpage->mlinks; mlink; | ||
mlink = mlink->next) | mlink = mlink->next) | ||
if (mlink_check(mpage, mlink)) | mlink_check(mpage, mlink); | ||
match = 1; | |||
} else | |||
match = 1; | |||
if (NULL != mdoc) { | dbadd(dba, mpage); | ||
if (NULL != (cp = mdoc_meta(mdoc)->name)) | mlink = mpage->mlinks; | ||
putkey(mpage, cp, TYPE_Nm); | |||
assert(NULL == mpage->desc); | |||
parse_mdoc(mpage, mdoc_node(mdoc)); | |||
putkey(mpage, NULL != mpage->desc ? | |||
mpage->desc : mpage->mlinks->name, TYPE_Nd); | |||
} else if (NULL != man) | |||
parse_man(mpage, man_node(man)); | |||
else | |||
parse_cat(mpage); | |||
dbadd(mpage, mc); | nextpage: | ||
ohash_delete(&strings); | ohash_delete(&strings); | ||
mpage = ohash_next(&mpages, &pslot); | ohash_delete(&names); | ||
} | } | ||
free(mplist); | |||
} | |||
if (0 == nodb) | int | ||
SQL_EXEC("END TRANSACTION"); | mpages_compare(const void *vp1, const void *vp2) | ||
{ | |||
const struct mpage *mp1, *mp2; | |||
mp1 = *(const struct mpage **)vp1; | |||
mp2 = *(const struct mpage **)vp2; | |||
return strcmp(mp1->mlinks->file, mp2->mlinks->file); | |||
} | } | ||
static void | static void | ||
parse_cat(struct mpage *mpage) | parse_cat(struct mpage *mpage, int fd) | ||
{ | { | ||
FILE *stream; | FILE *stream; | ||
char *line, *p, *title; | char *line, *p, *title; | ||
size_t len, plen, titlesz; | size_t linesz, plen, titlesz; | ||
ssize_t len; | |||
int offs; | |||
if (NULL == (stream = fopen(mpage->mlinks->file, "r"))) { | stream = (-1 == fd) ? | ||
fopen(mpage->mlinks->file, "r") : | |||
fdopen(fd, "r"); | |||
if (NULL == stream) { | |||
if (-1 != fd) | |||
close(fd); | |||
if (warnings) | if (warnings) | ||
say(mpage->mlinks->file, NULL); | say(mpage->mlinks->file, "&fopen"); | ||
return; | return; | ||
} | } | ||
line = NULL; | |||
linesz = 0; | |||
/* Skip to first blank line. */ | /* Skip to first blank line. */ | ||
while (NULL != (line = fgetln(stream, &len))) | while (getline(&line, &linesz, stream) != -1) | ||
if ('\n' == *line) | if (*line == '\n') | ||
break; | break; | ||
/* | /* | ||
|
|
||
* is the first section header. Skip to it. | * is the first section header. Skip to it. | ||
*/ | */ | ||
while (NULL != (line = fgetln(stream, &len))) | while (getline(&line, &linesz, stream) != -1) | ||
if ('\n' != *line && ' ' != *line) | if (*line != '\n' && *line != ' ') | ||
break; | break; | ||
/* | /* | ||
* Read up until the next section into a buffer. | * Read up until the next section into a buffer. | ||
* Strip the leading and trailing newline from each read line, | * Strip the leading and trailing newline from each read line, | ||
|
|
||
titlesz = 0; | titlesz = 0; | ||
title = NULL; | title = NULL; | ||
while (NULL != (line = fgetln(stream, &len))) { | while ((len = getline(&line, &linesz, stream)) != -1) { | ||
if (' ' != *line || '\n' != line[len - 1]) | if (*line != ' ') | ||
break; | break; | ||
while (len > 0 && isspace((unsigned char)*line)) { | offs = 0; | ||
line++; | while (isspace((unsigned char)line[offs])) | ||
len--; | offs++; | ||
} | if (line[offs] == '\0') | ||
if (1 == len) | |||
continue; | continue; | ||
title = mandoc_realloc(title, titlesz + len); | title = mandoc_realloc(title, titlesz + len - offs); | ||
memcpy(title + titlesz, line, len); | memcpy(title + titlesz, line + offs, len - offs); | ||
titlesz += len; | titlesz += len - offs; | ||
title[titlesz - 1] = ' '; | title[titlesz - 1] = ' '; | ||
} | } | ||
free(line); | |||
/* | /* | ||
* If no page content can be found, or the input line | * If no page content can be found, or the input line | ||
|
|
||
if (warnings) | if (warnings) | ||
say(mpage->mlinks->file, | say(mpage->mlinks->file, | ||
"Cannot find NAME section"); | "Cannot find NAME section"); | ||
assert(NULL == mpage->desc); | |||
mpage->desc = mandoc_strdup(mpage->mlinks->name); | |||
putkey(mpage, mpage->mlinks->name, TYPE_Nd); | |||
fclose(stream); | fclose(stream); | ||
free(title); | free(title); | ||
return; | return; | ||
} | } | ||
title = mandoc_realloc(title, titlesz + 1); | title[titlesz - 1] = '\0'; | ||
title[titlesz] = '\0'; | |||
/* | /* | ||
* Skip to the first dash. | * Skip to the first dash. | ||
|
|
||
if (0 == len) { | if (0 == len) { | ||
memmove(line, line + 1, plen--); | memmove(line, line + 1, plen--); | ||
continue; | continue; | ||
} | } | ||
memmove(line - 1, line + 1, plen - len); | memmove(line - 1, line + 1, plen - len); | ||
plen -= 2; | plen -= 2; | ||
} | } | ||
assert(NULL == mpage->desc); | |||
mpage->desc = mandoc_strdup(p); | mpage->desc = mandoc_strdup(p); | ||
putkey(mpage, mpage->desc, TYPE_Nd); | |||
fclose(stream); | fclose(stream); | ||
free(title); | free(title); | ||
} | } | ||
|
|
||
static void | static void | ||
putkey(const struct mpage *mpage, char *value, uint64_t type) | putkey(const struct mpage *mpage, char *value, uint64_t type) | ||
{ | { | ||
char *cp; | |||
assert(NULL != value); | |||
if (TYPE_arch == type) | |||
for (cp = value; *cp; cp++) | |||
if (isupper((unsigned char)*cp)) | |||
*cp = _tolower((unsigned char)*cp); | |||
putkeys(mpage, value, strlen(value), type); | putkeys(mpage, value, strlen(value), type); | ||
} | } | ||
|
|
||
*/ | */ | ||
static void | static void | ||
putmdockey(const struct mpage *mpage, | putmdockey(const struct mpage *mpage, | ||
const struct mdoc_node *n, uint64_t m) | const struct roff_node *n, uint64_t m) | ||
{ | { | ||
for ( ; NULL != n; n = n->next) { | for ( ; NULL != n; n = n->next) { | ||
if (NULL != n->child) | if (NULL != n->child) | ||
putmdockey(mpage, n->child, m); | putmdockey(mpage, n->child, m); | ||
if (MDOC_TEXT == n->type) | if (n->type == ROFFT_TEXT) | ||
putkey(mpage, n->string, m); | putkey(mpage, n->string, m); | ||
} | } | ||
} | } | ||
static void | static void | ||
parse_man(struct mpage *mpage, const struct man_node *n) | parse_man(struct mpage *mpage, const struct roff_meta *meta, | ||
const struct roff_node *n) | |||
{ | { | ||
const struct man_node *head, *body; | const struct roff_node *head, *body; | ||
char *start, *sv, *title; | char *start, *title; | ||
char byte; | char byte; | ||
size_t sz, titlesz; | size_t sz; | ||
if (NULL == n) | if (n == NULL) | ||
return; | return; | ||
/* | /* | ||
|
|
||
* the correct section or not. | * the correct section or not. | ||
*/ | */ | ||
if (MAN_BODY == n->type && MAN_SH == n->tok) { | if (n->type == ROFFT_BODY && n->tok == MAN_SH) { | ||
body = n; | body = n; | ||
assert(body->parent); | if ((head = body->parent->head) != NULL && | ||
if (NULL != (head = body->parent->head) && | (head = head->child) != NULL && | ||
1 == head->nchild && | head->next == NULL && | ||
NULL != (head = (head->child)) && | head->type == ROFFT_TEXT && | ||
MAN_TEXT == head->type && | strcmp(head->string, "NAME") == 0 && | ||
0 == strcmp(head->string, "NAME") && | body->child != NULL) { | ||
NULL != (body = body->child) && | |||
MAN_TEXT == body->type) { | |||
title = NULL; | |||
titlesz = 0; | |||
/* | /* | ||
* Suck the entire NAME section into memory. | * Suck the entire NAME section into memory. | ||
* Yes, we might run away. | * Yes, we might run away. | ||
|
|
||
* NAME sections over many lines. | * NAME sections over many lines. | ||
*/ | */ | ||
for ( ; NULL != body; body = body->next) { | title = NULL; | ||
if (MAN_TEXT != body->type) | deroff(&title, body); | ||
break; | |||
if (0 == (sz = strlen(body->string))) | |||
continue; | |||
title = mandoc_realloc | |||
(title, titlesz + sz + 1); | |||
memcpy(title + titlesz, body->string, sz); | |||
titlesz += sz + 1; | |||
title[titlesz - 1] = ' '; | |||
} | |||
if (NULL == title) | if (NULL == title) | ||
return; | return; | ||
title = mandoc_realloc(title, titlesz + 1); | /* | ||
title[titlesz] = '\0'; | |||
/* Skip leading space. */ | |||
sv = title; | |||
while (isspace((unsigned char)*sv)) | |||
sv++; | |||
if (0 == (sz = strlen(sv))) { | |||
free(title); | |||
return; | |||
} | |||
/* Erase trailing space. */ | |||
start = &sv[sz - 1]; | |||
while (start > sv && isspace((unsigned char)*start)) | |||
*start-- = '\0'; | |||
if (start == sv) { | |||
free(title); | |||
return; | |||
} | |||
start = sv; | |||
/* | |||
* Go through a special heuristic dance here. | * Go through a special heuristic dance here. | ||
* Conventionally, one or more manual names are | * Conventionally, one or more manual names are | ||
* comma-specified prior to a whitespace, then a | * comma-specified prior to a whitespace, then a | ||
|
|
||
* the name parts here. | * the name parts here. | ||
*/ | */ | ||
start = title; | |||
for ( ;; ) { | for ( ;; ) { | ||
sz = strcspn(start, " ,"); | sz = strcspn(start, " ,"); | ||
if ('\0' == start[sz]) | if ('\0' == start[sz]) | ||
|
|
||
('\\' == start[0] && '-' == start[1])) | ('\\' == start[0] && '-' == start[1])) | ||
break; | break; | ||
putkey(mpage, start, TYPE_Nm); | putkey(mpage, start, NAME_TITLE); | ||
if ( ! (mpage->name_head_done || | |||
strcasecmp(start, meta->title))) { | |||
putkey(mpage, start, NAME_HEAD); | |||
mpage->name_head_done = 1; | |||
} | |||
if (' ' == byte) { | if (' ' == byte) { | ||
start += sz + 1; | start += sz + 1; | ||
|
|
||
start++; | start++; | ||
} | } | ||
if (sv == start) { | if (start == title) { | ||
putkey(mpage, start, TYPE_Nm); | putkey(mpage, start, NAME_TITLE); | ||
if ( ! (mpage->name_head_done || | |||
strcasecmp(start, meta->title))) { | |||
putkey(mpage, start, NAME_HEAD); | |||
mpage->name_head_done = 1; | |||
} | |||
free(title); | free(title); | ||
return; | return; | ||
} | } | ||
|
|
||
while (' ' == *start) | while (' ' == *start) | ||
start++; | start++; | ||
assert(NULL == mpage->desc); | |||
mpage->desc = mandoc_strdup(start); | mpage->desc = mandoc_strdup(start); | ||
putkey(mpage, mpage->desc, TYPE_Nd); | |||
free(title); | free(title); | ||
return; | return; | ||
} | } | ||
|
|
||
for (n = n->child; n; n = n->next) { | for (n = n->child; n; n = n->next) { | ||
if (NULL != mpage->desc) | if (NULL != mpage->desc) | ||
break; | break; | ||
parse_man(mpage, n); | parse_man(mpage, meta, n); | ||
} | } | ||
} | } | ||
static void | static void | ||
parse_mdoc(struct mpage *mpage, const struct mdoc_node *n) | parse_mdoc(struct mpage *mpage, const struct roff_meta *meta, | ||
const struct roff_node *n) | |||
{ | { | ||
assert(NULL != n); | assert(NULL != n); | ||
for (n = n->child; NULL != n; n = n->next) { | for (n = n->child; NULL != n; n = n->next) { | ||
switch (n->type) { | switch (n->type) { | ||
case (MDOC_ELEM): | case ROFFT_ELEM: | ||
/* FALLTHROUGH */ | case ROFFT_BLOCK: | ||
case (MDOC_BLOCK): | case ROFFT_HEAD: | ||
/* FALLTHROUGH */ | case ROFFT_BODY: | ||
case (MDOC_HEAD): | case ROFFT_TAIL: | ||
/* FALLTHROUGH */ | |||
case (MDOC_BODY): | |||
/* FALLTHROUGH */ | |||
case (MDOC_TAIL): | |||
if (NULL != mdocs[n->tok].fp) | if (NULL != mdocs[n->tok].fp) | ||
if (0 == (*mdocs[n->tok].fp)(mpage, n)) | if (0 == (*mdocs[n->tok].fp)(mpage, meta, n)) | ||
break; | break; | ||
if (mdocs[n->tok].mask) | if (mdocs[n->tok].mask) | ||
putmdockey(mpage, n->child, | putmdockey(mpage, n->child, | ||
mdocs[n->tok].mask); | mdocs[n->tok].mask); | ||
break; | break; | ||
default: | default: | ||
assert(MDOC_ROOT != n->type); | assert(n->type != ROFFT_ROOT); | ||
continue; | continue; | ||
} | } | ||
if (NULL != n->child) | if (NULL != n->child) | ||
parse_mdoc(mpage, n); | parse_mdoc(mpage, meta, n); | ||
} | } | ||
} | } | ||
static int | static int | ||
parse_mdoc_Fd(struct mpage *mpage, const struct mdoc_node *n) | parse_mdoc_Fd(struct mpage *mpage, const struct roff_meta *meta, | ||
const struct roff_node *n) | |||
{ | { | ||
const char *start, *end; | char *start, *end; | ||
size_t sz; | size_t sz; | ||
if (SEC_SYNOPSIS != n->sec || | if (SEC_SYNOPSIS != n->sec || | ||
NULL == (n = n->child) || | NULL == (n = n->child) || | ||
MDOC_TEXT != n->type) | n->type != ROFFT_TEXT) | ||
return(0); | return 0; | ||
/* | /* | ||
* Only consider those `Fd' macro fields that begin with an | * Only consider those `Fd' macro fields that begin with an | ||
|
|
||
*/ | */ | ||
if (strcmp("#include", n->string)) | if (strcmp("#include", n->string)) | ||
return(0); | return 0; | ||
if (NULL == (n = n->next) || MDOC_TEXT != n->type) | if ((n = n->next) == NULL || n->type != ROFFT_TEXT) | ||
return(0); | return 0; | ||
/* | /* | ||
* Strip away the enclosing angle brackets and make sure we're | * Strip away the enclosing angle brackets and make sure we're | ||
|
|
||
start++; | start++; | ||
if (0 == (sz = strlen(start))) | if (0 == (sz = strlen(start))) | ||
return(0); | return 0; | ||
end = &start[(int)sz - 1]; | end = &start[(int)sz - 1]; | ||
if ('>' == *end || '"' == *end) | if ('>' == *end || '"' == *end) | ||
|
|
||
if (end > start) | if (end > start) | ||
putkeys(mpage, start, end - start + 1, TYPE_In); | putkeys(mpage, start, end - start + 1, TYPE_In); | ||
return(0); | return 0; | ||
} | } | ||
static int | static void | ||
parse_mdoc_Fn(struct mpage *mpage, const struct mdoc_node *n) | parse_mdoc_fname(struct mpage *mpage, const struct roff_node *n) | ||
{ | { | ||
char *cp; | char *cp; | ||
size_t sz; | |||
if (NULL == (n = n->child) || MDOC_TEXT != n->type) | if (n->type != ROFFT_TEXT) | ||
return(0); | return; | ||
/* | /* Skip function pointer punctuation. */ | ||
* Parse: .Fn "struct type *name" "char *arg". | |||
* First strip away pointer symbol. | |||
* Then store the function name, then type. | |||
* Finally, store the arguments. | |||
*/ | |||
if (NULL == (cp = strrchr(n->string, ' '))) | cp = n->string; | ||
cp = n->string; | while (*cp == '(' || *cp == '*') | ||
while ('*' == *cp) | |||
cp++; | cp++; | ||
sz = strcspn(cp, "()"); | |||
putkey(mpage, cp, TYPE_Fn); | putkeys(mpage, cp, sz, TYPE_Fn); | ||
if (n->sec == SEC_SYNOPSIS) | |||
putkeys(mpage, cp, sz, NAME_SYN); | |||
} | |||
if (n->string < cp) | static int | ||
putkeys(mpage, n->string, cp - n->string, TYPE_Ft); | parse_mdoc_Fn(struct mpage *mpage, const struct roff_meta *meta, | ||
const struct roff_node *n) | |||
{ | |||
for (n = n->next; NULL != n; n = n->next) | if (n->child == NULL) | ||
if (MDOC_TEXT == n->type) | return 0; | ||
parse_mdoc_fname(mpage, n->child); | |||
for (n = n->child->next; n != NULL; n = n->next) | |||
if (n->type == ROFFT_TEXT) | |||
putkey(mpage, n->string, TYPE_Fa); | putkey(mpage, n->string, TYPE_Fa); | ||
return(0); | return 0; | ||
} | } | ||
static int | static int | ||
parse_mdoc_Xr(struct mpage *mpage, const struct mdoc_node *n) | parse_mdoc_Fo(struct mpage *mpage, const struct roff_meta *meta, | ||
const struct roff_node *n) | |||
{ | { | ||
char *cp; | |||
if (NULL == (n = n->child)) | if (n->type != ROFFT_HEAD) | ||
return(0); | return 1; | ||
if (NULL == n->next) { | if (n->child != NULL) | ||
putkey(mpage, n->string, TYPE_Xr); | parse_mdoc_fname(mpage, n->child); | ||
return(0); | |||
} | |||
if (-1 == asprintf(&cp, "%s(%s)", n->string, n->next->string)) { | return 0; | ||
perror(NULL); | |||
exit((int)MANDOCLEVEL_SYSERR); | |||
} | |||
putkey(mpage, cp, TYPE_Xr); | |||
free(cp); | |||
return(0); | |||
} | } | ||
static int | static int | ||
parse_mdoc_Nd(struct mpage *mpage, const struct mdoc_node *n) | parse_mdoc_Va(struct mpage *mpage, const struct roff_meta *meta, | ||
const struct roff_node *n) | |||
{ | { | ||
size_t sz; | char *cp; | ||
if (MDOC_BODY != n->type) | if (n->type != ROFFT_ELEM && n->type != ROFFT_BODY) | ||
return(0); | return 0; | ||
/* | if (n->child != NULL && | ||
* Special-case the `Nd' because we need to put the description | n->child->next == NULL && | ||
* into the document table. | n->child->type == ROFFT_TEXT) | ||
*/ | return 1; | ||
for (n = n->child; NULL != n; n = n->next) { | cp = NULL; | ||
if (MDOC_TEXT == n->type) { | deroff(&cp, n); | ||
if (NULL != mpage->desc) { | if (cp != NULL) { | ||
sz = strlen(mpage->desc) + | putkey(mpage, cp, TYPE_Vt | (n->tok == MDOC_Va || | ||
strlen(n->string) + 2; | n->type == ROFFT_BODY ? TYPE_Va : 0)); | ||
mpage->desc = mandoc_realloc( | free(cp); | ||
mpage->desc, sz); | |||
strlcat(mpage->desc, " ", sz); | |||
strlcat(mpage->desc, n->string, sz); | |||
} else | |||
mpage->desc = mandoc_strdup(n->string); | |||
} | |||
if (NULL != n->child) | |||
parse_mdoc_Nd(mpage, n); | |||
} | } | ||
return(1); | |||
return 0; | |||
} | } | ||
static int | static int | ||
parse_mdoc_Nm(struct mpage *mpage, const struct mdoc_node *n) | parse_mdoc_Xr(struct mpage *mpage, const struct roff_meta *meta, | ||
const struct roff_node *n) | |||
{ | { | ||
char *cp; | |||
return(SEC_NAME == n->sec || | if (NULL == (n = n->child)) | ||
(SEC_SYNOPSIS == n->sec && MDOC_HEAD == n->type)); | return 0; | ||
if (NULL == n->next) { | |||
putkey(mpage, n->string, TYPE_Xr); | |||
return 0; | |||
} | |||
mandoc_asprintf(&cp, "%s(%s)", n->string, n->next->string); | |||
putkey(mpage, cp, TYPE_Xr); | |||
free(cp); | |||
return 0; | |||
} | } | ||
static int | static int | ||
parse_mdoc_Sh(struct mpage *mpage, const struct mdoc_node *n) | parse_mdoc_Nd(struct mpage *mpage, const struct roff_meta *meta, | ||
const struct roff_node *n) | |||
{ | { | ||
return(SEC_CUSTOM == n->sec && MDOC_HEAD == n->type); | if (n->type == ROFFT_BODY) | ||
deroff(&mpage->desc, n); | |||
return 0; | |||
} | } | ||
static int | static int | ||
parse_mdoc_head(struct mpage *mpage, const struct mdoc_node *n) | parse_mdoc_Nm(struct mpage *mpage, const struct roff_meta *meta, | ||
const struct roff_node *n) | |||
{ | { | ||
return(MDOC_HEAD == n->type); | if (SEC_NAME == n->sec) | ||
putmdockey(mpage, n->child, NAME_TITLE); | |||
else if (n->sec == SEC_SYNOPSIS && n->type == ROFFT_HEAD) { | |||
if (n->child == NULL) | |||
putkey(mpage, meta->name, NAME_SYN); | |||
else | |||
putmdockey(mpage, n->child, NAME_SYN); | |||
} | |||
if ( ! (mpage->name_head_done || | |||
n->child == NULL || n->child->string == NULL || | |||
strcasecmp(n->child->string, meta->title))) { | |||
putkey(mpage, n->child->string, NAME_HEAD); | |||
mpage->name_head_done = 1; | |||
} | |||
return 0; | |||
} | } | ||
static int | static int | ||
parse_mdoc_body(struct mpage *mpage, const struct mdoc_node *n) | parse_mdoc_Sh(struct mpage *mpage, const struct roff_meta *meta, | ||
const struct roff_node *n) | |||
{ | { | ||
return(MDOC_BODY == n->type); | return n->sec == SEC_CUSTOM && n->type == ROFFT_HEAD; | ||
} | } | ||
static int | |||
parse_mdoc_head(struct mpage *mpage, const struct roff_meta *meta, | |||
const struct roff_node *n) | |||
{ | |||
return n->type == ROFFT_HEAD; | |||
} | |||
/* | /* | ||
* Add a string to the hash table for the current manual. | * Add a string to the hash table for the current manual. | ||
* Each string has a bitmask telling which macros it belongs to. | * Each string has a bitmask telling which macros it belongs to. | ||
* When we finish the manual, we'll dump the table. | * When we finish the manual, we'll dump the table. | ||
*/ | */ | ||
static void | static void | ||
putkeys(const struct mpage *mpage, | putkeys(const struct mpage *mpage, char *cp, size_t sz, uint64_t v) | ||
const char *cp, size_t sz, uint64_t v) | |||
{ | { | ||
struct ohash *htab; | |||
struct str *s; | struct str *s; | ||
const char *end; | const char *end; | ||
uint64_t mask; | |||
unsigned int slot; | unsigned int slot; | ||
int i; | int i, mustfree; | ||
if (0 == sz) | if (0 == sz) | ||
return; | return; | ||
if (verb > 1) { | mustfree = render_string(&cp, &sz); | ||
for (i = 0, mask = 1; | |||
i < mansearch_keymax; | if (TYPE_Nm & v) { | ||
i++, mask <<= 1) | htab = &names; | ||
if (mask & v) | v &= name_mask; | ||
break; | if (v & NAME_FIRST) | ||
say(mpage->mlinks->file, "Adding key %s=%*s", | name_mask &= ~NAME_FIRST; | ||
mansearch_keynames[i], sz, cp); | if (debug > 1) | ||
say(mpage->mlinks->file, | |||
"Adding name %*s, bits=0x%llu", (int)sz, cp, v); | |||
} else { | |||
htab = &strings; | |||
if (debug > 1) | |||
for (i = 0; i < KEY_MAX; i++) | |||
if ((uint64_t)1 << i & v) | |||
say(mpage->mlinks->file, | |||
"Adding key %s=%*s", | |||
mansearch_keynames[i], (int)sz, cp); | |||
} | } | ||
end = cp + sz; | end = cp + sz; | ||
slot = ohash_qlookupi(&strings, cp, &end); | slot = ohash_qlookupi(htab, cp, &end); | ||
s = ohash_find(&strings, slot); | s = ohash_find(htab, slot); | ||
if (NULL != s && mpage == s->mpage) { | if (NULL != s && mpage == s->mpage) { | ||
s->mask |= v; | s->mask |= v; | ||
return; | return; | ||
} else if (NULL == s) { | } else if (NULL == s) { | ||
s = mandoc_calloc(sizeof(struct str) + sz + 1, 1); | s = mandoc_calloc(1, sizeof(struct str) + sz + 1); | ||
memcpy(s->key, cp, sz); | memcpy(s->key, cp, sz); | ||
ohash_insert(&strings, slot, s); | ohash_insert(htab, slot, s); | ||
} | } | ||
s->mpage = mpage; | s->mpage = mpage; | ||
s->mask = v; | s->mask = v; | ||
if (mustfree) | |||
free(cp); | |||
} | } | ||
/* | /* | ||
|
|
||
out[4] = (cp >> 6 & 63) | 128; | out[4] = (cp >> 6 & 63) | 128; | ||
out[5] = (cp & 63) | 128; | out[5] = (cp & 63) | 128; | ||
} else | } else | ||
return(0); | return 0; | ||
out[rc] = '\0'; | out[rc] = '\0'; | ||
return(rc); | return rc; | ||
} | } | ||
/* | /* | ||
* Store the rendered version of a key, or alias the pointer | * If the string contains escape sequences, | ||
* if the key contains no escape sequences. | * replace it with an allocated rendering and return 1, | ||
* such that the caller can free it after use. | |||
* Otherwise, do nothing and return 0. | |||
*/ | */ | ||
static void | static int | ||
render_key(struct mchars *mc, struct str *key) | render_string(char **public, size_t *psz) | ||
{ | { | ||
size_t sz, bsz, pos; | const char *src, *scp, *addcp, *seq; | ||
char utfbuf[7], res[5]; | char *dst; | ||
char *buf; | size_t ssz, dsz, addsz; | ||
const char *seq, *cpp, *val; | char utfbuf[7], res[6]; | ||
int len, u; | int seqlen, unicode; | ||
enum mandoc_esc esc; | |||
assert(NULL == key->rendered); | |||
res[0] = '\\'; | res[0] = '\\'; | ||
res[1] = '\t'; | res[1] = '\t'; | ||
res[2] = ASCII_NBRSP; | res[2] = ASCII_NBRSP; | ||
res[3] = ASCII_HYPH; | res[3] = ASCII_HYPH; | ||
res[4] = '\0'; | res[4] = ASCII_BREAK; | ||
res[5] = '\0'; | |||
val = key->key; | src = scp = *public; | ||
bsz = strlen(val); | ssz = *psz; | ||
dst = NULL; | |||
dsz = 0; | |||
/* | while (scp < src + *psz) { | ||
* Pre-check: if we have no stop-characters, then set the | |||
* pointer as ourselvse and get out of here. | |||
*/ | |||
if (strcspn(val, res) == bsz) { | |||
key->rendered = key->key; | |||
return; | |||
} | |||
/* Pre-allocate by the length of the input */ | /* Leave normal characters unchanged. */ | ||
buf = mandoc_malloc(++bsz); | if (strchr(res, *scp) == NULL) { | ||
pos = 0; | if (dst != NULL) | ||
dst[dsz++] = *scp; | |||
scp++; | |||
continue; | |||
} | |||
while ('\0' != *val) { | |||
/* | /* | ||
* Halt on the first escape sequence. | * Found something that requires replacing, | ||
* This also halts on the end of string, in which case | * make sure we have a destination buffer. | ||
* we just copy, fallthrough, and exit the loop. | |||
*/ | */ | ||
if ((sz = strcspn(val, res)) > 0) { | |||
memcpy(&buf[pos], val, sz); | if (dst == NULL) { | ||
pos += sz; | dst = mandoc_malloc(ssz + 1); | ||
val += sz; | dsz = scp - src; | ||
memcpy(dst, src, dsz); | |||
} | } | ||
if (ASCII_HYPH == *val) { | /* Handle single-char special characters. */ | ||
buf[pos++] = '-'; | |||
val++; | switch (*scp) { | ||
case '\\': | |||
break; | |||
case '\t': | |||
case ASCII_NBRSP: | |||
dst[dsz++] = ' '; | |||
scp++; | |||
continue; | continue; | ||
} else if ('\t' == *val || ASCII_NBRSP == *val) { | case ASCII_HYPH: | ||
buf[pos++] = ' '; | dst[dsz++] = '-'; | ||
val++; | /* FALLTHROUGH */ | ||
case ASCII_BREAK: | |||
scp++; | |||
continue; | continue; | ||
} else if ('\\' != *val) | default: | ||
break; | abort(); | ||
} | |||
/* Read past the slash. */ | |||
val++; | |||
/* | /* | ||
* Parse the escape sequence and see if it's a | * Found an escape sequence. | ||
* predefined character or special character. | * Read past the slash, then parse it. | ||
* Ignore everything except characters. | |||
*/ | */ | ||
esc = mandoc_escape | scp++; | ||
((const char **)&val, &seq, &len); | if (mandoc_escape(&scp, &seq, &seqlen) != ESCAPE_SPECIAL) | ||
if (ESCAPE_ERROR == esc) | |||
break; | |||
if (ESCAPE_SPECIAL != esc) | |||
continue; | continue; | ||
/* | /* | ||
|
|
||
*/ | */ | ||
if (write_utf8) { | if (write_utf8) { | ||
if (0 == (u = mchars_spec2cp(mc, seq, len))) | unicode = mchars_spec2cp(seq, seqlen); | ||
if (unicode <= 0) | |||
continue; | continue; | ||
cpp = utfbuf; | addsz = utf8(unicode, utfbuf); | ||
if (0 == (sz = utf8(u, utfbuf))) | if (addsz == 0) | ||
continue; | continue; | ||
sz = strlen(cpp); | addcp = utfbuf; | ||
} else { | } else { | ||
cpp = mchars_spec2str(mc, seq, len, &sz); | addcp = mchars_spec2str(seq, seqlen, &addsz); | ||
if (NULL == cpp) | if (addcp == NULL) | ||
continue; | continue; | ||
if (ASCII_NBRSP == *cpp) { | if (*addcp == ASCII_NBRSP) { | ||
cpp = " "; | addcp = " "; | ||
sz = 1; | addsz = 1; | ||
} | } | ||
} | } | ||
/* Copy the rendered glyph into the stream. */ | /* Copy the rendered glyph into the stream. */ | ||
bsz += sz; | ssz += addsz; | ||
buf = mandoc_realloc(buf, bsz); | dst = mandoc_realloc(dst, ssz + 1); | ||
memcpy(&buf[pos], cpp, sz); | memcpy(dst + dsz, addcp, addsz); | ||
pos += sz; | dsz += addsz; | ||
} | } | ||
if (dst != NULL) { | |||
*public = dst; | |||
*psz = dsz; | |||
} | |||
buf[pos] = '\0'; | /* Trim trailing whitespace and NUL-terminate. */ | ||
key->rendered = buf; | |||
while (*psz > 0 && (*public)[*psz - 1] == ' ') | |||
--*psz; | |||
if (dst != NULL) { | |||
(*public)[*psz] = '\0'; | |||
return 1; | |||
} else | |||
return 0; | |||
} | } | ||
static void | |||
dbadd_mlink(const struct mlink *mlink) | |||
{ | |||
dba_page_alias(mlink->mpage->dba, mlink->name, NAME_FILE); | |||
dba_page_add(mlink->mpage->dba, DBP_SECT, mlink->dsec); | |||
dba_page_add(mlink->mpage->dba, DBP_SECT, mlink->fsec); | |||
dba_page_add(mlink->mpage->dba, DBP_ARCH, mlink->arch); | |||
dba_page_add(mlink->mpage->dba, DBP_FILE, mlink->file); | |||
} | |||
/* | /* | ||
* Flush the current page's terms (and their bits) into the database. | * Flush the current page's terms (and their bits) into the database. | ||
* Wrap the entire set of additions in a transaction to make sqlite be a | |||
* little faster. | |||
* Also, handle escape sequences at the last possible moment. | * Also, handle escape sequences at the last possible moment. | ||
*/ | */ | ||
static void | static void | ||
dbadd(const struct mpage *mpage, struct mchars *mc) | dbadd(struct dba *dba, struct mpage *mpage) | ||
{ | { | ||
struct mlink *mlink; | struct mlink *mlink; | ||
struct str *key; | struct str *key; | ||
int64_t recno; | char *cp; | ||
uint64_t mask; | |||
size_t i; | size_t i; | ||
unsigned int slot; | unsigned int slot; | ||
int mustfree; | |||
if (verb) | mlink = mpage->mlinks; | ||
say(mpage->mlinks->file, "Adding to database"); | |||
if (nodb) | if (nodb) { | ||
for (key = ohash_first(&names, &slot); NULL != key; | |||
key = ohash_next(&names, &slot)) | |||
free(key); | |||
for (key = ohash_first(&strings, &slot); NULL != key; | |||
key = ohash_next(&strings, &slot)) | |||
free(key); | |||
if (0 == debug) | |||
return; | |||
while (NULL != mlink) { | |||
fputs(mlink->name, stdout); | |||
if (NULL == mlink->next || | |||
strcmp(mlink->dsec, mlink->next->dsec) || | |||
strcmp(mlink->fsec, mlink->next->fsec) || | |||
strcmp(mlink->arch, mlink->next->arch)) { | |||
putchar('('); | |||
if ('\0' == *mlink->dsec) | |||
fputs(mlink->fsec, stdout); | |||
else | |||
fputs(mlink->dsec, stdout); | |||
if ('\0' != *mlink->arch) | |||
printf("/%s", mlink->arch); | |||
putchar(')'); | |||
} | |||
mlink = mlink->next; | |||
if (NULL != mlink) | |||
fputs(", ", stdout); | |||
} | |||
printf(" - %s\n", mpage->desc); | |||
return; | return; | ||
} | |||
i = 1; | if (debug) | ||
SQL_BIND_INT(stmts[STMT_INSERT_PAGE], i, FORM_SRC == mpage->form); | say(mlink->file, "Adding to database"); | ||
SQL_STEP(stmts[STMT_INSERT_PAGE]); | |||
recno = sqlite3_last_insert_rowid(db); | |||
sqlite3_reset(stmts[STMT_INSERT_PAGE]); | |||
for (mlink = mpage->mlinks; mlink; mlink = mlink->next) { | cp = mpage->desc; | ||
i = 1; | i = strlen(cp); | ||
SQL_BIND_TEXT(stmts[STMT_INSERT_LINK], i, mlink->dsec); | mustfree = render_string(&cp, &i); | ||
SQL_BIND_TEXT(stmts[STMT_INSERT_LINK], i, mlink->arch); | mpage->dba = dba_page_new(dba->pages, | ||
SQL_BIND_TEXT(stmts[STMT_INSERT_LINK], i, mlink->name); | *mpage->arch == '\0' ? mlink->arch : mpage->arch, | ||
SQL_BIND_INT64(stmts[STMT_INSERT_LINK], i, recno); | cp, mlink->file, mpage->form); | ||
SQL_STEP(stmts[STMT_INSERT_LINK]); | if (mustfree) | ||
sqlite3_reset(stmts[STMT_INSERT_LINK]); | free(cp); | ||
dba_page_add(mpage->dba, DBP_SECT, mpage->sec); | |||
while (mlink != NULL) { | |||
dbadd_mlink(mlink); | |||
mlink = mlink->next; | |||
} | } | ||
for (key = ohash_first(&names, &slot); NULL != key; | |||
key = ohash_next(&names, &slot)) { | |||
assert(key->mpage == mpage); | |||
dba_page_alias(mpage->dba, key->key, key->mask); | |||
free(key); | |||
} | |||
for (key = ohash_first(&strings, &slot); NULL != key; | for (key = ohash_first(&strings, &slot); NULL != key; | ||
key = ohash_next(&strings, &slot)) { | key = ohash_next(&strings, &slot)) { | ||
assert(key->mpage == mpage); | assert(key->mpage == mpage); | ||
if (NULL == key->rendered) | i = 0; | ||
render_key(mc, key); | for (mask = TYPE_Xr; mask <= TYPE_Lb; mask *= 2) { | ||
i = 1; | if (key->mask & mask) | ||
SQL_BIND_INT64(stmts[STMT_INSERT_KEY], i, key->mask); | dba_macro_add(dba->macros, i, | ||
SQL_BIND_TEXT(stmts[STMT_INSERT_KEY], i, key->rendered); | key->key, mpage->dba); | ||
SQL_BIND_INT64(stmts[STMT_INSERT_KEY], i, recno); | i++; | ||
SQL_STEP(stmts[STMT_INSERT_KEY]); | } | ||
sqlite3_reset(stmts[STMT_INSERT_KEY]); | |||
if (key->rendered != key->key) | |||
free(key->rendered); | |||
free(key); | free(key); | ||
} | } | ||
} | } | ||
static void | static void | ||
dbprune(void) | dbprune(struct dba *dba) | ||
{ | { | ||
struct mpage *mpage; | struct dba_array *page, *files; | ||
struct mlink *mlink; | char *file; | ||
size_t i; | |||
unsigned int slot; | |||
if (0 == nodb) | dba_array_FOREACH(dba->pages, page) { | ||
SQL_EXEC("BEGIN TRANSACTION"); | files = dba_array_get(page, DBP_FILE); | ||
dba_array_FOREACH(files, file) { | |||
for (mpage = ohash_first(&mpages, &slot); NULL != mpage; | if (*file < ' ') | ||
mpage = ohash_next(&mpages, &slot)) { | file++; | ||
mlink = mpage->mlinks; | if (ohash_find(&mlinks, ohash_qlookup(&mlinks, | ||
if (verb) | file)) != NULL) { | ||
say(mlink->file, "Deleting from database"); | if (debug) | ||
if (nodb) | say(file, "Deleting from database"); | ||
continue; | dba_array_del(dba->pages); | ||
for ( ; NULL != mlink; mlink = mlink->next) { | break; | ||
i = 1; | } | ||
SQL_BIND_TEXT(stmts[STMT_DELETE_PAGE], | |||
i, mlink->dsec); | |||
SQL_BIND_TEXT(stmts[STMT_DELETE_PAGE], | |||
i, mlink->arch); | |||
SQL_BIND_TEXT(stmts[STMT_DELETE_PAGE], | |||
i, mlink->name); | |||
SQL_STEP(stmts[STMT_DELETE_PAGE]); | |||
sqlite3_reset(stmts[STMT_DELETE_PAGE]); | |||
} | } | ||
} | } | ||
if (0 == nodb) | |||
SQL_EXEC("END TRANSACTION"); | |||
} | } | ||
/* | /* | ||
* Close an existing database and its prepared statements. | * Write the database from memory to disk. | ||
* If "real" is not set, rename the temporary file into the real one. | |||
*/ | */ | ||
static void | static void | ||
dbclose(int real) | dbwrite(struct dba *dba) | ||
{ | { | ||
size_t i; | char tfn[32]; | ||
int status; | |||
pid_t child; | |||
if (nodb) | if (dba_write(MANDOC_DB "~", dba) != -1) { | ||
if (rename(MANDOC_DB "~", MANDOC_DB) == -1) { | |||
exitcode = (int)MANDOCLEVEL_SYSERR; | |||
say(MANDOC_DB, "&rename"); | |||
unlink(MANDOC_DB "~"); | |||
} | |||
return; | return; | ||
for (i = 0; i < STMT__MAX; i++) { | |||
sqlite3_finalize(stmts[i]); | |||
stmts[i] = NULL; | |||
} | } | ||
sqlite3_close(db); | (void)strlcpy(tfn, "/tmp/mandocdb.XXXXXXXX", sizeof(tfn)); | ||
db = NULL; | if (mkdtemp(tfn) == NULL) { | ||
exitcode = (int)MANDOCLEVEL_SYSERR; | |||
if (real) | say("", "&%s", tfn); | ||
return; | return; | ||
} | |||
if (-1 == rename(MANDOC_DB "~", MANDOC_DB)) { | (void)strlcat(tfn, "/" MANDOC_DB, sizeof(tfn)); | ||
if (dba_write(tfn, dba) == -1) { | |||
exitcode = (int)MANDOCLEVEL_SYSERR; | exitcode = (int)MANDOCLEVEL_SYSERR; | ||
say(MANDOC_DB, NULL); | say(tfn, "&dba_write"); | ||
goto out; | |||
} | } | ||
} | |||
/* | switch (child = fork()) { | ||
* This is straightforward stuff. | case -1: | ||
* Open a database connection to a "temporary" database, then open a set | |||
* of prepared statements we'll use over and over again. | |||
* If "real" is set, we use the existing database; if not, we truncate a | |||
* temporary one. | |||
* Must be matched by dbclose(). | |||
*/ | |||
static int | |||
dbopen(int real) | |||
{ | |||
const char *file, *sql; | |||
int rc, ofl; | |||
if (nodb) | |||
return(1); | |||
ofl = SQLITE_OPEN_READWRITE; | |||
if (0 == real) { | |||
file = MANDOC_DB "~"; | |||
if (-1 == remove(file) && ENOENT != errno) { | |||
exitcode = (int)MANDOCLEVEL_SYSERR; | |||
say(file, NULL); | |||
return(0); | |||
} | |||
ofl |= SQLITE_OPEN_EXCLUSIVE; | |||
} else | |||
file = MANDOC_DB; | |||
rc = sqlite3_open_v2(file, &db, ofl, NULL); | |||
if (SQLITE_OK == rc) | |||
goto prepare_statements; | |||
if (SQLITE_CANTOPEN != rc) { | |||
exitcode = (int)MANDOCLEVEL_SYSERR; | exitcode = (int)MANDOCLEVEL_SYSERR; | ||
say(file, NULL); | say("", "&fork cmp"); | ||
return(0); | return; | ||
case 0: | |||
execlp("cmp", "cmp", "-s", tfn, MANDOC_DB, (char *)NULL); | |||
say("", "&exec cmp"); | |||
exit(0); | |||
default: | |||
break; | |||
} | } | ||
if (waitpid(child, &status, 0) == -1) { | |||
sqlite3_close(db); | |||
db = NULL; | |||
if (SQLITE_OK != (rc = sqlite3_open(file, &db))) { | |||
exitcode = (int)MANDOCLEVEL_SYSERR; | exitcode = (int)MANDOCLEVEL_SYSERR; | ||
say(file, NULL); | say("", "&wait cmp"); | ||
return(0); | } else if (WIFSIGNALED(status)) { | ||
exitcode = (int)MANDOCLEVEL_SYSERR; | |||
say("", "cmp died from signal %d", WTERMSIG(status)); | |||
} else if (WEXITSTATUS(status)) { | |||
exitcode = (int)MANDOCLEVEL_SYSERR; | |||
say(MANDOC_DB, | |||
"Data changed, but cannot replace database"); | |||
} | } | ||
sql = "CREATE TABLE \"mpages\" (\n" | out: | ||
" \"form\" INTEGER NOT NULL,\n" | *strrchr(tfn, '/') = '\0'; | ||
" \"id\" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL\n" | switch (child = fork()) { | ||
");\n" | case -1: | ||
"\n" | |||
"CREATE TABLE \"mlinks\" (\n" | |||
" \"sec\" TEXT NOT NULL,\n" | |||
" \"arch\" TEXT NOT NULL,\n" | |||
" \"name\" TEXT NOT NULL,\n" | |||
" \"pageid\" INTEGER NOT NULL REFERENCES mpages(id) " | |||
"ON DELETE CASCADE\n" | |||
");\n" | |||
"\n" | |||
"CREATE TABLE \"keys\" (\n" | |||
" \"bits\" INTEGER NOT NULL,\n" | |||
" \"key\" TEXT NOT NULL,\n" | |||
" \"pageid\" INTEGER NOT NULL REFERENCES mpages(id) " | |||
"ON DELETE CASCADE\n" | |||
");\n"; | |||
if (SQLITE_OK != sqlite3_exec(db, sql, NULL, NULL, NULL)) { | |||
exitcode = (int)MANDOCLEVEL_SYSERR; | exitcode = (int)MANDOCLEVEL_SYSERR; | ||
say(file, "%s", sqlite3_errmsg(db)); | say("", "&fork rm"); | ||
return(0); | return; | ||
case 0: | |||
execlp("rm", "rm", "-rf", tfn, (char *)NULL); | |||
say("", "&exec rm"); | |||
exit((int)MANDOCLEVEL_SYSERR); | |||
default: | |||
break; | |||
} | } | ||
if (waitpid(child, &status, 0) == -1) { | |||
prepare_statements: | exitcode = (int)MANDOCLEVEL_SYSERR; | ||
SQL_EXEC("PRAGMA foreign_keys = ON"); | say("", "&wait rm"); | ||
sql = "DELETE FROM mpages WHERE id IN " | } else if (WIFSIGNALED(status) || WEXITSTATUS(status)) { | ||
"(SELECT pageid FROM mlinks WHERE " | exitcode = (int)MANDOCLEVEL_SYSERR; | ||
"sec=? AND arch=? AND name=?)"; | say("", "%s: Cannot remove temporary directory", tfn); | ||
sqlite3_prepare_v2(db, sql, -1, &stmts[STMT_DELETE_PAGE], NULL); | } | ||
sql = "INSERT INTO mpages " | |||
"(form) VALUES (?)"; | |||
sqlite3_prepare_v2(db, sql, -1, &stmts[STMT_INSERT_PAGE], NULL); | |||
sql = "INSERT INTO mlinks " | |||
"(sec,arch,name,pageid) VALUES (?,?,?,?)"; | |||
sqlite3_prepare_v2(db, sql, -1, &stmts[STMT_INSERT_LINK], NULL); | |||
sql = "INSERT INTO keys " | |||
"(bits,key,pageid) VALUES (?,?,?)"; | |||
sqlite3_prepare_v2(db, sql, -1, &stmts[STMT_INSERT_KEY], NULL); | |||
#ifndef __APPLE__ | |||
/* | |||
* When opening a new database, we can turn off | |||
* synchronous mode for much better performance. | |||
*/ | |||
if (real) | |||
SQL_EXEC("PRAGMA synchronous = OFF"); | |||
#endif | |||
return(1); | |||
} | } | ||
static void * | |||
hash_halloc(size_t sz, void *arg) | |||
{ | |||
return(mandoc_calloc(sz, 1)); | |||
} | |||
static void * | |||
hash_alloc(size_t sz, void *arg) | |||
{ | |||
return(mandoc_malloc(sz)); | |||
} | |||
static void | |||
hash_free(void *p, size_t sz, void *arg) | |||
{ | |||
free(p); | |||
} | |||
static int | static int | ||
set_basedir(const char *targetdir) | set_basedir(const char *targetdir, int report_baddir) | ||
{ | { | ||
static char startdir[PATH_MAX]; | static char startdir[PATH_MAX]; | ||
static int fd; | static int getcwd_status; /* 1 = ok, 2 = failure */ | ||
static int chdir_status; /* 1 = changed directory */ | |||
char *cp; | |||
/* | /* | ||
* Remember where we started by keeping a fd open to the origin | * Remember the original working directory, if possible. | ||
* path component: throughout this utility, we chdir() a lot to | * This will be needed if the second or a later directory | ||
* handle relative paths, and by doing this, we can return to | * on the command line is given as a relative path. | ||
* the starting point. | * Do not error out if the current directory is not | ||
* searchable: Maybe it won't be needed after all. | |||
*/ | */ | ||
if ('\0' == *startdir) { | if (0 == getcwd_status) { | ||
if (NULL == getcwd(startdir, PATH_MAX)) { | if (NULL == getcwd(startdir, sizeof(startdir))) { | ||
getcwd_status = 2; | |||
(void)strlcpy(startdir, strerror(errno), | |||
sizeof(startdir)); | |||
} else | |||
getcwd_status = 1; | |||
} | |||
/* | |||
* We are leaving the old base directory. | |||
* Do not use it any longer, not even for messages. | |||
*/ | |||
*basedir = '\0'; | |||
/* | |||
* If and only if the directory was changed earlier and | |||
* the next directory to process is given as a relative path, | |||
* first go back, or bail out if that is impossible. | |||
*/ | |||
if (chdir_status && '/' != *targetdir) { | |||
if (2 == getcwd_status) { | |||
exitcode = (int)MANDOCLEVEL_SYSERR; | exitcode = (int)MANDOCLEVEL_SYSERR; | ||
if (NULL != targetdir) | say("", "getcwd: %s", startdir); | ||
say(".", NULL); | return 0; | ||
return(0); | |||
} | } | ||
if (-1 == (fd = open(startdir, O_RDONLY, 0))) { | if (-1 == chdir(startdir)) { | ||
exitcode = (int)MANDOCLEVEL_SYSERR; | exitcode = (int)MANDOCLEVEL_SYSERR; | ||
say(startdir, NULL); | say("", "&chdir %s", startdir); | ||
return(0); | return 0; | ||
} | } | ||
if (NULL == targetdir) | |||
targetdir = startdir; | |||
} else { | |||
if (-1 == fd) | |||
return(0); | |||
if (-1 == fchdir(fd)) { | |||
close(fd); | |||
basedir[0] = '\0'; | |||
exitcode = (int)MANDOCLEVEL_SYSERR; | |||
say(startdir, NULL); | |||
return(0); | |||
} | |||
if (NULL == targetdir) { | |||
close(fd); | |||
return(1); | |||
} | |||
} | } | ||
/* | |||
* Always resolve basedir to the canonicalized absolute | |||
* pathname and append a trailing slash, such that | |||
* we can reliably check whether files are inside. | |||
*/ | |||
if (NULL == realpath(targetdir, basedir)) { | if (NULL == realpath(targetdir, basedir)) { | ||
basedir[0] = '\0'; | if (report_baddir || errno != ENOENT) { | ||
exitcode = (int)MANDOCLEVEL_BADARG; | exitcode = (int)MANDOCLEVEL_BADARG; | ||
say(targetdir, NULL); | say("", "&%s: realpath", targetdir); | ||
return(0); | } | ||
return 0; | |||
} else if (-1 == chdir(basedir)) { | } else if (-1 == chdir(basedir)) { | ||
exitcode = (int)MANDOCLEVEL_BADARG; | if (report_baddir || errno != ENOENT) { | ||
say("", NULL); | exitcode = (int)MANDOCLEVEL_BADARG; | ||
return(0); | say("", "&chdir"); | ||
} | |||
return 0; | |||
} | } | ||
return(1); | chdir_status = 1; | ||
cp = strchr(basedir, '\0'); | |||
if ('/' != cp[-1]) { | |||
if (cp - basedir >= PATH_MAX - 1) { | |||
exitcode = (int)MANDOCLEVEL_SYSERR; | |||
say("", "Filename too long"); | |||
return 0; | |||
} | |||
*cp++ = '/'; | |||
*cp = '\0'; | |||
} | |||
return 1; | |||
} | } | ||
static void | static void | ||
say(const char *file, const char *format, ...) | say(const char *file, const char *format, ...) | ||
{ | { | ||
va_list ap; | va_list ap; | ||
int use_errno; | |||
if ('\0' != *basedir) | if ('\0' != *basedir) | ||
fprintf(stderr, "%s", basedir); | fprintf(stderr, "%s", basedir); | ||
if ('\0' != *basedir && '\0' != *file) | if ('\0' != *basedir && '\0' != *file) | ||
fputs("//", stderr); | fputc('/', stderr); | ||
if ('\0' != *file) | if ('\0' != *file) | ||
fprintf(stderr, "%s", file); | fprintf(stderr, "%s", file); | ||
fputs(": ", stderr); | |||
if (NULL == format) { | use_errno = 1; | ||
perror(NULL); | if (NULL != format) { | ||
return; | switch (*format) { | ||
case '&': | |||
format++; | |||
break; | |||
case '\0': | |||
format = NULL; | |||
break; | |||
default: | |||
use_errno = 0; | |||
break; | |||
} | |||
} | } | ||
if (NULL != format) { | |||
va_start(ap, format); | if ('\0' != *basedir || '\0' != *file) | ||
vfprintf(stderr, format, ap); | fputs(": ", stderr); | ||
va_end(ap); | va_start(ap, format); | ||
vfprintf(stderr, format, ap); | |||
fputc('\n', stderr); | va_end(ap); | ||
} | |||
if (use_errno) { | |||
if ('\0' != *basedir || '\0' != *file || NULL != format) | |||
fputs(": ", stderr); | |||
perror(NULL); | |||
} else | |||
fputc('\n', stderr); | |||
} | } |