Description
What version of Go are you using (go version
)?
1.14.4
What operating system and processor architecture are you using (go env
)?
Windows
What did you do?
Read the docs.
What did you expect to see?
Documentation to the effect that most applications on all operating systems should not use this API, unless they write complex additional logic to resolve just those links that they are interested in (e.g. searching through one volume only, although this API cannot reliably enable this specific scenario) or responsible for (e.g. system administration software).
What did you see instead?
EvalSymLinks
intending to resolve all links in the path that it receives as argument, without any hint in the documentation that this might introduce issues due to system reconfigurations, amongst others.
What do you propose to fix this?
I propose documentation changes as the best solution for EvalSymLinks
short of deleting this API altogether, which is not possible today since deleting or no-op'ing this API as proposed in #40104 violates go policies.
EvalSymLink
violates what Wikipedia calls the Fundamental Theorem of Software Engineering
EvalSymLinks is not suitable for most applications on any operating system because it unconditionally undoes all indirections for a given path. This is a violation of the principle mentioned which states (in my wording more applicable here):
Any problem in software can be solved by adding another layer of indirection.
EvalSymLinks
always resolves all indirections, even those that might have been introduced to fix operational issues affecting the particular application using this API, or which might have been introduced to solve administrative problems on a system that applications should generally remain unaware of - such as disk full situations.
I quote from Microsoft documentation:
Mounted Folders - Win32 apps | Microsoft Docs
Neither users nor applications need information about the target volume on which a specific file is located.
This API gives software no options to pick and choose to remove only those indirections that they are aware of, are designed to resolve, or have responsibility for.
Practical reasons for most developers not to use EvalSymLinks
Certainly on Windows, the practical usefulness and applicability of this API for most applications is severely limited by the following four facts:
- Most users do not expect to ever see resolved paths
On the contrary, especially those users that might in some situations want to see resolved paths may configure links for the specific purpose of abbreviating paths that they frequently use, in which case resolving links and showing them to the user is a severe and annoying usability issue beyond what it in most situations normally already is. Applications should hence always remember the path they were configured or started with and generally only ever show that to the user. I champion that this point stands firmly on any operating system besides Windows. - Volume access paths and UNC mappings could change at any time
No reboot is necessary to create, change or delete junctions or links. Such events might hence invalidate results ofEvalSymLinks
at any time. No notification is given about such changes, unless the application specifically registers for them. But this is an advanced feature which is nontrivial to implement and test, even though go has/if go would have first-class support for this scenario. - Access paths and mappings could be modified - including deleted - while files are in use that were opened using that access path
The path through which files or directories on a volume are accessible might hence change at a moments notice, even while the application is using files or directories whose access path(s) changed and the application might continue to open handles to the access path(s) it was configured with on another volume, while the application remains unaware, if it does not use theEvalSymLinks
API, or if it takes care not to cache the result ofEvalSymLinks
in any way. - Volume access paths and UNC mappings do not have to be created with a drive letter
And if they do, they do not need to point at the root of the drive with that letter (Z:\
). This might be surprising to any developer that is not intimately aware of Windows internals and will produce issues as soon as they do not consistently use API that is itself properly aware of and tested thoroughly to work on Windows.
Similar expectations and issues may arise about paths that require the use of the \\?\
prefix, which complicates all operations with paths further, including long paths and volume GUID paths which EvalSymLinks
may also introduce in any application on any machine. It is fair to note that EvalSymLinks
is not the only source of such paths and Windows applications should be prepared to deal with them regardless of whether they use EvalSymLinks
- such paths might even be introduced by user input directly, or through the operating system working directory at application start, or be intermittently visible while the system is being reconfigured.
EvalSymLinks
cannot be used to identify volumes in applicable advanced scenario's
Finally, this API by design and as documented can on Windows not identify the volume on which a file or directory resides. Besides resolving UNC mappings, again only in advanced scenario's, this is - for as far as I have been able to identify - about the only situation where the above arguments against the use of EvalSymLinks
may not always apply for all applications.
Paths with a drive letter never identify volumes. Drive letters may in some trivial system configurations happen to correspond to identifiable volumes, but this can not and must never be relied upon. EvalSymLink
definition, documentation and behavior on Windows reinforces this wrong assumption.
That EvalSymLinks
is of very limited use on Windows is strikingly evidenced by the configuration provided to reproduce issue #40176. This configuration is of particular interest, since EvalSymLinks
provides here exactly zero information to the caller. Any path the application requests to be resolved will result in the exact path provided to EvalSymLinks
, since the reparse point resolves to the exact same path as that of the directory that points to it. (before making the path relative and cleaning it, perhaps against statements in the documentation if the fact that an absolute link was resolved might have been lost - and a conforming result might confuse anyone)
In fact, until #40095 is part of go, EvalSymLinks
is completely unable to identify volumes on 64-bit Windows and other Windows installations that use GPT partitioning at all. With this fix, it is only able to do so if the volume happens to have its volume GUID path as its only access path. However now this is merely accidental, due to a fortunate side effect of Windows system architecture.
On Windows systems that do not use GPT partitioning, or on (external) disks that do not use GPT, identifying volumes is simply not possible with a path alone, neither before nor after calling EvalSymLinks
.
How concretely do you propose to fix this?
Change the documentation of EvalSymLinks
in src\path\filepath\path.go
to read:
// EvalSymlinks returns the path name after the evaluation of any symbolic
// links.
// If path is relative the result will be relative to the current directory,
// unless one of the components is an absolute symbolic link.
// EvalSymlinks calls Clean on the result.
// Use of this function is unsuitable for the vast majority of applications.
// This function always resolves all links on path, but links may have
// been created to solve administrative problems of which
// most applications should remain unaware.
// Most applications should only resolve specific links that they
// require to resolve, use the result immediately, forget the result
// and never show the result to the user.
// On Windows, the result is not stable and must not be cached
// or stored in any way, resolving links may introduce complexities that
// the application must be prepared to deal with and the result
// does not identify the volume that a file or directory resides on.
func EvalSymlinks(path string) (string, error) {
return evalSymlinks(path)
}