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

TUser

UICs
In the last article, we described the SysUAF.DAT data structures. Now we will examine a class that uses these structures and the file heap classes to provide an interface to the SysUAF file. But first, a slight digression on User ID Codes (UICs). VMS UICs consist of a group and member number (for instance, [0,25]). UOS uses a different group mechanism, which we will discuss in future articles. UOS simply uses a single integer value for each user. Any non-negative integer value that is not greater than the maximum number of users in the SysUAF file is a valid UIC. Unless it refers to a null entry in the list. But there are some special cases that we must consider. First, UIC value 0 is used for the template user settings. That is, UIC 0 is not a user than can log in or be associated with ownership (in fact, a reference to UIC 0 in most places is an indication of an unknown, or unassigned, owner). However, within SysUAF, UIC 0 can be optionally defined. When it is defined, it serves as a template for all newly created user accounts. In terms of SysUAF, UIC 0 operates exactly like any other UIC, but outside of this module, it is not treated as a valid UIC. So, any account setup that you want to apply to new accounts by default can be done by setting up the template account exactly the way you want (although the password is not copied to new accounts.
Another special case is UIC 1. This is always reserved for the Startup account. No user can ever log into the Startup account. Its sole use is in starting up the system after the executive startup completes. In fact, that is the only way the Startup user is ever logged-in, via a call directly from the Kernel which forces the login. Thus, it makes no sense to allow the settings of the Startup account to be modified. Therefore, there really is no sense in storing the information for the account in SysUAF at all. Instead, as we will see, UIC 1 (and everything about it) is hard-coded. That way, a corrupted or missing SysUAF will never prevent system startup and/or recovery.
Finally, the UICs 2 through 7 are reserved as special "system" accounts. Normally, when a new user is created in SysUAF, it is assigned an unused UIC greater than 7. Although a user with sufficient privileges can create a new user as a "system" account (UIC<8). System UICs have some special treatment, which we will discsuss as it comes up. On VMS, system users are defined by group numbers, so this is another intentional departure from that standard.

TUser
Here is the TUser class definition:

type TUser = class( TCommon_COM_Interface )
                 public // Constructors and destructors...
                     constructor Create( UIC : longint ) ;
                     destructor Destroy ; override ;

                 protected // Instance data...
                     _UIC : longint ;
                     _Address : int64 ; // Sysuaf.dat address of user header
                     Rec : TUAF_User ;
                     Usage : TUAF_Quotas ;
                     SpinLock : int64 ;
                     Accounting, Flushed_Accounting : TUAF_Quotas ;
                     Flush_Time : int64 ;
                     Accounting_Address : int64 ; // Accounting.dat address of last accounting flush
                     Store_Usage : int64 ; // Number of bytes of storage currently in use

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

                 protected // Internal methods...
                     function Grab_String( A : int64 ) : string ;
                     procedure Update_Header ;
                     procedure Lock ;
                     procedure Unlock ;

                 public // API...
                     procedure Flush ;
                     procedure Delete ;
                     function Authentication_Count : longint ;
                     function Access_Count : longint ;
                     function Add_Authentication( Value : TUAF_Authentication ) : longint ;
                     function Add_Access( Value : TUAF_Access ) : longint ;
                     procedure Delete_Access( Index : longint ) ;
                     procedure Delete_Authentication( Index : longint ) ;
                     procedure Insert_Authentication( Index : longint ; Value : TUAF_Authentication ) ;

                 public // Property handlers...
                     procedure Set_Address( Value : int64 ) ;

                     function Get_Name : string ;
                     procedure Set_Name( Value : string ) ;
                     function Get_Flags : longint ;
                     procedure Set_Flags( Value : longint ) ;
                     function Get_Authentication( Index : longint ) : TUAF_Authentication ;
                     procedure Set_Authentication( Index : longint ; Value : TUAF_Authentication ) ;
                     function Get_Access( Index : longint ) : TUAF_Access ;
                     procedure Set_Access( Index : longint ; Value : TUAF_Access ) ;
                     function Get_Shell : string ;
                     procedure Set_Shell( Value : string ) ;
                     function Get_Home : string ;
                     procedure Set_Home( Value : string ) ;
                     function Get_LGICMD : string ;
                     procedure Set_LGICMD( Value : string ) ;
                     function Get_Privileges : int64 ;
                     procedure Set_Privileges( Value : int64 ) ;
                     function Get_Auth_Privileges : int64 ;
                     procedure Set_Auth_Privileges( Value : int64 ) ;
                     function Get_Expiration : TTimeStamp ;
                     procedure Set_Expiration( Value : TTimeStamp ) ;
                     function Get_Owner : longint ;
                     procedure Set_Owner( Value : longint ) ;
                     function Get_Priority : longint ;
                     procedure Set_Priority( Value : longint ) ;
                     function Get_Quotas : TUAF_Quotas ;
                     procedure Set_Quotas( Value : TUAF_Quotas ) ;

                 public // Properties...
                     property Address : int64
                         read _Address
                         write Set_Address ;

                     property Flags : longint
                         read Get_Flags
                         write Set_Flags ;
                     property Name : string
                         read Get_Name
                         write Set_Name ;
                     property Authentication[ Index : longint ] : TUAF_Authentication
                         read Get_Authentication
                         write Set_Authentication ;
                     property Access[ Index : longint ] : TUAF_Access
                         read Get_Access
                         write Set_Access ;
                     property Shell : string
                         read Get_Shell
                         write Set_Shell ;
                     property Home : string
                         read Get_Home
                         write Set_Home ;
                     property LGICMD : string
                         read Get_LGICMD
                         write Set_LGICMD ;
                     property Privileges : int64
                         read Get_Privileges
                         write Set_Privileges ;
                     property Auth_Privileges : int64
                         read Get_Auth_Privileges
                         write Set_Auth_Privileges ;
                     property Expiration : TTimeStamp
                         read Get_Expiration
                         write Set_Expiration ;
                     property Owner : longint
                         read Get_Owner
                         write Set_Owner ;
                     property Priority : longint
                         read Get_Priority
                         write Set_Priority ;
                     property Quotas : TUAF_Quotas
                         read Get_Quotas
                         write Set_Quotas ;
             end ; // TUser

You can tell from the properties, and the getters/setters, that this class simply provides access to the data in the SysUAF data structures in a "friendly" way that is decoupled from the underlying implementation. The class contains _UIC, which is the UIC for the user. _Address is the address of the user's header structure in SysUAF, and Rec is an in-memory copy of that structure. Rec contains the quotas for the user, and Usage is a quota structure that is used to keep track of the current usage of various quota-limited resrouces. Spinlock is used to synchronize access to SysUAF. We will discuss this below. The remaining instance data is used to keep track of accounting information.

function Cached_Users : TList ; forward ;



// TUser methods...

// Constructors and destructors...

constructor TUser.Create( UIC : longint ) ;

begin
    inherited Create ;

    _UIC := UIC ;
    Cached_Users.Add( self ) ;
end ;


destructor TUser.Destroy ;

begin
    Flush ;
    Cached_Users.Remove( self ) ;

    inherited Destroy ;
end ;

Of first note is the Cached_Users function, which the TUser class' constructor and destructor make use of. It is forward declared and we will discuss it later in this article. For now, it will suffice to understand that it returns an instance of a singleton list that contains all currently loaded users. The constructor adds the instance to the list and the destructor removes itself from the list. Note that neither the constructor or destructor will be called from outside the SysUAF module. A TUser instance is created via a function which we will describe later, and the destructor is called when the last reference to the instance detaches from it. The destructor also calls the Flush method. Flush is used to write accounting information for the user to an accounting file. We will cover the subject of user accounting, including the TUser.Flush method, in future articles.

procedure TUser.Update_Header ;

var UEC : TUnified_Exception ;

begin
    if( _UIC = 1 ) then
    begin
        Set_Last_Error( nil ) ;
        exit ;
    end ;
    _SysUAF.Write_Data( Rec, _Address, sizeof( Rec ), UEC ) ;
    Set_Last_Error( UEC ) ;
end ;


function TUser.Grab_String( A : int64 ) : string ;

var S : TStore_String ;

begin
    Set_Last_Error( nil ) ;
    Result := '' ;
    if( A = 0 ) then
    begin
        exit ;
    end ;
    S := Get_Store_String( _SysUAF, A ) ;
    Result := S.Value ;
    S.Detach ;
end ;

These utility routines are used to simplify the rest of the code. Update_Header makes sure that any changes to the user header structure are flushed to the SysUAF.DAT file. Grab_String simply converts a string address (int64) to a Pascal string, by constructing a store string, getting the value, and then detaching from the object.

procedure TUser.Lock ;

begin
    while( Spinlock = 0 ) do
    begin
        Spinlock := _SSC.Create_Spinlock( 'sysuaf' ) ;
    end ;
end ;


procedure TUser.Unlock ;

begin
    _SSC.Release_Spinlock( SpinLock ) ;
end ;

These functions allow us to synchronize access to SysUAF.DAT so that we don't have multiple simultaneous updates to the file. How simultaneous updates can happen is a subject for a future article. For now, we merely acknowledge that the situation can happen and we take appropriate steps to prevent problems. Because SysUAF.DAT is a file, we could use the file locking mechanism (also a topic for a future article) to synchronize access. But for various reasons that I won't get into at present, it makes more sense to lock access to the file via another means. Spinlocks are semaphores that are "owned" by one object at a time. Attempting to acquire a spinlock when it is already owned results in a wait loop until the spinlock is released. This wait loop is the source of the "spin" part of "spinlock". We will look at spinlocks in greater detail in the future. For now, just understand that we ask the SSC for a spinlock by name. If the Create_Spinlock method returns 0, ownership of the spinlock was not acquired, and we keep spinning (looping). Otherwise, a handle to the spinlock is returned. When we are done, we release the spinlock in the Unlock routine by passing the spinlock handle to the Release_Spinlock method of the SSC. Obviously, we need to be careful that we always release the spinlock when we are done with it - otherwise, we can hang all calls that access SysUAF.DAT.

procedure TUser.Delete ;

var I : int64 ;
    L : TStore_List ;
    Loop : integer ;

begin
    Lock ;
    try
        _SysUAF.freemem( Users_List[ _UIC ] ) ;
        Users_List[ _UIC ] := 0 ;
        Users_Names_List[ _UIC ] := '' ;

        if( Rec.Authentication <> 0 ) then
        begin
            L := Get_Store_List( _SysUAF, Rec.Authentication ) ;
            for Loop := 0 to L.Count - 1 do
            begin
                I := L[ Loop ] ;
                if( I <> 0 ) then
                begin
                    _SysUAF.Freemem( I ) ;
                end ;
            end ;
        end ;
        Delete_Store_List( _SysUAF, Rec.Authentication ) ;

        Delete_Store_String( _SysUAF, Rec.Name ) ;
        Delete_Store_List( _SysUAF, Rec.Access ) ;
        Delete_Store_String( _SysUAF, Rec.Shell ) ;
        Delete_Store_String( _SysUAF, Rec.LGICMD ) ;
        Delete_Store_String( _SysUAF, Rec.Home ) ;
    finally
        Unlock ;
    end ;
    Free ;
end ;

The Delete method physically deletes the user from SysUAF. First, it removes the items from the Users and Users_Names lists. This essentially removes the user from the file, although the various data structures for the user are still allocated within the file heap. Delete then goes through the various store utility structures in the header (strings and lists) and deletes them, along with the user header itself, and then it self-destructs.
Generally, one does not want to physically delete a user, because the UIC may have been used for file ownership on devices which are not currently connected to the system (such as thumb drives). Creating a new user might reuse that UIC and then those files would be associated with the new user. A better policy is to permanently disable the account. However, the ability to delete a user is provided for those who know what they are doing.

function TUser.Access_Count : longint ;

var L : TStore_List ;

begin
    Set_Last_Error( nil ) ;
    Result := 0 ;
    if( Rec.Access = 0 ) then
    begin
        exit ;
    end ;
    L := Get_Store_List( _SysUAF, Rec.Access ) ;
    Result := L.Count ;
    L.Detach ;
end ;


function TUser.Add_Access( Value : TUAF_Access ) : longint ;

var A : int64 ;
    L : TStore_List ;
    UEC : TUnified_Exception ;

begin
    Set_Last_Error( nil ) ;
    Result := 0 ;
    if( Rec.Access = 0 ) then
    begin
        L := Create_Store_List( _SysUAF, 4 ) ;
        Rec.Access := L.Address ;
        Update_Header ;
        if( Last_Error <> nil ) then
        begin
            L.Detach ;
            exit ;
        end ;
    end else
    begin
        L := Get_Store_List( _SysUAF, Rec.Access ) ;
    end ;
    try
        A := _SysUAF.Getmem( sizeof( Value ) ) ;
        if( A = 0 ) then // Failure
        begin
            Set_Last_Error( Create_Error( UOSErr_No_Room_On_Device, _SysUAF.Last_Error ) ) ;
            exit ;
        end ;
        Result := L.Add( int64( Value ) ) ;
    finally
        L.Detach ;
    end ;
end ;


procedure TUser.Delete_Access( Index : longint ) ;

var I : int64 ;
    L : TStore_List ;

begin
    Set_Last_Error( nil ) ;
    if( Rec.Access = 0 ) then
    begin
        Set_Last_Error( Create_Error( UOSErr_Index_Out_Of_Bounds ) ) ;
        exit ;
    end else
    begin
        L := Get_Store_List( _SysUAF, Rec.Access ) ;
        try
            if( ( Index < 0 ) or ( Index >= L.Count ) ) then
            begin
                Set_Last_Error( Create_Error( UOSErr_Index_Out_Of_Bounds ) ) ;
                exit ;
            end ;
            L[ Index ] := 0 ;
        finally
            L.Detach ;
        end ;
    end ;
end ;

These three methods allow us to manage a user's access records without the caller having to know the way the access list is implemented. We will cover the getter and setter below. Access_Count returns the number of access records associated with the user. If the Access list pointer is null, there are no records and we return 0. Otherwise we create a store list wrapper for the pointer, grab the count, detach from the store list, and return the value. Note that the access records are a set of four 2-byte values, which exactly fits in a 64-bit integer value. Thus, rather than storing a separate record with a pointer to it in the list, we simply store the value in the list.
Add_Access adds a new access record to the user. If the access list pointer is null, we create a new list, assign the pointer and update the header. Otherwise, we create a store list wrapper for the pointer. Either way, we have a store list. Finally, we add the new access record to the end of the access list.
Delete_Access removes an access record from the access list for the user. If the index of the specified record is out of range, we exit with an error (a null access list pointer means that any index is out of range). Finally, in this case, we simply set the list item to 0.

function TUser.Authentication_Count : longint ;

var L : TStore_List ;

begin
    Set_Last_Error( nil ) ;
    Result := 0 ;
    if( Rec.Authentication = 0 ) then
    begin
        exit ;
    end ;
    L := Get_Store_List( _SysUAF, Rec.Authentication ) ;
    Result := L.Count ;
    L.Detach ;
end ;


function TUser.Add_Authentication( Value : TUAF_Authentication ) : longint ;

var L : TStore_List ;
    AA : int64 ;
    UEC : TUnified_Exception ;

begin
    Set_Last_Error( nil ) ;
    Result := 0 ;
    if( Rec.Authentication = 0 ) then
    begin
        L := Create_Store_List( _SysUAF, 4 ) ;
        Rec.Authentication := L.Address ;
        Update_Header ;
        if( Last_Error <> nil ) then
        begin
            L.Detach ;
            exit ;
        end ;
    end else
    begin
        L := Get_Store_List( _SysUAF, Rec.Authentication ) ;
    end ;
    try
        AA := _SysUAF.Getmem( sizeof( Value ) ) ;
        if( AA = 0 ) then // Failure
        begin
            Set_Last_Error( Create_Error( UOSErr_No_Room_On_Device, _SysUAF.Last_Error ) ) ;
            exit ;
        end ;
        _SysUAF.Write_Data( Value, AA, sizeof( Value ), UEC ) ;
        if( UEC <> nil ) then
        begin
            Set_Last_Error( UEC ) ;
            exit ;
        end ;
        Result := L.Add( AA ) ;
    finally
        L.Detach ;
    end ;
end ; //  TUser.Add_Authentication


procedure TUser.Delete_Authentication( Index : longint ) ;

var I : int64 ;
    L : TStore_List ;

begin
    Set_Last_Error( nil ) ;
    if( Rec.Authentication = 0 ) then
    begin
        Set_Last_Error( Create_Error( UOSErr_Index_Out_Of_Bounds ) ) ;
        exit ;
    end else
    begin
        L := Get_Store_List( _SysUAF, Rec.Authentication ) ;
        try
            if( ( Index < 0 ) or ( Index >= L.Count ) ) then
            begin
                Set_Last_Error( Create_Error( UOSErr_Index_Out_Of_Bounds ) ) ;
                exit ;
            end ;
            I := L[ Index ] ;
            if( I <> 0 ) then
            begin
                Old := Get_Authentication( Index ) ;
                if( Last_Error <> nil ) then
                begin
                    exit ;
                end ;
                L[ Index ] := 0 ;
                _SysUAF.freemem( I ) ;
                Delete_Store_String( _SysUAF, Old.Description ) ;
                Delete_Store_String( _SysUAF, Old.Auth ) ;
            end ;
        finally
            L.Detach ;
        end ;
    end ;
end ; // TUser.Delete_Authentication


procedure TUser.Insert_Authentication( Index : longint ; Value : TUAF_Authentication ) ;

var AA : int64 ;
    L : TStore_List ;
    UEC : TUnified_Exception ;

begin
    // Create authentication record...
    AA := _SysUAF.Getmem( sizeof( Value ) ) ;
    if( AA = 0 ) then // Failure
    begin
        Set_Last_Error( Create_Error( UOSErr_No_Room_On_Device, _SysUAF.Last_Error ) ) ;
        exit ;
    end ;
    _SysUAF.Write_Data( Value, AA, sizeof( Value ), UEC ) ;
    Set_Last_Error( UEC ) ;
    if( Last_Error <> nil ) then
    begin
        exit ;
    end ;

    // Get Authentication list...
    if( Rec.Authentication = 0 ) then
    begin
        L := Create_Store_List( _SysUAF, 4 ) ;
        Rec.Authentication := L.Address ;
        Update_Header ;
        if( Last_Error <> nil ) then
        begin
            L.Detach ;
            exit ;
        end ;
    end else
    begin
        L := Get_Store_List( _SysUAF, Rec.Authentication ) ;
    end ;

    // Insert it into the list...
    try
        L.Insert( Index, AA ) ;
    finally
        L.Detach ;
    end ;
end ; //  TUser.Insert_Authentication

These four methods provide an implementation-independent way to access the underlying authentication records. The first three are almost exactly the same as the corresponding methods for the access records, except that the authentication list and authentication records are used in place of the access list and access records. The main difference is that authentication records cannot be packed into the int64s that are stored in the list. Rather, the list contains pointers to the authentication records. This requires that we allocate and write new records, and deallocate existing ones, as appropriate. Note also that in Delete_Authentication, we need to make sure the strings in the record are deallocated. We do this by reading the record before we free it (and exiting on error). We can now deallocate the existing record in SysUAF. Then we delete the store strings. We will cover the getter and setter below.
Insert_Authentication is added because, although the order of access records is irrelevant, the order of authentication records is potentially significant - remember that the authentication process will proceed through these records in order, and that order may be essential to proper operation. For instance, assume that two passwords are required: if the user doesn't know which password is being requested first, he won't know which password to supply. The Add_Authentication method adds an authentication record to the end of the list. But Insert_Authentication allows us to specify the exact position of the new record within the list. The passed index indicates the index (0-based) where we want to place the new record. The record already at the index, and all that follow, are moved up one index to make room for the new entry. First we allocate space for the new record and write it to SysUAF. If this fails, we set an error and exit. If there is no authentication list, we create one. Otherwise, we load the existing store list. Then we tell the list to insert the new value, detach from the list, and exit.

procedure TUser.Set_Address( Value : int64 ) ;

var UEC : TUnified_Exception ;

begin
    _Address := Value ;
    UEC := nil ;
    if( Value <> 0 ) then
    begin
        _SysUAF.Read_Data( Rec, Value, sizeof( Rec ), UEC ) ;
    end ;
    Set_Last_Error( UEC ) ;
end ;

This property handler loads the user authorization record from SysUAF.

We come now to the rest of the getters and setters for the various TUser properties. These can be grouped into two categories: 1) values that are in the user header record, and 2) values that we must retrieve from the SysUAF file. The second category involves any of the header record items that are file heap pointers, which require a little more work to implement (such as the user name). The first category simply involves reading and writing the values in the header (such as the flags value). We'll take a closer look at some of the patterns used in different property handlers, then we'll list the rest - which will simply be minor variations of one of these patterns.

