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

PROCESS_SCAN

Before we look at the first of the lexical functions, we will look at a system call that we will be making use of. Normally, we work from the top down - from an application down to the executive API code. But, for reasons that will become clear later on, we'll start with the executive and then look at the application (UCL) code that uses the system call.

Process_Scan
Process_Scan is a system call that works with the GetJPI call. It is used to iterate through processes. Process_Scan creates a context that GETJPI uses - each call to GETJPI using that context will get the next matching process ID. Filters can be added to the context when it is created. Each filter is used to filter out undesired processes. When Process_Scan is called, it is passed an item list that indicates one or more filters to use (or no filters, if all process IDs are to be iterated over). Each filter consists of an item to compare, the value to compare against, and the type of comparison to do. The following items can be used for comparisons:
ItemMeaning
PSCAN_ACCOUNTAccount name
PSCAN_AUTHPRIAuthorized base priority
PSCAN_CURPRIVPrivilege name keyword
PSCAN_HW_MODELHardware model number
PSCAN_HW_NAMEHardware name
PSCAN_JOBPRCCNTSubprocess count for entire job
PSCAN_JOBTYPEJob-type keyword
PSCAN_KT_COUNTKernel thread count
PSCAN_MASTER_PIDPID of master process
PSCAN_MODEProcess mode keyword
PSCAN_MULTITHREADMaximum thread count
PSCAN_NODE_CSIDNode's cluster ID number
PSCAN_NODENAMENode name
PSCAN_OWNERPID of immediate parent process
PSCAN_PRCCNTSubprocess count of process
PSCAN_PRCNAMProcess name
PSCAN_PRIProcess priority level number
PSCAN_PRIBBase process priority level number
PSCAN_STATEProcess state keyword
PSCAN_STSProcess status keyword
PSCAN_TERMINALTerminal name
PSCAN_USERNAMEUser name
For instance, the PSCAN_ACCOUNT item can be used to compare the account name for the user associated with a process.

The types of comparisons include the following:
ComparisonMeaning
PSCAN_M_EQLEqual to the value
PSCAN_M_LSSLess than the value
PSCAN_M_LEQLess than or equal to the value
PSCAN_M_GTRGreater than the value
PSCAN_M_GEQGreater than or equal to the value
PSCAN_M_NEQNot equal to the value

Thus, we could have a filter that would compare the account name of a process' user with "SYSTEM" and a comparison of PSCAN_M_NEQ to only iterate over processes whose account name is not "SYSTEM".

The comparisons are combined with optional flags. But trying to combine different comparisons in the same filter will result in something unexpected - or even an error. For instance, PSCAN_M_NEQ or PSCAN_M_GEQ not only makes no sense, but would produce unexpected results or an exception. The following flags can be combined, although some combinations may not make much sense. VMS reports errors if certain flags are used with certain comparison items. UOS will allow any of the flags on any value, although certain combinations are probably not useful.
FlagMeaning
PSCAN_M_BIT_ALLAll bits in the comparison value must be set in the item
PSCAN_M_BIT_ANYAny bit in the comparison value that is set in the item means a match
PSCAN_M_CASE_BLINDPerform string comparisons case-insensitive
PSCAN_M_ORLogically or this filter with the next one
PSCAN_M_PREFIX_MATCHMatch against the portion of the item to the length of the specified comparison value
PSCAN_M_WILDCARDTreat wildcard characters as wildcards for matching
Thus, we could use PSCAN_M_EQL or PSCAN_M_WILDCARD with the PSCAN_USERNAME item and a comparison value of "A*" to filter out all processes whose username doesn't start with "A". This particular filter could also be accomplished with a prefix match, by the way. Note that if we compared with "A*" but didn't include the wildcard flag, no processes would match, because it would look for a user name that is literally "A*", but user names cannot contain asterisks.

When a context contains multiple filters, a process must meet every requirement. However, if the PSCAN_M_OR flag is used, then the filter comparison is logically ored with the next filter. In fact, any number of filters can be ored together this way.

