From 75d4cb6a02c30f8b1be934f06f0ba5c49bfc34a0 Mon Sep 17 00:00:00 2001 From: Andrew Gerrand Date: Thu, 17 Mar 2011 16:36:37 +1100 Subject: [PATCH] path/filepath: add EvalSymlinks function R=rsc, niemeyer, r2, rog, iant2, r CC=golang-dev https://golang.org/cl/4235060 --- src/pkg/path/filepath/path.go | 72 +++++++++++++++++++++++++++--- src/pkg/path/filepath/path_test.go | 60 +++++++++++++++++++++++++ 2 files changed, 127 insertions(+), 5 deletions(-) diff --git a/src/pkg/path/filepath/path.go b/src/pkg/path/filepath/path.go index 64cef291e6..6cd6cf2ab0 100644 --- a/src/pkg/path/filepath/path.go +++ b/src/pkg/path/filepath/path.go @@ -8,11 +8,17 @@ package filepath import ( + "bytes" "os" "sort" "strings" ) +const ( + SeparatorString = string(Separator) + ListSeparatorString = string(ListSeparator) +) + // Clean returns the shortest path name equivalent to path // by purely lexical processing. It applies the following rules // iteratively until no further processing can be done: @@ -113,7 +119,7 @@ func ToSlash(path string) string { if Separator == '/' { return path } - return strings.Replace(path, string(Separator), "/", -1) + return strings.Replace(path, SeparatorString, "/", -1) } // FromSlash returns the result of replacing each slash ('/') character @@ -122,7 +128,7 @@ func FromSlash(path string) string { if Separator == '/' { return path } - return strings.Replace(path, "/", string(Separator), -1) + return strings.Replace(path, "/", SeparatorString, -1) } // SplitList splits a list of paths joined by the OS-specific ListSeparator. @@ -130,7 +136,7 @@ func SplitList(path string) []string { if path == "" { return []string{} } - return strings.Split(path, string(ListSeparator), -1) + return strings.Split(path, ListSeparatorString, -1) } // Split splits path immediately following the final Separator, @@ -150,7 +156,7 @@ func Split(path string) (dir, file string) { func Join(elem ...string) string { for i, e := range elem { if e != "" { - return Clean(strings.Join(elem[i:], string(Separator))) + return Clean(strings.Join(elem[i:], SeparatorString)) } } return "" @@ -169,6 +175,62 @@ func Ext(path string) string { return "" } +// EvalSymlinks returns the path name after the evaluation of any symbolic +// links. +// If path is relative it will be evaluated relative to the current directory. +func EvalSymlinks(path string) (string, os.Error) { + const maxIter = 255 + originalPath := path + // consume path by taking each frontmost path element, + // expanding it if it's a symlink, and appending it to b + var b bytes.Buffer + for n := 0; path != ""; n++ { + if n > maxIter { + return "", os.NewError("EvalSymlinks: too many links in " + originalPath) + } + + // find next path component, p + i := strings.IndexRune(path, Separator) + var p string + if i == -1 { + p, path = path, "" + } else { + p, path = path[:i], path[i+1:] + } + + if p == "" { + if b.Len() == 0 { + // must be absolute path + b.WriteRune(Separator) + } + continue + } + + fi, err := os.Lstat(b.String() + p) + if err != nil { + return "", err + } + if !fi.IsSymlink() { + b.WriteString(p) + if path != "" { + b.WriteRune(Separator) + } + continue + } + + // it's a symlink, put it at the front of path + dest, err := os.Readlink(b.String() + p) + if err != nil { + return "", err + } + if IsAbs(dest) { + b.Reset() + } + path = dest + SeparatorString + path + } + return Clean(b.String()), nil +} + // Visitor methods are invoked for corresponding file tree entries // visited by Walk. The parameter path is the full path of f relative // to root. @@ -267,7 +329,7 @@ func Base(path string) string { } // If empty now, it had only slashes. if path == "" { - return string(Separator) + return SeparatorString } return path } diff --git a/src/pkg/path/filepath/path_test.go b/src/pkg/path/filepath/path_test.go index c23cb6c0ec..e07b6b0c6f 100644 --- a/src/pkg/path/filepath/path_test.go +++ b/src/pkg/path/filepath/path_test.go @@ -414,3 +414,63 @@ func TestIsAbs(t *testing.T) { } } } + +type EvalSymlinksTest struct { + path, dest string +} + +var EvalSymlinksTestDirs = []EvalSymlinksTest{ + {"test", ""}, + {"test/dir", ""}, + {"test/dir/link3", "../../"}, + {"test/link1", "../test"}, + {"test/link2", "dir"}, +} + +var EvalSymlinksTests = []EvalSymlinksTest{ + {"test", "test"}, + {"test/dir", "test/dir"}, + {"test/dir/../..", "."}, + {"test/link1", "test"}, + {"test/link2", "test/dir"}, + {"test/link1/dir", "test/dir"}, + {"test/link2/..", "test"}, + {"test/dir/link3", "."}, + {"test/link2/link3/test", "test"}, +} + +func TestEvalSymlinks(t *testing.T) { + defer os.RemoveAll("test") + for _, d := range EvalSymlinksTestDirs { + var err os.Error + if d.dest == "" { + err = os.Mkdir(d.path, 0755) + } else { + err = os.Symlink(d.dest, d.path) + } + if err != nil { + t.Fatal(err) + } + } + // relative + for _, d := range EvalSymlinksTests { + if p, err := filepath.EvalSymlinks(d.path); err != nil { + t.Errorf("EvalSymlinks(%v) error: %v", d.path, err) + } else if p != d.dest { + t.Errorf("EvalSymlinks(%v)=%v, want %v", d.path, p, d.dest) + } + } + // absolute + testroot := filepath.Join(os.Getenv("GOROOT"), "src", "pkg", "path", "filepath") + for _, d := range EvalSymlinksTests { + a := EvalSymlinksTest{ + filepath.Join(testroot, d.path), + filepath.Join(testroot, d.dest), + } + if p, err := filepath.EvalSymlinks(a.path); err != nil { + t.Errorf("EvalSymlinks(%v) error: %v", a.path, err) + } else if p != a.dest { + t.Errorf("EvalSymlinks(%v)=%v, want %v", a.path, p, a.dest) + } + } +}