-- FileStreamImpl.mesa, edited by Johnsson, 13-Feb-81 14:14:14
DIRECTORY
ByteBlt USING [ByteBlt],
DCSFileTypes USING [tLeaderPage],
Environment USING [
Block, Byte, bytesPerPage, PageCount, wordsPerPage],
File USING [
Capability, GetAttributes, GetSize, grow, PageCount, Permissions,
read, SetSize, shrink, write],
FileStream USING [Subtype],
Heap USING [systemMDSZone],
Inline USING [
BITAND, BITOR, HighHalf, LongCOPY, LongDiv, LongDivMod, LongMult, LowHalf],
Space USING [
CopyIn, Create, CreateUniformSwapUnits, Delete, Error, ForceOut, GetAttributes,
GetHandle, GetWindow, Handle, LongPointer, Map, PageFromLongPointer,
Unmap, virtualMemory],
Stream USING [
Block, Byte, defaultInputOptions, EndOfStream, GetProcedure,
Handle, InputOptions, Object, PutProcedure, SendAttentionProcedure,
SetSSTProcedure, ShortBlock, WaitAttentionProcedure],
System USING [GetGreenwichMeanTime, GreenwichMeanTime];
FileStreamImpl: PROGRAM
IMPORTS ByteBlt, File, Heap, Inline, Space, Stream, System
EXPORTS FileStream
SHARES File =
BEGIN
bytesPerPage: CARDINAL = Environment.bytesPerPage;
FSObject: TYPE = RECORD [
stream: Stream.Object,
index: CARDINAL ← 0,
bufferBytes: CARDINAL ← 0,
dataBytesInBuffer: CARDINAL ← 0,
firstFilePageInBuffer: CARDINAL ← 0,
filePages: CARDINAL ← 0,
password: CARDINAL ← fsPassword,
buffer: LONG POINTER TO PACKED ARRAY [0..0) OF Environment.Byte ← NIL,
leader: LONG POINTER TO LeaderPage ← NIL,
eofInBuffer: BOOLEAN ← FALSE,
read: BOOLEAN,
write: BOOLEAN,
grow: BOOLEAN,
shrink: BOOLEAN,
lengthChanged: BOOLEAN ← FALSE,
dataOffset: [0..1] ← 0,
file: File.Capability,
bufferSpace: Space.Handle ← NULL];
fsPassword: CARDINAL = 102774B;
FSHandle: TYPE = POINTER TO FSObject;
conversionFudge: CARDINAL = LOOPHOLE[@LOOPHOLE[0,FSHandle].stream];
ConvertHandle: PROCEDURE [h: Stream.Handle] RETURNS [FSHandle] =
INLINE {RETURN[LOOPHOLE[h-conversionFudge]]};
InvalidHandle: PUBLIC ERROR [errorStream: Stream.Handle] = CODE;
InvalidOperation: PUBLIC ERROR [errorStream: Stream.Handle] = CODE;
ValidateHandle: PROCEDURE [h: Stream.Handle] RETURNS [FSHandle] =
INLINE BEGIN
t: FSHandle ← ConvertHandle[h];
IF t.password # fsPassword THEN ERROR InvalidHandle[h];
RETURN[t];
END;
FileTooLong: PUBLIC ERROR [longFile: POINTER TO File.Capability] = CODE;
Create: PUBLIC PROCEDURE [
capability: File.Capability, options: Stream.InputOptions ← Stream.defaultInputOptions]
RETURNS [Stream.Handle] =
BEGIN
fsh: FSHandle = Heap.systemMDSZone.NEW[FSObject];
permissions: File.Permissions = capability.permissions;
filePages: File.PageCount = File.GetSize[capability];
IF Inline.HighHalf[filePages] # 0 THEN ERROR FileTooLong[@capability];
capability.permissions ← Inline.BITOR[permissions, File.read];
fsh↑ ← [
stream: [
getByte: GetByte,
putByte: PutByte,
getWord: GetWord,
putWord: PutWord,
get: GetBlock,
put: PutBlock,
delete: Delete,
setSST: SetSSTNop,
sendAttention: SendAttentionNop,
waitAttention: WaitAttentionNop,
options: options],
read: Inline.BITAND[permissions, File.read] # 0,
write: Inline.BITAND[permissions, File.write] # 0,
grow: Inline.BITAND[permissions, File.grow] # 0,
shrink: Inline.BITAND[permissions, File.shrink] # 0,
filePages: Inline.LowHalf[filePages],
file: capability];
IF ~fsh.read THEN
BEGIN
fsh.stream.getByte ← fsh.stream.getWord ← GetError;
fsh.stream.get ← GetBlockError;
END;
IF ~fsh.write THEN
BEGIN
fsh.stream.putByte ← LOOPHOLE[PutError];
fsh.stream.putWord ← PutError;
fsh.stream.put ← PutBlockError;
END;
InitLeader[fsh];
SetupBuffer[fsh, fsh.dataOffset];
RETURN[@fsh.stream];
END;
-- Generic Stream procedures; assume handle ok
GetByte: PROCEDURE [sH: Stream.Handle] RETURNS [byte: UNSPECIFIED[0..256)] =
BEGIN
fsh: FSHandle ← ConvertHandle[sH];
WHILE fsh.index = fsh.dataBytesInBuffer DO
IF fsh.eofInBuffer THEN {SIGNAL Stream.EndOfStream[0]; RETURN[0]};
AdvanceBuffer[fsh];
ENDLOOP;
byte ← fsh.buffer[fsh.index];
fsh.index ← fsh.index + 1;
RETURN
END;
GetWord: PROCEDURE [sH: Stream.Handle] RETURNS [UNSPECIFIED] =
BEGIN
t: UNSPECIFIED = CARDINAL[GetByte[sH]] * 256;
RETURN[t + GetByte[sH]]
END;
GetError: PROCEDURE [sH: Stream.Handle] RETURNS [UNSPECIFIED] =
{ERROR InvalidOperation[sH]};
PutByte: PROCEDURE [sH: Stream.Handle, byte: UNSPECIFIED[0..256)] =
BEGIN
fsh: FSHandle ← ConvertHandle[sH];
WHILE fsh.index = fsh.dataBytesInBuffer DO
IF fsh.eofInBuffer AND ~fsh.grow THEN {Stream.EndOfStream[0]; RETURN};
IF fsh.dataBytesInBuffer = fsh.bufferBytes THEN AdvanceBuffer[fsh]
ELSE
BEGIN
fsh.dataBytesInBuffer ← fsh.dataBytesInBuffer + 1;
fsh.lengthChanged ← TRUE;
END;
ENDLOOP;
fsh.buffer[fsh.index] ← byte;
fsh.index ← fsh.index + 1;
RETURN
END;
PutWord: PROCEDURE [sH: Stream.Handle, word: UNSPECIFIED] =
BEGIN
PutByte[sH, CARDINAL[word] / 256];
PutByte[sH, CARDINAL[word] MOD 256];
RETURN
END;
PutError: PROCEDURE [sH: Stream.Handle, word: UNSPECIFIED] =
{ERROR InvalidOperation[sH]};
GetBlock: Stream.GetProcedure =
BEGIN
fsh: FSHandle = ConvertHandle[sH];
bufferBlock: Environment.Block;
countRemaining: CARDINAL ← block.stopIndexPlusOne-block.startIndex;
countTransferred: CARDINAL;
sst ← 0;
bytesTransferred ← 0;
why ← normal;
WHILE countRemaining # 0 DO
bufferBlock ← [
blockPointer: fsh.buffer,
startIndex: fsh.index,
stopIndexPlusOne: fsh.dataBytesInBuffer];
countTransferred ← ByteBlt.ByteBlt[from: bufferBlock, to: block];
fsh.index ← fsh.index + countTransferred;
bytesTransferred ← bytesTransferred + countTransferred;
IF (countRemaining ← countRemaining - countTransferred) = 0 THEN EXIT;
IF fsh.eofInBuffer THEN
BEGIN
IF options.signalEndOfStream THEN
SIGNAL Stream.EndOfStream[block.startIndex+countTransferred];
IF options.signalShortBlock THEN ERROR Stream.ShortBlock;
why ← endOfStream;
EXIT
END;
block.startIndex ← block.startIndex + countTransferred;
AdvanceBuffer[fsh];
ENDLOOP;
RETURN
END;
GetBlockError: Stream.GetProcedure = {ERROR InvalidOperation[sH]};
PutBlock: Stream.PutProcedure =
BEGIN
fsh: FSHandle = ConvertHandle[sH];
bufferBlock: Environment.Block;
countRemaining: CARDINAL ← block.stopIndexPlusOne-block.startIndex;
countTransferred: CARDINAL;
WHILE countRemaining # 0 DO
bufferBlock ← [
blockPointer: fsh.buffer,
startIndex: fsh.index,
stopIndexPlusOne: fsh.dataBytesInBuffer];
IF fsh.eofInBuffer AND fsh.grow THEN
bufferBlock.stopIndexPlusOne ← fsh.bufferBytes;
countTransferred ← ByteBlt.ByteBlt[from: block, to: bufferBlock];
fsh.index ← fsh.index + countTransferred;
IF fsh.eofInBuffer AND fsh.index > fsh.dataBytesInBuffer THEN {
fsh.dataBytesInBuffer ← fsh.index; fsh.lengthChanged ← TRUE};
IF (countRemaining ← countRemaining - countTransferred) = 0 THEN EXIT;
IF fsh.eofInBuffer THEN
IF ~fsh.grow THEN {
IF fsh.stream.options.signalEndOfStream THEN
SIGNAL Stream.EndOfStream[block.startIndex+countTransferred];
IF fsh.stream.options.signalShortBlock THEN ERROR Stream.ShortBlock;
EXIT}
ELSE {
fsh.dataBytesInBuffer ← fsh.bufferBytes;
fsh.lengthChanged ← TRUE};
block.startIndex ← block.startIndex + countTransferred;
AdvanceBuffer[fsh];
ENDLOOP;
IF endPhysicalRecord THEN Cleanup[fsh];
RETURN
END;
PutBlockError: Stream.PutProcedure = {ERROR InvalidOperation[sH]};
Delete: PROCEDURE [sH: Stream.Handle] =
BEGIN
fsh: FSHandle ← ConvertHandle[sH];
newFilePages: CARDINAL;
index: LONG CARDINAL ← GetIndex[@fsh.stream];
ReadLeader[fsh];
EmptyBuffer[fsh, TRUE];
IF ~fsh.read AND index # 0 THEN fsh.leader.length ← index;
newFilePages ← Inline.LowHalf[PagesForBytes[fsh.leader.length]] + fsh.dataOffset;
IF fsh.write AND newFilePages < fsh.filePages AND fsh.shrink THEN
File.SetSize[fsh.file, newFilePages];
CloseLeader[fsh];
Zero[fsh, SIZE[FSObject]];
Heap.systemMDSZone.FREE[@fsh];
RETURN
END;
SetSSTNop: Stream.SetSSTProcedure = {};
SendAttentionNop: Stream.SendAttentionProcedure = {};
WaitAttentionNop: Stream.WaitAttentionProcedure = {RETURN[0]};
-- FileStream specific procedures; must validate handle
EndOf: PUBLIC PROCEDURE [sH: Stream.Handle] RETURNS [BOOLEAN] =
BEGIN
fsh: FSHandle = ValidateHandle[sH];
RETURN[fsh.eofInBuffer AND fsh.index = fsh.dataBytesInBuffer]
END;
GetIndex: PUBLIC PROCEDURE [sH: Stream.Handle] RETURNS [LONG CARDINAL] =
BEGIN
fsh: FSHandle = ValidateHandle[sH];
RETURN[Inline.LongMult[fsh.firstFilePageInBuffer-fsh.dataOffset, bytesPerPage] + fsh.index]
END;
IndexOutOfRange: PUBLIC SIGNAL [errorStream: Stream.Handle] = CODE;
SetIndex: PUBLIC PROCEDURE [sH: Stream.Handle, nextByte: LONG CARDINAL] =
BEGIN
fsh: FSHandle = ValidateHandle[sH];
filePage, byte: CARDINAL;
bufferPage: CARDINAL = fsh.firstFilePageInBuffer;
bufferPages: CARDINAL = fsh.bufferBytes/bytesPerPage;
IF (Inline.LongMult[bufferPage-fsh.dataOffset, bytesPerPage] + fsh.index) = nextByte THEN RETURN;
IF Inline.HighHalf[nextByte] >= bytesPerPage THEN GOTO OutOfRange;
[filePage, byte] ←
Inline.LongDivMod[nextByte,bytesPerPage];
filePage ← filePage + fsh.dataOffset;
IF filePage NOT IN[bufferPage..bufferPage+bufferPages) THEN
BEGIN
growFile: BOOLEAN ← FALSE;
IF fsh.lengthChanged THEN Cleanup[fsh];
IF nextByte > fsh.leader.length THEN
IF ~fsh.grow THEN GOTO OutOfRange
ELSE
{fsh.filePages ← filePage + growIncrement; growFile ← TRUE};
SetupBuffer[fsh,
MAX[filePage,fsh.dataOffset+swapUnitSize]-swapUnitSize, growFile];
END;
fsh.index ←
(filePage-fsh.firstFilePageInBuffer) * bytesPerPage + byte;
IF fsh.index > fsh.dataBytesInBuffer THEN
{fsh.dataBytesInBuffer ← fsh.index; fsh.lengthChanged ← TRUE};
RETURN;
EXITS OutOfRange => SIGNAL IndexOutOfRange[sH];
END;
GetLength: PUBLIC PROCEDURE [sH: Stream.Handle] RETURNS [LONG CARDINAL] =
BEGIN
fsh: FSHandle = ValidateHandle[sH];
Cleanup[fsh];
RETURN[fsh.leader.length]
END;
SetLength: PUBLIC PROCEDURE [sH: Stream.Handle, fileLength: LONG CARDINAL] =
BEGIN
fsh: FSHandle = ValidateHandle[sH];
newFilePages: CARDINAL;
growFile: BOOLEAN ← FALSE;
bufferPage: CARDINAL = fsh.firstFilePageInBuffer;
bufferPages: CARDINAL = fsh.bufferBytes/bytesPerPage;
Cleanup[fsh];
IF Inline.HighHalf[fileLength] >= bytesPerPage THEN GOTO OutOfRange;
newFilePages ←
Inline.LongDiv[fileLength+bytesPerPage-1,bytesPerPage]+fsh.dataOffset;
IF newFilePages > fsh.filePages THEN
IF fsh.grow THEN {fsh.filePages ← newFilePages; growFile ← TRUE}
ELSE GOTO OutOfRange;
fsh.leader.length ← fileLength;
WriteLeader[fsh];
SetupBuffer[fsh,
IF newFilePages IN [bufferPage..bufferPage+bufferPages) THEN fsh.firstFilePageInBuffer
ELSE MAX[newFilePages, fsh.dataOffset+swapUnitSize] - swapUnitSize, growFile];
fsh.index ← fsh.dataBytesInBuffer;
RETURN;
EXITS OutOfRange => SIGNAL IndexOutOfRange[sH];
END;
GetCapability: PUBLIC PROCEDURE [sH: Stream.Handle] RETURNS [File.Capability] =
{fsh: FSHandle = ValidateHandle[sH]; RETURN[fsh.file]};
-- LeaderPage
leaderVersionID: CARDINAL = 01240;
maxLeaderNameCharacters: CARDINAL = 40;
LeaderPage: TYPE = MACHINE DEPENDENT RECORD [
versionID: CARDINAL,
dataType: FileStream.Subtype,
create, write, read: System.GreenwichMeanTime,
length: LONG CARDINAL,
nameLength: CARDINAL,
name: PACKED ARRAY [0..maxLeaderNameCharacters) OF CHARACTER];
InitLeader: PROCEDURE [fsh: FSHandle] =
BEGIN
leaderSpace: Space.Handle = Space.Create[1, Space.virtualMemory];
leader: LONG POINTER TO LeaderPage = Space.LongPointer[leaderSpace];
hasLeader: BOOLEAN ←
File.GetAttributes[fsh.file].type = DCSFileTypes.tLeaderPage;
maxLength: LONG CARDINAL ← Inline.LongMult[fsh.filePages, bytesPerPage];
IF fsh.filePages = 0 THEN
IF fsh.grow THEN File.SetSize[fsh.file, fsh.filePages ← growIncrement + 1]
ELSE hasLeader ← FALSE;
IF hasLeader THEN
BEGIN
leaderCap: File.Capability ← fsh.file;
now: System.GreenwichMeanTime = System.GetGreenwichMeanTime[];
leaderCap.permissions ← File.read+File.write;
IF maxLength # 0 THEN maxLength ← maxLength - bytesPerPage;
Space.Map[leaderSpace, [leaderCap, 0]];
fsh.dataOffset ← 1;
IF leader.versionID # leaderVersionID THEN
BEGIN
Zero[leader, Environment.wordsPerPage];
leader.versionID ← leaderVersionID;
leader.length ← maxLength;
fsh.lengthChanged ← TRUE;
END
ELSE IF leader.length > maxLength THEN
{leader.length ← maxLength; fsh.lengthChanged ← TRUE};
IF fsh.write THEN leader.create ← leader.write ← now;
IF fsh.read THEN leader.read ← now ELSE leader.read ← [0];
END
ELSE
BEGIN
Space.Map[leaderSpace];
Zero[leader, Environment.wordsPerPage];
leader.length ← maxLength;
END;
Space.ForceOut[leaderSpace];
fsh.leader ← leader;
RETURN
END;
CloseLeader: PROCEDURE [fsh: FSHandle] =
{Space.Delete[GetMappedSpace[fsh.leader]]; fsh.leader ← NIL};
SetLeaderProperties: PUBLIC PROCEDURE [
sH: Stream.Handle,
create, write, read: System.GreenwichMeanTime ← [0],
name: STRING ← NIL,
type: FileStream.Subtype ← null] =
BEGIN
fsh: FSHandle = ValidateHandle[sH];
leader: LONG POINTER TO LeaderPage = fsh.leader;
ReadLeader[fsh];
IF create # 0 THEN leader.create ← create;
IF write # 0 THEN leader.write ← write;
IF read # 0 THEN leader.read ← read;
IF name # NIL THEN
BEGIN
leader.nameLength ← MIN[LENGTH[leader.name],name.length];
FOR i: CARDINAL IN [0..leader.nameLength) DO
leader.name[i] ← name[i];
ENDLOOP;
END;
IF type # null THEN leader.dataType ← type;
WriteLeader[fsh];
RETURN
END;
GetLeaderProperties: PUBLIC PROCEDURE [sH: Stream.Handle, name: STRING ← NIL]
RETURNS [type: FileStream.Subtype, create, write, read: System.GreenwichMeanTime] =
BEGIN
fsh: FSHandle = ValidateHandle[sH];
leader: LONG POINTER TO LeaderPage = fsh.leader;
ReadLeader[fsh];
IF name # NIL THEN
BEGIN
name.length ← MIN[leader.nameLength,name.maxlength];
FOR i: CARDINAL IN [0..name.length) DO
name[i] ← leader.name[i];
ENDLOOP;
END;
RETURN[leader.dataType, leader.create, leader.write, leader.read]
END;
-- Leader page operations without a stream
NoLeaderPage: PUBLIC ERROR [errorCap: POINTER TO File.Capability] = CODE;
GetLeader: PROCEDURE [cap: POINTER TO File.Capability]
RETURNS [leader: LONG POINTER TO LeaderPage] =
BEGIN
leaderSpace: Space.Handle;
pages: File.PageCount;
IF File.GetAttributes[cap↑].type # DCSFileTypes.tLeaderPage OR
(pages←File.GetSize[cap↑]) = 0 THEN ERROR NoLeaderPage[cap];
leaderSpace ← Space.Create[1, Space.virtualMemory];
leader ← Space.LongPointer[leaderSpace];
cap.permissions ← File.read + File.write;
Space.Map[leaderSpace, [cap↑, 0]];
IF leader.versionID # leaderVersionID THEN {
Zero[leader,Environment.wordsPerPage];
leader.versionID ← leaderVersionID;
leader.length ← (pages-1)*Environment.bytesPerPage};
RETURN
END;
GetLeaderPropertiesForCapability: PUBLIC PROCEDURE [cap: File.Capability, name: STRING ← NIL]
RETURNS [type: FileStream.Subtype, create, write, read: System.GreenwichMeanTime, length: LONG CARDINAL] =
BEGIN
leader: LONG POINTER TO LeaderPage = GetLeader[@cap];
IF name # NIL THEN
BEGIN
name.length ← MIN[leader.nameLength,name.maxlength];
FOR i: CARDINAL IN [0..name.length) DO
name[i] ← leader.name[i];
ENDLOOP;
END;
type ← leader.dataType;
create ← leader.create;
write ← leader.write;
read ← leader.read;
length ← leader.length;
Space.Delete[GetMappedSpace[leader]];
RETURN
END;
SetLeaderPropertiesForCapability: PUBLIC PROCEDURE [
cap: File.Capability,
create, write, read: System.GreenwichMeanTime ← [0],
name: STRING ← NIL,
type: FileStream.Subtype ← null] =
BEGIN
leader: LONG POINTER TO LeaderPage = GetLeader[@cap];
IF create # 0 THEN leader.create ← create;
IF write # 0 THEN leader.write ← write;
IF read # 0 THEN leader.read ← read;
IF name # NIL THEN
BEGIN
leader.nameLength ← MIN[LENGTH[leader.name],name.length];
FOR i: CARDINAL IN [0..leader.nameLength) DO
leader.name[i] ← name[i];
ENDLOOP;
END;
IF type # null THEN leader.dataType ← type;
Space.Delete[GetMappedSpace[leader]];
RETURN
END;
-- Utilities
maxBufferPages: CARDINAL = 70;
maxBufferBytes: CARDINAL = maxBufferPages * Environment.bytesPerPage;
swapUnitSize: CARDINAL = 5;
growIncrement: CARDINAL = 10;
Cleanup: PROCEDURE [fsh: FSHandle] =
BEGIN
Space.ForceOut[fsh.bufferSpace];
ReadLeader[fsh];
IF fsh.lengthChanged THEN
BEGIN
fsh.leader.length ← Inline.LongMult[fsh.firstFilePageInBuffer-fsh.dataOffset, bytesPerPage] + fsh.dataBytesInBuffer;
WriteLeader[fsh];
fsh.lengthChanged ← FALSE;
END;
RETURN
END;
ReadLeader: PROCEDURE [fsh: FSHandle] =
BEGIN
leaderSpace: Space.Handle = GetMappedSpace[fsh.leader];
Space.CopyIn[leaderSpace, Space.GetWindow[leaderSpace] ! Space.Error => CONTINUE];
END;
WriteLeader: PROCEDURE [fsh: FSHandle] =
BEGIN
Space.ForceOut[GetMappedSpace[fsh.leader]];
END;
SetupBuffer: PROCEDURE [
fsh: FSHandle, filePage: CARDINAL, setLength: BOOLEAN ← FALSE] =
BEGIN
filePagesFollowing: CARDINAL;
indexOfStartOfBuffer: LONG CARDINAL;
IF fsh.buffer = NIL THEN
BEGIN
fsh.bufferSpace ← Space.Create[maxBufferPages, Space.virtualMemory];
Space.CreateUniformSwapUnits[swapUnitSize, fsh.bufferSpace];
fsh.buffer ← Space.LongPointer[fsh.bufferSpace];
END
ELSE EmptyBuffer[fsh, FALSE];
IF setLength THEN File.SetSize[fsh.file, fsh.filePages];
Space.Map[fsh.bufferSpace, [fsh.file, filePage]];
filePagesFollowing ← fsh.filePages - filePage;
indexOfStartOfBuffer ← LONG[filePage-fsh.dataOffset]*bytesPerPage;
IF fsh.eofInBuffer ←
(fsh.leader.length <= indexOfStartOfBuffer + maxBufferBytes) THEN
BEGIN
fsh.dataBytesInBuffer ← Inline.LowHalf[
fsh.leader.length - indexOfStartOfBuffer]
END
ELSE fsh.dataBytesInBuffer ← maxBufferBytes;
fsh.bufferBytes ←
MIN[filePagesFollowing, maxBufferPages] * bytesPerPage;
fsh.firstFilePageInBuffer ← filePage;
RETURN
END;
EmptyBuffer: PROCEDURE [fsh: FSHandle, delete: BOOLEAN] =
BEGIN
IF fsh.buffer = NIL THEN RETURN;
Cleanup[fsh];
IF delete THEN {Space.Delete[fsh.bufferSpace]; fsh.buffer ← NIL}
ELSE Space.Unmap[fsh.bufferSpace];
fsh.index ← fsh.bufferBytes ← fsh.dataBytesInBuffer ← 0;
RETURN
END;
AdvanceBuffer: PROCEDURE [fsh: FSHandle] =
BEGIN
filePagesInBuffer: CARDINAL = fsh.bufferBytes/bytesPerPage;
nextPage: CARDINAL = fsh.firstFilePageInBuffer + filePagesInBuffer;
changeSize: BOOLEAN ← FALSE;
IF nextPage >= fsh.filePages THEN
{fsh.filePages ← fsh.filePages + growIncrement; changeSize ← TRUE};
SetupBuffer[fsh,
MAX[nextPage,fsh.dataOffset+swapUnitSize]-swapUnitSize, changeSize];
fsh.index ←
MIN[(nextPage-fsh.firstFilePageInBuffer) * bytesPerPage, fsh.dataBytesInBuffer];
RETURN
END;
GetMappedSpace: PROCEDURE [p: LONG POINTER] RETURNS [h: Space.Handle] =
BEGIN
mapped: BOOLEAN;
parent: Space.Handle;
h ← Space.GetHandle[Space.PageFromLongPointer[p]];
DO
[parent: parent, mapped: mapped] ← Space.GetAttributes[h];
IF mapped THEN EXIT;
h ← parent;
ENDLOOP;
RETURN
END;
PagesForBytes: PROCEDURE [w: LONG CARDINAL] RETURNS [p: LONG CARDINAL] =
INLINE {RETURN[(w+bytesPerPage-1)/bytesPerPage]};
Zero: PROCEDURE [p: LONG POINTER, n: CARDINAL] =
BEGIN
IF n = 0 THEN RETURN;
p↑ ← 0;
Inline.LongCOPY[from: p, to: p+1, nwords: n-1];
RETURN
END;
END...