Home | History | Annotate | Download | only in filepath
      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