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

INQUIRE

The primary use for UCL during startup is to dialog with the user to set up UOS. Although we've covered ways to output text for the user, we will also need to provide a means of getting input from the user. This is accomplished via the INQUIRE command. Here is the user documentation:

INQUIRE

Reads a value from SYS$COMMAND (usually the terminal or the next line in the current command procedure) and assigns that value to a symbol.

Format

INQUIRE {qualifiers} symbolname {prompt}

Parameters

symbolname
Specifies the name of the symbol to which the value is assigned. If the symbol doesn't exist, it is created.

prompt
Specifies optional prompt text to be displayed to the user just prior to accepting input. The prompt is converted to upper case unless enclosed in quotes ("). Quotes must be used if the prompt contains punctation or whitespace (tabs and/or spaces). To include quotes in the prompt, use doubled quotes.
If a prompt is not specified, UCL uses the symbol name as the prompt. The prompt is displayed with a colon (:) and space at the end of the prompt string. This behavior can be modified with the /PUNCTUATION qualifier.

Description

INQUIRE displays the prompt and reads the response from the process' command stream. That is: the sys$command device originally assigned to the process. That means that if the INQUIRE command is encountered inside a command procedure file (however deeply nested), the prompt will be sent to the terminal that started the command file, and the input value will be read from the same.
If the command file is being run as a batch job, the input value is read from the next line of the topmost nesting level. If that line begins with a dollar sign ($), it is considered to be a command instead of input. In this case, the symbol is assigned a null value.
When the value is assigned to the symbol, the value is first converted to upper case, leading and trailing whitespace is trimmed, and multiple spaces/tabs between characters are reduced to a single space. This conversion is not done within quotes (") within the input.
Single quotes (') not occurring within double quotes (") will trigger the symbol substitution feature of UCL.

Qualifiers

/GLOBAL
This indicates that the symbol is a global symbol rather than a local symbol.

/LOCAL (default)
This indicates that the symbol is a local symbol rather than a global symbol. This is the default behavior

/{NO}PUNCTUATION
The default is /PUNCTUATION, which displays a colon and space after the prompt. Using /NOPUNCTUATION suppresses the colon and space.

Example:


$ INQUIRE/NOPUNCTUATION CONFIRM "Are you sure you want to proceed? "
$ IF .NOT. CONFIRM THEN RETURN
This code would result in the following prompt being shown on the terminal (no colon is shown due to the /NOPUNCTUATION):

Are you sure you want to proceed? 
If the user enters an odd numeric value or a string that begins with T, t, Y, or y, the command file will continue. Otherwise it will return to the caller.


The behavior described above might seem a little convoluted. Certainly, if I weren't using the VMS documentation as the specification, I would have implemented this as read from SYS$INPUT as it exists at the time of the execution of INQUIRE, rather than using SYS$COMMAND of the outermost level. But one of the reasons we are following the VMS specification is because the designers of VMS put a lot of thought into the design, which was refined over many years of wide usage (VMS has been around longer than MS Windows), so I tend to defer to its behavior despite the few deviations we've made so far. And if you think about it, the VMS behavior makes sense: one may want to query the user for information during some long, involved process. SYS$COMMAND may have been redirected due to a nested command file, but we still want to query the user. That is, essentially, what the above description defines. As we will see in a future article, there are ways of getting input from a specific place, but INQUIRE is used to query the user.
Of course, the situation with a batch file (a command file running without an attached terminal) is a little different. Basically, the outermost level serves as the terminal would in an interactive process.

                    if( Sym = 'inquire' ) then
                    begin
                        Process_Inquire ;
                    end else
This code is added to the Process routine.

procedure Process_Inquire ;

var Err : integer ;
    Buffer : PAnsiChar ;
    Context : string ;
    F : TCOM_File64 ;
    I, Index : integer ;
    E, I64 : int64 ;
    Prompt, S, Sym : string ;
    Res : int64 ;
    Status : integer ;
    Switches : TStringList ;
    Local, Punctuation : boolean ;
    Table : integer ;
    Original_Context : TUCL_Context ;

begin
    // Setup...
    Local := True ;
    Punctuation := True ;
    Switches := Parse_Switches ;

    // Process switches...
    for I := 0 to Switches.Count - 1 do
    begin
        S := uppercase( Switches[ I ] ) ;
        if( Name_Match( S, 'LOCAL', 1 ) ) then
        begin
            Local := True ;
        end else
        if( Name_Match( S, 'GLOBAL', 1 ) ) then
        begin
            Local := False ;
        end else
        if( Name_Match( S, 'PUNCTUATION', 1 ) ) then
        begin
            Punctuation := True ;
        end else
        if( Name_Match( S, 'NOPUNCTUATION', 1 ) ) then
        begin
            Punctuation := False ;
        end else
        begin
            Exception( UCL_IVQUAL, S ) ;
            Switches.Free ;
            exit ;
        end ;
    end ;
    Switches.Free ;
This new function handles the INQUIRE command. First we parse the switches, setting the appropriate flags and exiting on unrecognized switches.

    // Get symbol name...
    Sym := lowercase( Get_Token ) ;
    if( Sym = '' ) then // No symbol specified
    begin
        Exception( UCL_PARMDEL, '' ) ;
        exit ;
    end ;
    if( not Valid_Symbol_Name( Sym ) ) then // Invalid symbol
    begin
        Exception( UCL_PARMDEL, Sym ) ;
        exit ;
    end ;
After switches, we get the symbol name that is the target of the input. If one was not provided or isn't a valid symbol name, we exit with an error.

    // Get prompt...
    Prompt := Edit( Get_Token, 8 or 16 or 32 or 128 or 256 ) ;
    if( length( Prompt ) = 0 ) then
    begin
        Prompt := uppercase( Sym ) ;
    end ;
    if( Punctuation ) then
    begin
        Prompt := Prompt + ': ' ;
    end ;
Next we get the prompt. If no prompt was provided, we use the uppercase of the symbol name. Either way, if /NOPUNCTUATION wasn't specified, we suffix the prompt with a colon and a space.

    // Prompt the user...
    F := nil ;
    Original_Context := TUCL_Context( Contexts[ 0 ] ) ;
    if( Original_Context.syscommand_name = This_UCL_Context.syscommand_name ) then
    begin
        if( Interactive ) then
        begin
            Output( RH_SysCommand, Prompt ) ;
        end ;
    end else
    if( Interactive( Original_Context.syscommand_name ) ) then
    begin
        F := Open_Binary_File( Original_Context.syscommand_name, 0 ) ;
        F.Blockwrite( PChar( S )[ 0 ], length( S ), Res ) ;
    end ;
    F.Free ;
    F := nil ;
Now it is time to prompt the user. We get the outermost context and compare its syscommand name to the current syscommand name. If they are the same, we can simply output the prompt to the current sys$command if we are interactive. If they differ, we are currently using a different source for commands, which is probably not the terminal. In that case, we open the file and write the prompt if the device is interactive. Note that we have added a parameter to Interactive, which we will address in a bit.
You might be saying, "Wait! You're outputting to the input-only sys$command!" Well, we only do that if the sys$command is interactive - meaning it is a terminal. In such a case, the terminal isn't open as read-only, so we can write to it without problem. Why not use sys$output? Because that might have been redirected (either by reassignment or by a /OUTPUT switch), and the point of INQUIRE is to prompt the user, so will send the prompt to the terminal, regardless of any output redirection.

    // Get input from user...
    if( F = nil ) then
    begin
        S := Get_Line( '' ) ;
        if( not Interactive ) then
        begin
            if( copy( S, 1, 1 ) = '$' ) then
            begin
                Put_Line( S ) ;
                S := '' ;
            end ;
        end ;
    end else
    begin
        for I := 0 to Original_Context.syscommand_line do // Read up to current line
        begin
            F.Readln( Buffer ) ;
        end ;
        S := Buffer ;
        F.Free ;
        if( copy( S, 1, 1 ) = '$' ) then
        begin
            S := '' ;
        end else
        begin
            inc( Original_Context.syscommand_line ) ;
        end ;        
    end ;
Now we are ready to read the user's response. If no file is assigned (F is nil), we are already getting our input from the outermost source, so we use Get_Line (with no prompt) to get the value. If it begins with a dollar sign and we're not interactive, the source must be a command file that doesn't have data for the prompt. This means that we've read a line that is actually the next command, so we use Put_Line (covered below) to essentially put the command back. And we clear S.
However, if we opened a file for the prompt, we need to read the input from that file. Likewise, if the first character isn't a dollar sign, we clear the value to be assigned to the symbol. Otherwise, we increment the syscommand_line of the outermost nesting level, so that when we return to that level, UCL will read up to, and past, the line that we just read as the response to the INQUIRE.

    // Massage value and do symbol substitution...
    S := Edit( S, 4 or 8 or 16 or 32 or 128 or 256 or $100000 ) ;
    // Remove CR/LF/etc, leading/trailing spaces, and reduce whitespace to single
    // space, but not within quotes

    S := Trim_Quotes( S ) ;
    S := Phase_I_Substitution( S ) ;
Now that we have a response, we format it according to the above rules. We trim any quotes from the start/end of the string, and then run the phase I substitution process on the data to do symbol substitution.

    // Set symbol...
    if( Local ) then
    begin
        Table := LNM_PROCESS ;
    end else
    begin
        Table := LNM_JOB ;
    end ;
    LIB_Set_Symbol( Sym, S, Table ) ;
    E := LIB_Get_Exception( 0 ) ;
    if( E <> 0 ) then
    begin
        S := LIB_Get_Exception_Text( 0, Status ) ;
        Set_Exception( S ) ;
    end ;
end ; // Process_Inquire
Finally, we assign the value to the symbol. We choose the appropriate table, do the assignment, and report any errors.

function Interactive( Dev : string = '' ) : boolean ;

begin
    Buff := 0 ;
    BufLen := 0 ;
    fillchar( Descriptor, sizeof( Descriptor ), 0 ) ;
    Descriptor[ 0 ].Buffer_Length := sizeof( Buff ) ;
    Descriptor[ 0 ].Item_Code := DVI_DEVCHAR ; // DVIDEF item
    Descriptor[ 0 ].Buffer_Address := integer( @Buff ) ;
    Descriptor[ 0 ].Return_Length_Address := integer( @BufLen ) ;
    if( Dev = '' ) then // Default command
    begin
        GETDVIW( 0, RH_SysCommand, '', integer( @Descriptor ), integer( @IOSB ),
            0, 0, '', 1 ) ;
    end else
    begin
        GETDVIW( 0, 0, Dev, integer( @Descriptor ), integer( @IOSB ), 0, 0, '',
            1 ) ;
    end ;
    Result := ( ( Buff and DEV_V_TRM ) <> 0 ) ; // Terminal device
end ;
This function has been updated to take an optional parameter indicating which device to check - it defaults to the current sys$command.

var Pushed_Lines : TStringList = nil ;

procedure Put_Line( const S : string ) ;

begin
    if( Pushed_Lines = nil ) then
    begin
        Pushed_Lines := TStringList.Create ;
    end ;
    Pushed_Lines.Add( S ) ;
end ;
This new code is to support "putting a line back" if we got one that we didn't expect. Specifically: a command line instead of a data line.

    if( ( Pushed_Lines <> nil ) and ( Pushed_Lines.Count > 0 ) ) then
    begin
        Result := Pushed_Lines[ 0 ] ;
        Pushed_Lines.Delete( 0 ) ;
        exit ;
    end ;
This paragraph of code is inserted at the beginning of the Get_Line function to return the next line that was put back with Put_Line, if any, rather than reading from the actual command source.

We referenced the Blockwrite method for the file object in the code above. We've covered reading data from files, but we haven't yet discussed writing to files. So, in the next article, we will look at the WRITE system service.

 

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