1 // Copyright 2013 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 5 package filepath_test 6 7 import ( 8 "flag" 9 "fmt" 10 "io/ioutil" 11 "os" 12 "os/exec" 13 "path/filepath" 14 "reflect" 15 "runtime/debug" 16 "strings" 17 "testing" 18 ) 19 20 func TestWinSplitListTestsAreValid(t *testing.T) { 21 comspec := os.Getenv("ComSpec") 22 if comspec == "" { 23 t.Fatal("%ComSpec% must be set") 24 } 25 26 for ti, tt := range winsplitlisttests { 27 testWinSplitListTestIsValid(t, ti, tt, comspec) 28 } 29 } 30 31 func testWinSplitListTestIsValid(t *testing.T, ti int, tt SplitListTest, 32 comspec string) { 33 34 const ( 35 cmdfile = `printdir.cmd` 36 perm os.FileMode = 0700 37 ) 38 39 tmp, err := ioutil.TempDir("", "testWinSplitListTestIsValid") 40 if err != nil { 41 t.Fatalf("TempDir failed: %v", err) 42 } 43 defer os.RemoveAll(tmp) 44 45 for i, d := range tt.result { 46 if d == "" { 47 continue 48 } 49 if cd := filepath.Clean(d); filepath.VolumeName(cd) != "" || 50 cd[0] == '\\' || cd == ".." || (len(cd) >= 3 && cd[0:3] == `..\`) { 51 t.Errorf("%d,%d: %#q refers outside working directory", ti, i, d) 52 return 53 } 54 dd := filepath.Join(tmp, d) 55 if _, err := os.Stat(dd); err == nil { 56 t.Errorf("%d,%d: %#q already exists", ti, i, d) 57 return 58 } 59 if err = os.MkdirAll(dd, perm); err != nil { 60 t.Errorf("%d,%d: MkdirAll(%#q) failed: %v", ti, i, dd, err) 61 return 62 } 63 fn, data := filepath.Join(dd, cmdfile), []byte("@echo "+d+"\r\n") 64 if err = ioutil.WriteFile(fn, data, perm); err != nil { 65 t.Errorf("%d,%d: WriteFile(%#q) failed: %v", ti, i, fn, err) 66 return 67 } 68 } 69 70 for i, d := range tt.result { 71 if d == "" { 72 continue 73 } 74 exp := []byte(d + "\r\n") 75 cmd := &exec.Cmd{ 76 Path: comspec, 77 Args: []string{`/c`, cmdfile}, 78 Env: []string{`Path=` + tt.list}, 79 Dir: tmp, 80 } 81 out, err := cmd.CombinedOutput() 82 switch { 83 case err != nil: 84 t.Errorf("%d,%d: execution error %v\n%q", ti, i, err, out) 85 return 86 case !reflect.DeepEqual(out, exp): 87 t.Errorf("%d,%d: expected %#q, got %#q", ti, i, exp, out) 88 return 89 default: 90 // unshadow cmdfile in next directory 91 err = os.Remove(filepath.Join(tmp, d, cmdfile)) 92 if err != nil { 93 t.Fatalf("Remove test command failed: %v", err) 94 } 95 } 96 } 97 } 98 99 // TestEvalSymlinksCanonicalNames verify that EvalSymlinks 100 // returns "canonical" path names on windows. 101 func TestEvalSymlinksCanonicalNames(t *testing.T) { 102 tmp, err := ioutil.TempDir("", "evalsymlinkcanonical") 103 if err != nil { 104 t.Fatal("creating temp dir:", err) 105 } 106 defer os.RemoveAll(tmp) 107 108 // ioutil.TempDir might return "non-canonical" name. 109 cTmpName, err := filepath.EvalSymlinks(tmp) 110 if err != nil { 111 t.Errorf("EvalSymlinks(%q) error: %v", tmp, err) 112 } 113 114 dirs := []string{ 115 "test", 116 "test/dir", 117 "testing_long_dir", 118 "TEST2", 119 } 120 121 for _, d := range dirs { 122 dir := filepath.Join(cTmpName, d) 123 err := os.Mkdir(dir, 0755) 124 if err != nil { 125 t.Fatal(err) 126 } 127 cname, err := filepath.EvalSymlinks(dir) 128 if err != nil { 129 t.Errorf("EvalSymlinks(%q) error: %v", dir, err) 130 continue 131 } 132 if dir != cname { 133 t.Errorf("EvalSymlinks(%q) returns %q, but should return %q", dir, cname, dir) 134 continue 135 } 136 // test non-canonical names 137 test := strings.ToUpper(dir) 138 p, err := filepath.EvalSymlinks(test) 139 if err != nil { 140 t.Errorf("EvalSymlinks(%q) error: %v", test, err) 141 continue 142 } 143 if p != cname { 144 t.Errorf("EvalSymlinks(%q) returns %q, but should return %q", test, p, cname) 145 continue 146 } 147 // another test 148 test = strings.ToLower(dir) 149 p, err = filepath.EvalSymlinks(test) 150 if err != nil { 151 t.Errorf("EvalSymlinks(%q) error: %v", test, err) 152 continue 153 } 154 if p != cname { 155 t.Errorf("EvalSymlinks(%q) returns %q, but should return %q", test, p, cname) 156 continue 157 } 158 } 159 } 160 161 // checkVolume8dot3Setting runs "fsutil 8dot3name query c:" command 162 // (where c: is vol parameter) to discover "8dot3 name creation state". 163 // The state is combination of 2 flags. The global flag controls if it 164 // is per volume or global setting: 165 // 0 - Enable 8dot3 name creation on all volumes on the system 166 // 1 - Disable 8dot3 name creation on all volumes on the system 167 // 2 - Set 8dot3 name creation on a per volume basis 168 // 3 - Disable 8dot3 name creation on all volumes except the system volume 169 // If global flag is set to 2, then per-volume flag needs to be examined: 170 // 0 - Enable 8dot3 name creation on this volume 171 // 1 - Disable 8dot3 name creation on this volume 172 // checkVolume8dot3Setting verifies that "8dot3 name creation" flags 173 // are set to 2 and 0, if enabled parameter is true, or 2 and 1, if enabled 174 // is false. Otherwise checkVolume8dot3Setting returns error. 175 func checkVolume8dot3Setting(vol string, enabled bool) error { 176 // It appears, on some systems "fsutil 8dot3name query ..." command always 177 // exits with error. Ignore exit code, and look at fsutil output instead. 178 out, _ := exec.Command("fsutil", "8dot3name", "query", vol).CombinedOutput() 179 // Check that system has "Volume level setting" set. 180 expected := "The registry state of NtfsDisable8dot3NameCreation is 2, the default (Volume level setting)" 181 if !strings.Contains(string(out), expected) { 182 // Windows 10 version of fsutil has different output message. 183 expectedWindow10 := "The registry state is: 2 (Per volume setting - the default)" 184 if !strings.Contains(string(out), expectedWindow10) { 185 return fmt.Errorf("fsutil output should contain %q, but is %q", expected, string(out)) 186 } 187 } 188 // Now check the volume setting. 189 expected = "Based on the above two settings, 8dot3 name creation is %s on %s" 190 if enabled { 191 expected = fmt.Sprintf(expected, "enabled", vol) 192 } else { 193 expected = fmt.Sprintf(expected, "disabled", vol) 194 } 195 if !strings.Contains(string(out), expected) { 196 return fmt.Errorf("unexpected fsutil output: %q", string(out)) 197 } 198 return nil 199 } 200 201 func setVolume8dot3Setting(vol string, enabled bool) error { 202 cmd := []string{"fsutil", "8dot3name", "set", vol} 203 if enabled { 204 cmd = append(cmd, "0") 205 } else { 206 cmd = append(cmd, "1") 207 } 208 // It appears, on some systems "fsutil 8dot3name set ..." command always 209 // exits with error. Ignore exit code, and look at fsutil output instead. 210 out, _ := exec.Command(cmd[0], cmd[1:]...).CombinedOutput() 211 if string(out) != "\r\nSuccessfully set 8dot3name behavior.\r\n" { 212 // Windows 10 version of fsutil has different output message. 213 expectedWindow10 := "Successfully %s 8dot3name generation on %s\r\n" 214 if enabled { 215 expectedWindow10 = fmt.Sprintf(expectedWindow10, "enabled", vol) 216 } else { 217 expectedWindow10 = fmt.Sprintf(expectedWindow10, "disabled", vol) 218 } 219 if string(out) != expectedWindow10 { 220 return fmt.Errorf("%v command failed: %q", cmd, string(out)) 221 } 222 } 223 return nil 224 } 225 226 var runFSModifyTests = flag.Bool("run_fs_modify_tests", false, "run tests which modify filesystem parameters") 227 228 // This test assumes registry state of NtfsDisable8dot3NameCreation is 2, 229 // the default (Volume level setting). 230 func TestEvalSymlinksCanonicalNamesWith8dot3Disabled(t *testing.T) { 231 if !*runFSModifyTests { 232 t.Skip("skipping test that modifies file system setting; enable with -run_fs_modify_tests") 233 } 234 tempVol := filepath.VolumeName(os.TempDir()) 235 if len(tempVol) != 2 { 236 t.Fatalf("unexpected temp volume name %q", tempVol) 237 } 238 239 err := checkVolume8dot3Setting(tempVol, true) 240 if err != nil { 241 t.Fatal(err) 242 } 243 err = setVolume8dot3Setting(tempVol, false) 244 if err != nil { 245 t.Fatal(err) 246 } 247 defer func() { 248 err := setVolume8dot3Setting(tempVol, true) 249 if err != nil { 250 t.Fatal(err) 251 } 252 err = checkVolume8dot3Setting(tempVol, true) 253 if err != nil { 254 t.Fatal(err) 255 } 256 }() 257 err = checkVolume8dot3Setting(tempVol, false) 258 if err != nil { 259 t.Fatal(err) 260 } 261 TestEvalSymlinksCanonicalNames(t) 262 } 263 264 func TestToNorm(t *testing.T) { 265 stubBase := func(path string) (string, error) { 266 vol := filepath.VolumeName(path) 267 path = path[len(vol):] 268 269 if strings.Contains(path, "/") { 270 return "", fmt.Errorf("invalid path is given to base: %s", vol+path) 271 } 272 273 if path == "" || path == "." || path == `\` { 274 return "", fmt.Errorf("invalid path is given to base: %s", vol+path) 275 } 276 277 i := strings.LastIndexByte(path, filepath.Separator) 278 if i == len(path)-1 { // trailing '\' is invalid 279 return "", fmt.Errorf("invalid path is given to base: %s", vol+path) 280 } 281 if i == -1 { 282 return strings.ToUpper(path), nil 283 } 284 285 return strings.ToUpper(path[i+1:]), nil 286 } 287 288 // On this test, toNorm should be same as string.ToUpper(filepath.Clean(path)) except empty string. 289 tests := []struct { 290 arg string 291 want string 292 }{ 293 {"", ""}, 294 {".", "."}, 295 {"./foo/bar", `FOO\BAR`}, 296 {"/", `\`}, 297 {"/foo/bar", `\FOO\BAR`}, 298 {"/foo/bar/baz/qux", `\FOO\BAR\BAZ\QUX`}, 299 {"foo/bar", `FOO\BAR`}, 300 {"C:/foo/bar", `C:\FOO\BAR`}, 301 {"C:foo/bar", `C:FOO\BAR`}, 302 {"c:/foo/bar", `C:\FOO\BAR`}, 303 {"C:/foo/bar", `C:\FOO\BAR`}, 304 {"C:/foo/bar/", `C:\FOO\BAR`}, 305 {`C:\foo\bar`, `C:\FOO\BAR`}, 306 {`C:\foo/bar\`, `C:\FOO\BAR`}, 307 {"C://", `C:\\`}, 308 } 309 310 for _, test := range tests { 311 got, err := filepath.ToNorm(test.arg, stubBase) 312 if err != nil { 313 t.Errorf("toNorm(%s) failed: %v\n", test.arg, err) 314 } else if got != test.want { 315 t.Errorf("toNorm(%s) returns %s, but %s expected\n", test.arg, got, test.want) 316 } 317 } 318 319 testPath := `{{tmp}}\test\foo\bar` 320 321 testsDir := []struct { 322 wd string 323 arg string 324 want string 325 }{ 326 // test absolute paths 327 {".", `{{tmp}}\test\foo\bar`, `{{tmp}}\test\foo\bar`}, 328 {".", `{{tmp}}\.\test/foo\bar`, `{{tmp}}\test\foo\bar`}, 329 {".", `{{tmp}}\test\..\test\foo\bar`, `{{tmp}}\test\foo\bar`}, 330 {".", `{{tmp}}\TEST\FOO\BAR`, `{{tmp}}\test\foo\bar`}, 331 332 // test relative paths begin with drive letter 333 {`{{tmp}}\test`, `{{tmpvol}}.`, `{{tmpvol}}.`}, 334 {`{{tmp}}\test`, `{{tmpvol}}..`, `{{tmpvol}}..`}, 335 {`{{tmp}}\test`, `{{tmpvol}}foo\bar`, `{{tmpvol}}foo\bar`}, 336 {`{{tmp}}\test`, `{{tmpvol}}.\foo\bar`, `{{tmpvol}}foo\bar`}, 337 {`{{tmp}}\test`, `{{tmpvol}}foo\..\foo\bar`, `{{tmpvol}}foo\bar`}, 338 {`{{tmp}}\test`, `{{tmpvol}}FOO\BAR`, `{{tmpvol}}foo\bar`}, 339 340 // test relative paths begin with '\' 341 {"{{tmp}}", `{{tmpnovol}}\test\foo\bar`, `{{tmpnovol}}\test\foo\bar`}, 342 {"{{tmp}}", `{{tmpnovol}}\.\test\foo\bar`, `{{tmpnovol}}\test\foo\bar`}, 343 {"{{tmp}}", `{{tmpnovol}}\test\..\test\foo\bar`, `{{tmpnovol}}\test\foo\bar`}, 344 {"{{tmp}}", `{{tmpnovol}}\TEST\FOO\BAR`, `{{tmpnovol}}\test\foo\bar`}, 345 346 // test relative paths begin without '\' 347 {`{{tmp}}\test`, ".", `.`}, 348 {`{{tmp}}\test`, "..", `..`}, 349 {`{{tmp}}\test`, `foo\bar`, `foo\bar`}, 350 {`{{tmp}}\test`, `.\foo\bar`, `foo\bar`}, 351 {`{{tmp}}\test`, `foo\..\foo\bar`, `foo\bar`}, 352 {`{{tmp}}\test`, `FOO\BAR`, `foo\bar`}, 353 } 354 355 tmp, err := ioutil.TempDir("", "testToNorm") 356 if err != nil { 357 t.Fatal(err) 358 } 359 defer func() { 360 err := os.RemoveAll(tmp) 361 if err != nil { 362 t.Fatal(err) 363 } 364 }() 365 366 // ioutil.TempDir might return "non-canonical" name. 367 ctmp, err := filepath.EvalSymlinks(tmp) 368 if err != nil { 369 t.Fatal(err) 370 } 371 372 err = os.MkdirAll(strings.Replace(testPath, "{{tmp}}", ctmp, -1), 0777) 373 if err != nil { 374 t.Fatal(err) 375 } 376 377 cwd, err := os.Getwd() 378 if err != nil { 379 t.Fatal(err) 380 } 381 defer func() { 382 err := os.Chdir(cwd) 383 if err != nil { 384 t.Fatal(err) 385 } 386 }() 387 388 tmpVol := filepath.VolumeName(ctmp) 389 if len(tmpVol) != 2 { 390 t.Fatalf("unexpected temp volume name %q", tmpVol) 391 } 392 393 tmpNoVol := ctmp[len(tmpVol):] 394 395 replacer := strings.NewReplacer("{{tmp}}", ctmp, "{{tmpvol}}", tmpVol, "{{tmpnovol}}", tmpNoVol) 396 397 for _, test := range testsDir { 398 wd := replacer.Replace(test.wd) 399 arg := replacer.Replace(test.arg) 400 want := replacer.Replace(test.want) 401 402 if test.wd == "." { 403 err := os.Chdir(cwd) 404 if err != nil { 405 t.Error(err) 406 407 continue 408 } 409 } else { 410 err := os.Chdir(wd) 411 if err != nil { 412 t.Error(err) 413 414 continue 415 } 416 } 417 418 got, err := filepath.ToNorm(arg, filepath.NormBase) 419 if err != nil { 420 t.Errorf("toNorm(%s) failed: %v (wd=%s)\n", arg, err, wd) 421 } else if got != want { 422 t.Errorf("toNorm(%s) returns %s, but %s expected (wd=%s)\n", arg, got, want, wd) 423 } 424 } 425 } 426 427 func TestUNC(t *testing.T) { 428 // Test that this doesn't go into an infinite recursion. 429 // See golang.org/issue/15879. 430 defer debug.SetMaxStack(debug.SetMaxStack(1e6)) 431 filepath.Glob(`\\?\c:\*`) 432 } 433