function TUser.Get_Flags : longint ;

begin
    Set_Last_Error( nil ) ;
    Result := Rec.Flags ;
end ;


procedure TUser.Set_Flags( Value : longint ) ;

begin
    Set_Last_Error( nil ) ;
    Rec.Flags := Value ;
    Update_Header ;
end ;

This is the simplest pattern - return or set the value in the header record. In the case of setting the value, we also update the header in SysUAF.

function TUser.Get_Name : string ;

begin
    Set_Last_Error( nil ) ;
    Result := Grab_String( Rec.Name ) ;
end ;


procedure TUser.Set_Name( Value : string ) ;

var S : TStore_String ;

begin
    Set_Last_Error( nil ) ;
    if( Rec.Name = 0 ) then // Not already assigned
    begin
        if( Value = '' ) then // No change
        begin
            exit ;
        end ;
        S := Create_Store_String( _SysUAF, '' ) ;
        Rec.Name := S.Address ;
        S.Detach ;
        Update_Header ;
    end else
    begin
        S := Get_Store_String( _SysUAF, Rec.Name ) ;
    end ;

    S.Value := Value ;
    S.Detach ;
end ;

These methods handle strings. The getter uses the Grab_String method, which was described above. For the setter, the process is a little more complicated. If the pointer is null, we have to create a new string, set the pointer to the new string, and update the header. Otherwise, we get a store string wrapper for the value. In either case then, we update the string value.

