/*#define	PLUS_DEBUG*/
/* %Z%%M%	%I% %E% Copyright 2004-2006 J. Schilling */
#ifndef lint
static	char sccsid[] =
	"%Z%%M%	%I% %E% Copyright 2004-2006 J. Schilling";
#endif
/*
 *	Another find implementation...
 *
 *	Copyright (c) 2004-2006 J. Schilling
 */
/*
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License, Version 1.0 only
 * (the "License").  You may not use this file except in compliance
 * with the License.
 *
 * See the file CDDL.Schily.txt in this distribution for details.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file CDDL.Schily.txt from this distribution.
 */

#ifdef	__FIND__
#define	FIND_MAIN
#endif

#include <mconfig.h>
#include <stdio.h>
#include <unixstd.h>
#include <stdxlib.h>
#ifdef	HAVE_FCHDIR
#include <fctldefs.h>
#else
#include <maxpath.h>
#endif
#include <statdefs.h>
#include <timedefs.h>
#include <waitdefs.h>
#include <strdefs.h>
#include <utypes.h>		/* incl. limits.h (_POSIX_ARG_MAX/ARG_MAX) */
#ifdef	HAVE_SYS_PARAM_H
#include <sys/param.h>		/* #defines NCARGS on old systems */
#endif
#ifndef	DEV_BSIZE
#define	DEV_BSIZE	512
#endif
#include <btorder.h>
#include <getcwd.h>
#include <patmatch.h>
#if	defined(HAVE_FNMATCH_H) & defined(HAVE_FNMATCH)
#include <fnmatch.h>
#endif
#include <standard.h>
#include <schily.h>
#include <pwd.h>
#include <grp.h>

#include <nlsdefs.h>

#ifdef	__FIND__
char	strvers[] = "1.2";	/* The pure version string	*/
#endif

typedef struct {
	char	*left;
	char	*right;
	char	*this;
	int	op;
	union {
		int	i;
		long	l;
		dev_t	dev;
		ino_t	ino;
		mode_t	mode;
		nlink_t	nlink;
		uid_t	uid;
		gid_t	gid;
		size_t	size;
		time_t	time;
	} val, val2;
} node;

#include "walk.h"
#define	FIND_NODE
#include "find.h"
#include "find_list.h"
#include "find_misc.h"
#include "mem.h"
#include "gettnum.h"

LOCAL	char	*tokennames[] = {
#define	OPEN	0
	"(",
#define	CLOSE	1
	")",
#define	LNOT	2
	"!",
#define	AND	3
	"a",
#define	LOR	4
	"o",
#define	ATIME	5
	"atime",
#define	CTIME	6
	"ctime",
#define	DEPTH	7
	"depth",
#define	EXEC	8
	"exec",
#define	FOLLOW	9	/* POSIX Extension */
	"follow",
#define	FSTYPE	10	/* POSIX Extension */
	"fstype",
#define	GROUP	11
	"group",
#define	INUM	12	/* POSIX Extension */
	"inum",
#define	LINKS	13
	"links",
#define	LOCL	14	/* POSIX Extension */
	"local",
#define	LS	15	/* POSIX Extension */
	"ls",
#define	MODE	16	/* POSIX Extension */
	"mode",
#define	MOUNT	17	/* POSIX Extension */
	"mount",
#define	MTIME	18
	"mtime",
#define	NAME	19
	"name",
#define	NEWER	20
	"newer",
#define	NOGRP	21
	"nogroup",
#define	NOUSER	22
	"nouser",
#define	OK_EXEC	23
	"ok",
#define	PERM	24
	"perm",
#define	PRINT	25
	"print",
#define	PRINTNNL 26	/* POSIX Extension */
	"printnnl",
#define	PRUNE	27
	"prune",
#define	SIZE	28
	"size",
#define	TIME	29	/* POSIX Extension */
	"time",
#define	TYPE	30
	"type",
#define	USER	31
	"user",
#define	XDEV	32
	"xdev",
#define	PATH	33	/* POSIX Extension */
	"path",
#define	LNAME	34	/* POSIX Extension */
	"lname",
#define	PAT	35	/* POSIX Extension */
	"pat",
#define	PPAT	36	/* POSIX Extension */
	"ppat",
#define	LPAT	37	/* POSIX Extension */
	"lpat",
#define	PACL	38	/* POSIX Extension */
	"acl",
#define	XATTR	39	/* POSIX Extension */
	"xattr",
#define	LINKEDTO 40	/* POSIX Extension */
	"linkedto",
#define	NEWERAA	41	/* POSIX Extension */
	"neweraa",
#define	NEWERAC	42	/* POSIX Extension */
	"newerac",
#define	NEWERAM	43	/* POSIX Extension */
	"neweram",
#define	NEWERCA	44	/* POSIX Extension */
	"newerca",
#define	NEWERCC	45	/* POSIX Extension */
	"newercc",
#define	NEWERCM	46	/* POSIX Extension */
	"newercm",
#define	NEWERMA	47	/* POSIX Extension */
	"newerma",
#define	NEWERMC	48	/* POSIX Extension */
	"newermc",
#define	NEWERMM	49	/* POSIX Extension */
	"newermm",
#define	SPARSE	50	/* POSIX Extension */
	"sparse",
#define	LTRUE	51	/* POSIX Extension */
	"true",
#define	LFALSE	52	/* POSIX Extension */
	"false",
#define	MAXDEPTH 53	/* POSIX Extension */
	"maxdepth",
#define	MINDEPTH 54	/* POSIX Extension */
	"mindepth",
#define	HELP	55	/* POSIX Extension */
	"help",
#define	CHOWN	56	/* POSIX Extension */
	"chown",
#define	CHGRP	57	/* POSIX Extension */
	"chgrp",
#define	CHMOD	58	/* POSIX Extension */
	"chmod",
#define	DOSTAT	59	/* POSIX Extension */
	"dostat",
#define	ENDPRIM	60
	0,
#define	EXECPLUS 61
	"exec",
	0
};
#define	NTOK	((sizeof (tokennames) / sizeof (tokennames[0])) - 1)

/*
 *	The struct plusargs and the adjacent space that holds the
 *	arg vector and the string table. The struct plusargs member "av"
 *	is also part of the ARG_MAX sized space that follows.
 *
 *	---------------------------------
 *	| Other struct plusargs fields	|	Don't count against ARG_MAX
 *	---------------------------------
 *	---------------------------------
 *	| 	New Arg vector[0]	|	Space for ARG_MAX starts here
 *	---------------------------------
 *	|		.		|
 *	|		.		|	Arg space grows upwards
 *	|		V		|
 *	---------------------------------
 *	|	 Arg vector end		|	"nextargp" points here
 *	---------------------------------
 *	---------------------------------
 *	| Space for first arg string	|
 *	---------------------------------	"laststr" points here
 *	|		^		|
 *	|		.		|	String space "grows" downwards
 *	|		.		|
 *	---------------------------------
 *	| Space for first arg string	|	Space for ARG_MAX ends here
 *	---------------------------------	"endp" points here
 */
struct plusargs {
	struct plusargs	*next;		/* Next in list for flushing	*/
	char		*endp;		/* Points to end of data block	*/
	char		**nextargp;	/* Points to next av[] entry	*/
	char		*laststr;	/* points to last used string	*/
	int		ac;		/* The argc for our command	*/
	char		*av[1];		/* The argv for our command	*/
};

#define	MINSECS		(60)
#define	HOURSECS	(60 * MINSECS)
#define	DAYSECS		(24 * HOURSECS)
#define	YEARSECS	(365 * DAYSECS)

EXPORT	time_t	sixmonth;		/* 6 months before limit (ls)	*/
EXPORT	time_t	now;			/* now limit (ls)		*/

LOCAL	node	Printnode = { 0, 0, 0, PRINT };

