-
Notifications
You must be signed in to change notification settings - Fork 17.7k
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
os: MkdirAll returns error with 'NUL' on Windows #24556
Comments
/cc @alexbrainman |
These files can be created, but they will break many programs that try to open the directories containing such filenames. In my opinion, the error is the correct thing to do and Windows is wrong. |
@as Given that this is the os pkg, I would expect it to mirror the platform native behaviour, if I expect an error with other reserved names (such as CON), but not NUL, which is the subject here. |
@djdv I don't agree that the behavior of
|
@as go/src/syscall/syscall_windows.go Lines 410 to 416 in 360c191
The Windows API succeeds here. #include <windows.h>
int main(void) {
return CreateDirectory(L"NUL", NULL);
} https://msdn.microsoft.com/en-us/library/windows/desktop/aa363855(v=vs.85).aspx
I'm receiving a return of 1 (call succeeded) and a GetLastError of 0 (Success). |
I tested all the alleged device files on windows. What I don't understand is why this behavior is necessary.
It seems more sane to run What problem would this change solve for you that justifies adding the proposed behavior? |
I'm not proposing that Go handle all Windows device files, only
The intention of the os pkg seems to be to interface with the OS, this behaviour would fall in line with the OS API convention. I fail to see the purpose of defining
I see no problem here, as it is now, the full path can be stat'ed and recursively created via
It does not, Lines 20 to 28 in dafca7d
It's true that NUL is not an existing directory, but that does not mean calls to create it (or its parents) will fail, the error is artificially inserted by Go. In actuality, system calls to CreateDir will not fail when supplied with NUL as a target.
Since there is (to my knowledge) no Windows system call to create directories recursively, that seems to be left up to implementation. I think a sane expectation for Go's The big issue here is that these are valid Windows paths that should NOT be causing an error, but currently are. The use cases for |
Sorry, this isn't true. Creating a directory isn't the same as writing to an open file. If I run
Which is just a footgun, because after a successful call to |
I did not mean to imply I feel like
diverges from the actual issue, the footgun in question doesn't relate to It's moot either way since it's an existing issue outPath := `NUL`
err := os.Mkdir(outPath, 0755`)
if err == nil {
os.Create(`outPath\file.ext`) // this will hit
} This can easily be rectified by taking advantage of the error returned by In addition the error returned currently makes more sense in the
It makes much more sense to me that What's important is not how No matter what decision is made, a special case has to be put in place to allow or prevent this, either in If I were to make a proposal I would say that MkdirAll should not fail early with My reason for supporting that behaviour is that it's consistent with the underlying system which seems to fit the purpose of pkg os, to act as an interface between Go and the OS. The current behaviour is purely coincidental, there is no guard against null-devices specifically, it just happens to fail on As for implementation on either front, it may be easiest to utilize Allowing the system to decide the return value, could be handled with func MkdirAll(path string, perm FileMode) error {
// Fast path: if we can tell whether path is a directory or file, stop with success or error.
dir, err := Stat(path)
if err == nil {
if dir.IsDir() {
return nil
}
if dir.Mode() != ModeCharDevice || pathBase != DevNull {
return &PathError{"mkdir", path, syscall.ENOTDIR}
}
//... continue on to recursive portion
} Denying this would still require the same checks to be in place, but inverted, for both i.e. if dir.Mode() == ModeCharDevice && pathBase == DevNull {
return PathErrDevnull
} |
I was surprised to see that os.Mkdir("NUL") indeed succeeds, but it returns whatever Windows CreateDirectory returns. That call does not create NUL directory - it looks like it does nothing. I don't care if os.Mkdir("NUL") succeeds or fails. But, I suppose, Go should be consistent and have os.Mkdir and os.MkdirAll both succeed or both fail with nice error message. I cannot decide which, so leaving it for others. Alex |
@alexbrainman
Agreed.
Who has experience/authority of Windows issues here? I'm in favour of succeeding. |
If you use UNC path to create directory, you'll get it. Personally, I think this Windows behavior is NOT correct. But Windows API returns it as normal, we have to obey it. Impersonate the error code or add the workaround for the path are not proper way. os package is not a package to hide differences between OSs. The aim is to provide a consistent API. What Go can do is only providing way to use UNC paths and non-UNC paths both on Windows. package main
import (
"fmt"
"log"
"os"
"path/filepath"
)
func main() {
wd, err := os.Getwd()
if err != nil {
log.Fatalf("Getwd: %v", err)
}
dir := `\\.\` + filepath.Join(wd, "NUL")
err = os.MkdirAll(dir, 0755)
if err != nil {
log.Fatalf("MkdirAll: %v", err)
}
fi, err := os.Stat(dir)
if err != nil {
log.Fatalf("Stat: %v", err)
}
fmt.Println(fi.IsDir()) // true
err = os.Remove(dir)
if err != nil {
log.Fatalf("Remove: %v", err)
}
dir = `\\.\` + filepath.Join(wd, "NUL", "CON", "PRT")
err = os.MkdirAll(dir, 0755)
if err != nil {
log.Fatalf("MkdirAll: %v", err)
}
f, err := os.Create(`\\.\` + filepath.Join(wd, "NUL", "CON", "PRT", "Hi-Gopher.txt"))
if err != nil {
log.Fatalf("Create: %v", err)
}
_, err = f.Write([]byte("I love golang!"))
if err != nil {
log.Fatalf("Write: %v", err)
}
err = f.Close()
if err != nil {
log.Fatalf("Close: %v", err)
}
err = os.RemoveAll(`\\.\` + filepath.Join(wd, "NUL"))
if err != nil {
log.Fatalf("RemoveAll: %v", err)
}
} |
Under what practical conditions would you expect
|
We are not discussing UNC paths, this issue is only about "NUL" file. @djdv @mattn @as and anyone else who cares, should we
or
? And why? Thank you. Alex |
So we should remove this part? diff --git a/src/os/path.go b/src/os/path.go
index 5c5350670d..81033ea5e0 100644
--- a/src/os/path.go
+++ b/src/os/path.go
@@ -18,14 +18,7 @@ import (
// If path is already a directory, MkdirAll does nothing
// and returns nil.
func MkdirAll(path string, perm FileMode) error {
- // Fast path: if we can tell whether path is a directory or file, stop with success or error.
- dir, err := Stat(path)
- if err == nil {
- if dir.IsDir() {
- return nil
- }
- return &PathError{"mkdir", path, syscall.ENOTDIR}
- }
+ var err error
// Slow path: make sure parent exists and then call Mkdir for path.
i := len(path)
FYI MkdirAll seems having this part from 66f5e89 (2009). |
We cannot remove that part. That part makes MkdirAll faster. Alex |
I posted this above, I modified the code a bit.
func MkdirAll(path string, perm FileMode) error {
// Fast path: if we can tell whether path is a directory or file, stop with success or error.
dir, err := Stat(path)
if err == nil {
if dir.IsDir() {
return nil
}
// if not a char device, if not devnul, return error, otherwise proceed
if dir.Mode()&ModeCharDevice == 0 || !isDevNul(pathBase) {
return &PathError{"mkdir", path, syscall.ENOTDIR}
}
//... continue on to recursive portion
} I'm not sure if this is a good condition to branch off of but I think adding some condition here may be the solution. As long as that condition is only in the Windows implementation. |
Why are we discussing solution? We have not decided what to do yet. See my question here #24556 (comment) Alex |
I believe in either case we'll need a similar approach. This was meant more as a response to @mattn's proposal. Edit: make os.Mkdir("NUL") fail: make os.MkdirAll("NUL") succeed: If I've misinterpreted the responses/opinions, please correct me. |
I don't know if we need to do anything here. But if we do something, I think the only correct change would be to make |
It is not the https://docs.microsoft.com/en-us/windows/desktop/api/fileapi/nf-fileapi-createdirectoryw I built this little test diff --git a/src/os/path_test.go b/src/os/path_test.go
index 6cb25bcaa7..69fafc8723 100644
--- a/src/os/path_test.go
+++ b/src/os/path_test.go
@@ -126,3 +126,10 @@ func TestMkdirAllAtSlash(t *testing.T) {
}
RemoveAll("/_go_os_test")
}
+
+func TestMkdirDevNull(t *testing.T) {
+ err := MkdirAll(DevNull, 0777)
+ t.Errorf("os.MkdirAll(os.DevNull) error: %q %#v", err, err)
+ err = Mkdir(DevNull, 0777)
+ t.Errorf("os.Mkdir(os.DevNull) error: %q %#v", err, err)
+} and it outputs this on Linux
and this on Windows
Err:0x3 is ERROR_PATH_NOT_FOUND. Perhaps os.Mkdir(os.DevNull) should also return ERROR_PATH_NOT_FOUND. ERROR_PATH_NOT_FOUND is even mentioned in https://docs.microsoft.com/en-us/windows/desktop/api/fileapi/nf-fileapi-createdirectoryw I do not see downsides of changing os.Mkdir(os.DevNull) to return ERROR_PATH_NOT_FOUND. I will do this change unless others object. Alex |
Change https://golang.org/cl/186139 mentions this issue: |
This fix aligns the behavior of Mkdir and MkdirAll when os.DevNull is the argument:
The returned error message:
|
Test added. Fixes golang#24556 Change-Id: I4d1cd4513142edeea1a983fbfde46c2fccecab2a Reviewed-on: https://go-review.googlesource.com/c/go/+/186139 Run-TryBot: Alex Brainman <alex.brainman@gmail.com> TryBot-Result: Gobot Gobot <gobot@golang.org> Reviewed-by: Alex Brainman <alex.brainman@gmail.com>
Test added. Fixes golang#24556 Change-Id: I4d1cd4513142edeea1a983fbfde46c2fccecab2a Reviewed-on: https://go-review.googlesource.com/c/go/+/186139 Run-TryBot: Alex Brainman <alex.brainman@gmail.com> TryBot-Result: Gobot Gobot <gobot@golang.org> Reviewed-by: Alex Brainman <alex.brainman@gmail.com>
What version of Go are you using (
go version
)?go1.10 windows/amd64,
go version devel +b63b0f2b75 Tue Mar 27 08:16:50 2018 +0000 windows/amd64
Does this issue reproduce with the latest release?
Yes
What operating system and processor architecture are you using (
go env
)?GOOS=windows GOARCH=amd64
What did you do?
Tried to use os.MkdirAll on "NUL":
os.MkdirAll("NUL", 0777)
What did you expect to see?
no error returned, as is the case with:
os.Mkdir("NUL", 0777)
.What did you see instead?
An
os.PathError
:The system cannot find the path specified.
Additional
Similar to #24482
https://golang.org/src/os/path.go?s=705:716#L24
Is the problem area, since
NUL
is not considered a directory, MkDirAll returns a PathError.In the Windows command interpreter
md NUL
andmd intermediate\paths\NUL
create no paths and return no errors.However
md NUL\path\beyond
will return an error.Attn: @alexbrainman
The text was updated successfully, but these errors were encountered: