<> <> <> 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, GetRoot, GetVolumeName, GetVolumePages, Handle, Info, NextFile, nullFP, Open, PageCount, PageNumber, Reason, SetFreeboard, SetRoot, 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 <> ScavengeDirectoryAndCache: PUBLIC PROC [volName: Rope.ROPE] = BEGIN Activate: ENTRY PROC = BEGIN ENABLE UNWIND => NULL; ActivateVolume[vDesc]; END; 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} ]; Activate[ ! 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; <> GetVolumeDesc: PUBLIC PROC [vName: Rope.ROPE] RETURNS [vDesc: FSFileOps.VolumeDesc] = BEGIN errorDesc: FS.ErrorDesc; vDesc _ InnerGetVolumeDesc[vName, FALSE ! FS.Error => {errorDesc _ error; CONTINUE} ]; IF errorDesc.group # ok THEN ERROR FS.Error[errorDesc]; END; LPCreatedTime: PUBLIC PROC [vol: File.Volume, fp: File.FP] RETURNS [BasicTime.GMT] = BEGIN handle: File.Handle; handle _ File.Open[ vol, fp ! File.Error => FSReport.FileError[why] ]; RETURN [ FSFileOps.GetProps[handle].created ]; END; OpenFile: PUBLIC PROC [vol: File.Volume, fp: File.FP] RETURNS [h: File.Handle] = BEGIN RETURN [ File.Open[ vol, fp ! File.Error => FSReport.FileError[why] ] ]; END; CreateFile: PUBLIC PROC [vol: File.Volume, pages: INT] RETURNS [fp: File.FP, h: File.Handle] = BEGIN ENABLE File.Error => FSReport.FileError[why]; h _ File.Create[vol, pages, FSFileOps.InitializePropertyStorage]; fp _ File.Info[h].fp; END; DeleteFile: PUBLIC PROC [h: File.Handle] = BEGIN File.Delete[ h ! File.Error => FSReport.FileError[why] ]; END; SetFilePages: PUBLIC PROC [h: File.Handle, pages: INT] = BEGIN File.SetSize[h, pages ! File.Error => FSReport.FileError[why] ]; END; GetFileInfo: PUBLIC PROC [h: File.Handle] RETURNS [pages: INT, fp: File.FP] = BEGIN [fp: fp, size: pages] _ File.Info[h ! File.Error => FSReport.FileError[why] ]; END; <> waitForRollback: BOOLEAN _ FALSE; rollback: CONDITION; svDesc, volumeDescList: FSFileOps.VolumeDesc _ NIL; InnerGetVolumeDesc: ENTRY PROC [vName: Rope.ROPE, suspendVolume: 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 suspendVolume THEN SuspendVolume[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 suspendVolume 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 suspendVolume THEN SuspendVolume[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 suspendVolume 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; SuspendVolume: 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; IF vDesc = svDesc -- turn off volume flusher if any THEN FSFileOps.RegisterVolumeFlusher[NIL]; END; ActivateVolume: INTERNAL PROC [vDesc: FSFileOps.VolumeDesc] = BEGIN SetUpBTree[vDesc ! FS.Error => DestroyVolumeDesc[vDesc] ]; IF vDesc = svDesc THEN FSFileOps.RegisterVolumeFlusher[svDesc]; 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 -- special steps for the system volume FSFileOps.RegisterVolumeFlusher[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 SuspendVolume[vDesc]; ENDLOOP; END; FSRollbackProc: ENTRY Booting.RollbackProc = BEGIN FOR vDesc: FSFileOps.VolumeDesc _ volumeDescList, vDesc.next UNTIL vDesc = NIL DO ActivateVolume[vDesc ! FS.Error => CONTINUE ]; 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; 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; 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 ! FS.Error => IF error.code = $badFP THEN 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 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, fakeUsedTime, 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; <> Booting.RegisterProcs[c: FSCheckpointProc, r: FSRollbackProc]; END.