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

Lexical functions - F$DEVICE

The next lexical function is F$DEVICE. In a manner similar to how F$PID allows us to iterate through processes, F$DEVICE allows us to iterate through devices. However, this is a simpler process as F$DEVICE does not require a separate function to construct filters. The filter is essentially defined in the call to F$DEVICE. Here is the function description:

F$DEVICE iterates through system devices.

Format
F$DEVICE({devnam}, {devclass}, {devtype}, {context})

Return Value
The name of the next device matching the criteria. After the last device name in the device list is returned, the function returns a null string ("").

Arguments
devnam

Specifies a string containing the name of the device to search for. The asterisk (*) and question mark (?) wildcards are allowed. If omitted, all devices are searched.

devclass

Specifies a string containing the device class to match. This must be one of the following values:
ValueDescription
anyMatch any device
diskDisks
tapeTapes
cardCards
termTerminals
lpPrinters
realtimeRealtime devices
audioAudio devices
videoNon-terminal video devices
mailboxMailboxes
remcsl_storageRemove storage
miscAll other devices

devtype

Specifies a string containing the device type to match. This is included for compatiblity with VMS, but is ignored by UOS. The value of this argument has no effect on the operation of the function.

context
An integer value representing the device scan context. This is used to maintain separate search contexts when F$DEVICE is used more than once in a command procedure. If omitted, a default wildcard context is used.

If the same context value is specified but different criteria are specified, then that context is reset and the first matching device is returned.

Description
F$DEVICE allows you to search or devices matching certain criteria.

F$DEVICE can be used in a loop to return all device names that match the selection criteria. Note that the order the devices is returned cannot be assumed to follow any particular order. After the last device name is returnd, the next F$DEVICE call returns a null string.

You must maintain the context of the search explicitly by specifying the context and using the same selection criteria in each F$DEVICE call.

Example
$ X = F$DEVICE("","DISK",,) This would return the first/next disk device. Because no context is specified, F$DEVICE uses an implicit context.

        Function_Device : begin
                              if( Missing_Parentheses( '(' ) ) then
                              begin
                                  exit ;
                              end ;
                              if( Parse_Device( Err, Context ) ) then
                              begin
                                  exit ;
                              end ;
                              if( Missing_Parentheses( ')' ) ) then
                              begin
                                  exit ;
                              end ;
                              S := Context ;
                          end ;
We add code to Function_Reference to handle the F$DEVICE lexical function.

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

var DevName, DevClass, DevType, S : string ;
    Dev_Class : int64 ;
    I, Index : integer ;
    C : int64 ;
    Descriptors : Array[ 0..1 ] of TDVI_Descriptor ;

begin
    DevName := trim( Get_Parameter( Err, Context, True ) ) ;
    if( Err <> 0 ) then
    begin
        Result := True ;
        exit ;
    end ;
    Result := Missing_Comma( Err ) ;
    if( Result ) then
    begin
        Result := True ;
        exit ;
    end ;

    DevClass := lowercase( trim( Get_Parameter( Err, Context, True ) ) ) ;
    if( Err <> 0 ) then
    begin
        Result := True ;
        exit ;
    end ;
    Result := Missing_Comma( Err ) ;
    if( Result ) then
    begin
        Result := True ;
        exit ;
    end ;
    if( DevClass = 'any' ) then
    begin
        Dev_Class := DC_ANY ;
    end else
    if( DevClass = 'disk' ) then
    begin
        Dev_Class := DC_DISK ;
    end else
    if( DevClass = 'tape' ) then
    begin
        Dev_Class := DC_TAPE ;
    end else
    if( DevClass = 'card' ) then
    begin
        Dev_Class := DC_CARD ;
    end else
    if( DevClass = 'term' ) then
    begin
        Dev_Class := DC_TERM ;
    end else
    if( DevClass = 'lp' ) then
    begin
        Dev_Class := DC_LP ;
    end else
    if( DevClass = 'realtime' ) then
    begin
        Dev_Class := DC_REALTIME ;
    end else
    if( DevClass = 'audio' ) then
    begin
        Dev_Class := DC_AUDIO ;
    end else
    if( DevClass = 'video' ) then
    begin
        Dev_Class := DC_VIDEO ;
    end else
    if( DevClass = 'mailbox' ) then
    begin
        Dev_Class := DC_MAILBOX ;
    end else
    if( DevClass = 'remcsl_storage' ) then
    begin
        Dev_Class := DC_REMCSL_STORAGE ;
    end else
    if( DevClass = 'misc' ) then
    begin
        Dev_Class := DC_MISC ;
    end else
    begin
        Context := S ;
        Err := UCL_IVKEYW ;
        exit ;
    end ;

    DevType := trim( Get_Parameter( Err, Context, True ) ) ;
    if( Err <> 0 ) then
    begin
        Result := True ;
        exit ;
    end ;
    Result := Missing_Comma( Err ) ;
    if( Result ) then
    begin
        Result := True ;
        exit ;
    end ;
    S := lowercase( Get_Parameter( Err, Context, True ) ) ;
    if( Err <> 0 ) then
    begin
        exit ;
    end ;
