/* Edit by Bill on Thu May 30, 00:18 */
/* Do error handling, neaten up comments, and some code. */
/* Edit by Bill on Wed May 15, 16:09 */
/* Make zrtol call common sfprtol, .RSRC overrides default settings */
/* ckmfio.c, Mon Apr 29 17:48, Edit by Bill*2 */
/* Put null in translated name to tie it off. */
/* Make author text of new file to ???? instead of random string */
/* Do flushvol after closing a file */
/* Bill C., Apr 24 */
/* Change zchin to allow sending of files with high order bits on */
/* Bill C., Apr 22 */
/* Add error handling (informing) for more cases, e.g. can't delete */
/* Bill C., Apr 22 */
/* Fix Resource/Data fork stuff.  Uppercase things where needed */
/* ckzmac.c, Thu Apr 21 17:19, Edit by Bill */
/*  Ignore trying to close an not-openend file, driver does it alot */
/* ckzmac.c, Thu Apr 11 21:18, Edit by Bill */
/*  Catch error in ZOPENO when trying to open an existing file */
/* ckzmac.c, Thu Apr 14 20:07, Edit by Bill */
/*  Translate calls with ZCTERM to go to the console routines */

/* 
 * File ckmfio  --  Kermit file system support for the Macintosh
 *
 * Copyright (C) 1985, Trustees of Columbia University in the City of
 * New York.  Permission is granted to any individual or institution to
 * use, copy, or redistribute this software so long as it is not sold
 * for profit, provided this copyright notice is retained.
 *
 */

/* Definitions of some Unix system commands */

char *DIRCMD = "";			/* For directory listing */
char *DELCMD = "";			/* For file deletion */
char *TYPCMD = "";			/* For typing a file */
char *SPACMD = "";			/* Space/quota of current directory */
char *SPACM2 = "";			/* For space in specified directory */
char *WHOCMD = "";			/* For seeing who's logged in */

/*
  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)         -- 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.
   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.
 */

#include "ckcker.h"			/* Kermit definitions */
#include "ckmdef.h"			/* Common Mac module definitions */
#include "ckmres.h"			/* Resource defs */
#include "ckcdeb.h"			/* Debug() and tlog() defs */
#include <ctype.h>			/* Get islower and toupper */
#include "mac/quickdraw.h"
#include "mac/osintf.h"
#include "mac/toolintf.h"

/* These should all be settable by the File Settings Menu */

char  *authortext="????";		/* String to use as "author" of file */

#define FS_WIND 1			/* file is a text edit buffer */
#define FS_OPEN 2			/* file has been opened */
#define FS_RSRC 4			/*  opened in resource fork */
#define FS_DATA 8
#define FS_PIPE 16			/* file is a memory buffer */

typedef struct {
    PLONG frefnum;			/* file reference number (pascal) */
    int fstatus;			/* file status bits */
    char *fpipeptr;			/* pipe pointer */
} MACFILE;

MACFILE fp[ZNFILS] = { 			/* File information */
    {0,0},{0,0},{0,0},{0,0},
    {0,0},{0,0},{0,0}};

char pipebuf[128];			/* there's a limit to pipes! */


/*  Z O P E N I --  Open an existing file for input.  
 *
 * The file name has been returned from and the volume reference
 * number set by SFGetFile.
 *
 * Returns:
 *  TRUE: file opened ok
 *  FALSE: some error.
 */

zopeni(n,name)
int n;
char *name;
{
  int err;
  register MACFILE *fpp;

  if (chkfn(n)) {
    printerr("At zopeni file is already open ",n);
    return(FALSE);
  }

  fpp = &fp[n];

  if (n == ZCTERM) {			/* Terminal open? */
    if (chkfn(ZIFILE))			/* Check current ZOFILE */
      printerr("ZIFILE already open...: ",n);
    fp[ZIFILE].fstatus = FS_WIND;	/* redirect... here it is */
    fpp->fstatus = FS_WIND;		/* Indicate this is open too */
    return(conopen());			/* Return from low level open */
  }
    
  if (n == ZSYSFN)			/* trying to open a pipe? */
    return(zxcmd(name));		/* yes... */
     
  if (n == ZIFILE &&			/* opening input file? */
      (filargs.filflg & FIL_RSRC)) 	/*  and they said resource? */
    err = OpenRF(name,filargs.filvol,&fpp->frefnum);
  else					/* else some other channel or data */
    err = FSOpen(name,filargs.filvol,&fpp->frefnum);

  if (err != noErr)			/* check for open error */
    return(ioutil(err));		/* failed... */

  fpp->fstatus = FS_OPEN | (		/* set flags */
      	    (filargs.filflg & FIL_RSRC) ? FS_RSRC : FS_DATA);

  GetEOF(fpp->frefnum,&filargs.filsiz);  /* set size for screen */
  return(TRUE);				/* Return success */
  SYM(ZOPENI);
}