function TUser.Get_Authentication( Index : longint ) : TUAF_Authentication ;

var L : TStore_List ;
    UEC : TUnified_Exception ;

begin
    if( ( Rec.Authentication = 0 ) or ( Index < 0 ) ) then
    begin
        Set_Last_Error( Create_Error( UOSErr_Index_Out_Of_Bounds ) ) ;
        exit ;
    end ;
    L := Get_Store_List( _SysUAF, Rec.Authentication ) ;
    try
        if( Index >= L.Count ) then
        begin
            Set_Last_Error( Create_Error( UOSErr_Index_Out_Of_Bounds ) ) ;
            exit ;
        end ;
        _SysUAF.Read_Data( Result, L[ Index ], sizeof( Result ), UEC ) ;
        Set_Last_Error( UEC ) ;
    finally
        L.Detach ;
    end ;
end ;


procedure TUser.Set_Authentication( Index : longint ; Value : TUAF_Authentication ) ;

var L : TStore_List ;
    Old : TUAF_Authentication ;
    UEC : TUnified_Exception ;

begin
    if( ( Rec.Authentication = 0 ) or ( Index < 0 ) ) then
    begin
        Set_Last_Error( Create_Error( UOSErr_Index_Out_Of_Bounds ) ) ;
        exit ;
    end ;
    L := Get_Store_List( _SysUAF, Rec.Authentication ) ;
    try
        if( Index >= L.Count ) then
        begin
            Set_Last_Error( Create_Error( UOSErr_Index_Out_Of_Bounds ) ) ;
            exit ;
        end ;
        _SysUAF.Read_Data( Old, L[ Index ], sizeof( Old ), UEC ) ;
        if( UEC <> nil ) then
        begin
            Set_Last_Error( UEC ) ;
            exit ;
        end ;
        _SysUAF.Write_Data( Value, L[ Index ], sizeof( Value ), UEC ) ;
        Set_Last_Error( UEC ) ;
        if( UEC = nil ) then
        begin
            if( Old.Auth <> Value.Auth ) then
            begin
                Delete_Store_String( _SysUAF, Old.Auth ) ;
            end ;
            if( Old.Description <> Value.Description ) then
            begin
                Delete_Store_String( _SysUAF, Old.Description ) ;
            end ;
        end ;
    finally
        L.Detach ;
    end ;
