github.com/demonoid81/moby@v0.0.0-20200517203328-62dd8e17c460/pkg/symlink/fs_windows.go (about) 1 package symlink // import "github.com/demonoid81/moby/pkg/symlink" 2 3 import ( 4 "bytes" 5 "errors" 6 "os" 7 "path/filepath" 8 "strings" 9 10 "golang.org/x/sys/windows" 11 ) 12 13 func toShort(path string) (string, error) { 14 p, err := windows.UTF16FromString(path) 15 if err != nil { 16 return "", err 17 } 18 b := p // GetShortPathName says we can reuse buffer 19 n, err := windows.GetShortPathName(&p[0], &b[0], uint32(len(b))) 20 if err != nil { 21 return "", err 22 } 23 if n > uint32(len(b)) { 24 b = make([]uint16, n) 25 if _, err = windows.GetShortPathName(&p[0], &b[0], uint32(len(b))); err != nil { 26 return "", err 27 } 28 } 29 return windows.UTF16ToString(b), nil 30 } 31 32 func toLong(path string) (string, error) { 33 p, err := windows.UTF16FromString(path) 34 if err != nil { 35 return "", err 36 } 37 b := p // GetLongPathName says we can reuse buffer 38 n, err := windows.GetLongPathName(&p[0], &b[0], uint32(len(b))) 39 if err != nil { 40 return "", err 41 } 42 if n > uint32(len(b)) { 43 b = make([]uint16, n) 44 n, err = windows.GetLongPathName(&p[0], &b[0], uint32(len(b))) 45 if err != nil { 46 return "", err 47 } 48 } 49 b = b[:n] 50 return windows.UTF16ToString(b), nil 51 } 52 53 func evalSymlinks(path string) (string, error) { 54 path, err := walkSymlinks(path) 55 if err != nil { 56 return "", err 57 } 58 59 p, err := toShort(path) 60 if err != nil { 61 return "", err 62 } 63 p, err = toLong(p) 64 if err != nil { 65 return "", err 66 } 67 // windows.GetLongPathName does not change the case of the drive letter, 68 // but the result of EvalSymlinks must be unique, so we have 69 // EvalSymlinks(`c:\a`) == EvalSymlinks(`C:\a`). 70 // Make drive letter upper case. 71 if len(p) >= 2 && p[1] == ':' && 'a' <= p[0] && p[0] <= 'z' { 72 p = string(p[0]+'A'-'a') + p[1:] 73 } else if len(p) >= 6 && p[5] == ':' && 'a' <= p[4] && p[4] <= 'z' { 74 p = p[:3] + string(p[4]+'A'-'a') + p[5:] 75 } 76 return filepath.Clean(p), nil 77 } 78 79 const ( 80 utf8RuneSelf = 0x80 81 longPathPrefix = `\\?\` 82 ) 83 84 func walkSymlinks(path string) (string, error) { 85 const maxIter = 255 86 originalPath := path 87 // consume path by taking each frontmost path element, 88 // expanding it if it's a symlink, and appending it to b 89 var b bytes.Buffer 90 for n := 0; path != ""; n++ { 91 if n > maxIter { 92 return "", errors.New("EvalSymlinks: too many links in " + originalPath) 93 } 94 95 // A path beginning with `\\?\` represents the root, so automatically 96 // skip that part and begin processing the next segment. 97 if strings.HasPrefix(path, longPathPrefix) { 98 b.WriteString(longPathPrefix) 99 path = path[4:] 100 continue 101 } 102 103 // find next path component, p 104 var i = -1 105 for j, c := range path { 106 if c < utf8RuneSelf && os.IsPathSeparator(uint8(c)) { 107 i = j 108 break 109 } 110 } 111 var p string 112 if i == -1 { 113 p, path = path, "" 114 } else { 115 p, path = path[:i], path[i+1:] 116 } 117 118 if p == "" { 119 if b.Len() == 0 { 120 // must be absolute path 121 b.WriteRune(filepath.Separator) 122 } 123 continue 124 } 125 126 // If this is the first segment after the long path prefix, accept the 127 // current segment as a volume root or UNC share and move on to the next. 128 if b.String() == longPathPrefix { 129 b.WriteString(p) 130 b.WriteRune(filepath.Separator) 131 continue 132 } 133 134 fi, err := os.Lstat(b.String() + p) 135 if err != nil { 136 return "", err 137 } 138 if fi.Mode()&os.ModeSymlink == 0 { 139 b.WriteString(p) 140 if path != "" || (b.Len() == 2 && len(p) == 2 && p[1] == ':') { 141 b.WriteRune(filepath.Separator) 142 } 143 continue 144 } 145 146 // it's a symlink, put it at the front of path 147 dest, err := os.Readlink(b.String() + p) 148 if err != nil { 149 return "", err 150 } 151 if isAbs(dest) { 152 b.Reset() 153 } 154 path = dest + string(filepath.Separator) + path 155 } 156 return filepath.Clean(b.String()), nil 157 } 158 159 func isDriveOrRoot(p string) bool { 160 if p == string(filepath.Separator) { 161 return true 162 } 163 164 length := len(p) 165 if length >= 2 { 166 if p[length-1] == ':' && (('a' <= p[length-2] && p[length-2] <= 'z') || ('A' <= p[length-2] && p[length-2] <= 'Z')) { 167 return true 168 } 169 } 170 return false 171 } 172 173 // isAbs is a platform-specific wrapper for filepath.IsAbs. On Windows, 174 // golang filepath.IsAbs does not consider a path \windows\system32 as absolute 175 // as it doesn't start with a drive-letter/colon combination. However, in 176 // docker we need to verify things such as WORKDIR /windows/system32 in 177 // a Dockerfile (which gets translated to \windows\system32 when being processed 178 // by the daemon. This SHOULD be treated as absolute from a docker processing 179 // perspective. 180 func isAbs(path string) bool { 181 if filepath.IsAbs(path) || strings.HasPrefix(path, string(os.PathSeparator)) { 182 return true 183 } 184 return false 185 }