FSMainImpl1.mesa
Copyright © 1984 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
Last Edited by:
Schroeder, January 17, 1984 11:31 am
Bob Hagmann, May 10, 1984 11:27:37 am PDT
Russ Atkinson, October 19, 1984 11:31:12 am PDT
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
Useful types declared externally
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;
Exported to FS
Open: PUBLIC PROC [name: ROPE, lock: Lock, wantedCreatedTime: GMT, remoteCheck: BOOL, wDir: ROPE] RETURNS [file: OpenFile] = {
openedName: ROPENIL;
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 {
collapse attachment
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: BOOLFALSE;
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: BOOLFALSE;
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];
};
Internal procedures
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: STREAMNIL;
fileToDelete: File.Handle ← NIL;
fileCreated: BOOLFALSE;
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 {
new file was created and filled in
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: BOOLFALSE] 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 {
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];
};
pn.fullName ← FSBackdoor.MakeFName[pn.nameBody, pn.version];
FSReport.ReportRemote[startDeleting, pn.fullName];
startedDeletion ← TRUE;
RETURN [TRUE];
};
startedDeletion: BOOLFALSE;
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 {
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] ];
};
};
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 {
have an fp we need to delete
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: STREAMNIL;
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 {
have a cache
[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 {
version assigned by server was already in the cache, so we must flush it
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.