1 Introduction
2 Ground Rules

Building a File System
3 File Systems
4 File Content Data Structure
5 Allocation Cluster Manager
6 Exceptions and Emancipation
7 Base Classes, Testing, and More
8 File Meta Data
9 Native File Class
10 Our File System
11 Allocation Table
12 File System Support Code
13 Initializing the File System
14 Contiguous Files
15 Rebuilding the File System
16 Native File System Support Methods
17 Lookups, Wildcards, and Unicode, Oh My
18 Finishing the File System Class

The Init Program
19 Hardware Abstraction and UOS Architecture
20 Init Command Mode
21 Using Our File System
22 Hardware and Device Lists
23 Fun with Stores: Partitions
24 Fun with Stores: RAID
25 Fun with Stores: RAM Disks
26 Init wrap-up

The Executive
27 Overview of The Executive
28 Starting the Kernel
29 The Kernel
30 Making a Store Bootable
31 The MMC
32 The HMC
33 Loading the components
34 Using the File Processor
35 Symbols and the SSC
36 The File Processor and Device Management
37 The File Processor and File System Management
38 Finishing Executive Startup

Users and Security
39 Introduction to Users and Security
40 More Fun With Stores: File Heaps
41 File Heaps, part 2
42 SysUAF
43 TUser

Terminal I/O
45 Shells and UCL
46 UOS API, the Application Side
47 UOS API, the Executive Side
48 I/O Devices
49 Streams
50 Terminal Output Filters
51 The TTerminal Class
52 Handles
53 Putting it All Together
54 Getting Terminal Input
55 QIO
56 Cooking Terminal Input
57 Putting it all together, part 2
58 Quotas and I/O

59 UCL Basics
60 Symbol Substitution
61 UCL Command execution
62 UCL Command execution, part 2
63 UCL Command Abbreviation


Download sources
Download binaries

Getting Terminal Input

Getting input from a terminal is more complicated than doing output. The main issue is that input is unsolicited. Another way of saying this is that input happens asynchronously - while the program is doing something else. That is, input may come in even before we ask for it. With a store, we only get input after we request it, so we have a place for the data to go. We know ahead of time how big the buffer needs to be because we request a specific amount of data. We have none of these advantages when dealing with terminal input.

Just as we have an output buffer to hold output for a terminal until the terminal is ready for it, we also need an input buffer to hold input from a terminal until the program is ready for it. We will discuss this in one of the following articles.

Also, just as we have an output filter to cook terminal output, we also have an input filter to cook input from the terminal. But the input filter does a lot more work than the output filter, as we will see in a following article.

So, we can see that the input and output stacks have a similar struture, however the input stack has more going on. As we did with output, we will start with the application (UCL) and work our way back to the UOS code that interfaces with the hardware. But first, we need to consider something about outputting a prompt that we didn't address when we discussed using Put_Output.


We used LIB_Put_Output to write the UCL prompt to the terminal. But, as we've discussed, UCL can get commands from any source that sys$command points to, including a file. If we are getting commands from a file, there is no need to write a prompt. In fact, if the command source is not a terminal, we don't want to try writing a prompt to it. We could query UOS for information on the device and see if it is a terminal, but VMS/UOS has a system call named LIB_Get_Command. This call will get input from sys$command, and it will also write a prompt if the device is a terminal.

function Get_Command : string ;

var S : Ansistring ;

    S := LIB_Get_Symbol( '$ucl_prompt' ) ;
    if( S = '' ) then
        S := '$ ' ;
    end ;
    Result := LIB_Get_Command( S ) ;
end ;

procedure Run ;

var S : string ;

    while( True ) do
        S := Get_Command ;
    end ;
end ;
We've replaced Show_Prompt with Get_Command. Instead of calling LIB_PUT_Output, we call LIB_Get_Command, passing the prompt. LIB_Get_Command will display the prompt on the terminal (if sys$command is a terminal), obtain the input and return a string containing it. The program will pause until an entire line of input has been processed. We determine the prompt string as before, and make the call.

Now let's look at LIB_Get_Command.

function LIB_Get_Command( const Prompt : string ) : string ;

var RAB : TRAB ;

    fillchar( RAB, sizeof( RAB ), 0 ) ;
    RAB.RAB_Size := sizeof( RAB ) ;
    RAB.RAB_W_ISI := RH_SysCommand ;
    RAB.RAB_L_RBF := integer( PAnsiChar( Prompt ) ) ;
    RAB.RAB_W_RSZ := length( Prompt ) ;
    Result := RMS.Get_Command( RAB ) ;
end ;
Just as we did in LIB_Put_Output, we fill a RAB structure and call RMS. The RAB is set to point to the prompt string and is given the length of that string.

In the RMS unit, we add the Get_Command function.

var Temp : string ;

function Get_Command( var RAB : TRAB ) : PChar ;

var Length : int64 ;
    Status : byte ;
    SysRequest : TString2I1_Request ;
    ResString : string ;

    fillchar( SysRequest, sizeof( SysRequest ), 0 ) ;
    SysRequest.Request.Subsystem := UOS_Subsystem_FIP ;
    SysRequest.Request.Request := UOS_FIP_Get_Command ;
    SysRequest.Request.Length := sizeof( TString2I1_Request ) - sizeof( SysRequest.Request ) ;
    SysRequest.Request.Status := integer( @Status ) ;
    SysRequest.String2.Buffer := RAB.RAB_L_RBF ;
    SysRequest.String2.Length := RAB.RAB_W_RSZ ;

    setlength( ResString, 256 ) ;
    SysRequest.String1.Buffer := integer( PChar( ResString ) ) ; // Address of buffer to receive input
    SysRequest.String1.Length := system.length( ResString ) ; // Max buffer size
    SysRequest.Integer1 := integer( @Length ) ; // Where to write result length

    Call_To_Ring0( integer( @SysRequest ) ) ;
    setlength( ResString, Length ) ;
    Temp := ResString ;
    Result := PChar( Temp ) ;
