/******************************************************************************
*******************************************************************************

   Installation:  Western Michigan University Academic Computer Center

   System:	Directory/File System Maintenance
  
   Program:	maint

   Version=01	Level=01	08/24/87	Leonard J. Peirce

   Purpose:	This file contains routines used in maint but could be
		conveniently placed in another source file.  These routines
		were moved here because the first source file was getting
		too big to deal with.

   Version=01	Level=02	12/17/87	Leonard J. Peirce

   Purpose:	Add a few new routines for some added functionality and fix
		a few bugs.  See maint.c for more information about this
		version.

   Version=01	Level=03	08/04/88 	Leonard J. Peirce

   Purpose:	See maint.c for more information about this version.

   Version=01	Level=04	10/06/88 	Leonard J. Peirce

   Purpose:	Actually, these fixes were made for Version 1.5.

   Arguments:	See individual functions.

   External variables:  none

   WMU external functions:

          Defined:

          Called:	strindex,strins,cat,banystr

   Files accessed:	None.

   Return codes:	See individual functions.

   Compiling instructions:  cc maint2.c/optimize

   Linking instructions:  see linking instructions in maint.c

********************************************************************************
*******************************************************************************/

/******************************************************************************/
/*                                                                            */
/*                        # I N C L U D E   F I L E S                         */
/*                                                                            */
/******************************************************************************/

#include "maint.h"			/* our very own header file	      */

#ifdef WMUACC				/* we have these in a .TLB	      */

#include utype				/* unsigned data type header	      */
#include clidef				/* for CLI$ stuff		      */
#include itmlst				/* for item list definition	      */
#include acldef				/* some stuff for the ACL editor      */
#include fscndef			/* for SYS$FILESCAN stuff	      */

#else					/* look in current directory...	      */
					/* ...for everyone else		      */
#include "utype.h"
#include "clidef.h"
#include "itmlst.h"
#include "acldef.h"				
#include "fscndef.h"
#endif

#include stdio				/* standard I/O; for NULL definition  */
#include rms				/* RMS header; includes all others    */
#include descrip			/* for descriptors		      */
#include lnmdef				/* logical name symbolic values	      */
#include ssdef				/* SYS$ return code symbolics	      */
#include climsgdef			/* for more CLI$ stuff		      */
#include smgdef				/* for SMG$ symbolic definitions      */
#include ctype				/* character-type header	      */
#include string				/* C string functions		      */

/******************************************************************************/
/*                                                                            */
/*                             # D E F I N E S                                */
/*                                                                            */
/******************************************************************************/

#define PROMPT_MAX 15			/* max. length of prompt	      */
#define DEFAULT_FILESPEC "NONAME.TXT"

/******************************************************************************/
/*                                                                            */
/*          S T R U C T U R E S ,   U N I O N S ,   T Y P E D E F S           */
/*                                                                            */
/******************************************************************************/

/******************************************************************************/
/*                                                                            */
/*   E X T E R N A L   D E F I N I T I O N S   &   D E C L A R A T I O N S    */
/*                                                                            */
/******************************************************************************/

globalvalue	  LIB$K_CLI_GLOBAL_SYM,	/* for LIB$GET_SYMBOL()'s sake	      */
		  ACLEDIT$C_OBJNAM,	/* stuff for the ACL editor	      */
		  ACLEDIT$C_OBJTYP,
		  ACLEDIT$C_OPTIONS,
		  ACLEDIT$V_JOURNAL,
		  ACLEDIT$V_RECOVER,
		  ACLEDIT$V_KEEP_RECOVER,
		  ACLEDIT$V_KEEP_JOURNAL,
		  ACLEDIT$V_PROMPT_MODE,
		  ACLEDIT$C_BIT_TABLE;

extern	 char	  *strncat(),		/* finite string concatenate	      */
		  *strchr(),		/* find char in string		      */
		  *strcpy(),		/* string copy			      */
		  *strcat();		/* string concatenate		      */

extern	 ULONG	  CLI$GET_VALUE(),
		  SYS$IDTOASC(),
		  LIB$FIND_FILE(),
		  LIB$FIND_FILE_END(),
		  LIB$FREE_VM(),
		  LIB$GET_VM(),
		  LIB$GET_SYMBOL(),
		  LIB$SPAWN(),
		  SMG$ERASE_DISPLAY(),
		  SMG$PASTE_VIRTUAL_DISPLAY(),
		  SMG$PUT_CHARS(),
		  SMG$READ_KEYSTROKE(),
		  SMG$READ_STRING(),
		  SMG$UNPASTE_VIRTUAL_DISPLAY(),
		  SYS$CLOSE(),
		  SYS$CONNECT(),
		  SYS$CREATE(),
		  SYS$CRELNM(),
		  SYS$ERASE(),
		  SYS$FILESCAN(),
		  SYS$OPEN(),
                  SYS$PARSE(),
		  SYS$RENAME(),
		  SYS$SETDDIR();

extern	 int	  strins(),		/* insert a string in another	      */
		  strindex(),		/* search for substring of a string   */
		  banystr(),		/* search for a character in a string */
		  sleep(),		/* C version of LIB$WAIT()	      */
		  delete();		/* delete file			      */

extern	 void	  LIB$STOP();

	 POOL_DEF *new_pool();		/* initialize a new memory pool	      */

	 char	  *mystrcpy(),		/* string copy returning pointer      */
		  *get_pool_mem(),	/* get pointer to a memory slot	      */
		  *nullptr = "";	/* pointer to a null string	      */

	 ULONG	  push(),		/* spawn a subprocess		      */
		  get_string(),		/* prompt and read string from tty    */
		  edit_acl();		/* edit a file's ACL		      */

	 short	  get_num_file(),	/* get number of files in a directory */
		  mark_delete(), 	/* set file to be deleted	      */
		  mark_copy_ren(),	/* set file to be copied/renamed      */
		  mark_protect(),	/* set file to be reprotected	      */
		  mark_text(),		/* set file's text descriptor	      */
		  make_slot(),		/* create a screen slot for a file    */
		  create_text(),	/* attempt to create text desc. file  */
		  expand(),		/* get attributes of a file	      */
		  get_scr_num(),	/* let user specify new screen number */
		  put_line(),		/* write a line to a virtual display  */
		  prot_val_to_str();	/* get string value for protection    */

	 void	  get_device(),		/* get terminal name for debugging    */
		  info_mess(),		/* write informational message	      */
		  curr_dir(),		/* get current directory spec	      */
		  init_pool(),		/* initialize memory pool nodes	      */
		  reset_pool(),		/* setup pools to reuse memory	      */
		  put_pool(),		/* copy a string to the memory pools  */
		  free_pool(),		/* free memory pools for a directory  */
		  clear_mess(),		/* clear a message from the screen    */
		  set_width(),		/* set scr_width variable correctly   */
		  mark_cancel(),	/* cancel commands for a file entry   */
 		  set_direct(),		/* change to command line directory   */
		  set_args(),		/* set run-time args and other stuff  */
		  tag_file(),		/* "tag" current file on screen	      */
		  erase_tag(),		/* erase file tag		      */
		  set_sysdisk(),	/* set SYS$DISK logical		      */
		  put_text(),		/* put directory text descrip on scr. */
		  xecute(),		/* execute file commands	      */
		  free_com(),		/* free command structures	      */
		  exp_prot_str(),	/* make VMS-style protection string   */
		  xmess();		/* display messages while executing   */

/******************************************************************************/
/*                                                                            */
/*     S T A T I C   D E F I N I T I O N S   &   D E C L A R A T I O N S      */
/*                                                                            */
/******************************************************************************/

static	 COM_DEF  *new_comm();		/* allocate command structure	      */

static	 ULONG	  write_text();		/* write text descriptor record	      */

static	 short	  put_buf(),		/* write a buffer to a virtual disp   */
		  put_line(),		/* write a line to a virtual display  */
	 	  prot_str_to_val(),	/* compute protection integer value   */
		  put_acl(),		/* display ACL's on eXpand	      */
		  banynstr(),		/* search finite-length string	      */
		  make_name();		/* create filename for file operation */

static	 void	  free_comstr(),	/* free up a specific command struct  */
		  parse_arg(),		/* parse the command line file spec   */
		  copy_spec(),		/* copy directory, device, file specs */
		  make_text_rec();	/* make text descriptor record	      */
/*eject*/
/*******************************************************************************
********************************************************************************

  Function:	get_device

  Purpose:	This routine is used only for debugging purposes.  It obtains
		the name of the device to be used when debugging from the
		global symbol MAINT_DEV and uses it when creating the paste-
		board for the session.

  Global variables:
                      
	Name			Examine/Modify/Use/Read/Write
	----			-----------------------------
	none

  Return Codes:

	Code			Reason
	----			------
	none

********************************************************************************
*******************************************************************************/

void get_device(outdev_d,indev_d)
					/*******   FORMAL  PARAMETERS   *******/
struct	 dsc$descriptor_s  *outdev_d,	/* destination for device name	      */
			   *indev_d;	/* for creating virtual keyboard      */

{	/*** get_device ***/
					/********   LOCAL  VARIABLES   ********/
	 ULONG	  status;		/* return code status		      */
	 short	  length;		/* length of symbol value returned    */
static $DESCRIPTOR(sym_d,"MAINT_DEV");	/* symbol to look for		      */

                                 
#ifdef DEBUG

   status = LIB$GET_SYMBOL(&sym_d,outdev_d,&length,&LIB$K_CLI_GLOBAL_SYM);

   if(status != SS$_NORMAL)
      LIB$STOP(status);

   /* terminate the symbol value in the return descriptor */

   outdev_d->dsc$a_pointer[length] = '\0';
   indev_d->dsc$w_length = length;
   strcpy(indev_d->dsc$a_pointer,outdev_d->dsc$a_pointer);

#else

   /* just set up the normal stuff for using the correct terminal */

   strcpy(indev_d->dsc$a_pointer,"SYS$INPUT");
   strcpy(outdev_d->dsc$a_pointer,"SYS$OUTPUT");
   indev_d->dsc$w_length = strlen(indev_d->dsc$a_pointer);
   outdev_d->dsc$w_length = strlen(outdev_d->dsc$a_pointer);

#endif

   return;

}	/*** get_device ***/
/*eject*/
/*******************************************************************************
********************************************************************************

  Function:	curr_dir

  Purpose:	Obtain the current default directory specification.

  Global variables:

	Name			Examine/Modify/Use/Read/Write
	----   			-----------------------------
	none

  Return Codes:

	Code			Reason
	----			------
	none

********************************************************************************
*******************************************************************************/

void curr_dir(dir_name)
					/*******   FORMAL  PARAMETERS   *******/
	 char	  *dir_name;		/* where to write directory spec      */

{	/*** curr_dir ***/
					/********   LOCAL  VARIABLES   ********/
	 ULONG	  status;		/* return code status		      */
	 char	  *tptr;		/* temporary character pointer	      */
static	 short	  pass;			/* have we been here before?	      */
static	 struct	  FAB	  fab;		/* file access block		      */
static	 struct	  NAM	  nam;		/* name access block		      */


   if(!pass)				/* have we already initialized stuff? */
   {
      fab = cc$rms_fab;			/* no, initialize the RMS blocks      */
      nam = cc$rms_nam;

      fab.fab$l_dna = "[]";		/* set up for getting default dir     */
      fab.fab$b_dns = 2;
      fab.fab$l_fna = "[]";
      fab.fab$b_fns = 2;
      fab.fab$l_fop = FAB$M_OFP | FAB$M_NAM;
      fab.fab$l_nam = &nam;		/* set up NAM block address	      */

      nam.nam$b_nop = NAM$M_PWD | NAM$M_SYNCHK;
      nam.nam$b_ess = NAM$C_MAXRSS;
      pass = 1;				/* make sure we do this only once.... */
   }

   nam.nam$l_esa = dir_name;		/* where do we want it to go?	      */

   status = SYS$PARSE(&fab,0,0);	/* parse things....		      */

   if(status != RMS$_NORMAL)
      LIB$STOP(status);			/* #$*!@+?&^			      */

   tptr = strchr(dir_name,']');		/* find where directory spec ends     */
   *(tptr + 1) = '\0';			/* terminate it			      */

   return;				/* go home			      */

}	/*** curr_dir ***/
/*eject*/
/*******************************************************************************
********************************************************************************

  Function:	get_num_file

  Purpose:	Get the number of files in the current directory.  The formal
		parameter file_spec contains the string that is used to count
		the number of files in the current directory that will be
		included.

  Global variables:

	Name  			Examine/Modify/Use/Read/Write
	----			-----------------------------
	none

  Return Codes:

	Code			Reason
	----			------
	count			number of files in directory

  Termination Codes:

	Code			Reason
	----			------
	status			return code from LIB$FIND_FILE()

********************************************************************************
*******************************************************************************/

short get_num_file(file_spec)
					/*******   FORMAL  PARAMETERS   *******/
	 char	  *file_spec;		/* file spec for current directory    */

{	/*** get_num_file ***/
					/********   LOCAL  VARIABLES   ********/
register ULONG	  status;		/* return code status		      */
register short	  count;		/* number of files in directory	      */
static	 ULONG	  fcontext;		/* file context for getting files     */
static	 char	  buf2[BUFSIZ];		/* resulting filename		      */
static $DESCRIPTOR(file_d,"");		/* descriptor for wildcard string     */
static $DESCRIPTOR(file2_d,buf2);	/* resultant file-spec descriptor     */


   file_d.dsc$a_pointer = file_spec;
   file_d.dsc$w_length = (USHORT) strlen(file_spec);

   fcontext = 0L;			/* initialize context		      */
   count = 0;				/* MUST initialize register variable! */

   /* now parse the wildcard file specification, counting each file as
    * we go along
    */

   status = LIB$FIND_FILE(&file_d,&file2_d,&fcontext);

   while(status == RMS$_NORMAL)
   {
      count++;				/* count it			      */
      status = LIB$FIND_FILE(&file_d,&file2_d,&fcontext);
   }

   if(!count) 				/* did we find any files?	      */
      return(0);			/* in case directory is empty...      */

   if(status != RMS$_NMF)		/* did we stop for the right reason?  */
      LIB$STOP(status);			/* nope.....			      */


   /* terminate the "finding" of files */

   status = LIB$FIND_FILE_END(&fcontext);

   if((status != SS$_NORMAL) && (status != RMS$_NORMAL))
      LIB$STOP(status);

   return(count);			/* return number of files	      */

}	/*** get_num_file ***/
/*eject*/
/*******************************************************************************
********************************************************************************

  Function:	set_sysdisk

  Purpose:	Set the value of the SYS$DISK logical to the specified value.

  Global variables:

	Name			Examine/Modify/Use/Read/Write
	----			-----------------------------
	none

  Return Codes:

	Code			Reason
	----			------
	none

********************************************************************************
*******************************************************************************/

void set_sysdisk(dev_spec)
					/*******   FORMAL  PARAMETERS   *******/
	 char	  *dev_spec;		/* value to set SYS$DISK to	      */

{	/*** set_sysdisk ***/
					/********   LOCAL  VARIABLES   ********/
	 ULONG	  status;		/* return code status holder	      */
static $DESCRIPTOR(table_d,"LNM$PROCESS"); /* where SYS$DISK is		      */
static $DESCRIPTOR(logical_d,"SYS$DISK"); /* this is what we need to set      */
static	 ITM_LST  items[] = {		/* item list for SYS$CRELNM	      */
{0,LNM$_STRING,NULL,NULL},
{0,0,NULL,NULL}};


   items[0].buf_len = (USHORT) strlen(dev_spec);
   items[0].buf_addr = dev_spec;

   status = SYS$CRELNM(0,&table_d,&logical_d,0,items);

   if(status != SS$_NORMAL && status != SS$_SUPERSEDE)
   {
      LIB$STOP(status);
   }

   return;

}	/*** set_sysdisk ***/
/*eject*/
/*******************************************************************************
********************************************************************************

  Function:	set_direct

  Purpose:	Determine if a directory and/or file specification was speci-
		fied on the command line.  Change to the directory if appro-
		priate and save the filespec for parsing later if one was
		specified.

  Global variables:

	Name			Examine/Modify/Use/Read/Write
	---- 			-----------------------------
	none

  Return Codes:

	Code			Reason
	----			------
	none

********************************************************************************
*******************************************************************************/

void set_direct(file_spec,dir_spec,dev_spec,user_dir)
			               	/*******   FORMAL  PARAMETERS   *******/
	 char	  *file_spec,		/* starting file specification        */
		  *dir_spec,   		/* name of directory to start in      */
		  *dev_spec,   		/* device specification               */
		  *user_dir;   		/* user's directory at run-time       */

{	/*** set_direct ***/
					/********   LOCAL  VARIABLES   ********/
	 char	  *tptr,		/* temporary character pointer	      */
		  *tptr2;		/* ditto.....			      */
	 ULONG	  status;		/* return code status holder	      */
	 USHORT	  leng;			/* length of first parameter returned */
static	 char	  buf[NAM$C_MAXRSS+1];	/* for holding the first parameter    */
static $DESCRIPTOR(file_d,"INFILE");	/* for getting the first parameter    */
static $DESCRIPTOR(buf_d,buf);		/* output descriptor		      */
static $DESCRIPTOR(dir_d,"");		/* descriptor for changing directory  */


   /* get the user's directory so that we can go back to it later */

   curr_dir(buf);
   tptr = strchr(buf,'[');		/* get start of directory	      */
   tptr2 = strchr(tptr,']');		/* get end of directory		      */
   strmcpy(user_dir,tptr,tptr2 - tptr + 1);

   /* see if the user specified a directory specification on the command line */

   status = CLI$GET_VALUE(&file_d,&buf_d,&leng);

   if(status != CLI$_ABSENT)
   {   
      /* now try to change to the directory that was left over after lopping
       * off the filename or type
       */

      buf[leng] = '\0';			/* make it a string		      */

      parse_arg(buf,file_spec,dir_spec,dev_spec);

      if(*dev_spec != '\0')
      {
	 /* there is a device specification; we must set the SYS$DISK logical
	  * so that when we change directories, the device is correct; un-
	  * fortunately, SYS$SETDDIR can't handle directories that are on
	  * another device :-(
	  */

	 set_sysdisk(dev_spec);
      }

      if(strcmp(dir_spec,"[]") != 0 && *dir_spec != '\0')
      {
	 /* only change directory if we need to */

	 dir_d.dsc$w_length = (USHORT) strlen(dir_spec);
	 dir_d.dsc$a_pointer = dir_spec;

	 status = SYS$SETDDIR(&dir_d,0,0);

	 if(status != RMS$_NORMAL)
	 {
	    fprintf(stdout,
		    "\nMAINT:  Cannot change to specified directory\n\n");
	    exit();
	 }
      }
   }
   else
   {
      strcpy(dir_spec,user_dir);	/* just the current directory	      */
      strcpy(file_spec,"*.*;*");	/* wildcard to get all files	      */
   }

   return;

}	/*** set_direct ***/
/*eject*/
/*******************************************************************************
********************************************************************************

  Function:	parse_arg

  Purpose:	Parse the command line argument into pieces that we can
		deal with.

  Global variables:

	Name			Examine/Modify/Use/Read/Write
	----			-----------------------------
	none

  Return Codes:

	Code			Reason
	----			------
	none

  Termination Codes:

	Code			Reason
	----			------
	status			return code from SYS$TRNLNM()

********************************************************************************
*******************************************************************************/

static void parse_arg(cmd_arg,file_spec,dir_spec,dev_spec)
					/*******   FORMAL  PARAMETERS   *******/
	 char	  *cmd_arg,		/* command-line argument	      */
		  *file_spec,		/* file specification to return	      */
		  *dir_spec,		/* directory specification to return  */
		  *dev_spec;		/* device specification to return     */

{	/*** parse_arg ***/
					/********   LOCAL  VARIABLES   ********/
       	 ULONG	  status,		/* return code status holder	      */
		  status2;		/* secondary return code status	      */
	 USHORT	  done,			/* loop control flag		      */
		  file_saved = 0,	/* set if file name on command line   */
		  type_saved = 0,	/* set if file type on command line   */
		  dir_saved = 0,	/* set if directory spec saved	      */
		  dev_saved = 0;	/* set if device spec saved	      */
static	 ULONG	  attr;			/* logical name attributes	      */
static	 short	  length,		/* length returned from SYS$TRNLNM()  */
	     	  length2;		/* length of attributes returned      */
static	 char	  buffer[BUFSIZ];	/* buffer for holding translations    */
static $DESCRIPTOR(arg_d,"");		/* descriptor for command-line arg    */
static $DESCRIPTOR(table_d,"LNM$PROCESS");
static $DESCRIPTOR(table2_d,"LNM$JOB");
static $DESCRIPTOR(table3_d,"LNM$GROUP");
static $DESCRIPTOR(table4_d,"LNM$SYSTEM");
static $DESCRIPTOR(logical_d,"");
static	 struct	  fscndef  items[] = {	/* for parsing file specifications    */
{0,FSCN$_DEVICE,0L},
{0,FSCN$_DIRECTORY,0L},
{0,FSCN$_NAME,0L},
{0,FSCN$_TYPE,0L},
{0,0,0L}};
static	 ITM_LST  items2[] = {		/* item list for translating logicals */
{sizeof(buffer),LNM$_STRING,buffer,&length},
{sizeof(attr),LNM$_ATTRIBUTES,&attr,&length2},
{0,0,NULL,NULL}};


   /* first parse the argument into 1) the device, 2) the directory, and
    * 3) the filename
    */

   *file_spec = '\0';
   arg_d.dsc$a_pointer = cmd_arg;
   arg_d.dsc$w_length = (USHORT) strlen(cmd_arg);

   status = SYS$FILESCAN(&arg_d,items,0);

   if(status != SS$_NORMAL)
   {
      printf("\nMAINT:  invalid command line argument\n");
      SYS$EXIT();
   }   

   /* right here, if we have a filename and spec, save them and clear them
    * out of the item list
    */

   if(items[2].fscn$w_length && (items[0].fscn$w_length ||
      items[1].fscn$w_length || items[3].fscn$w_length ||
      banynstr(items[2].fscn$l_addr,"*%",items[2].fscn$w_length) != -1))
   {
      strmcpy(file_spec,items[2].fscn$l_addr,items[2].fscn$w_length);
      items[2].fscn$w_length = 0;
      items[2].fscn$l_addr = NULL;
      file_saved = 1;			/* means we saved file name already   */
   }

   if(items[3].fscn$w_length)
   {
      strncat(file_spec,items[3].fscn$l_addr,items[3].fscn$w_length);
      items[3].fscn$w_length = 0;
      items[3].fscn$l_addr = NULL;
      type_saved = 1;			/* means we saved file type already   */
   }

   if(items[1].fscn$w_length)
   {
      /* save a directory spec if there is one */

      strmcpy(dir_spec,items[1].fscn$l_addr,items[1].fscn$w_length);
      dir_saved = 1;
   }

   if(items[0].fscn$w_length)
   {
      /* save the device spec if there is one */

      strmcpy(dev_spec,items[0].fscn$l_addr,items[0].fscn$w_length);
      dev_saved = 1;
   }

   if(!items[1].fscn$w_length)
   {
      /* hmmm....this might be a logical name so we must translate it until
       * we get a directory spec; first set up the descriptor that points to
       * what we want to translate
       */

      if(!items[0].fscn$w_length)
      {
	 logical_d.dsc$a_pointer = items[2].fscn$l_addr;
	 logical_d.dsc$w_length = items[2].fscn$w_length;
      }
      else
      {
	 logical_d.dsc$a_pointer = items[0].fscn$l_addr;
       	 logical_d.dsc$w_length = items[0].fscn$w_length - 1;
      }

      /* try to translate it */

      length = 0;
      done = FALSE;
      arg_d.dsc$a_pointer = buffer;

      status = SYS$TRNLNM(0,&table_d,&logical_d,0,items2);

      while(status == SS$_NORMAL && !done)
      {
	 /* scan the resultant translation for a file specification; if
	  * we have one, we stop here and take it
	  */

	 arg_d.dsc$w_length = length;

	 status2 = SYS$FILESCAN(&arg_d,items,0);

	 if(status2 != SS$_NORMAL)
	    LIB$STOP(status2);

	 /* look to see if we have a file specification here; if not,
	  * translate it again
	  */

	 if(items[0].fscn$w_length == 0 && items[1].fscn$w_length == 0)
	 {
	    /* no device or directory spec yet */

	    logical_d.dsc$a_pointer = buffer;
	    logical_d.dsc$w_length = (USHORT) length;

	    status = SYS$TRNLNM(0,&table_d,&logical_d,0,items2);
	 }
	 else
	    done = TRUE;		/* found one; exit loop....	      */
      }
   }

   /* if we have a filetype OR a device, we are finished since there is
    * nothing left to translate; otherwise, 1) save the filename and
    * file type if they BOTH exist; if one of them doesn't exist yet, defer
    * saving them for the moment; 2) try to translate the device name until
    * we get what we need
    */

   if(items[1].fscn$w_length || done)
   {
      copy_spec(dev_spec,dir_spec,file_spec,items,file_saved,type_saved);
   }
   else
   {
      /* now the hard part; there is a device spec but no file name/type; we
       * must try to translate the device spec until 1) we get both a device
       * spec and a directory, or 2) we can't translate it anymore; we might
       * have to move to another table to get the translation; we start
       * with LNM$PROCESS
       */

      if(items[0].fscn$w_length)
      {
	 strmcpy(buffer,items[0].fscn$l_addr,items[0].fscn$w_length);
	 logical_d.dsc$w_length = items[0].fscn$w_length - 1;
      }
      else
      {
	 strmcpy(buffer,items[2].fscn$l_addr,items[2].fscn$w_length);
	 logical_d.dsc$w_length = items[2].fscn$w_length;
      }

      /* now try to translate it */

      logical_d.dsc$a_pointer = buffer;
      arg_d.dsc$a_pointer = buffer;
      done = FALSE;

      status = SYS$TRNLNM(0,&table_d,&logical_d,0,items2);

      while(status == SS$_NORMAL && !done)
      {
	 /* scan it for device and directory spec */

	 arg_d.dsc$w_length = length;

	 status2 = SYS$FILESCAN(&arg_d,items,0);

	 if(status2 != SS$_NORMAL)
	    LIB$STOP(status2);

	 /* now see if we have a device and directory spec */

	 if(items[0].fscn$w_length == 0 && items[1].fscn$w_length == 0 &&
	   !(attr & LNM$M_TERMINAL))
	 {
	    /* no device or directory spec yet */

	    logical_d.dsc$w_length = (USHORT) length;

	    status = SYS$TRNLNM(0,&table_d,&logical_d,0,items2);
	 }
	 else
	    done = TRUE;		/* found one; exit loop....	      */
      }

      /* why did we exit the loop?  if we have a directory and device spec,
       * we're finished; if not, or the logical was terminal, we have to
       * go to the next table and continue to try to translate
       */

      if(done || (items[0].fscn$w_length || items[1].fscn$w_length))
      {
	 copy_spec(dev_spec,dir_spec,file_spec,items,file_saved,type_saved);
	 return;
      }

      done = FALSE;

      status = SYS$TRNLNM(0,&table2_d,&logical_d,0,items2);

      while(status == SS$_NORMAL && !done)
      {
	 /* scan it for device and directory spec */

	 arg_d.dsc$w_length = length;

	 status2 = SYS$FILESCAN(&arg_d,items,0);

	 if(status2 != SS$_NORMAL)
	    LIB$STOP(status2);

	 /* now see if we have a device and directory spec */

	 if(items[0].fscn$w_length == 0 && items[1].fscn$w_length == 0 &&
	   !(attr & LNM$M_TERMINAL))
	 {
	    /* no device or directory spec yet */

	    logical_d.dsc$w_length = (USHORT) length;

	    status = SYS$TRNLNM(0,&table2_d,&logical_d,0,items2);
	 }
	 else
	    done = TRUE;		/* found one; exit loop....	      */
      }

      if(done || (items[0].fscn$w_length || items[1].fscn$w_length))
      {
	 copy_spec(dev_spec,dir_spec,file_spec,items,file_saved,type_saved);
	 return;
      }

      done = FALSE;

      status = SYS$TRNLNM(0,&table3_d,&logical_d,0,items2);

      while(status == SS$_NORMAL && !done)
      {
	 /* scan it for device and directory spec */

	 arg_d.dsc$w_length = length;

	 status2 = SYS$FILESCAN(&arg_d,items,0);

	 if(status2 != SS$_NORMAL)
	    LIB$STOP(status2);

	 /* now see if we have a device and directory spec */

	 if(items[0].fscn$w_length == 0 && items[1].fscn$w_length == 0 &&
	   !(attr & LNM$M_TERMINAL))
	 {
	    /* no device or directory spec yet */

	    logical_d.dsc$w_length = (USHORT) length;

	    status = SYS$TRNLNM(0,&table3_d,&logical_d,0,items2);
	 }
	 else
	    done = TRUE;		/* found one; exit loop....	      */
      }

      if(done || (items[0].fscn$w_length || items[1].fscn$w_length))
      {
	 copy_spec(dev_spec,dir_spec,file_spec,items,file_saved,type_saved);
	 return;
      }

      done = FALSE;

      status = SYS$TRNLNM(0,&table4_d,&logical_d,0,items2);

      while(status == SS$_NORMAL && !done)
      {
	 /* scan it for device and directory spec */

	 arg_d.dsc$w_length = length;

	 status2 = SYS$FILESCAN(&arg_d,items,0);

	 if(status2 != SS$_NORMAL)
	    LIB$STOP(status2);

	 /* now see if we have a device and directory spec */

	 if(items[0].fscn$w_length == 0 && items[1].fscn$w_length == 0 &&
	   !(attr & LNM$M_TERMINAL))
	 {
	    /* no device or directory spec yet */

	    logical_d.dsc$w_length = (USHORT) length;

	    status = SYS$TRNLNM(0,&table4_d,&logical_d,0,items2);
	 }
	 else
	    done = TRUE;		/* found one; exit loop....	      */
      }
             
      if(done || (items[0].fscn$w_length || items[1].fscn$w_length))
      {
	 copy_spec(dev_spec,dir_spec,file_spec,items,file_saved,type_saved);
	 return;
      }
   }

   /* copy the file, directory, and device specifications to where they
    * belong and apply any defaults that should be applied
    */

   copy_spec(dev_spec,dir_spec,file_spec,items,file_saved,type_saved);

   return;

}     	/*** parse_arg ***/   
/*eject*/
/*******************************************************************************
********************************************************************************

  Function:	copy_spec

  Purpose:	Copy the file, directory, and device specifications to their
		resting places from the item list returned by SYS$FILESCAN,
		applying default values where appropriate.

  Global variables:

	Name			Examine/Modify/Use/Read/Write
	----			-----------------------------
	none

  Return Codes:

	Code			Reason
	----			------
	none

  Termination Codes:

	Code			Reason
	----			------
	status			return code from SYS$TRNLNM()

********************************************************************************
*******************************************************************************/

static void copy_spec(device,directory,filename,items,file_saved,type_saved)
					/*******   FORMAL  PARAMETERS   *******/
	 char	  *device,		/* device specification		      */
		  *directory,		/* directory specification	      */
		  *filename;		/* file name specification	      */
struct	 fscndef  items[];		/* item list from SYS$FILESCAN()      */
	 USHORT	  file_saved,		/* if set, don't need file name	      */
		  type_saved;		/* if set, don't need file type	      */

{	/*** copy_spec ***/
					/********   LOCAL  VARIABLES   ********/
	 char	  *tptr = NULL,		/* temporary character pointer	      */
		  *tptr2 = NULL;	/* ditto.......			      */


   if(!file_saved)
   {
      /* the filename has not been saved yet; now see if the file type has
       * been saved yet; if it hasn't, then we just copy the filename to the
       * appropriate place; otherwise, we have to insert it instead of just
       * copying it because we don't want to trash the file type that is
       * already there
       */

      if(!type_saved)
      {
	 if(items[2].fscn$w_length)
	    strmcpy(filename,items[2].fscn$l_addr,items[2].fscn$w_length);
	 else
	    strcpy(filename,"*");
      }
      else
      {
	 if(items[2].fscn$w_length)
	 {
	    /* what a pain */

	    tptr = (char *) items[2].fscn$l_addr;
	    tptr[items[2].fscn$w_length] = '\0';	    
	    strins(filename,items[2].fscn$l_addr,0);
	    tptr[items[2].fscn$w_length] = '.';	    
	 }
	 else
	    strins(filename,"*",0);
      }
   }

   if(!type_saved)
   {
      /* we didn't get a file type from the command line; save whatever is
       * appropriate here
       */

      if(items[3].fscn$w_length)
	 strncat(filename,items[3].fscn$l_addr,items[3].fscn$w_length);
      else
	 strcat(filename,".*");
   }

   if(items[0].fscn$w_length)
      strmcpy(device,items[0].fscn$l_addr,items[0].fscn$w_length);
   else
      *device = '\0';

   /* save the directory spec */

   if(items[1].fscn$w_length)
      strmcpy(directory,items[1].fscn$l_addr,items[1].fscn$w_length);
   else
   {
      /* this is weird.....for some reason, SYS$FILESCAN will not detect a
       * directory spec for something like _$255$DUA1:[SCR.]; it seems that
       * this only happen with logicals names that translate to something with
       * a period adjacent to the right bracket; so.......if this logical
       * if we find a directory like this, we will strip out the period
       * because it will just mess us up later when we try to do a SYS$SETDDIR
       * if we don't
       */

      if(items[0].fscn$l_addr)
      {
	 tptr = strchr(items[0].fscn$l_addr,'[');
	 tptr2 = strchr(items[0].fscn$l_addr,']');
      }

      if(tptr && tptr2)
      {
	 /* look at character preceding the ']'; if its a period, get
	  * rid of it
	  */

	 if(*(tptr2 - 1) == '.')	/* [xxx.] format?		      */
	 {
	    tptr2--;			/* take out the period here...	      */
	    *tptr2 = ']';
	 }

	 strmcpy(directory,tptr,tptr2 - tptr + 1);
      }
      else
      {
	 strmcpy(directory,"[]");
      }
   }

   return;

}	/*** copy_spec ***/
/*eject*/
/*******************************************************************************
********************************************************************************

  Function:	push

  Purpose:	Spawn a subprocess that allows the user to escape to DCL.
		The call to LIB$SPAWN will hibernate the main process until
		the subprocess completes.  The subprocess will inherit all
		of the attributes of the user's login process.

  Global variables:

	Name			Examine/Modify/Use/Read/Write
	----			-----------------------------
	none

  Return Codes:

	Code			Reason
	----			------
	SS$_NORMAL		everything ok
	SS$_NORMAL+1		LIB$SPAWN() failed; couldn't push out

********************************************************************************
*******************************************************************************/

ULONG push(command)
					/*******   FORMAL  PARAMETERS   *******/
	 char	  *command;		/* command to be executed	      */

{	/*** push ***/
					/********   LOCAL  VARIABLES   ********/
	 ULONG	  status,		/* return code status		      */
		  comp_status;		/* subprocess completion status	      */
static $DESCRIPTOR(prompt_d,"Maint> ");	/* prompt for subprocess	      */
static $DESCRIPTOR(command_d,"");	/* for forming the command	      */



   /* spawn the subprocess; LIB$SPAWN will put us to sleep and wake us up when
    * the subprocess completes; after we wake up, just return the completion
    * code of the subprocess
    */

   command_d.dsc$a_pointer = command;	/* set up the command descriptor      */
   command_d.dsc$w_length = (USHORT) strlen(command);

   status = LIB$SPAWN(&command_d,0,0,0,0,0,&comp_status,0,0,0,&prompt_d,0);

   if(status != SS$_NORMAL)
   {
      return(SS$_NORMAL+1);		/* return something other than NORMAL */
   }

   return(SS$_NORMAL);			/* say that everything went ok	      */

}	/*** push ***/
/*eject*/
/*******************************************************************************
********************************************************************************

  Function:	mystrcpy

  Purpose:	Version of strcpy that copies a string and returns a pointer
		to the null character in the destination string.

  Global variables:

	Name			Examine/Modify/Use/Read/Write
	----			-----------------------------
	none

  Return Codes:

	Code			Reason
	----			------
	dest - 1		pointer to null character terminating the
				destionation string

********************************************************************************
*******************************************************************************/

char *mystrcpy(dest,source)
					/*******   FORMAL  PARAMETERS   *******/
register char	  *dest,		/* destination string		      */
		  *source;		/* source string		      */

{	/*** mystrcpy ***/

   while(*dest++ = *source++)		/* copy the source string to the dest */
      ;

   return(dest - 1);			/* return pointer to null character   */

}	/*** mystrcpy ***/
/*eject*/
/*******************************************************************************
********************************************************************************

  Function:	mark_delete

  Purpose:	The selected file is to be deleted; allocate memory for the
		command structure (if necessary), link it to the entry, and
		set up the command structure so that the file can be deleted.

  Global variables:

	Name			Examine/Modify/Use/Read/Write
	----			-----------------------------
	none

  Return Codes:

	Code			Reason
	----			------
	  0			completed successfully
	 -1			problems allocating memory

  Termination Codes:

	Code			Reason
	----			------
	status			LIB$FREE_VM() failed

********************************************************************************
*******************************************************************************/

short mark_delete(ent)
					/*******   FORMAL  PARAMETERS   *******/
register ENT_DEF  *ent;			/* entry for file to be deleted	      */

{	/*** mark_delete ***/
					/********   LOCAL  VARIABLES   ********/
register COM_DEF  *tptr;		/* temporary command structure ptr    */
	 ULONG	  status;		/* return code status holder	      */
	 long	  str_size;		/* amount of memory to allocate	      */


   /* do we need a command structure or does the file already have one? */

   if(ent->command == NULL)
   {
      /* we need command structure; allocate it and link it to the file entry */

      tptr = new_comm();
      if(tptr == NULL)
	 return(-1);			/* problems allocating memory	      */

      ent->command = tptr;		/* link it to the file entry	      */
   }
   else
   {
      tptr = ent->command;		/* else use the one already there     */
   }

   tptr->comm_del = 1;

   /* deallocate all of the memory that might have been used for things
    * like renaming the file and for a text descriptor
    */

   if(tptr->ren_name != NULL)		/* need to deallocate rename name?    */
   {
      str_size = (long) tptr->ren_len;	/* length to deallocate 	      */

      status = LIB$FREE_VM(&str_size,&(tptr->ren_name));

      if(status != SS$_NORMAL)
	 LIB$STOP(status);

      tptr->comm_ren = 0;		/* make sure to shut of renaming      */
      tptr->ren_name = NULL;		/* reset the rename name stuff	      */
      tptr->ren_len = 0;
   }

   if(tptr->text != NULL)		/* need to deallocate text descrip?   */
   {
      str_size = (long) tptr->text_len;	/* length to deallocate		      */

      status = LIB$FREE_VM(&str_size,&(tptr->text));

      if(status != SS$_NORMAL)
	 LIB$STOP(status);

      tptr->comm_text = 0;		/* make sure to shut of text descrip  */
      tptr->text = NULL;		/* reset the text descriptor stuff    */
      tptr->text_len = 0;
   }

   return(0);				/* no problem.....		      */

}	/*** mark_delete ***/
/*eject*/
/*******************************************************************************
********************************************************************************

  Function:  	mark_copy_ren

  Purpose:  	The selected file is to be copied or renamed; allocate the
		memory for the command structure (if necessary), link it to
		the file entry,	and set the command in the command structure.

  Global variables:
                          
	Name			Examine/Modify/Use/Read/Write
	----			-----------------------------
	none

  Return Codes:

	Code			Reason
	----			------
	  0			completed successfully
	 -1			problems allocating memory

  Termination Codes:

	Code			Reason
	----			------
	status			return code from various failed system calls

********************************************************************************
*******************************************************************************/
   
short mark_copy_ren(ent,pool,disp_id,keybd_id,pool_length,scr_width,command)
					/*******   FORMAL  PARAMETERS   *******/
register ENT_DEF  *ent;			/* entry for file to be deleted	      */
	 POOL_DEF **pool;		/* pointer to current pool structure  */
	 long	  disp_id,		/* display id for virtual display     */
		  keybd_id,		/* keyboard id			      */
		  pool_length;		/* length of pool if we need another  */
	 short	  scr_width;		/* width of screen		      */
	 char	  command;		/* either RENAME or COPY	      */
            
