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

Terminal 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
58 Quotas and I/O

UCL
59 UCL Basics
60 Symbol Substitution
61 Command execution
62 Command execution, part 2
63 Command Abbreviation
64 ASTs
65 Expressions, Part 1
66 Expressions, Part 2: Support code
67 Expressions, part 3: Parsing
68 SYS_GETJPIW and SYS_TRNLNM
69 Expressions, part 4: Evaluation

UCL Lexical Functions
70 PROCESS_SCAN
71 PROCESS_SCAN, Part 2
72 TProcess updates
73 Unicode revisted
74 Lexical functions: F$CONTEXT
75 Lexical functions: F$PID
76 Lexical Functions: F$CUNITS
77 Lexical Functions: F$CVSI and F$CVUI
78 UOS Date and Time Formatting
79 Lexical Functions: F$CVTIME
80 LIB_CVTIME
81 Date/Time Contexts
82 SYS_GETTIM, LIB_Get_Timestamp, SYS_ASCTIM, and LIB_SYS_ASCTIM
83 Lexical Functions: F$DELTA_TIME
84 Lexical functions: F$DEVICE
85 SYS_DEVICE_SCAN
86 Lexical functions: F$DIRECTORY
87 Lexical functions: F$EDIT and F$ELEMENT
88 Lexical functions: F$ENVIRONMENT
89 SYS_GETUAI
90 Lexical functions: F$EXTRACT and F$IDENTIFIER
91 LIB_FAO and LIB_FAOL
92 LIB_FAO and LIB_FAOL, part 2
93 Lexical functions: F$FAO
94 File Processing Structures
95 Lexical functions: F$FILE_ATTRIBUTES
96 SYS_DISPLAY
97 Lexical functions: F$GETDVI
98 Parse_GetDVI
99 GetDVI
100 GetDVI, part 2
101 GetDVI, part 3
102 Lexical functions: F$GETJPI
103 GETJPI
104 Lexical functions: F$GETSYI
105 GETSYI
106 Lexical functions: F$INTEGER, F$LENGTH, F$LOCATE, and F$MATCH_WILD
107 Lexical function: F$PARSE
108 FILESCAN
109 SYS_PARSE
110 Lexical Functions: F$MODE, F$PRIVILEGE, and F$PROCESS
111 File Lookup Service
112 Lexical Functions: F$SEARCH
113 SYS_SEARCH
114 F$SETPRV and SYS_SETPRV
115 Lexical Functions: F$STRING, F$TIME, and F$TYPE
116 More on symbols
117 Lexical Functions: F$TRNLNM
118 SYS_TRNLNM, Part 2
119 Lexical functions: F$UNIQUE, F$USER, and F$VERIFY
120 Lexical functions: F$MESSAGE
121 TUOS_File_Wrapper
122 OPEN, CLOSE, and READ system services

UCL Commands
123 WRITE
124 Symbol assignment
125 The @ command
126 @ and EXIT
127 CRELNT system service
128 DELLNT system service
129 IF...THEN...ELSE
130 Comments, labels, and GOTO
131 GOSUB and RETURN
132 CALL, SUBROUTINE, and ENDSUBROUTINE
133 ON, SET {NO}ON, and error handling
134 INQUIRE
135 SYS_WRITE Service
136 OPEN
137 CLOSE
138 DELLNM system service
139 READ
140 Command Recall
141 RECALL
142 RUN
143 LIB_RUN
144 The Data Stream Interface
145 Preparing for execution
146 EOJ and LOGOUT
147 SYS_DELPROC and LIB_GET_FOREIGN

