-- file CoreCom.Mesa (See also Core*.mesa.)
-- Edited by Kierr, February 14, 1978 1:43 PM.
-- Converted to Mesa 5.0: March 26, 1979 2:07 PM
-- Edited by Levin, June 1, 1978 9:03 AM
-- CoreCom is stuff common to CoreSS & CoreIO.
-- This file is part of a set. See [IFS]<DMS>Overview.mesa for more information.
DIRECTORY
DiskKDDefs: FROM "DiskKDDefs",
BFSDefs: FROM "BFSDefs",
DiskDefs: FROM "DiskDefs",
SystemDefs: FROM "SystemDefs",
AltoFileDefs: FROM "AltoFileDefs",
crD: FROM "CoreDefs",
crID: FROM "CoreImpDefs",
gsD: FROM "GlobalStorageDefs",
ovD: FROM "OverviewDefs";
CoreCom: PROGRAM -- [6] Core Division --
IMPORTS DiskKDDefs, BFSDefs, DiskDefs, SystemDefs, gsD, ovD
EXPORTS crD, crID
SHARES crD = PUBLIC BEGIN
OPEN crD, crID;
-- Purpose: provides an MC style interface to files and raw storage/structures.
--Topology of this file:
--
Core: PROGRAM
--Attachment Department
--Distributed File Department
--
AltoCore: INTERNAL MODULE
--
VDATable: INTERNAL MODULE
--
FilenameCache: INTERNAL MODULE
--
FreePageMonitor: INTERNAL MODULE
---------------------------------------------------------------
-- Attachment Department of the Core Division. This is a stub; attachments will not be implemented initially.
-- Empty. See CoreSS.
-------------------------------------------
-- Distributed File Department of the Core Division. This is a mini file system for DMS. It uses several, and as yet unspecified, file servers. The name of the file server is encoded in the UFilename string (i.e. the DMS generalized filename).
--Empty.
----------------------------------------------

-- INTERNAL MODULE: AltoCore.Mesa
-- Last edited by Horning, December 20, 1977 10:49 AM.
-- Last edited by Kierr, February 8, 1978 11:37 AM.
-- Last edited by Wegbreit, November 29, 1977 3:34 PM.
-- This file is part of a set. See [IFS/MAXC]<DMS>OverviewDefs.mesa for more info.
-- Purpose: provides support for Alto file system in MC.
-- ErrorCodes from ovD:
ok: ovD.ErrorCode=ovD.ok;
diskError: ovD.ErrorCode=ovD.diskError;
diskCorrupted: ovD.ErrorCode=ovD.diskCorrupted;
diskFull: ovD.ErrorCode=ovD.diskFull;
fileInUse: ovD.ErrorCode=ovD.fileInUse;
illegalFilename: ovD.ErrorCode=ovD.illegalFilename;
fileNotFound: ovD.ErrorCode=ovD.fileNotFound;
-- Note: all of the following operations require a UFileHandle for a file which is in open state. They have no way of checking the validity of the UFileHandle. Terrible things will happen if they are given an invalid UFileHandle.
GetVDA: PROCEDURE [targetPage: AltoPageNumber, aFH: AltoFileHandle]
RETURNS [erc: ovD.ErrorCode, vDA: VDA] =
-- Get the VDA for the target page, either from the table or, if necessary, by reading the disk.
-- ErrorCodes: DiskError.
BEGIN
erc ← ok;
vDA ← TableGetVDA[aFH.vDATable, targetPage];
IF vDA = AltoFileDefs.fillinDA
THEN [erc, vDA] ← GetVDAByDiskReads[targetPage, aFH];
END; -- of GetVDA.
GetVDAByDiskReads: PROCEDURE [targetPage: AltoPageNumber, aFH: AltoFileHandle]
RETURNS [errorCode: ovD.ErrorCode, targetVDA: VDA]=
-- Reads the disk to find the vDA of targetPage. Fills in the vDATable along the way. Returns the targetVDA if successful.
-- Error Codes: diskError
BEGIN
i, goodVDAIndex: AltoPageNumber;
scratchBuffer: gsD.MemoryPagePtr;

GetVDAInChunk: PROCEDURE
[firstP, lastP: AltoPageNumber, aFH: AltoFileHandle, vDAPtr: VDAsPtr]
RETURNS [erc: ovD.ErrorCode] =
-- Globals from GetVDAByDiskReads: scratchBuffer
-- Reads the disk to get from firstP to lastP, filling in entries in the VDATable along the way. firstP, lastP must be existing pages in the file described by aFH. scratchBuffer is used to hold the unwanted data.
BEGIN
endPage: AltoPageNumber;
uDR: update DiskDefs.DiskRequest ←
[ca: scratchBuffer,
da: LOOPHOLE[vDAPtr, POINTER TO VDA],
firstPage: firstP,
lastPage: lastP,
fp: @aFH.handle.fp,
fixedCA: TRUE,
action: ReadD,
lastAction: ReadD,
signalCheckError: FALSE,
option: update[cleanup: BFSDefs.GetNextDA]];

erc ← ok;
[endPage, ] ← BFSDefs.ActOnPages[@uDR !
-- Catch phrases:
DiskDefs.UnrecoverableDiskError => BEGIN erc ← diskError; CONTINUE; END];
END; -- of GetVDAInChunk

-- Strategy: This implements only the strategy of going forward from largest known vDA which is smaller than targetPage. It might be profitable to later add a strategy of going backward from a known vDA.

-- Find the largest known vDA that is smaller than targetPage.
FOR i DECREASING IN [0 .. targetPage] DO
IF TableGetVDA[aFH.vDATable, i] # AltoFileDefs.fillinDA THEN GOTO found;
REPEAT
found => goodVDAIndex ← i;
FINISHED => ovD.SysBug[NIL];
ENDLOOP;

[scratchBuffer, errorCode] ← gsD.GetMemoryPages[1, IObuffer];
IF errorCode # ok THEN ovD.SysBug[NIL];
errorCode ← CallProcedureForChunks[goodVDAIndex, targetPage, aFH, GetVDAInChunk];
gsD.ReturnMemoryPages[1, IObuffer, scratchBuffer];
targetVDA ← TableGetVDA[aFH.vDATable, targetPage];
END; -- of GetVDAByDiskReads
MakeFileIndexCanonical: PROCEDURE [page: UPageNumber, byte: CARDINAL]
RETURNS [UPageNumber, CARDINAL[0..512)] =
-- All FileIndexes (e.g., page,byte) have to property that the corresponding CharIndex is (page*512 + byte). But canonical FileIndexes also have the property that byte is IN [0 .. 512).
BEGIN
delta: UPageNumber = byte/512;
RETURN [page+delta, (byte MOD 512)];
END; -- of MakeFileIndexCanonical.
MapUToAltoPage: PROCEDURE [page: UPageNumber]
RETURNS [AltoPageNumber] =
BEGIN
RETURN [page+1];
END; -- of MapUToAltoPage
MapAltoToUPage: PROCEDURE [page: AltoPageNumber]
RETURNS [UPageNumber] =
BEGIN
RETURN [page-1];
END; -- of MapAltoToUPage
-- END of AltoCore: INTERNAL MODULE;
-- VDATable: INTERNAL MODULE =
-- The vDA table is represented as a set of chunks, indexed by a descriptor block.
TableGetVDA: PROCEDURE [vDATable: VDATablePtr, page: AltoPageNumber]
RETURNS [vDA: VDA]=
-- Look in vDATable for page. Return the contents of the entry if present; otherwise, return fillinDA.
BEGIN
base: VDAsPtr;
base ← vDATable↑[page/chunkSpan]; -- Address of chunk.
vDA ← (IF base=NIL THEN AltoFileDefs.fillinDA ELSE base↑[page]);
END; -- of TableGetVDA
TablePutVDA: PROCEDURE
[vDATable: VDATablePtr, page: AltoPageNumber, vDA: VDA] =
-- Puts the pair (page, vDA) in vDATable. The auxilliary entries which overlap between chunks are also stored. Adds a new chunk if necessary.
BEGIN
firstInChunk, lastInChunk: AltoPageNumber;
vDAs, shadowVDAs: VDAsPtr;
cIndex: CARDINAL;

[vDAs, firstInChunk, lastInChunk, cIndex] ← GetVDAChunk[page, vDATable];
vDAs↑[page] ← vDA; -- First store in normal spot.
-- Now store in (any) auxilliary spot.
IF page IN [firstInChunk .. firstInChunk+upperPad) AND
(shadowVDAs←vDATable↑[cIndex-1])#NIL THEN
shadowVDAs↑[page] ← vDA; -- Store at end of previous chunk.
IF page IN (lastInChunk-lowerPad .. lastInChunk] AND
(shadowVDAs←vDATable↑[cIndex+1])#NIL THEN
shadowVDAs↑[page] ← vDA; -- Store at begining of next chunk.
END; -- of TablePutVDA
GetVDAChunk: PROCEDURE [page: AltoPageNumber, vDATable: VDATablePtr]
RETURNS [base: VDAsPtr, firstInChunk,lastInChunk: AltoPageNumber, chunkIndex: CARDINAL[0 .. numberOfChunks)] =
-- The chunk which contains "page" is returned: in the form address of the chunk, inclusive limits of the chunk, and index of the chunk. The extra array entries on either side of the chunk (as required by ActOnPages, etc.) are not included in the limits.
BEGIN
-- The chunk is allocated if need be. An allocated chunk is initialized to "fillinDA". Then if the previous and/or next chunks are there, the first upperPad and/or last lowerPad entries are initialized from them.

