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 { 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 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; 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, readStreamTid: YggEnvironment.TransID, 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, readStreamTid: YggEnvironment.nullTransID, 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; myStream.readStreamTid _ YggEnvironment.nullTransID; }; 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]; myStream.readStreamTid _ trans; }; myUnsafeGetBlock: UNSAFE PROC [self: IO.STREAM, block: Basics.UnsafeBlock] RETURNS [nBytesRead: INT] ~ TRUSTED { myStream: MyStream = NARROW[self.streamData]; IF myStream.readStream = NIL OR (~YggTransaction.IsNullTrans[myStream.readStreamTid] AND ~YggTransaction.EqualTrans[myStream.tid, myStream.readStreamTid]) THEN { myStream.readStream _ YggFileStream.StreamFromComponentFilesAndTid[componentFiles: myStream.componentFiles, fileUse: DirectoryContentsAtom, tid: YggEnvironment.nullTransID, readOnly: TRUE]; myStream.readStreamTid _ YggEnvironment.nullTransID; }; 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 OR (~YggTransaction.IsNullTrans[myStream.readStreamTid] AND ~YggTransaction.EqualTrans[myStream.tid, myStream.readStreamTid]) THEN { myStream.readStream _ YggFileStream.StreamFromComponentFilesAndTid[componentFiles: myStream.componentFiles, fileUse: DirectoryContentsAtom, tid: YggEnvironment.nullTransID, readOnly: TRUE]; myStream.readStreamTid _ YggEnvironment.nullTransID; }; length _ IO.GetLength[myStream.readStream]; }; mySetIndex: PROC [self: IO.STREAM, index: INT] ~ { myStream: MyStream = NARROW[self.streamData]; IF myStream.readStream = NIL OR (~YggTransaction.IsNullTrans[myStream.readStreamTid] AND ~YggTransaction.EqualTrans[myStream.tid, myStream.readStreamTid]) THEN { myStream.readStream _ YggFileStream.StreamFromComponentFilesAndTid[componentFiles: myStream.componentFiles, fileUse: DirectoryContentsAtom, tid: YggEnvironment.nullTransID, readOnly: TRUE]; myStream.readStreamTid _ YggEnvironment.nullTransID; }; 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. �� ²��YggNamingImpl.mesa Copyright Ó 1988, 1989 by Xerox Corporation. All rights reserved. Bob Hagmann March 21, 1989 1:48:55 pm PST Top level naming ``interface'' for Yggdrasil. Things to think about: 1) Is the directory caching reasonable? 2) Does locking work OK? Types, variables, and constants Transactions Directories Make a new directory. Destroy a directory. Files Look up the name/version in the directory property of the directoryDid. The namePattern may include "*" characters. The version is either NIL (highest version), "h" (highest version), "l" (lowest version), or the ASCII string for a number (base 10). If nameFound is TRUE, then the name/version specifies a single file and it's did is returned in didFound while its name/version is returned in nameMatched and versionMatched. If nameFound is FALSE but moreThanOneMatch is TRUE, then the name/version specifies more than one file. didFound is not interesting. If both nameFound and moreThanOneMatch are FALSE, then no names match the name/version and didFound is not interesting. Calls `proc' for each entry in the specified matching key values. The enumeration is halted when either the matching of entries is exhausted or `proc' returns FALSE The match result is computed on the assumtion that name is always GE the pattern prefix (characters up to the first star). In this case, "compatible" means that it is sensible to present another name GE the current one, and "clash" means that such a name cannot "fit". "1" is the pattern, "2" is the name else must take all combinations at this point demand an exact match in both strings Directroy manipulation [name: REF TEXT, version: Version, did: YggDID.DID] RETURNS [continue: BOOL] set breakpoint here to look at entry Adds a new entry to the index. PROC [name: REF TEXT, version: Version, did: YggDID.DID] RETURNS [stop: BOOL _ FALSE]; Deletes the entry that contains the given attribute value for the given key. Returns TRUE iff there is a directory contents BTreeVM related procedures Must call DoneWithDirectory[treeStream] when done!! Initiates directory activity. This can be called any number of times to get a new directory handle or reopen a directory that has been closed. scratchDirectoryCacheKey _ NEW[DirectoryCacheEntry]; -- used to use scratchDirectoryCacheKey as a key NullReadStreamForStream [stream: treeStream]; -- use null trans next time SafeStorage.EnableFinalization[h]; Done with the current use of the BTree. RmDir has removed it. My streams Utilities Equals and hash PROC [key1, key2: Key] RETURNS [BOOL]; PROC PROC [key: Key] RETURNS [CARDINAL]; Initialization, cache trim, and finalization PROC [key: Key, val: Val] RETURNS [quit: BOOL _ FALSE]; Initialization �Ê!m��˜�codešœ™KšœB™BKšœ)™)—K™�K™�K™-K™�™K™'K™—K™�šÏk ˜ Kšœ˜Kšœœ)˜5Kšœ˜Kšœ˜Kšœ˜Kšœ˜Kšœ˜Kšœ˜Kšœœ9œ˜jKšœœ˜!Kšœœœ*˜;Kšœ œQ˜`Kšœœ ˜Kšœœ*˜>Kšœœl˜yKšœœ"˜5Kšœœ˜Kšœ œ˜Kšœ œ ˜/Kšœœ˜)Kšœœ$˜1Kšœ œ ˜/Kšœœ˜Kšœœ0˜D—K˜�KšÑbln œœ˜Kšœ’˜™Kšœ˜šœ˜K˜�—head™Icode0šœœœ˜M˜�KšÏnœœœ œ˜8K˜�K˜�Kšœœœœ˜Kšœœœ˜+K˜�Icode2šœ œœœœœ˜,Kšœœœ ˜#š œœœ œœ˜(Kšœ œÏcP˜cKšœ ˜6Kšœ ˜*Kšœœœœ .˜]Kšœ˜—K˜�Nšœœœ˜šœœœ˜Nšœœ˜Nšœ˜Nšœ˜—K˜�KšŸœœœœœœ˜BK˜�KšŸœœ˜K˜�Mšœœ˜Nšœœ˜!Nšœœ˜&šœœœ˜$Nšœœ˜Nšœœœ˜Nšœœœ˜Kšœ˜Kšœ˜Kšœœœ˜Nšœœ *˜:Nšœ˜—Nšœœ˜8N˜1NšŸœœœœ˜)šœœœ˜Nšœ˜Nšœœœ˜)N˜—šœœœ˜!Nšœœ˜Nšœ˜N˜—Nšœœ ˜,šœ œœ˜Nšœ˜Nšœœ˜Nšœ œ˜Nšœœ˜Nšœ˜N˜—N˜�NšŸœœ˜%Nšœ œ˜N˜�Nšœœ˜˜�K˜�——™šŸ œœœ!˜7Kšœœœœ˜0Kšœ-œ˜4šœœœ0œ œ˜VKšœ˜—K˜—K˜�šŸœœœ!˜4Kšœ!œ˜'K˜—K˜�šŸœœœ!˜3Kšœœœœ˜0Kšœ-œ˜3šœœœ0œ œ˜VKšœ3˜3Kšœ˜—K˜K˜�—šŸœœ(œœœœœ˜ŽKšœœœœ˜!šœœœ(œœ˜Lšœ2œ˜:Kšœ+˜+šœ œ˜Kšœœœ˜+Kšœœ˜K˜—Kšœ˜K˜—Kšœ˜Kšœ˜—K˜K˜�—šŸœœœ6œœœœ"˜ÂKšœI˜Išœœœ(œœ˜Lšœ4œ˜<Kšœœœ˜*šœAœœ˜fKšœ˜—Kšœœœœ˜?Kšœ˜K˜—Kš œœœ œœ˜dKšœ˜—K˜K™�K™�——™šŸœœœ-œœœœ˜uK™Kšœ'˜'Kšœœ˜ Kšœ œœ˜Kšœ œœ˜Kšœ œ ˜Kšœ˜Kšœœœ˜/Kšœ"œ˜&Kšœ$˜$Kšœœ#œ8œ˜}KšœZœ˜aKšœ œœœ˜Kšœ9˜9Kšœ2˜2šœcœœ˜oKšœY˜YJšœ#œN˜wKšœœ˜ K˜—KšœM˜MKšœx˜xKšœN˜NKšœR˜RKšœaœ˜uKšœ˜šœ#œ˜+Kšœ ˜ KšœS˜SKšœœœ_˜}K˜—Kšœ/œœ"˜YKšœœ˜K˜K™�—šŸœœœ-œœœœ˜vK™Kšœœ˜ Kšœ œœ˜Kšœ œœ˜Kšœ˜Kšœœœ˜/Kšœ"œ˜&Kšœ#œœB˜qKšœ$˜$KšœQœ˜XKšœ œœc˜yKšœ0˜0Kšœ2˜2Kš œnœœœœ˜‡KšœL˜LKšœ*˜*Kšœ˜K˜——™K™�šŸœœœ6œœœœ œœœœœ( œ œ˜™Kšœ:Ïoœ¡œ"¡œVœ$¡ œœŸ¡ œœ¡œœ7¡œ¡ œ¡œœ+¡œ™«šÏb œ˜!Nšœœ˜šœœ˜Nšœœ˜Nšœ"˜"Nšœœ˜N˜—Nšœ˜Nšœœ˜Nšœ˜Nšœ%˜%Nšœ.˜.N˜—Nšœ œ˜NšœAœœ ˜VK˜K˜�—Kšœœ,˜AK˜�šŸœœœ6œœœœœœœœ˜ŠKšœ ™¥š¢œœœœœœœ˜VNšœœ˜Nšœœ˜%Nšœ8œ ˜Fšœœ˜ Nšœ6˜6Nšœœ#œœœ ˜iNšœ˜N˜—šœ7˜Ašœ˜š¢œœœ œœœœœœœœœœ˜„šŸœœœœœœœœœœœ œ˜iNšœ˜šœ˜Nšœœ œ˜,Nšœ ˜ Nšœ˜—Nšœ˜—Nšœ œ˜šœ˜Nšœœœ˜)Nšœ˜Nšœ˜—Nšœœœ˜Nšœœ˜'N˜—Nšœœœœ˜Nšœœ%œœœ ˜eNšœ6˜6Nšœ[˜[Nšœ˜Nšœ˜—Nšœœœ˜Nšœ œœ˜Nšœœ˜—N˜—Nšœ œœ˜Nšœ˜Kšœ˜Nšœœ˜!Nšœ ˜ Nšœ7˜7Nšœœ˜Nšœ^˜^Kšœ˜Kšœ˜Kšœ-˜-KšœPœ˜WKšœ œœm˜ƒKšœ+˜+Kšœœœœœœ˜&Kšœ˜šœœ˜šœ6˜6Kšœ˜Kšœ˜K˜—šœœ˜#Kšœ˜Kšœ˜K˜—šœœ˜#Kšœ˜Kšœ˜K˜—šœ˜Kšœœœ˜Kšœ˜KšœLœœ˜^šœœ˜Kšœ ˜ Kšœ@˜EK˜—K˜——KšœSœœ˜‰Kšœ ˜ KšœS˜SKšœ˜K˜K™�—šœ œ˜-Nšœ™—šŸœœœœœœœœœ(œ œ˜šŸœœœœœœœœ˜YN™#šœ ˜Nšœœ ˜Nšœ˜ šœ %˜,Nšœ œœ˜Nšœ™šœ ˜Nšœœ ˜Nšœœ˜Nšœœ˜ Nšœœ˜šœ˜Nšœ&œœ˜:Nšœ˜Nšœ˜Nšœ˜—Nšœ˜—Nšœ˜Nšœ˜—Nšœ œœ˜%Nšœ3™3Nšœ)œœ ˜?Nšœ˜Nšœ˜Nšœ˜Nšœ˜Nšœ˜—Nšœœ œœ˜)Nšœ˜—Nšœœœœ ˜$Nšœ˜šœ˜Nšœœœ˜$Nšœ˜Nšœ˜—Nšœ+˜1Nšœ˜—K™�—™M˜�•StartOfExpansion6 -- [entry: IndexedLog.Entry] RETURNS [continue: BOOL]šÐbnœ ˜AKšœœœ œœœ™LKšœœœ˜Kšœ$™$Kšœ˜ K˜—K˜�K˜�K˜�šŸ œœœ6œœœœ2œœ œ˜ðKšœ ™ š¢œœ œ œ˜DKšœœ˜Kšœ œ˜Nšœœ˜%Nšœ"˜"Nšœ˜Kšœ:˜:Kšœ(œ8˜oKšœT˜TKšœœœ˜šœ˜"KšœO˜OKšœb˜bK˜—Kšœœœ˜#N˜—Kšœ œœ˜Kšœ˜Kšœ œ˜Nšœœ˜Nšœ œ˜ Kšœ˜Kšœœœ˜Kšœœœ˜Kšœ#œœG˜vKšœ˜Kšœ.œ˜;Kšœ˜Kšœ˜KšœLœœ˜^Kšœ'œœœ@˜{Kšœ-˜-KšœQœ˜XKšœ œœh˜~Kšœ1˜1Kšœ%˜%šœœ ,˜>Nšœœœ˜Nšœœœ˜ Nšœœœ˜Nšœ ˜ š¢œ˜ Nšœœœ œœœœ™VNšœœ˜Nšœ˜Nšœœ˜ N˜—Nšœé˜éšœœ˜Kšœ ˜ Jšœœœ˜J˜—šœœ˜šœœ˜+Jšœ"˜"J˜—šœœœ#˜NJšœ˜J˜—šœœœ#˜NJšœ˜J˜—šœœ˜-Kšœ ˜ JšœGœœ˜TJšœœœ˜J˜—Jšœœ˜—K˜—šœ6œœR˜ªšœ˜Kšœ œ œ˜1Kšœ˜ —Kšœ˜—Kšœ˜Kšœ8œœ"˜bKšœ ˜ Kšœ˜K˜—K˜�šŸ œœœ6œœœœ œ˜‰KšœL™LNšœœ˜Kšœ˜Kšœ œœ˜Kšœ˜Kšœœœ˜Kšœœœ˜Kšœ#œœG˜vKšœ˜Kšœ˜KšœLœœ˜^šœœ˜Kšœ@˜EK˜—Kšœ-˜-KšœQœ˜XKšœ œœh˜~Kšœ1˜1Kšœ%˜%KšœW˜WKšœ8œœ"˜bKšœ ˜ Kšœ˜K˜K˜�—š Ÿœœ6œœœ˜sKšœœ"™.Kšœ˜Kšœ+˜+š œœœœœœ˜(Kšœ ˜ Kšœœ˜K˜—K˜K™�—K˜�—™š¢ œœœœ œœ˜jKšœ ¡œ ¡œ™3KšœUœ˜\N˜N˜�—šŸœœœœ,œ>œœœœœ˜ÜKšœ™Nšœ œ˜$Nšœœ˜Nšœœ˜š¢ œœœ˜Nšœ˜Nšœ#˜#NšœN˜Nšœœ˜Nšœœ˜Nšœœœœ˜1šœœ˜Nšœ˜šœœ˜Nšœœ˜N˜—N˜—N˜—šœœ˜Nšœœœœœœ˜MNšœ5œœ˜BNšœœH™fN˜—N˜—Nšœ˜šœœ˜Nš œœœœœ˜*Nšœ˜N˜šœ$œ˜,Kšœ%˜%K˜—N˜—˜š¢œœœ˜Nšœ ˜ Nš œ ˜N˜—š¢œœœ˜Nšœ ˜ Nš œ ˜N˜—Kšœœ˜ Kšœœœ˜+Kšœ0˜0Kšœ2˜2šœIœœ˜UKšœœ˜Nšœ˜Kšœœœ˜K˜—Kšœ8˜8Kšœ%˜%Kšœ< +˜gKšœœœœœœ˜*NšœE˜ENšœ!˜!Nšœ˜šœ˜K˜2KšœS˜SKšœœœœ˜1K˜Kšœ˜—šœ˜Kšœ˜Kšœ˜Kšœ˜Kšœ˜Kšœ˜K˜—N˜Kšœ/ ™JNšœ ˜ N™"N˜—N˜�Nšœ˜N˜�—šŸœ œœ˜>K™'Nšœœ˜Nšœœ˜Nšœ˜Nšœ,˜,NšœN˜Nšœœ˜Nšœœ˜Nšœ˜N˜—Nšœœ ˜$K˜K˜�—š¢œ œœ˜EK™Kšœ,˜,NšœE˜EK˜K˜�—š Ÿœœœ&œ' œ˜sNšœœ˜Nšœœ˜%Nšœœ˜Nšœ œ˜ Nšœ8œ ˜FNšœ#˜#š œœœœ˜3Nšœœ˜Nšœ œ˜Nšœ'œœ ˜>Jšœ-˜-Jšœœœ˜!Jš œœœœœ˜@Nšœ˜—Nšœœœ˜1šœœ˜!Nšœœœ˜2Nšœ˜—šœ˜Nšœœ ˜&Nšœœ˜#Nšœœ˜—N˜—š Ÿ œœœœ œ˜\Nšœœ˜%Nšœ˜Nšœ˜——™ JšœœœœÈ˜îJšœ œœ ˜!šœ œœ˜Jšœ˜Jšœœœ˜/Jšœœœ˜Jšœ&˜&Jšœœœ˜Jšœœ˜J˜—šŸœœœœ8œ œœ˜‰Nšœ œ6œHœAœœ˜ëNšœ˜—šŸœœ,œœ˜QKšœœ˜2šœ1œ˜9Nšœ˜Nšœœ˜"Nšœ˜N˜—Nšœ˜—šŸœœ œœ˜5Kšœœ˜/Kšœœ˜Kšœ4˜4Nšœ˜—šŸœœ)œœ˜SKšœœ˜/Kšœ¢œ˜¨Kšœ˜Nšœ˜—š¢œœœœœœœœ˜pKšœœ˜-š œœœ6œCœ˜¡Kšœ·œ˜½Nšœ4˜4N˜—Nšœ œ,˜;N˜—š¢œœœœ ˜GKšœœ˜-šœœœ˜+Kšœ§˜§N˜—šœ&˜&KšœN˜PKšœ˜K˜—Kšœ4˜6N˜—š¢œœ œ˜#Kšœœ˜-šœœœ˜+Kšœ§˜§N˜—šœœ˜&KšœN˜PKšœ˜K˜—Kšœ$˜&N˜—š ¢œœ œœ œ˜=Kšœœ˜-š œœœ6œCœ˜¡Kšœ·œ˜½Nšœ4˜4N˜—Nšœœ ˜+N˜—š ¢ œœœœ œ˜2Kšœœ˜-š œœœ6œCœ˜¡Kšœ·œ˜½Nšœ4˜4N˜—Nšœ4œœ˜Ršœœœ˜+Nšœ<˜>Nšœ˜N˜—Nšœ&˜&N˜——™ Nš œœœœœ˜:Nšœœ˜Nšœœ˜šŸœœœœ ˜,Nšœœœ ˜*šœœ˜Nšœ"˜"Nšœ˜Nšœ˜—Nšœ˜—šŸ œœœ ˜#šœ#œ˜,Nšœ˜Nšœ"˜"N˜—Nšœ˜——™š¢ œ˜Nšœœœ™&Nšœœ˜Nšœœ˜Nšœœ˜Nšœœ˜Nšœ$˜*Nšœ˜—š¢œ˜Nšœœœœ™(Nšœœ˜ Nšœ œ˜Nšœ&˜,Nšœ˜——™,šŸœœ˜ Nšœ˜Nšœ(˜(š˜š¢ œœœ˜šœ)˜)Nšœœœœ™7Nšœœ˜Nšœœ˜šœ˜Nšœ/˜/Nšœ˜Nšœ%œœ˜8N˜—Nšœœ œ˜Nšœ˜—Nšœœ˜ Nšœ&˜&Nšœ=˜=Nšœ˜—Nšœ˜Nšœ>œ ˜QNšœ˜—Nšœ˜—K˜�—™N˜�šœ˜ Nšœœ˜,NšœM˜MNšœ˜—NšœJ˜JNšœ4˜4—K˜�Kšœ˜—�…—����o:��›Y��