<> <> <> <<>> <<>> <> <<>> <> <<1) Is the directory caching reasonable?>> <<2) Does locking work OK?>> <<>> DIRECTORY Ascii, Basics USING [charsPerWord, Comparison, UnsafeBlock], BTree, BTreeVM, Convert, IO, Process, RefTab, Rope USING [Equal, Flatten, FromRefText, Index, InlineFetch, Length, ROPE, Substr, Text, UnsafeMoveChars], PBasics USING [ByteBlt, LowHalf], YggDID USING [DID, EqualDIDs, StabilizeDID, VolatilizeDID], YggDIDMap USING [AddComponentFile, GetComponentFiles, OpenDocumentFromDID, RemoveComponentFile], YggDIDPrivate USING [DIDRep], YggEnvironment USING [nullDID, nullTransID, Outcome, TransID], YggFile USING [BytesForPages, Create, Delete, FileFromComponentFiles, FileHandle, PageCount, PagesForBytes, SetByteSize], YggFileStream USING [StreamFromComponentFilesAndTid], YggFixedNames USING [], YggIndex USING [], YggInline USING [BytesForWords, WordsForBytes], YggInternal USING [Document, FileHandle], YggLock USING [LockID, MakeLockID, Release, Set], YggNaming USING [EnumProc, ErrorDesc, Version], YggRep USING [DocType], YggTransaction USING [CreateTrans, EqualTrans, Finish, IsNullTrans]; YggNamingImpl: CEDAR MONITOR IMPORTS Ascii, BTree, BTreeVM, Convert, IO, PBasics, Process, RefTab, Rope, YggDID, YggDIDMap, YggFile, YggFileStream, YggInline, YggLock, YggTransaction EXPORTS YggDID, YggNaming ~ BEGIN <> ROPE: TYPE = Rope.ROPE; Error: PUBLIC ERROR [error: YggNaming.ErrorDesc] = CODE; DID: PUBLIC TYPE ~ REF DIDRep; DIDRep: PUBLIC TYPE ~ YggDIDPrivate.DIDRep; EntryPtr: TYPE = LONG BASE POINTER TO Entry; nameOffset: INT = 2+ UNITS[DIDRep]; Entry: TYPE = MACHINE DEPENDENT RECORD [ size(0): CARDINAL, -- size in words of entire entry (16 bit PrincOps words and 32 bit Mimosa words) version(1): YggNaming.Version, -- version part of name didNamed(2): DIDRep, -- DID that is named name(nameOffset): PACKED ARRAY [0..0) OF CHAR -- name of object, extra nulls added if needed ]; Key: TYPE = REF KeyRep; KeyRep: TYPE = RECORD [ name: ROPE, version: YggNaming.Version ]; AllNulls: PACKED ARRAY [0..Basics.charsPerWord) OF CHAR _ ALL[0C]; StandardCacheSize: INT _ 8; bytesPerPage: INT _ 1024; DirectoryCache: RefTab.Ref _ NIL; desiredSizeOfDirectoryCache: INT _ 50; DirectoryCacheEntry: TYPE = RECORD [ did: YggDID.DID, isADirectory: BOOL _ FALSE, treeStream: IO.STREAM, btreevmHandle: BTreeVM.Handle, tree: BTree.Tree, unused: BOOL _ FALSE, users: INT _ 0 -- -1 means in the process of being opened ]; scratchDirectoryCacheKey: REF DirectoryCacheEntry _ NIL; DirectoryContentsAtom: ATOM _ $directoryContents; DeferredList: LIST OF DeferredItem _ NIL; DeferredItem: TYPE = RECORD[ trans: YggEnvironment.TransID, deferredUpdate: LIST OF DeferredTransItem ]; DeferredTransItem: TYPE = RECORD[ directoryDid: YggDID.DID, update: LIST OF DUpdateItem ]; arcode: TYPE = {update, remove, invalidate}; DUpdateItem: TYPE = RECORD[ addOrRemove: arcode, name: ROPE, version: ROPE, itemDID: YggDID.DID, updateType: BTree.UpdateType ]; DefaultDirectorySizeInPages: INT _ 7; MyCondition: CONDITION; oops: INT _ 0; <> PreCommit: PUBLIC PROC[tid: YggEnvironment.TransID] ~ { deferredUpdate: LIST OF DeferredTransItem _ NIL; deferredUpdate _ FindItemOnDeferredList[tid, FALSE]; FOR lodti: LIST OF DeferredTransItem _ deferredUpdate, lodti.rest UNTIL lodti = NIL DO ENDLOOP; }; Commit: PUBLIC PROC[tid: YggEnvironment.TransID] ~ { [] _ FindItemOnDeferredList[tid, TRUE]; }; Abort: PUBLIC PROC[tid: YggEnvironment.TransID] ~ { deferredUpdate: LIST OF DeferredTransItem _ NIL; deferredUpdate _ FindItemOnDeferredList[tid, TRUE]; FOR lodti: LIST OF DeferredTransItem _ deferredUpdate, lodti.rest UNTIL lodti = NIL DO RemoveDirectoryFromCache[lodti.first.directoryDid]; ENDLOOP; }; FindItemOnDeferredList: ENTRY PROC [tid: YggEnvironment.TransID, removeIt: BOOL] RETURNS [deferredUpdate: LIST OF DeferredTransItem _ NIL] ~ { prev: LIST OF DeferredItem _ NIL; FOR oldi: LIST OF DeferredItem _ DeferredList, oldi.rest UNTIL oldi = NIL DO IF YggTransaction.EqualTrans[tid, oldi.first.trans] THEN { deferredUpdate _ oldi.first.deferredUpdate; IF removeIt THEN { IF prev = NIL THEN DeferredList _ oldi.rest ELSE prev.rest _ oldi.rest; }; EXIT; }; prev _ oldi; ENDLOOP; }; RememberDeferredUpdate: ENTRY PROC [trans: YggEnvironment.TransID, directoryDid: YggDID.DID, addOrRemove: arcode, name: ROPE, version: ROPE, itemDID: YggDID.DID, updateType: BTree.UpdateType]~ { dupdate: DUpdateItem _ [addOrRemove, name, version, itemDID, updateType]; FOR oldi: LIST OF DeferredItem _ DeferredList, oldi.rest UNTIL oldi = NIL DO IF YggTransaction.EqualTrans[trans, oldi.first.trans] THEN { deferredUpdate: LIST OF DeferredTransItem; FOR deferredUpdate _ oldi.first.deferredUpdate, deferredUpdate.rest UNTIL deferredUpdate.rest = NIL DO ENDLOOP; deferredUpdate.rest _ CONS[[directoryDid, LIST[dupdate]], NIL]; EXIT; }; REPEAT FINISHED => DeferredList _ CONS [[trans, LIST[[directoryDid, LIST[dupdate]]]], DeferredList]; ENDLOOP; }; <<>> <<>> <> MkDir: PUBLIC PROC [trans: YggEnvironment.TransID, did: YggDID.DID] RETURNS [directoryAlreadyExists: BOOL _ TRUE] ~ { <> effectiveTrans: YggEnvironment.TransID; doc: YggInternal.Document _ NIL; granted: BOOL _ FALSE; released: BOOL _ FALSE; pageSize: YggFile.PageCount; didLockID: YggLock.LockID; componentFiles: LIST OF YggInternal.FileHandle; newFile: YggInternal.FileHandle _ NIL; didLockID _ YggLock.MakeLockID[did]; effectiveTrans _ IF YggTransaction.IsNullTrans[trans] THEN YggTransaction.CreateTrans[YggEnvironment.nullTransID] ELSE trans; granted _ YggLock.Set[trans: effectiveTrans, lock: didLockID, mode: directoryWrite, wait: FALSE]; IF ~granted THEN RETURN[TRUE]; doc _ YggDIDMap.OpenDocumentFromDID[did, effectiveTrans]; componentFiles _ YggDIDMap.GetComponentFiles[doc]; IF YggFile.FileFromComponentFiles [componentFiles: componentFiles, fileUse: DirectoryContentsAtom] # NIL THEN { released _ YggLock.Release[trans: effectiveTrans, lock: didLockID, mode: directoryWrite]; IF YggTransaction.IsNullTrans[trans] THEN [] _ YggTransaction.Finish[transID: effectiveTrans, requestedOutcome: abort]; RETURN[TRUE]; }; pageSize _ DefaultDirectorySizeInPages * YggFile.PagesForBytes[bytesPerPage]; newFile _ YggFile.Create[size: pageSize, did: did, fileUse: DirectoryContentsAtom, nearToDid: did, tid: effectiveTrans]; YggFile.SetByteSize[newFile, YggFile.BytesForPages[pageSize], effectiveTrans]; YggDIDMap.AddComponentFile[doc: doc, componentFile: newFile, tid: effectiveTrans]; [] _ OpenDir[did: did, fileUse: DirectoryContentsAtom, cacheSize: StandardCacheSize, initialize: TRUE, trans: trans]; DoneWithDirectory[did]; IF YggTransaction.IsNullTrans[trans] THEN { outcome: YggEnvironment.Outcome; outcome _ YggTransaction.Finish[transID: effectiveTrans, requestedOutcome: commit]; IF outcome # commit THEN ERROR Error[[$commitFailed, "bug: commit failed in MkDir in Naming, but it really should succeed"]]; }; RememberDeferredUpdate[trans, did, invalidate, NIL, NIL, YggEnvironment.nullDID, insert]; RETURN[FALSE]; }; <<>> RmDir: PUBLIC PROC [trans: YggEnvironment.TransID, did: YggDID.DID] RETURNS [directoryDoesNotExist: BOOL _ FALSE] ~ { <> doc: YggInternal.Document _ NIL; granted: BOOL _ FALSE; released: BOOL _ FALSE; didLockID: YggLock.LockID; componentFiles: LIST OF YggInternal.FileHandle; oldFile: YggInternal.FileHandle _ NIL; IF YggTransaction.IsNullTrans[trans] THEN ERROR Error[[$nullTransaction, "RmDir in Naming needs a transaction"]]; didLockID _ YggLock.MakeLockID[did]; granted _ YggLock.Set[trans: trans, lock: didLockID, mode: directoryWrite, wait: FALSE]; IF ~granted THEN ERROR Error[[$cantGetDirectoryLock, "RmDir in Naming could not get a write lock; a retry might work."]]; doc _ YggDIDMap.OpenDocumentFromDID[did, trans]; componentFiles _ YggDIDMap.GetComponentFiles[doc]; IF (oldFile _ YggFile.FileFromComponentFiles[componentFiles: componentFiles, fileUse: DirectoryContentsAtom]) = NIL THEN RETURN[FALSE]; YggDIDMap.RemoveComponentFile[doc: doc, componentFile: oldFile, tid: trans]; YggFile.Delete[file: oldFile, tid: trans]; RemoveDirectoryFromCache[did]; }; <> <<>> Lookup: PUBLIC PROC [trans: YggEnvironment.TransID, directoryDid: YggDID.DID, namePattern: ROPE, version: ROPE] RETURNS [nameFound: BOOL _ FALSE, moreThanOneMatch: BOOL _ FALSE, didFound: YggDID.DID _ YggEnvironment.nullDID, nameMatched: ROPE _ NIL, versionMatched: ROPE _ NIL] ~ { <> foundProc: YggNaming.EnumProc ~ { nameFound _ TRUE; IF numberFound > 0 THEN { moreThanOneMatch _ TRUE; didFound _ YggEnvironment.nullDID; RETURN[stop: TRUE]; }; numberFound _ numberFound + 1; moreThanOneMatch _ FALSE; didFound _ did; nameMatched _ Rope.FromRefText[name]; versionMatched _ Convert.RopeFromInt[version]; }; numberFound: INT _ 0; [] _ EnumerateEntries[trans, directoryDid, namePattern, version, NIL, NIL, foundProc]; }; VersionMatching: TYPE = {all, bangHOnly, bangLOnly, numberMatch}; EnumerateEntries: PUBLIC PROC [trans: YggEnvironment.TransID, directoryDid: YggDID.DID, namePattern: ROPE, version: ROPE, nameToStart: ROPE, nameToStartVersion: ROPE, proc: YggNaming.EnumProc] RETURNS [notADirectory: BOOL _ TRUE, completedEnumerate: BOOL _ TRUE] ~ { <> enumProc: UNSAFE PROC [entry: BTree.Entry] RETURNS [continue: BOOL _ TRUE] = TRUSTED { maxEntryChars: INT; entryPtr: EntryPtr = LOOPHOLE[entry]; maxEntryChars _ YggInline.BytesForWords[entryPtr.size - WORDS[Entry]]; IF matching = numberMatch THEN { countSinceLastClashTest _ countSinceLastClashTest + 1; IF countSinceLastClashTest < 10 AND keyRef.version # entryPtr.version THEN RETURN[TRUE]; -- wrong version countSinceLastClashTest _ 0; }; SELECT Match[@entryPtr.name, maxEntryChars, flatNamePattern] FROM fit => { makeRefTextName: UNSAFE PROC [charArray: LONG POINTER TO PACKED ARRAY [0..0) OF CHAR, maxSize: INT] RETURNS [textName: REF TEXT] ~ { GetText: UNSAFE PROC [textRep: LONG POINTER TO PACKED ARRAY [0..0) OF CHAR, text: REF TEXT] = UNCHECKED { text.length _ nameSize; [] _ PBasics.ByteBlt [ to: [ BASE[DESCRIPTOR[text]], 0, nameSize ], from: [ charArray, 0, nameSize ] ]; }; nameSize: INT _ maxSize; WHILE nameSize > 0 DO IF charArray^[nameSize-1] # 0C THEN EXIT; nameSize _ nameSize-1; ENDLOOP; textName _ NEW[TEXT[nameSize]]; GetText[charArray, LOOPHOLE[textName]]; }; name: REF TEXT _ NIL; IF matching = numberMatch AND (keyRef.version # entryPtr.version) THEN RETURN[TRUE]; -- wrong version name _ makeRefTextName[@entryPtr.name, maxEntryChars]; continue _ ~proc[name, entryPtr.version, YggDID.VolatilizeDID[buffer: @entryPtr.didNamed]]; RETURN; }; compatible => RETURN[TRUE]; clash => RETURN[FALSE]; ENDCASE => ERROR; }; granted: BOOL _ FALSE; tree: BTree.Tree; didLockID: YggLock.LockID; countSinceLastClashTest: INT _ 0; matching: VersionMatching _ all; flatNamePattern: Rope.Text = Rope.Flatten[namePattern]; keyRef: Key _ NIL; start: Rope.Text = Rope.Flatten[Rope.Substr[namePattern, 0, Rope.Index[namePattern, 0, "*"]]]; keyRef _ CheckoutKey[]; keyRef.name _ start; didLockID _ YggLock.MakeLockID[directoryDid]; granted _ YggLock.Set[trans: trans, lock: didLockID, mode: directoryRead, wait: FALSE]; IF ~granted THEN ERROR Error[[$cantGetDirectoryLock, "EnumerateEntries in Naming could not get a read lock; a retry might work."]]; [tree: tree] _ GetTreeForDID[directoryDid]; IF tree = NIL THEN RETURN[TRUE, TRUE]; notADirectory _ FALSE; SELECT TRUE FROM Rope.Equal[version, "*"], Rope.Equal[version, ""] => { keyRef.version _ [0]; matching _ all; }; Rope.Equal[version, "h", FALSE]=> { keyRef.version _ [0]; matching _ bangHOnly; }; Rope.Equal[version, "l", FALSE]=> { keyRef.version _ [0]; matching _ bangLOnly; }; ENDCASE => { notInt: BOOL _ FALSE; matching _ numberMatch; keyRef.version _ [Convert.CardFromRope[version ! Convert.Error => {notInt _ TRUE; CONTINUE}]]; IF notInt THEN { DoneWithDirectory[directoryDid]; ERROR Error[[$badVersionSpec, "The version specification is bogus"]]; }; }; TRUSTED {[] _ BTree.EnumerateEntries[tree: tree, relation: greater, key: keyRef, pathStk: NIL, useExistingPath: FALSE, Proc: enumProc];}; DoneWithDirectory[directoryDid]; IF ~YggLock.Release[trans: trans, lock: didLockID, mode: directoryRead] THEN ERROR; RecycleKey[keyRef]; }; <<>> MatchResult: TYPE = {fit, compatible, clash}; <> Match: UNSAFE PROC [name: LONG POINTER TO PACKED ARRAY [0..0) OF CHAR, maxNameSize: INT, pattern: Rope.Text] RETURNS [MatchResult] = UNCHECKED { SubMatch: PROC [i1: INT, len1: INT, i2: INT, len2: INT] RETURNS [MatchResult] = TRUSTED { <<"1" is the pattern, "2" is the name>> WHILE len1 > 0 DO c1: CHAR = pText[i1]; IF c1 = '* THEN { -- quick kill for * at end of pattern IF len1 = 1 THEN RETURN [fit]; <> { -- first, accept the * j1: INT = i1 + 1; nlen1: INT = len1 - 1; j2: INT _ i2; nlen2: INT _ len2; WHILE nlen2 >= 0 DO IF SubMatch[j1, nlen1, j2, nlen2] = fit THEN RETURN [fit]; j2 _ j2 + 1; nlen2 _ nlen2 - 1; ENDLOOP; }; RETURN [compatible]; }; IF len2 = 0 THEN RETURN [compatible]; <> IF Ascii.Upper[c1] # Ascii.Upper[name[i2]] THEN RETURN [clash]; i1 _ i1 + 1; len1 _ len1 - 1; i2 _ i2 + 1; len2 _ len2 - 1; ENDLOOP; RETURN [IF len2 = 0 THEN fit ELSE clash]; }; pText: REF TEXT = LOOPHOLE[pattern]; nameSize: INT _ maxNameSize; WHILE nameSize > 0 DO IF name^[nameSize-1] # 0C THEN EXIT; nameSize _ nameSize-1; ENDLOOP; RETURN [SubMatch [0, pText.length, 0, nameSize]]; }; <<>> <> SimpleEntryProc: YggNaming.EnumProc = { -- for debugging purposes <<[name: REF TEXT, version: Version, did: YggDID.DID] RETURNS [continue: BOOL]>> cont: BOOLEAN _ TRUE; <> RETURN[cont]; }; UpdateItem: PUBLIC PROC [trans: YggEnvironment.TransID, directoryDid: YggDID.DID, name: ROPE, version: ROPE, did: YggDID.DID, updateType: BTree.UpdateType _ insertOrReplace] RETURNS [notADirectory: BOOL _ FALSE, nameFound: BOOL _ FALSE] ~ { <> writeEntryInner: UNSAFE PROCEDURE [entry: BTree.Entry] = UNCHECKED { nBytes: INT; nullsToMove: INT; entryPtr: EntryPtr = LOOPHOLE[entry]; entryPtr.version _ keyRef.version; entryPtr.size _ entSize; YggDID.StabilizeDID[did: did, buffer: @entryPtr.didNamed]; TRUSTED {nBytes _ Rope.UnsafeMoveChars[block: [LOOPHOLE[@entryPtr.name], 0, nameSize], rope: name, start: 0];}; nullsToMove _ YggInline.BytesForWords[YggInline.WordsForBytes[nameSize]] - nameSize; IF nullsToMove < 0 THEN ERROR; TRUSTED {nBytes _ PBasics.ByteBlt[ from: [blockPointer: @AllNulls, startIndex: 0, stopIndexPlusOne: nullsToMove], to: [blockPointer: @entryPtr.name, startIndex: nameSize, stopIndexPlusOne: nameSize+nullsToMove]]; }; IF nBytes # nullsToMove THEN ERROR; }; granted: BOOL _ FALSE; didLockID: YggLock.LockID; nameSize: INT; keyRef: Key _ NIL; entSize: INT; tree: BTree.Tree; notInt: BOOL _ FALSE; treeStream: IO.STREAM; IF YggTransaction.IsNullTrans[trans] THEN ERROR Error[[$nullTransaction, "UpdateItem in Naming needs a transaction"]]; nameSize _ Rope.Length[name]; entSize _ YggInline.WordsForBytes[nameSize] + WORDS[Entry]; keyRef _ CheckoutKey[]; keyRef.name _ name; keyRef.version _ [Convert.CardFromRope[version ! Convert.Error => {notInt _ TRUE; CONTINUE}]]; IF notInt AND ~(Rope.Equal[version, "h", FALSE]) THEN ERROR Error[[$badVersionSpec, "The version specification is bogus"]]; didLockID _ YggLock.MakeLockID[directoryDid]; granted _ YggLock.Set[trans: trans, lock: didLockID, mode: directoryWrite, wait: FALSE]; IF ~granted THEN ERROR Error[[$cantGetDirectoryLock, "UpdateItem in Naming could not get a write lock; a retry might work."]]; [tree, treeStream] _ GetTreeForDID[directoryDid]; NewTransForStream[trans, treeStream]; IF notInt THEN { -- highest or lowest version has to be found notADirectory: BOOL _ TRUE; completedEnumerate: BOOL _ TRUE; foundSomething: BOOL _ FALSE; versionFound: YggNaming.Version; enumProc: YggNaming.EnumProc ~ { <> foundSomething _ TRUE; versionFound _ version; RETURN[TRUE]; }; [notADirectory: notADirectory, completedEnumerate: completedEnumerate] _ EnumerateEntries[trans: trans, directoryDid: directoryDid, namePattern: name, version: version, nameToStart: name, nameToStartVersion: version, proc: enumProc]; IF notADirectory THEN { DoneWithDirectory[directoryDid]; RETURN[TRUE, FALSE]; }; SELECT TRUE FROM foundSomething AND updateType = insert => { keyRef.version _ [versionFound+1]; }; foundSomething AND (updateType = replace OR updateType = insertOrReplace) => { keyRef.version _ versionFound; }; ~foundSomething AND (updateType = insert OR updateType = insertOrReplace) => { keyRef.version _ [1]; }; ~foundSomething AND updateType = replace => { DoneWithDirectory[directoryDid]; IF ~YggLock.Release[trans: trans, lock: didLockID, mode: directoryWrite] THEN ERROR; RETURN[FALSE, FALSE] }; ENDCASE => ERROR; }; TRUSTED {BTree.UpdateEntry[tree: tree, key: keyRef, pathStk: NIL, useExistingPath: FALSE, words: entSize, Proc: writeEntryInner, updateType: updateType ! BTree.Error => { SELECT reason FROM wrongUpdateType => {nameFound _ TRUE; CONTINUE;}; ENDCASE ; }; ];}; RememberDeferredUpdate[trans, directoryDid, invalidate, NIL, NIL, YggEnvironment.nullDID, insert]; DoneWithDirectory[directoryDid]; RecycleKey[keyRef]; }; DeleteItem: PUBLIC PROC [trans: YggEnvironment.TransID, directoryDid: YggDID.DID, name: ROPE, version: ROPE] RETURNS [found: BOOLEAN] ~ { <> keyRef: Key _ NIL; tree: BTree.Tree; granted: BOOL _ FALSE; didLockID: YggLock.LockID; notInt: BOOL _ FALSE; treeStream: IO.STREAM; IF YggTransaction.IsNullTrans[trans] THEN ERROR Error[[$nullTransaction, "UpdateItem in Naming needs a transaction"]]; keyRef _ CheckoutKey[]; keyRef.name _ name; keyRef.version _ [Convert.CardFromRope[version ! Convert.Error => {notInt _ TRUE; CONTINUE}]]; IF notInt THEN { ERROR Error[[$badVersionSpec, "The version specification is bogus"]]; }; didLockID _ YggLock.MakeLockID[directoryDid]; granted _ YggLock.Set[trans: trans, lock: didLockID, mode: directoryWrite, wait: FALSE]; IF ~granted THEN ERROR Error[[$cantGetDirectoryLock, "DeleteItem in Naming could not get a write lock; a retry might work."]]; [tree, treeStream] _ GetTreeForDID[directoryDid]; NewTransForStream[trans, treeStream]; found _ BTree.DeleteKey[tree: tree, key: keyRef, pathStk: NIL, useExistingPath: FALSE]; RememberDeferredUpdate[trans, directoryDid, invalidate, NIL, NIL, YggEnvironment.nullDID, insert]; DoneWithDirectory[directoryDid]; RecycleKey[keyRef]; }; HasDirectory: PUBLIC PROC [trans: YggEnvironment.TransID, directoryDid: YggDID.DID] RETURNS [isDirectory: BOOL] ~ { <> tree: BTree.Tree; [tree: tree] _ GetTreeForDID[directoryDid]; IF tree = NIL THEN RETURN [FALSE] ELSE { DoneWithDirectory[directoryDid]; RETURN [TRUE]; }; }; <<>> <> GetTreeForDID: PROC [directoryDid: YggDID.DID] RETURNS [tree: BTree.Tree, treeStream: IO.STREAM _ NIL] ~ { <> [tree, treeStream] _ OpenDir[directoryDid, DirectoryContentsAtom, StandardCacheSize, FALSE]; }; OpenDir: PROC [did: YggDID.DID, fileUse: ATOM, cacheSize: BTreeVM.CacheSize, initialize: BOOL, trans: YggEnvironment.TransID _ YggEnvironment.nullTransID] RETURNS [tree: BTree.Tree _ NIL, treeStream: IO.STREAM _ NIL] ~ { <> btreevmHandle: BTreeVM.Handle _ NIL; ce: REF DirectoryCacheEntry; found: BOOL; innerOpen: ENTRY PROC = { val: RefTab.Val; scratchDirectoryCacheKey.did _ did; [found, val] _ RefTab.Fetch[x: DirectoryCache, key: scratchDirectoryCacheKey]; IF found THEN { ce _ NARROW[val]; WHILE ce.users = -1 DO WAIT MyCondition; ENDLOOP; IF ce.isADirectory THEN { ce.users _ ce.users + 1; IF ce.unused THEN { ce.unused _ FALSE; }; }; } ELSE { ce _ NEW[DirectoryCacheEntry _ [did, TRUE, treeStream, NIL, NIL, FALSE, -1]]; IF ~RefTab.Insert[x: DirectoryCache, key: ce, val: ce] THEN ERROR; <> }; }; innerOpen[]; IF found THEN { IF ~ce.isADirectory THEN RETURN[NIL, NIL]; treeStream _ ce.treeStream; tree _ ce.tree; IF ~YggTransaction.IsNullTrans[trans] THEN { NewTransForStream[trans, treeStream]; }; } ELSE { opened: ENTRY PROC = { ce.users _ 1; BROADCAST MyCondition; }; letGo: ENTRY PROC = { ce.users _ 0; BROADCAST MyCondition; }; doc: YggInternal.Document _ NIL; componentFiles: LIST OF YggFile.FileHandle; doc _ YggDIDMap.OpenDocumentFromDID[did, trans]; componentFiles _ YggDIDMap.GetComponentFiles[doc]; IF YggFile.FileFromComponentFiles[componentFiles, DirectoryContentsAtom] = NIL THEN { ce.isADirectory _ FALSE; letGo[]; RETURN[NIL, NIL]; }; treeStream _ NewStreamForBTreeVM[componentFiles, trans]; NewTransForStream[trans, treeStream]; NewReadStreamForStream [trans: trans, stream: treeStream]; -- use this trans for read while doing init IF treeStream = NIL THEN RETURN[NIL, NIL]; btreevmHandle _ BTreeVM.Open[treeStream, bytesPerPage, cacheSize, 0]; ce.btreevmHandle _ btreevmHandle; ce.treeStream _ treeStream; tree _ BTree.New[ repPrim: [compare: Compare, entrySize: EntrySize], storPrim: [referencePage: BTreeVM.ReferencePage, releasePage: BTreeVM.ReleasePage], minEntrySize: SIZE[YggRep.DocType] + SIZE[CARD], initialState: suspended ]; BTree.Open[ tree: tree, storage: btreevmHandle, pageSize: bytesPerPage, initialize: initialize, maintainRecomputableState: TRUE ]; ce.tree _ tree; NullReadStreamForStream [stream: treeStream]; -- use null trans next time opened[]; <> }; }; DoneWithDirectory: ENTRY PROC [ directoryDid: YggDID.DID ] = { <> ce: REF DirectoryCacheEntry; found: BOOL; val: RefTab.Val; scratchDirectoryCacheKey.did _ directoryDid; [found, val] _ RefTab.Fetch[x: DirectoryCache, key: scratchDirectoryCacheKey]; IF found THEN { ce _ NARROW[val]; ce.users _ ce.users - 1; } ELSE oops _ oops + 1; -- debugging }; RemoveDirectoryFromCache: ENTRY PROC [ directoryDid: YggDID.DID ] = { <> scratchDirectoryCacheKey.did _ directoryDid; [] _ RefTab.Delete[x: DirectoryCache, key: scratchDirectoryCacheKey]; }; Compare: UNSAFE PROC [key: BTree.Key, entry: BTree.Entry] RETURNS [result: Basics.Comparison _ equal] = UNCHECKED { keyRef: Key = NARROW[key]; entryPtr: EntryPtr = LOOPHOLE[entry]; maxEntryChars: INT; keySize: INT; maxEntryChars _ YggInline.BytesForWords[entryPtr.size - WORDS[Entry]]; keySize _ Rope.Length[keyRef.name]; FOR inx: INT IN [0..MIN[maxEntryChars, keySize]) DO entryChar: CHAR; keyChar: CHAR; IF (entryChar _ entryPtr.name[inx]) = 0C THEN RETURN[greater]; keyChar _ Rope.InlineFetch[keyRef.name, inx]; IF entryChar = keyChar THEN LOOP; IF keyChar < entryChar THEN RETURN [less] ELSE RETURN [greater]; ENDLOOP; IF maxEntryChars < keySize THEN RETURN [greater]; IF maxEntryChars > keySize THEN { IF entryPtr.name[keySize] # 0C THEN RETURN [less]; }; SELECT keyRef.version FROM > entryPtr.version => RETURN[greater]; < entryPtr.version => RETURN[less]; ENDCASE => RETURN[equal]; }; EntrySize: UNSAFE PROC [entry: BTree.Entry] RETURNS [words: BTree.EntSize _ 4] = UNCHECKED { entryPtr: EntryPtr = LOOPHOLE[entry]; RETURN[entryPtr.size]; }; <> myStreamProcs: REF IO.StreamProcs _ IO.CreateStreamProcs[variety: inputOutput, class: $YggdrasilDirectory, unsafeGetBlock: myUnsafeGetBlock, unsafePutBlock: myUnsafePutBlock, flush: myFlush, getLength: myGetLength, setIndex: mySetIndex]; MyStream: TYPE = REF MyStreamRep; MyStreamRep: TYPE = RECORD [ tid: YggEnvironment.TransID, componentFiles: LIST OF YggInternal.FileHandle, readStream: IO.STREAM, currentWriteStream: IO.STREAM, writeStreamPos: INT _ -1 ]; NewStreamForBTreeVM: PROC [componentFiles: LIST OF YggInternal.FileHandle, trans: YggEnvironment.TransID] RETURNS [stream: IO.STREAM] = { stream _ IO.CreateStream[streamProcs: myStreamProcs, streamData: NEW[MyStreamRep _ [tid: trans, componentFiles: componentFiles, readStream: NIL, currentWriteStream: NIL]], backingStream: NIL]; }; NewTransForStream: PROC [trans: YggEnvironment.TransID, newStream: IO.STREAM] = { myStream: MyStream = NARROW[newStream.streamData]; IF ~YggTransaction.EqualTrans[trans, myStream.tid] THEN { myStream.tid _ trans; myStream.currentWriteStream _ NIL; myStream.writeStreamPos _ -1; }; }; NullReadStreamForStream: PROC [stream: IO.STREAM] = { myStream: MyStream = NARROW[stream.streamData]; myStream.readStream _ NIL; }; NewReadStreamForStream: PROC [trans: YggEnvironment.TransID, stream: IO.STREAM] = { myStream: MyStream = NARROW[stream.streamData]; myStream.readStream _ YggFileStream.StreamFromComponentFilesAndTid[componentFiles: myStream.componentFiles, fileUse: DirectoryContentsAtom, tid: trans, readOnly: TRUE]; }; myUnsafeGetBlock: UNSAFE PROC [self: IO.STREAM, block: Basics.UnsafeBlock] RETURNS [nBytesRead: INT] ~ TRUSTED { myStream: MyStream = NARROW[self.streamData]; IF myStream.readStream = NIL THEN { myStream.readStream _ YggFileStream.StreamFromComponentFilesAndTid[componentFiles: myStream.componentFiles, fileUse: DirectoryContentsAtom, tid: YggEnvironment.nullTransID, readOnly: TRUE]; }; nBytesRead _ IO.UnsafeGetBlock[myStream.readStream, block]; }; myUnsafePutBlock: PROC [self: IO.STREAM, block: Basics.UnsafeBlock] ~ { myStream: MyStream = NARROW[self.streamData]; IF myStream.currentWriteStream = NIL THEN { myStream.currentWriteStream _ YggFileStream.StreamFromComponentFilesAndTid[componentFiles: myStream.componentFiles, fileUse: DirectoryContentsAtom, tid: myStream.tid]; }; IF myStream.writeStreamPos # -1 THEN { IO.SetIndex[self: myStream.currentWriteStream, index: myStream.writeStreamPos] ; myStream.writeStreamPos _ -1; }; IO.UnsafePutBlock[myStream.currentWriteStream, block]; }; myFlush: PROC [self: IO.STREAM] ~ { myStream: MyStream = NARROW[self.streamData]; IF myStream.currentWriteStream = NIL THEN { myStream.currentWriteStream _ YggFileStream.StreamFromComponentFilesAndTid[componentFiles: myStream.componentFiles, fileUse: DirectoryContentsAtom, tid: myStream.tid]; }; IF myStream.writeStreamPos # -1 THEN { IO.SetIndex[self: myStream.currentWriteStream, index: myStream.writeStreamPos] ; myStream.writeStreamPos _ -1; }; IO.Flush[myStream.currentWriteStream]; }; myGetLength: PROC [self: IO.STREAM] RETURNS [length: INT] ~ { myStream: MyStream = NARROW[self.streamData]; IF myStream.readStream = NIL THEN { myStream.readStream _ YggFileStream.StreamFromComponentFilesAndTid[componentFiles: myStream.componentFiles, fileUse: DirectoryContentsAtom, tid: YggEnvironment.nullTransID, readOnly: TRUE]; }; length _ IO.GetLength[myStream.readStream]; }; mySetIndex: PROC [self: IO.STREAM, index: INT] ~ { myStream: MyStream = NARROW[self.streamData]; IF myStream.readStream = NIL THEN { myStream.readStream _ YggFileStream.StreamFromComponentFilesAndTid[componentFiles: myStream.componentFiles, fileUse: DirectoryContentsAtom, tid: YggEnvironment.nullTransID, readOnly: TRUE]; }; IO.SetIndex[self: myStream.readStream, index: index ! IO.EndOfStream => CONTINUE]; IF myStream.currentWriteStream # NIL THEN { IO.SetIndex[self: myStream.currentWriteStream, index: index] ; myStream.writeStreamPos _ -1; } ELSE myStream.writeStreamPos _ index; }; <> stockKeys: ARRAY [0..numberOfStockKeys] OF Key _ ALL[NIL]; numberOfStockKeys: INT = 10; stockKeyIndex: INT _ 0; CheckoutKey: ENTRY PROC RETURNS [k: Key] = { IF stockKeyIndex = 0 THEN k _ NEW [KeyRep] ELSE { stockKeyIndex _ stockKeyIndex - 1; k _ stockKeys[stockKeyIndex]; }; }; RecycleKey: ENTRY PROC [k: Key] = { IF stockKeyIndex < numberOfStockKeys THEN { stockKeys[stockKeyIndex] _ k; stockKeyIndex _ stockKeyIndex + 1; }; }; <> equalProc: RefTab.EqualProc = { <> k1: REF DirectoryCacheEntry; k2: REF DirectoryCacheEntry; k1 _ NARROW[key1]; k2 _ NARROW[key2]; RETURN [YggDID.EqualDIDs[k1.did, k2.did]]; }; hashProc: RefTab.HashProc = { <> keyRef: REF DirectoryCacheEntry; keyRef _ NARROW[key]; RETURN [PBasics.LowHalf[keyRef.did.didLow]]; }; <> NamingCacheTrimProcess: PROC = { ticksToWait: Process.Ticks; ticksToWait _ Process.MsecToTicks[1315]; DO innerTrim: ENTRY PROC = { eachPairAction: RefTab.EachPairAction = { <> ce: REF DirectoryCacheEntry; ce _ NARROW[val]; IF ce.unused THEN { [] _ RefTab.Delete[x: DirectoryCache, key: ce]; size _ size - 1; IF size <= desiredSizeOfDirectoryCache THEN quit _ TRUE; } ELSE ce.unused _ TRUE; }; size: INT; size _ RefTab.GetSize[DirectoryCache]; [] _ RefTab.Pairs[x: DirectoryCache, action: eachPairAction]; }; Process.Pause[ticksToWait]; IF RefTab.GetSize[DirectoryCache] > desiredSizeOfDirectoryCache THEN innerTrim[]; ENDLOOP; }; <> TRUSTED { Process.Detach[FORK NamingCacheTrimProcess]; Process.SetTimeout[condition: @MyCondition, ticks: Process.MsecToTicks[171]]; }; DirectoryCache _ RefTab.Create[mod: 47, equal: equalProc, hash: hashProc]; scratchDirectoryCacheKey _ NEW[DirectoryCacheEntry]; END.