end ;

The third pattern involves structures from a list. For the getter, we get the pointer to the structure, then read it and return the value. Obviously, if the index is out of range, or the read fails, we set an error. The setter requires that we get the pointer and then override the existing record values. Since the Authentication record contains string pointers itself, we need to free those resources in SysUAF, if the new record has different values (meaning that the old string is now unused).

Here are the rest of the getters and setters.

function TUser.Get_Access( Index : longint ) : TUAF_Access ;

var L : TStore_List ;

begin
    if( ( Rec.Access = 0 ) or ( Index < 0 ) ) then
    begin
        Set_Last_Error( Create_Error( UOSErr_Index_Out_Of_Bounds ) ) ;
        exit ;
    end ;
    Set_Last_Error( nil ) ;
    L := Get_Store_List( _SysUAF, Rec.Access ) ;
    try
        if( Index >= L.Count ) then
        begin
            Set_Last_Error( Create_Error( UOSErr_Index_Out_Of_Bounds ) ) ;
            exit ;
        end ;
        Result := TUAF_Access( L[ Index ] ) ;
    finally
        L.Detach ;
    end ;
end ;


procedure TUser.Set_Access( Index : longint ; Value : TUAF_Access ) ;

var L : TStore_List ;

begin
    if( ( Rec.Access = 0 ) or ( Index < 0 ) ) then
    begin
        Set_Last_Error( Create_Error( UOSErr_Index_Out_Of_Bounds ) ) ;
        exit ;
    end ;
    Set_Last_Error( nil ) ;
    L := Get_Store_List( _SysUAF, Rec.Access ) ;
    try
        if( Index >= L.Count ) then
        begin
            Set_Last_Error( Create_Error( UOSErr_Index_Out_Of_Bounds ) ) ;
            exit ;
        end ;
        L[ Index ] := int64( Value ) ;
    finally
        L.Detach ;
    end ;
