<> <> <> <> <> <<>> DIRECTORY BasicTime USING [ FromPupTime, Now, ToPupTime ], BootFile USING [Entry, Header, maxEntriesPerHeader, maxEntriesPerTrailer, MemorySizeToFileSize, TrailerArray, TrailerStart ], Booting USING [ Boot, Switches, switches ], DebuggerFormat USING [ExternalStateVector, LabelChecksum, Run, SwapInfo, VersionID, VMRunTable ], Disk USING [ Channel, DoIO, DriveAttributes, Label, NextChannel, ok, Request, Status ], DiskFace USING [ Type ], File USING [Error, FindVM, FP, Handle, Info, Open, PageCount, PageNumber, Read, SetSize, SystemVolume, wordsPerPage, Write ], FileBackdoor USING [GetRoot], IO USING [ GetChar, Flush, PutChar, PutF, PutRope, STREAM ], PrincOps USING [ flagsVacant, PageCount, PageNumber, PageValue, PDA, wordsPerPage ], PrincOpsUtils USING [ LongCopy ], ProcessorFace USING [ GetNextAvailableVM ], Rope USING [ ROPE ], SimpleTerminal USING [ TurnOn ], SystemVersion USING [ bootFileDate, Date, release, ReleaseNumber ], VM USING [AddressForPageNumber, CantAllocate, Interval, PageCount, PageNumber, PagesForWords, Pin, SimpleAllocate], VMBacking USING [StateFromPageValue]; RollbackImpl: MONITOR IMPORTS BasicTime, BootFile, Booting, DebuggerFormat, Disk, File, FileBackdoor, IO, PrincOpsUtils, ProcessorFace, SimpleTerminal, SystemVersion, VM, VMBacking = { vmPagesPerFilePage: VM.PageCount = VM.PagesForWords[File.wordsPerPage]; valuesPerPage: CARDINAL = PrincOps.wordsPerPage / SIZE[PrincOps.PageValue]; PageValueArray: TYPE = ARRAY [0..valuesPerPage) OF PrincOps.PageValue; mapEntriesPerPage: CARDINAL = valuesPerPage; <> outLdFile: File.Handle _ NIL; outLdFirstPage: File.PageNumber; outLdSpace: VM.Interval = VM.SimpleAllocate[vmPagesPerFilePage]; outLdPage: File.PageNumber _ [LAST[INT]]; outLdAddress: LONG POINTER TO ARRAY [0..PrincOps.wordsPerPage) OF WORD = VM.AddressForPageNumber[outLdSpace.page]; ReadOutLdPage: PROC [page: File.PageNumber] = { IF outLdPage # page THEN File.Read[outLdFile, outLdPage_page, 1, outLdAddress]; }; <> BFMEntryIndex: TYPE = [0..MAX[BootFile.maxEntriesPerHeader, BootFile.maxEntriesPerTrailer]]; BootFileMapItem: TYPE = RECORD [ link: BFMPointer _ NIL, lastMemPage: PrincOps.PageNumber, filePageOffset: File.PageNumber, entryCount: BFMEntryIndex, entries: EntrySeq]; EntrySeq: TYPE = LONG POINTER TO BootFile.TrailerArray; BFMPointer: TYPE = REF BootFileMapItem; bfmHead: BFMPointer _ NIL; OpenOutLdFile: PROC = { bfm: BFMPointer; bfh: LONG POINTER TO BootFile.Header; entries: EntrySeq; curmax: CARDINAL _ BootFile.maxEntriesPerHeader; curbase: File.PageNumber _ [0]; OutLdPageSpace: PROC RETURNS [vm: LONG POINTER] = { <> space: VM.Interval = VM.SimpleAllocate[vmPagesPerFilePage]; vm _ VM.AddressForPageNumber[space.page]; File.Read[outLdFile, curbase, 1, vm]; }; pagesremaining: CARDINAL; bfmHead _ NIL; bfh _ OutLdPageSpace[]; entries _ LOOPHOLE[@bfh.entries]; pagesremaining _ bfh.countData; bfmHead _ bfm _ NEW[BootFileMapItem]; DO bfm.entries _ entries; bfm.entryCount _ MIN[curmax, pagesremaining]; bfm.lastMemPage _ bfm.entries[bfm.entryCount-1].page; bfm.filePageOffset _ [curbase+1]; pagesremaining _ pagesremaining - bfm.entryCount; IF pagesremaining = 0 THEN EXIT; curbase _ [bfm.filePageOffset+bfm.entryCount]; curmax _ BootFile.maxEntriesPerTrailer; entries _ OutLdPageSpace[] + BootFile.TrailerStart; bfm.link _ NEW[BootFileMapItem]; bfm _ bfm.link; ENDLOOP; }; PageNotFound: ERROR[mempage: PrincOps.PageNumber] = CODE; MalformedOutload: ERROR = CODE; SearchOutLdFile: PROCEDURE [mempage: PrincOps.PageNumber] = { <> FOR bfm: BFMPointer _ bfmHead, bfm.link UNTIL bfm = NIL DO IF mempage <= PrincOps.PageNumber[bfm.lastMemPage] THEN { <> FOR i: BFMEntryIndex IN [0..bfm.entryCount] DO SELECT PrincOps.PageNumber[bfm.entries[i].page] FROM < mempage => NULL; > mempage => ERROR PageNotFound[mempage]; ENDCASE => { ReadOutLdPage[[bfm.filePageOffset+i]]; RETURN }; REPEAT FINISHED => ERROR MalformedOutload[]; ENDLOOP; }; REPEAT FINISHED => ERROR PageNotFound[mempage] ENDLOOP; }; CopyRead: PUBLIC PROC [from: INT, nwords: INT, to: LONG POINTER] = { <> n: PrincOps.PageNumber; w: [0..PrincOps.wordsPerPage); WHILE nwords > 0 DO amount: (0..PrincOps.wordsPerPage]; n _ from / PrincOps.wordsPerPage; w _ from - n * LONG[PrincOps.wordsPerPage]; SearchOutLdFile[n]; amount _ MIN[nwords,PrincOps.wordsPerPage-w]; PrincOpsUtils.LongCopy[from: @outLdAddress[w], to: to, nwords: amount]; nwords _ nwords - amount; from _ from + amount; to _ to + amount; ENDLOOP; }; <> UnknownDrive: ERROR = CODE; BadTransfer: ERROR = CODE; DiskAddress: TYPE = RECORD[ deviceType: DiskFace.Type, deviceOrdinal: CARDINAL, diskPage: LONG CARDINAL, -- device-relative page number of start of run offset: CARDINAL, -- page number from start of run, for label checksum calculation labelCheck: CARDINAL -- label checksum of start of run ]; lastChannel: Disk.Channel _ NIL; lastType: DiskFace.Type; lastOrdinal: CARDINAL; TransferBackingRun: PUBLIC ENTRY PROC [ data: LONG POINTER, label: LONG POINTER TO Disk.Label, which: { readAndChecksum, readAndVerify, write }, count: INT, addr: DiskAddress] = { <> ENABLE UNWIND => NULL; req: Disk.Request; status: Disk.Status; countDone: INT; IF lastChannel = NIL OR addr.deviceType # lastType OR addr.deviceOrdinal # lastOrdinal THEN { FOR lastChannel _ Disk.NextChannel[NIL], Disk.NextChannel[lastChannel] UNTIL lastChannel = NIL DO [type: lastType, ordinal: lastOrdinal] _ Disk.DriveAttributes[lastChannel]; IF addr.deviceType = lastType AND addr.deviceOrdinal = lastOrdinal THEN EXIT; REPEAT FINISHED => ERROR UnknownDrive[]; ENDLOOP; }; req _ [ diskPage: [addr.diskPage], data: data, command: SELECT which FROM readAndChecksum => [header: verify, label: read, data: read], readAndVerify => [header: verify, label: verify, data: read], write => [header: verify, label: verify, data: write], ENDCASE => ERROR, count: count ]; [status, countDone] _ Disk.DoIO[lastChannel, label, @req]; IF status # Disk.ok THEN ERROR BadTransfer[]; IF which = readAndChecksum AND DebuggerFormat.LabelChecksum[label^, addr.offset + count-1] # addr.labelCheck THEN ERROR BadTransfer[]; label.filePage _ label.filePage - (count-1); }; <> ReadBackingMap: PROC RETURNS [table: REF DebuggerFormat.VMRunTable] = { swapInfo: DebuggerFormat.SwapInfo; swapInfoAddr: INT = LOOPHOLE[@PrincOps.PDA.available]; esv: DebuggerFormat.ExternalStateVector; esvAddr: INT; CopyRead[from: swapInfoAddr, nwords: SIZE[DebuggerFormat.SwapInfo], to: @swapInfo]; esvAddr _ LOOPHOLE[swapInfo.externalStateVector]; CopyRead[esvAddr, SIZE[DebuggerFormat.ExternalStateVector], @esv]; SELECT esv.versionident FROM DebuggerFormat.VersionID => { IF esv.vmRunTable = NIL THEN table _ NIL ELSE { temp: ARRAY [0..SIZE[DebuggerFormat.VMRunTable[0]]) OF WORD; cheat: POINTER TO DebuggerFormat.VMRunTable = LOOPHOLE[@temp]; CopyRead[ LOOPHOLE[esv.vmRunTable, INT], SIZE[DebuggerFormat.VMRunTable[0]], @temp ]; table _ NEW[DebuggerFormat.VMRunTable[cheat.length]]; CopyRead[ LOOPHOLE[esv.vmRunTable, INT], SIZE[DebuggerFormat.VMRunTable[cheat.length]], LOOPHOLE[table, LONG POINTER] ]; }; }; ENDCASE => table _ NIL; }; <> in, out: IO.STREAM; vmImageVersion: CARDINAL = 1; VMImageHeader: TYPE = MACHINE DEPENDENT RECORD[ version(0): CARDINAL _ vmImageVersion, runs(1): INT, pages(3): INT, bootVersion(5): SystemVersion.ReleaseNumber, bootFileDate(8): SystemVersion.Date, checkpointDate(10): SystemVersion.Date ]; VMImageRun: TYPE = MACHINE DEPENDENT RECORD[ pages: CARDINAL, deviceType: DiskFace.Type, deviceOrdinal: CARDINAL, diskPage: LONG CARDINAL, label: Disk.Label]; vmImageRunsPerPage: CARDINAL = File.wordsPerPage / SIZE[VMImageRun]; VMImageRunTable: TYPE = ARRAY [0..vmImageRunsPerPage) OF VMImageRun; vmImageBufferPages: NAT _ 256; NoBackingStore: ERROR[PrincOps.PageNumber] = CODE; ShortFile: ERROR = CODE; WrongPageCount: ERROR = CODE; which: { checkpoint, rollback }; Complain: PROC [msg: Rope.ROPE] = { out.PutRope[msg]; out.PutRope["\nType any character to "]; out.PutRope[IF which = checkpoint THEN "resume the outloaded session: " ELSE "re-boot with the \"f\" switch: "]; in.Flush[]; [] _ in.GetChar[]; IF which = checkpoint THEN [] _ Booting.Boot[boot: [file[outLdFile, outLdFirstPage]], switches: ALL[FALSE] ] ELSE [] _ Booting.Boot[boot: [self[]], switches: [f: TRUE] ]; ERROR }; DoIt: PROC = TRUSTED { ENABLE { File.Error => Complain[SELECT why FROM wentOffline => "System volume has gone offline", nonCedarVolume => "System volume is not a cedar volume", inconsistent => "System volume or checkpoint file is inconsistent - scavenge", software => "Label-check when accessing the checkpoint file", hardware => "Hard disk error when accessing the checkpoint file", unknownFile => "The checkpoint file does not exist", unknownPage => "Bogus checkpoint file (unknownPage)", volumeFull => "Can't checkpoint (system volume full)", fragmented => "Can't checkpoint (system volume fragmented)", ENDCASE => "Unexpected file error - consult expert"]; PageNotFound => Complain["Can't find required swapped-in page in outload"]; MalformedOutload => Complain["The outload appears to be malformed"]; UnknownDrive => Complain["Can't find the disk drive for a swapped-out page"]; BadTransfer => Complain["Disk error when accessing a swapped out page"]; NoBackingStore => Complain["Can't find the backing store for a swapped out page"]; ShortFile => Complain["The checkpoint file appears to be truncated"]; WrongPageCount => Complain["I'm confused about how many pages to transfer"]; }; outloadDataPages: PrincOps.PageCount; countVM: INT _ 0; mapPages: CARDINAL; mapBufferSpace: VM.Interval = VM.SimpleAllocate[VM.PagesForWords[File.wordsPerPage]]; mapBuffer: LONG POINTER TO PageValueArray = VM.AddressForPageNumber[mapBufferSpace.page]; mapStart: File.PageNumber; -- start of map dump in checkpoint file vmImagePos: File.PageNumber; -- start of vmImage in checkpoint file vmImageBufferSpace: VM.Interval = AllocateBuffer[]; vmImageBufferAddr: LONG POINTER = VM.AddressForPageNumber[vmImageBufferSpace.page]; vmImageHeader: LONG POINTER TO VMImageHeader = vmImageBufferAddr; vmImageRunTable: REF VMImageRunTable _ NEW[VMImageRunTable]; AllocateBuffer: PROC RETURNS [interval: VM.Interval] = { <> DO RETURN [VM.SimpleAllocate[vmImageBufferPages ! VM.CantAllocate => IF vmImageBufferPages > 4 THEN {vmImageBufferPages _ vmImageBufferPages / 2; LOOP} ELSE REJECT; ]]; ENDLOOP; }; GetMapStart: PROC RETURNS [File.PageNumber] = TRUSTED { fp: File.FP; header: LONG POINTER TO BootFile.Header = LOOPHOLE[mapBuffer]; [fp: fp, page: outLdFirstPage] _ FileBackdoor.GetRoot[File.SystemVolume[], checkpoint]; outLdFile _ File.Open[File.SystemVolume[], fp]; File.Read[file: outLdFile, from: outLdFirstPage, nPages: 1, to: header]; outloadDataPages _ header.countData; RETURN [[outLdFirstPage+BootFile.MemorySizeToFileSize[outloadDataPages]]] }; [in: in, out: out] _ SimpleTerminal.TurnOn[]; VM.Pin[vmImageBufferSpace]; out.PutRope[IF which = checkpoint THEN "\nCheckpoint." ELSE "\nRollback."]; { count: PrincOps.PageCount; page: PrincOps.PageNumber; FOR countVM _ 0, page + count DO [firstPage: page, count: count] _ ProcessorFace.GetNextAvailableVM[countVM]; IF count = 0 THEN EXIT; ENDLOOP; }; mapPages _ (countVM + mapEntriesPerPage - 1) / mapEntriesPerPage; mapStart _ GetMapStart[]; vmImagePos _ [mapStart+mapPages]; IF which = checkpoint THEN { <> GetMapPage: PROC [p: CARDINAL] = TRUSTED { File.Read[file: outLdFile, from: [mapStart+p], nPages: 1, to: mapBuffer] }; PutMapPage: PROC [p: CARDINAL] = TRUSTED { File.Write[file: outLdFile, to: [mapStart+p], nPages: 1, from: mapBuffer] }; firstEnumerate: BOOL _ TRUE; InterestingPage: PROC [e: [0..mapEntriesPerPage)] RETURNS [yes: BOOL] = TRUSTED { mapEntry: PrincOps.PageValue = mapBuffer[e]; IF mapEntry.state.flags = PrincOps.flagsVacant THEN yes _ VMBacking.StateFromPageValue[mapEntry] = active ELSE { yes _ FALSE; <> <> <<( "firstEnumerate" optimizes so we only update the entry once )>> IF firstEnumerate THEN { mapBuffer[e].state.flags.referenced _ TRUE; mapBuffer[e].state.flags.dirty _ TRUE; }; }; }; firstPassCount: INT _ 0; -- consistency check between passes of EnumerateRuns table: REF DebuggerFormat.VMRunTable; <> runSize: CARDINAL; -- pages in current run; runStart: VM.PageNumber; -- page number of first page in run backingRun: LONG POINTER TO DebuggerFormat.Run _ NIL; -- covers entire run EnumerateRuns: PROC [work: PROC] = { <> pageNumber: VM.PageNumber _ 0; runSize _ 0; FOR p: CARDINAL IN [0..mapPages) DO GetMapPage[p]; FOR e: [0..mapEntriesPerPage) IN [0..mapEntriesPerPage) DO interesting: BOOL = InterestingPage[e]; IF runSize > 0 AND (NOT interesting OR pageNumber NOT IN [backingRun.page..backingRun.page+backingRun.count) ) THEN { -- end of current run work[]; runSize _ 0; }; IF interesting THEN { IF runSize = 0 THEN { <> IF backingRun = NIL OR pageNumber NOT IN [backingRun.page..backingRun.page+backingRun.count) THEN { -- find entry in backing run table IF table = NIL THEN ERROR NoBackingStore[pageNumber]; FOR i: CARDINAL IN [0..table.nRuns) DO backingRun _ @table[i]; IF pageNumber IN [backingRun.page..backingRun.page+backingRun.count) THEN EXIT; REPEAT FINISHED => ERROR NoBackingStore[pageNumber] ENDLOOP; }; runStart _ pageNumber; }; runSize _ runSize+1; }; pageNumber _ pageNumber+1; ENDLOOP; IF firstEnumerate THEN PutMapPage[p]; ENDLOOP; IF runSize > 0 THEN work[]; firstEnumerate _ FALSE; }; OpenOutLdFile[]; table _ ReadBackingMap[]; vmImageHeader^ _ [ runs: 0, pages: 0, bootVersion: SystemVersion.release, bootFileDate: SystemVersion.bootFileDate, checkpointDate: BasicTime.ToPupTime[BasicTime.Now[]] ]; { -- First pass: count runs to determine file size needed fileSize: File.PageCount; -- required checkpoint file size CountRuns: PROC = { vmImageHeader.runs _ vmImageHeader.runs+1; vmImageHeader.pages _ vmImageHeader.pages + runSize; }; out.PutRope["\nCounting swapped-out VM pages ... "]; EnumerateRuns[CountRuns]; firstPassCount _ vmImageHeader.pages; fileSize _ vmImagePos+1+vmImageHeader.pages+ (vmImageHeader.runs +vmImageRunsPerPage-1) / vmImageRunsPerPage; out.PutF["\nThe VM has %g pages, of which %g are swapped-in and %g are swapped-out (in %g runs).\nEnsuring checkpoint file length is at least %g ... ", [integer[countVM]], [integer[outloadDataPages]], [integer[vmImageHeader.pages]], [integer[vmImageHeader.runs]], [integer[fileSize]] ]; IF File.Info[outLdFile].size < fileSize THEN File.SetSize[outLdFile, fileSize]; }; -- first pass <> File.Write[outLdFile, vmImagePos, 1, vmImageHeader]; vmImagePos _ [vmImagePos+1]; { -- Second pass: transfer the pages vmImageRunTablePos: [0..vmImageRunsPerPage] _ 0; pages: CARDINAL _ 0; -- pages used in vmImageBuffer Copier: PROC = -- Add run to the vmImageRuntable we're building { offset: CARDINAL = runStart-backingRun.page; vmImageRunTable[vmImageRunTablePos] _ [ pages: runSize, deviceType: backingRun.deviceType, deviceOrdinal: backingRun.deviceOrdinal, diskPage: backingRun.diskPage + offset, label: NULL ]; <> TransferBackingRun[ data: vmImageBufferAddr + pages * PrincOps.wordsPerPage, label: @vmImageRunTable[vmImageRunTablePos].label, which: readAndChecksum, count: 1, addr: [ deviceType: vmImageRunTable[vmImageRunTablePos].deviceType, deviceOrdinal: vmImageRunTable[vmImageRunTablePos].deviceOrdinal, diskPage: vmImageRunTable[vmImageRunTablePos].diskPage, offset: offset, labelCheck: backingRun.labelCheck] ]; vmImageRunTablePos _ vmImageRunTablePos+1; IF vmImageRunTablePos = vmImageRunsPerPage THEN CopyToBuffer[]; }; CopyToBuffer: PROC = { <> LOOPHOLE[vmImageBufferAddr + pages * PrincOps.wordsPerPage, LONG POINTER TO VMImageRunTable]^ _ vmImageRunTable^; pages _ pages + 1; firstPassCount _ firstPassCount + 1; IF pages = vmImageBufferPages THEN WriteBuffer[]; FOR r: CARDINAL IN [0..vmImageRunTablePos) DO total: CARDINAL = vmImageRunTable[r].pages; reqd: CARDINAL _ total; WHILE reqd > 0 DO amount: CARDINAL = MIN[vmImageBufferPages-pages, reqd]; label: Disk.Label _ vmImageRunTable[r].label; label.filePage _ label.filePage + (total-reqd); TransferBackingRun[ data: vmImageBufferAddr + pages * PrincOps.wordsPerPage, label: @label, which: readAndVerify, count: amount, addr: [ deviceType: vmImageRunTable[r].deviceType, deviceOrdinal: vmImageRunTable[r].deviceOrdinal, diskPage: vmImageRunTable[r].diskPage + (total-reqd), offset: 0, labelCheck: 0 ] ]; pages _ pages + amount; IF pages = vmImageBufferPages THEN WriteBuffer[]; reqd _ reqd - amount; ENDLOOP; ENDLOOP; vmImageRunTablePos _ 0; }; WriteBuffer: PROC = { <> File.Write[outLdFile, vmImagePos, pages, vmImageBufferAddr]; firstPassCount _ firstPassCount - pages; vmImagePos _ [vmImagePos+pages]; out.PutChar['.]; pages _ 0; }; out.PutF["ok\nCopying VM into checkpoint file (each dot = %g pages transferred) ", [integer[vmImageBufferPages]]]; EnumerateRuns[Copier]; IF vmImageRunTablePos # 0 THEN CopyToBuffer[]; WriteBuffer[]; IF firstPassCount # 0 THEN ERROR WrongPageCount[]; }; -- second pass } ELSE { <> remainingFile: File.PageCount; -- amount of file that should remain remainingRuns: INT; pages: CARDINAL _ 0; -- position in vmImageBuffer ReadBuffer: PROC = { amount: File.PageCount = MIN[vmImageBufferPages, remainingFile]; File.Read[outLdFile, vmImagePos, amount, vmImageBufferAddr]; vmImagePos _ [vmImagePos+amount]; remainingFile _ remainingFile - amount; pages _ 0; out.PutChar['.]; }; ReadImageRunTable: PROC = { vmImageRunTable^ _ LOOPHOLE[vmImageBufferAddr + pages * PrincOps.wordsPerPage, LONG POINTER TO VMImageRunTable]^; pages _ pages + 1; IF pages = vmImageBufferPages THEN ReadBuffer[]; }; File.Read[outLdFile, vmImagePos, 1, vmImageHeader]; vmImagePos _ [vmImagePos+1]; out.PutF["\nThis checkpoint was created on %g by boot file version %g.%g.%g of %g", [time[BasicTime.FromPupTime[vmImageHeader.checkpointDate]]], [integer[vmImageHeader.bootVersion.major]], [integer[vmImageHeader.bootVersion.minor]], [integer[vmImageHeader.bootVersion.patch]], [time[BasicTime.FromPupTime[vmImageHeader.bootFileDate]]] ]; out.PutF["\nThere were %g swapped-in pages, and %g swapped-out pages in %g runs", [integer[outloadDataPages]], [integer[vmImageHeader.pages]], [integer[vmImageHeader.runs]] ]; remainingFile _ vmImageHeader.pages+ (vmImageHeader.runs +vmImageRunsPerPage-1) / vmImageRunsPerPage; IF File.Info[outLdFile].size < vmImagePos+remainingFile THEN ERROR ShortFile[]; remainingRuns _ vmImageHeader.runs; <> out.PutF["\nCopying VM from checkpoint file (each dot = %g pages transferred) ", [integer[vmImageBufferPages]]]; ReadBuffer[]; WHILE remainingRuns > 0 DO runsThisTime: CARDINAL = MIN[remainingRuns, vmImageRunsPerPage]; remainingRuns _ remainingRuns - runsThisTime; ReadImageRunTable[]; FOR r: CARDINAL IN [0 .. runsThisTime) DO total: CARDINAL = vmImageRunTable[r].pages; reqd: CARDINAL _ total; WHILE reqd > 0 DO amount: CARDINAL = MIN[vmImageBufferPages-pages, reqd]; label: Disk.Label _ vmImageRunTable[r].label; label.filePage _ label.filePage + (total-reqd); TransferBackingRun[ data: vmImageBufferAddr + pages * PrincOps.wordsPerPage, label: @label, which: write, count: amount, addr: [ deviceType: vmImageRunTable[r].deviceType, deviceOrdinal: vmImageRunTable[r].deviceOrdinal, diskPage: vmImageRunTable[r].diskPage + (total-reqd), offset: 0, labelCheck: 0 ] ]; pages _ pages + amount; IF pages = vmImageBufferPages THEN ReadBuffer[]; reqd _ reqd - amount; ENDLOOP; ENDLOOP; ENDLOOP; }; out.PutRope[" ok\nInloading real memory from checkpoint file ... "]; IF Booting.switches[l] THEN { <> out.PutRope["\nPausing (\"L\" switch). Type any character to continue: "]; [] _ in.GetChar[]; }; [] _ Booting.Boot[boot: [file[outLdFile, outLdFirstPage]], switches: ALL[FALSE] ]; ERROR }; IF Booting.switches[v] THEN { which _ checkpoint; DoIt[] }; IF Booting.switches[r] THEN { which _ rollback; DoIt[] }; [] _ File.FindVM[]; -- in this case we're continuing with the full boot sequence }.