#ifndef NO_IDENT
static char *Id = "$Id: dirent.c,v 1.20 1995/11/16 22:18:29 tom Exp $";
#endif

/*
 * Title:	dirent.c
 * Author:	T.E.Dickey
 * Created:	30 Apr 1984
 * Last update:
 *		15 Nov 1995, latch width for filesize, allocated
 *		25 Oct 1995, workaround for DEC-C bug in sprintf.
 *		23 Oct 1995, added 'u(ser)' column
 *		21 Oct 1995, /dlong repeated shows time in seconds.
 *		28 May 1995, prototypes
 *		18 Feb 1995, port to AXP (DATENT mods)
 *		05 Dec 1989, corrected typeof(llast)
 *		04 Nov 1988, took masking from group/member code displays
 *			     (should work out maximum field length).
 *			     Added code to support expiration-date display.
 *		16 Aug 1988, corrected 'dirent_conv()', which permitted a
 *			     buffer-overflow on terminals wider than 132!
 *		09 Sep 1985, corrected collating-order: VMS apparently sorts
 *			     up-to-and-including trailing '.' in filenames.
 *		03 Sep 1985, added 'update' option to 'dirent_all' and to
 *			     'dirent__read'.  Broke 'dirent__one' into chop-
 *			     and look-parts for this purpose.
 *		24 Aug 1985, use 'dirent_width' to reset entire-list too.
 *		20 Aug 1985, broke out 'dirent_width'.
 *		30 Jul 1985, 'numreads' (pruning only after first SYS$SEARCH)
 *			     is obsolete with muti-level heap.
 *		20 Jul 1985, added 'dirent_chk3', 'dirent_nul'.
 *		14 Jul 1985, added calls on 'dirdata' module.
 *		05 Jul 1985, moved 'dirpath_init' to main-loop.
 *		04 Jul 1985, if file-not-found in 'dirent_old_any', do not
 *			     assume NAM-block points to current parsed (expanded)
 *			     string: it may contain the previous esa/rsa.
 *		02 Jul 1985, cleanup of 'dirent.h' definitions
 *		23 Jun 1985, cleanup of 'dirent_look', return status in 'dirent_chop'
 *			     Use 'scanver' to (try to) bypass CC2.0 bug.  Got
 *			     rid of unnecessary 'strlen' calls in 'dirent_chop'.
 *		13 Jun 1985, added /AFTER and /BEFORE options (dateflag)
 *		18 May 1985, added 'pcolumns'
 *		26 Mar 1985, tuned column-widths for file-id, file-size
 *		22 Mar 1985, added file-id, record-length conversions.
 *		21 Mar 1985, added very-short date format.
 *		08 Mar 1985, added 'dirent_isdir'.
 *		03 Mar 1985, move flcols-init to main so we can treat this as
 *			     a pure hierarchy.  Restrict 'dirent_col' so that
 *			     we have a command field even if the name fills the
 *			     line by itself (still no attribute columns).
 *		01 Feb 1985, check for "*" device in 'dirent_glue' for 'dirfind'.
 *			     and "*" version, as well as in 'dirent_chop' (to
 *			     support read-list pruning 'dirread').
 *		30 Dec 1984, deselect file if it is deleted.
 *		24 Dec 1984, added 'D_mode' to drive day-of-week display.
 *		22 Dec 1984, added ALLOC column-type.
 *		21 Dec 1984, use 'flist_sysmsg'.
 *		20 Dec 1984, decode file-org, format from single byte, driving
 *			     'ccolumns' if non-sequential file.
 *		14 Dec 1984, use single FAB for parsing, search, open where
 *			     possible, to reduce paging.  Re-coded 'dirent_chop'
 *			     to use existing NAM-block, if available.  Use global
 *			     variable 'znam' in local routines 'dirent__one' and
 *			     'dirent__read'.
 *		12 Dec 1984, don't show error in 'dirent_chk'.
 *		10 Dec 1984, use 'acplook', 'rmslook' for directory-read.
 *			     Added 'nam' argument to 'dirent__read' for compat
 *			     with new 'dirent__one()'.  Moved 'syshour' to
 *			     'dirsrt'.
 *		06 Dec 1984, use 'nameheap' to store filename, filetype.  Do
 *			     better overflow check in 'dirent_conv'.  Added
 *			     'p' (PATH) to conv_list.  Interlock 'dds_tell'
 *			     call with 'flread'.
 *		17 Nov 1984, added record-format stuff
 *		15 Oct 1984, locked-files should have high-date, if unknown
 *		10 Sep 1984, use "rmsinit_???", fixed "?locked", blocksize
 *		03 Sep 1984, use "import"
 *		27 Aug 1984, cleanup buffer sizes
 *		15 Aug 1984, added 'dirent_dft()', multi-date support
 *		14 Aug 1984, added 'ccolumns[]', 'dirent_col()'
 *		06 Aug 1984, added '.fhour' component to FILENT
 *		02 Aug 1984, broke out 'sysasctim' procedure
 *		30 Jul 1984, added 'dirent_dlet' entry.
 *		28 Jul 1984, correction to same-file login
 *		27 Jul 1984, broke out pathlist/readlist logic to 'dirpath'.
 *		26 Jul 1984, added 'dirent_chk2' code, 'readlist'
 *		20 Jul 1984, added 'numreads' logic
 *		17 Jul 1984, added V_opt/duplicate pruning to 'dirent__read'.
 *		15 Jul 1984, make sort-keys for 'pathlist'
 *		14 Jul 1984, use 'dclopt' to process options, added OWNER
 *		10 Jul 1984, added warning-option to 'dirent_old'
 *		04 Jul 1984, must clear temp in 'dirent_old' before compare
 *		03 Jul 1984, broke out 'dirent_acc' as 'cmpprot.c', cleaned up.
 *		02 Jul 1984, broke out 'dirent_path' as 'pathdown.c'
 *		28 May 1984
 *
 * Function:	This module performs wildcard directory lookup using the
 *		argc/argv argument list passed to the main program of "FLIST".
 *		It processes options which permit "FLIST" to display less than
 *		the full directory listing (for speed).
 *
 * Entry:	dirent:		Read directory, given argument list
 *		dirent_acc:	Test a FILENT-block for needed access-mode
 *		dirent_add:	increment 'numfiles', reallocate 'filelist[]'
 *		dirent_all:	Load a series of files to 'filelist[]'
 *		dirent_ccol:	Return crt command-column start
 *		dirent_chk:	re-read a file-spec to update FILENT-block
 *		dirent_chk2:	re-read a file-spec to update 'filelist[]'
 *		dirent_chk3:	read a file-spec toupdate (?) 'filelist[]'
 *		dirent_chop:	parse a file-spec to load FILENT-block
 *		dirent_conv:	Convert a FILENT-block to CRT_COLS (max) string
 *		dirent_dft:	Return wildcard default, based on V_opt
 *		dirent_dlet:	Mark a 'filelist[]' entry deleted.
 *		dirent_glue:	form a complete file-spec from FILENT block
 *		dirent_glue2:	form a (short?) file-spec from FILENT block
 *		dirent_init:	Initialize this module
 *		dirent_isdir:	Test FILENT to see if it is a directory.
 *		dirent_look:	lookup a single file-spec, returns full name
 *		dirent_misc:	Set/clear bits in the '.fmisc' component
 *		dirent_nul:	Create a null-entry in 'filelink'.
 *		dirent_old:	find a filespec in 'filelist[]'
 *		dirent_old1:	Lookup any file, returning FILENT-block
 *		dirent_old_any:	Generalized lookup to 'filelist[]'
 *		dirent_width:	latch maximum column-widths for new entry
 *		dirent__cnv:	Convert numbers for display
 *		dirent__date:	Select date-field
 *		dirent__datechek:Test date-selection
 *		dirent__look:	read a FILENT-block w/o SYS$SEARCH
 *		dirent__one:	read a FILENT-block for all file-info
 *		dirent__read:	read a FILENT-block, pruning if duplicates
 */

