mirror of
https://github.com/PDP-10/its.git
synced 2026-02-24 16:17:26 +00:00
1141 lines
35 KiB
Common Lisp
Executable File
1141 lines
35 KiB
Common Lisp
Executable File
;;; -*- Mode: LISP; Syntax: Common-Lisp; Package: (KERMIT LISP) -*-
|
||
|
||
;;; Common Lisp KERMIT
|
||
|
||
;;; Authorship Information:
|
||
;;; Mark David (LMI) Original version, using KERMIT.C as a guide
|
||
;;; George Carrette (LMI) Various enhancements
|
||
;;; Mark Ahlstrom (Honeywell) Port to 3600 (edits marked with ";1;")
|
||
;;; Jonathan Rees (MIT) Major total rewrite for Maclisp/Common Lisp
|
||
|
||
;;; Don't make any attempt to understand this code without having access
|
||
;;; to the Kermit Protocol Manual, Frank Da Cruz, Columbia University,
|
||
;;; 5th edition.
|
||
|
||
;;; This code is written in Common Lisp, but will run in Maclisp given
|
||
;;; a Common Lisp compatibility mode (see AI: MATH; COMMON >). To
|
||
;;; port to an arbitrary CL implementation, one must supply
|
||
;;; appropriate low-level primitives for doing read-with-timeout and
|
||
;;; no-echo terminal I/O.
|
||
|
||
;;; I think the main thing making it not yet usable outside of Maclisp
|
||
;;; is that it doesn't convert CR/LF into NEWLINE on the way in and out.
|
||
|
||
;;;+ Bugs
|
||
;;; What to do about the control-C's at the end of ITS files?
|
||
;;; There's apparently no way to distinguish them from legitimate control-C's.
|
||
;;; Right now they're interpreted exactly the same as end of file.
|
||
;;;
|
||
;;; The receive packet size rightly ought to be about 93 or so, but
|
||
;;; for some reason can't RECEIVE packets bigger than about 64 bytes.
|
||
;;; For the life of me I can't figure out why. Debug this some day.
|
||
;;; [And due to a bug in Kermit-9000, I have set the packet size down
|
||
;;; to 50 (probably should be 32) to try to make SURE...]
|
||
|
||
;;; The low level 3600 hacks are undebugged!!
|
||
;;;-
|
||
|
||
#+Maclisp
|
||
(EVAL-WHEN (EVAL LOAD COMPILE)
|
||
(OR (GET 'COMMON 'LOADEDP) (LOAD '((DSK MATH) COMMON))))
|
||
|
||
(IN-PACKAGE 'KERMIT :USE '(LISP))
|
||
|
||
(SHADOW '(LOG))
|
||
|
||
(EXPORT '(SERVE
|
||
SET-LINE
|
||
GET-FILE
|
||
SEND-FILE
|
||
FINISH
|
||
BYE))
|
||
|
||
(DEFVAR *KERMIT-VERSION*
|
||
#+Maclisp (STATUS OPSYS)
|
||
#+Symbolics "Symbolics"
|
||
#-(or Maclisp Symbolics) (LISP-IMPLEMENTATION-TYPE))
|
||
|
||
#+Maclisp
|
||
(PROGN 'COMPILE
|
||
(DECLARE (*LEXPR SEND-ERROR-PACKET INIT-SERIAL-I/O))
|
||
(DECLARE (FIXNUM (SERIAL-WRITE-CHAR FIXNUM))
|
||
(FIXNUM (SERIAL-READ-CHAR))))
|
||
|
||
;;; Fundamental constants
|
||
(DEFCONSTANT *SOH* (CODE-CHAR 1) "start of header")
|
||
(DEFCONSTANT *MAX-PACKET-SIZE* (- 128. 32.) "maximum packet size")
|
||
|
||
;;; The following three values merely need to answer true to FAILP
|
||
;;; and not be legal packet types.
|
||
(DEFCONSTANT *TIMED-OUT* (CODE-CHAR 2))
|
||
(DEFCONSTANT *LENGTH-ERROR* (CODE-CHAR 3))
|
||
(DEFCONSTANT *CHECKSUM-ERROR* (CODE-CHAR 4))
|
||
(DEFCONSTANT *BOGUS-CHARACTER* (CODE-CHAR 5))
|
||
|
||
;;; If you want Kermit to be re-entrant you'll have to specbind everything
|
||
;;; from here on down, excpting the state machine variables which are
|
||
;;; bound by RUN-STATE-MACHINE.
|
||
|
||
;;; Global parameters, user-adjustable
|
||
(DEFVAR *IMAGE?* NIL "T means 8-bit mode")
|
||
(DEFVAR *DEBUG?* NIL "T means supply debugging info as you run")
|
||
(DEFVAR *VERSION-NUMBERS?* NIL "T means include version numbers in file names")
|
||
(DEFVAR *MAX-RETRY-COUNT* 10. "times to retry a packet")
|
||
(DEFVAR *MIN-TIMEOUT* 2 "minimum timeout interval in seconds")
|
||
(DEFVAR *SERVER-TIMEOUT* 30 "timeout interval when in server mode")
|
||
(DEFVAR *SERIAL-INPUT* NIL "input stream for serial line")
|
||
(DEFVAR *SERIAL-OUTPUT* NIL "output stream for serial line")
|
||
(DEFVAR *COMMENTARY-STREAM* *TERMINAL-IO* "stream for messages to user")
|
||
(DEFVAR *LOG* NIL "log file")
|
||
|
||
;;; Packet reception parameters (see KPM page 26):
|
||
(DEFVAR *RECEIVE-PACKET-SIZE* 50 "maximum packet size") ;see bug remark, above
|
||
(DEFVAR *MY-TIMEOUT* 10 "seconds after which I time out")
|
||
(DEFVAR *MY-PAD* 0 "number of padding characters I need")
|
||
(DEFVAR *MY-PAD-CHAR* #\SPACE "char I want as a padding char")
|
||
(DEFVAR *MY-EOL* #\RETURN "my kind of return char")
|
||
(DEFVAR *MY-QUOTE* #\# "my quote character")
|
||
|
||
;;; Packet transmission parameters:
|
||
(DEFVAR *SEND-PACKET-SIZE* 80 "maximum send packet size") ;MAXL
|
||
(DEFVAR *TIMEOUT* 15 "timeout for remote host on sends") ;TIME
|
||
(DEFVAR *PAD* 0 "how much padding to send") ;NPAD
|
||
(DEFVAR *PAD-CHAR* #\SPACE "padding character to send") ;PADC
|
||
(DEFVAR *EOL* #\RETURN "end-of-line character to send") ;EOL
|
||
(DEFVAR *QUOTE* #\# "quote character in outgoing data") ;QCTL
|
||
|
||
;;; Packet buffers, globally allocated for no very good reason
|
||
(DEFPARAMETER *RECEIVE-PACKET*
|
||
(MAKE-ARRAY *MAX-PACKET-SIZE* :ELEMENT-TYPE 'STRING-CHAR :FILL-POINTER 0)
|
||
"receive packet buffer")
|
||
|
||
(DEFPARAMETER *SEND-PACKET*
|
||
(MAKE-ARRAY *MAX-PACKET-SIZE* :ELEMENT-TYPE 'STRING-CHAR :FILL-POINTER 0)
|
||
"send packet buffer")
|
||
|
||
;;; State machine variables, bound by RUN-STATE-MACHINE
|
||
(DEFVAR *SERVER-MODE* NIL "true if in server mode")
|
||
(DEFVAR *FILES-TO-SEND* '() "list of source/dest filename pairs")
|
||
(DEFVAR *COMMAND* NIL "generic or other command to execute")
|
||
(DEFVAR *SEQUENCE-NUMBER* 0 "the packet number")
|
||
(DEFVAR *RETRY-COUNT* 0 "times this packet retried")
|
||
(DEFVAR *STREAM* NIL "file pointer for current disk file")
|
||
(DEFVAR *DISCARD* NIL "T if current file is to be discarded")
|
||
|
||
|
||
;;; Utility routines & macros
|
||
|
||
#-(OR Maclisp Symbolics) ;COMMON > defines this for Maclisp
|
||
(DEFMACRO DEFSUBST (NAME ARGS &BODY BODY)
|
||
`(PROGN 'COMPILE
|
||
(DEFUN ,NAME ,ARGS ,@BODY)
|
||
(PROCLAIM '(INLINE ,NAME))))
|
||
|
||
#+Symbolics
|
||
(DEFMACRO DEFSUBST (NAME ARGS &BODY BODY)
|
||
`(SCL:DEFSUBST ,NAME ,ARGS ,@BODY))
|
||
|
||
#-Maclisp
|
||
(PROGN 'COMPILE
|
||
|
||
(DEFSUBST STRING-ELT (S I) ;Not the same as CHAR!
|
||
(AREF (THE STRING S) I))
|
||
|
||
(DEFSUBST SET-STRING-ELT (S I C)
|
||
(SETF (AREF (THE STRING S) I) C))
|
||
|
||
(DEFMACRO UNKLUDGIFY-STRING (S)
|
||
S)
|
||
|
||
(DEFUN QUIT () NIL)
|
||
) ;psilcaM-#
|
||
|
||
|
||
(DEFMACRO MESSAGE (&REST CRUFT)
|
||
`(IF (OR (NOT *SERVER-MODE*) *DEBUG?* *LOG*)
|
||
(FORMAT *COMMENTARY-STREAM* ,@CRUFT)))
|
||
|
||
(DEFMACRO DEBUG-MESSAGE (&REST CRUFT)
|
||
`(IF *DEBUG?*
|
||
(FORMAT *DEBUG-IO* ,@CRUFT)))
|
||
|
||
(DEFMACRO STRING-APPEND (&REST STRINGS)
|
||
`(CONCATENATE 'STRING ,@STRINGS))
|
||
|
||
#-Maclisp
|
||
(PROGN 'COMPILE
|
||
;;; Character-set-independent ASCII character conversion
|
||
|
||
(DEFPARAMETER ASCII-CHARS
|
||
(CONCATENATE 'VECTOR
|
||
'(#\NULL)
|
||
(MAKE-LIST 7)
|
||
'(#\BACKSPACE #\TAB #\NEWLINE NIL #\PAGE #\RETURN)
|
||
(MAKE-LIST 18)
|
||
" !\"#$%&'()*+,-./0123456789:;<=>?"
|
||
"@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_"
|
||
"`abcdefghijklmnopqrstuvwxyz{|}~"
|
||
'(#\RUBOUT)))
|
||
|
||
(DEFUN ASCII->CHAR (N)
|
||
(OR (SVREF ASCII-CHARS N)
|
||
(CODE-CHAR N)))
|
||
|
||
(DEFPARAMETER NATIVE-CHARS
|
||
(LET ((TABLE (MAKE-HASH-TABLE :TEST 'EQUAL :SIZE 128))
|
||
(LEAST 10000000)
|
||
(GREATEST -10000000))
|
||
(DO ((I 0 (+ I 1)))
|
||
((>= I (LENGTH ASCII-CHARS))
|
||
(LET ((V (MAKE-ARRAY (+ (- GREATEST LEAST) 1))))
|
||
(DO ((I LEAST (+ I 1)))
|
||
((> I GREATEST) (CONS LEAST V))
|
||
(SETF (SVREF V (- I LEAST)) (GETHASH I TABLE)))))
|
||
(LET ((CH (SVREF ASCII-CHARS I)))
|
||
(WHEN CH
|
||
(LET ((CODE (CHAR-CODE CH)))
|
||
(SETQ LEAST (MIN LEAST CODE))
|
||
(SETQ GREATEST (MAX GREATEST CODE))
|
||
(SETF (GETHASH CODE TABLE) I)))))))
|
||
|
||
(DEFUN CHAR->ASCII (CHAR)
|
||
(LET ((INDEX (- (CHAR-CODE CHAR) (CAR NATIVE-CHARS))))
|
||
(IF (OR (< INDEX 0)
|
||
(>= INDEX (LENGTH (CDR NATIVE-CHARS))))
|
||
(CHAR-CODE CHAR)
|
||
(OR (SVREF (CDR NATIVE-CHARS) INDEX)
|
||
(CHAR-CODE CHAR)))))
|
||
)
|
||
|
||
#+Maclisp
|
||
(PROGN 'COMPILE
|
||
(DEFMACRO CHAR->ASCII (C) C)
|
||
(DEFMACRO ASCII->CHAR (N) N)
|
||
)
|
||
|
||
;;; INTEGER->GRAPHIC: converts a number to a graphic character by adding
|
||
;;; 32 and deasciizing.
|
||
;;; GRAPHIC->INTEGER: undoes INTEGER->GRAPHIC.
|
||
;;; CTL: converts between control characters and printable characters by
|
||
;;; toggling the control bit (i.e. ^a becomes a and a becomes ^a).
|
||
|
||
(DEFSUBST INTEGER->GRAPHIC (N)
|
||
(ASCII->CHAR (+ N 32.)))
|
||
|
||
(DEFSUBST GRAPHIC->INTEGER (CH)
|
||
(- (CHAR->ASCII CH) 32.))
|
||
|
||
(DEFSUBST CTL (CH)
|
||
(ASCII->CHAR (LOGXOR (CHAR->ASCII CH) #o100)))
|
||
|
||
(DEFSUBST COMPUTE-FINAL-CHECKSUM (CHKSUM)
|
||
(LOGAND (+ (ASH (LOGAND CHKSUM #o0300) -6) CHKSUM) #o077))
|
||
|
||
;;; ACKP, NACKP, ERRP, FAILP
|
||
;;; Predicates applied to packet type returned by RECEIVE-PACKET:
|
||
;;; an ACK (Y), a NACK (N), an error message (E), or a failed
|
||
;;; packet transmission (timeout, bad checksum, or illegal length).
|
||
|
||
(DEFSUBST ACKP (TYPE)
|
||
(CHAR= TYPE #\Y))
|
||
|
||
(DEFSUBST NACKP (TYPE)
|
||
(CHAR= TYPE #\N))
|
||
|
||
(DEFSUBST ERRP (TYPE)
|
||
(CHAR= TYPE #\E))
|
||
|
||
(DEFSUBST FAILP (TYPE)
|
||
(CHAR< TYPE #\SPACE))
|
||
|
||
;;; Top-level entry routines
|
||
|
||
;;; Specify a terminal line [e.g. (SET-LINE "TTA1:")]:
|
||
|
||
(DEFUN SET-LINE (LINE)
|
||
(INIT-SERIAL-I/O LINE))
|
||
|
||
;;; Server mode:
|
||
|
||
(DEFUN SERVE () ;See LIBDOC; TTY >
|
||
(FORMAT *COMMENTARY-STREAM*
|
||
"~&Entering KERMIT server mode.~
|
||
~%Control-G throws to Lisp top level, (SERVE) starts serving again.~%")
|
||
;; Kludge!!! Logging isn't done right at all. Ought to use broadcast stream
|
||
;; so that logging works even when not in server mode.
|
||
(IF *LOG*
|
||
(FORMAT *LOG* "~2&----- Entering server mode -----~&"))
|
||
(UNWIND-PROTECT
|
||
(LET ((*COMMENTARY-STREAM* (OR *LOG* *COMMENTARY-STREAM*))
|
||
(*DEBUG-IO* (OR *LOG* *DEBUG-IO*)))
|
||
(LET ((STATUS (RUN-STATE-MACHINE #'RECEIVE-SERVER-IDLE T '() NIL)))
|
||
(IF (EQ STATUS 'LOGOUT) (QUIT) STATUS)))
|
||
(IF *LOG* (FORMAT *LOG* "~&----- Leaving server mode -----~&"))))
|
||
|
||
;;; Send a file:
|
||
|
||
(DEFUN SEND-FILE (LOCAL-FILESPEC &OPTIONAL REMOTE-FILESPEC)
|
||
(LET ((XFER-LIST (MAKE-TRANSFER-LIST LOCAL-FILESPEC REMOTE-FILESPEC)))
|
||
(COND ((NULL XFER-LIST)
|
||
(MESSAGE "~&No files match this specification: ~S" LOCAL-FILESPEC))
|
||
(T
|
||
(RUN-STATE-MACHINE #'SEND-INIT NIL XFER-LIST NIL)))))
|
||
|
||
;;; Get a file:
|
||
|
||
(DEFUN GET-FILE (REMOTE-SPEC &OPTIONAL LOCAL-SPEC)
|
||
(RUN-STATE-MACHINE #'SEND-MISC
|
||
NIL
|
||
'()
|
||
`(GET ,REMOTE-SPEC ,LOCAL-SPEC)))
|
||
|
||
;;; Receive file: useless, but included for completeness.
|
||
|
||
(DEFUN RECEIVE-FILE (&OPTIONAL LOCAL-SPEC)
|
||
(RUN-STATE-MACHINE #'RECEIVE-INIT
|
||
NIL
|
||
'()
|
||
`(GET NIL ,LOCAL-SPEC)))
|
||
|
||
;;; Tell remote server to go away:
|
||
|
||
(DEFUN FINISH ()
|
||
(RUN-STATE-MACHINE #'SEND-MISC NIL '() '(FINISH)))
|
||
|
||
(DEFUN BYE ()
|
||
(RUN-STATE-MACHINE #'SEND-MISC NIL '() '(BYE)))
|
||
|
||
(DEFUN LOG (&OPTIONAL PATH)
|
||
(COND ((NULL PATH)
|
||
(COND ((NULL *LOG*)
|
||
(FORMAT T "~&There is no log file to close."))
|
||
(T
|
||
(FORMAT T "~&Closing log file ~s" (TRUENAME *LOG*))
|
||
(LISP:CLOSE *LOG*)
|
||
(SETQ *LOG* NIL))))
|
||
((NULL *LOG*)
|
||
(SETQ *LOG* (LISP:OPEN PATH :DIRECTION :OUTPUT))
|
||
(FORMAT T "~&Logging on ~s" (TRUENAME *LOG*)))))
|
||
|
||
;;; The KERMIT state machine
|
||
|
||
(DEFUN RUN-STATE-MACHINE (STATE *SERVER-MODE*
|
||
*FILES-TO-SEND*
|
||
*COMMAND*)
|
||
(IF (NULL *SERIAL-INPUT*) (INIT-SERIAL-I/O))
|
||
(LET ((TEMP (SERIAL-STATE)))
|
||
(UNWIND-PROTECT
|
||
(PROGN (DISABLE-ECHOING TEMP)
|
||
(FLUSH-INPUT)
|
||
(LISP:CATCH 'DONE
|
||
(DO ()
|
||
(NIL)
|
||
(LISP:CATCH 'ABORT
|
||
(UNWIND-PROTECT
|
||
(LET ((*SEQUENCE-NUMBER* 0)
|
||
(*RETRY-COUNT* 0)
|
||
(*DISCARD* NIL)
|
||
(*TIMEOUT* *TIMEOUT*))
|
||
(DO ((STATE STATE (FUNCALL STATE)))
|
||
(NIL)
|
||
;; (DEBUG-MESSAGE "~&State = ~S~&" STATE)
|
||
))
|
||
(COND (*STREAM*
|
||
(LISP:CLOSE *STREAM* :ABORT T)
|
||
(SETQ *STREAM* NIL)))))
|
||
(IF (NOT *SERVER-MODE*) (RETURN 'ABORTED)))))
|
||
(SET-SERIAL-STATE TEMP))))
|
||
|
||
(DEFMACRO DEFSTATE (NAME &REST BODY)
|
||
`(DEFUN ,NAME () ,@BODY))
|
||
|
||
;(DEFMACRO GOTO-STATE (STATE)
|
||
; `#',STATE)
|
||
(defmacro goto-state (state)
|
||
`(%goto-state #',state))
|
||
(defun %goto-state (state)
|
||
(if (null state) (error "null state") state))
|
||
|
||
(DEFUN ABORT ()
|
||
(DEBUG-MESSAGE "~&Aborting.")
|
||
(LISP:THROW 'ABORT NIL))
|
||
|
||
(DEFUN COMPLETE ()
|
||
(IF *SERVER-MODE*
|
||
(GOTO-STATE RECEIVE-SERVER-IDLE)
|
||
(LISP:THROW 'DONE 'COMPLETE)))
|
||
|
||
(DEFSUBST NEXT-SEQUENCE-NUMBER ()
|
||
(LOGAND (1+ *SEQUENCE-NUMBER*) #o77))
|
||
|
||
(DEFSUBST PREVIOUS-SEQUENCE-NUMBER ()
|
||
(LOGAND (+ *SEQUENCE-NUMBER* #o77) #o77))
|
||
|
||
(DEFUN BUMP-SEQUENCE-NUMBER ()
|
||
(SETQ *SEQUENCE-NUMBER* (NEXT-SEQUENCE-NUMBER))
|
||
(SETQ *RETRY-COUNT* 0))
|
||
|
||
(DEFUN BUMP-RETRY-COUNT ()
|
||
(INCF *RETRY-COUNT*)
|
||
(COND ((> *RETRY-COUNT* *MAX-RETRY-COUNT*)
|
||
(SEND-ERROR-PACKET "aborting after ~D retries" *MAX-RETRY-COUNT*)
|
||
(ABORT))))
|
||
|
||
;;; Rec_Server_Idle -- Server idle, waiting for a message
|
||
|
||
(DEFSTATE RECEIVE-SERVER-IDLE
|
||
(SETQ *SEQUENCE-NUMBER* 0)
|
||
(SETQ *TIMEOUT* *SERVER-TIMEOUT*) ;will get reset later
|
||
(MULTIPLE-VALUE-BIND (TYPE NUM DATA)
|
||
(RECEIVE-PACKET)
|
||
(COND ((FAILP TYPE)
|
||
(SEND-NACK 0)
|
||
(GOTO-STATE RECEIVE-SERVER-IDLE))
|
||
((ERRP TYPE)
|
||
;; Error has already been reported, so don't worry about it.
|
||
(GOTO-STATE RECEIVE-SERVER-IDLE))
|
||
((NOT (= NUM 0))
|
||
(SEND-ERROR-PACKET "bad sequence number - ~S" NUM)
|
||
(GOTO-STATE RECEIVE-SERVER-IDLE))
|
||
((CHAR= TYPE #\I)
|
||
;; Initialize & send ACK
|
||
(INITIALIZE-PARAMETERS DATA)
|
||
(GOTO-STATE RECEIVE-SERVER-IDLE))
|
||
((CHAR= TYPE #\S)
|
||
;; Remote host wants to send a file; we should prepare to receive.
|
||
(INITIALIZE-PARAMETERS DATA)
|
||
(BUMP-SEQUENCE-NUMBER)
|
||
(SETQ *COMMAND* '(GET NIL NIL))
|
||
(GOTO-STATE RECEIVE-FILE-HEADER))
|
||
((CHAR= TYPE #\R)
|
||
;; Remote host wants to receive a file, i.e. we should send it.
|
||
(SETQ *FILES-TO-SEND*
|
||
(MAKE-TRANSFER-LIST (UNKLUDGIFY-STRING DATA) NIL))
|
||
(COND ((NULL *FILES-TO-SEND*)
|
||
(SEND-ERROR-PACKET "no files matching this specification")
|
||
(GOTO-STATE RECEIVE-SERVER-IDLE))
|
||
(T
|
||
(DEBUG-MESSAGE "~&Files to send: ~S" *FILES-TO-SEND*)
|
||
(GOTO-STATE SEND-INIT))))
|
||
((CHAR= TYPE #\G)
|
||
(COND ((< (LISP:LENGTH DATA) 1)
|
||
(SEND-ERROR-PACKET "ill-formed generic command packet")
|
||
(GOTO-STATE RECEIVE-SERVER-IDLE))
|
||
(T
|
||
(PERFORM-GENERIC-COMMAND DATA))))
|
||
(T
|
||
(SEND-ERROR-PACKET "unimplemented server command - ~C" TYPE)
|
||
(GOTO-STATE RECEIVE-SERVER-IDLE)))))
|
||
|
||
(DEFUN PERFORM-GENERIC-COMMAND (DATA)
|
||
(CASE (STRING-ELT DATA 0)
|
||
((#\F) ;Finished
|
||
(SEND-ACK *SEQUENCE-NUMBER*)
|
||
(LISP:THROW 'DONE 'FINISHED))
|
||
((#\L) ;Logout
|
||
(SEND-ACK *SEQUENCE-NUMBER*)
|
||
(LISP:THROW 'DONE 'LOGOUT))
|
||
((#\C) ;Change working directory
|
||
;; Data looks something like
|
||
;; C$directory#password (I think)
|
||
;; where $ is the length of the directory name (! = 1, etc.)
|
||
;; and # is the length of the password.
|
||
;; This has only been tested with Kermit-32 (on VMS), and I haven't
|
||
;; checked the protocol manual to see what's really supposed to
|
||
;; happen. -- JAR 3 March 1988
|
||
(IF (> (LISP:LENGTH DATA) 3)
|
||
(LET ((END (MIN (LISP:LENGTH DATA)
|
||
(+ 2 (GRAPHIC->INTEGER (AREF DATA 1))))))
|
||
(DO ((I 2 (1+ I))
|
||
(L '() (CONS (AREF DATA I) L)))
|
||
((>= I END)
|
||
(LET ((DIR (COERCE (REVERSE L) 'STRING)))
|
||
(MESSAGE "~&Directory ~S" DIR)
|
||
(SETQ *DEFAULT-PATHNAME-DEFAULTS* (PATHNAME DIR))
|
||
(SEND-ACK 0)))))
|
||
(SEND-ERROR-PACKET "missing directory name or ill-formed packet - ~A"
|
||
(UNKLUDGIFY-STRING DATA)))
|
||
(GOTO-STATE RECEIVE-SERVER-IDLE))
|
||
;; Unimplemented: Directory, diskUsage,
|
||
;; dEletefile, Type, Rename, Kopy, Whoisloggedin,
|
||
;; sendMessage, runProgram, Journal, Variable
|
||
(T
|
||
(SEND-ERROR-PACKET "unimplemented generic command - ~A"
|
||
(UNKLUDGIFY-STRING DATA))
|
||
(GOTO-STATE RECEIVE-SERVER-IDLE))))
|
||
|
||
;;; Rec_Init - entry point for non-server RECEIVE command (pretty useless)
|
||
;;; *COMMAND* should be (GET remote local)
|
||
|
||
(DEFSTATE RECEIVE-INIT
|
||
(SETQ *SEQUENCE-NUMBER* 0)
|
||
(MULTIPLE-VALUE-BIND (TYPE NUM DATA)
|
||
(RECEIVE-PACKET)
|
||
(COND ((AND (CHAR= TYPE #\S) (= NUM 0))
|
||
(INITIALIZE-PARAMETERS DATA)
|
||
(BUMP-SEQUENCE-NUMBER)
|
||
(GOTO-STATE RECEIVE-FILE-HEADER))
|
||
((FAILP TYPE)
|
||
(SEND-NACK 0)
|
||
(BUMP-RETRY-COUNT)
|
||
(GOTO-STATE RECEIVE-INIT))
|
||
(T
|
||
(SEND-NACK 0)
|
||
(ABORT)))))
|
||
|
||
;;; Rec_File -- look for a file header or EOT message
|
||
;;; *COMMAND* should be (GET remote local)
|
||
|
||
(DEFSTATE RECEIVE-FILE-HEADER
|
||
(MULTIPLE-VALUE-BIND (TYPE NUM PACKET)
|
||
(RECEIVE-PACKET)
|
||
(COND ((AND (CHAR= TYPE #\F) (= NUM *SEQUENCE-NUMBER*))
|
||
;; File header; ignore it if a local name was specified.
|
||
(LET* ((REMOTE (UNKLUDGIFY-STRING PACKET))
|
||
(LOCAL (OR (CADDR *COMMAND*) (CADR *COMMAND*)))
|
||
(LOCAL (IF LOCAL
|
||
(MERGE-PATHNAMES (FLUSH-WILDCARDS LOCAL) REMOTE)
|
||
REMOTE)))
|
||
(COND ((SETQ *STREAM* (MAYBE-OPEN-FOR-OUTPUT LOCAL))
|
||
(MESSAGE "~&Receiving ~A as ~A"
|
||
REMOTE
|
||
(LISP:NAMESTRING LOCAL))
|
||
(SEND-ACK NUM)
|
||
(BUMP-SEQUENCE-NUMBER)
|
||
(GOTO-STATE RECEIVE-DATA))
|
||
(T
|
||
(MESSAGE "~&Can't create ~S" LOCAL)
|
||
(SEND-ERROR-PACKET "Can't create ~S" LOCAL)
|
||
(ABORT)))))
|
||
((AND (CHAR= TYPE #\B) (= NUM *SEQUENCE-NUMBER*))
|
||
;; End of transmission
|
||
(SEND-ACK NUM)
|
||
(COMPLETE))
|
||
((AND (CHAR= TYPE #\S) (= NUM (PREVIOUS-SEQUENCE-NUMBER)))
|
||
;; Send init
|
||
(SEND-PARAMETERS #\Y NUM)
|
||
(BUMP-RETRY-COUNT)
|
||
(GOTO-STATE RECEIVE-FILE-HEADER))
|
||
((AND (CHAR= TYPE #\Z) (= NUM (PREVIOUS-SEQUENCE-NUMBER)))
|
||
;; End of file
|
||
(SEND-ACK NUM)
|
||
(BUMP-RETRY-COUNT)
|
||
(GOTO-STATE RECEIVE-FILE-HEADER))
|
||
((FAILP TYPE)
|
||
;; Timeout
|
||
(SEND-NACK *SEQUENCE-NUMBER*)
|
||
(GOTO-STATE RECEIVE-FILE-HEADER))
|
||
(T
|
||
(SEND-ERROR-PACKET "error receiving file header")
|
||
(ABORT)))))
|
||
|
||
;;; Rec_Data - Receive data up to end of file
|
||
|
||
(DEFSTATE RECEIVE-DATA
|
||
(MULTIPLE-VALUE-BIND (TYPE NUM DATA)
|
||
(RECEIVE-PACKET)
|
||
(COND ((AND (CHAR= TYPE #\D) (= NUM *SEQUENCE-NUMBER*))
|
||
(EMPTY-BUFFER DATA)
|
||
(SEND-ACK NUM)
|
||
(BUMP-SEQUENCE-NUMBER)
|
||
(GOTO-STATE RECEIVE-DATA))
|
||
((AND (OR (CHAR= TYPE #\D) (CHAR= TYPE #\F))
|
||
(= NUM (PREVIOUS-SEQUENCE-NUMBER)))
|
||
(SEND-ACK NUM)
|
||
(BUMP-RETRY-COUNT)
|
||
(GOTO-STATE RECEIVE-DATA))
|
||
((AND (CHAR= TYPE #\Z) (= NUM *SEQUENCE-NUMBER*))
|
||
(LET (#+Maclisp
|
||
(IO-LOSSAGE #'(LAMBDA IGNORE
|
||
(SEND-ERROR-PACKET "error closing file")
|
||
(ABORT))))
|
||
(COND ((OR (< (LISP:LENGTH DATA) 1)
|
||
(NOT (CHAR= (STRING-ELT DATA 0) #\D)))
|
||
(MESSAGE "~&File received successfully: ~A"
|
||
(LISP:NAMESTRING *STREAM*))
|
||
(LISP:CLOSE *STREAM*))
|
||
(T
|
||
;; Discard
|
||
(MESSAGE "~&File discarded: ~A"
|
||
(LISP:NAMESTRING *STREAM*))
|
||
(LISP:CLOSE *STREAM* :ABORT T)))
|
||
(SEND-ACK NUM)
|
||
(SETQ *STREAM* NIL)
|
||
(BUMP-SEQUENCE-NUMBER)
|
||
(GOTO-STATE RECEIVE-FILE-HEADER)))
|
||
((FAILP TYPE)
|
||
(SEND-NACK *SEQUENCE-NUMBER*)
|
||
(BUMP-RETRY-COUNT)
|
||
(GOTO-STATE RECEIVE-DATA))
|
||
(T
|
||
(IF (NOT (ERRP TYPE))
|
||
(SEND-ERROR-PACKET "error during data reception"))
|
||
(ABORT)))))
|
||
|
||
|
||
;;; Send_Init - entry for SEND command (or remote kermit's GET)
|
||
|
||
(DEFSTATE SEND-INIT
|
||
(SETQ *SEQUENCE-NUMBER* 0)
|
||
(SEND-PARAMETERS #\S 0)
|
||
(MULTIPLE-VALUE-BIND (REPLY NUM PACKET)
|
||
(RECEIVE-PACKET)
|
||
(COND ((AND (ACKP REPLY) (= *SEQUENCE-NUMBER* NUM))
|
||
(DECODE-PARAMETERS PACKET) ;Check and set defaults
|
||
(BUMP-SEQUENCE-NUMBER)
|
||
(GOTO-STATE OPEN-FILE))
|
||
((ERRP REPLY)
|
||
(ABORT))
|
||
(T
|
||
(GOTO-STATE SEND-INIT)))))
|
||
|
||
;;; Open_File
|
||
|
||
(DEFSTATE OPEN-FILE
|
||
(COND ((NULL *FILES-TO-SEND*)
|
||
(GOTO-STATE SEND-BREAK))
|
||
(T
|
||
(LET ((LOCAL-FILENAME (CAAR *FILES-TO-SEND*)))
|
||
(COND ((NOT (SETQ *STREAM* (MAYBE-OPEN-FOR-INPUT LOCAL-FILENAME)))
|
||
;; This ain't right, but what's to do for it??
|
||
(MESSAGE "~&Can't open file ~A" LOCAL-FILENAME)
|
||
(POP *FILES-TO-SEND*)
|
||
(SETQ *DISCARD* T)
|
||
(GOTO-STATE SEND-EOF))
|
||
(T
|
||
(GOTO-STATE SEND-FILE-HEADER)))))))
|
||
|
||
;;; Send_File
|
||
|
||
(DEFUN NORMAL-ACKP (TYPE NUM)
|
||
(OR (AND (ACKP TYPE) (= NUM *SEQUENCE-NUMBER*))
|
||
(AND (NACKP TYPE) (= NUM (NEXT-SEQUENCE-NUMBER)))))
|
||
|
||
(DEFUN NORMAL-NACKP (TYPE)
|
||
(OR (FAILP TYPE)
|
||
(NACKP TYPE)))
|
||
|
||
(DEFSTATE SEND-FILE-HEADER
|
||
(SEND-PACKET #\F *SEQUENCE-NUMBER*
|
||
(KERMITIFY-PATHNAME (CDAR *FILES-TO-SEND*)
|
||
*VERSION-NUMBERS?*))
|
||
(MULTIPLE-VALUE-BIND (REPLY NUM IGNORE)
|
||
(RECEIVE-PACKET)
|
||
IGNORE
|
||
(COND ((NORMAL-ACKP REPLY NUM)
|
||
(BUMP-SEQUENCE-NUMBER)
|
||
(GOTO-STATE SEND-DATA))
|
||
((NORMAL-NACKP REPLY)
|
||
(BUMP-RETRY-COUNT)
|
||
(GOTO-STATE SEND-FILE-HEADER))
|
||
(T
|
||
(IF (NOT (ERRP REPLY))
|
||
(SEND-ERROR-PACKET "error sending file header"))
|
||
(ABORT)))))
|
||
|
||
;;; Send_Data -- Send contents of file
|
||
|
||
(DEFSTATE SEND-DATA
|
||
(IF (= *RETRY-COUNT* 0)
|
||
(FILL-BUFFER *SEND-PACKET*))
|
||
(COND ((= (LISP:LENGTH *SEND-PACKET*) 0)
|
||
(GOTO-STATE SEND-EOF))
|
||
(T
|
||
(SEND-PACKET #\D *SEQUENCE-NUMBER* *SEND-PACKET*)
|
||
(MULTIPLE-VALUE-BIND (REPLY NUM PACKET)
|
||
(RECEIVE-PACKET)
|
||
(COND ((NORMAL-ACKP REPLY NUM)
|
||
(BUMP-SEQUENCE-NUMBER)
|
||
(COND ((AND (> (LISP:LENGTH PACKET) 0)
|
||
(OR (CHAR= (STRING-ELT PACKET 0) #\X)
|
||
(CHAR= (STRING-ELT PACKET 0) #\Z)))
|
||
(SETQ *DISCARD* T)
|
||
(GOTO-STATE SEND-EOF))
|
||
(T
|
||
(GOTO-STATE SEND-DATA))))
|
||
((NORMAL-NACKP REPLY)
|
||
(BUMP-RETRY-COUNT)
|
||
(GOTO-STATE SEND-DATA))
|
||
(T
|
||
(IF (NOT (ERRP REPLY))
|
||
(SEND-ERROR-PACKET "error during data transmission"))
|
||
(ABORT)))))))
|
||
|
||
;;; Send_EOF -- Send end of file indicator
|
||
|
||
(DEFSTATE SEND-EOF
|
||
(SEND-PACKET #\Z *SEQUENCE-NUMBER* (IF *DISCARD* "Z" NIL))
|
||
(MULTIPLE-VALUE-BIND (REPLY NUM IGNORE)
|
||
(RECEIVE-PACKET)
|
||
IGNORE
|
||
(COND ((NORMAL-ACKP REPLY NUM)
|
||
(MESSAGE "~&File sent successfully: ~A~%"
|
||
(TRUENAME *STREAM*))
|
||
(CLOSE *STREAM*)
|
||
(SETQ *STREAM* NIL)
|
||
(POP *FILES-TO-SEND*)
|
||
(BUMP-SEQUENCE-NUMBER)
|
||
(SETQ *DISCARD* NIL)
|
||
(GOTO-STATE OPEN-FILE))
|
||
((NORMAL-NACKP REPLY)
|
||
(GOTO-STATE SEND-EOF))
|
||
(T
|
||
(SETQ *DISCARD* NIL)
|
||
(IF (NOT (ERRP REPLY))
|
||
(SEND-ERROR-PACKET "error during EOF transmission"))
|
||
(ABORT)))))
|
||
|
||
;;; Send_Break - End of transaction
|
||
|
||
(DEFSTATE SEND-BREAK
|
||
(SEND-PACKET #\B *SEQUENCE-NUMBER* NIL)
|
||
(MULTIPLE-VALUE-BIND (REPLY NUM IGNORE)
|
||
(RECEIVE-PACKET)
|
||
IGNORE
|
||
(COND ((OR (AND (ACKP REPLY) (= NUM *SEQUENCE-NUMBER*))
|
||
(AND (NACKP REPLY) (= NUM 0)))
|
||
(COMPLETE))
|
||
((OR (AND (NACKP REPLY) (= NUM *SEQUENCE-NUMBER*))
|
||
(FAILP REPLY))
|
||
(GOTO-STATE SEND-BREAK))
|
||
(T
|
||
(IF (NOT (ERRP REPLY))
|
||
(SEND-ERROR-PACKET "error during break transmission"))
|
||
(ABORT)))))
|
||
|
||
;;; Send_Gen_Cmd - Command to server
|
||
|
||
(DEFSTATE SEND-MISC
|
||
(CASE (CAR *COMMAND*)
|
||
((FINISH) (SEND-PACKET #\G 0 "F"))
|
||
((LOGOUT) (SEND-PACKET #\G 0 "L"))
|
||
((GET)
|
||
(SEND-PACKET #\R 0 (KERMITIFY-PATHNAME (CADR *COMMAND*)
|
||
*VERSION-NUMBERS?*)))
|
||
(OTHERWISE (ERROR "weird command in SEND-MISC" *COMMAND* 'FAIL-ACT)))
|
||
(MULTIPLE-VALUE-BIND (TYPE NUM DATA)
|
||
(RECEIVE-PACKET)
|
||
(COND ((AND (ACKP TYPE) (= NUM 0))
|
||
(MESSAGE "~&OK")
|
||
(COMPLETE))
|
||
((AND (CHAR= TYPE #\S) (= NUM 0) (>= (LISP:LENGTH DATA) 6))
|
||
(DECODE-PARAMETERS DATA)
|
||
(SEND-PARAMETERS #\Y 0)
|
||
(BUMP-SEQUENCE-NUMBER)
|
||
(GOTO-STATE RECEIVE-FILE-HEADER))
|
||
((NORMAL-NACKP TYPE)
|
||
(BUMP-RETRY-COUNT)
|
||
(GOTO-STATE SEND-MISC))
|
||
(T
|
||
(IF (NOT (ERRP TYPE))
|
||
(SEND-ERROR-PACKET "error during command transmission"))
|
||
(ABORT)))))
|
||
|
||
;;; Packet sending and receiving
|
||
|
||
(DEFUN SEND-ERROR-PACKET (&REST CRUFT)
|
||
(LET* ((FROB (LISP:APPLY #'FORMAT NIL CRUFT))
|
||
(FROB (FORMAT NIL "Kermit/~A: ~A" *KERMIT-VERSION* FROB)))
|
||
(DEBUG-MESSAGE "~&Sending error packet: ~A" FROB)
|
||
(SEND-PACKET #\E *SEQUENCE-NUMBER* FROB)))
|
||
|
||
(DEFUN SEND-ACK (NUM)
|
||
(SEND-PACKET #\Y NUM NIL))
|
||
|
||
(DEFUN SEND-NACK (NUM)
|
||
(SEND-PACKET #\N NUM NIL))
|
||
|
||
;;; Remember remote parameters and ackowledge with ours
|
||
|
||
(DEFUN INITIALIZE-PARAMETERS (DATA)
|
||
(DECODE-PARAMETERS DATA)
|
||
(SEND-PARAMETERS #\Y *SEQUENCE-NUMBER*))
|
||
|
||
(DEFUN DECODE-PARAMETERS (DATA)
|
||
(SETQ *SEND-PACKET-SIZE* (GRAPHIC->INTEGER (STRING-ELT DATA 0)))
|
||
(SETQ *TIMEOUT* (GRAPHIC->INTEGER (STRING-ELT DATA 1)))
|
||
(SETQ *PAD* (GRAPHIC->INTEGER (STRING-ELT DATA 2)))
|
||
(SETQ *PAD-CHAR* (CTL (STRING-ELT DATA 3)))
|
||
(SETQ *EOL* (ASCII->CHAR (GRAPHIC->INTEGER (STRING-ELT DATA 4))))
|
||
(SETQ *QUOTE* (STRING-ELT DATA 5)))
|
||
|
||
(DEFUN SEND-PARAMETERS (TYPE NUM)
|
||
(LET ((PACKET *SEND-PACKET*))
|
||
(SET-STRING-ELT PACKET 0 (INTEGER->GRAPHIC *RECEIVE-PACKET-SIZE*))
|
||
(SET-STRING-ELT PACKET 1 (INTEGER->GRAPHIC *MY-TIMEOUT*))
|
||
(SET-STRING-ELT PACKET 2 (INTEGER->GRAPHIC *MY-PAD*))
|
||
(SET-STRING-ELT PACKET 3 (CTL *MY-PAD-CHAR*))
|
||
(SET-STRING-ELT PACKET 4 (INTEGER->GRAPHIC (CHAR->ASCII *MY-EOL*)))
|
||
(SET-STRING-ELT PACKET 5 *MY-QUOTE*)
|
||
(SETF (FILL-POINTER PACKET) 6)
|
||
(SEND-PACKET TYPE NUM PACKET)))
|
||
|
||
|
||
;;; SEND-PACKET
|
||
;;; TYPE -- a number, the type of packet this is.
|
||
;;; NUM -- a number, the packet-number of this packet.
|
||
;;; DATA -- a string, i.e. an art-string type of array, the data of this pkt.
|
||
|
||
(DEFUN SEND-PACKET (TYPE NUM DATA)
|
||
(LET ((CHKSUM 0)
|
||
(LEN (IF DATA (LISP:LENGTH DATA) 0)))
|
||
(DECLARE (FIXNUM CHKSUM LEN))
|
||
(DEBUG-MESSAGE "~&Sending packet: ")
|
||
(DO ((I 0 (1+ I)))
|
||
((>= I *PAD*))
|
||
(SERIAL-WRITE-CHAR *PAD-CHAR*))
|
||
(SERIAL-WRITE-CHAR *SOH*)
|
||
(INCF CHKSUM (SERIAL-WRITE-CHAR (INTEGER->GRAPHIC (+ LEN 3))))
|
||
(INCF CHKSUM (SERIAL-WRITE-CHAR (INTEGER->GRAPHIC NUM)))
|
||
(INCF CHKSUM (SERIAL-WRITE-CHAR TYPE))
|
||
(IF DATA
|
||
(DO ((I 0 (1+ I)))
|
||
((>= I LEN))
|
||
(DECLARE (FIXNUM I))
|
||
(INCF CHKSUM (SERIAL-WRITE-CHAR (STRING-ELT DATA I)))))
|
||
(SERIAL-WRITE-CHAR (INTEGER->GRAPHIC (COMPUTE-FINAL-CHECKSUM CHKSUM)))
|
||
(SERIAL-WRITE-CHAR *EOL*)
|
||
(FORCE-OUTPUT *SERIAL-OUTPUT*)
|
||
(DEBUG-MESSAGE "~&type = ~3C, num = ~3D, len = ~3D~& Data = ~A"
|
||
TYPE NUM LEN (IF DATA (UNKLUDGIFY-STRING DATA) ""))))
|
||
|
||
;;; RECEIVE-PACKET
|
||
;;; Values returned are, in order:
|
||
;;; TYPE -- a character (fixnum), in {#\A, #\S, ...}. E.g. #\A means "abort".
|
||
;;; NUM -- a number, the packet-number of this packet.
|
||
;;; DATA -- a string, the data of this packet, which is as many
|
||
;;; characters as appropriate/desired for this type of packet.
|
||
;;; Many callers need only one (usually the type) value.
|
||
|
||
(DEFUN RECEIVE-PACKET ()
|
||
(LISP:CATCH 'TIMEOUT
|
||
(LET ((ALARMCLOCK #'(LAMBDA (IGNORE) IGNORE
|
||
(DEBUG-MESSAGE "~&RECEIVE-PACKET timed out")
|
||
(LISP:THROW 'TIMEOUT *TIMED-OUT*))))
|
||
(DECLARE (SPECIAL ALARMCLOCK))
|
||
(UNWIND-PROTECT
|
||
(PROGN
|
||
(ALARMCLOCK 'TIME (MAX *TIMEOUT* *MIN-TIMEOUT*))
|
||
(REALLY-RECEIVE-PACKET))
|
||
(ALARMCLOCK 'TIME NIL)))))
|
||
|
||
(DEFUN REALLY-RECEIVE-PACKET ()
|
||
(LET ((CH 0) (TYPE 0) (I 0) (CCHKSUM 0) (RCHKSUM 0) (LEN 0) (NUM 0)
|
||
(DATA *RECEIVE-PACKET*))
|
||
(DECLARE (FIXNUM TYPE I CCHKSUM RCHKSUM LEN NUM)
|
||
#+Maclisp (FIXNUM CH))
|
||
(PROG ()
|
||
CONTINUE
|
||
|
||
(SETQ CH (SERIAL-READ-CHAR))
|
||
(COND ((NOT (CHAR= CH *SOH*))
|
||
(DEBUG-MESSAGE "~&Noise: ")
|
||
(GO CONTINUE)))
|
||
|
||
(DEBUG-MESSAGE "~&Receiving packet: ")
|
||
|
||
(SETQ CH (SERIAL-READ-CHAR))
|
||
(IF (CHAR= CH *SOH*) (GO CONTINUE))
|
||
(SETQ CCHKSUM (CHAR->ASCII CH)) ;OK, start checksum
|
||
(SETQ LEN (- (GRAPHIC->INTEGER CH) 3)) ;Get character count
|
||
;; (if *debug?* (format t "<~d>" len))
|
||
|
||
(COND ((OR (< LEN 0) (> LEN (- *MAX-PACKET-SIZE* 3)))
|
||
(DEBUG-MESSAGE "~&RECEIVE-PACKET got illegal packet length: ~D"
|
||
LEN)
|
||
(RETURN (VALUES *LENGTH-ERROR* NUM DATA))))
|
||
|
||
(SETQ CH (SERIAL-READ-CHAR))
|
||
(IF (CHAR= CH *SOH*) (GO CONTINUE))
|
||
(INCF CCHKSUM (CHAR->ASCII CH)) ;OK, update checksum
|
||
(SETQ NUM (GRAPHIC->INTEGER CH)) ;Get packet number
|
||
|
||
(SETQ CH (SERIAL-READ-CHAR))
|
||
(IF (CHAR= CH *SOH*) (GO CONTINUE))
|
||
(INCF CCHKSUM (CHAR->ASCII CH)) ;OK, update checksum
|
||
(SETQ TYPE CH) ;Get packet type
|
||
|
||
(DEBUG-MESSAGE "type = ~3C, num = ~3D, len = ~3D~%"
|
||
TYPE NUM LEN)
|
||
(SETQ I 0)
|
||
READ-THE-PACKET
|
||
(COND ((< I LEN)
|
||
(SETQ CH (SERIAL-READ-CHAR))
|
||
(IF (CHAR= CH *SOH*) (GO CONTINUE))
|
||
(INCF CCHKSUM (CHAR->ASCII CH)) ;OK, update checksum
|
||
(SET-STRING-ELT DATA I CH)
|
||
(INCF I)
|
||
(GO READ-THE-PACKET)))
|
||
|
||
;; (SET-STRING-ELT DATA LEN 0) ;Mark end of data
|
||
;; ^^ unnecessary!?
|
||
(SETF (FILL-POINTER DATA) LEN)
|
||
|
||
(DEBUG-MESSAGE " Data = ~A" (UNKLUDGIFY-STRING DATA))
|
||
|
||
(SETQ CH (SERIAL-READ-CHAR))
|
||
(SETQ RCHKSUM (GRAPHIC->INTEGER CH)) ;OK, get checksum
|
||
|
||
(SETQ CH (SERIAL-READ-CHAR))
|
||
(IF (CHAR= CH *SOH*) (GO CONTINUE)) ;OK, get eol char and toss it
|
||
;Safe!
|
||
(SETQ CCHKSUM (COMPUTE-FINAL-CHECKSUM CCHKSUM))
|
||
|
||
(COND ((NOT (= CCHKSUM RCHKSUM))
|
||
(DEBUG-MESSAGE "RECEIVE-PACKET received bad checksum (expected ~A, got ~A)"
|
||
RCHKSUM CCHKSUM)
|
||
;; Corruption, oh no!
|
||
(RETURN (VALUES *CHECKSUM-ERROR* NUM DATA)))
|
||
(T
|
||
;; Else checksum ok, 'uncorrupted'.
|
||
(IF (ERRP TYPE)
|
||
(MESSAGE "~&Aborting with following error from remote host:~% ~S~%"
|
||
(UNKLUDGIFY-STRING DATA)))
|
||
(RETURN (VALUES TYPE NUM DATA)))))))
|
||
|
||
|
||
;;; Serial I/O. Everything here is pretty implementation-dependent, or at
|
||
;;; least tunable for speed.
|
||
|
||
#-Maclisp
|
||
(DEFUN ALARMCLOCK (WHICH SECONDS) WHICH SECONDS
|
||
;; Call the value of global variable ALARMCLOCK when SECONDS seconds
|
||
;; have passed. If SECONDS is NIL then disable the alarm.
|
||
'UNIMPLEMENTED)
|
||
|
||
(DEFUN INIT-SERIAL-I/O (&OPTIONAL IN (OUT IN))
|
||
#-Maclisp
|
||
(PROGN (SETQ *SERIAL-INPUT*
|
||
(OR (OPEN IN :DIRECTION :INPUT) *TERMINAL-IO*))
|
||
(SETQ *SERIAL-OUTPUT*
|
||
(OR (OPEN OUT :DIRECTION :OUTPUT) *TERMINAL-IO*)))
|
||
#+Maclisp
|
||
(PROGN (IF *SERIAL-INPUT* (TERMINATE-SERIAL-I/O))
|
||
(SETQ *SERIAL-INPUT*
|
||
(OPEN `((,(OR IN 'TTY))) '(TTY SINGLE IMAGE IN)))
|
||
(SETQ *SERIAL-OUTPUT*
|
||
(OPEN `((,(OR IN 'TTY))) '(TTY BLOCK IMAGE OUT)))))
|
||
|
||
#+DEC ;; For testing purposes
|
||
(DEFUN MBX (&OPTIONAL IN (OUT IN))
|
||
(SETQ *EOL* #\NEWLINE)
|
||
(SETQ *MY-EOL* #\NEWLINE)
|
||
(SETQ *SERIAL-INPUT* (OPEN-MAILBOX IN))
|
||
(SETQ *SERIAL-OUTPUT*
|
||
(IF (EQUAL OUT IN)
|
||
*SERIAL-INPUT*
|
||
(OPEN-MAILBOX OUT)))
|
||
(VALUES (NAMESTRING *SERIAL-INPUT*) (NAMESTRING *SERIAL-OUTPUT*)))
|
||
|
||
(DEFUN TERMINATE-SERIAL-I/O ()
|
||
(IF (NOT (EQ *SERIAL-INPUT* *TERMINAL-IO*))
|
||
(CLOSE *SERIAL-INPUT*))
|
||
(IF (NOT (EQ *SERIAL-OUTPUT* *TERMINAL-IO*))
|
||
(CLOSE *SERIAL-OUTPUT*))
|
||
(SETQ *SERIAL-INPUT* NIL *SERIAL-OUTPUT* NIL))
|
||
|
||
(DEFUN SERIAL-STATE ()
|
||
#-Maclisp
|
||
NIL
|
||
#+Maclisp
|
||
(SYSCALL 3. 'TTYGET *SERIAL-INPUT*))
|
||
|
||
(DEFUN SET-SERIAL-STATE (TEMP)
|
||
#-Maclisp
|
||
TEMP
|
||
#+Maclisp
|
||
(SYSCALL 0. 'TTYSET *SERIAL-INPUT* (CAR TEMP) (CADR TEMP)))
|
||
|
||
(DEFUN DISABLE-ECHOING (TEMP)
|
||
#-Maclisp
|
||
TEMP
|
||
#+Maclisp
|
||
(SYSCALL 0. 'TTYSET *SERIAL-INPUT*
|
||
(LOGAND (CAR TEMP) #o070707070707)
|
||
(LOGAND (CADR TEMP) #o070707070707)))
|
||
|
||
;(DEFUN ENABLE-ECHOING (TEMP)
|
||
; (SYSCALL 0. 'TTYSET *SERIAL-INPUT*
|
||
; (LOGIOR (CAR TEMP) #o202020202020)
|
||
; (LOGIOR (CADR TEMP) #o202020200020)))
|
||
|
||
(DEFUN SERIAL-READ-CHAR ()
|
||
#-(or Maclisp Symbolics)
|
||
;; This is supposed to read without echoing.
|
||
(READ-CHAR *SERIAL-INPUT*)
|
||
#+Maclisp
|
||
(LET ((CH (+TYI *SERIAL-INPUT*)))
|
||
(IF *IMAGE?* CH (LOGAND CH #o177)))
|
||
#+Symbolics
|
||
(PROGN (ZL:SEND *SERIAL-INPUT* :INPUT-WAIT NIL
|
||
#'ZL:TIME-ELAPSED-P
|
||
(FLOOR (* (MAX *TIMEOUT* *MIN-TIMEOUT*) 60))
|
||
(ZL:TIME))
|
||
(IF (ZL:SEND *TERMINAL-IO* :LISTEN)
|
||
(LET ((CH (ZL:SEND *TERMINAL-IO* :TYI)))
|
||
(CODE-CHAR (ZL:CHAR-CODE CH) (ZL:CHAR-BITS CH)))
|
||
(LISP:THROW 'TIMEOUT *TIMED-OUT*))))
|
||
|
||
(DEFUN SERIAL-WRITE-CHAR (CH)
|
||
#-(or Maclisp Symbolics)
|
||
(WRITE-CHAR CH *SERIAL-OUTPUT*)
|
||
#+Maclisp
|
||
(+TYO CH *SERIAL-OUTPUT*)
|
||
#+Symbolics
|
||
(ZL:SEND *SERIAL-OUTPUT* :TYO (CHAR->ASCII CH)) ;?
|
||
(CHAR->ASCII CH)) ;for checksums
|
||
|
||
(DEFUN FLUSH-INPUT ()
|
||
(CLEAR-INPUT *SERIAL-INPUT*))
|
||
|
||
;;; Implementation-dependent fast character file I/O:
|
||
|
||
#-Maclisp
|
||
(PROGN 'COMPILE
|
||
(DEFSUBST +TYI (STREAM)
|
||
(READ-CHAR STREAM NIL NIL))
|
||
|
||
(DEFSUBST +TYO (CH STREAM)
|
||
(WRITE-CHAR CH STREAM))
|
||
|
||
(DEFSUBST END-OF-FILE-P (C) ;Called only on value of +TYI
|
||
(NULL C))
|
||
)
|
||
|
||
;;; File I/O
|
||
|
||
;;; Put a pathname into Kermit standard form.
|
||
|
||
(DEFUN KERMITIFY-PATHNAME (PATHNAME VERSIONP)
|
||
(LET* ((NAME (STRINGIFY-COMPONENT (PATHNAME-NAME PATHNAME)))
|
||
(TYPE (STRINGIFY-COMPONENT (PATHNAME-TYPE PATHNAME)))
|
||
(VERSION (STRINGIFY-COMPONENT (PATHNAME-VERSION PATHNAME))))
|
||
(COND ((AND VERSIONP VERSION)
|
||
(STRING-APPEND NAME "." (OR TYPE "") "." VERSION))
|
||
(TYPE
|
||
(STRING-APPEND NAME "." TYPE))
|
||
(T NAME))))
|
||
|
||
(DEFUN STRINGIFY-COMPONENT (X)
|
||
(COND ((EQ X ':WILD) "*")
|
||
((EQ X ':NEWEST) "0")
|
||
((EQ X ':OLDEST) "-1")
|
||
((NUMBERP X) (FORMAT NIL "~d" X))
|
||
(T X)))
|
||
|
||
;;; Use by SEND-FILE and by SERVE to service remote GET requests.
|
||
;;; Returns a list of pairs (<local-pathname> . <remote-filespec>).
|
||
|
||
(DEFUN MAKE-TRANSFER-LIST (LOCAL-FILESPEC REMOTE-FILESPEC)
|
||
#+Maclisp (DECLARE (SPECIAL REMOTE-FILESPEC)) ;No closures
|
||
(LET* ((PATH (PATHNAME LOCAL-FILESPEC))
|
||
(PATH (IF (NULL (PATHNAME-VERSION PATH))
|
||
(MAKE-PATHNAME :DEFAULTS PATH :VERSION :NEWEST)
|
||
PATH))
|
||
(PATHS (LISP:DIRECTORY PATH)))
|
||
(MAPCAR #'(LAMBDA (PATH)
|
||
(CONS PATH (IF REMOTE-FILESPEC
|
||
(MERGE-PATHNAMES REMOTE-FILESPEC PATH
|
||
(PATHNAME-VERSION PATH))
|
||
PATH)))
|
||
PATHS)))
|
||
|
||
;;; Opening files...
|
||
|
||
(DEFUN MAYBE-OPEN-FOR-INPUT (FILESPEC)
|
||
(LISP:OPEN FILESPEC :DIRECTION :INPUT :IF-DOES-NOT-EXIST NIL))
|
||
|
||
(DEFUN MAYBE-OPEN-FOR-OUTPUT (FILESPEC)
|
||
(LISP:OPEN FILESPEC :DIRECTION :OUTPUT :IF-EXISTS NIL))
|
||
|
||
;;; Get around vagaries of file merging.
|
||
|
||
(DEFUN FLUSH-WILDCARDS (PATHNAME)
|
||
(MAKE-PATHNAME :HOST (PATHNAME-HOST PATHNAME)
|
||
:DEVICE (NOT-WILD (PATHNAME-DEVICE PATHNAME))
|
||
:DIRECTORY (NOT-WILD (PATHNAME-DIRECTORY PATHNAME))
|
||
:NAME (NOT-WILD (PATHNAME-NAME PATHNAME))
|
||
:TYPE (NOT-WILD (PATHNAME-TYPE PATHNAME))))
|
||
|
||
(DEFUN NOT-WILD (COMPONENT)
|
||
(IF (OR (EQ COMPONENT :WILD)
|
||
(EQUAL COMPONENT "*"))
|
||
NIL COMPONENT))
|
||
|
||
;;; Write a packet buffer into the currently open local output file.
|
||
|
||
(DEFUN EMPTY-BUFFER (BUFFER)
|
||
"Put data from an incoming packet into a local disk file."
|
||
(LET ((LEN (LISP:LENGTH BUFFER)))
|
||
(DO ((I 0 (1+ I)))
|
||
((>= I LEN) BUFFER)
|
||
(LET ((CH (STRING-ELT BUFFER I)))
|
||
(DECLARE (FIXNUM CH))
|
||
(COND ((CHAR= CH *MY-QUOTE*)
|
||
(SETQ CH (STRING-ELT BUFFER (INCF I)))
|
||
(IF (NOT (CHAR= CH #|(LOGAND CH #o177)|# *MY-QUOTE*))
|
||
(SETQ CH (CTL CH)))))
|
||
;; (IF (NOT *IMAGE?*) (SETQ CH (LOGAND CH #o177)))
|
||
(+TYO CH *STREAM*)))))
|
||
|
||
;;; Fill a packet buffer from the currently open local input file.
|
||
;;; Returns the number of characters stored into the packet buffer.
|
||
;;; A return value of zero means that the end of the file has been reached.
|
||
|
||
;;; There are four ways to fill a buffer:
|
||
;;; 1. kermit default: 7-bit, quote all control characters.
|
||
;;; 3. image mode: send everything through with no conversion, except for
|
||
;;; quoting the quote character.
|
||
;;; 2, 4: lose mode: excised by JAR.
|
||
|
||
(DEFUN FILL-BUFFER (BUFFER)
|
||
"Fill buffer with the outgoing data from the file *STREAM* points to.
|
||
Only control quoting is done; 8-bit and repeat count prefixes are not handled."
|
||
;;1; Changed 6 to 7!! See lmbugs.doc file item #14.
|
||
(LET ((INDEX 0)
|
||
(LIMIT (- *SEND-PACKET-SIZE* 7)))
|
||
(DECLARE (FIXNUM LIMIT INDEX))
|
||
(DO ()
|
||
((>= INDEX LIMIT))
|
||
(LET ((C (+TYI *STREAM*)))
|
||
#+Maclisp (DECLARE (FIXNUM C))
|
||
(COND ((END-OF-FILE-P C) ;???
|
||
(RETURN NIL))
|
||
(T (LET ((N (CHAR->ASCII C)))
|
||
(COND ((CHAR= C *QUOTE*)
|
||
(SET-STRING-ELT BUFFER INDEX *QUOTE*)
|
||
(INCF INDEX)
|
||
(SET-STRING-ELT BUFFER INDEX C)
|
||
(INCF INDEX))
|
||
((GRAPHIC-CHAR-P C)
|
||
;; Regular character
|
||
(SET-STRING-ELT BUFFER INDEX C)
|
||
(INCF INDEX))
|
||
((AND (NOT *IMAGE?*)
|
||
(> N #o177))
|
||
;; Weird character. Don't send anything for it.
|
||
(MESSAGE "~&The character ~C [~N octal] couldn't be translated to ASCII."
|
||
C N))
|
||
;; Deal with newline characters?
|
||
(T (SET-STRING-ELT BUFFER INDEX *QUOTE*)
|
||
(INCF INDEX)
|
||
(SET-STRING-ELT BUFFER
|
||
INDEX
|
||
(IF (OR *IMAGE?* (>= N 32.))
|
||
C
|
||
(CTL C)))
|
||
(INCF INDEX))))))))
|
||
(SETF (FILL-POINTER BUFFER) INDEX)
|
||
INDEX))
|
||
|
||
|
||
#+Symbolics
|
||
(defvar cl-readtable (copy-readtable nil))
|
||
|
||
#+Symbolics
|
||
(defun zl-user::setup ()
|
||
(zl:setq-standard-value *readtable* cl-readtable)
|
||
(zl:setq-standard-value *package* (find-package 'kermit)))
|
||
|
||
|