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

Glossary/Index


Downloads

OPEN, CLOSE, and READ system services

We discussed the TUOS_File_Wrapper class in the previous article. That class is made available from the Starlet library to ring 3 applications. It is a wrapper to the system calls that manipulate files. In this article, we will take a look at the OPEN, CLOSE, and READ system services used by that class. OPEN is used to open a file on behalf of an application, CLOSE is used to close an open file, and READ is used to read data from an open file.

procedure SYS_OPEN( fab, err, suc : int64 ) ;

var Status : int64 ;
    SysRequest : TInteger3_Request ;

begin
    Status := 0 ;
    fillchar( SysRequest, sizeof( SysRequest ), 0 ) ;
    SysRequest.Request.Subsystem :=  UOS_Subsystem_FIP ;
    SysRequest.Request.Request := UOS_FIP_Open ;
    SysRequest.Request.Length := sizeof( SysRequest ) - sizeof( TSystem_Request ) ;
    SysRequest.Request.Status := integer( @Status ) ;
    SysRequest.Int1 := FAB ;
    SysRequest.Int2 := Err ;
    SysRequest.Int3 := Suc ;

    Call_To_Ring0( integer( @SysRequest ) ) ;
end ;
This routine in the SYS unit is a wrapper for the OPEN system call.

        UOS_FIP_Open:
            begin
                UE := Enter_System_Call( Request, SReq, PID, MMC,
                    sizeof( S1I3_Request ) - sizeof( SReq ), Address ) ;
                if( UE <> nil ) then
                begin
                    Set_Last_Error( UE ) ;
                    exit ;
                end ;
                try
                    S1I3_Request := PS1I3_Request( Address ) ;

                    Status := File_Open( S1I3_Request.SRB, S1I3_Request.Integer1,
                        S1I3_Request.Integer3, IOSB ) ;
                    Write_User( Kernel, Kernel.PID, S1I3_Request.Request.Status,
                        sizeof( Status ), Status ) ;
                    Write_User_int64( Kernel, PID, S1I3_Request.Request.Status,
                        IOSB.r_io_64.r_bcnt_32.l_bcnt ) ;
                finally
                    Exit_System_Call( integer( S1I3_Request ), PID, MMC,
                        sizeof( S1I3_Request ) - sizeof( SReq ) ) ;
                end ;
            end ;
This code is added to the FIP's .API function.

function TUOS_FiP.File_Open( Name : TSRB ; Flags, Handle : int64 ;
    var IOSB : TIOSB ) : int64 ;

var H : TResource ;
    SName : string ;
    Node, Access, Secondary_Node, SDevice, Path, FName, Extension, Version : string ;
    US : TUOS_String ;

begin
    if( Handle = 0 ) then // No address for handle
    begin
        Generate_Exception( UOSErr_Bad_Parameter ) ;
        exit ;
    end ;

    // Get name...
    US := Get_User_String( Kernel, Kernel.PID, Name, IOSB.r_io_64.w_status ) ;
    if( IOSB.r_io_64.w_status = UE_Error ) then
    begin
        Result := IOSB.r_io_64.w_status ;
        exit ;
    end ;
    SName := US.Contents ;
    US.Free ;
    Parse_Filename( SName, Node, Access, Secondary_Node, SDevice, Path, FName,
        Extension, Version ) ;

    // Process Node...
    if( ( Node <> '' ) and ( Node <> Kernel.Node_Name ) ) then
    begin
        Result := UOSErr_Node_Not_Found ;
        Generate_Exception( Result ) ;
        exit ;
    end ;

    H := Create_File_Handle( Kernel.PID, PChar( SName ), Flags ) ;
    if( H <> nil ) then
    begin
        IOSB.r_io_64.w_status := Write_User_int64( Kernel, Kernel.PID, Handle, int64( H ) ) ;
        if( IOSB.r_io_64.w_status = UE_Error ) then
        begin
            Result := IOSB.r_io_64.w_status ;
            exit ;
        end ;
    end ;
end ; // TUOS_FiP.File_Open
If the address of the Handle is 0, we exit with an error. Next we get the file name from the user memory, exiting if there was any error. For now, we aren't going to deal with other nodes. So, if a node is specified and isn't the name of the current node, we exit with an error. Finally, we call Create_File_Handle and write the resulting handle value to the user memory. We've covered that routine in the past. It calls Open_File, which we will finally cover here.