#ifndef	__GNUC__
#define	inline
#endif

EXPORT	void	init_findargs	__PR((finda_t *fap));
LOCAL	node	*allocnode	__PR((void));
LOCAL	void	nexttoken	__PR((finda_t *fap));
EXPORT	int	find_token	__PR((char *word));
EXPORT	char	*find_tname	__PR((int op));
LOCAL	char	*nextarg	__PR((finda_t *fap, node *t));
EXPORT	node	*find_parse	__PR((finda_t *fap));
LOCAL	node	*parse		__PR((finda_t *fap));
LOCAL	node	*parseand	__PR((finda_t *fap));
LOCAL	node	*parseprim	__PR((finda_t *fap));
LOCAL	void	getperm		__PR((node *t, BOOL findperm, int smode, int isX));
LOCAL	char	*getsperm	__PR((char *p, mode_t *mp, int smode, int isX));
LOCAL	mode_t	iswho		__PR((int c));
LOCAL	int	isop		__PR((int c));
LOCAL	mode_t	isperm		__PR((int c, int isX));
EXPORT	void	find_firstprim	__PR((int *pac, char *const **pav));
EXPORT	BOOL	find_primary	__PR((node *t, int op));
EXPORT	BOOL	find_pname	__PR((node *t, char *word));
LOCAL	int	walkfunc	__PR((char *nm, struct stat *fs, int type, struct WALK *state));
#ifdef	__FIND__
LOCAL	inline BOOL find_expr	__PR((char *f, char *ff, struct stat *fs, struct WALK *state, node *t));
#else
EXPORT	BOOL	find_expr	__PR((char *f, char *ff, struct stat *fs, struct WALK *state, node *t));
#endif
LOCAL	BOOL	doexec		__PR((char *f, int ac, char **av, struct WALK *state));
LOCAL	int	argsize		__PR((void));
LOCAL	void	pluscreate	__PR((int ac, char **av, finda_t *fap));
LOCAL	BOOL	plusexec	__PR((char *f, node *t, struct WALK *state));
EXPORT	int	find_plusflush	__PR((void *p, struct WALK *state));
LOCAL	void	usage		__PR((int ret));
#ifdef	FIND_MAIN
LOCAL	int	getflg		__PR((char *optstr, long *argp));
EXPORT	int	main		__PR((int ac, char **av));
#endif


EXPORT void
init_findargs(fap)
	finda_t	*fap;
{
	fap->Argc = 0;
	fap->Argv = (char **)NULL;
	fap->primtype = 0;
	fap->found_action = FALSE;
	fap->patlen = 0;
	fap->walkflags = 0;
	fap->maxdepth = -1;
	fap->mindepth = -1;
	fap->plusp = (struct plusargs *)NULL;
}

LOCAL node *
allocnode()
{
	node *n;

	n = __malloc(sizeof (node), "allocnode");
	n->left = 0;
	n->right = 0;
	n->this = 0;
	n->op = 0;
	n->val.l = 0;
	n->val2.l = 0;
	return (n);
}

LOCAL void
nexttoken(fap)
	register finda_t	*fap;
{
	register char	*word;
	register char	*tail;

	if (fap->Argc <= 0) {
		fap->primtype = ENDARGS;
		return;
	}
	word = *fap->Argv;
	if ((tail = strchr(word, '=')) != NULL) {
#ifdef	XXX
		if (*tail == '\0') {
			fap->Argv++; fap->Argc--;
		} else
#endif
			*fap->Argv = ++tail;
	} else {
		fap->Argv++; fap->Argc--;
	}
	if ((fap->primtype = find_token(word)) >= 0)
		return;

	errmsgno(EX_BAD, gettext("Bad Option: '%s'.\n"), word);
	usage(EX_BAD);
}

EXPORT int
find_token(word)
	register char	*word;
{
	char	**tp;
	char	*equalp;
	int	type;

	if ((equalp = strchr(word, '=')) != NULL)
		*equalp = '\0';

	if (*word == '-') {
		/*
		 * Do not allow -(, -), -!
		 */
		if (word[1] == '\0' || !strchr("()!", word[1]))
			word++;
	} else if (!strchr("()!", word[0]) && (!equalp || equalp[1] == '\0')) {
		goto bad;
	}
	for (type = 0, tp = tokennames; *tp; tp++, type++) {
		if (streql(*tp, word)) {
			if (equalp)
				*equalp = '=';
			return (type);
		}
	}
bad:
	if (equalp)
		*equalp = '=';

	return (-1);
}

EXPORT char *
find_tname(op)
	int	op;
{
	if (op >= 0 && op < NTOK)
		return (tokennames[op]);
	return ("unknown");
}

LOCAL char *
nextarg(fap, t)
	finda_t	*fap;
	node	*t;
{
	if (fap->Argc-- <= 0) {
		char	*prim	= NULL;
		int	pt	= t->op;

		if (pt >= 0 && pt < NTOK)
			prim = tokennames[pt];
		if (prim) {
			comerrno(EX_BAD,
				gettext("Missing arg for '%s%s'.\n"),
				pt > LNOT ? "-":"", prim);
		} else {
			comerrno(EX_BAD, gettext("Missing arg.\n"));
		}
		/* NOTREACHED */
		return ((char *)0);
	} else {
		return (*fap->Argv++);
	}
}

EXPORT node *
find_parse(fap)
	finda_t	*fap;
{
	nexttoken(fap);
	return (parse(fap));
}

LOCAL node *
parse(fap)
	finda_t	*fap;
{
	node	*n;

	n = parseand(fap);
	if (fap->primtype == LOR) {
		node	*l = allocnode();

		l->left = (char *)n;
		l->op = fap->primtype;
		nexttoken(fap);
		l->right = (char *)parse(fap);
		return (l);
	}
	return (n);
}

LOCAL node *
parseand(fap)
	finda_t	*fap;
{
	node	*n;

	n = parseprim(fap);
	if ((fap->primtype == AND) ||
	    (fap->primtype != LOR && fap->primtype != CLOSE &&
	    fap->primtype != ENDARGS)) {
		node	*l = allocnode();

		l->left = (char *)n;
		l->op = AND;		/* If no Operator, default to AND -a */
		if (fap->primtype == AND) /* Fetch Operator for next node */
			nexttoken(fap);
		l->right = (char *)parseand(fap);
		return (l);
	}
	return (n);
}

