1// Copyright 2016 The Go Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style 3// license that can be found in the LICENSE file. 4 5package os_test 6 7import ( 8 "fmt" 9 "internal/syscall/windows" 10 "internal/testenv" 11 "os" 12 "path/filepath" 13 "strings" 14 "syscall" 15 "testing" 16) 17 18func TestAddExtendedPrefix(t *testing.T) { 19 // Test addExtendedPrefix instead of fixLongPath so the path manipulation code 20 // is exercised even if long path are supported by the system, else the 21 // function might not be tested at all if/when all test builders support long paths. 22 cwd, err := os.Getwd() 23 if err != nil { 24 t.Fatal("cannot get cwd") 25 } 26 drive := strings.ToLower(filepath.VolumeName(cwd)) 27 cwd = strings.ToLower(cwd[len(drive)+1:]) 28 // Build a very long pathname. Paths in Go are supposed to be arbitrarily long, 29 // so let's make a long path which is comfortably bigger than MAX_PATH on Windows 30 // (256) and thus requires fixLongPath to be correctly interpreted in I/O syscalls. 31 veryLong := "l" + strings.Repeat("o", 500) + "ng" 32 for _, test := range []struct{ in, want string }{ 33 // Test cases use word substitutions: 34 // * "long" is replaced with a very long pathname 35 // * "c:" or "C:" are replaced with the drive of the current directory (preserving case) 36 // * "cwd" is replaced with the current directory 37 38 // Drive Absolute 39 {`C:\long\foo.txt`, `\\?\C:\long\foo.txt`}, 40 {`C:/long/foo.txt`, `\\?\C:\long\foo.txt`}, 41 {`C:\\\long///foo.txt`, `\\?\C:\long\foo.txt`}, 42 {`C:\long\.\foo.txt`, `\\?\C:\long\foo.txt`}, 43 {`C:\long\..\foo.txt`, `\\?\C:\foo.txt`}, 44 {`C:\long\..\..\foo.txt`, `\\?\C:\foo.txt`}, 45 46 // Drive Relative 47 {`C:long\foo.txt`, `\\?\C:\cwd\long\foo.txt`}, 48 {`C:long/foo.txt`, `\\?\C:\cwd\long\foo.txt`}, 49 {`C:long///foo.txt`, `\\?\C:\cwd\long\foo.txt`}, 50 {`C:long\.\foo.txt`, `\\?\C:\cwd\long\foo.txt`}, 51 {`C:long\..\foo.txt`, `\\?\C:\cwd\foo.txt`}, 52 53 // Rooted 54 {`\long\foo.txt`, `\\?\C:\long\foo.txt`}, 55 {`/long/foo.txt`, `\\?\C:\long\foo.txt`}, 56 {`\long///foo.txt`, `\\?\C:\long\foo.txt`}, 57 {`\long\.\foo.txt`, `\\?\C:\long\foo.txt`}, 58 {`\long\..\foo.txt`, `\\?\C:\foo.txt`}, 59 60 // Relative 61 {`long\foo.txt`, `\\?\C:\cwd\long\foo.txt`}, 62 {`long/foo.txt`, `\\?\C:\cwd\long\foo.txt`}, 63 {`long///foo.txt`, `\\?\C:\cwd\long\foo.txt`}, 64 {`long\.\foo.txt`, `\\?\C:\cwd\long\foo.txt`}, 65 {`long\..\foo.txt`, `\\?\C:\cwd\foo.txt`}, 66 {`.\long\foo.txt`, `\\?\C:\cwd\long\foo.txt`}, 67 68 // UNC Absolute 69 {`\\srv\share\long`, `\\?\UNC\srv\share\long`}, 70 {`//srv/share/long`, `\\?\UNC\srv\share\long`}, 71 {`/\srv/share/long`, `\\?\UNC\srv\share\long`}, 72 {`\\srv\share\long\`, `\\?\UNC\srv\share\long\`}, 73 {`\\srv\share\bar\.\long`, `\\?\UNC\srv\share\bar\long`}, 74 {`\\srv\share\bar\..\long`, `\\?\UNC\srv\share\long`}, 75 {`\\srv\share\bar\..\..\long`, `\\?\UNC\srv\share\long`}, // share name is not removed by ".." 76 77 // Local Device 78 {`\\.\C:\long\foo.txt`, `\\.\C:\long\foo.txt`}, 79 {`//./C:/long/foo.txt`, `\\.\C:\long\foo.txt`}, 80 {`/\./C:/long/foo.txt`, `\\.\C:\long\foo.txt`}, 81 {`\\.\C:\long///foo.txt`, `\\.\C:\long\foo.txt`}, 82 {`\\.\C:\long\.\foo.txt`, `\\.\C:\long\foo.txt`}, 83 {`\\.\C:\long\..\foo.txt`, `\\.\C:\foo.txt`}, 84 85 // Misc tests 86 {`C:\short.txt`, `C:\short.txt`}, 87 {`C:\`, `C:\`}, 88 {`C:`, `C:`}, 89 {`\\srv\path`, `\\srv\path`}, 90 {`long.txt`, `\\?\C:\cwd\long.txt`}, 91 {`C:long.txt`, `\\?\C:\cwd\long.txt`}, 92 {`C:\long\.\bar\baz`, `\\?\C:\long\bar\baz`}, 93 {`C:long\.\bar\baz`, `\\?\C:\cwd\long\bar\baz`}, 94 {`C:\long\..\bar\baz`, `\\?\C:\bar\baz`}, 95 {`C:long\..\bar\baz`, `\\?\C:\cwd\bar\baz`}, 96 {`C:\long\foo\\bar\.\baz\\`, `\\?\C:\long\foo\bar\baz\`}, 97 {`C:\long\..`, `\\?\C:\`}, 98 {`C:\.\long\..\.`, `\\?\C:\`}, 99 {`\\?\C:\long\foo.txt`, `\\?\C:\long\foo.txt`}, 100 {`\\?\C:\long/foo.txt`, `\\?\C:\long/foo.txt`}, 101 } { 102 in := strings.ReplaceAll(test.in, "long", veryLong) 103 in = strings.ToLower(in) 104 in = strings.ReplaceAll(in, "c:", drive) 105 106 want := strings.ReplaceAll(test.want, "long", veryLong) 107 want = strings.ToLower(want) 108 want = strings.ReplaceAll(want, "c:", drive) 109 want = strings.ReplaceAll(want, "cwd", cwd) 110 111 got := os.AddExtendedPrefix(in) 112 got = strings.ToLower(got) 113 if got != want { 114 in = strings.ReplaceAll(in, veryLong, "long") 115 got = strings.ReplaceAll(got, veryLong, "long") 116 want = strings.ReplaceAll(want, veryLong, "long") 117 t.Errorf("addExtendedPrefix(%#q) = %#q; want %#q", in, got, want) 118 } 119 } 120} 121 122func TestMkdirAllLongPath(t *testing.T) { 123 t.Parallel() 124 125 tmpDir := t.TempDir() 126 path := tmpDir 127 for i := 0; i < 100; i++ { 128 path += `\another-path-component` 129 } 130 if err := os.MkdirAll(path, 0777); err != nil { 131 t.Fatalf("MkdirAll(%q) failed; %v", path, err) 132 } 133 if err := os.RemoveAll(tmpDir); err != nil { 134 t.Fatalf("RemoveAll(%q) failed; %v", tmpDir, err) 135 } 136} 137 138func TestMkdirAllExtendedLength(t *testing.T) { 139 t.Parallel() 140 tmpDir := t.TempDir() 141 142 const prefix = `\\?\` 143 if len(tmpDir) < 4 || tmpDir[:4] != prefix { 144 fullPath, err := syscall.FullPath(tmpDir) 145 if err != nil { 146 t.Fatalf("FullPath(%q) fails: %v", tmpDir, err) 147 } 148 tmpDir = prefix + fullPath 149 } 150 path := tmpDir + `\dir\` 151 if err := os.MkdirAll(path, 0777); err != nil { 152 t.Fatalf("MkdirAll(%q) failed: %v", path, err) 153 } 154 155 path = path + `.\dir2` 156 if err := os.MkdirAll(path, 0777); err == nil { 157 t.Fatalf("MkdirAll(%q) should have failed, but did not", path) 158 } 159} 160 161func TestOpenRootSlash(t *testing.T) { 162 t.Parallel() 163 164 tests := []string{ 165 `/`, 166 `\`, 167 } 168 169 for _, test := range tests { 170 dir, err := os.Open(test) 171 if err != nil { 172 t.Fatalf("Open(%q) failed: %v", test, err) 173 } 174 dir.Close() 175 } 176} 177 178func testMkdirAllAtRoot(t *testing.T, root string) { 179 // Create a unique-enough directory name in root. 180 base := fmt.Sprintf("%s-%d", t.Name(), os.Getpid()) 181 path := filepath.Join(root, base) 182 if err := os.MkdirAll(path, 0777); err != nil { 183 t.Fatalf("MkdirAll(%q) failed: %v", path, err) 184 } 185 // Clean up 186 if err := os.RemoveAll(path); err != nil { 187 t.Fatal(err) 188 } 189} 190 191func TestMkdirAllExtendedLengthAtRoot(t *testing.T) { 192 if testenv.Builder() == "" { 193 t.Skipf("skipping non-hermetic test outside of Go builders") 194 } 195 196 const prefix = `\\?\` 197 vol := filepath.VolumeName(t.TempDir()) + `\` 198 if len(vol) < 4 || vol[:4] != prefix { 199 vol = prefix + vol 200 } 201 testMkdirAllAtRoot(t, vol) 202} 203 204func TestMkdirAllVolumeNameAtRoot(t *testing.T) { 205 if testenv.Builder() == "" { 206 t.Skipf("skipping non-hermetic test outside of Go builders") 207 } 208 209 vol, err := syscall.UTF16PtrFromString(filepath.VolumeName(t.TempDir()) + `\`) 210 if err != nil { 211 t.Fatal(err) 212 } 213 const maxVolNameLen = 50 214 var buf [maxVolNameLen]uint16 215 err = windows.GetVolumeNameForVolumeMountPoint(vol, &buf[0], maxVolNameLen) 216 if err != nil { 217 t.Fatal(err) 218 } 219 volName := syscall.UTF16ToString(buf[:]) 220 testMkdirAllAtRoot(t, volName) 221} 222 223func TestRemoveAllLongPathRelative(t *testing.T) { 224 // Test that RemoveAll doesn't hang with long relative paths. 225 // See go.dev/issue/36375. 226 tmp := t.TempDir() 227 chdir(t, tmp) 228 dir := filepath.Join(tmp, "foo", "bar", strings.Repeat("a", 150), strings.Repeat("b", 150)) 229 err := os.MkdirAll(dir, 0755) 230 if err != nil { 231 t.Fatal(err) 232 } 233 err = os.RemoveAll("foo") 234 if err != nil { 235 t.Fatal(err) 236 } 237} 238 239func testLongPathAbs(t *testing.T, target string) { 240 t.Helper() 241 testWalkFn := func(path string, info os.FileInfo, err error) error { 242 if err != nil { 243 t.Error(err) 244 } 245 return err 246 } 247 if err := os.MkdirAll(target, 0777); err != nil { 248 t.Fatal(err) 249 } 250 // Test that Walk doesn't fail with long paths. 251 // See go.dev/issue/21782. 252 filepath.Walk(target, testWalkFn) 253 // Test that RemoveAll doesn't hang with long paths. 254 // See go.dev/issue/36375. 255 if err := os.RemoveAll(target); err != nil { 256 t.Error(err) 257 } 258} 259 260func TestLongPathAbs(t *testing.T) { 261 t.Parallel() 262 263 target := t.TempDir() + "\\" + strings.Repeat("a\\", 300) 264 testLongPathAbs(t, target) 265} 266 267func TestLongPathRel(t *testing.T) { 268 chdir(t, t.TempDir()) 269 270 target := strings.Repeat("b\\", 300) 271 testLongPathAbs(t, target) 272} 273 274func BenchmarkAddExtendedPrefix(b *testing.B) { 275 veryLong := `C:\l` + strings.Repeat("o", 248) + "ng" 276 b.ReportAllocs() 277 for i := 0; i < b.N; i++ { 278 os.AddExtendedPrefix(veryLong) 279 } 280} 281