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

Init Command Mode

Command Line Basics
Before we get into the code for the Init command line processing, we should discuss how it is supposed to work. Remember, this is a text-only interface, so it will prompt the user, take what the user types, and then act on it. Anything the user types is considered a "command", which may consist of multiple parts, each delimited by a space. To make things easier on the user, so that they don't have to remember the exact syntax of each command, Init will allow them to type as little as one part of a command, and the program will prompt them for the next part. Let's take the disk directory command as an example. A full command has the format:
DISK DIRECTORY <drive> <folder>
where "drive" indicates which disk and "folder" indicates which folder to list a directory of. This command has 4 parts. The user can type the entire command or only one to three of the parts. If they only type "DISK", Init will prompt them like this:
Disk>
at which point, the user can type "DIRECTORY" (or the entire rest of the command). And so forth. To be nice, we will accept the minimal number of characters that uniquely identifies a command or subcommand. Obviously a disk or folder specification will have to be complete. Also, at any prompt, the user can type "HELP" or "?" to get a list of options available at that point. Further, once a command is completed (either by the user typing the entire thing, or specifying each part at a prompt), the user is returned to the last prompt that was presented. To back out of a given prompt level (to return to the earlier prompt), the user can type control-Z (hold the Ctrl key and press the "Z" key). Or control-C can be used to jump immediately back to the main Init prompt. The following are a few sample interactions that all do the same thing, which is to get a directory of the Store folder on DISKA0.

Init> DISK DIR DISKA0 Store
...
Init>

Init> DISK
Disk> DIR DISKA0 Store
...
Disk>

Init> DISK
Disk> DIR
Disk Dir (DISKA0) > DISKA0
Disk Dir DISKA0 (Root) > Store
...
Disk Dir DISKA0 (Root) >

"..." indicates output by Init based the entered command. The bold portions indicate what is typed by the user.

Device specifications
UOS physical device names contain the following parts:
<type><controller><unit>{<partition>}
For example, "DISKA0" indicates unit 0 on the first disk controller. The controller is A for the first controller, B for the second, and so forth. Note that the device specification doesn't indicate the type of controller - on a PC it could be a floppy disk, IDE, SCSI, SATA, Firewire, or USB disk (or something else). If the disk is partitioned, a letter code is added to the end. The first partition is A, the second is B, and so on.

TypeDescription
DISKAny random mass storage file-structured device not otherwise covered
NETNetwork interface
TERMAny physical terminal
VTERMAny virtual (network) terminal
The system console is always device specification TERMA0.

Keystroke processing
Init doesn't provide all possible features of UOS. So, it can be used to look at the contents of a folder, but not to edit them. But, when it isn't a bunch of extra effort, we will try to make the Init environment as much like the (command-line) UOS shell as possible. One area to which the approach applies is the processing of keystrokes. Terminals can operate in two modes: half-duplex and full-duplex. You see, when you type a character on a PC's keyboard, the character is not sent directly to the display. Instead, it is sent to the computer, which places the corresponding glyph on the screen. Likewise, with old-style terminals, the key that is pressed is sent to the computer, which then may send the character back for display on the terminal. This is called full-duplex mode. In half-duplex mode, the keys are sent to the computer, but nothing is sent back to the terminal (or screen). Because of this back-and-forth, the process is called an "echo" - that is, the character is echoed back to the sending terminal. Obviously, without the echo, the user can't see what they've typed. But sometimes we want that, such as when entering a password. In those cases, we turn the echo off until the user has entered the password. If simply deciding whether or not to echo a character back to a terminal was all that we needed to do, our keystroke processing would be very easy to implement. However, there are other issues to consider. For instance, what do we do about characters that have no corresponding glyph (such as all ASCII values below 32)? We could just echo those, but on many terminals those characters are invisible to the user. And since we don't know whether the connected terminal displays these characters or not, Init will treat them all as if the terminal doesn't. What that means is that, with a few exceptions, we will echo those characters as a caret (^) followed an ASCII character. The ASCII character is the one that, when combined with the Control key, generates that value. For instance control-A generates ASCII value 1, which we will echo to the terminal as ^A. That way, the user has a visual indicator of what he has typed. I mentioned exceptions. Here they are:

ASCII ValueKeystrokeDescription
0Nulls are ignored.
3ctrl+CEchoed as ^C, but it clears the input buffer and jumps back to the main Init prompt. It can also abort some processing.
7ctrl+GSounds a beep (or bell). Echoed back literally.
8ctrl+HDeletes the last typed character. Not echoed. See discussion later about deletion.
9ctrl+I (or Tab)Horizontal tab. Ctrl+I does the same thing as the Tab key. Echoed back literally.
10ctrl+JLinefeed. It moves the cursor down one line. This is a line delimiter, meaning that it ends a line of input. Echoed back as a cariage-return and line-feed so that the cursor is at the start of the next line.
12ctrl+LFormfeed. Used with printing terminals and printers. This is a line delimiter, meaning that it ends a line of input. Echoed back as a cariage-return and line-feed so that the cursor is at the start of the next line.
13ctrl+M (or Enter)Carriage return. It moves the cursor to the start of the line. This is a line delimiter, meaning that it ends a line of input. Echoed back as a cariage-return and line-feed so that the cursor is at the start of the next line.
15ctrl+OToggles the output state. Pressed once, all output to the terminal is ignored. Pressed again and it resumes. This is used when there is a lot of output being sent to the terminal that the user doesn't want to see, but also doesn't want to abort with a ^C.
17ctrl+QNot echoed. Resumes paused output.
18ctrl+REchoed as ^R. It is possible under some circumstances, especially with printing terminals or terminals with local echos, to have the input data not appear exactly as typed. ^R will redisplay the current input line (since the last delimiter) so that the user can verify what they typed.
19ctrl+SNot echoed. Pauses output. Unlike ^O, which essentially throws away the output, this will pause the output until ^Q is typed. This provides a way of seeing output that might otherwise scroll off the screen. It is also used by some terminals that cannot display the data as fast as it is sent and have to pause it until their internal buffer is empty.
20ctrl+TNot echoed. This displays a status line that allows the user to see what is happening during long pauses when Init is doing something. This is not treated as input and has no effect on the input buffer. It is responded to immediately even while Init is busy with some process. Note, however, that in the simulated environment, Init may not be notified immediately that ctrl+T has been typed.
21ctrl+UDeletes any pending input typed since the last delimiter.
26ctrl-ZEchoed as ^Z and jumps back to the last prompt.
27EscEchoed as $ and jumps back to the last prompt.

The terminal returned to us from the HAL doesn't handle any of this - it simply sends us what is typed on the keyboard and displays what we output to it. Therefore, we need to add a filter to the console to handle these special circumstances. We will do this by creating a class that will serve as our terminal and will wrap around a TTerminal instance. Here is the class definition and constructor for this class:

type TUOS_Terminal = class
                         public // Cosntructors and destructors...
                             constructor Create( T : TTerminal ) ;

                         private // Instance data...
                             Term : TTerminal ;

                             Buffered_Input : string ;
                             Buffered_Output : string ;
                             Type_Ahead_Buffer : string ;
                             Echo : boolean ;
                             In_Deletion : boolean ;
                             Toss_Output : boolean ;
                             Pause_Output : boolean ;
                             Video : boolean ;

                         public // Callbacks...
                             procedure Next_Char( Key : cardinal ) ;
                             procedure Switch_To_KB_Mode ;

                         public // API...
                             procedure Output( S : string ) ;
                             function Input_Line : string ;
                             function Peek : char ;
                     end ; // TUOS_Terminal

// Cosntructors and destructors...

constructor TUOS_Terminal.Create( T : TTerminal ) ;

begin
    inherited Create ;

    Term := T ;
    Term.OnNewChar := Next_Char ;
    Echo := True ;
end ;

The class is passed the terminal on construction. We make sure the echo is on and set a callback from the terminal. Let's examine the new methods in TTerminal:
function Input( var C : integer ) : boolean ;
    virtual ; stdcall ; abstract ;
procedure Clear_Typeahead ;
    virtual ; stdcall ; abstract ;
function Peek : char ;
    virtual ; stdcall ; abstract ;
