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
44 SysUAF API

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

UCL
59 UCL Basics
60 Symbol Substitution
61 Command execution
62 Command execution, part 2
63 Command Abbreviation
64 ASTs
65 Expressions, Part 1
66 Expressions, Part 2: Support code
67 Expressions, part 3: Parsing
68 SYS_GETJPIW and SYS_TRNLNM
69 Expressions, part 4: Evaluation

UCL Lexical Functions
70 PROCESS_SCAN
71 PROCESS_SCAN, Part 2
72 TProcess updates
73 Unicode revisted
74 Lexical functions: F$CONTEXT
75 Lexical functions: F$PID
76 Lexical Functions: F$CUNITS
77 Lexical Functions: F$CVSI and F$CVUI
78 UOS Date and Time Formatting
79 Lexical Functions: F$CVTIME
80 LIB_CVTIME
81 Date/Time Contexts
82 SYS_GETTIM, LIB_Get_Timestamp, SYS_ASCTIM, and LIB_SYS_ASCTIM
83 Lexical Functions: F$DELTA_TIME
84 Lexical functions: F$DEVICE
85 SYS_DEVICE_SCAN
86 Lexical functions: F$DIRECTORY
87 Lexical functions: F$EDIT and F$ELEMENT
88 Lexical functions: F$ENVIRONMENT
89 SYS_GETUAI
90 Lexical functions: F$EXTRACT and F$IDENTIFIER
91 LIB_FAO and LIB_FAOL
92 LIB_FAO and LIB_FAOL, part 2
93 Lexical functions: F$FAO
94 File Processing Structures
95 Lexical functions: F$FILE_ATTRIBUTES
96 SYS_DISPLAY
97 Lexical functions: F$GETDVI
98 Parse_GetDVI
99 GetDVI
100 GetDVI, part 2
101 GetDVI, part 3
102 Lexical functions: F$GETJPI
103 GETJPI
104 Lexical functions: F$GETSYI
105 GETSYI
106 Lexical functions: F$INTEGER, F$LENGTH, F$LOCATE, and F$MATCH_WILD
107 Lexical function: F$PARSE
108 FILESCAN
109 SYS_PARSE
110 Lexical Functions: F$MODE, F$PRIVILEGE, and F$PROCESS
111 File Lookup Service
112 Lexical Functions: F$SEARCH
113 SYS_SEARCH
114 F$SETPRV and SYS_SETPRV
115 Lexical Functions: F$STRING, F$TIME, and F$TYPE
116 More on symbols
117 Lexical Functions: F$TRNLNM
118 SYS_TRNLNM, Part 2
119 Lexical functions: F$UNIQUE, F$USER, and F$VERIFY
120 Lexical functions: F$MESSAGE
121 TUOS_File_Wrapper
122 OPEN, CLOSE, and READ system services

UCL Commands
123 WRITE
124 Symbol assignment
125 The @ command
126 @ and EXIT
127 CRELNT system service
128 DELLNT system service
129 IF...THEN...ELSE
130 Comments, labels, and GOTO
131 GOSUB and RETURN
132 CALL, SUBROUTINE, and ENDSUBROUTINE
133 ON, SET {NO}ON, and error handling
134 INQUIRE
135 SYS_WRITE Service
136 OPEN
137 CLOSE
138 DELLNM system service
139 READ
140 Command Recall
141 RECALL
142 RUN
143 LIB_RUN
144 The Data Stream Interface
145 Preparing for execution
146 EOJ and LOGOUT
147 SYS_DELPROC and LIB_GET_FOREIGN

CUSPs and utilities
148 The I/O Queue
149 Timers
150 Logging in, part one
151 Logging in, part 2
152 System configuration
153 SET NODE utility
154 UUI
155 SETTERM utility
156 SETTERM utility, part 2
157 SETTERM utility, part 3
158 AUTHORIZE utility
159 AUTHORIZE utility, UI
160 AUTHORIZE utility, Access Restrictions
161 AUTHORIZE utility, Part 4
162 AUTHORIZE utility, Reporting
163 AUTHORIZE utility, Part 6
164 Authentication
165 Hashlib
166 Authenticate, Part 7
167 Logging in, part 3
168 DAY_OF_WEEK, CVT_FROM_INTERNAL_TIME, and SPAWN
169 DAY_OF_WEEK and CVT_FROM_INTERNAL_TIME
170 LIB_SPAWN
171 CREPRC
172 CREPRC, Part 2
173 COPY
174 COPY, part 2
175 COPY, part 3
176 COPY, part 4
177 LIB_Get_Default_File_Protection and LIB_Substitute_Wildcards
178 CREATESTREAM, STREAMNAME, and Set_Contiguous
179 Help Files
180 LBR Services
181 LBR Services, Part 2
182 LIBRARY utility
183 LIBRARY utility, Part 2
184 FS Services
185 FS Services, Part 2
186 Implementing Help
187 HELP
188 HELP, Part 2
189 DMG_Get_Key and LIB_Put_Formatted_Output
190 LIBRARY utility, Part 3
191 Shutting Down UOS
192 SHUTDOWN
193 WAIT
194 SETIMR
195 WAITFR and Scheduling
196 REPLY, OPCOM, and Mailboxes
197 REPLY utility
198 Mailboxes
199 BRKTHRU
200 OPCOM

