Return to mandocdb.c CVS log | Up to [cvsweb.bsd.lv] / mandoc |
version 1.81, 2013/12/27 01:16:54 | version 1.175, 2014/12/04 21:48:48 | ||
---|---|---|---|
|
|
||
/* $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, 2012, 2013, 2014 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 | ||
|
|
||
* 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 <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" | ||
|
|
||
#include "mdoc.h" | #include "mdoc.h" | ||
#include "man.h" | #include "man.h" | ||
#include "mandoc.h" | #include "mandoc.h" | ||
#include "mandoc_aux.h" | |||
#include "manpath.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_SRC, /* format is -man or -mdoc */ | |||
FORM_CAT, /* format is cat */ | |||
FORM_NONE /* format is unknown */ | |||
}; | |||
struct str { | struct str { | ||
char *utf8; /* key in UTF-8 form */ | char *rendered; /* key in UTF-8 or ASCII form */ | ||
const struct mpage *mpage; /* if set, the owning parse */ | const struct mpage *mpage; /* if set, the owning parse */ | ||
uint64_t mask; /* bitmask in sequence */ | uint64_t mask; /* bitmask in sequence */ | ||
char key[]; /* the string itself */ | char key[]; /* may contain escape sequences */ | ||
}; | }; | ||
struct inodev { | struct inodev { | ||
|
|
||
struct mpage { | struct mpage { | ||
struct inodev inodev; /* used for hashing routine */ | struct inodev inodev; /* used for hashing routine */ | ||
enum form dform; /* path-cued form */ | int64_t pageid; /* pageid in mpages SQL table */ | ||
enum form sform; /* suffix-cued form */ | char *sec; /* section from file content */ | ||
char file[PATH_MAX]; /* filename rel. to manpath */ | char *arch; /* architecture from file content */ | ||
char *desc; /* parsed description */ | char *title; /* title from file content */ | ||
char *name; /* name (from filename) (not empty) */ | char *desc; /* description from file content */ | ||
char *sec; /* suffix-cued section (or empty) */ | struct mlink *mlinks; /* singly linked list */ | ||
char *dsec; /* path-cued section (or empty) */ | int form; /* format from file content */ | ||
char *arch; /* path-cued arch. (or empty) */ | int name_head_done; | ||
}; | }; | ||
struct title { | struct mlink { | ||
char *title; /* name(sec/arch) given inside the file */ | char file[PATH_MAX]; /* filename rel. to manpath */ | ||
char *file; /* file name in case of mismatch */ | char *dsec; /* section from directory */ | ||
char *arch; /* architecture from directory */ | |||
char *name; /* name from file name (not empty) */ | |||
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 */ | |||
}; | }; | ||
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 mdoc_meta *, | ||
const struct mdoc_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(struct mchars *, int, const struct mpage *); | static void dbadd(struct mpage *, struct mchars *); | ||
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 fileadd(struct mpage *); | |||
static int filecheck(const char *); | |||
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 void inoadd(const struct stat *, struct mpage *); | static void mlink_add(struct mlink *, const struct stat *); | ||
static int inocheck(const struct stat *); | static void mlink_check(struct mpage *, struct mlink *); | ||
static void ofadd(int, const char *, const char *, const char *, | static void mlink_free(struct mlink *); | ||
const char *, const char *, const struct stat *); | 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 mchars *, 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 man_meta *, | ||
static int parse_mdoc_body(struct mpage *, const struct mdoc_node *); | const struct man_node *); | ||
static int parse_mdoc_head(struct mpage *, const struct mdoc_node *); | static void parse_mdoc(struct mpage *, const struct mdoc_meta *, | ||
static int parse_mdoc_Fd(struct mpage *, const struct mdoc_node *); | const struct mdoc_node *); | ||
static int parse_mdoc_Fn(struct mpage *, const struct mdoc_node *); | static int parse_mdoc_body(struct mpage *, const struct mdoc_meta *, | ||
static int parse_mdoc_In(struct mpage *, const struct mdoc_node *); | const struct mdoc_node *); | ||
static int parse_mdoc_Nd(struct mpage *, const struct mdoc_node *); | static int parse_mdoc_head(struct mpage *, const struct mdoc_meta *, | ||
static int parse_mdoc_Nm(struct mpage *, const struct mdoc_node *); | const struct mdoc_node *); | ||
static int parse_mdoc_Sh(struct mpage *, const struct mdoc_node *); | static int parse_mdoc_Fd(struct mpage *, const struct mdoc_meta *, | ||
static int parse_mdoc_St(struct mpage *, const struct mdoc_node *); | const struct mdoc_node *); | ||
static int parse_mdoc_Xr(struct mpage *, const struct mdoc_node *); | static int parse_mdoc_Fn(struct mpage *, const struct mdoc_meta *, | ||
static void putkey(const struct mpage *, | const struct mdoc_node *); | ||
const char *, uint64_t); | static int parse_mdoc_Fo(struct mpage *, const struct mdoc_meta *, | ||
const struct mdoc_node *); | |||
static int parse_mdoc_Nd(struct mpage *, const struct mdoc_meta *, | |||
const struct mdoc_node *); | |||
static int parse_mdoc_Nm(struct mpage *, const struct mdoc_meta *, | |||
const struct mdoc_node *); | |||
static int parse_mdoc_Sh(struct mpage *, const struct mdoc_meta *, | |||
const struct mdoc_node *); | |||
static int parse_mdoc_Xr(struct mpage *, const struct mdoc_meta *, | |||
const struct mdoc_node *); | |||
static void putkey(const struct mpage *, char *, uint64_t); | |||
static void putkeys(const struct mpage *, | static void putkeys(const struct mpage *, | ||
const char *, size_t, uint64_t); | const char *, size_t, uint64_t); | ||
static void putmdockey(const struct mpage *, | static void putmdockey(const struct mpage *, | ||
const struct mdoc_node *, uint64_t); | const struct mdoc_node *, uint64_t); | ||
static void render_key(struct mchars *, struct str *); | |||
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 tempfilename[32]; | |||
static char *progname; | static char *progname; | ||
static int use_all; /* use all found files */ | |||
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 ohash mpages; /* table of distinct manual pages */ | static struct ohash mpages; /* table of distinct manual pages */ | ||
static struct ohash filenames; /* table of filenames */ | 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[]) | main(int argc, char *argv[]) | ||
{ | { | ||
|
|
||
struct mchars *mc; | struct mchars *mc; | ||
struct manpaths dirs; | struct manpaths dirs; | ||
struct mparse *mp; | struct mparse *mp; | ||
struct ohash_info mpages_info, filename_info; | struct ohash_info mpages_info, mlinks_info; | ||
memset(stmts, 0, STMT__MAX * sizeof(sqlite3_stmt *)); | memset(stmts, 0, STMT__MAX * sizeof(sqlite3_stmt *)); | ||
memset(&dirs, 0, sizeof(struct manpaths)); | memset(&dirs, 0, sizeof(struct manpaths)); | ||
mpages_info.alloc = filename_info.alloc = hash_alloc; | mpages_info.alloc = mlinks_info.alloc = hash_alloc; | ||
mpages_info.halloc = filename_info.halloc = hash_halloc; | mpages_info.calloc = mlinks_info.calloc = hash_calloc; | ||
mpages_info.hfree = filename_info.hfree = hash_free; | mpages_info.free = mlinks_info.free = hash_free; | ||
mpages_info.key_offset = offsetof(struct mpage, inodev); | mpages_info.key_offset = offsetof(struct mpage, inodev); | ||
filename_info.key_offset = offsetof(struct mpage, file); | mlinks_info.key_offset = offsetof(struct mlink, file); | ||
progname = strrchr(argv[0], '/'); | progname = strrchr(argv[0], '/'); | ||
if (progname == NULL) | if (progname == NULL) | ||
|
|
||
++progname; | ++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)); \ | fprintf(stderr, "%s: -%c: Conflicting option\n", \ | ||
progname, (_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")) { | |||
fprintf(stderr, "%s: -T%s: " | |||
"Unsupported output format\n", | |||
progname, 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"); | fprintf(stderr, "%s: -C: Too many arguments\n", | ||
progname); | |||
goto usage; | goto usage; | ||
} | } | ||
exitcode = (int)MANDOCLEVEL_OK; | exitcode = (int)MANDOCLEVEL_OK; | ||
mp = mparse_alloc(MPARSE_AUTO, | |||
MANDOCLEVEL_FATAL, NULL, NULL, NULL); | |||
mc = mchars_alloc(); | mc = mchars_alloc(); | ||
mp = mparse_alloc(mparse_options, MANDOCLEVEL_FATAL, NULL, | |||
mc, NULL); | |||
ohash_init(&mpages, 6, &mpages_info); | ohash_init(&mpages, 6, &mpages_info); | ||
ohash_init(&filenames, 6, &filename_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(mc, 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. | ||
|
|
||
* manpath_parse() wants to do it. | * manpath_parse() wants to do it. | ||
*/ | */ | ||
if (argc > 0) { | if (argc > 0) { | ||
dirs.paths = mandoc_calloc | dirs.paths = mandoc_reallocarray(NULL, | ||
(argc, sizeof(char *)); | argc, sizeof(char *)); | ||
dirs.sz = (size_t)argc; | dirs.sz = (size_t)argc; | ||
for (i = 0; i < argc; i++) | for (i = 0; i < argc; i++) | ||
dirs.paths[i] = mandoc_strdup(argv[i]); | dirs.paths[i] = mandoc_strdup(argv[i]); | ||
} else | } else | ||
manpath_parse(&dirs, path_arg, NULL, NULL); | manpath_parse(&dirs, path_arg, NULL, NULL); | ||
if (0 == dirs.sz) { | |||
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. | ||
|
|
||
if (j) { | if (j) { | ||
ohash_init(&mpages, 6, &mpages_info); | ohash_init(&mpages, 6, &mpages_info); | ||
ohash_init(&filenames, 6, &filename_info); | ohash_init(&mlinks, 6, &mlinks_info); | ||
} | } | ||
if (0 == set_basedir(dirs.paths[j])) | if (0 == set_basedir(dirs.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(mc, mp); | ||
if (warnings && !nodb && | |||
! (MPARSE_QUICK & mparse_options)) | |||
names_check(); | |||
dbclose(0); | dbclose(0); | ||
if (j + 1 < dirs.sz) { | if (j + 1 < dirs.sz) { | ||
mpages_free(); | mpages_free(); | ||
ohash_delete(&mpages); | ohash_delete(&mpages); | ||
ohash_delete(&filenames); | ohash_delete(&mlinks); | ||
} | } | ||
} | } | ||
} | } | ||
out: | out: | ||
set_basedir(NULL); | |||
manpath_free(&dirs); | manpath_free(&dirs); | ||
mchars_free(mc); | |||
mparse_free(mp); | mparse_free(mp); | ||
mchars_free(mc); | |||
mpages_free(); | mpages_free(); | ||
ohash_delete(&mpages); | ohash_delete(&mpages); | ||
ohash_delete(&filenames); | 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; | ||
int dform; | struct mlink *mlink; | ||
char *sec; | int dform, gzip; | ||
const char *dsec, *arch, *cp, *path; | char *dsec, *arch, *fsec, *cp; | ||
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. | |||
*/ | |||
f = fts_open((char * const *)argv, FTS_LOGICAL, NULL); | |||
if (NULL == f) { | if (NULL == f) { | ||
exitcode = (int)MANDOCLEVEL_SYSERR; | exitcode = (int)MANDOCLEVEL_SYSERR; | ||
say("", NULL); | say("", "&fts_open"); | ||
return(0); | return(0); | ||
} | } | ||
|
|
||
while (NULL != (ff = fts_read(f))) { | while (NULL != (ff = fts_read(f))) { | ||
path = ff->fts_path + 2; | path = ff->fts_path + 2; | ||
switch (ff->fts_info) { | |||
/* | /* | ||
* If we're a regular file, add an mpage by using the | * Symbolic links require various sanity checks, | ||
* then get handled just like regular files. | |||
*/ | |||
case FTS_SL: | |||
if (NULL == realpath(path, buf)) { | |||
if (warnings) | |||
say(path, "&realpath"); | |||
continue; | |||
} | |||
if (strstr(buf, basedir) != buf) { | |||
if (warnings) say("", | |||
"%s: outside base directory", buf); | |||
continue; | |||
} | |||
/* Use logical inode to avoid mpages dupe. */ | |||
if (-1 == stat(path, ff->fts_statp)) { | |||
if (warnings) | |||
say(path, "&stat"); | |||
continue; | |||
} | |||
/* FALLTHROUGH */ | |||
/* | |||
* 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 (0 == 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 (NULL == fsec) { | ||
} else if (NULL == (sec = | fsec = strrchr(ff->fts_name, '.'); | ||
strrchr(ff->fts_name, '.'))) { | if (NULL == fsec || strcmp(fsec+1, "gz")) | ||
break; | |||
gzip = 1; | |||
*fsec = '\0'; | |||
fsec = NULL; | |||
} | |||
if (NULL == fsec) { | |||
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(++sec, "html")) { | } else if (0 == strcmp(++fsec, "html")) { | ||
if (warnings) | if (warnings) | ||
say(path, "Skip html"); | say(path, "Skip html"); | ||
continue; | continue; | ||
} else if (0 == strcmp(sec, "gz")) { | } else if (0 == strcmp(fsec, "ps")) { | ||
if (warnings) | if (warnings) | ||
say(path, "Skip gz"); | |||
continue; | |||
} else if (0 == strcmp(sec, "ps")) { | |||
if (warnings) | |||
say(path, "Skip ps"); | say(path, "Skip ps"); | ||
continue; | continue; | ||
} else if (0 == strcmp(sec, "pdf")) { | } else if (0 == 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(sec, dsec)) || | ((FORM_SRC == dform && strcmp(fsec, dsec)) || | ||
(FORM_CAT == dform && strcmp(sec, "0")))) { | (FORM_CAT == dform && strcmp(fsec, "0")))) { | ||
if (warnings) | if (warnings) | ||
say(path, "Wrong filename suffix"); | say(path, "Wrong filename suffix"); | ||
continue; | continue; | ||
} else | } else | ||
sec[-1] = '\0'; | fsec[-1] = '\0'; | ||
ofadd(dform, path, ff->fts_name, dsec, sec, | |||
arch, ff->fts_statp); | mlink = mandoc_calloc(1, sizeof(struct mlink)); | ||
if (strlcpy(mlink->file, path, | |||
sizeof(mlink->file)) >= | |||
sizeof(mlink->file)) { | |||
say(path, "Filename too long"); | |||
free(mlink); | |||
continue; | |||
} | |||
mlink->dform = dform; | |||
mlink->dsec = dsec; | |||
mlink->arch = arch; | |||
mlink->name = ff->fts_name; | |||
mlink->fsec = fsec; | |||
mlink->gzip = gzip; | |||
mlink_add(mlink, ff->fts_statp); | |||
continue; | continue; | ||
} else if (FTS_D != ff->fts_info && | |||
FTS_DP != ff->fts_info) { | case FTS_D: | ||
/* FALLTHROUGH */ | |||
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 (FTS_DP == ff->fts_info) | ||
break; | break; | ||
|
|
||
} else if (0 == strncmp(cp, "cat", 3)) { | } else if (0 == 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 (NULL != dsec || 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 (FTS_DP != ff->fts_info && NULL != dsec) | 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 (FTS_DP == ff->fts_info || use_all) | ||
|
|
||
} | } | ||
/* | /* | ||
* 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 mpage vector. | |||
* See treescan() for the fts(3) version of this. | * See treescan() for the fts(3) version of this. | ||
*/ | */ | ||
static void | static void | ||
filescan(const char *file) | filescan(const char *file) | ||
{ | { | ||
char buf[PATH_MAX]; | char buf[PATH_MAX]; | ||
const char *sec, *arch, *name, *dsec; | |||
char *p, *start; | |||
int dform; | |||
struct stat st; | struct stat st; | ||
struct mlink *mlink; | |||
char *p, *start; | |||
assert(use_all); | assert(use_all); | ||
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); | |||
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"); | /* | ||
* 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->dform = FORM_NONE; | |||
if (strlcpy(mlink->file, start, sizeof(mlink->file)) >= | |||
sizeof(mlink->file)) { | |||
say(start, "Filename too long"); | |||
return; | return; | ||
} | } | ||
start = buf + strlen(basedir); | |||
sec = arch = name = dsec = NULL; | |||
dform = FORM_NONE; | |||
/* | /* | ||
* First try to guess our directory structure. | * First try to guess our directory structure. | ||
|
|
||
if (NULL != (p = strchr(start, '/'))) { | if (NULL != (p = strchr(start, '/'))) { | ||
*p++ = '\0'; | *p++ = '\0'; | ||
if (0 == strncmp(start, "man", 3)) { | if (0 == strncmp(start, "man", 3)) { | ||
dform = FORM_SRC; | mlink->dform = FORM_SRC; | ||
dsec = start + 3; | mlink->dsec = start + 3; | ||
} else if (0 == strncmp(start, "cat", 3)) { | } else if (0 == strncmp(start, "cat", 3)) { | ||
dform = FORM_CAT; | mlink->dform = FORM_CAT; | ||
dsec = start + 3; | mlink->dsec = start + 3; | ||
} | } | ||
start = p; | start = p; | ||
if (NULL != dsec && NULL != (p = strchr(start, '/'))) { | if (NULL != mlink->dsec && NULL != (p = strchr(start, '/'))) { | ||
*p++ = '\0'; | *p++ = '\0'; | ||
arch = start; | mlink->arch = start; | ||
start = p; | start = p; | ||
} | } | ||
} | } | ||
/* | /* | ||
|
|
||
if ('.' == *p) { | if ('.' == *p) { | ||
*p++ = '\0'; | *p++ = '\0'; | ||
sec = p; | mlink->fsec = p; | ||
} | } | ||
/* | /* | ||
* Now try to parse the name. | * Now try to parse the name. | ||
* Use the filename portion of the path. | * Use the filename portion of the path. | ||
*/ | */ | ||
name = start; | mlink->name = start; | ||
if (NULL != (p = strrchr(start, '/'))) { | if (NULL != (p = strrchr(start, '/'))) { | ||
name = p + 1; | mlink->name = p + 1; | ||
*p = '\0'; | *p = '\0'; | ||
} | } | ||
mlink_add(mlink, &st); | |||
ofadd(dform, file, name, dsec, sec, arch, &st); | |||
} | } | ||
/* | static void | ||
* See fileadd(). | mlink_add(struct mlink *mlink, const struct stat *st) | ||
*/ | |||
static int | |||
filecheck(const char *name) | |||
{ | { | ||
struct inodev inodev; | |||
struct mpage *mpage; | |||
unsigned int slot; | |||
return(NULL != ohash_find(&filenames, | assert(NULL != mlink->file); | ||
ohash_qlookup(&filenames, name))); | |||
mlink->dsec = mandoc_strdup(mlink->dsec ? mlink->dsec : ""); | |||
mlink->arch = mandoc_strdup(mlink->arch ? mlink->arch : ""); | |||
mlink->name = mandoc_strdup(mlink->name ? mlink->name : ""); | |||
mlink->fsec = mandoc_strdup(mlink->fsec ? mlink->fsec : ""); | |||
if ('0' == *mlink->fsec) { | |||
free(mlink->fsec); | |||
mlink->fsec = mandoc_strdup(mlink->dsec); | |||
mlink->fform = FORM_CAT; | |||
} else if ('1' <= *mlink->fsec && '9' >= *mlink->fsec) | |||
mlink->fform = FORM_SRC; | |||
else | |||
mlink->fform = FORM_NONE; | |||
slot = ohash_qlookup(&mlinks, mlink->file); | |||
assert(NULL == ohash_find(&mlinks, slot)); | |||
ohash_insert(&mlinks, slot, mlink); | |||
inodev.st_ino = st->st_ino; | |||
inodev.st_dev = st->st_dev; | |||
slot = ohash_lookup_memory(&mpages, (char *)&inodev, | |||
sizeof(struct inodev), inodev.st_ino); | |||
mpage = ohash_find(&mpages, slot); | |||
if (NULL == mpage) { | |||
mpage = mandoc_calloc(1, sizeof(struct mpage)); | |||
mpage->inodev.st_ino = inodev.st_ino; | |||
mpage->inodev.st_dev = inodev.st_dev; | |||
ohash_insert(&mpages, slot, mpage); | |||
} else | |||
mlink->next = mpage->mlinks; | |||
mpage->mlinks = mlink; | |||
mlink->mpage = mpage; | |||
} | } | ||
/* | |||
* Use the standard hashing mechanism (K&R) to see if the given filename | |||
* already exists. | |||
*/ | |||
static void | static void | ||
fileadd(struct mpage *mpage) | mlink_free(struct mlink *mlink) | ||
{ | { | ||
unsigned int slot; | |||
slot = ohash_qlookup(&filenames, mpage->file); | free(mlink->dsec); | ||
assert(NULL == ohash_find(&filenames, slot)); | free(mlink->arch); | ||
ohash_insert(&filenames, slot, mpage); | free(mlink->name); | ||
free(mlink->fsec); | |||
free(mlink); | |||
} | } | ||
/* | static void | ||
* See inoadd(). | mpages_free(void) | ||
*/ | |||
static int | |||
inocheck(const struct stat *st) | |||
{ | { | ||
struct inodev inodev; | struct mpage *mpage; | ||
uint32_t hash; | struct mlink *mlink; | ||
unsigned int slot; | |||
memset(&inodev, 0, sizeof(inodev)); | mpage = ohash_first(&mpages, &slot); | ||
inodev.st_ino = hash = st->st_ino; | while (NULL != mpage) { | ||
inodev.st_dev = st->st_dev; | while (NULL != (mlink = mpage->mlinks)) { | ||
mpage->mlinks = mlink->next; | |||
return(NULL != ohash_find(&mpages, ohash_lookup_memory( | mlink_free(mlink); | ||
&mpages, (char *)&inodev, sizeof(inodev), hash))); | } | ||
free(mpage->sec); | |||
free(mpage->arch); | |||
free(mpage->title); | |||
free(mpage->desc); | |||
free(mpage); | |||
mpage = ohash_next(&mpages, &slot); | |||
} | |||
} | } | ||
/* | /* | ||
* The hashing function used here is quite simple: simply take the inode | * For each mlink to the mpage, check whether the path looks like | ||
* and use uint32_t of its bits. | * it is formatted, and if it does, check whether a source manual | ||
* Then when we do the lookup, use both the inode and device identifier. | * exists by the same name, ignoring the suffix. | ||
* If both conditions hold, drop the mlink. | |||
*/ | */ | ||
static void | static void | ||
inoadd(const struct stat *st, struct mpage *mpage) | mlinks_undupe(struct mpage *mpage) | ||
{ | { | ||
unsigned int slot; | char buf[PATH_MAX]; | ||
struct mlink **prev; | |||
struct mlink *mlink; | |||
char *bufp; | |||
mpage->inodev.st_ino = st->st_ino; | mpage->form = FORM_CAT; | ||
mpage->inodev.st_dev = st->st_dev; | prev = &mpage->mlinks; | ||
slot = ohash_lookup_memory(&mpages, (char *)&mpage->inodev, | while (NULL != (mlink = *prev)) { | ||
sizeof(struct inodev), st->st_ino); | if (FORM_CAT != mlink->dform) { | ||
mpage->form = FORM_NONE; | |||
assert(NULL == ohash_find(&mpages, slot)); | goto nextlink; | ||
ohash_insert(&mpages, slot, mpage); | } | ||
(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 | static void | ||
ofadd(int dform, const char *file, const char *name, const char *dsec, | mlink_check(struct mpage *mpage, struct mlink *mlink) | ||
const char *sec, const char *arch, const struct stat *st) | |||
{ | { | ||
struct mpage *mpage; | struct str *str; | ||
int sform; | unsigned int slot; | ||
assert(NULL != file); | /* | ||
* 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 (NULL == name) | if (FORM_SRC == mpage->form && | ||
name = ""; | strcasecmp(mpage->sec, mlink->dsec)) | ||
if (NULL == sec) | say(mlink->file, "Section \"%s\" manual in %s directory", | ||
sec = ""; | mpage->sec, mlink->dsec); | ||
if (NULL == dsec) | |||
dsec = ""; | |||
if (NULL == arch) | |||
arch = ""; | |||
if ('0' == *sec) { | /* | ||
sec = dsec; | * Manual page directories exist for each kernel | ||
sform = FORM_CAT; | * architecture as returned by machine(1). | ||
} else if ('1' <= *sec && '9' >= *sec) | * However, many manuals only depend on the | ||
sform = FORM_SRC; | * application architecture as returned by arch(1). | ||
else | * For example, some (2/ARM) manuals are shared | ||
sform = FORM_NONE; | * 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. | |||
*/ | |||
mpage = mandoc_calloc(1, sizeof(struct mpage)); | if (strcasecmp(mpage->arch, mlink->arch)) | ||
strlcpy(mpage->file, file, PATH_MAX); | say(mlink->file, "Architecture \"%s\" manual in " | ||
mpage->name = mandoc_strdup(name); | "\"%s\" directory", mpage->arch, mlink->arch); | ||
mpage->sec = mandoc_strdup(sec); | |||
mpage->dsec = mandoc_strdup(dsec); | |||
mpage->arch = mandoc_strdup(arch); | |||
mpage->sform = sform; | |||
mpage->dform = dform; | |||
/* | /* | ||
* Add to unique identifier hash. | * XXX | ||
* Then if it's a source manual and we're going to use source in | * parse_cat() doesn't set NAME_TITLE yet. | ||
* favour of catpages, add it to that hash. | |||
*/ | */ | ||
inoadd(st, mpage); | |||
fileadd(mpage); | |||
} | |||
static void | if (FORM_CAT == mpage->form) | ||
mpages_free(void) | return; | ||
{ | |||
struct mpage *mpage; | |||
unsigned int slot; | |||
mpage = ohash_first(&mpages, &slot); | /* | ||
while (NULL != mpage) { | * Check whether this mlink | ||
free(mpage->name); | * appears as a name in the NAME section. | ||
free(mpage->sec); | */ | ||
free(mpage->dsec); | |||
free(mpage->arch); | slot = ohash_qlookup(&names, mlink->name); | ||
free(mpage); | str = ohash_find(&names, slot); | ||
mpage = ohash_next(&mpages, &slot); | assert(NULL != str); | ||
} | if ( ! (NAME_TITLE & str->mask)) | ||
say(mlink->file, "Name missing in NAME section"); | |||
} | } | ||
/* | /* | ||
|
|
||
* and filename to determine whether the file is parsable or not. | * and filename to determine whether the file is parsable or not. | ||
*/ | */ | ||
static void | static void | ||
mpages_merge(struct mchars *mc, struct mparse *mp, int check_reachable) | mpages_merge(struct mchars *mc, 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 mdoc *mdoc; | ||
struct man *man; | struct man *man; | ||
struct title *title_entry; | char *sodest; | ||
char *bufp, *title_str; | char *cp; | ||
const char *msec, *march, *mtitle, *cp; | int fd; | ||
size_t sz; | unsigned int pslot; | ||
int form; | |||
int match; | |||
unsigned int pslot, tslot; | |||
enum mandoclevel lvl; | 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.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 (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->dform) { | |||
sz = strlcpy(buf, mpage->file, PATH_MAX); | |||
if (sz >= PATH_MAX) { | |||
if (warnings) | |||
say(mpage->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->dsec, PATH_MAX); | |||
if (filecheck(buf)) { | |||
if (warnings) | |||
say(mpage->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; | mdoc = NULL; | ||
man = NULL; | man = NULL; | ||
match = 1; | sodest = NULL; | ||
mparse_open(mp, &fd, mpage->mlinks->file); | |||
if (fd == -1) { | |||
say(mpage->mlinks->file, "&open"); | |||
goto nextpage; | |||
} | |||
/* | /* | ||
* Try interpreting the file as mdoc(7) or man(7) | * Try interpreting the file as mdoc(7) or man(7) | ||
* source code, unless it is already known to be | * source code, unless it is already known to be | ||
* formatted. Fall back to formatted mode. | * formatted. Fall back to formatted mode. | ||
*/ | */ | ||
if (FORM_CAT != mpage->dform || FORM_CAT != mpage->sform) { | if (mpage->mlinks->dform != FORM_CAT || | ||
lvl = mparse_readfd(mp, -1, mpage->file); | mpage->mlinks->fform != FORM_CAT) { | ||
lvl = mparse_readfd(mp, fd, mpage->mlinks->file); | |||
if (lvl < MANDOCLEVEL_FATAL) | if (lvl < MANDOCLEVEL_FATAL) | ||
mparse_result(mp, &mdoc, &man); | mparse_result(mp, &mdoc, &man, &sodest); | ||
} | } | ||
if (NULL != mdoc) { | if (sodest != NULL) { | ||
form = 1; | mlink_dest = ohash_find(&mlinks, | ||
msec = mdoc_meta(mdoc)->msec; | ohash_qlookup(&mlinks, sodest)); | ||
march = mdoc_meta(mdoc)->arch; | if (mlink_dest == NULL) { | ||
mtitle = mdoc_meta(mdoc)->title; | mandoc_asprintf(&cp, "%s.gz", sodest); | ||
} else if (NULL != man) { | mlink_dest = ohash_find(&mlinks, | ||
form = 1; | ohash_qlookup(&mlinks, cp)); | ||
msec = man_meta(man)->msec; | free(cp); | ||
march = mpage->arch; | } | ||
mtitle = man_meta(man)->title; | if (mlink_dest != NULL) { | ||
} else { | |||
form = 0; | |||
msec = mpage->dsec; | |||
march = mpage->arch; | |||
mtitle = mpage->name; | |||
} | |||
if (NULL == msec) | /* The .so target exists. */ | ||
msec = ""; | |||
if (NULL == march) | |||
march = ""; | |||
if (NULL == mtitle) | |||
mtitle = ""; | |||
/* | mpage_dest = mlink_dest->mpage; | ||
* Check whether the manual section given in a file | mlink = mpage->mlinks; | ||
* agrees with the directory where the file is located. | while (1) { | ||
* Some manuals have suffixes like (3p) on their | mlink->mpage = mpage_dest; | ||
* section number either inside the file or in the | |||
* directory name, some are linked into more than one | |||
* section, like encrypt(1) = makekey(8). Do not skip | |||
* manuals for such reasons. | |||
*/ | |||
if (warnings && !use_all && form && | |||
strcasecmp(msec, mpage->dsec)) { | |||
match = 0; | |||
say(mpage->file, "Section \"%s\" " | |||
"manual in %s directory", | |||
msec, mpage->dsec); | |||
} | |||
/* | /* | ||
* Manual page directories exist for each kernel | * If the target was already | ||
* architecture as returned by machine(1). | * processed, add the links | ||
* However, many manuals only depend on the | * to the database now. | ||
* application architecture as returned by arch(1). | * Otherwise, this will | ||
* For example, some (2/ARM) manuals are shared | * happen when we come | ||
* across the "armish" and "zaurus" kernel | * to the target. | ||
* 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(march, mpage->arch)) { | |||
match = 0; | |||
say(mpage->file, "Architecture \"%s\" " | |||
"manual in \"%s\" directory", | |||
march, mpage->arch); | |||
} | |||
if (warnings && !use_all && strcasecmp(mtitle, mpage->name)) | |||
match = 0; | |||
putkey(mpage, mpage->name, TYPE_Nm); | if (mpage_dest->pageid) | ||
dbadd_mlink_name(mlink); | |||
if (NULL != mdoc) { | if (mlink->next == NULL) | ||
if (NULL != (cp = mdoc_meta(mdoc)->name)) | break; | ||
putkey(mpage, cp, TYPE_Nm); | mlink = mlink->next; | ||
assert(NULL == mpage->desc); | } | ||
parse_mdoc(mpage, mdoc_node(mdoc)); | |||
putkey(mpage, NULL != mpage->desc ? | |||
mpage->desc : mpage->name, TYPE_Nd); | |||
} else if (NULL != man) | |||
parse_man(mpage, man_node(man)); | |||
else | |||
parse_cat(mpage); | |||
/* | /* Move all links to the target. */ | ||
* Build a title string for the file. If it matches | |||
* the location of the file, remember the title as | |||
* found; else, remember it as missing. | |||
*/ | |||
if (check_reachable) { | mlink->next = mlink_dest->next; | ||
if (-1 == asprintf(&title_str, "%s(%s%s%s)", mtitle, | mlink_dest->next = mpage->mlinks; | ||
msec, '\0' == *march ? "" : "/", march)) { | mpage->mlinks = NULL; | ||
perror(NULL); | |||
exit((int)MANDOCLEVEL_SYSERR); | |||
} | } | ||
tslot = ohash_qlookup(&title_table, title_str); | goto nextpage; | ||
title_entry = ohash_find(&title_table, tslot); | } else if (mdoc != NULL) { | ||
if (NULL == title_entry) { | mpage->form = FORM_SRC; | ||
title_entry = mandoc_malloc( | mpage->sec = mdoc_meta(mdoc)->msec; | ||
sizeof(struct title)); | mpage->sec = mandoc_strdup( | ||
title_entry->title = title_str; | mpage->sec == NULL ? "" : mpage->sec); | ||
title_entry->file = mandoc_strdup( | mpage->arch = mdoc_meta(mdoc)->arch; | ||
match ? "" : mpage->file); | mpage->arch = mandoc_strdup( | ||
ohash_insert(&title_table, tslot, | mpage->arch == NULL ? "" : mpage->arch); | ||
title_entry); | mpage->title = | ||
} else { | mandoc_strdup(mdoc_meta(mdoc)->title); | ||
if (match) | } else if (man != NULL) { | ||
*title_entry->file = '\0'; | mpage->form = FORM_SRC; | ||
free(title_str); | mpage->sec = | ||
} | mandoc_strdup(man_meta(man)->msec); | ||
mpage->arch = | |||
mandoc_strdup(mpage->mlinks->arch); | |||
mpage->title = | |||
mandoc_strdup(man_meta(man)->title); | |||
} else { | |||
mpage->form = FORM_CAT; | |||
mpage->sec = | |||
mandoc_strdup(mpage->mlinks->dsec); | |||
mpage->arch = | |||
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); | |||
dbindex(mc, form, mpage); | for (mlink = mpage->mlinks; mlink; mlink = mlink->next) { | ||
if ('\0' != *mlink->dsec) | |||
putkey(mpage, mlink->dsec, TYPE_sec); | |||
if ('\0' != *mlink->fsec) | |||
putkey(mpage, mlink->fsec, TYPE_sec); | |||
putkey(mpage, '\0' == *mlink->arch ? | |||
any : mlink->arch, TYPE_arch); | |||
putkey(mpage, mlink->name, NAME_FILE); | |||
} | |||
assert(mpage->desc == NULL); | |||
if (mdoc != NULL) | |||
parse_mdoc(mpage, mdoc_meta(mdoc), mdoc_node(mdoc)); | |||
else if (man != NULL) | |||
parse_man(mpage, man_meta(man), man_node(man)); | |||
else | |||
parse_cat(mpage, fd); | |||
if (mpage->desc == NULL) | |||
mpage->desc = mandoc_strdup(mpage->mlinks->name); | |||
if (warnings && !use_all) | |||
for (mlink = mpage->mlinks; mlink; | |||
mlink = mlink->next) | |||
mlink_check(mpage, mlink); | |||
dbadd(mpage, mc); | |||
nextpage: | |||
if (mparse_wait(mp) != MANDOCLEVEL_OK) { | |||
exitcode = (int)MANDOCLEVEL_SYSERR; | |||
say(mpage->mlinks->file, "&wait gunzip"); | |||
} | |||
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->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->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 (NULL == title || '\0' == *title) { | if (NULL == title || '\0' == *title) { | ||
if (warnings) | if (warnings) | ||
say(mpage->file, "Cannot find NAME section"); | say(mpage->mlinks->file, | ||
assert(NULL == mpage->desc); | "Cannot find NAME section"); | ||
mpage->desc = mandoc_strdup(mpage->name); | |||
putkey(mpage, mpage->name, TYPE_Nd); | |||
fclose(stream); | fclose(stream); | ||
free(title); | free(title); | ||
return; | return; | ||
|
|
||
/* Skip to next word. */ ; | /* Skip to next word. */ ; | ||
} else { | } else { | ||
if (warnings) | if (warnings) | ||
say(mpage->file, "No dash in title line"); | say(mpage->mlinks->file, | ||
"No dash in title line"); | |||
p = title; | p = title; | ||
} | } | ||
|
|
||
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 | ||
parse_man(struct mpage *mpage, const struct man_node *n) | parse_man(struct mpage *mpage, const struct man_meta *meta, | ||
const struct man_node *n) | |||
{ | { | ||
const struct man_node *head, *body; | const struct man_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; | ||
|
|
||
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 && | MAN_TEXT == head->type && | ||
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) | man_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 mdoc_meta *meta, | ||
const struct mdoc_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 MDOC_ELEM: | ||
/* FALLTHROUGH */ | /* FALLTHROUGH */ | ||
case (MDOC_BLOCK): | case MDOC_BLOCK: | ||
/* FALLTHROUGH */ | /* FALLTHROUGH */ | ||
case (MDOC_HEAD): | case MDOC_HEAD: | ||
/* FALLTHROUGH */ | /* FALLTHROUGH */ | ||
case (MDOC_BODY): | case MDOC_BODY: | ||
/* FALLTHROUGH */ | /* FALLTHROUGH */ | ||
case (MDOC_TAIL): | 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, | ||
|
|
||
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 mdoc_meta *meta, | ||
const struct mdoc_node *n) | |||
{ | { | ||
const char *start, *end; | const 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) | MDOC_TEXT != n->type) | ||
return(0); | return(0); | ||
/* | /* | ||
|
|
||
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 int | ||
parse_mdoc_In(struct mpage *mpage, const struct mdoc_node *n) | parse_mdoc_Fn(struct mpage *mpage, const struct mdoc_meta *meta, | ||
const struct mdoc_node *n) | |||
{ | { | ||
char *cp; | |||
if (NULL != n->child && MDOC_TEXT == n->child->type) | |||
return(0); | |||
putkey(mpage, n->child->string, TYPE_In); | |||
return(1); | |||
} | |||
static int | |||
parse_mdoc_Fn(struct mpage *mpage, const struct mdoc_node *n) | |||
{ | |||
const char *cp; | |||
if (NULL == (n = n->child) || MDOC_TEXT != n->type) | if (NULL == (n = n->child) || MDOC_TEXT != n->type) | ||
return(0); | return(0); | ||
/* | /* | ||
* Parse: .Fn "struct type *name" "char *arg". | * Parse: .Fn "struct type *name" "char *arg". | ||
* First strip away pointer symbol. | * First strip away pointer symbol. | ||
* Then store the function name, then type. | * Then store the function name, then type. | ||
* Finally, store the arguments. | * Finally, store the arguments. | ||
*/ | */ | ||
if (NULL == (cp = strrchr(n->string, ' '))) | if (NULL == (cp = strrchr(n->string, ' '))) | ||
|
|
||
cp++; | cp++; | ||
putkey(mpage, cp, TYPE_Fn); | putkey(mpage, cp, TYPE_Fn); | ||
if (n->sec == SEC_SYNOPSIS) | |||
putkey(mpage, cp, NAME_SYN); | |||
if (n->string < cp) | if (n->string < cp) | ||
putkeys(mpage, n->string, cp - n->string, TYPE_Ft); | putkeys(mpage, n->string, cp - n->string, TYPE_Ft); | ||
|
|
||
} | } | ||
static int | static int | ||
parse_mdoc_St(struct mpage *mpage, const struct mdoc_node *n) | parse_mdoc_Fo(struct mpage *mpage, const struct mdoc_meta *meta, | ||
const struct mdoc_node *n) | |||
{ | { | ||
if (NULL == n->child || MDOC_TEXT != n->child->type) | putmdockey(mpage, n->child, TYPE_Fn); | ||
return(0); | if (n->sec == SEC_SYNOPSIS) | ||
putmdockey(mpage, n->child, NAME_SYN); | |||
putkey(mpage, n->child->string, TYPE_St); | return(0); | ||
return(1); | |||
} | } | ||
static int | static int | ||
parse_mdoc_Xr(struct mpage *mpage, const struct mdoc_node *n) | parse_mdoc_Xr(struct mpage *mpage, const struct mdoc_meta *meta, | ||
const struct mdoc_node *n) | |||
{ | { | ||
char *cp; | char *cp; | ||
|
|
||
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 mdoc_meta *meta, | ||
const struct mdoc_node *n) | |||
{ | { | ||
size_t sz; | |||
if (MDOC_BODY != n->type) | if (MDOC_BODY == n->type) | ||
return(0); | mdoc_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 mdoc_meta *meta, | ||
const struct mdoc_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 (SEC_SYNOPSIS == n->sec && MDOC_HEAD == n->type) { | ||
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, NAME_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 mdoc_meta *meta, | ||
const struct mdoc_node *n) | |||
{ | { | ||
return(SEC_CUSTOM == n->sec && MDOC_HEAD == n->type); | return(SEC_CUSTOM == n->sec && MDOC_HEAD == n->type); | ||
} | } | ||
static int | static int | ||
parse_mdoc_head(struct mpage *mpage, const struct mdoc_node *n) | parse_mdoc_head(struct mpage *mpage, const struct mdoc_meta *meta, | ||
const struct mdoc_node *n) | |||
{ | { | ||
return(MDOC_HEAD == n->type); | return(MDOC_HEAD == n->type); | ||
} | } | ||
static int | static int | ||
parse_mdoc_body(struct mpage *mpage, const struct mdoc_node *n) | parse_mdoc_body(struct mpage *mpage, const struct mdoc_meta *meta, | ||
const struct mdoc_node *n) | |||
{ | { | ||
return(MDOC_BODY == n->type); | return(MDOC_BODY == n->type); | ||
|
|
||
putkeys(const struct mpage *mpage, | putkeys(const struct mpage *mpage, | ||
const 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; | |||
if (0 == sz) | if (0 == sz) | ||
return; | return; | ||
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; | ||
|
|
||
} | } | ||
/* | /* | ||
* Store the UTF-8 version of a key, or alias the pointer if the key has | * Store the rendered version of a key, or alias the pointer | ||
* no UTF-8 transcription marks in it. | * if the key contains no escape sequences. | ||
*/ | */ | ||
static void | static void | ||
utf8key(struct mchars *mc, struct str *key) | render_key(struct mchars *mc, struct str *key) | ||
{ | { | ||
size_t sz, bsz, pos; | size_t sz, bsz, pos; | ||
char utfbuf[7], res[5]; | char utfbuf[7], res[6]; | ||
char *buf; | char *buf; | ||
const char *seq, *cpp, *val; | const char *seq, *cpp, *val; | ||
int len, u; | int len, u; | ||
enum mandoc_esc esc; | enum mandoc_esc esc; | ||
assert(NULL == key->utf8); | assert(NULL == key->rendered); | ||
res[0] = '\\'; | res[0] = '\\'; | ||
res[1] = '\t'; | res[1] = '\t'; | ||
res[2] = ASCII_NBRSP; | res[2] = ASCII_NBRSP; | ||
res[3] = ASCII_HYPH; | res[3] = ASCII_HYPH; | ||
res[4] = '\0'; | res[4] = ASCII_BREAK; | ||
res[5] = '\0'; | |||
val = key->key; | val = key->key; | ||
bsz = strlen(val); | bsz = strlen(val); | ||
|
|
||
* pointer as ourselvse and get out of here. | * pointer as ourselvse and get out of here. | ||
*/ | */ | ||
if (strcspn(val, res) == bsz) { | if (strcspn(val, res) == bsz) { | ||
key->utf8 = key->key; | key->rendered = key->key; | ||
return; | return; | ||
} | } | ||
/* Pre-allocate by the length of the input */ | /* Pre-allocate by the length of the input */ | ||
|
|
||
val += sz; | val += sz; | ||
} | } | ||
if (ASCII_HYPH == *val) { | switch (*val) { | ||
case ASCII_HYPH: | |||
buf[pos++] = '-'; | buf[pos++] = '-'; | ||
val++; | val++; | ||
continue; | continue; | ||
} else if ('\t' == *val || ASCII_NBRSP == *val) { | case '\t': | ||
/* FALLTHROUGH */ | |||
case ASCII_NBRSP: | |||
buf[pos++] = ' '; | buf[pos++] = ' '; | ||
val++; | val++; | ||
/* FALLTHROUGH */ | |||
case ASCII_BREAK: | |||
continue; | continue; | ||
} else if ('\\' != *val) | default: | ||
break; | break; | ||
} | |||
if ('\\' != *val) | |||
break; | |||
/* Read past the slash. */ | /* Read past the slash. */ | ||
val++; | val++; | ||
u = 0; | |||
/* | /* | ||
* Parse the escape sequence and see if it's a | * Parse the escape sequence and see if it's a | ||
* predefined character or special character. | * predefined character or special character. | ||
*/ | */ | ||
esc = mandoc_escape | |||
((const char **)&val, &seq, &len); | esc = mandoc_escape((const char **)&val, | ||
&seq, &len); | |||
if (ESCAPE_ERROR == esc) | if (ESCAPE_ERROR == esc) | ||
break; | break; | ||
if (ESCAPE_SPECIAL != esc) | if (ESCAPE_SPECIAL != esc) | ||
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) { | |||
if ((u = mchars_spec2cp(mc, seq, len)) <= 0) | |||
continue; | |||
cpp = utfbuf; | |||
if (0 == (sz = utf8(u, utfbuf))) | |||
continue; | |||
sz = strlen(cpp); | |||
} else { | |||
cpp = mchars_spec2str(mc, seq, len, &sz); | |||
if (NULL == cpp) | |||
continue; | |||
if (ASCII_NBRSP == *cpp) { | |||
cpp = " "; | |||
sz = 1; | |||
} | |||
} | |||
/* Copy the rendered glyph into the stream. */ | /* Copy the rendered glyph into the stream. */ | ||
sz = strlen(cpp); | |||
bsz += sz; | bsz += sz; | ||
buf = mandoc_realloc(buf, bsz); | buf = mandoc_realloc(buf, bsz); | ||
memcpy(&buf[pos], cpp, sz); | memcpy(&buf[pos], cpp, sz); | ||
pos += sz; | pos += sz; | ||
} | } | ||
buf[pos] = '\0'; | buf[pos] = '\0'; | ||
key->utf8 = buf; | key->rendered = buf; | ||
} | } | ||
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]); | |||
} | |||
i = 1; | |||
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(struct mchars *mc, int form, const struct mpage *mpage) | dbadd(struct mpage *mpage, struct mchars *mc) | ||
{ | { | ||
struct mlink *mlink; | |||
struct str *key; | struct str *key; | ||
const char *desc; | |||
int64_t recno; | |||
size_t i; | size_t i; | ||
unsigned int slot; | unsigned int slot; | ||
if (verb) | mlink = mpage->mlinks; | ||
say(mpage->file, "Adding to index"); | |||
if (nodb) | if (nodb) { | ||
for (key = ohash_first(&names, &slot); NULL != key; | |||
key = ohash_next(&names, &slot)) { | |||
if (key->rendered != key->key) | |||
free(key->rendered); | |||
free(key); | |||
} | |||
for (key = ohash_first(&strings, &slot); NULL != key; | |||
key = ohash_next(&strings, &slot)) { | |||
if (key->rendered != key->key) | |||
free(key->rendered); | |||
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"); | |||
i = strlen(mpage->desc) + 1; | |||
key = mandoc_calloc(1, sizeof(struct str) + i); | |||
memcpy(key->key, mpage->desc, i); | |||
render_key(mc, key); | |||
i = 1; | i = 1; | ||
/* | SQL_BIND_TEXT(stmts[STMT_INSERT_PAGE], i, key->rendered); | ||
* 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->file); | |||
SQL_BIND_TEXT(stmts[STMT_INSERT_PAGE], i, mpage->sec); | |||
SQL_BIND_TEXT(stmts[STMT_INSERT_PAGE], i, mpage->arch); | |||
SQL_BIND_TEXT(stmts[STMT_INSERT_PAGE], i, desc); | |||
SQL_BIND_INT(stmts[STMT_INSERT_PAGE], i, 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]); | ||
i = 1; | if (key->rendered != key->key) | ||
SQL_BIND_TEXT(stmts[STMT_INSERT_LINK], i, mpage->sec); | free(key->rendered); | ||
SQL_BIND_TEXT(stmts[STMT_INSERT_LINK], i, mpage->arch); | free(key); | ||
SQL_BIND_TEXT(stmts[STMT_INSERT_LINK], i, mpage->file); | |||
SQL_BIND_INT64(stmts[STMT_INSERT_LINK], i, recno); | |||
SQL_STEP(stmts[STMT_INSERT_LINK]); | |||
sqlite3_reset(stmts[STMT_INSERT_LINK]); | |||
while (NULL != mlink) { | |||
dbadd_mlink(mlink); | |||
mlink = mlink->next; | |||
} | |||
mlink = mpage->mlinks; | |||
for (key = ohash_first(&names, &slot); NULL != key; | |||
key = ohash_next(&names, &slot)) { | |||
assert(key->mpage == mpage); | |||
if (NULL == key->rendered) | |||
render_key(mc, key); | |||
i = 1; | |||
SQL_BIND_INT64(stmts[STMT_INSERT_NAME], i, key->mask); | |||
SQL_BIND_TEXT(stmts[STMT_INSERT_NAME], i, key->rendered); | |||
SQL_BIND_INT64(stmts[STMT_INSERT_NAME], i, mpage->pageid); | |||
SQL_STEP(stmts[STMT_INSERT_NAME]); | |||
sqlite3_reset(stmts[STMT_INSERT_NAME]); | |||
if (key->rendered != key->key) | |||
free(key->rendered); | |||
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) | if (NULL == key->rendered) | ||
utf8key(mc, key); | render_key(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->rendered); | ||
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) | if (key->rendered != key->key) | ||
free(key->utf8); | free(key->rendered); | ||
free(key); | free(key); | ||
} | } | ||
SQL_EXEC("END TRANSACTION"); | |||
} | } | ||
static void | static void | ||
dbprune(void) | dbprune(void) | ||
{ | { | ||
struct mpage *mpage; | struct mpage *mpage; | ||
struct mlink *mlink; | |||
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)) { | ||
i = 1; | mlink = mpage->mlinks; | ||
SQL_BIND_TEXT(stmts[STMT_DELETE_PAGE], i, mpage->file); | if (debug) | ||
SQL_STEP(stmts[STMT_DELETE_PAGE]); | say(mlink->file, "Deleting from database"); | ||
sqlite3_reset(stmts[STMT_DELETE_PAGE]); | if (nodb) | ||
if (verb) | continue; | ||
say(mpage->file, "Deleted from index"); | for ( ; NULL != mlink; mlink = mlink->next) { | ||
mpage = ohash_next(&mpages, &slot); | i = 1; | ||
SQL_BIND_TEXT(stmts[STMT_DELETE_PAGE], | |||
i, mlink->dsec); | |||
SQL_BIND_TEXT(stmts[STMT_DELETE_PAGE], | |||
i, mlink->arch); | |||
SQL_BIND_TEXT(stmts[STMT_DELETE_PAGE], | |||
i, mlink->name); | |||
SQL_STEP(stmts[STMT_DELETE_PAGE]); | |||
sqlite3_reset(stmts[STMT_DELETE_PAGE]); | |||
} | |||
} | } | ||
if (0 == nodb) | |||
SQL_EXEC("END TRANSACTION"); | |||
} | } | ||
/* | /* | ||
|
|
||
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, 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, 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) | ||
say(MANDOC_DB, "%s", sqlite3_errstr(rc)); | |||
return(0); | 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) { | } | ||
ofl |= SQLITE_OPEN_CREATE | SQLITE_OPEN_EXCLUSIVE; | |||
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); | ||
} | } | ||
sqlite3_close(db); | (void)strlcpy(tempfilename, "/tmp/mandocdb.XXXXXX", | ||
db = NULL; | sizeof(tempfilename)); | ||
if (NULL == mkdtemp(tempfilename)) { | |||
if (SQLITE_OK != (rc = sqlite3_open(file, &db))) { | |||
exitcode = (int)MANDOCLEVEL_SYSERR; | exitcode = (int)MANDOCLEVEL_SYSERR; | ||
say(file, NULL); | say("", "&%s", tempfilename); | ||
return(0); | 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: | ||
* XXX The first three columns in table mpages are obsolete | |||
* and only kept for backward compatibility | |||
* until apropos(1) and friends have caught up. | |||
*/ | |||
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)); | ||
sqlite3_close(db); | |||
return(0); | 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 * | ||
|
|
||
} | } | ||
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; | ||
say("", "&chdir"); | |||
} | |||
return(0); | return(0); | ||
} | } | ||
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); | return(1); | ||
} | } | ||
|
|
||
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); | |||
} | } |