Here are the definitions for the above constants:

// Process Scan Filter criteria...
const PSCAN_Terminator = 0 ; // Account name - by reference
const PSCAN_ACCOUNT = 1 ; // Account name - by reference
const PSCAN_AUTHPRI = 2 ; // Authorized base priority
const PSCAN_CURPRIV = 3 ; // Current privileges - by reference
const PSCAN_HW_MODEL = 4 ; // Hardware model number
const PSCAN_HW_NAME = 5 ; // Hardware name - by reference
const PSCAN_JOBPRCCNT = 6 ; // Subprocess count for entire job.
const PSCAN_JOBTYPE = 7 ; // Job-type keyword
const PSCAN_MASTER_PID = 8 ; // PID of master process
const PSCAN_MODE = 9 ; // Process mode keyword
const PSCAN_NODE_CSID = 10 ; // Node's cluster ID number
const PSCAN_NODENAME = 11 ; // Node name - by reference
const PSCAN_OWNER = 12 ; // PID of immediate parent process
const PSCAN_PRCCNT = 13 ; // Subprocess count of process
const PSCAN_PRCNAM = 14 ; // Process name - by reference
const PSCAN_PRI = 15 ; // Process priority level number
const PSCAN_PRIB = 16 ; // Base process priority level number
const PSCAN_STATE = 17 ; // Process state keyword
const PSCAN_STS = 18 ; // Process status keyword
const PSCAN_TERMINAL = 19 ; // Terminal name - by reference
const PSCAN_USERNAME = 20 ; // User name - by reference
const PSCAN_KT_COUNT = 21 ; // Kernel threads
const PSCAN_MULTITHREAD = 22 ; // Maximum Kernel threads

// Process Scan Filter qualifiers...
const PSCAN_M_COMPARISON_MASK = 7 ;
const PSCAN_M_EQL = 0 ; // Equal to the value
const PSCAN_M_LSS = 1 ; // Less than the value
const PSCAN_M_LEQ = 2 ; // Less than or equal to the value
const PSCAN_M_GTR = 3 ; // Greater than the value
const PSCAN_M_GEQ = 4 ; // Greater than or equal to the value
const PSCAN_M_NEQ = 5 ; // Not equal to the value

const PSCAN_M_BIT_ALL = 8 ; // Requires that all bits be set
const PSCAN_M_BIT_ANY = 16 ; // Requests that any bit be set
const PSCAN_M_CASE_BLIND = 32 ; // Match without case sensitivity
const PSCAN_M_WILDCARD = 64 ; // Match a wildcard pattern
const PSCAN_M_OR = 128 ; // Or this filter item with the next one
const PSCAN_M_PREFIX_MATCH = 256 ; // Match prefix

We've encapsulated filters in the TFilter class:

TFilter = class
              public // API...
                  Selection_Criteria : longint ; // See PSCAN_*
                  Value : string ;
                  ValueI : int64 ; // Non-string value
                  Qualifier : longint ; // See PSCAN_M_*

                  function Get_Filter_Value( PID : TPID ;
                      Criteria : int64 ) : string ;
          end ;
The TFilter class holds the information for a single comparison.

function TFilter.Get_Filter_Value( PID : TPID ; Criteria : int64 ) : string ;

var I, L : integer ;
    Process, _Process : TProcess ;

begin
    // Setup...
    Result := '' ;
    Process := Get_Process( PID ) ;
    if( Process = nil ) then
    begin
        exit ;
    end ;
