blob: bab0c69d8a8b4bab59f6e9b1f4247d72929b735e [file] [log] [blame]
-- Derived from GRUB -- GRand Unified Bootloader
-- Copyright (C) 1999, 2001, 2003 Free Software Foundation, Inc.
-- Copyright (C) 2023 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 Ada.Unchecked_Conversion;
with System;
with Interfaces;
with Interfaces.C;
with FILO.Blockdev;
with FILO.FS.VFS;
use Interfaces.C;
package body FILO.FS.Ext2 is
function Is_Mounted (State : T) return Boolean is (State.S >= Mounted);
function Is_Open (State : T) return Boolean is (State.S = File_Opened);
--------------------------------------------------------------------------
SUPERBLOCK_SIZE : constant := 1024;
SUPERBLOCK_MAGIC : constant := 16#ef53#;
OLD_REV : constant := 0;
DYNAMIC_REV : constant := 1;
FEATURE_INCOMPAT_EXTENTS : constant := 16#0040#;
FEATURE_INCOMPAT_64BIT : constant := 16#0080#;
EXT4_EXTENTS_FL : constant := 16#8_0000#;
procedure Mount
(State : in out T;
Part_Len : in Partition_Length;
Success : out Boolean)
is
Static : Mount_State renames State.Static;
Super_Block : Buffer_Type (0 .. 64 * 4 - 1);
begin
if Part_Len < 2 * SUPERBLOCK_SIZE then
Success := False;
return;
end if;
Blockdev.Read (Super_Block, 1 * SUPERBLOCK_SIZE, Success);
if not Success then
return;
end if;
if Read_LE16 (Super_Block, 14 * 4) /= 16#ef53# then
Success := False;
return;
end if;
Static.Part_Len := Part_Len;
Static.First_Data_Block := Read_LE32 (Super_Block, 5 * 4);
declare
S_Log_Block_Size : constant Unsigned_32 := Read_LE32 (Super_Block, 6 * 4);
begin
if S_Log_Block_Size <= Unsigned_32 (Log_Block_Size'Last - 10) then
Static.Block_Size_Bits := Log_Block_Size (S_Log_Block_Size + 10);
Static.Block_Size := 2 ** Log_Block_Size (S_Log_Block_Size + 10);
else
Success := False;
return;
end if;
end;
pragma Assert_And_Cut (Success and not Is_Mounted (State));
declare
S_Inodes_Per_Group : constant Unsigned_32 := Read_LE32 (Super_Block, 10 * 4);
begin
if S_Inodes_Per_Group in 1 .. Unsigned_32'Last then
Static.Inodes_Per_Group := Inode_In_Group_Count (S_Inodes_Per_Group);
else
Success := False;
return;
end if;
end;
declare
S_Rev_Level : constant Unsigned_32 := Read_LE32 (Super_Block, 19 * 4);
begin
if S_Rev_Level >= DYNAMIC_REV then
declare
S_Inode_Size : constant Unsigned_16 := Read_LE16 (Super_Block, 22 * 4);
begin
if Natural (S_Inode_Size) in Inode_Size and then
Natural (S_Inode_Size) <= Static.Block_Size
then
Static.Inode_Size := Inode_Size (S_Inode_Size);
else
Success := False;
return;
end if;
pragma Assert (Static.Block_Size / Static.Inode_Size <= Natural (Inode_In_Block_Count'Last));
end;
else
Static.Inode_Size := Inode_Size'First;
end if;
Static.Inodes_Per_Block := Inode_In_Block_Count (Static.Block_Size / Static.Inode_Size);
end;
pragma Assert_And_Cut (Success and not Is_Mounted (State));
declare
S_Feature_Incompat : constant Unsigned_32 := Read_LE32 (Super_Block, 24 * 4);
begin
Static.Feature_Extents := (S_Feature_Incompat and FEATURE_INCOMPAT_EXTENTS) /= 0;
Static.Feature_64Bit := (S_Feature_Incompat and FEATURE_INCOMPAT_64BIT) /= 0;
if Static.Feature_64Bit then
declare
S_Desc_Size : constant Unsigned_16 := Read_LE16 (Super_Block, 63 * 4 + 2);
begin
if Natural (S_Desc_Size) in Group_Desc_Size and
Natural (S_Desc_Size) <= Static.Block_Size and
Is_Power_Of_2 (S_Desc_Size)
then
Static.Group_Desc_Size := Group_Desc_Size (S_Desc_Size);
else
Success := False;
return;
end if;
pragma Assert (Static.Block_Size / Static.Group_Desc_Size <= Natural (Group_In_Block_Count'Last));
end;
Static.Feature_64Bit := Static.Feature_64Bit and Static.Group_Desc_Size >= 64;
else
Static.Group_Desc_Size := Group_Desc_Size'First;
end if;
Static.Group_Desc_Per_Block := Group_In_Block_Count (Static.Block_Size / Static.Group_Desc_Size);
end;
State.S := Mounted;
end Mount;
procedure Read_FSBlock
(Buf : in out Buffer_Type;
FSBlock : in FSBlock_Offset;
Part_Len : in Partition_Length;
Success : out Boolean)
with
Pre => Buf'Length in Block_Size
is
FSBlock_64 : constant Integer_64 := Integer_64 (FSBlock);
Block_Size : constant Integer_64 := Integer_64 (Buf'Length);
Max_Block_Offset : constant Integer_64 := Integer_64 (Part_Len) / Block_Size - 1;
begin
if FSBlock_64 > Max_Block_Offset then
Success := False;
return;
end if;
Blockdev.Read (Buf, Blockdev_Length (FSBlock_64 * Block_Size), Success);
end Read_FSBlock;
procedure Cache_FSBlock
(Static : in Mount_State;
Cache : in out Block_Cache;
Phys : in FSBlock_Offset;
Level : in Block_Cache_Index;
Cache_Start : out Max_Block_Index;
Cache_End : out Max_Block_Index;
Success : out Boolean)
with
Post => Cache_End = Cache_Start + Static.Block_Size - 1
is
-- Limit cache usage depending on block size:
Max_Level : constant Block_Cache_Index := Block_Size'Last / Static.Block_Size - 1;
Cache_Level : constant Block_Cache_Index := Block_Cache_Index'Min (Level, Max_Level);
begin
Cache_Start := Cache_Level * Static.Block_Size;
Cache_End := Cache_Start + Static.Block_Size - 1;
if Cache.Phys (Cache_Level) = Phys then
Success := True;
else
Read_FSBlock
(Buf => Cache.Buffer (Cache_Start .. Cache_End),
FSBlock => Phys,
Part_Len => Static.Part_Len,
Success => Success);
Cache.Phys (Cache_Level) := Phys;
end if;
end Cache_FSBlock;
procedure Ext2_Block_Map
(State : in out T;
Logical : in FSBlock_Logical;
Physical : out FSBlock_Offset;
Success : out Boolean)
with
Post => State.Static = State.Static'Old and State.S = State.S'Old
is
Static : constant Mount_State := State.Static;
Direct_Blocks : constant := 12;
type Direct_Blocks_Array is array (Natural range 0 .. Direct_Blocks - 1) of Unsigned_32;
type Inode_Blocks is record
Direct_Blocks : Direct_Blocks_Array;
Indirect_Block : Unsigned_32;
Double_Indirect : Unsigned_32;
Triple_Indirect : Unsigned_32;
end record
with
Size => Inline_Extents'Length * 8,
Object_Size => Inline_Extents'Length * 8;
function I_Blocks is new Ada.Unchecked_Conversion (Inline_Extents, Inode_Blocks);
function I_Blocks (State : T) return Inode_Blocks is (I_Blocks (State.Inode.Inline));
Addr_Per_Block : constant FSBlock_Logical := FSBlock_Logical (Static.Block_Size / 4);
Max_Addr_Per_Block : constant FSBlock_Logical := FSBlock_Logical (Block_Size'Last / 4);
type Addr_In_Block_Range is range 0 .. Max_Addr_Per_Block - 1;
procedure Indirect_Block_Lookup
(Indirect_Block_Phys : in FSBlock_Offset;
Addr_In_Block : in Addr_In_Block_Range;
Level : in Block_Cache_Index;
Next_Physical : out FSBlock_Offset;
Success : out Boolean)
with
Pre =>
Addr_Per_Block = FSBlock_Logical (Static.Block_Size / 4) and
FSBlock_Logical (Addr_In_Block) < Addr_Per_Block,
Post => State.Static = State.Static'Old and State.S = State.S'Old
is
Cache_Start, Cache_End : Max_Block_Index;
begin
if Indirect_Block_Phys = 0 then
Next_Physical := 0;
Success := True;
return;
end if;
Cache_FSBlock
(Static => Static,
Cache => State.Cache,
Phys => Indirect_Block_Phys,
Level => Level,
Cache_Start => Cache_Start,
Cache_End => Cache_End,
Success => Success);
Next_Physical := FSBlock_Offset (Read_LE32
(Buf => State.Cache.Buffer (Cache_Start .. Cache_End),
Off => Natural (Addr_In_Block) * 4));
end Indirect_Block_Lookup;
Logical_Rest : FSBlock_Logical := Logical;
pragma Assert (Addr_Per_Block <= 2 ** 14);
begin
if Logical_Rest < Direct_Blocks then
Physical := FSBlock_Offset (I_Blocks (State).Direct_Blocks (Natural (Logical)));
Success := True;
return;
end if;
Logical_Rest := Logical_Rest - Direct_Blocks;
if Logical_Rest < Addr_Per_Block then
Indirect_Block_Lookup
(Indirect_Block_Phys => FSBlock_Offset (I_Blocks (State).Indirect_Block),
Addr_In_Block => Addr_In_Block_Range (Logical_Rest),
Level => Block_Map_Cache_Level,
Next_Physical => Physical,
Success => Success);
return;
end if;
Logical_Rest := Logical_Rest - Addr_Per_Block;
if Logical_Rest < Addr_Per_Block ** 2 then
Indirect_Block_Lookup
(Indirect_Block_Phys => FSBlock_Offset (I_Blocks (State).Double_Indirect),
Addr_In_Block => Addr_In_Block_Range ((Logical_Rest / Addr_Per_Block) mod Addr_Per_Block),
Level => Block_Map_Cache_Level + 1,
Next_Physical => Physical,
Success => Success);
if not Success then
return;
end if;
Indirect_Block_Lookup
(Indirect_Block_Phys => Physical,
Addr_In_Block => Addr_In_Block_Range (Logical_Rest mod Addr_Per_Block),
Level => Block_Map_Cache_Level,
Next_Physical => Physical,
Success => Success);
return;
end if;
Logical_Rest := Logical_Rest - Addr_Per_Block ** 2;
if Logical_Rest < Addr_Per_Block ** 3 then
Indirect_Block_Lookup
(Indirect_Block_Phys => FSBlock_Offset (I_Blocks (State).Triple_Indirect),
Addr_In_Block => Addr_In_Block_Range ((Logical_Rest / Addr_Per_Block ** 2) mod Addr_Per_Block),
Level => Block_Map_Cache_Level + 2,
Next_Physical => Physical,
Success => Success);
if not Success then
return;
end if;
Indirect_Block_Lookup
(Indirect_Block_Phys => Physical,
Addr_In_Block => Addr_In_Block_Range ((Logical_Rest / Addr_Per_Block) mod Addr_Per_Block),
Level => Block_Map_Cache_Level + 1,
Next_Physical => Physical,
Success => Success);
if not Success then
return;
end if;
Indirect_Block_Lookup
(Indirect_Block_Phys => Physical,
Addr_In_Block => Addr_In_Block_Range (Logical_Rest mod Addr_Per_Block),
Level => Block_Map_Cache_Level,
Next_Physical => Physical,
Success => Success);
return;
end if;
-- Logical address was just too high.
Physical := 0;
Success := False;
end Ext2_Block_Map;
procedure Extent_Block_Map
(State : in out T;
Logical : in FSBlock_Logical;
Physical : out FSBlock_Offset;
Success : out Boolean)
with
Post => State.Static = State.Static'Old and State.S = State.S'Old
is
Cache : Cache_Buffer renames State.Cache.Buffer;
-- Extent blocks always start with a 12B header and contain 12B entries.
-- Every entry starts with the number of the first logical block it
-- covers. Entries are sorted by this number.
-- Depth > 0 blocks have index entries, referencing further extent blocks.
-- Depth = 0 blocks have extent entries, referencing a contiguous range
-- of data blocks.
--
-- +-----------------+
-- .-> | Hdr depth=0 |
-- | +-----------------+
-- | | Extent 0.. 12 |
-- +-------------+ | +-----------------+
-- | Hdr depth=1 | | | Extent 13.. 13 |
-- +-------------+ | +-----------------+
-- | Index 0 | --' | Extent 14..122 |
-- +-------------+ +-----------------+
-- | Index 123 | --.
-- +-------------+ | +-----------------+
-- `-> | Hdr depth=0 |
-- +-----------------+
-- | Extent 123..125 |
-- +-----------------+
-- | Extent 126..234 |
-- +-----------------+
--
Extent_Header_Size : constant := 12;
Extent_Header_Magic : constant := 16#f30a#;
Initialized_Extent_Max_Len : constant := 2 ** 15;
subtype Extent_Off is Natural range 0 .. Extent_Header_Size;
subtype Extent_Idx is Natural range 1 .. (Max_Block_Index'Last + 1) / Extent_Header_Size - 1;
Dynamic_Max_Index : constant Extent_Idx := State.Static.Block_Size / Extent_Header_Size - 1;
subtype Extent_Depth is Natural range 0 .. 32;
function Extent_Byte_Offset (Idx : Extent_Idx; Off : Extent_Off) return Natural
is
(Idx * Extent_Header_Size + Off);
function Header_Magic (Buf : Buffer_Type) return Unsigned_16
is
(Read_LE16 (Buf, 0))
with
Pre => Buf'Length >= 2;
function Header_Entries (Buf : Buffer_Type) return Natural
is
(Natural (Read_LE16 (Buf, 2)))
with
Pre => Buf'Length >= 4;
function Header_Depth (Buf : Buffer_Type) return Natural
is
(Natural (Read_LE16 (Buf, 6)))
with
Pre => Buf'Length >= 8;
function Index_Logical (Buf : Buffer_Type; Idx : Extent_Idx) return FSBlock_Logical
is
(FSBlock_Logical (Read_LE32 (Buf, Extent_Byte_Offset (Idx, 0))))
with
Pre => Buf'Length >= Extent_Byte_Offset (Idx, 4);
function Index_Physical (Buf : Buffer_Type; Idx : Extent_Idx) return FSBlock_Offset
is
(FSBlock_Offset
(Shift_Left (Unsigned_64 (Read_LE16 (Buf, Extent_Byte_Offset (Idx, 8))), 32) or
Unsigned_64 (Read_LE32 (Buf, Extent_Byte_Offset (Idx, 4)))))
with
Pre => Buf'Length >= Extent_Byte_Offset (Idx, 10);
function Extent_Logical (Buf : Buffer_Type; Idx : Extent_Idx) return FSBlock_Logical
renames Index_Logical;
function Extent_Length (Buf : Buffer_Type; Idx : Extent_Idx) return FSBlock_Logical
is
(FSBlock_Logical (Read_LE16 (Buf, Extent_Byte_Offset (Idx, 4))))
with
Pre => Buf'Length >= Extent_Byte_Offset (Idx, 6);
function Extent_Physical (Buf : Buffer_Type; Idx : Extent_Idx) return FSBlock_Offset
is
(FSBlock_Offset
(Shift_Left (Unsigned_64 (Read_LE16 (Buf, Extent_Byte_Offset (Idx, 6))), 32) or
Unsigned_64 (Read_LE32 (Buf, Extent_Byte_Offset (Idx, 8)))))
with
Pre => Buf'Length >= Extent_Byte_Offset (Idx, 12);
function Bin_Search (Buf : Buffer_Type; Refs : Extent_Idx) return Extent_Idx
with
Pre =>
Buf'Length in 2 * Extent_Header_Size .. Max_Block_Index'Last and then
Refs in 1 .. Extent_Idx (Buf'Length / Extent_Header_Size - 1) and then
Extent_Logical (Buf, 1) <= Logical,
Post =>
Bin_Search'Result in 1 .. Refs and
Extent_Logical (Buf, Bin_Search'Result) <= Logical
is
Left : Positive := 2;
Right : Extent_Idx := Refs;
begin
while Left <= Right loop
declare
Mid : constant Extent_Idx := (Left + Right) / 2;
Ext_Logical : constant FSBlock_Logical := Extent_Logical (Buf, Mid);
begin
if Logical < Ext_Logical then
Right := Mid - 1;
else
Left := Mid + 1;
end if;
pragma Loop_Invariant
(Right <= Refs and then
Left in 2 .. Refs + 1 and then
Extent_Logical (Buf, Left - 1) <= Logical);
end;
end loop;
return Left - 1;
end Bin_Search;
procedure Next_Ref
(Current : in FSBlock_Offset;
Depth : in Extent_Depth;
Next : out Extent_Idx;
Cache_Start : out Max_Block_Index;
Cache_End : out Max_Block_Index;
Success : out Boolean)
with
Pre =>
Dynamic_Max_Index = State.Static.Block_Size / Extent_Header_Size - 1,
Post =>
State.Static = State.Static'Old and State.S = State.S'Old and
(if Success then
Next <= Dynamic_Max_Index and then
Cache_End = Cache_Start + State.Static.Block_Size - 1)
is
begin
Cache_FSBlock
(Static => State.Static,
Cache => State.Cache,
Phys => Current,
Level => Block_Map_Cache_Level + Depth,
Cache_Start => Cache_Start,
Cache_End => Cache_End,
Success => Success);
if not Success then
Next := 1;
return;
end if;
declare
Hdr_Magic : constant Unsigned_16 := Header_Magic (Cache (Cache_Start .. Cache_End));
Hdr_Entries : constant Natural := Header_Entries (Cache (Cache_Start .. Cache_End));
Hdr_Depth : constant Natural := Header_Depth (Cache (Cache_Start .. Cache_End));
First_Logical : constant FSBlock_Logical :=
Extent_Logical (Cache (Cache_Start .. Cache_End), 1);
begin
Success := Success and then
Hdr_Magic = Extent_Header_Magic and then
Hdr_Depth = Depth and then
Hdr_Entries in Extent_Idx and then
Hdr_Entries <= Dynamic_Max_Index;
if not Success or else
-- Is the searched `Logical' out of range?
First_Logical > Logical
then
Next := 1;
else
pragma Assert (Cache_End - Cache_Start + 1 = State.Static.Block_Size);
Next := Bin_Search (Cache (Cache_Start .. Cache_End), Hdr_Entries);
end if;
end;
end Next_Ref;
Inline_Extents : Ext2.Inline_Extents renames State.Inode.Inline;
Inode_Magic : constant Unsigned_16 := Header_Magic (Inline_Extents);
Inode_Entries : constant Natural := Header_Entries (Inline_Extents);
First_Logical : constant FSBlock_Logical := Extent_Logical (Inline_Extents, 1);
Depth : Natural := Header_Depth (Inline_Extents);
Cache_Start, Cache_End : Max_Block_Index;
Logical_Off, Length : FSBlock_Logical;
Physical_Off : FSBlock_Offset;
Idx : Extent_Idx;
begin
Physical := 0; -- Return `0' if the searched `Logical' is not allocated.
Success :=
Inode_Magic = Extent_Header_Magic and then
Inode_Entries < Inline_Extents'Length / Extent_Header_Size and then
Depth in Extent_Depth;
if not Success or else
-- Is there no extent or the searched `Logical' out of range?
Inode_Entries = 0 or else First_Logical > Logical
then
return;
end if;
Idx := Bin_Search (Inline_Extents, Inode_Entries);
if Depth = 0 then
Physical_Off := Extent_Physical (Inline_Extents, Idx);
Logical_Off := Extent_Logical (Inline_Extents, Idx);
Length := Extent_Length (Inline_Extents, Idx);
else
Physical_Off := Index_Physical (Inline_Extents, Idx);
Logical_Off := Index_Logical (Inline_Extents, Idx);
loop
pragma Loop_Invariant
(State.Static = State.Static'Loop_Entry and then
State.S = State.S'Loop_Entry and then
Depth > 0 and then
Depth in Extent_Depth and then
Logical_Off <= Logical);
Depth := Depth - 1;
Next_Ref
(Current => Physical_Off,
Depth => Depth,
Next => Idx,
Cache_Start => Cache_Start,
Cache_End => Cache_End,
Success => Success);
if not Success then
return;
end if;
exit when Depth = 0;
Physical_Off := Index_Physical (Cache (Cache_Start .. Cache_End), Idx);
Logical_Off := Index_Logical (Cache (Cache_Start .. Cache_End), Idx);
-- Is the searched `Logical' out of range?
if Logical_Off > Logical then
return;
end if;
end loop;
Physical_Off := Extent_Physical (Cache (Cache_Start .. Cache_End), Idx);
Logical_Off := Extent_Logical (Cache (Cache_Start .. Cache_End), Idx);
Length := Extent_Length (Cache (Cache_Start .. Cache_End), Idx);
end if;
-- Is the searched `Logical' out of range?
if Logical_Off > Logical or else
-- Is this an empty extent?
(Length = 0 or Length > Initialized_Extent_Max_Len)
then
return;
end if;
Success := Logical_Off <= FSBlock_Logical'Last - Length + 1;
if not Success or else
-- Is the searched `Logical' after this extent?
Logical > Logical_Off + Length - 1
then
return;
end if;
Success := FSBlock_Offset (Logical - Logical_Off) <= FSBlock_Offset'Last - Physical_Off;
if Success then
Physical := Physical_Off + FSBlock_Offset (Logical - Logical_Off);
end if;
end Extent_Block_Map;
procedure Parse_Group
(Static : in Mount_State;
Table_Start : out FSBlock_Offset;
Buf : in Buffer_Type;
Success : out Boolean)
with
Pre =>
Buf'Length >= Group_Desc_Size'First and
(if Static.Feature_64Bit then Buf'Length >= 64)
is
Inode_Table_Block : constant Unsigned_64 :=
(if Static.Feature_64Bit
then Shift_Left (Unsigned_64 (Read_LE32 (Buf, 40)), 32)
else 0)
or
Unsigned_64 (Read_LE32 (Buf, 8));
begin
if Inode_Table_Block >= Unsigned_64 (Static.First_Data_Block) and
Inode_Table_Block <= Unsigned_64 (FSBlock_Offset'Last)
then
Table_Start := FSBlock_Offset (Inode_Table_Block);
Success := True;
else
Table_Start := 0;
Success := False;
end if;
end Parse_Group;
procedure Parse_Inode
(Static : in Mount_State;
Inode : in out Inode_Info;
Buf : in Buffer_Type;
Success : out Boolean)
with
Pre => Buf'Length >= Inode_Size'First
is
S_IFMT : constant := 8#170000#;
S_IFDIR : constant := 8#040000#;
S_IFREG : constant := 8#100000#;
S_IFLNK : constant := 8#120000#;
I_Mode : constant Unsigned_16 := Read_LE16 (Buf, 0);
I_Blocks : constant Unsigned_32 := Read_LE32 (Buf, 28);
I_Flags : constant Unsigned_32 := Read_LE32 (Buf, 32);
I_File_ACL : constant Unsigned_32 := Read_LE32 (Buf, 104);
begin
case I_Mode and S_IFMT is
when S_IFDIR => Inode.Mode := Dir;
when S_IFREG => Inode.Mode := Regular;
when S_IFLNK => Inode.Mode := Link;
when others => Success := False; return;
end case;
if Inode.Mode = Link and
((I_File_ACL = 0 and I_Blocks = 0) or
(I_File_ACL /= 0 and I_Blocks = 2 ** (Static.Block_Size_Bits - 9)))
then
Inode.Mode := Fast_Link;
end if;
declare
I_Size : constant Unsigned_64 :=
(if Inode.Mode = Regular
then Shift_Left (Unsigned_64 (Read_LE32 (Buf, 108)), 32)
else 0) or
Unsigned_64 (Read_LE32 (Buf, 4));
begin
if I_Size > Unsigned_64 (Inode_Length'Last) then
Success := False;
return;
end if;
Inode.Size := Inode_Length (I_Size);
end;
Inode.Use_Extents := Static.Feature_Extents and (I_Flags and EXT4_EXTENTS_FL) /= 0;
Inode.Inline := Buf (Buf'First + 40 .. Buf'First + 100 - 1);
Success := True;
end Parse_Inode;
procedure Open
(State : in out T;
Inode : 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;
Cache : Cache_Buffer renames State.Cache.Buffer;
Group : constant Group_Index := Group_Index ((Inode - 1) / Static.Inodes_Per_Group);
Group_Block : constant FSBlock_Offset :=
1 + FSBlock_Offset (Static.First_Data_Block) + FSBlock_Offset (Group / Static.Group_Desc_Per_Block);
Group_In_Block : constant Group_In_Block_Index := Group_In_Block_Index (Group mod Static.Group_Desc_Per_Block);
Group_In_Block_Offset : constant Natural := Natural (Group_In_Block) * Static.Group_Desc_Size;
Cache_Start, Cache_End : Max_Block_Index;
First_Table_Block : FSBlock_Offset;
begin
State.S := Mounted;
Cache_FSBlock
(Static => Static,
Cache => State.Cache,
Phys => Group_Block,
Level => Group_Cache_Level,
Cache_Start => Cache_Start,
Cache_End => Cache_End,
Success => Success);
Success := Success and then
-- TODO: Prove this using a predicate on Mount_State:
Cache_Start <= Cache_End - Group_In_Block_Offset - Static.Group_Desc_Size + 1;
if not Success then
return;
end if;
Parse_Group
(Static => Static,
Table_Start => First_Table_Block,
Buf => Cache (Cache_Start + Group_In_Block_Offset ..
Cache_Start + Group_In_Block_Offset + Static.Group_Desc_Size - 1),
Success => Success);
if not Success then
return;
end if;
declare
Inode_In_Group : constant Inode_In_Group_Index :=
Inode_In_Group_Index ((Inode - 1) mod Static.Inodes_Per_Group);
Inode_Block : constant FSBlock_Offset := FSBlock_Offset (Inode_In_Group / Static.Inodes_Per_Block);
Inode_In_Block : constant Inode_In_Block_Index :=
Inode_In_Block_Index (Inode_In_Group mod Static.Inodes_Per_Block);
Inode_In_Block_Offset : constant Natural :=
Natural (Inode_In_Block) * Static.Inode_Size;
begin
if First_Table_Block > FSBlock_Offset'Last - Inode_Block then
Success := False;
return;
end if;
Cache_FSBlock
(Static => Static,
Cache => State.Cache,
Phys => FSBlock_Offset (First_Table_Block + Inode_Block),
Level => Inode_Cache_Level,
Cache_Start => Cache_Start,
Cache_End => Cache_End,
Success => Success);
Success := Success and then
-- TODO: Prove this using a predicate on Mount_State:
Cache_Start <= Cache_End - Inode_In_Block_Offset - Static.Inode_Size + 1;
if not Success then
return;
end if;
Parse_Inode
(Static => State.Static,
Inode => State.Inode,
Buf => Cache (Cache_Start + Inode_In_Block_Offset ..
Cache_Start + Inode_In_Block_Offset + Static.Inode_Size - 1),
Success => Success);
if Success then
State.Inode.I := Inode;
State.Inode.Map_Cache := (others => <>);
State.S := File_Opened;
end if;
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;
Root_Inode : constant := 2;
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;
File_Inode : Inode_Index;
File_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 := Root_Inode;
end if;
Open (State, State.Cur_Dir, Success);
if not Success then
return;
end if;
end if;
-- Lookup file in opened dir:
--
File_Pos := 0;
Success := False;
loop
pragma Loop_Invariant (Is_Open (State) and not Success);
declare
Dir_Entry_Header_Length : constant := 4 + 2 + 1 + 1;
Dir_Entry_Header : Buffer_Type (0 .. Dir_Entry_Header_Length - 1);
Dir_Entry_Name : Buffer_Type (0 .. File_Name_Max - 1);
Entry_File_Pos : File_Offset := File_Pos;
Dir_Entry_Length : File_Length;
Inode : Inode_0_Index;
Len : Natural;
begin
Read
(State => State,
File_Pos => Entry_File_Pos,
Buf => Dir_Entry_Header,
Len => Len);
if Len < Dir_Entry_Header_Length then
return;
end if;
-- Only check filenames of exact same length
Inode := Inode_0_Index (Read_LE32 (Dir_Entry_Header, 0));
if Inode >= Root_Inode and then
File_Name'Length = Natural (Dir_Entry_Header (6))
then
pragma Warnings
(GNATprove, Off, """Entry_File_Pos"" is set*but not used",
Reason => "We only care about intermedidate values.");
Read
(State => State,
File_Pos => Entry_File_Pos,
Buf => Dir_Entry_Name,
Len => Len);
pragma Warnings (GNATprove, On, """Entry_File_Pos"" is set*but not used");
if Len < File_Name'Length then
return;
end if;
Success := Str_Buf_Equal (File_Name, Dir_Entry_Name);
if Success then
File_Inode := Inode;
exit;
end if;
end if;
Dir_Entry_Length := File_Length (Read_LE16 (Dir_Entry_Header, 4));
if Dir_Entry_Length = 0 or
File_Pos > File_Length'Last - Dir_Entry_Length or
Unsigned_64 (File_Pos) >= Unsigned_64 (State.Inode.Size) - Unsigned_64 (Dir_Entry_Length)
then
return;
end if;
File_Pos := File_Pos + Dir_Entry_Length;
end;
end loop;
pragma Assert_And_Cut (Success and Is_Mounted (State));
Open (State, File_Inode, Success);
if not Success then
return;
end if;
if State.Inode.Mode = Dir then
State.Cur_Dir := File_Inode;
end if;
Success := State.Inode.Size <= Inode_Length (File_Length'Last);
if Success then
File_Len := File_Length (State.Inode.Size);
File_Type := (case State.Inode.Mode is
when Dir => FS.Dir,
when Regular => FS.Regular,
when Link .. Fast_Link => FS.Link);
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;
Cache : Cache_Buffer renames State.Cache.Buffer;
Pos : Natural range Buf'First .. Buf'Last + 1;
begin
if State.Inode.Mode = Fast_Link then
if State.Inode.Size > Inode_Length (State.Inode.Inline'Length) or
Inode_Length (File_Pos) >= State.Inode.Size then
Len := 0;
else
Len := Natural'Min (Buf'Length, Natural (State.Inode.Size) - Natural (File_Pos));
end if;
Buf (Buf'First .. Buf'First + Len - 1) :=
State.Inode.Inline (Natural (File_Pos) .. Natural (File_Pos) + Len - 1);
Buf (Buf'First + Len .. Buf'Last) := (others => 16#00#);
File_Pos := File_Pos + File_Length (Len);
return;
end if;
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 Max_Block_Index := Natural (File_Pos) mod Static.Block_Size;
Logical : constant FSBlock_Logical :=
FSBlock_Logical (File_Pos / File_Offset (Static.Block_Size));
In_Block_Space : constant Positive := Static.Block_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
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;
Cache_Start, Cache_End : Max_Block_Index;
Physical : FSBlock_Offset;
Success : Boolean;
begin
if State.Inode.Map_Cache.Logical = Block_Offset (Logical) then
Physical := State.Inode.Map_Cache.Phys;
Success := True;
elsif State.Inode.Use_Extents then
Extent_Block_Map (State, Logical, Physical, Success);
else
Ext2_Block_Map (State, Logical, Physical, Success);
end if;
exit when not Success;
State.Inode.Map_Cache := (Phys => Physical, Logical => Block_Offset (Logical));
if Physical > 0 then
Cache_FSBlock
(Static => Static,
Cache => State.Cache,
Phys => Physical,
Level => File_Cache_Level,
Cache_Start => Cache_Start,
Cache_End => Cache_End,
Success => Success);
pragma Assert (Cache_Start <= Cache_End - (In_Block + Len_Here - 1));
exit when not Success;
Buf (Pos .. Last) := Cache (
Cache_Start + In_Block .. Cache_Start + In_Block + Len_Here - 1);
else
-- Treat unallocated and unwritten blocks as all 00 (sparse files).
Buf (Pos .. Last) := (others => 16#00#);
end if;
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 => "ext2fs_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 => "ext2fs_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 => "ext2fs_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.Ext2;