Rationale for Ada 2005

John Barnes
Table of Contents   Index   References   Search   Previous   Next 

7.4 Operational environment

Two new packages are added to Ada 2005 in order to aid communication with the operational environment. They are Ada.Environment_Variables and Ada.Directories.
The package Ada.Environment_Variables has the following specification
package Ada.Environment_Variables is
   pragma Preelaborate(Environment_Variables);
   function Value(Name: String) return String;
   function Exists(Name: String) return Boolean;
   procedure Set(Name: in String; Value: in String);
   procedure Clear(Name: in String);
   procedure Clear;
   procedure Iterate(Process: not null access procedure (Name, Value: in String));
end Ada.Environment_Variables;
This package provides access to environment variables by name. What this means and whether it is supported depends upon the implementation. But most operating systems have environment variables of some sort. And if not, the implementation is encouraged to simulate them.
The values of the variable are also implementation defined and so simply represented by strings.
The behaviour is straightforward. We might check to see if there is a variable with the name "Ada" and then read and print her value and set it to 2005 if it is not, thus
if not Exists("Ada") then
   raise Horror;    -- quel dommage!
end if;
Put("Current value of Ada is ");  Put_Line(Value("Ada"));
if Value("Ada") /= "2005" then
   Put_Line("Revitalizing Ada now");
   Set("Ada", "2005");
end if;
The procedure Clear with a parameter deletes the variable concerned. Thus Clear("Ada") eliminates her completely so that a subsequent call Exists("Ada") will return False. Note that Set actually clears the variable concerned and then defines a new one with the given name and value. The procedure Clear without a parameter clears all variables.
We can iterate over the variables using the procedure Iterate. For example we can print out the current state by 
procedure Print_One(Name, Value: in String) is
begin
   Put_Line(Name & "=" & Value);
end Print_One;
...
Iterate(Print_One'Access);
The procedure Print_One prints the name and value of the variable passed as parameters. We then pass an access to this procedure as a parameter to the procedure Iterate and Iterate then calls Print_One for each variable in turn.
Note that the slave procedure has both Name and Value as parameters. It might be thought that this was unnecessary since the user can always call the function Value. However, real operating systems can sometimes have several variables with the same name; providing two parameters ensures that the name/value pairs are correctly matched.
Attempting to misuse the environment package such as reading a variable that doesn't exist raises Constraint_Error or Program_Error.
There are big dangers of race conditions because the environment variables are really globally shared. Moreover, they might be shared with the operating system itself as well as programs written in other languages.
A particular point is that we must not call the procedures Set or Clear within a procedure passed as a parameter to Iterate.
The other environment package is Ada.Directories. Its specification is 
with Ada.IO_Exceptions;
with Ada.Calendar;
package Ada.Directories is
   -- Directory and file operations:
   function Current_Directory return String;
   procedure Set_Directory(Directory: in String);
   procedure Create_Directory(New_Directory: in String; Form: in String := "");
   procedure Delete_Directory(Directory: in String);
   procedure Create_Path(New_Directory: in String; Form: in String := "");
   procedure Delete_Tree(Directory: in String);
   procedure Delete_File(Name: in String);
   procedure Rename(Old_Name: in String; New_Name: in String);
   procedure Copy_File(Source_Name: in String; Target_Name: in String; Form: in String := "");
   -- File and directory name operations:
   function Full_Name(Name: String) return String;
   function Simple_Name(Name: String) return String;
   function Containing_Directory(Name: String) return String;
   function Extension(Name: String) return String;
   function Base_Name(Name: String) return String;
   function Compose(Containing_Directory: String := ""; Name: String; Extension: String := "")
      return String;
   -- File and directory queries:
   type File_Kind is (Directory, Ordinary_File, Special_File);
   type File_Size is range 0 .. implementation_defined;
   function Exists(Name: String) return Boolean;
   function Kind(Name: String) return File_Kind;
   function Size(Name: String) return File_Size;
   function Modification_Time(Name: String) return Ada.Calendar.Time;
   -- Directory searching:
   type Directory_Entry_Type is limited private;
   type Filter_Type is array (File_Kind) of Boolean;
   type Search_Type is limited private;
   procedure Start_Search(
      Search: in out Search_Type;
      Directory: in String; Pattern: in String;
      Filter: in Filter_Type := (others => True));
   procedure End_Search(Search: in out Search_Type);
   function More_Entries(Search: Search_Type) return Boolean;
   procedure Get_Next_Entry(
      Search: in out Search_Type;
      Directory_Entry: out Directory_Entry_Type);
   procedure Search(
      Directory: in String;
      Pattern: in String;
      Filter: in Filter_Type := (others => True);
      Process: not null access procedure
                 (Directory_Entry: in Directory_Entry_Type));
   -- Operations on Directory Entries:
   function Simple_Name(Directory_Entry: Directory_Entry_Type) return String;
   function Full_Name(Directory_Entry: Directory_Entry_Type) return String;
   function Kind(Directory_Entry: Directory_Entry_Type) return File_Kind;
   function Size(Directory_Entry: Directory_Entry_Type) return File_Size;
   function Modification_Time(Directory_Entry: Directory_Entry_Type)
      return Ada.Calendar.Time;
   Status_Error: exception renames Ada.IO_Exceptions.Status_Error;
   Name_Error: exception renames Ada.IO_Exceptions.Name_Error;
   Use_Error: exception renames Ada.IO_Exceptions.Use_Error;
   Device_Error: exception renames Ada.IO_Exceptions.Device_Error;
private
   -- Not specified by the language
end Ada.Directories;
Most operating systems have some sort of tree-structured filing system. The general idea of this package is that it allows the manipulation of file and directory names as far as is possible in a unified manner which is not too dependent on the implementation and operating system.
Files are classified as directories, special files and ordinary files. Special files are things like devices on Windows and soft links on Unix; these cannot be created or read by the predefined Ada input–output packages.
Files and directories are identified by strings in the usual way. The interpretation is implementation defined.
The full name of a file is a string such as 
"c:\adastuff\rat\library.doc"
and the simple name is 
"library.doc"
At least that is in good old DOS. In Windows XP it is something like 
"C:\Documents and Settings\john.JBI3\My Documents\adastuff\rat\library.doc"
For the sake of illustration we will continue with the simple DOS example. The current directory is that set by the "cd" command. So assuming we have done 
c:\>cd adastuff
c:\adastuff>
then the function Current_Directory will return the string "c:\adastuff". The procedure Set_Directory sets the current default directory. The procedures Create_Directory and Delete_Directory create and delete a single directory. We can either give the full name or just the part starting from the current default. Thus
Create_Directory("c:\adastuff\history");
Delete_Directory("history");
will cancel out.
The procedure Create_Path creates several nested directories as necessary. Thus starting from the situation above, if we write 
Create_Path("c:\adastuff\books\old");
then it will first create a directory "books" in "c:\adastuff" and then a directory "old" in "books". On the other hand if we wrote Create_Path("c:\adastuff\rat"); then it would do nothing since the path already exists. The procedure Delete_Tree deletes a whole tree including subdirectories and files.
The procedures Delete_File, Rename and Copy_File behave as expected. Note in particular that Copy_File can be used to copy any file that could be copied using a normal input–output package such as Text_IO. For example, it is really tedious to use Text_IO to copy a file intact including all line and page terminators. It is a trivial matter using Copy_File.
Note also that the procedures Create_Directory, Create_Path and Copy_File have an optional Form parameter. Like similar parameters in the predefined input–output packages the meaning is implementation defined.
The next group of six functions, Full_Name, Simple_Name, Containing_Directory, Extension, Base_Name and Compose just manipulate strings representing file names and do not in any way interact with the actual external file system. Moreover, of these, only the behaviour of Full_Name depends upon the current directory.
The function Full_Name returns the full name of a file. Thus assuming the current directory is still "c:\adastuff"
Full_Name("rat\library.doc")
returns "c:\adastuff\rat\library.doc" and 
Full_Name("library.doc")
returns "c:\adastuff\library.doc". The fact that such a file does not exist is irrelevant. We might be making up the name so that we can then create the file. If the string were malformed in some way (such as "66##77") so that the corresponding full name if returned would be nonsense then Name_Error is raised. But Name_Error is never raised just because the file does not exist.
On the other hand 
Simple_Name("c:\adastuff\rat\library.doc")
returns "library.doc" and not "rat\library.doc". We can also apply Simple_Name to a string that does not go back to the root. Thus
Simple_Name("rat\library.doc");
is allowed and also returns "library.doc".
The function Containing_Directory_Name removes the simple name part of the parameter. We can even write 
Containing_Directory_Name("..\rat\library.doc")
and this returns "..\rat"; note that it also removes the separator "\".
The functions Extension and Base_Name return the corresponding parts of a file name thus 
Base_Name("rat\library.doc")    -- "library"
Extension("rat\library.doc")    -- "doc"
Note that they can be applied to a simple name or to a full name or, as here, to something midway between.
The function Compose can be used to put the various bits together, thus 
Compose("rat", "library", "doc")
returns "rat\library.doc". The default parameters enable bits to be omitted. In fact if the third parameter is omitted then the second parameter is treated as a simple name rather than a base name. So we could equally write 
Compose("rat","library.doc")
The next group of functions, Exists, Kind, Size and Modification_Time act on a file name (that is the name of a real external file) and return the obvious result. (The size is measured in stream elements – usually bytes.)
Various types and subprograms are provided to support searching over a directory structure for entities with appropriate properties. This can be done in two ways, either as a loop under the direct control of the programmer (sometimes called an active iterator) or via an access to subprogram parameter (often called a passive iterator). We will look at the active iterator approach first.
The procedures Start_Search, End_Search and Get_Next_Entry and the function More_Entries control the search loop. The general pattern is 
Start_Search( ... );
while More_Entries( ... ) loop
   Get_Next_Entry( ... );
   ...    -- do something with the entry found
end loop;
End_Search( ... );
Three types are involved. The type Directory_Entry_Type is limited private and acts as a sort of handle to the entries found. Valid values of this type can only be created by a call of Get_Next_Entry whose second parameter is an out parameter of the type Directory_Entry_Type. The type Search_Type is also limited private and contains the state of the search. The type Filter_Type provides a simple means of identifying the kinds of file to be found. It has three components corresponding to the three values of the enumeration type File_Kind and is used by the procedure Start_Search.
Suppose we want to look for all ordinary files with extension "doc" in the directory "c:\adastuff\rat". We could write 
Rat_Search: Search_Type;
Item: Directory_Entry_Type;
Filter: Filter_Type := (Ordinary_File => True, others => False);
...
Start_Search(Rat_Search, "c:\adastuff\rat", "*.doc", Filter);
while More_Entries(Rat_Search) loop
   Get_Next_Entry(Rat_Search, Item);
   ...    -- do something with Item
end loop;
End_Search(Rat_Search);
The third parameter of Start_Search (which is "*.doc" in the above example) represents a pattern for matching names and thus provides further filtering of the search. The interpretation is implementation defined except that a null string means match everything. However, we would expect that writing "*.doc" would mean search only for files with the extension "doc".
The alternative mechanism using a passive iterator is as follows. We first declare a subprogram such as 
procedure Do_It(Item: in Directory_Entry_Type) is
begin
   ...    -- do something with item
end Do_It;
and then declare a filter and call the procedure Search thus
Filter: Filter_Type := (Ordinary_File => True, others => False);
...
Search("c:\adastuff\rat", "*.doc", Filter, Do_It'Access);
The parameters of Search are the same as those of Start_Search except that the first parameter of type Search_Type is omitted and a final parameter which identifies the procedure Do_It is added. The variable Item which we declared in the active iterator is now the parameter Item of the procedure Do_It.
Each approach has its advantages. The passive iterator has the merit that we cannot make mistakes such as forget to call End_Search. But some find the active iterator easier to understand and it can be easier to use for parallel searches.
The final group of functions enables us to do useful things with the results of our search. Thus Simple_Name and Full_Name convert a value of Directory_Entry_Type to the corresponding simple or full file name. Having obtained the file name we can do everything we want but for convenience the functions Kind, Size and Modification_Time are provided which also directly take a parameter of Directory_Entry_Type.
So to complete this example we might print out a table of the files found giving their simple name, size and modification time. Using the active approach the loop might then become 
while More_Entries(Rat_Search) loop
   Get_Next_Entry(Rat_Search, Item);
   Put(Simple_Name(Item));  Set_Col(15);
   Put(Size(Item/1000));  Put(" KB");  Set_Col(25);
   Put_Line(Image(Modification_Time(Item)));
end loop;
This might produce a table such as 
access.doc152 KB  2005-04-05  09:03:10
containers.doc372 KB  2005-06-14  21:39:05
general.doc181 KB  2005-03-03  08:43:15
intro.doc173 KB  2004-11-25  15:52:20
library.doc149 KB  2005-04-08  13:50:05
oop.doc179 KB  2005-02-25  18:34:55
structure.doc151 KB  2005-04-05  09:09:25
tasking.doc174 KB  2005-03-31  11:16:40
Note that the function Image is from the package Ada.Calendar.Formatting discussed in the previous section.
Observe that the search is carried out on the directory given and does not look at subdirectories. If we want to do that then we can use the function Kind to identify subdirectories and then search recursively.
It has to be emphasized that the package Ada.Directories is very implementation dependent and indeed might not be supported by some implementations at all. Implementations are advised to provide any additional useful functions concerning retrieving other information about files (such as name of the owner or the original creation date) in a child package Ada.Directories.Information.
Finally, note that misuse of the various operations will raise one of the exceptions Status_Error, Name_Error, Use_Error or Device_Error from the package IO_Exceptions.

Table of Contents   Index   References   Search   Previous   Next 
© 2005, 2006 John Barnes Informatics.
Sponsored in part by:
The Ada Resource Association and its member companies: ARA Members AdaCore Polyspace Technologies Praxis Critical Systems IBM Rational Sofcheck and   Ada-Europe:
Ada-Europe