-/* $Id: mandocdb.c,v 1.143 2014/04/23 19:09:16 schwarze Exp $ */
+/* $Id: mandocdb.c,v 1.166 2014/10/12 20:32:39 schwarze Exp $ */
/*
* Copyright (c) 2011, 2012 Kristaps Dzonsons <kristaps@bsd.lv>
* Copyright (c) 2011, 2012, 2013, 2014 Ingo Schwarze <schwarze@openbsd.org>
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
-#ifdef HAVE_CONFIG_H
#include "config.h"
-#endif
+#include <sys/types.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <ctype.h>
#include <errno.h>
#include <fcntl.h>
+#if HAVE_FTS
#include <fts.h>
+#else
+#include "compat_fts.h"
+#endif
#include <getopt.h>
#include <limits.h>
#include <stddef.h>
#include <string.h>
#include <unistd.h>
-#ifdef HAVE_OHASH
+#if HAVE_OHASH
#include <ohash.h>
#else
#include "compat_ohash.h"
OP_TEST /* change no databases, report potential problems */
};
-enum form {
- FORM_NONE, /* format is unknown */
- FORM_SRC, /* format is -man or -mdoc */
- FORM_CAT /* format is cat */
-};
-
struct str {
char *rendered; /* key in UTF-8 or ASCII form */
const struct mpage *mpage; /* if set, the owning parse */
struct mpage {
struct inodev inodev; /* used for hashing routine */
int64_t pageid; /* pageid in mpages SQL table */
- enum form form; /* format from file content */
char *sec; /* section from file content */
char *arch; /* architecture from file content */
char *title; /* title from file content */
char *desc; /* description from file content */
struct mlink *mlinks; /* singly linked list */
+ int form; /* format from file content */
};
struct mlink {
char file[PATH_MAX]; /* filename rel. to manpath */
- enum form dform; /* format from directory */
- enum form fform; /* format from file name suffix */
char *dsec; /* section from directory */
char *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 */
};
static void dbprune(void);
static void filescan(const char *);
static void *hash_alloc(size_t, void *);
-static void hash_free(void *, size_t, void *);
-static void *hash_halloc(size_t, void *);
+static void hash_free(void *, void *);
+static void *hash_calloc(size_t, size_t, void *);
static void mlink_add(struct mlink *, const struct stat *);
static void mlink_check(struct mpage *, struct mlink *);
static void mlink_free(struct mlink *);
const struct mdoc_node *, uint64_t);
static void render_key(struct mchars *, struct str *);
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 size_t utf8(unsigned int, char [7]);
{ NULL, 0 }, /* sp */
{ NULL, 0 }, /* %U */
{ NULL, 0 }, /* Ta */
+ { NULL, 0 }, /* ll */
};
memset(&dirs, 0, sizeof(struct manpaths));
mpages_info.alloc = mlinks_info.alloc = hash_alloc;
- mpages_info.halloc = mlinks_info.halloc = hash_halloc;
- mpages_info.hfree = mlinks_info.hfree = hash_free;
+ mpages_info.calloc = mlinks_info.calloc = hash_calloc;
+ mpages_info.free = mlinks_info.free = hash_free;
mpages_info.key_offset = offsetof(struct mpage, inodev);
mlinks_info.key_offset = offsetof(struct mlink, file);
*/
#define CHECKOP(_op, _ch) do \
if (OP_DEFAULT != (_op)) { \
- fprintf(stderr, "-%c: Conflicting option\n", (_ch)); \
+ fprintf(stderr, "%s: -%c: Conflicting option\n", \
+ progname, (_ch)); \
goto usage; \
} while (/*CONSTCOND*/0)
break;
case 'T':
if (strcmp(optarg, "utf8")) {
- fprintf(stderr, "-T%s: Unsupported "
- "output format\n", optarg);
+ fprintf(stderr, "%s: -T%s: "
+ "Unsupported output format\n",
+ progname, optarg);
goto usage;
}
write_utf8 = 1;
argv += optind;
if (OP_CONFFILE == op && argc > 0) {
- fprintf(stderr, "-C: Too many arguments\n");
+ fprintf(stderr, "%s: -C: Too many arguments\n",
+ progname);
goto usage;
}
if (OP_UPDATE == op || OP_DELETE == op || OP_TEST == op) {
/*
- * All of these deal with a specific directory.
+ * Most of these deal with a specific directory.
* Jump into that directory first.
*/
- if (0 == set_basedir(path_arg))
+ if (OP_TEST != op && 0 == set_basedir(path_arg, 1))
goto out;
if (dbopen(1)) {
* manpath_parse() wants to do it.
*/
if (argc > 0) {
- dirs.paths = mandoc_calloc(argc,
- sizeof(char *));
+ dirs.paths = mandoc_reallocarray(NULL,
+ argc, sizeof(char *));
dirs.sz = (size_t)argc;
for (i = 0; i < argc; i++)
dirs.paths[i] = mandoc_strdup(argv[i]);
ohash_init(&mlinks, 6, &mlinks_info);
}
- if (0 == set_basedir(dirs.paths[j]))
- goto out;
+ if (0 == set_basedir(dirs.paths[j], argc > 0))
+ continue;
if (0 == treescan())
- goto out;
- if (0 == set_basedir(dirs.paths[j]))
- goto out;
+ continue;
if (0 == dbopen(0))
- goto out;
+ continue;
mpages_merge(mc, mp);
- if (warnings &&
+ if (warnings && !nodb &&
! (MPARSE_QUICK & mparse_options))
names_check();
dbclose(0);
}
}
out:
- set_basedir(NULL);
manpath_free(&dirs);
mchars_free(mc);
mparse_free(mp);
return;
}
- if (strstr(buf, basedir) == buf)
- start = buf + strlen(basedir) + 1;
- else if (OP_TEST == op)
+ if (OP_TEST == op)
start = buf;
+ else if (strstr(buf, basedir) == buf)
+ start = buf + strlen(basedir);
else {
exitcode = (int)MANDOCLEVEL_BADARG;
say("", "%s: outside base directory", buf);
say(file, "Filename too long");
return;
}
- start = strstr(buf, basedir) == buf ?
- buf + strlen(basedir) + 1 : buf;
+ 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");
{
char any[] = "any";
struct ohash_info str_info;
- int fd[2];
struct mpage *mpage, *mpage_dest;
struct mlink *mlink, *mlink_dest;
struct mdoc *mdoc;
char *sodest;
char *cp;
pid_t child_pid;
- int status;
+ int fd;
unsigned int pslot;
enum mandoclevel lvl;
str_info.alloc = hash_alloc;
- str_info.halloc = hash_halloc;
- str_info.hfree = hash_free;
+ str_info.calloc = hash_calloc;
+ str_info.free = hash_free;
str_info.key_offset = offsetof(struct str, key);
if (0 == nodb)
man = NULL;
sodest = NULL;
child_pid = 0;
- fd[0] = -1;
- fd[1] = -1;
-
- if (mpage->mlinks->gzip) {
- if (-1 == pipe(fd)) {
- exitcode = (int)MANDOCLEVEL_SYSERR;
- say(mpage->mlinks->file, "&pipe gunzip");
- goto nextpage;
- }
- switch (child_pid = fork()) {
- case -1:
- exitcode = (int)MANDOCLEVEL_SYSERR;
- say(mpage->mlinks->file, "&fork gunzip");
- child_pid = 0;
- close(fd[1]);
- close(fd[0]);
- goto nextpage;
- case 0:
- close(fd[0]);
- if (-1 == dup2(fd[1], STDOUT_FILENO)) {
- say(mpage->mlinks->file,
- "&dup gunzip");
- exit(1);
- }
- execlp("gunzip", "gunzip", "-c",
- mpage->mlinks->file, NULL);
- say(mpage->mlinks->file, "&exec gunzip");
- exit(1);
- default:
- close(fd[1]);
- break;
- }
+
+ mparse_open(mp, &fd, mpage->mlinks->file, &child_pid);
+ if (fd == -1) {
+ say(mpage->mlinks->file, "&open");
+ goto nextpage;
}
/*
*/
if (FORM_CAT != mpage->mlinks->dform ||
FORM_CAT != mpage->mlinks->fform) {
- lvl = mparse_readfd(mp, fd[0], mpage->mlinks->file);
+ lvl = mparse_readfd(mp, fd, mpage->mlinks->file);
if (lvl < MANDOCLEVEL_FATAL)
mparse_result(mp, &mdoc, &man, &sodest);
}
goto nextpage;
} else if (NULL != mdoc) {
mpage->form = FORM_SRC;
- mpage->sec =
- mandoc_strdup(mdoc_meta(mdoc)->msec);
+ mpage->sec = mdoc_meta(mdoc)->msec;
+ mpage->sec = mandoc_strdup(
+ NULL == mpage->sec ? "" : mpage->sec);
mpage->arch = mdoc_meta(mdoc)->arch;
mpage->arch = mandoc_strdup(
NULL == mpage->arch ? "" : mpage->arch);
mpage->title =
mandoc_strdup(mpage->mlinks->name);
}
+ if (mpage->mlinks->gzip)
+ mpage->form |= FORM_GZ;
putkey(mpage, mpage->sec, TYPE_sec);
- putkey(mpage, '\0' == *mpage->arch ?
- any : mpage->arch, TYPE_arch);
+ if (*mpage->arch != '\0')
+ putkey(mpage, mpage->arch, TYPE_arch);
for (mlink = mpage->mlinks; mlink; mlink = mlink->next) {
if ('\0' != *mlink->dsec)
} else if (NULL != man)
parse_man(mpage, man_node(man));
else
- parse_cat(mpage, fd[0]);
+ parse_cat(mpage, fd);
if (NULL == mpage->desc)
mpage->desc = mandoc_strdup(mpage->mlinks->name);
dbadd(mpage, mc);
nextpage:
- if (child_pid) {
- if (-1 == waitpid(child_pid, &status, 0)) {
- exitcode = (int)MANDOCLEVEL_SYSERR;
- say(mpage->mlinks->file, "&wait gunzip");
- } else if (WIFSIGNALED(status)) {
- exitcode = (int)MANDOCLEVEL_SYSERR;
- say(mpage->mlinks->file,
- "gunzip died from signal %d",
- WTERMSIG(status));
- } else if (WEXITSTATUS(status)) {
- exitcode = (int)MANDOCLEVEL_SYSERR;
- say(mpage->mlinks->file,
- "gunzip failed with code %d",
- WEXITSTATUS(status));
- }
+ if (child_pid &&
+ mparse_wait(mp, child_pid) != MANDOCLEVEL_OK) {
+ exitcode = (int)MANDOCLEVEL_SYSERR;
+ say(mpage->mlinks->file, "&wait gunzip");
}
ohash_delete(&strings);
ohash_delete(&names);
say("", "%s", sqlite3_errmsg(db));
while (SQLITE_ROW == (irc = sqlite3_step(stmt))) {
- name = sqlite3_column_text(stmt, 0);
- sec = sqlite3_column_text(stmt, 1);
- arch = sqlite3_column_text(stmt, 2);
- key = sqlite3_column_text(stmt, 3);
+ 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);
fopen(mpage->mlinks->file, "r") :
fdopen(fd, "r");
if (NULL == stream) {
+ if (-1 != fd)
+ close(fd);
if (warnings)
say(mpage->mlinks->file, "&fopen");
return;
htab = &strings;
if (debug > 1)
for (i = 0; i < mansearch_keymax; i++)
- if (1 << i & v)
+ if ((uint64_t)1 << i & v)
say(mpage->mlinks->file,
"Adding key %s=%*s",
mansearch_keynames[i], sz, cp);
s->mask |= v;
return;
} 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);
ohash_insert(htab, slot, s);
}
SQL_BIND_INT64(stmts[STMT_INSERT_LINK], i, mlink->mpage->pageid);
SQL_STEP(stmts[STMT_INSERT_LINK]);
sqlite3_reset(stmts[STMT_INSERT_LINK]);
+
+ i = 1;
+ SQL_BIND_INT64(stmts[STMT_INSERT_NAME], i, NAME_FILE);
+ 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]);
}
/*
mlink = mpage->mlinks;
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 ||
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;
- SQL_BIND_TEXT(stmts[STMT_INSERT_PAGE], i, mpage->desc);
- SQL_BIND_INT(stmts[STMT_INSERT_PAGE], i, FORM_SRC == mpage->form);
+ SQL_BIND_TEXT(stmts[STMT_INSERT_PAGE], i, key->rendered);
+ SQL_BIND_INT(stmts[STMT_INSERT_PAGE], i, mpage->form);
SQL_STEP(stmts[STMT_INSERT_PAGE]);
mpage->pageid = sqlite3_last_insert_rowid(db);
sqlite3_reset(stmts[STMT_INSERT_PAGE]);
+ if (key->rendered != key->key)
+ free(key->rendered);
+ free(key);
+
while (NULL != mlink) {
dbadd_mlink(mlink);
mlink = mlink->next;
rc = sqlite3_open_v2(MANDOC_DB, &db, ofl, NULL);
if (SQLITE_OK != rc) {
exitcode = (int)MANDOCLEVEL_SYSERR;
- say(MANDOC_DB, "%s", sqlite3_errmsg(db));
+ if (SQLITE_CANTOPEN != rc)
+ say(MANDOC_DB, "%s", sqlite3_errstr(rc));
return(0);
}
goto prepare_statements;
goto create_tables;
if (MPARSE_QUICK & mparse_options) {
exitcode = (int)MANDOCLEVEL_SYSERR;
- say(MANDOC_DB "~", "%s", sqlite3_errmsg(db));
+ say(MANDOC_DB "~", "%s", sqlite3_errstr(rc));
return(0);
}
rc = sqlite3_open_v2(tempfilename, &db, ofl, NULL);
if (SQLITE_OK != rc) {
exitcode = (int)MANDOCLEVEL_SYSERR;
- say("", "%s: %s", tempfilename, sqlite3_errmsg(db));
+ say("", "%s: %s", tempfilename, sqlite3_errstr(rc));
return(0);
}
if (SQLITE_OK != sqlite3_exec(db, sql, NULL, NULL, NULL)) {
exitcode = (int)MANDOCLEVEL_SYSERR;
say(MANDOC_DB, "%s", sqlite3_errmsg(db));
+ sqlite3_close(db);
return(0);
}
prepare_statements:
- SQL_EXEC("PRAGMA foreign_keys = ON");
+ if (SQLITE_OK != sqlite3_exec(db,
+ "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=?)";
* synchronous mode for much better performance.
*/
- if (real)
- SQL_EXEC("PRAGMA synchronous = OFF");
+ if (real && SQLITE_OK != sqlite3_exec(db,
+ "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
return(1);
}
static void *
-hash_halloc(size_t sz, void *arg)
+hash_calloc(size_t n, size_t sz, void *arg)
{
- return(mandoc_calloc(sz, 1));
+ return(mandoc_calloc(n, sz));
}
static void *
}
static void
-hash_free(void *p, size_t sz, void *arg)
+hash_free(void *p, void *arg)
{
free(p);
}
static int
-set_basedir(const char *targetdir)
+set_basedir(const char *targetdir, int report_baddir)
{
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 the original working directory, if possible.
+ * This will be needed if the second or a later directory
+ * on the command line is given as a relative path.
+ * Do not error out if the current directory is not
+ * searchable: Maybe it won't be needed after all.
+ */
+ if (0 == getcwd_status) {
+ if (NULL == getcwd(startdir, sizeof(startdir))) {
+ getcwd_status = 2;
+ (void)strlcpy(startdir, strerror(errno),
+ sizeof(startdir));
+ } else
+ getcwd_status = 1;
+ }
/*
- * Remember where we started by keeping a fd open to the origin
- * path component: throughout this utility, we chdir() a lot to
- * handle relative paths, and by doing this, we can return to
- * the starting point.
+ * We are leaving the old base directory.
+ * Do not use it any longer, not even for messages.
*/
- if ('\0' == *startdir) {
- if (NULL == getcwd(startdir, PATH_MAX)) {
+ *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;
- if (NULL != targetdir)
- say("", "&getcwd");
+ say("", "getcwd: %s", startdir);
return(0);
}
- if (-1 == (fd = open(startdir, O_RDONLY, 0))) {
- exitcode = (int)MANDOCLEVEL_SYSERR;
- say("", "&open %s", startdir);
- return(0);
- }
- if (NULL == targetdir)
- targetdir = startdir;
- } else {
- if (-1 == fd)
- return(0);
- if (-1 == fchdir(fd)) {
- close(fd);
- basedir[0] = '\0';
+ if (-1 == chdir(startdir)) {
exitcode = (int)MANDOCLEVEL_SYSERR;
say("", "&chdir %s", startdir);
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)) {
- basedir[0] = '\0';
- exitcode = (int)MANDOCLEVEL_BADARG;
- say("", "&%s: realpath", targetdir);
+ if (report_baddir || errno != ENOENT) {
+ exitcode = (int)MANDOCLEVEL_BADARG;
+ say("", "&%s: realpath", targetdir);
+ }
return(0);
} else if (-1 == chdir(basedir)) {
- exitcode = (int)MANDOCLEVEL_BADARG;
- say("", "&chdir");
+ if (report_baddir || errno != ENOENT) {
+ exitcode = (int)MANDOCLEVEL_BADARG;
+ say("", "&chdir");
+ }
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);
}
if ('\0' != *basedir)
fprintf(stderr, "%s", basedir);
if ('\0' != *basedir && '\0' != *file)
- fputs("//", stderr);
+ fputc('/', stderr);
if ('\0' != *file)
fprintf(stderr, "%s", file);