#include	<starlet.h>
#include	<lib$routines.h>
#include	<stdlib.h>
#include	<stdio.h>
#include	<string.h>
#include	<rms.h>
#include	<descrip.h>
#include	<stsdef.h>

#include	"flist.h"
#define		DIRENT		/* local */
#include	"dirent.h"
#include	"dircmd.h"
#include	"dirdata.h"
#include	"dirpath.h"
#include	"dirread.h"
#include	"dclarg.h"
#include	"dds.h"

#include	"getprot.h"
#include	"rmsinit.h"
#include	"nameheap.h"
#include	"scanver.h"

#include	"strutils.h"
#include	"sysutils.h"

#define	MAXFILES	(4096 / sizeof(filelist[0]))

#define	LATCH(i,f)	if ((f) > ccolumns[i])	ccolumns[i] = latch = len

static	void	dirent__cnv2 (int no_priv, char *c_, unsigned number);
static	DATENT*	dirent__date (FILENT *z, int opt);
static	int	dirent__look (FILENT *z, char *filespec);
static	int	dirent__one (FILENT *z, char *filespec);
static	int	dirent__read (char *filespec, FLINK **flink, int update);
static	void	dirent_init (void);

/*
 * Public data:
 */
import(filelist); import(numfiles); import(numdlets);

import(AnyXAB);
import(A_opt);	import(D_opt);	import(M_opt);
import(O_opt);	import(P_opt);	import(V_opt);

import(D_mode);

import(ccolumns);
import(pcolumns);
import(conv_list);
import(maxfiles);
import(namelist);

import(dateflag);
import(datechek);

	int	prot_col;		/* (used by protection-edit)	*/

/*
 * Data structures used in directory lookup:
 */
static	struct	FAB	zfab;
static	struct	NAM	znam;		/* used in wildcard parsing	*/

static
char	zrsa[NAM$C_MAXRSS],		/* resultant string area	*/
	zesa[NAM$C_MAXRSS];		/* expanded string area		*/

/*
 * Data structures used to obtain info on particular file:
 */
static	struct	XABALL	xaball;		/* Allocation data		*/
static	struct	XABDAT	xabdat;		/* Date and time		*/
static	struct	XABFHC	xabfhc;		/* File header characteristics	*/
static	struct	XABPRO	xabpro;		/* Protection			*/

/*
 * The RMS copy of the file-organization is simply masked from the ACP copy.
 * Exploit this to make a mask for the format portion:
 */
#define	FAB$M_RFM	((1 << FAB$V_ORG)-1)

static
char	*known_org[] = {"SEQ", "REL", "IDX", "HSH"},
	*known_rfm[] = {" ", "FIX", "VAR", "VFC", "STM", "STMLF", "STMCR"},
	*known_rat[] = {"FTN", "CR", "PRN", "BLK"},
	*day_of_week[] = {"Wed ", "Thu ", "Fri ", "Sat ", "Sun ", "Mon ", "Tue "};