/*  Z O P E N O  --  Open a new file for output.
 *
 * Returns:
 *  TRUE: File opened ok
 *  FALSE: some error has occured or channel occupied.
 *
 */

zopeno(n,name)
int n;
char *name;
{
  int err;
  char *forktext;
  FInfo finfo;
  register MACFILE *fpp;

  if (chkfn(n)) {
    printerr("zopeno - file is already open: ",n);
    return(FALSE);
  }

  fpp = &fp[n];

  if (n == ZCTERM || n == ZSTDIO) {	/* Terminal open? */
    if (chkfn(ZOFILE))			/* Check current ZOFILE */
      printerr("ZOFILE already open...: ",n);
    fp[ZOFILE].fstatus = FS_WIND;	/* yes, redirect... here it is */
    fpp->fstatus = FS_WIND;		/* Indicate this is open too */
    return(conopen());			/* Return from low level open */
  }
    
  if (n == ZOFILE && (filargs.filflg & FIL_RSRC))
    forktext = "APPL";
  else
    forktext = "TEXT";			/* Make fork reflect fork choice */

  err = Create(name,filargs.filvol,authortext,forktext);
  if (err == dupFNErr) {		/* duplicate file? */
    if (!ioutil(FSDelete(name,		/* Try to delete it */
      	      	      filargs.filvol)))	/*  checking for failure */
      return(FALSE);			/* failed... */
    err = Create(name,filargs.filvol,	/* recreate */
      	      	  authortext,forktext); 
  }
  
  if (err != noErr)			/* some error? */
   return(ioutil(err));			/* yes, do message and return */
    
/* set file's folder from filargs.filfldr which is either the */
/* applications folder or the settings file folder */

  GetFInfo(name,filargs.filvol,&finfo); /* read current finder info */
  finfo.fdFldr = filargs.filfldr;	/* set new folder */
  SetFInfo(name,filargs.filvol,&finfo); /* and tell system about it */

  if (n == ZOFILE && 			/* is it our transferred file? */
      (filargs.filflg & FIL_RSRC))	/*  want to use resource fork?  */
    err = OpenRF(name,filargs.filvol,	/* yes... */
      	      	  &fpp->frefnum);
  else					/* else data, or some other file */
    err = FSOpen(name,filargs.filvol,
      	      	  &fpp->frefnum);

  if (err != noErr)			/* able to open? */
   return(ioutil(err));			/* no. fail return now */
   
  fp[n].fstatus = FS_OPEN | 
      ((filargs.filflg & FIL_RSRC) ? FS_RSRC : FS_DATA);
      
  return(TRUE);				/* done ok */
  SYM(ZOPENO);
}


/*  Z C L O S E  --  Close the given file.
 *
 * Returns:
 *  TRUE: file closed ok.
 *  FLASE: some error has occured.
 *
 */

zclose(n)
int n;
{
  int err = noErr;
  register MACFILE *fpp;

  if (!chkfn(n))			/* is it opened? */
    return(FALSE);			/* no return now */

  fpp = &fp[n];

  if (fpp->fstatus == FS_WIND)		/* is this a window? */
    fp[ZCTERM].fstatus = 0;		/* yes, clear ZCTERM */
  else
    if (fpp->fstatus == FS_PIPE)	/* is this a pipe? */
      fp[ZSYSFN].fstatus = 0;		/* yes, no pipe now, clear ZSYSFN */
    else {
      err = FSClose(fpp->frefnum);	/* else use OS close */
      if (err != noErr)			/* and if that worked */
        err = FlushVol(NILPTR,		/* flush buffers in case write */
	      filargs.filvol);	
    }
    
  fpp->fstatus = 0;			/* clear out status word */
  if (n == ZOFILE || n == ZIFILE)	/* turn off both flags */
      filargs.filflg &= ~(FIL_RSRC | FIL_DATA);

  return(ioutil(err));			/* return according to io operations */
  SYM(ZCLOSE);
}


/*  Z C H I N  --  Get a character from the input file.
 *
 * Returns:
 *  0: Ok
 * -1: EOF (or other error).
 *
 */

