-- SubrImpl.Mesa
-- last edit March 28, 1983 4:29 pm
-- last edit May 22, 1983 5:28 pm, Russ Atkinson
 -- changed short STRING to LONG STRING
-- General purpose procedures
-- the Defs file is Subr.Mesa

    
DIRECTORY
Ascii: TYPE USING [ControlZ],
CWF: TYPE USING [WFC, WFCR, WF0, WF1, WF2],
DCSFileTypes: TYPE USING [tLeaderPage],
Directory: TYPE USING [CreateFile, Error, GetNext, GetProperty, Handle, ignore, Lookup,
 PropertyType, PutProperty, UpdateDates],
Environment: TYPE USING [wordsPerPage],
File: TYPE USING [Capability, Permissions, SetSize],
FileStream: TYPE USING [Create, GetLeaderPropertiesForCapability, Subtype],
Heap: TYPE USING [systemZone],
Inline: TYPE USING [BytePair, LongCOPY, LowHalf],
IO: TYPE USING[CreateEditedStream, GetChar, GetSequence, Handle, Signal, UserAbort],
LongString: TYPE USING [AppendChar],
Rope: TYPE USING[Concat, FromChar, ROPE, Text],
RopeInline: TYPE USING[InlineFlatten],
Runtime: TYPE USING [GetBcdTime],
Space: TYPE USING [CopyIn, Create, CreateUniformSwapUnits, Delete, Error, ForceOut,
 GetAttributes, GetHandle, Handle, Kill, LongPointer, Map, nullHandle,
  PageFromLongPointer, Unmap, virtualMemory],
Stream: TYPE USING [EndOfStream, GetChar, Handle],
Subr: TYPE USING [FileErrorType, NameType, PackedTime, Read, ReadWrite, TTYProcs,
 TTYProcsRecord, Write],
System: TYPE USING [GreenwichMeanTime],
UnsafeStorage: TYPE USING[FreeUZone, NewUZone],
UserCredentialsUnsafe: TYPE USING[GetUserCredentials, SetUserCredentials];

SubrImpl: PROGRAM
IMPORTS CWF, Directory, File, FileStream, Heap, Inline, IO, LongString,
 Rope, RopeInline, Runtime, Space, Stream, UnsafeStorage, UserCredentialsUnsafe