LOCAL node *
parseprim(fap)
	finda_t	*fap;
{
	register node	*n;
	register char	*p;
		Llong	ll;

	n = allocnode();
	switch (n->op = fap->primtype) {

	/*
	 * Use simple to old (historic) shell globbing.
	 */
	case NAME:
	case PATH:
	case LNAME:
#if	defined(HAVE_FNMATCH_H) & defined(HAVE_FNMATCH)
		n->this = nextarg(fap, n);
		nexttoken(fap);
		return (n);
#endif
		/* FALLTHROUGH */
		/* Implement "fallback" to patmatch() if we have no fnmatch() */

	/*
	 * Use patmatch() which is a regular expression matcher that implements
	 * extensions that are compatible to old (historic) shell globbing.
	 */
	case PAT:
	case PPAT:
	case LPAT: {
		int	plen;

		plen = strlen(n->this = nextarg(fap, n));
		if (plen > fap->patlen)
			fap->patlen = plen;
		n->right = __malloc(sizeof (int)*plen, "space for pattern");

		if ((n->val.i = patcompile((Uchar *)n->this, plen, (int *)n->right)) == 0)
			comerrno(EX_BAD, gettext("Bad pattern in '-%s %s'.\n"),
						tokennames[n->op], n->this);
		nexttoken(fap);
		return (n);
	}

	case SIZE:
		fap->walkflags &= ~WALK_NOSTAT;

		p = n->left = nextarg(fap, n);
		if (p[0] == '-' || p[0] == '+')
			p++;
		p = astoll(p, &ll);
		if (p[0] == 'c' && p[1] == '\0')
			n->this = p;
		else if (*p)
			comerrno(EX_BAD,
			gettext("Non numeric character '%c' in '-size %s'.\n"),
				*p, n->left);
		n->val.size = ll;
		nexttoken(fap);
		return (n);

	case LINKS:
		fap->walkflags &= ~WALK_NOSTAT;

		p = n->left = nextarg(fap, n);
		if (p[0] == '-' || p[0] == '+')
			p++;
		p = astoll(p, &ll);
		if (*p)
			comerrno(EX_BAD,
			gettext("Non numeric character '%c' in '-links %s'.\n"),
				*p, n->left);
		n->val.nlink = ll;
		nexttoken(fap);
		return (n);

	case INUM:
		fap->walkflags &= ~WALK_NOSTAT;

		p = n->left = nextarg(fap, n);
		if (p[0] == '-' || p[0] == '+')
			p++;
		p = astoll(p, &ll);
		if (*p)
			comerrno(EX_BAD,
			gettext("Non numeric character '%c' in '-inum %s'.\n"),
				*p, n->left);
		n->val.ino = ll;
		nexttoken(fap);
		return (n);

	case LINKEDTO: {
		struct stat ns;

		fap->walkflags &= ~WALK_NOSTAT;

		if (stat(n->left = nextarg(fap, n), &ns) < 0)
			comerr(gettext("Cannot stat '%s'.\n"), n->left);

		n->val.ino = ns.st_ino;
		n->val2.dev = ns.st_dev;
		nexttoken(fap);
		return (n);
	}

	case TIME:
	case ATIME:
	case CTIME:
	case MTIME: {
		int	len;

		fap->walkflags &= ~WALK_NOSTAT;

		p = n->left = nextarg(fap, n);
		if (p[0] == '-' || p[0] == '+')
			p++;
		if (gettnum(p, &n->val.time) != 1) {
			comerrno(EX_BAD,
				gettext("Bad timespec in '-%s %s'.\n"),
				tokennames[n->op], n->left);
		}
		len = strlen(p);
		if (len > 0) {
			len = (Uchar)p[len-1];
			if (!(len >= '0' && len <= '9'))
				n->val2.i = 1;
		}
		nexttoken(fap);
		return (n);
	}

	case NEWERAA:
	case NEWERCA:
	case NEWERMA: {
		struct stat ns;

		fap->walkflags &= ~WALK_NOSTAT;

		if (stat(n->left = nextarg(fap, n), &ns) < 0)
			comerr(gettext("Cannot stat '%s'.\n"), n->left);

		n->val.time = ns.st_atime;
		nexttoken(fap);
		return (n);
	}

	case NEWERAC:
	case NEWERCC:
	case NEWERMC: {
		struct stat ns;

		fap->walkflags &= ~WALK_NOSTAT;

		if (stat(n->left = nextarg(fap, n), &ns) < 0)
			comerr(gettext("Cannot stat '%s'.\n"), n->left);

		n->val.time = ns.st_ctime;
		nexttoken(fap);
		return (n);
	}

	case NEWERAM:
	case NEWERCM:
	case NEWERMM:
	case NEWER: {
		struct stat ns;

		fap->walkflags &= ~WALK_NOSTAT;

		if (stat(n->left = nextarg(fap, n), &ns) < 0)
			comerr(gettext("Cannot stat '%s'.\n"), n->left);

		n->val.time = ns.st_mtime;
		nexttoken(fap);
		return (n);
	}

	case TYPE:
		fap->walkflags &= ~WALK_NOSTAT;

		n->this = (char *)nextarg(fap, n);
		switch (*(n->this)) {

		case 'b': case 'c': case 'd': case 'D':
		case 'e': case 'f': case 'l': case 'p':
		case 's':
			if ((n->this)[1] == '\0') {
				nexttoken(fap);
				return (n);
			}
		}
		comerrno(EX_BAD, gettext("Bad type '%c' in '-type %s'.\n"),
			*n->this, n->this);
		break;

	case FSTYPE:
		fap->walkflags &= ~WALK_NOSTAT;

#ifdef	HAVE_ST_FSTYPE
		n->this = (char *)nextarg(fap, n);
#else
		comerrno(EX_BAD,
			gettext("-fstype not supported by this OS.\n"));
#endif
		nexttoken(fap);
		return (n);

	case LOCL:
		fap->walkflags &= ~WALK_NOSTAT;

#ifndef	HAVE_ST_FSTYPE
		comerrno(EX_BAD,
			gettext("-local not supported by this OS.\n"));
#endif
		nexttoken(fap);
		return (n);

#ifdef	CHOWN
	case CHOWN:
#endif
	case USER: {
		struct  passwd  *pw;
		char		*u;

		fap->walkflags &= ~WALK_NOSTAT;

		u = n->left = nextarg(fap, n);
		if (u[0] == '-' || u[0] == '+')
			u++;
		if ((pw = getpwnam(u)) != NULL) {
			n->val.uid = pw->pw_uid;
		} else {
			if (*astoll(n->left, &ll))
				comerrno(EX_BAD,
				gettext("User '%s' not in passwd database.\n"),
				n->left);
			n->val.uid = ll;
		}
		nexttoken(fap);
		return (n);
	}

#ifdef	CHGRP
	case CHGRP:
#endif
	case GROUP: {
		struct  group	*gr;
		char		*g;

		fap->walkflags &= ~WALK_NOSTAT;

		g = n->left = nextarg(fap, n);
		if (g[0] == '-' || g[0] == '+')
			g++;
		if ((gr = getgrnam(g)) != NULL) {
			n->val.gid = gr->gr_gid;
		} else {
			if (*astoll(n->left, &ll))
				comerrno(EX_BAD,
				gettext("Group '%s' not in group database.\n"),
				n->left);
			n->val.gid = ll;
		}
		nexttoken(fap);
		return (n);
	}

#ifdef	CHMOD
	case CHMOD:
#endif
	case PERM:
		fap->walkflags &= ~WALK_NOSTAT;

		n->left = nextarg(fap, n);
		getperm(n, n->op == PERM, 0, n->op == PERM ? -1:0);
		nexttoken(fap);
		return (n);

	case MODE:
		fap->walkflags &= ~WALK_NOSTAT;

		comerrno(EX_BAD, gettext("-mode not yet implemented.\n"));
		nexttoken(fap);
		return (n);

	case XDEV:
	case MOUNT:
		fap->walkflags &= ~WALK_NOSTAT;
		fap->walkflags |= WALK_MOUNT;
		nexttoken(fap);
		return (n);
	case DEPTH:
		fap->walkflags |= WALK_DEPTH;
		nexttoken(fap);
		return (n);
	case FOLLOW:
		fap->walkflags &= ~WALK_PHYS;
		nexttoken(fap);
		return (n);

	case MAXDEPTH:
	case MINDEPTH:
		p = n->left = nextarg(fap, n);
		p = astoll(p, &ll);
		if (*p)
			comerrno(EX_BAD,
			gettext("Non numeric character '%c' in '-%s %s'.\n"),
				*p, tokennames[n->op], n->left);
		n->val.l = ll;
		if (n->op == MAXDEPTH)
			fap->maxdepth = ll;
		else
			fap->mindepth = ll;
		nexttoken(fap);
		return (n);

	case NOUSER:
	case NOGRP:
	case PACL:
	case XATTR:
	case SPARSE:
	case DOSTAT:
		fap->walkflags &= ~WALK_NOSTAT;
	case PRUNE:
	case LTRUE:
	case LFALSE:
		nexttoken(fap);
		return (n);

	case OK_EXEC:
	case EXEC: {
		int	i = 1;

		n->this = (char *)fap->Argv;	/* Cheat: Pointer is pointer */
		nextarg(fap, n);		/* Eat up cmd name	    */
		while ((p = nextarg(fap, n)) != NULL) {
			if (streql(p, "{}"))
				fap->Argv[-1] = NULL;
			else if (streql(p, ";"))
				break;
			else if (streql(p, "+") && fap->Argv[-2] == NULL) {
				n->op = fap->primtype = EXECPLUS;
				pluscreate(--i, (char **)n->this, fap);
				n->this = (char *)fap->plusp;
				break;
			}
			i++;
		}
		n->val.i = i;
#ifdef	PLUS_DEBUG
		if (0) {
			char **pp = (char **)n->this;
			for (i = 0; i < n->val.i; i++, pp++)
				printf("ARG %d '%s'\n", i, *pp);
		}
#endif
	}
	/* FALLTHRU */

	case LS:
		fap->walkflags &= ~WALK_NOSTAT;
	case PRINT:
	case PRINTNNL:
		fap->found_action = TRUE;
		nexttoken(fap);
		return (n);

	case ENDARGS:
#ifdef	DEBUG
		errmsgno(EX_BAD, gettext("ENDARGS in parseprim()\n"));
#endif
		comerrno(EX_BAD, gettext("Incomplete expression.\n"));
		/* NOTREACHED */

	case OPEN:
		nexttoken(fap);
		n->this = (char *)parse(fap);
		if (fap->primtype != CLOSE) {
			comerrno(EX_BAD,
				gettext("Found '%s', but ')' expected.\n"),
				fap->Argv[-1]);
		} else {
			nexttoken(fap);
			return (n);
		}
		break;

	case CLOSE:
		comerrno(EX_BAD, gettext("Missing '('.\n"));
		/* NOTREACHED */

	case LNOT:
		nexttoken(fap);
		n->this = (char *)parseprim(fap);
		return (n);

	case AND:
	case LOR:
		comerrno(EX_BAD,
		gettext("Invalid expression with -%s.\n"), tokennames[n->op]);
		/* NOTREACHED */

	case HELP:
		usage(0);

	default:
		comerrno(EX_BAD, gettext("Internal malfunction.\n"));
		/* NOTREACHED */
	}
	return (0);
}

/*
 * This is the mode bit translation code stolen from star...
 */
#define	TSUID		04000	/* Set UID on execution */
#define	TSGID		02000	/* Set GID on execution */
#define	TSVTX		01000	/* On directories, restricted deletion flag */
#define	TUREAD		00400	/* Read by owner */
#define	TUWRITE		00200	/* Write by owner special */
#define	TUEXEC		00100	/* Execute/search by owner */
#define	TGREAD		00040	/* Read by group */
#define	TGWRITE		00020	/* Write by group */
#define	TGEXEC		00010	/* Execute/search by group */
#define	TOREAD		00004	/* Read by other */
#define	TOWRITE		00002	/* Write by other */
#define	TOEXEC		00001	/* Execute/search by other */

#define	TALLMODES	07777	/* The low 12 bits mentioned in the standard */

#define	S_ALLPERM	(S_IRWXU|S_IRWXG|S_IRWXO)
#define	S_ALLFLAGS	(S_ISUID|S_ISGID|S_ISVTX)
#define	S_ALLMODES	(S_ALLFLAGS | S_ALLPERM)

#if	S_ISUID == TSUID && S_ISGID == TSGID && S_ISVTX == TSVTX && \
	S_IRUSR == TUREAD && S_IWUSR == TUWRITE && S_IXUSR == TUEXEC && \
	S_IRGRP == TGREAD && S_IWGRP == TGWRITE && S_IXGRP == TGEXEC && \
	S_IROTH == TOREAD && S_IWOTH == TOWRITE && S_IXOTH == TOEXEC

#define	HAVE_POSIX_MODE_BITS	/* st_mode bits are equal to TAR mode bits */
#endif

#ifdef	HAVE_POSIX_MODE_BITS	/* st_mode bits are equal to TAR mode bits */
#define	OSMODE(xmode)	    (xmode)
#else
#define	OSMODE(xmode)	    ((xmode & TSUID   ? S_ISUID : 0)  \
			    | (xmode & TSGID   ? S_ISGID : 0) \
			    | (xmode & TSVTX   ? S_ISVTX : 0) \
			    | (xmode & TUREAD  ? S_IRUSR : 0) \
			    | (xmode & TUWRITE ? S_IWUSR : 0) \
			    | (xmode & TUEXEC  ? S_IXUSR : 0) \
			    | (xmode & TGREAD  ? S_IRGRP : 0) \
			    | (xmode & TGWRITE ? S_IWGRP : 0) \
			    | (xmode & TGEXEC  ? S_IXGRP : 0) \
			    | (xmode & TOREAD  ? S_IROTH : 0) \
			    | (xmode & TOWRITE ? S_IWOTH : 0) \
			    | (xmode & TOEXEC  ? S_IXOTH : 0))
#endif

LOCAL void
getperm(t, findperm, smode, isX)
	node	*t;
	BOOL	findperm;
	int	smode;
	int	isX;
{
	char	*p;
	Llong	ll;
	mode_t	mm;

	p = t->left;
	if (findperm && *p == '-')
		p++;

	if (*p >= '0' && *p <= '7') {
		p = astollb(p, &ll, 8);
		if (*p) {
			comerrno(EX_BAD,
			gettext("Non octal character '%c' in '-%s %s'.\n"),
				*p, tokennames[t->op], t->left);
		}
		mm = ll & TALLMODES;
		t->val.mode = OSMODE(mm);
		return;
	}
	p = getsperm(p, &t->val.mode, smode, isX);
	if (p && *p != '\0') {
		comerrno(EX_BAD,
		gettext("Bad perm character '%c' found in '-%s %s'.\n"),
			*p, tokennames[t->op], t->left);
	}
#ifdef	PERM_DEBUG
	error("GETPERM (%c) %4.4lo\n", *t->left, (long)t->val.mode);
#endif
}

LOCAL char *
getsperm(p, mp, smode, isX)
	char	*p;		/* The perm input string		*/
	mode_t	*mp;		/* To set t->val.mode			*/
	int	smode;		/* The start mode for the computation	*/
	int	isX;
{
#ifdef	OLD
	mode_t	permval = 0;	/* POSIX start value for "find" */
#else
	mode_t	permval = smode;	/* POSIX start value for "find" */
#endif
	mode_t	who;
	mode_t	m;
	int	op;
	mode_t	perms;
	mode_t	pm;

nextclause:
#ifdef	PERM_DEBUG
	error("getsperm(%s)\n", p);
#endif
	who = 0;
	while ((m = iswho(*p)) != 0) {
		p++;
		who |= m;
	}
	if (who == 0) {
		mode_t	mask = umask(0);

		umask(mask);
		who = ~mask;
		who &= (S_ISUID|S_ISGID|S_ISVTX|S_IRWXU|S_IRWXG|S_IRWXO);
	}
#ifdef	PERM_DEBUG
	error("WHO %4.4lo\n", (long)who);
	error("getsperm(--->%s)\n", p);
#endif

nextop:
	if ((op = isop(*p)) != '\0')
		p++;
#ifdef	PERM_DEBUG
	error("op '%c'\n", op);
	error("getsperm(--->%s)\n", p);
#endif
	if (op == 0) {
		errmsgno(EX_BAD, gettext("Missing -perm op.\n"));
		return (p);
	}

	perms = 0;
	while ((pm = isperm(*p, isX)) != -1) {
		p++;
		perms |= pm;
	}
#ifdef	PERM_DEBUG
	error("PERM %4.4lo\n", (long)perms);
	error("START PERMVAL %4.4lo\n", (long)permval);
#endif
	if ((perms == 0) && (*p == 'u' || *p == 'g' || *p == 'o')) {
		mode_t	what = 0;

		/*
		 * First select the bit triple we like to copy.
		 */
		switch (*p) {

		case 'u':
			what = permval & S_IRWXU;
			break;
		case 'g':
			what = permval & S_IRWXG;
			break;
		case 'o':
			what = permval & S_IRWXO;
			break;
		}
		/*
		 * Now copy over bit by bit without relying on shifts
		 * that would make implicit assumptions on values.
		 */
		if (what & (S_IRUSR|S_IRGRP|S_IROTH))
			perms |= (who & (S_IRUSR|S_IRGRP|S_IROTH));
		if (what & (S_IWUSR|S_IWGRP|S_IWOTH))
			perms |= (who & (S_IWUSR|S_IWGRP|S_IWOTH));
		if (what & (S_IXUSR|S_IXGRP|S_IXOTH))
			perms |= (who & (S_IXUSR|S_IXGRP|S_IXOTH));
		p++;
	}
#ifdef	PERM_DEBUG
	error("getsperm(--->%s)\n", p);
#endif
	switch (op) {

	case '=':
		permval &= ~who;
		/* FALLTHRU */
	case '+':
		permval |= (who & perms);
		break;

	case '-':
		permval &= ~(who & perms);
		break;
	}
#ifdef	PERM_DEBUG
	error("END PERMVAL %4.4lo\n", (long)permval);
#endif
	if (isop(*p))
		goto nextop;
	if (*p == ',') {
		p++;
		goto nextclause;
	}
	*mp = permval;
	return (p);
}

LOCAL mode_t
iswho(c)
	int	c;
{
	switch (c) {

	case 'u':					/* user */
		return (S_ISUID|S_ISVTX|S_IRWXU);
	case 'g':					/* group */
		return (S_ISGID|S_ISVTX|S_IRWXG);
	case 'o':					/* other */
		return (S_ISVTX|S_IRWXO);
	case 'a':					/* all */
		return (S_ISUID|S_ISGID|S_ISVTX|S_IRWXU|S_IRWXG|S_IRWXO);
	default:
		return (0);
	}
}

LOCAL int
isop(c)
	int	c;
{
	switch (c) {

	case '+':
	case '-':
	case '=':
		return (c);
	default:
		return (0);
	}
}

LOCAL mode_t
isperm(c, isX)
	int	c;
	int	isX;
{
	switch (c) {

	case 'r':
		return (S_IRUSR|S_IRGRP|S_IROTH);
	case 'w':
		return (S_IWUSR|S_IWGRP|S_IWOTH);
	case 'X':
		if (isX < 0)
			return (-1);
		if (isX == 0)
			return (0);
		/* FALLTROUGH */
	case 'x':
		return (S_IXUSR|S_IXGRP|S_IXOTH);
	case 's':
		return (S_ISUID|S_ISGID);
	case 'l':
		return (S_ISGID);
	case 't':
		return (S_ISVTX);
	default:
		return (-1);
	}
}

EXPORT void
find_firstprim(pac, pav)
	int	*pac;
	char    *const *pav[];
{
	register int	cac  = *pac;
	register char *const *cav = *pav;
	register char	c;

	while (cac > 0 &&
		(c = **cav) != '-' && c != '(' && c != ')' && c != '!') {
		cav++;
		cac--;
	}
	*pac = cac;
	*pav = cav;
}

EXPORT BOOL
find_primary(t, op)
	node	*t;
	int	op;
{
	BOOL	ret = FALSE;

	if (t->op == op) {
		return (TRUE);
	}
	switch (t->op) {

	case OPEN:
		ret = find_primary((node *)t->this, op);
		break;
	case LNOT:
		ret = find_primary((node *)t->this, op);
		break;
	case AND:
		ret = find_primary((node *)t->left, op);
		if (ret)
			return (ret);
		ret = find_primary((node *)t->right, op);
		break;
	case LOR:
		ret = find_primary((node *)t->left, op);
		if (ret)
			return (ret);
		ret = find_primary((node *)t->right, op);
		break;

	default:
		;
	}
	return (ret);
}

EXPORT BOOL
find_pname(t, word)
	node	*t;
	char	*word;
{
	if (streql(word, "-exec+"))
		return (find_primary(t, EXECPLUS));
	return (find_primary(t, find_token(word)));
}


LOCAL int
walkfunc(nm, fs, type, state)
	char		*nm;
	struct stat	*fs;
	int		type;
	struct WALK	*state;
{
	if (type == WALK_NS) {
		errmsg(gettext("Cannot stat '%s'.\n"), nm);
		state->err = 1;
		return (0);
	} else if (type == WALK_SLN && (state->walkflags & WALK_PHYS) == 0) {
		errmsg(gettext("Cannot follow symlink '%s'.\n"), nm);
		state->err = 1;
		return (0);
	} else if (type == WALK_DNR) {
		if (state->flags & WALK_WF_NOCHDIR)
			errmsg(gettext("Cannot chdir to '%s'.\n"), nm);
		else
			errmsg(gettext("Cannot read '%s'.\n"), nm);
		state->err = 1;
		return (0);
	}

	if (state->maxdepth >= 0 && state->level >= state->maxdepth)
		state->flags |= WALK_WF_PRUNE;
	if (state->mindepth >= 0 && state->level < state->mindepth)
		return (0);

	find_expr(nm, nm + state->base, fs, state, state->tree);
	return (0);
}

