![]() ![]() | ![]() |
version 1.84, 2013/12/27 15:39:03 | version 1.200, 2015/10/12 00:32:55 | ||
---|---|---|---|
|
|
||
/* $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 Ingo Schwarze <schwarze@openbsd.org> | * Copyright (c) 2011-2015 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> | ||
#include <err.h> | |||
#include <errno.h> | #include <errno.h> | ||
#include <fcntl.h> | #include <fcntl.h> | ||
#if HAVE_FTS | |||
#include <fts.h> | #include <fts.h> | ||
#else | |||
#include "compat_fts.h" | |||
#endif | |||
#include <getopt.h> | #include <getopt.h> | ||
#include <limits.h> | #include <limits.h> | ||
#include <stddef.h> | #include <stddef.h> | ||
|
|
||
#include <string.h> | #include <string.h> | ||
#include <unistd.h> | #include <unistd.h> | ||
#ifdef HAVE_OHASH | #if HAVE_OHASH | ||
#include <ohash.h> | #include <ohash.h> | ||
#else | #else | ||
#include "compat_ohash.h" | #include "compat_ohash.h" | ||
#endif | #endif | ||
#include <sqlite3.h> | #include <sqlite3.h> | ||
#include "mandoc_aux.h" | |||
#include "mandoc.h" | |||
#include "roff.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" | ||
extern int mansearch_keymax; | |||
extern const char *const mansearch_keynames[]; | |||
#define SQL_EXEC(_v) \ | #define SQL_EXEC(_v) \ | ||
if (SQLITE_OK != sqlite3_exec(db, (_v), NULL, NULL, NULL)) \ | if (SQLITE_OK != sqlite3_exec(db, (_v), NULL, NULL, NULL)) \ | ||
fprintf(stderr, "%s\n", sqlite3_errmsg(db)) | say("", "%s: %s", (_v), sqlite3_errmsg(db)) | ||
#define SQL_BIND_TEXT(_s, _i, _v) \ | #define SQL_BIND_TEXT(_s, _i, _v) \ | ||
if (SQLITE_OK != sqlite3_bind_text \ | if (SQLITE_OK != sqlite3_bind_text \ | ||
((_s), (_i)++, (_v), -1, SQLITE_STATIC)) \ | ((_s), (_i)++, (_v), -1, SQLITE_STATIC)) \ | ||
fprintf(stderr, "%s\n", sqlite3_errmsg(db)) | say(mlink->file, "%s", sqlite3_errmsg(db)) | ||
#define SQL_BIND_INT(_s, _i, _v) \ | #define SQL_BIND_INT(_s, _i, _v) \ | ||
if (SQLITE_OK != sqlite3_bind_int \ | if (SQLITE_OK != sqlite3_bind_int \ | ||
((_s), (_i)++, (_v))) \ | ((_s), (_i)++, (_v))) \ | ||
fprintf(stderr, "%s\n", sqlite3_errmsg(db)) | say(mlink->file, "%s", sqlite3_errmsg(db)) | ||
#define SQL_BIND_INT64(_s, _i, _v) \ | #define SQL_BIND_INT64(_s, _i, _v) \ | ||
if (SQLITE_OK != sqlite3_bind_int64 \ | if (SQLITE_OK != sqlite3_bind_int64 \ | ||
((_s), (_i)++, (_v))) \ | ((_s), (_i)++, (_v))) \ | ||
fprintf(stderr, "%s\n", sqlite3_errmsg(db)) | say(mlink->file, "%s", sqlite3_errmsg(db)) | ||
#define SQL_STEP(_s) \ | #define SQL_STEP(_s) \ | ||
if (SQLITE_DONE != sqlite3_step((_s))) \ | if (SQLITE_DONE != sqlite3_step((_s))) \ | ||
fprintf(stderr, "%s\n", sqlite3_errmsg(db)) | say(mlink->file, "%s", 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_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 *utf8; /* key in UTF-8 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[]; /* the string itself */ | 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 */ | int64_t pageid; /* pageid in mpages SQL table */ | ||
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 form; /* format from file content */ | |||
int name_head_done; | |||
}; | }; | ||
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 mpage *mpage; /* parent */ | |||
int dform; /* format from directory */ | |||
int fform; /* format from file name suffix */ | |||
int gzip; /* filename has a .gz suffix */ | |||
}; | }; | ||
struct title { | |||
char *title; /* name(sec/arch) given inside the file */ | |||
char *file; /* file name in case of mismatch */ | |||
}; | |||
enum stmt { | enum stmt { | ||
STMT_DELETE_PAGE = 0, /* delete mpage */ | STMT_DELETE_PAGE = 0, /* delete mpage */ | ||
STMT_INSERT_PAGE, /* insert mpage */ | STMT_INSERT_PAGE, /* insert mpage */ | ||
STMT_INSERT_LINK, /* insert mlink */ | STMT_INSERT_LINK, /* insert mlink */ | ||
STMT_INSERT_NAME, /* insert name */ | |||
STMT_SELECT_NAME, /* retrieve existing name flags */ | |||
STMT_INSERT_KEY, /* insert parsed key */ | STMT_INSERT_KEY, /* insert parsed key */ | ||
STMT__MAX | STMT__MAX | ||
}; | }; | ||
typedef int (*mdoc_fp)(struct mpage *, const struct mdoc_node *); | typedef int (*mdoc_fp)(struct mpage *, const struct roff_meta *, | ||
const struct roff_node *); | |||
struct mdoc_handler { | struct mdoc_handler { | ||
mdoc_fp fp; /* optional handler */ | mdoc_fp fp; /* optional handler */ | ||
|
|
||
}; | }; | ||
static void dbclose(int); | static void dbclose(int); | ||
static void dbindex(const struct mpage *, struct mchars *); | static void dbadd(struct mpage *); | ||
static void dbadd_mlink(const struct mlink *mlink); | |||
static void dbadd_mlink_name(const struct mlink *mlink); | |||
static int dbopen(int); | static int dbopen(int); | ||
static void dbprune(void); | static void dbprune(void); | ||
static void filescan(const char *); | static void filescan(const char *); | ||
static void *hash_alloc(size_t, void *); | static void *hash_alloc(size_t, void *); | ||
static void hash_free(void *, size_t, void *); | static void hash_free(void *, void *); | ||
static void *hash_halloc(size_t, void *); | static void *hash_calloc(size_t, size_t, void *); | ||
static int inocheck(const struct stat *); | |||
static void mlink_add(struct mlink *, const struct stat *); | static void mlink_add(struct mlink *, const struct stat *); | ||
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 mpages_free(void); | static void mpages_free(void); | ||
static void mpages_merge(struct mchars *, struct mparse *, int); | static void mpages_merge(struct mparse *); | ||
static void parse_cat(struct mpage *); | static void names_check(void); | ||
static void parse_man(struct mpage *, const struct man_node *); | static void parse_cat(struct mpage *, int); | ||
static void parse_mdoc(struct mpage *, const struct mdoc_node *); | static void parse_man(struct mpage *, const struct roff_meta *, | ||
static int parse_mdoc_body(struct mpage *, const struct mdoc_node *); | const struct roff_node *); | ||
static int parse_mdoc_head(struct mpage *, const struct mdoc_node *); | static void parse_mdoc(struct mpage *, const struct roff_meta *, | ||
static int parse_mdoc_Fd(struct mpage *, const struct mdoc_node *); | const struct roff_node *); | ||
static int parse_mdoc_Fn(struct mpage *, const struct mdoc_node *); | static int parse_mdoc_body(struct mpage *, const struct roff_meta *, | ||
static int parse_mdoc_In(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_head(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 int parse_mdoc_Fd(struct mpage *, const struct roff_meta *, | ||
static int parse_mdoc_St(struct mpage *, const struct mdoc_node *); | const struct roff_node *); | ||
static int parse_mdoc_Xr(struct mpage *, const struct mdoc_node *); | static void parse_mdoc_fname(struct mpage *, const struct roff_node *); | ||
static void putkey(const struct mpage *, | static int parse_mdoc_Fn(struct mpage *, const struct roff_meta *, | ||
const char *, uint64_t); | const struct roff_node *); | ||
static void putkeys(const struct mpage *, | static int parse_mdoc_Fo(struct mpage *, const struct roff_meta *, | ||
const char *, size_t, uint64_t); | 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_Xr(struct mpage *, const struct roff_meta *, | |||
const struct roff_node *); | |||
static void putkey(const struct mpage *, char *, uint64_t); | |||
static void putkeys(const struct mpage *, 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 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 *); | 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 void utf8key(struct mchars *, struct str *); | |||
static char *progname; | extern char *__progname; | ||
static int use_all; /* use all found files */ | |||
static char tempfilename[32]; | |||
static int nodb; /* no database changes */ | static int nodb; /* no database changes */ | ||
static int verb; /* print what we're doing */ | static int mparse_options; /* abort the parse early */ | ||
static int warnings; /* warn about crap */ | static int use_all; /* use all found files */ | ||
static int debug; /* print what we're doing */ | |||
static int warnings; /* warn about crap */ | |||
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 mchars *mchars; /* table of named characters */ | |||
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 sqlite3 *db = NULL; /* current database */ | ||
static sqlite3_stmt *stmts[STMT__MAX]; /* current statements */ | static sqlite3_stmt *stmts[STMT__MAX]; /* current statements */ | ||
static uint64_t name_mask; | |||
static const struct mdoc_handler mdocs[MDOC_MAX] = { | static const struct mdoc_handler mdocs[MDOC_MAX] = { | ||
{ NULL, 0 }, /* Ap */ | { NULL, 0 }, /* Ap */ | ||
|
|
||
{ parse_mdoc_Fn, 0 }, /* Fn */ | { parse_mdoc_Fn, 0 }, /* Fn */ | ||
{ NULL, TYPE_Ft }, /* Ft */ | { NULL, TYPE_Ft }, /* Ft */ | ||
{ NULL, TYPE_Ic }, /* Ic */ | { NULL, TYPE_Ic }, /* Ic */ | ||
{ parse_mdoc_In, 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 */ | ||
{ parse_mdoc_St, 0 }, /* St */ | { NULL, TYPE_St }, /* St */ | ||
{ NULL, TYPE_Va }, /* Va */ | { NULL, TYPE_Va }, /* Va */ | ||
{ parse_mdoc_body, TYPE_Va }, /* Vt */ | { parse_mdoc_body, TYPE_Va }, /* Vt */ | ||
{ parse_mdoc_Xr, 0 }, /* Xr */ | { parse_mdoc_Xr, 0 }, /* Xr */ | ||
|
|
||
{ 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 ohash_info mpages_info, mlinks_info; | struct ohash_info mpages_info, mlinks_info; | ||
struct mparse *mp; | |||
const char *path_arg; | |||
size_t j, sz; | |||
int ch, i; | |||
memset(&conf, 0, sizeof(conf)); | |||
memset(stmts, 0, STMT__MAX * sizeof(sqlite3_stmt *)); | memset(stmts, 0, STMT__MAX * sizeof(sqlite3_stmt *)); | ||
memset(&dirs, 0, sizeof(struct manpaths)); | |||
mpages_info.alloc = mlinks_info.alloc = hash_alloc; | mpages_info.alloc = mlinks_info.alloc = hash_alloc; | ||
mpages_info.halloc = mlinks_info.halloc = hash_halloc; | mpages_info.calloc = mlinks_info.calloc = hash_calloc; | ||
mpages_info.hfree = mlinks_info.hfree = hash_free; | mpages_info.free = mlinks_info.free = hash_free; | ||
mpages_info.data = mlinks_info.data = NULL; | |||
mpages_info.key_offset = offsetof(struct mpage, inodev); | mpages_info.key_offset = offsetof(struct mpage, inodev); | ||
mlinks_info.key_offset = offsetof(struct mlink, file); | 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:ntu: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 ('t'): | case 'p': | ||
warnings = 1; | |||
break; | |||
case 'Q': | |||
mparse_options |= MPARSE_QUICK; | |||
break; | |||
case 'T': | |||
if (strcmp(optarg, "utf8")) { | |||
warnx("-T%s: Unsupported output format", | |||
optarg); | |||
goto usage; | |||
} | |||
write_utf8 = 1; | |||
break; | |||
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; | ||
} | } | ||
|
|
||
argv += optind; | argv += optind; | ||
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 = mchars_alloc(); | ||
MANDOCLEVEL_FATAL, NULL, NULL, NULL); | mp = mparse_alloc(mparse_options, MANDOCLEVEL_BADARG, NULL, | ||
mc = mchars_alloc(); | mchars, NULL); | ||
ohash_init(&mpages, 6, &mpages_info); | ohash_init(&mpages, 6, &mpages_info); | ||
ohash_init(&mlinks, 6, &mlinks_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]); | if (dbopen(1)) { | ||
if (0 == dbopen(1)) | /* | ||
goto out; | * The existing database is usable. Process | ||
if (OP_TEST != op) | * all files specified on the command-line. | ||
dbprune(); | */ | ||
use_all = 1; | |||
for (i = 0; i < argc; i++) | |||
filescan(argv[i]); | |||
if (OP_TEST != op) | |||
dbprune(); | |||
} else { | |||
/* | |||
* Database missing or corrupt. | |||
* Recreate from scratch. | |||
*/ | |||
exitcode = (int)MANDOCLEVEL_OK; | |||
op = OP_DEFAULT; | |||
if (0 == treescan()) | |||
goto out; | |||
if (0 == dbopen(0)) | |||
goto out; | |||
} | |||
if (OP_DELETE != op) | if (OP_DELETE != op) | ||
mpages_merge(mc, mp, 0); | mpages_merge(mp); | ||
dbclose(1); | dbclose(OP_DEFAULT == op ? 0 : 1); | ||
} 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, grok from manpath(1) or however else | ||
* manpath_parse() wants to do it. | * manconf_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; | ||
|
|
||
ohash_init(&mlinks, 6, &mlinks_info); | ohash_init(&mlinks, 6, &mlinks_info); | ||
} | } | ||
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])) | |||
goto out; | |||
if (0 == dbopen(0)) | if (0 == dbopen(0)) | ||
goto out; | continue; | ||
mpages_merge(mc, mp, warnings && !use_all); | mpages_merge(mp); | ||
if (warnings && !nodb && | |||
! (MPARSE_QUICK & mparse_options)) | |||
names_check(); | |||
dbclose(0); | dbclose(0); | ||
if (j + 1 < dirs.sz) { | if (j + 1 < conf.manpath.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(mchars); | |||
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 [-anvW] [-C file]\n" | fprintf(stderr, "usage: %s [-aDnpQ] [-C file] [-Tutf8]\n" | ||
" %s [-anvW] dir ...\n" | " %s [-aDnpQ] [-Tutf8] dir ...\n" | ||
" %s [-nvW] -d dir [file ...]\n" | " %s [-DnpQ] [-Tutf8] -d dir [file ...]\n" | ||
" %s [-nvW] -u dir [file ...]\n" | " %s [-Dnp] -u dir [file ...]\n" | ||
" %s -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 | ||
* | * | ||
|
|
||
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 dform, gzip; | ||
char *fsec; | char *dsec, *arch, *fsec, *cp; | ||
const char *dsec, *arch, *cp, *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. | ||
* Disallow duplicate (hard-linked) files. | |||
*/ | */ | ||
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 (inocheck(ff->fts_statp)) { | } | ||
if (warnings) | gzip = 0; | ||
say(path, "Duplicate file"); | fsec = NULL; | ||
continue; | while (fsec == NULL) { | ||
} else if (NULL == (fsec = | fsec = strrchr(ff->fts_name, '.'); | ||
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; | ||
} else | } else | ||
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; | ||
if (NULL != dsec) | mlink->dsec = dsec; | ||
mlink->dsec = mandoc_strdup(dsec); | mlink->arch = arch; | ||
if (NULL != arch) | mlink->name = ff->fts_name; | ||
mlink->arch = mandoc_strdup(arch); | mlink->fsec = fsec; | ||
mlink->name = mandoc_strdup(ff->fts_name); | mlink->gzip = gzip; | ||
if (NULL != fsec) | |||
mlink->fsec = mandoc_strdup(fsec); | |||
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. | ||
*/ | */ | ||
dsec = NULL; | |||
dform = FORM_NONE; | |||
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 { | |||
dform = FORM_NONE; | |||
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. | ||
*/ | */ | ||
arch = NULL; | if (ff->fts_info != FTS_DP && dsec != NULL) | ||
if (FTS_DP != ff->fts_info && NULL != dsec) | |||
arch = ff->fts_name; | arch = ff->fts_name; | ||
else | |||
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; | ||
} | } | ||
/* | /* | ||
* Add a file to the file vector. | * Add a file to the mlinks table. | ||
* Do not verify that it's a "valid" looking manpage (we'll do that | * Do not verify that it's a "valid" looking manpage (we'll do that | ||
* later). | * later). | ||
* | * | ||
* 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 | ||
* | * | ||
* Stuff this information directly into the mlink vector. | |||
* See treescan() for the fts(3) version of this. | * See treescan() for the fts(3) version of this. | ||
*/ | */ | ||
static void | static void | ||
|
|
||
if (0 == strncmp(file, "./", 2)) | if (0 == strncmp(file, "./", 2)) | ||
file += 2; | file += 2; | ||
if (NULL == realpath(file, buf)) { | /* | ||
* 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; | exitcode = (int)MANDOCLEVEL_BADARG; | ||
say(file, NULL); | say(file, "&lstat"); | ||
return; | return; | ||
} else if (OP_TEST != op && strstr(buf, basedir) != buf) { | } else if (0 == ((S_IFREG | S_IFLNK) & st.st_mode)) { | ||
exitcode = (int)MANDOCLEVEL_BADARG; | exitcode = (int)MANDOCLEVEL_BADARG; | ||
say("", "%s: outside base directory", buf); | say(file, "Not a regular file"); | ||
return; | return; | ||
} else if (-1 == stat(buf, &st)) { | } | ||
/* | |||
* We have to resolve the file name to the real path | |||
* in any case for the base directory check. | |||
*/ | |||
if (NULL == realpath(file, buf)) { | |||
exitcode = (int)MANDOCLEVEL_BADARG; | exitcode = (int)MANDOCLEVEL_BADARG; | ||
say(file, NULL); | say(file, "&realpath"); | ||
return; | return; | ||
} else if ( ! (S_IFREG & st.st_mode)) { | } | ||
if (OP_TEST == op) | |||
start = buf; | |||
else if (strstr(buf, basedir) == buf) | |||
start = buf + strlen(basedir); | |||
#ifdef HOMEBREWDIR | |||
else if (strstr(buf, HOMEBREWDIR) == buf) | |||
start = buf; | |||
#endif | |||
else { | |||
exitcode = (int)MANDOCLEVEL_BADARG; | exitcode = (int)MANDOCLEVEL_BADARG; | ||
say(file, "Not a regular file"); | say("", "%s: outside base directory", buf); | ||
return; | return; | ||
} else if (inocheck(&st)) { | |||
if (warnings) | |||
say(file, "Duplicate file"); | |||
return; | |||
} | } | ||
start = buf + strlen(basedir); | |||
/* | |||
* Now we are sure the file is inside our tree. | |||
* If it is a symbolic link, ignore the real path | |||
* and use the original name. | |||
* This implies passing stuff like "cat1/../man1/foo.1" | |||
* on the command line won't work. So don't do that. | |||
* Note the stat(2) can still fail if the link target | |||
* 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. | ||
|
|
||
*p++ = '\0'; | *p++ = '\0'; | ||
if (0 == strncmp(start, "man", 3)) { | if (0 == strncmp(start, "man", 3)) { | ||
mlink->dform = FORM_SRC; | mlink->dform = FORM_SRC; | ||
mlink->dsec = mandoc_strdup(start + 3); | mlink->dsec = start + 3; | ||
} else if (0 == strncmp(start, "cat", 3)) { | } else if (0 == strncmp(start, "cat", 3)) { | ||
mlink->dform = FORM_CAT; | mlink->dform = FORM_CAT; | ||
mlink->dsec = mandoc_strdup(start + 3); | mlink->dsec = start + 3; | ||
} | } | ||
start = p; | start = p; | ||
if (NULL != mlink->dsec && NULL != (p = strchr(start, '/'))) { | if (NULL != mlink->dsec && NULL != (p = strchr(start, '/'))) { | ||
*p++ = '\0'; | *p++ = '\0'; | ||
mlink->arch = mandoc_strdup(start); | mlink->arch = start; | ||
start = p; | start = p; | ||
} | } | ||
} | } | ||
|
|
||
if ('.' == *p) { | if ('.' == *p) { | ||
*p++ = '\0'; | *p++ = '\0'; | ||
mlink->fsec = mandoc_strdup(p); | mlink->fsec = p; | ||
} | } | ||
/* | /* | ||
|
|
||
mlink->name = p + 1; | mlink->name = p + 1; | ||
*p = '\0'; | *p = '\0'; | ||
} | } | ||
mlink->name = mandoc_strdup(mlink->name); | |||
mlink_add(mlink, &st); | mlink_add(mlink, &st); | ||
} | } | ||
static int | |||
inocheck(const struct stat *st) | |||
{ | |||
struct inodev inodev; | |||
uint32_t hash; | |||
memset(&inodev, 0, sizeof(inodev)); | |||
inodev.st_ino = hash = st->st_ino; | |||
inodev.st_dev = st->st_dev; | |||
return(NULL != ohash_find(&mpages, ohash_lookup_memory( | |||
&mpages, (char *)&inodev, sizeof(inodev), hash))); | |||
} | |||
static void | static void | ||
mlink_add(struct mlink *mlink, const struct stat *st) | mlink_add(struct mlink *mlink, const struct stat *st) | ||
{ | { | ||
|
|
||
assert(NULL != mlink->file); | assert(NULL != mlink->file); | ||
if (NULL == mlink->dsec) | mlink->dsec = mandoc_strdup(mlink->dsec ? mlink->dsec : ""); | ||
mlink->dsec = mandoc_strdup(""); | mlink->arch = mandoc_strdup(mlink->arch ? mlink->arch : ""); | ||
if (NULL == mlink->arch) | mlink->name = mandoc_strdup(mlink->name ? mlink->name : ""); | ||
mlink->arch = mandoc_strdup(""); | mlink->fsec = mandoc_strdup(mlink->fsec ? mlink->fsec : ""); | ||
if (NULL == mlink->name) | |||
mlink->name = mandoc_strdup(""); | |||
if (NULL == mlink->fsec) | |||
mlink->fsec = mandoc_strdup(""); | |||
if ('0' == *mlink->fsec) { | if ('0' == *mlink->fsec) { | ||
free(mlink->fsec); | free(mlink->fsec); | ||
|
|
||
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->inodev.st_dev = inodev.st_dev; | mpage->inodev.st_dev = inodev.st_dev; | ||
ohash_insert(&mpages, slot, mpage); | ohash_insert(&mpages, slot, mpage); | ||
} else | } else | ||
abort(); | mlink->next = mpage->mlinks; | ||
mpage->mlinks = mlink; | mpage->mlinks = mlink; | ||
mlink->mpage = mpage; | |||
} | } | ||
static void | static void | ||
|
|
||
mpage = ohash_first(&mpages, &slot); | mpage = ohash_first(&mpages, &slot); | ||
while (NULL != mpage) { | while (NULL != mpage) { | ||
while (NULL != (mlink = mpage->mlinks)) { | while (NULL != (mlink = mpage->mlinks)) { | ||
mpage->mlinks = NULL; | mpage->mlinks = mlink->next; | ||
mlink_free(mlink); | mlink_free(mlink); | ||
} | } | ||
free(mpage->sec); | free(mpage->sec); | ||
|
|
||
} | } | ||
/* | /* | ||
* For each mlink to the mpage, check whether the path looks like | |||
* it is formatted, and if it does, check whether a source manual | |||
* exists by the same name, ignoring the suffix. | |||
* If both conditions hold, drop the mlink. | |||
*/ | |||
static void | |||
mlinks_undupe(struct mpage *mpage) | |||
{ | |||
char buf[PATH_MAX]; | |||
struct mlink **prev; | |||
struct mlink *mlink; | |||
char *bufp; | |||
mpage->form = FORM_CAT; | |||
prev = &mpage->mlinks; | |||
while (NULL != (mlink = *prev)) { | |||
if (FORM_CAT != mlink->dform) { | |||
mpage->form = FORM_NONE; | |||
goto nextlink; | |||
} | |||
(void)strlcpy(buf, mlink->file, sizeof(buf)); | |||
bufp = strstr(buf, "cat"); | |||
assert(NULL != bufp); | |||
memcpy(bufp, "man", 3); | |||
if (NULL != (bufp = strrchr(buf, '.'))) | |||
*++bufp = '\0'; | |||
(void)strlcat(buf, mlink->dsec, sizeof(buf)); | |||
if (NULL == ohash_find(&mlinks, | |||
ohash_qlookup(&mlinks, buf))) | |||
goto nextlink; | |||
if (warnings) | |||
say(mlink->file, "Man source exists: %s", buf); | |||
if (use_all) | |||
goto nextlink; | |||
*prev = mlink->next; | |||
mlink_free(mlink); | |||
continue; | |||
nextlink: | |||
prev = &(*prev)->next; | |||
} | |||
} | |||
static void | |||
mlink_check(struct mpage *mpage, struct mlink *mlink) | |||
{ | |||
struct str *str; | |||
unsigned int slot; | |||
/* | |||
* Check whether the manual section given in a file | |||
* agrees with the directory where the file is located. | |||
* Some manuals have suffixes like (3p) on their | |||
* section number either inside the file or in the | |||
* directory name, some are linked into more than one | |||
* section, like encrypt(1) = makekey(8). | |||
*/ | |||
if (FORM_SRC == mpage->form && | |||
strcasecmp(mpage->sec, mlink->dsec)) | |||
say(mlink->file, "Section \"%s\" manual in %s directory", | |||
mpage->sec, mlink->dsec); | |||
/* | |||
* Manual page directories exist for each kernel | |||
* architecture as returned by machine(1). | |||
* However, many manuals only depend on the | |||
* application architecture as returned by arch(1). | |||
* For example, some (2/ARM) manuals are shared | |||
* across the "armish" and "zaurus" kernel | |||
* architectures. | |||
* A few manuals are even shared across completely | |||
* different architectures, for example fdformat(1) | |||
* on amd64, i386, sparc, and sparc64. | |||
*/ | |||
if (strcasecmp(mpage->arch, mlink->arch)) | |||
say(mlink->file, "Architecture \"%s\" manual in " | |||
"\"%s\" directory", mpage->arch, mlink->arch); | |||
/* | |||
* XXX | |||
* parse_cat() doesn't set NAME_TITLE yet. | |||
*/ | |||
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"); | |||
} | |||
/* | |||
* Run through the files in the global vector "mpages" | * Run through the files in the global vector "mpages" | ||
* and add them to the database specified in "basedir". | * and add them to the database specified in "basedir". | ||
* | * | ||
|
|
||
* 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, int check_reachable) | mpages_merge(struct mparse *mp) | ||
{ | { | ||
struct ohash title_table; | char any[] = "any"; | ||
struct ohash_info title_info, str_info; | struct ohash_info str_info; | ||
char buf[PATH_MAX]; | struct mpage *mpage, *mpage_dest; | ||
struct mpage *mpage; | struct mlink *mlink, *mlink_dest; | ||
struct mdoc *mdoc; | struct roff_man *man; | ||
struct man *man; | char *sodest; | ||
struct title *title_entry; | char *cp; | ||
char *bufp, *title_str; | int fd; | ||
const char *cp; | unsigned int pslot; | ||
size_t sz; | |||
int match; | |||
unsigned int pslot, tslot; | |||
enum mandoclevel lvl; | |||
str_info.alloc = hash_alloc; | str_info.alloc = hash_alloc; | ||
str_info.halloc = hash_halloc; | str_info.calloc = hash_calloc; | ||
str_info.hfree = hash_free; | str_info.free = hash_free; | ||
str_info.data = NULL; | |||
str_info.key_offset = offsetof(struct str, key); | str_info.key_offset = offsetof(struct str, key); | ||
if (check_reachable) { | if ( ! nodb) | ||
title_info.alloc = hash_alloc; | SQL_EXEC("BEGIN TRANSACTION"); | ||
title_info.halloc = hash_halloc; | |||
title_info.hfree = hash_free; | |||
title_info.key_offset = offsetof(struct title, title); | |||
ohash_init(&title_table, 6, &title_info); | |||
} | |||
mpage = ohash_first(&mpages, &pslot); | mpage = ohash_first(&mpages, &pslot); | ||
while (NULL != mpage) { | while (mpage != NULL) { | ||
/* | mlinks_undupe(mpage); | ||
* If we're a catpage (as defined by our path), then see | if ((mlink = mpage->mlinks) == NULL) { | ||
* if a manpage exists by the same name (ignoring the | mpage = ohash_next(&mpages, &pslot); | ||
* suffix). | continue; | ||
* If it does, then we want to use it instead of our | |||
* own. | |||
*/ | |||
if ( ! use_all && FORM_CAT == mpage->mlinks->dform) { | |||
sz = strlcpy(buf, mpage->mlinks->file, PATH_MAX); | |||
if (sz >= PATH_MAX) { | |||
if (warnings) | |||
say(mpage->mlinks->file, | |||
"Filename too long"); | |||
mpage = ohash_next(&mpages, &pslot); | |||
continue; | |||
} | |||
bufp = strstr(buf, "cat"); | |||
assert(NULL != bufp); | |||
memcpy(bufp, "man", 3); | |||
if (NULL != (bufp = strrchr(buf, '.'))) | |||
*++bufp = '\0'; | |||
strlcat(buf, mpage->mlinks->dsec, PATH_MAX); | |||
if (NULL != ohash_find(&mlinks, | |||
ohash_qlookup(&mlinks, buf))) { | |||
if (warnings) | |||
say(mpage->mlinks->file, "Man " | |||
"source exists: %s", buf); | |||
mpage = ohash_next(&mpages, &pslot); | |||
continue; | |||
} | |||
} | } | ||
name_mask = NAME_MASK; | |||
ohash_init(&names, 4, &str_info); | |||
ohash_init(&strings, 6, &str_info); | ohash_init(&strings, 6, &str_info); | ||
mparse_reset(mp); | mparse_reset(mp); | ||
mdoc = NULL; | |||
man = NULL; | man = NULL; | ||
match = 1; | sodest = NULL; | ||
mparse_open(mp, &fd, mlink->file); | |||
if (fd == -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); | mparse_result(mp, &man, &sodest); | ||
if (lvl < MANDOCLEVEL_FATAL) | |||
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->pageid) | |||
dbadd_mlink_name(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) { | |||
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) { | |||
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); | |||
if (*mpage->arch != '\0') | |||
putkey(mpage, mpage->arch, TYPE_arch); | |||
/* | for ( ; mlink != NULL; mlink = mlink->next) { | ||
* Check whether the manual section given in a file | if ('\0' != *mlink->dsec) | ||
* agrees with the directory where the file is located. | putkey(mpage, mlink->dsec, TYPE_sec); | ||
* Some manuals have suffixes like (3p) on their | if ('\0' != *mlink->fsec) | ||
* section number either inside the file or in the | putkey(mpage, mlink->fsec, TYPE_sec); | ||
* directory name, some are linked into more than one | putkey(mpage, '\0' == *mlink->arch ? | ||
* section, like encrypt(1) = makekey(8). Do not skip | any : mlink->arch, TYPE_arch); | ||
* manuals for such reasons. | putkey(mpage, mlink->name, NAME_FILE); | ||
*/ | |||
if (warnings && !use_all && FORM_SRC == mpage->form && | |||
strcasecmp(mpage->sec, mpage->mlinks->dsec)) { | |||
match = 0; | |||
say(mpage->mlinks->file, "Section \"%s\" " | |||
"manual in %s directory", | |||
mpage->sec, mpage->mlinks->dsec); | |||
} | } | ||
/* | assert(mpage->desc == NULL); | ||
* Manual page directories exist for each kernel | if (man != NULL && man->macroset == MACROSET_MDOC) | ||
* architecture as returned by machine(1). | parse_mdoc(mpage, &man->meta, man->first); | ||
* However, many manuals only depend on the | else if (man != NULL) | ||
* application architecture as returned by arch(1). | parse_man(mpage, &man->meta, man->first); | ||
* For example, some (2/ARM) manuals are shared | |||
* across the "armish" and "zaurus" kernel | |||
* architectures. | |||
* A few manuals are even shared across completely | |||
* different architectures, for example fdformat(1) | |||
* on amd64, i386, sparc, and sparc64. | |||
* Thus, warn about architecture mismatches, | |||
* but don't skip manuals for this reason. | |||
*/ | |||
if (warnings && !use_all && | |||
strcasecmp(mpage->arch, mpage->mlinks->arch)) { | |||
match = 0; | |||
say(mpage->mlinks->file, "Architecture \"%s\" " | |||
"manual in \"%s\" directory", | |||
mpage->arch, mpage->mlinks->arch); | |||
} | |||
if (warnings && !use_all && | |||
strcasecmp(mpage->title, mpage->mlinks->name)) | |||
match = 0; | |||
putkey(mpage, mpage->mlinks->name, TYPE_Nm); | |||
if (NULL != mdoc) { | |||
if (NULL != (cp = mdoc_meta(mdoc)->name)) | |||
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 | else | ||
parse_cat(mpage); | parse_cat(mpage, fd); | ||
if (mpage->desc == NULL) | |||
mpage->desc = mandoc_strdup(mpage->mlinks->name); | |||
/* | if (warnings && !use_all) | ||
* Build a title string for the file. If it matches | for (mlink = mpage->mlinks; mlink; | ||
* the location of the file, remember the title as | mlink = mlink->next) | ||
* found; else, remember it as missing. | mlink_check(mpage, mlink); | ||
*/ | |||
if (check_reachable) { | dbadd(mpage); | ||
if (-1 == asprintf(&title_str, "%s(%s%s%s)", | mlink = mpage->mlinks; | ||
mpage->title, mpage->sec, | |||
'\0' == *mpage->arch ? "" : "/", | |||
mpage->arch)) { | |||
perror(NULL); | |||
exit((int)MANDOCLEVEL_SYSERR); | |||
} | |||
tslot = ohash_qlookup(&title_table, title_str); | |||
title_entry = ohash_find(&title_table, tslot); | |||
if (NULL == title_entry) { | |||
title_entry = mandoc_malloc( | |||
sizeof(struct title)); | |||
title_entry->title = title_str; | |||
title_entry->file = mandoc_strdup( | |||
match ? "" : mpage->mlinks->file); | |||
ohash_insert(&title_table, tslot, | |||
title_entry); | |||
} else { | |||
if (match) | |||
*title_entry->file = '\0'; | |||
free(title_str); | |||
} | |||
} | |||
dbindex(mpage, mc); | nextpage: | ||
ohash_delete(&strings); | ohash_delete(&strings); | ||
ohash_delete(&names); | |||
mpage = ohash_next(&mpages, &pslot); | mpage = ohash_next(&mpages, &pslot); | ||
} | } | ||
if (check_reachable) { | if (0 == nodb) | ||
title_entry = ohash_first(&title_table, &tslot); | SQL_EXEC("END TRANSACTION"); | ||
while (NULL != title_entry) { | } | ||
if ('\0' != *title_entry->file) | |||
say(title_entry->file, | static void | ||
"Probably unreachable, title is %s", | names_check(void) | ||
title_entry->title); | { | ||
free(title_entry->title); | sqlite3_stmt *stmt; | ||
free(title_entry->file); | const char *name, *sec, *arch, *key; | ||
free(title_entry); | int irc; | ||
title_entry = ohash_next(&title_table, &tslot); | |||
} | sqlite3_prepare_v2(db, | ||
ohash_delete(&title_table); | "SELECT name, sec, arch, key FROM (" | ||
"SELECT name AS key, pageid FROM names " | |||
"WHERE bits & ? AND NOT EXISTS (" | |||
"SELECT pageid FROM mlinks " | |||
"WHERE mlinks.pageid == names.pageid " | |||
"AND mlinks.name == names.name" | |||
")" | |||
") JOIN (" | |||
"SELECT sec, arch, name, pageid FROM mlinks " | |||
"GROUP BY pageid" | |||
") USING (pageid);", | |||
-1, &stmt, NULL); | |||
if (SQLITE_OK != sqlite3_bind_int64(stmt, 1, NAME_TITLE)) | |||
say("", "%s", sqlite3_errmsg(db)); | |||
while (SQLITE_ROW == (irc = sqlite3_step(stmt))) { | |||
name = (const char *)sqlite3_column_text(stmt, 0); | |||
sec = (const char *)sqlite3_column_text(stmt, 1); | |||
arch = (const char *)sqlite3_column_text(stmt, 2); | |||
key = (const char *)sqlite3_column_text(stmt, 3); | |||
say("", "%s(%s%s%s) lacks mlink \"%s\"", name, sec, | |||
'\0' == *arch ? "" : "/", | |||
'\0' == *arch ? "" : arch, key); | |||
} | } | ||
sqlite3_finalize(stmt); | |||
} | } | ||
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 len, plen, titlesz; | ||
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; | ||
} | } | ||
|
|
||
while (NULL != (line = fgetln(stream, &len))) | while (NULL != (line = fgetln(stream, &len))) | ||
if ('\n' != *line && ' ' != *line) | if ('\n' != *line && ' ' != *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, | ||
|
|
||
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; | ||
|
|
||
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); | ||
} | } | ||
|
|
||
* Put a type/word pair into the word database for this particular file. | * Put a type/word pair into the word database for this particular file. | ||
*/ | */ | ||
static void | static void | ||
putkey(const struct mpage *mpage, const char *value, uint64_t type) | putkey(const struct mpage *mpage, char *value, uint64_t type) | ||
{ | { | ||
char *cp; | |||
assert(NULL != value); | 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 (NULL == n) | ||
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); | assert(body->parent); | ||
if (NULL != (head = body->parent->head) && | if (NULL != (head = body->parent->head) && | ||
1 == head->nchild && | 1 == head->nchild && | ||
NULL != (head = (head->child)) && | NULL != (head = (head->child)) && | ||
MAN_TEXT == head->type && | head->type == ROFFT_TEXT && | ||
0 == strcmp(head->string, "NAME") && | 0 == strcmp(head->string, "NAME") && | ||
NULL != (body = body->child) && | NULL != 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]) | ||
|
|
||
byte = start[sz]; | byte = start[sz]; | ||
start[sz] = '\0'; | start[sz] = '\0'; | ||
putkey(mpage, start, TYPE_Nm); | /* | ||
* Assume a stray trailing comma in the | |||
* name list if a name begins with a dash. | |||
*/ | |||
if ('-' == start[0] || | |||
('\\' == start[0] && '-' == start[1])) | |||
break; | |||
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; | ||
break; | break; | ||
|
|
||
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(1); | return 0; | ||
} | } | ||
static int | static void | ||
parse_mdoc_In(struct mpage *mpage, const struct mdoc_node *n) | parse_mdoc_fname(struct mpage *mpage, const struct roff_node *n) | ||
{ | { | ||
char *cp; | |||
size_t sz; | |||
if (NULL != n->child && MDOC_TEXT == n->child->type) | if (n->type != ROFFT_TEXT) | ||
return(0); | return; | ||
putkey(mpage, n->child->string, TYPE_In); | /* Skip function pointer punctuation. */ | ||
return(1); | |||
cp = n->string; | |||
while (*cp == '(' || *cp == '*') | |||
cp++; | |||
sz = strcspn(cp, "()"); | |||
putkeys(mpage, cp, sz, TYPE_Fn); | |||
if (n->sec == SEC_SYNOPSIS) | |||
putkeys(mpage, cp, sz, NAME_SYN); | |||
} | } | ||
static int | static int | ||
parse_mdoc_Fn(struct mpage *mpage, const struct mdoc_node *n) | parse_mdoc_Fn(struct mpage *mpage, const struct roff_meta *meta, | ||
const struct roff_node *n) | |||
{ | { | ||
const char *cp; | |||
if (NULL == (n = n->child) || MDOC_TEXT != n->type) | if (n->child == NULL) | ||
return(0); | return 0; | ||
/* | parse_mdoc_fname(mpage, n->child); | ||
* 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, ' '))) | for (n = n->child->next; n != NULL; n = n->next) | ||
cp = n->string; | if (n->type == ROFFT_TEXT) | ||
while ('*' == *cp) | |||
cp++; | |||
putkey(mpage, cp, TYPE_Fn); | |||
if (n->string < cp) | |||
putkeys(mpage, n->string, cp - n->string, TYPE_Ft); | |||
for (n = n->next; NULL != n; n = n->next) | |||
if (MDOC_TEXT == n->type) | |||
putkey(mpage, n->string, TYPE_Fa); | putkey(mpage, n->string, TYPE_Fa); | ||
return(0); | return 0; | ||
} | } | ||
static int | static int | ||
parse_mdoc_St(struct mpage *mpage, const struct mdoc_node *n) | parse_mdoc_Fo(struct mpage *mpage, const struct roff_meta *meta, | ||
const struct roff_node *n) | |||
{ | { | ||
if (NULL == n->child || MDOC_TEXT != n->child->type) | if (n->type != ROFFT_HEAD) | ||
return(0); | return 1; | ||
putkey(mpage, n->child->string, TYPE_St); | if (n->child != NULL) | ||
return(1); | parse_mdoc_fname(mpage, n->child); | ||
return 0; | |||
} | } | ||
static int | static int | ||
parse_mdoc_Xr(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; | char *cp; | ||
if (NULL == (n = n->child)) | if (NULL == (n = n->child)) | ||
return(0); | return 0; | ||
if (NULL == n->next) { | if (NULL == n->next) { | ||
putkey(mpage, n->string, TYPE_Xr); | putkey(mpage, n->string, TYPE_Xr); | ||
return(0); | return 0; | ||
} | } | ||
if (-1 == asprintf(&cp, "%s(%s)", n->string, n->next->string)) { | mandoc_asprintf(&cp, "%s(%s)", n->string, n->next->string); | ||
perror(NULL); | |||
exit((int)MANDOCLEVEL_SYSERR); | |||
} | |||
putkey(mpage, cp, TYPE_Xr); | putkey(mpage, cp, TYPE_Xr); | ||
free(cp); | free(cp); | ||
return(0); | return 0; | ||
} | } | ||
static int | static int | ||
parse_mdoc_Nd(struct mpage *mpage, const struct mdoc_node *n) | parse_mdoc_Nd(struct mpage *mpage, const struct roff_meta *meta, | ||
const struct roff_node *n) | |||
{ | { | ||
size_t sz; | |||
if (MDOC_BODY != n->type) | if (n->type == ROFFT_BODY) | ||
return(0); | deroff(&mpage->desc, n); | ||
return 0; | |||
/* | |||
* Special-case the `Nd' because we need to put the description | |||
* into the document table. | |||
*/ | |||
for (n = n->child; NULL != n; n = n->next) { | |||
if (MDOC_TEXT == n->type) { | |||
if (NULL != mpage->desc) { | |||
sz = strlen(mpage->desc) + | |||
strlen(n->string) + 2; | |||
mpage->desc = mandoc_realloc( | |||
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); | |||
} | } | ||
static int | static int | ||
parse_mdoc_Nm(struct mpage *mpage, const struct mdoc_node *n) | parse_mdoc_Nm(struct mpage *mpage, const struct roff_meta *meta, | ||
const struct roff_node *n) | |||
{ | { | ||
if (SEC_NAME == n->sec) | if (SEC_NAME == n->sec) | ||
return(1); | putmdockey(mpage, n->child, NAME_TITLE); | ||
else if (SEC_SYNOPSIS != n->sec || MDOC_HEAD != n->type) | else if (n->sec == SEC_SYNOPSIS && n->type == ROFFT_HEAD) { | ||
return(0); | if (n->child == NULL) | ||
putkey(mpage, meta->name, NAME_SYN); | |||
return(1); | 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, ROFFT_HEAD); | |||
mpage->name_head_done = 1; | |||
} | |||
return 0; | |||
} | } | ||
static int | static int | ||
parse_mdoc_Sh(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(SEC_CUSTOM == n->sec && MDOC_HEAD == n->type); | return n->sec == SEC_CUSTOM && n->type == ROFFT_HEAD; | ||
} | } | ||
static int | static int | ||
parse_mdoc_head(struct mpage *mpage, const struct mdoc_node *n) | parse_mdoc_head(struct mpage *mpage, const struct roff_meta *meta, | ||
const struct roff_node *n) | |||
{ | { | ||
return(MDOC_HEAD == n->type); | return n->type == ROFFT_HEAD; | ||
} | } | ||
static int | static int | ||
parse_mdoc_body(struct mpage *mpage, const struct mdoc_node *n) | parse_mdoc_body(struct mpage *mpage, const struct roff_meta *meta, | ||
const struct roff_node *n) | |||
{ | { | ||
return(MDOC_BODY == n->type); | return n->type == ROFFT_BODY; | ||
} | } | ||
/* | /* | ||
|
|
||
* 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; | ||
unsigned int slot; | |||
const char *end; | const char *end; | ||
unsigned int slot; | |||
int i, mustfree; | |||
if (0 == sz) | if (0 == sz) | ||
return; | return; | ||
mustfree = render_string(&cp, &sz); | |||
if (TYPE_Nm & v) { | |||
htab = &names; | |||
v &= name_mask; | |||
if (v & NAME_FIRST) | |||
name_mask &= ~NAME_FIRST; | |||
if (debug > 1) | |||
say(mpage->mlinks->file, | |||
"Adding name %*s, bits=%d", sz, cp, v); | |||
} else { | |||
htab = &strings; | |||
if (debug > 1) | |||
for (i = 0; i < mansearch_keymax; i++) | |||
if ((uint64_t)1 << i & v) | |||
say(mpage->mlinks->file, | |||
"Adding key %s=%*s", | |||
mansearch_keynames[i], 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 UTF-8 version of a key, or alias the pointer if the key has | * If the string contains escape sequences, | ||
* no UTF-8 transcription marks in it. | * 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 | ||
utf8key(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->utf8); | |||
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->utf8 = 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++; | |||
u = 0; | |||
/* | /* | ||
* 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 | |||
((const char **)&val, &seq, &len); | |||
if (ESCAPE_ERROR == esc) | |||
break; | |||
if (ESCAPE_SPECIAL != esc) | scp++; | ||
if (mandoc_escape(&scp, &seq, &seqlen) != ESCAPE_SPECIAL) | |||
continue; | continue; | ||
if (0 == (u = mchars_spec2cp(mc, seq, len))) | |||
continue; | |||
/* | /* | ||
* If we have a Unicode codepoint, try to convert that | * Render the special character | ||
* to a UTF-8 byte string. | * as either UTF-8 or ASCII. | ||
*/ | */ | ||
cpp = utfbuf; | |||
if (0 == (sz = utf8(u, utfbuf))) | |||
continue; | |||
if (write_utf8) { | |||
unicode = mchars_spec2cp(mchars, seq, seqlen); | |||
if (unicode <= 0) | |||
continue; | |||
addsz = utf8(unicode, utfbuf); | |||
if (addsz == 0) | |||
continue; | |||
addcp = utfbuf; | |||
} else { | |||
addcp = mchars_spec2str(mchars, seq, seqlen, &addsz); | |||
if (addcp == NULL) | |||
continue; | |||
if (*addcp == ASCII_NBRSP) { | |||
addcp = " "; | |||
addsz = 1; | |||
} | |||
} | |||
/* Copy the rendered glyph into the stream. */ | /* Copy the rendered glyph into the stream. */ | ||
sz = strlen(cpp); | ssz += addsz; | ||
bsz += sz; | dst = mandoc_realloc(dst, ssz + 1); | ||
memcpy(dst + dsz, addcp, addsz); | |||
dsz += addsz; | |||
} | |||
if (dst != NULL) { | |||
*public = dst; | |||
*psz = dsz; | |||
} | |||
buf = mandoc_realloc(buf, bsz); | /* Trim trailing whitespace and NUL-terminate. */ | ||
memcpy(&buf[pos], cpp, sz); | while (*psz > 0 && (*public)[*psz - 1] == ' ') | ||
pos += sz; | --*psz; | ||
if (dst != NULL) { | |||
(*public)[*psz] = '\0'; | |||
return 1; | |||
} else | |||
return 0; | |||
} | |||
static void | |||
dbadd_mlink(const struct mlink *mlink) | |||
{ | |||
size_t i; | |||
i = 1; | |||
SQL_BIND_TEXT(stmts[STMT_INSERT_LINK], i, mlink->dsec); | |||
SQL_BIND_TEXT(stmts[STMT_INSERT_LINK], i, mlink->arch); | |||
SQL_BIND_TEXT(stmts[STMT_INSERT_LINK], i, mlink->name); | |||
SQL_BIND_INT64(stmts[STMT_INSERT_LINK], i, mlink->mpage->pageid); | |||
SQL_STEP(stmts[STMT_INSERT_LINK]); | |||
sqlite3_reset(stmts[STMT_INSERT_LINK]); | |||
} | |||
static void | |||
dbadd_mlink_name(const struct mlink *mlink) | |||
{ | |||
uint64_t bits; | |||
size_t i; | |||
dbadd_mlink(mlink); | |||
i = 1; | |||
SQL_BIND_INT64(stmts[STMT_SELECT_NAME], i, mlink->mpage->pageid); | |||
bits = NAME_FILE & NAME_MASK; | |||
if (sqlite3_step(stmts[STMT_SELECT_NAME]) == SQLITE_ROW) { | |||
bits |= sqlite3_column_int64(stmts[STMT_SELECT_NAME], 0); | |||
sqlite3_reset(stmts[STMT_SELECT_NAME]); | |||
} | } | ||
buf[pos] = '\0'; | i = 1; | ||
key->utf8 = buf; | SQL_BIND_INT64(stmts[STMT_INSERT_NAME], i, bits); | ||
SQL_BIND_TEXT(stmts[STMT_INSERT_NAME], i, mlink->name); | |||
SQL_BIND_INT64(stmts[STMT_INSERT_NAME], i, mlink->mpage->pageid); | |||
SQL_STEP(stmts[STMT_INSERT_NAME]); | |||
sqlite3_reset(stmts[STMT_INSERT_NAME]); | |||
} | } | ||
/* | /* | ||
* 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 | * Wrap the entire set of additions in a transaction to make sqlite be a | ||
* little faster. | * little faster. | ||
* Also, UTF-8-encode the description at the last possible moment. | * Also, handle escape sequences at the last possible moment. | ||
*/ | */ | ||
static void | static void | ||
dbindex(const struct mpage *mpage, struct mchars *mc) | dbadd(struct mpage *mpage) | ||
{ | { | ||
struct mlink *mlink; | |||
struct str *key; | struct str *key; | ||
const char *desc; | char *cp; | ||
int64_t recno; | |||
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 index"); | |||
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; | ||
desc = ""; | |||
if (NULL != mpage->desc && '\0' != *mpage->desc) { | |||
key = ohash_find(&strings, | |||
ohash_qlookup(&strings, mpage->desc)); | |||
assert(NULL != key); | |||
if (NULL == key->utf8) | |||
utf8key(mc, key); | |||
desc = key->utf8; | |||
} | } | ||
SQL_EXEC("BEGIN TRANSACTION"); | if (debug) | ||
say(mlink->file, "Adding to database"); | |||
cp = mpage->desc; | |||
i = strlen(cp); | |||
mustfree = render_string(&cp, &i); | |||
i = 1; | i = 1; | ||
/* | SQL_BIND_TEXT(stmts[STMT_INSERT_PAGE], i, cp); | ||
* XXX The following three lines are obsolete | SQL_BIND_INT(stmts[STMT_INSERT_PAGE], i, mpage->form); | ||
* and only kept for backward compatibility | |||
* until apropos(1) and friends have caught up. | |||
*/ | |||
SQL_BIND_TEXT(stmts[STMT_INSERT_PAGE], i, mpage->mlinks->file); | |||
SQL_BIND_TEXT(stmts[STMT_INSERT_PAGE], i, mpage->mlinks->dsec); | |||
SQL_BIND_TEXT(stmts[STMT_INSERT_PAGE], i, mpage->mlinks->arch); | |||
SQL_BIND_TEXT(stmts[STMT_INSERT_PAGE], i, desc); | |||
SQL_BIND_INT(stmts[STMT_INSERT_PAGE], i, FORM_SRC == mpage->form); | |||
SQL_STEP(stmts[STMT_INSERT_PAGE]); | SQL_STEP(stmts[STMT_INSERT_PAGE]); | ||
recno = sqlite3_last_insert_rowid(db); | mpage->pageid = sqlite3_last_insert_rowid(db); | ||
sqlite3_reset(stmts[STMT_INSERT_PAGE]); | sqlite3_reset(stmts[STMT_INSERT_PAGE]); | ||
if (mustfree) | |||
free(cp); | |||
i = 1; | while (NULL != mlink) { | ||
SQL_BIND_TEXT(stmts[STMT_INSERT_LINK], i, mpage->mlinks->dsec); | dbadd_mlink(mlink); | ||
SQL_BIND_TEXT(stmts[STMT_INSERT_LINK], i, mpage->mlinks->arch); | mlink = mlink->next; | ||
SQL_BIND_TEXT(stmts[STMT_INSERT_LINK], i, mpage->mlinks->file); | } | ||
SQL_BIND_INT64(stmts[STMT_INSERT_LINK], i, recno); | mlink = mpage->mlinks; | ||
SQL_STEP(stmts[STMT_INSERT_LINK]); | |||
sqlite3_reset(stmts[STMT_INSERT_LINK]); | |||
for (key = ohash_first(&names, &slot); NULL != key; | |||
key = ohash_next(&names, &slot)) { | |||
assert(key->mpage == mpage); | |||
i = 1; | |||
SQL_BIND_INT64(stmts[STMT_INSERT_NAME], i, key->mask); | |||
SQL_BIND_TEXT(stmts[STMT_INSERT_NAME], i, key->key); | |||
SQL_BIND_INT64(stmts[STMT_INSERT_NAME], i, mpage->pageid); | |||
SQL_STEP(stmts[STMT_INSERT_NAME]); | |||
sqlite3_reset(stmts[STMT_INSERT_NAME]); | |||
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->utf8) | |||
utf8key(mc, key); | |||
i = 1; | i = 1; | ||
SQL_BIND_INT64(stmts[STMT_INSERT_KEY], i, key->mask); | SQL_BIND_INT64(stmts[STMT_INSERT_KEY], i, key->mask); | ||
SQL_BIND_TEXT(stmts[STMT_INSERT_KEY], i, key->utf8); | SQL_BIND_TEXT(stmts[STMT_INSERT_KEY], i, key->key); | ||
SQL_BIND_INT64(stmts[STMT_INSERT_KEY], i, recno); | SQL_BIND_INT64(stmts[STMT_INSERT_KEY], i, mpage->pageid); | ||
SQL_STEP(stmts[STMT_INSERT_KEY]); | SQL_STEP(stmts[STMT_INSERT_KEY]); | ||
sqlite3_reset(stmts[STMT_INSERT_KEY]); | sqlite3_reset(stmts[STMT_INSERT_KEY]); | ||
if (key->utf8 != key->key) | |||
free(key->utf8); | |||
free(key); | free(key); | ||
} | } | ||
SQL_EXEC("END TRANSACTION"); | |||
} | } | ||
static void | static void | ||
|
|
||
size_t i; | size_t i; | ||
unsigned int slot; | unsigned int slot; | ||
if (nodb) | if (0 == nodb) | ||
return; | SQL_EXEC("BEGIN TRANSACTION"); | ||
mpage = ohash_first(&mpages, &slot); | for (mpage = ohash_first(&mpages, &slot); NULL != mpage; | ||
while (NULL != mpage) { | mpage = ohash_next(&mpages, &slot)) { | ||
mlink = mpage->mlinks; | mlink = mpage->mlinks; | ||
i = 1; | if (debug) | ||
SQL_BIND_TEXT(stmts[STMT_DELETE_PAGE], i, mlink->file); | say(mlink->file, "Deleting from database"); | ||
SQL_STEP(stmts[STMT_DELETE_PAGE]); | if (nodb) | ||
sqlite3_reset(stmts[STMT_DELETE_PAGE]); | continue; | ||
if (verb) | for ( ; NULL != mlink; mlink = mlink->next) { | ||
say(mlink->file, "Deleted from index"); | i = 1; | ||
mpage = ohash_next(&mpages, &slot); | 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"); | |||
} | } | ||
/* | /* | ||
|
|
||
dbclose(int real) | dbclose(int real) | ||
{ | { | ||
size_t i; | size_t i; | ||
int status; | |||
pid_t child; | |||
if (nodb) | if (nodb) | ||
return; | return; | ||
|
|
||
if (real) | if (real) | ||
return; | return; | ||
if (-1 == rename(MANDOC_DB "~", MANDOC_DB)) { | if ('\0' == *tempfilename) { | ||
if (-1 == rename(MANDOC_DB "~", MANDOC_DB)) { | |||
exitcode = (int)MANDOCLEVEL_SYSERR; | |||
say(MANDOC_DB, "&rename"); | |||
} | |||
return; | |||
} | |||
switch (child = fork()) { | |||
case -1: | |||
exitcode = (int)MANDOCLEVEL_SYSERR; | exitcode = (int)MANDOCLEVEL_SYSERR; | ||
say(MANDOC_DB, NULL); | say("", "&fork cmp"); | ||
return; | |||
case 0: | |||
execlp("cmp", "cmp", "-s", | |||
tempfilename, MANDOC_DB, (char *)NULL); | |||
say("", "&exec cmp"); | |||
exit(0); | |||
default: | |||
break; | |||
} | } | ||
if (-1 == waitpid(child, &status, 0)) { | |||
exitcode = (int)MANDOCLEVEL_SYSERR; | |||
say("", "&wait cmp"); | |||
} 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"); | |||
} | |||
*strrchr(tempfilename, '/') = '\0'; | |||
switch (child = fork()) { | |||
case -1: | |||
exitcode = (int)MANDOCLEVEL_SYSERR; | |||
say("", "&fork rm"); | |||
return; | |||
case 0: | |||
execlp("rm", "rm", "-rf", tempfilename, (char *)NULL); | |||
say("", "&exec rm"); | |||
exit((int)MANDOCLEVEL_SYSERR); | |||
default: | |||
break; | |||
} | |||
if (-1 == waitpid(child, &status, 0)) { | |||
exitcode = (int)MANDOCLEVEL_SYSERR; | |||
say("", "&wait rm"); | |||
} else if (WIFSIGNALED(status) || WEXITSTATUS(status)) { | |||
exitcode = (int)MANDOCLEVEL_SYSERR; | |||
say("", "%s: Cannot remove temporary directory", | |||
tempfilename); | |||
} | |||
} | } | ||
/* | /* | ||
|
|
||
static int | static int | ||
dbopen(int real) | dbopen(int real) | ||
{ | { | ||
const char *file, *sql; | const char *sql; | ||
int rc, ofl; | int rc, ofl; | ||
if (nodb) | if (nodb) | ||
return(1); | return 1; | ||
*tempfilename = '\0'; | |||
ofl = SQLITE_OPEN_READWRITE; | ofl = SQLITE_OPEN_READWRITE; | ||
if (0 == real) { | |||
file = MANDOC_DB "~"; | if (real) { | ||
if (-1 == remove(file) && ENOENT != errno) { | rc = sqlite3_open_v2(MANDOC_DB, &db, ofl, NULL); | ||
if (SQLITE_OK != rc) { | |||
exitcode = (int)MANDOCLEVEL_SYSERR; | exitcode = (int)MANDOCLEVEL_SYSERR; | ||
say(file, NULL); | if (SQLITE_CANTOPEN != rc) | ||
return(0); | say(MANDOC_DB, "%s", sqlite3_errstr(rc)); | ||
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; | goto prepare_statements; | ||
if (SQLITE_CANTOPEN != rc) { | |||
exitcode = (int)MANDOCLEVEL_SYSERR; | |||
say(file, NULL); | |||
return(0); | |||
} | } | ||
sqlite3_close(db); | ofl |= SQLITE_OPEN_CREATE | SQLITE_OPEN_EXCLUSIVE; | ||
db = NULL; | |||
if (SQLITE_OK != (rc = sqlite3_open(file, &db))) { | remove(MANDOC_DB "~"); | ||
rc = sqlite3_open_v2(MANDOC_DB "~", &db, ofl, NULL); | |||
if (SQLITE_OK == rc) | |||
goto create_tables; | |||
if (MPARSE_QUICK & mparse_options) { | |||
exitcode = (int)MANDOCLEVEL_SYSERR; | exitcode = (int)MANDOCLEVEL_SYSERR; | ||
say(file, NULL); | say(MANDOC_DB "~", "%s", sqlite3_errstr(rc)); | ||
return(0); | return 0; | ||
} | } | ||
/* | (void)strlcpy(tempfilename, "/tmp/mandocdb.XXXXXX", | ||
* XXX The first three columns in table mpages are obsolete | sizeof(tempfilename)); | ||
* and only kept for backward compatibility | if (NULL == mkdtemp(tempfilename)) { | ||
* until apropos(1) and friends have caught up. | exitcode = (int)MANDOCLEVEL_SYSERR; | ||
*/ | say("", "&%s", tempfilename); | ||
return 0; | |||
} | |||
(void)strlcat(tempfilename, "/" MANDOC_DB, | |||
sizeof(tempfilename)); | |||
rc = sqlite3_open_v2(tempfilename, &db, ofl, NULL); | |||
if (SQLITE_OK != rc) { | |||
exitcode = (int)MANDOCLEVEL_SYSERR; | |||
say("", "%s: %s", tempfilename, sqlite3_errstr(rc)); | |||
return 0; | |||
} | |||
create_tables: | |||
sql = "CREATE TABLE \"mpages\" (\n" | sql = "CREATE TABLE \"mpages\" (\n" | ||
" \"file\" TEXT NOT NULL,\n" | |||
" \"sec\" TEXT NOT NULL,\n" | |||
" \"arch\" TEXT NOT NULL,\n" | |||
" \"desc\" TEXT NOT NULL,\n" | " \"desc\" TEXT NOT NULL,\n" | ||
" \"form\" INTEGER NOT NULL,\n" | " \"form\" INTEGER NOT NULL,\n" | ||
" \"id\" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL\n" | " \"pageid\" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL\n" | ||
");\n" | ");\n" | ||
"\n" | "\n" | ||
"CREATE TABLE \"mlinks\" (\n" | "CREATE TABLE \"mlinks\" (\n" | ||
" \"sec\" TEXT NOT NULL,\n" | " \"sec\" TEXT NOT NULL,\n" | ||
" \"arch\" TEXT NOT NULL,\n" | " \"arch\" TEXT NOT NULL,\n" | ||
" \"name\" TEXT NOT NULL,\n" | " \"name\" TEXT NOT NULL,\n" | ||
" \"pageid\" INTEGER NOT NULL REFERENCES mpages(id) " | " \"pageid\" INTEGER NOT NULL REFERENCES mpages(pageid) " | ||
"ON DELETE CASCADE\n" | |||
");\n" | |||
"CREATE INDEX mlinks_pageid_idx ON mlinks (pageid);\n" | |||
"\n" | |||
"CREATE TABLE \"names\" (\n" | |||
" \"bits\" INTEGER NOT NULL,\n" | |||
" \"name\" TEXT NOT NULL,\n" | |||
" \"pageid\" INTEGER NOT NULL REFERENCES mpages(pageid) " | |||
"ON DELETE CASCADE,\n" | "ON DELETE CASCADE,\n" | ||
" \"id\" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL\n" | " UNIQUE (\"name\", \"pageid\") ON CONFLICT REPLACE\n" | ||
");\n" | ");\n" | ||
"\n" | "\n" | ||
"CREATE TABLE \"keys\" (\n" | "CREATE TABLE \"keys\" (\n" | ||
" \"bits\" INTEGER NOT NULL,\n" | " \"bits\" INTEGER NOT NULL,\n" | ||
" \"key\" TEXT NOT NULL,\n" | " \"key\" TEXT NOT NULL,\n" | ||
" \"pageid\" INTEGER NOT NULL REFERENCES mpages(id) " | " \"pageid\" INTEGER NOT NULL REFERENCES mpages(pageid) " | ||
"ON DELETE CASCADE,\n" | "ON DELETE CASCADE\n" | ||
" \"id\" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL\n" | |||
");\n" | ");\n" | ||
"\n" | "CREATE INDEX keys_pageid_idx ON keys (pageid);\n"; | ||
"CREATE INDEX \"key_index\" ON keys (key);\n"; | |||
if (SQLITE_OK != sqlite3_exec(db, sql, NULL, NULL, NULL)) { | 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(MANDOC_DB, "%s", sqlite3_errmsg(db)); | ||
return(0); | sqlite3_close(db); | ||
return 0; | |||
} | } | ||
prepare_statements: | prepare_statements: | ||
SQL_EXEC("PRAGMA foreign_keys = ON"); | if (SQLITE_OK != sqlite3_exec(db, | ||
sql = "DELETE FROM mpages where file=?"; | "PRAGMA foreign_keys = ON", NULL, NULL, NULL)) { | ||
exitcode = (int)MANDOCLEVEL_SYSERR; | |||
say(MANDOC_DB, "PRAGMA foreign_keys: %s", | |||
sqlite3_errmsg(db)); | |||
sqlite3_close(db); | |||
return 0; | |||
} | |||
sql = "DELETE FROM mpages WHERE pageid IN " | |||
"(SELECT pageid FROM mlinks WHERE " | |||
"sec=? AND arch=? AND name=?)"; | |||
sqlite3_prepare_v2(db, sql, -1, &stmts[STMT_DELETE_PAGE], NULL); | sqlite3_prepare_v2(db, sql, -1, &stmts[STMT_DELETE_PAGE], NULL); | ||
sql = "INSERT INTO mpages " | sql = "INSERT INTO mpages " | ||
"(file,sec,arch,desc,form) VALUES (?,?,?,?,?)"; | "(desc,form) VALUES (?,?)"; | ||
sqlite3_prepare_v2(db, sql, -1, &stmts[STMT_INSERT_PAGE], NULL); | sqlite3_prepare_v2(db, sql, -1, &stmts[STMT_INSERT_PAGE], NULL); | ||
sql = "INSERT INTO mlinks " | sql = "INSERT INTO mlinks " | ||
"(sec,arch,name,pageid) VALUES (?,?,?,?)"; | "(sec,arch,name,pageid) VALUES (?,?,?,?)"; | ||
sqlite3_prepare_v2(db, sql, -1, &stmts[STMT_INSERT_LINK], NULL); | sqlite3_prepare_v2(db, sql, -1, &stmts[STMT_INSERT_LINK], NULL); | ||
sql = "SELECT bits FROM names where pageid = ?"; | |||
sqlite3_prepare_v2(db, sql, -1, &stmts[STMT_SELECT_NAME], NULL); | |||
sql = "INSERT INTO names " | |||
"(bits,name,pageid) VALUES (?,?,?)"; | |||
sqlite3_prepare_v2(db, sql, -1, &stmts[STMT_INSERT_NAME], NULL); | |||
sql = "INSERT INTO keys " | sql = "INSERT INTO keys " | ||
"(bits,key,pageid) VALUES (?,?,?)"; | "(bits,key,pageid) VALUES (?,?,?)"; | ||
sqlite3_prepare_v2(db, sql, -1, &stmts[STMT_INSERT_KEY], NULL); | sqlite3_prepare_v2(db, sql, -1, &stmts[STMT_INSERT_KEY], NULL); | ||
|
|
||
* synchronous mode for much better performance. | * synchronous mode for much better performance. | ||
*/ | */ | ||
if (real) | if (real && SQLITE_OK != sqlite3_exec(db, | ||
SQL_EXEC("PRAGMA synchronous = OFF"); | "PRAGMA synchronous = OFF", NULL, NULL, NULL)) { | ||
exitcode = (int)MANDOCLEVEL_SYSERR; | |||
say(MANDOC_DB, "PRAGMA synchronous: %s", | |||
sqlite3_errmsg(db)); | |||
sqlite3_close(db); | |||
return 0; | |||
} | |||
#endif | #endif | ||
return(1); | return 1; | ||
} | } | ||
static void * | static void * | ||
hash_halloc(size_t sz, void *arg) | hash_calloc(size_t n, size_t sz, void *arg) | ||
{ | { | ||
return(mandoc_calloc(sz, 1)); | return mandoc_calloc(n, sz); | ||
} | } | ||
static void * | static void * | ||
hash_alloc(size_t sz, void *arg) | hash_alloc(size_t sz, void *arg) | ||
{ | { | ||
return(mandoc_malloc(sz)); | return mandoc_malloc(sz); | ||
} | } | ||
static void | static void | ||
hash_free(void *p, size_t sz, void *arg) | hash_free(void *p, void *arg) | ||
{ | { | ||
free(p); | 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); | |||
} | } |