diff mbox series

[Ada] ACATS 4.1G - CXAG003 - Name_Case_Equivalence doesn't exist

Message ID 20180530090033.GA22572@adacore.com
State New
Headers show
Series [Ada] ACATS 4.1G - CXAG003 - Name_Case_Equivalence doesn't exist | expand

Commit Message

Pierre-Marie de Rodat May 30, 2018, 9 a.m. UTC
Implement a missing portion of Ada 2005's AI05-0049-1 for subprogram
Ada.Directories.Name_Case_Equivalence so that user programs can account for
operating system differences in case sensitivity.

------------
-- Source --
------------

--  main.adb

with Ada.Directories; use Ada.Directories;
with Ada.Text_IO;     use Ada.Text_IO;
procedure Main is
begin

  --  Directory layout:
  --     /empty +-- Nothing...
  --
  --     /mutliplefiles +-- "TEST1.TXT"
  --                    |
  --                "test1.txt"
  --
  --     /singlefile +-- "test1.txt"
  --
  --     /noncasable +-- "!"
  --

  Put_Line (Name_Case_Equivalence ("./empty")'Image);
  Put_Line (Name_Case_Equivalence ("./multiplefiles")'Image);
  Put_Line (Name_Case_Equivalence ("./singlefile")'Image);
  Put_Line (Name_Case_Equivalence ("./multiplefiles/test1.txt")'Image);
  Put_Line (Name_Case_Equivalence ("./singlefile/test1.txt")'Image);
  Put_Line (Name_Case_Equivalence ("./noncaseable/!")'Image);
end;

----------------------------
-- Compilation and Output --
----------------------------

& gnatmake -q main.adb
& main
CASE_SENSITIVE
CASE_SENSITIVE
CASE_SENSITIVE
CASE_SENSITIVE
CASE_SENSITIVE
CASE_SENSITIVE

Tested on x86_64-pc-linux-gnu, committed on trunk

2018-05-30  Justin Squirek  <squirek@adacore.com>

gcc/ada/

	* libgnat/a-direct.adb, libgnat/a-direct.ads (Name_Case_Equivalence):
	Add implementation.
	(Start_Search): Modify to use Start_Search_Internal
	(Start_Search_Internal): Add to break out an extra flag for searching
	case insensative due to the potential for directories within the same
	OS to allow different casing schemes.
	* sysdep.c (__gnat_name_case_equivalence): Add as a default fallback
	for when the more precise solution fails.
diff mbox series

Patch

--- gcc/ada/libgnat/a-direct.adb
+++ gcc/ada/libgnat/a-direct.adb
@@ -38,6 +38,8 @@  with Ada.Strings.Maps;           use Ada.Strings.Maps;
 with Ada.Strings.Unbounded;      use Ada.Strings.Unbounded;
 with Ada.Unchecked_Deallocation;
 
+with Interfaces.C;
+
 with System;                 use System;
 with System.CRTL;            use System.CRTL;
 with System.File_Attributes; use System.File_Attributes;
@@ -91,6 +93,16 @@  package body Ada.Directories is
    --  Get the next entry in a directory, setting Entry_Fetched if successful
    --  or resetting Is_Valid if not.
 
+   procedure Start_Search_Internal
+     (Search                 : in out Search_Type;
+      Directory              : String;
+      Pattern                : String;
+      Filter                 : Filter_Type := (others => True);
+      Force_Case_Insensitive : Boolean);
+   --  Similar to Start_Search except we can force a search to be
+   --  case-insensitive, which is important for detecting the name-case
+   --  equivalence for a given directory.
+
    ---------------
    -- Base_Name --
    ---------------
@@ -1057,6 +1069,103 @@  package body Ada.Directories is
       return Search.Value.Is_Valid;
    end More_Entries;
 
+   ---------------------------
+   -- Name_Case_Equivalence --
+   ---------------------------
+
+   function Name_Case_Equivalence (Name : String) return Name_Case_Kind is
+      Dir_Path  : Unbounded_String := To_Unbounded_String (Name);
+      S         : Search_Type;
+      Test_File : Directory_Entry_Type;
+
+      function GNAT_name_case_equivalence return Interfaces.C.int;
+      pragma Import
+        (C, GNAT_name_case_equivalence, "__gnat_name_case_equivalence");
+
+   begin
+      --  Check for the invalid case
+
+      if not Is_Valid_Path_Name (Name) then
+         raise Name_Error with "invalid path name """ & Name & '"';
+      end if;
+
+      --  We were passed a "full path" to a file and not a directory, so obtain
+      --  the containing directory.
+
+      if Is_Regular_File (Name) then
+         Dir_Path := To_Unbounded_String (Containing_Directory (Name));
+      end if;
+
+      --  Since we must obtain a file within the Name directory, let's grab the
+      --  first for our test. When the directory is empty, Get_Next_Entry will
+      --  fall through to a Status_Error where we then take the imprecise
+      --  default for the host OS.
+
+      Start_Search (Search    => S,
+                    Directory => To_String (Dir_Path),
+                    Pattern   => "",
+                    Filter    => (Directory => False, others => True));
+
+      loop
+         Get_Next_Entry (S, Test_File);
+
+         --  Check if we have found a "caseable" file
+
+         exit when To_Lower (Simple_Name (Test_File)) /=
+                     To_Upper (Simple_Name (Test_File));
+      end loop;
+
+      End_Search (S);
+
+      --  Search for files within the directory with the same name, but
+      --  differing cases.
+
+      Start_Search_Internal
+        (Search                 => S,
+         Directory              => To_String (Dir_Path),
+         Pattern                => Simple_Name (Test_File),
+         Filter                 => (Directory => False, others => True),
+         Force_Case_Insensitive => True);
+
+      --  We will find at least one match due to the search hitting our test
+      --  file.
+
+      Get_Next_Entry (S, Test_File);
+
+      begin
+         --  If we hit two then we know we have a case-sensitive directory
+
+         Get_Next_Entry (S, Test_File);
+         End_Search (S);
+
+         return Case_Sensitive;
+      exception
+         when Status_Error =>
+            null;
+      end;
+
+      --  Finally, we have a file in the directory whose name is unique and
+      --  "caseable". Let's test to see if the OS is able to identify the file
+      --  in multiple cases, which will give us our result without having to
+      --  resort to defaults.
+
+      if Exists (To_String (Dir_Path) & Directory_Separator
+                  & To_Lower (Simple_Name (Test_File)))
+        and then Exists (To_String (Dir_Path) & Directory_Separator
+                          & To_Upper (Simple_Name (Test_File)))
+      then
+         return Case_Preserving;
+      end if;
+
+      return Case_Sensitive;
+   exception
+      when Status_Error =>
+         --  There is no unobtrusive way to check for the directory's casing so
+         --  return the OS default.
+
+         return Name_Case_Kind'Val (Integer (GNAT_name_case_equivalence));
+   end Name_Case_Equivalence;
+
    ------------
    -- Rename --
    ------------
@@ -1289,6 +1398,21 @@  package body Ada.Directories is
       Pattern   : String;
       Filter    : Filter_Type := (others => True))
    is
+   begin
+      Start_Search_Internal (Search, Directory, Pattern, Filter, False);
+   end Start_Search;
+
+   ---------------------------
+   -- Start_Search_Internal --
+   ---------------------------
+
+   procedure Start_Search_Internal
+     (Search                 : in out Search_Type;
+      Directory              : String;
+      Pattern                : String;
+      Filter                 : Filter_Type := (others => True);
+      Force_Case_Insensitive : Boolean)
+   is
       function opendir (file_name : String) return DIRs;
       pragma Import (C, opendir, "__gnat_opendir");
 
@@ -1306,11 +1430,17 @@  package body Ada.Directories is
 
       --  Check the pattern
 
+      declare
+         Case_Sensitive : Boolean := Is_Path_Name_Case_Sensitive;
       begin
+         if Force_Case_Insensitive then
+            Case_Sensitive := False;
+         end if;
+
          Pat := Compile
            (Pattern,
             Glob           => True,
-            Case_Sensitive => Is_Path_Name_Case_Sensitive);
+            Case_Sensitive => Case_Sensitive);
       exception
          when Error_In_Regexp =>
             Free (Search.Value);
@@ -1339,6 +1469,6 @@  package body Ada.Directories is
       Search.Value.Pattern  := Pat;
       Search.Value.Dir      := Dir;
       Search.Value.Is_Valid := True;
-   end Start_Search;
+   end Start_Search_Internal;
 
 end Ada.Directories;

--- gcc/ada/libgnat/a-direct.ads
+++ gcc/ada/libgnat/a-direct.ads
@@ -231,6 +231,11 @@  package Ada.Directories is
    -- File and directory name operations --
    ----------------------------------------
 
+   type Name_Case_Kind is
+      (Unknown, Case_Sensitive, Case_Insensitive, Case_Preserving);
+   --  The type Name_Case_Kind represents the kind of file-name equivalence
+   --  rule for directories.
+
    function Full_Name (Name : String) return String;
    --  Returns the full name corresponding to the file name specified by Name.
    --  The exception Name_Error is propagated if the string given as Name does
@@ -281,6 +286,16 @@  package Ada.Directories is
    --  Name is not a possible simple name (if Extension is null) or base name
    --  (if Extension is non-null).
 
+   function Name_Case_Equivalence (Name : String) return Name_Case_Kind;
+   --  Returns the file-name equivalence rule for the directory containing
+   --  Name. Raises Name_Error if Name is not a full name. Returns
+   --  Case_Sensitive if file names that differ only in the case of letters are
+   --  considered different names. If file names that differ only in the case
+   --  of letters are considered the same name, then Case_Preserving is
+   --  returned if names have the case of the file name used when a file is
+   --  created; and Case_Insensitive is returned otherwise. Returns Unknown if
+   --  the file-name equivalence is not known.
+
    --------------------------------
    -- File and directory queries --
    --------------------------------

--- gcc/ada/sysdep.c
+++ gcc/ada/sysdep.c
@@ -1049,3 +1049,21 @@  _getpagesize (void)
   return getpagesize ();
 }
 #endif
+
+int
+__gnat_name_case_equivalence ()
+{
+  /*  the values here must be synchronized with Ada.Directories.Name_Case_Kind:
+
+      Unknown          = 0
+      Case_Sensitive   = 1
+      Case_Insensitive = 2
+      Case_Preserving  = 3  */
+
+#if defined (__APPLE__) || defined (WIN32)
+  return 3;
+#else
+  return 1;
+#endif
+}
+