CUSPs and utilities
148 The I/O Queue
149 Timers
150 Logging in, part one
151 Logging in, part 2
152 System configuration
153 SET NODE utility
154 UUI
155 SETTERM utility
156 SETTERM utility, part 2
157 SETTERM utility, part 3
158 AUTHORIZE utility
159 AUTHORIZE utility, UI
160 AUTHORIZE utility, Access Restrictions
161 AUTHORIZE utility, Part 4
162 AUTHORIZE utility, Reporting
163 AUTHORIZE utility, Part 6
164 Authentication
165 Hashlib
166 Authenticate, Part 7
167 Logging in, part 3
168 DAY_OF_WEEK, CVT_FROM_INTERNAL_TIME, and SPAWN
169 DAY_OF_WEEK and CVT_FROM_INTERNAL_TIME
170 LIB_SPAWN
171 CREPRC
172 CREPRC, Part 2
173 COPY
174 COPY, part 2
175 COPY, part 3
176 COPY, part 4
177 LIB_Get_Default_File_Protection and LIB_Substitute_Wildcards
178 CREATESTREAM, STREAMNAME, and Set_Contiguous
179 Help Files
180 LBR Services
181 LBR Services, Part 2
182 LIBRARY utility
183 LIBRARY utility, Part 2
184 FS Services
185 FS Services, Part 2
186 Implementing Help
187 HELP
188 HELP, Part 2
189 DMG_Get_Key and LIB_Put_Formatted_Output
190 LIBRARY utility, Part 3
191 Shutting Down UOS
192 SHUTDOWN
193 WAIT
194 SETIMR
195 WAITFR and Scheduling
196 REPLY, OPCOM, and Mailboxes
197 REPLY utility
198 Mailboxes
199 BRKTHRU
200 OPCOM

Glossary/Index


Downloads

Mailboxes

In the previous articles, we've discussed mailboxes conceptually. As described, UOS mailboxes are not related to email, but are virtual devices used for interprocess communication. In this article, we will look at the code that implements mailboxes.

type TMailbox = class( TFiP_File )
                    public // Constructors and destructors...
                        constructor Create( _Kernel : TUOS_Kernel ) ;
                        destructor Destroy ; override ;

                    public // API...
                        Protection : cardinal ;
                        Owner : int64 ;
                        Max_Storage : int64 ;
                        Permanent : boolean ;
                        Kernel : TUOS_Kernel ;
                        Device : Devices.TDevice ;

                    private // Instance data...
                        Current_Storage : int64 ;
                        Deleted : boolean ; // True if marked for deletion
                        Queue : TList ;

                    public // API...
                        function Is_Class( Name : PChar ) : boolean ; override ;

                        function Delete : TUnified_Exception ;

                        function Read( Stream : longint ;
                            Position : TStore_Address64 ;
                            Length : TStore_Size64 ; var Buff ;
                            Flags : longint ) : TStore_Size64 ; override ;

                        function Write( Stream : longint ;
                            Position : TStore_Address64 ;
                            Length : TStore_Size64 ; var Buff ;
                            Flags : longint ) : TStore_Size64 ; override ;

                        procedure Check_Delete ;
                end ;
We start with the mailbox class. Each mailbox created on UOS will have an instance of this class.

constructor TMailbox.Create( _Kernel : TUOS_Kernel ) ;

begin
    inherited Create( _Kernel ) ;

    Kernel := _Kernel ;
    Queue := TList.Create ;
    Device := Devices.TDevice.Create ;
    Device.Mounted := True ;
    Device.Info.Flags := DFC_Stream ;
end ;


destructor TMailbox.Destroy ;

var I : integer ;

begin
    for I := 0 to Queue.Count - 1 do
    begin
        TMessage( Queue[ I ] ).Free ;
        Queue[ I ] := nil ;
    end ;
    Queue.Free ;
    Device.Free ;
    if( Permanent ) then
    begin
        I := TUOS_FIP( Kernel.FIP ).Permanent_Mailboxes.Indexof( self ) ;
        TUOS_FIP( Kernel.FIP ).Permanent_Mailboxes[ I ] := nil ;
    end else
    begin
        I := TUOS_FIP( Kernel.FIP ).Temporary_Mailboxes.Indexof( self ) ;
        TUOS_FIP( Kernel.FIP ).Temporary_Mailboxes[ I ] := nil ;
    end ;

    inherited Destroy ;
end ;
The constructor initializes data and also sets up a TDevice instance for the mailbox. Physical devices have a TDevice instance set up for them during system startup, but mailboxes are virtual and when one is created, we need a device instance for each one. As a device, a mailbox is always mounted and is considered a stream. The Queue list will contain the message queue. On destruction, we free up each individual message before freeing the queue list. Finally, we remove the mailbox from the appropriate list. The FIP has two lists: one for permanent mailboxes and one for temporary mailboxes.