{	/*** mark_copy_ren ***/
					/********   LOCAL  VARIABLES   ********/
register COM_DEF  *tptr;		/* temporary command structure ptr    */
	 char	  *ptr,			/* temporary character pointer	      */
		  *dest_ptr;		/* destination string pointer	      */
 	 ULONG	  status;		/* return code status holder	      */
	 long	  str_size;		/* amount of memory to allocate	      */
	 USHORT	  length;		/* length of string read in	      */
	 char	  buf[FULL_SPEC_MAX+1],	/* array for new filename	      */
		  prompt[PROMPT_MAX+1];	/* prompt array			      */


   buf[0] = '\0';			/* reset the buffer		      */

   /* set up the prompt for the filename */

   if(command == COPY)
      strcpy(prompt,"Copy to: ");
   else
      strcpy(prompt,"Rename to: ");

   /* read in the file spec */

   status = get_string(keybd_id,disp_id,FULL_SPEC_MAX,buf,prompt,scr_width);

   if(status != SS$_NORMAL)
      return(-1);

   /* was anything specified for the filename?  if not, just return from here */

   if(*buf == '\0')
      return(0);

   length = (USHORT)strlen(buf) + 1; 	/* don't forget to count the NUL      */

   /* ok, we have a filename; now determine if we need to allocate a command
    * structure for this file or if one already exists from a previous command
    */

   if(ent->command == NULL)
   {
      /* we need command structure; allocate it and link it to the file entry */
            
      tptr = new_comm();
      if(tptr == NULL)
	 return(-1);			/* problems allocating memory	      */

      ent->command = tptr;		/* link it to the file entry	      */
   }
   else
   {
      tptr = ent->command;		/* else use the one already there     */
   }

   ptr = buf;				/* convert filename to all uppercase  */
   while(*ptr)
   {
      *ptr = _toupper(*ptr);
      ptr++;
   }

   /* is there already enough room to put the new name in a memory pool? if
    * there is, just reuse the memory we already have; otherwise, free up
    * the memory (if there is any) and allocate some to hold the new name
    */

   if(command == COPY)
   {
      if(length > tptr->copy_len)
      {
	 /* nope, we need more memory; first free the old memory if there
	  * was any
	  */

	 if(tptr->copy_len != 0)
	 {
	    str_size = (long) tptr->copy_len;

	    status = LIB$FREE_VM(&str_size,&(tptr->copy_name));

	    if(status != SS$_NORMAL)
	       LIB$STOP(status);
	 }

	 /* now allocate some new memory to hold the longer filename */

	 str_size = (long) length;
	 status = LIB$GET_VM(&str_size,&(tptr->copy_name));

	 if(status != SS$_NORMAL)
	    LIB$STOP(status);

	 tptr->copy_len = length;	/* save the length of the memory      */
      }

      dest_ptr = tptr->copy_name;	/* get where to copy the filename to  */
      tptr->comm_copy = 1;		/* set the command in the structure   */
   }
   else
   {
      if(length > tptr->ren_len)
      {
	 /* nope, we need more memory; first free the old memory if there
	  * was any
	  */

	 if(tptr->ren_len != 0)
	 {
	    str_size = (long) tptr->ren_len;

	    status = LIB$FREE_VM(&str_size,&(tptr->ren_name));

	    if(status != SS$_NORMAL)
	       LIB$STOP(status);
	 }

	 /* now allocate some new memory to hold the longer filename */

	 str_size = (long) length;
	 status = LIB$GET_VM(&str_size,&(tptr->ren_name));

	 if(status != SS$_NORMAL)
	    LIB$STOP(status);

	 tptr->ren_len = length;	/* save the length of the memory      */
      }

      dest_ptr = tptr->ren_name;	/* get where to copy the filename to  */
      tptr->comm_ren = 1;		/* set the command in the structure   */
      tptr->comm_del = 0;		/* rename implies no delete	      */
   }

   strcpy(dest_ptr,buf);		/* copy it to the right place	      */

   return(0);

}	/*** mark_copy_ren ***/
/*eject*/
/*******************************************************************************
********************************************************************************

  Function:  	mark_text

  Purpose:  	The selected file is to get a text descriptor; allocate the
		memory for the command structure (if necessary), link it to
		the file entry,	and set the command in the command structure.

  Global variables:
                          
	Name			Examine/Modify/Use/Read/Write
	----			-----------------------------
	none

  Return Codes:

	Code			Reason
	----			------
	  0			completed successfully
	 -1			problems allocating memory

  Termination Codes:

	Code			Reason
	----			------
	status			status of various failed LIB$ calls

********************************************************************************
*******************************************************************************/
   
short mark_text(ent,pool,disp_id,keybd_id,pool_length,scr_width)
					/*******   FORMAL  PARAMETERS   *******/
register ENT_DEF  *ent;			/* entry for file to be deleted	      */
	 POOL_DEF **pool;		/* pointer to current pool structure  */
	 long	  disp_id,		/* display id for virtual display     */
		  keybd_id,		/* keyboard id			      */
	    	  pool_length;		/* length of pool if we need another  */
	 short	  scr_width;		/* width of screen		      */

{	/*** mark_text ***/
					/********   LOCAL  VARIABLES   ********/
register COM_DEF  *tptr;		/* temporary command structure ptr    */
 	 ULONG	  status;		/* return code status holder	      */
	 long	  str_size,		/* amount of memory to allocate	      */
		  column;		/* column of where to write	      */
	 USHORT	  length;		/* length of string read in	      */
static	 char	  buf[TEXT_MAX+1],	/* array for text descriptor	      */
		  prompt[] =		/* for prompting the user	      */
		  {"Text descriptor: "};
static $DESCRIPTOR(buf_d,buf);		/* descriptor for getting filename    */
static $DESCRIPTOR(limit_d,"<");
static $DESCRIPTOR(prompt_d,prompt);


   buf[0] = '\0';			/* reset the buffer		      */

   /* first put the character that marks the maximum number of characters
    * that can be specified for the text descriptor
    */

   column = (long) (TEXT_MAX +  strlen(prompt)) + 1L;
   status = SMG$PUT_CHARS(&disp_id,&limit_d,&MAIN_ROWS,&column,0,&SMG$M_BOLD,
			  0,0);

   if(status != SS$_NORMAL)
      return(-1);

   /* position the cursor to prompt for the text descriptor */

   status = SMG$SET_CURSOR_ABS(&disp_id,&MAIN_ROWS,&1L);

   if(status != SS$_NORMAL)
      return(-1);   

   /* read in the text descriptor */

   status = SMG$READ_STRING(&keybd_id,&buf_d,&prompt_d,&TEXT_MAX,0,0,0,&length,
			    0,&disp_id,0,&SMG$M_BOLD,0);
                                     
   /* erase the prompt and the user's response from the virtual display */

   clear_mess(disp_id,scr_width);

   if(status != SS$_NORMAL)
      return(-1);

   buf[length] = '\0';			/* make the filename a string	      */
   length++;				/* don't forget to count the NUL      */

   /* ok, we have the text descriptor; determine if we need to allocate a
    * command structure for this file or if one already exists from a
    * previous command
    */

   if(ent->command == NULL)
   {
      /* we need command structure; allocate it and link it to the file entry */

      tptr = new_comm();
      if(tptr == NULL)
	 return(-1);			/* problems allocating memory	      */

      ent->command = tptr;		/* link it to the file entry	      */
   }
   else
   {
      tptr = ent->command;		/* else use the one already there     */
   }

   /* is there already enough room to store the text descriptor?
    * if there is, just reuse the memory we already have; otherwise,
    * allocate some new memory
    */

   if(length > tptr->text_len)
   {
      /* we need more memory; first free up any old memory if there was any */

      if(tptr->text_len != 0)
      {
	 str_size = (long) tptr->text_len;

	 status = LIB$FREE_VM(&str_size,&(tptr->text));

	 if(status != SS$_NORMAL)
	    LIB$STOP(status);
      }

      /* allocate some memory to hold the text descriptor */

      str_size = (long) length;
      status = LIB$GET_VM(&str_size,&(tptr->text));

      if(status != SS$_NORMAL)
	 LIB$STOP(status);

      tptr->text_len = length;		/* we have a new length		      */
   }

   strcpy(tptr->text,buf);		/* copy it to its resting place	      */

   /* set the command in the command structure */

   tptr->comm_text = 1;
   tptr->comm_del = 0;			/* text descriptor implies no delete  */

   return(0);

}	/*** mark_text ***/
/*eject*/
/*******************************************************************************
********************************************************************************

  Function:  	mark_protect

  Purpose:  	The selected file is to be reprotected; allocate the memory
		for the command structure (if necessary), link it to the file
		entry,	and set the command in the command structure.

  Global variables:
                          
	Name			Examine/Modify/Use/Read/Write
	----			-----------------------------
	none

  Return Codes:

	Code			Reason
	----			------
	  0			completed successfully
	 -1			problems allocating memory

********************************************************************************
*******************************************************************************/

short mark_protect(ent,disp_id,keybd_id,scr_width)
					/*******   FORMAL  PARAMETERS   *******/
register ENT_DEF  *ent;			/* entry for file to be deleted	      */
	 long	  disp_id,		/* display id for virtual display     */
		  keybd_id;		/* keyboard id			      */
	 short	  scr_width;		/* width of screen		      */

{	/*** mark_protect ***/
					/********   LOCAL  VARIABLES   ********/
register COM_DEF  *tptr;		/* temporary command structure ptr    */
register char	  *ptr;			/* temporary character pointer	      */
	 ULONG	  status;		/* return code status holder	      */
	 long	  str_size,		/* amount of memory to allocate	      */
		  end_col;		/* for erasing the prompt	      */
	 USHORT	  prot_val;		/* temporary protection value	      */
	 char	  buf[PROT_INP_MAX+1];	/* array for protection string	      */


   buf[0] = '\0';			/* initialize input buffer	      */

   /* prompt for and read in the protection string */

   status = get_string(keybd_id,disp_id,PROT_INP_MAX,buf,"Protection: ",
		       scr_width);

   if(status != SS$_NORMAL)
      return(-1);

   /* if nothing was specified for the protection string, just return from
    * right here
    */

   if(*buf == '\0')
      return(0);

   /* now determine if the protection string specified is valid */

   if(prot_str_to_val(buf,&prot_val,ent->prot) == -1)
      return(-1);			/* invalid protection string	      */

   /* at this point, the protection string has been edited and is considered
    * valid; get the integer value for the protection string and store it in
    * in the command structure; first, do we need a command structure or does
    * the file have one from a previous command?
    */

   if(ent->command == NULL)
   {
      /* we need command structure; allocate it and link it to the file entry */

      tptr = new_comm();
      if(tptr == NULL)
	 return(-1);			/* problems allocating memory	      */

      ent->command = tptr;		/* link it to the file entry	      */
   }
   else
   {
      tptr = ent->command;		/* else use the one already there     */
   }

   tptr->comm_prot = 1;			/* set the command in the structure   */
   tptr->prot = prot_val;		/* set the protection integer value   */
   tptr->comm_del = 0;			/* protection implies no delete	      */

   return(0);				/* fine.........		      */

}	/*** mark_protect ***/
/*eject*/
/*******************************************************************************
********************************************************************************

  Function:  	mark_cancel

  Purpose:  	Cancel all commands for the specified file entry and deallocate
		the command structure for the file.

  Global variables:
                          
	Name			Examine/Modify/Use/Read/Write
	----			-----------------------------
	none

  Return Codes:

	Code			Reason
	----			------
	  0			completed successfully
	 -1			problems allocating memory

********************************************************************************
*******************************************************************************/

void mark_cancel(ent)
					/*******   FORMAL  PARAMETERS   *******/
	 ENT_DEF *ent;			/* file entry pointer		      */

{	/*** mark_cancel ***/


   if(ent->command != NULL)
   {
      /* first free up any memory that might be  holding character strings */

      free_comstr(ent->command);

      ent->command = NULL;	 	/* no more structure for file entry   */
   }

   return;

}	/*** mark_cancel ***/
/*eject*/
/*******************************************************************************
********************************************************************************

  Function:	init_pool

  Purpose:	Allocate the first memory pool for the current directory,
		initializing the pool structure node and initialize all of
		the other structures to point to null pools.  The remaining
		pool node structures are linked together in a linked list.

  Global variables:

	Name			Examine/Modify/Use/Read/Write
	----			-----------------------------
	none

  Return Codes:
                 
	Code			Reason
	----			------
	none

  Termination Codes:

	Code			Reason
	----			------
	status			LIB$GET_VM() failed

********************************************************************************
*******************************************************************************/

void init_pool(pool,pool_length)
					/*******   FORMAL  PARAMETERS   *******/
	 POOL_DEF **pool;		/* array of pool node structures      */
	 long	  pool_length;		/* length of pool to allocate	      */

{	/*** init_pool ***/
					/********   LOCAL  VARIABLES   ********/
register POOL_DEF *tptr;		/* temporary pool node structure ptr  */
register ULONG	  status;		/* return code status		      */
static	 long	  num_byte;		/* number of bytes to allocate	      */


   /* allocate memory for the pool node structure first */

   num_byte = sizeof(POOL_DEF);
   status = LIB$GET_VM(&num_byte,&tptr);

   if(status != SS$_NORMAL)
      LIB$STOP(status);

   /* allocate memory for the first pool and initialize all of the other
    * accounting stuff associated with it
    */

   num_byte = pool_length;		/* set up size of pool to allocate    */
   status = LIB$GET_VM(&num_byte,&(tptr->first));

   if(status != SS$_NORMAL)
      LIB$STOP(status);

   /* set up the other stuff for the first memory pool */

   tptr->ptr = tptr->first;		/* set pointer to available memory    */
   tptr->remaining = num_byte;		/* all bytes in pool available	      */
   tptr->next_pool = NULL;		/* terminate the linked list....      */
   tptr->length = pool_length;		/* save original length of pool	      */
   *pool = tptr;			/* start everything....		      */

   return;

}	/*** init_pool ***/
/*eject*/
/*******************************************************************************
********************************************************************************

  Function:	reset_pool

  Purpose:	reset the pool pointers so that the memory allocated for them
		can be used again without reallocating the memory for them.

  Global variables:

	Name			Examine/Modify/Use/Read/Write
	----			-----------------------------
	none

  Return Codes:

	Code			Reason
	----			------
	none

********************************************************************************
*******************************************************************************/

void reset_pool(pool)
					/*******   FORMAL  PARAMETERS   *******/
register POOL_DEF *pool;		/* memory pool node structures	      */

{	/*** reset_pool ***/                 

   while(pool != NULL)
   {
      pool->ptr = pool->first;		/* reset available byte pointer	      */
      pool->remaining = pool->length;	/* reset number of bytes available    */
      pool = pool->next_pool;		/* go to next pool node structure     */
   }

   return;

}	/*** reset_pool ***/
/*eject*/
/*******************************************************************************
********************************************************************************

  Function:	new_pool

  Purpose:	allocate memory for a pool node structure and the actual
		memory for the pool; also link the pool to the pool list.

  Global variables:

	Name			Examine/Modify/Use/Read/Write
	----			-----------------------------
	none

  Return Codes:

	Code			Reason
	----			------
	none

  Termination Codes:

	Code			Reason
	----			------
	status			LIB$GET_VM() failed

********************************************************************************
*******************************************************************************/

POOL_DEF *new_pool(pool,pool_length)
					/*******   FORMAL  PARAMETERS   *******/
register POOL_DEF *pool;		/* pointer to current memory pool     */
	 long	  pool_length;		/* length of pool to allocate	      */

{	/*** new_pool ***/
					/********   LOCAL  VARIABLES   ********/
register POOL_DEF *tptr;		/* temporary pool pointer	      */
register ULONG	  status;		/* return code status		      */
	 ULONG	  num_byte;		/* number of bytes to allocate	      */


   /* first see if there is a pool already out there; if there is,
    * just return so that we don't have to reuse the memory
    */

   if(pool->next_pool != NULL)		/* is there a pool out there?	      */
      return(pool->next_pool);		/* yes, just return a pointer to it   */

   /* at this point we have conceded that a new memory pool is needed; allocate
    * the structure to hold the new pool's accounting information
    */

   num_byte = sizeof(POOL_DEF);		/* allocate enough for a structure    */

   status = LIB$GET_VM(&num_byte,&tptr);

   if(status != SS$_NORMAL)
      LIB$STOP(status);

   pool->next_pool = tptr;		/* link to current pool		      */
   pool = pool->next_pool;		/* move to new pool structure	      */

   /* now allocate the actual memory for the pool */

   num_byte = pool_length;		/* set length of pool to allocate     */

   status = LIB$GET_VM(&num_byte,&tptr);

   if(status != SS$_NORMAL)
      LIB$STOP(status);

   pool->next_pool = NULL;		/* terminate pool list		      */
   pool->ptr = tptr;			/* set available memory pointer	      */
   pool->first = tptr;			/* save beginning of pool	      */
   pool->remaining = pool_length;	/* set number of bytes available      */
   pool->length = pool_length;		/* save original pool length	      */

   return(pool);			/* return pointer to new pool struct. */

}	/*** new_pool ***/
/*eject*/
/*******************************************************************************
********************************************************************************

  Function:	put_pool

  Purpose:	Copy a string to a memory pool.  If the current pool does not
		have enough memory to accomodate the string, allocate a new
		pool first and then copy the string to the new pool.

  Global variables:

	Name			Examine/Modify/Use/Read/Write
	----			-----------------------------
	none

  Return Codes:

	Code			Reason
	----			------
	none

********************************************************************************
*******************************************************************************/

void put_pool(dest,pool,str,length,pool_length)
					/*******   FORMAL  PARAMETERS   *******/
	 char	  **dest;		/* where to store pointer to str      */
	 POOL_DEF **pool;		/* current memory pool structure      */
	 char	  *str;			/* string to be stored in memory pool */
register long	  length;		/* length of string to be stored      */
	 long	  pool_length;		/* length of pool if we need another  */
                
{	/*** put_pool ***/
					/********   LOCAL  VARIABLES   ********/
register POOL_DEF *tptr;		/* temporary pool structure pointer   */


   if((*pool)->remaining < (length + 1)) /* has the pool run dry....?	      */
   {
      *pool = new_pool(*pool,pool_length); /* yes, get another pool	      */
   }

   tptr = *pool;			/* set up temporary pool struct. ptr. */
   strcpy(tptr->ptr,str);		/* copy the string to the pool	      */
   *dest = tptr->ptr;			/* set the pointer to the string      */
   tptr->ptr = tptr->ptr + length + 1;	/* update available memory pointer    */
   tptr->remaining = tptr->remaining - (length + 1);

   return;

}	/*** put_pool ***/
/*eject*/
/*******************************************************************************
********************************************************************************

  Function:	get_pool_mem

  Purpose:	Return a pointer to a memory slot that will satisfy the request.

  Global variables:

	Name			Examine/Modify/Use/Read/Write
	----			-----------------------------
	none

  Return Codes:

	Code			Reason
	----			------
	retptr			pointer to memory slot

********************************************************************************
*******************************************************************************/

char *get_pool_mem(pool,pool_length,length)
					/*******   FORMAL  PARAMETERS   *******/
register POOL_DEF  **pool;		/* current memory pool		      */
	 long	  pool_length,		/* length of pool to request	      */
		  length;		/* length of memory slot needed	      */

{	/*** get_pool_mem ***/
					/********   LOCAL  VARIABLES   ********/
register char	  *retptr;		/* pointer to return		      */
register POOL_DEF *tptr;		/* used to speed things up a bit....  */
static	 short	  count;

   /* is there enough room in the current memory pool?  If not, allocate
    * another one and get the memory from there
    */

   count++;
   if((*pool)->remaining < (length + 1))
      *pool = new_pool(*pool,pool_length);

   tptr = *pool;			/* make a pointer		      */

   /* we have the memory, whether it is in a new pool or the one we had on
    * entry; update the pool structure to reflect the memory that we need
    * to take from it
    */

   retptr = tptr->ptr;			/* save pointer to memory slot	      */
   tptr->ptr = tptr->ptr + length + 1L;
   tptr->remaining = tptr->remaining - (length + 1);
   
   return(retptr);			/* return pointer to slot	      */

}	/*** get_pool_mem ***/
/*eject*/
/*******************************************************************************
********************************************************************************

  Function:	free_pool

  Purpose:	Free the memory of all of the memory pools for a directory,
		including the node structures for all of the nodes.  This
		routine will be called when exiting a directory.

  Global variables:

	Name			Examine/Modify/Use/Read/Write
	----			-----------------------------
	none

  Return Codes:

	Code 			Reason
	----			------
	none

  Termination Codes:

	Code			Reason
	----			------
	status			LIB$FREE_VM() failed

********************************************************************************
*******************************************************************************/

void free_pool(pool)
					/*******   FORMAL  PARAMETERS   *******/
register POOL_DEF *pool;		/* first pool in pool node list	      */

