aboutsummaryrefslogtreecommitdiff
path: root/src/path
diff options
context:
space:
mode:
authorKatie Hockman <katie@golang.org>2020-12-14 10:03:05 -0500
committerKatie Hockman <katie@golang.org>2020-12-14 10:06:13 -0500
commit0345ede87ee12698988973884cfc0fd3d499dffd (patch)
tree7123cff141ee5661208d2f5f437b8f5252ac7f6a /src/path
parent4651d6b267818b0e0d128a5443289717c4bb8cbc (diff)
parent0a02371b0576964e81c3b40d328db9a3ef3b031b (diff)
downloadgo-0345ede87ee12698988973884cfc0fd3d499dffd.tar.xz
[dev.fuzz] all: merge master into dev.fuzz
Change-Id: I5d8c8329ccc9d747bd81ade6b1cb7cb8ae2e94b2
Diffstat (limited to 'src/path')
-rw-r--r--src/path/filepath/example_unix_walk_test.go6
-rw-r--r--src/path/filepath/match.go61
-rw-r--r--src/path/filepath/match_test.go19
-rw-r--r--src/path/filepath/path.go163
-rw-r--r--src/path/filepath/path_test.go147
-rw-r--r--src/path/filepath/path_windows_test.go25
-rw-r--r--src/path/filepath/symlink.go3
-rw-r--r--src/path/filepath/symlink_windows.go2
-rw-r--r--src/path/match.go64
-rw-r--r--src/path/match_test.go11
-rw-r--r--src/path/path.go36
-rw-r--r--src/path/path_test.go3
12 files changed, 378 insertions, 162 deletions
diff --git a/src/path/filepath/example_unix_walk_test.go b/src/path/filepath/example_unix_walk_test.go
index fa8b8e393b..c8a818fd6e 100644
--- a/src/path/filepath/example_unix_walk_test.go
+++ b/src/path/filepath/example_unix_walk_test.go
@@ -8,13 +8,13 @@ package filepath_test
import (
"fmt"
- "io/ioutil"
+ "io/fs"
"os"
"path/filepath"
)
func prepareTestDirTree(tree string) (string, error) {
- tmpDir, err := ioutil.TempDir("", "")
+ tmpDir, err := os.MkdirTemp("", "")
if err != nil {
return "", fmt.Errorf("error creating temp directory: %v\n", err)
}
@@ -40,7 +40,7 @@ func ExampleWalk() {
subDirToSkip := "skip"
fmt.Println("On Unix:")
- err = filepath.Walk(".", func(path string, info os.FileInfo, err error) error {
+ err = filepath.Walk(".", func(path string, info fs.FileInfo, err error) error {
if err != nil {
fmt.Printf("prevent panic by handling failure accessing a path %q: %v\n", path, err)
return err
diff --git a/src/path/filepath/match.go b/src/path/filepath/match.go
index 20a334805b..c77a26952a 100644
--- a/src/path/filepath/match.go
+++ b/src/path/filepath/match.go
@@ -122,25 +122,28 @@ Scan:
// If so, it returns the remainder of s (after the match).
// Chunk is all single-character operators: literals, char classes, and ?.
func matchChunk(chunk, s string) (rest string, ok bool, err error) {
+ // failed records whether the match has failed.
+ // After the match fails, the loop continues on processing chunk,
+ // checking that the pattern is well-formed but no longer reading s.
+ failed := false
for len(chunk) > 0 {
- if len(s) == 0 {
- return
+ if !failed && len(s) == 0 {
+ failed = true
}
switch chunk[0] {
case '[':
// character class
- r, n := utf8.DecodeRuneInString(s)
- s = s[n:]
- chunk = chunk[1:]
- // We can't end right after '[', we're expecting at least
- // a closing bracket and possibly a caret.
- if len(chunk) == 0 {
- err = ErrBadPattern
- return
+ var r rune
+ if !failed {
+ var n int
+ r, n = utf8.DecodeRuneInString(s)
+ s = s[n:]
}
+ chunk = chunk[1:]
// possibly negated
- negated := chunk[0] == '^'
- if negated {
+ negated := false
+ if len(chunk) > 0 && chunk[0] == '^' {
+ negated = true
chunk = chunk[1:]
}
// parse all ranges
@@ -153,12 +156,12 @@ func matchChunk(chunk, s string) (rest string, ok bool, err error) {
}
var lo, hi rune
if lo, chunk, err = getEsc(chunk); err != nil {
- return
+ return "", false, err
}
hi = lo
if chunk[0] == '-' {
if hi, chunk, err = getEsc(chunk[1:]); err != nil {
- return
+ return "", false, err
}
}
if lo <= r && r <= hi {
@@ -167,35 +170,41 @@ func matchChunk(chunk, s string) (rest string, ok bool, err error) {
nrange++
}
if match == negated {
- return
+ failed = true
}
case '?':
- if s[0] == Separator {
- return
+ if !failed {
+ if s[0] == Separator {
+ failed = true
+ }
+ _, n := utf8.DecodeRuneInString(s)
+ s = s[n:]
}
- _, n := utf8.DecodeRuneInString(s)
- s = s[n:]
chunk = chunk[1:]
case '\\':
if runtime.GOOS != "windows" {
chunk = chunk[1:]
if len(chunk) == 0 {
- err = ErrBadPattern
- return
+ return "", false, ErrBadPattern
}
}
fallthrough
default:
- if chunk[0] != s[0] {
- return
+ if !failed {
+ if chunk[0] != s[0] {
+ failed = true
+ }
+ s = s[1:]
}
- s = s[1:]
chunk = chunk[1:]
}
}
+ if failed {
+ return "", false, nil
+ }
return s, true, nil
}
@@ -232,6 +241,10 @@ func getEsc(chunk string) (r rune, nchunk string, err error) {
// The only possible returned error is ErrBadPattern, when pattern
// is malformed.
func Glob(pattern string) (matches []string, err error) {
+ // Check pattern is well-formed.
+ if _, err := Match(pattern, ""); err != nil {
+ return nil, err
+ }
if !hasMeta(pattern) {
if _, err = os.Lstat(pattern); err != nil {
return nil, nil
diff --git a/src/path/filepath/match_test.go b/src/path/filepath/match_test.go
index b8657626bc..48880ea439 100644
--- a/src/path/filepath/match_test.go
+++ b/src/path/filepath/match_test.go
@@ -7,7 +7,6 @@ package filepath_test
import (
"fmt"
"internal/testenv"
- "io/ioutil"
"os"
. "path/filepath"
"reflect"
@@ -75,8 +74,10 @@ var matchTests = []MatchTest{
{"[", "a", false, ErrBadPattern},
{"[^", "a", false, ErrBadPattern},
{"[^bc", "a", false, ErrBadPattern},
- {"a[", "a", false, nil},
+ {"a[", "a", false, ErrBadPattern},
{"a[", "ab", false, ErrBadPattern},
+ {"a[", "x", false, ErrBadPattern},
+ {"a/b[", "x", false, ErrBadPattern},
{"*x", "xxx", true, nil},
}
@@ -155,9 +156,11 @@ func TestGlob(t *testing.T) {
}
func TestGlobError(t *testing.T) {
- _, err := Glob("[]")
- if err == nil {
- t.Error("expected error for bad pattern; got none")
+ bad := []string{`[]`, `nonexist/[]`}
+ for _, pattern := range bad {
+ if _, err := Glob(pattern); err != ErrBadPattern {
+ t.Errorf("Glob(%#q) returned err=%v, want ErrBadPattern", pattern, err)
+ }
}
}
@@ -178,7 +181,7 @@ var globSymlinkTests = []struct {
func TestGlobSymlink(t *testing.T) {
testenv.MustHaveSymlink(t)
- tmpDir, err := ioutil.TempDir("", "globsymlink")
+ tmpDir, err := os.MkdirTemp("", "globsymlink")
if err != nil {
t.Fatal("creating temp dir:", err)
}
@@ -264,7 +267,7 @@ func TestWindowsGlob(t *testing.T) {
t.Skipf("skipping windows specific test")
}
- tmpDir, err := ioutil.TempDir("", "TestWindowsGlob")
+ tmpDir, err := os.MkdirTemp("", "TestWindowsGlob")
if err != nil {
t.Fatal(err)
}
@@ -298,7 +301,7 @@ func TestWindowsGlob(t *testing.T) {
}
}
for _, file := range files {
- err := ioutil.WriteFile(Join(tmpDir, file), nil, 0666)
+ err := os.WriteFile(Join(tmpDir, file), nil, 0666)
if err != nil {
t.Fatal(err)
}
diff --git a/src/path/filepath/path.go b/src/path/filepath/path.go
index 26f1833189..2e7b439355 100644
--- a/src/path/filepath/path.go
+++ b/src/path/filepath/path.go
@@ -13,6 +13,7 @@ package filepath
import (
"errors"
+ "io/fs"
"os"
"sort"
"strings"
@@ -333,29 +334,82 @@ func Rel(basepath, targpath string) (string, error) {
// SkipDir is used as a return value from WalkFuncs to indicate that
// the directory named in the call is to be skipped. It is not returned
// as an error by any function.
-var SkipDir = errors.New("skip this directory")
+var SkipDir error = fs.SkipDir
-// WalkFunc is the type of the function called for each file or directory
-// visited by Walk. The path argument contains the argument to Walk as a
-// prefix; that is, if Walk is called with "dir", which is a directory
-// containing the file "a", the walk function will be called with argument
-// "dir/a". The info argument is the os.FileInfo for the named path.
+// WalkFunc is the type of the function called by Walk to visit each each
+// file or directory.
//
-// If there was a problem walking to the file or directory named by path, the
-// incoming error will describe the problem and the function can decide how
-// to handle that error (and Walk will not descend into that directory). In the
-// case of an error, the info argument will be nil. If an error is returned,
-// processing stops. The sole exception is when the function returns the special
-// value SkipDir. If the function returns SkipDir when invoked on a directory,
-// Walk skips the directory's contents entirely. If the function returns SkipDir
-// when invoked on a non-directory file, Walk skips the remaining files in the
-// containing directory.
-type WalkFunc func(path string, info os.FileInfo, err error) error
+// The path argument contains the argument to Walk as a prefix.
+// That is, if Walk is called with root argument "dir" and finds a file
+// named "a" in that directory, the walk function will be called with
+// argument "dir/a".
+//
+// The directory and file are joined with Join, which may clean the
+// directory name: if Walk is called with the root argument "x/../dir"
+// and finds a file named "a" in that directory, the walk function will
+// be called with argument "dir/a", not "x/../dir/a".
+//
+// The info argument is the fs.FileInfo for the named path.
+//
+// The error result returned by the function controls how Walk continues.
+// If the function returns the special value SkipDir, Walk skips the
+// current directory (path if info.IsDir() is true, otherwise path's
+// parent directory). Otherwise, if the function returns a non-nil error,
+// Walk stops entirely and returns that error.
+//
+// The err argument reports an error related to path, signaling that Walk
+// will not walk into that directory. The function can decide how to
+// handle that error; as described earlier, returning the error will
+// cause Walk to stop walking the entire tree.
+//
+// Walk calls the function with a non-nil err argument in two cases.
+//
+// First, if an os.Lstat on the root directory or any directory or file
+// in the tree fails, Walk calls the function with path set to that
+// directory or file's path, info set to nil, and err set to the error
+// from os.Lstat.
+//
+// Second, if a directory's Readdirnames method fails, Walk calls the
+// function with path set to the directory's path, info, set to an
+// fs.FileInfo describing the directory, and err set to the error from
+// Readdirnames.
+type WalkFunc func(path string, info fs.FileInfo, err error) error
var lstat = os.Lstat // for testing
+// walkDir recursively descends path, calling walkDirFn.
+func walkDir(path string, d fs.DirEntry, walkDirFn fs.WalkDirFunc) error {
+ if err := walkDirFn(path, d, nil); err != nil || !d.IsDir() {
+ if err == SkipDir && d.IsDir() {
+ // Successfully skipped directory.
+ err = nil
+ }
+ return err
+ }
+
+ dirs, err := readDir(path)
+ if err != nil {
+ // Second call, to report ReadDir error.
+ err = walkDirFn(path, d, err)
+ if err != nil {
+ return err
+ }
+ }
+
+ for _, d1 := range dirs {
+ path1 := Join(path, d1.Name())
+ if err := walkDir(path1, d1, walkDirFn); err != nil {
+ if err == SkipDir {
+ break
+ }
+ return err
+ }
+ }
+ return nil
+}
+
// walk recursively descends path, calling walkFn.
-func walk(path string, info os.FileInfo, walkFn WalkFunc) error {
+func walk(path string, info fs.FileInfo, walkFn WalkFunc) error {
if !info.IsDir() {
return walkFn(path, info, nil)
}
@@ -392,18 +446,59 @@ func walk(path string, info os.FileInfo, walkFn WalkFunc) error {
return nil
}
-// Walk walks the file tree rooted at root, calling walkFn for each file or
-// directory in the tree, including root. All errors that arise visiting files
-// and directories are filtered by walkFn. The files are walked in lexical
-// order, which makes the output deterministic but means that for very
-// large directories Walk can be inefficient.
+// WalkDir walks the file tree rooted at root, calling fn for each file or
+// directory in the tree, including root.
+//
+// All errors that arise visiting files and directories are filtered by fn:
+// see the fs.WalkDirFunc documentation for details.
+//
+// The files are walked in lexical order, which makes the output deterministic
+// but requires WalkDir to read an entire directory into memory before proceeding
+// to walk that directory.
+//
+// WalkDir does not follow symbolic links.
+func WalkDir(root string, fn fs.WalkDirFunc) error {
+ info, err := os.Lstat(root)
+ if err != nil {
+ err = fn(root, nil, err)
+ } else {
+ err = walkDir(root, &statDirEntry{info}, fn)
+ }
+ if err == SkipDir {
+ return nil
+ }
+ return err
+}
+
+type statDirEntry struct {
+ info fs.FileInfo
+}
+
+func (d *statDirEntry) Name() string { return d.info.Name() }
+func (d *statDirEntry) IsDir() bool { return d.info.IsDir() }
+func (d *statDirEntry) Type() fs.FileMode { return d.info.Mode().Type() }
+func (d *statDirEntry) Info() (fs.FileInfo, error) { return d.info, nil }
+
+// Walk walks the file tree rooted at root, calling fn for each file or
+// directory in the tree, including root.
+//
+// All errors that arise visiting files and directories are filtered by fn:
+// see the WalkFunc documentation for details.
+//
+// The files are walked in lexical order, which makes the output deterministic
+// but requires Walk to read an entire directory into memory before proceeding
+// to walk that directory.
+//
// Walk does not follow symbolic links.
-func Walk(root string, walkFn WalkFunc) error {
+//
+// Walk is less efficient than WalkDir, introduced in Go 1.16,
+// which avoids calling os.Lstat on every visited file or directory.
+func Walk(root string, fn WalkFunc) error {
info, err := os.Lstat(root)
if err != nil {
- err = walkFn(root, nil, err)
+ err = fn(root, nil, err)
} else {
- err = walk(root, info, walkFn)
+ err = walk(root, info, fn)
}
if err == SkipDir {
return nil
@@ -411,8 +506,24 @@ func Walk(root string, walkFn WalkFunc) error {
return err
}
-// readDirNames reads the directory named by dirname and returns
+// readDir reads the directory named by dirname and returns
// a sorted list of directory entries.
+func readDir(dirname string) ([]fs.DirEntry, error) {
+ f, err := os.Open(dirname)
+ if err != nil {
+ return nil, err
+ }
+ dirs, err := f.ReadDir(-1)
+ f.Close()
+ if err != nil {
+ return nil, err
+ }
+ sort.Slice(dirs, func(i, j int) bool { return dirs[i].Name() < dirs[j].Name() })
+ return dirs, nil
+}
+
+// readDirNames reads the directory named by dirname and returns
+// a sorted list of directory entry names.
func readDirNames(dirname string) ([]string, error) {
f, err := os.Open(dirname)
if err != nil {
diff --git a/src/path/filepath/path_test.go b/src/path/filepath/path_test.go
index d6f680556c..8616256ac0 100644
--- a/src/path/filepath/path_test.go
+++ b/src/path/filepath/path_test.go
@@ -8,7 +8,7 @@ import (
"errors"
"fmt"
"internal/testenv"
- "io/ioutil"
+ "io/fs"
"os"
"path/filepath"
"reflect"
@@ -393,8 +393,8 @@ func checkMarks(t *testing.T, report bool) {
// Assumes that each node name is unique. Good enough for a test.
// If clear is true, any incoming error is cleared before return. The errors
// are always accumulated, though.
-func mark(info os.FileInfo, err error, errors *[]error, clear bool) error {
- name := info.Name()
+func mark(d fs.DirEntry, err error, errors *[]error, clear bool) error {
+ name := d.Name()
walkTree(tree, tree.name, func(path string, n *Node) {
if n.name == name {
n.mark++
@@ -415,7 +415,7 @@ func chtmpdir(t *testing.T) (restore func()) {
if err != nil {
t.Fatalf("chtmpdir: %v", err)
}
- d, err := ioutil.TempDir("", "test")
+ d, err := os.MkdirTemp("", "test")
if err != nil {
t.Fatalf("chtmpdir: %v", err)
}
@@ -431,12 +431,34 @@ func chtmpdir(t *testing.T) (restore func()) {
}
func TestWalk(t *testing.T) {
- if runtime.GOOS == "darwin" && runtime.GOARCH == "arm64" {
+ walk := func(root string, fn fs.WalkDirFunc) error {
+ return filepath.Walk(root, func(path string, info fs.FileInfo, err error) error {
+ return fn(path, &statDirEntry{info}, err)
+ })
+ }
+ testWalk(t, walk, 1)
+}
+
+type statDirEntry struct {
+ info fs.FileInfo
+}
+
+func (d *statDirEntry) Name() string { return d.info.Name() }
+func (d *statDirEntry) IsDir() bool { return d.info.IsDir() }
+func (d *statDirEntry) Type() fs.FileMode { return d.info.Mode().Type() }
+func (d *statDirEntry) Info() (fs.FileInfo, error) { return d.info, nil }
+
+func TestWalkDir(t *testing.T) {
+ testWalk(t, filepath.WalkDir, 2)
+}
+
+func testWalk(t *testing.T, walk func(string, fs.WalkDirFunc) error, errVisit int) {
+ if runtime.GOOS == "ios" {
restore := chtmpdir(t)
defer restore()
}
- tmpDir, err := ioutil.TempDir("", "TestWalk")
+ tmpDir, err := os.MkdirTemp("", "TestWalk")
if err != nil {
t.Fatal("creating temp dir:", err)
}
@@ -454,11 +476,11 @@ func TestWalk(t *testing.T) {
makeTree(t)
errors := make([]error, 0, 10)
clear := true
- markFn := func(path string, info os.FileInfo, err error) error {
- return mark(info, err, &errors, clear)
+ markFn := func(path string, d fs.DirEntry, err error) error {
+ return mark(d, err, &errors, clear)
}
// Expect no errors.
- err = filepath.Walk(tree.name, markFn)
+ err = walk(tree.name, markFn)
if err != nil {
t.Fatalf("no error expected, found: %s", err)
}
@@ -468,10 +490,20 @@ func TestWalk(t *testing.T) {
checkMarks(t, true)
errors = errors[0:0]
- // Test permission errors. Only possible if we're not root
- // and only on some file systems (AFS, FAT). To avoid errors during
- // all.bash on those file systems, skip during go test -short.
- if os.Getuid() > 0 && !testing.Short() {
+ t.Run("PermErr", func(t *testing.T) {
+ // Test permission errors. Only possible if we're not root
+ // and only on some file systems (AFS, FAT). To avoid errors during
+ // all.bash on those file systems, skip during go test -short.
+ if runtime.GOOS == "windows" {
+ t.Skip("skipping on Windows")
+ }
+ if os.Getuid() == 0 {
+ t.Skip("skipping as root")
+ }
+ if testing.Short() {
+ t.Skip("skipping in short mode")
+ }
+
// introduce 2 errors: chmod top-level directories to 0
os.Chmod(filepath.Join(tree.name, tree.entries[1].name), 0)
os.Chmod(filepath.Join(tree.name, tree.entries[3].name), 0)
@@ -481,9 +513,9 @@ func TestWalk(t *testing.T) {
markTree(tree.entries[1])
markTree(tree.entries[3])
// correct double-marking of directory itself
- tree.entries[1].mark--
- tree.entries[3].mark--
- err := filepath.Walk(tree.name, markFn)
+ tree.entries[1].mark -= errVisit
+ tree.entries[3].mark -= errVisit
+ err := walk(tree.name, markFn)
if err != nil {
t.Fatalf("expected no error return from Walk, got %s", err)
}
@@ -499,10 +531,10 @@ func TestWalk(t *testing.T) {
markTree(tree.entries[1])
markTree(tree.entries[3])
// correct double-marking of directory itself
- tree.entries[1].mark--
- tree.entries[3].mark--
+ tree.entries[1].mark -= errVisit
+ tree.entries[3].mark -= errVisit
clear = false // error will stop processing
- err = filepath.Walk(tree.name, markFn)
+ err = walk(tree.name, markFn)
if err == nil {
t.Fatalf("expected error return from Walk")
}
@@ -516,7 +548,7 @@ func TestWalk(t *testing.T) {
// restore permissions
os.Chmod(filepath.Join(tree.name, tree.entries[1].name), 0770)
os.Chmod(filepath.Join(tree.name, tree.entries[3].name), 0770)
- }
+ })
}
func touch(t *testing.T, name string) {
@@ -530,7 +562,7 @@ func touch(t *testing.T, name string) {
}
func TestWalkSkipDirOnFile(t *testing.T) {
- td, err := ioutil.TempDir("", "walktest")
+ td, err := os.MkdirTemp("", "walktest")
if err != nil {
t.Fatal(err)
}
@@ -543,7 +575,7 @@ func TestWalkSkipDirOnFile(t *testing.T) {
touch(t, filepath.Join(td, "dir/foo2"))
sawFoo2 := false
- walker := func(path string, info os.FileInfo, err error) error {
+ walker := func(path string) error {
if strings.HasSuffix(path, "foo2") {
sawFoo2 = true
}
@@ -552,26 +584,35 @@ func TestWalkSkipDirOnFile(t *testing.T) {
}
return nil
}
+ walkFn := func(path string, _ fs.FileInfo, _ error) error { return walker(path) }
+ walkDirFn := func(path string, _ fs.DirEntry, _ error) error { return walker(path) }
- err = filepath.Walk(td, walker)
- if err != nil {
- t.Fatal(err)
- }
- if sawFoo2 {
- t.Errorf("SkipDir on file foo1 did not block processing of foo2")
+ check := func(t *testing.T, walk func(root string) error, root string) {
+ t.Helper()
+ sawFoo2 = false
+ err = walk(root)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if sawFoo2 {
+ t.Errorf("SkipDir on file foo1 did not block processing of foo2")
+ }
}
- err = filepath.Walk(filepath.Join(td, "dir"), walker)
- if err != nil {
- t.Fatal(err)
- }
- if sawFoo2 {
- t.Errorf("SkipDir on file foo1 did not block processing of foo2")
- }
+ t.Run("Walk", func(t *testing.T) {
+ Walk := func(root string) error { return filepath.Walk(td, walkFn) }
+ check(t, Walk, td)
+ check(t, Walk, filepath.Join(td, "dir"))
+ })
+ t.Run("WalkDir", func(t *testing.T) {
+ WalkDir := func(root string) error { return filepath.WalkDir(td, walkDirFn) }
+ check(t, WalkDir, td)
+ check(t, WalkDir, filepath.Join(td, "dir"))
+ })
}
func TestWalkFileError(t *testing.T) {
- td, err := ioutil.TempDir("", "walktest")
+ td, err := os.MkdirTemp("", "walktest")
if err != nil {
t.Fatal(err)
}
@@ -589,14 +630,14 @@ func TestWalkFileError(t *testing.T) {
*filepath.LstatP = os.Lstat
}()
statErr := errors.New("some stat error")
- *filepath.LstatP = func(path string) (os.FileInfo, error) {
+ *filepath.LstatP = func(path string) (fs.FileInfo, error) {
if strings.HasSuffix(path, "stat-error") {
return nil, statErr
}
return os.Lstat(path)
}
got := map[string]error{}
- err = filepath.Walk(td, func(path string, fi os.FileInfo, err error) error {
+ err = filepath.Walk(td, func(path string, fi fs.FileInfo, err error) error {
rel, _ := filepath.Rel(td, path)
got[filepath.ToSlash(rel)] = err
return nil
@@ -850,7 +891,7 @@ func testEvalSymlinksAfterChdir(t *testing.T, wd, path, want string) {
func TestEvalSymlinks(t *testing.T) {
testenv.MustHaveSymlink(t)
- tmpDir, err := ioutil.TempDir("", "evalsymlink")
+ tmpDir, err := os.MkdirTemp("", "evalsymlink")
if err != nil {
t.Fatal("creating temp dir:", err)
}
@@ -936,7 +977,7 @@ func TestEvalSymlinksIsNotExist(t *testing.T) {
func TestIssue13582(t *testing.T) {
testenv.MustHaveSymlink(t)
- tmpDir, err := ioutil.TempDir("", "issue13582")
+ tmpDir, err := os.MkdirTemp("", "issue13582")
if err != nil {
t.Fatal(err)
}
@@ -953,7 +994,7 @@ func TestIssue13582(t *testing.T) {
t.Fatal(err)
}
file := filepath.Join(linkToDir, "file")
- err = ioutil.WriteFile(file, nil, 0644)
+ err = os.WriteFile(file, nil, 0644)
if err != nil {
t.Fatal(err)
}
@@ -1023,7 +1064,7 @@ var absTests = []string{
}
func TestAbs(t *testing.T) {
- root, err := ioutil.TempDir("", "TestAbs")
+ root, err := os.MkdirTemp("", "TestAbs")
if err != nil {
t.Fatal("TempDir failed: ", err)
}
@@ -1094,7 +1135,7 @@ func TestAbs(t *testing.T) {
// We test it separately from all other absTests because the empty string is not
// a valid path, so it can't be used with os.Stat.
func TestAbsEmptyString(t *testing.T) {
- root, err := ioutil.TempDir("", "TestAbsEmptyString")
+ root, err := os.MkdirTemp("", "TestAbsEmptyString")
if err != nil {
t.Fatal("TempDir failed: ", err)
}
@@ -1278,7 +1319,7 @@ func TestDriveLetterInEvalSymlinks(t *testing.T) {
}
func TestBug3486(t *testing.T) { // https://golang.org/issue/3486
- if runtime.GOOS == "darwin" && runtime.GOARCH == "arm64" {
+ if runtime.GOOS == "ios" {
t.Skipf("skipping on %s/%s", runtime.GOOS, runtime.GOARCH)
}
root, err := filepath.EvalSymlinks(runtime.GOROOT() + "/test")
@@ -1289,7 +1330,7 @@ func TestBug3486(t *testing.T) { // https://golang.org/issue/3486
ken := filepath.Join(root, "ken")
seenBugs := false
seenKen := false
- err = filepath.Walk(root, func(pth string, info os.FileInfo, err error) error {
+ err = filepath.Walk(root, func(pth string, info fs.FileInfo, err error) error {
if err != nil {
t.Fatal(err)
}
@@ -1315,7 +1356,7 @@ func TestBug3486(t *testing.T) { // https://golang.org/issue/3486
}
func testWalkSymlink(t *testing.T, mklink func(target, link string) error) {
- tmpdir, err := ioutil.TempDir("", "testWalkSymlink")
+ tmpdir, err := os.MkdirTemp("", "testWalkSymlink")
if err != nil {
t.Fatal(err)
}
@@ -1338,7 +1379,7 @@ func testWalkSymlink(t *testing.T, mklink func(target, link string) error) {
}
var visited []string
- err = filepath.Walk(tmpdir, func(path string, info os.FileInfo, err error) error {
+ err = filepath.Walk(tmpdir, func(path string, info fs.FileInfo, err error) error {
if err != nil {
t.Fatal(err)
}
@@ -1365,14 +1406,14 @@ func TestWalkSymlink(t *testing.T) {
}
func TestIssue29372(t *testing.T) {
- tmpDir, err := ioutil.TempDir("", "TestIssue29372")
+ tmpDir, err := os.MkdirTemp("", "TestIssue29372")
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(tmpDir)
path := filepath.Join(tmpDir, "file.txt")
- err = ioutil.WriteFile(path, nil, 0644)
+ err = os.WriteFile(path, nil, 0644)
if err != nil {
t.Fatal(err)
}
@@ -1401,7 +1442,7 @@ func TestEvalSymlinksAboveRoot(t *testing.T) {
t.Parallel()
- tmpDir, err := ioutil.TempDir("", "TestEvalSymlinksAboveRoot")
+ tmpDir, err := os.MkdirTemp("", "TestEvalSymlinksAboveRoot")
if err != nil {
t.Fatal(err)
}
@@ -1418,7 +1459,7 @@ func TestEvalSymlinksAboveRoot(t *testing.T) {
if err := os.Symlink(filepath.Join(evalTmpDir, "a"), filepath.Join(evalTmpDir, "b")); err != nil {
t.Fatal(err)
}
- if err := ioutil.WriteFile(filepath.Join(evalTmpDir, "a", "file"), nil, 0666); err != nil {
+ if err := os.WriteFile(filepath.Join(evalTmpDir, "a", "file"), nil, 0666); err != nil {
t.Fatal(err)
}
@@ -1449,7 +1490,7 @@ func TestEvalSymlinksAboveRoot(t *testing.T) {
func TestEvalSymlinksAboveRootChdir(t *testing.T) {
testenv.MustHaveSymlink(t)
- tmpDir, err := ioutil.TempDir("", "TestEvalSymlinksAboveRootChdir")
+ tmpDir, err := os.MkdirTemp("", "TestEvalSymlinksAboveRootChdir")
if err != nil {
t.Fatal(err)
}
@@ -1472,7 +1513,7 @@ func TestEvalSymlinksAboveRootChdir(t *testing.T) {
if err := os.Symlink(subdir, "c"); err != nil {
t.Fatal(err)
}
- if err := ioutil.WriteFile(filepath.Join(subdir, "file"), nil, 0666); err != nil {
+ if err := os.WriteFile(filepath.Join(subdir, "file"), nil, 0666); err != nil {
t.Fatal(err)
}
diff --git a/src/path/filepath/path_windows_test.go b/src/path/filepath/path_windows_test.go
index f7c454bf65..1c3d84c62d 100644
--- a/src/path/filepath/path_windows_test.go
+++ b/src/path/filepath/path_windows_test.go
@@ -8,7 +8,7 @@ import (
"flag"
"fmt"
"internal/testenv"
- "io/ioutil"
+ "io/fs"
"os"
"os/exec"
"path/filepath"
@@ -34,10 +34,10 @@ func testWinSplitListTestIsValid(t *testing.T, ti int, tt SplitListTest,
const (
cmdfile = `printdir.cmd`
- perm os.FileMode = 0700
+ perm fs.FileMode = 0700
)
- tmp, err := ioutil.TempDir("", "testWinSplitListTestIsValid")
+ tmp, err := os.MkdirTemp("", "testWinSplitListTestIsValid")
if err != nil {
t.Fatalf("TempDir failed: %v", err)
}
@@ -62,7 +62,7 @@ func testWinSplitListTestIsValid(t *testing.T, ti int, tt SplitListTest,
return
}
fn, data := filepath.Join(dd, cmdfile), []byte("@echo "+d+"\r\n")
- if err = ioutil.WriteFile(fn, data, perm); err != nil {
+ if err = os.WriteFile(fn, data, perm); err != nil {
t.Errorf("%d,%d: WriteFile(%#q) failed: %v", ti, i, fn, err)
return
}
@@ -103,7 +103,7 @@ func testWinSplitListTestIsValid(t *testing.T, ti int, tt SplitListTest,
func TestWindowsEvalSymlinks(t *testing.T) {
testenv.MustHaveSymlink(t)
- tmpDir, err := ioutil.TempDir("", "TestWindowsEvalSymlinks")
+ tmpDir, err := os.MkdirTemp("", "TestWindowsEvalSymlinks")
if err != nil {
t.Fatal(err)
}
@@ -161,13 +161,13 @@ func TestWindowsEvalSymlinks(t *testing.T) {
// TestEvalSymlinksCanonicalNames verify that EvalSymlinks
// returns "canonical" path names on windows.
func TestEvalSymlinksCanonicalNames(t *testing.T) {
- tmp, err := ioutil.TempDir("", "evalsymlinkcanonical")
+ tmp, err := os.MkdirTemp("", "evalsymlinkcanonical")
if err != nil {
t.Fatal("creating temp dir:", err)
}
defer os.RemoveAll(tmp)
- // ioutil.TempDir might return "non-canonical" name.
+ // os.MkdirTemp might return "non-canonical" name.
cTmpName, err := filepath.EvalSymlinks(tmp)
if err != nil {
t.Errorf("EvalSymlinks(%q) error: %v", tmp, err)
@@ -412,9 +412,12 @@ func TestToNorm(t *testing.T) {
{`{{tmp}}\test`, `.\foo\bar`, `foo\bar`},
{`{{tmp}}\test`, `foo\..\foo\bar`, `foo\bar`},
{`{{tmp}}\test`, `FOO\BAR`, `foo\bar`},
+
+ // test UNC paths
+ {".", `\\localhost\c$`, `\\localhost\c$`},
}
- tmp, err := ioutil.TempDir("", "testToNorm")
+ tmp, err := os.MkdirTemp("", "testToNorm")
if err != nil {
t.Fatal(err)
}
@@ -425,7 +428,7 @@ func TestToNorm(t *testing.T) {
}
}()
- // ioutil.TempDir might return "non-canonical" name.
+ // os.MkdirTemp might return "non-canonical" name.
ctmp, err := filepath.EvalSymlinks(tmp)
if err != nil {
t.Fatal(err)
@@ -523,7 +526,7 @@ func TestNTNamespaceSymlink(t *testing.T) {
t.Skip("skipping test because mklink command does not support junctions")
}
- tmpdir, err := ioutil.TempDir("", "TestNTNamespaceSymlink")
+ tmpdir, err := os.MkdirTemp("", "TestNTNamespaceSymlink")
if err != nil {
t.Fatal(err)
}
@@ -560,7 +563,7 @@ func TestNTNamespaceSymlink(t *testing.T) {
testenv.MustHaveSymlink(t)
file := filepath.Join(tmpdir, "file")
- err = ioutil.WriteFile(file, []byte(""), 0666)
+ err = os.WriteFile(file, []byte(""), 0666)
if err != nil {
t.Fatal(err)
}
diff --git a/src/path/filepath/symlink.go b/src/path/filepath/symlink.go
index 335b315a20..6fefd15977 100644
--- a/src/path/filepath/symlink.go
+++ b/src/path/filepath/symlink.go
@@ -6,6 +6,7 @@ package filepath
import (
"errors"
+ "io/fs"
"os"
"runtime"
"syscall"
@@ -85,7 +86,7 @@ func walkSymlinks(path string) (string, error) {
return "", err
}
- if fi.Mode()&os.ModeSymlink == 0 {
+ if fi.Mode()&fs.ModeSymlink == 0 {
if !fi.Mode().IsDir() && end < len(path) {
return "", syscall.ENOTDIR
}
diff --git a/src/path/filepath/symlink_windows.go b/src/path/filepath/symlink_windows.go
index a799488c18..d72279e2bb 100644
--- a/src/path/filepath/symlink_windows.go
+++ b/src/path/filepath/symlink_windows.go
@@ -68,7 +68,7 @@ func toNorm(path string, normBase func(string) (string, error)) (string, error)
path = path[len(volume):]
// skip special cases
- if path == "." || path == `\` {
+ if path == "" || path == "." || path == `\` {
return volume + path, nil
}
diff --git a/src/path/match.go b/src/path/match.go
index d39d24450a..918624c60e 100644
--- a/src/path/match.go
+++ b/src/path/match.go
@@ -6,7 +6,7 @@ package path
import (
"errors"
- "strings"
+ "internal/bytealg"
"unicode/utf8"
)
@@ -43,7 +43,7 @@ Pattern:
star, chunk, pattern = scanChunk(pattern)
if star && chunk == "" {
// Trailing * matches rest of string unless it has a /.
- return !strings.Contains(name, "/"), nil
+ return bytealg.IndexByteString(name, '/') < 0, nil
}
// Look for match at current position.
t, ok, err := matchChunk(chunk, name)
@@ -75,6 +75,14 @@ Pattern:
}
}
}
+ // Before returning false with no error,
+ // check that the remainder of the pattern is syntactically valid.
+ for len(pattern) > 0 {
+ _, chunk, pattern = scanChunk(pattern)
+ if _, _, err := matchChunk(chunk, ""); err != nil {
+ return false, err
+ }
+ }
return false, nil
}
return len(name) == 0, nil
@@ -114,20 +122,28 @@ Scan:
// If so, it returns the remainder of s (after the match).
// Chunk is all single-character operators: literals, char classes, and ?.
func matchChunk(chunk, s string) (rest string, ok bool, err error) {
+ // failed records whether the match has failed.
+ // After the match fails, the loop continues on processing chunk,
+ // checking that the pattern is well-formed but no longer reading s.
+ failed := false
for len(chunk) > 0 {
- if len(s) == 0 {
- return
+ if !failed && len(s) == 0 {
+ failed = true
}
switch chunk[0] {
case '[':
// character class
- r, n := utf8.DecodeRuneInString(s)
- s = s[n:]
+ var r rune
+ if !failed {
+ var n int
+ r, n = utf8.DecodeRuneInString(s)
+ s = s[n:]
+ }
chunk = chunk[1:]
// possibly negated
- notNegated := true
+ negated := false
if len(chunk) > 0 && chunk[0] == '^' {
- notNegated = false
+ negated = true
chunk = chunk[1:]
}
// parse all ranges
@@ -140,12 +156,12 @@ func matchChunk(chunk, s string) (rest string, ok bool, err error) {
}
var lo, hi rune
if lo, chunk, err = getEsc(chunk); err != nil {
- return
+ return "", false, err
}
hi = lo
if chunk[0] == '-' {
if hi, chunk, err = getEsc(chunk[1:]); err != nil {
- return
+ return "", false, err
}
}
if lo <= r && r <= hi {
@@ -153,34 +169,40 @@ func matchChunk(chunk, s string) (rest string, ok bool, err error) {
}
nrange++
}
- if match != notNegated {
- return
+ if match == negated {
+ failed = true
}
case '?':
- if s[0] == '/' {
- return
+ if !failed {
+ if s[0] == '/' {
+ failed = true
+ }
+ _, n := utf8.DecodeRuneInString(s)
+ s = s[n:]
}
- _, n := utf8.DecodeRuneInString(s)
- s = s[n:]
chunk = chunk[1:]
case '\\':
chunk = chunk[1:]
if len(chunk) == 0 {
- err = ErrBadPattern
- return
+ return "", false, ErrBadPattern
}
fallthrough
default:
- if chunk[0] != s[0] {
- return
+ if !failed {
+ if chunk[0] != s[0] {
+ failed = true
+ }
+ s = s[1:]
}
- s = s[1:]
chunk = chunk[1:]
}
}
+ if failed {
+ return "", false, nil
+ }
return s, true, nil
}
diff --git a/src/path/match_test.go b/src/path/match_test.go
index 127180e570..996bd691eb 100644
--- a/src/path/match_test.go
+++ b/src/path/match_test.go
@@ -2,9 +2,12 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
-package path
+package path_test
-import "testing"
+import (
+ . "path"
+ "testing"
+)
type MatchTest struct {
pattern, s string
@@ -64,8 +67,10 @@ var matchTests = []MatchTest{
{"[", "a", false, ErrBadPattern},
{"[^", "a", false, ErrBadPattern},
{"[^bc", "a", false, ErrBadPattern},
- {"a[", "a", false, nil},
+ {"a[", "a", false, ErrBadPattern},
{"a[", "ab", false, ErrBadPattern},
+ {"a[", "x", false, ErrBadPattern},
+ {"a/b[", "x", false, ErrBadPattern},
{"*x", "xxx", true, nil},
}
diff --git a/src/path/path.go b/src/path/path.go
index c513114b4d..f1f3499f63 100644
--- a/src/path/path.go
+++ b/src/path/path.go
@@ -11,10 +11,6 @@
// operating system paths, use the path/filepath package.
package path
-import (
- "strings"
-)
-
// A lazybuf is a lazily constructed path buffer.
// It supports append, reading previously appended bytes,
// and retrieving the final string. It does not allocate a buffer
@@ -139,13 +135,22 @@ func Clean(path string) string {
return out.string()
}
+// lastSlash(s) is strings.LastIndex(s, "/") but we can't import strings.
+func lastSlash(s string) int {
+ i := len(s) - 1
+ for i >= 0 && s[i] != '/' {
+ i--
+ }
+ return i
+}
+
// Split splits path immediately following the final slash,
// separating it into a directory and file name component.
// If there is no slash in path, Split returns an empty dir and
// file set to path.
// The returned values have the property that path = dir+file.
func Split(path string) (dir, file string) {
- i := strings.LastIndex(path, "/")
+ i := lastSlash(path)
return path[:i+1], path[i+1:]
}
@@ -155,12 +160,23 @@ func Split(path string) (dir, file string) {
// empty or all its elements are empty, Join returns
// an empty string.
func Join(elem ...string) string {
- for i, e := range elem {
- if e != "" {
- return Clean(strings.Join(elem[i:], "/"))
+ size := 0
+ for _, e := range elem {
+ size += len(e)
+ }
+ if size == 0 {
+ return ""
+ }
+ buf := make([]byte, 0, size+len(elem)-1)
+ for _, e := range elem {
+ if len(buf) > 0 || e != "" {
+ if len(buf) > 0 {
+ buf = append(buf, '/')
+ }
+ buf = append(buf, e...)
}
}
- return ""
+ return Clean(string(buf))
}
// Ext returns the file name extension used by path.
@@ -189,7 +205,7 @@ func Base(path string) string {
path = path[0 : len(path)-1]
}
// Find the last element
- if i := strings.LastIndex(path, "/"); i >= 0 {
+ if i := lastSlash(path); i >= 0 {
path = path[i+1:]
}
// If empty now, it had only slashes.
diff --git a/src/path/path_test.go b/src/path/path_test.go
index 2a3635300e..a57286f6b8 100644
--- a/src/path/path_test.go
+++ b/src/path/path_test.go
@@ -2,9 +2,10 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
-package path
+package path_test
import (
+ . "path"
"runtime"
"testing"
)