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 UCL Command execution
62 UCL Command execution, part 2
63 UCL Command Abbreviation
64 ASTs
65 UCL Expressions, Part 1
66 UCL Expressions, Part 2: Support code
67 UCL Expressions, part 3: Parsing
68 SYS_GETJPIW and SYS_TRNLNM
69 UCL 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 UCL Lexical functions: F$GETDVI
98 Parse_GetDVI

Glossary/Index


Download sources
Download binaries

GETJPI and TRNLNM

In the previous article, we used two new system calls during the parsing of a UCL expression. These calls will be used for more than just this in the future. In this article we will examine the code for these calls.

SYS_GETJPIW

procedure SYS_GETJPIW( efn, pidadr : int64 ; prcnam : string ;
    itmlst, iosb, astadr, astprm : int64 ) ;

var Status : byte ;
    SysRequest : TItem_Request ;

begin
    fillchar( SysRequest, sizeof( SysRequest ), 0 ) ;
    SysRequest.Request.Subsystem :=  UOS_Subsystem_USC ;
    SysRequest.Request.Request := UOS_USC_GetJPIW ;
    SysRequest.Request.Length := sizeof( SysRequest ) - sizeof( Sysrequest.Request ) ;
    SysRequest.Request.Status := integer( @Status ) ;
    SysRequest.IRB.Item := pidadr ;
    SysRequest.IRB.Buffer := ItmLst ;
    SysRequest.SRB.Buffer := integer( PChar( prcnam ) ) ;
    SysRequest.SRB.Length := length( prcnam ) ;

    Call_To_Ring0( integer( @SysRequest ) ) ;
end ;
The function in the SYS unit follows the same pattern as previous system calls. The parameters are as follows:
ParameterDescription
efnEvent flags
pidadrProcess ID to return information on
prcnamProcess name of process to return information on
itmlstPointer to Item list
iosbPointer to IO Status Block
astadrPointer to AST address
astprmAST parameter
Note: If the Process ID is 0 and process name is null, the current process is assumed.

procedure TUSC.API( Request : int64 ; SReq : TSystem_Request ) ;

var Address : int64 ;
    Item_Request : PItem_Request ;
    IOSB : TIOSB ;
    PID : TPID ;
    S : TUOS_String ;
    St : string ;
    Status : integer ;
    UE : TUnified_Exception ;

begin
    fillchar( IOSB, sizeof( IOSB ), 0 ) ;
    PID := Kernel.PID ;
    case SReq.Request of
        UOS_USC_GetJPIW:
            begin
                UE := Enter_System_Call( Request, SReq, PID, MMC, sizeof( TItem_Request ) - sizeof( SReq ), Address ) ;
                if( UE <> nil ) then
                begin
                    Set_Last_Error( UE ) ;
                    exit ;
                end ;
                try
                    Item_Request := PItem_Request( Address ) ;

                    S := Get_User_String( Kernel, PID, Item_Request.SRB, IOSB.r_io_64.w_status ) ;
                    St := '' ;
                    if( S <> nil ) then
                    begin
                        St := S.Contents ;
                    end ;
                    if( IOSB.r_io_64.w_status = 0 ) then // No error getting process name string
                    begin
                        Get_Job_Process_Info( Item_Request.IRB.Item, St, Item_Request.IRB.Buffer, IOSB ) ;
                    end ;
                    Write_User( Kernel, PID, Item_Request.Integer1, sizeof( IOSB ), IOSB ) ;
                finally
                    Exit_System_Call( Request, PID, MMC, sizeof( TItem_Request ) - sizeof( SReq ) ) ;
                end ;
            end ;
    end ;
end ; // TUSC.API
The API handler for the User and Security component follows the design for the other components' API handler methods. Therefore, we won't delve into the details. So far, we are only responding to UOS_USC_GetJPIW calls.

procedure TUSC.Get_Job_Process_Info( pidadr : int64 ; const prcnam : string ;
    itmlst : int64 ; var IOSB : TIOSB ) ;

var Buff : PAnsiChar ;
    Descriptor : TSYS_Descriptor ;
    Len : int64 ;
    Offset : int64 ;
    PID : TPID ;
    Process : TProcess ;
    Res : int64 ;
    Target_PID : TPID ;