{	/*** free_pool ***/
					/********   LOCAL  VARIABLES   ********/
register POOL_DEF *tpool;		/* temporary pool node pointer	      */
register ULONG	  status;		/* return code status holder	      */
	 char	  *tptr;		/* temporary character pointer	      */
	 long	  tlength;		/* temporary length value	      */



   while(pool != NULL)
   {
      /* free the memory for the pool first */

      tptr = pool->first;		/* get pointer to first byte in pool  */
      tlength = pool->length;		/* get original length of pool	      */

      status = LIB$FREE_VM(&tlength,&tptr);
      if(status != SS$_NORMAL)
	 LIB$STOP(status);

      tpool = pool->next_pool;		/* save pointer to next pool node     */

      /* now free up the memory for the pool node structure */

      tptr = pool;			/* get pointer to pool node structure */
      tlength = (long) sizeof(POOL_DEF);/* get length of node structures      */

      status = LIB$FREE_VM(&tlength,&tptr);
      if(status != SS$_NORMAL)
	 LIB$STOP(status);

      pool = tpool;			/* move to next pool in list	      */
   }

   return;

}	/*** free_pool ***/
/*eject*/
/*******************************************************************************
********************************************************************************

  Function:	clear_mess

  Purpose:	Clear an informational message on the screen

  Global variables:

	Name			Examine/Modify/Use/Read/Write
	----			-----------------------------
	none

  Return Codes:
             
	Code			Reason
	----			------
	none

********************************************************************************
*******************************************************************************/

void clear_mess(disp_id,scr_width)
					/*******   FORMAL  PARAMETERS   *******/
	 long	  disp_id;		/* display of where to clear message  */
	 short	  scr_width;		/* width of screen display	      */

{	/*** clear_mess ***/
					/********   LOCAL  VARIABLES   ********/
    	 ULONG	  status;		/* return code status holder	      */
static	 long	  start_row = MAIN_ROWS, /* what row to start in 	      */
		  start_col = 1L,	/* what column to start in	      */
		  end_row = MAIN_ROWS,	/* what row to end in		      */
		  end_col;		/* what column to end in	      */


   end_col = (long) scr_width;

   /* clear out the message */

   status = SMG$ERASE_DISPLAY(&disp_id,&start_row,&start_col,&end_row,
			      &end_col);

   return;

}	/*** clear_mess ***/
/*eject*/
/*******************************************************************************
********************************************************************************

  Function:	prot_val_to_str

  Purpose:	Create the string representing the file protection for a
		specific file from the integer value for the protection.
		This function perhaps could be made general-purpose and put
		in the WMUCRTL library at a later date.

  Global variables:

	Name			Examine/Modify/Use/Read/Write
	----			-----------------------------
	none

  Return Codes:

	Code			Reason
	----	 		------
	retval			number of character in protection string

********************************************************************************
*******************************************************************************/

short prot_val_to_str(prot,str)
					/*******   FORMAL  PARAMETERS   *******/
register USHORT	  prot;			/* protection word value	      */
register char	  *str;			/* return protection string value     */

{	/*** prot_val_to_str ***/
					/********   LOCAL  VARIABLES   ********/
register char	  *save;		/* original str value		      */
static	 short	  retval;		/* value to return		      */


   save = str;				/* save where we started from	      */

   if(!(prot & SYSTEM_READ))
      *str++ = 'R';
   if(!(prot & SYSTEM_WRITE))
      *str++ = 'W';
   if(!(prot & SYSTEM_EXECUTE))
      *str++ = 'E';
   if(!(prot & SYSTEM_DELETE))
      *str++ = 'D';

   *str++ = ',';			/* insert the comma		      */

   if(!(prot & OWNER_READ))
      *str++ = 'R';
   if(!(prot & OWNER_WRITE))
      *str++ = 'W';
   if(!(prot & OWNER_EXECUTE))
      *str++ = 'E';
   if(!(prot & OWNER_DELETE))
      *str++ = 'D';

   *str++ = ',';			/* insert the comma		      */

   if(!(prot & GROUP_READ))
      *str++ = 'R';
   if(!(prot & GROUP_WRITE))
      *str++ = 'W';
   if(!(prot & GROUP_EXECUTE))
      *str++ = 'E';
   if(!(prot & GROUP_DELETE))
      *str++ = 'D';

   *str++ = ',';			/* insert the comma		      */

   if(!(prot & WORLD_READ))
      *str++ = 'R';
   if(!(prot & WORLD_WRITE))
      *str++ = 'W';
   if(!(prot & WORLD_EXECUTE))
      *str++ = 'E';
   if(!(prot & WORLD_DELETE))
      *str++ = 'D';

   /* calculate how many characters we put in */

   retval = str - save;			/* calculate length of string	      */
   save += PROT_MAX;			/* get where we should end up	      */

   /* pad the rest of the string with spaces */

   while(str < save)
      *str++ = ' ';

   *str = '\0';				/* terminate things		      */

   return(retval);

}	/*** prot_val_to_str ***/
/*eject*/
/*******************************************************************************
********************************************************************************

  Function:  	get_string

  Purpose:  	Prompt for and read in a string from the display.

  Global variables:
                          
	Name			Examine/Modify/Use/Read/Write
	----			-----------------------------
	none

  Return Codes:

	Code			Reason
	----			------
	status			return code from SMG$READ_STRING

  Termination Codes:

	Code			Reason
	----			------
	status			return code from SMG$SET_CURSOR_ABS

********************************************************************************
*******************************************************************************/

ULONG get_string(keybd_id,disp_id,max_length,response,prompt,scr_width)
	     				/*******   FORMAL  PARAMETERS   *******/
	 long	  keybd_id,		/* keyboard id			      */
		  disp_id,		/* display id for prompting	      */
		  max_length;		/* max. length of response	      */
	 char	  *response,		/* where to put response	      */
		  *prompt;		/* prompt to use		      */
	 short	  scr_width;		/* width of screen display	      */

{	/*** get_string ***/
					/********   LOCAL  VARIABLES   ********/
	 ULONG	  status;		/* return code status holder	      */
	 USHORT	  length;		/* length of string read in	      */
static $DESCRIPTOR(prompt_d,"");	/* descriptor for prompt	      */
static $DESCRIPTOR(response_d,"");	/* descriptor for response	      */


   prompt_d.dsc$a_pointer = prompt;	/* set up prompt descriptor	      */
   prompt_d.dsc$w_length = (USHORT) strlen(prompt);
   response_d.dsc$a_pointer = response;
   response_d.dsc$w_length = (USHORT) max_length;

   status = SMG$SET_CURSOR_ABS(&disp_id,&MAIN_ROWS,&1L);

   if(status != SS$_NORMAL)
      LIB$STOP(status);

   /* prompt for and read in the response */
             
   status = SMG$READ_STRING(&keybd_id,&response_d,&prompt_d,&max_length,0,0,0,
			    &length,0,&disp_id,0,&SMG$M_BOLD,0);

   if(status != SS$_NORMAL)
      return(status);

   clear_mess(disp_id,scr_width);	/* clear out the message	      */
   *(response + length) = '\0';		/* make the response a string	      */

   return(status);

}	/*** get_string ***/
/*eject*/
/*******************************************************************************
********************************************************************************

  Function:	prot_str_to_val

  Purpose:	Given a VMS protection string and the original integer value
		for a file's protection, edit the string and if it is valid,
		calculate the new protection integer value.

  Global variables:

	Name			Examine/Modify/Use/Read/Write
	----			-----------------------------
	none

  Return Codes:

	Code			Reason
	----			------
	  0			Everything fine
	 -1			Invalid protection string

********************************************************************************
*******************************************************************************/

static short prot_str_to_val(str,new_val,prot_val)
					/*******   FORMAL  PARAMETERS   *******/
register char	  *str;			/* protection string		      */
	 short	  *new_val,		/* calculated protection value	      */
	 	  prot_val;		/* original protection value	      */

{	/*** prot_str_to_val ***/
					/********   LOCAL  VARIABLES   ********/
static	 char	  *tptr,		/* temporary character pointer....    */
		  *tptr2;		/* ...used for editing protection str */
static	 int	  temp;			/* temporary value		      */
static	 USHORT	  mask;			/* mask value for constructing value  */
static	 UCHAR	  sys_flag,		/* flag for SYSTEM field processed    */
		  own_flag,		/*  "    "  OWNER    "       "	      */
		  grp_flag,		/*  "    "  GROUP    "	     "	      */
		  wld_flag,		/*  "    "  WORLD    "       "	      */
		  r_flag,		/* flag for READ subfield processed   */
		  w_flag,		/*   "   "  WRITE   "	      "	      */
		  e_flag,		/*   "   "  EXECUTE "	      "	      */
		  d_flag,		/*   "   "  DELETE  "	      "	      */
		  open_flag,		/* flag set for dangling commas	      */
		  paren_flag,		/* for checking balanced parentheses  */
		  ch;			/* temporary character holder	      */


   /* first make sure the string is all upper case with no spaces in it */

   tptr = str;				/* make a copy to use		      */
   tptr2 = str;				/* need second copy to squeeze it     */

   while(*tptr)
   {
      if((*tptr != ' ') && (*tptr != 11)) /* skip over spaces and tabs	      */
      {
	 *tptr2 = _toupper(*tptr);
	 tptr2++;
      }
      tptr++;
   }

   *tptr2 = '\0';			/* make sure it is still a string     */

   if(*str == '(')			/* string enclosed in parens?	      */
   {
      str++;				/* skip it			      */
      paren_flag = 1;			/* yes, remember for later......      */
   }
   else
      paren_flag = 0;			/* nope, clear it		      */

   sys_flag = own_flag = grp_flag = wld_flag = 0;

   while(*str && *str != ')')
   {
      /* get the delineating character for the field designator, whether
       * it be a comma or a colon
       */

      open_flag = 0;			/* no dangling comma yet	      */
      temp = banystr(str,",:");

      /* now terminate it temporarily at the delineator so we can see
       * what field was specified
       */

      if(temp >= 0)
      {
	 ch = *(str+temp);		/* save the character for a while     */
	 *(str+temp) = '\0';		/* terminate it there		      */
      }
      else
      {
	 /* we must lie here and pretend we have a colon to fool the upcoming
	  * test
	  */

	 ch = ':';
	 temp = 0;			/* also lie about the offset	      */
      }

      /* see what subfield we are dealing with */

      if(strindex("SYSTEM",str) == 0)
      {
	 if(!sys_flag)
	 {
	    sys_flag++;			/* only do SYSTEM once		      */
	    prot_val = prot_val | 0xF;
	    mask = 0x1;
	 }
	 else
	    return(-1);			/* error; field specified twice	      */
      }
      else if(strindex("OWNER",str) == 0)
      {
	 if(!own_flag)
	 {
	    own_flag++;			/* only do OWNER once		      */
	    prot_val = prot_val | 0xF0;
	    mask = 0x10;
	 }
	 else
	    return(-1);
      }
      else if(strindex("GROUP",str) == 0)
      {
	 if(!grp_flag)
	 {
	    grp_flag++;			/* only do GROUP once		      */
	    prot_val = prot_val | 0xF00;
	    mask = 0x100;
	 }
	 else
	    return(-1);
      }
      else if(strindex("WORLD",str) == 0)
      {
	 if(!wld_flag)
	 {
	    wld_flag++;			/* only do WORLD once		      */
	    prot_val = prot_val | 0xF000;
	    mask = 0x1000;
	 }
	 else
	    return(-1);
      }
      else
      {
	 /* invalid field specifier means error....... */

	 return(-1);
      }

      /* move the pointer to one character past the delineator character
       * if it is a colon; if it is a comma, put it on the comma so that
       * the loop will stop anything from being processed
       */

      str = str + (ULONG) temp;

      if(ch == ':')
	 str++;				/* skip the colon if there is one     */
      else
	 *str = ch;			/* else restore the comma	      */

      r_flag = w_flag = e_flag = d_flag = 0;

      /* process all parts of the field */

      while(*str && *str != ',' && *str != ')')
      {
	 switch(*str)
	 {
	    case('R'):

	       if(r_flag)
		  return(-1);		/* error, two READs for field	      */

	       prot_val = prot_val ^ mask;
	       r_flag++;		/* only process READ once	      */
	       break;

	    case('W'):

	       if(w_flag)
		  return(-1);		/* error, two WRITEs for field	      */

	       prot_val = prot_val ^ (mask << 1);
	       w_flag++;		/* only process WRITE once	      */
	       break;

	    case('E'):

	       if(e_flag)
		  return(-1);		/* error, two EXECUTEs for field      */

	       prot_val = prot_val ^ (mask << 2);
	       e_flag++;		/* only process EXECUTE once	      */
	       break;

	    case('D'):

	       if(d_flag)
		  return(-1);		/* error, two DELETEs for field	      */

	       prot_val = prot_val ^ (mask << 3);
	       d_flag++;		/* only process DELETE once	      */
	       break;

	    default:			/* should never get here	      */
	       return(-1);		/* but it is an error if we do....    */
	 }
	 str++;				/* go to next character in string     */
      }

      if(*str == ',')			/* if we hit a comma, skip over it    */
      {
	 str++;
	 open_flag++;			/* last char was a comma	      */
      }
   }

   if(paren_flag && *str != ')')
      return(-1);			/* no right parenthesis		      */

   if(!paren_flag && *str == ')')
      return(-1);			/* no left parenthesis		      */

   if(open_flag)			/* was there a dangling comma?	      */
      return(-1);			/* yup, error........		      */

   *new_val = prot_val;			/* send it back			      */
   return(0);

}	/*** prot_str_to_val ***/
/*eject*/
/*******************************************************************************
********************************************************************************

  Function:	make_slot

  Purpose:	Make a slot to be displayed on the screen for a file depending
		on the (possible) commands associated with the file.  In
		some cases, some funny stuff will be done with the screen
		slot to connotate that the file has some commands associated
		with it.

  Global variables:

	Name			Examine/Modify/Use/Read/Write
	----			-----------------------------
	none

  Return Codes:

	Code			Reason
	----			------
	  0			normal slot
	  1			file has commands; highlite it

********************************************************************************
*******************************************************************************/

short make_slot(buf,ent,args,slot_width,text_flag)
			 		/*******   FORMAL  PARAMETERS   *******/
	 char	  *buf;			/* where to put the screen slot	      */
register ENT_DEF  *ent;			/* file entry pointer		      */
register ARG_DEF  *args;		/* run-time arguments		      */
	 short	  slot_width,		/* width of a screen slot	      */
		  text_flag;		/* whether we display text descrips   */

{	/*** make_slot ***/
					/********   LOCAL  VARIABLES   ********/
	 short	  spot = TEXT_MAX;	/* where to terminate the blank text  */
static	 COM_DEF  *ptr;			/* pointer to possible command struct */
static	 char	  *prot_ptr,		/* pointer to the protection string   */
		  *text_ptr;		/* pointer to text descriptor	      */
static	 char	  prot_buf[PROT_MAX+1],	/* for making protection strings      */
		  text_buf[TEXT_MAX*2+1], /* for making text descriptors      */
		  blnk_text[] = 	/* blank text descriptor	      */
"                                        ";


   /* now see if there are commands associated with the file; we do things
    * a little differently if there are
    */

   if(ent->command == NULL)		/* are there commands for this file?  */
   {
      /* no commands associated with this file; just use the information
       * that came with it to create the slot; first see if we need to
       * include text descriptors with the slot
       */

      if(text_flag == DISPLAY_TEXT)	/* do we display the text descrips?   */
      {
	 /* yes, they don't overflow the file slots; now see if there is one
	  * for the current file
	  */

	 if(ent->text != NULL)		/* is there a text descriptor?	      */
	 {
	    /* there IS a text descriptor for this file; pad the text descriptor
	     * on the right with spaces so that if there was a descriptor on the
	     * screen in this slot (perhaps from a previous page) it will be
	     * completely overwritten by the current one
	     */

	    cat(text_buf,ent->text,blnk_text);
	    text_buf[TEXT_MAX] = '\0';	/* only use as much as we need	      */
	    text_ptr = text_buf;	/* set the pointer to the text desc.  */
	 }
	 else
	    text_ptr = blnk_text;	/* use a blank text descriptor	      */
	 }
      else
	 text_ptr = nullptr;		/* no text descriptor displayed	      */

      /* no commands for the file; just make the entry normally */

      if(args->size)			/* should the size be included?	      */
      {
	 /* no; there is a special case for the size because it is
	  * used as an integer instead as a string like the protection
	  * string; if the user did not want the date or the protection
	  * string, they will have null characters ('\0') in them and thus
	  * won't effect the sprintf() call; however, we can't make sprintf
	  * ignore integers even though they might contain 0
	  */

	 sprintf(buf,"%s %6d %s %s %s",ent->scr_name,ent->size,ent->date_str,
		 ent->prot_str,text_ptr);
      }
      else
      {
	 sprintf(buf,"%s %s %s %s",ent->scr_name,ent->date_str,ent->prot_str,
		 text_ptr);
      }

      return(0);			/* return from here		      */
   }
   else
   {
      /* there are some commands associated with this file; the slot
       * should reflect this and it should be written in reverse video
       */

      ptr = ent->command;		/* get pointer to command structure   */

      if(ptr->comm_del)			/* is the file going to be deleted?   */
      {
	 /* yes, now see if there is enough room to put in the "<delete>"
	  * string
	  */

	 if(slot_width <= (NAME_MAX + SIZE_MAX + 2))
	 {
	    /* the slot is only big enough to use <del> to signify that the
	     * file is marked for deletion
	     */

	    sprintf(buf,"%-14.14s <del>       ",ent->scr_name);
	 }
	 else
	 {
	    /* use the regular "<delete>" string to show that the user
	     * marked the file to be deleted
	     */

	    sprintf(buf,"%s <delete>                                         \
                                    ",ent->scr_name);
	 }

	 *(buf + slot_width) = '\0';	/* trim it to where it should be      */
      }
      else
      {
	 /* no the file is not going to be deleted but it might have some other
	  * command associated with it; check them to be sure
	  */

	 if(args->prot)
	 {
	    if(ptr->comm_prot)
	    {
	       /* new protection string; format it */

	       prot_val_to_str(ptr->prot,prot_buf);
	       prot_ptr = prot_buf;
	    }
	    else
	       prot_ptr = ent->prot_str; /* use the old protection string     */
	 }
	 else
	    prot_ptr = nullptr;		/* no string; make sure it is null    */

	 /* which text descriptor pointer should we use? */

	 if(text_flag == DISPLAY_TEXT)	/* should we display text descriptor? */
	 {
	    /* yes, text descriptors are to be displayed on the screen; now
	     * determine which, if any, text descriptor to use for the
	     * current file
	     */

	    if(ptr->text != NULL)	/* use new text descriptor?	      */
	    {
	       /* use the new text descriptor for the slot */

	       cat(text_buf,ptr->text,blnk_text);
	       text_buf[TEXT_MAX] = '\0'; /* only use as much as we need      */
	       text_ptr = text_buf;	/* set the pointer to the text desc.  */
	    }
	    else if(ent->text != nullptr) /* or should we use the original?   */
	    {
	       /* use the original text descriptor for the file */

	       cat(text_buf,ent->text,blnk_text);
	       text_buf[TEXT_MAX] = '\0'; /* only use as much as we need      */
	       text_ptr = text_buf;	/* set the pointer to the text desc.  */
	    }
	    else			/* or should we use a blank desc?     */
	    {
	       /* this file has no text descriptor; use a blank one to make
		* sure that we completely write over any text descriptor that
		* might been written in the current slot before this
		*/

	       text_ptr = blnk_text;	/* use a blank text descriptor	      */
	    }
	 }
	 else
	    text_ptr = nullptr;		/* no text descriptor at all......    */

	 /* now make the slot, but don't include the size if we don't need it */

      	 if(args->size)		/* should the size be included?	      */
	 {
	    sprintf(buf,"%s %6d %s %s %s",ent->scr_name,ent->size,
		    ent->date_str,prot_ptr,text_ptr);
	 }
	 else
	 {
	    sprintf(buf,"%s %s %s %s",ent->scr_name,ent->date_str,
		    prot_ptr,text_ptr);
	 }
      }
   }

   return(1);

}	/*** make_slot ***/
/*eject*/
/*******************************************************************************
********************************************************************************

  Function:	highlite

  Purpose:	Rewrite a slot entry for a file, highlighting it if it is
		necessary.  It will be highlighted if there are commands
		associated with the file entry.

  Global variables:

	Name			Examine/Modify/Use/Read/Write
	----			-----------------------------
	none

  Return Codes:

	Code			Reason
	----			------
	none

********************************************************************************
*******************************************************************************/

void highlite(disp_id,buf,ent,args,row,col,slot_width,text_flag)
					/*******   FORMAL  PARAMETERS   *******/
	 long	  disp_id;		/* display id of where to write	      */
	 char	  *buf;			/* where to put the screen slot	      */
register ENT_DEF  *ent;			/* file entry pointer		      */
register ARG_DEF  *args;		/* run-time arguments		      */
	 long	  row,			/* column to write it at	      */
		  col;			/* row to write it at		      */
	 short	  slot_width,		/* width of screen slot		      */
		  text_flag;		/* whether we display text descrips   */

{	/*** highlite ***/
					/********   LOCAL  VARIABLES   ********/
	 ULONG	  status;		/* return code status holder	      */
static	 long	  rend_set;		/* rendition setting for slot entry   */
static $DESCRIPTOR(slot_d,"");		/* descriptor for writing the slot    */


   /* do we need bold or normal? */

   if(make_slot(buf,ent,args,slot_width,text_flag) == 1)
      rend_set = SMG$M_BOLD;
   else
      rend_set = SMG$M_NORMAL;

   slot_d.dsc$a_pointer = buf;		/* set up the descriptor for writing  */
   slot_d.dsc$w_length = (USHORT) strlen(buf);

   status = SMG$PUT_CHARS(&disp_id,&slot_d,&row,&col,0,&rend_set,0,0);

   return;

}	/*** highlite ***/
/*eject*/
/*******************************************************************************
********************************************************************************

  Function:	expand

  Purpose:	Expand a file entry by displaying all the information about
		the file that the user could possibly want.  Basically, all
		of the information from a DIR/FULL on the file is displayed,
		along with any of the commands that might be associated with
		the file.

  Global variables:

	Name			Examine/Modify/Use/Read/Write
	----			-----------------------------
	none

  Return Codes:

	Code			Reason
	----			------
	  0			completed successfully
	CANT_DISPLAY		can't get file attributes to display

********************************************************************************
*******************************************************************************/

short expand(ent,args,disp_id,paste_id,keybd_id,scr_width)
					/*******   FORMAL  PARAMETERS   *******/
register ENT_DEF  *ent;			/* file entry pointer		      */
	 ARG_DEF  *args;		/* run-time arguments		      */
	 ULONG	  disp_id,		/* display id to write to	      */
		  paste_id,		/* pasteboard id		      */
		  keybd_id;		/* keyboard id to read from	      */
	 short	  scr_width;		/* width of screen		      */

{	/*** expand ***/
					/********   LOCAL  VARIABLES   ********/
	 COM_DEF  *comm_ptr;		/* pointer to file commands for file  */
	 UIC_DEF  *uic;			/* for splitting uic into owner/group */
	 ULONG	  status;		/* return code status		      */
	 long	  row = 1;		/* what row is being written to	      */
	 USHORT	  timelen,		/* length of date/time string	      */
		  seq_flag = 0,		/* flag set when file is sequential   */
		  term_code,		/* character read from keyboard	      */
		  temp0,		/* temporary variable #1	      */
		  temp1,		/*    "          "    #2	      */
		  temp2,		/*    "		 "    #3	      */
		  temp3,		/*    "		 "    #4	      */
		  *id_ptr;		/* pointer to file id values	      */
static	 DATE_DEF *date_ptr;		/* for checking date values	      */
static	 short	  pass;			/* whether we have been here before   */
static	 UCHAR	  acl_buf[ACLBUF_MAX];	/* for holding ACL in binary	      */
static	 char	  buf[SCR_MAX*2],	/* formatting buffer		      */
		  buf2[SCR_MAX+1],	/* another formatting buffer	      */
	     	  date1[DATE_TIME_MAX+1], /* for formatting date/time string  */
   		  date2[DATE_TIME_MAX+1]; /* ditto....			      */
static	 struct	  FAB     fab;		/* FAB for getting information	      */
static	 struct	  NAM     in_nam;	/* NAM  "     "         "	      */
static	 struct	  XABDAT  date_xab;	/* XAB for dates/times		      */
static	 struct	  XABPRO  prot_xab;	/* XAB for getting protection, etc.   */
static	 struct	  XABFHC  head_xab;	/* XAB for file header		      */
static	 struct	  XABSUM  sum_xab;	/* XAB for summary		      */
static	 struct	  dsc$descriptor_s
		  time_d =		/* for making date/time string	      */
{DATE_TIME_MAX,DSC$K_DTYPE_T,DSC$K_CLASS_S,NULL};
static $DESCRIPTOR(uic_d,buf2);		/* descrip for translating UIC	      */
static $DESCRIPTOR(prompt_d,"Press any key to continue                    \
                                                                           \
            ");


   if(!pass)				/* did we already initialize stuff?   */
   {
      fab = cc$rms_fab;			/* initialize all of the RMS blocks   */
      in_nam = cc$rms_nam;
      date_xab = cc$rms_xabdat;
      prot_xab = cc$rms_xabpro;
      head_xab = cc$rms_xabfhc;
      sum_xab = cc$rms_xabsum;
      fab.fab$l_nam = &in_nam;
      fab.fab$l_xab = &date_xab;
      date_xab.xab$l_nxt = &prot_xab;
      prot_xab.xab$l_nxt = &head_xab;
      head_xab.xab$l_nxt = &sum_xab;
      prot_xab.xab$l_aclbuf = acl_buf;
      prot_xab.xab$w_aclsiz = sizeof(acl_buf);
      prompt_d.dsc$a_pointer[scr_width] = '\0';
      pass = 1;				/* make sure we do this only once     */
   }

   prot_xab.xab$l_aclctx = 0L;		/* MUST be cleared EVERY TIME!	      */
   fab.fab$l_fna = ent->filename;	/* set up the filename in the FAB     */
   fab.fab$b_fns = (UCHAR) strlen(ent->filename);
   fab.fab$w_ifi = 0;			/* reset just in case......	      */

   status = SYS$OPEN(&fab);		/* open the file to get the info      */

   /* did the file disappear before we could open it?  this could happen
    * in very volatile directories where files are created and deleted
    * all the time
    */

   if(status != RMS$_NORMAL)
   {
      SYS$CLOSE(&fab);
      return(CANT_DISPLAY);
   }

   /* write the prompt to the virtual display */

   status = SMG$PUT_CHARS(&disp_id,&prompt_d,&WORK_ROWS,&1L,0,&SMG$M_REVERSE,0,
	     		  0);

   if(status != SS$_NORMAL)
   {
      SYS$CLOSE(&fab);			/* abandon ship.....!		      */
      SMG$ERASE_DISPLAY(&disp_id);
      return(CANT_DISPLAY);
   }

   /* paste the work virtual display to the screen so we have something
    * to write to
    */

   status = SMG$PASTE_VIRTUAL_DISPLAY(&disp_id,&paste_id,&3,&1,0);

   if(status != SS$_NORMAL)
      return(CANT_DISPLAY);

   /* now start creating the lines and writing them to the virtual display */

   id_ptr = &in_nam.nam$w_fid;
   temp1 = *id_ptr++;
   temp2 = *id_ptr++;
   temp3 = *id_ptr;
   sprintf(buf2,"File Id:  (%d,%d,%d)",temp1,temp2,temp3);

   /* is the filename so long that we have to write it by itself, or can
    * we add the "File Id:" stuff at the end?
    */

   if(strlen(ent->filename) + strlen(buf2) + 5 > scr_width)
   {
      /* we have to write them separately; write the filename first, letting
       * put_buf wrap it where it sees fit
       */

      put_buf(keybd_id,disp_id,ent->filename,&row,scr_width,NULL,' ');

      /* put in some tabs before writing the "File Id" line so that it
       * will decent
       */

      cat(buf,"					",buf2);
      put_buf(keybd_id,disp_id,buf,&row,scr_width,NULL,' ');
   }
   else
   {
      /* both of them will fit on one line; cat them together and send
       * the result to put_buf
       */
      cat(buf,ent->filename,"     ",buf2);
      put_buf(keybd_id,disp_id,buf,&row,scr_width,NULL,' ');
   }

   /* translate the owner uic to ASCII */

   status = SYS$IDTOASC(prot_xab.xab$l_uic,&temp0,&uic_d,0,0,0);

   if(status != SS$_NORMAL)
   {
      /* oops, something went wrong; just use the numeric values for the member
       * and group for the translated UIC
       */

      uic = &(prot_xab.xab$l_uic);
      sprintf(buf2,"%o,%o",uic->member,uic->group);
   }
   else
      buf2[temp0] = '\0';		/* use what was translated	      */

   sprintf(buf,"Size:  %d/%d			Owner: [%s]",ent->size,
	   head_xab.xab$l_hbk,buf2);

   put_buf(keybd_id,disp_id,buf,&row,scr_width,NULL,' ');

   time_d.dsc$a_pointer = date1;	/* set pointer to first date array    */
   date_ptr = &(date_xab.xab$q_cdt);	/* get a pointer to the date values   */

   if(date_ptr->left || date_ptr->right)
   {
      status = SYS$ASCTIM(&timelen,&time_d,date_ptr,0L);

      if(status != SS$_NORMAL)
      {
	 SYS$CLOSE(&fab);		/* abandon ship.....!		      */
         SMG$UNPASTE_VIRTUAL_DISPLAY(&disp_id,&paste_id);
	 SMG$ERASE_DISPLAY(&disp_id);
	 return(CANT_DISPLAY);
      }

      date1[DATE_MAX] = '\0';		/* make it a string		      */
   }
   else
      strcpy(date1,"<None specified>");

   time_d.dsc$a_pointer = date2;	/* now use the second date array      */
   date_ptr = &(date_xab.xab$q_rdt);	/* get a pointer to the date values   */

   if(date_ptr->left || date_ptr->right)
   {
      status = SYS$ASCTIM(&timelen,&time_d,date_ptr,0L);

      if(status != SS$_NORMAL)
      {
	 SYS$CLOSE(&fab);		/* abandon ship.....!		      */
         SMG$UNPASTE_VIRTUAL_DISPLAY(&disp_id,&paste_id);
	 SMG$ERASE_DISPLAY(&disp_id);
	 return(CANT_DISPLAY);
      }

      date2[DATE_MAX] = '\0';		/* make it a string, too......	      */
   }
   else
      strcpy(date2,"<None specified>");

   /* now make the line and display it */

   sprintf(buf,"Created:  %s	Revised:  %s (%d)",date1,date2,
	   date_xab.xab$w_rvn);

   put_buf(keybd_id,disp_id,buf,&row,scr_width,NULL,' ');

   time_d.dsc$a_pointer = date1;	/* set pointer to first date array    */
   date_ptr = &(date_xab.xab$q_edt);	/* get a pointer to the date values   */

   if(date_ptr->left || date_ptr->right)
   {
      status = SYS$ASCTIM(&timelen,&time_d,date_ptr,0L);
             
      if(status != SS$_NORMAL)
      {
	 SYS$CLOSE(&fab);		/* abandon ship.....!		      */
         SMG$UNPASTE_VIRTUAL_DISPLAY(&disp_id,&paste_id);
	 SMG$ERASE_DISPLAY(&disp_id);
	 return(CANT_DISPLAY);
      }

      date1[DATE_MAX] = '\0';		/* make it a string		      */
   }
   else
      strcpy(date1,"<None specified>");

   time_d.dsc$a_pointer = date2;	/* now use the second date array      */
   date_ptr = &(date_xab.xab$q_bdt);	/* get a pointer to the date values   */

   if(date_ptr->left || date_ptr->right)
   {
      status = SYS$ASCTIM(&timelen,&time_d,date_ptr,0L);

      if(status != SS$_NORMAL)
      {
	 SYS$CLOSE(&fab);		/* abandon ship.....!		      */
         SMG$UNPASTE_VIRTUAL_DISPLAY(&disp_id,&paste_id);
	 SMG$ERASE_DISPLAY(&disp_id);
	 return(CANT_DISPLAY);
      }

      date2[DATE_MAX] = '\0';
   }
   else
      strcpy(date2,"<No backup done>");

   /* now make the line and display it */

   sprintf(buf,"Expires:  %s	Backup:   %s",date1,date2);

   put_buf(keybd_id,disp_id,buf,&row,scr_width,NULL,' ');

   sprintf(buf,"File organization:  ");
       
   switch(fab.fab$b_org)
   {
      case(FAB$C_IDX):
	 strcat(buf,"Indexed");
	 break;

      case(FAB$C_REL):
	 strcat(buf,"Relative");
	 break;

      case(FAB$C_SEQ):
	 strcat(buf,"Sequential");
	 seq_flag = 1;			/* file is sequential; set a flag     */
	 break;

      default:
	 strcat(buf,"Sequential");
	 seq_flag = 1;			/* file is sequential; set a flag     */
	 break;
   }

   if(sum_xab.xab$w_pvn)
   {
      sprintf(buf2,", Prolog: %d",sum_xab.xab$w_pvn);
      strcat(buf,buf2);
   }

   if(sum_xab.xab$b_nok)
   {
      sprintf(buf2,", Using %d key(s)",sum_xab.xab$b_nok);
      strcat(buf,buf2);
   }

   put_buf(keybd_id,disp_id,buf,&row,scr_width,NULL,' ');

   sprintf(buf,"File attributes:    Allocation: %d, Extend: %d, Global \
buffer count: %d,",head_xab.xab$l_hbk,head_xab.xab$w_dxq,head_xab.xab$w_gbc);

   put_buf(keybd_id,disp_id,buf,&row,scr_width,NULL,' ');

   sprintf(buf,"                    Version limit: %d",head_xab.xab$w_verlimit);

   if(fab.fab$l_fop & FAB$M_CTG)
      strcat(buf,", Contiguous");

   if(fab.fab$l_fop & FAB$M_CBT)
      strcat(buf,", Contiguous best try");

   if(fab.fab$l_fop & FAB$M_TEF)
      strcat(buf,", Truncate");

   if(strindex(ent->filename,".DIR;") >= 0)
      strcat(buf,", Directory file");

   put_buf(keybd_id,disp_id,buf,&row,scr_width,NULL,' ');
   buf[0] = '\0';			/* clear in case we don't use it      */

   switch(fab.fab$b_rfm)		/* determine record format	      */
   {
      case(FAB$C_FIX):
	 sprintf(buf,"Record format:      Fixed length %d byte records",
		 fab.fab$w_mrs);
	 break;

      case(FAB$C_STMCR):
	 sprintf(buf,"Record format:      Stream_CR");
	 break;

      case(FAB$C_STM):
	 sprintf(buf,"Record format:      Stream");
	 break;

      case(FAB$C_STMLF):
	 sprintf(buf,"Record format:      Stream_LF");
	 break;

      case(FAB$C_UDF):

	 if(head_xab.xab$w_lrl)
	    sprintf(buf,"Record format:      Undefined, maximum %d bytes",
		    head_xab.xab$w_lrl);
	 else
	    sprintf(buf,"Record format:      Undefined");

	 break;

      case(FAB$C_VAR):

	 if(head_xab.xab$w_lrl)
	    sprintf(buf,"Record format:      Variable length, maximum %d bytes",
		    head_xab.xab$w_lrl);
	 else
	    sprintf(buf,"Record format:      Variable length");

	 break;

      default:
	 break;
   }

   put_buf(keybd_id,disp_id,buf,&row,scr_width,NULL,' ');

   sprintf(buf,"Record attributes:  ");

   if(fab.fab$b_rat & FAB$M_CR)
      strcat(buf,"Carriage return carriage control");
   else if(fab.fab$b_rat & FAB$M_FTN)
      strcat(buf,"FORTRAN carriage control ");
   else if(fab.fab$b_rat & FAB$M_PRN)
      strcat(buf,"Print carriage control ");
   else
   {
      if(fab.fab$b_rat != 0)
      {
	 strcat(buf,"No carriage control");
	 if(seq_flag)
	 {
	    if(fab.fab$b_rat & FAB$M_BLK)
	       strcat(buf,", Non-spanned");
	    else
	       strcat(buf,", Spanned");
	 }
      }
     else
	 strcat(buf,"None");
   }

   put_buf(keybd_id,disp_id,buf,&row,scr_width,NULL,' ');

   exp_prot_str(buf2,ent->prot);
   sprintf(buf,"File protection:    %s",buf2);

   put_buf(keybd_id,disp_id,buf,&row,scr_width,NULL,' ');

   if(prot_xab.xab$w_acllen != 0)	/* is there an ACL to display?	      */
   {
      /* format and display the ACL for the current file */

      if(put_acl(keybd_id,&fab,&prot_xab,acl_buf,prot_xab.xab$w_acllen,&row,
	      disp_id,scr_width) != 0)
	 return(CANT_DISPLAY);
   }
   else
   {
      /* no ACL here; just say "None" (or is that "Just say NO"?  oh, right,
       * that's for drugs.......)
       */

      put_buf(keybd_id,disp_id,"Access Cntrl List:  None",&row,scr_width,NULL,
	      ' ');
   }

   if(args->text)			/* does the user want text descrips?  */
   {
      if(*ent->text == '\0')		/* existing text descriptor?	      */
      {
	 put_buf(keybd_id,disp_id,"Text descriptor:    None",&row,scr_width,
	      	 NULL,' ');
      }
      else
      {
	 sprintf(buf,"Text descriptor:    %s",ent->text);
	 put_buf(keybd_id,disp_id,buf,&row,scr_width,NULL,' ');
      }
   }

   /* now display any command that might be associated with the file */

   sprintf(buf,"File Commands:");
   row++;				/* skip a line before writing	      */

   put_buf(keybd_id,disp_id,buf,&row,scr_width,NULL,' ');

   comm_ptr = ent->command;		/* get pointer to commands	      */

   if(comm_ptr != NULL)			/* are there any commands?	      */
   {
      /* yes, display them...... */

      if(comm_ptr->comm_copy)
      {
	 /* display the filename specified for copying */

	 sprintf(buf,"   Copy to:  %s",comm_ptr->copy_name);
	 buf[scr_width] = '\0';		/* truncate it if it is too long      */
	 put_buf(keybd_id,disp_id,buf,&row,scr_width,NULL,' ');
      }

      if(comm_ptr->comm_ren)
      {
	 /* display the filename specified for renaming */

	 sprintf(buf,"   Rename to:  %s",comm_ptr->ren_name);
	 buf[scr_width] = '\0';		/* truncate it if it is too long      */
	 put_buf(keybd_id,disp_id,buf,&row,scr_width,NULL,' ');
      }

      if(comm_ptr->comm_prot)
      {
	 /* display the new protection value */

	 exp_prot_str(buf2,comm_ptr->prot);
	 sprintf(buf,"   Protection:  %s",buf2);
	 put_buf(keybd_id,disp_id,buf,&row,scr_width,NULL,' ');
      }

      if(comm_ptr->comm_text)
      {
	 /* display the text descriptor */

	 sprintf(buf,"   Text descriptor:  %s",comm_ptr->text);
	 put_buf(keybd_id,disp_id,buf,&row,scr_width,NULL,' ');
      }

      if(comm_ptr->comm_del)
      {
	 /* display the fact that this file has been marked for deletion */

	 put_buf(keybd_id,disp_id,"   Delete",&row,scr_width,NULL,' ');
      }
   }
   else
   {
      /* no commands associated with this file */

      put_buf(keybd_id,disp_id,"   None",&row,scr_width,NULL,' ');
   }
                   
   /* put the cursor at the second to last line in the first column and
    * wait
    */

   status = SMG$SET_CURSOR_ABS(&disp_id,&(WORK_ROWS-1),&1L);

   if(status != SS$_NORMAL)
   {
      SYS$CLOSE(&fab);			/* abandon ship.....!		      */
      SMG$UNPASTE_VIRTUAL_DISPLAY(&disp_id,&paste_id);
      SMG$ERASE_DISPLAY(&disp_id);
      return(CANT_DISPLAY);
   }

   /* now just wait for any character to be pressed */

   status = SMG$READ_KEYSTROKE(&keybd_id,&term_code,0,0,&disp_id,0,0);

   if((status != SS$_NORMAL) && (status != SMG$_EOF))
   {
      SYS$CLOSE(&fab);			/* abandon ship.....!		      */
      SMG$UNPASTE_VIRTUAL_DISPLAY(&disp_id,&paste_id);
      SMG$ERASE_DISPLAY(&disp_id);
      return(CANT_DISPLAY);
   }

   /* close the file */

   SYS$CLOSE(&fab);

   /* unpaste the work virtual display */

   status = SMG$UNPASTE_VIRTUAL_DISPLAY(&disp_id,&paste_id);

   if(status != SS$_NORMAL)
      return(CANT_DISPLAY);

   /* clear out the virtual display */

   status = SMG$ERASE_DISPLAY(&disp_id);

   return(0);

}	/*** expand ***/
/*eject*/
/*******************************************************************************
********************************************************************************

  Function:	xecute

  Purpose:	Execute all of the file commands for the current directory.
		Display success and failure messages to the screen for each
		of the files.

  Global variables:

	Name			Examine/Modify/Use/Read/Write
	----			-----------------------------
	none

  Return Codes:

	Code			Reason
	----			------
	none

********************************************************************************
*******************************************************************************/

void xecute(ent,args,in_buf,dir_text,disp_id,num_file,scr_width)
					/*******   FORMAL  PARAMETERS   *******/
