char *ckzv = "OS-9 file support, 7.0.018, 1 Jan 2000";
char *ckzsys = " OS-9/68000";

/* c k 9 F I O  --  Kermit file system support for OS-9/68k systems */

/*
 Author: Peter Scholz,
 Ruhr University Bochum, Department for Analytical Chemistry,
 Federal Republic of Germany,   February 1987

 04/30/87 Robert Larson         Cleanup, merge with standard C-kermit
 04/07/89 Robert Larson         Update for ckermit 4f(77)
 07/16/89 Robert Larson         4f(85)
 Edition: 5A(01)
 06/21/91 Chris  Hemsing        general adaption to 5A(171)
                                miscellaneous bug fixes, utilization of
                                initial file size.
 07/25/91 Chris  Hemsing        minor bug fixes, changes for gnu (ansi) C
 01/14/92 Chris  Hemsing        uct/localtime  bug fix
 05/26/92 Chris  Hemsing        zltor and zrtol bug fix
 Edition: 5A(05)
 06/30/92 Chris  Hemsing        pipeopen stderr bug fix,dir shows filesize
 Edition: 5A(06)
 07/09/92 Chris  Hemsing        removed all chaining which would save a
                                process but f$chain does not look in PATH
 Edition: 5A(07)
 07/10/92 Chris  Hemsing        remove whole process tree of a lower fork
 Edition: 5A(08)
 12/01/92 Chris  Hemsing        zgtdir bugs repaired: upmost dir not closed
                                closedir of non-open dir could be called
 Edition: 5A(09)
 04/10/94 ?                     adapted password tag of struct zattr
 Edition: 5A(10)
 01/05/95 Ulli Schlueter        changed rename(), added isdir(), zmkdir() and
                                zfseek()
 Edition: 5A(11)
 01/13/95 Ulli Schlueter        changed zrtol() to enable directories in as-
                                names, corrected MAXNAMLEN, adapted znewn() to
                                the fact that it is called with full path
                                names, fixed bug in tilde_expand() (buffer to
                                small)
 Edition: 5A(12)
 03/13/95 Ulli Schlueter        some little bug fixies and cleanups
 Edition: 5A(13)
 03/20/95 Ulli Schlueter        kill_tree() now takes care of parent pid
 Edition: 5A(14)
 04/19/95 Ulli Schlueter        Rewrote zgtdir(), made some large buffers
                                dynamic.
 Edition: 6.1.015
 4 Dec 97 fdc               	Updated chkfn() to full capacity.
 15 Apr 99 Martin Whitaker	Added fix for name changes in dir.h.
 25 Apr 99 Martin Whitaker	Rewrote zsyscmd() and zshcmd().
 01 May 99 Martin Whitaker	Added nzxpand() and zxrewind(). Modified
				fgen() and traverse() to support nzxpand().
 Edition: 7.0.016
 31 May 99 Martin Whitaker	Fixed zchin to work when (n == ZIFILE).
 Edition: 7.0.017
 Edition: 7.0.018
 January 2000 Steve Rance, Frank da Cruz: adapt to Ultra C.

  Adapted from UNIX C-Kermit.
  Author: Frank da Cruz <fdc@columbia.edu>.

  Copyright (C) 1985, 2000,
    Trustees of Columbia University in the City of New York.
    All rights reserved.  See the C-Kermit COPYING.TXT file or the
    copyright text in the ckcmai.c module for disclaimer and permissions.
*/
/* In OS-9 V2.3, dir.h uses the field name _addr in the structure direct,
   whereas this file expects the field name d_addr. On the assumption that
   this was a change made in a later version of OS-9, I have fixed this up
   by the following macro definition, rather than changing the name in
   this file - MTW 15-Apr-99 */
#define	_addr	d_addr

/* Includes */

#include "ckcsym.h"
#include "ckcdeb.h"             /* Typedefs, debug formats, etc */
#include "ckcker.h"             /* Kermit definitions */
#include "ckcasc.h"
#include <errno.h>

#ifndef EOS_FULL
#ifdef E_FULL
#define EOS_FULL E_FULL
#endif /* E_FULL */
#endif /* EOS_FULL */

#include <dir.h>                /* Directory structure */
#include <direct.h>
#include <modes.h>
#include <procid.h>
#include <signal.h>
#include <strings.h>
#include <time.h>

/* special case since fdopen is not an ANSI function (see stdio.h) */
extern   FILE *fdopen(int, char *);

/* and this is not ANSI bust used here */
#define fileno(p)       ((p)->_fd)

#define ISDIRSEP(c) ((c)=='/')

#define	ISHIDDEN(name) \
((name[0] == '.') && (name[1] != '\0') && (name[1] != '.'))

/* Definitions of some system commands */

char *DIRCMD = "dir -e ";	/* For directory listing with filename*/
char *DIRCM2 = "dir -e ";	/* For directory listing without filename*/
char *DELCMD = "del ";		/* For file deletion */
char *TYPCMD = "list ";		/* For typing a file */
char *PWDCMD = "pd ";		/* For saying where I am */

char *SPACMD = "free ";
char *SPACM2 = "free ";         /* For space in specified directory */

char *WHOCMD = "procs -e ";	/* We have no who yet */

/*
  Functions (n is one of the predefined file numbers from ckermi.h):

   zopeni(n,name)   -- Opens an existing file for input.
   zopeno(n,name,attr,fcb) -- Opens a new file for output.
   zclose(n)        -- Closes a file.
   zchin(n,&c)      -- Gets the next character from an input file.
   zsinl(n,&s,x)    -- Read a line from file n, max len x, into address s.
   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 to create new file, name.
   znewn(name,s)    -- Make a new unique file name based on the given name.
   zdelet(name)     -- Delete the named file.
   nzxpand(string,flags) -- Expands wildcard string into a list of files.
   zxrewind()       -- Rewind nzxpand list.
   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(struc 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.
   zmkdir(path)     -- Create the directory path if possible  
 */

/* Some systems define these in include files, others don't... */

#define MAXWLD 500

/* MAXNAMLEN in <dir.h> is incorrect */
#ifdef MAXNAMLEN
#undef MAXNAMLEN
#endif /* MAXNAMLEN */
#define MAXNAMLEN 28

/* Declarations */
#define MAXPATH 256                     /* not really an OS-9 restriction */

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

/* (PWP) external def. of things used in buffered file input and output */
#ifdef DYNAMIC
extern CHAR *zinbuffer, *zoutbuffer;
#else
extern CHAR zinbuffer[], zoutbuffer[];
#endif /* DYNAMIC */
extern CHAR *zinptr, *zoutptr;
extern int zincnt, zoutcnt;

struct fildes cur_in_fd;                /* current input filedesc */
static int cur_in_size = -1;            /* current input file length */

static int pipe_pid;                    /* pid of child fork */
static int nxpand = 0;			/* Number of files in wild group */
static int fcount;                      /* Files remaining for znext() */
char *getenv(), *strcpy();              /* System functions */
/* VOID *malloc(); */
extern int errno;                       /* System error code */

char **mtchs,                           /* Matches found for filename */
     **mtchptr;                         /* Pointer to current match */

/*  Z K S E L F  --  Kill Self: log out own job, if possible.  */

zkself() {                              /* For "bye" */
procid pbuf;
  _get_process_desc(getpid(),sizeof(pbuf),&pbuf);
  if (kill((int)pbuf._pid,SIGKILL)==0)
    doexit(GOOD_EXIT,-1);/*kill parent process*/
  else
    doexit(errno,-1);
}

/*  Z G T D I R  --  Get current working directory. */