static
int	absolute_day;	/* Current day-number, for short-date computation */

/* <dirent>:
 * Main routine, read a list of filespecs into 'filelist[]'.
 */
int	dirent (DCLARG *arg_)
{
	int	j, didsome = 0;
	char	c, *c_;

	dirread_init ();

	numfiles =		/* (no files in list yet)	*/
	numdlets = 0;		/* (of course, none deleted yet)*/

	dirent_width ((FILENT *)0);	/* Set nominal widths for entries*/

	filelist = calloc(maxfiles = MAXFILES, sizeof(filelist[0]));

	dirent_init();
	for (; arg_; arg_ = arg_->dcl_next)
	{
		c_ = arg_->dcl_text;
		if (!isopt(*c_))
		{
			dirent_all(c_, FALSE);
			didsome++;
		}
	}
	if (!(didsome || numfiles))
		dirent_all ("*", FALSE);

	return (numfiles);
}

/* <dirent_acc>:
 * Test a file-entry to see if the user may access the file:
 */
int	dirent_acc (
	FILENT	*z,		/* file to test			*/
	char	*mode)		/* List of access rights needed	*/
{
	return (cmpprot (&z->f_getprot, mode));
}

/* <dirent_add>:
 * Whenever the amount of space reserved for 'filelist[]' is filled, reallocate
 * to obtain reserve.
 */
int	dirent_add (void)
{
	if (++numfiles >= maxfiles)
	{
		maxfiles += MAXFILES;
		filelist = realloc (filelist, maxfiles * sizeof(FILENT));
	}
	return (numfiles);
}

/* <dirent_all>:
 * Use a wildcard string to (possibly) examine all files in a directory.
 *
 * In the array 'readlist', maintain a list of the file-specs used
 * as calls on this procedure, to support the READ command (re-read all
 * specs).
 */
void	dirent_all(
	char	*filespec,		/* specifies files to lookup	*/
	int	update)			/* true iff we *must* re-read	*/
{
	unsigned status;
	int	numfirst = numfiles,
		newfiles = FALSE;	/* Latch for 'flread'	*/
	FLINK	*llast	 = 0;

	/*
	 * Initialize file-access-block:
	 */
	zfab.fab$l_fna = filespec;	/* given-names		*/
	zfab.fab$b_fns = strlen(filespec);

	if (sys$parse(&zfab) != RMS$_NORMAL)
		return;

	dirread_put (&zfab);		/* Update 'readlist'	*/

	/*
	 * MAXFILES is the number of files to access before reallocating
	 * the 'filelist[]' array to enlarge it.
	 */
	for (;;)
	{
		dds_while(nullC);
		if ((status = sys$search(&zfab)) == RMS$_NMF)
			return;
		else if (status == RMS$_NORMAL)
		{
			if (numfiles > numfirst)	newfiles = TRUE;
			if (newfiles && (numfiles % 5 == 0) && numfiles)
			{
				clrbeep ();
				dds_tell (nullC, -1);
			}
			zrsa[znam.nam$b_rsl] = EOS;
			if (dirent__read (zrsa, &llast, update))
			{
				if (dirent__datechek (&llast->fk))
				{
					filelist[numfiles] = llast;
					numfiles = dirent_add ();
				}
			}
		}
		else
			flist_sysmsg (status);
	}
}

/* <dirent_cat_n>:
 */
void	dirent_cat_n (char *s, FILENT *z)
{
	register len = strlen(z->fname);

	strncpy (s += strlen(s), z->fname, len);
	s[len-NAME_DOT] = EOS;
}

/* <dirent_ccol>:
 * Compute the starting column number at which "visible" commands
 * begin.  This is made a variable to support future (14-Aug-84) releases
 * of VMS, which may use long filenames.
 *
 * Protect further against fools with long filenames on a narrow terminal
 * by permitting the command-field to overlay the name-field if no room is
 * found on the right of it.
 */
#define	PCOLUMNS(n) (pcolumns[n] ? min(pcolumns[n],ccolumns[n]) : ccolumns[n])
int	dirent_ccol (void)
{
	register width = crt_width()-10,
	ccol = PCOLUMNS(0) + PCOLUMNS(1) + ccolumns[2] + 6;
	return (min(width, ccol));
}

/* <dirent_chk>:
 * Re-read a directory entry to obtain newer information about a file.
 */
int	dirent_chk (
	FILENT	*z,		/* data block to load into	*/
	char	*filespec)	/* filename to check up on	*/
{
	char	longspec[NAM$C_MAXRSS];

	if ((z->fstat = dirent_look (longspec, filespec)) == RMS$_NORMAL)
	{
		zrsa[znam.nam$b_rsl] = EOS;
		return (dirent__one (z, zrsa));
	}
	else if (! zDELETED(z))
	{
		warn2 ("Unexpected RMS-err: '%s' -> %X", filespec, z->fstat);
		flist_sysmsg (z->fstat);
	}

	return (FALSE);		/* file was not found	*/
}

/* <dirent_chk2>:
 * Re-read an entry in 'filelist[]'.  The primary intent of this is to check
 * for deleted files, and to mark them as such in this code.  If the file
 * still exists, TRUE is returned.  In either case, the display-line is
 * refreshed.
 */
int	dirent_chk2 (int j)
{
	int	retval;
	char	fullname[NAM$C_MAXRSS];

	dirent_glue (fullname, FK_(j));
	if (! (retval = dirent_chk (FK_(j), fullname)))
	{
		if (DELETED(j))	dirent_dlet(j);
	}
	dds_line (j);
	return (retval);
}

/* <dirent_chk3>:
 * Check a given filename.  If it exists, update the 'filelist[]' entry.  If
 * not, mark the given 'filelist[]' entry deleted.  This is used in DELETE
 * to uncover lower versions of a file which has been deleted.  Because the
 * name may not necessarily be the same as that in the 'filelist[j]' entry,
 * we must re-link the data-object to load altered data.
 */
int	dirent_chk3 (int j, char *fullname)
{
	FILENT	znew;		/* Don't destroy the old FILENT block	*/
	int	retval;

	if (retval = dirent_chk (&znew, fullname))
		dds_add2 (&znew, j);
	else
	{
		if (zDELETED((&znew)))
		{
			dirent_dlet(j);
			dds_line (j);
		}
		else
			dds_width (&znew, j);
	}
	return (retval);
}

/* <dirent_chop>:
 * Parse path, name, type and version for a full filename, loading it to
 * the indicated FILENT entry.  The filespec is assumed to be syntactically
 * correct, since it is obtained via RMS.
 *
 * We include the trailing '.' in the filename-string to force our sort to
 * collate in the same manner as the SYS$SEARCH.
 */
int	dirent_chop (FILENT *z, char *filespec, struct NAM *nam_)
{
	struct	FAB	fab;
	struct	NAM	nam;
	int	len, num,
		status	= RMS$_NORMAL;
	char	esa	[NAM$C_MAXRSS];

	z->fvers = 0;

	if (!nam_)
	{
		/* Set up RMS buffers to do name-parse: */
		rmsinit_fab (&fab, &nam, 0, filespec);
		rmsinit_nam (&nam, 0, esa);

		if (! $VMS_STATUS_SUCCESS(status = sys$parse(&fab)))
			return (status);
		/* esa[] <= parsed name	*/
		nam_ = &nam;
	}

#define	HEAP(n,l)	nameheap(nam_->n, nam_->l, (void *)&namelist)

	z->fvers = scanver (nam_->nam$l_ver, nam_->nam$b_ver);
	z->ftype = HEAP(nam$l_type+1,nam$b_type-1);
	z->fname = HEAP(nam$l_name,nam$b_name+NAME_DOT); /* name, with '.' */

	z->fpath_ = dirpath_sort (nam_->nam$l_node,
				  len = nam_->nam$l_name - nam_->nam$l_node);
	return (status);
}

/* <dirent_conv>:
 * Convert a FILENT entry to printing form, showing only those fields selected
 * in 'conv_list[]'.
 */