zchin(n,c)
int n;
char *c;
{
  int err;
  PLONG rdcnt;				/* pascal long */
  register MACFILE *fpp;

  if (!chkfn(n))
    return(0);

  fpp = &fp[n];

  if (fpp->fstatus == FS_WIND) {	/* a window? */
    printerr("zchin called for FS_WIND file: ",n);
    return(0);
  }
    
  if (fpp->fstatus == FS_PIPE)		/* a pipe? */
    if (*(fpp->fpipeptr) == '\0')	/* is this eo-pipe? */
      return(-1);			/* yes, fail return */
    else {
      *c = *(fpp->fpipeptr)++;		/* read character */
      return(0);			/* success */
    }
      
  rdcnt = 1;
  err = FSRead(fpp->frefnum,&rdcnt,c);
  if (err == eofErr) return(-1);	/* Failure return */
  return(ioutil(err) ? 0 : -1);		/* success or unknown failure */
  SYM(ZCHIN);
}

/*  Z S O U T  --  Write a string to the given file, buffered.
 *
 * Returns:
 *  0: OK
 * -1: Error
 *
 */ 

zsout(n,s)
int n;
char *s;
{
  PLONG wrcnt;				/* pascal long */

  if (n == ZCTERM || fp[n].fstatus == FS_WIND)
    return(conol(s));
    
  wrcnt = (long) strlen(s);
  return(ioutil(FSWrite(fp[n].frefnum,&wrcnt,s)) ? 0 : -1);
  SYM(ZSOUT);
}

/*  Z S O U T L  --  Write string to file, with line terminator, buffered.
 *
 * Returns:
 *  0: OK
 * -1: Error
 *
 */

zsoutl(n,s)
int n;
char *s;
{
  PLONG wrcnt;			/* pascal long */
  int err;

  if (n == ZCTERM || fp[n].fstatus == FS_WIND)
    return(conoll(s));
     
  wrcnt = (long) strlen(s);
  err = FSWrite(fp[n].frefnum,&wrcnt,s);
  if (err == noErr) {
    wrcnt = 2;
    err = FSWrite(fp[n].frefnum,&wrcnt,"\r\n");
  }
  
  return(ioutil(err) ? 0 : -1);
  SYM(ZSOUTL);
}

/*  Z S O U T X  --  Write x characters to file, unbuffered.
 *
 * Returns:
 *  0: OK
 * -1: Error
 */

zsoutx(n,s,x)
int n, x;
char *s;
{
  if (n == ZCTERM || fp[n].fstatus == FS_WIND)
    return(conxo(s,x));
    
  return(ioutil(FSWrite(fp[n].frefnum,(PLONG *) &x,s)) ? 0 : -1);
  SYM(ZSOUTX);
}


/*  Z C H O U T  --  Add a character to the given file.
 *
 * Returns:
 *  0: OK
 * -1: Error
 */

zchout(n,c)
int n;
char c;
{
  PLONG wrcnt;				/* pascal long */
  int err;

  if (n == ZCTERM || fp[n].fstatus == FS_WIND)
    return(conoc(c));			/* Then send to console routine */
     
  wrcnt = 1;
  err = FSWrite(fp[n].frefnum,&wrcnt,&c);
  if (err != noErr)			/* error occured? */
    sstate = 'a';			/* yes, abort protocol */
  return (ioutil(err) ? 0 : -1);	/* else return code */
  SYM(ZCOUT);
}

/*  C H K F N  --  Internal function to verify file number is ok.
 *
 * Returns:
 *   TRUE  - file is open
 *  FALSE  - file is not open
 * 
 * Issues an error message if the file number is not in range.
 *
 */

chkfn(n)
int n;
{
  switch (n) {
    case ZCTERM:
    case ZSTDIO:
    case ZIFILE:
    case ZOFILE:
    case ZDFILE:
    case ZTFILE:
    case ZPFILE:
    case ZSYSFN:
    case ZSFILE: break;
    default:
      debug(F101,"chkfn: file number out of range","",n);
      printerr("chkfn - file number not in range: ",n);
      return(FALSE);			/* ugh */
  }
  return((fp[n].fstatus != 0));		/* if open, fstatus is nonzero */
  SYM(CHKFN);
}

/*  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.
 */

