[BACK]Return to mansearch.c CVS log [TXT][DIR] Up to [cvsweb.bsd.lv] / mandoc

Annotation of mandoc/mansearch.c, Revision 1.81

1.81    ! schwarze    1: /*     $Id: mansearch.c,v 1.80 2018/12/13 11:55:46 schwarze Exp $ */
1.1       kristaps    2: /*
                      3:  * Copyright (c) 2012 Kristaps Dzonsons <kristaps@bsd.lv>
1.78      schwarze    4:  * Copyright (c) 2013-2018 Ingo Schwarze <schwarze@openbsd.org>
1.1       kristaps    5:  *
                      6:  * Permission to use, copy, modify, and distribute this software for any
                      7:  * purpose with or without fee is hereby granted, provided that the above
                      8:  * copyright notice and this permission notice appear in all copies.
                      9:  *
1.56      schwarze   10:  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHORS DISCLAIM ALL WARRANTIES
1.1       kristaps   11:  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
1.56      schwarze   12:  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR
1.1       kristaps   13:  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
                     14:  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
                     15:  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
                     16:  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
                     17:  */
1.67      schwarze   18: #include "config.h"
1.1       kristaps   19:
1.28      schwarze   20: #include <sys/mman.h>
1.43      schwarze   21: #include <sys/types.h>
                     22:
1.1       kristaps   23: #include <assert.h>
1.68      schwarze   24: #if HAVE_ERR
1.59      schwarze   25: #include <err.h>
1.68      schwarze   26: #endif
1.52      schwarze   27: #include <errno.h>
1.1       kristaps   28: #include <fcntl.h>
1.54      schwarze   29: #include <glob.h>
1.6       schwarze   30: #include <limits.h>
1.8       schwarze   31: #include <regex.h>
1.1       kristaps   32: #include <stdio.h>
                     33: #include <stdint.h>
                     34: #include <stddef.h>
                     35: #include <stdlib.h>
                     36: #include <string.h>
                     37: #include <unistd.h>
                     38:
1.23      schwarze   39: #include "mandoc_aux.h"
1.60      schwarze   40: #include "mandoc_ohash.h"
1.56      schwarze   41: #include "manconf.h"
1.1       kristaps   42: #include "mansearch.h"
1.66      schwarze   43: #include "dbm.h"
1.2       kristaps   44:
1.1       kristaps   45: struct expr {
1.66      schwarze   46:        /* Used for terms: */
                     47:        struct dbm_match match;   /* Match type and expression. */
                     48:        uint64_t         bits;    /* Type mask. */
                     49:        /* Used for OR and AND groups: */
                     50:        struct expr     *next;    /* Next child in the parent group. */
                     51:        struct expr     *child;   /* First child in this group. */
                     52:        enum { EXPR_TERM, EXPR_OR, EXPR_AND } type;
1.1       kristaps   53: };
                     54:
1.66      schwarze   55: const char *const mansearch_keynames[KEY_MAX] = {
                     56:        "arch", "sec",  "Xr",   "Ar",   "Fa",   "Fl",   "Dv",   "Fn",
                     57:        "Ic",   "Pa",   "Cm",   "Li",   "Em",   "Cd",   "Va",   "Ft",
                     58:        "Tn",   "Er",   "Ev",   "Sy",   "Sh",   "In",   "Ss",   "Ox",
                     59:        "An",   "Mt",   "St",   "Bx",   "At",   "Nx",   "Fx",   "Lk",
                     60:        "Ms",   "Bsx",  "Dx",   "Rs",   "Vt",   "Lb",   "Nm",   "Nd"
1.1       kristaps   61: };
                     62:
1.66      schwarze   63:
                     64: static struct ohash    *manmerge(struct expr *, struct ohash *);
                     65: static struct ohash    *manmerge_term(struct expr *, struct ohash *);
                     66: static struct ohash    *manmerge_or(struct expr *, struct ohash *);
                     67: static struct ohash    *manmerge_and(struct expr *, struct ohash *);
                     68: static char            *buildnames(const struct dbm_page *);
1.70      schwarze   69: static char            *buildoutput(size_t, struct dbm_page *);
                     70: static size_t           lstlen(const char *, size_t);
                     71: static void             lstcat(char *, size_t *, const char *, const char *);
1.66      schwarze   72: static int              lstmatch(const char *, const char *);
1.34      schwarze   73: static struct expr     *exprcomp(const struct mansearch *,
1.66      schwarze   74:                                int, char *[], int *);
                     75: static struct expr     *expr_and(const struct mansearch *,
                     76:                                int, char *[], int *);
                     77: static struct expr     *exprterm(const struct mansearch *,
                     78:                                int, char *[], int *);
1.1       kristaps   79: static void             exprfree(struct expr *);
1.39      schwarze   80: static int              manpage_compare(const void *, const void *);
1.28      schwarze   81:
1.1       kristaps   82:
                     83: int
