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

UCL
45 Shells and UCL

Glossary/Index


Download sources
Download binaries

Using the File Processor

Mounting the Boot Device
In the last article, we discussed the loading of the rest of the executive components. The next step is to dismount the file system and remount it via the File Processor. From this point on, the FiP will be the means of accessing file systems. Here is the code:

    // Dismount file system and remount via FiP...
    E := FS.Dismount ;
    if( E <> nil ) then
    begin
        Show_Error( E ) ;
        H.Halt ;
    end ;
    E := FiP.Mount( PChar( Boot_Name[ 1 ] ), FS, FiP_Flags or FiP_Force, '', '' ) ;
    if( E <> nil ) then
    begin
        Show_Error( E ) ;
        H.Halt ;
    end ;

The FiP's Mount method takes the name of the boot device, the file system instance, and some flags. FiP_Flags indicates that the flags value is to be interpreted. It is a bit mask indicating various options associated with the mount operation. If FiP_Flags isn't passed, then the value is ignored. Otherwise the flags are used, even if they are all unset. In this case, we are telling the FiP to force the mount. We do this because if the file system is password protected, we want to mount it anyway. This is a consequence of booting from the device - the user has no means of specifying a password on startup. Even if there was, we need to be able to reboot the system from this boot device without user interaction. The rule is: file system passwords are ignored on the boot device. The two null strings being passed would contain the file system name and password if we weren't forcing the mount. If something goes wrong, Mount will return an exception. In such a case, we report the error and halt. You may notice that this is a recurring theme. If things go wrong in the kernel startup, there isn't much that the user can do within the scope of UOS. Once system startup completes, we will shift gears and try to avoid halting the system when errors occur - instead allowing the user to correct the problem(s) within the context of a running UOS.

Processing Startup Parameters
The next step is to process the startup parameters. As you recall from past articles, Init will pass configuration information to UOS. This information includes such things as which devices are disabled, and any RAM disks. Other data is optionally passed to UOS, some of which we will cover in a future article. Let's take a look at that code:

    // Process startup parameters...
    if( not S.Read_Only ) then
    begin
        St := Get_Startup_Data ;
    end else
    begin
        setlength( St, H.Get_Configuration_Size ) ;
        move( H.Get_Configuration[ 0 ], PChar( St )[ 0 ], length( St ) ) ;
    end ;

If the boot store isn't read-only then we call the Get_Startup_Data function to read the configuration information from the store. If the store is read-only, we ask the HAL for the configuration. Either way, St will now contain the configuration data written by Init.

Here is the local Get_Startup_Data function:

function Get_Startup_Data : string ;

var E : TUnified_Exception ;
    Info : TUOS_File_Info ;
    UF : TUOS_File ;

begin
    Result := '' ;
    UF := FS.Get_File( PChar( '\uos\startup.dat' ) ) ;
    if( UF = nil ) then
    begin
        E := FS.Last_Error ;
        if( ( E <> nil ) and ( E.Get_Error = UOS_File_System_File_Not_Found ) ) then
        begin
            exit ; // No file means no parameters
        end ;
        if( E <> nil ) then
        begin
            Show_Error( E ) ;
            Output( 'Could not load startup parameters' + CRLF ) ;
            exit ;
        end ;
    end ;
    try
        Info := FS.Get_File_Info( PChar( '\uos\startup.dat' ), 0 ) ;
        setlength( Result, Info.Size ) ;
        UF.Read( 0, 0, Info.Size, PChar( Result )[ 0 ] ) ;
    finally
        UF.Free ;
    end ;
end ; // .Get_Startup_Data

All this function does is read the startup.dat file into a string and return it. If there is an error reading the file, we show the error, a descriptive message and exit with a null string. If the file doesn't exist, it isn't an error (it simply means that Init passed us no configuration information), and we return null.