This method is used to get the value associated with the criteria and the given process. The result is a string, but the string may contain an integer value (in binary form). The first thing we do is get the process object instance from the passed ID and set the result to a null string. We'll look at Get_Process later in the article.

    // Handle each criteria...
    case Criteria of
        PSCAN_ACCOUNT: // Account name
            begin
                Result := Process.User.Owner_Name ;
            end ;
        PSCAN_AUTHPRI: // Authorized base priority
            begin
                Set_Integer_Result( Process.User.Get_Auth_Privileges ) ;
            end ;
        PSCAN_CURPRIV: // Current Privileges
            begin
                Set_Integer_Result( Process.Current_Privileges ) ;
            end ;
        PSCAN_HW_MODEL: // Hardware model number
            begin
                Set_Integer_Result( Process.Hardware_Code ) ;
            end ;
        PSCAN_HW_NAME: // Hardware name
            begin
                Result := Process.Hardware_Description ;
            end ;
        PSCAN_JOBPRCCNT: // Subprocess count for entire job.
            begin
                _Process := Process.Job ;
                Set_Integer_Result( _Process.Child_Count ) ;
                _Process.Free_Remote ;
            end ;
        PSCAN_JOBTYPE: // Job-type
            begin
                _Process := Process.Job ;
                Set_Integer_Result( _Process.Job_Type ) ;
                _Process.Free_Remote ;
            end ;
        PSCAN_KT_COUNT: // Kernel thread count
            begin
                Set_Integer_Result( Process.KThread_Count ) ;
            end ;
        PSCAN_MASTER_PID: // PID of master process
            begin
                _Process := Process.Job ;
                Set_Integer_Result( _Process._PID ) ;
                _Process.Free_Remote ;
            end ;
        PSCAN_MODE: // Process mode
            begin
                _Process := Process.Job ;
                I := _Process.Job_Type ;
                if( I < JPI_K_BATCH ) then
                begin
                    I := JPI_K_INTERACTIVE ;
                end ;
                Set_Integer_Result( I ) ;
                _Process.Free_Remote ;
            end ;
        PSCAN_MULTITHREAD:
            begin
                Set_Integer_Result( Process.MaxK_Threads ) ;
            end ;
        PSCAN_NODE_CSID: // Node's cluster ID number
            begin
                Set_Integer_Result( Process.CSID ) ;
            end ;
        PSCAN_NODENAME: // Node name
            begin
                Result := Process.NodeName ;
            end ;
        PSCAN_OWNER: // PID of immediate parent process
            begin
                Set_Integer_Result( Process._Parent ) ;
            end ;
        PSCAN_PRCCNT: // Subprocess count of process
            begin
                Set_Integer_Result( Process.Child_Count ) ;
            end ;
        PSCAN_PRCNAM: // Process name
            begin
                Result := Process.Name ;
            end ;
        PSCAN_PRI: // Process priority level number
            begin
                Set_Integer_Result( Process.Priority ) ;
            end ;
        PSCAN_PRIB: // Base process priority level number
            begin
                Set_Integer_Result( Process.Base_Priority ) ;
            end ;
        PSCAN_STATE: // Process state
            begin
                Set_Integer_Result( Process.State ) ;
            end ;
        PSCAN_STS: // Process status
            begin
                Set_Integer_Result( Process.Status ) ;
            end ;
        PSCAN_TERMINAL: // Terminal name
            begin
                Result := Process.Terminal_Name ;
            end ;
        PSCAN_USERNAME: // User name
            begin
                Result := Process.User.Name ;
            end ;
    end ; // case Criteria
    Process.Free_Remote ;
end ; // TFilter.Get_Filter_Value
We switch on the criteria and return the appropriate value.

PSCAN_MODE is a special case in that the JPI_K_INTERACTIVE constant is not a value that the Process.Job_Type method returns, but the PSCAN_MODE criteria combines various process modes into that single constant, so we can't simply take the process mode value and use it as it. It's just one of those odd VMS things that we go with.
You might notice the calls to Process.Free_Remote. For now, this does nothing, but we will talk about it in the future. Essentially it has to do with accessing processes on other computers that are part of a cluster.
Finally, some of the above TProcess methods are new and we'll look at those in the next article.

Now let's look at the local helper function used by this method:

    procedure Set_Integer_Result( Int : int64 ) ;

    begin
        setlength( Result, sizeof( Int ) ) ;
        move( Int, PChar( Result )[ 0 ], length( Result ) ) ;
    end ;
