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

SYS_GETTIM, LIB_Get_Timestamp, SYS_ASCTIM, and LIB_SYS_ASCTIM

In the previous articles, we covered some of the date/time functions in Starlet, and date/time contexts. In this article, we will cover a few last functions.

function LIB_Get_Timestamp : int64 ;

var I : int64 ;

begin
    SYS_GETTIM( int64( @I ) ) ;
    Result := I ;
end ;
The LIB_Get_Timestamp function is a wrapper for SYS_GETTIM, returning the current system timestamp.

function SYS_GETTIM( Adr : int64 ) : int64 ;

var Status : byte ;
    SysRequest : TInteger_Request ;

begin
    fillchar( SysRequest, sizeof( SysRequest ), 0 ) ;
    SysRequest.Request.Subsystem := UOS_Subsystem_Kernel ;
    SysRequest.Request.Request := UOS_Kernel_Get_Time ;
    SysRequest.Request.Length := sizeof( SysRequest ) - sizeof( Sysrequest.Request ) ;
    SysRequest.Request.Status := integer( @Status ) ;
    SysRequest.Int := Adr ;

    Call_To_Ring0( integer( @SysRequest ) ) ;

    Result := Status ;
end ;
SYS_GETTIM has the standard interface to the executive that we've seen before.

    // Normal processing...
    Generate_Exception( 0 ) ; // Clear exception
    if( SReq.Request = UOS_Kernel_Get_Time ) then
    begin
        E := Enter_System_Call( Value, SReq, PID, MMC, sizeof( TInteger_Request ) - sizeof( SReq ), Address ) ;
        if( E <> nil ) then
        begin
            Set_Last_Error( E ) ;
            exit ;
        end ;
        try
            Integer_Request := PInteger_Request( Address ) ;
            Write_User_int64( Kernel, PID, Integer_Request.Int, HAL.Timestamp ) ;
        finally
            Exit_System_Call( Value, PID, MMC, sizeof( TItem_Request ) - sizeof( SReq ) ) ;
        end ;
        exit ;
    end ;
We add some code to the end of the Kernel's API method to handle this call. Besides the now familiar system call entry and exit code, we simply return the timestamp from the HAL.

function SYS_ASCTIM( LengthA : int64 ; SRBA : int64 ; Time : int64 = 0 ;
    Flags : cardinal = 0 ) : integer ;

begin
    Result := LIB_SYS_ASCTIM( LengthA, SRBA, Time, Flags ) ;
end ;
In VMS, LIB_SYS_ASCTIM is a wrapper for SYS_ASCTIM, and SYS_ASCTIM is handled in the executive. The situation is reversed in UOS for the reasons we discussed in couple articles ago. In UOS, all time formatting functions reside in starlet. So, this function is here only for compatibility with VMS.

// Flags: 0 = Date and time, 1 = time only, 2 = date only
function LIB_SYS_ASCTIM( LengthA : int64 ; SRBA : int64 ; Time : int64 = 0 ;
    Flags : cardinal = 0 ) : integer ;

var DoW : int64 ;
    I : integer ;
    SRB : PSRB ;
    Res : string ;
    SL : TStringList ;
    T : string ;
    Y, Mo, D, H, H12, M, Sec : word ;
    NS : longint ;
    TimePos : integer ;

begin
    // Setup and checks...
    Result := SS_NORMAL ; // Assume success
    if( LengthA = 0 ) then
    begin
        exit ;
    end ;
    if( SRBA = 0 ) then
    begin
        pint64( LengthA )^ := 0 ;
        exit ;
    end ;
    SRB := PSRB( SRBA ) ;
    if( ( SRB^.Length = 0 ) or ( SRB^.Buffer = 0 ) ) then
    begin
        pint64( LengthA )^ := 0 ;
        exit ;
    end ;
    if( Time = 0 ) then
    begin
        Time := LIB_Get_Timestamp ;
    end ;
    TimePos := 32767 ;
This function converts a timestamp to a string representation. "ASCTIM" stands for "ASCII Time". But since UOS uses UTF-8, it is a bit of a misnomer. However, we keep the name for compatibility sake with VMS. The Flags parameter indicates whether we want date and time, only date, or only time. However, it should be noted that if asked for both date and time, but the output string isn't large enough to hold both, only the date is returned.

