)
var winsymlink = godebug.New("winsymlink")
+var winreadlinkvolume = godebug.New("winreadlinkvolume")
// For TestRawConnReadWrite.
type syscallDescriptor = syscall.Handle
}
}
-func testReadlink(t *testing.T, path, want string) {
- got, err := os.Readlink(path)
- if err != nil {
- t.Error(err)
- return
- }
- if got != want {
- t.Errorf(`Readlink(%q): got %q, want %q`, path, got, want)
- }
-}
-
-func mklink(t *testing.T, link, target string) {
- output, err := testenv.Command(t, "cmd", "/c", "mklink", link, target).CombinedOutput()
- if err != nil {
- t.Fatalf("failed to run mklink %v %v: %v %q", link, target, err, output)
- }
-}
-
-func mklinkj(t *testing.T, link, target string) {
- output, err := testenv.Command(t, "cmd", "/c", "mklink", "/J", link, target).CombinedOutput()
- if err != nil {
- t.Fatalf("failed to run mklink %v %v: %v %q", link, target, err, output)
- }
-}
-
-func mklinkd(t *testing.T, link, target string) {
- output, err := testenv.Command(t, "cmd", "/c", "mklink", "/D", link, target).CombinedOutput()
+// replaceDriveWithVolumeID returns path with its volume name replaced with
+// the mounted volume ID. E.g. C:\foo -> \\?\Volume{GUID}\foo.
+func replaceDriveWithVolumeID(t *testing.T, path string) string {
+ t.Helper()
+ cmd := testenv.Command(t, "cmd", "/c", "mountvol", filepath.VolumeName(path), "/L")
+ out, err := cmd.CombinedOutput()
if err != nil {
- t.Fatalf("failed to run mklink %v %v: %v %q", link, target, err, output)
+ t.Fatalf("%v: %v\n%s", cmd, err, out)
}
+ vol := strings.Trim(string(out), " \n\r")
+ return filepath.Join(vol, path[len(filepath.VolumeName(path)):])
}
-func TestWindowsReadlink(t *testing.T) {
- tmpdir, err := os.MkdirTemp("", "TestWindowsReadlink")
- if err != nil {
- t.Fatal(err)
- }
- defer os.RemoveAll(tmpdir)
-
- // Make sure tmpdir is not a symlink, otherwise tests will fail.
- tmpdir, err = filepath.EvalSymlinks(tmpdir)
- if err != nil {
- t.Fatal(err)
- }
- chdir(t, tmpdir)
-
- vol := filepath.VolumeName(tmpdir)
- output, err := testenv.Command(t, "cmd", "/c", "mountvol", vol, "/L").CombinedOutput()
- if err != nil {
- t.Fatalf("failed to run mountvol %v /L: %v %q", vol, err, output)
- }
- ntvol := strings.Trim(string(output), " \n\r")
-
- dir := filepath.Join(tmpdir, "dir")
- err = os.MkdirAll(dir, 0777)
- if err != nil {
- t.Fatal(err)
- }
-
- absdirjlink := filepath.Join(tmpdir, "absdirjlink")
- mklinkj(t, absdirjlink, dir)
- testReadlink(t, absdirjlink, dir)
-
- ntdirjlink := filepath.Join(tmpdir, "ntdirjlink")
- mklinkj(t, ntdirjlink, ntvol+absdirjlink[len(filepath.VolumeName(absdirjlink)):])
- testReadlink(t, ntdirjlink, absdirjlink)
-
- ntdirjlinktolink := filepath.Join(tmpdir, "ntdirjlinktolink")
- mklinkj(t, ntdirjlinktolink, ntvol+absdirjlink[len(filepath.VolumeName(absdirjlink)):])
- testReadlink(t, ntdirjlinktolink, absdirjlink)
-
- mklinkj(t, "reldirjlink", "dir")
- testReadlink(t, "reldirjlink", dir) // relative directory junction resolves to absolute path
-
- // Make sure we have sufficient privilege to run mklink command.
- testenv.MustHaveSymlink(t)
-
- absdirlink := filepath.Join(tmpdir, "absdirlink")
- mklinkd(t, absdirlink, dir)
- testReadlink(t, absdirlink, dir)
-
- ntdirlink := filepath.Join(tmpdir, "ntdirlink")
- mklinkd(t, ntdirlink, ntvol+absdirlink[len(filepath.VolumeName(absdirlink)):])
- testReadlink(t, ntdirlink, absdirlink)
-
- mklinkd(t, "reldirlink", "dir")
- testReadlink(t, "reldirlink", "dir")
+func TestReadlink(t *testing.T) {
+ tests := []struct {
+ junction bool
+ dir bool
+ drive bool
+ relative bool
+ }{
+ {junction: true, dir: true, drive: true, relative: false},
+ {junction: true, dir: true, drive: false, relative: false},
+ {junction: true, dir: true, drive: false, relative: true},
+ {junction: false, dir: true, drive: true, relative: false},
+ {junction: false, dir: true, drive: false, relative: false},
+ {junction: false, dir: true, drive: false, relative: true},
+ {junction: false, dir: false, drive: true, relative: false},
+ {junction: false, dir: false, drive: false, relative: false},
+ {junction: false, dir: false, drive: false, relative: true},
+ }
+ for _, tt := range tests {
+ tt := tt
+ var name string
+ if tt.junction {
+ name = "junction"
+ } else {
+ name = "symlink"
+ }
+ if tt.dir {
+ name += "_dir"
+ } else {
+ name += "_file"
+ }
+ if tt.drive {
+ name += "_drive"
+ } else {
+ name += "_volume"
+ }
+ if tt.relative {
+ name += "_relative"
+ } else {
+ name += "_absolute"
+ }
- file := filepath.Join(tmpdir, "file")
- err = os.WriteFile(file, []byte(""), 0666)
- if err != nil {
- t.Fatal(err)
+ t.Run(name, func(t *testing.T) {
+ if !tt.relative {
+ t.Parallel()
+ }
+ // Make sure tmpdir is not a symlink, otherwise tests will fail.
+ tmpdir, err := filepath.EvalSymlinks(t.TempDir())
+ if err != nil {
+ t.Fatal(err)
+ }
+ link := filepath.Join(tmpdir, "link")
+ target := filepath.Join(tmpdir, "target")
+ if tt.dir {
+ if err := os.MkdirAll(target, 0777); err != nil {
+ t.Fatal(err)
+ }
+ } else {
+ if err := os.WriteFile(target, nil, 0666); err != nil {
+ t.Fatal(err)
+ }
+ }
+ var want string
+ if tt.relative {
+ relTarget := filepath.Base(target)
+ if tt.junction {
+ want = target // relative directory junction resolves to absolute path
+ } else {
+ want = relTarget
+ }
+ chdir(t, tmpdir)
+ link = filepath.Base(link)
+ target = relTarget
+ } else {
+ if tt.drive {
+ want = target
+ } else {
+ volTarget := replaceDriveWithVolumeID(t, target)
+ if winreadlinkvolume.Value() == "0" {
+ want = target
+ } else {
+ want = volTarget
+ }
+ target = volTarget
+ }
+ }
+ if tt.junction {
+ cmd := testenv.Command(t, "cmd", "/c", "mklink", "/J", link, target)
+ if out, err := cmd.CombinedOutput(); err != nil {
+ t.Fatalf("%v: %v\n%s", cmd, err, out)
+ }
+ } else {
+ if err := os.Symlink(target, link); err != nil {
+ t.Fatalf("Symlink(%#q, %#q): %v", target, link, err)
+ }
+ }
+ got, err := os.Readlink(link)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if got != want {
+ t.Fatalf("Readlink(%#q) = %#q; want %#q", target, got, want)
+ }
+ })
}
-
- filelink := filepath.Join(tmpdir, "filelink")
- mklink(t, filelink, file)
- testReadlink(t, filelink, file)
-
- linktofilelink := filepath.Join(tmpdir, "linktofilelink")
- mklink(t, linktofilelink, ntvol+filelink[len(filepath.VolumeName(filelink)):])
- testReadlink(t, linktofilelink, filelink)
-
- mklink(t, "relfilelink", "file")
- testReadlink(t, "relfilelink", "file")
}
func TestOpenDirTOCTOU(t *testing.T) {