void	dirent_conv (char *bfr, FILENT *z)
{
	DATENT	*date_;
	int	j, num,
		col0	= PCOLUMNS(0),
		col1	= PCOLUMNS(1),
		width	= crt_width(),
		no_priv	= zNOPRIV(z);
	static
	char	sFMT1[]	= "%%-%d.%ds %%-%d.%ds;%%.%ds",
		noDATE[] = "%-17s";

	char	tmp[CRT_COLS+256],
		tempid[80],
		*c_	= bfr,
		*ccol_	= conv_list,
		format	[sizeof(sFMT1)+20],
		filesize[20],
		version	[20],
		misc	[30];

#define	NXT	strnull(strcat(c_, "  "))
#define	AFTER_NXT (width - strlen(NXT) - 2)

	if (zDELETED(z))
	{
		*c_ = EOS;
		return;
	}

	if (z->fvers == WILD_VER)	strcpy (version, "*");
	else if (z->fvers)		sprintf (version, "%d", z->fvers);
	else				strcpy (version, "");

	sprintf  (format, sFMT1,
		col0,col0,		/* 'fname'	*/
		col1,col1,		/* 'ftype'	*/
		ccolumns[2]+1);		/* 'fvers', pad	*/
	sprintf  (bfr, format, z->fname, z->ftype, version);
	j = strlen(z->fname) - NAME_DOT;
	if (j > col0)			bfr[col0]	 = '|';
	else if (j >= 0)		bfr[j]		 = ' ';
	if (strlen(z->ftype) > col1)	bfr[col0+col1+1] = '|';

	sprintf	(filesize, "%u", z->fsize);

	/*
	 * If I have any data-columns to show, pad the name out to the
	 * beginning of the "command-column" first.
	 */
	if (AnyXAB)
	{
		num	= strlen(bfr);
		c_	= bfr + num;		/* => ending-null */
		while (num++ < dirent_ccol())
			*c_++ = ' ';
		*c_ = EOS;
	}

	/*
	 * Check for early termination of this conversion:
	 */
	if (zLOCKED(z))		/* file is locked, cannot open	*/
		strcpy (filesize, "*lock");
	else if (z->fstat != RMS$_NORMAL && !no_priv)
	{
		if (z->fstat)		/* If zero, no data	*/
			sysgetmsg (z->fstat, NXT, AFTER_NXT);
		return;
	}
	strcpy(tmp, bfr);		/* do the rest in a big-buffer	*/
	c_ = tmp + strlen(tmp);		/* ...to guard against overflow	*/

	/*
	 * Format each display-column in succession, under control of the
	 * 'conv_list'.
	 */
	prot_col = -1;			/* If visible, mask-col is reset */
	while (*ccol_ && ((c_-tmp) < width))
	{
		c_ = NXT;
		switch (*ccol_++)
		{
		case 'p':		/* PATH (node::directory) */
			sprintf (format, "%%-%ds", ccolumns[3]);
			sprintf (c_, format, zPATHOF(z));
			break;

		case 'a':		/* ALLOC (allocation)	*/
			if (no_priv)
				sprintf (c_, "%-*s", ccolumns[8], " ");
			else
				sprintf (c_, "%*.*u", ccolumns[8], ccolumns[8], z->fallc);
			break;

		case 's':		/* SIZE (usage)		*/
			if (no_priv)
				sprintf (c_, "%-*s", ccolumns[7], " ");
			else
				sprintf (c_, "%*.*s", ccolumns[7], ccolumns[7], filesize);
			break;

		case 'l':		/* LENGTH (of record)	*/
			dirent__cnv2 (no_priv, c_, z->f_recl);
			break;

		case 'f':		/* FORMAT (record)	*/
			*c_++ = (z->ftext ? '*' : ' ');
			j = z->f_rfm & FAB$M_RFM;
			if (j < SIZEOF(known_rfm))
				strcpy (misc, known_rfm[j]);
			else
				strcpy (misc, "?");
			if ((j = (z->f_rfm >> FAB$V_ORG)) > 0)
			{
				strcat (misc, ",");
				if (j < SIZEOF(known_org))
					strcat (misc, known_org[j]);
				else
					strcat (misc, "?");
			}
			sprintf (format, "%%-%d.%ds", ccolumns[4],ccolumns[4]);
			sprintf (c_, format, misc);
			break;

		case 'x':		/* XAB (record attributes)	*/
			{
				int	bit;
				char	*d_ = misc;
				*d_ = EOS;
				for (bit = 0; bit < SIZEOF(known_rat); bit++)
					if (z->f_rat & (1 << bit))
					{
						if (d_ != misc)	*d_++ = ',';
						strcpy (d_, known_rat[bit]);
						d_ = strnull(d_);
					}
				*d_ = EOS;
			}
			sprintf (format, "%%-%d.%ds", ccolumns[5],ccolumns[5]);
			sprintf (c_, format, misc);
			break;

		case 'e':	date_	= &z->fexpr;	goto date;
		case 'r':	date_	= &z->frevi;	goto date;
		case 'b':	date_	= &z->fback;	goto date;
		case 'c':	date_	= &z->fdate;	goto date;

		case 'd':		/* DATE "dd-mmm-yy hh:mm" */
			date_ = dirent__date (z, D_opt);

		date:
			if ((!no_priv)
			&&  isOkDate(date_)
			&&  !isBigDate(date_))
			{
				if (D_mode != 0)
					lib$day (&j, date_);
				if (D_mode > 0)
				{
					strncpy (c_, day_of_week[j % 7], 4);
					c_ += 4;
				}
				sysasctim (c_, date_, D_mode <= 1 ? 18 : 21);
				if (D_mode < 0)	/* "dd-mmm hh:mm" */
				{		/* "dd-mmm-yyyy " */
					if (j+180 > absolute_day)
						strcpy (&c_[6], &c_[11]);
				}
			}
			else
			{
				if (D_mode > 0)
				{
					strcpy (c_, "    ");
					if (D_mode > 1)
						strcat(c_, "   ");
					c_ = strnull(c_);
				}
				sprintf (c_, noDATE, " ");
			}
			if (D_mode < 0)	c_[12] = EOS;
			break;

		case 'm':		/* MASK (protection, len=20) */
			prot_col= 1 + (c_ - tmp);
			num	= z->fprot;
			for (j = 0; j < 4; j++)
			{
				*c_++ = (num & XAB$M_NOREAD)  ? '-' : 'r';
				*c_++ = (num & XAB$M_NOWRITE) ? '-' : 'w';
				*c_++ = (num & XAB$M_NOEXE)   ? '-' : 'e';
				*c_++ = (num & XAB$M_NODEL)   ? '-' : 'd';
				*c_++ = ' ';
				num >>= 4;
			}
			*(--c_) = EOS;
			break;

		case 'o':		/* OWNER (len=9)	*/
			if (no_priv)
				sprintf (c_, "%-9s", " ");
			else
				sprintf (c_, "%4o,%4o",
					z->f_grp, z->f_mbm);
			break;

		case 'u':		/* USER (len=18)	*/
			sprintf (c_, "%-*s", ccolumns[6], z->f_user ? z->f_user : "");
			break;

		/* Patch: Limit this to 13 columns until either we extend the
		   'ccolumns' array, or find a file-id which has a nonzero
		   [2]-entry. */
		case 'i':		/* IDENTIFIER (file-id)	*/
			if (no_priv)
				strcpy (misc, " ");
			else
				sprintf (misc, "%d,%d,%d", z->fidnum[0],
					z->fidnum[1], z->fidnum[2]);
			sprintf (c_, "%-13s", misc);
		}
	}
	strncpy(bfr, tmp, CRT_COLS-1);
	bfr[CRT_COLS-1] = '\0';
}

/* <dirent_dft>:
 * Return a pointer to wildcard, based on V_opt (VERSION).  If NOVERSION
 * is selected, all wildcard-SYS$SEARCH activity will be coerced to seek
 * only the current-version of files.  Otherwise, any version may be selected.
 */
char	*dirent_dft (void)
{
	return (V_opt ? "*.*;*" : "*.*;");
}

/* <dirent_dlet>:
 * Mark an entry in 'filelist[]' deleted.
 */
