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

I/O
45 Shells and UCL
46 UOS API, the Application Side
47 UOS API, the Executive Side
48 I/O Devices
49 Streams
50 Terminal Output Filters
51 The TTerminal Class
52 Handles
53 Putting it All Together
54 Getting Terminal Input
55 QIO
56 Cooking Terminal Input
57 Putting it all together, part 2

Glossary/Index


Download sources
Download binaries

Streams

Just as we built the file system layer upon layer, in this article (and the next few), we will build up from the physical device to the I/O calls in the application layer (ring 3). The software layer directly above the hardware is the device driver. We mentioned this in the previous article and we will cover drivers in more detail in a future article. To summarize, a driver runs in ring 0 or 1 and interfaces directly with specific hardware. It provides a standardized interface to all of the hardware registers, etc. Above the driver is the software "raw" interface. This is a somewhat abstracted interface to specific drivers. The class used at this level depends upon the nature of the device. For a disk drive, the class is a TStore. For a terminal, the class is a TStream, which we will discuss here.

The TCOM_Stream64 class is the abstract base class for all devices of the stream type. Here is the definition:

type TCOM_Stream64 = class( TCommon_COM_Interface )
                        public { API... }
                            function Read_Data( var Data ; _Size : TStore_Address64 ;
                                var UEC : TUnified_Exception ) : TStore_Address64 ;
                                virtual ; stdcall ; abstract ;
                            { Read data from the store to Data. Read _Size
                              bytes.  Returns success of operation in UEC.
                              Function returns the number of bytes actually read. }

                            function Write_Data( var Data ; _Size : TStore_Address64 ;
                                var UEC : TUnified_Exception ) : TStore_Address64 ;
                                virtual ; stdcall ; abstract ;
                            { Write data from Data to the store. Write _Size
                              bytes.  Returns success of operation in UEC.
                              Function returns the number of bytes actually written. }

                            function Max_Record_Size : TStore_Size64 ;
                                virtual ; stdcall ; abstract ;
                            { Returns the maximum (current) size of the store,
                              in bytes.  In other words, this is the highest
                              valid address (plus 1). }

                            function Min_Record_Size : TStore_Address64 ;
                                virtual ; stdcall ; abstract ;
                            { Returns the minimum atomic size of the store.  For
                              instance, this would be the sector size for a disk. }

                            function Get_Read_Only : boolean ;
                                virtual ; stdcall ; abstract ;
                            { Indicates that the store can only be read from. }

                            function Get_Write_Only : boolean ;
                                virtual ; stdcall ; abstract ;
                            { Indicates that the store can only be written to. }

                            procedure Set_Ready( Value : boolean ) ;
                                virtual ; stdcall ; abstract ;
                            { Set the stream's ready state. }

                            function Get_Bytes_Read : longint ;
                                virtual ; stdcall ; abstract ; { Data bytes read }
                            function Get_Bytes_Written : longint ;
                                virtual ; stdcall ; abstract ; { Data bytes written }
                            function Get_Reads : longint ;
                                virtual ; stdcall ; abstract ; { Data read count }
                            function Get_Writes : longint ;
                                virtual ; stdcall ; abstract ; { Data write count }
                            function Get_Error_Count : longint ;
                                virtual ; stdcall ; abstract ; { Error count }
                            procedure Set_Bytes_Read( Value : longint ) ;
                                virtual ; stdcall ; abstract ; { Data bytes read }
                            procedure Set_Bytes_Written( Value : longint ) ;
                                virtual ; stdcall ; abstract ; { Data bytes written }
                            procedure Set_Reads( Value : longint ) ;
                                virtual ; stdcall ; abstract ; { Data read count }
                            procedure Set_Writes( Value : longint ) ;
                                virtual ; stdcall ; abstract ; { Data write count }
                            procedure Set_Error_Count( Value : longint ) ;
                                virtual ; stdcall ; abstract ; { Error count }
                            procedure Set_Read_Only( Value : boolean ) ;
                                virtual ; stdcall ; abstract ;
                            { Sets the store read-only value. }
                            procedure Set_Write_Only( Value : boolean ) ;
                                virtual ; stdcall ; abstract ;
                            { Sets the store write-only value. }
                            function Get_Position : int64 ;
                                virtual ; stdcall ; abstract ;
                            { Sets the store write-only value. }
                            procedure Set_Position( Value : int64 ) ;
                                virtual ; stdcall ; abstract ;
                            { Sets the store write-only value. }

                        public // Properties...
                            property Read_Only : boolean
                                read Get_Read_Only
                                write Set_Read_Only ;

                            property Write_Only : boolean
                                read Get_Write_Only
                                write Set_Write_Only ;

                            property Bytes_Read : longint
                                read Get_Bytes_Read
                                write Set_Bytes_Read ;

                            property Bytes_Written : longint
                                read Get_Bytes_Written
                                write Set_Bytes_Written ;

                            property Position : int64
                                read Get_Position
                                write Set_Position ;

                            property Reads : longint
                                read Get_Reads
                                write Set_Reads ;

                            property Writes : longint
                                read Get_Writes
                                write Set_Writes ;

                            property Error_Count : longint
                                read Get_Error_Count
                                write Set_Error_Count ;
                    end ; { TCOM_Stream64 }

The non-abstract stream class is called TFiP_Stream. Here is the definition:

type TFiP_Stream = class( TCOM_Stream64 )
                        protected // Statistics...
                            Driver : _HAL.TTerminal ;
                            _Bytes_Read : longint ; // Data only
                            _Bytes_Written : longint ; // Data only
                            _Reads : longint ;
                            _Writes : longint ;
                            _Error_Count : longint ;
                            _Flow_Control_Bytes_Read : longint ;
                            _Flow_Control_Bytes_Written : longint ;
                            _Device : TDevice ;
                            _Software_Read_Only : boolean ;
                            _Software_Write_Only : boolean ;

                        public // API...
                            function Is_Class( N : PAnsiChar ) : boolean ;
                                override ;

                            function Read_Data( var Data ; _Size : TStore_Address64 ;
                                var UEC : TUnified_Exception ) : TStore_Address64 ;
                                override ;

                            function Write_Data( var Data ; _Size : TStore_Address64 ;
                                var UEC : TUnified_Exception ) : TStore_Address64 ;
                                override ;

                            function Max_Record_Size : TStore_Size64 ;
                                override ;

                            function Min_Record_Size : TStore_Address64 ;
                                override ;

                            function Get_Read_Only : boolean ; override ;

                            function Get_Write_Only : boolean ; override ;

                            function Get_Bytes_Read : longint ; override ;
                            function Get_Bytes_Written : longint ; override ;
                            function Get_Reads : longint ; override ;
                            function Get_Writes : longint ; override ;
                            function Get_Error_Count : longint ; override ;
                            procedure Set_Bytes_Read( Value : longint ) ;
                                override ;
                            procedure Set_Bytes_Written( Value : longint ) ;
                                override ;
                            procedure Set_Reads( Value : longint ) ; override ;
                            procedure Set_Writes( Value : longint ) ;
                                override ;
                            procedure Set_Error_Count( Value : longint ) ;
                                override ;
                            procedure Set_Read_Only( Value : boolean ) ;
                                override ;
                            procedure Set_Write_Only( Value : boolean ) ;
                                override ;
                            function Get_Position : int64 ; override ;
                            procedure Set_Position( Value : int64 ) ; override ;
                            procedure Set_Ready( Value : boolean ) ;
                                override ;
                   end ; // TFiP_Stream
For now, the only stream devices we are dealing with are terminals. Thus, the Driver instance data contains an instance of the THAL.TTerminal. _Bytes_Read, _Bytes_Written, _Reads, and _Writes keep track of the number of read and write operations and also the number of bytes written and read. _Flow_Control_Bytes_Read, and _Flow_Control_Bytes_Write keep track of the number of flow-control bytes read and written. We will discuss flow-control later. _Error_Count indicates the number of errors that have occurred on the device. For terminals, this may indicate framing errors on serial devices, or parity errors, etc. _Device is the HAL device for this stream.
As we discussed in the last article, any level of the hardware can be read-only or write-only. But the class also allows us to treat the device as read-only or write-only regardless of the hardware. This is the purpose of _Software_Read_Only and _Software_Write_Only.

Now let's look at the wrappers for the statistics and Is_Class method:

function TFiP_Stream.Is_Class( N : PAnsiChar ) : boolean ;

var _N : string ;

begin
    _N := string( N ) ;
    Result := lowercase( _N ) = 'tfip_stream' ;
end ;


function TFiP_Stream.Max_Record_Size : TStore_Size64 ;

begin
    Result := 0 ; // Undefined
end ;


function TFiP_Stream.Min_Record_Size : TStore_Address64 ;

begin
    Result := 0 ; // Undefined
end ;


function TFiP_Stream.Get_Bytes_Read : longint ;

begin
    Result := _Bytes_Read ;
end ;


function TFiP_Stream.Get_Bytes_Written : longint ;

begin
    Result := _Bytes_Written ;
end ;


function TFiP_Stream.Get_Reads : longint ;

begin
    Result := _Reads ;
end ;


function TFiP_Stream.Get_Writes : longint ;

begin
    Result := _Writes ;
end ;


function TFiP_Stream.Get_Error_Count : longint ;

begin
    Result := _Error_Count ;
end ;


procedure TFiP_Stream.Set_Bytes_Read( Value : longint ) ;

begin
    _Bytes_Read := Value ;
end ;


procedure TFiP_Stream.Set_Bytes_Written( Value : longint ) ;

begin
    _Bytes_Written := Value ;
end ;


procedure TFiP_Stream.Set_Reads( Value : longint ) ;

begin
    _Reads := Value ;
end ;


procedure TFiP_Stream.Set_Writes( Value : longint ) ;

begin
    _Writes := Value ;
end ;


procedure TFiP_Stream.Set_Error_Count( Value : longint ) ;

begin
    _Error_Count := Value ;
end ;


procedure TFiP_Stream.Set_Read_Only( Value : boolean ) ;

begin
    _Software_Read_Only := Value ;
end ;


procedure TFiP_Stream.Set_Write_Only( Value : boolean ) ;

begin
    _Software_Write_Only := Value ;
end ;


function TFiP_Stream.Get_Position : int64 ;

begin
    Result := 0 ;
end ;


procedure TFiP_Stream.Set_Position( Value : int64 ) ;

begin
    // This routine left intentionally blank
end ;
These should be self-explanatory and/or things we've seen before. In some cases, such as Set_Position, we ignore the call in this generic base class. We will implement it in descendent classes.

function TFiP_Stream.Get_Read_Only : boolean ;

begin
    Result := ( ( _Device.Info.Flags and DF_Access_Mask ) = DFM_Read_Only ) or _Software_Read_Only ;
end ;


function TFiP_Stream.Get_Write_Only : boolean ;

begin
    Result := ( ( _Device.Info.Flags and DF_Access_Mask ) = DFM_Write_Only ) or _Software_Write_Only ;
end ;
These functions are simple short-hand ways of obtaining read-only and write-only status. They extract the flags from the device flags and then include the appropriate software flag.

function TFiP_Stream.Write_Data( var Data ; _Size : TStore_Address64 ;
    var UEC : TUnified_Exception ) : TStore_Address64 ;

var S : Ansistring ;

begin
    Result := 0 ;
    if( _Device = nil ) then
    begin
        UEC := Create_Error( UOSErr_Invalid_Operation ) ;
        exit ;
    end ;
    if( Read_Only ) then
    begin
        UEC := Create_Error( UOSErr_Read_Only ) ;
        exit ;
    end ;
    if( Driver = nil ) then
    begin
        UEC := Create_Error( UOSErr_Invalid_Operation ) ;
        exit ;
    end ;
    setlength( S, _Size ) ;
    move( Data, PAnsiChar( S )[ 0 ], _Size ) ;
    Driver.Output( PAnsiChar( S ), _Size ) ;
    inc( _Writes ) ;
    _Bytes_Written := _Bytes_Written + _Size ;
end ;
We will discuss the Read_Data method later. For now, we will only consider Write_Data. First we verify that _Device and Driver are defined. We also verify that the hardware isn't read-only. In any of those cases, we return an error. Otherwise, we write the data to the driver, increment the number of write operations and add to the number of bytes written.

In the next article, we will discuss the terminal-specific classes.