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

The File Processor and Device Management

The File Processor is the component through which all file I/O happens. File management requires file system management, which requires device management. Since all three areas of management are intertwined, we include them all in the file processor instead of separate components. In this article we will address the methods related to device management. Here is the class definition:
type TUOS_FiP = class( TUOS_File_Processor )
                    private // Instance data...
                        _Device_Names : TUOS_Symbol_Table ;
                        _Devices : TDevice_List ;
                        _Virtual_Stores : longint ;

                        _Kernel : TUOS_Kernel ;
                        _SSC : TUOS_System_Services ;
                        _USC : TUOS_User_Security ;

                        Temp : string ;

                   private // Internal utility functions
                       function SSC : TUOS_System_Services ;
                       function USC : TUOS_User_Security ;

                       function Get_Device( const S : string ) : TDevice ;

                    public // API...
                        function Is_Class( Name : PChar ) : boolean ; override ;
                        procedure Set_Kernel( K : TUOS_Kernel ) ; override ;

                    public // Device Management...
                        function Device_Table : TUOS_Symbol_Table ; override ;
                        procedure Process_Store( Device : TDevice ; Index : integer ;
                            S : string ; Store : TCOM_Store64 ) ;
                        procedure Reload_Devices ; override ;
                        procedure Add_Store_Device( Store : TCOM_Store64 ;
                            RAMDisk : boolean ) ; override ;
                end ; // TUOS_FiP

Most of the instance data should be familiar to you after our previous articles about other executive components. The first new one, _Device_Names, is a symbol table that will hold the device names, following the UOS naming convention of <type><controller><unit> (for example DISKA0). The second, _Devices, is a list of device information. _Virtual_Stores is simply a count of the number of virtual stores. More on these later.

Here are the basic executive component methods:

// Internal utility functions...

function TUOS_FiP.SSC : TUOS_System_Services ;

begin
    if( _SSC = nil ) then
    begin
        if( _Kernel <> nil ) then
        begin
            _SSC := _Kernel.SSC ;
        end ;
    end ;
    Result := _SSC ;
end ;


function TUOS_FiP.USC : TUOS_User_Security ;

begin
    if( _USC = nil ) then
    begin
        if( _Kernel <> nil ) then
        begin
            _USC := _Kernel.USC ;
        end ;
    end ;
    Result := _USC ;
end ;


// API...

function TUOS_FiP.Is_Class( Name : PChar ) : boolean ;

var N : string ;

begin
    Result := False ;
    N := string( Name ) ;
    N := lowercase( N ) ;
    if( N = 'tuos_fip' ) then
    begin
        Result := True ;
    end ;
end ;


procedure TUOS_FiP.Set_Kernel( K : TUOS_Kernel ) ;

begin // TUOS_FiP.Set_Kernel
    _Kernel := K ;
    if( _Kernel <> nil ) then
    begin
        HAL := _Kernel.HAL ;
        __HMC := _Kernel.HMC ;
        if( __HMC <> nil ) then
        begin
{$WARNINGS OFF}
            SetMemoryManager( FIP_MM ) ;
{$WARNINGS ON}
        end ;
        Reload_Devices ;
    end ; // if( K <> nil )
end ; // TUOS_FiP.Set_Kernel


function TUOS_FiP.Device_Table : TUOS_Symbol_Table ;

begin
    if( _Device_Names = nil ) then
    begin
        _Device_Names := TSymbol_Table.Create ;
    end ;
    Result := _Device_Names ;
end ;

Again, most of this should look familiar from previous articles about other executive components. The last method shown is simply a deferred creation of the _Device_Names symbol table.

We need a way of looking up a device by name, which is what the Get_Device method does for us:

function TUOS_FiP.Get_Device( const S : string ) : TDevice ;

var Index : integer ;

begin
    Result := nil ;
    Index := _Devices.Indexof( S ) ;
    if( Index >= 0 ) then
    begin
        Result := _Devices.Get_Device( Index ) ;
    end ;
end ;