-- Logically, we should write: i: [FIRST[AltoPageNumber]-1 .. LAST[AltoPageNumber]+1];
-- but Mesa makes us write:
i: INTEGER;
prev, next: VDAsPtr; -- Before and after "base".

chunkIndex ← page/chunkSpan;
firstInChunk ← chunkIndex*chunkSpan;
lastInChunk ← firstInChunk+chunkSpan-1;
base ← vDATable↑[chunkIndex];
IF base=NIL THEN
BEGIN -- Allocate a new chunk.
base ← SystemDefs.AllocateHeapNode[chunkSize]-firstInChunk+lowerPad;
vDATable↑[chunkIndex] ← base;
-- The 3 FOR loops below cannot be written as "i IN [range]" because
-- Mesa can’t compile the proper condition tests. This is because AltoPageNumber
-- is really [0..77777B], and hence i is [177777B..100000B], and neither a signed nor
-- an unsigned test will work for both end-conditions. Sigh..... (RL)
FOR i ← firstInChunk-lowerPad, i+1 UNTIL i = lastInChunk+upperPad+1 DO
base↑[i] ← AltoFileDefs.fillinDA; ENDLOOP;
IF (prev←vDATable↑[chunkIndex-1])#NIL THEN
FOR i ← firstInChunk-lowerPad, i+1 UNTIL i = firstInChunk+upperPad DO
base↑[i] ← prev↑[i]; ENDLOOP;
IF (next←vDATable↑[chunkIndex+1])#NIL THEN
FOR i ← lastInChunk-lowerPad+1, i+1 UNTIL i = lastInChunk+upperPad+1 DO
base↑[i] ← next↑[i]; ENDLOOP;
END; -- of Allocate a new chunk.
END; -- of GetVDAChunk.
CallProcedureForChunks: PROCEDURE
[firstPage, lastPage: AltoPageNumber,
aFH: AltoFileHandle,
proc: PROCEDURE
[firstP, lastP: AltoPageNumber, aFH: AltoFileHandle, vDAPtr: VDAsPtr]
RETURNS [erc: ovD.ErrorCode] ]
RETURNS [erc: ovD.ErrorCode] =
-- The proc is called for each included chunk of the vDA array, unless it asks to be stopped via returning a non-ok ErrorCode. At each call to "proc", the vDAs array and its limits are given as args.
-- The ErrorCode returned is the one returned by proc.
BEGIN
-- Logically, we should write: i: [FIRST[AltoPageNumber]-1 .. LAST[AltoPageNumber]+1];
-- but Mesa makes us write:
i: INTEGER;
page, firstInChunk, lastInChunk, firstP, lastP: AltoPageNumber;
vDAs, shadowVDAs: VDAsPtr;
cIndex: CARDINAL[0..numberOfChunks);

erc←ok; -- Assume ok, even for empty loop, i.e. lastPage > firstPage.
page ← firstPage;
UNTIL page > lastPage DO
[vDAs, firstInChunk, lastInChunk, cIndex] ← GetVDAChunk[page, aFH.vDATable];
firstP ← MAX[page,firstInChunk];
lastP ← MIN[lastPage,lastInChunk];

erc ← proc[firstP, lastP, aFH, vDAs];

IF erc # ok THEN EXIT;

