Skip to content

Commit 61c5757

Browse files
committed
os: remove special casing of NUL in Windows file operations
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]>
1 parent be9d78c commit 61c5757

File tree

5 files changed

+57
-94
lines changed

5 files changed

+57
-94
lines changed

src/os/file.go

Lines changed: 0 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -254,9 +254,6 @@ func (f *File) WriteString(s string) (n int, err error) {
254254
// bits (before umask).
255255
// If there is an error, it will be of type *PathError.
256256
func Mkdir(name string, perm FileMode) error {
257-
if runtime.GOOS == "windows" && isWindowsNulName(name) {
258-
return &PathError{Op: "mkdir", Path: name, Err: syscall.ENOTDIR}
259-
}
260257
longName := fixLongPath(name)
261258
e := ignoringEINTR(func() error {
262259
return syscall.Mkdir(longName, syscallMode(perm))
@@ -591,24 +588,6 @@ func (f *File) SyscallConn() (syscall.RawConn, error) {
591588
return newRawConn(f)
592589
}
593590

594-
// isWindowsNulName reports whether name is os.DevNull ('NUL') on Windows.
595-
// True is returned if name is 'NUL' whatever the case.
596-
func isWindowsNulName(name string) bool {
597-
if len(name) != 3 {
598-
return false
599-
}
600-
if name[0] != 'n' && name[0] != 'N' {
601-
return false
602-
}
603-
if name[1] != 'u' && name[1] != 'U' {
604-
return false
605-
}
606-
if name[2] != 'l' && name[2] != 'L' {
607-
return false
608-
}
609-
return true
610-
}
611-
612591
// DirFS returns a file system (an fs.FS) for the tree of files rooted at the directory dir.
613592
//
614593
// Note that DirFS("/prefix") only guarantees that the Open calls it makes to the

src/os/os_test.go

Lines changed: 9 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2012,18 +2012,8 @@ func TestSameFile(t *testing.T) {
20122012
}
20132013
}
20142014

2015-
func testDevNullFileInfo(t *testing.T, statname, devNullName string, fi FileInfo, ignoreCase bool) {
2015+
func testDevNullFileInfo(t *testing.T, statname, devNullName string, fi FileInfo) {
20162016
pre := fmt.Sprintf("%s(%q): ", statname, devNullName)
2017-
name := filepath.Base(devNullName)
2018-
if ignoreCase {
2019-
if strings.ToUpper(fi.Name()) != strings.ToUpper(name) {
2020-
t.Errorf(pre+"wrong file name have %v want %v", fi.Name(), name)
2021-
}
2022-
} else {
2023-
if fi.Name() != name {
2024-
t.Errorf(pre+"wrong file name have %v want %v", fi.Name(), name)
2025-
}
2026-
}
20272017
if fi.Size() != 0 {
20282018
t.Errorf(pre+"wrong file size have %d want 0", fi.Size())
20292019
}
@@ -2038,7 +2028,7 @@ func testDevNullFileInfo(t *testing.T, statname, devNullName string, fi FileInfo
20382028
}
20392029
}
20402030

2041-
func testDevNullFile(t *testing.T, devNullName string, ignoreCase bool) {
2031+
func testDevNullFile(t *testing.T, devNullName string) {
20422032
f, err := Open(devNullName)
20432033
if err != nil {
20442034
t.Fatalf("Open(%s): %v", devNullName, err)
@@ -2049,17 +2039,21 @@ func testDevNullFile(t *testing.T, devNullName string, ignoreCase bool) {
20492039
if err != nil {
20502040
t.Fatalf("Stat(%s): %v", devNullName, err)
20512041
}
2052-
testDevNullFileInfo(t, "f.Stat", devNullName, fi, ignoreCase)
2042+
testDevNullFileInfo(t, "f.Stat", devNullName, fi)
20532043

20542044
fi, err = Stat(devNullName)
20552045
if err != nil {
20562046
t.Fatalf("Stat(%s): %v", devNullName, err)
20572047
}
2058-
testDevNullFileInfo(t, "Stat", devNullName, fi, ignoreCase)
2048+
testDevNullFileInfo(t, "Stat", devNullName, fi)
20592049
}
20602050

20612051
func TestDevNullFile(t *testing.T) {
2062-
testDevNullFile(t, DevNull, false)
2052+
testDevNullFile(t, DevNull)
2053+
if runtime.GOOS == "windows" {
2054+
testDevNullFile(t, "./nul")
2055+
testDevNullFile(t, "//./nul")
2056+
}
20632057
}
20642058

20652059
var testLargeWrite = flag.Bool("large_write", false, "run TestLargeWriteToConsole test that floods console with output")

src/os/os_windows_test.go

Lines changed: 24 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -891,10 +891,6 @@ func TestOneDrive(t *testing.T) {
891891
}
892892

893893
func TestWindowsDevNullFile(t *testing.T) {
894-
testDevNullFile(t, "NUL", true)
895-
testDevNullFile(t, "nul", true)
896-
testDevNullFile(t, "Nul", true)
897-
898894
f1, err := os.Open("NUL")
899895
if err != nil {
900896
t.Fatal(err)
@@ -922,6 +918,30 @@ func TestWindowsDevNullFile(t *testing.T) {
922918
}
923919
}
924920

921+
func TestFileStatNUL(t *testing.T) {
922+
f, err := os.Open("NUL")
923+
if err != nil {
924+
t.Fatal(err)
925+
}
926+
fi, err := f.Stat()
927+
if err != nil {
928+
t.Fatal(err)
929+
}
930+
if got, want := fi.Mode(), os.ModeDevice|os.ModeCharDevice|0666; got != want {
931+
t.Errorf("Open(%q).Stat().Mode() = %v, want %v", "NUL", got, want)
932+
}
933+
}
934+
935+
func TestStatNUL(t *testing.T) {
936+
fi, err := os.Stat("NUL")
937+
if err != nil {
938+
t.Fatal(err)
939+
}
940+
if got, want := fi.Mode(), os.ModeDevice|os.ModeCharDevice|0666; got != want {
941+
t.Errorf("Stat(%q).Mode() = %v, want %v", "NUL", got, want)
942+
}
943+
}
944+
925945
// TestSymlinkCreation verifies that creating a symbolic link
926946
// works on Windows when developer mode is active.
927947
// This is supported starting Windows 10 (1703, v10.0.14972).
@@ -1232,19 +1252,3 @@ func TestWindowsReadlink(t *testing.T) {
12321252
mklink(t, "relfilelink", "file")
12331253
testReadlink(t, "relfilelink", "file")
12341254
}
1235-
1236-
// os.Mkdir(os.DevNull) fails.
1237-
func TestMkdirDevNull(t *testing.T) {
1238-
err := os.Mkdir(os.DevNull, 777)
1239-
oserr, ok := err.(*fs.PathError)
1240-
if !ok {
1241-
t.Fatalf("error (%T) is not *fs.PathError", err)
1242-
}
1243-
errno, ok := oserr.Err.(syscall.Errno)
1244-
if !ok {
1245-
t.Fatalf("error (%T) is not syscall.Errno", oserr)
1246-
}
1247-
if errno != syscall.ENOTDIR {
1248-
t.Fatalf("error %d is not syscall.ENOTDIR", errno)
1249-
}
1250-
}

src/os/stat_windows.go

Lines changed: 24 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -16,40 +16,18 @@ func (file *File) Stat() (FileInfo, error) {
1616
if file == nil {
1717
return nil, ErrInvalid
1818
}
19-
2019
if file.isdir() {
2120
// I don't know any better way to do that for directory
2221
return Stat(file.dirinfo.path)
2322
}
24-
if isWindowsNulName(file.name) {
25-
return &devNullStat, nil
26-
}
27-
28-
ft, err := file.pfd.GetFileType()
29-
if err != nil {
30-
return nil, &PathError{Op: "GetFileType", Path: file.name, Err: err}
31-
}
32-
switch ft {
33-
case syscall.FILE_TYPE_PIPE, syscall.FILE_TYPE_CHAR:
34-
return &fileStat{name: basename(file.name), filetype: ft}, nil
35-
}
36-
37-
fs, err := newFileStatFromGetFileInformationByHandle(file.name, file.pfd.Sysfd)
38-
if err != nil {
39-
return nil, err
40-
}
41-
fs.filetype = ft
42-
return fs, err
23+
return statHandle(file.name, file.pfd.Sysfd)
4324
}
4425

4526
// stat implements both Stat and Lstat of a file.
4627
func stat(funcname, name string, createFileAttrs uint32) (FileInfo, error) {
4728
if len(name) == 0 {
4829
return nil, &PathError{Op: funcname, Path: name, Err: syscall.Errno(syscall.ERROR_PATH_NOT_FOUND)}
4930
}
50-
if isWindowsNulName(name) {
51-
return &devNullStat, nil
52-
}
5331
namep, err := syscall.UTF16PtrFromString(fixLongPath(name))
5432
if err != nil {
5533
return nil, &PathError{Op: funcname, Path: name, Err: err}
@@ -91,14 +69,34 @@ func stat(funcname, name string, createFileAttrs uint32) (FileInfo, error) {
9169
}
9270

9371
// Finally use CreateFile.
94-
h, err := syscall.CreateFile(namep, 0, 0, nil,
95-
syscall.OPEN_EXISTING, createFileAttrs, 0)
72+
h, err := syscall.CreateFile(namep,
73+
syscall.GENERIC_READ,
74+
syscall.FILE_SHARE_READ|syscall.FILE_SHARE_WRITE,
75+
nil,
76+
syscall.OPEN_EXISTING,
77+
createFileAttrs, 0)
9678
if err != nil {
9779
return nil, &PathError{Op: "CreateFile", Path: name, Err: err}
9880
}
9981
defer syscall.CloseHandle(h)
82+
return statHandle(name, h)
83+
}
10084

101-
return newFileStatFromGetFileInformationByHandle(name, h)
85+
func statHandle(name string, h syscall.Handle) (FileInfo, error) {
86+
ft, err := syscall.GetFileType(h)
87+
if err != nil {
88+
return nil, &PathError{Op: "GetFileType", Path: name, Err: err}
89+
}
90+
switch ft {
91+
case syscall.FILE_TYPE_PIPE, syscall.FILE_TYPE_CHAR:
92+
return &fileStat{name: basename(name), filetype: ft}, nil
93+
}
94+
fs, err := newFileStatFromGetFileInformationByHandle(name, h)
95+
if err != nil {
96+
return nil, err
97+
}
98+
fs.filetype = ft
99+
return fs, err
102100
}
103101

104102
// statNolog implements Stat for Windows.

src/os/types_windows.go

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -110,9 +110,6 @@ func (fs *fileStat) Size() int64 {
110110
}
111111

112112
func (fs *fileStat) Mode() (m FileMode) {
113-
if fs == &devNullStat {
114-
return ModeDevice | ModeCharDevice | 0666
115-
}
116113
if fs.FileAttributes&syscall.FILE_ATTRIBUTE_READONLY != 0 {
117114
m |= 0444
118115
} else {
@@ -204,15 +201,6 @@ func (fs *fileStat) saveInfoFromPath(path string) error {
204201
return nil
205202
}
206203

207-
// devNullStat is fileStat structure describing DevNull file ("NUL").
208-
var devNullStat = fileStat{
209-
name: DevNull,
210-
// hopefully this will work for SameFile
211-
vol: 0,
212-
idxhi: 0,
213-
idxlo: 0,
214-
}
215-
216204
func sameFile(fs1, fs2 *fileStat) bool {
217205
e := fs1.loadFileId()
218206
if e != nil {

0 commit comments

Comments
 (0)