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

The Kernel

In the previous article, we discussed the Startup routine which was responsible for creating an instance of the Kernel class, calling its Startup method, and the use of the temporary heap. Now let us turn to the Kernel itself - or rather, the slice of the Kernel necessary to complete system startup. The abstract class for the Kernel is defined as follows:

type TUOS_Heap_Manager = class ;
     TUOS_Memory_Manager = class ;
     TUOS_User_Security = class ;
     TUOS_File_Processor = class ;
     TUOS_System_Services = class ;

     TUOS_Kernel = class( TCommon_COM_Interface )
                       public // API...
                           procedure Startup( H : THAL ) ;
                               virtual ; stdcall ; abstract ;
                           function HAL : THAL ; virtual ; stdcall ; abstract ;
                           procedure Halt( C : longint ) ;
                               virtual ; stdcall ; abstract ;
                           function MMC : TUOS_Memory_Manager ;
                               virtual ; stdcall ; abstract ;
                           function HMC : TUOS_Heap_Manager ;
                               virtual ; stdcall ; abstract ;
                           function SSC : TUOS_System_Services ;
                               virtual ; stdcall ; abstract ;
                           function USC : TUOS_User_Security ;
                               virtual ; stdcall ; abstract ;
                           function FiP : TUOS_File_Processor ;
                               virtual ; stdcall ; abstract ;
                   end ; // TUOS_Kernel

First we forward declare the other components, which we will cover in future articles. The MMC, HMC, SSC, USC, and FiP methods simply return the instances of the corresponding components to whatever component needs them. The HAL method likewise returns the HAL instance. That leaves two other methods that we will be dealing with for now.

Startup - starts up UOS.
Halt - Halts UOS with an error code.

The Halt method is used when some unrecoverable startup error occurs. The idea is that the code is somehow returned to the user so that the problem can be identified and corrected. Spitting out a number may not be the nicest thing we could do, but it is the least likely to cause an error itself. Normally, we want to provide as much information as possible, so, as can be guessed, Halt is only used in special cases.
Startup takes an instance of a HAL as its only parameter. This cannot be null. We will cover the Startup method over the next several articles.

Here is our descendent class definition for the Kernel:

type TKernel = class( TUOS_Kernel )
                   private // Instance data...
                       __HAL : THAL ;
                       _FiP : TUOS_File_Processor ;
                       _HMC : TUOS_Heap_Manager ;
                       _MMC : TUOS_Memory_Manager ;
                       _SSC : TUOS_System_Services ;
                       _USC : TUOS_User_Security ;

                       PID : cardinal ; // Currently executing process

                   protected // Internal utility routines...
                       procedure Idle ;

                   public // API...
                       function Is_Class( Name : PChar ) : boolean ; override ;
                       function HAL : THAL ; override ;
                       procedure Halt( C : longint ) ; override ;
                       procedure Startup( H : THAL ) ; override ;
                       function FiP : TUOS_File_Processor ; override ;
                       function HMC : TUOS_Heap_Manager ; override ;
                       function MMC : TUOS_Memory_Manager ; override ;
                       function SSC : TUOS_System_Services ; override ;
                       function USC : TUOS_User_Security ; override ;
               end ; // TKernel

Our class has pointers to the various other executive components, and a pointer to the HAL. PID is the current process ID. We will discuss processes in a later article.

Here are the methods that return the various components:

function TKernel.HAL : THAL ;

begin
    Result := __HAL ;
end ;


function TKernel.FiP : TUOS_File_Processor ;

begin
    Result := _FiP ;
end ;


function TKernel.HMC : TUOS_Heap_Manager ;

begin
    Result := _HMC ;
end ;


function TKernel.MMC : TUOS_Memory_Manager ;

begin
    Result := _MMC ;
end ;


function TKernel.SSC : TUOS_System_Services ;

begin
    Result := _SSC ;
end ;


function TKernel.USC : TUOS_User_Security ;

begin
    Result := _USC ;
end ;

All that these methods do is return the component pointers from our instance data.

Now to our implementation of the Halt method:

var Buf : string[ 63 ] ;

procedure TKernel.Halt( C : longint ) ;

begin
    if( HAL.Console <> nil ) then
    begin
        Buf := inttostr( C ) ;
        Buf := 'Error ' + Buf + #0 ;
        HAL.Console.Output( @Buf[ 1 ], -1 ) ;
    end ;
    HAL.Halt ;
end ;

