-- file CoreImpl.Mesa
-- Last edited by Brotz, June 1, 1982 12:47 PM.

DIRECTORY
AltoFile USING [FP, IllegalFileName, MapFileNameToFP, NoSuchFile],
AltoFileDefs USING [FP, NullFP],
Core USING [CacheEntry, CacheEntryBlk, CacheHeader, CacheHeaderBlk, DMSUser,
LocalCacheEntry, OpenMode],
exD: FROM "ExceptionDefs" USING [SysBug],
intCommon USING [leafOk, remoteFilePath, retrieveHandle],
NameInfoDefs USING [IsMemberClosure, Membership],
NameInfoSpecialDefs USING [CleanUp],
RetrieveDefs USING [MailboxState],
Storage USING [CopyString, Free, FreeString, Node, String],
String USING [AppendChar, AppendString, AppendSubString, EquivalentString,
EquivalentSubString, SubStringDescriptor],
VMDefs USING [CantOpen, CloseFile, DestroyFile, Error, FileHandle, FileSystem, FSAlto,
GetFileSystem, Login, Logout, OpenFile, StartFile, UnableToLogin],
VMSpecial USING [OpenAltoFileFromFP];

CoreImpl: MONITOR
IMPORTS AltoFile, exD, intC: intCommon, NameInfoDefs, NameInfoSpecialDefs,
RetrieveDefs, Storage, String, VMDefs, VMSpecial
EXPORTS Core =

BEGIN
OPEN Core;

-- Implementation of Core. See Core.mesa for details.
-- MONITOR protects the file name cache. (We don’t want someone setting the open bit of
-- a cache entry while someone else is opening that file.)


-- **** Global Variables **** --

localCacheHead: PUBLIC CacheHeader;
remoteCacheHead: CacheHeader ← NIL;
currentLoginEpoch: CARDINAL ← 0;
localCacheSize: CARDINAL ← 0;
localCacheLimit: CARDINAL = 25;
currentName: STRING ← NIL;
currentSimpleName: STRING ← NIL;
currentPassword: STRING ← NIL;
leafOk: CARDINAL ← 0;


Login: PUBLIC ENTRY PROCEDURE [user: DMSUser] =
-- Notifies the Core of the new name, registry and password to be used on subsequent
-- Open’s.
BEGIN
currentLoginEpoch ← currentLoginEpoch + 1;
IF currentName # NIL THEN Storage.FreeString[currentName];
IF currentSimpleName # NIL THEN Storage.FreeString[currentSimpleName];
IF currentPassword # NIL THEN Storage.FreeString[currentPassword];
currentName ← Storage.String[user.name.length + 1 + user.registry.length];
String.AppendString[currentName, user.name];
String.AppendChar[currentName, ’.];
String.AppendString[currentName, user.registry];
currentSimpleName ← Storage.CopyString[user.name];
currentPassword ← Storage.CopyString[user.password];
END; -- of Login --


