GVPDriver.mesa, this module provides an interface to the GVPatch server
HGM, May 23, 1984 10:45:20 pm PDT
Steve Temple, November 18, 1982 11:10 am
This module implements the interface to the GVPatch program running on the Grapevine
server which we are mending. The only global data here is the bytestream. In general if
anything goes wrong with a server operation we close the stream just to be on the safe side.
All the basic server operation procs open a stream if need be.
DIRECTORY
FS USING [Error, StreamOpen],
GVPDefs,
IO USING [Close, GetLength, PutChar, PutF, PutFR, PutText, STREAM, SetIndex, SetLength, UnsafeGetBlock, UnsafePutBlock],
GVProtocol USING [CreateStream, Close, Failed, Handle, ReceiveByte, ReceiveBytes, ReceiveCount, ReceiveGVString, SendByte, SendBytes, SendCount, SendNow, SendRName, SendGVString],
PupDefs USING [PupAddress, PupNameLookup, PupNameTrouble, PupPackageDestroy, PupPackageMake, PupSocketID],
Rope USING [Concat, Fetch, Length],
UserCredentials USING [Get];
GVPDriver: CEDAR PROGRAM
IMPORTS
FS, GVPDefs, IO, GVProtocol, PupDefs, Rope, UserCredentials
EXPORTS GVPDefs =
BEGIN OPEN GVPDefs;
byteStream: GVProtocol.Handle ← NIL;
KillByteStream checks to see if the stream is alive (i.e. the REF to it is non-NIL) and if it
is it destroys the stream and NILs out the REF.
KillByteStream:
PROC[h: GVPRef] =
BEGIN
IF byteStream#
NIL
THEN
BEGIN
GVProtocol.Close[byteStream];
h.logStream.PutText["\nConnection with server broken\n"]
END;
byteStream ← NIL
END;
CheckByteStream is called by all procs which implement basic server operations. It
assumes no stream exists if the REF is NIL and creates one on that basis.
CheckByteStream:
PROC[h: GVPRef]
RETURNS [r:
ROPE ←
NIL]=
BEGIN
IF byteStream=NIL THEN r ← ConnectToServer[h]
END;
ConnectToServer opens the bytestream to the server end of the GVPatch system. It first
looks up the name the user has provided and then opens a stream to the GVPatch socket
on that machine (socket number 047B). If this goes OK it sends the login command byte and
the name and password of the logged in user (in clear text!). If the login command byte
is reflected we're home and dry. If anything fails we close the stream and drop out.
ConnectToServer: PROC[h: GVPRef] RETURNS [r: ROPE ← NIL] = BEGIN
serverAddress: PupDefs.PupAddress;
GVPatchSocket: PupDefs.PupSocketID = [0, 47B];
user, password, server: ROPE;
server ← GetServerName[];
Set[Rope.Concat["Trying to connect to server ", server]];
serverAddress ← PupDefs.PupNameLookup[GVPatchSocket, server ! PupDefs.PupNameTrouble =>
BEGIN
r ← IO.PutFR["Name lookup trouble, %g", [rope[e]]];
GOTO fail
END];
byteStream ← GVProtocol.CreateStream[serverAddress, none, 600 ! GVProtocol.Failed =>
BEGIN
r ← "Unable to open byte stream";
GOTO fail
END];
[user, password] ← UserCredentials.Get[];
GVProtocol.SendByte[byteStream, login];
GVProtocol.SendRName[byteStream, user];
GVProtocol.SendGVString[byteStream, password];
GVProtocol.SendNow[byteStream];
r ← ServerError[h, login ! GVProtocol.Failed =>
BEGIN
r ← "Server timed out during login";
GOTO fail
END];
IF r#NIL THEN GOTO fail;
h.logStream.PutF["\nConnected to Grapevine server %l%g%l\n",
[rope["b"]], [rope[server]], [rope[" "]]];
Set["Connected to server"]
EXITS fail => KillByteStream[h];
END;
RestartServer has the job of getting the server back to life again. It first wraps up the
command line that the user supplied in the file rem.cm and sends that to the server.
It then sends the restart command byte and waits for a reply before announcing the
restart.
RestartServer:
PUBLIC
PROC [h: GVPRef, line:
ROPE]
RETURNS[r:
ROPE] =
BEGIN
ENABLE GVProtocol.Failed => {r ← "Byte stream trouble"; KillByteStream[h]; GOTO fail};
remcm:
IO.
STREAM = FS.StreamOpen[fileName: "rem.cm", accessOptions: create ! FS.Error =>
{r ← "Unable to open local file rem.cm"; GOTO fail}];
Set["Creating local file rem.cm"];
FOR i:
INT
IN [0..Rope.Length[line])
DO remcm.PutChar[Rope.Fetch[line, i]] ENDLOOP;
remcm.PutChar['\n];
remcm.Close[];
r ← CheckByteStream[h];
IF r#NIL THEN RETURN;
r ← WriteFile[h, "rem.cm"];
IF r#NIL THEN RETURN;
Set["Restarting server"];
GVProtocol.SendByte[byteStream, restartServer];
GVProtocol.SendNow[byteStream];
r ← ServerError[h, restartServer];
IF r#NIL THEN GOTO fail;
h.logStream.PutF["\n%lServer restarted%l with command - %g\n",
[rope["b"]], [rope[" "]], [rope[line]]];
Set[];
KillByteStream[h]
EXITS fail => NULL
END;
ServerError waits for a reply byte from the server after we've sent a command. The two
cases we allow for are 1) the command succeeded and we get the command byte echoed or
b) the command failed and we get an error byte and a string which we return as a rope.
If neither of these we raise a protocol error so the caller will close the stream.
ServerError:
PROC [h: GVPRef, command: Byte]
RETURNS [r:
ROPE] =
BEGIN
reply: Byte ← GVProtocol.ReceiveByte[byteStream];
IF reply = errorCode THEN
BEGIN
r ← Rope.Concat["Server: ", GVProtocol.ReceiveGVString[byteStream]]
END
ELSE IF reply # command THEN ERROR GVProtocol.Failed[protocolError, "?? reply # command"]
END;
GetHeapFile reads the heap from the server. It first reads the file heap.segments and then
asks for the heap structure from the server. The first thing in the structure info is the heap
size and we try to open a file of that size before proceeding. We then get the structure on
a page by page basis (note that the object bodies contain garbage). We also create
the file heap.map (the page map) and fill it with the "empty" entry.
GetHeapFile: PUBLIC PROC [h: GVPRef] RETURNS [r: ROPE ← NIL] = BEGIN
ENABLE GVProtocol.Failed => {r ← "Byte stream trouble"; KillByteStream[h]; GOTO fail};
bothHdrByteSize: CARDINAL = pageHdrByteSize+objHdrByteSize;
fileStream: IO.STREAM;
mapStream:
IO.
STREAM =
FS.StreamOpen[fileName: "heap.map", accessOptions: create
! FS.Error => {r ← "Trouble opening heap.map here"; GOTO fail}];
pageBuffer: REF PageByteVec = NEW[PageByteVec];
RxVec: TYPE = PACKED ARRAY [0..bothHdrByteSize) OF Byte;
rxBuffer: REF RxVec ← NEW[RxVec];
toIndex: CARDINAL;
heapSize, mapSize: CARDINAL;
heapByteSize: INT;
r ← CheckByteStream[h];
IF r#NIL THEN GOTO fail;
r ← ReadFile[h, "heap.segments"];
IF r#NIL THEN GOTO fail;
Set["Reading heap structure"];
GVProtocol.SendByte[byteStream, readHeaders];
GVProtocol.SendNow[byteStream];
r ← ServerError[h, readHeaders];
IF r#NIL THEN GOTO fail;
heapSize ← GVProtocol.ReceiveCount[byteStream]; -- pages
mapSize ← (heapSize/segSize)*segSize;
heapByteSize ← LONG[heapSize]*bytesPerPage;
fileStream ←
FS.StreamOpen[fileName: "heap.data", accessOptions: create, createByteCount: heapByteSize
! FS.Error => {r ← "Trouble opening heap.data here"; GOTO fail} ];
FOR curPage:
CARDINAL
IN [1..heapSize]
DO
-- copy one page
objCount: CARDINAL ← GVProtocol.ReceiveCount[byteStream];
IF curPage
MOD 10 = 0
THEN Set[IO.PutFR["Reading page %d of %d", [cardinal[curPage]], [cardinal[heapSize]]]];
TRUSTED { GVProtocol.ReceiveBytes[byteStream, [LOOPHOLE[rxBuffer], 0, bothHdrByteSize]]; };
FOR i:
CARDINAL
IN [0..bothHdrByteSize)
DO
-- copy page and first object header
pageBuffer[i] ← rxBuffer[i]
ENDLOOP;
toIndex ← pageHdrByteSize;
TRUSTED {
THROUGH [0..objCount-1)
DO
foo: LONG POINTER = LOOPHOLE[pageBuffer];
objH: LONG POINTER TO ObjectHeader = foo + toIndex/bytesPerWord;
toIndex ← toIndex+objHdrByteSize+objH.size*bytesPerWord;
GVProtocol.ReceiveBytes[byteStream, [LOOPHOLE[rxBuffer], 0, objHdrByteSize]];
FOR i:
CARDINAL
IN [0..objHdrByteSize)
DO
pageBuffer[toIndex+i] ← rxBuffer[i]
ENDLOOP
ENDLOOP; };
fileStream.UnsafePutBlock[[LOOPHOLE[pageBuffer], 0, pageByteSize]];
ENDLOOP;
h.logStream.PutF["\nFile %lheap.data%l created, size is %d bytes\n",
[rope["b"]], [rope[" "]], [cardinal[LONG[heapSize]*pageByteSize]]];
FOR i:
CARDINAL
IN [0..mapSize)
DO
mapStream.PutChar[emptyPage]
ENDLOOP;
mapStream.Close[];
h.logStream.PutF["\nFile %lheap.map%l created, size is %d bytes\n",
[rope["b"]], [rope[" "]], [cardinal[mapSize]]];
fileStream.Close[];
r ← CheckStructure[h]
EXITS fail => NULL
END;
OpenFile tries to open a file on the server. We can specify whether it should already exist
or not. The file will always be opened for writing. We get back the size of the file in pages
and bytes if the open succeeds. If another file on the server was open it is closed by this
operation. Repeatedly opening the same file is noted by the server and handled efficiently.
OpenFile:
PROC[h: GVPRef, fileName:
ROPE, oldNew: Byte]
RETURNS [r: ROPE ← NIL, page, byte: CARDINAL] = BEGIN
ENABLE GVProtocol.Failed => {r ← "Byte stream trouble"; GOTO fail};
r ← CheckByteStream[h];
IF r#NIL THEN GOTO fail;
GVProtocol.SendByte[byteStream, openFile];
GVProtocol.SendGVString[byteStream, fileName];
GVProtocol.SendByte[byteStream, oldNew];
GVProtocol.SendNow[byteStream];
r ← ServerError[h, openFile];
IF r =
NIL
THEN
BEGIN
page ← GVProtocol.ReceiveCount[byteStream];
byte ← GVProtocol.ReceiveCount[byteStream]
END
EXITS fail => KillByteStream[h]
END;
SetFileLength sets the length of a file on the server. The file is assumed to have been opened
already with OpenFile (above). An error will come from the server if no file is open.
SetFileLength: PROC[h: GVPRef, page, byte: CARDINAL] RETURNS [r: ROPE ← NIL] = BEGIN
ENABLE GVProtocol.Failed => {r ← "Byte stream trouble"; GOTO fail};
r ← CheckByteStream[h];
IF r#NIL THEN GOTO fail;
GVProtocol.SendByte[byteStream, setLength];
GVProtocol.SendCount[byteStream, page];
GVProtocol.SendCount[byteStream, byte];
GVProtocol.SendNow[byteStream];
r ← ServerError[h, setLength];
EXITS fail => KillByteStream[h]
END;
ReadPage gets a page from the currently open file on the server. We get an error from the
server if no file is open or if the page number is out of range (of the file size).
ReadPage:
PUBLIC
PROC[h: GVPRef, page:
CARDINAL, pageBuffer:
REF PageByteVec]
RETURNS[r: ROPE ← NIL] = BEGIN
ENABLE GVProtocol.Failed => {r ← "Byte stream trouble"; GOTO fail};
r ← CheckByteStream[h];
IF r#NIL THEN GOTO fail;
GVProtocol.SendByte[byteStream, readPage];
GVProtocol.SendCount[byteStream, page];
GVProtocol.SendNow[byteStream];
r ← ServerError[h, readPage];
IF r#NIL THEN GOTO fail;
TRUSTED { GVProtocol.ReceiveBytes[byteStream, [LOOPHOLE[pageBuffer], 0, pageByteSize]]; };
EXITS fail => KillByteStream[h]
END;
WritePage writes a page of data to the server's open file. We are only allowed to write in
existing pages of the file but note that if we write to the last page the length is extended
to just beyond that page and so incremental writing of pages will extend the file sensibly.
WritePage:
PUBLIC
PROC[h: GVPRef, page:
CARDINAL, pageBuffer:
REF PageByteVec]
RETURNS[r: ROPE ← NIL] = BEGIN
ENABLE GVProtocol.Failed => {r ← "Byte stream trouble"; GOTO fail};
r ← CheckByteStream[h];
IF r#NIL THEN GOTO fail;
GVProtocol.SendByte[byteStream, writePage];
GVProtocol.SendCount[byteStream, page];
GVProtocol.SendBytes[byteStream, [LOOPHOLE[pageBuffer], 0, pageByteSize]];
GVProtocol.SendNow[byteStream];
r ← ServerError[h, writePage];
EXITS fail => KillByteStream[h]
END;
ReadFile reads a file from the server to a file on the local machine. The names of both
files must be the same. The event is logged.
ReadFile: PUBLIC PROC[h: GVPRef, name: ROPE] RETURNS[r: ROPE ← NIL] = BEGIN
page, byte, pagesToRead: CARDINAL;
fileStream: IO.STREAM;
pageBuffer: REF PageByteVec ← NIL;
fileByteSize: INT;
fileStream ←
FS.StreamOpen[fileName: name, accessOptions: create !
FS.Error => {r ← "Trouble opening local file"; GOTO fail}];
[r, page, byte] ← OpenFile[h, name, oldFile];
IF r#NIL THEN GOTO fail;
Set[Rope.Concat["Reading file ", name]];
pagesToRead ← IF byte=0 AND page#0 THEN page ELSE page+1;
fileByteSize ← LONG[page]*pageByteSize+byte;
pageBuffer ← NEW[PageByteVec];
FOR i:
CARDINAL
IN [0..pagesToRead)
DO
r ← ReadPage[h, i, pageBuffer];
IF r#NIL THEN GOTO fail;
fileStream.UnsafePutBlock[[LOOPHOLE[pageBuffer], 0, pageByteSize]];
ENDLOOP;
fileStream.SetLength[fileByteSize];
fileStream.Close[];
h.logStream.PutF["\nFile %l%g%l read from server, size is %d bytes\n",
[rope["b"]], [rope[name]], [rope[" "]], [cardinal[fileByteSize]]]
EXITS fail => NULL
END;
WriteFile copies a named local file to a file with the same name on the server and logs
the fact for us.
WriteFile: PUBLIC PROC[h: GVPRef, name: ROPE] RETURNS[r: ROPE ← NIL] = BEGIN
page, byte, pagesToWrite: CARDINAL;
length: INT;
fileStream: IO.STREAM;
pageBuffer: REF PageByteVec ← NIL;
fileStream ←
FS.StreamOpen[fileName: name, accessOptions: read
! FS.Error => {r ← "Trouble opening local file";
GOTO fail}];
[r, page, byte] ← OpenFile[h, name, oldOrNewFile];
IF r#NIL THEN GOTO fail;
Set[Rope.Concat["Writing file ", name]];
length ← fileStream.GetLength[];
pagesToWrite ← (length+pageByteSize-1) / pageByteSize;
page ← length / pageByteSize;
byte ← length MOD pageByteSize;
pageBuffer ← NEW[PageByteVec];
FOR i:
CARDINAL
IN [0..pagesToWrite)
DO
TRUSTED { [] ← fileStream.UnsafeGetBlock[[LOOPHOLE[pageBuffer], 0, pageByteSize]]; };
r ← WritePage[h, i, pageBuffer];
IF r#NIL THEN {pageBuffer ← NIL; GOTO fail}
ENDLOOP;
r ← SetFileLength[h, page, byte];
IF r#NIL THEN GOTO fail;
h.logStream.PutF["\nFile %l%g%l written to server, size is %d bytes\n",
[rope["b"]], [rope[name]], [rope[" "]], [cardinal[length]]]
EXITS fail => NULL
END;
SetServerLength just opens a server file and sets its length for us. The event is logged.
We insist that the file already exist.
SetServerLength:
PUBLIC
PROC[h: GVPRef, name:
ROPE, pages, bytes:
CARDINAL]
RETURNS[r: ROPE ← NIL] = BEGIN
[r: r] ← OpenFile[h, name, oldFile];
IF r#NIL THEN RETURN;
r ← SetFileLength[h, pages, bytes];
IF r#NIL THEN RETURN;
h.logStream.PutF["\nLength of server file %l%g%l set to %d pages, %d bytes\n",
[rope["b"]], [rope[name]], [rope[" "]], [cardinal[pages]], [cardinal[bytes]]]
END;
SetLocalLength sets the length of a (pre-existing) local file and logs the event.
SetLocalLength:
PUBLIC
PROC[h: GVPRef, name:
ROPE, pages, bytes:
CARDINAL]
RETURNS[r: ROPE ← NIL] = BEGIN
fileStream:
IO.
STREAM =
FS.StreamOpen[fileName: name, accessOptions: write ! FS.Error =>
BEGIN
r ← "Unable to open local file";
GOTO fail
END];
fileStream.SetLength[pages*bytesPerPage+bytes];
fileStream.Close[];
h.logStream.PutF["\nLength of local file %l%g%l set to %d pages, %d bytes\n",
[rope["b"]], [rope[name]], [rope[" "]], [cardinal[pages]], [cardinal[bytes]]]
EXITS fail => NULL
END;
ReadServerPage just opens the file heap.data on the server and reads a page from it into the
buffer that we supply. Note that the OpenFile operation on the server is implemented in such
a way that succesive opens of the same file are handled efficiently.
ReadServerPage:
PUBLIC
PROC[h: GVPRef, page:
CARDINAL, pageBuffer:
REF PageByteVec]
RETURNS [r: ROPE ← NIL] = BEGIN
Set["Fetching page from server"];
[r: r] ← OpenFile[h, "heap.data", oldFile];
IF r#NIL THEN RETURN;
r ← ReadPage[h, page, pageBuffer];
IF r#NIL THEN RETURN;
h.heapStream.SetIndex[LONG[page] * bytesPerPage];
[] ← h.heapStream.UnsafePutBlock[[LOOPHOLE[pageBuffer], 0, bytesPerPage]];
h.logStream.PutF["\nHeap page %d read from server\n", [cardinal[page]]];
SetPageState[page, fullPage]
END;
WriteServerPage performs the reverse operation to ReadServerPage.
WriteServerPage:
PUBLIC
PROC[h: GVPRef, page:
CARDINAL, pageBuffer:
REF PageByteVec]
RETURNS [r: ROPE ← NIL] = BEGIN
h.heapStream.SetIndex[LONG[page] * bytesPerPage];
TRUSTED { [] ← h.heapStream.UnsafeGetBlock[[LOOPHOLE[pageBuffer], 0, bytesPerPage]]; };
[r: r] ← OpenFile[h, "heap.data", oldFile];
IF r#NIL THEN RETURN;
r ← WritePage[h, page, pageBuffer];
IF r#NIL THEN RETURN;
h.logStream.PutF["\nHeap page %d updated on server\n", [cardinal[page]]]
END;
DriverInit and DriverTidyUp just start and stop the Pup package respectively
DriverInit:
PUBLIC
PROC =
BEGIN
PupDefs.PupPackageMake[]
END;
DriverTidyUp:
PUBLIC
PROC =
BEGIN
PupDefs.PupPackageDestroy[]
END;
END.