1.5       kristaps   84: mansearch(const struct mansearch *search,
1.12      schwarze   85:                const struct manpaths *paths,
                     86:                int argc, char *argv[],
1.1       kristaps   87:                struct manpage **res, size_t *sz)
                     88: {
1.6       schwarze   89:        char             buf[PATH_MAX];
1.66      schwarze   90:        struct dbm_res  *rp;
                     91:        struct expr     *e;
                     92:        struct dbm_page *page;
1.10      schwarze   93:        struct manpage  *mpage;
1.66      schwarze   94:        struct ohash    *htab;
                     95:        size_t           cur, i, maxres, outkey;
                     96:        unsigned int     slot;
                     97:        int              argi, chdir_status, getcwd_status, im;
1.57      schwarze   98:
1.66      schwarze   99:        argi = 0;
                    100:        if ((e = exprcomp(search, argc, argv, &argi)) == NULL) {
1.57      schwarze  101:                *sz = 0;
1.58      schwarze  102:                return 0;
1.57      schwarze  103:        }
1.1       kristaps  104:
1.57      schwarze  105:        cur = maxres = 0;
1.74      schwarze  106:        if (res != NULL)
                    107:                *res = NULL;
1.1       kristaps  108:
1.66      schwarze  109:        outkey = KEY_Nd;
                    110:        if (search->outkey != NULL)
                    111:                for (im = 0; im < KEY_MAX; im++)
1.45      schwarze  112:                        if (0 == strcasecmp(search->outkey,
1.66      schwarze  113:                            mansearch_keynames[im])) {
                    114:                                outkey = im;
1.12      schwarze  115:                                break;
                    116:                        }
                    117:
1.1       kristaps  118:        /*
1.57      schwarze  119:         * Remember the original working directory, if possible.
                    120:         * This will be needed if the second or a later directory
                    121:         * is given as a relative path.
                    122:         * Do not error out if the current directory is not
                    123:         * searchable: Maybe it won't be needed after all.
1.1       kristaps  124:         */
                    125:
1.57      schwarze  126:        if (getcwd(buf, PATH_MAX) == NULL) {
                    127:                getcwd_status = 0;
                    128:                (void)strlcpy(buf, strerror(errno), sizeof(buf));
                    129:        } else
                    130:                getcwd_status = 1;
1.1       kristaps  131:
                    132:        /*
                    133:         * Loop over the directories (containing databases) for us to
                    134:         * search.
                    135:         * Don't let missing/bad databases/directories phase us.
                    136:         * In each, try to open the resident database and, if it opens,
                    137:         * scan it for our match expression.
                    138:         */
                    139:
1.57      schwarze  140:        chdir_status = 0;
1.1       kristaps  141:        for (i = 0; i < paths->sz; i++) {
1.57      schwarze  142:                if (chdir_status && paths->paths[i][0] != '/') {
                    143:                        if ( ! getcwd_status) {
1.59      schwarze  144:                                warnx("%s: getcwd: %s", paths->paths[i], buf);
1.57      schwarze  145:                                continue;
                    146:                        } else if (chdir(buf) == -1) {
1.64      schwarze  147:                                warn("%s", buf);
1.57      schwarze  148:                                continue;
                    149:                        }
                    150:                }
                    151:                if (chdir(paths->paths[i]) == -1) {
1.64      schwarze  152:                        warn("%s", paths->paths[i]);
1.1       kristaps  153:                        continue;
1.34      schwarze  154:                }
1.57      schwarze  155:                chdir_status = 1;
1.1       kristaps  156:
1.66      schwarze  157:                if (dbm_open(MANDOC_DB) == -1) {
1.73      schwarze  158:                        if (errno != ENOENT)
                    159:                                warn("%s/%s", paths->paths[i], MANDOC_DB);
1.1       kristaps  160:                        continue;
                    161:                }
                    162:
1.66      schwarze  163:                if ((htab = manmerge(e, NULL)) == NULL) {
                    164:                        dbm_close();
                    165:                        continue;
1.1       kristaps  166:                }
                    167:
1.66      schwarze  168:                for (rp = ohash_first(htab, &slot); rp != NULL;
                    169:                    rp = ohash_next(htab, &slot)) {
                    170:                        page = dbm_page_get(rp->page);
1.1       kristaps  171:
1.66      schwarze  172:                        if (lstmatch(search->sec, page->sect) == 0 ||
1.76      schwarze  173:                            lstmatch(search->arch, page->arch) == 0 ||
                    174:                            (search->argmode == ARG_NAME &&
                    175:                             rp->bits <= (int32_t)(NAME_SYN & NAME_MASK)))
1.1       kristaps  176:                                continue;
                    177:
1.74      schwarze  178:                        if (res == NULL) {
                    179:                                cur = 1;
                    180:                                break;
                    181:                        }
1.1       kristaps  182:                        if (cur + 1 > maxres) {
                    183:                                maxres += 1024;
1.36      schwarze  184:                                *res = mandoc_reallocarray(*res,
1.66      schwarze  185:                                    maxres, sizeof(**res));
1.1       kristaps  186:                        }
1.10      schwarze  187:                        mpage = *res + cur;
1.66      schwarze  188:                        mandoc_asprintf(&mpage->file, "%s/%s",
                    189:                            paths->paths[i], page->file + 1);
1.77      schwarze  190:                        if (access(chdir_status ? page->file + 1 :
                    191:                            mpage->file, R_OK) == -1) {
                    192:                                warn("%s", mpage->file);
                    193:                                warnx("outdated mandoc.db contains "
                    194:                                    "bogus %s entry, run makewhatis %s",
                    195:                                    page->file + 1, paths->paths[i]);
                    196:                                free(mpage->file);
                    197:                                free(rp);
                    198:                                continue;
                    199:                        }
1.66      schwarze  200:                        mpage->names = buildnames(page);
1.70      schwarze  201:                        mpage->output = buildoutput(outkey, page);
1.81    ! schwarze  202:                        mpage->bits = search->firstmatch ? rp->bits : 0;
1.47      schwarze  203:                        mpage->ipath = i;
1.66      schwarze  204:                        mpage->sec = *page->sect - '0';
                    205:                        if (mpage->sec < 0 || mpage->sec > 9)
                    206:                                mpage->sec = 10;
                    207:                        mpage->form = *page->file;
                    208:                        free(rp);
                    209:                        cur++;
                    210:                }
                    211:                ohash_delete(htab);
                    212:                free(htab);
                    213:                dbm_close();
1.49      schwarze  214:
                    215:                /*
                    216:                 * In man(1) mode, prefer matches in earlier trees
                    217:                 * over matches in later trees.
                    218:                 */
                    219:
                    220:                if (cur && search->firstmatch)
                    221:                        break;
1.1       kristaps  222:        }
1.74      schwarze  223:        if (res != NULL)
                    224:                qsort(*res, cur, sizeof(struct manpage), manpage_compare);
1.57      schwarze  225:        if (chdir_status && getcwd_status && chdir(buf) == -1)
1.64      schwarze  226:                warn("%s", buf);
1.1       kristaps  227:        exprfree(e);
                    228:        *sz = cur;
1.74      schwarze  229:        return res != NULL || cur;
1.11      schwarze  230: }
                    231:
1.66      schwarze  232: /*
                    233:  * Merge the results for the expression tree rooted at e
                    234:  * into the the result list htab.
                    235:  */
                    236: static struct ohash *
                    237: manmerge(struct expr *e, struct ohash *htab)
1.45      schwarze  238: {
1.66      schwarze  239:        switch (e->type) {
                    240:        case EXPR_TERM:
                    241:                return manmerge_term(e, htab);
                    242:        case EXPR_OR:
                    243:                return manmerge_or(e->child, htab);
                    244:        case EXPR_AND:
                    245:                return manmerge_and(e->child, htab);
                    246:        default:
                    247:                abort();
1.45      schwarze  248:        }
                    249: }
                    250:
1.66      schwarze  251: static struct ohash *
                    252: manmerge_term(struct expr *e, struct ohash *htab)
1.39      schwarze  253: {
1.66      schwarze  254:        struct dbm_res   res, *rp;
                    255:        uint64_t         ib;
                    256:        unsigned int     slot;
                    257:        int              im;
                    258:
                    259:        if (htab == NULL) {
                    260:                htab = mandoc_malloc(sizeof(*htab));
                    261:                mandoc_ohash_init(htab, 4, offsetof(struct dbm_res, page));
                    262:        }
                    263:
                    264:        for (im = 0, ib = 1; im < KEY_MAX; im++, ib <<= 1) {
                    265:                if ((e->bits & ib) == 0)
                    266:                        continue;
                    267:
                    268:                switch (ib) {
                    269:                case TYPE_arch:
                    270:                        dbm_page_byarch(&e->match);
                    271:                        break;
                    272:                case TYPE_sec:
                    273:                        dbm_page_bysect(&e->match);
                    274:                        break;
                    275:                case TYPE_Nm:
                    276:                        dbm_page_byname(&e->match);
                    277:                        break;
                    278:                case TYPE_Nd:
                    279:                        dbm_page_bydesc(&e->match);
                    280:                        break;
                    281:                default:
                    282:                        dbm_page_bymacro(im - 2, &e->match);
                    283:                        break;
                    284:                }
                    285:
                    286:                /*
                    287:                 * When hashing for deduplication, use the unique
                    288:                 * page ID itself instead of a hash function;
                    289:                 * that is quite efficient.
                    290:                 */
1.39      schwarze  291:
1.66      schwarze  292:                for (;;) {
                    293:                        res = dbm_page_next();
                    294:                        if (res.page == -1)
                    295:                                break;
                    296:                        slot = ohash_lookup_memory(htab,
                    297:                            (char *)&res, sizeof(res.page), res.page);
1.81    ! schwarze  298:                        if ((rp = ohash_find(htab, slot)) != NULL) {
        !           299:                                rp->bits |= res.bits;
1.66      schwarze  300:                                continue;
1.81    ! schwarze  301:                        }
1.66      schwarze  302:                        rp = mandoc_malloc(sizeof(*rp));
                    303:                        *rp = res;
                    304:                        ohash_insert(htab, slot, rp);
                    305:                }
                    306:        }
                    307:        return htab;
1.39      schwarze  308: }
                    309:
1.66      schwarze  310: static struct ohash *
                    311: manmerge_or(struct expr *e, struct ohash *htab)
                    312: {
                    313:        while (e != NULL) {
                    314:                htab = manmerge(e, htab);
                    315:                e = e->next;
                    316:        }
                    317:        return htab;
                    318: }
1.22      schwarze  319:
1.66      schwarze  320: static struct ohash *
                    321: manmerge_and(struct expr *e, struct ohash *htab)
                    322: {
                    323:        struct ohash    *hand, *h1, *h2;
                    324:        struct dbm_res  *res;
                    325:        unsigned int     slot1, slot2;
1.22      schwarze  326:
1.66      schwarze  327:        /* Evaluate the first term of the AND clause. */
1.39      schwarze  328:
1.66      schwarze  329:        hand = manmerge(e, NULL);
1.39      schwarze  330:
1.66      schwarze  331:        while ((e = e->next) != NULL) {
1.22      schwarze  332:
1.66      schwarze  333:                /* Evaluate the next term and prepare for ANDing. */
1.22      schwarze  334:
1.66      schwarze  335:                h2 = manmerge(e, NULL);
                    336:                if (ohash_entries(h2) < ohash_entries(hand)) {
                    337:                        h1 = h2;
                    338:                        h2 = hand;
                    339:                } else
                    340:                        h1 = hand;
                    341:                hand = mandoc_malloc(sizeof(*hand));
                    342:                mandoc_ohash_init(hand, 4, offsetof(struct dbm_res, page));
1.22      schwarze  343:
1.66      schwarze  344:                /* Keep all pages that are in both result sets. */
1.22      schwarze  345:
1.66      schwarze  346:                for (res = ohash_first(h1, &slot1); res != NULL;
                    347:                    res = ohash_next(h1, &slot1)) {
                    348:                        if (ohash_find(h2, ohash_lookup_memory(h2,
                    349:                            (char *)res, sizeof(res->page),
                    350:                            res->page)) == NULL)
                    351:                                free(res);
                    352:                        else
                    353:                                ohash_insert(hand, ohash_lookup_memory(hand,
                    354:                                    (char *)res, sizeof(res->page),
                    355:                                    res->page), res);
1.22      schwarze  356:                }
                    357:
1.66      schwarze  358:                /* Discard the merged results. */
1.22      schwarze  359:
1.66      schwarze  360:                for (res = ohash_first(h2, &slot2); res != NULL;
                    361:                    res = ohash_next(h2, &slot2))
                    362:                        free(res);
                    363:                ohash_delete(h2);
                    364:                free(h2);
                    365:                ohash_delete(h1);
                    366:                free(h1);
                    367:        }
1.17      schwarze  368:
1.66      schwarze  369:        /* Merge the result of the AND into htab. */
1.17      schwarze  370:
1.66      schwarze  371:        if (htab == NULL)
                    372:                return hand;
                    373:
                    374:        for (res = ohash_first(hand, &slot1); res != NULL;
                    375:            res = ohash_next(hand, &slot1)) {
                    376:                slot2 = ohash_lookup_memory(htab,
                    377:                    (char *)res, sizeof(res->page), res->page);
                    378:                if (ohash_find(htab, slot2) == NULL)
                    379:                        ohash_insert(htab, slot2, res);
                    380:                else
                    381:                        free(res);
                    382:        }
                    383:
                    384:        /* Discard the merged result. */
1.17      schwarze  385:
1.66      schwarze  386:        ohash_delete(hand);
                    387:        free(hand);
                    388:        return htab;
                    389: }
