-- TfsOpenFile is the procedure which is the interface to the Tfs Software package for users.
--Last Changed December 14, 1981 2:28 PM by GWilliams
--Fixed TfsOpen to not care about case in filenames and to allocate enough space for the filename string
DIRECTORY
MiscDefs: FROM "miscdefs" USING [Zero],
TridentDefs: FROM "tridentdefs" USING [cbPtr, cbzPtr, dcWriteD, dcReadHLD, DefaultTfsCleanupRtn, DefaultTfsErrorRtn, DL, dlPtr, DV, eofDA, fillInDA, FP, LD, lFID, lFP, lSN, NoopTfsCleanupRtn, openMode, PAGE, TfsActOnPages, tfsdskPtr, TfsVirtualDA, TFSwordsPerPage, TfsWritePages],
Storage USING[String, FreeString],
StringDefs: FROM "stringdefs" USING [AppendChar, AppendString, BcplSTRING, BcplToMesaString, EquivalentString, MesaToBcplString],
InlineDefs: FROM "inlinedefs" USING [LowHalf, COPY];

TfsOpen: PROGRAM
IMPORTS TridentDefs, InlineDefs, MiscDefs, Storage, StringDefs
EXPORTS TridentDefs =

BEGIN OPEN TridentDefs, InlineDefs, MiscDefs, Storage, StringDefs;
TfsOpenFile: PUBLIC PROCEDURE [tfsDisk: tfsdskPtr, fileName: STRING, mode: openMode, fp: POINTER TO FP] RETURNS [leaderPage, firstPage, lastPage: CARDINAL ← 0] =
BEGIN

-- Search the file SysDir sequentially for fileName. If found return the leader page (firstPage), lastPage, and the file pointer (FP). IF a new file is to be created (and fileName doesn’t exist in SysDir) then create an entry. Take the short cut of using an empty entry that is long enough. In other words, leave fragments.
-- If opening for read, SysDir is searched and FP is returned if found, else firstPage is zero and FP is garbage. If opening for create and fileName is found in SysDir, FP is returned, else a valid FP passed to TfsOpen is used as part of the directory entry that is created. TfsCreate will allocate a Leader Page for you.

sysBuf: ARRAY [0..1024) OF WORD;-- Input buffer
sysPtr: POINTER TO WORD;-- Pointer to sysBuf
pag: CARDINAL;-- page counter
tempSiz, siz: CARDINAL;-- where we are in a page
dirPtr: POINTER TO DV;-- pointer to directory entry
i: CARDINAL;
found: BOOLEAN;-- TRUE => file name found in SysDir
pages: ARRAY [0..30) OF PAGE;-- SysDir’s pages
dAs: DESCRIPTOR FOR ARRAY OF PAGE;
cAs: DESCRIPTOR FOR ARRAY OF POINTER;
numChars: INTEGER;
numCharsPtr: POINTER;
fpTfsSysdir: FP;-- file pointer for Sysdir
overFlag: BOOLEAN;-- directory entry overflowed a page
dirEntry: DV;-- directory entry
leaderArea: LD;-- The leader page
labPtr: dlPtr;-- Pointer to disk label
savePage: CARDINAL ← 0;-- Page # of available DirEntry
savePtr: POINTER TO WORD;-- Locn of entry in savePage
z1: CARDINAL;
localFileName: STRING ← NIL;

-- New Page

MakeNewEntry
: PROCEDURE [savePage: CARDINAL, savePtr: POINTER TO WORD, fileName: STRING, fp: POINTER TO FP] RETURNS [dirEntry: DV] =

-- LOCAL PROC
-- A page number (hopefully one of a directory) and a pointer within the page are passed to MakeNewEntry, which reads the page and builds a directory (using fileName and file pointer) entry starting where pointer points. The page is rewritten. Variables dirEntry, found are used globally.
-- An improvement to this procedure would be to place a free directory after the one that is created, if there is room. (Done, along with checking for end of file while trying to add an entry to the end of file. July 21, 1981 5:15 PM GWilliams.)

BEGIN
dirEntry1: DV;
siz, sysBufPtr, oldLength, newLength: CARDINAL;-- When we’re done, make a new free entry

IF found THEN RETURN;-- We’re using an existing directory entry
siz ← LOOPHOLE[savePtr, CARDINAL] - LOOPHOLE[@sysBuf, CARDINAL];
Zero[@dirEntry1, SIZE[DV]];
dirEntry1.length ← 100;

-- Read the page which has the available directory entry

[] ← TfsActOnPages[tfsDisk,cAs,dAs,fpTfsSysdir,savePage,savePage,dcReadHLD,
numCharsPtr,dcReadHLD,@sysBuf,NoopTfsCleanupRtn,DefaultTfsErrorRtn,FALSE,0];

COPY[savePtr, 26, @dirEntry];-- Retrieve directory slot
--note that this doesn’t destroy data (26 IS longer than SIZE[DV]) because the leader page is just below it! (Clean up some time).
oldLength ← dirEntry.length;-- Raw space to work with

-- Build a directory entry for this file

dirEntry.type ← 1;-- Type = file
newLength ← (fileName.length+2)/2 + 6;-- We need this much space
dirEntry.length ← newLength;
COPY[fp, lFP, @dirEntry.fp];-- Move in the file pointer
MesaToBcplString[fileName, @dirEntry.name];-- and name
IF (TFSwordsPerPage - siz) < newLength
THEN
{COPY[@dirEntry, (TFSwordsPerPage - siz), savePtr];
--write out this page
[] ← TfsActOnPages [tfsDisk, cAs, dAs, fpTfsSysdir, savePage, savePage, dcWriteD, numCharsPtr,
dcWriteD, @sysBuf, NoopTfsCleanupRtn, DefaultTfsErrorRtn, FALSE, 0];
savePage ← savePage+1;--be sure to write out correct page later
IF dAs[savePage] = eofDA
THEN --add a page to sysdir
{dAs[savePage] ← fillInDA;--change eofDA to fillInDA
Zero[@sysBuf, TFSwordsPerPage];--zero buf and add new page to end of file.
[]← TfsWritePages[tfsDisk, cAs, dAs, fpTfsSysdir, savePage, savePage,
dcWriteD, NIL, 0, @sysBuf, NIL, DefaultTfsErrorRtn, NIL, 0];
--get remainder of dirEntry
COPY[@dirEntry+(TFSwordsPerPage-siz), newLength-(TFSwordsPerPage-siz), @sysBuf];

sysBufPtr ← LOOPHOLE[@sysBuf, CARDINAL];
z1 ← sysBufPtr + (newLength - (TFSwordsPerPage-siz));
WHILE z1 + 100 < (sysBufPtr + TFSwordsPerPage)
DO--fill out page w/ empties, make each of them 100 words long
COPY[@dirEntry1, SIZE[DV], @z1];
z1 ← z1 + 100;
ENDLOOP;
dirEntry1.length ← (sysBufPtr + TFSwordsPerPage)-(z1+1);
COPY[LOOPHOLE[z1, POINTER], 1, @dirEntry1]; --last entry, need copy only first word
}
ELSE--read next page of sysdir and put in rest of entry
{[] ← TfsActOnPages[tfsDisk,cAs,dAs,fpTfsSysdir,savePage,savePage,dcReadHLD, numCharsPtr,
dcReadHLD, @sysBuf, NoopTfsCleanupRtn, DefaultTfsErrorRtn, FALSE, 0];
COPY[@dirEntry, newLength, @sysBuf]; --there ought to be some space per above check.
IF oldLength-newLength >0 THEN--put in a new empty entry
{dirEntry.length ← oldLength - newLength; dirEntry.type ← 0;
COPY[@dirEntry, 6, @sysBuf+newLength];
};
};--end of ELSE leg of IF dAs[savePage] = eofDA
}
ELSE--Get here when we didn’t fragment the entry
{COPY[@dirEntry, oldLength, savePtr];-- Put entry back in page

-- Now see if any space is left over, and mark it as a free entry (type=0)
-- N.B. that this may leave two small free entries, one above the other. Improve later
IF oldLength - newLength > 0 THEN-- If <=6, => wasted space
BEGIN
dirEntry1 ← dirEntry;
dirEntry1.length ← oldLength - newLength;-- Make a fresh free entry
dirEntry1.type ← 0;
COPY[@dirEntry1, 6, savePtr + newLength];-- Put header back in page
END;};

-- Write the page back onto the directory
[] ← TfsActOnPages[tfsDisk,cAs,dAs,fpTfsSysdir,savePage,savePage,dcWriteD,
numCharsPtr,dcWriteD,@sysBuf,NoopTfsCleanupRtn,DefaultTfsErrorRtn,FALSE,0];

-- Return the directory entry we created
RETURN[dirEntry];

END;
-- of MakeNewEntry
-- New Page

-- Beginning code for TfsOpenFile

fpTfsSysdir ← tfsDisk.tfskd.fpTFSSysDir;--make local copy of record

-- Build the Array of diskaddresses for SysDir
FOR i IN [0..28) DO pages[i] ← fillInDA; ENDLOOP;
dAs ← DESCRIPTOR[@pages[1], 27];
dAs[0] ← 1;
dAs[1] ← 2;

numCharsPtr ← @numChars;

-- Fix up the filename, initialize for create mode. Get enough space to allow an appended period.
localFileName ← String[fileName.length + 1];
AppendString[localFileName, fileName];

IF fileName[fileName.length-1] # ’.
THEN AppendChar[localFileName, ’.];

savePage ← 0;

-- Read the first page of Sysdir
BEGIN ENABLE UNWIND => IF localFileName # NIL THEN {FreeString[localFileName]; localFileName ← NIL};
[]←TfsActOnPages[tfsDisk, cAs, dAs, fpTfsSysdir, 1, 1, dcReadHLD, numCharsPtr, dcReadHLD, @sysBuf, DefaultTfsCleanupRtn, DefaultTfsErrorRtn, FALSE, 0];
END;

-- search Sysdir sequentially for fileName
pag ← 1;-- page counter
sysPtr ← @sysBuf[0];-- input Buffer
i ← 0;-- number of files
overFlag ← FALSE;-- overflow flag
found ← FALSE;-- TRUE => file name found

-- Two loops: the first handles pages, 2nd directory entries within a page
-- N.B.: Rather than read the file of headers, or read the header page for hintLastPage, read on page at a time, letting the called code fill in the next disk address until it hits EOF
UNTIL pag >= 27 DO
siz ← 0;-- size of current page
sysPtr ← @sysBuf[0];-- re-init pointer to buffer
IF overFlag THEN-- see if we’re doing a split entry
BEGIN
overFlag ← FALSE;-- reset overflow flag
COPY[sysPtr, 26, dirPtr];-- copy in the rest of the entry
siz ← siz + tempSiz;
sysPtr ← sysPtr + tempSiz;-- point to next entry
i ← i + 1;-- count for listing
found←CheckDirectory[localFileName,dirEntry];-- see if this is it
IF found THEN EXIT;-- finished if TRUE
END;

DO-- look at directory entries in a page

COPY[sysPtr, 26, @dirEntry];-- copy in the next entry
IF dirEntry.length + siz > 1024 THEN
BEGIN-- entry spills onto next page
dirPtr ← @dirEntry + 1024 - siz;-- save o/p location
tempSiz ← dirEntry.length-(1024-siz);-- and amount left over
overFlag ← TRUE;-- set the overflow flag
EXIT;--N.B. this means no entry is ever created on boundary!
-- go read next page
END;

i ← i + 1;
found←CheckDirectory[localFileName,dirEntry];-- check this directory entry
IF found THEN EXIT;-- finished if TRUE

-- Look for a directory slot that is big enough if
-- we are creating (and haven’t done it yet)

IF dirEntry.type = 0 AND dirEntry.length = 0 AND siz<TFSwordsPerPage
THEN
{dirEntry.length ← TFSwordsPerPage-siz;--mal-formed directory, fix up.
COPY [@dirEntry, TFSwordsPerPage-siz, sysPtr];--re-set the length
};

IF (mode=create) AND (savePage=0) AND dirEntry.type = 0 AND (localFileName.length+2)/2 + 6 <= dirEntry.length
THEN
{savePage ← pag;-- save the Sysdir page number
savePtr ← sysPtr;-- save location in the page
};

sysPtr ← sysPtr + dirEntry.length;-- point to next entry (source)
siz ← siz + dirEntry.length;-- add size of next entry
IF siz >= 1024 THEN EXIT;-- can’t have an overflow
ENDLOOP;

pag ← pag + 1;--don’t need to preserve this invariant as savePage preserves state
IF (found) OR dAs[pag] = eofDA THEN EXIT;-- finished if TRUE or EOF
BEGIN ENABLE UNWIND => IF localFileName # NIL THEN {FreeString[localFileName]; localFileName ← NIL};
[]←TfsActOnPages[tfsDisk,cAs,dAs,fpTfsSysdir,pag,pag,dcReadHLD,
numChars
Ptr,dcReadHLD,@sysBuf,DefaultTfsCleanupRtn,DefaultTfsErrorRtn,FALSE,0];
END;
ENDLOOP;

