[comp.os.minix] Alistair G. Crooks version of find.c

ast@cs.vu.nl (Andy Tanenbaum) (06/15/87)

Here is a version of find written by Alistair Crooks.  
Andy Tanenbaum (ast@cs.vu.nl)

------------------------ cut here for manual --------------------------------

NAME
	find - find files

SYNOPSIS
	find pathname-list expression
	find name

DESCRIPTION
	find can be used to find a (number of) file(s) in a file system
	It is modelled after 4.3BSD's find, but has certain limitations.
	These are listed at the end of this page. There are two forms
	that the find command can take - the shortform, which is basically
		find / -name "*name*" -print
	in the longer form. The longer form has the following valid
	expressions :

 	-type x
		where x is bcdfgu for block special file, character
		special file, directory, regular file, setgid file
		and setuid file.
	-name s
		where the name of the file is s, which can contain
		wile cards, same as sh(1).
	-inum n
		where n is the inode number of the file.
	-mtime [+-]n
		where the modification time of the file is n. +n means
		more than n, -n means less than n, and n means exactly n.
		n is the number of days.
	-atime [+-]n
		where the access time of the file is n. +n means
		more than n, -n means less than n, and n means exactly n.
		n is the number of days.
	-ctime [+-]n
		where the change time of the file is n. +n means
		more than n, -n means less than n, and n means exactly n.
		n is the number of days.
	-size [+-]n[c]
		where the size of the file is n. Default radix is blocks.
		If the number is followed by the character 'c', the size
		unit of bytes is used.  +n means more than n (blocks), -n
		means less than n (blocks), n means exactly n (blocks).
	-links [+-]n
		where the number of links possessed by a file is n. +n
		means more than n, -n means less than n, and n means exactly
		n.
	-depth
		Does a depth first search of the file system. Default is
		to search a directory's entries after processing the directory
		itself. This expression means the directory entries are
		processed before the directory.
	-prune
		Does not progress the search into directories.
	-newer f
		where f is a file. Finds files that are newer than f.
	-perm [-]n
		where n is an octal number of the file permission form.
		If the number is preceded by '-', a long permission
		mode is used.
	-user u
		where u is a login name, or a uid. Finds files belonging
		to user u.
	-nouser
		finds bastard files (files whose owners are not in
		/etc/passwd).
	-group a
		where g is a group name or gid. Finds files belonging
		to group g.
	-nogroup
		finds group-delinquent files. (files whose group is no
		longer in /etc/group).

	In addition, the expressions can have the following Boolean
	operators:
	-a
		Boolean and operator.
	-o
		Boolean or operator.
	( and )
		group expressions together
	!
		Boolean not operator

	Find also has the following actions:
	-print
		print the file name on standard output.
	-exec
		execute a Minix command. The file name can be interpreted
		here by a pair of braces ("{}"). The command should (but
		not necessarily be concluded by a quoted semi-colon ("\;").
	-ok
		prints the command on standard output, and waits for the
		user to enter a character from the keyboard. If the character
		is 'y', the command will be executed, otherwise it will not.
	-ls
		provides a long listing of all salient points about the file,
		and is equivalent to the 4.3BSD Unix ls -gilds.

BUGS AND ENHANCEMENTS
	1. Only one directory can be used as the starting point for the
	   search. This is relatively simple to cure.
	2. Only one action can be specified (in contrast to the BSD
	   version) which views everything as an expression, and actions
	   as true expressions. This should be simple to cure as well.
	3. The building of the internal search tree leaves a little to
	   be desired. I'm not sure bracketed groups are done in the right
	   way, and the code is a mess.
	4. Build to run under many more different flavours of Unix.

AUTHOR
	Alistair G. Crooks, Joypace Ltd, London, UK, 1987.
	(agc@ist.co.uk, until July).
------------------------ cut here for find.c --------------------------------
/*
 *	find.c		1.1	7/6/87		agc	Joypace Ltd.
 *
 *	Copyright 1987, Joypace Ltd., London UK. All rights reserved.
 *	This code may be freely distributed, provided that this notice
 *	remains attached.
 *
 *	find - a public domain interpretation of find(1).
 *
 *	Compile-time flags:	-DMINIX for Minix (V7 file system, etc)
 *				-DSUN for SunOS (dp_ino or dp_fileno)
 *	default env:		BSD 4.3
 */

#include <stdio.h>

#ifdef	MINIX
#include <blocksize.h>
#include <stat.h>
struct passwd {
	char *pw_name;
	char *pw_passwd;
	int pw_uid;
	int pw_gid;
	char *pw_gecos;
	char *pw_dir;
	char *pw_shell;
};
struct	group { 
	char	*gr_name;
	char	*gr_passwd;
	int	gr_gid;
};
#else
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/dir.h>
#include <pwd.h>
#include <grp.h>
#define	BLOCK_SIZE	1024
#endif

p
#define	LINELEN		256		/* max no of chars in a line */
#define	SHORTLINE	80		/* a shorter line */
#define	DIRNAMELEN	14		/* max no of chars in a dir entry */
#define	MAXCRIT		10		/* max no of search criteria */
#define	MODELEN		11		/* length of mode readout */
#define	NAMELEN		8		/* length of a user name readout */

#define SHORTMASK	0777		/* short file permission mask */
#define LONGMASK	017777		/* long file permission mask */

#define ISDIGIT(x)	((x) >= '0' && (x) <= '9')
#define	ISOCT(c)	((c) >= '0' && (c) <= '7')

#define	CR_TYPE		1		/* -type criterion */
#define	CR_NAME		2		/* -name criterion */
#define	CR_INUM		3		/* -inum criterion */
#define	CR_MTIME	4		/* -mtime criterion */
#define	CR_ATIME	5		/* -atime criterion */
#define	CR_CTIME	6		/* -ctime criterion */
#define	CR_SIZE		7		/* -size criterion */
#define	CR_LINKS	8		/* -links criterion */
#define	CR_DEPTH	9		/* -depth criterion */
#define	CR_PRUNE	10		/* -prune criterion */
#define	CR_NEWER	11		/* -newer criterion */
#define	CR_PERM		12		/* -perm criterion */
#define	CR_USER		13		/* -user criterion */
#define	CR_NOUSER	14		/* -nouser criterion */
#define	CR_GROUP	15		/* -group criterion */
#define	CR_NOGROUP	16		/* -nogroup criterion */

#define ACT_PRINT	17		/* -print action */
#define	ACT_EXEC	18		/* -exec action */
#define	ACT_OK		19		/* -ok action */
#define	ACT_LS		20		/* -ls action */

#define AR_LT		21		/* arithmetical < */
#define	AR_EQ		22		/* arithmetical == */
#define	AR_GT		23		/* arithmetical > */

#define	OP_AND		24		/* and operator for node */
#define	OP_OR		25		/* or operator for node */
#define	OP_OPENB	26		/* open bracket operator for node */
#define	OP_CLOSEB	27		/* close bracket operator for node */
#define	OP_NOT		28		/* ! operator for node */

#define	ND_OP		29		/* operator node */
#define	ND_CRIT		30		/* criterion node */

p
typedef struct num {
	long	nm_num;
	char	nm_op;
} NUM;

typedef struct _critstr {
	char		cr_type;
	char		cr_not;
	union {
		char	*cr_name;
		NUM	cr_n;
	} cr_un;
} CRIT;

typedef struct _nodestr {
	char		nd_type;
	union {
		CRIT	*nd_crit;
		int	nd_op;
	} nd_un;
	struct _nodestr	*nd_left;
	struct _nodestr	*nd_right;
} NODE;

typedef struct _argstr {
	char	*ar_name;
	int	ar_argc;
	int	ar_ndtype;
	int	ar_attr;
} ARG;

#ifdef MINIX
typedef struct dir {
	short	inum;
	char	d_name[DIRNAMELEN];
} DIR;
DIR	dir;
#endif

p
char	*prog;				/* program name */
char	*startdir;			/* dir to start from */
char	cmd[LINELEN];			/* command to be executed */
char	expanded[LINELEN];		/* command with expanded {}'s */
int	act;				/* type of action to take */
int	bracec = 0;			/* no of braces in cmd */
NODE	*root;				/* root of search criteria tree */
CRIT	criteria[MAXCRIT];		/* search criteria */
int	critc = 0;			/* number of criteria */
int	depthfirst = 0;			/* depth first search of tree */
int	prune = 0;			/* stop at directories */

extern long	time(); 		/* no see */
extern char	*malloc();		/* shut lint up */
extern char	*ctime();		/* ditto */
extern long	atol();			/* nearly there */

p
ARG	argtab[] = {
	{"-o",		1,	ND_OP,		OP_OR},
	{"-a",		1,	ND_OP,		OP_AND},
	{"(",		1,	ND_OP,		OP_OPENB},
	{")",		1,	ND_OP,		OP_CLOSEB},
	{"!",		1,	ND_OP,		OP_NOT},
	{"-name",	2,	ND_CRIT,	CR_NAME},
	{"-type",	2,	ND_CRIT,	CR_TYPE},
	{"-inum",	2,	ND_CRIT,	CR_INUM},
	{"-mtime",	2,	ND_CRIT,	CR_MTIME},
	{"-ctime",	2,	ND_CRIT,	CR_CTIME},
	{"-atime",	2,	ND_CRIT,	CR_ATIME},
	{"-size",	2,	ND_CRIT,	CR_SIZE},
	{"-depth",	1,	ND_CRIT,	CR_DEPTH},
	{"-prune",	1,	ND_CRIT,	CR_PRUNE},
	{"-links",	2,	ND_CRIT,	CR_LINKS},
	{"-newer",	2,	ND_CRIT,	CR_NEWER},
	{"-perm",	2,	ND_CRIT,	CR_PERM},
	{"-user",	2,	ND_CRIT,	CR_USER},
	{"-nouser",	1,	ND_CRIT,	CR_NOUSER},
	{"-group",	2,	ND_CRIT,	CR_GROUP},
	{"-nogroup",	1,	ND_CRIT,	CR_NOGROUP},
	{NULL, 0, 0, 0}
};

p
/*
 *	myindex - index gets corrupted by that used in scanf.
 */
char *
myindex(s, c)
register char	*s;
register char	c;
{
	for ( ; *s ; s++)
		if (*s == c)
			return(s);
	return(NULL);
}

p
#ifdef MINIX
/*
 *	system - re-implemented for Minix. Possibly quicker to use execl
 *	rather than splitting the command line, but seems to be quick
 *	enough.
 */
int
system(s)
char	*s;
{
	int	pid;
	int	status;

	switch(pid = fork()) {
	case -1:
		(void) fprintf(stderr, "%s: fork failed\n", prog);
		return(127);
	case 0 :
		execl("/bin/sh", "sh", "-c", s, 0);
		(void) fprintf(stderr, "%s: exec failed\n", prog);
		return(127);
	default :
		while (wait(&status) != pid)
			;
	}
	return(status);
}
#endif


p
#ifdef MINIX
/*
 *	getpwuid - get the passwd file entry with the same uid as usrid.
 *	Returns the address in static area if found, NULL otherwise.
 */
struct passwd *
getpwuid(usrid)
int	usrid;
{
	static struct passwd	pw;
	static char		lbuf[100];
	char			*ptr;
	int			bin;
	FILE			*fp, *fopen();

	if ((fp = fopen("/etc/passwd", "r")) == (FILE *) NULL)
		return(NULL);
	while (fgets(lbuf, sizeof(lbuf), fp) != NULL) {
		for (ptr = pw.pw_name = lbuf; *ptr != 0 && *ptr != ':' ; ptr++)
			;
		if (*ptr == 0)
			continue;
		for (*ptr++ = 0, pw.pw_passwd = ptr ; *ptr != 0 && *ptr != ':' ; ptr++)
			;
		if (*ptr == 0)
			continue;
		for (bin = 0, *ptr++ = 0 ; *ptr && ISDIGIT(*ptr) ; ptr++)
			bin = 10*bin + (*ptr - '0');
		if (bin != usrid)
			continue;
		pw.pw_uid = bin;
		for (bin = 0, ++ptr; *ptr && ISDIGIT(*ptr) ; ptr++)
			bin = 10*bin + (*ptr - '0');
		pw.pw_gid = bin;
		for (pw.pw_gecos = ++ptr; *ptr && *ptr != ':' && *ptr != '\n' ; ptr++)
			;
		for (*ptr = 0, pw.pw_dir = ++ptr; *ptr && *ptr != ':' && *ptr != '\n' ; ptr++)
			;
		*ptr = 0 ;
		for (pw.pw_shell = ++ptr; *ptr && *ptr != '\n' ; ptr++)
			;
		*ptr = '\0';
		fclose(fp);
		return(&pw);
	}
	fclose(fp);
	return(NULL);
}
#endif


p
#ifdef MINIX
/*
 *	getpwnam - get passwd entry with same login name as s.
 *	Returns address in static area if found, NULL otherwise.
 */
struct passwd *
getpwnam(s)
char	*s;
{
	static struct passwd	pw;
	static char		lbuf[100];
	char			*ptr;
	int			bin;
	FILE			*fp, *fopen();

	if ((fp = fopen("/etc/passwd", "r")) == (FILE *) NULL)
		return(NULL);
	while (fgets(lbuf, sizeof(lbuf), fp) != NULL) {
		for (ptr = pw.pw_name = lbuf; *ptr != 0 && *ptr != ':' ; ptr++)
			;
		if (*ptr == 0)
			continue;
		*ptr++ = 0;
		if (strcmp(s, pw.pw_name) != 0)
			continue;
		for (pw.pw_passwd = ptr ; *ptr != 0 && *ptr != ':' ; ptr++)
			;
		if (*ptr == 0)
			continue;
		for (bin = 0, *ptr++ = 0 ; *ptr && ISDIGIT(*ptr) ; ptr++)
			bin = 10*bin + (*ptr - '0');
		pw.pw_uid = bin;
		for (bin = 0, ++ptr; *ptr && ISDIGIT(*ptr) ; ptr++)
			bin = 10*bin + (*ptr - '0');
		pw.pw_gid = bin;
		for (pw.pw_gecos = ++ptr; *ptr && *ptr != ':' && *ptr != '\n' ; ptr++)
			;
		for (*ptr = 0, pw.pw_dir = ++ptr; *ptr && *ptr != ':' && *ptr != '\n' ; ptr++)
			;
		*ptr = 0 ;
		pw.pw_shell = ++ptr;
		fclose(fp);
		return(&pw);
	}
	fclose(fp);
	return(NULL);
}
#endif

p
#ifdef MINIX
/*
 *	getgrgid - get group entry with same uid as passed to it.
 *	Returns address of entry in static area if found, NULL otherwise.
 */
struct group *
getgrgid(gid)
int	gid;
{
	static struct group	gp;
	static char		lbuf[100];
	char			*ptr;
	int			bin;
	FILE			*fp, *fopen();

	if ((fp = fopen("/etc/group", "r")) == (FILE *) NULL)
		return(NULL);
	while (fgets(lbuf, sizeof(lbuf), fp) != NULL) {
		for (ptr = gp.gr_name = lbuf; *ptr != 0 && *ptr != ':' ; ptr++)
			;
		if (*ptr == 0)
			continue;
		for (*ptr++ = 0, gp.gr_passwd = ptr ; *ptr != 0 && *ptr != ':' ; ptr++)
			;
		if (*ptr == 0)
			continue;
		for (bin = 0, *ptr++ = 0 ; *ptr && ISDIGIT(*ptr) ; ptr++)
			bin = 10*bin + (*ptr - '0');
		if (bin != gid)
			continue;
		gp.gr_gid = bin;
		fclose(fp);
		return(&gp);
	}
	fclose(fp);
	return(NULL);
}
#endif


p
#ifdef MINIX
/*
 *	getgrnam - get the group file entry corresponding to group name s.
 *	Returns address of entry in static store if found, NULL otherwise.
 */
struct group *
getgrnam(s)
char	*s;
{
	static struct group	gp;
	static char		lbuf[100];
	char			*ptr;
	int			bin;
	FILE			*fp, *fopen();

	if ((fp = fopen("/etc/group", "r")) == (FILE *) NULL)
		return(NULL);
	while (fgets(lbuf, sizeof(lbuf), fp) != NULL) {
		for (ptr = gp.gr_name = lbuf; *ptr != 0 && *ptr != ':' ; ptr++)
			;
		if (*ptr == 0)
			continue;
		*ptr++ = 0;
		if (strcmp(s, gp.gr_name) != 0)
			continue;
		for (gp.gr_passwd = ptr ; *ptr != 0 && *ptr != ':' ; ptr++)
			;
		if (*ptr == 0)
			continue;
		for (bin = 0, *ptr++ = 0 ; *ptr && ISDIGIT(*ptr) ; ptr++)
			bin = 10*bin + (*ptr - '0');
		gp.gr_gid = bin;
		fclose(fp);
		return(&gp);
	}
	fclose(fp);
	return(NULL);
}
#endif

#ifdef MINIX
/*
 *	ctime - doesn't seem to be in libc on Minix. Used to format
 *	time, passed as pointer to long (number of seconds). Returns
 *	pointer to static char string containing formatted time. Code
 *	stolen and hacked from date.c.
 */
int days_per_month[] =
  { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
char *months[] =
  { "Jan", "Feb", "Mar", "Apr", "May", "Jun",
    "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" };
char *days[] =
  { "Thu", "Fri", "Sat", "Sun", "Mon", "Tue", "Wed" };

struct {
	int year, month, day, hour, min, sec;
} tm;

#define S_P_MIN		60L
#define S_P_HOUR	60L * 60L
#define S_P_DAY		24L * 60L * 60L
#define S_P_YEAR	365L * 24L * 60L * 60L

char *
ctime(tp)
long *tp;
{
	static char 	out[26];
	long		t = *tp;

	tm.year = 0;
	tm.month = 0;
	tm.day = 1;
	tm.hour = 0;
	tm.min = 0;
	tm.sec = 0;
	while (t >= S_P_YEAR) {
		if (((tm.year + 2) % 4) == 0)
			t -= S_P_DAY;
		tm.year += 1;
		t -= S_P_YEAR;
	}
	if (((tm.year + 2) % 4) == 0)
		days_per_month[1]++;
	tm.year += 1970;
	while (t >= (days_per_month[tm.month] * S_P_DAY))
		t -= days_per_month[tm.month++] * S_P_DAY;
	while (t >= S_P_DAY) {
		t -= S_P_DAY;
		tm.day++;
	}
	while (t >= S_P_HOUR) {
		t -= S_P_HOUR;
		tm.hour++;
	}
	while (t >= S_P_MIN) {
		t -= S_P_MIN;
		tm.min++;
	}
	tm.sec = (int) t;
	sprintf(out, "%s %s %2d %02d:%02d:%02d %d\n",
		 days[(int)((t / S_P_DAY) % 7)],
		 months[tm.month], tm.day, tm.hour, tm.min, tm.sec, tm.year);
	return(out);
}
#endif



p
#ifdef MINIX
/*
 *	LSTAT - Minix's V7 File system has no symbolic links,
 *	so a crude work around is used here.
 */
#define	LSTAT	stat
#else
#define LSTAT	lstat
#endif

p
/*
 *	fatal - report an error, and exit
 */
void
fatal(s)
char	*s;
{
	(void) fprintf(stderr, "%s: %s\n", prog, s);
	exit(1);
}


p
/*
 *	newnode - create a new node, with the arguments passed to it.
 */
NODE *
newnode(typ, op, crp, left, right)
int	typ;
int	op;
CRIT	*crp;
NODE	*left;
NODE	*right;
{
	NODE	*np;

	if ((np = (NODE *) malloc(sizeof(NODE))) == (NODE *) NULL)
		fatal("can't malloc a node");
	np->nd_type = typ;
	switch (typ) {
	case ND_CRIT:
		np->nd_un.nd_crit = crp;
		break;
	case ND_OP:
		np->nd_un.nd_op = op;
		break;
	}
	np->nd_left = left;
	np->nd_right = right;
	return(np);
}

p
/*
 *	dotim - calculate the time stamp desired.
 */
void
dotim(s, np)
char	*s;
NUM	*np;
{
	np->nm_op = (*s == '+') ? AR_LT : (*s == '-') ? AR_GT : AR_EQ;
	(void) time(&(np->nm_num));
	np->nm_num -= (atol(++s) * 24L * 60L *60L);
}

p
/*
 *	nextcrit - return the address of the next criterion, assuming
 *	that we have some left.
 */
CRIT *
nextcrit()
{
	if (critc == MAXCRIT)
		fatal("too many search criteria");
	return(&criteria[critc++]);
}

p
/*
 *	octal - convert s to an octal number. Used for file permissions.
 *	Returns the long octal number derived from s.
 */
long
octal(s)
char	*s;
{
	long	l;

	for (l = 0; *s && ISOCT(*s) ; s++)
		l = l * 8L + *s - '0';
	return(l);
}


p
/*
 *	finduid - get the uid corresponding to the argument string s.
 *	Could be numeric or username.
 */
long
finduid(s)
char	*s;
{
	register char	*cp;
	struct passwd	*pwp;
	int		numeric;

	for (cp = s, numeric = 1 ; *cp && numeric ; cp++)
		if (!ISDIGIT(*cp))
			numeric = 0;
	if (numeric)
		return(atol(s));
	if ((pwp = getpwnam(s)) == NULL)
		fatal("user not found");
	return((long) pwp->pw_uid);
}


p
/*
 *	findgid - get the gid corresponding to the argument string s.
 *	Could be numeric or group name.
 */
long
findgid(s)
char	*s;
{
	register char	*cp;
	struct group	*grp;
	int		numeric;

	for (cp = s, numeric = 1 ; *cp ; cp++)
		if (!ISDIGIT(*cp))
			numeric = 0;
	if (numeric)
		return(atol(s));
	if ((grp = getgrnam(s)) == NULL)
		fatal("group not found");
	return((long) grp->gr_gid);
}

p
/*
 *	fillcmd - fill the string cmd with the command to be executed,
 *	starting at argv[i], until all arguments done.
 */
void
fillcmd(argc, argv, i)
int	argc;
char	**argv;
int	i;
{
	char	*cp = cmd;
	char	*cp2 = argv[i];

	for ( ; i < argc ; cp2 = argv[++i], *cp++ = ' ')
		while (*cp2)
			if ((*cp++ = *cp2++) == '{')
				bracec++;
	while (*(cp-1) == ' ')
		cp--;
	*cp = '\0';
}

p
/*
 *	argfind - takes the string s and returns its place in
 *	the argument table. NULL if not found.
 */
ARG *
argfind(s)
char	*s;
{
	ARG	*ap;

	for (ap = argtab ; ap && ap->ar_name ; ap++)
		if (strcmp(s, ap->ar_name) == 0)
			return(ap);
	return(NULL);
}

p
/*
 *	getcrit - recognise one criterion from the command line.
 *	pi will be incremented by number of arguments used in the
 *	criterion. Returns the address of the new node, if the
 *	criterion is not an operator, otherwise NULL, and status
 *	is filled with the operator type.
 */
NODE *
getcrit(pi, argv)
int	*pi;
char	**argv;
{
	struct stat	s;
	CRIT		*crp;
	char		*cp;
	long		l;
	ARG		*ap;

	if ((ap = argfind(argv[++*pi])) == NULL) {
		*pi--;
		return(NULL);
	}
	if (ap->ar_ndtype != ND_CRIT)
		return(NULL);
	switch(ap->ar_attr) {
	case CR_NAME:
		crp = nextcrit();
		crp->cr_type = CR_NAME;
		crp->cr_un.cr_name = argv[++*pi];
		break;
	case CR_TYPE:
		crp = nextcrit();
		crp->cr_type = CR_TYPE;
		crp->cr_un.cr_name = argv[++*pi];
		if (myindex("bcdfgu", *argv[*pi]) == NULL)
			fatal("bad type identifier");
		break;
	case CR_INUM:
		crp = nextcrit();
		crp->cr_type = CR_INUM;
		if ((crp->cr_un.cr_n.nm_num = atoi(argv[++*pi])) <= 0)
			fatal("Bad inode number");
		break;
	case CR_MTIME:
		crp = nextcrit();
		crp->cr_type = CR_MTIME;
		dotim(argv[++*pi], &(crp->cr_un.cr_n));
		break;
	case CR_CTIME:
		crp = nextcrit();
		crp->cr_type = CR_CTIME;
		dotim(argv[++*pi], &(crp->cr_un.cr_n));
		break;
	case CR_ATIME:
		crp = nextcrit();
		crp->cr_type = CR_ATIME;
		dotim(argv[++*pi], &(crp->cr_un.cr_n));
		break;
	case CR_SIZE:
		crp = nextcrit();
		crp->cr_type = CR_SIZE;
		cp = argv[++*pi];
		crp->cr_un.cr_n.nm_op = (*cp == '+') ? AR_GT : (*cp == '-') ? AR_LT : AR_EQ;
		if (*cp == '+' || *cp == '-')
			cp++;
		for (l = 0L ; *cp && ISDIGIT(*cp);)
			l = l * 10L + (long) (*cp++ - '0');
		crp->cr_un.cr_n.nm_num = (*cp && *cp == 'c') ? l : l * (long) BLOCK_SIZE;
		break;
	case CR_DEPTH:
		depthfirst = 1;
		break;
	case CR_PRUNE:
		prune = 1;
		break;
	case CR_LINKS:
		crp = nextcrit();
		crp->cr_type = CR_LINKS;
		cp = argv[++*pi];
		crp->cr_un.cr_n.nm_op = (*cp == '+') ? AR_GT : (*cp == '-') ? AR_LT : AR_EQ;
		if (*cp == '+' || *cp == '-')
			cp++;
		if ((crp->cr_un.cr_n.nm_num = atol(cp)) <= 0)
			fatal("Bad number of links");
		break;
	case CR_NEWER:
		if (LSTAT(argv[++*pi], &s) < 0)
			fatal("cannot stat file");
		crp = nextcrit();
		crp->cr_type = CR_NEWER;
		crp->cr_un.cr_n.nm_num = s.st_ctime;
		crp->cr_un.cr_n.nm_op = AR_GT;
		break;
	case CR_PERM:
		crp = nextcrit();
		crp->cr_type = CR_PERM;
		cp = argv[++*pi];
		crp->cr_un.cr_n.nm_op = (*cp == '-') ? AR_LT : AR_EQ;
		if (*cp == '-')
			cp++;
		crp->cr_un.cr_n.nm_num = octal(cp);
		break;
	case CR_USER:
		crp = nextcrit();
		crp->cr_type = CR_USER;
		crp->cr_un.cr_n.nm_num = finduid(argv[++*pi]);
		break;
	case CR_NOUSER:
		crp = nextcrit();
		crp->cr_type = CR_NOUSER;
		break;
	case CR_GROUP:
		crp = nextcrit();
		crp->cr_type = CR_GROUP;
		crp->cr_un.cr_n.nm_num = findgid(argv[++*pi]);
		break;
	case CR_NOGROUP:
		crp = nextcrit();
		crp->cr_type = CR_NOGROUP;
		break;
	}
	return(newnode(ND_CRIT, 0, crp, (NODE *)NULL, (NODE *)NULL));
}


p
/*
 *	getacts - recognise the actions from the command line.
 */
void
getacts(i, argc, argv)
int	i;
int	argc;
char	**argv;
{
	if (strcmp(argv[++i], "-print") == 0)
		act = ACT_PRINT;
	else if (strcmp(argv[i], "-exec") == 0) {
		act = ACT_EXEC;
		fillcmd(argc, argv, ++i);
	} else if (strcmp(argv[i], "-ls") == 0) {
		act = ACT_LS;
		fillcmd(argc, argv, ++i);
	} else if (strcmp(argv[i], "-ok") == 0) {
		act = ACT_OK;
		fillcmd(argc, argv, ++i);
	} else
		fatal("bad action");
}

p
/*
 *	getncrit - recognise n criteria, up to the start of the
 *	actions, or until a closing bracket received. Returns the
 *	top of the tree created by it.
 */
NODE *
getncrit(ip, argc, argv)
int	*ip;
int	argc;
char	**argv;
{
	NODE	*new;
	NODE	*top;
	char	prev = 0;
	ARG	*ap;

	for (top = NULL ; (ap = argfind(argv[*ip+1])) != NULL ; ) {
		if ((new = getcrit(ip, argv)) == NULL) {
			switch(ap->ar_attr) {
			case OP_OPENB :
				top = getncrit(ip, argc, argv);
				break;
			case OP_CLOSEB:
				return(top);
			case OP_NOT:
				prev = OP_NOT;
				break;
			case OP_AND:
			case OP_OR:
				if (argfind(argv[*ip+1]) == NULL)
					fatal("missing search criterion");
				top = newnode(ND_OP, ap->ar_attr,
					(CRIT *)NULL, top, (NODE *)NULL);
			}
		} else {
			if (prev == OP_NOT) {
				new->nd_un.nd_crit->cr_not = 1;
				prev = 0;
			}
			if (top == NULL)
				top = new;
			else if (top->nd_type == ND_CRIT)
				top = newnode(ND_OP, OP_AND, (CRIT *)NULL,
					top, new);
			else
				top->nd_right = new;
		}
	}
	return(top);
}

p
/*
 *	doargs - parse the command line arguments.
 */
void
doargs(argc, argv)
int	argc;
char	**argv;
{
	int	i = 1;

	startdir = argv[i];
	if (access(startdir, 01) < 0)
		fatal("cannot access start directory");
	root = getncrit(&i, argc, argv);
	getacts(i, argc, argv);
}

p
/*
 *	expand - take the command line, and replace all the occurrences
 *	of braces with the file name s.
 */
char *
expand(s)
char	*s;
{
	char	*cp;
	char	*cp2;
	char	*fn;

	for (cp2 = cmd, cp = expanded ; *cp2 && *cp2 != ';' ; ) {
		switch(*cp2) {
		case '{' :
			if (*(cp2+1) == '}') {
				for (fn = s ; *fn ; *cp++ = *fn++)
					;
				cp2 += 2;
			} else
				*cp++ = *cp2++;
			break;
		default :
			*cp++ = *cp2++;
		}
	}
	while (*(cp-1) == ' ')
		cp--;
	*cp = '\0';
	return(expanded);
}

p
/*
 *	prompt - prompt the user for input, and return char presented.
 */
int
prompt(s)
char	*s;
{
	int	c;

	(void) printf("%s [y/n]? ", s);
	(void) fflush(stdout);
	while ((c = getchar()) == '\n')
		;
	return(c);
}

p
/*
 *	fmtmode - format file permission mode (bit mask s) and print it.
 *	Returns 1 if a [char,block] special file, 0 otherwise.
 */
char
fmtmode(s)
int	s;
{
	char	ret = 0;

	switch(s & S_IFMT) {
	case S_IFDIR :
		putchar('d');
		break;
	case S_IFCHR :
		putchar('c');
		ret = 1;
		break;
	case S_IFBLK :
		putchar('b');
		ret = 1;
		break;
	default :
		putchar('-');
		break;
	}
	putchar((s & S_IREAD) ? 'r' : '-');
	putchar((s & S_IWRITE) ? 'w' : '-');
	putchar((s & S_ISUID) ? 's' : (s & S_IEXEC) ? 'x' : '-');
	putchar((s & (S_IREAD >> 3)) ? 'r' : '-');
	putchar((s & (S_IWRITE >> 3)) ? 'w' : '-');
	putchar((s & S_ISGID) ? 's' : (s & (S_IEXEC >> 3)) ? 'x' : '-');
	putchar((s & (S_IREAD >> 6)) ? 'r' : '-');
	putchar((s & (S_IWRITE >> 6)) ? 'w' : '-');
	putchar((s & (S_IEXEC >> 6)) ? 'x' : '-');
	return(ret);
}

p
/*
 *	ls - perform the -ls function
 */
void
ls(fname)
char	*fname;
{
	struct stat	s;
	struct passwd	*pwp;
	struct group	*grp;
	char		special;

	if (LSTAT(fname, &s) < 0) {
		(void) fprintf(stderr, "Bad status <%s>\n", fname);
		return;
	}
	(void) printf("%5d %4d ", s.st_ino, s.st_size/BLOCK_SIZE);
	special = fmtmode((int)s.st_mode);
	(void) printf(" %2d ", s.st_nlink);
	if ((pwp = getpwuid(s.st_uid)) == NULL)
		(void) printf("%-8d ", s.st_uid);
	else
		(void) printf("%-8.8s ", pwp->pw_name);
	if ((grp = getgrgid(s.st_gid)) == NULL)
		(void) printf("%-8d ", s.st_gid);
	else
		(void) printf("%-8.8s ", grp->gr_name);
	if (special)
		(void) printf("%2d, %4d ", s.st_rdev / 256, s.st_rdev % 256);
	else
		(void) printf("%8D ", s.st_size);
	(void) printf("%.12s %s\n", &(ctime(&s.st_mtime))[4], fname);
#ifdef MINIX
	(void) fflush(stdout);	/* otherwise nothing comes out??? */
#endif
}

p
/*
 *	action - take the action (held in act). Possible expansion of
 *	braces necessary.
 */
void
action(fname)
char	*fname;
{
	char		*cp;

	switch(act) {
	case ACT_PRINT :
		(void) printf("%s\n", fname);
		return;
	case ACT_EXEC :
		cp = (bracec > 0) ? expand(fname) : cmd;
		(void) system(cp);
		return;
	case ACT_OK :
		cp = (bracec > 0) ? expand(fname) : cmd;
		switch(prompt(cp)) {
		case 'y' :
		case 'Y' :
			(void) system(cp);
			return;
		default :
			return;
		}
	case ACT_LS :
		ls(fname);
		return;
	default :
		return;
	}
}

p
/*
 *	arithcmp - do an arithmetical comparison of the number held
 *	in the number struct, and the parameter n. Note long integers used.
 *	Returns 1 if comparison true, 0 otherwise.
 */
int
arithcmp(np, n)
NUM	*np;
long	n;
{
	switch(np->nm_op) {
	case AR_LT :
		return(n < np->nm_num);
	case AR_EQ :
		return(n == np->nm_num);
	case AR_GT :
		return(n > np->nm_num);
	default :
		return(0);
	}
}

p
/*
 *	singlematch - does the file with (complete) file name fname,
 *	and inode number inum meet the search criterion pointed to
 *	by crp. Returns 1 if so, 0 otherwise.
 */
int
singlematch(fname, inum, crp)
char	*fname;
int	inum;
CRIT	*crp;
{
	struct stat	s;
	char		*cp;
	char		*cp2;
	char		*cp3;
	int		fnd;

	switch(crp->cr_type) {
	case CR_INUM :
		return(crp->cr_un.cr_n.nm_num == inum);
	case CR_NAME :
		for (cp = &fname[strlen(fname)-1] ; *cp && *cp != '/' ; cp--)
			;
		cp++;
		if (myindex(crp->cr_un.cr_name, '*') != NULL ||
		    myindex(crp->cr_un.cr_name, '?') != NULL ||
		    myindex(crp->cr_un.cr_name, '[') != NULL) {
			if (strcmp(crp->cr_un.cr_name, "*") == 0)
				return(1);
			for (cp2 = crp->cr_un.cr_name ; *cp2 ; ) {
				switch(*cp2) {
				case '?' :
					cp2++ ;
					break;
				case '*' :
					if (*++cp2 == '\0')
						return(1);
					while (*cp && *cp != *cp2)
						cp++;
					if (*cp == '\0')
						return(0);
					break;
				case '[' :
					if ((cp3 = myindex(++cp2, ']')) == NULL)
						return(0);
					*cp3 = '\0';
					fnd = myindex(cp2, *cp++)!=NULL? 1 : 0;
					cp2 = cp3 + 1;
					*cp3 = ']';
					if (!fnd)
						return(0);
					break;
				default :
					if (*cp++ != *cp2++)
						return(0);
				}
			}
			return(*cp == '\0' ? 1 : 0);
		}
		return(strcmp(crp->cr_un.cr_name, cp) == 0 ? 1 : 0);
	case CR_MTIME :
	case CR_CTIME :
	case CR_ATIME :
	case CR_SIZE :
	case CR_TYPE :
	case CR_LINKS :
	case CR_NEWER :
	case CR_PERM :
	case CR_USER :
	case CR_NOUSER :
	case CR_GROUP :
	case CR_NOGROUP :
		if (LSTAT(fname, &s) < 0) {
			(void) fprintf(stderr, "Bad status <%s>\n", fname);
			return(0);
		}
		switch(crp->cr_type) {
		case CR_ATIME :
			return(arithcmp(&(crp->cr_un.cr_n), s.st_atime));
		case CR_CTIME :
			return(arithcmp(&(crp->cr_un.cr_n), s.st_ctime));
		case CR_MTIME :
			return(arithcmp(&(crp->cr_un.cr_n), s.st_mtime));
		case CR_SIZE :
			return(arithcmp(&(crp->cr_un.cr_n), (long) s.st_size));
		case CR_LINKS :
			return(arithcmp(&(crp->cr_un.cr_n), (long) s.st_nlink));
		case CR_NEWER :
			return(arithcmp(&(crp->cr_un.cr_n), s.st_ctime));
		case CR_PERM :
			fnd = crp->cr_un.cr_n.nm_op == AR_LT ? LONGMASK : SHORTMASK;
			return((s.st_mode & fnd) == crp->cr_un.cr_n.nm_num);
		case CR_USER :
			return(s.st_uid == (short) crp->cr_un.cr_n.nm_num);
		case CR_NOUSER :
			return(getpwuid(s.st_uid) == NULL);
		case CR_GROUP :
			return(s.st_gid == (short) crp->cr_un.cr_n.nm_num);
		case CR_NOGROUP :
			return(getgrgid(s.st_gid) == NULL);
		case CR_TYPE :
			switch(*(crp->cr_un.cr_name)) {
			case 'b' :
				return((s.st_mode & S_IFMT) == S_IFBLK);
			case 'c' :
				return((s.st_mode & S_IFMT) == S_IFCHR);
			case 'd' :
				return((s.st_mode & S_IFMT) == S_IFDIR);
			case 'f' :
				return((s.st_mode & S_IFMT) == S_IFREG);
			case 'g' :
				return(s.st_mode & S_ISGID);
			case 'u' :
				return(s.st_mode & S_ISUID);
			}
		}
	default :
		return(0);
	}
}

p
/*
 *	match - does the file fname, inode inum match the criteria tree
 *	staarting at np? Returns 1 for yes, 0 otherwise.
 */
int
match(fname, inum, np)
char	*fname;
int	inum;
NODE	*np;
{
	int	ret;

	if (np == NULL)
		return(0);
	switch(np->nd_type) {
	case ND_CRIT :
		ret = singlematch(fname, inum, np->nd_un.nd_crit);
		return(np->nd_un.nd_crit->cr_not ? 1 - ret : ret);
	case ND_OP :
		switch(np->nd_un.nd_op) {
		case OP_AND :
			return(match(fname, inum, np->nd_left) &&
			       match(fname, inum, np->nd_right));
		case OP_OR :
			return(match(fname, inum, np->nd_left) ||
			       match(fname, inum, np->nd_right));
		case OP_CLOSEB :
			fatal("close bracket in match operator");
		}
	default :
		(void) printf("unusual type %d\n", np->nd_type);
	}
	return(0);
}

p
/*
 *	makedname - make the full file name from the directory name,
 *	file name, and place it in out. If this would overflow, 0
 *	is returned, 1 otherwise.
 */
int
makedname(d, f, out, outlen)
char	*d;
char	*f;
char	*out;
int	outlen;
{
	register char	*cp;
	register char	*dp;

	if ((strlen(d) + strlen(f) + 2) > outlen) 
		return(0);
	for (cp = out, dp = d ; *dp ; *cp++ = *dp++)
		;
	if (d[strlen(d)-1] != '/')
		*cp++ = '/';
	while (*f)
		*cp++ = *f++;
	*cp = '\0';
	return(1);
}

p
#ifdef MINIX
/*
 *	dodir - process the directory dname. Minix uses the V7 file
 *	system, hence the hacked-in ifdefs.
 */
void
dodir(dname)
char	*dname;
{
	struct stat	s;
	char		dirent[LINELEN];
	int		fd;

#define ERR	{\
		(void) fprintf(stderr, "Bad status <%s>\n", dname);\
		return;\
		}

	if (LSTAT(dname, &s) < 0)
		ERR;
	if ((s.st_mode & S_IFMT) != S_IFDIR)
		ERR;
	if ((fd = open(dname, 0)) < 0)
		ERR;
	while (read(fd, &dir, sizeof(dir)) > 0) {
		if (strcmp(dir.d_name, ".") == 0 ||
		    strcmp(dir.d_name, "..") == 0)
			continue;
		if (dir.inum == 0)
			continue;
		if (!makedname(dname, dir.d_name, dirent, sizeof(dirent)))
			continue;
		if (LSTAT(dirent, &s) < 0) {
			close(fd);
			ERR;
		}
		if (((s.st_mode & S_IFMT) == S_IFDIR) && depthfirst && !prune)
			dodir(dirent);
		if (root == NULL || match(dirent, (int) dir.inum, root))
			action(dirent);
		if (((s.st_mode & S_IFMT) == S_IFDIR) && !depthfirst && !prune)
			dodir(dirent);
	}
	close(fd);
}


p
#else
/*
 *	dodir - process the directory dname. This function uses the
 *	directory processing functions.
 */
void
dodir(dname)
char	*dname;
{
	struct direct	*dp;
	struct stat	s;
	char		dent[LINELEN];
	DIR		*dirp;

#define ERR	{\
		(void) fprintf(stderr, "Bad status <%s>\n", dname);\
		return;\
		}

	if (LSTAT(dname, &s) < 0)
		ERR;
	if ((s.st_mode & S_IFMT) != S_IFDIR)
		ERR;
	if ((dirp = opendir(dname)) == NULL)
		ERR;
	while ((dp = readdir(dirp)) != NULL) {
		if (strcmp(dp->d_name, ".") == 0 ||
		    strcmp(dp->d_name, "..") == 0)
			continue;
		if (!makedname(dname, dp->d_name, dent, sizeof(dent)))
			continue;
		if (LSTAT(dent, &s) < 0) {
			closedir(dirp);
			ERR;
		}
		if (((s.st_mode & S_IFMT) == S_IFDIR) && depthfirst && !prune)
			dodir(dent);
#ifdef SUN
		if (root == NULL || match(dent, (int)dp->d_fileno, root))
#else
		if (root == NULL || match(dent, (int)dp->d_ino, root))
#endif
			action(dent);
		if (((s.st_mode & S_IFMT) == S_IFDIR) && !depthfirst && !prune)
			dodir(dent);
	}
	closedir(dirp);
}
#endif

p
/*
 *	shortform - 4.3 BSD has a shortform of find, "find name" which is
 *	"find / -name "*name*" -print.
 */
void
shortform(s)
char	*s;
{
	CRIT	*crp;
	char	name[SHORTLINE];

	critc = 1;
	crp = criteria;
	crp->cr_type = CR_NAME;
	(void) sprintf(name, "*%s*", s);
	crp->cr_un.cr_name = name;
	act = ACT_PRINT;
	root = newnode(ND_CRIT, 0, crp, (NODE *)NULL, (NODE *)NULL);
	dodir("/");
	exit(0);
}


p
/*
 *	At last...
 */
main(argc, argv)
int	argc;
char	**argv;
{
	prog = argv[0];
	if (argc == 1) {
		(void) fprintf(stderr,
			"Usage: %s name, or %s path-list predicate-list\n",
			prog, prog);
		exit(1);
	} else if (argc == 2)
		shortform(argv[1]);
	doargs(argc, argv);
	dodir(startdir);
	exit(0);
}