/* 
 * find.c - locate and optionally perform operations on files.
 * The author says this is only partly done, but I don't know what is missing.
 * But one thing that is certainly a bug is the existence of
 * MAXNAMELEN and MAXPATHLEN.
 */

#include <stdio.h>
#include <sys/types.h>			/* typedefs */
#include <sys/stat.h>			/* structure returned by stat      */
#ifdef HP-UX
#include <ndir.h>			/* BSD directory entry structure   */
#else
#include <dir.h>			/* directory entry structure   */
#endif

#define MAXPATHLEN 	256
#define MAXNAMELEN 	256
#define TRUE		1
#define FALSE		0
#define VALID		1
#define INVALID		0
#define	MIN_NODES	20

extern	DIR *opendir();
extern	struct direct *readdir();
extern	long telldir();
extern	void seekdir();
extern	void closedir();
extern  char *malloc();

void	ScanDir();
struct  fnode *GetNode();

typedef	unsigned char	bool;

struct	fnode {
		struct fnode  *next;
		char name[MAXNAMELEN];
	       };

struct	fnode *avail_head = NULL;


int	numpaths;			/* Number of paths to search */
int	Argc;				/* Global argc for functions */
char	**Argv;				/* Global argv for functions */

int	max_nodes_avail = 0;
int 	nodes_used = 0;
int 	nodes_freed = 0;

main (argc, argv)			
    int  argc;
    char *argv[];
{
    int i;				/* general purpose index */
    int pathindex;			/* argv[pathindex] = current path */
    char buf[MAXPATHLEN];		/* buffer to contain current path */

    /* Validate Usage */
    if (argc < 2) {
	fprintf(stderr,"Usage: find path-list predicate-list\n");
	exit(1);
    }

    /* Make argv and argc global for other functions */
    Argc = argc;
    Argv = argv;

    /* Parse Arguments and Validate */
    if (! ParseArguments()) {
	fprintf(stderr,"find: parsing error\n");
	exit(1);
    }

    /* Refresh the pool used to contain unevaluated directory entrys */
    RefreshPool();


    /* Descend argv[1] through argv[numpaths] */
    for (pathindex = 1; pathindex <= numpaths; pathindex++) {
	for (i = 0; i < sizeof (buf); i++)
	    buf[i] = '\0';
	strcpy(buf,argv[pathindex]);
	ScanDir(buf);
    }
    
    /* Print statistics */
    printf("\n\n\n");
    printf("Max nodes avail: %d\n",max_nodes_avail);
    printf("Nodes used:      %d\n",nodes_used);
    printf("Nodes freed:     %d\n",nodes_freed);
}

/*
 * Parse Arguments - calculate number of paths in path-list and compile
 *		     arguments into a linked list of functions to be
 *		     applied to each visited node.
 *
 *	side effects: global variable "numpaths" = numpaths to search.
 *
 *	returns:     VALID = argument list is valid
 *		     INVALID = invalid argument list
 */
int
ParseArguments()
{
    /* Calculate the number of paths in the path list */
    numpaths = Argc - 1;
    return VALID;
}




/*
 * RefreshPool - keep a minimum number of nodes to remember directory in.
 */
int
RefreshPool()
{
    struct fnode *anode;
    int i;

    for (i = 0; i < MIN_NODES; i++) {
	anode = (struct fnode *) malloc(sizeof(struct fnode));
	anode->next = avail_head;
	avail_head  = anode; 
    }
    max_nodes_avail += MIN_NODES;
}




/*
 * GetNode - acquire a node to save directory's file names in during descent.
 */
struct fnode
*GetNode()
{
    struct fnode *ap;

    if (avail_head == NULL) 
	RefreshPool();

    ap = avail_head;
    avail_head = avail_head->next;
    ap->next = NULL;
    nodes_used++;

    return ap;
}



/*
 * FreeNode - return file node to pool.
 */
void
FreeNode(nodeptr)
    struct fnode *nodeptr;
{

    nodeptr->next = avail_head;
    avail_head = nodeptr;
    nodes_freed++;

}

/*
 * ScanDir - scan the named directory and if file matches search criteria
 *	     apply any specified operations to it.
 */
void
ScanDir(name)	
    char *name;
{
    struct direct dirbuf, *dp = &dirbuf;
    DIR *d;
    struct fnode anode, *ap, *list_head, *trailp; 
    struct stat filestat;




    /* If we cannot open this directory, ignore it */
    if ((d = opendir(name)) == NULL) {
	printf("Cannot open directory: %s\n",name);
	return;
    }


    /* Change directories to requested directory */
    chdir(name);


    /* Obtain the head of the linked list of directory entries */
    list_head = GetNode();
    ap = list_head;


    /* Build a list of all of the relevant entries within this directory */
    for (dp = readdir(d); dp != NULL; dp = readdir(d)) {
	/* skip the current directory and its parent */
	if ( (strcmp(dp->d_name,".") == 0) || (strcmp(dp->d_name,"..") == 0) )
	    continue;
	strcpy(ap->name,dp->d_name);
	trailp = ap;
	ap = GetNode();
	trailp->next = ap;
    }

    /* We've saved a copy of all the entries in this directory */
    closedir(d);

    /* Perform processing for all files within this directory */
    for (ap=list_head;ap->next!=NULL;trailp=ap->next,FreeNode(ap),ap=trailp) {

	/* Get file status */
	if (stat(ap->name, &filestat) == -1) {
	    fprintf(stderr, "newfind: can't find %s\n", ap->name);
	    return;
	}


	/* Print the size of this file */
	printf("Visiting: %8ld %s/%s\n", filestat.st_size, name,ap->name);

	/* If file is a directory, descend */
	if (filestat.st_mode & S_IFDIR) {
	    DIR *subd;				/* sub-directory */
	    char cwd[MAXPATHLEN+1];		/* current working directory */
	    chdir(ap->name);
	    if ((subd = opendir(".")) == NULL) {
		printf("cannot open directory: %s\n",ap->name);
		exit(-1);
	    }
	    getwd(cwd);
	    closedir(subd);
	    ScanDir(cwd);
	    chdir("..");
	}
    }

    /* Free the unused trailing node on the list */
    FreeNode(ap);

}






