FSFileOpsImpl.mesa
Copyright © 1984, 1985 by Xerox Corporation. All rights reserved.
Schroeder, November 30, 1983 9:07 am
Levin, September 22, 1983 1:21 pm
Bob Hagmann, May 9, 1985 4:45:18 pm PDT
Russ Atkinson (RRA) May 13, 1985 8:34:04 pm PDT
DIRECTORY
BasicTime USING [GMT, Now, Update],
Booting USING [Deregister, CheckpointProc, RegisterProcs, RollbackProc],
BTree USING [Entry, Error, New, Open, SalvageEntries, SetState, SetUpdateInProgress, Tree, UpdateInProgress],
BTreeVM USING [FreeBuffers, Handle, Open, ReferencePage, ReleasePage],
File USING [Create, Delete, Error, FindVolumeFromID, FindVolumeFromName, FP, GetVolumeName, Handle, Info, nullFP, Open, PageCount, PageNumber, Reason, SetSize, SystemVolume, Volume, VolumeID],
FileBackdoor USING [CreateVMBacking, GetRoot, GetVolumePages, NextFile, SetFreeboard, SetRoot],
FS USING [Error, ErrorDesc, WordsForPages],
FSBackdoor USING [Entry, EntryPtr, EntryType, MakeFName, TextFromTextRep, TextRep, ProduceError, Version],
FSDir USING [Compare, EntrySize, UpdateAttachedEntry, UpdateCachedEntry, UpdateLocalEntry],
FSFileOps USING [GetNameBodyAndVersion, GetProps, InitializePropertyStorage, RegisterVolumeFlusher, VolumeDesc, VolumeDescObject],
FSName USING [IsLocal],
FSReport USING [FileError, UnknownVolume],
Rope USING [Cat, Equal, Fetch, Length, ROPE, Text];
FSFileOpsImpl:
CEDAR
MONITOR
IMPORTS BasicTime, Booting, BTree, BTreeVM, File, FileBackdoor, FS, FSBackdoor, FSDir, FSFileOps, FSName, FSReport, Rope
EXPORTS FSBackdoor, FSFileOps
= {
ROPE: TYPE = Rope.ROPE;
Exported to FSBackdoor
ScavengeDirectoryAndCache:
PUBLIC
PROC [volName:
ROPE] = {
Activate:
ENTRY
PROC = {
ENABLE UNWIND => NULL;
ActivateVolume[vDesc];
};
errorDesc: FS.ErrorDesc;
vDesc: FSFileOps.VolumeDesc;
Booting.RegisterProcs[c: RejectCheckpoint]; -- mutual exclusion with checkpoints
vDesc ← InnerGetVolumeDesc[ volName,
TRUE
-- suspension occurred only if no FS.Error
! FS.Error => {errorDesc ← error; CONTINUE} ];
IF errorDesc.group = ok
THEN {
Scavenge[ vDesc.vol ! FS.Error => {errorDesc ← error; CONTINUE} ];
Activate[ ! FS.Error => {IF errorDesc.group = ok THEN errorDesc ← error; CONTINUE} ];
};
Booting.Deregister[c: RejectCheckpoint];
IF errorDesc.group # ok THEN ERROR FS.Error[errorDesc];
};
SetFreeboard:
PUBLIC
PROC [freeboard:
INT] = {
vDesc: FSFileOps.VolumeDesc = GetVolumeDesc[NIL];
IF vDesc = NIL THEN FSReport.UnknownVolume[NIL];
FileBackdoor.SetFreeboard[ vDesc.vol, freeboard ! File.Error => FSReport.FileError[why] ];
};
VolumePages:
PUBLIC
PROC [volName:
ROPE ←
NIL]
RETURNS [size, free, freeboard:
INT] = {
vDesc: FSFileOps.VolumeDesc = GetVolumeDesc[volName];
IF vDesc = NIL THEN FSReport.UnknownVolume[NIL];
[size, free, freeboard] ← FileBackdoor.GetVolumePages[ vDesc.vol
! File.Error => FSReport.FileError[why] ];
};
FNameFromHandle:
PUBLIC
PROC [file: File.Handle]
RETURNS [
ROPE] = {
nameBody, prefix: ROPE;
version: FSBackdoor.Version;
{
ENABLE File.Error => FSReport.FileError[why];
volume: File.Volume = File.Info[file].volume;
prefix ← IF volume = File.SystemVolume[] THEN NIL ELSE Rope.Cat["[]<", File.GetVolumeName[volume], ">"];
};
[nameBody, version] ← FSFileOps.GetNameBodyAndVersion[file];
RETURN [ FSBackdoor.MakeFName[nameBody, version, prefix] ];
};
CloseVolume:
PUBLIC
ENTRY
PROC [v: File.Volume] = {
ENABLE UNWIND => NULL;
prev, vDesc: FSFileOps.VolumeDesc ← NIL;
FOR vDesc ← volumeDescList, vDesc.next
UNTIL vDesc =
NIL
DO
IF vDesc.vol = v
THEN {
IF prev =
NIL
THEN volumeDescList ← vDesc.next
ELSE prev.next ← vDesc.next;
IF vDesc = svDesc THEN svDesc ← NIL;
BTree.SetState[vDesc.tree, closed];
IF vDesc.treeVM #
NIL THEN {
BTreeVM.FreeBuffers[vDesc.treeVM];
vDesc.treeVM ← NIL;
};
EXIT;
};
prev ← vDesc;
ENDLOOP;
};
Internal procedures
waitForRollback: BOOL ← FALSE;
rollback: CONDITION;
svDesc, volumeDescList: FSFileOps.VolumeDesc ← NIL;
InnerGetVolumeDesc:
ENTRY
PROC [vName:
ROPE, suspendVolume:
BOOL]
RETURNS [FSFileOps.VolumeDesc] = {
ENABLE UNWIND => NULL;
vDesc: FSFileOps.VolumeDesc;
vol: File.Volume ← NIL;
WHILE waitForRollback
DO
can't make new ones now, so don't even look
WAIT rollback;
ENDLOOP;
IF Rope.Length[vName] = 0
AND svDesc #
NIL
THEN {
want system volume and already have descriptor
IF suspendVolume THEN SuspendVolume[svDesc];
RETURN [svDesc];
};
SELECT
TRUE
FROM
Rope.Length[vName] = 0 =>
{
-- system volume
vol ← File.SystemVolume[];
IF vol = NIL
THEN
{
-- no system volume is available
IF suspendVolume
THEN FSReport.UnknownVolume[NIL]
ELSE RETURN [NIL];
};
vName ← File.GetVolumeName[vol];
};
(Rope.Fetch[vName, 0] = '#) =>
{
-- volume number
vol ← File.FindVolumeFromID[ MakeVolumeID[vName] ];
IF vol = NIL THEN FSReport.UnknownVolume[vName];
vName ← File.GetVolumeName[vol];
};
ENDCASE;
FOR vDesc ← volumeDescList, vDesc.next
UNTIL vDesc =
NIL
DO
IF Rope.Equal[vName, vDesc.vName,
FALSE]
THEN {
found VolumeDesc in list
IF suspendVolume THEN SuspendVolume[vDesc];
RETURN [vDesc];
};
ENDLOOP;
IF vol =
NIL THEN {
vol ← File.FindVolumeFromName[vName];
IF vol = NIL THEN FSReport.UnknownVolume[vName];
};
vDesc ← NEW[ FSFileOps.VolumeDescObject ← [ NIL, vName, NIL, vol, NIL, NIL ] ];
vDesc.tree ← AllocateBTree[];
IF NOT suspendVolume THEN [] ← SetUpBTree[vDesc];
IF vol = File.SystemVolume[]
THEN {
FSFileOps.RegisterVolumeFlusher[vDesc];
svDesc ← vDesc;
}
ELSE vDesc.prefix ← Rope.Cat["[]<", vName, ">"];
vDesc.next ← volumeDescList;
volumeDescList ← vDesc;
RETURN [vDesc];
};
bufferSize: CARDINAL = 4;
buffers: CARDINAL = 32;
basePage: File.PageNumber = [0]; -- first page of the directory/cache btree
initialPages: File.PageCount = basePage + buffers*bufferSize;
AllocateBTree:
PROC
RETURNS [BTree.Tree] = {
RETURN [ BTree.New [
repPrim: [compare: FSDir.Compare, entrySize: FSDir.EntrySize],
storPrim: [referencePage: BTreeVM.ReferencePage, releasePage: BTreeVM.ReleasePage],
minEntrySize: SIZE[FSBackdoor.Entry.local] + SIZE[FSBackdoor.TextRep[1]], -- "a"
initialState: suspended
] ];
};
SetUpBTree:
PROC [vDesc: FSFileOps.VolumeDesc, scavenge:
BOOL ←
FALSE, newTemp:
BOOL ←
FALSE]
RETURNS [treeFile: File.Handle ←
NIL]= {
newFile: BOOL ← FALSE;
treeFP: File.FP ← File.nullFP;
treeVM: BTreeVM.Handle;
IF ~newTemp
THEN treeFP ← FileBackdoor.GetRoot[vDesc.vol, client
! File.Error =>
IF why = nonCedarVolume
THEN FSBackdoor.ProduceError[nonCedarVolume, Rope.Cat["Local volume \"", vDesc.vName, "\" not formatted for Cedar"]]
ELSE FSReport.FileError[why]
].fp;
IF treeFP = File.nullFP
THEN {
new tree file, but can't have validation stamp in property page
treeFile ← File.Create[vDesc.vol, initialPages,
NIL
! File.Error => FSReport.FileError[why] ];
newFile ← TRUE;
}
ELSE treeFile ← OpenFile[vDesc.vol, treeFP];
treeVM ← BTreeVM.Open[
file: treeFile,
filePagesPerPage: bufferSize,
cacheSize: buffers,
base: basePage
];
{
ENABLE
UNWIND => {
BTreeVM.FreeBuffers[treeVM];
IF newFile THEN DeleteFile[treeFile ! FS.Error => CONTINUE];
};
OpenBTree[vDesc.tree, treeVM, newFile, scavenge];
IF newFile AND ~newTemp THEN FileBackdoor.SetRoot[client, treeFile ! File.Error => FSReport.FileError[why] ];
};
vDesc.treeVM ← treeVM;
};
OpenBTree:
PROC [tree: BTree.Tree, treeVM: BTreeVM.Handle, new, scavenge:
BOOL] = {
BTree.Open[
tree: tree,
storage: treeVM,
pageSize: FS.WordsForPages[bufferSize],
initialize: new,
maintainRecomputableState: TRUE
!
BTree.UpdateInProgress => IF scavenge THEN RESUME ELSE FSBackdoor.ProduceError[badBTree, "Update in progress when opening the directory/cache BTree"];
BTree.Error => FSBackdoor.ProduceError[badBTree, "BTree.Error when opening the directory/cache BTree"];
File.Error => FSReport.FileError[why];
];
};
MakeVolumeID:
PROC [vName:
ROPE]
RETURNS [id: File.VolumeID] = {
FSBackdoor.ProduceError[notImplemented, "Can't handle hex volume ID's yet"];
};
SuspendVolume:
INTERNAL
PROC [vDesc: FSFileOps.VolumeDesc] = {
no SIGNALs or ERRORs
IF vDesc = svDesc
THEN
turn off volume flusher if any; (RRA) must do this before suspending the BTree
FSFileOps.RegisterVolumeFlusher[NIL];
BTree.SetState[vDesc.tree, suspended]; -- may wait forever
IF vDesc.treeVM #
NIL
THEN {
BTreeVM.FreeBuffers[vDesc.treeVM];
vDesc.treeVM ← NIL;
};
};
ActivateVolume:
INTERNAL
PROC [vDesc: FSFileOps.VolumeDesc] = {
[] ← SetUpBTree[vDesc ! FS.Error => DestroyVolumeDesc[vDesc] ];
IF vDesc = svDesc THEN FSFileOps.RegisterVolumeFlusher[svDesc];
};
DestroyVolumeDesc:
INTERNAL
PROC [victim: FSFileOps.VolumeDesc] = {
assumes that tree already is suspended
prev: FSFileOps.VolumeDesc ← NIL;
FOR vDesc: FSFileOps.VolumeDesc ← volumeDescList, vDesc.next
UNTIL vDesc =
NIL
DO
IF victim = vDesc THEN EXIT;
prev ← vDesc;
REPEAT FINISHED => ERROR;
ENDLOOP;
IF victim = svDesc
THEN {
special steps for the system volume; (RRA) must close this before closing the BTree
FSFileOps.RegisterVolumeFlusher[NIL];
svDesc ← NIL;
};
BTree.SetState[victim.tree, closed];
IF prev =
NIL
THEN volumeDescList ← victim.next
ELSE prev.next ← victim.next;
};
RejectCheckpoint: Booting.CheckpointProc = {
rejection ← "FS scavenge is in progress.";
};
FSCheckpointProc:
ENTRY Booting.CheckpointProc = {
ENABLE UNWIND => NULL;
waitForRollback ← TRUE;
FOR vDesc: FSFileOps.VolumeDesc ← volumeDescList, vDesc.next
UNTIL vDesc =
NIL
DO
SuspendVolume[vDesc];
ENDLOOP;
};
FSRollbackProc:
ENTRY Booting.RollbackProc = {
ENABLE UNWIND => NULL;
FOR vDesc: FSFileOps.VolumeDesc ← volumeDescList, vDesc.next
UNTIL vDesc =
NIL
DO
ActivateVolume[vDesc ! FS.Error => CONTINUE ];
ENDLOOP;
waitForRollback ← FALSE;
BROADCAST rollback;
};
AttachedEntry:
TYPE =
RECORD [
next: REF AttachedEntry,
nameBody: Rope.Text,
version: FSBackdoor.Version,
keep: CARDINAL,
created: BasicTime.GMT,
attachedTo: Rope.Text
];
Scavenge:
PROC [vol: File.Volume] =
TRUSTED {
SaveAttachments:
UNSAFE
PROC [entry: BTree.Entry]
RETURNS [continue:
BOOL] =
UNCHECKED {
entryPtr: FSBackdoor.EntryPtr = LOOPHOLE[entry];
continue ← TRUE;
WITH e: entryPtr^
SELECT
FROM
attached => attHead ←
NEW [ AttachedEntry ←
[ attHead, FSBackdoor.TextFromTextRep[@entryPtr[e.nameBody]], e.version, e.keep, e.created, FSBackdoor.TextFromTextRep[@entryPtr[e.attachedTo]] ] ];
ENDCASE;
};
fakeUsedTime: BasicTime.
GMT = BasicTime.Update[BasicTime.Now[], -60*20];
a little earlier so used times always will be updated after scavenge
attHead: REF AttachedEntry ← NIL;
vDesc: FSFileOps.VolumeDesc = NEW [FSFileOps.VolumeDescObject];
fp: File.FP ← File.nullFP;
newFSBTree: File.Handle ← NIL;
oldFSBTree: File.Handle ← NIL;
vDesc.vol ← vol;
vDesc.tree ← AllocateBTree[];
oldFSBTree ← SetUpBTree[vDesc, TRUE];
[] ← BTree.SalvageEntries[vDesc.tree, SaveAttachments];
BTree.SetState[vDesc.tree, closed];
BTreeVM.FreeBuffers[vDesc.treeVM]; -- dump old buffers
vDesc.tree ← AllocateBTree[]; -- get a new BTree
newFSBTree ← SetUpBTree[vDesc, TRUE, TRUE];
OpenBTree[vDesc.tree, vDesc.treeVM, TRUE, TRUE]; -- initialize the BTree — this file is not the root file yet
BTree.SetUpdateInProgress[vDesc.tree, TRUE];
{
ENABLE
UNWIND => {
BTree.SetUpdateInProgress[vDesc.tree, FALSE];
BTreeVM.FreeBuffers[vDesc.treeVM] };
FOR attHead ← attHead, attHead.next
UNTIL attHead =
NIL
DO
IF NOT FSName.IsLocal[attHead.nameBody] THEN LOOP;
FSDir.UpdateAttachedEntry[vDesc, attHead.nameBody, attHead.version, attHead.keep, attHead.created, attHead.attachedTo, insertOrReplace];
ENDLOOP;
UNTIL (fp ← FileBackdoor.NextFile[vDesc.vol, fp]) = File.nullFP
DO
nameBody: Rope.Text;
version: FSBackdoor.Version;
h: File.Handle = OpenFile[vDesc.vol, fp
! FS.Error => LOOP ];
[nameBody, version] ← FSFileOps.GetNameBodyAndVersion[h
! FS.Error => IF error.code = $invalidPropertyPage THEN LOOP ];
IF Rope.Length[nameBody] = 0
THEN DeleteFile[h] -- creation of this file was not completed
ELSE {
put file in directory/cache
exists: BOOL ← FALSE;
IF FSName.IsLocal[nameBody]
THEN FSDir.UpdateLocalEntry[
vDesc, nameBody, version, FSFileOps.GetProps[h].keep, fp, insert
! BTree.Error => IF reason = wrongUpdateType THEN {exists ← TRUE; CONTINUE}
]
ELSE FSDir.UpdateCachedEntry[
vDesc, nameBody, version, fakeUsedTime, fp, insert
! BTree.Error => IF reason = wrongUpdateType THEN {exists ← TRUE; CONTINUE}
];
IF exists THEN DeleteFile[h];
};
ENDLOOP;
};
BTree.SetUpdateInProgress[vDesc.tree, FALSE];
FileBackdoor.SetRoot[client, newFSBTree ! File.Error => FSReport.FileError[why] ];
File.Delete[oldFSBTree ! File.Error => CONTINUE];
BTreeVM.FreeBuffers[vDesc.treeVM];
};
}.