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

   Site:	Western Michigan University Academic Computer Center

   System:	Directory/File System Maintenance
  
   Program:	maint

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

   Purpose:	This program provides the user with a full-screen method for
		maintaining directories and file systems.  The directory
		will be presented to the user in a series of screens, each
		of which will contain information about individual files.
		The user may move through a directory structure, marking files
		for deletion, changing protection modes, renaming files,
		creating text descriptors, copying files, editing files, and
		viewing files.  Other options are available to allow the user
		to spawn a subprocess to return to DCL and search for a
		file.

		See the help file MAINT.HLP for more details.

   Version=01	Level=02	09/04/87	Leonard J. Peirce

   Purpose:	Make it so that protection strings can be specified exactly
		the same way they are specified at DCL.  Also display them
		the same way when an Expand command is issued.
		Fix a few miscellaneous bugs when making file slots.

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

   Purpose:	Add the capability to edit a file's Access Control List.
		Also add ACL's to the Expand command for a file.  Fix a number
		of nagging little bugs that only I would find.

		The information presented in the Expand command has been
		upgraded to include most (hopefully all) of the different
		information that will appear on a DIRECTORY/FULL.

		Cursoring now wraps around like it should; this should've
		been fixed a long time ago but I just now got motivated to
		do it.

		The /SORT=SIZE option has been added.

		The !/$ command has been added to allow a single DCL command
		to be executed and MAINT to regain control immediately.  What
		use this is is open to question.

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

   Purpose:	Fix a little nagging bug with formatting text descriptors
		that only I would find.

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

   Purpose:	Make the user confirm that they really want to Finish/Quit
		if there are some files marked.  Also fix a few little nagging
		bugs that only I find when I'm debugging.

   Arguments:	See associated CLD file MAINT.CLD and HELP file MAINT.HLP.

   External variables:  none

   WMU external functions:

          Defined: none

          Called:  strindex,cat,strmcpy,strdel

	  Other external functions called from MAINT2.C.

   Files accessed:  only those in the directory, along with the HELP library.

   Return codes:  return code status from a number of different RTL routines.

   Compiling instructions:

      To compile for production:

         $ CC MAINT.C/OPTIMIZE

      To compile with debugging mechanisms turned on:

         $ CC MAINT.C/DEBU/NOOP/DEFINE=DEBUG

   Linking instructions:

      To link for production:

         $ LINK/NOTRACE MAINT,MAINT2,SYS$SHARE:VAXCRTL.OPT/OPT

      To link in preparation for debugging:

         $ LINK/DEBU/NOOP MAINT,MAINT2,SYS$SHARE:VAXCRTL.OPT/OPT

   Other information:

   Built-in debugging mechanisms
   -----------------------------

   Debugging programs that use SMG routines can be difficult.  To make things
   easier, define DEBUG on the command as shown above.  This will make this
   program easier to debug because it will send its SMG output and get its
   SMG input from another terminal, the name of which must be set in the
   DCL symbol MAINT_DEV.  For example,

      $ MAINT_DEV :== LTA306:
   
   In order to make this work, you must enable SHARE.  Getting used to sharing
   keystrokes with the other session (if it is not a permanently connected
   terminal and you have to log in on it) gets a little tedious but it is
   workable.  If you can find a permanent, allocatable terminal then things
   are fine and dandy.

   Data structures
   ---------------

   Several different data structures are used internally to MAINT to store
   all kinds of information, ranging from the filenames and related information
   to text descriptors and new filenames.

   Directory information for the files in a directory is allocated dynamically
   upon entry to the proc_dir() function.  An automatic pointer, dirptr, is
   used to point to the memory that holds the information about the files.
   This pointer MUST remain automatic because each directory needs it own
   copy.  After the number of files in the directory is determined, the
   amount of memory needed to hold the information about each file in the
   directory can be calculated.  It is calculated by taking the number of
   files in the directory, adding one, and multiplying it by the length of
   the structure definition ENT_DEF.  This is the number of bytes needed.
   The memory is allocated by calling LIB$GET_VM() and the pointer to the
   memory is stored in dirptr.

   When a Finish command is issued or the commands in a directory are executed,
   the memory is deallocated, the number of files is computed, the size of
   the memory necessary is calculated, and the memory is allocated once again.

   A) Memory Pools

   MAINT uses its own memory pool allocation scheme.  These memory pools are
   used to store only CHARACTER data and thus there is no need to worry about
   byte alignment for integer values and such.  Items that are stored in the
   memory pools include:  1) the full filename of a file, 2) the protection
   string in case the /PROTECTION or /FULL switch is specified, 3) the date
   string in case the /DATE or the /FULL switch is specified, and 4) the
   text descriptor of the file in case the user selects text mode.

   The pools are allocated on a demand basis and are linked together into a
   linked list.  The size of each memory pool in the list can vary, depending
   on the state of the information that will be included for each directory.
   The global variable ent_factor is initially set to FUDGE_FACTOR, which is
   a guess at the number of bytes that each file entry is going to need to
   store its information.  Then the run-time arguments are taken into account,
   with ent_factor being updated for each that argument that will require
   something stored in the memory pools.  See the routine set_factors() for
   the values that ent_factor is increased by.

   When it is time to redo the information for a directory, the memory in the
   memory pools is reclaimed rather than released and reallocated.  The pointer 
   to the next available byte in the structure is set to point to the beginning
   of the memory pool and the remaining length is reset to the original length
   of the pool.  This may waste a little memory but it saves a few calls that
   would be needed to free and reallocate the memory.

   B) Command structures

   Command structures are used to hold the information necessary to carry out
   commands for a specific file.  When the first command for a file is speci-
   fied by the user, a new command structure is allocated and linked to the
   file entry for the file, the pointer to the command structure being stored
   in the command field of the ENT_DEF structure.

   All of the information in the command structure that requires string values
   (such as ren_name, copy_name, and text) are stored in memory allocated by
   LIB$GET_VM() and NOT in a memory pool.  This is to cut down on wasting of
   memory for file commands that are cancelled.

   When commands for a file are cancelled, all the memory for any of the char-
   acter strings are freed (via LIB$FREE_VM()) and the command structure itself
   is freed.

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


/******************************************************************************/
/*                                                                            */
/*                        # 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 itmlst				/* for item list definition	      */
#include utype				/* unsigned data type header	      */
#include clidef				/* for more CLI$ stuff		      */
#include string				/* string function header	      */

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

#endif

#include rms				/* RMS header; includes all others    */
#include descrip			/* for descriptors		      */
#include lnmdef				/* for translating logical names      */
#include ssdef				/* SYS$ return code symbolics	      */
#include stdio				/* who knows?	(NULL maybe?)	      */
#include smgdef				/* SMG$ stuff			      */
#include climsgdef			/* for CLI$ routines		      */
#include ctype				/* character-type header	      */
#include limits				/* scalar data type limits	      */

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

#define SYSDISK_MAX 256			/* max. length of SYS$DISK holder     */
#define MAX_DIR 15			/* max. # of subdirectories to save   */

/******************************************************************************/
/*                                                                            */
/*          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    */
/*                                                                            */
/******************************************************************************/

extern	 ULONG	  LBR$OUTPUT_HELP(),
		  LIB$FREE_VM(),
		  LIB$GET_INPUT(),
	 	  LIB$GET_VM(),
		  LIB$PUT_OUTPUT(),
		  SMG$CHANGE_PBD_CHARACTERISTICS(),
		  SMG$CREATE_PASTEBOARD(),
		  SMG$CREATE_VIRTUAL_DISPLAY(),
		  SMG$CREATE_VIRTUAL_KEYBOARD(),
		  SMG$DISABLE_BROADCAST_TRAPPING(),
		  SMG$ERASE_DISPLAY(),
		  SMG$GET_BROADCAST_MESSAGE(),
		  SMG$PASTE_VIRTUAL_DISPLAY(),
		  SMG$PUT_CHARS(),
		  SMG$READ_STRING(),
		  SMG$REPAINT_SCREEN(),
		  SMG$RING_BELL(),
		  SMG$SET_BROADCAST_TRAPPING(),
		  SMG$SET_CURSOR_ABS(),
		  SMG$SET_OUT_OF_BAND_ASTS(),
		  SYS$CLOSE(),
		  SYS$CONNECT(),
		  SYS$GET(),
		  SYS$OPEN(),
		  SYS$PARSE(),
		  SYS$SETDDIR();

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

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

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

extern	 int	  strindex(),		/* search for substring of a string   */
		  cat(),		/* concatenate multiple strings	      */
		  sleep(),		/* C version of LIB$WAIT()	      */
		  strdel();		/* delete characters from a string    */

extern	 short	  get_num_file(),	/* get number of files in a directory */
		  expand(),		/* display attributes of a file	      */
		  create_text(),	/* attempt to create text desc. file  */
		  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    */
		  get_scr_num(),	/* let user specify new screen number */
		  prot_val_to_str();	/* create protection string	      */

extern	 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  */
		  highlite(),		/* highlight a file slot if necessary */
		  free_pool(),		/* free memory pools for a directory  */
 		  clear_mess(),		/* clear an information message	      */
		  mark_cancel(),	/* cancel commands for a file entry   */
		  set_direct(),		/* change to command-line directory   */
		  tag_file(),		/* "tag" current file on screen	      */
		  set_sysdisk(),	/* set SYS$DISK logical		      */
		  set_width(),		/* set scr_width variable correctly   */
		  erase_tag(),		/* erase file tag		      */
		  xmess(),		/* display messages while eXecuting   */
		  set_args(),		/* set run-time args and other stuff  */
		  free_com(),		/* free command structures	      */
		  put_text(),		/* put directory text descrip on scr. */
		  xecute();		/* execute file commands	      */

/******************************************************************************/
/*                                                                            */
/*     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	 ENT_DEF  *baseptr;		/* pointer to base of memory for a    */
					/* directory; used for sorting	      */

static	 char	  *text_match(),	/* look for text descriptor record    */
		  *text_search();	/* search directory entries for match */

static	 long	  *global_row,		/* pointer to where screen row is     */
		  *global_col;		/* pointer to where screen col is     */

static	 ULONG	  paste_id,		/* pasteboard id		      */
		  disp1_id,		/* directory info virtual display id  */
		  disp2_id,		/* main virtual display id	      */
		  disp3_id,		/* options virtual display id	      */
		  disp4_id,		/* select file virtual display id     */
		  keybd_id,		/* virtual keyboard id		      */
		  make_ent(),		/* create directory entry	      */
		  ctrl_ast(),		/* called for control-C & control-Y   */
		  put_help(),		/* write to display during help	      */
		  get_help();		/* read from display during help      */

static	 USHORT	  check_marks();	/* check for commands in a directory  */

static	 short	  scr_width,		/* page width			      */
		  def_slot_wid,		/* default slot width		      */
		  ent_factor,		/* ent. size memory allocation factor */
		  proc_dir(),		/* process current directory	      */
		  mail_note,		/* set if mail notification on screen */
		  phone_note,		/*  "   " phone     "        "   "    */
		  state,		/* state variable for put_help()      */
		  get_text_des(),	/* read in text descrips for direct.  */
		  time_cmp(),		/* compare to quadword time values    */
		  file_select(),	/* process a Selected file	      */
		  file_locate(),	/* prompt for/locate a file	      */
		  file_search();	/* search directory for file	      */

static	 char	  dir_name[NAM$C_MAXRSS+1], /* current directory	      */
		  user_dir[NAM$C_MAXRSS+1], /* user's original directory      */
		  start_spec[SPEC_MAX+1]; /* starting file specification      */

static	 ARG_DEF  args;			/* run-time argument flags	      */

static	 NODE_DEF nodes[MAX_NODE_ROW+1][MAX_NODE_COL+1];

static	 void	  get_dir_mem(),	/* allocate memory for directory info */
	 	  get_dir(),		/* get information about directory    */
		  set_factors(),	/* set screen and memory factors      */
		  set_nodes(),		/* initialize screen node matrix      */
		  screen_reset(),	/* reset cursor/screen info	      */
		  get_args(),		/* process command-line arguments     */
		  set_screen(),		/* set up screen display parameters   */
		  set_name(),		/* create displayed filename	      */
		  put_stat(),		/* write directory statistics line    */
		  put_spec(),		/* write directory spec/page # info   */
		  put_pagnum(),		/* write "Page of" stuff in top-right */
	     	  brdcast_ast(),	/* broadcast message AST routine      */
		  name_qsort(),		/* quicksort directory by filename    */
		  date_qsort(),		/* quicksort directory by date	      */
	 	  make_screen(),	/* put file info on screen	      */
		  initialize(),		/* initial screen & virtual displays  */
		  get_sysdisk(),	/* get value of SYS$DISK at startup   */
		  size_qsort(),		/* quicksort by file size	      */
		  swap_entries(),	/* swap to file entries (for sorting) */
		  put_options();	/* write options to display	      */

#ifdef EXTERNAL_HLB
static $DESCRIPTOR(helplib_d,"SYS$HELP:MAINT.HLB");
#else
static $DESCRIPTOR(helplib_d,"SYS$HELP:HELPLIB.HLB");
#endif
/*eject*/         
/******************************************************************************/
/*                                                                            */
/*                        M A I N   P R O C E S S I N G                       */
/*                                                                            */
/******************************************************************************/

main()

{	/*** main ***/
					/********   LOCAL  VARIABLES   ********/
static	 ULONG	  status;		/* return code status holder	      */
static	 short	  retval;		/* return code from proc_dir()	      */
static	 char	  out_device[DEV_MAX+1], /* output device name		      */
		  in_device[DEV_MAX+1],	/* input device name		      */
		  sysdisk[SYSDISK_MAX+1], /* for saving SYS$DISK value	      */
		  dev_spec[DEV_MAX+1],	/* device specified on command line   */
		  dir_spec[DEV_MAX+1];	/* directory from command line	      */
static $DESCRIPTOR(out_d,out_device);	/* output device descriptor	      */
static $DESCRIPTOR(inp_d,in_device);	/* input device descriptor	      */


   get_device(&out_d,&inp_d);		/* set up the I/O device stuff	      */

   /* get the value of SYS$DISK when the program was entered */

   get_sysdisk(sysdisk,SYSDISK_MAX);
                         
   /* check the command-line arguments and set up 1) the screen width and 2)
    * the slot width
    */

   get_args(&helplib_d,&args,&scr_width);

   /* initialize some global factors, such as the ent_factor and default
    * slot width
    */

   set_args(&args,&def_slot_wid,&ent_factor,scr_width);

   /* see if a directory was specified on the command line and get the
    * directory that the user was in when the program was invoked; this
    * is needed to go back to where the user was when the program was
    * invoked
    */

   set_direct(start_spec,dir_spec,dev_spec,user_dir);
   curr_dir(dir_name);

   /* initialize 1) the pasteboard, 2) all of the virtual displays that will
    * be needed, and 3) the virtual keyboard for input purposes
    */

   initialize(&paste_id,&keybd_id,&disp1_id,&disp2_id,&disp3_id,&disp4_id,
	      &inp_d,&out_d,&scr_width);

   /* go for it -- process the directories and any thing else that the
    * user might desire
    */

   do
   {

      retval = proc_dir();
   }
   while(retval == RECALL_PROC);

   if(dev_spec[0])			/* if we changed SYS$DISK...	      */
      set_sysdisk(sysdisk);		/* ..... set it back		      */

   /* make sure the default directory is reset before exiting...... */

   out_d.dsc$a_pointer = user_dir;
   out_d.dsc$w_length = (USHORT) strlen(user_dir);

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

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

   exit();				/* NOW we can exit.....		      */

}	/*** main ***/
/*eject*/
/*******************************************************************************
********************************************************************************

  Function:	proc_dir

  Purpose:	Process the current directory.  This includes allocating memory
		for storing the information about the files and handling all
		of the user responses.  This routine is recursive and is called
		each time the user "selects" a new directory; the current dir-
		ectory is switched and this routine is called again.  When the
		user exits a directory, this routine frees up the memory that
		was allocated for the directory and returns.  The cursor is
		left pointing to the directory that was just exited.

		This is a *RECURSIVE* function.

  Global variables:

	Name			Examine/Modify/Use/Read/Write
	----			-----------------------------
	paste_id				X
	keybd_id				X
	disp1_id				X
	disp2_id				X
	disp3_id				X
	disp4_id				X
	nodes				  X     X
	scr_width				X
	dir_name			  X	X
	args			   X		X
	phone_note			  X	X
	mail_note			  X	X
	user_dir				X
	ent_factor				X
	def_slot_wid			  X	X
	start_spec				X
	global_row			  X
	global_col			  X

  Return Codes:

	Code			Reason
	----			------
   	  0			return normally
	RECALL_PROC		return and force this procedure to be recalled
				immediately
	EMPTY_DIR		the directory entry selected to get us here is
				empty

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

static short proc_dir()

{  	/*** proc_dir ***/
					/*********   LOCAL VARIABLES   ********/
register ULONG	  status;		/* return code status		      */
register short	  node_col,		/* current node array column	      */
	 	  node_row;		/* current node array row	      */
	 ENT_DEF  *dirptr,		/* pointer to directory information   */
		  *entptr;		/* pointer to directory entry	      */
	 POOL_DEF *first_pool,		/* pointer to first memory pool	      */
		  *curr_pool;		/* pointer to current memory pool     */
	 char	  *dir_text= NULL; 	/* pointer to directory text descrip  */
	 long	  scr_col,		/* current screen column	      */
	     	  scr_row,		/* current screen row		      */
		  dirsize,		/* size of directory info memory      */
		  pool_length,		/* length for allocating new pools    */
		  num_block = 0L;	/* number of blocks in directory      */
	 short	  num_col,		/* number of columns for full screen  */
		  slot_width,		/* slot width for current directory   */
		  num_row,		/* number of rows for full screen     */
		  max_num_file,		/* max. # of files found in directory */
	     	  num_file = 0,		/* number of files in directory	      */
	     	  curr_screen = 1,	/* current screen of directory	      */
	     	  num_screen = 0,	/* number of screens in directory     */
	       	  scr_file,		/* number of files for current screen */
	     	  num_slot,		/* number of slots per screen	      */
	     	  dir_length,		/* length of current directory string */
	 	  text_flag = 0;	/* text descrip file processed flag   */
					/** above vars MUST remain automatic **/
static	 char	  *tptr;		/* temporary character pointer	      */
static	 ULONG	  rend_set;		/* rendition setting for slots	      */
static	 long	  temp_wid,  		/* temporary screen width	      */
		  end_col,		/* end column for erasing things      */
		  mask;			/* out-of-band AST flag bit mask      */
static	 USHORT	  term_code;		/* code for keystroke		      */
static	 short	  temp,			/* temporary value		      */
		  temp2,		/* temporary value #2		      */
		  save_width,		/* temporary slot width holder	      */
		  new_screen,		/* new screen when locating files     */
		  level;		/* what recursion level we are at     */
static	 UCHAR 	  mess_flag,		/* set if information mess on screen  */
	     	  cursor_flag,		/* set if cursor needs to be set      */
		  exec_flag;		/* set if we just executed commands   */
static	 char	  slot_buf[BUFSIZ],	/* for formatting screen slots	      */
		  help_buf[BUF_MAX+1],	/* buffer sent to LBR$OUTPUT_HELP     */
		  response[RESP_MAX+1],	/* response from screen		      */
		  in_buf[BUF_MAX+1];	/* input buffer for various stuff     */
static	 struct	  dsc$descriptor 
		  mess_d;		/* screen message descriptor	      */
static	 DIR_SUMM dirmem[MAX_DIR];	/* summary info for all directories   */
static $DESCRIPTOR(confirm_d,in_buf);	/* for asking user's confirmation     */
static $DESCRIPTOR(help_line,help_buf);	/* for LBR$OUTPUT_HELP's sake	      */
static	 char	  auth_str[] =		/* take some credit for this thing    */
{"Author of MAINT is Leonard J. Peirce at Western Michigan University ACC"},
		  version_str[] =	/* what version number this is	      */
{"MAINT V1.5 (Western Michigan University Academic Computer Center)"},
		  cmd_prompt[] =	/* prompt for PUSHing command	      */
{"DCL Command: "};


   phone_note = 0;			/* clear these everytime we enter a   */
   mail_note = 0;			/* directory			      */
   cursor_flag = 0;
   slot_width = def_slot_wid;		/* initialize the slot width	      */

   /* save the addresses of the screen row and column so that the cursor
    * can be reset inside of the AST routine that notifies the user in the
    * event of mail; otherwise, the cursor will just stay at the bottom of
    * the screen until the user presses a key
    */

   global_row = &scr_row;
   global_col = &scr_col;

   dir_length = (short) strlen(dir_name); /* get number of chars in dir name  */
   num_row = MAX_NODE_ROW;		/* set number of rows for now	      */
   num_file = get_num_file(start_spec);	/* get number of files in directory   */

   if(num_file == 0)			/* is the directory empty?	      */
      return(EMPTY_DIR);		/* yup.........			      */

   /* this is the max. number of files we have found in this directory    */

   max_num_file = num_file;

   /* allocate the memory to hold the directory information */                 

   get_dir_mem(&dirptr,&dirsize,num_file);

   /* initialize the memory pools for holding the full filenames of the
    * files in the directory
    */

   pool_length = (long) ((num_file + 1) * ent_factor);
   init_pool(&first_pool,pool_length);
   curr_pool = first_pool;		/* set current pool pointer	      */
                                         
   /* get all of the information about the files in the directory */
             
   get_dir(dirptr,&args,&num_block,&curr_pool,&num_file,&num_screen,
	   start_spec,pool_length,dir_length);

   /* were there any files to get?  there might not be, depending on the
    * volatility of the directory and file protections
    */

   if(num_file == 0)
   {
      free_pool(first_pool);		/* free up the memory pools first     */

      /* now free up the memory for the directory information */

      status = LIB$FREE_VM(&dirsize,&dirptr);

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

      return(EMPTY_DIR);
   }

   if(args.text)
   {
      /* yes, read in the text descriptors */

      text_flag = args.text;		/* set descrip flag for this direct.  */

      if(args.text == DISPLAY_TEXT && !args.text_startup)
	 slot_width += TEXT_MAX + 1;	/* assume we will get text descrips   */

      temp = get_text_des(dirptr,&curr_pool,&dir_text,pool_length,num_file);

      /* check to see if there were any problems getting the text descriptors */

      if(temp == BAD_FILE)
      {
	 info_mess(disp2_id,"Invalid format for text descriptor file",
		   scr_width);
	 mess_flag++;
	 text_flag = 0;			/* no text descrips for directory     */
	 if(args.text == DISPLAY_TEXT)	/* only update slot_width if needed   */
	    slot_width = slot_width - (TEXT_MAX + 1);
	 sleep(1);
      }
      else if(temp == CANT_OPEN)
      {
	 info_mess(disp2_id,"Cannot open text descriptor file",scr_width);
	 mess_flag++;
	 text_flag = 0;			/* no text descrips for directory     */
	 if(args.text == DISPLAY_TEXT)	/* only update slot_width if needed   */
	    slot_width = slot_width - (TEXT_MAX + 1);
	 sleep(1);
      }
      else if(temp == NO_FILE)
      {
	 text_flag = 0;			/* no text descrips for directory     */
	 if(args.text == DISPLAY_TEXT)	/* update slot_width if it needs it   */
	   slot_width = slot_width - (TEXT_MAX + 1);
      }
   }

   dirmem[level].ptr = dirptr;		/* save a pointer to this memory      */
   dirmem[level].num_file = num_file;

   /* count this recursion level; when it equals one, it means that we are in
    * the top-level directory for this session
    */

   level++;

   /* all of the information about the files in the directory has been
    * obtained; now initialize the screen parameters, including the screen
    * nodes and the number of screens for the directory
    */

   set_screen(&num_screen,&scr_file,num_file,curr_screen,scr_width,slot_width,
	      &num_col,nodes);

   num_slot = scr_file;			/* save number of slots per screen    */

   /* initialize the screen slot node pointers for the screen */

   set_nodes(nodes,&scr_file,num_screen,curr_screen,num_file,slot_width,
	     num_col);

   /* write the directory statistics line at the bottom of the display
    * and the directory spec/page number line at the top of the display
    */

   put_spec(disp1_id,dir_name,num_screen,scr_width);
   put_stat(disp3_id,num_block,num_file,scr_width);

   if(args.text)			/* write text descrip for directory?  */
      put_text(disp1_id,dir_text,scr_width);

   /* clear the main virtual display; we do this every time we enter a
    * directory to make things simple; some directories might have text
    * descriptors and some might not; this way, we make sure that we have
    * a clean display to work with, no matter what
    */

   status = SMG$ERASE_DISPLAY(&disp2_id);

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

   /* put the information about the files for the first screen on the screen */
             
   make_screen(nodes,dirptr,&args,disp2_id,curr_screen,num_file,scr_file,
	       num_slot,slot_width,text_flag);

   /* position the cursor to the first file in the directory */

   node_row = 0;			/* start in top left of nodes array   */
   node_col = 0;
   scr_row = (long) nodes[0][0].row;	/* start in top left of screen, too   */
   scr_col = (long) nodes[0][0].column;

   /* set the cursor position */

   status = SMG$SET_CURSOR_ABS(&disp2_id,&scr_row,&scr_col);

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

   /* this is the main processing loop for the whole program; each keystroke
    * is read and appropriate action is taken for each; this is an infinite
    * loop and the only way out is to break out from the middle and return
    * to a previous invocation of this function
    */

   for(;;)   				/* loop forever.........	      */
   {
      /* get a keystroke from the keyboard */

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

      if(status != SS$_NORMAL)
      {
	 /* treat SMG$ read errors like Quits so we can quit gracefully */

	 term_code = SMG$K_TRM_UPPERCASE_Q;
      }

      if(mess_flag)			/* was a message on the screen?	      */
      {
	 clear_mess(disp2_id,scr_width);  /* clear the message		      */
	 mess_flag = 0;			/* clear the message flag	      */

	 /* reset the cursor to where it should be */

	 status = SMG$SET_CURSOR_ABS(&disp2_id,&scr_row,&scr_col);

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

      /* now look at the key that was pressed and take appropriate action */

      switch(term_code)
      {
	 case(SMG$K_TRM_RIGHT):		/**************************************/
	 case(SMG$K_TRM_HT):		/*             right-arrow	      */
	 case(SMG$K_TRM_SPACE):		/**************************************/
	 case(SMG$K_TRM_GREATER_THAN):
	 case(SMG$K_TRM_CTRLL):

	    /* update the pointer to the screen and then update the position
	     * of the physical cursor; we must use temp to get the up_row
	     * position and then assign it to node_row because we can't have
	     * node_row changing before we use it to get node_col
	     */

	    temp = (short) nodes[node_row][node_col].right_row;
	    node_col = (short) nodes[node_row][node_col].right_col;
	    node_row = temp;
	    scr_row = (long) nodes[node_row][node_col].row;
	    scr_col = (long) nodes[node_row][node_col].column;

	    cursor_flag++;		/* reset the cursor down below	      */

	    break;

	 case(SMG$K_TRM_LEFT):		/**************************************/
	 case(SMG$K_TRM_LESS_THAN):	/*             left-arrow	      */
	 case(SMG$K_TRM_BS):		/**************************************/

	    /* update the pointer to the screen and then update the position
	     * of the physical cursor; we must use temp to get the up_row
	     * position and then assign it to node_row because we can't have
	     * node_row changing before we use it to get node_col
	     */

	    temp =  (short) nodes[node_row][node_col].left_row;
	    node_col = (short) nodes[node_row][node_col].left_col;
	    node_row = temp;
	    scr_row = (long) nodes[node_row][node_col].row;
	    scr_col = (long) nodes[node_row][node_col].column;

	    cursor_flag++;		/* reset the cursor down below	      */

	    break;

	 case(SMG$K_TRM_DOWN):		/**************************************/
	 case(SMG$K_TRM_LF):		/*             down-arrow	      */
					/**************************************/

	    /* update the pointer to the screen and then update the position
	     * of the physical cursor; we must use temp to get the up_row
	     * position and then assign it to node_row because we can't have
	     * node_row changing before we use it to get node_col
	     */

	    temp = (short) nodes[node_row][node_col].down_row;
	    node_col = (short) nodes[node_row][node_col].down_col;
	    node_row = temp;
	    scr_row = (long) nodes[node_row][node_col].row;
	    scr_col = (long) nodes[node_row][node_col].column;

	    cursor_flag++;		/* reset the cursor down below	      */

	    break;

	 case(SMG$K_TRM_UP):		/**************************************/
	 case(SMG$K_TRM_CARET):		/*              up-arrow	      */
	 case(SMG$K_TRM_CTRLK):		/**************************************/

	    /* update the pointer to the screen and then update the position
	     * of the physical cursor; we must use temp to get the up_row
	     * position and then assign it to node_row because we can't have
	     * node_row changing before we use it to get node_col
	     */

	    temp = (short) nodes[node_row][node_col].up_row;
	    node_col = (short) nodes[node_row][node_col].up_col;
	    node_row = temp;
	    scr_row = (long) nodes[node_row][node_col].row;
	    scr_col = (long) nodes[node_row][node_col].column;

	    cursor_flag++;		/* reset the cursor down below	      */

	    break;

	 case(SMG$K_TRM_CR):		/**************************************/
	 case(SMG$K_TRM_ENTER):		/*          carriage return	      */
					/**************************************/

	    /* go to left of screen and down one row on the screen */

	    node_row = nodes[node_row][0].down_row;
	    node_col = 0;
	    scr_row = nodes[node_row][node_col].row;
	    scr_col = nodes[node_row][node_col].column;

	    cursor_flag++;		/* reset the cursor down below	      */

	    break;

	 case(SMG$K_TRM_EQUAL):		/**************************************/
	 case(SMG$K_TRM_NEXT_SCREEN):	/*            plus-screen	      */
	 case(SMG$K_TRM_ASTERISK + 1):	/**************************************/
	 case(SMG$K_TRM_CTRLF):

	    if(num_screen > 1)		/* more than one screen?	      */
	    {
	       curr_screen++;		/* go to next screen		      */
	       if(curr_screen > num_screen)
		  curr_screen = 1;	/* oops, I meant the first screen     */

	       if((curr_screen == 1) || (curr_screen == num_screen))
	       {
		  set_nodes(nodes,&scr_file,num_screen,curr_screen,num_file,
			    slot_width,num_col); 
	       }

	       /* update the page number in the corner */

	       put_pagnum(disp1_id,curr_screen,num_screen,scr_width);

	       /* write the files to the screen */

	       make_screen(nodes,dirptr,&args,disp2_id,curr_screen,num_file,
			   scr_file,num_slot,slot_width,text_flag);

	       /* reset screen and node pointers */

	       node_row = 0;
	       node_col = 0;
	       scr_row = (long) nodes[0][0].row;
	       scr_col = (long) nodes[0][0].column;

	       /* home the cursor */

	       cursor_flag++;		/* reset the cursor down below	      */
	    }

	    break;

	 case(SMG$K_TRM_MINUS):		/**************************************/
	 case(SMG$K_TRM_UNDERLINE):	/*            minus-screen	      */
	 case(SMG$K_TRM_PREV_SCREEN):	/**************************************/
	 case(SMG$K_TRM_DASH):
	 case(SMG$K_TRM_CTRLB):
                           
	    /* reset the screen nodes if we have to */

	    if(num_screen > 1)
	    {
	       curr_screen--;		/* go to previous screen 	      */
	       if(curr_screen == 0)	/* were we on the first screen?	      */
		  curr_screen = num_screen; /* oops, go to last screen	      */

	       if(curr_screen >= (num_screen-1))
	       {
	     	  set_nodes(nodes,&scr_file,num_screen,curr_screen,num_file,
			    slot_width,num_col); 
	       }

	       /* update the page number in the corner */

	       put_pagnum(disp1_id,curr_screen,num_screen,scr_width);

	       /* write the files to the screen */

	       make_screen(nodes,dirptr,&args,disp2_id,curr_screen,num_file,
			   scr_file,num_slot,slot_width,text_flag);

	       /* reset screen and node pointers */

	       node_row = 0;
	       node_col = 0;                         
	       scr_row = (long) nodes[0][0].row;
	       scr_col = (long) nodes[0][0].column;

	       /* home the cursor */

	       cursor_flag++;		/* reset the cursor down below	      */
	    }

	    break;

	 case(SMG$K_TRM_LOWERCASE_L):	/**************************************/
	 case(SMG$K_TRM_UPPERCASE_L):	/*            Locate-file	      */
	 case(SMG$K_TRM_FIND):		/**************************************/

	    /* user wants to search for a filename; go for it */

	    temp = file_locate(dirptr,disp2_id,keybd_id,num_file,scr_width);

	    if(temp >= 0)
	    {
	       /* file was found; recompute the node indexes so that we
		* can change the cursor on the screen
		*/

	       new_screen = (temp / num_slot) + 1;

	       /* get us to the current screen */

	       temp2 = temp - ((new_screen - 1) * num_slot);
	       node_col = temp2 / num_row;
	       node_row = temp % num_row;

	       /* do we need to go to a new screen?  if so, make the new
		* one now
		*/

	       if(new_screen != curr_screen)
	       {
		  /* create the new screen by first putting the new page
		   * number in the upper-right and then rebuilding the
		   * main display
		   */

	       	  /* do we need to update the screen node pointers? */

		  if((new_screen == num_screen) ||
		     (curr_screen == num_screen))
		  {
		     /* set the nodes */
                                                      
		     set_nodes(nodes,&scr_file,num_screen,new_screen,
			       num_file,slot_width,num_col);
		  }

		  curr_screen = new_screen;

  		  /* put the new page number on the screen */

		  put_pagnum(disp1_id,curr_screen,num_screen,scr_width);

		  /* build the display for the new screen */

		  make_screen(nodes,dirptr,&args,disp2_id,curr_screen,num_file,
			      scr_file,num_slot,slot_width,text_flag);
	       }

	       /* make the cursor point to the file that was found */

	       scr_row = nodes[node_row][node_col].row;
	       scr_col = nodes[node_row][node_col].column;
	    }
	    else if(temp == -1)
	    {
	       /* no file found; just put up a message and that's it */

	       info_mess(disp2_id,"File not found\7",scr_width);
	       mess_flag++;
	    }

	    /* set the cursor where it is supposed to be, whether on the
	     * file that was found or the file that it was on when the
	     * failed search request was initiated
	     */

	    cursor_flag++;		/* reset the cursor down below	      */

	    break;

	 case(SMG$K_TRM_LOWERCASE_U):	/**************************************/
	 case(SMG$K_TRM_UPPERCASE_U):	/*              Unmark		      */
				  	/**************************************/

	    /* first get the entry pointer for the file selected */
                      
	    entptr = dirptr + ((curr_screen - 1) * (num_row * num_col)) +
		     (node_col * num_row) + node_row;

	    /* now cancel all commands for the file entry and free up the
	     * command structure that was allocated for it
	     */

	    mark_cancel(entptr);

	    /* remake the slot so it can be written */

	    temp = make_slot(slot_buf,entptr,&args,slot_width,text_flag);
	    mess_d.dsc$a_pointer = slot_buf;
	    mess_d.dsc$w_length = (USHORT) strlen(slot_buf);

	    /* now write the file information to the screen in the right spot */

	    rend_set = SMG$M_NORMAL;
	    scr_col++;			/* don't write in cursoring spot      */

	    status = SMG$PUT_CHARS(&disp2_id,&mess_d,&scr_row,&scr_col,0,
				   &rend_set,0,0);
                   
	    scr_col--;			/* reset to where it should be	      */

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

	    /* move to the next logical file on the screen; basically, we are
	     * just performing the same stuff we do for a down-arrow
	     */

	    temp = (short) nodes[node_row][node_col].down_row;
	    node_col = (short) nodes[node_row][node_col].down_col;
	    node_row = temp;
	    scr_row = (long) nodes[node_row][node_col].row;
	    scr_col = (long) nodes[node_row][node_col].column;

	    cursor_flag++;		/* reset the cursor, too	      */

	    break;

	 case(SMG$K_TRM_LOWERCASE_S):	/**************************************/
	 case(SMG$K_TRM_UPPERCASE_S):	/*            Select-file	      */
	 case(SMG$K_TRM_SELECT):	/**************************************/

	    /* first get the entry pointer for the file selected */
                      
	    entptr = dirptr + ((curr_screen - 1) * (num_row * num_col)) +
		     (node_col * num_row) + node_row;

	    /* now see if the file selected was a directory or just a file */

	    temp = strindex(entptr->filename,".DIR;");

	    if(temp > 0)
	    {
	       /* it was a directory; temporarily lop off everything
		* after the filename so that we may treat just the filename
		* as a string
		*/

	       entptr->filename[temp] = '\0';

	       /* find out where we are to put in the period and the
		* new filename in the directory name
		*/

	       tptr = dir_name + (ULONG) dir_length - 1L;

	       /* now insert the stuff in the right place to create the
		* NEW and IMPROVED directory name
		*/

	       cat(tptr,".",entptr->filename,"]");
	       entptr->filename[temp] = '.';	/* reset the filename	      */
	       exec_flag = 0;		/* must reset to keep things straight */

	       /* now we have the new directory name; change to this
		* directory and do a recursive call to ourselves
		*/

	       mess_d.dsc$a_pointer = dir_name;
	       mess_d.dsc$w_length = (USHORT) strlen(dir_name);

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

	       if(status != RMS$_NORMAL)
	       {
		  /* problems changing to directory; scream at the user
		   * and get out of here
		   */

		  info_mess(disp2_id,"Cannot change to selected directory\7",
			    scr_width);
	       	  mess_flag++;		/* set the info message flag	      */

		  /* reset the cursor */

		  cursor_flag++;	/* reset the cursor down below	      */

		  break;
	       }

	       /* now call ourselves and start over with the new directory */

	       /***********************************
	        *   R E C U R S I V E   C A L L   *
	        ***********************************/

	       do
	       {
		  temp = proc_dir();
	       }
	       while(temp == RECALL_PROC);

	       global_row = &scr_row;	/* reset for the current directory    */
	       global_col = &scr_col;

	       /* first reset the directory name the way it was */

	       tptr = strchr(dir_name,']'); /* find the end of the directory  */

	       while(*tptr != '.')	/* find start of last level of name   */
		  tptr--;

	       strcpy(tptr,"]");	/* cut off the last level	      */

	       /* change back to the directory that we should be in */

	       mess_d.dsc$a_pointer = dir_name;
	       mess_d.dsc$w_length = (USHORT) strlen(dir_name);

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

	       if(status != RMS$_NORMAL)
	       {
		  /* problems changing to directory; scream at the user
		   * and get out of here
		   */

		   info_mess(disp2_id,
			     "Cannot reset directory.....exiting...\7",
			     scr_width);
		   sleep(2);
		   LIB$STOP(status);
	       }

 	       if(temp == EMPTY_DIR)	/* did the user make a good choice?   */
	       {
		  /* the directory that was selected was empty */

		  info_mess(disp2_id,
			    "Directory is empty........... \7",scr_width);
		  if(exec_flag)
		  {
		     sleep(2);		/* only pause if we just executed     */
		     clear_mess(disp2_id,scr_width);
		  }
		  else
		     mess_flag++;	/* clear it on next keystroke	      */
	       }

	       /* reset some things on the screen since we are actually
		* returning from a directory
		*/
                        
	       if((args.text) && (!text_flag) && temp != EMPTY_DIR)
	       {
		  /* when we left this directory to go down, the
		   * text descriptors were not included (at least not
		   * not on the screen, anyway); when we were in
		   * one of the descendant directories, the text
		   * descriptors were selected; we must read them
		   * in here; first, update the slot width on the
		   * assumption that we can get the text descriptors
		   * for the current directory without any trouble and
		   * set the flag that signifies that we have text
		   * descriptors read in for the current directory
		   */

		  if(args.text == DISPLAY_TEXT)	/* room to display descrips?  */
		     slot_width += TEXT_MAX + 1; /* yes, assume we get them   */

		  text_flag = args.text;

		  /* erase the display to make sure we don't have any
		   * garbage left over if we need to reformat the screen
		   */

		  status = SMG$ERASE_DISPLAY(&disp2_id);

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

		  temp = get_text_des(dirptr,&curr_pool,&dir_text,pool_length,
				      num_file);

		  if(temp == BAD_FILE)
		  {
		     info_mess(disp2_id,
			       "Invalid format for text descriptor file\7",
			       scr_width);
		     mess_flag++;
		     text_flag = 0;
		     if(args.text == DISPLAY_TEXT)
		        slot_width = slot_width - (TEXT_MAX + 1);
		     sleep(1);
		  }
		  else if(temp == CANT_OPEN)
		  {
		     info_mess(disp2_id,"Cannot open text descriptor file\7",
			       scr_width);
		     mess_flag++;
		     text_flag = 0;
		     if(args.text == DISPLAY_TEXT)
		        slot_width = slot_width - (TEXT_MAX + 1);
		     sleep(1);
		  }
		  else if(temp == NO_FILE)
		  {
		     text_flag = 0;
		     if(args.text == DISPLAY_TEXT)
		        slot_width = slot_width - (TEXT_MAX + 1);
		  }
		  else if(temp == 0)
		  {
		     /* reset the screen parameters, just in case they need
		      * to be changed to reflect text descriptors
		      */

		     set_screen(&num_screen,&scr_file,num_file,curr_screen,
				scr_width,slot_width,&num_col,nodes);
		     num_slot = scr_file;
		  }
               }

	       if((temp == EMPTY_DIR && exec_flag) || temp != EMPTY_DIR)
	       {
		  /* we just executed commands in a directory so we must
		   * rebuild stuff
		   */

		  set_nodes(nodes,&scr_file,num_screen,curr_screen,num_file,
			    slot_width,num_col);

		  /* rewrite all of the stuff local to this directory */

		  put_spec(disp1_id,dir_name,num_screen,scr_width);
		  put_stat(disp3_id,num_block,num_file,scr_width);

		  if(args.text) 	/* text descriptor to write?	      */
		     put_text(disp1_id,dir_text,scr_width);

		  /* this is important; we have to look for the directory
		   * that we were on when the last Select was done and de-
		   * termine what page it is on; we might have to change the
		   * current page and everything just to put the cursor back
		   * in the right spot
		   */

		  screen_reset(nodes,dirptr,entptr,&node_row,&node_col,
			       &scr_file,&curr_screen,num_file,num_slot,
			       num_screen,slot_width,num_row,num_col);

		  /* put the new page number on the screen */

		  put_pagnum(disp1_id,curr_screen,num_screen,scr_width);

		  /* create and write the screen again for the current
		   * directory
		   */

		  make_screen(nodes,dirptr,&args,disp2_id,curr_screen,
			      num_file,scr_file,num_slot,slot_width,
			      text_flag);

		  exec_flag = 0;	/* make sure to reset this	      */

		  /* reset the screen row and column */

		  scr_row = nodes[node_row][node_col].row;
		  scr_col = nodes[node_row][node_col].column;
	       }
	    }
	    else
	    {
	       /* this is a file; process it */

	       if(entptr->size != 0)
	       {
		  /* the file is not empty; go ahead and try to look at it */

		  temp = file_select(entptr->filename,in_buf,disp4_id,paste_id,
				     keybd_id,scr_width);
	       }
	       else
	          temp = EMPTY_FILE;	/* file is empty		      */

	       switch(temp)		/* what happened with the file?	      */
	       {
		  case(CANT_OPEN):

		     info_mess(disp2_id,"Cannot open selected file\7",
			       scr_width);
		     mess_flag++;
		     break;

		  case(CANT_DISPLAY):

		     info_mess(disp2_id,"Cannot display selected file\7",
			       scr_width);
		     mess_flag++;
		     break;

		  case(EMPTY_FILE):

		     info_mess(disp2_id,"Selected file is empty\7",scr_width);
		     mess_flag++;
		     break;

		  default:                      
		     break;
	       }
	    }

	    exec_flag = 0;
	    cursor_flag++;		/* reset the cursor down below	      */
	    break;
                        
	 case(SMG$K_TRM_LOWERCASE_X):	/**************************************/
	 case(SMG$K_TRM_UPPERCASE_X):	/*             eXecute		      */
	 case(SMG$K_TRM_DO):		/**************************************/

	    if(args.confirm)		/* should we ask to make sure?	      */
	    {
	       /* the user wants to be asked if they REALLY want to execute
		* the commands for the directory
		*/

	       info_mess(disp2_id,"REALLY eXecute commands? [N] ",scr_width);

	       /* get a keystroke from the keyboard */

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

	       if(status == SMG$_EOF)
		  SYS$EXIT();		/* just get out right now	      */

	       if(status != SS$_NORMAL)
		  LIB$STOP(status);	/* error if we get here.....	      */

	       /* now look at what the keystroke is; if it is not Y or y,
		* then we DO NOT execute the commands for the directory
	        */

	       if((term_code != SMG$K_TRM_UPPERCASE_Y) &&
		  (term_code != SMG$K_TRM_LOWERCASE_Y))
	       {
		  /* clear out the prompt message first and then set the
		   * flag to reset the cursor
		   */

		  clear_mess(disp2_id,scr_width);
		  cursor_flag++;	/* reset the cursor		      */
		  break;		/* nope, don't do anything yet.....   */
	       }
	    }

	    /* execute the file commands for all of the files in the current
	     * directory; informational messages will appear for each action
	     * for each file
	     */

	    xecute(dirptr,&args,in_buf,dir_text,disp2_id,num_file,scr_width);
	    exec_flag++;		/* set flag saying we just executed   */

	    /* just fall through here and do what we would normally do for
	     * a REBUILD command; no sense in putting the code in two places,
	     * now is there?
	     */

	 case(SMG$K_TRM_CTRLR):		/**************************************/
					/*	 rebuild the directory	      */
					/**************************************/

	    /* tell the user we are doing something so they don't worry */

	    xmess(disp2_id,"Rebuilding directory information......");

	    /* decrement the recursion level before returning */

	    --level;

	    /* free up the memory pools first */

	    free_pool(first_pool);

	    /* now free up the memory for the directory information */

	    status = LIB$FREE_VM(&dirsize,&dirptr);

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

	    return(RECALL_PROC);	/* force proc_dir() to be recalled    */
	    break;

	 case(SMG$K_TRM_LOWERCASE_H):	/**************************************/
	 case(SMG$K_TRM_UPPERCASE_H):	/*               Help		      */
	 case(SMG$K_TRM_HELP):		/**************************************/

	    state = 0;			/* reset help internal static var     */
	    strcpy(help_buf,"MAINT ");	/* initialize the help line buffer    */

	    /* first ask for a topic to be sent to help */

	    status = get_string(keybd_id,disp2_id,RESP_MAX,response,
				"HELP Topic? ",scr_width);

	    if(status == SS$_NORMAL)
	    {
	       if(response[0] != '\0')
		  strcat(help_buf,response);

	       status = SMG$PASTE_VIRTUAL_DISPLAY(&disp4_id,&paste_id,
						  &WORK_BASE_ROW,
						  &WORK_BASE_COL,0);

	       if(status != SS$_NORMAL)
	       {
		  info_mess(disp2_id,"Error in on-line help\7",scr_width);
		  mess_flag++;
		  cursor_flag++;
		  break;
	       }

	       /* start the help session */

	       help_line.dsc$w_length = (USHORT) strlen(help_buf);

	       status = LBR$OUTPUT_HELP(put_help,0,&help_line,&helplib_d,0,
					get_help);

	       if(status != SS$_NORMAL && status != RMS$_EOF)
	       {
		  info_mess(disp2_id,"Error in on-line help\7",scr_width);
		  mess_flag++;
		  cursor_flag++;
	       }

	       /* pop the work virtual display off the screen */

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

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

	       /* clear out the work virtual display before continuing */

 	       SMG$ERASE_DISPLAY(&disp4_id);
	    }
	    else if(status == SMG$_EOF)
	    {
	       /* just repaint the screen here; the control-Z messes up the
		* screen sometimes

	       status = SMG$REPAINT_SCREEN(&paste_id);

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

	       clear_mess(disp2_id,scr_width);
	    }

	    cursor_flag++;
	    break;

	 case(SMG$K_TRM_LOWERCASE_D):	/**************************************/
	 case(SMG$K_TRM_UPPERCASE_D):	/*              Delete		      */
	 case(SMG$K_TRM_REMOVE):	/**************************************/

	    /* first get the entry pointer for the file indicated */

	    entptr = dirptr + ((curr_screen - 1) * (num_row * num_col)) +
		     (node_col * num_row) + node_row;

	    mark_delete(entptr); 	/* mark file for deletion	      */

	    temp = make_slot(slot_buf,entptr,&args,slot_width,text_flag);
	    mess_d.dsc$a_pointer = slot_buf;
	    mess_d.dsc$w_length = (USHORT) strlen(slot_buf);
	    slot_buf[slot_width] = '\0';

	    /* now write the file information to the screen in the right spot */

	    if(temp == 0)
	       rend_set = SMG$M_NORMAL;
	    else
	       rend_set = SMG$M_BOLD;

	    scr_col++;			/* don't write in cursoring spot      */

	    status = SMG$PUT_CHARS(&disp2_id,&mess_d,&scr_row,&scr_col,0,
				   &rend_set,0,0);

	    scr_col--;			/* reset to where it should be	      */

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

	    /* move to the next logical file on the screen; basically, we are
	     * just performing the same stuff we do for a down-arrow
	     */

	    temp = (short) nodes[node_row][node_col].down_row;
	    node_col = (short) nodes[node_row][node_col].down_col;
	    node_row = temp;
	    scr_row = (long) nodes[node_row][node_col].row;
	    scr_col = (long) nodes[node_row][node_col].column;

	    cursor_flag++;		/* reset the cursor, too	      */
	    break;

	 case(SMG$K_TRM_LOWERCASE_P):	/**************************************/
	 case(SMG$K_TRM_UPPERCASE_P):	/*              Protect		      */
					/**************************************/

	    /* first get the entry pointer for the file indicated */

	    entptr = dirptr + ((curr_screen - 1) * (num_row * num_col)) +
		     (node_col * num_row) + node_row;

	    /* tag the file on the screen */

	    tag_file(scr_row,scr_col,disp2_id);

	    /* get the new filename and mark the file to be renamed */

	    temp = mark_protect(entptr,disp2_id,keybd_id,scr_width);

	    if(temp != 0)		/* wot happen?			      */
	    {
	       info_mess(disp2_id,"Invalid protection string\7",scr_width);
	       mess_flag++;
	    }
	    else
	    {
	       /* highlite the file slot */

	       highlite(disp2_id,slot_buf,entptr,&args,scr_row,scr_col+1,
			slot_width,text_flag);
	    }

	    /* erase the file tag */

	    erase_tag(scr_row,scr_col,disp2_id);

	    /* reset the cursor to where it should be */

	    cursor_flag++;		/* reset the cursor down below	      */

	    break;

	 case(SMG$K_TRM_LOWERCASE_F):	/**************************************/
	 case(SMG$K_TRM_UPPERCASE_F):	/*              Finish		      */
					/**************************************/


	    if(level == 1)		/* are we at the top?		      */
	    {
	       /* beep and continue, ignoring the Finish command */

	       SMG$RING_BELL(&disp2_id,0);
	       break;			/* yes, we can't go up any more       */
	    }

	    /* check for marked files first; if some are marked, ask the user
	     * if Finishing is the right thing to do
	     */

	    if(check_marks(dirptr,num_file) == TRUE)
	    {
	       info_mess(disp2_id,
			 "Some files are marked.  Finish anyway? [N] ",
			 scr_width);

	       /* get a keystroke from the keyboard */

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

	       if(status == SMG$_EOF)
		  SYS$EXIT();		/* just get out right now	      */

	       if(status != SS$_NORMAL)
		  LIB$STOP(status);	/* error if we get here.....	      */

	       clear_mess(disp2_id,scr_width);
	       cursor_flag++;		/* reset cursor			      */

	       if(term_code != SMG$K_TRM_LOWERCASE_Y && 
  		  term_code != SMG$K_TRM_UPPERCASE_Y)
		  break;
	    }

	    /* the user is finished with the current directory; free up the
	     * memory that was allocated for the directory and just return;
	     * whether we return to the mainline or to a previous call to
  	     * proc_dir() is no matter to us at this point
	     */

	    level--;			/* decrement recursion level counter  */

	    /* free up the memory pools first */

	    free_pool(first_pool);

	    /* free up the memory for any command structures that might
	     * have been allocated
	     */

	    free_comm(dirptr,num_file);

	    /* now free up the memory for the directory information */

	    status = LIB$FREE_VM(&dirsize,&dirptr);

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

	    /* erase the main virtual display */

	    status = SMG$ERASE_DISPLAY(&disp2_id);

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

	    return(0);			/* go back to where we were called    */

	    break;

	 case(SMG$K_TRM_LOWERCASE_E):	/**************************************/
	 case(SMG$K_TRM_UPPERCASE_E):	/*              Expand		      */
					/**************************************/

	    /* first get the entry pointer for the file indicated */

	    entptr = dirptr + ((curr_screen - 1) * (num_row * num_col)) +
		     (node_col * num_row) + node_row;

	    /* display all of the information about the file on the screen */

	    temp = expand(entptr,&args,disp4_id,paste_id,keybd_id,scr_width);

	    if(temp != 0)
	    {
	       info_mess(disp2_id,"Cannot expand on file\7",scr_width);
	       mess_flag++;
	    }

	    cursor_flag++;		/* reset the cursor		      */

	    break;

	 case(SMG$K_TRM_LOWERCASE_R):	/**************************************/
	 case(SMG$K_TRM_UPPERCASE_R):	/*              Rename		      */
					/**************************************/

	    /* first get the entry pointer for the file indicated */

	    entptr = dirptr + ((curr_screen - 1) * (num_row * num_col)) +
		     (node_col * num_row) + node_row;

	    /* tag the file on the screen */

	    tag_file(scr_row,scr_col,disp2_id);

	    /* get the new filename and mark the file to be renamed */

	    temp = mark_copy_ren(entptr,&curr_pool,disp2_id,keybd_id,
				 pool_length,scr_width,RENAME);

	    if(temp != 0)		/* wot happen?			      */
	    {
	       info_mess(disp2_id,"Error in trying to rename file\7",scr_width);
	       mess_flag++;
	    }
	    else
	    {
	       /* highlite the file slot */

	       highlite(disp2_id,slot_buf,entptr,&args,scr_row,scr_col+1,
			slot_width,text_flag);
	    }

	    /* erase the file tag */

	    erase_tag(scr_row,scr_col,disp2_id);

	    /* reset the cursor to where it should be */

	    cursor_flag++;		/* reset the cursor down below	      */

	    break;

	 case(SMG$K_TRM_LOWERCASE_C):	/**************************************/
	 case(SMG$K_TRM_UPPERCASE_C):	/*               Copy 		      */
					/**************************************/

 	    /* first get the entry pointer for the file indicated */

	    entptr = dirptr + ((curr_screen - 1) * (num_row * num_col)) +
		     (node_col * num_row) + node_row;
 
	    /* tag the file on the screen */

	    tag_file(scr_row,scr_col,disp2_id);

	    /* get the new filename and mark the file to be copied */

	    temp = mark_copy_ren(entptr,&curr_pool,disp2_id,keybd_id,
				 pool_length,scr_width,COPY);

	    if(temp != 0)		/* wot happen?			      */
	    {
	       info_mess(disp2_id,"Error in trying to copy file\7",scr_width);
	       mess_flag++;
	    }
	    else
	    {
	       /* highlite the file slot */

	       highlite(disp2_id,slot_buf,entptr,&args,scr_row,scr_col+1,
			slot_width,text_flag);
	    }

	    /* erase the file tag */

	    erase_tag(scr_row,scr_col,disp2_id);

	    /* reset the cursor to where it should be */

	    cursor_flag++;		/* reset the cursor down below	      */

	    break;

	 case(SMG$K_TRM_LOWERCASE_T):	/**************************************/
	 case(SMG$K_TRM_UPPERCASE_T):	/*               Text		      */
					/**************************************/

 	    /* first save what file the cursor was pointing to */

	    entptr = dirptr + ((curr_screen - 1) * (num_row * num_col)) +
		     (node_col * num_row) + node_row;
 
	    if(!text_flag)
	    {
	       /* were text descriptors already turned on? */

	       if(!args.text)
	       {
		  /* now see what we need to set the text global run-time
		   * flag to (whether we should display text descriptors
		   * or not
		   */

		  save_width = slot_width;

		  if((slot_width + TEXT_MAX + 1) <= scr_width)
		  {
		     /* Good news!  The text descriptors will fit on the
		      * screen; update the current slot width but leave the
		      * global slot width alone
		      */                                 

		     slot_width = slot_width + TEXT_MAX + 1;
		     args.text = DISPLAY_TEXT;
		  }
		  else
		  {
		     /* no, the text descriptors will not fit on the screen;
		      * but we want them read in from now on anyway
		      */

		     args.text = SLOT_OVF;
		  }
	       }

	       /* text descriptors have NOT been read in for the current dir-
		* ectory; first we must see if the text descriptor file exists;
		* if it is there, we will use it; if not, we will ask if they
		* want to create one
		*/

	       text_flag = args.text;

	       temp = get_text_des(dirptr,&curr_pool,&dir_text,pool_length,
				   num_file);

	       if(temp == NO_FILE)
	       {
		  /* the text descriptor file does not exist; ask the user if
		   * it should be created
		   */

		  temp = create_text(keybd_id,disp2_id,scr_width);
	       }

	       switch(temp)		/* what happened with the file?	      */
	       {
		  case(NEW_FILE):	/* new text descriptor file?	      */

		     /* ask the user if the commands should be executed
		      * before rebuilding things
		      */

		     info_mess(disp2_id,
			     "Commands will be wiped out.  eXecute them? [y] ",
				scr_width);

		     /* read the keystroke */

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

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

		     clear_mess(disp2_id,scr_width);

		     if((term_code != SMG$K_TRM_LOWERCASE_N) &&
		        (term_code != SMG$K_TRM_UPPERCASE_N))
		     {
			/* execute the stuff first */

			/* make sure we don't try to create a text descriptor
			 * file before eXecuting the commands
			 */                                 

			xecute(dirptr,&args,in_buf,dir_text,disp2_id,num_file);
		     }

		     /* free up the memory pools first */

		     free_pool(first_pool);

		     /* now free up the memory for the directory info */

		     status = LIB$FREE_VM(&dirsize,&dirptr);

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

		     --level;		/* don't count this level anymore     */

		     /* return here and force the routine to be called
		      * again; this makes the function epilogue (don't
		      * you just love those programming language terms)
		      * do all of the work by allocating and initializing
		      * all of the automatic variables; this is a neat
		      * way to take advantage of the recursive structure
		      * we have set up for ourselves because all of the
		      * code to reinitialize a directory does not have
		      * to be duplicated
		      */

		     return(RECALL_PROC);

		  case(0):		/* text descriptors read in ok?	      */

		     /* the text descriptors were read in ok; now rebuild the
		      * nodes for the directory and remake the screen
		      */

		     text_flag = args.text;
		     set_screen(&num_screen,&scr_file,num_file,curr_screen,
				scr_width,slot_width,&num_col,nodes);
		     num_slot = scr_file;
		     set_nodes(nodes,&scr_file,num_screen,curr_screen,num_file,
			       slot_width,num_col);
		     put_spec(disp1_id,dir_name,num_screen,scr_width);
		     put_text(disp1_id,dir_text,scr_width);

		     /* we have to look for the file that was being point to
		      * when the Text command was done and determine what page
		      * it is on; we might have to change the current page and
		      * everything just to put the cursor back in the right spot
		      */

		     screen_reset(nodes,dirptr,entptr,&node_row,&node_col,
				  &scr_file,&curr_screen,num_file,num_slot,
				  num_screen,slot_width,num_row,num_col);

		     /* put the page number on the screen again, just in case
		      * it might have changed
		      */

		     put_pagnum(disp1_id,curr_screen,num_screen,scr_width);

		     /* clear the screen to make sure we don't have any
		      * any garbage left over when we put the text des-
		      * criptors on the screen
		      */

		     status = SMG$ERASE_DISPLAY(&disp2_id);

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

		     /* remake the virtual display now that it is clear */

		     make_screen(nodes,dirptr,&args,disp2_id,curr_screen,
				 num_file,scr_file,num_slot,slot_width,
				 text_flag);

		     /* reset the screen cursor values */

		     scr_row = nodes[node_row][node_col].row;
		     scr_col = nodes[node_row][node_col].column;

		     /* make the sure pool length is set properly */

		     pool_length = (long) ((num_file + 1) * ent_factor);
		     cursor_flag++;	/* reset the cursor		      */
		     break;

		  case(BAD_FILE):

		     /* bad format for text descriptor file */

		     info_mess(disp2_id,
			       "Invalid format for text descriptor file\7",
			       scr_width);

		     /* reset the slot width */

		     slot_width = save_width;
		     text_flag = 0;
		     mess_flag++;
		     break;

		  case(CANT_OPEN):

		     info_mess(disp2_id,"Cannot open text descriptor file\7",
			       scr_width);

		     /* reset the slot width */

		     slot_width = save_width;
		     text_flag = 0;
		     mess_flag++;
		     break;

		  case(DONT_CREATE):

		     /* reset the slot width */

		     slot_width = save_width;
		     text_flag = 0;
		     break;

		  default:

		     /* reset the slot width */

		     slot_width = save_width;
		     text_flag = 0;
		     break;
	       }
	    }
	    else
	    {
	       /* the text descriptors have already been read in; get the
		* text descriptor for the selected file and store it in a
		* memory pool
		*/

	       /* first get the entry pointer for the file indicated */

	       entptr = dirptr + ((curr_screen - 1) * (num_row * num_col)) +
			(node_col * num_row) + node_row;

	       /* we only allow text descriptors to be specified for the
		* highest version of a file; the version numbers are not
		* stored in the text descriptor file itself so they really
		* don't matter; we just make sure that there is only one text
		* descriptor per filename right here to make things easier on
		* ourselves later
		*/

	       if(entptr != dirptr)
	       {
		  /* this is NOT the first file in the directory, so we have
		   * to look at the previous filename in the directory to make
		   * sure that the one selected by the user is the highest
		   * version
		   */

		  temp = (short) strindex(entptr->filename,";");

		  if(strncmp(entptr->filename,(entptr-1)->filename,temp) == 0)
		  {
		     /* we have a match; thus, we can't allow a text descriptor
		      * on this file
		      */

		     SMG$RING_BELL(&disp2_id,0);
		     break;		/* get out of the switch statement    */
		  }
	       }

	       /* tag the file on the screen */

	       tag_file(scr_row,scr_col,disp2_id);

	       /* get the text descriptor and mark the file */

	       temp = mark_text(entptr,&curr_pool,disp2_id,keybd_id,pool_length,
				scr_width);

	       if(temp != 0)		/* did everything go ok?	      */
	       {
		  info_mess(disp2_id,"Invalid text descriptor\7",scr_width);
		  mess_flag++;
	       }
	       else
	       {
		  /* highlite the file slot */

		  highlite(disp2_id,slot_buf,entptr,&args,scr_row,scr_col+1,
			   slot_width,text_flag);
	       }

	       /* erase the file tag */

	       erase_tag(scr_row,scr_col,disp2_id);
	    }

	    cursor_flag++;		/* reset the cursor down below	      */
	    break;

	 case(SMG$K_TRM_LOWERCASE_Q):	/**************************************/
	 case(SMG$K_TRM_UPPERCASE_Q):	/*               Quit		      */
	 				/**************************************/

	    /* check for marked files first; if some are marked, ask the user
	     * if Quitting is the right thing to do
	     */

	    for(temp = 0,status = FALSE; temp < level && status == FALSE;
		temp++)
	       status = check_marks(dirmem[temp].ptr,dirmem[temp].num_file);

	    if(status == TRUE)
	    {
	       info_mess(disp2_id,
			 "Some files are marked.  Quit anyway? [N] ",
			 scr_width);

	       /* get a keystroke from the keyboard */

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

	       if(status == SMG$_EOF)
		  SYS$EXIT();		/* just get out right now	      */

	       if(status != SS$_NORMAL)
		  LIB$STOP(status);	/* error if we get here.....	      */

	       clear_mess(disp2_id,scr_width);
	       cursor_flag++;		/* reset cursor			      */

	       if(term_code != SMG$K_TRM_LOWERCASE_Y && 
  		  term_code != SMG$K_TRM_UPPERCASE_Y)
		  break;

	    }

	    status = SMG$DISABLE_BROADCAST_TRAPPING(&paste_id);
             
	    if(status != SS$_NORMAL)
	       LIB$STOP(status);

	    /* change the default directory to where we should be now */

	    mess_d.dsc$a_pointer = user_dir;
	    mess_d.dsc$w_length = (USHORT) strlen(user_dir);

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

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

	    exit();			/* now we can exit		      */

	    break;
                                    
	 case(SMG$K_TRM_EXCLAMATION_POINT):
  	 case(SMG$K_TRM_DOLLAR_SIGN):	/**************************************/
					/*           DCL escape		      */
	 				/**************************************/

	    /* the user wants to suspend MAINT and execute just one command;
	     * prompt them for it and then just fall through and pretend we
	     * are doing a real ^Push command; the response string being
	     * non-NULL will cause the push() routine to execute LIB$SPAWN
	     * with a command string specified; a real ^Push command will
	     * have a NULL response string which cause a LIB$SPAWN to be
	     * called without a command to be executed
	     */

	    status = get_string(keybd_id,disp2_id,
			        scr_width-sizeof(cmd_prompt),response,
			        cmd_prompt,scr_width);

	    if(status == SMG$_EOF)
	    {
	       cursor_flag++;		/* reset the cursor		      */
	       clear_mess(disp2_id,scr_width);
	       break;
	    }
	    else if(status != SS$_NORMAL)
	    {
	       info_mess(disp2_id,"Cannot read from display\7",scr_width);
	       mess_flag++;		/* .... and clear it out later	      */
	       cursor_flag++;		/* reset the cursor down below	      */
	       break;
	    }

	    /* just fall through */

	 case(SMG$K_TRM_CTRLP):		/**************************************/
	 case(SMG$K_TRM_INSERT_HERE):	/*              ^Push		      */
	 				/**************************************/

	    /* first we have to disable BROADCAST_TRAPPING because LIB$SPAWN
	     * has fits if we don't
	     */

	    status = SMG$DISABLE_BROADCAST_TRAPPING(&paste_id);

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

	    /* shut off the trapping of control-C's and control-Y's so that
	     * they will worked in the spawned process
	     */

	    mask = 0L;
	    status = SMG$SET_OUT_OF_BAND_ASTS(&paste_id,&mask,ctrl_ast,0);

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

	    /* clear out a place to tell the user how to get back here */

	    temp_wid = (long) scr_width; /* get a long value for scr_width    */

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

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

	    /* tell the user how to get back after they are done messing
	     * around...
	     */

	    if(!response[0])
	       printf("Type \"log\" to return to MAINT\n");

	    if(push(response) != SS$_NORMAL) /* push to a subprocess and wait */
	    {
	       /* problems creating the subprocess; tell the user and
		* get out of here
		*/

	       info_mess(disp2_id,"Cannot create subprocess\7",scr_width);
	       mess_flag = 1;		/* set the info mess flag	      */
	       break;
	    }

	    cursor_flag++;		/* reset the cursor down below	      */

	    if(response[0])
	    {
	       /* ask for a return if returning from just a single command;
		* this pauses the screen just in case the user forgot to
		*/

	       printf("\nPress RETURN to return to MAINT: ");
	       getchar();
	    }

	    /* now that we are back, turn on BROADCAST_TRAPPING again */

	    status = SMG$SET_BROADCAST_TRAPPING(&paste_id,brdcast_ast,0);

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

	    /* turn on the trapping of control-C's and control-Y's again */

	    mask = (1 << 0x3) | (1 <<0x19); /* trap control-C and control-Y   */
	    status = SMG$SET_OUT_OF_BAND_ASTS(&paste_id,&mask,ctrl_ast,0);

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

	    /* rewrite the options line because we trashed it on the way out */

	    put_options(disp3_id,scr_width);

	    /* repaint the screen so that things are the same as before */

	    status = SMG$REPAINT_SCREEN(&paste_id);

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

	    /* write the directory statistics line to clear out any messages
	     * from the AST routine that might be hanging around
	     */

	    put_stat(disp3_id,num_block,num_file,scr_width);

	    response[0] = '\0';		/* clear this for next time through   */
	    break;

	 case(SMG$K_TRM_CTRLW):		/**************************************/
					/*           repaint screen	      */
					/**************************************/

	    if(mail_note || phone_note) /* need to clear a broadcast message? */
	    {
	       mail_note = 0;		/* clear the notification flags	      */
	       phone_note = 0;
	       put_stat(disp3_id,num_block,num_file,scr_width);
	    }

	    status = SMG$REPAINT_SCREEN(&paste_id);

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

	    break;


	 case(SMG$K_TRM_LOWERCASE_G):	/**************************************/
	 case(SMG$K_TRM_UPPERCASE_G):	/*                Goto		      */
					/**************************************/

	    /* ask for a new screen number */

	    temp = get_scr_num(disp2_id,keybd_id,curr_screen,num_screen,
			       scr_width);

	    if(temp == BAD_SCREEN_NO)	/* was it a bad screen number?	      */
	    {
	       info_mess(disp2_id,"\07Invalid page number",scr_width);
	       mess_flag++;
	    }
	    else
	    {
	       /* was the page number specified different from the
		* current one?  If not, don't do anything
		*/

	       if(curr_screen != temp)
	       {
		  /* the page number specified was valid; set up the new page */

		  curr_screen = temp;	/* store the new current screen	      */

		  /* update the screen nodes array */

		  set_nodes(nodes,&scr_file,num_screen,curr_screen,num_file,
			    slot_width,num_col);

		  /* update the page number in the corner */

		  put_pagnum(disp1_id,curr_screen,num_screen,scr_width);

		  /* remake the screen to reflect the new screen specified */

		  make_screen(nodes,dirptr,&args,disp2_id,curr_screen,num_file,
			      scr_file,num_slot,slot_width,text_flag);

		  /* reset the screen and node pointers */

		  node_row = 0;
		  node_col = 0;
		  scr_row = (long) nodes[0][0].row;
		  scr_col = (long) nodes[0][0].column;
	       }
	    }

	    cursor_flag++;		/* set the cursor		      */

	    break;

	 case(SMG$K_TRM_LOWERCASE_A):	/**************************************/
	 case(SMG$K_TRM_UPPERCASE_A):	/*               ACL		      */
					/**************************************/

	    /* user wants to edit the ACL for the specified object; first
	     * get the entry pointer for the file indicated
	     */

	    entptr = dirptr + ((curr_screen - 1) * (num_row * num_col)) +
		     (node_col * num_row) + node_row;

	    status = edit_acl(entptr->filename);

	    if(status != TPU$_NORMAL)
	    {
	       info_mess(disp2_id,"Error editing ACL\7",scr_width);
	       mess_flag++;		/* .... and clear it out later	      */
	       cursor_flag++;		/* reset the cursor down below	      */
	    }

	    /* we need to repaint the screen after exiting the ACL editor */

	    status = SMG$REPAINT_SCREEN(&paste_id);

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

	    break;

	 case(SMG$K_TRM_LOWERCASE_W):	/**************************************/
	 case(SMG$K_TRM_UPPERCASE_W):	/*               Who		      */
	     				/**************************************/

	    /* tell the user who write this monstrosity..... */

	    info_mess(disp2_id,auth_str,scr_width);
	    mess_flag++;
	    cursor_flag++;
	    break;

	 case(SMG$K_TRM_LOWERCASE_V):	/**************************************/
	 case(SMG$K_TRM_UPPERCASE_V):	/*               Version              */
	     				/**************************************/

	    /* tell the user what version this is.... */

	    info_mess(disp2_id,version_str,scr_width);
	    mess_flag++;
	    cursor_flag++;
	    break;

	 case(0):			/**************************************/
      					/*        something weird  :-C	      */
					/**************************************/
	    break;

	 default:			/**************************************/
					/*            all others	      */
	     				/**************************************/
	    /* scream at the user */

	    status = SMG$RING_BELL(&disp2_id,0);
	    break;

      } /* switch(term_code) */

      /* do we need to set the position of the cursor? */

      if(cursor_flag)
      {
	 status = SMG$SET_CURSOR_ABS(&disp2_id,&scr_row,&scr_col);

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

	 cursor_flag = 0;		/* clear the cursor set flag	      */
      }

   } /* for(;;) */			/* end of main processing loop	      */

}	/*** proc_dir ***/
/*eject*/
/*******************************************************************************
********************************************************************************

  Function:	initialize

  Purpose:	This routine initilalizes all of the SMG stuff that will be
		needed later.  This includes the pasteboard, virtual keyboard,
		and all of the virtual displays.

		This routines also sets the pasteboard to 132-column mode if
		it was specified on the command line with the /WIDE argument.

  Global variables:

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

  Return Codes:

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

  Termination Codes:

	Code			Reason
	----			------
	status			return code from various System Service calls  

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

static void initialize(paste_id,keybd_id,disp1_id,disp2_id,disp3_id,disp4_id,
		       inp_d,out_d,scr_width)
					/*******   FORMAL  PARAMETERS   *******/
	 ULONG	  *paste_id,		/* pasteboard id		      */
		  *keybd_id,		/* virtual keyboard id		      */
		  *disp1_id,		/* directory info virtual display id  */
		  *disp2_id,		/* main virtual display id	      */
		  *disp3_id,		/* options virtual display id	      */
		  *disp4_id;		/* select file virtual display id     */
struct	 dsc$descriptor_s
		  *inp_d,		/* input device			      */
		  *out_d;		/* output device		      */
	 short	  *scr_width;		/* width of terminal screen	      */
                                        
{	/*** initialize ***/
					/********   LOCAL  VARIABLES   ********/
register ULONG	  status;		/* return code status		      */
static	 ULONG	  rows,			/* number of rows in pasteboard	      */
		  columns,		/* number of columns in pasteboard    */
		  video_attr,		/* virtual display video attributes   */
		  rend_set,		/* display rendition setting	      */
		  rend_comp,		/* display rendition complement	      */
		  mask,			/* AST-trap mask longword	      */
		  mode;			/* control mode options		      */
static	 long	  start_row,		/* start row for CHANGE_RENDITION     */
		  start_col,		/* start col for CHANGE_RENDITION     */
		  num_row,		/* temporary number of rows	      */
		  num_col;		/* temporary number of columns	      */


   /* create the pasteboard first */

   status = SMG$CREATE_PASTEBOARD(paste_id,out_d,&rows,&columns,0);

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

   if(*scr_width == 132)		/* wide-mode specified?		      */
   {
      /* wants to be cute with wide mode, eh....?  we'll try but we aren't
       * guaranteeing anything; it's up to the user to make sure that their
       * terminal can handle it
       */

      status = SMG$CHANGE_PBD_CHARACTERISTICS(paste_id,&132,&columns,&24,&rows);

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

      if(columns != 132)
	 *scr_width = 80;		/* reset screen width		      */
   }

   /* create the directory info line virtual display; this will be at the
    * top of the screen and will contain 1) the name of the current directory,
    * and 2) the page number of the current page of the directory along with
    * the number of pages in the directory; the second line is reserved for
    * the text descriptor for the directory
    */

   video_attr = SMG$M_NORMAL;		/* normal video for this one	      */
   num_row = DIR_INFO_ROWS;		/* set number of rows and colums      */
   num_col = columns;

   status = SMG$CREATE_VIRTUAL_DISPLAY(&num_row,&num_col,disp1_id,0,
				       &video_attr,0);

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

   /* create the main virtual display which will eventually contain the in-
    * formation about the files in the directory; normal video attributes
    * for this display
    */

   num_row = MAIN_ROWS;			/* set number of rows and columns     */
   num_col = columns;

   status = SMG$CREATE_VIRTUAL_DISPLAY(&num_row,&num_col,disp2_id,0,0,0);

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

   /* create the workhorse virtual display; this display will be used for
    * a number of different tasks, including Selecting a file and Xpanding
    * a file
    */

   num_row = WORK_ROWS;			/* set number of rows and columns     */
   num_col = columns;

   status = SMG$CREATE_VIRTUAL_DISPLAY(&num_row,&num_col,disp4_id,0,0,0);

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

   /* now create the options virtual display; this will be at the bottom
    * of the screen and will contain the directory statistics line that
    * will have the number of files and blocks for the directory as well
    * as a list of the options available to the user
    */

   num_row = OPT_INFO_ROWS;		/* set number of rows and colums      */
   num_col = columns;
   video_attr = SMG$M_NORMAL;

   status = SMG$CREATE_VIRTUAL_DISPLAY(&num_row,&num_col,disp3_id,0,&video_attr,
				       0);

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

   /* put in the options line for the bottom display */

   put_options(*disp3_id,*scr_width);

   /* create the keyboard for the I/O device */
   
   status = SMG$CREATE_VIRTUAL_KEYBOARD(keybd_id,inp_d,0,0,0);

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

   /* now paste the three main virtual displays to the pasteboard */

   status = SMG$PASTE_VIRTUAL_DISPLAY(disp1_id,paste_id,&DIR_INFO_BASE_ROW,
				      &DIR_INFO_BASE_COL,0);

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

   status = SMG$PASTE_VIRTUAL_DISPLAY(disp2_id,paste_id,&MAIN_BASE_ROW,
				      &MAIN_BASE_COL,0);

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

   status = SMG$PASTE_VIRTUAL_DISPLAY(disp3_id,paste_id,&OPT_INFO_BASE_ROW,
				      &OPT_INFO_BASE_COL,0);

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

   /* make sure 1) the screen is cleared when we exit, and 2) minimal update
    * is enabled
    */

   mode = SMG$M_CLEAR_SCREEN | SMG$M_MINUPD;
   status = SMG$CONTROL_MODE(paste_id,&mode,0);

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

   /* trap control-C's and control-Y's so that when they are detected, will call
    * a routine so that the program can continue as if nothing has happened
    */

   mask = (1 << 0x3) | (1 <<0x19);	/* trap control-C and control-Y	      */
   status = SMG$SET_OUT_OF_BAND_ASTS(paste_id,&mask,ctrl_ast,0);

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

   /* trap broadcast messages during execution; upon exit, this is turned
    * off so that any broadcast messages that arrived during the run of the
    * program are sent to the terminal
    */

   status = SMG$SET_BROADCAST_TRAPPING(paste_id,brdcast_ast,0);

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

   return;

}	/*** initialize ***/
/*eject*/
/*******************************************************************************
********************************************************************************

  Function:	set_screen

  Purpose:	Compute values for parameters that relate to the screen,
		including number of columns, number of files, and the number
		of screens needed for the directory.

  Global variables:

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

  Return Codes:

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

  Termination Codes:

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

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

static void set_screen(num_screen,scr_file,num_file,curr_screen,scr_width,
		       slot_width,num_col,nodes)
					/*******   FORMAL  PARAMETERS   *******/
	 short	  *num_screen,		/* number of screens in directory     */
		  *scr_file,		/* number of file slots per screen    */
	 	  num_file,		/* number of files in directory	      */
		  curr_screen,		/* current screen		      */
		  scr_width,		/* width of screen		      */
		  slot_width,		/* width of file slot		      */
		  *num_col;		/* number of columns for full screen  */
register NODE_DEF nodes[][MAX_NODE_COL+1];

{	/*** set_screen ***/


   /* compute the number of files per row of the screen */

   *num_col = scr_width/(slot_width + 1);

   /* now compute how many file slots will fit on a screen */

   *scr_file = *num_col * MAX_NODE_ROW;

   /* compute how many screens will be needed for this directory */

   *num_screen = num_file/(*scr_file);

   if((num_file % (*scr_file)) != 0)
      *num_screen += 1;

   return;
             
}	/*** set_screen ***/
/*eject*/
/*******************************************************************************
********************************************************************************

  Function:	set_nodes

  Purpose:	Initialize the screen slot node pointers for the current
		directory.

  Global variables:

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

  Return Codes:

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

  Termination Codes:

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

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

static void set_nodes(nodes,scr_file,num_screen,curr_screen,num_file,slot_width,
		      num_col)
					/*******   FORMAL  PARAMETERS   *******/
register NODE_DEF nodes[][MAX_NODE_COL+1]; /* screen node matrix	      */
	 short	  *scr_file,		/* number of files on current screen  */
	 	  num_screen,		/* number of screens for directory    */
		  curr_screen,		/* current screen		      */
		  num_file,		/* number of files in directory	      */
		  slot_width,		/* number of characters for file slot */
		  num_col;		/* number of columns per screen	      */

{	/*** set_nodes ***/
					/********   LOCAL  VARIABLES   ********/
register NODE_DEF *tptr;		/* temporary node pointer	      */
register int	  i,			/* loop and array index		      */
     		  j;			/* ditto....			      */
register short	  file_count;		/* number of files processed	      */
static	 short	  left_val,		/* left pointer for node	      */
		  up_val,		/* up pointer for node		      */
		  column_val,		/* current column value		      */
		  row_val,		/* current row value		      */
		  num_row,		/* number of rows for this screen     */
		  full_row,		/* number of full rows for screen     */
		  full_col;		/* number of full columns	      */


   if(curr_screen == num_screen)	/* if this is the last page	      */
   {
      /* recompute the number of columns; it might be different because
       * this is the last page of the directory
       */

      /* compute how many files will be on this screen of the directory */

      *scr_file = num_file - (num_col * MAX_NODE_ROW * (curr_screen - 1));

      /* now compute how many columns are needed for the last screen */

      num_col = *scr_file / MAX_NODE_ROW;
      if((*scr_file % MAX_NODE_ROW) != 0)
	 num_col++;
   }
   else
   {
      /* compute how many files will be on this screen of the directory */

      *scr_file = num_col * MAX_NODE_ROW;
   }

   if(*scr_file < MAX_NODE_ROW)		/* determine number of rows	      */
      num_row = *scr_file;
   else
      num_row = MAX_NODE_ROW;

   left_val = num_col - 1;		/* set left and right pointers	      */
   j = 0;				/* start with the first column	      */
   column_val = 1;			/* cursor on screen starts @ (1,1)    */
   file_count = 0;
   full_col = *scr_file / MAX_NODE_ROW; /* calculate number of full columns   */

   if(full_col == 0)			/* must have at least one full column */
      full_col = 1;

   full_row = *scr_file - (full_col * num_row);
   if(full_row == 0)
      full_row = *scr_file / full_col;

   /* first initialize the node matrix just like it would be a full screen;
    * then we will "trim" the edges to make sure that things point to the right
    * places in case the cursor would to need to wrap around the screen
    */

   up_val = num_row - 1;
   i = 0;

   while(i < num_row)
   {
      j = 0;
      left_val = num_col - 1;		/* left j value			      */
      column_val = 1;

      while(j < num_col)
      {
	 tptr = &(nodes[i][j]);		/* get a pointer to the current node  */
	 tptr->right_col = (j + 1) % num_col;
	 tptr->left_col = left_val;
	 tptr->up_col = j;
	 tptr->down_col = j;

	 tptr->right_row = i;
	 tptr->left_row = i;
	 tptr->up_row = up_val;
	 tptr->down_row = (i + 1) % num_row;

	 tptr->row = i + 1;
	 tptr->column = column_val;
	 column_val = column_val + slot_width + 2;
	 left_val = (left_val + 1) % num_col;
	 j++;
      }
      up_val = (up_val + 1) % num_row;
      i++;
   }

   /* now trim the edges of the matrix just in case this is the last screen
    * of the directory
    */

   /* first go along the top of the screen */

   i = 0;
   for(j = 0; j < num_col; j++)
   {
      tptr = &(nodes[i][j]);		/* get a pointer to the node	      */
      if(!j)				/* are we in the top left node?	      */
      {
	 /* top left node; the values for going up from this node should
	  * point to bottom node in the last column on the screen
	  */

	 tptr->up_row = full_row - 1;
	 tptr->up_col = num_col - 1;
      }
      else
      {
	 tptr->up_col = j - 1;
	 tptr->up_row = num_row - 1;
      }
   }

   /* now go along the left of the screen */

   j = 0;

   for(i = 0; i < num_row; i++)
   {
      tptr = &(nodes[i][j]);
      if(!i)
      {
	 /* we are in the top left of the screen; going left from here
	  * should go to the bottom, right-most node
	  */

	 tptr->left_row = full_row - 1;
	 tptr->left_col = num_col - 1;
      }
      else
      {
	 if(i < full_row + 1)
	    tptr->left_col = num_col - 1;
	 else
	    tptr->left_col = full_col - 1;

	 tptr->left_row = (i - 1) % num_row;
      }
   }

   /* trim the bottom full columns */

   i = num_row - 1;
   for(j = 0; j < full_col; j++)
   {
      tptr = &(nodes[i][j]);		/* get a pointer to the node	      */
      tptr->down_col = j + 1;
   }

   /* now go along the right side of the last full column */

   j = full_col - 1;
   for(i = full_row; i < num_row; i++)
   {
      tptr = &(nodes[i][j]);
      tptr->right_row = (i + 1) % num_row;
      tptr->right_col = 0;
   }

   /* trim along the last column, whether it be full or not */

   j = num_col - 1;
   for(i = 0; i < full_row; i++)
   {
      tptr = &(nodes[i][j]);
      tptr->right_row = (i + 1) % num_row;
      tptr->right_col = 0;
   }

   /* set the last node on the screen; this is a special case */

   tptr = &(nodes[full_row-1][num_col-1]);
   tptr->down_row = 0;
   tptr->down_col = 0;

   return;

}	/*** set_nodes ***/
/*eject*/
/*******************************************************************************
********************************************************************************

  Function:	get_args

  Purpose:	Process the command-line arguments, setting the flags for
		what arguments are specified and determining the slot width.
		Also calculate the entry size memory allocation factor for
		allocating memory pools.

  Global variables:

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

  Return Codes:

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

  Termination Codes:

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

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

static void get_args(helplib_d,args,scr_width)
					/*******   FORMAL  PARAMETERS   *******/
struct	 dsc$descriptor_s  *helplib_d;	/* help library descriptor	      */
register ARG_DEF  *args;		/* run-time argument flags	      */
	 short	  *scr_width;		/* screen width			      */

{	/*** get_args ***/
	     				/********   LOCAL  VARIABLES   ********/
	 ULONG	  status;		/* return code status holder	      */
	 USHORT	  leng;			/* length returned from CLI$GET_VALUE */
static	 char	  help_buf[STR_MAX+1],	/* help line for accessing help libr. */
		  buf[STR_MAX+1];	/* for getting command-line options   */
static $DESCRIPTOR(help_line,help_buf);	/* for LBR$OUTPUT_HELP's sake	      */
static $DESCRIPTOR(conf_d,"CONFIRM");	/* descriptors for command-line args  */
static $DESCRIPTOR(date_d,"DATE");
static $DESCRIPTOR(full_d,"FULL");
static $DESCRIPTOR(help_d,"HELP");
static $DESCRIPTOR(prot_d,"PROTECTION");
static $DESCRIPTOR(size_d,"SIZE");
static $DESCRIPTOR(sort_d,"SORT");
static $DESCRIPTOR(text_d,"TEXT");
static $DESCRIPTOR(wide_d,"WIDE");
static $DESCRIPTOR(file_d,"INFILE");
static $DESCRIPTOR(buf_d,buf);		/* for getting command-line options   */

             
   if(CLI$PRESENT(&help_d) == CLI$_PRESENT)
   {

      /* set up the initialize HELP line, then check to see if anything
       * was specified on the command line as a sub-topic
       */

      strcpy(help_buf,"MAINT ");

      if(CLI$GET_VALUE(&file_d,&buf_d,&leng) != CLI$_ABSENT)
      {
	 /* the user specified a help option on the command line; add it
	  * to the help line so that can go right to the topic
	  */

	 buf[leng] = '\0';		/* make it a string first	      */
	 strcat(help_buf,buf);		/* add it to the original help line   */
      }

      help_line.dsc$w_length = (USHORT) strlen(help_buf);

      /* allow the user to access the help library and then exit */

      status = LBR$OUTPUT_HELP(LIB$PUT_OUTPUT,0,&help_line,helplib_d,0,
			       LIB$GET_INPUT);
      if(status != SS$_NORMAL)
	 LIB$STOP(status);
      else
	 exit();			/* just exit from here		      */
   }
             
   if(CLI$PRESENT(&wide_d) == CLI$_PRESENT)
   {
      args->wide = 1;
      *scr_width = 132;
   }
   else
      *scr_width = 80;

   if(CLI$PRESENT(&date_d) == CLI$_PRESENT)
      args->date = 1;

   if(CLI$PRESENT(&size_d) == CLI$_PRESENT)
      args->size = 1;

   if(CLI$PRESENT(&prot_d) == CLI$_PRESENT)
      args->prot = 1;

   if(CLI$PRESENT(&full_d) == CLI$_PRESENT)
   {
      args->full = 1;			/* turn on full mode flag	      */
      args->prot = 1;
      args->date = 1;
      args->size = 1;
   }

   if(CLI$PRESENT(&text_d) == CLI$_PRESENT)
   {
      args->text = DISPLAY_TEXT;	/* we will TRY to display them....    */
      args->text_startup = TRUE;
   }

   if(args->date && args->size && args->prot)
      args->full = 1;			/* might just as well say that the    */
					/* user specified /FULL		      */

   if(CLI$PRESENT(&sort_d) == CLI$_PRESENT)
   {
      /* find out what the sort option is */

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

      if(status != SS$_NORMAL)
	 args->sort = 0;
      else
      {
	 /* see what the /SORT option is */

	 buf[leng] = '\0';		/* make it a string		      */
	 if(strcmp(buf,"TYPE") == 0)
	    args->sort = TYPE;
	 else if(strcmp(buf,"DATE") == 0)
	    args->sort = DATE;
	 else if(strcmp(buf,"SIZE") == 0)
	    args->sort = SIZE;
	 else
	    args->text = 0;
      }
   }

   if(CLI$PRESENT(&conf_d) == CLI$_PRESENT)
      args->confirm = 1;

   return;

}	/*** get_args ***/
/*eject*/
/*******************************************************************************
********************************************************************************

  Function:	set_factors

  Purpose:	Set the slot width and entry size memory allocation factors
		for the session.

  Global variables:

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

  Return Codes:

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

  Termination Codes:

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

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

static void set_factors(args,slot_width,ent_factor,scr_width)
					/*******   FORMAL  PARAMETERS   *******/
register ARG_DEF  *args;		/* run-time arguments		      */
	 short	  *slot_width,		/* width of file slot on screen	      */
		  *ent_factor,		/* ent. size memory allocation factor */
		  scr_width;		/* screen width			      */
             
{	/*** set_factors ***/

   /* initialize the width to include the length of the filename that
    * will be displayed and the spot to hold the cursor; also update the
    * the entry size factor for later
    */

   *slot_width = NAME_MAX + 1;
   *ent_factor = FUDGE_FACTOR;

   if(args->full)			/* full options selected?	      */
   {
      /* just make the factors reflect full info on the screen */

      *slot_width = FULL_MAX;
      *ent_factor = *ent_factor + DATE_MAX + PROT_MAX + 1;
   }
   else
   {
      /* nope, we have to see what info should be included to get the slot
       * width and the ent 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;
      }
   }

   /* see if text descriptors were selected */

   if(args->text)
   {
      *slot_width = *slot_width + TEXT_MAX + 1;
      *ent_factor = *ent_factor + TEXT_MAX + 1;

      /* now see if there is enough room to include the text descriptors for
       * the individual files on the screen
       */

      if(*slot_width > scr_width)
      {
	 /* nope, too wide for the screen */

	 *slot_width = *slot_width - (TEXT_MAX + 1);
	 args->text = SLOT_OVF;
      }
   }

   if(*slot_width == FULL_MAX)
   {
      args->full = 1;
   }
   else
   {

      if(*slot_width == (NAME_MAX + 1))
	 args->def = 1;			/* just default information	      */
      else
	 args->def = 0;			/* clear just in case		      */
   }

   return;

}	/*** set_factors ***/
/*eject*/
/*******************************************************************************
********************************************************************************

  Function:  	get_dir_mem

  Purpose:	Compute the amount of memory needed for the current directory
		and allocate it.  Also set the global pointer to the base of
		the memory so that it may be used for sorting if so desired.

  Global variables:

	Name			Examine/Modify/Use/Read/Write
	----			-----------------------------
	baseptr				  X	X

  Return Codes:

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

  Termination Codes:

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

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

static void get_dir_mem(dirptr,dirsize,num_file)
					/*******   FORMAL  PARAMETERS   *******/
	 ENT_DEF  **dirptr;		/* where to store pointer to memory   */
	 long	  *dirsize;		/* amount of memory allocated	      */
	 short	  num_file;		/* number of files in directory	      */

{	/*** get_dir_mem ***/
					/********   LOCAL  VARIABLES   ********/
	 ULONG	  status;		/* return code status		      */


   /* allocate enough memory to hold the information about each of the
    * files in the current directory; we get the amount of memory by multi-
    * plying the number of files (plus 1, in case we want to sort them) 
    * in the directory by the length the structure definition that holds
    * the information about a file
    */

   /* compute how much memory to allocate */

   *dirsize = (long) ((num_file + 1) * sizeof(ENT_DEF));

   status = LIB$GET_VM(dirsize,dirptr); /* attempt to allocate the memory     */

   if(status != SS$_NORMAL)
      LIB$STOP(status);			/* bummer.....			      */

   baseptr = *dirptr;			/* set the global pointer to the      */
					/* memory allocated for the directory */

   return;

}	/*** get_dir_mem ***/
/*eject*/
/*******************************************************************************
********************************************************************************

  Function:	get_dir

  Purpose:	Get the number of files in the current directory, allocate the
		necessary memory to store the information about the directory,
		and obtain the information about the individual files.

  Global variables:

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

  Return Codes:

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

  Termination Codes:
             
	Code			Reason
	----			------
	none

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

static void get_dir(dirptr,args,num_block,curr_pool,num_file,num_screen,
		    start_spec,pool_length,dir_length)
					/*******   FORMAL  PARAMETERS   *******/
register ENT_DEF  *dirptr;		/* pointer to allocated memory	      */
register ARG_DEF  *args;		/* run-time argument flags	      */
	 long	  *num_block;		/* number of blocks in directory      */
	 POOL_DEF **curr_pool;		/* pointer to current memory pool     */
	 short	  *num_file,		/* number of files in directory	      */
	    	  *num_screen;		/* number of screens for directory    */
	 char	  *start_spec;		/* filespec for getting files	      */
	 long	  pool_length;		/* length to allocate for new pools   */
	 short	  dir_length;		/* length of current directory string */

{	/*** get_dir ***/
					/********   LOCAL  VARIABLES   ********/
register ULONG	  status;		/* return code status		      */
	 ENT_DEF  *save_ptr;		/* for saving dirptr value	      */
	 short	  retval = 0;		/* value to return		      */
static	 long	  name_length;		/* length of a filename minus dir-    */
					/* ectory specification		      */
register char	  *tptr,		/* temporary character pointer	      */
		  *name_ptr;		/* pointer to skip directory spec     */
static	 ULONG	  fcontext;		/* FIND_FILE context		      */
static	 char	  buf2[BUFSIZ];		/* resulting string		      */
static $DESCRIPTOR(file_d,"");		/* descriptor for wildcard string     */
static $DESCRIPTOR(file2_d,buf2);	/* resultant file-spec descriptor     */


   /* now create the directory entries for each of the files in the
    * current directory
    */

   save_ptr = dirptr;			/* make a copy of dirptr for later    */
   fcontext = 0L;			/* reset the context		      */
   name_ptr = &buf2[dir_length];	/* create a pointer to skip over the  */
					/* directory name in the file spec    */

   /* use start_spec the first time through here as is; after that, it will
    * just contain a wildcard spec
    */

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

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

   while(status == RMS$_NORMAL)
   {
      /* make the filename a real string */

      name_ptr = strchr(buf2,']') + 1L;	/* skip past directory spec	      */
      tptr = strchr(name_ptr,' ');	/* find the end of the filename	      */
      *tptr = '\0';			/* make the filename a string	      */
      name_length = tptr - name_ptr;	/* calculate length of filename	      */
             
      /* try to make the directory entry for this file */

      if(make_ent(dirptr,args,curr_pool,name_ptr,num_block,pool_length)
	 == RMS$_NORMAL)
      {
	 /* everything ok so far; store the full filename in a memory pool */

	 put_pool(&(dirptr->filename),curr_pool,name_ptr,name_length,
		  pool_length);

	 dirptr++;			/* go to next directory entry	      */
      }
      else
	 *num_file -= 1;		/* bummer; subtract one from num_file */

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

   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);

   switch(args->sort)			/* should we sort......?	      */
   {
      case(SIZE):			/* sort by file size		      */

	 dirptr->size = ULONG_MAX;
	 size_qsort(0,*num_file - 1);
	 break;

      case(DATE):			/* sort by file access date/time      */

         /* this is commented out because I haven't found a way to compare
	  * date quadwords; all that needs to be done is to change time_cmp()
	  * so that it can compare them and then uncomment this

	 dirptr->time[0] = LONG_MAX;
	 dirptr->time[1] = ULONG_MAX;
	 date_qsort(0,*num_file - 1);

	  */

	 break;

      default:
	 break;
   }

   strcpy(start_spec,"*.*;*");		/* reset start_spec to wildcard	      */

   return;

}	/*** get_dir ***/
/*eject*/
/*******************************************************************************
********************************************************************************

  Function:	put_stat

  Purpose:	write the directory statistics line in the third virtual
		display in reverse video; this line will include the number of
		files and number of blocks for the directory.

  Global variables:

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

  Return Codes:

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

  Termination Codes:
                        
	Code 			Reason
	----			------
	none

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

static void put_stat(disp_id,num_block,num_file,scr_width)
					/*******   FORMAL  PARAMETERS   *******/
	 ULONG	  disp_id;		/* where to write it		      */
	 long	  num_block;		/* numbe of blocks in directory	      */
	 short	  num_file,		/* number of files in directory	      */
		  scr_width;		/* width of screen		      */

{	/*** put_stat ***/
					/********   LOCAL  VARIABLES   ********/
	 ULONG	  status;		/* return code status		      */
static	 char	  buf[SCR_MAX+1],	/* formatting buffer		      */
		  buf2[SCR_MAX];	/* formatting buffer #2		      */
static $DESCRIPTOR(text_d,buf);		/* output text descriptor	      */


   /* get the first string */

   sprintf(buf2," %d files   %d blocks",num_file,num_block);

   /* now left-justify it with blanks on the right */

   sprintf(buf,"%-132.132s",buf2);
   buf[scr_width] = '\0';		/* terminate things where we want     */
   text_d.dsc$w_length = scr_width;	/* set width in descriptor	      */

   /* write it to the virtual display */

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

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

   return;

}	/*** put_stat ***/
/*eject*/
/*******************************************************************************
********************************************************************************

  Function:	put_spec

  Purpose:	write the directory specification/page number line at the top of
		screen

  Global variables:

	Name			Examine/Modify/Use/Read/Write
	----			-----------------------------
	disp1_id				X

  Return Codes:

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

  Termination Codes:

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

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

static void put_spec(disp_id,dir_name,num_screen,scr_width)
					/*******   FORMAL  PARAMETERS   *******/
	 ULONG	  disp_id;		/* where to write it		      */
	 char	  *dir_name;		/* current directory specification    */
	 short	  num_screen,		/* number of screens for directory    */
		  scr_width;		/* width of terminal screen	      */

{	/*** put_spec ***/
					/********   LOCAL  VARIABLES   ********/
	 ULONG	  status;		/* return code status		      */
	 short	  length;		/* length of stuff in buf2	      */
static	 char	  buf1[SCR_MAX+1],	/* formatting buffer		      */
		  buf2[SCR_MAX+1];	/* formatting buffer #2		      */
static $DESCRIPTOR(text_d,buf1);	/* output descriptor		      */


   sprintf(buf1,"%-132.132s",dir_name);	/* first insert the directory name    */
   sprintf(buf2,"Page  1 of %2d ",num_screen);
   length = (short) strlen(buf2) + 1;	/* get length of Page stuff	      */
   strcat(buf2,"  ");			/* add a few spaces to pad things     */
   strcpy(&buf1[scr_width-length],buf2); /* right-justify it in buf	      */
   text_d.dsc$w_length = scr_width;	/* set length of line in descriptor   */

   /* now write it to the virtual display */

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

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

   return;

}	/*** put_spec ***/
/*eject*/
/*******************************************************************************
********************************************************************************

  Function:	put_pagnum

  Purpose:	write the "Page nn of nn" to the top right of the screen.

  Global variables:

	Name			Examine/Modify/Use/Read/Write
	----			-----------------------------
	disp1_id				X

  Return Codes:

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

  Termination Codes:

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

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

static void put_pagnum(disp_id,curr_screen,num_screen,scr_width)
					/*******   FORMAL  PARAMETERS   *******/
	 ULONG	  disp_id;		/* where to write it		      */
	 short	  curr_screen,		/* current screen number	      */
		  num_screen,		/* number of screens for directory    */
		  scr_width;		/* screen width			      */

{	/*** put_pagnum ***/
					/********   LOCAL  VARIABLES   ********/
register ULONG	  status;		/* return code status		      */
	 long	  column;		/* where to start writing stuff	      */
	 short	  length;		/* length of formatted stuff	      */
static	 char	  buf[SCR_MAX];		/* for formatting things	      */
static $DESCRIPTOR(text_d,buf);		/* output descriptor		      */


   sprintf(buf,"Page %2d of %2d ",curr_screen,num_screen);
   length = (short) strlen(buf) + 1;	/* get length of Page stuff	      */
   strcat(buf,"  ");			/* add a few spaces to pad things     */
   column = (long) (scr_width - length) + 1L;
   text_d.dsc$w_length = length;	/* set length in descriptor	      */

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

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

   return;
            
}	/*** put_pagnum ***/
/*eject*/
/*******************************************************************************
********************************************************************************

  Function:	ctrl_ast

  Purpose:	Called when an out-of-band character is typed; specifically,
		this routine is called when a control-C or a control-Y is
		detected.

  Global variables:

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

  Return Codes:

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

  Termination Codes:

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

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

static ULONG ctrl_ast()

{	/*** ctrl_ast ***/

   return(SS$_CONTINUE);		/* just go back			      */
   
}	/*** ctrl_ast ***/
/*eject*/
/*******************************************************************************
********************************************************************************

  Function:	brdcast_ast

  Purpose:	Called when a broadcast message is detected.  The message is
		retrieved, examined, and the user is notified by a message in
		the bottom right-hand corner of the screen.

  Global variables:

	Name			Examine/Modify/Use/Read/Write
	----			-----------------------------
	paste_id				X
	scr_width				X
	phone_note			  X
	mail_note			  X
	disp2_id				X
	disp3_id				X
	global_row				X
	global_col				X

  Return Codes:

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

  Termination Codes:

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

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

static void brdcast_ast(ast_arg,arg1,arg2,arg3,arg4)
					/*******   FORMAL  PARAMETERS   *******/
	 ULONG	  ast_arg,		/* AST argument (none specified)      */
		  arg1,			/* remaining ignored arguments	      */
		  arg2,
		  arg3,
		  arg4;

{	/*** brdcast_ast ***/
					/********   LOCAL  VARIABLES   ********/
static	 ULONG	  status,		/* return code status		      */
		  column,		/* where to write notification mess.  */
		  rend_set;		/* rendition setting		      */
static	 short	  length,		/* length of broadcast message	      */
		  align_val;		/* user for aligning notification     */
static	 char	  mess_buf[MESS_MAX+1],	/* buffer for getting broadcast mess. */
		  *mailmess = "\7Mail",	/* mail notification message	      */
		  *phonemess = "\7Phone"; /* phone notification message	      */
static $DESCRIPTOR(mess_d,mess_buf);	/* descriptor for retrieving message  */


   mess_d.dsc$a_pointer = mess_buf;	/* reset the descriptor just in case  */
   mess_d.dsc$w_length = MESS_MAX;

   /* retrieve the broadcast message */

   status = SMG$GET_BROADCAST_MESSAGE(&paste_id,&mess_d,&length);

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

   mess_d.dsc$a_pointer[length] = '\0';	/* terminate the message first	      */

   /* look at the message and determine what kind of message it is */

   if(strindex(mess_buf,"New mail") >= 0)
   {
      /* this is a mail message; send a message to the screen */   

      mail_note = 1;			/* set a global flag		      */
      mess_d.dsc$a_pointer = mailmess;
      align_val = (short) strlen(mailmess); /* get offset of where to put it  */
   }
   else if(strindex(mess_buf,"phoning you") >= 0)
   {
      /* someone is phoning */

      phone_note = 1;			/* set a global flag		      */
      mess_d.dsc$a_pointer = phonemess;
      align_val = (short) (strlen(mailmess) + strlen(phonemess)) + 1;
   }
   else
      return;				/* nothing to be concerned about      */

   mess_d.dsc$w_length = (short) strlen(mess_d.dsc$a_pointer);
   column = (ULONG) (scr_width - align_val) - 1L;

   /* make it blink in reverse video */

   rend_set = SMG$M_REVERSE | SMG$M_BLINK;

   status = SMG$PUT_CHARS(&disp3_id,&mess_d,&1L,&column,0,&rend_set,0,0);

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

   /* reset the cursor to where it was */

   status = SMG$SET_CURSOR_ABS(&disp2_id,global_row,global_col);

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

   return;

}	/*** brdcast_ast ***/
/*eject*/
/*******************************************************************************
********************************************************************************

  Function:	make_ent

  Purpose:	Gather all the information about an individual file, including
		the protection string and filename that will be displayed on
		the screen.  This information is stored in the directory entry.

  Global variables:

	Name			Examine/Modify/Use/Read/Write
	----			-----------------------------
	nullptr					X

  Return Codes:

	Code			Reason
	----			------
	RMS$_FNF		file was deleted before we could open it
	RMS$_NORMAL		file was processed successfully

  Termination Codes:

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

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

static ULONG make_ent(ent,args,pool,filename,num_block,pool_length)
					/*******   FORMAL  PARAMETERS   *******/
register ENT_DEF  *ent;			/* pointer to file entry	      */
register ARG_DEF  *args;		/* run-time argument flags	      */
	 POOL_DEF **pool;		/* current memory pool		      */
register char	  *filename;		/* file to be looked up		      */
	 long	  *num_block,		/* number of blocks in directory      */
		  pool_length;		/* length of pool to allocate	      */

{	/*** make_ent ***/
					/********   LOCAL  VARIABLES   ********/
register ULONG	  status;		/* return code status		      */
static	 ULONG	  *tptr;		/* temporary pointer to date/time     */
static	 USHORT	  timelen;		/* length of date/time string	      */
static	 short	  pass;			/* whether we have been here before   */
static	 struct	  FAB     fab;		/* FAB for getting information	      */
static	 struct	  NAM     nam;		/* NAM  "     "         "	      */
static	 struct	  XABDAT  date_xab;	/* XAB for getting access date	      */
static	 struct	  XABPRO  prot_xab;	/* XAB for getting protection, etc.   */
static	 struct	  XABFHC  head_xab;	/* XAB for file header		      */
static	 struct	  dsc$descriptor_s
		  time_d =		/* for making date/time string	      */
{DATE_MAX,DSC$K_DTYPE_T,DSC$K_CLASS_S,NULL};


   if(!pass)				/* did we already initialize stuff?   */
   {
      fab = cc$rms_fab;			/* initialize all of the RMS blocks   */
      nam = cc$rms_nam;
      date_xab = cc$rms_xabdat;
      prot_xab = cc$rms_xabpro;
      head_xab = cc$rms_xabfhc;
      fab.fab$l_nam = &nam;
      fab.fab$l_xab = &date_xab;
      date_xab.xab$l_nxt = &prot_xab;
      prot_xab.xab$l_nxt = &head_xab;
      tptr = &(date_xab.xab$q_rdt);	/* for accessing date/time quadword   */
      pass = 1;				/* make sure we do this only once     */
   }

   fab.fab$l_fna = filename;		/* set up the filename in the FAB     */
   fab.fab$b_fns = (UCHAR) strlen(filename);
   
   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)
      return(RMS$_FNF);
   
   /* now extract the information from the RMS blocks */

   /* if the file is not indexed sequential, we want the number of the
    * end-of-file block for the size; if it IS indexed sequential, we want
    * the number of the highest virtual block in the file for the size
    */

   if(fab.fab$b_org != FAB$C_IDX)	/* relative or sequential file?	      */
   {
      ent->size = head_xab.xab$l_ebk;	/* yes, use highest block number      */
      if(!head_xab.xab$w_ffb)		/* is the highest block being used?   */
	 ent->size -= 1;		/* apparently not, so don't count it  */
   }
   else					/* nope, this is indexed sequential   */
   {
      ent->size = head_xab.xab$l_hbk;	/* use allocation quantity	      */
   }

   *num_block += ent->size;		/* add to total blocks for directory  */
   ent->prot = prot_xab.xab$w_pro;	/* get the protection word	      */

   if(args->prot)			/* does the user want protection?     */
   {
      ent->prot_str = get_pool_mem(pool,pool_length,PROT_MAX);

      /* create the protection string from the integer value */

      prot_val_to_str(ent->prot,ent->prot_str);
   }
   else	     				/* nope, not this time.....	      */
   {
      ent->prot_str = nullptr;		/* make sure nothing is there	      */
   }

   /* save the date quadword just for fun...... */

   ent->time[0] = *tptr;
   ent->time[1] = *(tptr + 1);

   if(args->date)			/* does user want the date string?    */
   {
      /* yes, get the revision date/time string */

      ent->date_str = get_pool_mem(pool,pool_length,DATE_MAX);
      time_d.dsc$a_pointer = ent->date_str;

      status = SYS$ASCTIM(&timelen,&time_d,tptr,0L);

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

      ent->date_str[DATE_MAX] = '\0';	/* terminate the date string	      */
   }
   else	     				/* nope, not this time.....	      */
   {
      ent->date_str = nullptr;		/* kill the date string		      */
   }

   /* set the filename that will be displayed; if the set_name function
    * determines that the filename is too long in either the filename or
    * the extension to be displayed on the screen, the FLAG_CHAR will be
    * appended to the filename
    */

   set_name(ent->scr_name,filename);

   ent->command = NULL;			/* no commands for file yet	      */
   ent->text = nullptr;			/* make null for now		      */

   SYS$CLOSE(&fab);			/* close the file		      */

   return(RMS$_NORMAL);
      
}	/*** make_ent ***/
/*eject*/
/*******************************************************************************
********************************************************************************

  Function:	set_name

  Purpose:	Extract the necessary parts of the filename to create the
		filename that will be displayed on the screen.  This means
		truncation parts of the file specification that are too long
		to be displayed.

  Global variables:

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

  Return Codes:

	Code			Reason
	----			------
	retval			1 = filename too long to be displayed on
				screen
				0 = filename can be displayed as it

  Termination Codes:

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

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

static void set_name(scr_name,filename)
					/*******   FORMAL  PARAMETERS   *******/
register char	  *scr_name,		/* name that will be displayed	      */
		  *filename;		/* filename to pick apart	      */

{	/*** set_name ***/
					/********   LOCAL  VARIABLES   ********/
register short	  temp;			/* loop and array index		      */
  	 short	  flag = 0;		/* long filename flag		      */


   temp = FILENAME_MAX;			/* max. number of chars for filename  */

   /* copy the filename part up to the maximum number of characters OR
    * until a period is encountered
    */

   while((*filename != '\0') && (*filename != '.') && (temp-- > 0))
      *scr_name++ = *filename++;

   if(*filename != '.')			/* was the filename too long?	      */
      flag = 1;				/* yep...			      */

   /* pad with spaces until temp is exhausted */

   while(temp-- > 0)
      *scr_name++ = ' ';

   /* make sure we are past the period   */

   while((*filename != '\0') && (*filename != '.'))
      filename++;

   filename++;				/* skip the period		      */
   *scr_name++ = '.';			/* put in the period ourselves	      */

   temp = TYPE_MAX;			/* maximum number of chars for type   */

   /* copy the filetype part up to the maximum number of characters OR
    * until a semicolon is encountered
    */

   while((*filename != '\0') && (*filename != ';') && (temp-- > 0))
      *scr_name++ = *filename++;

   if(*filename != ';')			/* was the filename too long?	      */
      flag = 1;				/* yep...			      */
             
   /* pad with spaces until temp is exhausted */

   while(temp-- > 0)
      *scr_name++ = ' ';

   /* make sure we are past the semicolon */

   while((*filename != '\0') && (*filename != ';'))
      filename++;

   filename++;
   *scr_name++ = ';';			/* put in the semicolon ourselves     */

   temp = VERSION_MAX;			/* max.  number of chars for version  */

   while((*filename != '\0') && (temp-- > 0))
      *scr_name++ = *filename++;

   if(*filename)			/* was the filename too long?	      */
      flag = 1;				/* yep...			      */

   /* pad with spaces until temp is exhausted */

   while(temp-- > 0)
      *scr_name++ = ' ';

   if(!flag)
      *scr_name++ = ' ';		/* no flag for this file	      */
   else
      *scr_name++ = FLAG_CHAR;		/* filename was too long; flag it.... */

   *scr_name = '\0';			/* terminate the filename	      */

   return;

}	/*** set_name ***/
/*eject*/
/*******************************************************************************
********************************************************************************

  Function:	name_qsort

  Purpose:	Sort the directory entries by filename.

		Algorithm for quicksort from "Fundamentals of Data Structures",
		Ellis Horowitz and Sartaj Sahni, page 137.

  Global variables:

	Name			Examine/Modify/Use/Read/Write
	----			-----------------------------
	baseptr					X

  Return Codes:

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

  Termination Codes:

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


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

static void name_qsort(m,n)
					/*******   FORMAL  PARAMETERS   *******/
register short	  m,			/* lower-bound of sub-array to be     */
					/* sorted			      */
		  n;			/* upper-bound of sub-array to be     */
					/* sorted			      */

{	/*** name_qsort ***/
					/********   LOCAL  VARIABLES   ********/
register long	  i,			/* loop and array index		      */
		  j;			/*  "    "    "     "		      */
static	 char	  *temp,		/* used for interchanging pointers    */
		  *key,			/* pointer to key value		      */
		  tflag;		/* for swapping filename flags	      */


   if(m < n)
   {
      i = m;
      j = n + 1;
      key = (baseptr+m)->filename;	/* get pointer to filename key	      */

      for(;;)
      {
	 do
	 {
	    ++i;
	 }
	 while(strcmp((baseptr+i)->filename,key) < 0);

	 do
	 {
	    --j;
	 }
	 while(strcmp((baseptr+j)->filename,key) > 0);

	 if(i < j)
	 {
	    /* swap the pointers to the filenames and the flags that indicate
	     * whether or not the filename is too long for all of it to be
	     * displayed on the screen
	     */

	    temp = (baseptr+i)->filename;
	    (baseptr+i)->filename = (baseptr+j)->filename;
	    (baseptr+j)->filename = temp;
	 }
	 else
	    break;

      } /* for(;;) */

      temp = (baseptr+m)->filename;
      (baseptr+m)->filename = (baseptr+j)->filename;
      (baseptr+j)->filename = temp;

      name_qsort(m,j-1);
      name_qsort(j+1,n);

   } /* if(m < n) */

   return;

}	/*** name_qsort ***/
/*eject*/
/*******************************************************************************
********************************************************************************

  Function:	make_screen

  Purpose:	Create the individual screen entries for the current screen
		of the directory and write them to the virtual display.

		This routine will also clear out the slots on the screen where
		the files should not appear.  This would happen only on the
		last page of a directory.

  Global variables:

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

  Return Codes:

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

  Termination Codes:        

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

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

static void make_screen(nodes,dirptr,args,disp_id,curr_screen,num_file,scr_file,
			num_slot,slot_width,text_flag)
					/*******   FORMAL  PARAMETERS   *******/
	 NODE_DEF nodes[][MAX_NODE_COL+1]; /* screen node matrix	      */
	 ENT_DEF  *dirptr;		/* pointer to directory information   */
