char *ckzv = "Data General file support, 5A(065) 12 May 93";
 
/* C K D F I O  --  Kermit file system support for AOS/VS & derivatives */
 
/*
  Author: Frank da Cruz (fdc@columbia.edu, FDCCU@CUVMA.BITNET),
  Columbia University Academic Information Systems, New York City.

  Copyright (C) 1985, 1993, Trustees of Columbia University in the City of New
  York.  The C-Kermit software may not be, in whole or in part, licensed or
  sold for profit as a software product itself, nor may it be included in or
  distributed with commercial products or otherwise distributed by commercial
  concerns to their clients or customers without written permission of the
  Office of Kermit Development and Distribution, Columbia University.  This
  copyright notice must not be removed, altered, or obscured.

  Adapted to Data General computers in 1985 by:
  Phil Julian, SAS Institute, Inc., Box 8000, Cary, NC 27512-8000,
  with additional help:
    For using sys_gnfn() in fgen(), instead of my kludge:
        Victor Johansen, Micro_rel, 2343 W 10th Place, Tempe AZ 85281
    For using dg_open() for the pipe in zxcmd(), and also for using
    sys_proc() in start_cli():
        Richard Lamb, MIT, 77 Mass. Ave., Room 35-437, Cambridge MA 02139
  Adapted to C-Kermit 5A by Eugenia Harris (ENH) at Data General, who
  thanks Larry McCoskery, also at Data General, for many library routines
  and much debugging assistance.
*/

/*
  Support for DG/UX and other non-AOS/VS implementations
  has been removed -- only exception is MV/UX which has been left in but
  has not been tested and probably doesn't work.  DG/UX Kermit can be
  built with the cku* modules from Columbia University.        --ENH
*/

/* Includes */

#nolist
#include "ckcdeb.h"                     /* Typedefs, debug formats, etc */
#include "ckcker.h"                     /* Kermit definitions */
#include <ctype.h>                      /* Character types */
#include <stdio.h>                      /* Standard i/o */
#include <time.h>
#include <packets:create.h>
#include <packets:common.h>
#include <memory.h>
#include <dglib.h>
#include <sys_calls.h>
#include <packets:process.h>
#include <packets/filestatus.h>         /* Used for ?GNFN */
#include <paru.h>
#include <bit.h>
#include <sys/dir.h>                    /* Directory structure */
#include <sys/stat.h>                   /* File status */
#list

/* The DG compiler version 3.21 has a bug in the get*id() functions, which
 * cause the program to go into an infinite loop.  These functions should
 * return -1 unless the system is in a UNIX environment, so I made the
 * appropriate kludges.   -- Phil Julian, 8 April 87
 */
#define getgid() -1
#define getuid() -1
#define geteuid() -1
#define MAXNAMLEN ($MXFN-1)
#define MAXPATH ($MXPL-1)               /* both from system PARU.32 */
#define DIRSEP ':'
#define ISDIRSEP(c)  (((c)==':') || ((c)=='^') || ((c)=='='))
#define TIMESTAMP
#define MAXWLD 500
#define fork() vfork()
 
char *ckzsys = " Data General AOS/VS";

/* Definitions of system commands for AOS/VS */

char *DIRCMD = "filestatus/sort/assortment ";   /* For directory listing */
char *DIRCM2 = "filestatus/sort/assortment";    /* For full directory list */
char *DELCMD = "delete/v ";                     /* For file deletion */
char *TYPCMD = "type ";                         /* For typing a file */

char *PWDCMD = "directory";                     /* For saying where I am */

/* SPACMD now returns space in current directory rather than home dir. */
char *SPACMD = "space =";               /* For space in current directory */
char *SPACM2 = "space ";                /* For space in specified directory */

char *WHOCMD = "who [!sons op:exec]";

/*
  Functions (n is one of the predefined file numbers from ckermi.h):
 
   zopeni(n,name)   -- Opens an existing file for input.
   zopeno(n,name)   -- Opens a new file for output.
   zclose(n)        -- Closes a file.
   zchin(n,&c)      -- Gets the next character from an input file.
   zsout(n,s)       -- Write a null-terminated string to output file, buffered.
   zsoutl(n,s)      -- Like zsout, but appends a line terminator.
   zsoutx(n,s,x)    -- Write x characters to output file, unbuffered.
   zchout(n,c)      -- Add a character to an output file, unbuffered.
   zchki(name)      -- Check if named file exists and is readable, return size.
   zchko(name)      -- Check if named file can be created.
   zchkspa(name,n)  -- Check if n bytes available for file 
   znewn(name,s)    -- Make a new unique file name based on the given name.
   zdelet(name)     -- Delete the named file.
   zxpand(string)   -- Expands the given wildcard string into a list of files.
   znext(string)    -- Returns the next file from the list in "string".
   zxcmd(cmd)       -- Execute the command in a lower fork.
   zclosf()         -- Close input file associated with zxcmd()'s lower fork.
   zrtol(n1,n2)     -- Convert remote filename into local form.
   zltor(n1,n2)     -- Convert local filename into remote form.
   zchdir(dirnam)   -- Change working directory.
   zhome()          -- Return pointer to home directory name string.
   zkself()         -- Kill self, log out own job.
   zsattr(struct zattr *) -- Return attributes for file which is being sent.
   zstime(f, struct zattr *, x) - Set file creation date from attribute packet.
   zrename(old, new) -- Rename a file.
 */

/* Some systems define these in include files, others don't... */
#ifndef R_OK
#define R_OK 4                          /* For access */
#endif /* ifndef R_OK */
 
#ifndef W_OK
#define W_OK 2
#endif /* ifndef W_OK */

#ifdef UXIII
#include <fcntl.h>
#define MAXNAMLEN DIRSIZ
#endif
 
#ifndef O_RDONLY
#define O_RDONLY 000
#endif /* ifndef O_RDONLY */
 
/* Declarations */

int wildcarlb;                          /* Wild card ^ or # */
int maxnam = MAXNAMLEN;                 /* Available to the outside */
int maxpath = MAXPATH;

 
FILE *fp[ZNFILS] = {                    /* File pointers */
    NULL, NULL, NULL, NULL, NULL, NULL, NULL };

/* Buffers and pointers used in buffered file input */

#ifdef DYNAMIC
extern char *zinbuffer, *zoutbuffer;
#else
extern char zinbuffer[], zoutbuffer[];
#endif /* ifdef DYNAMIC */

extern char *zinptr, *zoutptr;
extern int zincnt, zoutcnt;
extern int wildxpand;

static long iflen = -1L;                /* Input file length */
static PID_T pid;                       /* pid of child fork */
#ifdef SASMOD                           /* For remote Kermit command */
static int savout, saverr;              /* saved stdout and stderr streams */
#endif /* ifdef SASMOD */
static int fcount;                      /* Number of files in wild group */
static char nambuf[MAXNAMLEN+1];        /* Buffer for a filename */
char *malloc(), *getenv(), *strcpy();   /* System functions */
extern errno;                           /* System error code */
 
char *mtchs[MAXWLD];                    /* Matches found for filename */
static char **mtchptr;                  /* Pointer to current match */
static char zmbuf[200];

/*  Z K S E L F  --  Kill Self: log out own job, if possible.  */
 
zkself() {                              /* For "bye", but no guarantee! */
    
    /* sys_term works better than kill() on the DG, but does not log off. */

    char *msg ="bye ";
    int ac2;
    ac2 = (int) msg;
    return(sys_term(getpid(),0,ac2));
}

/*  Z O P E N I  --  Open an existing file for input. */
 
zopeni(n,name) int n; char *name; {
    debug(F111," zopeni",name,n);
    debug(F101,"  fp","",(int) fp[n]);
    if (chkfn(n) != 0) return(0);
    if (n == ZSYSFN) {                  /* Input from a system function? */
        debug(F110," invoking zxcmd",name,0);
        return(zxcmd(name));            /* Try to fork the command */
    }
    if (n == ZSTDIO) {                  /* Standard input? */
        if (isatty(0)) {
            ermsg("Terminal input not allowed");
            debug(F110,"zopeni: attempts input from unredirected stdin","",0);
            return(0);
        }
        fp[ZIFILE] = stdin;
        return(1);
    }
    fp[n] = fopen(name,"r");            /* Real file. */
    debug(F111," zopeni", name, (int) fp[n]);
    if (fp[n] == NULL) perror("zopeni");
    return((fp[n] != NULL) ? 1 : 0);
}
 
/*  Z O P E N O  --  Open a new file for output.  */
 
