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

CRELNT system service

Let's briefly review how symbol tables work in UOS. As we've discussed in the distant past, DCL uses local symbol tables and VMS only supports logical name tables. In UOS, we've combined these into system symbol tables. So, though we use the VMS system services names (such as CRELNT for Create Logical Name Table), remember that logical names are equivalent to symbol names in UOS. Logical name tables are equivalent to symbol name tables in UOS.

We have used the LN_* constants to reference the process/job/system/cluster symbol table names. That was fine for our uses up to this point, but with the need to dynamically create and delete tables, we need a means of refer to specific tables within the process name space. Thus, we have modified the code to handle actual table names, as does VMS. We translate the LN_* codes to actual names. For instance, LN_PROCESS is converted to "lnm$process". There is little change to behavior since lnm$process will almost always refer to the default process symbol table, no matter what the table's actual name is. However, there are certain cases, where we will need to look up the table that is literally named "lnm$process". I'll point those out when appropriate. Allowing tables by names means that we can create as many different concurrent tables as we need.

To keep track of the burgeoning number of potential tables, the tables are gathered into parent "tables", which are implemented as lists. but these tables also need names. For instance, the "lnm$process_directory" table is the collection of all process-private tables. It is necessary to refer to these parent tables to indicate where a table to be created/deleted is located, so we will use these parent table names in our code. CRELNT is the system service that we use to create a table. Here is the documentation:

CRELNT

Creates a process-private or public-sharable symbol table.

Format
SYS_CRELNT( Attr, Resnam, Reslen, Quota, Promsk, Tabnam, Partab, AcMode )

Arguments
Attr

Address of the attribute flags to apply to the newly created table. If the address is 0 or points to an integer 0 value, no flags are associated with the table. The valid flags are:
FlagDescription
LNM_M_CREATE_IFCreate the table only if it does not exist. If not specified, and a table with the same name and access mode already exists, it is deleted and a new table is created.
LNM_M_MAKE_DEFAULTApplies only to process-private tables. If specified, the new table is made the new default (lnm$process).

Resnam

Address where to write the new table name. If 0, the name isn't written.

Reslen

Address of a 64-bit integer to receive the length of the result name.

Quota

Quota for the new table, indicating the limit of the table contents in bytes. 0 indicates no quota.

Promsk

Protection mask for the new table.

Tabnam

Address of a TSRB structure specifying the new table name. If this is 0 or points to a TSRB of a null string, the table name is created by UOS with a unique name of "lnm$x" where "x" is a hexadecimal value.

Partab

Address of a TSRB structure specifying the new table's parent. If this is "lnm$process_directory" the new table will be private to the current process.

AcMode

Address of the access mode of the new table. If not specified (the address is 0), the process' current access mode is used.

Description

This system service creates a symbol table. The SYSNAM privilege is required to create a table at an access mode more privileged than that of the calling process.

Condition Codes

CodeMeaning
SS_NORMALNormal completion
SS_ACCVIOCould not access the memory associated with one of the parameters
SS_BADPARAMAn invalid parameter was passed
SS_NOLOGTABThe specified parent table wasn't found
SS_NOPRIVUser lacked the privilege to perform the operation
SS_RESULTOVFThe table was created, but the result name location was specified and was too small to contain the full name
SS_SUPERSEDENormal completion. Existing table was superseded by the new table

function CRELNT( Attr : int64 ; var Resnam : string ; Quota : int64 ;
    Promsk : int64 ; Tabnam, Partab : string ;
    AcMode : int64 ) : int64 ;

var S_Resname, S_Tabnam, S_Partab : TSRB ;
    Reslen : int64 ;

begin
    Resnam := '                                ' ;
    Set_String( Resnam, S_Resname ) ;
    Set_String( Tabnam, S_Tabnam ) ;
    Set_String( Partab, S_Partab ) ;
    Result := SYS_CRELNT( int64( @Attr ), int64( @S_Resname ), int64( @Reslen ),
        int64( @Quota ), int64( @Promsk ), int64( @S_Tabnam ), int64( @S_Partab ),
        int64( @AcMode ) ) ;
    setlength( Resnam, Reslen ) ;
