"It seems we need a new API that allows us to get the canonical path that hits the disk and follows links and fixes casing" —API Review 2015-08-27
C#
public static class Path
{
public static string GetCanonicalPath( string path ); // Resolves symbolic links
public static string GetCanonicalPath( string path, bool preserveSymbolicLinks );
}
A canonical path
DirectorySeparatorChar as a directory separator,. and ..) and no empty path elements (ex. /foo//bar),The behavior of .. with respect to symbolic links is platform dependent. On Windows, it removes the symbolic link element. On Linux, it removes an element of path that was resolved from the symbolic link.
Examples:
C:\Users\All Users is a symbolic link to C:\ProgramData then the canonical form of C:\Users\All Users\..\foo is C:\Users\foo./var/lock is a symbolic link to /run/lock then the canonical form of /var/lock/../foo is /run/foo.On Linux it is not always possible to preserve symbolic links while simplifying ... In these cases Path.GetCanonicalPath(string, bool) will resolve the symbolic link regardless of the value of preserveSymbolicLinks.
For the purposes of this API, NTFS Junction Points are considered to be like Linux bind mounts and are not considered to be symbolic links.
Since most of the members of Path do not do I/O there might be some objection to adding GetCanonicalPath to Path. There is at least one existing method that does do I/O — Path.GetTempFileName — so it would not be the first.
GetCanonicalPath handle paths in \\?\ and \\.\?\\?\ path if and only if the input path was in \\?\.\\.\.GetCanonicalPath do if the path does not exist?SymbolicLinkOption with bool.Should I expect to get some feedback so that I can get this out of api-needs-work?
cc: @JeremyKuhne
@carlreinke Sorry, missed this one. Can you please separate the canonical path API into a different issue? It will be hard to track the discussion otherwise. Both topics are of interest and I'll comment in detail once it is broken out.
@JeremyKuhne Done. (Though I split them opposite of how you suggested.)
Thanks for your work here!
We need to call out explicit usage cases to make sure we've got the design right. What problem is this intended to solve?
For this API in particular it is super important we're as detailed as possible. A problematic API, for example, is Path.IsPathRooted(). Many took this to mean "not relative", which it isn't. (I introduced Path.IsPathFullyQualified() to deal with this one.) I fear people will take the results of this to compare equality of inputs- they'll ask "are these two strings the same file?", which this can't answer easily (if at all) for a number of reasons:
One thing we might want to consider is extending GetFullPath() with options. Things like:
FullPathOptions.NormalizeCasingFullPathOptions.FollowSymbolicLinksFullPathOptions.ExpandShortNames (Another backlog item of mine- I want to kill this as default behavior as it is slow and isn't 100% accurate. As an aside- Is it possible to get at 8.3 names from FAT/NTFS mounted on Unix?)Would that address most of the scenarios? It is easier to explain and doesn't infer "identity" like canonicalize does. This does, however, have some problems (which are not unique to using GetFullPath):
How should
GetCanonicalPathhandle paths in\\?\and\\.\?
For \\?\ and \\.\ we can leave them with either header or perhaps always give back '\?'.
Linux: If
/var/lockis a symbolic link to/run/lockthen the canonical form of/var/lock/../foois/run/foo.
Is that true? What resolves an input like that? GetFullPath() would be wrong if this is the case.
Normalizing casing requires walking the input path segments afaik. Expensive, but I don't know of any way around that.
One Windows API to look at to help inform this work is GetFinalPathNameByHandle.
Is that true? What resolves an input like that? GetFullPath() would be wrong if this is the case.
Sure! Fire up WSL and give it a try. :)
user@roxy:~$ head -n 1 /var/lock/../resolvconf/resolv.conf /run/resolvconf/resolv.conf /var/resolvconf/resolv.conf
==> /var/lock/../resolvconf/resolv.conf <==
# This file was automatically generated by WSL. To stop automatic generation of this file, remove this line.
==> /run/resolvconf/resolv.conf <==
# This file was automatically generated by WSL. To stop automatic generation of this file, remove this line.
head: cannot open '/var/resolvconf/resolv.conf' for reading: No such file or directory
user@roxy:~$
(Behavior is the same on Ubuntu, so it's not just a quirk of WSL.)
Sure! Fire up WSL and give it a try. :)
Hmm- I need to set aside some time to fully understand this behavior. Windows claims to have implemented it's symbolic links just like Unix, I can't wrap my head around how this would manifest in Win32. To be honest, though, given the prior need for elevation my usage of symlinks has been pretty basic. :)