<> <> <> <> 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, highestVersion, lowestVersion, MakeFName, ProduceError, Version], FSDir USING [AcquireNextLName, AcquireOldFName, AcquireOldOrNewGName, DeleteEntry, UpdateAttachedEntry, UpdateCachedEntry, UpdateLocalEntry], FSExtras USING [], FSFileOps USING [Copy, CreateFile, CreateFileStream, DeleteFile, GetFileInfo, GetProps, OpenFile, RecordUsage, 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, ROPE, Text]; FSMainImpl1: CEDAR PROGRAM IMPORTS BasicTime, BTree, FS, FSBackdoor, FSDir, FSFileOps, FSLock, FSName, FSReport, Rope, FSRemoteFile, IO EXPORTS FS, FSExtras = BEGIN <> Open: PUBLIC PROC [name: Rope.ROPE, lock: FS.Lock, wantedCreatedTime: BasicTime.GMT, remoteCheck: BOOLEAN, wDir: Rope.ROPE] RETURNS [file: FS.OpenFile] = BEGIN openedName: Rope.ROPE _ NIL; a: FSLock.ActiveFile; pn: FSName.ParsedFName; [pn, ] _ FSName.ParseClientName[name, wDir, TRUE]; IF FSName.IsLocal[pn.nameBody] THEN BEGIN -- 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 BEGIN -- open LName for read gotLock: BOOLEAN = FSLock.LockFile[a, read]; IF a.attachedTo # NIL THEN BEGIN -- set lock in attachment too IF gotLock THEN { IF NOT FSLock.LockFile[a.attachedTo, read] THEN ERROR }; FSLock.ReleaseRecord[a.attachedTo]; END; FSLock.ReleaseRecord[a]; IF NOT gotLock THEN Conflict[pn]; END -- of open LName for read ELSE BEGIN -- open LName for write IF a.fileLock # none THEN BEGIN -- won't be able to get a write lock FSLock.ReleaseRecord[a.attachedTo]; -- nop if a.attachedTo = NIL FSLock.ReleaseRecord[a]; Conflict[pn]; END; IF a.attachedTo # NIL THEN BEGIN -- collapse attachment ENABLE FS.Error => { FSLock.ReleaseRecord[a.attachedTo]; FSLock.ReleaseRecord[a] }; globalNB: Rope.Text = Rope.Flatten[a.attachedTo.nameBody]; IF a.attachedTo.fileLock = none AND pn.volDesc.prefix = NIL THEN -- can do it by renaming RenameToLocalName[pn.volDesc, globalNB, a.attachedTo.version, pn.nameBody, pn.version, a.h, keep] ELSE BEGIN -- is open for read or on different volume, so must copy bytes: INT; createdTime: BasicTime.GMT; toFP: File.FP; [toFP, a.h] _ FSFileOps.CreateFile[pn.volDesc.vol, FSFileOps.GetFileInfo[a.attachedTo.h].pages]; BEGIN 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]; END; FSDir.UpdateLocalEntry[pn.volDesc, globalNB, a.version, keep, toFP, replace]; END; FSLock.ReleaseRecord[a.attachedTo]; a.attachedTo _ NIL; END; -- of collapse attachment IF NOT FSLock.LockFile[a, write] THEN ERROR; FSLock.ReleaseRecord[a]; END; -- of open LName for write END -- of open LName ELSE BEGIN -- open GName gotLock: BOOLEAN; 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]; END; -- of open GName file _ FSLock.NewOpenFile[a]; END; Create: PUBLIC PROC [name: Rope.ROPE, setPages: BOOLEAN, pages: INT, setKeep: BOOLEAN, keep: CARDINAL, wDir: Rope.ROPE] RETURNS [file: FS.OpenFile] = BEGIN a: FSLock.ActiveFile; newKeep: CARDINAL; fp: File.FP; pn: FSName.ParsedFName; vI: FSName.VersionInfo; [pn, vI] _ FSName.ParseClientName[name, wDir, TRUE]; IF NOT FSName.IsLocal[pn.nameBody] THEN FSBackdoor.ProduceError[globalCreation, "Can't call FS.Create 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]; 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]; END; OpenOrCreate: PUBLIC PROC [name: Rope.ROPE, keep: CARDINAL, pages: INT, wDir: Rope.ROPE] RETURNS [file: FS.OpenFile] = BEGIN needToCreate: BOOLEAN _ 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]; END; NewCopy: PUBLIC PROC [from, to: Rope.ROPE, setKeep: BOOLEAN, keep: CARDINAL, wantedCreatedTime: BasicTime.GMT, remoteCheck: BOOLEAN, attach: BOOLEAN, wDir: Rope.ROPE] RETURNS [toFName: Rope.ROPE] = BEGIN fpn, tpn: FSName.ParsedFName; tvI: FSName.VersionInfo; [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]; END; Copy: PUBLIC PROC [from, to: Rope.ROPE, setKeep: BOOLEAN, keep: CARDINAL, wantedCreatedTime: BasicTime.GMT, remoteCheck: BOOLEAN, attach: BOOLEAN, wDir: Rope.ROPE] = BEGIN [] _ NewCopy[from, to, setKeep, keep, wantedCreatedTime, remoteCheck, attach, wDir]; END; Delete: PUBLIC PROC [name: Rope.ROPE, wantedCreatedTime: BasicTime.GMT, wDir: Rope.ROPE] = BEGIN pn: FSName.ParsedFName; [pn, ] _ FSName.ParseClientName[name, wDir, FALSE]; InnerDelete[pn, wantedCreatedTime]; END; Rename: PUBLIC PROC [from, to: Rope.ROPE, setKeep: BOOLEAN, keep: CARDINAL, wantedCreatedTime: BasicTime.GMT, wDir: Rope.ROPE] = BEGIN fpn, tpn: FSName.ParsedFName; tvI: FSName.VersionInfo; toFName: Rope.ROPE; localFrom, localTo: BOOLEAN; fromType: FSBackdoor.EntryType; fromFP: File.FP; fromA: FSLock.ActiveFile _ NIL; [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 => BEGIN IF fpn.volDesc = NIL THEN FSReport.UnknownVolumeLName[fpn.fullName]; IF tpn.volDesc = NIL THEN FSReport.UnknownVolumeLName[tpn.fullName]; IF fpn.volDesc = tpn.volDesc THEN BEGIN attachedTo: Rope.Text; created: BasicTime.GMT; toA: FSLock.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 => BEGIN -- from local file to local file 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 BEGIN -- unattached local to local on same volume ENABLE FS.Error => FSLock.ReleaseRecord[toA]; RenameToLocalName[fpn.volDesc, fpn.nameBody, fpn.version, tpn.nameBody, tpn.version, FSFileOps.OpenFile[fpn.volDesc.vol, fromFP], keep]; END ELSE BEGIN -- attached local to local FSDir.DeleteEntry[fpn.volDesc, fpn.nameBody, fpn.version]; FSDir.UpdateAttachedEntry[tpn.volDesc, tpn.nameBody, tpn.version, keep, created, attachedTo, insert]; END; toFName _ FSBackdoor.MakeFName[toA.nameBody, toA.version]; FSLock.ReleaseRecord[fromA]; FSLock.ReleaseRecord[toA]; FSReport.ReportCreation[renameTo, toFName]; RETURN; END; ENDCASE => ERROR; END; END; NOT localFrom AND NOT localTo => BEGIN startedRenaming: BOOLEAN _ FALSE; fromServer, toServer, fromFile, toFile: Rope.ROPE; [fromServer, fromFile] _ FSName.ServerAndFileRopes[fpn.fullName]; [toServer, toFile] _ FSName.ServerAndFileRopes[tpn.fullName]; IF Rope.Equal[fromServer, toServer, FALSE] THEN BEGIN -- both GNames on the same server ENABLE FS.Error, ABORTED => BEGIN FSLock.ReleaseRecord[fromA]; IF startedRenaming THEN BEGIN FSReport.ReportRemote[endRenaming, fpn.fullName]; startedRenaming _ FALSE; END; END; ConfirmRename: PROC [v: FSBackdoor.Version] RETURNS [BOOLEAN] = BEGIN fpn.version _ v; IF fpn.volDesc # NIL THEN BEGIN -- have a cache [fromFP, fromA] _ FSDir.AcquireOldOrNewGName[fpn.volDesc, fpn.nameBody, fpn.version]; IF fromA.fileLock # none THEN Conflict[fpn]; END; fpn.fullName _ FSBackdoor.MakeFName[fpn.nameBody, fpn.version]; FSReport.ReportRemote[startRenaming, fpn.fullName]; startedRenaming _ TRUE; RETURN [TRUE]; END; fromFP _ File.nullFP; FSRemoteFile.Rename[fromServer, fromFile, wantedCreatedTime, toFile, ConfirmRename]; IF fromFP # File.nullFP THEN BEGIN -- from file is cached, so must be deleted FSDir.DeleteEntry[fpn.volDesc, fpn.nameBody, fpn.version]; FSFileOps.DeleteFile[FSFileOps.OpenFile[fpn.volDesc.vol, fromFP]]; END; FSReport.ReportRemote[endRenaming, fpn.fullName]; FSLock.ReleaseRecord[fromA]; RETURN; END; END; ENDCASE; [wantedCreatedTime, toFName] _ InnerCopy[fpn, tpn, setKeep, keep, wantedCreatedTime, FALSE, FALSE]; InnerDelete[fpn, wantedCreatedTime]; IF FSName.IsLocal[toFName] THEN FSReport.ReportCreation[renameTo, toFName]; END; <> Conflict: PROC [pn: FSName.ParsedFName] = { FSReport.LockConflict[pn.volDesc.prefix, pn.nameBody, pn.version] }; GlobalWriteLock: PROC = { FSBackdoor.ProduceError[globalWriteLock, "Can't open a GName with a write lock."] }; GetLName: PROC [pn: FSName.ParsedFName, wantedCreatedTime: BasicTime.GMT, remoteCheck: BOOLEAN] RETURNS [a: FSLock.ActiveFile, keep: CARDINAL] = BEGIN attachedTo: Rope.Text; attachmentCreatedTime: BasicTime.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 BEGIN ENABLE FS.Error, ABORTED => FSLock.ReleaseRecord[a]; gpn: FSName.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; END ELSE FSLock.LockAttachedToRecord[a]; ENDCASE => ERROR; END; GetGName: PROC [pn: FSName.ParsedFName, wantedCreatedTime: BasicTime.GMT, remoteCheck: BOOLEAN] RETURNS [a: FSLock.ActiveFile] = BEGIN usedTime: BasicTime.GMT; server, file: Rope.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 => BEGIN -- need to go to remote server RetrieveProc: PROC[fullGName: Rope.ROPE, bytes: INT, created: BasicTime.GMT] RETURNS [IO.STREAM] = BEGIN pn _ FSName.ParseName[NIL, fullGName]; [fp, a] _ FSDir.AcquireOldOrNewGName[pn.volDesc, pn.nameBody, pn.version]; <<-- What if STP times out while we're waiting?>> wantedCreatedTime _ created; IF fp = File.nullFP THEN BEGIN -- not in the cache, so must create and retrieve it [fp, a.h] _ FSFileOps.CreateFile[pn.volDesc.vol, FS.PagesForBytes[bytes]]; fileCreated _ TRUE; fileStream _ FSFileOps.CreateFileStream[a.h, newAppendOnly]; FSReport.ReportRemote[startRetrieving, pn.fullName]; END ELSE BEGIN -- something is in the cache IF a.h = NIL THEN BEGIN -- not already open, so have name lock a.h _ FSFileOps.OpenFile[pn.volDesc.vol, fp]; IF wantedCreatedTime # FSFileOps.GetProps[a.h].created THEN BEGIN -- wrong created time in the cache 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]; END; END ELSE BEGIN -- already open, so have read lock and can't delete IF wantedCreatedTime # FSFileOps.GetProps[a.h].created THEN Conflict[pn]; END; END; RETURN[fileStream]; END; fileStream: IO.STREAM _ NIL; fileToDelete: File.Handle _ NIL; fileCreated: BOOLEAN _ FALSE; a _ NIL; FSRemoteFile.Retrieve[server, file, wantedCreatedTime, RetrieveProc ! FS.Error, ABORTED => BEGIN IF fileStream # NIL THEN BEGIN fileStream.Close[ ! FS.Error => CONTINUE ]; fileStream _ NIL; -- ABORTED can follow an FS.Error FSReport.ReportRemote[endRetrieving, pn.fullName]; END; IF fileCreated THEN BEGIN FSFileOps.DeleteFile[a.h ! FS.Error => CONTINUE ]; fileCreated _ FALSE; -- ABORTED can follow an FS.Error END; FSLock.ReleaseRecord[a]; END ]; IF fileStream # NIL -- new file was created and filled in THEN BEGIN 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 ]; END; END; cached => BEGIN -- already in cache IF a.h = NIL THEN BEGIN a.h _ FSFileOps.OpenFile[pn.volDesc.vol, fp ! FS.Error => FSLock.ReleaseRecord[a] ]; END; UpdateUsedTime[pn, fp, usedTime]; END; ENDCASE => ERROR; END; RenameToLocalName: PROC [vDesc: FSFileOps.VolumeDesc, fNB: Rope.Text, fV: FSBackdoor.Version, tNB: Rope.Text, tV: FSBackdoor.Version, h: File.Handle, k: CARDINAL] = BEGIN BTree.SetUpdateInProgress[vDesc.tree, TRUE]; FSDir.DeleteEntry[vDesc, fNB, fV]; BEGIN 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]; END; BTree.SetUpdateInProgress[vDesc.tree, FALSE]; END; usedTimeGrain: INT = 600; --in seconds UpdateUsedTime: PROC [gpn: FSName.ParsedFName, fp: File.FP, usedTime: BasicTime.GMT] = BEGIN IF BasicTime.Period[from: usedTime, to: BasicTime.Now[]] > usedTimeGrain THEN BEGIN usedTime _ BasicTime.Now[]; FSDir.UpdateCachedEntry[gpn.volDesc, gpn.nameBody, gpn.version, usedTime, fp, replace]; FSFileOps.RecordUsage[fp, usedTime]; END; END; InnerCreate: PROC [vDesc: FSFileOps.VolumeDesc, body: Rope.Text, setPages: BOOLEAN, pages: INT, setKeep: BOOLEAN, keep: CARDINAL] RETURNS [a: FSLock.ActiveFile, newKeep: CARDINAL, fp: File.FP] = BEGIN [a, newKeep, fp] _ NewLName[vDesc, body, TRUE, setKeep, keep]; BEGIN ENABLE FS.Error => FSLock.ReleaseRecord[a]; IF fp = File.nullFP THEN BEGIN [fp, a.h] _ FSFileOps.CreateFile[vDesc.vol, pages]; setPages _ FALSE; END ELSE a.h _ FSFileOps.OpenFile[vDesc.vol, fp]; IF setPages THEN FSFileOps.SetFilePages[a.h, pages]; END; END; InnerCopy: PROC [fpn, tpn: FSName.ParsedFName, setKeep: BOOLEAN, keep: CARDINAL, wantedCreatedTime: BasicTime.GMT, remoteCheck: BOOLEAN, attach: BOOLEAN] RETURNS [createdTime: BasicTime.GMT, toFName: Rope.ROPE] = BEGIN localFrom: BOOLEAN = FSName.IsLocal[fpn.nameBody]; localTo: BOOLEAN = 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 => BEGIN IF attach THEN BEGIN -- attach GName to LName toA: FSLock.ActiveFile; IF remoteCheck OR createdTime = BasicTime.nullGMT THEN BEGIN server, file: Rope.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]]; END; [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]; END ELSE BEGIN -- copy GName to LName toA: FSLock.ActiveFile; [createdTime, toA] _ CopyGlobalToLocal[fpn, createdTime, remoteCheck, tpn, setKeep, keep]; toFName _ FSBackdoor.MakeFName[toA.nameBody, toA.version]; FSLock.ReleaseRecord[toA]; END; END; NOT localTo => BEGIN IF localFrom THEN BEGIN -- LName to GName fromA, globalA: FSLock.ActiveFile; fromKeep: CARDINAL; [fromA, fromKeep] _ GetLName[fpn, createdTime, FALSE]; fpn.version _ fromA.version; BEGIN ENABLE FS.Error, ABORTED => BEGIN FSLock.ReleaseRecord[fromA.attachedTo]; FSLock.ReleaseRecord[fromA]; END; IF fromA.fileLock = write <<-- currently open for write>> OR (attach AND fromA.fileLock=read AND tpn.volDesc#fpn.volDesc) <<-- open for read and system volume is different or missing>> THEN Conflict[fpn]; [tpn.version, createdTime, globalA] _ CopyFileToGlobal[fromA.h, tpn]; toFName _ FSBackdoor.MakeFName[tpn.nameBody, tpn.version]; IF attach THEN BEGIN -- attach LName to GName FSDir.UpdateAttachedEntry[fpn.volDesc, fpn.nameBody, fpn.version, fromKeep, createdTime, Rope.Flatten[toFName], replace]; IF fromA.attachedTo = NIL THEN BEGIN -- LName was not attached ENABLE FS.Error => FSLock.ReleaseRecord[globalA]; SELECT TRUE FROM (tpn.volDesc # fpn.volDesc) => BEGIN -- local and global volumes are different IF fromA.fileLock#none THEN ERROR; -- checked above FSFileOps.DeleteFile[fromA.h]; END; (fromA.fileLock = none) => -- local file not open right now Encache[fromA, tpn.nameBody, tpn.version, tpn.volDesc]; (fromA.fileLock = read) => BEGIN -- local file open for read right now 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; END; ENDCASE => ERROR; END; -- of LName was not attached END -- of attach LName to GName ELSE BEGIN END; -- opened LName already attached so just leave it END; FSLock.ReleaseRecord[globalA]; FSLock.ReleaseRecord[fromA.attachedTo]; FSLock.ReleaseRecord[fromA]; END -- of LName to GName ELSE BEGIN -- GName to GName fromA, globalA: FSLock.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]; END; END; ENDCASE => BEGIN -- LName to LName newKeep: CARDINAL; toFP: File.FP; fromA, toA: FSLock.ActiveFile _ NIL; fromPages: INT; [fromA, ] _ GetLName[fpn, createdTime, remoteCheck]; fpn.version _ fromA.version; BEGIN ENABLE FS.Error, ABORTED => BEGIN FSLock.ReleaseRecord[fromA.attachedTo]; FSLock.ReleaseRecord[fromA]; FSLock.ReleaseRecord[toA]; END; 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 BEGIN 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]; END; END; 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]; END; END; InnerDelete: PROC [pn: FSName.ParsedFName, wantedCreatedTime: BasicTime.GMT] = BEGIN type: FSBackdoor.EntryType; fp: File.FP; a: FSLock.ActiveFile; IF FSName.IsLocal[pn.nameBody] THEN BEGIN -- local deletion 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] }; END ELSE BEGIN -- remote deletion ConfirmDeletion: PROC [v: FSBackdoor.Version] RETURNS [BOOLEAN] = BEGIN pn.version _ v; IF pn.volDesc # NIL THEN BEGIN -- have a cache [type, , , fp, , , a] _ FSDir.AcquireOldFName[pn.volDesc, pn.nameBody, pn.version, BasicTime.nullGMT]; IF type # notFound AND a.fileLock # none THEN Conflict[pn]; END; pn.fullName _ FSBackdoor.MakeFName[pn.nameBody, pn.version]; FSReport.ReportRemote[startDeleting, pn.fullName]; startedDeletion _ TRUE; RETURN [TRUE]; END; startedDeletion: BOOLEAN _ FALSE; server, file: Rope.ROPE; [server, file] _ FSName.ServerAndFileRopes[pn.fullName]; type _ notFound; a _ NIL; FSRemoteFile.Delete[server, file, wantedCreatedTime, ConfirmDeletion ! FS.Error, ABORTED => BEGIN FSLock.ReleaseRecord[a]; IF startedDeletion THEN BEGIN FSReport.ReportRemote[endDeleting, pn.fullName]; startedDeletion _ FALSE; END; END ]; END; FSReport.ReportRemote[endDeleting, pn.fullName]; IF type # notFound THEN BEGIN -- have a file to delete 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] ]; END; END; NewLName: PROC [vDesc: FSFileOps.VolumeDesc, body: Rope.Text, wantFP: BOOLEAN, setKeep: BOOLEAN, keep: CARDINAL] RETURNS [a: FSLock.ActiveFile, newKeep: CARDINAL, fp: File.FP] = BEGIN 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 -- no existing versions, so use keep supplied ELSE BEGIN -- newKeep has been set IF NOT wantFP AND fp # File.nullFP THEN BEGIN -- have an fp we need to delete ENABLE FS.Error => FSLock.ReleaseRecord[a]; FSFileOps.DeleteFile[FSFileOps.OpenFile[vDesc.vol, fp]]; fp _ File.nullFP; END; END; END; CopyGlobalToLocal: PROC [gpn: FSName.ParsedFName, wantedCreatedTime: BasicTime.GMT, remoteCheck: BOOLEAN, lpn: FSName.ParsedFName, setKeep: BOOLEAN, keep: CARDINAL] RETURNS [createdTime: BasicTime.GMT, toA: FSLock.ActiveFile] = BEGIN localFP: File.FP; localStream: IO.STREAM _ NIL; createdTime _ wantedCreatedTime; toA _ NIL; BEGIN ENABLE FS.Error, ABORTED => BEGIN IF localStream # NIL THEN BEGIN localStream.Close[ ! FS.Error => CONTINUE ]; localStream _ NIL; -- ABORTED can follow an FS.Error FSReport.ReportRemote[endRetrieving, gpn.fullName]; END; IF toA # NIL THEN BEGIN IF toA.h # NIL THEN FSFileOps.DeleteFile[ toA.h ! FS.Error => CONTINUE ]; FSLock.ReleaseRecord[toA]; END; END; MakeLocalFile: PROC = BEGIN 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 BEGIN toA.h _ FSFileOps.OpenFile[lpn.volDesc.vol, localFP]; FSFileOps.SetFilePages[toA.h, localPages]; END; END; type: FSBackdoor.EntryType; globalFP: File.FP; globalA: FSLock.ActiveFile; usedTime: BasicTime.GMT; localBytes, localPages: INT; server, file: Rope.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 => BEGIN -- need to go to remote server RetrieveProc: PROC[fullGName: Rope.ROPE, bytes: INT, created: BasicTime.GMT] RETURNS [IO.STREAM] = BEGIN gpn.version _ FSName.ParseName[NIL, fullGName].version; createdTime _ created; localBytes _ bytes; MakeLocalFile[]; localStream _ FSFileOps.CreateFileStream[toA.h, newAppendOnly]; FSReport.ReportRemote[startRetrieving, gpn.fullName]; RETURN[localStream]; END; 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]; END; cached => BEGIN -- already in the cache 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]; END ENDCASE => ERROR; FSFileOps.SetProps[toA.h, localBytes, keep, createdTime, lpn.nameBody, lpn.version]; END; FSDir.UpdateLocalEntry[lpn.volDesc, lpn.nameBody, lpn.version, keep, localFP, insertOrReplace]; END; CopyFileToGlobal: PROC [h: File.Handle, global: FSName.ParsedFName] RETURNS [globalVersion: FSBackdoor.Version, created: BasicTime.GMT, globalA: FSLock.ActiveFile] = BEGIN GetVersion: PROC [v: FSBackdoor.Version] RETURNS [BOOLEAN] = BEGIN globalVersion _ v; global.fullName _ FSBackdoor.MakeFName[global.nameBody, globalVersion]; IF global.volDesc # NIL THEN BEGIN -- have a cache [ , globalA] _ FSDir.AcquireOldOrNewGName[global.volDesc, global.nameBody, globalVersion]; IF globalA.fileLock # none THEN ERROR; END; FSReport.ReportRemote[startStoring, global.fullName]; RETURN [TRUE]; END; server, file: Rope.ROPE; fromStream: IO.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 => BEGIN IF fromStream # NIL THEN BEGIN fromStream.Close[]; fromStream _ NIL; -- ABORTED can follow an FS.Error FSReport.ReportRemote[endStoring, global.fullName]; END; FSLock.ReleaseRecord[globalA]; END ]; fromStream.Close[]; FSReport.ReportRemote[endStoring, global.fullName]; END; Encache: PROC[localA: FSLock.ActiveFile, nB: Rope.Text, ver: FSBackdoor.Version, vDesc: FSFileOps.VolumeDesc] = BEGIN used: BasicTime.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; END.