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] */