First, we check that no null string pointers or lengths were passed to us. If one is found, we set the result length to 0 (unless the result address is 0 as well), and exit. If the passed time is 0, that means that the caller wants the current date/time, so we get it in that case. Then we set the TimePos variable to a large value (we picked 32767 because it is doubtful that a date/time string will ever approach that length). It will mark the position of the start of the time in the output string.

    // Convert time to ASCII...
    Res := '' ;
    if( Time < 0 ) then // Delta time
    begin
        Time := -Time ;
        Parse_Sirius_Timestamp( Time, Y, Mo, D, H, M, Sec, NS ) ;
        Res := '+' ;
        if( ( Flags and 1 ) = 0 ) then // Not requesting only time
        begin
            Res := Res + inttostr( Time div Day_Value ) ;
        end ;
        Res := Res + ':' + inttostr( H ) + ':' + inttostr( M ) + ':' + inttostr( Sec ) ;
        if( length( Res ) > SRB^.Length ) then
        begin
            setlength( Res, SRB^.Length ) ;
        end ;
        pint64( LengthA )^ := length( Res ) ;
        move( PChar( Res )[ 0 ], PChar( SRB^.Buffer )[ 0 ], length( Res ) ) ;
        exit ;
    end ;
If the timestamp passed in was negative, it indicates a delta time. So we negate it to make it positive, and then we parse the positive value. The output of a delta time will always start with a plus (+), so we initialize the result to that. If flags is 0, we add the day count to the result. Then we add the hour, minute, and second, delimited by colons (:). If the destination buffer is too small, we truncate our result to the size of that buffer. Then we set the result length and copy the output string to the buffer, and exit.

    // Get time and day of week...
    Parse_Sirius_Timestamp( Time, Y, Mo, D, H, M, Sec, NS ) ;
    H12 := H ;
    if( H > 11 ) then
    begin
        H12 := H - 12 ;
    end ;
    if( H12 = 0 ) then
    begin
        H12 := 12 ;
    end ;
Next, we parse the timestamp. The hour is always returned as a value from 0 to 23. Since the output format may request a 12-hour time, we calculate what the hour would be in 12-hour time (1-12).

    // Absolute time...

    // Get day of week...
    DoW := Time div Week_Value ;
    Dow := Time - Dow * Week_Value ;
    Dow := DoW div Day_Value + 1 ;
    if( Dow > 7 ) then
    begin
        Dow := Dow - 7 ;
    end ;
Since we may need the day of the week, we calculate it from the given time.

    // Get parsed list...
    case Flags and 3 of
        0 : // Requesting date and time
            SL := Default_Time_Context.Parsed_Output_Date_Time_List ;
        1 : // Requesting time only
            SL := Default_Time_Context.Parsed_Output_Time_List ;
        2 : // Requesting date only
            SL := Default_Time_Context.Parsed_Output_Date_List ;
    end ;