end ;
This new routine is added to PasStarlet. It provides a Pascal interface that wraps the strings into SRBs, calls the executive, and sets the result string.

function SYS_CRELNT( Attr, Resnam, Reslen, Quota, Promsk, Tabnam, Partab,
    AcMode : int64 ) : int64 ;

var SysRequest : TS3I5_Request ;
    Resname_SRB : PSRB ;
    Tabnam_SRB : PSRB ;
    Partab_SRB : PSRB ;

begin
    Resname_SRB := PSRB( pointer( Resnam ) ) ;
    Tabnam_SRB := PSRB( pointer( Tabnam ) ) ;
    Partab_SRB := PSRB( pointer( Partab ) ) ;

    SysRequest.Request.Subsystem :=  UOS_Subsystem_SSC ;
    SysRequest.Request.Request := UOS_SSC_CRELNT ;
    SysRequest.Request.Length := sizeof( SysRequest ) - sizeof( Sysrequest.Request ) ;
    SysRequest.Request.Status := 0 ;
    SysRequest.SRB1.Buffer := Resname_SRB.Buffer ;
    SysRequest.SRB2.Buffer := Tabnam_SRB.Buffer ;
    SysRequest.SRB3.Buffer := Partab_SRB.Buffer ;
    SysRequest.SRB1.Length := Resname_SRB.Length ;
    SysRequest.SRB2.Length := Tabnam_SRB.Length ;
    SysRequest.SRB3.Length := Partab_SRB.Length ;
    SysRequest.Integer1 := Attr ;
    SysRequest.Integer2 := Reslen ;
    SysRequest.Integer3 := Quota ;
    SysRequest.Integer4 := Promsk ;
    SysRequest.Integer5 := AcMode ;

    Call_To_Ring0( integer( @SysRequest ) ) ;

    Result := SysRequest.Request.Status ;
end ;
This is the new routine in the SYS unit. It is standard fare for system service wrappers.

        UOS_SSC_CRELNT:
            begin
                UE := Enter_System_Call( Request, SReq, PID, MMC, 
                    sizeof( TS3I5_Request ) - sizeof( TSystem_Request ), Base ) ;
                if( UE <> nil ) then
                begin
                    Set_Last_Error( UE ) ;
                    exit ;
                end ;

                try
                    PS3I5 := PS3I5_Request( Base ) ;
                    PS3I5.Request.Status := CRELNT( Kernel, PID, PS3I5.SRB2, PS3I5.SRB3,
                        PS3I5.SRB1, PS3I5.Integer2, PS3I5.Integer1,
                        PS3I5.Integer3, PS3I5.Integer4, PS3I5.Integer5 ) ;
                finally
                    Exit_System_Call( Base, PID, MMC, sizeof( TS3I5_Request ) ) ;
                end ;
            end ;
This code is added to the SSC's API method to handle the CRELNT call.

function TUOS_SSC.CRELNT( Kernel : TUOS_Kernel ; PID : TPID ;
    TableName, ParentName, Return_Name : TSRB ;
    Reslen, _Attr, _Quota, _Promsk, _AcMode : int64 ) : int64 ;

var Table_Name, Parent_Name : string ;
    Status : integer ;
    Attr, Quota, Promsk, AcMode : int64 ;
    US : TUOS_String ;

begin
    // Get the values and validate...
    if( ParentName.Buffer = 0 ) then
    begin
        Result := SS_BADPARAM ;
        exit ;
    end ;
    US := Get_User_String( Kernel, PID, TableName, Status ) ;
    if( Status = UE_Error ) then
    begin
        Result := Status ;
        exit ;
    end ;
    Table_Name := trim( US.Contents ) ;
