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

Comments, labels, and GOTO

We've discussed UCL comments in a few previous articles (primarily 59, 62 and 66) as part of looking at other code, however due to a bug, they didn't function properly before now. Since we never formally discussed comments, here is the user documentation:

! (Comment)

Indicates that the remainder of the line is a comment and should be ignored by UCL. You cannot use command continuation on a line that has a comment since the dash is interpreted as part of the comment itself.

Format

! comment text

Example:


$ WRITE SYS$OUTPUT FILENAME ! Show the user the next filename


Labels are a means of indicating a specific location in a command file that can be later used as a target for the GOTO command. Use of labels are only allowed in files, so if tried with a terminal, an error occurs. The way that DCL/UCL works is that whenever a label is encountered, the line of the file containing the label is saved along with the label name. If encountered again, the line number is updated. Thus, although not recommended, the same label can be used multiple times in a script. When referenced with GOTO, the last encountered label is the one that execution transfers to. Thus, our job of processing labels in UCL is quite simple.

                            Labels : TStringList ;
First we add a list to the TUCL_Context class. This will hold our labels.

    Labels := TStringList.Create ;
This line is added to the TUCL_Context constructor.

                if( S = ':' ) then // A label
                begin
                    if( Interactive ) then
                    begin
                        Exception( UCL_NOLBLS, Sym + ':' ) ;
//NEW------>
                    end else
                    begin
                        Add_Label( Sym ) ;
//<------NEW
                    end ;
                    continue ;
                end ;
Next, in the Process routine we add support for labels. The code between the comments is what we've added. If not in interactive mode, we call the Add_Label routine to add/update the label list.

procedure Add_Label( Sym : string ) ;

var I : integer ;

begin
    Sym := lowercase( Sym ) ;
    I := This_UCL_Context.Labels.IndexOf( Sym ) ;
    if( I = -1 ) then // Not encountered before
    begin
        This_UCL_Context.Labels.AddObject( Sym, pointer( This_UCL_Context.syscommand_line ) ) ;
    end else
    begin
        This_UCL_Context.Labels.Objects[ I ] :=
            pointer( This_UCL_Context.syscommand_line ) ;
    end ;
end ;
This routine adds the label and current line number to the list, if not already in it. If the label is already present we update the line number for the label.


The GOTO command is used to redirect execution of the script at a different location. It is only allowed in files, so use of it in interactive mode does nothing. My first inclination would be to show an error if used, but DCL just ignores it. In the case of an interrupted command file, we might allow it to operate (although DCL does not). But, that is something for a future article. Here is the documentation for the command.

GOTO

Transfers control to a label in a script.

Format

GOTO label

Parameter

label
Specifies a label in the script. When the GOTO command is executed, control passes to the command following the specified label.

Description

GOTO is used to transfer control to a line that is not the next line in the script file. If the script file is not on a random access device (such as a disk file), the GOTO command performs no operation.

If there are duplicate labels of the same name, control is always transferred to the label most recenly encountered during script execution. If not previously encountered, the entire script is searched for the label, in which case the first matching label is used. If no matching label is found, an error occurs.

Example:


$ A=0
$LOOP:
.
.
.
$ A=A+1
$ IF A.LT.10 THEN GOTO LOOP


                    if( Sym = 'goto' )
                    begin
                        Process_Goto ;
                        exit ; // Fetch next command
                    end else
This code is added to the Process routine. It calls the Process_Goto function and then exits to process the next command. Either there was an error or the command file is positioned at the specified label position from where the next command will be fetched and executed.

procedure Process_Goto ;

var I, Position : integer ;
    S : string ;

begin
    Position := This_UCL_Context.syscommand_line ; // Save our position
    S := Find_Target_Label ;
    if( S = '' ) then
    begin
        exit ; // Aborted
    end ;

    I := This_UCL_Context.Labels.Indexof( S ) ;
    Goto_Line( integer( This_UCL_Context.Labels.Objects[ I ] ) ) ;
end ; // Process_Goto
This new routine obtains the target label (exiting on error), and then calls Goto_Line to move execution to the line after that label.

function Find_Target_Label : string ;