function TUOS_FiP.Open_File( PID : TPID ; Name : PChar ;
    Mode : int64 ) : TUOS_File ;

var Device : Devices.TDevice ;
    Dummy : integer ;
    F : TUOS_File ;
    FI : TUOS_File_Info ;
    Open_Files : TOpen_Files ;
    S : string ;
    UEC : TUnified_Exception ;

begin
    // Setup...
    Result := nil ;
    Set_Last_Error( nil ) ;
    S := Resolve_Path( PID, string( Name ), True ) ;
    Dummy := pos( ':', S ) ;
    if( Dummy < 2 ) then
    begin
        Set_Last_Error( Create_Error( UOSErr_Device_Not_Found ) ) ;
        exit ;
    end ;
This method opens a file, returning a TUOS_File instance for that file (or nil if there was an error). First we resolve the path to take care of logicals.

    // Get device and its file system...
    Device := Devices.TDevice( Get_Device( copy( S, 1, Dummy ) ) ) ;
    if( Device = nil ) then
    begin
        Set_Last_Error( Create_Error( UOSErr_Device_Not_Found ) ) ;
        exit ;
    end ;
    if( not Device.Mounted ) then
    begin
        Set_Last_Error( Create_Error( UOSErr_Device_Not_Mounted ) ) ;
        exit ;
    end ;
    if( Device.FS = nil ) then
    begin
        Set_Last_Error( Create_Error( UOSErr_Device_Not_File_Structured ) ) ;
        exit ;
    end ;