register ENT_DEF  *ent;			/* file entry pointer		      */
	 ARG_DEF  *args;		/* run-time argument flags	      */
	 char	  *in_buf,		/* input buffer for copying	      */
		  *dir_text;		/* pointer to directory text descrip. */
	 ULONG	  disp_id;		/* display id for writing messages    */
	 short	  num_file,		/* number of files in directory	      */
		  scr_width;		/* width of screen display	      */

{	/*** xecute ***/
					/********   LOCAL  VARIABLES   ********/
register ULONG	  status;		/* return code status		      */
register COM_DEF  *comm_ptr;		/* pointer to command structure	      */
	 ULONG	  status2,		/* status code holder		      */
		  status3,		/*   "      "    "		      */
		  status4;		/*   "      "    "		      */
	 long	  size;			/* size to be deallocated	      */
	 short	  i = 0,		/* loop and array index		      */
		  bad_text = 0;		/* set if can't open text desc. file  */
static	 struct	  FAB	  in_fab,	/* input FAB			      */
			  out_fab,	/* output FAB			      */
			  text_fab;	/* text descriptor file FAB	      */
static	 struct	  RAB	  in_rab,	/* input RAB			      */
			  out_rab,	/* output RAB			      */
			  text_rab;	/* text descriptor file RAB	      */
static	 struct	  XABPRO  in_prot_xab,	/* input protection XAB		      */
			  out_prot_xab; /* output protection XAB	      */
static	 UCHAR	  pass;			/* RMS block initialization flag      */
static	 char	  buf[BUFSIZ],		/* message formatting buffer	      */
		  filename[FULL_SPEC_MAX+1];

             
   if(!pass)				/* did we already initialize stuff?   */
   {
      in_fab = cc$rms_fab;  		/* initialize all of the RMS blocks   */
      in_rab = cc$rms_rab;
      out_fab = cc$rms_fab;
      out_rab = cc$rms_rab;
      in_prot_xab = cc$rms_xabpro;
      out_prot_xab = cc$rms_xabpro;
      in_fab.fab$l_xab = &in_prot_xab;
      out_fab.fab$l_xab = &out_prot_xab;

      in_fab.fab$b_fac = FAB$M_BIO |	/* block I/O for input file	      */
			 FAB$M_GET;

      out_fab.fab$b_fac = FAB$M_BIO |	/* block I/O for output file	      */
	     		  FAB$M_PUT;

/*    out_fab.fab$l_dna = DEFAULT_FILESPEC;
      out_fab.fab$b_dns = (USHORT) strlen(DEFAULT_FILESPEC); */
      in_rab.rab$l_fab = &in_fab;	/* pointer to input FAB		      */
      in_rab.rab$b_rac = RAB$C_SEQ;	/* sequential access for copy	      */
      in_rab.rab$w_usz = BUF_MAX;	/* length of input buffer	      */
      in_rab.rab$l_ubf = in_buf;	/* pointer to buffer		      */

      out_rab.rab$l_fab = &out_fab;	/* pointer to input FAB		      */
      out_rab.rab$b_rac = RAB$C_SEQ;	/* sequential access for copy	      */
      out_rab.rab$w_usz = BUF_MAX;	/* length of output buffer	      */
      out_rab.rab$l_rbf = in_buf;	/* pointer to buffer		      */
      pass = 1;				/* make sure we do this only once     */
   }                      

   size = (long) sizeof(COM_DEF);	/* length of command structure	      */

   /* process all of the file commands for the files in the current directory */

   while(i < num_file)
   {
      if(ent->command != NULL)		/* any commands for this file entry?  */
      {
	 /* yes, now see what we need to do for this file */

	 comm_ptr = ent->command;	/* get pointer to command structure   */
	 in_fab.fab$l_fna = ent->filename;
	 in_fab.fab$b_fns = (UCHAR) strlen(ent->filename);

	 if(comm_ptr->comm_copy)	/* Copy command			      */
	 {
	    /* open the input file */

	    status = SYS$OPEN(&in_fab);

	    if(status != RMS$_NORMAL)
	    {
	       SYS$CLOSE(&in_fab);
	       sprintf(buf,"Copy %s? *ERROR*\7",ent->filename);
	       xmess(disp_id,buf,scr_width);
	       sleep(1);
	       goto lab1;
	    }

	    /* connect the input file's RMS blocks */

	    status = SYS$CONNECT(&in_rab);

	    if(status != RMS$_NORMAL)
	    {
	       SYS$CLOSE(&in_fab);
	       sprintf(buf,"Copy %s? *ERROR*\7",ent->filename);
	       xmess(disp_id,buf,scr_width);
	       sleep(1);
	       goto lab1;
	    }

	    out_fab = in_fab;		/* copy the input file's FAB	      */
	    out_fab.fab$w_ifi = 0;	/* reset this!			      */
            out_fab.fab$b_fac = FAB$M_BIO | FAB$M_PUT;
	    out_fab.fab$l_xab = &out_prot_xab;

	    /* create the filename for the output file */

	    make_name(ent->filename,comm_ptr->copy_name,filename);

	    out_fab.fab$l_fna = filename;
	    out_fab.fab$b_fns = (UCHAR) strlen(filename);

	    /* which protection value do we use? */

	    if(comm_ptr->comm_prot)
	       out_prot_xab.xab$w_pro = comm_ptr->prot;
	    else
	       out_prot_xab.xab$w_pro = in_prot_xab.xab$w_pro;

	    /* open the output file */

	    status = SYS$CREATE(&out_fab);

	    if(status != RMS$_NORMAL)
	    {
	       SYS$CLOSE(&out_fab);
	       SYS$CLOSE(&in_fab); 
	       sprintf(buf,"Copy %s? *ERROR*\7",filename);
	       xmess(disp_id,buf,scr_width);
	       sleep(1);
	       goto lab1;
	    }

	    /* connect the output file's RMS blocks */

	    status = SYS$CONNECT(&out_rab);

	    if(status != RMS$_NORMAL)
	    {
	       SYS$CLOSE(&out_fab);
	       SYS$CLOSE(&in_fab);
	       sprintf(buf,"Copy %s? *ERROR*\7",filename);
	       xmess(disp_id,buf,scr_width);
	       sleep(1);
	       goto lab1;
	    }

            /* now copy the input file to the output file */

	    status = SYS$READ(&in_rab);
	    status2 = RMS$_NORMAL;	/* have to lie to fool the loop	      */

	    while((status == RMS$_NORMAL) && (status2 == RMS$_NORMAL))
	    {
	       out_rab.rab$w_rsz = in_rab.rab$w_rsz;
	       status2 = SYS$WRITE(&out_rab);
	       status = SYS$READ(&in_rab);
	    }

	    /* close both of the files */

	    status3 = SYS$CLOSE(&in_fab);
	    status4 = SYS$CLOSE(&out_fab);

	    /* did the loop terminate properly? if not, delete the output
	     * file and scream at the user
	     */

	    if((status != RMS$_EOF) || (status2 != RMS$_NORMAL) ||
	       (status3 != RMS$_NORMAL) || (status4 != RMS$_NORMAL))
	    {
	       delete(comm_ptr->copy_name);
	       sprintf(buf,"Copy %s?  *ERROR*\7",ent->filename);
	       xmess(disp_id,buf,scr_width);
	       sleep(1);
	    }
	    else
	    {
	       sprintf(buf,"Copy %s? [OK]",ent->filename);
	       xmess(disp_id,buf,scr_width);
	    }
	 }

lab1:	 if(comm_ptr->comm_ren)		/* Rename command		      */
	 {
	    /* create the filename for the output file */

	    make_name(ent->filename,comm_ptr->ren_name,filename);

	    out_fab.fab$l_fna = filename;
	    out_fab.fab$b_fns = (UCHAR) strlen(filename);
	    out_fab.fab$w_ifi = 0;

	    status = SYS$RENAME(&in_fab,0,0,&out_fab);

	    if(status != RMS$_NORMAL)
	    {
	       sprintf(buf,"Rename %s? *ERROR*\7",ent->filename);
	       xmess(disp_id,buf,scr_width);
	       sleep(1);
	       goto lab2;		/* sorry about this......	      */
	    }

	    /* should the renamed file have a new protection? */

	    if(comm_ptr->comm_prot)
	    {
	       /* yes, reprotect the renamed file */

	       comm_ptr->comm_prot = 0;	/* don't try to protect it after this */
	       out_fab.fab$w_ifi = 0;	/* clear internal file identifier     */

	       status = SYS$OPEN(&out_fab);

	       if(status != RMS$_NORMAL)
	       {
		  SYS$CLOSE(&out_fab);
		  sprintf(buf,"File %s renamed but set protection failed\7\7",
			  ent->filename);
		  xmess(disp_id,buf,scr_width);
		  sleep(1);
		  goto lab2;
	       }

	       /* now insert the new protection before we close the file */

	       out_prot_xab.xab$w_pro = comm_ptr->prot;

	       status = SYS$CLOSE(&out_fab);

	       if(status != RMS$_NORMAL)
	       {
		  sprintf(buf,"File %s renamed but set protection failed\7\7",
			  ent->filename);
	     	  xmess(disp_id,buf,scr_width);
		  sleep(1);
	       }
	    } /* if(comm_ptr->comm_prot) */

	    sprintf(buf,"Rename %s? [OK]",ent->filename);
	    xmess(disp_id,buf,scr_width);

	    /* look to see if there was a text descriptor for the file; if
	     * there was, write to text descriptor file record to make sure
	     * the old text descriptor follows the file to its new filename
	     */

	    if((ent->text != nullptr) && (!bad_text) && (!comm_ptr->comm_text))
	    {
	       /* yes, write it to the text descriptor file */

	       make_text_rec(buf,comm_ptr->ren_name,ent->text);
	       status = write_text(&text_fab,&text_rab,buf,dir_text);

	       if(status != RMS$_NORMAL)
		  bad_text = 1;		/* do not process text descr. anymore */

	       ent->text = nullptr;	/* wipe out existing text descriptor  */
	    }
	 }

lab2:	 if(comm_ptr->comm_prot)
	 {
	    out_fab.fab$l_fna = ent->filename;
	    out_fab.fab$b_fns = (UCHAR) strlen(ent->filename);
	    out_fab.fab$w_ifi = 0;

	    status = SYS$OPEN(&out_fab);

	    if(status != RMS$_NORMAL)
	    {
	       SYS$CLOSE(&out_fab);
	       sprintf(buf,"Protect %s? *ERROR*\7",ent->filename);
	       xmess(disp_id,buf,scr_width);
	       sleep(1);
	       goto lab3;
	    }

	    status = SYS$CONNECT(&out_rab);

	    if(status != RMS$_NORMAL)
	    {
	       SYS$CLOSE(&out_fab);
	       sprintf(buf,"Protect %s? *ERROR*\7",ent->filename);
	       xmess(disp_id,buf,scr_width);
	       sleep(1);
	       goto lab3;
	    }

	    /* now insert the new protection before we close the file */

	    out_prot_xab.xab$w_pro = comm_ptr->prot;

    	    status = SYS$CLOSE(&out_fab);

	    if(status != RMS$_NORMAL)
	    {
	       sprintf(buf,"Protect %s? *ERROR*\7",ent->filename);
	       xmess(disp_id,buf,scr_width);
	       sleep(1);
	    }
	    else
	    {
	       sprintf(buf,"Protect %s? [OK]",ent->filename);
	       xmess(disp_id,buf,scr_width);
	    }
	 }

lab3:	 if((comm_ptr->comm_text) && (!bad_text))
	 {
	    /* is the text descriptor to be deleted?  if it is length zero,
	     * then it is;
	     */

	    if(strlen(comm_ptr->text) == 0)
	    {
	       ent->text = nullptr;
	       goto lab4;
	    }

	    /* now see which filename we should use; if the file is being
	     * renamed, use the new name
	     */

	    if(comm_ptr->comm_ren)
	       make_text_rec(buf,comm_ptr->ren_name,comm_ptr->text);
	    else
	       make_text_rec(buf,ent->filename,comm_ptr->text);

	    /* now write the text descriptor record to the file */

	    status = write_text(&text_fab,&text_rab,buf,dir_text);

	    if(status != RMS$_NORMAL)
	    {
	       bad_text = 1;		/* don't process any more text desc.  */
	       sprintf(buf,"Text %s? *ERROR*\7",ent->filename);
	       xmess(disp_id,buf,scr_width);
	    }
	    else
	    {
	       sprintf(buf,"Text %s? [OK]",ent->filename);
	       xmess(disp_id,buf,scr_width);
	    }

	    ent->text = nullptr;	/* clear out old text descriptor      */
	 }

lab4:	 if(comm_ptr->comm_del)
	 {
	    /* set up the FAB first */

	    out_fab.fab$l_fna = ent->filename;
	    out_fab.fab$b_fns = (UCHAR) strlen(ent->filename);
	    out_fab.fab$w_ifi = 0;

	    status = SYS$ERASE(&out_fab);

	    if(status != RMS$_NORMAL)
	    {
	       sprintf(buf,"Delete %s *ERROR*\7",ent->filename);
	       xmess(disp_id,buf,scr_width);
	       sleep(1);
	    }
	    else
	    {
	       sprintf(buf,"Delete %s? [OK]",ent->filename);
	       xmess(disp_id,buf,scr_width);
	    }

	    ent->text = nullptr;	/* clear out old text descriptor      */
	 }

	 /* now free up the command structure for the file */

	 free_comstr(ent->command);      
      }

      /* did the file have a text descriptor associated with it?  if it
       * does at this point, we have to write it to the text descriptor file
       */

      if((ent->text != nullptr) && (!bad_text))
      {
	 /* yes, write it to the text descriptor file */

	 make_text_rec(buf,ent->filename,ent->text);
	 status = write_text(&text_fab,&text_rab,buf,dir_text);

	 if(status != RMS$_NORMAL)
	    bad_text = 1;		/* do not process text descr. anymore */
      }

      i++;				/* count this file		      */
      ent++; 				/* go to next file entry	      */
   }

   buf[0] = '\0';			/* end-of-file record for text desc.  */

   status = write_text(&text_fab,&text_rab,buf,dir_text);

   return;

}	/*** xecute ***/     
/*eject*/
/*******************************************************************************
********************************************************************************

  Function:	xmess

  Purpose:	Display informational messages to the screen for files that
		are being manipulated during file command execution.

  Global variables:

	Name			Examine/Modify/Use/Read/Write
	----			-----------------------------
	none

  Return Codes:

	Code			Reason
	----			------
	none

********************************************************************************
*******************************************************************************/

void xmess(disp_id,buf,scr_width)
				   	/*******   FORMAL  PARAMETERS   *******/
	 ULONG	  disp_id;		/* display id of where to write to    */
	 char	  *buf;			/* buffer to write to screen	      */
	 short	  scr_width;		/* width of screen display	      */

{	/*** xmess ***/
					/********   LOCAL  VARIABLES   ********/
static $DESCRIPTOR(mess_d,"");		/* output message buffer	      */


   /* first clear out things in case the new message is shorter than the
    * previous one; we would have garbage at the end of it
    */

   clear_mess(disp_id,scr_width);	/* clear the message line first	      */

   mess_d.dsc$a_pointer = buf;		/* set up the descriptor	      */
   mess_d.dsc$w_length = (USHORT) strlen(buf);

   /* write the line to the display */

   SMG$PUT_CHARS(&disp_id,&mess_d,&MAIN_ROWS,&1L,&1L,&SMG$M_REVERSE,0,0);

   return;

}	/*** xmess ***/
/*eject*/
/*******************************************************************************
********************************************************************************

  Function:	tag_file

  Purpose:	Put a mark by a file on the screen so that user knows what
		file is being accessed when he is prompted at the bottom of
		the virtual display for information.  With the cursor at the
		bottom of the virtual display in preparation for receiving
		input, it is sometimes hard to remember what file was selected.
		This should help alleviate that.

  Global variables:

	Name			Examine/Modify/Use/Read/Write
	----			-----------------------------
	none

  Return Codes:

	Code			Reason
	----			------
	none

********************************************************************************
*******************************************************************************/

void tag_file(row,column,disp_id)
					/*******   FORMAL  PARAMETERS   *******/
	 long	  row,			/* row of where to write tag	      */
		  column;		/* column of where to write tag	      */
	 ULONG	  disp_id;		/* display id to write to	      */

{	/*** tag_file ***/
					/********   LOCAL  VARIABLES   ********/
	 ULONG	  status;		/* return code status holder	      */
static $DESCRIPTOR(tag_d,">");		/* char for tagging selected files    */


   status = SMG$PUT_CHARS(&disp_id,&tag_d,&row,&column,0,&SMG$M_BOLD,0,0);

   return;

}	/*** tag_file ***/
/*eject*/
/*******************************************************************************
********************************************************************************

  Function:	erase_tag

  Purpose:	Erase a file tag now that user is done doing what they wanted
		with the file.

  Global variables:

	Name			Examine/Modify/Use/Read/Write
	----			-----------------------------
	none

  Return Codes:

	Code			Reason
	----			------
	none

********************************************************************************
*******************************************************************************/

void erase_tag(row,column,disp_id)
					/*******   FORMAL  PARAMETERS   *******/
	 long	  row,			/* row of where to write tag	      */
		  column;		/* column of where to write tag	      */
	 ULONG	  disp_id;		/* display id to write to	      */

{	/*** erase_tag ***/
					/********   LOCAL  VARIABLES   ********/
	 ULONG	  status;		/* return code status holder	      */
static $DESCRIPTOR(tag_d," ");		/* blank for wiping out tag character */


   status = SMG$PUT_CHARS(&disp_id,&tag_d,&row,&column,0,0,0,0);

   return;

}	/*** erase_tag ***/
/*eject*/
/*******************************************************************************
********************************************************************************

  Function:	put_text

  Purpose:	Put the text descriptor for the directory on the screen.

  Global variables:

	Name			Examine/Modify/Use/Read/Write
	----			-----------------------------
	none

  Return Codes:

	Code			Reason
	----			------
	  0			Everything fine
	 -1			Error parsing new filename

  Termination Codes:

	Code			Reason
	----			------
	status			unable to display directory text descriptor

********************************************************************************
*******************************************************************************/

void put_text(disp_id,text_desc,scr_width)
      					/*******   FORMAL  PARAMETERS   *******/
	 ULONG	  disp_id;		/* display id to write to	      */
	 char	  *text_desc; 		/* text descriptor buffer	      */
	 short	  scr_width;		/* width of screen being used	      */

{	/*** put_text ***/
					/********   LOCAL  VARIABLES   ********/
	 ULONG	  status;		/* return code status holder	      */
	 short	  length;		/* string length holder		      */
static	 char	  buf[SCR_MAX+5],	/* formatting buffer		      */
		  spaces[] =		/* spaces array for centering stuff   */
"                                                                    ";
static $DESCRIPTOR(text_d,buf);		/* descriptor for writing to screen   */


   if(text_desc != NULL)		/* is there anything to write?	      */
   {
      length = (scr_width - (short) strlen(text_desc))/2;
      spaces[length] = '\0';  		/* make spaces for centering	      */
      cat(buf,spaces,text_desc,spaces," "); /* make a string to write	      */
      buf[scr_width] = '\0';		/* make sure we have the right length */
      spaces[length] = ' ';		/* reset spaces array for next time   */

      text_d.dsc$w_length = scr_width;	/* set length in descriptor	      */

      status = SMG$PUT_CHARS(&disp_id,&text_d,&2L,&1L,0,&SMG$M_REVERSE,0,0);

      if(status != SS$_NORMAL)
         LIB$STOP(status);
   }
   else
   {
      /* erase the one that might be on the screen, since this directory
       * doesn't have a text descriptor
       */

      status = SMG$ERASE_DISPLAY(&disp_id,&2L,&1L);

      if(status != SS$_NORMAL)
	 LIB$STOP(status);
   }

   return;

}	/*** put_text ***/
/*eject*/
/*******************************************************************************
********************************************************************************

  Function:	info_mess

  Purpose:	Display an informational message on the screen

  Global variables:

	Name			Examine/Modify/Use/Read/Write
	----			-----------------------------
	none

  Return Codes:

	Code			Reason
	----			------
	none

  Termination Codes:

	Code			Reason
	----			------
	status			unable to display screen message

********************************************************************************
*******************************************************************************/

void info_mess(disp_id,message,scr_width)
					/*******   FORMAL  PARAMETERS   *******/
	 ULONG	  disp_id;		/* where to write it		      */
	 char	  *message;		/* message to be written	      */
	 short	  scr_width;		/* width of screen		      */