var Code : int64 ;
    E, I, Position : integer ;
    Lab, S, S1, Sym : string ;

begin
    Lab := lowercase( Get_Prompted_Parameter( '_Label: ' ) ) ;
    if( Lab = '' ) then
    begin
        Result := '' ;
        exit ; // Aborted
    end ;
    Position := This_UCL_Context.syscommand_line ; // Save our position
    I := This_UCL_Context.Labels.Indexof( Lab ) ;
This new routine obtains the label which to position to. If there is no label following the command, DCL will prompt the user for the label. I am at a loss to understand why DCL does this. Consider: since GOTOs are ignored from a terminal, why prompt for the label on the terminal? And if the GOTO without a label is encountered in a script file, there is no point to prompt for a label. But since DCL does it, UCL will implement the same behavior. This is the purpose of the Get_Prompted_Parameter routine (covered later). If it returns null, there was no label or it was aborted and we exit. Otherwise, we save the current input line position and then look up the label in the label list.

    if( I = -1 ) then // Not already found
    begin
        LIB_Seek_File( RH_sysCommand, 0 ) ;
        while( true ) do
        begin
            S := Edit( Get_Command_Line( '' ), 4 or 128, 0 ) ; // Remove CRLF, trailing spaces, etc.
            E := LIB_Get_Exception( 0 ) ;
            if( E <> 0 ) then
            begin
                Goto_Line( Position + 1 ) ;
                LIB_Get_Exception_Info( 0, 0, int64( @Code ), 0 ) ;
                if( Code = UOSErr_Read_Past_End ) then // EOF
                begin
                    Exception( UCL_USGOTO, '' ) ;
                    exit ;
                end ;
                Set_Exception( LIB_Get_Exception_Text( 0, E ) ) ;
                Handle_Exception ;
                exit ;
            end ;
            if( copy( S, 1, 1 ) = '$' ) then
            begin
                S := trim( copy( S, 2, length( S ) ) ) ;
                if( Parser <> nil ) then
                begin
                    Parser.Free ;
                end ;
                Parser := TParser.Create ;
                Parser.Command := S ;
                Parser.Add_Special_Token( '==' ) ;
                Parser.Add_Special_Token( ':==' ) ;
                Parser.Add_Special_Token( '::' ) ;
                Parser.Add_Special_Token( ':=' ) ;
                Parser.Set_Quotes( '"' ) ;
                Sym := Get_Token ;
                S1 := Get_Token ;
                if( S1 = ':' ) then
                begin
                    if( lowercase( Sym ) = Lab ) then
                    begin
                        Add_Label( Sym ) ;
                        break ;
                    end ;
                end ;
            end ; // if( copy( S, 1, 1 ) = '$' )
        end ; // while( true )
    end ; // if( I = -1 )
    Result := Lab ;
end ;
If the label isn't in the list, we need to find it. Because previous GOTOs may have jumped over a matching label, we can't simply assume we haven't seen it and iterate through the rest of the command file to find the label. Thus, we have to start over looking for the label from the start of the script. The LIB_Seek_File system call will reset the file to the start of the script. We will cover that routine later in the article.
Each time through the loop, we read the next line, trim it and check for an error. If there was an error, we reposition to the next line after the GOTO in the script. Then we exit with the appropriate error. For each line that we read, we ignore those without a leading $. For the rest, we create a parser for the line and read it to see if it starts with a label. If so, if it is the label we are looking for, we add the label and exit the loop. Adding the label means that an Indexof call after the function exits will find the label, with the proper location, in the label list.

procedure Goto_Line( L : integer ) ;

begin
    if( L < This_UCL_Context.syscommand_line ) then
    begin
        LIB_Seek_File( RH_sysCommand, 0 ) ;
        This_UCL_Context.syscommand_line := 0 ;
    end ;

    // Read up to the target line...
    while( This_UCL_Context.syscommand_line < L - 1 ) do
    begin
        Get_Line( '' ) ;
    end ;
end ;
This routine positions the command file to the requested line. If the line is before the current line, we reset the file position. Either way, we iterate through the script to the specified line.

function Get_Prompted_Parameter( Prompt : string ) : string ;

