-- file CoreIO.Mesa (See also Core*.mesa.)
-- edited by Kierr, May 3, 1978 5:24 PM.
-- Converted to Mesa 5.0: March 26, 1979 6:07 PM.
-- CoreIO is the read/write portion of Core.
-- 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",
SegmentDefs: FROM "SegmentDefs",
crD: FROM "CoreDefs",
crID: FROM "CoreImpDefs",
gsD: FROM "GlobalStorageDefs",
ovD: FROM "OverviewDefs";
CoreIO: PROGRAM -- [6] Core Division --
IMPORTS DiskKDDefs, BFSDefs, DiskDefs, SystemDefs, SegmentDefs, gsD, ovD, crID
EXPORTS crD, crID --Alto[Read/Write]Pages--
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).
-- (Not Implemented) ReadBytes: PROCEDURE [buffer: POINTER, count: CARDINAL, fh: UFileHandle] RETURNS [CARDINAL] SIGNALS [EOF];
-- Reads data from the file. Possible errors: "File read error", "Bad handle", "File server died".
-- (Not Implemented) WriteBytes: PROCEDURE [buffer: POINTER, count: CARDINAL, fh: UFileHandle];
-- Write data to the file. Possible errors: "Bad handle", "File server died".
ReadPages: PROCEDURE [buffer: gsD.MemoryPagePtr, byteCount: CARDINAL, pageNumber: PageNumber, uFH: UFileHandle]
RETURNS [erc: ovD.ErrorCode, bytesRead: CARDINAL]=
-- Reads up to "byteCount" bytes of data from the file asscoiated with uFH beginning with page "pageNumber" into the memory page(s) beginning at "buffer". The byteCount must be a multiple of 512 (an even page). The bytesRead returned is the count of bytes actually read and will be equal to the requested byteCount except at EOF, when it may be less. (Note that if pageNumber is beyound the end-of-file then bytesRead=0 is returned.)
-- Error Codes: (##DO: Update)diskError.
BEGIN
[erc, bytesRead] ← uFH.read[buffer, byteCount, pageNumber, uFH];
END; -- of ReadPages.
WritePages: PROCEDURE [buffer: gsD.MemoryPagePtr, byteCount: CARDINAL, pageNumber: PageNumber, uFH: UFileHandle]
RETURNS [ovD.ErrorCode]=
-- Writes byteCount of data from memory, beginning at buffer, to pageNumber of the file uFH. The operation is legal only if the file was opened in update OpenMode. This can be used to extend the file’s size by writing beyond the current end. (Note, however, that the file is never shortened.) The byteCount must always be a multiple of 512, an even page, except on the last page of the file.
-- Regarding byteCount, let "remainder" be (byteCount MOD 512). If non-zero, remainder denotes the number of bytes supplied by the client to be written on the last page of the current transaction. The number of byte actually written will be the MAX[current-bytes-on-that-file-page, remainder]; the value of all bytes beyond remainder are indeterminate.
-- Side effects: Updates the file size in the uFH as needed.
-- Error Codes: (##DO: Update), diskError.
BEGIN
RETURN [uFH.write[buffer, byteCount, pageNumber, uFH]];
END; -- of WritePages.
----------------------------------------------
-- AltoCore: INTERNAL MODULE =
-- Purpose: provides support for Alto file system in MC.
-- This file is part of a set. See [IFS/MAXC]<DMS>OverviewDefs.mesa for more info.
-- 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.
AltoReadPages: PROCEDURE
[buffer: gsD.MemoryPagePtr, byteCount: CARDINAL,
pageNumber: UPageNumber, uFH: UFileHandle]
RETURNS [erc: ovD.ErrorCode, bytesRead: CARDINAL]=
-- Reads up to "byteCount" bytes of data from the file asscoiated with uFH beginning with page "pageNumber" into the memory page(s) beginning at "buffer". The byteCount must be a multiple of 512 (an even page). The bytesRead returned is the count of bytes actually read and will be equal to the requested byteCount except at EOF, when it may be less. (Note that if pageNumber is beyound the end-of-file then bytesRead=0 is returned.)
-- Error Codes: diskError.
BEGIN
firstPage: AltoPageNumber = MapUToAltoPage[pageNumber];
lastPage: AltoPageNumber = firstPage+(byteCount/512)-1;
endPage: AltoPageNumber;
numBytes: CARDINAL[0..512];
aFH:AltoFileHandle;

WITH fh:uFH↑ SELECT FROM
alto => aFH←@fh;
ENDCASE => ovD.SysBug[NIL];

IF pageNumber > aFH.lastFilePage -- Past EOF.
OR firstPage > lastPage -- Reading less than a page.
THEN RETURN[ovD.ok, 0];
[erc, endPage, numBytes] ← AltoTransferExistingPages
[bufferAddress: buffer, firstPage: firstPage,
lastPage: lastPage, aFH: aFH, action: ReadD];
bytesRead ← (endPage-firstPage)*512 + numBytes;
END; -- of AltoReadPages
AltoWritePages: PROCEDURE [buffer: gsD.MemoryPagePtr, byteCount: CARDINAL,
pageNumber: UPageNumber, uFH: UFileHandle]
RETURNS [erc: ovD.ErrorCode]=
-- Writes byteCount of data from memory, beginning at buffer, to pageNumber of the file aFH. The operation is legal only if the file was opened in update OpenMode. This can be used to extend the file’s size by writing beyond the current end. (Note, however, that the file is never shortened.) The byteCount must always be a multiple of 512, an even page, except on the last page of the file. And it cannot be 0.
-- Regarding byteCount, let "remainder" be (byteCount MOD 512). If non-zero, remainder denotes the number of bytes supplied by the client to be written on the last page of the current transaction. The number of byte actually written will be the MAX[current-bytes-on-that-file-page, remainder]; the value of all bytes beyond remainder are indeterminate.
-- Side effects: Updates the file size in the uFH as needed.
-- Error Codes: diskFull, diskError, fileTooBig.
BEGIN
smallCAs: ARRAY [0..2) OF gsD.MemoryPagePtr;
cAs: CAsPtr;
scratchBufferPtr, zeroedBufferPtr, bigCAsPtr: POINTER ← NIL;
lastFilePage: AltoPageNumber = MapUToAltoPage[uFH.lastFilePage];
firstPage: AltoPageNumber = MapUToAltoPage[pageNumber];
lastPage: AltoPageNumber ← firstPage+(byteCount+511)/512-1;
bytesRequestedForLastPage: CARDINAL; -- [1..512]
bytesForLastPage: CARDINAL [0..512];
pageCount: CARDINAL;
i: AltoPageNumber;
j: CARDINAL[0..512);
aFH: AltoFileHandle;

WITH fh: uFH↑ SELECT FROM
alto => aFH ← @fh;
ENDCASE => ovD.SysBug[NIL];

BEGIN -- this is a "Block for EXITS"

[i,] ← MakeFileIndexCanonical[firstPage, byteCount]; -- Calculate new last page.
IF i>maxVDAIndex THEN RETURN [ovD.fileTooBig]; -- Making file too long.
IF byteCount=0 -- Bad arg.
OR aFH.access#update -- Read-only.
THEN ovD.SysBug[NIL];

-- Part 1: Re-write previously existing full pages, i.e. the
--
possibly empty interval [firstPage .. lastFilePage).

IF firstPage < lastFilePage THEN
BEGIN
[erc, , ] ← AltoTransferExistingPages
[bufferAddress: buffer,
firstPage: firstPage,
lastPage: MIN[lastPage, lastFilePage-1],
aFH: aFH,
action: WriteD];
IF erc # ok -- Error.
OR lastPage < lastFilePage -- All done.
THEN RETURN;
END;


-- Part 2: Write the last file page and any new file pages,
--
i.e. the possibly empty interval [lastFilePage .. lastPage].
--
Construct an array of buffers for the transaction. Then
--
use WriteAtFileEnd to effect the transfers.

bytesRequestedForLastPage ← ((byteCount+511) MOD 512)+1;
bytesForLastPage ← IF lastPage = lastFilePage
THEN MAX[bytesRequestedForLastPage, uFH.byteFF]
ELSE bytesRequestedForLastPage;
IF bytesForLastPage = 512 THEN
-- Set-up to write an extra empty page at the end.
BEGIN lastPage ← lastPage+1; bytesForLastPage ← 0; END;
pageCount ← (lastPage+1)-lastFilePage;
cAs ← IF pageCount <= LENGTH[smallCAs]
THEN LOOPHOLE[@smallCAs - lastFilePage]
ELSE LOOPHOLE[(bigCAsPtr ← SystemDefs.AllocateHeapNode[pageCount])-lastFilePage];

IF firstPage > lastFilePage THEN
BEGIN
-- Client is skipping over a hole. We should write zeros there.

-- Prepare pointer (i.e. cAs↑[lastFilePage]) with which to
--overwrite current last file page. Depending on the byte
--count (aFH.byteFF) we will either read & re-write the last
--file page or just write a page of zeros.
IF aFH.byteFF > 0 THEN
BEGIN-- We have no good data for the current last file page,
-- but there is good data there now, so preserve the
-- current data for the write.
cAs↑[lastFilePage] ←
scratchBufferPtr ← GetPageBuffer[zeroOutData: FALSE];
[erc,,] ← AltoTransferExistingPages[
bufferAddress: scratchBufferPtr,
firstPage: lastFilePage, lastPage: lastFilePage,
aFH: aFH, action: ReadD];
IF erc # ok THEN GOTO leaving; -- Disk problems.
END -- of aFH.byteFF > 0.
ELSE -- i.e. aFH.byteFF = 0.
cAs↑[lastFilePage] ← zeroedBufferPtr ←
GetPageBuffer[zeroOutData: TRUE]; -- Use zeroed data.

-- Prepare pointers for writing all pages beyound current last
--file page.
-- Set up pointers to zeroed data.
FOR i IN [lastFilePage+1 .. firstPage-1] DO
IF zeroedBufferPtr = NIL THEN
zeroedBufferPtr ←
GetPageBuffer[zeroOutData: TRUE]; -- Use zeroed data.
cAs↑[i] ← zeroedBufferPtr; ENDLOOP;
-- Set up pointers to clients data.
FOR i IN [firstPage .. lastPage] DO
cAs↑[i] ← buffer + 256*(i-firstPage); ENDLOOP;
END -- of firstPage > lastFilePage.

ELSE BEGIN -- i.e. firstPage <= lastFilePage (i.e., no filler needed)
-- Set up pointers to clients data.
FOR i IN [lastFilePage .. lastPage] DO
cAs↑[i] ← buffer + 256*(i-firstPage); ENDLOOP;
END;

[erc, i, j] ← WriteAtFileEnd[cAs, pageCount, bytesForLastPage, aFH];
IF (i > lastFilePage OR (i = lastFilePage AND j # aFH.byteFF)) THEN
BEGIN -- File length change.
aFH.lastFilePage ← MapAltoToUPage[i];
aFH.byteFF ← j;
END;
GOTO leaving;

EXITS
leaving => BEGIN -- of cleanup for return.
IF bigCAsPtr # NIL THEN SystemDefs.FreeHeapNode[bigCAsPtr];
IF scratchBufferPtr # NIL THEN
gsD.ReturnMemoryPages[1, IObuffer, scratchBufferPtr];
IF zeroedBufferPtr # NIL THEN
gsD.ReturnMemoryPages[1, IObuffer, zeroedBufferPtr];
END; -- of cleanup;
END; -- of "Block for EXITS".
END; -- of AltoWritePages.
GetPageBuffer: PROCEDURE [zeroOutData: BOOLEAN] RETURNS
[p: gsD.MemoryPagePtr] = BEGIN
j: CARDINAL; erc: ovD.ErrorCode;
[p, erc] ← gsD.GetMemoryPages[1, IObuffer];
IF erc # ok THEN ovD.SysBug[NIL]; -- Out of space!
IF zeroOutData THEN
FOR j IN [0..256) DO (p+j)↑←0; ENDLOOP;
END; -- of GetPageBuffer.
AltoTransferExistingPages: PROCEDURE
[bufferAddress: gsD.MemoryPagePtr, firstPage, lastPage: AltoPageNumber,
aFH: AltoFileHandle, action: AltoFileDefs.vDC]
RETURNS [errorCode: ovD.ErrorCode, endPage: AltoPageNumber, numBytes: [0 .. 512] ]=
-- Read/write a set of existing page to/from bufferAddress in memory, starting with firstPage. firstPage MUST be part of the file. If lastPage is off the end of the file, then page transfers will stop on end of file.
-- If errorCode = ok, then returns the endPage actually transfered and numBytes on the end page.
-- ErrorCodes: DiskError.
BEGIN
-- Method: Find firstPageVDA. Then use SwapPages to effect the transfer. Then record the VDA of the last page transfered and those on either side. Note that SwapPages does not use the vDATable, so the chunk mechanism (used elsewhere in this module) is bypassed.

sDR: swap DiskDefs.DiskRequest;
firstPageVDA: VDA;
diskPageDesc: DiskDefs.DiskPageDesc;

[errorCode, firstPageVDA] ← GetVDA[firstPage, aFH];
IF errorCode # ok THEN RETURN;

sDR ← [
ca: bufferAddress,
da: @firstPageVDA,
firstPage: firstPage,
lastPage: lastPage,
fp: @aFH.handle.fp,
fixedCA: FALSE,
action: action,
lastAction: action,
signalCheckError: FALSE,
option: swap [desc: @diskPageDesc]];

errorCode ← ok;
[endPage,numBytes] ←
DiskDefs.SwapPages[@sDR ! DiskDefs.UnrecoverableDiskError =>
BEGIN errorCode ← diskError; CONTINUE; END];

IF errorCode = ok THEN
BEGIN
TablePutVDA[aFH.vDATable, sDR.desc.page-1, sDR.desc.prev];
TablePutVDA[aFH.vDATable, sDR.desc.page, sDR.desc.this];
TablePutVDA[aFH.vDATable, sDR.desc.page+1, sDR.desc.next];
END;
END; -- of AltoTransferExistingPages
WriteAtFileEnd: PROCEDURE
[cAs: CAsPtr, itemCount: CARDINAL, bytesInLastPage: CARDINAL[0..512),
aFH: AltoFileHandle]
RETURNS [erc: ovD.ErrorCode, lastPageWritten: AltoPageNumber,
lastBytes: CARDINAL[0..512)] =
-- Write from memory buffers to file, starting with the last file page. cAs is the address of an array of pointers to memory buffers. itemCount is the number of buffers to be written.
-- ErrorCodes: diskFull, diskError ## check for more & implications..
BEGIN
upDR: update DiskDefs.DiskRequest;
extDR: extend DiskDefs.DiskRequest ← [ca: cAs, da: , firstPage: , lastPage: ,
fp: @aFH.handle.fp, fixedCA: FALSE,
action: WriteD, lastAction: WriteD, signalCheckError: FALSE,
option: extend[lastBytes: ]];
oldLastPage: AltoPageNumber = MapUToAltoPage[aFH.lastFilePage];
newLastPage: AltoPageNumber = oldLastPage+itemCount-1; -- Limit of write.

AssignPagesInChunk: PROCEDURE
[firstP, lastP: AltoPageNumber, aFH: AltoFileHandle, vDAPtr: VDAsPtr]
RETURNS [erc: ovD.ErrorCode] =
-- Called to get new pages assigned at file’s end. Uses the following variables from WriteAtFileEnd: upDR, oldLastPage.
BEGIN p: AltoPageNumber; vDA: VDA;
erc ← ok; -- Assume the best.
upDR.da ← LOOPHOLE[vDAPtr, POINTER TO VDA];
upDR.firstPage ← firstP; upDR.lastPage ← lastP;
BFSDefs.AssignPages[@upDR !
-- Catch phrases:
DiskKDDefs.DiskFull => BEGIN -- Oops, undo (any) assigned.
erc←diskFull;
FOR p IN [oldLastPage+1 .. lastP] DO
vDA←TableGetVDA[aFH.vDATable, p]; --Can’t use vDAPtr.
IF vDA=AltoFileDefs.fillinDA OR vDA=AltoFileDefs.eofDA THEN
EXIT;
DiskKDDefs.ReleaseDiskPage[vDA];
ENDLOOP;
CONTINUE; END;
DiskDefs.UnrecoverableDiskError => BEGIN erc←diskError; CONTINUE; END;
SegmentDefs.InsufficientVM => ovD.SysBug[NIL] -- Need memory.
];
RETURN[erc];
END; -- of AssignPagesInChunk.


RewritePagesInChunk: PROCEDURE
[firstP, lastP: AltoPageNumber, aFH: AltoFileHandle, vDAPtr: VDAsPtr]
RETURNS [erc: ovD.ErrorCode] =
-- Called to write existing but not yet written pages for a chunk of the VDAs. Uses the following variables from WriteAtFileEnd: extDR, newLastPage, bytesInLastPage, lastPageWritten, lastBytes.
BEGIN erc ← ok;
extDR.da ← LOOPHOLE[vDAPtr, POINTER TO VDA];
extDR.firstPage ← firstP; extDR.lastPage ← lastP;
extDR.lastBytes ← IF lastP=newLastPage THEN bytesInLastPage ELSE 512;
[lastPageWritten, lastBytes] ← BFSDefs.RewritePages[@extDR !
-- Catch phrases:
DiskDefs.UnrecoverableDiskError => BEGIN erc←diskError; CONTINUE; END
];
RETURN[erc];
END; -- of RewritePagesInChunk.

-- Note: AssignPages requires that the VDA be present for the old last page and its predecessor.
[erc, ] ← GetVDA[oldLastPage-1, aFH]; -- Make sure VDA is in the table.
IF erc # ok THEN RETURN;
[erc, ] ← GetVDA[oldLastPage, aFH]; -- Make sure VDA is in the table.
IF erc # ok THEN RETURN;

erc ← CallProcedureForChunks[oldLastPage+1, newLastPage, aFH, AssignPagesInChunk];
IF erc # ok THEN RETURN;
erc ← CallProcedureForChunks[oldLastPage, newLastPage, aFH, RewritePagesInChunk];
DecrementFreePageCount[itemCount-1];
RETURN [erc, lastPageWritten, lastBytes];
END; -- of WriteAtFileEnd


-- END of AltoCore: INTERNAL MODULE;
-- VDATable: INTERNAL MODULE =
-- The vDA table is represented as a set of chunks, indexed by a descriptor block.
-- EMPTY.
-- 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.
-- Empty. (See CoreCom.)
-- END of FreePageMonitor: INTERNAL MODULE;
--------------------------------------------------------------
END.; -- of CoreIO, Core Division implementation module.