void	dirent_dlet (int j)
{
	FK(j).fstat = RMS$_FNF;
	numdlets++;
	if (j == dircmd_select(-2))	dircmd_select(-1);
	dds_line (j);
}

/* <dirent_glue>:
 * Concatenate all substrings to make a complete file specification, for
 * the indicated FILENT entry.
 */
char	*dirent_glue (char *filespec, FILENT *z)
{
	return (dirent_glue2 (filespec, z, -1));
}

/* <dirent_glue2>:
 * Concatenate all substrings to make a complete file specification, for
 * the indicated FILENT entry.  The 'curfile' argument is provided so that
 * warning messages and other similar functions can make the inclusion of
 * the pathname dependent on whether it is the same as that of the current
 * entry on the screen.  (The path of the current entry is also the default
 * directory.)
 */
char	*dirent_glue2 (char *filespec, FILENT *z, int curfile)
{
	static	char	fmt[] =
#if	NAME_DOT
			"%s%s;";
#else
			"%s.%s;";
#endif
	if (curfile >= 0)
	{
		strcpy (filespec, (zPATHOF(z) != PATHOF(curfile))
			? zPATHOF(z) : "");
	}
	else
		strcpy (filespec, zPATHOF(z));
	if (strcmp(filespec, "*") == 0)	strcat (filespec, ":");

	sprintf (strnull(filespec), fmt, z->fname, z->ftype);
	if (z->fvers == WILD_VER)
		strcat (filespec, "*");
	else if (z->fvers)
		sprintf (strnull(filespec), "%d", z->fvers);
	return (filespec);
}

/* <dirent_init>:
 * Initialize data structures used in this module (used only via main routine).
 */
static
void	dirent_init(void)
{
	/*
	 * Chain pointers for XAB (the "nxt" is the same offset in all variations)
	 */
	struct	XABALL	*first_ = 0, *last_ = 0;
	int	today_date[2];

	/* dirent_all state: */

	rmsinit_fab (&zfab, &znam, dirent_dft(), 0);
	rmsinit_nam (&znam, zrsa, zesa); /* Initialize name-block	*/

	/* dirent__one state: */

	xaball	= cc$rms_xaball;
	xabdat	= cc$rms_xabdat;
	xabfhc	= cc$rms_xabfhc;
	xabpro	= cc$rms_xabpro;

	/*
	 * Initialize extended-attribute-blocks.
	 *	For each file found, will retrieve the size "-a", the last
	 *	modification date "-d", and the protection codes "-m".
	 */
	if (A_opt)
	{
		if (first_ == 0) first_           = (struct XABALL *)&xaball;
		if (last_  != 0) last_->xab$l_nxt = (char *)&xaball;
		xaball.xab$l_nxt = (char *)&xabfhc;
		last_	= (struct XABALL *)&xabfhc;
	}
	if (D_opt)
	{
		if (first_ == 0) first_           = (struct XABALL *)&xabdat;
		if (last_  != 0) last_->xab$l_nxt = (char *)&xabdat;
		last_	= (struct XABALL *)&xabdat;
	}
	if (M_opt || O_opt)
	{
		if (first_ == 0) first_           = (struct XABALL *)&xabpro;
		if (last_  != 0) last_->xab$l_nxt = (char *)&xabpro;
		last_	= (struct XABALL *)&xabpro;
	}
	if (first_)
	{
		AnyXAB = TRUE;
		zfab.fab$l_xab = (char *)first_; /* ...and => XAB chain	*/
	}

	pcolumns[0] = pcolumns[1] = 0;	/* Show name,type as-is	*/
	prot_col = -1;		/* assume no mask-column until displayed */
	sys$gettim (today_date);
	lib$day (&absolute_day, today_date);
}

/* <dirent_isdir>:
 * Test a FILENT block to see if it is a directory.  While ACP can do additional
 * tests, we will assume that a ".DIR" filetype is enough indication.  Note
 * that VMS insists that the version number of a directory must be "1".
 */
int	dirent_isdir (FILENT *z)
{
	return ((strcmp ("DIR", z->ftype) == 0) && (z->fvers <= 1));
}

/* <dirent_look>:
 * Lookup a single file, returning (via pointer) its fully-resolved name,
 * and the lookup status.
 */
unsigned dirent_look (char *longspec, char *filespec)
{
	size_t	len;
	unsigned status;

	zfab.fab$l_fna = filespec;
	zfab.fab$b_fns = strlen(filespec);

	if ($VMS_STATUS_SUCCESS(status = sys$parse(&zfab)))
	{
		if ($VMS_STATUS_SUCCESS(status = sys$search(&zfab)))
		{
			strncpy (longspec, zrsa, len = znam.nam$b_rsl);
			longspec[len] = EOS;
		}
	}
	return (status);
}

/* <dirent_misc>:
 * Set/clear bits in the '.fmisc' component.  This is used to keep track of
 * wildcard searches (limited to entries in 'filelist[]').  By flagging
 * entries, we need examine each once, making it faster, and (in the case of
 * DELETE) more precise.
 *
 * Patch: This doesn't clear-specific, or do testing.
 */
void	dirent_misc (
	int	inx,		/* index into 'filelist[]', or negative	*/
	int	bitv)		/* value with 1's where setting is needed*/
{
	int	j;

	if (inx >= 0)	/* If legal index, set specific bit	*/
	{
		FK(inx).fmisc |= bitv;
	}
	else			/* Do a global change		*/
	{
		if (inx == -1)	/* Clear this bit everywhere ?	*/
		{
			for (j = 0; j < numfiles; j++)
				FK(j).fmisc &= (~bitv);
		}
		else		/* Set this bit everywhere	*/
		{
			for (j = 0; j < numfiles; j++)
				FK(j).fmisc |= bitv;
		}
	}
}

