#ifndef NO_IDENT
static char *Id = "$Id: dirdata.c,v 1.10 1995/06/04 21:18:02 tom Exp $";
#endif

/*
 * Title:	dirdata.c
 * Author:	Thomas E. Dickey
 * Created:	13 Jul 1985
 * Last update:
 *		28 May 1995, prototypes
 *		22 Jul 1985, if rename, make sure path has same references as
 *			     the data objects, use 'dirpath_add'
 *		16 Jul 1985, added 'dirdata_one', 'dirdata_ren'.
 *
 * Function:	This module contains the principal routines which maintain the
 *		'filelink' linked-list of file information for FLIST.  We
 *		keep a single list of all files known to all levels of FLIST,
 *		distinguishing ownership by a given level with a bit-mask
 *		'.file_refs' (see NAMEHEAP).  The list is sorted in the order
 *		which a SYS$SEARCH would yield the entire list of files:
 *
 *		* increasing by pathname,
 *		* increasing by filename,
 *		* increasing by filetype, and
 *		* decreasing by version.
 *
 *		Each list entry contains a FILENT block (.fk) which records
 *		everything which we know about a particular file.
 *
 *		The linked-list routines use an initial-pointer argument so
 *		that successive calls (as within a SYS$SEARCH-loop) will tend
 *		to reduce search time: we need only search from the last file
 *		known.
 *
 * Entrypoint:	dirdata_add:	Add the given FILENT block to this level.
 *		dirdata_chg:	Add FILENT block, given prior link-pointer.
 *		dirdata_find:	Find a given FILENT block in the list.
 *		dirdata_high:	Search list for higher-version than FILENT block.
 *		dirdata_one:	Add FILENT block, searching from beginning.
 *		dirdata_ren:	Rename file, relinking block.
 */

#include	<stdlib.h>
#include	<string.h>

#include	<rms.h>

#include	"flist.h"
#include	"dirent.h"
#include	"dirdata.h"
#include	"dirpath.h"
#include	"nameheap.h"

import(filelink);

/* <dirdata_find>:
 * Returns:
 *	1, iff we find the entry already in the list or
 *	-1, 0, iff the entry is not in the list.
 *
 * If we do not find the entry in the list, we leave 'lastp' set to the entry
 * which would precede the new data.  Thus, iff the list is empty, 'lastp' will
 * be zero.  The "-1" return value is used to distinguish the list-beginning
 * when the entry is not found in the list.
 */
int	dirdata_find (FILENT *z, FLINK **lastp)
{
FLINK	*now	= filelink,
	*old	= filelink;
int	found	= 0,
	first,
	cmp;

	if (lastp)	if (*lastp)	now	= *lastp;
	first	= (now == filelink);
#ifdef	DEBUG
	trace ("Find: %s%s.%s;%d\n", zPATHOF(z), z->fname, z->ftype, z->fvers);
	trace ("%08X ", now);
#endif

#define	BINCMP(q)	(cmp = (z->q - now->fk.q))
#define	STRCMP(q)	(cmp = strcmp(z->q, now->fk.q))
#define	DIFFP(q)	(z->q != now->fk.q)
#define	SAMEP(q)	(z->q == now->fk.q)

	while (now)
	{
		if (BINCMP(fpath_->path_sort) > 0)	goto after;
		else if (cmp < 0)			goto before;

		if (DIFFP(fname))
		{
			if (STRCMP(fname) > 0)		goto after;
			else if (cmp < 0)		goto before;
		}

		if (DIFFP(ftype))
		{
			if (STRCMP(ftype) > 0)		goto after;
			else if (cmp < 0)		goto before;
		}

		if (BINCMP(fvers) < 0)			goto after;
		else if (cmp >  0)			goto before;
		else if (cmp == 0)
		{
			found = 1;
			goto exit;
		}

		/*
		 * The new entry should go after the 'now' entry.  Link to the
		 * next one, to test.
		 */
after:		old = now;
		now = now->file_next;
		first = FALSE;
	}

	/*
	 * The new entry should go before the 'now' entry.  Assume that 'old'
	 * points to the proper position.
	 */
before:	if (first)
	{
		now	= filelink;	/* Insert at list-beginning	*/
		found	= -1;
	}
	else
		now	= old;

exit:	if (lastp)			*lastp	= now;
#ifdef	DEBUG
	trace ("=> %08X (%d):%d\n", now, found, first);
#endif
	return (found);
}

/* <dirdata_add>:
 * Unconditionally add/replace data in the linked-list.  We use this entry, for
 * example, when showing the result of reading a directory (without suppressing
 * lower versions).
 */
void	dirdata_add (FILENT *z, FLINK **lastp)
{
	dirdata_chg (z, lastp, dirdata_find (z, lastp));
}

/* <dirdata_chg>:
 * Add/replace data in the linked-list, using the return value from 'dirdata_find'
 * to guide the link-placement.  We use this entry, for example, when reading
 * a directory subject to pruning lower versions (i.e., first, '_find', then
 * '_high', then '_chg'.
 */
