|
@@ -0,0 +1,156 @@
|
|
|
|
+package symlink
|
|
|
|
+
|
|
|
|
+import (
|
|
|
|
+ "bytes"
|
|
|
|
+ "errors"
|
|
|
|
+ "os"
|
|
|
|
+ "path/filepath"
|
|
|
|
+ "strings"
|
|
|
|
+ "syscall"
|
|
|
|
+
|
|
|
|
+ "github.com/docker/docker/pkg/longpath"
|
|
|
|
+)
|
|
|
|
+
|
|
|
|
+func toShort(path string) (string, error) {
|
|
|
|
+ p, err := syscall.UTF16FromString(path)
|
|
|
|
+ if err != nil {
|
|
|
|
+ return "", err
|
|
|
|
+ }
|
|
|
|
+ b := p // GetShortPathName says we can reuse buffer
|
|
|
|
+ n, err := syscall.GetShortPathName(&p[0], &b[0], uint32(len(b)))
|
|
|
|
+ if err != nil {
|
|
|
|
+ return "", err
|
|
|
|
+ }
|
|
|
|
+ if n > uint32(len(b)) {
|
|
|
|
+ b = make([]uint16, n)
|
|
|
|
+ n, err = syscall.GetShortPathName(&p[0], &b[0], uint32(len(b)))
|
|
|
|
+ if err != nil {
|
|
|
|
+ return "", err
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ return syscall.UTF16ToString(b), nil
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+func toLong(path string) (string, error) {
|
|
|
|
+ p, err := syscall.UTF16FromString(path)
|
|
|
|
+ if err != nil {
|
|
|
|
+ return "", err
|
|
|
|
+ }
|
|
|
|
+ b := p // GetLongPathName says we can reuse buffer
|
|
|
|
+ n, err := syscall.GetLongPathName(&p[0], &b[0], uint32(len(b)))
|
|
|
|
+ if err != nil {
|
|
|
|
+ return "", err
|
|
|
|
+ }
|
|
|
|
+ if n > uint32(len(b)) {
|
|
|
|
+ b = make([]uint16, n)
|
|
|
|
+ n, err = syscall.GetLongPathName(&p[0], &b[0], uint32(len(b)))
|
|
|
|
+ if err != nil {
|
|
|
|
+ return "", err
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ b = b[:n]
|
|
|
|
+ return syscall.UTF16ToString(b), nil
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+func evalSymlinks(path string) (string, error) {
|
|
|
|
+ path, err := walkSymlinks(path)
|
|
|
|
+ if err != nil {
|
|
|
|
+ return "", err
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ p, err := toShort(path)
|
|
|
|
+ if err != nil {
|
|
|
|
+ return "", err
|
|
|
|
+ }
|
|
|
|
+ p, err = toLong(p)
|
|
|
|
+ if err != nil {
|
|
|
|
+ return "", err
|
|
|
|
+ }
|
|
|
|
+ // syscall.GetLongPathName does not change the case of the drive letter,
|
|
|
|
+ // but the result of EvalSymlinks must be unique, so we have
|
|
|
|
+ // EvalSymlinks(`c:\a`) == EvalSymlinks(`C:\a`).
|
|
|
|
+ // Make drive letter upper case.
|
|
|
|
+ if len(p) >= 2 && p[1] == ':' && 'a' <= p[0] && p[0] <= 'z' {
|
|
|
|
+ p = string(p[0]+'A'-'a') + p[1:]
|
|
|
|
+ } else if len(p) >= 6 && p[5] == ':' && 'a' <= p[4] && p[4] <= 'z' {
|
|
|
|
+ p = p[:3] + string(p[4]+'A'-'a') + p[5:]
|
|
|
|
+ }
|
|
|
|
+ return filepath.Clean(p), nil
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+const utf8RuneSelf = 0x80
|
|
|
|
+
|
|
|
|
+func walkSymlinks(path string) (string, 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 "", errors.New("EvalSymlinks: too many links in " + originalPath)
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // A path beginnging with `\\?\` represents the root, so automatically
|
|
|
|
+ // skip that part and begin processing the next segment.
|
|
|
|
+ if strings.HasPrefix(path, longpath.Prefix) {
|
|
|
|
+ b.WriteString(longpath.Prefix)
|
|
|
|
+ path = path[4:]
|
|
|
|
+ continue
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // find next path component, p
|
|
|
|
+ var i = -1
|
|
|
|
+ for j, c := range path {
|
|
|
|
+ if c < utf8RuneSelf && os.IsPathSeparator(uint8(c)) {
|
|
|
|
+ i = j
|
|
|
|
+ break
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ 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(filepath.Separator)
|
|
|
|
+ }
|
|
|
|
+ continue
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // If this is the first segment after the long path prefix, accept the
|
|
|
|
+ // current segment as a volume root or UNC share and move on to the next.
|
|
|
|
+ if b.String() == longpath.Prefix {
|
|
|
|
+ b.WriteString(p)
|
|
|
|
+ b.WriteRune(filepath.Separator)
|
|
|
|
+ continue
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ fi, err := os.Lstat(b.String() + p)
|
|
|
|
+ if err != nil {
|
|
|
|
+ return "", err
|
|
|
|
+ }
|
|
|
|
+ if fi.Mode()&os.ModeSymlink == 0 {
|
|
|
|
+ b.WriteString(p)
|
|
|
|
+ if path != "" || (b.Len() == 2 && len(p) == 2 && p[1] == ':') {
|
|
|
|
+ b.WriteRune(filepath.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 filepath.IsAbs(dest) || os.IsPathSeparator(dest[0]) {
|
|
|
|
+ b.Reset()
|
|
|
|
+ }
|
|
|
|
+ path = dest + string(filepath.Separator) + path
|
|
|
|
+ }
|
|
|
|
+ return filepath.Clean(b.String()), nil
|
|
|
|
+}
|