MAINSAIL STREAMS User's Guide, Chapter 2

previous   next   top   contents   index   framed top   this page unframed


2. Overview and Examples

This chapter provides an overview of how to write several common types of STREAMS applications. It contains fairly detailed descriptions of how to write some small sample programs. You are invited to try the examples for yourself (the text of the programs can be found in the file http://www.xidak.com/mainsail/toolu.txt).

2.1. What Can I Do Using STREAMS?

STREAMS can be used to write the following types of applications (among others) in a portable way:

Of course, nothing in MAINSAIL prevents you from writing such things without using STREAMS. You could write a program using operating-system-specific calls (using the Foreign Language Interface) and then port the program to each platform you need to run it on. However, in STREAMS, XIDAK has already ported a set of general-purpose facilities to many different platforms. These general-purpose facilities can be used to write many different types of applications using a single, consistent model.

2.2. How to Write Network Servers

Network servers (or “traditional” servers) are separate, independent processes that provide services to other processes (the client processes). The server process often runs on a different machine from the client process.

A server is similar in many ways to a MAINSAIL MODULE. Both a server and a MODULE have well-defined interfaces that provide specific functions. The MAINSAIL Remote Procedure Call (RPC) mechanism takes advantage of the similarity between servers and MODULEs: it lets you write a server that is just a MAINSAIL MODULE.

Writing and starting a server in MAINSAIL entails these steps:

2.2.1. A Normal MAINSAIL MODULE That Provides a Simple Service

Examples 2–1 and 2–2 show the header and implementation for a MODULE ECHOMOD that provides a trivial service (converting a STRING to upper case). This is a normal MAINSAIL MODULE that can be tested by using the MODULE of Example 2–3; a sample run is shown in Example 2–4.

Example 2–1. Intmod Containing Interface for ECHOMOD
BEGIN "echoHdr"

$DIRECTIVE "NOOUTPUT";

CLASS echoCls (
    
STRING PROCEDURE echoUpperCase (STRING s);
);

MODULE(echoClsechoMod;

SAVEON;

END "echoHdr"

Example 2–2. ECHOMOD: A MODULE That Provides a Trivial Service
BEGIN "echoMod"

RESTOREFROM "echoHdr";

STRING PROCEDURE echoUpperCase (STRING s);
RETURN(cvu(s));

END "echoMod"

Example 2–3. ECHOTST, a MODULE That Tests ECHOMOD
BEGIN "echoTst"

RESTOREFROM "echoHdr";

INITIAL PROCEDURE;
BEGIN
POINTER(echoClsp;

p := new(echoMod);
write(logFile,
    
p.echoUpperCase
        (
$sGet("String to convert to upper case: ")),
    
eol);
dispose(p);
END;

END "echoTst"

Example 2–4. Execution of ECHOTST
*echotst<eol>
String to convert to upper caseHello there!<eol>
HELLO THERE!

2.2.2. Changing a MODULE to Be an RPC Server

The following changes are needed to a MODULE that provides a service and a MODULE that calls it in order to make them into an RPC server and an RPC client:

Examples 2–5, 2–6, and 2–7 show the changed MODULEs. The changed parts are underlined. The MODULE names have also been changed so that you cannot confuse them with the MODULEs of the previous section, but this is not required when you write your own server.

Example 2–5. Changes to ECHOHDR for RPC
BEGIN "echoHdrRpc"

$DIRECTIVE "NOOUTPUT";

RESTOREFROM "rpcHdr";

CLASS($remoteModuleCls) echoCls (
    
STRING PROCEDURE echoUpperCase (STRING s);
);

MODULE(echoClsechoModRpc;

SAVEON;

END "echoHdrRpc"

Example 2–6. Changes to ECHOMOD for RPC
BEGIN "echoModRpc"

RESTOREFROM "echoHdrRpc";

STRING PROCEDURE echoUpperCase (STRING s);
RETURN(cvu(s));

$remoteModuleDefaults

END "echoModRpc"

Example 2–7. Changes to ECHOTST for RPC
BEGIN "echoTstRpc"

RESTOREFROM "echoHdrRpc";

INITIAL PROCEDURE;
BEGIN
POINTER(echoClsp;

$initializeStreams;

p := $newRemoteModule("service>echosrv","echoModRpc");
write(logFile,
    
p.echoUpperCase
        (
$sGet("String to convert to upper case: ")),
    
eol);
dispose(p);
END;

END "echoTstRpc"

2.2.3. Compiling the Server MODULE with the RPC Subcommand

Compile the server MODULE with the RPC subcommand, as shown in
Example 2–8.

Example 2–8. Compiling with the RPC Subcommand

MAINSAIL (R) Compiler
Copyright (c) 1984-1998 by XIDAK, Inc., Point Arena,
 California, USA.


compile (? for help): echomodrpc.msl,<eol>
rpc<eol>
<eol>
Opening intmod for $SYS from ...

echomodrpc.msl 1
Opening intmod for ECHOHDRRPC from ...
Opening intmod for RPCHDR from ...

Opening intmod for $SYS from ...
Client for ECHOMODRPC stored on echomodrpccli.msl
Server for ECHOMODRPC stored on echomodrpcsrv.msl
Intmod for ECHOMODRPC not stored

The compilation produces two MODULEs with names derived from the name of the server MODULE: one for the server (ending in srv) and one for the client (ending in cli).

2.2.4. Compiling the Server and Client MODULEs

You need to compile two MODULEs to run in the server and two MODULEs to run in the client. The MODULEs that run in the server are the server MODULE itself and the srv MODULE produced by the RPC compilation of the previous section. The MODULEs that run in the client are the client MODULE itself and the cli MODULE produced by the RPC compilation.

It is possible (but not required) that the server and client may run on two different types of machines. In Examples 2–9 and 2–10 it is assumed that the server runs on SPARC UNIX and the client runs on RISC System/6000 UNIX (abbreviated USPA and UIRS, respectively; the abbreviations used in file names for these operating systems are usp and uir).

Example 2–9. Compiling the Server MODULEs

MAINSAIL (R) Compiler
Copyright (c) 1984-1998 by XIDAK, Inc., Point Arena,
 California, USA.


compile (? for help): echomodrpc.msl,<eol>
target uspa<eol>
<eol>
Opening intmod for $SYS from ...

echomodrpc.msl 1
Opening intmod for ECHOHDRRPC from ...
Opening intmod for RPCHDR from ...

Objmod for ECHOMODRPC stored on echomodrpc-usp.obj
Intmod for ECHOMODRPC not stored

compile (? for help): echomodrpcsrv.msl<eol>
Opening intmod for $SYS from ...

echomodrpcsrv.msl 1
Opening intmod for RPCHDR from ...

Objmod for ECHOMODRPCSRV stored on echomodrpcsrv-usp.obj
Intmod for ECHOMODRPCSRV not stored

Example 2–10. Compiling the Client MODULEs

MAINSAIL (R) Compiler
Copyright (c) 1984-1998 by XIDAK, Inc., Point Arena,
 California, USA.


compile (? for help): echotstrpc.msl,<eol>
target uirs<eol>
>
Opening intmod for $SYS from ...

echotstrpc.msl 1
Opening intmod for ECHOHDRRPC from ...
Opening intmod for RPCHDR from ...

Objmod for ECHOTSTRPC stored on echotstrpc-uir.obj
Intmod for ECHOTSTRPC not stored

compile (? for help): echomodrpccli.msl<eol>
Opening intmod for $SYS from ...

echomodrpccli.msl 1
Opening intmod for RPCHDR from ...

Objmod for ECHOMODRPCCLI stored on echomodrpccli-uir.obj
Intmod for ECHOMODRPCCLI not stored

2.2.5. Adding an Entry for Your Service to the Services Table and Setting Up the Unregistered Server Directory

These two steps are the most difficult in creating a server, since they normally involve modifying resources (the services table and the operating system's service registry) that are shared with other users. This may require coordination with your system manager. Fortunately, these steps normally need to be done only once for each server name that is used. Once a server name is entered in the services table and registered, it stays that way unless someone explicitly removes it.

To simplify this example, we will make a private copy of the services table and a private copy of the unregistered server directory (which avoids having to register the service with the operating system). This means you will be the only user who can access the server you will set up (unless other users also elect to use your private services table and unregistered server directory). To make a server that could be accessed by everyone, you would modify the site-wide services table and register the service with the operating system as described in Appendix A.

To set up your own services table describing the service echosrv running on the host serverHost and to be accessed from the host clientHost, create a file (call it xidakservices) containing the following:

XIDAKSERVICES 1

DEFAULTPROTOCOL  tcp

SERVICE serverHost:echosrv SUPPORTS echomodrpc

This example assumes that TCP is the appropriate protocol for these two hosts; substitute a different protocol if appropriate to your system (if a system-wide services table is already set up, you may be able to find the appropriate protocols in it; try opening the file with the logical name:

(xidak services)

from MAINSAIL).

Put the xidakservices file you have created on a directory that is accessible from both client and server nodes. For purposes of the example, assume the full file name is /myNode/xidakservices.

Create a directory (say, servicesDirectory; the name does not matter) that is accessible from both client and server nodes. This is the unregistered server directory. For purposes of the example, assume the full file name is /myNode/servicesDirectory.

Finally, in the system parameter file (e.g., v1620.prm) on both the directory where the server is to run and the directory where the client is to run, add the following lines to the $MAINEX group:

ENTER (xidak services) /myNode/xidakservices
GLOBALSYMBOL servicesDirectory /myNode/servicesDirectory

This assures that you access your own services table and unregistered server directory instead of the system-wide ones, if system-wide ones exist.

After finishing with this example, be sure to remove the above lines from your v1620.prm file so that you can once again access system-wide servers.

2.2.6. Starting the Server

While connected to the node where the server is to run, run MAINSAIL and invoke RPCSRV:

*rpcsrv service>echosrv echomodrpc<eol>
service>echosrv = ECHOMODRPC

Your server is now running.

Then, while connected to the node where the client is to run, run the test client:

*echotstrpc<eol>
String to convert to upper caseThis is a string.<eol>
THIS IS A STRING.

If you like, verify that more than one client process can communicate with the server at a time by running the test simultaneously in another MAINSAIL process.

In this example, there is no polite way to kill the server; you will have to abort the server process in a system-dependent manner, e.g., with CTRL-C.

Finally, when you are finished testing your server, do not forget to remove the lines you added to your v1620.prm file; otherwise, you will not be able to access system-wide servers.

2.3. How to Write Embedded Legacy Applications

For the purposes of this section, a legacy application is one whose only available interface is through its terminal interaction. The UNIX C compiler is such an application.

It is often useful to invoke a legacy application from another program. You can do this by creating a child process that communicates through a pseudoterminal stream with its parent. The STREAMS MODULE PROCESS is used to create such a child process.

Example 2–11 shows a MODULE that invokes the UNIX C compiler (which is normally named cc). This program passes some arguments to cc, then waits for cc to finish execution. It writes any output from cc during execution to logFile. A sample execution of the program of Example 2–11 is shown in Example 2–12.

Example 2–11. Invoking the C Compiler from a Program
BEGIN "cc"

RESTOREFROM "strHdr";

STRING PROCEDURE invokeCc (STRING args);
BEGIN
STRING line,s;
POINTER($streamst;

s := "";
$openStream(st,"process>cc " & args);
    # 
Create the child process and open a stream to its
    # 
pseudoterminal
WHILE $success($readStream(st,line,errorOK)) DO
    # 
as long as reading from the child's pseudoterminal
    # 
does not result in the end-of-stream indicator...
    
write(s,line,eol);
        # ... 
append the result to the output
$closeStream(st);
    # 
close the stream (since the child process has died)
RETURN(s);
    # 
Return cc's output to the caller
END;



INITIAL PROCEDURE;
BEGIN
$initializeStreams;
write(logFile,invokeCc($sGet("cc arguments: ")));
END;

END "cc"

Example 2–12. Execution of the Program of Example 2–11
*cc<eol>
cc arguments-o prog prog.c<eol>
*

2.4. How to Write Parallel Processing Applications

A program that uses parallel processing divides a problem into several parts (subprocesses) that can be farmed out to several different processors. The main program sends the data for each part to the appropriate remote processor, the remote processor computes a result, and finally the remote processor transmits the result back to the main program. The main program may have to integrate the results from the various subprocesses or periodically synchronize the activities of the subprocesses.

As an example of a program where parallel processing could be useful, consider Conway's Life. Life is a popular cellular automaton that has been written about in many places. Two excellent references are Martin Gardner's Wheels, Life, and Other Mathematical Amusements (New York: W.H. Freeman and Company, 1983) and William Poundstone's The Recursive Universe (New York: William Morrow and Company, 1985). In case you are unfamiliar with the rules, here they are:

It is easy to write a program that executes and displays the rules above, and many such programs have been written. It is also easy to see how to decompose the problem so as to use parallel processing: the board can be divided into regions, and each subprocess can work on a single region. Examples 2–14, 2–15, 2–16, and 2–17 show a program that divides the work at each step in this way.

The basic architecture of the program is as shown in Figure 2–13. The parent process starts a number child processes (which are RPC servers) on different hosts. It then communicates with them through RPC calls. When the parent is finished, it terminates the child processes.

Figure 2–13. The Main Program Starts Multiple Parallel Processes on Different Hosts and Communicates with Them through RPC

In order to run the program of Examples 2–14, 2–15, 2–16, and 2–17, you must compile LIFEHDR, LIFE, and LIFECOMOD for the machine where the main program is to run. You can also compile SUBLIFEMOD for the same machine if you want to use the ! option (meaning run SUBLIFEMOD in the same process instead of remotely). You must also compile SUBLIFEMOD with the RPC compiler (as in Section 2.2) and the result client MODULE (SUBLIFEMODCLI) for the machine where the main program is to run.

You must then compile LIFEHDR, SUBLIFEMOD, and the server MODULE produced by the RPC compilation (SUBLIFEMODSRV) on the login directory on each machine where a child process is to run.

Finally, you must make sure that the gensrv server process is running on each machine where a child process is to run. If your site uses STREAMS extensively, it may already be running. However, if you attempt to run LIFE and get an error about a refused connection, you can follow the directions in Section A.5 to start gensrv.

Example 2–14. LIFEHDR, Life Program Intmod
BEGIN "lifeHdr"

$DIRECTIVE "NOOUTPUT";

RESTOREFROM "rpcHdr";

MODULE life (
    
INTEGER ARRAY(1 TO *,1 TO *) lifeBoard;
        # 
The INTEGER is a character code in the range '0'
        # 
to '9', representing how many turns this cell
        # 
has been filled ('9' if more than 9 turns), or
        # ' ', 
meaning empty

    
INTEGER maxX,maxY; # upper bounds of lifeBoard

    
INTEGER numStepsAtATime;
        # 
Number of Life steps to perform between displays
);

CLASS lifeCoCls (
    # 
One data section of this MODULE per coroutine.
    # 
Each coroutine talks to a remote instance of
    # 
subLifeMod.
    
INTEGER lowX,highX,lowY,highY;
        # 
Bounds of lifeBoard for which this MODULE is
        # 
responsible

    
STRING hostName;
        # 
Name of host on which to run my subLifeMod
        # 
serveror "!" if should run locally.

    
PROCEDURE initMe;

    
PROCEDURE deInit;

    
PROCEDURE doLifeCo;

    
PROCEDURE copyResultsToLifeBoard;
);

MODULE(lifeCoClslifeCoMod;

CLASS($remoteModuleClssubLifeCls (
    
PROCEDURE runLife
        (
MODIFIES INTEGER ARRAY(*,*) ary;
         
INTEGER numSteps);
);

MODULE(subLifeClssubLifeMod;

SAVEON;

END "lifeHdr"

Example 2–15. LIFE, Life Main Program
BEGIN "life" # Conway's lifedistributed

RESTOREFROM "lifeHdr";

POINTER($ranClsranPtr;

LONG INTEGER stepNum;  # Current step number in game

PROCEDURE initializeLifeBoard;
BEGIN
INTEGER x,y;

maxX := $iGet("Width of Life board: ");
maxY := $iGet("Height of Life board: ");
new(lifeBoard,1,maxX,1,maxY);

Randomly fill in the board.  Approximately 1/3 of
spaces (arbitraryare initially filled.
FOR x := 1 UPTO maxX DO FOR y := 1 UPTO maxY DO
        
lifeBoard[x,y] :=
            
IF NOT ranPtr.$rand MOD 3L THEN '0' EL ' ';
END;



PROCEDURE displayLifeBoard;
A more sophisticated display would be nice.
BEGIN
INTEGER x,y;
write(logFile,eol & "Life at step #",stepNum,
        ":" & 
eol & eol);
FOR y := maxY DOWNTO 1 DOB
    
FOR x := 1 UPTO maxX DO
        
cWrite(logFile,lifeBoard[x,y]);
    
write(logFile,eolEND;
write(logFile,eol);
END;



INITIAL PROCEDURE;
BEGIN
INTEGER i,numHosts,stripWidth;
STRING s,hostNames;
POINTER(lifeCoClsp;
POINTER(lifeCoClsARRAY (1 TO *) lifeCoAry;

$initializeStreams;

ranPtr := new($ranMod);
ranPtr.$initRand($date,$time);

initializeLifeBoard;

numStepsAtATime := $iGet
   ("
Number of Life steps to perform between displays: ");

numHosts := 0; hostNames := "";
WHILE s := $sGet
       ("
Next host ('!' to run locally, <eolto stop): ")
    
DOB write(hostNames,s,eol); numHosts .+ 1 END;

Divide lifeBoard into vertical strips (other divisions
are possiblebut this one is easy)
IF NOT stripWidth := maxX DIV numHosts THEN
    
errMsg("More hosts than the board is wide!","",fatal);

new(lifeCoAry,1,numHosts);
FOR i := 1 UPTO numHosts DOB
    
lifeCoAry[i] := p := new(lifeCoMod);
    
p.lowX := 1 + stripWidth * (i - 1);
    
p.highX := IF i = numHosts THEN maxX
               
EL stripWidth * i;
    
p.lowY := 1; p.highY := maxY;
    
read(hostNames,p.hostName);
    
p.initMe END;

displayLifeBoard;
DOB FOR i := 1 UPTO numHosts DO
        
$queueCoroutine($createCoroutine(lifeCoAry[i],
                                         "
doLifeCo"));
    
$waitForDescendants;
    
FOR i := 1 UPTO numHosts DO
        
lifeCoAry[i].copyResultsToLifeBoard;
    
stepNum .+ cvli(numStepsAtATime);
    
displayLifeBoard END
    
UNTIL $sGet
        ("<
eolto continueanything else to stop: ");

FOR i := 1 UPTO numHosts DOB
    
lifeCoAry[i].deInitdispose(lifeCoAry[i]) END;
dispose(lifeBoard); dispose(ranPtr);
END;

END "life"

Example 2–16. LIFECOMOD, MODULE for Dealing with a Single Client Coroutine
BEGIN "lifeCoMod"

RESTOREFROM "lifeHdr";

Each instance of this MODULE runs in its own coroutine
when in the "doLifeCointerface PROCEDURE.  The
coroutine's job is to talk with a single remote
subLifeMod instance long enough to do a single call to
the runLife PROCEDURE.

POINTER($streamchCtl,chTty;

POINTER(subLifeClssubLifePtr;

INTEGER ARRAY(*,*) subLifeBoard;
    # 
My own copy of my area of the board

PROCEDURE initMe;
Note:  The child server can be properly started only if
the host has a bootstrap named $MS/mainsa and the
remote MODULE subLifeMod has been compiled for the
host and is sitting on the login directory on that
host.
BEGIN
IF hostName = "!" THEN # do locally (within this process)
    
subLifePtr := new(subLifeMod)
EB  $openStream(chTty,chCtl,
        "
process(" & hostName & ")>"
            & "
$MS/mainsa rpcsrv - subLifeMod");
    
subLifePtr := $newRemoteModule(chCtl,"subLifeMod");
    
END;
new(subLifeBoard,
    # 
Allow extra space because edges become inaccurate
    # 
as computation proceeds
    (
lowX - numStepsAtATimeMAX 1,
    (
highX + numStepsAtATimeMIN maxX,
    (
lowY - numStepsAtATimeMAX 1,
    (
highY + numStepsAtATimeMIN maxY);
END;



PROCEDURE deInit;
BEGIN
dispose(subLifePtr);
IF chCtl THENB
    
$closeStream(chCtl);
    
$closeStream(chTty); # This kills the child process
    
END;
dispose(subLifeBoard);
END;



PROCEDURE reader;
BEGIN
STRING s;
WHILE $success($readStream(chTty,s,errorOK)) DO
    
$writeStream($tty,s,$line);
Can get here only if child dies unexpectedly
errMsg("Unexpected error reading from child on host",
    
hostName,fatal);
END;



PROCEDURE doLifeCo;
BEGIN
INTEGER x,y;
POINTER($coroutinereaderCo;
IF chCtl THEN
    # 
Handle child process's TTY outputin case of errors
    
$queueCoroutine(readerCo :=
            
$createCoroutine(thisDataSection,"reader"));

FOR x := subLifeBoard.lb1 UPTO subLifeBoard.ub1 DO
    
FOR y := subLifeBoard.lb2 UPTO subLifeBoard.ub2 DO
        
subLifeBoard[x,y] := lifeBoard[x,y];
subLifePtr.runLife(subLifeBoard,numStepsAtATime);

IF chCtl THEN $killCoroutine(readerCo);
$reschedule(delete);
END;



PROCEDURE copyResultsToLifeBoard;
This is called when all the children have finished
to update the master board.
BEGIN
INTEGER x,y;
FOR x := lowX UPTO highX DO
    
FOR y := lowY UPTO highY DO
        
lifeBoard[x,y] := subLifeBoard[x,y];
END;

END "lifeCoMod"

Example 2–17. SUBLIFEMOD, Server MODULE to Which Computation Is “Subcontracted”
BEGIN "subLifeMod"

RESTOREFROM "lifeHdr";

This MODULE is the child (which is also the serverin
this case).

INTEGER ARRAY(*,*) newAry;

INTEGER PROCEDURE numNeighbors (INTEGER ARRAY(*,*) ary;
                                
INTEGER x,y);
BEGIN
INTEGER xx,yy,sum;
sum := 0;
FOR xx := x - 1 MAX ary.lb1 UPTO x + 1 MIN ary.ub1 DO
    
FOR yy := y - 1 MAX ary.lb2 UPTO y + 1 MIN ary.ub2 DO
        
IF NOT (xx = x AND yy = yAND ary[xx,yyNEQ ' '
            
THEN sum .+ 1;
RETURN(sum);
END;



PROCEDURE runLife
    (
MODIFIES INTEGER ARRAY(*,*) ary;
     
INTEGER numSteps);
BEGIN
INTEGER i,x,y;
INTEGER ARRAY(*,*) swapAry;
IF newAry AND
    (
newAry.lb1 NEQ ary.lb1 OR newAry.ub1 NEQ ary.ub1
     
OR newAry.lb2 NEQ ary.lb2 OR newAry.ub2 NEQ ary.ub2)
    
THEN dispose(newAry);
IF NOT newAry THEN
    
new(newAry,ary.lb1,ary.ub1,ary.lb2,ary.ub2);
FOR i := 1 UPTO numSteps DOB
    
FOR x := ary.lb1 UPTO ary.ub1 DO
        
FOR y := ary.lb2 UPTO ary.ub2 DO
            
CASE numNeighbors(ary,x,yOFB
                # 
If a filled cell has two or three filled
                # 
neighborsit survives.
                # 
If an empty cell has three filled
                # 
neighborsit is filled (born).
                # 
Otherwisethe cell becomes empty.
                [2] 
IF '0' LEQ ary[x,yLEQ '8' THEN
                        
newAry[x,y] := ary[x,y] + 1
                        # 
Mark it has having been around
                        # 
one turn longer
                    
EL  newAry[x,y] := ary[x,y];
                [3] 
IF ary[x,y] = ' ' THEN
                        
newAry[x,y] := '0'
                    
EF '0' LEQ ary[x,yLEQ '8' THEN
                        
newAry[x,y] := ary[x,y] + 1
                    
EL  newAry[x,y] := '9';
                [ ] 
newAry[x,y] := ' ';
                
END;
    
swapAry := aryary := newArynewAry := swapAry END;
END;

$remoteModuleDefaults

END "subLifeMod"

2.5. How to Write Low-Level Device Control Applications

If you open a stream to a particular device, you can use STREAMS PROCEDUREs (some of which may be device-specific) to control that device.

The only kind of device for which STREAMS currently provides such low-level control is terminals. Normal line-oriented terminal I/O automatically provides such features as line editing and echo (which are actually under the control of the operating system). STREAMS provides the ability to control such characteristics directly.

For example, Example 2–18 shows a program that reads in a password without echoing it.

Example 2–18. A Program That Reads a Password without Echoing It
BEGIN "dontEchoPassword"

RESTOREFROM "strHdr";

IFC $charSet = $ascii THENC
DEFINE bs = 8, del = 127;
ELSEC
MESSAGE "Unknown character set","error";
ENDC

INITIAL PROCEDURE;
BEGIN
INTEGER ch;
STRING s,ss;

$initializeStreams;

$writeStream($tty,"Password: "); ss := "";
DOB "outer"
    
$readStream($tty,s);
        # 
Because $line bit is not specifiedno echoing
        # 
occursread returns as soon as a character is
        # 
available
    
WHILE ch := cRead(sGEQ 0 DOB
        # 
If you want to allow backspace or delete to
        # 
erase (invisiblepreviously typed character,
        # 
you have to do it explicitly here (operating
        # 
system does not provide line editing in this
        # 
mode).
        
IF ch = $optionalFirstEol OR ch = last(eolTHEN
            
DONE "outer";
        
IF ch = bs OR ch = del THEN rcRead(ss)
        
EL  cWrite(ss,chEND END "outer";

write(logFile,eol & "The password you typed was ",ss,eol);
END;

END "dontEchoPassword"


previous   next   top   contents   index   framed top   this page unframed

MAINSAIL STREAMS User's Guide, Chapter 2