#include <stdio.h>
#include <dirent.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/param.h>
#include <sys/stat.h>

/*

#     #     #     #        #    #  
#  #  #    # #    #        #   #   
#  #  #   #   #   #        #  #    
#  #  #  #     #  #        ###     
#  #  #  #######  #        #  #    
#  #  #  #     #  #        #   #   
 ## ##   #     #  #######  #    #  


by CampbellWare,
1983, 2005

Linux  "walk" utility. This utility walks down a directory hierarchy, starting
at  the  directory indicated on the command line, and executes a user supplied
shell command at each level of the hierarchy. 

This  utility  is  patterned  after  the iRMX86 Human Interface "walk" command
created by CampbellWare in 1983. 

Usage: walk [-h | --help | -v || --verbose ] start_dir cmd_to_execute

*/

/* .page */

/*
============================
Global definitions, typedefs
============================
*/
 
#define FALSE 0
#define TRUE  !FALSE

#define MAX_PATH_LEN  1024    /* Buffer overflow limit - max size file name  */
#define MAX_CMD_SIZE  1024    /* Buffer overflow limit - max command size    */

typedef int BOOL;
 

/*
=====================
External declarations
=====================
*/

extern  int alphasort ();
 
/*
============================
Global variables
============================
*/

BOOL verbose;                            /* Was verbose mode requested ?     */
int  directories_visited = 0;            /* Number of directories visited    */

/* .page */

void parse_arguments ( argc, argv, help, verbose, start_dir, cmd_to_execute )

/*
==============================================================================

This  function  parses  the  user supplied command line arguments, and returns
the results as the variables help, verbose, start_dir and cmd_to_execute. 

The expected command syntax is:

walk [-h | --help | -v | --verbose] start_dir cmd_to_execute

Return values:
- If "help" is TRUE, the user has requested help
- If "verbose" is TRUE, the users wants walk to produce per directory messages
- If "start_dir" is NULL, this argument was lacking - walk will terminate
- If "cmd_to_execute" is NULL, this argument was lacking - walk will terminate

==============================================================================
*/

int argc;               /* Number of command line arguments present          */
char *argv[];           /* The command line arguments themselves             */
BOOL *help;             /* Was help requested                                */
BOOL *verbose;          /* Was verbose mode requested                        */
char start_dir [];      /* The directory at which to start walking           */
char cmd_to_execute []; /* The shell command to execute at each level        */

{
   register int i;                          /* Loop counter                  */
   register BOOL have_start_dir = FALSE;    /* Do we have start dir already? */

   /* Initialize the parsed results to logical "nil" */

   *help      = FALSE;
   *verbose   = FALSE;
   strcpy ( start_dir,      "\0" );
   strcpy ( cmd_to_execute, "\0" );

   /* Check for at least three tokens on command line ("walk start_dir       */
   /* cmd_to_execute"). If at least three are not supplied, present the user */
   /* with help info and exit.                                               */

   if ( argc < 3 )
   {
      *help = TRUE;
      return;
   }

   /* Iterate over each argument and parse it into its associated variable   */

   for ( i = 1; i < argc; i++ )
   {
      if ( !strcmp(argv[i], "-h") || !strcmp(argv[i], "--help") )
         *help = TRUE;

      else if ( !strcmp(argv[i], "-v") || !strcmp(argv[i], "--verbose") )
         *verbose = TRUE;

      else
      {
         /* This is not the help or verbose switches, so it is either the    */
         /* starting directory or the command to execute. Starting directory */
         /* must always be presented first.                                  */

         if ( have_start_dir )
         {
            /* Since we have the starting directory already, this is the     */
            /* command to execute at each level.                             */

            if ( strlen(argv[i]) < MAX_CMD_SIZE )
               strcpy ( cmd_to_execute, argv[i] );
            else
            {
               printf ( "\n%s\n", "Requested command exceeds maximum length" );
               *help = TRUE;
               return;
            }
         }

         else
         {
            /* This is the starting directory */

            if ( strlen(argv[i]) < MAX_PATH_LEN )
            {
               strcpy ( start_dir, argv[i] );
               have_start_dir = TRUE;
            }
            else
            {
               printf ( "%s\n", "Start directory name exceeds maximum length" );
               *help = TRUE;
               return;
            }
         }
      }
    }
      
} /* parse_arguments */

/* .page */

int file_select ( entry )

/*
==============================================================================

This  function  examines  the current directory entry, and determines if it is
the  current  or  home directory values ("." and ".." respectively). If so, it
returns  FALSE,  else  it  returns TRUE. This is used to ensure that these two
entries  are not included as part of walk's processing, potentially leading to
an endless loop. 

==============================================================================
*/
struct dirent *entry ;

{
   if ( !strcmp(entry->d_name, ".") || !strcmp(entry->d_name, "..") )
      return ( FALSE );
   else
      return ( TRUE );

} /* file_select */

/* .page */

void walk_hierarchy ( pathname, cmd_to_execute )

/*
==============================================================================

This routine is the main portion of walk. It recursively walks down the 
directory hierarcy, executing the supplied command once per level.

==============================================================================
*/