1.54      schwarze  390:
1.66      schwarze  391: void
                    392: mansearch_free(struct manpage *res, size_t sz)
                    393: {
                    394:        size_t   i;
1.54      schwarze  395:
1.66      schwarze  396:        for (i = 0; i < sz; i++) {
                    397:                free(res[i].file);
                    398:                free(res[i].names);
                    399:                free(res[i].output);
1.22      schwarze  400:        }
1.66      schwarze  401:        free(res);
                    402: }
                    403:
                    404: static int
                    405: manpage_compare(const void *vp1, const void *vp2)
                    406: {
                    407:        const struct manpage    *mp1, *mp2;
1.75      schwarze  408:        const char              *cp1, *cp2;
                    409:        size_t                   sz1, sz2;
1.66      schwarze  410:        int                      diff;
                    411:
                    412:        mp1 = vp1;
                    413:        mp2 = vp2;
1.81    ! schwarze  414:        if ((diff = mp2->bits - mp1->bits) ||
        !           415:            (diff = mp1->sec - mp2->sec))
1.75      schwarze  416:                return diff;
                    417:
                    418:        /* Fall back to alphabetic ordering of names. */
                    419:        sz1 = strcspn(mp1->names, "(");
                    420:        sz2 = strcspn(mp2->names, "(");
                    421:        if (sz1 < sz2)
                    422:                sz1 = sz2;
                    423:        if ((diff = strncasecmp(mp1->names, mp2->names, sz1)))
                    424:                return diff;
                    425:
                    426:        /* For identical names and sections, prefer arch-dependent. */
                    427:        cp1 = strchr(mp1->names + sz1, '/');
                    428:        cp2 = strchr(mp2->names + sz2, '/');
                    429:        return cp1 != NULL && cp2 != NULL ? strcasecmp(cp1, cp2) :
                    430:            cp1 != NULL ? -1 : cp2 != NULL ? 1 : 0;
1.12      schwarze  431: }
                    432:
                    433: static char *
1.66      schwarze  434: buildnames(const struct dbm_page *page)
1.12      schwarze  435: {
1.66      schwarze  436:        char    *buf;
                    437:        size_t   i, sz;
1.12      schwarze  438:
1.70      schwarze  439:        sz = lstlen(page->name, 2) + 1 + lstlen(page->sect, 2) +
                    440:            (page->arch == NULL ? 0 : 1 + lstlen(page->arch, 2)) + 2;
1.66      schwarze  441:        buf = mandoc_malloc(sz);
                    442:        i = 0;
1.70      schwarze  443:        lstcat(buf, &i, page->name, ", ");
1.66      schwarze  444:        buf[i++] = '(';
1.70      schwarze  445:        lstcat(buf, &i, page->sect, ", ");
1.66      schwarze  446:        if (page->arch != NULL) {
                    447:                buf[i++] = '/';
1.70      schwarze  448:                lstcat(buf, &i, page->arch, ", ");
1.66      schwarze  449:        }
                    450:        buf[i++] = ')';
                    451:        buf[i++] = '\0';
                    452:        assert(i == sz);
                    453:        return buf;
1.1       kristaps  454: }
                    455:
                    456: /*
1.66      schwarze  457:  * Count the buffer space needed to print the NUL-terminated
1.70      schwarze  458:  * list of NUL-terminated strings, when printing sep separator
1.66      schwarze  459:  * characters between strings.
1.7       schwarze  460:  */
1.66      schwarze  461: static size_t
1.70      schwarze  462: lstlen(const char *cp, size_t sep)
1.7       schwarze  463: {
1.66      schwarze  464:        size_t   sz;
1.7       schwarze  465:
1.76      schwarze  466:        for (sz = 0; *cp != '\0'; cp++) {
                    467:
                    468:                /* Skip names appearing only in the SYNOPSIS. */
                    469:                if (*cp <= (char)(NAME_SYN & NAME_MASK)) {
                    470:                        while (*cp != '\0')
                    471:                                cp++;
                    472:                        continue;
                    473:                }
                    474:
                    475:                /* Skip name class markers. */
                    476:                if (*cp < ' ')
                    477:                        cp++;
                    478:
                    479:                /* Print a separator before each but the first string. */
                    480:                if (sz)
                    481:                        sz += sep;
                    482:
                    483:                /* Copy one string. */
                    484:                while (*cp != '\0') {
                    485:                        sz++;
                    486:                        cp++;
                    487:                }
1.66      schwarze  488:        }
                    489:        return sz;
1.7       schwarze  490: }
                    491:
                    492: /*
1.66      schwarze  493:  * Print the NUL-terminated list of NUL-terminated strings
1.70      schwarze  494:  * into the buffer, seperating strings with sep.
1.8       schwarze  495:  */
                    496: static void
1.70      schwarze  497: lstcat(char *buf, size_t *i, const char *cp, const char *sep)
1.8       schwarze  498: {
1.76      schwarze  499:        const char      *s;
                    500:        size_t           i_start;
1.70      schwarze  501:
1.76      schwarze  502:        for (i_start = *i; *cp != '\0'; cp++) {
                    503:
                    504:                /* Skip names appearing only in the SYNOPSIS. */
                    505:                if (*cp <= (char)(NAME_SYN & NAME_MASK)) {
                    506:                        while (*cp != '\0')
                    507:                                cp++;
                    508:                        continue;
                    509:                }
                    510:
                    511:                /* Skip name class markers. */
                    512:                if (*cp < ' ')
                    513:                        cp++;
                    514:
                    515:                /* Print a separator before each but the first string. */
                    516:                if (*i > i_start) {
1.70      schwarze  517:                        s = sep;
                    518:                        while (*s != '\0')
                    519:                                buf[(*i)++] = *s++;
1.76      schwarze  520:                }
                    521:
                    522:                /* Copy one string. */
                    523:                while (*cp != '\0')
                    524:                        buf[(*i)++] = *cp++;
1.66      schwarze  525:        }
1.76      schwarze  526:
1.8       schwarze  527: }
                    528:
1.66      schwarze  529: /*
                    530:  * Return 1 if the string *want occurs in any of the strings
                    531:  * in the NUL-terminated string list *have, or 0 otherwise.
                    532:  * If either argument is NULL or empty, assume no filtering
                    533:  * is desired and return 1.
                    534:  */
                    535: static int
                    536: lstmatch(const char *want, const char *have)
1.13      schwarze  537: {
1.66      schwarze  538:         if (want == NULL || have == NULL || *have == '\0')
                    539:                 return 1;
                    540:         while (*have != '\0') {
                    541:                 if (strcasestr(have, want) != NULL)
                    542:                         return 1;
                    543:                 have = strchr(have, '\0') + 1;
                    544:         }
                    545:         return 0;
1.13      schwarze  546: }
                    547:
1.8       schwarze  548: /*
1.70      schwarze  549:  * Build a list of values taken by the macro im in the manual page.
1.1       kristaps  550:  */
                    551: static char *
1.70      schwarze  552: buildoutput(size_t im, struct dbm_page *page)
1.1       kristaps  553: {
1.70      schwarze  554:        const char      *oldoutput, *sep, *input;
1.66      schwarze  555:        char            *output, *newoutput, *value;
1.70      schwarze  556:        size_t           sz, i;
                    557:
                    558:        switch (im) {
                    559:        case KEY_Nd:
                    560:                return mandoc_strdup(page->desc);
                    561:        case KEY_Nm:
                    562:                input = page->name;
                    563:                break;
                    564:        case KEY_sec:
                    565:                input = page->sect;
                    566:                break;
                    567:        case KEY_arch:
                    568:                input = page->arch;
                    569:                if (input == NULL)
                    570:                        input = "all\0";
                    571:                break;
                    572:        default:
                    573:                input = NULL;
                    574:                break;
                    575:        }
                    576:
                    577:        if (input != NULL) {
                    578:                sz = lstlen(input, 3) + 1;
                    579:                output = mandoc_malloc(sz);
                    580:                i = 0;
                    581:                lstcat(output, &i, input, " # ");
1.71      schwarze  582:                output[i++] = '\0';
                    583:                assert(i == sz);
1.70      schwarze  584:                return output;
                    585:        }
1.66      schwarze  586:
                    587:        output = NULL;
1.70      schwarze  588:        dbm_macro_bypage(im - 2, page->addr);
1.66      schwarze  589:        while ((value = dbm_macro_next()) != NULL) {
                    590:                if (output == NULL) {
                    591:                        oldoutput = "";
                    592:                        sep = "";
                    593:                } else {
                    594:                        oldoutput = output;
                    595:                        sep = " # ";
                    596:                }
                    597:                mandoc_asprintf(&newoutput, "%s%s%s", oldoutput, sep, value);
                    598:                free(output);
                    599:                output = newoutput;
1.1       kristaps  600:        }
1.66      schwarze  601:        return output;
1.1       kristaps  602: }
                    603:
                    604: /*
                    605:  * Compile a set of string tokens into an expression.
                    606:  * Tokens in "argv" are assumed to be individual expression atoms (e.g.,
                    607:  * "(", "foo=bar", etc.).
                    608:  */
                    609: static struct expr *
1.66      schwarze  610: exprcomp(const struct mansearch *search, int argc, char *argv[], int *argi)
1.1       kristaps  611: {
1.66      schwarze  612:        struct expr     *parent, *child;
                    613:        int              needterm, nested;
                    614:
                    615:        if ((nested = *argi) == argc)
                    616:                return NULL;
                    617:        needterm = 1;
                    618:        parent = child = NULL;
                    619:        while (*argi < argc) {
                    620:                if (strcmp(")", argv[*argi]) == 0) {
                    621:                        if (needterm)
                    622:                                warnx("missing term "
                    623:                                    "before closing parenthesis");
                    624:                        needterm = 0;
                    625:                        if (nested)
                    626:                                break;
                    627:                        warnx("ignoring unmatched right parenthesis");
                    628:                        ++*argi;
1.13      schwarze  629:                        continue;
1.66      schwarze  630:                }
                    631:                if (strcmp("-o", argv[*argi]) == 0) {
                    632:                        if (needterm) {
                    633:                                if (*argi > 0)
                    634:                                        warnx("ignoring -o after %s",
                    635:                                            argv[*argi - 1]);
                    636:                                else
                    637:                                        warnx("ignoring initial -o");
                    638:                        }
                    639:                        needterm = 1;
                    640:                        ++*argi;
1.13      schwarze  641:                        continue;
1.66      schwarze  642:                }
                    643:                needterm = 0;
                    644:                if (child == NULL) {
                    645:                        child = expr_and(search, argc, argv, argi);
1.13      schwarze  646:                        continue;
1.1       kristaps  647:                }
1.66      schwarze  648:                if (parent == NULL) {
                    649:                        parent = mandoc_calloc(1, sizeof(*parent));
                    650:                        parent->type = EXPR_OR;
                    651:                        parent->next = NULL;
                    652:                        parent->child = child;
                    653:                }
                    654:                child->next = expr_and(search, argc, argv, argi);
                    655:                child = child->next;
                    656:        }
                    657:        if (needterm && *argi)
                    658:                warnx("ignoring trailing %s", argv[*argi - 1]);
                    659:        return parent == NULL ? child : parent;
                    660: }