This function merely looks up the device name in the _Devices list and then returns the device instance associated with that class. If the device name is not in the list, the function returns nil.

Next we have the code to interface the FiP's memory manager with the executive's HMC.

var __HMC : TUOS_Heap_Manager ;

function HMC_NewGetMem( Size : Integer ) : Pointer ;

begin
    Result := __HMC.Getmem( Size ) ;
end ;


function HMC_NewFreeMem( P : Pointer ) : Integer ;

begin
    Result := 0 ;
    __HMC.Free( P ) ;
end ;


function HMC_NewReallocMem( P : Pointer ; Size : Integer ) : Pointer ;

begin
    Result := __HMC.Realloc( P, Size ) ;
end ;



{$WARNINGS OFF}
const FIP_MM : TMemoryManager = (
                                    GetMem : HMC_NewGetMem ;
                                    FreeMem : HMC_NewFreeMem ;
                                    ReallocMem : HMC_NewReallocMem ;
                                 ) ;
{$WARNINGS ON}


procedure TUOS_FiP.Set_Kernel( K : TUOS_Kernel ) ;

begin // TUOS_FiP.Set_Kernel
    _Kernel := K ;
    if( _Kernel <> nil ) then
    begin
        HAL := _Kernel.HAL ;
        __HMC := _Kernel.HMC ;
        if( __HMC <> nil ) then
        begin
{$WARNINGS OFF}
            SetMemoryManager( FIP_MM ) ;
{$WARNINGS ON}
        end ;
        Reload_Devices ;
    end ; // if( K <> nil )
end ; // TUOS_FiP.Set_Kernel

The Set_Kernel method does the familiar job of setting the _Kernal instance data, the HMC is obtained from the Kernel, and the local memory manager is set up. The only new item here is the call to Reload_Devices.

Here's the code for Reload_Devices:

procedure TUOS_FiP.Reload_Devices ;

var Device : TDevice ;
    Index : integer ; // HAL device index
    Info : TDevice_Info ;
    S, St : string ;
    Store : TCOM_Store64 ;

begin
    // (Re)Create device and device name table...
    if( _Device_Names <> nil ) then
    begin
        _Device_Names.Detach ;
        _Devices.Free ;
        MStores.Clear ;
        _Virtual_Stores := 0 ;
    end ;
    _Device_Names := TSymbol_Table.Create ;
    _Devices := TDevice_List.Create ;

    // Build device and device name tables...
    Index := 0 ;
    Info := HAL.Device( Index ) ;
    while( Info.Device_Type <> DT_Non_Existant ) do
    begin
        S := chr( 65 + Info.Controller ) + inttostr( Info.Device_Unit ) ;
        Device := TDevice.Create ;
        Device.Index := Index ;
        Device.Info := Info ;
        case Info.Device_Type of
            DT_Serial :
                begin
                    S := '_TERM' + S + ':' ;
                    _Device_Names.Set_Value( PChar( S ), '', 0 ) ;
                    _Devices.AddObject( S, Device ) ;
                end ; // DT_Serial
            DT_Store :
                begin
                    S := '_DISK' + S + ':' ;
                    _Devices.AddObject( S, Device ) ;
                    if( Info.Media_Present ) then
                    begin
                        Store := HAL.Store( Index ) ;
                        Process_Store( Device, Index, S, Store ) ;
                    end ; // if( Info.Media_Present )
                end ; // DT_Store
        end ; // case Info.Device_Type
        inc( Index ) ;
        Info := HAL.Device( Index ) ;
    end ; // while( Info.Device_Type <> DT_Non_Existant )

    // Determine RAID sets...
    for Index := 0 to MStores.Count - 1 do
    begin
        S := '_DISKZ' + inttostr( Index ) + ':' ;
        Device := TDevice.Create ;
        Device.Index := -1 ; // Not a HAL device
        Device.Info.Controller := 26 ;
        Device.Info.Device_Type := DT_Store ;
	Device.Info.Device_Unit := Index ;
        Store := MStores.Get( Index ) ;
        Process_Store( Device, -1, S, Store ) ;
    end ;

    // Special devices...
    Device := TDevice.Create ;
    Device.Index := -1 ; // Not a HAL device
    S := '_NULL:' ;
    _Device_Names.Set_Value( PChar( S ), '', 0 ) ;
    _Devices.AddObject( S, Device ) ;
