-- file CoreSS.Mesa
-- Last edited by Levin, January 12, 1981 11:58 AM.
-- Last edited by Schroeder, January 19, 1981 1:00 PM.
-- Last edited by Brotz, February 20, 1981 9:11 AM.

-- CoreSS supports calls done at open & close time. SS = Start/Stop.

DIRECTORY
AltoFileDefs,
CoreSwapDefs,
crD: FROM "CoreDefs",
crID: FROM "CoreImpDefs",
DirectoryDefs,
DiskDefs,
DiskKDDefs,
exD: FROM "ExceptionDefs",
ImageDefs,
ovD: FROM "OverviewDefs",
SegmentDefs,
Storage,
StringDefs,
TimeDefs;

CoreSS: PROGRAM
IMPORTS crD, crID, DirectoryDefs, DiskDefs, DiskKDDefs, exD, ImageDefs,
SegmentDefs, Storage, StringDefs
EXPORTS crD
SHARES crD = PUBLIC BEGIN

OPEN crD, crID;

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

-- Distributed File Department of the Core Division. This is a mini file system
-- for Laurel. 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).


OpenFile: PROCEDURE [user: DMSUser, filename: UFilename, mode: OpenMode]
RETURNS [erc: ovD.ErrorCode, uFH: UFileHandle] =
-- Opens the specified file in the specified mode, and returns a handle to the
-- opened file.
-- (Handles are useable until a CloseFile or DeleteFile. When OpenFile fails, i.e.
-- ErrorCode # ok, a special handle, NIL, is returned.)
-- Error Codes: diskError, diskFull, diskCorrupted, illegalFilename, fileInUse,
-- fileTooBig.
BEGIN
-- ##DO: Parse the filename; determine the server (e.g. local disk, IFS,
-- whatever); then dispatch to the correct open file routine. For now, assume
-- the local disk.
IF filename.length > 0 AND filename[0] = ’[ THEN
[erc, uFH] ← LeafOpenFile[user, filename, mode]
ELSE [erc, uFH] ← AltoOpenFile[user, filename, mode];
-- [erc, uFH] ← AltoOpenFile[user, filename, mode];
END; -- of OpenFile.


CloseFile: PROCEDURE [uFH: UFileHandle] RETURNS [ovD.ErrorCode] =
-- Closes the specified file. (Special Case: a nop if handle = NIL.) The handle is
-- closed and should not be re-used by the caller.
-- Error Codes: (##DO: update) diskError.
BEGIN
IF uFH = NIL THEN RETURN[ovD.ok] ELSE RETURN [uFH.close[uFH]];
END; -- of CloseFile.


DeleteFile: PROCEDURE [uFH: UFileHandle] RETURNS [ovD.ErrorCode] =
-- Deletes the file associated with the file handle. (Special case: a nop if
-- handle = NIL.) The handle is closed and should not be re-used by the caller.
-- Error Codes: (##DO: update) diskError.
BEGIN
IF uFH = NIL THEN RETURN[ovD.ok] ELSE RETURN [uFH.delete[uFH]];
END; -- of DeleteFile. --


UFileLength: PROCEDURE [uFH: UFileHandle]
RETURNS [erc: ovD.ErrorCode, lastPage: PageNumber, byteFF: PageByte] =
-- Returns the first free byte in the file, i.e. its length, via the formula (lastPage*512 +
-- byteFF). Any data that has been written since the file was opened is included. (Note,
-- empty files return [0,0].)
-- Error Codes: (None for Alto.)
BEGIN
[erc, lastPage, byteFF] ← uFH.length[uFH];
END; -- of UFileLength. --


UFileTruncate: PROCEDURE [lastPage: PageNumber, byteFF: PageByte,
uFH: UFileHandle] RETURNS [ovD.ErrorCode] =
-- Shorten a file which has been opened by OpenFile. The position has the same semantics
-- as UFileLength.
-- Do NOT use to LENGTHEN a file!
-- Error Codes: (##DO: update) diskError.
BEGIN
RETURN[uFH.truncate[lastPage, byteFF, uFH]];
END; -- of UFileTruncate. --

GetUFileTimes: PROCEDURE [uFH: UFileHandle]
RETURNS [read, write, create: TimeDefs.PackedTime, ec: ovD.ErrorCode] =
-- Reads the read, write, and create times associated with the file handle.
-- Error Codes: diskError.
{[read, write, create, ec] ← uFH.getTimes[uFH]};


SetUFileTimes: PROCEDURE [uFH: UFileHandle,
read, write, create: TimeDefs.PackedTime ← TimeDefs.DefaultTime]
RETURNS [ovD.ErrorCode] =
-- Sets the read, write, and create times associated with the file handle.
-- If a time is set to TimeDefs.DefaultTime then the current time is used.
-- Error Codes: diskError.
{RETURN[uFH.setTimes[uFH, read, write, create]]};


-- INTERNAL MODULE: AltoCore.Mesa
-- 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;


AltoOpenFile: PROC [user: DMSUser, filename: UFilename, mode: OpenMode]
RETURNS [ovD.ErrorCode, UFileHandle] =
-- Opens the specified file in the specified mode, and returns a handle to the
-- opened file. (Handles are useable until a CloseFile or DeleteFile. When
-- OpenFile fails, i.e. ErrorCode # ok, a special handle, NIL, is returned.)
-- The maximun size of a filename is AltoFileDefs.FilenameChars (=39).
-- Error Codes: diskError, diskFull, diskCorrupted, illegalFilename, fileInUse,
-- fileTooBig.
BEGIN
mesaFH: SegmentDefs.FileHandle ← NIL;
altoMode: SegmentDefs.AccessOptions;
aFH: AltoFileHandle;
eofPage: AltoPageNumber;
eofByte: CARDINAL[0 .. 512];
eofFA: AltoFileDefs.FA;
erc: ovD.ErrorCode;
i: CARDINAL;

BEGIN -- For EXITS.
SELECT mode FROM
read => altoMode ← SegmentDefs.Read;
update => altoMode ← SegmentDefs.ReadWriteAppend;
ENDCASE => exD.SysBug[];

-- Get mesaFH and open file, one way or another.
mesaFH ← LookupInFileCache[filename]; -- Try for a quick open.
IF mesaFH = NIL THEN --i.e. it wasn’t cached
BEGIN
IF filename.length = 0 THEN GOTO CBadFilename;
IF (IF filename[filename.length - 1] = ’. THEN filename.length ELSE filename.length + 1)
> AltoFileDefs.FilenameChars THEN GOTO CBadFilename;
FOR i IN [0 .. filename.length) DO
SELECT filename[i] FROM
IN [’a .. ’z], IN [’A .. ’Z], IN[’0 .. ’9], ’<, ’>, ’+, ’-, ’., ’!, ’$ => NULL;
ENDCASE => GOTO CBadFilename;
ENDLOOP;
mesaFH ← SegmentDefs.NewFile[filename, altoMode, SegmentDefs.DefaultVersion !
DirectoryDefs.BadDirectory=> GOTO CBadDirectory;
SegmentDefs.FileNameError =>
IF mode = read THEN GO TO CFileNotFound ELSE GO TO CBadFilename;
DirectoryDefs.BadFilename => GOTO CBadFilename;
DiskDefs.UnrecoverableDiskError => GOTO CUDErr;
DiskKDDefs.DiskFull => GOTO CDiskFull];
InsertInFileCache[filename, mesaFH];
END;
-- We would like the invariant for all files in use by Laurel to be that
-- mesaFH.lock > 0. However, this doesn’t work for Swatee, because it is locked by
-- the Mesa system at start-up and Hardcopy wants to be able to use Swatee.
-- Fortunately, Swatee isn’t opened by Mesa, so we make the invariant include
-- the open requirement.

-- Allow read (but not update) of SysDir, which unfortunately doesn’t satisfy the invariant.
IF ~(mesaFH.fp = AltoFileDefs.DirFP AND mode = read)
AND mesaFH.lock > 0 AND mesaFH.open
THEN RETURN[fileInUse, NIL];

SegmentDefs.SetFileAccess[mesaFH, altoMode];
-- ensure in-use invariant
SegmentDefs.LockFile[mesaFH];
SegmentDefs.OpenFile[mesaFH];
[eofPage, eofByte] ← SegmentDefs.GetEndOfFile[mesaFH];
[eofPage, eofByte] ← MakeFileIndexCanonical[eofPage, eofByte];
aFH ← Storage.Node[SIZE[alto UFileObject]];
aFH↑ ← UFileObject
[access: mode,
lastFilePage: MapAltoToUPage[eofPage],
byteFF: eofByte,
close: AltoCloseFile,
delete: AltoDeleteFile,
getTimes: AltoGetTimes,
setTimes: AltoSetTimes,
length: AltoFileLength,
truncate: AltoTruncateFile,
read: AltoReadPages,
write: AltoWritePages,
varpart: alto
[handle: mesaFH,
vDATable: CreateVDATable[]]];
IF eofPage > maxVDAIndex THEN
BEGIN -- Oops, too long!
erc ← AltoCloseFileF[aFH]; -- Undo this open.
RETURN[IF erc # ovD.ok THEN erc ELSE ovD.fileTooBig, NIL];
END;
TablePutVDA[aFH.vDATable, 0, aFH.handle.fp.leaderDA];
SegmentDefs.GetFileLength[aFH.handle, @eofFA];
TablePutVDA[aFH.vDATable, eofPage, eofFA.da];
UpdateFreePageCount[];
RETURN[ok, aFH];

EXITS
CBadDirectory => erc ← diskCorrupted;
CBadFilename => erc ← illegalFilename;
CFileNotFound => erc ← fileNotFound;
CUDErr => erc ← diskError;
CDiskFull => erc ← diskFull;
END; -- of block for EXITS.

RETURN[erc, NIL];
END; -- of AltoOpenFile.


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


AltoCloseFile: PROCEDURE [uFH: UFileHandle] RETURNS [erc: ovD.ErrorCode] =
-- Closes the specified file. The handle is closed and should not be re-used by
-- the caller. (The special Case "A nop if handle = NIL," is fielded by Core.)
-- Error Codes: diskError, diskCorrupted.
BEGIN
aFH: AltoFileHandle;
eofPage: AltoPageNumber = MapUToAltoPage[uFH.lastFilePage];
fA: AltoFileDefs.FA;
vDA: VDA;

WITH fh: uFH SELECT FROM
alto => aFH ← @fh;
ENDCASE => exD.SysBug[];
[erc, vDA] ← GetVDA[eofPage, aFH];
IF erc # ok THEN RETURN [erc];
fA ← [da: vDA, page: eofPage, byte: uFH.byteFF];
SegmentDefs.UpdateFileLength[aFH.handle, @fA];
RETURN[AltoCloseFileF[aFH]];
END; -- of AltoCloseFile. --


AltoCloseFileF: PROCEDURE [aFH: AltoFileHandle] RETURNS [erc: ovD.ErrorCode] =
-- Closes the specified file. Used by AltoCloseFile and AltoOpenFile to just close
-- the file.
-- Error Codes: diskError, diskCorrupted.
BEGIN OPEN SegmentDefs;
MakeFileGoAway: PROCEDURE =
BEGIN
CloseFile[aFH.handle ! DiskDefs.UnrecoverableDiskError =>
{erc ← diskError; CONTINUE};
InvalidFP => {erc ← diskCorrupted; CONTINUE}];
ReleaseFile[aFH.handle];
END;
ThisIsSwatee: PROCEDURE RETURNS [BOOLEAN] = INLINE
{OPEN CoreSwapDefs;
RETURN [PuntInfo↑ ~= NIL AND aFH.handle = PuntInfo↑.puntESV.drumFile]};

erc ← ovD.ok; -- Asssume the best.
UnlockFile[aFH.handle];
SELECT aFH.handle.lock FROM
0 => MakeFileGoAway[];
1 => IF ThisIsSwatee[] THEN MakeFileGoAway[];
ENDCASE;
FreeAFHStorage[aFH];
UpdateFreePageCount[];
END; -- of AltoCloseFileF.


AltoDeleteFile: PROCEDURE [uFH: UFileHandle] RETURNS [ovD.ErrorCode] =
-- Deletes the file associated with the file handle. The handle is closed and
-- should not be re-used by the caller. (The special case "A nop if handle =
-- NIL," is fielded by Core.)
-- Error Codes: diskError, diskCorrupted.
BEGIN
aFH: AltoFileHandle;
erc: ovD.ErrorCode ← ok; -- Assume all will go well.

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

FreeCacheEntry[aFH.handle];
SegmentDefs.UnlockFile[aFH.handle];
SegmentDefs.DestroyFile[aFH.handle !
DiskDefs.UnrecoverableDiskError, SegmentDefs.FileError =>
{erc←diskError; CONTINUE};
SegmentDefs.InvalidFP => {erc←diskCorrupted; CONTINUE}];
FreeAFHStorage[aFH];
UpdateFreePageCount[];
RETURN[erc];
END; -- of AltoDeleteFile --

AltoGetTimes: PROCEDURE [uFH: UFileHandle]
RETURNS [read, write, create: TimeDefs.PackedTime, ec: ovD.ErrorCode] =
BEGIN
aFH: AltoFileHandle;
WITH fh: uFH SELECT FROM
alto => aFH ← @fh;
ENDCASE => exD.SysBug[];
ec ← ok; -- Assume all will go well.
[read, write, create] ← SegmentDefs.GetFileTimes[aFH.handle !
DiskDefs.UnrecoverableDiskError => {ec ← ovD.diskError; CONTINUE};
SegmentDefs.InvalidFP => {ec ← ovD.diskCorrupted; CONTINUE} ];
END; -- AltoGetTimes--


AltoSetTimes: PROCEDURE [uFH: UFileHandle, read, write, create: TimeDefs.PackedTime]
RETURNS [ec: ovD.ErrorCode] =
BEGIN
aFH: AltoFileHandle;
WITH fh: uFH SELECT FROM
alto => aFH ← @fh;
ENDCASE => exD.SysBug[];
ec ← ovD.ok;
SegmentDefs.SetFileTimes[aFH.handle, read, write, create !
DiskDefs.UnrecoverableDiskError => {ec ← ovD.diskError; CONTINUE};
SegmentDefs.InvalidFP => {ec ← ovD.diskCorrupted; CONTINUE} ];
END; -- AltoGetTimes--


AltoFileLength: PROCEDURE [uFH: UFileHandle]
RETURNS [erc: ovD.ErrorCode, lastPage: UPageNumber, byteFF: PageByte] =
-- Returns the first free byte in the file, i.e. its length, via the formula
-- (lastPage*512 + byteFF). Any data that has been written since the file was
-- opened is included. (Note, empty files return [0,0].)
-- Note: The name of this routine may be misleading. The length returned is a
-- DMS file length; the page number component is a UPageNumber, not an
-- AltoPageNumber. (The name falls out of the naming conventions elsewhere,
-- alas.)
-- Error Codes: none for the Alto.
BEGIN
RETURN[ok, uFH.lastFilePage, uFH.byteFF];
END; -- of AltoFileLength --


AltoTruncateFile: PROCEDURE [lastPage: UPageNumber, byteFF: PageByte,
uFH: UFileHandle] RETURNS [ovD.ErrorCode] =
-- Shorten a file which has been opened by OpenFile. The position has the
-- same semantics as UFileLength.
-- Do NOT use to LENGTHEN a file!
-- File must have been opened in "update" mode.
-- Error Codes: diskError, diskCorrupted.
BEGIN
aFH: AltoFileHandle;
erc: ovD.ErrorCode ← ok; -- Assume all will go well.
WITH fh: uFH SELECT FROM
alto => aFH ← @fh;
ENDCASE => exD.SysBug[];
[lastPage, byteFF] ← MakeFileIndexCanonical[lastPage, byteFF];
SegmentDefs.SetEndOfFile[aFH.handle, MapUToAltoPage[lastPage], byteFF !
DiskDefs.UnrecoverableDiskError => {erc ← diskError; CONTINUE};
SegmentDefs.InvalidFP => {erc ← diskCorrupted; CONTINUE}];
uFH.lastFilePage ← lastPage;
uFH.byteFF ← byteFF;
UpdateFreePageCount[];
RETURN[erc];
END; -- of AltoTruncateFile --


FreeAFHStorage: PROCEDURE [aFH: AltoFileHandle] =
-- Return to the Mesa runtime all allocated storage associated with aFH.
BEGIN
DeallocateVDATable[aFH];
Storage.Free[aFH];
END; -- of FreeAFHStorage. --


-- END of AltoCore: INTERNAL MODULE;


-- VDATable: INTERNAL MODULE =
-- The vDA table is represented as a set of chunks, indexed by a descriptor
-- block.


CreateVDATable: PROCEDURE RETURNS [vDATablePtr: VDATablePtr] =
-- Allocates and initializes a VDATable and returns a pointer to it.
-- Note that the VDATable has 2 extra entries (containing NIL), one before and
-- one after the real data. The purpose of these entries is to obviate the need
-- to bounds check when making shadow copies of the padded entries.
BEGIN
i: INTEGER;
vDATablePtr ← Storage.Node[SIZE[VDATable] + 2] + 1;
FOR i IN [-1 .. numberOfChunks] DO
vDATablePtr[i] ← NIL;
ENDLOOP;
END; -- of CreateVDATable --


DeallocateVDATable: PROCEDURE [aFH: AltoFileHandle] =
-- Returns all allocated storage of the vDATable to the Mesa runtime (i.e. the
-- chunk index table and the individual chunks).
BEGIN
i: CARDINAL;
base: VDAsPtr;
FOR i IN [0 .. numberOfChunks) DO
base ← aFH.vDATable[i];
IF base # NIL THEN
Storage.Free[base + (i * chunkSpan) - lowerPad];
ENDLOOP;
Storage.Free[aFH.vDATable - 1];
END; -- of DeallocateVDATable --


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


InsertInFileCache: PUBLIC PROCEDURE [name: STRING,
fH: SegmentDefs.FileHandle] =
-- Return immediately iff fH = NIL. Else insert the name/fp into the cache.
BEGIN
oldFH: SegmentDefs.FileHandle;
node: CacheNodePtr;
copyOfName: STRING;
hasDot: BOOLEAN = (name[name.length - 1] = ’.);

IF fH = NIL THEN RETURN;

-- See if this entry has been already made.
-- The consistency check below is removable##.
oldFH ← LookupInFileCache[name];
IF oldFH # NIL THEN
IF oldFH.fp = fH.fp THEN RETURN -- already cached.
ELSE exD.SysBug[]; -- Inconsistent data.

copyOfName ← Storage.String[name.length + (IF hasDot THEN 0 ELSE 1)];
StringDefs.AppendString[copyOfName, name];
IF ~hasDot THEN StringDefs.AppendChar[copyOfName, ’.];
IF fileCacheEntries >= maxFileCacheEntries THEN
BEGIN
prev: CacheNodePtr;
FOR node ← fileCacheHeader, node.next UNTIL node.next = NIL DO
prev ← node;
ENDLOOP;
prev.next ← NIL;
Storage.FreeString[node.key];
Storage.Free[node];
END
ELSE fileCacheEntries ← fileCacheEntries + 1;
node ← Storage.Node[SIZE[CacheNode]];
node↑ ← [next: fileCacheHeader, fp: fH.fp, key: copyOfName];
fileCacheHeader ← node;
END; -- of InsertFileInCache. --


FreeCacheEntry: PROCEDURE [fH: SegmentDefs.FileHandle] =
-- Remove an entry from the cache and free associated storage.
BEGIN
node: CacheNodePtr;
prev: CacheNodePtr ← NIL; -- Inited for special case in loop.
IF fH = NIL THEN exD.SysBug[];

FOR node ← fileCacheHeader, node.next UNTIL node = NIL DO
IF node.fp = fH.fp THEN
BEGIN
IF prev # NIL THEN prev.next ← node.next
ELSE fileCacheHeader ← node.next;
Storage.FreeString[node.key];
Storage.Free[node];
fileCacheEntries ← fileCacheEntries - 1;
RETURN
END;
prev ← node;
ENDLOOP;
END; -- of FreeCacheEntry. --


LookupInFileCache: PROCEDURE [name: STRING]
RETURNS [fH: SegmentDefs.FileHandle] =
-- Look for name in the file cache. Return the associated FileHandle, or NIL iff
-- the name isn’t found in the cache.
BEGIN
node: CacheNodePtr;
prev: CacheNodePtr ← NIL;
newSsd, oldSsd: StringDefs.SubStringDescriptor;

newSsd ← [base: name, offset: 0,
length: name.length - (IF name[name.length - 1] = ’. THEN 1 ELSE 0)];

FOR node ← fileCacheHeader, node.next UNTIL node = NIL DO
oldSsd ← [base: node.key, offset: 0, length: node.key.length - 1];
IF StringDefs.EquivalentSubString[@newSsd, @oldSsd] THEN GOTO gotIt;
prev ← node;
REPEAT
gotIt =>
BEGIN
fH ← SegmentDefs.InsertFile[@node.fp, SegmentDefs.DefaultAccess];
IF prev # NIL THEN
BEGIN -- move node to head of file cache for LRU.
prev.next ← node.next;
node.next ← fileCacheHeader;
fileCacheHeader ← node;
END;
END;
FINISHED => fH ← NIL; -- not found.
ENDLOOP;
END; -- of LookupInFileCache. --


-- Data Structures and Types for FilenameCache: INTERNAL MODULE.


fileCacheHeader: CacheNodePtr ← NIL;
fileCacheEntries: CARDINAL ← 0;
maxFileCacheEntries: CARDINAL = 25;


CleanUpFileNameCache: ImageDefs.CleanupProcedure =
BEGIN
next: CacheNodePtr;

IF why ~= Save THEN RETURN;
UNTIL fileCacheHeader = NIL DO
next ← fileCacheHeader.next;
Storage.FreeString[fileCacheHeader.key];
Storage.Free[fileCacheHeader];
fileCacheHeader ← next;
ENDLOOP;
fileCacheEntries ← 0;
END; -- of CleanUpFileNameCache.


-- END of FilenameCache: INTERNAL MODULE;


cleanup: ImageDefs.CleanupItem ←
[link:, mask: ImageDefs.CleanupMask[Save], proc: CleanUpFileNameCache];

ImageDefs.AddCleanupProcedure[@cleanup];


END. -- of CoreSS --