"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)
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)
}
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)
}
}
}
- // 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)
return
}
var lsfi2 fs.FileInfo
- base := filepath.Base(path)
for _, fi2 := range fis {
if fi2.Name() == base {
lsfi2 = fi2
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)
}
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) {
+ params := testStatAndLstatParams{
+ isLink: true,
+ lstatCheck: testIsSymlink,
+ }
if isdir {
- testStatAndLstat(t, path, true, testIsDir, testIsSymlink)
+ params.statCheck = testIsDir
} else {
- testStatAndLstat(t, path, true, testIsFile, testIsSymlink)
+ params.statCheck = testIsFile
}
+ testStatAndLstat(t, path, params)
}
func testSymlinkSameFile(t *testing.T, path, link string) {
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)
+ }
+}
// 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