2005-12-26 Anthony Green <green@redhat.com>
[official-gcc.git] / gcc / ada / a-direct.adb
blob318d10445029c2d26006834d3455e974a5230515
1 ------------------------------------------------------------------------------
2 -- --
3 -- GNAT RUN-TIME COMPONENTS --
4 -- --
5 -- A D A . D I R E C T O R I E S --
6 -- --
7 -- B o d y --
8 -- --
9 -- Copyright (C) 2004-2005, Free Software Foundation, Inc. --
10 -- --
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 2, 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. See the GNU General Public License --
17 -- for more details. You should have received a copy of the GNU General --
18 -- Public License distributed with GNAT; see file COPYING. If not, write --
19 -- to the Free Software Foundation, 51 Franklin Street, Fifth Floor, --
20 -- Boston, MA 02110-1301, USA. --
21 -- --
22 -- As a special exception, if other files instantiate generics from this --
23 -- unit, or you link this unit with other files to produce an executable, --
24 -- this unit does not by itself cause the resulting executable to be --
25 -- covered by the GNU General Public License. This exception does not --
26 -- however invalidate any other reasons why the executable file might be --
27 -- covered by the GNU Public License. --
28 -- --
29 -- GNAT was originally developed by the GNAT team at New York University. --
30 -- Extensive contributions were provided by Ada Core Technologies Inc. --
31 -- --
32 ------------------------------------------------------------------------------
34 with Ada.Directories.Validity; use Ada.Directories.Validity;
35 with Ada.Strings.Unbounded; use Ada.Strings.Unbounded;
36 with Ada.Unchecked_Deallocation;
37 with Ada.Characters.Handling; use Ada.Characters.Handling;
39 with GNAT.Directory_Operations; use GNAT.Directory_Operations;
40 with GNAT.OS_Lib; use GNAT.OS_Lib;
41 with GNAT.Regexp; use GNAT.Regexp;
42 -- ??? Ada units should not depend on GNAT units
44 with System;
46 package body Ada.Directories is
48 type Search_Data is record
49 Is_Valid : Boolean := False;
50 Name : Ada.Strings.Unbounded.Unbounded_String;
51 Pattern : Regexp;
52 Filter : Filter_Type;
53 Dir : Dir_Type;
54 Entry_Fetched : Boolean := False;
55 Dir_Entry : Directory_Entry_Type;
56 end record;
57 -- Comment required ???
59 Empty_String : constant String := (1 .. 0 => ASCII.NUL);
60 -- Comment required ???
62 procedure Free is new Ada.Unchecked_Deallocation (Search_Data, Search_Ptr);
64 function File_Exists (Name : String) return Boolean;
65 -- Returns True if the named file exists
67 procedure Fetch_Next_Entry (Search : Search_Type);
68 -- Get the next entry in a directory, setting Entry_Fetched if successful
69 -- or resetting Is_Valid if not.
71 procedure To_Lower_If_Case_Insensitive (S : in out String);
72 -- Put S in lower case if file and path names are case-insensitive
74 ---------------
75 -- Base_Name --
76 ---------------
78 function Base_Name (Name : String) return String is
79 Simple : String := Simple_Name (Name);
80 -- Simple'First is guaranteed to be 1
82 begin
83 To_Lower_If_Case_Insensitive (Simple);
85 -- Look for the last dot in the file name and return the part of the
86 -- file name preceding this last dot. If the first dot is the first
87 -- character of the file name, the base name is the empty string.
89 for Pos in reverse Simple'Range loop
90 if Simple (Pos) = '.' then
91 return Simple (1 .. Pos - 1);
92 end if;
93 end loop;
95 -- If there is no dot, return the complete file name
97 return Simple;
98 end Base_Name;
100 -------------
101 -- Compose --
102 -------------
104 function Compose
105 (Containing_Directory : String := "";
106 Name : String;
107 Extension : String := "") return String
109 Result : String (1 .. Containing_Directory'Length +
110 Name'Length + Extension'Length + 2);
111 Last : Natural;
113 begin
114 -- First, deal with the invalid cases
116 if not Is_Valid_Path_Name (Containing_Directory) then
117 raise Name_Error;
119 elsif
120 Extension'Length = 0 and then (not Is_Valid_Simple_Name (Name))
121 then
122 raise Name_Error;
124 elsif Extension'Length /= 0 and then
125 (not Is_Valid_Simple_Name (Name & '.' & Extension))
126 then
127 raise Name_Error;
129 -- This is not an invalid case so build the path name
131 else
132 Last := Containing_Directory'Length;
133 Result (1 .. Last) := Containing_Directory;
135 -- Add a directory separator if needed
137 if Result (Last) /= Dir_Separator then
138 Last := Last + 1;
139 Result (Last) := Dir_Separator;
140 end if;
142 -- Add the file name
144 Result (Last + 1 .. Last + Name'Length) := Name;
145 Last := Last + Name'Length;
147 -- If extension was specified, add dot followed by this extension
149 if Extension'Length /= 0 then
150 Last := Last + 1;
151 Result (Last) := '.';
152 Result (Last + 1 .. Last + Extension'Length) := Extension;
153 Last := Last + Extension'Length;
154 end if;
156 To_Lower_If_Case_Insensitive (Result (1 .. Last));
157 return Result (1 .. Last);
158 end if;
159 end Compose;
161 --------------------------
162 -- Containing_Directory --
163 --------------------------
165 function Containing_Directory (Name : String) return String is
166 begin
167 -- First, the invalid case
169 if not Is_Valid_Path_Name (Name) then
170 raise Name_Error;
172 else
173 -- Get the directory name using GNAT.Directory_Operations.Dir_Name
175 declare
176 Value : constant String := Dir_Name (Path => Name);
177 Result : String (1 .. Value'Length);
178 Last : Natural := Result'Last;
180 begin
181 Result := Value;
183 -- Remove any trailing directory separator, except as the first
184 -- character.
186 while Last > 1 and then Result (Last) = Dir_Separator loop
187 Last := Last - 1;
188 end loop;
190 -- Special case of current directory, identified by "."
192 if Last = 1 and then Result (1) = '.' then
193 return Get_Current_Dir;
195 else
196 To_Lower_If_Case_Insensitive (Result (1 .. Last));
197 return Result (1 .. Last);
198 end if;
199 end;
200 end if;
201 end Containing_Directory;
203 ---------------
204 -- Copy_File --
205 ---------------
207 procedure Copy_File
208 (Source_Name : String;
209 Target_Name : String;
210 Form : String := "")
212 pragma Unreferenced (Form);
213 Success : Boolean;
215 begin
216 -- First, the invalid cases
218 if not Is_Valid_Path_Name (Source_Name)
219 or else not Is_Valid_Path_Name (Target_Name)
220 or else not Is_Regular_File (Source_Name)
221 then
222 raise Name_Error;
224 elsif Is_Directory (Target_Name) then
225 raise Use_Error;
227 else
228 -- The implementation uses GNAT.OS_Lib.Copy_File, with parameters
229 -- suitable for all platforms.
231 Copy_File
232 (Source_Name, Target_Name, Success, Overwrite, None);
234 if not Success then
235 raise Use_Error;
236 end if;
237 end if;
238 end Copy_File;
240 ----------------------
241 -- Create_Directory --
242 ----------------------
244 procedure Create_Directory
245 (New_Directory : String;
246 Form : String := "")
248 pragma Unreferenced (Form);
250 begin
251 -- First, the invalid case
253 if not Is_Valid_Path_Name (New_Directory) then
254 raise Name_Error;
256 else
257 -- The implementation uses GNAT.Directory_Operations.Make_Dir
259 begin
260 Make_Dir (Dir_Name => New_Directory);
262 exception
263 when Directory_Error =>
264 raise Use_Error;
265 end;
266 end if;
267 end Create_Directory;
269 -----------------
270 -- Create_Path --
271 -----------------
273 procedure Create_Path
274 (New_Directory : String;
275 Form : String := "")
277 pragma Unreferenced (Form);
279 New_Dir : String (1 .. New_Directory'Length + 1);
280 Last : Positive := 1;
282 begin
283 -- First, the invalid case
285 if not Is_Valid_Path_Name (New_Directory) then
286 raise Name_Error;
288 else
289 -- Build New_Dir with a directory separator at the end, so that the
290 -- complete path will be found in the loop below.
292 New_Dir (1 .. New_Directory'Length) := New_Directory;
293 New_Dir (New_Dir'Last) := Directory_Separator;
295 -- Create, if necessary, each directory in the path
297 for J in 2 .. New_Dir'Last loop
299 -- Look for the end of an intermediate directory
301 if New_Dir (J) /= Dir_Separator then
302 Last := J;
304 -- We have found a new intermediate directory each time we find
305 -- a first directory separator.
307 elsif New_Dir (J - 1) /= Dir_Separator then
309 -- No need to create the directory if it already exists
311 if Is_Directory (New_Dir (1 .. Last)) then
312 null;
314 -- It is an error if a file with such a name already exists
316 elsif Is_Regular_File (New_Dir (1 .. Last)) then
317 raise Use_Error;
319 else
320 -- The implementation uses
321 -- GNAT.Directory_Operations.Make_Dir.
323 begin
324 Make_Dir (Dir_Name => New_Dir (1 .. Last));
326 exception
327 when Directory_Error =>
328 raise Use_Error;
329 end;
330 end if;
331 end if;
332 end loop;
333 end if;
334 end Create_Path;
336 -----------------------
337 -- Current_Directory --
338 -----------------------
340 function Current_Directory return String is
342 -- The implementation uses GNAT.Directory_Operations.Get_Current_Dir
344 Cur : String := Normalize_Pathname (Get_Current_Dir);
346 begin
347 To_Lower_If_Case_Insensitive (Cur);
349 if Cur'Length > 1 and then Cur (Cur'Last) = Dir_Separator then
350 return Cur (1 .. Cur'Last - 1);
351 else
352 return Cur;
353 end if;
354 end Current_Directory;
356 ----------------------
357 -- Delete_Directory --
358 ----------------------
360 procedure Delete_Directory (Directory : String) is
361 begin
362 -- First, the invalid cases
364 if not Is_Valid_Path_Name (Directory) then
365 raise Name_Error;
367 elsif not Is_Directory (Directory) then
368 raise Name_Error;
370 else
371 -- The implementation uses GNAT.Directory_Operations.Remove_Dir
373 begin
374 Remove_Dir (Dir_Name => Directory, Recursive => False);
376 exception
377 when Directory_Error =>
378 raise Use_Error;
379 end;
380 end if;
381 end Delete_Directory;
383 -----------------
384 -- Delete_File --
385 -----------------
387 procedure Delete_File (Name : String) is
388 Success : Boolean;
390 begin
391 -- First, the invalid cases
393 if not Is_Valid_Path_Name (Name) then
394 raise Name_Error;
396 elsif not Is_Regular_File (Name) then
397 raise Name_Error;
399 else
400 -- The implementation uses GNAT.OS_Lib.Delete_File
402 Delete_File (Name, Success);
404 if not Success then
405 raise Use_Error;
406 end if;
407 end if;
408 end Delete_File;
410 -----------------
411 -- Delete_Tree --
412 -----------------
414 procedure Delete_Tree (Directory : String) is
415 begin
416 -- First, the invalid cases
418 if not Is_Valid_Path_Name (Directory) then
419 raise Name_Error;
421 elsif not Is_Directory (Directory) then
422 raise Name_Error;
424 else
425 -- The implementation uses GNAT.Directory_Operations.Remove_Dir
427 begin
428 Remove_Dir (Directory, Recursive => True);
430 exception
431 when Directory_Error =>
432 raise Use_Error;
433 end;
434 end if;
435 end Delete_Tree;
437 ------------
438 -- Exists --
439 ------------
441 function Exists (Name : String) return Boolean is
442 begin
443 -- First, the invalid case
445 if not Is_Valid_Path_Name (Name) then
446 raise Name_Error;
448 else
449 -- The implementation is in File_Exists
451 return File_Exists (Name);
452 end if;
453 end Exists;
455 ---------------
456 -- Extension --
457 ---------------
459 function Extension (Name : String) return String is
460 begin
461 -- First, the invalid case
463 if not Is_Valid_Path_Name (Name) then
464 raise Name_Error;
466 else
467 -- Look for first dot that is not followed by a directory separator
469 for Pos in reverse Name'Range loop
471 -- If a directory separator is found before a dot, there
472 -- is no extension.
474 if Name (Pos) = Dir_Separator then
475 return Empty_String;
477 elsif Name (Pos) = '.' then
479 -- We found a dot, build the return value with lower bound 1
481 declare
482 Result : String (1 .. Name'Last - Pos);
483 begin
484 Result := Name (Pos + 1 .. Name'Last);
485 return Result;
486 -- This should be done with a subtype conversion, avoiding
487 -- the unnecessary junk copy ???
488 end;
489 end if;
490 end loop;
492 -- No dot were found, there is no extension
494 return Empty_String;
495 end if;
496 end Extension;
498 ----------------------
499 -- Fetch_Next_Entry --
500 ----------------------
502 procedure Fetch_Next_Entry (Search : Search_Type) is
503 Name : String (1 .. 255);
504 Last : Natural;
506 Kind : File_Kind := Ordinary_File;
507 -- Initialized to avoid a compilation warning
509 begin
510 -- Search.Value.Is_Valid is always True when Fetch_Next_Entry is called
512 loop
513 Read (Search.Value.Dir, Name, Last);
515 -- If no matching entry is found, set Is_Valid to False
517 if Last = 0 then
518 Search.Value.Is_Valid := False;
519 exit;
520 end if;
522 -- Check if the entry matches the pattern
524 if Match (Name (1 .. Last), Search.Value.Pattern) then
525 declare
526 Full_Name : constant String :=
527 Compose
528 (To_String
529 (Search.Value.Name), Name (1 .. Last));
530 Found : Boolean := False;
532 begin
533 if File_Exists (Full_Name) then
535 -- Now check if the file kind matches the filter
537 if Is_Regular_File (Full_Name) then
538 if Search.Value.Filter (Ordinary_File) then
539 Kind := Ordinary_File;
540 Found := True;
541 end if;
543 elsif Is_Directory (Full_Name) then
544 if Search.Value.Filter (Directory) then
545 Kind := Directory;
546 Found := True;
547 end if;
549 elsif Search.Value.Filter (Special_File) then
550 Kind := Special_File;
551 Found := True;
552 end if;
554 -- If it does, update Search and return
556 if Found then
557 Search.Value.Entry_Fetched := True;
558 Search.Value.Dir_Entry :=
559 (Is_Valid => True,
560 Simple => To_Unbounded_String (Name (1 .. Last)),
561 Full => To_Unbounded_String (Full_Name),
562 Kind => Kind);
563 exit;
564 end if;
565 end if;
566 end;
567 end if;
568 end loop;
569 end Fetch_Next_Entry;
571 -----------------
572 -- File_Exists --
573 -----------------
575 function File_Exists (Name : String) return Boolean is
576 function C_File_Exists (A : System.Address) return Integer;
577 pragma Import (C, C_File_Exists, "__gnat_file_exists");
579 C_Name : String (1 .. Name'Length + 1);
581 begin
582 C_Name (1 .. Name'Length) := Name;
583 C_Name (C_Name'Last) := ASCII.NUL;
584 return C_File_Exists (C_Name (1)'Address) = 1;
585 end File_Exists;
587 --------------
588 -- Finalize --
589 --------------
591 procedure Finalize (Search : in out Search_Type) is
592 begin
593 if Search.Value /= null then
595 -- Close the directory, if one is open
597 if Is_Open (Search.Value.Dir) then
598 Close (Search.Value.Dir);
599 end if;
601 Free (Search.Value);
602 end if;
603 end Finalize;
605 ---------------
606 -- Full_Name --
607 ---------------
609 function Full_Name (Name : String) return String is
610 begin
611 -- First, the invalid case
613 if not Is_Valid_Path_Name (Name) then
614 raise Name_Error;
616 else
617 -- Build the return value with lower bound 1
619 -- Use GNAT.OS_Lib.Normalize_Pathname
621 declare
622 Value : String := Normalize_Pathname (Name);
623 subtype Result is String (1 .. Value'Length);
624 begin
625 To_Lower_If_Case_Insensitive (Value);
626 return Result (Value);
627 end;
628 end if;
629 end Full_Name;
631 function Full_Name (Directory_Entry : Directory_Entry_Type) return String is
632 begin
633 -- First, the invalid case
635 if not Directory_Entry.Is_Valid then
636 raise Status_Error;
638 else
639 -- The value to return has already been computed
641 return To_String (Directory_Entry.Full);
642 end if;
643 end Full_Name;
645 --------------------
646 -- Get_Next_Entry --
647 --------------------
649 procedure Get_Next_Entry
650 (Search : in out Search_Type;
651 Directory_Entry : out Directory_Entry_Type)
653 begin
654 -- First, the invalid case
656 if Search.Value = null or else not Search.Value.Is_Valid then
657 raise Status_Error;
658 end if;
660 -- Fetch the next entry, if needed
662 if not Search.Value.Entry_Fetched then
663 Fetch_Next_Entry (Search);
664 end if;
666 -- It is an error if no valid entry is found
668 if not Search.Value.Is_Valid then
669 raise Status_Error;
671 else
672 -- Reset Entry_Fatched and return the entry
674 Search.Value.Entry_Fetched := False;
675 Directory_Entry := Search.Value.Dir_Entry;
676 end if;
677 end Get_Next_Entry;
679 ----------
680 -- Kind --
681 ----------
683 function Kind (Name : String) return File_Kind is
684 begin
685 -- First, the invalid case
687 if not File_Exists (Name) then
688 raise Name_Error;
690 elsif Is_Regular_File (Name) then
691 return Ordinary_File;
693 elsif Is_Directory (Name) then
694 return Directory;
696 else
697 return Special_File;
698 end if;
699 end Kind;
701 function Kind (Directory_Entry : Directory_Entry_Type) return File_Kind is
702 begin
703 -- First, the invalid case
705 if not Directory_Entry.Is_Valid then
706 raise Status_Error;
708 else
709 -- The value to return has already be computed
711 return Directory_Entry.Kind;
712 end if;
713 end Kind;
715 -----------------------
716 -- Modification_Time --
717 -----------------------
719 function Modification_Time (Name : String) return Ada.Calendar.Time is
720 Date : OS_Time;
721 Year : Year_Type;
722 Month : Month_Type;
723 Day : Day_Type;
724 Hour : Hour_Type;
725 Minute : Minute_Type;
726 Second : Second_Type;
728 begin
729 -- First, the invalid cases
731 if not (Is_Regular_File (Name) or else Is_Directory (Name)) then
732 raise Name_Error;
734 else
735 Date := File_Time_Stamp (Name);
736 -- ???? We need to be able to convert OS_Time to Ada.Calendar.Time
737 -- For now, use the component of the OS_Time to create the
738 -- Calendar.Time value.
740 GM_Split (Date, Year, Month, Day, Hour, Minute, Second);
742 return Ada.Calendar.Time_Of
743 (Year, Month, Day, Duration (Second + 60 * (Minute + 60 * Hour)));
744 end if;
745 end Modification_Time;
747 function Modification_Time
748 (Directory_Entry : Directory_Entry_Type) return Ada.Calendar.Time
750 begin
751 -- First, the invalid case
753 if not Directory_Entry.Is_Valid then
754 raise Status_Error;
756 else
757 -- The value to return has already be computed
759 return Modification_Time (To_String (Directory_Entry.Full));
760 end if;
761 end Modification_Time;
763 ------------------
764 -- More_Entries --
765 ------------------
767 function More_Entries (Search : Search_Type) return Boolean is
768 begin
769 if Search.Value = null then
770 return False;
772 elsif Search.Value.Is_Valid then
774 -- Fetch the next entry, if needed
776 if not Search.Value.Entry_Fetched then
777 Fetch_Next_Entry (Search);
778 end if;
779 end if;
781 return Search.Value.Is_Valid;
782 end More_Entries;
784 ------------
785 -- Rename --
786 ------------
788 procedure Rename (Old_Name, New_Name : String) is
789 Success : Boolean;
791 begin
792 -- First, the invalid cases
794 if not Is_Valid_Path_Name (Old_Name)
795 or else not Is_Valid_Path_Name (New_Name)
796 or else (not Is_Regular_File (Old_Name)
797 and then not Is_Directory (Old_Name))
798 then
799 raise Name_Error;
801 elsif Is_Regular_File (New_Name) or Is_Directory (New_Name) then
802 raise Use_Error;
804 else
805 -- The implementation uses GNAT.OS_Lib.Rename_File
807 Rename_File (Old_Name, New_Name, Success);
809 if not Success then
810 raise Use_Error;
811 end if;
812 end if;
813 end Rename;
815 -------------------
816 -- Set_Directory --
817 -------------------
819 procedure Set_Directory (Directory : String) is
820 begin
821 -- The implementation uses GNAT.Directory_Operations.Change_Dir
823 Change_Dir (Dir_Name => Directory);
825 exception
826 when Directory_Error =>
827 raise Name_Error;
828 end Set_Directory;
830 -----------------
831 -- Simple_Name --
832 -----------------
834 function Simple_Name (Name : String) return String is
835 begin
836 -- First, the invalid case
838 if not Is_Valid_Path_Name (Name) then
839 raise Name_Error;
841 else
842 -- Build the value to return with lower bound 1
844 -- The implementation uses GNAT.Directory_Operations.Base_Name
846 declare
847 Value : String := GNAT.Directory_Operations.Base_Name (Name);
848 subtype Result is String (1 .. Value'Length);
849 begin
850 To_Lower_If_Case_Insensitive (Value);
851 return Result (Value);
852 end;
853 end if;
854 end Simple_Name;
856 function Simple_Name
857 (Directory_Entry : Directory_Entry_Type) return String
859 begin
860 -- First, the invalid case
862 if not Directory_Entry.Is_Valid then
863 raise Status_Error;
865 else
866 -- The value to return has already be computed
868 return To_String (Directory_Entry.Simple);
869 end if;
870 end Simple_Name;
872 ----------
873 -- Size --
874 ----------
876 function Size (Name : String) return File_Size is
877 C_Name : String (1 .. Name'Length + 1);
879 function C_Size (Name : System.Address) return Long_Integer;
880 pragma Import (C, C_Size, "__gnat_named_file_length");
882 begin
883 -- First, the invalid case
885 if not Is_Regular_File (Name) then
886 raise Name_Error;
888 else
889 C_Name (1 .. Name'Length) := Name;
890 C_Name (C_Name'Last) := ASCII.NUL;
891 return File_Size (C_Size (C_Name'Address));
892 end if;
893 end Size;
895 function Size (Directory_Entry : Directory_Entry_Type) return File_Size is
896 begin
897 -- First, the invalid case
899 if not Directory_Entry.Is_Valid then
900 raise Status_Error;
902 else
903 -- The value to return has already be computed
905 return Size (To_String (Directory_Entry.Full));
906 end if;
907 end Size;
909 ------------------
910 -- Start_Search --
911 ------------------
913 procedure Start_Search
914 (Search : in out Search_Type;
915 Directory : String;
916 Pattern : String;
917 Filter : Filter_Type := (others => True))
919 begin
920 -- First, the invalid case
922 if not Is_Directory (Directory) then
923 raise Name_Error;
924 end if;
926 -- If needed, finalize Search
928 Finalize (Search);
930 -- Allocate the default data
932 Search.Value := new Search_Data;
934 begin
935 -- Check the pattern
937 Search.Value.Pattern := Compile (Pattern, Glob => True);
939 exception
940 when Error_In_Regexp =>
941 Free (Search.Value);
942 raise Name_Error;
943 end;
945 -- Initialize some Search components
947 Search.Value.Filter := Filter;
948 Search.Value.Name := To_Unbounded_String (Full_Name (Directory));
949 Open (Search.Value.Dir, Directory);
950 Search.Value.Is_Valid := True;
951 end Start_Search;
953 ----------------------------------
954 -- To_Lower_If_Case_Insensitive --
955 ----------------------------------
957 procedure To_Lower_If_Case_Insensitive (S : in out String) is
958 begin
959 if not Is_Path_Name_Case_Sensitive then
960 for J in S'Range loop
961 S (J) := To_Lower (S (J));
962 end loop;
963 end if;
964 end To_Lower_If_Case_Insensitive;
966 end Ada.Directories;