;;; -*- Mode:LISP; Package:SYSTEM-INTERNALS; Readtable:ZL; Base:10; Fonts:(CPTFONTB) -*-

(defmacro with-decoded-disk-unit ((decoded encoded use . options) &body body)
  (let ((u (gentemp "unit"))
	(d (gentemp "dont-dispose")))
    `(let (,u ,d)
       (unwind-protect
	   (let ((,decoded (multiple-value-setq (,u ,d) (decode-unit-argument ,encoded ,use ,@options))))
	     ,@body)
	 (and ,u (not ,d) (dispose-of-unit ,u))))))


(DEFMACRO WITH-DISK-RQB ((RQB . OPTIONS) &BODY BODY)
  (LET ((R (GENTEMP "rqb")))
    `(LET (,R)
       (UNWIND-PROTECT
	   (LET ((,RQB (SETQ ,R (GET-DISK-RQB ,@OPTIONS))))
	     ,@BODY)
	 (AND ,R (RETURN-DISK-RQB ,R))))))


(DEFUN MAGTAPE-UNIT-P (FROM-UNIT)
  (AND (CLOSUREP FROM-UNIT)
       (EQ (CLOSURE-FUNCTION FROM-UNIT) 'FS:BAND-MAGTAPE-HANDLER)))
		       

(DEFUN MEASURED-FROM-PART-SIZE (FROM-UNIT FROM-PART FROM-PART-BASE FROM-PART-SIZE)
  (COND ((MAGTAPE-UNIT-P FROM-UNIT)
	 ;; PROTOCOL KLUDGE. WE ALREADY KNOW THE SIZE IS CORRECT FROM FIND-DISK-PARTITION-FOR-READ
	 ;; AND WE ALSO KNOW THAT WE CANNOT DO RANDOM READS ON SUCH A UNIT.
	 ())
	((STRING-EQUAL FROM-PART "LOD" :END1 3)
	 (WITH-DISK-RQB (RQB 1)
	   (LET ((BUF (RQB-BUFFER RQB)))
	     (DISK-READ RQB FROM-UNIT (1+ FROM-PART-BASE))
	     (LET ((SIZE (SYS-COM-PAGE-NUMBER BUF %SYS-COM-VALID-SIZE)))
	       (AND (> SIZE #o10)
		    ( SIZE FROM-PART-SIZE)
		    SIZE)))))
	((OR (STRING-EQUAL FROM-PART "LMC" :END1 3)
	     (STRING-EQUAL FROM-PART "MCR" :END1 3))
	 (WITH-DISK-RQB (RQB)
	   (LET ((LMC-FORMATP (STRING-EQUAL FROM-PART "LMC" :END1 3))
		 (I-MEM-LOCATIONS 0)
		 (MAIN-MEMORY-PAGES 0)
		 (A//M-LOCATIONS 0)
		 (MID-LOCATIONS 0)
		 (MEASURED-SIZE 0)
		 (DISK-WORD-ADDRESS (* PAGE-SIZE FROM-PART-BASE))
		 (M-B-section-type)
		 (M-C-initial-address)
		 (M-D-n-locs))
	     (DO-FOREVER
	       (SETQ M-B-section-type (READ-lmc-word disk-WORD-address RQB FROM-unit LMC-FORMATP))
	       (SETQ M-C-initial-address (READ-LMC-WORD (1+ disk-WORD-address) RQB FROM-unit LMC-FORMATP))
	       (SETQ M-D-n-locs (READ-lmc-word (+ 2 disk-WORD-address) RQB FROM-unit LMC-FORMATP))
	       (ECASE M-B-section-type
		 (1			   ;I-MEM
		  (setq I-MEM-LOCATIONS M-D-n-locs	
			DISK-WORD-ADDRESS (+ DISK-WORD-ADDRESS 3 (* 2 M-D-n-locs))))
		 (2			   ;D--MEM obsolete
		  nil)
		 (3			   ;MAIN MEMORY
		  (setq MAIN-MEMORY-PAGES M-C-initial-address	
			DISK-WORD-ADDRESS (+ DISK-WORD-ADDRESS 3 1)
			MEASURED-SIZE (+ M-C-initial-address M-D-n-locs)))
		 (4			   ;A and M Memory
		  (setq A//M-LOCATIONS M-D-n-locs	
			DISK-WORD-ADDRESS (+ 3 DISK-WORD-ADDRESS M-D-n-locs))
		  (return (VALUES MEASURED-SIZE I-MEM-LOCATIONS A//M-LOCATIONS MID-LOCATIONS
				  MAIN-MEMORY-PAGES)))
		 (5			   ;Macro-IR-Decode
		  (setq MID-LOCATIONS M-D-n-locs
			DISK-WORD-ADDRESS (+ 3 M-D-n-locs DISK-WORD-ADDRESS))))))))))


(DEFUN READ-LMC-WORD (WORD-ADDRESS RQB UNIT LMC-FORMATP)
  (multiple-value-bind (page offset)
      (floor word-address page-size)
    (DISK-READ rqb UNIT page)
    (COND (LMC-FORMATP
	   (dpb (aref (rqb-buffer RQB) (1+ (* offset 2)))
		#o2020
		(aref (rqb-buffer RQB) (* offset 2))))
	  ('ELSE
	   (dpb (aref (rqb-buffer RQB) (* offset 2))
		#o2020
		(aref (rqb-buffer RQB) (1+ (* offset 2))))))))


(DEFUN NEW-COPY-DISK-PARTITION (FROM-UNIT FROM-PART TO-UNIT TO-PART &OPTIONAL &KEY
				(PAGES-AT-A-TIME (min page-rqb-size 85.))
				(VERBOSE T)
				(STARTING-HUNDRED 0)
				(WHOLE-THING-P NIL)
				(DELAY NIL)
				(SEQUENTIAL-OPTIMIZE T)
				(COMPARE NIL))
  "Copy partition FROM-PART on FROM-UNIT to partition TO-PART on TO-UNIT."
  (WITH-DECODED-DISK-UNIT (FROM-UNIT FROM-UNIT (FORMAT NIL "reading ~A partition" FROM-PART))
    (WITH-DECODED-DISK-UNIT (TO-UNIT TO-UNIT
				     (FORMAT NIL "~:[writing~;reading~] ~A partition"
					     COMPARE
					     TO-PART) NIL T)
      (MULTIPLE-VALUE-BIND (FROM-PART-BASE FROM-PART-SIZE NIL FROM-PART)
	  (FIND-DISK-PARTITION-FOR-READ FROM-PART NIL FROM-UNIT)
	(MULTIPLE-VALUE-BIND (TO-PART-BASE TO-PART-SIZE NIL TO-PART)
	    (IF COMPARE
		(FIND-DISK-PARTITION-FOR-READ TO-PART NIL TO-UNIT)
	      (FIND-DISK-PARTITION-FOR-WRITE TO-PART NIL TO-UNIT))
	  (LET ((PART-COMMENT (PARTITION-COMMENT FROM-PART FROM-UNIT)))
	    (IF VERBOSE (FORMAT T "~&Copying ~S" PART-COMMENT))
	    (LET ((MEASURED-FROM-PART-SIZE (AND (NOT WHOLE-THING-P)
						(MEASURED-FROM-PART-SIZE FROM-UNIT
									 FROM-PART
									 FROM-PART-BASE
									 FROM-PART-SIZE))))
	      (COND (MEASURED-FROM-PART-SIZE
		     (FORMAT T "... using measured size of ~D blocks." MEASURED-FROM-PART-SIZE))
		    ('ELSE
		     (FORMAT T "... using total size of ~D blocks." FROM-PART-SIZE)))
	      (UNLESS (>= TO-PART-SIZE (OR MEASURED-FROM-PART-SIZE FROM-PART-SIZE))
		(CERROR "leave out last part"
			"Target partition is only ~D blocks long; ~D needed."
			TO-PART-SIZE (OR MEASURED-FROM-PART-SIZE FROM-PART-SIZE)))
	      (FORMAT T "~%")
	      (WHEN (NOT COMPARE)
		(UPDATE-PARTITION-COMMENT TO-PART "Incomplete Copy" TO-UNIT)
		(WHEN (AND (CLOSUREP TO-UNIT)
			   (FUNCALL TO-UNIT :HANDLES-LABEL))
		  ;; This is the magtape unit which is sequential
		  (FUNCALL TO-UNIT :PUT PART-COMMENT :COMMENT)
		  (FUNCALL TO-UNIT
			   :PUT
			   (OR MEASURED-FROM-PART-SIZE FROM-PART-SIZE)
			   :SIZE)))
	      (PROG1 (COPY-DISK-PORTION
		       FROM-UNIT
		       FROM-PART-BASE
		       (+ FROM-PART-BASE (OR MEASURED-FROM-PART-SIZE
					     FROM-PART-SIZE))
		       TO-UNIT
		       TO-PART-BASE
		       (+ TO-PART-BASE TO-PART-SIZE)
		       :OFFSET (* 100. STARTING-HUNDRED)
		       :PAGES-AT-A-TIME PAGES-AT-A-TIME
		       :COMPARE COMPARE
		       :DELAY DELAY
		       :VERBOSE VERBOSE
		       :SEQUENTIAL-OPTIMIZE (AND SEQUENTIAL-OPTIMIZE
						 (NOT (MAGTAPE-UNIT-P FROM-UNIT))
						 (NOT (MAGTAPE-UNIT-P TO-UNIT))))
		     (WHEN (NOT COMPARE)
		       (UPDATE-PARTITION-COMMENT TO-PART PART-COMMENT TO-UNIT))))))))))


(DEFUN COPY-DISK-PORTION (FROM-UNIT FROM-START FROM-END TO-UNIT TO-START TO-END &OPTIONAL &KEY
			  (OFFSET 0) (PAGES-AT-A-TIME 1) DELAY (VERBOSE T)
			  (SEQUENTIAL-OPTIMIZE T) COMPARE)
  (LET ((N-BIG-OPS (MIN (FLOOR (- FROM-END FROM-START OFFSET) PAGES-AT-A-TIME)
			(FLOOR (- TO-END TO-START OFFSET) PAGES-AT-A-TIME)))
	(RQB)(RQB2)
	(COMPARE-OK T)
	(HPRINTER (MAKE-SEQUENCE-PRINTER 100))
	(TIME (TIME)))
    (WHEN SEQUENTIAL-OPTIMIZE
      (ANTICIPATE-DISK-OPERATIONS FROM-UNIT :READ FROM-START N-BIG-OPS PAGES-AT-A-TIME)
      (ANTICIPATE-DISK-OPERATIONS TO-UNIT (IF COMPARE :READ :WRITE)
				  TO-START N-BIG-OPS PAGES-AT-A-TIME))
    (UNWIND-PROTECT
	(DO ((FROM-INDEX (+ FROM-START OFFSET))
	     (TO-INDEX (+ TO-START OFFSET))
	     (AMT))
	    ((OR (>= FROM-INDEX FROM-END)
		 (>= TO-INDEX TO-END)
		 (< FROM-INDEX FROM-START)
		 (< TO-INDEX TO-START))
	     (SETQ TIME (TIME-DIFFERENCE (TIME) TIME))
	     (IF VERBOSE
		 (FORMAT T "~&Took ~$ minutes realtime, ~$ Kbytes per second~%"
			 (QUOTIENT TIME (* 60.0 60.0))
			 (QUOTIENT (* (- FROM-END FROM-START OFFSET)
				      1024
				      60.0)
				   (* 1000 TIME))))
	     COMPARE-OK)
	  (SETQ AMT (MIN (- FROM-END FROM-INDEX) (- TO-END TO-INDEX) PAGES-AT-A-TIME))
	  (WHEN (NOT (AND RQB (= AMT PAGES-AT-A-TIME)))
	    (RETURN-DISK-RQB RQB)
	    (SETQ RQB (GET-DISK-RQB AMT)))
	  (WHEN (AND COMPARE (NOT (AND RQB2 (= AMT PAGES-AT-A-TIME))))
	    (RETURN-DISK-RQB RQB2)
	    (SETQ RQB2 (GET-DISK-RQB AMT)))
	  (condition-case ()
	      (disk-read rqb from-unit FROM-INDEX)
	    ((fs:end-of-tape si:end-of-file-1)
	     (LET ((BACKUP
		     (- FROM-INDEX
			(COPY-DISK-PORTION-NEXT-READ-TAPE-VOLUME FROM-UNIT
								 FROM-INDEX))))
	       (DECF FROM-INDEX BACKUP)
	       (DECF TO-INDEX BACKUP))
	     (GO RETRY-READ-WRITE)))
	  (CONDITION-CASE ()
	      (IF COMPARE
		  (DISK-READ RQB2 TO-UNIT TO-INDEX)
		(DISK-WRITE rqb to-unit TO-INDEX))
	    ((fs:end-of-tape si:end-of-file-1)
	     (LET ((BACKUP (- TO-INDEX
			      (FUNCALL (IF COMPARE
					   #'COPY-DISK-PORTION-NEXT-READ-TAPE-VOLUME
					 #'COPY-DISK-PORTION-NEXT-WRITE-TAPE-VOLUME)
				       TO-UNIT
				       TO-INDEX))))
	       (DECF FROM-INDEX BACKUP)
	       (DECF TO-INDEX BACKUP))
	     (GO RETRY-READ-WRITE)))
	  (WHEN COMPARE
	    (SETQ COMPARE-OK
		  (COMPARE-DISK-DATA RQB RQB2 AMT (- FROM-INDEX FROM-START))))
	  (WHEN VERBOSE (FUNCALL HPRINTER (- FROM-INDEX FROM-START)))
	  (IF DELAY
	      (PROCESS-SLEEP DELAY)
	    (PROCESS-ALLOW-SCHEDULE))
	  (INCF FROM-INDEX AMT)
	  (INCF TO-INDEX AMT)
	  RETRY-READ-WRITE)
      (AND RQB (RETURN-DISK-RQB RQB))
      (AND RQB2 (RETURN-DISK-RQB RQB2)))))


(DEFUN COMPARE-DISK-DATA (RQB1 RQB2 AMT BLOCK)
  (OR (LET ((ALPHABETIC-CASE-AFFECTS-STRING-COMPARISON T))
	(%STRING-EQUAL (RQB-8-BIT-BUFFER RQB1) 0
		       (RQB-8-BIT-BUFFER RQB2) 0
		       (* 1024 AMT)))
      (DO ((C 0 (1+ C))
	   (ERRS 0)
	   (LIM (* 512 AMT))
	   (BUF1 (RQB-BUFFER RQB1))
	   (BUF2 (RQB-BUFFER RQB2)))
	  ((OR (= C LIM) (= ERRS 3))
	   NIL)
	(WHEN (NOT (= (AREF BUF1 C) (AREF BUF2 C)))
	  (FORMAT T "~%ERROR in Block ~D. Halfword ~D., S1: ~O S2: ~O "
		  (+ BLOCK (FLOOR C 512))
		  (REMAINDER C 512)
		  (AREF BUF C)
		  (AREF BUF2 C))
	  (INCF ERRS)))))



(DEFUN MAKE-SEQUENCE-PRINTER (&OPTIONAL (N 1) &AUX LAST)
  #'(LAMBDA (I)
      (LET ((NEW (FLOOR I N)))
	(COND ((ZEROP NEW))
	      ((AND LAST (= NEW LAST)))
	      ('ELSE
	       (FORMAT T "~D " NEW)
	       (SETQ LAST NEW))))))


(DEFUN ANTICIPATE-DISK-OPERATIONS (UNIT OPERATION ADDRESS N-OPERATIONS RQB-NPAGES)
  (AND (INSTANCEP UNIT)
       (SEND-IF-HANDLES UNIT :ANTICIPATE-OPERATIONS OPERATION ADDRESS N-OPERATIONS RQB-NPAGES)))


;;; This next-volume crap is implementated as gross violation of modularity.
;;; DG has implemented new stuff of course, and reimplemented the old FS:MAKE-MT-STREAM
;;; for compatibility, which keeps crufty old code like the FS:BAND-MAGTAPE-HANDLER
;;; working like a charm. A new magtape disk unit implementation is called for of course.

(defun MT-UNIT-MAGTAPE-UNIT (from-unit &AUX WIN)
  ;; a gross violation of modularity.
  (IF (AND (CLOSUREP FROM-UNIT)
	   (EQ (CLOSURE-FUNCTION FROM-UNIT) 'FS:BAND-MAGTAPE-HANDLER)
	   (SETQ WIN (SYMEVAL-IN-CLOSURE FROM-UNIT 'FS:*BAND-STREAM*)))
      (OR (SEND-IF-HANDLES WIN :UNIT) ;; THE OLD THING
	  (SEND (SEND WIN :DEVICE) :UNIT))
    (FERROR NIL "not an open magtape unit: ~S" from-unit)))
      
(DEFUN COPY-DISK-PORTION-NEXT-READ-TAPE-VOLUME (UNIT EXPECT-INDEX)
  (FS:MT-MAYBE-UNLOAD (MT-UNIT-MAGTAPE-UNIT UNIT))
  (fquery '(:choices (((resume "Resume") #\resume #\space #/y))
		     :list-choices nil)
	  "End of tape reached ... mount next tape and type RESUME ")
  (let ((NEXT-INDEX (funcall unit ':prepare-next-volume-for-read)))
    (OR (<= NEXT-INDEX EXPECT-INDEX)
	(cerror "continue anyway"
		"The last tape ended at block ~d., but this tape starts at ~d."
		EXPECT-INDEX
		NEXT-INDEX))
    NEXT-INDEX))


(defun COPY-DISK-PORTION-NEXT-WRITE-TAPE-VOLUME (UNIT INDEX &AUX (BACKUP 10))
  (FS:MT-SPACE-REV 3 (MT-UNIT-MAGTAPE-UNIT UNIT))
  (fs:mt-write-eof (MT-UNIT-MAGTAPE-UNIT UNIT))
  (fs:mt-write-eof (MT-UNIT-MAGTAPE-UNIT UNIT))
  (fs:mt-MAYBE-UNLOAD (MT-UNIT-MAGTAPE-UNIT UNIT))
  (fquery '(:choices (((resume "Resume") #\resume #\space #/y))
		     :list-choices nil)
	  "End of tape reached ... mount next tape and type RESUME ")
  (funcall UNIT ':prepare-next-volume-for-write (- INDEX BACKUP))
  (format t "~&Continuing at partition address ~d.~&" (- INDEX BACKUP))
  (- INDEX BACKUP))

(DEFVAR *REWIND-INSTEAD-OF-UNLOAD* T)

(DEFUN FS:MT-MAYBE-UNLOAD (X)
  (IF *REWIND-INSTEAD-OF-UNLOAD*
      (FS:MT-REWIND X)
    (FS:MT-UNLOAD X)))