Glossary/Index


Downloads

QIO

QIO is the main I/O service of VMS and UOS. In UOS, it is a part of the File Processor component. Most I/O requests end up being processed by QIO. The "Q" is for "Queue", which refers to the ability of QIO to queue up several requests. We will discuss I/O queues in the future. For now, we will only look at what is necessary for Get_Command. The QIO parameters are: Event_Flags (for future discussion), the handle, the QIO function number, an IOSB (I/O Status Block) structure, Astadr (asynchronous response address, which we will discuss in the future along with Astprm), Astprm (asynchronous response parameters), and then six parameters whose meaning depends upon the QIO function number. Event_Flags, Astadr, and Astprm should always be 0 unless we are doing asychronous I/O. I should note that asynchronous I/O is not the same thing as the asychronous nature of terminals (and some other streams). In the context of QIO, it indicates that a program can queue up multiple I/O requests to the any given device. Normal (synchronous) operation indicates that the program issues a single I/O request and waits (blocks) until that operation completes.

The IOSB is a record that is passed to QIO, which fills the record with I/O status. IOSB is used in several different situations and so it has a morphable structure. Here is the IOSB definition:

type TIOSB = packed record
                 case boolean of // r_io_get
                     false:
                         ( r_io_64 : record
                                         w_status : longint ; // Final I/O status
                                         case boolean of // r_devdepend
                                             false:
                                                  ( r_bcnt_16 : record  // 16-bit byte count variant
                                                                    w_bcnt : word ; // 16-bit byte count
                                                                    case boolean of // r_l
                                                                        false: ( l_dev_depend : cardinal ) ; 
                                                                                 // 32-bit device dependent info
                                                                        true: ( l_pid : TPID ) ;
                                                                end // r_bcnt_16
                                                  ) ;
                                             true:
                                                 ( r_bcnt_32 : record // 32-bit byte count variant
                                                                   l_bcnt : cardinal ; // 32-bit byte count (unaligned)
                                                                   w_dev_depend_high : word ; // 16-bit device dependent info
                                                               end ; // r_bcnt_32
                                                 )
                                     end // r_io_64
                         ) ;
                     true:
                         ( r_get_64 : record
                                          case boolean of // r_l_status
                                              false: ( l_getxxi_status : cardinal ) ; // Final GETxxI status
                                              true: ( l_reg_status : cardinal ) ; // Final $Registry status
                                      end // r_get_64
                         ) ;
              end ; // TIOSB
Which items are used in the structure depends upon the specific QIO call. We will address this as we look at each QIO function in turn.

Here is the QIO function.

procedure TUOS_FiP.QIO( Event_Flags : cardinal ; Handle : THandle ; Func : cardinal ;
    var IOSB : TIOSB ; Astadr, Astprm, p1, p2, p3, p4, p5, p6 : int64 ) ;

var Resource : TResource ;
    B : string ;
    Prompt : TUOS_String ;
    SRB : TSRB ;

begin
    // Setup...
    fillchar( IOSB, sizeof( IOSB ), 0 ) ;
    Handle := USC.Translate_Handle( Kernel.PID, Handle ) ;
    if( not USC.Valid_Handle( Kernel.PID, Handle ) ) then
    begin
        Set_Last_Error( Create_Error( UOSErr_Invalid_Handle ) ) ;
        IOSB.r_io_64.w_status := UE_Error ;
        exit ;
    end ;