This new method starts by obtaining the parent table name and the new table name, exiting if there was an error.

    US := Get_User_String( Kernel, PID, ParentName, Status ) ;
    if( Status = UE_Error ) then
    begin
        Result := Status ;
        exit ;
    end ;
    Parent_Name := US.Contents ;

    Attr := Get_User_Integer( Kernel, PID, _Attr, Status ) ;
    if( Status = UE_Error ) then // Error reading user value
    begin
        Result := Status ;
        exit ;
    end ;
    Quota := Get_User_Integer( Kernel, PID, _Quota, Status ) ;
    if( Status = UE_Error ) then // Error reading user value
    begin
        Result := Status ;
        exit ;
    end ;
    Promsk := Get_User_Integer( Kernel, PID, _Promsk, Status ) ;
    if( Status = UE_Error ) then // Error reading user value
    begin
        Result := Status ;
        exit ;
    end ;
    if( _AcMode = 0 ) then
    begin
        _AcMode := USC.Access_Mode( PID ) ;
    end else
    begin
        AcMode := Get_User_Integer( Kernel, PID, _AcMode, Status ) ;
        if( Status = UE_Error ) then // Error reading user value
        begin
            Result := Status ;
            exit ;
        end ;
        if( ( AcMode > 3 ) or ( AcMode < 0 ) ) then
        begin
            Result := SS_BADPARAM ;
            exit ;
        end ;
        if( AcMode < USC.Access_Mode( PID ) ) then
        begin
            AcMode := USC.Access_Mode( PID ) ;
        end ;
    end ;
    if( ( PID <> 0 ) and ( AcMode < PSL_C_SUPER ) ) then
    begin
        if( ( USC.Get_Process_Info( PID, JPI_CURPRIV ) and SYSNAM ) = 0 ) then
        begin
            Result := SS_NOPRIV ; // User must have SYSNAM to use KERNEL or EXECUTIVE access mode
            exit ;
        end ;
    end ;
Next we get the rest of the parameters. If the access mode address is not provided (0), we default to the process' access mode. Otherwise we get the value from the user's memory, exiting with an error if that fails. If the accessmode is not in the range of 0 to 3, inclusive, we exit with an error. Finally, we maximize the current process' access mode with the requested access mode. That is, the outermost value of the two is what the access mode will be set to. However, if we are setting the access mode to Kernel or Executive, the process must have the SYSNAM privilege or we exit with an error.

    // Create the table...
    Result := Create_Symbol_Table( Kernel, PID, Table_Name, Parent_Name, Return_Name,
        Reslen, Attr, Quota, Promsk, AcMode ) ;

    if( ( Result <> 0 ) and ( Result <> SS_SUPERSEDE ) ) then
    begin
        Generate_Exception( Result ) ;
    end ;
end ; // TUOS_SSC.CRELNT
Finally we call the Create_Symbol_Table function to create the table, and if the result is an error, we generate an exception.

function TUOS_SSC.Create_Symbol_Table( Kernel : TUOS_Kernel ; PID : TPID ;
    var Table_Name, Parent_Name : string ;
    Return_Name : TSRB ; Reslen : int64 ;
    Attr, Quota, Promsk, AcMode : int64 ) : int64 ;

var I : integer ;
    Table : TUOS_Symbol_Table ;

begin
    // Setup...
    Result := 0 ;
    Table_Name := trim( lowercase( Table_Name ) ) ;
    Parent_Name := trim( lowercase( Parent_Name ) ) ;

    // Default the name...
    if( Table_Name = '' ) then
    begin
        Table_Name := 'lnm$' + lowercase( CVTB( 10, 16, inttostr( LIB_Get_Timestamp ) ) ) ;
    end ;