Set_Integer_Result is used to pack a 64-bit integer value into an 8-byte string.

Now let's look at the TContext class:

type TContext = class
                    public // Constructors and destructors...
                        constructor Create ;
                        destructor Destroy ; override ;

                    public // API...
                        Filters : TList ; // List of filters
                        Last_PID : TPID ; // Context

                        function Next_PID : TPID ;
                end ; // TContext
The TContext class is used to maintain a context as a program iterates through the processes on the system. It contains a list of TFilter objects, and the last process ID returned by the context.

// API...

constructor TContext.Create ;

begin
    inherited Create ;

    Filters := TList.Create ;
end ;


destructor TContext.Destroy ;

var I : integer ;

begin
    for I := 0 to Filters.Count - 1 do
    begin
        TFilter( Filters[ I ] ).Free ;
        Filters[ I ] := nil ;
    end ;
    Filters.Free ;

    inherited Destroy ;
end ;
The constructor creates the filter list. The destructor frees each filter and then frees the list.

function TContext.Next_PID : TPID ;

var C : boolean ;
    F : integer ;
    Filter : TFilter ;
    I : integer ;
    Match : boolean ;
    Process : TProcess ;
    L, R : string ;

begin
    if( Last_PID = 0 - 1 ) then // Already processed all processes
    begin
        Result := 0 ;
        exit ;
    end ;
The Next_PID method returns the next process ID matching the filters. If the last ID returned is the maximum value (all bits set), the iteration is complete and we exit with the result of 0. Using the expression "0 - 1" gives us that maimum value - regardless of whether it is a signed or unsigned integer.

    // Find next matching process...
    while( True ) do
    begin
        Result := Last_PID ;
        if( Result = 0 ) then // First time
        begin
            // Find first PID...
            for I := 0 to Processes.Count - 1 do
            begin
                Process := TProcess( Processes[ I ] ) ;
                if( Process <> nil ) then
                begin
                    Result := Process._PID ;
                    break ;
                end ;
            end ;
        end else
        begin
            // Find next PID
            Result := Find_Next_PID( Last_PID ) ;
            if( Result = 0 ) then
            begin
                Last_PID := 0 ; // Done
                dec( Last_PID ) ;
                exit ;
            end ;
        end ; // if( Result = 0 )
        Last_PID := Result ;
We loop until we've found a matching process, or run out of them. First, we start with the last returned ID. If this is 0, then this is our first iteration. We iterate through the Processes list until we find one that isn't nil.
On the other hand, if we're not on the first iteration, we find the next process ID.

        // Now that we have the next potential process, check against the filters...
        Match := False ;
        F := -1 ;
        while( F < Filters.Count - 1 ) do
        begin
            inc( F ) ;
            Filter := TFilter( Filters[ F ] ) ; // Get next filter

            // Get values to compare...
            L := Filter.Get_Filter_Value( Result, Filter.Selection_Criteria ) ;
            if( Is_By_Reference( Filter.Selection_Criteria ) ) then
            begin
                R := Filter.Value ;
            end else
            begin
                setlength( R, sizeof( Filter.ValueI ) ) ;
                move( Filter.ValueI, PChar( R )[ 0 ], sizeof( Filter.ValueI ) ) ;
            end ;
At this point, we have the next potential ID. So we need to apply our filters to see if we should return this ID or move on to the next one. We set the Match flag to false and then loop through the filters.

