diff options
| author | Bryan C. Mills <bcmills@google.com> | 2023-01-04 17:23:46 -0500 |
|---|---|---|
| committer | Gopher Robot <gobot@golang.org> | 2023-01-23 22:46:55 +0000 |
| commit | 3e44b7d07a7b3c6233eb1bf4cf3cb00a0b85adec (patch) | |
| tree | 7d404b995c7cba691854cd313f993e0e38987c50 /src/os/os_windows_test.go | |
| parent | 2423370136d4b1915d06bb1aaacbedaa900bc5c7 (diff) | |
| download | go-3e44b7d07a7b3c6233eb1bf4cf3cb00a0b85adec.tar.xz | |
os: treat non-symlink reparse points as irregular files
Prior to this change (as of CL 143578), our stat function attempted to
resolve all reparse points as if they were symlinks.
This results in an additional call to CreateFile when statting a
symlink file: we use CreateFile once to obtain the reparse tag and
check whether the file is actually a symlink, and if it is we call
CreateFile again without FILE_FLAG_OPEN_REPARSE_POINT to stat the link
target. Fortunately, since symlinks are rare on Windows that overhead
shouldn't be a big deal in practice.
Fixes #42919.
Change-Id: If453930c6e98040cd6525ac4aea60a84498c9579
Reviewed-on: https://go-review.googlesource.com/c/go/+/460595
Reviewed-by: Quim Muntal <quimmuntal@gmail.com>
Run-TryBot: Bryan Mills <bcmills@google.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
Reviewed-by: Alex Brainman <alex.brainman@gmail.com>
Reviewed-by: Cherry Mui <cherryyz@google.com>
Auto-Submit: Bryan Mills <bcmills@google.com>
Diffstat (limited to 'src/os/os_windows_test.go')
| -rw-r--r-- | src/os/os_windows_test.go | 82 |
1 files changed, 80 insertions, 2 deletions
diff --git a/src/os/os_windows_test.go b/src/os/os_windows_test.go index 17b03e9508..1133639105 100644 --- a/src/os/os_windows_test.go +++ b/src/os/os_windows_test.go @@ -14,6 +14,7 @@ import ( "io" "io/fs" "os" + "os/exec" "path/filepath" "reflect" "runtime" @@ -713,11 +714,13 @@ func TestReadStdin(t *testing.T) { func TestStatPagefile(t *testing.T) { t.Parallel() - fi, err := os.Stat(`c:\pagefile.sys`) + const path = `c:\pagefile.sys` + fi, err := os.Stat(path) if err == nil { if fi.Name() == "" { - t.Fatal(`FileInfo of c:\pagefile.sys has empty name`) + t.Fatalf("Stat(%q).Name() is empty", path) } + t.Logf("Stat(%q).Size() = %v", path, fi.Size()) return } if os.IsNotExist(err) { @@ -1290,3 +1293,78 @@ func TestOpenDirTOCTOU(t *testing.T) { t.Error(err) } } + +func TestAppExecLinkStat(t *testing.T) { + // We expect executables installed to %LOCALAPPDATA%\Microsoft\WindowsApps to + // be reparse points with tag IO_REPARSE_TAG_APPEXECLINK. Here we check that + // such reparse points are treated as irregular (but executable) files, not + // broken symlinks. + appdata := os.Getenv("LOCALAPPDATA") + if appdata == "" { + t.Skipf("skipping: LOCALAPPDATA not set") + } + + pythonExeName := "python3.exe" + pythonPath := filepath.Join(appdata, `Microsoft\WindowsApps`, pythonExeName) + + lfi, err := os.Lstat(pythonPath) + if err != nil { + t.Skip("skipping test, because Python 3 is not installed via the Windows App Store on this system; see https://golang.org/issue/42919") + } + + // An APPEXECLINK reparse point is not a symlink, so os.Readlink should return + // a non-nil error for it, and Stat should return results identical to Lstat. + linkName, err := os.Readlink(pythonPath) + if err == nil { + t.Errorf("os.Readlink(%q) = %q, but expected an error\n(should be an APPEXECLINK reparse point, not a symlink)", pythonPath, linkName) + } + + sfi, err := os.Stat(pythonPath) + if err != nil { + t.Fatalf("Stat %s: %v", pythonPath, err) + } + + if lfi.Name() != sfi.Name() { + t.Logf("os.Lstat(%q) = %+v", pythonPath, lfi) + t.Logf("os.Stat(%q) = %+v", pythonPath, sfi) + t.Errorf("files should be same") + } + + if lfi.Name() != pythonExeName { + t.Errorf("Stat %s: got %q, but wanted %q", pythonPath, lfi.Name(), pythonExeName) + } + if m := lfi.Mode(); m&fs.ModeSymlink != 0 { + t.Errorf("%q should be a file, not a link (mode=0x%x)", pythonPath, uint32(m)) + } + if m := lfi.Mode(); m&fs.ModeDir != 0 { + t.Errorf("%q should be a file, not a directory (mode=0x%x)", pythonPath, uint32(m)) + } + if m := lfi.Mode(); m&fs.ModeIrregular == 0 { + // A reparse point is not a regular file, but we don't have a more appropriate + // ModeType bit for it, so it should be marked as irregular. + t.Errorf("%q should not be a regular file (mode=0x%x)", pythonPath, uint32(m)) + } + + if sfi.Name() != pythonExeName { + t.Errorf("Stat %s: got %q, but wanted %q", pythonPath, sfi.Name(), pythonExeName) + } + if m := sfi.Mode(); m&fs.ModeSymlink != 0 { + t.Errorf("%q should be a file, not a link (mode=0x%x)", pythonPath, uint32(m)) + } + if m := sfi.Mode(); m&fs.ModeDir != 0 { + t.Errorf("%q should be a file, not a directory (mode=0x%x)", pythonPath, uint32(m)) + } + if m := sfi.Mode(); m&fs.ModeIrregular == 0 { + // A reparse point is not a regular file, but we don't have a more appropriate + // ModeType bit for it, so it should be marked as irregular. + t.Errorf("%q should not be a regular file (mode=0x%x)", pythonPath, uint32(m)) + } + + p, err := exec.LookPath(pythonPath) + if err != nil { + t.Errorf("exec.LookPath(%q): %v", pythonPath, err) + } + if p != pythonPath { + t.Errorf("exec.LookPath(%q) = %q; want %q", pythonPath, p, pythonPath) + } +} |
