<> <> <> <<>> <> <<>> DIRECTORY Basics USING [BITAND, BITSHIFT, Comparison], Camelot USING [btidT, DSLogNewValue, DSPinObject, -- ErSuccess, -- optrT, segmentIdT, -- TABegin, TAEnd, -- tidT], CamelotRecoverable USING [CamelotRecoveryComplete], Mach USING [kernReturnT, -- KernSuccess, -- pointerT, taskSelf, vmAddressT, vmAllocate, vmAllocateWithPager, vmDeallocate], PBasics USING [bytesPerWord, Copy, ShiftRight, Word], RedBlackTree USING [Compare, Create, Delete, GetKey, Insert, Lookup, Node, Table, UserData], Rope USING [ROPE], YggCamelotSegment USING [DIDMapLeaderPage, NextDIDPage, PageUse, PageZero, SegmentUseType, SegPort], YggDID USING [DID], YggDIDPrivate USING [DIDRep, HighDIDFirstClient], YggDIDMap USING [AddRuns, NullRun, RemoveRuns, Run, RunList, SetByteSize], YggEnvironment USING [dsPort, nullTransID, Outcome -- , taPort -- ], YggFile USING [bytesPerPage, Error, logBytesPerPage, logWordsPerPage, PageCount, PageNumber, Reason, wordsPerPage], YggFileInternal USING[Alloc, AllocAndFree, AllocForCreate, AllocWord, ClearLatches, FileHandleRep, FileMod, Free, Lookup, Modification, ModType, opRun, PTAllocBitmap, RunOpsList, SegmentMetadata, SegmentMetadataList], YggInternal USING[FileHandle], YggLogBasic USING[EstablishLogFile, OpenForPut], YggTransaction USING[CreateTrans, EqualTrans, Finish, GetParent, IsNullTrans, IsTopLevel]; YggFileImpl: CEDAR MONITOR IMPORTS Basics, Camelot, CamelotRecoverable, Mach, PBasics, RedBlackTree, YggDIDMap, YggEnvironment, YggFile, YggFileInternal, YggLogBasic, YggTransaction EXPORTS YggDID, YggFile, YggFileInternal, YggInternal ~ BEGIN <> ROPE: TYPE ~ Rope.ROPE; DID: PUBLIC TYPE ~ REF DIDRep; DIDRep: PUBLIC TYPE ~ YggDIDPrivate.DIDRep; Error: PUBLIC ERROR[why: YggFile.Reason, diskPage: INT] = CODE; <> FileHandle: TYPE = YggInternal.FileHandle; FileHandleRep: PUBLIC TYPE = YggFileInternal.FileHandleRep; SegmentMetadata: TYPE = YggFileInternal.SegmentMetadata; SegmentMetadataList: TYPE = YggFileInternal.SegmentMetadataList; SegMetadataList: PUBLIC SegmentMetadataList _ NIL; nextUID: CARD _ 1000; transactionToFileMap: RedBlackTree.Table; myCondition: CONDITION; <> Open: PUBLIC PROC [runList: YggDIDMap.RunList, byteSize: INT, did: YggDID.DID, fileUse: ATOM, verifyLeaderNow: BOOL] RETURNS [file: FileHandle _ NIL] ~ { <> assignUID: ENTRY PROC ~ { IF file.uid < 1000 THEN { file.uid _ nextUID; nextUID _ nextUID + 1; }; }; file _ YggFileInternal.Lookup[runList, did, fileUse]; file.sizeInBytes _ byteSize; assignUID[]; }; <<>> Create: PUBLIC PROC [size: YggFile.PageCount, did: YggDID.DID, fileUse: ATOM, nearToDid: YggDID.DID, tid: Camelot.tidT] RETURNS [file: FileHandle _ NIL] ~ { assignUID: ENTRY PROC ~ { file.uid _ nextUID; nextUID _ nextUID + 1; }; file _ YggFileInternal.AllocForCreate[]; -- gives us a handle not yet in FileTable assignUID[]; file.did _ did; file.fileUse _ fileUse; lockFile[file]; InnerSetSize[file, size, TRUE, tid, nearToDid]; unlockFile[file]; }; <<>> Delete: PUBLIC PROC [file: FileHandle, tid: Camelot.tidT] ~ { lockFile[file]; InnerSetSize[file, 0, FALSE, tid, NIL]; AddMod[file, tid, delete, YggDIDMap.NullRun, 0]; noteUseInTransaction[file, tid]; unlockFile[file]; }; <<>> nullCount: INT _ 0; nullSetSizeCount: INT _ 0; Info: PUBLIC PROC [file: FileHandle, tid: Camelot.tidT] RETURNS [did: YggDID.DID, size: YggFile.PageCount, byteSize: INT, runList: YggDIDMap.RunList] ~ { IF YggTransaction.IsNullTrans[tid] THEN nullCount _ nullCount + 1; lockFile[file]; did _ file.did; runList _ InnerLocate[file, tid, [0], INT.LAST]; [size, byteSize] _ fileSize[file, tid]; unlockFile[file]; }; <<>> SetSize: PUBLIC PROC [file: FileHandle, size: YggFile.PageCount, tid: Camelot.tidT] ~ { currentSize: YggFile.PageCount; lockFile[file]; currentSize _ fileSize[file, tid].size; IF size = currentSize THEN {unlockFile[file]; RETURN}; IF currentSize = -1 THEN {unlockFile[file]; ERROR YggFile.Error[unknownFile, -1]}; IF YggTransaction.IsNullTrans[tid] THEN nullSetSizeCount _ nullSetSizeCount + 1; InnerSetSize[file, size, FALSE, tid, file.did]; unlockFile[file]; }; <<>> SetByteSize: PUBLIC PROC [file: FileHandle, byteSize: YggFile.PageCount, tid: Camelot.tidT] ~ { lockFile[file]; AddMod [file: file, tid: tid, modType: setByteSize, run: YggDIDMap.NullRun, size: byteSize]; noteUseInTransaction[file, tid]; unlockFile[file]; }; Locate: PUBLIC PROC [file: FileHandle, tid: Camelot.tidT, from: YggFile.PageNumber, nPages: YggFile.PageCount] RETURNS [runList: YggDIDMap.RunList _ NIL] ~ { lockFile[file]; runList _ InnerLocate[file, tid, from, nPages]; unlockFile[file]; }; PagesForBytes: PUBLIC PROC [bytes: CARD] RETURNS [pages: YggFile.PageCount] ~ { IF bytes = 0 THEN RETURN [0] ELSE RETURN[PBasics.ShiftRight[[int[(bytes-1)]], YggFile.logBytesPerPage].int + 1]; }; WordsForPages: PUBLIC PROC [pages: YggFile.PageCount] RETURNS [words: CARD] ~ { RETURN[pages*YggFile.wordsPerPage]; }; PagesForWords: PUBLIC PROC [words: CARD] RETURNS [pages: YggFile.PageCount] ~ { IF words = 0 THEN RETURN [0] ELSE RETURN[PBasics.ShiftRight[[int[(words-1)]], YggFile.logWordsPerPage].int + 1]; }; BytesForPages: PUBLIC PROC [pages: YggFile.PageCount] RETURNS [bytes: CARD] ~ { RETURN[pages*YggFile.bytesPerPage]; }; ServerInfo: PUBLIC PROC RETURNS [blockSize: CARD, secondaryBlocks: CARD _ 0, secondaryBlocksFree: CARD _ 0, tertiaryBlocks: CARD _ 0, tertiaryBlocksFree: CARD _ 0] ~ { <> blockSize _ YggFile.bytesPerPage; FOR seg: YggFileInternal.SegmentMetadataList _ SegMetadataList, seg.rest UNTIL seg = NIL DO secondaryBlocks _ seg.first.numberOfPages + secondaryBlocks; secondaryBlocksFree _ seg.first.freePages + secondaryBlocksFree; ENDLOOP; }; <<>> <> minFactor: INT _ 10; -- => initial minimal runs are 1/10th of size change lockFile: ENTRY PROC [file: FileHandle] ~ { ENABLE UNWIND => {}; WHILE file.interlock DO {file.needBroadcast _ TRUE; WAIT myCondition; } ENDLOOP; file.interlock _ TRUE; }; unlockFile: ENTRY PROC [file: FileHandle] ~ { ENABLE UNWIND => {}; IF ~file.interlock THEN ERROR; file.interlock _ FALSE; IF file.needBroadcast THEN { file.needBroadcast _ FALSE; BROADCAST myCondition; }; }; segMetaFromSegID: PROC [segmentId: Camelot.segmentIdT] RETURNS [seg: LIST OF SegmentMetadata _ NIL] ~ { FOR seg _ SegMetadataList, seg.rest UNTIL seg = NIL DO IF seg.first.segmentId = segmentId THEN EXIT; ENDLOOP; }; fileSize: PROC [file: FileHandle, tid: Camelot.tidT] RETURNS [size: YggFile.PageCount, sizeInBytes: INT _ 0] ~ { <> sizeInBytes _ file.sizeInBytes; IF file.modificationList = NIL THEN RETURN[file.sizeInPages, sizeInBytes] ELSE { size _ file.sizeInPages; FOR modTL: LIST OF YggFileInternal.Modification _ file.modificationList, modTL.rest UNTIL modTL = NIL DO scratchTid: Camelot.tidT _ tid; DO -- once for each parent of the modification transFound: BOOL _ FALSE; IF YggTransaction.EqualTrans[modTL.first.tid, scratchTid] THEN { FOR mods: LIST OF YggFileInternal.FileMod _ modTL.first.mods, mods.rest UNTIL mods = NIL DO SELECT mods.first.type FROM addPages => { newSize:YggFile.PageCount; newSize _ mods.first.run.firstPage + mods.first.run.pages; IF newSize <= size THEN ERROR; size _ newSize; }; removePages => { IF mods.first.size >= size THEN ERROR; size _ mods.first.size; }; delete => { size _ -1; }; setByteSize => { sizeInBytes _ mods.first.size; }; ENDCASE; ENDLOOP; EXIT; }; IF YggTransaction.IsTopLevel[scratchTid] THEN EXIT; [transFound, scratchTid] _ YggTransaction.GetParent[scratchTid]; IF ~transFound THEN ERROR; ENDLOOP; ENDLOOP; }; }; InnerLocate: PROC [file: FileHandle, tid: Camelot.tidT, from: YggFile.PageNumber, nPages: YggFile.PageCount, ignoreDelete: BOOL _ FALSE] RETURNS [runList: YggDIDMap.RunList _ NIL] ~ { lastFrom: INT _ from + nPages - 1; <> FOR rl: YggDIDMap.RunList _ file.runList, rl.rest UNTIL rl = NIL DO firstPage: INT _ rl.first.firstPage; lastPage: INT _ rl.first.firstPage + rl.first.pages - 1; firstIntersection: INT _ -1; lastIntersection: INT _ -1; IF lastPage < from OR firstPage > lastFrom THEN LOOP; -- no intersection firstIntersection _ MAX[from, firstPage]; lastIntersection _ MIN[lastFrom, lastPage]; IF lastIntersection < firstIntersection THEN ERROR; runList _ CONS[[segmentId: rl.first.segmentId, segmentPage: rl.first.segmentPage + firstIntersection - rl.first.firstPage, firstPage: firstIntersection, pages: lastIntersection-firstIntersection+1, leader: FALSE], runList]; ENDLOOP; <> FOR modTL: LIST OF YggFileInternal.Modification _ file.modificationList, modTL.rest UNTIL modTL = NIL DO scratchTid: Camelot.tidT _ tid; DO -- once for each parent of the modification transFound: BOOL _ FALSE; IF YggTransaction.EqualTrans[modTL.first.tid, scratchTid] THEN { FOR mods: LIST OF YggFileInternal.FileMod _ modTL.first.mods, mods.rest UNTIL mods = NIL DO SELECT mods.first.type FROM addPages => { firstPage: INT _ mods.first.run.firstPage; lastPage: INT _ mods.first.run.firstPage + mods.first.run.pages - 1; firstIntersection: INT _ -1; lastIntersection: INT _ -1; IF lastPage < from OR firstPage > lastFrom THEN LOOP; -- no intersection firstIntersection _ MAX[from, firstPage]; lastIntersection _ MIN[lastFrom, lastPage]; IF lastIntersection < firstIntersection THEN ERROR; runList _ CONS[[segmentId: mods.first.run.segmentId, segmentPage: mods.first.run.segmentPage + firstIntersection - mods.first.run.firstPage, firstPage: firstIntersection, pages: lastIntersection-firstIntersection+1, leader: FALSE], runList]; }; removePages => { firstPage: INT _ mods.first.run.firstPage; lastPage: INT _ mods.first.run.firstPage + mods.first.run.pages - 1; firstIntersection: INT _ -1; lastIntersection: INT _ -1; prevRL: YggDIDMap.RunList _ NIL; IF lastPage < from OR firstPage > lastFrom THEN LOOP; -- no intersection firstIntersection _ MAX[from, firstPage]; lastIntersection _ MIN[lastFrom, lastPage]; IF lastIntersection < firstIntersection THEN ERROR; FOR rl: YggDIDMap.RunList _ runList, rl.rest UNTIL rl = NIL DO firstP: INT _ rl.first.firstPage; lastP: INT _ rl.first.firstPage + rl.first.pages - 1; firstI: INT _ -1; lastI: INT _ -1; IF lastP < from OR firstP > lastFrom THEN { -- no intersection prevRL _ rl; LOOP; }; firstI _ MAX[from, firstP]; lastI _ MIN[lastFrom, lastP]; IF lastI < firstI THEN ERROR; IF firstI = firstP AND lastI = lastP THEN { -- remove entry IF prevRL = NIL THEN runList _ runList.rest ELSE prevRL.rest _ rl.rest; LOOP; -- to avoid setting prevRL } ELSE { -- shrink entry rl.first.segmentPage _ rl.first.segmentPage + firstIntersection - rl.first.firstPage; rl.first.firstPage _ firstI; rl.first.pages _ lastI - firstI + 1; }; prevRL _ rl; ENDLOOP; }; delete => { IF ~ignoreDelete THEN runList _ NIL; }; ENDCASE; ENDLOOP; EXIT; }; IF YggTransaction.IsTopLevel[scratchTid] THEN EXIT; [transFound, scratchTid] _ YggTransaction.GetParent[scratchTid]; IF ~transFound THEN ERROR; ENDLOOP; ENDLOOP; }; <<>> InnerSetSize: PROC [file: FileHandle, size: YggFile.PageCount, create: BOOL, tid: Camelot.tidT, nearToDid: YggDID.DID] ~ { <> uncommittedSize: YggFile.PageCount _ 0 ; -- smallest run we will accept minRun: INT _ MIN[size, 20]; -- smallest run we will accept small: BOOL = size < 8; nearToSegment: LIST OF SegmentMetadata; nearToPage: YggFile.PageNumber; [nearToSegment, nearToPage] _ FindNearTo[nearToDid, small]; WHILE size # (uncommittedSize _ fileSize[file: file, tid: tid].size) DO delta: INT; delta _ size-uncommittedSize; SELECT TRUE FROM delta > 0 => { IF delta < minRun THEN minRun _ delta; [] _ Extend[file, delta, minRun, small, tid, nearToSegment, nearToPage]; minRun _ MAX[1, minRun/2]; }; delta < 0 => { Contract[file, tid]; EXIT; -- Fix this when Contract does something! }; ENDCASE => ERROR; [nearToSegment, nearToPage] _ FindNearTo[NIL, small]; ENDLOOP; }; FindNearTo: PROC [nearToDid: YggDID.DID, small: BOOL] RETURNS [nearToSegment: LIST OF SegmentMetadata, nearToPage: YggFile.PageNumber] ~ { <> <> nearToSegment _ SegMetadataList; FOR seg: LIST OF SegmentMetadata _ SegMetadataList.rest, seg.rest UNTIL seg = NIL DO IF seg.first.freePages > nearToSegment.first.freePages THEN nearToSegment _ seg; ENDLOOP; nearToPage _ [0]; -- fix to use the most free "cylinder group" <<}>> << ELSE {>> -- fix to use the nearToDid <<};>> }; AddMod: PROC [file: FileHandle, tid: Camelot.tidT, modType: YggFileInternal.ModType, run: YggDIDMap.Run, size: YggFile.PageCount] ~ { IF YggTransaction.IsNullTrans[tid] THEN nullCount _ nullCount + 1; IF file.modificationList = NIL THEN file.modificationList _ LIST[[tid, LIST[[modType, run, size]]]] ELSE { modForTid: LIST OF YggFileInternal.Modification; FOR modForTid _ file.modificationList, modForTid.rest UNTIL modForTid = NIL DO IF YggTransaction.EqualTrans[modForTid.first.tid, tid] THEN { modL: LIST OF YggFileInternal.FileMod; FOR modL _ modForTid.first.mods, modL.rest UNTIL modL.rest = NIL DO ENDLOOP; modL.rest _ CONS[[modType, run, size], modL.rest]; EXIT; }; REPEAT FINISHED => { file.modificationList _ CONS[[tid, LIST[[modType, run, size]]], file.modificationList]; }; ENDLOOP; }; }; Extend: PROC [file: FileHandle, delta, min: INT, small: BOOL, tid: Camelot.tidT, nearToSegment: LIST OF SegmentMetadata, nearToPage: YggFile.PageNumber] RETURNS [done: BOOL _ TRUE, runList: LIST OF YggFileInternal.FileMod _ NIL] = TRUSTED { uncommittedSize: YggFile.PageCount; amount: INT _ delta; extendInRange: PROC [minPage: YggFile.PageNumber, maxPage: YggFile.PageNumber, suppressErrors: BOOL] RETURNS [done: BOOL _ FALSE] = TRUSTED { <> WHILE amount > 0 DO <> run: YggDIDMap.Run; run _ YggFileInternal.Alloc[segment: nearToSegment, first: nearToPage, size: amount, min: min, minPage: minPage, maxPage: maxPage ]; IF run.pages <= 0 THEN RETURN [FALSE]; run.firstPage _ uncommittedSize; nearToPage _ [run.segmentPage + run.pages]; -- hint for next call of Alloc -- AddMod[file, tid, addPages, run, 0]; uncommittedSize _ uncommittedSize + run.pages; amount _ amount - run.pages; ENDLOOP -- Loop for each allocated disk run --; RETURN [TRUE]; }; uncommittedSize _ fileSize[file, tid].size; IF ~extendInRange[[0], [nearToSegment.first.numberOfPages-1], FALSE] THEN RETURN [FALSE]; noteUseInTransaction[file, tid]; }; Contract: PROC [file: FileHandle, tid: Camelot.tidT] = { <> }; <> OpsList: TYPE = LIST OF OpsListRec; OpsListRec: TYPE = RECORD[ segmentId: Camelot.segmentIdT, runOps: YggFileInternal.RunOpsList ]; PreCommit: PUBLIC PROC [tid: Camelot.tidT] ~ { IF YggTransaction.IsTopLevel[tid] THEN { -- top level transaction data: RedBlackTree.UserData; scrTIDObj: TIDObj _ CheckoutTIDObj[tid.top]; checkForOnlyTopLevelTrans[tid]; data _ RedBlackTree.Lookup[ transactionToFileMap, scrTIDObj]; IF data # NIL THEN { < no deadlock.>> opsToDo: OpsList _ NIL; addOp: PROC [ op: YggFileInternal.opRun, segmentId: Camelot.segmentIdT, run: YggDIDMap.Run] ~ { prevOpsToDo: OpsList _ NIL; FOR ol: OpsList _ opsToDo, ol.rest UNTIL ol = NIL DO IF ol.first.segmentId = segmentId THEN { ol.first.runOps _ CONS[[op, run], ol.first.runOps]; EXIT; }; IF segmentId < ol.first.segmentId THEN { IF prevOpsToDo = NIL THEN { opsToDo _ LIST[[segmentId, LIST[[op, run]]]]; opsToDo.rest _ prevOpsToDo; } ELSE { newOps: OpsList _ NIL; newOps _ LIST[[segmentId, LIST[[op, run]]]]; newOps.rest _ prevOpsToDo.rest; prevOpsToDo.rest _ newOps; }; EXIT; }; prevOpsToDo _ ol; REPEAT FINISHED => { IF opsToDo = NIL THEN opsToDo _ LIST[[segmentId, LIST[[op, run]]]] ELSE prevOpsToDo.rest _ LIST[[segmentId, LIST[[op, run]]]]; }; ENDLOOP; }; ft: FileTrans; ft _ NARROW[data]; FOR fH: LIST OF FileHandle _ ft.files, fH.rest UNTIL fH = NIL DO -- look at all the file handles FOR fileMods: LIST OF YggFileInternal.Modification _ fH.first.modificationList, fileMods.rest UNTIL fileMods = NIL DO -- for each file, look at the modification lists IF ~YggTransaction.EqualTrans[fileMods.first.tid, tid] THEN ERROR; FOR mods: LIST OF YggFileInternal.FileMod _ fileMods.first.mods, mods.rest UNTIL mods = NIL DO SELECT mods.first.type FROM addPages => { <> <> <> <> addOp[stableAlloc, mods.first.run.segmentId, mods.first.run]; YggDIDMap.AddRuns[fH.first.did, tid, fH.first.fileUse, LIST[mods.first.run]]; }; removePages => { <> <> <> <> <> addOp[stableAndVolatileFree, mods.first.run.segmentId, mods.first.run]; YggDIDMap.RemoveRuns[fH.first.did, tid, LIST[mods.first.run]]; }; delete => { runList: YggDIDMap.RunList _ NIL; runList _ InnerLocate[fH.first, tid, [0], INT.LAST, TRUE]; YggDIDMap.RemoveRuns[fH.first.did, tid, runList]; FOR rl: YggDIDMap.RunList _ runList, rl.rest UNTIL rl = NIL DO <> <> <> <> addOp[stableAndVolatileFree, rl.first.segmentId, rl.first]; ENDLOOP; }; setByteSize => { YggDIDMap.SetByteSize[did: fH.first.did, tid: tid, fileUse: fH.first.fileUse, bytes: mods.first.size]; }; ENDCASE; ENDLOOP; ENDLOOP; IF fH.first.modificationList # NIL THEN { [fH.first.sizeInPages, fH.first.sizeInBytes] _ fileSize[fH.first, tid]; fH.first.runList _ InnerLocate[fH.first, tid, [0], INT.LAST, TRUE]; }; fH.first.modificationList _ NIL; ENDLOOP; IF opsToDo # NIL THEN { ft.opsToDo _ opsToDo; FOR ol: OpsList _ opsToDo, ol.rest UNTIL ol = NIL DO YggFileInternal.AllocAndFree[segment: segMetaFromSegID[ol.first.segmentId], tid: tid, runOpsList: ol.first.runOps]; ENDLOOP; }; ft.didPreCommit _ TRUE; }; RecycleTIDObj[scrTIDObj]; } ELSE { -- nested transaction promoteTidsToParent: ENTRY PROC ~ { ENABLE UNWIND => {}; data: RedBlackTree.UserData; scratchTIDObj.btid _ tid.top; data _ RedBlackTree.Lookup[ transactionToFileMap, scratchTIDObj]; IF data # NIL THEN { ft: FileTrans; ft _ NARROW[data]; FOR fH: LIST OF FileHandle _ ft.files, fH.rest UNTIL fH = NIL DO -- look at all the file handles prevMods: LIST OF YggFileInternal.Modification _ NIL; FOR fileMods: LIST OF YggFileInternal.Modification _ fH.first.modificationList, fileMods.rest UNTIL fileMods = NIL DO -- for each file, look at the modification lists IF YggTransaction.EqualTrans[fileMods.first.tid, tid] THEN { -- got some modifications to promote FOR fileMods2: LIST OF YggFileInternal.Modification _ fH.first.modificationList, fileMods2.rest UNTIL fileMods2 = NIL DO IF YggTransaction.EqualTrans[fileMods.first.tid, parentTid] THEN { -- parent has some modifications of its own; add subtransaction modifications and remove subtransaction entry lastMod: LIST OF YggFileInternal.FileMod _ fileMods.first.mods; FOR mods: LIST OF YggFileInternal.FileMod _ fileMods.first.mods.rest, mods.rest UNTIL mods = NIL DO ENDLOOP; lastMod.rest _ fileMods.first.mods; IF prevMods = NIL THEN fH.first.modificationList _ fileMods.rest ELSE prevMods.rest _ prevMods.rest.rest; } ELSE { -- no parent; change tid in header to promote fileMods.first.tid _ parentTid; }; ENDLOOP; EXIT; -- done with file; go do the next file }; prevMods _ fileMods; ENDLOOP; ENDLOOP; }; }; parentTid: Camelot.tidT; transFound: BOOL _ FALSE; [transFound, parentTid] _ YggTransaction.GetParent[tid]; IF ~transFound THEN ERROR; promoteTidsToParent[]; }; }; <<>> Commit: PUBLIC PROC [tid: Camelot.tidT] ~ { IF YggTransaction.IsTopLevel[tid] THEN { -- top level transaction deletedNode: RedBlackTree.Node; scrTIDObj: TIDObj _ CheckoutTIDObj[tid.top]; checkForOnlyTopLevelTrans[tid]; deletedNode _ RedBlackTree.Delete[ transactionToFileMap, scrTIDObj]; RecycleTIDObj[scrTIDObj]; IF deletedNode # NIL THEN { ft: FileTrans; ft _ NARROW[deletedNode.data]; FOR ol: OpsList _ ft.opsToDo, ol.rest UNTIL ol = NIL DO YggFileInternal.ClearLatches[segment: segMetaFromSegID[ol.first.segmentId], tid: tid]; ENDLOOP; FOR fH: LIST OF FileHandle _ ft.files, fH.rest UNTIL fH = NIL DO -- look at all the file handles fH.first.modificationList _ NIL; ENDLOOP; }; }; }; Abort: PUBLIC PROC [tid: Camelot.tidT] ~ { IF YggTransaction.IsTopLevel[tid] THEN { -- top level transaction data: RedBlackTree.UserData; scrTIDObj: TIDObj _ CheckoutTIDObj[tid.top]; checkForOnlyTopLevelTrans[tid]; data _ RedBlackTree.Lookup[ transactionToFileMap, scrTIDObj]; IF data # NIL THEN { ft: FileTrans; ft _ NARROW[data]; FOR fH: LIST OF FileHandle _ ft.files, fH.rest UNTIL fH = NIL DO -- look at all the file handles FOR fileMods: LIST OF YggFileInternal.Modification _ fH.first.modificationList, fileMods.rest UNTIL fileMods = NIL DO -- for each file, look at the modification lists prevMod: LIST OF YggFileInternal.FileMod _ NIL; FOR mods: LIST OF YggFileInternal.FileMod _ fileMods.first.mods, mods.rest UNTIL mods = NIL DO SELECT mods.first.type FROM addPages => { seg: LIST OF SegmentMetadata _ NIL; seg _ segMetaFromSegID[mods.first.run.segmentId]; IF seg = NIL THEN ERROR; YggFileInternal.Free[seg, mods.first.run]; }; removePages => { }; delete => { }; ENDCASE; prevMod _ mods; ENDLOOP; ENDLOOP; ENDLOOP; FOR ol: OpsList _ ft.opsToDo, ol.rest UNTIL ol = NIL DO YggFileInternal.ClearLatches[segment: segMetaFromSegID[ol.first.segmentId], tid: tid]; ENDLOOP; }; [] _ RedBlackTree.Delete[ transactionToFileMap, scrTIDObj]; RecycleTIDObj[scrTIDObj]; } ELSE { -- nested transaction obliterateSubtransaction: ENTRY PROC ~ { ENABLE UNWIND => {}; data: RedBlackTree.UserData; scratchTIDObj.btid _ tid.top; data _ RedBlackTree.Lookup[ transactionToFileMap, scratchTIDObj]; IF data # NIL THEN { ft: FileTrans; ft _ NARROW[data]; FOR fH: LIST OF FileHandle _ ft.files, fH.rest UNTIL fH = NIL DO -- look at all the file handles prevMods: LIST OF YggFileInternal.Modification _ NIL; FOR fileMods: LIST OF YggFileInternal.Modification _ fH.first.modificationList, fileMods.rest UNTIL fileMods = NIL DO -- for each file, look at the modification lists IF YggTransaction.EqualTrans[fileMods.first.tid, tid] THEN { -- got some modifications to obliterate FOR mods: LIST OF YggFileInternal.FileMod _ fileMods.first.mods, mods.rest UNTIL mods = NIL DO SELECT mods.first.type FROM addPages => { seg: LIST OF SegmentMetadata _ NIL; seg _ segMetaFromSegID[mods.first.run.segmentId]; IF seg = NIL THEN ERROR; YggFileInternal.Free[seg, mods.first.run]; }; removePages => { }; delete => { }; ENDCASE; ENDLOOP; IF prevMods = NIL THEN fH.first.modificationList _ fileMods.rest ELSE prevMods.rest _ prevMods.rest.rest; EXIT; -- done with file; go do the next file }; prevMods _ fileMods; ENDLOOP; ENDLOOP; }; }; obliterateSubtransaction[]; }; }; <> TIDObj: TYPE = REF TIDObjRep; TIDObjRep: TYPE = RECORD [ btid: Camelot.btidT]; scratchTIDObj: TIDObj _ NEW[TIDObjRep]; -- must own the monitor to manipulate this FileTrans: TYPE = REF FileTransRep; FileTransRep: TYPE = RECORD [ btidForFT: TIDObj, didPreCommit: BOOL _ FALSE, files: LIST OF FileHandle, opsToDo: OpsList _ NIL ]; checkForOnlyTopLevelTrans: ENTRY PROC [tid: Camelot.tidT] ~ { <> ENABLE UNWIND => {}; data: RedBlackTree.UserData; scratchTIDObj.btid _ tid.top; data _ RedBlackTree.Lookup[ transactionToFileMap, scratchTIDObj]; IF data # NIL THEN { ft: FileTrans; ft _ NARROW[data]; FOR fH: LIST OF FileHandle _ ft.files, fH.rest UNTIL fH = NIL DO FOR fileMods: LIST OF YggFileInternal.Modification _ fH.first.modificationList, fileMods.rest UNTIL fileMods = NIL DO IF fileMods.first.tid # tid THEN ERROR; ENDLOOP; ENDLOOP; }; }; noteUseInTransaction: ENTRY PROC [file: FileHandle, tid: Camelot.tidT] ~ { ENABLE UNWIND => {}; data: RedBlackTree.UserData; scratchTIDObj.btid _ tid.top; data _ RedBlackTree.Lookup[ transactionToFileMap, scratchTIDObj]; IF data = NIL THEN { ft: FileTrans _ NEW[FileTransRep]; ft.btidForFT _ NEW[TIDObjRep _ [tid.top]]; ft.files _ LIST[file]; RedBlackTree.Insert[transactionToFileMap, ft, scratchTIDObj]; } ELSE { ft: FileTrans; ft _ NARROW[data]; FOR fH: LIST OF FileHandle _ ft.files, fH.rest UNTIL fH = NIL DO IF fH.first.uid = file.uid THEN RETURN; ENDLOOP; ft.files _ CONS[file, ft.files]; }; }; <<>> <> GetKeyProc: RedBlackTree.GetKey = { <> fileTrans: FileTrans _ NARROW[data]; RETURN[ fileTrans.btidForFT ]; }; CompareProc: RedBlackTree.Compare = { <> fileTransData: FileTrans _ NARROW[data]; keyData: TIDObj _ NARROW[k]; SELECT keyData.highTicker FROM > fileTransData.btidForFT.highTicker => RETURN [greater]; < fileTransData.btidForFT.highTicker => RETURN [less]; ENDCASE => { SELECT keyData.lowTicker FROM > fileTransData.btidForFT.lowTicker => RETURN [greater]; < fileTransData.btidForFT.lowTicker => RETURN [less]; ENDCASE => RETURN [equal]; }; }; stockTIDObj: TIDObj _ NIL; CheckoutTIDObj: ENTRY PROC [btid: Camelot.btidT] RETURNS [k: TIDObj] = { IF stockTIDObj = NIL THEN k _ NEW [TIDObjRep] ELSE { k _ stockTIDObj; stockTIDObj _ NIL }; k.btid _ btid; }; RecycleTIDObj: ENTRY PROC [k: TIDObj] = { stockTIDObj _ k; }; <> InitializeFile: PUBLIC PROC [segmentList: LIST OF YggCamelotSegment.SegPort] RETURNS [firstTime : BOOL _ FALSE ]~ { <> lastSegmentMetadata: SegmentMetadataList _ NIL; gotMetaData: BOOL _ FALSE; FOR sl: LIST OF YggCamelotSegment.SegPort _ segmentList, sl.rest UNTIL sl = NIL DO smd: SegmentMetadataList; mappedAddress: Mach.vmAddressT; segmentUse: YggCamelotSegment.SegmentUseType; DIDMapLogicalPage0: CARD32; NextDIDLogicalPage: CARD32; page0: LONG POINTER TO YggCamelotSegment.PageZero; <> freePages: INT _ 0; vmAddressForSegmentAllocMap: YggFileInternal.PTAllocBitmap; vmAddressForShadowAllocMap: YggFileInternal.PTAllocBitmap; kernCode: Mach.kernReturnT; [mappedAddress: mappedAddress, kernCode: kernCode] _ Mach.vmAllocateWithPager[targetTask: Mach.taskSelf[], address: 0, size: YggFile.bytesPerPage, anywhere: TRUE, pagingObject: sl.first.port, offset: 0, raiseSignal: TRUE]; TRUSTED { page0 _ LOOPHOLE[mappedAddress]; segmentUse _ page0.segmentUse; DIDMapLogicalPage0 _ page0.DIDMapLogicalPage0; NextDIDLogicalPage _ page0.NextDIDLogicalPage; }; IF segmentUse = undefined THEN { smd _ LIST[[,sl.first.segment.segmentId, sl.first.port, sl.first.segment.lowSize, undefined, 0, 0, 0, [0], 0, 0, NIL, NIL]]; } ELSE { TRUSTED { IF page0.segmentId # sl.first.segment.segmentId THEN ERROR; IF segmentUse = systemMetaData OR segmentUse = normalAndSystemMetaData THEN gotMetaData _ TRUE; [mappedAddress: mappedAddress, kernCode: kernCode] _ Mach.vmAllocateWithPager[targetTask: Mach.taskSelf[], address: 0, size: page0.allocationMapSize*YggFile.bytesPerPage, anywhere: TRUE, pagingObject: sl.first.port, offset: page0.allocationMapStartPage*YggFile.bytesPerPage, raiseSignal: TRUE]; vmAddressForSegmentAllocMap _ LOOPHOLE[mappedAddress]; [mappedAddress: mappedAddress, kernCode: kernCode] _ Mach.vmAllocate[targetTask: Mach.taskSelf[], address: 0, size: page0.allocationMapSize*YggFile.bytesPerPage, anywhere: TRUE, raiseSignal: TRUE]; vmAddressForShadowAllocMap _ LOOPHOLE[mappedAddress]; Copy[from: vmAddressForSegmentAllocMap, nwords: page0.allocationMapSize*YggFile.bytesPerPage/PBasics.bytesPerWord, to: vmAddressForShadowAllocMap]; FOR word: CARD IN [0..page0.segmentMaximum/BITS[YggFileInternal.AllocWord]] DO SELECT vmAddressForSegmentAllocMap[word].card FROM LAST[CARD32] => {}; 0 => freePages _ freePages + 32; ENDCASE => { countBitsInByte: PROC [byte: BYTE] RETURNS [cnt: INT _ 0] ~ CHECKED { wd: WORD _ 0; wd _ byte; UNTIL wd = 0 DO IF Basics.BITAND[wd, 1] = 0 THEN cnt _ cnt + 1; wd _ Basics.BITSHIFT[wd, 1]; ENDLOOP; }; freePages _ countBitsInByte[vmAddressForSegmentAllocMap[word].hh] + countBitsInByte[vmAddressForSegmentAllocMap[word].hl] + countBitsInByte[vmAddressForSegmentAllocMap[word].lh] + countBitsInByte[vmAddressForSegmentAllocMap[word].ll]; }; ENDLOOP; smd _ LIST[[, sl.first.segment.segmentId, sl.first.port, sl.first.segment.lowSize, segmentUse, page0.segmentLogicalPage0, page0.segmentMaximum - page0.segmentLogicalPage0, freePages, [0], DIDMapLogicalPage0, NextDIDLogicalPage, vmAddressForSegmentAllocMap, vmAddressForShadowAllocMap]]; }; }; kernCode _ Mach.vmDeallocate[targetTask: Mach.taskSelf[], address: LOOPHOLE[page0], size: YggFile.bytesPerPage, raiseSignal: TRUE]; IF SegMetadataList = NIL THEN SegMetadataList _ lastSegmentMetadata _ smd ELSE {lastSegmentMetadata.rest _ smd; lastSegmentMetadata _ smd}; ENDLOOP; IF SegMetadataList.rest = NIL AND SegMetadataList.first.segmentUse = undefined THEN { <> <> firstTimeTID: Camelot.tidT; outcome: YggEnvironment.Outcome; status: INT _ -1; kernCode: Mach.kernReturnT; page0MappedAddress: Mach.vmAddressT; nextDIDMappedAddress: Mach.vmAddressT; didMapPage0MappedAddress: Mach.vmAddressT; nextDIDLogicalPage: CARD; didMapLogicalPage0: CARD; YggLogBasic.EstablishLogFile[]; YggLogBasic.OpenForPut[nextPage: 1, version: 1, nextRecord: [zero, 4096, 0]]; firstTime _ TRUE; firstTimeTID _ YggTransaction.CreateTrans[YggEnvironment.nullTransID]; <<[newTid: firstTimeTID, kernCode: kernCode] _ Camelot.TABegin[taPort: YggEnvironment.taPort, parentTid: YggEnvironment.nullTransID, transType: ttNvServerBased, raiseSignal: TRUE];>> <> [mappedAddress: page0MappedAddress, kernCode: kernCode] _ Mach.vmAllocateWithPager[targetTask: Mach.taskSelf[], address: 0, size: YggFile.bytesPerPage, anywhere: TRUE, pagingObject: SegMetadataList.first.port, offset: 0, raiseSignal: TRUE]; -- map page 0 TRUSTED { page0: LONG POINTER TO YggCamelotSegment.PageZero; word32: INT _ -1; mappedAddress: Mach.vmAddressT; page0 _ LOOPHOLE[page0MappedAddress]; page0.segmentId _ SegMetadataList.first.segmentId; page0.segmentUse _ normalAndSystemMetaData; SegMetadataList.first.segmentUse _ normalAndSystemMetaData; page0.metadataReplicated _ FALSE; page0.segmentMaximum _ SegMetadataList.first.lowSize/YggFile.bytesPerPage; page0.allocationMapSize _ 256; page0.NextDIDLogicalPage _ nextDIDLogicalPage _ 80; SegMetadataList.first.NextDIDLogicalPage _ nextDIDLogicalPage; page0.DIDMapLogicalPage0 _ didMapLogicalPage0 _ page0.NextDIDLogicalPage+1; -- one page for the Next DID SegMetadataList.first.DIDMapLogicalPage0 _ didMapLogicalPage0; page0.allocationMapStartPage _ 68+page0.DIDMapLogicalPage0; -- 66 pages (leader + dictionary + 64 + a few) for initial DID map page0.segmentLogicalPage0 _ page0.allocationMapStartPage+256; -- 256 pages for the allocation map (a megabyte) SegMetadataList.first.offsetToFirstAllocPage _ page0.segmentLogicalPage0; SegMetadataList.first.freePages _ SegMetadataList.first.numberOfPages _ page0.segmentMaximum - page0.segmentLogicalPage0; [mappedAddress: mappedAddress, kernCode: kernCode] _ Mach.vmAllocateWithPager[targetTask: Mach.taskSelf[], address: 0, size: page0.allocationMapSize*YggFile.bytesPerPage, anywhere: TRUE, pagingObject: SegMetadataList.first.port, offset: page0.allocationMapStartPage*YggFile.bytesPerPage, raiseSignal: TRUE]; SegMetadataList.first.vmAddressForSegmentAllocMap _ LOOPHOLE[mappedAddress]; [mappedAddress: mappedAddress, kernCode: kernCode] _ Mach.vmAllocate[targetTask: Mach.taskSelf[], address: 0, size: page0.allocationMapSize*YggFile.bytesPerPage, anywhere: TRUE, raiseSignal: TRUE]; SegMetadataList.first.vmAddressForShadowAllocMap _ LOOPHOLE[mappedAddress]; word32 _ SegMetadataList.first.offsetToFirstAllocPage/32; FOR w: INT IN [0..word32) DO SegMetadataList.first.vmAddressForSegmentAllocMap[w].card _ LAST[CARD]; ENDLOOP; FOR bitNo: INT IN [0..SegMetadataList.first.offsetToFirstAllocPage - 32 * word32) DO SegMetadataList.first.vmAddressForSegmentAllocMap[word32].bits[bitNo] _ TRUE; ENDLOOP; Copy[from: SegMetadataList.first.vmAddressForSegmentAllocMap, nwords: page0.allocationMapSize*YggFile.bytesPerPage/PBasics.bytesPerWord, to: SegMetadataList.first.vmAddressForShadowAllocMap]; }; kernCode _ Camelot.DSPinObject[dsPort: YggEnvironment.dsPort, tid: firstTimeTID, optr: [segmentId: SegMetadataList.first.segmentId, highOffset: 0, lowOffset: 0], size: YggFile.bytesPerPage, raiseSignal: TRUE]; kernCode _ Camelot.DSLogNewValue[dsPort: YggEnvironment.dsPort, tid: firstTimeTID, optr: [segmentId: SegMetadataList.first.segmentId, highOffset: 0, lowOffset: 0], newValue: page0MappedAddress, newValueCnt: YggFile.bytesPerPage, raiseSignal: TRUE]; <> [mappedAddress: nextDIDMappedAddress, kernCode: kernCode] _ Mach.vmAllocateWithPager[targetTask: Mach.taskSelf[], address: 0, size: YggFile.bytesPerPage, anywhere: TRUE, pagingObject: SegMetadataList.first.port, offset: YggFile.bytesPerPage*nextDIDLogicalPage, raiseSignal: TRUE]; -- map NextDID page TRUSTED { nextDIDPage: LONG POINTER TO YggCamelotSegment.NextDIDPage; nextDIDPage _ LOOPHOLE[nextDIDMappedAddress]; nextDIDPage.sealDIDMapLeaderPage _ nextDID; nextDIDPage.didLow _ 0; nextDIDPage.didHigh _ YggDIDPrivate.HighDIDFirstClient; }; kernCode _ Camelot.DSPinObject[dsPort: YggEnvironment.dsPort, tid: firstTimeTID, optr: [segmentId: SegMetadataList.first.segmentId, highOffset: 0, lowOffset: YggFile.bytesPerPage*nextDIDLogicalPage], size: YggFile.bytesPerPage, raiseSignal: TRUE]; kernCode _ Camelot.DSLogNewValue[dsPort: YggEnvironment.dsPort, tid: firstTimeTID, optr: [segmentId: SegMetadataList.first.segmentId, highOffset: 0, lowOffset: YggFile.bytesPerPage*nextDIDLogicalPage], newValue: nextDIDMappedAddress, newValueCnt: YggFile.bytesPerPage, raiseSignal: TRUE]; <> [mappedAddress: didMapPage0MappedAddress, kernCode: kernCode] _ Mach.vmAllocateWithPager[targetTask: Mach.taskSelf[], address: 0, size: YggFile.bytesPerPage, anywhere: TRUE, pagingObject: SegMetadataList.first.port, offset: YggFile.bytesPerPage*didMapLogicalPage0, raiseSignal: TRUE]; -- map DIDMap page 0 TRUSTED { didMapPage0: LONG POINTER TO YggCamelotSegment.DIDMapLeaderPage; didMapPage0 _ LOOPHOLE[didMapPage0MappedAddress]; didMapPage0.sealDIDMapLeaderPage _ didLeader; didMapPage0.runSize _ 66; didMapPage0.nextRun _ 0; -- no more runs }; kernCode _ Camelot.DSPinObject[dsPort: YggEnvironment.dsPort, tid: firstTimeTID, optr: [segmentId: SegMetadataList.first.segmentId, highOffset: 0, lowOffset: YggFile.bytesPerPage*didMapLogicalPage0], size: YggFile.bytesPerPage, raiseSignal: TRUE]; kernCode _ Camelot.DSLogNewValue[dsPort: YggEnvironment.dsPort, tid: firstTimeTID, optr: [segmentId: SegMetadataList.first.segmentId, highOffset: 0, lowOffset: YggFile.bytesPerPage*didMapLogicalPage0], newValue: nextDIDMappedAddress, newValueCnt: YggFile.bytesPerPage, raiseSignal: TRUE]; <> <<[status: status, kernCode: kernCode] _ Camelot.TAEnd[taPort: YggEnvironment.taPort, tid: firstTimeTID, protocolType: ptTwoPhased, raiseSignal: TRUE];>> <> [outcome: outcome] _ YggTransaction.Finish[transID: firstTimeTID, requestedOutcome: commit]; IF outcome # commit THEN ERROR; kernCode _ Mach.vmDeallocate[targetTask: Mach.taskSelf[], address: page0MappedAddress, size: YggFile.bytesPerPage, raiseSignal: TRUE]; kernCode _ Mach.vmDeallocate[targetTask: Mach.taskSelf[], address: nextDIDMappedAddress, size: YggFile.bytesPerPage, raiseSignal: TRUE]; kernCode _ Mach.vmDeallocate[targetTask: Mach.taskSelf[], address: didMapPage0MappedAddress, size: YggFile.bytesPerPage, raiseSignal: TRUE]; CamelotRecoverable.CamelotRecoveryComplete[]; } ELSE { -- look for new segments IF ~gotMetaData THEN ERROR; -- missing metadata segment (the segment with the did map and all that) FOR sml: SegmentMetadataList _ SegMetadataList, sml.rest UNTIL sml = NIL DO IF sml.first.segmentUse = undefined THEN { -- a new segment ERROR; -- should init the new segment and fix up the list }; ENDLOOP; }; }; OpenDIDMapFile: PUBLIC PROC RETURNS [file: YggInternal.FileHandle _ NIL] ~ { FOR sml: SegmentMetadataList _ SegMetadataList, sml.rest UNTIL sml = NIL DO IF sml.first.segmentUse = systemMetaData OR sml.first.segmentUse = normalAndSystemMetaData THEN { kernCode: Mach.kernReturnT; didMapdid: DID _ NEW[DIDRep _ [0, 1]]; mappedAddress: Mach.vmAddressT; didLeader: LONG POINTER TO YggCamelotSegment.DIDMapLeaderPage; DIDMapLogicalPage0: CARD32; runList: YggDIDMap.RunList; DIDMapLogicalPage0 _ sml.first.DIDMapLogicalPage0; IF DIDMapLogicalPage0 = 0 THEN ERROR; [mappedAddress: mappedAddress, kernCode: kernCode] _ Mach.vmAllocateWithPager[targetTask: Mach.taskSelf[], address: 0, size: YggFile.bytesPerPage, anywhere: TRUE, pagingObject: sml.first.port, offset: DIDMapLogicalPage0*YggFile.bytesPerPage, raiseSignal: TRUE]; TRUSTED { didLeader _ LOOPHOLE[mappedAddress]; IF didLeader.sealDIDMapLeaderPage # didLeader THEN ERROR; IF didLeader.nextRun # 0 THEN ERROR; runList _ LIST[[sml.first.segmentId, DIDMapLogicalPage0+1, 0, didLeader.runSize, FALSE]]; }; kernCode _ Mach.vmDeallocate[targetTask: Mach.taskSelf[], address: mappedAddress, size: YggFile.bytesPerPage, raiseSignal: TRUE]; file _ Open[runList: runList, byteSize: 0, did: didMapdid, fileUse: $DIDMap, verifyLeaderNow: FALSE]; file.sizeInBytes _ BytesForPages[fileSize[file, YggEnvironment.nullTransID].size]; EXIT; }; ENDLOOP; }; GetMaxDIDAddress: PUBLIC PROC RETURNS [optr: Camelot.optrT, address: Mach.pointerT] ~ { FOR sml: SegmentMetadataList _ SegMetadataList, sml.rest UNTIL sml = NIL DO IF sml.first.segmentUse = systemMetaData OR sml.first.segmentUse = normalAndSystemMetaData THEN { kernCode: Mach.kernReturnT; mappedAddress: Mach.vmAddressT; IF sml.first.NextDIDLogicalPage = 0 THEN ERROR; [mappedAddress: mappedAddress, kernCode: kernCode] _ Mach.vmAllocateWithPager[targetTask: Mach.taskSelf[], address: 0, size: YggFile.bytesPerPage, anywhere: TRUE, pagingObject: sml.first.port, offset: sml.first.NextDIDLogicalPage*YggFile.bytesPerPage, raiseSignal: TRUE]; TRUSTED { nextDIDPage: LONG POINTER TO YggCamelotSegment.NextDIDPage; address _ LOOPHOLE[mappedAddress + UNITS[YggCamelotSegment.PageUse]]; nextDIDPage _ LOOPHOLE[mappedAddress]; IF nextDIDPage.sealDIDMapLeaderPage # nextDID THEN ERROR; }; optr _ [segmentId: sml.first.segmentId, highOffset: 0, lowOffset: sml.first.NextDIDLogicalPage*YggFile.bytesPerPage]; EXIT; }; ENDLOOP; }; <> Copy: UNSAFE PROC [from: LONG POINTER, nwords: CARD, to: LONG POINTER] = { nowFrom: LONG POINTER _ from; nowTo: LONG POINTER _ to; nWordsLeft: CARD _ nwords; WHILE nWordsLeft > 0 DO copyThisTime: CARD _ MIN[nWordsLeft, 10000]; TRUSTED {PBasics.Copy[from: nowFrom, nwords: copyThisTime, to: nowTo];}; nowFrom _ nowFrom + copyThisTime * UNITS[PBasics.Word]; nowTo _ nowTo + copyThisTime * UNITS[PBasics.Word]; nWordsLeft _ nWordsLeft - copyThisTime; ENDLOOP; }; <> Init: PROC ~ { transactionToFileMap _ RedBlackTree.Create[getKey: GetKeyProc, compare: CompareProc]; }; Init[]; END.