register ARG_DEF  *args;		/* command-line arguments	      */
	 ULONG	  disp_id;		/* display id of where to write	      */
	 short	  curr_screen,		/* current screen number	      */
		  num_file;		/* number of files in directory	      */
register short	  scr_file;		/* number of files for current screen */
	 short	  num_slot,		/* number of slots per screen	      */
		  slot_width,		/* width of a filename screen slot    */
		  text_flag;		/* whether we display text descrips   */

{	/*** make_screen ***/
					/********   LOCAL  VARIABLES   ********/
register ENT_DEF  *ent;			/* pointer to current directory entry */
register ULONG 	  status;		/* return code status		      */
register short	  file_count = 0;	/* number of files processed	      */
static	 ULONG	  rend_set;		/* rendition setting for slots	      */
static	 long	  scr_row,		/* what row on screen to write to     */
		  scr_col,		/* what column on screen to write to  */
		  len;			/* where we are in buf		      */
static	 short	  i,			/* loop and array index		      */
		  j;			/*  "    "    "     "		      */
static	 char	  *tptr;		/* temporary string pointer	      */
static	 char	  buf[SCR_MAX+1],	/* formatting buffer		      */
		  buf2[PROT_MAX+1],	/* formatting buffer #2		      */
		  blank_slot[] =	/* for blanking out file slots	      */
"                                                                      \
                                                    ";
static $DESCRIPTOR(text_d,buf);		/* descriptor for writing to display  */



   /* get pointer to the first directory entry for the current screen */

   ent = dirptr + (ULONG) ((curr_screen - 1) * num_slot);
   text_d.dsc$a_pointer = buf;		/* reset the descriptor just in case  */
   text_d.dsc$w_length = slot_width;
   j = 0;

   while((j < MAX_NODE_COL) && (file_count < scr_file))
   {
      i = 0;
      scr_col = nodes[i][j].column + 1;	/* get column value for now	      */
      scr_row = 1;

      while((i < MAX_NODE_ROW) && (file_count < scr_file))
      {
	 /* create the screen slot for the file; this included determining
	  * if we need to put something special up in case the file has
	  * some command(s) associated with it
	  */

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

	 text_d.dsc$w_length = (USHORT) strlen(buf);

	 /* now write the file information to the screen in the right spot */

	 status = SMG$PUT_CHARS(&disp_id,&text_d,&scr_row,&scr_col,0,
				&rend_set,0,0);

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

	 i++;				/* move to next column		      */
	 file_count++;			/* count this file as processed	      */
	 scr_row++;			/* move to next row on screen	      */
	 ent++;				/* go to next directory entry	      */
      }

      j++;				/* go down the screen node matrix     */
   }

   if(num_slot > scr_file)		/* do we need to clear out some of    */
   {					/* the slots on the screen?	      */
      /* clear out the slots */

      j--;				/* take off the last increment so     */
					/* we start in the right column	      */
      blank_slot[slot_width] = '\0';	/* terminate the blank slot line      */
      text_d.dsc$w_length = (USHORT) slot_width;
      text_d.dsc$a_pointer = blank_slot;

      while((j < MAX_NODE_COL) && (file_count < num_slot))
      {
	 while((i < MAX_NODE_ROW) && (file_count < num_slot))
	 {
	    /* now blank out the file slot in the virtual display */

	    status = SMG$PUT_CHARS(&disp_id,&text_d,&scr_row,&scr_col,0,0,0,0);

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

	    i++;			/* move to next column		      */
	    file_count++;		/* count this file as processed	      */
	    scr_row++;			/* move to next row on screen	      */
	 }

	 i = 0;				/* start the rows over		      */
	 j++;				/* move right one column	      */
	 scr_row = 1L;
	 scr_col = scr_col + slot_width + 2;
      }
   }

   return;

}	/*** make_screen ***/
/*eject*/
/*******************************************************************************
********************************************************************************

  Function:	file_select

  Purpose:	Process a file that has been selected.  This includes pasting/
		unpasting the virtual display for the file and processing the
		options associated with the file.  The file is presented to the
		user in a series of screens.  After each screen is displayed,
		the user has the option of continuing to view the file or
		quit and return to the directory.

  Global variables:

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

  Return Codes:

	Code			Reason
	----			------
	  0			File selection completed successfully
	CANT_OPEN		Error opening file
	CANT_DISPLAY		Error displaying file on screen

  Termination Codes:

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

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

static short file_select(filename,buf,disp_id,paste_id,keybd_id,scr_width)
					/*******   FORMAL  PARAMETERS   *******/
	 char	  *filename,		/* name of file to be accessed	      */
		  *buf;			/* reading buffer		      */
	 ULONG	  disp_id,		/* display to write to		      */
		  paste_id,		/* pasteboard id		      */
		  keybd_id;		/* keyboard id to read from	      */
	 short	  scr_width;		/* width of screen		      */

{	/*** file_select ***/
					/********   LOCAL  VARIABLES   ********/
register ULONG	  status;		/* return code status holder	      */
register char	  *tptr,		/* pointer to input buffer	      */
		  *tptr2;		/* pointer to output buffer	      */
	 ULONG	  status2,		/* secondary return code status	      */
		  status3;		/* yet another return code holder     */
	 USHORT	  term_code,		/* keystroke code read in	      */
		  finish_flag = 0,	/* set when user wants to finish file */
		  key_flag;		/* loop control flag		      */
	 short	  buf_cnt,		/* number of chars in output buffer   */
		  temp;			/* temporary value		      */
static	 long	  row,			/* current row in virtual display     */
		  end_col;		/* ending column for ERASE_DISPLAY    */
static	 UCHAR	  pass;			/* to make sure we initialize once    */
static	 char	  options[BUFSIZ],	/* options line			      */
		  out_buf[SCR_MAX+1];	/* for formatting output	      */
static	 struct	  FAB	in_fab;		/* input file FAB		      */
static	 struct	  RAB	in_rab;		/* input file RAB		      */
static $DESCRIPTOR(text_d,out_buf);	/* output record buffer descriptor    */
static $DESCRIPTOR(opt_d,options);	/* options descriptor		      */
static $DESCRIPTOR(eof_d,"[eof]");	/* end-of-file string descriptor      */


   if(!pass) 				/* first time we have been called?    */
   {
      /* initialize the RMS blocks only once */

      in_fab = cc$rms_fab;
      in_rab = cc$rms_rab;

      in_fab.fab$b_fac = FAB$M_GET;	/* going to use SYS$GET() on it	      */

      in_rab.rab$l_fab = &in_fab;	/* pointer to FAB		      */
      in_rab.rab$b_rac = RAB$C_SEQ;	/* sequential access to the file      */
      in_rab.rab$w_usz = IN_REC_SIZE;	/* input record buffer size	      */

      end_col = (long) scr_width;	/* set up for erasing the display     */
      opt_d.dsc$w_length = scr_width;	/* set width of options line	      */
      pass++;				/* make sure we only do this once     */
   }

   in_fab.fab$l_fna = filename;		/* set filename & length	      */
   in_fab.fab$b_fns = (UCHAR) strlen(filename);
   in_fab.fab$w_ifi = 0;		/* reset just in case.......	      */
   in_rab.rab$l_ubf = buf;		/* set where to read things into      */

   /* try to open the input file */

   status = SYS$OPEN(&in_fab);

   if(status != RMS$_NORMAL)
   {
      SYS$CLOSE(&in_fab);		/* try to close it to be sure.....    */
      return(CANT_OPEN);
   }

   status = SYS$CONNECT(&in_rab);	/* connect the FAB and the RAB	      */

   if(status != RMS$_NORMAL)
   {
      SYS$CLOSE(&in_fab);		/* what happened....?		      */
      return(CANT_OPEN);
   }

   /* get the first record and see if the file has anything in it; if it
    * is empty, no sense in pasting the virtual display for doing the work
    * and all that; just return that the file is empty
    */

   status = SYS$GET(&in_rab);		/* read-ahead the first record	      */

   if(status != RMS$_NORMAL)		/* anything in the file?	      */
   {
      SYS$CLOSE(&in_fab);		/* nope, close the file		      */
      return(CANT_DISPLAY);		/* return that we can't look at it    */
   }

   /* ok, we opened the file; now paste the virtual display over the main
    * display so that we can write to it
    */

   status2 = SMG$PASTE_VIRTUAL_DISPLAY(&disp_id,&paste_id,&WORK_BASE_ROW,
				       &WORK_BASE_COL,0);

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

   /* the virtual display has been pasted; create the options line to write
    * at the bottom with the options and the filename by concatenating the
    * the options line, the filename, and enough blank spaces to make sure
    * that the previous filename is wiped clean
    */

   cat(options,"+(next) screen   Finish    File:  ",filename,"               \
                                                                            \
       ");

   options[scr_width] = '\0';		/* terminate things where we want     */

   status2 = SMG$PUT_CHARS(&disp_id,&opt_d,&WORK_ROWS,&1L,0,&SMG$M_REVERSE,
			  0,0);

   if(status2 != SS$_NORMAL)
   {
      /* bummer; try to salvage the situation */

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

      if(status != SS$_NORMAL)
	 LIB$STOP(status);		/* double dummer......		      */

      return(CANT_DISPLAY);
   }

   /* now process the file, displaying it on the screen in a series of pages,
    * until end-of-file OR until the user wants to quit looking at the file
    */

   while((status == RMS$_NORMAL) && (!finish_flag))
   {
      row = 1L;				/* start on first row in display      */

      /* clear out the display */

      SMG$ERASE_DISPLAY(&disp_id,&1L,&1L,&(WORK_ROWS-1),&end_col);

      /* write a page of the file or as much as we can get if we hit
       * end-of-file here
       */

      while((status == RMS$_NORMAL) && (row < (WORK_ROWS - 1)))
      {
	 tptr = buf;			/* point to start of input buffer     */
	 buf[in_rab.rab$w_rsz] = '\0';	/* make the input record a string     */

	 do
	 {
	    tptr2 = out_buf;		/* point to beginning of output buf   */
	    buf_cnt = 0;		/* clear number of chars in buffer    */

       	    while((buf_cnt < scr_width) && (*tptr != '\0'))
	    {

	       if(*tptr >= ' ')		/* is character printable?	      */
	       {
		  *tptr2++ = *tptr++;	/* store the character		      */
		  buf_cnt++;		/* count the character		      */
	       }
	       else
	       {
 		  switch(*tptr)		/* what kind of control char is it?   */
		  {
		     case(0x9):		/* control-I (tab) character	      */

			temp = buf_cnt % 8; /* determine how many to add      */

			if(temp != 0)
			   buf_cnt = buf_cnt + (8 - temp);
			else
			   buf_cnt += 8;

			*tptr2++ = *tptr++; /* just use the tab as it is      */

			break;

		     case(10):		/* control-J (linefeed) character     */
		     case(13):		/* control-M (carriage return) char   */

			*tptr2++ = ' ';	/* store a space for ^J and ^M	      */
 		        tptr++;		/* skip over the character	      */
			buf_cnt++;	/* count the character		      */
			break;

		     default:		/* all other control characters	      */

			if(buf_cnt < scr_width - 2)
			{
			   /* only do this if we have room */

			   *tptr2++ = '^'; /* use two chars - ^<char>	      */
	     		   *tptr2++ = *tptr++ + 64;
			}
			buf_cnt += 2;   /* count two characters  	      */
			break;

		  } /* switch (*tptr) */

	       } /* if(*tptr >= ' ') */

	    } /* while((buf_cnt < scr_width) && (*tptr != '\0')) */
	    
	    *tptr2 = '\0';		/* make the output buffer a string    */
	    text_d.dsc$w_length = (USHORT) strlen(out_buf);

	    /* write this line to the display */

	    status2 = SMG$PUT_CHARS(&disp_id,&text_d,&row,&1L,0,0,0,0);

	    if(status2 != SS$_NORMAL)	/* were we able to write the record?  */
	    {
	       SYS$CLOSE(&in_fab);  	/* close the input file		      */
	       status = SMG$UNPASTE_VIRTUAL_DISPLAY(&disp_id,&paste_id);

	       if(status != SS$_NORMAL)
		  LIB$STOP(status);	/* BIG error here....		      */

	       SMG$ERASE_DISPLAY(&disp_id,&1L,&1L,&(WORK_ROWS-1),&end_col);

	       return(CANT_DISPLAY);	/* say that something went wrong      */
	    }

	    row++;			/* move to next row in display	      */
	 }
	 while(*tptr != '\0' && row < (WORK_ROWS - 1));

	 if(*tptr == '\0')		/* do we need another record yet?     */
	    status = SYS$GET(&in_rab);	/* get the next record		      */
      }

      /* do we need to put the the "[eof]" on the screen? */

      if(status != RMS$_NORMAL)
      {
	  if(SMG$PUT_CHARS(&disp_id,&eof_d,&row,&1L,0,&SMG$M_REVERSE,0,0)
	     != SS$_NORMAL)
	  {
	    SYS$CLOSE(&in_fab);		/* close the input file		      */
    	    status = SMG$UNPASTE_VIRTUAL_DISPLAY(&disp_id,&paste_id);

	    if(status != SS$_NORMAL)
	       LIB$STOP(status);	/* BIG error here....		      */

	    SMG$ERASE_DISPLAY(&disp_id,&1L,&1L,&(WORK_ROWS-1),&end_col);

	    return(CANT_DISPLAY);	/* say that something went wrong      */
	  }
      }

      /* we are at the end of a page; wait for the user to tell us what
       * s/he wants to do
       */

      key_flag = 0;			/* make sure we enter the loop	      */

      while(!key_flag)			/* loop until we get a good key	      */
      {
	 status3 = SMG$READ_KEYSTROKE(&keybd_id,&term_code,0,0,&disp_id,0,0);

	 if((status3 != SS$_NORMAL) && (status3 != SMG$_EOF))
	 {
	    SYS$CLOSE(&in_fab);	     	/* close the input file		      */
    	    status = SMG$UNPASTE_VIRTUAL_DISPLAY(&disp_id,&paste_id);

	    if(status != SS$_NORMAL)
	        LIB$STOP(status); 	/* BIG error here....		      */

	    /* clear out the display */

	    SMG$ERASE_DISPLAY(&disp_id,&1L,&1L,&(WORK_ROWS-1),&end_col);
   
	    return(CANT_DISPLAY); 	/* say that something went wrong      */
	 }

	 if(status3 == SMG$_EOF)	/* user pressed control-Z; same as F  */
	    term_code = SMG$K_TRM_LOWERCASE_F;

	 /* ok, then, see what the keystroke was */

	 switch(term_code)
	 {
	    case(SMG$K_TRM_EQUAL):	/* +(next) screen		      */
	    case(SMG$K_TRM_NEXT_SCREEN):
	    case(SMG$K_TRM_DOWN):
	    case(SMG$K_TRM_ASTERISK+1):
	    case(SMG$K_TRM_SPACE):
	    case(SMG$K_TRM_CR):

	       key_flag++;		/* break out of the while() loop      */
	       break;			/* just keep going		      */

	    case(SMG$K_TRM_UPPERCASE_F):  /* Finish viewing file	      */
	    case(SMG$K_TRM_LOWERCASE_F):
	    case(SMG$K_TRM_UPPERCASE_Q):
	    case(SMG$K_TRM_LOWERCASE_Q):

	       finish_flag++;		/* set the Finish key flag	      */
	       key_flag++;		/* break out of the key loop	      */
	       break;
					/* right here			      */
	    default:

	       SMG$RING_BELL(&disp_id,0); /* scream and try again 	      */
	       break;

	 } /* switch(term_code) */

      } /* while(!key_flag) */

   } /* while(status == RMS$_NORMAL) */

   status = SYS$CLOSE(&in_fab);		/* close the input file		      */

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

   /* unpaste the work virtual display so that the main screen and related
    * info is back on the screen
    */

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

   if(status != SS$_NORMAL)
      LIB$STOP(status);			/* BIG error here....		      */

   /* clear out the work virtual display */

   SMG$ERASE_DISPLAY(&disp_id,&1L,&1L,&WORK_ROWS,&end_col);


   return(0); 	   	  		/* everything is cool; just return    */

}	/*** file_select ***/
/*eject*/
/*******************************************************************************
********************************************************************************

  Function:	file_locate

  Purpose:	Get the filename prefix that is desired by the user and
		search for it.

  Global variables:

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

  Return Codes:

	Code			Reason
	----			------
	  0			no prefix string given
	return()		return code from file_search

  Termination Codes:

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

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

static short file_locate(dirptr,disp_id,keybd_id,num_file,scr_width)
					/*******   FORMAL  PARAMETERS   *******/
	 ENT_DEF  *dirptr;		/* pointer to directory entry memory  */
	 long	  disp_id,		/* display id of where to read from   */
		  keybd_id;		/* keyboard id of where to read from  */
	 short	  num_file;		/* number of files in directory	      */

