Skip to content
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: Windows: Stat fails on special files like 'con' #34900

Open
lifenjoiner opened this issue Oct 14, 2019 · 7 comments

Comments

@lifenjoiner
Copy link

@lifenjoiner lifenjoiner commented Oct 14, 2019

What version of Go are you using (go version)?

$ go version
go version go1.13.1 windows/386

Does this issue reproduce with the latest release?

Yes.

What operating system and processor architecture are you using (go env)?

go env Output
$ go env
set GOHOSTARCH=386
set GOHOSTOS=windows

What did you do?

try test_Stat-con.go to reproduce the issue

package main

import (
	"os"
)

func main() {
	_, err := os.Stat("con")
	if err != nil {
		panic(err)
	}
}

I have debugged and located that GetFileAttributesEx fails on special files like 'con':

err = syscall.GetFileAttributesEx(namep, syscall.GetFileExInfoStandard, (*byte)(unsafe.Pointer(&fa)))
if err == nil && fa.FileAttributes&syscall.FILE_ATTRIBUTE_REPARSE_POINT == 0 {
// Not a symlink.
fs := &fileStat{
FileAttributes: fa.FileAttributes,
CreationTime: fa.CreationTime,
LastAccessTime: fa.LastAccessTime,
LastWriteTime: fa.LastWriteTime,
FileSizeHigh: fa.FileSizeHigh,
FileSizeLow: fa.FileSizeLow,
}
if err := fs.saveInfoFromPath(name); err != nil {
return nil, err
}
return fs, nil
}

and only 'nul':

if isWindowsNulName(file.name) {
return &devNullStat, nil
}

if isWindowsNulName(file.name) {
return &devNullStat, nil
}

reserved names
CON, PRN, AUX, NUL, COM1, COM2, COM3, COM4, COM5, COM6, COM7, COM8, COM9, LPT1, LPT2, LPT3, LPT4, LPT5, LPT6, LPT7, LPT8, and LPT9:
https://docs.microsoft.com/zh-cn/windows/win32/fileio/naming-a-file#naming-conventions
CONIN$ and CONOUT$:
https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-createfilea#consoles

ref:
https://github.com/gcc-mirror/gcc/blob/bbcdad768752f5cab6727242515bcd15706acbea/gcc/ada/adaint.c#L1606-L1618

What did you expect to see?

We can access these special Windows files, especially con for printing to stdout.

What did you see instead?

I found sometimes we need to use stdout as a file:
https://github.com/DNSCrypt/dnscrypt-proxy/blob/858957ce91015057a1f6c5a9a24f3ea2beb48537/dnscrypt-proxy/example-dnscrypt-proxy.toml#L339-L341
But con doesn't work.
By doing some research, I located the code here as described.
GetFileAttributesEx and CreateFile of Stat failed on the reserved file name con, and only 'nul' is considered as the special case.

@ianlancetaylor ianlancetaylor changed the title Windows: os.Stat fails on special files like 'con' os: Windows: Stat fails on special files like 'con' Oct 14, 2019
@ianlancetaylor ianlancetaylor added this to the Unplanned milestone Oct 14, 2019
@ianlancetaylor

This comment has been minimized.

Copy link
Contributor

@ianlancetaylor ianlancetaylor commented Oct 14, 2019

Not quite sure what to do here if GetFileAttribuesEx fails on CON. What should os.Stat return?

CC @alexbrainman @mattn

@mattn

This comment has been minimized.

Copy link
Member

@mattn mattn commented Oct 15, 2019

#include <stdio.h>
#include <sys/stat.h>