zopeno(n,name,zz,fcb)
int n; char *name; struct zattr *zz; struct filinfo *fcb; {

    char p[3];
    char myname[] = "zopeno()";
    long validate();
 
    debug(F111," zopeno",name,n);

    if (fcb) {
        debug(F101,"zopeno fcb disp","",fcb->dsp);
        debug(F101,"zopeno fcb type","",fcb->typ);
        debug(F101,"zopeno fcb char","",fcb->cs);
    } else {
        debug(F100,"zopeno fcb is NULL","",0);
    }
    if (n != ZDFILE)
        debug(F111," zopeno",name,n);

    if (chkfn(n) != 0) return(0);
    if ((n == ZCTERM) || (n == ZSTDIO)) {   /* Terminal or standard output */
        fp[ZOFILE] = stdout;
        if (n != ZDFILE)
            debug(F101," fp[]=stdout", "",fp[n]);
        zoutcnt = 0;
        zoutptr = zoutbuffer;
        return(1);
    }

    /* ENH - in VS, the creation time can only be set on the              */
    /* create, so if the file exists and a creation date is specified     */
    /* we will delete the existing file and create the new with requested */
    /* creation date.  If no date is given, or it's invalid, we will      */
    /* not bother creating the file, as it will be created implicitly     */
    /* on the open.  Note that we ignore errors from the routine to       */
    /* set the creation date (and create the file), the assumption        */
    /* being that if it fails, the file is not created, but there's       */
    /* still a possibility that it might be created on the open.  If      */
    /* not, the error will be caught there.  Note that this whole pro-    */
    /* cedure is only invoked when the file collsion action is "update".  */
    /* That is the only time we will get to this point and find that a    */
    /* file of the same name already exists.                              */

    strcpy(p,"w");
    if (fcb) {                          /* If called with an FCB... */
        if (fcb->dsp == XYFZ_A) {       /* Does it say Append? */
            debug(F101,"zopeno opening for append access",name,0);
            strcpy(p,"a");              /* If so, open for append access */
        } else if (file_exists(name) != 0) { /* If it exists, delete it */
            if (zdelet(name) != 0)      /* and create w/incoming date */
                debug(F101,"zopeno error deleting existing file",name,0);
            else debug(F101,"zopeno existing file deleted",name,0);
            if (zz->date.len > 0) {    /* if creation date specified */
                debug(F101,"zopeno creating file with incoming date",
                      name,0);
                set_creation_date(name,zz->date.val);   /* set it */
                strcpy(p,"r+");
            }
        }
    }

    fp[n] = fopen(name,p);              /* A real file, try to open */
    if (fp[n] == NULL) {
        perror("zopeno can't open");
    } else {
        if (n == ZDFILE) setbuf(fp[n],NULL); /* Debugging file unbuffered */
    }
  
    zoutcnt = 0;                /* (PWP) reset output buffer */
    zoutptr = zoutbuffer;
    if (n != ZDFILE)
      debug(F101, " fp[n]", "", (int) fp[n]);
    return((fp[n] != NULL) ? 1 : 0);
}
 
/*  Z C L O S E  --  Close the given file.  */
 
/*  Returns 0 if arg out of range, 1 if successful, -1 if close failed.  */
 
int
zclose(n) int n; {
    int x, x2;

    if (chkfn(n) < 1) return(0);        /* Check range of n */

    if ((n == ZOFILE) && (zoutcnt > 0)) /* (PWP) output leftovers */
      x2 = zoutdump();
    else
      x2 = 0;

    x = 0;

    if (fp[ZSYSFN]) {                   /* If system function */
        x = zclosf(n);                  /* do it specially */
    } else {
        if ((fp[n] != stdout) && (fp[n] != stdin)) x = fclose(fp[n]);
        fp[n] = NULL;
    }
    iflen = -1L;                        /* invalidate file length */
    if (x == EOF)
        return (-1);
    else if (x2 < 0)
        return (-1);
    else
        return (1);
}
 
/*  Z C H I N  --  Get a character from the input file.  */
 
/*  Returns -1 if EOF, 0 otherwise with character returned in argument  */
int 
zchin(n,c) int n; int *c; {
    int a, x;

 /* (PWP) Just in case this gets called when it shouldn't. */
    if (n == ZIFILE) {
        x = zminchar();
        *c = x;
        return(x);
    }

    /* if (chkfn(n) < 1) return(-1); */
    a = getc(fp[n]);
    if (a == EOF) return(-1);
    *c = (CHAR) a & 0377;
    return(0);
}

/* Z S I N L  --  Reads a line from file number n  */
/*
 Reads line from file number n and writes it to address provided by caller.
 Writing terminates when a newline is read, but the newline is discarded.
 Writing also terminates on EOF or if length x is exhausted.  Returns 0
 on success and -1 on EOF
*/
int
zsinl(n,s,x) int n, x; char *s; {
    int a, z = 0;                       /* lifted verbatim from ckufio.c */
 
    if (chkfn(n) < 1) {                 /* Make sure file is open */
        return(-1);
    }
    a = -1;
    while (x--) {

#ifndef NLCHAR
        int old;
        old = a;                        /* Previous character */
#endif
        
        if (zchin(n,&a) < 0) {          /* Read a character from the file */
            z = -1;
            break;
        }

#ifdef NLCHAR
        if (a == (char) NLCHAR) break;  /* Single-character line terminator */
#else
        if (a == '\r') continue;        /* CRLF line terminator */
        if (old == '\r') {
            if (a == '\n') break;
            else *s++ = '\r';
        }
#endif /* NLCHAR */

        *s = a;
        s++;
    }
    *s = '\0';
    return(z);
}



/*  Z I N F I L L  --  Reads characters from ZIFILE when sending files.  */
/*
 * Suggestion: if fread() returns 0, call ferror to find out what the
 * problem was.  If it was not EOF, then return -2 instead of -1.
 * Upper layers (getpkt function in ckcfns.c) should set cxseen flag
 * if it gets -2 return from zminchar macro.
 */
int
zinfill() {
    int x;

    errno = 0;
    zincnt = fread(zinbuffer, sizeof (char), INBUFSIZE, fp[ZIFILE]);

#ifdef COMMENT
    debug(F101,"zinfill fp","",fp[ZIFILE]);
    debug(F101,"zinfill zincnt","",zincnt);
#endif

    if (zincnt == 0) {

#ifdef ferror
        x = ferror(fp[ZIFILE]);
        debug(F101,"zinfill errno","",errno);
        debug(F101,"zinfill ferror","",x);
        if (x) return(-2);
#endif /* ferror */

        x = feof(fp[ZIFILE]);
        debug(F101,"zinfill errno","",errno);
        debug(F101,"zinfill feof","",x);
        if (!x && ferror(fp[ZIFILE])) return(-2);
        return(-1);
    }
    zinptr = zinbuffer; /* set ptr to beginning, (== &zinbuffer[0]) */
    zincnt--;                   /* one less char in buffer */
    return((int)(*zinptr++) & 0377); /* because we return the first */
}
 
/*  Z S O U T  --  Write a string to the given file, buffered.  */

int 
zsout(n,s) int n; char *s; {
   if (chkfn(n) < 1) return(-1); /* Keep this here, prevents memory faults */

#ifdef COMMENT
    while (*s) {                        /* (unbuffered for debugging) */
        write(fileno(fp[n]),s,1); ++s;
    }
    return(fputs(s,fp[n]) == EOF ? -1 : 0);
#else
    if (n == ZSFILE)
        return(write(fileno(fp[n]),s,(int)strlen(s)));
    else
        return((fputs(s,fp[n]) == EOF) ? -1 : 0);
#endif
}
 

/*  Z S O U T L  --  Write string to file, with line terminator, buffered  */

int 
zsoutl(n,s) int n; char *s; {
    /* if (chkfn(n) < 1) return(-1); */
    if (fputs(s,fp[n]) == EOF) return(-1);
    fputs("\n",fp[n]);
    return(0);
}
 
/*  Z S O U T X  --  Write x characters to file, unbuffered.  */

int 
zsoutx(n,s,x) int n, x; char *s; {

#ifdef COMMENT
    if (chkfn(n) < 1) return(-1);
    return(write(fp[n]->_file,s,x));
#endif

    return(write(fileno(fp[n]),s,x));
}
 
 
/*  Z C H O U T  --  Add a character to the given file.  */
 
/*  Should return 0 or greater on success, -1 on failure (e.g. disk full)  */

#ifdef CK_ANSIC
zchout (register int n, char c)
#else 
zchout(n,c) register int n; char c;        
#endif /* CK_ANSIC */

/* zchout() */ {

    /* if (chkfn(n) < 1) return(-1); */
    if (n == ZSFILE)
        return(write(fileno(fp[n]),&c,1)); /* Use unbuffered for session log */
    else {                              /* Buffered for everything else */
        if (putc(c,fp[n]) == EOF)       /* If true, maybe there was an error */
            return(ferror(fp[n])?-1:0); /* Check to make sure */
        else                            /* Otherwise... */
            return(0);                  /* There was no error. */
    }
}

zoutdump() {
    int x;
    zoutptr = zoutbuffer;               /* Reset buffer pointer in all cases */
    debug(F101,"zoutdump chars","",zoutcnt);
    if (zoutcnt == 0) {                 /* Nothing to output */
        return(0);
    } else if (zoutcnt < 0) {           /* Unexpected negative argument */
        zoutcnt = 0;                    /* Reset output buffer count */
        return(-1);                     /* and fail. */
    }
 
/* Frank Prindle suggested that replacing this fwrite() by an fflush() */
/* followed by a write() would improve the efficiency, especially when */
/* writing to stdout.  Subsequent tests showed a 5-fold improvement!   */
/* if (x = fwrite(zoutbuffer, 1, zoutcnt, fp[ZOFILE])) {              */
 
    fflush(fp[ZOFILE]);
    if ((x = write(fileno(fp[ZOFILE]),zoutbuffer,zoutcnt)) == zoutcnt) {
        debug(F101,"zoutdump write ok","",zoutcnt);
        zoutcnt = 0;                    /* Reset output buffer count */
        return(0);                      /* write() worked OK */
    } else {
 
        debug(F101,"zoutdump write error","",errno);
        debug(F101,"zoutdump write returns","",x);
        zoutcnt = 0;                    /* Reset output buffer count */
        return(-1);                     /* write() failed */
    }
}


