2024-07-25 08:55:23 -04:00
|
|
|
// Copyright 2012 The Go Authors. All rights reserved.
|
|
|
|
// Use of this source code is governed by a BSD-style
|
|
|
|
// license that can be found in the LICENSE.BSD file.
|
|
|
|
|
|
|
|
// This code is a modified version of [path/filepath/symlink_windows.go]
|
|
|
|
// and [path/filepath/symlink.go] from the Go 1.4.2 standard library, and
|
|
|
|
// added in [docker@9b648df].
|
|
|
|
//
|
|
|
|
// [path/filepath/symlink_windows.go]: https://github.com/golang/go/blob/go1.4.2/src/path/filepath/symlink_windows.go
|
|
|
|
// [path/filepath/symlink.go]: https://github.com/golang/go/blob/go1.4.2/src/path/filepath/symlink.go
|
|
|
|
// [docker@9b648df]: https://github.com/moby/moby/commit/9b648dfac6453de5944ee4bb749115d85a253a05
|
|
|
|
|
2020-11-10 08:26:15 -05:00
|
|
|
package symlink
|
2017-04-17 18:08:24 -04:00
|
|
|
|
|
|
|
import (
|
|
|
|
"bytes"
|
|
|
|
"errors"
|
|
|
|
"os"
|
|
|
|
"path/filepath"
|
|
|
|
"strings"
|
|
|
|
|
2017-08-07 05:52:40 -04:00
|
|
|
"golang.org/x/sys/windows"
|
2017-04-17 18:08:24 -04:00
|
|
|
)
|
|
|
|
|
|
|
|
func toShort(path string) (string, error) {
|
2017-08-07 05:52:40 -04:00
|
|
|
p, err := windows.UTF16FromString(path)
|
2017-04-17 18:08:24 -04:00
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
b := p // GetShortPathName says we can reuse buffer
|
2017-08-07 05:52:40 -04:00
|
|
|
n, err := windows.GetShortPathName(&p[0], &b[0], uint32(len(b)))
|
2017-04-17 18:08:24 -04:00
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
if n > uint32(len(b)) {
|
|
|
|
b = make([]uint16, n)
|
2017-08-07 05:52:40 -04:00
|
|
|
if _, err = windows.GetShortPathName(&p[0], &b[0], uint32(len(b))); err != nil {
|
2017-04-17 18:08:24 -04:00
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
}
|
2017-08-07 05:52:40 -04:00
|
|
|
return windows.UTF16ToString(b), nil
|
2017-04-17 18:08:24 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
func toLong(path string) (string, error) {
|
2017-08-07 05:52:40 -04:00
|
|
|
p, err := windows.UTF16FromString(path)
|
2017-04-17 18:08:24 -04:00
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
b := p // GetLongPathName says we can reuse buffer
|
2017-08-07 05:52:40 -04:00
|
|
|
n, err := windows.GetLongPathName(&p[0], &b[0], uint32(len(b)))
|
2017-04-17 18:08:24 -04:00
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
if n > uint32(len(b)) {
|
|
|
|
b = make([]uint16, n)
|
2017-08-07 05:52:40 -04:00
|
|
|
n, err = windows.GetLongPathName(&p[0], &b[0], uint32(len(b)))
|
2017-04-17 18:08:24 -04:00
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
b = b[:n]
|
2017-08-07 05:52:40 -04:00
|
|
|
return windows.UTF16ToString(b), nil
|
2017-04-17 18:08:24 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
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
|
|
|
|
}
|
2017-08-07 05:52:40 -04:00
|
|
|
// windows.GetLongPathName does not change the case of the drive letter,
|
2017-04-17 18:08:24 -04:00
|
|
|
// 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
|
|
|
|
}
|
|
|
|
|
2020-04-16 05:23:37 -04:00
|
|
|
const (
|
|
|
|
utf8RuneSelf = 0x80
|
|
|
|
longPathPrefix = `\\?\`
|
|
|
|
)
|
2017-04-17 18:08:24 -04:00
|
|
|
|
|
|
|
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 {
|
2021-09-16 10:17:20 -04:00
|
|
|
return "", errors.New("too many links in " + originalPath)
|
2017-04-17 18:08:24 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
// A path beginning with `\\?\` represents the root, so automatically
|
|
|
|
// skip that part and begin processing the next segment.
|
2020-04-16 05:23:37 -04:00
|
|
|
if strings.HasPrefix(path, longPathPrefix) {
|
|
|
|
b.WriteString(longPathPrefix)
|
2017-04-17 18:08:24 -04:00
|
|
|
path = path[4:]
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
// find next path component, p
|
2021-09-16 10:17:20 -04:00
|
|
|
i := -1
|
2017-04-17 18:08:24 -04:00
|
|
|
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.
|
2020-04-16 05:23:37 -04:00
|
|
|
if b.String() == longPathPrefix {
|
2017-04-17 18:08:24 -04:00
|
|
|
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
|
|
|
|
}
|
2020-04-16 05:23:37 -04:00
|
|
|
if isAbs(dest) {
|
2017-04-17 18:08:24 -04:00
|
|
|
b.Reset()
|
|
|
|
}
|
|
|
|
path = dest + string(filepath.Separator) + path
|
|
|
|
}
|
|
|
|
return filepath.Clean(b.String()), nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func isDriveOrRoot(p string) bool {
|
|
|
|
if p == string(filepath.Separator) {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
|
|
|
length := len(p)
|
|
|
|
if length >= 2 {
|
|
|
|
if p[length-1] == ':' && (('a' <= p[length-2] && p[length-2] <= 'z') || ('A' <= p[length-2] && p[length-2] <= 'Z')) {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false
|
|
|
|
}
|
2020-04-16 05:23:37 -04:00
|
|
|
|
|
|
|
// isAbs is a platform-specific wrapper for filepath.IsAbs. On Windows,
|
|
|
|
// golang filepath.IsAbs does not consider a path \windows\system32 as absolute
|
|
|
|
// as it doesn't start with a drive-letter/colon combination. However, in
|
|
|
|
// docker we need to verify things such as WORKDIR /windows/system32 in
|
|
|
|
// a Dockerfile (which gets translated to \windows\system32 when being processed
|
|
|
|
// by the daemon. This SHOULD be treated as absolute from a docker processing
|
|
|
|
// perspective.
|
|
|
|
func isAbs(path string) bool {
|
|
|
|
if filepath.IsAbs(path) || strings.HasPrefix(path, string(os.PathSeparator)) {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
return false
|
|
|
|
}
|