-- file CoreIO.Mesa
-- edited by Kierr, May 3, 1978 5:24 PM.
-- edited by Levin, January 16, 1981 1:37 PM.
-- edited by Brotz, September 2, 1980 8:42 PM.

DIRECTORY
AltoFileDefs: FROM "AltoFileDefs",
BFSDefs: FROM "BFSDefs",
crD: FROM "CoreDefs",
crID: FROM "CoreImpDefs",
DiskDefs: FROM "DiskDefs",
DiskKDDefs: FROM "DiskKDDefs",
exD: FROM "ExceptionDefs",
gsD: FROM "GlobalStorageDefs",
MiscDefs: FROM "MiscDefs",
ovD: FROM "OverviewDefs",
SystemDefs: FROM "SystemDefs";

CoreIO: PROGRAM
IMPORTS BFSDefs, crID, DiskDefs, DiskKDDefs, exD, gsD, MiscDefs, SystemDefs
EXPORTS crD, crID --Alto[Read/Write]Pages--
SHARES crD = PUBLIC

BEGIN

OPEN crD, crID;

-- Purpose: provides a Laurel style interface to files and raw storage/structures.


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 Laurel.


-- 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 => exD.SysBug[];

IF LOOPHOLE[buffer, CARDINAL] MOD 256 ~= 0 THEN exD.SysBug[]; -- catch smash?
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: PageByte;
aFH: AltoFileHandle;

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

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 exD.SysBug[];

-- 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.
{lastPage ← lastPage + 1; bytesForLastPage ← 0};
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. Use zeroed data.
cAs[lastFilePage] ← zeroedBufferPtr ← GetPageBuffer[zeroOutData: TRUE];

-- Prepare pointers for writing all pages beyound current last
--file page.
-- Set up pointers to zeroed data.
FOR i IN (lastFilePage .. firstPage) 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 -- 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;

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

EXITS
leaving => BEGIN -- of cleanup for return.
IF bigCAsPtr # NIL THEN SystemDefs.FreeHeapNode[bigCAsPtr];
IF scratchBufferPtr # NIL THEN gsD.ReturnMemoryPages[1, scratchBufferPtr];
IF zeroedBufferPtr # NIL THEN gsD.ReturnMemoryPages[1, zeroedBufferPtr];
END; -- of cleanup;
END; -- of "Block for EXITS".
END; -- of AltoWritePages --


GetPageBuffer: PROCEDURE [zeroOutData: BOOLEAN] RETURNS [p: gsD.MemoryPagePtr] =
BEGIN
p ← gsD.GetMemoryPages[1];
IF zeroOutData THEN MiscDefs.Zero[p, 256];
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 => {errorCode ← diskError; CONTINUE}];
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: PageByte,aFH: AltoFileHandle]
RETURNS [erc: ovD.ErrorCode, lastPageWritten: AltoPageNumber, lastBytes: PageByte] =
-- 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 !
DiskKDDefs.DiskFull => BEGIN -- Oops, undo (any) assigned.
erc ← diskFull;
FOR p IN (oldLastPage .. 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 => {erc ← diskError; CONTINUE}];
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 !
DiskDefs.UnrecoverableDiskError => {erc ← diskError; CONTINUE}];
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];
END; -- of WriteAtFileEnd --


-- END of AltoCore: INTERNAL MODULE;


END. -- of CoreIO --