/*  C H K F N  --  Internal function to verify file number is ok  */
/*
 Returns:
  -1: File number n is out of range
   0: n is in range, but file is not open

   1: n in range and file is open
*/
chkfn(n) int n; {
    switch (n) {
        case ZCTERM:
        case ZSTDIO:
        case ZIFILE:
        case ZOFILE:
        case ZDFILE:
        case ZTFILE:
        case ZPFILE:
        case ZSFILE:
        case ZSYSFN:
        case ZRFILE:
        case ZWFILE: break;
        default:
            debug(F101,"chkfn: file number out of range","",n);
            fprintf(stderr,"?File number out of range - %d\n",n);
            return(-1);
    }
    return( (fp[n] == NULL) ? 0 : 1 );
}

/*  Z C H K I  --  Check if input file exists and is readable  */
 
/*
  Returns:
   >= 0 if the file can be read (returns the size).
     -1 if file doesn't exist or can't be accessed,
     -2 if file exists but is not readable (e.g. a directory file).
     -3 if file exists but protected against read access.
*/
/*
 For Berkeley Unix, a file must be of type "regular" to be readable.
 Directory files, special files, and symbolic links are not readable.
*/
long
zchki(name) char *name; {
    
    /* For some reason, the DG croaks intermittently with a call to stat(),
     * and goes into an infinite loop.  This routine needs to see if the
     * file is okay to look at and access, so I will just call ?fstat to
     * do that.  The only files that cannot be accessed by normal i/o routines
     * are directories.  Other files are fair game.
     */

    int x; long y;                      
    P_FSTAT buf;        /* struct stat buf; */  
    int ac0,ac2;
    char * temp;         /* ENH - in case we have to prepend an = */
                        /* because ?fstat looks in searchlist    */
    temp = alloc (strlen(name)+2);
    if ((*name != '^') && (*name != '@') && (*name != ':') && (*name != '=')) {
        strcpy (temp+1,name);
        *temp = '=';
    }
    else
        strcpy (temp,name);

    ac0 = (int) temp;  ac2 = (int) &buf;
    x = sys_fstat(ac0,0,ac2);
    
    free(temp);
    if (x != 0) {
        debug(F111,"zchki sys_fstat fails",name,ac0);
        return(-1);
    }
    x = buf.styp_type;                  /* Isolate file format field */
    iflen = buf.sefm;

    if ((x >= $LDIR) && (x <= $HDIR)) {
        debug(F111,"zchki skipping DIR type:",name,x);
        return(-2);
    }

    debug(F111,"zchki stat ok:",name,x);
 
    if ((x = access(name,R_OK)) < 0) {  /* Is the file accessible? */
        debug(F111," access failed:",name,x); /* No */
        return(-3);                     
    } else {
        y = buf.sefm;
        debug(F111," access ok:",name,(int) y); /* Yes */
        return( (y > -1) ? y : 0 );
    }
}

/*  Z C H K O  --  Check if output file can be created  */
 
/*
 Returns -1 if write permission for the file would be denied, 0 otherwise.
*/
zchko(name) char *name; {
    int i, x;
    char s[50], *sp;    
 
    sp = s;                             /* Make a copy, get length */
    x = 0;
    while ((*sp++ = *name++) != '\0')
        x++;
    if (x == 0) return(-1);             /* If no filename, fail. */
 
    debug(F101," length","",x);
    for (i = x; i > 0; i--)             /* Strip filename. */
        if (ISDIRSEP(s[i-1])) break;
 
    debug(F101," i","",i);
    if (i == 0)                         /* If no path, use current directory */
        strcpy(s,"=");
    else                                /* Otherwise, use given one. */
        s[i] = '\0';
 
    x = access(s,W_OK);                 /* Check access of path. */
    if (x < 0) {
        debug(F111,"zchko access failed:",s,errno);
        return(-1);
    } else {
        debug(F111,"zchko access ok:",s,x);
        return(0);
    }
}

/*  Z D E L E T  --  Delete the named file.  */
 
zdelet(name) char *name; {
    if (*name == '\0') {
        debug(F111,"zdelet passed null filename","",0);
        return(-1);
    }
    return(unlink(name));
}
 
 
/*  Z R T O L  --  Convert remote filename into local form  */
 
/*  For AOS/VS, we don't care about case -- we only care about certain chars */
 
zrtol(name,name2) char *name, *name2; {
    char *p; int flag;
    if (!name || !name2)
      return;
    debug(F110,"zrtol original name ",name,0);
    p = name2;				/* Output pointer to new name */
    for ( ; *name != '\0'; name++) {
	if (*name > ' ') flag = 1;	/* Strip leading blanks and controls */
	if (flag == 0 && *name < '!')
	  continue; 
        if (*name == '-') *p = '_';	/* Change dash to underscore */
    	else if (*name == '^' ||	/* Change template characters to 'X' */
		 *name == '*' ||
		 *name == '+' ||
		 *name == '#' ||
		 *name == '\\' )
	  *p = 'X';
	else *p = *name;		/* Other characters are just copied */
	p++;	
    }
    *p-- = '\0';			/* Terminate */
    while (*p < '!' && p > name2)	/* Strip trailing blanks & controls */
      *p-- = '\0';
    if (*name2 == '\0') strcpy(name2,"NONAME");
    debug(F110,"zrtol translated name ",name2,0); /* Done */
}
/*  Z S T R I P  --  Strip device & directory name from file specification */
 
/*  Strip pathname from filename "name", return pointer to result in name2 */
 
static char work[257];          /* buffer for use by zstrip and zltor */
 
VOID                                    
zstrip(name,name2) char *name, **name2; {
    char *cp, *pp, *p2;
    debug(F110,"zstrip before",name,0);
    pp = work;

    for (cp = name; *cp != '\0'; cp++) {
        if (ISDIRSEP(*cp))
          pp = work;
        else
          *pp++ = *cp;
    }
    *pp = '\0';                         /* Terminate the string */
    *name2 = work;
    debug(F110,"zstrip after",*name2,0);
}
 
/*  Z L T O R  --  Local TO Remote */
 
/*  Convert filename from local format to common (remote) form.  */

VOID 
zltor(name,name2) char *name, *name2; {
    char work[100], *cp, *pp;
    int dc = 0;
 
    debug(F110,"zltor",name,0);
    pp = work;
    /* Strip off the DG directory prefix */
    for (cp=name; *cp != '\0'; cp++) {
        if (ISDIRSEP(*cp)) {
            dc = 0;
            pp = work;
        }
        else if (islower(*cp)) *pp++ = toupper(*cp); /* Uppercase letters */
        else if (*cp == '~') *pp++ = 'X';       /* Change tilde to 'X' */
        else if (*cp == '#') *pp++ = 'X';       /* Change number sign to 'X' */
        else if (*cp == '$') *pp++ = 'X';       /* Change dollar sign to 'X' */
        else if (*cp == '?') *pp++ = 'X';     /* Change question mark to 'X' */
        else if ((*cp == '.') && (++dc > 1)) *pp++ = 'X'; /* & extra dots */
        else *pp++ = *cp;
    }
    *pp = '\0';                         /* Tie it off. */
    cp = name2;                         /* If nothing before dot, */
    if (*work == '.') *cp++ = 'X';      /* insert 'X' */
    strcpy(cp,work);
    debug(F110," name2",name2,0);
}
 
 
/*  Z C H D I R  --  Change directory  */
 
zchdir(dirnam) char *dirnam; {
    char *hd;
    if (*dirnam == '\0') hd = getenv("HOME");
    else hd = dirnam;
    return((chdir(hd) == 0) ? 1 : 0);
}
 
/*  Z H O M E  --  Return pointer to user's home directory  */
 
char *
zhome() {
    char *result,*ptr;
    static char *buf;
    static int homeflag = 0;

    if (homeflag) return (buf);             /* if homeflag, result already */
    result = (getenv("HOME"));              /* in buf - don't do it again  */
    if (result != NULL) {
        buf = malloc (strlen (result) + 2); /* 1 for null, 1 for final : */
        if (buf == NULL) {
            debug(F111,"zhome can't allocate memory","",0);
            return(NULL);
        }
        for (ptr = buf ; *result != '\0'; ptr++,result++)
            if (*result == '/')
                *ptr = DIRSEP;
            else
                *ptr = toupper(*result);
        *ptr++ = DIRSEP;
        *ptr = '\0';
        homeflag = 1;
    }
    return(buf);
}

/*  Z G T D I R  --  Return pointer to user's current directory  */

#ifdef MAXPATH
#define CWDBL MAXPATH
#else
#define CWDBL 100
#endif

static char cwdbuf[CWDBL+1];

char *
zgtdir() {
    char *buf;
    int error;
    buf = cwdbuf;
    if ((error = get_dir(buf)) != 0) {
	debug(F111,"zgtdir can't get dir name, VS error","",error);
	return(NULL);
    }
    else
	return(buf); 
}