{	/*** file_locate ***/
					/********   LOCAL  VARIABLES   ********/
register short	  i;			/* loop and array index		      */
	 ULONG	  status;		/* return code holder		      */
	 long	  end_col;		/* number of columns to erase	      */
	 USHORT	  rlength;		/* received string length	      */
static	 long	  max_len = SPEC_MAX;	/* maximum prefix length allowed      */
static	 char	  prefix[SPEC_MAX+1];	/* filename prefix read in	      */
static $DESCRIPTOR(buf_d,prefix);	/* prefix string descriptor	      */
static $DESCRIPTOR(prompt_d,"Search for: ");


   /* position the cursor where we want it */

   status = SMG$SET_CURSOR_ABS(&disp_id,&MAIN_ROWS,&1L);
             
   if(status != SS$_NORMAL)
      return(-1);

   /* prompt and ye shall receive....... */

   status = SMG$READ_STRING(&keybd_id,&buf_d,&prompt_d,&max_len,0,0,0,
			    &rlength,0,&disp_id,0,&SMG$M_BOLD,0);

   if(status != SS$_NORMAL)
      return(-1);			/* problems reading from keyboard     */

   /* erase the prompt from the virtual display */

   end_col = (long) scr_width;
   status = SMG$ERASE_DISPLAY(&disp_id,&MAIN_ROWS,&1L,&MAIN_ROWS,&end_col);

   if(status != SS$_NORMAL)
      return(-1);
	       
   if(!rlength)
      return(0);			/* no string given; just leave	      */

   prefix[rlength] = '\0';		/* terminate the prefix string	      */

   /* convert the string to upper-case */

   for(i = 0; i < rlength; i++)
      prefix[i] = _toupper(prefix[i]);

   /* now search for the filename prefix in the directory entries */

   return(file_search(dirptr,prefix,num_file,rlength));

}	/*** file_locate ***/
/*eject*/
/*******************************************************************************
********************************************************************************

  Function:	file_search

  Purpose:	Search for the filename prefix specified

  Global variables:

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

  Return Codes:

	Code			Reason
	----			------
	 i			number of file in directory
	 -1			file prefix not found in directory

  Termination Codes:

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

********************************************************************************
*******************************************************************************/
                                   
static short file_search(dirptr,prefix,num_file,length)
					/*******   FORMAL  PARAMETERS   *******/
register ENT_DEF  *dirptr;		/* pointer to directory entries	      */
register char	  *prefix;		/* filename prefix to find	      */
register short	  num_file;		/* number of files in directory	      */
	 short	  length;		/* length of prefix string	      */

{	/*** file_search ***/
					/********   LOCAL  VARIABLES   ********/
register short	  i = 0;		/* loop index and file entry counter  */
	 short	  done = 0;		/* loop control flag		      */


   /* search from the beginning for the filename prefix until 1) we find
    * what we are looking for or 2) we run out of files; when we exit the
    * loop we look at why we exited to see if we found the file or not
    */

   while((i < num_file) && (!done))
   {
      if(!strncmp(dirptr->filename,prefix,length))
	 done++;			/* we found it; stop searching....    */
      else
      {
	 ++i;				/* count this file entry	      */
	 ++dirptr;			/* go to next file entry	      */
      }
   }

   /* did we find what we were looking for? */

   if(i == num_file)			/* did we find the filename prefix?   */
      i = -1;				/* nope.....			      */

   return(i);				/* say whether or not we found it...  */

}	/*** file_search ***/
/*eject*/
/*******************************************************************************
********************************************************************************

  Function:	put_help

  Purpose:	Called by LBR$OUTPUT_HELP, this routine will write a line
		of help text to the virtual display and prompt the user to
		press a key at the end of a page to give them a chance to
		read what is on the screen.  This routine is only called by
		LBR$OUTPUT_HELP.

  Global variables:

	Name			Examine/Modify/Use/Read/Write
	----			-----------------------------
	scr_width				X
    	state			   X	  X
	disp4_id				X
	keybd_id				X
	paste_id				X

  Return Codes:

	Code			Reason
	----			------
	RMS$_EOF		error writing to virtual display
	SS$_NORMAL		write to virtual display successful

  Termination Codes:

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

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

static ULONG put_help(msg_d)
					/*******   FORMAL  PARAMETERS   *******/
struct	 dsc$descriptor  *msg_d;	/* descriptor for message to write    */

{	/*** put_help ***/
					/********   LOCAL  VARIABLES   ********/
static	 ULONG	  status;		/* return code status holder	      */
static	 long	  row;			/* current row in virtual display     */
static	 USHORT	  term_code;		/* key read from keyboard	      */
static $DESCRIPTOR(prompt_d,"Press any key to continue                    \
                                                                           \
            ");
static $DESCRIPTOR(clear_d,"                                             \
                                                                           \
            ");


   if(!state)				/* set up initialization code	      */
   {
      /* we need to reset things because we are to start a new help session
       * or a new page
       */

      row = 1L;				/* reset row counter		      */
      prompt_d.dsc$a_pointer[scr_width] = '\0';
      clear_d.dsc$a_pointer[scr_width] = '\0';
      state++;
      status = SMG$PUT_CHARS(&disp4_id,&clear_d,&WORK_ROWS,&1L,0,
			     &SMG$M_REVERSE,0,0);
      if(status != SS$_NORMAL)
	 return(RMS$_EOF);
   }

   /* before we write anything, we must check to see if the screen is
    * full; if it is, ask the user to press a key before continuing,
    * thus giving them a chance to read what is on the screen
    */

   if(row == (WORK_ROWS - 1))		/* is the screen full?		      */
   {
      /* tell the user to press a key to continue */

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

      if(status != SS$_NORMAL)
	 return(RMS$_EOF);		/* problems writing to the screen     */

      /* now get a keystroke and continue */

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

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

      SMG$ERASE_DISPLAY(&disp4_id);	/* so NOW clear the screen....	      */

      /* .... and clear out the prompt information at the bottom */

      status = SMG$PUT_CHARS(&disp4_id,&clear_d,&WORK_ROWS,&1L,0,
			     &SMG$M_REVERSE,0,0);

      row = 1L;				/* reset the row counter	      */
   }

   /* now write the line we were supposed to write when we entered */

   row++;
   status = SMG$PUT_CHARS(&disp4_id,msg_d,&row,&1L,&1L,0,0,0);

   if(status != SS$_NORMAL)		/* bummer.....			      */
      return(RMS$_EOF);

   return(SS$_NORMAL);			/* no problem........		      */

}	/*** put_help ***/
/*eject*/
/*******************************************************************************
********************************************************************************

  Function:	get_help

  Purpose:	Called by LBR$OUTPUT_HELP, this routine will read a line
		of text from the virtual display and return it.  This routine
		is only called by LBR$OUTPUT_HELP.

  Global variables:

	Name			Examine/Modify/Use/Read/Write
	----			-----------------------------
	scr_width				X
	state				  X

  Return Codes:

	Code			Reason
	----			------
	RMS$_EOF		error reading virtual display
	SS$_NORMAL		read from virtual display successful

  Termination Codes:

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

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

static ULONG get_help(get_d,prompt_d,out_len)

struct	 dsc$descriptor			/*******   FORMAL  PARAMETERS   *******/
		  *get_d,		/* where to put string that is read   */
		  *prompt_d;		/* prompt to use		      */
	 USHORT	  *out_len;		/* length string in get_d	      */

{	/*** get_help ***/
					/********   LOCAL  VARIABLES   ********/
static	 ULONG	  status,		/* return code status holder	      */
		  status2;		/* another return code status holder  */
static	 long	  max_length = STR_MAX, /* max. # of characters to be read    */
		  end_col;		/* for erasing virtual display	      */
static	 char	  *ptr;			/* temporary character pointer	      */
static	 UCHAR	  pass;			/* prevents multiple intialization    */
static $DESCRIPTOR(prompt2_d,"");	/* alternate prompt descriptor	      */


   if(!pass)
   {
      get_d->dsc$b_dtype = DSC$K_DTYPE_T; /* set up the descriptor type	      */
      get_d->dsc$b_class = DSC$K_CLASS_S; /* set up the descriptor class      */
      end_col= (long) scr_width;  	/* for clearing the virtual display   */
      pass++;
   }

   /* we have to do the prompting ourselves rather than let SMG$READ_STRING
    * do it; for some reason, the prompt is not written to the virtual
    * display and thus cannot be erased later; first, set up the alternate
    * prompt descriptor so that all of the control characters at the beginning
    * of the prompt are ignored; then call put_help() to write the prompt
    * <CR><LF> pair that appears at the beginning of every prompt
    */

   ptr = prompt_d->dsc$a_pointer;

   while((*ptr != '\0') && (*ptr < ' '))
      ptr++;

   prompt2_d.dsc$a_pointer = ptr;
   prompt2_d.dsc$w_length = (USHORT) strlen(ptr);

   status = put_help(&prompt2_d);	/* write the prompt		      */

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

   get_d->dsc$w_length = STR_MAX;

   status = SMG$READ_STRING(&keybd_id,get_d,0,&max_length,0,0,0,out_len,0,
			    &disp4_id,0,0,0);

   get_d->dsc$w_length = *out_len;	/* set length read in the descriptor  */
   get_d->dsc$a_pointer[*out_len] = '\0';

   /* clear out the virtual display so that the next topic starts on a new
    * page
    */

   status2 = SMG$ERASE_DISPLAY(&disp4_id,&1L,&1L,&(WORK_ROWS-1),&end_col);

   if(status2 != SS$_NORMAL)
      return(RMS$_EOF);			/* bummer.....			      */

   state = 0;				/* make put_help reset row counter    */

   if(status != SS$_NORMAL)		/* now check READ_STRING's return     */
      return(RMS$_EOF);			/* use the right end-of-file value    */

   return(status);			/* or just return SS$_NORMAL....      */

}	/*** get_help ***/
/*eject*/
/*******************************************************************************
********************************************************************************

  Function:	put_options

  Purpose:	Write the options line to the statistics/options virtual
		display.  The keystroke needed to invoke the command is written
		in bold, the rest of the command in normal.

  Global variables:

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

  Return Codes:

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

  Termination Codes:

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

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

static void put_options(disp_id,scr_width)
					/*******   FORMAL  PARAMETERS   *******/
	 ULONG	  disp_id;		/* display id of where to write	      */
	 short	  scr_width;		/* width of screen		      */

