Re-invent FS.VFS.Open()

Handle the path traversal and symbolic links in VFS.Open(). For each
path component, query the file type (directory, regular file, or
symbolic link) from the FS driver and act accordingly.
diff --git a/src/filo-fs-ext2.adb b/src/filo-fs-ext2.adb
index ec17fe4..1d074d4 100644
--- a/src/filo-fs-ext2.adb
+++ b/src/filo-fs-ext2.adb
@@ -736,7 +736,9 @@
    procedure Open
      (State       : in out T;
       File_Len    :    out File_Length;
-      File_Path   : in     String;
+      File_Type   :    out FS.File_Type;
+      File_Name   : in     String;
+      In_Root     : in     Boolean;
       Success     : out    Boolean)
    is
    begin
diff --git a/src/filo-fs-ext2.ads b/src/filo-fs-ext2.ads
index a12c50d..2c30e0a 100644
--- a/src/filo-fs-ext2.ads
+++ b/src/filo-fs-ext2.ads
@@ -18,10 +18,12 @@
    procedure Open
      (State       : in out T;
       File_Len    :    out File_Length;
-      File_Path   : in     String;
+      File_Type   :    out FS.File_Type;
+      File_Name   : in     String;
+      In_Root     : in     Boolean;
       Success     : out    Boolean)
    with
-      Pre => Is_Mounted (State) and not Is_Open (State),
+      Pre => Is_Mounted (State),
       Post => Success = Is_Open (State);
 
    procedure Close (State : in out T)
diff --git a/src/filo-fs-vfs.adb b/src/filo-fs-vfs.adb
index a76d84c..388f2cb 100644
--- a/src/filo-fs-vfs.adb
+++ b/src/filo-fs-vfs.adb
@@ -1,3 +1,4 @@
+with Ada.Unchecked_Conversion;
 with Interfaces.C;
 with Interfaces.C.Strings;
 
@@ -10,6 +11,15 @@
 
    State : T := Initial;
 
+   Path_Max : constant := 4096;
+   Max_Link_Depth : constant := 32;
+
+   subtype Path_Buffer is Buffer_Type (1 .. Path_Max);
+   Path_Buf : Path_Buffer;
+
+   subtype Path_String is String (1 .. Path_Max);
+   Path : Path_String;
+
    function C_Mount return int
    is
       Success : Boolean;
@@ -23,16 +33,148 @@
 
    function C_Open (File_Path : Strings.chars_ptr) return int
    is