/*********************************************************************
                P I P E _ C A L L

   Split off the arguments from a command to the system, and
   pass the command and arguments to the system routine.
   Return the name of the temp file which stored the results of the
   command.
   This routine assumes that the "l=" option is available on all
   commands, which is true of AOS/VS CLI.  Other DG systems may need
   another method, if the l= option is not available.

   Returns 0 if the system() function succeeds (>1 status)
           1 if the system() function fails.

   I would have deleted this routine, except that I use it in zxpand()
   if a # is used as a wild card character.  I used to depend on this
   routine for zxcmd() as well as zxpand(), but I now use sys_proc and
   sys_gnfn() respectively.
   
*********************************************************************/

int pipe_call(command,output_fname)
   char  *command,output_fname[];
{
   /* Look for the first semi-colon, if any, and strip up to that point, plus 
      any blanks following it.  Because the system() command refers to
      the initial working directory, a "dir xxx;" is usually pre-pended. 
   */
   char  *arguments, syscommand[512], locommand[512];
   char *insert,*fname,*cp,*tempname;
   FILE *dummy;
   
   strcpy(locommand,command);
   /* Find the first ;, if any */
   for (insert=locommand; ((*insert) && (*insert!=';')); insert++);
   if (*insert) arguments = insert+1;
   else arguments = locommand;          /* No ; found */
   /* Skip leading spaces */
   for (; ((*arguments) && isspace(*arguments)); arguments++);
   /* Find the end of the command to CLI */
   for (; ((*arguments) && (*arguments!=',') && !isspace(*arguments));
        arguments++);
   /* If *arguments is NULL, there are no parameters to the command.
      Otherwise, null-terminate command and address the arguments */
   if (*arguments) *arguments++ = '\0';

   /* create the @OUTPUT file name -- a unique file name */
   fname = getenv("HOME");  
   for (cp=fname; *cp; cp++) if (*cp == '/') *cp=':';
   strcpy(syscommand,fname);   strcat(syscommand,":?kermit_pipe_123456");
   fname = &syscommand[0];
   mktemp(fname);
   strcpy(output_fname,fname);

   /* Create the file to establish its file type first */
   close(dummy=open(output_fname,O_WRONLY));

   /* Append an L= (listing =) switch.  All CLI commands have this. */
   sprintf(syscommand,"%s/l=%s%s",locommand,output_fname,arguments);
   debug(F101,"syscommand",syscommand,0);
   /* now execute the command */
   if (system(syscommand) > 1) {
        perror("system");
        return(1);
   }
   return(0);
}


/*  Z X C M D -- Run a system command so its output can be read like a file */

zxcmd(filenum,comand) int filenum; char *comand; {
/******** Start of Rick Lamb's addition to Kermit history. ****************/
/* I modified Rick's code to use a tempname type of file.  -- Phil Julian. */
/* Can open and start a task in AOS using their dg_open command
 * and a system call "sys_proc" in "start_cli". R.H.Lamb 12/86 
 */
        FILE *piped;
        char temp[256];
        char *tempfile,*cp,pipename[256];
        char *shell, *savep, *comand2;
        int i;
         
        /* Create a unique pipe file in the users home directory, because
         * the actual working directory could be where the user has no
         * write privelages, or two users may collide in the same directory.
         */
        debug(F000,"Entering zxcmd","",0);
        if (chkfn(filenum) < 0) return(-1);
        tempfile = getenv("HOME");  
        for (cp = tempfile; *cp; cp++) if (*cp == '/') *cp=':';
        /* Make sure and copy the NULL also */
        memcpy (pipename, tempfile, strlen(tempfile)+1);
        strcat (pipename,":?kermit_pipe_123456");
        mktemp(pipename);

/* The command interpreter for AOS is "cli.pr"
 * for MV/UX its :bin:sh:pr 
 */
        if ((piped=dg_open(pipename,$ICRF+$OFIN+$OFCR+$RTDY,$FPIP))==NULL) {
            perror("Trouble creating a pipe in zxcmd\n"); 
            fprintf(stderr,"Pipe filename was [%s]\n",pipename);
            return(0);
        }
        debug(F000," pipe file created","",0);
        debug(F110," zxcmd: received command = ",comand,0);

        savep = strchr(comand,' ');     /* enh - find blank and if there */
        if (savep != NULL) {            /* is one, split comand in 2     */
            *savep = '\0';
            comand2 = ++savep;          /* comand2 points to 1st arg */
        } else comand2 = NULL;          /* we're doing all this so we can */
        debug(F110," zxcmd: command = ",comand,0); /* put the L= switch on*/
        if (comand2 != NULL)            /* the command rather than the proc */
            debug(F110," zxcmd: arguments = ",comand2,0);
        else
            debug(F110," zxcmd: arguments = none","",0);
#ifdef mvux
        shell = ":bin:sh.pr";
#else
        shell = ":cli.pr";
#endif
        if (comand2 != NULL)
            sprintf(temp,"%s,%s/L,%s",shell,comand,comand2);
        else
            sprintf(temp,"%s,%s/L",shell,comand);

        debug(F110," zxcmd: revised command = ",temp,0);

        if (start_cli(temp,pipename))
                {perror("Can't execute command in zxcmd\n"); return(0);}
        fp[filenum]=piped;
        fp[ZSYSFN]=fp[filenum];
        zincnt = 0;
        zinptr = zinbuffer;
        return(1);
}

#ifdef SASMOD

/* For remote Kermit command */
/*  Z X L O G  --  redirect stderr and stdout for logging. */

zxlog() {
    FILE *tmpf, *tmpfile();

    if (chkfn(ZSYSFN) != 0) return(0);
    /* Unix magic to redirect stdout and stderr to temporary file */
    fflush(stdout); fflush(stderr);     /* synchronize */
    if ((tmpf = tmpfile()) == NULL) return(0);
    if ((savout = dup(1)) < 0 || (saverr = dup(2)) < 0) return(0);
    dup2(fileno(tmpf), 1); dup2(fileno(tmpf), 2);
    fp[ZSYSFN] = tmpf;
    return(1);
}


/*  Z X U N L O G  --  restore stderr and stdout from logging. */

zxunlog() {
    /* restore stdout and stderr */
    fflush(stdout); fflush(stderr);     /* synchronize */
    dup2(savout, 1); close(savout);
    dup2(saverr, 2); close(saverr);

    /* rewind to start of temporary file */
    rewind(fp[ZSYSFN]);

    fp[ZIFILE] = fp[ZSYSFN];
    pid = 0;
    return(1);
}

#endif SASMOD

/*  Z C L O S F  - wait for the child fork to terminate and close the pipe. */

zclosf() {
    int wstat;
    fclose(fp[ZIFILE]);
    fp[ZIFILE] = fp[ZSYSFN] = NULL;
    while ((wstat = wait(0)) != pid && wstat != -1) ;
    return(1);
}


/******** More of Rick Lamb's addition to Kermit history. */

/* S T A R T _ C L I - starts a command as another concurrent task.
 * The command is equivalent to the CLI command of
 * "proc/def/output=pipename :cli command" 
 */
start_cli(command,pipename)
char *command,*pipename;
{       

        P_PROC packet;
        P_ISEND message;
        int len,pid,err;
        short int *string = (short int *)command;
        
        debug(F110,"Start_cli: command = ",command,0);

        len = strlen(command);

        message.isfl = (short) 0;       /* System flags */
        message.iufl = (short) $RFCF;   /* User flags */
        message.idph = (long) 0;        /* Destination port number */
        message.iopn = (short) 0;       /* Local origin port number */
        message.ilth = (short) (len / 2 + 1);   /* Length (in words) of message */
        message.iptr = string;          /* Pointer to message buffer */

        packet.pflg = (short) 0;        /* Flags for process creation */
        packet.ppri = (short) -1;       /* Process priority -- was 3 */
        packet.psnm = ":cli.pr";        /* Byte pointer to program name */
        packet.pipc = &message;         /* Pointer to initial msg. or -1 */
        packet.pnm  = (char *) -1;      /* Byte ptr to process name or -1 */
        packet.pmem = (long) -1;        /* Maximum memory pages or -1 */
        packet.pdir = (char *) 0;       /* Byte ptr to initial dir. or -1/0 */
        packet.pcon = (char *) 0;       /* Byte ptr to console name or -1/0 */
        packet.pcal = (short) -1;       /* Max concurrent system calls or -1 */
        packet.pwss = (short) -1;       /* Max working set size or -1 */
        packet.punm = -1;               /* Byte ptr to username or -1 */
        /* Note that $PVPC (unlimited sons) causes privelage violations
         * for users that are not royally endowed.  Anyway, following are
         * the privileges bits.  -- Phil Julian
         */
        packet.pprv = (short) ( /* $PVPC+ */ $PVWS+$PVEX+$PVIP); 
        packet.ppcr = (short) -1;       /* Maximum sons or -1 */
        packet.pwmi = (short) -1;       /* Working set minimum or -1 */
                                        /* reserved */
        packet.pifp = "@Null";          /* Byte ptr to @INPUT  or -1/0 */
        packet.pofp = "@Null";          /* Byte ptr to @OUTPUT or -1/0 */
        packet.plfp = pipename;         /* Byte ptr to @LIST   or -1/0 */
        packet.pdfp = (char *) 0;       /* Byte ptr to @DATA   or -1/0 */
        packet.smch = (_ulong) -1;      /* Max CPU time or -1 */

        if (err = sys_proc(&packet,&pid)) {
                perror("Start_cli: sys_proc ");
                fprintf(stderr,"Start_cli: Error in sys_proc = %#o\n",err);
                return(1);
        }
        else
        return(0);
}
/******** End of Rick Lamb's addition to Kermit history. */


