;PUPXFR.MAC;6     2-SEP-79 16:01:37    EDIT BY TAFT
;PUPXFR.MAC;5     1-SEP-77 16:14:07    EDIT BY TAFT
; Make a few things internal for the mail server
;PUPXFR.MAC;4    18-MAR-77 19:44:47    EDIT BY TAFT
; Fix bugs in paged transfer code
;PUPXFR.MAC;3    18-MAR-77 17:07:39    EDIT BY TAFT
; Remove ## from property list offset symbols
;PUPXFR.MAC;2    15-MAR-77 18:56:49    EDIT BY TAFT
; Implement Tenex-paged transfer type
;PUPXFR.MAC;1    10-MAR-77 14:05:22    EDIT BY TAFT
; Split out from PUPFTP.MAC

; Copyright 1979 by Xerox Corporation

	TITLE PUPXFR -- DATA TRANSFER CODE FOR FTP USER AND SERVER
	SUBTTL E. A. Taft / March 1977

	SEARCH PUPDEF,STENEX
	USEVAR FTPVAR,FTPPVR


; Receive data from net to local file
; Assumes SRCJFN, DSTJFN setup and local file open
;	A/ Property list pointer
; Returns +1: Data error during Retrieve
;	+2: Retrieve completed successfully
; No messages generated
; JFN still open
; Clobbers A-D

RECDAT::PUSHJ P,SAVE1##
	MOVE P1,A		; Preserve property list pointer

	MOVEI A,NETBUF##	; Source is net
	MOVEM A,SRCIBP
	MOVEI A,8		; Bytesize always 8
	PUSHJ P,SETINP		; Setup for input
	MOVEI A,RECNDE		; Set dispatch for net data error
	MOVEM A,SRCDSP##

	MOVEI A,FILBUF##	; Destination is local file system
	MOVEM A,DSTIBP
	HRRZ A,P.BYTE(P1)	; Bytesize as specified
	PUSHJ P,SETOUT		; Setup for output
	MOVEI A,RECFDE		; Set dispatch for file data error
	MOVEM A,DSTDSP##
	MOVEM P,ERRPDP##	; Save stack level for error

	HRRZ A,P.BYTE(P1)	; Get type and bytesize again
	HRRZ B,P.TYPE(P1)
	MOVEI D,PAGREC		; Assume paged
	CAIN B,3		; Check type
	 JRST RECDA5		; Paged
	CAIN B,2
	 JRST RECDA3		; Binary
	MOVEI D,TXTREC		; Text, assume have to convert
	HRRZ C,P.EOLC(P1)	; Get end-of-line convention
	JUMPE C,RECDA5		; Jump if must convert CR to CRLF
RECDA3:	MOVEI D,BINBYT		; Assume can do byte-at-a-time
	CAIN A,8		; Local byte size 8?
	 MOVEI D,BINPAG		; Yes, can do page-at-a-time
	CAILE A,8		; Local byte size more than 8?
	 MOVEI D,MULREC		; Yes, must do multi-byte shuffle
RECDA5:	PUSHJ P,0(D)		; Do the transfer
	PUSHJ P,PUTLPG		; Force out last buffer if required
	PUSHJ P,UNMAP		; Unmap window page if any
	DTYPE <%/>		; Eol after "!"s if debugging
	JRST SKPRET##		; Give +2 return

; Here if I/O data error occurs while writing into local file
RECFDE:	PUSHJ P,UNMAP		; Unmap window page if any
	HRRZ A,DSTJFN		; Get destination JFN
	SETZ B,			; Clear error flag if on
	STSTS
	 CAI
	POPJ P,			; Give +1 return

; Here if I/O data error occurs while reading from net
RECNDE:	PUSHJ P,UNMAP		; Unmap window page if any
	PUSHJ P,KILFIL##	; Try to eliminate the file
	MOVEI A,400000		; Force unenabled data error PSI
	MOVSI B,(1B11)
	IIC
	PUSHJ P,SCREWUP##

; Send data from local file to net
; Assumes SRCJFN, DSTJFN setup and local file open
;	A/ Property list pointer
; Returns +1: Data error
;	+2: Transfer completed successfully
; No reply messages generated
; JFN still open
; Clobbers A-D