begin
    PID := Kernel.PID ;
    Target_PID := PID ;
    if( pidadr <> 0 ) then
    begin
        Target_PID := Get_User_Integer( Kernel, PID, pidadr, Status ) ;
        if( Status = UE_Error ) then
        begin
            IOSB.r_io_64.w_status := Status ;
            if( MMC.Last_Error = nil ) then
            begin
                Generate_Exception( UOSErr_Memory_Address_Error ) ;
            end ;
            exit ;
        end ;
    end else
    if( prcnam <> '' ) then
    begin
        ...
    end ;

    while( true ) do
    begin
        // Map item and process descriptor...
        Offset := MMC.Lock_Pages( PID, itmlst, sizeof( Descriptor ) ) ;
        try
            Buff := PAnsiChar( MMC.Map_Pages( PID, 0, itmlst, sizeof( Descriptor ), MAM_Read or MAM_Lock ) ) ;
            if( Buff = nil ) then
            begin
                if( MMC.Last_Error = nil ) then
                begin
                    Generate_Exception( UOSErr_Memory_Address_Error ) ;
                end ;
                exit ;
            end ;

            try
                move( Buff[ Offset ], Descriptor, sizeof( Descriptor ) ) ;
                if(
                    ( Descriptor.Buffer_Length = 0 )
                    or
                    ( Descriptor.Buffer_Address = 0 )
                    or
                    ( Descriptor.Return_Length_Address = 0 )
                  ) then // End of list
                begin
                    exit ;
                end ;
                Len := Descriptor.Buffer_Length ;
                if( Descriptor.Buffer_Address = 0 ) then
                begin
                    Generate_Exception( UOSErr_Memory_Address_Error ) ;
                    exit ;
                end ;
                case Descriptor.Item_Code of
                    JPI_OWNER : // Get owner (parent) of process
                        begin
                            if( Len > sizeof( Len ) ) then
                            begin
                                Len := sizeof( Len ) ;
                            end ;
                            Process := Get_Process( Target_PID ) ;
                            Res := Process._Parent ;
                            Write_User( Kernel, PID, Descriptor.Buffer_Address, Len, Res ) ;
                            Write_User( Kernel, PID, Descriptor.Return_Length_Address, sizeof( Len ), Len ) ;
                        end ;
                    JPI_PID : // Get PID
                        begin
                            if( Len > sizeof( Len ) ) then
                            begin
                                Len := sizeof( Len ) ;
                            end ;
                            Write_User( Kernel, PID, Descriptor.Buffer_Address, Len, Target_PID ) ;
                            Write_User( Kernel, PID, Descriptor.Return_Length_Address, sizeof( Len ), Len ) ;
                        end ;
                end ;
            finally
                MMC.UnMap_Pages( 0, itmlst, sizeof( Descriptor ) ) ;
            end ;
        finally
            MMC.Unlock_Pages( PID, itmlst, sizeof( Descriptor ) ) ;
        end ;
        itmlst := itmlst + sizeof( Descriptor ) ;
    end ; // while( true )
end ; // TUSC.Get_Job_Process_Info
This function handles the details of getting job/process information based on an item list passed from an application. The mapping/unmapping and locking/unlocking are the same as for other system calls we've talked about before. In fact, this function is very similar to the Get_Device_Info method of the File Processor. In terms of the the data passed to the system call, there is one notable difference: Get_Device_Info is passed a length parameter which limits the size of the passed array of descriptors, whereas Get_Job_Process_Info does not. This is in accordance with the VMS system calls. I have no idea why VMS is inconsistent in this area and I was tempted to alter this call to also have a max length parameter as well. In the end, I just went with the way VMS does it.

In terms of what is unique to this system call, we default to the current PID, unless a specific PID is given. The only two descriptors we currently support are JPI_OWNER which returns the parent PID of the target PID, and JPI_PID which returns the current process ID.

Here is the newly added utility function we used above:

function Get_User_Integer( Kernel : TUOS_Kernel ; PID : TPID ; Address : int64 ;
    var Status : longint ) : int64 ;

var Buffer : PAnsiChar ;
    Len : int64 ;
    Offset : int64 ;
    MMC : TUOS_Memory_Manager ;