end ;


function TUser.Get_Shell : string ;

begin
    Set_Last_Error( nil ) ;
    Result := Grab_String( Rec.Shell ) ;
end ;


procedure TUser.Set_Shell( Value : string ) ;

var S : TStore_String ;

begin
    Set_Last_Error( nil ) ;
    if( Rec.Name = 0 ) then // Not already assigned
    begin
        if( Value = '' ) then // No change
        begin
            exit ;
        end ;
        S := Create_Store_String( _SysUAF, '' ) ;
        Rec.Shell := S.Address ;
        S.Detach ;
        Update_Header ;
    end else
    begin
        S := Get_Store_String( _SysUAF, Rec.Shell ) ;
    end ;

    S.Value := Value ;
    S.Detach ;
end ;


function TUser.Get_Home : string ;

begin
    Set_Last_Error( nil ) ;
    Result := Grab_String( Rec.Home ) ;
end ;


procedure TUser.Set_Home( Value : string ) ;

var S : TStore_String ;

begin
    Set_Last_Error( nil ) ;
    if( Rec.Home = 0 ) then // Not already assigned
    begin
        if( Value = '' ) then // No change
        begin
            exit ;
        end ;
        S := Create_Store_String( _SysUAF, '' ) ;
        Rec.Home := S.Address ;
        S.Detach ;
        Update_Header ;
    end else
    begin
        S := Get_Store_String( _SysUAF, Rec.Home ) ;
    end ;

    S.Value := Value ;
    S.Detach ;