/*  Z X P A N D  --  Expand a wildcard string into an array of strings  */
/*
  Returns the number of files that match fn1, with data structures set up
  so that first file (if any) will be returned by the next znext() call.
*/
zxpand(fn) char *fn; {

   /* Victor Johansen's code requires no change in zxpand(). 
    * However, it is difficult to expand wild card strings that
    * contain some ^ or #.  For a #, you must check the file type for
    * being a directory, then open up the directory, and keep searching
    * for sub-directories, etc.  However, an simple kludge can be used
    * in this case, which saves me the programming effort.  Anyone
    * using the # should pay an execution time penalty for using it.
    * So when a string contains the #, we use the original kludge that
    * I used before getting Victor's code.  Victor Johansen's code is more 
    * efficient because it uses sys_gnfn(), but I have to do more work
    * in some cases.  With the ^'s, the directory name must be parsed 
    * and adjusted, before opening the correct directory.
    */

   char tempname[256],command[256];
   int pipe_call();
   FILE *sysout;
   char *curptr, *saveptr, buffer[257], *pos, *end;
   int n;

   wildcarlb = 0;
   for (curptr = fn; *curptr; curptr++) 
        if (*curptr == '#') { wildcarlb = 0; goto nonvictor; }
   wildcarlb = 1; goto victor;

nonvictor:           
   curptr = command;
   sprintf(curptr, "filestatus/cpl=16/nheader %s", fn);
   curptr = tempname;
   pipe_call(command,curptr);

   /* Read the file of filenames, and parse out a universal name */
   sysout = fopen(tempname,"r");
   for (fcount=0; n=dg_fgets(buffer,256,sysout); )
      {
        curptr = (char *) malloc(min(256,strlen(buffer)));
        mtchs[fcount]=curptr;
        /* delete leading spaces, leading directory name, and and trailing LF */
        if (iscntrl(*(pos = &buffer[strlen(buffer)-1])))
               *pos-- = '\0';
        /* First char will be =, if working dir, or : or @ if not.
           Delete the =, but keep others.
        */
        for (pos=buffer; *pos; pos++) {
             if (*pos == '=') break;
             if ((*pos == ':') || (*pos == '@'))  { pos--; break; }
        }
        strcpy(mtchs[fcount],pos+1);
        fcount++;
       }
   fclose(sysout);
   zdelet(tempname);

victor:
    if (wildcarlb)
        fcount = fgen(fn,mtchs,MAXWLD); /* Look up the file. */

    if (fcount > 0) {
        mtchptr = mtchs;                /* Save pointer for next. */
    }
    debug(F111,"zxpand",mtchs[0],fcount);
    return(fcount);
}

 
 
/*  Z N E X T  --  Get name of next file from list created by zxpand(). */
/*
 Returns >0 if there's another file, with its name copied into the arg string,
 or 0 if no more files in list.
*/
znext(fn) char *fn; {
    if (fcount-- > 0)
       {
          /* Victor Johansen's code requires no change in znext(), but my
           * code does.  The flag, wildcardlb is 1, if Victor's code is
           * in effect.
           */
          strcpy(fn,*mtchptr++);
          if (wildcarlb == 0)  /* My old code: Phil Julian */ 
               free (*(mtchptr-1));
       }
    else *fn = '\0';
    debug(F111,"znext",fn,fcount+1);
    return(fcount+1);
}
 
/*  Z C H K S P A  --  Check if there is enough space to store the file  */

/*
 Call with file specification f, size n in bytes.
 Returns -1 on error, 0 if not enough space, 1 if enough space.
*/
int
zchkspa(f,n) char *f; long n; {         /* ENH - this is not feasible in VS */
    return(1);                          /* where the space returned is an   */
}                                       /* allocation limit rather than a   */
                                        /* physical one - so always return 1*/
 
/*  Z N E W N  --  Make a new name for the given file  */
 
znewn(fn,s) char *fn, **s; {
    static char buf[100];
#define ZNEWNMD 4                       /* Max digits for version number */
    char *bp, *xp;
    int len = 0, n = 0, d = 0, t, i, power = 1;
    int j, k;
    int max = MAXNAMLEN;

    bp = buf;
    while (*fn) {                       /* Copy name into buf */
        *bp++ = *fn++;
        len++;
    }
    if (len > max-2) {                  /* Don't let it get too long */
        bp = buf + max-2;
        len = max - 2;
    }
        
    for (i = 1; i < (ZNEWNMD - 1); i++) {
        power *= 10;
        *bp++ = '+';
        *bp-- = '\0';
        
        n = zxpand(buf);                /* Expand the resulting wild name */
 
        while (n-- > 0) {               /* Find any existing name~d files */
            xp = *mtchptr++;
            xp += len;
            if (*xp == '.') {
                t = atoi(xp+1);
                if (t > d) d = t;       /* Get maximum d */
            }
        }
        if (d < power-1) {
            sprintf(bp,".%d",d+1);      /* Make name~(d+1) */
            *s = buf;
            return;
        }
        bp--; len--;
    }
/* If we ever get here, we'll overwrite the xxx~100 file... */
}

#ifndef NOFRILLS

int
zmail(p,f) char *p, *f; { /* Send f as mail to addr p */
/*
  Returns 0 on success
   2 if mail delivered but temp file can't be deleted
  -2 if mail can't be delivered
  The UNIX version always returns 0 because it can't get a good return
  code from zsyscmd.

    *zmbuf = '\0';                      /* Not implemented in AOS/VS Kermit */
    return(-2);
}
#endif /* NOFRILLS */
 
#ifndef NOFRILLS
int
zprint(p,f) char *p; char *f; {         /* Print file f with options p */

    sprintf(zmbuf,"qprint%s %s",p,f);  /* ENH - options must be specified */
    zsyscmd(zmbuf);                    /* by user as /<option>/<option>.. */
    return(0);
}
#endif /* NOFRILLS */

/*  Z R E N A M E  -- changes the name of old to new  */
/* 
 Returns -1 on failure, 0 on success
*/
zrename(old,new) char *old, *new; {
    int     ac0,ac1,ac2,result;
 
    ac0 = (char *) old;
    ac1 = (char *) new;
    ac2 = 0;

    result = sys($RENAME,&ac0,&ac1,&ac2);
    if (result > 0) {
        debug(F110," rename fails",old,result); /* last arg is zero in unix */
        return(-1);                             /* - using result may not   */
    }                                           /* work                     */
    if (result < 0) {
        debug (F110," negative return from rename system call",old,result);
        return(-1);
    }
    return(0); 
}

/*  Z F C D A T  --  Get file creation date */
/*
  Call with pointer to filename.
  On success, returns pointer to creation date in yyyymmdd hh:mm:ss format.
  On failure, returns pointer to null string.
*/
static char datbuf[40];
 
/* static */                            /* (===OS2 change===) */
char *
zfcdat(name) char *name; {

#ifdef TIMESTAMP

    P_FSTAT fs_pak;                             /* for AOS/VS only */
    int year,month,day,hours,minutes,seconds;
    int     error;
    int     ac0,ac1,ac2;                /* registers for fstat system call */
    long    date;

    ac0 = name;                         /* ptr to filename */
    ac1 = 0;                            /* resolve links */
    ac2 = (P_FSTAT *) &fs_pak;
    error = sys ($FSTAT, &ac0, &ac1, &ac2); /* get modification date */
    if (error != 0) {
        debug(F110,"zfcdat stat failed",name,error); 
        return("");
    }                                  
                                        
    date = fs_pak.stmh.long_time;       /* last modification date in seconds */
    error = dg_date(fs_pak.stmh.short_time[_DATE],&day,&month,&year);
    if (error != 0) {
        debug(F110,"zfcdat call to dg_date() failed","",error);
        return("");
    }
    error = dg_time(fs_pak.stmh.short_time[_TIME],&hours,&minutes,&seconds);
    if (error != 0) {
        debug(F110,"zfcdat call to dg_time() failed","",error);
        return("");
    }
    if (year < 100)
      year += 1900;
    if (year < 0 || year > 2100) {          /* Make sure year is ok */
        debug(F110,"zfcdat date failed",name,0);
        return("");
    }
    if (month  < 1 || month  > 12)
      return("");
    if (day < 0 || day > 31)
      return("");
    if (hours < 0 || hours > 23)
      return("");
    if (minutes  < 0 || minutes  > 59)
      return("");
    if (seconds < 0 || seconds  > 59)
      return("");
    sprintf(datbuf,"%04d%02d%02d %02d:%02d:%02d",year,month,day,hours,
                                                 minutes,seconds);
    debug(F111,"zfcdat",datbuf,strlen(datbuf));
    return(datbuf);

#else
    return ("");
#endif /* timestamp */
}

/* Z S T I M E  --  Set creation date for incoming file */
/*
 Call with:
 f  = pointer to name of existing file.
 yy = pointer to a Kermit file attribute structure in which yy->date.val
      is a date of the form yyyymmdd hh:mm:ss, e.g. 19900208 13:00:00.
 x  = is a function code: 0 means to set the file's creation date as given.
      1 means compare the given date with the file creation date.
 Returns:
 -1 on any kind of error.
  0 if x is 0 and the file date was set successfully.
  0 if x is 1 and date from attribute structure <= file creation date.
  1 if x is 1 and date from attribute structure > file creation date.
*/