-- Propagate possible new information from the ends of this
-- chunk to the the shadow values in the previous and next
-- chunks.
--
-- Note: The limits on the FOR loops below are tricky. They
--assume several things about the world. (1) It is always safe
--to copy VDA’s into adjacent chunks, e.g. you never end up
--copying fillinDA on top of a real VDA. (2) The upperPad and
--lowerPad are >= 1. The reasons why first-1 (rather than
--firstInChunk-lowerPad), in the first FOR, and lastP+1
--(rather than lastInChunk+1), in the second FOR, are used as
--limits are (a) Why bother to copy data that hasn’t been
--touched? and (b) Why bother to do the FOR loop at all if
--nothing on this end of the chunk has been touched?. (3) The
--lowest VDA that can be changed in a call to a chunk proc
--is one below the range (i.e. firstP-1), and the highest is
--one above (i.e. lastP+1).
-- I feel somewhat shakey about assumption 1. In case of bugs
--I feel it better to enforce the assumption than to relax
--it, but a final determination will depend upon the bug.
--/Robert
--
--All of the above is reasonable, as far as I can tell, but we still have
--the CARDINAL/INTEGER problem for the loop index. Unfortunately,
-- we can’t fix it with the same hack used in GetVDAChunk, since these
-- loops may need to be executed zero times. I’ve been forced to include an
-- explicit check for the this case.
--Roy
--
IF (shadowVDAs←aFH.vDATable↑[cIndex-1])#NIL AND
firstP <= AltoPageNumber[firstInChunk+upperPad] THEN -- copy.
FOR i ← firstP-1, i+1 UNTIL i = firstInChunk+upperPad DO
shadowVDAs↑[i]←vDAs↑[i]; ENDLOOP;
IF (shadowVDAs←aFH.vDATable↑[cIndex+1])#NIL AND
AltoPageNumber[lastInChunk-lowerPad] <= lastP THEN -- copy.
FOR i ← lastInChunk-lowerPad+1, i+1 UNTIL i = lastP+1+1 DO
shadowVDAs↑[i]←vDAs↑[i]; ENDLOOP;

page ← lastInChunk+1;
ENDLOOP;
END; -- of CallProcedureForChunks.
-- END of VDATable: INTERNAL MODULE;
-- FilenameCache: INTERNAL MODULE =
-- Filenames and their associated Mesa FileHandles are cached for faster opening of a file that has been opened previously or inserted in the cache at startup time.
-- Empty. See CoreSS.
-- END of FilenameCache: INTERNAL MODULE;
----------------------------------------------
-- FreePageMonitor: INTERNAL MODULE;
-- This module is responsible for maintaining a free page count and announcing changes to the client.
-- Last edited by Kierr, January 27, 1978 11:57 AM.
-- Some stoarge for this module (FPC=FreePageCount):
currentFPC, previousFPC: CARDINAL;
notifyWithFPC: PROCEDURE [CARDINAL];
notifyWithFPCIsArmed: BOOLEAN←FALSE;
SetAltoFreePageMonitor: PROCEDURE [proc: PROCEDURE [count: CARDINAL]]=
-- The arg, proc, is called immediately and whenever the tally of Alto free pages changes. (Note that this tally is only a hint.) To inhibit the function, call InhibitAltoFreePageMonitor.
BEGIN
notifyWithFPC←proc;
notifyWithFPCIsArmed←TRUE;
currentFPC←previousFPC←CountAltoFreePages[];
notifyWithFPC[currentFPC];
END; -- of SetAltoFreePageMonitor.
InhibitAltoFreePageMonitor: PROCEDURE=
-- Any free page monitoring set up by SetAltoFreePageMonitor is discontinued.
BEGIN
notifyWithFPCIsArmed←FALSE;
END; -- of InhibitAltoFreePageMonitor.
CountAltoFreePages: PROCEDURE RETURNS [freePages: CARDINAL]=
-- Returns the number of pages available on the Alto dsik pack. This number is a real count, not just a hint.
BEGIN
freePages←DiskKDDefs.CountFreeDiskPages[];
END; -- of AltoFreePageCount.
UpdateFreePageCount: PROCEDURE =
-- This is called internally to set up (and announce if necessary and enabled) the free page tally by actually counting whenever currentFPC might be wrong.
BEGIN
currentFPC←CountAltoFreePages[];
BroadcastFreePageCount[];
END; -- of UpdateFreePageCount.
DecrementFreePageCount: PROCEDURE [deltaFPC: CARDINAL] =
-- This is called internally to change (and announce if necessary and enabled) the free page tally by subtracting deltaFPC from the current total.
BEGIN
currentFPC←currentFPC-deltaFPC;
BroadcastFreePageCount[];
END; -- of DecrementFreePageCount.
BroadcastFreePageCount: PRIVATE PROCEDURE =
-- If the FPC has changed, the client is notified through his FreePageMonitor procedure set up by SetAltoFreePageMonitor.
BEGIN
IF currentFPC#previousFPC AND notifyWithFPCIsArmed THEN
notifyWithFPC[previousFPC←currentFPC];
END; -- of BroadcastFreePageCount.
-- END of FreePageMonitor: INTERNAL MODULE;
END.; -- of CoreCom implementation module.