end ;
We create a system request structure, filling it with the appropriate data. The call to Get_Command uses a String2I1 structure (2 strings, 1 integer). The strings are the prompt and the return result. Since we don't know what the size of the input line will be, we allocate 256 bytes. Then we call to the executive through ring 0. Upon return, ResString will have been filled with input and Length will be set to the length of the data in ResString. So we use it to trim the string to only what was returned. We assign the result to Temp and return a pointer to it.

We update the TUOS_FiP.API method to handle the new FIP_Get_Command call.

              UE := Enter_System_Call( Request, SReq, PID, MMC, sizeof( TString2I1_Request ) - sizeof( SReq ), Address ) ;
              if( UE <> nil ) then
                  Set_Last_Error( UE ) ;
                  exit ;
              end ;
                  String2I1_Request := PString2I1_Request( Address ) ;

                  // Read input into user's buffer...
                  QIO( 0, RH_SysCommand, IO_READPROMPT, IOSB, 0, 0,
                      String2I1_Request.String1.Length, 0, {timeout} 0, {read terminator block}
                      String2I1_Request.String2.Length ) ;
                  Status := Write_User_int64( Kernel, PID, String2I1_Request.Integer1, IOSB.r_io_64.r_bcnt_32.l_bcnt ) ;
                  Exit_System_Call( integer( String2I1_Request ), PID, MMC, sizeof( TString2I1_Request ) - sizeof( SReq ) ) ;
              end ;
          end ;
We need to do the same process of mapping the user's memory into the executive memory space so we can access the structure. We've extracted this to a function, Enter_System_Call, so as not to duplicate the work with each call we add to the API. When we're done, we call Exit_System_Call to clean up.

We call QIO to perform most I/O operations. QIO stands for "Queue I/O", which will make sense in the future when we discuss I/O queues. Upon return, we call Write_User_int64 to write the length value.

We will discuss QIO in the next article, but before we end this article, let's briefly discuss the other functions that we call in this code.

function Enter_System_Call( Request : int64 ; const SReq : TSystem_Request ;
    PID : TPID ; MMC : TUOS_Memory_Manager ; Size : integer ;
    var Address : int64 ) : TUnified_Exception ;

var Base, Offset : int64 ;

    // Setup and sanity check...
    Result := nil ; // Assume success
    if( SReq.Length < Size ) then
        Result := Create_Error( UOSErr_Invalid_System_Request ) ;
        exit ;
    end ;
    Size := Size + sizeof( SReq ) ;

    // Get access to Request structure...
    Offset := MMC.Lock_Pages( PID, Request,  Size ) ;
        Base := MMC.Map_Pages( PID, 0, Request, Size, MAM_Read or MAM_Lock ) ;
        if( Base = 0 ) then // Couldn't map memory
            if( MMC.Last_Error = nil ) then
                Result := Create_Error( UOSErr_Memory_Address_Error ) ;
            end else
                Result := MMC.Last_Error ;
            end ;
            exit ;
        end ;
        Address := Base + Offset ;
        if( Result <> nil ) then // Failure
            MMC.Unlock_Pages( PID, Base, Size ) ;
        end ;
    end ;
end ; // Enter_System_Call

procedure Exit_System_Call( Address : int64 ; PID : TPID ;
    MMC : TUOS_Memory_Manager ; Size : integer ) ;

    // Release system request structure...
    Size := Size + sizeof( TSystem_Request ) ;
    MMC.UnMap_Pages( 0, Address, Size ) ;
    MMC.Unlock_Pages( PID, Address, Size ) ;
end ;
This is the same code we covered in a past article, but it has been extracted to two separate functions that can be called from the API method of any of the executive components.

function Write_User_int64( Kernel : TUOS_Kernel ; PID : TPID ;
    Address, Value : int64 ) : integer ;

var Buffer : PAnsiChar ;
    Offset : int64 ;
    MMC : TUOS_Memory_Manager ;
    USC : TUOS_User_Security ;

    MMC := Kernel.MMC ;
    USC := Kernel.USC ;
    Result := UE_Success ;
    Offset := MMC.Lock_Pages( PID, Address, sizeof( Value ) ) ;
        Buffer := PAnsiChar( MMC.Map_Pages( PID, 0, Address, sizeof( Value ), MAM_Write or MAM_Lock ) ) ;
        if( Buffer = nil ) then
            USC.Set_Process_Exception( PID, MMC.Last_Error ) ;
            Result := UE_Error ;
        end else
            move( Value, Buffer[ Offset ], sizeof( Value ) ) ;
        end ;
        MMC.UnMap_Pages( 0, Address, sizeof( Value ) ) ;
        MMC.Unlock_Pages( PID, Address, sizeof( Value ) ) ;
    end ;
end ; // Set_User_String
Write_User_int64 writes a 64-bit integer value into the user's address space. This is used to return integer data to the user in response to a system call. The code resembles the previous two functions - it locks the pages containing the destination address, maps those pages into the executive's address space, and exits with an error if there is a problem. We then write the value to the user's memory, unmap the pages, and finally unlock the pages.

In the next article, we will discuss QIO and more of the input stack.