For each time through the loop, we get the filter instance, then get the filter value. This is the process-specific value to which we compare the comparison value. We assign this to L because this is the left side of the comparison. Then we get the comparison value from the filter and assign it to R (right side value). Because the comparison value could be a string or an integer, we handle this slightly differently. For strings, we simple get Filter.Value. For integers, we set the length of R to 8 and copy the 64-bit integer value into it. Either way, we now have the comparison value in R. (Is_By_Reference is described later in the article.)

        // Compare the values and filter based on comparison mask...
            C := Compare_Values( L, R, Filter.Qualifier, Numeric_Criteria( Filter.Selection_Criteria ) ) ;
            if( not C ) then // Not a match
            begin
                if( ( Filter.Qualifier and PSCAN_M_OR ) = 0 ) then // Not oring with next filter
                begin
                    Match := False ;
                    break ;
                end ;
            end else
            begin
                Match := True ;
                while( ( Filter.Qualifier and PSCAN_M_OR ) <> 0 ) do // Skip further oring filters
                begin
                    if( F >= Filters.Count - 1 ) then
                    begin
                        break ; // Ran out of filters
                    end ;
                    inc( F ) ;
                    Filter := TFilter( Filters[ F ] ) ; // Get next filter
                end ;
            end ; // if( not C )
        end ; // while( F < Filters.Count - 1 )
        if( Match ) then
        begin
            exit ;
        end ;
    end ; // while( True )
end ; // TContext.Next_PID
Next we compare the values using the filter's qualifier. If there isn't a match, we check to see if the filter has the OR flag. If not, the match fails so we set Match to false and exist the inner loop. If there is an OR, we continue on to the next filter. However, if the compared values do match, we set Match to true and then check to see if the filter has the OR flag. If so, we skip through the rest of the filters until a non-OR filter is found (since we found a match, the rest of the ORed filter checks are unnecessary). The functions, Compare_Values and Numeric_Criteria are described later in the article.

If we have a match when we're done with the filters, we return the process ID. If not, we loop back and move to the next process and then repeat the inner loop to apply the filters.

function Is_By_Reference( Item : integer ) : boolean ;

begin
    Result := (
                ( Item = PSCAN_ACCOUNT )
                or
                ( Item = PSCAN_CURPRIV )
                or
                ( Item = PSCAN_HW_NAME )
                or
                ( Item = PSCAN_NODENAME )
                or
                ( Item = PSCAN_PRCNAM )
                or
                ( Item = PSCAN_TERMINAL )
                or
                ( Item = PSCAN_USERNAME )
              ) ;

end ; // Is_By_Reference
This function simply returns true if the filter item is passed by reference (as a string). Note that the privileges are passed by reference even though they would fit in an 8-byte integer. This is because on 32-bit systems, VMS passed privileges via a 32-bit integer value, which wasn't enough to pass the entire privilege mask. UOS follows the VMS approach.

function Numeric_Criteria( Item : integer ) : boolean ;

begin
    Result := (
                ( Item = PSCAN_AUTHPRI )
                or
                ( Item = PSCAN_CURPRIV )
                or
                ( Item = PSCAN_HW_MODEL )
                or
                ( Item = PSCAN_JOBPRCCNT )
                or
                ( Item = PSCAN_JOBTYPE )
                or
                ( Item = PSCAN_MASTER_PID )
                or
                ( Item = PSCAN_MODE )
                or
                ( Item = PSCAN_NODE_CSID )
                or
                ( Item = PSCAN_OWNER )
                or
                ( Item = PSCAN_PRCCNT )
                or
                ( Item = PSCAN_PRI )
                or
                ( Item = PSCAN_PRIB )
                or
                ( Item = PSCAN_STATE )
                or
                ( Item = PSCAN_STS )
                or
                ( Item = PSCAN_KT_COUNT )
                or
                ( Item = PSCAN_MULTITHREAD )
              ) ;
end ; // Numeric_Criteria
This function returns true if the comparison value is numeric.

function Compare_Values( const L, R : string ; Flags : integer ; Binary : boolean ) : boolean ;

var I : integer ;
    LC, RC : cardinal ;
    LU, RU : TUnicode_String ;