-      File_Len : File_Length;
-      Success : Boolean;
+      function Component_Start (Path : String; Pos : Positive) return Positive is
+      begin
+         for I in Pos .. Path'Last loop
+            if Path (I) /= '/' then
+               return I;
+            end if;
+         end loop;
+         return Path'Last + 1;
+      end Component_Start;
+
+      function Component_End (Path : String; Start : Positive) return Positive is
+      begin
+         for I in Start .. Path'Last loop
+            if Path (I) = '/' or Is_Space (Path (I)) then
+               return I - 1;
+            end if;
+         end loop;
+         return Path'Last;
+      end Component_End;
+
+      function Path_End (Path : String; Start : Positive) return Positive is
+      begin
+         for I in Start .. Path'Last loop
+            if Is_Space (Path (I)) then
+               return I - 1;
+            end if;
+         end loop;
+         return Path'Last;
+      end Path_End;
+
+      procedure Read_Link
+        (Path        : in out Path_String;
+         Rest_First  : in     Positive;
+         Rest_Last   : in     Positive;
+         Link_Len    : in     Natural;
+         Success     :    out Boolean)
+      is
+         function As_String is new Ada.Unchecked_Conversion (Path_Buffer, Path_String);
+
+         Rest_Len : constant Natural := Rest_Last - Rest_First + 1;
+         File_Pos : File_Offset := 0;
+         Read_Len : Natural;
+      begin
+         if Path_Max - Rest_Len < Link_Len then
+            Success := False;
+            return;
+         end if;
+
+         Read
+           (State    => State,
+            File_Pos => File_Pos,
+            Buf      => Path_Buf (1 .. Link_Len),
+            Len      => Read_Len);
+         Success := Read_Len = Link_Len;
+
+         if Success then
+            Path (Link_Len + 1 .. Link_Len + Rest_Len) := Path (Rest_First .. Rest_Last);
+            Path (Link_Len + Rest_Len + 1 .. Path'Last) := (others => ' ');
+            Path (1 .. Link_Len) := As_String (Path_Buf) (1 .. Link_Len);
+         end if;
+      end Read_Link;
+
+      Path_Len : constant size_t := Strings.Strlen (File_Path);
+      Root_Dir : Boolean := True;
    begin
-      if Is_Mounted (State) and not Is_Open (State) then
-         Open (State, File_Len, Strings.Value (File_Path), Success);
-         Set_File_Max (File_Len);
-      else
-         Success := False;
+      if not Is_Mounted (State) or Path_Len > Path_Max then
+         return 0;
       end if;
-      return (if Success then 1 else 0);
+
+      Path (1 .. Natural (Path_Len)) := Strings.Value (File_Path);
+      Path (Natural (Path_Len + 1) .. Path'Last) := (others => ' ');
+
+      Link_Loop :
+      for I in 1 .. Max_Link_Depth loop
+         declare
+            Path_Pos : Positive := Path'First;
+            Path_Last : constant Positive := Path_End (Path, Path_Pos);
+         begin
+            Path_Loop :
+            loop
+               declare
+                  Comp_First : constant Positive := Component_Start (Path, Path_Pos);
+                  Comp_Last : constant Positive := Component_End (Path, Comp_First);
+                  File_Type : FS.File_Type;
+                  File_Len : File_Length;
+                  Success : Boolean;
+               begin
+                  if Comp_First > Comp_Last then
+                     return 0;
+                  end if;
+
+                  Open
+                    (State       => State,
+                     File_Len    => File_Len,
+                     File_Type   => File_Type,
+                     File_Name   => Path (Comp_First .. Comp_Last),
+                     In_Root     => Root_Dir,
+                     Success     => Success);
+                  if not Success then
+                     return 0;
+                  end if;
+                  Root_Dir := False;
+
+                  case File_Type is
+                     when Dir =>
+                        if Comp_Last = Path_Last then
+                           Close (State);
+                           return 0;
+                        end if;
+                        Path_Pos := Comp_Last + 1;
+                     when Regular =>
+                        if Comp_Last = Path_Last then
+                           Set_File_Max (File_Len);
+                           return 1;
+                        else
+                           Close (State);
+                           return 0;
+                        end if;
+                     when Link =>
+                        if File_Len > Path_Max then
+                           Success := False;
+                        else
+                           Read_Link
+                             (Path        => Path,
+                              Rest_First  => Comp_Last + 1,
+                              Rest_Last   => Path_Last,
+                              Link_Len    => Natural (File_Len),
+                              Success     => Success);
+                        end if;
+                        Close (State);
+                        if not Success then
+                           return 0;
+                        end if;
+                        Root_Dir := Path (1) = '/';
+                        exit Path_Loop; -- continue in Link_Loop
+                  end case;
+               end;
+            end loop Path_Loop;
+         end;
+      end loop Link_Loop;
+
+      return 0;
    end C_Open;
 
    procedure C_Close is
diff --git a/src/filo-fs-vfs.ads b/src/filo-fs-vfs.ads
index 9e47f6f..5ae086c 100644
--- a/src/filo-fs-vfs.ads
+++ b/src/filo-fs-vfs.ads
@@ -27,11 +27,13 @@
    with procedure Open
      (State       : in out T;
       File_Len    :    out File_Length;
-      File_Path   : in     String;
+      File_Type   :    out FS.File_Type;
+      File_Name   : in     String;
+      In_Root     : in     Boolean;
       Success     : out    Boolean)
    is <>
    with
-      Pre => Is_Mounted (State) and not Is_Open (State),
+      Pre => Is_Mounted (State),
       Post => Success = Is_Open (State);
 
    with procedure Close (State : in out T)
diff --git a/src/filo-fs.ads b/src/filo-fs.ads
index 183b1f7..a09df7c 100644
--- a/src/filo-fs.ads
+++ b/src/filo-fs.ads
@@ -5,6 +5,8 @@
 
 package FILO.FS is
 
+   type File_Type is (Dir, Regular, Link);
+
    type File_Length is range 0 .. int'Last; -- Should be higher, fix FILO first
    subtype File_Offset is File_Length;