/* * Copyright (c) 1997, 1998, 1999, 2000, 2001, 2002, 2005, 2006 * 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 . */ #ifdef HAVE_CONFIG_H #include #endif #include #include #ifdef HAVE_DIRENT_H #include #include #include #endif #ifdef HAVE_LIMITS_H #include #endif #include #ifdef STDC_HEADERS #include #endif #ifdef HAVE_STRING_H #include #else #include #endif #ifdef HAVE_UNISTD_H #include #endif #include "gparam.h" #include "regex.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 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; #ifdef HAVE_LSTAT if (lstat(makepath(dir, dp->d_name, NULL), &st) < 0) { warning("cannot lstat '%s'. (Ignored)", dp->d_name); continue; } #else if (stat(makepath(dir, dp->d_name, NULL), &st) < 0) { warning("cannot stat '%s'. (Ignored)", dp->d_name); continue; } #endif if (S_ISDIR(st.st_mode)) strbuf_putc(sb, 'd'); else if (S_ISREG(st.st_mode)) strbuf_putc(sb, 'f'); #ifdef S_ISLNK else if (S_ISLNK(st.st_mode)) strbuf_putc(sb, 'l'); #endif 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, "/")) strcpy(rootdir, root); else snprintf(rootdir, sizeof(rootdir), "%s/", root); /* * 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' || type == 'l') { 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; } if (realpath(path, buf) == NULL) { if (!qflag) warning("realpath(\"%s\", buf) failed.", path); continue; } if (!isabspath(buf)) die("realpath(3) is not compatible with BSD version."); /* * Remove the root part of buf and insert './'. * rootdir /a/b/ * buf /a/b/c/d.c -> c/d.c -> ./c/d.c */ path = locatestring(buf, rootdir, MATCH_AT_FIRST); if (path == NULL) { if (!qflag) warning("'%s' is out of source tree.", buf); continue; } path -= 2; *path = '.'; /* * 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; }