begin
    LU := TUnicode_String.Create ;
    RU := TUnicode_String.Create ;
    try
        if( Binary ) then
        begin
            LU.Assign_From_String( L, ST_ASCII ) ;
            RU.Assign_From_String( R, ST_ASCII ) ;
        end else
        begin
            LU.Assign_From_String( L, ST_YTF8 ) ;
            RU.Assign_From_String( R, ST_UTF8 ) ;
            if( ( Flags and PSCAN_M_CASE_BLIND ) <> 0 ) then
            begin
                LU.Lowercase ;
                RU.Lowercase ;
            end ;
            if( ( Flags and PSCAN_M_PREFIX_MATCH ) <> 0 ) then
            begin
                if( RU.Length > LU.Length ) then
                begin
                    RU.Length := LU.Length ;
                end ;
            end ;
        end ;
Compare_Values is used to compare the left and right values for a filter. First we create unicode versions of both strings, because all string comparisons assume UTF8. If the values being compared are binary, we load the unicode strings as ASCII strings instead of UTF8. In the case of non-binary values, if the PSCAB_M_CASE_BLIND flag is set, we convert the values to lowercase so that the case is made irrelevant. Also, for non-binary values, if the PSCAN_M_PREFIX_MATCH flag is set we minimize the right string to the left string so that we are only comparing the prefix.

        if( ( Flags and PSCAN_M_BIT_ALL ) <> 0 ) then
        begin
            for I := 1 to LU.Length do
            begin
                LC := LU.Get_Char( I ) ;
                RC := RU.Get_Char( I ) ;
                if( ( LC and RC ) <> RC ) then
                begin
                    Result := False ;
                    exit ;
                end ;
            end ;
            Result := True ;
            exit ;
        end ;
        if( ( Flags and PSCAN_M_BIT_ANY ) <> 0 ) then
        begin
            Result := False ;
            for I := 1 to LU.Length do
            begin
                LC := LU.Get_Char( I ) ;
                RC := RU.Get_Char( I ) ;
                if( ( LC and RC ) <> 0 ) then
                begin
                    Result := True ;
                    exit ;
                end ;
            end ;
            exit ;
        end ;
Although VMS limits the PSCAN_M_BIT_ALL and PSCAN_M_BIT_ANY flags to non-string comparisons (it generates an error in that case), UOS will allow this kind of comparison on strings as well - in which case the strings are treated as binary data. In the first case, the comparison is true if all of the bits in the right string are set in the left string. In the second case, the comparison is true if any of the bits in the right string are set in the left string.

        I := Compare( LU, RU, ( Flags and PSCAN_M_WILDCARD ) <> 0 ) ;
        case Flags and PSCAN_M_COMPARISON_MASK of
            PSCAN_M_EQL: Result := I = 0 ; // Equal to the value
            PSCAN_M_LSS: Result := I < 0 ; // Less than the value
            PSCAN_M_LEQ: Result := I <= 0 ; // Less than or equal to the value
            PSCAN_M_GTR: Result := I > 0 ; // Greater than the value
            PSCAN_M_GEQ: Result := I >= 0 ; // Greater than or equal to the value
            PSCAN_M_NEQ: Result := I <> 0 ; // Not equal to the value
        end ;
    finally
        LU.Free ;
        RU.Free ;
    end ;
end ; // Compare_Values
If we get down to this point of the function, we call the Compare function to compare the unicode strings. We will talk about the comparison function in the next article. It returns -1 if L is less than R, 1 if L is greater than R, and 0 if they are equal. Based on the flags, we do a comparison and return a true/false result indicating if the requested comparison evaluated to true or false.

function Get_Process( PID : TPID ) : TProcess ;

var Loop : integer ;

begin
    for Loop := 0 to Processes.Count - 1 do
    begin
        Result := TProcess( Processes[ Loop ] ) ;
        if( Result._PID = PID ) then
        begin
            exit ;
        end ;
    end ;
    Result := nil ;
end ;
This function simply returns the process object instance for a given ID. The code runs through the Processes list, looking for a match. If none is found, we return nil. When we get to discussing clusters, we will expand this function to handle remote processes.

In the next article we will look at the actual Process_Scan system call.

 

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