int
zstime(f,yy,x) char *f; struct zattr *yy; int x; {
    int r = -1;                         /* return code */
    int     error;
    long tm;                            /* UNIX scalar form of date/time */
    char myname[] = "zstime()";
    long validate();
    int     vs_existing_datetime, vs_incoming_datetime;
    char    existing_kdate[20], *incoming_kdate = yy->date.val;
    void    convert_kdate_to_aosvs(),convert_aosvs_to_kdate();

    if ((x != 0) && (x != 1)) {
        debug(F111,"Bad argument",yy->date.val,yy->date.len);
        return (-1);
    }
    tm = validate(yy,myname);                  /* tm not used unless error */
    if (tm == -1) {
        debug(F111,"zstime fails on date check",incoming_kdate,yy->date.len);
        return (-1);
    }
    if ( error = fs_get_modification_date (f,&vs_existing_datetime) ) {
        debug(F110,"Can't stat file:",f,0);
        return(-1);
    }
    debug(F111,"zstime FSTAT of existing file okay",f,vs_existing_datetime);
    convert_kdate_to_aosvs(incoming_kdate,&vs_incoming_datetime);
    debug(F111,"zstime date of incoming file","",vs_incoming_datetime);

    /* ENH - modified for VS - we set the creation date in zopeno() */
    /* because the only straightforward way of setting it in VS is on the  */
    /* create; so, if x is 0 here we don't do anything                     */

    switch (x) {                        /* Execute desired function */
      case 0:                           /* Set the creation date of the file */
        debug(F110,"VS creation date intentionally not set in zstime(): ",f,0);
        r = 0;
        break;
      case 1:                           /* Compare the dates */
        convert_aosvs_to_kdate(existing_kdate,vs_existing_datetime);
        debug(F111,"zstime compare existing file date",existing_kdate,0);
        debug(F111,"zstime compare incoming file date",incoming_kdate,0);
        if (vs_incoming_datetime < vs_existing_datetime) r = 1; else r = 0;
        break;
      default:                          /* Error */
        r = -1;
    }
    return (r);
}

        
/*  Z S A T T R  --  fills in Kermit file attribute structure */
/*
 Returns 0 on success or -1 on falure.  Any string members that are null
 should be ignored by the caller; likewise, any numeric members that are
 -1 should be ignored.  (Actually, there is no error return right now.)
*/
int
zsattr(xx) struct zattr *xx; {
    long k;

    if (iflen > 0)
        k = iflen % 1024L;                  /* file length in k bytes */
    else k = 0L;
    if (k != 0L) k = 1L;
    xx->lengthk = (iflen / 1024L) + k;
    xx->type.len = 0;                   /* file type can't be filled in here */
    xx->type.val = "";
    if (*nambuf) {
        xx->date.val = zfcdat(nambuf);  /* File creation (mod. in VS) date */
        xx->date.len = (int)strlen(xx->date.val);
    } else {
        xx->date.len = 0;
        xx->date.val = "";
    }
    xx->creator.len = 0;                /* File creator */
    xx->creator.val = "";
    xx->account.len = 0;                /* File account */
    xx->account.val = "";
    xx->area.len = 0;                   /* File area */
    xx->area.val = "";
    xx->passwd.len = 0;                 /* Area password */
    xx->passwd.val = "";
    xx->blksize = -1L;                  /* File blocksize */
    xx->access.len = 0;                 /* File access */
    xx->access.val = "";
    xx->encoding.len = 0;               /* Transfer syntax */
    xx->encoding.val = 0;
    xx->disp.len = 0;                   /* Disposition upon arrival */
    xx->disp.val = "";
    xx->lprotect.len = 0;               /* Local protection */
    xx->lprotect.val = "";
    xx->gprotect.len = 0;               /* Generic protection */
    xx->gprotect.val = "";
    xx->systemid.len = 2;               /* System ID */
    xx->systemid.val = "U1";
    xx->recfm.len = 0;                  /* Record format */
    xx->recfm.val = "";
    xx->sysparam.len = 0;               /* System-dependent parameters */
    xx->sysparam.val = "";
    xx->length = iflen;                 /* Length */
    return(0);
}
 


/* Directory Functions for Unix, written by Jeff Damens, CUCCA, 1984. */
 
/*
 * The path structure is used to represent the name to match.
 * Each slash-separated segment of the name is kept in one
 * such structure, and they are linked together, to make
 * traversing the name easier.
 */
 
struct path {
              char npart[MAXNAMLEN];    /* name part of path segment */
              struct path *fwd;         /* forward ptr */
            };
 
#define SSPACE 10000                    /* size of string-generating buffer */
static char sspace[SSPACE];             /* buffer to generate names in */

static char *freeptr,**resptr;          /* copies of caller's arguments */
static int remlen;                      /* remaining length in caller's array*/
static int numfnd;                      /* number of matches found */
 
/*
 * splitpath:
 *  takes a string and splits the slash-separated portions into
 *  a list of path structures.  Returns the head of the list.  The
 *  structures are allocated by malloc, so they must be freed.
 *  Splitpath is used internally by the filename generator.
 *
 * Input: A string.
 * Returns: A linked list of the slash-separated segments of the input.
 */
 
struct path *
splitpath(p)
char *p;
{
 struct path *head,*cur,*prv;
 int i;
 head = prv = NULL;
 if (*p == '/') p++;            /* skip leading slash */
 while (*p != '\0')
 {
   cur = (struct path *) malloc(sizeof (struct path));
   debug(F101,"splitpath malloc","",cur);
   if (cur == NULL) fatal("malloc fails in splitpath()");
   cur -> fwd = NULL;
   if (head == NULL) head = cur;
   else prv -> fwd = cur;       /* link into chain */
   prv = cur;
   for (i=0; i < MAXNAMLEN && *p != '/' && *p != '\0'; i++)
     cur -> npart[i] = *p++;
   cur -> npart[i] = '\0';      /* end this segment */
   if (i >= MAXNAMLEN) while (*p != '/' && *p != '\0') p++;
   if (*p == '/') p++;
 }
 return(head);
}

/*  F G E N  --  finds filenames that match string  */
/*
 * fgen:
 *  This is the actual name generator.  It is passed a string,
 *  possibly containing wildcards, and an array of character pointers.
 *  It finds all the matching filenames and stores them into the array.
 *  The returned strings are allocated from a static buffer local to
 *  this module (so the caller doesn't have to worry about deallocating
 *  them); this means that successive calls to fgen will wipe out
 *  the results of previous calls.  This isn't a problem here
 *  because we process one wildcard string at a time.
 *
 * Input: a wildcard string, an array to write names to, the
 *        length of the array.
 * Returns: the number of matches.  The array is filled with filenames
 *          that matched the pattern.  If there wasn't enough room in the

 *          array, -1 is returned.
 * By: Jeff Damens, CUCCA, 1984.
 */
 
fgen(pat,resarry,len)
char *pat,*resarry[];
int len;
{
/* Victor Johannsen helped out with this addition to Kermit.  The use of
 * sys_gnfn() is the proper way to solve this problem.  But, I would prefer
 * not building up the static string space.  This can be a problem when a
 * long, full-qualified directory name pattern is used, since this directory
 * name would be pre-pended to each file name.  
 *   A later fix would actually not build a list, but call sys_gnfn() within
 * znext(), and update the counters appropriately.  Several systems support
 * this model, which seems much more flexible and not at all space-limited.
 *   -- Phil Julian, 30 April 1987
 */
   P_GNFN          packet;                 /* packet for gnfn call */
   char            prefix[256];
   char            dirname[256];
   int             chan;                   /* Channel for open the DIR */
   FILE            *Ftemp;
   char            *DIRptr;                /* DIR name */
   int             i,off;
   int             got_dir = 0;
   char            *pattern,*cp;

   /* If a directory prefix is passed in, and possibly some wild cards, we
    * will need to parse off the directory to open.  We do not assume that
    * directories can be descended ad infinitum.
    */
   i = strlen(pat); zero(prefix,min(i+1,256));
   for (cp = pat+i-1; i-- >= 0; cp--)
        if ((*cp == ':') || (*cp == '^') || (*cp == '=') || (*cp == '@')) {
             memcpy(prefix,pat,off = i+1);
             memcpy(dirname,prefix,off-1), dirname[off-1] = 0;
             DIRptr = dirname, pattern = cp+1;
             got_dir = 1; 
             /* Parse ^ for moving up a directory */
             if (*pat == '^') {
                  /* From the tail of the current dir, back up to : */
                  char *kp,*dp;
                  int pos = 0;
                  
                  i = strlen(dp = getdir()) - 1;
                  for (kp = pat; *kp == '^'; kp++,i--,pos++) 
                       for (;dp[i] != ':';i--);
                  i++;
                  if (strlen(DIRptr)) {                /* Less ^s */
                       memcpy(prefix,DIRptr+pos,strlen(DIRptr)-pos);
                       prefix[strlen(DIRptr)-pos] = 0; /* Terminate */
                  } else prefix[0] = 0;
                  memcpy(dirname,dp,i);                /* The ^'d DIR */
                  off -= pos;
                  if (pos = strlen(prefix)) {
                       memcpy(dirname+i,":",1);        /* Dir separator */
                       memcpy(dirname+i+1,prefix,pos); /* Rest of dir name */
                  }
                  dirname[i+off] = 0;                  /* Null terminate */
                  memcpy(prefix,dirname,off = strlen(dirname));
                  prefix[off] = ':'; prefix[++off] = 0;
             }
             break;
        }

   if (got_dir == 0) {
        DIRptr = getdir();
        pattern = pat;
   }

   if ( (Ftemp = fopen(DIRptr,"r")) == NULL) return(0);
   chan = fchannel(Ftemp);

   numfnd = 0;
   freeptr = sspace;
   resptr = resarry;
   remlen = len;

   packet.nfky = 0;
   packet.nftp = pattern;
   packet.nfnm = nambuf;
   
   while (i = !sys_gnfn(chan,&packet)) {
         if (got_dir == 0) addresult( nambuf );
         else {
             memcpy(prefix+off,nambuf,strlen(nambuf)+1);
             addresult( prefix );
         }
   }

   fclose(Ftemp);

   return(numfnd);              /* and return the number of matches */
}

