github.com/sijibomii/docker@v0.0.0-20231230191044-5cf6ca554647/pkg/symlink/fs_windows.go (about) 1 package symlink 2 3 import ( 4 "bytes" 5 "errors" 6 "os" 7 "path/filepath" 8 "strings" 9 "syscall" 10 11 "github.com/docker/docker/pkg/longpath" 12 ) 13 14 func toShort(path string) (string, error) { 15 p, err := syscall.UTF16FromString(path) 16 if err != nil { 17 return "", err 18 } 19 b := p // GetShortPathName says we can reuse buffer 20 n, err := syscall.GetShortPathName(&p[0], &b[0], uint32(len(b))) 21 if err != nil { 22 return "", err 23 } 24 if n > uint32(len(b)) { 25 b = make([]uint16, n) 26 if _, err = syscall.GetShortPathName(&p[0], &b[0], uint32(len(b))); err != nil { 27 return "", err 28 } 29 } 30 return syscall.UTF16ToString(b), nil 31 } 32 33 func toLong(path string) (string, error) { 34 p, err := syscall.UTF16FromString(path) 35 if err != nil { 36 return "", err 37 } 38 b := p // GetLongPathName says we can reuse buffer 39 n, err := syscall.GetLongPathName(&p[0], &b[0], uint32(len(b))) 40 if err != nil { 41 return "", err 42 } 43 if n > uint32(len(b)) { 44 b = make([]uint16, n) 45 n, err = syscall.GetLongPathName(&p[0], &b[0], uint32(len(b))) 46 if err != nil { 47 return "", err 48 } 49 } 50 b = b[:n] 51 return syscall.UTF16ToString(b), nil 52 } 53 54 func evalSymlinks(path string) (string, error) { 55 path, err := walkSymlinks(path) 56 if err != nil { 57 return "", err 58 } 59 60 p, err := toShort(path) 61 if err != nil { 62 return "", err 63 } 64 p, err = toLong(p) 65 if err != nil { 66 return "", err 67 } 68 // syscall.GetLongPathName does not change the case of the drive letter, 69 // but the result of EvalSymlinks must be unique, so we have 70 // EvalSymlinks(`c:\a`) == EvalSymlinks(`C:\a`). 71 // Make drive letter upper case. 72 if len(p) >= 2 && p[1] == ':' && 'a' <= p[0] && p[0] <= 'z' { 73 p = string(p[0]+'A'-'a') + p[1:] 74 } else if len(p) >= 6 && p[5] == ':' && 'a' <= p[4] && p[4] <= 'z' { 75 p = p[:3] + string(p[4]+'A'-'a') + p[5:] 76 } 77 return filepath.Clean(p), nil 78 } 79 80 const utf8RuneSelf = 0x80 81 82 func walkSymlinks(path string) (string, error) { 83 const maxIter = 255 84 originalPath := path 85 // consume path by taking each frontmost path element, 86 // expanding it if it's a symlink, and appending it to b 87 var b bytes.Buffer 88 for n := 0; path != ""; n++ { 89 if n > maxIter { 90 return "", errors.New("EvalSymlinks: too many links in " + originalPath) 91 } 92 93 // A path beginning with `\\?\` represents the root, so automatically 94 // skip that part and begin processing the next segment. 95 if strings.HasPrefix(path, longpath.Prefix) { 96 b.WriteString(longpath.Prefix) 97 path = path[4:] 98 continue 99 } 100 101 // find next path component, p 102 var i = -1 103 for j, c := range path { 104 if c < utf8RuneSelf && os.IsPathSeparator(uint8(c)) { 105 i = j 106 break 107 } 108 } 109 var p string 110 if i == -1 { 111 p, path = path, "" 112 } else { 113 p, path = path[:i], path[i+1:] 114 } 115 116 if p == "" { 117 if b.Len() == 0 { 118 // must be absolute path 119 b.WriteRune(filepath.Separator) 120 } 121 continue 122 } 123 124 // If this is the first segment after the long path prefix, accept the 125 // current segment as a volume root or UNC share and move on to the next. 126 if b.String() == longpath.Prefix { 127 b.WriteString(p) 128 b.WriteRune(filepath.Separator) 129 continue 130 } 131 132 fi, err := os.Lstat(b.String() + p) 133 if err != nil { 134 return "", err 135 } 136 if fi.Mode()&os.ModeSymlink == 0 { 137 b.WriteString(p) 138 if path != "" || (b.Len() == 2 && len(p) == 2 && p[1] == ':') { 139 b.WriteRune(filepath.Separator) 140 } 141 continue 142 } 143 144 // it's a symlink, put it at the front of path 145 dest, err := os.Readlink(b.String() + p) 146 if err != nil { 147 return "", err 148 } 149 if filepath.IsAbs(dest) || os.IsPathSeparator(dest[0]) { 150 b.Reset() 151 } 152 path = dest + string(filepath.Separator) + path 153 } 154 return filepath.Clean(b.String()), nil 155 }