end ; // TUOS_FiP.Relead_Devices

Normally, this function will only be called once, but it is written so that it can be called multiple times, if so desired. So the first thing we check is if _Device_Names is non-nil. If so, the routine has been called before, so we must clear the existing _Devices and _Device_Names via Free and Detach, respectively. We also have to clear the mirror stores via MStores.Clear since the RAID sets will be recalculated. Then we reset the number of virtual stores to 0.
The first order of business is to create the _Devices and _Device_Names class instances. Then we loop through the HAL's devices. This should look familiar - it is very similar to code in Init which does the same function. We skip non-existant devices, then construct the device name (in S). We also construct a TDevice instance for this device. We add the name to _Device_Names and the name/device to _Devices. Why do we add the device name to two different lists instead of only one? Because of two reasons: data decoupling and performance. The FiP relies on the _Devices list for device management. But other components (such as the SSC) needs a simple list of device names that it can look through. _Device_Names provides this list, which can be traversed quickly when someone wants to find a device name. Also, that way no other component needs to know any details about the _Devices list and so we avoid pathological coupling between the FiP and other components.
The device type determines what exact processing is to be done. In the case of a serial device, the processing is what we described above. In the case of a store, we also get a store instance (assuming that media is present) and save that in the current device instance. Then we process the store (more on that in a moment).
Next, we check against the RAID sets (if any) and handle them like any physical store returned to us by the HAL. Note that we don't handle RAM disks here. On executive startup, those are handled by the Kernel, and any after that are the consequence of a user request. In either case, our Add_Store_Device method is called to add the device to our device list.
Finally, we handle special devices. For now, the only special device we will deal with is the NULL device. This kind of device exists on most Operating Systems for, at very least, testing purposes. For example, on RSTS/E it was called NL:, and on MSDOS it was called NUL. Essentially, it is a device in name only. It has no associated physical or logical device associated with it. Output sent to it is ignored, and attempts to read from it result in errors (or EOF indicators). We will discuss the null device more in later articles.

Here is the code for the Process_Store method:

procedure Process_Store( Device : TDevice ; Index : integer ;
                            S : string ; Store : TCOM_Store64 ) ;

var Buffer : PChar ;
    E : TUnified_Exception ;
    GUID_Partition : TGUID_Partition ;
    N : string ;
    Partition : TPartition ;
    PIndex : integer ; // Partition index
    PInfo : TPartition_Info ;
    PStore : TCOM_Store64 ;
    RInfo : TRAID_Info ; // RAID information
    Size : longint ;
    St : string ;

begin
    Buffer := allocmem( Store.Min_Storage ) ;
    try
        Device.Store := Store ;
        RInfo := Get_RAID_Info( Store ) ;
        Device.RInfo := RInfo ;

        if( RInfo.RAID_Level = 1 ) then
        begin
            _Device_Names.Set_Value( PChar( S ), '', 0 ) ;
        end else
        begin
            PInfo := Partition_Info( Store ) ;
            if( PInfo.Partition_Count > 0 ) then
            begin
                if( PInfo.Partition_Type = PT_MBR ) then
                begin
                    Store.Read_Data( Buffer[ 0 ], 0, Store.Min_Storage, E ) ;
                    if( E <> nil ) then
                    begin
                        exit ;
                    end ;
                    setlength( S, length( S ) - 1 ) ;
                    St := S ;

                    // MBR...
                    for PIndex := 0 to 3 do
                    begin
                        move( Buffer[ 446 + PIndex * sizeof( Partition ) ], Partition, sizeof( Partition ) ) ;
                        if( Partition.Partition_Type <> 0 ) then // Not an unused entry
                        begin
                            if( Partition.Drive_Status <> 0 ) then // Active
                            begin
                                S := St + uppercase( Partition_Spec( PIndex ) ) + ':' ;
                                PStore := Get_Partition( Store, PIndex ) ;
                                RInfo := Get_RAID_Info( PStore ) ;
                                Device.RInfo := RInfo ;
                                Device := TDevice.Create ;
                                Device.Index := PIndex ;
                                Device.Parent_Index := Index ;
                                Device.Store := PStore ;
                                _Devices.AddObject( S, Device ) ;
                                if( RInfo.RAID_Level = 1 ) then // Partition is part of a RAID set
                                begin
                                    Device.RAID_Check := Check_Store( PStore ) ;
                                end else
                                begin
                                    _Device_Names.Set_Value( PChar( S ), '', 0 ) ; // Partition
                                end ;
                            end ; // if( Partition.Drive_Status <> 0 )
                        end ; // if( Partition.Partition_Type <> 0 )
                    end ; // for Index := 0 to 3
                end else
                begin
                    // GUID...
                    Size := PInfo.GPT_Header.Entry_Size * PInfo.GPT_Header.Partition_Count ;
                    Size := ( Size + Store.Min_Storage - 1 ) and
                        not ( Store.Min_Storage - 1 ) ;
                    Reallocmem( Buffer, Size ) ;
                    Store.Read_Data( Buffer[ 0 ],
                        PInfo.GPT_Header.Entries_LBA * Store.Min_Storage, Size, E ) ;
                    setlength( S, length( S ) - 1 ) ;
                    St := S ;

                    for PIndex := 0 to PInfo.GPT_Header.Partition_Count - 1 do
                    begin
                        move( Buffer[ Index * PInfo.GPT_Header.Entry_Size ], GUID_Partition,
                            sizeof( GUID_Partition ) ) ;//Info.GPT_Header.Entry_Size ) ;
                        if(
                            ( GUID_Partition.Partition_Type[ 0 ] <> 0 )
                            or
                            ( GUID_Partition.Partition_Type[ 1 ] <> 0 )
                          ) then // Used
                        begin
                            if( GUID_Partition.Attributes <> 0 ) then // Active
                            begin
                                setlength( N, sizeof( GUID_Partition.Name ) ) ;
                                move( GUID_Partition.Name, PChar( N )[ 0 ], length( N ) ) ;
                                N := Edit( N, 4 or 128 ) ;
                                setlength( N, length( N ) - 1 ) ;
                                Device := TDevice.Create ;
                                Device.Name := N ;
                                Device.Index := PIndex ;
                                Device.Parent_Index := Index ;
                                PStore := Get_Partition( Store, PIndex ) ;
                                Device.Store := PStore ;
                                if( PStore <> nil ) then
                                begin
                                    RInfo := Get_RAID_Info( PStore ) ;
                                    Device.RInfo := RInfo ;
                                    if( RInfo.RAID_Level = 1 ) then // Partition is part of a RAID set
                                    begin
                                        Device.RAID_Check := Check_Store( Store ) ;
                                    end else
                                    begin
                                        S := St + uppercase( Partition_Spec( PIndex ) ) + ':' ;
                                        _Device_Names.Set_Value( PChar( S ), '', 0 ) ; // Partition
                                        _Devices.AddObject( S, Device ) ;
                                    end ;
                                end ; // if( PStore <> nil )
                            end ; // if( GUID_Partition.Attributes <> 0 )
                        end ; // if
                    end ; // for Index := 0 to Info.GPT_Header.Partition_Count - 1
                end ; // if
            end else
            begin
                _Device_Names.Set_Value( PChar( S ), '', 0 ) ; // Normal store
            end ; // if( Info.Partition_Count > 0 )
        end ; // if( RInfo.RAID_Level = 1 )
    finally
        freemem( Buffer ) ;
    end ;
end ; // TUOS_FiP.Reload_Devices.Process_Store