SNDDAT::PUSHJ P,SAVE1##
	MOVE P1,A		; Preserve property list pointer

	MOVEI A,FILBUF##	; Source is local file system
	MOVEM A,SRCIBP
	HRRZ A,P.BYTE(P1)	; Bytesize as specified
	PUSHJ P,SETINP		; Setup for input
	MOVEI A,SNDDTE		; Set input error dispatch
	MOVEM A,SRCDSP##
	MOVEM P,ERRPDP##	; Save stack level

	MOVEI A,NETBUF##	; Destination is net
	MOVEM A,DSTIBP
	MOVEI A,8		; Bytesize always 8
	PUSHJ P,SETOUT		; Setup for output
	SETZM DSTDSP##		; Do not enable for output errors

	HRRZ A,P.BYTE(P1)	; Get type and bytesize again
	HRRZ B,P.TYPE(P1)
	MOVEI D,PAGSND		; Assume paged
	CAIN B,3		; Check type
	 JRST SNDDA5		; Paged
	CAIN B,2
	 JRST SNDDA3		; Binary
	MOVEI D,TXTSND		; Text, assume have to convert
	HRRZ C,P.EOLC(P1)	; Get end-of-line convention
	JUMPE C,SNDDA5		; Jump if must convert CRLF to CR
SNDDA3:	MOVEI D,BINBYT		; Assume can do byte-at-a-time
	CAIN A,8		; Local byte size 8?
	 MOVEI D,BINPAG		; Yes, can do page-at-a-time
	CAILE A,8		; Local byte size more than 8?
	 MOVEI D,MULSND		; Yes, must do multi-byte shuffle
SNDDA5:	PUSHJ P,0(D)		; Do the transfer
	PUSHJ P,PUTLPG		; Force out last buffer if required
	PUSHJ P,UNMAP		; Unmap window page if any
	DTYPE <%/>		; Eol after "!"s if debugging
	JRST SKPRET##		; Return +2

; Here if I/O data error occurs while reading from local file
SNDDTE:	PUSHJ P,UNMAP		; Unmap window page if any
	HRRZ A,SRCJFN		; Get source JFN
	SETZ B,			; Clear error flag if on
	STSTS
	 CAI
	POPJ P,			; Return +1

; Routines implementing the various types of data transfer.
; Assume that source and destination parameters are set up
; and that P1 points to the file property list.
; Return +1 always
; Clobber A-D

; Transfer binary data page at a time (no conversion)
BINPAG:	HRRZ A,SRCIBP		; Use same buffer for destination
	HRRM A,DSTIBP		;  as for source
	HRRM A,DSTBYT
BINPA1:	PUSHJ P,GETPAG		; Get a page of data
	 POPJ P,		; No more
	PUSHJ P,PUTPAG		; Output the data we got
	JRST BINPA1		; Repeat


; Transfer binary data byte at a time (no conversion)
BINBYT:	SOSGE SRCCNT		; Count down source bytes
	 PUSHJ P,GETNPG		; None, get new page (return .-1)
	ILDB A,SRCBYT		; Get the byte
	SOSGE DSTCNT		; Count down destination bytes
	 PUSHJ P,PUTNPG		; No more, dump buffer (return .-1)
	IDPB A,DSTBYT		; Store in output buffer
	JRST BINBYT		; Repeat


; Receive text from net (CR to CRLF conversion)
TXTREC:	SOSGE SRCCNT		; Count down source bytes
	 PUSHJ P,GETNPG		; None, get new page (return .-1)
	ILDB A,SRCBYT		; Get the byte
TXTRE1:	SOSGE DSTCNT		; Count down destination bytes
	 PUSHJ P,PUTNPG		; No more, dump buffer (return .-1)
	IDPB A,DSTBYT		; Store in output buffer
	CAIE A,15		; Carriage return?
	 JRST TXTREC		; No, not special
	MOVEI A,12		; Yes, append line feed
	PUSHJ P,PUTBYT
	PUSHJ P,GETBYT		; Get next byte
	 POPJ P,		; No more
	CAIN A,12		; A line feed?
	 JRST TXTREC		; Yes, don't double it
	JRST TXTRE1		; No, handle normally


