github.com/tinygo-org/tinygo@v0.31.3-0.20240404173401-90b0bf646c27/src/os/path_windows.go (about) 1 // Copyright 2011 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 package os 6 7 const ( 8 PathSeparator = '\\' // OS-specific path separator 9 PathListSeparator = ';' // OS-specific path list separator 10 ) 11 12 // IsPathSeparator reports whether c is a directory separator character. 13 func IsPathSeparator(c uint8) bool { 14 // NOTE: Windows accept / as path separator. 15 return c == '\\' || c == '/' 16 } 17 18 // basename removes trailing slashes and the leading 19 // directory name and drive letter from path name. 20 func basename(name string) string { 21 // Remove drive letter 22 if len(name) == 2 && name[1] == ':' { 23 name = "." 24 } else if len(name) > 2 && name[1] == ':' { 25 name = name[2:] 26 } 27 i := len(name) - 1 28 // Remove trailing slashes 29 for ; i > 0 && (name[i] == '/' || name[i] == '\\'); i-- { 30 name = name[:i] 31 } 32 // Remove leading directory name 33 for i--; i >= 0; i-- { 34 if name[i] == '/' || name[i] == '\\' { 35 name = name[i+1:] 36 break 37 } 38 } 39 return name 40 } 41 42 func isAbs(path string) (b bool) { 43 v := volumeName(path) 44 if v == "" { 45 return false 46 } 47 path = path[len(v):] 48 if path == "" { 49 return false 50 } 51 return IsPathSeparator(path[0]) 52 } 53 54 func volumeName(path string) (v string) { 55 if len(path) < 2 { 56 return "" 57 } 58 // with drive letter 59 c := path[0] 60 if path[1] == ':' && 61 ('0' <= c && c <= '9' || 'a' <= c && c <= 'z' || 62 'A' <= c && c <= 'Z') { 63 return path[:2] 64 } 65 // is it UNC 66 if l := len(path); l >= 5 && IsPathSeparator(path[0]) && IsPathSeparator(path[1]) && 67 !IsPathSeparator(path[2]) && path[2] != '.' { 68 // first, leading `\\` and next shouldn't be `\`. its server name. 69 for n := 3; n < l-1; n++ { 70 // second, next '\' shouldn't be repeated. 71 if IsPathSeparator(path[n]) { 72 n++ 73 // third, following something characters. its share name. 74 if !IsPathSeparator(path[n]) { 75 if path[n] == '.' { 76 break 77 } 78 for ; n < l; n++ { 79 if IsPathSeparator(path[n]) { 80 break 81 } 82 } 83 return path[:n] 84 } 85 break 86 } 87 } 88 } 89 return "" 90 } 91 92 func fromSlash(path string) string { 93 // Replace each '/' with '\\' if present 94 var pathbuf []byte 95 var lastSlash int 96 for i, b := range path { 97 if b == '/' { 98 if pathbuf == nil { 99 pathbuf = make([]byte, len(path)) 100 } 101 copy(pathbuf[lastSlash:], path[lastSlash:i]) 102 pathbuf[i] = '\\' 103 lastSlash = i + 1 104 } 105 } 106 if pathbuf == nil { 107 return path 108 } 109 110 copy(pathbuf[lastSlash:], path[lastSlash:]) 111 return string(pathbuf) 112 } 113 114 func dirname(path string) string { 115 vol := volumeName(path) 116 i := len(path) - 1 117 for i >= len(vol) && !IsPathSeparator(path[i]) { 118 i-- 119 } 120 dir := path[len(vol) : i+1] 121 last := len(dir) - 1 122 if last > 0 && IsPathSeparator(dir[last]) { 123 dir = dir[:last] 124 } 125 if dir == "" { 126 dir = "." 127 } 128 return vol + dir 129 } 130 131 // This is set via go:linkname on runtime.canUseLongPaths, and is true when the OS 132 // supports opting into proper long path handling without the need for fixups. 133 var canUseLongPaths bool 134 135 // fixLongPath returns the extended-length (\\?\-prefixed) form of 136 // path when needed, in order to avoid the default 260 character file 137 // path limit imposed by Windows. If path is not easily converted to 138 // the extended-length form (for example, if path is a relative path 139 // or contains .. elements), or is short enough, fixLongPath returns 140 // path unmodified. 141 // 142 // See https://msdn.microsoft.com/en-us/library/windows/desktop/aa365247(v=vs.85).aspx#maxpath 143 func fixLongPath(path string) string { 144 if canUseLongPaths { 145 return path 146 } 147 // Do nothing (and don't allocate) if the path is "short". 148 // Empirically (at least on the Windows Server 2013 builder), 149 // the kernel is arbitrarily okay with < 248 bytes. That 150 // matches what the docs above say: 151 // "When using an API to create a directory, the specified 152 // path cannot be so long that you cannot append an 8.3 file 153 // name (that is, the directory name cannot exceed MAX_PATH 154 // minus 12)." Since MAX_PATH is 260, 260 - 12 = 248. 155 // 156 // The MSDN docs appear to say that a normal path that is 248 bytes long 157 // will work; empirically the path must be less then 248 bytes long. 158 if len(path) < 248 { 159 // Don't fix. (This is how Go 1.7 and earlier worked, 160 // not automatically generating the \\?\ form) 161 return path 162 } 163 164 // The extended form begins with \\?\, as in 165 // \\?\c:\windows\foo.txt or \\?\UNC\server\share\foo.txt. 166 // The extended form disables evaluation of . and .. path 167 // elements and disables the interpretation of / as equivalent 168 // to \. The conversion here rewrites / to \ and elides 169 // . elements as well as trailing or duplicate separators. For 170 // simplicity it avoids the conversion entirely for relative 171 // paths or paths containing .. elements. For now, 172 // \\server\share paths are not converted to 173 // \\?\UNC\server\share paths because the rules for doing so 174 // are less well-specified. 175 if len(path) >= 2 && path[:2] == `\\` { 176 // Don't canonicalize UNC paths. 177 return path 178 } 179 if !isAbs(path) { 180 // Relative path 181 return path 182 } 183 184 const prefix = `\\?` 185 186 pathbuf := make([]byte, len(prefix)+len(path)+len(`\`)) 187 copy(pathbuf, prefix) 188 n := len(path) 189 r, w := 0, len(prefix) 190 for r < n { 191 switch { 192 case IsPathSeparator(path[r]): 193 // empty block 194 r++ 195 case path[r] == '.' && (r+1 == n || IsPathSeparator(path[r+1])): 196 // /./ 197 r++ 198 case r+1 < n && path[r] == '.' && path[r+1] == '.' && (r+2 == n || IsPathSeparator(path[r+2])): 199 // /../ is currently unhandled 200 return path 201 default: 202 pathbuf[w] = '\\' 203 w++ 204 for ; r < n && !IsPathSeparator(path[r]); r++ { 205 pathbuf[w] = path[r] 206 w++ 207 } 208 } 209 } 210 // A drive's root directory needs a trailing \ 211 if w == len(`\\?\c:`) { 212 pathbuf[w] = '\\' 213 w++ 214 } 215 return string(pathbuf[:w]) 216 } 217 218 // fixRootDirectory fixes a reference to a drive's root directory to 219 // have the required trailing slash. 220 func fixRootDirectory(p string) string { 221 if len(p) == len(`\\?\c:`) { 222 if IsPathSeparator(p[0]) && IsPathSeparator(p[1]) && p[2] == '?' && IsPathSeparator(p[3]) && p[5] == ':' { 223 return p + `\` 224 } 225 } 226 return p 227 }