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

Lexical function: F$PARSE

F$PARSE has to do with parsing a filename into its constituent parts. We've seen code for parsing filenames before, but here we will address an expanded parsing capability that handles all valid UOS file and device specifications. A fully-qualified file specification contains the following fields:
node::device:\path\name.type;version
For instance:
MYNODE::DISKA0:\UOS\System\ucl.exe;1
Note that the node name may include access information within quotes. For example:
UNICORN"access"::
This would include the specified access information (user name, password, etc) for access to the node named UNICORN.

Note that if no node name is specified, the current node is assumed. Also note that if the value following the semicolon is not an integer value, it is considered part of the file type and it is assumed that there is no version field. As a reminder, a UOS filename can contain any number of periods (.), thus only the last period found in the specification will be considered the start of the file type. Likewise, any number of semi-colons can appear in the filename or file type, in which case only the last semi-colon in the specification is considered the start of the version (and then only if followed by an integer).

For instance, "Myfile.123.exe" would be parsed as having the filename "Myfile.123" and the file type of ".exe".
"File.exe;abcdef" would be parsed as having the filename "File", the type ".exe;abcdef", and no version field.

As we discussed when we taked about the File Processor many articles ago, the FIP doesn't consider any characters in a file specification to be significant except for those which are used to delimit the node, device, and paths. Thus, to the FIP, file types and versions mean nothing - syntactically, they are just part of the filename. However, certain other parts of UOS can attribute special significance to certain fields of a file specification. We will talk about these in the future, but as an example in the meantime, a file type can be associated with a default action under certain circumstances (for instance, the ".txt" type could default to a given text editor when it is double-clicked in the file explorer, if no other action has been explicitly defined for that specific file).

As we've discussed before, certain characters are not allowed in filenames (including in file types and versions). For instance, colons are used to delimit node and device names and cannot appear in the file's name. Likewise, backslash (\) can only be used as a delimiter for directory names in a path - never in a filename. Asterisk (*) and question mark (?) are reserved for use as wildcards and cannot be part of a file's name. Finally, certain characters act as delimiters (such as slashes and spaces) and, as such, are interpreted as a delimiter ending a file specification. So, if those characters are part of a filename, the filename must be enclosed in quotes (") - which also means that quotes are not valid in a filename.

Here is the definition for F$PARSE.

F$PARSE parses a file specification and returns either the expanded file specification or the requested field requested.

Format
F$PARSE( filespec, {default}, {related}, {field}, {type} )

Return Value
A character string containing the expanded file specification or the specified field. If a complete file specification is not provided for the filespec argument, F$PARSE will supply defaults from the default argument, and if the corresponding item isn't in either the filespec or default, it is provided from the related argument.

If an error is detected during the parse, a null string is returned by F$PARSE. This can occur if the node, device, or directory do not exist. However, if a field name or a type argument of "SYNTAX_ONLY" is passed, F$PARSE returns the requested item without checking for the existence of the node, device, or directory.

Arguments
filespec

Specifies a character string containing the file specification to be parsed. This specification may include asterisk (*) and question mark (?) wildcards. Wildcards will be returned for the corresponding items.

default

Specifies a character string containing the default file specification. The fields in this specification will be substituted if the field is missing from the specification passed in filespec.

related

Specifies a character string containing the default file specification. The fields in this specification will be substituted if the field is missing from both of the specifications passed in filespec and default.

field

Specifies which field of the file specification is to be returned. If a null string is passed to this argument, the entire file specification is returned. The following are the valid field names that can be specified (do not abbreviate):
NODENode name
DEVICEDevice name
DIRECTORYDirectory/path
NAMEFile name
TYPEFile type
VERSIONFile version number

type

