ext2: Support gaps in extents for sparse files
diff --git a/src/filo-fs-ext2.adb b/src/filo-fs-ext2.adb
index 89d2dbb..ee9265b 100644
--- a/src/filo-fs-ext2.adb
+++ b/src/filo-fs-ext2.adb
@@ -449,7 +449,6 @@
 
       procedure Next_Ref
         (Current     : in     FSBlock_Offset;
-         Logical_Off : in     FSBlock_Logical;
          Depth       : in     Extent_Depth;
          Next        :    out Extent_Idx;
          Cache_Start :    out Max_Block_Index;
@@ -457,14 +456,12 @@
          Success     :    out Boolean)
       with
          Pre =>
-            Logical_Off <= Logical and
             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 and then
-               Extent_Logical (Cache (Cache_Start .. Cache_End), Next) <= Logical)
+               Cache_End = Cache_Start + State.Static.Block_Size - 1)
       is
       begin
          Cache_FSBlock
@@ -491,9 +488,11 @@
                Hdr_Magic = Extent_Header_Magic and then
                Hdr_Depth = Depth and then
                Hdr_Entries in Extent_Idx and then
-               Hdr_Entries <= Dynamic_Max_Index and then
-               First_Logical = Logical_Off;
-            if not Success 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);
@@ -510,26 +509,28 @@
 
       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 > 0 and then
          Inode_Entries < Inline_Extents'Length / Extent_Header_Size and then
-         First_Logical <= Logical and then
          Depth in Extent_Depth;
-      if not Success then
-         Physical := 0;
+      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 := Extent_Physical (Inline_Extents, Idx);
+         Physical_Off := Extent_Physical (Inline_Extents, Idx);
          Logical_Off := Extent_Logical (Inline_Extents, Idx);
          Length := Extent_Length (Inline_Extents, Idx);
       else
-         Physical := Index_Physical (Inline_Extents, Idx);
+         Physical_Off := Index_Physical (Inline_Extents, Idx);
          Logical_Off := Index_Logical (Inline_Extents, Idx);
          loop
             pragma Loop_Invariant
@@ -540,8 +541,7 @@
                Logical_Off <= Logical);
             Depth := Depth - 1;
             Next_Ref
-              (Current     => Physical,
-               Logical_Off => Logical_Off,
+              (Current     => Physical_Off,
                Depth       => Depth,
                Next        => Idx,
                Cache_Start => Cache_Start,
@@ -552,22 +552,39 @@
             end if;
 
             exit when Depth = 0;
-            Physical := Index_Physical (Cache (Cache_Start .. Cache_End), Idx);
+            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 := Extent_Physical (Cache (Cache_Start .. Cache_End), Idx);
+         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;
 
-      Success :=
-         (0 < Length and Length <= Initialized_Extent_Max_Len) and then
-         Logical_Off <= FSBlock_Logical'Last - Length and then
-         Logical < Logical_Off + Length and then
-         FSBlock_Offset (Logical - Logical_Off) <= FSBlock_Offset'Last - Physical;
+      -- 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 + FSBlock_Offset (Logical - Logical_Off);
+         Physical := Physical_Off + FSBlock_Offset (Logical - Logical_Off);
       end if;
    end Extent_Block_Map;
 
@@ -946,19 +963,25 @@
                end if;
                exit when not Success;
 
-               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;
+               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);
+                  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;