begin
    MMC := Kernel.MMC ;
    Status := UE_Success ;
    Result := 0 ;
    if( Address = 0 ) then
    begin
        Status := UE_Error ;
        exit ;
    end ;
    Offset := MMC.Lock_Pages( PID, Address, sizeof( int64 ) ) ;
    try
        Buffer := PAnsiChar( MMC.Map_Pages( PID, 0, Address, sizeof( int64 ), MAM_Read or MAM_Lock ) ) ;
        if( Buffer = nil ) then
        begin
            Status := UE_Error ;
            exit ;
        end ;
        move( Buffer[ Offset ], Result, sizeof( int64 ) ) ;
        MMC.UnMap_Pages( 0, Address, sizeof( int64 ) ) ;
    finally
        MMC.Unlock_Pages( PID, Address, sizeof( int64 ) ) ;
    end ;
end ;
This is a conceptually equivalent to Get_User_String, but it obtains and returns a 64-bit integer value from the user address space.

SYS_TRNLNM
As we mentioned previously, VMS has logical name tables and DCL has its own symbol tables. UOS combines these two concepts into symbol tables that are available to all shells for use as symbol tables, but are also used for logical device names. TRNLNM (Translate Logical Name) is used on VMS to access logical name tables. On UOS, it is a means of accessing UOS symbol tables.

You may ask why we need TRNLNM since we already have LIB_Get_Symbol. The problem is that the latter is a kind of short-hand. It will return a null if the specified symbol is empty or if it is not defined. The former allows us to see if the symbol exists.

Here is the code for accessing the system call.

procedure SYS_TRNLNM( attr : int64 ; tabnam, lognam : PAnsiChar ;
    acmode, itmlst : int64 ) ;

var Status : byte ;
    SysRequest : TS2I3_Request ;

begin
    fillchar( SysRequest, sizeof( SysRequest ), 0 ) ;
    SysRequest.Request.Subsystem :=  UOS_Subsystem_SSC ;
    SysRequest.Request.Request := UOS_SSC_TRNLNM ;
    SysRequest.Request.Length := sizeof( SysRequest ) - sizeof( TSystem_Request ) ;
    SysRequest.Request.Status := integer( @Status ) ;
    SysRequest.SRB1.Buffer := integer( PChar( Tabnam ) ) ;
    SysRequest.SRB1.Length := length( Tabnam ) ;
    SysRequest.SRB2.Buffer := integer( PChar( Lognam ) ) ;
    SysRequest.SRB2.Length := length( Lognam ) ;
    SysRequest.Integer1 := Attr ;
    SysRequest.Integer2 := AcMode ;
    SysRequest.Integer3 := ItmLst ;

    Call_To_Ring0( integer( @SysRequest ) ) ;
end ;
The SYS_TRNLNM library call is the wrapper for the system call. The only new thing of note here is the introduction of the TS2I3_Request structure, which passes two strings and three integers to the executive.

And here is the definition of TS2I3_Request and PS2I3_Request.

type TS2I3_Request = packed record
                         Request : TSystem_Request ;
                         SRB1 : TSRB ;
                         SRB2 : TSRB ;
                         Integer1 : int64 ;
                         Integer2 : int64 ;
                         Integer3 : int64 ;
                     end ;
     PS2I3_Request = ^TS2I3_Request ;

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

                try
                    PS2I3 := PS2I3_Request( Base ) ;
                    S := Get_User_String( Kernel, PID, PS2I3.SRB1, Status ) ; // Get table name
                    S1 := Get_User_String( Kernel, PID, PS2I3.SRB2, Status ) ; // Get symbol name
                    PS2I3.Request.Status := TRNLNM( S, S1, PS2I3.Integer1, PS2I3.Integer2, PS2I3.Integer3 ) ;
                finally
                    Exit_System_Call( Base, PID, MMC, sizeof( TS2I3_Request ) ) ;
                end ;
            end ; // UOS_SSC_TRNLNM
This code is added to the API method of the TUOS_SSC class to handle the TRNLNM call. We simply obtain the two passed strings, and call the TRNLNM method. The rest of the code is just like the other system call handlers.

function TUOS_SSC.TRNLNM( Tabname, SymName : TUOS_string ;
    Attr, AcMode, ItemLst : int64 ) : int64 ;

var Buff : PAnsiChar ;
    Descriptor : TSYS_Descriptor ;
    L : longint ;
    Flags, Index, Len : int64 ;
    Offset : int64 ;
    P : PChar ;
    PID : TPID ;
    Res : int64 ;
    Status : longint ;
    S, SName : string ;
    Table : TUOS_Symbol_Table ;
    Tname : string ;

