Google
 

Trailing-Edge - PDP-10 Archives - SRI_NIC_PERM_SRC_3_19910112 - stanford/ftp/ftp.mac
There are 5 other files named ftp.mac in the archive. Click here to see a list.
;[SRI-NIC]SRC:<STANFORD.FTP>FTP.MAC.265, 31-Aug-87 17:15:38, Edit by MKL
; use GTDOM% instead of GTHST%
;<FTP>FTP.MAC.289,  3-May-85 19:34:19, Edit by LOUGHEED
; Alternate sockets capability (F%ALTS) is now always on by default.
;  Easier than guessing which hosts can't cope.
;<FTP>FTP.MAC.288, 30-Apr-85 00:03:40, Edit by LOUGHEED
; More from SRA:
;  - Send "anonymous" instead of "ANONYMOUS" for UNIX systems.
;  - Make SET NO ANONYMOUS-LOGIN work
;<FTP>FTP.MAC.287, 29-Apr-85 03:19:00, Edit by LOUGHEED
; From Rob Austein (SRA@XX):
;  Fix initialization of FRECOR so that NLST (etc.) works
;<FTP>FTP.MAC.286, 20-Apr-85 14:45:38, Edit by LOUGHEED
; Reset all defaults if switching from one operating system to another.
;  Prevents nonsense like TOPS-20 paged transfers to UNIX systems.
;<FTP>FTP.MAC.285, 12-Feb-85 21:50:59, Edit by LOUGHEED
; From Rutgers:
;  - Print out file's protection when doing a retrieve operation
;  - RLOGIN code uses TMPHST to avoid clobbering TEMP.
;<FTP>FTP.MAC.284, 29-Oct-84 18:39:52, Edit by LOUGHEED
; Somehow STTXT8 got lost in the last shuffle
;<FTP>FTP.MAC.283, 29-Oct-84 18:20:29, Edit by LOUGHEED
; Add SITE as invisible synonym for QUOTE command
; From Rutgers:  Try to use RHOSTS file, if any, to default logins
;<FTP>FTP.MAC.282, 16-Sep-84 14:52:24, Edit by LOUGHEED
; STTEXT routine for setting Multics defaults correctly
;<FTP>FTP.MAC.281,  4-Sep-84 01:46:18, Edit by LOUGHEED
; Support the SET [NO] ANONYMOUS-LOGIN command
;<FTP>FTP.MAC.280, 31-Aug-84 15:28:04, Edit by SATZ
; Make transfer defaults protocol dependent
;<FTP>FTP.MAC.279, 29-Aug-84 16:10:05, Edit by SATZ
; Parse for quoted strings too when getting Unix file names
;<FTP>FTP.MAC.278, 28-Aug-84 14:43:04, Edit by LOUGHEED
; On continue after a fancy quit, don't set connected directory
;  if it wasn't set before.
;<FTP>FTP.MAC.277, 27-Aug-84 16:21:23, Edit by LOUGHEED
; Fix help message in QUOTE command parse
;<FTP>FTP.MAC.276, 25-Aug-84 16:15:07, Edit by LOUGHEED
;<FTP>FTP.MAC.275, 25-Aug-84 15:30:58, Edit by LOUGHEED
;<FTP>FTP.MAC.274, 25-Aug-84 14:14:39, Edit by LOUGHEED
; Add support for a QUOTE command
;<FTP>FTP.MAC.273, 23-Aug-84 17:00:25, Edit by SATZ
; Make Unix systems use a default byte size of 8.
;<FTP>FTP.MAC.272, 20-Aug-84 23:28:45, Edit by SATZ
; Use a single asterisk for the default file spec. for Unix
;<FTP>FTP.MAC.271, 15-Aug-84 19:14:50, Edit by LOUGHEED
; For TOPS-20 filename bizarreness:
;  - Make FFLBUF stack variable NAMSTL words long instead of just 10.
;  - Make STKLEN be 512. words long so that FFLBUF fits.
;<FTP>FTP.MAC.270, 31-Jul-84 13:26:26, Edit by LOUGHEED
; Set MULTIPLE GET default strategy to HEURISTIC.  This works around the
;  problem of hosts such as SU-STAR and MIT-MC who claim to use NLST, but
;  lie and hang us up during a single file GET command.
;<FTP>FTP.MAC.269, 16-May-84 12:16:55, Edit by KRONJ
; Clean up GTJFN block in file parse for OUTPUT subcommand of DIR
; Fix PRMCNF to really index through AC1
;<FTP>FTP.MAC.268, 10-May-84 16:25:42, Edit by KRONJ
; PUT is another synonym for SEND
;<FTP>FTP.MAC.267, 26-Apr-84 19:58:25, Edit by KRONJ
; OPNSTO checks FB%DIR!FB%NEX so we don't need to duplicate that
;<FTP>FTP.MAC.265, 26-Apr-84 14:26:27, Edit by KRONJ
; Move OPNSTO out to FTPDEF so can use common code with PUPPNT and PUPFSV
; (and so printing files can call CKSPAR to set everything nicely)
;<FTP>FTP.MAC.264, 23-Apr-84 14:57:30, Edit by KRONJ
; 36-bit files with type unspecified are assumed to be text
;<FTP>FTP.MAC.263, 19-Apr-84 22:04:28, Edit by KRONJ
; Separate-line GET local file parse forgot to default generation
;<FTP>FTP.MAC.262, 31-Mar-84 17:37:22, Edit by LOUGHEED
; Remove GENAUT and GENPRO extern definitions - not used by TCP code
; Define SETWDT here instead of in PUPFTP.MAC
	SEARCH FTPDEF

VMAJOR==3			; Major version (not protocol version)
VMINOR==0			; Minor version
VEDIT==277			; Edit version
VWHO==0				; Who last edited (4 = Stanford)

	PTITLE(FTP, -- Pup and Internet FTP user program)
	SUBTTL David Eppstein / Stanford University / 1-Oct-83

	;; Largely based on the PUPFTP module written in 1976
	;; by Ed Taft at Xerox PARC, Copyright 1979 Xerox Corp.
	;;
	;; Copyright (C) 1984 Board of Trustees, Stanford University
	;; The information in this software is subject to change without
	;; notice and should not be construed as a commitment by Stanford
	;; University.  Stanford assumes no responsibility for the use or
	;; reliability of this software.

	.REQUIRE FTPUTL		;Yoyos
	.REQUIRE FTPPRO		;Protocols (will require any necessary files)
	.REQUIRE FTPDIR		;Directory listing subroutine
	.REQUIRE HSTNAM		;Host name parsing routines
	.REQUIRE FTPLUD		;Postlude

	EXTERN STRCMP,CHKDSK,.SFUST,KILFIL,SETPLT,.JBSYM,.JBUSY,JFNVRS
	EXTERN NXTFIL,$GTNAM,$GTPRO,$RMREL,PROTS,GETNMB,SKPDIR,SHXDEF
	EXTERN FILPRP,DEFPRP,TMPPRP,SETPRP,ZDATPA,TYPBUF,PRTDSP,OPNSTO
SUBTTL Parameters, storage, and macros

STKLEN==:^D512			; Length of stack
IPDLEN==10			; TAKE file maximum nesting level

DEFINE KFLS (NAME,FLG) <LS FLG>
KFLAGS(KFLS)			; Define permanent storage for KEEP flags
; Macros for building command tables

; Use DEFTAB to build a command-table macro

DEFINE DEFTAB (TNAME,BODY) <
  DEFINE TNAME (PREF) <
    DEFINE T (CMD,SYN,FLG) <
	IFB <SYN>,<KEY CMD,DOT(PREF,CMD),FLG>
	IFNB <SYN>,<KEY CMD,DOT(PREF,SYN),FLG>
    >;DEFINE T
	TABLE
	BODY
	TEND
  >;TNAME
>;DEFTAB

DEFINE DOT (PREF,CMD) <PREF'.'CMD>

DEFINE TI (CMD,SYN,FLG) < T (CMD,SYN,CM%INV) >
DEFINE TA (CMD,SYN) < T (CMD,SYN,CM%INV!CM%ABR) >
; COMND JSYS support data.

CMDBLK::0			; Reparse address.
	0			; Input, Output JFNs.
	0			; Ctrl-R text.
	-1,,CMDBUF		; Pointer to start of text buffer.
	-1,,CMDBUF		; Pointer next input to be parsed.
	CMDBFW*5		; Space left in buffer.
	CMDBFW*5		; Number of characters left in buffer.
	-1,,ATMBUF		; Pointer to atom buffer.
	ATMBFW*5		; Size of atom buffer.
	CMDGTJ			; Pointer to GTJFN% argument block.

CMDBFW==^D50			; Size in words of command buffer.
ATMBFW==^D50			; Size in words of atom buffer.

LS CMDBUF,CMDBFW		; Command buffer.
LS ATMBUF,ATMBFW		; Atom buffer.

LS CMDGTJ,20			; COMND JSYS GTJFN% block.

; Username and remote filename break mask.
REMBRK:	777777,,777760		; All controls are breaks (?)
	400000,,000020		; Space and question marks are breaks
	000000,,000000		; Everything else
	000000,,000020		; except DEL is not a break.

; Host name break mask
HSTBRK::777777,,777760		; All controls are breaks
	737644,,001760		; Digits, -, #, +, . not breaks
	400000,,000260		; Alphabetics and square brackets
	400000,,000760		; are not breaks
; Macros

; Get a keyword.
DEFINE KEYWRD (TABLE) <
	MOVEI B,[FLDDB. .CMKEY,,TABLE]
	CALL .COMND
	 ERMSG <Invalid subcommand>
>
; Assemble command dispatch tables
DEFTAB COMMANDS,<
	TA B,BY
	TA BY
DOT(PREF,BY):
	T BYE
	TA C,CO
	TI CD,CONNECT		; Lots of ways to connect to directories
	TI CHDIR,CONNECT
	TI CLOSE,DISCONNECT
DOT(PREF,CO):
	T CONNECT
	TI CPATH,CONNECT
	TI CWD,CONNECT
	TI DDT
	T DELETE
	TA DI
DOT(PREF,DI):
	T DIRECTORY
	TI DISCONNECT
	TI DISPLAY
	T EXIT
	T GET
	TA H
	TI HALT
DOT(PREF,H):
	T HELP
	TI HOST
	TA I
	TA IN,I
	TI INFORMATION
DOT(PREF,I):
	T INSTALL
	TA L
	TI LIST,DIRECTORY
DOT(PREF,L):
	T LOGIN
	TI MULTIPLE
	T OPEN
	T PRINT
	TA PU
DOT(PREF,PU):
	T PUSH
	TI PUT,SEND
	TA Q
DOT(PREF,Q):
	T QUIT,HALT
	T QUOTE
	TA R
	TA RE,R
DOT(PREF,R):
	TI RECEIVE,GET
	T RENAME
	TI RESTORE,GET
	TI RETRIEVE,GET
	TA S
	TI SAVE,SEND
	TA SE,S
DOT(PREF,S):
	T SEND
	T SET
	TI SHOW,DISPLAY
	TI SITE,QUOTE
	TA ST
DOT(PREF,ST):
	T STATUS,STS		; Don't conflict with SET STATISTICS
	TI STORE,SEND
	TA T
	T TAKE
DOT(PREF,T):
	T TYPE
	T UPDATE
	TI VDIRECTORY
>

CMDDSP:	COMMANDS(C)
SUBTTL Main loop and user command handling

; Start of program