{	/*** put_options ***/
					/********   LOCAL  VARIABLES   ********/
register OPT_DEF  *opt_ptr;		/* pointer to options array	      */
register ULONG	  status;		/* return code status holder	      */
static	 long	  column;		/* current column number for writing  */
static $DESCRIPTOR(text_d,"");		/* descriptor for writing	      */
static	 OPT_DEF  options1[] =		/* options array for 80-column mode   */
{
{"A","cl",1,2,1},
{"D","el",1,2,1},
{"R","en",1,2,1},
{"P","rot",1,3,1},
{"C","opy",1,3,1},
{"U","nmark",1,5,1},
{"+","",1,0,1},
{"-","",1,0,1},
{"S","el",1,2,1},
{"F","in",1,2,1},
{"X","ecute",1,5,1},
{"Q","uit",1,3,1},
{"T","ext",1,3,1},
{"^P","ush",2,3,1},
{"E","xpnd",1,4,1},
{"L","ocat",1,4,1},
{"H","elp",1,3,0},
{"","",0,0,0}
},
		  options2[] =		/* options array for 132-column mode  */
{
{"A","cl",1,2,3},
{"D","elete",1,5,3},
{"R","ename",1,5,3},
{"P","rotect",1,6,3},
{"C","opy",1,3,3},
{"U","nmark",1,5,3},
{"+","",1,0,3},
{"-","",1,0,3},
{"S","elect",1,5,4},
{"F","inish",1,5,4},
{"X","ecute",1,5,4},
{"Q","uit",1,3,3},
{"T","ext",1,3,3},
{"^P","ush",2,3,3},
{"E","xpand",1,5,3},
{"L","ocate",1,5,3},
{"H","elp",1,3,0},
{"","",0,0,0}
};


   if(scr_width == 80)			/* set pointer to options array	      */
      opt_ptr = &options1[0];
   else
      opt_ptr = &options2[0];

   column = 1L;				/* make sure we start ok	      */

   while(opt_ptr->key_len != 0)		/* do them ALL......		      */
   {
      /* write the key that activates the option in bright mode */

      text_d.dsc$a_pointer = opt_ptr->keystr;
      text_d.dsc$w_length = (USHORT) opt_ptr->key_len;

      status = SMG$PUT_CHARS(&disp_id,&text_d,&2L,&column,0,&SMG$M_BOLD,
			     0,0);

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

      /* write the rest of the command name in normal video */

      column += (long) opt_ptr->key_len; /* move the column over	      */

      text_d.dsc$a_pointer = opt_ptr->remaining;
      text_d.dsc$w_length = opt_ptr->rem_len;

      status = SMG$PUT_CHARS(&disp_id,&text_d,&2L,&column,0,&SMG$M_NORMAL,
			     0,0);

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

      /* skip the length of the remaining command name AND the number of
       * spaces that separates this command from the next
       */

      column = column + (long) opt_ptr->spaces + (long) opt_ptr->rem_len;
      opt_ptr++;			/* go to next option slot	      */
   }

   return;

}	/*** put_options ***/
/*eject*/
/*******************************************************************************
********************************************************************************

  Function:	get_text_des

  Purpose:	Attempt to read in the text descriptors from the text des-
		criptor file for the current directory.

  Global variables:

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

  Return Codes:

	Code			Reason
	----			------
	NO_FILE			file does not exist
	CANT_OPEN		cannot open MAINT.TDF file
	BAD_FILE		bad text descriptor file
	 0			text descriptor file successfully read

  Termination Codes:

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

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

static short get_text_des(dirptr,curr_pool,dir_text,pool_length,num_file)
					/*******   FORMAL  PARAMETERS   *******/
register ENT_DEF  *dirptr;		/* pointer to directory entries	      */
	 POOL_DEF **curr_pool;		/* pointer to pointer to current pool */
	 char	  **dir_text;		/* directory text descriptor array    */
	 long	  pool_length;		/* length of pool to allocate	      */
	 short	  num_file;		/* number of files in directory	      */

{	/*** get_text_des ***/
					/********   LOCAL  VARIABLES   ********/
	 ULONG	  status;		/* return code status holder	      */
static	 ENT_DEF  *currptr;		/* current entry for searching	      */
static	 char	  *tptr;		/* temporary character pointer	      */
static	 struct	  FAB	in_fab;		/* input FAB			      */
static	 struct	  RAB	in_rab;		/* input RAB			      */
static	 char	  pass,			/* to prevent multiple initialization */
		  text_buf[TEXT_REC_MAX+1];


   if(!pass)				/* only initialize first time thru    */
   {                                                            
      in_fab = cc$rms_fab;
      in_rab = cc$rms_rab;
      in_fab.fab$b_fac = FAB$M_GET;
      in_rab.rab$l_fab = &in_fab;
      in_rab.rab$w_usz = TEXT_REC_MAX;
      in_rab.rab$l_ubf = text_buf;
      in_rab.rab$b_rac = RAB$C_SEQ;
      in_fab.fab$l_fna = TEXT_FILE;
      in_fab.fab$b_fns = (UCHAR) strlen(TEXT_FILE);
      pass++;
   }

   in_fab.fab$w_ifi = 0;		/* reset this every time	      */

   status = SYS$OPEN(&in_fab);		/* open the text descriptor file      */

   if(status == RMS$_FNF)		/* no text descriptor file; oh well   */
   {
      /* everything is ok; we just did not find a text descriptor file */

      return(NO_FILE);
   }
   else if(status != RMS$_NORMAL)
   {
      *dir_text = NULL;
      return(CANT_OPEN);
   }

   /* connect the FAB and the RAB */

   status = SYS$CONNECT(&in_rab);

   if(status != RMS$_NORMAL)
   {
      *dir_text = NULL;
      SYS$CLOSE(&in_fab);
      return(CANT_OPEN);
   }         

   /* get the first record; this is the text descriptor for the directory;
    * the format for this record is
    *    >>><text descriptor for directory>
    */

   status = SYS$GET(&in_rab);

   if(status != RMS$_NORMAL)
   {
      *dir_text = NULL;
      SYS$CLOSE(&in_fab);
      return(CANT_OPEN);
   }

   /* make sure this record is in the right form */

   if(strindex(text_buf,">>>") != 0)
   {
      SYS$CLOSE(&in_fab);
      return(BAD_FILE);
   }

   /* store this in the memory pools and set the dir_text pointer to point
    * to it
    */

   text_buf[in_rab.rab$w_rsz] = '\0';	/* make it a string		      */
             
   put_pool(dir_text,curr_pool,&text_buf[3],in_rab.rab$w_rsz,pool_length);

   /* skip the comment lines in the text descriptor file */

   status = SYS$GET(&in_rab);

   while((status == RMS$_NORMAL) && (text_buf[0] == '>'))
      status = SYS$GET(&in_rab);

   if(status == RMS$_EOF)
   {
      SYS$CLOSE(&in_fab);
      return(0);			/* end-of-file here....		      */
   }

   if(status != RMS$_NORMAL)		/* error reading the file?	      */
   {
      SYS$CLOSE(&in_fab);
      return(BAD_FILE);			/* yup.....			      */
   }

   /* now get each text descriptor record and match it up with a filename */

   /* set the current file entry pointer to actually pointer to one entry
    * BEFORE the current entry; it will be updated inside text_search to
    * point to the correct entry; this is done so that text_search can be
    * more general
    */

   currptr = dirptr - 1L;	     	/* set where we start to look	      */

   while(status == RMS$_NORMAL)
   {                         
      text_buf[in_rab.rab$w_rsz] = '\0'; /* make the record a string	      */

      /* search for a match with the filename in the text descriptor record */

      if((tptr = text_search(dirptr,&currptr,text_buf,num_file)) != NULL)
      {
	 /* match; store the text descriptor in a memory pool */

	 put_pool(&(currptr->text),curr_pool,tptr,(long) strlen(tptr),
		  pool_length);
      }

      status = SYS$GET(&in_rab);
   }

   status = SYS$CLOSE(&in_fab);		/* close the text descriptor file     */

   if(status != RMS$_NORMAL)
   {
      return(BAD_FILE);
   }

   return(0);				/* everything done		      */

}	/*** get_text_des ***/
/*eject*/
/*******************************************************************************
********************************************************************************

  Function:	text_search

  Purpose:	Search the directory entries for a match with the current
		text descriptor record.  If one is found, return a pointer
		to the actual text of the text descriptor and update the
		current directory entry pointer to point to the NEXT entry
		to check for a text descriptor match.

  Global variables:

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

  Return Codes:

	Code			Reason
	----			------
	retval			pointer to actual text of text descriptor
	NULL			no match for text descriptor

  Termination Codes:

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

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

static char *text_search(dirptr,currptr,text_rec,num_file)
					/*******   FORMAL  PARAMETERS   *******/
	 ENT_DEF  *dirptr,		/* pointer to all directory entries   */
	 	  **currptr;		/* pointer to current directory entry */
	 char	  *text_rec;		/* current text descriptor record     */
	 short	  num_file;		/* number of files in directory	      */

{	/*** text_search ***/
					/********   LOCAL  VARIABLES   ********/
register ENT_DEF  *ptr;			/* current file entry pointer	      */
static	 ENT_DEF  *origptr;		/* original directory pointer	      */
static	 char	  *retval;		/* return value			      */
static	 ULONG	  curr_index;		/* index value for setting pointer    */


   /* get pointer to directory that we need; this is the one following
    * (modulo the number of files in the directory) the entry that we
    * were on the last time through
    */

   curr_index = *currptr - dirptr;	/* get current entry index	      */
   curr_index = (curr_index + 1L) % (ULONG) num_file;

   ptr = dirptr + curr_index;		/* get pointer to the NEW entry	      */

   /* first check to see if we have a match with the current entry */

   if((retval = text_match(ptr->filename,text_rec)) != NULL)
   {
      /* we have a match; return the pointer to the beginning of the
       * actual text of the text descriptor AND update the currptr
       * return value to point to the next entry so that the next time
       * this routine is called, we can start up one after where we
       * left off here
       */

      *currptr = ptr;			/* return pointer to current entry    */
      return(retval);			/* match....			      */
   }

   /* no match with the current entry; search the entries until 1) we find a
    * a match, or until we have examined all of the entries
    */

   origptr = ptr;			/* save where we are right now	      */
   curr_index = (curr_index + 1L) % (ULONG) num_file;
   ptr = dirptr + curr_index;

   while(ptr != origptr)		/* until we are back where we started */
   {
      /* check for a match */

      if((retval = text_match(ptr->filename,text_rec)) != NULL)
      {
	 /* we have a match; return the pointer to the beginning of the
	  * actual text of the text descriptor AND update the currptr
	  * return value to point to the current directory entry so that
	  * we know where we left off last time through
	  */

	 *currptr = ptr;
	 return(retval);
      }

      /* update the current entry pointer to point to the next entry */

      curr_index = (curr_index + 1L) % (ULONG) num_file;
      ptr = dirptr + curr_index;
   }

   return(NULL);			/* no match for this text descriptor  */

}	/*** text_search ***/
/*eject*/
/*******************************************************************************
********************************************************************************

  Function:	text_match

  Purpose:	Determine if a text descriptor record is the correct one
		for a file entry.

  Global variables:

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

  Return Codes:

	Code			Reason
	----			------

	NULL			no match for text descriptor record
	text_rec + 1L		pointer to beginning of text descriptor part
				of the record

  Termination Codes:

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

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

static char *text_match(filename,text_rec)
					/*******   FORMAL  PARAMETERS   *******/
register char	  *filename,		/* filename to look for		      */
		  *text_rec;		/* current text descriptor record     */

{	/*** text_match ***/


   while((*filename == *text_rec) && (*filename != ';'))
   {
      filename++;
      text_rec++;
   }

   /* now check to see if we had a match */

   if(*text_rec == ' ')
   {
      /* return a pointer to the beginning of the text descriptor */

      return(text_rec + 1L);
   }

   return(NULL);			/* not correct record for file	      */

}	/*** text_match ***/
/*eject*/
/*******************************************************************************
********************************************************************************

  Function:	get_sysdisk

  Purpose:	Get the value of SYS$DISK when the program is invoked so that
		we can set it back when we exit.

  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 get_sysdisk(orig_disk,length)
					/*******   FORMAL  PARAMETERS   *******/
	 char	  *orig_disk;		/* where to store value of SYS$DISK   */
	 USHORT	  length;		/* length of array orig_disk	      */

{	/*** get_sysdisk ***/
					/********   LOCAL  VARIABLES   ********/
       	 ULONG	  status;		/* return code status holder	      */
static	 ITM_LST  items[] = {		/* items obtained during translation  */
{0,LNM$_STRING,NULL,NULL},		/* things are filled in below	      */
{0,0,NULL,NULL}};
static $DESCRIPTOR(table_d,"LNM$PROCESS");
static $DESCRIPTOR(sysdisk_d,"SYS$DISK");


   items[0].buf_len = length;
   items[0].buf_addr = orig_disk;
   items[0].ret_len = &length;		/* store it right back in length      */

   status = SYS$TRNLNM(0,&table_d,&sysdisk_d,0,items);

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

   *(orig_disk + length) = '\0';	/* terminate it			      */

   return;

}	/*** get_sysdisk ***/   
/*eject*/
/*******************************************************************************
********************************************************************************

  Function:	screen_reset

  Purpose:	Called when something has changed the format of the screen
		and we have to make sure that the cursor still points to the
		right place.  The current file is located and the row, column,
		and screen values are reset/recalculated.

  Global variables:

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

  Return Codes:

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

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

static void screen_reset(nodes,dirptr,entptr,node_row,node_col,scr_file,
			 curr_screen,num_file,num_slot,num_screen,slot_width,
			 num_row,num_col)
					/*******   FORMAL  PARAMETERS   *******/
	 NODE_DEF nodes[][MAX_NODE_COL+1]; /* screen node matrix	      */
	 ENT_DEF  *dirptr,		/* pointer to directory information   */
		  *entptr;		/* pointer to directory entry	      */
      	 short	  *node_row,		/* current node array row	      */
		  *node_col,		/* current node array column	      */
		  *scr_file,		/* number of files for current screen */
		  *curr_screen,		/* current screen of directory	      */
	 	  num_file,	  	/* number of files in current direct. */
		  num_slot,		/* number of slots per screen	      */
		  num_screen,		/* number of screens in directorys    */
		  slot_width,		/* width of one screen slot	      */
		  num_row,		/* number of rows on the screen	      */
		  num_col;		/* number of columns on the screen    */

{	/*** screen_reset ***/
					/********   LOCAL  VARIABLES   ********/
	 short	  temp,			/* temporary variable and return code */
		  temp2,		/* temporary variable number two      */
		  new_screen;		/* temporary current screen number    */


   /* find the file in memory so we can calculate what page it is supposed to
    * be on
    */

   temp = file_search(dirptr,entptr->filename,num_file,
		      (short) strlen(entptr->filename));

   if(temp >= 0)
   {
      /* we should ALWAYS get here, because the directory name should not
       * disappear from our memory, right?
       */

      new_screen = (temp / num_slot) + 1;

      /* get us to the current screen */

      temp2 = temp - ((new_screen - 1) * num_slot);
      *node_col = temp2 / num_row;
      *node_row = temp % num_row;
                     
      /* do we need to go to a new screen?  if so, make the new one now */

      if(new_screen != *curr_screen)
      {
	 /* create the new screen by first putting the new page number in the
	  * upper-right and then rebuilding the main display
	  */

	 /* do we need to update the screen node pointers? */

	 if((new_screen == num_screen) || (*curr_screen == num_screen))
	 {
	    /* set the nodes */
                                                      
	    set_nodes(nodes,scr_file,num_screen,new_screen,num_file,
		      slot_width,num_col);
	 }

	 *curr_screen = new_screen;
      }
   }

   return;

}	/*** screen_reset ***/
/*eject*/
/*******************************************************************************
********************************************************************************

  Function:	size_qsort

  Purpose:	Sort the directory entries by size.

		Algorithm for quicksort from "Fundamentals of Data Structures",
		Ellis Horowitz and Sartaj Sahni, page 137.

  Global variables:

	Name			Examine/Modify/Use/Read/Write
	----			-----------------------------
	baseptr					X

  Return Codes:

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

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

static void size_qsort(m,n)
					/*******   FORMAL  PARAMETERS   *******/
register short	  m,			/* lower-bound of sub-array to be     */
					/* sorted			      */
		  n;			/* upper-bound of sub-array to be     */
					/* sorted			      */

{	/*** size_qsort ***/
					/********   LOCAL  VARIABLES   ********/
register long	  i,			/* loop and array index		      */
		  j;			/*  "    "    "     "		      */
static	 ULONG	  key;			/* key size entry for sorting	      */


   if(m < n)
   {
      i = m;
      j = n + 1;
      key = baseptr[m].size;

      for(;;)
      {
	 do
	 {
	    ++i;
	 }
	 while(baseptr[i].size < key);

	 do
	 {
	    --j;
	 }
	 while(baseptr[j].size > key);

	 if(i < j)
	 {
	    /* swap the file entries */

	    swap_entries(&baseptr[i],&baseptr[j]);
	 }
	 else
	    break;

      } /* for(;;) */

      swap_entries(&baseptr[m],&baseptr[j]);
      size_qsort(m,j-1);
      size_qsort(j+1,n);

   } /* if(m < n) */

   return;

}	/*** size_qsort ***/
/*eject*/
/*******************************************************************************
********************************************************************************

  Function:	date_qsort

  Purpose:	Sort the directory entries by last access date.

		Algorithm for quicksort from "Fundamentals of Data Structures",
		Ellis Horowitz and Sartaj Sahni, page 137.

  Global variables:

	Name			Examine/Modify/Use/Read/Write
	----			-----------------------------
	baseptr					X

  Return Codes:

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

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

static void date_qsort(m,n)
					/*******   FORMAL  PARAMETERS   *******/
register short	  m,			/* lower-bound of sub-array to be     */
					/* sorted			      */
		  n;			/* upper-bound of sub-array to be     */
					/* sorted			      */

{	/*** date_qsort ***/
					/********   LOCAL  VARIABLES   ********/
register long	  i,			/* loop and array index		      */
		  j;			/*  "    "    "     "		      */
static	 long	  key[2];		/* key size entry for sorting	      */


   if(m < n)
   {
      i = m;
      j = n + 1;
      key[0] = baseptr[m].time[0];
      key[1] = baseptr[m].time[1];

      for(;;)
      {
	 do
	 {
	    ++i;
	 }
	 while(time_cmp(baseptr[i].time,key) == -1);

	 do
	 {
	    --j;
	 }
	 while(time_cmp(baseptr[j].time,key) == 1);

	 if(i < j)
	 {
	    /* swap the file entries */

	    swap_entries(&baseptr[i],&baseptr[j]);
	 }
	 else
	    break;

      } /* for(;;) */

      swap_entries(&baseptr[m],&baseptr[j]);
      size_qsort(m,j-1);
      size_qsort(j+1,n);

   } /* if(m < n) */

   return;

}	/*** date_qsort ***/
/*eject*/
/*******************************************************************************
********************************************************************************

  Function:	time_cmp

  Purpose:	Compare two quadword time values.

  Global variables:

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

  Return Codes:

	Code			Reason
	----			------
	  0			time1 = time2
	 -1			time1 < time2
	  1			time1 > time2

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

static short time_cmp(time1,time2)
					/*******   FORMAL  PARAMETERS   *******/
	 long	  time1[],		/* first quadword time value	      */
		  time2[];		/* second quadword time value	      */

{	/*** time_cmp ***/
					/********   LOCAL  VARIABLES   ********/
static	 long	  temp[2];		/* for saving the result	      */
   
   LIB$SUBX(time1,time2,temp);		/* subtract time1 from time2	      */

   if(temp[1] < 0L)
      return(-1);
   else if(temp[1] == 0L && temp[0] == 0L)
      return(0);

   return(1);

}	/*** time_cmp ***/
/*eject*/
/*******************************************************************************
********************************************************************************

  Function:	swap_entries

  Purpose:	Swap two file entries.

  Global variables:

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

  Return Codes:

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

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

static void swap_entries(a,b)
					/*******   FORMAL  PARAMETERS   *******/
	 ENT_DEF  *a,			/* first to be swapped 		      */
		  *b;			/* other entry to be swapped	      */

{	/*** swap_entries ***/
					/********   LOCAL  VARIABLES   ********/
static	 ENT_DEF  temp;			/* temporary entry for swapping	      */

   memcpy((void *) &temp,(void *) a, sizeof(temp));
   memcpy((void *) a,(void *) b, sizeof(temp));
   memcpy((void *) b,(void *) &temp, sizeof(temp));

}	/*** swap_entries ***/
/*eject*/
/*******************************************************************************
********************************************************************************

  Function:	check_marks

  Purpose:	Check to see if there are any marks in the current directory.

  Global variables:

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

  Return Codes:

	Code			Reason
	----			------
	TRUE			marks exist in current directory
	FALSE			no marks in current directory

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

static USHORT check_marks(ent,num_files)
					/*******   FORMAL  PARAMETERS   *******/
register ENT_DEF  *ent;			/* pointer to current file entry      */
register short	  num_files;		/* number of files in directory	      */

{	/*** check_marks ***/

   while(num_files--)			/* check all entries if necessary     */
   {
      if(ent->command)			/* command for this file?	      */
	 return(TRUE);			/* yes, no need to check any more     */

      ent++;				/* nope, go to next entry	      */
   }

   return(FALSE);

}	/*** check_marks ***/
