1 ------------------------------------------------------------------------------
3 -- GNAT COMPILER COMPONENTS --
5 -- G N A T . D I R E C T O R Y _ O P E R A T I O N S --
11 -- Copyright (C) 1998-2001 Ada Core Technologies, Inc. --
13 -- GNAT is free software; you can redistribute it and/or modify it under --
14 -- terms of the GNU General Public License as published by the Free Soft- --
15 -- ware Foundation; either version 2, or (at your option) any later ver- --
16 -- sion. GNAT is distributed in the hope that it will be useful, but WITH- --
17 -- OUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY --
18 -- or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License --
19 -- for more details. You should have received a copy of the GNU General --
20 -- Public License distributed with GNAT; see file COPYING. If not, write --
21 -- to the Free Software Foundation, 59 Temple Place - Suite 330, Boston, --
22 -- MA 02111-1307, USA. --
24 -- As a special exception, if other files instantiate generics from this --
25 -- unit, or you link this unit with other files to produce an executable, --
26 -- this unit does not by itself cause the resulting executable to be --
27 -- covered by the GNU General Public License. This exception does not --
28 -- however invalidate any other reasons why the executable file might be --
29 -- covered by the GNU Public License. --
31 -- GNAT is maintained by Ada Core Technologies Inc (http://www.gnat.com). --
33 ------------------------------------------------------------------------------
35 with Ada.Characters.Handling;
36 with Ada.Strings.Fixed;
37 with Ada.Strings.Unbounded;
38 with Ada.Strings.Maps;
39 with Unchecked_Deallocation;
40 with Unchecked_Conversion;
41 with System; use System;
46 package body GNAT.Directory_Operations is
50 type Dir_Type_Value is new System.Address;
51 -- This is the low-level address directory structure as returned by the C
54 Dir_Seps : constant Strings.Maps.Character_Set :=
55 Strings.Maps.To_Set ("/\");
56 -- UNIX and DOS style directory separators.
59 Unchecked_Deallocation (Dir_Type_Value, Dir_Type);
67 Suffix : String := "")
70 function Get_File_Names_Case_Sensitive return Integer;
72 (C, Get_File_Names_Case_Sensitive,
73 "__gnat_get_file_names_case_sensitive");
75 Case_Sensitive_File_Name : constant Boolean :=
76 Get_File_Names_Case_Sensitive = 1;
80 Suffix : String := "")
82 -- This function does the job. The only difference between Basename
83 -- and Base_Name (the parent function) is that the former is case
84 -- sensitive, while the latter is not. Path and Suffix are adjusted
85 -- appropriately before calling Basename under platforms where the
86 -- file system is not case sensitive.
94 Suffix : String := "")
97 Cut_Start : Natural :=
99 (Path, Dir_Seps, Going => Strings.Backward);
103 -- Cut_Start point to the first basename character
105 if Cut_Start = 0 then
106 Cut_Start := Path'First;
109 Cut_Start := Cut_Start + 1;
112 -- Cut_End point to the last basename character.
114 Cut_End := Path'Last;
116 -- If basename ends with Suffix, adjust Cut_End.
119 and then Path (Path'Last - Suffix'Length + 1 .. Cut_End) = Suffix
121 Cut_End := Path'Last - Suffix'Length;
124 Check_For_Standard_Dirs : declare
125 BN : constant String := Base_Name.Path (Cut_Start .. Cut_End);
128 if BN = "." or else BN = ".." then
132 and then Characters.Handling.Is_Letter (BN (BN'First))
133 and then BN (BN'First + 1) = ':'
135 -- We have a DOS drive letter prefix, remove it
137 return BN (BN'First + 2 .. BN'Last);
142 end Check_For_Standard_Dirs;
145 -- Start processing for Base_Name
148 if Case_Sensitive_File_Name then
149 return Basename (Path, Suffix);
153 (Characters.Handling.To_Lower (Path),
154 Characters.Handling.To_Lower (Suffix));
162 procedure Change_Dir (Dir_Name : Dir_Name_Str) is
163 C_Dir_Name : String := Dir_Name & ASCII.NUL;
165 function chdir (Dir_Name : String) return Integer;
166 pragma Import (C, chdir, "chdir");
169 if chdir (C_Dir_Name) /= 0 then
170 raise Directory_Error;
178 procedure Close (Dir : in out Dir_Type) is
180 function closedir (Directory : System.Address) return Integer;
181 pragma Import (C, closedir, "closedir");
186 if not Is_Open (Dir) then
187 raise Directory_Error;
190 Discard := closedir (System.Address (Dir.all));
198 function Dir_Name (Path : Path_Name) return Dir_Name_Str is
199 Last_DS : constant Natural :=
201 (Path, Dir_Seps, Going => Strings.Backward);
206 -- There is no directory separator, returns current working directory
208 return "." & Dir_Separator;
211 return Path (Path'First .. Last_DS);
219 function Expand_Path (Path : Path_Name) return String is
220 use Ada.Strings.Unbounded;
222 procedure Read (K : in out Positive);
223 -- Update Result while reading current Path starting at position K. If
224 -- a variable is found, call Var below.
226 procedure Var (K : in out Positive);
227 -- Translate variable name starting at position K with the associated
228 -- environement value.
231 new Unchecked_Deallocation (String, OS_Lib.String_Access);
233 Result : Unbounded_String;
239 procedure Read (K : in out Positive) is
241 For_All_Characters : loop
242 if Path (K) = '$' then
244 -- Could be a variable
246 if K < Path'Last then
248 if Path (K + 1) = '$' then
250 -- Not a variable after all, this is a double $, just
251 -- insert one in the result string.
253 Append (Result, '$');
257 -- Let's parse the variable
264 -- We have an ending $ sign
266 Append (Result, '$');
270 -- This is a standard character, just add it to the result
272 Append (Result, Path (K));
275 -- Skip to next character
279 exit For_All_Characters when K > Path'Last;
280 end loop For_All_Characters;
287 procedure Var (K : in out Positive) is
291 if Path (K) = '{' then
293 -- Look for closing } (curly bracket).
299 exit when Path (E) = '}' or else E = Path'Last;
302 if Path (E) = '}' then
304 -- OK found, translate with environement value
307 Env : OS_Lib.String_Access :=
308 OS_Lib.Getenv (Path (K + 1 .. E - 1));
311 Append (Result, Env.all);
316 -- No closing curly bracket, not a variable after all or a
317 -- syntax error, ignore it, insert string as-is.
319 Append (Result, '$' & Path (K .. E));
323 -- The variable name is everything from current position to first
324 -- non letter/digit character.
328 -- Check that first chartacter is a letter
330 if Characters.Handling.Is_Letter (Path (E)) then
334 exit Var_Name when E = Path'Last;
336 if Characters.Handling.Is_Letter (Path (E))
337 or else Characters.Handling.Is_Digit (Path (E))
347 Env : OS_Lib.String_Access := OS_Lib.Getenv (Path (K .. E));
350 Append (Result, Env.all);
355 -- This is not a variable after all
357 Append (Result, '$' & Path (E));
365 -- Start of processing for Expand_Path
369 K : Positive := Path'First;
373 return To_String (Result);
381 function File_Extension (Path : Path_Name) return String is
384 (Path, Dir_Seps, Going => Strings.Backward);
393 Dot := Strings.Fixed.Index (Path (First .. Path'Last),
395 Going => Strings.Backward);
397 if Dot = 0 or else Dot = Path'Last then
400 return Path (Dot .. Path'Last);
408 function File_Name (Path : Path_Name) return String is
410 return Base_Name (Path);
418 (Root_Directory : Dir_Name_Str;
419 File_Pattern : String)
421 File_Regexp : constant Regexp.Regexp := Regexp.Compile (File_Pattern);
422 Index : Natural := 0;
424 procedure Read_Directory (Directory : Dir_Name_Str);
425 -- Open Directory and read all entries. This routine is called
426 -- recursively for each sub-directories.
428 function Make_Pathname (Dir, File : String) return String;
429 -- Returns the pathname for File by adding Dir as prefix.
435 function Make_Pathname (Dir, File : String) return String is
437 if Dir (Dir'Last) = '/' or else Dir (Dir'Last) = '\' then
440 return Dir & Dir_Separator & File;
448 procedure Read_Directory (Directory : Dir_Name_Str) is
450 Buffer : String (1 .. 2_048);
455 Open (Dir, Directory);
458 Read (Dir, Buffer, Last);
462 Dir_Entry : constant String := Buffer (1 .. Last);
463 Pathname : constant String
464 := Make_Pathname (Directory, Dir_Entry);
466 if Regexp.Match (Dir_Entry, File_Regexp) then
471 Action (Pathname, Index, Quit);
481 -- Recursively call for sub-directories, except for . and ..
483 if not (Dir_Entry = "." or else Dir_Entry = "..")
484 and then OS_Lib.Is_Directory (Pathname)
486 Read_Directory (Pathname);
495 Read_Directory (Root_Directory);
498 ---------------------
499 -- Get_Current_Dir --
500 ---------------------
503 pragma Import (C, Max_Path, "max_path_len");
505 function Get_Current_Dir return Dir_Name_Str is
506 Current_Dir : String (1 .. Max_Path + 1);
510 Get_Current_Dir (Current_Dir, Last);
511 return Current_Dir (1 .. Last);
514 procedure Get_Current_Dir (Dir : out Dir_Name_Str; Last : out Natural) is
515 Path_Len : Natural := Max_Path;
516 Buffer : String (Dir'First .. Dir'First + Max_Path + 1);
518 procedure Local_Get_Current_Dir
519 (Dir : System.Address;
520 Length : System.Address);
521 pragma Import (C, Local_Get_Current_Dir, "__gnat_get_current_dir");
524 Local_Get_Current_Dir (Buffer'Address, Path_Len'Address);
526 if Dir'Length > Path_Len then
527 Last := Dir'First + Path_Len - 1;
532 Dir (Buffer'First .. Last) := Buffer (Buffer'First .. Last);
539 function Is_Open (Dir : Dir_Type) return Boolean is
541 return Dir /= Null_Dir
542 and then System.Address (Dir.all) /= System.Null_Address;
549 procedure Make_Dir (Dir_Name : Dir_Name_Str) is
550 C_Dir_Name : String := Dir_Name & ASCII.NUL;
552 function mkdir (Dir_Name : String) return Integer;
553 pragma Import (C, mkdir, "__gnat_mkdir");
556 if mkdir (C_Dir_Name) /= 0 then
557 raise Directory_Error;
561 ------------------------
562 -- Normalize_Pathname --
563 ------------------------
565 function Normalize_Pathname
567 Style : Path_Style := System_Default)
570 N_Path : String := Path;
571 K : Positive := N_Path'First;
572 Prev_Dirsep : Boolean := False;
575 for J in Path'Range loop
577 if Strings.Maps.Is_In (Path (J), Dir_Seps) then
578 if not Prev_Dirsep then
581 when UNIX => N_Path (K) := '/';
582 when DOS => N_Path (K) := '\';
583 when System_Default => N_Path (K) := Dir_Separator;
592 N_Path (K) := Path (J);
594 Prev_Dirsep := False;
598 return N_Path (N_Path'First .. K - 1);
599 end Normalize_Pathname;
607 Dir_Name : Dir_Name_Str)
609 C_File_Name : String := Dir_Name & ASCII.NUL;
613 return Dir_Type_Value;
614 pragma Import (C, opendir, "opendir");
617 Dir := new Dir_Type_Value'(opendir (C_File_Name));
619 if not Is_Open (Dir) then
622 raise Directory_Error;
631 (Dir : in out Dir_Type;
635 Filename_Addr : Address;
636 Filename_Len : Integer;
638 Buffer : array (0 .. 1024) of Character;
639 -- 1024 is the value of FILENAME_MAX in stdio.h
641 function readdir_gnat
642 (Directory : System.Address;
643 Buffer : System.Address)
644 return System.Address;
645 pragma Import (C, readdir_gnat, "__gnat_readdir");
647 function strlen (S : Address) return Integer;
648 pragma Import (C, strlen, "strlen");
651 if not Is_Open (Dir) then
652 raise Directory_Error;
656 readdir_gnat (System.Address (Dir.all), Buffer'Address);
658 if Filename_Addr = System.Null_Address then
663 Filename_Len := strlen (Filename_Addr);
665 if Str'Length > Filename_Len then
666 Last := Str'First + Filename_Len - 1;
672 subtype Path_String is String (1 .. Filename_Len);
673 type Path_String_Access is access Path_String;
675 function Address_To_Access is new
678 Target => Path_String_Access);
680 Path_Access : Path_String_Access := Address_To_Access (Filename_Addr);
683 for J in Str'First .. Last loop
684 Str (J) := Path_Access (J - Str'First + 1);
689 -------------------------
690 -- Read_Is_Thread_Sage --
691 -------------------------
693 function Read_Is_Thread_Safe return Boolean is
695 function readdir_is_thread_safe return Integer;
697 (C, readdir_is_thread_safe, "__gnat_readdir_is_thread_safe");
700 return (readdir_is_thread_safe /= 0);
701 end Read_Is_Thread_Safe;
707 procedure Remove_Dir (Dir_Name : Dir_Name_Str) is
708 C_Dir_Name : String := Dir_Name & ASCII.NUL;
710 procedure rmdir (Dir_Name : String);
711 pragma Import (C, rmdir, "rmdir");
717 -----------------------
718 -- Wildcard_Iterator --
719 -----------------------
721 procedure Wildcard_Iterator (Path : Path_Name) is
723 Index : Natural := 0;
727 File_Pattern : String;
728 Suffix_Pattern : String);
729 -- Read entries in Directory and call user's callback if the entry
730 -- match File_Pattern and Suffix_Pattern is empty otherwise it will go
731 -- down one more directory level by calling Next_Level routine above.
734 (Current_Path : String;
735 Suffix_Path : String);
736 -- Extract next File_Pattern from Suffix_Path and call Read routine
744 (Current_Path : String;
745 Suffix_Path : String)
748 SP : String renames Suffix_Path;
752 and then SP (SP'First) = '.'
753 and then Strings.Maps.Is_In (SP (SP'First + 1), Dir_Seps)
755 -- Starting with "./"
757 DS := Strings.Fixed.Index
758 (SP (SP'First + 2 .. SP'Last),
765 Read (Current_Path & ".", "*", "");
770 Read (Current_Path & ".",
771 SP (SP'First + 2 .. DS - 1),
776 and then SP (SP'First .. SP'First + 1) = ".."
777 and then Strings.Maps.Is_In (SP (SP'First + 2), Dir_Seps)
779 -- Starting with "../"
781 DS := Strings.Fixed.Index
782 (SP (SP'First + 3 .. SP'Last),
789 Read (Current_Path & "..", "*", "");
794 Read (Current_Path & "..",
795 SP (SP'First + 4 .. DS - 1),
799 elsif Current_Path = ""
800 and then SP'Length > 1
801 and then Characters.Handling.Is_Letter (SP (SP'First))
802 and then SP (SP'First + 1) = ':'
804 -- Starting with "<drive>:"
807 and then Strings.Maps.Is_In (SP (SP'First + 2), Dir_Seps)
809 -- Starting with "<drive>:\"
811 DS := Strings.Fixed.Index
812 (SP (SP'First + 3 .. SP'Last), Dir_Seps);
816 -- Se have "<drive>:\dir"
818 Read (SP (SP'First .. SP'First + 1),
819 SP (SP'First + 3 .. SP'Last),
823 -- We have "<drive>:\dir\kkk"
825 Read (SP (SP'First .. SP'First + 1),
826 SP (SP'First + 3 .. DS - 1),
831 -- Starting with "<drive>:"
833 DS := Strings.Fixed.Index
834 (SP (SP'First + 2 .. SP'Last), Dir_Seps);
838 -- We have "<drive>:dir"
840 Read (SP (SP'First .. SP'First + 1),
841 SP (SP'First + 2 .. SP'Last),
845 -- We have "<drive>:dir/kkk"
847 Read (SP (SP'First .. SP'First + 1),
848 SP (SP'First + 2 .. DS - 1),
854 elsif Strings.Maps.Is_In (SP (SP'First), Dir_Seps) then
858 DS := Strings.Fixed.Index
859 (SP (SP'First + 1 .. SP'Last),
867 SP (SP'First + 1 .. SP'Last),
870 -- We have "/dir/kkk"
873 SP (SP'First + 1 .. DS - 1),
878 -- Starting with a name
880 DS := Strings.Fixed.Index (SP, Dir_Seps);
886 Read (Current_Path & '.',
892 Read (Current_Path & '.',
893 SP (SP'First .. DS - 1),
904 Quit : Boolean := False;
905 -- Global state to be able to exit all recursive calls.
909 File_Pattern : String;
910 Suffix_Pattern : String)
912 File_Regexp : constant Regexp.Regexp :=
913 Regexp.Compile (File_Pattern, Glob => True);
915 Buffer : String (1 .. 2_048);
919 if OS_Lib.Is_Directory (Directory) then
920 Open (Dir, Directory);
923 Read (Dir, Buffer, Last);
924 exit Dir_Iterator when Last = 0;
927 Dir_Entry : constant String := Buffer (1 .. Last);
928 Pathname : constant String :=
929 Directory & Dir_Separator & Dir_Entry;
931 -- Handle "." and ".." only if explicit use in the
935 ((Dir_Entry = "." and then File_Pattern /= ".")
937 (Dir_Entry = ".." and then File_Pattern /= ".."))
939 if Regexp.Match (Dir_Entry, File_Regexp) then
941 if Suffix_Pattern = "" then
943 -- No more matching needed, call user's callback
948 Action (Pathname, Index, Quit);
956 exit Dir_Iterator when Quit;
962 (Directory & Dir_Separator & Dir_Entry,
969 exit Dir_Iterator when Quit;
971 end loop Dir_Iterator;
978 Next_Level ("", Path);
979 end Wildcard_Iterator;
981 end GNAT.Directory_Operations;