Source file src/os/path_windows.go
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 import ( 8 "internal/filepathlite" 9 "internal/syscall/windows" 10 "syscall" 11 ) 12 13 const ( 14 PathSeparator = '\\' // OS-specific path separator 15 PathListSeparator = ';' // OS-specific path list separator 16 ) 17 18 // IsPathSeparator reports whether c is a directory separator character. 19 func IsPathSeparator(c uint8) bool { 20 // NOTE: Windows accepts / as path separator. 21 return c == '\\' || c == '/' 22 } 23 24 // splitPath returns the base name and parent directory. 25 func splitPath(path string) (string, string) { 26 if path == "" { 27 return ".", "." 28 } 29 30 // The first prefixlen bytes are part of the parent directory. 31 // The prefix consists of the volume name (if any) and the first \ (if significant). 32 prefixlen := filepathlite.VolumeNameLen(path) 33 if len(path) > prefixlen && IsPathSeparator(path[prefixlen]) { 34 if prefixlen == 0 { 35 // This is a path relative to the current volume, like \foo. 36 // Include the initial \ in the prefix. 37 prefixlen = 1 38 } else if path[prefixlen-1] == ':' { 39 // This is an absolute path on a named drive, like c:\foo. 40 // Include the initial \ in the prefix. 41 prefixlen++ 42 } 43 } 44 45 i := len(path) - 1 46 47 // Remove trailing slashes. 48 for i >= prefixlen && IsPathSeparator(path[i]) { 49 i-- 50 } 51 path = path[:i+1] 52 53 // Find the last path separator. The basename is what follows. 54 for i >= prefixlen && !IsPathSeparator(path[i]) { 55 i-- 56 } 57 basename := path[i+1:] 58 if basename == "" { 59 basename = "." 60 } 61 62 // Remove trailing slashes. The remainder is dirname. 63 for i >= prefixlen && IsPathSeparator(path[i]) { 64 i-- 65 } 66 dirname := path[:i+1] 67 if dirname == "" { 68 dirname = "." 69 } 70 71 return dirname, basename 72 } 73 74 func dirname(path string) string { 75 vol := filepathlite.VolumeName(path) 76 i := len(path) - 1 77 for i >= len(vol) && !IsPathSeparator(path[i]) { 78 i-- 79 } 80 dir := path[len(vol) : i+1] 81 last := len(dir) - 1 82 if last > 0 && IsPathSeparator(dir[last]) { 83 dir = dir[:last] 84 } 85 if dir == "" { 86 dir = "." 87 } 88 return vol + dir 89 } 90 91 // fixLongPath returns the extended-length (\\?\-prefixed) form of 92 // path when needed, in order to avoid the default 260 character file 93 // path limit imposed by Windows. If the path is short enough or already 94 // has the extended-length prefix, fixLongPath returns path unmodified. 95 // If the path is relative and joining it with the current working 96 // directory results in a path that is too long, fixLongPath returns 97 // the absolute path with the extended-length prefix. 98 // 99 // See https://learn.microsoft.com/en-us/windows/win32/fileio/naming-a-file#maximum-path-length-limitation 100 func fixLongPath(path string) string { 101 if windows.CanUseLongPaths { 102 return path 103 } 104 return addExtendedPrefix(path) 105 } 106 107 // addExtendedPrefix adds the extended path prefix (\\?\) to path. 108 func addExtendedPrefix(path string) string { 109 if len(path) >= 4 { 110 if path[:4] == `\??\` { 111 // Already extended with \??\ 112 return path 113 } 114 if IsPathSeparator(path[0]) && IsPathSeparator(path[1]) && path[2] == '?' && IsPathSeparator(path[3]) { 115 // Already extended with \\?\ or any combination of directory separators. 116 return path 117 } 118 } 119 120 // Do nothing (and don't allocate) if the path is "short". 121 // Empirically (at least on the Windows Server 2013 builder), 122 // the kernel is arbitrarily okay with < 248 bytes. That 123 // matches what the docs above say: 124 // "When using an API to create a directory, the specified 125 // path cannot be so long that you cannot append an 8.3 file 126 // name (that is, the directory name cannot exceed MAX_PATH 127 // minus 12)." Since MAX_PATH is 260, 260 - 12 = 248. 128 // 129 // The MSDN docs appear to say that a normal path that is 248 bytes long 130 // will work; empirically the path must be less then 248 bytes long. 131 pathLength := len(path) 132 if !filepathlite.IsAbs(path) { 133 // If the path is relative, we need to prepend the working directory 134 // plus a separator to the path before we can determine if it's too long. 135 // We don't want to call syscall.Getwd here, as that call is expensive to do 136 // every time fixLongPath is called with a relative path, so we use a cache. 137 // Note that getwdCache might be outdated if the working directory has been 138 // changed without using os.Chdir, i.e. using syscall.Chdir directly or cgo. 139 // This is fine, as the worst that can happen is that we fail to fix the path. 140 getwdCache.Lock() 141 if getwdCache.dir == "" { 142 // Init the working directory cache. 143 getwdCache.dir, _ = syscall.Getwd() 144 } 145 pathLength += len(getwdCache.dir) + 1 146 getwdCache.Unlock() 147 } 148 149 if pathLength < 248 { 150 // Don't fix. (This is how Go 1.7 and earlier worked, 151 // not automatically generating the \\?\ form) 152 return path 153 } 154 155 var isUNC, isDevice bool 156 if len(path) >= 2 && IsPathSeparator(path[0]) && IsPathSeparator(path[1]) { 157 if len(path) >= 4 && path[2] == '.' && IsPathSeparator(path[3]) { 158 // Starts with //./ 159 isDevice = true 160 } else { 161 // Starts with // 162 isUNC = true 163 } 164 } 165 var prefix []uint16 166 if isUNC { 167 // UNC path, prepend the \\?\UNC\ prefix. 168 prefix = []uint16{'\\', '\\', '?', '\\', 'U', 'N', 'C', '\\'} 169 } else if isDevice { 170 // Don't add the extended prefix to device paths, as it would 171 // change its meaning. 172 } else { 173 prefix = []uint16{'\\', '\\', '?', '\\'} 174 } 175 176 p, err := syscall.UTF16FromString(path) 177 if err != nil { 178 return path 179 } 180 // Estimate the required buffer size using the path length plus the null terminator. 181 // pathLength includes the working directory. This should be accurate unless 182 // the working directory has changed without using os.Chdir. 183 n := uint32(pathLength) + 1 184 var buf []uint16 185 for { 186 buf = make([]uint16, n+uint32(len(prefix))) 187 n, err = syscall.GetFullPathName(&p[0], n, &buf[len(prefix)], nil) 188 if err != nil { 189 return path 190 } 191 if n <= uint32(len(buf)-len(prefix)) { 192 buf = buf[:n+uint32(len(prefix))] 193 break 194 } 195 } 196 if isUNC { 197 // Remove leading \\. 198 buf = buf[2:] 199 } 200 copy(buf, prefix) 201 return syscall.UTF16ToString(buf) 202 } 203