function TMailbox.Is_Class( Name : PChar ) : boolean ;

var _N : string ;

begin
    _N := Name ;
    _N := lowercase( _N ) ;
    Result := ( _N = 'TMailbox' ) ;
    if( not Result ) then
    begin
        Result := inherited Is_Class( Name ) ;
    end ;
end ;


function TMailbox.Delete : TUnified_Exception ;

begin
    Deleted := True ;
end ;
The Is_Class method is similiar to all previous instances of such method. The Delete method simply markes the Mailbox for deletion. It doesn't actually delete the mailbox since there may be open channels to the mailbox. The deletion will occur later. In the case of permanent mailboxes, the deletion occurs as the result of a system service call. For temporary mailboxes, the deletion occurs when the last handle to the mailbox is closed.

unction TMailbox.Write( Stream : longint ; Position : TStore_Address64 ;
    Length : TStore_Size64 ; var Buff ; Flags : longint ) : TStore_Size64 ;

var FIP : TUOS_FIP ;
    I : integer ;
    Request : TUOS_IO_Request ;
    S : TMessage ;
    St : string ;
    V : int64 ;

begin
    // Check quota...
    if( Length > Max_Storage - Current_Storage ) then // No room in mailbox
    begin
        Set_Last_Error( Create_Error( UOSErr_No_Room_On_Device ) ) ;
        Result := 0 ;
        exit ;
    end ;

    // Check protection...
    if( not Kernel.USC.Validate_Protection( Kernel.PID, FAB_V_PUT, Device.Owner.Owner, 
        Device.Owner.Protections, 0{ACL} ) ) then
    begin
        Set_Last_Error( Create_Error( UOSErr_Protection_Violation ) ) ;
        Result := 0 ;
        exit ;
    end ;
This method does the write operation to the mailbox. First we check to see if the length of the data would cause the total mailbox data size to exceed its maximum quota. If so, we exit with an error. Then we check that the user isn't violating the protection of the mailbox.

    // Create queue entry...
    Current_Storage := Current_Storage + Length ;
    setlength( St, Length ) ;
    move( Buff, PChar( St )[ 0 ], Length ) ;
    S := TMessage.Create ;
    S.Set_Value( St ) ;
    S.Time := HAL.Timestamp ;
    PID := Kernel.PID ;
    S.PID := PID ;
    S.UIC := Kernel.USC.Get_Process_Info( PID, JPI_UIC ) ;
    S.Privileges := Kernel.USC.Get_Process_Info( PID, JPI_CURPRIV ) ;
    Queue.Add( S ) ;
    Result := Length ;
Next we create a TMessage instance to store the data. We'll cover this class later in the article, but essentially it is nothing more than a way to store a string as an object. We copy the data into a string, write it into the object instance and then add the object to Queue, which is a TList that holds all messages sent to the mailbox. Each write to the mailbox is a single message in the queue. Then we return the length of the data, indicating that the whole data was written. Reads and writes are atomic operations: either the entire amount is read or written, or nothing is.

Also notable here is the assignment of Time, PID, UIC, and Privileges to the message instance. The timestamp allows a mailbox reader to know exactly when the message was sent (written). The PID lets it know which process sent the message. But why pass the UIC and Privileges since those could be obtained based on the process ID? The reason is that the process could have ended before the message was processed, leaving that information unavailable. A message receiver may want to know which user sent the message. Further, it is possible (though unlikely) that when a new process is created, it might be assigned a previously used ID, which leaves the original UIC and privileges unavailable. The reason for including privileges is not only because the originating process may no longer be available, but because the current privileges (which is what is passed) can change over time even with given process. Some operations may require certain privileges (such as OPCOM operations), and the reader may need to know if the sender was authorized to make the request.

    // Notify FIP of status change of device...
    FIP := TUOS_FIP( Kernel.FIP ) ;
    V := IT_Status ;
    V := V shl 8 ;
    V := V or GDT_Mailbox ;
    V := V shl 16 ;
    if( not Permanent ) then
    begin
        V := V or 1 ; // MAILB
    end ;
    V := V shl 16 ;
    if( Permanent ) then
    begin
        V := V or FIP._Permanent_Mailboxes.Indexof( self ) ;
    end else
    begin
        V := V or FIP._Temporary_Mailboxes.Indexof( self ) ;
    end ;
    V := V shl 16 ;
    V := V or Interrupt_IO ;
    FIP.Interrupt( V ) ;
