#ifndef NO_IDENT
static char *Id = "$Id: dirfind.c,v 1.8 1995/10/25 15:40:18 tom Exp $";
#endif

/*
 * Title:	dirfind.c
 * Author:	Thomas E. Dickey
 * Created:	28 Jul 1984 (broke off search code from 'flfind.c')
 * Last update:
 *		25 Oct 1995, corrected missing check for deleted-files in
 *			     'dirfind()'.
 *		18 Mar 1995, prototypes
 *		14 Sep 1985, enhanced 'strwcmp' to process "..." in pathname.
 *			     Therefore, we need substitute a "*" into the
 *			     pathname only for the case of non-explicit path.
 *		09 Sep 1985, count trailing '.' in fname-field for VMS-collating
 *		13 Aug 1985, added 'dirfind_chop' to make clean "?FIND" display.
 *		03 Jul 1985, cleanup 'filelist' definition
 *		23 Jun 1985, use 'scanver' to isolate version-scanning.
 *		15 Jun 1985, eliminated unused 'j' argument from 'if_T'.
 *			     Reference function-'each' as '(*each) to make
 *			     CC2.0 happy.
 *		02 May 1985, changed call on 'dircmd_dirflg'
 *		10 Mar 1985, revised PATHNT structure.
 *		08 Mar 1985, defined negative code for 'forward' argument.
 *		05 Feb 1985, broke out 'dirfind_notexp' to permit reuse.
 *		30 Jan 1985, re-code so that 'dirfind_tst' receives pointers to
 *			     two FILENT-blocks (permitting re-use).
 *		05 Jan 1985, use DCLARG-structure rather than string to
 *			     represent search-spec, so that we needn't do
 *			     another parse here on the default-spec to obtain
 *			     wildcard information.
 *		29 Dec 1984, do "working..." if 'dft_spec' uses wildcard,
 *			     when 'each' is set.
 *		24 Dec 1984, added 'unfind' argument so that this can support
 *			     the "NFIND" command (search for mismatch).
 *		14 Dec 1984, added nam-argument to 'dirent_chop'
 *		10 Sep 1984, use "rmsinit"
 *		26 Aug 1984, cleanup buffer sizes
 *		16 Aug 1984, use '.fmisc' flag to hit entries once
 *		13 Aug 1984, fixed use of 'must_find', permit quit from loop.
 *			     Added (kludgy) relative version testing.
 *		24 Jul 1984, re-coded test for wildcard pathname
 *		15 Jul 1984, use PATHOF()
 *		05 Jul 1984
 *
 * Function:	The 'dirfind' procedure performs in-list searches for FLIST.
 *		This directly supports the FIND and NEXT commands, as well
 *		as the VERIFY command (which must find specific wildcard lists
 *		in 'filelist[]').
 *
 *		The FIND/NEXT commands require that the search be startable
 *		from any point, in FLIST's sorted order rather than that known
 *		to SYS$SEARCH.  Therefore I use my own wildcard compare.
 *
 *		If 'each' is set, we use a separate loop from the "once"
 *		searches.  We use the actual search loop to find all files
 *		to which the search applies before calling 'each'.  This is
 *		needed to make DELETE (especially of current versions!) work
 *		properly.
 *
 * Parameters:	curfile	- is the starting index in 'filelist[].
 *		forward - defines direction of search:
 *			  (-1): Use default direction-flag to modify 'curfile'
 *				so that the apparent search will begin at
 *				'curfile' and loop around.
 *			  (0):	Search backward from 'curfile', with wraparound.
 *			  (1):	Search forward from 'curfile', with wraparound.
 *		find_spec - is a DCLARG structure containing the parsed filespec
 *			(containing wildcards) to use as the search pattern.
 *		each - is a pointer to a function to execute on each entry
 *			which matches.  If no function is given, assume that
 *			we wish to find only the next occurrence.  If the
 *			pointer isn't null, continue executing the search
 *			loop until no more items are found.  If the function
 *			returns FALSE, do a quit after executing.
 *		must_find - is TRUE if failure to find any entry will cause
 *			an error message.
 *		unfind - TRUE if we are searching for a mismatch, rather than
 *			a match.
 *
 * Returns:	If the filespec is found, return the index into 'filelist[]'
 *		at which it was found, or a negative number if it was not.
 *		(If 'each' is set, instead return the number of entries
 *		which were found.)
 *
 * patch:	Should provide date-wise selection options to support DELETE,
 *		PURGE, FIND.
 */

#include	<string.h>

#include	<rms.h>
#include	<stsdef.h>

#include	"flist.h"
#include	"dirent.h"
#include	"dclarg.h"
#include	"dds.h"
#include	"dircmd.h"

#include	"dirfind.h"

#include	"scanver.h"
#include	"strutils.h"

import(filelist); import(numfiles);

int	dirfind (
	int	curfile,
	int	forward,
	DCLARG	*find_spec,
	void	(*each)(int, int *),
	int	must_find,
	int	unfind)
{
#define	FIND_FNB	find_spec->dcl$l_fnb
#define	FIND_TEXT	find_spec->dcl_text

PATHNT	pz;
FILENT	fz;

int	flag	= TRUE,		/* continue-loop unless 'each' quits	*/
	found_any = 0;
int	j, k, tst;
char	Fpath[MAX_PATH],	Fname[MAX_NAME+1],	Ftype[MAX_TYPE+1];

	dirfind_chop (find_spec, &fz, &pz, Fpath, Fname, Ftype);

	/*
	 * If 'forward' is negative, obtain the proper value from the
	 * command decoder:
	 */
	if (forward < 0)
	{
		forward = dircmd_dirflg(-1);
		curfile	= dirfind_nxt (curfile, !forward);
	}

#define	XOR(a,b) ((a && !b) || (!a && b))
#define	if_T	tst = dirfind_tst(FK_(j), &fz, TRUE);\
		if (XOR(tst,unfind))
#define	LOOP	for (j = dirfind_nxt(curfile,forward), k = numfiles; k > 0;\
			j = dirfind_nxt(j,forward), k--)