START::	RESET%			; Initialize the world
	PCLEAR (DATA,[.FHSLF])	; Clear local word storage.
	PCLEAR (GDATA,[.FHSLF])	; Clear global word storage.
	PCLEAR (DATPAG,[.FHSLF]) ; Clear local page storage.

	MOVE P,[IOWD STKLEN,STACK]	; Setup stack
	MOVE IOP,[IOWD IPDLEN,IPDL]	; and TAKE file JFN stack.
	MOVX F,F%INIC!F%ALTS	; Initial flags
	MVI. ZDATPA+1,FRECOR	; Set free storage pointer to top of core
	MVI. VB.NRM,VRBSTY	; Normal verbosity
	MVI. ^D10,SHXDEF	; Start with ten pages per hash mark
	MVI. MG.HEU,MULGET	; Default MULTIPLE GET strategy is heuristic

	GJINF%			; Set up local user name.
	HRLI A,500000
	MOVE B,A
	HRROI A,LCLUSR
	DIRST%
	 ERJMP .+1
	SETZM B
	IDPB B,A
	CALL INIKEP		; Initialize kept atributes
	CALL INIPSI		; Initialize psi system
	CALL CMDFIL		; Read input from command file
				; (if that returned, we didn't have one)
	TXOA F,F%TEMP		; We want to print the header
REENTR:: TXZ F,F%TEMP		; We don't want to print the header
	MOVE IOP,[IOWD IPDLEN,IPDL] ; Reset TAKE file JFN stack.
	MVI. <.PRIIN,,.PRIOU>,CMDBLK+.CMIOJ ; Set command JFNs
	MOVX A,.RSINI
	RSCAN%			; Read JCL input.
	 ERJMP COMHDR		; Couldn't, assume none.
	JUMPE A,COMHDR		; If none, go start up normally
	MOVE C,A		; Move count to AC 3
	MOVEI A,.PRIIN		; From the terminal,
	MOVEI B,.NULIO		; to the bit bucket,
	MOVEI D," "		; until out of JCL or a space reached,
	SIN%			; read those bits!
	JUMPE C,COMHDR		; All gone, go start normally.
	HRRM B,CMDBLK+.CMIOJ	; Set null JFN as command output (input TTY)
	JRST COMINI		; Go read a command.

DEFINE MOVKF (NAME,FLG) <MOVEM KF,FLG>

INIKEP:	MOVX KF,K%INIT		; Get initial attributes
	KFLAGS(MOVKF)		; Set in each command's KEEP word
	RET
; Main command loop

COMHDR:	IFXN. F,F%TEMP		; If we still want a header
	  HRROI A,VERTXT
	  TYPE <Stanford TOPS-20 FTP %1S, type HELP if you need it.%/>
	ENDIF.
COMLP::	SKIPN A,CMDBLK+.CMIOJ	; Get I/O JFNs.
	 JRST REENTR		; If none set, go back and read JCL etc.
	CAME A,[.PRIIN,,.NULIO]	; Was input from JCL?
	 JRST COMINI		; No, go on.
	MOVEI A,.PRIOU		; Get terminal output JFN
	HRRM A,CMDBLK+.CMIOJ	; and restore it as command output JFN.
	CALL FQUIT		; Close connection if open and stop program
COMINI:	SETZM ABTLOC		; No aborts left hanging around
	CALL KILFIL		; Flush input or output file if one got left
	PROMPT [ASCIZ/FTP>/]
	CALL SETCMD
	MOVE P,[IOWD STKLEN,STACK] ; Fix stack
	CALL FLTFIL		; Flush temporary parse files
	HLLZS F			; Clear temporary flags
	MOVE B,MULGET		; Get MULTIPLE GET state
	CAIE B,MG.TMP		; Temporarily on?
	 IFSKP. <MVI. MG.OFF,MULGET> ; Yes, turn off again
	CAIE B,MG.THE		; Temporarily on from heuristic?
	 IFSKP. <MVI. MG.HEU,MULGET> ; Yes, set back to heuristic
	MOVEI B,[FLDDB. .CMKEY,,CMDDSP,<
  Type HELP for a brief example of how to use FTP.
  Type HELP <command> for a detailed explanation of a particular command.
  Now type an FTP command,>,,[
		 FLDDB. .CMKEY,CM%SDH,SETDSP,,,[
		 FLDBK. .CMFLD,CM%SDH,,<type a network host name or number>,,HSTBRK]]]
	CALL .COMND
	 ERMSG <Unrecognized command>
	LOAD D,CM%FNC,(C)	; Get FDB parsed
	CAIE D,.CMKEY		; Is it a command?
	IFSKP.
	  HRRZ A,(B)		; Yes, get handler routine
	ELSE.
	  CALL VALFLD		; Make sure we have a real field
	   ERMSGX <Null command or host name given>
	  MOVEI A,C.OPNM	; Set up with net num handler
	ENDIF.
	CALL (A)		; Dispatch to command handler
	JRST COMLP		; Back to top
SUBTTL MULTIPLE command
; see also S.MULT (SET MULTIPLE-GET)

H.MULT:	ASCIZ/There are two ways of retrieving files over TCP connections:
one can do a MULTIPLE GET, in which case FTP first asks the remote
server for a list of files to retrieve and then asks to retrieve each
of the file names returned; or one can do a non-MULTIPLE GET in which
FTP merely asks the server for the filename you specify.  In the
latter case the file name must refer to only one file.

FTP has various options for which style of GET to use:
  - ALWAYS always tries to use MULTIPLE GET
  - NEVER always uses non-MULTIPLE GET
  - HEURISTIC (the default) uses MULTIPLE GET only if there are wildcard
    characters (i.e. "*" or "%") in the remote filename.

You can set these options with SET MULTIPLE-GET; you can temporarily
override them with the MULTIPLE command.  If FTP thinks the remote
host can not do MULTIPLE GETs it will stop until a new connection is
made; either of the above commands forces it to try again.  If smart
directories are available then they will always be used instead of
MULTIPLE GETs (see SET SMART-DIRECTORIES).
/

DEFTAB MULCMD,<
	T GET
	TI INSTALL
	TI PUT,SEND
	TI RECEIVE,GET
	TI RESTORE,GET
	TI RETRIEVE,GET
	TI SAVE,SEND
	TI SEND
	TI STORE,SEND
	TI UPDATE
>
T.MULT:	MULCMD(C)

LS MULGET			; MULTIPLE GET state
				; LH: 0/Ok, -1/Not ok for this host
				; RH: MG.XXX normal MULTIPLE GET state

C.MULT:	HRRZ B,MULGET		; Get state, forget if host can handle
	MOVE B,[ MG.ON		; MG.ON stays that way
		 MG.TMP		; MG.OFF becomes temporarily on
		 MG.TMP		; MG.TMP stays that way
		 MG.THE		; MG.HEU becomes temporarily on
		 MG.THE ](B)	; MG.THE stays that way
	MOVEM B,MULGET		; Save new MULTIPLE GET state
	MOVEI B,[FLDDB. .CMKEY,,T.MULT,<file transfer command,>,<GET>]
	CALL .COMND		; Parse command
	 ERMSG <Invalid MULTIPLE subcommand>
	HRRZ B,(B)		; Get routine to call
	CALLRET (B)		; Go run the command, GET or SEND
SUBTTL HELP command.

HLPTXT:	ASCIZ/FTP transfers files between computers on the Ethernet or the
ARPAnet. The first thing you should do when using PUPFTP is to type the
name of the machine you are transferring files to, and your username there:

FTP>SCORE				; The computer to connect to
< SCORE Pup FTP Server 1.30 16-Apr-82	; It will reply something like this
FTP>LOGIN CSD.KRONJ			; Your username there
Password:				; Your password (not echoed)

Then use the SEND command to send a file to the other machine, or the
GET command to get a file from the other machine to this machine:

FTP>SEND FOO.FAH (to remote file) BAR.BAZ	; Give SEND command
 FOO.FAH.3 => BAR.BAZ.-1 !! [OK]		; The computer types this

FTP>GET BAR.BAZ (to local file) FOO.FAH		; Give GET command
 BAR.BAZ.3 => FOO.FAH.2 !! [OK]			; The computer types this

When you are done with FTP, use the EXIT command to return to the EXEC.
Type ^G to abort any subcommand parse or multiple-file transfer.
/

CMDHLP:	COMMANDS(H)

H.HELP:	ASCIZ/Use the HELP command for general information about FTP,
or for a detailed explanation of a FTP command.

FTP>HELP <command>
	describes a particular command, e.g. HELP HELP prints this text.

FTP>HELP
	gives an introduction to FTP, with examples of common commands.
/

C.HELP: NOISE <on FTP command>
	MOVEI B,[FLDDB. .CMCFM,,,,,[
		 FLDDB. .CMKEY,,CMDHLP,<FTP command,>,,[
		 FLDDB. .CMKEY,CM%SDH,SETHLP]]]
	CALL .COMND
	 ERMSG <Invalid FTP command>
	LOAD C,CM%FNC,(C)
	CAIE C,.CMCFM		; Bare return?
	IFSKP.
	  HRROI A,HLPTXT	; Main help, point to general examples
	  PSOUT%
	  RET
	ENDIF.
	HRRZ D,(B)		; Get pointer to string
	CAIE D,H.SET		; Was it SET?
	IFSKP.
	  NOISE <subcommand>
	  MOVEI B,[FLDDB. .CMCFM,,,,,[
		   FLDDB. .CMKEY,,SETHLP,<SET subcommand,>]]
	  CALL .COMND
	   ERMSG <Invalid SET subcommand>
	  LOAD C,CM%FNC,(C)
	  CAIN C,.CMCFM		; Bare return?
	   JRST C.HLP1		; Yes, go give main SET help
	  HRRZ D,(B)		; Else get subcommand help text
	ENDIF.
	CALL CONFRM		; Confirm parse
C.HLP1:	HRRO A,D
	PSOUT%
	RET
SUBTTL TAKE command.
H.TAKE:	ASCIZ/The TAKE command tells FTP to read commands from a file.
When the end of the file is reached, FTP will once again
read commands from the terminal.

While reading commands from a file, double confirmation is not required
for any command; e.g. DELETE.  Passwords (e.g. for the LOGIN command)
are always read from the terminal.  Any error encountered will terminate
command file execution.

The file FTP.INIT in the user's home directory is automatically
read when FTP is started if it exists.
/

C.TAKE:	HRLOI B,2-IPDL-IPDLEN(IOP)
	JUMPGE B,[ERMSGX <TAKE files nested too deeply>]
	NOISE <commands from file>
	SETZM CMDGTJ
	MOVE B,[CMDGTJ,,CMDGTJ+1]
	BLT B,CMDGTJ+20-1	; Zero GTJFN command block.
	MOVEI B,[FLDDB. .CMIFI]
	CALL .COMND		; Parse input file.
	 ERMSG <Invalid TAKE file>
	MOVEM B,TMPJFN		; Save JFN for reparse
	CALL CONFRM		; Finish command parse.
	MOVE A,TMPJFN		; Get JFN back
	SETZM TMPJFN		; Don't lose it at end of command
TAKFIL:	MOVX B,FLD(7,OF%BSZ)!OF%RD
	OPENF%			; Open the file.
	 ERMSG <Couldn't open TAKE file>
	PUSH IOP,CMDBLK+.CMIOJ	; Save old I/O JFNs.
	MOVSS A			; Put JFN in left half
	HRRI A,.NULIO		; and get null output JFN for right half.
	MOVEM A,CMDBLK+.CMIOJ	; Save as command input and output JFNs.
	RET

CMDFIL:	HRROI B,LCLUSR		; Point to our user name
	HRROI A,TEMP		; Into temporary space
	WRITE (PS:<%2S>FTP.INIT.0) ; Finish off filename.
	MOVX A,GJ%SHT!GJ%OLD
	HRROI B,TEMP
	GTJFN%			; Get a JFN on the file
	 RET			; No such file, return now.
	SETZM CMDBLK+.CMIOJ	; When file execution ends read JCL etc.
	CALL TAKFIL		; Set up to read commands from the file.
	JRST COMLP		; Go start reading commands (fixes stack).

ENDTAK:	HLRZ A,CMDBLK+.CMIOJ	; Get command file JFN.
	CAIN A,.PRIIN		; If it's the terminal, just return +2.
	 RETSKP
	JUMPE A,RSKP		; If it's not set yet, just return +2.
	CLOSF%			; Else  close it,
	 NOP			; but don't care if the close fails.
	POP IOP,CMDBLK+.CMIOJ	; Restore old input and output pointers.
	RET

FLSTAK::CALL ENDTAK		; Close this TAKE file
	 JRST FLSTAK		; More left, go back for them
	MOVEI A,.PRIOU
	DOBE%			; Wait for all typeout to finish.
	MOVEI A,.PRIIN
	CFIBF%			; Then clear typeahead.
	RET

; Save some parse parameters
; Must *not* be called in the middle of a parse!  Trashes reparse address...

PUSHIO::EXCH A,(P)		; Save an accumulator, get caller's address
	EXCH A,CMDBLK+.CMFLG	; Save caller addr, get reparse address
	EXCH A,(P)		; Restore AC, save reparse address
	PUSH P,CMDBLK+.CMIOJ	; Save command I/O JFNs
	PUSH P,CMDBLK+.CMRTY	; Save prompt
	PUSH P,A		; Save accumulator
	HRRZ A,CMDBLK+.CMIOJ	; Get command output JFN.
	CAIN A,.PRIOU		; If anything other than primary output
	IFSKP.
	  MOVE A,[.PRIIN,,.PRIOU] ; Get canonical input/output pointers
	  MOVEM A,CMDBLK+.CMIOJ	; and set them in command block.
	ENDIF.
	POP P,A			; Restore accumulator again
	CALL @CMDBLK+.CMFLG	; Call caller in this strange way
	 IFSKP. <AOS -3(P)>	; Propagate skip return.
	POP P,CMDBLK+.CMRTY	; Restore prompt
	POP P,CMDBLK+.CMIOJ	; Restore command I/O JFNs.
	POP P,CMDBLK+.CMFLG	; Restore reparse address
	RET			; Return to caller's caller.
SUBTTL EXIT and QUIT commands.
H.EXIT:	ASCIZ/The EXIT command closes any open connection and returns
to the EXEC.  If FTP is continued a new foreign host name
will have to be given before any file transfers can occur.
/

C.EXIT:	TXNN F,F%COPN		; Connection open?
	 JRST C.HAL0		; No, just exit
	NOISE2 <and close connection>,<to EXEC>
	CALL CONFRM
	CALL @.CLOSE(V)		; Close it
	JRST EXEUNT

; HALT command.
H.HALT:	ASCIZ/The QUIT command returns to the EXEC without closing any open
connection.  FTP can be continued without having to specify
the foreign host name again.  If SET FANCY-QUIT is in effect, FTP
will actually close the connection and then re-open it after it
is continued.
/

C.HALT:	TXNN F,F%COPN		; Connection open?
	 JRST C.HAL0		; No, give simple noise word
	IFXE. F,F%QUIT		; Fancy quit?
	  NOISE2 <without closing connection>,<to EXEC>
	  JRST C.HAL1
	ENDIF.
	NOISE2 <temporarily closing connection>,<to EXEC>
	CALL CONFRM		; Confirm fancy quit
FQUIT:	JE F%QUIT,F,EXEUNT	; If got here with no conn, don't try to close
	CALL @.CLOSE(V)		; Disconnect
	HALTF%			; Stop the program
	TXO F,F%STYO		; Suppress terminal output for rest of command
	CALL @.OPEN(V)		; And re-open
	 RET			; Ignore errors
	CALL @.LOGIN(V)		; Log in again
	 RET			; Ignore errors
	SKIPE CONNAM		; Skip if no connected directory 
	 CALL @.CWDIR(V)	; Else, set working directory appropriately
	  RET			; Ignore errors
	RET			; All done

C.HAL0:	NOISE <to EXEC>
C.HAL1:	CALL CONFRM
EXEUNT:	HALTF%			; Exit FTP
	RET			; Back to command loop if resumed
SUBTTL PUSH command.

LS XECFRK			; Fork handle of the pushed EXEC

H.PUSH:	ASCIZ/The PUSH command creates an inferior EXEC to FTP and runs it.
To return to FTP use the POP command from the EXEC.
/

C.PUSH: NOISE <to EXEC>
	CALL CONFRM
	SKIPE A,XECFRK		; Do we have a fork already?
	IFSKP.
	  MOVX A,GJ%OLD!GJ%SHT
	  HRROI B,[ASCIZ /SYSTEM:EXEC.EXE/]
	  GTJFN%		; Get JFN on the EXEC
	   JERMSG <Can't find EXEC!>
	  MOVEM A,TMPJFN	; Save the JFN
	  MOVX A,CR%CAP		; With our capabilities
	  CFORK%		; Make a new fork
	   JERMSG <Couldn't make a new fork>
	  MOVEM A,XECFRK	; Save fork handle
	  MOVSI A,(A)		; Get fork,,jfn
	  HRR A,TMPJFN		; JFN in right half (will go away eventually)
	  GET%			; Get file into fork
	  HLRZ A,A		; Now just get bare fork handle
	ENDIF.
	SETZ B,			; Entry point 0
	SFRKV%			; Start fork
	WFORK%			; Wait for it to terminate
	RET			; Resume program
SUBTTL DISCONNECT command

H.BYE:
H.DISC:	ASCIZ/The BYE command breaks the connection to the other computer.
A new connection will have to be specified by typing the foreign
host name or with the OPEN command before any more files can
be transferred.
/

C.BYE:	NOISE <break connection with remote host>
	JRST C.DIS1		; Join common code

; DISCONNECT and CLOSE (synonyms for BYE) come in here
C.DISC:	NOISE <connection with remote host>
C.DIS1:	CALL CONFRM
	TXNN F,F%COPN		; Is there a connection open now?
	 RET			; No, nothing to do.
	CALLRET @.CLOSE(V)	; Yes, go run handler routine
SUBTTL STATUS command

H.INFO:
H.STS:
H.DISP:	ASCIZ/The STATUS command types various information about the state
of the FTP program, including the version, connected host,
logged in user name, and parameters changed with the SET command.

There are various subcommands to list only part of the status
information available; the default is to display it all.
/

C.STS:	NOISE <of>
	JRST C.DSP1

C.INFO:	NOISE <about>
	JRST C.DSP1

C.DISP: NOISE <status of>
C.DSP1:	MOVEI B,[FLDDB. .CMKEY,,STSTBL,,<ALL>]
	CALL .COMND		; Parse keyword
	 ERMSG <Invalid STATUS subcommand>
	CALL CONFRM		; Finish parse
	HRRZ B,(B)		; Get bare keyword
	CALLRET 0(B)		; Jump to it

STSTBL:	TABLE
	KEY ALL,DSPALL
	KEY CONNECTION,DSPCON
	KEY LOGIN,DSPLGN
	KEY PROGRAM,DSPPRG
	KEY SETTINGS,DSPSET
	TEND

; Here to display all status settings
DSPALL:	CALL DSPPRG		; Display program version
	CALL DSPSET		; And parameter settings
	CALL DSPLGN		; And login parameters, drop into DSPCON

; Here to display connection setting
DSPCON:	IFXE. F,F%COPN
	  TYPE <No connection is open%/>
	  RET
	ENDIF.
	HLRO A,V		; Get network type
	HRROI B,HSTSTR		; And host name buffer
	TYPE <Connected to %1S host %2S%/>
	CALLRET @.STAT(V)	; Type protocol-dependant status

; Here to display program version
DSPPRG:	HRROI A,VERTXT
	MOVE B,VERDAT
	HRROI C,VERWHO
	TYPE <Stanford TOPS-20 FTP user process
Version %1S, compiled %2T by %3S>
	HRROI B,VERHST
	SKIPE VERHST
	 TYPE < at %2S>
	TYPE <%/>		; Add CRLF to finish off
	HLRZ A,CMDBLK+.CMIOJ
	CAIE A,.PRIIN
	 TYPE <Reading commands from file %1F%/>
	RET
; Here for login parameter status

DSPLGN:	TRVAR <OLGDEF>		; Storage for last-typed username
	SKIPN USRNAM
	IFSKP.
	  HRROI A,USRNAM
	  TYPE <Remote username is %1S>
	  HRROI A,USRACT
	  SKIPE USRACT		; Is there an account too?
	   TYPE <, remote account is %1S>
	  TYPE <%/>
	ENDIF.

	HRROI A,DEFPRP+P.DIRE
	SKIPE DEFPRP+P.DIRE
	 TYPE <Default directory for file transfers is %1S%/>

	SKIPN B,OLDLGN		; Non-default old-login action?
	IFSKP.
	  HRROI B,[ ASCIZ/always/
		    ASCIZ/never/ ]+1(B)
	  TYPE <Old logins are %2S kept for new hosts%/>
	ENDIF.

	MOVE D,ULGDEF		; Get top of default login list
	SETZM OLGDEF		; No default yet
	DO.
	  JUMPE D,ENDLP.	; If zero, done
	  CALL S1DLGN		; Type one
	  HRRZ D,(D)		; Get next in list
	  LOOP.			; Go back for it
	ENDDO.
	CALLRET PCRIF		; Finish with a new line
; subroutine for default login display

S1DLGN:	SKIPGE (D)		; Is there a default there?
	 RET			; No, skip it
	MOVE C,ULGDEF		; Get top of list again
	DO.
	  CAMN C,D		; If the address we already had
	   EXIT.		; Then we have gone through all prev, exit loop
	  MOVE A,1(C)		; Get protocol info
	  CAME A,1(D)		; Same as before?
	  IFSKP.		; If not, then these two are different
	    MOVE A,2(C)		; Get host number
	    CAMN A,2(D)		; Same as this host address?
	     RET		; Then duplicate, forget it
	  ENDIF.
	  HRRZ C,(C)		; Else get next in the list
	  LOOP.			; And go back to check it.
	ENDDO.

	HRROI B,3(D)		; Point to the string
	SKIPE A,OLGDEF		; Get old pointer
	 CALL STRCMP		; Same as what we just typed?
	  IFSKP.
	    TYPE <, >		; Yes, just type comma
	  ELSE.
	    HRROI B,3(D)	; Get pointer to user name again
	    MOVEM B,OLGDEF	; Save as last typed
	    TYPE <%_Login defaults to %2S on >
	  ENDIF.

	HRROI A,TEMP		; Into temporary storage
	MOVE B,2(D)		; With host number
	MOVEI C,1(D)		; Pointing to protocol info
	CALL $GTNAM		; Get host name
	 NOP			; Can't fail
	HRROI A,TEMP		; Point to temporary again
	CALL $RMREL		; Remove ugly relative domains
	HRROI A,TEMP		; And host name
	PSOUT%			; Type out the name we got
	RET
; Here for random parameter settings

DSPSET:	CALL SHKPAR		; Show kept parameters
	CALL SHTTYP		; Show transfer type parameters

	SKIPN A,MULGET		; Strange multiple-get state?
	IFSKP.
	  IFL. A
	    TXNE F,F%COPN	; If "remote file server" meaningful
	     TYPE <Remote file server does not understand MULTIPLE GET%/>
	  ELSE.
	    HRRO A,[ [ASCIZ/never/] ;MG.OFF
		     [ASCIZ/never/] ;MG.TMP
		     [ASCIZ/heuristically/] ;MG.HEU
		     [ASCIZ/heuristically/] ]-1(A) ;MG.THE
	    TYPE <Using MULTIPLE GET %1S%/>
	  ENDIF.
	ENDIF.

	MOVE A,SHXDEF		; Get hash mark interval
	CAIN A,^D10		; Is it something different?
	IFSKP.
	  IFE. A
	    TYPE <Hash marks are not displayed in file transfers%/>
	  ELSE.
	    TYPE <Hash marks are displayed every %1D pages in file transfers%/>
	  ENDIF.
	ENDIF.

	CALL TYPCNF		; Show confirmations required
	MOVE A,VRBSTY		; Get verbosity
	HRRO B,[[ASCIZ/super-terse/]
		[ASCIZ/terse/]
		[ASCIZ/normal/]
		[ASCIZ/verbose/]
		[ASCIZ/extra-verbose/]
		[ASCIZ/debugging/]](A)
	CAIE A,VB.NRM		; If normal then be quiet
	 TYPE <Verbosity level is set to %2S%/>

	MOVSI C,-NFLAGS		; Make AOBJN pointer
	DO.
	  HRRO A,FLGDSP(C)	; Point to string for flag
	  HLRZ B,FLGDSP(C)	; Get the flag there
	  TLNE F,(B)		; Set in flags?
	   TYPE <%1S%/>		; Yes, type string for it
	  AOBJN C,TOP.		; Back for the next
	ENDDO.
	RET

FLGDSP:	F%NSMD![ASCIZ/Smart directory listings for TCP are disabled/]
	F%MUNG![ASCIZ/Generation numbers of remote file specs are included/]
	F%NCSM![ASCIZ/Data connections are not checksummed/]
	F%QUIT![ASCIZ/Connections are invisibly closed and re-opened on QUIT/]
	F%STAT![ASCIZ/Statistics of transfer rates are collected/]
	F%LOWR![ASCIZ/Wildcard filenames are translated to lower case/]
	F%ANON![ASCIZ/Automatic ANONYMOUS logins are in effect/]
NFLAGS==.-FLGDSP
; Show confirmations required

TYPCNF:	TYPE <Confirmation required for>
	MOVE B,F
	ANDX B,F%ALLC		; Isolate just these bits.
	IFE. B
	  TYPE < no commands%/>
	  RET
	ENDIF.
	TXO B,F%TEMP		; Say no typeout done.
	IFXN. B,F%DELC
	  TXZ B,F%TEMP		; Say we've typed one.
	  TYPE < DELETE>
	ENDIF.
	IFXN. B,F%GETC
	  TXNN B,F%SENC!F%UPDC	; Any following this?
	   TXNE F,F%TEMP	; No, have we typed any?
	    IFSKP.
	      TYPE < and GET>	; Yes, make conjunctive
	      JRST TYPCN0
	    ENDIF.
	  TXZN F,F%TEMP
	   TYPE <,>
	  TYPE < GET>
	ENDIF.
	IFXN. B,F%UPDC
	  TXNN B,F%SENC		; Any following this?
	   TXNE F,F%TEMP	; No, have we typed any?
	    IFSKP.
	      TYPE < and UPDATE> ; Yes, make conjunctive
	      JRST TYPCN0
	    ENDIF.
	  TXZN F,F%TEMP
	   TYPE <,>
	  TYPE < UPDATE>
	ENDIF.
	IFXN. B,F%SENC
	  HRROI A,[ASCIZ/ and/]
	  TXNN F,F%TEMP	; Typed anything?
	   PSOUT%		; Yes
	  TYPE < SEND>
	ENDIF.
TYPCN0:	TYPE < file operations%/>
	RET
; Here to show transfer type parameters
SHTTYP:	MOVE C,DEFPRP+P.TYPE
	HRRO C,[ [ASCIZ/unspecified/]
		 [ASCIZ/text/]
		 [ASCIZ/binary/]
		 [ASCIZ/paged/]
		 [ASCIZ/directory/]
		 [ASCIZ/paged/]
		 [ASCIZ/ebcdic/]
		 [ASCIZ/image/] ](C)
	TYPE <Transfer type is %3S>

	SKIPE C,DEFPRP+P.BYTE
	 TYPE <, byte size %3D>

	SKIPN C,DEFPRP+P.TFRM	; Get format
	IFSKP.
	  HRRO C,[ [ASCIZ/non-print/]
		   [ASCIZ/TELNET/]
		   [ASCIZ/carriage control/] ]-1(C)
	  TYPE <, %3S format>
	ENDIF.

	SKIPN C,DEFPRP+P.TMOD	; Get mode
	IFSKP.			; If non-default
	  HRRO C,[ [ASCIZ/stream/]		;MD.STR
		   [ASCIZ/block/]		;MD.BLK
		   [ASCIZ/compressed/] ]-1(C)	;MD.CMP
	  TYPE <, %3S mode>
	ENDIF.
	TYPE <%/>		; Finish line

	SKIPN D,DEFPRP+P.EOLC	; Get end of line convention
	 RET			; None, done
	MOVEI C,EOLDSP	; Find its name
	CALL TABLUK
	TYPE <End-of-line convention is %4S%/>
	RET
; Show kept parameters

DEFINE SHK(NAME,FLG) <
	SKIPN FLG		;;Anything in that flag?
	IFSKP.
	  CAME KF,FLG		;;Same as previous?
	  IFSKP.
	    TYPE <, NAME>	;;Yes, say so
	  ELSE.
	    MOVE KF,FLG		;;Else get new flag
	    CALL OUTKF		;;Type out set bits
	    TYPE < for NAME>	;;Say what this is for
	  ENDIF.
	ENDIF.
>

SHKPAR:	SETZ KF,		; Clear "previous flag" for start
	TXZ F,F%TEMP		; No CRLF needed yet
	KFLAGS(SHK)		; Chain list of kept flags
	CALLRET PCRIF		; Finish with a CRLF if necessary

OUTKF:	TYPE <%_Keeping >
	CAIE KF,K%ALL		; All flags?
	IFSKP.
	  TYPE <all properties>
	  RET
	ENDIF.
	SAVEAC <KF>		; Don't mung - must be kept for SHKPAR
	TXZN KF,K%VERS		; Keep VERSION?
	IFSKP.
	  TYPE <file versions>
	  CALL KFCOMA
	ENDIF.
	TXZN KF,K%AUTH		; Keep USERNAME?
	IFSKP.
	  TYPE <author names>
	  CALL KFCOMA		; Maybe a comma
	ENDIF.
	TXZN KF,K%PROT		; Keep PROTECTION?
	IFSKP.
	  TYPE <protections>
	  CALL KFCOMA
	ENDIF.
	CAIE KF,K%DATE		; Keep all date fields?
	IFSKP.
	  TYPE <dates>
	  RET
	ENDIF.
	TXZN KF,K%RDAT		; Keep READ-DATE?
	IFSKP.
	  TYPE <read dates>
	  CALL KFCOMA
	ENDIF.
	TXZN KF,K%CDAT		; Keep CREATION-DATE?
	IFSKP.
	  TYPE <creation dates>
	  CALL KFCOMA
	ENDIF.
	TXZN KF,K%WDAT		; Keep WRITE-DATE?
	 RET
	TYPE <write dates>
	RET

KFCOMA:	JUMPE KF,R		; If no flags left, no comma
	TYPE <, >
	RET
; Look up a name in a table
; call with C/table, D/value to look up
; returns with d/string pointer

TABLUK:	HLRZ B,(C)		; Get length of table
	MOVNS B			; Turn into AOBJN pointer left half
	AOS C			; Point to next entry
	HRL C,B			; and merge left half in to make full pointer 
	DO.
	  HRRZ A,(C)		; Get value of that table entry
	  CAME A,D		; Same as target?
	  IFSKP.
	    HLRZ A,(C)		; Get right half of table entry
	    MOVE A,(A)		; Word pointed to
	    TXC A,CM%FW		; Complement FW bit for easier testing
	    IFXN. A,<-1B7>	; If not FW word
	      HLRO D,(C)	; Point to string
	      RET		; Just return with it
	    ENDIF.
	    IFXE. A,CM%INV!CM%ABR ; Ignore funny keys
	      HLRO D,(C)	; Point to string
	      AOJA D,R		; Skip over flag word and return with it
	    ENDIF.
	  ENDIF.
	  AOBJN C,TOP.		; Else go back for the next
	ENDDO.
	HRROI D,[ASCIZ/*INVALID*/]
	RET
SUBTTL SET command

H.SET:	ASCIZ/The SET command changes various parameters of the FTP program.
For more information type HELP SET <subcommand>, as in

FTP>HELP SET CONFIRMATION

For a list of available subcommands type SET ? to the FTP> prompt.
You can also run SET subcommands from the FTP> prompt, rather than
having to type the SET command first.
/

C.SET:	KEYWRD SETDSP
	HRRZ B,(B)
	CALLRET (B)		; Dispatch off to command.

DEFTAB SETTAB,<
	T ALTERNATE-SOCKETS,ALTS
	T ANONYMOUS-LOGIN,ANON
	TI BYTE-SIZE,BYTE
	TA C
	T CHECKSUMS
DOT(PREF,C):
	T CONFIRMATION
	TA D,SD
	TI DEBUG
DOT(PREF,SD):
	T DIRECTORY,SDIR
	T END-OF-LINE,EOL
	T FANCY-QUIT,FQUI
	T HASH-MARK,HASH
	T INCLUDE-VERSIONS,MUNG
	T KEEP
	T LOWERCASE
	T MODE
	T MULTIPLE-GET,MULT
	T NO
	T OLD-LOGIN,OLDL
	TI PRESERVATION
	TA S,SST
	T SMART-DIRECTORIES,SMDR
DOT(PREF,SST):
	T STATISTICS
	T TYPE,STYP		; Don't conflict with TYPE command
	T USER
	T VERBOSITY
>

SETDSP:	SETTAB(S)
SETHLP:	SETTAB(H)
; Parse confirmation options for SET BYTE and SET TYPE
; Leaves flags in FX (which must be saved by callers).

	S%LOCL==1B32		; Use for local end of transfer
	S%REMT==1B33		; Use for remote end of transfer
	S%CURX==1B34		; Use for this transfer
	S%DEFX==1B35		; Use for default transfers

TYPOPT:	SAVEAC <A,B,C>
	NOISE <for>
	MOVEI B,[FLDDB. .CMKEY,,TOCETB,<connection end,>,<BOTH>]
	CALL .COMND		; Parse keyword
	 ERMSG <Invalid connection end>
	HRRZ FX,(B)		; Get flags
	NOISE <transfer>
	MOVEI B,[FLDDB. .CMKEY,,TOTXTB,<transfer to set type for,>,<ALL>]
	CALL .COMND		; Parse keyword
	 ERMSG <Invalid transfer specification>
	HRRZ B,(B)		; Get flags
	TRO FX,(B)		; Set them
	CALLRET CONFRM		; Finish parse

TOCETB:	TABLE
	KEY BOTH,S%LOCL!S%REMT
	KEY LOCAL,S%LOCL
	KEY REMOTE,S%REMT
	TEND

TOTXTB:	TABLE
	KEY ALL,S%CURX!S%DEFX
	KEY CURRENT,S%CURX
	KEY DEFAULT,S%DEFX
	TEND
; SET [NO] ALTERNATE-SOCKETS - make different socket #s for new data conns

H.ALTS:	ASCIZ/Normally when talking to a TCP server FTP uses the same socket
for its data and TELNET connections.  When combined with the default
MULTIPLE GET action or when transferring several files, this can
lead to rapid opening and closing of the same local and remote
socket connection.

Some hosts do not treat this situation very well, and work much
better with a different socket number for each data connection.
If this is the case you can use SET ALTERNATE-SOCKETS to force FTP
to use a different socket number every time.  Rather than guess which
hosts require this treatement, this option is by default always on.
/

S.ALTS:	NOISE <for data connections>
	MOVX D,F%ALTS		; Get flag to (re)set
	JRST SETFLG		; Join common code
; SET BYTE -- supply byte size for unknown cases
; invisible keyword, normally used as subroutine of SET TYPE BINARY

H.BYTE:	ASCIZ/SET BYTE-SIZE changes the number of bits per word FTP
expects the transferred file to have.  Typical values for the
size are 7, 8, and 36.  If the byte size is 0 (the default), the
byte size will be decided separately for each file.
/

S.BYTE:	NOISE <for binary transfers>
	CALL SETPRP		; Set up property list
	TXO F,F%PSET		; Remember we did that
	SAVEAC <FX>		; Don't mung this flag register we use
	CALL SETBYT		; Do actual work
	CALLRET NOHALT		; Now don't stop on JCL

SETBYT:	MOVEI B,[FLDDB. .CMNUM,CM%SDH,^D10,<number between 1 and 36
  or zero to leave the default byte size unspecified>,<0>]
	CALL .COMND
	 ERMSG <Invalid bytesize>
	SKIPL B			; Ensure in range (0 = unspecified is Ok)
	 CAILE B,^D36
	  ERMSGB <Bytesize not between 1 and 36>
	CALL TYPOPT		; Finish parse
BYTSET:	IFXN. FX,S%CURX		; If current transfer
	  MOVEI A,FILPRP	; Get file property list
	  CALL BYTSE0		; Set properties
	ENDIF.
	TXNN FX,S%DEFX		; If not default transfer
	 RET			; Then done now
	MOVEI A,DEFPRP		; Get default transfer property list
BYTSE0:	TXNE FX,S%LOCL		; Local?
	 MOVEM B,P.BYTE(A)	; Yes
	TXNE FX,S%REMT		; Remote?
	 MOVEM B,P.XBYT(A)	; Yes
	RET
; SET [NO] CHECKSUMS -- turn checksum suppression bit in BSP conn on or off

H.CHEC:	ASCIZ/SET CHECKSUMS makes FTP take advantage of whatever checksumming
facility the protocol in use might offer.  With checksums enabled file
transfers may be somewhat slower but they will be less likely to
change the data in the files transferred.  SET NO CHECKSUMS turns
checksumming off.  The default is for checksums to be enabled.
/

S.CHEC:	NOISE <for connections>
	CALL CONFRM
	TXNN F,F%NPRF		; (Can't use common code, flag parity is wrong)
	 TXZA F,F%NCSM		; "No", allow checksumming
	  TXO F,F%NCSM		; Else disallow it
	TXNE F,F%COPN		; If a connection is open
	 CALL @.CFLAG(V)	; Then set up checksumming or no
	CALLRET NOHALT		; Handle JCL
; SET [NO] CONFIRMATION -- specify which commands are to be confirmed.
H.CONF:	ASCIZ/If confirmation is not required for the SEND or GET commands, FTP
will use the same filename on both machines rather than asking for the
other filename.  If confirmation is set for the DELETE command, FTP
will ask to make sure you really want to delete the file or files.
If confirmation is required for UPDATE, then FTP will ask before
transferring any files in the UPDATE and INSTALL commands.
Confirmation is initially required for all commands.
/

S.CONF:	NOISE <required for>
	KEYWRD CNFRMS
	HRLZ D,(B)		; Get the flag to be (re)set (in left half).
	IFE. D			; Is this OLD-LOGIN stuff?
	  TXNN F,F%NPRF		; Yes, was NO given?
	   TDZA D,D		; No, assume confirmation
	    MOVEI D,1		; NO CONF LOGIN, equiv to SET OLD-LOGIN NEVER
	  CALLRET OLLSET	; Go join OLD-LOGIN code
	ENDIF.
SETFLG:	CALL CONFRM
	TXNE F,F%NPRF		; Prefixed with NO?
	 TDZA F,D		; Yes, clear flags
	  TDO F,D		; Else set them
	CALLRET NOHALT		; Don't stop for JCL

DEFINE T (NAME,VAL,FLG) <KEY NAME,(VAL),FLG>

CNFRMS: TABLE
	T ALL,F%ALLC
	T DELETE,F%DELC
	T GET,F%GETC
	TI INSTALL,F%UPDC
	TI LOGIN,0		; Make work for old PUPFTP users
	KEY R,CNFREC,CM%INV!CM%ABR ; Abbreviation - TA doesn't seem to work
	KEY RE,CNFREC,CM%INV!CM%ABR
CNFREC:	TI RECEIVE,F%GETC
	TI RESTORE,F%GETC
	TI RETRIEVE,F%GETC
	KEY S,CNFSND,CM%INV!CM%ABR
	TI SAVE,F%SENC
CNFSND:	T SEND,F%SENC
	TI STORE,F%SENC
	T UPDATE,F%UPDC
	TEND
; SET [NO] DIRECTORY -- set a default directory for file transfers
H.SDIR:	ASCIZ/SET DIRECTORY sets a default directory for file transfers.
Files given without a directory will be assumed by the other
machine to be in the given directory.

Note that this does not give you any special access to the
given directory.  To get that effect use the CONNECT command.

Directory defaults set with SET DIRECTORY will automatically
go away if you make a connection to a different machine.
/

S.SDIR:	CALL NOHALT		; Don't stop for JCL
	IFXN. F,F%NPRF		; Prefixed with "No"?
	  NOISE <default>
	  CALL CONFRM		; Yes, just confirm the parse
ZDIRDF:	  SETZM DEFPRP+P.DIRE	; clear default directory
	  SETZM FILPRP+P.DIRE	; and current transfer directory.
	  RET
	ENDIF.

	NOISE <default to>
SETDIR::MOVEI B,[FLDDB. .CMCFM,CM%SDH,,<return to clear default>,,[
		 FLDBK. .CMFLD,CM%SDH,,<remote directory>,,REMBRK]]
	CALL .COMND
	 ERMSG <Invalid remote directory>
	LOAD C,CM%FNC,(C)	; Get FDB parsed
	CAIN C,.CMCFM		; Was it confirm?
	 JRST ZDIRDF		; Yes, clear default
	CALL CONFRM		; else finish parse.
	MOVE A,[POINT 7,TEMP]
	CALL CPYATM		; Copy atom buffer
	CAILE D,USRSTL		; Was it too long?
	 ERMSGB <Directory string too long>
	MOVE A,[TEMP,,DEFPRP+P.DIRE]
	BLT A,DEFPRP+P.DIRE+<USRSTL/5>
	MOVE A,[TEMP,,FILPRP+P.DIRE]
	BLT A,FILPRP+P.DIRE+<USRSTL/5>
	RET
; SET END-OF-LINE -- set EOL convention.
H.EOL:	ASCIZ/SET END-OF-LINE (convention to) <convention> tells FTP to translate
the end-of-line delimiters of local files (CRLF) to some other form
that the other machine is better equipped to send or receive.
Acceptable values for <convention> are:
  CR -- carriage return only
  CRLF -- carriage return and line feed
  TRANSPARENT -- no translation by either machine

This is currently useful only for Pup transfers - TCP transfers have
no particular concept of an end of line convention.
/

S.EOL:	CALL NOHALT		; Don't stop now
	NOISE <convention to>
SETEOL::KEYWRD EOLDSP
	HRRZ B,(B)		; Get the matching parameter.
	CALL CONFRM
	MOVEM B,DEFPRP+P.EOLC	; Set default eol convention
	MOVEM B,FILPRP+P.EOLC	; Set current eol convention
	RET

EOLDSP:	TABLE
	[ASCIZ /CR/],,EC.CR
	[ASCIZ /CRLF/],,EC.CRL
	[ASCIZ /TRANSPARENT/],,EC.TRN
	TEND
; SET [NO] FANCY-QUIT -- close connection and re-open during QUIT

H.FQUI:	ASCIZ/SET FANCY-QUIT changes FTP's behavior in the QUIT command.
Normally, QUIT will simply exit to the EXEC without changing any
of FTP's state.  However, if you want to keep a FTP fork lying
around for a long time, the server is likely to time out your connection.
This command makes FTP close your connection when you QUIT, and re-open
it when you continue.  This will all happen invisibly to you; it should
appear as though your old connection remained open during the QUIT.
/

S.FQUI:	MOVX D,F%QUIT		; Get flag to (re)set
	JRST SETFLG		; Join common code
; SET [NO] HASH-MARK - control display of excls on file transfers

H.HASH:	ASCIZ/Normally FTP will print an exclamation mark every ten disk pages
sent or received to show that the connection is still active.
This command lets you set the number of pages between these
"hash marks" to be some other number; by setting it to zero or by
using SET NO HASH-MARK you can disable their typeout altogether.

If the verbosity level is set to DEBUGGING then this count will be
ignored and one hash mark will always be typed per page.
/

S.HASH:	IFXE. F,F%NPRF		; NO typed?
	  NOISE <display interval>
	  MOVEI B,[FLDDB. .CMNUM,,^D10,<decimal number of pages>]
	  CALL .COMND		; Parse how many
	   ERMSG <Invalid number of pages>
	ELSE.
	  NOISE <display on file transfers> ; NO, use appropriate noise
	  SETZ B,		; No count
	ENDIF.
	CALL CONFRM		; Finish parse
	MOVEM B,SHXDEF		; Save count
	CALLRET NOHALT		; Don't stop if JCL
; SET [NO] INCLUDE-VERSIONS - hack up escape-completed generation strings

H.MUNG:	ASCIZ\The TOPS-20 command completion system automatically includes a
file generation when completing a file name.  Some operating systems
will interpret this generation as merely another part of the file name,
leaving incautious users with the string ".-1" at the end of their file
names.  FTP goes to great lengths to make this unwanted completion not
happen, but in cases where the filename is longer than 6 characters or
the extension longer than 3, there is no way to avoid it.  Because of this,
if escape completion includes a generation in a filespec then FTP will
automatically edit it out.

If the user really wants a ".-1" or other such generation at the end of
the filename, he can type it out without using escape completion, or if he
is particularly paranoid he can enclose the whole filename in doublequotes.
To make this editing never happen, use SET INCLUDE-VERSIONS.  Perhaps an
example is needed to make this clearer.  In the following, the user typed
an escape immediately after each "(to remote file)".

FTP>SEND FINGER.PLAN (to remote file) FINGER.PLAN.-1
 FINGER.PLAN => /csd/kronj/FINGER.PLAN !! [OK]
FTP>SET INCLUDE-VERSIONS
FTP>SEND FINGER.PLAN (to remote file) FINGER.PLAN.-1
 FINGER.PLAN => /csd/kronj/FINGER.PLAN.-1 !! [OK]
\

S.MUNG:	NOISE <on escape completion of remote files>
	MOVX D,F%MUNG		; Get flag to (re)set
	JRST SETFLG		; Join common code
; SET [NO] ANONYMOUS-LOGIN  -- do ANONYMOUS login if no username is specified 

H.ANON:	ASCIZ\If you give a command that requires that you be logged in
and you are not already logged in, FTP will normally prompt you for a
username and password.  The command SET ANONYMOUS-LOGIN will cause FTP to
instead try to automatically log you in on the remote system as ANONYMOUS,
password GUEST.  If that fails, FTP will go ahead and prompt you for another
username.
\

S.ANON:	NOISE <if username not specified>
	MOVX D,F%ANON		; Get flag to (re)set
	JRST SETFLG		; Joing common code
; SET [NO] KEEP -- keep or don't keep various parameters in file xfers

H.KEEP:	ASCIZ\SET KEEP tells FTP to attempt to preserve various attributes
of the files it transfers with different commands, or with
all commands together.

Usage is SET KEEP <attribute> <command>.  Possible attributes are:
    ALL           - keep all the following attributes
    CREATION-DATE - when the file was first written
    DATES         - CREATION-DATE, READ-DATE, and WRITE-DATE
    PROTECTION    - who can access the file in what ways
    READ-DATE     - when the file was last looked at
    USERNAMES     - the name of the writer and creator
    VERSION       - the generation number of the file
    WRITE-DATE    - when the file was last written

Commands that SET KEEP is applicable to are GET, SEND, and RENAME.
Not all flags are applicable to RENAME.  SET parameters also apply
to INSTALL, and likewise GET parameters apply to UPDATE.  You can
type ALL to specify all of the above commands.  Some attributes
require special capabilities to be kept - if an attempt to keep an
attribute fails FTP will go on with the transfer after warning you.

FTP initially keeps all attributes except VERSION.
\

DEFINE T (NAME,VAL,FLG) <KEY NAME,VAL,FLG>

KEPTBL:	TABLE
	TA A,K.A
K.A:	T ALL,K%ALL
	TI AUTHOR,K%AUTH
	T CREATION-DATE,K%CDAT
	T DATES,K%DATE
	TI GENERATION,K%VERS
	T PROTECTION,K%PROT
	T READ-DATE,K%RDAT
	T USERNAMES,K%AUTH
	T VERSION,K%VERS
	T WRITE-DATE,K%WDAT
	TEND

KEPCMD:	TABLE
	T ALL,0			; Luckily ALL is before all other flag names
	KFLAGS(T)		; Make table entry for each flag name
	TEND

DEFINE IORKF (NAME,FLG) <IORM KF,FLG>
DEFINE ANDKF (NAME,FLG) <ANDCAM KF,FLG>

S.KEEP:	CALL NOHALT		; Fix JCL
	NOISE <attribute>
	MOVEI B,[FLDDB. .CMKEY,,KEPTBL,,<ALL>]
	CALL .COMND		; Get attribute flags
	 ERMSG <Illegal SET KEEP atribute>
	HRRZ KF,(B)		; Keep them
	NOISE <in command>
	MOVEI B,[FLDDB. .CMKEY,,KEPCMD,,<ALL>]
	CALL .COMND
	 ERMSG <Illegal command to KEEP attributes in>
	HRRZ B,(B)		; Get address or 0 in B
	CALL CONFRM		; Finish parse
	IFE. B			; If zero, set all flag sets
KP.ALL:	  IFXE. F,F%NPRF	; "No" typed?
	    KFLAGS(IORKF)	; Want to turn on flags, IORM into each cell
	    RET
	  ENDIF.
	  KFLAGS(ANDKF)		; Else want to turn them off, ANDCAM each
	  RET
	ENDIF.
	IORM KF,(B)		; Turn on flag
	TXNE F,F%NPRF		; "No" typed?
	 ANDCAM KF,(B)		; Yes, turn it off
	RET


; SET [NO] PRESERVATION -- shorthand for SET [NO] KEEP VERSION

H.PRES:	ASCIZ/SET PRESERVATION preserves version numbers across systems.  Normally
files are created with the next highest generation, but with this set
the generation of the old file is kept if possible.

SET PRESERVATION is a synonym for SET KEEP VERSION ALL.
/

S.PRES:	NOISE <of version numbers during transfers>
	CALL CONFRM		; Finish parse
	MOVEI KF,K%VERS		; Preserving versions
	JRST KP.ALL		; Go set the flags
; SET [NO] LOWERCASE -- fill wildcard names with lowercase

H.LOWE:	ASCIZ\SET LOWERCASE will make the names of files filled out from
wildcards get filled out in lowercase instead of the normal
upper case.  This is useful for sending files to machines
running UNIX (tm) or some similar operating system in which
filenames are typically kept in lower case.  Example:

FTP>SEND FINGER.PLAN *.*
 FINGER.PLAN => /csd/kronj/FINGER.PLAN !! [OK]
FTP>SET LOWERCASE
FTP>SEND FINGER.PLAN *.*
 FINGER.PLAN => /csd/kronj/finger.plan !! [OK]
\

S.LOWE:	NOISE <file names for UNIX sites>
	MOVX D,F%LOWR		; Get flag to (re)set
	JRST SETFLG		; Join common code
; SET MODE - low level format for TCP data transfers

H.MODE:	ASCIZ/SET MODE tells FTP in what format to send bits across a data
connection.
    STREAM mode merely sends the bits and bytes as is.  This has the
	advantage of simplicity, but the disadvantage that it requires
	the data connection to be opened and closed for each file
	transfer.  Most FTP servers support only this mode.
    BLOCK mode is a more structured format which allows one data
	connection to remain open throughout a file transfer session.
    COMPRESSED mode is like stream mode except that if there are
	repetitions of some byte in the file, it will send only one
	copy of the byte along with a repetition count.  This is
	useful for sending files across a very slow connection.
    AUTO-BLOCK mode (the default) tries to use block mode, but if
	the remote server can't handle that it quietly reverts to
	stream mode rather than prompting for a new mode.
This is only applicable to TCP transfers; Pup file transfers have no
concept of a data mode and ignore this parameter.
/

S.MODE:	CALL NOHALT		; Don't stop if this is JCL
	NOISE2 <for data connections to>,<is>
SETMOD::KEYWRD MODTAB		; Get mode type
	CALL CONFRM		; Finish parse
	HRRZ B,(B)		; Just the value
	MOVEM B,DEFPRP+P.TMOD	; Save in file properties
	MOVEM B,FILPRP+P.TMOD
	RET

MODTAB:	TABLE
	KEY AUTO-BLOCK,MD.ABK
	KEY BLOCK,MD.BLK
	KEY COMPRESSED,MD.CMP
	KEY STREAM,MD.STR
	TEND
; SET [NO] MULTIPLE-GET - when to use multiple or non-multiple receive

; Help is with MULTIPLE comand

S.MULT:	IFXN. F,F%NPRF		; No prefix?
	  CALL CONFRM		; Yes, finish parse
	  MVI. MG.OFF,MULGET	; Turn off multiple gets
	  CALLRET NOHALT	; That's all
	ENDIF.
	MOVEI B,[FLDDB. .CMKEY,,MLGTAB,,<HEURISTIC>]
	CALL .COMND		; Parse multiple state
	 ERMSG <Invalid MULTIPLE-GET state>
	CALL CONFRM		; Finish parse
	HRRZ B,(B)		; Get value
	MOVEM B,MULGET		; Save state
	CALLRET NOHALT		; That's all

MLGTAB:	TABLE
	KEY ALWAYS,MG.ON
	KEY HEURISTIC,MG.HEU
	KEY NEVER,MG.OFF
	TEND
; SET NO -- Prefix set option with NO.

H.NO:	ASCIZ/SET NO <parameter> turns a SET parameter off.  Parameters that can be
turned off are ALTERNATE-SOCKETS, ANONYMOUS-LOGIN, CHECKSUMS, CONFIRMATION,
DIRECTORY, FANCY-QUIT, INCLUDE-VERSIONS, KEEP, LOWERCASE, SMART-DIRECTORIES,
STATISTICS, and USER.
/

C.NO:	CALL NOHALT		; Don't stop if this is JCL
S.NO:	KEYWRD NOOPTS
	TXO F,F%NPRF		; Show a no prefix.
	HRRZ B,(B)		; Dispatch to the routine.
	CALLRET (B)

DEFTAB SNOCMD,<
	T ALTERNATE-SOCKETS,ALTS
	T ANONYMOUS-LOGIN,ANON
	TA C,NC
	T CHECKSUMS
DOT(PREF,NC):
	T CONFIRMATION
	TI DEBUG
	T DIRECTORY,SDIR
	T FANCY-QUIT,FQUI
	T HASH-MARK,HASH
	T INCLUDE-VERSIONS,MUNG
	T KEEP
	T LOWERCASE
	TI MULTIPLE-GET,MULT
	TI PRESERVATION
	TA S,SN
	T SMART-DIRECTORIES,SMDR
DOT(PREF,SN):
	T STATISTICS
	T USER
>
NOOPTS:	SNOCMD(S)
; SET OLD-LOGIN - what to do with old username when open a new connection

H.OLDL:	ASCIZ/SET OLD-LOGIN tells FTP what to do when you open a connection
to another computer after having logged in to one computer.
The default, CONFIRM, makes FTP ask for confirmation before
logging in as the same user.  ALWAYS makes FTP log in without
asking for confirmation.  NEVER makes FTP never log in.

If you log in while you are not connected to any computer,
FTP will always keep that login information without requiring
any confirmation.
/

LS OLDLGN			; Where to keep OLD-LOGIN status

S.OLDL:	NOISE (action to)
	MOVEI B,[FLDDB. .CMKEY,,OLLTAB]
	CALL .COMND		; Parse a keyword
	 ERMSG <Invalid OLD-LOGIN setting>
	HRRE D,(B)		; Get keyword contents
	IFE. D			; CONFIRM gets different noise
	  NOISE (before using old user name)
	ELSE.			; than do ALWAYS and NEVER
	  NOISE (use old user name)
	ENDIF.
OLLSET:	CALL CONFRM		; Finish parse
	MOVEM D,OLDLGN		; Save for later
	CALLRET NOHALT		; Fix JCL

OLLTAB:	TABLE
	KEY ALWAYS,-1
	KEY CONFIRM,0
	KEY NEVER,1
	TEND
; SET [NO] SMART-DIRECTORIES - use STAT for dirs if possible

H.SMDR:	ASCIZ/SET SMART-DIRECTORIES makes FTP try to get a formatted directory
listing in the directory command at times when this may be difficult.
With the TCP file transfer protocol there is no general way to do this
but for most TOPS-20 and TENEX hosts the output from certain listing
commands is regular enough that it can be parsed by FTP and used to
create a formattedlisting.

It may be useful for debugging purposes, or in cases where a host is
not obeying FTP's expectations, to turn this behavior off.  This can
be done with SET NO SMART-DIRECTORIES.
/

S.SMDR:	NOISE <for TCP listings>
	TXC F,F%NPRF		; Parity of this flag is opposite from normal
	MOVX D,F%NSMD		; Get flag to (re)set
	JRST SETFLG		; Join common code
; SET [NO] STATISTICS -- enable/disable statistics reporting.

H.STAT:	ASCIZ/SET STATISTICS tells FTP to remember information about how long
each file transfer took.  After the transfer, the time (CPU and real),
file size, and baud rates of the transfer will be typed.
/

S.STAT:	NOISE <gathering>
	MOVX D,F%STAT		; Get flag to (re)set
	JRST SETFLG		; Join common code
; SET TYPE -- supply transfer type when FTP can't figure it out itself

H.STYP:	ASCIZ/SET TYPE (of transfer to) <transfer-type> specifies the format to send
data across the net.  Possible values of <transfer-type> are BINARY,
EBCDIC, IMAGE, PAGED, TEXT, and UNSPECIFIED.  PAGED is the most efficient,
but only works from one TOPS-20 or TENEX site to another.  If UNSPECIFIED
(the default for machines that don't handle PAGED transfers) is set,
FTP will decide which type of transfer is correct for each file.

If BINARY or IMAGE types are given, FTP will also want to know a byte
size to use and will parse it after the file size.  The default, zero,
makes FTP choose an appropriate byte size for each transferred file.
If ASCII or EBCDIC types are given, FTP will then prompt for a format.
This tells some sites what style of carriage control to use when printing
the file.  The default is to treat the file as a simple text stream.

You can give various options after the type, byte size, and format,
to tell FTP whether to use the setting for the next transfer or for
all other transfers, and to make FTP use different settings on the
local and remote ends of the data connection.
/

; Here when STYCHG detects an error, string in A
STYBAD::TYPE <%1S%/>		; Type error message from CKSPAR
				; Drop into SETTYP

; Here when protocol handler or someone notes a need to get a new type
; returns +1/lost, +2/won
SETTYP::SAVEAC <A,B,C,D>	; Don't mung registers
	CALL PUSHIO		; Make sure talking to the terminal
	PROMPT [ASCIZ/SET TYPE (of transfer to) /]
	SETABORT (R)		; Set abort during transfer type prompt
	CALL SETCMD		; Set prompt and start parsing
	MOVE P,SAVPDL		; Fix stack if necessary
	CALL STYPSB		; Set file type
	CLRABORT		; Finished parse, forget abort

	;; Now make sure opened file is opened w/correct parameters
	SKIPN A,FILJFN		; Get input or output file
	 RETSKP			; None, give up
	HRRZS A			; Right half only
	GTSTS%			; Get file status
	TXNE B,GS%NAM		; Is the JFN ok?
	 TXNN B,GS%OPN		; Is it open?
	  RETSKP		; No, so don't touch it
	TXO A,CO%NRJ!CZ%ABT	; Aborting output, keeping JFN
	CLOSF%			; Close the file
	 JERMSG <Unexpected JFN close error>
	JN GS%RDF,B,OPNSTO	; If SEND, go re-open for that
	CALLRET RECYES		; This must be GET, fix up file


S.STYP:	NOISE <of transfer>
	CALL SETPRP		; Set up property list
	TXO F,F%PSET		; Remember we did that
	CALL STYPSB		; Call subroutine to do work
	CALLRET NOHALT		; Fix JCL

STYPSB:	SAVEAC <FX>		; Don't mung this register
	KEYWRD TYPDSP
	HRRZ B,(B)
	CALL (B)		; Dispatch by type
TYPSET:	IFXN. FX,S%CURX		; Setting for this time?
	  MOVEI A,FILPRP	; Yes, get plist
	  CALL TYPSE0		; Set it
	ENDIF.
	TXNN FX,S%DEFX		; Want defaults set?
	 RET			; No
	MOVEI A,DEFPRP		; Yes, use them
TYPSE0:	TXNE FX,S%LOCL		; Want local setting?
	 MOVEM B,P.TYPE(A)	; Yes
	TXNE FX,S%REMT		; Remote?
	 MOVEM B,P.XTYP(A)	; Yes
	RET

TYPASC:	CALL SETFMT		; Set format too - this is a text type
	MOVEI B,TT.TXT		; Text transfer type
	RET

TYPBIN:	NOISE <byte size>	; Noise words
	CALL SETBYT		; Set byte size too
	MOVEI B,TT.BIN		; Binary type
	RET

TYPIMG:	NOISE <byte size>	; Noise words
	CALL SETBYT		; Set byte size too
	MOVEI B,TT.IMG		; Image type
	RET

TYPEBC:	CALL SETFMT		; Set format too - this is a text type
	MOVEI B,TT.EBC		; EBCDIC transfer type
	RET

TYPPAG:	SKIPA B,[TT.PAG]	; TENEX data type
TYPMEI:	MOVEI B,TT.MEI		; MEIS data type
	CALLRET TYPOPT

TYPUNS:	MOVEI B,TT.UNS		; Unspecified data type
	CALLRET TYPOPT

; Here for ASCII or EBCDIC transfer type, also want printing format
SETFMT:	NOISE <format>
	MOVEI B,[FLDDB. .CMKEY,,FMTDSP,<format for printing>,<UNSPECIFIED>]
	CALL .COMND		; Parse format name
	 ERMSG <Illegal text format name>
	HRRZ B,(B)		; Get actual format
	CALL TYPOPT		; Get options to set
	TXNE FX,S%CURX		; Set current transfer?
	 MOVEM B,P.TFRM+FILPRP	; Yes
	TXNE FX,S%DEFX		; Set default transfer?
	 MOVEM B,P.TFRM+DEFPRP	; Yes
	RET

DEFINE T (NAME,VAL,FLG) <KEY NAME,VAL,FLG>

TYPDSP:	TABLE
	TI ASCII,TYPASC
	T BINARY,TYPBIN
	T EBCDIC,TYPEBC
	T IMAGE,TYPIMG
	TI LOGICAL-BYTE,TYPBIN
	T PAGED,TYPMEI
	T TEXT,TYPASC
	T UNSPECIFIED,TYPUNS
	TEND

FMTDSP:	TABLE
	T CARRIAGE-CONTROL,FM.ASA
	T NON-PRINT,FM.NPR
	T TELNET,FM.TEL
	T UNSPECIFIED,FM.UNS
	TEND
; SET [NO] USER - say who to log in as on new computer

H.USER:	ASCIZ/SET USER (default for login at) site (to) user-name
makes FTP automatically log you in as that user whenever
you open a connection to the given site.  The password is
not remembered, so you will have to give it each time.
This command is useful for including in a FTP.INIT file,
so you can specify your username on any other machines you
may have an account on.

SET NO USER (default for login at) site
clears any defaults previously set with SET USER.

If you use the LOGIN command before opening a connection to a host,
the login defaults for that host are not checked.  If you can connect
to the same site using more than one protocol you will have to specify
a user login default for each protocol you use.
/

; The format of a user login default block is:
;  LH[0]: 0 if a default, -1 if cancelling a default
;  RH[0]: pointer to next default block
;  [1]:   copy of appropriate V, i.e. protocol information
;  [2]:   host number as returned by HSTNAM
;  rest:  ASCIZ/user/ then ASCIZ/account/
;  

S.USER:	NOISE <default for login at>
	MOVEI B,[FLDBK. .CMFLD,CM%SDH,,<host name>,,HSTBRK]
	CALL .COMND		; Parse site name
	 ERMSG <Invalid host name>
	CALL VALFLD		; Make sure valid field
	 ERMSGX <Null host name given>
	HRROI A,ATMBUF		; Point to name we just parsed
	MOVEI C,PROTS		; And list of protocols
	CALL $GTPRO
	 ERMSG <No such host>
	MOVE D,FRECOR		; Get address to stuff all this
	MOVE C,(C)		; Get protocol info
	MOVEM C,1(D)		; Save it
	MOVEM B,2(D)		; Save host number
	MOVEI A,3(D)		; Point to next word
	IFXE. F,F%NPRF		; "NO" typed?
	  NOISE <to>
	  MOVEI B,[FLDBK. .CMFLD,CM%SDH,,<user name at remote site>,,REMBRK]
	  CALL .COMND		; Parse user name
	   ERMSG <Invalid remote user name>
	  HRROI A,3(D)		; Point to where user name goes
	  HRROI B,ATMBUF	; And atom buffer
	  SETZ C,		; Ending on null
	  SOUT%			; Copy across
	  HRROI A,1(A)		; Now for account
	  PUSH P,A		; Save pointer
	  NOISE <account>
	  MOVEI B,[FLDBK. .CMFLD,CM%SDH,,<account string>,,REMBRK]
	  CALL .COMND		; Parse account
	   ERMSG <Invalid account string>
	  POP P,A		; Get destination pointer back
	  HRROI B,ATMBUF	; And pointer to atom buffer
	  SETZ C,		; Ending on null
	  SOUT%			; Copy across
	ENDIF.
	CALL CONFRM		; Finish parse
	MOVEI A,1(A)		; Point to next word
	MOVEM A,FRECOR		; Save as place to save next default
	HRRZ B,ULGDEF		; Get previous default
	TXNE F,F%NPRF		; "No" typed?
	 TXO B,.LHALF		; Yes, set flag saying so
	MOVEM B,0(D)		; Save start word
	MOVEM D,ULGDEF		; Save new login default
	CALLRET NOHALT		; Done
; SET VERBOSITY - say how much typeout to do

H.VERB:	ASCIZ/SET VERBOSITY tells FTP how much information to type on your
terminal.  There are several possible settings of this parameter:

    SUPER-TERSE means almost nothing will be typed on your terminal.

    TERSE types very little on the screen, but will type some error messages.

    NORMAL typeout mode types everything you need to see but not much more.

    VERBOSE typeout mode displays more text, such as positive server
	replies that might not normally be shown.

    EXTRA-VERBOSE types the machine-readable part in displayed server replies.

    DEBUGGING displays all interactions with the FTP server.  This may display
	the password you use to log in to the remote site, so be careful.
/

S.VERB:	NOISE <level to be>
	KEYWRD VRBTAB		; Parse a verbosity level name
	CALL CONFRM		; Finish parse
	HRRZ B,(B)		; Get value
	MOVEM B,VRBSTY		; Save as new verbosity
	CALLRET NOHALT		; Done

LS VRBSTY			; Where to keep verbosity level

VRBTAB:	TABLE
	KEY DEBUGGING,VB.DEB
	KEY EXTRA-VERBOSE,VB.EVB
	KEY NORMAL,VB.NRM
	KEY SUPER-TERSE,VB.SRV
	KEY TERSE,VB.TRS
	KEY VERBOSE,VB.VRB
	TEND

H.DEBU:	ASCIZ/SET DEBUG is shorthand for SET VERBOSITY DEBUGGING.
SET NO DEBUG is shorthand for SET VERBOSITY NORMAL.
/

S.DEBU:	NOISE <mode>
	CALL CONFRM		;Finish parse
	IFXE. F,F%NPRF		;No?
	  MVI. VB.DEB,VRBSTY	;SET DEBUG = SET VERBOSITY DEBUGGING
	  CALLRET NOHALT
	ENDIF.
	MVI. VB.NRM,VRBSTY	;SET NO DEBUG = SET VERBOSITY NORMAL
	CALLRET NOHALT
SUBTTL OPEN command -- initiate connection to foreign port

H.HOST:!
H.OPEN:	ASCIZ/The OPEN command opens a FTP connection to another machine.
you can also type the host name to the FTP> prompt without the OPEN
command in front of it, if the host name is different from any FTP
command.  The machine you open a connection to will type a welcome
message when you successfully connect, the transfer type may be set
automatically based on the remote host's operating system (see SET TYPE)
and FTP will prompt for a default login if you have set one (see SET USER).

FTP>OPEN SIERRA			; User wants to open connection to Sierra
< Sierra Pup FTP Server 1.30 16-Apr-82
Setting default transfer type to paged.
Default login as Kronj		; Previously set default with SET USER
Password:			; Now type password for Kronj@Sierra

To specify a specific network protocol, give it after the host name,
as "OPEN SIERRA PUP".  You may also specify a socket name or number
after the host number; the format of this may vary according to protocol.
/

C.OPEN:	NOISE <connection to>	; Make noise words
	CALL OPNSUB		; Do the actual OPEN command
	 RET			; On error and JCL still want to halt
	CALLRET NOHALT		; All done, keep going if JCL

C.HOST:	NOISE <name>		; Different noise words for this command
	CALL OPNSUB		; Do the actual OPEN command
	 RET			; On error and JCL still want to halt
	CALLRET NOHALT		; All done, keep going if JCL

C.OPNM:	CALL OPNSB1		; Here with host name already parsed
	 RET			; On error and JCL still want to halt
	CALLRET NOHALT		; All done, keep going if JCL
; Handler subroutine for OPEN command
; Enter at OPNSUB for a subroutine call so blank line reprompts etc
; returns +1/connection attempt failed, +2/connection open

OPNSUB:	MOVEI B,[FLDBK. .CMFLD,CM%SDH,,<host name>,,HSTBRK]
	CALL .COMND		; Parse host name as a field
	 ERMSG <Invalid host name>
	CALL VALFLD		; Make sure it was parsed
	 ERMSGX <Null host name given>
OPNSB1:	MOVE A,[POINT 7,TEMP+100] ; Point to a somewhat safe place
	CALL CPYATM		; Copy host name

	;; Host name is parsed, find which protocol to use.
	;; First see which is our preferred protocol for that host.
	HRROI A,TEMP+100	; Point to name we just parsed
	MOVEI C,PROTS		; And list of protocols
	CALL $GTPRO
	 ERMSG <No such host>	; Not listed in any protocol, give up.

	;; Now use preferred as default in parsing protocol name.
	PUSH P,C		; Save dispatch
	NOISE <protocol>
	MOVE A,[[FLDDB. .CMKEY,,PRTDSP,,<X>],,TEMP]
	BLT A,TEMP+.CMBRK	; Copy whole FDB into scratch
	POP P,C			; Get dispatch back
	HLRO A,(C)		; Copy protocol parsed
	MOVEM A,TEMP+.CMDEF	; Set default
	MOVEI B,TEMP		; With hacked up FDB
	CALL .COMND		; Parse protocol name
	 ERMSG <Invalid protocol name>

	;; Got a protocol name, match host name in that protocol.
	MOVE C,B		; Point to protocol we just parsed
	MOVE D,B		; Copy for comparison later
	HRROI A,TEMP+100	; With host name again
	CALL $GTPRO		; Try it with this protocol
	 ERMSG <No such host with that protocol>
	CAME C,D		; Same protocol parsed?
	 ERMSG <No such host with that protocol>
	MOVE C,(C)		; Get protocol name and vector pointers

	;; Protocol all parsed, get socket if protocol wants and finish parse.
	CALL @.PARSK(C)		; Parse socket
	CALL CONFRM		; Finish parse

	;; Now we have parsed host, see if we already have a connection.
	IFXN. F,F%COPN		; If we have a connection open
	  PROMPT TEMP,A		; Start making prompt
	  HRROI D,HSTSTR	; Point to host name (can't mung B or C)
	  WRITE <Connection already open to %4S [Confirm] >
	  CALL DOCONF		; Attempt double confirmation
	  IFNSK.
	    SKVERB VB.NRM	; If less than normal typeout mode
	     RET		; Don't type this message
	    TYPE <Not confirmed, old connection kept%/>
	    RET
	  ENDIF.
	  CALL @.CLOSE(V)	; Else close old connection
	ENDIF.

	;; Everything ok, open the connection.
	MOVE V,C		; All confirmed, get new protocol vector
	MOVEM B,HSTNUM		; Save new host number
	HRROI A,HSTSTR		; Point to host string
	MOVEI C,V		; Point to protocol again
	CALL $GTNAM		; Get host name
	 NOP			; Can't fail
	HRROI A,HSTSTR		; Point to string again
	CALL $RMREL		; Remove ugly relative domains
	CALL @.OPEN(V)		; Call open routine for protocol
	 RET
	CALL @.OPSYS(V)		; Look up operating system, return in A/
	SKIPN OPSYS		; Did we already have an operating system?
	IFSKP.
	  CAMN A,OPSYS		; Yes, was it the same type as the new one?
	ANSKP.
	  SETZM DEFPRP		; No, clear default properties
	  MOVE B,[XWD DEFPRP, DEFPRP+1]	; So we don't do bizarre things
	  BLT B,DEFPRP+PLSIZE-1	; ...
	ENDIF.
	MOVEM A,OPSYS		; Update operating system type
	CALL OPTTYP		; Set default transfer types


	;; Connection opened, now handle old and default logins.
	CALL OLDUSR		; Have an old user name to use?
	 CALL GETUDF		; Get user default login
	  RETSKP		; Kept old login or no default, done now
	IFVERB VB.NRM		; If we're being noisy...
	  HRROI A,3(B)		; Point to string
	  TYPE <Default login as %1S%/>
	ENDIF.
	SETZM CONNAM		; No connected directory
	HRROI A,USRNAM		; Point to where user name lives
	HRROI B,3(B)		; Point to start of string.
	SETZ C,			; Until null
	SOUT%			; Copy string
	SETZM USRACT		; Be careful in case no account
	HRROI A,USRACT		; Now point to where account lives
	HRROI B,1(B)		; Point to rest of default string
	SOUT%			; Copy that too
	SETZM USRPSW		; No password yet
	SETZM LGNUNC		; Login is connected
	CALL @.LOGIN(V)		; Try logging in
	 NOP			; Don't care about failures here
	RETSKP
; Old user name is left around from previous connection, confirm
; that user wants to keep it.

LS LGNUNC			; Nonzero = last login was while unconnected

OLDUSR:	SKIPN LGNUNC		; Logged in unconnected
	 SKIPG OLDLGN		; Or confirming old login?
	  SKIPN USRNAM		; Have user name?
	   JRST CLROLG		; No user name or flush old login, done
	SKIPN LGNUNC		; Previous login was connected
	 SKIPE OLDLGN		; And we have to ask?
	  IFSKP.
	    PROMPT TEMP,A	; Start making a prompt
	    HRROI B,USRNAM	; First the user name
	    WRITE <Logged in as %2S>
	    HRROI B,USRACT	; Then the account
	    SKIPE USRACT	; If there is an account set
	     WRITE <, account %2S>
	    WRITE < [Confirm] >	; Finish prompt
	    CALL DOCONF		; Confirm it all
	     JRST CLROLG	; Not confirmed, clear all old defs
	  ENDIF.
	SETZM LGNUNC		; Now if we remain logged in it's connected
	CALL @.LOGIN(V)		; Finish logging in (don't ask for psw)
	 JRST CLROLG		; Failed, stop now
	AOS (P)			; After that we always skip return
	SKIPN CONNAM		; Is there a connected directory?
	 JRST CLRODR		; No, clear out default dir
	PROMPT TEMP,A		; Else start making a prompt
	HRROI B,CONNAM		; Point to the dir name
	WRITE <Connected to %2S [Confirm] >
	CALL DOCONF		; See if we want to stay connected
	 JRST CLRODR		; No, clear old directory
	CALL @.CWDIR(V)		; Yes, try connecting
	 RET			; Don't care about failure
	RET

CLROLG:	SETZM USRNAM		; No user name
	SETZM USRACT		; or account
CLRODR:	SETZM CONNAM		; No connected directory
	SETZM DEFPRP+P.DIRE	; No default directory
	RET
; Here with HSTINF set up for new connection
; Set transfer type according to operating system
; Returns +1/always

LS OPSYS			; Operating system

OPTTYP:	MOVE A,OPSYS		; Get host info pointer
	MOVE B,DEFPRP+P.TYPE	; Get transfer type
	MOVE C,DEFPRP+P.BYTE	; and byte size
	CAIE B,TT.PAG		; If neither this paged
	 CAIN B,TT.MEI		; nor this paged
	  IFSKP.
	    CAIE C,^D36		; Not paged, if bytesize other than 36
	     JUMPN B,R		; and type specified, don't change setting
	  ENDIF.		; Otherwise unspecified or 36-bit, need to set
	CALLRET @.SDEFS(V)	; Set default transfer types
STTXT8::HRROI A,[ASCIZ/ascii, byte size 8/]
	MOVX B,TT.TXT		; Type text
	MOVX D,^D8		; Byte size 8
	JRST STIMAG		; Go set it

STPAGE::HRROI A,[ASCIZ/paged/]
	MOVEI B,TT.MEI		; Use efficient type unless asked otherwise
	MOVEI C,"P"		; Paged for TCP too
	MOVEI D,^D36		; Default byte size is 36
	JRST STMSG		; Go set it

ST36BN::HRROI A,[ASCIZ/binary, byte size 36/]
	MOVEI B,TT.BIN		; Not paged but can't tell us file types
	MOVEI D,^D36		; Host is 36 bit, use that and binary transfers
	JRST STIMAG		; Go set it

STNOPG::HRROI A,[ASCIZ/unspecified/]
	SETZB B,D		; Other hosts can decide on a per-file basis
STIMAG::MOVEI C,"I"		; Image mode for TCP
	JRST STMSG		; Go set it

STTEXT::HRROI A,[ASCIZ/text/]	; Primarily for MULTICS
	MOVEI B,TT.TXT		; Type is ASCII text
	MOVEI C,"S"		; TCP Mode is stream
	SETZ D,			; Default the byte size
	JRST STMSG		; Go set default parameters

STMSG:	SAVEAC <FX>		; Save flag register
	SETO FX,		; Setting all possible combinations
	IFVERB VB.NRM		; If not quiet
	  CAMN B,DEFPRP+P.TYPE	; then if transfer type
	   CAME D,DEFPRP+P.BYTE	; or byte size is changed
	    TYPE <Setting default transfer type to %1S.%/>
	ENDIF.
	CALL TYPSET		; Set the transfer type
	MOVE B,D		; Get byte size
	CALLRET BYTSET		; Set that too
; Get pointer to user for default login

GETUDF:	MOVE B,ULGDEF		; Get user-login defaults.
	MOVE C,HSTNUM		; Get host number to look for
	DO.
	  JUMPE B,R		; If no more, return
	  CAME V,1(B)		; Same as current protocol?
	  IFSKP.
	    CAMN C,2(B)		; Yes, same host number?
	     EXIT.		; Yes, all done
	  ENDIF.
	  HRRZ B,(B)		; Didn't match, get the next one
	  LOOP.			; And go back to try it
	ENDDO.
	SKIPL (B)		; Is this a real default?
	 AOS (P)		; Yes, set up success return
	RET
SUBTTL LOGIN command

; LOGCMD routine can be called as a subroutine (as from PROCNO in PUPFTP)
; it will return +1 on success or repeat parse on failure

H.LOGI:	ASCIZ/The LOGIN command tells the foreign host your username and
password.  Depending on the protocol in use they may either be
checked immediately, or not until you do a file transfer.
The password is prompted for on a separate line, and is not echoed.
See also the SET USER command for a way to specify in an FTP.INIT
usernames to log in as whenever you connect to a given host.
/

C.LOGI:	NOISE <user>		; Give noise words
	CALL NOHALT		; Don't stop if JCL 
LOGCMD::CALL CLRTMP		; Clear temp page
	MOVEI B,[FLDDB. .CMCFM,CM%SDH,,,,[
		 FLDBK. .CMFLD,CM%SDH,,<user name at remote site>,,REMBRK]]
	CALL .COMND
	 ERMSG <Invalid remote username>
	LOAD C,CM%FNC,(C)	; Get function parsed
	CAIE C,.CMCFM		; Carriage return?
	IFSKP.
	  SETZM USRNAM		; Yes, no username
	ELSE.
	  MOVE A,[POINT 7,TEMP]	; Copy the string to there.
	  CALL CPYATM		; copy from atom buffer into TEMP
	  CAILE D,USRSTL	; Was it too long?
	   ERMSGB <Username too long>
	  NOISE <account>
	  MOVEI B,[FLDDB. .CMCFM,CM%SDH,,,,[
		   FLDBK. .CMFLD,CM%SDH,,<remote account string>,,REMBRK]]
	  CALL .COMND
	   ERMSG <Invalid account string>
	  LOAD A,CM%FNC,(C)	; Get FDB that won.
	  CAIN A,.CMCFM		; Did user confirm command here?
	  IFSKP.
	    MOVE A,[POINT 7,TEMP+400] ; No, point to halfway through temp space
	    CALL CPYATM		; Copy account string there
	    CAILE D,USRSTL	; Was it too long?
	     ERMSGB <Account string too long>
	    CALL CONFRM		; Finish the parse
	  ENDIF.
	  MOVE A,[TEMP,,USRNAM]	; Ok, remember all parameters
	  BLT A,USRNAM+USRSTL/5 ; Copy user name
	  MOVE A,[TEMP+400,,USRACT]
	  BLT A,USRACT+USRSTL/5 ; and account name
	ENDIF.

	;; All parsed - now figure out what to do with it
	IFXE. F,F%COPN		; Have a connection open?
	  SKIPN USRNAM		; No, if just wanted no user name
	   RET			; Then don't ask for the password
	  HRROI A,USRPSW	; Point to where to put password
	  MOVEI B,USRNAM	; And location to clear on abort
	  CALL GETPSW		; Get password from user
	   JRST .ERMSX		; Aborted (must have been top level)
	  SETOM LGNUNC		; Login is not connected
	ELSE.
	  SETZM USRPSW		; No password yet
	  SETZM LGNUNC		; Login is connected
	  CALL @.LOGIN(V)	; Check login against protocol handler
	   JRST .ERMSX		; Failed, back to top of same parse
	ENDIF.
	RET
SUBTTL CONNECT command.
; CONSUB can be called analogously to LOGCMD

H.CONN:	ASCIZ/The CONNECT command gives FTP the name of a directory
on the foreign machine to connect to, and the password for
that directory.

As with the LOGIN command, the directory name and password are not
checked until a file transfer is attempted.

Note that most machines will require a password for directories connected
to with this command.  If you want to simply set a default without
requiring full access to the directory, use the SET DIRECTORY command.
/

C.CONN:	NOISE <to directory>
	CALL NOHALT
CONSUB::CALL CLRTMP		; Clear temp page
	MOVEI B,[FLDDB. .CMCFM,CM%SDH,,,,[
		 FLDBK. .CMFLD,CM%SDH,,<remote directory>,,REMBRK]]
	CALL .COMND
	 ERMSG <Unable to parse remote directory specification>
	LOAD C,CM%FNC,(C)	; Get FDB parsed.
	CAIE C,.CMCFM		; Was it carriage return?
	IFSKP.
	  SETZM CONNAM		; Yes, clear directory.
	ELSE.
	  MOVE A,[POINT 7,TEMP]	; Copy the string to temp buffer.
	  CALL CPYATM
	  CAILE D,USRSTL	; Was it too long?
	   ERMSGB <Remote directory name too long>
	  CALL CONFRM		; Finish parse
	  MOVE A,[TEMP,,CONNAM]	; Ok, remember all parameters
	  BLT A,CONNAM+USRSTL/5
	ENDIF.

	;; All parsed - now figure out what to do with it
	IFXE. F,F%COPN		; Have a connection open?
	  SKIPN CONNAM		; No, if just wanted no dir name
	   RET			; Then don't ask for the password
	  HRROI A,CONPSW	; Point to where to put password
	  MOVEI B,CONNAM	; And location to clear on abort
	  CALL GETPSW		; Get password from user
	   JRST .ERMSX		; Aborted (must have been top level)
	ELSE.
	  SETZM CONPSW		; No password yet
	  CALL @.CWDIR(V)	; Check dir against protocol handler
	   JRST .ERMSX		; Failed, back to top of same parse
	ENDIF.
	RET
SUBTTL DDT command
H.DDT:	ASCIZ/The DDT command enters DDT on FTP's core image.
This command is mostly useful for debugging the FTP program.
Return with R$G or RET$X.
/

C.DDT:	NOISE <self>
	CALL CONFRM
	MOVE A,[.FHSLF,,770]	; This process, starting at 770000
	RMAP%			; Read page status.
	TXNE B,RM%PEX		; Page exists?
	 JRST 770000		; Yes, don't bother re-mapping
	MOVEI A,.FHSLF		; For this process
	GEVEC%			; get entry vector
	PUSH P,B		; and save it.
	MOVX A,GJ%SHT!GJ%OLD	; Short form, old file
	HRROI B,[ASCIZ/SYS:UDDT.EXE/]
	GTJFN%			; Get handle on DDT program.
	 ERMSGX <Couldn't get JFN for UDDT>
	HRLI A,.FHSLF		; On ourself again
	GET%			; Map in DDT
	HRRZS A			; Get JFN alone again
	RLJFN%			; and flush it
	 ERNOP			; but don't care if it refuses to leave.
	MOVEI A,.FHSLF		; Get process handle back
	POP P,B			; and saved entry vector
	SEVEC%			; to reset our entry vector to normal.
	CALL NOHALT		; Don't stop if this was JCL
	MV. .JBSYM,@770001	; Set symbol table location
	MV. .JBUSY,@770002	; And undefined symbol pointer
	JRST 770000		; Start up DDT, return from command
SUBTTL TYPE command to GET file to TTY:

H.TYPE:	ASCIZ/The TYPE command retrieves a file to the terminal.
Except in that it doesn't ask for the local filename and that
it doesn't type exclamation marks or such, it is very much like
the GET command with a local filename of TTY:.
/

C.TYPE:	NOISE (remote file)
	SETZ A,			; No file default
	HRROI B,TMPPRP+P.SFIL	; Into this file
	CALL FRNFIL		; Parse foreign filename
	 ERMSGX <Null remote filename specified>
	CALL CONFRM		; Finish parse
	TXZ F,F%UPDA		; Not checking against local file date
	TXO F,F%STYO		; Suppress typeout
	CALL SETPRP		; Get a property list
	TXO F,F%PSET		; Don't do it again within SET command
	MVI. TT.TXT,FILPRP+P.TYPE ; Set text mode transfer
	MVI. TT.TXT,FILPRP+P.XTYP ; Set text mode transfer remotely too
	SETZM RCDDEV		; Only one part of device
	MVI. <ASCIZ/TTY:/>,RCDDEV+1 ; With terminal as default device
	SETZM RCDDIR+1		; No directory
	SETZM RCDNAM+1		; No filename
	SETZM RCDEXT+1		; No extension
	MVI. GJ%FOU,RCDFLG	; Set flag so we don't ask for each file
	JRST C.RET1		; Go join GET command
SUBTTL GET and UPDATE commands

LS RCDDEV,10			; Device name
LS RCDDIR,10			; directory name
LS RCDNAM,10			; filename
LS RCDEXT,10			; and extension
LS RCDFLG			; and flags/generation for GTJFN block.

H.GET:	ASCIZ/The GET command copies a file or files from the foreign host
to the local host.  The directory, filename, and extension of the
local filename may each contain one "*", which if given is filled in
with the appropriate part of the remote filename.  If confirmation is
required for GET (see SET CONFIRMATION) and no local filename is given,
FTP will prompt for a local file individually for each remote file.

FTP>GET *.TXT SCORE-*.TXT		; GET command with wildcards
 MAIL.TXT.1 => SCORE-MAIL.TXT.1 !! [OK]
 NOTES.TXT.23 => SCORE-NOTES.TXT.1 !! [OK]
FTP>GET FOO.*				; GET command with no local file given
FOO.FAH (to local file) ^G
Ok, ignoring that file.			; Didn't want FOO.FAH, type ^G
FOO.FOO (to local file) BAR.BAZ		; Name of file to transfer to
 FOO.FOO.3 => BAR.BAZ.2 !! [OK]		; System types this out

If the LOGIN or CONNECT commands have been used incorrectly,
FTP will ask for a corrected user name or connected directory.
To return to top level from this type ^G.
/


H.UPDA:	ASCIZ/The UPDATE command is the same as the GET command, except
that files with a write-date earlier than that of the latest
local copy will not be retrieved.  If confirmation is required
for update then FTP will ask the user whether to go ahead with
a transfer after it has checked the write date.
/

C.UPDA:	TXO F,F%UPDA		; UPDATE not GET
	NOISE2 <from remote file>,<remote file>
	JRST C.GET0		; jump into GET code

C.GET:	TXZ F,F%UPDA		; GET not UPDATE
	NOISE <remote file>
C.GET0:	MOVE KF,KF.GET		; Set up keep flags
	SETZ A,			; No default
	HRROI B,TMPPRP+P.SFIL	; Into this file
	CALL FRNFIL		; Parse foreign filename
	 ERMSGX <Null remote filename specified>

	;; Parsed remote filename, now try for local file
	;; First set up GTJFN block for nice filename defaults
	SETZM RCDFLG		; No one-liner defaults yet.
	SETZM CMDGTJ		; Start setting up GTJFN defaults
	MOVE B,[CMDGTJ,,CMDGTJ+1]
	BLT B,CMDGTJ+20-1	; Zero command GTJFN block
	HRROI B,TMPPRP+P.SFIL	; Pointer to filename string
	CALL SKPDIR		; Skip over directory name
	MOVX A,GJ%SHT!GJ%FOU!GJ%OFG
	GTJFN%			; Turn into a JFN
	IFNJE.
	  MOVE B,A		; Move JFN to a new register
	  CALL GTJDFT		; Set defaults from it.
	  MOVE A,B
	  RLJFN%		; Get rid of temporary JFN
	   NOP
	ELSE.
	  HRROI A,[ASCIZ/*/]	; Couldn't get defaults, just use * instead
	  MOVEM A,CMDGTJ+.GJNAM
	  MOVEM A,CMDGTJ+.GJEXT
	ENDIF.

	;; Defaults all set up, now try parsing file
	NOISE <to local file>
	MOVX A,GJ%FOU!GJ%OFG!GJ%FLG
	TXNE KF,K%VERS
	 HRRI A,.GJALL		; If preserving versions, default "*"
	MOVEM A,CMDGTJ+.GJGEN	; Save flags in GTJFN block.
	MOVEI B,[FLDDB. .CMCFM,,,,,[
		 FLDDB. .CMFIL,CM%SDH,,<local filename>]]
	TXNN F,F%GETC		; Confirming GETs?
	 LOAD B,CM%LST,(B)	; No, accept carriage return as default file
	CALL .COMND		; Do the parse
	 JRST CNFERR
	LOAD C,CM%FNC,(C)	; Find out which FDB parsed

	;; Have parsed filename, now check it over (JFN in B)
	TXO F,F%DSKF		; Assume disk if we don't have a file yet
	CAIN C,.CMCFM		; Was it carriage return?
	 JRST C.RET1		; Yes, all parsed
	MOVEM B,TMPJFN		; Save in case of reparse or command error
	CALL OUTWLD		; Make sure wildcards are normal, set names
	MOVE A,TMPJFN		; Get JFN back
	CALL CHKDSK		; See if it is on disk or not for protocol.
	CALL JFNVRS		; Get version number of JFN spec.
	HRLI B,(GJ%FOU)
	MOVEM B,RCDFLG		; Save it and other flags
	RLJFN%			; Flush the JFN
	 ERNOP
	SETZM TMPJFN		; JFN flushed, don't flush it twice

	;; Finish parse, check if confirmation needed for wildcarding
	CALL CONFRM		; Finish parse
	IFXE. F,F%TEMP		; If no wildcards
	  CALL WLDRET		; See if there were wildcards in remote spec
	ANSKP.
	  HRROI A,TMPPRP+P.SFIL	; Point to filename
	  TYPE <Remote spec "%1S" is wildcard but local destination is not.
Multiple remote files may be sent to the same local file.%/>
	  PROMPT [ASCIZ/Using specification as is [Confirm] /]
	  CALL DOCONF		; Save user from possible lossage
	   RET			; Not confirmed, give up command
	ENDIF.

; Here to generate "Retrieve" command to server
C.RET1:	CALL SETPRP		; Now get a new property list
	HRROI A,FILPRP+P.SFIL	; Point to filename there
	HRROI B,TMPPRP+P.SFIL	; And what we have
	WRITE <%2S>		; Copy
	ABTSET			; Set up so we can tell we were aborted
	CALL CHKCON		; Make sure we have a connection.
	 RET			; Didn't want one, stop
	TXZ F,F%ABTD		; Haven't typed abort message yet
	CALLRET @.RECV(V)	; Go do the transfer
; Check remote filespec for wildcarding
; returns +1/no wildcards, +2/wildcard character found

WLDRET:	MOVE A,[POINT 7,TMPPRP+P.SFIL] ; Point to parsed filename
	DO.
	  ILDB B,A		; Get next char of spec
	  CAIE B,"*"		; Is it this wildcard
	   CAIN B,"%"		; Or this wildcard?
	    RETSKP		; Yes, found a wildcard
	  JUMPN B,TOP.		; Else try again until wild or end of string
	ENDDO.
	RET			; No wildcards found, all done
; Check output filespec for appropriate wildcarding.
; Call with B/JFN (flags should be included), returns +1
; Directory, filename, and extension may only have one * (position is saved).
; Sets F%TEMP if any wildcard chars are seen

OUTWLD:	TXZ F,F%TEMP		; No wildcards seen yet
	MOVX C,FLD(.JSSSD,JS%DEV)
	MOVEI D,RCDDEV		; Save device if it was given
	CALL CHKWLD
	MOVX C,FLD(.JSSSD,JS%DIR)
	MOVEI D,RCDDIR		; Save directory if it was given
	CALL CHKWLD
	MOVX C,FLD(.JSAOF,JS%NAM)
	MOVEI D,RCDNAM		; Save filename always
	CALL CHKWLD
	MOVX C,FLD(.JSAOF,JS%TYP)
	MOVEI D,RCDEXT		; Save extension always
;	CALLRET CHKWLD

CHKWLD:	SETZM 1(D)		; No string yet
	HRROI A,1(D)		; Into place to store string
	TXO C,JS%PAF		; Punctuating
	JFNS%			; get string for file part
	 ERJMP R		; Allow errors to mean null string
	SETZM (D)		; No pointer yet
	MOVEI A,1(D)		; Point to start again
	HRLI A,(POINT 7)	; Make into a byte pointer
	DO.
	  ILDB C,A		; Get next char
	  CAIN C,"*"		; If star
	   EXIT.		; Go process
	  CAIN C,"%"		; If percent
	   ERMSGB <Illegal to use "%" in wildcard filename>
	  JUMPN C,TOP.		; If not null, back for the next
	  RET			; Else done
	ENDDO.
	TXO F,F%TEMP		; Star, remember we had wildcard
	SETZ C,			; Get null
	DPB C,A			; Drop in over star
	MOVEM A,(D)		; Save pointer to second half
	SKIPN 1(D)		; If nothing in the string
	 HLLOS 1(D)		; Make it at least not zero
	DO.
	  ILDB C,A		; Get next char
	  CAIE C,"*"		; If star
	   CAIN C,"%"		; or percent
	    ERMSGB <Multiple wildcard chars in filename field>
	  JUMPN C,TOP.		; Neither, if not null back for next
	ENDDO.
	RET			; Else all done
; Here when protocol retrieve routine asks for a JFN to retrieve into
; assumes FILPRP has the specific file properties being retrieved

LS PRTSTR,4			; Where to put string for file protection

RECFIL::SAVEAC <A,B,C,D>	; Don't mung caller's ACs
	ABTSKIP			; If abort has been typed
	IFSKP.
	  TXON F,F%ABTD		; Remember we've done this before
	   TYPE <[Aborting multiple-file retrieval]%/>
	  RET			; If already typed, don't say so twice
	ENDIF.

	SETZM CMDGTJ		; Clear out GTJFN defaults
	MOVE B,[CMDGTJ,,CMDGTJ+1]
	BLT B,CMDGTJ+20-1	; Zero command GTJFN block
	MOVX A,GJ%FOU!GJ%MSG
	TXNE KF,K%VERS		; Preserving versions?
	 HRR A,FILPRP+P.VERS	; Yes, include new version in GTJFN block
	MOVEM A,CMDGTJ+.GJGEN	; (used for all but one-liner filename fill)
	SKIPL B,FILPRP+P.PROT	; Protection given?
	 TXNN KF,K%PROT		; and keeping protection?
	  IFSKP.
	    HRROI A,PRTSTR	; Yes, get pointer to buffer space
	    MOVEM A,CMDGTJ+.GJPRO ; Save as file default protection
	    MOVEI C,^D8		; In octal
	    NOUT%		; Type out the number
	     JFATAL
	  ENDIF.

	SKIPN A,RCDFLG		; Did user do this as a one-liner?
	 JRST RFPARS		; No, go ask for filename
	MOVEM A,CMDGTJ+.GJGEN	; Save RCDFLG in GTJFN block.
	HRRZS A			; Get only right half
	CAIE A,.GJALL		; Wildcard?
	IFSKP.
	  SKIPN A,FILPRP+P.VERS	; Yes, get version from file props
	   MOVX A,.GJNHG	; or next highest file if none given
	  HRRM A,CMDGTJ+.GJGEN	; and use that instead.
	ENDIF.
	MOVX A,GJ%OFG!GJ%SHT	; Get parse-only JFN for server filespec
	CALL GETNMB		; from name-body of remote file.
	GTJFN%
	 TDZA B,B		; Couldn't, clear it
	  MOVE B,A		; Got one, put in correct register
	HRROI A,TEMP		; Start building filename in temp register
	HRROI C,FILPRP+P.DEVI	; Use device
	MOVEI D,RCDDEV
	CALL RCDOUT		; Process device name
	HRROI C,FILPRP+P.DIRE
	MOVEI D,RCDDIR
	CALL RCDOUT		; and directory
	MOVX C,FLD(.JSAOF,JS%NAM)
	MOVEI D,RCDNAM
	CALL RCDOUT		; and filename
	MOVX C,FLD(.JSAOF,JS%TYP)
	MOVEI D,RCDEXT
	CALL RCDOUT		; and extension
	SKIPE A,B		; Did we have a JFN to default with?
	 RLJFN%			; Yes.  Flush it
	  ERNOP			; and ignore any errors in doing so.
	MOVE A,CMDGTJ+.GJGEN	; Get generation
	TXO A,GJ%SHT		; Short form
	HRROI B,TEMP		; From string we just made
	GTJFN%			; Get JFN for destination file.
	IFSKP.
	  MOVE B,A		; Got it, put it where we want it
	  JRST C.RET7		; Go check UPDATE and send it off
	ENDIF.
	HRROI A,TEMP		; Get string we tried to use
	TYPE <%% %J, bypassed - "%1S"%/>
	RET			; Return failure to .RECV(V)
; Here to fill in one field of file name being built
; A/pointer to file name, B/JFN to default with,
; C/-1,,string or JFNS% bits, D/where default lives

RCDOUT:	SKIPN 1(D)		; If no string
	 RET			; Then all done with this one
	PUSH P,B		; Save register
	HRROI B,1(D)		; Get string
	WRITE <%2S>		; and add it in
	POP P,B			; Get JFN back
	SKIPN D,0(D)		; Get second part if any
	 RET			; None, done now
	IFL. C			; If we had a string
	  WRITE <%3S%4S>	; Write it
	  RET			; all done
	ENDIF.
	CALL .JFNS		; Else add wild part of filename
	WRITE <%4S>		; and then add second part
	RET
; Here when user needs to give a specific filename to retrieve into

RFPARS:	SETZM CMDGTJ		; Clear GTJFN block
	MOVE A,[CMDGTJ,,CMDGTJ+1]
	BLT A,CMDGTJ+20-1
	MOVX A,GJ%OFG!GJ%SHT	; Get parse-only JFN for server filespec
	CALL GETNMB		; from name-body of remote file.
	GTJFN%
	IFNJE.
	  MOVE B,A		; Save JFN we just got
	  CALL GTJDFT		; Set up GTJFN defaults from it
	  MOVE A,B		; Get JFN back
	  RLJFN%		; and flush it
	   NOP
	ENDIF.
	SKIPLE A,FILPRP+P.VERS	; Look at file version if we have one
	 TXNN KF,K%VERS		; Want to keep versions?
	  MOVX A,.GJNHG		; No version or not keeping, use next highest
	TXO A,GJ%FOU		; This is for output
	MOVEM A,CMDGTJ+.GJGEN	; Save as default generation and flags
	PROMPT TYPBUF,A		; Set prompt avoiding file defaults
	HRROI B,FILPRP+P.SFIL
	WRITE <%2S (to local file) >
	SETABORT (GETABT)
	CALL SETCMD
	MOVE P,SAVPDL
	CALL FLTFIL		; Flush JFN on reparse
	MOVEI B,[FLDDB. .CMFIL,CM%SDH,,<local filename>]
	CALL .COMND
	 ERMSG <Invalid local filename>
	MOVEM B,TMPJFN		; Save for reparse or abort
	CALL CONFRM
	CLRABORT
	SETZM TMPJFN		; Parse complete, don't flush JFN

; Now we have JFN for the local file in B.
; Open it and tell server to send file
C.RET7:	MOVEM B,FILJFN		; Save the JFN
	TXNN F,F%UPDA		; Is this UPDATE command?
	 JRST RECYES		; No, always want the file
	SKIPN FILPRP+P.WDAT	; Do we have a write date?
	 JRST UPDCNF		; No, always copy file across
	HRROI A,TEMP		; Into temp buffer
	MOVX C,FLD(.JSAOF,JS%DEV)!FLD(.JSAOF,JS%DIR)!FLD(.JSAOF,JS%NAM)!FLD(.JSAOF,JS%TYP)!JS%PAF
	JFNS%			; get filename, flush generation.
	 ERJMP UPDCNF		; Failed, always transfer the file
	MOVX A,GJ%OLD!GJ%SHT	; Looking for an old file
	HRROI B,TEMP		; With name we just got
	GTJFN%			; Get the JFN
	 JRST UPDCNF		; Failed, give up
	MOVX B,<1,,.FBWRT>	; Want write date
	MOVEI C,C		; Into C
	GTFDB%			; Get FDB word
	 ERNOP
	RLJFN%			; Now flush useless JFN
	 ERNOP
	;; Should do IDCNV/ODCNV to canonicalize date here.  Instead...
	ADDI C,3		; Fudge thirds of seconds in date number
	CAMGE C,FILPRP+P.WDAT	; Greater than before?
	 JRST UPDCNF		; No, want that file
UPDNO:	MOVE A,FILJFN		; Get JFN back
	SETZM FILJFN		; Don't have anything there now
	RLJFN%			; Flush it since it is useless
	 ERNOP
	RET			; Tell .RECV(V) not to use it
; Here when we want the file, maybe ask the user for confirmation

UPDCNF:	TXNN F,F%UPDC		; Confirm for updates?
	IFSKP.
	  PROMPT TEMP,A		; Set prompt to temp area, get ptr to start
	  MOVE B,FILJFN		; Get JFN back
	  WRITE <Updating %2F [Confirm] >
	  CALL DOCONF		; Confirm action
	   JRST UPDNO		; Didn't want it, give up
	ENDIF.
RECYES:	MOVE A,FILJFN		; Get JFN again
	CALL CHKDSK		; Now see if it is a disk file
	MOVE A,FILPRP+P.TYPE	; Get desired transfer type
	JRST @[	RTYUNS		; Unspecified
		RTYTXT		; Text
		RTYBIN		; Binary
		RTYPAG		; Tenex-Paged
		RTYDIR		; Directory
		RTYPAG		; MEIS-Paged
		RTYTXT		; EBCDIC
		RTYBIN ](A)	; Image

; Here when user types ^G to "FOO (to local file) " parse
GETABT:	TYPE <Ok, ignoring that file.%/>
	ABTSET			; Set so we still prompt for other files
	RET
; Here to try opening the file depending on the transfer type

RTYUNS:	HRROI A,FILPRP+P.SFIL	; Transfer type not given, get file name
	TYPE <?Transfer type unspecified, skipping %1S%/>
	JRST UPDNO		; Give up on the file

RTYDIR:	HRROI A,FILPRP+P.SFIL	; Transfer type not given, get file name
	TYPE <%% %1S is a directory, not transferred%/>
	JRST UPDNO		; Give up on the file

RTYPAG:	IFXN. F,F%DSKF		; Paged, device disk?;
	  MOVEI B,^D36		; Byte size is 36
	  JRST RTYPG2		; (may be changed later by transfer code)
	ENDIF.
	MOVE A,FILJFN		; Paged transfer for non-disk file
	TYPE <?%1F is not a disk file, paged transfers illegal%/>
	JRST UPDNO

RTYBIN:	SKIPA B,FILPRP+P.BYTE	; Binary, use specified byte size
RTYTXT:	 MOVEI B,7		; Text, use byte size 7
RTYPG2:	MOVE A,FILJFN		; Get JFN back
	ROT B,-6		; Position byte size for OPENF%
	TXO B,OF%WR
	OPENF%
	 JRST RFNOPN		; Couldn't open, go complain
	RETSKP			; Else return success

; Here when really retrieving data, types file names and calls TIMEIN.
FILEIN::SKTERS VB.DEB		; Debugging?
	 JRST TIMEIN		; Yes, will see later
	SKVERB VB.NRM		; Terse?
	 JRST TIMEIN		; Yes, done now.
	HRROI A,FILPRP+P.SFIL	; Point to remote filename
	MOVE B,FILJFN		; and local filename
	TYPE ( %1S => )		; Print remote filename
	MOVX A,.PRIOU		; To the TTY
	MOVE B,FILJFN		; Local filename
	MOVX C,<211111000000!JS%PAF> ; Optional device, force up to protection
	JFNS%			; Print local filename, including protections!
	TYPE ( )		; Finish up message
	CALLRET TIMEIN		; Go set up timers
; Here when trying to open the file failed

RFNOPN:	HRRZ A,FILJFN		; Release the JFN
	SETZM FILJFN		; Don't think we still have it
	ETYPE <Couldn't open %1F - %J>
	RLJFN%
	 NOP
	TXNE F,F%GETC
	 JRST RFPARS		; Ask for new filename
	TYPE <, transfer aborted.%/>
	RET			; Not asking user, just give up on that xfer
; Here when a transfer is finished, to finish timing and close the file
; note that if the transfer is abnormally terminated then the transfer
; routine should worry about cleaning up, and not call this routine.

FILOUT::SAVEAC <A,B,C>		; Don't mung registers
	HRRZ A,FILJFN		; Get JFN of file we just retrieved
	MOVE B,FILPRP		; Get file properties
	CALL SETPLT		; Remember if a plot file
	TXO A,CO%NRJ
	CLOSF%			; Close local file but keep JFN
	 JERMSG <Unlikely CLOSF% error>
	HRRZ A,FILJFN
	CALL TIMOUT		; Finish statistics (must be after file closed)
	CALL SETKEP		; Set kept parameters
	MOVE A,FILJFN
	SETZM FILJFN
	RLJFN%			; Flush the now-useless JFN
	 NOP
	RET
; Set various kept fields of the destination file

DEFINE UNSETK (NAME) <
	ERCAL [	TYPE <Couldn't set NAME of %1F - %J%/>
		RET ]
>

SETKEP:	TXNN F,F%DSKF		; Only if it's a disk file...
	 RET
	IFXN. KF,K%AUTH		; Keeping author fields?
	  HRRZ A,FILJFN		; Yes, get JFN, set up for creator (.SFAUT = 0)
	  HRROI B,FILPRP+P.OAUT ; Point to creator string
	  SKIPN (B)		; If none,
	   HRROI B,FILPRP+P.AUTH ; Point to last writer instead
	  SKIPE (B)		; If we have something
	   CALL .SFUST		; Then set it
	  HRROI B,FILPRP+P.AUTH ; Now point to author for sure
	  HRLI A,.SFLWR		; Set up for last writer
	  SKIPE (B)		; If we have something
	   CALL .SFUST		; Then set it
	ENDIF.
	HRRZ A,FILJFN		; Get JFN back again
	SETO B,
	TXNE KF,K%CDAT		; Keeping creation date?
	 SKIPN C,FILPRP+P.CDAT	; And date present in property list?
	  IFSKP.
	    HRLI A,.FBCRV
	    CHFDB%
	     UNSETK <creation date>
	  ENDIF.
	TXNE KF,K%WDAT		; Keeping write date?
	 SKIPN C,FILPRP+P.WDAT	; And write date present?
	  IFSKP.
	    HRLI A,.FBWRT
	    CHFDB%
	     UNSETK <write date>
	  ENDIF.
	TXNE KF,K%RDAT		; Keeping read date?
	 SKIPN C,FILPRP+P.RDAT	; Read date seen?
	  RET
	HRLI A,.FBREF
	CHFDB%
	 UNSETK <read date>
	RET
SUBTTL PRINT file on remote lineprinter

H.PRIN:	ASCIZ/The print command sends a file or files to the lineprinter of
another system.  Typically this merely means sending to file LPT:.
/

C.PRIN:	NOISE <local file>
	MOVE KF,KF.SND		; KEEP doesn't matter but use SEND flags
	SETZM CMDGTJ
	MOVE A,[CMDGTJ,,CMDGTJ+1]
	BLT A,CMDGTJ+20-1
	MVI. <GJ%OLD!GJ%IFG!.GJDEF>,CMDGTJ+.GJGEN ; Old, wild
	MOVEI B,[FLDDB. .CMFIL,CM%SDH,,<local filename>]
	CALL .COMND
	 ERMSG <Invalid local filename>
	MOVEM B,TMPJFN		; Save for reparse
	CALL CONFRM		; Finish parse
	CALL CHKCON		; Make sure we have a connection
	 RET
	MOVE B,TMPJFN		; Confirmed, get JFN back
	SETZM TMPJFN		; Don't flush next time through command loop
	MOVEM B,FILJFN		; Save JFN for file transfer
	CALLRET @.PRINT(V)	; Go print it
SUBTTL STORE command.

H.SEND:	ASCIZ/The SEND command sends a file from the local machine, storing
on the other machine.  Example:

FTP>SEND FOO.*				; The name of the local file
FOO.FAH (to remote file) ^G		; Don't want to send that one, type ^G
Ok, ignoring that file.
FOO.FOO (to remote file) BAR.BAZ	; Name of file on other machine
 FOO.FOO.3 => BAR.BAZ.-1 !! [OK]	; System types this

Wildcards may be used to fill in the filename, extension, and version
of the local file somewhere within the remote filename.  If more than
one of the above is given they must each have exactly one character
between them.  Example:

FTP>SEND FINGER.PLAN "GUEST2;* *"
 FINGER.PLAN => DSK:GUEST2;FINGER PLAN !! [OK]

If the LOGIN or CONNECT commands have been used incorrectly,
FTP will ask for a corrected user name or connected directory.
To return to top level from this type ^G.
/

H.INST:	ASCIZ/The INSTALL command is the same as the SEND command, except
that files with a write-date earlier than that of the latest
remote copy will not be stored.  Confirmation for UPDATE also
applies to INSTALL; see the help on UPDATE for details.
/

C.INST:	TXOA F,F%UPDA		; INSTALL not SEND
C.SEND:	 TXZ F,F%UPDA		; SEND not INSTALL
	MOVE KF,KF.SND		; Set up keep flags
	NOISE <local file>
	SETZM CMDGTJ
	MOVE A,[CMDGTJ,,CMDGTJ+1]
	BLT A,CMDGTJ+20-1	; Clear out previous cruft from GTJFN block
	MVI. <GJ%OLD!GJ%IFG!GJ%FLG>,CMDGTJ+.GJGEN ; Old, wild
	MOVEI B,[FLDDB. .CMFIL,CM%SDH,,<local filename>]
	CALL .COMND
	 ERMSG <Invalid local filename>
	MOVEM B,TMPJFN		; Save for later or reparse
	NOISE <to remote file>	; Always give noise word.

	;; Local file parsed, now parse name of file to send it to
	SETZM SFPREF		; No prefix yet
	SETOM SFECHR		; Flag nothing seen yet
	SETZM SFVCHR		; Don't even think about a version
	SETZM SFSUFF		; No suffix yet either
	MOVE A,TMPJFN		; Set up default for remote file parse.
	HRROI B,FILPRP+P.SFIL	; Into this place
	CALL NFRNFL		; Parse remote filespec for wildcarding
	 JRST SDFPAR		; None, finish parse
; Here with remote filespec in one piece, break up for later reassembly
; Syntax is firstpart ["*" [delim "*" [ delim "*"]] secondpart].
; So DEV:*.*;*[PRJ,PGR] for FOO.BAR.3 would expand to DEV:FOO.BAR;3[PRJ,PGR].
; Firstpart and secondpart are (possibly null) strings of non-wildcards.
; Delim can be any character except "%" or "*" (even space is ok).

	MOVE A,[POINT 7,FILPRP+P.SFIL] ; Point to server filename
	MOVE B,[POINT 7,SFPREF]	; and to remote file prefix string
	DO.
	  ILDB C,A		; Get next char
	  IDPB C,B		; Drop it in
	  JUMPE C,SDFPAR	; If null, got everything
	  CAIN C,"%"		; Percent?
	   ERMSGX <Invalid use of wildcards>
	  CAIE C,"*"		; Star for first part of filename?
	   LOOP.		; No, normal character, go back for the next
	ENDDO.

	;; Prefix found, now see what we have for the wildcard part
	SETZB C,SFECHR		; Get null, assume no extension
	DPB C,B			; Drop null in over star
	ILDB D,A		; Get delimiter
	JUMPE D,SDFPAR		; If null, done
	CAIE D,"*"		; Star
	 CAIN D,"%"		; and percent are illegal here
	  ERMSGX <Invalid use of wildcards>
	ILDB C,A		; Get char after delimiter
	CAIE C,"*"		; If asterisk
	IFSKP.
	  MOVEM D,SFECHR	; Save delimiter
	  ILDB D,A		; Get next delimiter
	  JUMPE D,SDFPAR	; If null, done
	  CAIE D,"*"		; Star
	   CAIN D,"%"		; and percent are illegal here
	    ERMSGX <Invalid use of wildcards>
	  ILDB C,A		; Get char after delimiter
	  CAIE C,"*"		; If asterisk
	ANSKP.
	  MOVEM D,SFVCHR	; Save version delimiter
	  MOVE B,[POINT 7,SFSUFF] ; Point to suffix
	  ILDB C,A		; Get next char after that
	ELSE.
	  MOVE B,[POINT 7,SFSUFF] ; No extension or version, get pointer
	  IDPB D,B		; Drop in delimiter
	ENDIF.

	;; Now we have everything except suffix, next char in C
	DO.
	  CAIE C,"*"		; If asterisk
	   CAIN C,"%"		; or percent
	    ERMSGX <Invalid use of wildcards> ; then complain
	  IDPB C,B		; Drop it in
	  JUMPE C,SDFPAR	; If null, done
	  ILDB C,A		; Else get next char
	  LOOP.			; and go back with it
	ENDDO.
; Finish SEND parse and set up one-time info for multiple file send

; Here to add *.* to end of filename (jumped to from within SDFPAR)
SNDWLD:	MOVEI A,"."		; No filename, get a dot
	MOVEM A,SFECHR		; Then treat as if it were "*.*"
	JRST STONXT		; Go finish file handling

; Here when remote file parsed, finish parse and start sending files
SDFPAR:	SETOM SFEPRT		; Assume starting at filename part
	SKIPGE SFECHR		; Doing any wildcarding?
	IFSKP.
	  MOVE A,TMPJFN		; Yes, get the file we are wildcarding from
	  SKIPE SFECHR		; Do we have two stars?
	   SKIPN SFVCHR		; Three?
	    TXNE A,GJ%NAM	; Less than three, is it a wildcard fn1?
	ANSKP.			; Full wildcard or wildcard fn1, leave alone
	  HRROI B,SFPREF	; No, get what went before it
	  CALL SKPDIR		; Skip over directory part
	  DO.
	    ILDB A,B		; Get next byte
	    JUMPE A,ENDLP.	; Null, * must really be filename part
	    CAIE A,"."		; Is it a period?
	     LOOP.		; Nothing we're interested in, go get more
	    AOS SFEPRT		; Was a period, skip over fn1
	  ENDDO.
	ENDIF.

	;; Finished munging wildcards, now finish parse
	CALL CONFRM		; Finish the parse
	CALL SETPRP		; Init property list for first time through
	CALL CHKCON		; Make sure we have a connection.
	 RET			; Didn't want one, done with SEND command
	MOVE A,TMPJFN		; Now that we're all parsed, get local file
	SETZM TMPJFN		; Don't do a random RLJFN sometime later
	MOVEM A,FILJFN		; Save for subsequent iterations
	HRRZS A			; Clear flag bits
	CALL CHKDSK		; Set up F%DSKF flag for later
	ABTSET			; Be able to check later if abort was given

	;; Check existence and wildcarding of remote filenames we set up
	SKIPL SFECHR		; Do we include local filename anywhere?
	 JRST STONXT		; Yes, don't need any of this checking
	SKIPE SFPREF		; No, do we have a filename at all?
	IFSKP.			
	  TXNN F,F%SENC		; No, if user doesn't want to be asked
	   JRST SNDWLD		; Go treat as *.*
	  JRST STONXT		; Else go ask individually for each file
	ENDIF.

	;; Here when the filename the user gave has no wildcards in it.
	;; For hosts other than SAIL, make sure it has a filename part
	;; at all, and if not act as if there was a *.* after the dir.
	MOVE A,OPSYS		; Get operating system
	CAIN A,OS.DEC		; Normal file conventions?
	IFSKP.
	  HRROI B,SFPREF	; Yes, point to filename
	  CALL SKPDIR		; Skip past directory part
	  ILDB A,B		; Get first char of filename part
	  JUMPE A,SNDWLD	; If no filename, add *.* to end
	ENDIF.

	;; Has a non-wild filename or is SAIL, confirm if local file is wild
	MOVE A,FILJFN		; Get JFN of local file
	TXNN A,GJ%NAM!GJ%EXT	; Is it wildcard?
	 JRST STONXT		; No, let non-wild storage spec slide by
	HRROI B,SFPREF		; Else point to filename user gave
	TYPE <%1F is wildcard but "%2S" is not.
Multiple local files may be sent to the same remote file.%/>
	PROMPT [ASCIZ/Using specification as is [Confirm] /]
	CALL DOCONF		; Save user from possible lossage
	 RET			; Not confirmed, give up command
;	JRST STONXT		; One-time info all set up, go do first file
; Loop here for each local file designated by JFN
; (also falls in from previous page)

STONXT:	ABTSKIP			; See if an abort was hit
	IFSKP.
	  TYPE <[Aborting multiple-file store]%/>
	  HRRZS FILJFN		; Disable indexing
	  JRST STONFL		; Go finish up
	ENDIF.

; Here to parse remote file on separate line
; if we find that autofill has been set up, go do that instead

	SKIPN SFPREF		; Do we have a prefix
	 SKIPL SFECHR		; Or a wildcard filename?
	  JRST SFAUTO		; Yes, autofill it
	PROMPT TYPBUF,A		; Start making prompt, avoid GTJDFT confusion
	HRRZ B,FILJFN		; Point to our local file
	WRITE <%2F (to remote file) > ; Make prompt
	SETABORT (SNDABT)	; Set parse abort
	CALL SETCMD		; Start parse
	MOVE P,SAVPDL		; Fix stack if necessary
	CALL FLTFIL		; Flush any JFNs FRNFIL may have made
	HRRZ A,FILJFN		; Default to local file
	SETZ B,			; Into FILPRP
	CALL NFRNFL		; Parse file
	 NOP			; Use default we gave on return
	CALL CONFRM		; Finish parse
	CLRABORT		; Disable our abort
	JRST STOCHK		; Go send it off

SNDABT:	TYPE <Ok, ignoring that file.%/>
	ABTSET			; Don't abort whole multiple send
	JRST STONFL		; Go on to the next file
; Here to fill filenames from broken up wildcard info parsed earlier

LS SFPREF,20			; File name prefix
LS SFECHR			; Char to separate extension
				; 0 for no extension, -1 for no filename
LS SFVCHR			; Char to separate vers (0 for no extension)
LS SFSUFF,20			; File name suffix
LS SFEPRT			; Whether to start with fn1 or fn2

; Table of printer routines for different parts of a wildcard filename
SFEPTT:	SFEPT1			; FN1 printer
	SFEPT2			; FN2 printer
	SFEPTV			; Version printer
	[FATAL <More than 3 wildcard parts to fill out>]

; FN1 printer
SFEPT1:	HRRZ B,FILJFN		; Point to file JFN
	MOVX C,FLD(.JSAOF,JS%NAM) ; Just outputting FN1
	CALLRET .JFNS		; Send name off

; FN2 printer
SFEPT2:	HRRZ B,FILJFN		; Point to file JFN
	MOVX C,FLD(.JSAOF,JS%TYP) ; Just outputting FN2
	CALLRET .JFNS		; Send name off

; Version printer
SFEPTV:	PUSH P,A		; Save string pointer
	HRRZ A,FILJFN		; Get JFN again
	CALL GTVERS		; Get generation of JFN
	 SETO B,		; Couldn't, send to new version
	POP P,A			; Restore pointer
	WRITE <%2D>		; Write version
	RET

SFAUTO:	HRROI A,FILPRP+P.SFIL	; Now point to where to put filename
	HRROI B,SFPREF		; Point to prefix
	SKIPE (B)		; If we have anything there
	 WRITE <%2S>		; Copy it in
	SKIPGE D,SFECHR		; If we also want wildcarding
	 JRST STOCHK		; Then go on else done with file name
		
	;; Have wildcards, fill them out
	PUSH P,SFEPRT		; Save for next run
	AOS B,SFEPRT		; Get which part to use
	CALL @SFEPTT(B)		; Print that part
	SKIPG B,SFECHR		; Get extension delim again.  If we want it
	IFSKP.
	  IDPB B,A		; Drop delimiter in
	  AOS B,SFEPRT		; Get which part to use
	  CALL @SFEPTT(B)	; Print that part
	  SKIPG B,SFVCHR	; If we want version as well
	ANSKP.
	  IDPB B,A		; Drop delimiter in
	  AOS B,SFEPRT		; Get which part to use (always version here)
	  CALL @SFEPTT(B)	; Print it
	ENDIF.			; End if have extension
	POP P,SFEPRT		; Now restore saved value
	HRROI B,SFSUFF		; Point to suffix
	SKIPE (B)		; If we have anything there
	 WRITE <%2S>		; Copy it in
;	JRST STOCHK		; Now that we have filled filename, go send
; Here to send file off after remote filename has been agreed upon
; (also falls in from previous page)

STOCHK:	HRRZ A,FILJFN		; Get local JFN
	TXNE KF,K%VERS		; Keeping version?
	 CALL GTVERS		; Yes, get file version
	  SETO B,		; Not keeping or not disk, use version -1
	   MOVEM B,FILPRP+P.VERS ; Save version number to send off
	CALL OPNSTO		; Open local file
	 JRST STONOP		; Failed, bypass file
	CALL @.SEND(V)		; Send the file
	 HRRZS FILJFN		; Permanent failure, disable indexing

; Here when done with a file, go on to transfer the next
STONFL:	CALL NXTFIL		; Step filespec
	 RET			; All done, return
	JRST STONXT		; More, go back and do it again

; Here when OPNSTO fails
STONOP:	TYPE <%_%% %1S%/>	; Print error message as a warning
	JRST STONFL		; On to the next file
; Here to check on whether install is Ok
; call with D/property list pointer
; returns +1/local copy more recent or not confirmed, +2/ok to store
; need only be called for INSTALL

SNDCNF::TXNN F,F%UPDA		; Updating?
	 RETSKP			; No, always copy file across
	SAVEAC <A,B,C>		; Don't mung registers
	HRRZ A,FILJFN		; With source JFN
	MOVX B,<1,,.FBWRT>	; Want write date
	MOVEI C,C		; Into C
	GTFDB%			; Get FDB word
	IFNJE.
	  SUBI C,3		; Fudge thirds of seconds in date number
	  CAMG C,P.PWDT(D)	; Is local version more recent than remote?
	   RET			; Yes, don't send it
	ELSE.
	  HRRZ A,FILJFN		; GTFDB failed, get JFN again
	  TYPE <%% Couldn't get FDB for %1F, assuming it is more recent%/>
	ENDIF.
	TXNN F,F%UPDC		; Want to install - need confirmation?
	 RETSKP			; No, just go send it
	PROMPT TEMP,A		; Start making a prompt
	HRROI B,P.SFIL(D)	; Point to destination filename
	WRITE <Installing %2S [Confirm] > ; Make prompt
	CALLRET DOCONF		; Go confirm it
SUBTTL DELETE command

H.DELE:	ASCIZ/The DELETE command is used to delete files on the other system.
For each file that matches the given specification, you will be
asked whether to delete it.  If you still want to delete it, just
type return; otherwise type NO.  Example:

FTP>DELETE FOO.*				; File name to delete
Deleting PS:<CSD.KRONJ>FOO.FAH.3 [Confirm] NO	; Don't want to delete this
Not confirmed, delete aborted.
Deleting PS:<CSD.KRONJ>FOO.FOO.2 [Confirm]	; Type return to delete it

There is (currently) no way to expunge a directory with FTP.
/

C.DELE:	NOISE <remote file>
	SETZB A,B		; No default, normal destination
	CALL FRNFIL		; Parse remote filename.
	 ERMSGX <Null remote file specified>
	MVI. .GJALL,FILPRP+P.VERS ; Set wildcard default version
	CALL CONFRM		; Confirm the parse.
	CALL CHKCON		; Make sure we have a connection.
	 RET
	CALLRET @.DELFL(V)	; Call protocol handler to delete file

; Here with property list in FILPRP, confirm file deletion
DELCNF::TXNN F,F%DELC		; Delete confirmation required?
	 RETSKP			; No, return now
	PROMPT TEMP,A		; Build prompt
	HRROI B,FILPRP+P.SFIL	; Get string for filename
	WRITE <Deleting %2S [Confirm] >
	CALLRET DOCONF		; Confirm action
SUBTTL RENAME command.

H.RENA:	ASCIZ/The RENAME command gives a file on the other machine a new
name.  The file is not transferred across the network.  Example:

FTP>RENAME FOO.BAR			; Old remote filename
FOO.BAR.3 (to be) FOO.BAZ		; New name for old file
< Rename to <KRONJ>FOO.BAZ successful	; Computer tells you it worked
/

LS RENSTR,20			; Where to put second remote filename

C.RENA:	NOISE <remote file>
	MOVE KF,KF.REN		; Flags for RENAME (not usually useful)
	SETZB A,B		; No default, normal dest.
	CALL FRNFIL		; Parse remote filename.
	 ERMSGX <Null remote file specified>
	NOISE <to be>
	HRROI A,FILPRP+P.SFIL	; Default to other file
	HRROI B,RENSTR		; into string where we want second filename
	CALL NFRNFL		; read destination file.
	IFSKP.
	  CALL CONFRM		; If got a filename, finish parse
	ELSE.
	  CALL CONFRM		; Else finish that line of parse
	  PROMPT TYPBUF,A	; Set prompt, avoid GTJDFT confusion
	  HRROI B,FILPRP+P.SFIL	; Using first filename
	  WRITE <%2S (to be) >	; Make prompt
	  SETABORT (RENABT)	; Let user abort out of parse
	  CALL SETCMD		; Start parsing second line
	  MOVE P,SAVPDL		; Fix stack if necessary
	  HRROI A,FILPRP+P.SFIL	; Default to other file,
	  HRROI B,RENSTR	; sending into RENSTR,
	  CALL NFRNFL		; parse foreign filename.
	   NOP			; If return, keep filled with default
	  CALL CONFRM		; Finish parse
	  CLRABORT
	ENDIF.
	CALL CHKCON		; Make sure we have a connection
	 RET			; Didn't want one, stop now
	HRROI A,RENSTR		; Point to file to rename to
	CALLRET @.RENAM(V)	; Go rename it

; Here when user types ^G to rename
RENABT:	TYPE <Ok, rename aborted.%/>
	RET
SUBTTL DIRECTORY and VDIRECTORY commands.

H.VDIR:	ASCIZ/The VDIRECTORY command is equivalent to the DIRECTORY command
with the VERBOSE option.
/

H.DIRE:	ASCIZ/The DIRECTORY command lists information about files on the other
system matching the given specification.

Type a comma at the end of the filename to give subcommands to the
DIRECTORY command.   If no comma is given, only the file names will
be listed.  When you are done typing subcommands, leave a blank line
to tell FTP to do the directory.  To list available options type
HELP to the subcommand prompt ("FTP>>").

Example:

FTP>DIRECTORY FOO.*,		; File spec, comma to enter subcommands
FTP>>VERBOSE			; Include author, read, write, type
FTP>>				; Blank line to finish subcommands

                         Type    Prot  Write     Read     Author
   <CSD.KRONJ>
FOO.FAH.1                B(36) 775202 25-Aug-82    ---    CSD.KRONJ
FOO.FOO.1                Text  775202 12-Jun-82    ---    CSD.KRONJ
/

HH.DIR:	ASCIZ/Directory options are:

AUTHOR      - type the username of each file's creator
CREATION    - type the creation date of each file
EVERYTHING  - include all optional fields
HELP        - output this text
NO          - negate any option (except QUIT or HELP)
ORIG-AUTHOR - the name of the user who created the file
OUTPUT      - send the information to a file rather than the terminal
PROTECTION  - the file's protection
QUIT        - abort the DIRECTORY command
READ        - type the date each file was last examined
SIZE        - type the number of bytes in each file
TIMES       - include times along with read, write, or creation dates
TYPE        - type the type (binary or text) and bytesize of each file
VERBOSE     - type the author, read and write dates, protection, and type
WRITE       - type the date each file was last changed
/
; Parse DIRECTORY and VDIRECTORY commands

LS DIRJFN			; JFN for output

C.DIRE:	TDZA FX,FX		; Clear list flags
C.VDIR:	 MOVX FX,L%VERB		; Or initialize flags to "Verbose"
	MVI. .PRIOU,DIRJFN	; Assume output to TTY
	NOISE <of remote files>
	SETZB A,B		; No default, into FILPRP
	CALL FRNFIL		; parse a remote filename.
	 SETZM FILPRP+P.SFIL	; If user typed bare return let him have it.
	MVI. .GJALL,FILPRP+P.VERS ; Set wildcard default version

	;; Remote file parsed, see if it ended with a comma
	MOVE A,[POINT 7,FILPRP+P.SFIL]
	DO.
	  ILDB B,A		; Get the next character
	  JUMPE B,ENDLP.	; No comma, go on with parse
	  CAIE B,","		; Is it a comma?
	   LOOP.		; No, go back for more.
	  MOVE B,A		; Yes, get copy of byte pointer
	  ILDB B,B		; and read char from it.
	  JUMPN B,TOP.		; If non-null, false alarm - go back for more.
	  DPB B,A		; Else drop the null where the comma was
	  JRST DIRSUB		; and go start parsing subcommands
	ENDDO.

	;; No comma in filespec already, try parsing it separately
	MOVEI B,[FLDDB. .CMCMA,CM%SDH,,<comma to enter subcommands>,,[
		 FLDDB. .CMCFM]]
	CALL .COMND		; Parse comma or carriage return
	 JRST CNFERR
	LOAD C,CM%FNC,(C)	; Get FDB parsed.
	CAIE C,.CMCFM		; Was it confirmation?
	 JRST DIRSUB		; No, go read subcommands

; Here when command and subcommand parsing complete, run directory command
DIRCHK:	CALL CHKCON		; Make sure there is a connection.
	 RET			; Didn't want one, done now
	ABTSET			; From now on we handle aborts more carefully
	CALL @.DIREC(V)		; Go call handler routine
	MOVE A,DIRJFN		; Get source JFN
	CAIN A,.PRIOU		; If was to a file
	IFSKP.
	  ABTSKIP		; If aborted
	   IFSKP. <TXO A,CZ%ABT> ; then abort the file
	  CLOSF%		; Else close it normally
	   TYPE <%% Couldn't close directory output file - %J%/>
	  ENDIF.
	ABTSKIP			; If aborted
	 TXNE FX,L%HDR		; or something was typed
	  RET			; then all done
	TYPE <%% No files match that specification%/>
	RET
; Loop to gather subcommands.

DIRSUB:	CALL CONFRM		; Finish command line parse
	PROMPT [ASCIZ/FTP>>/]	; Set subcommand prompt
	SETABORT (R)		; On abort, return from directory command
DIRSCM:	CALL SETCMD		; Start parse for subcommands
	MOVE P,SAVPDL		; Fix stack if necessary
	CALL FLTFIL		; Flush temporary file if any
	TXZ F,F%NPRF		; No NO prefix typed yet
DIRSNO:	MOVEI B,[FLDDB. .CMCFM,,,,,[
		 FLDDB. .CMKEY,,LSUBTB]]
	TXNE F,F%NPRF		; NO typed?
	 HRRZ B,(B)		; Yes, don't allow confirmation
	CALL .COMND		; Parse subcommand
	 ERMSG <Invalid subcommand>
	LOAD C,CM%FNC,(C)	; Get function code that won.
	CAIN C,.CMCFM		; Done here with subcommands?
	 JRST DIRSDN		; Yes, finish up
	HRRZ D,(B)		; Get format flags for this keyword.
	TXNE D,L%NO		; NO command?
	 JRST DIRNO		; Yes, go handle
	TXNE D,L%HELP		; HELP command?
	 JRST DIRHLP		; Yes, go handle
	TXNE D,L%QUIT		; QUIT command?
	 JRST DIRQIT		; Yes, go handle
	TXNE D,L%OUTF		; Output to file?
	 JRST DIROUT		; Yes, go handle
	CALL CONFRM		; Otherwise must be a flag, finish parse
	TXNN F,F%NPRF		; NO given?
	 TROA FX,(D)		; No, set flags
	  TRZ FX,(D)		; Else clear them
	JRST DIRSCM		; Back for another subcommand

; Here for QUIT
DIRQIT:	TXNE F,F%NPRF		; NO given?
	 ERMSGB <Illegal keyword with NO prefix>
	NOISE (from DIRECTORY command)
	CALL CONFRM
	CLRABORT		; Flush abort we may have had
	RET

; Here for NO
DIRNO:	TXOE F,F%NPRF		; Yes, set flag for later
	 ERMSGX <Illegal to type NO twice in same line>
	JRST DIRSNO		; Go parse a second command

; Here for return to FTP>> parse
DIRSDN:	CLRABORT		; Clear our abort
	MOVE A,DIRJFN		; Get destination JFN
	CAIN A,.PRIOU		; If the terminal
	 JRST DIRCHK		; Then go do the directory now
	MOVX B,FLD(7,OF%BSZ)!OF%WR
	OPENF%			; Else open it for write
	 ERMSG <Couldn't open output file>
	HRROI B,FILPRP+P.SFIL	; Point to file we parsed
	HRROI C,HSTSTR		; and host we're talking to
	WRITE <Files matching %2S from %3S at %T%/%/> ; Start header
	JRST DIRCHK		; Succeeded, go do the directory
; DIRECTORY subcommand handlers and command table

DEFINE T (NAME,VAL,FLG) <KEY NAME,VAL,FLG>

LSUBTB:	TABLE
	T AUTHOR,L%AUTH
	T CREATION,L%CDAT
	T EVERYTHING,777777-<L%OUTF!L%QUIT!L%HELP!L%NO!L%HDR>
	T HELP,L%HELP
	T NO,L%NO
	T ORIG-AUTHOR,L%OAUT
	T OUTPUT,L%OUTF
	T PROTECTION,L%PROT
	T QUIT,L%QUIT
	T READ,L%RDAT
	T SIZE,L%SIZE
	T TIMES,L%TIME
	T TYPE,L%TYPE
	T VERBOSE,L%VERB
	T WRITE,L%WDAT
	TEND

; Here for HELP (with the DIRECTORY command)
DIRHLP:	TXNE F,F%NPRF		; NO given?
	 ERMSGB <Illegal keyword with NO prefix>
	NOISE (with the DIRECTORY command)
	CALL CONFRM		; Finish parse
	HRROI A,HH.DIR		; Get list of subcommands
	PSOUT%			; Send it off to the terminal
	JRST DIRSCM		; Done with that keyword, on to next

; Here for OUTPUT (to file)
DIROUT:	NOISE (to file)
	SETZM CMDGTJ
	MOVE B,[CMDGTJ,,CMDGTJ+1]
	BLT B,CMDGTJ+20-1	; Zero GTJFN command block.
	HRROI B,[ASCIZ/LST/]	; Make an extension
	MOVEM B,CMDGTJ+.GJEXT	; Set it as the default
	MOVEI B,[FLDDB. .CMOFI,CM%SDH,,<file to send listing to>,,[
		 FLDDB. .CMCFM,CM%SDH,,<carriage return to send to terminal>]]
	TXNE F,F%NPRF		; No given?
	 HRRZ B,(B)		; Yes, only allow carriage return
	CALL .COMND		; Parse CR or filename
	 ERMSG <Invalid filename>
	LOAD C,CM%FNC,(C)	; Get FDB parsed
	CAIE C,.CMOFI		; File?
	IFSKP.
	  MOVEM B,TMPJFN	; Yes, save for reparse flush
	  CALL CONFRM		; Finish the parse
	  MOVE A,TMPJFN		; Get JFN back
	  SETZM TMPJFN		; Parse is over, file is safe
	ELSE.
	  MOVEI A,.PRIOU	; Carriage return, use terminal
	ENDIF.
	EXCH A,DIRJFN		; Get old dir JFN, save new
	CAIE A,.PRIOU		; If old was not the terminal
	 RLJFN%			; Then release the JFN
	  ERNOP			; But don't care if it is lost
	JRST DIRSCM		; Go back for next subcommand
SUBTTL QUOTE command.

H.QUOT:	ASCIZ/The QUOTE command passes an arbitrary command string to the
remote FTP server.  The command string must be enclosed in double quotes,
for example:
			FTP>quote "STAT"
/

LS QUOTES,ATMBFW		;Storage for QUOTE command argument

C.QUOT:	NOISE <command to server>	;Some noise
	MOVEI B,[FLDDB. .CMQST,CM%SDH,,<server command, in double quotes>]
	CALL .COMND		;Parse an unquoted string
	 ERMSG <Unable to parse command for remote server>
	SETZM QUOTES		;Clear first word in case of null string
	MOVE A,[POINT 7,QUOTES]	;Set up pointer to argument buffer
	CALL CPYATM		;Copy from atom buffer into QUOTES
	CALL CONFRM		;Wait for confirmation
	CALL CHKCON		;Check for a connection
	 RET			;Didn't want one, done now
	MOVE A,[POINT 7,QUOTES]	;Set up pointer to quote string
	CALL @.QUOTE(V)		;Call protocol dependent quote routine
	RET			;Return to caller
SUBTTL Remote filespec parsing

; Parse a foreign filename.
;
; Enter at FRNFIL for existing file name, NFRNFL for new file.
; Call with A/JFN or -1,,Text pointer for defaults (if 0 then uses *.*)
;	    B/String to store filename, or zero for standard file props.
;	    KF/K%VERS set or cleared (only checked for NFRNFL)
; Returns +1/Null string parse, +2/User typed something
;
; Preserves case of command buffer even if a TOPS-20 filespec is parsed

LS FFLDEF			; Space to store foreign file default string
LS FFLJFN			; Place for default JFN
LS FFLSTR			; Where to save the completed string
LS FFLPTR			; Place to save old command pointer
LS FFLPRS			; Place to store ptr to COMND% function blocks

NFRNFL:	TXOA F,F%TEMP		; New foreign file, set flag
FRNFIL:	 TXZ F,F%TEMP		; else clear it.
	MOVEM B,FFLSTR		; Save place to save completed string.
	SETZM FFLDEF		; No default string yet.
	SETZM FFLJFN		; No JFN yet.
	SETZM CMDGTJ
	MOVE B,[CMDGTJ,,CMDGTJ+1]
	BLT B,CMDGTJ+20-1	; Zero GTJFN command block.
	IFXN. F,F%TEMP		; New file?
	  HLLOS CMDGTJ+.CMFLG	; Yes, set version -1 instead of 0
	  MOVX B,.GJALL		; Get wildcard version
	  TXNE KF,K%VERS	; Keeping versions?
	   HRRM B,CMDGTJ+.CMFLG	; Yes, set in default version
	ENDIF.

	SKIPE A			; If no default
	 IFSKP.
	   MOVE A,OPSYS		; Use default, get operating system type
	   CAIE A,OS.UNX	; Is it Unix?
	    IFSKP.
	      HRROI A,[ASCIZ/*/] ; Use this default
	    ELSE.
	      HRROI A,[ASCIZ/*.*/] ; Then use this default (wildcard or -1 gen)
	    ENDIF.
	 ENDIF.
	TLC A,-1		; Flip some bits
	TLCN A,-1		; Test default - can it be a JFN?
	IFSKP.
	  TXNE F,F%TEMP		; New file?
	   TXNN KF,K%VERS	; And keeping versions?
	    IFSKP.
	      IFXE. A,GJ%DEV!GJ%UNT!GJ%DIR!GJ%NAM!GJ%EXT!GJ%VER
		PUSH P,A	; If not wildcard, save JFN
		HRRZS A		; and clear left half bits. 
		CALL GTVERS	; Find file version.
		 CAIA		; Not disk file.
		  HRRM B,CMDGTJ+.CMFLG ; Else set version same as file.
		POP P,A		; Get unmunged JFN back
	      ENDIF.
	    ENDIF.
	  STKVAR <<FFLBUF,NAMSTL>>	; Declare some local storage
	  MOVE B,A		; Copy JFN
	  HRROI A,FFLBUF	; Point to our new buffer
	  MOVEM A,FFLDEF	; Save string as default
	  MOVX C,FLD(.JSAOF,JS%NAM)!FLD(.JSAOF,JS%TYP)!JS%PAF
	  CALL .JFNS		; Make string default from file name
	ELSE.
	  MOVEM A,FFLDEF	; Save string as default
	  MOVE B,A		; copy into B
	  CALL SKPDIR		; and skip over directory terminators.
	  MOVX A,GJ%SHT!GJ%OFG!GJ%FLG
	  GTJFN%		; Make a JFN for the file (released on reparse)
	   IFJER.
	     MOVEI B,[FLDBK. .CMQST,CM%SDH,,<remote file, optionally quoted>,0,REMBRK,[
		       FLDBK. .CMFLD,CM%SDH]]
	     MOVEM B,FFLPRS	; Save it back, remove file parse
	     HRR B,B		; Point to second item
	     MOVE A,FFLDEF	; Get default file ptr
	     MOVEM A,.CMDEF(B)	; Save it in function block
	     JRST FFLPAR	; Go on with parse.
	   ENDIF.
	  MOVE B,A
	  MOVEM B,FRNJFN	; Save for reparse etc
	ENDIF.
	MOVEM B,FFLJFN		; Save JFN in a different cell.
	CALL GTJDFT		; Set up defaults from filename.
	MOVEI A,[FLDDB. .CMCFM,CM%SDH,,,,[
		  FLDDB. .CMQST,CM%SDH,,<remote file, optionally quoted>,,[
		   FLDDB. .CMFIL,CM%SDH]]]
	MOVEM A,FFLPRS		; Save it for COMND%
; Now that we've set up our defaults and arguments, do the actual parse.
; We only parse a file and quoted string for now, but if we get a filename
; that ends on some break char other than space we will try the parse
; again as an unquoted string (falls in from previous page).

FFLPAR:	CALL SKPSPC		; Skip over spaces in parse
	MOVX A,GJ%OFG!GJ%XTN	; Parse-only, using extended flag word
	HLLM A,CMDGTJ+.GJGEN	; Set parse-only flag.
	MOVX A,G1%NLN		; Trying to avoid the generation
	MOVEM A,CMDGTJ+.GJF2	; Set in secondary flag word
	MV. CMDBLK+.CMPTR,FFLPTR ; Save command pointer for later
	MOVE B,FFLPRS
	CALL .COMND		; Try this parse first
	 TXOA F,F%TEMP		; Failed, remember that we have to mung vers
	  JRST FFLFLP		; Succeeded, go on
	SETZM CMDGTJ+.GJF2	; Didn't, try a normal file
	MOVEI B,[FLDDB. .CMFIL,CM%SDH,,<remote file, optionally quoted>]
	CALL .COMND		; Try quoted string or normal file
	 JRST FFLUQS		; Failed, do unquoted string instead
	TXNE A,CM%ESC		; Unless user typed an escape
	 TXNE F,F%MUNG		; Or if user doesn't want versions munged
FFLFLP:	  TXZ F,F%TEMP		; Don't mung versions
	CALL FFLFFJ		; Flush temporary file
	LOAD C,CM%FNC,(C)	; Succeeded, find out what we got

	;; We might have parsed a carriage return to cause the default
	;; to happen if user typed a bare CR to the start of the line.
	CAIE C,.CMCFM		; Carriage return?
	IFSKP.
	  CALL FFLBKP		; Yes, back up pointer
	  JRST FFILZ		; And go use default
	ENDIF.

	;; Check other possibilities for parse
	CAIE C,.CMQST		; Was it a file?
	 JRST FFLFIL		; Yes, handle it specially
	AOS (P)			; No, quoted string.  Set up for success return
	CALL FFLDES		; Point to where to put the string
	CALLRET CPYATM		; Copy atom buffer there and return


; Here on successful .CMFIL parse of remote filespec
; check it to make sure there's nothing after the file name
; that COMND% mistakenly broke on.

FFLFIL:	SKIPE A,B		; Parsed file, copy into appropriate register
	 RLJFN%			; to flush it
	  ERNOP
	MOVE A,CMDBLK+.CMPTR	; Get pointer to next input
	ILDB A,A		; and look at the char there.
	CAILE A," "		; Space or control character?
	 JRST FFLNBL		; No, need to redo parse as unquoted string
	MOVE B,FFLPTR		; Yes, file parse Ok, so get old command ptr
	CAMN B,CMDBLK+.CMPTR	; Got any text?
	 JRST FFLUQS		; No, need to parse something more
FFIL3:	AOS (P)			; Yes, set up for success return
	JRST FFIL4		; Go copy parsed text and return

; File parse succeeded but then we see these non-blank characters
; immediately following the file name.  Fudge the CSB to look like
; we never did the file parse and then do it as an unquoted string.

FFLNBL:	CALL FFLBKP		; Back up pointer
;	JRST FFLUQS		; Go parse as unquoted string
; Here to attempt to parse an unquoted string.  Either the file parse
; failed, or it succeeded but terminated on a non-space char.
; (falls through from previous page)

FFLUQS:	CALL SKPSPC		; Skip over spaces at start of buffer
	MV. CMDBLK+.CMPTR,FFLPTR ; Save pointer again
	TXZ F,F%TEMP		; Not munging versions
	CALL FFLFFJ		; Flush file default JFN we made.
	MOVEI B,[FLDDB. .CMUQS,CM%SDH,REMBRK,<remote file, optionally quoted>]
	CALL .COMND		; TOPS-20 filespec failed, try unquoted string
	 ERMSG <Unable to parse remote filename>
	MOVE B,FFLPTR		; Get old COMND pointer
	CAME B,CMDBLK+.CMPTR	; Any text parsed?
	 JRST FFIL3		; Yes, go win with it
FFILZ:	SKIPN B,FFLDEF		; End of buf, if there's no default
	 RET			; Then give up in disgust
	MKPTR (B)		; Make sure it's a byte pointer.
FFIL4:	CALL FFLDES		; Get destination string into A.
	DO.
	  CAMN B,CMDBLK+.CMPTR	; Was that the end of the command text?
	   TDZA C,C		; Yes, get a zero
	    ILDB C,B		; Else get the next character.
	  IDPB C,A		; Drop the char into the destination string.
	  CAIN C,"."		; If possibly the start of a version
	   MOVEM A,FFLSTR	; Save pointer for version munging
	  JUMPN C,TOP.		; Maybe go back for more
	ENDDO.

; Brian K Reid memorial crock.  This implements SET NO INCLUDE-VERSIONS...
	TXNE F,F%TEMP		; If munging versions
	 DPB C,FFLSTR		; Drop a null in our saved pointer
	RET
; Various subroutines for FRNFIL

; Skip over spaces in the COMND% buffer.  This is done to make
; comparison against the command block pointer work better.

SKPSP0:	SOS CMDBLK+.CMINC	; One less char unparsed
	SOSA CMDBLK+.CMCNT	; One less char left in command buffer
SKPSPC:	 MOVE A,CMDBLK+.CMPTR	; Get command block pointer
	MOVEM A,CMDBLK+.CMPTR	; Put it back
	SKIPN CMDBLK+.CMINC	; Any unparsed chars?
	 RET			; No, done
	ILDB B,A		; Get next char
	INSET B,<.CHSPC,.CHTAB>,JRST SKPSP0
	RET			; If non-blank just return


; Here to get string pointer to store remote file name we just parsed into.
; Returns +1/always with A: pointer to string buffer

FFLDES:	SKIPN A,FFLSTR		; Is there a string to save into?
	IFSKP.
	  MKPTR (A)		; Yes, make sure it's a real byte pointer
	  RET			; and return with it.
	ENDIF.
	CALL SETPRP		; Else init file property list
	MOVE A,[POINT 7,FILPRP+P.SFIL]	; and set dest to server-filename.
	RET

; Here from FRNFIL to clear out default
FFLFFJ:	SKIPN A,FRNJFN		; Did we have a file JFN to clear out?
	 RET			; No, just return
	RLJFN%			; Else clear it
	 ERNOP
	SETZM FRNJFN		; Clear out so we don't do it again
	RET

; Here to back up pointer to previous mark
FFLBKP:	MOVE B,FFLPTR		; Get old pointer
	SETZ A,			; and clear a counter.
	DO.
	  CAMN B,CMDBLK+.CMPTR	; Is it up to new pointer?
	   EXIT.		; Yes, done with loop
	  IBP B			; No, increment it
	  AOJA A,TOP.		; and A, and go back for more.
	ENDDO.
	ADDM A,CMDBLK+.CMINC	; Use count to set num unparsed chars
	ADDM A,CMDBLK+.CMCNT	; and space left in command buffer.
	MV. FFLPTR,CMDBLK+.CMPTR ; Put command pointer back
	RET			; All done
SUBTTL Subroutines

; Do JFNS call, perhaps lowercasing.
; Call with A,B,C/set up for JFNS%, returns +1 always
.JFNS:	STKVAR <JFSPTR>		; Need an extra variable
	MKPTR(A)		; Make sure we have a real byte pointer
	MOVEM A,JFSPTR		; Save start of destination buffer
	JFNS%			; Translate the JFN into a string
	 ERJMP R		; On error, don't try lowercasing nothing
	PUSH P,A		; Save returned pointer
	SETZ C,			; Get a null
	IDPB C,A		; To make sure string is terminated
	POP P,A			; Restore pointer
	TXNN F,F%LOWR		; Lowercasing UNIX files?
	 RET			; No, all done
	IFXN. F,F%COPN		; Connection open?
	  MOVE C,OPSYS		; Yes, get operating system
	  INSET C,<OS.T20,OS.10X,OS.DEC,OS.ITS>,RET ; Don't downcase for these
	ENDIF.
	EXCH A,JFSPTR		; Get start ptr and save end
	DO.
	  CAMN A,JFSPTR		; Up to where JFNS% left off?
	   RET			; Yes, stop
	  ILDB C,A		; Get next character of new string part
	  CAIL C,"A"		; Below
	   CAILE C,"Z"		; or above range?
	    LOOP.		; Yes, pass it by
	  TRO C,40		; It's a letter, lowercase it
	  DPB C,A		; Drop back in
	  LOOP.
	ENDDO.

; Copy B into A, smashing C
CPYSTR:	MKPTR(A)		; Make sure we have real byte pointers
	MKPTR(B)
	DO.
	  ILDB C,B		; Get a byte
	  JUMPE C,R		; If null, done
	  IDPB C,A		; Else drop it in
	  LOOP.
	ENDDO.
; Initialize PSI system
; Returns +1
; Clobbers A, B

INIPSI:	MOVEI A,.FHSLF		; Initialize psi system
	MOVE B,[LEVTAB,,CHNTAB]
	SIR%
	EIR%
	MOVX B,ACTCHN		; Activate channels
	AIC%
	MOVX A,<.TICCG,,ABTCHN>	; ^G interrupt on channel 1
	ATI%
	RET


; PSI channel definitions
DEFINE PSI(CH,LEV,DISP) <
	ACTCHN==ACTCHN!1B<CH>
RELOC CHNTAB+^D<CH>
	LEV ,, DISP
>
	ACTCHN==0		; Bit mask of active channels
	ABTCHN==:1		; ^G abort channel

CHNTAB::PSI(ABTCHN,2,ABTINT)	; Channel 1: ^G abort
RELOC CHNTAB+^D36		; That's all for now

LS CH1PC			; PSI return locations
LS CH2PC
LS CH3PC

LEVTAB::CH1PC			; Level 1 - fatal errors
	CH2PC			; Level 2 - ^G command abort interrupt
	CH3PC			; Level 3 - normal wakeups, eof, etc.
; Fatal errors

; Routine to call if an impossible error occurs
; Does not return

SCREW::	HRRZ A,0(P)		; Get return pc
	SUBI A,1		; Backup to call
	ETYPE <%2S at %1O - %J%/> ; Type message, address, and error
	CALL FLSTAK
	MOVX A,<CZ%ABT!.FHSLF>	; On ourself, aborting so we don't hang
	CLZFF%			; close all our files.
	 ERNOP			; This is in error handler, so ignore errors.
	DO.
	  HALTF%		; Stop the program
	  ETYPE <Can't continue> ; Complain if continued
	  LOOP.			; and go back to stop again
	ENDDO.
; Handler for ^G command abort

LS ABTLOC			; Place to hold abort setting
				; ABTLOC <= 0  -- no action, set to 0
				; ABTLOC > 0  -- clear abort, jump to @ABTLOC

ABTINT:	PUSH P,A		; Don't mung this register
	HLRZ A,CMDBLK+.CMIOJ	; Get command file JFN.
	CAIN A,.PRIIN		; If it's the terminal, don't say anything
	IFSKP.
	  TYPE <%_[Command files aborted]%/> ; Tell user what he just did
	  CALL FLSTAK		; Clear out any command files we were running
	  HRRZ A,CH2PC		; Get return location
	  CAIE A,$COMND+1	; Are we in COMND?
	  IFSKP.
	    MOVEI A,NPARSE	; Yes, get NPARSE instead
	    MOVEM A,CH2PC	; and save as new PC (may be overridden later)
	  ENDIF.
	  TXO F,F%ABTC		; Make sure .COMND knows to abort parse
	ENDIF.
	SKIPLE A,ABTLOC		; Is there an abort set?
	IFSKP.
	  MOVEI A,.CHBEL
	  PBOUT%		; Beep
	  POP P,A		; Restore saved register
	ELSE.
	  MOVEM A,CH2PC		; Return to abort loc from interrupt
	  TYPE <^G%/>		; Uparrow G, beep
	  CLRABORT		; Restore the previous abort and stack
	ENDIF.
	SETZM ABTLOC		; Canonicalize for ABTSKIP
	DEBRK%			; Return from interrupt

; Here to try running the next abort after one has been hit.
; If no more aborts, returns +1
NXTABT:	SKIPG A,ABTLOC		; Get abort if any
	IFSKP.
	  CLRABORT		; Got one, pop a level beyond it
	  JRST (A)		; and go to next handler
	ENDIF.
	SETZM ABTLOC		; None, canonnicalize abort location
	RET			; and return normally

; Here to start setting an abort
; call with JSP CX,.SETAB then set appropriate value in ABTLOC

.SETAB::PUSH P,ABTLOC		; Save old abort if any
	PUSH P,SAVPDL		; And old saved stack pointer
	SETZM ABTLOC		; Avoid timing races
	MOVEM P,SAVPDL		; Save stack
	JRST (CX)		; Return

; Here to clear current abort
; call with JSP CX,.CLRAB, returns +1 always

.CLRAB::MOVE P,SAVPDL		; Restore saved stack
	SETZM ABTLOC		; Avoid timing races
	POP P,SAVPDL		; Restore saved saved stack
	POP P,ABTLOC		; Restore saved abort
	JRST (CX)		; Return
; Parsing support routines.

; .COMND - do a COMND% JSYS.
; Called with B/ Pointer to FLDDB list.
; Returns:  +1, error has occurred (CM%NOP)
;	    +2, successful, ACs are as returned by COMND%.

.COMND::TXZE F,F%ABTC		; Command parse aborted?
	 JRST NPARSE		; Yes, go retry
	MOVEI A,CMDBLK		; With normal command state block
$COMND:	COMND%			; Do the parse
	IFNJE.
	  TXNN A,CM%NOP		; Was it parsed?
	   AOS (P)		; Yes, set up for skip return
	  RET
	ENDIF.
	MOVEI A,.FHSLF		; Error, get pointer to self
	GETER%			; to find out what it was
	HRRZS B			; Don't worry about our fork handle.
	CAIE B,COMNX9		; End of file?
	 CAIN B,IOX4		; or this kind of end of file?
	  IFNSK.
	    CALL ENDTAK		; Yes, try popping a level
	     JRST NPARSE	; And go restart parse if we succeeded
	  ENDIF.
	FATAL <Fatal error in command parse>


; VALFLD - make sure field just parsed is non-null
; returns +1/null, +2/not
VALFLD::SAVEAC <A>		; Don't mung register
	MOVE A,[POINT 7,ATMBUF]	; Point to atom buffer
	ILDB A,A		; Get char there
	JUMPN A,RSKP		; If non-null, then we parsed a real field
	RET			; Else blank, return +1
; .ERMSJ - called for JSYS errors
.ERMSJ::MOVE P,[IOWD STKLEN,STACK]	; Setup stack
	TYPE < - %J%/>		; Type error string
	CALL FLSTAK		; Flush any TAKE files.
	JRST COMLP		; Go get the next command.

; .ERMSG - Routine to do error message stuff for parse failures.
.ERMSG::MOVE B,[POINT 7,ATMBUF]
	ILDB B,B		; Get first char in atom buffer.
	JUMPE B,.ERMSX		; Null?  Then go on.
	HRROI A,ATMBUF
	TYPE < - "%1S"%/>
.ERMSX::CALL FLSTAK		; Flush TAKE files
;	CALLRET NPARSE

; CMINI functions
NPARSE:	SKIPN A,CMDBLK+.CMIOJ	; Get I/O JFNs
	 JRST REENTR		; None, go back to top-level parse.
	CAME A,[.PRIIN,,.NULIO]	; If not reading JCL,
	 JRST INICMD		; go restart parse.
	MOVEI A,.PRIOU		; (assume this was from top level)
	HRRM A,CMDBLK+.CMIOJ	; Reset command output JFN.
	CALL FQUIT		; Stop, reparse if continued.
	CAIA			; Skip into...
SETCMD:: POP P,CMDBLK+.CMFLG	; SETCMD -- do a CMINI (setting reparse)
INICMD:	MOVEI B,[FLDDB. .CMINI]	; Assumes caller is careful with SAVPDL.
	CALL .COMND		; Re-initialize parse.
	 JERMSG <Impossible error initializing COMND jsys>
	MOVE B,CMDBLK+.CMFLG	; Get reparse address
	CALLRET (B)		; And go to it.

; Pretend not reading JCL input so program won't halt at command loop
NOHALT:	MOVE A,CMDBLK+.CMIOJ	; Get command input and output JFNs
	CAME A,[.PRIIN,,.NULIO]	; Are we reading from JCL?
	 RET			; No, stop now
	MOVEI A,.PRIOU		; Yes, get normal output JFN.
	HRRM A,CMDBLK+.CMIOJ	; Don't HALTF back at command loop.
	RET

; Here to get rid of any JFNs left over from reparsing
; This should only be called on reparses of parses that can parse files.
; E.g. CHKCON is relied on not to call it.
FLTFIL:	SKIPN A,TMPJFN		; Did we have a parse JFN left over?
	 JRST FLFIL3		; No, go try for foreign file
FLFIL2:	RLJFN%			; Yes, release it
	 ERNOP			; Ignore errors - message would confuse parse
	SETZB A,TMPJFN		; Don't have a parse JFN now
FLFIL3:	EXCH A,FRNJFN		; Clear FRNFIL jfn, get what was there
	JUMPN A,FLFIL2		; Got a JFN, release it
	RET
; CPYATM - Copy from atom buffer to destination in A, counting chars in D
CPYATM::MKPTR (A)		; Make sure it's a real byte pointer.
	MOVE B,[POINT 7,ATMBUF]
	SETZ D,
	DO.
	  ILDB C,B
	  IDPB C,A
	  JUMPE C,R
	  AOJA D,TOP.
	ENDDO.


;CLRTMP - zero the temporary buffer page
CLRTMP:	SETZM TEMP
	MOVE A,[XWD TEMP, TEMP+1]
	BLT A,TEMP+777
	RET


; Subroutine to turn off echoing before password input
NOECHO:	PUSH P,A
	PUSH P,B
	MOVEI A,.CTTRM
	RFMOD%			; Read teletype mode word
	TXZ B,TT%ECO		; No echoing
	JRST ECHOST		; Join common code

; Subroutine to turn on echoing after password input
DOECHO:	PUSH P,A
	PUSH P,B
	MOVEI A,.CTTRM
	RFMOD%			; Read teletype mode word
	TXO B,TT%ECO		; Turn echoing back on
ECHOST:	SFMOD%			; Set TTY mode word
	STPAR%			; And the other way
	POP P,B
	POP P,A
	RET
; Parse a password.  Call with A/pointer to password, B/where to clear on abort
; Returns +1/aborted, +2/Ok (with password 0,,-1 if user typed return)

LS PSWPTR,2			; Where to put pointer to password for later

GETPSW::CALL PUSHIO		; Make sure we are talking to the terminal.
	SAVEAC <A,B,C,D>	; Save all accumulators
	DMOVEM A,PSWPTR		; Save pointer to put password into
	MOVEI A,.CTTRM
	RFMOD%			; Read TTY mode.
	TXNE B,TT%DUM		; Skip if full duplex, otherwise long prompt.
	aSKIPAdA,[POINTW7,[ASCIZ/WWWW      XXXXXXXXXXXXXXXXXXXX
	  HRROI A,[ASCIZ/Password: /]
	MOVEM A,CMDBLK+.CMRTY	; Set ^R text.
	SETABORT (PASABT)
	CALL SETCMD		; Start parsing a command
	MOVE P,SAVPDL		; Make sure stack is straight
	CALL NOECHO		; Turn off echoing
	MOVEI B,[FLDDB. .CMCFM,CM%SDH,,,,[
		 FLDDB. .CMTXT,CM%SDH,,<remote password>]]
	CALL .COMND
	 ERMSGX <Unable to parse password string>
	MOVE A,PSWPTR		; Get pointer to password string back
	LOAD C,CM%FNC,(C)	; Get FDB parsed
	CAIE C,.CMCFM		; Was it bare carriage return?
	IFSKP.
	  SETZM (A)		; Clear it out
	  HLLOS (A)		; Set right half -1 so we know psw was parsed
	ELSE.
	  CALL CPYATM		; Copy the new password to where it should go
	  CAILE D,USRSTL	; Was it too long?
	   ERMSGX <Password too long>
	  CALL CONFRM		; Finish parse
	ENDIF.
	CLRABORT		; Turn off our abort now that parse is over
	CALL DOECHO		; We want echoing on
	AOS (P)			; Set up for skip return
	CALLRET PCRIF		; Make sure we have a blank line

PASABT:	CALL DOECHO		; Make sure echoing is turned back on
	SETZM @1+PSWPTR		; Clear out location we were supposed to
	CALLRET NXTABT		; Try dropping another level of aborts
; CONFRM - wait for a user to type RETURN

CONFRM:	SAVEAC <A,B,C>		; Don't mung caller's ACs
	MOVEI B,[FLDDB. .CMCFM]
	CALL .COMND
	 IFSKP. <RET>		; Return on success
CNFERR:	ETYPE <Not confirmed - ">
	MOVE B,CMDBLK+.CMPTR
	DO.
	  ILDB A,B		; Get a char
	  INSET A,<.CHSPC,.CHTAB>,LOOP. ; If space/tab then back for real char
	ENDDO.
	DO.
	  JUMPE A,ENDLP.	; If null, done
	  CAIE A,.CHCRT		; If CR
	   CAIN A,.CHLFD	; or LF
	    EXIT.		; Likewise done
	  PBOUT%		; None of the above, send off the character
	  ILDB A,B		; Get another
	  LOOP.			; And go back to process it
	ENDDO.
	TYPE <"%/>		; Finish error message
	JRST .ERMSX		; Finish error processing

; CONFRM, return +1 if not confirmed, +2 confirmed
; always returns +2 if taking commands from a file.
PRMCNF::PROMPT 0(A)		; First prompt with AC1
DOCONF::SAVEAC <A,B,C>		; Don't mung caller ACs
	HLRZ A,CMDBLK+.CMIOJ	; Get input JFN.
	CAIE A,.PRIIN		; If reading from a TAKE file, return now.
	 RETSKP
	CALL PUSHIO		; Be careful with JCL input.
	SETABORT (PCRIF)	; ^G means not confirmed - newline and +1 ret
	CALL SETCMD
	MOVE P,SAVPDL
	MOVEI B,[FLDDB. .CMCFM]
	CALL .COMND
	IFSKP.
	  CLRABORT		; Confirmed, undo abort setting
	  RETSKP		; And finish up
	ENDIF.
	CLRABORT		; Not confirmed, clear the abort
;	CALLRET PCRIF		; and make sure we have a blank line

PCRIF:	TYPE <%_>		; Make sure we have a blank line
	RET
; Statistics routines.

; Call TIMEIN
; initialize the clock for a later call to TIMEOUT
; smashes registers A B and C.

LS STTCPU
LS STTCON

TIMEIN::TXNN F,F%STAT		; Statistics turned on?
	 RET			; No, just return.
	MOVEI A,.FHSLF		; On ourself
	RUNTM%			; read console and CPU time.
	MOVEM A,STTCPU		; Save CPU millisecs used
	MOVEM C,STTCON		; and console time used.
	RET			; All done.

; Call TIMOUT
; A/JFN of disk file
; If statistics turned on, uses info set by TIMEIN to show
; statistics about transfer rate.
; A is not smashed.

LS STTJFN

TIMOUT::TXNN F,F%STAT		; Statistics turned on?
	 RET			; No, just return.
	MOVEM A,STTJFN		; Save the JFN for later
	MOVEI A,.FHSLF		; On ourself
	RUNTM%			; read console and CPU time.
	SUB A,STTCPU		; Take difference between old cpu time and now
	SUB C,STTCON		; and old console time and now
	MOVEM A,STTCPU		; and save them again.
	MOVEM C,STTCON
	HRRZ A,STTJFN		; Now get that JFN back again
	CALL CHKDSK		; If it is not a disk file, can't get size...
	TXNN F,F%DSKF		; is it a disk?
	 JRST STNDSK		; No, go say how long we took.
	MOVE B,[2,,.FBBYV]	; 2 words starting at .FBBYV (includes .FBSIZ)
	MOVEI C,B		; into registers B and C.
	GTFDB%			; Get pages and number of bytes.
	 ERJMP STNDSK		; couldn't, go say something else.
	LOAD A,FB%BSZ,B		; Get byte size
	HRRZS B			; and page count and n bytes in individual ACs.
	TYPE <Transferred %3D %1D-bit bytes (%2D pages) in >
	IMUL A,C		; Get total number of bytes
	PUSH P,A		; and save it on the stack.
	CALL SHOTIM		; Say how much time was used.
	POP P,A			; Get time back
	IDIV A,STTCON		; Divide by console time to get bps.
	TYPE <%/for a total transfer rate of %1D kilobaud.%/>
	MOVE A,STTJFN		; Get the JFN back for caller.
	RET
; Routines for displaying calculated statistics

; Here when file is not a disk file, just say how long it took

STNDSK:	TYPE <Completed transfer in >
	CALL SHOTIM		; type CPU and console used
	TYPE <.%/>
	MOVE A,STTJFN		; Get the JFN back.
	RET

; Here with disk file, we know how long it is so we can give real stats

SHOTIM:	MOVE A,STTCON		; Get connection time
	TXZ F,F%TEMP		; Clear flag for later use
	IDIVI A,^D100		; Only keep tenths of seconds
	IDIVI A,^D60*^D60*^D10	; Get hours
	IFN. A			; If we have hours
	  TYPE <%1D:>		; Type them
	  TXO F,F%TEMP		; and remember we need both digits of minutes
	ENDIF.
	IDIVI B,^D60*^D10	; Get minutes
	TXNN F,F%TEMP		; If we haven't seen hours
	 JUMPE B,SHOSEC		; and there are no minutes, just do seconds
	PUSH P,C		; Otherwise save seconds
	CALL TWODIG		; Show minutes
	POP P,C			; Restore seconds
	MOVEI A,":"		; and type a colon to separate
	PBOUT%
SHOSEC:	IDIVI C,^D10		; Get seconds and tenths
	MOVE B,C		; Copy seconds
	CALL TWODIG		; Type them out
	TYPE <.%4D seconds, >	; Finish with tenths of seconds
	MOVE A,STTCPU
	IDIVI A,^D1000		; CPU into secs and msecs
	TYPE <%1D.>		; Type the seconds
	MOVEI A,.PRIOU		; then to the terminal
	MOVX C,NO%LFL!NO%ZRO!FLD(3,NO%COL)!FLD(^D10,NO%RDX)
	NOUT%			; Type the msecs as 3 digits
	 NOP
	TYPE < CPU>		; Finish off message
	RET

; Here to type out part of a time - hours, minutes etc.
; if F%TEMP is set, we have been called before, so use two columns

TWODIG:	MOVEI A,.PRIOU		; To the terminal
	MOVEI C,^D10		; Radix decimal
	TXOE F,F%TEMP		; Remember called.  If called before,
	 TXO C,NO%LFL!NO%ZRO!FLD(2,NO%COL) ; make sure we use both columns
	NOUT%			; Type out the number in B
	 NOP
	RET
; Get generation number of a file.
; Call with A/JFN
; Returns +1/Not a disk file, +2/Generation in B.

GTVERS:	CALL CHKDSK		; Set F%DSKF appropriately
	TXNN F,F%DSKF		; Is local file on disk?
	 RET			; No, can't get version.
	MOVE B,[XWD 1,.FBGEN]	; One word, generation (JFN still in A).
	MOVEI C,B		; Put it into B again.
	GTFDB%
	ERJMP R			; If failed, just return +1
	HLRZS B			; Put in RH
	RETSKP


; Make sure there is an Ethernet connection.
; Returns +1/No connection, +2/Connection open

CHKCON:	TXNE F,F%COPN		; Is there a connection open?
	 RETSKP			; Yes, done now.
	CALL PUSHIO		; Make sure talking to the terminal
	TYPE <No connection has been opened yet%/>
	PROMPT [ASCIZ/OPEN (connection to) /]
	SETABORT (R)
	DO.
	  CALL SETCMD		; Set up command parser.
	  MOVE P,SAVPDL
	  CALL OPNSUB		; Parse host-name and open connection.
	   LOOP.		; Failed, try once more.
	ENDDO.
	CLRABORT
	RETSKP			; Done, return.
; Check if we should do automatic ANONYMOUS login
; Returns +1 if the login was attempted
;	  +2 if no login was attempted

DOANON::SKIPE USRNAM		; Do we already have a user name?
	 RETSKP			; Yes, forget this
	SETZM USRPSW		; Clear password, rhost will set user
	SETZM USRACT
	CALL RHOST		; Try rhosts
	IFSKP.			; If it got something
	 MOVX A,<POINT 7,USRNAM>
	 TYPE <[Automatic login to %1S]%/>
	 CALL @.LOGIN(V)	; Try the login
	 IFSKP. <RET>		; It worked
	ENDIF.
	SETZM USRNAM		; Clear any user name that got set
	SETZM USRACT		; And any account
	TXNN F,F%ANON		; Do we want ANONYMOUS logins by default?
	 RETSKP			; No, take skip return
	HRROI A,USRNAM
	HRROI B,[ASCIZ/anonymous/] ; Lowercase for UNIX.  Everyone else copes.
	WRITE <%2S>		; Set up a username
	HRROI A,USRPSW
	HRROI B,[ASCIZ/guest/]	; A lowercase password as well.
	WRITE <%2S>		; Set up a password
	CALL @.LOGIN(V)		; Do protocol dependent stuff
	 RETSKP			; Some failure
	RET			; Possible success, take a single return
;RHOST - Try to set up USRNAM based on the user's RHOSTS file
;Returns +1 if found something
;	 +2 error

RHOST:	STKVAR <RHTJFN>		; Declare local storage
;construct name of user's magic file
	HRROI B,LCLUSR		; Point to our user name
	HRROI A,TMPHST		; Into temporary space
	WRITE (PS:<%2S>RHOSTS)	; Finish off filename.
	MOVX A,GJ%SHT!GJ%OLD
	HRROI B,TMPHST
	GTJFN%			; Get a JFN on the file
	 RET			; No such file, return now.
	MOVEM A,RHTJFN		; Save JFN in case OPENF% bombs
	MOVX B,<7B5!OF%RD>	; Open file for ead
	OPENF%
	IFJER.
	  MOVE A,RHTJFN
	  RLJFN%
	   NOP
	  RET
	ENDIF.

;now scan the file - main loop

	BIN%			;prime the pump
	 ERJMP RHOSTF		;stop at end of file
	DO.
	 MOVE C,[POINT 7, TMPHST] ; First thing in a line is host
	 SETZM TMPHST
;host name
	 DO.
	  CAIE B,40
	   CAIN B,11
	    EXIT.
	  IDPB B,C
	  BIN%
	   ERJMP RHOSTF		;stop at end of file
	  LOOP.
	 ENDDO.
	 SETZ D,		;make asciz
	 IDPB D,C
;skip whitespace
	 DO.
	  CAIE B,40
	   CAIN B,11
	    IFSKP. <EXIT.>
	  BIN%
	   ERJMP RHOSTF		;eof
	  LOOP.
	 ENDDO.
;user name
	 MOVX C,<POINT 7,USRNAM>
	 DO.
	  CAIE B,40
	   CAIN B,11
	    EXIT.
	  CAIN B,"/"
	   EXIT.
	  CAIE B,12
	   CAIN B,15
	    EXIT.
	  IDPB B,C
	  BIN%
	   ERJMP RHOSTF
	  LOOP.
	 ENDDO.
	 SETZ D,
	 IDPB D,C		;make asciz
;skip whitespace
	 DO.
	  CAIE B,40
	   CAIN B,11
	    IFSKP. <EXIT.>
	  BIN%
	   ERJMP RHOSTF		;eof
	  LOOP.
	 ENDDO.
;see if account.  Anything except 12, 15, or / must be account
	 DO.
	  CAIN B,"/"
	   EXIT.
	  CAIE B,15
	   CAIN B,12
	    EXIT.
	  MOVX C,<POINT 7,USRACT>
	  DO.
	   CAIE B,40
	    CAIN B,11
	     EXIT.
	   CAIN B,"/"
	    EXIT.
	   CAIE B,12
	    CAIN B,15
	     EXIT.
	   IDPB B,C
	   BIN%
	    ERJMP RHOSTF
	   LOOP.
	  ENDDO.
	  SETZ D,
	  IDPB D,C		;make asciz
	 ENDDO.
; skip to end of line, checking for switch
	 DO.
;see if switch
	  CAIE B,"/"
	  IFSKP.
	   BIN%
	    ERJMP RHOSTF
	   TRZ B,40		;upper case it
	   CAIE B,"O"		;if not /O
	    SETZM TMPHST	;then ignore this entry
	  ENDIF.
	  CAIE B,15
	   CAIN B,12
	    EXIT.
	  BIN%
	   ERJMP RHOSTF		;Eof
	  LOOP.
	 ENDDO.			;End of skip
;skip end of lines
	 DO.
	  CAIE B,12
	   CAIN B,15
	    IFSKP. <EXIT.>
	  BIN%
	  IFJER.
	   MOVEI B,0
	   EXIT.
	  ENDIF.
	  LOOP.
	 ENDDO.
;now see if we can use this user name
	 PUSH P,A
	 PUSH P,B
	 MOVX A,.GTHSN		;name to number
	 HRROI B,TMPHST		;it's here
	 GTDOM%			;get number of host in file
	 IFJER.
	  POP P,B
	  POP P,A
	  JRST TOP.		;can't, next line
	 ENDIF.
	 POP P,B
	 POP P,A
	 CAME C,HSTNUM		;is it the host we want?
	  JRST TOP.		;no, next line...
	 CLOSF%			;yes, is what we done.  Done with file
	  JFCL
	 RETSKP			;good return
	ENDDO.
;here if something goes wrong
RHOSTF:	MOVE A,RHTJFN		;Get back JFN
	CLOSF%			;Close file
	 NOP			;Ignore an error
	RET			;Return to caller
; Handle quota exceeded or disk full
; given error number in B

OVRQTA::ETYPE <Quota exceeded - EXPUNGE some files and continue%/>
	HALTF%			; Stop the program
	RET			; Try again


; Handle unexpected end of connection
DISCON::ETYPE <Connection closed by remote host>
EDISC::	TXNE F,F%COPN		; Connection still open?
	 CALL @.CLOSE(V)	; Yes, close it
	CALL KILFIL		; Abort input or output file
	CALL FLSTAK		; Flush take files
	JRST COMLP		; Restart command parse

; Here to set watchdog timer to avoid idle timeouts
SETWDT::RET			; Don't need to do anything in user program
; Set up filename and extension defaults from TEMP space for long GTJFN
; Call with B/JFN

GTJDFT:	CALL CLRTMP
	HRROI A,TEMP
	HRROI D,[ASCIZ/*/]
	IFXE. B,GJ%NAM		; Wildcard name?
	  MOVEM A,CMDGTJ+.GJNAM
	  MOVX C,FLD(.JSAOF,JS%NAM)
	  CALL .JFNS		; Set up default filename
	  HRROI A,2(A)		; Set at word boundary with plenty of margin
	ELSE.
	  MOVEM D,CMDGTJ+.GJNAM
	ENDIF.
	IFXE. B,GJ%EXT		; Wildcard extension?
	  MOVEM A,CMDGTJ+.GJEXT
	  MOVX C,FLD(.JSAOF,JS%TYP)
	  CALLRET .JFNS		; Set up extension.
	ENDIF.
	MOVEM D,CMDGTJ+.GJEXT
	RET
; Handle protocol-dependent yoyo

%UFTPM::TXNE F,F%COPN		; Connection open?
	 JRST @.UFTPM(V)	; Yes, run yoyo for protocol
	FATAL <UFTPM UUO when no connection is open>


; Construct Server-Filename if necessary from other props, plist in A

FIXNAM::SKIPE P.SFIL(A)		; Server-Filename specified?
	 RET			; Yes, nothing to do
	HRROI B,P.SFIL(A)	; No, start string pointer to cell
	HRROI C,P.DEVI(A)	; Get Device string ptr
	SKIPE P.DEVI(A)		; Device specified?
	 WRITE B,<%3S:>		; Yes, prefix it
	HRROI C,P.DIRE(A)	; Get directory string pointer
	SKIPE P.DIRE(A)		; Directory specified?
	 WRITE B,<<%3S>>	; Yes, expand into string
	HRROI C,P.NAMB(A)	; Append Name-Body string
	WRITE B,<%3S>
	SKIPE C,P.VERS(A)	; Version specified?
	 WRITE B,<.%3D>		; Yes, append it
	RET

; Clear UPDATE or INSTALL if unhandleable
; Call with A/reason why not available
; Returns +1 or jumps out to COMLP

NOUPDA::TXZN F,F%UPDA		;UPDATE or INSTALL?
	 RET			;No, nothing to do
	TYPE <%% UPDATE or INSTALL not available - %1S%/>
	PROMPT [ASCIZ/Always assuming source is more recent [Confirm] /]
	CALL DOCONF		;Make sure user really wants to do it
	 JRST COMLP		;Didn't want to, throw to top level
	RET			;Wanted to, return normally
SUBTTL Storage assignments

LS LCLHST,20			; Local host name
LS LCLUSR,20			; Local user name

LS STRPTR
LS EDTPTR

LS SAVPDL			; Saved stack pointer for subcommand parses

LS STACK,STKLEN			; Local stack
LS IPDL,IPDLEN			; TAKE file JFN stack
LSP TEMP,1			; One page for general scratch use
LS TMPHST,20			; Work space for RHOSTS code

LS NETJFN			; Network input and output JFNs
LS FILJFN			; File send/receive JFN
LS TMPJFN			; Temporary JFN for file parsing
				; (should not be used by other modules)
LS FRNJFN			; Same for use by FRNFIL

LS PUPPAR			; PUPPAR tabel definition for PUPXFR

LSP NETBUF,1			; Network I/O buffer
LSP FILBUF,1			; Local file buffer

LS HSTNUM			; Host number
LS HSTSTR,10			; Host name
LS HSTOPS			; Host operating system

LS FRECOR			; Pointer to user-login-default free area
LS ULGDEF			; Pointer to last-defined login default

LS USRNAM,USRSTL/5+1		; User-Name string
LS USRPSW,USRSTL/5+1		; User-Password string
LS USRACT,USRSTL/5+1		; User-Account string
LS CONNAM,USRSTL/5+1		; Connect-Name string
LS CONPSW,USRSTL/5+1		; Connect-Password string

	END