## root/libutil/find.c(117.html)

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

## DEFINITIONS

This source file includes following definitions.

1. trim
2. prepare_source
3. prepare_skip
4. skipthisfile
5. getdirs
6. find_open
7. find_open_filelist
8. find_read
9. find_read_traverse
10. find_read_filelist
11. find_close

    /*
     * Copyright (c) 1997, 1998, 1999, 2000, 2001, 2002, 2005, 2006, 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 <assert.h>
    #include <ctype.h>
    #ifdef HAVE_DIRENT_H
    #include <sys/types.h>
    #include <dirent.h>
    #include <sys/stat.h>
    #endif
    #ifdef HAVE_LIMITS_H
    #include <limits.h>
    #endif
    #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 "gparam.h"
    #include "regex.h"

    #include "abs2rel.h"
    #include "char.h"
    #include "checkalloc.h"
    #include "conf.h"
    #include "die.h"
    #include "find.h"
    #include "is_unixy.h"
    #include "locatestring.h"
    #include "makepath.h"
    #include "path.h"
    #include "strbuf.h"
    #include "strlimcpy.h"
    #include "test.h"

    /*
     * usage of find_xxx()
     *
     *      find_open(NULL);
     *      while (path = find_read()) {
     *              ...
     *      }
     *      find_close();
     *
     */
    static regex_t skip_area;
    static regex_t *skip;                   /* regex for skipping units */
    static regex_t suff_area;
    static regex_t *suff = &suff_area;      /* regex for suffixes */
    static STRBUF *list;
    static int list_count;
    static char **listarray;                /* list for skipping full path */
    static FILE *ip;
    static FILE *temp;
    static char rootdir[MAXPATHLEN+1];
    static char cwddir[MAXPATHLEN+1];
    static int status;
    #define FIND_OPEN       1
    #define FILELIST_OPEN   2
    #define END_OF_FIND     3

    static void trim(char *);
    static char *find_read_traverse(void);
    static char *find_read_filelist(void);

    extern int qflag;
    #ifdef DEBUG
    extern int debug;
    #endif
    /*
     * trim: remove blanks and '\'.
     */
    static void
    trim(char *s)
    {
            char *p;

            for (p = s; *s; s++) {
                    if (isspace((unsigned char)*s))
                            continue;
                    if (*s == '\\' && *(s + 1))
                            s++;
                    *p++ = *s;
            }
            *p = 0;
    }
    /*
     * prepare_source: preparing regular expression.
     *
     *      i)      flags   flags for regcomp.
     *      go)     suff    regular expression for source files.
     */
    static void
    prepare_source(void)
    {
            STRBUF *sb = strbuf_open(0);
            char *sufflist = NULL;
            int flags = REG_EXTENDED;

            /*
             * load icase_path option.
             */
            if (getconfb("icase_path"))
                    flags |= REG_ICASE;
    #if defined(_WIN32) || defined(__DJGPP__)
            flags |= REG_ICASE;
    #endif
            strbuf_reset(sb);
            if (!getconfs("suffixes", sb))
                    die("cannot get suffixes data.");
            sufflist = check_strdup(strbuf_value(sb));
            trim(sufflist);
            {
                    const char *suffp;
                    int retval;

                    strbuf_reset(sb);
                    strbuf_puts(sb, "\\.(");       /* ) */
                    for (suffp = sufflist; suffp; ) {
                            const char *p;

                            for (p = suffp; *p && *p != ','; p++) {
                                    if (!isalnum((unsigned char)*p))
                                            strbuf_putc(sb, '\\');
                                    strbuf_putc(sb, *p);
                            }
                            if (!*p)
                                    break;
                            assert(*p == ',');
                            strbuf_putc(sb, '|');
                            suffp = ++p;
                    }
                    strbuf_puts(sb, ")$");
                    /*
                     * compile regular expression.
                     */
                    retval = regcomp(suff, strbuf_value(sb), flags);
    #ifdef DEBUG
                    if (debug)
                            fprintf(stderr, "find regex: %s\n", strbuf_value(sb));
    #endif
                    if (retval != 0)
                            die("cannot compile regular expression.");
            }
            strbuf_close(sb);
            if (sufflist)
                    free(sufflist);
    }
    /*
     * prepare_skip: prepare skipping files.
     *
     *      go)     skip    regular expression for skip files.
     *      go)     listarry[] skip list.
     *      go)     list_count count of skip list.
     */
    static void
    prepare_skip(void)
    {
            char *skiplist;
            STRBUF *reg = strbuf_open(0);
            int reg_count = 0;
            char *p, *q;
            int flags = REG_EXTENDED|REG_NEWLINE;

            /*
             * load icase_path option.
             */
            if (getconfb("icase_path"))
                    flags |= REG_ICASE;
    #if defined(_WIN32) || defined(__DJGPP__)
            flags |= REG_ICASE;
    #endif
            /*
             * initinalize common data.
             */
            if (!list)
                    list = strbuf_open(0);
            else
                    strbuf_reset(list);
            list_count = 0;
            if (listarray)
                    (void)free(listarray);
            listarray = (char **)0;
            /*
             * load skip data.
             */
            if (!getconfs("skip", reg)) {
                    strbuf_close(reg);
                    return;
            }
            skiplist = check_strdup(strbuf_value(reg));
            trim(skiplist);
            strbuf_reset(reg);
            /*
             * construct regular expression.
             */
            strbuf_putc(reg, '(');  /* ) */
            for (p = skiplist; p; ) {
                    char *skipf = p;
                    if ((p = locatestring(p, ",", MATCH_FIRST)) != NULL)
                            *p++ = 0;
                    if (*skipf == '/') {
                            list_count++;
                            strbuf_puts0(list, skipf);
                    } else {
                            reg_count++;
                            strbuf_putc(reg, '/');
                            for (q = skipf; *q; q++) {
                                    if (isregexchar(*q))
                                            strbuf_putc(reg, '\\');
                                    strbuf_putc(reg, *q);
                            }
                            if (*(q - 1) != '/')
                                    strbuf_putc(reg, '$');
                            if (p)
                                    strbuf_putc(reg, '|');
                    }
            }
            strbuf_unputc(reg, '|');
            strbuf_putc(reg, ')');
            if (reg_count > 0) {
                    int retval;

                    /*
                     * compile regular expression.
                     */
                    skip = &skip_area;
                    retval = regcomp(skip, strbuf_value(reg), flags);
    #ifdef DEBUG
                    if (debug)
                            fprintf(stderr, "skip regex: %s\n", strbuf_value(reg));
    #endif
                    if (retval != 0)
                            die("cannot compile regular expression.");
            } else {
                    skip = (regex_t *)0;
            }
            if (list_count > 0) {
                    int i;
                    listarray = (char **)check_malloc(sizeof(char *) * list_count);
                    p = strbuf_value(list);
    #ifdef DEBUG
                    if (debug)
                            fprintf(stderr, "skip list: ");
    #endif
                    for (i = 0; i < list_count; i++) {
    #ifdef DEBUG
                            if (debug) {
                                    fprintf(stderr, "%s", p);
                                    if (i + 1 < list_count)
                                            fputc(',', stderr);
                            }
    #endif
                            listarray[i] = p;
                            p += strlen(p) + 1;
                    }
    #ifdef DEBUG
                    if (debug)
                            fputc('\n', stderr);
    #endif
            }
            strbuf_close(reg);
            free(skiplist);
    }
    /*
     * skipthisfile: check whether or not we accept this file.
     *
     *      i)      path    path name (must start with ./)
     *      r)              1: skip, 0: dont skip
     */
    static int
    skipthisfile(const char *path)
    {
            const char *first, *last;
            int i;

            /*
             * unit check.
             */
            if (skip && regexec(skip, path, 0, 0, 0) == 0)
                    return 1;
            /*
             * list check.
             */
            if (list_count == 0)
                    return 0;
            for (i = 0; i < list_count; i++) {
                    first = listarray[i];
                    last = first + strlen(first);
                    /*
                     * the path must start with "./".
                     */
                    if (*(last - 1) == '/') {       /* it's a directory */
                            if (!strncmp(path + 1, first, last - first))
                                    return 1;
                    } else {
                            if (!strcmp(path + 1, first))
                                    return 1;
                    }
            }
            return 0;
    }

    #define STACKSIZE 50
    static  char dir[MAXPATHLEN+1];                 /* directory path */
    static  struct {
            STRBUF *sb;
            char *dirp, *start, *end, *p;
    } stack[STACKSIZE], *topp, *curp;               /* stack */

    /*
     * getdirs: get directory list
     *
     *      i)      dir     directory
     *      o)      sb      string buffer
     *      r)              -1: error, 0: normal
     *
     * format of directory list:
     * |ddir1\0ffile1\0llink\0|
     * means directory 'dir1', file 'file1' and symbolic link 'link'.
     */
    static int
    getdirs(const char *dir, STRBUF *sb)
    {
            DIR *dirp;
            struct dirent *dp;
            struct stat st;

            if ((dirp = opendir(dir)) == NULL)
                    return -1;
            while ((dp = readdir(dirp)) != NULL) {
                    if (!strcmp(dp->d_name, "."))
                            continue;
                    if (!strcmp(dp->d_name, ".."))
                            continue;
                    if (stat(makepath(dir, dp->d_name, NULL), &st) < 0) {
                            warning("cannot stat '%s'. (Ignored)", dp->d_name);
                            continue;
                    }
                    if (S_ISDIR(st.st_mode))
                            strbuf_putc(sb, 'd');
                    else if (S_ISREG(st.st_mode))
                            strbuf_putc(sb, 'f');
                    else
                            strbuf_putc(sb, ' ');
                    strbuf_puts(sb, dp->d_name);
                    strbuf_putc(sb, '\0');
            }
            (void)closedir(dirp);
            return 0;
    }
    /*
     * find_open: start iterator without GPATH.
     *
     *      i)      start   start directory
     *                      If NULL, assumed '.' directory.
     */
    void
    find_open(const char *start)
    {
            assert(status == 0);
            status = FIND_OPEN;

            if (!start)
                    start = ".";
            /*
             * setup stack.
             */
            curp = &stack[0];
            topp = curp + STACKSIZE;
            strlimcpy(dir, start, sizeof(dir));
            curp->dirp = dir + strlen(dir);
            curp->sb = strbuf_open(0);
            if (getdirs(dir, curp->sb) < 0)
                    die("cannot open '.' directory.");
            curp->start = curp->p = strbuf_value(curp->sb);
            curp->end   = curp->start + strbuf_getlen(curp->sb);

            /*
             * prepare regular expressions.
             */
            prepare_source();
            prepare_skip();
    }
    /*
     * find_open_filelist: find_open like interface for handling output of find(1).
     *
     *      i)      filename        file including list of file names.
     *                              When "-" is specified, read from standard input.
     *      i)      root            root directory of source tree
     */
    void
    find_open_filelist(const char *filename, const char *root)
    {
            assert(status == 0);
            status = FILELIST_OPEN;

            if (!strcmp(filename, "-")) {
                    /*
                     * If the filename is '-', copy standard input onto
                     * temporary file to be able to read repeatedly.
                     */
                    if (temp == NULL) {
                            char buf[MAXPATHLEN+1];

                            temp = tmpfile();
                            while (fgets(buf, sizeof(buf), stdin) != NULL)
                                    fputs(buf, temp);
                    }
                    rewind(temp);
                    ip = temp;
            } else {
                    ip = fopen(filename, "r");
                    if (ip == NULL)
                            die("cannot open '%s'.", filename);
            }
            /*
             * rootdir always ends with '/'.
             */
            if (!strcmp(root, "/"))
                    strlimcpy(rootdir, root, sizeof(rootdir));
            else
                    snprintf(rootdir, sizeof(rootdir), "%s/", root);
            strlimcpy(cwddir, root, sizeof(cwddir));
            /*
             * prepare regular expressions.
             */
            prepare_skip();
            prepare_source();
    }
    /*
     * find_read: read path without GPATH.
     *
     *      r)              path
     */
    char *
    find_read(void)
    {
            static char *path;

            assert(status != 0);
            if (status == END_OF_FIND)
                    path = NULL;
            else if (status == FILELIST_OPEN)
                    path = find_read_filelist();
            else if (status == FIND_OPEN)
                    path = find_read_traverse();
            else
                    die("find_read: internal error.");
            return path;
    }
    /*
     * find_read_traverse: read path without GPATH.
     *
     *      r)              path
     */
    char *
    find_read_traverse(void)
    {
            static char val[MAXPATHLEN+1];

            for (;;) {
                    while (curp->p < curp->end) {
                            char type = *(curp->p);
                            const char *unit = curp->p + 1;

                            curp->p += strlen(curp->p) + 1;
                            if (type == 'f') {
                                    char path[MAXPATHLEN];

                                    /* makepath() returns unsafe module local area. */
                                    strlimcpy(path, makepath(dir, unit, NULL), sizeof(path));
                                    if (skipthisfile(path))
                                            continue;
                                    /*
                                     * Skip the following:
                                     * o directory
                                     * o file which does not exist
                                     * o dead symbolic link
                                     */
                                    if (!test("f", path)) {
                                            if (!qflag) {
                                                    if (test("d", path))
                                                            warning("'%s' is a directory. (Ignored)", path);
                                                    else
                                                            warning("'%s' not found. (Ignored)", path);
                                            }
                                            continue;
                                    }
                                    /*
                                     * GLOBAL cannot treat path which includes blanks.
                                     * It will be improved in the future.
                                     */
                                    if (locatestring(path, " ", MATCH_FIRST)) {
                                            if (!qflag)
                                                    warning("'%s' ignored, because it includes blank.", &path[2]);
                                            continue;
                                    }
                                    /*
                                     * A blank at the head of path means
                                     * other than source file.
                                     */
                                    if (regexec(suff, path, 0, 0, 0) == 0) {
                                            /* source file */
                                            strlimcpy(val, path, sizeof(val));
                                    } else {
                                            /* other file like 'Makefile' */
                                            val[0] = ' ';
                                            strlimcpy(&val[1], path, sizeof(val) - 1);
                                    }
                                    val[sizeof(val) - 1] = '\0';
                                    return val;
                            }
                            if (type == 'd') {
                                    STRBUF *sb = strbuf_open(0);
                                    char *dirp = curp->dirp;

                                    strcat(dirp, "/");
                                    strcat(dirp, unit);
                                    if (getdirs(dir, sb) < 0) {
                                            warning("cannot open directory '%s'. (Ignored)", dir);
                                            strbuf_close(sb);
                                            *(curp->dirp) = 0;
                                            continue;
                                    }
                                    /*
                                     * Push stack.
                                     */
                                    if (++curp >= topp)
                                            die("directory stack over flow.");
                                    curp->dirp = dirp + strlen(dirp);
                                    curp->sb = sb;
                                    curp->start = curp->p = strbuf_value(sb);
                                    curp->end   = curp->start + strbuf_getlen(sb);
                            }
                    }
                    strbuf_close(curp->sb);
                    curp->sb = NULL;
                    if (curp == &stack[0])
                            break;
                    /*
                     * Pop stack.
                     */
                    curp--;
                    *(curp->dirp) = 0;
            }
            status = END_OF_FIND;
            return NULL;
    }
    /*
     * find_read_filelist: read path from file
     *
     *      r)              path
     */
    static char *
    find_read_filelist(void)
    {
            STATIC_STRBUF(ib);
            static char buf[MAXPATHLEN + 1];
            static char *path;

            strbuf_clear(ib);
            for (;;) {
                    path = strbuf_fgets(ib, ip, STRBUF_NOCRLF);
                    if (path == NULL) {
                            /* EOF */
                            status = END_OF_FIND;
                            return NULL;
                    }
                    if (*path == '\0') {
                            /* skip empty line.  */
                            continue;
                    }
                    /*
                     * Skip the following:
                     * o directory
                     * o file which does not exist
                     * o dead symbolic link
                     */
                    if (!test("f", path)) {
                            if (!qflag) {
                                    if (test("d", path))
                                            warning("'%s' is a directory. (Ignored)", path);
                                    else
                                            warning("'%s' not found. (Ignored)", path);
                            }
                            continue;
                    }
                    /*
                     * normalize path name.
                     *
                     *      rootdir  /a/b/
                     *      buf      /a/b/c/d.c -> c/d.c -> ./c/d.c
                     */
                    path = normalize(path, rootdir, cwddir, buf, sizeof(buf));
                    /*
                     * GLOBAL cannot treat path which includes blanks.
                     * It will be improved in the future.
                     */
                    if (locatestring(path, " ", MATCH_LAST)) {
                            if (!qflag)
                                    warning("'%s' ignored, because it includes blank.", path + 2);
                            continue;
                    }
                    if (skipthisfile(path))
                            continue;
                    /*
                     * A blank at the head of path means
                     * other than source file.
                     */
                    if (regexec(suff, path, 0, 0, 0) != 0)
                            *--path = ' ';
                    return path;
            }
    }
    /*
     * find_close: close iterator.
     */
    void
    find_close(void)
    {
            assert(status != 0);
            if (status == FIND_OPEN) {
                    for (curp = &stack[0]; curp < topp; curp++)
                            if (curp->sb != NULL)
                                    strbuf_close(curp->sb);
            } else if (status == FILELIST_OPEN) {
                    /*
                     * The --file=- option is specified, we don't close file
                     * to read it repeatedly.
                     */
                    if (ip != temp)
                            fclose(ip);
            } else if (status != END_OF_FIND) {
                    die("illegal find_close");
            }
            regfree(suff);
            if (skip)
                    regfree(skip);
            status = 0;
    }

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