8.11 An Extended Low Level I/O Example--TermFile

As remarked in a previous chapter, I/O using the terminal device has some special characteristics. One is that characters typed at the console keyboard are usually placed on the screen as a visual aid to the user of what has been typed.

Copying input characters to the output device is called echoing.

In most implementations, of ISO Modula-2 the Module StdChans probably sets up its default I/O channels through the facilities of TermFile (yet another device driver) though this dependence is not actually required by the standard itself. Indeed, there is no requirement that an implementation even support a terminal, or even programs that do I/O at all. Assuming it does, it is somewhat unlikely that many users would employ TermFile directly in a program. However, TermFile does permit the opening of channels to the terminal (assuming one exists) in such a way that the user can determine at what point the items typed at the keyboard are to be echoed on the screen.

If a TermFile channel is opened without the flag echo, the channel is in line mode and the echoing to the screen is done when the character is typed. If the echo flag is set for an input, then the channel is said to be in single character mode and characters are echoed only when they are read (presumably from an internal buffer) by a text read operation such as ReadChar, or ReadString and not at the time they are typed.

This may seem like a fine distinction; after all, it appears at first glance that the characters are all echoed to the screen sooner or later. In most cases, they are, but in single character mode (echo flag) if the read operation is not a text read, then the characters will never be echoed. This can be useful if an application program requires the user is to type a password. To prevent a snooper looking over her shoulder from reading that password from the screen, echoing should be disabled for that read. This is easily done, because any number of channels may be opened to the terminal through TermFile, some with echoing, and some without. Indeed, the rule applied by TermFile is that if any channels are open to the terminal in single character mode, then echoing must be postponed until characters are read. Therefore, in such a case, if some characters are read with a text read operation such as TextIO.ReadString, they will be echoed. If others are read by the use of IOChan.Look and IOChan.Skip, (defined in the standard as not being text reads for this purpose) they will not be echoed.

Here is a module to determine whether this behaviour is properly exhibited by an ISO compliant TermFile device driver.

MODULE TestTerminal;

(* Written by R.J. Sutcliffe *)
(* to illustrate aspects of the use of ISO module TermFile *)
(* last revision 1994 02 21 *)

FROM TermFile IMPORT
  ChanId, Open, OpenResults, read, echo, Close;
FROM STextIO IMPORT
  WriteString, ReadString, SkipLine, WriteLn;
FROM IOChan IMPORT
  SkipLook, Look, SetReadResult;
FROM SIOResult IMPORT
  ReadResults, ReadResult;
  
PROCEDURE ReadToken (cid: ChanId; VAR s: ARRAY OF CHAR);
  (* Skips leading spaces, and then removes characters from the input stream cid before the next space or line mark, copying to s as many as can be accommodated as a string value. The read result is set to the value allRight, outOfRange, endOfLine, or endOfInput. *)

VAR
  lastToRead, count : CARDINAL;
  ch : CHAR;
  resRead : ReadResults;

BEGIN
  count := 0;
  lastToRead := HIGH(s);
  Look (cid, ch, resRead);
  WHILE (ch = " ") AND (resRead = allRight)
    DO
      SkipLook (cid, ch, resRead);
    END;
  WHILE (count <= lastToRead) AND (ch # " ")
         AND (resRead = allRight)
    DO
      s[count] := ch;
      INC (count);
      SkipLook (cid, ch, resRead);
    END;
    (* if room left in string, terminate it *)
  IF (count <= lastToRead) AND ((ch = " ")
        OR (resRead # allRight))
     THEN
       s[count] := 0C
     ELSIF count > lastToRead THEN
       (* if the string for the token is not big enough, say so *)
       SetReadResult (cid, outOfRange);
       WHILE (ch # " ") AND (resRead = allRight)
        DO (* and knock the rest out *)
          INC (count);
          SkipLook (cid, ch, resRead);
        END;
     END;
END ReadToken;

VAR
  term : ChanId;
  resOpen : OpenResults;
  password : ARRAY [0..63] OF CHAR;
  count : CARDINAL;

BEGIN
  (* first, do things in the usual way *)
  WriteString ("First test.  All terminal channels in line mode");
  WriteLn;
  WriteString ("type password ==>");
  ReadString (password);
  SkipLine;
  WriteLn ;
  WriteString ("password typed was:  ");
  WriteString (password);
  WriteLn ;
  
  (* Open new channel to the terminal in single character mode *)
   Open (term, read+echo, resOpen);
  IF resOpen = opened
    THEN
      WriteLn;
      WriteString ("Second test. One channel in char mode");
      WriteLn;
      WriteString ("type password ==>");
      ReadToken (term, password);
      SkipLine;
      WriteLn ;
      WriteString ("password was:  ");
      WriteString (password);
      Close (term);
    END;

END TestTerminal.

When this program was executed, the output from one run looked like this:

First test.  All terminal channels in line mode
type password ==>we34fd

password typed was:  we34fd

Second test. One channel in char mode
type password ==>
password was:  ui56bn43

Observe that, as expected, the password was echoed when typed in the normal (line) mode, and was not when there was a channel to the terminal open in character mode, even though that channel was not explicitly used for the input.

Note also that the procedure ReadToken contained in the above test program is also taken from the author's implementation of TextIO and behaves in that way when imported. That is, because it is implemented in terms of Look and Skip, it is not a text read for the purposes of this echoing rule. Although the author has tested other implementations and found them to have the same interpretation, it is possible that some may not, and therefore the user may need the ReadToken shown here.


Contents