/* mygrep.c
 * 
 * Recursive grep
 * Assignment 3, CS228
 * 
 * Anthony Towns
 * #343676-968
 *
 * 13 Oct 1997
 *
 * Usage:   mygrep [-w] [-r] [-h] <searchstring> {<files>}
 *
 * -w gives warnings
 * -r recurses through subdirectories
 * -h gives a help message and terminates
 * -v gives verbose messages
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <dirent.h>
#include <assert.h>
#include <sys/stat.h>

#ifndef TRUE
#define TRUE (1)
#endif

#ifndef FALSE
#define FALSE (0)
#endif

/* rgrep_opts specifies the options accepted by the grep procedures */

typedef struct rgrep_opts {
     int warnings;          /* display a warning on stderr for
			     * files/directories skipped */ 
     int recursive;         /* recurse into directories */

     int verbose;           /* display debugging output on stderr */
} rgrep_opts;

/* function prototypes */

/* grep through /filename/. If /filename/ is a directory, and
 * opts.recursive is non-zero, this will behave recursively.  Calls
 * grepFile() and grepDir(). Returns TRUE if any matching lines were
 * found, FALSE otherwise.  
 */
int rgrep( char* string, char* filename, rgrep_opts opts );

/* search for each line in the file named /filename/ that contains
 * /string/, outputting matching lines to stdout. Returns TRUE if any
 * matching lines were found, FALSE otherwise.  
 */

int grepFile(char* string, char* filename, rgrep_opts opts );

/* call rgrep() for each file or directory (except . and ..) in the
 * directory /dirname/. Returns TRUE if any matching lines were found,
 * FALSE otherwise.  
 */
int grepDir( char* string, char* dirname, rgrep_opts opts );



int grepFile(char* string, char* filename, rgrep_opts opts )
{
     int result = FALSE;    /* found any matching lines yet? */
     FILE* file;
     char* buffer;          /* read in each line */
     size_t size = 10;      /* how large the buffer is */

     assert( string != NULL );
     assert( filename != NULL );

     if ( (buffer = malloc( size )) == NULL ) {
	  fprintf( stderr, "Out of memory!\n" );
	  return FALSE;
     }

     if ( (file = fopen( filename, "r" ) ) == NULL ) { 
	  perror( filename );
	  free( buffer );
	  return FALSE;
     }

     while ( !feof( file ) && fgets( buffer, size, file ) ) {

	  /* while we're not at the eof, our buffer is full, and
	   * we don't appear to have read an entire line */
	  while ( !feof( file ) 
		  && strlen( buffer ) == size - 1 
		  && buffer[strlen(buffer)-1] != '\n' ) 
	  {
	       /* double the buffer size */
	       char* newbuf;
	       if ( (newbuf = realloc( buffer, size *= 2 )) == NULL ) {
		    size /= 2;
		    break; /* out of mem: the results will generally be 
			    * incorrect (the line was too long to read at 
			    * once), but possibly useful in spite of
			    * this */
	       }
	       buffer = newbuf;
	       fgets( buffer + strlen(buffer), size - strlen(buffer), file );
	  }
 
	  /* remove trailing \n if necessary */
	  if ( buffer[ strlen(buffer) - 1 ] == '\n' ) {
	       buffer[ strlen(buffer) - 1 ] = '\0';
	  }

	  /* check, and print if found */
	  if ( strstr( buffer, string ) != NULL ) {
	       fprintf( stdout, "%s:%s\n", filename, buffer );
	       result = TRUE;
	  }
     }
     
     if ( !result && opts.verbose ) {
	  fprintf( stderr, "%s - no matches\n", filename );
     }

     free( buffer );
     fclose( file );

     return result;
}



int grepDir( char* string, char* dirname, rgrep_opts opts )
{
     DIR *dirp;               /* for finding the dir contents */
     struct dirent *direntp;

     char* buffer;  /* to store the names of the files in the dir */
     char* addhere; /* points to the first character after the dir's
		     * trailing / */

     int result = FALSE; /* found anything yet? */
     
     assert( string != NULL );
     assert( dirname != NULL );
     assert( dirname[0] != '\0' ); /* needs a directory name! */

     /* allocate memory: this is guaranteed to be enough by POSIX */
     buffer = malloc( strlen(string) + MAXNAMLEN + 2 ); /* for '/' & '\0' */
     if ( buffer == NULL ) {
	  fprintf( stderr, "Out of memory!\n" );
	  return FALSE;
     }

     /* add dirname to buffer */
     strcpy( buffer, dirname );

     /* make sure there is a trailing slash */
     addhere = buffer + strlen(buffer);
     if ( *(addhere - 1) != '/' ) {
	  *(addhere++) = '/';
	  *addhere = '\0';
     }

     /* open the directory */
     dirp = opendir( dirname );
     if ( dirp == NULL ) {
	  perror( dirname );
	  free( buffer );
	  return FALSE;
     }
     
     /* for each dir entry... */
     while ( (direntp = readdir( dirp )) != NULL ) { 

	  /* ignore the parent and current dir markers */
	  if ( strcmp( direntp->d_name, "." ) == 0 ) continue;
	  if ( strcmp( direntp->d_name, ".." ) == 0 ) continue;

	  /* add the current entry's name to the buffer */
	  strcpy(addhere, direntp->d_name );

	  /* call rgrep */
	  result |= rgrep( string, buffer, opts );
     }

     closedir( dirp );
     free(buffer);

     return result;
}



