!standard A.16(20/2) 10-04-06 AI05-0049-1/03 !standard A.16(82/2) !standard A.16.1(1) !class Amendment 07-04-10 !status Amendment 2012 10-04-06 !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 (See proposal.) !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 2 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 indicates 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 indicates the current directory 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 has proper syntax but is not a full name, and returns False otherwise. AARM Ramification: Relative names include simple names as a special case. function Initial_Directory (Name : in String) return String; Initial_Directory 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; Relative_Name 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 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) @dinsa @xcode<@b Compose (Containing_Directory : @b String := ""; Name : @b String; Extension : @b String := "") @b String;> @dinss @xcode<@b Name_Case_Kind @b (Unknown, Case_Sensitive, Case_Insensitive, Case_Preserving);> @xcode<@b Name_Case_Equivalence (Name : @b String) @b Name_Case_Kind;> !corrigendum A.16(82/2) @dinsa @xindent @dinss @xcode<@b Name_Case_Equivalence (Name : @b String) @b Name_Case_Kind;> @xindent !corrigendum A.16.1(1) @dinsc 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. @i<@s8> If provided, the library package Directories.Hierarchical_File_Names has the following declaration: @xcode<@b Ada.Directories.Hierarchical_File_Names @b @b Is_Simple_Name (Name : @b String) @b Boolean; @b Is_Root_Directory_Name (Name : @b String) @b Boolean; @b Is_Parent_Directory_Name (Name : @b String) @b Boolean; @b Is_Current_Directory_Name (Name : @b String) @b Boolean; @b Is_Full_Name (Name : @b String) @b Boolean; @b Is_Relative_Name (Name : @b String) @b Boolean; @b Simple_Name (Name : @b String) @b String @b Ada.Directories.Simple_Name; @b Containing_Directory (Name : @b String) @b String @b Ada.Directories.Containing_Directory; @b Initial_Directory (Name : @b String) @b String; @b Relative_Name (Name : @b String) @b String; @b Compose (Directory : @b String := ""; Relative_Name : @b String; Extension : @b String := "") @b String; @b 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. @xcode<@b Is_Simple_Name (Name : @b String) @b Boolean;> @xindent @xcode<@b Is_Root_Directory_Name (Name : @b String) @b Boolean;> @xindent @xcode<@b Is_Parent_Directory_Name (Name : @b String) @b Boolean;> @xindent @xcode<@b Is_Current_Directory_Name (Name : @b String) @b Boolean;> @xindent @xcode<@b Is_Full_Name (Name : @b String) @b Boolean;> @xindent @xcode<@b Is_Relative_Name (Name : @b String) @b Boolean;> @xindent @xcode<@b Initial_Directory (Name : @b String) @b String;> @xindent @xcode<@b Relative_Name (Name : @b String) @b String;> @xindent @xcode<@b Compose (Directory : @b String := ""; Relative_Name : @b String; Extension : @b String := "") @b String;> @xindent @xindent @i<@s8> Directories.Hierarchical_File_Names should be provided for systems with hierarchical file naming, and should not be provided on other systems. @xindent<@s9> !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. ****************************************************************