Specifies a character string containing the type of parsing to be performed. By default (if this argument is omitted), this function will verify the existence of the node, device, and path specified. However, this verification does not happen if a value is passed to the field argument. F$PARSE will also translate any logical names provided in the arguments. If not null, the argument must be one of the following keywords:
NO_CONCEALTranslates concealed logicals
SYNTAX_ONLYParse the file specification without verying that the node/device/directory path exist.

Description
All but the first argument may be omitted, but commas must be provided in all cases. The SYS$PARSE system call is used to process the file specifications.

If the device and/or path are not provided in any of the arguments, F$PARSE substitutes the current default device and directory. Any other fields not provided in any of the arguments is returned as null.

F$PARSE can validate that a node, device, and path exist, but it will not validate the presence of a specific file.

F$PARSE will ignore any text that occurs after any character that is not valid for use in a file specification unless the specification is included in quotes. For the case of access information for a node, which is enclosed within quotes, a double-quote can be used within quoted node names to indicate the access information, which will be returned as part of the node field.

Examples
$ A = F$PARSE("INFO.DAT","_DISKA1:\uos",,,,"SYNTAX_ONLY")
The symbol A would be set to "_DISKA1:\uos\INFO.DAT".

$ A = F$PARSE("_DISKB0:\UOS\Users\Fred\INFO.DAT",,,"DIRECTORY",)
The symbol A would be set to "\UOS\Users\".

        Function_PARSE : begin
                             if( Missing_Parentheses( '(' ) ) then
                             begin
                                 exit ;
                             end ;
                             if( Parse_Parse( Err, Context ) ) then
                             begin
                                 exit ;
                             end ;
                             if( Missing_Parentheses( ')' ) ) then
                             begin
                                 exit ;
                             end ;
                             S := Context ;
                         end ;
We add this code to the Function_Reference function.

function Parse_Parse( var Err : integer ; var Context : string ) : boolean ;

var I : integer ;
    Code : integer ;
    Name, Default, Related, Field, Parse_Type, S : string ;
    Name_List, Default_List, Related_List : TStringList ;

begin
    // Setup...
    Result := False ; // Assume no problems
    Context := '' ;

    // Get items...
    Name := Get_Parameter( Err, Context ) ;
    if( Err <> 0 ) then
    begin
        exit ;
    end ;
    if( Parser.Peek = ',' ) then
    begin
        Get_Token ; // Eat comma
        Default := Get_Parameter( Err, Context ) ;
        if( Err <> 0 ) then
        begin
            exit ;
        end ;
    end ;
    if( Parser.Peek = ',' ) then
    begin
        Get_Token ; // Eat comma
        Related := Get_Parameter( Err, Context ) ;
        if( Err <> 0 ) then
        begin
            exit ;
        end ;
    end ;
    if( Parser.Peek = ',' ) then
    begin
        Get_Token ; // Eat comma
        Field := lowercase( Get_Parameter( Err, Context ) ) ;
        if( Err <> 0 ) then
        begin
            exit ;
        end ;
    end ;
    if( Parser.Peek = ',' ) then
    begin
        Get_Token ; // Eat comma
        Parse_Type := lowercase( Get_Parameter( Err, Context ) ) ;
        if( Err <> 0 ) then
        begin
            exit ;
        end ;
    end ;
    if( ( Parse_Type <>  'no_conceal' ) and ( Parse_Type <>  'syntax_only' ) and ( Parse_Type <>  '' ) ) then
    begin
        Err := UCL_IVKEYW ; // Unrecognized keyword
        Context := Parse_Type ;
        exit ;
    end ;
This new function processes the F$PARSE function. First it grabs the arguments and validates the parse type.

    // Handle logical translation...
    if( copy( Name, length( Name ), 1 ) = ':' ) then
    begin
        S := Get_Symbol_Value( '', Name ) ;
        if( S <> '' ) then
        begin
            Name := S ;
        end ;
    end ;
    if( copy( Default, length( Default ), 1 ) = ':' ) then
    begin
        S := Get_Symbol_Value( '', Default ) ;
        if( S <> '' ) then
        begin
            Default := S ;
        end ;
    end ;
    if( copy( Related, length( Related ), 1 ) = ':' ) then
    begin
        S := Get_Symbol_Value( '', Related ) ;
        if( S <> '' ) then
        begin
            Related := S ;
        end ;
    end ;
We check each specification. If any of them end with a colon, we try a symbol translation. If the symbol exists, we substitute it for that specification.

    // Parse file specs...
    Name_List := FILESCAN( Name ) ;
    Default_List := FILESCAN( Default ) ;
    Related_List := FILESCAN( Related ) ;
Next we call FILESCAN to parse each specification, which returns a string list. We will cover this function in the next article.

    try
        // Substitute defaults...
        for I := FSCN_V_NODE to FSCN_V_VERSION do
        begin
            if( Name_List[ I ] = '' ) then
            begin
                if( Default_List[ I ] <> '' ) then
                begin
                    Name_List[ I ] := Default_List[ I ] ;
                end else
                if( Related_List[ I ] <> '' ) then
                begin
                    Name_List[ I ] := Related_List[ I ] ;
                end ;
            end ;
        end ;
Now we iterate through the returned string lists and substitute any empty fields with the corresponding default or related fields.

        // Default the device and path, if necessary...
        if( S := Name_List[ FSCN_V_DEVICE ] = '' ) then
        begin
            S := Get_Symbol_Value( '', 'sys$disk' ) ;
            if( S = '' ) then
            begin
                S := 'sys$disk:' ;
            end ;
            Name_List[ FSCN_V_DEVICE ] := S ;
        end ;
        if( Name_List[ FSCN_V_DIRECTORY ] = '' ) then
        begin
            S := GETDDIR ;
            Name_List[ FSCN_V_DIRECTORY ] := S ;
        end ;
If the device is still null after the above processing, we substitute the value of sys$disk. Likewise, if the path is still null, we substitute with the default directory (returned by GETDDIR).

        // Process the specification...
        if( Name_List[ FSCN_V_DEVICE ] <> '' ) then
        begin
            S := Name_List[ FSCN_V_DEVICE ] ;
            setlength( S, length( S ) - 1 ) ; // Trim colon
            S := Get_Symbol_Value( '', S, Parse_Type = 'no_conceal' ) ;
            if(
                ( S <> '' )
                and
                ( copy( S, length( S ), 1 ) = ':' )
                and
                ( copy( S, length( S ) - 1, 2 ) <> '::' )
              ) then
            begin
                Name_List[ FSCN_V_DEVICE ] := S ;
            end ;
        end ;
If the device is not null (shouldn't be at this point, but we check just in case), we translate the logical. It is possible that the device specification isn't a valid physical device or logical device (or any symbol at all), so we only change it if it does translate to a device name - or at least something that ends with a single colon.

        if( Field <> '' ) then
        begin
            S := '' ;
            if( Field = 'node' ) then
            begin
                S := Name_List[ FSCN_V_NODE ] ;
            end else
            if( Field = 'device' ) then
            begin
                S := Name_List[ FSCN_V_DEVICE ] ;
            end else
            if( Field = 'directory' ) then
            begin
                S := Name_List[ FSCN_V_DIRECTORY ] ;
            end else
            if( Field = 'name' ) then
            begin
                S := Name_List[ FSCN_V_NAME ] ;
            end else
            if( Field = 'type' ) then
            begin
                S := Name_List[ FSCN_V_TYPE ] ;
            end else
            if( Field = 'version' ) then
            begin
                S := Name_List[ FSCN_V_VERSION ] ;
            end else
            begin
                Err := UCL_IVKEYW ; // Unrecognized keyword
                Context := Field ;
                exit ;
            end ;
        end else
        begin
            S := Name_List[ FSCN_V_NODE ] + Name_List[ FSCN_V_DEVICE ] +
                Name_List[ FSCN_V_DIRECTORY ] + Name_List[ FSCN_V_NAME ] +
                Name_List[ FSCN_V_TYPE ] + Name_List[ FSCN_V_VERSION ] ;
            if( Parse_Type <> 'syntax_only' ) then
            begin
                if( not Directory_Exists( Name_List[ FSCN_V_NODE ] + 
                  Name_List[ FSCN_V_DEVICE ] + Name_List[ FSCN_V_DIRECTORY ] ) ) then
                begin
                    S := '' ;
                end ;
            end ;
        end ;
Now that we have all the fields, and they've been defaulted and translated, we can return the function result. If a field was specified in the arguments, we return the appropriate field. If the specified field was not recognized, we return an error. If no field was specified, we return the qualified file name, which is done by concatenating the various fields. In that case, if the parse type isn't SYNTAX_ONLY, we check to see if the node/device/path exists by concatenating those values and passing them to the Directory_Exists function (covered below).

        Context := S ;
    finally
        Name_List.Free ;
        Default_List.Free ;
        Related_List.Free ;
    end ;
end ; // Parse_Parse
Finally, we return the value and then free the string lists.

function Directory_Exists( S : string ) : boolean ;

var SRB : TSRB ;

begin
    if( copy( S, length( S ), 1 ) = '\' ) then
    begin
        setlength( S, length( S ) - 1 ) ; // Trim trailing backslash
    end ;
    Set_String( S, SRB ) ;
    if( SYS_File_Exists( int64( @SRB ) ) = 0 ) then
    begin
        Result := True ; // No error on check
    end else
    begin
        Result := False ;
    end ;
end ;
There is no specific "directory exists" function on UOS because directories are just files. So, we simply remove any trailing backslash and send a request to the system to ask if that file exists.

function SYS_File_Exists( Name : int64 ) : int64 ;

var SRB : PSRB ;
    Status : int64 ;
    SysRequest : TString_Request ;

begin
    SRB := PSRB( pointer( Name ) ) ;
    fillchar( SysRequest, sizeof( SysRequest ), 0 ) ;
    Status := 0 ;
    SysRequest.Request.Subsystem :=  UOS_Subsystem_FIP ;
    SysRequest.Request.Request := UOS_FIP_File_Exists ;
    SysRequest.Request.Length := sizeof( SysRequest ) - sizeof( Sysrequest.Request ) ;
    SysRequest.Request.Status := integer( @Status ) ;
    SysRequest.SRB.Buffer := SRB.Buffer ;
    SysRequest.SRB.Length := SRB.Length ;

    Call_To_Ring0( integer( @SysRequest ) ) ;

    Result := Status ;
end ;
This function packages up a system request and calls to the kernel.

        UOS_FIP_File_Exists:
            begin
                UE := Enter_System_Call( Request, SReq, PID, MMC, 
                    sizeof( String_Request ) - sizeof( SReq ), Address ) ;
                if( UE <> nil ) then
                begin
                    Set_Last_Error( UE ) ;
                    exit ;
                end ;
                try
                    String_Request := PString_Request( Address ) ;

                    US := Get_User_String( Kernel, PID, String_Request.SRB, Status ) ;
                    try
                        if( Status = 0 ) then
                        begin
                            Status := ord( not File_Exists( Kernel.PID, US.Contents ) ) ;
                        end ;
                    finally
                        US.Free ;
                    end ;
                    Write_User( Kernel, Kernel.PID, String_Request.Request.Status, sizeof( Status ), 
                        Status ) ;
                finally
                    Exit_System_Call( Request, PID, MMC, sizeof( String_Request ) - sizeof( SReq ) ) ;
                end ;
            end ;
this code is added to the FIP.API method. It simply obtains the passed filename and calls the File_Exists function. The function returns 0 if the file exists (there was no error) or non-zero if the file isn't found (error finding file).

In the next article, we will look at the FILESCAN system service.

 

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