begin
    Result := Edit( Get_Token, 512, 0 ) ;
    if( Interactive ) then
    begin
        exit ;
    end ;
    Waiting_For_Label := True ;
    while( Result = '' ) do
    begin
        Result := Get_Line( Prompt ) ;
        if( not Waiting_For_Label ) then // Aborted
        begin
            exit ;
        end ;
    end ;
end ;
This routine obtains the next command line, using the prompt specified. If the result is empty (the user just pressed ENTER), we prompt again. Either we loop until there is input or until the Waiting_For_Label flag is cleared. This flag is set just before we loop, and it is only cleared if the user interrupts the process with control-C or control-Y (handled in the CC_AST and CY_AST routines that we covered in the past).

procedure Exception( Err : integer ; const Context : string = '' ) ;

begin
    Set_Exception( UCL_ERT( Err ), Context ) ;
end ;
This new routine simply takes a numeric UCL error, converts it to a textual form and calls Set_Exception.

function LIB_Seek_File( Handle, Position : int64 ) : int64 ;

var Status : int64 ;
    SysRequest : TFile_Request ;

begin
    Status := 0 ;
    fillchar( SysRequest, sizeof( SysRequest ), 0 ) ;
    SysRequest.Request.Subsystem :=  UOS_Subsystem_FIP ;
    SysRequest.Request.Request := UOS_FIP_Position ;
    SysRequest.Request.Length := sizeof( SysRequest.FRB ) ;
    SysRequest.Request.Status := integer( @Status ) ;
    SysRequest.FRB.Length := Position ;
    SysRequest.FRB.Handle := Handle ;
    Call_To_Ring0( integer( @SysRequest ) ) ;
end ;
This function is the wrapper for the system call to set the file position. Nothing exceptional here.

function SYS_REWIND( _RAB, Err, Success : int64 ) : int64 ;

var RAB : TRAB ;

begin
    if( _RAB = 0 ) then
    begin
        Result := RMS_RAB ;
        exit ;
    end ;
    Result := LIB_Seek_File( PRAB( _RAB ).RAB_W_ISI, 0 ) ;
end ;
While LIB_Seek_File is what we use above and provides us with the ability to arbitrarily position any file, the SYS_REWIND routine (in the RMS unit) is the VMS means of setting the file position to 0, so we support that for compatibility sake. In UOS, it is simply a wrapper for LIB_Seek_File. When we add RMS support to UOS (in the distant future), this function will do additional work, depending on the RMS file's structure.

        UOS_FIP_Position:
            begin
                if( not Enter_Standard_FRB_Call( False ) ) then
                begin
                    exit ;
                end ;
                try
                    Position_File( SysReqFRB.FRB.Handle, SysReqFRB.FRB.Length, SysReqFRB.FRB.Flags ) ;
                finally
                    Exit_Standard_FRB_Call ;
                end ;
            end ;
This code is added to the FiP's API method.

function TUOS_FiP.Position_File( Handle : THandle ; Pos, Flags : int64 ) : TUnified_Exception ;

var Len : TStore_Size64 ;
    Resource : TResource ;
    Written : cardinal ;

begin
    Set_Last_Error( nil ) ;
    Handle := USC.Translate_Handle( Kernel.PID, Handle ) ;
    if( not USC.Valid_Handle( Kernel.PID, Handle ) ) then
    begin
        Result := Set_Last_Error( Create_Error( UOSErr_Invalid_Handle ) ) ;
        exit ;
    end ;
    Resource := TResource( Handle ) ;
    if( Resource._File.Is_Store ) then // Only affects stores and streams
    begin
        if( Pos > Resource._File.Get_File_Size ) then
        begin
            Pos := Resource._File.Get_File_Size ; // Cannot position beyond EOF
        end ;
        Resource.Position := Pos ;
    end ;
end ; // TUOS_FiP.Position_File
This function converts from the handle to the resource and, if it is a store, it sets the file position. Note that we compare the position with the file length. If the position is beyond that, we change it to the file length before setting the file's position. Note that we pass some flags to the routine as well. At this point that is ignored, but is included in case we wish to do some optional behavior sometime in the future.

In the next article, we will look at two more UCL commands.

 

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