char pathname[];
char cmd_to_execute[];

{
    char subdir_pathname [MAX_PATH_LEN];

    int i;                  /* Loop counter                                  */
    int count;              /* Number of files in current directory          */
    struct dirent **files;  /* String table of filenames in the directory    */
    int stat_res;           /* Result of status call on file                 */
    struct stat file_info;  /* File information from the stat function       */

    directories_visited += 1;

    if ( verbose )
       printf ( "%s %s \n", "Entering Directory: ", pathname );

    count = scandir ( pathname, &files, file_select, alphasort );

    /* If no files found, simply return. Clearly, no further directories to  */
    /* descend into here!                                                    */

    if ( count <= 0 )
    {      
       if ( verbose ) 
       {
          printf ( "%s\n", "No files in this directory" );
          printf( "%s %s\n", "Leaving Directory: ", pathname);
       }

       return;
    }

    /* Change to the present directory, and then execute the user supplied   */
    /* command                                                               */

    if ( chdir(pathname) == 0 )
       system ( cmd_to_execute );

    /* Iterate over every entry in the directory, searching for sub          */
    /* directories. If found, we will descend into them.                     */

    for ( i = 0; i < count; i++ )
    {
       stat_res = stat ( files[i]->d_name, &file_info );

       if ( S_ISDIR(file_info.st_mode) )
       {
          /* This is a directory - descend into it and walk it */

          /* Append the directory name to the pathname, and recursively call */
          /* ourselves. First check to ensure that this will not overflow    */
          /* the available filename buffer space. If so, error out.          */

           if ( strlen(pathname) + strlen(files[i]->d_name) + 1 > MAX_PATH_LEN )
           {
              /* Overflowed our maximum pathname - quit with an error */

              printf ( "\n%s\n", "Walk's maximum directory depth exceeded - error exit" );
              exit ( 1 );
           }

           /* Append the subdirectory name to the current path, and descend  */
           /* into the subdirectory.                                         */

           strcpy ( subdir_pathname, pathname );
           strcat ( subdir_pathname, "/" );
           strcat ( subdir_pathname, files[i]->d_name );

           walk_hierarchy ( subdir_pathname, cmd_to_execute );

           /* Descending into the subdirectory may have changed the current  */
           /* directory. As a safety measure, change back to the directory   */
           /* provided as the argument to this call.                         */

           chdir ( pathname );
       }

       /* scandir builds a table of malloc'd directory entries. As we finish */
       /* each entry, free the memory associated with it.                    */

       free ( files[i] );

    } /* Per file loop */

    /* The table of directory entries returned by scandir is itself a        */
    /* malloc'd segment. Free it as well now that we are all done with this  */
    /* directory.                                                            */

    free ( files );

    if ( verbose )
       printf( "%s %s\n", "Leaving Directory: ", pathname);

} /* walk_hierarchy */

/* .page */

int main ( argc, argv )   

/*
==============================================================================

This  is  the  walk  mainline.  It  initiates  parsing  of  the  command  line
arguments,  establishes  the  complete pathname of the starting directory, and
initiates  the  walk_hierarchy  process. It generates a final message when the
process is completed. 

==============================================================================
*/

int argc;
char *argv[];

{ 
    BOOL help;                              /* Has help been requested?      */
    char start_dir      [ MAX_PATH_LEN ];   /* Starting directory pathname    */
    char cmd_to_execute [ MAX_CMD_SIZE ];   /* Command to execute each level */                     

    directories_visited = 0;

    parse_arguments ( argc, argv, &help, &verbose, start_dir, cmd_to_execute );

    if ( help || strlen(start_dir) == 0 || strlen(cmd_to_execute) == 0 )
    {
       /* Help has been requested - provide the same and then exit */

       printf ( "%s\n\n", "Usage: walk [-h | --help | -v | --verbose] start_dir cmd_to_execute" );
       printf ( "%s\n",   "walk descends a directory hierarchy, executing cmd_to_execute" );
       printf ( "%s\n\n", "at each level of the hierarchy. Enclose cmd_to_execute in quotes" );

       exit (0);
    }

    /* Change to the supplied directory */

    if ( chdir(start_dir) )
    { 
       printf ( "%s\n", "Cannot enter start directory" );
       exit(1);
    }
    
    /* Get its full pathname */

    if ( getcwd(start_dir, MAX_PATH_LEN) == NULL )
    {
       printf ( "%s\n", "Failed to set up start dir absolute pathname" );
       exit(1);
    }

    if ( verbose )
    {
       printf ( "\n%s %s \n", "Linux walk, v1.0 - Starting Directory: ", start_dir      );
       printf ( "%s %s \n\n", "Command to  be Executed Per Directory: ", cmd_to_execute );
    }

    /* OK, here goes! Lets dive into the directory hierarchy */

    walk_hierarchy ( start_dir, cmd_to_execute );

    /* Print out some final stats and we are done */

    printf( "\n%s %d %s\n\n", "walk completed.", directories_visited, "directories visited" );

} /* walk main */