int
main(int argc, char* argv[]) {
  FILE* fp = fopen("CON", "r");
  if (fp == NULL) {
	perror("fopen");
	return 1;
  }
  fclose(fp);

  struct stat st;
  int r = stat("CON", &st);
  printf("result of stat: %d\n", r);

  printf("st_dev = %d\n", st.st_dev);
  printf("st_ino = %d\n", st.st_ino);
  printf("st_mode = %x\n", st.st_mode);
  printf("st_nlink = %d\n", st.st_nlink);
  printf("st_uid = %d\n", st.st_uid);
  printf("st_gid = %d\n", st.st_gid);
  printf("st_rdev = %d\n", st.st_rdev);
  printf("st_size = %lu\n", st.st_size);
  return 0;
}
result of stat: 0
st_dev = 2
st_ino = 0
st_mode = 81b6
st_nlink = 1
st_uid = 0
st_gid = 0
st_rdev = 2
st_size = 0

In this C program, stat(2) return 0 with successful. Go also should return nil for err.

@mattn

This comment has been minimized.

Copy link
Member

@mattn mattn commented Oct 15, 2019

Hopefully this will work.

diff --git a/src/os/stat_windows.go b/src/os/stat_windows.go
index 3e0e0a59ed..a6830c0f6e 100644
--- a/src/os/stat_windows.go
+++ b/src/os/stat_windows.go
@@ -74,6 +74,18 @@ func stat(funcname, name string, createFileAttrs uint32) (FileInfo, error) {
 		}
 		return fs, nil
 	}
+	if err == syscall.ERROR_FILE_NOT_FOUND {
+		if attr, err := syscall.GetFileAttributes(namep); err == nil && attr == syscall.FILE_ATTRIBUTE_ARCHIVE {
+			return &fileStat{
+				name: name,
+				// hopefully this will work for CON, PRN, AUX, CONOUT$ or etc.
+				vol:   0,
+				idxhi: 0,
+				idxlo: 0,
+			}, nil
+		}
+	}
+
 	// GetFileAttributesEx fails with ERROR_SHARING_VIOLATION error for
 	// files, like c:\pagefile.sys. Use FindFirstFile for such files.
 	if err == windows.ERROR_SHARING_VIOLATION {
@gopherbot

This comment has been minimized.

Copy link

@gopherbot gopherbot commented Oct 15, 2019

Change https://golang.org/cl/201157 mentions this issue: os: Fix Stat() with special file on Windows

@lifenjoiner

This comment has been minimized.

Copy link
Author

@lifenjoiner lifenjoiner commented Oct 15, 2019

@lifenjoiner

This comment has been minimized.

Copy link
Author

@lifenjoiner lifenjoiner commented Oct 15, 2019

@mattn
If your solution works well, maybe we can remove:

if isWindowsNulName(file.name) {
return &devNullStat, nil
}

if isWindowsNulName(file.name) {
return &devNullStat, nil
}

and maybe isWindowsNulName can be cleaned:

go/src/os/file.go

Lines 565 to 581 in a38a917

// isWindowsNulName reports whether name is os.DevNull ('NUL') on Windows.
// True is returned if name is 'NUL' whatever the case.
func isWindowsNulName(name string) bool {
if len(name) != 3 {
return false
}
if name[0] != 'n' && name[0] != 'N' {
return false
}
if name[1] != 'u' && name[1] != 'U' {
return false
}
if name[2] != 'l' && name[2] != 'L' {
return false
}
return true
}

WinAPI CreateDirectory should have covered these reserved names:
func Mkdir(path string, mode uint32) (err error) {
pathp, err := UTF16PtrFromString(path)
if err != nil {
return err
}
return CreateDirectory(pathp, nil)
}

confiremed by

package main

import (
	"os"
)

func main() {
	err := os.Mkdir("con", os.ModePerm)
	if err != nil {
		panic(err)
	}
}
@lifenjoiner

This comment has been minimized.

Copy link
Author

@lifenjoiner lifenjoiner commented Oct 15, 2019

append:
on windows, mkdir nul goes silence and returns success, mkdir conin$ and mkdir conin$ have no visible effect and also succeed, while other reserved name trigger errors ...

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
4 participants
You can’t perform that action at this time.