#define	BIT_1	1		/* Use this bit via 'dirent_misc'	*/

	if (each)		/* Multiple action ?	*/
	{
		dirent_misc (-1, BIT_1);	/* Reset all '.fmisc'	*/
		if (strchr(FIND_TEXT,'*') || strchr(FIND_TEXT,'%')
		||  strchr(Fpath, '*'))
			dds_while (nullC);

		for (j = 0; j < numfiles; j++)
		{
			if_T
				dirent_misc (j, BIT_1);
		}
		LOOP
		{
			if (DELETED(j))		continue;
			dds_while(nullC);
			if (FK(j).fmisc & BIT_1)
			{
				(*each)(j, &flag);
				found_any++;
				if (!flag)		break;
			}
		}
		dirent_misc (-1, BIT_1);	/* cleanup after use	*/
	}
	else
	{
		LOOP
		{
			if (DELETED(j))		continue;
			if_T			return (j);
		}
	}

	if (must_find && !found_any)
	{
		char	fullname[MAX_PATH];
		warn ("Not found: %s", dirent_glue (fullname, &fz));
	}

	if (found_any)	return (found_any);
	else		return (-2);
}

/*
 * Split the given DCLARG-pattern into a FILENT-block, so that the pathname,
 * filename, type and version will be readily accessible for use in the
 * directory-search.  This routine is also used to partly-parse the search
 * target when "?FIND" is executed.
 */
void	dirfind_chop (
	DCLARG	*find_spec,
	FILENT	*fz_,
	PATHNT	*pz_,
	char	*Fpath, char *Fname, char *Ftype)
{
	int	len	= 0,
		j,
		version;	/* value if version null or "*"		*/

#define	CPY(to)	if (len > 0) strncpy(to, &FIND_TEXT[j], len);\
		to[len] = EOS; j += len

	j	= 0;
	len	= find_spec->dcl$b_node
		+ find_spec->dcl$b_dev
		+ find_spec->dcl$b_dir;			/* Directory-path*/
	CPY(Fpath);

	len	= find_spec->dcl$b_name + NAME_DOT;	/* name with trailing '.' */
	CPY(Fname);
	j	-= NAME_DOT;		/* '.' is normally counted in type */

	len	= find_spec->dcl$b_type - 1;	j++;	/* Don't use "." */
	CPY(Ftype);

	version	= scanver (&FIND_TEXT[j], find_spec->dcl$b_ver);

	/*
	 * If pathname was not explicitly given, make the entire path wild.
	 */
	if (dirfind_notexp(find_spec))
		strcpy (Fpath, "*");

	/*
	 * Set up structure so we can use 'dirfind_tst':
	 */
	fz_->fpath_ = pz_;	pz_->path_text = Fpath;
	fz_->fname  = Fname;
	fz_->ftype  = Ftype;
	fz_->fvers  = version;
}

/*
 * Make an index to the next item to test:
 */
int	dirfind_nxt (int j, int forward)
{
	if (forward)
	{
		if ((++j) >= numfiles)	j = 0;
	}
	else
	{
		if ((--j) <  0)		j = numfiles-1;
	}
	return (j);
}

/*
 * Return true iff the pathname of the indicated DCLARG-structure was
 * not explicit.  Inexplicit pathnames within FLIST are treated as a special
 * type of wildcard.
 */
int	dirfind_notexp (DCLARG *find_spec)
{
	return (!(FIND_FNB & (NAM$M_EXP_DIR + NAM$M_EXP_DEV)));
}

/*
 * Test the entry 'z' against the wildcard and exact matches in 'zref':
 */
int	dirfind_tst (
	FILENT	*z,
	FILENT	*zref,
	int	lookup)	/* TRUE if we permit lookup for relative versions */
{
	FILENT	ztmp;
	char	relnam[NAM$C_MAXRSS];
	char	absnam[NAM$C_MAXRSS];

	if (strwcmp(zPATHOF(zref), zPATHOF(z)))	return (FALSE);
	if (strwcmp(zref->fname,   z->fname))	return (FALSE);
	if (strwcmp(zref->ftype,   z->ftype))	return (FALSE);

	/*
	 * If the reference version is a wild-card, or if there is an exact
	 * match (only possible for versions greater than zero), return
	 * true immediately.
	 */
	if (zref->fvers == WILD_VER
	||  zref->fvers == z->fvers)
		return (TRUE);
	/*
	 * Otherwise, if the reference version is negative (i.e., relative),
	 * construct a temporary copy of the test-name, and see if the given
	 * relative version is identical with the test-name.  To permit re-use
	 * (e.g., for read-list pruning), we permit the argument 'lookup' to
	 * suppress this clause.
	 *
	 * patch: This is slow, but works !!
	 */
	else if (zref->fvers <= 0 && lookup)
	{
		ztmp	= *z;
		ztmp.fvers = zref->fvers;
		dirent_glue (relnam, &ztmp);
		if (! $VMS_STATUS_SUCCESS (dirent_look (absnam, relnam)))
			return (FALSE);
		dirent_chop (&ztmp, absnam, 0);
		return (ztmp.fvers == z->fvers);
	}
	else
		return (FALSE);
}
