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
201 Mailbox Services
202 Mailboxes, Part 2
203 DEFINE
204 CRELNM
205 DISABLE
206 STOP
207 OPCCRASH and SHUTDOWN
208 APPEND

Glossary/Index


Downloads

SYS_PARSE

VMS has another system service that operates similarly to FILESCAN. In fact, FILESCAN is most likely implemented as a simplified interface to PARSE on VMS. It does the same thing as FILESCAN but fills up a NAML file structure with the parsed information. Our implementation of FILESCAN bypassed all the messy handling of FAB/NAML structures, but fear not: we won't be reimplementing much code for PARSE. Most of the processing that is common to both system services was put into the Parse_Filename function. Like FILESCAN, PARSE is implemented in Starlet in UOS (as opposed to the executive in VMS). Note that, unlike VMS, we only fill in NAML structures. VMS supports both NAM and NAML. As we discussed previously, UOS is only supporting NAML. We may decide to add NAM support as well in the future, but we'll cross that bridge if we get to it.

You might question why we need to have PARSE if we already have FILESCAN. First, we include it for compatibility with VMS. Second, it does a useful job of setting up a NAML structure for us. And, in fact, we will be using it in some future file-related system calls.

Here is the definition of the system call.

The PARSE service parses a file specification string and fills in various NAML fields.

Format
SYS$PARSE( fab, err, suc )

Returns

The result of the operation is stored in the FAB_L_STS item in the FAB structure.

Arguments
fab
Pointer to a FAB block whose contents are to be used as arguments for the PARSE call.

err

Address of a user-written routine to be called if there was an error. If 0, no routine is called. The called routine is assumed to take no parameters and return void.

suc

Address of a user-written routine to be called if there were no errors. If 0, no routine is called. The called routine is assumed to take no parameters and return void.

Description

This function is automatically called as part of the OPEN, CREATE, and ERASE services. It is also used to prepare the FAB and NAML blocks for use in the SEARCH service. The following FAB and NAML fields are potentially read and/or written by this service.
BlockFieldR/WDescription
FABFAB_L_DNAReadDefault file specification string.
FABFAB_L_DNSReadDefault file specification string length, in bytes.
FABFAB_L_FNAReadFile specification string address.
FABFAB_B_FNSReadFile specification string address length, in bytes.
FABFAB_L_NAMReadAddress of NAML block.
FABFAB_L_STSWriteCompletion status code.
NAMLNAML_B_NOPReadProcessing flags. If the NAML_V_SYNCHK flag is set, only a syntax check is performed. Otherwise, the node, device, and path are checked for validity.
NAMLNAML_L_LONG_EXPANDReadAddress of output expanded string value.
NAMLNAML_L_LONG_EXPAND_ALLOCReadMaximum size of expanded output buffer.
NAMLNAML_L_LONG_EXPAND_SIZEWriteLength of output expanded string value.
NAMLNAML_L_LONG_DEFNAMEReadAddress of default file specification. If FAB.FAB_L_DNS is -1, this is used as the default.
NAMLNAML_L_LONG_DEFNAME_SIZEReadLength of default file specification.
NAMLNAML_L_LONG_FILENAMEReadAddress of file specification. If FAB.FAB_L_FNA is -1, this is used as the file specification.
NAMLNAML_L_LONG_FILENAME_SIZEReadLength of file specification.
NAMLNAML_L_LONG_DEVWriteAddress of device name, or 0 if none.
NAMLNAML_L_LONG_DEV_SIZEWriteLength of device name.
NAMLNAML_L_LONG_DIRWriteAddress of the path, or 0 if none.
NAMLNAML_L_LONG_DIR_SIZEWriteLength of the path specification.
NAMLNAML_L_LONG_NAMEWriteAddress of the name portion of the file name, or 0 if none.
NAMLNAML_L_LONG_NAME_SIZEWriteLength of the name portion of the file name.
NAMLNAML_L_LONG_NODEWriteAddress of the node name, or 0 if none.
NAMLNAML_L_LONG_NODE_SIZEWriteLength of the node name.
NAMLNAML_L_LONG_TYPEWriteAddress of the type portion of the file name, or 0 if none.
NAMLNAML_L_LONG_TYPE_SIZEWriteLength of the type portion of the file name.
NAMLNAML_L_LONG_VERWriteAddress of the version portion of the file name, or 0 if none.
NAMLNAML_L_LONG_VER_SIZEWriteLength of the version portion of the file name.
NAMLNAML_L_LONG_RESULT_SIZEWriteSet to 0.
NAMLNAML_W_FIDWriteSet to 0.
NAMLNAML_L_FNBWriteFilename flags.

Condition Codes

The following condition values can be returned:
RMS_FAB
RMS_BLN
RMS_DNA

function LIB_SYS_PARSE( fab : int64 ; err : int64 = 0 ; suc : int64 = 0 ) : int64 ;

var _FAB : PFAB ;
    _NAML : PNAML ;
    FNode, FAccess, FNode2, FDevice, FPath, FName, FType, FVersion : string ;
    DNode, DAccess, DNode2, DDevice, DPath, DName, DType, DVersion : string ;
    CNode, CAccess, CNode2, CDevice, CPath, CName, CType, CVersion : string ;
    DeviceOffset, PathOffset, NameOffset, TypeOffset, VersionOffset : integer ;
    S : string ;
    DirCount, I : integer ;

begin
    // Setup...
    Result := 0 ;
    if( FAB = 0 ) then // No FAB provided
    begin
        Result := RMS_FAB ;
        if( err <> 0 ) then
        begin
            PProcedure( err ) ;
        end ;
        exit ;
    end ;
    _FAB := PFAB( fab ) ;
    if( _FAB^.FAB_B_BID <> FAB_C_BID ) then
    begin
        Result := RMS_FAB ;
        if( err <> 0 ) then
        begin
            PProcedure( err ) ;
        end ;
        exit ;
    end ;
    if( _FAB^.FAB_B_BLN < sizeof( _FAB^ ) ) then
    begin
        Result := RMS_BLN ;
        if( err <> 0 ) then
        begin
            PProcedure( err ) ;
        end ;
        exit ;
    end ;
    if( _FAB^.FAB_L_NAM = 0 ) then // Nothing to do
    begin
        if( suc <> 0 ) then
        begin
            PProcedure( suc ) ;
        end ;
        exit ;
    end ;
    _NAML := PNAML( _FAB^.FAB_L_NAM ) ;
    if( _NAML^.NAML_B_BID <> NAML_C_BID ) then
    begin
        Result := RMS_FAB ;
        _FAB^.FAB_L_STS := Result ;
        if( err <> 0 ) then
        begin
            PProcedure( err ) ;
        end ;
        exit ;
    end ;
    if( _NAML^.NAML_B_BLN < sizeof( _NAML^ ) ) then
    begin
        Result := RMS_BLN ;
        _FAB^.FAB_L_STS := Result ;
        if( err <> 0 ) then
        begin
            PProcedure( err ) ;
        end ;
        exit ;
    end ;
    _FAB^.FAB_L_STS := 0 ;
    CNode := '' ;
    CAccess := '' ;
    CNode2 := '' ;
    CDevice := '' ;
    CPath := '' ;
    CName := '' ;
    CType := '' ;
    CVersion := '' ;
    DeviceOffset := 0 ;
    PathOffset := 0 ;
    NameOffset := 0 ;
    TypeOffset := 0 ;
    VersionOffset := 0 ;