This code is almost identical to the partition-handling code used in Init. We didn't cover it there because it was specific to the handling of the MBR and GPT partitioning standards and I didn't want to bog things down with detailed descriptions of those standards. You can go find the standards and read all about them if you wish. What we do in this routine is important to our implementation of the FiP, so I've included the code. But I will not describe it in gory detail. The first thing we do is set the Device instance's store to this store. Then we get the RAID information and store that in the Device instance as well. If the store is part of a RAID set, we remove the device from the _Device_Names list - UOS provides a separate device name that includes the whole set and we don't allow access to the individual members. In that case we are done processing this store.
If the store isn't a RAID member, we get the partition information. It is either an MBR or a GPT type of partition structure, and we process whichever one it is. Other than the differences inherent in the partition structure, we do the exact same thing in both cases: iterate through the partitions and create additional entries in the _Device_Names and _Devices lists. But we also check if the partition is part of a RAID set and handle that as well. For RAID members, we set the Device's RAID_Check instance data to the result of the call to Check_Store, which returns True if the store was successfully added to the RAID set.

Here is the code for Check_Store:

function Check_Store( Store : TCOM_Store64 ) : boolean ;

var RInfo : TRAID_Info ;
    MStore : TMirror_Store ;

begin
    Result := False ;
    if( Store = nil ) then
    begin
        exit ;
    end ;
    RInfo := Get_RAID_Info( Store ) ;
    if( RInfo.RAID_Level = 1 ) then // Found a mirror member
    begin
        MStore := MStores.Store_With_GUID( RInfo.RAID_GUID ) ;
        if( MStore = nil ) then
        begin
            MStore := TMirror_Store.Create ;
        end else
        if(
            (
              ( RInfo.Timestamp > MStore.Timestamp )
              and
              ( RInfo.Sequence <= MStore.Sequence )
            )
            or
            (
              ( RInfo.Timestamp < MStore.Timestamp )
              and
              ( RInfo.Sequence >= MStore.Sequence )
            )
          ) then // Some sort of mismatch
        begin
            Result := True ;
        end ;
        MStore.Add_Store( Store ) ;
    end ; // if( RInfo.RAID_Level = 1 )
end ; // Check_Store