--
-- Search is finished, next find the last page in the file (if found)
--
--
found AND create=>not allowed, return page numbers
--
found AND ~create=>OK, return page numbers
--
~found AND create=>OK, create dirEntry, return page numbers
--
~found AND ~create=>OK, return 0’s

IF ~found AND (mode=read) THEN RETURN [0,0,0];

-- Make a directory entry for this file we’ve created
BEGIN ENABLE UNWIND => IF localFileName # NIL THEN {FreeString[localFileName]; localFileName ← NIL};
IF ~found AND (mode=create)
THEN dirEntry ← MakeNewEntry[savePage, savePtr, localFileName, fp];
END;

-- Find out what the relevant page numbers are and quit
IF found OR mode=create THEN
BEGIN
leaderPage ← dirEntry.fp.leaderVirtualDA;
pages[0] ← eofDA;
pages[1] ← leaderPage;
pages[2] ← fillInDA;
dAs ← DESCRIPTOR[@pages[1],2];

-- Read the leader page, use ReadLabel as cleanup routine to get disk page label
BEGIN ENABLE UNWIND => IF localFileName # NIL THEN {FreeString[localFileName]; localFileName ← NIL};
[]←TfsActOnPages[tfsDisk, cAs, dAs, dirEntry.fp, 0, 0, dcReadHLD, numCharsPtr, dcReadHLD, @sysBuf, ReadLabel, DefaultTfsErrorRtn, FALSE, 0];
END;
-- Fill up the leader page record

sysPtr ← @sysBuf[0];
COPY[sysPtr,256,@leaderArea];
lastPage ← leaderArea.hintLastPageFa.da;

labPtr ← tfsDisk.dsk.spare;-- Retrieve pointer to label
COPY[labPtr, lFID, fp];-- Copy fileid part of label to FP
fp.leaderVirtualDA ← leaderPage;-- leaderpage VDA into file pointer
firstPage ← LowHalf[TfsVirtualDA[tfsDisk,@labPtr.next]];-- first data page

END;
FreeString[localFileName];
RETURN[leaderPage, firstPage, lastPage];

END;
-- of TfsOpenFile


CheckDirectory: PUBLIC PROCEDURE [fileName: STRING, dirEntry: DV] RETURNS [found: BOOLEAN] =

-- This procedure simply compares the STRING fileName with the string in the directory entry dirEntry and returns TRUE if they’re equal, FALSE otherwise.

BEGIN

bPtr: POINTER TO BcplSTRING;-- The string in SysDir
sysFile: STRING ← [40];-- Sysdir string converted to Mesa

IF dirEntry.type # 1 THEN RETURN[FALSE];-- This entry is not in use
bPtr ← @dirEntry.name;-- Get pointer to Bcpl string
BcplToMesaString[bPtr,sysFile];-- Convert to a Mesa string
RETURN [EquivalentString[sysFile, fileName]];-- Return answer

END;
-- of CheckDirectory


MakeSysDirFP: PROCEDURE RETURNS [fpTfsSysdir: FP] =

-- Procedure returns the file pointer for SysDir

BEGIN

fidTfsSysdir: ARRAY [0..2) OF CARDINAL;-- file ID part of file pointer
fidSysDirPtr: POINTER;

fidTfsSysdir[0] ← 100000B;-- directory bit is on
fidTfsSysdir[1] ← 100;-- serial number
fidSysDirPtr ← @fidTfsSysdir;
COPY[fidSysDirPtr,lSN,@fpTfsSysdir.serialNumber];

-- just moved serial number into Sysdir’s FP, now finish up

fpTfsSysdir.version ← 1;
fpTfsSysdir.leaderVirtualDA ← 1;

RETURN [fpTfsSysdir];

END;
-- of MakeSysDirFP

ReadLabel
: PROCEDURE [disk: tfsdskPtr, cb: cbPtr, cbz: cbzPtr] =

-- Use this procedure as a cleanup routine in order to get needed information from the leader page’s label (next disk address). Store this information in the disk object.

BEGIN

disk.dsk.spare ← @cb.kcb.blockL.addr.fileID[0];
-- get the address of the label

END;
-- of ReadLabel

END.
-- of TfsOpen
--Last Changed July 31, 1981 3:47 PM by GWilliams
--Now send in DefaultTfsCleanupRtn with TfsActOnPages to get vDa’s
--Changed TfsOpenFile to initialize pages array correctly and to check for EOF