/*  T R A V E R S E  -- searches directory for matches  */
/* traverse:
 *  Walks the directory tree looking for matches to its arguments.
 *  The algorithm is, briefly:
 *   If the current pattern segment contains no wildcards, that
 *   segment is added to what we already have.  If the name so far
 *   exists, we call ourselves recursively with the next segment
 *   in the pattern string; otherwise, we just return.
 *
 *   If the current pattern segment contains wildcards, we open the name
 *   we've accumulated so far (assuming it is really a directory), then read

 *   each filename in it, and, if it matches the wildcard pattern segment, add
 *   that filename to what we have so far and call ourselves recursively on the
 *   next segment.
 *
 *   Finally, when no more pattern segments remain, we add what's accumulated
 *   so far to the result array and increment the number of matches.
 *
 * Input: a pattern path list (as generated by splitpath), a string
 *        pointer that points to what we've traversed so far (this
 *        can be initialized to "/" to start the search at the root
 *        directory, or to "./" to start the search at the current
 *        directory), and a string pointer to the end of the string
 *        in the previous argument.
 * Returns: nothing.
 */
traverse(pl,sofar,endcur)
struct path *pl;
char *sofar,*endcur;
{
 int fd;
 struct direct dir_entry;
 struct direct *dirbuf = &dir_entry;
 struct stat statbuf;
 if (pl == NULL)
 {
  *--endcur = '\0';                    /* end string, overwrite trailing / */
  addresult(sofar);
  return;
 }
 if (!iswild(pl -> npart))
 {
  strcpy(endcur,pl -> npart);
  endcur += (int)strlen(pl -> npart);
  *endcur = '\0';                       /* end current string */
  if (stat(sofar,&statbuf) == 0)        /* if current piece exists */
  {
      *endcur++ = DIRSEP;
      *endcur = '\0';
      traverse(pl -> fwd,sofar,endcur);
  }
  return;
 }
/* cont'd... */

/*...traverse, cont'd */
 
/* segment contains wildcards, have to search directory */
 *endcur = '\0';                                /* end current string */
 if (stat(sofar,&statbuf) == -1) return;        /* doesn't exist, forget it */
 if ((statbuf.st_mode & S_IFDIR) == 0) return;  /* not a directory, skip */
 
 if ((fd = open(sofar,O_RDONLY)) < 0) return;   /* can't open, forget it */
 while ( read(fd,dirbuf,sizeof dir_entry) )
{
  strncpy(nambuf,dirbuf->d_name,MAXNAMLEN); /* Get a null terminated copy!!! */

  nambuf[MAXNAMLEN] = '\0';
  if (dirbuf->d_ino != 0 && match(pl -> npart,nambuf)) {
    char *eos;
    strcpy(endcur,nambuf);
    eos = endcur + strlen(nambuf);
    *eos = '/';                    /* end this segment */
    traverse(pl -> fwd,sofar,eos+1);
  }
}
 close(fd);
}

/*  A D D R E S U L T  --  adds result string to result array  */
/*
 * addresult:
 *  Adds a result string to the result array.  Increments the number
 *  of matches found, copies the found string into our string
 *  buffer, and puts a pointer to the buffer into the caller's result
 *  array.  Our free buffer pointer is updated.  If there is no
 *  more room in the caller's array, the number of matches is set to -1.
 * Input: a result string.
 * Returns: nothing.
 */
 
addresult(str)
char *str;
{
 int l;
 if (strncmp(str,"./",2) == 0) str += 2;
 if (--remlen < 0) {
  numfnd = -1;
  return;
 }
 l = strlen(str) + 1;                   /* size this will take up */
 if ((freeptr + l) > &sspace[SSPACE]) {
    numfnd = -1;                        /* do not record if not enough space */
    return;
  }
 strcpy(freeptr,str);
 *resptr++ = freeptr;
 freeptr += l;
 numfnd++;
}

/*  Z S Y S C M D  --  Supposed to execute a "system" command.  */
/*
 Code copied verbatim from zshcmd for now
*/
int
zsyscmd(s) char *s; {
 
    if (priv_chk()) return(1);
    if (*s == '\0')                     /* Interactive shell requested? */

#ifdef mvux
        system("/bin/sh ");
#else
        system("x :cli prefix Kermit_Baby:");
#endif /* mvux */

    else                                /* Otherwise, */
        system(s);                      /* Best for aos/vs?? */
    return (0);                         /* Assume no errors */
}


/*  Z S H C M D  --  Start a CLI */
int
zshcmd(s) char *s; {                    /* ENH - new function */

    if (priv_chk()) return(1);
    if (*s == '\0')                     /* Interactive shell requested? */

#ifdef mvux
        system("/bin/sh ");
#else
        system("x :cli prefix Kermit_CLI:");
#endif /* mvux */

    else                                /* Otherwise, */
        system(s);                      /* Best for aos/vs?? */
    return (0);                         /* Assume no errors */
}
 
/*  I S W I L D  --  Check if filespec is "wild  */
/*
  Returns 0 if it is a single file, 1 if it contains wildcard characters.
  Note:  must match the algorithm used by match(), hence no [a-z], etc
*/  
int
iswild(filespec)
char *filespec;
{
char c; int x; char *p;
    if (wildxpand) {
        if ((x = zxpand(filespec)) > 1) return(1);
        p = malloc(MAXNAMLEN + 20);
        znext(p);
        x = (strcmp(filespec,p) != 0);
        free(p);
        return(x);
    } else {
        while ((c = *filespec++) != '\0')
          if (c == '*' || c == '?') return(1);
        return(0);
    }
}
/*  M A T C H  --  see if pattern matches string  */
/*
 * match:
 *  pattern matcher.  Takes a string and a pattern possibly containing
 *  the wildcard characters '*' and '?'.  Returns true if the pattern
 *  matches the string, false otherwise.
 * by: Jeff Damens, CUCCA

 *
 * Input: a string and a wildcard pattern.
 * Returns: 1 if match, 0 if no match.
 */
 
match(pattern,string) char *pattern,*string; {
    char *psave,*ssave;                 /* back up pointers for failure */
    psave = ssave = NULL;
    while (1) {
        for (; *pattern == *string; pattern++,string++)  /* skip first */
            if (*string == '\0') return(1);     /* end of strings, succeed */
        if (*string != '\0' && *pattern == '?') {
            pattern++;                  /* '?', let it match */
            string++;
        } else if (*pattern == '*') {   /* '*' ... */
            psave = ++pattern;          /* remember where we saw it */
            ssave = string;             /* let it match 0 chars */
        } else if (ssave != NULL && *ssave != '\0') {   /* if not at end  */
                                        /* ...have seen a star */
            string = ++ssave;           /* skip 1 char from string */
            pattern = psave;            /* and back up pattern */
        } else return(0);               /* otherwise just fail */
    }
}

