;;; -*- Mode:LISP; Package:CHAOS; Base:8; Readtable:ZL -*- ;;; Copyright LISP Machine, Inc. 1984, 1985, 1986 ;;; See filename "Copyright.Text" for ;;; licensing and release information. ;;; Stuff that uses the NCP. Servers, etc. (DEFUN VANILLA-CHAOS-SERVER-FUNCTION (CONTACT FUNCTION REJECTP) (WITH-OPEN-STREAM (STREAM (CHAOS:OPEN-STREAM NIL CONTACT)) (LET ((CONN (SEND STREAM :CONNECTION))) (IF (UNWANTED-CONNECTION-REJECTED-P CONN) (RETURN-FROM VANILLA-CHAOS-SERVER-FUNCTION NIL)) (LET ((ST (SUBSTRING (CHAOS:PKT-STRING (CHAOS:CONN-READ-PKTS CONN)) (STRING-LENGTH CONTACT)))) (LET ((REJECT? (AND REJECTP (FUNCALL REJECTP ST)))) (IF REJECT? (RETURN-FROM VANILLA-CHAOS-SERVER-FUNCTION (CHAOS:REJECT CONN REJECT?)))) (CHAOS:ACCEPT CONN) (SEND TV:WHO-LINE-FILE-STATE-SHEET :ADD-SERVER CONN CONTACT) (CONDITION-CASE () (FUNCALL FUNCTION STREAM ST) (SYS:REMOTE-NETWORK-ERROR NIL)))))) (DEFUN ADD-VANILLA-CHAOS-SERVER (CONTACT FUNCTION &KEY REJECTP) "Contant is CONTACT name, function is called on stream and contact argument as long as REJECTP predicate returns NIL." (check-type contact string) (ADD-INITIALIZATION CONTACT `(PROCESS-RUN-FUNCTION ,(FORMAT NIL "~A server" contact) 'vanilla-chaos-server-function ,contact ',function ',REJECTP) nil 'CHAOS:SERVER-ALIST)) (DEFUN HOST-UP-P (HOST &OPTIONAL (TIMEOUT 180.) &AUX PKT) "Return T if the host is up, otherwise (). Always () for non-chaosnet machines." (COND ((ON-CHAOSNET-P HOST) (CONDITION-CASE () (SETQ PKT (SIMPLE HOST "STATUS" TIMEOUT)) (SYS:REMOTE-NETWORK-ERROR NIL) (:NO-ERROR (RETURN-PKT PKT) T))) (T NIL))) (DEFF HOST-UP 'HOST-UP-P) ;s name. sigh (DEFUN UP-HOSTS (LIST-OF-HOSTS &OPTIONAL NUMBER-OF-HOSTS (TIMEOUT 240.) &AUX CONNECTIONS TIME-BEGAN WINNERS) "Returns a list of hosts the hosts in LIST-OF-HOSTS that are deemed to be up. IF NUMBER-OF-HOSTS not NIL, then return as soon as we determine that at least NUMBER-OF-HOSTS are up. In that case, we still return a list of hosts. TIMEOUT is how long in sixtieths of a second to give each host a chance to respond before deeming that that host is down. All testing is done in parallel. Non-chaosnet machines are presently not checked at all; they are assumed to be down." ;;the last line should be fixed in the future (SETQ LIST-OF-HOSTS (MAPCAR #'SI:PARSE-HOST LIST-OF-HOSTS)) (CATCH 'DONE (UNWIND-PROTECT (PROGN (SETQ CONNECTIONS (MAKE-FAST-CONNECTION-LIST LIST-OF-HOSTS "STATUS" 1)) (PROCESS-ALLOW-SCHEDULE) ;wait around a bit (SETQ TIME-BEGAN (TIME)) (DO () ((NULL CONNECTIONS)) (DOLIST (HOST-AND-CONNECTIONS CONNECTIONS) (LET ((HOST (CAR HOST-AND-CONNECTIONS))) (DOLIST (CONNECTION (CDR HOST-AND-CONNECTIONS)) (LET ((ADDRESS (CAR CONNECTION)) (CONN (CDR CONNECTION))) (IF (NOT CONN) (SETF (CDR CONNECTION) ; MAKE-FAST-CONNECTION-LIST lost (CONDITION-CASE () ; before, so try again. (OPEN-CONNECTION ADDRESS "STATUS" 1) (SYS:NETWORK-RESOURCES-EXHAUSTED ()))) (LET ((STATE (STATE CONN))) (COND ((EQ STATE 'ANSWERED-STATE) (DOLIST (C (CDR HOST-AND-CONNECTIONS)) ; Close all connections (IF (CDR C) (CLOSE-CONN (CDR C)))) ; knock it off (SETQ CONNECTIONS (DELQ HOST-AND-CONNECTIONS CONNECTIONS)) (PUSH HOST WINNERS)) ((MEMQ STATE '(OPEN-STATE CLS-RECEIVED-STATE LOS-RECEIVED-STATE)) (PUSH HOST WINNERS) (CLOSE-CONN CONN) (SETF (CDR HOST-AND-CONNECTIONS) ; Remove a single connection (DELQ CONNECTION (CDR HOST-AND-CONNECTIONS)))) (T ;(this is primarily RFC-SENT-STATE) (WHEN ( (TIME-DIFFERENCE (TIME) (TIME-LAST-RECEIVED CONN)) TIMEOUT) (CLOSE-CONN CONN) ;loser (SETF (CDR HOST-AND-CONNECTIONS) ;Remove a single connection (DELQ CONNECTION (CDR HOST-AND-CONNECTIONS))))))))) (COND ((AND NUMBER-OF-HOSTS ;not NIL ( (LENGTH WINNERS) NUMBER-OF-HOSTS)) (THROW 'DONE WINNERS))) (IF (NULL (CDR HOST-AND-CONNECTIONS)) (SETQ CONNECTIONS (DELQ HOST-AND-CONNECTIONS CONNECTIONS))))))) (THROW 'DONE WINNERS)) ; Remove host objects with no connections attached to them ;; Unwind-protect cleanup -- Flush any connections that remain (DOLIST (H-AND-C CONNECTIONS) (DOLIST (CONNECTION (CDR H-AND-C)) (IF (CDR CONNECTION) (REMOVE-CONN (CDR CONNECTION))))))) WINNERS) ;;; The following function is used whenever one wants to make many connections ;;; to many hosts quickly. It is used as part of Hostat, Finger (all LMs), ;;; and find-user-logged-in... (DEFUN MAKE-FAST-CONNECTION-LIST (HOSTS CONTACT-NAME &OPTIONAL (WINDOW-SIZE 1) USE-ALL-ADDRESSES &AUX TABLE-FULL) "Return a list of (HOST . CONNECTIONs) at CONTACT-NAME. The caller is responsible for checking the state of the connection. CONNECTIONS is a list of /(ADDRESS . CONN). CONN is () if the connection table was full at the time." (ASSURE-ENABLED) (MAPCAR #'(LAMBDA (HOST) (CONS HOST (MAPCAR #'(LAMBDA (ADDRESS) (CONS ADDRESS (CONDITION-CASE () (IF (NOT TABLE-FULL) (OPEN-CONNECTION ADDRESS CONTACT-NAME WINDOW-SIZE) ()) ;not likely for table to shrink (SYS:NETWORK-RESOURCES-EXHAUSTED (SETQ TABLE-FULL T) ())))) ;; don't try to get other addresses if user just specified a number (IF (AND USE-ALL-ADDRESSES (NOT (FIXNUMP HOST))) (SEND HOST :CHAOS-ADDRESSES) (NCONS (IF (FIXNUMP HOST) HOST (SEND HOST :CHAOS-ADDRESS))))))) HOSTS)) ;;;; Poll hosts (DEFUN POLL-HOSTS (HOSTS CONTACT-NAME STREAM HEADER-FUNCTION FORMAT-FUNCTION &KEY IGNORE-STATES (WINDOW-SIZE 1) (TIMEOUT 600.) (WHOSTATE "Poll Hosts") &AUX CONNECTIONS (OPEN-CONNECTIONS 0)) "Print the status of chaosnet hosts in HOSTS, or all known chaosnet hosts. STREAM is where all of the information is printed. HOSTS is a list of hosts to report about. If NIL, then all chaonset hosts are used. HEADER-FUNCTION is a function called with one arg, STREAM, to print out the intial header. FORMAT-FUNCTION is called on STREAM chaos address and response packet for successful connections. WINDOW-SIZE defaults to 1 and the TIMEOUT defaults to 10 seconds (600.) IGNORE-STATES is either NIL or a list of states for which nothing is printed if the connection goes into that state. The states can be any of CHAOS:RFC-SENT-STATE CHAOS:ANSWERED-STATE CHAOS:CLS-RECEIVED-STATE CHAOS:OPEN-STATE CHAOS:LOS-RECEIVED-STATE or OTHERWISE (any other state)" (UNWIND-PROTECT (PROGN (SETQ CONNECTIONS (IF HOSTS (LOOP FOR HOST IN HOSTS nconc (let (address object) (cond ((NUMBERP HOST) (ncons (LIST NIL HOST NIL))) ((multiple-value-setq (address object) (ADDRESS-PARSE HOST)) (ncons (LIST object ADDRESS NIL))) (t (FORMAT STREAM "~&No Chaos address for ~S~%" host) nil)))) (CREATE-HOSTAT-CONNECTION-LIST NIL))) (when connections (FUNCALL HEADER-FUNCTION STREAM) (DO () ((NULL CONNECTIONS)) ;loop until there are no more ;; Handle any replies that have come in. ;; Note host-name truncated to 27. characters to make more room for statistics ;; Only have up to 20. outstanding connections at a time (LOOP FOR ELEM IN CONNECTIONS WHILE (< OPEN-CONNECTIONS 20.) WITH NEW-CONN DO (WHEN (AND (NULL (THIRD ELEM)) (SECOND ELEM) (SETQ NEW-CONN (CONDITION-CASE () (OPEN-CONNECTION (CADR ELEM) CONTACT-NAME WINDOW-SIZE) (SYS:NETWORK-RESOURCES-EXHAUSTED NIL)))) (INCF OPEN-CONNECTIONS) (SETF (THIRD ELEM) NEW-CONN))) ;; tell user that something is happening... (PROCESS-WAIT-WITH-TIMEOUT WHOSTATE 120. #'(LAMBDA () (DOLIST (ELEM CONNECTIONS) (WHEN (AND (THIRD ELEM) (NEQ (STATE (THIRD ELEM)) 'RFC-SENT-STATE)) (RETURN T))))) (DOLIST (ELEM CONNECTIONS) (LET ((HOST (CAR ELEM)) (ADDRESS (CADR ELEM)) (CONN (CADDR ELEM)) (PUNT 'CONN) (PKT NIL)) (WHEN CONN (CASE (STATE CONN) (RFC-SENT-STATE (IF (< (TIME-DIFFERENCE (TIME) (TIME-LAST-RECEIVED CONN)) TIMEOUT) (SETQ PUNT NIL) (UNLESS (MEMQ 'RFC-SENT-STATE IGNORE-STATES) (FORMAT STREAM "~O~7T~@[~A ~]Host not responding~%" ADDRESS HOST)))) (ANSWERED-STATE (UNWIND-PROTECT (PROGN (SETQ PKT (GET-NEXT-PKT CONN)) (UNLESS (MEMQ 'ANSWERED-STATE IGNORE-STATES) (FUNCALL FORMAT-FUNCTION STREAM ADDRESS PKT))) (AND PKT (RETURN-PKT PKT))) ;; Delete not only this connection, but every one to this same host, in ;; case it has multiple addresses. One copy of the answer is enough, but ;; if it fails we would like to see all paths. (when host (SETQ PUNT 'HOST))) (CLS-RECEIVED-STATE (UNWIND-PROTECT (PROGN (SETQ PKT (GET-NEXT-PKT CONN)) (UNLESS (MEMQ 'CLS-RECEIVED-STATE IGNORE-STATES) (FORMAT STREAM "~O~7T~@[~A ~]returned a CLS:~A~%" ADDRESS HOST (PKT-STRING PKT)))) (AND PKT (RETURN-PKT PKT)))) (OPEN-STATE (UNLESS (MEMQ 'OPEN-STATE IGNORE-STATES) (FORMAT STREAM "~#oO~7T~@[~A ~]returned an OPN~%" ADDRESS HOST))) (LOS-RECEIVED-STATE (SETQ PKT (READ-PKTS-LAST CONN)) (UNLESS (MEMQ 'LOS-RECEIVED-STATE IGNORE-STATES) (FORMAT STREAM "~O~7T~@[~A ~]returned a LOS:~A~%" ADDRESS HOST (PKT-STRING PKT)))) (OTHERWISE (UNLESS (MEMQ 'OTHERWISE IGNORE-STATES) (FORMAT STREAM "~O~7T~@[~A ~]connection entered bad state: ~A~%" ADDRESS HOST (STATE CONN))))) (CASE PUNT (CONN (REMOVE-CONN CONN) (SETQ CONNECTIONS (DELQ ELEM CONNECTIONS)) (DECF OPEN-CONNECTIONS)) (HOST (WHEN HOST (DOLIST (C CONNECTIONS) (WHEN (EQ (CAR C) HOST) (WHEN (THIRD C) (REMOVE-CONN (THIRD C))) (SETQ CONNECTIONS (DELQ C CONNECTIONS)) (DECF OPEN-CONNECTIONS)))))))))))) ;; Remove host objects with no connections attached to them ;; Unwind-protect cleanup -- Flush any connections that remain (LOOP FOR (HOST ADDRESS CONN) IN CONNECTIONS WHEN CONN DO (CLOSE-CONN CONN)))) ;;; what a kludge (DEFVAR CANONICAL-HOSTAT-ALL-LIST NIL "Internal list in the form (host address nil) which is used to speed up hostat.") (DEFUN INITIALIZE-CANONICAL-HOSTAT-ALL-LIST (&AUX HOSTS) "Initializes the variable CANONICAL-HOSTAT-ALL-LIST to have the right value." (SETQ CANONICAL-HOSTAT-ALL-LIST NIL HOSTS NIL) ;; reverse does a copylist which we need before sortcar (DOLIST (HOST (REVERSE (ALL-CHAOS-HOSTS))) ;; speed up as this is likely to be in reverse alphabetical order (PUSH (CONS (SEND HOST :NAME) HOST) HOSTS)) (SETQ HOSTS (SORTCAR HOSTS #'ALPHALESSP)) (DOLIST (CONS HOSTS) (PUSH (LIST (CDR CONS) (ADDRESS-PARSE (CDR CONS)) NIL) CANONICAL-HOSTAT-ALL-LIST)) (SETQ CANONICAL-HOSTAT-ALL-LIST (NREVERSE CANONICAL-HOSTAT-ALL-LIST))) (DEFUN CREATE-HOSTAT-CONNECTION-LIST (HOSTS &AUX LIST) "Return a list in the form (host address nil) for each host in HOSTS, or for all chaos hosts" (SUBSET #'(LAMBDA (OBJ) (NET:NETWORK-PATH-AVAILABLE :CHAOS (CAR OBJ))) (COND ((NULL HOSTS) (COPYTREE ;please don't mung this variable (OR CANONICAL-HOSTAT-ALL-LIST ;could be NIL (INITIALIZE-CANONICAL-HOSTAT-ALL-LIST)))) (T (DOLIST (HOST HOSTS) (LET ((PARSED (SI:PARSE-HOST HOST T))) (WHEN PARSED (PUSH (LIST PARSED (ADDRESS-PARSE PARSED) NIL) LIST)))) LIST)))) ;;;; The HOSTAT function (DEFUN HOSTAT-FULL-HANDLER (&REST IGNORE) (THROW 'CONNECTION-TABLE-FULL NIL)) (DEFUN HOSTAT (&REST HOSTS) "Prints out information on STREAM on the status of all of the hosts specified by HOSTS." (POLL-HOSTS HOSTS "STATUS" *STANDARD-OUTPUT* (IF (CDR HOSTS) #'HOSTAT-HEADING #'(LAMBDA (STREAM) (HOSTAT-HEADING STREAM NIL))) #'HOSTAT-FORMAT-ANS :WHOSTATE "Hostat Reply")) (DEFUN HOSTAT-HEADING (STREAM &OPTIONAL (VERBOSE T)) (FORMAT STREAM "~&Chaosnet host status report. ~:[~;Type Control-Abort to quit.~]" VERBOSE) (FORMAT STREAM "~%~7A~25A" "Site" "Name//Status") (DO ((HEADS '("Subnet" "#-in" "#-out" "abort" "lost" "crc" "ram" "bitc" "other") (CDR HEADS)) (WIDTHS '(6 9 9 8 8 8 4 5 6) (CDR WIDTHS))) ((NULL HEADS) (WRITE-CHAR #/NEWLINE STREAM)) (FORMAT STREAM "~V@A" (CAR WIDTHS) (CAR HEADS)))) (DEFUN HOSTAT-FORMAT-ANS (STREAM HOST PKT &AUX (NBYTES (PKT-NBYTES PKT))) (FORMAT STREAM "~7@<~O ~>~27A" ;Print host number and name as returned HOST (NSUBSTRING (PKT-STRING PKT) 0 (MIN NBYTES 27. (OR (STRING-SEARCH-CHAR 0 (PKT-STRING PKT) 0 32.) ;; This line is temporary! ******* (STRING-SEARCH-CHAR #o200 (PKT-STRING PKT) 0 32.) 32.)))) (HOSTAT-FORMAT-ANS-1 PKT 34. '#10r(4 9 9 8 8 8 4 5 6) STREAM)) (DEFUN HOSTAT-FORMAT-ANS-1 (PKT START-COLUMN COLUMN-WIDTHS STREAM &AUX (NBYTES (PKT-NBYTES PKT))) (DO ((I 24. (+ I 2 CT)) ;Now display subnet meters (FIRST-LINE T NIL) (ID) (CT) (MAXI (+ 8 (TRUNCATE NBYTES 2)))) (( I MAXI) (AND FIRST-LINE (WRITE-CHAR #/NEWLINE STREAM))) (SETQ ID (AREF PKT I) CT (AREF PKT (1+ I))) ;Block header (OR FIRST-LINE (FORMAT STREAM "~VA" START-COLUMN "")) (COND ((< ID #o400) ;Subnet info (old 16-bit format) (FORMAT STREAM "~VO" (CAR COLUMN-WIDTHS) ID) (DO ((J (+ I 2) (1+ J)) ;Now print those meters that are present (L (CDR COLUMN-WIDTHS) (CDR L)) (N (MIN CT 8) (1- N))) ((ZEROP N)) (FORMAT STREAM "~VD" (CAR L) (AREF PKT J)))) ((< ID #o1000) ;Subnet info (FORMAT STREAM "~VO" (CAR COLUMN-WIDTHS) (- ID #o400)) (DO ((J (+ I 2) (+ J 2)) ;Now print those meters that are present (L (CDR COLUMN-WIDTHS) (CDR L)) (N (MIN (TRUNCATE CT 2) 8) (1- N))) ((ZEROP N)) (FORMAT STREAM "~VD" (CAR L) (DPB (AREF PKT (1+ J)) (BYTE #O20 #o20) (AREF PKT J))))) (T ;I don't know about this (FORMAT STREAM "~O unknown info block ID" ID))) (WRITE-CHAR #/NEWLINE STREAM))) (DEFUN PRINT-HOST-TIMES (&OPTIONAL (HOSTS TIME-SERVER-HOSTS) (STREAM *STANDARD-OUTPUT*)) (LET ((BEGIN-TIME (TIME:TIME)) (TOTAL-TIME 0) (RESPONSES 0)) (POLL-HOSTS HOSTS "TIME" STREAM #'(LAMBDA (STREAM) (FORMAT STREAM "~&Host~26TTime")) #'(LAMBDA (STREAM ADDRESS PKT) (LET ((TIME (DECODE-CANONICAL-TIME-PACKET PKT))) (FORMAT STREAM "~&~:[~;! ~]~A~22,2T~\TIME\~%" (> (ABS (- (TIME:GET-UNIVERSAL-TIME) TIME)) 180.) (SI:GET-HOST-FROM-ADDRESS ADDRESS :CHAOS) TIME) (INCF TOTAL-TIME TIME) (INCF RESPONSES))) :WHOSTATE "Host time") (FORMAT STREAM "~2%Average time: ~\TIME\; total time elapsed: ~D seconds.~%" (ROUND TOTAL-TIME RESPONSES) (TRUNCATE (- (TIME:TIME) BEGIN-TIME) 60.)))) ;;;this time divided by 2 is significant ;(DEFUN MOST-PROBABLE-TIME (TIME-LIST &AUX L PUNT) ; "Given a list of times, return what the time is most likely to be." ; (SETQ L (LENGTH TIME-LIST)) ; (SETQ PUNT (IF (< L 3) ;if we just want to take the exact average ; 0 ; (+ 1 (// L 4)))) ;it works ; (SETQ TIME-LIST (SORT TIME-LIST #'>)) ; (LOOP FOR I FROM (+ 0 PUNT) TO (- L PUNT 1) ; SUMMING (NTH I TIME-LIST) INTO SUM ; FINALLY (// SUM (- L (* 2 PUNT))))) ;;; This always works for MINITS boxes (DEFUN RESET-TIME-SERVER (HOST &AUX PKT) (SETQ HOST (SI:PARSE-HOST HOST)) (PRINT-HOST-TIMES (LIST HOST)) (UNWIND-PROTECT (CONDITION-CASE (RESULT) (SETQ PKT (SIMPLE HOST "RESET-TIME-SERVER")) (SYS:NETWORK-ERROR (FORMAT *ERROR-OUTPUT* "~&Network error: ") (SEND RESULT :REPORT *ERROR-OUTPUT*) (WRITE-CHAR #/NEWLINE *ERROR-OUTPUT*)) (:NO-ERROR (FORMAT T "~&Successfully reset the time of ~A." HOST))) (AND PKT (RETURN-PKT PKT)))) ;;; SHOUT to all Lisp Machines. (DEFUN SHOUT (&AUX MSG-TEXT HOST PERSON) "Send a message to all Lisp machines. The message is read from the terminal." (FS:FORCE-USER-TO-LOGIN) (FORMAT T "~%Message: (terminate with ~:@C)~%" #/END) (SETQ MSG-TEXT (STRING-APPEND "Everybody: " (ZWEI:QSEND-GET-MESSAGE *QUERY-IO* (FORMAT NIL "You are typing a message which will be sent to everyone who is using a Lisp Machine. If you want to quit, hit the ~:@C key. To end your message, hit the ~:@C key." #/ABORT #/END)) PERSON "anyone")) (DO ((MACHINE SI:MACHINE-LOCATION-ALIST (CDR MACHINE))) ((NULL MACHINE)) (SETQ HOST (CAAR MACHINE)) (NET:SEND-TERMINAL-MESSAGE (SI:PARSE-HOST HOST) "" #'(LAMBDA (STREAM) (FORMAT STREAM "~A@~A ~\DATIME\~%" USER-ID SI:LOCAL-HOST) (SEND STREAM :STRING-OUT MSG-TEXT))))) (NET:DEFINE-NETWORK-FUNCTION (NET:SEND-TERMINAL-MESSAGE :CHAOS) (HOST PERSON MESSAGE-GENERATOR) (WITH-OPEN-STREAM (STREAM (CHAOS:OPEN-STREAM HOST (STRING-APPEND "SEND " PERSON) :ERROR NIL :DIRECTION :OUTPUT)) (COND ((not (errorp stream)) (FUNCALL MESSAGE-GENERATOR STREAM) (SEND STREAM :CLOSE) nil) ('ELSE stream)))) (DEFUN EXPAND-MAILING-LISTS (NAMES HOST &AUX RESULT STREAM BAD-ADDRESSES CHAR) "Return a list of the recipients of the addresses NAMES at HOST. Returns either a list of addresses, and a list of bad addresses, or an error instance. The NAMES should not contain atsigns." (DECLARE (VALUES ADDRESSES BAD-ADDRESSES)) (UNWIND-PROTECT (PROGN (SETQ STREAM (OPEN-STREAM HOST "EXPAND-MAILING-LIST" :ERROR NIL)) (IF (ERRORP STREAM) STREAM (DOLIST (NAME NAMES (VALUES (NREVERSE RESULT) (NREVERSE BAD-ADDRESSES))) (SEND STREAM :LINE-OUT NAME) (SEND STREAM :FORCE-OUTPUT) (COND ((CHAR= (SETQ CHAR (SEND STREAM :TYIPEEK)) #/-) (SEND STREAM :LINE-IN) (PUSH NAME BAD-ADDRESSES)) ((CHAR= CHAR #/+) (SEND STREAM :LINE-IN) (DO ((LINE (SEND STREAM :LINE-IN T) (SEND STREAM :LINE-IN T))) ((ZEROP (STRING-LENGTH LINE))) (PUSHNEW LINE RESULT :TEST #'STRING-EQUAL))) (T (FERROR "Unknown character ~C in response" CHAR)))))) (AND STREAM (NOT (ERRORP STREAM)) (SEND STREAM :CLOSE :ABORT)))) ;;;; Finger server and NAME user end (ADD-INITIALIZATION "FINGER" '(GIVE-FINGER) NIL 'SERVER-ALIST) (DEFVAR GIVE-FINGER-SAVED-STRING NIL) (DEFVAR GIVE-FINGER-SAVED-IDLE NIL) (DEFVAR GIVE-FINGER-SAVED-USER-ID NIL) ;This runs in the background task now. (DEFUN GIVE-FINGER (&AUX IDLE) (SETQ IDLE (FLOOR (TIME-DIFFERENCE (TIME) TV:KBD-LAST-ACTIVITY-TIME) 3600.)) ;Minutes ;; Making the string is expensive in terms of paging, and it is almost ;; always the same as last time. So try to use a saved string. (COND ((OR (NEQ GIVE-FINGER-SAVED-IDLE IDLE) (NEQ GIVE-FINGER-SAVED-USER-ID USER-ID)) (SETQ GIVE-FINGER-SAVED-IDLE IDLE GIVE-FINGER-SAVED-USER-ID USER-ID GIVE-FINGER-SAVED-STRING (FORMAT NIL "~A~%~A~%~:[~3*~;~:[~D:~2,48D~;~*~D~]~]~%~A~%~C~%" USER-ID SI:LOCAL-FINGER-LOCATION (NOT (ZEROP IDLE)) (ZEROP (FLOOR IDLE 60.)) (FLOOR IDLE 60.) (\ IDLE 60.) FS:USER-PERSONAL-NAME-FIRST-NAME-FIRST FS:USER-GROUP-AFFILIATION)))) (ERRSET (FAST-ANSWER-STRING "FINGER" GIVE-FINGER-SAVED-STRING) NIL)) ;;; This can't run in the background process, since it uses a full byte-stream ;;; connection, which requires retransmission, which is done by the background process. (ADD-INITIALIZATION "NAME" '(PROCESS-RUN-FUNCTION "NAME Server" 'GIVE-NAME) NIL 'SERVER-ALIST) (DEFUN GIVE-NAME (&AUX CONN IDLE) (SETQ CONN (LISTEN "NAME")) (IF (UNWANTED-CONNECTION-REJECTED-P CONN) (RETURN-FROM GIVE-NAME NIL)) (SETQ IDLE (FLOOR (TIME-DIFFERENCE (TIME) TV:KBD-LAST-ACTIVITY-TIME) 3600.)) ;Minutes (ERRSET (FORMAT-AND-EOF CONN "~6A ~C ~22A ~6A ~:[ ~3*~;~:[~D:~2,48D~; ~*~D~]~] ~A" USER-ID FS:USER-GROUP-AFFILIATION FS:USER-PERSONAL-NAME-FIRST-NAME-FIRST (USER-ACTIVITY-STRING) (NOT (ZEROP IDLE)) (ZEROP (FLOOR IDLE 60.)) (FLOOR IDLE 60.) (\ IDLE 60.) SI:LOCAL-FINGER-LOCATION) NIL)) (DEFUN USER-ACTIVITY-STRING () (LET ((W (DO ((W TV:SELECTED-WINDOW (SEND W :SUPERIOR)) (W1 NIL W)) ((OR (NULL W) (TYPEP W 'TV:SCREEN)) W1)))) (OR (IGNORE-ERRORS (TYPECASE W (SUPDUP "Supdup") (ZWEI:ZMACS-FRAME "Zmacs") (TV:PEEK-FRAME "Peek") (ZWEI:CONVERSE-FRAME "Converse") (TV:INSPECT-FRAME "Inspect") (TV:LISP-LISTENER "Lisp") (TELNET "Telnet") (FED:FED-FRAME "Font Edit") (ZWEI:ZMAIL-FRAME "ZMail"))) SI:LOCAL-HOST-NAME))) ;; ********************************************************************************************* ;; **** THIS FUNCTION SHOULD NO LONGER BE IN THIS FILE, SINCE IT IS NO LONGER CHAOS SPECIFIC *** ;; ********************************************************************************************* (DEFUN FINGER (&OPTIONAL SPEC (STREAM *STANDARD-OUTPUT*) HACK-BRACKETS-P &AUX HOST USER INDEX) "Print brief information about a user as specified by SPEC. SPEC can be a user name, or user@host. If HACK-BRACKETS-P is T, then make the first line show what host we are fingering." (COND ((NULL SPEC) (SETQ USER "") (SETQ HOST SI:ASSOCIATED-MACHINE)) ((SETQ INDEX (STRING-SEARCH-CHAR #/@ SPEC)) (SETQ HOST (SUBSTRING SPEC (1+ INDEX))) (SETQ USER (SUBSTRING SPEC 0 INDEX))) ('ELSE (SETQ USER SPEC) (SETQ HOST SI:ASSOCIATED-MACHINE))) (NET:FINGER-HOST (SI:PARSE-HOST HOST) USER STREAM (IF HACK-BRACKETS-P :BRACKETS))) (NET:DEFINE-NETWORK-FUNCTION (NET:FINGER-HOST :CHAOS) (HOST USER STREAM STYLE) (BLOCK LOSE (LET ((HOST-NAME (SEND HOST :NAME)) (HACK-BRACKETS-P (EQ STYLE :BRACKETS)) (FIRST-LINE NIL)) (WITH-OPEN-STREAM (CSTREAM (OPEN-STREAM HOST (STRING-APPEND "NAME " (OR USER "")) :DIRECTION :INPUT :ERROR NIL)) (IF (ERRORP CSTREAM) (RETURN-FROM LOSE CSTREAM)) (FORMAT STREAM "~2&") (SETQ FIRST-LINE (SEND CSTREAM :LINE-IN)) (IF (STRING-EQUAL "" FIRST-LINE) (SETQ FIRST-LINE (SEND CSTREAM :LINE-IN))) (AND HACK-BRACKETS-P (AND (NOT (STRING-SEARCH-CHAR #/[ FIRST-LINE)) (NOT (STRING-SEARCH-CHAR #/] FIRST-LINE))) (SEND STREAM :LINE-OUT (STRING-APPEND #/[ HOST-NAME #/]))) (SEND STREAM :LINE-OUT FIRST-LINE) (STREAM-COPY-UNTIL-EOF CSTREAM STREAM) NIL)))) (DEFUN WHOIS (&OPTIONAL SPEC (STREAM *STANDARD-OUTPUT*)) "Print verbose information about a user as specified by SPEC. SPEC can be a user name, or user@host." (FINGER (STRING-APPEND "//W " (IF SPEC SPEC (SEND SI:ASSOCIATED-MACHINE :SHORT-NAME))) STREAM)) ;yes, I've heard of selectq! - change this lossage to METHODS, except for :WAITS (DEFVAR HOST-FINGER-PROTOCOL-ALIST '((:LISPM . PARSE-LISPM-FINGER) (:TOPS-20 . PARSE-TWENEX-FINGER) (:ITS . PARSE-ITS-FINGER) (:WAITS . PARSE-WAITS-FINGER) (:UNIX . PARSE-UNIX-FINGER) (:MULTICS . PARSE-MULTICS-FINGER) (:VMS . PARSE-VMS-FINGER) (:TOPS-10 . PARSE-TENEX-FINGER)) "This list is for use by chaos:user-logged-into-host-p.") (DEFUN PARSE-TWENEX-FINGER (USER FINGER-INFO) "Return T if the USER is logged to a twenex site, based on the finger info we have." (IGNORE USER) (SETQ FINGER-INFO (STRING-UPCASE FINGER-INFO)) (AND (NOT (STRING-SEARCH "LOGOUT" FINGER-INFO)) ;last logged out ==> not logged in. (NOT (STRING-EQUAL "" FINGER-INFO)) ; (OR (< (STRING-LENGTH FINGER-INFO) 4) ;;don't lose with detached jobs ; (NOT (STRING-EQUAL (STRING-APPEND "DET" #/CR) ;this could be more clever ; (SUBSTRING FINGER-INFO (- (STRING-LENGTH FINGER-INFO) 4))))) (NOT (MEMQ (CHAR FINGER-INFO 0) '(#/? #/%))) )) (DEFUN PARSE-MULTICS-FINGER (USER FINGER-INFO) "Return T if the USER is logged to a Multics site, based on the finger info we have." (IGNORE USER) (NOT (STRING-SEARCH "Not logged in" FINGER-INFO))) (DEFUN PARSE-UNIX-FINGER (USER FINGER-INFO) "Return T if the USER is logged to a Unix site, based on the finger info we have." (IGNORE USER) (IF (STRING-SEARCH "On since" FINGER-INFO) T)) ;catch all (DEFUN PARSE-TENEX-FINGER (USER FINGER-INFO) "Return T if the USER is logged to a Tenex site, based on the finger info we have." (IGNORE USER) (IF (STRING-SEARCH "Who What" FINGER-INFO) T)) (DEFUN PARSE-WAITS-FINGER (USER FINGER-INFO) ;this may not be exactly correct "Return T if the USER is logged to a waits site, based on the finger info we have." (IGNORE USER) (IF (STRING-SEARCH "Logged in" FINGER-INFO) T)) ;catch all (DEFUN PARSE-VMS-FINGER (USER FINGER-INFO) "Return T if the USER is logged to a VMS site, based on the finger info we have." (IGNORE USER) (IF (STRING-SEARCH "Personal Name" FINGER-INFO) T)) ;catch all (DEFUN PARSE-ITS-FINGER (USER FINGER-INFO) "Return T if the USER is logged to a ITS site, based on the finger info we have." (IGNORE USER) (AND (NOT (STRING-SEARCH "Last logout" FINGER-INFO)) ;not logged in (NOT (STRING-SEARCH "user" FINGER-INFO)) ;"No users" -- no such user )) (DEFUN PARSE-LISPM-FINGER (USER FINGER-INFO) "Return T if the USER is logged into a Lispm, based on the finger info we were given." (IF (STRING-EQUAL USER (SUBSTRING FINGER-INFO 0 (STRING-LENGTH USER))) T)) ;we don't ignore (DEFUN USER-LOGGED-INTO-HOST-P (USER HOST &AUX FINGER-INFO HOST-TYPE FUNCTION) "Return T or NIL on the basis of whether USER is logged into HOST." (SETQ FINGER-INFO (WITH-OUTPUT-TO-STRING (FINGER-INFO) (FINGER (STRING-APPEND (STRING-APPEND USER "@") HOST) FINGER-INFO))) (SETQ FINGER-INFO (STRING-LEFT-TRIM #/RETURN FINGER-INFO)) ;meaningless (SETQ HOST-TYPE (SEND (SI:PARSE-HOST HOST) :SYSTEM-TYPE)) (SETQ FUNCTION (SYS:ASSOC-EQUAL HOST-TYPE HOST-FINGER-PROTOCOL-ALIST)) (IF (NULL FUNCTION) (FERROR "~S is not a known type of host that supports the NAME protocol." HOST-TYPE)) (FUNCALL (CDR FUNCTION) USER FINGER-INFO)) ;;; I'd like to see some parallelism here.... (DEFUN FIND-HOSTS-OR-LISPMS-LOGGED-IN-AS-USER (USER &OPTIONAL HOSTS NO-LISPMS-P &AUX LISPMS) "Return a list of host objects for hosts on which USER is logged in. HOSTS is the list of hosts to check (in addition to all Lisp machines). If NO-LISPMS-P is T, don't return any Lispms." (IF (NULL NO-LISPMS-P) (SETQ LISPMS (FIND-LISPMS-LOGGED-IN-AS-USER USER))) (DOLIST (HOST HOSTS) (IF (USER-LOGGED-INTO-HOST-P USER HOST) (PUSH (SI:PARSE-HOST HOST) LISPMS))) LISPMS) (DEFUN FIND-ANY-UP-HOST-OF-TYPE (TYPE &OPTIONAL DONT-USE-HOSTS EVEN-IF-DOWN (TIMEOUT 360.) &AUX HOSTS-TO-TRY) "Returns a host which is of that is of system type TYPE. Will not return a host if it is in the list DONT-USE-HOSTS. TIMEOUT specifies how long to wait before giving up, in 60ths of a sec. Default is 6 seconds. If DONT-USE-HOSTS is T, then use every host with that system type, instead of just one. If EVEN-IF-DOWN is T, return all such hosts, regardless if they are up or not. If you are using the EVEN-IF-DOWN argument, you probably want LIST-ALL-NET-MACHINES." (SETQ HOSTS-TO-TRY (LIST-ALL-NET-MACHINES TYPE)) (IF (NEQ DONT-USE-HOSTS T) (DOLIST (HOST DONT-USE-HOSTS) (SETQ HOSTS-TO-TRY (DELQ (SI:PARSE-HOST HOST) HOSTS-TO-TRY)))) (IF EVEN-IF-DOWN HOSTS-TO-TRY (UP-HOSTS HOSTS-TO-TRY (IF (EQ DONT-USE-HOSTS T) NIL ;find all 1.) ;find any TIMEOUT))) (DEFUN LISPM-FINGER-INFO (LM-HOST) "Given a LispM, return its default finger info." (LET ((NAME (SEND (SI:PARSE-HOST LM-HOST) :NAME))) (OR (THIRD (SYS:ASSOC-EQUALP NAME SI:MACHINE-LOCATION-ALIST)) "Unknown."))) ;;;; Finger All Lispms crap (DEFVAR *SAVED-ALL-LISPMS* () "Generated first time we want to finger all LispMs.") (DEFVAR *SAVED-LOCAL-LISPMS* () "Generated first time we want to finger local LispMs.") (DEFVAR *SAVED-ALL-CHAOS-HOSTS* () "Generated first time we want a list of every chaosnet hosts.") (ADD-INITIALIZATION "Initialize host lists" '(cond ((not si:*in-cold-load-p*) (RESET-SAVED-HOST-LISTS T T T) (INITIALIZE-CANONICAL-HOSTAT-ALL-LIST))) () 'SI:SITE-INITIALIZATION-LIST) ;; THIS CHAOS:ALL-* FUNCTIONS HAVE BEEN MODIFIED TO RETURN TRUE CHAOS PROTOCOL AVAILABLE ;; HOSTS ONLY. (DEFUN CHAOS-PROTOCOL-HOSTS-FILTER (HOSTS) (SUBSET #'(LAMBDA (X) (NET:NETWORK-PATH-AVAILABLE :CHAOS X)) HOSTS)) (DEFUN ALL-LOCAL-LISPMS (&OPTIONAL RESET) "Returns a list of all local lisp machines. If RESET is T, then generate the list from scratch." (IF (OR (NULL *SAVED-LOCAL-LISPMS*) RESET) (RESET-SAVED-HOST-LISTS NIL T NIL)) (CHAOS-PROTOCOL-HOSTS-FILTER *SAVED-LOCAL-LISPMS*)) (DEFUN ALL-LISPMS (&OPTIONAL RESET) "Returns a list of all lisp machines. If RESET is T, then generate the list from scratch." (IF (OR (NULL *SAVED-ALL-LISPMS*) RESET) (RESET-SAVED-HOST-LISTS NIL NIL T)) (CHAOS-PROTOCOL-HOSTS-FILTER *SAVED-ALL-LISPMS*)) (DEFUN ALL-CHAOS-HOSTS (&OPTIONAL RESET) "Returns a list of all hosts which are known and on the chaosnet. If RESET is T, then generate the list from scratch." (IF (OR (NULL *SAVED-ALL-CHAOS-HOSTS*) RESET) (RESET-SAVED-HOST-LISTS T NIL NIL)) (CHAOS-PROTOCOL-HOSTS-FILTER *SAVED-ALL-CHAOS-HOSTS*)) (DEFUN RESET-SAVED-HOST-LISTS (&OPTIONAL (DO-ALL-HOSTS T) DO-LOCAL-LISPMS DO-ALL-LISPMS) "Sets the magic variables that contain a list of all hosts for efficiency." (COND (DO-ALL-HOSTS (SETQ *SAVED-ALL-CHAOS-HOSTS* NIL) ;reset ;; code is from old hostat ;; use SI:PARSE-HOST because it's faster on host objects (DOLIST (H SI:HOST-ALIST) (AND (OR (SECOND H) (SI:PARSE-HOST (FIRST H) t nil)) (SECOND H) (SEND (SECOND H) :NETWORK-TYPEP :CHAOS) (PUSH (SECOND H) *SAVED-ALL-CHAOS-HOSTS*))))) (WHEN DO-LOCAL-LISPMS (SETQ *SAVED-LOCAL-LISPMS* NIL) (DOLIST (ELEM SI:MACHINE-LOCATION-ALIST) (LET ((TEM (SI:PARSE-HOST (CAR ELEM) T nil))) (WHEN TEM (PUSH TEM *SAVED-LOCAL-LISPMS*)))) (SETQ *SAVED-LOCAL-LISPMS* (NREVERSE *SAVED-LOCAL-LISPMS*))) (WHEN DO-ALL-LISPMS (SETQ *SAVED-ALL-LISPMS* (LIST-ALL-NET-MACHINES :LISPM)))) ;already parsed (DEFSUBST FCL-HOST (ELEM) (CAR ELEM)) (DEFSUBST FCL-CONN1 (ELEM) (CDADR ELEM)) ; First CHAOS CONN in an element ;;; SYS: WINDOW; BASSTR wants to call these some of these, please don't rename recklessly (DEFF FINGER-LOCAL-LISPMS 'FINGER-LISPMS) ;announced name (DEFF FINGER-ALL-LMS 'FINGER-ALL-LISPMS) (DEFUN FINGER-ALL-LISPMS (&OPTIONAL (STREAM *STANDARD-OUTPUT*) HOSTS (PRINT-FREE T) RETURN-FREE (PRINT-INUSE T) (PRINT-DOWN T)) "Display who is logged into the lispm machine HOSTS or all LispMs if HOSTS is NIL" (IF (NULL HOSTS) (SETQ HOSTS (ALL-LISPMS))) (FINGER-LISPMS STREAM HOSTS PRINT-FREE RETURN-FREE PRINT-INUSE PRINT-DOWN)) (DEFUN FINGER-LISPMS (&OPTIONAL (STREAM *STANDARD-OUTPUT*) HOSTS (PRINT-FREE T) RETURN-FREE (PRINT-INUSE T) (PRINT-DOWN T) &AUX FREE DOWN ELEMS HOST) "Print brief information about who is logged in to each Lisp machine. STREAM is where to print it. PRINT-FREE not NIL says print also lists of free and nonresponding Lisp machines. RETURN-FREE not NIL says return information about free and nonresponding machines; in this case, the first value is a list of host objects of free machines and the second a list of host objects of nonresponding ones. HOSTS is the list of hosts to check, defaulting to all known Lisp machines. HOSTS should be a list of already parsed hosts, or NIL." (IF (NULL HOSTS) (SETQ HOSTS (ALL-LOCAL-LISPMS))) (SETQ ELEMS (MAKE-FAST-CONNECTION-LIST HOSTS "FINGER" 1)) (UNWIND-PROTECT ;; If number of machines gets large enough, ;; put in a hack like HOSTAT's, to go around later and try again ;; to connect to machines which we couldn't get the first time ;; due to connection table full. (DO ((OLD-TIME (TIME))) (NIL) (DOLIST (ELEM ELEMS) (LET* ((CONN (FCL-CONN1 ELEM)) (STATE (AND CONN (STATE CONN)))) (UNLESS (EQ STATE 'RFC-SENT-STATE) ;; Got some reply for this one. (WHEN (EQ STATE 'ANSWERED-STATE) ;Got something meaningful (LET* ((PKT (GET-NEXT-PKT CONN)) (STR (PKT-STRING PKT)) (HOST-NAME (send (FCL-HOST ELEM) :short-name)) (IDX)) (UNWIND-PROTECT (COND ((NOT (MEMQ (CHAR STR 0) '(#/CR #/SP #/TAB))) ;Logged in (WHEN PRINT-INUSE (LET (USER (GROUP "") (NAME "") (IDLE "") (LOCATION "")) (SETQ USER (NSUBSTRING STR 0 (SETQ IDX (STRING-SEARCH-CHAR #/CR STR)))) (WHEN IDX (SETQ LOCATION (NSUBSTRING STR (1+ IDX) (SETQ IDX (STRING-SEARCH-CHAR #/CR STR (1+ IDX)))))) (WHEN IDX (SETQ IDLE (NSUBSTRING STR (1+ IDX) (SETQ IDX (STRING-SEARCH-CHAR #/CR STR (1+ IDX)))))) (WHEN IDX (SETQ NAME (NSUBSTRING STR (1+ IDX) (SETQ IDX (STRING-SEARCH-CHAR #/CR STR (1+ IDX)))))) (SETQ GROUP (IF IDX (AREF STR (1+ IDX)) #/SP)) (FORMAT STREAM "~&~15A ~C ~22A ~10A ~5@A ~A~%" USER GROUP NAME HOST-NAME IDLE LOCATION)))) ((OR PRINT-FREE RETURN-FREE) ;person CANNOT be logged in. (PUSH (LIST HOST-NAME (SUBSTRING STR 1 ;Please don't search for the space! (STRING-SEARCH-SET '(#/CR) STR 1))) FREE))) (RETURN-PKT PKT))) (SETQ ELEMS (DELQ ELEM ELEMS)) (CLOSE-CONN CONN)) (AND CONN (REMOVE-CONN CONN))))) (OR ELEMS (RETURN NIL)) (AND (> (TIME-DIFFERENCE (TIME) OLD-TIME) 240.) ;Allow 5 secs for this all (RETURN NIL)) ; someone can't tell time (PROCESS-WAIT "Finger Lispms" #'(LAMBDA (OLD-TIME ELEMS) (OR (> (TIME-DIFFERENCE (TIME) OLD-TIME) 240.) (dolist (elem elems) (LET ((CONN (FCL-CONN1 ELEM))) (AND CONN (neq (STATE CONN) 'RFC-SENT-STATE) (RETURN T)))))) OLD-TIME ELEMS)) ;; Flush all outstanding connections (SETQ DOWN (MAPCAR #'FCL-HOST ELEMS)) (DOLIST (ELEM ELEMS) (LET ((CONN (FCL-CONN1 ELEM))) (AND CONN (REMOVE-CONN (FCL-CONN1 ELEM)))))) ;; Print which machines responded that they are free. (AND PRINT-FREE (COND ((NULL FREE) (FORMAT STREAM "~18@T ~2&No Free Lisp machines.~%")) (T (FORMAT STREAM "~18@T ~2&Free Lisp machines: ~2%") (DOLIST (ENTRY FREE) (FORMAT:OSTRING (CAR ENTRY) 18.) (FORMAT STREAM "~A~&" (CADR ENTRY)))))) ;; Print which machines did not respond. (AND PRINT-DOWN (COND ((NOT (NULL DOWN)) (FORMAT STREAM "~18@T ~2&Lisp machines not responding: ~2%") (DOLIST (ENTRY DOWN) (SETQ HOST (send entry :name)) (FORMAT:OSTRING HOST 18.) (FORMAT STREAM "~A~&" (LISPM-FINGER-INFO HOST)))))) (AND RETURN-FREE (VALUES (MAPCAR 'CAR FREE) DOWN))) (DEFUN LIST-ALL-NET-MACHINES (SYSTEM-TYPE &AUX M MS) "Return a list of parsed hosts which represents all the chaos hosts with SYSTEM-TYPE." (DOLIST (ELEM SI:HOST-ALIST MS) (COND ((SETQ M (SECOND ELEM)) ; Parsed already (AND (SEND M :NETWORK-TYPEP :CHAOS) (EQUAL (SEND M :SYSTEM-TYPE) SYSTEM-TYPE) (PUSH M MS))) ((AND (EQUAL (FOURTH ELEM) SYSTEM-TYPE) ; Not parsed yet (MEMQ :CHAOS (NTHCDR 6. ELEM))) (PUSH (SI:PARSE-HOST (FIRST ELEM)) MS))))) (DEFF FINGER-ALL-NET-LMS 'FINGER-ALL-LISPMS) (DEFUN FIND-LISPMS-LOGGED-IN-AS-USER (USER &AUX (ELEMS (MAKE-FAST-CONNECTION-LIST (LIST-ALL-NET-MACHINES :LISPM) "FINGER" 1)) (HOST-LIST ())) "Return a list of (host objects of) lisp machines that USER is logged into." (DO ((OLD-TIME (TIME))) (NIL) (DOLIST (ELEM ELEMS) (LET* ((CONN (FCL-CONN1 ELEM)) (STATE (AND CONN (STATE CONN)))) (COND ((NEQ STATE 'RFC-SENT-STATE) ;Still waiting (AND (EQ STATE 'ANSWERED-STATE) ;Got something meaningful (LET* ((PKT (GET-NEXT-PKT CONN)) (STR (PKT-STRING PKT))) (AND (STRING-EQUAL STR USER :END1 (STRING-SEARCH-CHAR #/CR STR)) (PUSH (FCL-HOST ELEM) HOST-LIST)) (RETURN-PKT PKT))) (SETQ ELEMS (DELQ ELEM ELEMS)) (WHEN CONN (CLOSE-CONN CONN) (REMOVE-CONN CONN)))))) (OR ELEMS (RETURN NIL)) ; Done with all of them (AND (> (TIME-DIFFERENCE (TIME) OLD-TIME) 240.) ;Allow 5 secs for this all (RETURN NIL)) (PROCESS-WAIT "Finger" #'(LAMBDA (OLD-TIME ELEMS) (OR (> (TIME-DIFFERENCE (TIME) OLD-TIME) 240.) (DO ((ELEMS ELEMS (CDR ELEMS))) ((NULL ELEMS) NIL) (OR (EQ (STATE (FCL-CONN1 (CAR ELEMS))) 'RFC-SENT-STATE) (RETURN T))))) OLD-TIME ELEMS)) ;; Flush all outstanding connections (DOLIST (ELEM ELEMS) (REMOVE-CONN (FCL-CONN1 ELEM))) HOST-LIST) (defun output-host-other-attributes (stream attlist &aux format-fun) (do ((attlist attlist (cddr attlist))) ((null attlist)) (setq format-fun (or (get (first attlist) 'si:address-unparser) #'si:default-address-unparser)) (if (listp (second attlist)) (dolist (x (second attlist)) (output-one-host-attribute stream (symbol-name (first attlist)) x format-fun)) (output-one-host-attribute stream (symbol-name (first attlist)) (second attlist) format-fun)))) (defun output-one-host-attribute (stream name attribute &functional string-function) (send stream :string-out name) (send stream :tyo #\Space) (send stream :string-out (funcall string-function attribute)) (send stream :tyo #\Cr)) (defun hostab-server (&aux conn) (setq conn (listen "HOSTAB")) (IF (UNWANTED-CONNECTION-REJECTED-P CONN) (RETURN-FROM HOSTAB-SERVER NIL)) (accept conn) (with-open-stream (stream (chaos:make-stream conn)) (do-forever (condition-case () (progn (hostab-server-to-stream stream (send stream :line-in)) (send stream :eof) ; End of a request (unless (eq (state conn) 'open-state) ;; The other end should not close before another request... (chaos:close-conn conn "Bye") (return nil))) (sys:network-error (return nil)))))) (defun hostab-server-to-stream (stream host-name? &aux host) (if (setq host (si:parse-host host-name? t ())) (let ((elem (cli:assoc (send host :name) si:host-alist :test #'string-equal))) ; These are done specially, since they're so simple (dolist (name (reverse (si:host-name-list elem))) ; get official name first (output-one-host-attribute stream "NAME" name #'string)) (format stream "MACHINE-TYPE ~A~%SYSTEM-TYPE ~A~%" (si:host-machine-type-internal elem) (si:host-system-type-internal elem)) (output-host-other-attributes stream (si:host-addresses elem))) (send stream :line-out "ERROR No such host"))) (add-initialization "HOSTAB" '(process-run-function "HOSTAB Server" 'hostab-server) () 'server-alist) (DEFUN SEND-CANONICAL-TIME-PACKET (CONN TIME) "Send the magic N bits that represent the TIME time on connection CONN." (LET ((PKT (GET-PKT))) (SETF (PKT-NBYTES-on-write PKT) 4) (SETF (AREF PKT FIRST-DATA-WORD-IN-PKT) (LDB (BYTE #o20 0) TIME) (AREF PKT (1+ FIRST-DATA-WORD-IN-PKT)) (LDB (BYTE #o20 #o20) TIME)) (ANSWER CONN PKT))) (DEFUN DECODE-CANONICAL-TIME-PACKET (PKT) "Convert into an integral number of 60ths of a second, the time encoded in packet PKT." (DPB (AREF PKT (1+ FIRST-DATA-WORD-IN-PKT)) (BYTE #o20 #o20) (AREF PKT FIRST-DATA-WORD-IN-PKT))) ;;; TIME server!! (DEFUN TIME-SERVER () "The function that is called when another host asks us what the time is." (LET ((CONN (LISTEN "TIME"))) (IF (UNWANTED-CONNECTION-REJECTED-P CONN "I wouldnt even give you the time of day") (RETURN-FROM TIME-SERVER NIL)) (COND ((AND (VARIABLE-BOUNDP TIME:*LAST-TIME-UPDATE-TIME*) (NOT (NULL TIME:*LAST-TIME-UPDATE-TIME*))) (SEND-CANONICAL-TIME-PACKET CONN (TIME:GET-UNIVERSAL-TIME))) (T (REJECT CONN "I don't know what time it is."))))) ;;;; UPTIME server!! (DEFUN UPTIME-SERVER () "The function that is called when another host asks us how long we've been up." (LET ((CONN (LISTEN "UPTIME"))) (IF (UNWANTED-CONNECTION-REJECTED-P CONN "none of your bee's wax") (RETURN-FROM UPTIME-SERVER NIL)) (COND ((AND (VARIABLE-BOUNDP TIME:*LAST-TIME-UPDATE-TIME*) ;Dont bomb while cold-loading! (NOT (NULL TIME:*LAST-TIME-UPDATE-TIME*)) (NOT (NULL TIME:*UT-AT-BOOT-TIME*))) (SEND-CANONICAL-TIME-PACKET CONN (* 60. (- (TIME:GET-UNIVERSAL-TIME) TIME:*UT-AT-BOOT-TIME*)))) (T (REJECT CONN "I either don't know what time it is or how long I've been up."))))) (ADD-INITIALIZATION "TIME" '(TIME-SERVER) NIL 'SERVER-ALIST) (ADD-INITIALIZATION "UPTIME" '(UPTIME-SERVER) NIL 'SERVER-ALIST) (defun set-time-server () (ignore-errors (let (conn) (unwind-protect (let (address) (setq conn (listen "SET-TIME")) (IF (UNWANTED-CONNECTION-REJECTED-P CONN) (RETURN-FROM SET-TIME-SERVER NIL)) (setq address (foreign-address conn)) (answer-string (prog1 conn (setq conn nil)) "Ok.") (set-time-from-host address)) (if conn (remove-conn conn)))))) ;;;finish later (defun set-time-from-host (address) address) (ADD-INITIALIZATION "SET-TIME" '(PROCESS-RUN-FUNCTION "SET-TIME Server" 'set-time-server) NIL 'SERVER-ALIST) (DEFUN HOST-UPTIME (HOST &OPTIONAL (STREAM *STANDARD-OUTPUT*) (TIMEOUT 240.) &AUX PKT TIME) "Print a human readable time onto stream STREAM. Returns the uptime (an integer) if host up, NIL if host down." (SETQ HOST (SI:PARSE-HOST HOST)) (UNWIND-PROTECT (CONDITION-CASE () (SETQ PKT (SIMPLE HOST "UPTIME" TIMEOUT)) (SYS:REMOTE-NETWORK-ERROR (IF STREAM (FORMAT STREAM "Host ~A is apparently not up." HOST)) NIL) (:NO-ERROR (SETQ TIME (// (DECODE-CANONICAL-TIME-PACKET PKT) 60.)) (IF STREAM (TIME:PRINT-INTERVAL-OR-NEVER TIME STREAM)) TIME)) (AND PKT (RETURN-PKT PKT)))) (DEFUN UPTIME (&OPTIONAL (STREAM *STANDARD-OUTPUT*) &REST HOSTS) "Print onto STREAM a listing of the uptimes of HOSTS, or all chaosnet hosts if HOSTS is ()." (POLL-HOSTS HOSTS "UPTIME" STREAM #'(LAMBDA (STREAM) (FORMAT STREAM "~%~8A~25A~25A" "Address" "Host name" "Uptime")) #'(LAMBDA (STREAM HOST PKT) (FORMAT STREAM "~&~8@<~O ~>~25A~A~%" HOST (CHAOS:HOST-DATA HOST) (TIME:PRINT-INTERVAL-OR-NEVER (// (DECODE-CANONICAL-TIME-PACKET PKT) 60.) NIL))) :IGNORE-STATES '(CLS-RECEIVED-STATE) :WHOSTATE "Uptime reply" )) (DEFUN UPTIME-HEADING (&OPTIONAL (STREAM *STANDARD-OUTPUT*)) (FORMAT STREAM "~%~8A~25A~25A" "Address" "Host name" "Uptime")) (DEFUN UPTIME-FORMAT-ANS (HOST PKT &OPTIONAL (STREAM *STANDARD-OUTPUT*)) (FORMAT STREAM "~&~8@<~O ~>~25A~A~%" HOST (CHAOS:HOST-DATA HOST) (TIME:PRINT-INTERVAL-OR-NEVER (// (DECODE-CANONICAL-TIME-PACKET PKT) 60.) NIL))) ;;; Dummy mail server, rejects all incoming mail ;;; It really should be more clever, and notify the user of something... (DEFUN DUMMY-MAIL-SERVER (&AUX CONN STREAM RCPT) (SETQ CONN (LISTEN "MAIL")) (IF (UNWANTED-CONNECTION-REJECTED-P CONN) (RETURN-FROM DUMMY-MAIL-SERVER NIL)) (ACCEPT CONN) (SETQ STREAM (MAKE-STREAM CONN)) (CONDITION-CASE () (DO-FOREVER ;Read the rcpts (SETQ RCPT (SEND STREAM :LINE-IN NIL)) (AND (ZEROP (STRING-LENGTH RCPT)) ;Blank line = start text (RETURN)) (SEND STREAM :LINE-OUT ;; "-" as first char indicates failure to sender "-Lisp Machines do not accept mail, maybe you want the :LMSEND command.") (SEND STREAM :FORCE-OUTPUT)) (SYS:REMOTE-NETWORK-ERROR NIL)) (CLOSE-CONN CONN "all rcpts read")) (ADD-INITIALIZATION "MAIL" '(PROCESS-RUN-FUNCTION "MAIL Server" 'DUMMY-MAIL-SERVER) NIL 'SERVER-ALIST) ;;;; Remote disk facilities. (ADD-INITIALIZATION "REMOTE-DISK" '(PROCESS-RUN-FUNCTION "REMOTE-DISK Server" 'REMOTE-DISK-SERVER) NIL 'SERVER-ALIST) (DEFUN REMOTE-DISK-SERVER (&AUX CONN STREAM LINE CMD CMDX UNIT BLOCK N-BLOCKS RQB BLOCK-PKT-1 BLOCK-PKT-2 BLOCK-PKT-3) (SETQ CONN (LISTEN "REMOTE-DISK" 25.)) (IF (UNWANTED-CONNECTION-P CONN) (UNWANTED-REJECT CONN) (UNWIND-PROTECT (CONDITION-CASE (.error.) (PROGN (ACCEPT CONN) (SEND TV:WHO-LINE-FILE-STATE-SHEET :ADD-SERVER CONN "REMOTE-DISK") (SETQ STREAM (MAKE-STREAM CONN)) (DO-FOREVER (PROCESS-WAIT "Net Input" #'(LAMBDA (CONN) (OR (READ-PKTS CONN) (NEQ (STATE CONN) 'OPEN-STATE))) CONN) (AND (NEQ (STATE CONN) 'OPEN-STATE) (RETURN NIL)) (SETQ LINE (READLINE STREAM) ;Get a command line CMDX (STRING-SEARCH-CHAR #/SP LINE) CMD (SUBSTRING LINE 0 CMDX)) (COND ((OR (STRING-EQUAL CMD "READ") (STRING-EQUAL CMD "READ-PHYSICAL") (STRING-EQUAL CMD "WRITE") ) (LET ((*READ-BASE* 10.) (POS CMDX)) (SETF (VALUES UNIT POS) (READ-FROM-STRING LINE 'SI:NO-EOF-OPTION POS)) (SETF (VALUES BLOCK POS) (READ-FROM-STRING LINE 'SI:NO-EOF-OPTION POS)) (SETF (VALUES N-BLOCKS POS) (READ-FROM-STRING LINE 'SI:NO-EOF-OPTION POS)) (SETQ RQB NIL)) (UNWIND-PROTECT (PROGN (SETQ RQB (GET-DISK-RQB N-BLOCKS) BLOCK-PKT-1 (GET-DISK-STRING RQB 0 484. T) BLOCK-PKT-2 (GET-DISK-STRING RQB 121. 484. T) BLOCK-PKT-3 (GET-DISK-STRING RQB 242. 56. T)) (COND ((or (STRING-EQUAL CMD "READ") (STRING-EQUAL CMD "READ-PHYSICAL")) (if (string-equal cmd "READ") (DISK-READ RQB UNIT BLOCK) (si:disk-read-physical rqb unit block)) ;; Give to net (DO ((BLOCK BLOCK (1+ BLOCK)) (N-BLOCKS N-BLOCKS (1- N-BLOCKS))) ((ZEROP N-BLOCKS)) ;; Transmit three packets from block in buffer (SI:TRANSMIT-PARTITION-PACKET CONN BLOCK-PKT-1) (SI:TRANSMIT-PARTITION-PACKET CONN BLOCK-PKT-2) (SI:TRANSMIT-PARTITION-PACKET CONN BLOCK-PKT-3) ;; Advance magic strings to next block (%P-STORE-CONTENTS-OFFSET (+ (%P-CONTENTS-OFFSET BLOCK-PKT-1 3) (* 4 PAGE-SIZE)) BLOCK-PKT-1 3) (%P-STORE-CONTENTS-OFFSET (+ (%P-CONTENTS-OFFSET BLOCK-PKT-2 3) (* 4 PAGE-SIZE)) BLOCK-PKT-2 3) (%P-STORE-CONTENTS-OFFSET (+ (%P-CONTENTS-OFFSET BLOCK-PKT-3 3) (* 4 PAGE-SIZE)) BLOCK-PKT-3 3))) (T ;; Get from net (DO ((BLOCK BLOCK (1+ BLOCK)) (N-BLOCKS N-BLOCKS (1- N-BLOCKS))) ((ZEROP N-BLOCKS)) ;; Get 3 packets and form a block in the buffer ;; RECEIVE-PARTITION-PACKET will throw if it gets to eof. (SI:RECEIVE-PARTITION-PACKET CONN BLOCK-PKT-1) (SI:RECEIVE-PARTITION-PACKET CONN BLOCK-PKT-2) (SI:RECEIVE-PARTITION-PACKET CONN BLOCK-PKT-3) ;; Advance magic strings to next block (%P-STORE-CONTENTS-OFFSET (+ (%P-CONTENTS-OFFSET BLOCK-PKT-1 3) (* 4 PAGE-SIZE)) BLOCK-PKT-1 3) (%P-STORE-CONTENTS-OFFSET (+ (%P-CONTENTS-OFFSET BLOCK-PKT-2 3) (* 4 PAGE-SIZE)) BLOCK-PKT-2 3) (%P-STORE-CONTENTS-OFFSET (+ (%P-CONTENTS-OFFSET BLOCK-PKT-3 3) (* 4 PAGE-SIZE)) BLOCK-PKT-3 3)) (DISK-WRITE RQB UNIT BLOCK)))) ;(AND BLOCK-PKT-3 (RETURN-ARRAY (PROG1 BLOCK-PKT-3 (SETQ BLOCK-PKT-3 NIL)))) ;(AND BLOCK-PKT-2 (RETURN-ARRAY (PROG1 BLOCK-PKT-2 (SETQ BLOCK-PKT-2 NIL)))) ;(AND BLOCK-PKT-1 (RETURN-ARRAY (PROG1 BLOCK-PKT-1 (SETQ BLOCK-PKT-1 NIL)))) (RETURN-DISK-RQB RQB))) ((STRING-EQUAL CMD "SAY") (PROCESS-RUN-FUNCTION "Notify" 'TV:NOTIFY NIL "REMOTE-DISK-SERVER:~A" (SUBSTRING LINE CMDX)))))) (SYS:REMOTE-NETWORK-ERROR) (sys:disk-error (Close-Conn conn (format nil "~a" .error.)))) (AND CONN (REMOVE-CONN CONN))))) ;;;; Babel server (DEFVAR *BABEL-STRING* " !/"#$%&'()*+,-.//0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}" "This is the string which we send out over and over during the BABEL server.") (DEFUN BABEL-SERVER (&AUX CONN STREAM) "Gives the same characters to a connection over and over. Useful for debugging chaosnet." (SETQ CONN (LISTEN "BABEL")) (IF (UNWANTED-CONNECTION-REJECTED-P CONN) (RETURN-FROM BABEL-SERVER NIL)) (ACCEPT CONN) (SETQ STREAM (MAKE-STREAM CONN)) (CONDITION-CASE () (DO-FOREVER (FORMAT STREAM *BABEL-STRING*) (SEND STREAM :FORCE-OUTPUT)) (SYS:REMOTE-NETWORK-ERROR (CLOSE-CONN CONN)))) (ADD-INITIALIZATION "BABEL" '(PROCESS-RUN-FUNCTION "Babel Server" 'BABEL-SERVER) NIL 'SERVER-ALIST) ;;;; Echo server (DEFUN ECHO-SERVER (&AUX CONN pkt) (SETQ CONN (LISTEN "ECHO")) (IF (UNWANTED-CONNECTION-REJECTED-P CONN) (RETURN-FROM ECHO-SERVER NIL)) (ACCEPT CONN) (CONDITION-CASE () (DO-FOREVER (setq pkt (get-next-pkt conn)) (select (pkt-opcode pkt) (dat-op (send-pkt conn pkt)) (eof-op (send-pkt conn pkt eof-op) (finish-conn conn) (close-conn conn)))) (SYS:REMOTE-NETWORK-ERROR (CLOSE-CONN CONN)))) (ADD-INITIALIZATION "ECHO" '(PROCESS-RUN-FUNCTION "Echo Server" 'ECHO-SERVER) NIL 'SERVER-ALIST) (defvar echo-test-string (make-string (* 2 488.))) (defun echo-test (host &aux conn) (dotimes (i (string-length echo-test-string)) (aset (ldb (byte 8. 0) i) echo-test-string i)) (unwind-protect (progn (setq conn (connect host "ECHO")) (do ((start 0 (1+ start))) (()) (if (= start 488.) (setq start 0)) (let ((pkt (get-pkt))) (copy-array-portion echo-test-string start (+ start 488.) (pkt-string pkt) 0 488.) (setf (pkt-nbytes-on-write pkt) 488.) (send-pkt conn pkt) (setq pkt (get-next-pkt conn)) (if (not (string-equal echo-test-string (pkt-string pkt) :start1 start :end1 (+ start 488.))) (format t "~2&Wrote ~s read ~s" (substring echo-test-string start (+ start 488.)) (pkt-string pkt))) (return-pkt pkt)))) (close-conn conn))) ;;;; for the spell server (DEFUN UP-SPELL-HOST (&OPTIONAL ERROR-OK (TIMEOUT 240.)) "Return any host that is up which supports the spell protocol. If no such host, signal an error, unless ERROR-OK is T." (LET ((UP-HOST (UP-HOSTS (GET-SITE-OPTION :SPELL-SERVER-HOSTS) 1 TIMEOUT))) (AND (NULL UP-HOST) (NOT ERROR-OK) (FERROR 'SYS:NO-SERVER-UP "No host which supports the spell protocol is up now.")) (CAR UP-HOST))) (DEFUN CHECK-SPELLING-WORDLIST (WORDLIST &OPTIONAL HOST) "Ask a host about the spelling of each of the words in wordlist." (LET ((CONN (CONNECT (IF (NULL HOST) (UP-SPELL-HOST) HOST) "SPELL"))) (SEND-STRING CONN WORDLIST) ;send it our stuff (PROCESS-WAIT "Dictionary" #'DATA-AVAILABLE CONN) (LET* ((PKT (GET-NEXT-PKT CONN)) (INFO (STRING-APPEND (PKT-STRING PKT)))) ;the data part of the packet (RETURN-PKT PKT) (CLOSE-CONN CONN) INFO))) ;;;; Frobbing routing tables ;;; Routing table format: for N subnets, N*4 bytes of data, holding N*2 words ;;; For subnet n, pkt[2n] has the method; if this is less than 400 (octal), it's ;;; an interface number; otherwise, it's a host which will forward packets to that ;;; subnet. pkt[2n+1] has the host's idea of the cost. (DEFUN FORMAT-ROUTING-TABLE-PKT (PKT &OPTIONAL (STREAM *STANDARD-OUTPUT*) &AUX METHOD COST (I first-data-word-in-pkt)) (FORMAT STREAM "~%Subnet Method Cost~%") (DOTIMES (SUBNET (- (TRUNCATE (PKT-NWORDS PKT) 2) first-data-word-in-pkt)) (SETQ METHOD (AREF PKT I)) (INCF I) (SETQ COST (AREF PKT I)) (INCF I) (AND (NOT (ZEROP METHOD)) (> MAXIMUM-ROUTING-COST COST) (FORMAT STREAM "~3O ~A~28T~6D~%" SUBNET (IF (< METHOD #o400) (FORMAT NIL "Interface ~D" METHOD) (HOST-DATA METHOD)) COST)))) (DEFUN SHOW-ROUTING-TABLE (HOST &OPTIONAL (STREAM *STANDARD-OUTPUT*)) (CONDITION-CASE (PKT) (SIMPLE HOST "DUMP-ROUTING-TABLE") (SYS:NETWORK-ERROR (SEND STREAM :FRESH-LINE) (SEND STREAM :STRING-OUT "Network error: ") (SEND PKT :REPORT STREAM)) (:NO-ERROR (UNWIND-PROTECT (FORMAT-ROUTING-TABLE-PKT PKT STREAM) (RETURN-PKT PKT))))) (DEFUN SHOW-ROUTING-PATH (&KEY (FROM SI:LOCAL-HOST) TO (STREAM *STANDARD-OUTPUT*) (TO-HOST TO) &AUX METHOD-AS-HOST) "Show how packets would most likely from a host to TO. The required TO argument can be a ChaosNet host/address or a subnet number." (OR (AND (NUMBERP TO) (< TO #2r11111111)) (SETQ TO (LDB (BYTE 8. 8.) (CHAOS:ADDRESS-PARSE TO)))) (CONDITION-CASE (PKT) (SIMPLE FROM "DUMP-ROUTING-TABLE") (SYS:NETWORK-ERROR (FORMAT STREAM "~&Network error: ") (SEND PKT :REPORT STREAM)) (:NO-ERROR (UNWIND-PROTECT (LET ((METHOD (AREF PKT (+ FIRST-DATA-WORD-IN-PKT (* 2 TO)))) (COST (AREF PKT (+ FIRST-DATA-WORD-IN-PKT 1 (* 2 TO))))) (COND ((OR (ZEROP METHOD) ( TO (TRUNCATE (PKT-NBYTES PKT) 4))) (FORMAT STREAM "~&No routing table entry for subnet ~O in ~A." TO (HOST-DATA FROM))) ((< METHOD #o400) (FORMAT STREAM "~&Direct path from ~A to host ~A on subnet ~O at interface ~D." (HOST-DATA FROM) TO-HOST TO METHOD)) (T (FORMAT STREAM "~&~A will bounce the packet off ~A at cost ~D." (HOST-DATA FROM) (HOST-DATA METHOD) COST) (SETQ METHOD-AS-HOST METHOD)))) (RETURN-PKT PKT)) (IF METHOD-AS-HOST (SHOW-ROUTING-PATH :FROM METHOD-AS-HOST :TO TO :STREAM STREAM :TO-HOST TO-HOST))))) (DEFUN DUMP-ROUTING-TABLE (&AUX (PKT (GET-PKT)) (N-SUBNETS (MIN (ARRAY-LENGTH ROUTING-TABLE) (TRUNCATE MAX-DATA-WORDS-PER-PKT 2)))) (DO ((SUBNET 0 (1+ SUBNET)) (PKT-IDX FIRST-DATA-WORD-IN-PKT)) ((= SUBNET N-SUBNETS)) (SETF (AREF PKT PKT-IDX) (AREF ROUTING-TABLE SUBNET)) (INCF PKT-IDX) ; deposit cost in next word (SETF (AREF PKT PKT-IDX) (AREF ROUTING-TABLE-COST SUBNET)) (INCF PKT-IDX)) (SETF (AREF PKT (+ FIRST-DATA-WORD-IN-PKT (* MY-SUBNET 2))) 1) ; Interface 1 (SETF (AREF PKT (+ FIRST-DATA-WORD-IN-PKT 1 (* MY-SUBNET 2))) 15.) ;; set the number of bytes before actually sending.... (SETF (PKT-NBYTES-on-write PKT) (* 4 N-SUBNETS)) (ANSWER (LISTEN "DUMP-ROUTING-TABLE") PKT)) (ADD-INITIALIZATION "DUMP-ROUTING-TABLE" '(DUMP-ROUTING-TABLE) () 'SERVER-ALIST) (DEFUN DISALLOW-CONNECTION? (WHAT CONN FASCISM-LEVEL) "Return a string explaining why someone can't use the server, or NIL if it's allowed." (COND ((MEMQ NIL FASCISM-LEVEL) ;Server always unavailable (FORMAT NIL "Fascism at this site prevents the usage of the ~A server." WHAT)) ((AND (MEMQ :REJECT-UNWANTED FASCISM-LEVEL) ;Special check available.... (UNWANTED-CONNECTION-P CONN)) (UNWANTED-CONNECTION-REJECTION-STRING WHAT CONN)) ((MEMQ :NOT-LOGGED-IN FASCISM-LEVEL) ;Server available if not logged in... (UNLESS (SYS:MEMBER-EQUAL USER-ID '(NIL "")) (FORMAT NIL "This machine is in use by ~A, try again later." USER-ID))) ((MEMQ :NOTIFY FASCISM-LEVEL) ;Server available after Notification (PROCESS-RUN-FUNCTION "Notify" 'TV:NOTIFY NIL "Attempt to use ~A server by the user at ~A" WHAT (HOST-SHORT-NAME (FOREIGN-ADDRESS CONN))) (PROCESS-ALLOW-SCHEDULE) NIL) ((MEMQ T FASCISM-LEVEL) ;Server always available NIL) (T (FORMAT NIL "Unknown rejection: ~A" (set-difference fascism-level '(:reject-unwanted :not-logged-in)))))) ;;; Values can be T, :NOTIFY, or NIL (DEFVAR EVAL-SERVER-ON T "T => allow EVAL server requests; :NOTIFY => allow them but notify the user.") (DEFVAR EVAL-SERVER-CONNECTIONS NIL) ;;; Call this if you want to enable the eval server on your machine (DEFUN EVAL-SERVER-ON (&OPTIONAL (MODE T)) "Allow remote connections to this machine's EVAL server. If mode is :NOTIFY, you will be notified whenever an EVAL server is created." (SETQ EVAL-SERVER-ON MODE)) (DEFUNP EVAL-SERVER-FUNCTION (&AUX CONN) (SETQ CONN (LISTEN "EVAL")) (LET ((LOSE (DISALLOW-CONNECTION? "EVAL" CONN (LIST EVAL-SERVER-ON :NOT-LOGGED-IN :REJECT-UNWANTED)))) (WHEN LOSE (REJECT CONN LOSE) (RETURN-FROM EVAL-SERVER-FUNCTION NIL))) (ACCEPT CONN) (PUSH CONN EVAL-SERVER-CONNECTIONS) (SEND TV:WHO-LINE-FILE-STATE-SHEET :ADD-SERVER CONN "EVAL") (CATCH-ERROR (WITH-OPEN-STREAM (STREAM (MAKE-STREAM CONN :ASCII-TRANSLATION T)) ;; Flush any number of telnet negotiations. (We only understand the simplest kind). (DO-FOREVER (LET ((CH (TYI STREAM))) (IF (= CH #o377) (PROGN (TYI STREAM) (TYI STREAM)) (RETURN (SEND STREAM :UNTYI CH))))) (DO ((*TERMINAL-IO* STREAM) (INPUT)) (NIL) (AND (EQ (SETQ INPUT (READ STREAM 'QUIT)) 'QUIT) (RETURN NIL)) (CATCH-ERROR (PRIN1 (MULTIPLE-VALUE-LIST (EVAL INPUT))) T) (WRITE-CHAR #/NEWLINE STREAM) (SEND STREAM :FORCE-OUTPUT))) NIL)) (ADD-INITIALIZATION "EVAL" '(PROCESS-RUN-FUNCTION "EVAL Server" 'EVAL-SERVER-FUNCTION) NIL 'SERVER-ALIST) ;; Values can be T, :NOT-LOGGED-IN :NOTIFY NIL (DEFVAR TELNET-SERVER-ON T "T => always allow TELNET server requests, :NOTIFY => allow them but notify the user NIL means never to allow them and :not-logged-in means allow them wehn no-one is logged in.") (DEFVAR *TELNET-SERVER-STREAM* :UNBOUND "Special variable for telnet-server.") (DEFVAR *TELNET-OUTPUT-TRANSLATION-TABLE* ; for 32 random LM-characters #("." "v" "a" "b" "^" "~" "e" "p" "l" "g" "d" "^" "+-" "+" "infty" "d" "<" ">" "^" "v" "A" "E" "x" "<->" "<-" "->" "//=" "$" "<=" "=>" "=" "v")) (DEFVAR *ENABLE-TELNET-OUTPUT-TRANSLATION* T) (DEFUN TELNET-SERVER-FUNCTION (&AUX CONN) (SETQ CONN (LISTEN "TELNET")) (LET ((LOSE (DISALLOW-CONNECTION? "TELNET" CONN (LIST TELNET-SERVER-ON :REJECT-UNWANTED)))) (WHEN LOSE (REJECT CONN LOSE) (RETURN-FROM TELNET-SERVER-FUNCTION NIL))) (ACCEPT CONN) (PUSH CONN EVAL-SERVER-CONNECTIONS) (SEND TV:WHO-LINE-FILE-STATE-SHEET :ADD-SERVER CONN "TELNET") (CATCH-ERROR (WITH-OPEN-STREAM (REMOTE (MAKE-STREAM CONN :ASCII-TRANSLATION T)) (PRINT-HERALD REMOTE) (FORMAT REMOTE "~&Telnet server here, hit to begin~%") (SEND REMOTE :FORCE-OUTPUT) (TELNET-SERVER-NEGOTIATIONS REMOTE) (SI:LISP-TOP-LEVEL1 (MAKE-TELNET-ECHOING-STREAM REMOTE))) NIL)) (DEFUN TELNET-SERVER-NEGOTIATIONS (STREAM) (DO () ((NOT (= #O377 (SEND STREAM :TYI)))) (SEND STREAM :TYI) (SEND STREAM :TYI))) (DEFVAR *TELNET-RECORD-STREAM* NIL "If T a stream to spy on TELNET interactions") (DEFUN MAKE-TELNET-ECHOING-STREAM (STREAM &AUX UNTYI-CHAR) #'(LAMBDA (OP &REST ARGS) (CONDITION-CASE () (case OP (:TYI (OR (PROG1 UNTYI-CHAR (SETQ UNTYI-CHAR NIL)) (LET (CH) (SEND STREAM :FORCE-OUTPUT) (SETQ CH (logand #o177 (SEND STREAM :TYI))) (IF *TELNET-RECORD-STREAM* (SEND *TELNET-RECORD-STREAM* :TYO CH)) (cond ((= ch #o177) (send stream :string-out #\overstrike) (setq ch #\rubout)) ((<= ch #o37) (SETQ CH (SET-CHAR-BIT (LOGIOR #O100 CH) :CONTROL 1)) (SEND STREAM :STRING-OUT "c-") (SEND STREAM :TYO (CHAR-CODE CH))) (t (SEND STREAM :TYO CH))) CH))) (:UNTYI (SETQ UNTYI-CHAR (CAR ARGS))) (:READ-CURSORPOS (VALUES 0 0)) (:TYO (LET ((C (CAR ARGS))) (COND ((NOT *ENABLE-TELNET-OUTPUT-TRANSLATION*) (SEND STREAM :TYO C)) ((> C #O37) (SEND STREAM :TYO C)) (T (SEND STREAM :STRING-OUT (AREF *TELNET-OUTPUT-TRANSLATION-TABLE* C)))))) (:BEEP (FORMAT STREAM "~%<<*BEEP*>>~%")) (:CLEAR-SCREEN (FORMAT STREAM "~%<<*CLEAR-WINDOW*>>~%")) (:CLEAR-WINDOW (FORMAT STREAM "~%<<*CLEAR-WINDOW*>>~%")) (OTHERWISE (LEXPR-SEND STREAM OP ARGS))) (SYS:REMOTE-NETWORK-ERROR (PROCESS-RUN-FUNCTION "Kill" CURRENT-PROCESS :KILL) (PROCESS-WAIT "Die" 'FALSE))))) (ADD-INITIALIZATION "TELNET" '(PROCESS-RUN-FUNCTION "TELNET Server" 'TELNET-SERVER-FUNCTION) NIL 'SERVER-ALIST) (defvar *mini-server-log-stream* nil) (defun mini-server-function (&aux conn pkt ;; 15 minutes ... 3 in standard system (chaos:host-down-interval (* 60. 60. 15.))) (unwind-protect (condition-case (lossage) (let ((defaults (send (pathname "SYS: SYS;  QFASL >") :translated-pathname)) charactersp pathname) (setq conn (chaos:listen "MINI")) (IF (UNWANTED-CONNECTION-REJECTED-P CONN) (RETURN-FROM MINI-SERVER-FUNCTION NIL)) (chaos:accept conn) (send tv:who-line-file-state-sheet :add-server conn "MINI") (when *mini-server-log-stream* (format *mini-server-log-stream* "~3%Yow! MINI serving ~O (~A)~%" (conn-foreign-address conn) (si:get-host-from-address (conn-foreign-address conn) :chaos))) (do-forever (setq pkt (chaos:get-next-pkt conn)) (case (chaos:pkt-opcode pkt) (#o200 (setq charactersp t)) (#o201 (setq charactersp nil)) (t (return-from mini-server-function nil))) (setq pathname (merge-pathnames (chaos:pkt-string pkt) defaults)) (chaos:return-pkt pkt) (with-open-file-retry (file-stream (pathname sys:network-error fs:file-error) :characters charactersp) (setq pkt (chaos:get-pkt)) (let ((creation-date (send file-stream :creation-date))) (chaos:set-pkt-string pkt ;; Yes, folks, OCTAL universal times. ;; Don't ask -me- why... (format nil "~A~%~O" (send (send file-stream :truename) :string-for-printing) creation-date)) (chaos:send-pkt conn (prog1 pkt (setq pkt nil)) #o202) (when *mini-server-log-stream* (format *mini-server-log-stream* "~&Sending ~A~% ~ (~:[binary~;characters~]. Date ~O = ~:*~\time\. Length ~D)~%" (send file-stream :truename) charactersp creation-date (send file-stream :length)))) (let ((outstream (chaos:make-stream conn :direction :output :characters charactersp))) ;(stream-copy-until-eof file-stream outstream) (do ((char)) ((null (setq char (send file-stream :tyi)))) (send outstream :tyo char)) (send outstream :eof))))) (sys:remote-network-error (when *mini-server-log-stream* (format *mini-server-log-stream* "~&--- lossage: ~A~%" lossage)) nil)) (ignore-errors (when pkt (chaos:return-pkt pkt)) (when conn (chaos:close-conn conn) (chaos:remove-conn conn))))) (add-initialization "MINI" '(process-run-function "MINI Server" 'MINI-SERVER-FUNCTION) nil 'server-alist) (DEFINE-SITE-VARIABLE *UNWANTED-CONNECTION-P* :UNWANTED-CHAOS-CONNECTION-P "A function to call on the CONN. If it returns T then the connection is rejected") (DEFINE-SITE-VARIABLE *UNWANTED-CONNECTION-REJECTION-STRING* :UNWANTED-CHAOS-CONNECTION-REJECTION-STRING "Either a string or a function to call on the arguments: SERVER-NAME CONN to get the string") (DEFUN UNWANTED-CONNECTION-P (CONN) "Returns T if the CONN is unwanted due to bother, privacy, security, or other issues at the site" (IF *UNWANTED-CONNECTION-P* (FUNCALL *UNWANTED-CONNECTION-P* CONN))) (DEFUN MIT-SITE-BOTHERSOME-CONNECTION-P (CONN) ;; This is the value of the :UNWANTED-CHAOS-CONNECTION-P site variable at MIT ;; It is mostly of historical interest only. (LET ((HOST (CATCH-ERROR (SI:GET-HOST-FROM-ADDRESS (FOREIGN-ADDRESS CONN) :CHAOS) NIL))) (COND ((NOT HOST) T) ('ELSE (OR (STRING-EQUAL (SEND HOST :NAME) "SCRC" :END1 4) (STRING-EQUAL (SEND HOST :NAME) "SPA" :END1 3) (STRING-EQUAL (SEND HOST :NAME) "SWW" :END1 3) (STRING-EQUAL (SEND HOST :NAME) "SCH" :END1 3)))))) (DEFUN UNWANTED-CONNECTION-REJECTION-STRING (WHAT CONN) "Returns the string to use for CHAOS:REJECT on an unwanted connection." (COND ((NOT *UNWANTED-CONNECTION-REJECTION-STRING*) (IF WHAT (FORMAT NIL "Link to ~A is down" WHAT) "Link to network is down")) ((STRINGP *UNWANTED-CONNECTION-REJECTION-STRING*) (IF WHAT (FORMAT NIL "Cant use ~A because ~A" WHAT *UNWANTED-CONNECTION-REJECTION-STRING*) *UNWANTED-CONNECTION-REJECTION-STRING*)) ('ELSE (FUNCALL *UNWANTED-CONNECTION-REJECTION-STRING* WHAT CONN)))) (DEFUN UNWANTED-REJECT (CONN) "Call this if CHAOS:UNWANTED-CONNECTION-P returns T" (REJECT CONN (UNWANTED-CONNECTION-REJECTION-STRING NIL CONN))) (DEFUN UNWANTED-CONNECTION-REJECTED-P (CONN &OPTIONAL WHAT) "Returns T if this connection was unwanted. Also calls UNWANTED-REJECT" (WHEN (UNWANTED-CONNECTION-P CONN) (REJECT CONN (UNWANTED-CONNECTION-REJECTION-STRING WHAT CONN)) T)) (DEFVAR LAST-NOTIFICATION NIL "This variable contains the last string which was a notification for another chaos host. If you want an older notification, try looking in TV:NOTIFICATION-HISTORY") (DEFUN NOTIFY-SERVER () (LET* ((CONN (LISTEN "NOTIFY")) (PKT (READ-PKTS CONN)) (PKT-STRING (SUBSTRING (PKT-STRING PKT) 7)) (HOST (FOREIGN-ADDRESS CONN)) (INHIBIT-SCHEDULING-FLAG T)) (IF (UNWANTED-CONNECTION-REJECTED-P CONN) (RETURN-FROM NOTIFY-SERVER NIL)) (ANSWER-STRING CONN "Done") (COND ((OR (NULL LAST-NOTIFICATION) (NOT (STRING-EQUAL LAST-NOTIFICATION PKT-STRING))) (SETQ LAST-NOTIFICATION PKT-STRING) (SETQ INHIBIT-SCHEDULING-FLAG NIL) (SETQ HOST (OR (SI:GET-HOST-FROM-ADDRESS HOST :CHAOS) (FORMAT NIL "chaos host ~O" HOST))) (TV:NOTIFY NIL "From ~A: ~A" HOST PKT-STRING))))) (ADD-INITIALIZATION "NOTIFY" '(NOTIFY-SERVER) NIL 'SERVER-ALIST) (DEFUN NOTIFY (HOST &OPTIONAL (MESSAGE (NOTIFY-GET-MESSAGE))) "Send a brief message MESSAGE to HOST. It is printed as a notification. If MESSAGE is omitted, it is read from the terminal." (LET ((PKT (SIMPLE HOST (STRING-APPEND "NOTIFY " MESSAGE)))) (IF (STRINGP PKT) PKT (PROG1 (STRING-APPEND (PKT-STRING PKT)) (RETURN-PKT PKT))))) (DEFUN NOTIFY-ALL-LMS (&OPTIONAL (MESSAGE (NOTIFY-GET-MESSAGE)) &AUX CONNS) "Send a brief message MESSAGE to all Lisp machines. It is printed as a notification. If MESSAGE is omitted, it is read from the terminal." (ASSURE-ENABLED) (UNWIND-PROTECT (PROGN (LOOP WITH CONTACT = (STRING-APPEND "NOTIFY " MESSAGE) FOR (HOST) IN SI:MACHINE-LOCATION-ALIST AS ADDRESS = (ADDRESS-PARSE HOST) WHEN ADDRESS DO (PUSH (OPEN-CONNECTION ADDRESS CONTACT) CONNS)) (PROCESS-WAIT-WITH-TIMEOUT "Notify" (* 5 60.) #'(LAMBDA (CONNS) (LOOP FOR CONN IN CONNS ALWAYS (NEQ (STATE CONN) 'RFC-SENT-STATE))) CONNS) (LOOP FOR CONN IN CONNS AS STATE = (STATE CONN) DO (FORMAT T "~%~A: ~A" (SI:GET-HOST-FROM-ADDRESS (FOREIGN-ADDRESS CONN) :CHAOS) (case STATE (ANSWERED-STATE (PKT-STRING (READ-PKTS CONN))) (RFC-SENT-STATE "Host not responding") (OTHERWISE (FORMAT NIL "Connection in ~A?" STATE)))))) (MAPC #'CHAOS:REMOVE-CONN CONNS))) (DEFUN NOTIFY-GET-MESSAGE (&OPTIONAL STREAM) (FORMAT T "~&Message: (terminate with End)~%") (ZWEI:QSEND-GET-MESSAGE STREAM (FORMAT NIL "You are typing in a message which will be send to someone on another chaosnet machine. If you want to quit, hit the ~:@C key, to end your message hit the ~:@C key." #/ABORT #/END))) ;;; sdu serial stream server ;;; there is a possible timing screw here, with in-use-p returning NIL, ;;; and then someone else allocates the device before the actual server ;;; is entered. (DEFUN SDU-SERIAL-STREAM-IN-USE-P (ARG) (COND ((NOT (FIXP (catch-error (PARSE-INTEGER (STRING-TRIM " " ARG)) nil))) (FORMAT NIL "bad baud rate argument: ~S" arg)) ('else (LET* ((DEVICE (SEND (FS:PARSE-PATHNAME "SDU-SERIAL-B:") :HOST)) (LOCK (CDR (SEND DEVICE :LOCK)))) (COND ((NOT (SEND DEVICE :ALLOCATE-IF-EASY)) "device not available") (LOCK (FORMAT NIL "device in use by ~A" LOCK))))))) (DEFUN SDU-SERIAL-STREAM-SERVER (NETWORK-STREAM ARG) (WITH-OPEN-FILE (SERIAL-STREAM "SDU-SERIAL-B:") (SEND SERIAL-STREAM :SET-BAUD-RATE (PARSE-INTEGER (STRING-TRIM " " ARG))) (CATCH 'DEATH (LET ((PROC)) (UNWIND-PROTECT (PROGN (SETQ PROC (PROCESS-RUN-FUNCTION "SDU SERIAL STREAM => NETWORK" #'(LAMBDA (FROM-STREAM TO-STREAM SUPERIOR) (CONDITION-CASE () (do ((buf) (offset) (limit)) (()) (multiple-value (buf offset limit) (send from-stream :read-input-buffer)) (cond ((null buf) (return nil))) (send to-stream :string-out buf offset limit) (SEND TO-STREAM :FORCE-OUTPUT) (send from-stream :advance-input-buffer)) (SYS:REMOTE-NETWORK-ERROR)) (SEND SUPERIOR :INTERRUPT #'(LAMBDA () (THROW 'DEATH NIL))) (SI:PROCESS-WAIT-FOREVER)) SERIAL-STREAM NETWORK-STREAM CURRENT-PROCESS)) (STREAM-COPY-UNTIL-EOF NETWORK-STREAM SERIAL-STREAM)) (AND PROC (SEND PROC :KILL))))))) (ADD-VANILLA-CHAOS-SERVER "SDU-SERIAL-B" 'SDU-SERIAL-STREAM-SERVER :REJECTP 'SDU-SERIAL-STREAM-IN-USE-P) (DEFUN OPEN-REMOTE-SDU-SERIAL-STREAM (HOST &OPTIONAL &KEY (BAUD-RATE 1200.)) (OPEN-STREAM HOST (FORMAT NIL "SDU-SERIAL-B ~D" BAUD-RATE))) ;;;; Debugging Stuff #| (DEFUN CD-SEND ( &AUX PKT) (DISABLE) (SETQ PKT (ALLOCATE-PKT)) (TERPRI) (TERPRI) (SETF (PKT-OPCODE PKT) (PROMPT-AND-READ :EVAL-READ "~&Opcode: ")) (SETF (PKT-DEST-ADDRESS PKT) (PROMPT-AND-READ :NUMBER "~&Destination: ")) (SETF (PKT-DEST-INDEX-NUM PKT) (PROMPT-AND-READ :NUMBER "~&Destination index: ")) (SETF (PKT-SOURCE-ADDRESS PKT) (PROMPT-AND-READ :NUMBER "~&Source address: ")) (SETF (PKT-SOURCE-INDEX-NUM PKT) (PROMPT-AND-READ :NUMBER "~&Source index: ")) (SETF (PKT-NUM PKT) (PROMPT-AND-READ :NUMBER "~&Packet number: ")) (SETF (PKT-ACK-NUM PKT) (PROMPT-AND-READ :NUMBER "~&Ack packet number: ")) (SET-PKT-STRING PKT (PROMPT-AND-READ :STRING "~&Data (a string): ")) ;after SET-PKT-STRING to avoid getting stored over. (SETF (PKT-FWD-COUNT PKT) (PROMPT-AND-READ :NUMBER "~&Forwarding count: ")) (TRANSMIT-INT-PKT (CONVERT-TO-INT-PKT PKT)) (FREE-PKT PKT)) (DEFUN CD-RECEIVE ( &AUX PKT) (DISABLE) (SETQ PKT (CONVERT-TO-PKT (RECEIVE-PROCESS-NEXT-INT-PKT))) (COND (PKT (PRINT-PKT PKT)) (T NIL))) (DEFUN SOAK (CONN &AUX PKT) (AND (NUMBERP CONN) (SETQ CONN (AREF INDEX-CONN CONN))) (SETQ PKT (GET-NEXT-PKT CONN)) (COND ((= (PKT-OPCODE PKT) CLS-OP) (FORMAT T "==> CLOSED!!! <=== ~S" (PKT-STRING PKT))) (PKT (PRINT-PKT PKT))) (AND PKT (FREE-PKT PKT))) |# ;;;; Stuff for PEEK. (DEFVAR PEEK-SHORT-PKT-DISPLAY T "Display packets in short form in peek") (DEFUN PEEK-DISPLAY (&AUX CONN) (FORMAT T "~&ChaosNet Status: ~O" (TIME)) (FORMAT T (COND (ENABLE " Active!~%") (T " Deactivated.~%"))) (DO ((I 0 (1+ I))) (( I (ARRAY-LENGTH INDEX-CONN))) (COND ((ARRAYP (SETQ CONN (AR-1 INDEX-CONN I))) (PRINT-CONN CONN PEEK-SHORT-PKT-DISPLAY) (TERPRI)))) (FORMAT T "~2%Forwarded: ~O Overforwarded: ~O Lost: ~O Made: ~O Free: ~O (+~O Recorded LOS packets)~%" PKTS-FORWARDED PKTS-OVER-FORWARDED PKTS-LOST PKTS-MADE (DO ((I 0 (1+ I)) (FP FREE-PKTS (PKT-LINK FP))) ((SYMBOLP FP) I)) CURRENT-LOS-PKT-COUNT) (FORMAT T "Bad Destination: ~O Bad Bit Count: ~O Bad CRC-1: ~O Bad CRC-2: ~O~%" PKTS-BAD-DEST PKTS-BAD-BIT-COUNT PKTS-BAD-CRC-1 PKTS-BAD-CRC-2) (WHEN PENDING-LISTENS (FORMAT T "~%Pending LISTENs:~%") (DO ((L PENDING-LISTENS (CDR L))) ((NULL L)) (FORMAT T " Contact name: ~S~%" (CAR L)))) (WHEN PENDING-RFC-PKTS (FORMAT T "~%Pending RFCs:~%") (DO ((PKT PENDING-RFC-PKTS (PKT-LINK PKT))) ((NULL PKT)) (FORMAT T " Contact name: ~S~%" (PKT-STRING PKT))))) (DEFUN PRINT-BAD-PKTS () (DO ((LIST BAD-PKT-LIST (CDR LIST))) ((NULL LIST)) (FORMAT T "~&~A" (CAAR LIST)) (PRINT-PKT (CADAR LIST)) (TERPRI))) (DEFUN PRINT-RECENT-HEADERS ( &OPTIONAL (NBR #o200)) (DO ((I (\ (+ #o177 RECENT-HEADERS-POINTER) #o200) (IF (ZEROP I) #o177 (1- I))) (COUNT NBR (1- COUNT))) ((ZEROP COUNT)) (FORMAT T "~%Nbr:~O Opcd:~O(~A). Len:~O bytes. " (RCNT-PKT-NUM I) (RCNT-OPCODE I) (COND ((< (RCNT-OPCODE I) (LENGTH OPCODE-LIST)) (NTH (RCNT-OPCODE I) OPCODE-LIST)) (( (RCNT-OPCODE I) DAT-OP) 'DAT) (T (FORMAT NIL "==> ~O <==" (RCNT-OPCODE I)))) (RCNT-NBYTES I)) (FORMAT T "From ~O ~O to ~O ~O, Fwded ~O Times, Rcrded:~O" (si:get-host-from-address (RCNT-SOURCE-ADDRESS I) :chaos) (RCNT-SOURCE-INDEX I) (si:get-host-from-address (RCNT-DEST-ADDRESS I) :chaos) (RCNT-DEST-INDEX I) (RCNT-FWD-COUNT I) (RCNT-TIME-RECORDED I)))) (DEFUN PRINT-ROUTING-TABLE (&OPTIONAL (STREAM *STANDARD-OUTPUT*)) "Display onto STREAM a list of subnets, gateways, and chaonet costs for each subnet." (FORMAT STREAM "~&Subnet Fastest Gateway~35TCost") (DOTIMES (I (ARRAY-LENGTH ROUTING-TABLE)) (IF (= MY-SUBNET I) (FORMAT STREAM "~&~3O Direct" MY-SUBNET) (WHEN (< (CLI:AREF ROUTING-TABLE-COST I) #o1000) (LET ((GATEWAY (AREF ROUTING-TABLE I))) (FORMAT STREAM "~&~3O ~O ~A~35T~O" I GATEWAY (HOST-DATA GATEWAY) (AREF ROUTING-TABLE-COST I))))))) ;;; Prints as much info as possible (DEFUN DUMP-GUTS ( &AUX (PEEK-SHORT-PKT-DISPLAY NIL)) (PEEK-DISPLAY) (FORMAT T "~2%Recent headers:") (PRINT-RECENT-HEADERS))