This new function in Starlet first validates that a valid FAB was passed. Then we check the NAML to be sure it is valid. To be valid, there must be a non-zero address for both the FAB (as a parameter) and NAML (in the FAB), the block ID (BID) must be appropriate, and the block length (BLN) must be at least as long as the block. If any of these conditions are not met, we set the FAB_L_STS code, if the FAB is valid, and we call the error (err) procedure (if it is non-zero). Finally, we clear various variables as well as the FAB_L_STS value.

    // Parse the file spec...
    if( _FAB^.FAB_L_FNA = -1 ) then
    begin
        setlength( S, _NAML^.NAML_L_LONG_FILENAME_SIZE ) ;
        move( PChar( _NAML^.NAML_L_LONG_FILENAME )[ 0 ], PChar( S )[ 0 ], length( S ) ) ;
    end else
    begin
        setlength( S, _FAB^.FAB_B_FNS ) ;
        move( PChar( _FAB^.FAB_L_FNA )[ 0 ], PChar( S )[ 0 ], length( S ) ) ;
    end ;
    Parse_Filename( S, FNode, FAccess, FNode2, FDevice, FPath, FName, FType, FVersion ) ;
    if( FDevice <> '' ) then
    begin
        DeviceOffset := length( FNode ) ;
    end ;
    if( FPath <> '' ) then
    begin
        PathOffset := length( FNode ) + length( FDevice ) ;
    end ;
    if( FName <> '' ) then
    begin
        NameOffset := length( FNode ) + length( FDevice ) + length( FPath ) ;
    end ;
    if( FType <> '' ) then
    begin
        TypeOffset := length( FNode ) + length( FDevice ) + length( FPath ) + length( FName ) ;
    end ;
    if( FVersion <> '' ) then
    begin
        VersionOffset :=  length( FNode ) + length( FDevice ) + length( FPath ) + length( FName ) + 
            length( FType ) ;
    end ;
First, we parse the file specification. If FAB_L_FNA is -1, we grab the specification from the NAML block's pointer, otherwise we use the FAB_L_FNA pointer. We then calculate the offsets for each field in the file specification.

    // Parse the default spec...
    if( _FAB^.FAB_L_DNA = -1 ) then
    begin
        setlength( S, _NAML^.NAML_L_LONG_DEFNAME_SIZE ) ;
        move( PChar( _NAML^.NAML_L_LONG_DEFNAME )[ 0 ], PChar( S )[ 0 ], length( S ) ) ;
    end else
    begin
        setlength( S, _FAB^.FAB_B_DNS ) ;
        move( PChar( _FAB^.FAB_L_DNA )[ 0 ], PChar( S )[ 0 ], length( S ) ) ;
    end ;
    Parse_Filename( S, DNode, DAccess, DNode2, DDevice, DPath, DName, DType, DVersion ) ;
    if( ( DeviceOffset <> 0 ) and ( DDevice <> '' ) ) then
    begin
        DeviceOffset := length( DNode ) ;
    end ;
    if( ( PathOffset <> 0 ) and ( DPath <> '' ) ) then
    begin
        PathOffset := length( DNode ) + length( DDevice ) ;
    end ;
    if( ( NameOffset <> 0 ) and ( DName <> '' ) ) then
    begin
        NameOffset := length( DNode ) + length( DDevice ) + length( DPath ) ;
    end ;
    if( ( TypeOffset <> 0 ) and ( DType <> '' ) ) then
    begin
        TypeOffset := length( DNode ) + length( DDevice ) + length( DPath ) + length( DName ) ;
    end ;
    if( ( VersionOffset <> 0 ) and ( DVersion <> '' ) ) then
    begin
        NameOffset := length( DNode ) + length( DDevice ) + length( DPath ) + length( DName ) + 
            length( DType ) ;
    end ;
Next, we parse the default file specification. If FAB_L_DNA is -1, we grab the specification from the NAML block's pointer, otherwise we use the FAB_L_DNA pointer. We then calculate the offsets for each field in the file specification if the offset wasn't already set above.

    // Write extended string...
    if( ( _NAML^.NAML_L_LONG_EXPAND <> 0 ) and ( _NAML^.NAML_L_LONG_EXPAND_SIZE <> 0 ) ) then
    begin
        CNode := FNode ;
        if( CNode = '' ) then
        begin
            CNode := DNode ;
        end ;
        CAccess := FAccess ;
        if( CAccess = '' ) then
        begin
            CAccess := DAccess ;
        end ;
        CNode2 := FNode2 ;
        if( CNode2 = '' ) then
        begin
            CNode2 := DNode2 ;
        end ;
        CDevice := FDevice ;
        if( CDevice = '' ) then
        begin
            CDevice := DDevice ;
        end ;
        CPath := FPath ;
        if( CPath = '' ) then
        begin
            CPath := DPath ;
        end ;
        CName := FName ;
        if( CName = '' ) then
        begin
            CName := DName ;
        end ;
        CType := FType ;
        if( CType = '' ) then
        begin
            CType := DType ;
        end ;
        CVersion := FVersion ;
        if( CVersion = '' ) then
        begin
            CVersion := DVersion ;
        end ;
        S := Get_Symbol_Value( '', CNode, ( _NAML^.NAML_B_NOP and NAML_V_NOCONCEAL ) <> 0 ) ;
        if( S <> '' ) then
        begin
            CNode := S ;
        end ;
        S := CNode + CDevice + CPath + CName + CType + CVersion ;
        if( length( S ) > _NAML^.NAML_L_LONG_EXPAND_ALLOC ) then
        begin
            setlength( S, _NAML^.NAML_L_LONG_EXPAND_ALLOC ) ; // Restrict to max length
        end ;

        // Write final spec...
        _NAML^.NAML_L_LONG_EXPAND_SIZE := length( S ) ; // Update NAML length
        move( PChar( S )[ 0 ], PChar( _NAML^.NAML_L_LONG_EXPAND )[ 0 ], length( S ) ) ;
If an expanded output buffer is provided, we construct a full specification from the parsed fields. For each field, we use the field from the file spec. If the given field wasn't in the specification, we take it from the default. If the resulting string is longer than the expanded buffer size, we trim the string to fit. Then we write that value out to the buffer and set the length.

        // Prepare for following code...
        Parse_Filename( S, CNode, CAccess, CNode2, CDevice, CPath, CName, CType, CVersion ) ;
        FNode := '' ;
        FAccess := '' ;
        FNode2 := '' ;
        FDevice := '' ;
        FPath := '' ;
        FName := '' ;
        FType := '' ;
        FVersion := '' ;
        DNode := '' ;
        DAccess := '' ;
        DNode2 := '' ;
        DDevice := '' ;
        DPath := '' ;
        DName := '' ;
        DType := '' ;
        DVersion := '' ;
    end ; // if( ( _FAB^.NAM_L_ESA <> 0 ) and ( _FAB^.NAM_B_ESS <> 0 ) )
Now we parse the file specification. We do this after truncating the string, if we had to, because the fields we set in NAML will be pointing into this string. Let's say that the file version had been truncated from the string due to output buffer size constraints. If we parsed the string before the truncation, we'd incorrectly believe that we had a version and we'd set the NAML pointer to somewhere past the end of the buffer. But parsing after the truncation means we will not have a value for the version, thus the NAML pointer for the version will be properly set to 0. Finally, in this case of writing the expanded string, we clear all the strings for the file and default specifications since everything will be based off of the combined (expanded) string.

    _NAML^.NAML_L_LONG_NODE_SIZE := _Write( FNode, DNode, CNode, 0, int64( @_NAML^.NAML_L_LONG_NODE ) ) ;
    _NAML^.NAML_L_LONG_DEV_SIZE := 
        _Write( FDevice, DDevice, CDevice, DeviceOffset, int64( @_NAML^.NAML_L_LONG_DEV ) ) ;
    _NAML^.NAML_L_LONG_DIR_SIZE := 
        _Write( FPath, DPath, CPath, PathOffset, int64( @_NAML^.NAML_L_LONG_DIR ) ) ;
    _NAML^.NAML_L_LONG_NAME_SIZE := 
        _Write( FName, DName, CName, NameOffset, int64( @_NAML^.NAML_L_LONG_NAME ) ) ;
    _NAML^.NAML_L_LONG_TYPE_SIZE := 
        _Write( FType, DType, CType, TypeOffset, int64( @_NAML^.NAML_L_LONG_TYPE ) ) ;
    _NAML^.NAML_L_LONG_VER_SIZE := 
        _Write( FVersion, DVersion, CVersion, VersionOffset, int64( @_NAML^.NAML_L_LONG_VER ) ) ;