Buf is a global static string so that we don't use the heap (which may not be available), or any extra stack. The method simply converts the integer value to a human-readable string and outputs it on the system console. Then the HAL's halt method is called.

The Purpose of Startup
As mentioned previously, it is the job of the kernel to marshal the other executive components. This is phase I of the UOS startup, or the "executive startup". All executive components must be loaded and set up before programs can start running. Once the executive is set up, phase II starts - known as the "system startup". Although it is initiated by the executive, that part of startup happens via programs outside of the executive. The startup process is accomplished via the Startup method.

Here is the first few lines of code in the Kernel's Startup procedure.

procedure TKernel.Startup( H : THAL ) ;

var Buffer : PChar ;
    E : TUnified_Exception ;
    FS : TUOS_File_System ;
    FSH : FS_Header ;
    Bootstrap_Fixed : FS_Boot_Record ;
    S : TCOM_Store64 ;
    M : TUOS_Managed_Store ;
    Boot : TDevice_Info ;
    Boot_Name : string[ 255 ] ;
    _S, _T : longint ;

begin
    // Setup
    __HAL := H ;

    // Mount boot device...
    // Get boot store...
    Boot := H.Boot_Device ;
    Boot_Name := Device_Name( Boot ) ;
    S := Get_Store_For_Device( H, Boot_Name ) ;
    if( S = nil ) then
    begin
        Halt( UOSErr_No_Boot_Device ) ; // No boot device
    end ;
    Boot_Name := Boot_Name + ':'#0 ; // Make sure it ends with a colon and null
    setlength( Boot_Name, length( Boot_Name ) - 1 ) ; // Hide null

The startup method takes an instance of a HAL and saves that to our instance data. We then ask the HAL for the boot device. If the HAL returns null, it means we have no boot device. Note: this is a purely theoretical situation that shouldn't ever happen. But, part of programming is anticipating these theoretical situations and handling them. Some things must be assumed, but we want to reduce the number of assumptions made. As an example, we assume that the HAL passed to us is not null. But if it is, we don't even have a way to notify the user, so checking for the situation is pointless. If the HAL is null, the code will die and nothing will happen. There's not much we can do about that. If there is no boot device, we call the Halt method with the appropriate error code. As shown above, the Halt routine will display that numeric value and halt. Finally, we append a colon and a null to the end of the boot name. We will see the reason for the colon later on. The null is so we can pass the string to a function that needs a null-terminated string. But we reduce the length of the string so that the null is not considered part of the string otherwise.