begin
    PID := Kernel.PID ;
    if( ( SymName = nil ) or ( SymName.Contents = '' ) ) then
    begin
        Generate_Exception( UOSErr_Missing_Name ) ;
        exit ;
    end ;
    SName := SymName.Contents ;
    if( ( TabName = nil ) or ( TabName.Contents = '' ) ) then
    begin
        Table := Resolve_Table( LNM_PROCESS, PID, SName ) ;
    end else
    begin
        Table := Get_Symbol_Table( TabName.Contents ) ;
    end ;
    if( Table = nil ) then
    begin
        Generate_Exception( UOSErr_Resource_Not_Found ) ;
        exit ;
    end ;
The TRNLNM method starts out by verifying that an actual symbol name was passed. If not, we exit with an error. Then we determine which table to use for looking up the symbol. If a name was passed, we use that to get the table, otherwise we use the process' standard symbol table. If the requested table doesn't exist, we exit with an error.

Before we continue, we should address an expansion to the concept of UOS symbol tables. As we've discussed before, there are a series of default symbol tables. Each process has its own symbol table. There are also system-wide and cluster-wide symbol tables. But it is possible to create additional symbol tables. These are distinguished by some unique name. The Resolve_Table method locates and returns the proper table, if it exists, whereas Get_Symbol_Table returns the table with a specific name. This is for more than the ability to create additional tables - it also allows one process (with sufficient privileges) to look at the symbol table of another process. We will examine those methods later in the article.

It should be noted that if the standard tables are used, the symbol will be resolved by looking first at the process table, then the system table, and then the clusterside table. But if a specific named table is used, only that table is examined.

    Flags := 0 ;
    S := '' ;
    if( Table.Exists( PAnsiChar( SName ) ) ) then
    begin
        Flags := LNM_M_EXISTS ;
        P := Table.Value( PAnsiChar( SName ), L ) ;
        if( Index >= 0 ) then
        begin
            setlength( S, L ) ;
            move( P[ 0 ], PChar( S )[ 0 ], L ) ;
        end ;
    end ; // if( Table.Exists( SName ) )
The Flags variable is used to hold attributes about the symbol name, if that is asked for. We initialize it to zero. Then we check to see if the symbol name exists in the specified table. If so, we set the LNM_M_EXISTS flag. Then we get the symbol value.

    while( true ) do
    begin
        // Map item and process descriptor...
        Offset := MMC.Lock_Pages( PID, Itemlst, sizeof( Descriptor ) ) ;
        try
            Buff := PAnsiChar( MMC.Map_Pages( PID, 0, itemlst, sizeof( Descriptor ), MAM_Read or MAM_Lock ) ) ;
            if( Buff = nil ) then
            begin
                if( MMC.Last_Error = nil ) then
                begin
                    Generate_Exception( UOSErr_Memory_Address_Error ) ;
                end ;
                exit ;
            end ;
Since we are processing a descriptor list, we loop until we hit a terminating descriptor. For each descriptor, we lock and map the memory for it. If that fails, we exit with an error.

            try
                move( Buff[ Offset ], Descriptor, sizeof( Descriptor ) ) ;
                if(
                    ( Descriptor.Buffer_Length = 0 )
                    or
                    ( Descriptor.Buffer_Address = 0 )
                    or
                    ( Descriptor.Return_Length_Address = 0 )
                  ) then // End of list
                begin
                    exit ;
                end ;
                Len := Descriptor.Buffer_Length ;
Next we copy the descriptor from the user memory to the local variable. If any of the addresses or the buffer lengths are zero, we consider this a terminating descriptor and exit. Otherwise, we get the length of the buffer.

                case Descriptor.Item_Code of
                    LNM_ATTRIBUTES :
                        begin
                             if( Len > sizeof( int64 ) ) then
                             begin
                                 Len := sizeof( int64 ) ;
                             end ;
                             Write_User( Kernel, PID, Descriptor.Buffer_Address, Len, Flags ) ;
                             Write_User( Kernel, PID, Descriptor.Return_Length_Address, sizeof( int64 ), Len ) ;
                        end ;
                    LNM_STRING :
                        begin
                            if( Len > length( S ) ) then
                            begin
                                Len := length( S ) ;
                            end ;
                            Write_User( Kernel, PID, Descriptor.Buffer_Address, Len, PChar( S )[ 0 ] ) ;
                            Write_User( Kernel, PID, Descriptor.Return_Length_Address, sizeof( int64 ), Len ) ;
                        end ;
                end ;