/* C H E C K _ K E R M I T _ D A T E  --  see if Kermit date is valid */
/*
 This function is new in the VS version of Kermit only.  It replaces code
 that was previously in-line in zstime() that checks the validity of the
 date found in an argument passed in, and if it's valid, converts it to a
 scalar, stored in tm, which is returned.  It takes a second argument, which
 is the name of the calling function.  This is there so that entries to 
 log will report the accurate Kermit function.  It returns the value of
 tm if the date is valid and a -1 if not.                       --ENH
*/
long
validate (yy,caller) struct zattr *yy; char *caller; {
    int r = -1;                         /* return code */
    long tm, days;
    int i, n, isleapyear;
                   /*       J  F  M  A   M   J   J   A   S   O   N   D   */
                   /*      31 28 31 30  31  30  31  31  30  31  30  31   */
    static
    int monthdays [13] = {  0,0,31,59,90,120,151,181,212,243,273,304,334 };
    char s[5];
    struct stat sb;

    debug(F111,caller," calling validate()",0);
    if ((yy->date.len == 0)
        || (yy->date.len != 17)
        || (yy->date.val[8] != ' ')
        || (yy->date.val[11] != ':')
        || (yy->date.val[14] != ':') ) {
        debug(F111,"Bad creation date ",yy->date.val,yy->date.len);
        return(-1);
    }
    debug(F111,"Date check 1",yy->date.val,yy->date.len);
    for(i = 0; i < 8; i++) {
        if (!isdigit(yy->date.val[i])) {
            debug(F111,"Bad creation date ",yy->date.val,yy->date.len);
            return(-1);
        }
    }
    debug(F111,"Date check 2",yy->date.val,yy->date.len);
    i++;
 
    for (; i < 16; i += 3) {
        if ((!isdigit(yy->date.val[i])) || (!isdigit(yy->date.val[i + 1]))) {
            debug(F111,"Bad creation date ",yy->date.val,yy->date.len);
            return(-1);
        }
    }
    debug(F111,"Date check 3",yy->date.val,yy->date.len);
    debug(F100,"So far so good","",0);
 
    s[4] = '\0';
    for (i = 0; i < 4; i++)     /* Fix the year */
      s[i] = yy->date.val[i];
 
    debug(F110,"year",s,0);
 
    n = atoi(s);
 
    debug(F111,"year",s,n);
 
/* Previous year's leap days. This won't work after year 2100, */
/* I don't care about that! */

    isleapyear = (( n % 4 == 0 && n % 100 !=0) || n % 400 == 0);
    days = (long) (n - 1970) * 365;
    days += (n - 1968 - 1) / 4 - (n - 1900 - 1) / 100 + (n - 1600 - 1) / 400;
 
    s[2] = '\0';
 
    for (i = 4 ; i < 16; i += 2) {
        s[0] = yy->date.val[i];
        s[1] = yy->date.val[i + 1];
        n = atoi(s);
        debug(F110,"zstime entering switch",s,0);
        switch (i) {
          case 4:                       /* MM: month */
            if ((n < 1 ) || ( n > 12)) {
                debug(F111,"Bad creation date ",yy->date.val,yy->date.len);
                return(-1);
            }
            days += monthdays [n];
            if (isleapyear && n > 2)
              ++days;
            continue;
 
          case 6:                       /* DD: day */
            if ((n < 1 ) || ( n > 31)) {
                debug(F111,"Bad creation date ",yy->date.val,yy->date.len);
                return(-1);
            }
            tm = (days + n - 1) * 24L * 60L * 60L;
            i++;                        /* Skip the space */
            continue;
 
          case 9:                       /* hh: hour */
            if ((n < 0 ) || ( n > 23)) {
                debug(F111,"Bad creation date ",yy->date.val,yy->date.len);
                return(-1);
            }
            tm += n * 60L * 60L;
            i++;                        /* Skip the colon */
            continue;
 
          case 12:                      /* mm: minute */
            if ((n < 0 ) || ( n > 59)) {
                debug(F111,"Bad creation date ",yy->date.val,yy->date.len);
                return(-1);
            }
            tm += n * 60L;
            i++;                        /* Skip the colon */
            continue;
 
          case 15:                      /* ss: second */
            if ((n < 0 ) || ( n > 59)) {
                debug(F111,"Bad creation date ",yy->date.val,yy->date.len);
                return(-1);
            }
            tm += n;
        }
 
        if (localtime(&tm)->tm_isdst)
          tm -= 60L * 60L;              /* Adjust for daylight savings time */
    }
 
    debug(F111,"Attribute creation date ok ",yy->date.val,yy->date.len);
 
    return(tm);
} 

/*  F I L E _ E X I S T S  -- returns zero if filename already exists  */
/*
 Takes a pointer to a filename as an argument and determines whether that
 file exists.  Returns 1 if it does, 0 if not, -1 on other error.
*/
int
file_exists(name) char *name; {

    P_FSTAT fs_pak;
    int ac0,ac1,ac2,error;

    ac0 = name;
    ac1 = 0;                                    /* resolve links */
    ac2 = (P_FSTAT *) &fs_pak;
    error = sys($FSTAT,&ac0,&ac1,&ac2);
    if (error == ERFDE)                         /* file doesn't exist */
        return (0);
    if (error == 0)                             /* file does exist */
        return (1);
    else                                        /* some other error */
        return (-1);
}


/*  S E T _ C R E A T I O N _ D A T E  --  set creation date on VS file  */
/*
 Takes a pointer to a filename and a timestamp in Kermit format and sets
 the creation date/time of the file -- returns 0 on success, -1 on failure
*/                                      /* Contributed by:               */
int                                     /* Larry McCoskery, Data General */
set_creation_date( filename, date_time )
    char *  filename;
    char *  date_time;
{
    int     ac0,ac1,ac2,error;
    int     vs_datetime;                   /* VS scalar */

    P_CREATE    crepak;
    P_CTIM      timepak;
    void    convert_kdate_to_aosvs();

    /*
     *  First, convert the silly date.  EH assures me that it's ok
     */

    convert_kdate_to_aosvs ( date_time, &vs_datetime);

    /*
     *  Build the time packet. Set TCR, TLM, and TLA to specified date
     */
 
    timepak.tcth.long_time =
    timepak.tath.long_time =
    timepak.tmth.long_time = vs_datetime;
 
    /*
     *  Build the create packet
     */

    crepak.cftyp_format =   0;     /* record format */
    crepak.cftyp_entry  =   $FUDF; /* entry type */
    crepak.ccps         =   0;     /* file control parameters */
    crepak.ctim         =   &timepak;   /* address of time block */
    crepak.cacp         =   -1;           /* address of ACL */
    crepak.cdeh         =   0;     /* reserved */
    crepak.cdel         =   -1;    /* file element size */
    crepak.cmil         =   -1;    /* maximum index levels */
    crepak.cmrs         =   0;     /* reserved */

    /*
     *  Create it
     */

    ac0 = (char *) filename;
    ac1 = 0;        /* reserved */
    ac2 = (P_CREATE *) &crepak;

    error = sys( $CREATE, &ac0, &ac1, &ac2 );

    if( error != NULL ) {
        debug(F111, "can't create file - VS error",filename,error);
        error = -1;                         /* unix style "hide the error" */
    }
    else debug(F111,"file created",filename,0);
    return  error;
}

void
convert_kdate_to_aosvs( kermit_date, vs_scalar )
    char *  kermit_date;
    int  *  vs_scalar;
{
    int     month, day, year, hour, minute, second;
    int     date, time;
    char    c;
    short * sp = vs_scalar;

    sscanf( kermit_date,"%4d%2d%2d%c%2d%c%2d%c%2d",
        &year,&month,&day,&c,&hour,&c,&minute,&c,&second );

    convert_date_to_dg( day, month, year, &date );
    convert_time_to_dg( hour, minute, second, &time );

    sp[0] = (short) date;
    sp[1] = (short) time;
}

void
convert_aosvs_to_kdate( string, aosvs_scalar )
    char *  string;
    int     aosvs_scalar;
{
    int     day,month,year,hours,minutes,seconds;
    short * sp = &aosvs_scalar;

    dg_date( (int) sp[0], &day, &month, &year );
    dg_time( (int) sp[1], &hours, &minutes, &seconds );

    sprintf( string, "%4d%02d%02d %02d:%02d:%02d",
        year,month,day,hours,minutes,seconds );
}

int
convert_date_to_dg( day, month, year, scalar )
    int     day,month,year,*scalar;
{
    int     ac0,ac1,ac2,error;
 
    ac0 = day;
    ac1 = month;
    ac2 = year - 1900;
 
    error = sys( $FDAY, &ac0, &ac1, &ac2 );
 
    *scalar = ac0;
 
    return error;
}
 
int
convert_time_to_dg( hours, mins, secs, scalar )
    int     hours, mins, secs, *scalar;
{
    int     ac0,ac1,ac2,error; 

    ac0 = secs;
    ac1 = mins;
    ac2 = hours;
 
    error = sys( $FTOD, &ac0, &ac1, &ac2 );
 
    *scalar = ac0;
 
    return  error;
}

int
dg_date( scalar, day, month, year )
    int     scalar, *day, *month, *year;
{
    int     ac0,ac1,ac2,error;

    ac0 = scalar;
    ac1 = ac2 = 0;
    error = sys( $CDAY, &ac0, &ac1, &ac2 );

    *day = ac0;
    *month = ac1;
    *year = ac2 + 1900;

    return error;
}

int
dg_time( scalar, hours, mins, secs )
    int     scalar, *hours, *mins, *secs;
{
    int     ac0,ac1,ac2,error;

    ac0 = scalar;
    ac1 = ac2 = 0;
    error = sys( $CTOD, &ac0, &ac1, &ac2 );

    *hours = ac2;
    *mins  = ac1;
    *secs  = ac0;

    return error;
}

fs_get_modification_date( filename, date )
    char *  filename;
    int  *  date;
{
    P_FSTAT fs_pak;
    int ac0,ac1,ac2,error;

    ac0 = filename;
    ac1 = 0;                                    /* resolve links */
    ac2 = (P_FSTAT *) &fs_pak;
    error = sys($FSTAT,&ac0,&ac1,&ac2);

    *date = fs_pak.stmh.long_time;

    return  error;
}

int
get_dir(dirbuf)
char *dirbuf;
{
	int ac0,ac1,ac2,error;

	ac0 = (char *) "=";
	ac1 = (char *) dirbuf;
	ac2 = MAXPATH+1;

	error = sys($GNAME,&ac0,&ac1,&ac2);
	if (error == NULL)
	    *(dirbuf+ac2) = '\000';		/* null terminator */

	return(error);
}	