This method does the actual creation of the table. If the table name is null or not provided, we default to a name starting with "LNM$" followed by a unique hexadecimal value. To get a unique value, we use the current timestamp. This doesn't guarantee that there are no conflicts with existing table names, just that this routine will never generate two identical table names.

    // Create table...
    if( Parent_Name = 'lnm$process_directory' ) then
    begin
        if( PID = 0 ) then
        begin
            Result := SS_NOLOGTAB ;
            exit ;
        end ;
        Table := USC.Resolve_Table( PChar( Table_Name ), PID, nil, True, 3 ) ;
        if( Table <> nil ) then // Table exists
        begin
            if( ( Attr and LNM_M_CREATE_IF ) <> 0 ) then // Create if not exists
            begin
                Result := 0 ;
                exit ;
            end ;
            Table.Clear ;
            Result := SS_SUPERSEDE ;
        end else
        begin
            Table := USC.Create_Symbol_Table( Kernel, PID, PChar( Table_Name ),
                Attr, Quota, Promsk, AcMode ) ; // Create table
        end ;
    end else
If the name of the parent table is "lnm$process_directory", we first see if the table already exists. If so, we check for the presence of the LNM_M_CREATE_IF flag. If set, we exit with no error. If not set, we clear the existing table - essentially recreating it. If the table doesn't already exist, we pass the request on to the USC component.

    if( Parent_Name = 'lnm$system_directory' ) then
    begin
        if( ( PID <> 0 )
            and
            not USC.Validate_Protection( PID, FAB_V_UPD, 0, 
                PROTECTION_SYSTEM_WRITE or PROTECTION_SYSTEM_DELETE, 0 )
          ) then 
        begin
            Result := SS_NOPRIV ;
            exit ;
        end ;
        I := Symbols.IndexOf( Table_Name ) ;
        if( I <> -1 ) then // Table already exists
        begin
            if( ( Attr and LNM_M_CREATE_IF ) <> 0 ) then // Create if not exists
            begin
                Result := 0 ;
                exit ;
            end ;
            Table := TUOS_Symbol_Table( Symbols.Objects[ I ] ) ;
            Table.Clear ;
            Result := SS_SUPERSEDE ;
        end else
        begin
            Symbols.AddObject( Table_Name, TSymbol_Table.Create( Table_Name ) ) ;
        end ;
    end else
If the parent table is "lnm$system_directory", we check the protection. For the moment, we are hardcoding the protection to PROTECTION_SYSTEM_WRITE or PROTECTION_SYSTEM_DELETE. If the user doesn't have the necessary privileges, we exit with an error. Otherwise, we check the flags as above, again clearing the table or exiting. If the table doesn't exist, we construct a new TSymbol_Table instance and add it to lnm$system_directory.

    begin
        Result := SS_NOLOGTAB ;
        exit ;
    end ;

    // Return table name...
    if( Return_Name.Buffer <> 0 ) then
    begin
        Table_Name := Table.Name ;
        if( Return_Name.Length < length( Table_Name ) ) then
        begin
            setlength( Table_Name, Return_Name.Length ) ;
            Result := SS_RESULTOVF ;
        end ;
        Set_User_String( Kernel, PID, Return_Name, Table_Name ) ;
        if( ResLen <> 0 ) then
        begin
            Write_User_int64( Kernel, PID, ResLen, length( Table_Name ) ) ;
        end ;
    end ; // if( Return_Name.Buffer <> 0 )
end ; // TUOS_SSC.Create_Symbol_Table
If the parent table is not one we recognize, we exit with the SS_NOLOGTAB error. Otherwise, we fall through to the code to return the table name. Because the table name may have been created by this routine (if null was passed for the table name), the calling code will want to know the name we assigned to it. So, if a return name address was supplied, we first check to see if the size of the buffer is large enough to hold the name. If not, we set the return error code to SS_RESULTOVF and trim the length of the table name to write (the actual table name is not truncated - only the value we write back). If a result length is specified, we then write the value of the (truncated) string length to that address.

function TUSC.Create_Symbol_Table( Kernel : TUOS_Kernel ; PID : TPID ;
    Table_Name : PChar ; Attr, Quota, Promsk, AcMode : int64 ) : TUOS_Symbol_Table ;

var Process : TProcess ;
    T : string ;