end ;


function TUser.Get_LGICMD : string ;

begin
    Set_Last_Error( nil ) ;
    Result := Grab_String( Rec.LGICMD ) ;
end ;


procedure TUser.Set_LGICMD( Value : string ) ;

var S : TStore_String ;

begin
    Set_Last_Error( nil ) ;
    if( Rec.Name = 0 ) then // Not already assigned
    begin
        if( Value = '' ) then // No change
        begin
            exit ;
        end ;
        S := Create_Store_String( _SysUAF, '' ) ;
        Rec.LGICMD := S.Address ;
        S.Detach ;
        Update_Header ;
    end else
    begin
        S := Get_Store_String( _SysUAF, Rec.LGICMD ) ;
    end ;

    S.Value := Value ;
    S.Detach ;
end ;


function TUser.Get_Privileges : int64 ;

begin
    Set_Last_Error( nil ) ;
    Result := Rec.Privileges ;
end ;


procedure TUser.Set_Privileges( Value : int64 ) ;

begin
    Set_Last_Error( nil ) ;
    Rec.Privileges := Value ;
    Update_Header ;
end ;


function TUser.Get_Auth_Privileges : int64 ;

begin
    Set_Last_Error( nil ) ;
    Result := Rec.Auth_Privileges ;
end ;