In article 26 we discussed the Init code to generate the configuration information. The following code parses the information from Init:

    P := 1 ;
    while( P <= length( St ) ) do
    begin
        R := ord( St[ P ] ) ;
        inc( P ) ;
        case R and 3 of
            0 : Size := 1 ;
            1 : Size := 2 ;
            2 : Size := 4 ;
            4 : Size := 8 ;
        end ;
        R := R and ( not 3 ) ;
        case R of
            UOS_Config_Disabled_Device:
                begin
                    P1 := Get_Value( copy( St, P, Size ) ) ; // Device type
                    P := P + Size ;
                    P2 := Get_Value( copy( St, P, Size ) ) ; // Controller #
                    P := P + Size ;
                    P3 := Get_Value( copy( St, P, Size ) ) ; // Unit #
                    P := P + Size ;
                    H.Disable( P1, P2, P3 ) ;
                end ;
            UOS_Config_RAM_Disk:
                begin
                    P1 := Get_Value( copy( St, P, Size ) ) ; // Address
                    P := P + Size ;
                    P2 := Get_Value( copy( St, P, Size ) ) ; // Size
                    P := P + Size ;
                    P3 := Get_Value( copy( St, P, Size ) ) ; // Clustersize
                    P := P + Size ;
                    if( RAM_Disks = nil ) then
                    begin
                        RAM_Disks := TList.Create ;
                    end ;
                    RAM_Disk := TRAM_Disk64.Create ;
                    RAM_Disk.Set_Min_Storage( P3 ) ;
                    RAM_Disk.Set_Buffer( pointer( P1 ), P2, E ) ;
                    if( not Show_Error( E ) ) then
                    begin
                        RAM_Disks.Add( RAM_Disk ) ;
                    end ;
                end ;
            UOS_Config_Boot_Device: // Ignore since we already know the boot device
                begin
                    P := P + Size * 3 ; // Skip past the items
                end ;
            UOS_Config_Installation:
                begin
                    P1 := Get_Value( copy( St, P, Size ) ) ; // Length of string
                    P := P + Size ;
                    setlength( Installation, P1 ) ;
                    move( St[ P ], PChar( Installation )[ 0 ], P1 ) ;
                    P := P + P1 ;
                end ;
        end ;
    end ; // while( P <= length( St ) )

Since this is the flip-side of the Init code, we won't go into this code in detail. We'll consider two issues, though. In the case of disabled devices, we parse the device, controller, and unit and then tell the HAL to disable the device. Didn't we already disable the device in Init? Yes we did (in fact, if we hadn't then the information wouldn't be passed in the configuration). But, we have a new instance of the HAL and it may have reset the devices to enabled (or some devices may only be disabled via the HAL's device driver instead of by the hardware). The other issue is that of RAM disks. RAM_Disks is a list that contains RAM disk instances. For each RAM disk record, we parse the address, size, and clustersize, create a RAM disk instance, and add it to the list.

The FiP provides the interface to all devices, so after getting the configuration, we have to notify it.

    // Readjust FiP devices...
    FiP.Reload_Devices ;
    if( RAM_Disks <> nil ) then
    begin
        for I := 0 to RAM_Disks.Count - 1 do
        begin
            // Add to FiP...
            FiP.Add_Store_Device( TRAM_Disk64( RAM_Disks[ I ] ), True ) ; // Add RAM disk
            if( Show_Error( FiP.Last_Error ) ) then
            begin
                Output( 'Could not add RAM Disk ' + inttostr( I ) ) ;
            end ;
        end ;
    end ;

First, we tell the FiP to reload the devices in their current enabled/disabled state. This is done with the Reload_Devices method. Then we loop through the RAM disk list and add each store to the FiP with the Add_Store_Device method.

Sys$system and Loading the SSC
Now we load the final executive component - the User and Security Component (USC).

    // Load USC component...
    _USC := TUOS_User_Security( Load_Component( FS, Installation + 'usc.sys', USC_Index ) ) ;
    if( _USC = nil ) then
    begin
        Output( 'Error loading the USC' ) ;
        H.Halt ;
    end ;
    _USC.Kernel := self ;

This code mirrors the code for loading the other components and we don't need to discuss it further here.

The next step is to define a symbol that can be used to refer to the booted installation folder.

    Set_Symbol( 'sys$system:', PChar( Installation ) ) ;

Here is the local Set_Symbol function:

procedure Set_Symbol( const S, S1 : string ) ;

var Str : TString ;

begin
    Str := TString.Create ;
    try
        Str.P := Pchar( S1 ) ;
        Str.Len := length( S1 ) ;
        SSC.Set_Symbol( LNM_System, 0, PChar( S ), Str ) ;
    finally
        Str.Free ;
    end ;
end ;

We will discuss symbols in the next article, but for now simply realize that sys$system is a synonym for the installation path that we booted from. The LNM_System flag indicates that this is a system-wide symbol that is available for use for everyone on the system.

In the next article, we will discuss the implementation of the System Services component.