1.26      schwarze  661:
1.66      schwarze  662: static struct expr *
                    663: expr_and(const struct mansearch *search, int argc, char *argv[], int *argi)
                    664: {
                    665:        struct expr     *parent, *child;
                    666:        int              needterm;
1.26      schwarze  667:
1.66      schwarze  668:        needterm = 1;
                    669:        parent = child = NULL;
                    670:        while (*argi < argc) {
                    671:                if (strcmp(")", argv[*argi]) == 0) {
                    672:                        if (needterm)
                    673:                                warnx("missing term "
                    674:                                    "before closing parenthesis");
                    675:                        needterm = 0;
                    676:                        break;
                    677:                }
                    678:                if (strcmp("-o", argv[*argi]) == 0)
                    679:                        break;
                    680:                if (strcmp("-a", argv[*argi]) == 0) {
                    681:                        if (needterm) {
                    682:                                if (*argi > 0)
                    683:                                        warnx("ignoring -a after %s",
                    684:                                            argv[*argi - 1]);
                    685:                                else
                    686:                                        warnx("ignoring initial -a");
1.27      schwarze  687:                        }
1.66      schwarze  688:                        needterm = 1;
                    689:                        ++*argi;
                    690:                        continue;
1.27      schwarze  691:                }
1.66      schwarze  692:                if (needterm == 0)
                    693:                        break;
                    694:                if (child == NULL) {
                    695:                        child = exprterm(search, argc, argv, argi);
                    696:                        if (child != NULL)
                    697:                                needterm = 0;
                    698:                        continue;
                    699:                }
                    700:                needterm = 0;
                    701:                if (parent == NULL) {
                    702:                        parent = mandoc_calloc(1, sizeof(*parent));
                    703:                        parent->type = EXPR_AND;
                    704:                        parent->next = NULL;
                    705:                        parent->child = child;
                    706:                }
                    707:                child->next = exprterm(search, argc, argv, argi);
                    708:                if (child->next != NULL) {
                    709:                        child = child->next;
                    710:                        needterm = 0;
                    711:                }
                    712:        }
                    713:        if (needterm && *argi)
                    714:                warnx("ignoring trailing %s", argv[*argi - 1]);
                    715:        return parent == NULL ? child : parent;
1.15      schwarze  716: }
                    717:
                    718: static struct expr *