int rgrep( char* string, char* filename, rgrep_opts opts ) 
{ 
     struct stat stat_buf;
     
     if ( opts.verbose ) { 
 	  fprintf( stderr,
		   "searching %s for string \"%s\"%s%s\n", 
		   filename, string, 
		   (opts.warnings ? " (warning)" : "" ), 
		   (opts.recursive ? " (recursive)" : "" ) ); 
     } 
     
     if ( lstat( filename, &stat_buf ) == -1 ) {
	  perror( filename );
	  return FALSE;
     }

     /* link */
     if ( (stat_buf.st_mode & S_IFLNK) == S_IFLNK ) {
	  if ( opts.verbose || opts.warnings ) {
	       fprintf( stderr, 
			"warning: %s is a link. Skipping.\n", 
			filename );
	  }
	  return FALSE;
     }

     /* character or block special, or named pipe */
     if ( (stat_buf.st_mode & S_IFCHR) == S_IFCHR ||
	  (stat_buf.st_mode & S_IFBLK) == S_IFBLK ||
	  (stat_buf.st_mode & S_IFIFO) == S_IFIFO ) {
	  if ( opts.verbose || opts.warnings ) {
	       fprintf( stderr, 
			"warning: %s is special. Skipping.\n", 
			filename );
	  }
	  return FALSE;
     }

     /* directory */
     if ( (stat_buf.st_mode & S_IFDIR) == S_IFDIR ) {
	  if ( !opts.recursive ) {
	       if ( opts.verbose || opts.warnings ) {
		    fprintf( stderr, 
			     "%s is a directory. Skipping. (use -r)\n",
			     filename );
	       }
	       return FALSE;
	  }

	  return grepDir(string, filename, opts);
     }

     /* executable */
     if ( stat_buf.st_mode & (S_IXUSR | S_IXGRP | S_IXOTH) ) {
	  if ( opts.verbose || opts.warnings ) {
	       fprintf( stderr, 
			"warning: %s is executable. Skipping.\n", 
			filename );
	  }
	  return FALSE;
     }

     /* file */
     return grepFile( string, filename, opts );
} 



int main( int argc, char **argv ) 
{ 
     int found = FALSE;
     rgrep_opts opts = { FALSE, FALSE, FALSE }; 
      
     char searchstring[100]; 
     
     int c_arg; 
     
     c_arg = 1; 
     while( c_arg < argc && argv[c_arg][0] == '-' ) { 
 	  if ( strlen( argv[c_arg] ) != 2 ) { 
 	       printf( "Bad arguments! Use -h for help.\n" ); 
 	       exit(EXIT_FAILURE); 
 	  } 
	  
 	  switch( argv[c_arg][1] ) { 
 	  case 'h': 
 	       printf("Usage: mygrep [-w] [-r] [-h] <searchstring> {<files>}"); 
 	       printf("\n");  
 	       printf(" -w gives warnings\n"); 
 	       printf(" -r recurses through subdirectories\n"); 
 	       printf(" -h gives a help message and terminates\n"); 
 	       printf(" -v gives verbose messages\n"); 
 	       exit(0); 
	       
 	  case 'w': 
 	       opts.warnings = TRUE; 
 	       break; 
	       
 	  case 'r': 
 	       opts.recursive = TRUE; 
 	       break; 
	       
 	  case 'd': 
 	  case 'v': 
 	       opts.verbose = TRUE; 
 	       break; 
	       
 	  default: 
 	       printf("Unknown option. Use -h for help.\n" ); 
 	       exit(EXIT_FAILURE); 
 	  } 
 	  c_arg++; 
     } 
     
      if ( argc - c_arg < 2 ) { 
	   printf( "Too few arguments. Use -h for help.\n" ); 
	   exit(EXIT_FAILURE); 
      } 
      
      strcpy( searchstring, argv[c_arg++] );
     
      for ( ; c_arg < argc; c_arg++ ) {
	   found |= rgrep( searchstring, argv[c_arg], opts );
      }

      return (found ? 0 : 1);
}


	       