zchki(name)
char *name;
{
  PLONG size;
  int err;
  FileParam info;

  if (strcmp(name,"stdin") == 0)	/* stdin is a pipe */
   return(strlen(pipebuf));		/* return size of buffer */

  c2pstr(name);				/* convert to a pascal string */
  info.ioFVersNum = 0;			/* No version number */  
  info.ioFDirIndex = 0;			/* Use the file name */
  info.ioNamePtr = name;		/* Point to the file name */
  info.ioVRefNum = filargs.filvol;	/* Volume number */
  err = PBGetFInfo(&info,FALSE);	/* Get info on file */
  p2cstr(name);				/* put the name back */
  
  if (err == fnfErr)			/* file not found? */
    return(-1);				/* then that is what they want */
    
  if (err != noErr)			/* any other error? */
    printerr("zchki failed: ",err);  	/* tell me about it */

  size = (filargs.filflg & FIL_RSRC) ?	/* if thinking about RSRC */
      	info.ioFlRPyLen : info.ioFlPyLen; /*  return that size, else DATA */
  return(size);				/* did ok */
  SYM(ZCHKI);
}

/*  Z C H K O  --  Check if output file can be created.
 *
 * Returns
 *  0: Write OK
 * -1: write permission for the file should be denied.
 */
 
zchko(name)
char *name;
{
  char volname[100];
  VolumeParam info;

  info.ioVolIndex = 0;			/* Use the vol ref num only */
  info.ioNamePtr = volname;		/* Pointer to the volume name */
  info.ioVRefNum = filargs.filvol;	/* Volume reference number */
  if (!ioutil(PBGetVInfo(&info,0)))	/* Get info on vol, synchronously */
    return(-1);				/* failed... */

  if ((info.ioVAtrb & 0x8000) != 0)	/* Write locked? */
    return(-1);				/* yes... */
    
  return(0);				/* else success */
  SYM(ZCHKO);
}

/*  Z D E L E T  --  Delete the named file.  */

zdelet(name)
char *name;
{
  int err;
  err = FSDelete(name,filargs.filvol);
  if (err != fnfErr && err != noErr)	/* file not found... I guess thats */
    return(ioutil(err));		/*  ok... */
  return(TRUE);				/* well done */
  SYM(ZDELETE);
}


/*  Z R T O L  --  Convert remote filename into local form.
 *
 * Check here to see if this should go into the resource fork (.rsrc)
 * or into the data fork (.data).
 *
 */ 

zrtol(name,name2)
char *name, *name2;
{
  
  strcpy(name2,name);			/* copy name to destination */
    
  if (filargs.filflg & (FIL_DODLG))	/* selected by user? */
    return;				/* won't be called but... */

  filargs.filflg &= ~(FIL_RBDT);	/* clear out flags */
  filargs.filflg |= sfprtol(name2);	/* convert name2 and set flags */
  binary = (filargs.filflg & FIL_BINA); /* selected binary mode? */  
  return;
  SYM(ZRTOL);
}


/*  Z L T O R  --  Convert filename from local format to common form. */

zltor(name,name2)
char *name, *name2;
{
  int dc = 0;

  while (*name != '\0') {
    if (*name == ' ') 
      name++;				/* Skip spaces */
    else
      if ((*name == '.') && (++dc > 1)) {
	*name2++ = 'X'; 		/* Just 1 dot */
	name++;
      }
      else 
      	*name2++ = (islower(*name)) ? toupper(*name++) : *name++;
  }
  *name2++ = '\0';			/* deposit final null */
  return;
  SYM(ZLTOR);
}    


/*  Z C H D I R  --  Change directory  (used on the Mac to switch vols) */

zchdir(dirnam)
char *dirnam;
{
  int err;

  err = SetVol(dirnam,0);		/* set default volume */
  if (err == noErr)
  {
   screen(SCR_TN,0,0l,dirnam);
   filargs.filvol = 0;			/* make default */
  }
  else
   screen(SCR_TN,0,0l,"Can't set volume");

  return(err == noErr);			/* return ok or fail */
  SYM(ZCHDIR);
}


/*  Z X C M D -- Run a system command so its output can be read like a file.
 *
 * Used on the MAC to implement MAC settings commands -- commands from a
 * remote system when in server mode that change internal variables.
 *
 */

#define CMD_RSRC 1
#define CMD_DATA 2
#define CMD_TEXT 3
#define CMD_BINA 4
#define CMD_UNK 255

zxcmd(comand)
char *comand;
{
  int sc;

  fp[ZIFILE].fstatus = FS_PIPE;		/* set input from pipe */
  fp[ZIFILE].fpipeptr = pipebuf;	/* init pointer to buffer */      
 
  switch (sc = getcmd(comand)) {
    case CMD_RSRC:
    case CMD_DATA:
      strcpy(pipebuf,"Default Fork set OK\n");
      filargs.filflg &= ~(FIL_RSRC | FIL_DATA); /* turn off  */
      filargs.filflg |= (sc == CMD_RSRC) ? FIL_RSRC : FIL_DATA;
      return(TRUE);			/* success */

    case CMD_TEXT:
    case CMD_BINA:
      strcpy(pipebuf,"Default Mode set OK\n");
      filargs.filflg &= ~(FIL_TEXT | FIL_BINA);
      filargs.filflg |= (sc == CMD_BINA) ? FIL_BINA : FIL_TEXT;
      return(TRUE);			/* ok */
      
    default:
      return(FALSE);			/* fail, unknown */
  }    
  SYM(ZXCMD);
}

char *cmdtab[] = {
  "fork rsrc",
  "fork data",
  "mode binary",
  "mode text"
};

int toktab[] = {
  CMD_RSRC,
  CMD_DATA,
  CMD_BINA,
  CMD_TEXT
};

#define NTOKS (sizeof (toktab)/sizeof(int))

getcmd(cmd)
char *cmd;
{
  int k;

  for (k=0; k < NTOKS; k++)
   if (strcmp(cmdtab[k],cmd) == 0)
    return(toktab[k]);			/* and return ID */
  return(CMD_UNK);			/* else unknown */
  SYM(GETCMD);
}



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

zclosf() 
{
  return;
}


int znfirst = 0;
char *zname;

/*  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;
{
  znfirst = 0;				/* Say this is the first time thru */
  zname = fn;				/* Save a pointer to that name */
  return(1);				/* Say one file matches */
  SYM(ZXPAND);
}

/*  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 (znfirst++ == 0) {
    strcpy(fn,zname);			/* Get the file's name */
    return(1);				/* No more files in this wildcard */
  }
  else return(0);
  SYM(ZNEXT);
}

/*  Z N E W N  --  Make a new name for the given file  */

znewn(fn,s)
char *fn, **s;
{
  char *extp;
  int ver;

  strcpy(*s,fn);			/* copy in the name */
  if (strlen(*s) > 59)			/* don't allow long names */
   *s[59] = '\0';			/* it breaks the finder */
  extp = *s+strlen(*s);			/* find position of extension */
  *extp++ = '.';			/* add in the dot now */
  
  for (ver=0; ver < 99; ver++)		/* I'll try this many names */
  {
    NumToString(ver,extp);		/* add in the number */
    if (zchki(*s) == -1)		/* is this file known? */
     return;				/* no, made a good one! */
  }
  fatal("znewn failed to find unique name in 64 attempts",0);
  return;
  SYM(ZNEWN);
}

/* zkself() - Kill self (reboot).  On other machines does a logout.
 *    	      Flush volumes and reboot.  Called by remote BYE.
 *
 */

zkself()
{
  DrvQEl *drvqe;
  char vname[255];
  PLONG vfreeb;
  int vrefnum,err;

  for (drvqe = (DrvQEl *) DrvQHdr->qHead; /* handle on drive q */
       drvqe != NULL;			/* while still something */
       drvqe = drvqe->qLink)		/* step to next */
  {					/* for each drive */
    err = GetVInfo(drvqe->dQDrive,vname,&vrefnum,&vfreeb);
    if (err = noErr)
      err = FlushVol(NILPTR,vrefnum);	/* flush the volume given refnum */
    else
     if (err != nsvErr)
       screen(SCR_TN,0,0l,"Remote cmd: GetVinfo returned unknown code");
  }    
  asm("	reset");			/* reset the machine */
  asm("	jmp /40000A");			/* boot address */
  return(FALSE);
  SYM(ZKSELF);
}


/* ioutil - handle the result from an IO call, checking for an
 *    	    error return and displaying an appropriate error
 *    	    message.  Returns TRUE if no error occured, FALSE
 *    	    otherwise.
 */

struct {
  int errnum;
  char *errstr;
} ioerrs[] = {
    {dirFulErr,"Directory is full"},
    {dskFulErr,"Disk is full"},
    {wPrErr,"Diskette is write protected"},
    {fLckdErr,"File is software locked"},
    {vLckdErr,"Volume is software locked"},
    {fBsyErr,"File is busy"},
    {opWrErr,"File is already open with write permission"},
    {0,NILPTR}
  };

ioutil(err)
int err;
{
  int e;

  if (err == noErr)
   return(TRUE);

  for (e = 0; ioerrs[e].errnum != 0 && 
      	      ioerrs[e].errnum != err; e++);

  if (ioerrs[e].errstr == NILPTR)	/* anything there? */
   printerr("Unknown IO error: ",err);
  else
   printerr(ioerrs[e].errstr,0);
   
  return(FALSE);   
  SYM(IOUTIL);
}
