/* Copyright (c) 1993, 1994 Ron Winacott */
/* This program may be used, copied, modified, and redistributed freely */
/* for ANY purpose, so long as this notice remains intact. */
#define _REENTRANT
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <assert.h>
#include <errno.h>
#include <ctype.h>
#include <sys/types.h>
#include <time.h>
#include <sys/stat.h>
#include <dirent.h>
#include "version.h"
#include <fcntl.h>
#include <sys/uio.h>
#include <pthread.h>
#include <sched.h>
#ifdef MARK
#include <prof.h> /* to turn on MARK(), use -DMARK to compile (see man prof5)*/
#endif
#include "pmatch.h"
#define PATH_MAX 1024 /* max # of characters in a path name */
#define HOLD_FDS 6 /* stdin,out,err and a buffer */
#define UNLIMITED 99999 /* The default tglimit */
#define MAXREGEXP 10 /* max number of -e options */
#define FB_BLOCK 0x00001
#define FC_COUNT 0x00002
#define FH_HOLDNAME 0x00004
#define FI_IGNCASE 0x00008
#define FL_NAMEONLY 0x00010
#define FN_NUMBER 0x00020
#define FS_NOERROR 0x00040
#define FV_REVERSE 0x00080
#define FW_WORD 0x00100
#define FR_RECUR 0x00200
#define FU_UNSORT 0x00400
#define FX_STDIN 0x00800
#define TG_BATCH 0x01000
#define TG_FILEPAT 0x02000
#define FE_REGEXP 0x04000
#define FS_STATS 0x08000
#define FC_LINE 0x10000
#define TG_PROGRESS 0x20000
#define FILET 1
#define DIRT 2
typedef struct work_st {
char *path;
int tp;
struct work_st *next;
} work_t;
typedef struct out_st {
char *line;
int line_count;
long byte_count;
struct out_st *next;
} out_t;
#define ALPHASIZ 128
typedef struct bm_pattern { /* Boyer - Moore pattern */
short p_m; /* length of pattern string */
short p_r[ALPHASIZ]; /* "r" vector */
short *p_R; /* "R" vector */
char *p_pat; /* pattern string */
} BM_PATTERN;
/* bmpmatch.c */
extern BM_PATTERN *bm_makepat(char *p);
extern char *bm_pmatch(BM_PATTERN *pat, register char *s);
extern void bm_freepat(BM_PATTERN *pattern);
BM_PATTERN *bm_pat; /* the global target read only after main */
/* pmatch.c */
extern char *pmatch(register PATTERN *pattern, register char *string, int *len);
extern PATTERN *makepat(char *string, char *metas);
extern void freepat(register PATTERN *pat);
extern void printpat(PATTERN *pat);
PATTERN *pm_pat[MAXREGEXP]; /* global targets read only for pmatch */
#include "proto.h" /* function prototypes of main.c */
/* local functions to POSIX only */
void pthread_setconcurrency_np(int con);
int pthread_getconcurrency_np(void);
void pthread_yield_np(void);
pthread_attr_t detached_attr;
pthread_mutex_t output_print_lk;
pthread_mutex_t global_count_lk;
int global_count = 0;
work_t *work_q = NULL;
pthread_cond_t work_q_cv;
pthread_mutex_t work_q_lk;
pthread_mutex_t debug_lock;
#include "debug.h" /* must be included AFTER the
mutex_t debug_lock line */
work_t *search_q = NULL;
pthread_mutex_t search_q_lk;
pthread_cond_t search_q_cv;
int search_pool_cnt = 0; /* the count in the pool now */
int search_thr_limit = 0; /* the max in the pool */
work_t *cascade_q = NULL;
pthread_mutex_t cascade_q_lk;
pthread_cond_t cascade_q_cv;
int cascade_pool_cnt = 0;
int cascade_thr_limit = 0;
int running = 0;
pthread_mutex_t running_lk;
pthread_mutex_t stat_lk;
time_t st_start = 0;
int st_dir_search = 0;
int st_file_search = 0;
int st_line_search = 0;
int st_cascade = 0;
int st_cascade_pool = 0;
int st_cascade_destroy = 0;
int st_search = 0;
int st_pool = 0;
int st_maxrun = 0;
int st_worknull = 0;
int st_workfds = 0;
int st_worklimit = 0;
int st_destroy = 0;
int all_done = 0;
int work_cnt = 0;
int current_open_files = 0;
int tglimit = UNLIMITED; /* if -B limit the number of
threads */
int progress_offset = 1;
int progress = 0; /* protected by the print_lock ! */
unsigned int flags = 0;
int regexp_cnt = 0;
char *string[MAXREGEXP];
int debug = 0;
int use_pmatch = 0;
char file_pat[255]; /* file patten match */
PATTERN *pm_file_pat; /* compiled file target string (pmatch()) */
/*
* Main: This is where the fun starts
*/
int
main(int argc, char **argv)
{
int c,out_thr_flags;
long max_open_files = 0l, ncpus = 0l;
extern int optind;
extern char *optarg;
int prio = 0;
struct stat sbuf;
pthread_t tid,dtid;
void *status;
char *e = NULL, *d = NULL; /* for debug flags */
int debug_file = 0;
struct sigaction sigact;
sigset_t set,oset;
int err = 0, i = 0, pm_file_len = 0;
work_t *work;
int restart_cnt = 10;
/* NO OTHER THREADS ARE RUNNING */
flags = FR_RECUR; /* the default */
while ((c = getopt(argc, argv, "d:e:bchilnsvwruf:p:BCSZzHP:")) != EOF) {
switch (c) {
#ifdef DEBUG
case 'd':
debug = atoi(optarg);
if (debug == 0)
debug_usage();
d = optarg;
fprintf(stderr,"tgrep: Debug on at level(s) ");
while (*d) {
for (i=0; i<9; i++)
if (debug_set[i].level == *d) {
debug_levels |= debug_set[i].flag;
fprintf(stderr,"%c ",debug_set[i].level);
break;
}
d++;
}
fprintf(stderr,"\n");
break;
case 'f': debug_file = atoi(optarg); break;
#endif /* DEBUG */
case 'B':
flags |= TG_BATCH;
#ifndef __lock_lint
/* locklint complains here, but there are no other threads */
if ((e = getenv("TGLIMIT"))) {
tglimit = atoi(e);
}
else {
if (!(flags & FS_NOERROR)) /* order dependent! */
fprintf(stderr,"env TGLIMIT not set, overriding -B\n");
flags &= ~TG_BATCH;
}
#endif
break;
case 'p':
flags |= TG_FILEPAT;
strcpy(file_pat,optarg);
pm_file_pat = makepat(file_pat,NULL);
break;
case 'P':
flags |= TG_PROGRESS;
progress_offset = atoi(optarg);
break;
case 'S': flags |= FS_STATS; break;
case 'b': flags |= FB_BLOCK; break;
case 'c': flags |= FC_COUNT; break;
case 'h': flags |= FH_HOLDNAME; break;
case 'i': flags |= FI_IGNCASE; break;
case 'l': flags |= FL_NAMEONLY; break;
case 'n': flags |= FN_NUMBER; break;
case 's': flags |= FS_NOERROR; break;
case 'v': flags |= FV_REVERSE; break;
case 'w': flags |= FW_WORD; break;
case 'r': flags &= ~FR_RECUR; break;
case 'C': flags |= FC_LINE; break;
case 'e':
if (regexp_cnt == MAXREGEXP) {
fprintf(stderr,"Max number of regexp's (%d) exceeded!\n",
MAXREGEXP);
exit(1);
}
flags |= FE_REGEXP;
if ((string[regexp_cnt] =(char *)malloc(strlen(optarg)+1))==NULL){
fprintf(stderr,"tgrep: No space for search string(s)\n");
exit(1);
}
memset(string[regexp_cnt],0,strlen(optarg)+1);
strcpy(string[regexp_cnt],optarg);
regexp_cnt++;
break;
case 'z':
case 'Z': regexp_usage();
break;
case 'H':
case '?':
default : usage();
}
}
if (flags & FS_STATS)
st_start = time(NULL);
if (!(flags & FE_REGEXP)) {
if (argc - optind < 1) {
fprintf(stderr,"tgrep: Must supply a search string(s) "
"and file list or directory\n");
usage();
}
if ((string[0]=(char *)malloc(strlen(argv[optind])+1))==NULL){
fprintf(stderr,"tgrep: No space for search string(s)\n");
exit(1);
}
memset(string[0],0,strlen(argv[optind])+1);
strcpy(string[0],argv[optind]);
regexp_cnt=1;
optind++;
}
if (flags & FI_IGNCASE)
for (i=0; i<regexp_cnt; i++)
uncase(string[i]);
if (flags & FE_REGEXP) {
for (i=0; i<regexp_cnt; i++)
pm_pat[i] = makepat(string[i],NULL);
use_pmatch = 1;
}
else {
bm_pat = bm_makepat(string[0]); /* only one allowed */
}
flags |= FX_STDIN;
max_open_files = sysconf(_SC_OPEN_MAX);
ncpus = sysconf(_SC_NPROCESSORS_ONLN);
if ((max_open_files - HOLD_FDS - debug_file) < 1) {
fprintf(stderr,"tgrep: You MUST have at least ONE fd "
"that can be used, check limit (>10)\n");
exit(1);
}
search_thr_limit = max_open_files - HOLD_FDS - debug_file;
cascade_thr_limit = search_thr_limit / 2;
/* the number of files that can be open */
current_open_files = search_thr_limit;
pthread_attr_init(&detached_attr);
pthread_attr_setdetachstate(&detached_attr,
PTHREAD_CREATE_DETACHED);
pthread_mutex_init(&global_count_lk,NULL);
pthread_mutex_init(&output_print_lk,NULL);
pthread_mutex_init(&work_q_lk,NULL);
pthread_mutex_init(&running_lk,NULL);
pthread_cond_init(&work_q_cv,NULL);
pthread_mutex_init(&search_q_lk,NULL);
pthread_cond_init(&search_q_cv,NULL);
pthread_mutex_init(&cascade_q_lk,NULL);
pthread_cond_init(&cascade_q_cv,NULL);
if ((argc == optind) && ((flags & TG_FILEPAT) || (flags & FR_RECUR))) {
add_work(".",DIRT);
flags = (flags & ~FX_STDIN);
}
for ( ; optind < argc; optind++) {
restart_cnt = 10;
flags = (flags & ~FX_STDIN);
STAT_AGAIN:
if (stat(argv[optind], &sbuf)) {
if (errno == EINTR) { /* try again !, restart */
if (--restart_cnt)
goto STAT_AGAIN;
}
if (!(flags & FS_NOERROR))
fprintf(stderr,"tgrep: Can't stat file/dir %s, %s\n",
argv[optind], strerror(errno));
continue;
}
switch (sbuf.st_mode & S_IFMT) {
case S_IFREG :
if (flags & TG_FILEPAT) {
if (pmatch(pm_file_pat, argv[optind], &pm_file_len))
DP(DLEVEL1,("File pat match %s\n",argv[optind]));
add_work(argv[optind],FILET);
}
else {
add_work(argv[optind],FILET);
}
break;
case S_IFDIR :
if (flags & FR_RECUR) {
add_work(argv[optind],DIRT);
}
else {
if (!(flags & FS_NOERROR))
fprintf(stderr,"tgrep: Can't search directory %s, "
"-r option is on. Directory ignored.\n",
argv[optind]);
}
break;
}
}
pthread_setconcurrency_np(3);
if (flags & FX_STDIN) {
fprintf(stderr,"tgrep: stdin option is not coded at this time\n");
exit(0); /* XXX Need to fix this SOON */
search_thr(NULL);
if (flags & FC_COUNT) {
pthread_mutex_lock(&global_count_lk);
printf("%d\n",global_count);
pthread_mutex_unlock(&global_count_lk);
}
if (flags & FS_STATS)
prnt_stats();
exit(0);
}
pthread_mutex_lock(&work_q_lk);
if (!work_q) {
if (!(flags & FS_NOERROR))
fprintf(stderr,"tgrep: No files to search.\n");
exit(0);
}
pthread_mutex_unlock(&work_q_lk);
DP(DLEVEL1,("Starting to loop through the work_q for work\n"));
/* OTHER THREADS ARE RUNNING */
while (1) {
pthread_mutex_lock(&work_q_lk);
while ((work_q == NULL || current_open_files == 0 || tglimit <= 0) &&
all_done == 0) {
if (flags & FS_STATS) {
pthread_mutex_lock(&stat_lk);
if (work_q == NULL)
st_worknull++;
if (current_open_files == 0)
st_workfds++;
if (tglimit <= 0)
st_worklimit++;
pthread_mutex_unlock(&stat_lk);
}
pthread_cond_wait(&work_q_cv,&work_q_lk);
}
if (all_done != 0) {
pthread_mutex_unlock(&work_q_lk);
DP(DLEVEL1,("All_done was set to TRUE\n"));
goto OUT;
}
work = work_q;
work_q = work->next; /* maybe NULL */
work->next = NULL;
current_open_files--;
pthread_mutex_unlock(&work_q_lk);
tid = 0;
switch (work->tp) {
case DIRT:
pthread_mutex_lock(&cascade_q_lk);
if (cascade_pool_cnt) {
if (flags & FS_STATS) {
pthread_mutex_lock(&stat_lk);
st_cascade_pool++;
pthread_mutex_unlock(&stat_lk);
}
work->next = cascade_q;
cascade_q = work;
pthread_cond_signal(&cascade_q_cv);
pthread_mutex_unlock(&cascade_q_lk);
DP(DLEVEL2,("Sent work to cascade pool thread\n"));
}
else {
pthread_mutex_unlock(&cascade_q_lk);
err = pthread_create(&tid,&detached_attr,cascade,(void *)work);
DP(DLEVEL2,("Sent work to new cascade thread\n"));
if (flags & FS_STATS) {
pthread_mutex_lock(&stat_lk);
st_cascade++;
pthread_mutex_unlock(&stat_lk);
}
}
break;
case FILET:
pthread_mutex_lock(&search_q_lk);
if (search_pool_cnt) {
if (flags & FS_STATS) {
pthread_mutex_lock(&stat_lk);
st_pool++;
pthread_mutex_unlock(&stat_lk);
}
work->next = search_q; /* could be null */
search_q = work;
pthread_cond_signal(&search_q_cv);
pthread_mutex_unlock(&search_q_lk);
DP(DLEVEL2,("Sent work to search pool thread\n"));
}
else {
pthread_mutex_unlock(&search_q_lk);
err = pthread_create(&tid,&detached_attr,
search_thr,(void *)work);
pthread_setconcurrency_np(pthread_getconcurrency_np()+1);
DP(DLEVEL2,("Sent work to new search thread\n"));
if (flags & FS_STATS) {
pthread_mutex_lock(&stat_lk);
st_search++;
pthread_mutex_unlock(&stat_lk);
}
}
break;
default:
fprintf(stderr,"tgrep: Internal error, work_t->tp not valid\n");
exit(1);
}
if (err) { /* NEED TO FIX THIS CODE. Exiting is just wrong */
fprintf(stderr,"Could not create new thread!\n");
exit(1);
}
}
OUT:
if (flags & TG_PROGRESS) {
if (progress)
fprintf(stderr,".\n");
else
fprintf(stderr,"\n");
}
/* we are done, print the stuff. All other threads are parked */
if (flags & FC_COUNT) {
pthread_mutex_lock(&global_count_lk);
printf("%d\n",global_count);
pthread_mutex_unlock(&global_count_lk);
}
if (flags & FS_STATS)
prnt_stats();
return(0); /* should have a return from main */
}
/*
* Add_Work: Called from the main thread, and cascade threads to add file
* and directory names to the work Q.
*/
int
add_work(char *path,int tp)
{
work_t *wt,*ww,*wp;
if ((wt = (work_t *)malloc(sizeof(work_t))) == NULL)
goto ERROR;
if ((wt->path = (char *)malloc(strlen(path)+1)) == NULL)
goto ERROR;
strcpy(wt->path,path);
wt->tp = tp;
wt->next = NULL;
if (flags & FS_STATS) {
pthread_mutex_lock(&stat_lk);
if (wt->tp == DIRT)
st_dir_search++;
else
st_file_search++;
pthread_mutex_unlock(&stat_lk);
}
pthread_mutex_lock(&work_q_lk);
work_cnt++;
wt->next = work_q;
work_q = wt;
pthread_cond_signal(&work_q_cv);
pthread_mutex_unlock(&work_q_lk);
return(0);
ERROR:
if (!(flags & FS_NOERROR))
fprintf(stderr,"tgrep: Could not add %s to work queue. Ignored\n",
path);
return(-1);
}
/*
* Search thread: Started by the main thread when a file name is found
* on the work Q to be serached. If all the needed resources are ready
* a new search thread will be created.
*/
void *
search_thr(void *arg) /* work_t *arg */
{
FILE *fin;
char fin_buf[(BUFSIZ*4)]; /* 4 Kbytes */
work_t *wt,std;
int line_count;
char rline[128];
char cline[128];
char *line;
register char *p,*pp;
int pm_len;
int len = 0;
long byte_count;
long next_line;
int show_line; /* for the -v option */
register int slen,plen,i;
out_t *out = NULL; /* this threads output list */
pthread_yield_np();
wt = (work_t *)arg; /* first pass, wt is passed to use. */
/* len = strlen(string);*/ /* only set on first pass */
while (1) { /* reuse the search threads */
/* init all back to zero */
line_count = 0;
byte_count = 0l;
next_line = 0l;
show_line = 0;
pthread_mutex_lock(&running_lk);
running++;
pthread_mutex_unlock(&running_lk);
pthread_mutex_lock(&work_q_lk);
tglimit--;
pthread_mutex_unlock(&work_q_lk);
DP(DLEVEL5,("searching file (STDIO) %s\n",wt->path));
if ((fin = fopen(wt->path,"r")) == NULL) {
if (!(flags & FS_NOERROR)) {
fprintf(stderr,"tgrep: %s. File \"%s\" not searched.\n",
strerror(errno),wt->path);
}
goto ERROR;
}
setvbuf(fin,fin_buf,_IOFBF,(BUFSIZ*4)); /* XXX */
DP(DLEVEL5,("Search thread has opened file %s\n",wt->path));
while ((fgets(rline,127,fin)) != NULL) {
if (flags & FS_STATS) {
pthread_mutex_lock(&stat_lk);
st_line_search++;
pthread_mutex_unlock(&stat_lk);
}
slen = strlen(rline);
next_line += slen;
line_count++;
if (rline[slen-1] == '\n')
rline[slen-1] = '\0';
/*
** If the uncase flag is set, copy the read in line (rline)
** To the uncase line (cline) Set the line pointer to point at
** cline.
** If the case flag is NOT set, then point line at rline.
** line is what is compared, rline is what is printed on a
** match.
*/
if (flags & FI_IGNCASE) {
strcpy(cline,rline);
uncase(cline);
line = cline;
}
else {
line = rline;
}
show_line = 1; /* assume no match, if -v set */
/* The old code removed */
if (use_pmatch) {
for (i=0; i<regexp_cnt; i++) {
if (pmatch(pm_pat[i], line, &pm_len)) {
if (!(flags & FV_REVERSE)) {
add_output_local(&out,wt,line_count,
byte_count,rline);
continue_line(rline,fin,out,wt,
&line_count,&byte_count);
}
else {
show_line = 0;
} /* end of if -v flag if / else block */
/*
** if we get here on ANY of the regexp targets
** jump out of the loop, we found a single
** match so do not keep looking!
** If name only, do not keep searcthing the same
** file, we found a single match, so close the file,
** print the file name and move on to the next file.
*/
if (flags & FL_NAMEONLY)
goto OUT_OF_LOOP;
else
goto OUT_AND_DONE;
} /* end found a match if block */
} /* end of the for pat[s] loop */
}
else {
if (bm_pmatch( bm_pat, line)) {
if (!(flags & FV_REVERSE)) {
add_output_local(&out,wt,line_count,byte_count,rline);
continue_line(rline,fin,out,wt,
&line_count,&byte_count);
}
else {
show_line = 0;
}
if (flags & FL_NAMEONLY)
goto OUT_OF_LOOP;
}
}
OUT_AND_DONE:
if ((flags & FV_REVERSE) && show_line) {
add_output_local(&out,wt,line_count,byte_count,rline);
show_line = 0;
}
byte_count = next_line;
}
OUT_OF_LOOP:
fclose(fin);
/*
** The search part is done, but before we give back the FD,
** and park this thread in the search thread pool, print the
** local output we have gathered.
*/
print_local_output(out,wt); /* this also frees out nodes */
out = NULL; /* for the next time around, if there is one */
ERROR:
DP(DLEVEL5,("Search done for %s\n",wt->path));
free(wt->path);
free(wt);
notrun();
pthread_mutex_lock(&search_q_lk);
if (search_pool_cnt > search_thr_limit) {
pthread_mutex_unlock(&search_q_lk);
DP(DLEVEL5,("Search thread exiting\n"));
if (flags & FS_STATS) {
pthread_mutex_lock(&stat_lk);
st_destroy++;
pthread_mutex_unlock(&stat_lk);
}
return(0);
}
else {
search_pool_cnt++;
while (!search_q)
pthread_cond_wait(&search_q_cv,&search_q_lk);
search_pool_cnt--;
wt = search_q; /* we have work to do! */
if (search_q->next)
search_q = search_q->next;
else
search_q = NULL;
pthread_mutex_unlock(&search_q_lk);
}
}
/*NOTREACHED*/
}
/*
* Continue line: Special case search with the -C flag set. If you are
* searching files like Makefiles, some lines might have escape char's to
* contine the line on the next line. So the target string can be found, but
* no data is displayed. This function continues to print the escaped line
* until there are no more "\" chars found.
*/
int
continue_line(char *rline, FILE *fin, out_t *out, work_t *wt,
int *lc, long *bc)
{
int len;
int cnt = 0;
char *line;
char nline[128];
if (!(flags & FC_LINE))
return(0);
line = rline;
AGAIN:
len = strlen(line);
if (line[len-1] == '\\') {
if ((fgets(nline,127,fin)) == NULL) {
return(cnt);
}
line = nline;
len = strlen(line);
if (line[len-1] == '\n')
line[len-1] = '\0';
*bc = *bc + len;
*lc++;
add_output_local(&out,wt,*lc,*bc,line);
cnt++;
goto AGAIN;
}
return(cnt);
}
/*
* cascade: This thread is started by the main thread when directory names
* are found on the work Q. The thread reads all the new file, and directory
* names from the directory it was started when and adds the names to the
* work Q. (it finds more work!)
*/
void *
cascade(void *arg) /* work_t *arg */
{
char fullpath[1025];
int restart_cnt = 10;
DIR *dp;
char dir_buf[sizeof(struct dirent) + PATH_MAX];
struct dirent *dent = (struct dirent *)dir_buf;
struct stat sbuf;
char *fpath;
work_t *wt;
int fl = 0, dl = 0;
int pm_file_len = 0;
pthread_yield_np(); /* try toi give control back to main thread */
wt = (work_t *)arg;
while(1) {
fl = 0;
dl = 0;
restart_cnt = 10;
pm_file_len = 0;
pthread_mutex_lock(&running_lk);
running++;
pthread_mutex_unlock(&running_lk);
pthread_mutex_lock(&work_q_lk);
tglimit--;
pthread_mutex_unlock(&work_q_lk);
if (!wt) {
if (!(flags & FS_NOERROR))
fprintf(stderr,"tgrep: Bad work node passed to cascade\n");
goto DONE;
}
fpath = (char *)wt->path;
if (!fpath) {
if (!(flags & FS_NOERROR))
fprintf(stderr,"tgrep: Bad path name passed to cascade\n");
goto DONE;
}
DP(DLEVEL3,("Cascading on %s\n",fpath));
if (( dp = opendir(fpath)) == NULL) {
if (!(flags & FS_NOERROR))
fprintf(stderr,"tgrep: Can't open dir %s, %s. Ignored.\n",
fpath,strerror(errno));
goto DONE;
}
while ((readdir_r(dp,dent)) != NULL) {
restart_cnt = 10; /* only try to restart the interupted 10 X */
if (dent->d_name[0] == '.') {
if (dent->d_name[1] == '.' && dent->d_name[2] == '\0')
continue;
if (dent->d_name[1] == '\0')
continue;
}
fl = strlen(fpath);
dl = strlen(dent->d_name);
if ((fl + 1 + dl) > 1024) {
fprintf(stderr,"tgrep: Path %s/%s is too long. "
"MaxPath = 1024\n",
fpath, dent->d_name);
continue; /* try the next name in this directory */
}
strcpy(fullpath,fpath);
strcat(fullpath,"/");
strcat(fullpath,dent->d_name);
RESTART_STAT:
if (stat(fullpath,&sbuf)) {
if (errno == EINTR) {
if (--restart_cnt)
goto RESTART_STAT;
}
if (!(flags & FS_NOERROR))
fprintf(stderr,"tgrep: Can't stat file/dir %s, %s. "
"Ignored.\n",
fullpath,strerror(errno));
goto ERROR;
}
switch (sbuf.st_mode & S_IFMT) {
case S_IFREG :
if (flags & TG_FILEPAT) {
if (pmatch(pm_file_pat, dent->d_name, &pm_file_len)) {
DP(DLEVEL3,("file pat match (cascade) %s\n",
dent->d_name));
add_work(fullpath,FILET);
}
}
else {
add_work(fullpath,FILET);
DP(DLEVEL3,("cascade added file (MATCH) %s to Work Q\n",
fullpath));
}
break;
case S_IFDIR :
DP(DLEVEL3,("cascade added dir %s to Work Q\n",fullpath));
add_work(fullpath,DIRT);
break;
}
}
ERROR:
closedir(dp);
DONE:
free(wt->path);
free(wt);
notrun();
pthread_mutex_lock(&cascade_q_lk);
if (cascade_pool_cnt > cascade_thr_limit) {
pthread_mutex_unlock(&cascade_q_lk);
DP(DLEVEL5,("Cascade thread exiting\n"));
if (flags & FS_STATS) {
pthread_mutex_lock(&stat_lk);
st_cascade_destroy++;
pthread_mutex_unlock(&stat_lk);
}
return(0); /* pthread_exit */
}
else {
DP(DLEVEL5,("Cascade thread waiting in pool\n"));
cascade_pool_cnt++;
while (!cascade_q)
pthread_cond_wait(&cascade_q_cv,&cascade_q_lk);
cascade_pool_cnt--;
wt = cascade_q; /* we have work to do! */
if (cascade_q->next)
cascade_q = cascade_q->next;
else
cascade_q = NULL;
pthread_mutex_unlock(&cascade_q_lk);
}
}
/*NOTREACHED*/
}
/*
* Print Local Output: Called by the search thread after it is done searching
* a single file. If any oputput was saved (matching lines), the lines are
* displayed as a group on stdout.
*/
int
print_local_output(out_t *out, work_t *wt)
{
out_t *pp, *op;
int out_count = 0;
int printed = 0;
pp = out;
pthread_mutex_lock(&output_print_lk);
if (pp && (flags & TG_PROGRESS)) {
progress++;
if (progress >= progress_offset) {
progress = 0;
fprintf(stderr,".");
}
}
while (pp) {
out_count++;
if (!(flags & FC_COUNT)) {
if (flags & FL_NAMEONLY) { /* Pint name ONLY ! */
if (!printed) {
printed = 1;
printf("%s\n",wt->path);
}
}
else { /* We are printing more then just the name */
if (!(flags & FH_HOLDNAME))
printf("%s :",wt->path);
if (flags & FB_BLOCK)
printf("%ld:",pp->byte_count/512+1);
if (flags & FN_NUMBER)
printf("%d:",pp->line_count);
printf("%s\n",pp->line);
}
}
op = pp;
pp = pp->next;
/* free the nodes as we go down the list */
free(op->line);
free(op);
}
pthread_mutex_unlock(&output_print_lk);
pthread_mutex_lock(&global_count_lk);
global_count += out_count;
pthread_mutex_unlock(&global_count_lk);
return(0);
}
/*
* add output local: is called by a search thread as it finds matching lines.
* the matching line, its byte offset, line count, etc. are stored until the
* search thread is done searching the file, then the lines are printed as
* a group. This way the lines from more then a single file are not mixed
* together.
*/
int
add_output_local(out_t **out, work_t *wt,int lc, long bc, char *line)
{
out_t *ot,*oo, *op;
if (( ot = (out_t *)malloc(sizeof(out_t))) == NULL)
goto ERROR;
if (( ot->line = (char *)malloc(strlen(line)+1)) == NULL)
goto ERROR;
strcpy(ot->line,line);
ot->line_count = lc;
ot->byte_count = bc;
if (!*out) {
*out = ot;
ot->next = NULL;
return(0);
}
/* append to the END of the list; keep things sorted! */
op = oo = *out;
while(oo) {
op = oo;
oo = oo->next;
}
op->next = ot;
ot->next = NULL;
return(0);
ERROR:
if (!(flags & FS_NOERROR))
fprintf(stderr,"tgrep: Output lost. No space. "
"[%s: line %d byte %d match : %s\n",
wt->path,lc,bc,line);
return(1);
}
/*
* print stats: If the -S flag is set, after ALL files have been searched,
* main thread calls this function to print the stats it keeps on how the
* search went.
*/
void
prnt_stats(void)
{
float a,b,c;
float t = 0.0;
time_t st_end = 0;
char tl[80];
st_end = time(NULL); /* stop the clock */
printf("\n----------------- Tgrep Stats. --------------------\n");
printf("Number of directories searched: %d\n",st_dir_search);
printf("Number of files searched: %d\n",st_file_search);
c = (float)(st_dir_search + st_file_search) / (float)(st_end - st_start);
printf("Dir/files per second: %3.2f\n",c);
printf("Number of lines searched: %d\n",st_line_search);
printf("Number of matching lines to target: %d\n",global_count);
printf("Number of cascade threads created: %d\n",st_cascade);
printf("Number of cascade threads from pool: %d\n",st_cascade_pool);
a = st_cascade_pool; b = st_dir_search;
printf("Cascade thread pool hit rate: %3.2f%%\n",((a/b)*100));
printf("Cascade pool overall size: %d\n",cascade_pool_cnt);
printf("Number of search threads created: %d\n",st_search);
printf("Number of search threads from pool: %d\n",st_pool);
a = st_pool; b = st_file_search;
printf("Search thread pool hit rate: %3.2f%%\n",((a/b)*100));
printf("Search pool overall size: %d\n",search_pool_cnt);
printf("Search pool size limit: %d\n",search_thr_limit);
printf("Number of search threads destroyed: %d\n",st_destroy);
printf("Max # of threads running concurrenly: %d\n",st_maxrun);
printf("Total run time, in seconds. %d\n",
(st_end - st_start));
/* Why did we wait ? */
a = st_workfds; b = st_dir_search+st_file_search;
c = (a/b)*100; t += c;
printf("Work stopped due to no FD's: (%.3d) %d Times, %3.2f%%\n",
search_thr_limit,st_workfds,c);
a = st_worknull; b = st_dir_search+st_file_search;
c = (a/b)*100; t += c;
printf("Work stopped due to no work on Q: %d Times, %3.2f%%\n",
st_worknull,c);
if (tglimit == UNLIMITED)
strcpy(tl,"Unlimited");
else
sprintf(tl," %.3d ",tglimit);
a = st_worklimit; b = st_dir_search+st_file_search;
c = (a/b)*100; t += c;
printf("Work stopped due to TGLIMIT: (%.9s) %d Times, %3.2f%%\n",
tl,st_worklimit,c);
printf("Work continued to be handed out: %3.2f%%\n",100.00-t);
printf("----------------------------------------------------\n");
}
/*
* not running: A glue function to track if any search threads or cascade
* threads are running. When the count is zero, and the work Q is NULL,
* we can safely say, WE ARE DONE.
*/
void
notrun (void)
{
pthread_mutex_lock(&work_q_lk);
work_cnt--;
tglimit++;
current_open_files++;
pthread_mutex_lock(&running_lk);
if (flags & FS_STATS) {
pthread_mutex_lock(&stat_lk);
if (running > st_maxrun) {
st_maxrun = running;
DP(DLEVEL6,("Max Running has increased to %d\n",st_maxrun));
}
pthread_mutex_unlock(&stat_lk);
}
running--;
if (work_cnt == 0 && running == 0) {
all_done = 1;
DP(DLEVEL6,("Setting ALL_DONE flag to TRUE.\n"));
}
pthread_mutex_unlock(&running_lk);
pthread_cond_signal(&work_q_cv);
pthread_mutex_unlock(&work_q_lk);
}
/*
* uncase: A glue function. If the -i (case insensitive) flag is set, the
* target strng and the read in line is converted to lower case before
* comparing them.
*/
void
uncase(char *s)
{
char *p;
for (p = s; *p != NULL; p++)
*p = (char)tolower(*p);
}
/*
* usage: Have to have one of these.
*/
void
usage(void)
{
fprintf(stderr,"usage: tgrep <options> pattern <{file,dir}>...\n");
fprintf(stderr,"\n");
fprintf(stderr,"Where:\n");
#ifdef DEBUG
fprintf(stderr,"Debug -d = debug level -d <levels> (-d0 for usage)\n");
fprintf(stderr,"Debug -f = block fd's from use (-f #)\n");
#endif
fprintf(stderr," -b = show block count (512 byte block)\n");
fprintf(stderr," -c = print only a line count\n");
fprintf(stderr," -h = Do NOT print file names\n");
fprintf(stderr," -i = case insensitive\n");
fprintf(stderr," -l = print file name only\n");
fprintf(stderr," -n = print the line number with the line\n");
fprintf(stderr," -s = Suppress error messages\n");
fprintf(stderr," -v = print all but matching lines\n");
#ifdef NOT_IMP
fprintf(stderr," -w = search for a \"word\"\n");
#endif
fprintf(stderr," -r = Do not search for files in all "
"sub-directories\n");
fprintf(stderr," -C = show continued lines (\"\\\")\n");
fprintf(stderr," -p = File name regexp pattern. (Quote it)\n");
fprintf(stderr," -P = show progress. -P 1 prints a DOT on stderr\n"
" for each file it finds, -P 10 prints a DOT\n"
" on stderr for each 10 files it finds, etc...\n");
fprintf(stderr," -e = expression search.(regexp) More then one\n");
fprintf(stderr," -B = limit the number of threads to TGLIMIT\n");
fprintf(stderr," -S = Print thread stats when done.\n");
fprintf(stderr," -Z = Print help on the regexp used.\n");
fprintf(stderr,"\n");
fprintf(stderr,"Notes:\n");
fprintf(stderr," If you start tgrep with only a directory name\n");
fprintf(stderr," and no file names, you must not have the -r option\n");
fprintf(stderr," set or you will get no output.\n");
fprintf(stderr," To search stdin (piped input), you must set -r\n");
fprintf(stderr," Tgrep will search ALL files in ALL \n");
fprintf(stderr," sub-directories. (like */* */*/* */*/*/* etc..)\n");
fprintf(stderr," if you supply a directory name.\n");
fprintf(stderr," If you do not supply a file, or directory name,\n");
fprintf(stderr," and the -r option is not set, the current \n");
fprintf(stderr," directory \".\" will be used.\n");
fprintf(stderr," All the other options should work \"like\" grep\n");
fprintf(stderr," The -p patten is regexp; tgrep will search only\n");
fprintf(stderr,"\n");
fprintf(stderr," Copy Right By Ron Winacott, 1993-1995.\n");
fprintf(stderr,"\n");
exit(0);
}
/*
* regexp usage: Tell the world about tgrep custom (THREAD SAFE) regexp!
*/
int
regexp_usage (void)
{
fprintf(stderr,"usage: tgrep <options> -e \"pattern\" <-e ...> "
"<{file,dir}>...\n");
fprintf(stderr,"\n");
fprintf(stderr,"metachars:\n");
fprintf(stderr," . - match any character\n");
fprintf(stderr," * - match 0 or more occurrences of previous char\n");
fprintf(stderr," + - match 1 or more occurrences of previous char.\n");
fprintf(stderr," ^ - match at beginning of string\n");
fprintf(stderr," $ - match end of string\n");
fprintf(stderr," [ - start of character class\n");
fprintf(stderr," ] - end of character class\n");
fprintf(stderr," ( - start of a new pattern\n");
fprintf(stderr," ) - end of a new pattern\n");
fprintf(stderr," @(n)c - match <c> at column <n>\n");
fprintf(stderr," | - match either pattern\n");
fprintf(stderr," \\ - escape any special characters\n");
fprintf(stderr," \\c - escape any special characters\n");
fprintf(stderr," \\o - turn on any special characters\n");
fprintf(stderr,"\n");
fprintf(stderr,"To match two diffrerent patterns in the same command\n");
fprintf(stderr,"Use the or function. \n"
"ie: tgrep -e \"(pat1)|(pat2)\" file\n"
"This will match any line with \"pat1\" or \"pat2\" in it.\n");
fprintf(stderr,"You can also use up to %d -e expressions\n",MAXREGEXP);
fprintf(stderr,"RegExp Pattern matching brought to you by Marc Staveley\n");
exit(0);
}
/*
* debug usage: If compiled with -DDEBUG, turn it on, and tell the world
* how to get tgrep to print debug info on different threads.
*/
#ifdef DEBUG
void
debug_usage(void)
{
int i = 0;
fprintf(stderr,"DEBUG usage and levels:\n");
fprintf(stderr,"--------------------------------------------------\n");
fprintf(stderr,"Level code\n");
fprintf(stderr,"--------------------------------------------------\n");
fprintf(stderr,"0 This message.\n");
for (i=0; i<9; i++) {
fprintf(stderr,"%d %s\n",i+1,debug_set[i].name);
}
fprintf(stderr,"--------------------------------------------------\n");
fprintf(stderr,"You can or the levels together like -d134 for levels\n");
fprintf(stderr,"1 and 3 and 4.\n");
fprintf(stderr,"\n");
exit(0);
}
#endif
/* Pthreads NP functions */
#ifdef __sun
void
pthread_setconcurrency_np(int con)
{
thr_setconcurrency(con);
}
int
pthread_getconcurrency_np(void)
{
return(thr_getconcurrency());
}
void
pthread_yield_np(void)
{
/* In Solaris 2.4, these functions always return - 1 and set errno to ENOSYS */
if (sched_yield()) /* call UI interface if we are older than 2.5 */
thr_yield();
}
#else
void
pthread_setconcurrency_np(int con)
{
return;
}
int
pthread_getconcurrency_np(void)
{
return(0);
}
void
pthread_yield_np(void)
{
return;
}
#endif
|