/*
 *	descend - a function to do a recursive descent of the unix filestore
 *		applying a function to each file and directory found.
 *
 *	It processes a directory before processing the files in the directory
 *	itself. This makes it easier to implement, but means that decisions
 *	cannot be made about the fate of directories according to their
 *	updated contents. EG removing junk files and empty directories.
 *	If anybody can think of a way to do it the nice way...
 *	
 *	To be kind to namei, we cd down into directories instead of growing
 *	long pathnames.
 *
 *	arguments to descend:	name of file/directory in which to start
 *				pointer to function to apply to each file.
 *
 *	arguments to function:	name of file relative to cwd.
 *				full name of file from starting directory.
 *				pointer to stat structure for that file.
 *					(may be NULL if not stattable)
 *
 *	return value from descend:
 *	descend returns the logical OR of the values returned by all the
 *	applications of the function, 0 or 1. It is envisaged that return value
 *	of 1 will indicate successfully finding some file or other.
 *	If some fatal error occurred, it returns -1.
 *	The function may also cause the descent to be abandoned by returning -1.
 *
 *	BUGS
 *		If descend() chdirs down into a directory but cannot get
 *	back up again, it returns immediately with value -1, and leaves its
 *	caller in the strange directory.
 *
 *		May behave strangely if told to start from "" (I'm not sure).
 */

#include <stdio.h>	/* for NULL */
#include <sys/types.h>
#include <sys/stat.h>
#include <setjmp.h>

extern char *strcpy();

/*
 *	Cope with symbolic links, and lack thereof on 4.1BSD
 */
#ifdef S_IFLNK

  /* assume 4.2BSD */
# include <sys/dir.h>

#else

  /* no symbolic links on this system */
# define lstat stat
  /* Flaky assumption that systems without symlinks have -lndir */
#include <ndir.h>

#endif

static void desc();

static char fullname[1024];	/* full pathname from starting point */
static result;			/* the value we are to return */
static int giveup;		/* has function() told us to abandon? */
static jmp_buf panic_env;	/* abort on fatal error */

int
descend(name, function)
char *name;
int (*function)();
{
	char *cp;

	result = 0; giveup = 0;
	/* cope with the historical filename "", meaning "." */
	(void) strcpy(fullname, name ? name : ".");
	for (cp = fullname; *cp != '\0'; cp++);
	if (setjmp(panic_env) != 0) {
		return(-1);
	}
	desc(name, function, cp);
	return(result);
}

/*
 *	same as descend except that it is handed a pointer to the terminating
 *	null of the full filename.
 */
static void
desc(name, function, endp)
char *name;
int (*function)();
char *endp;
{
	struct stat stbuf;
	int stattable;		/* could we stat the file? */
	
	/*	arguments to function:
	 *		name of file relative to cwd.
	 *		full name of file from starting directory.
	 *		pointer to stat structure for that file (perh. NULL).
	 */

	stattable = (lstat(name, &stbuf) == 0);

	switch ((* function)(name, fullname, stattable ? &stbuf : NULL)) {
	case 0:
		break;
	case 1:
		result = 1;
		break;
	case -1:
		giveup = 1;	/* tell parents to abort */
		return;
	}
		
	if (!stattable) return;

	/*
	 *	If directory, apply ourselves to its contents
	 */
	switch (stbuf.st_mode & S_IFMT) {
	case S_IFDIR:
		if (chdir(name) == 0) {
			DIR *dirp;
			struct direct *direct;
			
			*endp++ = '/';
			dirp = opendir(".");
			if (dirp != NULL) {
				/* fails when perm = 711 and you are mortal */
				/* and when you run out of fds due to depth */
				while ((direct = readdir(dirp)) != NULL) {
					char *cp, *dp;

					/* Discard . and .. */
					if (   direct->d_name[0] == '.'
					    && (   direct->d_name[1] == '\0' /*.*/
						|| (   direct->d_name[1] == '.'
						    && direct->d_name[2] == '\0' /*..*/
						    ))) {
							continue;
					}

					/* Grow the full filename.
					 * strcpy(endp, direct->d_name)
					 * Do inline not for speed, but so that
					 * we get a pointer to the end of the new
					 * string for free */
					for (	cp=endp, dp=direct->d_name;
						(*cp = *dp) != '\0';
						cp++, dp++ );
					desc(direct->d_name, function, cp);
					if (giveup) break;
					/* Must not use *direct after recursion
					 * because it may have been overwritten */
				}
				closedir(dirp);
			}
			*--endp = '\0';
			if (chdir("..")) {
				longjmp(panic_env, 1);
			}
		}
	}
}