Depending upon the flags, we obtain the output format for date/time, date, or time. Now we're prepared for the conversion of an absolute time.

    // Build output string...
    for I := 0 to SL.Count - 1 do
    begin
        T := SL[ I ] ;
        if( copy( T, 1, 1 ) <> '!' ) then // A delimiter
        begin
            Res := Res + T ;
        end else
        begin
            // Date...
            if( T = '!D0' ) then
            begin
                Res := Res + LPad( inttostr( D ), 2, '0' ) ;
            end else
            if( T = '!DB' ) then
            begin
                Res := Res + LPad( inttostr( D ), 2, ' ' ) ;
            end else
            if( T = '!DD' ) then
            begin
                Res := Res + inttostr( D ) ;
            end else
            if( T = '!MAAC' ) then
            begin
                Res := Res + Default_Time_Context.Parsed_List( LIB_K_MONTH_NAME_ABB_C )[ Mo - 1 ] ;
            end else
            if( T = '!MAAL' ) then
            begin
                Res := Res + Default_Time_Context.Parsed_List( LIB_K_MONTH_NAME_ABB_L )[ Mo - 1 ] ;
            end else
            if( T = '!MAAU' ) then
            begin
                Res := Res + Default_Time_Context.Parsed_List( LIB_K_MONTH_NAME_ABB_U )[ Mo - 1 ] ;
            end else
            if( T = '!MAC' ) then
            begin
                Res := Res + Default_Time_Context.Parsed_List( LIB_K_MONTH_NAME_C )[ Mo - 1 ] ;
            end else
            if( T = '!MAL' ) then
            begin
                Res := Res + Default_Time_Context.Parsed_List( LIB_K_MONTH_NAME_L )[ Mo - 1 ] ;
            end else
            if( T = '!MAU' ) then
            begin
                Res := Res + Default_Time_Context.Parsed_List( LIB_K_MONTH_NAME_U )[ Mo - 1 ] ;
            end else
            if( T = '!MN0' ) then
            begin
                Res := Res + LPad( inttostr( Mo ), 2, '0' ) ;
            end else
            if( T = '!MNB' ) then
            begin
                Res := Res + LPad( inttostr( Mo ), 2, ' ' ) ;
            end else
            if( T = '!MNM' ) then
            begin
                Res := Res + inttostr( Mo ) ;
            end else
            if( T = '!WAC' ) then
            begin
                Res := Res + Default_Time_Context.Parsed_List( LIB_K_WEEKDAY_NAME_ABB_C )[ DOW - 1 ] ;
            end else
            if( T = '!WAL' ) then
            begin
                Res := Res + Default_Time_Context.Parsed_List( LIB_K_WEEKDAY_NAME_ABB_L )[ DOW - 1 ] ;
            end else
            if( T = '!WAU' ) then
            begin
                Res := Res + Default_Time_Context.Parsed_List( LIB_K_WEEKDAY_NAME_ABB_U )[ DOW - 1 ] ;
            end else
            if( T = '!WC' ) then
            begin
                Res := Res + Default_Time_Context.Parsed_List( LIB_K_WEEKDAY_NAME_C )[ DOW - 1 ] ;
            end else
            if( T = '!WL' ) then
            begin
                Res := Res + Default_Time_Context.Parsed_List( LIB_K_WEEKDAY_NAME_L )[ DOW - 1 ] ;
            end else
            if( T = '!WU' ) then
            begin
                Res := Res + Default_Time_Context.Parsed_List( LIB_K_WEEKDAY_NAME_U )[ DOW - 1 ] ;
            end else
            if( T = '!Y4' ) then
            begin
                Res := Res + LPad( inttostr( Y ), 4, '0' ) ;
            end else
            if( T = '!Y3' ) then
            begin
                Res := Res + LPad( Rightmost( inttostr( Y ), 3 ), 3, '0' ) ;
            end else
            if( T = '!Y2' ) then
            begin
                Res := Res + LPad( Rightmost( inttostr( Y ), 2 ), 2, '0' ) ;
            end else
            if( T = '!Y1' ) then
            begin
                Res := Res + Rightmost( inttostr( Y ), 3 ) ;
            end else
            if( T = '!Z4' ) then
            begin
                Res := Res + LPad( inttostr( Y ), 4, '0' ) ;
            end else
            if( T = '!Z3' ) then
            begin
                Res := Res + LPad( Rightmost( inttostr( Y ), 3 ), 3, '0' ) ;
            end else
            if( T = '!Z2' ) then
            begin
                Res := Res + LPad( Rightmost( inttostr( Y ), 2 ), 2, '0' ) ;
            end else
            if( T = '!Z1' ) then
            begin
                Res := Res + Rightmost( inttostr( Y ), 3 ) ;
            end else