; Send text to net (CRLF to CR conversion)
TXTSND:	SOSGE SRCCNT		; Count down source bytes
	 PUSHJ P,GETNPG		; None, get new page (return .-1)
	ILDB A,SRCBYT		; Get the byte
	SOSGE DSTCNT		; Count down destination bytes
	 PUSHJ P,PUTNPG		; No more, dump buffer (return .-1)
	IDPB A,DSTBYT		; Store in output buffer
	CAIE A,15		; Carriage return?
	 JRST TXTSND		; No, not special
	PUSHJ P,GETBYT		; Yes, get next byte
	 POPJ P,		; No more
	CAIE A,12		; Line feed?
	 PUSHJ P,PUTBYT		; No, store it
	JRST TXTSND		; Repeat

; Receive data from net with multiple-byte shuffle
; Each local byte received right-justified in an integral number
; of network bytes
MULREC:	PUSHJ P,SAVE1##		; Get another ac
	HRRZ A,P.BYTE(P1)	; Get local byte size
	ADDI A,7		; Compute net bytes per local byte
	IDIVI A,8
	HRLM A,0(P)		; Save it

; Loop here for each local byte
MULRE1:	HLRZ B,0(P)		; Get net bytes/local byte
	JRST @MULRTB-2(B)	; Dispatch to code to receive bytes

MULRTB:	MULRE2			; 2 net bytes/local byte
	MULRE3			; 3
	MULRE4			; 4
	MULRE5			; 5

MULRE5:	SOSGE SRCCNT		; Count down source bytes
	 PUSHJ P,GETNPG		; None, get new page (return .-1)
	ILDB A,SRCBYT		; Get the byte
	LSH P1,8		; Make room in local byte
	IORI P1,(A)		; Append new byte

MULRE4:	SOSGE SRCCNT		; Count down source bytes
	 PUSHJ P,GETNPG		; None, get new page (return .-1)
	ILDB A,SRCBYT		; Get the byte
	LSH P1,8		; Make room in local byte
	IORI P1,(A)		; Append new byte

MULRE3:	SOSGE SRCCNT		; Count down source bytes
	 PUSHJ P,GETNPG		; None, get new page (return .-1)
	ILDB A,SRCBYT		; Get the byte
	LSH P1,8		; Make room in local byte
	IORI P1,(A)		; Append new byte

MULRE2:	SOSGE SRCCNT		; Count down source bytes
	 PUSHJ P,GETNPG		; None, get new page (return .-1)
	ILDB A,SRCBYT		; Get the byte
	LSH P1,8		; Make room in local byte
	IORI P1,(A)		; Append new byte

	SOSGE SRCCNT		; Count down source bytes
	 PUSHJ P,GETNPG		; None, get new page (return .-1)
	ILDB A,SRCBYT		; Get the byte
	LSH P1,8		; Make room in local byte
	IOR A,P1		; Append new byte

	SOSGE DSTCNT		; Count down destination bytes
	 PUSHJ P,PUTNPG		; No more, dump buffer (return .-1)
	IDPB A,DSTBYT		; Store byte in output buffer
	JRST MULRE1		; Repeat for next local byte

; Send data to net with multiple-byte shuffle
; Each local byte sent right-justified in an integral number
; of network bytes
MULSND:	HRRZ A,P.BYTE(P1)	; Get local byte size
	ADDI A,7		; Compute net bytes per local byte
	IDIVI A,8
	HRLM A,0(P)		; Save it

; Loop here for each local byte
MULSN1:	SOSGE SRCCNT		; Count down source bytes
	 PUSHJ P,GETNPG		; None, get new page (return .-1)
	ILDB A,SRCBYT		; Get the byte
	HLRZ B,0(P)		; Recover net bytes/local byte
	XCT ROTTBL(B)		; Right-justify first net byte
	JRST @MULSTB-2(B)	; Branch to code to send bytes

ROTTBL:	PUSHJ P,SCREWUP		; 0 net bytes/local byte
	PUSHJ P,SCREWUP		; 1
	ROT A,-8		; 2
	ROT A,-↑D16		; 3
	ROT A,-↑D24		; 4
	ROT A,-↑D32		; 5

MULSTB:	MULSN2			; 2 net bytes/local byte
	MULSN3			; 3
	MULSN4			; 4
	MULSN5			; 5