static char *
getcwd (pwdbuf, size)
     char *pwdbuf;
     unsigned size;
{
    DIR *dirp;
    long cur, tmp, here;
#ifdef CWDNU
    void *mem = NULL;
#endif /* CWDNU */
    char *cp;
    char *cd;
    struct direct *ent;
    char buffer[128];
#define MAXDEPTH 64
    char dots[MAXDEPTH+1];

#ifdef CWDNU
    if (size < MAXNAMLEN + 2) { /* '/' and '\0' */
        errno = E_ILLARG;
        return NULL;
    }
    if (pwdbuf == NULL) {
        if ((pwdbuf = malloc(size)) == NULL)
          return NULL;
        mem = pwdbuf;
    }
#endif /* CWDNU */
    cp = pwdbuf + size;
    *--cp = '\0';
    cd = dots + sizeof dots;
    *--cd = '\0';
    here = 0;
    do {
        if (cd <= dots) {       /* out of space */
            errno = EOS_FULL;
#ifdef CWDNU
            if (mem) free(mem);
#endif /* CWDNU */
            return NULL;
        }
        *--cd = '.';
        if ((dirp = opendir(cd)) == NULL) {
#ifdef CWDNU
            if (mem) free(mem);
#endif /* CWDNU */
            return NULL;
        }
        /* read '..' and '.' entries */
        if ((ent = readdir(dirp)) == NULL
            || (tmp = ent->d_addr, (ent = readdir(dirp)) == NULL)) {
            closedir(dirp);
#ifdef CWDNU
            if (mem) free(mem);
#endif /* CWDNU */
            return NULL;
        }
        cur = ent->d_addr;
        if (cur == tmp) cur = 0;
        if (here == 0) {
            here = cur;
            continue;           /* Break if in the root */
        }
        while ((ent = readdir(dirp)) != NULL)
        {
            if (ent->d_addr == here)
            {
                strcpy(buffer, ent->d_name);
                cp -= (tmp = strlen(buffer));
                if (cp <= pwdbuf) { /* out of space */
                    errno = EOS_FULL;
                    break;
                }
                memcpy(cp, buffer, tmp);
                *--cp = '/';
                here = cur;
                break;
            }
        }
        if (here != cur) {      /* read error, entry not found or no space */
            closedir(dirp);
#ifdef CWDNU
            if (mem) free(mem);
#endif /* CWDNU */
            return NULL;
        }
    } while (cur != 0 && (closedir(dirp), 1));
    if (_gs_devn(dirp->dd_fd, buffer) < 0) { /* get device name */
        closedir(dirp);
#ifdef CWDNU
        if (mem) free(mem);
#endif /* CWDNU */
        return NULL;
    }
    closedir(dirp);
    cp -= (tmp = strlen(buffer));
    if (cp <= pwdbuf) {
        errno = EOS_FULL;
#ifdef CWDNU
        if (mem) free(mem);
#endif /* CWDNU */
        return NULL;
    }
    memcpy(cp, buffer, tmp);
    *--cp = '/';
    memcpy(pwdbuf, cp, pwdbuf + size - cp);
    return pwdbuf;
}

char *zgtdir() {
    static char *cwd;
#define CWDBL (MAXPATH + 1)

    if (cwd == NULL && (cwd = malloc(CWDBL)) == NULL) return "";
    cwd[0] = '\0';
    getcwd(cwd, CWDBL);
    return cwd;
}


/*  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);
    zincnt = 0;                         /* Reset input buffer */
    if (n == ZSYSFN) {                  /* Input from a system function? */
/*** Note, this function should not be called with ZSYSFN ***/
/*** Always call zxcmd() directly, and give it the real file number ***/
/*** you want to use.  ***/
        debug(F110,"zopeni called with ZSYSFN, failing!",name,0);
                return(0);                      /* fail. */
    }
    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)
/* zopeno */  int n; char *name; struct zattr *zz; struct filinfo *fcb; {

/* As of Version 5A, the attribute structure and the file information */
/* structure are included in the arglist. */

    char *p;
    int pn;
    debug(F111,"zopeno",name,n);
    if (chkfn(n) != 0) return(0);
    zoutcnt = 0;
    zoutptr = zoutbuffer;
    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 == ZCTERM) || (n == ZSTDIO))
    {   /* Terminal or standard output */
          fp[ZOFILE] = stdout;
          debug(F101," fp[]=stdout", "", (int) fp[n]);
          return(1);
    }

/* A real file.  Open it in desired mode (create or append). */
    p = "w";                            /* Assume write/create mode */
    if (fcb) {                          /* If called with an FCB... */
        if (fcb->dsp == XYFZ_A)         /* Does it say Append? */
          p = "a";                      /* Yes. */
    }
    if (access(name,0) == 0) /* does file exist ? */
    {
      if ((fp[n] = fopen(name,p)) == NULL)      /* Simply open the file */
      {
        perror("zopeno can't open");
        return(0);
      }
      debug(F111, " exist:fp[n]",p, (int) fp[n]);
    }
    else /* file does not exist, we can open with initial size */
         /* this is very important for long files not to be too fragmented */
         /* so that after a long transmission OS-9 may finish saying that */
         /* the segment allocation table is full */
    {
      if ((zz) && ((zz->length > 0) || (zz->lengthk > 0)))
                                    /* length estimate available ? */
      {
        if ((pn = create(name,S_IREAD+S_IWRITE+S_ISIZE,
             S_IREAD+S_IWRITE,
             (zz->length > 0) ? zz->length : (zz->lengthk*1024L))) != -1)
        {
          debug(F111, " create with init. size:",p,
                (zz->length > 0) ? zz->length : (zz->lengthk*1024L));
          if ((fp[n] = fdopen(pn,"w")) == NULL) /* get filepntr for pn */
          {
            perror("zopeno can't open");
            return(0);
          }
          debug(F111, " create with init. size:fp[n]",p, (int) fp[n]);
        }
        else
        {
          perror("zopeno can't open");
          return(0);
        }
      }
      else /* no length available */
      {
        if ((fp[n] = fopen(name,p)) == NULL)    /* Simply open the file */
        {
          perror("zopeno can't open");
          return(0);
        }
        debug(F111, " create without init size fp[n]",p, (int) fp[n]);
      }
    }
    if((zz) && (zz->systemid.val[0] == 'U') && ( zz->systemid.val[1] == 'D'))
    {
      debug(F101, "zopeno:file originated from OS-9","",zz->lprotect.val[0]);
      if (zz->lprotect.len != 0)
      {
        if (_ss_attr(fp[n]->_fd,(zz->lprotect.val[0]&0x7f)|S_IWRITE) == -1)
        {
          debug(F101, "zopeno:can't set file attr","",errno);
          return(0);
        }
      }
    }
    if (n == ZDFILE) setbuf(fp[n],(char *)NULL); /* Debug file unbuffered */
    return(1);
}

/*  Z C L O S E  --  Close the given file.  */
/*  Returns 0 if arg out of range, 1 if successful, -1 if close failed.  */

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;                              /* Initialize return code */
    if (fp[ZSYSFN]) {                   /* If file is really pipe */
        x = zclosf(n);                  /* do it specially */
    } else {
        if ((fp[n] != stdout) && (fp[n] != stdin)) x = fclose(fp[n]);
            fp[n] = NULL;
    }
    if (x == EOF)                       /* if we got a close error */
                return (-1);
    else if (x2 < 0)            /* or an error flushing the last buffer */
                return (-1);            /* then return an error */
    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  */

zchin(n,c) int n; int *c; {
    int a;

    /* (PWP) Just in case this gets called when it shoudn't */
    if (n == ZIFILE)
      a = zminchar();
    else
      a = getc(fp[n]);
    if ((a == -1) || (a == EOF)) return(-1);
    *c = a & 0377;
    return(0);
}

/*  Z I N F I L L  --  Get a character from the input file.
 * (PWP) (re)fill the buffered input buffer with data.  All file input
 * should go through this routine, usually by calling the zminchar()
 * macro (in ckcker.h).
 */