Now we can iterate through the parsed list. Each loop, we get the next item from the list. If it doesn't start with an exclamation (!), it is a delimiter and we simply add it to the output string. Otherwise, we process the code in this item. For instance, "!D0" indicates that we want a zero-padded two-digit day of month. So, based on the code, we build the appropriate part of the output string.

            begin
                // Time...
                if( length( Res ) < TimePos ) then
                begin
                    TimePos := Length( Res ) ;
                end ;
                if( T = '!C1' ) then
                begin
                    Res := Res + copy( LPad( inttostr( NS ), 9, '0' ), 1, 1 ) ;
                end else
                if( T = '!C2' ) then
                begin
                    Res := Res + copy( LPad( inttostr( NS ), 9, '0' ), 1, 2 ) ;
                end else
                if( T = '!C3' ) then
                begin
                    Res := Res + copy( LPad( inttostr( NS ), 9, '0' ), 1, 3 ) ;
                end else
                if( T = '!C4' ) then
                begin
                    Res := Res + copy( LPad( inttostr( NS ), 9, '0' ), 1, 4 ) ;
                end else
                if( T = '!C5' ) then
                begin
                    Res := Res + copy( LPad( inttostr( NS ), 9, '0' ), 1, 5 ) ;
                end else
                if( T = '!C6' ) then
                begin
                    Res := Res + copy( LPad( inttostr( NS ), 9, '0' ), 1, 6 ) ;
                end else
                if( T = '!C7' ) then
                begin
                    Res := Res + copy( LPad( inttostr( NS ), 9, '0' ), 1, 7 ) ;
                end else
                if( T = '!H02' ) then
                begin
                    Res := Res + LPad( inttostr( H12 ), 2, '0' ) ;
                end else
                if( T = '!H04' ) then
                begin
                    Res := Res + LPad( inttostr( H ), 2, '0' ) ;
                end else
                if( T = '!HB2' ) then
                begin
                    Res := Res + LPad( inttostr( H12 ), 2, ' ' ) ;
                end else
                if( T = '!HB4' ) then
                begin
                    Res := Res + LPad( inttostr( H ), 2, ' ' ) ;
                end else
                if( T = '!HH2' ) then
                begin
                    Res := Res + inttostr( H12 ) ;
                end else
                if( T = '!HH4' ) then
                begin
                    Res := Res + inttostr( H ) ;
                end else
                if( T = '!M0' ) then
                begin
                    Res := Res + LPad( inttostr( M ), 2, '0' ) ;
                end else
                if( T = '!MB' ) then
                begin
                    Res := Res + LPad( inttostr( M ), 2, ' ' ) ;
                end else
                if( T = '!MM' ) then
                begin
                    Res := Res + inttostr( M ) ;
                end else
                if( T = '!S0' ) then
                begin
                    Res := Res + LPad( inttostr( Sec ), 2, '0' ) ;
                end else
                if( T = '!SB' ) then
                begin
                    Res := Res + LPad( inttostr( Sec ), 2, ' ' ) ;
                end else
                if( T = '!SS' ) then
                begin
                    Res := Res + inttostr( Sec ) ;
                end else
                if( T = '!MIU' ) then
                begin
                    if( H > 11 ) then
                    begin
                        Res := Res + Default_Time_Context.Parsed_List( LIB_K_MERIDIEM_INDICATOR_U )[ 0 ] ;
                    end else
                    begin
                        Res := Res + Default_Time_Context.Parsed_List( LIB_K_MERIDIEM_INDICATOR_U )[ 1 ] ;
                    end ;
                end else
                if( T = '!MIC' ) then
                begin
                    if( H > 11 ) then
                    begin
                        Res := Res + Default_Time_Context.Parsed_List( LIB_K_MERIDIEM_INDICATOR_C )[ 0 ] ;
                    end else
                    begin
                        Res := Res + Default_Time_Context.Parsed_List( LIB_K_MERIDIEM_INDICATOR_C )[ 1 ] ;
                    end ;
                end else
                if( T = '!MIL' ) then
                begin
                    if( H > 11 ) then
                    begin
                        Res := Res + Default_Time_Context.Parsed_List( LIB_K_MERIDIEM_INDICATOR_L )[ 0 ] ;
                    end else
                    begin
                        Res := Res + Default_Time_Context.Parsed_List( LIB_K_MERIDIEM_INDICATOR_L )[ 1 ] ;
                    end ;
                end ;
            end ;
        end ; // if( copy( T, 1, 1 ) <> '!' )
    end ; // for I := 0 to SL.Count - 1
Note that the previous section of code dealt only with date codes. All of the time codes are handled in an else. This is so we can set the TimePos variable to the current output position if that position is smaller than its current value. In other words, as soon as we encounter a time entry, we set TimePos to that position, so we know where the time specification begins. Like the date codes, we handle the time codes by substituting the appropriate value for the given code. The handling of the "!H02" code is where we use the H12 value that we set above.

    // Trim if too long...
    if( ( Flags and 1 ) = 0 ) then // Wanted both date and time
    begin
        if( length( Res ) > SRB^.Length ) then // Can't fit both in target buffer
        begin
            Res := copy( Res, 1, TimePos - 1 ) ; // Trim off time portion
        end ;
    end ;
Now that we've built the output string, it is time to handle trimming it, if needed. If the flags indicate that both date and time are needed, but the output is longer than the target buffer, we need to trim the output at the end of the date portion. This is where the TimePos variable comes in. We trim the output string to the position just prior to the first time code.

    // Return value...
    if( length( Res ) > SRB^.Length ) then // Can't fit into target buffer
    begin
        setlength( Res, SRB^.Length ) ; // Trim to fit
    end ;
    pint64( LengthA )^ := length( Res ) ;
    move( PChar( Res )[ 0 ], PChar( SRB^.Buffer )[ 0 ], length( Res ) ) ;
end ; // LIB_SYS_ASCTIM
Even with the above possible trimming of the output, the result can still be longer than the target buffer. So, at this point, we trim the output again, if necessary. Then we write the output and it's length back to the calling code's addresses. Although the code is lengthly, it is fairly straight-forward.

In the next article, we'll cover the next lexical function.

 

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