|
|
DataMuseum.dkPresents historical artifacts from the history of: Rational R1000/400 |
This is an automatic "excavation" of a thematic subset of
See our Wiki for more about Rational R1000/400 Excavated with: AutoArchaeologist - Free & Open Source Software. |
top - metrics - download
Length: 28672 (0x7000)
Types: Ada Source
Notes: 03_class, FILE, R1k_Segment, e3_tag, package body File_Support, seg_04b932
└─⟦8527c1e9b⟧ Bits:30000544 8mm tape, Rational 1000, Arrival backup of disks in PAM's R1000
└─⟦cfc2e13cd⟧ »Space Info Vol 2«
└─⟦this⟧
with System;
use System;
with Io_Exceptions;
use Io_Exceptions;
with Os_Files;
use Os_Files;
with A_Strings;
use A_Strings;
with Close_All;
with Unchecked_Deallocation;
with Unchecked_Conversion;
with Safe_Support;
with Ada_Krn_I;
with Ada_Krn_Defs;
pragma Elaborate (Io_Exceptions, Os_Files);
package body File_Support is
pragma Suppress (Access_Check);
pragma Suppress (Discriminant_Check);
pragma Suppress (Index_Check);
pragma Suppress (Length_Check);
pragma Suppress (Range_Check);
pragma Suppress (Division_Check);
pragma Suppress (Overflow_Check);
-- NOTE: to inhibit mutex deadlock, when file_support is locked,
-- no file can be locked. After doing a file_support_lock(), file_lock()
-- is called in check_file_already_open().
Char_Size : constant := Character'Size / 8;
-- Access to buffer_pool must be protected via
-- safe_support.file_support_lock().
Buffer_Pool : array (0 .. 4) of Access_Bytes;
Open_Modes : constant array (File_Mode) of Open_Flags :=
(Input => O_Rdonly, Output => O_Wronly, In_Out => O_Rdwr);
procedure Free is new Unchecked_Deallocation (File_Record, File_Ptr);
procedure Free is new Unchecked_Deallocation (String_Rec, A_String);
function To_Long_Addr is new Unchecked_Conversion (Address, Long_Address);
-- Upon entry/exit: file is unlocked
procedure Free_File (F : in out File_Ptr) is
Other_Fd : File_Ptr;
begin
if F = null or else F.Style = Special then
return;
end if;
Safe_Support.File_Support_Lock; -- protect linked list of files
Other_Fd := File_List;
F.File_Id := Invalid;
if F = Other_Fd then
File_List := F.Next;
else
while Other_Fd /= null loop
if Other_Fd.Next = F then
Other_Fd.Next := F.Next;
exit;
end if;
Other_Fd := Other_Fd.Next;
end loop;
end if;
Safe_Support.File_Support_Unlock;
Safe_Support.File_Mutex_Destroy (F);
if F.Name /= null then
Free (F.Name);
end if;
if F.Form /= null then
Free (F.Form);
end if;
Free (F);
end Free_File;
procedure Put_Buffer (B : in out Access_Bytes) is
procedure Free is new Unchecked_Deallocation (Buffer_Rec, Access_Bytes);
begin
Safe_Support.File_Support_Lock; -- protect buffer pool
for I in Buffer_Pool'Range loop
if Buffer_Pool (I) = null then
Buffer_Pool (I) := B;
Safe_Support.File_Support_Unlock;
return;
end if;
end loop;
Safe_Support.File_Support_Unlock;
Free (B);
end Put_Buffer;
function Get_Buffer (Size : Integer) return Access_Bytes is
B : Access_Bytes;
begin
Safe_Support.File_Support_Lock; -- protect buffer pool
for I in Buffer_Pool'Range loop
if Buffer_Pool (I) /= null then
if Buffer_Pool (I).Size = Size then
B := Buffer_Pool (I);
Buffer_Pool (I) := null;
Safe_Support.File_Support_Unlock;
return B;
end if;
end if;
end loop;
Safe_Support.File_Support_Unlock;
return new Buffer_Rec (Size);
end Get_Buffer;
procedure Write_Buffer (File : File_Ptr);
-- Upon entry/exit: file_support is locked, the file is unlocked.
-- Also upon entry, the file being opened hasn't been added to the
-- file list.
procedure Check_File_Already_Open (File : File_Ptr) is
Other_Fd : File_Ptr;
begin
if File.File_Id = Invalid then
return;
end if;
-- cross-io may try to reopen stdin & stdout--
if File.Style = Special then
return;
end if;
Other_Fd := File_List;
while (Other_Fd /= null) loop
if Same_Id (File.File_Id, Other_Fd.File_Id) then
begin
Safe_Support.File_Lock (Other_Fd);
if not Flushable (Other_Fd.Fd) or else
not Flushable (File.Fd) then
raise Use_Error;
end if;
if Other_Fd.Mode = Input then
if Other_Fd.Pos /= Unknown and then
File.Mode /= Input then
raise Use_Error;
end if;
elsif Other_Fd.Out_Ptr /= -1 then
Write_Buffer (Other_Fd);
end if;
Other_Fd.Always_Flush := True;
File.Always_Flush := True;
Safe_Support.File_Unlock (Other_Fd);
exception
when others =>
Safe_Support.File_Unlock (Other_Fd);
raise;
end;
end if;
Other_Fd := Other_Fd.Next;
end loop;
end Check_File_Already_Open;
-- Upon exit: the file is unlocked
procedure File_Open (Name : String := "";
File : in out File_Ptr;
Mode : File_Mode := In_Out;
Create : Boolean := False;
Form : String := "";
Style : File_Styles := Text;
Record_Size : Integer := 0) is
Temp : Name_String;
Len : Integer := 0;
Open_Mode : Open_Flags;
This_Name : A_String;
This_Form : A_String;
Is_Temp : Boolean := Name'Length = 0;
begin
if File /= null then
raise Status_Error;
end if;
--
-- a null string is used to create a temporary file\x09
--
if Is_Temp and then not Create then
raise Name_Error;
end if;
This_Name := Get_Full_Name (Name, Style);
if Form = "" then
This_Form := null;
else
This_Form := new String_Rec (Form'Length);
This_Form.S := Form;
end if;
Open_Mode := Open_Modes (Mode);
if Create then
Open_Mode := Open_Mode + O_Creat + O_Trunc;
elsif Style /= Direct and Mode /= Input then
Open_Mode := Open_Mode + O_Trunc;
end if;
File := new File_Record;
if not Safe_Support.File_Mutex_Init (File) then
raise Storage_Error;
end if;
File.Fd := File_Descriptor (0);
File.Name := This_Name;
File.Mode := Mode;
File.Form := This_Form;
File.Style := Style;
File.Resetable := True;
File.Index := 1;
File.Linelength := Record_Size;
File.Pagelength := 0;
File.Line := 1;
File.Page := 1;
File.Pos := Unknown;
File.Delete := Is_Temp;
File.File_Id := Invalid;
File.Eof_Char := Ascii.Eot;
File.Test_Eof := False;
File.Buffer := null;
File.Last := -1;
File.Last_Lf := -1;
File.In_Ptr := -1;
File.Out_Ptr := -1;
File.Always_Flush := Os_Files.Always_Flush_Files;
File.Want_Ff := False;
--\x09\x09file.next\x09\x09-- initialized below
--\x09\x09file.mutex\x09\x09-- initialized above by safe_support.file_mutex_init()
-- we now have an initialized file that can be free_file()'ed
begin
Safe_Support.File_Support_Lock;
Open (File.all'Long_Address, Style, Open_Mode);
Check_File_Already_Open (File);
-- for acvc ce3208a:
-- AS A RESULT OF AI-00048, OPENING A FILE WITH IN_FILE MODE WHICH
-- IS THE DEFAULT OUTPUT FILE WILL RAISE MODE_ERROR, and vice-versa.
if Mode /= Input and then Same_Id
(File.File_Id, Cur_Input_Id) and then
Name /= Os_Files.Std_Error_Name and then
Name /= Os_Files.Std_Output_Name then
raise Mode_Error;
end if;
if Mode /= Output and then Same_Id
(File.File_Id, Cur_Output_Id) and then
Name /= Os_Files.Std_Input_Name then
raise Mode_Error;
end if;
-- Add just opened file to list of files
File.Next := File_List;
File_List := File;
Safe_Support.File_Support_Unlock;
exception
when others =>
Safe_Support.File_Support_Unlock;
if File.Fd /= File_Descriptor (0) then
Close (File.Fd);
end if;
Free_File (File);
raise;
end;
end File_Open;
-- Upon entry: file is locked
-- Upon exit: file is unlocked
procedure File_Close (File : in out File_Ptr) is
begin
if File = null then
raise Status_Error; -- we better not be locked
end if;
begin
Free_File_Id (File.File_Id);
if File.Mode /= Input then
Flush (File);
end if;
Close (File.Fd);
if File.Delete then
Delete (File.Fd, File.Name);
end if;
File.File_Id := Invalid;
Safe_Support.File_Unlock (File);
exception
when others =>
Safe_Support.File_Unlock (File);
raise;
end;
-- put_buffer does a file_support_lock(). Therefore, file must
-- be unlocked before it is called.
Put_Buffer (File.Buffer);
Free_File (File);
end File_Close;
-- Since we are called from a program exit callout, don't do any
-- locks or frees.
procedure File_Close_Upon_Exit (File : File_Ptr) is
begin
if File.Mode /= Input then
Flush (File);
end if;
Close (File.Fd);
if File.Delete then
Delete (File.Fd, File.Name);
end if;
end File_Close_Upon_Exit;
-- Upon entry/exit:
--\x09when called from setup_buffer() => file is unlocked.
--\x09when called from file_reset() => file is locked.
procedure Setup_Buffer_Ptrs (File : File_Ptr) is
begin
if File.Style = Text or else File.Style = Special then
File.Last := File.Buffer.Size;
File.In_Ptr := File.Last;
-- for use by lookaheads for tstfile_beyond_eol
File.Buffer.Elem (0) := Ascii.Lf;
if File.Mode = Output then
File.Out_Ptr := 0;
else
File.Out_Ptr := File.In_Ptr;
end if;
File.Last_Lf := File.Out_Ptr;
end if;
end Setup_Buffer_Ptrs;
-- Upon entry/exit: file is unlocked.
procedure Setup_Buffer (File : File_Ptr; Size : Natural) is
begin
-- get_buffer does a file_support_lock()
File.Buffer := Get_Buffer (Size);
Setup_Buffer_Ptrs (File);
end Setup_Buffer;
-- Upon entry: file is locked
-- Upon exit: file is unlocked
procedure File_Delete (File : in out File_Ptr) is
begin
if File = null then
raise Status_Error; -- better not be locked
end if;
begin
Free_File_Id (File.File_Id);
Close (File.Fd);
if File.Name /= null then
Delete (File.Fd, File.Name);
end if;
File.File_Id := Invalid;
Safe_Support.File_Unlock (File);
exception
when others =>
Safe_Support.File_Unlock (File);
raise;
end;
-- put_buffer does a file_support_lock(). Therefore, file must
-- be unlocked before it is called.
Put_Buffer (File.Buffer);
Free_File (File);
end File_Delete;
-- Upon entry/exit: file is locked
procedure File_Reset (File : in out File_Ptr; Mode : File_Mode) is
Open_Mode : Open_Flags := Open_Modes (Mode);
begin
if File.Mode /= Input then
Flush (File);
end if;
Close (File.Fd);
File.Mode := Mode;
File.Index := 1;
File.Line := 1;
File.Page := 1;
File.Pos := Unknown;
File.File_Id := Invalid;
Open (File.all'Long_Address, File.Style, Open_Mode);
if (File.Out_Ptr /= -1) then
Setup_Buffer_Ptrs (File);
end if;
end File_Reset;
-- Upon entry/exit: file is locked
function Input_Col (File : File_Ptr) return Natural is
begin
return File.In_Ptr - File.Last_Lf + 1;
end Input_Col;
-- Upon entry/exit: file is locked
function Output_Col (File : File_Ptr) return Natural is
begin
return File.Out_Ptr - File.Last_Lf + 1;
end Output_Col;
-- Upon entry/exit: file is locked
function File_Eof (File : in File_Ptr) return Boolean is
begin
return At_End_Of_File (File.Fd);
end File_Eof;
-- Upon entry/exit: file is locked
procedure Write_Buffer (File : File_Ptr) is
Desired : Integer;
Last_Out : Integer;
begin
if (Ok_To_Write (File.all'Long_Address)) then
Last_Out := File.Out_Ptr;
File.Out_Ptr := 0;
Desired := Last_Out - File.Out_Ptr;
File.Last_Lf := File.Last_Lf - Desired;
Write (File.Fd, File.Buffer.Elem (File.Out_Ptr + 1)'Long_Address,
Desired * Char_Size);
elsif File.Out_Ptr >= File.Last then
-- truncate buffer so file can be closed
File.Out_Ptr := File.Last;
File.Buffer.Elem (File.Out_Ptr) := Ascii.Lf;
raise Device_Error;
end if;
end Write_Buffer;
-- Upon entry/exit: file is locked
procedure Putchar (File : File_Ptr; Char : Character) is
begin
if File.Out_Ptr >= File.Last then
Write_Buffer (File);
end if;
File.Out_Ptr := File.Out_Ptr + 1;
File.Buffer.Elem (File.Out_Ptr) := Char;
if Char = Ascii.Ff then
if Output_Col (File) = 2 then -- already incremented
File.Line := 1;
File.Page := File.Page + 1;
File.Last_Lf := File.Out_Ptr;
end if;
elsif Char = Ascii.Lf then
File.Last_Lf := File.Out_Ptr;
Write_Buffer (File);
File.Line := File.Line + 1;
if File.Pagelength /= 0 and then File.Line > File.Pagelength then
Putchar (File, Ascii.Ff);
end if;
else
if File.Linelength /= 0 and then
Output_Col (File) > File.Linelength + 1 then
File.Out_Ptr := File.Out_Ptr - 1;
Putchar (File, Ascii.Lf);
Putchar (File, Char);
end if;
end if;
end Putchar;
-- Upon entry/exit: file is locked
procedure Perform_Buffer_Read (File : File_Ptr) is
Actual : Integer;
Next : Integer := File.Last + 1;
begin
Actual := Read (File.Fd, File.Buffer.Elem (Next)'Long_Address,
Char_Size * (File.Buffer.Size - File.Last)) / Char_Size;
if File.Last = 0 then
File.In_Ptr := 0;
else
File.In_Ptr := 1;
end if;
File.Last := File.Last + Actual;
Fix_End_Of_Record (File.all'Long_Address, Actual);
if Actual > 0 then
if File.Test_Eof and then
File.Buffer.Elem (Next) = File.Eof_Char then
raise End_Error;
end if;
return;
elsif Actual = 0 then
if Is_Interactive (File.Fd) then
File.Test_Eof := True;
File.Buffer.Elem (Next) := File.Eof_Char;
end if;
raise End_Error;
else
raise Device_Error;
end if;
end Perform_Buffer_Read;
--
-- refill_buffer exists so that if there are only a few characters left
-- in a buffer more characters can be read in before doing a get of
-- a real or integer. If there are less than 100 characters in the
-- buffer and no end of line markers in the buffer the data at the
-- end of the buffer is moved to the front and another read performed.
-- This will only happen for disc files.
--
-- Upon entry/exit: file is locked
procedure Refill_Buffer (File : File_Ptr) is
Size_In_Bytes : Natural;
begin
if File.In_Ptr + 100 < File.Last then
return;
end if;
for I in File.In_Ptr + 1 .. File.Last loop
if File.Buffer.Elem (I) = Ascii.Lf then
return;
end if;
end loop;
Size_In_Bytes := File.Last - File.In_Ptr + 1;
File.Buffer.Elem (1 .. Size_In_Bytes) :=
File.Buffer.Elem (File.In_Ptr .. File.Last);
File.Last_Lf := File.Last_Lf - File.Last + Size_In_Bytes;
File.Last := Size_In_Bytes;
Perform_Buffer_Read (File);
end Refill_Buffer;
-- Upon entry/exit: file is locked
procedure Read_Buffer (File : File_Ptr) is
Actual : Integer;
begin
if File.Test_Eof and then File.Buffer.Elem (1) = File.Eof_Char then
raise End_Error;
end if;
File.Last_Lf := File.Last_Lf - File.Last;
File.Last := 0;
Perform_Buffer_Read (File);
end Read_Buffer;
-- Upon entry/exit: file is locked
function Getchar (File : File_Ptr) return Character is
C : Character;
begin
if File = null then
raise Status_Error;
end if;
loop
if File.In_Ptr >= File.Last then
if File.Mode /= Input then
raise Mode_Error;
end if;
if File.Pos = At_Eof then
raise End_Error;
end if;
begin
Read_Buffer (File);
exception
when End_Error =>
if File.Pos = Unknown then
File.Pos := At_Delayed_Eol;
end if;
File.Pos := Tstfile (File); -- set page, etc.
return ' ';
end;
end if;
File.In_Ptr := File.In_Ptr + 1;
C := File.Buffer.Elem (File.In_Ptr);
if C = Ascii.Lf then
File.Line := File.Line + 1;
File.Pos := At_Delayed_Eol;
File.Last_Lf := File.In_Ptr;
return ' ';
elsif C /= Ascii.Ff then
File.Pos := At_Char;
return C;
elsif File.Pos = At_Delayed_Eol then
-- this ff is considered to be "part" of the previous lf;
-- together, they make up a page_terminator.
File.Last_Lf := File.In_Ptr; -- considered part of lf
File.Line := 1;
File.Page := File.Page + 1;
else
File.Pos := At_Char;
return C;
end if;
end loop;
end Getchar;
-- get the NEXT file_pos that we're going to see for "file"
--
-- Upon entry/exit: file is locked
function Tstfile (File : File_Ptr) return File_Pos is
C : Character;
begin
if File.In_Ptr >= File.Last then
if File.Mode /= Input then
raise Mode_Error;
end if;
if File.Pos = At_Eof then
return At_Eof;
end if;
begin
Read_Buffer (File);
exception
when End_Error =>
if File.Pos /= Unknown then
if File.Pos /= At_Eop then
File.Line := 1;
File.Page := File.Page + 1;
end if;
File.Last_Lf := File.In_Ptr; -- set col to 1
File.Pos := At_Eof;
end if;
return At_Eof;
end;
end if;
C := File.Buffer.Elem (File.In_Ptr + 1);
if C = Ascii.Ff and then File.Pos = At_Delayed_Eol then
File.Line := 1;
File.Page := File.Page + 1;
File.Pos := At_Eop;
File.In_Ptr := File.In_Ptr + 1; -- consume the ff
File.Last_Lf := File.In_Ptr; -- considered part of lf
-- want form feed in the string, if want_ff flag is set in the file.
if File.Want_Ff then
File.In_Ptr := File.In_Ptr - 1;
return At_Char;
end if;
return Tstfile (File);
end if;
if C = Ascii.Lf then
return At_Delayed_Eol;
else
return At_Char;
end if;
end Tstfile;
-- if we will next read an ascii.lf, then if the character that follows
-- is an ascii.ff then the two behave as one character, called a
-- page_terminator. This routine looks at the next two characters (maybe
-- forcing a read of the next input line) and returns at_eol, at_eop,
-- or at_eof as appropriate. It puts everything back as it
-- was, except, of course, that the next line is now in the buffer. Note
-- that elem(0), which contains an ascii.lf, may be used to simulate the
-- last character from the previous buffer.
--
-- Upon entry/exit: file is locked
function Tstfile_Beyond_Eol (File : File_Ptr) return File_Pos is
C : Character;
Pos : File_Pos := Tstfile (File);
begin
if Pos = At_Eof then
return At_Eof;
end if;
if Pos = At_Delayed_Eol then
if File.In_Ptr + 1 >= File.Last then
begin
Read_Buffer (File);
exception
when End_Error =>
File.In_Ptr := File.In_Ptr -
1; -- push back the elem(0) lf
--file.last := file.last - 1;
return At_Eof;
end;
File.In_Ptr := File.In_Ptr - 1; -- push back the elem(0) lf
end if;
-- look past the ascii.lf for an ascii.ff
if File.Buffer.Elem (File.In_Ptr + 2) /= Ascii.Ff then
return At_Eol;
end if;
-- at end-of-file?
if File.In_Ptr + 2 >= File.Last then
if At_End_Of_File (File.Fd) then
return At_Eof;
end if;
end if;
return At_Eop;
end if;
return Pos;
end Tstfile_Beyond_Eol;
-- Upon entry/exit: file is locked
procedure Skip_Past_Eol (File : File_Ptr) is
Void : Character;
Void_Pos : File_Pos := Tstfile (File);
begin
loop
if File.In_Ptr >= File.Last then
begin
Read_Buffer (File);
exception
when End_Error =>
if File.Pos = Unknown then
File.Pos := At_Eof;
return;
end if;
Void := Getchar (File); -- set line, etc. reraise
end;
end if;
File.In_Ptr := File.In_Ptr + 1;
exit when File.Buffer.Elem (File.In_Ptr) = Ascii.Lf;
end loop;
File.In_Ptr := File.In_Ptr - 1;
Void := Getchar (File);
end Skip_Past_Eol;
-- Upon entry/exit: file is locked
procedure Always_Flush (File : File_Ptr) is
begin
File.Always_Flush := True;
Flush (File);
end Always_Flush;
-- Want text_io.get_line to return any ff's in the returned string.
-- Upon entry/exit: file is locked
procedure Want_Ff (File : File_Ptr) is
begin
if File = null then
raise Status_Error;
end if;
File.Want_Ff := True;
end Want_Ff;
-- Upon entry/exit: file is locked
procedure Flush (File : File_Ptr) is
begin
if File = null then
raise Status_Error;
end if;
if File.Out_Ptr = -1 then
return;
end if;
if File.Mode /= Output then
raise Mode_Error;
end if;
Write_Buffer (File_Ptr (File));
end Flush;
-- Upon entry/exit: file is locked
procedure Set_Buffer_Size (File : File_Ptr; Size : Natural) is
begin
if File = null then
raise Status_Error;
end if;
null;
end Set_Buffer_Size;
-- Upon entry/exit: file is locked
procedure Set_Eof_Char (File : File_Ptr;
Eof_Char : Character := Ascii.Eot) is
begin
if File = null then
raise Status_Error;
end if;
if File.Mode /= Input then
raise Mode_Error;
end if;
File.Eof_Char := Eof_Char;
File.Test_Eof := True;
end Set_Eof_Char;
procedure Write_To_Stderr (Message : String) is
begin
Write (Stderr_Fd, Message'Long_Address, Message'Length * Char_Size);
end Write_To_Stderr;
function Read (Fd : File_Descriptor; Addr : System.Address; Cnt : Integer)
return Integer is
begin
return Read (Fd, To_Long_Addr (Addr), Cnt);
end Read;
procedure Write (Fd : File_Descriptor;
Addr : System.Address;
Cnt : Integer) is
begin
Write (Fd, To_Long_Addr (Addr), Cnt);
end Write;
begin
declare
Result : Boolean;
begin
Result := Ada_Krn_I.Callout_Install
(Ada_Krn_Defs.Exit_Event, Close_All'Address);
end;
end File_Support;
nblk1=1b
nid=0
hdr6=36
[0x00] rec0=25 rec1=00 rec2=01 rec3=02c
[0x01] rec0=1a rec1=00 rec2=02 rec3=032
[0x02] rec0=20 rec1=00 rec2=03 rec3=06a
[0x03] rec0=1c rec1=00 rec2=04 rec3=01e
[0x04] rec0=19 rec1=00 rec2=05 rec3=040
[0x05] rec0=1b rec1=00 rec2=06 rec3=00e
[0x06] rec0=22 rec1=00 rec2=07 rec3=00c
[0x07] rec0=1c rec1=00 rec2=08 rec3=000
[0x08] rec0=16 rec1=00 rec2=09 rec3=060
[0x09] rec0=22 rec1=00 rec2=0a rec3=02c
[0x0a] rec0=1c rec1=00 rec2=0b rec3=008
[0x0b] rec0=1e rec1=00 rec2=0c rec3=012
[0x0c] rec0=1e rec1=00 rec2=0d rec3=048
[0x0d] rec0=20 rec1=00 rec2=0e rec3=044
[0x0e] rec0=1a rec1=00 rec2=0f rec3=03e
[0x0f] rec0=1c rec1=00 rec2=10 rec3=018
[0x10] rec0=1b rec1=00 rec2=11 rec3=018
[0x11] rec0=1b rec1=00 rec2=12 rec3=036
[0x12] rec0=1d rec1=00 rec2=13 rec3=036
[0x13] rec0=18 rec1=00 rec2=14 rec3=026
[0x14] rec0=1d rec1=00 rec2=15 rec3=024
[0x15] rec0=1a rec1=00 rec2=16 rec3=030
[0x16] rec0=16 rec1=00 rec2=17 rec3=066
[0x17] rec0=1d rec1=00 rec2=18 rec3=008
[0x18] rec0=1c rec1=00 rec2=19 rec3=01c
[0x19] rec0=24 rec1=00 rec2=1a rec3=00a
[0x1a] rec0=22 rec1=00 rec2=1b rec3=000
tail 0x21750b81c868434ddbb61 0x42a00088462060003