Open: PUBLIC ENTRY PROCEDURE [filename: STRING, mode: OpenMode]
RETURNS [handle: VMDefs.FileHandle] =
-- Opens "filename" in "mode", and returns a handle to the opened file.
-- ERRORS:
-- VMDefs.CantOpen (notFound, alreadyExists, accessDenied, illegalFileName)
-- VMDefs.Error (io, resources, credentials) [io = unrecoverable disk error or communications
-- failure for remote files, resources = disk full or directory limit on remote server,
-- credentials = credentials presented at Login were not sufficient to permit this Open].
BEGIN ENABLE UNWIND => NULL;
IF filename = NIL OR filename.length = 0 THEN
ERROR VMDefs.CantOpen[illegalFileName];
handle ← IF filename[0] = ’[
THEN OpenRemoteFile[filename, mode] ELSE OpenLocalFile[filename, mode];
END; -- of Open --


OpenLocalFile: INTERNAL PROCEDURE [name: STRING, mode: OpenMode]
RETURNS [handle: VMDefs.FileHandle] =
BEGIN
entry: LocalCacheEntry ← LookupInLocalCache[name, NIL];
fp: AltoFileDefs.FP;
IF entry = NIL THEN
BEGIN
e: CacheEntry;
fp ← AltoFile.MapFileNameToFP[name, IF mode = read THEN old ELSE oldOrNew
! AltoFile.NoSuchFile => VMDefs.CantOpen[notFound];
AltoFile.IllegalFileName => VMDefs.CantOpen[illegalFileName]];
e ← InsertInCache[localCacheHead, name, fp, FALSE];
WITH le: e SELECT FROM
local => entry ← @le; ENDCASE => exD.SysBug[];
END
ELSE {fp ← entry.fp; IF entry.open THEN ERROR VMDefs.CantOpen[alreadyExists]};
handle ← VMSpecial.OpenAltoFileFromFP[fp: fp, writable: mode = update, cacheFraction: 0];
entry.handle ← handle;
entry.open ← TRUE;
END; -- of OpenLocalFile --


OpenRemoteFile: INTERNAL PROCEDURE [name: STRING, mode: OpenMode]
RETURNS [handle: VMDefs.FileHandle] =
BEGIN
madeTemp, newFileSystem: BOOLEAN ← FALSE;
fileSSD, serverSSD: String.SubStringDescriptor;
i: CARDINAL;
server: STRING ← [50];
file: STRING ← [99];
head: CacheHeader ← NIL;
fileSystem: VMDefs.FileSystem;
entry: CacheEntry;

Cleanup: INTERNAL PROCEDURE =
BEGIN
IF madeTemp THEN {Storage.FreeString[name]; madeTemp ← FALSE};
IF head # NIL AND head.firstEntry = NIL THEN RemoveCacheHead[head];
IF newFileSystem THEN VMDefs.Logout[fileSystem];
END; -- of Cleanup --

IF ~intC.leafOk THEN ERROR VMDefs.Error[credentials];
IF name.length < 2 THEN ERROR VMDefs.CantOpen[illegalFileName];
IF leafOk # 12795 THEN
BEGIN
mbr: NameInfoDefs.Membership;
SELECT RetrieveDefs.MailboxState[intC.retrieveHandle] FROM
badName, badPwd => ERROR VMDefs.Error[credentials];
ENDCASE;
mbr ← NameInfoDefs.IsMemberClosure["CoreLeafUsers.ms"L, currentName];
NameInfoSpecialDefs.CleanUp[];
SELECT mbr FROM
yes, allDown => leafOk ← 12795;
ENDCASE => ERROR VMDefs.Error[credentials];
END;
IF name[1] = ’] THEN
BEGIN -- Handle name of form []Filename, replacing the [] with intC.remoteFilePrefix.
temp: STRING;
dollarSignCount: CARDINAL ← 0;
remoteFilePath: STRING = intC.remoteFilePath;
IF remoteFilePath = NIL THEN ERROR VMDefs.CantOpen[illegalFileName];
FOR i: CARDINAL IN [0 .. remoteFilePath.length) DO
IF remoteFilePath[i] = ’$ THEN dollarSignCount ← dollarSignCount + 1;
ENDLOOP;
temp ← Storage.String[name.length - 2 + remoteFilePath.length
+ dollarSignCount * (currentSimpleName.length - 1)];
IF dollarSignCount > 0 THEN
FOR i: CARDINAL IN [0 .. remoteFilePath.length) DO
IF remoteFilePath[i] = ’$ THEN String.AppendString[temp, currentSimpleName]
ELSE String.AppendChar[temp, remoteFilePath[i]];
ENDLOOP
ELSE String.AppendString[temp, remoteFilePath];
fileSSD ← String.SubStringDescriptor[name, 2, name.length - 2];
String.AppendSubString[temp, @fileSSD];
name ← temp;
madeTemp ← TRUE;
END;
-- Parse name into server and file.
FOR i IN [1 .. name.length) DO
IF name[i] = ’] THEN EXIT;
REPEAT FINISHED => ERROR VMDefs.CantOpen[illegalFileName];
ENDLOOP;
serverSSD ← String.SubStringDescriptor[name, 1, i - 1];
String.AppendSubString[server, @serverSSD];
fileSSD ← String.SubStringDescriptor[name, i + 1, name.length - i - 1];
String.AppendSubString[file, @fileSSD];
head ← GetRemoteCacheHead[server];
IF SearchCache[head, file, NIL] # NIL THEN
{Cleanup[]; ERROR VMDefs.CantOpen[alreadyExists]};
[fileSystem, newFileSystem] ← GetCurrentFileSystem[head];
IF fileSystem = NIL THEN {Cleanup[]; ERROR VMDefs.CantOpen[io]};
handle ← VMDefs.OpenFile
[fileSystem, file, IF mode = update THEN oldOrNew ELSE oldReadOnly
! VMDefs.CantOpen => Cleanup[]];
entry ← InsertInCache[head, name, AltoFileDefs.NullFP];
entry.handle ← handle;
IF madeTemp THEN Storage.FreeString[name];
END; -- of OpenRemoteFile --


