os_services.ads |
A library unit package renaming, that allows you to chose the desired implementation |
os_services_gnat.ad[sb] |
specification and body of package OS_Services, Gnat implementation |
os_services_tcl.ad[sb] |
specification and body of package OS_Services, Tcl (Tash) implementation |
calendar_utilities.ad[sb] |
specification and body of package Calendar_Utilities, a reusable component for formatting dates. Used by TCL implementation only. |
os_services.htm |
(this file) package OS_Services documentation |
os_services.tcl |
Supporting Tcl functions for the Tcl implementation. You can ignore this file if you just want to use the package; it is provided as a convenience for those who would like to modify the Tcl implementation. |
purge.adb |
An example program that uses OS_Services |
Portability conditions depend on the selected implementation, and are documented in the body of this documentation.
Package OS_Services, the various implementations provided for it, and the accompanying documentation are Copyright © 2000 ADALOG.
This software is distributed under Adalog's "advertiseware" license. The goal of this license is to make this product widely available as an advertisement for Adalog activities and a demonstration of Adalog's know-how in Ada development, without imposing any constraint on the user, except that code not made by Adalog should not be confused with original code (we cannot accept responsability for your bugs). Therefore:
Rights to use, distribute or modify in any way this software and its documentation are hereby granted, provided:
This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
Although we do not require it, you are very welcome, if you find this software useful, to send us a note at rosen@adalog.fr. We also welcome critics, suggestions for improvement, etc.
ADALOG is providing training, consultancy and expertise in Ada and related software engineering techniques. For more info about our services:
ADALOG
19-21 rue du 8 mai 1945 94110 ARCUEIL FRANCE |
Tel: +33 1 41 24 31 40 Fax: +33 1 41 24 07 36 |
E-m: info@adalog.fr
URL: http://www.adalog.fr/adalog2.htm |
The goal of package OS_Services is to provide access to the commonly used features of operating systems, such as browsing a file list, moving files between directories, accessing environment variables, etc. If you want a short example of what it can do, have a look at the provided example. Since OS vary greatly, it is difficult to provide a general interface that can be fully supported on every OS (except, of course, the empty interface ;-). Similarly, there are several ways to implement such specifications, with varying trade-offs between efficiency, compiler independence, and OS independence.
For these reasons, we provide various packages, that share (more
or less) the same specification, with different implementation
techniques. The user is invited to have a library package
renaming of the appropriate implementation, and then to "with"
that renaming in all his application. This way, it is easy to
move a project from one implementation to another one, by simply
changing the renaming. Such a package renaming is provided in
file os_services.ads
; if you don't understand what
we mean, here is the content of os_services.ads
(here,
we have chosen the Gnat implementation):
-- Choose which implementation of OS_Services you want to use by -- uncommenting the appropriate lines (and commenting out the other ones) -- Gnat implementation: with OS_Services_Gnat; package OS_Services renames OS_Services_Gnat; -- Tcl implementation: --with OS_Services_Tcl; --package OS_Services renames OS_Services_Tcl;
As mentioned in the previous paragraph, all implementations do not have exactly the same specification. However, we were able to design a mechanism such that variations in implementations capabilities simply result in some values being provided, or not, in the definition of an enumeration type. In the following descriptions, we will describe the most general case, and document how much of it is supported by each implementation. If you use only the elements supported by all implementations, you will be able to switch implementations freely; on the other hand, if you switch to an implementation that does not provide some element, the compiler will tell you.
This release includes two implementations of OS_Services
:
one which uses packages provided with the Gnat compiler, and will
therefore not compile on any other compiler, and one which is
based on Tcl (Tash). The following table should help you decide
which implementation better fits your needs:
Gnat implementation |
Tcl implementation |
|
Dependencies | Requires the Gnat compiler | Requires Tcl and Tash (the Ada binding to Tcl) |
Supported OS | DOS, NT, OS_2 (untested), Unix | Every system that supports Tcl and Tash.
This should include (at least): DOS, NT, OS_2 (untested), Unix, MacOS (untested) |
Reentrancy | No known reentrancy problems | Not all functionalities are reentrant; see package specification before using in multi-tasks programs |
Properties | Some properties not supported | All properties supported |
It is expected to provide more implementations in the future; good candidates are implementations based on Posix or Win32. If you are interested in writing an implementation, or if you think that some feature is badly missing from this package, please drop a note to rosen@adalog.fr .
A number of services accept file names as input, or produce file names as output. As a general principle, OS_Services is tolerant on input, and rigorous on output. This means that an input file name can use, for example, either '\' or '/' as the directory separator, but that functions that return file names will always use the separator which is appropriate for the OS.
The most general file name is made up of several parts:
C:
")
/a/b/c/
"), separated by a path
separator. foo
")
.bar
") ;3
"). Not all of these parts exist on every OS; for example, Device exists only on DOS-like systems, and Version exists only on VMS. However, if you ask for a part of a file name that is not meaningful for your OS, the corresponding function will simply return the empty string. This should make the writing of OS independent programs easier.
A Path is considered absolute if it starts from a root directory; otherwise, it is considered relative (its interpretation depends on the current directory).
A number of services feature a String parameter called Pattern
.
A pattern is used to describe a set of files that match the
pattern (wild-cards). The wild-carding used is the so-called
"glob" pattern matching in every case (even on Dos-like
systems), summarized in the following table:
This sequence: | Matches: |
* | any string of 0 or more characters |
? | any single character |
[char char ...] | any character listed |
[char - char] | any character in given range |
{elmt, elmt, ...} | alternation (matches any of elmt, where elmt is any sequence) |
In addition, the pattern may include several sub-patterns, separated by space characters. A file name will match the pattern if it matches any of the sub-patterns inside it. This rule, which is natural in many cases, creates a problem for sub-patterns that include a space character. For that reason, a sub-pattern may be enclosed in double-quotes ("). In short, the following:
File_Copy (Pattern => "*.ads *.adb ""* *""", To => "My_Dir");
will copy all files whose extension is ".ads
",
".adb
", or any file with a space in its
name, to the directory "My_Dir
" (remember
that quotes are doubled in an Ada string).
The package defines only one exception, Service_Error
,
which is raised by any operation that cannot be correctly
completed. Whenever this exception is raised, the package
provides an explanatory message that can be retrieved with the Ada.Exception.Exception_Message
subprogram.
There is no special installation requirement; unzip and compile the packages as you see fit. You don't need to compile the packages corresponding to implementations that you don't want, but obviously, you'll need to compile at least one implementation!
Both currently provided implementations do their best to automatically recognize the operating system. Should this fail, you can force the package to a given operating system; see the comment in the first lines of the package body to learn how to do that.
Note that if you have -say- a directory for software components, and separate directories for each of your projects, you should compile the implementation packages in your software components directory, and put (a copy of) os_services.ads in each of your project files: this would allow you to use different implementations for different projects.
Don't forget in any case to comment/uncomment the lines in os_services.ads
that allow you to select the desired implementation.
The package is set for GNAT 3.13 and above. Due to some (now corrected) bugs, it will not work correctly with older versions of GNAT.
The packages compiles just fine as provided. However, if you
want to make some changes, you may want to alter the file os_services.tcl
,
which contains the source of some useful TCL functions. Merging
this file into the Ada code can be done automatically with the
utility tcl2ada
, which is available from Adalog's components page.
Default_Ignore_Case : Boolean;
This variable defines the default value for the Ignore_Case
parameter used in some subprograms that do file name
comparisons. If it is set to True
, case is ignored (by
default) when comparing file names. The variable is initialized
to the normal behavior of the OS (i.e. True
for DOS-like
systems, False
for Unix-like systems).
type OS_Kind is (Unknown, Dos, NT, OS_2, Unix, VMS, MacOS); subtype Dos_OS is OS_Kind range Dos .. OS_2; function Operating_System return OS_Kind;
The type OS_Kind
defines the (currently)
supported operating systems for the implementation; some
implementations may not declare some values of OS_Kind
if the implementation is not supported on the corresponding OS.
Note that the Dos value covers basic MS-Dos, Windows 3.x, and
Windows 9x (but not NT). The subtype Dos_OS
is a
convenience to designate the OSes that follow (more or less) MS-DOS
naming system (i.e case not signficant, .EXE for executables, etc.).
The function Operating_System
returns the current
OS. The value Unknown
is returned if and only if the
implementation was unable to recognize the OS, but is still able
to provide (most of) the functionnalities. If an operation is not
available due to the failure to recognize the OS, every call to
this operation will raise Service_Error
with a
message of "Not implemented".
Supported operating systems: | |
Gnat implementation | Dos, NT, OS_2 (untested), Unix |
Tcl implementation | Dos, NT, OS_2 (untested), Unix, MacOS (untested) |
function Get_Environment (Name : String) return String;
Get the value of an environment variable. Name
is
interpreted as an environment variable name, according to the OS
rules for environment variables. If the environment variable is
undefined, an empty string is returned.
function Executable_Path return String;
Get the name of the executable which is currently running.
This includes the full (absolute) path to where the executable is
located, and the full name of the executable, irrespectively of
how the user typed it (for example under Dos, a ".exe
"
extension will be added).
procedure OS_Execute (Command : in String; Result : out Ada.Command_Line.Exit_Status; Blocking : in Boolean := True); procedure Shell_Execute (Command : in String; Result : out Ada.Command_Line.Exit_Status Blocking : in Boolean := True);
These procedures perform the execution of an OS command. OS_Execute
calls the OS directly, while Shell_Execute
executes
the command in a sub-shell (using COMMAND.COM
under
Dos/Windows9X, CMD.EXE
under Windows-NT/OS2, and sh
under Unix systems). Note that under DOS-like systems, built-in
commands like copy
and dir
are only
available in a sub-shell.
If the Blocking
parameter is set to True
(the default), the procedure will not return until the command is
completed, and Result
will be set to the error code
returned by the command. If Blocking
is set to False
,
the procedure will return immediately and the command will be run
in parallel; in this case, Result
is set to Success
if the command was successfully started, or to Failure
if the command could not be started by the OS.
These subprograms provide facilities for matching names against patterns. They are not strictly speaking related to files, but the matching mechanism corresponds to what is needed for dealing with file names; more specifically, these functions can be handy in writing filters.
function Match (Name : String; Pattern : String; Ignore_Case : Boolean := Default_Ignore_Case) return Boolean; type Compiled_Pattern is private; function Compile (Pattern : String; Ignore_Case : Boolean := Default_Ignore_Case) return Compiled_Pattern; function Match (Name : String; Pattern : Compiled_Pattern) return Boolean;
The first function checks if Name
matches Pattern
,
according to the "glob" style of pattern matching (see Patterns). If the parameter Ignore_Case
is True
, case is not considered in the matching (i.e.
both the name and the pattern are converted to lower case before
doing the comparison).
On some implementations, it is possible to pre-compile a
pattern to get more efficient pattern matching operations when
matching several strings against the same pattern. The Compile
function is used to compile a pattern into the compiled form, and
the second form of the Match
function can be used to
match strings against this precompiled pattern.
Note that the second form is always provided, even on implementations that do not provide the precompilation facility. Using the second form on such implementations will work, but there will be no gain in efficiency. In practice, this means that there is no risk for portability to use the precompiled form. As a rule of thumb, use the regular form if you are making a single match, and use the precompiled form if you are matching many strings against the same pattern.
Support for precompiled patterns: | |
Gnat implementation | Effective |
Tcl implementation | Ineffective |
These subprograms provide facilities for manipulating file names, extracting fields from full (or partial) names, etc, as well as querying information about files.
function Join (Path : String; Name : String) return String;
Assembles a path (list of directories) with a file name to make a full name.
type String_Property is private; function "+" (Left, Right : String_Property) return String_Property; Expanded : constant String_Property; As_File : constant String_Property; Device : constant String_Property; Path : constant String_Property; Name : constant String_Property; Extension : constant String_Property; Version : constant String_Property; Native_Form : constant String_Property; -- = Device + Path + Name + Extension + Version function Property (Query : String_Property; Name : String; Default : String := ""; Relative_To : String := "") return String;
This function extracts various parts from a given file name; a
value of type String_Property
defines which part are
to be included in the result. Note that Name
needs
not be the name of an existing file. If a (requested) part is not
present in Name
, then the same part (if any)
extracted from Default
is returned.
The Relative_To
parameter indicates how to
interpret relative file names; any relative name will be assumed
to be starting from this directory. If Relative_To
is
itself relative, it will be interpreted according to the current
directory. The default value ("") means that relative
values will be interpreted according to the current directory.
This feature is especially useful for multi-tasking programs
where tasks operate under different directories, since the notion
of current directory is global to the program, not to individual
tasks.
Constants are provided that identify the basic elements of a
file name; properties can be joined by the "+"
operator. The result will include all the parts specified and
only those, so for example:
Property (Device + Name + Extension, "C:\a\b\foo.bar")= "C:foo.bar"
If you retrieve separately the various parts, note that:
It is therefore guaranteed that the various parts can be
concatenated with "&"
to form a legal
name.
Native_Form
is a useful constant, equivalent to Device
+ Path + Name + Extension + Version
, i.e. it returns all
the fields from the given name. It is however not
equivalent to doing nothing, since the returned string always
uses the proper separators for the operating system. Its effect
is therefore to return the original name with the convention of
your operating system. This is quite handy when you want to pass
a file name to a system command, but want to allow the user to
use other separators... or when you are working with programs
like Emacs, that insist on putting Unix separators, even on Dos!
Two values of type String_Property
do not
designate parts of a file name, but are used to modify the form
of the result:
Expanded
is specified, the Path
of the returned value (if any) will always be an absolute
path; i.e. the path will be expanded with the current
directory if the value specified in the input string is a
relative path. Note also that the path is simplified,
i.e. /a/./b//../c
will be returned as /a/c.
As_File
is specified, the input value
is always considered as a file name, even if it ends with
a directory separator. This can be helpful when breaking
a path in subcomponents. The effect of "As_File"
is shown in the following example: Property (Path, "/a/b/c/") = "/a/b/c/" |
Property (Name, "/a/b/c/") = "" |
Property (Path + As_File, "/a/b/c/") = "/a/b/" |
Property (Name + As_File, "/a/b/c/") = "c" |
Note that it is possible to give only Expanded
or As_File
, without specifying any other part; in
this case, it is considered to apply to the full input string.
This means that Property (Expanded, S)
will return
the full (absolute) name of the file name contained in S
,
and that Property (As_File, S)
will return the input
string, except that if there was a trailing directory separator
in it, it is removed.
The remaining Property
functions query various
aspects of a file, given by its name; the corresponding file must
therefore exist. Note that this mechanism is intended to allow
future implementations to define more properties, without
sacrificing the compatibility with previous implementations.
type Boolean_Property is (Exists, Is_Directory, Is_Regular_File, Is_Owned, Is_Readable, Is_Writable, Is_Executable); function Property (Query : Boolean_Property; Name : String) return Boolean; type Time_Property is (Access_Time, Change_Time); function Property (Query : Time_Property; Name : String) return Ada.Calendar.Time; type Long_Property is (Size); function Property (Query : Long_Property; Name : String) return Long_Integer; type Enum_Property is (File_Kind, Path_Kind); type Enum_Values is (File, Directory, CharacterSpecial, BlockSpecial, Fifo, Link, Socket, -- Values for File_Kind Absolute, Relative, VolumeRelative); -- Values for Path_Kind subtype File_Kind_Enums is Enum_Values range File..Socket; subtype Path_Kind_Enums is Enum_Values range Absolute..VolumeRelative; function Property (Query : Enum_Property; Name : String) return Enum_Values;
Note that for properties that return an enumeration, the type Enum_Values
gathers all possible values, and subtypes correspond to each
possible query; therefore Property (File_Kind, Name)
will return a value in the subtype File_Kind_Enums
,
etc.
The definition of the various properties is given by the following table:
Result subtype |
Property |
Effect if file exists |
Effect if file does not exist |
---|---|---|---|
Boolean | Exists | Returns True if Name
identifies an existing file, directory, etc. |
Returns False |
Is_Directory | Returns True if Name
identifies an existing directory |
Returns False |
|
Is_Regular_File | Returns True if Name
identifies an existing regular file |
Returns False |
|
Is_Owned | Returns True if Name
identifies an existing file owned by the user. |
Returns False |
|
Is_Readable | Returns True if Name
identifies an existing file, and the user has read access
to it |
Returns False |
|
Is_Writable | Returns True if Name
identifies an existing file, and the user has write
access to it |
Returns False |
|
Is_Executable | Returns True if Name
identifies an existing file, and the user has execute
access to it |
Returns False |
|
Time | Access_Time | Time the file was last accessed. | Raises Service_error |
Change_Time | Time the file was last modified | Raises Service_error |
|
Long_Integer | Size | Size of the file, or 0 if the file does not exist. | Raises Service_error |
File_Kind_Enums | File_Kind | Returns the kind of the file (regular file, directory, socket, etc). | Raises Service_error |
Path_Kind_Enums | Path_Kind | Returns whether the Name is
absolute, relative, or volume-relative (i.e., on DOS
systems, the path is relative and includes an explicit
drive letter). |
Returns correct result |
Support for properties: | |
Gnat implementation | Unsupported properties:Is_Owned , Is_Readable , Is_Writable ,
Is_Executable , Access_Time Unsupported
values (defined, but never returned): |
Tcl implementation | All properties and values are supported |
type File_Properties (Kind : File_Kind_Enums := File) is record Name : Ada.Strings.Unbounded.Unbounded_String; Path : Ada.Strings.Unbounded.Unbounded_String; case Kind is when File | Directory => Access_Time : Ada.Calendar.Time; Change_Time : Ada.Calendar.Time; Size : Long_Integer; when others => null; end case; end record; function Properties (Name : String) return File_Properties;
This function returns a record containing all the properties
of the file. The specification of the type File_Properties
above is the most general one; an implementation will include in
its declaration only the fields corresponding to supported
properties.
Supported fields: | |
Gnat implementation | All except Access_Time |
Tcl implementation | All fields supported |
These operations allow to build lists of files corresponding
to certain properties, together with operations to get the
properties of any file in the list. The first element of the list
has Index
one. If a variable of type File_List
is declared without explicit initialization, it is automatically
set to the "empy list" state.
type File_List is private; Empty_File_List : constant File_List; Sort_Mapping : Ada.Strings.Maps.Character_Mapping := Ada.Strings.Maps.Constants.Lower_Case_Map; function Build_List (Pattern : in String := "*"; Relative_To : in String := Current_Dir; Sort : in Boolean := False) return File_List; function Length (List : File_List) return Natural; function To_String (List : File_List; Separator : Character := ' ') return String;
The Build_List
procedure returns a list of all
files that match the given Pattern. The Relative_To
parameter indicates how to interpret relative file names; see File properties services for details. If Sort
is set to False
(the default), the files appear in
an unspecified order. If Sort
is set to True
,
the files are sorted in dictionnary order of their absolute
names, since the list may include files from different
directories. (dictionnary order means that casing is not
considered when sorting, except to break a tie).
The constant Empty_File_List
is a value of type File_List
that contains no files.
The function Length
returns the number of files
in a file list.
The function To_String
returns a string
representation of the list. File names are in absolute form, and
separated with the Separator
character. If a file
name contains (at least) one Separator
, it is put in
quotes. Note that the list returned when Separator
is the space character (the default) is appropriate for passing
to the file operations.
function "+" (Left, Right : File_List) return File_List;
The "+"
function
merges two lists. If both lists are sorted, then the
result list is sorted and duplicates (files present in both lists)
are removed. If at least one of the lists is not sorted,
the left list is appended to the right list, and possible
duplicates are not removed; the resulting list is
considered as not sorted. Note that a list that contains
zero or one element is always considered sorted.
generic with function Is_Selected (List : File_List; Index : Positive) return Boolean; function Generic_Filter (List : File_List) return File_List;
This generic allows for the definitions of file list "filters",
i.e. functions that take a list and return a different list
containing only files that correspond to certain criteria. An
instantiation of Generic_Filter
will call the actual
for the function Is_Selected
with all values of Index
in the list, and will keep in the resulting list only the entries
for which the function returned True
. Not that the
function is passed the full list, and can access other elements
in the list; this can be useful if you want to filter a list and
keep, for example, only the two most recent versions of the files.
Of course, when you want to write a filter, you would like to pass some parameters to the selection function to determine which files to retain. This can be easily done by following the scheme below, which is an example of a filter that keeps only files above a given size:
function Filter (List : File_List; Min_Size : Long_Integer) return File_List is function Is_Selected (List : File_List; Index : Natural) return Boolean is begin return Property (Size, List, Index) >= Min_Size; end Is_Selected; function Local_Filter is new Generic_Filter (Is_Selected); begin return Local_Filter (List); end Filter;
subtype Iter_Enum_Property is Enum_Property range File_Kind..File_Kind; function Property (Query : String_Property; List : File_List; Index : Positive) return String; function Property (Query : Boolean_Property; List : File_List; Index : Positive) return Boolean; function Property (Query : Time_Property; List : File_List; Index : Positive) return Ada.Calendar.Time; function Property (Query : Long_Property; List : File_List; Index : Positive) return Long_Integer; function Property (Query : Iter_Enum_Property; List : File_List; Index : Positive) return Enum_Values; function Properties (List : File_List; Index : Positive) return File_Properties;
These functions operate exactly like the corresponding
operations on file names, but return information about the
element in the List
at position Index
. Constraint_Error
is raised if Index
is greater than Length(List)
.
The only differences are that, for enumeration properties, only a
subset of enumeration properties is provided, since Path_Type
would make no sense;, and that the String_Property
function does not include a default, since it always operates on
the full name of the file.
Note that when called, these operations store locally the corresponding information, so if you query the same information more than once, subsequent calls will be much faster. For this reason, this form should be preferred to calling the corresponding property operation with the name (as a string) of the corresponding file. There is an exception however: if you want to check, for example, how far an FTP transfer has been. In that case, you would like to reevaluate the size of the file each time; use the property operation with the file name, rather than the list entry.
procedure Copy_File (Pattern : String; To : String); procedure Move_File (Pattern : String; To : String); procedure Rename_File (File : String; To : String); procedure Delete_File (Pattern : String);
These subprograms provide basic facilities for file
manipulations. Names should be self-explanatory :-). Note that
the value returned by Current_Dir
has the form of a
file name, not a directory (i.e. it does not include a
trailing directory separator).
Operations whose first parameter is Pattern
operate on all files that match Pattern
. Copy_File
and Delete_File
treat directories as normal files, i.e.
they are deleted (even if non empty) or fully copied (including
-recursively- their content). These operations behave as much as
possible as the usual commands of the system, i.e. creation date
is kept during a Copy_File
, etc.
procedure Create_Dir (Name : String); procedure Change_Dir (To : String); function Current_Dir return String;
These subprograms provide basic facilities for directory
manipulations. Names should be self-explanatory :-) :-). Note
that the value returned by Current_Dir
has the form
of a file name, not a directory (i.e. there is no
trailing directory separator).
The file purge.adb
contains an example program
that demonstrates typical usage of OS_Services. It is a program
that purges backup files. Usage:
purge [-r] [-i]
The program removes any "*.bak", "*.backup.*", and "*~" files. if the option -r is given, purge applies recursively to all subdirectories. If the option -i is given, the user is asked for permission before deleting the file.
Q: How do I simply provide a default extension for a
name?
A: Suppose you want to add ".adb" to a name only
if there is no extension already. Use:
Name_Ext : constant String := Property (Native_Name, Raw_Name, ".adb");
Q: With the (string) property As_File
, it
is easy to turn a directory specification into a file
specification. How do I do it the other way round ?
A: Make a Join
with an empty string as
the file name.
Q: Why is the default value for parameter Relative_To
in (string) property "" rather than Current_Directory
?
A: That's what we did first, but we discovered
that evaluating Current_Directory
can have a
terrible performance hit. With this convention, we save this
evaluation when not needed.
Q: Why aren't there more "questions and answers"
?
A: Because we're waiting for your questions :-). You are
welcome to send them to rosen@adalog.fr.
[More to be supplied later]
Dealing with file names can be tricky, especially since we wanted to handle blanks in file names properly.
We tried to optimize queries by having a kind of lazy evaluation of file properties. File properties are evaluated, and then stored, only when requested. This may not be noticeable locally, but it has a tremendous effect on speed when you are dealing with files over a network.
If you found this package useful...
If you think that it would have taken an awful time to write it
yourself...
If it showed you some usages of Ada that you didn't think about...
Maybe you should consider using Adalog's consulting and training services !