Next we get the device for the file. If the device is not found, not mounted, or has no file system, we exit with an error.

    // Get normalized filename...
    S := Edit( copy( S, Dummy + 1, length( S ) ), 8 or 16 or 128 ) ; // Extract filename from spec and trim
    Dummy := pos( ' \', S ) ;
    while( Dummy <> 0 ) do
    begin
        delete( S, Dummy, 1 ) ;
        Dummy := pos( ' \', S ) ;
    end ;
    Dummy := pos( '\ ', S ) ;
    while( Dummy <> 0 ) do
    begin
        delete( S, Dummy + 1, 1 ) ;
        Dummy := pos( '\ ', S ) ;
    end ;
    while( ( copy( S, 1, 3 ) = '\.\' ) or ( copy( S, 1, 2 ) = '.\' ) ) do
    begin
        if( copy( S, 1, 1 ) = '\' ) then
        begin
            S := copy( S, 3, length( S ) ) ;
        end else
        begin
            S := copy( S, 2, length( S ) ) ;
        end ;
    end ;
    if( pos( '*', S ) + pos( '?', S ) + pos( '/', S ) + pos( '\\', S ) > 0 ) then
    begin // Cannot open a wildcard, syntax error, or a name with a slash in it
        Set_Last_Error( Create_Error( UOSErr_Invalid_Filename ) ) ;
        exit ;
    end ;
    S := Device.FS.Normalize( PAnsiChar( S ) ) ;
Now we normalize the file name by extracting the name from the full specification, trimming leading/trailing spaces, and validating the absence of a slash. Then we pass the name to the file system to normalize it further. If the filename contains a wildcard, we exit with an error. Although wildcards are useful for searches, the file open requires an actual file name.

    // Create file object...
    F := nil ;
    if( S <> '' ) then
    begin
        F := Device.FS.Get_File( PChar( S ) ) ;
    end else
    begin
        F := Device._File ; // Device
    end ;
    if( F <> nil ) then
    begin
        F.Attach ;
    end ;
The user can specify a file on a file system or specify the device itself. If S is null, the request is for the device. We get the file interface object for the device or file, as appropriate.

    if( ( F = nil ) and ( ( Mode and FAB_V_CIF ) <> 0 ) ) then
    begin
        // Create if file doesn't exist
        I := RPos( '\', S ) ;
        FI := Device.FS.Get_File_Info( PChar( copy( S, 1, I - 1 ) ), 0 ) ;
        if( not Validate_Protection( PID, Device.FS, FAB_V_PUT, FI.Owner, FI.Flags, FI.ACL ) ) then
        begin
            Set_Last_Error( Create_Error( UOSErr_Protection_Violation ) ) ;
            exit ;
        end ;
        if( USC.Check_Quota( Kernel.PID, Quota_FILLM, 1 ) ) then
        begin
            Set_Last_Error( Create_Error( UOSErr_Quota_Exceeded ) ) ;
            exit ;
        end ;
        if( Device.FS.Last_Error <> nil ) then // File doesn't exist
        begin
            fillchar( FI, sizeof( FI ), 0 ) ;
            FI.Creator := USC.Get_Process_Info( PID, JPI_UIC ) ;
            FI.Owner := FI.Creator ;
            FI.Creation := HAL.Timestamp ;
            FI.Flags := FI.Flags or USC.Get_Process_Info( PID, JPI_RMS_FILEPROT ) ;
            UEC := Device.FS.Create_File( PChar( S ), FI ) ;
            if( UEC <> nil ) then // Failure
            begin
                USC.Check_Quota( Kernel.PID, Quota_FILLM, -1 ) ;
                Set_Last_Error( UEC ) ;
                exit ;
            end ;
            F := Device.FS.Get_File( PChar( S ) ) ;
        end ; // if( Device.FS.Last_Error <> nil )
    end ; // if( ( F = nil ) and ( ( Mode and FAB_V_CIF ) <> 0 ) )
    if( F = nil ) then
    begin
        Set_Last_Error( Create_Error( Device.FS.Translate_Error, Device.FS.Last_Error ) ) ;
        exit ;
    end ;
If F is nil, the specified file doesn't exist. In that case, if the user specified the create-if-doesn't-exist flag (FAB_V_CIF), we attempt to create the file. This requires us to check the protection of the directory we are going to create the file in. If it doesn't allow write access (FAB_V_PUT) to for this process, we exit with an error. Otherwise, we check the file limit (open file quota) and exit if we exceed the quota. Otherwise, we create the new file and get the file interface for that file. At the end, whether we want to create a new file or open an existing file, if F is still nil, the file doesn't exist so we exit with an error.

    // See if already open...
    if( S <> '' ) then // Have a filename
    begin
        FI := Device.FS.Get_File_Info( PChar( S ), 0 ) ;
        if( not Validate_Protection( PID, Device.FS, Mode, FI.Owner, FI.Flags, FI.ACL ) ) then
        begin
            Set_Last_Error( Create_Error( UOSErr_Protection_Violation ) ) ;
            exit ;
        end ;
        if( USC.Check_Quota( Kernel.PID, Quota_FILLM, 1 ) ) then
        begin
            Set_Last_Error( Create_Error( UOSErr_Quota_Exceeded ) ) ;
            exit ;
        end ;
        Open_Files := Device.Open_File( S, F ) ;
        F := Open_Files._File ; // Get current open file
    end ; // if( S <> '' )

    Result := TFiP_File.Create( _Kernel ) ;
    TFiP_File( Result )._File := F ;
    if( S <> '' ) then
    begin
        Open_Files._File := F ;
    end else
    begin
        Device._File := F ;
    end ;
end ; // TUOS_FiP.Open_File
If we get to this point, we have a file instance. If it is a file, as opposed to a device, we get the file information and check that the file protection allows the user for this process to access the file with the requested mode. If not, we exit with an error. Otherwise, we call the Device's Open_File method. We will address this below. Note that we will address the file protection handling in a future article.

Next we create a TFiP_File wrapper instance for the file system file instance to return to the caller. Then we make sure the file instance is saved to the Device or Open_Files structure.

The file interface returned by the UOS native file system is cached so that multiple calls to the file system to open the file all return the same instance. This saves memory and time. However, we cannot be sure that this will be true of all possible file systems we may use. Thus, the File Processor caches the file instance in the Open_Files structure. We cache the file instance not only for the sake of saving memory, but it allows us to do data caching and locking operations that would be much more complicated if we had to coordinate between multiple instances of the same file. It will also have ramifications on such operations as renaming or moving directory trees. We will cover these topics in the future.

The structure that we use to cache files is a tree where the root is the root directory. For each subdirectory, there is a link from the root. For each node in the tree, we also store the file instance for that file/directory (nil if not opened). When opening an existing file, we traverse the tree, one directory level at a time, until we reach the final directory in which the file exists. Then we have a node for that file as well. If the file is a directory, it may also have children nodes. When the file is not already open, or is being created, we construct the nodes for the path and the code above assigns the file instance to the terminal node.

Although we won't cover this until the future, this structure also allows us to traverse the open files structure for any given device to see what files are currently open on that device. Here is a sample of the tree structure.

In this sample, the top boxes indicate two different devices. The other square boxes represent nodes in the Open_Files tree for each device, with the name of the file (or directory) at that level. The boxes with rounded corners represent the file system file instance for those files, containing the number of handles associated with that file. For example, we can see that DISKA1:\UOS\Users\Don\MyFile.txt is opened once, as is DISKA1:\UOS\Users\Fred\login.com. DISKA1:\UOS\Users\Lisa (a directory) is opened once. Remember that directories are a type of file on UOS. On DISKA0, there is one handle for the DISKA0:\UOS directory, one handle for DISKA0:\UOS\uos1\startup.com, two handles for DISKA0:\UOS\uos1\login.exe, and five handles for DISKA0:\UOS\uos1\messages.txt. Note also that there is a handle associated with DISKA1: as well, indicating that someone is accessing the store in a non-file-structured manner.

Each file instance maintains a list of handles associated with that file. The file instance does nothing with these handles (it doesn't know anything other than that the handles are pointers), but it stores them on behalf of the File Processor.

     TOpen_Files = class
                       public // Constructors and destructors...
                           constructor Create( P : TOpen_Files ) ;

                       public // API...
                           Parent : TOpen_Files ;
                           Subdirectories : UOS_Util.TStringList ;
                           _File : TUOS_File ; // File interface for this file

                       public // API...
                           function Get_File( Name : string ;
                               _ID : int64 ) : TOpen_Files ;
                           procedure Delete_File( Name : string ;
                               _ID : int64 ) ;
                   end ; // TOpen_Files
This is the definition of the Open_Files class. Each of the squares in the above figure (other than the devices) is an instance of this class. Each instance has a pointer to it's parent (used to navigate the tree), a list of all children nodes, and the file-system file instance associated with the file.

// TOpen_Files methods...

// Constructors and destructors...

constructor TOpen_Files.Create( P : TOpen_Files ) ;

begin
   inherited Create ;

   Parent := P ;
end ;
The constructor needs no explanation.

function TOpen_Files.Get_File( Name : string ; _ID : int64 ) : TOpen_Files ;

var I, Index : integer ;
    Original, S : string ;
    New_Open_Files, This_Open_Files : TOpen_Files ;

begin
    // Setup...
    Original := Name ;
    if( copy( Name, 1, 1 ) = '\' ) then
    begin
        Name := copy( Name, 2, length( Name ) ) ;
    end ;

    // Find/create path to file...
    This_Open_Files := self ;
    while( Name <> '' ) do
    begin
        I := pos( '\', Name + '\' ) ;
        S := copy( Name, 1, I - 1 ) ;
        Name := copy( Name, I + 1, length( Name ) ) ;
        if( This_Open_Files.Subdirectories = nil ) then
        begin
            This_Open_Files.Subdirectories := UOS_Util.TStringList.Create ;
        end ;
        Index := IndexOf( Subdirectories, S ) ;
        if( Index = -1 ) then // Not found
        begin
            Index := This_Open_Files.Subdirectories.Count ;
            New_Open_Files := TOpen_Files.Create( This_Open_Files ) ;
            This_Open_Files.Subdirectories.AddObject( S, New_Open_Files ) ;
        end ;
        This_Open_Files := TOpen_Files( This_Open_Files.Subdirectories.Objects[ Index ] ) ;
    end ; // while( Name <> '' )

    Result := This_Open_Files ;
end ; // TOpen_Files.Get_File
This method will traverse the tree structure that it is the root of, for the specified file. If not found, it will create the nodes for the specified file and path. Either way, it returns the TOpen_Files instance for the file. The _ID parameter is a means of distinguishing different versions of the same file. For instance, a file might be opened, then deleted. Until all handles associated with that file are closed, the file is still open - it is only marked as deleted in the file system. Now, if another user creates a new file with the same name in the same path, the file ID is different even though the name is the same. Thus, a new node will be created for the file with the new ID. That is, there are two nodes for the file, one with the ID of the original file and one with the ID of the new file.

Note that file systems which do not allow mark-for-delete (such as FAT), do not need a unique ID for each file since the file cannot be deleted while open - files are either deleted (and gone) or not.

First we normalize the filename by removing the initial backslash, if there is one. Then we iterate through the name, parsing each directory in the path. For each one, if the path already exists in the list of children (Subdirectories), we set This_Open_Files to the new node and loop back for the next directory in the path. If the directory doesn't exist in our list of children, we add it to the list, make that our current node, and continue to the next directory.

    function IndexOf( Items : TStringList ; const Value : string ) : integer ;

    var I : integer ;
        Open_Files : TOpen_Files ;

    begin
        Result := -1 ; // Assume not found
        for I := 0 to Items.Count - 1 do
        begin
            if( Items[ I ] = Value ) then
            begin
                Open_Files := TOpen_Files( Items.Objects[ I ] ) ;
                if( Open_Files._File = nil ) then
                begin
                    Result := I ;
                    exit ;
                end ;
                if( Open_Files._File.ID = _ID ) then
                begin
                    Result := I ;
                    exit ;
                end ;
            end ; // if( Items[ I ] = Value )
        end ; // for I := 0 to Items.Count - 1
    end ; // IndexOf
This local function is used to look up a file name in the subdirectories list, matching both name and ID.

procedure TOpen_Files.Delete_File( Name : string ; _ID : int64 ) ;

var Index : integer ;
    Open_Files, Parent_Open_Files : TOpen_Files ;

begin
    // Setup...
    Open_Files := Get_File( Name, _ID ) ;
    Parent_Open_Files := Open_Files.Parent ;
    Open_Files._File := nil ;

    while( Open_Files <> nil ) do
    begin
        f( ( Open_Files.Subdirectories <> nil ) and Open_Files.Subdirectories.Count > 0 ) ) then
        begin
            exit ; // Cannot delete this level because children files are open
        end ;
        if( _File <> nil ) then
        begin
            exit ; // Cannot delete this level, because the directory itself is open
        end ;
        if( Parent_Open_Files <> nil ) then // Not at the root level
        begin
            Open_Files.Free ;
            for Index := 0 to Parent_Open_Files.Subdirectories.Count - 1 do
            begin
                if( Parent_Open_Files.Subdirectories.Objects[ Index ] = Open_Files ) then
                begin
                    Parent_Open_Files.Subdirectories.Delete( Index ) ;
                    break ;
                end ;
            end ;
        end ;
        Open_Files := Parent_Open_Files ; // Move up one level
        Parent_Open_Files := Open_Files.Parent ;
    end ; // while( Open_Files <> nil )
end ; // TOpen_Files.Delete_File
This method doesn't delete the file - it deletes the file's node from the Open Files structure. It expects that the handle to the file has already been removed. It gets the appropriate node by calling Get_File. It then iterates up the tree structure, using the Parent pointer. At each level, it checks to see if the count of children is non-zero. If so, this directory has open files somewhere further down in the structure, so we exit. Otherwise, we check the file for this level. If it is not nil, someone still has it open and we exit. Otherwise, we can free this level and proceed to the parent, where we remove the node from the children list. Then we repeat the process again.

In summary, this prunes unused nodes from the structure.

function TDevice.Open_File( const Name : string ; _File : TUOS_File ) : TOpen_Files ;

begin
    Result := Store_Files.Get_File( Name, _File.ID ) ;

    Result._File := _File ;
end ; // TDevice.Open_File


procedure TDevice.Close_File( const Name : string ; ID : int64 ) ;

begin
    Store_Files.Delete_File( Name, ID ) ;
end ;
These methods are added to the TDevice class. They simply call the appropriate method for the root Open Files structure.

Store_Files : TOpen_Files ;
We make a change from our previous definition of the TDevice so that this item now points to the root instance of the Open Files structure.


function SYS_CLOSE( FAB, Err, Suc : int64 ) : int64 ;

var Status : int64 ;
    SysRequest : TInteger3_Request ;

begin
    SysRequest.Request.Subsystem :=  UOS_Subsystem_FIP ;
    SysRequest.Request.Request := UOS_FIP_Close ;
    SysRequest.Request.Length := sizeof( SysRequest ) - sizeof( Sysrequest.Request ) ;
    SysRequest.Request.Status := integer( @Status ) ;
    SysRequest.Int1 := FAB ;
    SysRequest.Int2 := Err ;
    SysRequest.Int3 := Suc ;

    Call_To_Ring0( integer( @SysRequest ) ) ;

    Result := Status ;
end ;
This new function in the SYS unit wraps the call to the CLOSE system service.

        UOS_FIP_Close:
            begin
                UE := Enter_System_Call( Request, SReq, PID, MMC, 
                    sizeof( Integer_Request ) - sizeof( SReq ), Address ) ;
                if( UE <> nil ) then
                begin
                    Set_Last_Error( UE ) ;
                    exit ;
                end ;
                try
                    Integer3_Request := PInteger3_Request( Address ) ;

                    Status := File_Close( Integer3_Request.Int1, Integer3_Request.Int2, 
                        Integer3_Request.Int3 ) ;
                    Write_User( Kernel, Kernel.PID, Integer3_Request.Request.Status, 
                        sizeof( Status ), Status ) ;
                finally
                    Exit_System_Call( integer( Integer3_Request ), PID, MMC, 
                        sizeof( Integer3_Request ) - sizeof( SReq ) ) ;
                end ;
            end ;
This code is added to the FIP's API method.

function TUOS_FIP.File_Close( _FAB, Err, Succ : int64 ) : int64 ;

var FAB : TFAB ;
    PID : TPID ;
    Status : longint ;

begin
    // Get and validate the FAB...
    if( _FAB = 0 ) then // No FAB passed
    begin
        Generate_Exception( UOSErr_Missing_Value ) ;
        Call_To_User( Err ) ;
        exit ;
    end ;
    PID := Kernel.PID ;
    fillchar( FAB, sizeof( FAB ), 0 ) ;
    Get_User_Data( Kernel, PID, _FAB, 2, FAB, Status ) ;
    if( FAB.FAB_B_BID <> FAB_C_BID ) then
    begin
        Generate_Exception( RMS_FAB ) ;
        Call_To_User( Err ) ;
        exit ;
    end ;
    Get_User_Data( Kernel, PID, _FAB, FAB.FAB_B_BLN, FAB, Status ) ;
    if( FAB.FAB_Q_Handle = 0 ) then // No handle
    begin
        Call_To_User( Succ ) ;
        exit ; // Nothing to do
    end ;
This new method is used to close a file handle. First, we obtain and validate the passed FAB structure. Then we make sure a non-zero handle is being passed to us.

    if( Close_Handle( FAB.FAB_Q_Handle ) = nil ) then
    begin
        USC.Check_Quota( Kernel.PID, Quota_FILLM, -1 ) ;
        Call_To_User( Succ ) ;
    end else
    begin
        Call_To_User( Err ) ;
    end ;
end ; // TUOS_FIP.File_Close
Next we call Close_Handle to do the actual close operation, which also disposes of the handle. Finally, we call the success or error handler, if one was passed, as appropriate. Note that on success, we also decrement our file limit usage by 1.

function TUOS_FiP.Close_Handle( Handle : THandle ) : TUnified_Exception ;

var _File : TFiP_File ;
    I : integer ;
    Open_Files : TOpen_Files ;
    Resource : TResource ;
    S : string ;

begin
    Result := Set_Last_Error( nil ) ;
    if( not USC.Remove_Handle( Kernel.PID, Handle ) ) then
    begin
        Result := Set_Last_Error( Create_Error( UOSErr_Invalid_Handle ) ) ;
        exit ;
    end ;

    Resource := TResource( Handle ) ;
    _File := TFiP_File( Resource._File ) ;
    _File.Remove_Handle( Resource ) ;
    if( not _File.Any_Handles ) then // No more handles for this file
    begin
        S := _File.Name ;
        I := pos( ':', S ) ;
        S := copy( S, I + 1, length( S ) ) ;
        Open_Files := TDevice( Resource._Device ).Open_File( S, _File._File ) ;
        Open_Files._File := nil ;
        TDevice( Resource._Device ).Close_File( S, _File.ID ) ;
        _File.Detach ;
    end ;
    Resource.Free ;
end ; // TUOS_FiP.Close_Handle
We covered this method back in article 52, but we've made some changes. First, we now remove the handle, rather than merely validating it (if it isn't a process handle, the error condition is triggered). If the file has no more handles, we get the file name, strip off the device, and call Open_File to get the Open Files node for this file. We nil out the file instance since we're about to detach from it. Then we call Close_File to prune the Open Files structure, as appropriate.

        // Apply on-close actions/data...
        FS := TDevice( Resource._Device ).FS ;
        Info := FS.Get_File_Info( PAnsiChar( S ), 0 ) ;
        if(
            ( XAB <> 0 ) // XAB chain was provided
            and
            Validate_Protection( PID, FS, PROTECTION_OWNER_CONTROL, Info.Owner, Info.Flags, Info.ACL )
          ) then // User has control access to file
        begin
            while( XAB <> 0 ) do
            begin
                // Get type, length, and next XAB pointer...
                Get_User_Data( Kernel, PID, XAB, 10, XABALL, Status ) ;
                if( Status <> 0 ) then
                begin
                    Result := Generate_Exception( RMS_XAB ) ;
                    exit ;
                end ;
                I := XABALL.XAB_B_BLN ;

                // Get the XAB...
                case XABALL.XAB_B_COD of
                    XAB_C_PRO: // XABPRO
                        begin
                            if( I > sizeof( XABPRO ) ) then
                            begin
                                I := sizeof( XABPRO ) ;
                            end ;
                            Get_User_Data( Kernel, PID, XAB, I, XABPRO, Status ) ;
                            if( Status = 0 ) then
                            begin
                                Info.Flags :=
                                    Info.Flags and ( not $FFFF00000000 )
                                    or ( XABPRO.XAB_W_PRO shl 32 ) ;
                            end ;
                        end ;
                    XAB_C_RDT: // XABRDT
                        begin
                            if( I > sizeof( XABRDT ) ) then
                            begin
                                I := sizeof( XABRDT ) ;
                            end ;
                            Get_User_Data( Kernel, PID, XAB, I, XABRDT, Status ) ;
                            if( Status = 0 ) then
                            begin
                                Info.Last_Modified := XABRDT.XAB_Q_RDT ;
                            end ;
                        end ;
                end ; // case XABALL.COD
                XAB := XABALL.XAB_L_NXT ; // Follow link to next XAB
            end ; // while( XAB <> 0 )
            FS.Set_File_Info( PAnsichar( S ), 0, Info, Info ) ;
        end ;
    end ; // if( not _File.Any_Handles )
    Resource.Free ;
end ; // TUOS_FiP.Close_Handle
Finally, we process the XAB chain, if provided. The CLOSE service allows the file's protection code and last-modified date to be set upon closing the file. Doing this requires that the process has control authorization for the file, hence we only process the XAB chain if we validated that access to the file. If we encounter a XABPRO block, we set the file's protection accordingly. Likewise, if we encounter a XABRDT block, we update the file's last-modified timestamp.


function SYS_READ( RAB : int64 ; Err : int64 = 0 ; Succ : int64 = 0 ) : int64 ;

var Status : int64 ;
    SysRequest : TInteger_Request ;

begin
    if( RAB = 0 ) then
    begin
        Result := RMS_RAB ;
        Call_To_User( Err ) ;
        exit ;
    end ;
    Status := 0 ;
    fillchar( SysRequest, sizeof( SysRequest ), 0 ) ;
    SysRequest.Request.Subsystem :=  UOS_Subsystem_FIP ;
    SysRequest.Request.Request := UOS_FIP_Read ;
    SysRequest.Request.Length := sizeof( SysRequest ) - sizeof( TSystem_Request ) ;
    SysRequest.Request.Status := integer( @Status ) ;
    SysRequest.Int := RAB ;

    Call_To_Ring0( integer( @SysRequest ) ) ;
end ; // SYS_READ
This function is added to the SYS unit to wrap the READ system service.

        UOS_FIP_Read:
            begin
                UE := Enter_System_Call( Request, SReq, PID, MMC,
                    sizeof( Integer_Request ) - sizeof( SReq ), Address ) ;
                if( UE <> nil ) then
                begin
                    Set_Last_Error( UE ) ;
                    exit ;
                end ;
                try
                    Integer_Request := PInteger_Request( Address ) ;

                    Status := File_Read( Integer_Request.Int, IOSB ) ;
                    Write_User_int64( Kernel, PID, Integer_Request.Request.Status,
                        IOSB.r_io_64.r_bcnt_32.l_bcnt ) ;
                finally
                    Exit_System_Call( integer( Integer_Request ), PID, MMC,
                        sizeof( Integer_Request ) - sizeof( SReq ) ) ;
                end ;
            end ;
This code is added to the FIP's API method.

function TUOS_FiP.File_Read( _RAB : int64 ; var IOSB : TIOSB ) : int64 ;

var RAB : TRAB ;
    Resource : TResource ;
    S : string ;

begin
    if( _RAB = 0 ) then
    begin
        Result := RMS_RAB ;
        exit ;
    end ;

    // Get the RAB...
    fillchar( RAB, sizeof( RAB ), 0 ) ;
    Get_User_Data( Kernel, Kernel.PID, _RAB, 2, RAB, IOSB.r_io_64.w_status ) ; // Get RAB length
    if( IOSB.r_io_64.w_status <> 0 ) then
    begin
        Result := IOSB.r_io_64.w_status ;
        exit ;
    end ;
    if( RAB.RAB_Size > sizeof( RAB ) ) then
    begin
        RAB.RAB_Size := sizeof( RAB ) ;
    end ;
    Get_User_Data( Kernel, Kernel.PID, _RAB, RAB.RAB_Size, RAB, IOSB.r_io_64.w_status ) ; // Get RAB
    if( IOSB.r_io_64.w_status <> 0 ) then
    begin
        Result := IOSB.r_io_64.w_status ;
        exit ;
    end ;
This method is used to obtain a chunk of data from a file. First we obtain and validate the RAB structure from the user memory, exiting if there is a problem.

    // Validate...
    if( not USC.Valid_Handle( Kernel.PID, RAB.RAB_W_ISI ) ) then
    begin
        Set_Last_Error( Create_Error( UOSErr_Invalid_Handle ) ) ;
        Result := UOSErr_Invalid_Handle ;
        exit ;
    end ;
    Resource := TResource( RAB.RAB_W_ISI ) ;
    if( ( RAB.RAB_B_RAC = 0 ) or ( RAB.RAB_L_BKT <> 0 ) ) then // Binary I/O or non-zero position
    begin
        Resource.Position := RAB.RAB_L_BKT ;
    end ;
Next we validate that the process has the specified handle, and exit if not. You may recall that the TResource object contains the current position in a file, so we need to update this appropriately if the user requests a specific position in the file. In VMS, if position 0 is specified, it means reading from the current position rather than reading from position 0. That means that, on VMS, one must reopen the file to obtain the first bytes of data from it. For compatibility sake, we do the same, except in the case of the Record Access Mode (RAB_B_RAC) being 0, which indicates a UOS-specific case.

    // Read the file...
    S := Read_File( RAB.RAB_W_ISI, RAB.RAB_Data_Stream, RAB.RAB_W_USZ, 0, IOSB ) ;
    Resource.Position := Resource.Position + length( S ) ;
    IOSB.r_io_64.w_status :=
        Write_User( Kernel, Kernel.PID, RAB.RAB_L_UBF, length( S ), PAnsiChar( S )[ 0 ] ) ;
    if( IOSB.r_io_64.w_status <> 0 ) then
    begin
        Result := IOSB.r_io_64.w_status ;
        exit ;
    end ;
    RAB.RAB_W_RSZ := length( S ) ;
    IOSB.r_io_64.w_status :=
        Write_User( Kernel, Kernel.PID, _RAB, RAB.RAB_Size, RAB ) ;
    if( IOSB.r_io_64.w_status <> 0 ) then
    begin
        Result := IOSB.r_io_64.w_status ;
        exit ;
    end ;
end ; // TUOS_FiP.File_Read
Next, we read the data from the file, adjust the resource position, write the data to the user buffer, and update the user's RAB to indicate the size of the data returned.

We will cover more aspects of file I/O in the future. In the next article, we will look at some more features of UCL.

 

Copyright © 2020 by Alan Conroy. This article may be copied in whole or in part as long as this copyright is included.