EXPORTS Subr = {

-- MDS USAGE !!!
errorflg: PUBLIC BOOLFALSE;
debugflg: PUBLIC BOOLFALSE;
numberofleaders: PUBLIC CARDINAL ← 0;
ncharsallocated: INT ← 0;
-- zone stuff
longzone: UNCOUNTED ZONENIL; -- default long zone
hugezone: UNCOUNTED ZONENIL; -- default huge zone
szprocs: UZProcs ← [alloc: SZAlloc, dealloc: SZDeAlloc];
szobject: UZObject ← [procs: @szprocs, data: NIL];
spacezonehandle: UZHandle ← @szobject;
-- endof MDS USAGE !!!

-- SIGNALS
AbortMyself: PUBLIC SIGNAL = CODE; -- raised usually by something that calls
    -- Exec.CheckForAbort[] or TypeScript.UserAbort[]
-- PROCS

MakeTTYProcs: PUBLIC PROC[in, out: IO.Handle, data: REF ANY,
  Confirm: PROC[in, out: IO.Handle, data: REF ANY, msg: Rope.ROPE, dch: CHAR]
   RETURNS[CHAR]]
RETURNS[h: Subr.TTYProcs] = {
  RETURN[NEW[Subr.TTYProcsRecord ← [in, out, data, Confirm]]];
 };

-- long strings
AllocateString: PUBLIC PROC[nchars: CARDINAL, zone: UNCOUNTED ZONE ← NIL]
RETURNS[s: LONG STRING] = {
IF zone = NIL THEN {
IF longzone = NIL THEN SubrInit[];
 zone ← longzone;
 };
IF zone = longzone THEN
 ncharsallocated ← ncharsallocated + nchars;
s ← zone.NEW[StringBody[nchars]
 ← StringBody[length: 0, maxlength: nchars, text:]];
};

FreeString: PUBLIC PROC[str: LONG STRING, zone: UNCOUNTED ZONE ← NIL] = {
IF str = NIL THEN RETURN;
IF zone = NIL THEN {
IF longzone = NIL THEN ERROR;
 zone ← longzone;
 };
IF zone = longzone THEN
 ncharsallocated ← ncharsallocated - str.maxlength;
zone.FREE[@str];
};

AllocateWords: PUBLIC PROC[nwords: CARDINAL] RETURNS[ptr: LONG POINTER] = {
seq: TYPE = RECORD[
 body: SEQUENCE maxsize: CARDINAL OF WORD
 ];
IF nwords > 32000 THEN ERROR;
IF longzone = NIL THEN SubrInit[];
ptr ← longzone.NEW[seq[nwords]];
};

FreeWords: PUBLIC PROC[ptr: LONG POINTER] = {
IF longzone = NIL THEN ERROR;
IF ptr = NIL THEN RETURN;
longzone.FREE[@ptr];
};

CheckVitalSigns: PROC = {
IF ncharsallocated ~= 0 THEN
CWF.WF1["Schmidt's debugging: Some memory not freed (%ld bytes).\n"L,
   @ncharsallocated];
ncharsallocated ← 0;
};

-- like Storage.CopyString
CopyString: PUBLIC PROC[sold: LONG STRING, zone: UNCOUNTED ZONE]
RETURNS[snew: LONG STRING] = {
nchars: CARDINAL;
IF sold = NIL THEN ERROR;
nchars ← sold.length;
IF zone = NIL THEN {
IF longzone = NIL THEN ERROR;
 zone ← longzone;
 };
IF zone = longzone THEN ncharsallocated ← ncharsallocated + nchars;
snew ← zone.NEW[StringBody[nchars]
 ← StringBody[length: nchars, maxlength: nchars, text:]];
Inline.LongCOPY[from: sold+2, nwords: (nchars+1)/2, to: snew+2];
};


strcpy: PUBLIC PROC[sto, sfrom: LONG STRING] = {
i : CARDINAL;
i ← MIN[sfrom.length, sto.maxlength];
Inline.LongCOPY[from: sfrom+2, nwords: (i+1)/2, to: sto+2];
sto.length ← i;
};

-- can also be used if sto and sfrom point to the same StringBody
SubStrCopy: PUBLIC PROC[sto, sfrom: LONG STRING, sfinx: CARDINAL] ={
i: CARDINAL ← 0;
WHILE sfinx < sfrom.length AND i < sto.maxlength DO
 sto[i] ← sfrom[sfinx];
 i ← i + 1;
 sfinx ← sfinx + 1;
ENDLOOP;
sto.length ← i;
};

-- from CharIO.GetID
GetID: PUBLIC PROC [in: Stream.Handle, s: LONG STRING] = {
c: CHAR ← ' ;
DO
c ← Stream.GetChar[in ! ANY => EXIT];
IF c # ' THEN EXIT;
ENDLOOP;
DO
IF c = ' OR c = '\n THEN EXIT;
LongString.AppendChar[s, c];
c ← GetChar[in ! ANY => EXIT];
ENDLOOP;
};

StripLeadingBlanks: PUBLIC PROC[str: LONG STRING] = {
i: CARDINAL ← 0;
WHILE i < str.length AND (str[i] = ' OR str[i] = '\t) DO
 i ← i + 1;
ENDLOOP;
SubStrCopy[str,str,i];
};


Any: PUBLIC PROC[str: LONG STRING, ch: CHAR] RETURNS[BOOL] = {
FOR i: CARDINAL IN [0..str.length) DO
IF str[i] = ch THEN RETURN[TRUE];
ENDLOOP;
RETURN[FALSE];
};

EndsIn: PUBLIC PROC[str: LONG STRING, suf: LONG STRING] RETURNS[BOOL] = {
FOR i: CARDINAL IN [0 .. str.length) DO {
IF LowerCase[str[i]] = LowerCase[suf[0]] THEN {
  FOR j: CARDINAL IN [0 ..suf.length) DO
   IF i+j >= str.length
   OR LowerCase[str[i+j]] ~= LowerCase[suf[j]] THEN
    GOTO outer;
   ENDLOOP;
  IF i+suf.length < str.length THEN GOTO outer;
  RETURN[TRUE];
  };
EXITS
 outer => NULL;
 };
ENDLOOP;
RETURN[FALSE];
};

ControlChars: PUBLIC PROC[str: LONG STRING] RETURNS[BOOL] = {
j: CARDINAL;
FOR i: CARDINAL IN [0..str.length) DO
 j ← LOOPHOLE[str[i], CARDINAL];
IF j IN [0..32) OR j >= 177B THEN RETURN[TRUE];
ENDLOOP;
RETURN[FALSE];
};


Prefix: PUBLIC PROC[str,pref: LONG STRING] RETURNS[BOOL] = {
i: CARDINAL ← 0;
WHILE i < str.length AND i <pref.length
AND LowerCase[str[i]] = LowerCase[pref[i]] DO
 i ← i + 1;
ENDLOOP;
RETURN[i = pref.length];
};

-- from [Indigo]<APilot>ComSoft>Private>StringsImplB.Mesa
-- only difference: INLINE

LowerCase: PROC [c: CHAR] RETURNS [CHAR] = INLINE
BEGIN IF c IN ['A..'Z] THEN c ← c + ('a - 'A); RETURN[c] END;

PrintGreeting: PUBLIC PROC[str: LONG STRING] = {
date: Subr.PackedTime ← Runtime.GetBcdTime[];
CWF.WF2["%s, Version Of %lt.\n"L, str, @date];
};

FindMappedSpace: PUBLIC PROC [space: Space.Handle] RETURNS [Space.Handle] =
BEGIN
mapped: BOOL;
parent: Space.Handle;
DO
[mapped: mapped, parent: parent] ← Space.GetAttributes[space];
IF mapped THEN RETURN[space];
space ← parent;
ENDLOOP;
END;

-- does not put a trailing CR into string "line"
-- if line ends with \ then omit \ and the following CR
-- append next line to last line
-- returns TRUE if NOT end-of-file, FALSE if eof has occured
GetLine: PUBLIC PROC[sh: Stream.Handle, line: LONG STRING]
RETURNS[noteof: BOOL] = {
i: CARDINAL ← 0;
ch: CHAR;
DO
IF i >= line.maxlength THEN EXIT;
 ch ← GetChar[sh];
 line[i] ← ch;
IF ch = 0C OR ch = '\n THEN EXIT;
 i ← i + 1;
ENDLOOP;
line.length ← i;
IF line.length >= line.maxlength THEN {
 line.length ← line.maxlength;
CWF.WF1["GetLine-- line '%s' is too long.\n"L, line];
 };
RETURN[NOT (ch = 0C AND line.length = 0)];
};

-- return next char in input, return 0C if EOF
GetChar: PUBLIC PROC[sh: Stream.Handle] RETURNS[ch: CHAR] = {
{
ch ← Stream.GetChar[sh
 ! Stream.EndOfStream => {
  ch ← 0C;
  GOTO out
  }
 ];
IF ch = Ascii.ControlZ THEN DO  -- strip bravo trailer
 ch ← Stream.GetChar[sh
  ! Stream.EndOfStream => {
   ch ← 0C;
   GOTO out
   }
  ];
IF ch = '\n OR ch = 0C THEN EXIT;
ENDLOOP;
EXITS
out => NULL;
};
RETURN[ch];
};

-- parses line, beginning at line[inx], into sub
-- considers a string to be a sequence of characters
-- begun by anything except blank, :, =, tab, and [
-- and terminated by blank, :, =, tab, and [
-- leading and trailing blanks are ignored
-- TAB is not considered a blank
GetString: PUBLIC PROC[line: LONG STRING, sub: LONG STRING, inx: CARDINAL]
RETURNS[CARDINAL] = {
i: CARDINAL;
sub.length ← 0;
WHILE inx < line.length AND line[inx] = ' DO
 inx ← inx + 1;
ENDLOOP;
IF inx >= line.length THEN RETURN[inx];
i ← 0;
DO
 sub[i] ← line[inx];
 inx ← inx + 1;
 i ← i + 1;
IF sub[i-1] = '=
OR sub[i-1] = ': THEN
  EXIT; -- tests for termination chars
IF inx < line.length AND i < sub.maxlength
AND line[inx] ~= 0C
AND line[inx] ~= '
AND line[inx] ~= '\t
AND line[inx] ~= ':
AND line[inx] ~= '=
AND line[inx] ~= '[ THEN
  LOOP; -- tests for begin chars
EXIT;
ENDLOOP;
sub.length ← i;
IF sub.length >= sub.maxlength THEN
CWF.WF1["Substring %s not long enough\n"L, sub];
RETURN[inx];
};

-- heap management

INITHEAP: CARDINAL = 40; -- # pages initially allocated to the heap
DEFPAGES: CARDINAL = 256; -- size of long heap default, in pages

SubrInit: PUBLIC PROC[npages: CARDINAL ← INITHEAP] = {
-- space: Space.Handle;
IF longzone ~= NIL THEN {
CWF.WF0["Schmidt's debugging: SubrInit called twice.\n"L];
RETURN;
 };
errorflg ← FALSE;
numberofleaders ← 0;
ncharsallocated ← 0;
-- space ← Space.Create[size: npages, parent: Space.virtualMemory
-- ! Space.InsufficientSpace => {
  -- CWF.WF1["ERROR Space.InsufficientSpace[%u].\n"L, @available];
  -- CWF.WF1["This program asked Pilot for %u pages, but the largest\n"L, @npages];
  -- CWF.WF1["space available was %u pages. You should rollback or boot\n"L, @available];
  -- CWF.WF0["and try again.\n"L];
  -- SIGNAL AbortMyself;
  -- }];
-- longzone ← Heap.Create[initial: INITHEAP, parent: space, increment: 20];
longzone ← UnsafeStorage.NewUZone[initialSize: INITHEAP*Environment.wordsPerPage,
     sr: prefixed];
};

SubrStop: PUBLIC PROC = {
deleteAgain: BOOLFALSE;
IF hugezone ~= NIL THEN hugezone ← FreeHugeZone[hugezone];
IF longzone = NIL THEN RETURN;
-- [parent: space] ← Heap.GetAttributes[longzone ! Heap.Error => CONTINUE];
CheckVitalSigns[];
-- Heap.Delete[z: longzone, checkEmpty: TRUE
-- ! Heap.Error => {
   -- IF type = invalidHeap THEN {
   -- deleteAgain ← TRUE;
   -- CWF.WF0["Schmidt's debugging: Heap not empty.\n"L];
   -- };
--   CONTINUE
--   }
--  ];
-- IF deleteAgain THEN
--  Heap.Delete[z: longzone, checkEmpty: FALSE
--   ! Heap.Error => CONTINUE];
-- Space.Delete[space: space ! Space.Error => CONTINUE];
UnsafeStorage.FreeUZone[longzone];
longzone ← NIL;
};

LongZone: PUBLIC PROC RETURNS[UNCOUNTED ZONE] = {
IF longzone = NIL THEN SubrInit[DEFPAGES];
RETURN[longzone]
};


-- first the TYPEs
UZHandle: TYPE = LONG POINTER TO UZObject;

UZObject: TYPE = MACHINE DEPENDENT RECORD [
 procs: LONG POINTER TO UZProcs,
 data: LONG POINTER
 ];

UZProcs: TYPE = MACHINE DEPENDENT RECORD [
 alloc: PROC[zone: UZHandle, size: CARDINAL] RETURNS[LONG POINTER],
 dealloc: PROC[zone: UZHandle, object: LONG POINTER]
 ];

-- the HugeZone stuff

HZHeader: TYPE = LONG POINTER TO HZHeaderRecord;
HZHeaderRecord: TYPE = RECORD[
 next: HZHeader ← NIL, -- next in list
 space: Space.Handle ← Space.nullHandle, -- this space
 current: LONG POINTERNIL, -- next free spot in this segment
 ending: LONG POINTERNIL, -- last valid address in this segment + 1
 npages: CARDINAL ← 0  -- number of pages in this segment
 ];

-- now the procedures
HZAlloc: PROC[zone: UZHandle, size: CARDINAL] RETURNS[lp: LONG POINTER] =
 {
RETURN[WordsFromHugeZone[LOOPHOLE[zone], size]];
 };

HZDeAlloc: PROC[zone: UZHandle, object: LONG POINTER] =
 {
-- currently do nothing
 };

pagesPerChunk: CARDINAL = 32;

WordsFromHugeZone: PUBLIC PROC[zone: UNCOUNTED ZONE, nwords: LONG CARDINAL]
RETURNS[lp: LONG POINTER] =
 {
 hzheader: HZHeader ← LOOPHOLE[zone, UZHandle].data;
IF LOOPHOLE[hzheader.current, LONG CARDINAL] + nwords >
  LOOPHOLE[hzheader.ending, LONG CARDINAL] THEN {
   oldheader: HZHeader ← hzheader;
   Space.ForceOut[oldheader.space]; -- avoids running out of Pilot Swap Buffers
   hzheader ← AllocHeader[MAX[pagesPerChunk,
    Inline.LowHalf[(nwords + SIZE[HZHeaderRecord])/Environment.wordsPerPage + 1]]];
   hzheader.next ← oldheader;
   LOOPHOLE[zone, UZHandle].data ← hzheader;
   };
 lp ← hzheader.current;
 hzheader.current ← hzheader.current + nwords;
RETURN[lp];
 };

PagesUsedInHugeZone: PUBLIC PROC[zone: UNCOUNTED ZONE] RETURNS[npages: CARDINAL] =
 {
 header: HZHeader ← LOOPHOLE[zone, UZHandle].data;
 npages ← 0;
WHILE header ~= NIL DO
  npages ← npages + header.npages;
  header ← header.next;
  ENDLOOP;
 };

-- now the procedure that will give you one of these
HugeZone: PUBLIC PROC RETURNS[UNCOUNTED ZONE] =
 {
 hzprocs: LONG POINTER TO UZProcs;
 hzheader: HZHeader;
 hugezonehandle: UZHandle;
IF hugezone ~= NIL THEN RETURN[hugezone];
 hzheader ← AllocHeader[1];
 hzprocs ← Heap.systemZone.NEW[UZProcs ← [alloc: HZAlloc, dealloc: HZDeAlloc]];
 hugezonehandle ← Heap.systemZone.NEW[UZObject ← [procs: hzprocs, data: hzheader]];
 hugezone ← LOOPHOLE[hugezonehandle];
RETURN[hugezone];
 };

AllocHeader: PROC[npages: CARDINAL] RETURNS[header: HZHeader] =
{
space: Space.Handle;
nwords: LONG CARDINALLONG[npages] * Environment.wordsPerPage;
-- CWF.WF1["Allocating %u huge pages.\n"L, @npages];
space ← Space.Create[size: npages, parent: Space.virtualMemory];
Space.Map[space];
header ← Space.LongPointer[space];
IF npages > 32 THEN
 Space.CreateUniformSwapUnits[8, space];
header^ ← [next: NIL, space: space, current: header + SIZE[HZHeaderRecord],
 ending: header + nwords, npages: npages];
};

-- to free the huge zone
-- always returns nil
FreeHugeZone: PUBLIC PROC[zone: UNCOUNTED ZONE] RETURNS[UNCOUNTED ZONE]=
 {
 space: Space.Handle;
 uzhandle: UZHandle ← LOOPHOLE[zone];
 header: HZHeader ← uzhandle.data;
 uzhandle.data ← NIL;
WHILE header ~= NIL DO
  space ← header.space;
  header ← header.next;
  Space.Delete[space: space
   ! Space.Error => {
    CWF.WF0["Schmidt's Debugging: Error freeing hugespace.\n"L];
    CONTINUE;
    }];
   ENDLOOP;
 Heap.systemZone.FREE[@uzhandle.procs];
 Heap.systemZone.FREE[@uzhandle];
-- kludge until multiple hugezones exist
 hugezone ← NIL;
RETURN[NIL];
 };

pairsPerLine: CARDINAL = 40;

-- debugging procedure
DebugPrintZone: PUBLIC PROC[h: Subr.TTYProcs] =
{
i, j, n1, n2, nwords: CARDINAL;
addr, lp: LONG POINTER TO CARDINAL;
header: HZHeader ← LOOPHOLE[hugezone, UZHandle].data;
WHILE header ~= NIL DO
  lp ← LOOPHOLE[header + SIZE[HZHeaderRecord]];
 nwords ← (header.npages * Environment.wordsPerPage) - SIZE[HZHeaderRecord];
  i ← 0;
  WHILE i < nwords DO
   addr ← lp + i;
   CWF.WF1["%06u "L, @addr];
  FOR j IN [0 .. MIN[pairsPerLine, nwords - i]) DO
   [n1, n2] ← LOOPHOLE[(addr + j)^, Inline.BytePair];
   CWF.WFC[IF n1 < 040B OR n1 >= 0177B THEN ' ELSE LOOPHOLE[n1, CHAR]];
   CWF.WFC[IF n2 < 040B OR n2 >= 0177B THEN ' ELSE LOOPHOLE[n2, CHAR]];
   ENDLOOP;
  CWF.WFCR[];
  i ← i + pairsPerLine;
  IF h.in.UserAbort[] THEN SIGNAL AbortMyself;
  ENDLOOP;
 header ← header.next;
  ENDLOOP;
 };

-- Space Zone stuff

-- now the procedures
SZAlloc: PROC[zone: UZHandle, size: CARDINAL] RETURNS[lp: LONG POINTER] = {
space: Space.Handle;
npages: CARDINAL ← (size/Environment.wordsPerPage) + 1;
space ← Space.Create[npages, Space.virtualMemory];
Space.Map[space];
lp ← Space.LongPointer[space];
IF npages > 20 THEN Space.CreateUniformSwapUnits[10, space];
};

SZDeAlloc: PROC[zone: UZHandle, object: LONG POINTER] = {
Space.Delete[FindMappedSpace[Space.GetHandle[
 Space.PageFromLongPointer[object]]]];
};

-- now the procedure that will give you one of these
SpaceZone: PUBLIC PROC RETURNS[UNCOUNTED ZONE] = {
RETURN[LOOPHOLE[spacezonehandle]];
};

-- end of heap stuff

-- if nt = login, sets Profile.userName and userPassword
-- if nt = connect, sets name2, password2

GetNameandPassword: PUBLIC PROC[nt: Subr.NameType, name2, password2: LONG STRING,
 h: Subr.TTYProcs] = {
n: LONG STRING ← AllocateString[50];
p: LONG STRING ← AllocateString[50];
{ENABLE UNWIND => {FreeString[n]; FreeString[p]};
IF
nt = login THEN
 UserCredentialsUnsafe.GetUserCredentials[name: n, password: NIL]
ELSE IF nt = connect THEN
 strcpy[n, name2];
--
CWF.WF0["Name: "L];
GetIDProc[h: h, s: n, echo: TRUE];
--
CWF.WF0[" Password: "L];
p.length ← 0;
GetIDProc[h: h, s: p, echo: FALSE];
--
IF nt = login THEN
 UserCredentialsUnsafe.SetUserCredentials[name: n, password: p]
ELSE {
 strcpy[name2, n];
 strcpy[password2, p];
 };
}; -- of ENABLE UNWIND
FreeString[n]; FreeString[p];
};

useIOGetSequence: BOOLFALSE;
GetIDProc: PROC[h: Subr.TTYProcs, s: LONG STRING, echo: BOOL] = {
 input: IO.Handle ← h.in;
 r: Rope.ROPE;
 f: Rope.Text;
IF NOT echo THEN
  input ← h.in.backingStream;
IF input = NIL THEN -- in case it was not an edited stream
  input ← IO.CreateEditedStream[in: h.in, echoTo: h.out];
IF useIOGetSequence
  THEN r ← input.GetSequence[]
  ELSE r ← SpecialGetLine[input];
IF r ~= NIL THEN {
  f ← RopeInline.InlineFlatten[r];
  strcpy[s, LOOPHOLE[f]];
  };
 };

SpecialGetLine: PROC[in: IO.Handle] RETURNS[r: Rope.ROPE] = {
 ch: CHAR;
DO
  ch ← in.GetChar[! IO.Signal => TRUSTED{EXIT}];
  IF ch = '\n THEN EXIT;
  r ← r.Concat[Rope.FromChar[ch]];
  ENDLOOP;
 };

-- here because I wanted to remove reliance on PilotSSImpl and DummyFileCache
FileError: PUBLIC ERROR[error: Subr.FileErrorType] = CODE;

-- npages should not include the leader page; I'll add +1
NewFile: PUBLIC PROC [name: LONG STRING, access: File.Permissions,
 npages: CARDINAL] RETURNS [cap: File.Capability] = {
old: BOOLFALSE;
IF access ~= Subr.Read THEN
 cap ← Directory.CreateFile[name, DCSFileTypes.tLeaderPage, npages + 1
  ! Directory.Error => {
   IF type = fileAlreadyExists THEN old ← TRUE
   ELSE ERROR FileError[notFound];
   CONTINUE
  }]
ELSE old ← TRUE;
IF old THEN
 cap ← Directory.Lookup[fileName: name, permissions: Directory.ignore
  ! Directory.Error => ERROR FileError[notFound]];
cap ← Directory.UpdateDates[cap, access];
IF old AND npages > 0 AND (access = Subr.Write OR access = Subr.ReadWrite) THEN
 File.SetSize[cap, npages + 1];
};

NewStream: PUBLIC PROC [name: LONG STRING, access: File.Permissions]
RETURNS [Stream.Handle] = {
RETURN[FileStream.Create[NewFile[name, access, 0]]];
};

EnumerateDirectory: PUBLIC PROC [proc: PROC [File.Capability, LONG STRING]
RETURNS [BOOL]] =
BEGIN
next: LONG STRING ← AllocateString[100];
name: LONG STRING ← AllocateString[100];
cap: File.Capability;
{ENABLE UNWIND => {FreeString[next]; FreeString[name]};
DO
cap ← Directory.GetNext[pathName: "WorkDir>*"L, currentName: next,
  nextName: next];
StripPathName[name, next];
IF name.length = 0 THEN EXIT;
IF proc[cap, name] THEN EXIT;
ENDLOOP;
}; -- of ENABLE UNWIND
FreeString[next]; FreeString[name]
END;

StripPathName: PROC [noPath, withPath: LONG STRING] = {
pos, len: CARDINAL ← withPath.length;
WHILE pos > 0 AND withPath[pos-1] # '> DO pos ← pos - 1; ENDLOOP;
len ← len - pos;
FOR i: CARDINAL IN [0..len) DO noPath[i] ← withPath[pos+i] ENDLOOP;
noPath.length ← len;
};

-- by convention, the remote name is where the file was retrieved FROM or stored ONTO
RemoteNameProperty: Directory.PropertyType = LOOPHOLE[213B];

-- the maxlength of remotename MUST be 125
SetRemoteFilenameProp: PUBLIC PROC[cap: File.Capability, remotename: LONG STRING] = {
-- remotename should be of the form [ivy]<directory>name!vers
IF remotename.maxlength ~= 125 THEN ERROR;
Directory.PutProperty[cap, RemoteNameProperty,
DESCRIPTOR[remotename, SIZE[StringBody[remotename.maxlength]]], TRUE];
};

-- the maxlength of remotename MUST be 125
GetRemoteFilenameProp: PUBLIC PROC[cap: File.Capability, remotename: LONG STRING] = {
mlen: CARDINAL ← remotename.maxlength;
-- this is erroneous, because reading the property will set the maxlength
IF mlen ~= 125 THEN ERROR;
Directory.GetProperty[cap, RemoteNameProperty,
DESCRIPTOR[remotename, SIZE[StringBody[mlen]]]
 ! Directory.Error => IF type = invalidProperty THEN GOTO leave];
LOOPHOLE[remotename+1, LONG POINTER TO CARDINAL]^ ← mlen; -- reset the maxlength!!!
EXITS
leave => remotename.length ← 0;
};


-- if window is NIL then gets the window that Exec.w is in
CursorInWindow: PUBLIC PROC[h: Subr.TTYProcs] RETURNS[BOOL] = {RETURN[FALSE]};

-- if window is NIL, will use Exec.w
CheckForModify: PUBLIC PROC[file: LONG STRING, h: Subr.TTYProcs]
RETURNS[oktomodify: BOOL] = {
-- short: STRING ← [100];
-- strcpy[short, file];
-- IF NOT Segments.ModifyFile[short] THEN {
-- CWF.WF1["%s cannot be modified.\n"L, file];
-- CWF.WF0["Shall I go ahead and modify it"L];
-- IF Confirm['n, h] = 'y THEN {
  -- CWF.WF0["Yes.\n"L];
  -- RETURN[TRUE];
  -- }
-- ELSE {
  -- CWF.WF0["No.\n"L];
  -- RETURN[FALSE];
  -- };
-- };
RETURN[TRUE];
};

GetCreateDate: PUBLIC PROC[cap: File.Capability]
RETURNS[create: LONG CARDINAL] = {
[create: create] ← FileStream.GetLeaderPropertiesForCapability[cap];
numberofleaders ← numberofleaders + 1;
};

-- leader page
-- (copied straight from [Idun]<APilot60>ComSoft>Private>FileStreamImpl.Mesa)
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 CHAR
];

-- experiments with SModel Nov 81, Rubicon:
-- using CopyIn is 2-5% faster than using Map
-- using Map is 2-5% faster than using FileStream
-- (these times include other things like parsing the DF file,
-- writing the DF file, etc.)

-- space is a 1-page space, and must BE MAPPED (anonymously)
GetCreateDateWithSpace: PUBLIC PROC[cap: File.Capability, space: Space.Handle]
RETURNS[create: LONG CARDINAL] = {
leader: LONG POINTER TO LeaderPage;
Space.CopyIn[space, [cap, 0]];
leader ← Space.LongPointer[space];
create ← leader.create;
numberofleaders ← numberofleaders + 1;
Space.Kill[space];
RETURN[create];
};

-- space is a 1-page space, and must NOT BE MAPPED
OldGetCreateDateWithSpace: PUBLIC PROC[cap: File.Capability, space: Space.Handle]
RETURNS[create: LONG CARDINAL] = {
leader: LONG POINTER TO LeaderPage;
Space.Map[space, [cap, 0]];
leader ← Space.LongPointer[space];
create ← leader.create;
numberofleaders ← numberofleaders + 1;
Space.Unmap[space];
RETURN[create];
};

}.


Confirm: PUBLIC PROC[dch: CHAR, h: Subr.TTYProcs] RETURNS[CHAR] = {
ch: CHAR;
DO
 {
ENABLE h.rubOut => LOOP;
CWF.FWF0[h.putChar, "? "L];
 ch ← h.getChar[];
IF ch = '\n THEN ch ← dch;
 ch ← LowerCase[ch];
RETURN[ch];
 };
ENDLOOP;
};