procedure TUser.Set_Auth_Privileges( Value : int64 ) ;

begin
    Set_Last_Error( nil ) ;
    Rec.Auth_Privileges := Value ;
    Update_Header ;
end ;


function TUser.Get_Expiration : TTimeStamp ;

begin
    Set_Last_Error( nil ) ;
    Result := Rec.Expiration ;
end ;


procedure TUser.Set_Expiration( Value : TTimeStamp ) ;

begin
    Set_Last_Error( nil ) ;
    Rec.Expiration := Value ;
    Update_Header ;
end ;


function TUser.Get_Owner : longint ;

begin
    Set_Last_Error( nil ) ;
    Result := Rec.Owner ;
end ;


procedure TUser.Set_Owner( Value : longint ) ;

begin
    Set_Last_Error( nil ) ;
    Rec.Owner := Value ;
    Update_Header ;
end ;


function TUser.Get_Priority : longint ;

begin
    Set_Last_Error( nil ) ;
    Result := Rec.Priority ;
end ;


procedure TUser.Set_Priority( Value : longint ) ;

begin
    Set_Last_Error( nil ) ;
    Rec.Priority := Value ;
    Update_Header ;
end ;


function TUser.Get_Quotas : TUAF_Quotas ;

begin
    Set_Last_Error( nil ) ;
    Result := Rec.Quotas ;
