Path: utzoo!attcan!utgpu!jarvis.csri.toronto.edu!mailrus!uwm.edu!uakari.primate.wisc.edu!ginosko!uunet!mcsun!ukc!edcastle!lfcs!paul
From: paul@lfcs.ed.ac.uk (Paul Anderson)
Newsgroups: comp.protocols.appletalk
Subject: Executing remote Unix processes from a Mac (part1 of 2)
Message-ID: <513@castle.ed.ac.uk>
Date: 28 Sep 89 11:36:37 GMT
Sender: root@castle.ed.ac.uk
Reply-To: paul@lfcs.ed.ac.uk (Paul Anderson)
Organization: Laboratory for the Foundations of Computer Science, University of Edinburgh
Lines: 884

Here are the patches to Aufs that allow a Mac to execute proceses on the
Unix Host.

#! /bin/sh
# This is a shell archive.  Remove anything before this line, then unpack
# it by saving it into a file and typing "sh file".  To overwrite existing
# files, type "sh file -c".  You can also feed this as standard input via
# unshar, or by typing "sh MANIFEST <<'END_OF_MANIFEST'
X   File Name		Archive #	Description
X-----------------------------------------------------------
X MANIFEST                  1	This shipping list
X README.PROCESS            1	
X afproc.c                  1	
X afproc.h                  1	
X aufs.diffs                2	
X hypertalk                 1	
END_OF_MANIFEST
if test 294 -ne `wc -c README.PROCESS <<'END_OF_README.PROCESS'
X
XExecuting Unix Processes from a Macintosh
X=========================================
X
XThis package contains some extensions to the AUFS file server enabling
XMacintosh programs to execute and communicate with processes on the Unix host.
X
XInstallation
X============
X
XThis distribution contains a number of additional source files and a set
Xof patches to the version of AUFS distributed with CAP 5.0. To build the
Xnew server, take a copy of the CAP 5.0 AUFS source directory and apply
Xthe patches -
X
X	patch  INT = nn 	- set interrupt char to nn (decimal)
X	 KIL = nn 	- set kill char to nn (decimal)
X	 EOF = nn 	- set EOF char to nn (decimal)
X	 IDL = nn 	- set end of data char to nn (decimal)
X	 ON = nn 	- set escape char to nn (decimal)
X	 OFF = nn 	- set end of escape seq char to nn (decimal)
X	 TIM = nn 	- set read timeout to nn seconds
X
XThe control codes can be set to -1 to disable them totally.
X
XExample
X=======
X
XTo interact with a Unix process such as a shell or an interpreter, the
XMacintosh program will normally issue a command then read a reply.
XTo maintain synchronization, there needs to be some way for the
XMacintosh to identify the end of the reply and it is easiest if the
XUnix process supplies a unique prompt as a "turnaround" character.
X
XThe code then typically looks something like ...
X
X	open( "|code-file-name" )
X
X	repeat
X
X		write( "command" )
X
X		while true
X
X			read( data, len )		Read the data
X			if (len==0) break		Must be EOF
X			if (len==1) continue		Timeout
X			Display( data, len-1 )		Show the returned data
X			if (data[len-2]=prompt) break	End of reply
X		end
X
X	until done
X
X	write( EOF )
X
X	close()
X
XThis code keeps polling the remote process for data. If the remote
Xprocess is expected to take some time, the read timeout period can be changed
X(using the control codes) to decrease the polling frequency, but the
XMac cannot be interrupted by the user whilst inside the read call, so
Xthis increases the response time to any keyboard break-in.
X
XThe file Hypertalk shows an example of Hypertalk code which uses a CTRL-G
Xcharacter as the turnaround. These could be used to issue commands to a
Xshell which rings the bell as a prompt.
X
X
X
XPaul Anderson                      JANET: paul@uk.ac.ed.lfcs
XLFCS, Dept. of Computer Science    UUCP:  ..!mcvax!ukc!lfcs!paul	
XUniversity of Edinburgh            ARPA:  paul%lfcs.ed.ac.uk@nsfnet-relay.ac.uk
XEdinburgh EH9 3JZ, UK.             Tel:   031-667-1081 Ext 2788
END_OF_README.PROCESS
if test 4578 -ne `wc -c afproc.c <<'END_OF_afproc.c'
X/*
X * $Author: paul $
X * $Header: /tmp_mnt/auto1b00053/paul/code/aufs/p2/RCS/afproc.c,v 1.6 89/06/28 17:20:36 paul Exp $
X * $Revision: 1.6 $
X*/
X
X/* Extended routines for AUFS to allow communication with UNIX processes.
X*/
X
Xstatic char rcsid[]="$Header: /tmp_mnt/auto1b00053/paul/code/aufs/p2/RCS/afproc.c,v 1.6 89/06/28 17:20:36 paul Exp $"; /* RCS identifier */
X
X#include 
X#include 
X#include 
X#include 
X#include 
X#include 
X#include 
X#include 
X#include 
X#include "afps.h"
X#include "afpvols.h"
X#include "afppasswd.h"
X#include "afposncs.h"
X#include "afpgc.h"
X#include "afproc.h"
X
Xexport void ProcInit();
Xexport void ProcCleanup();
Xexport PROCESS *FindProcess();
Xexport OSErr ProcOpen();
Xexport OSErr ProcClose();
Xexport OSErr ProcRead();
Xexport OSErr ProcWrite();
Xexport OSErr ProcDelete();
X
Ximport int errno;
Ximport int enableProcs;
X
XPROCESS *procList = NULL;	/* Linked list of process records */
X
Xexport PROCESS
X*FindProcess( fd )
Xint fd;
X{
X  PROCESS *p = procList;
X
X  /* Find any attached process corresponding to this FD */
X  while (p) {
X    if ( (p->status&OPEN) && (p->fdo[0]==fd) ) return p;
X    p = p->nextProc;
X  }
X  return NULL;
X}
X
Xprivate void
XCheckProcess( p )
XPROCESS *p;
X{
X  /* See if the process has died */
X  /* If it stops (Uh?) hit it on the head */
X  if ((p->status&RUNP)==0) return;
X  else for (;;) {
X    if ( wait4( p->pid, &(p->termStat), WNOHANG, NULL ) != 0 ) {
X      if (p->termStat.w_stopval==WSTOPPED) {
X	if (DBPRO)
X	  printf( "PROCESS: Stopped: [%08x] %s status=%04x\n", 
X		 (unsigned)p, p->cmnd, p->status );
X	kill( p->pid, SIGKILL );
X      } else {
X	if (DBPRO)
X	  if (p->termStat.w_termsig == 0)
X	    printf( "PROCESS: Died: [%08x] %s exit=%04x\n", 
X		   (unsigned)p, p->cmnd, p->termStat.w_retcode );
X	  else
X	    printf( "PROCESS: Died: [%08x] %s signal=%04x\n", 
X		   (unsigned)p, p->cmnd, p->termStat.w_termsig );
X	p->status &= ~RUNP;
X	return;
X      }
X    } else return;
X  }
X}
X
Xprivate void
XKillProcess( p )
XPROCESS *p;
X{
X  int timer;
X
X  /* Give it two secpid, &(p->termStat), WNOHANG, NULL ) > 0 ) break;
X    abSleep( sectotick(1), TRUE );
X  }
X
X  /* Give it a hangup, then another two seconds to clean up */
X  if ( wait4( p->pid, &(p->termStat), WNOHANG, NULL ) == 0 ) {
X    if (DBPRO)
X      printf( "PROCESS: Hanging Up: [%08x] %s\n", (unsigned)p, p->cmnd );
X    kill( p->pid, SIGHUP );
X    for (timer=0; timerpid, &(p->termStat), WNOHANG, NULL ) > 0 ) break;
X      abSleep( sectotick(1), TRUE );
X    }
X  }
X
X  /* If it still hasnt gone, kill it */
X  if ( wait4( p->pid, &(p->termStat), WNOHANG, NULL ) == 0 ) {
X    if (DBPRO)
X      printf( "PROCESS: Killing: [%08x] %s\n", (unsigned)p, p->cmnd );
X    kill( p->pid, SIGKILL );
X  }
X
X  /* Now wait for it */
X  while ( wait4( p->pid, &(p->termStat), WNOHANG, NULL ) == 0 )
X    abSleep( 2, TRUE );;
X  if (DBPRO)
X    if (p->termStat.w_termsig == 0)
X      printf( "PROCESS: Died: [%08x] %s exit=%04x\n", 
X	     (unsigned)p, p->cmnd, p->termStat.w_retcode );
X    else
X      printf( "PROCESS: Died: [%08x] %s signal=%04x\n", 
X	     (unsigned)p, p->cmnd, p->termStat.w_termsig );
X
X  /* Mark it as not running */
X  p->status &= ~RUNP;
X}
X
Xprivate void
XDeleteProcess( p )
XPROCESS *p;
X{
X  PROCESS *q = p->nextProc;
X  PROCESS *s = procList;
X
X  /* Kill the process if its running */
X  if (p->status&RUNP) KillProcess(p);
X
X  /* Close all pipes */
X  if (p->status&FDI0) close(p->fdi[0]);
X  if (p->status&FDI1) close(p->fdi[1]);
X  if (p->status&FDO0) close(p->fdo[0]);
X  if (p->status&FDO1) close(p->fdo[1]);
X
X  /* Delete the process record */
X  free(p->cmnd); free(p);
X  if (p=procList) {
X    procList = q;
X  } else {
X    while (s->nextProc != p) s = s->nextProc;
X    s->nextProc = q;
X  }
X}
X
Xexport void
XProcCleanup()
X{
X  log( "Process cleanup" );
X
X  while (procList) {
X    log( "Terminating %s pid=%d", procList->cmnd, procList->pid );
X    DeleteProcess(procList);
X  }
X}
X
Xexport void
XProcInit()
X{
X  if (enableProcs)
X    log( "Process code $Revision: 1.6 $ ** Enabled" );
X  else
X    log( "Process code $Revision: 1.6 $ ** Disabled" );
X}
X
Xexport OSErr
XProcDelete( path )
Xchar *path;
X{
X  PROCESS *p = procList;
X
X  if (DBPRO)
X    printf( "PROCESS: Deleting: %s\n", path );
X
X  while (p) {
X    if ( ((p->status&OPEN)==0) && (strcmp(path,p->cmnd)==0) ) {
X      DeleteProcess(p);
X      return(noErr);
X    }
X    p = p->nextProc;
X  }
X  return(peNoProcess);
X}
X
Xexport OSErr
XProcOpen( path, mo, fhdl )
Xchar *path;
Xint mo;
Xint *fhdl;
X{
X  PROCESS *p = procList;
X  PROCESS *q;
X  int block = 1;
X
X  if (!enableProcs) return(peDisabled);
X
X  /* Re-attach to any matching detached processes */
X  while (p) {
X    q = p->nextProc;
X    if ( ((p->status&OPEN)==0) && (strcmp(path,p->cmnd)==0) ) {
X      CheckProcess( p );
X      if ((p->status&RUNP)==0) {
X	DeleteProcess( p );
X      } else {
X	if (DBPRO)
X	  printf( "PROCESS: Attaching: [%08x] %s\n", (unsigned)p, path );
X	p->status |= OPEN;
X	*fhdl = p->fdo[0];
X	return(noErr);
X      }
X    }
X    p = q;
X  }
X
X  /* Otherwise open a new process record */
X  if (DBPRO)
X    printf( "PROCESS: Opening: %s\n", path );
X
X  if ( access( path, X_OK ) == -1 ) return(peNoExec);
X
X  if ( !( p=(PROCESS*)malloc( sizeof(*p) ) ) ) return(peSysErr);
X
X  if ( !( p->cmnd=malloc( 1+strlen(path) ) ) ) {
X    free(p); return(peSysErr);
X  }
X
X  /* Initialize the process record with the defaults */
X  strcpy( p->cmnd, path );
X  bzero( &(p->codes[0]), 256 );
X  p->codes[DEF_EOF] = EOF_SIG;
X  p->codes[DEF_INT] = INT_SIG;
X  p->codes[DEF_KIL] = KIL_SIG;
X  p->codes[DEF_ON]  = ESC_ON;
X  p->codes[DEF_OFF] = ESC_OFF;
X  p->recSep = DEF_RS;
X  p->timeOut = DEF_TIMEOUT;
X  p->inEsc = FALSE;
X  p->escCount = 0;
X  p->status = OPEN;
X
X  /* Open the pipes */
X  if ( ( pipe(&p->fdi[0]) == -1 ) || ( pipe(&p->fdo[0]) == -1 ) ) {
X    free(p->cmnd); free(p); return(peSysErr);
X  }
X  p->status |= (FDI0|FDI1|FDO0|FDO1);
X
X  ioctl( p->fdi[1], FIONBIO, &block );
X
X  p->nextProc = procList;
X  procList = p;
X  *fhdl = p->fdo[0];
X
X  if (DBPRO)
X    printf( "PROCESS: Starting: [%08x] %s\n", (unsigned)p, p->cmnd );
X
X  if (p->pid=fork()) {
X    close(p->fdi[0]);
X    close(p->fdo[1]);
X    p->status &= ~(FDI0|FDO1);
X    p->status |= RUNP;
X  } else {
X    dup2(p->fdi[0],0);
X    dup2(p->fdo[1],1);
X    dup2(p->fdo[1],2);
X    close(p->fdi[0]);
X    close(p->fdi[1]);
X    close(p->fdo[0]);
X    close(p->fdo[1]);
X    execlp( p->cmnd, p->cmnd, (char*)0 );
X    exit(procFail);
X  }
X
X  return(noErr);
X}
X
Xexport OSErr
XProcClose( p )
XPROCESS *p;
X{
X  if (DBPRO)
X    printf( "PROCESS: Closing: [%08x]\n", (unsigned)p );
X
X  CheckProcess( p );
X  if ((p->status&RUNP)==0) DeleteProcess(p);
X  else p->status &= ~OPEN;
X
X  return(noErr);
X}
X
Xprivate OSErr
XWriteData( p, buf, len )
XPROCESS *p;
Xchar *buf;
Xint len;
X{
X  int i, count;
X
X  if (len<=0) return(noErr);
X
X  if (p->inEsc) {
X    if (len+p->escCount > ESC_SIZE) return(peESCErr);
X    bcopy( buf, &(p->escBufr[p->escCount]), len );
X    p->escCount += len;
X    return(noErr);
X  }
X
X  if (DBPRO)
X    printf( "PROCESS: Writing: [%08x] %s bytes=%d\n",
X	   (unsigned)p, p->cmnd, len );
X
X  /* Make sure that the process is available */
X  CheckProcess( p );
X  if ((p->status&OPEN)==0) return(peImpossible);
X  if ((p->status&RUNP)==0) return(peDeadProc);
X  if ((p->status&FDI1)==0) return(peShutProc);
X
X  /* Translate the new lines */
X  for (i=0; ifdi[1], buf, len );
X    if (count==len) break;
X    else if (count>0) { len-=count; buf=&buf[count]; }
X    else {
X      if (errno==EWOULDBLOCK) return(peBlock);
X      else {
X	CheckProcess( p );
X	return(peSysErr);
X      }
X    }
X  }
X  return (noErr);
X}
X
Xprivate OSErr
XSetCode( p, code, value )
XPROCESS *p;
Xint code, value;
X{
X  int i;
X
X  if ((value<-1)||(value>255)) return(peESCErr);
X  for (i=0;i<256;++i) {
X    if (p->codes[i]==code) {
X      p->codes[i]=NORMAL;
X      break;
X    }
X  }
X  if (value>=0) p->codes[value]=code;
X  return(noErr);
X}
X
Xprivate OSErr
XProcControl( p )
XPROCESS *p;
X{
X  char cmnd[ESC_SIZE+1];
X  int value, count;
X  char *s;
X
X  if (DBPRO) {
X    printf( "PROCESS: Control: [%08x] ", (unsigned)p );
X    for ( s=p->escBufr; s < &(p->escBufr[p->escCount]); ++s ) putchar(*s);
X    putchar( '\n' );
X  }
X  
X  count = sscanf( p->escBufr, " %[^= ] = %d %c", cmnd, &value, &count );
X  if (count != 2) return(peESCErr);
X
X  if      (strcmp(cmnd,"EOF")==0) return SetCode( p, EOF_SIG, value );
X  else if (strcmp(cmnd,"INT")==0) return SetCode( p, INT_SIG, value );
X  else if (strcmp(cmnd,"KIL")==0) return SetCode( p, KIL_SIG, value );
X  else if (strcmp(cmnd,"ON" )==0) return SetCode( p, ESC_ON, value );
X  else if (strcmp(cmnd,"OFF")==0) return SetCode( p, ESC_OFF, value );
X  else if (strcmp(cmnd,"TIM")==0) {
X    if ((value<0)||(value>60)) return(peESCErr);
X    else p->timeOut=value;
X  } else if (strcmp(cmnd,"IDL")==0) {
X    if ((value<0)||(value>255)) return(peESCErr);
X    else p->recSep=value;
X  }
X  else return(peESCErr);
X}
X
Xexport OSErr
XProcSignal( p, sig )
XPROCESS *p;
Xint sig;
X{
X  if (DBPRO)
X    printf( "PROCESS: Signalling: [%08x] %s signal=%d\n",
X	   (unsigned)p, p->cmnd, sig );
X
X  switch (sig) {
X  case INT_SIG:
X    if (kill( p->pid, SIGINT ) == -1) {
X      CheckProcess( p );
X      if ((p->status&RUNP)==0) return(peDeadProc);
X      else return(peSysErr);
X    }
X    break;	 
X  case KIL_SIG:
X    if (kill( p->pid, SIGKILL ) == -1) {
X      CheckProcess( p );
X      if ((p->status&RUNP)==0) return(peDeadProc);
X      else return(peSysErr);
X    }
X    break;
X  case EOF_SIG:
X    if (p->status&FDI1) {
X      close( p->fdi[1] );
X      p->status &= ~FDI1;
X    }
X    break;
X  case ESC_ON:
X    p->inEsc = TRUE;
X    p->escCount = 0;
X    break;
X  case ESC_OFF:
X    p->inEsc = FALSE;
X    p->escBufr[p->escCount] = '\0';
X    return ProcControl( p );
X  default:
X    break;
X  }
X  return (noErr);
X}
X
Xexport OSErr
XProcWrite( p, buf, len )
XPROCESS *p;
Xchar *buf;
Xint len;
X{
X  register char *s=buf, *t;
X  int result, c;
X
X  for ( t=s; t<&buf[len]; ++t ) {
X    c = p->codes[*t];
X    if ((!p->inEsc && (c!=NORMAL) && (c!=ESC_OFF)) ||
X	( p->inEsc && (c==ESC_OFF)) ) {
X      if (t>s) {
X	result = WriteData( p, s, (int)(t-s) );
X	if (result != noErr) return(result);
X      }
X      result = ProcSignal( p, c );
X      if (result != noErr) return(result);
X      s = t+1;
X    }
X  }
X  if (t>s) return WriteData( p, s, (int)(t-s) );
X  else return(noErr);
X}
X
Xexport OSErr
XProcRead( p, buf, len, rl )
XPROCESS *p;
Xchar *buf;
Xint len;
Xint *rl;
X{
X  long count;
X  int ticks;
X
X  if (DBPRO)
X    printf( "PROCESS: Reading: [%08x] %s\n", (unsigned)p, p->cmnd );
X
X  for ( count=0, ticks=2*(p->timeOut); count==0; --ticks ) {
X
X    /* Make sure that the process is available */
X    CheckProcess( p );
X    if ((p->status&FDO0)==0) return(peImpossible);
X    if ((p->status&OPEN)==0) return(peImpossible);
X
X    /* See if there is any data to read */
X    if ( ioctl( p->fdo[0], FIONREAD, &count ) == - 1 ) {
X      CheckProcess( p );
X      if ((p->status&RUNP)==0) return(peEOFErr);
X      else return(peSysErr);
X    }
X
X    /* Otherwise, check for timeout & return and record separator */
X    if (count<=0) {
X      if ((p->status&RUNP)==0) return(peEOFErr);
X      if (ticks<=0) {
X	if (DBPRO)
X	  printf( "PROCESS: Timeout: [%08x] %s\n", (unsigned)p, p->cmnd );
X	*buf = p->recSep;
X	*rl = 1;
X	return (noErr);
X      } else {
X	abSleep( (sectotick(1)/2), TRUE );
X      }
X    }
X  }
X
X  /* Otherwise, read the data */
X  count = read( p->fdo[0], buf, (count>len-1)?len-1:(int)count );
X  if (count<0) {
X    CheckProcess( p );
X    return(peSysErr);
X  }
X  buf[count++] = p->recSep;
X
X  /* Translate the new lines */
X  while (*buf) { if (*buf==INEWLINE) *buf=ENEWLINE; ++buf; }
X
X  if (DBPRO)
X    printf( "PROCESS: Read: [%08x] %s bytes=%d\n",
X	   (unsigned)p, p->cmnd, count );
X
X  *rl = count;
X  return (noErr);
X}
END_OF_afproc.c
if test 11943 -ne `wc -c afproc.h <<'END_OF_afproc.h'
X#include 
X
X/* Process name syntax */
X
X#define IsProcess(fn) (*fn=='|')
X#define NameOf(fn) (IsProcess(fn)?&fn[1]:fn)
X
X/* Process error codes */
X
X#define peInvalid aeObjectTypeErr      /* Operation not allowed on process */
X#define peNoProcess aeObjectNotFound   /* No process with this name */
X#define peNoExec aeObjectTypeErr       /* Can't execute process file */
X#define peSysErr aeMiscErr             /* System error (malloc etc) */
X#define peImpossible aeMiscErr         /* Repeated process close etc. */
X#define peShutProc aeSessClosed        /* Writing to process after EOF_SIG */
X#define peDeadProc aeSessClosed        /* Writing to dead process */
X#define peBlock aeFileBusy             /* Write would block */
X#define peEOFErr aeEOFErr              /* EOF or dead on read */
X#define peDisabled aeCallNotSupported  /* Process code not enabled */
X#define peESCErr aeParamErr            /* Illegal ESC sequence */
X
X/* Active process record */
X
X#define ESC_SIZE 32             /* Max size od ESC sequence */
X
Xtypedef struct process {
X  int pid;			/* Process ID */
X  union wait termStat;		/* Termination status */
X  int fdi[2];			/* Input pipe to process */
X  int fdo[2];			/* Output pipe from process */
X  int status;                   /* Process status (see below) */
X  char codes[256];              /* Special characters (see below) */
X  int timeOut;                  /* No. of secs to wait for data on read */
X  int escCount;                 /* No. of bytes in ESC buffer */
X  int inEsc;                    /* TRUE when reading ESC sequence */
X  char *cmnd;			/* Process command line */
X  struct process *nextProc;	/* Pointer to next process record */
X  char escBufr[ESC_SIZE+1];     /* Buffer for ESC sequences */
X  char recSep;                  /* Record separator character */
X} PROCESS;
X
X/* States in process record */
X
X#define FDI0 0x01               /* We have fdi[0] open */
X#define FDI1 0x02               /* We have fdi[1] open */
X#define FDO0 0x04               /* We have fdo[0] open */
X#define FDO1 0x08               /* We have fdo[1] open */
X#define RUNP 0x10               /* Process has been forked, but not reaped */
X#define OPEN 0x20               /* Mac has fd open on process */
X
X/* Special character interpretations  */
X
X#define NORMAL  0               /* Normal character to be written to process */
X#define EOF_SIG 1               /* Close input pipe to process */
X#define INT_SIG 2               /* Send SIGINT to process */
X#define KIL_SIG 3               /* Send SIGKILL to process */
X#define ESC_ON  4               /* Start of ESC sequence */
X#define ESC_OFF 5               /* End of ESC sequence */
X
X/* Default values */
X
X#define DEF_EOF 0x04            /* Default character for EOF_SIG */
X#define DEF_INT 0x03            /* Default character for INT_SIG */
X#define DEF_KIL 0x1c            /* Default character for KIL_SIG */
X#define DEF_RS  0x04            /* Default character for recSep */
X#define DEF_ON  0x1b            /* Default character for ESC_ON */
X#define DEF_OFF 0x0d            /* Default character for ESC_OFF */
X
X#define DEF_TIMEOUT 1           /* Default read timeout */
X#define EXIT_GRACE 2            /* Clean up time allowed before HUP */
X#define HUP_GRACE 2             /* Clean up time allowed before KILL */
X
X/* termStat values */
X
X#define procFail 0x99           /* Failed exec */
X
X
X
END_OF_afproc.h
if test 3358 -ne `wc -c hypertalk <<'END_OF_hypertalk'
X-- These Hypertalk routines show how to communicate with a remote
X-- Process which return CTRL-G as a prompt when it has finished processing
X-- a command.
X
X-- The startup process uses an XCMD "mount" to mount the aufs server
X-- volume automatically.
X
Xfunction startProcess theProcess
X  global currentProcess
X  global thePrompt
X  put the numToChar of 7 into thePrompt
X  ask "Enter password:"
X  put it into password
X  mount "", "YOUR-SERVER-NAME", "YOUR-VOLUME-NAME", "YOUR-USER-NAME", password
X  if the result is not empty then beep
X  put "Processes" & ":|" & theProcess into currentProcess
X  open file currentProcess
Xend startProcess
X
Xfunction endProcess
X  global currentProcess
X  write numToChar of 4 to file currentProcess
X  close file currentProcess
X  unmount "Processes"
Xend endProcess
X
Xfunction sendCommand theCommand
X  global currentProcess
X  write theCommand to file currentProcess
Xend sendCommand
X
Xfunction getReply
X  global thePrompt
X  global currentProcess
X  put false into done
X  put empty into reply
X  repeat until done
X    read from file currentProcess until the numToChar of 4
X    put the length of it - 1 into len
X    if len < 0 then
X      put true into done
X    else
X      if len > 0 then
X        if char len of it = thePrompt then
X          put true into done
X          put len - 1 into len
X          if len > 0 then
X            put reply & char 1 to len of it into reply
X          end if
X        else
X          put reply & char 1 to len of it into reply
X        end if
X      end if
X    end if
X  end repeat
X  return reply
Xend getReply
X
X
END_OF_hypertalk
if test 1554 -ne `wc -c