MULSN5:	SOSGE DSTCNT		; Count down destination bytes
	 PUSHJ P,PUTNPG		; No more, dump buffer (return .-1)
	IDPB A,DSTBYT		; Store byte in output buffer
	ROT A,8			; Prepare next byte

MULSN4:	SOSGE DSTCNT		; Count down destination bytes
	 PUSHJ P,PUTNPG		; No more, dump buffer (return .-1)
	IDPB A,DSTBYT		; Store byte in output buffer
	ROT A,8			; Prepare next byte

MULSN3:	SOSGE DSTCNT		; Count down destination bytes
	 PUSHJ P,PUTNPG		; No more, dump buffer (return .-1)
	IDPB A,DSTBYT		; Store byte in output buffer
	ROT A,8			; Prepare next byte

MULSN2:	SOSGE DSTCNT		; Count down destination bytes
	 PUSHJ P,PUTNPG		; No more, dump buffer (return .-1)
	IDPB A,DSTBYT		; Store byte in output buffer
	ROT A,8			; Prepare next byte

	SOSGE DSTCNT		; Count down destination bytes
	 PUSHJ P,PUTNPG		; No more, dump buffer (return .-1)
	IDPB A,DSTBYT		; Store byte in output buffer
	JRST MULSN1		; Loop for next local byte

; Receive Tenex-paged file

