FSMainImpl1.mesa
Copyright © 1984, 1985 by Xerox Corporation. All rights reserved.
Exports FS.Open, FS.Create, FS.Copy, FS.Delete, and FS.Rename
See FSMainImpl2 for FS.EnumerateForInfo, FS.EnumerateForNames, FS.FileInfo and FS.SetKeep
Russ Atkinson (RRA) August 27, 1985 1:21:34 pm PDT
Bob Hagmann, April 24, 1986 8:30:18 am PST
DIRECTORY
BasicTime USING [GMT, Now, nullGMT, Period],
BTree USING [SetUpdateInProgress],
File USING [FP, Handle, nullFP],
FS USING [BytesForPages, Create, Error, Lock, Open, OpenFile, PagesForBytes],
FSBackdoor USING [EntryType, GuestProcsRec, highestVersion, lowestVersion, MakeFName, ProduceError, Version],
FSDir USING [AcquireNextLName, AcquireOldFName, AcquireOldOrNewGName, DeleteEntry, UpdateAttachedEntry, UpdateCachedEntry, UpdateLocalEntry],
FSFileOps USING [Copy, CreateFile, CreateFileStream, DeleteFile, GetFileInfo, GetProps, OpenFile, RecordUsage, SetBytesAndCreated, SetFilePages, SetProps, VolumeDesc],
FSLock USING [ActiveFile, LockFile, LockAttachedToRecord, NewOpenFile, ReleaseRecord],
FSName USING [IsLocal, ParseClientName, ParsedFName, ParseName, ServerAndFileRopes, VersionInfo, VersionPartFromVersion],
FSRemoteFile USING [Delete, Info, Rename, Retrieve, Store],
FSReport USING [LockConflict, NoCache, ReportCreation, ReportRemote, UnknownVolumeLName, UnknownFile, VersionSpecified],
IO USING [Close, GetIndex, STREAM],
Rope USING [Concat, Equal, Flatten, Match, ROPE, Text];
Exported to FS
Open:
PUBLIC
PROC [name:
ROPE, lock: Lock, wantedCreatedTime:
GMT, remoteCheck:
BOOL, wDir:
ROPE]
RETURNS [file: OpenFile] = {
openedName: ROPE ← NIL;
a: ActiveFile;
pn: ParsedFName;
IF IsGuestProcess[]
THEN
RETURN [GuestProcs.Open[name, lock, wantedCreatedTime, remoteCheck, wDir]];
[pn, ] ← FSName.ParseClientName[name, wDir, TRUE];
IF FSName.IsLocal[pn.nameBody]
THEN {
-- open LName
keep: CARDINAL;
IF pn.volDesc = NIL THEN FSReport.UnknownVolumeLName[pn.fullName];
[a, keep] ← GetLName[pn, wantedCreatedTime, remoteCheck];
pn.version ← a.version;
IF lock = read
THEN {
-- open LName for read
gotLock: BOOL = FSLock.LockFile[a, read];
IF a.attachedTo # NIL
THEN {
-- set lock in attachment too
IF gotLock
THEN { IF NOT FSLock.LockFile[a.attachedTo, read] THEN ERROR };
FSLock.ReleaseRecord[a.attachedTo];
};
FSLock.ReleaseRecord[a];
IF NOT gotLock THEN Conflict[pn];
} -- of open LName for read
ELSE {
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
};
}
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];
};
file ← FSLock.NewOpenFile[a];
};
Create:
PUBLIC
PROC [name:
ROPE, setPages:
BOOL, pages:
INT, setKeep:
BOOL, keep:
CARDINAL, wDir:
ROPE]
RETURNS [file: OpenFile] = {
IF IsGuestProcess[] THEN RETURN [GuestProcs.Create[name, setPages, pages, setKeep, keep, wDir]];
RETURN[CreateBody[name, setPages, pages, setKeep, keep, wDir, FALSE]];
};
CreateVMBacking:
PUBLIC
PROC [name:
ROPE, setPages:
BOOL, pages:
INT, setKeep:
BOOL, keep:
CARDINAL, wDir:
ROPE]
RETURNS [file: OpenFile] = {
RETURN[CreateBody[name, setPages, pages, setKeep, keep, wDir, TRUE]];
};
CreateBody:
PUBLIC
PROC [name:
ROPE, setPages:
BOOL, pages:
INT, setKeep:
BOOL, keep:
CARDINAL, wDir:
ROPE, VMBackingFile:
BOOL]
RETURNS [file: OpenFile] = {
a: ActiveFile;
newKeep: CARDINAL;
fp: File.FP;
pn: ParsedFName;
vI: FSName.VersionInfo;
[pn, vI] ← FSName.ParseClientName[name, wDir, TRUE];
IF NOT FSName.IsLocal[pn.nameBody]
THEN FSBackdoor.ProduceError[globalCreation, "FS.Create illegal for a GName"];
IF pn.volDesc = NIL THEN FSReport.UnknownVolumeLName[pn.fullName];
IF vI # missing THEN FSReport.VersionSpecified[pn.fullName];
[a, newKeep, fp] ← InnerCreate[pn.volDesc, pn.nameBody, setPages, pages, setKeep, keep, VMBackingFile];
pn.version ← a.version; -- the real version number
FSFileOps.SetProps[a.h, 0, newKeep, BasicTime.Now[], pn.nameBody, pn.version
! FS.Error => FSLock.ReleaseRecord[a] ];
FSDir.UpdateLocalEntry[pn.volDesc, pn.nameBody, pn.version, newKeep, fp, insert];
IF NOT FSLock.LockFile[a, write] THEN ERROR;
FSLock.ReleaseRecord[a];
file ← FSLock.NewOpenFile[a];
};
OpenOrCreate:
PUBLIC
PROC [name:
ROPE, keep:
CARDINAL, pages:
INT, wDir:
ROPE]
RETURNS [file: OpenFile] = {
needToCreate: BOOL ← FALSE;
file ←
FS.Open[name: name, lock: write, wDir: wDir
! FS.Error => IF error.code = $unknownFile THEN {needToCreate ← TRUE; CONTINUE} ];
IF needToCreate
THEN file ← FS.Create [name: name, pages: pages, keep: keep, wDir: wDir];
};
Copy:
PUBLIC PROC [from, to:
ROPE, setKeep:
BOOL, keep:
CARDINAL, wantedCreatedTime:
GMT, remoteCheck:
BOOL, attach:
BOOL, wDir:
ROPE]
RETURNS [toFName:
ROPE] = {
fpn, tpn: ParsedFName;
tvI: FSName.VersionInfo;
IF IsGuestProcess[] THEN toFName ← GuestProcs.Copy[from, to, setKeep, keep,wantedCreatedTime, remoteCheck, attach, wDir];
[tpn, tvI] ← FSName.ParseClientName[to, wDir, TRUE];
IF tvI # missing THEN FSReport.VersionSpecified[tpn.fullName];
[fpn, ] ← FSName.ParseClientName[from, wDir, TRUE];
toFName ← InnerCopy[fpn, tpn, setKeep, keep, wantedCreatedTime, remoteCheck, attach].toFName;
IF FSName.IsLocal[toFName] THEN FSReport.ReportCreation[copyTo, toFName];
};
Delete:
PUBLIC
PROC [name:
ROPE, wantedCreatedTime:
GMT, wDir:
ROPE] = {
pn: ParsedFName;
IF IsGuestProcess[]
THEN {
GuestProcs.Delete[name, wantedCreatedTime, wDir];
}
ELSE {
[pn, ] ← FSName.ParseClientName[name, wDir, FALSE];
InnerDelete[pn, wantedCreatedTime];
};
};
Rename:
PUBLIC
PROC [from, to:
ROPE, setKeep:
BOOL, keep:
CARDINAL, wantedCreatedTime:
GMT, wDir:
ROPE] = {
fpn, tpn: ParsedFName;
tvI: FSName.VersionInfo;
toFName: ROPE;
localFrom, localTo: BOOL;
fromType: FSBackdoor.EntryType;
fromFP: File.FP;
fromA: ActiveFile ← NIL;
IF IsGuestProcess[]
THEN {
GuestProcs.Rename[from, to, setKeep, keep, wantedCreatedTime, wDir];
}
ELSE {
[tpn, tvI] ← FSName.ParseClientName[to, wDir, TRUE];
IF tvI # missing THEN FSReport.VersionSpecified[tpn.fullName];
[fpn, ] ← FSName.ParseClientName[from, wDir, TRUE];
localFrom ← FSName.IsLocal[fpn.nameBody];
localTo ← FSName.IsLocal[tpn.nameBody];
SELECT
TRUE
FROM
localFrom
AND localTo => {
IF fpn.volDesc = NIL
THEN FSReport.UnknownVolumeLName[fpn.fullName];
IF tpn.volDesc = NIL
THEN FSReport.UnknownVolumeLName[tpn.fullName];
IF fpn.volDesc = tpn.volDesc
THEN {
attachedTo: Text;
created: GMT;
toA: ActiveFile;
[fromType, fpn.version, , fromFP, created, attachedTo, fromA] ← FSDir.AcquireOldFName[fpn.volDesc, fpn.nameBody, fpn.version, wantedCreatedTime];
SELECT fromType
FROM
notFound =>
FSReport.UnknownFile[fpn.fullName, wantedCreatedTime];
local, attached => {
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];
};
};
Internal procedures
Conflict:
PROC [pn: ParsedFName] = {
FSReport.LockConflict[pn.volDesc.prefix, pn.nameBody, pn.version] };
GlobalWriteLock:
PROC = {
FSBackdoor.ProduceError[globalWriteLock, "Can not open a GName with a write lock"];
};
GetLName:
PROC [pn: ParsedFName, wantedCreatedTime:
GMT, remoteCheck:
BOOL]
RETURNS [a: ActiveFile, keep:
CARDINAL] = {
attachedTo: Text;
attachmentCreatedTime: GMT;
type: FSBackdoor.EntryType;
fp: File.FP;
[type, pn.version, keep, fp, attachmentCreatedTime, attachedTo, a] ← FSDir.AcquireOldFName[pn.volDesc, pn.nameBody, pn.version, wantedCreatedTime];
SELECT type
FROM
notFound =>
FSReport.UnknownFile[pn.fullName, wantedCreatedTime];
local =>
IF a.h =
NIL THEN
a.h ← FSFileOps.OpenFile[pn.volDesc.vol, fp
! FS.Error => FSLock.ReleaseRecord[a] ];
attached =>
IF a.attachedTo =
NIL
THEN {
ENABLE FS.Error, ABORTED => FSLock.ReleaseRecord[a];
gpn: ParsedFName = FSName.ParseName[NIL, attachedTo];
IF gpn.volDesc = NIL THEN FSReport.UnknownVolumeLName[gpn.fullName];
a.attachedTo ← GetGName[gpn, attachmentCreatedTime, remoteCheck];
a.h ← a.attachedTo.h;
}
ELSE FSLock.LockAttachedToRecord[a];
ENDCASE => ERROR;
};
GetGName:
PROC [pn: ParsedFName, wantedCreatedTime:
GMT, remoteCheck:
BOOL]
RETURNS [a: ActiveFile] = {
usedTime: GMT;
server, file: ROPE;
type: FSBackdoor.EntryType;
fp: File.FP;
[server, file] ← FSName.ServerAndFileRopes[pn.fullName];
IF remoteCheck
AND pn.version
NOT
IN (FSBackdoor.lowestVersion .. FSBackdoor.highestVersion)
AND wantedCreatedTime = BasicTime.nullGMT
THEN
[version: pn.version, created: wantedCreatedTime] ← FSRemoteFile.Info[server, file, BasicTime.nullGMT];
[type, pn.version, , fp, usedTime, , a] ← FSDir.AcquireOldFName[pn.volDesc, pn.nameBody, pn.version, wantedCreatedTime];
SELECT type
FROM
notFound => {
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 {
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:
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, fromA];
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;
};
}
ELSE {
opened LName already attached so just leave it
};
};
FSLock.ReleaseRecord[globalA];
FSLock.ReleaseRecord[fromA.attachedTo];
FSLock.ReleaseRecord[fromA];
}
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: 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 {
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
no existing versions, so use keep supplied
newKeep ← keep
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: 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, fromActiveFile: ActiveFile ←
NIL]
RETURNS [globalVersion: Version, created:
GMT, globalA: ActiveFile] = {
GetVersion:
PROC [v: Version]
RETURNS [
BOOL] = {
globalVersion ← global.version ← v;
global.fullName ← FSBackdoor.MakeFName[global.nameBody, global.version];
IF global.volDesc #
NIL
THEN {
have a cache
nameBody: ROPE ← global.nameBody;
IF NOT Rope.Match["[*", nameBody] THEN nameBody ← Rope.Concat[global.volDesc.prefix, nameBody];
IF fromActiveFile #
NIL
AND fromActiveFile.attachedTo #
NIL AND Rope.Equal[fromActiveFile.attachedTo.nameBody, nameBody]
AND fromActiveFile.attachedTo.version = global.version
THEN {
version assigned by server is same as the one we are copying. This can happen due to crash or inadvertent deletion of server files.
dummy ← dummy + 1; -- a place to hang performance hooks
}
ELSE {
[fpToDelete, globalA] ← FSDir.AcquireOldOrNewGName[global.volDesc, global.nameBody, global.version];
IF globalA.fileLock # none
THEN Conflict[global]; -- server's !N version already cached and open
};
};
FSReport.ReportRemote[startStoring, global.fullName];
RETURN [TRUE];
};
dummy: INT ← 0 ; -- no real use
fpToDelete: File.FP ← File.nullFP;
server, file: ROPE;
fromStream: STREAM ← FSFileOps.CreateFileStream[h, oldReadOnly];
created ← FSFileOps.GetProps[h].created;
[server, file] ← FSName.ServerAndFileRopes[global.fullName];
globalA ← NIL;
FSRemoteFile.Store[server, file, fromStream, created, GetVersion
!
FS.Error,
ABORTED => {
IF fromStream #
NIL
THEN {
fromStream.Close[];
fromStream ← NIL; -- ABORTED can follow an FS.Error
FSReport.ReportRemote[endStoring, global.fullName];
};
FSLock.ReleaseRecord[globalA];
} ];
fromStream.Close[];
FSReport.ReportRemote[endStoring, global.fullName];
IF fpToDelete # File.nullFP
THEN {
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];
};