This function simply does a consistency check between this store and the RAID set. If there is a mismatch (or the passed store is nil, or the RAID level isn't 1), the function returns False (meaning failure). Otherwise, we add the store to the RAID set.

In article 24 we discussed the TMirror_Manager class, an instance of which is published via the MStores variable. We've added a new method to it:

procedure TMirror_Manager.Clear ;

var Index : integer ;

begin
    for Index := Count - 1 downto 0 do
    begin
        Remove( Index ) ;
    end ;
end ;

The function simply iterates through the mirror sets and calls Remove on each one.

Next we need a way to add new virtual stores (such as RAMDisks and network disks). The following method supports this:

procedure TUOS_FiP.Add_Store_Device( Store : TCOM_Store64 ; RAMDisk : boolean ) ;

var Device : TDevice ;
    S : string ;

begin
    Set_Last_Error( nil ) ;
    if( Store = nil ) then
    begin
        exit ;
    end ;
    if( _Virtual_Stores = high( Device.Info.Device_Unit ) - 1 ) then
    begin
        Set_Last_Error( Create_Error( UOSErr_Exhausted_Resource ) ) ;
        exit ;
    end ;

    // Create device...
    Device := TDevice.Create ;
    Device.Index := -1 ; // Not a HAL device
    Device.RAM := RAMDisk ;
    Device.Info.Controller := 25 ;
    Device.Info.Device_Type := DT_Store ;
    Device.Info.Device_Unit := _Virtual_Stores ;
    S := '_DISKY' + inttostr( _Virtual_Stores ) + ':' ;
    inc( _Virtual_Stores ) ;
    _Devices.AddObject( S, Device ) ;
    Process_Store( Device, -1, S, Store ) ;
end ; // TUOS_FiP.Add_Store_Device

This method is called when a virtual device that is a store is being added. These devices are not recognized by the HAL because they are not physical devices in the normal sense. First we clear any errors. If nil is passed, we exit. Otherwise We check to see if there are any unit numbers remaining for virtual stores. There can be up to 65535 virtual stores in any run of UOS. If we are at the limit, we set an error and exit. Next we create a TDevice for the passed store. All virtual disks are considered to be controller 25 ("Y"). The unit will be a one-up number (_Virtual_Stores) that we increment on each call. Even if a virtual disk is disposed of later, its unit number is not recycled - the unit just keeps increasing. We add the device to _Devices and call Process_Store to handle any issues of partitioning or mirroring.

_Devices is an instance of the TDevice_List class, whose definition follows:

type TDevice_List = class( TStringList )
                        public // Constructors and destructors...
                            destructor Destroy ; override ;

                        public // API...
                            function Get_Device( Index : integer ) : TDevice ;
                    end ;

This class is a descendent of TStringList, which is a list of strings associated with object instances. The string for each item is the device name and the object is the TDevice instance associated with that device name.

Here are the methods:

// Constructors and destructors...

destructor TDevice_List.Destroy ;

var Loop : integer ;

begin
    for Loop := 0 to Count - 1 do
    begin
        TDevice( Objects[ Loop ] ).Free ;
        Objects[ Loop ] := nil ;
    end ;

    inherited Destroy ;
end ;


// API...

function TDevice_List.Get_Device( Index : integer ) : TDevice ;

begin
    Result := TDevice( Objects[ Index ] ) ;
end ;

The destructor loops through the devices and frees them. Get_Device simply provides a means of casting the generic pointer in the TStringList to a TDevice instance.

The definitions used by TDevice, and the class follow:

const Protection_Allow_User_Read = 1 ;
const Protection_Allow_User_Write = 2 ;
const Protection_Allow_User_Control = 4 ;
const Protection_Allow_User_All = 7 ;
const Protection_Allow_Group_Read = 8 ;
const Protection_Allow_Group_Write = 16 ;
const Protection_Allow_Group_Control = 32 ;
const Protection_Allow_Group_All = 56 ;
const Protection_Allow_World_Read = 64 ;
const Protection_Allow_World_Write = 128 ;
const Protection_Allow_World_Control = 256 ;
const Protection_Allow_World_All = 448 ;

type TOwnership_Type = ( OT_None, // Not owned
                         OT_User, // Owned by a user
                         OT_Process // Owned by a process
                       ) ;

type TOwnership = packed record
                      Ownership_Type : TOwnership_Type ;
                      Owner : cardinal ; // User or PID
                      Protections : cardinal ; // See Protection_*
                      //ACL
                  end ;

type TDevice = class
                   public // API...
                       // Identification...
                       Index : integer ; // HAL device index
                       Parent_Index : integer ;
                       Info : TDevice_Info ;
                       RInfo : TRAID_Info ; // RAID information
                       Store : TCOM_Store64 ; // Nil if not a store
                       Name : string ; // Named GPT partition

                       // For RAID...
                       RAID : integer ; // RAID level
                       RAID_Check : boolean ; // True if there is an inconsistency between RAID members

                       // For terminals...
                       Attached : cardinal ; // PID attached to terminal (0 if none)

                       // Other...
                       Owner : TOwnership ;

                       // For stores...
                       Mounted : boolean ; // True if mounted
                       FS : TUOS_File_System ; // File system for device (nil if non-structured store)
                       RAM : boolean ; // True if a RAM disk

                   public // Constructors and destructors...
                       constructor Create ;
               end ; // TDevice

// Constructors and destructors...

constructor TDevice.Create ;

begin
    inherited Create ;

    Index := -1 ;
    Parent_Index := -1 ;
    RAID := -1 ;
end ;

We will discuss protection codes and ownership in a future article. All instance data is zeroed when the class instance is created, so we set the values that need to be something other than 0 initially.

There are a couple of other device management methods that we will not cover until a later article because they fit into a larger discussion of users and security. In the next article, we will discuss the File System management portion of the FiP.

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