procedure Poll ; virtual ; stdcall ; abstract ;

function Get_OnNewChar : TCharNotify ;
    virtual ; stdcall ; abstract ;
procedure Set_OnNewChar( Value : TCharNotify ) ;
    virtual ; stdcall ; abstract ;

public // Properties...
property OnNewChar : TCharNotify
    read Get_OnNewChar
    write Set_OnNewChar ;

Serial input involves a signal coming in from a device and being stored in a hardware buffer (often a device called a UART). Usually that buffer is a single character, although some hardware can buffer several characters per serial port. On some hardware, that is as far as the hardware goes. In such a case, the software has to regularly check the device to see if a character has come into the buffer. This checking of the buffer is called "polling". In other cases, the hardware will cause an interrupt when the character arrives. Upon interrupt, the current software is momentarily paused and an interrupt routine is run. There are advantages and disadvantages to both approaches, but UOS doesn't get to choose how the hardware handles this. So, we must both handle polling a terminal (synchronous input) and handling asychronous input to cover both possibilities. For asynchronous devices, the call to the Poll method does nothing. Otherwise, the Poll method will check for the input and, if found, call through the asynchronous interface to pass the data to the software. The asynchronous interface is via a delegation hook. These are callbacks to an object method. The OnNewChar delegation indicates which of the TUOS_Terminal methods is called when input happens on the terminal. Some older operating systems didn't process any characters from the terminal until the program wanted input. Characters typed when not in input mode were ignored and, thus, thrown away. All modern operating systems allow the user to type even when the system isn't asking for input.

Let's look at the Next_Char method, which processes the characters sent from the terminal.

procedure TUOS_Terminal.Next_Char( Key : cardinal ) ;

begin
    // Handle type-ahead...
    if( Current_Status <> Status_KB ) then // Not getting input
    begin
        case Key of
            3, 17, 19, 20: ; // Pass these along
            else
                begin
                    Type_Ahead_Buffer := Type_Ahead_Buffer + chr( Key ) ;
                    exit ;
                end ;
        end ;
    end ;

Since terminal input can be asynchronous, we do not want to process it unless/until we are ready to process input. Consider that if we echo typed characters as they are typed, they can appear interspersed with whatever current output is being sent to the terminal. So, if we are not doing input, we will save the characters in a type-ahead buffer to be processed later. Note, however, that certain characters perform operations immediately when received whether we are waiting for input or not. So for character values 3, 17, 19, and 20, we allow the routine to continue on. Otherwise, we save the character and immediately exit. Current_Status is a global variable in Init that is used to keep track of what Init is doing. This is used both to indicate when we are in input mode and for reporting status (^T). Here are the variable and constants used for the status:
// Status...
const Status_Idle = 0 ;
Status_TT = 1 ; // Output to terminal
Status_KB = 2 ; // Waiting for keyboard input

var Current_Status : integer = Status_Idle ;

Back to the Next_Char method.

    // Handle echo...
    if( Echo ) then
    begin
        case Key of
            15: // ^O
                begin
                    Toss_Output := not Toss_Output ;
                    if( Buffered_Output <> '' ) then
                    begin
                        Output( Buffered_Output ) ;
                        Buffered_Output := '' ;
                    end ;
                end ;
            17: // ^Q
                begin
                    Pause_Output := False ;
                    if( Buffered_Output <> '' ) then
                    begin
                        Output( Buffered_Output ) ;
                        Buffered_Output := '' ;
                    end ;
                end ;
            19: // ^S
                begin
                    Pause_Output := True ;
                end ;
            20: // ^T
                begin
                    Output( Translate_Key( Key ) + CRLF ) ;

                    // Show status...
                    case Current_Status of
                        Status_Idle : Show_Status( 'Idle' ) ;
                        Status_TT : Show_Status( 'TERMA0(W)' ) ;
                        Status_KB : Show_Status( 'TERMA0(R)' ) ;
                    end ;
                    exit ;
                end ;
        end ; // case Key of
    end ; // If echo

The next thing we do is handle the character echo. Translate_Key is used to translate from the input character to what we echo back (see above table). We also handle the ^T status by displaying the status after echoing the character. Here is the local Show_Status function:
procedure Show_Status( const S : string ) ;