#ifdef	__FIND__
LOCAL inline BOOL
#else
EXPORT BOOL
#endif
find_expr(f, ff, fs, state, t)
	char		*f;	/* path name */
	char		*ff;	/* file name */
	struct stat	*fs;
	struct WALK	*state;
	node		*t;
{
	time_t	xtime;
	char	*p;
	char	lname[8192];

	switch (t->op) {

	case LNAME: {
		int	lsize;

		if (!S_ISLNK(fs->st_mode))
			return (FALSE);

		if (state->lname != NULL) {
			p = state->lname;
			goto nmatch;
		}
		lname[0] = '\0';
		lsize = readlink(state->level ? ff : f, lname, sizeof (lname));
		if (lsize < 0) {
			errmsg(gettext("Cannot read link '%s'.\n"), ff);
			return (FALSE);
		}
		lname[sizeof (lname)-1] = '\0';
		if (lsize >= 0)
			lname[lsize] = '\0';
		p = lname;
		goto nmatch;
	}
	case PATH:
		p = f;
		goto nmatch;
	case NAME:
		p = ff;
	nmatch:
#if	defined(HAVE_FNMATCH_H) & defined(HAVE_FNMATCH)
		return (!fnmatch(t->this, p, 0));
#else
		goto pattern;		/* Use patmatch() as "fallback" */
#endif

	case LPAT: {
		int	lsize;

		if (!S_ISLNK(fs->st_mode))
			return (FALSE);

		if (state->lname != NULL) {
			p = state->lname;
			goto pattern;
		}
		lname[0] = '\0';
		lsize = readlink(state->level ? ff : f, lname, sizeof (lname));
		if (lsize < 0) {
			errmsg(gettext("Cannot read link '%s'.\n"), ff);
			return (FALSE);
		}
		lname[sizeof (lname)-1] = '\0';
		if (lsize >= 0)
			lname[lsize] = '\0';
		p = lname;
		goto pattern;
	}
	case PPAT:
		p = f;
		goto pattern;
	case PAT:
		p = ff;
	pattern: {
		Uchar	*pr;		/* patmatch() return */

		pr = patmatch((Uchar *)t->this, (int *)t->right,
			(Uchar *)p, 0, strlen(p), t->val.i, state->patstate);
		return (*p && pr && (*pr == '\0'));
	}

	case SIZE:
		switch (*(t->left)) {
		case '+':
			if (t->this)
				return (fs->st_size    > t->val.size);
			return ((fs->st_size+511)/512  > t->val.size);
		case '-':
			if (t->this)
				return (fs->st_size   <  t->val.size);
			return ((fs->st_size+511)/512 <  t->val.size);
		default:
			if (t->this)
				return (fs->st_size   == t->val.size);
			return ((fs->st_size+511)/512 == t->val.size);
		}

	case LINKS:
		switch (*(t->left)) {
		case '+':
			return (fs->st_nlink  > t->val.nlink);
		case '-':
			return (fs->st_nlink <  t->val.nlink);
		default:
			return (fs->st_nlink == t->val.nlink);
		}

	case INUM:
		switch (*(t->left)) {
		case '+':
			return (fs->st_ino  > t->val.ino);
		case '-':
			return (fs->st_ino <  t->val.ino);
		default:
			return (fs->st_ino == t->val.ino);
		}

	case LINKEDTO:
			return ((fs->st_ino == t->val.ino) &&
				(fs->st_dev == t->val2.dev));

	case ATIME:
		xtime = fs->st_atime;
		goto times;
	case CTIME:
		xtime = fs->st_ctime;
		goto times;
	case MTIME:
	case TIME:
		xtime = fs->st_mtime;
	times:
		if (t->val2.i != 0)
			goto timex;

		switch (*(t->left)) {
		case '+':
			return ((now-xtime)/DAYSECS >  t->val.time);
		case '-':
			return ((now-xtime)/DAYSECS <  t->val.time);
		default:
			return ((now-xtime)/DAYSECS == t->val.time);
		}
	timex:
		switch (*(t->left)) {
		case '+':
			return ((now-xtime) >  t->val.time);
		case '-':
			return ((now-xtime) <  t->val.time);
		default:
			return ((now-xtime) == t->val.time);
		}

	case NEWERAA:
	case NEWERAC:
	case NEWERAM:
		return (t->val.time < fs->st_atime);

	case NEWERCA:
	case NEWERCC:
	case NEWERCM:
		return (t->val.time < fs->st_ctime);

	case NEWER:
	case NEWERMA:
	case NEWERMC:
	case NEWERMM:
		return (t->val.time < fs->st_mtime);

	case TYPE:
		switch (*(t->this)) {
		case 'b':
			return (S_ISBLK(fs->st_mode));
		case 'c':
			return (S_ISCHR(fs->st_mode));
		case 'd':
			return (S_ISDIR(fs->st_mode));
		case 'D':
			return (S_ISDOOR(fs->st_mode));
		case 'e':
			return (S_ISEVC(fs->st_mode));
		case 'f':
			return (S_ISREG(fs->st_mode));
		case 'l':
			return (S_ISLNK(fs->st_mode));
		case 'p':
			return (S_ISFIFO(fs->st_mode));
		case 's':
			return (S_ISSOCK(fs->st_mode));
		default:
			return (FALSE);
		}

	case FSTYPE:
#ifdef	HAVE_ST_FSTYPE
		return (streql(t->this, fs->st_fstype));
#else
		return (TRUE);
#endif

	case LOCL:
#ifdef	HAVE_ST_FSTYPE
		if (streql("nfs", fs->st_fstype) ||
		    streql("autofs", fs->st_fstype) ||
		    streql("cachefs", fs->st_fstype))
			return (FALSE);
#endif
		return (TRUE);

#ifdef	CHOWN
	case CHOWN:
		fs->st_uid = t->val.uid;
		return (TRUE);
#endif

	case USER:
		switch (*(t->left)) {
		case '+':
			return (fs->st_uid  > t->val.uid);
		case '-':
			return (fs->st_uid <  t->val.uid);
		default:
			return (fs->st_uid == t->val.uid);
		}

#ifdef	CHGRP
	case CHGRP:
		fs->st_gid = t->val.gid;
		return (TRUE);
#endif

	case GROUP:
		switch (*(t->left)) {
		case '+':
			return (fs->st_gid  > t->val.gid);
		case '-':
			return (fs->st_gid <  t->val.gid);
		default:
			return (fs->st_gid == t->val.gid);
		}

#ifdef	CHMOD
	case CHMOD:
		getperm(t, FALSE, fs->st_mode & S_ALLMODES,
			S_ISDIR(fs->st_mode) ||
			(fs->st_mode & (S_IXUSR|S_IXGRP|S_IXOTH)) != 0);
		fs->st_mode &= ~S_ALLMODES;
		fs->st_mode |= t->val.mode;
		return (TRUE);
#endif

	case PERM:
		if (t->left[0] == '-')
			return ((fs->st_mode & t->val.mode) == t->val.mode);
		else
			return ((fs->st_mode & S_ALLMODES) == t->val.mode);

	case MODE:
		return (TRUE);

	case XDEV:
	case MOUNT:
	case DEPTH:
	case FOLLOW:
	case DOSTAT:
		return (TRUE);

	case NOUSER:
/*		return (nameuid(NULL, 32, fs->st_uid));*/
		return (getpwuid(fs->st_uid) == NULL);

	case NOGRP:
/*		return (namegid(NULL, 32, fs->st_gid));*/
		return (getgrgid(fs->st_gid) == NULL);

	case PRUNE:
		state->flags |= WALK_WF_PRUNE;
		return (TRUE);

	case MAXDEPTH:
	case MINDEPTH:
		return (TRUE);

	case PACL:
		if (state->pflags & PF_ACL) {
			return ((state->pflags & PF_HAS_ACL) != 0);
		}
		return (has_acl(f, ff, fs));

	case XATTR:
		if (state->pflags & PF_XATTR) {
			return ((state->pflags & PF_HAS_XATTR) != 0);
		}
		return (has_xattr(ff));

	case SPARSE:
		if (!S_ISREG(fs->st_mode))
			return (FALSE);
#ifdef	HAVE_ST_BLOCKS
		return (fs->st_size > (fs->st_blocks * DEV_BSIZE + DEV_BSIZE));
#else
		return (FALSE);
#endif

	case OK_EXEC: {
		char qbuf[32];

		flush();
		fprintf(stderr, "< %s ... %s > ? ", ((char **)t->this)[0], f);
		fflush(stderr);
		getline(qbuf, sizeof (qbuf) - 1);

		switch (qbuf[0]) {
		case 'y':
			if (qbuf[1] == '\0' || streql(qbuf, "yes")) break;
		default:
			return (FALSE);
		}
	}
	/* FALLTHRU */

	case EXEC:
		return (doexec(f, t->val.i, (char **)t->this, state));

	case EXECPLUS:
		return (plusexec(f, t, state));

	case PRINT:
		filewrite(stdout, f, strlen(f)); putchar('\n');
		return (TRUE);

	case PRINTNNL:
		filewrite(stdout, f, strlen(f)); putchar(' ');
		return (TRUE);

	case LS:
		/*
		 * The third parameter is the file name used for readlink()
		 * (inside find_list()) relatively to the current working
		 * directory. For file names from the command line, we did not
		 * perform a chdir() before, so we need to use the full path
		 * name.
		 */
		find_list(fs, f, state->level ? ff : f, state);
		return (TRUE);

	case LTRUE:
		return (TRUE);

	case LFALSE:
		return (FALSE);

	case OPEN:
		return (find_expr(f, ff, fs, state, (node *)t->this));
	case LNOT:
		return (!find_expr(f, ff, fs, state, (node *)t->this));
	case AND:
		return (find_expr(f, ff, fs, state, (node *)t->left) ?
			find_expr(f, ff, fs, state, (node *)t->right) : 0);
	case LOR:
		return (find_expr(f, ff, fs, state, (node *)t->left) ?
			1 : find_expr(f, ff, fs, state, (node *)t->right));
	}
	return (FALSE);		/* Unknown operator ??? */
}

LOCAL BOOL
doexec(f, ac, av, state)
	char	*f;
	int	ac;
	char	**av;
	struct WALK *state;
{
	pid_t	pid;
	int	retval;