/* <dirent_nul>:
 * Create a dummy entry in 'filelink' so that we can mark an entry in 'filelist'
 * deleted without clobbering our database entry for the file which used to be
 * there.  For instance, we make this when we do a VERIFY and purge lots of
 * renamed entries from 'filelist[]'.
 */
void	dirent_nul (int inx)
{
	FILENT	ztmp = FK(inx);		/* make sure path-name is legal	*/
	ztmp.fname = ztmp.ftype = "";	/* dummy-out the name, type	*/
	ztmp.fvers = 0;			/* ...and version		*/
	ztmp.fstat = RMS$_FNF;		/* Show a DELETED() code	*/
	dirdata_one (&ztmp, &filelist[inx]);
	numdlets++;
	dds_line(inx);
}

/* <dirent_old>:
 * Given a filespec, convert it to a FILENT structure, and attempt to find
 * it in the current 'filelist[]' array.  If found, return its index.  Else,
 * return a "-1" or "-2".
 */
int	dirent_old (
	char	*filespec,
	int	need)	/* 0: no warn, 1: any file, 2: in filelist[]	*/
{
	FILENT	ztmp;

	return (dirent_old_any (&ztmp, filespec, need));
}

/* <dirent_old1>:
 * Lookup filename, returning TRUE if there is any file available by that name.
 * If not, flag a warning message.  If the lookup is successful, will return
 * the FILENT entry in the output argument.
 */
int	dirent_old1 (FILENT *z, char *spec)
{
	int	inx	= dirent_old_any (z, spec, 1);

	return (! didwarn());
}

/* <dirent_old_any>:
 * Given a filespec, convert it to a FILENT structure, and attempt to find
 * it in the current 'filelist[]' array.  If found, return its index.  Else,
 * return a "-1" or "-2".  If the file exists, the FILENT structure '*z' is
 * loaded with the data.
 */
int	dirent_old_any (
	FILENT	*z,		/* => buffer to load with file data	*/
	char	*filespec,	/* => file specification to lookup	*/
	int	need)		/* 0: no warn, 1: any file, 2: in filelist[] */
{
	char	longspec[NAM$C_MAXRSS];

	if (dirent_look (longspec, filespec) == RMS$_FNF)
		dirent_chop (z, filespec, (struct NAM *)0);
	else if (dirent__one (z, longspec))
	{
		int	j;
		for (j = 0; j < numfiles; j++)
		{
			if (DELETED(j))				continue;
			if (memcmp (z, FK_(j), FILENT_name_size) == 0)
				return (j);
		}
		if (need >= 2)
			warn ("File '%s' is not a member of file-list", filespec);
		return (-1);
	}

	if (need >= 1)
	{
		if (zDELETED(z))
			warn ("File not found: %s", filespec);
		else
			warn ("File isn't readable: %s", filespec);
	}
	return (-2);
}

static
int	digits_of (unsigned value)
{
	static	unsigned tens[20] = { 1 };
	int	count;

	for (count = 1; count < sizeof(tens)/sizeof(tens[0]); count++)
	{
		if (tens[count] == 0)
			tens[count] = 10 * tens[count-1];
		if (value < tens[count]
		 || tens[count] <= tens[count-1])
			break;
	}
	return count;
}

/* <dirent_width>:
 * Latch the widest field of each type which is actually used in the display
 * list.  We call this from points at which we are actually loading a FILENT
 * block to the display list; other points may read a FILENT block which will
 * be included in the 'dirdata' list, but not displayed.
 */
int	dirent_width (FILENT *z)
{
	register int	len, num, latch;

	if (z)
	{
		LATCH(0,len = strlen(z->fname)-1);
		LATCH(1,len = strlen(z->ftype));
		LATCH(2,len = digits_of(z->fvers));
		LATCH(3,len = strlen(zPATHOF(z)));

		/*
		 * Latch the widest FORMAT description, so that we can do this
		 * in minimum width:
		 */
		num = z->f_rfm & FAB$M_RFM;
		if (num >= SIZEOF(known_rfm))	len = 1;
		else				len = strlen(known_rfm[num]);
		if (z->f_rfm > FAB$M_RFM)	len += 4;
		LATCH(4,len);

		len = 0;
		for (num = 1; num < (1 << SIZEOF(known_rat)); num <<= 1)
			if (num & z->f_rat)
			{
				if (len)	len++;
				len += 3;
			}
		LATCH(5,len);

		LATCH(6,len = strlen(z->f_user));
		LATCH(7,len = digits_of(z->fsize));
		LATCH(8,len = digits_of(z->fallc));
	}
	else		/* Reset entire list	*/
	{
		ccolumns[0] = 9;	/* filename	*/
		ccolumns[1] = 3;	/* filetype	*/
		ccolumns[2] = 4;	/* version	*/
		ccolumns[3] = 1;	/* pathname	*/
		ccolumns[4] = 3;	/* format + (organization)	*/
		ccolumns[5] = 1;	/* attributes	*/
		ccolumns[6] = 8;	/* user-identifier */
		ccolumns[7] = 5;	/* filesize (blocks) */
		ccolumns[8] = 5;	/* allocation (blocks) */
		for (num = 0; num < numfiles; num++)
			if (!DELETED(num))
				latch |= dirent_width (FK_(num));
	}
	return (latch);
}

