FSMainImpl1.mesa
Exports FS.Open, FS.Create, FS.Copy, FS.Delete, and FS.Rename
See FSMainImpl2 for FS.EnumerateForInfo, FS.EnumerateForNames, FS.FileInfo and FS.SetKeep
Last Edited by: Schroeder, November 15, 1983 4:17 pm
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
Exported to FS
Open: PUBLIC PROC [name: Rope.ROPE, lock: FS.Lock, wantedCreatedTime: BasicTime.GMT, remoteCheck: BOOLEAN, wDir: Rope.ROPE] RETURNS [file: FS.OpenFile] =
BEGIN
openedName: Rope.ROPENIL;
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: BOOLEANFALSE;
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: BOOLEANFALSE;
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;
Internal procedures
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.STREAMNIL;
fileToDelete: File.Handle ← NIL;
fileCreated: BOOLEANFALSE;
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: BOOLEANFALSE;
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.STREAMNIL;
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.