-- File: AltoFileOpsB.mesa
-- Last edited by Levin: 30-Apr-81 14:24:14
DIRECTORY
AltoDefs USING [PageSize],
AltoFile USING [
AllocateDiskPage, ByteNumber, CFP, CloseDirectory, DeleteFP, DirHandle,
DiskFull, FileAlreadyExists, FP, FreeDiskPage, IllegalFileName,
InvalidFP, LDPtr, MapFileNameToFP, NewSN, NoSuchFile, OpenDirectory, PageNumber,
VersionOption],
AltoFileDefs USING [FA, TIME],
AltoFilePrivate USING [
AltoPageNumber, DoDiskRequest, FileHandle, FileObject, FindEOF, GetvDAForPage,
infinityPage, initialRuns, InsertFile, LastPageBytes, minUsefulRuns,
openSeal, PurgeFile, ReleaseFile, RunIndex, RunTable, TruncatevDATable, VdaRun],
DiskIODefs USING [
CompletionStatus, DiskError, DiskRequest, eofvDA, FID, fillInvDA,
PageCount, RequestID, vDA, XferSpec],
FileDefs USING [
ComparePositions, defaultTime, FileTime, FSInstance, OpenOptions, Position],
MiscDefs USING [Zero],
Mopcodes USING [zEXCH],
StringDefs USING [MesaToBcplString],
VMDefs USING [CantOpen, Error],
VMStorage USING [AllocatePage, FreePage, shortTerm];
AltoFileOpsB: MONITOR LOCKS file.LOCK USING file: AltoFilePrivate.FileHandle
IMPORTS
AltoFile, AltoFilePrivate, DiskIODefs, FileDefs, MiscDefs, StringDefs,
VMDefs, VMStorage
EXPORTS AltoFile, AltoFilePrivate, FileDefs =
BEGIN OPEN AltoFile, AltoFilePrivate, DiskIODefs, FileDefs;
-- Miscellaneous Declarations --
unknownLengthFA: AltoFileDefs.FA = [da: eofvDA, page: 0, byte: 0];
IllegalExtend: ERROR = CODE;
IllegalTruncate: ERROR = CODE;
InvalidFile: ERROR = CODE;
-- Types Exported to FileDefs --
FileObject: PUBLIC TYPE = AltoFilePrivate.FileObject;
-- Operations (exported to AltoFilePrivate on behalf of FileDefs) --
Open: PUBLIC PROCEDURE [
instance: FileDefs.FSInstance, name: STRING,
options: FileDefs.OpenOptions ← oldReadOnly]
RETURNS [FileHandle] =
BEGIN
DoOpen: PROCEDURE [file: FileHandle, newlyOpened: BOOLEAN]
RETURNS [worked: BOOLEAN] =
{RETURN[OpenFromHandle[file, newlyOpened, options ~= oldReadOnly]]};
fv: VersionOption =
SELECT options FROM oldReadOnly, old => old, new => new, ENDCASE => oldOrNew;
fp: FP ← MapFileNameToFP[name, fv
! DiskFull => ERROR VMDefs.Error[resources];
NoSuchFile => ERROR VMDefs.CantOpen[notFound];
IllegalFileName => ERROR VMDefs.CantOpen[illegalFileName];
FileAlreadyExists => ERROR VMDefs.CantOpen[alreadyExists]];
RETURN[InsertFile[fp, DoOpen ! DiskError => ERROR VMDefs.Error[io]]]
END;
Close, CloseFile: PUBLIC PROCEDURE [file: FileHandle] =
BEGIN
DoClose: PROCEDURE [file: FileHandle] =
BEGIN OPEN VMStorage;
UpdateLengthHint[file ! DiskError => ERROR VMDefs.Error[io]];
shortTerm.FREE[@file.runTable];
END;
ValidateFile[file];
ReleaseFile[file, DoClose];
END;
Abandon: PUBLIC PROCEDURE [file: FileHandle] =
BEGIN
DoAbandon: PROCEDURE [file: FileHandle] = {VMStorage.shortTerm.FREE[@file.runTable]};
ValidateFile[file];
ReleaseFile[file, DoAbandon];
END;
Destroy: PUBLIC PROCEDURE [file: FileHandle] =
BEGIN
DoDestroy: PROCEDURE [file: FileHandle] =
BEGIN
dirFP: FP;
fileFP: FP ← FP[serial: file.fileID.serial, leaderDA: file.leadervDA];
ThrowAwayFilePages: PROCEDURE =
BEGIN
request: DiskRequest;
leader: LDPtr ← ReadLeaderPage[file, @request];
dirFP ← FP[serial: leader.dirFP.serial, leaderDA: leader.dirFP.leaderDA];
VMStorage.FreePage[leader];
EnsureKnownVDAs[file, 0]; -- discover any unknown disk addresses
FreeFileTail[file, 0];
END;
ThrowAwayFilePages[ ! DiskError => ERROR VMDefs.Error[io]];
VMStorage.shortTerm.FREE[@file.runTable];
BEGIN OPEN AltoFile;
dir: DirHandle = OpenDirectory[dirFP ! InvalidFP => GO TO SkipDirectoryDelete];
[] ← DeleteFP[dir, @fileFP];
CloseDirectory[dir];
EXITS SkipDirectoryDelete => NULL;
END;
END;
ValidateFile[file];
PurgeFile[file, DoDestroy];
END;
GetLength: PUBLIC ENTRY PROCEDURE [file: FileHandle] RETURNS [Position] =
BEGIN
page: AltoPageNumber;
bytes: LastPageBytes;
ValidateFile[file];
IF ~file.lengthKnown THEN FindEOF[file ! UNWIND => NULL];
page ← file.lastPage;
bytes ← file.bytes;
RETURN[MakeFileLength[page, bytes]]
END;
SetLength: PUBLIC PROCEDURE [file: FileHandle, length: Position] =
BEGIN
oldLength: Position;
ValidateFile[file];
oldLength ← GetLength[file];
SELECT ComparePositions[oldLength, length] FROM
less =>
BEGIN
buffer: POINTER = VMStorage.AllocatePage[];
BEGIN
ENABLE DiskError => {VMStorage.FreePage[buffer]; ERROR VMDefs.Error[io]};
tempLength: Position ← [page: oldLength.page, byte: 0];
IF oldLength.byte ~= 0 THEN -- read partial last page into buffer
BEGIN
xferSpec: ARRAY [0..1) OF XferSpec ←
[[buffer, GetvDAForPage[file, file.lastPage], 0]];
request: DiskRequest ←
[firstPage: file.lastPage, fileID:, firstPagevDA:, pagesToSkip: 0,
nonXferID:, proc:, xfers: DESCRIPTOR[@xferSpec, 1],
noRestore: FALSE, command: ReadD[]];
[] ← DoDiskRequest[@request, file];
END;
-- not the most efficient algorithm...
UNTIL tempLength.page = length.page DO
tempLength.page ← tempLength.page + 1;
Extend[file, tempLength, buffer];
ENDLOOP;
IF length.byte ~= 0 THEN Extend[file, length, buffer];
END;
VMStorage.FreePage[buffer];
END;
equal => NULL;
greater => Truncate[file, length];
ENDCASE;
END;
Extend: PUBLIC ENTRY PROCEDURE [
file: FileHandle, length: Position, buffer: POINTER] =
BEGIN
xferSpec: ARRAY [0..2) OF XferSpec;
request: DiskRequest;
lastvDA: vDA;
oldLength: Position;
needANewPage: BOOLEAN = (length.byte = 0);
ValidateFile[file];
IF ~file.lengthKnown THEN FindEOF[file ! UNWIND => NULL];
oldLength ← MakeFileLength[file.lastPage, file.bytes];
IF ComparePositions[length, oldLength] ~= greater OR
~(length.page = oldLength.page OR
(length.page = oldLength.page + 1 AND length.byte = 0)) THEN
ERROR IllegalExtend;
xferSpec[0] ← [buffer, (lastvDA ← GetvDAForPage[file, file.lastPage]), 0];
request ← DiskRequest[
firstPage: file.lastPage, fileID:, firstPagevDA:, pagesToSkip: 0,
nonXferID:, proc:, xfers: DESCRIPTOR[@xferSpec, 1], noRestore: FALSE,
command: WriteLD[
next: eofvDA, prev: GetvDAForPage[file, file.lastPage - 1],
lastByteCount: AltoByteCount[length.byte] - 1]];
IF needANewPage THEN
BEGIN -- the following hack writes the client's data in the shadow page.
xferSpec[1] ← [buffer, AllocateDiskPage[lastvDA ! UNWIND => NULL], 0];
request.xfers ← DESCRIPTOR[@xferSpec, 2];
END;
[] ← DoDiskRequest[@request, file];
IF needANewPage THEN {file.lastPage ← file.lastPage + 1; file.bytes ← 0}
ELSE file.bytes ← AltoByteCount[length.byte] - 1;
file.lengthChanged ← TRUE;
END;
Truncate: PUBLIC ENTRY PROCEDURE [file: FileHandle, length: Position] =
BEGIN
newLastPage: AltoPageNumber = AltoFromFilePageNumber[length.page];
pagesToFree: BOOLEAN;
WriteNewLastPage: PROCEDURE =
-- truncates the file at 'newLastPage', setting the appropriate byte count.
BEGIN
buffer: POINTER ← VMStorage.AllocatePage[];
xferSpec: ARRAY [0..1) OF XferSpec ←
[[buffer, GetvDAForPage[file, newLastPage], 0]];
request: DiskRequest ←
[firstPage: newLastPage, fileID: file.fileID, firstPagevDA:,
pagesToSkip: 0, nonXferID:, proc:, xfers: DESCRIPTOR[@xferSpec, 1],
noRestore: FALSE, command: ReadD[]];
[] ← DoDiskRequest[@request, file ! DiskError =>
{VMStorage.FreePage[buffer]; ERROR VMDefs.Error[io]}];
request.command ← WriteLD[
next: eofvDA, prev: GetvDAForPage[file, newLastPage - 1],
lastByteCount: AltoByteCount[length.byte] - 1];
[] ← DoDiskRequest[@request, file ! DiskError =>
{VMStorage.FreePage[buffer]; ERROR VMDefs.Error[io]}];
VMStorage.FreePage[buffer];
END;
ValidateFile[file];
IF ~file.lengthKnown THEN FindEOF[file ! UNWIND => NULL];
SELECT ComparePositions[length, MakeFileLength[file.lastPage, file.bytes]] FROM
less =>
BEGIN
IF (pagesToFree ← file.lastPage > newLastPage) THEN
EnsureKnownVDAs[file, newLastPage - 1];
WriteNewLastPage[];
IF pagesToFree THEN
{FreeFileTail[file, newLastPage + 1]; TruncatevDATable[file, newLastPage]};
file.lastPage ← newLastPage;
file.bytes ← AltoByteCount[length.byte] - 1;
file.lengthChanged ← TRUE;
END;
equal => NULL;
greater => ERROR IllegalTruncate;
ENDCASE;
END;
GetTimes: PUBLIC PROCEDURE [file: FileHandle]
RETURNS [read, write, create: FileTime] = GetFileTimes;
SetCreationTime: PUBLIC PROCEDURE [
file: FileHandle, create: FileTime ← defaultTime] =
BEGIN
request: DiskRequest;
leader: LDPtr = ReadLeaderPage[file, @request];
IF create = defaultTime THEN create ← LeaderToMesaTime[DayTime[]];
leader.created ← MesaToLeaderTime[create];
RewriteLeaderPage[file, @request, leader];
END;
-- Procedures Exported to AltoFile --
OpenFromFP: PUBLIC PROCEDURE [fp: FP, markWritten: BOOLEAN] RETURNS [FileHandle] =
BEGIN
DoOpen: PROCEDURE [file: FileHandle, newlyOpened: BOOLEAN]
RETURNS [worked: BOOLEAN] =
{RETURN[OpenFromHandle[file, newlyOpened, markWritten]]};
RETURN[InsertFile[fp, DoOpen ! DiskError => ERROR VMDefs.Error[io]]]
END;
CreateFile: PUBLIC PROCEDURE [
name: STRING, directory: FP, leadervDA: vDA ← fillInvDA] RETURNS [FP] =
BEGIN
leader: LDPtr;
fp: FP;
vDAtoTry: vDA ← IF leadervDA = fillInvDA THEN vDA[0] ELSE vDA[leadervDA - 1];
lastPagevDA: vDA;
xferSpec: ARRAY [0..2) OF XferSpec;
request: DiskRequest;
xferSpec[0].diskAddress ← vDAtoTry ← AllocateDiskPage[vDAtoTry];
xferSpec[1].diskAddress ← lastPagevDA ← AllocateDiskPage[vDAtoTry];
xferSpec[0].buffer ← xferSpec[1].buffer ← leader ← VMStorage.AllocatePage[];
fp ← FP[NewSN[], vDAtoTry];
MiscDefs.Zero[leader, AltoDefs.PageSize];
leader.created ← DayTime[];
StringDefs.MesaToBcplString[name, LOOPHOLE[@leader.name]];
leader.propBegin ← @leader.props[0] - leader;
leader.propLength ← LENGTH[leader.props];
leader.dirFP ← CFP[directory.serial, 1, 0, directory.leaderDA];
leader.eofFA ← AltoFileDefs.FA[da: lastPagevDA, page: 1, byte: 0];
request ← DiskRequest[
firstPage: 0, fileID: FID[1, fp.serial], firstPagevDA:, pagesToSkip: 0,
nonXferID:, proc:, xfers: DESCRIPTOR[@xferSpec, 2], noRestore: FALSE,
command: WriteLD[next: eofvDA, prev: eofvDA, lastByteCount: 0]];
[] ← DoDiskRequest[@request ! DiskError => VMStorage.FreePage[leader]];
VMStorage.FreePage[leader];
RETURN[fp]
END;
GetFileTimes: PUBLIC PROCEDURE [file: FileHandle]
RETURNS [read, write, create: FileTime] =
BEGIN
request: DiskRequest;
leader: LDPtr = ReadLeaderPage[file, @request];
read ← LeaderToMesaTime[leader.read];
write ← LeaderToMesaTime[leader.written];
create ← LeaderToMesaTime[leader.created];
VMStorage.FreePage[leader];
END;
SetFileTimes: PUBLIC PROCEDURE [file: FileHandle,
read, write, create: FileTime ← defaultTime] =
BEGIN
now: FileTime = LeaderToMesaTime[DayTime[]];
request: DiskRequest;
leader: LDPtr = ReadLeaderPage[file, @request];
IF read = defaultTime THEN read ← now;
IF write = defaultTime THEN write ← now;
IF create = defaultTime THEN create ← now;
leader.read ← MesaToLeaderTime[read];
leader.written ← MesaToLeaderTime[write];
leader.created ← MesaToLeaderTime[create];
RewriteLeaderPage[file, @request, leader];
END;
ReadLeaderPage: PUBLIC PROCEDURE [file: FileHandle, request: POINTER TO DiskRequest]
RETURNS [leader: LDPtr] =
-- reads the leader page of 'file' into core, returning a pointer to it and leaving
-- request↑ suitable for calling RewriteLeaderPage.
BEGIN
xferSpec: ARRAY [0..1) OF XferSpec;
leader ← VMStorage.AllocatePage[];
xferSpec[0] ← [leader, file.leadervDA, 0];
request↑ ←
[firstPage: 0, fileID:, firstPagevDA:, pagesToSkip: 0, nonXferID:, proc:,
xfers: DESCRIPTOR[@xferSpec, 1], noRestore: FALSE, command: ReadD[]];
[] ← DoDiskRequest[request, file ! DiskError => VMStorage.FreePage[leader]];
END;
RewriteLeaderPage: PUBLIC PROCEDURE [
file: FileHandle, request: POINTER TO DiskRequest, leader: LDPtr] =
-- writes the leader page 'leader' onto 'file'. The storage associated with
-- 'leader' is then released.
BEGIN
xferSpec: ARRAY [0..1) OF XferSpec ← [[leader, file.leadervDA, 0]];
request.xfers ← DESCRIPTOR[@xferSpec, 1];
request.command ← WriteD[];
[] ← DoDiskRequest[request, file ! DiskError => VMStorage.FreePage[leader]];
VMStorage.FreePage[leader];
END;
-- Internal Procedures --
-- Open --
OpenFromHandle: PROCEDURE [file: FileHandle, checkLength, markWritten: BOOLEAN]
RETURNS [worked: BOOLEAN] =
BEGIN
leader: LDPtr = VMStorage.AllocatePage[];
request: DiskRequest;
xferSpec: ARRAY [0..1) OF XferSpec ← [[leader, file.leadervDA, 0]];
status: CompletionStatus;
worked ← FALSE;
request ←
[firstPage: 0, fileID:, firstPagevDA:, pagesToSkip: 0, nonXferID:, proc:,
xfers: DESCRIPTOR[@xferSpec, 1], noRestore: FALSE, command: ReadD[]];
file.runTable ← VMStorage.shortTerm.NEW[RunTable[initialRuns]];
file.nRuns ← minUsefulRuns;
file.runTable[0] ← VdaRun[page: 0, vda: file.leadervDA];
file.runTable[1] ← VdaRun[page: 1, vda: fillInvDA];
file.runTable[2] ← VdaRun[page: infinityPage, vda: eofvDA];
[status, , ] ← DoDiskRequest[@request, file, FALSE]; -- disk error => InvalidFP
IF status = ok THEN
BEGIN
leader.read ← DayTime[];
IF markWritten THEN leader.written ← leader.created ← leader.read;
request.command ← WriteD[];
[] ← DoDiskRequest[@request, file ! DiskError => VMStorage.FreePage[leader]];
IF checkLength THEN -- validate length hint
BEGIN
eofFA: AltoFileDefs.FA = leader.eofFA;
file.lengthKnown ← FALSE;
IF eofFA.da ~= unknownLengthFA.da THEN
BEGIN -- length hint is present - validate it.
next: vDA;
bytes: LastPageBytes;
request.firstPage ← eofFA.page;
xferSpec[0].diskAddress ← eofFA.da;
request.command ← ReadD[];
request.noRestore ← TRUE; -- anticipate possible check error
[status, next, bytes] ← DoDiskRequest[@request, file , FALSE];
IF status = ok AND next = eofvDA THEN
BEGIN
file.lastPage ← request.firstPage;
file.lengthChanged ← (file.bytes ← bytes) ~= eofFA.byte;
file.lengthKnown ← TRUE;
END;
END;
END;
worked ← TRUE;
END;
VMStorage.FreePage[leader];
END;
-- Truncation Utilities --
EnsureKnownVDAs: PROCEDURE [file: FileHandle, tail: AltoPageNumber] =
-- ensures that the vdas for all (Alto) pages >= 'tail' are known.
BEGIN
table: POINTER TO RunTable = file.runTable;
IF file.lengthKnown THEN
FOR i: RunIndex DECREASING IN [0..file.nRuns - 3] DO
IF table[i].vda = fillInvDA THEN EXIT;
IF table[i].page <= tail THEN RETURN;
ENDLOOP;
TruncatevDATable[file, tail];
FindEOF[file];
END;
FreeFileTail: PROCEDURE [file: FileHandle, tail: AltoPageNumber] =
-- releases all disk pages of 'file' beginning with 'tail'. It is assumed that
-- EnsureKnownVDAs has previously been called to fill the vda table appropriately.
BEGIN
FOR page: AltoPageNumber IN [tail..file.lastPage] DO
FreeDiskPage[GetvDAForPage[file, page] ! DiskError => CONTINUE]; ENDLOOP;
END;
UpdateLengthHint: PROCEDURE [file: FileHandle] =
-- rewrites the length hint into the leader page of the specified file. Note: it
-- is assumed that appropriate mutual exclusion on the file object has been
-- performed by the caller.
BEGIN
leader: LDPtr;
request: DiskRequest;
lastPagevDA: vDA;
IF ~(file.lengthKnown AND file.lengthChanged) THEN RETURN;
leader ← ReadLeaderPage[file, @request];
lastPagevDA ← GetvDAForPage[file, file.lastPage];
leader.eofFA ←
IF lastPagevDA = fillInvDA THEN unknownLengthFA
ELSE AltoFileDefs.FA[da: lastPagevDA, page: file.lastPage, byte: file.bytes];
RewriteLeaderPage[file, @request, leader];
END;
-- Miscellaneous Procedures --
ValidateFile: PROCEDURE [file: FileHandle] =
{IF file.seal ~= openSeal THEN ERROR InvalidFile};
AltoFromFilePageNumber: PROCEDURE [fp: PageNumber] RETURNS [AltoPageNumber] = INLINE
{RETURN[fp + 1]};
FileFromAltoPageNumber: PROCEDURE [ap: AltoPageNumber] RETURNS [PageNumber] = INLINE
{RETURN[ap - 1]};
AltoByteCount: PROCEDURE [fb: ByteNumber] RETURNS [LastPageBytes] = INLINE
-- ignore possibility that fb = LAST[ByteNumber]
{RETURN[fb + 1]};
FileByteNumber: PROCEDURE [ab: LastPageBytes] RETURNS [ByteNumber] = INLINE
{RETURN[ab - 1]};
MakeFileLength: PROCEDURE [page: AltoPageNumber, byte: LastPageBytes]
RETURNS [Position] = INLINE
{RETURN[[FileFromAltoPageNumber[page], FileByteNumber[byte] + 1]]};
DayTime: PROCEDURE RETURNS [AltoFileDefs.TIME] = INLINE
BEGIN
SecondsClock: POINTER TO AltoFileDefs.TIME = LOOPHOLE[572B];
RETURN[SecondsClock↑]
END;
LeaderToMesaTime: PROCEDURE [AltoFileDefs.TIME] RETURNS [FileTime] =
MACHINE CODE BEGIN Mopcodes.zEXCH END;
MesaToLeaderTime: PROCEDURE [FileTime] RETURNS [AltoFileDefs.TIME] =
MACHINE CODE BEGIN Mopcodes.zEXCH END;
END.