end ;
Lastly, we send an interrupt notice to the File Processor. This allows any pending read operations for the mailbox to complete now that there is data available.

function TMailbox.Read( Stream : longint ; Position : TStore_Address64 ;
    Length : TStore_Size64 ; var Buff ; Flags : longint ) : TStore_Size64 ;

var FIP : TUOS_FIP ;
    I : integer ;
    Request : TUOS_IO_Request ;
    S : TMessage ;
    V : int64 ;

begin
    // Check protection...
    if( not Kernel.USC.Validate_Protection( Kernel.PID, FAB_V_GET, Device.Owner.Owner, 
        Device.Owner.Protections, 0{ACL} ) ) then
    begin
        Set_Last_Error( Create_Error( UOSErr_Protection_Violation ) ) ;
        Result := 0 ;
        exit ;
    end ;

    if( Queue.Count = 0 ) then // No data in mailbox
    begin
        Set_Last_Error( Create_Error( UOSErr_Read_Past_End ) ) ;
        Result := 0 ;
        exit ;
    end ;
Thia method allows reading from a mailbox. First we check that the mailbox protections don't prevent us from reading. Then we check to see if the queue is empty. If either situation exists, we exit with an error.

    // Read from mailbox...
    S := TMessage( Queue[ 0 ] ) ;
    if( Length > S.Len ) then
    begin
        Length := S.Len ;
    end ;
    Result := Length ;
    move( S.Contents[ 0 ], Buff, Length ) ;
    Current_Storage := Current_Storage - Length ;

    // Remove message from mailbox...
    S.Free ;
    Queue.Delete( 0 ) ;
Next we get the first TMessage instance from the queue. Since we treat the list as a FIFO queue, we take messages from the beginning of the list (index 0) and add them to the end of the list in the write method. Once we write the data to the user's buffer, we remove the item from the queue, and destroy the instance.

    // Notify FIP of status change of device...
    FIP := TUOS_FIP( Kernel.FIP ) ;
    V := IT_Status ;
    V := V shl 8 ;
    V := V or GDT_Mailbox ;
    V := V shl 16 ;
    if( not Permanent ) then
    begin
        V := V or 1 ; // MAILB
    end ;
    V := V shl 16 ;
    if( Permanent ) then
    begin
        V := V or FIP._Permanent_Mailboxes.Indexof( self ) ;
    end else
    begin
        V := V or FIP._Temporary_Mailboxes.Indexof( self ) ;
    end ;
    V := V shl 16 ;
    V := V or Interrupt_IO ;
    FIP.Interrupt( V ) ;
end ;
As we saw in the write method, we send an interrupt to the File Processor to handle unblocking any process waiting on data to read from the mailbox.

procedure TMailbox.Check_Delete ;

begin
    if( not Any_Handles ) then // No more handles...
    begin
        if( Permanent ) then
        begin
            if( Deleted ) then
            begin
                Free ;
            end ;
        end else
        begin
            Free ;
        end ;
    end ;
end ;
This routine is used to check to see if the mailbox should be deleted. Deletion will only happen if the mailbox has been marked for deletion or is a temporary mailbox with no attached handles.

type TMessage = class( TString )
                    public // API...
                        Time : int64 ;
                        PID : TPID ;
                        UIC : int64 ;
                        Privileges : int64 ;

                        procedure Set_Value( St : string ) ;
                end ;

procedure TMessage.Set_Value( St : string ) ;

begin
    _Value := St ;
end ;
TMessage is a descendent of the TString class. It adds the additional contextual information we talked about above. It also contains a method that allows assigning a value via a Pascal string rather than a pchar.

There are some other code changes needed to support mailboxes in different parts of the File Processor, and we will address those in a future article (or two).

In the next article, we will look at the SYS_BRKTHRU system service, which is used by REPLY.