Version 1.10 of ai05s/ai05-0049-1.txt
!standard A.16(20/2) 10-06-08 AI05-0049-1/04
!standard A.16(82/2)
!standard A.16.1(0)
!class Amendment 07-04-10
!status Amendment 2012 10-04-06
!status WG9 Approved 10-06-18
!status ARG Approved 9-0-1 10-02-26
!status work item 07-04-10
!status received 07-02-28
!priority Medium
!difficulty Medium
!subject Extend file name processing in Ada.Directories
!summary
A new package Ada.Directories. Hierarchical_File_Names is added and a
new function Name_Case_Equivalence is added to the existing package
Ada.Directories.
!problem
There are various deficiencies in Ada.Directories.
(1) Missing support for concatenating a root directory with a relative
pathname. This is quite common in Web development where you know the Web
root where files are stored and you want to build the full pathname.
Something like this:
Full_Path : constant String := Compose ("/tmp", "public/file.txt");
This is not possible as "public/file.txt" is not a Simple_Name.
(2) There is no way to know we have a "root" directory, so:
Containing_Directory ("/")
is supposed to raise Use_Error as there is no containing directory for
'/' (RM A.16. 76/2). So the only solution proposed is to catch the
exception. That's ugly.
(3) Case sensitivity is not handled in Ada.Directories. This has the
unfortunate effect that programs written to be portable cannot account
for operating system differences. This is a serious issue, because the
two most common operating systems differ in the issue of case sensitivity.
!proposal
Add a new package Ada.Directories.Hierarchical_File_Names.
Add a new function Name_Case_Equivalence to Ada.Directories.
!wording
Add after A.16(20/2) (in Ada.Directories):
type Name_Case_Kind is (Unknown, Case_Sensitive, Case_Insensitive,
Case_Preserving);
function Name_Case_Equivalence (Name : in String) return Name_Case_Kind;
Add after A.16(82/2):
function Name_Case_Equivalence (Name : in String) return Name_Case_Kind;
Returns the file name equivalence rule for the directory containing Name.
Raises Name_Error if Name is not a full name. Returns Case_Sensitive if
file names that differ only in the case of letters are considered different
names. If file names that differ only in the case of letters are considered
the same name, then Case_Preserving is returned if the name has the case
of the file name used when a file is created; and Case_Insensitive otherwise.
Returns Unknown if the file name equivalence is not known.
AARM Note: Unix, Linux, and its relatives are Case_Sensitive systems.
Windows is a Case_Preserving system (unless the rarely used POSIX mode is used).
Ancient systems like CP/M and early MS-DOS were Case_Insensitive systems
(file names were always in UPPER CASE). Unknown is provided in case it is
impossible to tell (such as could be the case for network files).
Add a new clause A.16.1:
A.16.1 The Package Directories.Hierarchical_File_Names
The library package Directories.Hierarchical_File_Names is an optional package
providing operations for file name construction and decomposition for
targets with hierarchical file naming.
Static Semantics
If provided, the library package Directories.Hierarchical_File_Names has
the following declaration:
package Ada.Directories.Hierarchical_File_Names is
function Is_Simple_Name (Name : in String) return Boolean;
function Is_Root_Directory_Name (Name : in String) return Boolean;
function Is_Parent_Directory_Name (Name : in String) return Boolean;
function Is_Current_Directory_Name (Name : in String) return Boolean;
function Is_Full_Name (Name : in String) return Boolean;
function Is_Relative_Name (Name : in String) return Boolean;
function Simple_Name (Name : in String) return String
renames Ada.Directories.Simple_Name;
function Containing_Directory (Name : in String) return String
renames Ada.Directories.Containing_Directory;
function Initial_Directory (Name : in String) return String;
function Relative_Name (Name : in String) return String;
function Compose (Directory : in String := "";
Relative_Name : in String;
Extension : in String := "") return String;
end Ada.Directories.Hierarchical_File_Names;
In addition to the operations provided in package
Directories.Hierarchical_File_Names, operations in package Directories
can be used. In particular, functions Full_Name, Base_Name, and Extension are
usable with hierarchical file names.
function Is_Simple_Name (Name : in String) return Boolean;
Returns True if Name is a simple name, and returns False otherwise.
function Is_Root_Directory_Name (Name : in String) return Boolean;
Returns True if Name is syntactically a root (a
directory that cannot be decomposed further), and returns False otherwise.
AARM Implementation Note: For Unix and Unix-like systems, "/" is the root. For Microsoft
Windows, "C:\" and "\\Computer\Share" are roots.
function Is_Parent_Directory_Name (Name : in String) return Boolean;
Returns True if Name can be used to indicate symbolically the
parent directory of any directory, and returns False otherwise.
AARM Implementation Note: Is_Parent_Directory_Name returns True if and only if Name is ".." for
both Unix and Windows.
function Is_Current_Directory_Name (Name : in String) return Boolean;
Returns True if Name can be used to indicate symbolically the
the directory itself for any directory, and returns False otherwise.
AARM Implementation Note: Is_Current_Directory_Name returns True if and only if Name is "." for
both Unix and Windows.
function Is_Full_Name (Name : in String) return Boolean;
Returns True if the leftmost directory part of Name is a root, and
returns False otherwise.
function Is_Relative_Name (Name : in String) return Boolean;
Returns True if Name allows the identification of an external file but is
not a full name, and returns False otherwise.
AARM Ramification: Relative names include simple names as a special case. This
function returns False if the syntax of the name is incorrect.
function Initial_Directory (Name : in String) return String;
Returns the leftmost directory part in Name.
Redundant[That is a root directory name (for a full name), or one of
a parent directory name, a current directory name, or a simple name
(for a relative name).] The exception Name_Error is propagated if the
string given as Name does not allow the identification of an external file
(including directories and special files).
function Relative_Name (Name : in String) return String;
Returns the entire file name except the Initial_Directory
portion. The exception Name_Error is propagated if the
string given as Name does not allow the identification of an external file
(including directories and special files), or if Name has a
single part (this includes if any of Is_Simple_Name,
Is_Root_Directory_Name, Is_Parent_Directory_Name, or Is_Current_Directory_Name
are True).
AARM Note: The result might be a simple name.
function Compose (Directory : in String := "";
Relative_Name : in String;
Extension : in String := "") return String;
Returns the name of the external file with the specified
Directory, Relative_Name, and Extension. The exception Name_Error is
propagated if the string given as Directory is not the null string
and does not allow the identification of a directory, or if
Is_Relative_Name (Relative_Name) is False, or if the string
given as Extension is not the null string and is not a possible
extension, or if Extension is not the null string and
Simple_Name (Relative_Name) is not a base name.
[Editor's note: We don't have Is_Base_Name anywhere, so I can't use
it in the wording. I'd prefer "Is_Base_Name (Simple_Name (Relative_Name)
is False".]
The result of Compose is a full name if Is_Full_Name (Directory) is True;
result is a relative name otherwise.
AARM Ramification: Name_Error is raised by Compose if Directory is not the
null string, and both Is_Full_Name and Is_Relative_Name return False.
AARM Discussion: A common security problem is to include a parent directory
name in the middle of a file name; this is often used to navigate outside
of an intended root directory. We considered attempting to prevent that
case by having Compose detect it and raise an exception. But the extra
rules necessary were more confusing than helpful.
We can say more about the details of these operations by adopting the notation
of a subscript to specify how many path fragments a particular result has.
Then, we can abbreviate "Full Name" as "Full" and "Relative Name" as "Rel".
In this notation, Unix file name "a/b" is a Rel(2), "../c/d" is a Rel(3),
and "/a/b" is a Full(2). Rel(1) is equivalent to a simple name; thus we
don't have to describe that separately.
In this notation,
For N>1,
Containing_Directory(Rel(N)) = Leftmost Rel(N-1),
Containing_Directory(Full(N)) = Leftmost Full(N-1),
Name_Error if N = 1 in the above.
Similarly,
For N>1,
Relative_Name(Rel(N)) = Rightmost Rel(N-1),
Relative_Name(Full(N)) = Rightmost Full(N-1),
Name_Error if N = 1 in the above.
Finally, for Compose (ignoring the extension here):
Compose (Directory => Full(N), Relative_Name => Rel(M)) => Full(N+M)
Compose (Directory => Rel(N), Relative_Name => Rel(M)) => Rel(N+M)
Name_Error if Relative_Name is a Full(M).
We didn't try to write wording to reflect the details of these rules.
End AARM Notes.
Implementation Advice
Directories.Hierarchical_File_Names should be provided for systems with
hierarchical file naming, and should not be provided on other systems.
AARM Implementation Note: This package should be provided on
Microsoft Windows, Unix, Linux, and most Unix-like systems.
[Editor: The following are user notes, the first is similar to A.16(127/2).]
NOTES: These operations operate on file names, not external files. The
files identified by these operations do not need to exist. Name_Error is
raised only as specified or if the file name is malformed and cannot
possibly identify a file. The result of these operations depends only
on their parameters.
Containing_Directory raises Use_Error if Name does not have a containing
directory, including when any of Is_Simple_Name, Is_Root_Directory_Name,
Is_Parent_Directory_Name, or Is_Current_Directory_Name are True.
!discussion
The generalized Compose routine provides the solution to problem (1).
The function Is_Root_Directory_Name provides the solution to problem (2).
The function Name_Case_Equivalence provides some assistance to problem (3).
We do not provide file name comparison facilities that adjust to the
appropriate target semantics. This is an appealing idea, but unfortunately,
Microsoft recommends that Windows programmers not attempt to compare
file names. That's because the case-sensitity used is locale-dependent,
and the locale of a file system is set when it is created: it is not
necessarily the same as that of the host operating system. (That's even more
of a problem across networks.) Windows does not provide an API for this
purpose.
We could ignore this problem by saying that the comparison only works for
Latin-1 file names. But that is obnoxious as Windows (and other systems)
supports full Unicode file naming.
Similarly, we don't provide a file name normalization function, which would
be an alternative. However, normalization can only be done on Windows for files
that actually exist (the idea is that you look up and return the actual case
used by the existing file). That does not work in this context (of file name
construction) as we want to support building names of files that we haven't
yet created.
!example
!corrigendum A.16(20/2)
Insert after the paragraph:
function Compose (Containing_Directory : in String := "";
Name : in String;
Extension : in String := "") return String;
the new paragraphs:
type Name_Case_Kind is (Unknown, Case_Sensitive, Case_Insensitive,
Case_Preserving);
function Name_Case_Equivalence (Name : in String) return Name_Case_Kind;
!corrigendum A.16(82/2)
Insert after the paragraph:
Returns the name of the external file with the specified
Containing_Directory, Name, and Extension. If Extension is the null string, then
Name is interpreted as a simple name; otherwise Name is interpreted as a base
name. The exception Name_Error is propagated if the string given as
Containing_Directory is not null and does not allow the identification of a
directory, or if the string given as Extension is not null and is not a possible
extension, or if the string given as Name is not a possible simple name (if
Extension is null) or base name (if Extension is non-null).
the new paragraphs:
function Name_Case_Equivalence (Name : in String) return Name_Case_Kind;
Returns the file name equivalence rule for the directory containing
Name. Raises Name_Error if Name is not a full name. Returns Case_Sensitive if
file names that differ only in the case of letters are considered different
names. If file names that differ only in the case of letters are considered the
same name, then Case_Preserving is returned if the name has the case of the file
name used when a file is created; and Case_Insensitive otherwise. Returns
Unknown if the file name equivalence is not known.
!corrigendum A.16.1(0)
Insert new clause:
The library package Directories.Hierarchical_File_Names is an optional package
providing operations for file name construction and decomposition for
targets with hierarchical file naming.
Static Semantics
If provided, the library package Directories.Hierarchical_File_Names has
the following declaration:
package Ada.Directories.Hierarchical_File_Names is
function Is_Simple_Name (Name : in String) return Boolean;
function Is_Root_Directory_Name (Name : in String) return Boolean;
function Is_Parent_Directory_Name (Name : in String) return Boolean;
function Is_Current_Directory_Name (Name : in String) return Boolean;
function Is_Full_Name (Name : in String) return Boolean;
function Is_Relative_Name (Name : in String) return Boolean;
function Simple_Name (Name : in String) return String
renames Ada.Directories.Simple_Name;
function Containing_Directory (Name : in String) return String
renames Ada.Directories.Containing_Directory;
function Initial_Directory (Name : in String) return String;
function Relative_Name (Name : in String) return String;
function Compose (Directory : in String := "";
Relative_Name : in String;
Extension : in String := "") return String;
end Ada.Directories.Hierarchical_File_Names;
In addition to the operations provided in package
Directories.Hierarchical_File_Names, operations in package Directories
can be used. In particular, functions Full_Name, Base_Name, and Extension are
usable with hierarchical file names.
function Is_Simple_Name (Name : in String) return Boolean;
Returns True if Name is a simple name, and returns False otherwise.
function Is_Root_Directory_Name (Name : in String) return Boolean;
Returns True if Name is syntactically a root (a directory that cannot
be decomposed further), and returns False otherwise.
function Is_Parent_Directory_Name (Name : in String) return Boolean;
Returns True if Name can be used to indicate symbolically the
parent directory of any directory, and returns False otherwise.
function Is_Current_Directory_Name (Name : in String) return Boolean;
Returns True if Name can be used to indicate symbolically the
the directory itself for any directory, and returns False otherwise.
function Is_Full_Name (Name : in String) return Boolean;
Returns True if the leftmost directory part of Name is a root, and
returns False otherwise.
function Is_Relative_Name (Name : in String) return Boolean;
Returns True if Name allows the identification of an external file but is
not a full name, and returns False otherwise.
function Initial_Directory (Name : in String) return String;
Returns the leftmost directory part in Name.
That is a root directory name (for a full name), or one of a parent
directory name, a current directory name, or a simple name (for a relative
name). The exception Name_Error is propagated if the string given as Name does
not allow the identification of an external file (including directories and
special files).
function Relative_Name (Name : in String) return String;
Returns the entire file name except the Initial_Directory
portion. The exception Name_Error is propagated if the string given as Name does
not allow the identification of an external file (including directories and
special files), or if Name has a single part (this includes if any of
Is_Simple_Name, Is_Root_Directory_Name, Is_Parent_Directory_Name, or
Is_Current_Directory_Name are True).
function Compose (Directory : in String := "";
Relative_Name : in String;
Extension : in String := "") return String;
Returns the name of the external file with the specified Directory,
Relative_Name, and Extension. The exception Name_Error is propagated if the
string given as Directory is not the null string and does not allow the
identification of a directory, or if Is_Relative_Name (Relative_Name) is False,
or if the string given as Extension is not the null string and is not a possible
extension, or if Extension is not the null string and Simple_Name
(Relative_Name) is not a base name.
The result of Compose is a full name if Is_Full_Name (Directory) is
True; result is a relative name otherwise.
Implementation Advice
Directories.Hierarchical_File_Names should be provided for systems with
hierarchical file naming, and should not be provided on other systems.
NOTES:
42 These operations operate on file names, not external files. The
files identified by these operations do not need to exist. Name_Error is
raised only as specified or if the file name is malformed and cannot
possibly identify a file. The result of these operations depends only
on their parameters.
43 Containing_Directory raises Use_Error if Name does not have a containing
directory, including when any of Is_Simple_Name, Is_Root_Directory_Name,
Is_Parent_Directory_Name, or Is_Current_Directory_Name are True.
!ACATS test
Create an ACATS test to check that the above new facilities work.
!appendix
From: Pascal Obry
Sent: Wednesday, February 28, 2007 7:59 AM
Here are some feedbacks using the new Ada.Directories package. I have
found that some services are missing in some cases which are quite
common in fact.
1. missing support for concatenating a root directory with a relative
pathname. This is quite common in Web development where you know the Web
root where files are stored and you want to build the full pathname.
Something like this:
Full_Path : constant String := Compose ("/tmp", "public/file.txt");
Sadly this is not possible as "public/file.txt" is not a Simple_Name.
As there is no way to retrieve the host directory separator I really
don't see how to achieve that using plain Ada support.
=> 2 possibles changes:
- Add a Compose_Pathname routine that can handle relative pathname as
second argument.
- Add a character constant into Ada.Directories that contains the host
directory separator to be able to do:
Full_Path : constant String :=
"/tmp" & Directory_Separator & "public/file.txt";
2. There is no way to know we have a "root" directory, so:
Containing_Directory ("/")
is supposed to raise Use_Error as there is no containing directory for
'/' (RM A.16. 76/2). So the only solution proposed is to catch the
exception. Ugly I would say as some of us don't like using the exception
mechanism for "standard" usage. Anyway to please both side I think it
will be good to provide a routine to check if a containing directory exists.
function Has_Containing_Directory (Name : in String) return Boolean;
-- Returns True if the external file represented by Name has
-- a containing directory.
This will be quite convenient and will avoid to rely on the exception.
Thoughts ?
****************************************************************
From: Randy Brukardt
Sent: Tuesday, March 27, 2007 4:26 PM
[Sorry for the late response; I just ran across this message again and
noticed that I hadn't properly replied.]
...
> As there is no way to retrieve the host directory separator I really
> don't see how to achieve that using plain Ada support.
But you're abusing the file name construction functions here. The intent is
that they are to be used for the portable construction of file names and
paths. In this case, you are assuming that the path separator is "/". That
means your code isn't going to be portable anyway, and there is no reason to
even use Compose. Just cat the portions together.
For web use, you have to assume the path separator in the URL. If you really
want to create fully portable names, then you have to parse the URL into
simple names and use Compose to build that into a path. In your example, you
would end up with:
Full_Path : constant String := Compose (Compose ("/tmp", "public"),
"file.txt");
The design of the operations is intended to make it hard to abuse the
facilities to make a seemingly portable program that is not.
> => 2 possibles changes:
>
> - Add a Compose_Pathname routine that can handle relative pathname as
> second argument.
As noted above, this is dangerous. Remember that it is never necessary to
use these file name functions, and I'd recommend avoiding them unless you
really want universal portability. And if you do want universal portability,
then you have to do the work. Still, this is a better option than the
following...
> - Add a character constant into Ada.Directories that contains the host
> directory separator to be able to do:
>
> Full_Path : constant String :=
> "/tmp" & Directory_Separator & "public/file.txt";
We discussed that, but we did not want to assume that the separator is a
single character, or even contiguous characters. We had some examples from
VMS and other systems where more flexibility is needed.
If you are willing to make this sort of target assumption, it is easy enough
to do it in your own code. Ada.Directories is trying to support universally
portable code, and that means it requires more work to use.
...
> function Has_Containing_Directory (Name : in String) return Boolean;
> -- Returns True if the external file represented by Name has
> -- a containing directory.
>
> This will be quite convenient and will avoid to rely on the exception.
Sounds OK as an Amendment suggestion, but of course I have no idea when
we'll be seriously considering them again. In the mean time, you'll just
have to handle the exception.
****************************************************************
From: Jeffrey R. Carter
Sent: Tuesday, March 27, 2007 5:17 PM
...
> For web use, you have to assume the path separator in the URL. If you really
> want to create fully portable names, then you have to parse the URL into
> simple names and use Compose to build that into a path. In your example, you
> would end up with:
>
> Full_Path : constant String := Compose (Compose ("/tmp", "public"),
> "file.txt");
>
> The design of the operations is intended to make it hard to abuse the
> facilities to make a seemingly portable program that is not.
However, this contains '/', and so is still not portable. As there's no
way to obtain the root directory, there doesn't seem to be a portable
way to do this.
> Sounds OK as an Amendment suggestion, but of course I have no idea when
> we'll be seriously considering them again. In the mean time, you'll just
> have to handle the exception.
My guess is 2011 for suggestions for the next TC, and 2016 for the next
major version.
****************************************************************
From: Pascal Obry
Sent: Wednesday, March 28, 2007 3:21 AM
>
> But you're abusing the file name construction functions here. The intent is
No, this was meant to be a simple example. Of course "/tmp" is in a
variable and was retrieved/constructed from the system using
Ada.Directories. Idem for "public/file.txt".
In the program I tried to build I had Root_Dir and Relative_Path (BTW,
this was on Windows so using \, as I said the above is just a simple
example). And from Root_Dir and Relative_Path I found no solution to
build a full pathname. Quite a hard limit to me!
> Full_Path : constant String := Compose (Compose ("/tmp", "public"),
> "file.txt");
In my context this was not possible. Root_Dir and Relative_Path were
built separately.
****************************************************************
From: Randy Brukardt
Sent: Wednesday, March 28, 2007 6:07 PM
> No, this was meant to be a simple example. Of course "/tmp" is in a
> variable and was retrieved/constructed from the system using
> Ada.Directories. Idem for "public/file.txt".
Sorry, but you're still abusing the file name construction function, because
there is no such thing as a relative path as you are using it. A relative
path would be one starting with .: "./public/file.txt". What you are doing
is creating a path fragment, and that is simply not supported by
Ada.Directories.
That was an intentional decision, because there are a lot of systems
(admittedly not that widely used) that don't support arbitrary nesting of
directories. On such a system, a relative path is an impossibility.
Ada.Directories does not define what you get when you Compose two simple
names, other than to say that it is neither a full name nor a simple name.
It's best to avoid such things; they surely aren't going to be portable to
any unusual systems.
> In the program I tried to build I had Root_Dir and Relative_Path (BTW,
> this was on Windows so using \, as I said the above is just a simple
> example). And from Root_Dir and Relative_Path I found no solution to
> build a full pathname. Quite a hard limit to me!
Yes, because there is no such thing as a relative path, so any attempt to
define such a thing is wrong. This isn't to say that what you are doing is
wrong, its just not what the functions in Ada.Directories were intended for.
> > Full_Path : constant String := Compose (Compose ("/tmp", "public"),
> > "file.txt");
>
> In my context this was not possible. Root_Dir and Relative_Path were
> built separately.
Well, you can't (and probably shouldn't even have been allowed to) build
Relative_Path separately. I think it is a bug that Compose is even allowed
to product something that is not a full name (or at least a relative full
name), specifically because that result cannot be used for anything else.
Indeed, I think it is arguable that Compose should raise Name_Error in this
instance, because "public" (or any simple name) does not represent a
containing directory. (No, the Janus/Ada implementation doesn't do that,
either. :-)
Now, make no mistake about it, I don't think that the functions in
Ada.Directories are very useful. I didn't think they were worthwhile to add
to the package in the first place, because it is very difficult to do them
right and still have them portable to all possible file systems. And more
importantly, they don't do anything useful: if you are willing to assume a
Windows/Unix-like file system (which probably covers 99% of the systems that
you'll encounter), there is zero value to using them. The only important
differences between Windows and Unix is case-sensitivity (which
Ada.Directories doesn't handle at all), and the form of root directories
(ditto). (That's assuming you use "/" as your file separator, which works
fine on both.) So there's hardly any need to use fancy Compose functions
anyway: just concat the silly things. The decomposition functions are
slightly harder, but they take two whole lines to write using Ada.Strings.
We tried to build packages for handling file names for the Janus/Ada
compiler, and they added mainly complications rather than help. I don't
think it is worth it.
Probably the best solution for Ada.Directories would be to define a child
package for constructing/deconstructing names that is *only* intended to
work on Windows/Unix-like systems. That could define relative paths and path
fragments and root directories and case-sensitivity and not have to fight a
lot of phantoms (with the attendant compromises). But I seriously doubt that
it is worth the fairly massive time investment. I suppose an alternative
would be to give a child package with the constants Path_Separator,
Case_Sensitive, and functions Is_Root and Get_Root; that would be enough to
do the job with little complication. But the important thing is such a
package would have to be child, and probably one defined in the AARM [like
Ada.Directories.Information]: it would not make any sense on some targets,
and shouldn't be provided on those targets.
****************************************************************
From: Tucker Taft
Sent: Wednesday, March 28, 2007 10:21 PM
I'm not sure I agree with Randy's assessment of the
intent of Ada.Directories. I never saw it as a lowest
common denominator. I saw it mainly as a way to isolate
the Ada programmer from the quirks of Posix-like, MacOSX-like,
or Windows-like systems. I don't think it should be
crippled just because non-hierarchical file systems exist on
some operating systems. These days that is certainly
uncommon, and I know at least that I never realized that
Ada.Directories was somehow limited in this way.
If Ada.Directories doesn't support hierarchical file systems
well, then it should be augmented sooner rather than later
with additional operations or an "Ada.Directories.Hierarchical"
child package of some sort. Pascal is pointing out what
I would consider real deficiencies, and I'm sorry they weren't
identified earlier.
****************************************************************
From: Randy Brukardt
Sent: Wednesday, March 28, 2007 11:28 PM
> I'm not sure I agree with Randy's assessment of the
> intent of Ada.Directories. I never saw it as a lowest
> common denominator. I saw it mainly as a way to isolate
> the Ada programmer from the quirks of Posix-like, MacOSX-like,
> or Windows-like systems.
But those barely exist: there is hardly any need to be insulated from the
few that there are.
> I don't think it should be
> crippled just because non-hierarchical file systems exist on
> some operating systems.
It isn't "crippled"; you just have to use its parts the way they were
intended, so it can provide appropriate error handling when something is not
supported.
It is important that the results be well-defined, so that code is portable
between implementations of Ada.Directories. Similarly, it ought to be
possible for conformity assessment to be performed on the package - so there
is a way to judge if the implementation is generating those well-defined
results.
> These days that is certainly
> uncommon, and I know at least that I never realized that
> Ada.Directories was somehow limited in this way.
>
> If Ada.Directories doesn't support hierarchical file systems
> well, then it should be augmented sooner rather than later
> with additional operations or an "Ada.Directories.Hierarchical"
> child package of some sort. Pascal is pointing out what
> I would consider real deficiencies, and I'm sorry they weren't
> identified earlier.
When it comes to the file name routines (the ones that *YOU* insisted on
adding to the thing over my strong objections), they are one big deficiency
(just based on the discussions here):
(1) There are issues handling '.' in file names; not all Unix file names
are decomposable and recomposable;
(2) There is no handling of case sensistivity issues (probably the most
important difference between Windows and Unix;
(3) Finding out if you have a root is annoying; if there is more than one
root as on Windows, finding one is impossible.
There are probably others that I've forgotten about. I find it extremely
annoying that this file name crap (which I knew was never going to work out)
is messing up an otherwise nice package.
Anyway, as a practical matter, changing the spec. of Ada.Directories is out
of the question at this point. We've discussed that in the context of
several other issues, and there has never been anything near the will to
change it.
This particular issue could probably be "fixed" by changing the cases where
Name_Error is specified to be raised by Compose. Instead of requiring it to
be raised if the item is not a simple name, we could simply say that the
results are implementation-defined in that case. Then Pascal could do what
he wants to do. Trying to specify what it does in that case is going to be
nearly impossible, as we don't have the definitions in Ada.Directories to do
it (there is no such thing as a path fragment, for instance). OTOH, that
would pretty much make testing of Compose's error handling impossible, as it
would be hard to know of any input that would raise an exception.
But that does nothing for the root issues, the case issues, the issues with
decomposition not being reversible, etc. So it's hardly a comprehensive fix.
Beyond that, I wash my hands of it. I don't think it is worth trying to have
a general file name manager that actually does all of the things that you
actually need to do in practice, because such a thing is either not portable
or unacceptably limited. But if you or anyone else disagrees, feel free to
propose a child package. But it had better be fully documented in the RM
style; I'm sick and tired of having to guess and/or invent the semantics of
these proposals and then taking all of the crap that comes from that. (I
realize that you have lots of experience in that area. :-) And I'll feel
free to take potshots at any such proposal... :-) (Although in all honesty,
I don't know if I have the stomach to dive into that can of worms again,
even as an observer.)
****************************************************************
From: Randy Brukardt
Sent: Thursday, March 29, 2007 8:32 PM
> > I'm not sure I agree with Randy's assessment of the
> > intent of Ada.Directories.
After my tirade last night, I figured I had better waste^H^H^H^H^Hspend a
couple of hours explaining how and why we have the rules we have and how
they could be extended. After all, it is isn't obvious why this should be
such a hard problem.
Early on, we decided that the primary intent of the file name functions
would be to construct file names from parts, as opposed to being able to
deconstruct arbitrary file names. Specifically, the deconstruction functions
are really only expected to work on file names created by Compose; other
file names might work, but the existence of corner cases is not necessarily
important. It's this focus that allows us to downplay most of the
deficiencies of the package, as they typically occur only in file names from
outside sources, not those constructed by Ada.Directories. (You can
legitimately disagree with this focus, but it should be obvious that a
different focus would generate a very different package. And for now, it is
more relevant to discuss what we have rather than what we could have had
with more will and a lot more effort...)
One other important point is that the file name functions are purely
syntactic. That means that properties cannot depend on run-time information.
For instance, a "root" be something that is "mounted" on a Unix system --
that is not known without run-time queries. This property is needed so that
file names can be constructed for things that do not exist. Otherwise, it
would be impossible to create a new file with a constructed name!
This focus means that everything flows from Compose. Currently, there are
two well-defined concepts in Ada.Directories: Full Names (abbreviated here
as Full for reasons that will become clear) and Simple Names (abbreviated
Simp). Other cases exist, but aren't well defined. Decomposition is built
around them. Compose (where Compose is represented as "+") essentially has
the semantics (ignoring extensions, which aren't interesting:
Simp + Simp => Others
Full + Simp => Full
Others + Simp => Others
(Any) + Others => Error
(Any) + Full => Error
This semantics has the advantage that the decomposition is reversible:
Containing_Directory (A + B) => A
Simple_Name (A + B) => B
The only decomposition provided by Ada.Directories is from these two
routines, and that means that all decomposition has to proceed from right to
left in a file name. Similarly, the rules means that composition has to
proceed from left to right in a file name.
Now, the problem is that these definitions aren't necessarily convenient.
Specifically, Pascal Obry wants to construct file names in two pieces, and
put them together later. It should be obvious that the package wasn't
designed to do that! He also comments that it is hard to tell where the root
is. That's certainly true, but that's because the package wasn't intended to
decompose existing file names. A root by definition has to be something that
comes from outside, as Ada.Directories has no way to generate one. (And that
is intentional, while Unix only has a single root, Windows has a forest of
roots that could even include things on machines far away. There isn't any
practical way to name roots on Windows (especially from the perspective of a
"portable" interface); they pretty much have to be provided by the user via
a GUI or other input.
The most important property of this package is that is operates the same way
on every implementation: so that programs can be ported to new targets and
new implementations without requiring nasty debugging sessions. That was
accomplished in two ways: first, by describing as much as possible in
normative terms. With normative descriptions, we open up the possibility of
ACATS tests, and the fact that an implementation can actually be determined
to be wrong. Also, users will have a much better idea of what to expect.
Second, where normative wording couldn't be given (such as guidance for a
particular OS), we gave AARM notes giving examples of what we meant. But
keep in mind that these things can't be tested, at least in the sense that
it isn't possible to fail a conformity assessment due to ignoring those
notes of intent.
Now, let's look at extending Compose to allow Pascal's intended design to
work.
Obviously, we could just leave "Others" as implementation-defined (as I
suggested yesterday). But note that that eliminates the reversibility of
decomposition. And it also compromises testability, as nothing that happened
would really be wrong.
So let's take a look at what it would take to define this properly. First of
all, we need two new kinds of entities (which would replace "Others"):
Relative paths (those starting with "./" or "../") and
path fragments (like "a/b"). Let's abbreviate relative paths as "Rel" and
fragments as "Frag". (I know that it might be easier to treat these
together, but there are a number of operations where the sensibility of the
operations differ. Anyway, this is not the only new things that need
definition, and surely it will require a lot more analysis than I am going
to give it now.)
The first problem is how to handle the decomposition functions. Simple_Name
(abbr. SN below) will always return the rightmost Simp, but what does
Containing_Directory (abbr. CD below) return for a Fraq? It might be a Simp
or a Fraq; we need more information.
One way to handle that is to add a subscript to each item other than Simp
specifying how many path fragments they contain. Thus, "a/b" is a Frag(2),
"../c/d" is a Rel(3). Frag(1) is equivalent to a Simp (simple name). (We'll
need to define roots to decide what a Full(1) and a Rel(1) is.)
In this notation, CD(Frag(2)) = Leftmost Fraq(1) = Leftmost Simp,
CD(Fraq(3)) = Leftmost Frag(2), and so on.
Or more generally,
CD(Frag(N)) = Leftmost Frag(N-1)
Similarly (for N>1):
CD(Full(N)) = Leftmost Full(N-1)
CD(Rel(N)) = Leftmost Rel(N-1)
OK, now we can define what Compose does:
Simp + Simp => Fraq(2)
Full(N) + Simp => Full(N+1)
Fraq(N) + Simp => Fraq(N+1)
Rel(N) + Simp => Rel(N+1)
Full(N) + Fraq(M) => Full(N+M)
Rel(N) + Fraq(M) => Rel(N+M)
Fraq(N) + Fraq(M) => Fraq(N+M)
Simp + Fraq(M) => Fraq(M+1)
(Any) + Rel(M) => Error(*)
(Any) + Full(M) => Error
(*Note: Putting a "../" into a middle of a path is a common security
vulnerability; I've made it illegal for that reason. But that might be
asking too much of these tools and making things a bit less flexible than
they should be. An area for discussion. OTOH, adding something in front of a
root is nonsense and surely should raise an exception.)
The problem is, I don't know how to write these rules in English, and
particularly in understandable English. Pseudo-code would be easier, and as
above is not too hard, but I don't think we can do that in a standard. To
leave the rules non-normative isn't very good for reasons outlined above.
Anyway, this solves Pascal's first problem, but it does nothing for the
second. It's also uncomfortable that you can Compose in any order, but you
can only decompose right-to-left. To fix the root issue, we'd need to define
a Root, and have a Root operation:
Root(Full(N)) => Root
Root(Rel(N)) => Root (not sure about this)
Root(Fraq(N)) => Error
Root(Simp) => Error
Root(Root) => Root
Root + Fraq(N) => Full(N+1)
Root + Simp => Full(2)
(Any) + Root => Error
We'd also probably want an operation that gives the rightmost remainder to
allow more general decomposition (not sure what to call it in general):
Remain(Full(N)) => Rightmost Fraq(N-1)
Remain(Fraq(N)) => Rightmost Fraq(N-1)
etc.
Anyway, it should be pretty obvious that this has ever-growing complexity.
And writing it in English wouldn't be very easy.
In any case, an immediate solution isn't needed for this issue. It *is*
possible to do these operations, it just is less convenient than it could
be. Yes, you have to Compose from left-to-right, but that surely is possible
using the right data structures (i.e. a list of simple names rather than a
single string). And yes, you have to handle an exception rather than being
able to determine if something is a root. Less than ideal, but surely
possible. (As I liked to say during Ada 9X: "everything is implementable,
the question is how hard do you want us to work to do it??")
And none of this addresses case sensitivity, or the problem with
decomposition of Unix file names, etc. A lot of work to do, which could take
away from other things. But if someone wants to take a stab at it
(especially with the wording of the Standard, which is clearly the hard
part), feel free. I'll stand ready to poke holes in it... :-)
****************************************************************
From: Tucker Taft
Sent: Thursday, March 29, 2007 9:15 PM
Java has a "File" class that could be used as a model.
It has the notion of "relative" paths, which is any
sequence of path elements that does not form an absolute
pathname (i.e. doesn't start with "/" on Unix, or doesn't
start with a drive letter or a double backslash
on Windows). If you try to open a file specified with a relative
path, it automatically prepends the current directory.
You can concatenate two sequences of path elements together to form
longer sequences of path elements (so long as the second one is
not an absolute path), and you can extract the
tail path element (the "simple" name in our parlance) and
the parent sequence (all but the last). If the sequence
consists of only one path element then the parent sequence
is empty. There is the notion of an "absolute prefix" part which on
Unix can only be "/" and on Windows can be a drive letter
or a double-slash.
It is not clear the Java scheme can work for systems like VMS where
files and directories are named distinctly (directories go inside
[] if I remember correctly), but perhaps they don't care, and
perhaps we shouldn't either. It's not clear Ada.Directories
can work for VMS either.
In any case, thanks for taking the time to explain this better.
****************************************************************
From: Robert Leif
Sent: Thursday, March 29, 2007 11:04 PM
Please include the possibility of describing the web (http://www. and
file:///C:/) in a child package as an example of a file system.
****************************************************************
From: Randy Brukardt
Sent: Friday, March 30, 2007 12:14 PM
...
> It is not clear the Java scheme can work for systems like VMS where
> files and directories are named distinctly (directories go inside
> [] if I remember correctly), but perhaps they don't care, and
> perhaps we shouldn't either. It's not clear Ada.Directories
> can work for VMS either.
Java's world view appears to me that everyone should do what they say (like
requiring specific kinds of float math), so I'd be surprised if they cared
about anything even slightly offbeat (like VMS). I think Ada.Directories
does work for VMS. I know we talked about that, I don't know if anyone
actually tried to implement it. It is true that Compose is a lot more work
on VMS than a simple text concatenation (you have to change the form of the
last element from file to directory at each step). Whether it is worth
worrying about VMS I can't say and a question for the full ARG to decide;
surely we thought it was when Ada.Directories was designed but whether that
is still true is worth considering (another 5 years has gone by, and I doubt
VMS has gotten more popular in that time). The design is a lot simpler if it
only really is intended to work on Windows and Unix and similar systems.
****************************************************************
Editor's note:
The following National Body comment and disposition is relevant to this AI, as
is the discussion recorded in AI95-00248 (February 2-3, 2006):
Comment 19 [Canada]:
Case sensitivity in Directories, Environment_Variables
Case sensitivity is an implementation issue, but these packages
currently ignore the issue. This has the unfortunate effect that
programs written to be portable cannot account for operating system
differences. This is a serious issue, because the 2 most common
OS's differ in the issue of case sensitivity.
A new enumeration type and two subprograms are required
type Case_Sensitivity is (Upper, Lower, Mixed);
-- mixed means case sensitive, Upper and Lower are case
-- insensitive but the interpretation is upper or lower.
function Is_Case_Sensitive return Case_Sensitivity;
-- returns current behaviour, or default behaviour if
-- Set_Case_Sensitive has not been called.
procedure Set_Case_Sensitive( Sensitivity : in Case_Sensitivity );
-- Sets case sensitivity
Since the type Case_sensitivity is common to Directories,
Sequential_IO, Text_IO, etc so we recommend putting them in
Environment_Variables or Directories.
---
Disposition:
AXE submitted this comment to the ARG for suggestions. The proposed solution
was regarded as too general, but there was no agreement on an alternative
solution. There seemed to be a consensus that alternative facilities could
easily be added as a child package in the future, so it is not necessary to
address this at this point.
The complete ARG e-mail thread is filed in AI-248.
Obviously, AXE Consultants is in no position to promise future work by WG 9.
Therefore, AXE has made no change in the current Amendment but has forwarded
the issue to the convenor of WG 9 so that the question of future work may be
considered.
****************************************************************
From: Edmond Schonberg
Sent: Wednesday, June 9, 2010 12:33 PM
> Ed Schonberg:
> . Study whether the proposed solution for AI05-0049-1 would make sense for VMS.
The answer turns out to be unequivocally yes, so there is no concern here.
****************************************************************
Questions? Ask the ACAA Technical Agent