begin
    Result := nil ;
    Process := Get_Process( PID ) ;
    if( Process = nil ) then // Process doesn't exist
    begin
        Generate_Exception( UOSErr_Nonexistent_Process ) ;
        exit ;
    end ;
    T := lowercase( trim( string( Table_Name ) ) ) ;
    if( T = '' ) then
    begin
        exit ;
    end ;
    if( Process.Symbol_Table_Directory.Indexof( T ) <> -1 ) then
    begin
        exit ;
    end ;
    Result := TSymbol_Table.Create( T ) ;
    Process.Symbol_Table_Directory.AddObject( T, Result ) ;
    if( ( Attr and LNM_M_MAKE_DEFAULT ) <> 0 ) then
    begin
        Process._Symbols := Result ;
    end ;
end ; // TUSC.Create_Symbol_Table
This new method of the USC first gets the specified process - exiting with an error if the process doesn't exist. We also exit if a null table name is passed or the parent table doesn't exist. The calling code checks for validity, so here we don't bother returning those error codes. Otherwise, we create a symbol table and tell the process to add the table list. If the LNM_M_MAKE_DEFAULT flag is set, we point the default table to the new table.

function TProcess.Create_Symbol_Table( Name : string ) : int64 ;

var ST : TSymbol_Table ;

begin
    Name := uppercase( Name ) ;
    if( Symbol_Table_Directory.Indexof( Name ) <> -1 ) then // Already exists
    begin
        exit ;
    end ;
    Result := 0 ;
    ST := TSymbol_Table.Create( Name ) ;
    Symbol_Table_Directory.AddObject( ST.Name, ST ) ;
end ;
This new TProcess method creates the table and adds it to the table directory list. However, if a table with the same name exists, we exit.

function TUSC.Resolve_Table( LNTable : PChar ; PID : TPID ; N : PChar ;
    CS : boolean ; Access : integer ) : TUOS_Symbol_Table ;

var I : integer ;
    Process : TProcess ;
    Name, Table : string ;

begin
    Result := nil ;
    Process := Get_Process( PID ) ;
    if( Process = nil ) then
    begin
        exit ;
    end ;
    Table := LNTable ;
    Name := N ;
This method is used to return a table instance given a table name. If no table name is specified, we return the table in which we find the specified symbol. In that case, we first check the current lnm$process table, and then the lnm$job table (which is the default table for the top-level job process). The first thing we do is get the specified process and exit if it doesn't exist.

    if( Table = 'lnm$process' ) then
    begin
        Result := Process._Symbols ;
        exit ;
    end ;
    if( Table = 'lnm$job' ) then
    begin
        Result := Process._Job_Symbols ;
        exit ;
    end ;

    if( Table = '' ) then
    begin
        Result := Process._Symbols ;
        if( Result.Exists( N, CS, Access ) ) then
        begin
            exit ;
        end ;
        Result := nil ;
        if( Process._Job_Symbols <> nil ) then
        begin
            Result := Process._Job_Symbols ;
            if( Result.Exists( N, CS, Access ) ) then
            begin
                exit ;
            end ;
            Result := nil ;
        end ;
    end ; // if( Table = '' )
In the case of lnm$process and lnm$job, we return the appropriate table. If no table is specified we first check for the passed symbol in the process default table. If not there, we likewise check the job table. If the symbol is found, we return the table instance that contains the symbol.

    for I := 0 to Process.Symbol_Table_Directory.Count - 1 do
    begin
        if( Table = Process.Symbol_Table_Directory[ I ] ) then
        begin
            Result := TUOS_Symbol_Table( Process.Symbol_Table_Directory.Objects[ I ] ) ;
            exit ;
        end ;
    end ;
end ; // TUSC.Resolve_Table
If we get here, either the symbol wasn't found in either the process or job table, or a table name other than lnm$job or lnm$process was passed. So we loop through the tables in the list. If the specified table is found, we return the instance. Otherwise the method ends with a result of nil.

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

 

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