<> <> <> <> <> <> <> <> 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 [], FSExtrasForIago USING [], 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], FSPseudoServers, 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, FSPseudoServers, FSReport, Rope, FSRemoteFile, IO EXPORTS FS, FSExtras, FSExtrasForIago = 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; <> Open: PUBLIC PROC [name: ROPE, lock: Lock, wantedCreatedTime: GMT, remoteCheck: BOOL, wDir: ROPE] RETURNS [file: OpenFile] = { openedName: ROPE _ NIL; a: ActiveFile; pn: ParsedFName; [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 { -- open LName for write IF a.fileLock # none THEN { -- won't be able to get a write lock 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 -- can do it by renaming RenameToLocalName[pn.volDesc, globalNB, a.attachedTo.version, pn.nameBody, pn.version, a.h, keep] ELSE { -- is open for read or on different volume, so must copy 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 }; -- of open LName for write } -- of open LName ELSE { -- open GName 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]; }; -- of open GName file _ FSLock.NewOpenFile[a]; }; Create: 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, 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, "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, 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]; }; NewCopy: 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; [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]; }; Copy: PUBLIC PROC [from, to: ROPE, setKeep: BOOL, keep: CARDINAL, wantedCreatedTime: GMT, remoteCheck: BOOL, attach: BOOL, wDir: ROPE] = { [] _ NewCopy[from, to, setKeep, keep, wantedCreatedTime, remoteCheck, attach, wDir]; }; Delete: PUBLIC PROC [name: ROPE, wantedCreatedTime: GMT, wDir: ROPE] = { pn: ParsedFName; [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; [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 => { -- 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 { -- 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]; } ELSE { -- attached local to local 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 { -- both GNames on the same server 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 { -- have a cache [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 { -- from file is cached, so must be deleted 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't 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 FSPseudoServers.AvoidRemoteCheck[server] THEN remoteCheck _ FALSE; 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 => { -- need to go to remote server 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]; <<-- What if STP times out while we're waiting?>> wantedCreatedTime _ created; IF fp = File.nullFP THEN { -- 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]; } ELSE { -- something is in the cache IF a.h = NIL THEN { -- not already open, so have name lock a.h _ FSFileOps.OpenFile[pn.volDesc.vol, fp]; IF wantedCreatedTime # FSFileOps.GetProps[a.h].created THEN { -- 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]; }; } ELSE { -- already open, so have read lock and can't delete 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 => { -- already in cache 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 { -- attach GName to LName 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 { -- copy GName to LName 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 { -- LName to GName 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 <<-- 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 { -- attach LName to GName FSDir.UpdateAttachedEntry[fpn.volDesc, fpn.nameBody, fpn.version, fromKeep, createdTime, Rope.Flatten[toFName], replace]; IF fromA.attachedTo = NIL THEN { -- LName was not attached ENABLE FS.Error => FSLock.ReleaseRecord[globalA]; SELECT TRUE FROM (tpn.volDesc # fpn.volDesc) => { -- local and global volumes are different IF fromA.fileLock#none THEN ERROR; -- checked above FSFileOps.DeleteFile[fromA.h]; }; (fromA.fileLock = none) => -- local file not open right now Encache[fromA, tpn.nameBody, tpn.version, tpn.volDesc]; (fromA.fileLock = read) => { -- 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; }; ENDCASE => ERROR; }; -- of LName was not attached } -- of attach LName to GName ELSE { }; -- opened LName already attached so just leave it }; FSLock.ReleaseRecord[globalA]; FSLock.ReleaseRecord[fromA.attachedTo]; FSLock.ReleaseRecord[fromA]; } -- of LName to GName ELSE { -- GName to GName 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 => { -- LName to LName 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 { -- 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] }; } ELSE { -- remote deletion 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 -- no existing versions, so use keep supplied ELSE { -- newKeep has been set 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 => { -- need to go to remote server 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 => { -- 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]; } 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] 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 { <> [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]; }; 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.