zinfill() {
    zincnt = fread((char *)zinbuffer, sizeof (char), INBUFSIZE, fp[ZIFILE]);
    if (zincnt == 0) return (-1);       /* end of file */
    zinptr = zinbuffer;    /* set pointer 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.  */

zsout(n,s) int n; char *s; {
    if (chkfn(n) < 1) return(-1);
    fputs(s,fp[n]);
    return(0);
}

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

zsoutl(n,s) int n; char *s; {
    /* if (chkfn(n) < 1) return(-1); */
    fputs(s,fp[n]);
    fputs("\n",fp[n]);
    return(0);
}

/*  Z S O U T X  --  Write x characters to file, unbuffered.  */

zsoutx(n,s,x) int n, x; char *s; {
    /* if (chkfn(n) < 1) return(-1); */
    return(write(fileno(fp[n]),s,(unsigned int)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)  */

int
#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])); /* Check to make sure */
        }
        else return(0);   /* There was no error. */
    }
}

/* (PWP) buffered character output routine to speed up file IO */
zoutdump()
{
    if (zoutcnt == 0) return (0); /* nothing to output */
    if (zoutcnt < 0) return (-1); /* unexpected */

    zoutptr = zoutbuffer;      /* Reset buffer pointer in all cases */
    if (fwrite ((char *)zoutbuffer, 1, zoutcnt, fp[ZOFILE]))
    {
        fflush(fp[ZOFILE]);
        zoutcnt = 0;            /* reset output buffer */
        zoutptr = zoutbuffer;
        return(0);              /* things worked OK */
    }
    zoutcnt = 0;            /* reset output buffer */
    return(ferror(fp[ZOFILE])?-1:0); /* Check to make sure */
}

/*  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; {
    if (n < 0 || n >= ZNFILS) {
	if (n != ZDFILE) debug(F101,"chkfn out of range","",n);
	return(-1);
    } else {
	/* if (n != ZDFILE) debug(F101,"chkfn fp[n]","",fp[n]); */
	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.
