-
Notifications
You must be signed in to change notification settings - Fork 17.9k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
path/filepath: IsAbs
inconsistently handles Windows reserved filenames
#56217
Comments
CC @golang/windows |
For reference, the list of reserved filenames is here: However, it's a little fuzzy on the behavior for names with extensions (emphasis mine)
That suggests that maybe a name with an extension doesn't actually end up referring to the global device? |
Empirically, |
As an additional data point, according to .NET docs it seems that |
I don't still understand what is wrong here. As far as i can see, current implementation handle NUL as null device but not handle NUL.txt as null device. os.Create( |
using System;
using System.IO;
class Program
{
static void Main(string[] args)
{
Console.WriteLine(Path.GetFullPath("CON"));
Console.WriteLine(Path.GetFullPath("CON.txt"));
Console.WriteLine(Path.GetFullPath(@"COM1.TXT\file1.txt"));
}
}
|
What does "works fine for me" mean? I can't create a directory named |
Windows 11 Home 21H2 Build 22000.1098 |
So, I believe that a possible course of action could be any of the following three.
@alexbrainman What do you think? |
I suspect that the behavior here may depend on the filesystem configuration, not just the Windows version. Would it make sense to |
It'd be nice to know exactly when and why this changed. The conservative approach would be for |
My inclination is to take the most conservative approach: Is there a reason why we shouldn't do this? @qmuntal for another possible opinion on this. |
Given the comments in this issue, it is clear that there is no general rule to decide if those cases will be handled as device paths, so we should let Windows decide. We can do so by calling GetFullPathName and check if the returned path follows the pattern |
Right; pretend I said
This sounds like the right approach to me. |
Discovered a number of other exciting bugs and possible bugs while fixing this, filed #56336. |
Change https://go.dev/cl/444279 mentions this issue: |
I've done some more research and it seems that reserved names in path segments (e.g. |
@neild I'm moving the discussion from CL 444279 back to this issue, so it has more visibility. Hope you don't mind. Quoting @neild:
These are the benchmark @neild did to bench CL 444279: func BenchmarkIsAbsSafe(b *testing.B) {
for i := 0; i < b.N; i++ {
filepath.IsAbs("/a/b/c/d/e/f/g")
}
}
func BenchmarkIsAbsNul(b *testing.B) {
for i := 0; i < b.N; i++ {
filepath.IsAbs("nul")
}
}
func BenchmarkIsAbsLongNul(b *testing.B) {
for i := 0; i < b.N; i++ {
filepath.IsAbs("/a/b/c/d/e/f/g/nul")
}
} name old time/op new time/op delta
IsAbsSafe-8 6.97ns ± 3% 68.20ns ± 1% +877.93% (p=0.000 n=10+9)
IsAbsNul-8 6.55ns ± 2% 674.15ns ± 9% +10185.61% (p=0.000 n=10+10)
IsAbsLongNul-8 7.01ns ± 2% 776.82ns ± 5% +10976.54% (p=0.000 n=10+10) I agree with @neild that Reserved names are a legacy thing from 1970 (see this post to understand why they exist) and using them in path segments or with file extensions is not recommended nor well supported. See, for example, this comment from a .Net team member:
My opinion is that It is probably too late for doing that, so my next option would be to state that we don't fully support legacy reserved names. I'm worried that we might never get to fully supporting them, it being a moving target (see #56217 (comment)), and we will lose performance on the road. Merge CL 444279 is still ok for me, but I wanted to give more context so we take a conscious decision. |
I'm not convinced by the argument from efficiency. If However, I dug further into the history of how we came about the current behavior, and I no longer believe
This change was made to fix #28035. The root cause of #28035 is this code in
This code is incorrect on Windows. It was incorrect before https://go.dev/cl/145220, and it is still incorrect now. The first issue in this code is that it assumes an absolute base path may be joined to a relative path with On Unix systems, all paths are either absolute or relative to the current directory. An absolute base path may be joined to a relative path to produce an absolute path, and this code is correct. On Windows systems, however, relative paths come in a variety of flavors: relative to the current directory ( The second issue in this code is that it assumes there is a single name for The third issue is that this code assumes the name of the null device in While https://go.dev/cl/145220 attempts to correct the above code by adding So while there may be some argument for treating reserved device names as absolute paths, fixing #28035 is not that argument, because the root cause of that issue is not actually fixed by this change. I think we should therefore consider rolling back https://go.dev/cl/145220. Windows has a definition of what paths are absolute (https://learn.microsoft.com/en-us/dotnet/standard/io/file-path-formats). On Windows, The argument against rolling back https://go.dev/cl/145220 is that this change is four years old at this point, and some users may be relying on This does leave the question of what the correct way is to write code like this in a platform-agnostic fashion:
On Windows using .NET, I believe the Perhaps we need a new function for this operation. I'm not sure what we would call it.
To summarize the above:
|
Great summary. Some thoughts:
Agree that
This is what
Following this description, Given all the issues stated by @neild, would it make sense to do something similar? I mean, refactor |
|
Thank you @neild for taking time to investigate this issue.
What do you propose we do? I am happy to revert https://go.dev/cl/145220 . Is that all you plan to do?
SGTM.
I see no problem adding any new APIs to Alex |
Change https://go.dev/cl/448897 mentions this issue: |
Change https://go.dev/cl/448898 mentions this issue: |
Change https://go.dev/cl/448896 mentions this issue: |
Change https://go.dev/cl/449117 mentions this issue: |
An upcoming change to the filepath package to make IsAbs("NUL")==false on Windows will cause this test to fail, since it sets GOPATH=NUL and GOPATH must be an absolute path. Set GOPATH to the name of a text file instead. (The intent is that GOPATH be set to a path that is not writable.) For #56217. Change-Id: I18e645fe11547d02d1a2e0e580085e6348c4009a Reviewed-on: https://go-review.googlesource.com/c/go/+/448896 Reviewed-by: Bryan Mills <[email protected]> Run-TryBot: Damien Neil <[email protected]> TryBot-Result: Gopher Robot <[email protected]>
The "go test" and "go build" commands have special-case behavior when passed "-o /dev/null". These checks are case-sensitive and assume that os.DevNull is an absolute path. Windows filesystems are case-insensitive and os.DevNull is NUL, which is not an absolute path. CL 145220 changed filepath.IsAbs to report "NUL" as absolute to work around this issue; that change is being rolled back and a better fix here is to compare the value of -o against os.DevNull before attempting to merge it with a base path. Make that fix. On Windows, accept any capitilization of "NUL" as the null device. This change doesn't cover every possible name for the null device, such as "-o //./NUL", but this test is for efficiency rather than correctness. Accepting just the most common name is fine. For #56217. Change-Id: I60b59b671789fc456074d3c8bc755a74ea8d5765 Reviewed-on: https://go-review.googlesource.com/c/go/+/449117 TryBot-Result: Gopher Robot <[email protected]> Run-TryBot: Damien Neil <[email protected]> Reviewed-by: Bryan Mills <[email protected]>
Some file operations, notably Stat and Mkdir, special cased their behavior when operating on a file named "NUL" (case-insensitive). This check failed to account for the many other names of the NUL device, as well as other non-NUL device files: "./nul", "//./nul", "nul.txt" (on some Windows versions), "con", etc. Remove the special case. os.Mkdir("NUL") now returns no error. This is consonant with the operating system's behavior: CreateDirectory("NUL") succeeds, as does "MKDIR NUL" on the command line. os.Stat("NUL") now follows the existing path for FILE_TYPE_CHAR devices, returning a FileInfo which correctly reports the file as being a character device. os.Stat and os.File.Stat have common elements of their logic unified. For #24482. For #24556. For #56217. Change-Id: I7e70f45901127c9961166dd6dbfe0c4a10b4ab64 Reviewed-on: https://go-review.googlesource.com/c/go/+/448897 Run-TryBot: Damien Neil <[email protected]> Reviewed-by: Bryan Mills <[email protected]> TryBot-Result: Gopher Robot <[email protected]> Reviewed-by: Quim Muntal <[email protected]>
This reverts commit d154ef6. This change made IsAbs return true for certain reserved filenames, but does not consistently detect reserved names. For example, "./COM1", "//./COM1", and (on some Windows versions) "COM1.txt" all refer to the COM1 device, but IsAbs detects none of them. Since NUL is not an absolute path, do not attempt to detect it or other device paths in IsAbs. See #56217 for more discussion of IsAbs and device paths. For #56217. Change-Id: If4bf81c7e1a2e8842206c7c5268555102140dae8 Reviewed-on: https://go-review.googlesource.com/c/go/+/448898 Reviewed-by: Michael Knyszek <[email protected]> Run-TryBot: Damien Neil <[email protected]> Reviewed-by: Quim Muntal <[email protected]> TryBot-Result: Gopher Robot <[email protected]> Reviewed-by: Rob Pike <[email protected]>
Change https://go.dev/cl/450296 mentions this issue: |
CL 448897 changed os.Stat to request GENERIC_READ access when using CreateFile to examine a file. This is unnecessary; access flags of 0 will permit examining file metadata even if the file isn't readable. Revert to the old behavior here. For #56217 Change-Id: I09220b3bbee304bd89f4a94ec9b0af42042b7773 Reviewed-on: https://go-review.googlesource.com/c/go/+/450296 TryBot-Result: Gopher Robot <[email protected]> Reviewed-by: Bryan Mills <[email protected]> Run-TryBot: Bryan Mills <[email protected]> Run-TryBot: Quim Muntal <[email protected]>
On Windows,
filepath.IsAbs
returns true for reserved filenames likeCON
,NUL
, orLPT1
: https://go.googlesource.com/go/+/refs/tags/go1.19.2/src/path/filepath/path_windows.go#16This behavior is not documented, but seems reasonable in that these filenames reference objects outside of the current directory. The check for these filenames was added in 2018 in https://go.dev/cl/145220; see that CL for some more history on how we got to the current state.
However, this check is not sufficient.
The filename
NUL
is the null device. So isNUL.txt
. So isC:\Path\To\NUL.a.b.c
.If
filepath.IsAbs("NUL")
returns true, thenIsAbs
should also return true for any path that contains a reserved name as a directory component (including reserved names with extensions).This also affects the special-case handling of
NUL
added toos.Mkdir
as a resolution for #24556.The text was updated successfully, but these errors were encountered: