-- LeafSubrImpl.Mesa, last edit December 30, 1982 2:11 pm
-- Pilot 6.0/ Mesa 7.0
				
DIRECTORY
  CWF: TYPE USING [SWF1, WF0, WF1, WF2, WFCR],
  DCSFileTypes: TYPE USING [tLeaderPage],
  Directory: TYPE USING [CreateFile, Error, GetProps, Handle, ignore, Lookup],
  Environment: TYPE USING [bytesPerPage, wordsPerPage],
  File: TYPE USING [Capability, SetSize],
  FileStream: TYPE USING [Create, SetLeaderPropertiesForCapability, SetLength],
  IFSFile: TYPE USING [AccessFailure, CantOpen, Close, Completer, Error, 
  	FileHandle, FSInstance, GetLength, GetTimes, Initialize, Login, Logout, 
	Open, OpenOptions, Problem, SetCreation, SetLength, StartRead, 
	StartWrite, UnableToLogin],
  Inline: TYPE USING [LowHalf],
  LeafSubr: TYPE USING [],
  LongString: TYPE USING [EquivalentString],
  Space: TYPE USING [CopyIn, CopyOut, Create, Delete, GetAttributes, Handle, Kill, 
  	LongPointer, Map, nullHandle, virtualMemory],
  Stream: TYPE USING [Delete, Handle],
  Subr: TYPE USING [AbortMyself, AllocateString, Any, CopyString, FileError, 
  	FreeString, GetNameandPassword, LongZone, Prefix, TTYProcs],
  UserCredentialsUnsafe: TYPE USING[GetUserCredentials];

LeafSubrImpl: MONITOR
IMPORTS CWF, Directory, File, FileStream, IFSFile, Inline, 
	LongString, Space, Stream, Subr, UserCredentialsUnsafe