begin
    Output( '0       TERMA0    Init            ' + S + CRLF ) ;
end ;

The format and content of the status matches, to some degree, how it shows in UOS. The important item is the passed status.

Back to the Next_Char method again...

    if( ( Key <> 8 ) and ( Key <> 255 ) ) then
    begin
        if( In_Deletion ) then
        begin
            In_Deletion := False ; // Exit delete mode
            Output( '\' ) ;
        end ;
    end ;
    if( Echo ) then
    begin
        Output( Translate_Key( Key ) ) ;
    end ; // If echo

On video screens, to delete a character is simple: backspace, write a space, and backspace again. However, for printing terminals, backspacing will just result in overprinted characters. So, we will show deleted characters between backslashes. In_Deletion is set when the user deletes a character. But since he could immediately delete another character, we set the In_Deletion flag and don't output the final backslash yet. But any other character other than delete/backspace will end the deletion sequence, so before we echo anything, we make sure that we exit delete mode if we are in it by clearing the flag and outputting the backslash. Finally, we echo the character.

The rest of the routine performs special actions based on what the user types, as described in the above table:

    // Handle input...
    case Key of
        3 : // ^C
            begin
                Buffered_Input := #3 ;
            end ;
        18 : // ^R
             begin
                 Output( CRLF + Translate_String( Buffered_Input ) ) ;
             end ;
        21 : // ^U
             begin
                 if( Video ) then
                 begin
                     for Loop := 1 to length( Translate_String( Buffered_Input ) ) do
                     begin
                         Output( BS + '  ' + BS ) ;
                     end ;
                 end else
                 begin
                     Output( CRLF ) ;
                 end ;
                 Buffered_Input := '' ;
             end ;
        8, 255 : // Delete
              begin
                  if( length( Buffered_Input ) > 0 ) then
                  begin
                      case Buffered_Input[ length( Buffered_Input ) ] of
                          #3, CR, LF, FF, #26, ESC : ; // Can't delete past delimiter
                          else
                              begin
                                  Do_Delete( ord( Buffered_Input[ length( Buffered_Input ) ] ) ) ;
                                  setlength( Buffered_Input, length( Buffered_Input ) - 1 ) ;
                              end ;
                      end ;
                  end ;
              end 
        else Buffered_Input := Buffered_Input + chr( Key ) ;
    end ; // case Key of
end ; // TUOS_Terminal.Next_Char

Here is the local Do_Delete function:

    procedure Do_Delete( Ch : cardinal ) ;

    var Loop : integer ;
        S : string ;

    begin
        S := Translate_Key( Ch ) ;
        if( Video ) then
        begin
            for Loop := 1 to length( S ) do
            begin
                Output( BS + ' ' + BS ) ;
            end ;
        end else
        begin
            if( In_Deletion ) then
            begin
                Output( S ) ;
            end else
            begin
                In_Deletion := True ;
                Output( '\' + S ) ;
            end ;
        end ;
    end ;

Because some control codes (like, say, ^A) translate to two characters, in video mode, we have to delete both the caret and the letter. So, we translate the character to the formatted version, and use that length to backspace, output a space, and backspace again.

Here are the translation routines, which return the characters to echo for the typed character. In most cases, this will be the typed character. In other cases, it will be the caret and letter for a control-character, or nothing (such as for NUL), or multiple characters (such as CR+LF for the FF character):

function Translate_Key( Key : cardinal ) : string ;

begin
    Result := '' ;
    case Key of
        0, 7, 8 : ; // NUL, BEL and BS
        9 : // TAB
            begin
                Result := chr( Key ) ;
            end ;
        10, 12, 13 : // LF, FF, and CR
            begin
                Result := CRLF ;
            end ;
        17, 19, 20: ; // ^Q, ^S, ^T
        27 : Result := '$' ;
        else
            begin
                if( Key < 32 ) then // All other control keys
                begin
                    Result := '^' + chr( Key + 64 ) ;
                end else
                begin
                    Result := chr( Key ) ;
                end ;
            end ;
    end ; // case Key of
end ;


function Translate_String( S : string ) : string ;

var Loop : integer ;

begin
    Result := '' ;
    for Loop := 1 to length( S ) do
    begin
        Result := Result + Translate_Key( ord( S[ Loop ] ) ) ;
    end ;
end ;

The output method is much simpler than input:

procedure TUOS_Terminal.Output( S : string ) ;

var Saved : integer ;

begin
    if( Toss_Output ) then
    begin
        exit ;
    end ;
    Saved := Current_Status ;
    Current_Status := Status_TT ;
    try
        while( S <> '' ) do // Keep trying until we succeed
        begin
            try
                Buffered_Output := Buffered_Output + S ;
                S := '' ;
            except
                // Probably ran out of memory if we get here
            end ;
            Term.Poll ;

            while( ( length( Buffered_Output ) > 0 ) and ( not Pause_Output ) ) do
            begin
                Term.Output( Buffered_Output[ 1 ] ) ;
                delete( Buffered_Output, 1, 1 ) ;
                Term.Poll ;
            end ;
        end ; // while( S <> '' )
    finally
        Current_Status := Saved ;
    end ;
end ;

If Toss_Output is set, we just exit - essentially ignoring the output. Next we save and change the current status. Then we add the output to the output buffer. Note that while appending the output, we could run out of memory if there is a lot of output while the output is paused. In this case, we loop until we can append the data. In other words, if the output buffer cannot accept any more characters, we stop further work until the buffer has been emptied (via the user unpausing the output) enough to append the new output text. While output is not paused, we will output to the physical terminal, one character at a time. That way, the user could pause or unpause the output as desired during the process of emptying the output buffer. In the case of UOS, the output buffer will be limited to a specific size so that one user cannot fill memory with paused terminal output. Finally, we restore the previous status.

function TUOS_Terminal.Peek : char ;

begin
    Term.Poll ;
    if( Buffered_Input = '' ) then
    begin
        Result := #0 ;
    end else
    begin
        Result := Buffered_Input[ 1 ] ;
    end ;
end ;

The Peek method allows Init to see what the next character in the input buffer is without removing it from the buffer. NUL is returned if the buffer is empty.

Finally, we present the Input_Line method.

function TUOS_Terminal.Input_Line : string ;

var Loop : integer ;
    Saved : integer ;

begin
    Saved := Current_Status ;
    Switch_To_KB_Mode ;
    try
        while( True ) do
        begin
            for Loop := 1 to length( Buffered_Input ) do
            begin
                case Buffered_Input[ Loop ] of
                    CR, LF, FF, #3, #26 :
                        begin
                            Result := copy( Buffered_Input, 1, Loop ) ;
                            Buffered_Input := copy( Buffered_Input, Loop + 1, length( Buffered_Input ) ) ;
                            exit ;
                        end ;
                end ;
            end ;
            Term.Poll ;
        end ; // while( True )
    finally
        Current_Status := Saved ;
    end ;
end ; // TUOS_Terminal.Input_Line

This method reads input until a delimiter is found and returns the entire line of input as a string. It makes use of the Switch_To_KB_Mode method, which looks like this:
procedure TUOS_Terminal.Switch_To_KB_Mode ;

var Loop : integer ;

begin
    Current_Status := Status_KB ;

    // Handle input typed before we were in input mode...
    for Loop := 1 to length( Type_Ahead_Buffer ) do
    begin
        Next_Char( ord( Type_Ahead_Buffer[ Loop ] ) ) ;
    end ;
    Type_Ahead_Buffer := '' ;
end ;

Remember when we said that we'd store up input for when we were ready for it, but not process it until then? This is the method that does the processing of type-ahead input. It sets the status to input so that the characters are processed and not just put back into the type-ahead buffer. Then it goes through all items in that buffer, passing them to the Next_Char method. Finally, it clears that buffer and ends.

Now that we have a wrapper around the Console terminal, we write a few functions that the rest of Init will use to do I/O to the console:

procedure Output( const S : string ) ;

begin
    Keyboard.Output( S ) ;
end ;


procedure Output_Line( const S : string ) ;

begin
    Output( S ) ;
    Output( CRLF ) ;
end ;


function Input( const Default : string ; Upper : boolean = True ) : string ;

begin
    Result := Keyboard.Input_Line ;
    if( Result = #3  ) then
    begin
        Output( CRLF ) ;
        exit ;
    end ;
    if( pos( ESC, Result ) + pos( #26, Result ) > 0 ) then
    begin
        Output( CRLF ) ;
        Result := ESC ;
        exit ;
    end ;
    Result := Edit( Result, 4 or 8 or 128 ) ;
    if( Upper ) then
    begin
        Result := Edit( Result, 32 ) ;
    end ;
    if( Result = '' ) then
    begin
        Result := Default ;
    end ;
end ;

The Output function is just shorthand for calling the terminal's Output method. Output_Line is also shorthand for outputting text and then a CRLF to complete a line of output. Otherwise, we'd have to include CRLF on the end of all the strings that we output. The Input method gets a line of input from the terminal, and then does some processing for us. In the case of ^C, ^Z, and escape, we make sure to start a new line. If ^Z or escape occur anywhere in the string, we ignore the rest of the characters and set the result to ESC. For normal input, we get rid of leading and trailing spaces, and reduce multiple spaces to a single space. Unless told otherwise, we also convert the input to uppercase for easy comparisons. If the user didn't type anything other than a line delimiter, we set the result to the passed default value (if any).

Here is the Command_Mode procedure, which contains the main input loop for Init:

procedure Command_Mode ;

var Dummy : integer ;
    S, S1 : string ;

begin
    Keyboard := TUOS_Terminal.Create( HAL.Console ) ;
    Output( 'UOS Init V0.1 ' + Format_Sirius_Date( '', HAL.Timestamp ) + ' ' +
        Format_Sirius_Time( 'HH:MM', HAL.Timestamp ) + CRLF + CRLF ) ;

    while( True ) do
    begin
        Keyboard.Echo := True ;
        Output( 'Init> ' ) ;
        S := Input( '' ) ;
        if( ( S = '' ) or ( S = #3 ) or ( S = ESC ) or ( S = #26 ) ) then
        begin
            continue ;
        end ;
        Dummy := pos( ' ', S + ' ' ) ;
        S1 := copy( S, Dummy + 1, length( S ) ) ;
        S := copy( S, 1, Dummy - 1 ) ;
        if( ( S = '?' ) or ( ( copy( 'HELP', 1, length( S ) ) = S ) and ( length( S ) > 1 ) ) ) then
        begin
            Output_Line( 'DISK - Manage disks' ) ;
            Output_Line( 'HALT - Halt system' ) ;
            Output_Line( 'HELP - Show this help text' ) ;
        end else
        if( copy( 'DISK', 1, length( S ) ) = S ) then
        begin
            Do_Disk( S1 ) ;
        end else
        if( copy( 'HALT', 1, length( S ) ) = S ) then
        begin
            halt ;
        end else
        begin
            Output_Line( 'Invalid command'  ) ;
        end ;
    end ;
end ; // Command_Mode

First we create our wrapper for the system console and identify ourselves. Then we go into an infinite loop for command input. We make sure the keyboard echo is on, just in case it got turned off at some point and never got turned back on. Then we output our prompt ("Init> ") and call the Input function to get the (next) command line. Remember that a ^C always returns back to the main prompt, and since there is no prompt to back up to, ^C, ^Z, and escape just loop back to the prompt. Next we parse out the first part of any multipart command. Then we compare the first (or only) part against the various valid commands, telling the user if we don't recognize it. In the case of the HELP and HALT commands, we act on them. In the case of the DISK command, we call the Do_Disk function to process disk-related commands.

Here is the Match function:

function Match( const Full, Partial : string ; Minimum : integer ) : boolean ;

begin
    Result := False ;
    if( length( Partial ) < Minimum ) then
    begin
        exit ;
    end ;
    if( copy( Full, 1, length( Partial ) ) = Partial ) then
    begin
        Result := True ;
    end ;
end ;

This simply makes sure that the entered command (Partial) matches the first part of the command (Full) and is at least Minimum characters long.

Well, we didn't quite get to the point where we use our file system. I promise that we will get to it in the next article.