void	dirdata_chg (FILENT *z, FLINK **lastp, int found)
{
	FLINK	*p, *q, *r;

#ifdef	DEBUG
trace ("CHG: %08X %d\n", *lastp, found);
#endif
	if (found <= 0)
	{
		p = calloc (1, sizeof(FLINK));
		if (q = *lastp)
		{
			if (found < 0)	/* Insert at beginning	*/
			{
				p->file_next = filelink;
				filelink = p;
			}
			else		/* Normal insertion	*/
			{
				r = q->file_next;
				q->file_next = p;
				p->file_next = r;
			}
		}
		else			/* Only entry in list	*/
			filelink = p;
		*lastp	= p;
	}
	else
		p = *lastp;
	p->file_refs |= nameheap_ref();
	p->fk	= *z;
#ifdef	DEBUG
	trace ("FLINK: ");
	for (p = filelink; p; p = p->file_next)
		trace ("%08X ", p);
	trace ("\n");
#endif
}

/* <dirdata_high>:
 * Search the list to determine if there is an entry with a higher version
 * than the FILENT-block.  Use the 'lastp' argument as a reference; don't
 * need to search past this point.
 */
int	dirdata_high (FILENT *z, FLINK **lastp)
{
	FLINK	*now = filelink;
	int	cmp;

	while (now)
	{
		if (SAMEP(fpath_) && SAMEP(fname) && SAMEP(ftype))
			return (BINCMP(fvers) < 0);
		if (now == *lastp)	break;
		now = now->file_next;
	}
	return (FALSE);
}

/* <dirdata_one>:
 * Search from list-beginning to insert/update a FILENT block.  We use this for
 * code which is not driven from a SYS$SEARCH loop.
 */
void	dirdata_one (FILENT *z, FLINK **lastp)
{
	*lastp = filelink;
	dirdata_add (z, lastp);
}

/* <dirdata_ren>:
 * Rename a file.  Because entries in 'filelist' will point to the original
 * block, we do not simply allocate a new block, but re-use the old one.
 * This requires that we re-link the list.
 */
void	dirdata_ren (FILENT *znew, FILENT *zold)
{
	FLINK	*pnew = 0,
		*pold = 0;
	int	found = dirdata_find (znew, &pnew);

	if (!memcmp(znew, zold, sizeof(FILENT)))
		return;		/* don't waste my time !! */

	/*
	 * If the "new" name is in the list already, then this is probably
	 * (if no bug!) because the caller marked the file deleted.  To safely
	 * dispose of this, we must move it also in the list by making its
	 * version number non-conflicting with other entries.
	 */
	if (found > 0)
	{
	FLINK	*old,
		*now	= pnew->file_next;

	    /*
	     * De-link the "new" entry from the list, saving pointer-before.
	     */
	    if (pnew == filelink)
	    	old = filelink = pnew->file_next;
	    else
	    {
		for (old = filelink; old->file_next != pnew; old = old->file_next);
		old->file_next = pnew->file_next;
	    }

	    /*
	     * If the block is referenced by more than the current level, find
	     * a new insertion point (with a negative version code to make it
	     * transparent to other data).
	     */
	    if (pnew->file_refs != nameheap_ref())
	    {
	    FILENT	*z	= &(pnew->fk);
	    int		tvers	= -1;

		now = old->file_next;	/* => after de-linked "new" data */
		while (now)
		{
		    if (!(SAMEP(fpath_) && SAMEP(fname) && SAMEP(ftype)))
			break;
		    if (tvers >= now->fk.fvers) tvers = now->fk.fvers - 1;
		    old = now;
		    now = now->file_next;
		}
		pnew->fk.fvers = tvers;
		if (old == filelink)
		{
		    pnew->file_next = filelink;
		    filelink = pnew;
		}
		else
		{
		    pnew->file_next = old->file_next;
		    old->file_next  = pnew;
		}
	    }
	    else	/* simply de-allocate the entry	*/
		cfree (pnew);
	}

	/*
	 * Now, we know that the "new" name does not appear in the list.  We
	 * can re-link the entry in which we have the "old" filename stored
	 * (if it is in the list).
	 *
	 * From the code above, we now have no instance of 'znew' in the list.
	 */
	if ((found = dirdata_find (zold, &pold)) > 0)
	{
	FLINK	*now = filelink;

	    if (now == pold)	/* de-link at beginning */
		filelink = now->file_next;
	    else
	    {
		while (now->file_next != pold)
		    now = now->file_next;
		now->file_next = pold->file_next;
	    }
	    pold->fk = *znew;
	    pnew = nullS(FLINK);
	    if ((found = dirdata_find (znew, &pnew)) < 0)
	    {
		pold->file_next = filelink;
		filelink = pold;
	    }
	    else
	    {
		pold->file_next = pnew->file_next;
		pnew->file_next = pold;
	    }
	    pold->fk = *znew;
	}
	else
	{
	    pold = 0;
	    dirdata_one (znew, &pold);
	}

	/*
	 * Ensure that any strings and pathnames to which we refer have at least
	 * the same scope as the object which we have just renamed:
	 */
	dirpath_add  (pold->file_refs, pold->fk.fpath_);
	nameheap_add (pold->file_refs, pold->fk.fname);
	nameheap_add (pold->file_refs, pold->fk.ftype);
}