*/
long
zchki(name) char *name; {
    int x;

    if (strcmp(name, "/nil") == 0) {
        memset(&cur_in_fd, 0, sizeof cur_in_fd);
        cur_in_fd.fd_att = S_IREAD|S_IWRITE;
        getime((struct sgtbuf *)cur_in_fd.fd_date);
        cur_in_size = -1;
        return 0;
    }
    if (access(name,0) < 0) {
        if(access(name,S_IFDIR)>=0) {
            debug(F111,"zchki skipping:",name,errno);
            return(-2);
        }
        debug(F111,"zchki can't access",name,errno);
        return(-1);
    }

    if ((x = open(name,S_IREAD)) < 0) {  /* Is the file accessible? */
        debug(F111," access failed:",name,x); /* No */
        return(-3);
    }
    debug(F110," access ok:",name,0);
    cur_in_size = _gs_size(x);                  /* remember size */
    _gs_gfd(x,&cur_in_fd,sizeof(cur_in_fd));    /* remember filedespr. */
    close(x);
    debug(F111," access ok:",name,cur_in_size);
    return( (cur_in_size > -1) ? cur_in_size : 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;

    x = strlen(name);
    debug(F101," length","",x);
    if (x == 0) return(-1);             /* If no filename, fail. */
    if (strcmp(name, "/nil") == 0) return 0;
    if(isdir(name)) return -1;          /* it's a directory */
    for (s = name, i = x; i > 0; i--) { /* Strip filename from right. */
        if (ISDIRSEP(s[i-1])) break;
    }
    debug(F101," i","",i);
    if (_prsnam(s + i) != strlen(s + i)) {
        debug(F111," _prsnam failed:",s,errno);
        return -1;
    }
/* We don't want to write in our arg, so make a copy */
    if ((s = malloc(x + 1 + 1)) == NULL) return -1;
    strcpy(s, name);
/* Now we use "path/." if path given, or "." if no path given. */
    s[i++] = '.';                       /* Append "." to path. */
    s[i] = '\0';
    x = access(s,S_IFDIR|S_IWRITE);     /* Check access of path. */
    if (x < 0) {
        debug(F111,"zchko access failed:",s,errno);
    } else {
        debug(F111,"zchko access ok:",s,x);
    }
    free(s);
    return x < 0 ? -1 : 0;
}

/*  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.
*/
zchkspa(f,n) char *f; long n; {         /* Just dummy for now. */
    return(1);                          /* Always say OK. */
}


/*  Z D E L E T  --  Delete the named file.  */

zdelet(name) char *name; {
    return(unlink(name));
}


/*  Z R T O L  --  Convert remote filename into local form  */

/*  For OS9, this means changing uppercase letters to lowercase
	and making shure only a-z,A-Z,0-9,$,_,. are used
	keep the / for dir name (as-names)
*/
VOID
zrtol(name,name2) char *name, *name2; {
    char *p; int flag = 0, n = 0;
    int maxnam = MAXNAMLEN;
    if (!name || !name2) return;
    debug(F101,"zrtol original name","",name);
    for (p = name2; *name != '\0' && n < maxnam; name++) {
        if (*name > ' ') flag = 1;      /* Strip leading blanks and controls */
        if (flag == 0 && *name < '!') continue;
        *p = isupper(*name) ? tolower(*name) : *name;
        n++;
        if (ISDIRSEP(*p++)) n = 0;
    }
    *p-- = '\0';                        /* Terminate */
    while (*p < '!' && p > name2)       /* Strip trailing blanks & controls */
      *p-- = '\0';
    if (*name2 == '\0') strcpy(name2,"noname");
    for (p = name2; *p != '\0'; p++) {
        if (!isalnum(*p) && *p != '$' && *p != '.' && *p != '_'
          && !ISDIRSEP(*p))
          *p = '_';
    }
    debug(F110,"zrtol new name",name2,0);
}


/*  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[MAXPATH+1]; /* buffer for use by zstrip and zltor */

VOID
zstrip(name,name2) char *name, **name2; {
    char *cp, *pp;
    debug(F110,"zstrip before",name,0);
    pp = work;
#ifdef DTILDE
    if (*name == '~') name++;
#endif
    for (cp = name; *cp != '\0'; cp++) {
        if (*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 *cp, *pp;
    int dc = 0;

    debug(F110,"zltor",name,0);
    pp = work;
    for (cp = name; *cp != '\0'; cp++) { /* strip path name */
        if (*cp == '/') {
            dc = 0;
            pp = work;
        }
        else if (islower(*cp)) *pp++ = toupper(*cp); /* Uppercase letters */
        else if (*cp == '$') *pp++ = 'X';	/* Change '$' 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 == NULL || *dirnam == '\0') hd = zhome();
    else hd = dirnam;
#ifdef DTILDE
    hd = tilde_expand(dirnam);          /* Attempt to expand tilde */
    if (*hd == '\0') hd = dirnam;       /* in directory name. */
#endif /* DTILDE */
    return((chdir(hd) == 0) ? 1 : 0);
}


/*  Z H O M E  --  Return pointer to user's home directory  */

char *
zhome() {
    char *home = getenv("HOME");
    return home ? home : ".";
}

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

zxcmd(filnum,comand) int filnum;char *comand; {
FILE *pipeopen(),*filedesc;

    if (chkfn(filnum) < 0) return(-1); /* Need a valid kermit file number */
    if (filnum == ZSTDIO || filnum == ZCTERM) return(0);

    if (filnum == ZIFILE || filnum == ZRFILE) /* kermit wants to read */
    {
      debug(F111," zxcmd read:",comand,filnum);
      if ((filedesc = pipeopen(comand,"r")) != (FILE *)NULL)
      {
        fp[filnum] = filedesc;
        fp[ZSYSFN] = fp[filnum];    /* Remember. */
        zincnt = 0;
        zinptr = zinbuffer;
      }
      else
        return(0);
    }
    else
    {
      debug(F111," zxcmd write:",comand,filnum);
      if ((filedesc = pipeopen(comand,"w")) != (FILE *)NULL)
      {
        fp[filnum] = filedesc;
        fp[ZSYSFN] = fp[filnum];    /* Remember. */
        zoutcnt = 0;
        zoutptr = zoutbuffer;
      }
      else
        return(0);
    }
    return(1); /* return success if we ever get here */
}

/* KILL_TREE recursively kill all processes of a forked tree */
static void
#ifdef CK_ANSIC
kill_tree(int root, int ppid)
#else
kill_tree(root, ppid) int root; int ppid;
#endif /* CK_ANSIC */
{
  struct {
    unsigned short
      _id,      /* process id */
      _pid,     /* parent's id */
      _sid,     /* sibling's id */
      _cid;     /* child's id */
  } prdesc;     /* NOT whole process descriptor! too much stack needed */
  int child;

  if(_get_process_desc(root, sizeof(prdesc), &prdesc) == -1) return;
  if (prdesc._pid != ppid) return;
  /* kill child's tree */
  if ((child = prdesc._cid) != 0) kill_tree(child, root);
  /* kill siblings tree */
  if ((child = prdesc._sid) != 0) kill_tree(child, ppid);
  kill(root,SIGKILL);
}

/* Z C L O S F - kill the child(action may have aborted and close the pipe.*/

zclosf(filnum) int filnum;{
    int wstat;
    debug(F101," zclosf filnum:","",filnum);
    fclose(fp[filnum]);
    fp[filnum] = fp[ZSYSFN] = NULL;
    if (pipe_pid == 0) return(-1);
    /* kill all lower processes in case things have been aborted */
    kill_tree(pipe_pid, getpid());
    debug(F101," zclosf killed:","",pipe_pid);
    while ((wstat = wait((unsigned int *)0)) != pipe_pid && wstat != -1) ;
    pipe_pid = 0;
    return(1);
}

/*  N Z X P A N D  --  Expand a file list, with options.  */
/*
  Call with:
   fnarg = pointer to filename or pattern.
   flags = option bits:

     flags & ZX_FILONLY   Match regular files
     flags & ZX_DIRONLY   Match directories
     flags & ZX_RECURSE   Descend through directory tree
     flags & ZX_MATCHDOT  Match "dot files"
     flags & ZX_NOBACKUP  Don't match "backup files"

   Returns the number of files that match s, with data structures set up
   so that first file (if any) will be returned by the next znext() call.
*/
static int xdironly = 0;
static int xfilonly = 0;
static int xmatchdot = 0;
static int xrecursive = 0;

int
nzxpand(fnarg, flags) char *fnarg; int flags; {
    char fnbuf[CKMAXPATH+8];
    char *fn;
    char *p;
#ifdef DTILDE				/* Built with tilde-expansion? */
    char *tnam;
#endif /* DTILDE */
    int x;

    debug(F111,"nzxpand",fnarg,flags);
    xdironly   = (flags & ZX_DIRONLY);
    xfilonly   = (flags & ZX_FILONLY);
    xmatchdot  = (flags & ZX_MATCHDOT);
    xrecursive = (flags & ZX_RECURSE);

    debug(F101,"nzxpand xdironly","",xdironly);
    debug(F101,"nzxpand xfilonly","",xfilonly);
    debug(F101,"nzxpand xmatchdot","",xmatchdot);
    debug(F101,"nzxpand xrecursive","",xrecursive);

    if (!fnarg)				/* If no argument provided */
      return(0);			/* Return zero files found */

#ifdef DTILDE				/* Built with tilde-expansion? */
    if (*fnarg == '~') {		/* Starts with tilde? */
	tnam = tilde_expand(fnarg);	/* Try to expand it */
	strncpy(fnbuf,tnam,CKMAXPATH);
    } else
#endif /* DTILDE */
        strncpy(fnbuf,fnarg,CKMAXPATH);
  
    fn = fnbuf;				/* Point to what we'll work with */

    debug(F110,"nzxpand fn 1",fn,0);

    if (!*fn)				/* But make sure something is there */
      return(0);

    p = fn + (int)strlen(fn) - 1;
    if (*p == '/') {			/* If last char = / it must be a dir */
	strcat(fn, "*");		/* so append '*' */
    } else if (p > fn) {		/* If ends in "/." */
	if (*(p-1) == '/' && *p == '.')	/* change '.' to '*' */
	  *p = '*';
    } else if (p == fn) {		/* If it's '.' alone */
	if (*p == '.')			/* change '.' to '*' */
	  *p = '*';
    }
    debug(F110,"nzxpand fn 2",fn,0);
    x = isdir(fn);			/* Is it a directory? */
    debug(F111,"nzxpand isdir 1",fn,x);
    if (x) {				/* If so, make it into a wildcard */
	if ((x = strlen(fn)) > 0) {
	    if (!ISDIRSEP(fn[x-1]))
	      fn[x++] = '/';
	    fn[x++] = '*';
	    fn[x] = '\0';
	}
    }
    debug(F110,"nzxpand fn 3",fn,0);

    if (mtchs == NULL) mtchs = (char **)malloc(MAXWLD * sizeof(*mtchs));

    /* Look up the file */
    nxpand = (mtchs == NULL) ? 0 : fgen(fn,mtchs,MAXWLD);

    mtchptr = mtchs;			/* Save pointer for next */
    fcount = nxpand;

    debug(F101,"nzxpand fcount","",fcount);

    return(fcount);
}

/*  Z X R E W I N D  --  Rewinds the nzxpand() list */

int
zxrewind() {
    if (!mtchs) return(-1);
    fcount = nxpand;
    mtchptr = mtchs;
    return(fcount);
}

/*  Z N E X T  --  Get name of next file from list created by nzxpand(). */
/*
 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) strcpy(fn,*mtchptr++); else *fn = '\0';
    debug(F111,"znext",fn,fcount);
    return(fcount--);
}

/*  Z N E W N  --  Make a new name for the given file  */
VOID
znewn(fn,s) char *fn, **s; {
#define ZNEWNBL MAXPATH                 /* Name buffer length */
#define ZNEWNMD 4                       /* Max digits for version number */
/* OS-9 names are short, so take a short version number extension */
#define ZNEWNVC 1                       /* Number of additional vers chars */
    static char buf[ZNEWNBL+1];
    char *bp, *xp;
    int len = 0, n = 0, d = 0, t, i, power = 1;

    int max;

    bp = buf;
    xp = bp;
    while (*fn) {                       /* Copy old name into buf */
        *bp++ = *fn++;
        if (ISDIRSEP(bp[-1])) xp = bp;  /* Remember latest filename */
        if (len++ > ZNEWNBL) break;     /* ...up to buffer length */
    }
    max = (xp - buf) + MAXNAMLEN;
    if (max > ZNEWNBL) max = ZNEWNBL;

    *s = NULL;
    for (i = 1; i < ZNEWNMD; i++) {     /* Version numbers up to 10**i - 1 */
        int j, k;
        power *= 10;                    /* Next power of 10 */
        j = max - len;                  /* Space left for version number */
        k = ZNEWNVC + i;                /* Space needed for it */
        if (j < k) {                    /* Make room if necessary */
            len -= k - j;               /* Adjust length of filename */
            bp = buf + len;             /* Point to new end */
        }
        *bp++ = '*';                    /* Put a star on the end */
        *bp-- = '\0';

        debug(F110, "znewn: about to expand", buf, 0);
        n = nzxpand(buf, 0);             /* Expand the resulting wild name */
                                        /* n is the number of matches */
        while (n-- > 0) {               /* Find any existing name_n files */
            xp = *mtchptr++;            /* Point at matching name */
            xp += len;                  /* Lock for _<n> at the end of it */
            if (*xp == '_') {           /* Has it a version number */
                t = atoi(xp+1);         /* Get it */
                if (t > d) d = t;       /* Get maximum d */
            }
        }
        if (d < power-1) {              /* Less than maximum possible? */
            sprintf(bp,"_%d",d+1);      /* Yes, make "name_<d+1>" */
            *s = buf;
            break;
        }
    }
    if (*s == NULL) {
        debug(F110, "znewn: too many names", buf, 0);
        sprintf(bp, "_xxxx");           /* Too many, use xxxx. */
        *s = buf;
    }
}

/*  Z S A T T R */
/*
 Fills in a Kermit file attribute structure for the file which is to be sent.
 Returns 0 on success with the structure filled in, or -1 on failure.
 If any string member is null, then it should be ignored.
 If any numeric member is -1, then it should be ignored.
*/
zsattr(xx) struct zattr *xx; {
    int k;
    static char protection[2];
    char *get_gmtime();

    k = cur_in_size % 1024L;                  /* File length in K */
    if (k != 0L) k = 1L;
    xx->lengthk = (cur_in_size / 1024L) + k;
/*  if ((cur_in_fd.fd_att & S_IEXEC) || (cur_in_fd.fd_att & S_IOEXEC))
    {
      xx->type.len = 1;
      xx->type.val = "B";
    }
    else
    {
      xx->type.len = 0;
      xx->type.val = "";
    }
*/  xx->type.len = 0;               /* better let the user decide */
    xx->type.val = "";
    xx->date.val = get_gmtime(&cur_in_fd); /* get UCT time, if possible */
    xx->date.len = strlen(xx->date.val);
    debug(F111,"zsattr date",xx->date.val,xx->date.len);
    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->password.len = 0;                 /* Area password */
    xx->password.val = "";
    xx->blksize = -1L;                  /* File blocksize */
    xx->xaccess.len = 0;		/* File access */
    xx->xaccess.val = "";
    xx->encoding.len = 0;               /* Transfer syntax */
    xx->encoding.val = 0;
    xx->disp.len = 0;                   /* Disposition upon arrival */
    xx->disp.val = "";

    protection[0] = cur_in_fd.fd_att & 0x7f;
    protection[1] = 0;
    xx->lprotect.val = protection;
    xx->lprotect.len = 1;
    debug(F101,"zsattr lprotect","",xx->lprotect.val[0]);

    xx->gprotect.len = 0;               /* Generic protection */
    xx->gprotect.val = "";
    xx->systemid.len = 2;               /* System ID */
    xx->systemid.val = "UD";
    xx->recfm.len = 0;                  /* Record format */
    xx->recfm.val = "";
    xx->sysparam.len = 0;               /* System-dependent parameters */
    xx->sysparam.val = "";
    debug(F101,"zsattr size","",cur_in_size);
    xx->length = cur_in_size;                 /* Length */
    return(0);
}

zmail(p,f) char *p; char *f; {
    /* Send file f as mail to address p; no mail on OS-9 */
    return(-1);
}

zprint(p,f) char *p; char *f; {
    /* Print file f with options p; no unique print device on OS-9 */
    return(-1);
}

/* 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+4]; /* name part of path segment */
              struct path *fwd;  /* forward ptr */
            };

#define SSPACE 2000   /* size of string-generating buffer */
static char *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","",(int)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);
}

/*
 * 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;
{
    VOID traverse();
    struct path *head;
    char scratch[100+MAXNAMLEN],*sptr;
    head = splitpath(pat);
    sptr = scratch;
    if (*pat != '/') {
        *sptr++ = '.';
    }
    *sptr = '\0';
    numfnd = 0;                            /* none found yet */
    if (sspace == NULL && (sspace = malloc(SSPACE)) == NULL) {
        fprintf(stderr,"fgen can't malloc string space\n");
        return(-1);
    }
    freeptr = sspace;   /* this is where matches are copied */
    resptr = resarry;   /* static copies of these so*/
    remlen = len;    /* recursive calls can alter them */
    traverse(head,scratch,sptr);  /* go walk the directory tree */
    while (head != NULL) {
        struct path *next = head->fwd;
        free((char *)head);
        head = next;
    }
    return(numfnd);   /* and return the number of matches */
}

/* traverse:
 *  Walks the directory tree looking for matches to its arguments.
 *  The algorithm is, briefly:
 *   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.
 *
 *   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.
 *
 *   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.
 */
VOID
traverse(pl,sofar,endcur)
struct path *pl;
register char *sofar,*endcur;
{
    DIR *fd, *opendir();
    VOID recurse(), addresult();
    struct direct *dirbuf;
    unsigned char attr;
    char *name;

    debug(F110,"traverse ",sofar,0);
    if (iswild(pl->npart)) {	/* segment contains wildcards */
        fd = opendir(sofar);
        if (fd == NULL) {
            debug(F111,"traverse can't open directory",sofar,errno);
            return;		/* can't open, forget it */
        }
        dirbuf = readdir(fd);	/* skip '..' entry */
        dirbuf = readdir(fd);	/* and '.' entry */
        *endcur++ = '/';
        name = endcur;
        while (dirbuf = readdir(fd)) {
            if (dirbuf->d_addr != 0) {
                /* Get a null terminated copy!!! */
                strncpy(name,dirbuf->d_name,MAXNAMLEN);
                name[MAXNAMLEN] = '\0';
                if (match(pl->npart,name)) {
                    endcur = name + strlen(name);
                    if ((_gs_gfdinf(fd->dd_fd,
				    dirbuf->d_addr,
				    &attr,
				    sizeof(attr)) != -1) && (attr & S_IFDIR)) {
                        /* it's a dir */
                        if (pl->fwd == NULL) {
                            if (!xfilonly) addresult(sofar);
                            if (xrecursive) recurse(sofar,endcur);
                        } else {
                            traverse(pl->fwd,sofar,endcur);
                        }
                    } else {
                        /* it's a file */
                        if (pl->fwd == NULL) {
                            if (!xdironly) addresult(sofar);
                        }
                    }
                }
            }
        }
        closedir(fd);
    } else {
        *endcur++ = '/';
        strcpy(endcur,pl->npart);
        endcur += strlen(endcur);
        if (access(sofar,S_IFDIR) == 0) {
            /* exists and is a dir */
            if (pl->fwd == NULL) {
                if (!xfilonly) addresult(sofar);
                if (xrecursive) recurse(sofar,endcur);
            } else {
                traverse(pl->fwd,sofar,endcur);
            }
        } else if (access(sofar,0) == 0) {
           /* exists and is a file */
            if (pl->fwd == NULL) {
                if (!xdironly) addresult(sofar);
            }
        }
    }
}

VOID
recurse(sofar,endcur)
register char *sofar,*endcur;
{
    DIR *fd, *opendir();
    VOID addresult();
    struct direct *dirbuf;
    unsigned char attr;
    char *name;

    debug(F110,"recurse ",sofar,0);
    fd = opendir(sofar);
    if (fd == NULL) {
        debug(F111,"recurse can't open directory",sofar,errno);
        return;
    }
    dirbuf = readdir(fd);	/* skip '..' entry */
    dirbuf = readdir(fd);	/* and '.' entry */
    *endcur++ = '/';
    name = endcur;
    while (dirbuf = readdir(fd)) {
        if (dirbuf->d_addr != 0) {
            /* Get a null terminated copy!!! */
            strncpy(name,dirbuf->d_name,MAXNAMLEN);
            name[MAXNAMLEN] = '\0';
            endcur = name + strlen(name);
            if ((_gs_gfdinf(fd->dd_fd,
			    dirbuf->d_addr,
			    &attr,
			    sizeof(attr)) != -1) && (attr & S_IFDIR)) {
                /* it's a dir */
                if (!xfilonly) addresult(sofar);
                recurse(sofar,endcur);
            } else {
                /* it's a file */
                if (!xdironly) addresult(sofar);
            }
        }
    }
    closedir(fd);
}

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

VOID
addresult(str)
register char *str;
{
    register 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++;
}

iswild(str)
register char *str;
{
    register char c;
    while ((c = *str++) != '\0')
        if (c == '*' || c == '?') return 1;
    return 0;
}

/*
 * match:
 *  pattern matcher.  Takes a name and a pattern possibly containing
 *  the wildcard characters '*' and '?'.  Returns true if the pattern
 *  matches the name, false otherwise. Hidden files are only matched
 *  if xmatchdot is true or if the pattern starts with '.'.
 * Input: a name and a wildcard pattern.
 * Returns: 1 if match, 0 if no match.
 */

match(pattern,name)
char *pattern,*name;
{
    if (ISHIDDEN(name) && !(xmatchdot || (pattern[0] == '.'))) {
        return 0;
    } else if ((pattern[0] == '*') && (pattern[1] == '\0')) {
        return 1;
    } else {
        return (_cmpnam(name,pattern,strlen(pattern)) == 0);
    }
}

/** Get file descriptor info from given sector **/
/* _gs_gfdinf(path, sector, buffer, count) */

#ifdef _UCC
_asm("
_gs_gfdinf:	movem.l	d0-d4/a0,-(a7)	* Save used registers
		os9	F$ID		* Get current user id
		move.l	d1,d4		* Save it for later
		moveq.l	#0,d1		* Load superuser id
		os9	F$SUser		* Change to superuser
		bcs.s	_gs_gfdinf10	* Exit if failed
		move.l	0(a7),d0	* Load path number
		move.l	4(a7),d3	* Load sector address
		move.l	28(a7),a0	* Load buffer pointer
		move.l	32(a7),d2	* Load buffer size
		move.w	#SS_FDInf,d1	* Load function code
		os9	I$GetStt	* Call getstat function
		bcs.s	_gs_gfdinf10	* If no error
		moveq.l	#0,d0		*  set return code to 0
		bra.s	_gs_gfdinf20	* else
_gs_gfdinf10	moveq.l	#-1,d0		*  set return code to 1
_gs_gfdinf20	move.l	d4,d1		* Load original user id
		os9	F$SUser		* Change to original user
		lea.l	4(a7),a7	* Discard saved copy of d0
		movem.l (a7)+,d1-d4/a0	* Restore other registers
		rts
");
#else
#asm
_gs_gfdinf:	movem.l	d0-d4/a0,-(a7)	* Save used registers
		os9	F$ID		* Get current user id
		move.l	d1,d4		* Save it for later
		moveq.l	#0,d1		* Load superuser id
		os9	F$SUser		* Change to superuser
		bcs.s	_gs_gfdinf10	* Exit if failed
		move.l	0(a7),d0	* Load path number
		move.l	4(a7),d3	* Load sector address
		move.l	28(a7),a0	* Load buffer pointer
		move.l	32(a7),d2	* Load buffer size
		move.w	#SS_FDInf,d1	* Load function code
		os9	I$GetStt	* Call getstat function
		bcs.s	_gs_gfdinf10	* If no error
		moveq.l	#0,d0		*  set return code to 0
		bra.s	_gs_gfdinf20	* else
_gs_gfdinf10	moveq.l	#-1,d0		*  set return code to 1
_gs_gfdinf20	move.l	d4,d1		* Load original user id
		os9	F$SUser		* Change to original user
		lea.l	4(a7),a7	* Discard saved copy of d0
		movem.l (a7)+,d1-d4/a0	* Restore other registers
		rts
#endasm
#endif /* _UCC */

#ifndef _UCC
/* emulate unix perror function */

perror(string)
char *string;
{
    extern int errno;
    fprintf(stderr,"%s ERRNO: %d\n",string,errno);
}
#endif /* _UCC */

#ifdef DTILDE
char *
tilde_expand(dirname)
register char *dirname;
{
    static char *home = NULL;
    static char *temp;

    debug(F111,"tilde_expand dirname", dirname, dirname[0]);
    if (temp == NULL && (temp = malloc(MAXPATH)) == NULL)
      return dirname;
    if(*dirname++ != '~' || (*dirname != '\0' && *dirname != '/'))
                return --dirname;
    if(home == NULL && (home = zhome()) == NULL) return --dirname;
    if(*dirname == '\0') return home;
    strcpy(temp, home);
    strcat(temp, dirname);
    return temp;
}
#endif /* DTILDE */

#ifdef ZFCDAT
/* Z F C D A T  --  Get file creation date */
/*
  Call with pointer to filename.
  On success, returns pointer to modification date in yyyymmdd hh:mm:ss format.
  On failure, returns pointer to null string.
*/
/* static */
char *
zfcdat(name)
char *name;
{
    static char datbuf[20];
    struct my_fildes {                  /* free stack of charge */
        unsigned char fd_att, fd_own[2], fd_date[5], fd_link, fd_fsize[4],
        fd_dcr[3];
    } fdbuf;
    int fd;
    int yy;

    datbuf[0] = '\0';
   
    fd = open(name, 0);
    if (fd == -1)
        fd = open(name, S_IFDIR);
    if (fd != -1) {
        if (_gs_gfd(fd, &fdbuf, sizeof fdbuf) != -1) {
            yy = fdbuf.fd_date[0] + 1900;
            if (fdbuf.fd_date[0] < (unsigned char)70) yy += 100;
            sprintf(datbuf, "%04d%02d%02d %02d:%02d:00",
              yy, fdbuf.fd_date[1], fdbuf.fd_date[2], fdbuf.fd_date[3],
              fdbuf.fd_date[4]);
            yy = strlen(datbuf);
            debug(F111, "zfcdat", datbuf, yy);
            datbuf[17] = '\0';
        }
        close(fd);
    }
    return(datbuf);
}
#endif /* ZFCDAT */

/* 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 [yy]yymmdd[ 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.
*/

zstime(f,yy,x) char *f; register struct zattr *yy; int x;
{
  extern int errno;
  int r = -1;                             /* return code */
  int path;
  register char *point;
  register char save;
  unsigned char year,month,day,hour,minute,second;
  struct fildes buffer;
  unsigned char long_year=0,hour_min=0;
  time_t mktime(),file_date;
  VOID convert_to_local_time();
  struct tm tp;

    debug(F110,"zstime",f,0);

    switch (yy->date.len)
    {
    case 6:   /* yymmdd */
        break;
    case 8:   /* yyyymmdd */
        long_year = 1;
        break;
    case 12:  /* yymmdd hh:mm */
        hour_min = 1;
        break;
    case 14:  /* yyyymmdd hh:mm */
        long_year = 1;
        hour_min = 1;
        break;
    case 15:  /* yymmdd hh:mm:ss */
        hour_min = 1;
        break;
    case 17:  /* yyyymmdd hh:mm:ss */
        long_year = 1;
        hour_min = 1;
        break;
    default:
        debug(F111,"Bad creation date ",yy->date.val,yy->date.len);
        return(-1);
    }

    point = yy->date.val;
    if (long_year)
	{
      save = *(point+4);
      *(point+4) = '\0';
      year = atoi(point) % 100; /* OS-9 only has 2 digit year */
      *(point+4) = save;
      point += 4;
    }
	else
	{
      save = *(point+2);
      *(point+2) = '\0';
      year = atoi(point);
      point += 2; /* don't add 1900: OS-9 only has 2 digit year anyway */
	}

    save = *(point+2);
    *(point+2) = '\0';
    month = atoi(point);
    *(point+2) = save;

    point += 2;
    save = *(point+2);
    *(point+2) = '\0';
    day = atoi(point);
    *(point+2) = save;

    if (hour_min)
    {
        point += 3; /* skip the blank */
        save = *(point+2);
        *(point+2) = '\0';
        hour = atoi(point);
        *(point+2) = save;

        point += 3; /* skip the : */
        save = *(point+2);
        *(point+2) = '\0';
        minute = atoi(point);
        *(point+2) = save;

        second = 0; /* no OS-9 seconds */
    }
    else /* no hours and minutes given */
    {
        hour = minute = second = 0;
    }
    if (   (year   > 99)
        || (month  > 12) || (month  < 1)
        || (day    > 31) || (day    < 1)
        || (hour   > 23)
        || (minute > 59))
    {
        debug(F111,"Bad creation date ",yy->date.val,yy->date.len);
        return(-1);
    }

    /* Convert GMT broken down time to local broken down time */
    convert_to_local_time(&year,&month,&day,&hour,&minute,&second);

    if ((path = open(f,0)) == -1) /* mode 0 is sufficient for _gs_gfd */
    {
        debug(F110,"Can't open file to get fdesc:",f,0);
        return(r);
    }
    if (_gs_gfd(path,&buffer,sizeof(buffer)) == -1)
    {
        debug(F111,"Can't get descriptor for file:",f,errno);
        close(path);
        return(r);
    }
    close(path);
    switch (x)       /* Execute desired function */
    {
        case 0:      /* Set the creation and modification date of the file */
            buffer.fd_date[0] = year;
            buffer.fd_date[1] = month;
            buffer.fd_date[2] = day;
            buffer.fd_date[3] = hour;
            buffer.fd_date[4] = minute;
#ifdef COMMENT
/* The creation date can't be set */
            buffer.fd_dcr[0] = year;
            buffer.fd_dcr[1] = month;
            buffer.fd_dcr[2] = day;
#endif /* COMMENT */
            if ((path = open(f,S_IWRITE)) == -1) /* _ss_pfd needs writing */
            {
                debug(F110,"Can't open file for write:",f,0);
                return(r);
            }
            if (_ss_pfd(path,&buffer) == -1)
            {
              debug(F111,"Can't update descriptor for file:",f,errno);
            }
            else
            {
              debug(F110,"Mod. time is set for file: ",f,0);
              r = 0;
            }
            close(path);
            break;
    case 1:             /* Compare the dates */
            tp.tm_sec  = 0;
            tp.tm_min  = buffer.fd_date[4];
            tp.tm_hour = buffer.fd_date[3];
            tp.tm_mday = buffer.fd_date[2];
            tp.tm_mon  = buffer.fd_date[1]-1;
            tp.tm_year = buffer.fd_date[0];
            if (buffer.fd_date[0] < (unsigned char)70) tp.tm_year += 100;

            file_date = mktime(&tp);

            tp.tm_min  = minute;
            tp.tm_hour = hour;
            tp.tm_mday = day;
            tp.tm_mon  = month-1;
            tp.tm_year = year;
            if (year < (unsigned char)70) tp.tm_year += 100;

/* 1 if x is 1 and date from attribute structure <= file creation date. */
            r = mktime(&tp) <= file_date;
            break;
    default:                            /* Error */
            break;
    }
    return(r);
}

#ifdef RENAME
/*  Z R E N A M E  --  Rename a file  */
/*
   Call with old and new names.
   If new name is the name of a directory, the 'old' file is moved to
   that directory.
   Returns 0 on success, -1 on failure.
*/
int
zrename(old,new) char *old, *new; {
    char *p = NULL, *s = new;
    int x;

    debug(F110,"zrename old",old,0);
    debug(F110,"zrename new",s,0);
    if (isdir(new)) {
        char *q = NULL;
        x = strlen(new);
        if (!(p = malloc(strlen(new) + strlen(old) + 2)))
          return(-1);
        strcpy(p,new);                  /* Directory part */
        if (!ISDIRSEP(*(new+x-1)))      /* Separator, if needed */
          strcat(p,"/");
        zstrip(old,&q);                 /* Strip path part from old name */
        strcat(p,q);                    /* Concatenate to new directory */
        s = p;
        debug(F110,"zrename dir",s,0);
    } else debug(F110,"zrename no dir",s,0);
/*
  Atomic, preferred, uses a single system call, rename(), if available.
  OS/2 rename() returns nonzero, but not necessarily -1 (?), on failure.
*/
    x = rename(old,s);
    if (p) free(p);
    return(x ? -1 : 0);
}

#else /* RENAME */

/*  Z R E N A M E  --  Rename a file  */

/*  Call with old and new names */
/*  Returns 0 on success, -1 on failure. */

zrename(old,new) register char *old, *new;
{
char buffer[256];
        sprintf(buffer,"rename %s %s",old,new);
        if (zsyscmd(buffer) != 0)
        {
          debug(F111,"Can't rename:",old,errno);
          return(-1);
        }
        return(0);
}
#endif /* !RENAME */

/* Sorry...gotta avoid namespace pollution! */
#ifdef _UCC
#define environ	_environ
#endif /* _UCC */
/*
  Functions for executing system commands.
  zsyscmd() executes the system command in the normal, default way for
  the system.  In UNIX, it does what system() does.  Thus, its results
  are always predictable.
  zshcmd() executes the command using the user's preferred shell.
*/
static int
zcmd(shell,command)
char * shell;			/* Name of shell to use		*/
char * command;			/* Command line to execute	*/
{
  char *argblock[3];		/* Argument block for fork	*/
  int pid;			/* Process ID for forked shell	*/
  int wstatus;			/* Status from wait		*/
  unsigned pstatus;		/* Process exit status		*/

  extern char ** environ;
  extern int os9fork();

  debug(F110,shell,command,0);

  /* If no command line, force interactive operation */
  if ((command != NULL) && (*command == '\0')) command = NULL;

  /* Build argument block */
  argblock[0] = shell;
  argblock[1] = command;
  argblock[2] = NULL;

  /* Fork the shell and wait for it to terminate */
  pid = os9exec(os9fork, argblock[0], argblock, environ, 0, 0, 0);
  if (pid != -1) {
    wstatus = wait(&pstatus);
    if (wstatus == pid) {
      debug(F111,"zcmd: process done",command,pstatus);
      if (pstatus == 0) return 1; else return 0;
    } else {
      debug(F111,"zcmd: wait failed",command,wstatus);
      return -1;
    }
  } else {
    debug(F111,"zcmd: fork failed",command,errno);
    return -1;
  }
}

int
zsyscmd(command)
char * command;			/* Command line to execute	*/
{
  debug(F110,"zsyscmd",command,0);

  return zcmd("shell",command);
}

int
zshcmd(command)
char * command;			/* Command line to execute	*/
{
  char * shell;			/* Name of shell to use		*/

  extern char * getenv();

  debug(F110,"zshcmd",command,0);

  shell = getenv("SHELL");
  if (shell == NULL) {
    shell = "shell";
  }
  return zcmd(shell,command);
}

/*  Z S I N L  --  Read a line from a file  */

/*
  Writes the line into the address provided by the caller.
  n is the Kermit "channel number".
  Writing terminates when newline is encountered, newline is not copied.
  Writing also terminates upon EOF or if length x is exhausted.
  Returns 0 on success, -1 on EOF or error.
*/
zsinl(n,s,x) int n, x; char *s; {
    int a, z = 0;

    if (chkfn(n) < 1) {                 /* Make sure file is open */
          return(-1);
    }
    a = -1;
    while (x--) {
      if (zchin(n,&a) < 0) {       /* Read a character from the file */
        z = -1;
        break;
      }
      if ( a == (char) NLCHAR) break; /* Single-character line terminator */
      *s = a;
      s++;
    }
    *s = '\0';
    return(z);
}

static char
*get_gmtime(file_desc) struct fildes *file_desc;
/* returns UCT/GMT yyyymmdd hh:mm:00 time out of the file descriptor
   if impossible to get GMT it takes the time from the file descriptor
   without converion */
{
  static char datbuf[18]; /* must be static cause returned!!! */
  struct tm *gmt = NULL, tp;
  time_t local_file_date;

  tp.tm_sec  = 0;
  tp.tm_min  = file_desc->fd_date[4];
  tp.tm_hour = file_desc->fd_date[3];
  tp.tm_mday = file_desc->fd_date[2];
  tp.tm_mon  = file_desc->fd_date[1]-1;
  tp.tm_year = ((unsigned char *)file_desc->fd_date)[0];
  if (tp.tm_year < 70) tp.tm_year += 100;

  /* If TZ not defined send modification date literally */
  if (getenv("TZ") != (char *)NULL) { 
    local_file_date = mktime(&tp);
    if (local_file_date != (time_t)-1) /* Take time literally */
      gmt = gmtime(&local_file_date);
  }

  if (gmt == NULL) gmt = &tp;

  sprintf(datbuf,"%4d%02d%02d %02d:%02d:00",
          1900+(unsigned int)gmt->tm_year,
          gmt->tm_mon+1,gmt->tm_mday,gmt->tm_hour,gmt->tm_min);

  return(datbuf);
}

static VOID
convert_to_local_time(year,month,day,hour,minute,second)
unsigned char *year,*month,*day,*hour,*minute,*second;
/* will convert to local time if timezone information is available assuming
   UCT/GMT time as input , otherwise just return.
   year must be since 1900 */
{
  struct tm *tp;
  time_t cal_date;
  int time,date;
  char *getenv();
  int y;

  if (getenv("TZ") == (char *)NULL) return;
  y = *year + 1900;
  if (*year < (unsigned char)70) y += 100;
  date = (y<<16)+(*month<<8)+*day;
  time = (*hour<<16)+(*minute<<8)+*second;
  if (_julian(&time,&date) == -1) return;
  cal_date = (date-2440587)*86400+time; /* 2440587 = 1st of Jan 1970 */

  tp = localtime(&cal_date);

  if (tp == (struct tm*)NULL) return;

  *second = tp->tm_sec;
  *minute = tp->tm_min;
  *hour = tp->tm_hour;
  *day = tp->tm_mday;
  *month = tp->tm_mon+1;
  *year = tp->tm_year % 100;

}

FILE *
pipeopen(command,access_type) char *command,*access_type;
{
  extern int os9forkc();
  extern char **environ;
  int duped,dupederr,stdinout;
  FILE *pipe;
  char *argv[3];

    if (*access_type=='r') stdinout = 1; /* redirect stdout and stderr*/
    else if (*access_type=='w') stdinout = 0; /* redirect stdin */
    else return((FILE *)NULL);

    if((duped = dup(stdinout)) <= 0) return((FILE *)NULL);
    if(stdinout == 1)
	{
      if((dupederr = dup(2)) <= 0)
	  {
        close(duped);
		return((FILE *)NULL);
	  }
	}
    if((pipe = fopen("/pipe", "r+")) == NULL)
    {
        close(duped);
	    if(stdinout == 1) close(dupederr);
        return((FILE *)NULL);
    }
    close(stdinout);
    dup(fileno(pipe));
	if (stdinout == 1) /* also dupe stderr */
	{
      close(2);
      dup(fileno(pipe));
	}
    argv[0] = "shell";
    argv[1] = command;
    argv[2] = (char *)NULL;
    if((pipe_pid = os9exec(os9forkc,argv[0],argv,environ,0,0,3)) < 0)
    {
        pipe_pid = 0;
        fclose(pipe);

        close(stdinout);
        dup(duped); /* restore old stdinout */
        close(duped);
	    if(stdinout == 1) /* also restore old sterr */
		{
		  close(2);
	      dup(dupederr);
	      close(dupederr);
		}
        return (FILE *)NULL;
    }
    debug(F101," pipeopen success id:","",pipe_pid);
    close(stdinout);
    dup(duped);
    close(duped);
	if(stdinout == 1)
	{
	  close(2);
	  dup(dupederr);
      close(dupederr);
	}
    return(pipe);
}

/*
   Tell if string pointer s is the name of an existing directory.
   Returns 1 if directory, 0 if not a directory.
*/
int
isdir(s) char *s; {
    int x;

    if (!s) return(0);
    if (!*s) return(0);

    x = access(s, S_IFDIR);
    debug(F111,"isdir stat",s,x);
    if (x == -1) {
        debug(F101,"isdir errno","",errno);
        return(0);
    } else {
        return 1;
    }
}

#ifdef CK_MKDIR
/* Some systems don't have mkdir(), e.g. Tandy Xenix 3.2.. */

/* Z M K D I R  --  Create directory(s) if necessary */
/*
   Call with:
    A pointer to a file specification that might contain directory
    information.  The filename is expected to be included.
    If the file specification does not include any directory separators,
    then it is assumed to be a plain file.
    If one or more directories are included in the file specification,
    this routine tries to create them if they don't already exist.
   Returns:
    0 on success, i.e. the directory was created
   -1 on failure to create the directory
*/
int
zmkdir(path) char *path; {
    char *xp, *tp, c;
    int x;

    x = strlen(path);
    debug(F111,"zmkdir",path,x);
    if (x < 1 || x > MAXPATH)           /* Check length */
      return(-1);
    if (!(tp = malloc(x+1)))            /* Make a temporary copy */
      return(-1);
    strcpy(tp,path);
#ifdef DTILDE
    if (*tp == '~') {                   /* Starts with tilde? */
        xp = tilde_expand(tp);          /* Attempt to expand tilde */
        if (*xp) {
            char *zp;
            debug(F110,"zmkdir tilde_expand",xp,0);
            if (!(zp = malloc(strlen(xp) + 1))) { /* Make a place for it */
                free(tp);
                return(-1);
            }
            free(tp);                   /* Free previous buffer */
            tp = zp;                    /* Point to new one */
            strcpy(tp,xp);              /* Copy expanded name to new buffer */
        }
        debug(F110,"zmkdir tp after tilde_expansion",tp,0);
    }
#endif /* DTILDE */
    xp = tp;
    if (ISDIRSEP(*xp))                  /* Don't create root directory! */
      xp++;

    /* Go thru filespec from left to right... */

    for (; *xp; xp++) {                 /* Create parts that don't exist */
        if (!ISDIRSEP(*xp))             /* Find next directory separator */
          continue;
        c = *xp;                        /* Got one. */
        *xp = NUL;                      /* Make this the end of the string. */
        if (!isdir(tp)) {               /* This directory exists already? */
            debug(F110,"zmkdir making",tp,0);
            x =                         /* No, try to create it */
#ifdef NOMKDIR
               -1                       /* Systems without mkdir() */
#else
               makdir(tp,0,077)
#endif /* NOMKDIR */
                 ;
            if (x < 0) {
                debug(F101,"zmkdir failed, errno","",errno);
                free(tp);               /* Free temporary buffer. */
                return(-1);             /* Freturn failure code. */
            }
        }
        *xp = c;                        /* Replace the separator. */
    }
    free(tp);                           /* Free temporary buffer. */
    return(0);                          /* Return success code. */
}
#endif /* CK_MKDIR */

/* Z F S E E K  --  Position input file pointer */
/*
   Call with:
    Long int, 0-based, indicating desired position.
   Returns:
    0 on success.
   -1 on failure.
*/
#ifndef NORESEND
int
#ifdef CK_ANSIC
zfseek(long pos)
#else
zfseek(pos) long pos;
#endif /* CK_ANSIC */
/* zfseek */ {
    debug(F101,"zfseek","",pos);
    return(fseek(fp[ZIFILE], pos, 0));
}
#endif /* NORESEND */
