Start ISO9660 support
diff --git a/src/filo-fs-iso9660.adb b/src/filo-fs-iso9660.adb
new file mode 100644
index 0000000..8c1b441
--- /dev/null
+++ b/src/filo-fs-iso9660.adb
@@ -0,0 +1,381 @@
+-- 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
+
+ function Is_Mounted (State : T) return Boolean is (State.S >= Mounted);
+ function Is_Open (State : T) return Boolean is (State.S = File_Opened);
+
+ --------------------------------------------------------------------------
+
+ procedure Read
+ (Buf : in out Buffer_Type;
+ Block : in FSBlock;
+ Offset : in FSBlock_Index;
+ FS_Len : in Partition_Length;
+ Success : out Boolean)
+ with
+ Pre => Buf'Length <= FSBlock_Size - Offset
+ is
+ Block_64 : constant Integer_64 := Integer_64 (Block);
+ Offset_64 : constant Integer_64 := Integer_64 (Offset);
+ Max_Block_Offset : constant Integer_64 := Integer_64 (FS_Len) / FSBlock_Size - 1;
+ begin
+ if Block_64 > Max_Block_Offset then
+ Success := False;
+ return;
+ end if;
+ 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 .. 190 - 1);
+ Block : FSBlock := 32;
+ begin
+ loop
+ pragma Loop_Invariant (Block < 1024);
+
+ if Part_Len < Partition_Length (Block + 1) * FSBlock_Size then
+ Success := False;
+ return;
+ end if;
+
+ Read (Volume_Descriptor (0 .. D_Ver), Block, 0, Part_Len, 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
+ return;
+ end if;
+
+ exit when Volume_Descriptor (D_Type) = D_Type_Primary;
+
+ Block := Block + 1;
+ if Block >= 1024 then
+ return;
+ end if;
+ end loop;
+
+ Static.Part_Len := Part_Len;
+ Static.Root_Inode := (Block, 156);
+
+ State.S := Mounted;
+ end Mount;
+
+ 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
+ Static : Mount_State renames State.Static;
+ Inode : Inode_Info renames State.Inode;
+
+ Dir_Rec : Directory_Record;
+ begin
+ Inode := (I, others => <>);
+
+ Read (Dir_Rec, I.Block, I.Offset, Static.Part_Len, 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);
+ begin
+ 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 (Read_LE32 (Dir_Rec, 2)),
+ 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;
+
+ function Str_Buf_Equal (Str : String; Buf : Buffer_Type) return Boolean
+ with
+ Pre => Str'Length <= Buf'Length
+ is
+ begin
+ for I in Str'Range loop
+ if Character'Pos (Str (I)) /= Buf (Buf'First + (I - Str'First)) then
+ return False;
+ end if;
+ end loop;
+ return True;
+ end Str_Buf_Equal;
+
+ 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);
+ declare
+ Dir_Rec : Directory_Record;
+ Dir_Rec_Name : Buffer_Type (0 .. File_Name_Max - 1);
+ Record_Dir_Pos : File_Offset := Dir_Pos;
+ Dir_Rec_Length : File_Length;
+ Inode : Inode_Index;
+ Len : Natural;
+ begin
+ Read
+ (State => State,
+ File_Pos => Record_Dir_Pos,
+ Buf => Dir_Rec,
+ Len => Len);
+ if Len < Dir_Rec'Length then
+ return;
+ end if;
+
+ -- FIXME: need to strip trailing . and file version
+ if File_Name'Length = Natural (Dir_Rec (32)) then
+ pragma Warnings
+ (GNATprove, Off, """Record_Dir_Pos"" is set*but not used",
+ Reason => "We only care about intermedidate values.");
+ Read
+ (State => State,
+ File_Pos => Record_Dir_Pos,
+ Buf => Dir_Rec_Name,
+ Len => Len);
+ pragma Warnings (GNATprove, On, """Record_Dir_Pos"" is set*but not used");
+ if Len < File_Name'Length then
+ return;
+ end if;
+
+ Success := Str_Buf_Equal (File_Name, Dir_Rec_Name);
+ if Success then
+ Found_Inode :=
+ (Block => FSBlock (Dir_Pos / FSBlock_Size),
+ Offset => FSBlock_Index (Dir_Pos mod FSBlock_Size));
+ exit;
+ end if;
+ end if;
+
+ Dir_Rec_Length := File_Length (Dir_Rec (0));
+ if Dir_Rec_Length = 0 or
+ Dir_Pos > File_Length'Last - Dir_Rec_Length or
+ Unsigned_64 (Dir_Pos) >= Unsigned_64 (State.Inode.Size) - Unsigned_64 (Dir_Rec_Length)
+ then
+ return;
+ end if;
+ Dir_Pos := Dir_Pos + Dir_Rec_Length;
+ 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 (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),
+ Block => Physical,
+ Offset => In_Block,
+ FS_Len => Static.Part_Len,
+ 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;
diff --git a/src/filo-fs-iso9660.ads b/src/filo-fs-iso9660.ads
new file mode 100644
index 0000000..dd82d74
--- /dev/null
+++ b/src/filo-fs-iso9660.ads
@@ -0,0 +1,90 @@
+package FILO.FS.ISO9660 is
+
+ type T is private;
+
+ function Is_Mounted (State : T) return Boolean;
+ function Is_Open (State : T) return Boolean
+ with
+ Post => (if Is_Open'Result then Is_Mounted (State));
+
+ procedure Mount
+ (State : in out T;
+ Part_Len : in Partition_Length;
+ Success : out Boolean)
+ with
+ Pre => not Is_Mounted (State),
+ Post => Success = Is_Mounted (State);
+
+ 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)
+ with
+ Pre => Is_Mounted (State),
+ Post => (if Success then Is_Open (State) else Is_Mounted (State));
+
+ procedure Close (State : in out T)
+ with
+ Pre => Is_Open (State),
+ Post => Is_Mounted (State);
+
+ procedure Read
+ (State : in out T;
+ File_Pos : in out File_Offset;
+ Buf : out Buffer_Type;
+ Len : out Natural)
+ with
+ Pre => Is_Open (State) and Buf'Length > 0,
+ Post => Is_Open (State);
+
+private
+ type State is (Unmounted, Mounted, File_Opened);
+
+ FSBlock_Size : constant := 2048;
+ subtype FSBlock_Index is Index_Type range 0 .. FSBlock_Size - 1;
+
+ type FSBlock_Count is range 0 .. 2 ** 32 - 1;
+ subtype FSBlock is FSBlock_Count range 0 .. FSBlock_Count'Last - 1;
+ type FSBlock_Logical is new FSBlock;
+
+ subtype Directory_Record_Range is Index_Type range 0 .. 32;
+ subtype Directory_Record is Buffer_Type (Directory_Record_Range);
+
+ -- We'll use the absolute position of the dir record
+ type Inode_Index is record
+ Block : FSBlock := 0;
+ Offset : FSBlock_Index := 0;
+ end record;
+ type Inode_Length is range 0 .. FSBlock_Count'Last * FSBlock_Size;
+
+ type Mount_State is record
+ Part_Len : Partition_Length := 0;
+ Root_Inode : Inode_Index := (others => <>);
+ end record;
+
+ type Extent is record
+ Start : FSBlock := 0;
+ Count : FSBlock_Count := 0;
+ end record;
+
+ type Extent_Range is range 0 .. 0; -- Linux allows 100 "file sections"
+ type Extents is array (Extent_Range) of Extent;
+
+ type Inode_Info is record
+ I : Inode_Index := (others => <>);
+ Mode : File_Type := File_Type'First;
+ Size : Inode_Length := 0;
+ Extents : ISO9660.Extents := (Extent_Range => (others => <>));
+ end record;
+
+ type T is record
+ Static : Mount_State := (others => <>);
+ S : State;
+ Inode : Inode_Info := (others => <>);
+ Cur_Dir : Inode_Index := (others => <>);
+ end record;
+
+end FILO.FS.ISO9660;
diff --git a/src/vfs.c b/src/vfs.c
index 943fea4..03e9374 100644
--- a/src/vfs.c
+++ b/src/vfs.c
@@ -12,6 +12,7 @@
int (*close_func) (void);
} static const fsys_table[] = {
{ "ext2", ext2fs_mount, ext2fs_read, ext2fs_dir, NULL },
+ { "iso9660", iso9660_mount, iso9660_read, iso9660_dir, NULL },
};
static const size_t fsys_table_length = sizeof(fsys_table) / sizeof(fsys_table[0]);
diff --git a/src/vfs.h b/src/vfs.h
index d811def..cff04cd 100644
--- a/src/vfs.h
+++ b/src/vfs.h
@@ -31,5 +31,8 @@
int ext2fs_mount(void);
int ext2fs_read(char *buf, int len);
int ext2fs_dir(char *path);
+int iso9660_mount(void);
+int iso9660_read(char *buf, int len);
+int iso9660_dir(char *path);
#endif /* VFS_H */
diff --git a/ext2-test.sh b/test.sh
similarity index 90%
rename from ext2-test.sh
rename to test.sh
index 504d8d3..8a594a8 100755
--- a/ext2-test.sh
+++ b/test.sh
@@ -7,7 +7,7 @@
build=${PWD}/build
-test() {
+test_ext2() {
type=$1
img=${tmp}/${type}.img
@@ -70,6 +70,27 @@
sudo umount ${mnt}
+ test_img ${img}
+}
+
+test_isofs() {
+ img=${tmp}/test.iso
+ template=${tmp}/ext2.img
+
+ printf "\n===> Prepping \`${img}'\n"
+
+ sudo mount -o loop ${template} ${mnt} -o ro
+
+ mkisofs -o ${img} ${mnt}
+
+ sudo umount ${mnt}
+
+ test_img ${img}
+}
+
+test_img() {
+ img=$1
+
sudo mount -o loop ${img} ${mnt} -o ro
cd ${mnt}
@@ -97,5 +118,6 @@
sudo umount ${mnt}
}
-test ext2
-test ext4
+test_ext2 ext2
+test_ext2 ext4
+test_isofs