DIRECTORY Basics, BasicTime, Camelot, Commander, CommandTool, Convert, File, FS, IO, Mach, PBasics, Process, Random, RefText, Rope, SunMount, SunNFS, YggDID, YggDIDPrivate, YggDIDMapPrivate, YggdrasilInit, YggLock, YggFixedNames, YggEnvironment, YggFile, YggFileStream, YggIndexMaint, YggInternal, YggMonitoringLog, YggNFS, YggTransaction; NFSTestImpl: CEDAR PROGRAM IMPORTS Basics, BasicTime, Commander, CommandTool, Convert, IO, Process, Random, RefText, Rope, YggDID, YggFile, YggNFS EXPORTS YggDID, YggInternal = BEGIN ROPE: TYPE = Rope.ROPE; DID: PUBLIC TYPE ~ REF DIDRep; DIDRep: PUBLIC TYPE ~ YggDIDPrivate.DIDRep; Document: TYPE = REF DocumentRep; DocumentRep: PUBLIC TYPE = YggDIDMapPrivate.DocumentRep; sunEpoch: BasicTime.GMT _ BasicTime.Pack[ [year~1970, month~January, day~1, hour~0, minute~0, second~0, zone~0, dst~no] ]; randomStream: Random.RandomStream; RandomSequenceSize: INT = 20; RandomSequence: ARRAY [0..RandomSequenceSize) OF INT _ [3, 1, 77, 16, 0, 2, 2, 1, 3, 0, 2, 6, 1234, 3, 1, 13, 0, 52, 4, 77]; KnownDirectoryINodes: LIST OF REF DirectoryINode _ NIL; NumberOfKnownDirectoryINodess: INT _ 0; NumberOfRMDirectories: INT _ 0; FailedToRMDirCount: INT _ 0; DirectoryStuff: TYPE = RECORD [ directoryName: REF TEXT, dirINode: REF DirectoryINode ]; DirectoryINode: TYPE = RECORD [ fHandle: SunNFS.FHandle, inDirectory: LIST OF REF DirectoryINode, filesInDirectory: LIST OF FileStuff, directoriesInDirectory: LIST OF DirectoryStuff, isRoot: BOOL _ FALSE, rmOnly: BOOL _ FALSE ]; KnownFiles: LIST OF FileStuff _ NIL; NumberOfKnownFiles: INT _ 0; FileStuff: TYPE = RECORD [ fileName: REF TEXT, inode: REF INodeStuff ]; INodeStuff: TYPE = RECORD [ fHandle: SunNFS.FHandle, inDirectory: LIST OF REF DirectoryINode, contents: REF TEXT, mode: CARD, uid: CARD, gid: CARD ]; MaxFileSize: INT _ 1234; ReadBuffer: REF TEXT; NFSTestProc: Commander.CommandProc = { randomIndex: INT _ 0; replyLookup: SunNFS.DirOpRes; CheckAFile: PROC [lofs: LIST OF FileStuff] ~ { reply: SunNFS.AttrStat; gotAHit: BOOL _ FALSE; FOR dirs: LIST OF REF DirectoryINode _ lofs.first.inode.inDirectory, dirs.rest UNTIL dirs = NIL DO matchedOK: BOOL _ FALSE; FOR files: LIST OF FileStuff _ dirs.first.filesInDirectory, files.rest UNTIL files = NIL DO IF files.first.inode = lofs.first.inode AND RefText.Equal[files.first.fileName, lofs.first.fileName] THEN {matchedOK _ TRUE; EXIT;}; ENDLOOP; IF ~matchedOK THEN LOOP; gotAHit _ TRUE; replyLookup _ YggNFS.Lookup[[dir: dirs.first.fHandle, name: lofs.first.fileName]]; IF replyLookup.status # ok THEN ERROR; IF ~RefText.Equal[replyLookup.file, lofs.first.inode.fHandle] THEN ERROR; ENDLOOP; IF ~gotAHit THEN ERROR; reply _ YggNFS.Getattr[lofs.first.inode.fHandle]; IF reply.status # ok THEN ERROR; IF reply.attributes.mode # lofs.first.inode.mode THEN ERROR; IF reply.attributes.uid # lofs.first.inode.uid THEN ERROR; IF reply.attributes.gid # lofs.first.inode.gid THEN ERROR; IF reply.attributes.size # RefText.Length[lofs.first.inode.contents] THEN ERROR; SELECT CARD[Basics.BITAND[reply.attributes.mode, YggNFS.typeBits]] FROM YggNFS.regularModeBits => { reply _ YggNFS.Read[file: lofs.first.inode.fHandle, offset: 0, count: reply.attributes.size, block: ReadBuffer]; IF reply.status # ok THEN ERROR; IF ~RefText.Equal[ReadBuffer, lofs.first.inode.contents] THEN ERROR; }; YggNFS.symbolicLinkModeBits => { status: SunNFS.Stat; path: SunNFS.Path; [status: status, data: path] _ YggNFS.Readlink[file: lofs.first.inode.fHandle]; IF status # ok THEN ERROR; IF ~RefText.Equal[path, lofs.first.inode.contents] THEN ERROR; }; ENDCASE => ERROR; reply _ YggNFS.Read[file: lofs.first.inode.fHandle, offset: 0, count: reply.attributes.size, block: ReadBuffer]; IF reply.status # ok THEN ERROR; IF ~RefText.Equal[ReadBuffer, lofs.first.inode.contents] THEN ERROR; }; CheckADirectory: PROC [lodin: LIST OF REF DirectoryINode] ~ { cookie: SunNFS.Cookie _ NIL; fileNameList: LIST OF REF TEXT _ NIL; files: LIST OF Rope.ROPE _ NIL; sawPointPoint: BOOL _ FALSE; allAtOnceFiles: LIST OF Rope.ROPE _ NIL; allAtOnceSawPointPoint: BOOL _ FALSE; allAtOnceNoOfNFSItems: INT _ 0; noOfFiles: INT _ 0; noOfDirs: INT _ 0; noOfNFSItems: INT _ 0; FOR files: LIST OF FileStuff _ lodin.first.filesInDirectory, files.rest UNTIL files = NIL DO CheckAFile[files]; noOfFiles _ noOfFiles + 1; ENDLOOP; FOR dirsInDir: LIST OF DirectoryStuff _ lodin.first.directoriesInDirectory, dirsInDir.rest UNTIL dirsInDir = NIL DO noOfDirs _ noOfDirs + 1; FOR parentDirs: LIST OF REF DirectoryINode _ dirsInDir.first.dirINode.inDirectory, parentDirs.rest UNTIL parentDirs = NIL DO IF parentDirs.first = lodin.first THEN EXIT; REPEAT FINISHED => ERROR; ENDLOOP; ENDLOOP; { XeachDir: YggNFS.EachDirEntryProc ~ { IF RefText.Equal[s1: filename, s2: "..", case: FALSE] THEN { allAtOnceSawPointPoint _ TRUE; RETURN; }; allAtOnceFiles _ CONS[Rope.FromRefText[filename], allAtOnceFiles]; allAtOnceNoOfNFSItems _ allAtOnceNoOfNFSItems + 1; }; status: SunNFS.Stat; eof: BOOL _ FALSE; [status: status, eof: eof] _ YggNFS.Readdir[dir: lodin.first.fHandle, cookie: NIL, count: 1000, eachDirEntry: XeachDir]; IF status # ok THEN ERROR; IF ~eof THEN ERROR; }; DO eachDir: YggNFS.EachDirEntryProc ~ { IF RefText.Equal[s1: filename, s2: "..", case: FALSE] THEN { sawPointPoint _ TRUE; RETURN; }; fileNameList _ CONS[filename, fileNameList]; files _ CONS[Rope.FromRefText[filename], files]; noOfNFSItems _ noOfNFSItems + 1; }; status: SunNFS.Stat; eof: BOOL _ FALSE; [status: status, eof: eof, newCookie: cookie] _ YggNFS.Readdir[dir: lodin.first.fHandle, cookie: cookie, count: RandomChooseInt[randomStream, 1, 5], eachDirEntry: eachDir]; IF status # ok THEN ERROR; IF eof THEN EXIT; ENDLOOP; IF noOfNFSItems # (noOfFiles + noOfDirs) THEN ERROR; FOR fnl: LIST OF REF TEXT _ fileNameList, fnl.rest UNTIL fnl = NIL DO foundIt: BOOL _ FALSE; FOR files: LIST OF FileStuff _ lodin.first.filesInDirectory, files.rest UNTIL files = NIL DO IF RefText.Equal[fnl.first, files.first.fileName] THEN {foundIt _ TRUE; EXIT}; ENDLOOP; IF foundIt THEN LOOP; FOR dirs: LIST OF DirectoryStuff _ lodin.first.directoriesInDirectory, dirs.rest UNTIL dirs = NIL DO IF RefText.Equal[fnl.first, dirs.first.directoryName] THEN {foundIt _ TRUE; EXIT}; ENDLOOP; IF ~foundIt THEN ERROR; ENDLOOP; FOR files: LIST OF FileStuff _ lodin.first.filesInDirectory, files.rest UNTIL files = NIL DO CheckAFile[files]; ENDLOOP; }; argv: CommandTool.ArgumentVector; out: IO.STREAM; noisy: BOOL _ FALSE; fixedRandom: BOOL _ FALSE; offset: INT _ 0; noTests: INT; mntStatus: SunMount.FHStatus; mountINode: REF DirectoryINode; startTime: BasicTime.GMT _ BasicTime.Now[]; out _ cmd.out; argv _ CommandTool.Parse[cmd: cmd ! CommandTool.Failed => {msg _ errorMsg; GO TO failed}]; IF argv.argc < 2 THEN GOTO failed; DO IF Rope.InlineFetch[argv[offset+1], 0] = '- THEN { c: CHAR; IF argv[offset+1].Length[] # 2 THEN GOTO failed; c _ argv[offset+1].InlineFetch[1] ; SELECT c FROM 'n => noisy _ TRUE; 'f => fixedRandom _ TRUE; ENDCASE; offset _ offset + 1; } ELSE EXIT; ENDLOOP; noTests _ Convert.IntFromRope[argv[1+offset] ! Convert.Error => GOTO failed]; KnownDirectoryINodes _ NIL; NumberOfKnownDirectoryINodess _ 0; NumberOfRMDirectories _ 0; FailedToRMDirCount _ 0; KnownFiles _ NIL; NumberOfKnownFiles _ 0; IF noTests <= 0 THEN RETURN; randomStream _ Random.Create[range: 16384, seed: -1] ; IF YggNFS.MakeFileSystem[] THEN ERROR; mntStatus _ YggNFS.Mnt["/"]; IF mntStatus.status # 0 THEN ERROR; mountINode _ NEW[DirectoryINode _ [mntStatus.directory, NIL, NIL, NIL, TRUE]]; KnownDirectoryINodes _ LIST[mountINode]; FOR loopCount: INT IN (0..noTests] DO rand: INT _ RandomChooseInt[randomStream, 0, 40]; Process.CheckForAbort[]; IF noisy AND (loopCount MOD 100) = 0 THEN { freePages: INT _ YggFile.ServerInfo[].secondaryBlocksFree; out.PutF["..."]; PrintTimeOnOut[startTime, out]; out.PutF["(loopCount %g, number of files %g, free pages %g, at %g)...\n", IO.int[loopCount], IO.int[NumberOfKnownFiles], IO.int[freePages], IO.time[]]; }; IF loopCount = noTests THEN rand _ 0; SELECT rand FROM IN [0..1) => { -- look up all old files lofs: LIST OF FileStuff; lodin: LIST OF REF DirectoryINode; skipMax: INT _ 1; skipCnt: INT _ 0; IF NumberOfKnownFiles <= 0 THEN LOOP; SELECT loopCount FROM IN [250..500) => skipMax _ RandomChooseInt[randomStream, 1, 3]; IN [500..1000) => skipMax _ RandomChooseInt[randomStream, 1, 7]; IN [1000..2000) => skipMax _ RandomChooseInt[randomStream, 1, 15]; IN [2000..4000) => skipMax _ RandomChooseInt[randomStream, 1, 33]; IN [4000..8000) => skipMax _ RandomChooseInt[randomStream, 1, 67]; IN [8000..16000) => skipMax _ RandomChooseInt[randomStream, 1, 155]; ENDCASE; IF loopCount = noTests THEN skipMax _ 1; IF noisy THEN { out.PutF["\n"]; PrintTimeOnOut[startTime, out]; out.PutF["Start Look Up All Old files/directories, loopCount= %g, skipMax= %g\n", IO.int[loopCount], IO.int[skipMax]]; }; FOR lofs _ KnownFiles, lofs.rest UNTIL lofs = NIL DO skipCnt _ skipCnt + 1; IF skipCnt >= skipMax THEN skipCnt _ 0 ELSE LOOP; CheckAFile[lofs]; ENDLOOP; FOR lodin _ KnownDirectoryINodes, lodin.rest UNTIL lodin = NIL DO skipCnt _ skipCnt + 1; IF skipCnt >= skipMax THEN skipCnt _ 0 ELSE LOOP; CheckADirectory[lodin]; ENDLOOP; IF noisy THEN { PrintTimeOnOut[startTime, out]; out.PutF["Look Up All Old files/directories Done\n\n"]; }; }; IN [1..13) => { -- make new file with a contents; sometimes the file is a symbolic link rand2: INT; lodin: LIST OF REF DirectoryINode; now: SunNFS.TimeVal; symbolicLink: BOOL _ FALSE; mode: CARD; uid: CARD; gid: CARD; oldFree: INT; newFileName: REF TEXT; contents: REF TEXT; reply: SunNFS.DirOpRes; replyAttr: SunNFS.AttrStat; status: SunNFS.Stat; now _ SunTimeFromGMT[BasicTime.Now[]]; rand2 _ RandomChooseInt[randomStream, 0, NumberOfKnownDirectoryINodess]; FOR lodin _ KnownDirectoryINodes, lodin.rest UNTIL lodin = NIL DO rand2 _ rand2 - 1; IF rand2 <= 0 AND ~lodin.first.rmOnly THEN EXIT; ENDLOOP; IF lodin = NIL THEN LOOP; IF lodin.first.isRoot AND NumberOfKnownDirectoryINodess # 0 THEN LOOP; -- root directory, and there are others newFileName _ PickAName[lodin]; uid _ PickUID[]; gid _ PickGID[]; oldFree _ YggFile.ServerInfo[].secondaryBlocksFree; IF RandomChooseInt[randomStream, 0, 6] = 3 THEN { lookupStat: SunNFS.DirOpRes; symbolicLink _ TRUE; mode _ PickMode[] + YggNFS.symbolicLinkModeBits; contents _ PickAName[NIL]; status _ YggNFS.Symlink[from: [dir: lodin.first.fHandle, name: newFileName], to: contents, attributes: [mode: mode, uid: uid, gid: gid, size: RefText.Length[contents], atime: now, mtime: now]]; IF status # ok THEN ERROR; lookupStat _ YggNFS.Lookup[which: [dir: lodin.first.fHandle, name: newFileName]]; IF lookupStat.status # ok THEN ERROR; reply.file _ lookupStat.file; } ELSE { contents _ MakeUpContents[]; mode _ PickMode[] + YggNFS.regularModeBits; reply _ YggNFS.Create[where: [dir: lodin.first.fHandle, name: newFileName], attributes: [mode: mode, uid: uid, gid: gid, size: RefText.Length[contents], atime: now, mtime: now]]; IF reply.status # ok THEN ERROR; replyAttr _ YggNFS.Write[file: reply.file, offset: 0, count: RefText.Length[contents], block: contents]; IF replyAttr.status # ok THEN ERROR; }; NumberOfKnownFiles _ NumberOfKnownFiles + 1; lodin.first.filesInDirectory _ CONS[[newFileName, NEW[INodeStuff _ [fHandle: reply.file, inDirectory: LIST[lodin.first], contents: contents, mode: mode, uid: uid, gid: gid]]], lodin.first.filesInDirectory]; KnownFiles _ CONS[lodin.first.filesInDirectory.first, KnownFiles]; IF noisy THEN { newFree: INT _ YggFile.ServerInfo[].secondaryBlocksFree; newDID: DID _ DIDFromFHandle[reply.file]; preferedPrintName: Rope.ROPE; preferedPrintName _ MakePreferedPrintName[lodin.first.filesInDirectory]; IF symbolicLink THEN out.PutF["Make new symbolic link: name= %g, did: %g, took %g pages\n", IO.rope[preferedPrintName], IO.card[newDID.didLow], IO.int[oldFree - newFree] ] ELSE out.PutF["Make new file: name= %g, did: %g, took %g pages\n", IO.rope[preferedPrintName], IO.card[newDID.didLow], IO.int[oldFree - newFree] ]; }; }; IN [13..19) => { -- delete a file rand2: INT; fileCount: INT _ 0; lodin: LIST OF REF DirectoryINode; lofiles: LIST OF FileStuff; allKnownFiles: LIST OF FileStuff _ NIL; prevAllKnownFiles: LIST OF FileStuff _ NIL; prevFiles: LIST OF FileStuff _ NIL; lordin: LIST OF REF DirectoryINode; prevDir: LIST OF REF DirectoryINode; dirINode: REF DirectoryINode; status: SunNFS.Stat; oldFree: INT; IF NumberOfKnownFiles = 0 THEN LOOP; rand2 _ RandomChooseInt[randomStream, 0, NumberOfKnownDirectoryINodess]; FOR lodin _ KnownDirectoryINodes, lodin.rest UNTIL lodin = NIL DO rand2 _ rand2 - 1; IF rand2 <= 0 THEN EXIT; ENDLOOP; IF lodin = NIL THEN LOOP; dirINode _ lodin.first; FOR lofs: LIST OF FileStuff _ dirINode.filesInDirectory, lofs.rest UNTIL lofs = NIL DO fileCount _ fileCount + 1; ENDLOOP; IF fileCount = 0 THEN LOOP; rand2 _ RandomChooseInt[randomStream, 0, fileCount-1]; FOR lofiles _ dirINode.filesInDirectory, lofiles.rest UNTIL lofiles = NIL DO rand2 _ rand2 -1; IF rand2 <= 0 THEN EXIT; prevFiles _ lofiles; ENDLOOP; IF lofiles = NIL THEN LOOP; oldFree _ YggFile.ServerInfo[].secondaryBlocksFree; status _ YggNFS.Remove[which: [dir: dirINode.fHandle, name: lofiles.first.fileName]]; IF status # ok THEN ERROR; NumberOfKnownFiles _ NumberOfKnownFiles - 1; FOR allKnownFiles _ KnownFiles, allKnownFiles.rest UNTIL allKnownFiles = NIL DO IF RefText.Equal[lofiles.first.fileName, allKnownFiles.first.fileName] AND (lofiles.first.inode = allKnownFiles.first.inode) THEN EXIT; prevAllKnownFiles _ allKnownFiles; REPEAT FINISHED => ERROR; ENDLOOP; IF prevAllKnownFiles = NIL THEN KnownFiles _ allKnownFiles.rest ELSE prevAllKnownFiles.rest _ allKnownFiles.rest; IF prevFiles = NIL THEN dirINode.filesInDirectory _ lofiles.rest ELSE prevFiles.rest _ lofiles.rest; FOR lordin _ lofiles.first.inode.inDirectory, lordin.rest UNTIL lordin = NIL DO IF lordin.first = dirINode THEN { EXIT; }; prevDir _ lordin; REPEAT FINISHED => ERROR; ENDLOOP; IF prevDir = NIL THEN lofiles.first.inode.inDirectory _ lordin.rest ELSE prevDir.rest _ lordin.rest; IF lofiles.first.inode.inDirectory # NIL THEN { -- multi-hard link file has had one of it's links deleted. Does the other one still work? FOR files: LIST OF FileStuff _ lofiles.first.inode.inDirectory.first.filesInDirectory, files.rest UNTIL files = NIL DO IF files.first.inode = lofiles.first.inode THEN { CheckAFile[files]; EXIT; }; REPEAT FINISHED => ERROR; ENDLOOP; CheckADirectory[lofiles.first.inode.inDirectory]; -- check the directory too }; IF noisy THEN { newFree: INT _ YggFile.ServerInfo[].secondaryBlocksFree; out.PutF["Delete file: name= %g in directory name= %g, got %g pages\n", IO.rope[RefText.TrustTextAsRope[lofiles.first.fileName]], IO.rope[MakePreferedDirPrintName[dirINode]], IO.int[newFree - oldFree] ]; }; }; IN [19..21) => { -- make new directory rand2: INT; lodin: LIST OF REF DirectoryINode; now: SunNFS.TimeVal; mode: CARD; uid: CARD; gid: CARD; oldFree: INT; newDirName: REF TEXT; reply: SunNFS.DirOpRes; now _ SunTimeFromGMT[BasicTime.Now[]]; rand2 _ RandomChooseInt[randomStream, 0, NumberOfKnownDirectoryINodess]; FOR lodin _ KnownDirectoryINodes, lodin.rest UNTIL lodin = NIL DO rand2 _ rand2 - 1; IF rand2 <= 0 AND ~lodin.first.rmOnly THEN EXIT; ENDLOOP; IF lodin = NIL THEN LOOP; IF lodin.first.isRoot AND NumberOfKnownDirectoryINodess # 0 THEN LOOP; -- root directory, and there are others newDirName _ PickAName[lodin]; mode _ PickMode[]; uid _ PickUID[]; gid _ PickGID[]; oldFree _ YggFile.ServerInfo[].secondaryBlocksFree; reply _ YggNFS.Mkdir[where: [dir: lodin.first.fHandle, name: newDirName], attributes: [mode: mode, uid: uid, gid: gid, size: 0, atime: now, mtime: now]]; IF reply.status # ok THEN ERROR; NumberOfKnownDirectoryINodess _ NumberOfKnownDirectoryINodess + 1; lodin.first.directoriesInDirectory _ CONS[[newDirName, NEW[DirectoryINode _ [fHandle: reply.file, inDirectory: LIST[lodin.first], filesInDirectory: NIL, directoriesInDirectory: NIL, isRoot: FALSE]]], lodin.first.directoriesInDirectory]; KnownDirectoryINodes _ CONS[lodin.first.directoriesInDirectory.first.dirINode, KnownDirectoryINodes]; IF noisy THEN { newFree: INT _ YggFile.ServerInfo[].secondaryBlocksFree; newDID: DID _ DIDFromFHandle[reply.file]; preferedPrintName: Rope.ROPE; preferedPrintName _ MakePreferedDirPrintName[lodin.first.directoriesInDirectory.first.dirINode]; out.PutF["Make new directory: name= %g, did: %g, took %g pages\n", IO.rope[preferedPrintName], IO.card[newDID.didLow], IO.int[oldFree - newFree] ]; }; }; IN [21..22) => { -- remove a directory lodin: LIST OF REF DirectoryINode; FOR lodin _ KnownDirectoryINodes, lodin.rest UNTIL lodin = NIL DO IF lodin.first.isRoot THEN LOOP; IF ~lodin.first.rmOnly THEN LOOP; IF lodin.first.filesInDirectory = NIL AND lodin.first.directoriesInDirectory = NIL THEN { FailedToRMDirCount _ 0; RemoveDirectory[lodin, noisy, out]; EXIT; }; ENDLOOP; IF lodin # NIL THEN { LOOP; }; IF NumberOfRMDirectories * 5 < NumberOfKnownDirectoryINodess OR FailedToRMDirCount > 5 THEN { FailedToRMDirCount _ 0; FOR lodin _ KnownDirectoryINodes, lodin.rest UNTIL lodin = NIL DO IF lodin.first.isRoot THEN LOOP; IF lodin.first.rmOnly THEN LOOP; lodin.first.rmOnly _ TRUE; IF noisy THEN { preferedPrintName: Rope.ROPE; preferedPrintName _ MakePreferedDirPrintName[lodin.first]; out.PutF["Flag directory for RmDir: name= %g\n", IO.rope[preferedPrintName] ]; }; NumberOfRMDirectories _ NumberOfRMDirectories + 1; EXIT; ENDLOOP; } ELSE { FailedToRMDirCount _ FailedToRMDirCount + 1; }; }; IN [22..24) => { -- change a file's attrs rand2: INT; dirINode: REF DirectoryINode; prevFiles: LIST OF FileStuff _ NIL; fileCount: INT _ 0; lofiles: LIST OF FileStuff; lodin: LIST OF REF DirectoryINode; mode: CARD; uid: CARD; gid: CARD; oldFree: INT _ YggFile.ServerInfo[].secondaryBlocksFree; reply: SunNFS.AttrStat; IF NumberOfKnownFiles = 0 THEN LOOP; rand2 _ RandomChooseInt[randomStream, 0, NumberOfKnownDirectoryINodess]; FOR lodin _ KnownDirectoryINodes, lodin.rest UNTIL lodin = NIL DO rand2 _ rand2 - 1; IF rand2 <= 0 THEN EXIT; ENDLOOP; IF lodin = NIL THEN LOOP; dirINode _ lodin.first; FOR lofs: LIST OF FileStuff _ dirINode.filesInDirectory, lofs.rest UNTIL lofs = NIL DO fileCount _ fileCount + 1; ENDLOOP; IF fileCount = 0 THEN LOOP; rand2 _ RandomChooseInt[randomStream, 0, fileCount-1]; FOR lofiles _ dirINode.filesInDirectory, lofiles.rest UNTIL lofiles = NIL DO rand2 _ rand2 -1; IF rand2 <= 0 THEN EXIT; prevFiles _ lofiles; ENDLOOP; IF lofiles = NIL THEN LOOP; mode _ PickMode[] + CARD[Basics.BITAND[lofiles.first.inode.mode, YggNFS.typeBits]]; uid _ PickUID[]; gid _ PickGID[]; reply _ YggNFS.Setattr[file: lofiles.first.inode.fHandle, attributes: [mode: mode, uid: uid, gid: gid, size: CARD.LAST, atime: [CARD.LAST, CARD.LAST], mtime: [CARD.LAST, CARD.LAST]]]; IF reply.status # ok THEN ERROR; lofiles.first.inode.mode _ mode; lofiles.first.inode.uid _ uid; lofiles.first.inode.gid _ gid; IF noisy THEN { newFree: INT _ YggFile.ServerInfo[].secondaryBlocksFree; preferedPrintName: Rope.ROPE; preferedPrintName _ MakePreferedPrintName[lofiles]; out.PutF["Changed attributes on file: name= %g\n", IO.rope[preferedPrintName] ]; IF newFree # oldFree THEN out.PutF[" ---- Change attributes took %g pages\n", IO.int[newFree - oldFree] ]; }; }; IN [24..29) => { -- make a hard link rand2: INT; lodin: LIST OF REF DirectoryINode; loallFileStuff: LIST OF FileStuff; mode: CARD; uid: CARD; gid: CARD; oldFree: INT _ YggFile.ServerInfo[].secondaryBlocksFree; newFileName: REF TEXT; oldPreferedPrintName: Rope.ROPE; fileFHandle: SunNFS.FHandle; status: SunNFS.Stat; IF NumberOfKnownFiles = 0 THEN LOOP; rand2 _ RandomChooseInt[randomStream, 0, NumberOfKnownDirectoryINodess]; FOR lodin _ KnownDirectoryINodes, lodin.rest UNTIL lodin = NIL DO rand2 _ rand2 - 1; IF rand2 <= 0 AND ~lodin.first.rmOnly THEN EXIT; ENDLOOP; IF lodin = NIL THEN LOOP; IF lodin.first.isRoot AND NumberOfKnownDirectoryINodess # 0 THEN LOOP; -- root directory, and there are others rand2 _ RandomChooseInt[randomStream, 0, NumberOfKnownFiles-1]; FOR loallFileStuff _ KnownFiles, loallFileStuff.rest UNTIL loallFileStuff = NIL DO rand2 _ rand2 - 1; IF rand2 <= 0 AND CARD[Basics.BITAND[loallFileStuff.first.inode.mode, YggNFS.typeBits]] # YggNFS.symbolicLinkModeBits THEN EXIT; ENDLOOP; IF loallFileStuff = NIL THEN LOOP; oldPreferedPrintName _ MakePreferedPrintName[loallFileStuff]; fileFHandle _ loallFileStuff.first.inode.fHandle; newFileName _ PickAName[lodin]; mode _ PickMode[]; uid _ PickUID[]; gid _ PickGID[]; status _ YggNFS.Link[to: fileFHandle, as: [dir: lodin.first.fHandle, name: newFileName]]; lodin.first.filesInDirectory _ CONS[[newFileName, loallFileStuff.first.inode], lodin.first.filesInDirectory]; loallFileStuff.first.inode.inDirectory _ CONS[lodin.first, loallFileStuff.first.inode.inDirectory]; KnownFiles _ CONS[lodin.first.filesInDirectory.first, KnownFiles]; NumberOfKnownFiles _ NumberOfKnownFiles + 1; IF noisy THEN { newFree: INT _ YggFile.ServerInfo[].secondaryBlocksFree; preferedPrintName: Rope.ROPE; preferedPrintName _ MakePreferedPrintName[lodin.first.filesInDirectory]; out.PutF["Make new hard link: name= %g (one other name is %g)\n", IO.rope[preferedPrintName], IO.rope[oldPreferedPrintName] ]; IF newFree # oldFree THEN out.PutF[" ---- New hard link took %g pages\n", IO.int[newFree - oldFree] ]; }; }; IN [29..35) => { -- rename file rand2: INT; fileCount: INT _ 0; fromDir: LIST OF REF DirectoryINode; lofiles: LIST OF FileStuff; allKnownFiles: LIST OF FileStuff _ NIL; prevAllKnownFiles: LIST OF FileStuff _ NIL; prevFiles: LIST OF FileStuff _ NIL; lordin: LIST OF REF DirectoryINode; prevDir: LIST OF REF DirectoryINode; dirINode: REF DirectoryINode; status: SunNFS.Stat; toDir: LIST OF REF DirectoryINode; now: SunNFS.TimeVal; symbolicLink: BOOL _ FALSE; newFileName: REF TEXT; oldPreferedPrintName: Rope.ROPE; oldFree: INT _ YggFile.ServerInfo[].secondaryBlocksFree; IF NumberOfKnownFiles = 0 THEN LOOP; rand2 _ RandomChooseInt[randomStream, 0, NumberOfKnownDirectoryINodess]; FOR fromDir _ KnownDirectoryINodes, fromDir.rest UNTIL fromDir = NIL DO rand2 _ rand2 - 1; IF rand2 <= 0 THEN EXIT; ENDLOOP; IF fromDir = NIL THEN LOOP; dirINode _ fromDir.first; FOR lofs: LIST OF FileStuff _ dirINode.filesInDirectory, lofs.rest UNTIL lofs = NIL DO fileCount _ fileCount + 1; ENDLOOP; IF fileCount = 0 THEN LOOP; rand2 _ RandomChooseInt[randomStream, 0, fileCount-1]; FOR lofiles _ dirINode.filesInDirectory, lofiles.rest UNTIL lofiles = NIL DO rand2 _ rand2 -1; IF rand2 <= 0 THEN EXIT; prevFiles _ lofiles; ENDLOOP; IF lofiles = NIL THEN LOOP; oldPreferedPrintName _ MakePreferedPrintName[lofiles]; now _ SunTimeFromGMT[BasicTime.Now[]]; rand2 _ RandomChooseInt[randomStream, 0, NumberOfKnownDirectoryINodess]; FOR toDir _ KnownDirectoryINodes, toDir.rest UNTIL toDir = NIL DO rand2 _ rand2 - 1; IF rand2 <= 0 AND ~toDir.first.rmOnly THEN EXIT; ENDLOOP; IF toDir = NIL THEN LOOP; IF toDir.first.isRoot AND NumberOfKnownDirectoryINodess # 0 THEN LOOP; -- root directory, and there are others newFileName _ PickAName[toDir]; status _ YggNFS.Rename[from: [dir: fromDir.first.fHandle, name: lofiles.first.fileName], to: [dir: toDir.first.fHandle, name: newFileName]]; IF status # ok THEN ERROR; IF prevFiles = NIL THEN dirINode.filesInDirectory _ lofiles.rest ELSE prevFiles.rest _ lofiles.rest; FOR lordin _ lofiles.first.inode.inDirectory, lordin.rest UNTIL lordin = NIL DO IF lordin.first = dirINode THEN { EXIT; }; prevDir _ lordin; REPEAT FINISHED => ERROR; ENDLOOP; IF prevDir = NIL THEN lofiles.first.inode.inDirectory _ lordin.rest ELSE prevDir.rest _ lordin.rest; toDir.first.filesInDirectory _ CONS[[newFileName, lofiles.first.inode], toDir.first.filesInDirectory]; lofiles.first.inode.inDirectory _ CONS[toDir.first, lofiles.first.inode.inDirectory]; FOR allKnownFiles _ KnownFiles, allKnownFiles.rest UNTIL allKnownFiles = NIL DO IF RefText.Equal[lofiles.first.fileName, allKnownFiles.first.fileName] AND (lofiles.first.inode = allKnownFiles.first.inode) THEN { allKnownFiles.first.fileName _ newFileName; EXIT; }; REPEAT FINISHED => ERROR; ENDLOOP; IF noisy THEN { newFree: INT _ YggFile.ServerInfo[].secondaryBlocksFree; newPreferedPrintName: Rope.ROPE; newPreferedPrintName _ MakePreferedPrintName[toDir.first.filesInDirectory]; out.PutF["Rename: from = %g to = %g\n", IO.rope[oldPreferedPrintName], IO.rope[newPreferedPrintName] ]; IF newFree # oldFree THEN out.PutF[" ---- Rename took %g pages\n", IO.int[newFree - oldFree] ]; }; }; ENDCASE; ENDLOOP; EXITS failed => {result _ $Failure}; }; PrintTimeOnOut: PROC [startTime: BasicTime.GMT, out: IO.STREAM] ~ { now: BasicTime.GMT _ BasicTime.Now[]; delta: INT; delta _ BasicTime.Period[from: startTime, to: now]; out.PutF["%g seconds: ", IO.int[delta]]; }; RemoveDirectory: PROC [lodin: LIST OF REF DirectoryINode, noisy: BOOL, out: IO.STREAM] ~ { status: SunNFS.Stat; oldFree: INT; preferedPrintName: Rope.ROPE; prevDir: LIST OF REF DirectoryINode _ NIL; IF lodin.first.isRoot THEN ERROR; FOR ll: LIST OF REF DirectoryINode _ KnownDirectoryINodes, ll.rest UNTIL ll = NIL DO IF ll = lodin THEN EXIT; prevDir _ ll; REPEAT FINISHED => ERROR; ENDLOOP; oldFree _ YggFile.ServerInfo[].secondaryBlocksFree; preferedPrintName _ MakePreferedDirPrintName[lodin.first]; IF prevDir = NIL THEN KnownDirectoryINodes _ lodin.rest ELSE prevDir.rest _ lodin.rest; FOR parentDirs: LIST OF REF DirectoryINode _ lodin.first.inDirectory, parentDirs.rest UNTIL parentDirs = NIL DO prevDirInParent: LIST OF DirectoryStuff _ NIL; FOR nowDirs: LIST OF DirectoryStuff _ parentDirs.first.directoriesInDirectory, nowDirs.rest UNTIL nowDirs = NIL DO IF nowDirs.first.dirINode = lodin.first THEN { status _ YggNFS.Rmdir[which: [dir: parentDirs.first.fHandle, name: nowDirs.first.directoryName]]; IF status # ok THEN ERROR; IF prevDirInParent = NIL THEN parentDirs.first.directoriesInDirectory _ nowDirs.rest ELSE prevDirInParent.rest _ nowDirs.rest; EXIT; }; prevDirInParent _ nowDirs; REPEAT FINISHED => ERROR; ENDLOOP; ENDLOOP; NumberOfKnownDirectoryINodess _ NumberOfKnownDirectoryINodess - 1; IF noisy THEN { newFree: INT _ YggFile.ServerInfo[].secondaryBlocksFree; out.PutF["Directory RmDired: name= %g, got %g pages\n", IO.rope[preferedPrintName], IO.int[newFree - oldFree] ]; }; }; RandomChooseInt: PROC [rs: Random.RandomStream _ NIL, min: INT _ 0, max: INT] RETURNS [int: INT] ~ { RETURN[Random.ChooseInt[rs, min, max]]; }; PickAName: PROC [lodin: LIST OF REF DirectoryINode] RETURNS [newFileName: REF TEXT] ~ { DO nameLength: INT; itsADup: BOOL _ FALSE; nameLength _ RandomChooseInt[randomStream, 3, 10]; newFileName _ RefText.New[nameLength]; FOR inx: INT IN [0..nameLength) DO char: CHAR; char _ VAL[BYTE[ORD['a] + RandomChooseInt[randomStream, 0, 25]]]; [] _ RefText.AppendChar[newFileName, char]; ENDLOOP; IF lodin # NIL THEN { FOR lof: LIST OF FileStuff _ lodin.first.filesInDirectory, lof.rest UNTIL lof = NIL DO IF RefText.Equal[newFileName, lof.first.fileName] THEN { itsADup _ TRUE; EXIT; }; ENDLOOP; IF itsADup THEN LOOP; FOR dirs: LIST OF DirectoryStuff _ lodin.first.directoriesInDirectory, dirs.rest UNTIL dirs = NIL DO IF RefText.Equal[newFileName, dirs.first.directoryName] THEN { itsADup _ TRUE; EXIT; }; ENDLOOP; IF itsADup THEN LOOP; }; EXIT; ENDLOOP; }; MakePreferedPrintName: PROC [filesInDirectory: LIST OF FileStuff] RETURNS [preferedPrintName: Rope.ROPE] ~ { preferedPrintName _ Rope.Concat[MakePreferedDirPrintName[filesInDirectory.first.inode.inDirectory.first], RefText.TrustTextAsRope[filesInDirectory.first.fileName]]; }; MakePreferedDirPrintName: PROC [dirINode: REF DirectoryINode] RETURNS [preferedPrintName: Rope.ROPE _ NIL] ~ { DO dirStuff: REF DirectoryINode; IF dirINode.isRoot THEN EXIT; dirStuff _ dirINode.inDirectory.first; FOR lodirs: LIST OF DirectoryStuff _ dirStuff.directoriesInDirectory, lodirs.rest UNTIL lodirs = NIL DO IF lodirs.first.dirINode = dirINode THEN { preferedPrintName _ Rope.Cat[RefText.TrustTextAsRope[lodirs.first.directoryName], "/", preferedPrintName]; EXIT; }; REPEAT FINISHED => ERROR; ENDLOOP; dirINode _ dirStuff; ENDLOOP; preferedPrintName _ Rope.Concat["/", preferedPrintName]; }; MakeUpContents: PROC RETURNS [contents: REF TEXT] ~ { size: INT; size _ RandomChooseInt[randomStream, 0, MaxFileSize-1]; contents _ RefText.New[size]; FOR inx: INT IN [0..size) DO char: CHAR; char _ VAL[BYTE[ORD['a] + RandomChooseInt[randomStream, 0, 25]]]; [] _ RefText.InlineAppendChar[contents, char]; ENDLOOP; }; PickUID: PROC RETURNS [uid: CARD] ~ { uid _ RandomChooseInt[randomStream, 1, 321]; }; PickGID: PROC RETURNS [gid: CARD] ~ { gid _ RandomChooseInt[randomStream, 1, 42]; }; PickMode: PROC RETURNS [theBits: CARD] ~ { theBits _ 0; FOR i: INT IN [0..4) DO theBits _ theBits * 8; theBits _ theBits + RandomChooseInt[randomStream, 0, 7]; ENDLOOP; }; PointerFromRefText: UNSAFE PROC [block: REF READONLY TEXT] RETURNS [LONG POINTER] ~ TRUSTED INLINE { RETURN[ LOOPHOLE[block, LONG POINTER] + UNITS[TEXT[0]] ] }; DIDFromFHandle: PROC [file: SunNFS.FHandle] RETURNS [did: YggDID.DID] ~ TRUSTED { did _ YggDID.VolatilizeDID[PointerFromRefText[file]]; }; FHandleFromDID: PROC [did: YggDID.DID] RETURNS [file: SunNFS.FHandle] ~ TRUSTED { file _ NEW[TEXT[SunNFS.fhSize]]; YggDID.StabilizeDID[did, PointerFromRefText[file]]; }; GMTFromSunTime: PROC [sunTime: SunNFS.TimeVal] RETURNS [gmt: BasicTime.GMT] ~ { RETURN [BasicTime.Update[sunEpoch, INT[sunTime.seconds]]]; }; SunTimeFromGMT: PROC [gmt: BasicTime.GMT] RETURNS [sunTime: SunNFS.TimeVal] ~ { RETURN [ [seconds~BasicTime.Period[from~sunEpoch, to~gmt], useconds~0] ]; }; CompareSunTimes: PROC [t1, t2: SunNFS.TimeVal] RETURNS [Basics.Comparison] ~ { RETURN [SELECT t1.seconds FROM < t2.seconds => less, > t2.seconds => greater, ENDCASE => Basics.CompareCard[t1.useconds, t2.useconds]]; }; Init: PROC = { Commander.Register["NFSTest", NFSTestProc, "NFSTest noTests"]; ReadBuffer _ RefText.New[MaxFileSize]; }; Init[]; END. xNFSTestImpl.mesa Copyright Σ 1989 by Xerox Corporation. All rights reserved. Bob Hagmann May 10, 1989 10:10:09 am PDT junk Global data 1) make a new object with int contents of 77 1a) bug: now it looks it up 2) add one attribute to it: "two" with two values: (NIL with a "foo", and field2 with a did of 1234 and a string of bar) VolatilizeTest Command [cmd: Handle] RETURNS [result: REF _ NIL, msg: ROPE _ NIL] CommandObject = [in, out, err: STREAM, commandLine, command: ROPE, ...] REPEAT FINISHED => ERROR; PROC [fileid: CARD, filename: -- ephemeral -- FileName] RETURNS [accept: BOOL, continue: BOOL _ TRUE]; PROC [fileid: CARD, filename: -- ephemeral -- FileName] RETURNS [accept: BOOL, continue: BOOL _ TRUE]; YggdrasilInit.Initialize["bogus", "pooz"]; See if dirs are empty If it looks like we need another lofiles.first is the file to rename Utilities IF fixedRandom THEN { v: INT; v _ RandomSequence[randomIndex]; IF v >= min AND v <= max THEN int _ v ELSE { v _ v MOD (max-min); v _ v + min; }; IF v < min OR v > max THEN ERROR; int _ v; randomIndex _ (randomIndex + 1) MOD RandomSequenceSize; } ELSE Initialization Κ&˜šœ™Icodešœ<™K˜—Kšœ˜—Kšœp˜pKšœœœ˜ Kšœ7œœ˜DK˜K˜—š žœœ œœœ˜=Kšœœ˜Kš œœœœœœ˜%Kš œœœœœ˜Kšœœœ˜Kš œœœœœ˜(Kšœœœ˜%Kšœœ˜Kšœ œ˜Kšœ œ˜Kšœœ˜š œœœ6œ œ˜\Kšœ˜K˜Kšœ˜—š œ œœEœ œ˜sK˜š œ œœœHœœ˜|Kšœ œœ˜,Kšœœœ˜Kšœ˜—Kšœ˜—šœ˜šžœ˜%šœ œ Οcœ ™7Kšœ œ œœ™.—šœ-œœ˜˜@Mšœ@˜BMšœ@˜BMšœ@˜BMšœB˜DMšœ˜—Mšœœ ˜(šœœ˜Mšœ˜Mšœ˜MšœRœœ˜vM˜—šœœœ˜4M˜Mšœœ œœ˜1Mšœ˜Mšœ˜—šœ*œ œ˜AM˜Mšœœ œœ˜1Mšœ˜Mšœ˜—šœœ˜Mšœ˜Mšœ7˜7M˜—M˜—šœŸG˜WM˜ Mšœœœ˜"M˜Mšœœœ˜Mšœœ˜ Mšœœ˜ Mšœœ˜ Mšœ œ˜ Mšœ œœ˜Kšœ œœ˜Kšœ˜Kšœ˜Kšœ˜Kšœ&˜&MšœH˜Hšœ*œ œ˜AMšœ˜Mšœ œœœ˜0Mšœ˜—Mšœ œœœ˜Mš œœ#œœŸ'˜nMšœ˜Mšœ˜Mšœ˜Mšœ3˜3šœ)œ˜1Mšœ˜Mšœœ˜Mšœ0˜0Mšœ˜MšœΑ˜ΑMšœ œœ˜MšœQ˜QMšœ%˜%Mšœ˜M˜—šœœ˜Mšœ˜Mšœ+˜+Mšœ²˜²Mšœœœ˜ Mšœh˜hMšœœœ˜$M˜—Mšœ,˜,Mšœœœ1œd˜ΞMšœ œ1˜Bšœœ˜Mšœ œ,˜8Mšœ)˜)Mšœœ˜MšœH˜HMšœœHœM˜«Mšœœ?œO˜•M˜—M˜—šœŸ˜!Mšœœ˜ Mšœ œ˜Mšœœœœ˜"Mšœ œœ ˜Mšœœœ˜'Mšœœœ˜+Mšœ œœ œ˜#Mšœœœœ˜#Mšœ œœœ˜$Mšœ œ˜Mšœ˜Mšœ œ˜ Mšœœœ˜$MšœH˜Hšœ*œ œ˜AMšœ˜Mšœ œœ˜Mšœ˜—Mšœ œœœ˜Mšœ˜š œœœ2œœ˜VM˜Mšœ˜—Mšœœœ˜Mšœ6˜6šœ3œ œ˜LMšœ˜Mšœ œœ˜Mšœ˜Mšœ˜—Mšœ œœœ˜Mšœ3˜3MšœU˜UMšœ œœ˜Mšœ,˜,šœ0œœ˜OMšœEœ3œœ˜‡Mšœ"˜"Mšœœœ˜Mšœ˜—Mšœœœ ˜?Mšœœ-˜2Mšœ œœ)˜@Mšœœ˜$šœ7œ œ˜Ošœœ˜!Mšœ˜M˜—Mšœ˜Mšœœœ˜Mšœ˜—Mšœ œœ.˜CMšœœ˜!šœ#œœŸZ˜‹š œœœPœ œ˜všœ)œ˜1Kšœ˜Kšœ˜K˜—Kšœœœ˜Kšœ˜—Mšœ3Ÿ˜MM˜—šœœ˜Mšœ œ,˜8MšœHœ8œG˜ΛM˜—M˜—šœŸ˜&Mšœœ˜ Mšœœœœ˜"M˜Mšœœ˜ Mšœœ˜ Mšœœ˜ Mšœ œ˜ Mšœ œœ˜Kšœ˜Kšœ&˜&MšœH˜Hšœ*œ œ˜AMšœ˜Mšœ œœœ˜0Mšœ˜—Mšœ œœœ˜Mš œœ#œœŸ'˜nMšœ˜Mšœ˜Mšœ˜Mšœ˜Mšœ3˜3Mšœ™˜™Mšœœœ˜ MšœB˜BMš œ%œœ5œ!œœ œ)˜μMšœœJ˜ešœœ˜Mšœ œ,˜8Mšœ)˜)Mšœœ˜Mšœ`˜`MšœCœN˜“M˜—M˜—šœŸ˜&Mšœœœœ˜"M™šœ*œ œ˜AMšœœœ˜ Mšœœœ˜!š œ œœ&œœ˜YMšœ˜Mšœ#˜#Mšœ˜M˜—Mšœ˜—šœ œœ˜Mšœ˜M˜—M™!šœ;œœ˜]Mšœ˜šœ*œ œ˜AMšœœœ˜ Mšœœœ˜ Mšœœ˜šœœ˜Mšœœ˜Mšœ:˜:Mšœ1œ˜NM˜—M˜Mšœ2˜2Mšœ˜Mšœ˜—M˜—šœœ˜Mšœ,˜,M˜—M˜—šœŸ˜)Mšœœ˜ Mšœ œ˜Mšœ œœ œ˜#Mšœ œ˜Mšœ œœ ˜Mšœœœœ˜"Mšœœ˜ Mšœœ˜ Mšœœ˜ Mšœ8˜8Mšœ˜Mšœœœ˜$MšœH˜Hšœ*œ œ˜AMšœ˜Mšœ œœ˜Mšœ˜—Mšœ œœœ˜Mšœ˜š œœœ2œœ˜VM˜Mšœ˜—Mšœœœ˜Mšœ6˜6šœ3œ œ˜LMšœ˜Mšœ œœ˜Mšœ˜Mšœ˜—Mšœ œœœ˜Mšœ œ-˜SMšœ˜Mšœ˜Mšœmœœ œœœœ œœœœ˜·Mšœœœ˜ Mšœ ˜ Mšœ˜Mšœ˜šœœ˜Mšœ œ,˜8Mšœœ˜Mšœ3˜3Mšœ3œ˜PMšœœ5œ˜jM˜—M˜—šœŸ˜$Mšœœ˜ Mšœœœœ˜"Mšœœœ ˜"Mšœœ˜ Mšœœ˜ Mšœœ˜ Mšœ8˜8Mšœ œœ˜Mšœœ˜ Mšœ˜Kšœ˜Mšœœœ˜$MšœH˜Hšœ*œ œ˜AMšœ˜Mšœ œœœ˜0Mšœ˜—Mšœ œœœ˜Mš œœ#œœŸ'˜nMšœ?˜?šœ2œœ˜RMšœ˜Mš œ œœœRœœ˜€Mšœ˜—Mšœœœœ˜"Mšœ=˜=Mšœ1˜1Mšœ˜Mšœ˜Mšœ˜Mšœ˜MšœY˜YMšœœJ˜mMšœ)œ6˜cMšœ œ1˜BMšœ,˜,šœœ˜Mšœ œ,˜8Mšœœ˜MšœH˜HMšœBœœ˜~Mšœœ1œ˜fM˜—M˜—šœŸ˜Mšœœ˜ Mšœ œ˜Mšœ œœœ˜$Mšœ œœ ˜Mšœœœ œ˜'Mšœœœ œ˜+Mšœ œœ œ˜#Mšœœœœ˜#Mšœ œœœ˜$Mšœ œ˜Mšœ˜Mšœœœœ˜"M˜Mšœœœ˜Mšœ œœ˜M˜ Mšœ8˜8Mšœœœ˜$MšœH˜Hšœ.œ œ˜GMšœ˜Mšœ œœ˜Mšœ˜—Mšœ œœœ˜Mšœ˜š œœœ2œœ˜VM˜Mšœ˜—Mšœœœ˜Mšœ6˜6šœ3œ œ˜LMšœ˜Mšœ œœ˜Mšœ˜Mšœ˜—Mšœ œœœ˜šœ6˜6Mšœ#™#—Kšœ&˜&MšœH˜Hšœ*œ œ˜AMšœ˜Mšœ œœœ˜0Mšœ˜—Mšœ œœœ˜Mš œœ#œœŸ'˜nMšœ˜MšœŒ˜ŒMšœ˜Mšœ œœ)˜@Mšœœ˜$šœ7œ œ˜Ošœœ˜!Mšœ˜M˜—Mšœ˜Mšœœœ˜Mšœ˜—Mšœ œœ.˜CMšœœ˜!MšœœC˜fMšœU˜Ušœ0œœ˜OšœEœ3œ˜ƒMšœ+˜+Mšœ˜M˜—Mšœœœ˜Mšœ˜—šœœ˜Mšœ œ,˜8M˜ MšœK˜KMšœ(œœ˜gMšœœ*œ˜_M˜—M˜—Mšœ˜—Mšœ˜—š˜Kšœ˜—M˜J˜M˜——K˜K˜—™ šžœœ!œœ˜DK˜%Kšœœ˜ Kšœ3˜3Kšœœ ˜(K˜K˜—šžœœ œœœœœœ˜ZMšœ˜Mšœ œ˜ Mšœœ˜Kš œ œœœœ˜*Kšœœœ˜!š œœœœ0œœ˜TMšœ œœ˜Mšœ ˜ Mšœœœ˜Mšœ˜—Mšœ3˜3Mšœ:˜:Kšœ œœ"˜7Kšœœ˜ š œ œœœ;œœ˜oKšœœœœ˜.š œ œœHœ œ˜ršœ&œ˜.Mšœa˜aKšœ œœ˜Kšœœœ7˜TKšœœ%˜*Kšœ˜K˜—Kšœ˜Kšœœœ˜Kšœ˜—Kšœ˜—MšœB˜Bšœœ˜Mšœ œ,˜8Mšœ8œ6˜pM˜—K˜K˜—šžœœœœ œœœ˜dšœ œ™Kšœœ™K™ Kšœ œ œ™%šœœ™Kšœœ ™K™ K™—Kšœ œ œœ™!Kšœ ™ Kšœ œ™7K™—Kšœœ™Kšœ!˜'K˜—šž œœ œœœœœ˜Wš˜Kšœ œ˜Kšœ œœ˜Kšœ2˜2Kšœ&˜&šœœœ˜"Kšœœ˜ Kšœœœœ.˜AKšœ+˜+Kšœ˜—šœ œœ˜š œœœ4œœ˜Všœ0œ˜8Kšœ œ˜Kšœ˜K˜—Kšœ˜—Kšœ œœ˜š œœœ@œœ˜dšœ6œ˜>Kšœ œ˜Kšœ˜K˜—Kšœ˜—Kšœ œœ˜K˜—Kšœ˜Kšœ˜—K˜K˜—š žœœœœ œœ˜lKšœ€˜€K˜K˜—š žœœ œœ œ˜nš˜Kšœ œ˜Kšœœœ˜Kšœ&˜&š œ œœ?œ œ˜gšœ"œ˜*Kšœj˜jKšœ˜K˜—Kšœœœ˜Kšœ˜—Kšœ˜Kšœ˜—Kšœ8˜8K˜K˜—š žœœœ œœ˜5Kšœœ˜ Kšœ7˜7Kšœ˜šœœœ ˜Kšœœ˜ Kšœœœœ.˜AKšœ.˜.Kšœ˜—K˜K˜—šžœœœœ˜%Kšœ,˜,K˜K˜—šžœœœœ˜%Kšœ+˜+K˜K˜—šžœœœ œ˜*Kšœ ˜ šœœœ˜Kšœ˜Mšœ8˜8Kšœ˜—K˜K˜—šžœœœ œœœœœœœœ˜dKš œœœœœœ ˜;K˜—š žœœœœœ˜QKšœ5˜5K˜K˜—š žœœœœœ˜QKšœ ˜ Kšœ3˜3K˜K˜—šžœœœœ˜OKšœœ˜:K˜K˜—šžœœœœ˜OKšœC˜IK˜K˜—šžœœœ˜Nšœœ ˜Kšœ˜Kšœ˜Kšœ2˜9—K˜——™K˜šžœœ˜Kšœ>˜>Kšœ&˜&K˜K˜K˜—K˜—K˜Kšœ˜K™J˜—…—uŸ