end ;


procedure TUser.Set_Quotas( Value : TUAF_Quotas ) ;

begin
    Set_Last_Error( nil ) ;
    Rec.Quotas := Value ;
    Update_Header ;
end ;

All of these methods use one of the aforementioned patterns. In the case of the Access list, since the records are packed into the int64s of the list, we just get/set the value in the list.

We will finish up this article with a couple API functions from the SysUAF module.

var _Cached_Users : TList = nil ;


function Cached_Users : TList ;

begin
    if( _Cached_Users = nil ) then
    begin
        _Cached_Users := TList.Create ;
    end ;
    Result := _Cached_Users ;
end ;


function Get_User( UIC : longint ) : TUser ;

var A : int64 ;
    Loop : longint ;

begin
    Result := nil ;
    if( ( UIC < 0 ) or ( UIC >= Users_List.Count ) ) then // Invalid UIC
    begin
        exit ;
    end ;

    // Check for cached user...
    for Loop := 0 to Cached_Users.Count - 1 do
    begin
        Result := TUser( Cached_Users[ Loop ] ) ;
        if( Result._UIC = UIC ) then
        begin
            exit ;
        end ;
    end ; // for

    // Load user from sysUAF...
    A := Users_List[ UIC ] ;

    // Create new user (cached) instance...
    Result := TUser.Create( UIC ) ;
    if( UIC = 1 ) then
    begin
        Result.Flags := UAF_DisCtlY or UAF_Disabled ; // No interrupts and can only be run by Kernel
        Result.Privileges := -1 ;
        Result.Auth_Privileges := -1 ;
    end else
    begin
        Result._Address := A ;
    end ;
end ; // Get_User

The Cached_Users function returns the _Cached_Users list, creating it if this is the first reference to it. The purpose of the list is so that there is only a single instance of TUser for a given user at a given time. The Get_User function searches the list for a matching UIC and returns the existing instance if found. Otherwise, a new TUser is created for the requested UIC. The TUser class instance itself takes care of adding/removing itself from the list.

In the next article, we will look at the rest of the API for the SysUAF module and how the USC makes use of it.