## root/global/global.c

/* [<][>][^][v][top][bottom][index][help] */

## DEFINITIONS

This source file includes following definitions.

1. usage
2. help
3. setcom
4. decide_tag_by_context
5. main
6. completion
7. print_count
8. idutils
9. grep
10. pathlist
11. parsefile
12. search
13. tagsearch
14. encode

    /*
     * Copyright (c) 1997, 1998, 1999, 2000, 2001, 2002, 2003, 2005, 2006,
     *      2007, 2008 Tama Communications Corporation
     *
     * This file is part of GNU GLOBAL.
     *
     * This program is free software: you can redistribute it and/or modify
     * it under the terms of the GNU General Public License as published by
     * the Free Software Foundation, either version 3 of the License, or
     * (at your option) any later version.
     *
     * This program is distributed in the hope that it will be useful,
     * but WITHOUT ANY WARRANTY; without even the implied warranty of
     * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     * GNU General Public License for more details.
     *
     * You should have received a copy of the GNU General Public License
     * along with this program.  If not, see <http://www.gnu.org/licenses/>.
     */

    #ifdef HAVE_CONFIG_H
    #include <config.h>
    #endif
    #include <sys/types.h>
    #include <sys/stat.h>
    #include <errno.h>

    #include <ctype.h>
    #include <stdio.h>
    #ifdef STDC_HEADERS
    #include <stdlib.h>
    #endif
    #ifdef HAVE_STRING_H
    #include <string.h>
    #else
    #include <strings.h>
    #endif
    #ifdef HAVE_UNISTD_H
    #include <unistd.h>
    #endif
    #include "getopt.h"

    #include "global.h"
    #include "regex.h"
    #include "const.h"

    static void usage(void);
    static void help(void);
    static void setcom(int);
    int decide_tag_by_context(const char *, const char *, const char *);
    int main(int, char **);
    void completion(const char *, const char *, const char *);
    void idutils(const char *, const char *);
    void grep(const char *, const char *);
    void pathlist(const char *, const char *);
    void parsefile(int, char **, const char *, const char *, const char *, int);
    int search(const char *, const char *, const char *, const char *, int);
    void tagsearch(const char *, const char *, const char *, const char *, int);
    void encode(char *, int, const char *);

    const char *localprefix;                /* local prefix         */
    int aflag;                              /* [option]             */
    int cflag;                              /* command              */
    int fflag;                              /* command              */
    int gflag;                              /* command              */
    int Gflag;                              /* [option]             */
    int iflag;                              /* [option]             */
    int Iflag;                              /* command              */
    int lflag;                              /* [option]             */
    int nflag;                              /* [option]             */
    int oflag;                              /* [option]             */
    int Oflag;                              /* [option]             */
    int pflag;                              /* command              */
    int Pflag;                              /* command              */
    int qflag;                              /* [option]             */
    int rflag;                              /* [option]             */
    int sflag;                              /* [option]             */
    int tflag;                              /* [option]             */
    int Tflag;                              /* [option]             */
    int uflag;                              /* command              */
    int vflag;                              /* [option]             */
    int xflag;                              /* [option]             */
    int show_version;
    int show_help;
    int nofilter;
    int nosource;                           /* undocumented command */
    int debug;
    int format;
    int type;                               /* path conversion type */
    char cwd[MAXPATHLEN+1];                 /* current directory    */
    char root[MAXPATHLEN+1];                /* root of source tree  */
    char dbpath[MAXPATHLEN+1];              /* dbpath directory     */
    char *context_file;
    char *context_lineno;

    static void
    usage(void)
    {
            if (!qflag)
                    fputs(usage_const, stderr);
            exit(2);
    }
    static void
    help(void)
    {
            fputs(usage_const, stdout);
            fputs(help_const, stdout);
            exit(0);
    }

    #define RESULT          128
    #define FROM_HERE       129
    #define SORT_FILTER     1
    #define PATH_FILTER     2
    #define BOTH_FILTER     (SORT_FILTER|PATH_FILTER)

    static struct option const long_options[] = {
            {"absolute", no_argument, NULL, 'a'},
            {"completion", no_argument, NULL, 'c'},
            {"regexp", required_argument, NULL, 'e'},
            {"file", no_argument, NULL, 'f'},
            {"local", no_argument, NULL, 'l'},
            {"nofilter", optional_argument, NULL, 'n'},
            {"grep", no_argument, NULL, 'g'},
            {"basic-regexp", no_argument, NULL, 'G'},
            {"ignore-case", no_argument, NULL, 'i'},
            {"idutils", no_argument, NULL, 'I'},
            {"other", no_argument, NULL, 'o'},
            {"only-other", no_argument, NULL, 'O'},
            {"print-dbpath", no_argument, NULL, 'p'},
            {"path", no_argument, NULL, 'P'},
            {"quiet", no_argument, NULL, 'q'},
            {"reference", no_argument, NULL, 'r'},
            {"rootdir", no_argument, NULL, 'r'},
            {"symbol", no_argument, NULL, 's'},
            {"tags", no_argument, NULL, 't'},
            {"through", no_argument, NULL, 'T'},
            {"update", no_argument, NULL, 'u'},
            {"verbose", no_argument, NULL, 'v'},
            {"cxref", no_argument, NULL, 'x'},

            /* long name only */
            {"from-here", required_argument, NULL, FROM_HERE},
            {"debug", no_argument, &debug, 1},
            {"version", no_argument, &show_version, 1},
            {"help", no_argument, &show_help, 1},
            {"result", required_argument, NULL, RESULT},
            {"nosource", no_argument, &nosource, 1},
            { 0 }
    };

    static int command;
    static void
    setcom(int c)
    {
            if (command == 0)
                    command = c;
            else if (command != c)
                    usage();
    }
    /*
     * decide_tag_by_context: decide tag type by context
     *
     *      i)      tag     tag name
     *      i)      file    context file
     *      i)      lineno  context lineno
     *      r)              GTAGS, GRTAGS, GSYMS
     */
    int
    decide_tag_by_context(const char *tag, const char *file, const char *lineno)
    {
            char path[MAXPATHLEN+1], s_fid[32];
            char rootdir[MAXPATHLEN+1];
            const char *tagline, *p;
            DBOP *dbop;
            int db = GSYMS;

            /*
             * rootdir always ends with '/'.
             */
            if (!strcmp(root, "/"))
                    strlimcpy(rootdir, root, sizeof(rootdir));
            else
                    snprintf(rootdir, sizeof(rootdir), "%s/", root);
            if (normalize(file, rootdir, cwd, path, sizeof(path)) == NULL)
                    die("'%s' is out of source tree.", file);
            /*
             * get file id
             */
            if (gpath_open(dbpath, 0) < 0)
                    die("GPATH not found.");
            if ((p = gpath_path2fid(path, NULL)) == NULL)
                    die("path name in the context is not found.");
            strlimcpy(s_fid, p, sizeof(s_fid));
            gpath_close();
            /*
             * read btree records directly to avoid the overhead.
             */
            dbop = dbop_open(makepath(dbpath, dbname(GTAGS), NULL), 0, 0, 0);
            tagline = dbop_first(dbop, tag, NULL, 0);
            if (tagline)
                    db = GTAGS;
            for (; tagline; tagline = dbop_next(dbop)) {
                    /*
                     * examine whether the definition record include the context.
                     */
                    p = locatestring(tagline, s_fid, MATCH_AT_FIRST);
                    if (p != NULL && *p == ' ') {
                            for (p++; *p && *p != ' '; p++)
                                    ;
                            if (*p++ != ' ')
                                    die("Impossible!");
                            if ((p = locatestring(p, lineno, MATCH_AT_FIRST)) != NULL && *p == ' ') {
                                    db = GRTAGS;
                                    break;
                            }
                    }
            }
            dbop_close(dbop);
            return db;
    }
    int
    main(int argc, char **argv)
    {
            const char *av = NULL;
            int db;
            int optchar;
            int option_index = 0;

            while ((optchar = getopt_long(argc, argv, "ace:ifgGIlnoOpPqrstTuvx", long_options, &option_index)) != EOF) {
                    switch (optchar) {
                    case 0:
                            break;
                    case 'a':
                            aflag++;
                            break;
                    case 'c':
                            cflag++;
                            setcom(optchar);
                            break;
                    case 'e':
                            av = optarg;
                            break;
                    case 'f':
                            fflag++;
                            xflag++;
                            setcom(optchar);
                            break;
                    case 'l':
                            lflag++;
                            break;
                    case 'n':
                            nflag++;
                            if (optarg) {
                                    if (!strcmp(optarg, "sort"))
                                            nofilter |= SORT_FILTER;
                                    else if (!strcmp(optarg, "path"))
                                            nofilter |= PATH_FILTER;
                            } else {
                                    nofilter = BOTH_FILTER;
                            }
                            break;
                    case 'g':
                            gflag++;
                            setcom(optchar);
                            break;
                    case 'G':
                            Gflag++;
                            break;
                    case 'i':
                            iflag++;
                            break;
                    case 'I':
                            Iflag++;
                            setcom(optchar);
                            break;
                    case 'o':
                            oflag++;
                            break;
                    case 'O':
                            Oflag++;
                            break;
                    case 'p':
                            pflag++;
                            setcom(optchar);
                            break;
                    case 'P':
                            Pflag++;
                            setcom(optchar);
                            break;
                    case 'q':
                            qflag++;
                            setquiet();
                            break;
                    case 'r':
                            rflag++;
                            break;
                    case 's':
                            sflag++;
                            break;
                    case 't':
                            tflag++;
                            break;
                    case 'T':
                            Tflag++;
                            break;
                    case 'u':
                            uflag++;
                            setcom(optchar);
                            break;
                    case 'v':
                            vflag++;
                            break;
                    case 'x':
                            xflag++;
                            break;
                    case FROM_HERE:
                            {
                            char *p = optarg;
                            const char *usage = "usage: global --from-here=lineno:path.";

                            context_lineno = p;
                            while (*p && isdigit(*p))
                                    p++;
                            if (*p != ':')
                                    die_with_code(2, usage);
                            *p++ = '\0';
                            if (!*p)
                                    die_with_code(2, usage);
                            context_file = p;
                            }
                            break;
                    case RESULT:
                            if (!strcmp(optarg, "ctags-x"))
                                    format = FORMAT_CTAGS_X;
                            else if (!strcmp(optarg, "ctags-xid"))
                                    format = FORMAT_CTAGS_XID;
                            else if (!strcmp(optarg, "ctags"))
                                    format = FORMAT_CTAGS;
                            else if (!strcmp(optarg, "path"))
                                    format = FORMAT_PATH;
                            else if (!strcmp(optarg, "grep"))
                                    format = FORMAT_GREP;
                            else if (!strcmp(optarg, "cscope"))
                                    format = FORMAT_CSCOPE;
                            else
                                    die_with_code(2, "unknown format type for the --result option.");
                            break;
                    default:
                            usage();
                            break;
                    }
            }
            if (qflag)
                    vflag = 0;
            if (show_help)
                    help();

            argc -= optind;
            argv += optind;
            /*
             * At first, we pickup pattern from -e option. If it is not found
             * then use argument which is not option.
             */
            if (!av)
                    av = (argc > 0) ? *argv : NULL;

            if (show_version)
                    version(av, vflag);
            /*
             * invalid options.
             */
            if (sflag && rflag)
                    die_with_code(2, "both of -s and -r are not allowed.");
            /*
             * only -c, -u, -P and -p allows no argument.
             */
            if (!av) {
                    switch (command) {
                    case 'c':
                    case 'u':
                    case 'p':
                    case 'P':
                            break;
                    default:
                            usage();
                            break;
                    }
            }
            /*
             * -u and -p cannot have any arguments.
             */
            if (av) {
                    switch (command) {
                    case 'u':
                    case 'p':
                            usage();
                    default:
                            break;
                    }
            }
            if (tflag)
                    xflag = 0;
            if (nflag > 1)
                    nosource = 1;   /* to keep compatibility */
            /*
             * remove leading blanks.
             */
            if (!Iflag && !gflag && av)
                    for (; *av == ' ' || *av == '\t'; av++)
                            ;
            if (cflag && av && isregex(av))
                    die_with_code(2, "only name char is allowed with -c option.");
            /*
             * get path of following directories.
             *      o current directory
             *      o root of source tree
             *      o dbpath directory
             *
             * if GTAGS not found, getdbpath doesn't return.
             */
            getdbpath(cwd, root, dbpath, (pflag && vflag));
            if (Iflag && !test("f", makepath(root, "ID", NULL)))
                    die("You must have idutils's index at the root of source tree.");
            /*
             * print dbpath or rootdir.
             */
            if (pflag) {
                    fprintf(stdout, "%s\n", (rflag) ? root : dbpath);
                    exit(0);
            }
            /*
             * incremental update of tag files.
             */
            if (uflag) {
                    STRBUF  *sb = strbuf_open(0);
                    char *gtags = usable("gtags");

                    if (!gtags)
                            die("gtags command not found.");
                    if (chdir(root) < 0)
                            die("cannot change directory to '%s'.", root);
                    strbuf_puts(sb, gtags);
                    strbuf_puts(sb, " -i");
                    if (vflag)
                            strbuf_putc(sb, 'v');
                    strbuf_putc(sb, ' ');
                    strbuf_puts(sb, dbpath);
                    if (system(strbuf_value(sb)))
                            exit(1);
                    strbuf_close(sb);
                    exit(0);
            }
            /*
             * complete function name
             */
            if (cflag) {
                    completion(dbpath, root, av);
                    exit(0);
            }
            /*
             * make local prefix.
             * local prefix must starts with './' and ends with '/'.
             */
            if (lflag) {
                    STRBUF *sb = strbuf_open(0);

                    strbuf_putc(sb, '.');
                    if (strcmp(root, cwd) != 0) {
                            char *p = cwd + strlen(root);
                            if (*p != '/')
                                    strbuf_putc(sb, '/');
                            strbuf_puts(sb, p);
                    }
                    strbuf_putc(sb, '/');
                    localprefix = check_strdup(strbuf_value(sb));
                    strbuf_close(sb);
    #ifdef DEBUG
                    fprintf(stderr, "root=%s\n", root);
                    fprintf(stderr, "cwd=%s\n", cwd);
                    fprintf(stderr, "localprefix=%s\n", localprefix);
    #endif
            }
            /*
             * decide tag type.
             */
            if (context_file) {
                    if (isregex(av))
                            die_with_code(2, "regular expression is not allowed with the --from-here option.");
                    db = decide_tag_by_context(av, context_file, context_lineno);
            } else {
                    db = (rflag) ? GRTAGS : ((sflag) ? GSYMS : GTAGS);
            }
            /*
             * decide format.
             * The --result option is given to priority more than the -t and -x option.
             */
            if (format == 0) {
                    if (tflag) {                    /* ctags format */
                            format = FORMAT_CTAGS;
                    } else if (xflag) {             /* print details */
                            format = FORMAT_CTAGS_X;
                    } else {                        /* print just a file name */
                            format = FORMAT_PATH;
                    }
            }
            /*
             * decide path conversion type.
             */
            if (nofilter & PATH_FILTER)
                    type = PATH_THROUGH;
            else if (aflag)
                    type = PATH_ABSOLUTE;
            else
                    type = PATH_RELATIVE;
            /*
             * exec lid(idutils).
             */
            if (Iflag) {
                    chdir(root);
                    idutils(av, dbpath);
            }
            /*
             * search pattern (regular expression).
             */
            else if (gflag) {
                    chdir(root);
                    grep(av, dbpath);
            }
            /*
             * locate paths including the pattern.
             */
            else if (Pflag) {
                    chdir(root);
                    pathlist(av, dbpath);
            }
            /*
             * parse source files.
             */
            else if (fflag) {
                    parsefile(argc, argv, cwd, root, dbpath, db);
            }
            /*
             * tag search.
             */
            else {
                    tagsearch(av, cwd, root, dbpath, db);
            }
            return 0;
    }
    /*
     * completion: print completion list of specified prefix
     *
     *      i)      dbpath  dbpath directory
     *      i)      root    root directory
     *      i)      prefix  prefix of primary key
     */
    void
    completion(const char *dbpath, const char *root, const char *prefix)
    {
            int flags = GTOP_KEY;
            GTOP *gtop;
            GTP *gtp;
            int db;

            flags |= GTOP_NOREGEX;
            if (prefix && *prefix == 0)     /* In the case global -c '' */
                    prefix = NULL;
            if (prefix)
                    flags |= GTOP_PREFIX;
            db = (sflag) ? GSYMS : GTAGS;
            gtop = gtags_open(dbpath, root, db, GTAGS_READ);
            for (gtp = gtags_first(gtop, prefix, flags); gtp; gtp = gtags_next(gtop)) {
                    fputs(gtp->tag, stdout);
                    fputc('\n', stdout);
            }
            gtags_close(gtop);
    }
    /*
     * Output filter
     *
     * (1) Old architecture (- GLOBAL-4.7.8)
     *
     * process1          process2       process3
     * +=============+  +===========+  +===========+
     * |global(write)|->|sort filter|->|path filter|->[stdout]
     * +=============+  +===========+  +===========+
     *
     * (2) Recent architecture (GLOBAL-5.0 - 5.3)
     *
     * 1 process
     * +===========================================+
     * |global(write)->[sort filter]->[path filter]|->[stdout]
     * +===========================================+
     *
     * (3) Current architecture (GLOBAL-5.4 -)
     *
     * 1 process
     * +===========================================+
     * |[sort filter]->global(write)->[path filter]|->[stdout]
     * +===========================================+
     *
     * Sort filter is implemented in gtagsop module (libutil/gtagsop.c).
     * Path filter is implemented in pathconvert module (libutil/pathconvert.c).
     */
    /*
     * print number of object.
     */
    void
    print_count(int number)
    {
            if (format == FORMAT_PATH) {
                    switch (number) {
                    case 0:
                            fprintf(stderr, "object not found");
                            break;
                    case 1:
                            fprintf(stderr, "1 file located");
                            break;
                    default:
                            fprintf(stderr, "%d files located", number);
                            break;
                    }
            } else {
                    switch (number) {
                    case 0:
                            fprintf(stderr, "object not found");
                            break;
                    case 1:
                            fprintf(stderr, "1 object located");
                            break;
                    default:
                            fprintf(stderr, "%d objects located", number);
                            break;
                    }
            }
    }
    /*
     * idutils:  lid(idutils) pattern
     *
     *      i)      pattern POSIX regular expression
     *      i)      dbpath  GTAGS directory
     */
    void
    idutils(const char *pattern, const char *dbpath)
    {
            FILE *ip;
            CONVERT *cv;
            STRBUF *ib = strbuf_open(0);
            char edit[IDENTLEN+1];
            const char *path, *lno, *lid;
            int linenum, count;
            char *p, *grep;

            lid = usable("lid");
            if (!lid)
                    die("lid(idutils) not found.");
            /*
             * convert spaces into %FF format.
             */
            encode(edit, sizeof(edit), pattern);
            /*
             * make lid command line.
             * Invoke lid with the --result=grep option to generate grep format.
             */
            strbuf_puts(ib, lid);
            strbuf_puts(ib, " --separator=newline");
            if (!tflag && !xflag)
                    strbuf_puts(ib, " --result=filenames --key=none");
            else
                    strbuf_puts(ib, " --result=grep");
            if (iflag)
                    strbuf_puts(ib, " --ignore-case");
            strbuf_putc(ib, ' ');
            strbuf_puts(ib, quote_string(pattern));
            if (debug)
                    fprintf(stderr, "idutils: %s\n", strbuf_value(ib));
            if (!(ip = popen(strbuf_value(ib), "r")))
                    die("cannot execute '%s'.", strbuf_value(ib));
            cv = convert_open(type, format, root, cwd, dbpath, stdout);
            count = 0;
            while ((grep = strbuf_fgets(ib, ip, STRBUF_NOCRLF)) != NULL) {
                    p = grep;
                    /* extract path name */
                    path = p;
                    while (*p && *p != ':')
                            p++;
                    if ((xflag || tflag) && !*p)
                            die("invalid lid(idutils) output format. '%s'", grep);
                    *p++ = 0;
                    /* normalize path name */
                    path = makepath(".", path, NULL);
                    if (lflag) {
                            if (!locatestring(path, localprefix, MATCH_AT_FIRST))
                                    continue;
                    }
                    count++;
                    switch (format) {
                    case FORMAT_PATH:
                            convert_put(cv, path);
                            break;
                    default:
                            /* extract line number */
                            while (*p && isspace(*p))
                                    p++;
                            lno = p;
                            while (*p && isdigit(*p))
                                    p++;
                            if (*p != ':')
                                    die("invalid lid(idutils) output format. '%s'", grep);
                            *p++ = 0;
                            linenum = atoi(lno);
                            if (linenum <= 0)
                                    die("invalid lid(idutils) output format. '%s'", grep);
                            /*
                             * print out.
                             */
                            convert_put_using(cv, edit, path, linenum, p);
                            break;
                    }
            }
            if (pclose(ip) < 0)
                    die("terminated abnormally.");
            convert_close(cv);
            strbuf_close(ib);
            if (vflag) {
                    print_count(count);
                    fprintf(stderr, " (using idutils index in '%s').\n", dbpath);
            }
    }
    /*
     * grep: grep pattern
     *
     *      i)      pattern POSIX regular expression
     */
    void
    grep(const char *pattern, const char *dbpath)
    {
            FILE *fp;
            CONVERT *cv;
            GFIND *gp;
            STRBUF *ib = strbuf_open(MAXBUFLEN);
            const char *path;
            char edit[IDENTLEN+1];
            const char *buffer;
            int linenum, count;
            int flags = 0;
            int target = GPATH_SOURCE;
            regex_t preg;

            /*
             * convert spaces into %FF format.
             */
            encode(edit, sizeof(edit), pattern);

            if (oflag)
                    target = GPATH_BOTH;
            if (Oflag)
                    target = GPATH_OTHER;
            if (!Gflag)
                    flags |= REG_EXTENDED;
            if (iflag)
                    flags |= REG_ICASE;
            if (regcomp(&preg, pattern, flags) != 0)
                    die("invalid regular expression.");
            cv = convert_open(type, format, root, cwd, dbpath, stdout);
            count = 0;

            gp = gfind_open(dbpath, localprefix, target);
            while ((path = gfind_read(gp)) != NULL) {
                    if (!(fp = fopen(path, "r")))
                            die("'%s' not found. Please remake tag files by invoking gtags(1).", path);
                    linenum = 0;
                    while ((buffer = strbuf_fgets(ib, fp, STRBUF_NOCRLF)) != NULL) {
                            linenum++;
                            if (regexec(&preg, buffer, 0, 0, 0) == 0) {
                                    count++;
                                    if (format == FORMAT_PATH) {
                                            convert_put(cv, path);
                                            break;
                                    } else {
                                            convert_put_using(cv, edit, path, linenum, buffer);
                                    }
                            }
                    }
                    fclose(fp);
            }
            gfind_close(gp);
            convert_close(cv);
            strbuf_close(ib);
            regfree(&preg);
            if (vflag) {
                    print_count(count);
                    fprintf(stderr, " (no index used).\n");
            }
    }
    /*
     * pathlist: print candidate path list.
     *
     *      i)      dbpath
     */
    void
    pathlist(const char *pattern, const char *dbpath)
    {
            GFIND *gp;
            CONVERT *cv;
            const char *path, *p;
            regex_t preg;
            int count;
            int target = GPATH_SOURCE;

            if (oflag)
                    target = GPATH_BOTH;
            if (Oflag)
                    target = GPATH_OTHER;
            if (pattern) {
                    int flags = 0;
                    char edit[IDENTLEN+1];

                    if (!Gflag)
                            flags |= REG_EXTENDED;
                    if (iflag || getconfb("icase_path"))
                            flags |= REG_ICASE;
    #ifdef _WIN32
                    flags |= REG_ICASE;
    #endif /* _WIN32 */
                    /*
                     * We assume '^aaa' as '^/aaa'.
                     */
                    if (*pattern == '^' && *(pattern + 1) != '/') {
                            snprintf(edit, sizeof(edit), "^/%s", pattern + 1);
                            pattern = edit;
                    }
                    if (regcomp(&preg, pattern, flags) != 0)
                            die("invalid regular expression.");
            }
            if (!localprefix)
                    localprefix = "./";
            cv = convert_open(type, format, root, cwd, dbpath, stdout);
            count = 0;

            gp = gfind_open(dbpath, localprefix, target);
            while ((path = gfind_read(gp)) != NULL) {
                    /*
                     * skip localprefix because end-user doesn't see it.
                     */
                    p = path + strlen(localprefix) - 1;
                    if (pattern && regexec(&preg, p, 0, 0, 0) != 0)
                            continue;
                    if (format == FORMAT_PATH)
                            convert_put(cv, path);
                    else
                            convert_put_using(cv, "path", path, 1, " ");
                    count++;
            }
            gfind_close(gp);
            convert_close(cv);
            if (pattern)
                    regfree(&preg);
            if (vflag) {
                    switch (count) {
                    case 0:
                            fprintf(stderr, "file not found");
                            break;
                    case 1:
                            fprintf(stderr, "%d file located", count);
                            break;
                    default:
                            fprintf(stderr, "%d files located", count);
                            break;
                    }
                    fprintf(stderr, " (using '%s').\n", makepath(dbpath, dbname(GPATH), NULL));
            }
    }
    /*
     * parsefile: parse file to pick up tags.
     *
     *      i)      argc
     *      i)      argv
     *      i)      cwd     current directory
     *      i)      root    root directory of source tree
     *      i)      dbpath  dbpath
     *      i)      db      type of parse
     */
    void
    parsefile(int argc, char **argv, const char *cwd, const char *root, const char *dbpath, int db)
    {
            CONVERT *cv;
            int count = 0;
            int file_count = 0;
            STRBUF *comline = strbuf_open(0);
            STRBUF *path_list = strbuf_open(MAXPATHLEN);
            XARGS *xp;
            char *ctags_x;
            char rootdir[MAXPATHLEN+1];

            /*
             * rootdir always ends with '/'.
             */
            if (!strcmp(root, "/"))
                    strlimcpy(rootdir, root, sizeof(rootdir));
            else
                    snprintf(rootdir, sizeof(rootdir), "%s/", root);
            /*
             * teach parser where is dbpath.
             */
            set_env("GTAGSDBPATH", dbpath);
            /*
             * teach parser language mapping.
             */
            {
                    STRBUF *sb = strbuf_open(0);

                    if (getconfs("langmap", sb))
                            set_env("GTAGSLANGMAP", strbuf_value(sb));
                    strbuf_close(sb);
            }
            /*
             * get parser.
             */
            if (!getconfs(dbname(db), comline))
                    die("cannot get parser for %s.", dbname(db));
            cv = convert_open(type, format, root, cwd, dbpath, stdout);
            if (gpath_open(dbpath, 0) < 0)
                    die("GPATH not found.");
            /*
             * Make a path list while checking the validity of path name.
             */
            for (; argc > 0; argv++, argc--) {
                    const char *av = argv[0];
                    char path[MAXPATHLEN+1];

                    /*
                     * convert the path into relative from the root directory of source tree.
                     */
                    if (normalize(av, rootdir, cwd, path, sizeof(path)) == NULL)
                            if (!qflag)
                                    fprintf(stderr, "'%s' is out of source tree.\n", path + 2);
                    if (!gpath_path2fid(path, NULL)) {
                            if (!qflag)
                                    fprintf(stderr, "'%s' not found in GPATH.\n", path + 2);
                            continue;
                    }
                    if (!test("f", makepath(root, path, NULL))) {
                            if (test("d", NULL)) {
                                    if (!qflag)
                                            fprintf(stderr, "'%s' is a directory.\n", av);
                            } else {
                                    if (!qflag)
                                            fprintf(stderr, "'%s' not found.\n", av);
                            }
                            continue;
                    }
                    if (lflag && !locatestring(path, localprefix, MATCH_AT_FIRST))
                            continue;
                    /*
                     * Add a path to the path list.
                     */
                    strbuf_puts0(path_list, path);
                    file_count++;
            }
            if (file_count > 0) {
                    /*
                     * Execute parser in the root directory of source tree.
                     */
                    if (chdir(root) < 0)
                            die("cannot move to '%s' directory.", root);
                    xp = xargs_open_with_strbuf(strbuf_value(comline), 0, path_list);
                    if (format == FORMAT_PATH) {
                            SPLIT ptable;
                            char curpath[MAXPATHLEN+1];

                            curpath[0] = '\0';
                            while ((ctags_x = xargs_read(xp)) != NULL) {
                                    if (split((char *)ctags_x, 4, &ptable) < 4) {
                                            recover(&ptable);
                                            die("too small number of parts.\n'%s'", ctags_x);
                                    }
                                    if (strcmp(curpath, ptable.part[PART_PATH].start)) {
                                            strlimcpy(curpath, ptable.part[PART_PATH].start, sizeof(curpath));
                                            convert_put(cv, curpath);
                                            count++;
                                    }
                            }
                    } else {
                            while ((ctags_x = xargs_read(xp)) != NULL) {
                                    convert_put(cv, ctags_x);
                                    count++;
                            }
                    }
                    xargs_close(xp);
                    if (chdir(cwd) < 0)
                            die("cannot move to '%s' directory.", cwd);
            }
            /*
             * Settlement
             */
            gpath_close();
            convert_close(cv);
            strbuf_close(comline);
            strbuf_close(path_list);
            if (file_count == 0)
                    die("file not found.");
            if (vflag) {
                    print_count(count);
                    fprintf(stderr, " (no index used).\n");
            }
    }
    /*
     * search: search specified function
     *
     *      i)      pattern         search pattern
     *      i)      root            root of source tree
     *      i)      cwd             current directory
     *      i)      dbpath          database directory
     *      i)      db              GTAGS,GRTAGS,GSYMS
     *      r)                      count of output lines
     */
    /* get next number and seek to the next character */
    #define GET_NEXT_NUMBER(p) do {                                                 \
                    if (!isdigit(*p))                                              \
                            p++;                                                    \
                    for (n = 0; isdigit(*p); p++)                                  \
                            n = n * 10 + (*p - '0');                                \
            } while (0)
    int
    search(const char *pattern, const char *root, const char *cwd, const char *dbpath, int db)
    {
            CONVERT *cv;
            int count = 0;
            GTOP *gtop;
            GTP *gtp;
            int flags = 0;
            STRBUF *sb = NULL, *ib = NULL;
            char curpath[MAXPATHLEN+1], curtag[IDENTLEN+1];
            FILE *fp = NULL;
            const char *src = "";
            int lineno, last_lineno;

            lineno = last_lineno = 0;
            curpath[0] = curtag[0] = '\0';
            /*
             * open tag file.
             */
            gtop = gtags_open(dbpath, root, db, GTAGS_READ);
            cv = convert_open(type, format, root, cwd, dbpath, stdout);
            /*
             * search through tag file.
             */
            if (nofilter & SORT_FILTER)
                    flags |= GTOP_NOSORT;
            if (iflag) {
                    if (!isregex(pattern)) {
                            sb = strbuf_open(0);
                            strbuf_putc(sb, '^');
                            strbuf_puts(sb, pattern);
                            strbuf_putc(sb, '$');
                            pattern = strbuf_value(sb);
                    }
                    flags |= GTOP_IGNORECASE;
            }
            if (Gflag)
                    flags |= GTOP_BASICREGEX;
            if (format == FORMAT_PATH)
                    flags |= GTOP_PATH;
            if (gtop->format & GTAGS_COMPACT)
                    ib = strbuf_open(0);
            for (gtp = gtags_first(gtop, pattern, flags); gtp; gtp = gtags_next(gtop)) {
                    if (lflag && !locatestring(gtp->path, localprefix, MATCH_AT_FIRST))
                            continue;
                    if (format == FORMAT_PATH) {
                            convert_put(cv, gtp->path);
                            count++;
                    } else if (gtop->format & GTAGS_COMPACT) {
                            /*
                             *                    a          b
                             * tagline = <file id> <tag name> <line no>,...
                             */
                            char *p = (char *)gtp->tagline;
                            const char *tagname;
                            int n = 0;

                            while (*p != ' ')
                                    p++;
                            *p++ = '\0';                    /* a */
                            tagname = p;
                            while (*p != ' ')
                                    p++;
                            *p++ = '\0';                    /* b */
                            /*
                             * Reopen or rewind source file.
                             */
                            if (!nosource) {
                                    if (strcmp(gtp->path, curpath) != 0) {
                                            if (curpath[0] != '\0' && fp != NULL)
                                                    fclose(fp);
                                            strlimcpy(curtag, tagname, sizeof(curtag));
                                            strlimcpy(curpath, gtp->path, sizeof(curpath));
                                            /*
                                             * Use absolute path name to support GTAGSROOT
                                             * environment variable.
                                             */
                                            fp = fopen(makepath(root, curpath, NULL), "r");
                                            if (fp == NULL)
                                                    warning("source file '%s' is not available.", curpath);
                                            last_lineno = lineno = 0;
                                    } else if (strcmp(gtp->tag, curtag) != 0) {
                                            strlimcpy(curtag, gtp->tag, sizeof(curtag));
                                            if (atoi(p) < last_lineno && fp != NULL) {
                                                    rewind(fp);
                                                    lineno = 0;
                                            }
                                            last_lineno = 0;
                                    }
                            }
                            /*
                             * Unfold compact format.
                             *
                             * If GTAGS_COMPLINE flag is set, each line number is expressed as
                             * the difference from the previous line number except for the head.
                             * Please see flush_pool() in libutil/gtagsop.c for the details.
                             */
                            if (!isdigit(*p))
                                    die("illegal compact format.");
                            if (gtop->format & GTAGS_COMPLINE) {
                                    int last = 0, cont = 0;

                                    while (*p || cont > 0) {
                                            if (cont > 0) {
                                                    n = last + 1;
                                                    if (n > cont) {
                                                            cont = 0;
                                                            continue;
                                                    }
                                            } else if (isdigit(*p)) {
                                                    GET_NEXT_NUMBER(p);
                                            }  else if (*p == '-') {
                                                    GET_NEXT_NUMBER(p);
                                                    cont = n + last;
                                                    n = last + 1;
                                            } else if (*p == ',') {
                                                    GET_NEXT_NUMBER(p);
                                                    n += last;
                                            }
                                            if (last_lineno != n && fp) {
                                                    while (lineno < n) {
                                                            if (!(src = strbuf_fgets(ib, fp, STRBUF_NOCRLF)))
                                                                    die("unexpected end of file. '%s: %d/%d'", gtp->path, lineno, n);
                                                            lineno++;
                                                    }
                                            }
                                            if (gtop->format & GTAGS_COMPNAME)
                                                    tagname = (char *)uncompress(tagname, gtp->tag);
                                            convert_put_using(cv, tagname, gtp->path, n, src);
                                            count++;
                                            last_lineno = last = n;
                                    }
                            } else {
                                    while (*p) {
                                            for (n = 0; isdigit(*p); p++)
                                                    n = n * 10 + *p - '0';
                                            if (*p == ',')
                                                    p++;
                                            if (last_lineno == n)
                                                    continue;
                                            if (last_lineno != n && fp) {
                                                    while (lineno < n) {
                                                            if (!(src = strbuf_fgets(ib, fp, STRBUF_NOCRLF)))
                                                                    die("unexpected end of file. '%s: %d/%d'", gtp->path, lineno, n);
                                                            lineno++;
                                                    }
                                            }
                                            if (gtop->format & GTAGS_COMPNAME)
                                                    tagname = (char *)uncompress(tagname, gtp->tag);
                                            convert_put_using(cv, tagname, gtp->path, n, src);
                                            count++;
                                            last_lineno = n;
                                    }
                            }
                    } else {
                            /*
                             *                    a          b         c
                             * tagline = <file id> <tag name> <line no> <line image>
                             */
                            char *p = (char *)gtp->tagline;
                            char namebuf[IDENTLEN+1];
                            const char *tagname, *image;

                            while (*p != ' ')
                                    p++;
                            *p++ = '\0';                    /* a */
                            tagname = p;
                            while (*p != ' ')
                                    p++;
                            *p++ = '\0';                    /* b */
                            if (gtop->format & GTAGS_COMPNAME) {
                                    strlimcpy(namebuf, (char *)uncompress(tagname, gtp->tag), sizeof(namebuf));
                                    tagname = namebuf;
                            }
                            if (nosource) {
                                    image = " ";
                            } else {
                                    while (*p != ' ')
                                            p++;
                                    image = p + 1;          /* c + 1 */
                                    if (gtop->format & GTAGS_COMPRESS)
                                            image = (char *)uncompress(image, gtp->tag);
                            }
                            convert_put_using(cv, tagname, gtp->path, gtp->lineno, image);
                            count++;
                    }
            }
            convert_close(cv);
            if (sb)
                    strbuf_close(sb);
            if (ib)
                    strbuf_close(ib);
            if (fp)
                    fclose(fp);
            gtags_close(gtop);
            return count;
    }
    /*
     * tagsearch: execute tag search
     *
     *      i)      pattern         search pattern
     *      i)      cwd             current directory
     *      i)      root            root of source tree
     *      i)      dbpath          database directory
     *      i)      db              GTAGS,GRTAGS,GSYMS
     */
    void
    tagsearch(const char *pattern, const char *cwd, const char *root, const char *dbpath, int db)
    {
            int count, total = 0;
            char libdbpath[MAXPATHLEN+1];

            /*
             * search in current source tree.
             */
            count = search(pattern, root, cwd, dbpath, db);
            total += count;
            /*
             * search in library path.
             */
            if (db == GTAGS && getenv("GTAGSLIBPATH") && (count == 0 || Tflag) && !lflag) {
                    STRBUF *sb = strbuf_open(0);
                    char *libdir, *nextp = NULL;

                    strbuf_puts(sb, getenv("GTAGSLIBPATH"));
                    /*
                     * search for each tree in the library path.
                     */
                    for (libdir = strbuf_value(sb); libdir; libdir = nextp) {
                            if ((nextp = locatestring(libdir, PATHSEP, MATCH_FIRST)) != NULL)
                                    *nextp++ = 0;
                            if (!gtagsexist(libdir, libdbpath, sizeof(libdbpath), 0))
                                    continue;
                            if (!strcmp(dbpath, libdbpath))
                                    continue;
                            if (!test("f", makepath(libdbpath, dbname(db), NULL)))
                                    continue;
                            /*
                             * search again
                             */
                            count = search(pattern, libdir, cwd, libdbpath, db);
                            total += count;
                            if (count > 0 && !Tflag) {
                                    /* for verbose message */
                                    dbpath = libdbpath;
                                    break;
                            }
                    }
                    strbuf_close(sb);
            }
            if (vflag) {
                    print_count(total);
                    if (!Tflag)
                            fprintf(stderr, " (using '%s')", makepath(dbpath, dbname(db), NULL));
                    fputs(".\n", stderr);
            }
    }
    /*
     * encode: string copy with converting blank chars into %ff format.
     *
     *      o)      to      result
     *      i)      size    size of 'to' buffer
     *      i)      from    string
     */
    void
    encode(char *to, int size, const char *from)
    {
            const char *p;
            char *e = to;

            for (p = from; *p; p++) {
                    if (*p == '%' || *p == ' ' || *p == '\t') {
                            if (size <= 3)
                                    break;
                            snprintf(e, size, "%%%02x", *p);
                            e += 3;
                            size -= 3;
                    } else {
                            if (size <= 1)
                                    break;
                            *e++ = *p;
                            size--;
                    }
            }
            *e = 0;
    }

/* [<][>][^][v][top][bottom][index][help] */