The next step is to create the class instance for the file system on the boot device. A given boot device might have one of many possible file systems. How do we decide which file system to use? We don't want to limit the possible file systems that UOS can use or boot from. We also don't want to include every possible file system in the kernel (which isn't even possible since new file systems could be released after we release UOS). We solve this situation by making sure that the File System component for the appropriate file system is included on the store. Thus the kernel can simply load the component from any store and use it. This does present a problem though - without having a file system to use in finding the file system component file, how do we load it? So, UOS includes a pointer to the file system inside the boot block, which is the offset on the store for the file's data. A length is also included for the component. This requires that the file's data be contiguous since we cannot follow extent chains (or any other structure) without the file system component. Here is the updated boot block structure:

type FS_Boot_Record = packed record // First 60 bytes of store header
                          Bootstrap1 : array[ 0..15 ] of byte ;
                          Header : TStore_Address64 ; // Address of FS header
                          HAL_Position : TStore_Address64 ;
                          HAL_Length : longint ;
                          FS_Position : TStore_Address64 ;
                          FS_Length : longint ;
                          Bootstrap_Position : TStore_Address64 ;
                          Bootstrap_Length : longint ;
                      end ;

FS_Position is the store offset of the file system component and FS_Length is the file system component length. Bootstrap_Position and Bootstrap_Length perform the same function for the secondary bootstrap program image. The UOS secondary bootstrap is the Init program, which we discussed in previous articles.

There are two other executive components that need to be loaded before we can use any file system component - the MMC and HMC. So we also need position and lengths for those components. But rather than use up space in the primary bootstrap block, we will place them in the File System header. What if the store has a different file system than the UOS file system? It may not have a file system header, or its header may not be compatible with the UOS FS header. In such cases, making the store bootable for UOS means the creation of a file that contains the header data and then pointing to that file's data. Here's the updated file system header:

FS_Header = packed record // 240 bytes
                ID : smallint ; // 255                                     // 0-1
                ID1 : byte ; // 135                                        // 2
                Version : byte ; { UOSFS format version 10 = V1.0 }        // 3
                AT_Offset : TStore_Address64 ; { Current AT location }     // 4-11
                AT_Size : TStore_Size64 ; { AT size }                      // 12-19
                Flags : int64 ;                                            // 20-27
                Clustersize : cardinal ; // Volume clustersize             // 28-31
                Folder_Clustersize : cardinal ; // Default folder file clustersize // 32-35
                Root : TStore_Address64 ; // Root folder header            // 36-43
                Volume_Label : string[ 127 ] ;                             // 44-171
                Password : string[ 31 ] ;                                  // 172-203
                OS_Position : TStore_Address64 ;                           // 204-211
                OS_Length : longint ;                                      // 212-215
                MMC_Position : TStore_Address64 ;                          // 216-223
                MMC_Length : longint ;                                     // 224-227
                HMC_Position : TStore_Address64 ;                          // 228-235
                HMC_Length : longint ;                                     // 236-239
            end ;

The last 6 items in the structure are the new additions. The OS pair points to the kernel component, the MMC pair points to the Memory Management component, and the HMC pair points to the Heap Management component. Again, all of these components must be contiguous. If the position or length is 0, then the disk has not been made bootable for UOS - although it could be bootable for Init, which could then make the store bootable for UOS.

Here is the code for loading the file system from the store.

    // Load File System for boot device...
    Buffer := allocmem( S.Min_Storage ) ;
    S.Read_Data( Buffer^, 0, S.Min_Storage, E ) ;
    if( E <> nil ) then
    begin
        if( H.Console <> nil ) then
        begin
            H.Console.Output( PChar( E.Error_Text( _S, _T ) + CRLF ), -1 ) ;
        end ;
        H.Halt ;
    end ;
    move( Buffer[ 0 ], Bootstrap_Fixed, sizeof( Bootstrap_Fixed ) ) ;

If there is an error on the read, we display it and halt. Otherwise, we copy the boot block into the Bootstrap_Fixed structure.

    FS := TUOS_File_System( Get_Image( H, S, Bootstrap_Fixed.FS_Position, Bootstrap_Fixed.FS_Length, 0 ) ) ;
    if( FS = nil ) then // None specified on boot device
    begin
        if( H.Console <> nil ) then
        begin
            H.Console.Output( 'Could not load FS', -1 ) ;
        end ;
        H.Halt ;
    end ;

Then we load the file system via the Get_Image function. If it returns nil, then the file system could not be loaded, so we report the problem and halt.

Here is the code for Get_Image:

function Get_Image( H : THAL ; S : TCOM_Store64 ; Root, Len : int64 ; O6 : byte ) : TObject ;

var Address : int64 ;
    P : pointer ;
    UEC : TUnified_Exception ;

begin
    Result := nil ;
    if( ( Root <> 0 ) and ( Len > 0 ) and ( ( Root and 1 ) = 0 ) and ( ( Len and 1 ) = 0 ) ) then
    begin
        // Round length up to full sector size
        Len := ( Len + S.Min_Storage - 1 ) and ( not ( S.Min_Storage - 1 ) ) ;

        // Allocate storage and read into memory...
        Address := H.Allocate( 0, Len ) ;
        if( Address <> 0 ) then
        begin
            P := pointer( Address ) ;
            S.Read_Data( P^, Root, Len, UEC ) ;
            if( UEC = nil ) then // Read into memory okay
            begin
                PChar( P )[ 6 ] := char( O6 ) ;
                Result := TObject( H.Execute( Address ) ) ;
            end ;
        end ;
    end ;
end ; // Get_Image

Get_Image loads an image from the store, executes its startup code (which should return an instance of a class), and returns the resulting data. If something goes wrong, nil is returned. H is an instance of the HAL, S is the store, Root is the offset on the store, Len is the length of the item (in bytes), and O6 is a special code. First we check to make sure that the position and length are believable values. For instance, they cannot be odd values. We request some RAM from the HAL and, if successful, read from the store into that buffer. Then we tell the HAL to execute the code. Why don't we jump directly to the loaded code ourselves? This allows us to be agnostic of the format of the executable image, which may differ depending upon the hardware platform on which we are running. The setting of offset 6 with the O6 value is generally ignored, however, it is used by some simulators (such as UOSSim).

In the next article, we will look at the Init code for making a store bootable for UOS.