Parsing the F$DEVICE arguments is straight-forward. We get the parameters and validate the device class.

    fillchar( Descriptors, sizeof( Descriptors ), 0 ) ;
    Descriptors[ 0 ].Item_Code := DVS_DEVCLASS ;
    Descriptors[ 0 ].Buffer_Address := int64( @Dev_Class ) ;
    Descriptors[ 0 ].Buffer_Length := sizeof( Dev_Class ) ;
    if( S <> '' ) then
    begin
        S := UCL_Strtoint( S ) ;
        if( not trystrtoint64( S, C ) ) then
        begin
            Err := UCL_EXPSYN ; // Invalid expression syntax
            Context := S ;
            exit ;
        end ;
        I := Device_Contexts_Indexes.Indexof( C ) ;
        if( I = -1 ) then
        begin
            Device_Contexts_Indexes.Add( C ) ;
            Device_Contexts_Contexts.Add( 0 ) ;
            I := Device_Contexts_Contexts.Count - 1 ;
        end ;
        C := Device_Contexts_Contexts[ I ] ;
        S := DEVICE_SCAN( Devname, int64( @Descriptors[ 0 ] ), int64( @C ) ) ;
        Device_Contexts_Contexts[ I ] := C ;
    end else
    begin
        S := DEVICE_SCAN( Devname, int64( @Descriptors[ 0 ] ), int64( @Default_Device_Context ) ) ;
    end ;

    Context := S ;
end ; // Parse_Device
The DEVICE_SCAN system call takes a set of descriptors that indicate the search criteria. We only pass a single descriptor for the device class, since the device type is ignored. The list of descriptors is terminated with 64 zero bits. So, first we zero-fill a two-descriptor array with zeros. Then we construct the construct the first descriptor for the device class.

At this point, S is the context parameter. If empty, we simply call DEVICE_SCAN with a pointer to the default device context. Otherwise, we ensure that the context is numeric, and search our local list of device contexts for the specified value. If not found, we construct a new device and add it to the list. Either way, we call DEVICE_SCAN with the device name and context.

Finally, we return the device found by the call.

var Default_Device_Context : int64 = 0 ;
var _Device_Contexts_Indexes : TInteger_List = nil ;
var _Device_Contexts_Contexts : TInteger_List = nil ;

function Device_Contexts_Indexes : TInteger_List ;

begin
    if( _Device_Contexts_Indexes = nil ) then
    begin
        _Device_Contexts_Indexes := TInteger_List.Create ;
    end ;
    Result := _Device_Contexts_Indexes ;
end ;


function Device_Contexts_Contexts : TInteger_List ;

begin
    if( _Device_Contexts_Contexts = nil ) then
    begin
        _Device_Contexts_Contexts := TInteger_List.Create ;
    end ;
    Result := _Device_Contexts_Contexts ;
end ;
Default_Device_Context defines the implicit device context used when F$DEVICE is called without a context. All other contexts consist of two parts: the integer value passed to F$DEVICE as the context number, and the actual context value from the executive. We will look at the executive side in the next article, but suffice it to say that if the contents of the context address we pass is 0, the exceutive writes a new UOS device context value to it.

// VMS-compatible device classes...
const DC_ANY = 0 ;
const DC_DISK	= 1 ;
const DC_TAPE	= 2 ;
const DC_CARD	= 3 ;
const DC_TERM	= 4 ; // Terminal
const DC_LP	= 5 ; // Printer
const DC_REALTIME	= 6 ;
const DC_AUDIO = 7 ;
const DC_VIDEO = 8 ;
const DC_MAILBOX = 9 ;
const DC_REMCSL_STORAGE =	10 ; // Remote storage
const DC_MISC	= 11 ;

// Device scan item codes...
const DVS_DEVCLASS = 0 ;
const DVS_DEVTYPE = 1 ;
These are the constants used in the above code.

function DEVICE_SCAN( const Devnam : string ; Itemlist, Context : int64 ) : string ; //NEW

var Len : int64 ;
    SRB : TSRB ;
    Status : integer ;

begin
    Len := 0 ;
    setlength( Result, 64 ) ;
    Set_String( Devnam, SRB ) ;
    Status := SYS_DEVICE_SCAN( int64( PChar( Result ) ), int64( @Len ), int64( @SRB ), Itemlist, Context ) ;
    setlength( Result, Len ) ;
    if( Status <> 0 ) then // An error occurred
    begin
        Set_Error( LIB_Get_Exception( 0 ) ) ;
    end ;
end ;
DEVICE_SCAN is the PasStarlet routine that interfaces our Pascal code to system routines. It wraps the passed string into a TSRB structure, makes the call to SYS_DEVICE_SCAN and then returns the device name. Device names will never exceed 64 bytes in length, so we initialize the result to 64 bytes. After the call, the result is reset to the length returned by the system call.

In the next article, we'll look at SYS_DEVICE_SCAN and the executive routines called by it.

 

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