DIRECTORY
BasicTime USING [GMT, Now],
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, GetRoot, GetVolumeName, GetVolumePages, Handle, Info, NextFile, nullFP, Open, PageCount, PageNumber, Reason, SetFreeboard, SetRoot, SetFlusher, SetSize, SystemVolume, Volume, VolumeID],
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, FS, FSBackdoor, FSDir, FSFileOps, FSName, FSReport, Rope
EXPORTS FSBackdoor, FSFileOps
= BEGIN
Exported to FSBackdoor
ScavengeDirectoryAndCache:
PUBLIC
PROC [volName: Rope.
ROPE] =
BEGIN
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 BEGIN
Scavenge[ vDesc.vol
! FS.Error => {errorDesc ← error; CONTINUE} ];
Unsuspend[ vDesc
! FS.Error => {IF errorDesc.group = ok THEN errorDesc ← error; CONTINUE} ];
END;
Booting.Deregister[c: RejectCheckpoint];
IF errorDesc.group # ok THEN ERROR FS.Error[errorDesc];
END;
SetFreeboard:
PUBLIC
PROC [freeboard:
INT] =
BEGIN
vDesc: FSFileOps.VolumeDesc = GetVolumeDesc[NIL];
IF vDesc = NIL THEN FSReport.UnknownVolume[NIL];
File.SetFreeboard[ vDesc.vol, freeboard
! File.Error => FSReport.FileError[why] ];
END;
VolumePages:
PUBLIC
PROC [volName: Rope.
ROPE ←
NIL]
RETURNS [size, free, freeboard:
INT] =
BEGIN
vDesc: FSFileOps.VolumeDesc = GetVolumeDesc[volName];
IF vDesc = NIL THEN FSReport.UnknownVolume[NIL];
[size, free, freeboard] ← File.GetVolumePages[ vDesc.vol
! File.Error => FSReport.FileError[why] ];
END;
FNameFromHandle:
PUBLIC
PROC [file: File.Handle]
RETURNS [Rope.
ROPE] =
BEGIN
nameBody, prefix: Rope.ROPE;
version: FSBackdoor.Version;
BEGIN
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], ">"];
END;
[nameBody, version] ← FSFileOps.GetNameBodyAndVersion[file];
RETURN [ FSBackdoor.MakeFName[nameBody, version, prefix] ];
END;
CloseVolume:
PUBLIC
ENTRY
PROC [v: File.Volume] =
BEGIN
prev, vDesc: FSFileOps.VolumeDesc ← NIL;
FOR vDesc ← volumeDescList, vDesc.next
UNTIL vDesc =
NIL
DO
IF vDesc.vol = v
THEN
BEGIN
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
BEGIN
BTreeVM.FreeBuffers[vDesc.treeVM];
vDesc.treeVM ← NIL;
END;
EXIT;
END;
prev ← vDesc;
ENDLOOP;
END;
Internal procedures
waitForRollback: BOOLEAN ← FALSE;
rollback: CONDITION;
svDesc, volumeDescList: FSFileOps.VolumeDesc ← NIL;
InnerGetVolumeDesc:
ENTRY
PROC [vName: Rope.
ROPE, suspendTree:
BOOLEAN]
RETURNS [FSFileOps.VolumeDesc] =
BEGIN ENABLE UNWIND => NULL;
vDesc: FSFileOps.VolumeDesc;
vol: File.Volume ← NIL;
WHILE waitForRollback
DO
WAIT rollback ENDLOOP; -- can't make new ones now, so don't even look
IF Rope.Length[vName] = 0 AND svDesc # NIL
THEN
BEGIN
-- want system volume and already have descriptor
IF suspendTree THEN Suspend[svDesc];
RETURN [svDesc];
END;
SELECT
TRUE
FROM
Rope.Length[vName] = 0 =>
BEGIN
-- system volume
vol ← File.SystemVolume[];
IF vol = NIL
THEN
BEGIN
-- no system volume is available
IF suspendTree
THEN FSReport.UnknownVolume[NIL]
ELSE RETURN [NIL];
END;
vName ← File.GetVolumeName[vol];
END;
(Rope.Fetch[vName, 0] = '#) =>
BEGIN
-- volume number
vol ← File.FindVolumeFromID[ MakeVolumeID[vName] ];
IF vol = NIL THEN FSReport.UnknownVolume[vName];
vName ← File.GetVolumeName[vol];
END;
ENDCASE;
FOR vDesc ← volumeDescList, vDesc.next
UNTIL vDesc =
NIL
DO
IF Rope.Equal[vName, vDesc.vName, FALSE]
THEN
BEGIN
-- found VolumeDesc in list
IF suspendTree THEN Suspend[vDesc];
RETURN [vDesc];
END;
ENDLOOP;
IF vol = NIL
THEN
BEGIN
vol ← File.FindVolumeFromName[vName];
IF vol = NIL THEN FSReport.UnknownVolume[vName];
END;
vDesc ← NEW[ FSFileOps.VolumeDescObject ← [ NIL, vName, NIL, vol, NIL, NIL ] ];
vDesc.tree ← AllocateBTree[];
IF NOT suspendTree
THEN SetUpBTree[vDesc];
IF vol = File.SystemVolume[]
THEN
BEGIN
FSFileOps.RegisterVolumeFlusher[vDesc];
svDesc ← vDesc;
END
ELSE vDesc.prefix ← Rope.Cat["[]<", vName, ">"];
vDesc.next ← volumeDescList;
volumeDescList ← vDesc;
RETURN [vDesc];
END;
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] =
BEGIN
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
] ];
END;
SetUpBTree:
PROC [vDesc: FSFileOps.VolumeDesc, scavenge:
BOOLEAN ←
FALSE] =
BEGIN
newFile: BOOLEAN ← FALSE;
treeFile: File.Handle;
treeFP: File.FP;
treeVM: BTreeVM.Handle;
treeFP ← File.GetRoot[vDesc.vol, client
! File.Error =>
IF why = nonCedarVolume
THEN FSBackdoor.ProduceError[nonCedarVolume, Rope.Cat["The local volume \"", vDesc.vName, "\" is not formatted for Cedar."]]
ELSE FSReport.FileError[why]
].fp;
IF treeFP = File.nullFP
THEN
BEGIN
-- 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;
END
ELSE treeFile ← OpenFile[vDesc.vol, treeFP];
treeVM ← BTreeVM.Open[
file: treeFile,
filePagesPerPage: bufferSize,
cacheSize: buffers,
base: basePage
];
BEGIN
ENABLE
UNWIND =>
BEGIN
BTreeVM.FreeBuffers[treeVM];
IF newFile THEN DeleteFile[treeFile ! FS.Error => CONTINUE];
END;
OpenBTree[vDesc.tree, treeVM, newFile, scavenge];
IF newFile
THEN File.SetRoot[client, treeFile
! File.Error => FSReport.FileError[why] ];
END;
vDesc.treeVM ← treeVM;
END;
OpenBTree:
PROC [tree: BTree.Tree, treeVM: BTreeVM.Handle, new, scavenge:
BOOLEAN] =
BEGIN
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 discovered to be in progress when opening the directory/cache BTree."];
BTree.Error =>
FSBackdoor.ProduceError[badBTree, "Error from the BTree package when opening the directory/cache BTree."];
File.Error =>
FSReport.FileError[why];
];
END;
MakeVolumeID:
PROC [vName: Rope.
ROPE]
RETURNS [id: File.VolumeID] =
BEGIN
FSBackdoor.ProduceError[notImplemented, "Can't handle hex volume ID's yet."];
END;
Suspend:
INTERNAL
PROC [vDesc: FSFileOps.VolumeDesc] =
BEGIN -- no SIGNALs or ERRORs
BTree.SetState[vDesc.tree, suspended]; -- may wait forever
IF vDesc.treeVM # NIL
THEN
BEGIN
BTreeVM.FreeBuffers[vDesc.treeVM];
vDesc.treeVM ← NIL;
END;
END;
Unsuspend:
ENTRY PROC [vDesc: FSFileOps.VolumeDesc] =
BEGIN ENABLE UNWIND => NULL;
SetUpBTree[vDesc
! FS.Error => DestroyVolumeDesc[vDesc]
];
END;
DestroyVolumeDesc:
INTERNAL
PROC [victim: FSFileOps.VolumeDesc] =
BEGIN -- 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;
BTree.SetState[victim.tree, closed];
IF prev = NIL
THEN volumeDescList ← victim.next
ELSE prev.next ← victim.next;
IF victim = svDesc
THEN
BEGIN
File.SetFlusher[svDesc.vol, NIL, NIL];
svDesc ← NIL;
END;
END;
RejectCheckpoint: Booting.CheckpointProc =
{ rejection ← "FS scavenge is in progress." };
FSCheckpointProc:
ENTRY Booting.CheckpointProc =
BEGIN
waitForRollback ← TRUE;
FOR vDesc: FSFileOps.VolumeDesc ← volumeDescList, vDesc.next
UNTIL vDesc =
NIL
DO
Suspend[vDesc];
ENDLOOP;
END;
FSRollbackProc:
ENTRY Booting.RollbackProc =
BEGIN
FOR vDesc: FSFileOps.VolumeDesc ← volumeDescList, vDesc.next
UNTIL vDesc =
NIL
DO
SetUpBTree[vDesc
! FS.Error => { DestroyVolumeDesc[vDesc]; LOOP }
];
ENDLOOP;
waitForRollback ← FALSE;
BROADCAST rollback;
END;
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
BEGIN
SaveAttachments:
UNSAFE
PROC [entry: BTree.Entry]
RETURNS [continue:
BOOLEAN] =
UNCHECKED
BEGIN
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;
END;
attHead: REF AttachedEntry ← NIL;
vDesc: FSFileOps.VolumeDesc = NEW [FSFileOps.VolumeDescObject];
fp: File.FP ← File.nullFP;
vDesc.vol ← vol;
vDesc.tree ← AllocateBTree[];
SetUpBTree[vDesc, TRUE];
[] ← BTree.SalvageEntries[vDesc.tree, SaveAttachments];
OpenBTree[vDesc.tree, vDesc.treeVM, TRUE, TRUE]; -- reinitialize the BTree
BTree.SetUpdateInProgress[vDesc.tree, TRUE];
BEGIN
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 ← File.NextFile[vDesc.vol, fp]) = File.nullFP
DO
nameBody: Rope.Text;
version: FSBackdoor.Version;
h: File.Handle = OpenFile[vDesc.vol, fp];
[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
BEGIN
-- put file in directory/cache
exists: BOOLEAN ← 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, BasicTime.Now[], fp, insert
! BTree.Error => IF reason = wrongUpdateType THEN {exists ← TRUE; CONTINUE} ];
IF exists THEN DeleteFile[h];
END;
ENDLOOP;
END;
BTree.SetUpdateInProgress[vDesc.tree, FALSE];
BTreeVM.FreeBuffers[vDesc.treeVM];
END;