EXPORTS LeafSubr = {

-- MDS USAGE !!!
connseq: LONG POINTER TO ConnSeqRecord ← NIL;
-- endof MDS USAGE !!!

-- connection table
MAXCONNS: CARDINAL = 8;
-- we use the username and password from Profile.userName, userPassword
ConnSeqRecord: TYPE = RECORD[
	user2: LONG STRING ← NIL,	-- connect name
	password2: LONG STRING ← NIL,	-- connect password
	size: CARDINAL ← 0,	-- # of connections
	body: SEQUENCE maxsize: CARDINAL OF ConnTable
	];
ConnTable: TYPE = RECORD[
	fsinstance: IFSFile.FSInstance ← NIL,
	host: LONG STRING ← NIL 
	];

Open: PUBLIC PROC[host, sfn: LONG STRING, h: Subr.TTYProcs,
	openOptions: IFSFile.OpenOptions] 
	RETURNS[fh: IFSFile.FileHandle] = {
fsinstance: IFSFile.FSInstance ← Connect[host,h, NIL, NIL, NIL, NIL];
fh ← IFSFile.Open[fsinstance, sfn, openOptions
	! IFSFile.CantOpen =>
		SELECT reason FROM
		ok => NULL -- can't happen -- ;
		io, alreadyExists, other => ERROR IFSFile.CantOpen[reason];
		accessDenied, accessConflict => {
			SetOtherPass[h, "access Denied or Conflict"L];
			fsinstance ← ForceClosed[fsinstance];
			fsinstance ← Connect[host, h, NIL, NIL, connseq.user2,
				connseq.password2];
			RETRY;
			};
		notFound, illegalFileName => ERROR Subr.FileError[notFound];
		ENDCASE => ERROR
	];
};

-- base is # of page to start mapping on
-- note that here, base = 0 is the first page of data
-- whereas in Pilot base = 1 is the first page
-- space may have arbitrary size (> 64k words)
RemoteMap: PUBLIC PROC[space: Space.Handle, fh: IFSFile.FileHandle, base: CARDINAL] = {
problem: IFSFile.Problem;
notready: BOOL;
finished: CONDITION ← [timeout: 0];

	DoRead: ENTRY PROC [bytebase: LONG CARDINAL, bytesize: CARDINAL,
		buffer: LONG POINTER] = {
	notready ← TRUE;
	IFSFile.StartRead[fh, bytebase, bytesize, buffer, CallProc, 0];
	WHILE notready DO
		WAIT finished;
		ENDLOOP;
	SELECT problem FROM
	ok => NULL;
	io, resources, other, credentials => 
		RETURN WITH ERROR IFSFile.Error[problem];
	ENDCASE => ERROR;
	};
	
	CallProc: ENTRY IFSFile.Completer = {
	problem ← outcome;
	notready ← FALSE;
	NOTIFY finished;
	};
	
{
bytebase: LONG CARDINAL ← base*Environment.bytesPerPage;
buffer: LONG POINTER ← Space.LongPointer[space];
filelength: LONG CARDINAL ← IFSFile.GetLength[fh];
pagesize: CARDINAL;
bytesize: LONG CARDINAL;

Space.Map[space];
[size: pagesize] ← Space.GetAttributes[space];
bytesize ← LONG[pagesize] * Environment.bytesPerPage;
IF bytebase + bytesize > filelength THEN {
	IF filelength >= bytebase THEN
		bytesize ← filelength - bytebase
	ELSE ERROR; 	-- base is a page number bigger than the file
	};
-- leaf can handle up to 64K in the protocol
-- the existing leaf servers can only handle up to 32K because of bugs
WHILE bytesize > 0 DO
	thisTimeSize: CARDINAL = Inline.LowHalf[MIN[bytesize, LONG[63*512]]];
	DoRead[bytebase, thisTimeSize, buffer];
	bytesize ← bytesize - thisTimeSize;
	bytebase ← bytebase + thisTimeSize;
	buffer ← buffer + (thisTimeSize/2);
	ENDLOOP;
}};

NPages: CARDINAL = 4;
NBytes: CARDINAL = NPages * Environment.bytesPerPage;

BytesAtATime: PROC[len: LONG CARDINAL] RETURNS[nb: CARDINAL] = INLINE {
RETURN[IF len > NBytes THEN NBytes ELSE Inline.LowHalf[len]];
};

RemoteCopy: PUBLIC PROC[fromHost, fromFilename, toHost, toFilename: LONG STRING,
	h: Subr.TTYProcs] RETURNS[length: LONG CARDINAL] = {
createtime, len: LONG CARDINAL;
fhRead, fhWrite: IFSFile.FileHandle ← NIL;
buffer: ARRAY[0 .. NPages*Environment.wordsPerPage) OF WORD;
base: CARDINAL ← 0;
problem: IFSFile.Problem;
notready: BOOL;
finished: CONDITION ← [timeout: 0];

{
ENABLE UNWIND => {
	IF fhRead ~= NIL THEN IFSFile.Close[fhRead];  fhRead ← NIL;
	IF fhWrite ~= NIL THEN IFSFile.Close[fhWrite]; fhWrite ← NIL;
	};
	
	DoRead: ENTRY PROC = {
	notready ← TRUE;
	IFSFile.StartRead[fhRead, base*Environment.bytesPerPage, 
		BytesAtATime[len], @buffer, CallProc, 0];
	WHILE notready DO
		WAIT finished;
		ENDLOOP;
	SELECT problem FROM
	ok => NULL;
	io, resources, other, credentials => 
		RETURN WITH ERROR IFSFile.Error[problem];
	ENDCASE => ERROR;
	};
	
	DoWrite: ENTRY PROC = {
	notready ← TRUE;
	IFSFile.StartWrite[fhWrite, base*Environment.bytesPerPage, 
		BytesAtATime[len], @buffer, CallProc, 0];
	WHILE notready DO
		WAIT finished;
		ENDLOOP;
	SELECT problem FROM
	ok => NULL;
	io, resources, other, credentials => 
		RETURN WITH ERROR IFSFile.Error[problem];
	ENDCASE => ERROR;
	};
	
	CallProc: ENTRY IFSFile.Completer = {
	problem ← outcome;
	notready ← FALSE;
	NOTIFY finished;
	};

fhRead ← Open[fromHost, fromFilename, h, oldReadOnly];
fhWrite ← Open[toHost, toFilename, h, new];
[create: createtime] ← IFSFile.GetTimes[fhRead];
length ← len ← IFSFile.GetLength[fhRead];
IFSFile.SetLength[fhWrite, length];
WHILE len > 0 DO
	DoRead[];
	DoWrite[];
	len ← len - BytesAtATime[len];
	base ← base + NPages;
	ENDLOOP;
IFSFile.SetCreation[fhWrite, createtime];
IFSFile.Close[fhRead];
fhRead ← NIL;
IFSFile.Close[fhWrite];
fhWrite ← NIL;
}};

	
Retrieve: PUBLIC PROC[remoteHost, remoteFilename, localFilename: LONG STRING,
	h: Subr.TTYProcs] 
	RETURNS[length: LONG CARDINAL, cap: File.Capability] = {
npages, base: CARDINAL;
buffer: LONG POINTER;
len, create, read, write: LONG CARDINAL;
sh: Stream.Handle ← NIL;
fh: IFSFile.FileHandle ← NIL;
space: Space.Handle ← Space.nullHandle;
problem: IFSFile.Problem;
notready: BOOL;
finished: CONDITION ← [timeout: 0];

{
ENABLE UNWIND => {
	IF sh ~= NIL THEN Stream.Delete[sh]; sh ← NIL;
	IF space ~= Space.nullHandle THEN Space.Delete[space]; space ← Space.nullHandle;
	IF fh ~= NIL THEN IFSFile.Close[fh]; fh ← NIL;
	};
	
	DoReadAndWrite: ENTRY PROC = {
	base ← 0;
	WHILE len > 0 DO
		notready ← TRUE;
		IFSFile.StartRead[fh, base*Environment.bytesPerPage,
			BytesAtATime[len], buffer, CallProc, 0];
		WHILE notready DO
			WAIT finished;
			ENDLOOP;
		SELECT problem FROM
		ok => NULL;
		io, resources, other, credentials => 
			RETURN WITH ERROR IFSFile.Error[problem];
		ENDCASE => ERROR;
		Space.CopyOut[space, [cap, base+1]];
		base ← base + NPages;
		len ← len - BytesAtATime[len];
		Space.Kill[space];
		ENDLOOP;
	};
	
	CallProc: ENTRY IFSFile.Completer = {
	problem ← outcome;
	notready ← FALSE;
	NOTIFY finished;
	};

fh ← Open[remoteHost, remoteFilename, h, oldReadOnly];
len ← length ← IFSFile.GetLength[fh];
npages ← Inline.LowHalf[length/Environment.bytesPerPage] + 2;
cap ← Directory.CreateFile[localFilename, DCSFileTypes.tLeaderPage, npages
	! Directory.Error => {
			IF type = fileAlreadyExists THEN {
				cap ← Directory.Lookup[fileName: localFilename, 
					permissions: Directory.ignore];
				File.SetSize[cap, npages];
				}
			ELSE ERROR Subr.FileError[notFound];
			CONTINUE
		}];
space ← Space.Create[NPages, Space.virtualMemory];
buffer ← Space.LongPointer[space];
Space.Map[space];
DoReadAndWrite[];
Space.Delete[space];
space ← Space.nullHandle;
[read, write, create] ← IFSFile.GetTimes[fh];
sh ← FileStream.Create[cap];
FileStream.SetLength[sh, length];
FileStream.SetLeaderPropertiesForCapability[cap: cap, 
	create: LOOPHOLE[create], write: LOOPHOLE[write], read: LOOPHOLE[read]];
Stream.Delete[sh];
sh ← NIL;
IFSFile.Close[fh];
fh ← NIL;
}};
	
	
Store: PUBLIC PROC[localFilename, remoteHost, remoteFilename: LONG STRING,
	h: Subr.TTYProcs] RETURNS[length: LONG CARDINAL] = {
base: CARDINAL;
buffer: LONG POINTER;
len, create: LONG CARDINAL;
fh: IFSFile.FileHandle ← NIL;
space: Space.Handle ← Space.nullHandle;
problem: IFSFile.Problem;
notready: BOOL;
finished: CONDITION ← [timeout: 0];
cap: File.Capability;
directoryfilename: STRING ← [100];

{
ENABLE UNWIND => {
	IF space ~= Space.nullHandle THEN Space.Delete[space]; space ← Space.nullHandle;
	IF fh ~= NIL THEN IFSFile.Close[fh]; fh ← NIL;
	};
	
	DoReadAndWrite: ENTRY PROC = {
	base ← 0;
	WHILE len > 0 DO
		notready ← TRUE;
		Space.CopyIn[space, [cap, base+1]];
		IFSFile.StartWrite[fh, base*Environment.bytesPerPage,
			BytesAtATime[len], buffer, CallProc, 0];
		WHILE notready DO
			WAIT finished;
			ENDLOOP;
		SELECT problem FROM
		ok => NULL;
		io, resources, other, credentials => 
			RETURN WITH ERROR IFSFile.Error[problem];
		ENDCASE => ERROR;
		base ← base + NPages;
		len ← len - BytesAtATime[len];
		Space.Kill[space];
		ENDLOOP;
	};
	
	CallProc: ENTRY IFSFile.Completer = {
	problem ← outcome;
	notready ← FALSE;
	NOTIFY finished;
	};

fh ← Open[remoteHost, remoteFilename, h, new];
space ← Space.Create[NPages, Space.virtualMemory];
buffer ← Space.LongPointer[space];
Space.Map[space];
cap ← Directory.Lookup[fileName: localFilename, permissions: Directory.ignore];
[byteLength: length, createDate: create] ← Directory.GetProps[cap, directoryfilename];
len ← length;
IFSFile.SetLength[fh, length];
DoReadAndWrite[];
Space.Delete[space];
space ← Space.nullHandle;
IFSFile.SetCreation[fh, create];
IFSFile.Close[fh];
fh ← NIL;
}};
	
PrintLeafProblem: PUBLIC PROC[problem: IFSFile.Problem] = {
CWF.WF2["Leaf Error: %s, #%u in IFSFile.Problem.\n"L, 
	(SELECT problem FROM
	ok => "ok"L,
	io => "io"L,
	resources => "resources"L,
	credentials => "credentials"L,
	illegalIO => "illegalIO"L,
	other => "other"L,
	ENDCASE => ERROR),
	@problem];
};

PrintLeafAccessFailure: PUBLIC PROC[reason: IFSFile.AccessFailure] = {
CWF.WF2["Leaf Error: %s, #%u in IFSFile.AccessFailure.\n"L, 
	(SELECT reason FROM
	ok => "ok"L,
	io => "io"L,
	notFound => "notFound"L,
	alreadyExists => "alreadyExists"L,
	accessDenied => "accessDenied"L,
	accessConflict => "accessConflict"L,
	illegalFileName => "illegalFileName"L,
	other => "other"L,
	ENDCASE => ERROR),
	@reason];
};
--
SetOtherPass: PROC[h: Subr.TTYProcs, error: STRING] = {
CheckStarted[];
CWF.WF1["Error '%s'\nEnter Connect "L, error];
Subr.GetNameandPassword[connect, connseq.user2, connseq.password2, h];
-- by convention, if username and password are both 0 length, 
-- then we just abort the program
IF connseq.user2.length = 0 AND connseq.password2.length = 0 THEN {
	IF h.Confirm[h.in, h.out, h.data,
	  "\nType y to retry with different login name, n to abort ", 'n] = 'n THEN {
		CWF.WF0["No.\n"L];
		SIGNAL Subr.AbortMyself;
		};
	CWF.WF0["Yes.\nEnter "L];
	Subr.GetNameandPassword[login, NIL, NIL, h];
	CWF.WFCR[];
	};
};

Connect: PROC[host: LONG STRING, h: Subr.TTYProcs, 
	defaultUsername, defaultPassword, secondaryName, secondaryPassword: LONG STRING] 
	RETURNS[IFSFile.FSInstance] = {
StartLeaf[];
FOR i: CARDINAL IN [0..connseq.size) DO
	IF connseq[i].host ~= NIL 
	AND LongString.EquivalentString[host, connseq[i].host]
	THEN
		RETURN[connseq[i].fsinstance];
	ENDLOOP;
connseq[connseq.size].fsinstance ← MakeFSInstance[host, h, 
	defaultUsername, defaultPassword, secondaryName, secondaryPassword];
connseq[connseq.size].host ← Subr.CopyString[host];
IF connseq.size >= connseq.maxsize THEN {
	CWF.WF0["Error - to many conns\n"L];
	RETURN[NIL];
	};
connseq.size ← connseq.size + 1;
RETURN[connseq[connseq.size-1].fsinstance];
};

ForceClosed: PROC[fsinstance: IFSFile.FSInstance] RETURNS[IFSFile.FSInstance] = {
FOR i: CARDINAL IN [0 .. connseq.size) DO
	IF fsinstance = connseq[i].fsinstance THEN 
		CloseAndFree[i];
	ENDLOOP;
RETURN[NIL];
};

CloseAndFree: PROC[i: CARDINAL] = {
CWF.WF1["Closing Leaf connection to %s ... "L, connseq[i].host];
IFSFile.Logout[connseq[i].fsinstance];
CWF.WF0["closed.\n"L];
Subr.FreeString[connseq[i].host];
connseq[i].host ← NIL;
connseq[i].fsinstance ← NIL;
};

-- always returns NIL
MakeFSInstance: PROC[host: LONG STRING, h: Subr.TTYProcs, 
	defaultUsername, defaultPassword, secondaryName, secondaryPassword: LONG STRING] 
	RETURNS[fsinstance: IFSFile.FSInstance] = {
IF host = NIL THEN {
	CWF.WF0["Error: remote host has not been specified.\n"L];
	RETURN[NIL];
	};
CWF.WF1["Opening Leaf connection to %s.\n"L, host];
IF defaultUsername ~= NIL THEN {
	fsinstance ← IFSFile.Login[host, defaultUsername, defaultPassword, 
		secondaryName, secondaryPassword
		! IFSFile.UnableToLogin =>
			IF reason = credentials THEN GOTO normal;
		];
	RETURN[fsinstance];
	};
GOTO normal;
EXITS
normal => {
	n: STRING ← [50];
	p: STRING ← [50];
	AddUserName[NIL, NIL, h];
	UserCredentialsUnsafe.GetUserCredentials[name: n, password: p];
	fsinstance ← IFSFile.Login[host, -- NameAndPasswordOps.userName -- n,
		-- NameAndPasswordOps.userPassword -- p, secondaryName, secondaryPassword
		! IFSFile.UnableToLogin =>
			IF reason = credentials THEN {
				SetPass[h, NIL];
				RETRY;
				}
			];
	};
				
};

SetPass: PROC[h: Subr.TTYProcs, error: LONG STRING] = {
CheckStarted[];
IF error ~= NIL THEN CWF.WF1["Error '%s'\n"L,error];
CWF.WF0["Enter "L];
Subr.GetNameandPassword[login, NIL, NIL, h];
CWF.WFCR[];
};

AddUserName: PROC[sto: LONG STRING, spat: STRING, h: Subr.TTYProcs] = {
-- u: STRING;
-- p: STRING ← NameAndPasswordOps.userPassword;
u: STRING ← [50];
p: STRING ← [50];
lengthzero: BOOL;
UserCredentialsUnsafe.GetUserCredentials[name: NIL, password: p];
lengthzero ← p = NIL OR p.length = 0;
DO
	-- u ← NameAndPasswordOps.userName;
	UserCredentialsUnsafe.GetUserCredentials[name: u, password: NIL];
	IF u = NIL
     OR u.length = 0 
     OR Subr.Any[u,' ]
     OR Subr.Prefix[u, "CedarUser"L] 
     OR lengthzero THEN {
		lengthzero ← FALSE;
		CWF.WF0["Enter "L];
		Subr.GetNameandPassword[login, NIL, NIL, h];
		CWF.WFCR[];
		}
	ELSE EXIT;
	ENDLOOP;
IF sto ~= NIL THEN CWF.SWF1[sto,spat,u];
};

	
StartLeaf: PROC = {
longzone: UNCOUNTED ZONE;
IF connseq ~= NIL THEN RETURN;
longzone ← Subr.LongZone[];
connseq ← longzone.NEW[ConnSeqRecord[MAXCONNS]];
connseq.user2 ← Subr.AllocateString[40];
connseq.password2 ← Subr.AllocateString[40];
};

StopLeaf: PUBLIC PROC = {
ENABLE UNWIND => connseq ← NIL;
longzone: UNCOUNTED ZONE;
IF connseq = NIL THEN RETURN;
longzone ← Subr.LongZone[];
FOR i: CARDINAL IN [0..connseq.size) DO
	IF connseq[i].fsinstance ~= NIL THEN CloseAndFree[i];
	ENDLOOP;
Subr.FreeString[connseq.user2];
Subr.FreeString[connseq.password2];
longzone.FREE[@connseq];
-- never called IFSFile.Finalize[];
};

CheckStarted: PROC = {
IF connseq = NIL THEN ERROR;
};

IFSFile.Initialize[];
}.