PAGREC:	PUSHJ P,SAVE3##		; Save ac's used by coroutine
	MOVEI P1,RECCOR		; Initialize coroutine
	MOVE P3,[-25,,FILBUF##]	; Receive FDB
	PUSHJ P,GETBLK
	 JRST ILLREC		; Eof at bad time

; Copy selected fields from the received FDB
	MOVEI D,NORTBL		; Pointer to field descriptor table
	MOVE P3,0(D)		; Get byte pointer
PAGRE1:	ILDB A,P3		; Get FDB offset
	JUMPE A,PAGRE2		; Jump if done
	MOVE B,1(D)		; Get word mask
	MOVE C,FILBUF##(A)	; Get data from received FDB
	HRLZ A,A		; Put offset in lh
	HRR A,DSTJFN		; File JFN
	CHFDB			; Set the FDB word
	AOJA D,PAGRE1		; Repeat

; Receive next page of file
PAGRE2:	JSP P1,0(P1)		; Get protection,,page #
	 POPJ P,		; No more
	PUSH P,A		; Save them
	MOVE P3,[-1000,,FILBUF##]  ; Get page of data
	PUSHJ P,GETBLK
	 JRST ILLREC		; Eof at bad time
	MOVEI A,FILBUF##	; Get window address in this fork
	LSH A,-9		; Convert to page number
	HRLI A,400000		; This fork
	HRRZ B,0(P)		; File page number
	HRL B,DSTJFN		; File JFN
	MOVSI C,(1B3)		; Give us write access
	PMAP			; Put fork page in file
	MOVE A,B		; File JFN,,page
	POP P,B			; Recover protection
	SPACS			; Set page protection
	PUSHJ P,UNMAP		; Remove page from fork's map
	JRST PAGRE2		; Repeat for next page

; Here if EOF occurs at a bad time.  Force data error PSI
ILLREC:	MOVEI A,400000		; This fork
	MOVSI B,(1B11)
	IIC			; Initiate PSI
	PUSHJ P,SCREWUP##	; Should never return


; Table describing fields to copy from received FDB

; Normal attribute retention:
NORTBL:	POINT 5,[BYTE(5) 11,12,0]
	7700,,0		; FDBBYV: byte size
	-1		; FDBSIZ

; May add other tables later for additional attribute retention


; Receive block of 36-bit data from net
;	P3/ -length,,address of block
;	P1, P2/ set up for coroutine
; Returns +1:  EOF before block exhausted
;	+2:  Normal
; Clobbers A-D, P3

GETBLK:	JSP P1,0(P1)		; Coreturn for next word
	 POPJ P,		; End of file
	MOVEM A,0(P3)		; Store in block
	AOBJN P3,GETBLK		; Loop thru block
	JRST SKPRET##		; Return +2

; Receive coroutine
; Uses P1 for coroutine linkage, P2 for -word count,,pointer.
;	JSP P1,0(P1)
; Returns +1:  end of file
;	+2:  normal, A/ data word
; Clobbers A-D;  updates P1, P2

; Coroutine initially starts here
RECCOR:	MOVE B,SRCBPP		; Get nominal bytes per page
	IDIVI B,↑D20		; Length of cycle is 20 bytes
	IMULI B,↑D20
	MOVEM B,SRCBPP		; Store actual bytes per buffer
	JRST RECNPG		; Go read first buffer

; Top of loop
RECCO0:	AOBJP P2,RECNPG		; Jump if buffer empty
	MOVE A,-1(P2)		; Get first 28 bits
	MOVE B,0(P2)		; Get last 8 bits
	LSH A,-4		; Concatenate
	LSHC A,8
	JSP P1,1(P1)		; Coreturn +2

	AOBJP P2,RECNPG		; Jump if buffer empty
	MOVE A,-1(P2)		; Get first 20 bits
	MOVE B,0(P2)		; Get last 16 bits
	LSH A,-4		; Concatenate
	LSHC A,↑D16
	JSP P1,1(P1)		; Coreturn +2

	AOBJP P2,RECNPG		; Jump if buffer empty
	MOVE A,-1(P2)		; Get first 12 bits
	MOVE B,0(P2)		; Get last 24 bits
	LSH A,-4		; Concatenate
	LSHC A,↑D24
	JSP P1,1(P1)		; Coreturn +2

	AOBJP P2,RECNPG		; Jump if buffer empty
	MOVE A,-1(P2)		; Get first 4 bits
	MOVE B,0(P2)		; Get last 32 bits
	LSH A,-4		; Concatenate
	LSHC A,↑D32
	JSP P1,1(P1)		; Coreturn +2

	AOJA P2,RECCO0		; Back to top of loop

; Here when buffer empty, get another
RECNPG:	PUSHJ P,GETPAG		; Get another buffer from net
	 JRST 0(P1)		; No more, return +1
	IDIVI C,5		; Number of words in network format
	HRLOI P2,0(C)		; Make -(word count +1),,pointer
	EQVI P2,0(B)
	JRST RECCO0		; Back to top of coroutine

; Send Tenex-paged file

PAGSND:	PUSHJ P,SAVE3##		; Save ac's used by coroutine
	MOVEI P1,SNDCOR		; Initialize coroutine
	HRRZ A,SRCJFN		; Get FDB for local file
	MOVSI B,25		; The whole thing
	MOVEI C,FILBUF##	; Put it here
	GTFDB
	MOVE P3,[-25,,FILBUF##]	; Send FDB to net
	PUSHJ P,PUTBLK

	HRROS 0(P)		; Init previous page # to -1

; Send next page of file
PAGSN2:	PUSHJ P,SETWDT##	; Reset watchdog timer
	DTYPE 		; Signal progress if debugging
	HLRZ A,0(P)		; Get previous page number
	ADDI A,1		; Increment
	HRL A,SRCJFN
	FFUFP			; Find next used page
	 JRST FIXCNT		; No more, fix count for flush
	HRLM A,0(P)		; Save page number
	MOVEI B,FILBUF##	; Get window address in this fork
	LSH B,-9		; Convert to page number
	HRLI B,400000		; This fork
	MOVSI C,(1B2)		; Give us read access
	PMAP			; Map fork page into file
	RPACS			; Get page protection
	HLL A,B			; Make protection,,page #
	JSP P1,0(P1)		; Send it
	MOVE P3,[-1000,,FILBUF##]  ; Send page of data
	PUSHJ P,PUTBLK
	JRST PAGSN2		; Repeat for next page


; Send block of 36-bit data to net
;	P3/ -length,,address of block
;	P1, P2/ set up for coroutine
; Returns +1 always
; Clobbers A-D, P3

PUTBLK:	MOVE A,0(P3)		; Get word from block
	JSP P1,0(P1)		; Coreturn to send next word
	AOBJN P3,PUTBLK		; Loop thru block
	POPJ P,


; Unmap window page when done with file (so we can close it)
; Returns +1
; Clobbers A, B

UNMAP::	SETO A,
	MOVEI B,FILBUF##	; File buffer address
	LSH B,-9		; Convert to page number
	HRLI B,400000		; This fork
	PMAP			; Unmap page
	POPJ P,

; Send coroutine
; Uses P1 for coroutine linkage, P2 for -word count,,pointer.
;	A/ data word
;	JSP P1,0(P1)
; Returns +1 always
; Clobbers A-D;  updates P1, P2

; Coroutine initially starts here
SNDCOR:	PUSH P,A		; Save data
	MOVE B,DSTBPP		; Get nominal bytes per page
	IDIVI B,↑D20		; Length of cycle is 20 bytes
	IMULI B,↑D20
	MOVEM B,DSTBPP		; Store actual bytes per buffer
	MOVEM B,DSTCNT		; No bytes actually stored yet
	JRST SNDNP0		; Go setup first buffer

; Top of loop
SNDCO0:	AOBJP P2,SNDNPG		; Jump if buffer full
	SETZ B,
	LSHC A,-4
	MOVEM A,-1(P2)		; Store first 28 bits
	LSHC A,-4
	MOVEM B,0(P2)		; Store last 8 bits
	JSP P1,0(P1)		; Coreturn

	AOBJP P2,SNDNPG		; Jump if buffer full
	SETZ B,
	LSHC A,-↑D12
	IORM A,-1(P2)		; Store first 20 bits
	LSHC A,-4
	MOVEM B,0(P2)		; Store last 16 bits
	JSP P1,0(P1)		; Coreturn

	AOBJP P2,SNDNPG		; Jump if buffer full
	SETZ B,
	LSHC A,-↑D20
	IORM A,-1(P2)		; Store first 12 bits
	LSHC A,-4
	MOVEM B,0(P2)		; Store last 24 bits
	JSP P1,0(P1)		; Coreturn

	AOBJP P2,SNDNPG		; Jump if buffer full
	LSHC A,-↑D28
	IORM A,-1(P2)		; Store first 4 bits
	LSHC A,-4
	MOVEM B,0(P2)		; Store last 32 bits
	JSP P1,0(P1)		; Coreturn

	AOJA P2,SNDCO0		; Back to top of loop

; Here when buffer full, send it and start another.
SNDNPG:	PUSH P,A		; Save data
	SUB P2,[1,,1]		; Correct word count
	PUSHJ P,FIXCNT		; Recompute byte count
SNDNP0:	PUSHJ P,PUTLPG		; Send another buffer to net
	IDIVI C,5		; Number of words in network format
	HRLOI P2,0(C)		; Make -(word count +1),,pointer
	EQVI P2,0(B)
	POP P,A			; Recover data
	JRST SNDCO0		; Back to top of coroutine


; Fix up output byte count (DSTCNT) to be consistent with count
; of words actually stored
;	P2/ -(word count +1),,pointer
; Returns +1
; Clobbers A

FIXCNT:	HLRE A,P2		; Get -(word count+1)
	MOVNI A,1(A)		; Compute word count in RH
	IMULI A,5		; Byte count
	HRRZM A,DSTCNT		; Store in standard place
	POPJ P,

; Setup to do input
;	A/ Byte size
; Assumes SRCJFN setup
; Returns +1
; Clobbers B, C

SETINP::MOVEI B,4400(A)		; Setup initial byte pointer
	LSH B,6
	HRLM B,SRCIBP
	MOVEI B,↑D36		; Compute bytes per word
	IDIVI B,(A)
	LSH B,9			; Compute bytes per page
	MOVEM B,SRCBPP		; Store bytes per page
	SETZM SRCCNT		; No bytes yet in buffer
	POPJ P,


; Get next byte of data from input file
; Returns +1:  End of file
;	+2:  A/ the byte
; Clobbers A-D

GETBYT::SOSGE SRCCNT		; Count down source bytes
	 PUSHJ P,GETNPG		; None, get new page (return .-1)
	ILDB A,SRCBYT		; Get the byte
	JRST SKPRET##		; Return +2


; Get next page of data from input file (special call)
; Returns to caller-1 on success
; Returns to caller of caller +1 on end of file
; Clobbers A-D

GETNPG::PUSHJ P,GETPAG		; Get page from input file
	 JRST [	SUB P,[1,,1]	; No more, flush caller
		POPJ P,]	; Return to caller of caller
	POP P,D			; Got some, pop off return
	JRST -2(D)		; Return to caller-1


; Get a page of data from input file
; Returns +1:  End of file
;	+2:  B/ Byte pointer, C/ Byte count
;		Also setup in SRCBYT and SRCCNT respectively
; Clobbers A-D

GETPAG::PUSHJ P,SETWDT##	; Reset watchdog timer
	DTYPE 		; Signal progress if debugging
	HRRZ A,SRCJFN		; Get source JFN
	MOVE B,SRCIBP		; Get source buffer byte ptr
	MOVN C,SRCBPP		; Get negative of bytes/page
	HRROS SRCDSP		; Enable data error dispatch
	SIN			; Read bytes from source
	HRRZS SRCDSP		; Disable data error dispatch
	ADD C,SRCBPP		; Compute # bytes read
	JUMPLE C,CPOPJ##	; Return +1 if none (eof)
	MOVEM C,SRCCNT
	MOVE B,SRCIBP		; Setup source buffer byte ptr
	MOVEM B,SRCBYT
	JRST SKPRET##		; Return +2

; Setup to do output
;	A/ Byte size
; Assumes DSTJFN setup
; Returns +1
; Clobbers B, C

SETOUT::MOVEI B,4400(A)		; Setup initial byte pointer
	LSH B,6
	HRLM B,DSTIBP
	MOVE B,DSTIBP		; Copy to current byte pointer
	MOVEM B,DSTBYT
	SETZM 0(B)		; Clear the buffer so that leftover
	MOVSI C,0(B)		;  bits in byte-by-byte transfers
	HRRI C,1(B)		;  stay zero
	BLT C,777(B)
	MOVEI B,↑D36		; Compute bytes per word
	IDIVI B,(A)
	LSH B,9			; Compute bytes per page
	MOVEM B,DSTBPP		; Store bytes per page
	MOVEM B,DSTCNT		; Set initial count
	POPJ P,


; Put next byte of data into output file
;	A/ the byte
; Returns +1 always
; Clobbers A-D

PUTBYT::SOSGE DSTCNT		; Count down destination bytes
	 PUSHJ P,PUTNPG		; No more, write page (return .-1)
	IDPB A,DSTBYT		; Ok, store the byte
	POPJ P,			; Return


; Put next page of data on output file (special call)
; Assumes one excess "SOSGE DSTCNT" has been done
; Returns to caller-1 always
; Clobbers B-D;  preserves A

PUTNPG::PUSH P,A		; Save possible data being stored
	MOVE C,DSTBPP		; Get bytes/page
	SUB C,DSTCNT		; Compute actual # bytes stored
	SUBI C,1
	PUSHJ P,PUTPAG		; Put page on output file
	POP P,A			; Restore data
	POP P,D			; Pop off return
	JRST -2(D)		; Return to caller-1


; Output last (partial) page if necessary
; Assumes DSTCNT is correct
; Returns +1
; Clobbers A-D

PUTLPG::MOVE C,DSTBPP		; Get bytes/page
	SUB C,DSTCNT		; Compute actual # bytes in buffer
				; Fall into PUTPAG


; Put a page of data on output file
;	C/ # of data bytes in page
; Returns +1 always
;	B/ Byte ptr, C/ Byte count for buffer
;	Also setup in DSTBYT and DSTCNT respectively
; Clobbers A-D

PUTPAG::JUMPLE C,PUTPA1		; Do nothing if none
	HRRZ A,DSTJFN		; Get destination JFN
	MOVE B,DSTIBP		; Get destination buffer byte ptr
	MOVN C,C		; Make negative byte count
	HRROS DSTDSP		; Enable data error dispatch
	SOUT			; Write bytes on destination
	HRRZS DSTDSP		; Disable data error dispatch
PUTPA1:	MOVE C,DSTBPP		; Setup destination bytes/page
	MOVEM C,DSTCNT
	MOVE B,DSTIBP		; Setup destination buffer byte ptr
	MOVEM B,DSTBYT
	POPJ P,			; Return

; Storage

LS SRCJFN		; Source JFN
LS SRCIBP		; Source buffer byte pointer
LS SRCBPP		; Source bytes/page
LS SRCBYT		; Current byte ptr
LS SRCCNT		; Remaining bytes in buffer

LS DSTJFN		; Destination JFN
LS DSTIBP		; Destination buffer byte pointer
LS DSTBPP		; Destination bytes/page
LS DSTBYT		; Current byte ptr
LS DSTCNT		; Remaining bytes in buffer


	END