At this point, we either have the combined fields (CName, CType, etc) or the original and default values, so we set the NAML fields appropriate. We'll look at _Write shortly.

    // Fill file name flags...
    _NAML^.NAML_L_FNB := 0 ;
    if( _NAML^.NAML_L_LONG_VER_SIZE > 0 ) then
    begin
        _NAML^.NAML_L_FNB := _NAML^.NAML_L_FNB or NAM_V_exp_ver ;
    end ;
    if( _NAML^.NAML_L_LONG_TYPE_SIZE > 0 ) then
    begin
        _NAML^.NAML_L_FNB := _NAML^.NAML_L_FNB or NAM_V_exp_type ;
    end ;
    if( _NAML^.NAML_L_LONG_NAME_SIZE > 0 ) then
    begin
        _NAML^.NAML_L_FNB := _NAML^.NAML_L_FNB or NAM_V_exp_name ;
    end ;
    if( _NAML^.NAML_L_LONG_DIR_SIZE > 0 ) then
    begin
        _NAML^.NAML_L_FNB := _NAML^.NAML_L_FNB or NAM_V_exp_dir or NAM_V_root_dir ;
    end ;
    if( _NAML^.NAML_L_LONG_DEV_SIZE > 0 ) then
    begin
        _NAML^.NAML_L_FNB := _NAML^.NAML_L_FNB or NAM_V_exp_dev ;
    end ;
    if( length( FNode ) + length( DNode ) + length( CNode ) > 0 ) then
    begin
        _NAML^.NAML_L_FNB := _NAML^.NAML_L_FNB or NAM_V_node ;
    end ;
    if( pos( '*', FVersion + DVersion + CVersion ) + pos( '?', FVersion + DVersion + CVersion ) > 0 ) then
    begin
        _NAML^.NAML_L_FNB := _NAML^.NAML_L_FNB or NAM_V_wild_ver ;
    end ;
    if( pos( '*', FType + DType + CType ) + pos( '?', FType + DType + CType ) > 0 ) then
    begin
        _NAML^.NAML_L_FNB := _NAML^.NAML_L_FNB or NAM_V_wild_type ;
    end ;
    if( pos( '*', FName + DName + CName ) + pos( '?', FName + DName + CName ) > 0 ) then
    begin
        _NAML^.NAML_L_FNB := _NAML^.NAML_L_FNB or NAM_V_wild_name ;
    end ;
    if( pos( '*', FPath + DPath + CPath ) + pos( '?', FPath + DPath + CPath ) > 0 ) then
    begin
        _NAML^.NAML_L_FNB := _NAML^.NAML_L_FNB or NAM_V_wild_dir ;
    end ;
    if( ( _NAML^.NAML_L_FNB and ( NAM_V_wild_type or NAM_V_wild_ver or NAM_V_wild_name ) ) <> 0 ) then
    begin
        _NAML^.NAML_L_FNB := _NAML^.NAML_L_FNB or NAM_V_wildcard ;
    end ;
    if( pos( '"', S ) > 0 ) then
    begin
        _NAML^.NAML_L_FNB := _NAML^.NAML_L_FNB or NAM_V_quoted ;
    end ;
    FPath := FPath + DPath + CPath ;
    if( copy( FPath, 1, 1 ) = '\' ) then
    begin
        FPath := copy( FPath, 2, length( FPath ) ) ;
    end ;
    DirCount := 0 ;
    I := pos( '\', FPath ) ;
    while( I > 0 ) do
    begin
        inc( DirCount ) ;
        FPath := copy( FPath, I + 1, length( FPath ) ) ;
        I := pos( '\', FPath ) ;
        if( DirCount = 7 ) then
        begin
            if( pos( '*', FPath ) + pos( '?', FPath ) > 0 ) then
            begin
                _NAML^.NAML_L_FNB := _NAML^.NAML_L_FNB or NAM_V_wild_sfdg7 ;
            end ;
        end ;
    end ;
    if( DirCount > 7 ) then
    begin
        _NAML^.NAML_L_FNB := _NAML^.NAML_L_FNB or NAM_V_dir_lvls_g7 ;
    end else
    begin
        DirCount := DirCount shl 20 ;
        _NAML^.NAML_L_FNB := _NAML^.NAML_L_FNB or DirCount ;
    end ;
Next we set the appropriate flags in the NAML_L_FNB field of the NAML block. This is pretty simple until the end. There is a bitmask field in the FNB for the number of directories in the path. This is 3 bits wide, which means it can have the values 0 through 7. If there are more than 7 levels, the number won't fit in this field. Thus, we count up the number of directories in the path by counting the number of slashes (ignoring the initial one, if present). If the count is more than 7, we set the appropriate flag. Otherwise we shift the count appropriately and or it into the FNB. If we reach 7 levels in the loop, we check for any wildcards in the remainder of the path and set the NAM_V_wild_sfdg7 flag if so.

    // Zero out certain fields...
    _NAML^.NAML_L_LONG_RESULT_SIZE := 0 ;
    _NAML^.NAML_W_FID := 0 ;

    // Synatx check...
    if( ( _NAML^.NAML_B_NOP and NAML_V_SYNCHK ) = 0 ) then
    begin
        if( not Directory_Exists( FNode + FDevice + FPath ) ) then
        begin
            Result := RMS_DNF ;
        end ;
    end ;
    _FAB^.FAB_L_STS := Result ;
    if( Result = 0 ) then
    begin
        if( suc <> 0 ) then
        begin
            PProcedure( suc ) ;
        end ;
    end else
    if( err <> 0 ) then
    begin
        PProcedure( err ) ;
    end ;
end ; // LIB_SYS_PARSE
To finish up, we clear certain fields, as per the VMS specification. If the NOP flags indicate that a syntax-only check wasn't requested, we check to make sure the node, device, and path exist. If not, we return an error. Again, if there was an error and the err parameter was non-zero, we call the routine. Otherwise, if the suc parameter was non-zero, we call that routine.

    function _Write( F, D, C : string ; Offset, Out_Address : int64 ) : integer ;

    var I : int64 ;

    begin
        if( F <> '' ) then
        begin
            Result := length( F ) ;
            if( _FAB^.FAB_L_FNA = -1 ) then
            begin
                I := Offset + _NAML^.NAML_L_LONG_FILENAME ;
            end else
            begin
                I := Offset + _FAB^.FAB_L_FNA ;
            end ;
        end else
        if( D <> '' ) then
        begin
            Result := length( D ) ;
            I := Offset + _FAB^.FAB_L_DNA ;
        end else
        if( C <> '' ) then
        begin
            I := Offset + _NAML^.NAML_L_LONG_EXPAND ;
            Result := length( C ) ;
        end else
        begin
            Result := 0 ;
            exit ;
        end ;
        move( I, PChar( Out_Address )[ 0 ], sizeof( int64 ) ) ;
    end ; // ._Write
This local function is used to write the appropriate value to the NAML block. This is called for each field, as described earlier. The file specification, default specification, and combined specification for the field is passed in along with the field offset and the output address to write the offset into. If a file specification was passed, then we offset from the NAML address if that is where it came from (FAB_L_FNA is 0), otherwise we offset from the FAB address. If the value came from the default specification, we offset from the FAB or NAML pointers, as appropriate. If the value came from the combined specification, we know this is an offset into the expanded string, so we offset from that. If all strings are zero, the offset is 0. We return the length, which is the length of the appropriate field. Then we write the address to the passed output address (which will be in the NAML).

When we are finished, the NAML will contain pointers into the appropriate string, whether that was the FAB or NAML specification, the FAB or NAML default specification, or the output expanded string.

function SYS_PARSE( fab : int64 ; err : int64 = 0 ; suc : int64 = 0 ) : int64 ;

begin
    Result := LIB_SYS_PARSE( fab, err, suc ) ;
end ;
Finally, there is the SYS_PARSE routine which simply calls the Startlet function.

In the next article, we will look at the next lexical function.

 

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