	if ((pid = fork()) < 0)
		comerr(gettext("Cannot fork child.\n"));
	if (pid) {
		while (wait(&retval) != pid)
			/* LINTED */
			;
		return (retval == 0);
	} else {
		register int	i;
		register char	**pp = av;

		if (walkhome(state) < 0)
			comerr(gettext("Cannot chdir to '.'.\n"));
#ifndef	F_SETFD
		walkclose(state);
#endif
		if (f) {
			for (i = 0; i < ac; i++, pp++) {
				if (*pp == NULL)
					*pp = f;
			}
		}
#ifdef	PLUS_DEBUG
		error("argsize %d\n",
			(plusp->endp - (char *)&plusp->nextargp[0]) -
			(plusp->laststr - (char *)&plusp->nextargp[1]));
#endif
		fexecv(av[0], stdin, stdout, stderr, ac, av);
#ifdef	PLUS_DEBUG
		error("argsize %d\n",
			(plusp->endp - (char *)&plusp->nextargp[0]) -
			(plusp->laststr - (char *)&plusp->nextargp[1]));
#endif
		comerr(gettext("Cannot execute '%s'.\n"), av[0]);
		/* NOTREACHED */
		return (-1);
	}
}

#ifndef	LINE_MAX
#define	LINE_MAX	1024
#endif
/*
 * Return ARG_MAX - LINE_MAX - size of current environment.
 *
 * The return value is reduced by LINE_MAX to allow the called
 * program to do own exec(2) calls with slightly increased arg size.
 */
LOCAL int
argsize()
{
	static int	ret = 0;
	extern char	**environ;

	if (ret == 0) {
		register int	evs = 0;
		register char	**ep;

		for (ep = environ; *ep; ep++) {
			evs += strlen(*ep) + 1 + sizeof (ep);
		}
		evs += sizeof (char **); /* The environ NULL ptr at the end */

#ifdef	_SC_ARG_MAX
		ret = sysconf(_SC_ARG_MAX);
		if (ret < 0)
			ret = _POSIX_ARG_MAX;
#else
#ifdef	ARG_MAX
		ret = ARG_MAX;
#else
#ifdef	NCARGS
		ret = NCARGS;
#endif
#endif
#endif

#ifdef	PLUS_DEBUG
		ret = 3000;
#define		LINE_MAX	100
		error("evs %d\n", evs);
#endif
		if (ret <= 0)
			ret = 10000;	/* Just a guess */

		ret -= evs;
		if ((ret - LINE_MAX) > 0)
			ret -= LINE_MAX;
		else
			ret -= 100;
	}
	return (ret);
}

LOCAL void
pluscreate(ac, av, fap)
	int	ac;
	char	**av;
	finda_t	*fap;
{
	struct plusargs	*pp;
	register char	**ap = av;
	register int	i;

	for (i = ac; --i >= 0; ap++) {	/* Restore marked "{}" args */
		if (*ap == NULL)
			*ap = "{}";
	}

#ifdef	PLUS_DEBUG
	printf("Arvc %d\n", ac);
	ap = av;
	for (i = 0; i < ac; i++, ap++)
		printf("ARG %d '%s'\n", i, *ap);
#endif

	pp = __malloc(argsize() + sizeof (struct plusargs), "-exec args");
	pp->laststr = pp->endp = (char *)(&pp->av[0]) + argsize();
	pp->ac = 0;
	pp->nextargp = &pp->av[0];

#ifdef	PLUS_DEBUG
	printf("pp          %d\n", pp);
	printf("pp->laststr %d\n", pp->laststr);
	printf("argsize()   %d\n", argsize());
#endif

	/*
	 * Copy args from command line.
	 */
	ap = av;
	for (i = 0; i < ac; i++, ap++) {
#ifdef	PLUS_DEBUG
		printf("ARG %d '%s'\n", i, *ap);
#endif
		*(pp->nextargp++) = *ap;
		pp->laststr -= strlen(*ap) + 1;
		pp->ac++;
		if (pp->laststr <= (char *)pp->nextargp)
			comerrno(EX_BAD,
				gettext("No space to copy -exec args.\n"));

	}
#ifdef	PLUS_DEBUG
	error("lastr %d endp %d diff %d\n",
		pp->laststr, pp->endp, pp->endp - pp->laststr);
#endif
	pp->endp = pp->laststr;	/* Reduce endp by the size of cmdline args */

#ifdef	PLUS_DEBUG
	ap = &pp->av[0];
	for (i = 0; i < pp->ac; i++, ap++) {
		printf("ARG %d '%s'\n", i, *ap);
	}
#endif
#ifdef	PLUS_DEBUG
	printf("pp          %d\n", pp);
	printf("pp->laststr %d\n", pp->laststr);
#endif
	pp->next = fap->plusp;
	fap->plusp = pp;
}

LOCAL BOOL
plusexec(f, t, state)
	char	*f;
	node	*t;
	struct WALK *state;
{
	register struct plusargs *pp = (struct plusargs *)t->this;
#ifdef	PLUS_DEBUG
	register char	**ap;
	register int	i;
#endif
	int	size;
	int	slen = strlen(f) + 1;
	char	*cp;
	int	ret = TRUE;

	size = pp->laststr - (char *)&pp->nextargp[2];

	if (slen > size) {
		pp->nextargp[0] = NULL;
		ret = doexec(NULL, pp->ac, pp->av, state);
		pp->laststr = pp->endp;
		pp->ac = t->val.i;
		pp->nextargp = &pp->av[t->val.i];
		size = pp->laststr - (char *)&pp->nextargp[2];
	}
	if (slen > size)
		comerrno(EX_BAD, gettext("No space for arg '%s'.\n"), f);

	cp = pp->laststr - slen;
	strcpy(cp, f);
	pp->nextargp[0] = cp;
	pp->ac++;
	pp->nextargp++;
	pp->laststr -= slen;

#ifdef	PLUS_DEBUG
	ap = &plusp->av[0];
	for (i = 0; i < plusp->ac; i++, ap++) {
		printf("ARG %d '%s'\n", i, *ap);
	}
	error("EXECPLUS '%s'\n", f);
#endif
	return (ret);
}

EXPORT int
find_plusflush(p, state)
	void	*p;
	struct WALK *state;
{
	struct plusargs	*plusp = p;
	BOOL		ret = TRUE;

	/*
	 * Execute all unflushed '-exec .... {} +' expressions.
	 */
	while (plusp) {
#ifdef	PLUS_DEBUG
		error("lastr %p endp %p\n", plusp->laststr, plusp->endp);
#endif
		if (plusp->laststr != plusp->endp) {
			plusp->nextargp[0] = NULL;
			if (!doexec(NULL, plusp->ac, plusp->av, state))
				ret = FALSE;
		}
		plusp = plusp->next;
	}
	return (ret);
}