Depending on the descriptor's item code, we return either the attributes (via Flags) or the symbol's value. Note that we minimize the actual length of the data to the return buffer length so we don't overwrite the user memory. There are other item codes for TRNLNM, which we will examine in a future article.

            finally
                MMC.UnMap_Pages( 0, itemlst, sizeof( Descriptor ) ) ;
            end ;
        finally
            MMC.Unlock_Pages( PID, itemlst, sizeof( Descriptor ) ) ;
        end ;
        itemlst := itemlst + sizeof( Descriptor ) ;
    end ; // while( true )
end ; // TUOS_SSC.TRNLNM
After we're done processing the descriptor, we unmap and unlock the user's memory and then increment the item list address to the next descriptor. Then we loop back for the next descriptor.

We covered Resolve_Table back in article 35, but we've modified the code a bit to handle the additional named symbol tables capability.

function TUOS_SSC.Resolve_Table( LNTable : cardinal ; PID : TPID ;
    var N : string ) : TUOS_Symbol_Table ;

begin
    Result := nil ; // Assume failure...
    if( length( N ) = 0 ) then
    begin
        Set_Error( UOSErr_Missing_Name ) ;
        exit ;
    end ;

    if( LNTable = LNM_SYSTEM ) then // System-level
    begin
        if( N[ 1 ] = '_' ) then
        begin
            N := copy( N, 2, length( N ) ) ;
        end ;
        if( length( N ) = 0 ) then
        begin
            Set_Error( UOSErr_Missing_Name ) ;
            exit ;
        end ;
    end ;
    if( N[ 1 ] = '_' ) then
    begin
        Set_Error( UOSErr_Invalid_Symbol_Name ) ;
        exit ;
    end ;

    // Determine which table...
    case LNTable of
        LNM_GROUP:
            Result := Get_Symbol_Table( 'group$' + inttostr( PID ) ) ;
        LNM_JOB: // Group- and Job-specific (all processes in tree)
            Result := USC.Symbols( LNTable, PID, 0 ) ;
        LNM_SYSTEM: // System-specific
            Result := Get_Symbol_Table( '$system' ) ;
        LNM_CLUSTER: // Cluster-specific
            Result := Get_Symbol_Table( '$cluster' ) ;
        else // Process-specific
            Result := Get_Symbol_Table( '$' + inttostr( USC.Get_Job( PID ) ) ) ;
    end ; // case Table
end ; // TUOS_SSC.Resolve_Table
Essentially, we've moved the code for actually getting a table to the Get_Symbol_Table method and here we merely wrap that call. This means we don't have to change any other code we've written. Resolve_Table still operates the same - it just accomplishes the task through the other method. Because all symbol tables have names now, the standard table constants map to the following names.
ConstantTable names
LNM_JOBSame as LNM_PROCESS, but for the top-most parent process
LNM_CLUSTER$cluster
LNM_PROCESS$pid where "pid" is the process ID. Thus, for process ID 7439, the table name would be "$7439"
The rest of the code is the same as before.

function TUOS_SSC.Get_Symbol_Table( T : string ) : TUOS_Symbol_Table ;

begin
    Result := nil ; // Assume failure...
    T := trim( lowercase( T ) ) ;
    if( length( T ) = 0 ) then
    begin
        Set_Error( UOSErr_Missing_Name ) ;
        exit ;
    end ;

    if( T = '$system' ) then
    begin
        Result := System_Symbols ;
        exit ;
    end ;
    if( T = '$cluster' ) then
    begin
        Result := Cluster_Symbols ;
        exit ;
    end ;
    if( copy( T, 1, 1 ) = '$' ) then
    begin
        T := copy( T, 2, length( T ) ) ;
        if( Is_Integer( T ) ) then
        begin
            Result := USC.Symbols( LNM_PROCESS, strtoint( T ), 0 ) ;
            exit ;
        end ;
    end ;
end ; // TUOS_SSC.Get_Symbol_Table
This method simply translates from a given name to a specific table. Note that we don't have the code for other custom tables here yet - we will address that in a future article. This code simply generalizes what we did in the past before we had named symbol tables.

In the next article, we will look at the code to evaluate UCL expression trees.

 

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