1.66      schwarze  719: exprterm(const struct mansearch *search, int argc, char *argv[], int *argi)
1.1       kristaps  720: {
1.15      schwarze  721:        char             errbuf[BUFSIZ];
1.1       kristaps  722:        struct expr     *e;
1.38      schwarze  723:        char            *key, *val;
1.20      schwarze  724:        uint64_t         iterbit;
1.66      schwarze  725:        int              cs, i, irc;
1.1       kristaps  726:
1.66      schwarze  727:        if (strcmp("(", argv[*argi]) == 0) {
                    728:                ++*argi;
                    729:                e = exprcomp(search, argc, argv, argi);
                    730:                if (*argi < argc) {
                    731:                        assert(strcmp(")", argv[*argi]) == 0);
                    732:                        ++*argi;
                    733:                } else
                    734:                        warnx("unclosed parenthesis");
                    735:                return e;
                    736:        }
1.1       kristaps  737:
1.72      schwarze  738:        if (strcmp("-i", argv[*argi]) == 0 && *argi + 1 < argc) {
                    739:                cs = 0;
                    740:                ++*argi;
                    741:        } else
                    742:                cs = 1;
                    743:
1.66      schwarze  744:        e = mandoc_calloc(1, sizeof(*e));
                    745:        e->type = EXPR_TERM;
                    746:        e->bits = 0;
                    747:        e->next = NULL;
                    748:        e->child = NULL;
1.1       kristaps  749:
1.45      schwarze  750:        if (search->argmode == ARG_NAME) {
                    751:                e->bits = TYPE_Nm;
1.66      schwarze  752:                e->match.type = DBM_EXACT;
                    753:                e->match.str = argv[(*argi)++];
1.58      schwarze  754:                return e;
1.5       kristaps  755:        }
                    756:
1.1       kristaps  757:        /*
1.45      schwarze  758:         * Separate macro keys from search string.
1.66      schwarze  759:         * If needed, request regular expression handling.
1.1       kristaps  760:         */
                    761:
1.45      schwarze  762:        if (search->argmode == ARG_WORD) {
                    763:                e->bits = TYPE_Nm;
1.66      schwarze  764:                e->match.type = DBM_REGEX;
1.61      schwarze  765: #if HAVE_REWB_BSD
1.66      schwarze  766:                mandoc_asprintf(&val, "[[:<:]]%s[[:>:]]", argv[*argi]);
1.61      schwarze  767: #elif HAVE_REWB_SYSV
1.66      schwarze  768:                mandoc_asprintf(&val, "\\<%s\\>", argv[*argi]);
1.61      schwarze  769: #else
                    770:                mandoc_asprintf(&val,
1.66      schwarze  771:                    "(^|[^a-zA-Z01-9_])%s([^a-zA-Z01-9_]|$)", argv[*argi]);
1.61      schwarze  772: #endif
1.46      schwarze  773:                cs = 0;
1.66      schwarze  774:        } else if ((val = strpbrk(argv[*argi], "=~")) == NULL) {
1.45      schwarze  775:                e->bits = TYPE_Nm | TYPE_Nd;
1.78      schwarze  776:                e->match.type = DBM_REGEX;
                    777:                val = argv[*argi];
                    778:                cs = 0;
1.38      schwarze  779:        } else {
1.66      schwarze  780:                if (val == argv[*argi])
1.45      schwarze  781:                        e->bits = TYPE_Nm | TYPE_Nd;
1.66      schwarze  782:                if (*val == '=') {
                    783:                        e->match.type = DBM_SUB;
                    784:                        e->match.str = val + 1;
                    785:                } else
                    786:                        e->match.type = DBM_REGEX;
1.38      schwarze  787:                *val++ = '\0';
1.66      schwarze  788:                if (strstr(argv[*argi], "arch") != NULL)
1.21      schwarze  789:                        cs = 0;
1.38      schwarze  790:        }
                    791:
                    792:        /* Compile regular expressions. */
                    793:
1.66      schwarze  794:        if (e->match.type == DBM_REGEX) {
                    795:                e->match.re = mandoc_malloc(sizeof(*e->match.re));
                    796:                irc = regcomp(e->match.re, val,
1.38      schwarze  797:                    REG_EXTENDED | REG_NOSUB | (cs ? 0 : REG_ICASE));
1.66      schwarze  798:                if (irc) {
                    799:                        regerror(irc, e->match.re, errbuf, sizeof(errbuf));
                    800:                        warnx("regcomp /%s/: %s", val, errbuf);
                    801:                }
1.45      schwarze  802:                if (search->argmode == ARG_WORD)
1.38      schwarze  803:                        free(val);
                    804:                if (irc) {
1.66      schwarze  805:                        free(e->match.re);
1.8       schwarze  806:                        free(e);
1.66      schwarze  807:                        ++*argi;
1.58      schwarze  808:                        return NULL;
1.8       schwarze  809:                }
1.38      schwarze  810:        }
                    811:
1.66      schwarze  812:        if (e->bits) {
                    813:                ++*argi;
1.58      schwarze  814:                return e;
1.66      schwarze  815:        }
1.1       kristaps  816:
                    817:        /*
                    818:         * Parse out all possible fields.
                    819:         * If the field doesn't resolve, bail.
                    820:         */
                    821:
1.66      schwarze  822:        while (NULL != (key = strsep(&argv[*argi], ","))) {
1.1       kristaps  823:                if ('\0' == *key)
                    824:                        continue;
1.66      schwarze  825:                for (i = 0, iterbit = 1; i < KEY_MAX; i++, iterbit <<= 1) {
                    826:                        if (0 == strcasecmp(key, mansearch_keynames[i])) {
1.20      schwarze  827:                                e->bits |= iterbit;
                    828:                                break;
                    829:                        }
                    830:                }
1.66      schwarze  831:                if (i == KEY_MAX) {
                    832:                        if (strcasecmp(key, "any"))
                    833:                                warnx("treating unknown key "
                    834:                                    "\"%s\" as \"any\"", key);
1.20      schwarze  835:                        e->bits |= ~0ULL;
1.1       kristaps  836:                }
                    837:        }
                    838:
1.66      schwarze  839:        ++*argi;
1.58      schwarze  840:        return e;
1.1       kristaps  841: }
                    842:
                    843: static void
1.66      schwarze  844: exprfree(struct expr *e)
1.1       kristaps  845: {
1.66      schwarze  846:        if (e->next != NULL)
                    847:                exprfree(e->next);
                    848:        if (e->child != NULL)
                    849:                exprfree(e->child);
                    850:        free(e);
1.1       kristaps  851: }

CVSweb