mirror of https://github.com/golang/go.git
os: support Stat and LStat for CON device on Windows
\\.\con and CON need to be opened with GENERIC_READ access, else CreateFile will fail with ERROR_INVALID_PARAMETER. Special-case ERROR_INVALID_PARAMETER in os.[L]Stat so it retries with GENERIC_READ access. Fixes #34900. Change-Id: I5010e736d0189c8ada4fc0eca98d71a438c41426 Reviewed-on: https://go-review.googlesource.com/c/go/+/560755 LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com> Reviewed-by: Bryan Mills <bcmills@google.com> Reviewed-by: David Chase <drchase@google.com>
This commit is contained in:
parent
e55bf08d98
commit
28b8851671
|
|
@ -9,18 +9,25 @@ import (
|
|||
"io/fs"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"testing"
|
||||
)
|
||||
|
||||
type testStatAndLstatParams struct {
|
||||
isLink bool
|
||||
statCheck func(*testing.T, string, fs.FileInfo)
|
||||
lstatCheck func(*testing.T, string, fs.FileInfo)
|
||||
}
|
||||
|
||||
// testStatAndLstat verifies that all os.Stat, os.Lstat os.File.Stat and os.Readdir work.
|
||||
func testStatAndLstat(t *testing.T, path string, isLink bool, statCheck, lstatCheck func(*testing.T, string, fs.FileInfo)) {
|
||||
func testStatAndLstat(t *testing.T, path string, params testStatAndLstatParams) {
|
||||
// test os.Stat
|
||||
sfi, err := os.Stat(path)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
statCheck(t, path, sfi)
|
||||
params.statCheck(t, path, sfi)
|
||||
|
||||
// test os.Lstat
|
||||
lsfi, err := os.Lstat(path)
|
||||
|
|
@ -28,9 +35,9 @@ func testStatAndLstat(t *testing.T, path string, isLink bool, statCheck, lstatCh
|
|||
t.Error(err)
|
||||
return
|
||||
}
|
||||
lstatCheck(t, path, lsfi)
|
||||
params.lstatCheck(t, path, lsfi)
|
||||
|
||||
if isLink {
|
||||
if params.isLink {
|
||||
if os.SameFile(sfi, lsfi) {
|
||||
t.Errorf("stat and lstat of %q should not be the same", path)
|
||||
}
|
||||
|
|
@ -53,13 +60,13 @@ func testStatAndLstat(t *testing.T, path string, isLink bool, statCheck, lstatCh
|
|||
t.Error(err)
|
||||
return
|
||||
}
|
||||
statCheck(t, path, sfi2)
|
||||
params.statCheck(t, path, sfi2)
|
||||
|
||||
if !os.SameFile(sfi, sfi2) {
|
||||
t.Errorf("stat of open %q file and stat of %q should be the same", path, path)
|
||||
}
|
||||
|
||||
if isLink {
|
||||
if params.isLink {
|
||||
if os.SameFile(sfi2, lsfi) {
|
||||
t.Errorf("stat of opened %q file and lstat of %q should not be the same", path, path)
|
||||
}
|
||||
|
|
@ -69,12 +76,13 @@ func testStatAndLstat(t *testing.T, path string, isLink bool, statCheck, lstatCh
|
|||
}
|
||||
}
|
||||
|
||||
// test fs.FileInfo returned by os.Readdir
|
||||
if len(path) > 0 && os.IsPathSeparator(path[len(path)-1]) {
|
||||
// skip os.Readdir test of directories with slash at the end
|
||||
parentdir, base := filepath.Split(path)
|
||||
if parentdir == "" || base == "" {
|
||||
// skip os.Readdir test of files without directory or file name component,
|
||||
// such as directories with slash at the end or Windows device names.
|
||||
return
|
||||
}
|
||||
parentdir := filepath.Dir(path)
|
||||
|
||||
parent, err := os.Open(parentdir)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
|
|
@ -88,7 +96,6 @@ func testStatAndLstat(t *testing.T, path string, isLink bool, statCheck, lstatCh
|
|||
return
|
||||
}
|
||||
var lsfi2 fs.FileInfo
|
||||
base := filepath.Base(path)
|
||||
for _, fi2 := range fis {
|
||||
if fi2.Name() == base {
|
||||
lsfi2 = fi2
|
||||
|
|
@ -99,7 +106,7 @@ func testStatAndLstat(t *testing.T, path string, isLink bool, statCheck, lstatCh
|
|||
t.Errorf("failed to find %q in its parent", path)
|
||||
return
|
||||
}
|
||||
lstatCheck(t, path, lsfi2)
|
||||
params.lstatCheck(t, path, lsfi2)
|
||||
|
||||
if !os.SameFile(lsfi, lsfi2) {
|
||||
t.Errorf("lstat of %q file in %q directory and %q should be the same", lsfi2.Name(), parentdir, path)
|
||||
|
|
@ -140,19 +147,34 @@ func testIsFile(t *testing.T, path string, fi fs.FileInfo) {
|
|||
}
|
||||
|
||||
func testDirStats(t *testing.T, path string) {
|
||||
testStatAndLstat(t, path, false, testIsDir, testIsDir)
|
||||
params := testStatAndLstatParams{
|
||||
isLink: false,
|
||||
statCheck: testIsDir,
|
||||
lstatCheck: testIsDir,
|
||||
}
|
||||
testStatAndLstat(t, path, params)
|
||||
}
|
||||
|
||||
func testFileStats(t *testing.T, path string) {
|
||||
testStatAndLstat(t, path, false, testIsFile, testIsFile)
|
||||
params := testStatAndLstatParams{
|
||||
isLink: false,
|
||||
statCheck: testIsFile,
|
||||
lstatCheck: testIsFile,
|
||||
}
|
||||
testStatAndLstat(t, path, params)
|
||||
}
|
||||
|
||||
func testSymlinkStats(t *testing.T, path string, isdir bool) {
|
||||
if isdir {
|
||||
testStatAndLstat(t, path, true, testIsDir, testIsSymlink)
|
||||
} else {
|
||||
testStatAndLstat(t, path, true, testIsFile, testIsSymlink)
|
||||
params := testStatAndLstatParams{
|
||||
isLink: true,
|
||||
lstatCheck: testIsSymlink,
|
||||
}
|
||||
if isdir {
|
||||
params.statCheck = testIsDir
|
||||
} else {
|
||||
params.statCheck = testIsFile
|
||||
}
|
||||
testStatAndLstat(t, path, params)
|
||||
}
|
||||
|
||||
func testSymlinkSameFile(t *testing.T, path, link string) {
|
||||
|
|
@ -294,3 +316,24 @@ func TestSymlinkWithTrailingSlash(t *testing.T) {
|
|||
t.Errorf("os.Stat(%q) and os.Stat(%q) are not the same file", dir, dirlinkWithSlash)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStatConsole(t *testing.T) {
|
||||
if runtime.GOOS != "windows" {
|
||||
t.Skip("skipping on non-Windows")
|
||||
}
|
||||
t.Parallel()
|
||||
consoleNames := []string{
|
||||
"CONIN$",
|
||||
"CONOUT$",
|
||||
"CON",
|
||||
}
|
||||
for _, name := range consoleNames {
|
||||
params := testStatAndLstatParams{
|
||||
isLink: false,
|
||||
statCheck: testIsFile,
|
||||
lstatCheck: testIsFile,
|
||||
}
|
||||
testStatAndLstat(t, name, params)
|
||||
testStatAndLstat(t, `\\.\`+name, params)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -74,7 +74,16 @@ func stat(funcname, name string, followSurrogates bool) (FileInfo, error) {
|
|||
// save information about the link target.
|
||||
// Set FILE_FLAG_BACKUP_SEMANTICS so that CreateFile will create the handle
|
||||
// even if name refers to a directory.
|
||||
h, err := syscall.CreateFile(namep, 0, 0, nil, syscall.OPEN_EXISTING, syscall.FILE_FLAG_BACKUP_SEMANTICS|syscall.FILE_FLAG_OPEN_REPARSE_POINT, 0)
|
||||
var flags uint32 = syscall.FILE_FLAG_BACKUP_SEMANTICS | syscall.FILE_FLAG_OPEN_REPARSE_POINT
|
||||
h, err := syscall.CreateFile(namep, 0, 0, nil, syscall.OPEN_EXISTING, flags, 0)
|
||||
|
||||
if err == windows.ERROR_INVALID_PARAMETER {
|
||||
// Console handles, like "\\.\con", require generic read access. See
|
||||
// https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-createfilew#consoles.
|
||||
// We haven't set it previously because it is normally not required
|
||||
// to read attributes and some files may not allow it.
|
||||
h, err = syscall.CreateFile(namep, syscall.GENERIC_READ, 0, nil, syscall.OPEN_EXISTING, flags, 0)
|
||||
}
|
||||
if err != nil {
|
||||
// Since CreateFile failed, we can't determine whether name refers to a
|
||||
// name surrogate, or some other kind of reparse point. Since we can't return a
|
||||
|
|
|
|||
Loading…
Reference in New Issue