Close: PUBLIC ENTRY PROCEDURE [handle: VMDefs.FileHandle] =
-- Closes the file associated with "handle". "handle" must not be re-used by the caller.
BEGIN ENABLE UNWIND => NULL;
fileSystem: VMDefs.FileSystem ← VMDefs.GetFileSystem[handle];
VMDefs.StartFile[handle];
IF fileSystem = VMDefs.FSAlto THEN
BEGIN
entry: LocalCacheEntry ← LookupInLocalCache[NIL, handle];
VMDefs.CloseFile[handle];
entry.open ← FALSE;
entry.handle ← NIL;
END
ELSE BEGIN
entry: CacheEntry;
head: CacheHeader;
[head, entry] ← LookupRemoteFile[handle];
VMDefs.CloseFile[handle];
head ← RemoveRemoteCacheEntry[head, entry];
IF ~FileSystemExists[head, fileSystem] THEN VMDefs.Logout[fileSystem];
END;
END; -- of Close --


Delete: PUBLIC ENTRY PROCEDURE [handle: VMDefs.FileHandle] =
-- Deletes the file associated with "handle". "handle" must not be re-used by the caller.
BEGIN ENABLE UNWIND => NULL;
fileSystem: VMDefs.FileSystem ← VMDefs.GetFileSystem[handle];
VMDefs.StartFile[handle];
IF fileSystem = VMDefs.FSAlto THEN
BEGIN
entry: LocalCacheEntry ← LookupInLocalCache[NIL, handle];
VMDefs.DestroyFile[handle];
RemoveEntryFromCache[localCacheHead, entry];
END
ELSE BEGIN
entry: CacheEntry;
head: CacheHeader;
[head, entry] ← LookupRemoteFile[handle];
VMDefs.DestroyFile[handle];
head ← RemoveRemoteCacheEntry[head, entry];
IF ~FileSystemExists[head, fileSystem] THEN VMDefs.Logout[fileSystem];
END;
END; -- of Delete --


InsertInFileCache: PUBLIC ENTRY PROCEDURE
[name: STRING, fp: AltoFileDefs.FP, open: BOOLEAN ← TRUE]
RETURNS [entry: LocalCacheEntry] =
-- Inserts "name" and "fp" in the Alto file name cache.
-- To protect a file from being opened, "open" should be TRUE. In this case, subsequent
-- Open’s on this file will fail with VMDefs.Error[alreadyExists].
-- VMDefs.Error[alreadyExists].
-- Possible uses of this procedure are to provide faster Open’s and to protect critical files as
-- described above.
BEGIN
e: CacheEntry ← InsertInCache[localCacheHead, name, fp, open];
WITH le: e SELECT FROM local => RETURN[@le]; ENDCASE => exD.SysBug[];
END; -- of InsertInFileCache --


InsertInCache: INTERNAL PROCEDURE
[head: CacheHeader, name: STRING, fp: AltoFileDefs.FP, open: BOOLEAN ← TRUE]
RETURNS [entry: CacheEntry] =
-- Inserts "name" and "fp" in the Alto file name cache.
-- To protect a file from being opened, "open" should be TRUE. In this case, subsequent
-- Open’s on this file will fail with VMDefs.Error[alreadyExists].
-- Possible uses of this procedure are to provide faster Open’s and to protect critical files as
-- described above.
BEGIN
hasDot: BOOLEAN = name[name.length - 1] = ’.;
nameCopy: STRING ← Storage.String[name.length + (IF hasDot THEN 0 ELSE 1)];
IF head = localCacheHead THEN
BEGIN
entry ← GetReusableLocalCacheEntry[];
IF entry # NIL THEN
BEGIN
UnlinkEntryFromCache[head, entry];
IF entry.name # NIL THEN Storage.FreeString[entry.name];
END
ELSE
{entry ← Storage.Node[SIZE[local CacheEntryBlk]]; localCacheSize ← localCacheSize + 1};
END
ELSE entry ← Storage.Node[SIZE[remote CacheEntryBlk]];
String.AppendString[nameCopy, name];
IF ~hasDot THEN String.AppendChar[nameCopy, ’.];
IF head = localCacheHead THEN
entry↑ ← CacheEntryBlk
[next: NIL, prev: NIL, name: NIL, handle: NIL, open: open, vp: local[fp: fp]]
ELSE entry↑ ← CacheEntryBlk[next: NIL, prev: NIL, name: NIL, handle: NIL, open: TRUE,
vp: remote[loginEpoch: currentLoginEpoch]];
entry.name ← nameCopy;
-- Link entry in at the beginning of the cache list.
InsertEntryAtHeadOfCache[head, entry];
END; -- of InsertInCache --


GetRemoteCacheHead: PROCEDURE [server: STRING] RETURNS [head: CacheHeader] =
BEGIN
FOR head ← remoteCacheHead, head.next UNTIL head = NIL DO
IF String.EquivalentString[server, head.server] THEN RETURN;
ENDLOOP;
head ← Storage.Node[SIZE[CacheHeaderBlk]];
head↑ ← CacheHeaderBlk[server: Storage.CopyString[server], firstEntry: NIL, lastEntry: NIL,
next: remoteCacheHead, prev: NIL];
IF remoteCacheHead # NIL THEN remoteCacheHead.prev ← head;
remoteCacheHead ← head;
END; -- of GetRemoteCacheHead --


UnlinkEntryFromCache: INTERNAL PROCEDURE [head: CacheHeader, entry: CacheEntry] =
BEGIN
IF entry.next = NIL THEN head.lastEntry ← entry.prev
ELSE entry.next.prev ← entry.prev;
IF entry.prev = NIL THEN head.firstEntry ← entry.next
ELSE entry.prev.next ← entry.next;
END; -- of UnlinkEntryFromCache --


InsertEntryAtHeadOfCache: INTERNAL PROC [head: CacheHeader, entry: CacheEntry] =
BEGIN
IF head.firstEntry # NIL THEN head.firstEntry.prev ← entry
ELSE head.lastEntry ← entry;
entry.next ← head.firstEntry;
entry.prev ← NIL;
head.firstEntry ← entry;
END; -- of InsertEntryAtHeadOfCache --


RemoveEntryFromCache: INTERNAL PROCEDURE [head: CacheHeader, entry: CacheEntry] =
BEGIN
UnlinkEntryFromCache[head, entry];
IF entry.name # NIL THEN Storage.FreeString[entry.name];
Storage.Free[entry];
IF head = localCacheHead THEN localCacheSize ← localCacheSize - 1;
END; -- of RemoveEntryFromCache --


GetReusableLocalCacheEntry: INTERNAL PROCEDURE RETURNS [entry: LocalCacheEntry] =
BEGIN
e: CacheEntry;
IF localCacheSize < localCacheLimit THEN RETURN[NIL];
FOR e ← localCacheHead.lastEntry, e.prev UNTIL e = NIL DO
IF ~e.open THEN EXIT;
REPEAT FINISHED => RETURN[NIL];
ENDLOOP;
WITH le: e SELECT FROM
local => RETURN[@le];
ENDCASE => exD.SysBug[];
END; -- of GetReusableLocalCacheEntry --


LookupInFileCache: PUBLIC ENTRY PROCEDURE [name: STRING]
RETURNS [fp: AltoFileDefs.FP] =
-- Returns the FP associated with the name (or NullFP if no such file).
BEGIN
entry: LocalCacheEntry;
IF name = NIL THEN RETURN[AltoFileDefs.NullFP];
entry ← LookupInLocalCache[name, NIL];
RETURN[IF entry = NIL THEN AltoFileDefs.NullFP ELSE entry.fp];
END; -- of LookupInFileCache --


LookupInLocalCache: INTERNAL
PROCEDURE [name: STRING, handle: VMDefs.FileHandle]
RETURNS [entry: LocalCacheEntry] =
-- Returns the CacheEntry associated with the name (or NIL if no such file).
BEGIN
e: CacheEntry ← SearchCache[localCacheHead, name, handle];
IF e = NIL THEN RETURN[NIL];
UnlinkEntryFromCache[localCacheHead, e];
InsertEntryAtHeadOfCache[localCacheHead, e];
WITH le: e SELECT FROM local => RETURN[@le]; ENDCASE => exD.SysBug[];
END; -- of LookupInLocalCache --


SearchCache: INTERNAL
PROCEDURE [head: CacheHeader, name: STRING, handle: VMDefs.FileHandle]
RETURNS [entry: CacheEntry] =
-- Searches one cache list, the one corresponding to the particular server for the file "name".
-- If "name" is NIL, then searches the list for handle.
-- Returns the CacheEntry associated with the name (or NIL if no such file).
BEGIN
nameSSD, cacheSSD: String.SubStringDescriptor;
IF head = NIL THEN RETURN[NIL];
IF name # NIL THEN
BEGIN
IF name.length = 0 THEN RETURN[NIL];
nameSSD ← [base: name, offset: 0,
length: name.length - (IF name[name.length - 1] = ’. THEN 1 ELSE 0)];
END;
FOR entry ← head.firstEntry, entry.next UNTIL entry = NIL DO
IF name = NIL THEN {IF entry.handle = handle THEN EXIT}
ELSE BEGIN
cacheSSD ← [base: entry.name, offset: 0, length: entry.name.length - 1];
IF String.EquivalentSubString[@nameSSD, @cacheSSD] THEN EXIT;
END;
ENDLOOP;
END; -- of SearchCache --


GetCurrentFileSystem: INTERNAL PROCEDURE [head: CacheHeader]
RETURNS [fileSystem: VMDefs.FileSystem, new: BOOLEAN] =
BEGIN
name: STRING;
IF head = NIL THEN RETURN[NIL, FALSE];
FOR entry: CacheEntry ← head.firstEntry, entry.next UNTIL entry = NIL DO
WITH re: entry SELECT FROM
remote =>
IF re.loginEpoch = currentLoginEpoch THEN
RETURN[VMDefs.GetFileSystem[re.handle], FALSE];
ENDCASE => exD.SysBug[];
ENDLOOP;
fileSystem ← NIL;
new ← TRUE;
name ← IF String.EquivalentString[head.server, "Cherry"L]
THEN currentSimpleName ELSE currentName;
fileSystem ← VMDefs.Login[IFS, head.server, name, currentPassword
! VMDefs.UnableToLogin => {new ← FALSE; CONTINUE}];
END; -- of GetCurrentFileSystem --


FreeCacheEntry: PUBLIC ENTRY PROCEDURE [name: STRING] =
-- Removes the cache entry for "name" from the local file cache (if it existed).
-- May call SysBug if that file is marked open.
BEGIN
entry: CacheEntry = LookupInLocalCache[name, NIL];
IF entry = NIL THEN RETURN;
IF entry.open THEN exD.SysBug[];
RemoveEntryFromCache[localCacheHead, entry];
END; -- of FreeCacheEntry --


LookupRemoteFile: INTERNAL PROCEDURE [handle: VMDefs.FileHandle]
RETURNS [head: CacheHeader, entry: CacheEntry] =
BEGIN
entry ← NIL;
FOR head ← remoteCacheHead, head.next UNTIL head = NIL DO
FOR entry ← head.firstEntry, entry.next UNTIL entry = NIL DO
IF entry.handle = handle THEN RETURN;
ENDLOOP;
ENDLOOP;
END; -- of LookupRemoteFile --


FileSystemExists: INTERNAL PROCEDURE
[head: CacheHeader, fileSystem: VMDefs.FileSystem] RETURNS [exists: BOOLEAN] =
BEGIN
IF head = NIL THEN RETURN[FALSE];
FOR entry: CacheEntry ← head.firstEntry, entry.next UNTIL entry = NIL DO
IF VMDefs.GetFileSystem[entry.handle] = fileSystem THEN RETURN[TRUE];
ENDLOOP;
RETURN[FALSE];
END; -- of FileSystemExists --


RemoveRemoteCacheEntry: INTERNAL PROCEDURE [head: CacheHeader, entry: CacheEntry]
RETURNS [newHead: CacheHeader] =
BEGIN
RemoveEntryFromCache[head, entry];
IF head.firstEntry = NIL THEN {RemoveCacheHead[head]; newHead ← NIL}
ELSE newHead ← head;
END; -- of RemoveRemoteCacheEntry --


RemoveCacheHead: INTERNAL PROCEDURE [head: CacheHeader] =
BEGIN
IF head.next # NIL THEN head.next.prev ← head.prev;
IF head.prev # NIL THEN head.prev.next ← head.next
ELSE remoteCacheHead ← head.next;
END; -- of RemoveCacheHead --


-- Start code --

localCacheHead ← Storage.Node[SIZE[CacheHeaderBlk]];
localCacheHead↑ ← CacheHeaderBlk
[server: NIL, firstEntry: NIL, lastEntry: NIL, next: NIL, prev: NIL];


END. -- of CoreImpl --