/* <dirent__cnv2>:
 * Convert (for 'dirent_conv') 16-bit numbers.
 */
static
void	dirent__cnv2 (int no_priv, char *c_, unsigned number)
{
	if (no_priv)
		sprintf (c_, "%-5s", " ");
	else
		sprintf (c_, "%5u", number);
}

/* <dirent__date>:
 * Given a date-selection option (/DC, /DB, /DR) return the appropriate
 * pointer to a date-field.  Note that this is used to control the
 * interpretation of the /AFTER and /BEFORE options.
 */
static
DATENT	*dirent__date (FILENT *z, int opt)
{
	register DATENT	*date_;

	if (opt == 4)		date_	= &z->fexpr;
	else if (opt == 3)	date_	= &z->frevi;
	else if (opt == 2)	date_	= &z->fback;
	else			date_	= &z->fdate;
	return (date_);
}

/* <dirent__datechek>:
 * Test a FILENT structure to see if it is permitted by the current date-
 * selection option (/AFTER or /BEFORE).
 */
int	dirent__datechek (FILENT *z)
{
	register int ok = TRUE;

	if (dateflag[0])
	{
	register
	DATENT	*date_	= dirent__date (z, dateflag[1]);
#ifdef __alpha
	int	gt	= *date_ > datechek;
#else
	register
	int	gt	=   (date_->date64[1]  > datechek.date64[1])
			|| ((date_->date64[0]  > datechek.date64[0]) &&
			    (date_->date64[1] == datechek.date64[1]));
#endif
		ok = (dateflag[0] > 0) ? gt : ! gt;
	}
	return (ok);
}

/* <dirent__look>:
 * Lookup information about a particular file, to fill all fields in the
 * indicated FILENT structure, except those set in 'dirent_chop'.  Returns
 * TRUE iff the file was found.
 *
 * To make the RMS-lookup go faster, we assume that the global 'znam' block
 * is set from the SYS$SEARCH for this file.
 */
static
int	dirent__look (
	FILENT	*z,			/* => Data block to load	*/
	char	*filespec)		/* Full, unique filename	*/
{
	unsigned status	= RMS$_NORMAL;		/* return-status	*/
	int	ok	= TRUE;
	char	temp[80];

	/*
	 * Fill the FILENT block with the name, and null-data:
	 */
	memset (((char *)z) + FILENT_name_size, 0,
		sizeof(FILENT) - FILENT_name_size);
	z->f_grp = z->f_mbm = z->fprot = -1;

	/*
	 * ACP is generally faster than RMS (less overhead), but cannot
	 * (under VMS 3.7) go across DECNET.
	 */
	if (znam.nam$l_fnb & NAM$M_NODE)
		status = rmslook (z, &zfab);
	else
		status = acplook (z, filespec, &znam);

	if (!P_opt && zNOPRIV(z))	ok = FALSE;

	vms_uid2s(temp, z->f_mbm, z->f_grp);
	if (!strcmp(temp, "[]"))
		temp[0] = '\0';
	/* FIXME: memory leak */
	z->f_user = strcpy(malloc(strlen(temp)+1), temp);

	return (ok);
}

/* <dirent__one>:
 * Lookup information about a particular file, to fill all fields in the
 * indicated FILENT structure.  Returns TRUE iff the file was found.
 * In all cases, the filename is parsed.
 */
static
int	dirent__one (
	FILENT	*z,			/* => Data block to load	*/
	char	*filespec)		/* Full, unique filename	*/
{
	dirent_chop (z, filespec, &znam);
	return (dirent__look(z, filespec));
}

/* <dirent__read>:
 * Lookup information about a particular file, to fill all fields in the
 * indicated FILENT structure.  Returns TRUE iff:
 *	(a) the file was found,
 *	(b) the file did not appear elsewhere in the list, and
 *	(c) the file does not conflict with /NOVERSION
 * If the name (counting version) is an exact duplicate, load the newer file
 * data, to support re-reads.
 */
static
int	dirent__read (
	char	*filespec,
	FLINK	**flink,
	int	update)		/* true if we *must* re-read directory	*/
{
	FILENT	filent;
	int	j,
		ok	= TRUE;

	if (update)
	{
		if (! dirent__one (&filent, filespec))	return (FALSE);
	}
	else		/* Look in database before the physical directory */
	{
		dirent_chop (&filent, filespec, &znam);
		if ((j = dirdata_find (&filent, flink)) <= 0)
		{
			if (! dirent__look (&filent, filespec))
				return (FALSE);
		}
		else
			filent	= (*flink)->fk;
	}

	if (! V_opt)	/* Suppress if a higher version is in 'filelist[]' */
	{
		if (update) j = dirdata_find (&filent, flink);
		/* Position the link-pointer if we have not already */
		if (dirdata_high (&filent, flink))
			ok = FALSE;
		else
		{
			dirdata_chg (&filent, flink, j);
			dirent_width (&filent);
		}
	}
	else		/* Create/replace (if duplicate) database entry */
	{
		dirdata_add (&filent, flink);	/* Get data's list-pointer */
		dirent_width (&filent);
	}

	if (ok)
	{
		for (j = 0; j < numfiles; j++)
		{
			if (filelist[j] == *flink)
			{
				ok = FALSE;
				break;
			}
		}
	}
	return (ok);
}
