DIRECTORY BasicTime USING [GMT, Now, nullGMT, Period], BTree USING [SetUpdateInProgress], File USING [FP, Handle, nullFP], FS USING [BytesForPages, Create, Error, Lock, Open, OpenFile, PagesForBytes], FSBackdoor USING [EntryType, GuestProcsRec, highestVersion, lowestVersion, MakeFName, ProduceError, Version], FSDir USING [AcquireNextLName, AcquireOldFName, AcquireOldOrNewGName, DeleteEntry, UpdateAttachedEntry, UpdateCachedEntry, UpdateLocalEntry], FSFileOps USING [Copy, CreateFile, CreateFileStream, DeleteFile, GetFileInfo, GetProps, OpenFile, RecordUsage, SetBytesAndCreated, SetFilePages, SetProps, VolumeDesc], FSLock USING [ActiveFile, LockFile, LockAttachedToRecord, NewOpenFile, ReleaseRecord], FSName USING [IsLocal, ParseClientName, ParsedFName, ParseName, ServerAndFileRopes, VersionInfo, VersionPartFromVersion], FSRemoteFile USING [Delete, Info, Rename, Retrieve, Store], FSReport USING [LockConflict, NoCache, ReportCreation, ReportRemote, UnknownVolumeLName, UnknownFile, VersionSpecified], IO USING [Close, GetIndex, STREAM], Rope USING [Concat, Equal, Flatten, Match, ROPE, Text]; FSMainImpl1: CEDAR PROGRAM IMPORTS BasicTime, BTree, FS, FSBackdoor, FSDir, FSFileOps, FSLock, FSName, FSReport, Rope, FSRemoteFile, IO EXPORTS FS, FSBackdoor = BEGIN ActiveFile: TYPE = FSLock.ActiveFile; GMT: TYPE = BasicTime.GMT; Lock: TYPE = FS.Lock; OpenFile: TYPE = FS.OpenFile; ParsedFName: TYPE = FSName.ParsedFName; ROPE: TYPE = Rope.ROPE; Text: TYPE = Rope.Text; STREAM: TYPE = IO.STREAM; Version: TYPE = FSBackdoor.Version; IsGuestProcess: PROC [] RETURNS [isGuest: BOOL] _ FalseProc; FalseProc: PROC RETURNS [isGuest: BOOL] = { isGuest _ FALSE; }; GuestProcs: PUBLIC REF FSBackdoor.GuestProcsRec _ NEW[FSBackdoor.GuestProcsRec _ [FalseProc]]; RegisterGuestProcs: PUBLIC PROC [newProcs: REF FSBackdoor.GuestProcsRec] = { GuestProcs _ newProcs; IsGuestProcess _ newProcs.IsGuestProcess; }; Open: PUBLIC PROC [name: ROPE, lock: Lock, wantedCreatedTime: GMT, remoteCheck: BOOL, wDir: ROPE] RETURNS [file: OpenFile] = { openedName: ROPE _ NIL; a: ActiveFile; pn: ParsedFName; IF IsGuestProcess[] THEN RETURN [GuestProcs.Open[name, lock, wantedCreatedTime, remoteCheck, wDir]]; [pn, ] _ FSName.ParseClientName[name, wDir, TRUE]; IF FSName.IsLocal[pn.nameBody] THEN { -- open LName keep: CARDINAL; IF pn.volDesc = NIL THEN FSReport.UnknownVolumeLName[pn.fullName]; [a, keep] _ GetLName[pn, wantedCreatedTime, remoteCheck]; pn.version _ a.version; IF lock = read THEN { -- open LName for read gotLock: BOOL = FSLock.LockFile[a, read]; IF a.attachedTo # NIL THEN { -- set lock in attachment too IF gotLock THEN { IF NOT FSLock.LockFile[a.attachedTo, read] THEN ERROR }; FSLock.ReleaseRecord[a.attachedTo]; }; FSLock.ReleaseRecord[a]; IF NOT gotLock THEN Conflict[pn]; } -- of open LName for read ELSE { IF a.fileLock # none THEN { FSLock.ReleaseRecord[a.attachedTo]; -- nop if a.attachedTo = NIL FSLock.ReleaseRecord[a]; Conflict[pn]; }; IF a.attachedTo # NIL THEN { ENABLE FS.Error => { FSLock.ReleaseRecord[a.attachedTo]; FSLock.ReleaseRecord[a] }; globalNB: Text = Rope.Flatten[a.attachedTo.nameBody]; IF a.attachedTo.fileLock = none AND pn.volDesc.prefix = NIL THEN RenameToLocalName[pn.volDesc, globalNB, a.attachedTo.version, pn.nameBody, pn.version, a.h, keep] ELSE { bytes: INT; createdTime: GMT; toFP: File.FP; [toFP, a.h] _ FSFileOps.CreateFile[pn.volDesc.vol, FSFileOps.GetFileInfo[a.attachedTo.h].pages]; { ENABLE FS.Error => FSFileOps.DeleteFile[a.h ! FS.Error => CONTINUE]; [bytes, createdTime] _ FSFileOps.Copy[a.attachedTo.h, a.h]; FSFileOps.SetProps[a.h, bytes, keep, createdTime, pn.nameBody, pn.version]; }; FSDir.UpdateLocalEntry[pn.volDesc, globalNB, a.version, keep, toFP, replace]; }; FSLock.ReleaseRecord[a.attachedTo]; a.attachedTo _ NIL; }; -- of collapse attachment IF NOT FSLock.LockFile[a, write] THEN ERROR; FSLock.ReleaseRecord[a]; FSFileOps.SetBytesAndCreated[a.h, -1, BasicTime.Now[]]; -- update create-time }; } ELSE { gotLock: BOOL; IF lock = write THEN GlobalWriteLock[]; IF pn.volDesc = NIL THEN FSReport.NoCache[pn.fullName]; a _ GetGName[pn, wantedCreatedTime, remoteCheck]; gotLock _ FSLock.LockFile[a, read]; FSLock.ReleaseRecord[a]; IF NOT gotLock THEN Conflict[pn]; }; file _ FSLock.NewOpenFile[a]; }; Create: PUBLIC PROC [name: ROPE, setPages: BOOL, pages: INT, setKeep: BOOL, keep: CARDINAL, wDir: ROPE] RETURNS [file: OpenFile] = { IF IsGuestProcess[] THEN RETURN [GuestProcs.Create[name, setPages, pages, setKeep, keep, wDir]]; RETURN[CreateBody[name, setPages, pages, setKeep, keep, wDir, FALSE]]; }; CreateVMBacking: PUBLIC PROC [name: ROPE, setPages: BOOL, pages: INT, setKeep: BOOL, keep: CARDINAL, wDir: ROPE] RETURNS [file: OpenFile] = { RETURN[CreateBody[name, setPages, pages, setKeep, keep, wDir, TRUE]]; }; CreateBody: PUBLIC PROC [name: ROPE, setPages: BOOL, pages: INT, setKeep: BOOL, keep: CARDINAL, wDir: ROPE, VMBackingFile: BOOL] RETURNS [file: OpenFile] = { a: ActiveFile; newKeep: CARDINAL; fp: File.FP; pn: ParsedFName; vI: FSName.VersionInfo; [pn, vI] _ FSName.ParseClientName[name, wDir, TRUE]; IF NOT FSName.IsLocal[pn.nameBody] THEN FSBackdoor.ProduceError[globalCreation, "FS.Create illegal for a GName"]; IF pn.volDesc = NIL THEN FSReport.UnknownVolumeLName[pn.fullName]; IF vI # missing THEN FSReport.VersionSpecified[pn.fullName]; [a, newKeep, fp] _ InnerCreate[pn.volDesc, pn.nameBody, setPages, pages, setKeep, keep, VMBackingFile]; pn.version _ a.version; -- the real version number FSFileOps.SetProps[a.h, 0, newKeep, BasicTime.Now[], pn.nameBody, pn.version ! FS.Error => FSLock.ReleaseRecord[a] ]; FSDir.UpdateLocalEntry[pn.volDesc, pn.nameBody, pn.version, newKeep, fp, insert]; IF NOT FSLock.LockFile[a, write] THEN ERROR; FSLock.ReleaseRecord[a]; file _ FSLock.NewOpenFile[a]; }; OpenOrCreate: PUBLIC PROC [name: ROPE, keep: CARDINAL, pages: INT, wDir: ROPE] RETURNS [file: OpenFile] = { needToCreate: BOOL _ FALSE; file _ FS.Open[name: name, lock: write, wDir: wDir ! FS.Error => IF error.code = $unknownFile THEN {needToCreate _ TRUE; CONTINUE} ]; IF needToCreate THEN file _ FS.Create [name: name, pages: pages, keep: keep, wDir: wDir]; }; Copy: PUBLIC PROC [from, to: ROPE, setKeep: BOOL, keep: CARDINAL, wantedCreatedTime: GMT, remoteCheck: BOOL, attach: BOOL, wDir: ROPE] RETURNS [toFName: ROPE] = { fpn, tpn: ParsedFName; tvI: FSName.VersionInfo; IF IsGuestProcess[] THEN toFName _ GuestProcs.Copy[from, to, setKeep, keep,wantedCreatedTime, remoteCheck, attach, wDir]; [tpn, tvI] _ FSName.ParseClientName[to, wDir, TRUE]; IF tvI # missing THEN FSReport.VersionSpecified[tpn.fullName]; [fpn, ] _ FSName.ParseClientName[from, wDir, TRUE]; toFName _ InnerCopy[fpn, tpn, setKeep, keep, wantedCreatedTime, remoteCheck, attach].toFName; IF FSName.IsLocal[toFName] THEN FSReport.ReportCreation[copyTo, toFName]; }; Delete: PUBLIC PROC [name: ROPE, wantedCreatedTime: GMT, wDir: ROPE] = { pn: ParsedFName; IF IsGuestProcess[] THEN { GuestProcs.Delete[name, wantedCreatedTime, wDir]; } ELSE { [pn, ] _ FSName.ParseClientName[name, wDir, FALSE]; InnerDelete[pn, wantedCreatedTime]; }; }; Rename: PUBLIC PROC [from, to: ROPE, setKeep: BOOL, keep: CARDINAL, wantedCreatedTime: GMT, wDir: ROPE] = { fpn, tpn: ParsedFName; tvI: FSName.VersionInfo; toFName: ROPE; localFrom, localTo: BOOL; fromType: FSBackdoor.EntryType; fromFP: File.FP; fromA: ActiveFile _ NIL; IF IsGuestProcess[] THEN { GuestProcs.Rename[from, to, setKeep, keep, wantedCreatedTime, wDir]; } ELSE { [tpn, tvI] _ FSName.ParseClientName[to, wDir, TRUE]; IF tvI # missing THEN FSReport.VersionSpecified[tpn.fullName]; [fpn, ] _ FSName.ParseClientName[from, wDir, TRUE]; localFrom _ FSName.IsLocal[fpn.nameBody]; localTo _ FSName.IsLocal[tpn.nameBody]; SELECT TRUE FROM localFrom AND localTo => { IF fpn.volDesc = NIL THEN FSReport.UnknownVolumeLName[fpn.fullName]; IF tpn.volDesc = NIL THEN FSReport.UnknownVolumeLName[tpn.fullName]; IF fpn.volDesc = tpn.volDesc THEN { attachedTo: Text; created: GMT; toA: ActiveFile; [fromType, fpn.version, , fromFP, created, attachedTo, fromA] _ FSDir.AcquireOldFName[fpn.volDesc, fpn.nameBody, fpn.version, wantedCreatedTime]; SELECT fromType FROM notFound => FSReport.UnknownFile[fpn.fullName, wantedCreatedTime]; local, attached => { ENABLE FS.Error => FSLock.ReleaseRecord[fromA]; IF fromA.fileLock # none THEN Conflict[fpn]; [toA, keep, ] _ NewLName[tpn.volDesc, tpn.nameBody, FALSE, setKeep, keep]; tpn.version _ toA.version; IF fromType = local THEN { ENABLE FS.Error => FSLock.ReleaseRecord[toA]; RenameToLocalName[fpn.volDesc, fpn.nameBody, fpn.version, tpn.nameBody, tpn.version, FSFileOps.OpenFile[fpn.volDesc.vol, fromFP], keep]; } ELSE { FSDir.DeleteEntry[fpn.volDesc, fpn.nameBody, fpn.version]; FSDir.UpdateAttachedEntry[tpn.volDesc, tpn.nameBody, tpn.version, keep, created, attachedTo, insert]; }; toFName _ FSBackdoor.MakeFName[toA.nameBody, toA.version]; FSLock.ReleaseRecord[fromA]; FSLock.ReleaseRecord[toA]; FSReport.ReportCreation[renameTo, toFName]; RETURN; }; ENDCASE => ERROR; }; }; NOT localFrom AND NOT localTo => { startedRenaming: BOOL _ FALSE; fromServer, toServer, fromFile, toFile: ROPE; [fromServer, fromFile] _ FSName.ServerAndFileRopes[fpn.fullName]; [toServer, toFile] _ FSName.ServerAndFileRopes[tpn.fullName]; IF Rope.Equal[fromServer, toServer, FALSE] THEN { ENABLE FS.Error, ABORTED => { FSLock.ReleaseRecord[fromA]; IF startedRenaming THEN { FSReport.ReportRemote[endRenaming, fpn.fullName]; startedRenaming _ FALSE; }; }; ConfirmRename: PROC [v: Version] RETURNS [BOOL] = { fpn.version _ v; IF fpn.volDesc # NIL THEN { [fromFP, fromA] _ FSDir.AcquireOldOrNewGName[fpn.volDesc, fpn.nameBody, fpn.version]; IF fromA.fileLock # none THEN Conflict[fpn]; }; fpn.fullName _ FSBackdoor.MakeFName[fpn.nameBody, fpn.version]; FSReport.ReportRemote[startRenaming, fpn.fullName]; startedRenaming _ TRUE; RETURN [TRUE]; }; fromFP _ File.nullFP; FSRemoteFile.Rename[fromServer, fromFile, wantedCreatedTime, toFile, ConfirmRename]; IF fromFP # File.nullFP THEN { FSDir.DeleteEntry[fpn.volDesc, fpn.nameBody, fpn.version]; FSFileOps.DeleteFile[FSFileOps.OpenFile[fpn.volDesc.vol, fromFP]]; }; FSReport.ReportRemote[endRenaming, fpn.fullName]; FSLock.ReleaseRecord[fromA]; RETURN; }; }; ENDCASE; [wantedCreatedTime, toFName] _ InnerCopy[fpn, tpn, setKeep, keep, wantedCreatedTime, FALSE, FALSE]; InnerDelete[fpn, wantedCreatedTime]; IF FSName.IsLocal[toFName] THEN FSReport.ReportCreation[renameTo, toFName]; }; }; Conflict: PROC [pn: ParsedFName] = { FSReport.LockConflict[pn.volDesc.prefix, pn.nameBody, pn.version] }; GlobalWriteLock: PROC = { FSBackdoor.ProduceError[globalWriteLock, "Can not open a GName with a write lock"]; }; GetLName: PROC [pn: ParsedFName, wantedCreatedTime: GMT, remoteCheck: BOOL] RETURNS [a: ActiveFile, keep: CARDINAL] = { attachedTo: Text; attachmentCreatedTime: GMT; type: FSBackdoor.EntryType; fp: File.FP; [type, pn.version, keep, fp, attachmentCreatedTime, attachedTo, a] _ FSDir.AcquireOldFName[pn.volDesc, pn.nameBody, pn.version, wantedCreatedTime]; SELECT type FROM notFound => FSReport.UnknownFile[pn.fullName, wantedCreatedTime]; local => IF a.h = NIL THEN a.h _ FSFileOps.OpenFile[pn.volDesc.vol, fp ! FS.Error => FSLock.ReleaseRecord[a] ]; attached => IF a.attachedTo = NIL THEN { ENABLE FS.Error, ABORTED => FSLock.ReleaseRecord[a]; gpn: ParsedFName = FSName.ParseName[NIL, attachedTo]; IF gpn.volDesc = NIL THEN FSReport.UnknownVolumeLName[gpn.fullName]; a.attachedTo _ GetGName[gpn, attachmentCreatedTime, remoteCheck]; a.h _ a.attachedTo.h; } ELSE FSLock.LockAttachedToRecord[a]; ENDCASE => ERROR; }; GetGName: PROC [pn: ParsedFName, wantedCreatedTime: GMT, remoteCheck: BOOL] RETURNS [a: ActiveFile] = { usedTime: GMT; server, file: ROPE; type: FSBackdoor.EntryType; fp: File.FP; [server, file] _ FSName.ServerAndFileRopes[pn.fullName]; IF remoteCheck AND pn.version NOT IN (FSBackdoor.lowestVersion .. FSBackdoor.highestVersion) AND wantedCreatedTime = BasicTime.nullGMT THEN [version: pn.version, created: wantedCreatedTime] _ FSRemoteFile.Info[server, file, BasicTime.nullGMT]; [type, pn.version, , fp, usedTime, , a] _ FSDir.AcquireOldFName[pn.volDesc, pn.nameBody, pn.version, wantedCreatedTime]; SELECT type FROM notFound => { RetrieveProc: PROC [fullGName: ROPE, bytes: INT, created: GMT] RETURNS [STREAM] = { pn _ FSName.ParseName[NIL, fullGName]; [fp, a] _ FSDir.AcquireOldOrNewGName[pn.volDesc, pn.nameBody, pn.version]; wantedCreatedTime _ created; IF fp = File.nullFP THEN { [fp, a.h] _ FSFileOps.CreateFile[pn.volDesc.vol, FS.PagesForBytes[bytes]]; fileCreated _ TRUE; fileStream _ FSFileOps.CreateFileStream[a.h, newAppendOnly]; FSReport.ReportRemote[startRetrieving, pn.fullName]; } ELSE { IF a.h = NIL THEN { a.h _ FSFileOps.OpenFile[pn.volDesc.vol, fp]; IF wantedCreatedTime # FSFileOps.GetProps[a.h].created THEN { fileToDelete _ a.h; -- remember this file for later deletion [fp, a.h] _ FSFileOps.CreateFile[pn.volDesc.vol, FS.PagesForBytes[bytes]]; fileCreated _ TRUE; fileStream _ FSFileOps.CreateFileStream[a.h, newAppendOnly]; FSReport.ReportRemote[startRetrieving, pn.fullName]; }; } ELSE { IF wantedCreatedTime # FSFileOps.GetProps[a.h].created THEN Conflict[pn]; }; }; RETURN[fileStream]; }; fileStream: STREAM _ NIL; fileToDelete: File.Handle _ NIL; fileCreated: BOOL _ FALSE; a _ NIL; FSRemoteFile.Retrieve[server, file, wantedCreatedTime, RetrieveProc ! FS.Error, ABORTED => { IF fileStream # NIL THEN { fileStream.Close[ ! FS.Error => CONTINUE ]; fileStream _ NIL; -- ABORTED can follow an FS.Error FSReport.ReportRemote[endRetrieving, pn.fullName]; }; IF fileCreated THEN { FSFileOps.DeleteFile[a.h ! FS.Error => CONTINUE ]; fileCreated _ FALSE; -- ABORTED can follow an FS.Error }; FSLock.ReleaseRecord[a]; } ]; IF fileStream # NIL THEN { ENABLE FS.Error => FSLock.ReleaseRecord[a]; byteCount: INT = fileStream.GetIndex[]; neededPages: INT = FS.PagesForBytes[byteCount]; FSReport.ReportRemote[endRetrieving, pn.fullName]; fileStream.Close[]; IF neededPages # FSFileOps.GetFileInfo[a.h].pages THEN FSFileOps.SetFilePages[a.h, neededPages]; FSFileOps.SetProps[a.h, byteCount, 0, wantedCreatedTime, pn.nameBody, pn.version]; usedTime _ BasicTime.Now[]; FSDir.UpdateCachedEntry[pn.volDesc, pn.nameBody, pn.version, usedTime, fp, IF fileToDelete = NIL THEN insert ELSE replace ]; FSFileOps.RecordUsage[fp, usedTime]; IF fileToDelete # NIL THEN FSFileOps.DeleteFile[fileToDelete ! FS.Error => CONTINUE ]; }; }; cached => { IF a.h = NIL THEN a.h _ FSFileOps.OpenFile[pn.volDesc.vol, fp ! FS.Error => FSLock.ReleaseRecord[a] ]; UpdateUsedTime[pn, fp, usedTime]; }; ENDCASE => ERROR; }; RenameToLocalName: PROC [vDesc: FSFileOps.VolumeDesc, fNB: Text, fV: Version, tNB: Text, tV: Version, h: File.Handle, k: CARDINAL] = { BTree.SetUpdateInProgress[vDesc.tree, TRUE]; FSDir.DeleteEntry[vDesc, fNB, fV]; { ENABLE FS.Error => BTree.SetUpdateInProgress[vDesc.tree, FALSE]; FSFileOps.SetProps[ h, -1, k, BasicTime.nullGMT, tNB, tV]; FSDir.UpdateLocalEntry[vDesc, tNB, tV, k, FSFileOps.GetFileInfo[h].fp, insertOrReplace]; }; BTree.SetUpdateInProgress[vDesc.tree, FALSE]; }; usedTimeGrain: INT = 600; --in seconds UpdateUsedTime: PROC [gpn: ParsedFName, fp: File.FP, usedTime: GMT] = { IF BasicTime.Period[from: usedTime, to: BasicTime.Now[]] > usedTimeGrain THEN { usedTime _ BasicTime.Now[]; FSDir.UpdateCachedEntry[gpn.volDesc, gpn.nameBody, gpn.version, usedTime, fp, replace]; FSFileOps.RecordUsage[fp, usedTime]; }; }; InnerCreate: PROC [vDesc: FSFileOps.VolumeDesc, body: Text, setPages: BOOL, pages: INT, setKeep: BOOL, keep: CARDINAL, VMBackingFile: BOOL _ FALSE] RETURNS [a: ActiveFile, newKeep: CARDINAL, fp: File.FP] = { [a, newKeep, fp] _ NewLName[vDesc, body, TRUE, setKeep, keep]; { ENABLE FS.Error => FSLock.ReleaseRecord[a]; IF fp = File.nullFP THEN { [fp, a.h] _ FSFileOps.CreateFile[vDesc.vol, pages, VMBackingFile]; setPages _ FALSE; } ELSE a.h _ FSFileOps.OpenFile[vDesc.vol, fp]; IF setPages THEN FSFileOps.SetFilePages[a.h, pages]; }; }; InnerCopy: PROC [fpn, tpn: ParsedFName, setKeep: BOOL, keep: CARDINAL, wantedCreatedTime: GMT, remoteCheck: BOOL, attach: BOOL] RETURNS [createdTime: GMT, toFName: ROPE] = { localFrom: BOOL = FSName.IsLocal[fpn.nameBody]; localTo: BOOL = FSName.IsLocal[tpn.nameBody]; IF (fpn.volDesc = NIL AND localFrom) THEN FSReport.UnknownVolumeLName[fpn.fullName]; IF (tpn.volDesc = NIL AND localTo) THEN FSReport.UnknownVolumeLName[tpn.fullName]; createdTime _ wantedCreatedTime; SELECT TRUE FROM NOT localFrom AND localTo => { IF attach THEN { toA: ActiveFile; IF remoteCheck OR createdTime = BasicTime.nullGMT THEN { server, file: ROPE; [server, file] _ FSName.ServerAndFileRopes[fpn.fullName]; [version: fpn.version, created: createdTime] _ FSRemoteFile.Info[server, file, createdTime]; fpn.fullName _ Rope.Concat[fpn.nameBody, FSName.VersionPartFromVersion[fpn.version]]; }; [toA, keep, ] _ NewLName[tpn.volDesc, tpn.nameBody, FALSE, setKeep, keep]; tpn.version _ toA.version; FSDir.UpdateAttachedEntry[tpn.volDesc, tpn.nameBody, tpn.version, keep, createdTime, Rope.Flatten[fpn.fullName], insert]; toFName _ FSBackdoor.MakeFName[toA.nameBody, toA.version]; FSLock.ReleaseRecord[toA]; } ELSE { toA: ActiveFile; [createdTime, toA] _ CopyGlobalToLocal[fpn, createdTime, remoteCheck, tpn, setKeep, keep]; toFName _ FSBackdoor.MakeFName[toA.nameBody, toA.version]; FSLock.ReleaseRecord[toA]; }; }; NOT localTo => { IF localFrom THEN { fromA, globalA: ActiveFile; fromKeep: CARDINAL; [fromA, fromKeep] _ GetLName[fpn, createdTime, FALSE]; fpn.version _ fromA.version; { ENABLE FS.Error, ABORTED => { FSLock.ReleaseRecord[fromA.attachedTo]; FSLock.ReleaseRecord[fromA]; }; IF fromA.fileLock = write OR (attach AND fromA.fileLock=read AND tpn.volDesc#fpn.volDesc) THEN Conflict[fpn]; [tpn.version, createdTime, globalA] _ CopyFileToGlobal[fromA.h, tpn, fromA]; toFName _ FSBackdoor.MakeFName[tpn.nameBody, tpn.version]; IF attach THEN { FSDir.UpdateAttachedEntry[fpn.volDesc, fpn.nameBody, fpn.version, fromKeep, createdTime, Rope.Flatten[toFName], replace]; IF fromA.attachedTo = NIL THEN { ENABLE FS.Error => FSLock.ReleaseRecord[globalA]; SELECT TRUE FROM (tpn.volDesc # fpn.volDesc) => { IF fromA.fileLock#none THEN ERROR; -- checked above FSFileOps.DeleteFile[fromA.h]; }; (fromA.fileLock = none) => Encache[fromA, tpn.nameBody, tpn.version, tpn.volDesc]; (fromA.fileLock = read) => { Encache[fromA, tpn.nameBody, tpn.version, tpn.volDesc]; THROUGH [1.. fromA.fileLockCount] DO IF NOT FSLock.LockFile[globalA, read] THEN ERROR; ENDLOOP; globalA.h _ fromA.h; fromA.attachedTo _ globalA; globalA _ NIL; }; ENDCASE => ERROR; }; } ELSE { }; }; FSLock.ReleaseRecord[globalA]; FSLock.ReleaseRecord[fromA.attachedTo]; FSLock.ReleaseRecord[fromA]; } ELSE { fromA, globalA: ActiveFile; IF fpn.volDesc = NIL THEN FSReport.NoCache[fpn.fullName]; fromA _ GetGName[fpn, createdTime, remoteCheck]; fpn.version _ fromA.version; [tpn.version, createdTime, globalA] _ CopyFileToGlobal[fromA.h, tpn ! FS.Error => FSLock.ReleaseRecord[fromA] ]; toFName _ FSBackdoor.MakeFName[tpn.nameBody, tpn.version]; FSLock.ReleaseRecord[globalA]; FSLock.ReleaseRecord[fromA]; }; }; ENDCASE => { newKeep: CARDINAL; toFP: File.FP; fromA, toA: ActiveFile _ NIL; fromPages: INT; [fromA, ] _ GetLName[fpn, createdTime, remoteCheck]; fpn.version _ fromA.version; { ENABLE FS.Error, ABORTED => { FSLock.ReleaseRecord[fromA.attachedTo]; FSLock.ReleaseRecord[fromA]; FSLock.ReleaseRecord[toA]; }; fromPages _ FSFileOps.GetFileInfo[fromA.h].pages; [toA, newKeep, toFP] _ InnerCreate[tpn.volDesc, tpn.nameBody, TRUE, fromPages, setKeep, keep]; tpn.version _ toA.version; -- the real version number { ENABLE FS.Error => { FSFileOps.DeleteFile[toA.h ! FS.Error => CONTINUE] }; bytes: INT; [bytes, createdTime] _ FSFileOps.Copy[fromA.h, toA.h]; FSFileOps.SetProps[toA.h, bytes, newKeep, createdTime, tpn.nameBody, tpn.version]; }; }; FSDir.UpdateLocalEntry[tpn.volDesc, tpn.nameBody, tpn.version, newKeep, toFP, insert]; FSLock.ReleaseRecord[fromA.attachedTo]; FSLock.ReleaseRecord[fromA]; toFName _ FSBackdoor.MakeFName[toA.nameBody, toA.version]; FSLock.ReleaseRecord[toA]; }; }; InnerDelete: PROC [pn: ParsedFName, wantedCreatedTime: GMT] = { type: FSBackdoor.EntryType; fp: File.FP; a: ActiveFile; IF FSName.IsLocal[pn.nameBody] THEN { IF pn.volDesc = NIL THEN FSReport.UnknownVolumeLName[pn.fullName]; [type, pn.version, , fp, , , a] _ FSDir.AcquireOldFName[pn.volDesc, pn.nameBody, pn.version, wantedCreatedTime]; IF type = notFound THEN FSReport.UnknownFile[pn.fullName, wantedCreatedTime]; IF a.fileLock # none THEN { FSLock.ReleaseRecord[a]; Conflict[pn] }; } ELSE { ConfirmDeletion: PROC [v: Version] RETURNS [BOOL] = { pn.version _ v; IF pn.volDesc # NIL THEN { [type, , , fp, , , a] _ FSDir.AcquireOldFName[pn.volDesc, pn.nameBody, pn.version, BasicTime.nullGMT]; IF type # notFound AND a.fileLock # none THEN Conflict[pn]; }; pn.fullName _ FSBackdoor.MakeFName[pn.nameBody, pn.version]; FSReport.ReportRemote[startDeleting, pn.fullName]; startedDeletion _ TRUE; RETURN [TRUE]; }; startedDeletion: BOOL _ FALSE; server, file: ROPE; [server, file] _ FSName.ServerAndFileRopes[pn.fullName]; type _ notFound; a _ NIL; FSRemoteFile.Delete[server, file, wantedCreatedTime, ConfirmDeletion ! FS.Error, ABORTED => { FSLock.ReleaseRecord[a]; IF startedDeletion THEN { FSReport.ReportRemote[endDeleting, pn.fullName]; startedDeletion _ FALSE; }; } ]; }; FSReport.ReportRemote[endDeleting, pn.fullName]; IF type # notFound THEN { FSDir.DeleteEntry[pn.volDesc, pn.nameBody, pn.version]; FSLock.ReleaseRecord[a]; -- not in directory any more, so ok to release lock IF type # attached THEN FSFileOps.DeleteFile[ FSFileOps.OpenFile[pn.volDesc.vol, fp] ]; }; }; NewLName: PROC [vDesc: FSFileOps.VolumeDesc, body: Text, wantFP: BOOL, setKeep: BOOL, keep: CARDINAL] RETURNS [a: ActiveFile, newKeep: CARDINAL, fp: File.FP] = { IF keep = 0 THEN FSBackdoor.ProduceError[zeroKeep, "Zero is an illegal keep"]; [a, newKeep, fp] _ FSDir.AcquireNextLName[vDesc, body, IF setKeep THEN keep ELSE 0]; IF a.version = 1 THEN newKeep _ keep ELSE { IF NOT wantFP AND fp # File.nullFP THEN { ENABLE FS.Error => FSLock.ReleaseRecord[a]; FSFileOps.DeleteFile[FSFileOps.OpenFile[vDesc.vol, fp]]; fp _ File.nullFP; }; }; }; CopyGlobalToLocal: PROC [gpn: ParsedFName, wantedCreatedTime: GMT, remoteCheck: BOOL, lpn: ParsedFName, setKeep: BOOL, keep: CARDINAL] RETURNS [createdTime: GMT, toA: ActiveFile] = { localFP: File.FP; localStream: STREAM _ NIL; createdTime _ wantedCreatedTime; toA _ NIL; { ENABLE FS.Error, ABORTED => { IF localStream # NIL THEN { localStream.Close[ ! FS.Error => CONTINUE ]; localStream _ NIL; -- ABORTED can follow an FS.Error FSReport.ReportRemote[endRetrieving, gpn.fullName]; }; IF toA # NIL THEN { IF toA.h # NIL THEN FSFileOps.DeleteFile[ toA.h ! FS.Error => CONTINUE ]; FSLock.ReleaseRecord[toA]; }; }; MakeLocalFile: PROC = { localPages _ FS.PagesForBytes[localBytes]; [toA, keep, localFP] _ NewLName[lpn.volDesc, lpn.nameBody, TRUE, setKeep, keep]; lpn.version _ toA.version; IF localFP = File.nullFP THEN [localFP, toA.h] _ FSFileOps.CreateFile[lpn.volDesc.vol, localPages] ELSE { toA.h _ FSFileOps.OpenFile[lpn.volDesc.vol, localFP]; FSFileOps.SetFilePages[toA.h, localPages]; }; }; type: FSBackdoor.EntryType; globalFP: File.FP; globalA: ActiveFile; usedTime: GMT; localBytes, localPages: INT; server, file: ROPE; [server, file] _ FSName.ServerAndFileRopes[gpn.fullName]; IF remoteCheck AND gpn.version NOT IN (FSBackdoor.lowestVersion .. FSBackdoor.highestVersion) AND createdTime = BasicTime.nullGMT THEN [gpn.version, localBytes, createdTime] _ FSRemoteFile.Info[server, file, BasicTime.nullGMT]; IF gpn.volDesc = NIL THEN type _ notFound ELSE [type, gpn.version, , globalFP, usedTime, , globalA] _ FSDir.AcquireOldFName[gpn.volDesc, gpn.nameBody, gpn.version, createdTime]; SELECT type FROM notFound => { RetrieveProc: PROC [fullGName: ROPE, bytes: INT, created: GMT] RETURNS [STREAM] = { gpn.fullName _ fullGName; createdTime _ created; localBytes _ bytes; MakeLocalFile[]; localStream _ FSFileOps.CreateFileStream[toA.h, newAppendOnly]; FSReport.ReportRemote[startRetrieving, gpn.fullName]; RETURN[localStream]; }; FSRemoteFile.Retrieve[server, file, createdTime, RetrieveProc]; localBytes _ localStream.GetIndex[]; localPages _ FS.PagesForBytes[localBytes]; localStream.Close[]; localStream _ NIL; FSReport.ReportRemote[endRetrieving, gpn.fullName]; IF localPages # FSFileOps.GetFileInfo[toA.h].pages THEN FSFileOps.SetFilePages[toA.h, localPages]; }; cached => { ENABLE FS.Error => FSLock.ReleaseRecord[globalA]; IF globalA.h = NIL THEN globalA.h _ FSFileOps.OpenFile[gpn.volDesc.vol, globalFP]; localBytes _ FS.BytesForPages[FSFileOps.GetFileInfo[globalA.h].pages]; MakeLocalFile[]; [localBytes, createdTime] _ FSFileOps.Copy[globalA.h, toA.h]; UpdateUsedTime[gpn, globalFP, usedTime]; FSLock.ReleaseRecord[globalA]; } ENDCASE => ERROR; FSFileOps.SetProps[toA.h, localBytes, keep, createdTime, lpn.nameBody, lpn.version]; }; FSDir.UpdateLocalEntry[lpn.volDesc, lpn.nameBody, lpn.version, keep, localFP, insertOrReplace]; }; CopyFileToGlobal: PROC [h: File.Handle, global: ParsedFName, fromActiveFile: ActiveFile _ NIL] RETURNS [globalVersion: Version, created: GMT, globalA: ActiveFile] = { GetVersion: PROC [v: Version] RETURNS [BOOL] = { globalVersion _ global.version _ v; global.fullName _ FSBackdoor.MakeFName[global.nameBody, global.version]; IF global.volDesc # NIL THEN { nameBody: ROPE _ global.nameBody; IF NOT Rope.Match["[*", nameBody] THEN nameBody _ Rope.Concat[global.volDesc.prefix, nameBody]; IF fromActiveFile # NIL AND fromActiveFile.attachedTo # NIL AND Rope.Equal[fromActiveFile.attachedTo.nameBody, nameBody] AND fromActiveFile.attachedTo.version = global.version THEN { dummy _ dummy + 1; -- a place to hang performance hooks } ELSE { [fpToDelete, globalA] _ FSDir.AcquireOldOrNewGName[global.volDesc, global.nameBody, global.version]; IF globalA.fileLock # none THEN Conflict[global]; -- server's !N version already cached and open }; }; FSReport.ReportRemote[startStoring, global.fullName]; RETURN [TRUE]; }; dummy: INT _ 0 ; -- no real use fpToDelete: File.FP _ File.nullFP; server, file: ROPE; fromStream: STREAM _ FSFileOps.CreateFileStream[h, oldReadOnly]; created _ FSFileOps.GetProps[h].created; [server, file] _ FSName.ServerAndFileRopes[global.fullName]; globalA _ NIL; FSRemoteFile.Store[server, file, fromStream, created, GetVersion ! FS.Error, ABORTED => { IF fromStream # NIL THEN { fromStream.Close[]; fromStream _ NIL; -- ABORTED can follow an FS.Error FSReport.ReportRemote[endStoring, global.fullName]; }; FSLock.ReleaseRecord[globalA]; } ]; fromStream.Close[]; FSReport.ReportRemote[endStoring, global.fullName]; IF fpToDelete # File.nullFP THEN { FSDir.DeleteEntry[global.volDesc, global.nameBody, global.version]; FSFileOps.DeleteFile[ FSFileOps.OpenFile[global.volDesc.vol, fpToDelete] ! FS.Error, ABORTED => FSLock.ReleaseRecord[globalA] ]; }; }; Encache: PROC [localA: ActiveFile, nB: Text, ver: Version, vDesc: FSFileOps.VolumeDesc] = { used: GMT = BasicTime.Now[]; fp: File.FP = FSFileOps.GetFileInfo[localA.h].fp; FSFileOps.SetProps[localA.h, -1, 0, BasicTime.nullGMT, nB, ver]; FSDir.UpdateCachedEntry[vDesc, nB, ver, used, fp, insert]; FSFileOps.RecordUsage[fp, used]; }; END. ²FSMainImpl1.mesa Copyright c 1984, 1985 by Xerox Corporation. All rights reserved. Exports FS.Open, FS.Create, FS.Copy, FS.Delete, and FS.Rename See FSMainImpl2 for FS.EnumerateForInfo, FS.EnumerateForNames, FS.FileInfo and FS.SetKeep Russ Atkinson (RRA) August 27, 1985 1:21:34 pm PDT Bob Hagmann, April 24, 1986 8:30:18 am PST Useful types declared externally Local copy of IsGuestProcess procedure, and dummy proc Exported to FSBackdoor Exported to FS open LName for write won't be able to get a write lock collapse attachment can do it by renaming is open for read or on different volume, so must copy open GName from local file to local file unattached local to local on same volume attached local to local both GNames on the same server have a cache from file is cached, so must be deleted Internal procedures need to go to remote server What if STP times out while we're waiting? not in the cache, so must create and retrieve it something is in the cache not already open, so have name lock wrong created time in the cache already open, so have read lock and can't delete new file was created and filled in already in cache attach GName to LName copy GName to LName LName to GName currently open for write open for read and system volume is different or missing attach LName to GName LName was not attached local and global volumes are different local file not open right now local file open for read right now opened LName already attached so just leave it GName to GName LName to LName local deletion remote deletion have a cache have a file to delete no existing versions, so use keep supplied newKeep has been set have an fp we need to delete need to go to remote server already in the cache have a cache version assigned by server is same as the one we are copying. This can happen due to crash or inadvertent deletion of server files. version assigned by server was already in the cache, so we must flush it Bob Hagmann January 29, 1985 3:37:37 pm PST Cedar 6.0 interface changes Bob Hagmann March 15, 1985 4:10:26 pm PST changes to: DIRECTORY, IsGuestProcess, FalseProc, GuestProcs, RegisterGuestProcs, Open, Create, Copy, Delete, Rename Russ Atkinson (RRA) August 27, 1985 1:21:08 pm PDT changes to: FSMainImpl1 (fixed bad interaction between -r switch on pseudo-servers and remoteCheck option) Bob Hagmann November 5, 1985 8:28:29 am PST fix lockup when a global copy of an attached file that is cached results in the same global name and version number as the cached copy (due to server crash or user error) changes to: InnerCopy, CopyFileToGlobal, GetVersion (local of CopyFileToGlobal) Bob Hagmann April 24, 1986 8:30:18 am PST fix to check attached version changes to: GetVersion (local of CopyFileToGlobal) Κη– "cedar" style˜code2šœ™Jšœ Οmœ7™BJšœ=™=JšœY™YIcode™2J™*—code1šΟk ˜ Mšœ žœžœ˜,Mšœžœ˜"Mšœžœžœ˜ MšžœžœE˜MMšœ žœ]˜mMšœžœ‚˜Mšœ žœ˜˜§MšœžœJ˜VMšœžœm˜yMšœ žœ)˜;Mšœ žœj˜xMšžœžœžœ˜#Mšœžœžœ˜7—headšœ žœž˜MšžœžœNž˜lMšžœžœ ˜Mšœž˜M˜—šœ ™ M˜Mšœ žœ˜%Mšžœžœ žœ˜Mšœžœžœ˜Mšœ žœžœ ˜Mšœ žœ˜'Mšžœžœžœ˜Mšœžœ ˜Mšžœžœžœžœ˜Mšœ žœ˜#—šœ6™6KšΟbœžœžœ žœ˜Kšœ-žœ˜3Kšœ]˜]Kšžœžœ*˜IKšœ˜—š  œžœžœžœžœžœ˜HKšœ˜šžœžœ˜Kšœ1˜1K˜—šœžœ˜Kšœ,žœ˜3Kšœ#˜#K˜—Kšœ˜—š œžœžœ žœ žœžœžœžœ˜kKšœ˜K˜Kšœ žœ˜Kšœžœ˜Kšœ˜Kšœ žœ˜Kšœžœ˜šžœ˜šžœ˜KšœD˜DK˜—šžœ˜Kšœ.žœ˜4Kšžœžœ)˜>Kšœ-žœ˜3Kšœ)˜)Kšœ'˜'šžœžœž˜šœ žœ ˜Kšžœž˜Kšžœ+˜/Kšžœž˜Kšžœ+˜/šžœžœ˜#Kšœ˜Kšœ žœ˜ Kšœ˜Kšœ‘˜‘šžœ ž˜šœ ˜ Kšœ6˜6—šœ˜Kšœ™Kšžœžœ&˜/Kšžœžœ˜,Kšœ4žœ˜JKšœ˜šžœ˜šžœ˜Kšœ(™(Kšžœžœ$˜-Kšœˆ˜ˆKšœ˜—šžœ˜Kšœ™Kšœ:˜:Kšœe˜eKšœ˜——Kšœ:˜:Kšœ˜K˜Kšœ+˜+Kšžœ˜Kšœ˜—Kšžœžœ˜—Kšœ˜—Kšœ˜—šžœ žœžœ ˜"Kšœžœžœ˜Kšœ(žœ˜-KšœA˜AKšœ=˜=Kšžœ"žœ˜*šžœ˜Kšœ™šžœžœžœ˜Kšœ˜Kšžœ˜šžœ˜Kšœ1˜1Kšœžœ˜Kšœ˜—Kšœ˜—š  œžœžœžœ˜3Kšœ˜Kšžœž˜šžœ˜Kšœ ™ KšœU˜UKšžœžœ˜,Kšœ˜—Kšœ?˜?Kšœ3˜3Kšœžœ˜Kšžœžœ˜Kšœ˜—Kšœ˜KšœT˜Tšžœžœ˜Kšœ'™'Kšœ:˜:KšœB˜BKšœ˜—Kšœ1˜1Kšœ˜Kšžœ˜Kšœ˜—Kšœ˜—Kšžœ˜—KšœUžœžœ˜cKšœ$˜$Kšžœžœ,˜KKšœ˜——K˜——™š œžœ˜$KšœD˜D—š œžœ˜KšœS˜SKšœ˜—š  œžœ&žœžœžœžœ˜xKšœ˜Kšœžœ˜Kšœ˜Kšœ žœ˜ Kšœ“˜“šžœž˜šœ ˜ Kšœ5˜5—šœ˜šžœž˜šœ+˜+Kšœžœ$˜(———šœ ˜ šžœž˜šžœ˜Kšžœžœžœ˜4Kšœ$žœ˜5Kšžœžœžœ+˜DKšœA˜AKšœ˜Kšœ˜—Kšžœ ˜$——Kšžœžœ˜—Kšœ˜—š  œžœ&žœžœžœ˜hKšœ žœ˜Kšœžœ˜Kšœ˜Kšœ žœ˜ Kšœ8˜8š žœ žœ žœžœ9žœ&ž˜‹Kšœg˜g—Kšœx˜xšžœž˜šœ ˜ Kšœ™š  œž˜Kš œ žœ žœ žœžœžœ˜@Kšœžœ ˜&KšœJ˜JKš‘*™*Kšœ˜šžœ˜šžœ˜Kšœ0™0Kšœ1žœ˜JKšœžœ˜Kšœ<˜šœ˜Kšžœžœ"˜+šžœ˜šžœ˜KšœB˜BKšœ žœ˜Kšœ˜—Kšžœ)˜-—Kšžœ žœ$˜4Kšœ˜—Kšœ˜—š  œžœ"žœžœžœžœ žœžœžœ žœ˜­Kšœ žœ ˜/Kšœ žœ ˜-Kšžœžœžœ ˜$Kšžœ+˜/Kšžœžœžœ ˜"Kšžœ+˜/Kšœ ˜ šžœžœž˜šžœ žœ ˜šžœ˜ šžœ˜Kšœ™Kšœ˜šžœ žœ žœ˜8Kšœžœ˜Kšœ9˜9Kšœ\˜\KšœU˜UKšœ˜—Kšœ4žœ˜JKšœ˜Kšœy˜yKšœ:˜:K˜Kšœ˜—šžœ˜Kšœ™Kšœ˜KšœZ˜ZKšœ:˜:Kšœ˜Kšœ˜——Kšœ˜—šžœ ˜šžœ ˜ šžœ˜Kšœ™Kšœ˜Kšœ žœ˜Kšœ/žœ˜6Kšœ˜šœ˜šžœžœžœ˜Kšœ'˜'Kšœ˜Kšœ˜—šžœ˜Kš‘™Kšžœ žœžœ˜?Kš‘7™7Kšžœ˜—KšœL˜LKšœ:˜:šžœ˜ šžœ˜Kšœ™Kšœy˜yšžœžœžœ˜ Kšœ™Kšžœžœ(˜1Kšžœžœž˜šœ ˜ Kšœ&™&Kšžœžœžœ‘˜3Kšœ˜Kšœž˜—šœ˜Kšœ™Kšœ7˜7—šœžœ˜Kšœ"™"Kšœ7˜7šžœž˜$Kšžœžœ žœžœ˜1Kšžœ˜—Kšœ˜Kšœ˜Kšœ žœ˜Kšœ˜—Kšžœžœ˜Kšœ˜—Kšœ˜—šžœ˜Kšœ.™.Kšœ˜—Kšœ˜——Kšœ˜Kšœ'˜'Kšœ˜Kšœ˜—šžœ˜Kšœ™Kšœ˜Kšžœžœžœ ˜9Kšœ0˜0Kšœ˜šœC˜CKšœžœ(˜,—Kšœ:˜:Kšœ˜Kšœ˜Kšœ˜——Kšœ˜—šžœ˜ Kšœ™Kšœ žœ˜Kšœ žœ˜Kšœžœ˜Kšœ žœ˜Kšœ4˜4Kšœ˜šœ˜šžœžœžœ˜Kšœ'˜'Kšœ˜Kšœ˜Kšœ˜—Kšœ1˜1Kšœ>žœ˜^Kšœ‘˜5šœ˜Kš žœžœ žœžœ žœž˜JKšœžœ˜ Kšœ6˜6KšœR˜RKšœ˜—Kšœ˜—KšœV˜VKšœ'˜'Kšœ˜Kšœ:˜:Kšœ˜Kšœ˜——Kšœ˜—š  œžœ&žœ˜?Kšœ˜Kšœ žœ˜ Kšœ˜šžœ˜šžœ˜Kšœ™Kšžœžœžœ*˜BKšœp˜pKšžœžœ6˜MKšžœ˜Kšžœ+˜/Kšœ˜—šžœ˜Kšœ™š œžœžœžœ˜5Kšœ˜šžœžœžœ˜Kšœ ™ Kšœf˜fKšžœžœžœ˜;Kšœ˜—Kšœ<˜˜NKšœ7žœ žœžœ˜Tšžœ˜šž˜Kšœ*™*Kšœ˜—šžœ˜Kšœ™šžœžœžœžœ˜)Kšœ™Kšžœžœ"˜+Kšœ8˜8Kšœ˜Kšœžœ˜—Kšœ˜——Kšœ˜—š œžœ'žœžœžœžœžœžœ˜ΆKšœžœ˜Kšœ žœžœ˜Kšœ ˜ Kšœžœ˜ šœ˜šžœžœžœ˜šžœžœ˜Kšœžœ žœ˜,Kšœžœ‘’‘’‘˜4Kšœ3˜3Kšœ˜—šžœžœ˜Kšžœ ž˜Kšžœžœ žœ˜:Kšœ˜Kšœ˜—Kšœ˜—š  œžœ˜Kšœ žœ˜*Kšœ;žœ˜PKšœ˜šžœ˜KšžœE˜Išžœ˜Kšœ5˜5Kšœ*˜*Kšœ˜——Kšœ˜—Kšœ˜Kšœžœ˜Kšœ˜Kšœ žœ˜Kšœžœ˜Kšœžœ˜Kšœ9˜9š žœ žœ žœžœ9žœ ˜Kšžœ]˜a—šžœž˜Kšžœ˜Kšžœƒ˜‡—šžœž˜šœ ˜ Kšœ™š  œžœ žœ žœ žœžœžœ˜SKšœ˜Kšœ˜Kšœ˜Kšœ˜Kšœ?˜?Kšœ5˜5Kšžœ˜Kšœ˜—Kšœ?˜?Kšœ$˜$Kšœ žœ˜*Kšœ˜Kšœžœ˜Kšœ3˜3šžœ0ž˜7Kšœ*˜*—Kšœ˜—šœ ˜ Kšœ™Kšžœžœ(˜1šžœ ž˜Kšœ:˜:—Kšœ žœ7˜FKšœ˜Kšœ=˜=Kšœ(˜(Kšœ˜Kšœ˜—Kšžœžœ˜—KšœT˜TKšœ˜—Kšœ_˜_Kšœ˜—š  œžœDžœžœ#žœ˜¦š  œžœžœžœ˜0Kšœ#˜#KšœH˜Hšžœžœžœ˜Kšœ ™ Lšœ žœ˜!Lšžœžœžœ9˜_š žœžœžœžœ:žœ4žœ˜ΆKšœ„™„Kšœ‘$˜8K˜—šœžœ˜Kšœd˜dKšžœ˜Kšžœ‘.˜EK˜—Kšœ˜—Kšœ5˜5Kšžœžœ˜Kšœ˜—Kšœžœ‘˜ Kšœžœ˜"Kšœžœ˜Kšœ žœ.˜@Kšœ(˜(Kšœ<˜