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

Finishing the File System Class

Support Methods
In this article, we will finish up the methods for our file system class. The first two are internal routines that are used by the API methods.

The Parse_Filespec method parses a file/path name. It returns the file name portion of the passed string, and sets the Directory parameter (passed by reference) to a file object for the folder specified in the passed filespec. If the path doesn't exist, the returned Directory is nil (and an error is set). In any case, the parsed filename is returned.

function TUOS_Native_File_System.Parse_Filespec( Filespec : string ;
    var Directory : TFS_File ) : string ;

var Dummy : integer ;

begin
    Directory := nil ;
    Result := '' ;
    if( copy( Filespec, 1, 1 ) = '\' ) then
    begin
        Filespec := copy( Filespec, 2, length( Filespec ) ) ;
    end ;
    if( Filespec = '' ) then
    begin
        Set_Last_Error( Create_Exception( UOS_File_System_Invalid_Filename, nil ) ) ;
        exit ;
    end ;
    Dummy := length( Filespec ) ;
    while( ( Dummy > 0 ) and ( Filespec[ Dummy ] <> '\' ) ) do
    begin
        dec( Dummy ) ;
    end ;
    Directory := Lookup_Directory( copy( Filespec, 1, Dummy ) ) ;
    Result := copy( Filespec, Dummy + 1, length( Filespec ) + 1 ) ;
    if( Directory = nil ) then
    begin
        Set_Last_Error( Create_Exception( UOS_File_System_Path_Not_Found, nil ) ) ;
        exit ;
    end ;
end ; // TUOS_Native_File_System.Parse_Filespec

The code looks for the last backslash in the filespec. To the right of that is the filename; to the left is the path. It calls Directory_Lookup to get the folder file for the path, checks for a failure, and returns the file name from filespec.

procedure TUOS_Native_File_System.Destroy_File( Sender : TObject ) ;

var F : TUOS_Native_File ;

begin
    F := TUOS_Native_File( Sender ) ;
    if( F.Directory <> nil ) then
    begin
        F.Directory.Detach ;
    end ;
    if( F = _Root ) then
    begin
        _Root := nil ;
    end else
    if( F = _Store_Folder ) then
    begin
        _Store_Folder := nil ;
    end ;
end ;

This method is a callback that happens when a file instance is destructed. The point here is to make sure our internal state is consistent. For instance, if the root folder file instance is destructed, we set _Root to nil.

API
We've covered some of the API methods in previous articles. Here, we will address the rest. These API methods deal directly with individual files.

The Lookup function returns file names, but returns no information about those files. Get_File_Info returns detailed information about the file passed to the routine. This must be a fully-qualified filename, including the path.

function TUOS_Native_File_System.Get_File_Info( Name : Pchar ;
    Stream : longint ) : TUOS_File_Info ;

var Directory : TFS_File ;
    F : TFS_File ;
    H : TUOS_File_Header ;
    Index : integer ;
    S : string ;
    _S : TUnicode_String ;

begin
    // Setup...
    fillchar( Result, sizeof( Result ), 0 ) ;
    Set_Last_Error( nil ) ;

    // Parse specification...
    S := Name ;
    if( S = '' ) then
    begin
        Set_Last_Error( Create_Exception( UOS_File_System_Invalid_Filename, nil ) ) ;
        exit ; // No context and no file spec
    end ;
    S := Parse_Filespec( S, Directory ) ;
    if( Directory = nil ) then
    begin
        Set_Last_Error( Create_Exception( UOS_File_System_Path_Not_Found, nil ) ) ;
        exit ; // Directory not found
    end ;

    _S := TUnicode_String.Create ;
    try
        _S.Assign_From_String( S, 3 ) ;
        _S.Lowercase ;
        Index := 0 ;
        while( true ) do
        begin
            inc( Index ) ;
            if( Directory.Read( 0, ( Index - 1 ) * sizeof( H ), sizeof( H ), H ) = 0 ) then
            begin
                // No more entries in this directory...
                Set_Last_Error( Create_Exception( UOS_File_System_File_Not_Found, nil ) ) ;
                exit ;
            end ;
            if( ( H.Name <> 0 ) and ( ( H.Flags and FAF_DELETED ) = 0 ) ) then
            begin // Not a null entry or deleted file
                if( Matched_Strings( _S, Get_String( H.Name ) ) ) then
                begin
                    Result.Size := H.Size ;
                    Result.EOF := H.EOF ;
                    Result.Uncompressed_Size := H.Uncompressed_Size ;
                    Result.Clustersize := H.Clustersize ;
                    Result.Record_Size := H.Record_Size ;
                    Result.Creation := H.Creation ;
                    Result.Last_Modified := H.Last_Modified ;
                    Result.Last_Backup := H.Last_Backup ;
                    Result.Last_Access := H.Last_Access ;
                    Result.Expiration := H.Expiration ;
                    Result.Creator := H.Creator ;
                    Result.Owner := H.Owner ;
                    Result.Flags := H.Flags ;
                    Result.Version_Limit := H.Version_Limit ;
                    Result.Location := 0 ;
                    if( ( H.Flags and FAF_PLACED ) <> 0 ) then
                    begin
                        Result.Location := H.Clusters[ 0 ] ;
                    end ;
                    if( Stream > 0 ) then
                    begin
                        F := Create_File_Instance( Directory, Index - 1 ) ;
                        try
                            F.Attach ;
                            F.Header := H ;
                            F.Store := _Store ;
                            if( F.Stream_Name( Stream ) = 0 ) then
                            begin // Stream not defined
                                Set_Last_Error( Create_Exception( UOS_File_System_Illegal_Operation,
                                    nil ) ) ;
                                exit ;
                            end ;
                            Result.Size := F.Get_Stream_Size( Stream ) ;
                            Result.EOF := Result.Size ;
                        finally
                            F.Detach ;
                        end ;
                    end ;
                    exit ;
                end ;
            end ; // if( H.Name <> 0 )
        end ;  // while( true )
    finally
        _S.Free ;
    end ;
end ; // TUOS_Native_File_System.Get_File_Info

The function is simple. It calls Lookup_Directory, then reads through the file entries in the folder file. Once the file is found, we fill the result with the data from the header. The result is a TUOS_File_Info structure:
     TUOS_File_Info = packed record
                          Size : int64 ; // Size on disk
                          EOF : int64 ; // Logical size
                          Uncompressed_Size : int64 ;
                          Clustersize : cardinal ;
                          Record_Size : cardinal ;
                          Creation : int64 ; // Creation date
                          Last_Modified : int64 ; // Last write date
                          Last_Backup : int64 ;
                          Last_Access : int64 ; // Last read/open date
                          Expiration : int64 ;
                          Creator : cardinal ;
                          Owner : cardinal ;
                          Flags : int64 ;
                          Version_Limit : longint ;
                          Location : int64 ;
                      end ;

The fields in this record match the corresponding data in the header, with the exception of Location, which is returned as 0 unless the file is placed, in which case, it is the location of the file's data on the store. The Stream parameter indicates which file stream's size is return in Size. EOF is also set to the stream's size if a stream other than 0 is requested (otherwise EOF comes from the header and reflects stream 0).

Set_File_Info is the inverse of Get_File_Info. It assigns the values from TUOS_File_Info structure to the file header. The Size value affects the passed stream instead of the header (unless the stream is 0). Two instances of TUOS_File_Info are passed. Info contains the actual data to assign to the file. Flags indicates which items are to be assigned. If the item in Flags is 0, then the corresponding value is used from Info. If the item in Flags is non-zero, then the value in Info is used. Without using Flags, you'd have to make sure that Info contained all the proper values for all fields. And we can't use a value of 0 in Info to indicate non-used values because 0 may be the value that we want to assign (for instance, setting the size to 0).

function TUOS_Native_File_System.Set_File_Info( Name : Pchar ;
    Stream : longint ; Info, Flags : TUOS_File_Info ) : TUnified_Exception ;

var Directory : TFS_File ;
    Dummy, Dummy1 : integer ;
    F : TFS_File ;
    FileSpec, S : string ;
    H : TUOS_File_Header ;
    Index : integer ;
    _S : TUnicode_String ;

begin
    // Setup...
    if( not Mounted ) then
    begin
        Set_Last_Error( Create_Exception( UOS_File_System_Not_Mounted, nil ) ) ;
        Result := Last_Error ;
        exit ; 
    end ;
    Result := nil ;
    Set_Last_Error( nil ) ;

    // Parse specification...
    S := Name ;
    if( S = '' ) then
    begin
        Set_Last_Error( Create_Exception( UOS_File_System_Invalid_Filename, nil ) ) ;
        Result := Last_Error ;
        exit ; // No context and no file spec
    end ;
    S := Parse_Filespec( S, Directory ) ;
    FileSpec := S ;
    if( Directory = nil ) then
    begin
        Set_Last_Error( Create_Exception( UOS_File_System_Path_Not_Found, nil ) ) ;
        Result := Last_Error ;
        exit ; // Directory not found
    end ;

    S := lowercase( S ) ;
    _S := TUnicode_String.Create ;
    try
        _S.Assign_From_String( S, 3 ) ;
        _S.Lowercase ;
        Index := 0 ;
        while( true ) do
        begin
            inc( Index ) ;
            if( Directory.Read( 0, ( Index - 1 ) * sizeof( H ), sizeof( H ),
              H ) = 0 ) then
            begin
                // No more entries in this directory...
                Set_Last_Error( Create_Exception( UOS_File_System_File_Not_Found,
                    nil ) ) ;
                Result := Last_Error ;
                exit ;
            end ;
            if( ( H.Name <> 0 ) and ( ( H.Flags and FAF_DELETED ) = 0 ) ) then
            begin // Not a null entry or deleted file
                if( Matched_Strings( _S, Get_String( H.Name ) ) ) then
                begin
                    Dummy := Info.Clustersize ;
                    if( Dummy = 0 ) then
                    begin
                        Dummy := Store.Min_Storage ;
                    end ;
                    Dummy1 := H.Clustersize ;
                    if( Dummy1 = 0 ) then
                    begin
                        Dummy1 := Store.Min_Storage ;
                    end ;
                    if(
                        ( Flags.Clustersize <> 0 )
                        and
                        ( Dummy <> Dummy1 )
                      ) then
                    begin
                        if( H.Size <> 0 ) then
                        begin // Can't set clustersize on a file that already has data
                            Set_Last_Error( Create_Exception( UOS_File_System_Illegal_Operation,
                                nil ) ) ;
                            Result := Last_Error ;
                            exit ;
                        end ;
                        H.Clustersize := Info.Clustersize ;
                    end ;
                    if( Flags.Record_Size <> 0 ) then
                    begin
                        H.Record_Size := Info.Record_Size ;
                    end ;
                    if( Flags.Creation <> 0 ) then
                    begin
                        H.Creation := Info.Creation ;
                    end ;
                    if( Flags.Last_Modified <> 0 ) then
                    begin
                        H.Last_Modified := Info.Last_Modified ;
                    end ;
                    if( Flags.Last_Backup <> 0 ) then
                    begin
                        H.Last_Backup := Info.Last_Backup ;
                    end ;
                    if( Flags.Last_Access <> 0 ) then
                    begin
                        H.Last_Access := Info.Last_Access ;
                    end ;
                    if( Flags.Expiration <> 0 ) then
                    begin
                        H.Expiration := Info.Expiration ;
                    end ;
                    if( Flags.Creator <> 0 ) then
                    begin
                        H.Creator := Info.Creator ;
                    end ;
                    if( Flags.Owner <> 0 ) then
                    begin
                        H.Owner := Info.Owner ;
                    end ;
                    if( Flags.Flags <> 0 ) then
                    begin
                        H.Flags := Info.Flags
                                   and
                                   ( not FAF_Contiguous ) ;
                    end ;
                    if( Flags.Version_Limit <> 0 ) then
                    begin
                        H.Version_Limit := Info.Version_Limit ;
                    end ;
                    if( Flags.EOF <> 0 ) then
                    begin
                        if( Info.EOF > H.Size ) then
                        begin
                            Info.EOF := H.Size ;
                        end ;
                        H.EOF := Info.EOF ;
                    end ;
                    F := Create_File_Instance( Directory, Index - 1 ) ;
                    try
                        F.Attach ;
                        F.Header := H ;
                        F.Store := _Store ;
                        if(
                            ( Stream > 0 )
                            and
                            ( F.Stream_Name( Stream ) = 0 )
                          ) then // Stream not defined
                        begin
                            Set_Last_Error( Create_Exception( UOS_File_System_Illegal_Operation,
                                nil ) ) ;
                            exit ;
                        end ;
                        if( Flags.Flags <> 0 ) then
                        begin
                            F.Set_Contiguous( ( ( Info.Flags and FAF_Contiguous ) <> 0 ) ) ;
                        end ;
                        if( F.Last_Error <> nil ) then
                        begin
                            Set_Last_Error( Create_Exception( UOS_File_System_Illegal_Operation,
                                F.Last_Error ) ) ;
                            Result := Last_Error ;
                            exit ;
                        end ;
                        if( Flags.Size <> 0 ) then
                        begin
                            F.Set_Stream_Size( Stream, Info.Size ) ;
                        end ;
                        if( Directory.Write( 0, ( Index - 1 ) * sizeof( H ),
                          sizeof( H ), F.Header ) = 0 ) then
                        begin
                            Set_Last_Error( Create_Exception( UOS_File_System_Write_Error,
                                Directory.Last_Error ) ) ;
                            Result := Last_Error ;
                            exit ;
                        end ;
                        if( Directory.Last_Error <> nil ) then
                        begin
                            Set_Last_Error( Create_Exception( UOS_File_System_Write_Error,
                                Directory.Last_Error ) ) ;
                            Result := Last_Error ;
                            exit ;
                        end ;
                    finally
                        F.Detach ;
                    end ;
                    exit ;
                end ; // if( Matched_Strings( _S, Get_String( H.Name ) ) )
            end ; // if( H.Name <> 0 )
        end ; // while( true )
    finally
        _S.Free ;
    end ;
end ; //  TUOS_Native_File_System.Set_File_Info

Before we get into the various API methods, we need to address the possibility of multiple instances of a given file being open at the same time. We don't want to have multiple instances for the same file or else we risk file corruption. This wasn't an issue for our Init and Rebuild methods since they are, by definition, single-user file instances. You may recall the class definition included an Open_Files list. This is how we keep track of which files are already open, and we will reuse an already-open file instance when needed, instead of creating an additional instance. In order to distinguish the files and find matches, we need a means of identifying each file. We do this by adding a new item to the TUOS_Native_File class called Directory_Cluster which indicates the first allocation cluster of the parent directory, and the index of the file header is stored in the Directory_Index. These two items are sufficient to uniquely identify any file on the store. Each time we want a file instance, we call Create_File_Instance:

function TUOS_Native_File_System.Create_File_Instance( Parent : TUOS_Native_File ;
    Index : cardinal ) : TFS_File ;

var Dummy : integer ;

begin
    for Dummy := 0 to Open_Files.Count - 1 do
    begin
        Result := TFS_File( Open_Files[ Dummy ] ) ;
        if(
            ( Result.Directory_Cluster = Parent.Header.Clusters[ 0 ] )
            and
            ( Result.Directory_Index = Index )
          ) then
        begin
            exit ;
        end ;
    end ;
    Result := TFS_File.Create( self ) ;
    Result.Store := _Store ;
    Result.Directory_Index := Index ;
    Result.Directory_Cluster := Parent.Header.Clusters[ 0 ] ;
    Result.Directory := Parent ;
    if( Parent <> nil ) then
    begin
        Parent.Attach ;
    end ;
end ;

Given the parent directory object, we can get the first cluster of the directory, and with the header index we can look through the currently open files to see if we can find the requested file. If so, we return the instance. If not, we construct a new instance and set the cluster and index values for it. Note that we are returning a TFS_File class instance instead of TUOS_Native_File. Here is that class:
     TFS_File = class( TUOS_Native_File )
                    public // Constructors and destructors...
                        constructor Create( _FS : TUOS_Native_File_System ) ;
                        destructor Destroy ; override ;

                    protected // Overrides...
                        procedure Update_Header ; override ;

                    private // Instance data...
                        FS : TUOS_Native_File_System ;
                end ;

constructor TFS_File.Create( _FS : TUOS_Native_File_System ) ;

begin
    inherited Create ;

    FS := _FS ;
    if( FS <> nil ) then
    begin
        FS.Open_Files.Add( self ) ;
    end ;
end ;


destructor TFS_File.Destroy ;

var Dummy : integer ;

begin
    if( FS <> nil ) then
    begin
        Dummy := FS.Open_Files.Indexof( self ) ;
        if( Dummy <> -1 ) then
        begin
            FS.Open_Files.Remove( self ) ;
        end ;
    end ;
    
    inherited Destroy ;
end ;


procedure TFS_File.Update_Header ;

var Dummy : integer ;
    F : TFS_File ;

begin
    inherited Update_Header ;

    if( ( FS <> nil ) and ( ( Header.Flags and FAF_DIRECTORY ) <> 0 ) ) then
    begin
        for Dummy := 0 to FS.Open_Files.Count - 1 do
        begin
            F := TFS_File( FS.Open_Files[ Dummy ] ) ;
            if( F.Directory = self ) then
            begin
                F.Directory_Cluster := Header.Clusters[ 0 ] ;
            end ;
        end ;
    end ;
end ;

TFS_File is a descendent of TUOS_Native_File, and we use instances of this class everywhere in the File System, except for the Init and Rebuild methods. The constructor adds the instance to the Open_Files list. The destructor removes the instance from Open_Files.
Update_Header overrides the ancestor method so that we can do additional processing whenever the file header is updated. If the first cluster of a directory file changes, then we have to update the child object instances to keep them synchronized with any header changes.

function TUOS_Native_File_System.Create_File( Filespec : PChar ;
    Info : TUOS_File_Info ) : TUnified_Exception ;

var Directory : TFS_File ;
    S : string ;
    F : TFS_File ;
    Free_Index : longint ;
    Header : TUOS_File_Header ;
    Index : longint ;

begin
    if( not Mounted ) then
    begin
        Set_Last_Error( Create_Exception( UOS_File_System_Not_Mounted, nil ) ) ;
        Result := Last_Error ;
        exit ;
    end ;
    Set_Last_Error( nil ) ;
    Result := nil ;

    // Parse specification...
    S := Filespec ;
    S := Parse_Filespec( S, Directory ) ;
    if( ( S = '' ) or ( Directory = nil ) ) then
    begin
        Result := Last_Error ;
        exit ;
    end ;

    Directory.Attach ;
    try
        // Find file...
        Index := Find_File( S, Directory, Free_Index ) ;
        if( _Last_Error <> nil ) then
        begin
            if(
                ( _Last_Error.Get_Facility = UOS_File_System_Facility_ID )
                and
                ( _Last_Error.Get_Error = UOS_File_System_File_Not_Found )
              ) then
            begin
                Set_Last_Error( nil ) ;
                Index := -1 ;
            end else
            begin
                exit ;
            end ;
        end ;

We make sure the file system is mounted, clear any exceptions, parse the file specification, and then see if the file already exists by calling Find_File.

        if( Index >= 0 ) then
        begin
            // Mark existing file as deleted (Delete_File does physical deletion)...
            if( Directory.Read( 0, ( Index ) * sizeof( Header ), sizeof( Header ),
              Header ) = 0 ) then
            begin
                Set_Last_Error( Create_Exception( UOS_File_System_Read_Error,
                    Directory.Last_Error ) ) ;
                Result := Last_Error ;
                exit ;
            end ;
            Header.Flags := Header.Flags or FAF_DELETED ;
            if( Directory.Write( 0, ( Index ) * sizeof( Header ), sizeof( Header ),
              Header ) = 0 ) then
            begin
                Set_Last_Error( Create_Exception( UOS_File_System_Write_Error,
                    Directory.Last_Error ) ) ;
                Result := Last_Error ;
                exit ;
            end ;
        end ;

If the returned index is -1, the file doesn't exist. Otherwise, it is the index of the file in the directory. If the file already exists, we need to mark it as deleted (but not physically delete it).

        // Make sure clustersize if correct...
        if( Info.Clustersize < Store.Min_Storage ) then
        begin
            Info.Clustersize := Store.Min_Storage ;
        end ;
        if( ( Info.Flags and FAF_DIRECTORY ) <> 0 ) then
        begin
            if( Info.Clustersize < _Header.Folder_Clustersize ) then
            begin
                Info.Clustersize := _Header.Folder_Clustersize ;
            end ;
        end ;
        Info.Clustersize := Info.Clustersize and ( not ( Store.Min_Storage - 1 ) ) ;

        // Create and write header...
        if( Free_Index < 0 ) then
        begin
            Free_Index := Directory.Get_Stream_Size( 0 ) div sizeof( Header ) ;
        end ;
        fillchar( Header, sizeof( Header ), 0 ) ;
        Header.Clustersize := Info.Clustersize ;
        Header.Record_Size := Info.Record_Size ;
        Header.Creation := Info.Creation ;
        Header.Last_Modified := Info.Last_Modified ; // Last write date
        Header.Last_Backup := Info.Last_Backup ;
        Header.Last_Access := Info.Last_Access ; // Last read/open date
        Header.Expiration := Info.Expiration ;
        Header.Creator := Info.Creator ;
        Header.Owner := Info.Owner ;
        Header.Flags := Info.Flags ;
        Header.Version_Limit := Info.Version_Limit ;
        Header.Name := _String_Table.Add_String( S ) ;
        F := Create_File_Instance( Directory, Free_Index ) ;

We create the file instance, using the first free index returned by Find_File. We set various items in the header, but setting the size requires special handling.

        try
            F.Attach ;
            F.Header := Header ;
            F.Store := Store ;
            F.Set_Stream_Size( 0, Info.Size ) ;
            if(
                ( F.Last_Error <> nil )
                or
                ( F.Get_Stream_Size( 0 ) < Info.Size )
              ) then
            begin
                Set_Last_Error( Create_Exception( UOS_File_System_File_Creation_Error,
                    F.Last_Error ) ) ;
                Result := Last_Error ;
                exit ;
            end ;
            if( Info.EOF > Header.Size ) then
            begin
                Info.EOF := Header.Size ;
            end ;
            F.Header.EOF := Info.EOF ;
            if( Directory.Write( 0, Free_Index * sizeof( F.Header ),
              sizeof( F.Header ), F.Header ) = 0 ) then
            begin
                Set_Last_Error( Create_Exception( UOS_File_System_Write_Error,
                    Directory.Last_Error ) ) ;
                Result := Last_Error ;
                exit ;
            end ;
            F.Dirty := False ;
        finally
            F.Detach ;
        end ;
    finally
        Directory.Detach ;
    end ;
end ; // TUOS_Native_File_System.Create_File

We make sure the file is pre-extended to the requested length and verify that the EOF isn't past the physical end of file. Finally, we write the header, clear the dirty bit in the file, and then detach the file and directory file.

function TUOS_Native_File_System.Delete_File( Filespec : PChar ) : TUnified_Exception ;

var Directory : TFS_File ;
    Dummy : integer ;
    S : string ;
    F : TFS_File ;
    Header : TUOS_File_Header ;
    Free_Index, Index : integer ;

begin
    if( not Mounted ) then
    begin
        Set_Last_Error( Create_Exception( UOS_File_System_Not_Mounted, nil ) ) ;
        Result := Last_Error ;
        exit ; 
    end ;
    Result := nil ;
    Set_Last_Error( nil ) ;

    // Parse specification...
    S := Filespec ;
    S := Parse_Filespec( S, Directory ) ;
    if( ( S = '' ) or ( Directory = nil ) ) then
    begin
        Result := Last_Error ;
        exit ;
    end ;

    try
        // Find file...
        Index := Find_File( S, Directory, Free_Index ) ;
        Directory.Attach ;
        if( Index >= 0 ) then
        begin
            if( Directory.Read( 0, ( Index ) * sizeof( Header ), sizeof( Header ),
              Header ) = 0 ) then
            begin
                Set_Last_Error( Create_Exception( UOS_File_System_Read_Error,
                    Directory.Last_Error ) ) ;
                Result := Last_Error ;
                exit ;
            end ;

            // Release space allocated for file...
            F := Create_File_Instance( Directory, Index ) ;
            try
                F.Attach ;
                F.Header := Header ;
                F.Store := Store ;
                for Dummy := 0 to F.Max_Stream do
                begin
                    F.Set_Stream_Size( Dummy, 0 ) ;
                end ;
            finally
                F.Detach ;
            end ;

            // Mark the directory entry as unused...
            fillchar( Header, sizeof( Header ), 0 ) ;
            if( Directory.Write( 0, Index * sizeof( Header ), sizeof( Header ),
              Header ) = 0 ) then
            begin
                Set_Last_Error( Create_Exception( UOS_File_System_Write_Error,
                    Directory.Last_Error ) ) ;
                Result := Last_Error ;
            end ;
            exit ;
        end ; // if( Index >= 0 )

        Set_Last_Error( Create_Exception( UOS_File_System_File_Not_Found, nil ) ) ;
        Result := Last_Error ;
    finally
        Directory.Detach ;
    end ;
end ; // TUOS_Native_File_System.Delete_File

As in the previous method, we make sure we're mounted, clear exceptions, parse the file specification, and look up the file.
This method physically deletes the file, by clearing out the header entry and releasing the space for the file on the store. We create an instance of the file, loop through the streams and set them all to 0 length.

function TUOS_Native_File_System.Rename( Filespec,
    New_Name : PChar ) : TUnified_Exception ;

var Directory : TFS_File ;
    Header : TUOS_File_Header ;
    Free_Index, Index : longint ;
    N, O : cardinal ;
    S : string ;

begin
    if( not Mounted ) then
    begin
        Set_Last_Error( Create_Exception( UOS_File_System_Not_Mounted, nil ) ) ;
        Result := Last_Error ;
        exit ;
    end ;
    Result := nil ;
    Set_Last_Error( nil ) ;

    // Parse specification...
    if( ( length( New_Name ) = 0 ) or ( pos( '\', New_Name ) > 0 ) ) then
    begin
        Set_Last_Error( Create_Exception( UOS_File_System_Invalid_Filename, nil ) ) ;
        Result := Last_Error ;
        exit ;
    end ;
    S := Filespec ;
    S := Parse_Filespec( S, Directory ) ;
    if( ( S = '' ) or ( Directory = nil ) ) then
    begin
        Result := Last_Error ;
        exit ;
    end ;
    Directory.Attach ;
    try
        if( S = New_Name ) then
        begin
            exit ; // No change
        end ;

        Index := Find_File( New_Name, Directory, Free_Index ) ;
        if( Index >= 0 ) then // New file already exists
        begin
            Set_Last_Error( Create_Exception( UOS_File_System_File_Already_Exists,
                nil ) ) ;
            Result := Last_Error ;
            exit ;
        end ;
        if(
            ( _Last_Error.Get_Facility <> UOS_File_System_Facility_ID )
            or
            ( _Last_Error.Get_Error <> UOS_File_System_File_Not_Found )
          ) then
        begin
            exit ;
        end ;
        Set_Last_Error( nil ) ;
        Index := Find_File( S, Directory, Free_Index ) ;
        if( Index < 0 ) then // Old file isn't there
        begin
            Set_Last_Error( Create_Exception( UOS_File_System_File_Not_Found, nil ) ) ;
            Result := Last_Error ;
            exit ;
        end ;

        if( Directory.Read( 0, ( Index ) * sizeof( Header ), sizeof( Header ),
          Header ) = 0 ) then
        begin
            Set_Last_Error( Create_Exception( UOS_File_System_Read_Error,
                Directory.Last_Error ) ) ;
            Result := Last_Error ;
            exit ;
        end ;
        N := _String_Table.Add_String( New_Name ) ;
        if( Last_Error <> nil ) then
        begin
            Result := Last_Error ;
            exit ;
        end ;
        O := Header.Name ;
        Header.Name := N ;
        if( Directory.Write( 0, ( Index ) * sizeof( Header ), sizeof( Header ),
          Header ) = 0 ) then
        begin
            Set_Last_Error( Create_Exception( UOS_File_System_Write_Error,
                Directory.Last_Error ) ) ;
            Result := Last_Error ;
            _String_Table.Delete_String( N ) ;
            exit ;
        end ;
        _String_Table.Delete_String( O ) ;
    finally
        Directory.Detach ;
    end ;
end ; // TUOS_Native_File_System.Rename

This method renames an existing file. As in the previous method, we make sure we're mounted, clear exceptions, parse the file specification, and look up the file.
We make sure that a file with the new name doesn't already exist. If it does, we exit with an error. If the old and new names are identical, we are done. We don't define an exception in this case because we have, in essence, succeded in renaming the file to its current name. We request a string index from the string table for the new name, release the old name, and set the Name field in the file header, and then update the header on the store.

function TUOS_Native_File_System.Move_File( Old_Filespec,
    New_Filespec : PChar ) : TUnified_Exception ;

var Directory, Directory1 : TFS_File ;
    Header : TUOS_File_Header ;
    Free_Index, Index, Index1 : longint ;
    New_Filename : string ;
    S : string ;

begin
    // Setup and sanity check...
    if( not Mounted ) then
    begin
        Set_Last_Error( Create_Exception( UOS_File_System_Not_Mounted, nil ) ) ;
        Result := Last_Error ;
        exit ;
    end ;
    Result := nil ;
    Set_Last_Error( nil ) ;

    // Parse specifications...
    S := Old_Filespec ;
    S := Parse_Filespec( S, Directory ) ;
    if( ( S = '' ) or ( Directory = nil ) ) then
    begin
        Result := Last_Error ;
        exit ;
    end ;

    New_Filename := New_Filespec ;
    if( copy( New_Filename, length( New_Filename ), 1 ) <> '\' ) then
    begin
        New_Filename := New_Filename + '\' ;
    end ;
    New_Filename := Parse_Filespec( New_Filename + S, Directory1 ) ;
    if( ( New_Filename = '' ) or ( Directory1 = nil ) ) then
    begin
        Result := Last_Error ;
        exit ;
    end ;

    if( Directory = Directory1 ) then // Moving to the same directory
    begin
        Set_Last_Error( Create_Exception( UOS_File_System_Source_Same_As_Destination, nil ) ) ;
        Result := Last_Error ;
        exit ;
    end ;

    // Lookup old file...
    Index := Find_File( S, Directory, Free_Index ) ;
    if( Index < 0 ) then // Old file isn't there
    begin
        Set_Last_Error( Create_Exception( UOS_File_System_File_Not_Found, nil ) ) ;
        Result := Last_Error ;
        exit ;
    end ;

    // Validate no existing (new) file ...
    Index1 := Find_File( S, Directory1, Free_Index ) ;
    if( Index1 >= 0 ) then // New file is already there
    begin
        Set_Last_Error( Create_Exception( UOS_File_System_File_Exists, nil ) ) ;
        Result := Last_Error ;
        exit ;
    end ;
    if(
        ( _Last_Error.Get_Facility = UOS_File_System_Facility_ID )
        and
        ( _Last_Error.Get_Error = UOS_File_System_File_Not_Found )
      ) then
    begin
        Set_Last_Error( nil ) ;
    end else
    begin
        exit ;
    end ;
    if( Free_Index < 0 ) then
    begin
        Free_Index := Directory1.Get_Stream_Size( 0 ) div sizeof( Header ) ;
    end ;

    // Get file header...
    if( Directory.Read( 0, Index * sizeof( Header ), sizeof( Header ),
      Header ) = 0 ) then
    begin
        Set_Last_Error( Create_Exception( UOS_File_System_Read_Error,
            Directory.Last_Error ) ) ;
        Result := Last_Error ;
        exit ;
    end ;

    // Update old header...
    Header.Flags := Header.Flags or FAF_COPYING ;
    if( Directory.Write( 0, Index * sizeof( Header ), sizeof( Header ),
      Header ) = 0 ) then
    begin
        Set_Last_Error( Create_Exception( UOS_File_System_Write_Error,
            Directory.Last_Error ) ) ;
        Result := Last_Error ;
        exit ;
    end ;
    Header.Flags := Header.Flags and ( not FAF_COPYING ) ;

    // Write to new directory...
    if( Directory1.Write( 0, Free_Index * sizeof( Header ), sizeof( Header ),
      Header ) = 0 ) then
    begin
        Set_Last_Error( Create_Exception( UOS_File_System_Write_Error,
            Directory1.Last_Error ) ) ;
        Result := Last_Error ;
        exit ;
    end ;

    // Erase from old directory...
    fillchar( Header, sizeof( Header ), 0 ) ;
    if( Directory.Write( 0, Index * sizeof( Header ), sizeof( Header ),
      Header ) = 0 ) then
    begin
        Set_Last_Error( Create_Exception( UOS_File_System_Write_Error,
            Directory.Last_Error ) ) ;
        Result := Last_Error ;
        exit ;
    end ;
end ; // TUOS_Native_File_System.Move_File

This final API method is used to quickly move a file to a new folder. We could simply create a new file in the destination location, copy the file contents, and then delete the original file. This method is superior in several respects:
  • The method is faster since we just move the file header instead of copying the file.
  • The method doesn't use any additional space on the store - the create/copy/delete process requires as much additional space as the original file by the time the copy is complete (although the extra space is returned to the store afterwards). On stores with little free space, the create/copy/delete process may not have enough room on the store to complete the operation.
  • The method retains the contiguous, placed, and other, flags. A create/copy/delete process can't preserve a placed file in place. Nor is there any guarantee that there is enough contiguous space on the store to keep the new file contiguous.
  • The method moves the file in such a way that if the file is already open, it stays open without affecting any program using it. The create/copy/delete process cannot do this.
It should be noted that a file can only be moved within the same store with this message. A move between stores will require the create/copy/delete process.
As in the other API methods, we make sure we're mounted, clear exceptions, parse the file specification, and look up the file.
Attempting to move from and to the same folder is an error. We make sure a file with the same name doesn't exist in the destination folder (which is an error). The destination file header has the FAF_COPYING flag set to indicate that the file is in the process of being moved. We clear the flag after the operation is complete. Then we zero the entry in the old directory. In this case, we don't set the old header to FAF_DELETED, because the file isn't deleted - it is just in a different place.

We aren't finished with our File System class yet. However, it now has all of the basic functionality required by UOS. The work remaining has to do with integrating with the File Processor, which we will address in a future article. After all this work, wouldn't it be nice if we could actually make use of it?
We will do that in the next article.