{	/*** info_mess ***/
					/********   LOCAL  VARIABLES   ********/
	 ULONG	  status;		/* return code status holder	      */
	 long	  start_row;		/* row to start writing at	      */
static	 struct	  dsc$descriptor_s
		  text_d;		/* output descriptor		      */

   /* first clear out things in case the new message is shorter than the
    * previous one; we would have garbage at the end of it
    */

   clear_mess(disp_id,scr_width);	/* clear the message line first	      */

   /* set up the descriptor for the message */

   text_d.dsc$a_pointer = message;
   text_d.dsc$w_length = (USHORT) strlen(message);
   start_row = MAIN_ROWS;
   status = SMG$PUT_CHARS(&disp_id,&text_d,&start_row,&1L,0,&SMG$M_BOLD,0,0);

   if(status != SS$_NORMAL)
      LIB$STOP(status);

   return;

}	/*** info_mess ***/
/*eject*/
/*******************************************************************************
********************************************************************************

  Function:	create_text

  Purpose:	Attempt to create the text descriptor file if the user wants
		to.  The user is prompted for the text descriptor for the
		directory and the file is written with the entry for the
		text descriptor file itself.

  Global variables:

	Name			Examine/Modify/Use/Read/Write
	----			-----------------------------
	none

  Return Codes:

	Code			Reason
	----			------
	DONT_CREATE		user does not want text descriptor file created
	CANT_OPEN		error opening/writing text descriptor file
	NEW_FILE		new text descriptor file created

********************************************************************************
*******************************************************************************/

short create_text(keybd_id,disp_id,scr_width)
					/*******   FORMAL  PARAMETERS   *******/
	 ULONG	  keybd_id,		/* keyboard id to read from	      */
	 	  disp_id;		/* display id of where to prompt      */
	 short	  scr_width;		/* screen width			      */

{	/*** create_text ***/
					/********   LOCAL  VARIABLES   ********/
	 ULONG	  status;		/* return code status holder	      */
	 USHORT	  term_code,		/* keystroke read in		      */
		  length;		/* length of string read in	      */
static	 struct	  FAB  out_fab;		/* text descriptor file FAB	      */
static	 struct	  RAB  out_rab;		/* text descriptor file RAB	      */
static	 char	  prompt[] =		/* prompt for directory text descrip  */
		  {"Text descriptor for directory: "},
		  buf[SCR_MAX+1],	/* for all kinds of output stuff      */
		  text_buf[DIR_TEXT_MAX+1]; /* buffer for reading from screen */
static $DESCRIPTOR(text_d,text_buf);	/* descriptor for reading from screen */
static $DESCRIPTOR(prompt_d,prompt);


   sprintf(buf,"Text descriptor file %s does not exist.  Create it? [y] ",
	   TEXT_FILE);

   /* prompt the user to see if they want to create the text descriptor file */

   info_mess(disp_id,buf,scr_width);

   /* read the keystroke for their response */

   status = SMG$READ_KEYSTROKE(&keybd_id,&term_code,0,0,&disp_id,0,0);

   clear_mess(disp_id,scr_width);	/* clear the prompt from the screen   */

   /* now see what key was pressed */

   if(status != SS$_NORMAL)		/* ^Z maybe....??		      */
      return(DONT_CREATE);		/* do not create file		      */

   if((term_code == SMG$K_TRM_LOWERCASE_N) ||
      (term_code == SMG$K_TRM_UPPERCASE_N))
      return(DONT_CREATE);		/* don't create the file here, either */

   /* ok, we need to create the file; open it for writing */

   out_fab = cc$rms_fab;		/* initialize the RMS blocks	      */
   out_rab = cc$rms_rab;

   out_fab.fab$b_fac = FAB$M_PUT;	/* using SYS$PUT for record I/O	      */
   out_fab.fab$w_ifi = 0;		/* make sure to reset every time      */
   out_fab.fab$b_rat = FAB$M_CR;	/* carriage-return carriage control   */
   out_fab.fab$l_fna = TEXT_FILE;	/* set filename			      */
   out_fab.fab$b_fns = (UCHAR) strlen(TEXT_FILE);

   out_rab.rab$l_fab = &out_fab;	/* set RAB fields		      */
   out_rab.rab$b_rac = RAB$C_SEQ;	/* sequential access		      */
   out_rab.rab$l_rbf = buf;

   status = SYS$CREATE(&out_fab);

   if(status != RMS$_NORMAL)
      return(CANT_OPEN);

   /* connect the RMS blocks */

   status = SYS$CONNECT(&out_rab);

   if(status != RMS$_NORMAL)
   {
      SYS$CLOSE(&out_fab);
      delete(TEXT_FILE);		/* delete the file to be thorough     */
      return(CANT_OPEN);
   }

   /* the file has been opened ok; get the descriptor for the directory,
    * format it, and write it to the text descriptor file
    */

   /* position the cursor to prompt for the text descriptor */

   status = SMG$SET_CURSOR_ABS(&disp_id,&MAIN_ROWS,&1L);

   if(status != SS$_NORMAL)
   {
      SYS$CLOSE(&out_fab);
      delete(TEXT_FILE);
      return(CANT_OPEN);
   }

   /* prompt for and read in the text descriptor for the directory itself */

   status = SMG$READ_STRING(&keybd_id,&text_d,&prompt_d,&DIR_TEXT_MAX,0,0,0,
			    &length,0,&disp_id,0,&SMG$M_BOLD,0);

   if(status != SS$_NORMAL)
   {
      SYS$CLOSE(&out_fab);
      delete(TEXT_FILE);
      return(CANT_OPEN);
   }

   text_buf[length] = '\0';		/* make it a string		      */

   clear_mess(disp_id,scr_width);	/* clear things out		      */

   cat(buf,">>>",text_buf);		/* format it correctly		      */

   /* now write the directory text descriptor record to the file */

   out_rab.rab$w_rsz = length + 3;	/* set length in RAB		      */
   status = SYS$PUT(&out_rab);		/* write it to the file		      */

   if(status != RMS$_NORMAL)
   {
      SYS$CLOSE(&out_rab);
      delete(TEXT_FILE);
      return(CANT_OPEN);
   }

   /* write the spacing record to the file */

   strcpy(buf,">");			/* copy spacing record to the buffer  */
   out_rab.rab$w_rsz = 1;		/* set length of spacing record	      */

   status = SYS$PUT(&out_rab);		/* write two of them and check later  */
   status = SYS$PUT(&out_rab);

   if(status != RMS$_NORMAL)
   {
      SYS$CLOSE(&out_rab);
      delete(TEXT_FILE);
      return(CANT_OPEN);
   }

   /* now write the text descriptor entry for the text descriptor file
    * itself; this is so the user knows what the file is when they bring
    * the text descriptors in
    */

   cat(buf,TEXT_FILE," text descriptor file");
   out_rab.rab$w_rsz = (USHORT) strlen(buf);

   status = SYS$PUT(&out_rab);

   if(status != RMS$_NORMAL)
   {
      SYS$CLOSE(&out_rab);
      delete(TEXT_FILE);
      return(CANT_OPEN);
   }

   /* we are done (finally!); just close the file and return */

   status = SYS$CLOSE(&out_fab);

   if(status != RMS$_NORMAL)
   {
      delete(TEXT_FILE);
      return(CANT_OPEN);
   }

   return(NEW_FILE);

}	/*** create_text ***/
/*eject*/
/*******************************************************************************
********************************************************************************

  Function:	free_com

  Purpose:	Free the memory that was allocated for all of the command
		structures for a directory, including all of the memory that
		may have been allocated to hold things such as the copy name,
		the rename name, and the text descriptor.

  Global variables:

	Name			Examine/Modify/Use/Read/Write
	----			-----------------------------
	none

  Return Codes:

	Code			Reason
	----			------
	none

********************************************************************************
*******************************************************************************/

void free_comm(dirptr,num_file)
					/*******   FORMAL  PARAMETERS   *******/
register ENT_DEF  *dirptr;		/* pointer to directory entries	      */
register short	  num_file;		/* number of files in directory	      */

{	/*** free_comm ***/


   while(num_file-- >0)			/* delete all command structs	      */
   {
      if(dirptr->command != NULL) 	/* command struct for this entry?     */
      {
	 /* free up the command structure and everything associated with it */

	 free_comstr(dirptr->command);
      }

      dirptr++;				/* go to next command strucure	      */
   }

   return;

}	/*** free_comm ***/
/*eject*/
/*******************************************************************************
********************************************************************************

  Function:	exp_prot_str

  Purpose:	Take an integer value for a file's protection and produce a
		nice VMS-style string for the protection.  This routine will
		only be used by the Expand command because it won't fit on the
		screen in a file slot.

  Global variables:

	Name			Examine/Modify/Use/Read/Write
	----			-----------------------------
	none

  Return Codes:

	Code			Reason
	----			------
	none

********************************************************************************
*******************************************************************************/

void exp_prot_str(str,prot)
					/*******   FORMAL  PARAMETERS   *******/
register char	  *str;			/* where to put the string	      */
	 USHORT	  prot;			/* protection value		      */

{	/*** exp_prot_str ***/


   /* initialize the string with the first field name and get a pointer
    * to the end of the string
    */

   str = mystrcpy(str,"System:");

   /* now see what the protection values for the field(s) are */

   if(!(prot & SYSTEM_READ))
      *str++ = 'R';
   if(!(prot & SYSTEM_WRITE))
      *str++ = 'W';
   if(!(prot & SYSTEM_EXECUTE))
      *str++ = 'E';
   if(!(prot & SYSTEM_DELETE))
      *str++ = 'D';

   str = mystrcpy(str,", Owner:");

   if(!(prot & OWNER_READ))
      *str++ = 'R';
   if(!(prot & OWNER_WRITE))
      *str++ = 'W';
   if(!(prot & OWNER_EXECUTE))
      *str++ = 'E';
   if(!(prot & OWNER_DELETE))
      *str++ = 'D';

   str = mystrcpy(str,", Group:");

   if(!(prot & GROUP_READ))
      *str++ = 'R';
   if(!(prot & GROUP_WRITE))
      *str++ = 'W';
   if(!(prot & GROUP_EXECUTE))
      *str++ = 'E';
   if(!(prot & GROUP_DELETE))
      *str++ = 'D';

   str = mystrcpy(str,", World:");

   if(!(prot & WORLD_READ))
      *str++ = 'R';
   if(!(prot & WORLD_WRITE))
      *str++ = 'W';
   if(!(prot & WORLD_EXECUTE))
      *str++ = 'E';
   if(!(prot & WORLD_DELETE))
      *str++ = 'D';

   *str = '\0';				/* make it a string		      */

   return;

}	/*** exp_prot_str ***/
/*eject*/
/*******************************************************************************
********************************************************************************

  Function:	set_args

  Purpose:	Set the original values for ent_factor and slot_width along
		with initializing some of the other flags that provide infor-
		mation about what run-time arguments were specified.

  Global variables:

	Name			Examine/Modify/Use/Read/Write
	----			-----------------------------
	none

  Return Codes:

	Code			Reason
	----			------
	none

********************************************************************************
*******************************************************************************/

void set_args(args,slot_width,ent_factor,scr_width)
					/*******   FORMAL  PARAMETERS   *******/
register ARG_DEF  *args;		/* run-time argument flags	      */
	 short	  *slot_width,		/* starting slot width		      */
		  *ent_factor,		/* ent. size memory allocation factor */
		  scr_width;		/* width of screen being used	      */

{	/*** set_args ***/

   /* initialize the width to include the length of the filename that
    * will be displayed and the spot to hold the cursor; also update
    * the entry size factor for later
    */

   *slot_width = NAME_MAX + 1;
   *ent_factor = FUDGE_FACTOR;

   if(args->full)
   {
      /* just make the factors relect full info on the screen */

      *slot_width = FULL_MAX;
      *ent_factor = NAME_MAX + DATE_MAX + PROT_MAX + 3;
   }
   else
   {
      /* nope, we have to see what info should be included to get the
       * slot width and the entry factor
       */

      if(args->date)
      {
	 *slot_width = *slot_width + DATE_MAX + 1;
	 *ent_factor = *ent_factor + DATE_MAX + 1;
      }

      if(args->size)
	 *slot_width = *slot_width + SIZE_MAX + 1;

      if(args->prot)
      {
	 *slot_width = *slot_width + PROT_MAX + 1;
	 *ent_factor = *ent_factor + PROT_MAX + 1;
      }
   }

   if(args->text)
   {
      /* the user wants text descriptors */

      *ent_factor = *ent_factor + TEXT_MAX + 1;
      *slot_width = *slot_width + TEXT_MAX + 1;
 
      /* now see if there is enough room to include the text descriptors on
       * the screen
       */

      if(*slot_width > scr_width)
      {
	 args->text = SLOT_OVF;		/* they overflow the file slots	      */
	 *slot_width = *slot_width - (TEXT_MAX + 1);
      }
   }

   if(*slot_width == FULL_MAX)
      args->full = 1;
   else
   {
      if(*slot_width == (NAME_MAX + 1))
	 args->def = 1;			/* just default info on screen	      */
      else
	 args->def = 0;			/* clear to be thorough.....	      */
   }

   return;

}	/*** set_args ***/
/*eject*/
/*******************************************************************************
********************************************************************************

  Function:	set_width

  Purpose:	Set the slot width for the current directory based on whether
		or not text descriptors will be included on the screen

  Global variables:

	Name			Examine/Modify/Use/Read/Write
	----			-----------------------------
	none

  Return Codes:

	Code			Reason
	----			------
	none

********************************************************************************
*******************************************************************************/

void set_width(slot_width,text_flag)
					/*******   FORMAL  PARAMETERS   *******/
	 short	  *slot_width,		/* current slot width		      */
		  text_flag;		/* text descrips included for dir?    */

{	/*** set_width ***/


   /* should we at least TRY to get text descriptors on the screen? */

   if(text_flag == DISPLAY_TEXT)
   {
      /* yes, they need to be displayed; increase the slot_width
       * to accommodate them
       */

      *slot_width = *slot_width + TEXT_MAX + 1;
   }

   return;

}	/*** set_width ***/
/*eject*/
/*******************************************************************************
********************************************************************************

  Function:	get_scr_num

  Purpose:	Prompt for and read in a digit string that is supposed to
		represent the screen number that the user wants to go to.
		Also check to see if the screen number is valid.

  Global variables:

	Name			Examine/Modify/Use/Read/Write
	----			-----------------------------
	none

  Return Codes:

	Code			Reason
	----			------
	BAD_SCREEN_NO		invalid screen number specified
	new_screen		new screen number

********************************************************************************
*******************************************************************************/

short get_scr_num(disp_id,keybd_id,curr_screen,num_screen,scr_width)
					/*******   FORMAL  PARAMETERS   *******/
	 long	  disp_id,		/* display id to work with	      */
 	 	  keybd_id;		/* keyboard id value		      */
	 short	  curr_screen,		/* current screen of directory	      */
		  num_screen,		/* number of screens in directory     */
		  scr_width;		/* width of screen being used	      */

{	/*** get_scr_num ***/
					/********   LOCAL  VARIABLES   ********/
	 ULONG	  status;		/* return code status holder	      */
	 int	  new_screen;		/* new screen number		      */
	 USHORT	  length;		/* length of string read in	      */
	 short	  temp;			/* temporary string length	      */
static	 char	  buf[PAGE_NO_MAX+1];	/* for holding page number string     */
static $DESCRIPTOR(buf_d,buf);
static $DESCRIPTOR(prompt_d,"Page number: ");


   /* position the cursor to prompt for the page number */

   status = SMG$SET_CURSOR_ABS(&disp_id,&MAIN_ROWS,&1L);

   if(status != SS$_NORMAL)
      return(curr_screen);   

   /* prompt for and read it */

   status = SMG$READ_STRING(&keybd_id,&buf_d,&prompt_d,&PAGE_NO_MAX,0,0,0,
			    &length,0,&disp_id,0,&SMG$M_BOLD,0);

   if(status != SS$_NORMAL)
   {
      info_mess(disp_id,"\07Error obtaining page number!",scr_width);
      return(curr_screen);
   }

   clear_mess(disp_id,scr_width);	/* clear the stuff from the screen    */

   if(length == 0)
      return(curr_screen);		/* user didn't specify anything	      */

   buf[length--] = '\0';		/* make it a string		      */

   /* make sure that this is a string of DIGITS or atoi() will do
    * weird things
    */

   temp = (short) length;		/* get a SIGNED version of length     */

   while(temp >= 0)
   {
      if(isdigit(buf[temp]) || isspace(buf[temp]))
	 temp--;
      else
	 return(BAD_SCREEN_NO);
   }

   new_screen = atoi(buf);		/* NOW make it an integer	      */

   if((new_screen < 1) || (new_screen > num_screen))
      return(BAD_SCREEN_NO);		/* bad screen number was specified    */

   return((short) new_screen);		/* return the new screen number	      */

}	/*** get_scr_num ***/
/*eject*/
/*******************************************************************************
********************************************************************************

  Function:	edit_acl

  Purpose:	Call the ACL editor for the specified file.

  Global variables:

	Name			Examine/Modify/Use/Read/Write
	----			-----------------------------
	none

  Return Codes:

	Code			Reason
	----			------
	none

********************************************************************************
*******************************************************************************/
 
ULONG edit_acl(filename)
       					/*******   FORMAL  PARAMETERS   *******/
	 char	  *filename;		/* file whose ACL will be edited      */

{	/*** edit_acl ***/
					/********   LOCAL  VARIABLES   ********/
static	ULONG	  status,		/* return code status holder	      */
		  type_val,		/* type of object being edited	      */
		  options;		/* options to ACL editor	      */
static $DESCRIPTOR(item_d,"");		/* descriptor for object name	      */
static	ITM_LST	items[] = {		/* items for calling the ACL editor   */
{0,ACLEDIT$C_OBJNAM,&item_d,0},
{4,ACLEDIT$C_OBJTYP,&type_val,0L},
{4,ACLEDIT$C_OPTIONS,&options,0L},
{0,0,0L,0L}
};


   /* use autoprompt mode for the ACL editor and journal the session */

   options = (1 << ACLEDIT$V_PROMPT_MODE) | (1 << ACLEDIT$V_JOURNAL);
   type_val = ACL$C_FILE;		/* editing a file's ACL		      */
   item_d.dsc$a_pointer = filename;	/* set address in descriptor	      */
   item_d.dsc$w_length = (USHORT) strlen(filename);
   items[0].buf_len = item_d.dsc$w_length;

   return(ACLEDIT$EDIT(items));

}	/*** edit_acl ***/
/*eject*/
/*******************************************************************************
********************************************************************************

  Function:	put_acl

  Purpose:	Format the ACL for the current file and write it to the
		screen; this routine is called by expand().

  Global variables:

	Name			Examine/Modify/Use/Read/Write
	----			-----------------------------
	none

  Return Codes:

	Code			Reason
	----			------
	CANT_DISPLAY		error obtaining/formatting ACL
	  0			Everything fine

********************************************************************************
*******************************************************************************/

static short put_acl(keybd_id,fab,prot_xab,acl_buf,acl_len,row,disp_id,
		     scr_width)
					/*******   FORMAL  PARAMETERS   *******/
	 ULONG	  keybd_id;		/* keyboard id for user's terminal    */
struct	 FAB	  *fab;			/* current file's FAB		      */
struct	 XABPRO	  *prot_xab;		/* XAB for getting ACL information    */
	 UCHAR	  *acl_buf;		/* where ACL binary info is stored    */
	 USHORT	  acl_len;		/* length of all ACL's for file       */
	 long	  *row;			/* current row of the display	      */
	 ULONG	  disp_id;		/* where to write the information     */
	 short	  scr_width;		/* width of user's screen	      */

