1 ------------------------------------------------------------------------------
3 -- GNAT RUN-TIME COMPONENTS --
5 -- A D A . D I R E C T O R I E S --
9 -- Copyright (C) 2004-2023, Free Software Foundation, Inc. --
11 -- GNAT is free software; you can redistribute it and/or modify it under --
12 -- terms of the GNU General Public License as published by the Free Soft- --
13 -- ware Foundation; either version 3, or (at your option) any later ver- --
14 -- sion. GNAT is distributed in the hope that it will be useful, but WITH- --
15 -- OUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY --
16 -- or FITNESS FOR A PARTICULAR PURPOSE. --
18 -- As a special exception under Section 7 of GPL version 3, you are granted --
19 -- additional permissions described in the GCC Runtime Library Exception, --
20 -- version 3.1, as published by the Free Software Foundation. --
22 -- You should have received a copy of the GNU General Public License and --
23 -- a copy of the GCC Runtime Library Exception along with this program; --
24 -- see the files COPYING3 and COPYING.RUNTIME respectively. If not, see --
25 -- <http://www.gnu.org/licenses/>. --
27 -- GNAT was originally developed by the GNAT team at New York University. --
28 -- Extensive contributions were provided by Ada Core Technologies Inc. --
30 ------------------------------------------------------------------------------
32 with Ada
.Calendar
; use Ada
.Calendar
;
33 with Ada
.Characters
.Handling
; use Ada
.Characters
.Handling
;
34 with Ada
.Containers
.Vectors
;
35 with Ada
.Directories
.Validity
; use Ada
.Directories
.Validity
;
36 with Ada
.Directories
.Hierarchical_File_Names
;
37 use Ada
.Directories
.Hierarchical_File_Names
;
38 with Ada
.Strings
.Fixed
;
39 with Ada
.Strings
.Maps
; use Ada
.Strings
.Maps
;
40 with Ada
.Strings
.Unbounded
; use Ada
.Strings
.Unbounded
;
41 with Ada
.Unchecked_Deallocation
;
45 with System
; use System
;
46 with System
.CRTL
; use System
.CRTL
;
47 with System
.File_Attributes
; use System
.File_Attributes
;
48 with System
.File_IO
; use System
.File_IO
;
49 with System
.OS_Constants
; use System
.OS_Constants
;
50 with System
.OS_Lib
; use System
.OS_Lib
;
51 with System
.Regexp
; use System
.Regexp
;
53 package body Ada
.Directories
is
55 type Dir_Type_Value
is new Address
;
56 -- This is the low-level address directory structure as returned by the C
59 No_Dir
: constant Dir_Type_Value
:= Dir_Type_Value
(Null_Address
);
60 -- Null directory value
62 Dir_Separator
: constant Character;
63 pragma Import
(C
, Dir_Separator
, "__gnat_dir_separator");
64 -- Running system default directory separator
66 Dir_Seps
: constant Character_Set
:= Strings
.Maps
.To_Set
("/\");
67 -- UNIX and DOS style directory separators
70 pragma Import (C, Max_Path, "__gnat_max_path_len
");
71 -- The maximum length of a path
73 function C_Modification_Time (N : System.Address) return Ada.Calendar.Time;
74 pragma Import (C, C_Modification_Time, "__gnat_file_time
");
75 -- Get modification time for file with name referenced by N
77 Invalid_Time : constant Ada.Calendar.Time :=
78 C_Modification_Time (System.Null_Address);
79 -- Result returned from C_Modification_Time call when routine unable to get
80 -- file modification time.
82 Empty_String : constant String := "";
83 -- Empty string, returned by function Extension when there is no extension
85 ----------------------------
86 -- Directory Search Types --
87 ----------------------------
89 package Directory_Vectors is new
90 Ada.Containers.Vectors
91 (Index_Type => Natural,
92 Element_Type => Directory_Entry_Type);
93 use Directory_Vectors;
94 -- Used to store the results of the directory search
96 type Dir_Contents_Ptr is access Directory_Vectors.Vector;
98 procedure Free is new Ada.Unchecked_Deallocation
99 (Directory_Vectors.Vector, Dir_Contents_Ptr);
100 -- Directory_Vectors.Vector deallocation routine
102 type Search_State is new Ada.Finalization.Controlled with record
103 Dir_Contents : Dir_Contents_Ptr;
106 -- The Search_State consists of a vector of directory items that match the
107 -- search pattern and filter, and a cursor pointing to the next item of the
108 -- vector to be returned to the user.
110 procedure Free is new Ada.Unchecked_Deallocation (Search_State, Search_Ptr);
111 -- Search_State deallocation routine
113 Dir_Vector_Initial_Size : constant := 100;
114 -- Initial size for the Dir_Contents vector, sized to ensure the vector
115 -- does not need to be reallocated for reasonably sized directory searches.
117 ------------------------
118 -- Helper Subprograms --
119 ------------------------
121 function File_Exists (Name : String) return Boolean;
122 -- Returns True if the named file exists
124 procedure Start_Search_Internal
125 (Search : in out Search_Type;
128 Filter : Filter_Type := [others => True];
129 Case_Insensitive : Boolean);
130 -- Similar to Start_Search except we can specify a case-insensitive search.
131 -- This enables detecting the name-case equivalence for a given directory.
137 function Base_Name (Name : String) return String is
138 Simple : constant String := Simple_Name (Name);
139 -- Simple'First is guaranteed to be 1
142 -- Look for the last dot in the file name and return the part of the
143 -- file name preceding this last dot. If the first dot is the first
144 -- character of the file name, the base name is the empty string.
146 for Pos in reverse Simple'Range loop
147 if Simple (Pos) = '.' then
148 return Simple (1 .. Pos - 1);
152 -- If there is no dot, return the complete file name
162 (Containing_Directory : String := "";
164 Extension : String := "") return String
166 Result : String (1 .. Containing_Directory'Length +
167 Name'Length + Extension'Length + 2);
171 -- First, deal with the invalid cases
173 if Containing_Directory /= ""
174 and then not Is_Valid_Path_Name (Containing_Directory)
176 raise Name_Error with
177 "invalid directory path name
""" & Containing_Directory & '"';
180 Extension'Length = 0 and then (not Is_Valid_Simple_Name (Name))
182 raise Name_Error with
183 "invalid simple name """ & Name & '"';
185 elsif Extension'Length /= 0
186 and then not Is_Valid_Simple_Name (Name & '.' & Extension)
188 raise Name_Error with
189 "invalid file name
""" & Name & '.' & Extension & '"';
191 -- This is not an invalid case so build the path name
194 Last := Containing_Directory'Length;
195 Result (1 .. Last) := Containing_Directory;
197 -- Add a directory separator if needed
199 if Last /= 0 and then not Is_In (Result (Last), Dir_Seps) then
201 Result (Last) := Dir_Separator;
206 Result (Last + 1 .. Last + Name'Length) := Name;
207 Last := Last + Name'Length;
209 -- If extension was specified, add dot followed by this extension
211 if Extension'Length /= 0 then
213 Result (Last) := '.';
214 Result (Last + 1 .. Last + Extension'Length) := Extension;
215 Last := Last + Extension'Length;
218 return Result (1 .. Last);
222 --------------------------
223 -- Containing_Directory --
224 --------------------------
226 function Containing_Directory (Name : String) return String is
228 -- First, the invalid case
230 if not Is_Valid_Path_Name (Name) then
231 raise Name_Error with "invalid path name """ & Name & '"';
235 Last_DS : constant Natural :=
236 Strings.Fixed.Index (Name, Dir_Seps, Going => Strings.Backward);
239 -- If Name indicates a root directory, raise Use_Error, because
240 -- it has no containing directory.
242 if Is_Parent_Directory_Name (Name)
243 or else Is_Current_Directory_Name (Name)
244 or else Is_Root_Directory_Name (Name)
247 "directory
""" & Name & """ has no containing directory
";
249 elsif Last_DS = 0 then
250 -- There is no directory separator, so return ".", representing
251 -- the current working directory.
257 Last : Positive := Last_DS - Name'First + 1;
258 Result : String (1 .. Last);
261 Result := Name (Name'First .. Last_DS);
263 -- Remove any trailing directory separator, except as the
264 -- first character or the first character following a drive
265 -- number on Windows.
268 exit when Is_Root_Directory_Name (Result (1 .. Last))
269 or else (Result (Last) /= Directory_Separator
270 and then Result (Last) /= '/');
275 return Result (1 .. Last);
280 end Containing_Directory;
287 (Source_Name : String;
288 Target_Name : String;
292 Mode : Copy_Mode := Overwrite;
293 Preserve : Attribute := None;
296 -- First, the invalid cases
298 if not Is_Valid_Path_Name (Source_Name) then
299 raise Name_Error with
300 "invalid source path name
""" & Source_Name & '"';
302 elsif not Is_Valid_Path_Name (Target_Name) then
303 raise Name_Error with
304 "invalid target path name """ & Target_Name & '"';
306 elsif not Is_Regular_File (Source_Name) then
307 raise Name_Error with '"' & Source_Name & """ is not a file";
309 elsif Is_Directory (Target_Name) then
310 raise Use_Error with "target """ & Target_Name & """ is a directory";
313 if Form'Length > 0 then
315 Formstr : String (1 .. Form'Length + 1);
319 -- Acquire form string, setting required NUL terminator
321 Formstr (1 .. Form'Length) := Form;
322 Formstr (Formstr'Last) := ASCII.NUL;
324 -- Convert form string to lower case
326 for J in Formstr'Range loop
327 if Formstr (J) in 'A
' .. 'Z
' then
329 Character'Val (Character'Pos (Formstr (J)) + 32);
335 Form_Parameter (Formstr, "mode", V1, V2);
339 elsif Formstr (V1 .. V2) = "copy" then
341 elsif Formstr (V1 .. V2) = "overwrite" then
343 elsif Formstr (V1 .. V2) = "append" then
346 raise Use_Error with "invalid Form";
349 Form_Parameter (Formstr, "preserve", V1, V2);
353 elsif Formstr (V1 .. V2) = "timestamps" then
354 Preserve := Time_Stamps;
355 elsif Formstr (V1 .. V2) = "all_attributes" then
357 elsif Formstr (V1 .. V2) = "no_attributes" then
360 raise Use_Error with "invalid Form";
365 -- Do actual copy using System.OS_Lib.Copy_File
367 Copy_File (Source_Name, Target_Name, Success, Mode, Preserve);
370 raise Use_Error with "copy of """ & Source_Name & """ failed";
375 ----------------------
376 -- Create_Directory --
377 ----------------------
379 procedure Create_Directory
380 (New_Directory : String;
383 Dir_Name_C : constant String := New_Directory & ASCII.NUL;
386 -- First, the invalid case
388 if not Is_Valid_Path_Name (New_Directory) then
389 raise Name_Error with
390 "invalid new directory path name """ & New_Directory & '"';
393 -- Acquire setting of encoding parameter
396 Formstr : constant String := To_Lower (Form);
398 Encoding : CRTL.Filename_Encoding;
399 -- Filename encoding specified into the form parameter
404 Form_Parameter (Formstr, "encoding
", V1, V2);
407 Encoding := CRTL.Unspecified;
408 elsif Formstr (V1 .. V2) = "utf8
" then
409 Encoding := CRTL.UTF8;
410 elsif Formstr (V1 .. V2) = "8bits
" then
411 Encoding := CRTL.ASCII_8bits;
413 raise Use_Error with "invalid Form
";
416 if CRTL.mkdir (Dir_Name_C, Encoding) /= 0 then
418 "creation
of new directory
""" & New_Directory & """ failed
";
422 end Create_Directory;
428 procedure Create_Path
429 (New_Directory : String;
432 New_Dir : String (1 .. New_Directory'Length + 1);
433 Last : Positive := 1;
434 Start : Positive := 1;
437 -- First, the invalid case
439 if not Is_Valid_Path_Name (New_Directory) then
440 raise Name_Error with
441 "invalid
new directory path name
""" & New_Directory & '"';
444 -- Build New_Dir with a directory separator at the end, so that the
445 -- complete path will be found in the loop below.
447 New_Dir (1 .. New_Directory'Length) := New_Directory;
448 New_Dir (New_Dir'Last) := Directory_Separator;
450 -- If host is windows, and the first two characters are directory
451 -- separators, we have an UNC path. Skip it.
453 if Directory_Separator = '\
'
454 and then New_Dir'Length > 2
455 and then Is_In (New_Dir (1), Dir_Seps)
456 and then Is_In (New_Dir (2), Dir_Seps)
461 exit when Start = New_Dir'Last
462 or else Is_In (New_Dir (Start), Dir_Seps);
466 -- Create, if necessary, each directory in the path
468 for J in Start + 1 .. New_Dir'Last loop
470 -- Look for the end of an intermediate directory
472 if not Is_In (New_Dir (J), Dir_Seps) then
475 -- We have found a new intermediate directory each time we find
476 -- a first directory separator.
478 elsif not Is_In (New_Dir (J - 1), Dir_Seps) then
480 -- No need to create the directory if it already exists
482 if not Is_Directory (New_Dir (1 .. Last)) then
485 (New_Directory => New_Dir (1 .. Last), Form => Form);
489 if File_Exists (New_Dir (1 .. Last)) then
491 -- A file with such a name already exists. If it is
492 -- a directory, then it was apparently just created
493 -- by another process or thread, and all is well.
494 -- If it is of some other kind, report an error.
496 if not Is_Directory (New_Dir (1 .. Last)) then
498 "file """ & New_Dir (1 .. Last) &
499 """ already exists and is not a directory";
503 -- Create_Directory failed for some other reason:
504 -- propagate the exception.
515 -----------------------
516 -- Current_Directory --
517 -----------------------
519 function Current_Directory return String is
520 Path_Len : Natural := Max_Path;
521 Buffer : String (1 .. 1 + Max_Path + 1);
523 procedure Local_Get_Current_Dir (Dir : Address; Length : Address);
524 pragma Import (C, Local_Get_Current_Dir, "__gnat_get_current_dir");
527 Local_Get_Current_Dir (Buffer'Address, Path_Len'Address);
530 raise Use_Error with "current directory does not exist";
533 -- We need to resolve links because of RM A.16(47), which requires
534 -- that we not return alternative names for files.
536 return Normalize_Pathname (Buffer (1 .. Path_Len));
537 end Current_Directory;
539 ----------------------
540 -- Delete_Directory --
541 ----------------------
543 procedure Delete_Directory (Directory : String) is
545 -- First, the invalid cases
547 if not Is_Valid_Path_Name (Directory) then
548 raise Name_Error with
549 "invalid directory path name """ & Directory & '"';
551 elsif not Is_Directory (Directory) then
552 raise Name_Error with '"' & Directory & """ not a directory";
554 -- Do the deletion, checking for error
558 Dir_Name_C : constant String := Directory & ASCII.NUL;
560 if rmdir (Dir_Name_C) /= 0 then
562 "deletion of directory """ & Directory & """ failed";
566 end Delete_Directory;
572 procedure Delete_File (Name : String) is
576 -- First, the invalid cases
578 if not Is_Valid_Path_Name (Name) then
579 raise Name_Error with "invalid path name """ & Name & '"';
581 elsif not Is_Regular_File (Name)
582 and then not Is_Symbolic_Link (Name)
584 raise Name_Error with "file
""" & Name & """ does
not exist
";
587 -- Do actual deletion using System.OS_Lib.Delete_File
589 Delete_File (Name, Success);
592 raise Use_Error with "file
""" & Name & """ could
not be deleted
";
601 procedure Delete_Tree (Directory : String) is
602 Search : Search_Type;
603 Dir_Ent : Directory_Entry_Type;
605 -- First, the invalid cases
607 if not Is_Valid_Path_Name (Directory) then
608 raise Name_Error with
609 "invalid directory path name
""" & Directory & '"';
611 elsif not Is_Directory (Directory) then
612 raise Name_Error with '"' & Directory & """ not a directory
";
616 -- We used to change the current directory to Directory here,
617 -- allowing the use of a local Simple_Name for all references. This
618 -- turned out unfriendly to multitasking programs, where tasks
619 -- running in parallel of this Delete_Tree could see their current
620 -- directory change unpredictably. We now resort to Full_Name
621 -- computations to reach files and subdirs instead.
623 Start_Search (Search, Directory => Directory, Pattern => "");
624 while More_Entries (Search) loop
625 Get_Next_Entry (Search, Dir_Ent);
628 Fname : constant String := Full_Name (Dir_Ent);
629 Sname : constant String := Simple_Name (Dir_Ent);
632 if OS_Lib.Is_Directory (Fname) then
633 if Sname /= "." and then Sname /= ".." then
645 Dir_Name_C : constant String := Directory & ASCII.NUL;
648 if rmdir (Dir_Name_C) /= 0 then
650 "directory tree rooted
at """ &
651 Directory & """ could
not be deleted
";
661 function Exists (Name : String) return Boolean is
663 -- First, the invalid case
665 if not Is_Valid_Path_Name (Name) then
666 raise Name_Error with "invalid path name
""" & Name & '"';
669 -- The implementation is in File_Exists
671 return File_Exists (Name);
679 function Extension (Name : String) return String is
681 -- First, the invalid case
683 if not Is_Valid_Path_Name (Name) then
684 raise Name_Error with "invalid path name """ & Name & '"';
687 -- Look for first dot that is not followed by a directory separator
689 for Pos in reverse Name'Range loop
691 -- If a directory separator is found before a dot, there is no
694 if Is_In (Name (Pos), Dir_Seps) then
697 elsif Name (Pos) = '.' then
699 -- We found a dot, build the return value with lower bound 1
702 subtype Result_Type is String (1 .. Name'Last - Pos);
704 return Result_Type (Name (Pos + 1 .. Name'Last));
709 -- No dot were found, there is no extension
719 function File_Exists (Name : String) return Boolean is
720 function C_File_Exists (A : Address) return Integer;
721 pragma Import (C, C_File_Exists, "__gnat_file_exists
");
723 C_Name : String (1 .. Name'Length + 1);
726 C_Name (1 .. Name'Length) := Name;
727 C_Name (C_Name'Last) := ASCII.NUL;
728 return C_File_Exists (C_Name'Address) = 1;
735 procedure Finalize (Search : in out Search_Type) is
737 if Search.State /= null then
738 Free (Search.State.Dir_Contents);
747 function Full_Name (Name : String) return String is
749 -- First, the invalid case
751 if not Is_Valid_Path_Name (Name) then
752 raise Name_Error with "invalid path name
""" & Name & '"';
755 -- Build the return value with lower bound 1
757 -- Use System.OS_Lib.Normalize_Pathname
760 -- We need to resolve links because of (RM A.16(47)), which says
761 -- we must not return alternative names for files.
763 Value : constant String := Normalize_Pathname (Name);
764 subtype Result is String (1 .. Value'Length);
767 return Result (Value);
772 function Full_Name (Directory_Entry : Directory_Entry_Type) return String is
774 -- If the Directory_Entry is valid return the full name contained in the
777 if not Directory_Entry.Valid then
778 raise Status_Error with "invalid directory entry";
780 return To_String (Directory_Entry.Full_Name);
788 procedure Get_Next_Entry
789 (Search : in out Search_Type;
790 Directory_Entry : out Directory_Entry_Type)
793 -- A Search with no state implies the user has not called Start_Search
795 if Search.State = null then
796 raise Status_Error with "search not started";
799 -- If the next entry is No_Element it means the search is finished and
800 -- there are no more entries to return.
802 if Search.State.Next_Entry = No_Element then
803 raise Status_Error with "no more entries";
806 -- Populate Directory_Entry with the next entry and update the search
809 Directory_Entry := Element (Search.State.Next_Entry);
810 Next (Search.State.Next_Entry);
812 -- If Start_Search received a non-zero error code when trying to read
813 -- the file attributes of this entry, raise an Use_Error so the user
814 -- is aware that it was not possible to retrieve the attributes of this
817 if Directory_Entry.Attr_Error_Code /= 0 then
819 with To_String (Directory_Entry.Full_Name) & ": " &
820 Errno_Message (Err => Directory_Entry.Attr_Error_Code);
828 function Kind (Name : String) return File_Kind is
830 -- First, the invalid case
832 if not File_Exists (Name) then
833 raise Name_Error with "file """ & Name & """ does not exist";
835 -- If OK, return appropriate kind
837 elsif Is_Regular_File (Name) then
838 return Ordinary_File;
840 elsif Is_Directory (Name) then
848 function Kind (Directory_Entry : Directory_Entry_Type) return File_Kind is
850 if not Directory_Entry.Valid then
851 raise Status_Error with "invalid directory entry";
853 return Directory_Entry.Kind;
857 -----------------------
858 -- Modification_Time --
859 -----------------------
861 function Modification_Time (Name : String) return Time is
864 C_Name : aliased String (1 .. Name'Length + 1);
866 -- First, the invalid cases
868 if not (Is_Regular_File (Name) or else Is_Directory (Name)) then
869 raise Name_Error with '"' & Name & """ not a file
or directory
";
872 C_Name := Name & ASCII.NUL;
873 Date := C_Modification_Time (C_Name'Address);
875 if Date = Invalid_Time then
877 "Unable to get modification time
of the file
""" & Name & '"';
882 end Modification_Time;
884 function Modification_Time
885 (Directory_Entry : Directory_Entry_Type) return Ada.Calendar.Time
888 -- If the Directory_Entry is valid return the modification time
889 -- contained in the entry record. The modification time is recorded in
890 -- the entry since its cheap to query all the file the attributes in
891 -- one read when the directory is searched.
893 if not Directory_Entry.Valid then
894 raise Status_Error with "invalid directory entry";
896 return Directory_Entry.Modification_Time;
898 end Modification_Time;
904 function More_Entries (Search : Search_Type) return Boolean is
906 -- If the vector cursor Search.State.Next_Entry points to an element in
907 -- Search.State.Dir_Contents then there is another entry to return.
908 -- Otherwise, we return False.
910 if Search.State = null then
912 elsif Search.State.Next_Entry = No_Element then
919 ---------------------------
920 -- Name_Case_Equivalence --
921 ---------------------------
923 function Name_Case_Equivalence (Name : String) return Name_Case_Kind is
924 Dir_Path : Unbounded_String := To_Unbounded_String (Name);
926 Test_File : Directory_Entry_Type;
928 function GNAT_name_case_equivalence return Interfaces.C.int;
929 pragma Import (C, GNAT_name_case_equivalence,
930 "__gnat_name_case_equivalence");
933 -- Check for the invalid case
935 if not Is_Valid_Path_Name (Name) then
936 raise Name_Error with "invalid path name """ & Name & '"';
939 -- We were passed a "full path
" to a file and not a directory, so obtain
940 -- the containing directory.
942 if Is_Regular_File (Name) then
943 Dir_Path := To_Unbounded_String (Containing_Directory (Name));
946 -- Since we must obtain a file within the Name directory, let's grab the
947 -- first for our test. When the directory is empty, Get_Next_Entry will
948 -- fall through to a Status_Error where we then take the imprecise
949 -- default for the host OS.
953 Directory => To_String (Dir_Path),
955 Filter => [Directory => False, others => True]);
958 Get_Next_Entry (S, Test_File);
960 -- Check if we have found a "caseable
" file
962 exit when To_Lower (Simple_Name (Test_File)) /=
963 To_Upper (Simple_Name (Test_File));
968 -- Search for files within the directory with the same name, but
971 Start_Search_Internal
973 Directory => To_String (Dir_Path),
974 Pattern => Simple_Name (Test_File),
975 Filter => [Directory => False, others => True],
976 Case_Insensitive => True);
978 -- We will find at least one match due to the search hitting our test
981 Get_Next_Entry (S, Test_File);
984 -- If we hit two then we know we have a case-sensitive directory
986 Get_Next_Entry (S, Test_File);
989 return Case_Sensitive;
995 -- Finally, we have a file in the directory whose name is unique and
996 -- "caseable
". Let's test to see if the OS is able to identify the file
997 -- in multiple cases, which will give us our result without having to
998 -- resort to defaults.
1000 if Exists (To_String (Dir_Path) & Directory_Separator
1001 & To_Lower (Simple_Name (Test_File)))
1002 and then Exists (To_String (Dir_Path) & Directory_Separator
1003 & To_Upper (Simple_Name (Test_File)))
1005 return Case_Preserving;
1008 return Case_Sensitive;
1010 when Status_Error =>
1012 -- There is no unobtrusive way to check for the directory's casing so
1013 -- return the OS default.
1015 return Name_Case_Kind'Val (Integer (GNAT_name_case_equivalence));
1016 end Name_Case_Equivalence;
1022 procedure Rename (Old_Name, New_Name : String) is
1026 -- First, the invalid cases
1028 if not Is_Valid_Path_Name (Old_Name) then
1029 raise Name_Error with "invalid old path name
""" & Old_Name & '"';
1031 elsif not Is_Valid_Path_Name (New_Name) then
1032 raise Name_Error with "invalid new path name """ & New_Name & '"';
1034 elsif not Is_Regular_File (Old_Name)
1035 and then not Is_Directory (Old_Name)
1037 raise Name_Error with "old file
""" & Old_Name & """ does
not exist
";
1039 elsif Is_Regular_File (New_Name) or else Is_Directory (New_Name) then
1040 raise Use_Error with
1041 "new name
""" & New_Name
1042 & """ designates a file that already exists
";
1044 -- Do actual rename using System.OS_Lib.Rename_File
1047 Rename_File (Old_Name, New_Name, Success);
1051 -- AI05-0231-1: Name_Error should be raised in case a directory
1052 -- component of New_Name does not exist (as in New_Name =>
1053 -- "/no
-such
-dir
/new-filename
"). ENOENT indicates that. ENOENT
1054 -- also indicate that the Old_Name does not exist, but we already
1055 -- checked for that above. All other errors are Use_Error.
1057 if Errno = ENOENT then
1058 raise Name_Error with
1059 "file
""" & Containing_Directory (New_Name) & """ not found
";
1062 raise Use_Error with
1063 "file
""" & Old_Name & """ could
not be renamed
";
1074 (Directory : String;
1076 Filter : Filter_Type := [others => True];
1077 Process : not null access procedure
1078 (Directory_Entry : Directory_Entry_Type))
1081 Directory_Entry : Directory_Entry_Type;
1084 Start_Search (Srch, Directory, Pattern, Filter);
1085 while More_Entries (Srch) loop
1086 Get_Next_Entry (Srch, Directory_Entry);
1087 Process (Directory_Entry);
1097 procedure Set_Directory (Directory : String) is
1098 Dir_Name_C : constant String := Directory & ASCII.NUL;
1100 if not Is_Valid_Path_Name (Directory) then
1101 raise Name_Error with
1102 "invalid directory path name
& """ & Directory & '"';
1104 elsif not Is_Directory (Directory) then
1105 raise Name_Error with
1106 "directory """ & Directory & """ does not exist";
1108 elsif chdir (Dir_Name_C) /= 0 then
1109 raise Name_Error with
1110 "could not set to designated directory """ & Directory & '"';
1118 function Simple_Name (Name : String) return String is
1120 function Simple_Name_Internal (Path : String) return String;
1121 -- This function does the job
1123 --------------------------
1124 -- Simple_Name_Internal --
1125 --------------------------
1127 function Simple_Name_Internal (Path : String) return String is
1128 Cut_Start : Natural :=
1129 Strings.Fixed.Index (Path, Dir_Seps, Going => Strings.Backward);
1131 -- Cut_End points to the last simple name character
1133 Cut_End : Natural := Path'Last;
1136 -- Root directories are considered simple
1138 if Is_Root_Directory_Name (Path) then
1142 -- Handle trailing directory separators
1144 if Cut_Start = Path'Last then
1145 Cut_End := Path'Last - 1;
1146 Cut_Start := Strings.Fixed.Index
1147 (Path (Path'First .. Path'Last - 1),
1148 Dir_Seps, Going => Strings.Backward);
1151 -- Cut_Start points to the first simple name character
1153 Cut_Start := (if Cut_Start = 0 then Path'First else Cut_Start + 1);
1155 Check_For_Standard_Dirs : declare
1156 BN : constant String := Path (Cut_Start .. Cut_End);
1158 Has_Drive_Letter : constant Boolean :=
1159 OS_Lib.Path_Separator /= ':';
1160 -- If Path separator is not ':' then we are on a DOS based OS
1161 -- where this character is used as a drive letter separator.
1164 if BN = "." or else BN = ".." then
1167 elsif Has_Drive_Letter
1168 and then BN'Length > 2
1169 and then Characters.Handling.Is_Letter (BN (BN'First))
1170 and then BN (BN'First + 1) = ':'
1172 -- We have a DOS drive letter prefix, remove it
1174 return BN (BN'First + 2 .. BN'Last);
1179 end Check_For_Standard_Dirs;
1180 end Simple_Name_Internal;
1182 -- Start of processing for Simple_Name
1185 -- First, the invalid case
1187 if not Is_Valid_Path_Name (Name) then
1188 raise Name_Error with "invalid path name
""" & Name & '"';
1191 -- Build the value to return with lower bound 1
1194 Value : constant String := Simple_Name_Internal (Name);
1195 subtype Result is String (1 .. Value'Length);
1197 return Result (Value);
1202 function Simple_Name
1203 (Directory_Entry : Directory_Entry_Type) return String is
1205 -- If the Directory_Entry is valid return the simple name contained in
1206 -- the entry record.
1208 if not Directory_Entry.Valid then
1209 raise Status_Error with "invalid directory entry";
1211 return To_String (Directory_Entry.Name);
1219 function Size (Name : String) return File_Size is
1220 C_Name : String (1 .. Name'Length + 1);
1222 function C_Size (Name : Address) return int64;
1223 pragma Import (C, C_Size, "__gnat_named_file_length");
1226 -- First, the invalid case
1228 if not Is_Regular_File (Name) then
1229 raise Name_Error with "file """ & Name & """ does not exist";
1232 C_Name (1 .. Name'Length) := Name;
1233 C_Name (C_Name'Last) := ASCII.NUL;
1234 return File_Size (C_Size (C_Name'Address));
1238 function Size (Directory_Entry : Directory_Entry_Type) return File_Size is
1240 -- If the Directory_Entry is valid return the size contained in the
1241 -- entry record. The size is recorded in the entry since it is cheap to
1242 -- query all the file the attributes in one read when the directory is
1245 if not Directory_Entry.Valid then
1246 raise Status_Error with "invalid directory entry";
1248 return Directory_Entry.Size;
1256 procedure Start_Search
1257 (Search : in out Search_Type;
1260 Filter : Filter_Type := [others => True])
1263 Start_Search_Internal (Search, Directory, Pattern, Filter, False);
1266 ---------------------------
1267 -- Start_Search_Internal --
1268 ---------------------------
1270 procedure Start_Search_Internal
1271 (Search : in out Search_Type;
1274 Filter : Filter_Type := [others => True];
1275 Case_Insensitive : Boolean)
1277 function closedir (Directory : DIRs) return Integer
1278 with Import, External_Name => "__gnat_closedir", Convention => C;
1279 -- C lib function to close Directory
1281 function opendir (Directory : String) return DIRs
1282 with Import, External_Name => "__gnat_opendir", Convention => C;
1283 -- C lib function to open Directory
1285 function readdir_gnat
1286 (Directory : Address;
1288 Last : not null access Integer) return Address
1289 with Import, External_Name => "__gnat_readdir", Convention => C;
1290 -- Read the next item in Directory
1292 Dir_Name_C : constant String := Directory & ASCII.NUL;
1293 Dir_Entry_Buffer : array (1 .. SIZEOF_struct_dirent_alloc) of Character;
1294 Dir_Pointer : Dir_Type_Value;
1295 File_Name_Addr : Address;
1296 File_Name_Len : aliased Integer;
1297 Pattern_Regex : Regexp;
1299 Call_Result : Integer;
1300 pragma Warnings (Off, Call_Result);
1301 -- Result of calling a C function that returns a status
1304 -- Check that Directory is a valid directory
1306 if not Is_Directory (Directory) then
1307 raise Name_Error with
1308 "unknown directory """ & Simple_Name (Directory) & '"';
1311 -- Check and compile the pattern
1314 Case_Sensitive : Boolean := Is_Path_Name_Case_Sensitive;
1316 if Case_Insensitive then
1317 Case_Sensitive := False;
1321 Compile (Pattern, Glob => True, Case_Sensitive => Case_Sensitive);
1323 when Error_In_Regexp =>
1324 raise Name_Error with "invalid pattern
""" & Pattern & '"';
1329 Dir_Pointer := Dir_Type_Value (opendir (Dir_Name_C));
1331 if Dir_Pointer = No_Dir then
1332 raise Use_Error with
1333 "unreadable directory """ & Simple_Name (Directory) & '"';
1336 -- If needed, finalize Search. Note: we should probably raise an
1337 -- exception here if Search belongs to an existing search rather than
1338 -- quietly end it. However, we first need to check that it won't break
1339 -- existing software.
1343 -- Allocate and initialize the search state
1345 Search.State := new Search_State'
1346 (Ada.Finalization.Controlled with
1347 Dir_Contents => new Vector,
1348 Next_Entry => No_Element);
1350 -- Increase the size of the Dir_Contents vector so it does not need to
1351 -- grow for most reasonable directory searches.
1353 Search.State.Dir_Contents.Reserve_Capacity (Dir_Vector_Initial_Size);
1355 -- Read the contents of Directory into Search.State
1358 -- Get next item in the directory
1362 (Address (Dir_Pointer),
1363 Dir_Entry_Buffer'Address,
1364 File_Name_Len'Access);
1366 exit when File_Name_Addr = Null_Address;
1368 -- If the file name matches the Pattern and the file type matches
1369 -- the Filter add it to our search vector.
1372 subtype File_Name_String is String (1 .. File_Name_Len);
1374 File_Name : constant File_Name_String
1375 with Import, Address => File_Name_Addr;
1378 if Match (File_Name, Pattern_Regex) then
1380 Path_C : constant String :=
1381 Compose (Directory, File_Name) & ASCII.NUL;
1382 Path : String renames
1383 Path_C (Path_C'First .. Path_C'Last - 1);
1384 Found : Boolean := False;
1385 Attr : aliased File_Attributes;
1392 -- Get the file attributes for the directory item
1394 Reset_Attributes (Attr'Access);
1395 Exists := File_Exists_Attr (Path_C'Address, Attr'Access);
1396 Error := Error_Attributes (Attr'Access);
1398 -- If there was an error when trying to read the attributes
1399 -- of a Directory entry, record the error so it can be
1400 -- propagated to the user when they interate through the
1401 -- directory results.
1404 Search.State.Dir_Contents.Append
1405 (Directory_Entry_Type'
1407 Name => To_Unbounded_String (File_Name),
1408 Full_Name => To_Unbounded_String (Path),
1409 Attr_Error_Code => Error,
1412 -- Otherwise, if the file exists and matches the file kind
1413 -- Filter, add the file to the search results. We capture
1414 -- the size and modification time here as we have already
1415 -- the entry's attributes above.
1417 elsif Exists = 1 then
1418 if Is_Regular_File_Attr (Path_C'Address, Attr'Access) = 1
1419 and then Filter (Ordinary_File)
1422 Kind := Ordinary_File;
1426 (-1, Path_C'Address, Attr'Access));
1428 elsif Is_Directory_Attr (Path_C'Address, Attr'Access) = 1
1429 and then Filter (File_Kind'First)
1432 Kind := File_Kind'First;
1433 -- File_Kind'First is used instead of Directory due
1434 -- to a name overload issue with the procedure
1435 -- parameter Directory.
1438 elsif Filter (Special_File) then
1440 Kind := Special_File;
1445 Search.State.Dir_Contents.Append
1446 (Directory_Entry_Type'
1449 To_Unbounded_String (File_Name),
1450 Full_Name => To_Unbounded_String (Path),
1451 Attr_Error_Code => 0,
1453 Modification_Time => Modification_Time (Path),
1462 -- Set the first entry to be returned to the user to be the first
1463 -- element of the Dir_Contents vector. If no items were found, First
1464 -- will return No_Element, which signals
1465 Search.State.Next_Entry := Search.State.Dir_Contents.First;
1467 -- Search is finished, close Directory
1469 Call_Result := closedir (DIRs (Dir_Pointer));
1471 end Start_Search_Internal;
1473 end Ada.Directories;