多线程编程指南

tgrep 的说明

tgrep 支持除常规 grep-w 单词搜索选项和可独占使用选项以外的所有选项。

缺省情况下,tgrep 搜索与以下命令类似:

find . -exec grep [options] pattern {} \;

对于大型目录分层结构,tgrepfind 命令获取结果的速度更快,具体速度取决于可用的处理器数目。在单处理器计算机上,tgrep 的速度大约是 find 的两倍;在包含四个处理器的计算机上,tgrep 的速度大约是 find 的四倍。

-e 选项可用于更改 tgrep 解释模式字符串的方式。通常,在不使用 -e 选项的情况下,tgrep 将使用文字字符串匹配。如果使用 -e 选项,tgrep 将使用正则表达式处理程序的 MT-Safe(多线程安全)公共域版本。正则表达式方法的速度比较慢。

tgrep -B 选项可以使用 TGLIMIT 环境变量的值来限制搜索期间使用的线程的数目。如果未设置 TGLIMIT,则此选项没有效果。由于 tgrep 可以使用许多系统资源,因而使用 -B 选项是在分时系统上适当运行 tgrep 的一种方式。


注 –

Catalyst 开发者的 CD 中包括 tgrep 的源代码。要了解如何获取副本,请与销售代表联系。


此处仅出现了多线程 main.c 模块。可以在 Catalyst 开发者的 CD 中找到其他模块(包括用于处理正则表达式的其他模块),以及文档和 make 程序的描述文件。


示例 A–1 tgrep 程序的源代码

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