{	/*** put_acl ***/
					/********   LOCAL  VARIABLES   ********/
register UCHAR	  *tptr;		/* pointer to current binary ACE      */
	 ULONG	  status,		/* return code status holder	      */
		  ace_len;		/* length of ASCII ACE		      */
	 short	  put_cnt = 0;		/* set after first put_buf() call     */
	 USHORT	  accum_len = 0,	/* accumulated length of all ACE's    */
	     	  buf_len;		/* total processed in current buffer  */
static	 char	  outbuf[BUFSIZ];	/* holds formatted ACE's	      */
static $DESCRIPTOR(in_d,"");		/* input descrip for formatting ACL's */
static $DESCRIPTOR(out_d,outbuf);	/* output descrip for     "      "    */


   /* process ALL of the ACL information for the current file; we do this by
    * continuing until the accumulated length of all ACE's processed meets
    * or exceeds the length that came in acl_len
    */
                                                           
   while(accum_len < acl_len)
   {
      /* process all of the ACE's in the current buffer; after we do that,
       * see if we need to do a SYS$DISPLAY() to look for more
       */

      tptr = acl_buf;
      buf_len = 0;
      while((buf_len < ACLBUF_MAX) && (*tptr != 0))
      {
	 /* format the current ACE */

	 in_d.dsc$a_pointer = tptr;
	 in_d.dsc$w_length = (USHORT) *tptr;
	 status = SYS$FORMAT_ACL(&in_d,&ace_len,&out_d,0,0,0,0);

	 if(status != SS$_NORMAL)
	    return(CANT_DISPLAY);

	 *(outbuf + ace_len) = '\0';	/* make it a string		      */

	 if(!put_cnt)
	 {
	    /* this is the first ACE that we are writing; insert the
	     * identification string before calling put_buf() to write it
	     */
                                                    
	    strins(outbuf,"Access Cntrl List:  ",0);
	    put_buf(keybd_id,disp_id,outbuf,row,scr_width,NULL,'+');
	    put_cnt++;
	 }
	 else
	 {
	    /* here we specify a prefix pad string so that put_buf() formats
	     * things nicely
	     */

	    put_buf(keybd_id,disp_id,outbuf,row,scr_width,
		    "                    ",'+');
	 }

	 buf_len += *tptr;		/* update processed length	      */
	 tptr = tptr + (ULONG) *tptr;	/* move to next ACE in buffer	      */
      }

      accum_len += buf_len;		/* add in what was processed	      */
      if(accum_len < acl_len)		/* do we need to a SYS$DISPLAY()?     */
      {
	 status = SYS$DISPLAY(fab);
	 if(status != RMS$_NORMAL)
	    return(CANT_DISPLAY);
      }
   }

   return(0);

}	/*** put_acl ***/
/*eject*/
/*******************************************************************************
********************************************************************************

  Function:	free_comstr

  Purpose:	Free the memory that belongs to a specific command structure.

  Global variables:

	Name			Examine/Modify/Use/Read/Write
	----			-----------------------------
	none

  Return Codes:

	Code			Reason
	----			------
	none

  Termination Codes:

	Code			Reason
	----			------
	status			failure freeing memory

********************************************************************************
*******************************************************************************/
 
static void free_comstr(command_str)
       					/*******   FORMAL  PARAMETERS   *******/
	 COM_DEF  *command_str;		/* pointer to structure to be freed   */

{	/*** free_comstr ***/
		      			/********   LOCAL  VARIABLES   ********/
register COM_DEF  *comm_ptr;		/* register pointer for quickness     */
static	 ULONG	  status;		/* return code status holder	      */
static	 long	  num_bytes;		/* number of bytes to be freed	      */


   comm_ptr = command_str;		/* make a copy to make it faster      */

   if(comm_ptr->copy_name != NULL)
   {
      /* free up the memory for the copy name */

      num_bytes = (long) comm_ptr->copy_len;
      status = LIB$FREE_VM(&num_bytes,&(comm_ptr->copy_name));

      if(status != SS$_NORMAL)
	 LIB$STOP(status);
   }

   if(comm_ptr->ren_name != NULL)
   {
      /* free up the memory for the rename name */

      num_bytes = (long) comm_ptr->ren_len;
      status = LIB$FREE_VM(&num_bytes,&(comm_ptr->ren_name));

      if(status != SS$_NORMAL)
	 LIB$STOP(status);
   }

   if(comm_ptr->text != NULL)
   {
      /* free up the memory for the copy name */

      num_bytes = (long) comm_ptr->text_len;
      status = LIB$FREE_VM(&num_bytes,&(comm_ptr->text));

      if(status != SS$_NORMAL)
	 LIB$STOP(status);
   }

   /* now free up the command structure itself */

   num_bytes = (long) sizeof(COM_DEF);
   status = LIB$FREE_VM(&num_bytes,&command_str);

   if(status != SS$_NORMAL)
      LIB$STOP(status);

   return;
}	/*** free_comstr ***/
/*eject*/
/*******************************************************************************
********************************************************************************

  Function:	make_text_rec

  Purpose:	Create a record to be written to a text descriptor file.

  Global variables:

	Name			Examine/Modify/Use/Read/Write
	----			-----------------------------
	none

  Return Codes:

	Code			Reason
	----			------
	none

********************************************************************************
*******************************************************************************/

static void make_text_rec(buf,filename,text)
					/*******   FORMAL  PARAMETERS   *******/
	 char	  *buf,			/* where to put formatted record      */
		  *filename,		/* filename to be inserted in record  */
		  *text;		/* text descriptor		      */

{	/*** make_text_rec ***/
					/********   LOCAL  VARIABLES   ********/
register char	  *tptr;		/* temporary character pointer	      */


   tptr = strchr(filename,';');		/* lop off the version number.....    */

   if(tptr != NULL)			/* ..... if there was one	      */
      *tptr = '\0';

   sprintf(buf,"%s %s",filename,text);	/* make the text descriptor record    */
   
   if(tptr != NULL)
      *tptr = ';';			/* restore the filename		      */

   return;

}	/*** make_text_rec ***/
/*eject*/
/*******************************************************************************
********************************************************************************

  Function:	put_buf

  Purpose:	Output the current buffer to the display.

		This routine will also wrap long lines.  It will look
		for the character specified by the separator parameter
		and wrap it there if it finds one.

		Also, if there is a pad_str specified, it will be inserted
		at the beginning of the buffer to be printed; this is es-
		pecially useful when printing the output of put_acl().

  Global variables:

	Name			Examine/Modify/Use/Read/Write
	----			-----------------------------
	none

  Return Codes:
                   
	Code			Reason
	----			------
	 0			successful
	ret_val			return code (possibly successful) from call
				to put_buf
                                            
********************************************************************************
*******************************************************************************/

static short put_buf(keybd_id,disp_id,buf,row,scr_width,pad_str,separator)
					/*******   FORMAL  PARAMETERS   *******/
	 ULONG	  keybd_id,		/* keyboard id for terminal	      */
	 	  disp_id;		/* display id			      */
	 char	  *buf;			/* buffer to be written		      */
	 long	  *row;			/* row # where line is to be written  */
	 short	  scr_width;		/* width of screen being written to   */
	 char	  *pad_str,		/* prefix padding string	      */
	 	  separator;		/* separator char for wrapping	      */

{	/*** put_buf ***/
					/********   LOCAL  VARIABLES   ********/
register char	  *tptr;		/* pointer to long lines	      */
static	 ULONG	  spot;			/* used for finding word separators   */
static	 USHORT	  buf_len,		/* length of current buffer	      */
	       	  temp_len,		/* temporary length		      */
		  ret_val;		/* return value holder		      */


   if(*buf == '\0')			/* buffer is null; don't write it     */
      return(0);

   if(pad_str != NULL)			/* if there was a pad string....      */
      strins(buf,pad_str,0);		/* .... insert it at the beginning    */

   ret_val = 0;				/* set default return value	      */
   buf_len = (USHORT) strlen(buf);	/* get length of stuff to be written  */

   if(buf_len >= scr_width)		/* too long for screen?		      */
   {
      /* the line is too long so we have to wrap it ourselves by breaking
       * the input line up into segments that will fit on the screen
       */

      tptr = buf;
      temp_len = scr_width;		/* start with a whole line's worth    */
      while(temp_len < buf_len)
      {
	 /* move backwards until we find a word boundary */

	 spot = (ULONG) temp_len;
	 while((*(tptr + spot) != separator) && (spot > 0L))
	    --spot;
                
	 if(spot == 0L)			/* couldn't find a word boundary?     */
	    spot = (ULONG) temp_len;	/* reset for a whole screen width     */

	 ret_val = put_line(keybd_id,disp_id,tptr,row,(USHORT) spot,scr_width);

	 tptr += spot;			/* start up where we left off	      */
	 temp_len = spot + scr_width;	/* get another line's worth	      */
      }

      /* write the last segment that wouldn't be written by the above loop */

      ret_val = put_line(keybd_id,disp_id,tptr,row,(USHORT) strlen(tptr),
			 scr_width);
   }
   else
   {
      /* everything will fit on one line; just write it */

      ret_val = put_line(keybd_id,disp_id,buf,row,buf_len,scr_width);
   }

   return(ret_val);

}	/*** put_buf ***/
/*eject*/
/*******************************************************************************
********************************************************************************

  Function:	put_line

  Purpose:	Write the buffer to the display, checking first to see if we
		need to ask the user to type a character before continuing.

  Global variables:

	Name			Examine/Modify/Use/Read/Write
	----			-----------------------------
	none

  Return Codes:

	Code			Reason
	----			------
	  0			completed successfully
	CANT_DISPLAY		can't write line to virtual display
                                            
********************************************************************************
*******************************************************************************/

short put_line(keybd_id,disp_id,buf,row,buf_len,scr_width)
					/*******   FORMAL  PARAMETERS   *******/
	 ULONG	  keybd_id,		/* keyboard id for user's terminal    */
		  disp_id;		/* where the output is to go	      */
	 char	  *buf;			/* buffer to be written		      */
	 long	  *row;			/* row where output is to go	      */
	 USHORT	  buf_len;		/* length of buffer to be written     */
	 short	  scr_width;		/* width of screen being used	      */

{	/*** put_line ***/
					/********   LOCAL  VARIABLES   ********/
static	 ULONG	  status,		/* return code status holder	      */
		  temp;			/* temporary value		      */
static	 USHORT	  term_code;		/* character read from keyboard	      */
static $DESCRIPTOR(buf_d,"");		/* buffer for outputting to screen    */


   /* do we need refresh the screen and keep writing?  this could happen
    * for files that might have long ACL's
    */

   if(*row >= (WORK_ROWS - 1))
   {
      /* make the user type a character before we continue */

      if(SMG$READ_KEYSTROKE(&keybd_id,&term_code,0,0,&disp_id,0,0)
	 != SS$_NORMAL)
	 return(CANT_DISPLAY);

      /* now erase the display */

      temp = (ULONG) scr_width;

      if(SMG$ERASE_DISPLAY(&disp_id,&1L,&1L,&(WORK_ROWS-2),&temp)
	 != SS$_NORMAL)
	 return(CANT_DISPLAY);

      *row = 1L;			/* reset the row number		      */
   }

   /* write the buffer to the screen */

   buf_d.dsc$a_pointer = buf;
   buf_d.dsc$w_length = buf_len;

   if(SMG$PUT_CHARS(&disp_id,&buf_d,row,&1L,0,0,0,0) != SS$_NORMAL)
      return(CANT_DISPLAY);

   *row += 1L;  			/* increment the row position	      */

   return(SS$_NORMAL);

}	/*** put_line ***/
/*eject*/
/*******************************************************************************
********************************************************************************

  Function:	new_comm

  Purpose:	Allocate memory for a new command structure and return a
		pointer to the memory.

  Global variables:

	Name			Examine/Modify/Use/Read/Write
	----			-----------------------------
	none

  Return Codes:

	Code			Reason
	----			------
	ptr			pointer to allocated memory
	NULL			problems allocating memory

********************************************************************************
*******************************************************************************/

static COM_DEF *new_comm()

{	/*** new_comm ***/
					/*******   FORMAL  PARAMETERS   *******/
register COM_DEF  *ptr;			/* pointer to allocated memory	      */
	 ULONG	  status;		/* return code status holder	      */
	 long	  length;		/* length of structure to allocate    */


   length = (long) sizeof(COM_DEF);	/* set up the length		      */
   status = LIB$GET_VM(&length,&ptr);	/* allocate the memory		      */

   if(status != SS$_NORMAL)
      return(NULL);

   ptr->copy_name = NULL;		/* initialize the command structure   */
   ptr->text = NULL;
   ptr->ren_name = NULL;
   ptr->copy_len = 0;
   ptr->text_len = 0;
   ptr->ren_len = 0;
   ptr->comm_prot = 0;
   ptr->comm_copy = 0;
   ptr->comm_ren = 0;
   ptr->comm_text = 0;
   ptr->comm_del = 0;
   ptr->prot = 0;

   return(ptr);				/* return pointer to the structure    */

}	/*** new_comm ***/
/*eject*/
/*******************************************************************************
********************************************************************************

  Function:	write_text

  Purpose:	Inverted coroutine (to xecute()) to write text descriptor
		records to the text descriptor file.  A record of length
		zero signifies that the file is to be closed.

  Global variables:

	Name			Examine/Modify/Use/Read/Write
	----			-----------------------------
	none

  Return Codes:

	Code			Reason
	----			------
	status			return code from SYS$ calls

********************************************************************************
*******************************************************************************/

static ULONG write_text(fab,rab,text,dir_text)
					/*******   FORMAL  PARAMETERS   *******/
struct	 FAB	  *fab;			/* text file FAB		      */
struct	 RAB	  *rab;			/* text file RAB		      */
	 char	  *text,		/* text descriptor record	      */
		  *dir_text;		/* pointer to directory text descrip. */

{	/*** write_text ***/
					/********   LOCAL  VARIABLES   ********/
static	 ULONG	  status;		/* return code status holder	      */
static	 UCHAR	  state,		/* coroutine state variable	      */
		  file_open;		/* set if file has been opened	      */
static	 char	  buf[DIR_TEXT_MAX+4];	/* for formatting header records      */


   /* special check in case an attempt is made to close the text file
    * when nothing had been written to it
    */

   if((*text == '\0') && (!file_open))
      return(RMS$_NORMAL);

   if(state == 1)			/* perform resuming operation	      */
      goto lab0;

   /* initialize the RMS blocks */

   *fab = cc$rms_fab;			/* initialize the RMS blocks	      */
   *rab = cc$rms_rab;

   fab->fab$b_fac = FAB$M_PUT;		/* using SYS$PUT for record I/O	      */
   fab->fab$w_ifi = 0;			/* make sure to reset every time      */
   fab->fab$b_rat = FAB$M_CR;		/* carriage-return carriage control   */
   fab->fab$l_fna = TEXT_FILE;		/* set filename			      */
   fab->fab$b_fns = (UCHAR) strlen(TEXT_FILE);

   rab->rab$l_fab = fab;		/* set RAB fields		      */
   rab->rab$b_rac = RAB$C_SEQ;		/* sequential access		      */
   rab->rab$l_rbf = buf;

   status = SYS$CREATE(fab);		/* try to create the file	      */

   if((status != RMS$_NORMAL) && (status != RMS$_FILEPURGED))
      return(status);

   status = SYS$CONNECT(rab);		/* connect the RMS blocks	      */

   if(status != RMS$_NORMAL)
   {
      SYS$CLOSE(fab);
      return(status);
   }
   
   /* write the text descriptor file header records */

   cat(buf,">>>",dir_text);		/* format directory text descriptor   */

   rab->rab$w_rsz = (USHORT) strlen(buf);
   status = SYS$PUT(rab);

   if(status != RMS$_NORMAL)
   {
      SYS$CLOSE(fab);
      return(status);
   }

   /* write the spacing record to the file */

   strcpy(buf,">");			/* copy spacing record to the buffer  */
   rab->rab$w_rsz = 1;			/* set length of spacing record	      */

   status = SYS$PUT(rab);		/* write two of them and check later  */
   status = SYS$PUT(rab);

   if(status != RMS$_NORMAL)
   {
      SYS$CLOSE(fab);
      return(status);
   }

   state = 1;				/* update coroutine state variable    */
   file_open = 1;			/* file successfully opened	      */
   rab->rab$l_rbf = text;		/* ok, because text is static	      */

   /* continue writing until there is an RMS problem or until we get a record
    * of length zero
    */

   while((status == RMS$_NORMAL) && (*text != '\0'))
   {
      /* write the current record to the text descriptor file */

      rab->rab$w_rsz = (USHORT) strlen(text);
      status = SYS$PUT(rab);

      if(status != RMS$_NORMAL)
	 SYS$CLOSE(fab);		/* close it because of the error      */

      return(status);			/* return the status		      */

lab0:					/* we return here on subsequent call  */
      continue;
   }

   /* close the text descriptor file */

   status = SYS$CLOSE(fab);		/* close the text descriptor file     */

   state = 0;				/* make sure to reset these	      */
   file_open = 0;

   return(status);

}	/*** write_text ***/
/*eject*/
/*******************************************************************************
********************************************************************************

  Function:	make_name

  Purpose:	Create the new file name for a file operation.  Insert the
		default values if they were omitted by the user.

  Global variables:

	Name			Examine/Modify/Use/Read/Write
	----			-----------------------------
	none

  Return Codes:

	Code			Reason
	----			------
	  0			Everything fine
	 -1			Error parsing new filename

********************************************************************************
*******************************************************************************/

static short make_name(orig_name,new_name,final_name)
					/*******   FORMAL  PARAMETERS   *******/
register char	  *orig_name,		/* name when directory was read in    */
		  *new_name,		/* name read when COPY/RENAME done    */
		  *final_name;		/* final name			      */
            
{	/*** make_name ***/
					/********   LOCAL  VARIABLES   ********/
	 char	  *tptr;		/* temporary character pointer	      */
	 ULONG	  status;		/* return code status holder	      */
	 long	  i;			/* return code from strindex()	      */
static	 struct	  fscndef  items[] = {	/* items used for scanning filenames  */
		  {0,FSCN$_NAME,0L},	/* look for the filename.....	      */
		  {0,FSCN$_TYPE,0L},	/* ...and the file type....	      */
		  {0,0,0L}};		/* terminate the item list	      */
static $DESCRIPTOR(file_d,"");		/* descriptor for filename	      */


   /* parse the name the user specified when the COPY or RENAME command
    * was issued; the missing parts must be determined in case some of
    * the new filename has to be provided
    */

   file_d.dsc$a_pointer = new_name;	/* set up the descriptor first	      */
   file_d.dsc$w_length = (USHORT) strlen(new_name);

   status = SYS$FILESCAN(&file_d,items,0);

   if(status != SS$_NORMAL)
      return(-1);

   /* now determine if we have to insert the filename and/or the file
    * type for the user
    */

   if((items[0].fscn$w_length != 0) && (items[1].fscn$w_length != 0))
   {
      /* we don't have to do anything; just copy it to the newname */

      strcpy(final_name,new_name);
   }
   else if((items[0].fscn$w_length == 0) && (items[1].fscn$w_length == 0))
   {
      /* neither the filename nor the filetype was specified; we cat them
       * on to the current filename
       */

      cat(final_name,new_name,orig_name);
      i = (long) strindex(final_name,";");
      *(final_name + i) = '\0';		/* lop off the version number	      */
   }
   else if(items[1].fscn$w_length == 0)
   {
      /* the filename was specified, the filetype was not; this defaults to
       * a .LIS filetype
       */

      /* make sure we terminate the filename in the new name */

      tptr = (char *) items[0].fscn$l_addr;
      tptr[items[0].fscn$w_length] = '\0';

      cat(final_name,new_name,".LIS");	/* make the new filename	      */
   }
   else
   {                                                   
      /* the filetype was specified but the filename was not; copy the
       * filename part from the original filename
       */

      i = (ptrdiff_t) ((char *) items[1].fscn$l_addr - new_name);

      strcpy(final_name,new_name);	/* first copy the new name in	      */
      tptr = strchr(orig_name,'.');	/* lop off everything except......    */
      *tptr = '\0';			/* ..... the filename		      */
      strins(final_name,orig_name,i);	/* insert the filename in the final   */
      *tptr = '.';			/* put orig_name back the way it was  */
   }

   return(0);

}	/*** make_name ***/
/*eject*/
/*******************************************************************************
********************************************************************************

  Function:	banynstr

  Purpose:	Determine if a finite-length character string contains
		any characters from a second null-terminated character
		string.

  Global variables:

	Name			Examine/Modify/Use/Read/Write
	----			-----------------------------
	none

  Return Codes:

	Code			Reason
	----			------
	 >=0			match was found; return code is the offset
				into str1 where the character is found
	  -1			no common characters in the two strings

********************************************************************************
*******************************************************************************/

static short banynstr(str1,str2,length)
		  			/*******   FORMAL  PARAMETERS   *******/
register char	  *str1,		/* string to search		      */
		  *str2;		/* what to look for		      */
	 short	  length;		/* when to stop searching	      */

{	/*** banynstr ***/
					/********   LOCAL  VARIABLES   ********/
register int	  offset1,		/* offset into string str1	      */
		  offset2;		/* offset into string str2	      */


   if(str1 == NULL || str2 == NULL || length <= 0)
      return(-1);

   if((*str1 == '\0') || (*str2 == '\0'))
      return(-1);

   offset1 = 0;

   while(*(str1+offset1) != '\0' && length--)
   {
      offset2 = 0;
      while(*(str2+offset2) != '\0')
      {
	 if(*(str1+offset1) == *(str2+offset2))
	 {
	    return(offset1);		/* match found; return offset	      */
	 }
	 ++offset2;
      }
      ++offset1;
   }

   return(-1);				/* no match; return -1		      */

}	/*** banynstr ***/
