| -- Copyright (C) 2024 secunet Security Networks AG |
| -- |
| -- This program is free software; you can redistribute it and/or modify |
| -- it under the terms of the GNU General Public License as published by |
| -- the Free Software Foundation; either version 2 of the License, or |
| -- (at your option) any later version. |
| |
| with System; |
| with Interfaces; |
| with Interfaces.C; |
| |
| with FILO.Blockdev; |
| with FILO.FS.VFS; |
| |
| use Interfaces.C; |
| |
| package body FILO.FS.ISO9660 is |
| |
| subtype SUSP_Signature is String (1 .. 2); |
| |
| -------------------------------------------------------------------------- |
| |
| function Is_Mounted (State : T) return Boolean is (State.S >= Mounted); |
| function Is_Open (State : T) return Boolean is (State.S = File_Opened); |
| |
| -------------------------------------------------------------------------- |
| |
| function "+" (Rec : FS_Record; Off : FSBlock_Length) return FS_Record |
| is |
| (Block => Rec.Block, Offset => Rec.Offset + Off, Size => Rec.Size - Off) |
| with |
| Pre => Rec.Size >= Off; |
| |
| procedure Read |
| (Buf : out Buffer_Type; |
| Rec : in FS_Record; |
| Success : out Boolean) |
| with |
| Pre => Buf'Length <= Rec.Size |
| is |
| Block_64 : constant Integer_64 := Integer_64 (Rec.Block); |
| Offset_64 : constant Integer_64 := Integer_64 (Rec.Offset); |
| begin |
| Blockdev.Read (Buf, Blockdev_Length (Block_64 * FSBlock_Size + Offset_64), Success); |
| end Read; |
| |
| procedure Mount |
| (State : in out T; |
| Part_Len : in Partition_Length; |
| Success : out Boolean) |
| is |
| D_Type : constant Index_Type := 0; |
| D_Id : constant Index_Type := 1; |
| D_Ver : constant Index_Type := 6; |
| |
| D_Type_Primary : constant := 1; |
| D_Type_Terminator : constant := 255; |
| |
| CD001 : constant Buffer_Type := |
| (Character'Pos ('C'), Character'Pos ('D'), |
| Character'Pos ('0'), Character'Pos ('0'), Character'Pos ('1')); |
| |
| Static : Mount_State renames State.Static; |
| |
| Volume_Descriptor : Buffer_Type (0 .. D_Ver); |
| Block : FSBlock := 16; |
| begin |
| loop |
| pragma Loop_Invariant (Block < 1024); |
| pragma Loop_Invariant (not Is_Mounted (State)); |
| |
| if Part_Len < Partition_Length (Block + 1) * FSBlock_Size then |
| Success := False; |
| return; |
| end if; |
| Static.Part_Len := Part_Len; |
| |
| Read (Volume_Descriptor, FS_Record'(Block, 0, FSBlock_Size), Success); |
| if not Success then |
| return; |
| end if; |
| |
| if Volume_Descriptor (D_Ver) /= 1 or |
| Volume_Descriptor (D_Id .. D_Ver - 1) /= CD001 or |
| Volume_Descriptor (D_Type) = D_Type_Terminator |
| then |
| Success := False; |
| return; |
| end if; |
| |
| exit when Volume_Descriptor (D_Type) = D_Type_Primary; |
| |
| Block := Block + 1; |
| if Block >= 1024 then |
| Success := False; |
| return; |
| end if; |
| end loop; |
| |
| Static.Root_Inode := (Block, 156, Directory_Record'Length); |
| |
| State.S := Mounted; |
| end Mount; |
| |
| procedure Find_SUSP_Record |
| (State : in T; |
| Rec : out FS_Record; |
| Dir_Rec_Pos : in File_Offset; |
| Dir_Rec_Len : in FSBlock_Length; |
| Searched_Sig : in SUSP_Signature) |
| with |
| Pre => FSBlock_Index (Dir_Rec_Pos mod FSBlock_Size) <= FSBlock_Size - Dir_Rec_Len |
| is |
| subtype Signature_Buf is Buffer_Type (0 .. 1); |
| function Signature (Str : SUSP_Signature) return Signature_Buf |
| is |
| ((Character'Pos (Str (Str'First)), Character'Pos (Str (Str'First + 1)))); |
| |
| Cur : FS_Record := |
| (Block => State.Inode.Extents (0).Start + FSBlock (Dir_Rec_Pos / FSBlock_Size), |
| Offset => FSBlock_Index (Dir_Rec_Pos mod FSBlock_Size), |
| Size => Dir_Rec_Len); |
| |
| Continuation : FS_Record := (others => <>); |
| |
| SUSP_Head : Buffer_Type (0 .. 3); |
| |
| Success : Boolean; |
| begin |
| Rec := (others => <>); |
| if Cur.Size = 0 then |
| return; |
| end if; |
| |
| if Cur.Offset mod 2 = 1 then |
| Cur := Cur + 1; |
| end if; |
| |
| for I in 1 .. 2**16 loop -- arbitrary limit to avoid endless loop |
| if Cur.Size < SUSP_Head'Length then |
| if Continuation.Size = 0 then |
| return; |
| else |
| Cur := Continuation; |
| Continuation := (others => <>); |
| end if; |
| end if; |
| |
| Read (SUSP_Head, Cur, Success); |
| declare |
| Rec_Len : constant Natural := Natural (SUSP_Head (2)); |
| SUSP_CE : Buffer_Type (0 .. 27); |
| begin |
| if not Success or else Rec_Len not in 4 .. Cur.Size then |
| return; |
| end if; |
| |
| if SUSP_Head (0 .. 1) = Signature (Searched_Sig) then |
| Rec := |
| (Block => Cur.Block, |
| Offset => Cur.Offset, |
| Size => Rec_Len); |
| return; |
| elsif SUSP_Head (0 .. 1) = Signature ("CE") then |
| if Rec_Len < SUSP_CE'Length then |
| return; |
| end if; |
| |
| Read (SUSP_CE, Cur, Success); |
| if not Success then |
| return; |
| end if; |
| declare |
| CE_Extent : constant Unsigned_32 := Read_LE32 (SUSP_CE, 4); |
| CE_Offset : constant Unsigned_32 := Read_LE32 (SUSP_CE, 12); |
| CE_Size : constant Unsigned_32 := Read_LE32 (SUSP_CE, 20); |
| begin |
| if (CE_Offset > FSBlock_Size or CE_Size > FSBlock_Size) or else |
| CE_Offset > FSBlock_Size - CE_Size |
| then |
| return; |
| end if; |
| Continuation := |
| (Block => FSBlock (CE_Extent), |
| Offset => FSBlock_Index (CE_Offset), |
| Size => FSBlock_Length (CE_Size)); |
| end; |
| end if; |
| |
| Cur := Cur + Rec_Len; |
| end; |
| end loop; |
| end Find_SUSP_Record; |
| |
| procedure Open |
| (State : in out T; |
| I : in Inode_Index; |
| Success : out Boolean) |
| with |
| Pre => Is_Mounted (State), |
| Post => Is_Mounted (State) and (Success = Is_Open (State)) |
| is |
| Inode : Inode_Info renames State.Inode; |
| |
| Dir_Rec : Directory_Record; |
| begin |
| State.S := Mounted; |
| |
| Inode := (I, others => <>); |
| |
| Read (Dir_Rec, I, Success); |
| if not Success then |
| return; |
| end if; |
| |
| declare |
| D_Flag_Dir : constant := 16#02#; |
| D_Flag_Cont : constant := 16#80#; |
| D_Flags : constant Unsigned_8 := Dir_Rec (25); |
| D_Extent : constant Unsigned_32 := Read_LE32 (Dir_Rec, 2); |
| begin |
| if D_Extent > Unsigned_32 (FSBlock'Last) then |
| Success := False; |
| return; |
| end if; |
| if (D_Flags and D_Flag_Cont) /= 0 then |
| Success := False; |
| return; |
| end if; |
| |
| if (D_Flags and D_Flag_Dir) /= 0 then |
| Inode.Mode := Dir; |
| else |
| Inode.Mode := Regular; |
| end if; |
| Inode.Size := Inode_Length (Read_LE32 (Dir_Rec, 10)); |
| Inode.Extents (0) := |
| (Start => FSBlock (D_Extent), |
| Count => FSBlock_Count (Inode.Size / Inode_Length (FSBlock_Size))); |
| State.S := File_Opened; |
| end; |
| end Open; |
| |
| procedure Open |
| (State : in out T; |
| File_Len : out File_Length; |
| File_Type : out FS.File_Type; |
| File_Name : in String; |
| In_Root : in Boolean; |
| Success : out Boolean) |
| is |
| File_Name_Max : constant := 255; |
| |
| procedure Match_ISO_Name |
| (ISO_Name_Length : in Natural; |
| Record_Dir_Pos : in File_Offset; |
| Success : in out Boolean) |
| with |
| Post => State.Static = State.Static'Old |
| is |
| function Upper_Case_Equal (Str : String; Buf : Buffer_Type) return Boolean |
| with |
| Pre => Str'Length <= Buf'Length |
| is |
| begin |
| for I in Str'Range loop |
| declare |
| Chr : constant Unsigned_8 := |
| (if 'a' <= Str (I) and Str (I) <= 'z' then |
| Character'Pos (Str (I)) - Character'Pos ('a') + Character'Pos ('A') |
| else Character'Pos (Str (I))); |
| begin |
| if Chr /= Buf (Buf'First + (I - Str'First)) then |
| return False; |
| end if; |
| end; |
| end loop; |
| return True; |
| end Upper_Case_Equal; |
| |
| Dir_Pos : File_Offset := Record_Dir_Pos; |
| ISO_Name : Buffer_Type (0 .. ISO_Name_Length - 1); |
| Name_Len : Natural; |
| begin |
| -- Check if length could match. |
| -- First for the weird `.' and `..' encoding, |
| -- then for regular file names + `.' and file version `;1'. |
| if not (ISO_Name_Length = 1 and File_Name'Length <= 2) and |
| ISO_Name_Length not in File_Name'Length .. File_Name'Length + 3 |
| then |
| return; |
| end if; |
| |
| pragma Warnings |
| (GNATprove, Off, """Dir_Pos"" is set*but not used", |
| Reason => "This is the last part of the record we care about."); |
| Read |
| (State => State, |
| File_Pos => Dir_Pos, |
| Buf => ISO_Name, |
| Len => Name_Len); |
| if Name_Len /= ISO_Name_Length then |
| return; |
| end if; |
| pragma Warnings (GNATprove, On, """Dir_Pos"" is set*but not used"); |
| |
| if (Name_Len = 1 and ISO_Name (0) = 16#00# and File_Name = ".") or |
| (Name_Len = 1 and ISO_Name (0) = 16#01# and File_Name = "..") |
| then |
| Success := True; |
| return; |
| end if; |
| |
| -- Ignore file version `;1' |
| if Name_Len >= File_Name'Length + 2 and |
| ISO_Name (Name_Len - 2) = Character'Pos (';') and |
| ISO_Name (Name_Len - 1) = Character'Pos ('1') |
| then |
| Name_Len := Name_Len - 2; |
| end if; |
| |
| -- Ignore trailing `.' |
| if Name_Len >= File_Name'Length + 1 and |
| ISO_Name (Name_Len - 1) = Character'Pos ('.') |
| then |
| Name_Len := Name_Len - 1; |
| end if; |
| |
| Success := Name_Len = File_Name'Length and then |
| Upper_Case_Equal (File_Name, ISO_Name); |
| end Match_ISO_Name; |
| |
| procedure Match_Rock_Ridge_Name |
| (Pos : in File_Offset; |
| Len : in Natural; |
| Success : in out Boolean) |
| with |
| Post => State = State'Old |
| is |
| Name_Rec : FS_Record; |
| begin |
| Find_SUSP_Record (State, Name_Rec, Pos, Len, "NM"); |
| if Name_Rec.Size /= File_Name'Length + 5 then |
| Success := False; |
| return; |
| end if; |
| |
| declare |
| RR_Name : Buffer_Type (0 .. File_Name'Length - 1); |
| begin |
| Read (RR_Name, Name_Rec + 5, Success); |
| Success := Success and then Equal (File_Name, RR_Name); |
| end; |
| end Match_Rock_Ridge_Name; |
| |
| Found_Inode : Inode_Index; |
| Dir_Pos : File_Length; |
| begin |
| File_Len := 0; |
| File_Type := FS.File_Type'First; |
| |
| if File_Name'Length > File_Name_Max then |
| Success := False; |
| return; |
| end if; |
| |
| -- Ensure dir is opened: |
| -- |
| if State.S = File_Opened then |
| if State.Cur_Dir /= State.Inode.I then |
| Success := False; |
| return; |
| end if; |
| else |
| if In_Root then |
| State.Cur_Dir := State.Static.Root_Inode; |
| end if; |
| declare |
| Cur_Dir : constant Inode_Index := State.Cur_Dir; |
| begin |
| Open (State, Cur_Dir, Success); |
| if not Success then |
| return; |
| end if; |
| end; |
| end if; |
| |
| -- Lookup file in opened dir: |
| -- |
| Dir_Pos := 0; |
| Success := False; |
| loop |
| pragma Loop_Invariant (Is_Open (State) and not Success); |
| pragma Loop_Invariant |
| (FSBlock_Index (Dir_Pos mod FSBlock_Size) <= Directory_Record_Offset'Last); |
| |
| declare |
| Dir_Rec_Head : Directory_Record; |
| Record_Dir_Pos : File_Offset := Dir_Pos; |
| Len : Natural; |
| begin |
| Read |
| (State => State, |
| File_Pos => Record_Dir_Pos, |
| Buf => Dir_Rec_Head, |
| Len => Len); |
| if Len /= Dir_Rec_Head'Length then |
| return; |
| end if; |
| |
| declare |
| Dir_Rec_Length : constant Natural := Natural (Dir_Rec_Head (0)); |
| Dir_Rec_Name_Length : constant Natural := Natural (Dir_Rec_Head (32)); |
| begin |
| if Dir_Rec_Length /= 0 then |
| |
| if Unsigned_64 (Dir_Pos) > Unsigned_64 (State.Inode.Size) - Unsigned_64 (Dir_Rec_Length) or |
| Dir_Rec_Name_Length > Dir_Rec_Length - Dir_Rec_Head'Length |
| then |
| return; |
| end if; |
| |
| -- First skip ISO name and try Rock Ridge extension: |
| Match_Rock_Ridge_Name |
| (Pos => Record_Dir_Pos + File_Offset (Dir_Rec_Name_Length), |
| Len => Dir_Rec_Length - Dir_Rec_Head'Length - Dir_Rec_Name_Length, |
| Success => Success); |
| if not Success then |
| Match_ISO_Name (Dir_Rec_Name_Length, Record_Dir_Pos, Success); |
| end if; |
| |
| if Success then |
| Found_Inode := |
| (Block => FSBlock (Dir_Pos / FSBlock_Size) + State.Inode.Extents (0).Start, |
| Offset => FSBlock_Index (Dir_Pos mod FSBlock_Size), |
| Size => Dir_Rec_Length); |
| exit; |
| end if; |
| |
| Dir_Pos := Dir_Pos + File_Length (Dir_Rec_Length); |
| |
| else |
| -- `Dir_Rec_Length = 0` means we have to skip to the next fs block: |
| declare |
| Block_Gap : constant File_Length := FSBlock_Size - (Dir_Pos mod FSBlock_Size); |
| begin |
| if Unsigned_64 (Dir_Pos) >= Unsigned_64 (State.Inode.Size) - Unsigned_64 (Block_Gap) then |
| return; |
| else |
| Dir_Pos := Dir_Pos + Block_Gap; |
| end if; |
| end; |
| end if; |
| end; |
| end; |
| end loop; |
| pragma Assert_And_Cut (Success and Is_Mounted (State)); |
| |
| Open (State, Found_Inode, Success); |
| if not Success then |
| return; |
| end if; |
| |
| if State.Inode.Mode = Dir then |
| State.Cur_Dir := Found_Inode; |
| end if; |
| |
| Success := State.Inode.Size <= Inode_Length (File_Length'Last); |
| if Success then |
| File_Len := File_Length (State.Inode.Size); |
| File_Type := State.Inode.Mode; |
| else |
| Close (State); |
| end if; |
| end Open; |
| |
| procedure Close (State : in out T) is |
| begin |
| State.S := Mounted; |
| end Close; |
| |
| procedure Read |
| (State : in out T; |
| File_Pos : in out File_Offset; |
| Buf : out Buffer_Type; |
| Len : out Natural) |
| is |
| Static : Mount_State renames State.Static; |
| |
| Pos : Natural range Buf'First .. Buf'Last + 1; |
| begin |
| Len := 0; |
| Pos := Buf'First; |
| while Pos <= Buf'Last and |
| File_Pos < File_Offset'Last and |
| Inode_Length (File_Pos) < State.Inode.Size |
| loop |
| pragma Loop_Invariant (for all I in Buf'First .. Pos - 1 => Buf (I)'Initialized); |
| pragma Loop_Invariant (Is_Open (State)); |
| declare |
| In_Block : constant FSBlock_Index := Natural (File_Pos mod FSBlock_Size); |
| Logical : constant FSBlock_Logical := FSBlock_Logical (File_Pos / FSBlock_Size); |
| In_Block_Space : constant Positive := Index_Type (FSBlock_Size) - In_Block; |
| In_File_Space : constant Inode_Length := State.Inode.Size - Inode_Length (File_Pos); |
| In_Buf_Space : constant Positive := Buf'Last - Pos + 1; |
| Len_Here : Positive; |
| begin |
| exit when FSBlock (Logical) > FSBlock'Last - State.Inode.Extents (0).Start; |
| |
| Len_Here := In_Block_Space; |
| if In_File_Space < Inode_Length (Len_Here) then |
| Len_Here := Positive (In_File_Space); |
| end if; |
| if In_Buf_Space < Len_Here then |
| Len_Here := In_Buf_Space; |
| end if; |
| if File_Offset'Last - File_Pos < File_Length (Len_Here) then |
| Len_Here := Positive (File_Offset'Last - File_Pos); |
| end if; |
| |
| declare |
| Last : constant Index_Type := Pos + Len_Here - 1; |
| Physical : constant FSBlock := State.Inode.Extents (0).Start + FSBlock (Logical); |
| Success : Boolean; |
| begin |
| Read |
| (Buf => Buf (Pos .. Last), |
| Rec => FS_Record'(Physical, In_Block, In_Block_Space), |
| Success => Success); |
| exit when not Success; |
| |
| File_Pos := File_Pos + File_Length (Len_Here); |
| Pos := Pos + Len_Here; |
| Len := Pos - Buf'First; |
| end; |
| end; |
| end loop; |
| Buf (Pos .. Buf'Last) := (others => 16#00#); |
| end Read; |
| |
| -------------------------------------------------------------------------- |
| |
| package C is new VFS (T => T, Initial => (S => Unmounted, others => <>)); |
| |
| function C_Mount return int |
| with |
| Export, |
| Convention => C, |
| External_Name => "iso9660_mount"; |
| function C_Mount return int |
| with |
| SPARK_Mode => Off |
| is |
| begin |
| return C.C_Mount; |
| end C_Mount; |
| |
| function C_Open (File_Path : System.Address) return int |
| with |
| Export, |
| Convention => C, |
| External_Name => "iso9660_dir"; |
| function C_Open (File_Path : System.Address) return int |
| with |
| SPARK_Mode => Off |
| is |
| begin |
| return C.C_Open (File_Path); |
| end C_Open; |
| |
| function C_Read (Buf : System.Address; Len : int) return int |
| with |
| Export, |
| Convention => C, |
| External_Name => "iso9660_read"; |
| function C_Read (Buf : System.Address; Len : int) return int |
| with |
| SPARK_Mode => Off |
| is |
| begin |
| return C.C_Read (Buf, Len); |
| end C_Read; |
| |
| end FILO.FS.ISO9660; |