First, we initialize the IOSB structure to zeroes. Next we translate the handle (all QIO calls operate on handles). Then we verify that the handle is valid for the process.

    if( ( Func and IO_Function_Mask ) = IO_READPROMPT ) then
    begin
        Resource := TResource( Handle ) ;
        if( ( p6 {Prompt length} > 0 ) and ( TFiP_File( Resource._File ) is TFiP_Terminal_File ) ) then
        begin
            // Get and display prompt...
            SRB.Buffer := p5 ;
            SRB.Length := p6 ;
            Prompt := Get_User_String( Kernel, Kernel.PID, SRB, IOSB.r_io_64.w_status ) ;
            if( IOSB.r_io_64.w_status = UE_Error ) then
            begin
                exit ;
            end ;
            Write_File( RH_SysOutput, 0, Prompt.Contents, Prompt.Size, 0 ) ;
        end ;
        if( ( p1 = 0 ) or ( p2 = 0 ) ) then // Zero-length buffer passed
        begin
            exit ;
        end ;
        B := Read_File( Handle, 0, p2 {receiving buffer max length}, 0, IOSB ) ;
        if( IOSB.r_io_64.w_status = UE_Error ) then
        begin
            exit ;
        end ;
        SRB.Buffer := p1 ;
        SRB.Length := length( B ) ;
        IOSB.r_io_64.w_status := Set_User_String( Kernel, Kernel.PID, SRB, B ) ;
        exit ;
    end ; // IO_READPROMPT
end ; // TUOS_FiP.QIO
Since handles are merely addresses of TResource instance, we cast the handle to TResource, so we can work with it as one. In the case of the IO_READPROMPT function, we get the prompt from parameters p5 (prompt text) and p6 (prompt length). If the length of the prompt is zero, then we won't output anything. Further, the handle must be a terminal (if the resource's file is a TFiP_Terminal_File then we know that to be true). If it is a terminal and there is a prompt, we write get the prompt string from the user's address space with Get_User_String. Then, assuming there is no error, we write it out to sys$output.

Next we see if the receiving buffer size is zero. If it is, or if the buffer address is 0, we exit since there is no buffer to receive input. Otherwise, we call Read_File to get the input. That function sets fields in the IOSB. If the status (IOSB.r_io_64.w_status) indicates an error, we exit. Finally, we write the input data to the user's buffer.

Here is the Read_File method:

function TUOS_FiP.Read_File( Handle : THandle ; Stream : longint ;
    Length : int64 ; Flags : integer ; var IOSB : TIOSB ) : string ;

var Resource : TResource ;

begin
    // Setup...
    Set_Last_Error( nil ) ;
    Result := '' ;

    Resource := TResource( Handle ) ;
    setlength( Result, Length ) ;
    IOSB.r_io_64.r_bcnt_32.l_bcnt := 
        Resource._File.Read( Stream, Resource.Position, Length, PAnsiChar( Result )[ 0 ], Flags ) ;
    setlength( Result, IOSB.r_io_64.r_bcnt_32.l_bcnt ) ;
end ;
First we clear any exceptions and set the result to null. Note that we do not translate the handle or validate it against the process. This is because we already did so in QIO and why repeat what we already did? We want calls to the executive to finish as quickly as possible to reduce overhead. Therefore, we adopt a strategy of only validating data when it comes into the executive from outside (such as via an system call). But within the executive, we assume that the data is valid. If Read_File is called from any other API code then the handle will need to be translated and validated before calling this routine.
We then set the length of the result string to the passed length and call the Read method of the file associated with the resource (a terminal in this case). We pass the current resource position, although it really doesn't apply in this case.

The file being used is a TFiP_Terminal_File instance.

function TFiP_Terminal_File.Read( Stream : longint ;
    Position : TStore_Address64 ; Length : TStore_Size64 ; var Buff ;
    Flags : longint ) : TStore_Size64 ;

var UEC : TUnified_Exception ;

begin
    Result := Terminal.Read_Data( Buff, Length, UEC ) ;
    if( UEC <> nil ) then
    begin
        Set_Last_Error( UEC ) ;
    end ;
end ;
This simply calls the Read_Data method of the terminal associated with this file and sets the exception if there is an error.

Here is the Read_Data method.

function TTerminal.Read_Data( var Data ; _Size : TStore_Address64 ;
    var UEC : TUnified_Exception ) : TStore_Address64 ;

var S : string ;

begin
    Result := 0 ;
    if( Stream.Write_Only ) then
    begin
        UEC := Set_Last_Error( Create_Error( UOSErr_Write_Only ) ) ;
        exit ;
    end ;
    UEC := Set_Last_Error( nil ) ;
    S := Input_Filter.Read( _Size ) ;
    move( PAnsiChar( S )[ 0 ], Data, length( S ) ) ;
    Result := length( S ) ;
end ;
First we verify that the stream is readable. If not, we set an exception and exit. Otherwise we clear the exception, then call the input filter to read the input from the stream. After that, we copy the input to the passed Data parameter.

At this point, we've followed the input stack from the application program to the terminal's input filter. In the next article, we will look at the input filter.

Copyright © 2018 by Alan Conroy. This article may be copied in whole or in part as long as this copyright is included.