(FILECREATED " 5-Mar-84 19:26:39" {PHYLUM}<STANSBURY>RELEASE>MOD44IO.;1 89330  

      changes to:  (FNS \VANILLADISKINIT)

      previous date: "24-Jan-84 11:03:43" {PHYLUM}<LISP>SOURCES>MOD44IO.;2)


(* Copyright (c) 1981, 1982, 1983, 1984 by Xerox Corporation)

(PRETTYCOMPRINT MOD44IOCOMS)

(RPAQQ MOD44IOCOMS ((* Device dependent code for the Model44 disk)
	(FNS \M44AddDiskPages \M44AllocFilePageMap \M44CloseFile \M44CompleteFH \M44CREATEFILE 
	     \M44DeleteFile \M44EVENTFN \M44ExtendFilePageMap \M44FillInMap \M44GENERATEFILES 
	     \M44GetAccessTime \M44GetFileHandle \M44GetFileInfo \M44GetFileName \M44GetPageLoc 
	     \M44KillFilePageMap \M44MAKEDIRENTRY \M44NEXTFILEFN \M44OpenFile \M44OPENFILEFROMFP 
	     \M44ReadDiskPage \M44ReadLeaderPage \M44ReadPages \M44ReleasePages \M44SetAccessTimes 
	     \M44SetEndOfFile \M44SetFileInfo \M44TruncateFile \M44WriteDiskPage \M44WriteLeaderPage 
	     \M44WritePages)
	(FNS \ADDDISKPAGES \GETPAGEHINT \M44DELETEPAGES \ASSIGNDISKPAGE \COUNTDISKFREEPAGES 
	     \M44MARKPAGEFREE \M44FLUSHDISKDESCRIPTOR \MAKELEADERDAS \CREATE.FID.FOR.DD \OPENDISK 
	     DISKFREEPAGES VMEMSIZE)
	(DECLARE: DONTCOPY (MACROS UCASECHAR UPDATEVALIDATION)
		  (RECORDS M44DEVICE)
		  (CONSTANTS (PageMapIncrement 64)
			     (NameFirstCharPos 13)))
	[GLOBALRESOURCES (\M44PAGEBUFFER (NCREATE (QUOTE VMEMPAGEP]
	(COMS (* Directory lookup routines)
	      (FNS \FILESPEC \FINDDIRHOLE \LISPFILENAME \LOOKUPVERSIONS \OPENDISKDESCRIPTOR 
		   \READDIRFPTR \SEARCHDIR1 \UNPACKFILENAME \WRITEDIRFPTR)
	      (FNS ALTOFILENAME)
	      (VARS \FILENAMECHARSLST)
	      (GLOBALVARS \FILENAMECHARSLST)
	      (DECLARE: DONTCOPY (RECORDS UNAME FILESPEC M44GENFILESTATE M44DIRSEARCHSTATE)
			(MACROS BETWEEN)))
	[COMS (FNS \VANILLADISKINIT \OPENDISKDEVICE \OPENDIR \M44CHECKPASSWORD \M44HOSTNAMEP)
	      (DECLARE: DONTCOPY (CONSTANTS \OFFSET.BCPLUSERNAME \OFFSET.BCPLPASSWORD 
					    \NWORDS.BCPLPASSWORD))
	      (DECLARE: DONTEVAL@LOAD DOCOPY (P (\VANILLADISKINIT)
						(OR (BOUNDP (QUOTE \CONNECTED.DIR))
						    (CNDIR]
	(COMS (* SYSOUT etc)
	      (FNS \COPYSYS \COPYSYS1))
	(COMS (* Stats code. On MOD44IO because it writes on the disk and uses records not exported 
		 from MOD44IO)
	      (FNS GATHERSTATS)
	      (VARS (\STATSON NIL)))
	(DECLARE: EVAL@COMPILE DONTCOPY (LOCALVARS . T)
		  (FILES (LOADCOMP)
			 LLBFS))))



(* Device dependent code for the Model44 disk)

(DEFINEQ

(\M44AddDiskPages
  [LAMBDA (STREAM NEWLASTPAGE NEWLASTBYTE)                   (* bvm: "29-DEC-82 17:49")

          (* Add pages to an existing Model44 file. NEWLASTPAGE is the page number of the last page in the extended file.
	  Return the disk address of the new last page.)


    (\M44FillInMap STREAM (fetch LastPage of STREAM))        (* Fill in map to end of file.
							     Code below assumes at least one valid map entry)
    (\ADDDISKPAGES STREAM (ADD1 (fetch LASTMAPPEDPAGE of STREAM))
		   (IDIFFERENCE NEWLASTPAGE (fetch LASTMAPPEDPAGE of STREAM))
		   (fetch (ARRAYP BASE) of (\M44ExtendFilePageMap STREAM NEWLASTPAGE))
		   NEWLASTBYTE)
    (replace LASTMAPPEDPAGE of STREAM with NEWLASTPAGE)
    (replace LastPage of STREAM with NEWLASTPAGE)
    (replace LastOffset of STREAM with NEWLASTBYTE)          (* record new eof in filehandle only)
    NEWLASTPAGE])

(\M44AllocFilePageMap
  [LAMBDA (STREAM LASTPAGE)                                  (* bvm: "20-OCT-82 16:46")

          (* Allocate a page map array large enough to map the given page and store the new map into the given file handle.
	  Return the new array.)


    (replace FILEPAGEMAP of STREAM with (ARRAY (CEIL (IPLUS LASTPAGE 4)
						     PageMapIncrement)
					       (QUOTE SMALLPOSP)
					       \FILLINDA 0])

(\M44CloseFile
  [LAMBDA (STREAM)                                           (* bvm: "12-NOV-83 16:26")
    (RELEASECPAGE STREAM)                                    (* Let the current page go)
    (\CLEARMAP STREAM)
    [COND
      ((NEQ (fetch ACCESS of STREAM)
	    (QUOTE INPUT))                                   (* Update EOF in leader page)
	(\M44TruncateFile STREAM (fetch EPAGE of STREAM)
			  (fetch EOFFSET of STREAM)
			  T)
	(\M44FLUSHDISKDESCRIPTOR (fetch DEVICE of STREAM]
    STREAM])

(\M44CompleteFH
  [LAMBDA (STREAM SCRBUFF)                                   (* bvm: "25-MAY-83 12:03")

          (* Completes the fields of a file handle that describes an existing file by reading in its leader page which it 
	  leaves for its caller)


    [PROG ((NUMCHARS (CONS))
	   (BUF (\M44ReadLeaderPage STREAM SCRBUFF))
	   (DSK (fetch DSKOBJ of (fetch DEVICE of STREAM)))
	   LASTPAGE# NBYTES)

          (* Get the page number and the number of bytes on the last page of the file specified by fHandle.
	  If the last page number hint is wrong in the leader page, then find the real last page and change the hint.)


          (COND
	    ((AND (NEQ (SETQ LASTPAGE# (SUB1 (fetch LastPageNumber of BUF)))
		       -1)
		  (EQ [PROG ((DAs (ARRAY 3 (QUOTE WORD)
					 \FILLINDA 0))
			     (BFSPG# (ADD1 LASTPAGE#)))
			    (SETA DAs 1 (fetch LastPageAddress of BUF))
			    (SETA DAs 2 \EOFDA)
			    (RETURN (AND (EQ (\ACTONDISKPAGES DSK NIL (fetch (ARRAYP BASE)
									 of DAs)
							      LASTPAGE# STREAM BFSPG# BFSPG# 
							      \DC.READD NUMCHARS NIL T)
					     BFSPG#)
					 (SETQ NBYTES (CAR NUMCHARS]
		      (fetch LastPageByteCount of BUF)))
	      (replace LastPage of STREAM with LASTPAGE#)    (* Update STREAM eof)
	      (replace LastOffset of STREAM with NBYTES))
	    (T                                               (* Hint was wrong so scan the file for last page)
	       (for PN from PageMapIncrement by PageMapIncrement
		  do (SETQ LASTPAGE# (\M44FillInMap STREAM PN)) 
                                                             (* Wait until attempt to find page fails)
		  repeatwhile (EQ PN LASTPAGE#))
	       (SETQ NBYTES (\ACTONDISKPAGES DSK NIL (fetch (ARRAYP BASE)
							of (fetch FILEPAGEMAP of STREAM))
					     -1 STREAM (ADD1 LASTPAGE#)
					     (ADD1 LASTPAGE#)
					     \DC.READD NUMCHARS))
	       (\M44SetEndOfFile STREAM LASTPAGE# (CAR NUMCHARS)
				 BUF)))
          (UPDATEVALIDATION STREAM BUF)                      (* Validation is low order bits of creation and write 
							     dates)
          (COND
	    ((EQ (fetch LastOffset of STREAM)
		 BYTESPERPAGE)                               (* Shouldn't happen, because alto files should never 
							     have a full last page. However, cope if it happens...)
	      (replace EPAGE of STREAM with (ADD1 (fetch LastPage of STREAM)))
	      (replace EOFFSET of STREAM with 0))
	    (T (replace EPAGE of STREAM with (fetch LastPage of STREAM))
	       (replace EOFFSET of STREAM with (fetch LastOffset of STREAM]
    STREAM])

(\M44CREATEFILE
  [LAMBDA (FDEV UNAME SCRBUFF DIRECTORYP)                    (* bvm: " 8-DEC-82 17:32")
                                                             (* Create a file on the Model44 disk.)
    (PROG ((DSK (fetch DSKOBJ of FDEV))
	   (PNAME (\LISPFILENAME UNAME))
	   NC STREAM FP MAP FPBASE DAT)
          (OR PNAME (RETURN))                                (* Cant create as name wasnt complete)
          (OR SCRBUFF (HELP "No buffer for createfile"))
          (SETQ STREAM (create M44STREAM))
          (replace FULLFILENAME of STREAM with PNAME)
          (replace DEVICE of STREAM with FDEV)
          (replace FID of STREAM with (SETQ FP (create FID)))
          (replace FILEPAGEMAP of STREAM with (SETQ MAP (ARRAY PageMapIncrement (QUOTE WORD)
							       \FILLINDA 0)))
          (replace LASTMAPPEDPAGE of STREAM with 0)
          (SETQ FPBASE (fetch (ARRAYP BASE) of FP))
          (replace FPSERIAL# of FPBASE with (add (fetch (DSKOBJ DISKLASTSERIAL#) of DSK)
						 1))
          (COND
	    (DIRECTORYP (add (fetch FPSERIALHI of FPBASE)
			     \FP.DIRECTORYP)))
          (replace FPVERSION of FPBASE with 1)
          (SETA MAP 0 \EOFDA)
          (SETA MAP 3 \EOFDA)                                (* We are about to create pages 0 and 1, everything else
							     is nonexistent)
          (\ZEROPAGE (fetch (POINTER PAGE#) of SCRBUFF))
          (\BLT (LOCF (fetch TimeCreate of SCRBUFF))
		(SETQ DAT (\DAYTIME0 (CREATECELL \FIXP)))
		WORDSPERCELL)                                (* Set creation and write dates)
          (\BLT (LOCF (fetch TimeWrite of SCRBUFF))
		DAT WORDSPERCELL)
          (replace PropertyPtr of SCRBUFF with \INITPROPPTR)
          (SETQ SCRBUFF SCRBUFF)                             (* Get leader page to write the name in.
							     See \M44MAKEDIRENTRY for the name logic.)
          (for POS from NameFirstCharPos as C in (fetch CHARPAIRS of UNAME)
	     do (\PUTBASEBYTE SCRBUFF POS (UCASECHAR (CAR C)))
	     finally [COND
		       ((OR (CDR (fetch VERSION of UNAME))
			    (NEQ (CAR (fetch VERSION of UNAME))
				 (CHARCODE 1)))
			 (\PUTBASEBYTE SCRBUFF POS (CHARCODE !))
			 (for old POS from (ADD1 POS) as C in (fetch VERSION of UNAME)
			    do (\PUTBASEBYTE SCRBUFF POS C]
		     (\PUTBASEBYTE SCRBUFF POS (CHARCODE %.))
		     (OR (EVENP (add POS 1)
				BYTESPERWORD)
			 (\PUTBASEBYTE SCRBUFF POS 0))       (* Fill out the last word)
		     (SETQ NC (IDIFFERENCE POS NameFirstCharPos)))
          (replace NameCharCount of SCRBUFF with NC)         (* The end of file will be zero and the validation not 
							     set as befits a new file.)
          (\WRITEDISKPAGES DSK (LIST SCRBUFF NIL)
			   (fetch (ARRAYP BASE) of MAP)
			   -1 STREAM 0 1 NIL NIL 0 0)
          (replace FPLEADERVDA of FPBASE with (\WORDELT MAP 1))
          (replace DIRINFO of STREAM with (\M44MAKEDIRENTRY (fetch FID of STREAM)
							    UNAME NC FDEV))
          (RETURN STREAM])

(\M44DeleteFile
  [LAMBDA (FILENAME DEV)                                     (* bvm: " 3-JAN-83 17:44")
                                                             (* Delete a Model44 file.)
    (GLOBALRESOURCE \M44PAGEBUFFER
        (PROG ((STREAM (\M44GetFileHandle FILENAME (QUOTE OLDEST)
					  DEV NIL \M44PAGEBUFFER T)))
	      (COND
		((OR (NOT STREAM)
		     (bind (NAME ←(fetch FULLFILENAME of STREAM)) find ST in \OPENFILES
			suchthat (EQ (fetch FULLFILENAME of ST)
				     NAME)))                 (* Can't delete an open file)
		  (RETURN)))
	      (\M44DELETEPAGES STREAM -1)
	      (PROG ((DIROFD (fetch (M44DEVICE SYSDIROFD) of DEV)))
                                                             (* Delete directory entry)
		    (\SETFILEPTR DIROFD (fetch DIRINFO of STREAM))
		    (\BOUT DIROFD (LOGAND 3 (\PEEKBIN DIROFD)))
		    (FLUSHMAP DIROFD))
	      (\M44KillFilePageMap STREAM)
	      (replace FID of STREAM with NIL)
	      (RETURN (fetch FULLFILENAME of STREAM))))])

(\M44EVENTFN
  [LAMBDA (FDEV EVENT)                                       (* bvm: "29-Nov-83 14:56")
    (DECLARE (GLOBALVARS \OPENFILES))
    (SELECTQ EVENT
	     [(AFTERLOGOUT AFTERSYSOUT AFTERMAKESYS AFTERSAVEVM)
	       (SELECTQ (MACHINETYPE)
			(DANDELION (\REMOVEDEVICE FDEV))
			(COND
			  ((AND (NEQ (fetch DEVICENAME of FDEV)
				     (QUOTE DSK))
				(for STREAM in \OPENFILES never (EQ (fetch DEVICE of STREAM)
								    FDEV)))
			    (\REMOVEDEVICE FDEV))
			  (T (PROG (DD)
			           (COND
				     ((AND (NOT (fetch (M44DEVICE DDVALID) of FDEV))
					   (SETQ DD (fetch (M44DEVICE DISKDESCRIPTOROFD)
						       of FDEV)))
                                                             (* Flush out of date disk descriptor, too)
				       (FORGETPAGES DD)
				       (replace (M44DEVICE DISKDESCRIPTOROFD) of FDEV with NIL)))
			           (FORGETPAGES (fetch (M44DEVICE SYSDIROFD) of FDEV))
			           (\OPENDIR FDEV]
	     (BEFORELOGOUT (\FLUSH.OPEN.STREAMS FDEV)
			   (\M44FLUSHDISKDESCRIPTOR FDEV))
	     NIL])

(\M44ExtendFilePageMap
  [LAMBDA (STREAM TOPAGE#)                                   (* bvm: "21-OCT-82 18:27")

          (* If the file's page map is not big enough to map the given page, then create a new one that is big enough and 
	  copy the old OLDMAP information into the new map. If the file has no map, then create one big enough to map the 
	  given page. Return the new map. -
	  Map entry 0 corresponds to bfs page -1, entry 1 corresponds to the leader page, and entry 2 corresponds to Lisp 
	  page 0)


    (PROG ((OLDMAP (fetch FILEPAGEMAP of STREAM))
	   NEWMAP)
          (RETURN (COND
		    ((NOT OLDMAP)
		      (SETQ NEWMAP (\M44AllocFilePageMap STREAM TOPAGE#))
                                                             (* Initialize with leader page hint)
		      (SETA NEWMAP 0 \EOFDA)
		      [SETA NEWMAP 1 (fetch FPLEADERVDA of (fetch (ARRAYP BASE)
							      of (fetch FID of STREAM]
		      NEWMAP)
		    ((ILESSP (IPLUS TOPAGE# 3)
			     (ARRAYSIZE OLDMAP))
		      OLDMAP)
		    (T (SETQ NEWMAP (\M44AllocFilePageMap STREAM TOPAGE#))
		       (for i from 0 to (SUB1 (ARRAYSIZE OLDMAP)) do (SETA NEWMAP i (\WORDELT OLDMAP 
											      i)))
		       NEWMAP])

(\M44FillInMap
  [LAMBDA (STREAM UPTOPAGE)                                  (* bvm: "29-OCT-82 13:16")
    (PROG ((MAP (\M44ExtendFilePageMap STREAM UPTOPAGE)))    (* Extend MAP)
          (RETURN (add (fetch LASTMAPPEDPAGE of STREAM)
		       (\GETPAGEHINT STREAM (fetch LASTMAPPEDPAGE of STREAM)
				     (IDIFFERENCE UPTOPAGE (fetch LASTMAPPEDPAGE of STREAM))
				     (fetch (ARRAYP BASE) of MAP])

(\M44GENERATEFILES
  [LAMBDA (FDEV PATTERN)                                     (* bvm: " 5-APR-83 17:53")

          (* Returns a file-generator object that will generate AT LEAST all files in the sys-dir of FDEV whose names match 
	  PATTERN. Clients might need to provide additional filtering. For M44, the generate state consists of the HOSTNAME 
	  (DSK) followed by a "search state", a directory pointer and a character list of the sort that \SEARCHDIR1 expects.
	  DIRPTR is the position of the next file to be considered in the directory.)


    (PROG ((DIROFD (fetch (M44DEVICE SYSDIROFD) of FDEV))
	   (HOSTNAME (FILENAMEFIELD PATTERN (QUOTE HOST)))
	   CHARLIST)
          [OR DIROFD (RETURN (create FILEGENOBJ
				     NEXTFILEFN ←(FUNCTION NILL]
          (SETQ PATTERN (PACKFILENAME (QUOTE HOST)
				      NIL
				      (QUOTE BODY)
				      PATTERN))
          [SETQ CHARLIST (for C inatom PATTERN as I from 1
			    collect (COND
				      [(AND (IGEQ C (CHARCODE a))
					    (ILEQ C (CHARCODE z)))
					(LIST C (IPLUS (IDIFFERENCE C (CHARCODE a))
						       (CHARCODE A]
				      [(AND (IGEQ C (CHARCODE A))
					    (ILEQ C (CHARCODE Z)))
					(LIST C (IPLUS (IDIFFERENCE C (CHARCODE A))
						       (CHARCODE a]
				      ((FMEMB C (CHARCODE (ESCAPE * ; ?)))

          (* \SEARCHDIR1 currently only checks prefixes, so we truncate at the first * or escape. Also ignore version 
	  specifications, because of the alternative representations of version 1)


					(RETURN $$VAL))
				      ([AND (EQ C (CHARCODE %.))
					    (FMEMB (NTHCHARCODE PATTERN (ADD1 I))
						   (CHARCODE (ESCAPE * ? ; NIL]
                                                             (* We don't require a dot match unless it separates a 
							     real extension. The client must filter for various 
							     combinations of version numbers.)
					(RETURN $$VAL))
				      (T (LIST C]
          (RETURN (create FILEGENOBJ
			  NEXTFILEFN ←(FUNCTION \M44NEXTFILEFN)
			  GENFILESTATE ←(create M44GENFILESTATE
						DIROFD ← DIROFD
						HOSTNAME ← HOSTNAME
						SEARCHSTATE ←(create M44DIRSEARCHSTATE
								     DIRPTR ← 0
								     CHARLIST ← CHARLIST])

(\M44GetAccessTime
  [LAMBDA (STREAM ACCESS SCRBUFF)                            (* bvm: "28-OCT-82 11:14")

          (* Return either the last write date, last read date, or creation date for the file specified by the file handle 
	  in the form of an integer. SCRBUFF is a scratch buffer for reading the leader page. ACCESS is assumed to be WRITE,
	  READ, or CREATE)


    (\M44ReadLeaderPage STREAM SCRBUFF)
    (SELECTQ ACCESS
	     (CREATE (fetch TimeCreate of SCRBUFF))
	     (READ (fetch TimeRead of SCRBUFF))
	     (WRITE (fetch TimeWrite of SCRBUFF))
	     (SHOULDNT])

(\M44GetFileHandle
  [LAMBDA (NAME RECOG FDEV CREATEFLG SCRBUFF FAST)           (* bvm: "14-JUN-83 16:03")

          (* Creates a STREAM for dsk file NAME, creating it if necessary when CREATEFLG is true. SCRBUFF is a scratch 
	  buffer for manipluating the leader page. If FAST is true, does not fill in any fields of STREAM that would require
	  reading the file, e.g., the length and full map)


    (PROG ((DIROFD (fetch (M44DEVICE SYSDIROFD) of FDEV))
	   FS STREAM)
          (RETURN (COND
		    ((NULL DIROFD)                           (* Non-existent device)
		      NIL)
		    ((EQ (fetch FSDIRPTR of (SETQ FS (\FILESPEC NAME RECOG DIROFD)))
			 (QUOTE DIRECTORY))                  (* directory name was given.)
		      NIL)
		    ((fetch FSDIRPTR of FS)
		      (\SETFILEPTR DIROFD (IPLUS 2 (fetch FSDIRPTR of FS)))
		      (SETQ STREAM (create M44STREAM))
		      (replace FULLFILENAME of STREAM with (fetch PNAME of FS))
		      (replace DEVICE of STREAM with FDEV)
		      (replace FID of STREAM with (\READDIRFPTR DIROFD))
		      (replace DIRINFO of STREAM with (fetch FSDIRPTR of FS))
		      (OR FAST (\M44CompleteFH STREAM SCRBUFF))
		      STREAM)
		    ((NULL (fetch UNAME of FS))
		      (LISPERROR "BAD FILE NAME" NAME))
		    (CREATEFLG (COND
				 ((AND (FIXP CREATEFLG)
				       (IGREATERP CREATEFLG (fetch (M44DEVICE DISKFREEPAGES)
							       of FDEV)))
				   (LISPERROR "FILE SYSTEM RESOURCES EXCEEDED" NAME))
				 (T (\M44CREATEFILE FDEV (fetch UNAME of FS)
						    SCRBUFF])

(\M44GetFileInfo
  [LAMBDA (STREAM ATTRIBUTE DEV)                             (* bvm: "28-OCT-82 10:58")
                                                             (* Get the value of the ATTRIBUTE for a model44 file.
							     If STREAM is a filename, then the file is not open.)
    (GLOBALRESOURCE \M44PAGEBUFFER
        (COND
	  ((OR (type? STREAM STREAM)
	       (SETQ STREAM (\M44GetFileHandle STREAM (QUOTE OLD)
					       DEV NIL \M44PAGEBUFFER T)))
	    (SELECTQ ATTRIBUTE
		     (LENGTH (COND
			       ((NULL (fetch VALIDATION of STREAM))
                                                             (* Need to read leader page etc to get length)
				 (\M44CompleteFH STREAM \M44PAGEBUFFER)))
			     (create BYTEPTR
				     PAGE ←(fetch EPAGE of STREAM)
				     OFFSET ←(fetch EOFFSET of STREAM)))
		     [WRITEDATE (GDATE (ALTO.TO.LISP.DATE (\M44GetAccessTime STREAM (QUOTE WRITE)
									     \M44PAGEBUFFER]
		     [READDATE (GDATE (ALTO.TO.LISP.DATE (\M44GetAccessTime STREAM (QUOTE READ)
									    \M44PAGEBUFFER]
		     [CREATIONDATE (GDATE (ALTO.TO.LISP.DATE (\M44GetAccessTime STREAM (QUOTE CREATE)
										\M44PAGEBUFFER]
		     (IWRITEDATE (ALTO.TO.LISP.DATE (\M44GetAccessTime STREAM (QUOTE WRITE)
								       \M44PAGEBUFFER)))
		     (IREADDATE (ALTO.TO.LISP.DATE (\M44GetAccessTime STREAM (QUOTE READ)
								      \M44PAGEBUFFER)))
		     (ICREATIONDATE (ALTO.TO.LISP.DATE (\M44GetAccessTime STREAM (QUOTE CREATE)
									  \M44PAGEBUFFER)))
		     NIL))))])

(\M44GetFileName
  [LAMBDA (NAME RECOG FDEV)                                  (* bvm: "19-NOV-82 17:02")
    (fetch PNAME of (\FILESPEC NAME RECOG (fetch (M44DEVICE SYSDIROFD) of FDEV])

(\M44GetPageLoc
  [LAMBDA (STREAM PAGENO CREATE?)                            (* bvm: "29-DEC-82 17:50")

          (* Look in the file's page map to find the disk address of the page. If the map does not include the page, then 
	  extend it appropriately. If page does not exit, create it if CREATE? is true, else return \EOFDA)


    (COND
      ((ILEQ PAGENO (fetch LastPage of STREAM))
	(COND
	  ((IGREATERP PAGENO (fetch LASTMAPPEDPAGE of STREAM))
	    (\M44FillInMap STREAM PAGENO)))
	(\WORDELT (fetch FILEPAGEMAP of STREAM)
		  (IPLUS PAGENO 2)))
      (CREATE? (\M44AddDiskPages STREAM PAGENO 0)
	       (\M44GetPageLoc STREAM PAGENO))
      (T \EOFDA])

(\M44KillFilePageMap
  [LAMBDA (fHandle)                                         (* bas: " 7-JAN-80 19:43")
                                                            (* Remove the file's page map.)
    (replace FILEPAGEMAP of fHandle with NIL)
    (replace LASTMAPPEDPAGE of fHandle with -1])

(\M44MAKEDIRENTRY
  [LAMBDA (FID UNAME NC FDEV)                                (* bvm: "19-NOV-82 17:04")
                                                             (* Makes a directory entry for a new file)
    (PROG (POS (DIROFD (fetch (M44DEVICE SYSDIROFD) of FDEV)))
          (SETQ POS (\FINDDIRHOLE (LRSH (IPLUS NC 16Q)
					1)
				  DIROFD))
          (\WRITEDIRFPTR DIROFD FID)
          (\BOUT DIROFD NC)

          (* Now write out the alto-style name "name[.ext]!ver." with ver omitted if 1; This is basically the same logic as 
	  is used to write the name in the leader page in \M44CREATEFILE. We can't share cause here we do bouts, cause we 
	  might run over a page; there we must do PUTBASEBYTE's cause we can't set the fileptr to the leader page.)


          [for C in (fetch CHARPAIRS of UNAME) do (\BOUT DIROFD (UCASECHAR (CAR C]
          [COND
	    ([OR (CDR (fetch VERSION of UNAME))
		 (NEQ (CHARCODE 1)
		      (CAR (fetch VERSION of UNAME]
	      (\BOUT DIROFD (CHARCODE !))
	      (for C in (fetch VERSION of UNAME) do (\BOUT DIROFD C]
          (\BOUT DIROFD (CHARCODE %.))
          (COND
	    ((EVENP NC BYTESPERWORD)
	      (\BOUT DIROFD 0)))
          (\SETFILEPTR DIROFD POS)
          (\BOUT DIROFD (LOGOR 4 (\PEEKBIN DIROFD)))         (* When everything is ready, finally change the type 
							     from hole to file.)
          (FLUSHMAP DIROFD)
          (RETURN POS])

(\M44NEXTFILEFN
  [LAMBDA (GENFILESTATE SCRATCHLIST NOVERSION HOST/DIR)     (* rmk: "16-JUL-81 17:41")

          (* GENFILESTATE is the state information from the file-generator object created by \M44GENERATEFILES.
	  This function returns the list of character codes of the next file generated by the generator, smashing them into 
	  SCRATCHLIST. Returns NIL if no files left. It updates GENFILESTATE so that it will get the following satisfactory 
	  file on the next call to this function. -
	  If NOVERSION, returns the filenames without the semi-colon and version number)


    (SCRATCHLIST SCRATCHLIST (PROG (TEMP LEN (DIROFD (fetch DIROFD of GENFILESTATE))
					 SAWDOT)
			           (COND
				     ((SETQ TEMP (\SEARCHDIR1 DIROFD (fetch SEARCHSTATE of 
										     GENFILESTATE)))
				       [COND
					 (HOST/DIR (ADDTOSCRATCHLIST (CHARCODE {))
						   (for C inatom (fetch HOSTNAME of GENFILESTATE)
						      do (ADDTOSCRATCHLIST C))
						   (ADDTOSCRATCHLIST (CHARCODE }]
				       [\SETFILEPTR DIROFD
						    (IDIFFERENCE (GETFILEPTR DIROFD)
								 (SETQ LEN
								   (LENGTH (fetch CHARLIST
									      of (fetch SEARCHSTATE
										    of GENFILESTATE]
                                                            (* Read all the characters from the directory)
				       (for I from 1 to (IPLUS TEMP (SUB1 LEN))
					  do                (* The SUB1 is because the last character is the 
							    undesired dot)
					     (COND
					       ((EQ (SETQ TEMP (\BIN DIROFD))
						    (CHARCODE !))
						 (COND
						   (NOVERSION (RETURN)))
						 (SETQ NOVERSION T)
						 (OR SAWDOT (ADDTOSCRATCHLIST (CHARCODE ".")))
						 (SETQ TEMP (CHARCODE ;)))
					       [(AND (IGEQ TEMP (CHARCODE a))
						     (ILEQ TEMP (CHARCODE z)))
						 (SETQ TEMP (IPLUS (IDIFFERENCE TEMP (CHARCODE a))
								   (CHARCODE A]
					       ((EQ TEMP (CHARCODE "."))
						 (SETQ SAWDOT T)))
					     (ADDTOSCRATCHLIST TEMP))
				       (COND
					 ((NOT NOVERSION)   (* No version found--insert ;1)
					   (OR SAWDOT (ADDTOSCRATCHLIST (CHARCODE %.)))
					   (ADDTOSCRATCHLIST (CHARCODE ;))
					   (ADDTOSCRATCHLIST (CHARCODE 1])

(\M44OpenFile
  [LAMBDA (NAME ACCESS RECOG OTHERINFO FDEV OLDSTREAM)       (* bvm: "14-JUN-83 16:29")
                                                             (* Open a Model44 file. Gets the physical end of file 
							     and sets up ofd)
    (GLOBALRESOURCE \M44PAGEBUFFER (PROG ([PAGESTIMATE
					    (AND (NEQ ACCESS (QUOTE INPUT))
						 (for X in OTHERINFO
						    when (EQ (CAR (LISTP X))
							     (QUOTE LENGTH))
						    do (RETURN (IPLUS 2 (FOLDHI (CADR X)
										BYTESPERPAGE]
					  STREAM)
				         (OR [SETQ STREAM
					       (COND
						 ((NOT (type? STREAM NAME))
						   (\M44GetFileHandle NAME RECOG FDEV
								      (AND (NEQ ACCESS (QUOTE INPUT))
									   (OR PAGESTIMATE T))
								      \M44PAGEBUFFER))
						 ((OR (fetch (M44DEVICE DSKPASSWORDOK)
							 of (fetch DEVICE of NAME))
						      (EQ (fetch W0 of (fetch FID of NAME))
							  32768))
                                                             (* Make sure password is ok if trying to reopen anything
							     but a directory)
						   (\M44CompleteFH NAME \M44PAGEBUFFER]
					     (RETURN NIL))
				         [COND
					   ([AND PAGESTIMATE (IGREATERP PAGESTIMATE
									(IPLUS (fetch (M44DEVICE
											DISKFREEPAGES)
										  of FDEV)
									       (fetch LastPage
										  of STREAM]
					     (RETURN (LISPERROR "FILE SYSTEM RESOURCES EXCEEDED" NAME]
				         [COND
					   ((EQ ACCESS (QUOTE OUTPUT))
                                                             (* File is EMPTY even if it is old)
					     (replace EPAGE of STREAM
						with (replace EOFFSET of STREAM with 0]
                                                             (* Leader page is read in during STREAM initialization)
				         (COND
					   ((AND (NOT OLDSTREAM)
						 (NOT (FMEMB (QUOTE DON'T.CHANGE.DATE)
							     OTHERINFO)))
                                                             (* For REOPENFILE op, don't change dates)
					     (\M44SetAccessTimes STREAM ACCESS \M44PAGEBUFFER)
                                                             (* Resets validation)
					     (\M44WriteLeaderPage STREAM \M44PAGEBUFFER)
                                                             (* We write out accumulated changes to leader page)
					     ))
				         (RETURN STREAM])

(\M44OPENFILEFROMFP
  [LAMBDA (DEV NAME ACCESS FID DIRINFO)                      (* bvm: " 1-NOV-82 15:13")
                                                             (* Opens a disk file given its FP)
    (PROG ((STREAM (create M44STREAM)))
          (replace FULLFILENAME of STREAM with (SETQ NAME (PACK* (QUOTE {)
								 (fetch DEVICENAME of DEV)
								 (QUOTE })
								 NAME)))
          (replace DEVICE of STREAM with DEV)
          (replace FID of STREAM with FID)
          (replace DIRINFO of STREAM with (OR DIRINFO -1))
          (RETURN (\OPENFILE STREAM ACCESS])

(\M44ReadDiskPage
  [LAMBDA (STREAM PAGENO BUF)                                (* bvm: "25-MAY-83 12:03")

          (* The functions for reading a disk page called by \M44ReadPages. Returns the number of bytes read.
	  If PAGEADDR is 0, then assume 0 bytes read. Fill the BUF with zeros beyond the last byte read.)


    (PROG ((PAGEADDR (\M44GetPageLoc STREAM PAGENO))
	   (BFSPG# (ADD1 PAGENO)))
          (RETURN (COND
		    ((EQ PAGEADDR \EOFDA)                    (* no bytes read, fill with zeroes.)
		      (\ZEROWORDS BUF (\ADDBASE BUF (SUB1 WordsPerPage)))
		      0)
		    ((EQ PAGEADDR \FILLINDA)
		      (SHOULDNT))
		    ((EQ (\ACTONDISKPAGES (fetch DSKOBJ of (fetch DEVICE of STREAM))
					  BUF
					  (fetch (ARRAYP BASE) of (fetch FILEPAGEMAP of STREAM))
					  -1 STREAM BFSPG# BFSPG# \DC.READD)
			 BFSPG#)
		      BYTESPERPAGE)
		    (T                                       (* if READDISKPAGE returns NIL, presumably there is an 
							     error of some kind, hope it was with the file map and 
							     try again.)
		       (\M44KillFilePageMap STREAM)
		       (\M44ReadDiskPage STREAM PAGENO BUF])

(\M44ReadLeaderPage
  [LAMBDA (STREAM BUFFER)                                    (* bvm: "25-MAY-83 12:04")

          (* File leader page format: Words 0-1, time created. Words 2-3, time last written. Words 4-5, time last read.
	  Words 6-25, name of file. Words 26-235, leader properties. Words 236-245, spare. Word 246, property pointer.
	  Word 247, change serial number. Words 248-252, STREAM hint for directory. Word 253, disk address of last page.
	  Word 254, page number of last page. Word 255, number of bytes on last page.)


    (\ACTONDISKPAGES (fetch DSKOBJ of (fetch DEVICE of STREAM))
		     BUFFER
		     (fetch (ARRAYP BASE) of (OR (fetch FILEPAGEMAP of STREAM)
						 (\MAKELEADERDAS STREAM)))
		     -1 STREAM 0 0 \DC.READD)
    BUFFER])

(\M44ReadPages
  [LAMBDA (STREAM FIRSTPAGE# BUFFERS)                        (* bvm: "26-DEC-81 23:50")
                                                             (* Read pages from a Model44 file.)
    (for BUF inside BUFFERS as PAGENO from FIRSTPAGE# sum (\M44ReadDiskPage STREAM PAGENO BUF])

(\M44ReleasePages
  [LAMBDA (STREAM LASTPAGE LASTOFFSET)                       (* bvm: "29-DEC-82 16:27")
                                                             (* Release all pages of the file beyond LASTPAGE)
    (\M44DELETEPAGES STREAM (ADD1 LASTPAGE))
    (COND
      ((ILESSP LASTPAGE (fetch LASTMAPPEDPAGE of STREAM))
	(for I from (ADD1 LASTPAGE) to (fetch LASTMAPPEDPAGE of STREAM)
	   do (SETA (fetch FILEPAGEMAP of STREAM)
		    (IPLUS I 2)
		    \EOFDA))
	(replace LASTMAPPEDPAGE of STREAM with LASTPAGE)))
    (\M44SetEndOfFile STREAM LASTPAGE LASTOFFSET])

(\M44SetAccessTimes
  [LAMBDA (STREAM ACCESS BUF)                                (* bvm: " 4-APR-83 11:26")

          (* The leader page of STREAM has been read into buffer. Set the "last read" and/or "last written" times in the BUF
	  according to access, which is assumed to be either INPUT, OUTPUT, BOTH, or APPEND.)


    (PROG [(DT (DAYTIME0 (create FIXP]                       (* Note: DAYTIME0 returns an Alto time, not Lisp time.
							     This is consistent with the dates in the leader page)
          (SELECTQ ACCESS
		   ((OUTPUT BOTH APPEND)
		     (replace TimeCreate of BUF with DT)
		     (replace TimeWrite of BUF with DT)      (* Must revalidate because write DT has changed)
		     (UPDATEVALIDATION STREAM BUF))
		   NIL)
          (SELECTQ ACCESS
		   ((INPUT BOTH)
		     (replace TimeRead of BUF with DT))
		   NIL])

(\M44SetEndOfFile
  [LAMBDA (STREAM EPAGE EOFFSET BUFFER)                      (* bvm: "20-OCT-82 18:28")

          (* Reset the file's leader page end-of-file hint. If BUFFER is given, then simply update the buffer.
	  If it is not, then read and write the leader page.)


    (replace LastPage of STREAM with EPAGE)                  (* Update handle)
    (replace LastOffset of STREAM with EOFFSET)
    (RESOURCECONTEXT \M44PAGEBUFFER
        [PROG [(EADDR (\M44GetPageLoc STREAM EPAGE))
	       (BUF (OR BUFFER (PROGN (GETRESOURCE \M44PAGEBUFFER)
				      (\M44ReadLeaderPage STREAM \M44PAGEBUFFER]
	      (replace LastPageAddress of BUF with EADDR)
	      (replace LastPageNumber of BUF with (ADD1 EPAGE))
                                                             (* M44 counts from 1)
	      (replace LastPageByteCount of BUF with EOFFSET)
	      (COND
		((NOT BUFFER)
		  (\M44WriteLeaderPage STREAM BUF)
		  (FREERESOURCE \M44PAGEBUFFER])])

(\M44SetFileInfo
  [LAMBDA (fHandle STREAM attribute value)                   (* bvm: "26-DEC-81 21:26")
                                                             (* Set the attribute of a Model44 file to value.
							     If STREAM is NIL, then the file is assumed to be 
							     closed.)
    NIL])

(\M44TruncateFile
  [LAMBDA (STREAM LP LO UPDATENOW)                           (* bvm: " 6-JAN-83 16:06")
                                                             (* Resets the length of the file to LP page and LO 
							     offset. Can both shorten and lengthen files.)
    [COND
      ((NOT LP)
	(SETQ LP (fetch EPAGE of STREAM))
	(SETQ LO (fetch EOFFSET of STREAM]
    (COND
      ((IGREATERP LP (fetch LastPage of STREAM))
	(\M44AddDiskPages STREAM LP LO))
      ((ILESSP LP (fetch LastPage of STREAM))
	(\M44ReleasePages STREAM LP LO)
	(replace LastPage of STREAM with LP)
	(replace LastOffset of STREAM with LO)               (* Now need to rewrite last page with new length, null 
							     next pointer)
	(\MAPPAGE LP STREAM)
	(\SETIODIRTY STREAM LP)
	(FLUSHMAP STREAM LP))
      (T (replace LastOffset of STREAM with LO)))
    (AND UPDATENOW (\M44SetEndOfFile STREAM LP LO))
    STREAM])

(\M44WriteDiskPage
  [LAMBDA (STREAM PAGENO BUF NBYTES)                         (* bvm: "25-MAY-83 12:04")
                                                             (* Write a disk page on the Model44.)
    (\M44GetPageLoc STREAM PAGENO T)                         (* Ensure that PAGENO is in map)
    (PROG ((BFSPG# (ADD1 PAGENO)))
          (RETURN (COND
		    ([COND
			((NEQ PAGENO (fetch LastPage of STREAM))
                                                             (* Writing only data)
			  (\ACTONDISKPAGES (fetch DSKOBJ of (fetch DEVICE of STREAM))
					   BUF
					   (fetch (ARRAYP BASE) of (fetch FILEPAGEMAP of STREAM))
					   -1 STREAM BFSPG# BFSPG# \DC.WRITED))
			(T                                   (* When writing last page, need to fill in the numchars 
							     field of label, so this is harder)
			   (COND
			     ((EQ PAGENO (fetch EPAGE of STREAM))
			       (EQ (\WRITEDISKPAGES (fetch DSKOBJ of (fetch DEVICE of STREAM))
						    BUF
						    (fetch (ARRAYP BASE)
						       of (fetch FILEPAGEMAP of STREAM))
						    -1 STREAM BFSPG# BFSPG# NIL NIL NBYTES)
				   BFSPG#))
			     (T 

          (* We will have to write more pages after this one, too, unless the file is truncated back to here, so extend the 
	  file while we're at it. This may save a call to \ADDDISKPAGES)


				[COND
				  ((ILEQ (fetch LASTMAPPEDPAGE of STREAM)
					 PAGENO)
				    (\M44ExtendFilePageMap STREAM (ADD1 PAGENO]
				(COND
				  ((EQ (\WRITEDISKPAGES (fetch DSKOBJ of (fetch DEVICE of STREAM))
							(LIST BUF NIL)
							(fetch (ARRAYP BASE)
							   of (fetch FILEPAGEMAP of STREAM))
							-1 STREAM BFSPG# (ADD1 BFSPG#)
							NIL NIL 0)
				       (ADD1 BFSPG#))        (* Write two pages, the second of which is blank)
				    (replace LastPage of STREAM with (ADD1 PAGENO))
				    (replace LastOffset of STREAM with 0)
				    T]
		      NBYTES)
		    (T (\M44KillFilePageMap STREAM)
		       (\M44WriteDiskPage STREAM PAGENO BUF NBYTES])

(\M44WriteLeaderPage
  [LAMBDA (STREAM BUFFER)                                    (* bvm: "25-MAY-83 12:04")
                                                             (* Write the file's leader page from the buffer)
    (\ACTONDISKPAGES (fetch DSKOBJ of (fetch DEVICE of STREAM))
		     BUFFER
		     (fetch (ARRAYP BASE) of (OR (fetch FILEPAGEMAP of STREAM)
						 (\MAKELEADERDAS STREAM)))
		     -1 STREAM 0 0 \DC.WRITED])

(\M44WritePages
  [LAMBDA (STREAM FIRSTPAGE# BUFFERS)                        (* bvm: "26-DEC-81 23:52")
                                                             (* Write pages onto a Model44 file.)
    (for BUF inside BUFFERS as PAGENO from FIRSTPAGE# do (\M44WriteDiskPage STREAM PAGENO BUF
									    (COND
									      ((EQ PAGENO
										   (fetch EPAGE
										      of STREAM))
										(fetch EOFFSET
										   of STREAM))
									      (T BYTESPERPAGE])
)
(DEFINEQ

(\ADDDISKPAGES
  [LAMBDA (STREAM FIRSTNEWPAGE NPAGES DAs LASTNUMCHARS)      (* bvm: "25-MAY-83 12:06")

          (* Adds to file STREAM NPAGES, where FIRSTNEWPAGE-1 is the last existing page. DAs is the vector of disk 
	  addresses, where first element corresponds to BFS page -1)

                                                             (* Note FIRSTNEWPAGE is in Lisp terms, so it is actually
							     LASTOLDPAGE for the BFS)
    (GLOBALRESOURCE \M44PAGEBUFFER
		    (PROG ((BUFFERS (CONS \M44PAGEBUFFER (for I from 1 to (IMIN NPAGES \MAXDISKDAs)
							    collect NIL)))
			   (DSK (fetch DSKOBJ of (fetch DEVICE of STREAM)))
			   (LASTEXISTINGPAGE FIRSTNEWPAGE)
			   CHUNK)
		          (\ACTONDISKPAGES DSK \M44PAGEBUFFER DAs -1 STREAM LASTEXISTINGPAGE 
					   LASTEXISTINGPAGE \DC.READD NIL NIL NIL LASTEXISTINGPAGE)
                                                             (* Read last existing page, so we can rewrite it with 
							     new label)
		          (while (IGREATERP NPAGES 0)
			     do (SETQ CHUNK (IMIN \MAXDISKDAs NPAGES))
				(\WRITEDISKPAGES DSK BUFFERS DAs -1 STREAM LASTEXISTINGPAGE
						 (IPLUS LASTEXISTINGPAGE CHUNK)
						 NIL NIL LASTNUMCHARS LASTEXISTINGPAGE)
				(RPLACA BUFFERS NIL)
				(add LASTEXISTINGPAGE CHUNK)
				(SETQ NPAGES (IDIFFERENCE NPAGES CHUNK])

(\GETPAGEHINT
  [LAMBDA (STREAM LASTKNOWNPAGE NPAGES DAs)                  (* bvm: "25-MAY-83 12:06")
    (PROG ((DSK (fetch DSKOBJ of (fetch DEVICE of STREAM)))
	   (KP (ADD1 LASTKNOWNPAGE))
	   (DONE 0)
	   CHUNK LASTPAGEREAD DA)
          [while (IGREATERP NPAGES DONE) do (COND
					      [(NEQ (SETQ DA (\GETBASE DAs (IPLUS KP 2)))
						    \FILLINDA)
                                                             (* There already is an entry for KP+1, so no need to 
							     read it)
						(COND
						  ((EQ DA \EOFDA)
						    (RETURN))
						  (T (add DONE 1)
						     (add KP 1]
					      (T [SETQ CHUNK (IMIN \MAXDISKDAs
								   (ADD1 (IDIFFERENCE NPAGES DONE]
						 (SETQ LASTPAGEREAD
						   (\ACTONDISKPAGES DSK NIL DAs -1 STREAM KP
								    (IPLUS KP CHUNK -1)
								    \DC.READD NIL NIL NIL NIL))
						 (add DONE (IDIFFERENCE LASTPAGEREAD KP))
						 (COND
						   ((ILESSP LASTPAGEREAD (IPLUS KP CHUNK -1))
                                                             (* Hit end of file)
						     (RETURN)))
						 (SETQ KP LASTPAGEREAD]
          (RETURN DONE])

(\M44DELETEPAGES
  [LAMBDA (STREAM FIRSTPAGE)                                 (* bvm: "25-MAY-83 12:07")
                                                             (* FIRSTPAGE is in Lisp terms, i.e. -1 = leader page)
    (PROG ((DEV (fetch DEVICE of STREAM))
	   (NPAGES (COND
		     ((fetch VALIDATION of STREAM)
		       (IPLUS (ADD1 (IDIFFERENCE (fetch LastPage of STREAM)
						 FIRSTPAGE))
			      2))
		     (T PageMapIncrement)))
	   (PN (ADD1 FIRSTPAGE))
	   DAs FIRSTDA LASTPAGESEEN DSK)

          (* NPAGES is used to decide how much to do at once. Need be no more than number of pages known to exist.
	  The ADD1 is that, plus two for the pages around it)


          (COND
	    ((ILESSP NPAGES 2)                               (* Nothing to delete)
	      (RETURN)))
          (SETQ DSK (fetch DSKOBJ of DEV))                   (* (\FLUSHDISKDESCRIPTOR (EMPOINTER 
							     (fetch (DSKOBJ DSKDDMGR) of DSK)) 
							     (fetch ALTODSKOBJ of DSK)))
                                                             (* Tell Alto to clear out anything it knows about dd)
                                                             (* IF STREAM:LASTMAPPEDPAGE GE FIRSTPAGE+NPAGES THEN DAs
							     ← STREAM:FILEPAGEMAP DAorigin ← -1)
          (SETQ DAs (ARRAY (SETQ NPAGES (IMIN NPAGES \MAXDISKDAs))
			   (QUOTE WORD)
			   NIL 0))
          [SETQ FIRSTDA (COND
	      [(EQ FIRSTPAGE -1)
		(fetch FPLEADERVDA of (fetch FIDBLOCK of (fetch FID of STREAM]
	      (T (\M44GetPageLoc STREAM FIRSTPAGE]
          (while (NEQ FIRSTDA \EOFDA)
	     do (SETA DAs 0 \FILLINDA)
		(SETA DAs 1 FIRSTDA)                         (* Corresponds to PN)
		(for I from 2 to (SUB1 NPAGES) do (SETA DAs I \FILLINDA))
		[SETQ LASTPAGESEEN (\ACTONDISKPAGES DSK NIL (fetch (ARRAYP BASE) of DAs)
						    (SUB1 PN)
						    STREAM PN (IPLUS PN NPAGES -3)
						    \DC.READD NIL NIL NIL
						    (ADD1 (fetch EPAGE of STREAM]
                                                             (* Read DAs for the next NPAGES-2)
		(\WRITEDISKPAGES DSK NIL (fetch (ARRAYP BASE) of DAs)
				 (SUB1 PN)
				 \FREEPAGEFID PN LASTPAGESEEN (UNSIGNED -1 BITSPERWORD))
		[for I from PN to LASTPAGESEEN do (\M44MARKPAGEFREE DEV
								    (ELT DAs (ADD1 (IDIFFERENCE
										     I PN]
		(SETQ FIRSTDA (ELT DAs (IPLUS (IDIFFERENCE LASTPAGESEEN PN)
					      2)))
		(SETQ PN (ADD1 LASTPAGESEEN)))               (* (FLUSHMAP (fetch (M44DEVICE DISKDESCRIPTOROFD) of 
							     DEV)))
                                                             (* (FORGETPAGES (fetch (M44DEVICE DISKDESCRIPTOROFD) of 
							     DEV)))
          (\M44FLUSHDISKDESCRIPTOR DEV])

(\ASSIGNDISKPAGE
  [LAMBDA (DSK PREVDA)                                       (* bvm: " 3-JAN-83 14:51")

          (* * Assigns a new page on DSK. If PREVDA is \EOFDA will pick random page, otherwise will attempt to allocate 
	  PREVDA+1. Returns NIL if disk is full)


    (PROG ([VDA (COND
		  ((OR (EQ PREVDA \EOFDA)
		       (COND
			 ((EQ PREVDA \FILLINDA)
			   (AND \DISKDEBUG (RAID 
			    "[Disk debug] \ASSIGNDISKPAGE called with \FILLINDA.  ↑N to continue"))
			   T)))
		    (fetch DISKLASTPAGEALLOC of DSK))
		  (T (ADD1 PREVDA]
	   (DD (fetch DISKDESCRIPTOROFD of DSK))
	   (MASK 128)
	   BITS A LOOPEDONCE FREE)
          (OR (fetch DDVALID of DSK)
	      (RAID "DISKDESCRIPTOR not open" DSK))
          (\SETFILEPTR DD (IPLUS \DDBITTABSTART (FOLDLO VDA BITSPERBYTE)))
          (SETQ A (MOD VDA BITSPERBYTE))
          (FRPTQ A (SETQ MASK (LRSH MASK 1)))
      LP  (COND
	    ((NULL (SETQ BITS (\BIN DD)))

          (* End of file -- wrap around to start. This technique takes longer than necessary to bomb out when disk is full, 
	  but who cares?)


	      (COND
		(LOOPEDONCE (RETURN NIL)))
	      (SETQ LOOPEDONCE T)
	      (\SETFILEPTR DD \DDBITTABSTART))
	    ((NEQ BITS 255)
	      (until [OR (ZEROP (LOGAND BITS MASK))
			 (ZEROP (SETQ MASK (LRSH MASK 1]
		 do (add A 1))
	      (COND
		((NOT (ZEROP MASK))                          (* Found a free page)
		  (\BACKFILEPTR DD)
		  (SETQ VDA (IPLUS (UNFOLD (IDIFFERENCE (\GETFILEPTR DD)
							\DDBITTABSTART)
					   BITSPERBYTE)
				   A))
		  (\BOUT DD (LOGOR BITS MASK))               (* Set bit indicating we snarfed this page)
                                                             (* Decrement free page count hint)
		  [replace DISKFREEPAGES of DSK with (COND
						       ((ZEROP (SETQ FREE (fetch DISKFREEPAGES
									     of DSK)))
							 (AND \DISKDEBUG (RAID 
				     "[Disk debug] Free page hint went negative.  ↑N to continue"))
							 (\COUNTDISKFREEPAGES DD))
						       (T (SUB1 FREE]
		  (replace DISKLASTPAGEALLOC of DSK with VDA)
		  (replace DDDIRTY of DSK with T)
		  (RETURN VDA)))
	      (SETQ MASK 128)
	      (SETQ A 0)))
          (GO LP])

(\COUNTDISKFREEPAGES
  [LAMBDA (DD)                                               (* bvm: " 3-JAN-83 12:25")

          (* * Counts number of free pages on a disk. DD is the diskdescriptor stream)


    [OR (type? STREAM DD)
	(SETQ DD (\OPENDISKDESCRIPTOR (\GETDEVICEFROMNAME (OR DD (QUOTE DSK]
    (PROG ((CNT 0)
	   MASK BITS)
          (\SETFILEPTR DD \DDBITTABSTART)
      LP  [COND
	    ((NULL (SETQ BITS (\BIN DD)))                    (* End of file)
	      (RETURN CNT))
	    ((EQ BITS 0)
	      (add CNT 8))
	    ((NEQ BITS 255)
	      (SETQ MASK 128)
	      (do (COND
		    ((ZEROP (LOGAND BITS MASK))
		      (add CNT 1)))
		 until (ZEROP (SETQ MASK (LRSH MASK 1]
          (GO LP])

(\M44MARKPAGEFREE
  [LAMBDA (DEV DA)                                           (* bvm: "16-DEC-82 13:02")
                                                             (* Mark disk address DA on disk device DEV free)
    (PROG ([DD (COND
		 ((fetch (M44DEVICE DDVALID) of DEV)
		   (fetch (M44DEVICE DISKDESCRIPTOROFD) of DEV))
		 (T (\OPENDISKDESCRIPTOR DEV]
	   BITS MASK)
          (SETFILEPTR DD (IPLUS \DDBITTABSTART (FOLDLO DA BITSPERBYTE)))
          (SETQ BITS (\BIN DD))
          [SETQ MASK (LLSH 1 (IDIFFERENCE 7 (MOD DA BITSPERBYTE]
          (COND
	    ((NEQ (LOGAND BITS MASK)
		  0)                                         (* Page is marked occupied, so free it)
	      (\BACKFILEPTR DD)
	      (\BOUT DD (LOGXOR BITS MASK))
	      (add (fetch (M44DEVICE DISKFREEPAGES) of DEV)
		   1)
	      (replace (M44DEVICE DDDIRTY) of DEV with T])

(\M44FLUSHDISKDESCRIPTOR
  [LAMBDA (DEV)                                              (* bvm: " 5-APR-83 12:36")
    (PROG ((DSK (COND
		  ((type? FDEV DEV)
		    (fetch (M44DEVICE DSKOBJ) of DEV))
		  (T DEV)))
	   DD)
          (OR (fetch (DSKOBJ DDDIRTY) of DSK)
	      (RETURN))
          (OR (SETQ DD (fetch DISKDESCRIPTOROFD of DSK))
	      (RETURN (RAID "[Disk debug] no disk descriptor stream")))
          (\SETFILEPTR DD \OFFSET.DISKLASTSERIAL#)
          (\BOUTS DD (LOCF (fetch DISKLASTSERIAL# of DSK))
		  0 \NBYTES.DISKINFO)                        (* Copy interesting stuff into diskdescriptor header)
          (FLUSHMAP DD)
          (replace DDDIRTY of DSK with NIL)
          (RETURN T])

(\MAKELEADERDAS
  [LAMBDA (STREAM)                                           (* bvm: "20-OCT-82 18:42")
                                                             (* Makes a page map for STREAM that includes the leader 
							     vda)
    (PROG ((MAP (ARRAY 4 (QUOTE WORD)
		       \FILLINDA 0)))
          (SETA MAP 0 \EOFDA)
          [SETA MAP 1 (fetch FPLEADERVDA of (fetch (ARRAYP BASE) of (fetch FID of STREAM]
          (replace FILEPAGEMAP of STREAM with MAP)
          (replace LASTMAPPEDPAGE of STREAM with -1)
          (RETURN MAP])

(\CREATE.FID.FOR.DD
  [LAMBDA (FDEV)                                             (* bvm: "25-MAY-83 12:16")
                                                             (* Creates a FID for the file DISKDESCRIPTOR on FDEV, 
							     which must be the default disk partition's device)
    (PROG ((FID (create FID)))

          (* Currently \SYSDISK has a copy of the diskdescriptor fp inside it, as looked up by alto at beginning of world, 
	  so be lazy and use that)


          (\BLT (fetch FIDBLOCK of FID)
		(LOCF (fetch (M44DEVICE DSKDDBLK) of FDEV))
		\LENFP)
          (RETURN FID])

(\OPENDISK
  [LAMBDA (PARTNUM)                                          (* bvm: " 5-APR-83 13:42")
    (PROG (DSK DD)
          (OR (\TESTPARTITION PARTNUM)
	      (RETURN))
          (SETQ DSK (create DSKOBJ))
          (\LOCKWORDS DSK \NWORDS.DSKOBJ)
          (replace DSKPARTITION of DSK with PARTNUM)
          (replace ddPOINTER of DSK with (LOCF (fetch ddLASTSERIAL# of DSK)))
          (replace NDISKS of DSK with 2)
          (replace NTRACKS of DSK with 406)
          (replace NHEADS of DSK with 2)
          (replace NSECTORS of DSK with 14)
          (replace RETRYCOUNT of DSK with 8)
          (replace CBQUEUE of DSK with (fetch CBQUEUE of \MAINDISK))
                                                             (* Really should have our own)
          (RETURN (\OPENDISKDEVICE PARTNUM DSK])

(DISKFREEPAGES
  [LAMBDA (DSK RECOMPUTE)                                    (* bvm: " 8-Jan-84 18:02")
                                                             (* DSK ignored for now)
    (SELECTC \MACHINETYPE
	     (\DANDELION                                     (* Temporary until this become a device op)
			 (\DFSFreeDiskPages (OR DSK (QUOTE DSK))
					    RECOMPUTE))
	     (PROG ((DEV (COND
			   ((LITATOM DSK)
			     (\GETDEVICEFROMNAME (OR DSK (QUOTE DSK))
						 T))
			   (T DSK)))
		    CNT)
	           (COND
		     ((NOT (type? M44DEVICE DEV))
		       (\ILLEGAL.ARG DSK)))
	           (RETURN (COND
			     (RECOMPUTE (SETQ CNT (\COUNTDISKFREEPAGES (\OPENDISKDESCRIPTOR DEV)))
					(COND
					  ((NEQ CNT (fetch (M44DEVICE DISKFREEPAGES) of DEV))
					    (replace (M44DEVICE DISKFREEPAGES) of DEV with CNT)
					    (replace (M44DEVICE DDDIRTY) of DEV with T)))
					CNT)
			     (T (fetch (M44DEVICE DISKFREEPAGES) of DEV])

(VMEMSIZE
  [LAMBDA NIL                                                (* bvm: " 1-NOV-82 16:44")
    (fetch (IFPAGE NActivePages) of \InterfacePage])
)
(DECLARE: DONTCOPY 
(DECLARE: EVAL@COMPILE 

(PUTPROPS UCASECHAR MACRO [(C)
			   (COND
			     ((ILESSP C (CHARCODE a))
			       C)
			     (T (IDIFFERENCE C (IDIFFERENCE (CHARCODE a)
							    (CHARCODE A])

(PUTPROPS UPDATEVALIDATION MACRO [(STREAM BUF)
				  (replace VALIDATION of STREAM with (\MAKENUMBER (\GETBASE BUF 1)
										  (\GETBASE BUF 3])
)

[DECLARE: EVAL@COMPILE 

(ACCESSFNS M44DEVICE ((DSKOBJ (fetch DEVICEINFO of DATUM)
			      (replace DEVICEINFO of DATUM with NEWVALUE)))
		     [TYPE? (AND (type? FDEV DATUM)
				 (EQ (fetch OPENFILE of DATUM)
				     (QUOTE \M44OpenFile])
]

(DECLARE: EVAL@COMPILE 

(RPAQQ PageMapIncrement 64)

(RPAQQ NameFirstCharPos 13)

(CONSTANTS (PageMapIncrement 64)
	   (NameFirstCharPos 13))
)
)
(DECLARE: DOEVAL@COMPILE DONTCOPY 
[PUTDEF (QUOTE \M44PAGEBUFFER)
	(QUOTE GLOBALRESOURCES)
	(QUOTE (NCREATE (QUOTE VMEMPAGEP]
)
(DECLARE: DOEVAL@COMPILE DONTCOPY

(ADDTOVAR GLOBALVARS \M44PAGEBUFFER)
)

(RPAQQ \M44PAGEBUFFER NIL)



(* Directory lookup routines)

(DEFINEQ

(\FILESPEC
  [LAMBDA (X RECOG DIROFD)                                  (* rrb "10-JUN-80 11:06")

          (* This returns a full file specification, with all the information needed to do open, delete, etc. A filespec is a 
	  (packedname unpackedname dirptr) triple, with the true version number smashed into the uname.
	  The dirptr is NIL if the file does not currently exist in the directory.)


    (PROG (DP V L (UNAME (\UNPACKFILENAME X)))
          [COND
	    ((NULL UNAME)                                   (* BAD FILE NAME)
	      (RETURN (create FILESPEC
			      UNAME ← NIL)))
	    [(EQ UNAME (QUOTE DIRECTORY))                   (* directory name found on front of file name, needs to 
							    generate file not found error in openfile.)
	      (RETURN (create FILESPEC
			      UNAME ← NIL
			      FSDIRPTR ←(QUOTE DIRECTORY]
	    ([AND (SETQ L (\LOOKUPVERSIONS UNAME DIROFD (SELECTQ RECOG
								 ((NEW OLD/NEW)
								   T)
								 NIL)))
		  (SETQ V (SELECTQ (OR (fetch VERSION of UNAME)
				       RECOG)
				   ((OLD OLD/NEW)
				     (CAR (LAST L)))
				   [NEW                     (* A new version, so the DIRPTR is NIL)
					(LIST (ADD1 (CAAR (LAST L]
				   (OLDEST (CAR L))
				   (SASSOC (fetch VERSION of UNAME)
					   L]
	      (SETQ DP (CDR V))
	      (SETQ V (CAR V)))
	    (T (SETQ DP NIL)                                (* Since file doesnt exist, recognition mode takes 
							    precedence over version number)
	       (SETQ V (SELECTQ (OR RECOG (fetch VERSION of UNAME))
				((NEW OLD/NEW)
				  (COND
				    ((fetch ESCFLAG of UNAME)
				      NIL)
				    ((FIXP (fetch VERSION of UNAME)))
				    (T 1)))
				((OLD OLDEST)
				  NIL)
				(FIXP (fetch VERSION of UNAME]
          (replace VERSION of UNAME with (AND V (CHCON V)))
                                                            (* We may have to zap a version number that was 
							    specified but not found)
          (RETURN (create FILESPEC
			  UNAME ← UNAME
			  FSDIRPTR ← DP])

(\FINDDIRHOLE
  [LAMBDA (WDS DIRSTREAM)                                    (* bvm: "10-MAR-83 22:01")

          (* Returns the byte address of a directory hole of size WDS. The directory file is positioned just after the 
	  2-byte length field of the hole.)


    (PROG ((PTR (OR (fetch DIRHOLEPTR of DIRSTREAM)
		    0))
	   T1 C)
      NEXT(\SETFILEPTR DIRSTREAM PTR)
          (COND
	    ((\EOFP DIRSTREAM)
	      (GO END))
	    ((ILESSP 3 (SETQ C (\BIN DIRSTREAM)))
	      (SETQ T1 (\BIN DIRSTREAM))                     (* Already occupied)
	      )
	    [(IGREATERP WDS (SETQ T1 (IPLUS (LLSH C 10Q)
					    (\BIN DIRSTREAM]
	    (T (\SETFILEPTR DIRSTREAM PTR)                   (* Hole is large enough)
	       [COND
		 ((IGREATERP T1 WDS)                         (* Too large, so break it apart.)
		   (SETQ T1 (IDIFFERENCE T1 WDS))
		   (\WOUT DIRSTREAM T1)
		   (\SETFILEPTR DIRSTREAM (SETQ PTR (IPLUS PTR T1 T1]
	       (GO END)))
          (SETQ PTR (IPLUS PTR T1 T1))
          (GO NEXT)
      END (\WOUT DIRSTREAM WDS)
          (RETURN PTR])

(\LISPFILENAME
  [LAMBDA (UNAME)                                           (* rmk: "26-OCT-81 19:11")
                                                            (* Produces a Lisp style file-name of the form 
							    "name.[ext];ver")
    (AND (fetch VERSION of UNAME)
	 (PACK* (COND
		  ((fetch PARTNUM of UNAME)
		    (PACK* "{DSK" (fetch PARTNUM of UNAME)
			   "}"))
		  (T "{DSK}"))
		(PACKC (NCONC (for X in (fetch CHARPAIRS of UNAME) collect (UCASECHAR (CAR X)))
			      [APPEND (COND
					((FASSOC (CHARCODE %.)
						 (fetch CHARPAIRS of UNAME))
					  (CHARCODE (;)))
					(T (CHARCODE (%. ;]
			      (fetch VERSION of UNAME])

(\LOOKUPVERSIONS
  [LAMBDA (UNAME STREAM HMIN)                                (* bvm: " 3-JUN-83 17:26")

          (* UNAME is a value of \UNPACKFILENAME. STREAM is the directory ofd. HMIN=T means look for a hole big enough for 
	  UNAME, a number N means look for that size hole, NIL means don't look. Returns a list of (version . fileptr) pairs
	  sorted by increasing version. Ptr is a pointer to the beginning of the directory slot for the file.
	  If the UNAME had an escape which matches unambiguously, the UNAME is smashed with the completion characters.)


    (PROG ([LEN1 (IPLUS 13 (LENGTH (fetch CHARPAIRS of UNAME]
	   (ESC (fetch ESCFLAG of UNAME))
	   (TLIST (CONS 0 (fetch CHARPAIRS of UNAME)))
	   (FIXEDVERSION (FIXP (fetch VERSION of UNAME)))
	   PTR NCHARSLEFT L V END CHARLIST L1 OLDESCLIST L2)
          (COND
	    ((AND FIXEDVERSION (OR ESC (ZEROP FIXEDVERSION)))
	      (SETQ FIXEDVERSION NIL)))
          [COND
	    ((EQ HMIN T)                                     (* The 6 is to allow for the maximum number of chars in 
							     a version number)
	      (SETQ HMIN (FOLDLO (IPLUS LEN1 6)
				 BYTESPERWORD]
      S   (COND
	    ((NULL (SETQ NCHARSLEFT (\SEARCHDIR1 STREAM TLIST HMIN)))
	      [AND ESC OLDESCLIST (NCONC UNAME (MAPCAR (DREVERSE OLDESCLIST)
						       (FUNCTION CONS]
	      (RETURN L)))
          (SETQ PTR (\GETFILEPTR STREAM))
          [for I C LASTBANG from NCHARSLEFT to 2 by -1
	     first (SETQ CHARLIST NIL)
		   (SETQ V NIL)
	     do                                              (* Stop at 2 to exclude the terminating period)
		(SETQ C (\BIN STREAM)) 

          (* Non-version characters are reversed in CL. Version characters (if any) are used to build the integer V.
	  We fool around with LASTBANG cause, according to the alto filespecs, names like FOO!BAR!3.
	  are legit.)


		(push CHARLIST C)
		(COND
		  [V (COND
		       [(AND (IGEQ C (CHARCODE 0))
			     (ILEQ C (CHARCODE 9)))
			 (SETQ V (IPLUS (ITIMES V 10)
					(IDIFFERENCE C (CHARCODE 0]
		       (T                                    (* A non-numeric after a ! means that that wasn't the 
							     version marker. Try again)
			  (SETQ V NIL]
		  ((EQ C (CHARCODE !))
		    (SETQ LASTBANG CHARLIST)                 (* This might or might not be the version marker.
							     Save the current pointer in case it is.)
		    (SETQ V 0)))
	     finally (SELECTQ V
			      ((NIL 0)
				(SETQ V 1))
			      (PROGN                         (* Turns out there WAS a real version;
							     remove its characters and the ! from CHARLIST)
				     (SETQ CHARLIST (CDR LASTBANG]
          [COND
	    [(NULL ESC)                                      (* Everything but version has to match exactly if there 
							     was no Escape)
	      (COND
		(CHARLIST (GO NEXT]
	    ((EQ ESC T)                                      (* Escape, first time.)
	      (SETQ OLDESCLIST CHARLIST)
	      (SETQ ESC 0)                                   (* 0 means ESC but not first time.
							     Used for ambiguity checking.)
	      )
	    (T                                               (* ESC is 0)
                                                             (* Make sure that the rest of the name is the same)
	       (OR [for (L1 ← CHARLIST) by (CDR L1) as (L2 ← OLDESCLIST) by (CDR L2)
		      do (COND
			   ((NULL L1)
			     (RETURN (NULL L2)))
			   ([OR (NULL L2)
				(AND (NEQ (CAR L1)
					  (CAR L2))
				     (NEQ (CAR L1)
					  (LOGXOR (CAR L2)
						  32]
			     (RETURN NIL]
		   (RETURN]

          (* * Name matches. V is the version number. Cons up a piece of the result. If UNAME has an explicit version, 
	  insist on it now)


          (SETQ PTR (IDIFFERENCE PTR LEN1))                  (* Find beginning of the directory entry)
                                                             (* Merge new element into L)
          (COND
	    [FIXEDVERSION (COND
			    [(EQ V FIXEDVERSION)
			      (RETURN (LIST (CONS V PTR]
			    (T (GO NEXT]
	    ((OR (NULL L)
		 (IGREATERP (CAAR L)
			    V))
	      (SETQ L (CONS (CONS V PTR)
			    L))
	      (GO NEXT)))
          (SETQ END L)
      INS (COND
	    ((AND (CDR END)
		  (IGREATERP V (CAADR END)))
	      (SETQ END (CDR END))
	      (GO INS)))
          (RPLACD END (CONS (CONS V PTR)
			    (CDR END)))
      NEXT(AND HMIN (fetch DIRHOLEPTR of STREAM)
	       (SETQ HMIN NIL))                              (* Stop looking if found a hole)
          (GO S])

(\OPENDISKDESCRIPTOR
  [LAMBDA (DEV)                                              (* bvm: "25-MAY-83 12:16")
                                                             (* Opens and returns a stream on the disk descriptor 
							     file for DEV)
    [COND
      ((NOT (type? FDEV DEV))
	(SETQ DEV (\GETDEVICEFROMNAME (fetch DISKDEVICENAME of DEV]
    (OR (fetch (M44DEVICE DDVALID) of DEV)
	(PROG ((OLDD (fetch (M44DEVICE DISKDESCRIPTOROFD) of DEV))
	       STREAM)
	      (COND
		(OLDD (FORGETPAGES OLDD)))
	      [SETQ STREAM (COND
		  ((EQ (fetch DSKOBJ of DEV)
		       \MAINDISK)
		    (\M44OPENFILEFROMFP DEV "DISKDESCRIPTOR.;1" (QUOTE BOTH)
					(\CREATE.FID.FOR.DD DEV)))
		  (T (\OPENFILE (PACK* (QUOTE {)
				       (fetch DEVICENAME of DEV)
				       (QUOTE })
				       "DISKDESCRIPTOR.;1")
				(QUOTE BOTH]
	      (replace (M44DEVICE DISKDESCRIPTOROFD) of DEV with STREAM)
	      (replace MAXBUFFERS of STREAM with (ADD1 (fetch EPAGE of STREAM)))
                                                             (* Prepare to buffer the whole file, so that we don't 
							     get in trouble under \NEWPAGE)
	      (for I from 0 to (fetch EPAGE of STREAM) do (\MAPPAGE I STREAM))
                                                             (* Ought to define a \MAPPAGES to do that more 
							     efficiently)
	      (replace ENDOFSTREAMOP of STREAM with (FUNCTION NILL))
	      (replace (M44DEVICE DDVALID) of DEV with T)))
    (fetch (M44DEVICE DISKDESCRIPTOROFD) of DEV])

(\READDIRFPTR
  [LAMBDA (STREAM FPTR)                                      (* bvm: "26-DEC-81 21:25")
    (OR FPTR (SETQ FPTR (create FID)))
    (replace W0 of FPTR with (\WIN STREAM))
    (replace W1 of FPTR with (\WIN STREAM))
    (replace W2 of FPTR with (\WIN STREAM))
    (replace W3 of FPTR with (\WIN STREAM))
    (replace W4 of FPTR with (\WIN STREAM))
    FPTR])

(\SEARCHDIR1
  [LAMBDA (STREAM TLIST HMIN)                                (* bvm: " 3-JUN-83 17:22")

          (* Finds next directory entry for which TLIST::1 is a prefix of the filename. Returns NIL if no entry found, else 
	  the length of the remaining chars in the entry. Leaves the directory positioned after the char matching the last 
	  char of TLIST::1 -
	  STREAM is the ofd of the directory file -
	  TLIST is a list of the form (POS . CHARPAIRS), where POS at entry is a fileptr in the directory file at which to 
	  start searching and CHARPAIRS is like the characters pairs of a uname. At exit, TLIST is smashed so that POS is 
	  the fileptr just beyond the found entry. -
	  if HMIN~=NIL, sets STREAM's DIRHOLEPTR to NIL or the fileptr of the first hole of at least HMIN words.)


    (PROG ((NEXT (CAR TLIST))
	   (CHARPAIRS (CDR TLIST))
	   THISNAMELENGTH TARGETLENGTH PTR L CHPAIR TYP ENTRYLENGTH CH)
          (COND
	    (HMIN (replace DIRHOLEPTR of STREAM with NIL)))
          (SETQ TARGETLENGTH (LENGTH CHARPAIRS))
      NEXT(\SETFILEPTR STREAM (SETQ PTR NEXT))
          (COND
	    ((\EOFP STREAM)
	      (RETURN)))

          (* * Format of a directory entry is -
	  Type (0 = hole, 1 = file), 6 bits -
	  Length of entry in words, 10 bits -
	  FP 5 words -
	  Name as a BcplString)


          (SETQ TYP (\BIN STREAM))
          (SETQ ENTRYLENGTH (IPLUS (LLSH (LOGAND TYP 3)
					 8)
				   (\BIN STREAM)))
          (SETQ NEXT (IPLUS (UNFOLD ENTRYLENGTH BYTESPERWORD)
			    PTR))
          (COND
	    ((NEQ (LRSH TYP 2)
		  1)                                         (* Not a file)
	      (COND
		((AND HMIN (NOT (IGREATERP HMIN ENTRYLENGTH)))
		  (replace DIRHOLEPTR of STREAM with PTR)
		  (SETQ HMIN NIL)))
	      (GO NEXT)))
          (\SETFILEPTR STREAM (IPLUS PTR 12))
          (COND
	    ((ILESSP (SETQ THISNAMELENGTH (\BIN STREAM))
		     TARGETLENGTH)
	      (GO NEXT)))
          (SETQ L CHARPAIRS)
      READ[COND
	    ((NULL L)
	      (RPLACA TLIST NEXT)
	      (RETURN (IDIFFERENCE THISNAMELENGTH TARGETLENGTH]
          (SETQ CHPAIR (CAR L))
          (SETQ CH (\BIN STREAM))
          (COND
	    ((OR (EQ CH (CAR CHPAIR))
		 (EQ CH (CADR CHPAIR)))
	      (SETQ L (CDR L))
	      (GO READ))
	    (T (GO NEXT])

(\UNPACKFILENAME
  [LAMBDA (NAME)                                             (* bvm: " 3-JUN-83 17:48")

          (* Unpacks file name into a UNAME of the form ((VERSION PARTNUM . ESCFLAG) . CHARPAIRS) where VERSION is the 
	  version indicator (either a positive integer or one of OLD, OLDEST, NEW) PARTNUM is the partion number 
	  (NIL for current Partition) and ESCFLAG indicates that NAME terminated in escape, and the CHARPAIRS is a list of 
	  pairs the first element of which is the char actually specified in name, and the second is the upper/lower case 
	  alternative for alphabetics.)

                                                             (* changed to generate a file not found error in the 
							     case that a directory is specified -
							     rrb.)
    (PROG (J C END NEGATEDVERSION VERSION RESULT PARTNUM)
          (COND
	    ([OR (NOT NAME)
		 (EQ NAME T)
		 (NOT (LITATOM NAME))
		 (NEQ (NTHCHARCODE NAME 1)
		      (CHARCODE {))
		 (NEQ (U-CASE (SUBATOM NAME 2 4))
		      (QUOTE DSK))
		 (NOT (SETQ J (STRPOS "}" NAME 5)))
		 (AND (NEQ J 5)
		      (NOT (FIXP (SETQ PARTNUM (SUBATOM NAME 5 (SUB1 J]
	      (RETURN)))
          (SETQ END (SETQ RESULT (create UNAME
					 PARTNUM ← PARTNUM)))
                                                             (* End is the cell whose CDR can be smashed.)
          (add J 1)
          [COND
	    ((EQ (NTHCHARCODE NAME J)
		 (CHARCODE <))                               (* if directory name is included, generate file not 
							     found error. If not, return NIL which will cause bad 
							     file name error.)
	      (RETURN (COND
			((STRPOS ">" NAME)                   (* pass back special error code.)
			  (QUOTE DIRECTORY]
      COLLECTNAME
          (COND
	    ((NOT (SETQ C (NTHCHARCODE NAME J)))
	      (GO RET))
	    (T [RPLACD END (SETQ END (LIST (COND
					     [(BETWEEN C (CHARCODE A)
						       (CHARCODE Z))
					       (LIST C (IPLUS C (IDIFFERENCE (CHARCODE a)
									     (CHARCODE A]
					     [(BETWEEN C (CHARCODE a)
						       (CHARCODE z))
					       (LIST C (IDIFFERENCE C (IDIFFERENCE (CHARCODE a)
										   (CHARCODE A]
					     ((BETWEEN C (CHARCODE 0)
						       (CHARCODE 9))
					       (LIST C))
					     (T (SELCHARQ C
							  ((; !)
							    (GO SEMI))
							  ((ESCAPE *)
							    (replace ESCFLAG of RESULT with T)
							    (SETQ C (NTHCHARCODE NAME
										 (add J 1)))
							    (GO TERM))
							  (%. 
                                                             (* Omit trailing dots)
							      (SELCHARQ (NTHCHARCODE NAME
										     (ADD1 J))
									(NIL (GO RET))
									((; !)
									  (add J 1)
									  (GO SEMI))
									(LIST C)))
							  (($ + -)
							    (LIST C))
							  (GO ERR]
	       (add J 1)
	       (GO COLLECTNAME)))
      SEMI[SELCHARQ (SETQ C (NTHCHARCODE NAME (add J 1)))
		    ((H h)
		      (SETQQ VERSION OLD)
		      (SELCHARQ (SETQ C (NTHCHARCODE NAME (add J 1)))
				(NIL (GO RET))
				((; !)
				  (GO SEMI))
				(GO ERR)))
		    ((L l)
		      (SETQQ VERSION OLDEST)
		      (SELCHARQ (SETQ C (NTHCHARCODE NAME (add J 1)))
				(NIL (GO RET))
				((; !)
				  (GO SEMI))
				(GO ERR)))
		    ((N n)
		      (SETQQ VERSION NEW)
		      (SELCHARQ (SETQ C (NTHCHARCODE NAME (add J 1)))
				(NIL (GO RET))
				((; !)
				  (GO SEMI))
				(GO ERR)))
		    [(T t S s A a P p)                       (* Various Tenex crocks. Not implemented, but don't 
							     complain about them)
		      (PROG NIL
			SKIP(SELCHARQ (NTHCHARCODE NAME (add J 1))
				      ((; !)
					(GO SEMI))
				      (NIL (GO RET))
				      (GO SKIP]
		    [- (COND
			 (VERSION (GO ERR))
			 (T (SETQ NEGATEDVERSION T)
			    (SETQ VERSION 0)
			    (SETQ C (NTHCHARCODE NAME (add J 1)))
			    (GO COLLECTVERSION]
		    (NIL (GO ERR))
		    (COND
		      (VERSION (GO ERR))
		      (T (SETQ VERSION 0)
			 (GO COLLECTVERSION]
      COLLECTVERSION
          (COND
	    ((AND C (BETWEEN C (CHARCODE 0)
			     (CHARCODE 9)))
	      [SETQ VERSION (IPLUS (ITIMES VERSION 10)
				   (IDIFFERENCE C (CHARCODE 0]
	      (SETQ C (NTHCHARCODE NAME (add J 1)))
	      (GO COLLECTVERSION)))
          (COND
	    [NEGATEDVERSION (SETQ VERSION (SELECTQ VERSION
						   (1 (QUOTE NEW))
						   (2 (QUOTE OLDEST))
						   (GO ERR]
	    ((ZEROP VERSION)
	      (SETQQ VERSION OLD))
	    ((IGREATERP VERSION 65535)
	      (GO ERR)))
      TERM(SELCHARQ C
		    (NIL (GO RET))
		    ((; !)
		      (GO SEMI))
		    (GO ERR))
      ERR                                                    (* BAD FILE NAME)
          (RETURN NIL)
      RET (replace VERSION of RESULT with VERSION)
          (RETURN RESULT])

(\WRITEDIRFPTR
  [LAMBDA (STREAM FPTR)                                      (* bvm: "26-DEC-81 21:26")
    (\WOUT STREAM (fetch W0 of FPTR))
    (\WOUT STREAM (fetch W1 of FPTR))
    (\WOUT STREAM (fetch W2 of FPTR))
    (\WOUT STREAM (fetch W3 of FPTR))
    (\WOUT STREAM (fetch W4 of FPTR))
    NIL])
)
(DEFINEQ

(ALTOFILENAME
  [LAMBDA (X)                                               (* rmk: "28-OCT-81 22:49")
                                                            (* Converts the lisp filename X to the equivalent 
							    Alto-format filename.)
    (PROG [(EXT (FILENAMEFIELD X (QUOTE EXTENSION]
          (RETURN (CONCAT (FILENAMEFIELD X (QUOTE NAME))
			  (COND
			    ((SETQ EXT (FILENAMEFIELD X (QUOTE EXTENSION)))
			      (CONCAT "." EXT))
			    (T ""))
			  (COND
			    ([EQ 1 (SETQ X (FILENAMEFIELD X (QUOTE VERSION]
			      "")
			    (T (CONCAT "!" X])
)

(RPAQQ \FILENAMECHARSLST (36 43 45 46))
(DECLARE: DOEVAL@COMPILE DONTCOPY

(ADDTOVAR GLOBALVARS \FILENAMECHARSLST)
)
(DECLARE: DONTCOPY 
[DECLARE: EVAL@COMPILE 

(RECORD UNAME ((VERSION PARTNUM . ESCFLAG) . CHARPAIRS))

(RECORD FILESPEC (UNAME FSDIRPTR)
		 [ACCESSFNS FILESPEC ((PNAME (\LISPFILENAME (fetch UNAME of DATUM])

(RECORD M44GENFILESTATE (DIROFD HOSTNAME . SEARCHSTATE))

(RECORD M44DIRSEARCHSTATE (DIRPTR . CHARLIST))
]

(DECLARE: EVAL@COMPILE 

(PUTPROPS BETWEEN MACRO (OPENLAMBDA (V LO HI)
				    (AND (IGEQ V LO)
					 (ILEQ V HI))))
)
)
(DEFINEQ

(\VANILLADISKINIT
  [LAMBDA NIL                                                (* hts: " 5-Mar-84 19:26")
                                                             (* Define a device whose sole purpose is to select the 
							     appropriate DSK device depending on which machine you're
							     on)
    (\DEFINEDEVICE
      NIL
      (create FDEV
	      DEVICENAME ← "VANILLADISK"
	      EVENTFN ←(FUNCTION NILL)
	      HOSTNAMEP ←(FUNCTION (LAMBDA (NAME)
		  (SELECTC \MACHINETYPE
			   [\DANDELION (COND
					 ((GETD (QUOTE \DFSDEVICEP))
					   (\DFSDEVICEP NAME))
					 ((EQ NAME (QUOTE DSK))
					   (PROG [(DEV (\GETDEVICEFROMNAME (COREDEVICE NAME]
                                                             (* Grumble. COREDEVICE returns name, not device)
					         [replace EVENTFN of DEV
						    with (FUNCTION (LAMBDA (FDEV EVENT)
							     (SELECTQ EVENT
								      ((AFTERLOGOUT AFTERSYSOUT 
										    AFTERMAKESYS)
									(if (NEQ (MACHINETYPE)
										 (QUOTE DANDELION))
									    then (\REMOVEDEVICE
										   FDEV)))
								      NIL]
					         (RETURN DEV]
			   (\M44HOSTNAMEP NAME])

(\OPENDISKDEVICE
  [LAMBDA (PARTITION DSKOBJ)                                 (* bvm: "29-Nov-83 14:48")
    (DECLARE (GLOBALVARS \MAINDISK))                         (* Creates the model 44 DSK device and opens its 
							     SYSDIR.)
    (PROG ([NAME (COND
		   ((ZEROP PARTITION)
		     (QUOTE DSK))
		   (T (PACK* (QUOTE DSK)
			     PARTITION]
	   FDEV)
          (SETQ FDEV
	    (create FDEV
		    FDBINABLE ← T
		    FDBOUTABLE ← T
		    FDEXTENDABLE ← T
		    DEVICENAME ← NAME
		    RESETABLE ← T
		    RANDOMACCESSP ← T
		    NODIRECTORIES ← T
		    PAGEMAPPED ← T
		    CLOSEFILE ←(FUNCTION \M44CloseFile)
		    DELETEFILE ←(FUNCTION \M44DeleteFile)
		    GETFILEINFO ←(FUNCTION \M44GetFileInfo)
		    GETFILENAME ←(FUNCTION \M44GetFileName)
		    OPENFILE ←(FUNCTION \M44OpenFile)
		    READPAGES ←(FUNCTION \M44ReadPages)
		    SETFILEINFO ←(FUNCTION \M44SetFileInfo)
		    TRUNCATEFILE ←(FUNCTION \M44TruncateFile)
		    WRITEPAGES ←(FUNCTION \M44WritePages)
		    REOPENFILE ←(FUNCTION \M44OpenFile)
		    GENERATEFILES ←(FUNCTION \M44GENERATEFILES)
		    EVENTFN ←(FUNCTION \M44EVENTFN)
		    DIRECTORYNAMEP ←[FUNCTION (LAMBDA (NAME)
                                                             (* Assume host is OK, check that no directory)
			(EQ (NTHCHARCODE NAME -1)
			    (CHARCODE }]
		    HOSTNAMEP ←(FUNCTION NILL)
		    READP ←(FUNCTION \PAGEDREADP)
		    BIN ←(FUNCTION \PAGEDBIN)
		    BOUT ←(FUNCTION \PAGEDBOUT)
		    PEEKBIN ←(FUNCTION \PAGEDPEEKBIN)
		    BACKFILEPTR ←(FUNCTION \PAGEDBACKFILEPTR)
		    SETFILEPTR ←(FUNCTION \PAGEDSETFILEPTR)
		    GETFILEPTR ←(FUNCTION \PAGEDGETFILEPTR)
		    GETEOFPTR ←(FUNCTION \PAGEDGETEOFPTR)
		    EOFP ←(FUNCTION \PAGEDEOFP)
		    BLOCKIN ←(FUNCTION \PAGEDBINS)
		    BLOCKOUT ←(FUNCTION \PAGEDBOUTS)
		    FLUSHOUTPUT ←(FUNCTION \PAGED.FLUSHOUTPUT)))
          (replace DSKOBJ of FDEV with (OR DSKOBJ (SETQ DSKOBJ \MAINDISK)))
          (replace DISKDEVICENAME of DSKOBJ with NAME)
          (RETURN (RESETLST (RESETSAVE NIL (LIST [FUNCTION (LAMBDA (DEV)
						     (COND
						       ((NOT (fetch (M44DEVICE DSKPASSWORDOK)
								of DEV))
                                                             (* Oops, it didn't work, take it away)
							 (\REMOVEDEVICE DEV]
						 FDEV))
			    (\DEFINEDEVICE (fetch DEVICENAME of FDEV)
					   FDEV)
			    (COND
			      ((\OPENDIR FDEV)
				FDEV])

(\OPENDIR
  [LAMBDA (FDEV)                                             (* bvm: " 6-APR-83 12:16")
                                                             (* Opens the model44 directory on the current partition)
    (PROG ((PART (fetch (M44DEVICE DSKPARTITION) of FDEV))
	   STREAM DD)
          (replace (M44DEVICE DSKPASSWORDOK) of FDEV with NIL)
          (COND
	    ((AND (NEQ PART 0)
		  (NOT (\TESTPARTITION PART)))
	      (replace (M44DEVICE SYSDIROFD) of FDEV with NIL)
	      (RETURN)))
          (SETQ STREAM
	    (\M44OPENFILEFROMFP FDEV "SYSDIR.;1" (QUOTE BOTH)
				(create FID
					W0 ← 100000Q
					W1 ← 144Q
					W2 ← 1
					W3 ← 0
					W4 ← 1)))            (* {DSK}SYSDIR.;1 always has sn 100, leader page on 
							     virtual page 1)
          [replace MAXBUFFERS of STREAM with (IMAX 100Q (ADD1 (fetch EPAGE of STREAM]
                                                             (* Enough buffers so that directory is effectively 
							     always in core)
          (replace (M44DEVICE SYSDIROFD) of FDEV with STREAM)
          [COND
	    ((NEQ PART 0)
	      (SETQ DD (\OPENDISKDESCRIPTOR FDEV))
	      (\SETFILEPTR DD \OFFSET.DISKLASTSERIAL#)
	      (\BINS DD (LOCF (fetch (M44DEVICE DISKLASTSERIAL#) of FDEV))
		     0 \NBYTES.DISKINFO)
	      (add (fetch (M44DEVICE DISKLASTSERIAL#) of FDEV)
		   3)                                        (* Try to avoid collisions)
	      (COND
		((NOT (\M44CHECKPASSWORD FDEV))
		  (replace (M44DEVICE SYSDIROFD) of FDEV with NIL)
		  (RETURN]
          (replace (M44DEVICE DSKPASSWORDOK) of FDEV with T)
          (RETURN STREAM])

(\M44CHECKPASSWORD
  [LAMBDA (DEV)                                              (* bvm: "21-NOV-83 17:15")
    (PROG ((STREAM (\OPENFILE (PACK* (QUOTE {)
				     (fetch DEVICENAME of DEV)
				     "}SYS.BOOT;1")
			      (QUOTE INPUT)
			      (QUOTE OLD)))
	   PASSVECTOR BUF PASSINFO ASKEDONCE NAME N)
          (COND
	    ((NULL STREAM)
	      (RETURN T)))
          (SETQ PASSVECTOR (\ALLOCBLOCK (FOLDHI \NWORDS.BCPLPASSWORD WORDSPERCELL)))
          (SETFILEPTR STREAM \OFFSET.BCPLPASSWORD)
          (\BINS STREAM PASSVECTOR 0 (UNFOLD \NWORDS.BCPLPASSWORD BYTESPERWORD))
          (COND
	    ((ZEROP (\GETBASE PASSVECTOR 0))                 (* No password)
	      (\CLOSEFILE STREAM)
	      (RETURN T)))
          (SETFILEPTR STREAM \OFFSET.BCPLUSERNAME)
          [SETQ NAME (ALLOCSTRING (SETQ N (\BIN STREAM]      (* Read in a bcpl string which is the username installed
							     on the disk)
          (\BINS STREAM (fetch (STRINGP BASE) of NAME)
		 0 N)
          (\CLOSEFILE STREAM)
          (SETQ NAME (MKATOM NAME))
      LP  (SETQ PASSINFO (\INTERNAL/GETPASSWORD (fetch DEVICENAME of DEV)
						ASKEDONCE NIL NIL NAME))
          (COND
	    ((NULL PASSINFO)
	      (RETURN NIL)))
          (COND
	    ((UNINTERRUPTABLY
                 (SETQ BUF (\GETPACKETBUFFER))               (* HORRIBLE CHEAP TRICK to get some emulator space)
		 (\BLT (\ADDBASE BUF 64)
		       PASSVECTOR \NWORDS.BCPLPASSWORD)
		 (SetBcplString (\ADDBASE BUF (IPLUS 64 \NWORDS.BCPLPASSWORD))
				(\DECRYPT.PWD (CDR PASSINFO)))
		 (\CHECKBCPLPASSWORD (\ADDBASE BUF (IPLUS 64 \NWORDS.BCPLPASSWORD))
				     (\ADDBASE BUF 64)))
	      (RETURN T))
	    (T (SETQ ASKEDONCE T)
	       (GO LP])

(\M44HOSTNAMEP
  [LAMBDA (NAME DEV)                                         (* bvm: "29-Nov-83 14:47")
    (PROG (PARTNUM)
          (RETURN (COND
		    ((EQ NAME (QUOTE DSK))
		      (\OPENDISKDEVICE 0))
		    ((AND (STRPOS (QUOTE DSK)
				  NAME 1 NIL T)
			  (SETQ PARTNUM (FIXP (SUBATOM NAME 4)))
			  (\TESTPARTITION PARTNUM))
		      (COND
			[(EQ PARTNUM (DISKPARTITION))
			  (RETURN (\GETDEVICEFROMNAME (QUOTE DSK]
			(T (\OPENDISK PARTNUM])
)
(DECLARE: DONTCOPY 
(DECLARE: EVAL@COMPILE 

(RPAQQ \OFFSET.BCPLUSERNAME 512)

(RPAQQ \OFFSET.BCPLPASSWORD 768)

(RPAQQ \NWORDS.BCPLPASSWORD 9)

(CONSTANTS \OFFSET.BCPLUSERNAME \OFFSET.BCPLPASSWORD \NWORDS.BCPLPASSWORD)
)
)
(DECLARE: DONTEVAL@LOAD DOCOPY 
(\VANILLADISKINIT)
(OR (BOUNDP (QUOTE \CONNECTED.DIR))
    (CNDIR))
)



(* SYSOUT etc)

(DEFINEQ

(\COPYSYS
  [LAMBDA (FILE SYSNAME DONTSAVE)                            (* bvm: "24-Jan-84 11:03")
    (DECLARE (GLOBALVARS SYSOUTCURSOR))
    (RESETLST
      (RESETSAVE \VMEM.INHIBIT.WRITE T)                      (* Prevent dirty pages from being written after the 
							     \FLUSHVM)
      (PROG (FL STREAM VAL LASTPAGE)
	RETRY
	    (RECLAIM)
	    (RETURN (PROG1 (COND
			     ([NULL (SETQ VAL
				      (OR (AND (NOT DONTSAVE)
					       (\FLUSHVM))
					  (PROGN (SETQ LASTPAGE (fetch (IFPAGE NActivePages)
								   of \InterfacePage))
                                                             (* Note length of sysout now, because NActivePages can 
							     grow as we prepare to write the sysout)
						 [SETQ FL (OPENFILE FILE (QUOTE OUTPUT)
								    (QUOTE NEW)
								    NIL
								    (CONS (LIST (QUOTE LENGTH)
										(UNFOLD LASTPAGE 
										     BYTESPERPAGE))
									  (QUOTE ((SEQUENTIAL T)
										   (TYPE BINARY]
						 (SETQ STREAM (GETSTREAM FL))
						 (RESETSAVE NIL
							    (LIST [FUNCTION (LAMBDA (FILE)
								      (CLOSEF FILE)
								      (AND RESETSTATE
									   (DELFILE (fetch FULLNAME
										       of FILE]
								  STREAM))
						 (COND
						   (SYSNAME (SET SYSNAME FL)))
						 [RESETSAVE (CURSOR (COND
								      ((type? CURSOR SYSOUTCURSOR)
                                                             (* Comes from a later file)
									SYSOUTCURSOR)
								      (T T]
						 (\COPYSYS1 STREAM LASTPAGE]

          (* First is T when resuming this vmem; second is starting the sysout. If \COPYSYS1 did not itself do a \FLUSHVM, 
	  the second would never return T, yes? NIL is normal return, <fixp> is error return)

                                                             (* Continuing in the current image)
			       FL)
			     ((AND (SMALLP VAL)
				   (IGREATERP 0 VAL))        (* Error occurred while making sysout.)
			       (LISPERROR (IMINUS VAL)
					  FL)
			       (GO RETRY))
			     (T                              (* Starting sysout)
				(\CLEARSYSBUF)
				(CLEARMOUSEBUF)
				(\RESETKEYBOARD)
				(LIST FL)))
			   (\RESETKEYBOARD])

(\COPYSYS1
  [LAMBDA (STREAM LASTPAGE)                                  (* bvm: "24-Jan-84 11:03")
    (COND
      [(AND (type? M44DEVICE (fetch DEVICE of STREAM))
	    (EQ (fetch DEVICENAME of (fetch DEVICE of STREAM))
		(QUOTE DSK)))                                (* Haven't quite worked out the buffer strategy yet)
	(\M44FLUSHDISKDESCRIPTOR (fetch DEVICE of STREAM))
	(replace DDVALID of (fetch DEVICE of STREAM) with NIL)
	(PROG1 (\COPYSYS0SUBR (fetch (ARRAYP BASE) of (fetch FID of STREAM)))
	       (replace LastPage of STREAM with (replace EPAGE of STREAM with (fetch (IFPAGE 
										     NActivePages)
										 of \InterfacePage)))
	       (replace LastOffset of STREAM with (replace EOFFSET of STREAM with 0]
      (T (PROG ((PAGEMAPPED (fetch PAGEMAPPED of (fetch DEVICE of STREAM)))
		(NBUFS (SUB1 \#EMUBUFFERS))
		(BUFBASE \EMUBUFFERS)
		(FIRSTPAGE 2)
		(CURSORBAR \EM.CURSORBITMAP)
		(CURSORMASK (LLSH 1 (SUB1 BITSPERWORD)))
		(DOMINOPAGE (fetch LastDominoFilePage of \InterfacePage))
		CURSORINC CURSORNEXT LASTPAGESEEN NPAGES BUFFERS DAS)
	       (RESETSAVE \#SWAPBUFFERS 1)                   (* Reduce us to one swap buffer, so we can use the rest 
							     for copying the vmem)
	       (RESETSAVE \EMUSWAPBUFFERS (\ADDBASE BUFBASE (UNFOLD NBUFS WORDSPERPAGE)))
	       (RESETSAVE \#DISKBUFFERS (COND
			    ((type? M44DEVICE (fetch DEVICE of STREAM))
                                                             (* Need some space for disk to run)
			      (SETQ NBUFS (LRSH NBUFS 1))
			      (SETQ BUFBASE (\ADDBASE BUFBASE (UNFOLD NBUFS WORDSPERPAGE)))
			      NBUFS)
			    (T 0)))
	       (SETQ DAS (ARRAY (IPLUS NBUFS 2)
				(QUOTE WORD)
				NIL 0))
	       (SETQ BUFFERS (to NBUFS as (BUF ← BUFBASE) by (\ADDBASE BUF WORDSPERPAGE)
				collect BUF))
	       [SETQ CURSORINC (SETQ CURSORNEXT (FOLDLO LASTPAGE (ITIMES 16 16]
                                                             (* How often to do something to the cursor)
	       (COND
		 ((ZEROP DOMINOPAGE)                         (* First page to write is the ISF map page, which should
							     be blank in a sysout)
		   (\ZEROPAGE (fetch (POINTER PAGE#) of BUFBASE))
		   (SETA DAS 1 (fetch ISFDA2 of \ISFMAP)))
		 ((EQ \MACHINETYPE \DANDELION)
		   (\DL.ACTONVMEMFILE DOMINOPAGE BUFBASE 1))
		 (T (SETA DAS 1 (\LOOKUPFMAP DOMINOPAGE))    (* Copy the first domino page, stashed at the end of the
							     Domino area, into page 1 for a good Dandelion boot 
							     image)
		    (\ACTONDISKPAGES \MAINDISK BUFBASE (fetch (ARRAYP BASE) of DAS)
				     (SUB1 DOMINOPAGE)
				     \ISFMAP DOMINOPAGE DOMINOPAGE \DC.READD)
		    (SETA DAS 1 (fetch ISFDA2 of \ISFMAP))   (* Prepare DAS to start reading at page 2)
		    ))
	       (COND
		 (PAGEMAPPED (replace EPAGE of STREAM with LASTPAGE)
                                                             (* Set up end of file correctly.
							     LASTPAGE is last alto page (full), which is last Lisp 
							     page plus 1)
			     (replace EOFFSET of STREAM with 0)
			     (\WRITEPAGES STREAM 0 (CAR BUFFERS)))
		 (T (\BOUTS STREAM (CAR BUFFERS)
			    0 BYTESPERPAGE)))
	       (while (ILEQ FIRSTPAGE LASTPAGE)
		  do [COND
		       ((IGEQ FIRSTPAGE CURSORNEXT)          (* Gradually complement the cursor)
			 (\PUTBASE CURSORBAR 0 (LOGXOR (\GETBASE CURSORBAR 0)
						       CURSORMASK))
			 (add CURSORNEXT CURSORINC)
			 (COND
			   ((ZEROP (SETQ CURSORMASK (LRSH CURSORMASK 1)))
			     (SETQ CURSORBAR (\ADDBASE CURSORBAR 1))
			     (SETQ CURSORMASK (LLSH 1 (SUB1 BITSPERWORD]
		     [COND
		       ((EQ \MACHINETYPE \DANDELION)
			 [\DL.ACTONVMEMFILE FIRSTPAGE BUFBASE (SETQ NPAGES
					      (IMIN NBUFS (ADD1 (IDIFFERENCE LASTPAGE FIRSTPAGE]
			 (SETQ LASTPAGESEEN (IPLUS FIRSTPAGE NPAGES -1)))
		       (T (for I from 2 to (ADD1 NBUFS) do (SETA DAS I \FILLINDA))
			  (SETQ LASTPAGESEEN (\ACTONDISKPAGES \MAINDISK BUFFERS (fetch (ARRAYP BASE)
										   of DAS)
							      (SUB1 FIRSTPAGE)
							      \ISFMAP FIRSTPAGE
							      (IMIN (IPLUS FIRSTPAGE NBUFS -1)
								    LASTPAGE)
							      \DC.READD))
			  (SETQ NPAGES (ADD1 (IDIFFERENCE LASTPAGESEEN FIRSTPAGE)))
			  (SETA DAS 1 (ELT DAS (ADD1 NPAGES]
                                                             (* Read NBUFS pages from vmem)
		     [COND
		       ((NOT PAGEMAPPED)                     (* Have to just ship the bits)
			 (\BOUTS STREAM BUFBASE 0 (UNFOLD NPAGES BYTESPERPAGE)))
		       (T (\WRITEPAGES STREAM (SUB1 FIRSTPAGE)
				       (COND
					 ((IGEQ LASTPAGESEEN LASTPAGE)
					   BUFFERS)
					 (T                  (* Don't write too many pages on the last pass if NPAGES
							     is less than length of BUFFERS)
					    (to NPAGES as BUF in BUFFERS collect BUF]
                                                             (* Write them to output)
		     (SETQ FIRSTPAGE (ADD1 LASTPAGESEEN)))
	       (RETURN NIL])
)



(* Stats code. On MOD44IO because it writes on the disk and uses records not exported from 
MOD44IO)

(DEFINEQ

(GATHERSTATS
  [LAMBDA (FILENAME)                                         (* bvm: "21-NOV-83 17:17")

          (* Enables and disables statistics gathering. Uses low level file operations to avoid stats file being visible 
	  from Lisp b/c the file position is not updated as it is written)


    (DECLARE (GLOBALVARS \STATSON))
    (COND
      ((EQ \MACHINETYPE \DANDELION)
	(ERROR "Stats not implemented for this type of machine" FILENAME))
      [FILENAME (AND \STATSON (GATHERSTATS))
		(SELECTQ (FILENAMEFIELD FILENAME (QUOTE HOST))
			 (DSK)
			 (NIL (SETQ FILENAME (PACKFILENAME (QUOTE HOST)
							   (QUOTE DSK)
							   (QUOTE BODY)
							   FILENAME)))
			 (ERROR "Stats file must be on DSK" FILENAME))
		(SETQ \STATSON T)
		(\GATHERSTATS (PROG [(STREAM (\OPENFILE FILENAME (QUOTE OUTPUT)
							(QUOTE NEW]
                                                             (* CLose before doing stats, cause file isn't really 
							     open from Lisp's point of view.)
				    (RETURN (fetch (ARRAYP BASE) of (fetch FID
								       of (PROG1 STREAM (\CLOSEFILE
										   STREAM]
      (\STATSON (\GATHERSTATS)
		(SETQ \STATSON NIL])
)

(RPAQQ \STATSON NIL)
(DECLARE: EVAL@COMPILE DONTCOPY 
(DECLARE: DOEVAL@COMPILE DONTCOPY

(LOCALVARS . T)
)

(FILESLOAD (LOADCOMP)
	   LLBFS)
)
(PUTPROPS MOD44IO COPYRIGHT ("Xerox Corporation" 1981 1982 1983 1984))
(DECLARE: DONTCOPY
  (FILEMAP (NIL (2423 38289 (\M44AddDiskPages 2433 . 3414) (\M44AllocFilePageMap 3416 . 3863) (
\M44CloseFile 3865 . 4419) (\M44CompleteFH 4421 . 7214) (\M44CREATEFILE 7216 . 10513) (\M44DeleteFile 
10515 . 11605) (\M44EVENTFN 11607 . 12734) (\M44ExtendFilePageMap 12736 . 14021) (\M44FillInMap 14023
 . 14476) (\M44GENERATEFILES 14478 . 16734) (\M44GetAccessTime 16736 . 17362) (\M44GetFileHandle 17364
 . 19043) (\M44GetFileInfo 19045 . 20631) (\M44GetFileName 20633 . 20846) (\M44GetPageLoc 20848 . 
21560) (\M44KillFilePageMap 21562 . 21892) (\M44MAKEDIRENTRY 21894 . 23407) (\M44NEXTFILEFN 23409 . 
25678) (\M44OpenFile 25680 . 28146) (\M44OPENFILEFROMFP 28148 . 28802) (\M44ReadDiskPage 28804 . 30007
) (\M44ReadLeaderPage 30009 . 30822) (\M44ReadPages 30824 . 31155) (\M44ReleasePages 31157 . 31798) (
\M44SetAccessTimes 31800 . 32710) (\M44SetEndOfFile 32712 . 33760) (\M44SetFileInfo 33762 . 34089) (
\M44TruncateFile 34091 . 35099) (\M44WriteDiskPage 35101 . 37281) (\M44WriteLeaderPage 37283 . 37755) 
(\M44WritePages 37757 . 38287)) (38290 51770 (\ADDDISKPAGES 38300 . 39691) (\GETPAGEHINT 39693 . 40851
) (\M44DELETEPAGES 40853 . 43701) (\ASSIGNDISKPAGE 43703 . 45977) (\COUNTDISKFREEPAGES 45979 . 46711) 
(\M44MARKPAGEFREE 46713 . 47624) (\M44FLUSHDISKDESCRIPTOR 47626 . 48391) (\MAKELEADERDAS 48393 . 49005
) (\CREATE.FID.FOR.DD 49007 . 49643) (\OPENDISK 49645 . 50585) (DISKFREEPAGES 50587 . 51600) (VMEMSIZE
 51602 . 51768)) (52866 71117 (\FILESPEC 52876 . 55014) (\FINDDIRHOLE 55016 . 56109) (\LISPFILENAME 
56111 . 56829) (\LOOKUPVERSIONS 56831 . 61495) (\OPENDISKDESCRIPTOR 61497 . 63160) (\READDIRFPTR 63162
 . 63605) (\SEARCHDIR1 63607 . 65949) (\UNPACKFILENAME 65951 . 70763) (\WRITEDIRFPTR 70765 . 71115)) (
71118 71712 (ALTOFILENAME 71128 . 71710)) (72300 79914 (\VANILLADISKINIT 72310 . 73505) (
\OPENDISKDEVICE 73507 . 75955) (\OPENDIR 75957 . 77713) (\M44CHECKPASSWORD 77715 . 79444) (
\M44HOSTNAMEP 79446 . 79912)) (80276 87769 (\COPYSYS 80286 . 82502) (\COPYSYS1 82504 . 87767)) (87879 
89089 (GATHERSTATS 87889 . 89087)))))
STOP