LOCAL void
usage(ret)
	int	ret;
{
	error(gettext("Usage:	%s [options] [path_1 ... path_n] [expression]\n"), get_progname());
	error(gettext("Options:\n"));
	error(gettext("	-H	follow symbolic links encountered on command line\n"));
	error(gettext("	-L	follow all symbolic links\n"));
	error(gettext("*	-P	do not follow symbolic links (default)\n"));
	error(gettext("*	-help	Print this help.\n"));
	error(gettext("*	-version Print version number.\n"));
	error(gettext("Operators in decreasing precedence:\n"));
	error(gettext("	( )	group an expression\n"));
	error(gettext("	!, -a, -o negate a primary (unary NOT), logical AND, logical OR\n"));
	error(gettext("Primaries:\n"));
	error(gettext("*	-acl	      TRUE if the file has additional ACLs defined\n"));
	error(gettext("	-atime #      TRUE if st_atime is in specified range\n"));
#ifdef	CHGRP
	error(gettext("*	-chgrp gname/gid always TRUE, sets st_gid to gname/gid\n"));
#endif
#ifdef	CHMOD
	error(gettext("*	-chmod mode/onum always TRUE, sets permissions to mode/onum\n"));
#endif
#ifdef	CHOWN
	error(gettext("*	-chown uname/uid always TRUE, sets st_uid to uname/uid\n"));
#endif
	error(gettext("	-ctime #      TRUE if st_ctime is in specified range\n"));
	error(gettext("	-depth	      evaluate directory content before directory (always TRUE)\n"));
	error(gettext("*	-dostat	      Do not do stat optimization (always TRUE)\n"));
	error(gettext("	-exec program [argument ...] \\;\n"));
	error(gettext("	-exec program [argument ...] {} +\n"));
	error(gettext("*	-false	      always FALSE\n"));
	error(gettext("*	-follow	      outdated: follow all symbolic links (always TRUE)\n"));
	error(gettext("*	-fstype type  TRUE if st_fstype matches type\n"));
	error(gettext("	-group gname/gid TRUE if st_gid matches gname/gid\n"));
	error(gettext("*	-inum #	      TRUE if st_ino is in specified range\n"));
	error(gettext("*	-linkedto path TRUE if the file is linked to path\n"));
	error(gettext("	-links #      TRUE if st_nlink is in specified range\n"));
	error(gettext("*	-lname glob   TRUE if symlink name matches shell glob\n"));
	error(gettext("*	-local	      TRUE if st_fstype does not match remote fs types\n"));
	error(gettext("*	-lpat pattern TRUE if symlink name matches pattern\n"));
	error(gettext("*	-ls	      list files similar to 'ls -ilds' (always TRUE)\n"));
	error(gettext("*	-maxdepth #   descend at most # directory levels (always TRUE)\n"));
	error(gettext("*	-mindepth #   start tests at directory level # (always TRUE)\n"));
	error(gettext("	-mtime #      TRUE if st_mtime is in specified range\n"));
	error(gettext("	-name glob    TRUE if path component matches shell glob\n"));
	error(gettext("	-newer file   TRUE if st_mtime newer then mtime of file\n"));
	error(gettext("	-newerXY file TRUE if [acm]time (X) newer then [acm]time (Y) of file\n"));
	error(gettext("	-nogroup      TRUE if not in group database\n"));
	error(gettext("	-nouser       TRUE if not in user database\n"));
	error(gettext("	-ok program [argument ...] \\;\n"));
	error(gettext("*	-pat pattern  TRUE if path component matches pattern\n"));
	error(gettext("*	-path glob    TRUE if full path matches shell glob\n"));
	error(gettext("	-perm mode/onum TRUE if symbolic/octal permission matches\n"));
	error(gettext("*	-ppat pattern TRUE if full path matches pattern\n"));
	error(gettext("	-print	      print file names line separated to stdout (always TRUE)\n"));
	error(gettext("*	-printnnl     print file names space separated to stdout (always TRUE)\n"));
	error(gettext("	-prune	      do not descent current directory (always TRUE)\n"));
	error(gettext("	-size #	      TRUE if st_size is in specified range\n"));
	error(gettext("*	-sparse	      TRUE if file appears to be sparse\n"));
	error(gettext("*	-true	      always TRUE\n"));
	error(gettext("	-type c	      TRUE if file type matches, c is from (b c d D e f l p s)\n"));
	error(gettext("	-user uname/uid TRUE if st_uid matches uname/uid\n"));
	error(gettext("*	-xattr	      TRUE if the file has extended attributes\n"));
	error(gettext("	-xdev, -mount restrict search to current filesystem (always TRUE)\n"));
	error(gettext("Primaries marked with '*' are POSIX extensions.\n"));
	error(gettext("If path is omitted, '.' is used. If expression is omitted, -print is used.\n"));
	exit(ret);
}

#ifdef FIND_MAIN

/* ARGSUSED */
LOCAL int
getflg(optstr, argp)
	char	*optstr;
	long	*argp;
{
/*	error("optstr: '%s'\n", optstr);*/

	if (optstr[1] != '\0')
		return (-1);

	switch (*optstr) {

	case 'H':
		*(int *)argp |= WALK_ARGFOLLOW;
		return (TRUE);
	case 'L':
		*(int *)argp |= WALK_ALLFOLLOW;
		return (TRUE);
	case 'P':
		*(int *)argp &= ~(WALK_ARGFOLLOW | WALK_ALLFOLLOW);
		return (TRUE);

	default:
		return (-1);
	}
}

EXPORT int
main(ac, av)
	int	ac;
	char	**av;
{
	int	cac  = ac;
	char *	*cav = av;
	char *	*firstpath;
	char *	*firstprim;
	BOOL	help = FALSE;
	BOOL	prversion = FALSE;
	finda_t	fa;
	node	*Tree;
	struct WALK	walkstate;

	save_args(ac, av);

#ifdef	USE_NLS
	setlocale(LC_ALL, "");
	bindtextdomain("SCHILY_FIND", "/opt/schily/lib/locale");
	textdomain("SCHILY_FIND");
#endif
	init_findargs(&fa);
	fa.walkflags = WALK_CHDIR | WALK_PHYS;
	fa.walkflags |= WALK_NOSTAT;

	cac--, cav++;
	getargs(&cac, (char * const **)&cav, "help,version,&",
			&help, &prversion,
			getflg, (long *)&fa.walkflags);
	if (help) {
		usage(0);
	}
	if (prversion) {
		printf("sfind release %s (%s-%s-%s) Copyright (C) 2004-2006 Jörg Schilling\n",
				strvers,
				HOST_CPU, HOST_VENDOR, HOST_OS);
		exit(0);
	}

	firstpath = cav;	/* Remember first file type arg */
	find_firstprim(&cac, (char *const **)&cav);
	firstprim = cav;	/* Remember first Primary type arg */
	fa.Argv = cav;
	fa.Argc = cac;

	if (cac) {
		Tree = find_parse(&fa);
		if (fa.primtype != ENDARGS)
			comerrno(EX_BAD, gettext("Incomplete expression.\n"));
		if (find_pname(Tree, "-chown") || find_pname(Tree, "-chgrp") ||
		    find_pname(Tree, "-chmod")) {
			comerrno(EX_BAD,
				gettext("Unsupported primary -chown/-chgrp/-chmod.\n"));
		}
	} else {
		Tree = 0;
	}
	if (Tree == 0) {
		Tree = &Printnode;
	} else if (!fa.found_action) {
		node	*n;

		n = allocnode();
		n->op = AND;
		n->left = (char *)Tree;
		n->right = (char *)&Printnode;
		Tree = n;
	}
	if (fa.patlen > 0) {
		walkstate.patstate = __malloc(sizeof (int) * fa.patlen,
						"space for pattern state");
	}

	now	 = time(0);
	sixmonth = now - 6L*30L*24L*60L*60L;
	now	 = now + 60;

	walkstate.walkflags	= fa.walkflags;
	walkstate.maxdepth	= fa.maxdepth;
	walkstate.mindepth	= fa.mindepth;
	walkstate.lname		= NULL;
	walkstate.tree		= Tree;
	walkstate.err		= 0;
	walkstate.pflags	= 0;

	if (firstpath == firstprim) {
		treewalk(".", walkfunc, &walkstate);
	} else {
		for (cav = firstpath; cav != firstprim; cav++) {
			treewalk(*cav, walkfunc, &walkstate);
			/*
			 * XXX hier break wenn treewalk() Fehler gemeldet
			 */
		}
	}
	/*
	 * Execute all unflushed '-exec .... {} +' expressions.
	 */
	find_plusflush(fa.plusp, &walkstate);
	return (walkstate.err);
}

#endif /* FIND_MAIN */
