diff options
author | Ingo Schwarze <schwarze@openbsd.org> | 2016-07-19 21:31:55 +0000 |
---|---|---|
committer | Ingo Schwarze <schwarze@openbsd.org> | 2016-07-19 21:31:55 +0000 |
commit | 4fe58b8b37f6b19d2c50441b4df20ca5687fe6ed (patch) | |
tree | 99ff72d0dd74a36a5e20f8cf1225a78361b8b4af | |
parent | 0512b80d5f59824b3d18cd1494119fd904528ca5 (diff) | |
download | mandoc-4fe58b8b37f6b19d2c50441b4df20ca5687fe6ed.tar.gz |
Remove the dependency on SQLite without loss of functionality.
Stop supporting systems that don't have mmap(3).
Drop the obsolete names_check() now that we deleted MLINKS.
-rw-r--r-- | INSTALL | 19 | ||||
-rw-r--r-- | Makefile | 79 | ||||
-rw-r--r-- | Makefile.depend | 20 | ||||
-rw-r--r-- | TODO | 6 | ||||
-rw-r--r-- | compat_sqlite3_errstr.c | 16 | ||||
-rwxr-xr-x | configure | 70 | ||||
-rw-r--r-- | configure.local.example | 65 | ||||
-rw-r--r-- | dba.c | 418 | ||||
-rw-r--r-- | dba.h | 51 | ||||
-rw-r--r-- | dba_array.c | 188 | ||||
-rw-r--r-- | dba_array.h | 47 | ||||
-rw-r--r-- | dba_read.c | 74 | ||||
-rw-r--r-- | dba_write.c | 117 | ||||
-rw-r--r-- | dba_write.h (renamed from test-sqlite3.c) | 47 | ||||
-rw-r--r-- | dbm.c | 452 | ||||
-rw-r--r-- | dbm.h | 68 | ||||
-rw-r--r-- | dbm_map.c | 174 | ||||
-rw-r--r-- | dbm_map.h (renamed from mansearch_const.c) | 28 | ||||
-rw-r--r-- | main.c | 27 | ||||
-rw-r--r-- | mandocdb.c | 459 | ||||
-rw-r--r-- | mansearch.c | 1046 | ||||
-rw-r--r-- | mansearch.h | 19 | ||||
-rw-r--r-- | read.c | 6 | ||||
-rw-r--r-- | test-mmap.c | 9 | ||||
-rw-r--r-- | test-sqlite3_errstr.c | 8 |
25 files changed, 2275 insertions, 1238 deletions
@@ -66,7 +66,7 @@ Otherwise, if your system uses man.conf(5), make sure it contains a "manpath" line for each directory tree, and the order of these lines meets your wishes. -7. If you compiled with database support, run the command "sudo +7. Run the command "sudo makewhatis" to build mandoc.db(5) databases in all the directory trees configured in step 6. Whenever installing new manual pages, re-run makewhatis(8) to update the databases, or apropos(1) will @@ -84,20 +84,9 @@ manual page source. Understanding mandoc dependencies --------------------------------- -The mandoc(1), man(1), and demandoc(1) utilities only depend -on the zlib library for decompressing gzipped manual pages, -but makewhatis(8) and apropos(1) depend on the following -additional software: - -1. The SQLite database system, see <http://sqlite.org/>. -The recommended version of SQLite is 3.8.4.3 or newer. The mandoc -toolset is known to work with version 3.7.5 or newer. Versions -older than 3.8.3 may not achieve full performance due to the -missing SQLITE_DETERMINISTIC optimization flag. Versions older -than 3.8.0 may not show full error information if opening a database -fails due to the missing sqlite3_errstr() API. Both are very minor -problems, apropos(1) is fully usable with SQLite 3.7.5. Versions -older than 3.7.5 may or may not work, they have not been tested. +The following libraries are required: + +1. zlib for decompressing gzipped manual pages. 2. The fts(3) directory traversion functions. If your system does not have them, the bundled compatibility version @@ -15,7 +15,7 @@ # ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF # OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -VERSION = 1.13.4 +VERSION = 1.14.0 # === LIST OF FILES ==================================================== @@ -26,7 +26,6 @@ TESTSRCS = test-dirent-namlen.c \ test-getsubopt.c \ test-isblank.c \ test-mkdtemp.c \ - test-mmap.c \ test-ohash.c \ test-pledge.c \ test-progname.c \ @@ -34,8 +33,6 @@ TESTSRCS = test-dirent-namlen.c \ test-rewb-bsd.c \ test-rewb-sysv.c \ test-sandbox_init.c \ - test-sqlite3.c \ - test-sqlite3_errstr.c \ test-strcasestr.c \ test-stringlist.c \ test-strlcat.c \ @@ -58,7 +55,6 @@ SRCS = att.c \ compat_ohash.c \ compat_progname.c \ compat_reallocarray.c \ - compat_sqlite3_errstr.c \ compat_strcasestr.c \ compat_stringlist.c \ compat_strlcat.c \ @@ -66,6 +62,12 @@ SRCS = att.c \ compat_strsep.c \ compat_strtonum.c \ compat_vasprintf.c \ + dba.c \ + dba_array.c \ + dba_read.c \ + dba_write.c \ + dbm.c \ + dbm_map.c \ demandoc.c \ eqn.c \ eqn_html.c \ @@ -86,7 +88,6 @@ SRCS = att.c \ manpage.c \ manpath.c \ mansearch.c \ - mansearch_const.c \ mdoc.c \ mdoc_argv.c \ mdoc_hash.c \ @@ -128,6 +129,11 @@ DISTFILES = INSTALL \ compat_stringlist.h \ configure \ configure.local.example \ + dba.h \ + dba_array.h \ + dba_write.h \ + dbm.h \ + dbm_map.h \ demandoc.1 \ eqn.7 \ gmdiff \ @@ -220,7 +226,6 @@ COMPAT_OBJS = compat_err.o \ compat_ohash.o \ compat_progname.o \ compat_reallocarray.o \ - compat_sqlite3_errstr.o \ compat_strcasestr.o \ compat_strlcat.o \ compat_strlcpy.o \ @@ -244,28 +249,35 @@ MANDOC_TERM_OBJS = eqn_term.o \ term_ps.o \ tbl_term.o -BASE_OBJS = $(MANDOC_HTML_OBJS) \ +DBM_OBJS = dbm.o \ + dbm_map.o \ + mansearch.o + +DBA_OBJS = dba.o \ + dba_array.o \ + dba_read.o \ + dba_write.o \ + mandocdb.o + +MAIN_OBJS = $(MANDOC_HTML_OBJS) \ $(MANDOC_MAN_OBJS) \ $(MANDOC_TERM_OBJS) \ + $(DBM_OBJS) \ + $(DBA_OBJS) \ main.o \ manpath.o \ out.o \ tag.o \ tree.o -MAIN_OBJS = $(BASE_OBJS) - -DB_OBJS = mandocdb.o \ - mansearch.o \ - mansearch_const.o - CGI_OBJS = $(MANDOC_HTML_OBJS) \ + $(DBM_OBJS) \ cgi.o \ - mansearch.o \ - mansearch_const.o \ out.o -MANPAGE_OBJS = manpage.o mansearch.o mansearch_const.o manpath.o +MANPAGE_OBJS = $(DBM_OBJS) \ + manpage.o \ + manpath.o DEMANDOC_OBJS = demandoc.o @@ -329,7 +341,7 @@ www: $(WWW_OBJS) $(WWW_MANS) $(WWW_MANS): mandoc -.PHONY: base-install cgi-install db-install install www-install +.PHONY: base-install cgi-install install www-install .PHONY: clean distclean depend include Makefile.depend @@ -341,7 +353,7 @@ distclean: clean clean: rm -f libmandoc.a $(LIBMANDOC_OBJS) $(COMPAT_OBJS) - rm -f mandoc $(BASE_OBJS) $(DB_OBJS) + rm -f mandoc $(MAIN_OBJS) rm -f man.cgi $(CGI_OBJS) rm -f manpage $(MANPAGE_OBJS) rm -f demandoc $(DEMANDOC_OBJS) @@ -351,47 +363,40 @@ clean: base-install: base-build mkdir -p $(DESTDIR)$(BINDIR) + mkdir -p $(DESTDIR)$(SBINDIR) mkdir -p $(DESTDIR)$(LIBDIR) mkdir -p $(DESTDIR)$(INCLUDEDIR) mkdir -p $(DESTDIR)$(MANDIR)/man1 mkdir -p $(DESTDIR)$(MANDIR)/man3 mkdir -p $(DESTDIR)$(MANDIR)/man5 mkdir -p $(DESTDIR)$(MANDIR)/man7 + mkdir -p $(DESTDIR)$(MANDIR)/man8 $(INSTALL_PROGRAM) mandoc demandoc $(DESTDIR)$(BINDIR) $(INSTALL_PROGRAM) soelim $(DESTDIR)$(BINDIR)/$(BINM_SOELIM) ln -f $(DESTDIR)$(BINDIR)/mandoc $(DESTDIR)$(BINDIR)/$(BINM_MAN) + ln -f $(DESTDIR)$(BINDIR)/mandoc $(DESTDIR)$(BINDIR)/$(BINM_APROPOS) + ln -f $(DESTDIR)$(BINDIR)/mandoc $(DESTDIR)$(BINDIR)/$(BINM_WHATIS) + ln -f $(DESTDIR)$(BINDIR)/mandoc \ + $(DESTDIR)$(SBINDIR)/$(BINM_MAKEWHATIS) $(INSTALL_LIB) libmandoc.a $(DESTDIR)$(LIBDIR) $(INSTALL_LIB) man.h mandoc.h mandoc_aux.h mdoc.h roff.h \ $(DESTDIR)$(INCLUDEDIR) $(INSTALL_MAN) mandoc.1 demandoc.1 $(DESTDIR)$(MANDIR)/man1 $(INSTALL_MAN) soelim.1 $(DESTDIR)$(MANDIR)/man1/$(BINM_SOELIM).1 $(INSTALL_MAN) man.1 $(DESTDIR)$(MANDIR)/man1/$(BINM_MAN).1 + $(INSTALL_MAN) apropos.1 $(DESTDIR)$(MANDIR)/man1/$(BINM_APROPOS).1 + ln -f $(DESTDIR)$(MANDIR)/man1/$(BINM_APROPOS).1 \ + $(DESTDIR)$(MANDIR)/man1/$(BINM_WHATIS).1 $(INSTALL_MAN) mandoc.3 mandoc_escape.3 mandoc_malloc.3 \ - mchars_alloc.3 tbl.3 $(DESTDIR)$(MANDIR)/man3 + mansearch.3 mchars_alloc.3 tbl.3 $(DESTDIR)$(MANDIR)/man3 $(INSTALL_MAN) man.conf.5 $(DESTDIR)$(MANDIR)/man5/${MANM_MANCONF}.5 + $(INSTALL_MAN) mandoc.db.5 $(DESTDIR)$(MANDIR)/man5 $(INSTALL_MAN) man.7 $(DESTDIR)$(MANDIR)/man7/${MANM_MAN}.7 $(INSTALL_MAN) mdoc.7 $(DESTDIR)$(MANDIR)/man7/${MANM_MDOC}.7 $(INSTALL_MAN) roff.7 $(DESTDIR)$(MANDIR)/man7/${MANM_ROFF}.7 $(INSTALL_MAN) eqn.7 $(DESTDIR)$(MANDIR)/man7/${MANM_EQN}.7 $(INSTALL_MAN) tbl.7 $(DESTDIR)$(MANDIR)/man7/${MANM_TBL}.7 $(INSTALL_MAN) mandoc_char.7 $(DESTDIR)$(MANDIR)/man7 - -db-install: base-build - mkdir -p $(DESTDIR)$(BINDIR) - mkdir -p $(DESTDIR)$(SBINDIR) - mkdir -p $(DESTDIR)$(MANDIR)/man1 - mkdir -p $(DESTDIR)$(MANDIR)/man3 - mkdir -p $(DESTDIR)$(MANDIR)/man5 - mkdir -p $(DESTDIR)$(MANDIR)/man8 - ln -f $(DESTDIR)$(BINDIR)/mandoc $(DESTDIR)$(BINDIR)/$(BINM_APROPOS) - ln -f $(DESTDIR)$(BINDIR)/mandoc $(DESTDIR)$(BINDIR)/$(BINM_WHATIS) - ln -f $(DESTDIR)$(BINDIR)/mandoc \ - $(DESTDIR)$(SBINDIR)/$(BINM_MAKEWHATIS) - $(INSTALL_MAN) apropos.1 $(DESTDIR)$(MANDIR)/man1/$(BINM_APROPOS).1 - ln -f $(DESTDIR)$(MANDIR)/man1/$(BINM_APROPOS).1 \ - $(DESTDIR)$(MANDIR)/man1/$(BINM_WHATIS).1 - $(INSTALL_MAN) mansearch.3 $(DESTDIR)$(MANDIR)/man3 - $(INSTALL_MAN) mandoc.db.5 $(DESTDIR)$(MANDIR)/man5 $(INSTALL_MAN) makewhatis.8 \ $(DESTDIR)$(MANDIR)/man8/$(BINM_MAKEWHATIS).8 diff --git a/Makefile.depend b/Makefile.depend index d2d2e001..e0c3a2e9 100644 --- a/Makefile.depend +++ b/Makefile.depend @@ -1,16 +1,15 @@ att.o: att.c config.h roff.h mdoc.h libmdoc.h cgi.o: cgi.c config.h mandoc_aux.h mandoc.h roff.h mdoc.h man.h main.h manconf.h mansearch.h cgi.h -chars.o: chars.c config.h mandoc.h mandoc_aux.h mandoc_ohash.h compat_ohash.h libmandoc.h +chars.o: chars.c config.h mandoc.h mandoc_aux.h mandoc_ohash.h libmandoc.h compat_err.o: compat_err.c config.h -compat_fts.o: compat_fts.c config.h compat_fts.h +compat_fts.o: compat_fts.c config.h compat_getline.o: compat_getline.c config.h compat_getsubopt.o: compat_getsubopt.c config.h compat_isblank.o: compat_isblank.c config.h compat_mkdtemp.o: compat_mkdtemp.c config.h -compat_ohash.o: compat_ohash.c config.h compat_ohash.h +compat_ohash.o: compat_ohash.c config.h compat_progname.o: compat_progname.c config.h compat_reallocarray.o: compat_reallocarray.c config.h -compat_sqlite3_errstr.o: compat_sqlite3_errstr.c config.h compat_strcasestr.o: compat_strcasestr.c config.h compat_stringlist.o: compat_stringlist.c config.h compat_stringlist.h compat_strlcat.o: compat_strlcat.c config.h @@ -18,6 +17,12 @@ compat_strlcpy.o: compat_strlcpy.c config.h compat_strsep.o: compat_strsep.c config.h compat_strtonum.o: compat_strtonum.c config.h compat_vasprintf.o: compat_vasprintf.c config.h +dba.o: dba.c mandoc_aux.h mansearch.h dba_write.h dba_array.h dba.h +dba_array.o: dba_array.c mandoc_aux.h dba_write.h dba_array.h +dba_read.o: dba_read.c mandoc_aux.h mansearch.h dba_array.h dba.h dbm.h +dba_write.o: dba_write.c dba_write.h +dbm.o: dbm.c mansearch.h dbm_map.h dbm.h +dbm_map.o: dbm_map.c mansearch.h dbm_map.h dbm.h demandoc.o: demandoc.c config.h roff.h man.h mdoc.h mandoc.h eqn.o: eqn.c config.h mandoc.h mandoc_aux.h libmandoc.h libroff.h eqn_html.o: eqn_html.c config.h mandoc.h out.h html.h @@ -34,11 +39,10 @@ man_validate.o: man_validate.c config.h mandoc_aux.h mandoc.h roff.h man.h libma mandoc.o: mandoc.c config.h mandoc.h mandoc_aux.h libmandoc.h mandoc_aux.o: mandoc_aux.c config.h mandoc.h mandoc_aux.h mandoc_ohash.o: mandoc_ohash.c mandoc_aux.h mandoc_ohash.h compat_ohash.h -mandocdb.o: mandocdb.c config.h compat_fts.h mandoc_aux.h mandoc_ohash.h compat_ohash.h mandoc.h roff.h mdoc.h man.h manconf.h mansearch.h +mandocdb.o: mandocdb.c config.h mandoc_aux.h mandoc_ohash.h mandoc.h roff.h mdoc.h man.h manconf.h mansearch.h dba_array.h dba.h manpage.o: manpage.c config.h manconf.h mansearch.h manpath.o: manpath.c config.h mandoc_aux.h manconf.h -mansearch.o: mansearch.c config.h mandoc.h mandoc_aux.h mandoc_ohash.h compat_ohash.h manconf.h mansearch.h -mansearch_const.o: mansearch_const.c config.h mansearch.h +mansearch.o: mansearch.c mandoc.h mandoc_aux.h mandoc_ohash.h compat_ohash.h manconf.h mansearch.h dbm.h mdoc.o: mdoc.c config.h mandoc_aux.h mandoc.h roff.h mdoc.h libmandoc.h roff_int.h libmdoc.h mdoc_argv.o: mdoc_argv.c config.h mandoc_aux.h mandoc.h roff.h mdoc.h libmandoc.h roff_int.h libmdoc.h mdoc_hash.o: mdoc_hash.c config.h mandoc.h roff.h mdoc.h libmandoc.h libmdoc.h @@ -55,7 +59,7 @@ read.o: read.c config.h mandoc_aux.h mandoc.h roff.h mdoc.h man.h libmandoc.h ro roff.o: roff.c config.h mandoc.h mandoc_aux.h roff.h libmandoc.h roff_int.h libroff.h predefs.in soelim.o: soelim.c config.h compat_stringlist.h st.o: st.c config.h roff.h mdoc.h libmdoc.h st.in -tag.o: tag.c config.h mandoc_aux.h mandoc_ohash.h compat_ohash.h tag.h +tag.o: tag.c config.h mandoc_aux.h mandoc_ohash.h tag.h tbl.o: tbl.c config.h mandoc.h mandoc_aux.h libmandoc.h libroff.h tbl_data.o: tbl_data.c config.h mandoc.h mandoc_aux.h libmandoc.h libroff.h tbl_html.o: tbl_html.c config.h mandoc.h out.h html.h @@ -612,7 +612,6 @@ are mere guesses, and some may be wrong. ************************************************************************ - Why are we using MAP_SHARED, not MAP_PRIVATE for mmap(2)? - How does SQLITE_CONFIG_PAGECACHE actually work? Document it! from kristaps@ Sat, 09 Aug 2014 13:51:36 +0200 Several areas can be cleaned up to make mandoc even faster. These are @@ -646,11 +645,6 @@ Several areas can be cleaned up to make mandoc even faster. These are - struct mparse refactoring Steffen Nurpmeso Thu, 04 Sep 2014 12:50:00 +0200 -- Consider creating some views that will make the database more - readable from the sqlite3 shell. Consider using them to - abstract from the database structure, too. - suggested by espie@ Sat, 19 Apr 2014 14:52:57 +0200 - ************************************************************************ * CGI issues ************************************************************************ diff --git a/compat_sqlite3_errstr.c b/compat_sqlite3_errstr.c deleted file mode 100644 index 8a6ace28..00000000 --- a/compat_sqlite3_errstr.c +++ /dev/null @@ -1,16 +0,0 @@ -#include "config.h" - -#if HAVE_SQLITE3_ERRSTR - -int dummy; - -#else - -const char * -sqlite3_errstr(int rc) -{ - - return rc ? "unknown error" : "not an error"; -} - -#endif @@ -40,10 +40,8 @@ CFLAGS="${CFLAGS} -Wno-unused-parameter" LDADD= LDFLAGS= LD_OHASH= -LD_SQLITE3= STATIC="-static" -BUILD_DB=1 BUILD_CGI=0 HAVE_DIRENT_NAMLEN= @@ -53,7 +51,6 @@ HAVE_GETLINE= HAVE_GETSUBOPT= HAVE_ISBLANK= HAVE_MKDTEMP= -HAVE_MMAP= HAVE_PLEDGE= HAVE_PROGNAME= HAVE_REALLOCARRAY= @@ -70,8 +67,6 @@ HAVE_STRTONUM= HAVE_VASPRINTF= HAVE_WCHAR= -HAVE_SQLITE3= -HAVE_SQLITE3_ERRSTR= HAVE_OHASH= HAVE_MANPATH= @@ -186,7 +181,6 @@ runtest getline GETLINE || true runtest getsubopt GETSUBOPT || true runtest isblank ISBLANK || true runtest mkdtemp MKDTEMP || true -runtest mmap MMAP || true runtest pledge PLEDGE || true runtest sandbox_init SANDBOX_INIT || true runtest progname PROGNAME || true @@ -203,44 +197,6 @@ runtest strtonum STRTONUM || true runtest vasprintf VASPRINTF || true runtest wchar WCHAR || true -# --- sqlite3 --- -if [ ${BUILD_DB} -eq 0 ]; then - echo "BUILD_DB=0 (manual)" 1>&2 - echo "BUILD_DB=0 (manual)" 1>&3 - echo 1>&3 - HAVE_SQLITE3=0 -elif ismanual sqlite3 "${HAVE_SQLITE3}"; then - if [ -z "${LD_SQLITE3}" ]; then - LD_SQLITE3="-lsqlite3" - fi -elif [ -n "${LD_SQLITE3}" ]; then - runtest sqlite3 SQLITE3 "${LD_SQLITE3}" || true -elif singletest sqlite3 SQLITE3 "-lsqlite3"; then - LD_SQLITE3="-lsqlite3" -elif runtest sqlite3 SQLITE3 \ - "-I/usr/local/include -L/usr/local/lib -lsqlite3"; then - LD_SQLITE3="-L/usr/local/lib -lsqlite3" - CFLAGS="${CFLAGS} -I/usr/local/include" -fi -if [ ${HAVE_SQLITE3} -eq 0 ]; then - LD_SQLITE3= - if [ ${BUILD_DB} -gt 0 ]; then - echo "BUILD_DB=0 (no sqlite3)" 1>&2 - echo "BUILD_DB=0 (no sqlite3)" 1>&3 - echo 1>&3 - BUILD_DB=0 - fi -fi - -# --- sqlite3_errstr --- -if [ ${BUILD_DB} -eq 0 ]; then - HAVE_SQLITE3_ERRSTR=1 -elif ismanual sqlite3_errstr "${HAVE_SQLITE3_ERRSTR}"; then - : -else - runtest sqlite3_errstr SQLITE3_ERRSTR "${LD_SQLITE3}" || true -fi - # --- ohash --- if ismanual ohash "${HAVE_OHASH}"; then : @@ -256,7 +212,7 @@ if [ "${HAVE_OHASH}" -eq 0 ]; then fi # --- LDADD --- -LDADD="${LDADD} ${LD_SQLITE3} ${LD_OHASH} -lz" +LDADD="${LDADD} ${LD_OHASH} -lz" echo "LDADD=\"${LDADD}\"" 1>&2 echo "LDADD=\"${LDADD}\"" 1>&3 echo 1>&3 @@ -315,7 +271,6 @@ cat << __HEREDOC__ #define HAVE_GETSUBOPT ${HAVE_GETSUBOPT} #define HAVE_ISBLANK ${HAVE_ISBLANK} #define HAVE_MKDTEMP ${HAVE_MKDTEMP} -#define HAVE_MMAP ${HAVE_MMAP} #define HAVE_PLEDGE ${HAVE_PLEDGE} #define HAVE_PROGNAME ${HAVE_PROGNAME} #define HAVE_REALLOCARRAY ${HAVE_REALLOCARRAY} @@ -331,8 +286,6 @@ cat << __HEREDOC__ #define HAVE_STRTONUM ${HAVE_STRTONUM} #define HAVE_VASPRINTF ${HAVE_VASPRINTF} #define HAVE_WCHAR ${HAVE_WCHAR} -#define HAVE_SQLITE3 ${HAVE_SQLITE3} -#define HAVE_SQLITE3_ERRSTR ${HAVE_SQLITE3_ERRSTR} #define HAVE_OHASH ${HAVE_OHASH} #define HAVE_MANPATH ${HAVE_MANPATH} @@ -371,9 +324,6 @@ fi [ ${HAVE_REALLOCARRAY} -eq 0 ] && \ echo "extern void *reallocarray(void *, size_t, size_t);" -[ ${BUILD_DB} -gt 0 -a ${HAVE_SQLITE3_ERRSTR} -eq 0 ] && - echo "extern const char *sqlite3_errstr(int);" - [ ${HAVE_STRCASESTR} -eq 0 ] && \ echo "extern char *strcasestr(const char *, const char *);" @@ -413,17 +363,10 @@ exec > Makefile.local [ -z "${INSTALL_MAN}" ] && INSTALL_MAN="${INSTALL} -m 0444" [ -z "${INSTALL_DATA}" ] && INSTALL_DATA="${INSTALL} -m 0444" -if [ ${BUILD_DB} -eq 0 -a ${BUILD_CGI} -gt 0 ]; then - echo "BUILD_CGI=0 (no BUILD_DB)" 1>&2 - echo "BUILD_CGI=0 (no BUILD_DB)" 1>&3 - BUILD_CGI=0 -fi - -BUILD_TARGETS="base-build" -[ ${BUILD_CGI} -gt 0 ] && BUILD_TARGETS="${BUILD_TARGETS} cgi-build" -INSTALL_TARGETS="base-install" -[ ${BUILD_DB} -gt 0 ] && INSTALL_TARGETS="${INSTALL_TARGETS} db-install" -[ ${BUILD_CGI} -gt 0 ] && INSTALL_TARGETS="${INSTALL_TARGETS} cgi-install" +BUILD_TARGETS= +[ ${BUILD_CGI} -gt 0 ] && BUILD_TARGETS="cgi-build" +INSTALL_TARGETS= +[ ${BUILD_CGI} -gt 0 ] && INSTALL_TARGETS="cgi-install" cat << __HEREDOC__ BUILD_TARGETS = ${BUILD_TARGETS} @@ -460,9 +403,6 @@ INSTALL_MAN = ${INSTALL_MAN} INSTALL_DATA = ${INSTALL_DATA} __HEREDOC__ -[ ${BUILD_DB} -gt 0 ] && \ - echo "MAIN_OBJS = \$(BASE_OBJS) \$(DB_OBJS)" - echo "Makefile.local: written" 1>&2 echo "Makefile.local: written" 1>&3 diff --git a/configure.local.example b/configure.local.example index 8304ded8..146ca726 100644 --- a/configure.local.example +++ b/configure.local.example @@ -65,7 +65,7 @@ MANPATH_DEFAULT="/usr/share/man:/usr/X11R6/man:/usr/local/man" # If you do not want uname(3) to be called but instead want a fixed # string to be used, use the following line: -OSNAME="OpenBSD 5.9" +OSNAME="OpenBSD 6.0" # The following installation directories are used. # It is possible to set only one or a few of these variables, @@ -111,14 +111,20 @@ MANM_ROFF="mandoc_roff" # default is "roff" MANM_EQN="mandoc_eqn" # default is "eqn" MANM_TBL="mandoc_tbl" # default is "tbl" -# Some distributions may want to avoid naming conflicts -# with other man(1) and soelim(1) utilities. +# Some distributions may want to avoid naming conflicts with +# other man(1), apropos(1), makewhatis(8), or soelim(1) utilities. # If you want to change the names of binary programs, # the following alternative names are suggested. # Using different names is possible as well. -# This changes the names of the installed section 1 manual pages as well. +# This changes the names of the installed section 1 and section 8 +# manual pages as well. +# It is possible to set only one or two of these variables, +# there is no need to copy the whole block. BINM_MAN=mman # default is "man" +BINM_APROPOS=mapropos # default is "apropos" +BINM_WHATIS=mwhatis # default is "whatis" +BINM_MAKEWHATIS=mandocdb # default is "makewhatis" BINM_SOELIM=msoelim # default is "soelim" # Before falling back to the bundled version of the ohash(3) hashing @@ -129,6 +135,13 @@ BINM_SOELIM=msoelim # default is "soelim" LD_OHASH="-lutil" +# When library autodetection decides to use -L/usr/local/lib, +# -I/usr/local/include is automatically added to CFLAGS. +# If you manually set LD_OHASH to something including -L/usr/local/lib, +# chances are you will also need the following line: + +CFLAGS="${CFLAGS} -I/usr/local/include" + # Some platforms may need additional linker flags to link against libmandoc # that are not autodetected. # For example, Solaris 9 and 10 need -lrt for nanosleep(2). @@ -150,43 +163,6 @@ INSTALL_LIB="${INSTALL} -m 0444" INSTALL_MAN="${INSTALL} -m 0444" INSTALL_DATA="${INSTALL} -m 0444" -# --- user settings related to database support ------------------------ - -# By default, building makewhatis(8) and apropos(1) is enabled. -# To disable it, for example to avoid the dependency on SQLite3, -# use the following line. It that case, the remaining settings -# in this section are irrelevant. - -BUILD_DB=0 - -# Autoconfiguration tries the following linker flags to find the -# SQLite3 library installed on your system. If none of these work, -# set the following variable to specify the required linker flags. - -LD_SQLITE3="-lsqlite3" -LD_SQLITE3="-L/usr/local/lib -lsqlite3" - -# When library autodetection decides to use -L/usr/local/lib, -# -I/usr/local/include is automatically added to CFLAGS. -# If you manually set LD_SQLITE3 to something including -L/usr/local/lib, -# chances are you will also need the following line: - -CFLAGS="${CFLAGS} -I/usr/local/include" - -# Some distributions may want to avoid naming conflicts -# with another implementation of apropos(1) and makewhatis(8). -# If you want to change the names of the binary programs, -# the following alternative names are suggested. -# Using other names is possible as well. -# This changes the names of the installed section 1 and section 8 -# manual pages as well. -# It is possible to set only one or two of these variables, -# there is no need to copy the whole block. - -BINM_APROPOS=mapropos # default is "apropos" -BINM_WHATIS=mwhatis # default is "whatis" -BINM_MAKEWHATIS=mandocdb # default is "makewhatis" - # When using the "homebrew" package manager on Mac OS X, the actual # manuals are located in a so-called "cellar" and only symlinked # into the manual trees. To allow mandoc to follow such symlinks, @@ -200,7 +176,6 @@ HOMEBREWDIR="${PREFIX}/Cellar" # By default, building man.cgi(8) is disabled. To enable it, copy # cgi.h.example to cgi.h, edit it, and use the following line. -# Obviously, this requires that BUILD_DB is enabled, too. BUILD_CGI=1 @@ -258,7 +233,7 @@ HAVE_GETLINE=0 HAVE_GETSUBOPT=0 HAVE_ISBLANK=0 HAVE_MKDTEMP=0 -HAVE_MMAP=0 +HAVE_OHASH=0 HAVE_PLEDGE=0 HAVE_PROGNAME=0 HAVE_REALLOCARRAY=0 @@ -273,7 +248,3 @@ HAVE_STRSEP=0 HAVE_STRTONUM=0 HAVE_VASPRINTF=0 HAVE_WCHAR=0 - -HAVE_SQLITE3=0 -HAVE_SQLITE3_ERRSTR=0 -HAVE_OHASH=0 @@ -0,0 +1,418 @@ +/* $Id$ */ +/* + * Copyright (c) 2016 Ingo Schwarze <schwarze@openbsd.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * Allocation-based version of the mandoc database, for read-write access. + * The interface is defined in "dba.h". + */ +#include <sys/types.h> +#include <errno.h> +#include <stdint.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include "mandoc_aux.h" +#include "mansearch.h" +#include "dba_write.h" +#include "dba_array.h" +#include "dba.h" + +static void *prepend(const char *, char); +static void dba_pages_write(struct dba_array *); +static int compare_names(const void *, const void *); +static void dba_macros_write(struct dba_array *); +static void dba_macro_write(struct dba_array *); + + +/*** top-level functions **********************************************/ + +struct dba * +dba_new(int32_t npages) +{ + struct dba *dba; + int32_t im; + + dba = mandoc_malloc(sizeof(*dba)); + dba->pages = dba_array_new(npages, DBA_GROW); + dba->macros = dba_array_new(MACRO_MAX, 0); + for (im = 0; im < MACRO_MAX; im++) + dba_array_set(dba->macros, im, dba_array_new(128, DBA_GROW)); + return dba; +} + +void +dba_free(struct dba *dba) +{ + struct dba_array *page, *macro, *entry; + + dba_array_FOREACH(dba->macros, macro) { + dba_array_undel(macro); + dba_array_FOREACH(macro, entry) { + free(dba_array_get(entry, 0)); + dba_array_free(dba_array_get(entry, 1)); + dba_array_free(entry); + } + dba_array_free(macro); + } + dba_array_free(dba->macros); + + dba_array_undel(dba->pages); + dba_array_FOREACH(dba->pages, page) { + dba_array_free(dba_array_get(page, DBP_NAME)); + dba_array_free(dba_array_get(page, DBP_SECT)); + dba_array_free(dba_array_get(page, DBP_ARCH)); + free(dba_array_get(page, DBP_DESC)); + dba_array_free(dba_array_get(page, DBP_FILE)); + dba_array_free(page); + } + dba_array_free(dba->pages); + + free(dba); +} + +/* + * Write the complete mandoc database to disk; the format is: + * - One integer each for magic and version. + * - One pointer each to the macros table and to the final magic. + * - The pages table. + * - The macros table. + * - And at the very end, the magic integer again. + */ +int +dba_write(const char *fname, struct dba *dba) +{ + int save_errno; + int32_t pos_end, pos_macros, pos_macros_ptr; + + if (dba_open(fname) == -1) + return -1; + dba_int_write(MANDOCDB_MAGIC); + dba_int_write(MANDOCDB_VERSION); + pos_macros_ptr = dba_skip(1, 2); + dba_pages_write(dba->pages); + pos_macros = dba_tell(); + dba_macros_write(dba->macros); + pos_end = dba_tell(); + dba_int_write(MANDOCDB_MAGIC); + dba_seek(pos_macros_ptr); + dba_int_write(pos_macros); + dba_int_write(pos_end); + if (dba_close() == -1) { + save_errno = errno; + unlink(fname); + errno = save_errno; + return -1; + } + return 0; +} + + +/*** functions for handling pages *************************************/ + +/* + * Create a new page and append it to the pages table. + */ +struct dba_array * +dba_page_new(struct dba_array *pages, const char *name, const char *sect, + const char *arch, const char *desc, const char *file, enum form form) +{ + struct dba_array *page, *entry; + + page = dba_array_new(DBP_MAX, 0); + entry = dba_array_new(1, DBA_STR | DBA_GROW); + dba_array_add(entry, prepend(name, NAME_FILE & NAME_MASK)); + dba_array_add(page, entry); + entry = dba_array_new(1, DBA_STR | DBA_GROW); + dba_array_add(entry, (void *)sect); + dba_array_add(page, entry); + if (arch != NULL && *arch != '\0') { + entry = dba_array_new(1, DBA_STR | DBA_GROW); + dba_array_add(entry, (void *)arch); + } else + entry = NULL; + dba_array_add(page, entry); + dba_array_add(page, mandoc_strdup(desc)); + entry = dba_array_new(1, DBA_STR | DBA_GROW); + dba_array_add(entry, prepend(file, form)); + dba_array_add(page, entry); + dba_array_add(pages, page); + return page; +} + +/* + * Add a section, architecture, or file name to an existing page. + * Passing the NULL pointer for the architecture makes the page MI. + * In that case, any earlier or later architectures are ignored. + */ +void +dba_page_add(struct dba_array *page, int32_t ie, const char *str) +{ + struct dba_array *entries; + char *entry; + + entries = dba_array_get(page, ie); + if (ie == DBP_ARCH) { + if (entries == NULL) + return; + if (str == NULL) { + dba_array_free(entries); + dba_array_set(page, DBP_ARCH, NULL); + return; + } + } + if (*str == '\0') + return; + dba_array_FOREACH(entries, entry) + if (strcmp(entry, str) == 0) + return; + dba_array_add(entries, (void *)str); +} + +/* + * Add an additional name to an existing page. + */ +void +dba_page_alias(struct dba_array *page, const char *name, uint64_t mask) +{ + struct dba_array *entries; + char *entry; + char maskbyte; + + if (*name == '\0') + return; + maskbyte = mask & NAME_MASK; + entries = dba_array_get(page, DBP_NAME); + dba_array_FOREACH(entries, entry) { + if (strcmp(entry + 1, name) == 0) { + *entry |= maskbyte; + return; + } + } + dba_array_add(entries, prepend(name, maskbyte)); +} + +/* + * Return a pointer to a temporary copy of instr with inbyte prepended. + */ +static void * +prepend(const char *instr, char inbyte) +{ + static char *outstr = NULL; + static size_t outlen = 0; + size_t newlen; + + newlen = strlen(instr) + 1; + if (newlen > outlen) { + outstr = mandoc_realloc(outstr, newlen + 1); + outlen = newlen; + } + *outstr = inbyte; + memcpy(outstr + 1, instr, newlen); + return outstr; +} + +/* + * Write the pages table to disk; the format is: + * - One integer containing the number of pages. + * - For each page, five pointers to the names, sections, + * architectures, description, and file names of the page. + * MI pages write 0 instead of the architecture pointer. + * - One list each for names, sections, architectures, descriptions and + * file names. The description for each page ends with a NUL byte. + * For all the other lists, each string ends with a NUL byte, + * and the last string for a page ends with two NUL bytes. + * - To assure alignment of following integers, + * the end is padded with NUL bytes up to a multiple of four bytes. + */ +static void +dba_pages_write(struct dba_array *pages) +{ + struct dba_array *page, *names; + void *entry; + int32_t pos_pages, pos_end; + + pos_pages = dba_array_writelen(pages, 5); + dba_array_FOREACH(pages, page) { + dba_array_setpos(page, DBP_NAME, dba_tell()); + names = dba_array_get(page, DBP_NAME); + dba_array_sort(names, compare_names); + dba_array_writelst(names); + } + dba_array_FOREACH(pages, page) { + dba_array_setpos(page, DBP_SECT, dba_tell()); + dba_array_writelst(dba_array_get(page, DBP_SECT)); + } + dba_array_FOREACH(pages, page) { + if ((entry = dba_array_get(page, DBP_ARCH)) != NULL) { + dba_array_setpos(page, DBP_ARCH, dba_tell()); + dba_array_writelst(entry); + } else + dba_array_setpos(page, DBP_ARCH, 0); + } + dba_array_FOREACH(pages, page) { + dba_array_setpos(page, DBP_DESC, dba_tell()); + dba_str_write(dba_array_get(page, DBP_DESC)); + } + dba_array_FOREACH(pages, page) { + dba_array_setpos(page, DBP_FILE, dba_tell()); + dba_array_writelst(dba_array_get(page, DBP_FILE)); + } + pos_end = dba_align(); + dba_seek(pos_pages); + dba_array_FOREACH(pages, page) + dba_array_writepos(page); + dba_seek(pos_end); +} + +static int +compare_names(const void *vp1, const void *vp2) +{ + const char *cp1, *cp2; + int diff; + + cp1 = *(char **)vp1; + cp2 = *(char **)vp2; + return (diff = *cp2 - *cp1) ? diff : + strcasecmp(cp1 + 1, cp2 + 1); +} + + +/*** functions for handling macros ************************************/ + +/* + * Create a new macro entry and append it to one of the macro tables. + */ +void +dba_macro_new(struct dba *dba, int32_t im, const char *value, + const int32_t *pp) +{ + struct dba_array *entry, *pages; + const int32_t *ip; + int32_t np; + + np = 0; + for (ip = pp; *ip; ip++) + np++; + pages = dba_array_new(np, DBA_GROW); + for (ip = pp; *ip; ip++) + dba_array_add(pages, dba_array_get(dba->pages, + be32toh(*ip) / 5 / sizeof(*ip) - 1)); + + entry = dba_array_new(2, 0); + dba_array_add(entry, mandoc_strdup(value)); + dba_array_add(entry, pages); + + dba_array_add(dba_array_get(dba->macros, im), entry); +} + +/* + * Look up a macro entry by value and add a reference to a new page to it. + * If the value does not yet exist, create a new macro entry + * and add it to the macro table in question. + */ +void +dba_macro_add(struct dba_array *macros, int32_t im, const char *value, + struct dba_array *page) +{ + struct dba_array *macro, *entry, *pages; + + if (*value == '\0') + return; + macro = dba_array_get(macros, im); + dba_array_FOREACH(macro, entry) + if (strcmp(value, dba_array_get(entry, 0)) == 0) + break; + if (entry == NULL) { + entry = dba_array_new(2, 0); + dba_array_add(entry, mandoc_strdup(value)); + pages = dba_array_new(1, DBA_GROW); + dba_array_add(entry, pages); + dba_array_add(macro, entry); + } else + pages = dba_array_get(entry, 1); + dba_array_add(pages, page); +} + +/* + * Write the macros table to disk; the format is: + * - The number of macro tables (actually, MACRO_MAX). + * - That number of pointers to the individual macro tables. + * - The individual macro tables. + */ +static void +dba_macros_write(struct dba_array *macros) +{ + struct dba_array *macro; + int32_t im, pos_macros, pos_end; + + pos_macros = dba_array_writelen(macros, 1); + im = 0; + dba_array_FOREACH(macros, macro) { + dba_array_setpos(macros, im++, dba_tell()); + dba_macro_write(macro); + } + pos_end = dba_tell(); + dba_seek(pos_macros); + dba_array_writepos(macros); + dba_seek(pos_end); +} + +/* + * Write one individual macro table to disk; the format is: + * - The number of entries in the table. + * - For each entry, two pointers, the first one to the value + * and the second one to the list of pages. + * - A list of values, each ending in a NUL byte. + * - To assure alignment of following integers, + * padding with NUL bytes up to a multiple of four bytes. + * - A list of pointers to pages, each list ending in a 0 integer. + */ +static void +dba_macro_write(struct dba_array *macro) +{ + struct dba_array *entry, *pages, *page; + int empty; + int32_t addr, pos_macro, pos_end; + + dba_array_FOREACH(macro, entry) { + pages = dba_array_get(entry, 1); + empty = 1; + dba_array_FOREACH(pages, page) + if (dba_array_getpos(page)) + empty = 0; + if (empty) + dba_array_del(macro); + } + pos_macro = dba_array_writelen(macro, 2); + dba_array_FOREACH(macro, entry) { + dba_array_setpos(entry, 0, dba_tell()); + dba_str_write(dba_array_get(entry, 0)); + } + dba_align(); + dba_array_FOREACH(macro, entry) { + dba_array_setpos(entry, 1, dba_tell()); + pages = dba_array_get(entry, 1); + dba_array_FOREACH(pages, page) + if ((addr = dba_array_getpos(page))) + dba_int_write(addr); + dba_int_write(0); + } + pos_end = dba_tell(); + dba_seek(pos_macro); + dba_array_FOREACH(macro, entry) + dba_array_writepos(entry); + dba_seek(pos_end); +} @@ -0,0 +1,51 @@ +/* $Id$ */ +/* + * Copyright (c) 2016 Ingo Schwarze <schwarze@openbsd.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * Public interface of the allocation-based version + * of the mandoc database, for read-write access. + * To be used by dba.c, dba_read.c, and makewhatis(8). + */ + +#define DBP_NAME 0 +#define DBP_SECT 1 +#define DBP_ARCH 2 +#define DBP_DESC 3 +#define DBP_FILE 4 +#define DBP_MAX 5 + +struct dba_array; + +struct dba { + struct dba_array *pages; + struct dba_array *macros; +}; + + +struct dba *dba_new(int32_t); +void dba_free(struct dba *); +struct dba *dba_read(const char *); +int dba_write(const char *, struct dba *); + +struct dba_array *dba_page_new(struct dba_array *, const char *, + const char *, const char *, const char *, + const char *, enum form); +void dba_page_add(struct dba_array *, int32_t, const char *); +void dba_page_alias(struct dba_array *, const char *, uint64_t); + +void dba_macro_new(struct dba *, int32_t, + const char *, const int32_t *); +void dba_macro_add(struct dba_array *, int32_t, + const char *, struct dba_array *); diff --git a/dba_array.c b/dba_array.c new file mode 100644 index 00000000..da5fc2e9 --- /dev/null +++ b/dba_array.c @@ -0,0 +1,188 @@ +/* $Id$ */ +/* + * Copyright (c) 2016 Ingo Schwarze <schwarze@openbsd.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * Allocation-based arrays for the mandoc database, for read-write access. + * The interface is defined in "dba_array.h". + */ +#include <assert.h> +#include <stdint.h> +#include <stdlib.h> +#include <string.h> + +#include "mandoc_aux.h" +#include "dba_write.h" +#include "dba_array.h" + +struct dba_array { + void **ep; /* Array of entries. */ + int32_t *em; /* Array of map positions. */ + int flags; + int32_t ea; /* Entries allocated. */ + int32_t eu; /* Entries used (including deleted). */ + int32_t ed; /* Entries deleted. */ + int32_t ec; /* Currently active entry. */ + int32_t pos; /* Map position of this array. */ +}; + + +struct dba_array * +dba_array_new(int32_t ea, int flags) +{ + struct dba_array *array; + + assert(ea > 0); + array = mandoc_malloc(sizeof(*array)); + array->ep = mandoc_reallocarray(NULL, ea, sizeof(*array->ep)); + array->em = mandoc_reallocarray(NULL, ea, sizeof(*array->em)); + array->ea = ea; + array->eu = 0; + array->ed = 0; + array->ec = 0; + array->flags = flags; + array->pos = 0; + return array; +} + +void +dba_array_free(struct dba_array *array) +{ + int32_t ie; + + if (array == NULL) + return; + if (array->flags & DBA_STR) + for (ie = 0; ie < array->eu; ie++) + free(array->ep[ie]); + free(array->ep); + free(array->em); + free(array); +} + +void +dba_array_set(struct dba_array *array, int32_t ie, void *entry) +{ + assert(ie >= 0); + assert(ie < array->ea); + assert(ie <= array->eu); + if (ie == array->eu) + array->eu++; + if (array->flags & DBA_STR) + entry = mandoc_strdup(entry); + array->ep[ie] = entry; + array->em[ie] = 0; +} + +void +dba_array_add(struct dba_array *array, void *entry) +{ + if (array->eu == array->ea) { + assert(array->flags & DBA_GROW); + array->ep = mandoc_reallocarray(array->ep, + 2, sizeof(*array->ep) * array->ea); + array->em = mandoc_reallocarray(array->em, + 2, sizeof(*array->em) * array->ea); + array->ea *= 2; + } + dba_array_set(array, array->eu, entry); +} + +void * +dba_array_get(struct dba_array *array, int32_t ie) +{ + if (ie < 0 || ie >= array->eu || array->em[ie] == -1) + return NULL; + return array->ep[ie]; +} + +void +dba_array_start(struct dba_array *array) +{ + array->ec = array->eu; +} + +void * +dba_array_next(struct dba_array *array) +{ + if (array->ec < array->eu) + array->ec++; + else + array->ec = 0; + while (array->ec < array->eu && array->em[array->ec] == -1) + array->ec++; + return array->ec < array->eu ? array->ep[array->ec] : NULL; +} + +void +dba_array_del(struct dba_array *array) +{ + if (array->ec < array->eu && array->em[array->ec] != -1) { + array->em[array->ec] = -1; + array->ed++; + } +} + +void +dba_array_undel(struct dba_array *array) +{ + memset(array->em, 0, sizeof(*array->em) * array->eu); +} + +void +dba_array_setpos(struct dba_array *array, int32_t ie, int32_t pos) +{ + array->em[ie] = pos; +} + +int32_t +dba_array_getpos(struct dba_array *array) +{ + return array->pos; +} + +void +dba_array_sort(struct dba_array *array, dba_compare_func func) +{ + assert(array->ed == 0); + qsort(array->ep, array->eu, sizeof(*array->ep), func); +} + +int32_t +dba_array_writelen(struct dba_array *array, int32_t nmemb) +{ + dba_int_write(array->eu - array->ed); + return dba_skip(nmemb, array->eu - array->ed); +} + +void +dba_array_writepos(struct dba_array *array) +{ + int32_t ie; + + array->pos = dba_tell(); + for (ie = 0; ie < array->eu; ie++) + if (array->em[ie] != -1) + dba_int_write(array->em[ie]); +} + +void +dba_array_writelst(struct dba_array *array) +{ + const char *str; + + dba_array_FOREACH(array, str) + dba_str_write(str); + dba_char_write('\0'); +} diff --git a/dba_array.h b/dba_array.h new file mode 100644 index 00000000..d9ad2665 --- /dev/null +++ b/dba_array.h @@ -0,0 +1,47 @@ +/* $Id$ */ +/* + * Copyright (c) 2016 Ingo Schwarze <schwarze@openbsd.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * Public interface for allocation-based arrays + * for the mandoc database, for read-write access. + * To be used by dba*.c and by makewhatis(8). + */ + +struct dba_array; + +#define DBA_STR 0x01 /* Map contains strings, not pointers. */ +#define DBA_GROW 0x02 /* Allow the array to grow. */ + +#define dba_array_FOREACH(a, e) \ + dba_array_start(a); \ + while (((e) = dba_array_next(a)) != NULL) + +typedef int dba_compare_func(const void *, const void *); + +struct dba_array *dba_array_new(int32_t, int); +void dba_array_free(struct dba_array *); +void dba_array_set(struct dba_array *, int32_t, void *); +void dba_array_add(struct dba_array *, void *); +void *dba_array_get(struct dba_array *, int32_t); +void dba_array_start(struct dba_array *); +void *dba_array_next(struct dba_array *); +void dba_array_del(struct dba_array *); +void dba_array_undel(struct dba_array *); +void dba_array_setpos(struct dba_array *, int32_t, int32_t); +int32_t dba_array_getpos(struct dba_array *); +void dba_array_sort(struct dba_array *, dba_compare_func); +int32_t dba_array_writelen(struct dba_array *, int32_t); +void dba_array_writepos(struct dba_array *); +void dba_array_writelst(struct dba_array *); diff --git a/dba_read.c b/dba_read.c new file mode 100644 index 00000000..f36c553c --- /dev/null +++ b/dba_read.c @@ -0,0 +1,74 @@ +/* $Id$ */ +/* + * Copyright (c) 2016 Ingo Schwarze <schwarze@openbsd.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * Function to read the mandoc database from disk into RAM, + * such that data can be added or removed. + * The interface is defined in "dba.h". + * This file is seperate from dba.c because this also uses "dbm.h". + */ +#include <regex.h> +#include <stdint.h> +#include <stdlib.h> +#include <stdio.h> +#include <string.h> + +#include "mandoc_aux.h" +#include "mansearch.h" +#include "dba_array.h" +#include "dba.h" +#include "dbm.h" + + +struct dba * +dba_read(const char *fname) +{ + struct dba *dba; + struct dba_array *page; + struct dbm_page *pdata; + struct dbm_macro *mdata; + const char *cp; + int32_t im, ip, iv, npages; + + if (dbm_open(fname) == -1) + return NULL; + npages = dbm_page_count(); + dba = dba_new(npages); + for (ip = 0; ip < npages; ip++) { + pdata = dbm_page_get(ip); + page = dba_page_new(dba->pages, pdata->name, pdata->sect, + pdata->arch, pdata->desc, pdata->file + 1, *pdata->file); + cp = pdata->name; + while (*(cp = strchr(cp, '\0') + 1) != '\0') + dba_page_add(page, DBP_NAME, cp); + cp = pdata->sect; + while (*(cp = strchr(cp, '\0') + 1) != '\0') + dba_page_add(page, DBP_SECT, cp); + if ((cp = pdata->arch) != NULL) + while (*(cp = strchr(cp, '\0') + 1) != '\0') + dba_page_add(page, DBP_ARCH, cp); + cp = pdata->file; + while (*(cp = strchr(cp, '\0') + 1) != '\0') + dba_page_add(page, DBP_FILE, cp); + } + for (im = 0; im < MACRO_MAX; im++) { + for (iv = 0; iv < dbm_macro_count(im); iv++) { + mdata = dbm_macro_get(im, iv); + dba_macro_new(dba, im, mdata->value, mdata->pp); + } + } + dbm_close(); + return dba; +} diff --git a/dba_write.c b/dba_write.c new file mode 100644 index 00000000..9746d575 --- /dev/null +++ b/dba_write.c @@ -0,0 +1,117 @@ +/* $Id$ */ +/* + * Copyright (c) 2016 Ingo Schwarze <schwarze@openbsd.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * Low-level functions for serializing allocation-based data to disk. + * The interface is defined in "dba_write.h". + */ +#include <assert.h> +#include <endian.h> +#include <err.h> +#include <errno.h> +#include <fcntl.h> +#include <stdint.h> +#include <stdio.h> + +#include "dba_write.h" + +static FILE *ofp; + + +int +dba_open(const char *fname) +{ + ofp = fopen(fname, "w"); + return ofp == NULL ? -1 : 0; +} + +int +dba_close(void) +{ + return fclose(ofp) == EOF ? -1 : 0; +} + +int32_t +dba_tell(void) +{ + long pos; + + if ((pos = ftell(ofp)) == -1) + err(1, "ftell"); + if (pos >= INT32_MAX) { + errno = EOVERFLOW; + err(1, "ftell = %ld", pos); + } + return pos; +} + +void +dba_seek(int32_t pos) +{ + if (fseek(ofp, pos, SEEK_SET) == -1) + err(1, "fseek(%d)", pos); +} + +int32_t +dba_align(void) +{ + int32_t pos; + + pos = dba_tell(); + while (pos & 3) { + dba_char_write('\0'); + pos++; + } + return pos; +} + +int32_t +dba_skip(int32_t nmemb, int32_t sz) +{ + const int32_t out[5] = {0, 0, 0, 0, 0}; + int32_t i, pos; + + assert(sz >= 0); + assert(nmemb > 0); + assert(nmemb <= 5); + pos = dba_tell(); + for (i = 0; i < sz; i++) + if (nmemb - fwrite(&out, sizeof(out[0]), nmemb, ofp)) + err(1, "fwrite"); + return pos; +} + +void +dba_char_write(int c) +{ + if (putc(c, ofp) == EOF) + err(1, "fputc"); +} + +void +dba_str_write(const char *str) +{ + if (fputs(str, ofp) == EOF) + err(1, "fputs"); + dba_char_write('\0'); +} + +void +dba_int_write(int32_t i) +{ + i = htobe32(i); + if (fwrite(&i, sizeof(i), 1, ofp) != 1) + err(1, "fwrite"); +} diff --git a/test-sqlite3.c b/dba_write.h index 0ec76744..4346af26 100644 --- a/test-sqlite3.c +++ b/dba_write.h @@ -1,6 +1,6 @@ -/* $Id$ */ +/* $Id$ */ /* - * Copyright (c) 2014 Ingo Schwarze <schwarze@openbsd.org> + * Copyright (c) 2016 Ingo Schwarze <schwarze@openbsd.org> * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -13,35 +13,18 @@ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * Internal interface to low-level functions + * for serializing allocation-based data to disk. + * For use by dba_array.c and dba.c only. */ -#include <stdio.h> -#include <unistd.h> -#include <sqlite3.h> - -int -main(void) -{ - sqlite3 *db; - - if (sqlite3_open_v2("test.db", &db, - SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE, - NULL) != SQLITE_OK) { - perror("test.db"); - fprintf(stderr, "sqlite3_open_v2: %s", sqlite3_errmsg(db)); - return 1; - } - unlink("test.db"); - - if (sqlite3_exec(db, "PRAGMA foreign_keys = ON", - NULL, NULL, NULL) != SQLITE_OK) { - fprintf(stderr, "sqlite3_exec: %s", sqlite3_errmsg(db)); - return 1; - } - - if (sqlite3_close(db) != SQLITE_OK) { - fprintf(stderr, "sqlite3_close: %s", sqlite3_errmsg(db)); - return 1; - } - return 0; -} +int dba_open(const char *); +int dba_close(void); +int32_t dba_tell(void); +void dba_seek(int32_t); +int32_t dba_align(void); +int32_t dba_skip(int32_t, int32_t); +void dba_char_write(int); +void dba_str_write(const char *); +void dba_int_write(int32_t); @@ -0,0 +1,452 @@ +/* $Id$ */ +/* + * Copyright (c) 2016 Ingo Schwarze <schwarze@openbsd.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * Map-based version of the mandoc database, for read-only access. + * The interface is defined in "dbm.h". + */ +#include <assert.h> +#include <endian.h> +#include <err.h> +#include <errno.h> +#include <regex.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "mansearch.h" +#include "dbm_map.h" +#include "dbm.h" + +struct macro { + int32_t value; + int32_t pages; +}; + +struct page { + int32_t name; + int32_t sect; + int32_t arch; + int32_t desc; + int32_t file; +}; + +enum iter { + ITER_NONE = 0, + ITER_NAME, + ITER_SECT, + ITER_ARCH, + ITER_DESC, + ITER_MACRO +}; + +static struct macro *macros[MACRO_MAX]; +static int32_t nvals[MACRO_MAX]; +static struct page *pages; +static int32_t npages; +static enum iter iteration; + +static struct dbm_res page_bytitle(enum iter, const struct dbm_match *); +static struct dbm_res page_byarch(const struct dbm_match *); +static struct dbm_res page_bymacro(int32_t, const struct dbm_match *); +static char *macro_bypage(int32_t, int32_t); + + +/*** top level functions **********************************************/ + +/* + * Open a disk-based mandoc database for read-only access. + * Map the pages and macros[] arrays. + * Return 0 on success. Return -1 and set errno on failure. + */ +int +dbm_open(const char *fname) +{ + const int32_t *mp, *ep; + int32_t im; + + if (dbm_map(fname) == -1) + return -1; + + if ((npages = be32toh(*dbm_getint(4))) < 0) { + warnx("dbm_open(%s): Invalid number of pages: %d", + fname, npages); + goto fail; + } + pages = (struct page *)dbm_getint(5); + + if ((mp = dbm_get(*dbm_getint(2))) == NULL) { + warnx("dbm_open(%s): Invalid offset of macros array", fname); + goto fail; + } + if (be32toh(*mp) != MACRO_MAX) { + warnx("dbm_open(%s): Invalid number of macros: %d", + fname, be32toh(*mp)); + goto fail; + } + for (im = 0; im < MACRO_MAX; im++) { + if ((ep = dbm_get(*++mp)) == NULL) { + warnx("dbm_open(%s): Invalid offset of macro %d", + fname, im); + goto fail; + } + nvals[im] = be32toh(*ep); + macros[im] = (struct macro *)++ep; + } + return 0; + +fail: + dbm_unmap(); + errno = EFTYPE; + return -1; +} + +void +dbm_close(void) +{ + dbm_unmap(); +} + + +/*** functions for handling pages *************************************/ + +int32_t +dbm_page_count(void) +{ + return npages; +} + +/* + * Give the caller pointers to the data for one manual page. + */ +struct dbm_page * +dbm_page_get(int32_t ip) +{ + static struct dbm_page res; + + assert(ip >= 0); + assert(ip < npages); + res.name = dbm_get(pages[ip].name); + res.sect = dbm_get(pages[ip].sect); + res.arch = pages[ip].arch ? dbm_get(pages[ip].arch) : NULL; + res.desc = dbm_get(pages[ip].desc); + res.file = dbm_get(pages[ip].file); + res.addr = dbm_addr(pages + ip); + return &res; +} + +/* + * Functions to start filtered iterations over manual pages. + */ +void +dbm_page_byname(const struct dbm_match *match) +{ + assert(match != NULL); + page_bytitle(ITER_NAME, match); +} + +void +dbm_page_bysect(const struct dbm_match *match) +{ + assert(match != NULL); + page_bytitle(ITER_SECT, match); +} + +void +dbm_page_byarch(const struct dbm_match *match) +{ + assert(match != NULL); + page_byarch(match); +} + +void +dbm_page_bydesc(const struct dbm_match *match) +{ + assert(match != NULL); + page_bytitle(ITER_DESC, match); +} + +void +dbm_page_bymacro(int32_t im, const struct dbm_match *match) +{ + assert(im >= 0); + assert(im < MACRO_MAX); + assert(match != NULL); + page_bymacro(im, match); +} + +/* + * Return the number of the next manual page in the current iteration. + */ +struct dbm_res +dbm_page_next(void) +{ + struct dbm_res res = {-1, 0}; + + switch(iteration) { + case ITER_NONE: + return res; + case ITER_ARCH: + return page_byarch(NULL); + case ITER_MACRO: + return page_bymacro(0, NULL); + default: + return page_bytitle(iteration, NULL); + } +} + +/* + * Functions implementing the iteration over manual pages. + */ +static struct dbm_res +page_bytitle(enum iter arg_iter, const struct dbm_match *arg_match) +{ + static const struct dbm_match *match; + static const char *cp; + static int32_t ip; + struct dbm_res res = {-1, 0}; + + assert(arg_iter == ITER_NAME || arg_iter == ITER_DESC || + arg_iter == ITER_SECT); + + /* Initialize for a new iteration. */ + + if (arg_match != NULL) { + iteration = arg_iter; + match = arg_match; + switch (iteration) { + case ITER_NAME: + cp = dbm_get(pages[0].name); + break; + case ITER_SECT: + cp = dbm_get(pages[0].sect); + break; + case ITER_DESC: + cp = dbm_get(pages[0].desc); + break; + default: + abort(); + } + ip = 0; + return res; + } + + /* Search for a name. */ + + while (ip < npages) { + if (iteration == ITER_NAME) + cp++; + if (dbm_match(match, cp)) + break; + cp = strchr(cp, '\0') + 1; + if (iteration == ITER_DESC) + ip++; + else if (*cp == '\0') { + cp++; + ip++; + } + } + + /* Reached the end without a match. */ + + if (ip == npages) { + iteration = ITER_NONE; + match = NULL; + cp = NULL; + return res; + } + + /* Found a match; save the quality for later retrieval. */ + + res.page = ip; + res.bits = iteration == ITER_NAME ? cp[-1] : 0; + + /* Skip the remaining names of this page. */ + + if (++ip < npages) { + do { + cp++; + } while (cp[-1] != '\0' || + (iteration != ITER_DESC && cp[-2] != '\0')); + } + return res; +} + +static struct dbm_res +page_byarch(const struct dbm_match *arg_match) +{ + static const struct dbm_match *match; + struct dbm_res res = {-1, 0}; + static int32_t ip; + const char *cp; + + /* Initialize for a new iteration. */ + + if (arg_match != NULL) { + iteration = ITER_ARCH; + match = arg_match; + ip = 0; + return res; + } + + /* Search for an architecture. */ + + for ( ; ip < npages; ip++) + if (pages[ip].arch) + for (cp = dbm_get(pages[ip].arch); + *cp != '\0'; + cp = strchr(cp, '\0') + 1) + if (dbm_match(match, cp)) { + res.page = ip++; + return res; + } + + /* Reached the end without a match. */ + + iteration = ITER_NONE; + match = NULL; + return res; +} + +static struct dbm_res +page_bymacro(int32_t im, const struct dbm_match *match) +{ + static const int32_t *pp; + struct dbm_res res = {-1, 0}; + const char *cp; + int32_t iv; + + assert(im >= 0); + assert(im < MACRO_MAX); + + /* Initialize for a new iteration. */ + + if (match != NULL) { + iteration = ITER_MACRO; + cp = nvals[im] ? dbm_get(macros[im]->value) : NULL; + for (iv = 0; iv < nvals[im]; iv++) { + if (dbm_match(match, cp)) + break; + cp = strchr(cp, '\0') + 1; + } + pp = iv == nvals[im] ? NULL : dbm_get(macros[im][iv].pages); + return res; + } + if (iteration != ITER_MACRO) + return res; + + /* No more matches. */ + + if (pp == NULL || *pp == 0) { + iteration = ITER_NONE; + pp = NULL; + return res; + } + + /* Found a match. */ + + res.page = (struct page *)dbm_get(*pp++) - pages; + return res; +} + + +/*** functions for handling macros ************************************/ + +int32_t +dbm_macro_count(int32_t im) +{ + assert(im >= 0); + assert(im < MACRO_MAX); + return nvals[im]; +} + +struct dbm_macro * +dbm_macro_get(int32_t im, int32_t iv) +{ + static struct dbm_macro macro; + + assert(im >= 0); + assert(im < MACRO_MAX); + assert(iv >= 0); + assert(iv < nvals[im]); + macro.value = dbm_get(macros[im][iv].value); + macro.pp = dbm_get(macros[im][iv].pages); + return ¯o; +} + +/* + * Filtered iteration over macro entries. + */ +void +dbm_macro_bypage(int32_t im, int32_t ip) +{ + assert(im >= 0); + assert(im < MACRO_MAX); + assert(ip != 0); + macro_bypage(im, ip); +} + +char * +dbm_macro_next(void) +{ + return macro_bypage(MACRO_MAX, 0); +} + +static char * +macro_bypage(int32_t arg_im, int32_t arg_ip) +{ + static const int32_t *pp; + static int32_t im, ip, iv; + + /* Initialize for a new iteration. */ + + if (arg_im < MACRO_MAX && arg_ip != 0) { + im = arg_im; + ip = arg_ip; + pp = dbm_get(macros[im]->pages); + iv = 0; + return NULL; + } + if (im >= MACRO_MAX) + return NULL; + + /* Search for the next value. */ + + while (iv < nvals[im]) { + if (*pp == ip) + break; + if (*pp == 0) + iv++; + pp++; + } + + /* Reached the end without a match. */ + + if (iv == nvals[im]) { + im = MACRO_MAX; + ip = 0; + pp = NULL; + return NULL; + } + + /* Found a match; skip the remaining pages of this entry. */ + + if (++iv < nvals[im]) + while (*pp++ != 0) + continue; + + return dbm_get(macros[im][iv - 1].value); +} @@ -0,0 +1,68 @@ +/* $Id$ */ +/* + * Copyright (c) 2016 Ingo Schwarze <schwarze@openbsd.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * Public interface for the map-based version + * of the mandoc database, for read-only access. + * To be used by dbm*.c, dba_read.c, and man(1) and apropos(1). + */ + +enum dbm_mtype { + DBM_EXACT = 0, + DBM_SUB, + DBM_REGEX +}; + +struct dbm_match { + regex_t *re; + const char *str; + enum dbm_mtype type; +}; + +struct dbm_res { + int32_t page; + int32_t bits; +}; + +struct dbm_page { + const char *name; + const char *sect; + const char *arch; + const char *desc; + const char *file; + int32_t addr; +}; + +struct dbm_macro { + const char *value; + const int32_t *pp; +}; + +int dbm_open(const char *); +void dbm_close(void); + +int32_t dbm_page_count(void); +struct dbm_page *dbm_page_get(int32_t); +void dbm_page_byname(const struct dbm_match *); +void dbm_page_bysect(const struct dbm_match *); +void dbm_page_byarch(const struct dbm_match *); +void dbm_page_bydesc(const struct dbm_match *); +void dbm_page_bymacro(int32_t, const struct dbm_match *); +struct dbm_res dbm_page_next(void); + +int32_t dbm_macro_count(int32_t); +struct dbm_macro *dbm_macro_get(int32_t, int32_t); +void dbm_macro_bypage(int32_t, int32_t); +char *dbm_macro_next(void); diff --git a/dbm_map.c b/dbm_map.c new file mode 100644 index 00000000..8d24832d --- /dev/null +++ b/dbm_map.c @@ -0,0 +1,174 @@ +/* $Id$ */ +/* + * Copyright (c) 2016 Ingo Schwarze <schwarze@openbsd.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * Low-level routines for the map-based version + * of the mandoc database, for read-only access. + * The interface is defined in "dbm_map.h". + */ +#include <sys/mman.h> +#include <sys/stat.h> +#include <sys/types.h> + +#include <endian.h> +#include <err.h> +#include <errno.h> +#include <fcntl.h> +#include <regex.h> +#include <stdint.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include "mansearch.h" +#include "dbm_map.h" +#include "dbm.h" + +static struct stat st; +static char *dbm_base; +static int ifd; +static int32_t max_offset; + +/* + * Open a disk-based database for read-only access. + * Validate the file format as far as it is not mandoc-specific. + * Return 0 on success. Return -1 and set errno on failure. + */ +int +dbm_map(const char *fname) +{ + int save_errno; + const int32_t *magic; + + if ((ifd = open(fname, O_RDONLY)) == -1) + return -1; + if (fstat(ifd, &st) == -1) + goto fail; + if (st.st_size < 5) { + warnx("dbm_map(%s): File too short", fname); + errno = EFTYPE; + goto fail; + } + if (st.st_size > INT32_MAX) { + errno = EFBIG; + goto fail; + } + if ((dbm_base = mmap(NULL, st.st_size, PROT_READ, MAP_SHARED, + ifd, 0)) == MAP_FAILED) + goto fail; + magic = dbm_getint(0); + if (be32toh(*magic) != MANDOCDB_MAGIC) { + warnx("dbm_map(%s): Bad initial magic %x (expected %x)", + fname, be32toh(*magic), MANDOCDB_MAGIC); + errno = EFTYPE; + goto fail; + } + magic = dbm_getint(1); + if (be32toh(*magic) != MANDOCDB_VERSION) { + warnx("dbm_map(%s): Bad version number %d (expected %d)", + fname, be32toh(*magic), MANDOCDB_VERSION); + errno = EFTYPE; + goto fail; + } + max_offset = be32toh(*dbm_getint(3)) + sizeof(int32_t); + if (st.st_size != max_offset) { + warnx("dbm_map(%s): Inconsistent file size %llu (expected %d)", + fname, st.st_size, max_offset); + errno = EFTYPE; + goto fail; + } + if ((magic = dbm_get(*dbm_getint(3))) == NULL) { + errno = EFTYPE; + goto fail; + } + if (be32toh(*magic) != MANDOCDB_MAGIC) { + warnx("dbm_map(%s): Bad final magic %x (expected %x)", + fname, be32toh(*magic), MANDOCDB_MAGIC); + errno = EFTYPE; + goto fail; + } + return 0; + +fail: + save_errno = errno; + close(ifd); + errno = save_errno; + return -1; +} + +void +dbm_unmap(void) +{ + if (munmap(dbm_base, st.st_size) == -1) + warn("dbm_unmap: munmap"); + if (close(ifd) == -1) + warn("dbm_unmap: close"); + dbm_base = (char *)-1; +} + +/* + * Take a raw integer as it was read from the database. + * Interpret it as an offset into the database file + * and return a pointer to that place in the file. + */ +void * +dbm_get(int32_t offset) +{ + offset = be32toh(offset); + if (offset < 0 || offset >= max_offset) { + warnx("dbm_get: Database corrupt: offset %d > %d", + offset, max_offset); + return NULL; + } + return dbm_base + offset; +} + +/* + * Assume the database starts with some integers. + * Assume they are numbered starting from 0, increasing. + * Get a pointer to one with the number "offset". + */ +int32_t * +dbm_getint(int32_t offset) +{ + return (int32_t *)dbm_base + offset; +} + +/* + * The reverse of dbm_get(). + * Take pointer into the database file + * and convert it to the raw integer + * that would be used to refer to that place in the file. + */ +int32_t +dbm_addr(const void *p) +{ + return htobe32((char *)p - dbm_base); +} + +int +dbm_match(const struct dbm_match *match, const char *str) +{ + switch (match->type) { + case DBM_EXACT: + return strcmp(str, match->str) == 0; + case DBM_SUB: + return strcasestr(str, match->str) != NULL; + case DBM_REGEX: + return regexec(match->re, str, 0, NULL, 0) == 0; + default: + abort(); + } +} diff --git a/mansearch_const.c b/dbm_map.h index 54768024..41690a6c 100644 --- a/mansearch_const.c +++ b/dbm_map.h @@ -1,6 +1,6 @@ /* $Id$ */ /* - * Copyright (c) 2014 Ingo Schwarze <schwarze@openbsd.org> + * Copyright (c) 2016 Ingo Schwarze <schwarze@openbsd.org> * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -13,21 +13,17 @@ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * Private interface for low-level routines for the map-based version + * of the mandoc database, for read-only access. + * To be used by dbm*.c only. */ -#include "config.h" - -#include <sys/types.h> - -#include <stdint.h> - -#include "mansearch.h" -const int mansearch_keymax = 40; +struct dbm_match; -const char *const mansearch_keynames[40] = { - "arch", "sec", "Xr", "Ar", "Fa", "Fl", "Dv", "Fn", - "Ic", "Pa", "Cm", "Li", "Em", "Cd", "Va", "Ft", - "Tn", "Er", "Ev", "Sy", "Sh", "In", "Ss", "Ox", - "An", "Mt", "St", "Bx", "At", "Nx", "Fx", "Lk", - "Ms", "Bsx", "Dx", "Rs", "Vt", "Lb", "Nm", "Nd" -}; +int dbm_map(const char *); +void dbm_unmap(void); +void *dbm_get(int32_t); +int32_t *dbm_getint(int32_t); +int32_t dbm_addr(const void *); +int dbm_match(const struct dbm_match *, const char *); @@ -82,9 +82,7 @@ struct curparse { }; -#if HAVE_SQLITE3 int mandocdb(int, char *[]); -#endif static int fs_lookup(const struct manpaths *, size_t ipath, const char *, @@ -147,11 +145,9 @@ main(int argc, char *argv[]) setprogname(progname); #endif -#if HAVE_SQLITE3 if (strncmp(progname, "mandocdb", 8) == 0 || strcmp(progname, BINM_MAKEWHATIS) == 0) return mandocdb(argc, argv); -#endif #if HAVE_PLEDGE if (pledge("stdio rpath tmppath tty proc exec flock", NULL) == -1) @@ -349,9 +345,6 @@ main(int argc, char *argv[]) /* man(1), whatis(1), apropos(1) */ if (search.argmode != ARG_FILE) { - if (argc == 0) - usage(search.argmode); - if (search.argmode == ARG_NAME && outmode == OUTMODE_ONE) search.firstmatch = 1; @@ -359,19 +352,9 @@ main(int argc, char *argv[]) /* Access the mandoc database. */ manconf_parse(&conf, conf_file, defpaths, auxpaths); -#if HAVE_SQLITE3 - mansearch_setup(1); if ( ! mansearch(&search, &conf.manpath, argc, argv, &res, &sz)) usage(search.argmode); -#else - if (search.argmode != ARG_NAME) { - fputs("mandoc: database support not compiled in\n", - stderr); - return (int)MANDOCLEVEL_BADARG; - } - sz = 0; -#endif if (sz == 0) { if (search.argmode == ARG_NAME) @@ -474,7 +457,7 @@ main(int argc, char *argv[]) if (resp == NULL) parse(&curp, fd, *argv); - else if (resp->form & FORM_SRC) { + else if (resp->form == FORM_SRC) { /* For .so only; ignore failure. */ chdir(conf.manpath.paths[resp->ipath]); parse(&curp, fd, resp->file); @@ -522,10 +505,7 @@ main(int argc, char *argv[]) out: if (search.argmode != ARG_FILE) { manconf_free(&conf); -#if HAVE_SQLITE3 mansearch_free(res, sz); - mansearch_setup(0); -#endif } free(defos); @@ -629,7 +609,8 @@ fs_lookup(const struct manpaths *paths, size_t ipath, glob_t globinfo; struct manpage *page; char *file; - int form, globres; + int globres; + enum form form; form = FORM_SRC; mandoc_asprintf(&file, "%s/man%s/%s.%s", @@ -667,10 +648,8 @@ fs_lookup(const struct manpaths *paths, size_t ipath, return 0; found: -#if HAVE_SQLITE3 warnx("outdated mandoc.db lacks %s(%s) entry, run %s %s", name, sec, BINM_MAKEWHATIS, paths->paths[ipath]); -#endif *res = mandoc_reallocarray(*res, ++*ressz, sizeof(struct manpage)); page = *res + (*ressz - 1); page->file = file; @@ -37,6 +37,7 @@ #if HAVE_SANDBOX_INIT #include <sandbox.h> #endif +#include <stdarg.h> #include <stddef.h> #include <stdio.h> #include <stdint.h> @@ -44,8 +45,6 @@ #include <string.h> #include <unistd.h> -#include <sqlite3.h> - #include "mandoc_aux.h" #include "mandoc_ohash.h" #include "mandoc.h" @@ -54,29 +53,11 @@ #include "man.h" #include "manconf.h" #include "mansearch.h" +#include "dba_array.h" +#include "dba.h" -extern int mansearch_keymax; extern const char *const mansearch_keynames[]; -#define SQL_EXEC(_v) \ - if (SQLITE_OK != sqlite3_exec(db, (_v), NULL, NULL, NULL)) \ - say("", "%s: %s", (_v), sqlite3_errmsg(db)) -#define SQL_BIND_TEXT(_s, _i, _v) \ - if (SQLITE_OK != sqlite3_bind_text \ - ((_s), (_i)++, (_v), -1, SQLITE_STATIC)) \ - say(mlink->file, "%s", sqlite3_errmsg(db)) -#define SQL_BIND_INT(_s, _i, _v) \ - if (SQLITE_OK != sqlite3_bind_int \ - ((_s), (_i)++, (_v))) \ - say(mlink->file, "%s", sqlite3_errmsg(db)) -#define SQL_BIND_INT64(_s, _i, _v) \ - if (SQLITE_OK != sqlite3_bind_int64 \ - ((_s), (_i)++, (_v))) \ - say(mlink->file, "%s", sqlite3_errmsg(db)) -#define SQL_STEP(_s) \ - if (SQLITE_DONE != sqlite3_step((_s))) \ - say(mlink->file, "%s", sqlite3_errmsg(db)) - enum op { OP_DEFAULT = 0, /* new dbs from dir list or default config */ OP_CONFFILE, /* new databases from custom config file */ @@ -98,14 +79,14 @@ struct inodev { struct mpage { struct inodev inodev; /* used for hashing routine */ - int64_t pageid; /* pageid in mpages SQL table */ + struct dba_array *dba; 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 */ int name_head_done; + enum form form; /* format from file content */ }; struct mlink { @@ -116,19 +97,9 @@ struct mlink { 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 { - STMT_DELETE_PAGE = 0, /* delete mpage */ - STMT_INSERT_PAGE, /* insert mpage */ - STMT_INSERT_LINK, /* insert mlink */ - STMT_INSERT_NAME, /* insert name */ - STMT_SELECT_NAME, /* retrieve existing name flags */ - STMT_INSERT_KEY, /* insert parsed key */ - STMT__MAX + enum form dform; /* format from directory */ + enum form fform; /* format from file name suffix */ }; typedef int (*mdoc_fp)(struct mpage *, const struct roff_meta *, @@ -142,20 +113,17 @@ struct mdoc_handler { int mandocdb(int, char *[]); -static void dbclose(int); -static void dbadd(struct mpage *); +static void dbadd(struct dba *, struct mpage *); static void dbadd_mlink(const struct mlink *mlink); -static void dbadd_mlink_name(const struct mlink *mlink); -static int dbopen(int); -static void dbprune(void); +static void dbprune(struct dba *); +static void dbwrite(struct dba *); static void filescan(const char *); static void mlink_add(struct mlink *, const struct stat *); static void mlink_check(struct mpage *, struct mlink *); static void mlink_free(struct mlink *); static void mlinks_undupe(struct mpage *); static void mpages_free(void); -static void mpages_merge(struct mparse *); -static void names_check(void); +static void mpages_merge(struct dba *, struct mparse *); static void parse_cat(struct mpage *, int); static void parse_man(struct mpage *, const struct roff_meta *, const struct roff_node *); @@ -191,7 +159,6 @@ static int set_basedir(const char *, int); static int treescan(void); static size_t utf8(unsigned int, char [7]); -static char tempfilename[32]; static int nodb; /* no database changes */ static int mparse_options; /* abort the parse early */ static int use_all; /* use all found files */ @@ -205,8 +172,6 @@ static struct ohash mpages; /* table of distinct manual pages */ 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 sqlite3 *db = NULL; /* current database */ -static sqlite3_stmt *stmts[STMT__MAX]; /* current statements */ static uint64_t name_mask; static const struct mdoc_handler mdocs[MDOC_MAX] = { @@ -341,6 +306,7 @@ mandocdb(int argc, char *argv[]) { struct manconf conf; struct mparse *mp; + struct dba *dba; const char *path_arg, *progname; size_t j, sz; int ch, i; @@ -360,7 +326,6 @@ mandocdb(int argc, char *argv[]) #endif memset(&conf, 0, sizeof(conf)); - memset(stmts, 0, STMT__MAX * sizeof(sqlite3_stmt *)); /* * We accept a few different invocations. @@ -461,7 +426,7 @@ mandocdb(int argc, char *argv[]) if (OP_TEST != op && 0 == set_basedir(path_arg, 1)) goto out; - if (dbopen(1)) { + if ((dba = dba_read(MANDOC_DB)) != NULL) { /* * The existing database is usable. Process * all files specified on the command-line. @@ -479,7 +444,7 @@ mandocdb(int argc, char *argv[]) for (i = 0; i < argc; i++) filescan(argv[i]); if (OP_TEST != op) - dbprune(); + dbprune(dba); } else { /* * Database missing or corrupt. @@ -489,12 +454,13 @@ mandocdb(int argc, char *argv[]) op = OP_DEFAULT; if (0 == treescan()) goto out; - if (0 == dbopen(0)) - goto out; + dba = dba_new(128); } if (OP_DELETE != op) - mpages_merge(mp); - dbclose(OP_DEFAULT == op ? 0 : 1); + mpages_merge(dba, mp); + if (nodb == 0) + dbwrite(dba); + dba_free(dba); } else { /* * If we have arguments, use them as our manpaths. @@ -539,14 +505,11 @@ mandocdb(int argc, char *argv[]) continue; if (0 == treescan()) continue; - if (0 == dbopen(0)) - continue; - - mpages_merge(mp); - if (warnings && !nodb && - ! (MPARSE_QUICK & mparse_options)) - names_check(); - dbclose(0); + dba = dba_new(128); + mpages_merge(dba, mp); + if (nodb == 0) + dbwrite(dba); + dba_free(dba); if (j + 1 < conf.manpath.sz) { mpages_free(); @@ -596,7 +559,8 @@ treescan(void) FTS *f; FTSENT *ff; struct mlink *mlink; - int dform, gzip; + int gzip; + enum form dform; char *dsec, *arch, *fsec, *cp; const char *path; const char *argv[2]; @@ -970,6 +934,7 @@ mlink_add(struct mlink *mlink, const struct stat *st) mpage = mandoc_calloc(1, sizeof(struct mpage)); mpage->inodev.st_ino = inodev.st_ino; mpage->inodev.st_dev = inodev.st_dev; + mpage->form = FORM_NONE; ohash_insert(&mpages, slot, mpage); } else mlink->next = mpage->mlinks; @@ -1118,7 +1083,7 @@ mlink_check(struct mpage *mpage, struct mlink *mlink) * and filename to determine whether the file is parsable or not. */ static void -mpages_merge(struct mparse *mp) +mpages_merge(struct dba *dba, struct mparse *mp) { char any[] = "any"; struct mpage *mpage, *mpage_dest; @@ -1129,9 +1094,6 @@ mpages_merge(struct mparse *mp) int fd; unsigned int pslot; - if ( ! nodb) - SQL_EXEC("BEGIN TRANSACTION"); - mpage = ohash_first(&mpages, &pslot); while (mpage != NULL) { mlinks_undupe(mpage); @@ -1188,8 +1150,8 @@ mpages_merge(struct mparse *mp) * to the target. */ - if (mpage_dest->pageid) - dbadd_mlink_name(mlink); + if (mpage_dest->dba != NULL) + dbadd_mlink(mlink); if (mlink->next == NULL) break; @@ -1254,7 +1216,7 @@ mpages_merge(struct mparse *mp) mlink = mlink->next) mlink_check(mpage, mlink); - dbadd(mpage); + dbadd(dba, mpage); mlink = mpage->mlinks; nextpage: @@ -1262,44 +1224,6 @@ nextpage: ohash_delete(&names); mpage = ohash_next(&mpages, &pslot); } - - if (0 == nodb) - SQL_EXEC("END TRANSACTION"); -} - -static void -names_check(void) -{ - sqlite3_stmt *stmt; - const char *name, *sec, *arch, *key; - - sqlite3_prepare_v2(db, - "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 (sqlite3_bind_int64(stmt, 1, NAME_TITLE) != SQLITE_OK) - say("", "%s", sqlite3_errmsg(db)); - - while (sqlite3_step(stmt) == SQLITE_ROW) { - 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 @@ -1823,7 +1747,7 @@ putkeys(const struct mpage *mpage, char *cp, size_t sz, uint64_t v) } else { htab = &strings; if (debug > 1) - for (i = 0; i < mansearch_keymax; i++) + for (i = 0; i < KEY_MAX; i++) if ((uint64_t)1 << i & v) say(mpage->mlinks->file, "Adding key %s=%*s", @@ -2029,53 +1953,23 @@ render_string(char **public, size_t *psz) 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]); + dba_page_alias(mlink->mpage->dba, mlink->name, NAME_FILE); + dba_page_add(mlink->mpage->dba, DBP_SECT, mlink->dsec); + dba_page_add(mlink->mpage->dba, DBP_ARCH, mlink->arch); + dba_page_add(mlink->mpage->dba, DBP_FILE, mlink->file); } /* * Flush the current page's terms (and their bits) into the database. - * Wrap the entire set of additions in a transaction to make sqlite be a - * little faster. * Also, handle escape sequences at the last possible moment. */ static void -dbadd(struct mpage *mpage) +dbadd(struct dba *dba, struct mpage *mpage) { struct mlink *mlink; struct str *key; char *cp; + uint64_t mask; size_t i; unsigned int slot; int mustfree; @@ -2120,111 +2014,87 @@ dbadd(struct mpage *mpage) cp = mpage->desc; i = strlen(cp); mustfree = render_string(&cp, &i); - i = 1; - SQL_BIND_TEXT(stmts[STMT_INSERT_PAGE], i, cp); - 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]); + mpage->dba = dba_page_new(dba->pages, mlink->name, + mlink->dsec, mlink->arch, cp, mlink->file, mpage->form); if (mustfree) free(cp); - while (NULL != mlink) { + while ((mlink = mlink->next) != NULL) 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); - i = 1; - SQL_BIND_INT64(stmts[STMT_INSERT_NAME], i, key->mask); - SQL_BIND_TEXT(stmts[STMT_INSERT_NAME], i, key->key); - SQL_BIND_INT64(stmts[STMT_INSERT_NAME], i, mpage->pageid); - SQL_STEP(stmts[STMT_INSERT_NAME]); - sqlite3_reset(stmts[STMT_INSERT_NAME]); + dba_page_alias(mpage->dba, key->key, key->mask); free(key); } for (key = ohash_first(&strings, &slot); NULL != key; key = ohash_next(&strings, &slot)) { assert(key->mpage == mpage); - i = 1; - SQL_BIND_INT64(stmts[STMT_INSERT_KEY], i, key->mask); - SQL_BIND_TEXT(stmts[STMT_INSERT_KEY], i, key->key); - SQL_BIND_INT64(stmts[STMT_INSERT_KEY], i, mpage->pageid); - SQL_STEP(stmts[STMT_INSERT_KEY]); - sqlite3_reset(stmts[STMT_INSERT_KEY]); + i = 0; + for (mask = TYPE_Xr; mask <= TYPE_Lb; mask *= 2) { + if (key->mask & mask) + dba_macro_add(dba->macros, i, + key->key, mpage->dba); + i++; + } free(key); } } static void -dbprune(void) +dbprune(struct dba *dba) { - struct mpage *mpage; - struct mlink *mlink; - size_t i; - unsigned int slot; - - if (0 == nodb) - SQL_EXEC("BEGIN TRANSACTION"); - - for (mpage = ohash_first(&mpages, &slot); NULL != mpage; - mpage = ohash_next(&mpages, &slot)) { - mlink = mpage->mlinks; - if (debug) - say(mlink->file, "Deleting from database"); - if (nodb) - continue; - for ( ; NULL != mlink; mlink = mlink->next) { - 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]); + struct dba_array *page, *files; + char *file; + + dba_array_FOREACH(dba->pages, page) { + files = dba_array_get(page, DBP_FILE); + dba_array_FOREACH(files, file) { + if (*file < ' ') + file++; + if (ohash_find(&mlinks, ohash_qlookup(&mlinks, + file)) != NULL) { + if (debug) + say(file, "Deleting from database"); + dba_array_del(dba->pages); + break; + } } } - - if (0 == nodb) - SQL_EXEC("END TRANSACTION"); } /* - * Close an existing database and its prepared statements. - * If "real" is not set, rename the temporary file into the real one. + * Write the database from memory to disk. */ static void -dbclose(int real) +dbwrite(struct dba *dba) { - size_t i; + char tfn[32]; int status; pid_t child; - if (nodb) + if (dba_write(MANDOC_DB "~", dba) != -1) { + if (rename(MANDOC_DB "~", MANDOC_DB) == -1) { + exitcode = (int)MANDOCLEVEL_SYSERR; + say(MANDOC_DB, "&rename"); + unlink(MANDOC_DB "~"); + } return; - - for (i = 0; i < STMT__MAX; i++) { - sqlite3_finalize(stmts[i]); - stmts[i] = NULL; } - sqlite3_close(db); - db = NULL; - - if (real) + (void)strlcpy(tfn, "/tmp/mandocdb.XXXXXXXX", sizeof(tfn)); + if (mkdtemp(tfn) == NULL) { + exitcode = (int)MANDOCLEVEL_SYSERR; + say("", "&%s", tfn); return; + } - if ('\0' == *tempfilename) { - if (-1 == rename(MANDOC_DB "~", MANDOC_DB)) { - exitcode = (int)MANDOCLEVEL_SYSERR; - say(MANDOC_DB, "&rename"); - } - return; + (void)strlcat(tfn, "/" MANDOC_DB, sizeof(tfn)); + if (dba_write(tfn, dba) == -1) { + exitcode = (int)MANDOCLEVEL_SYSERR; + say(tfn, "&dba_write"); + goto out; } switch (child = fork()) { @@ -2233,14 +2103,13 @@ dbclose(int real) say("", "&fork cmp"); return; case 0: - execlp("cmp", "cmp", "-s", - tempfilename, MANDOC_DB, (char *)NULL); + execlp("cmp", "cmp", "-s", tfn, MANDOC_DB, (char *)NULL); say("", "&exec cmp"); exit(0); default: break; } - if (-1 == waitpid(child, &status, 0)) { + if (waitpid(child, &status, 0) == -1) { exitcode = (int)MANDOCLEVEL_SYSERR; say("", "&wait cmp"); } else if (WIFSIGNALED(status)) { @@ -2252,173 +2121,27 @@ dbclose(int real) "Data changed, but cannot replace database"); } - *strrchr(tempfilename, '/') = '\0'; +out: + *strrchr(tfn, '/') = '\0'; switch (child = fork()) { case -1: exitcode = (int)MANDOCLEVEL_SYSERR; say("", "&fork rm"); return; case 0: - execlp("rm", "rm", "-rf", tempfilename, (char *)NULL); + execlp("rm", "rm", "-rf", tfn, (char *)NULL); say("", "&exec rm"); exit((int)MANDOCLEVEL_SYSERR); default: break; } - if (-1 == waitpid(child, &status, 0)) { + if (waitpid(child, &status, 0) == -1) { exitcode = (int)MANDOCLEVEL_SYSERR; say("", "&wait rm"); } else if (WIFSIGNALED(status) || WEXITSTATUS(status)) { exitcode = (int)MANDOCLEVEL_SYSERR; - say("", "%s: Cannot remove temporary directory", - tempfilename); - } -} - -/* - * This is straightforward stuff. - * Open a database connection to a "temporary" database, then open a set - * of prepared statements we'll use over and over again. - * If "real" is set, we use the existing database; if not, we truncate a - * temporary one. - * Must be matched by dbclose(). - */ -static int -dbopen(int real) -{ - const char *sql; - int rc, ofl; - - if (nodb) - return 1; - - *tempfilename = '\0'; - ofl = SQLITE_OPEN_READWRITE; - - if (real) { - rc = sqlite3_open_v2(MANDOC_DB, &db, ofl, NULL); - if (SQLITE_OK != rc) { - exitcode = (int)MANDOCLEVEL_SYSERR; - if (SQLITE_CANTOPEN != rc) - say(MANDOC_DB, "%s", sqlite3_errstr(rc)); - return 0; - } - goto prepare_statements; - } - - 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; - say(MANDOC_DB "~", "%s", sqlite3_errstr(rc)); - return 0; - } - - (void)strlcpy(tempfilename, "/tmp/mandocdb.XXXXXX", - sizeof(tempfilename)); - if (NULL == mkdtemp(tempfilename)) { - exitcode = (int)MANDOCLEVEL_SYSERR; - say("", "&%s", tempfilename); - return 0; - } - (void)strlcat(tempfilename, "/" MANDOC_DB, - sizeof(tempfilename)); - rc = sqlite3_open_v2(tempfilename, &db, ofl, NULL); - if (SQLITE_OK != rc) { - exitcode = (int)MANDOCLEVEL_SYSERR; - say("", "%s: %s", tempfilename, sqlite3_errstr(rc)); - return 0; - } - -create_tables: - sql = "CREATE TABLE \"mpages\" (\n" - " \"desc\" TEXT NOT NULL,\n" - " \"form\" INTEGER NOT NULL,\n" - " \"pageid\" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL\n" - ");\n" - "\n" - "CREATE TABLE \"mlinks\" (\n" - " \"sec\" TEXT NOT NULL,\n" - " \"arch\" TEXT NOT NULL,\n" - " \"name\" TEXT NOT NULL,\n" - " \"pageid\" INTEGER NOT NULL REFERENCES mpages(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" - " UNIQUE (\"name\", \"pageid\") ON CONFLICT REPLACE\n" - ");\n" - "\n" - "CREATE TABLE \"keys\" (\n" - " \"bits\" INTEGER NOT NULL,\n" - " \"key\" TEXT NOT NULL,\n" - " \"pageid\" INTEGER NOT NULL REFERENCES mpages(pageid) " - "ON DELETE CASCADE\n" - ");\n" - "CREATE INDEX keys_pageid_idx ON keys (pageid);\n"; - - 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: - 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=?)"; - sqlite3_prepare_v2(db, sql, -1, &stmts[STMT_DELETE_PAGE], NULL); - sql = "INSERT INTO mpages " - "(desc,form) VALUES (?,?)"; - sqlite3_prepare_v2(db, sql, -1, &stmts[STMT_INSERT_PAGE], NULL); - sql = "INSERT INTO mlinks " - "(sec,arch,name,pageid) VALUES (?,?,?,?)"; - sqlite3_prepare_v2(db, sql, -1, &stmts[STMT_INSERT_LINK], NULL); - sql = "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 " - "(bits,key,pageid) VALUES (?,?,?)"; - sqlite3_prepare_v2(db, sql, -1, &stmts[STMT_INSERT_KEY], NULL); - -#ifndef __APPLE__ - /* - * When opening a new database, we can turn off - * synchronous mode for much better performance. - */ - - if (real && 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; + say("", "%s: Cannot remove temporary directory", tfn); } -#endif - - return 1; } static int diff --git a/mansearch.c b/mansearch.c index 66cd7796..b2410e99 100644 --- a/mansearch.c +++ b/mansearch.c @@ -1,4 +1,4 @@ -/* $Id$ */ +/* $OpenBSD: mansearch.c,v 1.50 2016/07/09 15:23:36 schwarze Exp $ */ /* * Copyright (c) 2012 Kristaps Dzonsons <kristaps@bsd.lv> * Copyright (c) 2013, 2014, 2015 Ingo Schwarze <schwarze@openbsd.org> @@ -15,15 +15,12 @@ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ -#include "config.h" #include <sys/mman.h> #include <sys/types.h> #include <assert.h> -#if HAVE_ERR #include <err.h> -#endif #include <errno.h> #include <fcntl.h> #include <glob.h> @@ -36,121 +33,50 @@ #include <string.h> #include <unistd.h> -#include <sqlite3.h> -#ifndef SQLITE_DETERMINISTIC -#define SQLITE_DETERMINISTIC 0 -#endif - #include "mandoc.h" #include "mandoc_aux.h" #include "mandoc_ohash.h" #include "manconf.h" #include "mansearch.h" - -extern int mansearch_keymax; -extern const char *const mansearch_keynames[]; - -#define SQL_BIND_TEXT(_db, _s, _i, _v) \ - do { if (SQLITE_OK != sqlite3_bind_text \ - ((_s), (_i)++, (_v), -1, SQLITE_STATIC)) \ - errx((int)MANDOCLEVEL_SYSERR, "%s", sqlite3_errmsg((_db))); \ - } while (0) -#define SQL_BIND_INT64(_db, _s, _i, _v) \ - do { if (SQLITE_OK != sqlite3_bind_int64 \ - ((_s), (_i)++, (_v))) \ - errx((int)MANDOCLEVEL_SYSERR, "%s", sqlite3_errmsg((_db))); \ - } while (0) -#define SQL_BIND_BLOB(_db, _s, _i, _v) \ - do { if (SQLITE_OK != sqlite3_bind_blob \ - ((_s), (_i)++, (&_v), sizeof(_v), SQLITE_STATIC)) \ - errx((int)MANDOCLEVEL_SYSERR, "%s", sqlite3_errmsg((_db))); \ - } while (0) +#include "dbm.h" struct expr { - regex_t regexp; /* compiled regexp, if applicable */ - const char *substr; /* to search for, if applicable */ - struct expr *next; /* next in sequence */ - uint64_t bits; /* type-mask */ - int equal; /* equality, not subsring match */ - int open; /* opening parentheses before */ - int and; /* logical AND before */ - int close; /* closing parentheses after */ + /* Used for terms: */ + struct dbm_match match; /* Match type and expression. */ + uint64_t bits; /* Type mask. */ + /* Used for OR and AND groups: */ + struct expr *next; /* Next child in the parent group. */ + struct expr *child; /* First child in this group. */ + enum { EXPR_TERM, EXPR_OR, EXPR_AND } type; }; -struct match { - uint64_t pageid; /* identifier in database */ - uint64_t bits; /* name type mask */ - char *desc; /* manual page description */ - int form; /* bit field: formatted, zipped? */ +const char *const mansearch_keynames[KEY_MAX] = { + "arch", "sec", "Xr", "Ar", "Fa", "Fl", "Dv", "Fn", + "Ic", "Pa", "Cm", "Li", "Em", "Cd", "Va", "Ft", + "Tn", "Er", "Ev", "Sy", "Sh", "In", "Ss", "Ox", + "An", "Mt", "St", "Bx", "At", "Nx", "Fx", "Lk", + "Ms", "Bsx", "Dx", "Rs", "Vt", "Lb", "Nm", "Nd" }; -static void buildnames(const struct mansearch *, - struct manpage *, sqlite3 *, - sqlite3_stmt *, uint64_t, - const char *, int form); -static char *buildoutput(sqlite3 *, sqlite3_stmt *, - uint64_t, uint64_t); + +static struct ohash *manmerge(struct expr *, struct ohash *); +static struct ohash *manmerge_term(struct expr *, struct ohash *); +static struct ohash *manmerge_or(struct expr *, struct ohash *); +static struct ohash *manmerge_and(struct expr *, struct ohash *); +static char *buildnames(const struct dbm_page *); +static char *buildoutput(size_t, int32_t); +static size_t lstlen(const char *); +static void lstcat(char *, size_t *, const char *); +static int lstmatch(const char *, const char *); static struct expr *exprcomp(const struct mansearch *, - int, char *[]); + int, char *[], int *); +static struct expr *expr_and(const struct mansearch *, + int, char *[], int *); +static struct expr *exprterm(const struct mansearch *, + int, char *[], int *); static void exprfree(struct expr *); -static struct expr *exprterm(const struct mansearch *, char *, int); static int manpage_compare(const void *, const void *); -static void sql_append(char **sql, size_t *sz, - const char *newstr, int count); -static void sql_match(sqlite3_context *context, - int argc, sqlite3_value **argv); -static void sql_regexp(sqlite3_context *context, - int argc, sqlite3_value **argv); -static char *sql_statement(const struct expr *); - - -int -mansearch_setup(int start) -{ - static void *pagecache; - int c; - -#define PC_PAGESIZE 1280 -#define PC_NUMPAGES 256 - - if (start) { - if (NULL != pagecache) { - warnx("pagecache already enabled"); - return (int)MANDOCLEVEL_BADARG; - } - pagecache = mmap(NULL, PC_PAGESIZE * PC_NUMPAGES, - PROT_READ | PROT_WRITE, - MAP_SHARED | MAP_ANON, -1, 0); - - if (MAP_FAILED == pagecache) { - warn("mmap"); - pagecache = NULL; - return (int)MANDOCLEVEL_SYSERR; - } - - c = sqlite3_config(SQLITE_CONFIG_PAGECACHE, - pagecache, PC_PAGESIZE, PC_NUMPAGES); - - if (SQLITE_OK == c) - return (int)MANDOCLEVEL_OK; - - warnx("pagecache: %s", sqlite3_errstr(c)); - - } else if (NULL == pagecache) { - warnx("pagecache missing"); - return (int)MANDOCLEVEL_BADARG; - } - - if (-1 == munmap(pagecache, PC_PAGESIZE * PC_NUMPAGES)) { - warn("munmap"); - pagecache = NULL; - return (int)MANDOCLEVEL_SYSERR; - } - - pagecache = NULL; - return (int)MANDOCLEVEL_OK; -} int mansearch(const struct mansearch *search, @@ -158,21 +84,18 @@ mansearch(const struct mansearch *search, int argc, char *argv[], struct manpage **res, size_t *sz) { - int64_t pageid; - uint64_t outbit, iterbit; char buf[PATH_MAX]; - char *sql; + struct dbm_res *rp; + struct expr *e; + struct dbm_page *page; struct manpage *mpage; - struct expr *e, *ep; - sqlite3 *db; - sqlite3_stmt *s, *s2; - struct match *mp; - struct ohash htab; - unsigned int idx; - size_t i, j, cur, maxres; - int c, chdir_status, getcwd_status, indexbit; - - if (argc == 0 || (e = exprcomp(search, argc, argv)) == NULL) { + struct ohash *htab; + size_t cur, i, maxres, outkey; + unsigned int slot; + int argi, chdir_status, getcwd_status, im; + + argi = 0; + if ((e = exprcomp(search, argc, argv, &argi)) == NULL) { *sz = 0; return 0; } @@ -180,19 +103,14 @@ mansearch(const struct mansearch *search, cur = maxres = 0; *res = NULL; - if (NULL != search->outkey) { - outbit = TYPE_Nd; - for (indexbit = 0, iterbit = 1; - indexbit < mansearch_keymax; - indexbit++, iterbit <<= 1) { + outkey = KEY_Nd; + if (search->outkey != NULL) + for (im = 0; im < KEY_MAX; im++) if (0 == strcasecmp(search->outkey, - mansearch_keynames[indexbit])) { - outbit = iterbit; + mansearch_keynames[im])) { + outkey = im; break; } - } - } else - outbit = 0; /* * Remember the original working directory, if possible. @@ -208,8 +126,6 @@ mansearch(const struct mansearch *search, } else getcwd_status = 1; - sql = sql_statement(e); - /* * Loop over the directories (containing databases) for us to * search. @@ -235,123 +151,48 @@ mansearch(const struct mansearch *search, } chdir_status = 1; - c = sqlite3_open_v2(MANDOC_DB, &db, - SQLITE_OPEN_READONLY, NULL); - - if (SQLITE_OK != c) { + if (dbm_open(MANDOC_DB) == -1) { warn("%s/%s", paths->paths[i], MANDOC_DB); - sqlite3_close(db); continue; } - /* - * Define the SQL functions for substring - * and regular expression matching. - */ - - c = sqlite3_create_function(db, "match", 2, - SQLITE_UTF8 | SQLITE_DETERMINISTIC, - NULL, sql_match, NULL, NULL); - assert(SQLITE_OK == c); - c = sqlite3_create_function(db, "regexp", 2, - SQLITE_UTF8 | SQLITE_DETERMINISTIC, - NULL, sql_regexp, NULL, NULL); - assert(SQLITE_OK == c); - - j = 1; - c = sqlite3_prepare_v2(db, sql, -1, &s, NULL); - if (SQLITE_OK != c) - errx((int)MANDOCLEVEL_SYSERR, - "%s", sqlite3_errmsg(db)); - - for (ep = e; NULL != ep; ep = ep->next) { - if (NULL == ep->substr) { - SQL_BIND_BLOB(db, s, j, ep->regexp); - } else - SQL_BIND_TEXT(db, s, j, ep->substr); - if (0 == ((TYPE_Nd | TYPE_Nm) & ep->bits)) - SQL_BIND_INT64(db, s, j, ep->bits); + if ((htab = manmerge(e, NULL)) == NULL) { + dbm_close(); + continue; } - mandoc_ohash_init(&htab, 4, offsetof(struct match, pageid)); + for (rp = ohash_first(htab, &slot); rp != NULL; + rp = ohash_next(htab, &slot)) { + page = dbm_page_get(rp->page); - /* - * Hash each entry on its [unique] document identifier. - * This is a uint64_t. - * Instead of using a hash function, simply convert the - * uint64_t to a uint32_t, the hash value's type. - * This gives good performance and preserves the - * distribution of buckets in the table. - */ - while (SQLITE_ROW == (c = sqlite3_step(s))) { - pageid = sqlite3_column_int64(s, 2); - idx = ohash_lookup_memory(&htab, - (char *)&pageid, sizeof(uint64_t), - (uint32_t)pageid); - - if (NULL != ohash_find(&htab, idx)) + if (lstmatch(search->sec, page->sect) == 0 || + lstmatch(search->arch, page->arch) == 0) continue; - mp = mandoc_calloc(1, sizeof(struct match)); - mp->pageid = pageid; - mp->form = sqlite3_column_int(s, 1); - mp->bits = sqlite3_column_int64(s, 3); - if (TYPE_Nd == outbit) - mp->desc = mandoc_strdup((const char *) - sqlite3_column_text(s, 0)); - ohash_insert(&htab, idx, mp); - } - - if (SQLITE_DONE != c) - warnx("%s", sqlite3_errmsg(db)); - - sqlite3_finalize(s); - - c = sqlite3_prepare_v2(db, - "SELECT sec, arch, name, pageid FROM mlinks " - "WHERE pageid=? ORDER BY sec, arch, name", - -1, &s, NULL); - if (SQLITE_OK != c) - errx((int)MANDOCLEVEL_SYSERR, - "%s", sqlite3_errmsg(db)); - - c = sqlite3_prepare_v2(db, - "SELECT bits, key, pageid FROM keys " - "WHERE pageid=? AND bits & ?", - -1, &s2, NULL); - if (SQLITE_OK != c) - errx((int)MANDOCLEVEL_SYSERR, - "%s", sqlite3_errmsg(db)); - - for (mp = ohash_first(&htab, &idx); - NULL != mp; - mp = ohash_next(&htab, &idx)) { if (cur + 1 > maxres) { maxres += 1024; *res = mandoc_reallocarray(*res, - maxres, sizeof(struct manpage)); + maxres, sizeof(**res)); } mpage = *res + cur; + mandoc_asprintf(&mpage->file, "%s/%s", + paths->paths[i], page->file + 1); + mpage->names = buildnames(page); + mpage->output = (int)outkey == KEY_Nd ? + mandoc_strdup(page->desc) : + buildoutput(outkey, page->addr); mpage->ipath = i; - mpage->bits = mp->bits; - mpage->sec = 10; - mpage->form = mp->form; - buildnames(search, mpage, db, s, mp->pageid, - paths->paths[i], mp->form); - if (mpage->names != NULL) { - mpage->output = TYPE_Nd & outbit ? - mp->desc : outbit ? - buildoutput(db, s2, mp->pageid, outbit) : - NULL; - cur++; - } - free(mp); + mpage->bits = rp->bits; + mpage->sec = *page->sect - '0'; + if (mpage->sec < 0 || mpage->sec > 9) + mpage->sec = 10; + mpage->form = *page->file; + free(rp); + cur++; } - - sqlite3_finalize(s); - sqlite3_finalize(s2); - sqlite3_close(db); - ohash_delete(&htab); + ohash_delete(htab); + free(htab); + dbm_close(); /* * In man(1) mode, prefer matches in earlier trees @@ -365,11 +206,169 @@ mansearch(const struct mansearch *search, if (chdir_status && getcwd_status && chdir(buf) == -1) warn("%s", buf); exprfree(e); - free(sql); *sz = cur; return 1; } +/* + * Merge the results for the expression tree rooted at e + * into the the result list htab. + */ +static struct ohash * +manmerge(struct expr *e, struct ohash *htab) +{ + switch (e->type) { + case EXPR_TERM: + return manmerge_term(e, htab); + case EXPR_OR: + return manmerge_or(e->child, htab); + case EXPR_AND: + return manmerge_and(e->child, htab); + default: + abort(); + } +} + +static struct ohash * +manmerge_term(struct expr *e, struct ohash *htab) +{ + struct dbm_res res, *rp; + uint64_t ib; + unsigned int slot; + int im; + + if (htab == NULL) { + htab = mandoc_malloc(sizeof(*htab)); + mandoc_ohash_init(htab, 4, offsetof(struct dbm_res, page)); + } + + for (im = 0, ib = 1; im < KEY_MAX; im++, ib <<= 1) { + if ((e->bits & ib) == 0) + continue; + + switch (ib) { + case TYPE_arch: + dbm_page_byarch(&e->match); + break; + case TYPE_sec: + dbm_page_bysect(&e->match); + break; + case TYPE_Nm: + dbm_page_byname(&e->match); + break; + case TYPE_Nd: + dbm_page_bydesc(&e->match); + break; + default: + dbm_page_bymacro(im - 2, &e->match); + break; + } + + /* + * When hashing for deduplication, use the unique + * page ID itself instead of a hash function; + * that is quite efficient. + */ + + for (;;) { + res = dbm_page_next(); + if (res.page == -1) + break; + slot = ohash_lookup_memory(htab, + (char *)&res, sizeof(res.page), res.page); + if ((rp = ohash_find(htab, slot)) != NULL) { + rp->bits |= res.bits; + continue; + } + rp = mandoc_malloc(sizeof(*rp)); + *rp = res; + ohash_insert(htab, slot, rp); + } + } + return htab; +} + +static struct ohash * +manmerge_or(struct expr *e, struct ohash *htab) +{ + while (e != NULL) { + htab = manmerge(e, htab); + e = e->next; + } + return htab; +} + +static struct ohash * +manmerge_and(struct expr *e, struct ohash *htab) +{ + struct ohash *hand, *h1, *h2; + struct dbm_res *res; + unsigned int slot1, slot2; + + /* Evaluate the first term of the AND clause. */ + + hand = manmerge(e, NULL); + + while ((e = e->next) != NULL) { + + /* Evaluate the next term and prepare for ANDing. */ + + h2 = manmerge(e, NULL); + if (ohash_entries(h2) < ohash_entries(hand)) { + h1 = h2; + h2 = hand; + } else + h1 = hand; + hand = mandoc_malloc(sizeof(*hand)); + mandoc_ohash_init(hand, 4, offsetof(struct dbm_res, page)); + + /* Keep all pages that are in both result sets. */ + + for (res = ohash_first(h1, &slot1); res != NULL; + res = ohash_next(h1, &slot1)) { + if (ohash_find(h2, ohash_lookup_memory(h2, + (char *)res, sizeof(res->page), + res->page)) == NULL) + free(res); + else + ohash_insert(hand, ohash_lookup_memory(hand, + (char *)res, sizeof(res->page), + res->page), res); + } + + /* Discard the merged results. */ + + for (res = ohash_first(h2, &slot2); res != NULL; + res = ohash_next(h2, &slot2)) + free(res); + ohash_delete(h2); + free(h2); + ohash_delete(h1); + free(h1); + } + + /* Merge the result of the AND into htab. */ + + if (htab == NULL) + return hand; + + for (res = ohash_first(hand, &slot1); res != NULL; + res = ohash_next(hand, &slot1)) { + slot2 = ohash_lookup_memory(htab, + (char *)res, sizeof(res->page), res->page); + if (ohash_find(htab, slot2) == NULL) + ohash_insert(htab, slot2, res); + else + free(res); + } + + /* Discard the merged result. */ + + ohash_delete(hand); + free(hand); + return htab; +} + void mansearch_free(struct manpage *res, size_t sz) { @@ -396,260 +395,114 @@ manpage_compare(const void *vp1, const void *vp2) strcasecmp(mp1->names, mp2->names); } -static void -buildnames(const struct mansearch *search, struct manpage *mpage, - sqlite3 *db, sqlite3_stmt *s, - uint64_t pageid, const char *path, int form) -{ - glob_t globinfo; - char *firstname, *newnames, *prevsec, *prevarch; - const char *oldnames, *sep1, *name, *sec, *sep2, *arch, *fsec; - size_t i; - int c, globres; - - mpage->file = NULL; - mpage->names = NULL; - firstname = prevsec = prevarch = NULL; - i = 1; - SQL_BIND_INT64(db, s, i, pageid); - while (SQLITE_ROW == (c = sqlite3_step(s))) { - - /* Decide whether we already have some names. */ - - if (NULL == mpage->names) { - oldnames = ""; - sep1 = ""; - } else { - oldnames = mpage->names; - sep1 = ", "; - } - - /* Fetch the next name, rejecting sec/arch mismatches. */ - - sec = (const char *)sqlite3_column_text(s, 0); - if (search->sec != NULL && strcasecmp(sec, search->sec)) - continue; - arch = (const char *)sqlite3_column_text(s, 1); - if (search->arch != NULL && *arch != '\0' && - strcasecmp(arch, search->arch)) - continue; - name = (const char *)sqlite3_column_text(s, 2); - - /* Remember the first section found. */ - - if (9 < mpage->sec && '1' <= *sec && '9' >= *sec) - mpage->sec = (*sec - '1') + 1; - - /* If the section changed, append the old one. */ - - if (NULL != prevsec && - (strcmp(sec, prevsec) || - strcmp(arch, prevarch))) { - sep2 = '\0' == *prevarch ? "" : "/"; - mandoc_asprintf(&newnames, "%s(%s%s%s)", - oldnames, prevsec, sep2, prevarch); - free(mpage->names); - oldnames = mpage->names = newnames; - free(prevsec); - free(prevarch); - prevsec = prevarch = NULL; - } - - /* Save the new section, to append it later. */ - - if (NULL == prevsec) { - prevsec = mandoc_strdup(sec); - prevarch = mandoc_strdup(arch); - } - - /* Append the new name. */ - - mandoc_asprintf(&newnames, "%s%s%s", - oldnames, sep1, name); - free(mpage->names); - mpage->names = newnames; - - /* Also save the first file name encountered. */ - - if (mpage->file != NULL) - continue; - - if (form & FORM_SRC) { - sep1 = "man"; - fsec = sec; - } else { - sep1 = "cat"; - fsec = "0"; - } - sep2 = *arch == '\0' ? "" : "/"; - mandoc_asprintf(&mpage->file, "%s/%s%s%s%s/%s.%s", - path, sep1, sec, sep2, arch, name, fsec); - if (access(mpage->file, R_OK) != -1) - continue; - - /* Handle unusual file name extensions. */ - - if (firstname == NULL) - firstname = mpage->file; - else - free(mpage->file); - mandoc_asprintf(&mpage->file, "%s/%s%s%s%s/%s.*", - path, sep1, sec, sep2, arch, name); - globres = glob(mpage->file, 0, NULL, &globinfo); - free(mpage->file); - mpage->file = globres ? NULL : - mandoc_strdup(*globinfo.gl_pathv); - globfree(&globinfo); - } - if (c != SQLITE_DONE) - warnx("%s", sqlite3_errmsg(db)); - sqlite3_reset(s); - - /* If none of the files is usable, use the first name. */ - - if (mpage->file == NULL) - mpage->file = firstname; - else if (mpage->file != firstname) - free(firstname); - - /* Append one final section to the names. */ - - if (prevsec != NULL) { - sep2 = *prevarch == '\0' ? "" : "/"; - mandoc_asprintf(&newnames, "%s(%s%s%s)", - mpage->names, prevsec, sep2, prevarch); - free(mpage->names); - mpage->names = newnames; - free(prevsec); - free(prevarch); - } -} - static char * -buildoutput(sqlite3 *db, sqlite3_stmt *s, uint64_t pageid, uint64_t outbit) +buildnames(const struct dbm_page *page) { - char *output, *newoutput; - const char *oldoutput, *sep1, *data; - size_t i; - int c; - - output = NULL; - i = 1; - SQL_BIND_INT64(db, s, i, pageid); - SQL_BIND_INT64(db, s, i, outbit); - while (SQLITE_ROW == (c = sqlite3_step(s))) { - if (NULL == output) { - oldoutput = ""; - sep1 = ""; - } else { - oldoutput = output; - sep1 = " # "; - } - data = (const char *)sqlite3_column_text(s, 1); - mandoc_asprintf(&newoutput, "%s%s%s", - oldoutput, sep1, data); - free(output); - output = newoutput; + char *buf; + size_t i, sz; + + sz = lstlen(page->name) + 1 + lstlen(page->sect) + + (page->arch == NULL ? 0 : 1 + lstlen(page->arch)) + 2; + buf = mandoc_malloc(sz); + i = 0; + lstcat(buf, &i, page->name); + buf[i++] = '('; + lstcat(buf, &i, page->sect); + if (page->arch != NULL) { + buf[i++] = '/'; + lstcat(buf, &i, page->arch); } - if (SQLITE_DONE != c) - warnx("%s", sqlite3_errmsg(db)); - sqlite3_reset(s); - return output; + buf[i++] = ')'; + buf[i++] = '\0'; + assert(i == sz); + return buf; } /* - * Implement substring match as an application-defined SQL function. - * Using the SQL LIKE or GLOB operators instead would be a bad idea - * because that would require escaping metacharacters in the string - * being searched for. + * Count the buffer space needed to print the NUL-terminated + * list of NUL-terminated strings, when printing two separator + * characters between strings. */ -static void -sql_match(sqlite3_context *context, int argc, sqlite3_value **argv) +static size_t +lstlen(const char *cp) { + size_t sz; - assert(2 == argc); - sqlite3_result_int(context, NULL != strcasestr( - (const char *)sqlite3_value_text(argv[1]), - (const char *)sqlite3_value_text(argv[0]))); + for (sz = 0;; sz++) { + if (cp[0] == '\0') { + if (cp[1] == '\0') + break; + sz++; + } else if (cp[0] < ' ') + sz--; + cp++; + } + return sz; } /* - * Implement regular expression match - * as an application-defined SQL function. + * Print the NUL-terminated list of NUL-terminated strings + * into the buffer, seperating strings with a comma and a blank. */ static void -sql_regexp(sqlite3_context *context, int argc, sqlite3_value **argv) +lstcat(char *buf, size_t *i, const char *cp) { - - assert(2 == argc); - sqlite3_result_int(context, !regexec( - (regex_t *)sqlite3_value_blob(argv[0]), - (const char *)sqlite3_value_text(argv[1]), - 0, NULL, 0)); + for (;;) { + if (cp[0] == '\0') { + if (cp[1] == '\0') + break; + buf[(*i)++] = ','; + buf[(*i)++] = ' '; + } else if (cp[0] >= ' ') + buf[(*i)++] = cp[0]; + cp++; + } } -static void -sql_append(char **sql, size_t *sz, const char *newstr, int count) +/* + * Return 1 if the string *want occurs in any of the strings + * in the NUL-terminated string list *have, or 0 otherwise. + * If either argument is NULL or empty, assume no filtering + * is desired and return 1. + */ +static int +lstmatch(const char *want, const char *have) { - size_t newsz; - - newsz = 1 < count ? (size_t)count : strlen(newstr); - *sql = mandoc_realloc(*sql, *sz + newsz + 1); - if (1 < count) - memset(*sql + *sz, *newstr, (size_t)count); - else - memcpy(*sql + *sz, newstr, newsz); - *sz += newsz; - (*sql)[*sz] = '\0'; + if (want == NULL || have == NULL || *have == '\0') + return 1; + while (*have != '\0') { + if (strcasestr(have, want) != NULL) + return 1; + have = strchr(have, '\0') + 1; + } + return 0; } /* - * Prepare the search SQL statement. + * Build a list of values taken by the macro im + * in the manual page with big-endian address addr. */ static char * -sql_statement(const struct expr *e) +buildoutput(size_t im, int32_t addr) { - char *sql; - size_t sz; - int needop; - - sql = mandoc_strdup(e->equal ? - "SELECT desc, form, pageid, bits " - "FROM mpages NATURAL JOIN names WHERE " : - "SELECT desc, form, pageid, 0 FROM mpages WHERE "); - sz = strlen(sql); - - for (needop = 0; NULL != e; e = e->next) { - if (e->and) - sql_append(&sql, &sz, " AND ", 1); - else if (needop) - sql_append(&sql, &sz, " OR ", 1); - if (e->open) - sql_append(&sql, &sz, "(", e->open); - sql_append(&sql, &sz, - TYPE_Nd & e->bits - ? (NULL == e->substr - ? "desc REGEXP ?" - : "desc MATCH ?") - : TYPE_Nm == e->bits - ? (NULL == e->substr - ? "pageid IN (SELECT pageid FROM names " - "WHERE name REGEXP ?)" - : e->equal - ? "name = ? " - : "pageid IN (SELECT pageid FROM names " - "WHERE name MATCH ?)") - : (NULL == e->substr - ? "pageid IN (SELECT pageid FROM keys " - "WHERE key REGEXP ? AND bits & ?)" - : "pageid IN (SELECT pageid FROM keys " - "WHERE key MATCH ? AND bits & ?)"), 1); - if (e->close) - sql_append(&sql, &sz, ")", e->close); - needop = 1; - } + const char *oldoutput, *sep; + char *output, *newoutput, *value; - return sql; + output = NULL; + dbm_macro_bypage(im - 2, addr); + while ((value = dbm_macro_next()) != NULL) { + if (output == NULL) { + oldoutput = ""; + sep = ""; + } else { + oldoutput = output; + sep = " # "; + } + mandoc_asprintf(&newoutput, "%s%s%s", oldoutput, sep, value); + free(output); + output = newoutput; + } + return output; } /* @@ -658,195 +511,238 @@ sql_statement(const struct expr *e) * "(", "foo=bar", etc.). */ static struct expr * -exprcomp(const struct mansearch *search, int argc, char *argv[]) +exprcomp(const struct mansearch *search, int argc, char *argv[], int *argi) { - uint64_t mask; - int i, toopen, logic, igncase, toclose; - struct expr *first, *prev, *cur, *next; - - first = cur = NULL; - logic = igncase = toopen = toclose = 0; - - for (i = 0; i < argc; i++) { - if (0 == strcmp("(", argv[i])) { - if (igncase) - goto fail; - toopen++; - toclose++; - continue; - } else if (0 == strcmp(")", argv[i])) { - if (toopen || logic || igncase || NULL == cur) - goto fail; - cur->close++; - if (0 > --toclose) - goto fail; - continue; - } else if (0 == strcmp("-a", argv[i])) { - if (toopen || logic || igncase || NULL == cur) - goto fail; - logic = 1; + struct expr *parent, *child; + int needterm, nested; + + if ((nested = *argi) == argc) + return NULL; + needterm = 1; + parent = child = NULL; + while (*argi < argc) { + if (strcmp(")", argv[*argi]) == 0) { + if (needterm) + warnx("missing term " + "before closing parenthesis"); + needterm = 0; + if (nested) + break; + warnx("ignoring unmatched right parenthesis"); + ++*argi; continue; - } else if (0 == strcmp("-o", argv[i])) { - if (toopen || logic || igncase || NULL == cur) - goto fail; - logic = 2; + } + if (strcmp("-o", argv[*argi]) == 0) { + if (needterm) { + if (*argi > 0) + warnx("ignoring -o after %s", + argv[*argi - 1]); + else + warnx("ignoring initial -o"); + } + needterm = 1; + ++*argi; continue; - } else if (0 == strcmp("-i", argv[i])) { - if (igncase) - goto fail; - igncase = 1; + } + needterm = 0; + if (child == NULL) { + child = expr_and(search, argc, argv, argi); continue; } - next = exprterm(search, argv[i], !igncase); - if (NULL == next) - goto fail; - if (NULL == first) - first = next; - else - cur->next = next; - prev = cur = next; - - /* - * Searching for descriptions must be split out - * because they are stored in the mpages table, - * not in the keys table. - */ + if (parent == NULL) { + parent = mandoc_calloc(1, sizeof(*parent)); + parent->type = EXPR_OR; + parent->next = NULL; + parent->child = child; + } + child->next = expr_and(search, argc, argv, argi); + child = child->next; + } + if (needterm && *argi) + warnx("ignoring trailing %s", argv[*argi - 1]); + return parent == NULL ? child : parent; +} - for (mask = TYPE_Nm; mask <= TYPE_Nd; mask <<= 1) { - if (mask & cur->bits && ~mask & cur->bits) { - next = mandoc_calloc(1, - sizeof(struct expr)); - memcpy(next, cur, sizeof(struct expr)); - prev->open = 1; - cur->bits = mask; - cur->next = next; - cur = next; - cur->bits &= ~mask; +static struct expr * +expr_and(const struct mansearch *search, int argc, char *argv[], int *argi) +{ + struct expr *parent, *child; + int needterm; + + needterm = 1; + parent = child = NULL; + while (*argi < argc) { + if (strcmp(")", argv[*argi]) == 0) { + if (needterm) + warnx("missing term " + "before closing parenthesis"); + needterm = 0; + break; + } + if (strcmp("-o", argv[*argi]) == 0) + break; + if (strcmp("-a", argv[*argi]) == 0) { + if (needterm) { + if (*argi > 0) + warnx("ignoring -a after %s", + argv[*argi - 1]); + else + warnx("ignoring initial -a"); } + needterm = 1; + ++*argi; + continue; + } + if (needterm == 0) + break; + if (child == NULL) { + child = exprterm(search, argc, argv, argi); + if (child != NULL) + needterm = 0; + continue; + } + needterm = 0; + if (parent == NULL) { + parent = mandoc_calloc(1, sizeof(*parent)); + parent->type = EXPR_AND; + parent->next = NULL; + parent->child = child; + } + child->next = exprterm(search, argc, argv, argi); + if (child->next != NULL) { + child = child->next; + needterm = 0; } - prev->and = (1 == logic); - prev->open += toopen; - if (cur != prev) - cur->close = 1; - - toopen = logic = igncase = 0; } - if ( ! (toopen || logic || igncase || toclose)) - return first; - -fail: - if (NULL != first) - exprfree(first); - return NULL; + if (needterm && *argi) + warnx("ignoring trailing %s", argv[*argi - 1]); + return parent == NULL ? child : parent; } static struct expr * -exprterm(const struct mansearch *search, char *buf, int cs) +exprterm(const struct mansearch *search, int argc, char *argv[], int *argi) { char errbuf[BUFSIZ]; struct expr *e; char *key, *val; uint64_t iterbit; - int i, irc; - - if ('\0' == *buf) - return NULL; + int cs, i, irc; + + if (strcmp("(", argv[*argi]) == 0) { + ++*argi; + e = exprcomp(search, argc, argv, argi); + if (*argi < argc) { + assert(strcmp(")", argv[*argi]) == 0); + ++*argi; + } else + warnx("unclosed parenthesis"); + return e; + } - e = mandoc_calloc(1, sizeof(struct expr)); + e = mandoc_calloc(1, sizeof(*e)); + e->type = EXPR_TERM; + e->bits = 0; + e->next = NULL; + e->child = NULL; if (search->argmode == ARG_NAME) { e->bits = TYPE_Nm; - e->substr = buf; - e->equal = 1; + e->match.type = DBM_EXACT; + e->match.str = argv[(*argi)++]; return e; } /* * Separate macro keys from search string. - * If needed, request regular expression handling - * by setting e->substr to NULL. + * If needed, request regular expression handling. */ if (search->argmode == ARG_WORD) { e->bits = TYPE_Nm; - e->substr = NULL; + e->match.type = DBM_REGEX; #if HAVE_REWB_BSD - mandoc_asprintf(&val, "[[:<:]]%s[[:>:]]", buf); + mandoc_asprintf(&val, "[[:<:]]%s[[:>:]]", argv[*argi]); #elif HAVE_REWB_SYSV - mandoc_asprintf(&val, "\\<%s\\>", buf); + mandoc_asprintf(&val, "\\<%s\\>", argv[*argi]); #else mandoc_asprintf(&val, - "(^|[^a-zA-Z01-9_])%s([^a-zA-Z01-9_]|$)", buf); + "(^|[^a-zA-Z01-9_])%s([^a-zA-Z01-9_]|$)", argv[*argi]); #endif cs = 0; - } else if ((val = strpbrk(buf, "=~")) == NULL) { + } else if ((val = strpbrk(argv[*argi], "=~")) == NULL) { e->bits = TYPE_Nm | TYPE_Nd; - e->substr = buf; + e->match.type = DBM_SUB; + e->match.str = argv[*argi]; } else { - if (val == buf) + if (val == argv[*argi]) e->bits = TYPE_Nm | TYPE_Nd; - if ('=' == *val) - e->substr = val + 1; + if (*val == '=') { + e->match.type = DBM_SUB; + e->match.str = val + 1; + } else + e->match.type = DBM_REGEX; *val++ = '\0'; - if (NULL != strstr(buf, "arch")) + if (strstr(argv[*argi], "arch") != NULL) cs = 0; } /* Compile regular expressions. */ - if (NULL == e->substr) { - irc = regcomp(&e->regexp, val, + if (e->match.type == DBM_REGEX) { + e->match.re = mandoc_malloc(sizeof(*e->match.re)); + irc = regcomp(e->match.re, val, REG_EXTENDED | REG_NOSUB | (cs ? 0 : REG_ICASE)); + if (irc) { + regerror(irc, e->match.re, errbuf, sizeof(errbuf)); + warnx("regcomp /%s/: %s", val, errbuf); + } if (search->argmode == ARG_WORD) free(val); if (irc) { - regerror(irc, &e->regexp, errbuf, sizeof(errbuf)); - warnx("regcomp: %s", errbuf); + free(e->match.re); free(e); + ++*argi; return NULL; } } - if (e->bits) + if (e->bits) { + ++*argi; return e; + } /* * Parse out all possible fields. * If the field doesn't resolve, bail. */ - while (NULL != (key = strsep(&buf, ","))) { + while (NULL != (key = strsep(&argv[*argi], ","))) { if ('\0' == *key) continue; - for (i = 0, iterbit = 1; - i < mansearch_keymax; - i++, iterbit <<= 1) { - if (0 == strcasecmp(key, - mansearch_keynames[i])) { + for (i = 0, iterbit = 1; i < KEY_MAX; i++, iterbit <<= 1) { + if (0 == strcasecmp(key, mansearch_keynames[i])) { e->bits |= iterbit; break; } } - if (i == mansearch_keymax) { - if (strcasecmp(key, "any")) { - free(e); - return NULL; - } + if (i == KEY_MAX) { + if (strcasecmp(key, "any")) + warnx("treating unknown key " + "\"%s\" as \"any\"", key); e->bits |= ~0ULL; } } + ++*argi; return e; } static void -exprfree(struct expr *p) +exprfree(struct expr *e) { - struct expr *pp; - - while (NULL != p) { - pp = p->next; - free(p); - p = pp; - } + if (e->next != NULL) + exprfree(e->next); + if (e->child != NULL) + exprfree(e->child); + free(e); } diff --git a/mansearch.h b/mansearch.h index c6171f7b..82076e23 100644 --- a/mansearch.h +++ b/mansearch.h @@ -16,7 +16,13 @@ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ -#define MANDOC_DB "mandoc.db" +#define MANDOC_DB "mandoc.new.db" +#define MANDOCDB_MAGIC 0x3a7d0cdb +#define MANDOCDB_VERSION 0 /* XXX Start counting in production. */ + +#define MACRO_MAX 36 +#define KEY_Nd 39 +#define KEY_MAX 40 #define TYPE_arch 0x0000000000000001ULL #define TYPE_sec 0x0000000000000002ULL @@ -66,9 +72,11 @@ #define NAME_FILE 0x0000004000000010ULL #define NAME_MASK 0x000000000000001fULL -#define FORM_CAT 0 /* manual page is preformatted */ -#define FORM_SRC 1 /* format is mdoc(7) or man(7) */ -#define FORM_NONE 4 /* format is unknown */ +enum form { + FORM_SRC = 1, /* Format is mdoc(7) or man(7). */ + FORM_CAT, /* Manual page is preformatted. */ + FORM_NONE /* Format is unknown. */ +}; enum argmode { ARG_FILE = 0, @@ -84,7 +92,7 @@ struct manpage { size_t ipath; /* number of the manpath */ uint64_t bits; /* name type mask */ int sec; /* section number, 10 means invalid */ - int form; /* 0 == catpage */ + enum form form; }; struct mansearch { @@ -98,7 +106,6 @@ struct mansearch { struct manpaths; -int mansearch_setup(int); int mansearch(const struct mansearch *cfg, /* options */ const struct manpaths *paths, /* manpaths */ int argc, /* size of argv */ @@ -19,10 +19,8 @@ #include "config.h" #include <sys/types.h> -#if HAVE_MMAP #include <sys/mman.h> #include <sys/stat.h> -#endif #include <assert.h> #include <ctype.h> @@ -598,7 +596,6 @@ read_whole_file(struct mparse *curp, const char *file, int fd, size_t off; ssize_t ssz; -#if HAVE_MMAP struct stat st; if (fstat(fd, &st) == -1) @@ -622,7 +619,6 @@ read_whole_file(struct mparse *curp, const char *file, int fd, if (fb->buf != MAP_FAILED) return 1; } -#endif if (curp->gzip) { if ((gz = gzdopen(fd, "rb")) == NULL) @@ -747,11 +743,9 @@ mparse_readfd(struct mparse *curp, int fd, const char *file) (MPARSE_UTF8 | MPARSE_LATIN1); mparse_parse_buffer(curp, blk, file); curp->filenc = save_filenc; -#if HAVE_MMAP if (with_mmap) munmap(blk.buf, blk.sz); else -#endif free(blk.buf); } return curp->file_status; diff --git a/test-mmap.c b/test-mmap.c deleted file mode 100644 index 3a6232d9..00000000 --- a/test-mmap.c +++ /dev/null @@ -1,9 +0,0 @@ -#include <sys/types.h> -#include <sys/mman.h> -#include <stddef.h> - -int -main(void) -{ - return mmap(NULL, 1, PROT_READ, MAP_SHARED, -1, 0) != MAP_FAILED; -} diff --git a/test-sqlite3_errstr.c b/test-sqlite3_errstr.c deleted file mode 100644 index 4d3c7c54..00000000 --- a/test-sqlite3_errstr.c +++ /dev/null @@ -1,8 +0,0 @@ -#include <string.h> -#include <sqlite3.h> - -int -main(void) -{ - return strcmp(sqlite3_errstr(SQLITE_OK), "not an error"); -} |