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-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