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 func dirname(path string) string { 25 vol := filepathlite.VolumeName(path) 26 i := len(path) - 1 27 for i >= len(vol) && !IsPathSeparator(path[i]) { 28 i-- 29 } 30 dir := path[len(vol) : i+1] 31 last := len(dir) - 1 32 if last > 0 && IsPathSeparator(dir[last]) { 33 dir = dir[:last] 34 } 35 if dir == "" { 36 dir = "." 37 } 38 return vol + dir 39 } 40 41 // fixLongPath returns the extended-length (\\?\-prefixed) form of 42 // path when needed, in order to avoid the default 260 character file 43 // path limit imposed by Windows. If the path is short enough or already 44 // has the extended-length prefix, fixLongPath returns path unmodified. 45 // If the path is relative and joining it with the current working 46 // directory results in a path that is too long, fixLongPath returns 47 // the absolute path with the extended-length prefix. 48 // 49 // See https://learn.microsoft.com/en-us/windows/win32/fileio/naming-a-file#maximum-path-length-limitation 50 func fixLongPath(path string) string { 51 if windows.CanUseLongPaths { 52 return path 53 } 54 return addExtendedPrefix(path) 55 } 56 57 // addExtendedPrefix adds the extended path prefix (\\?\) to path. 58 func addExtendedPrefix(path string) string { 59 if len(path) >= 4 { 60 if path[:4] == `\??\` { 61 // Already extended with \??\ 62 return path 63 } 64 if IsPathSeparator(path[0]) && IsPathSeparator(path[1]) && path[2] == '?' && IsPathSeparator(path[3]) { 65 // Already extended with \\?\ or any combination of directory separators. 66 return path 67 } 68 } 69 70 // Do nothing (and don't allocate) if the path is "short". 71 // Empirically (at least on the Windows Server 2013 builder), 72 // the kernel is arbitrarily okay with < 248 bytes. That 73 // matches what the docs above say: 74 // "When using an API to create a directory, the specified 75 // path cannot be so long that you cannot append an 8.3 file 76 // name (that is, the directory name cannot exceed MAX_PATH 77 // minus 12)." Since MAX_PATH is 260, 260 - 12 = 248. 78 // 79 // The MSDN docs appear to say that a normal path that is 248 bytes long 80 // will work; empirically the path must be less then 248 bytes long. 81 pathLength := len(path) 82 if !filepathlite.IsAbs(path) { 83 // If the path is relative, we need to prepend the working directory 84 // plus a separator to the path before we can determine if it's too long. 85 // We don't want to call syscall.Getwd here, as that call is expensive to do 86 // every time fixLongPath is called with a relative path, so we use a cache. 87 // Note that getwdCache might be outdated if the working directory has been 88 // changed without using os.Chdir, i.e. using syscall.Chdir directly or cgo. 89 // This is fine, as the worst that can happen is that we fail to fix the path. 90 getwdCache.Lock() 91 if getwdCache.dir == "" { 92 // Init the working directory cache. 93 getwdCache.dir, _ = syscall.Getwd() 94 } 95 pathLength += len(getwdCache.dir) + 1 96 getwdCache.Unlock() 97 } 98 99 if pathLength < 248 { 100 // Don't fix. (This is how Go 1.7 and earlier worked, 101 // not automatically generating the \\?\ form) 102 return path 103 } 104 105 var isUNC, isDevice bool 106 if len(path) >= 2 && IsPathSeparator(path[0]) && IsPathSeparator(path[1]) { 107 if len(path) >= 4 && path[2] == '.' && IsPathSeparator(path[3]) { 108 // Starts with //./ 109 isDevice = true 110 } else { 111 // Starts with // 112 isUNC = true 113 } 114 } 115 var prefix []uint16 116 if isUNC { 117 // UNC path, prepend the \\?\UNC\ prefix. 118 prefix = []uint16{'\\', '\\', '?', '\\', 'U', 'N', 'C', '\\'} 119 } else if isDevice { 120 // Don't add the extended prefix to device paths, as it would 121 // change its meaning. 122 } else { 123 prefix = []uint16{'\\', '\\', '?', '\\'} 124 } 125 126 p, err := syscall.UTF16FromString(path) 127 if err != nil { 128 return path 129 } 130 // Estimate the required buffer size using the path length plus the null terminator. 131 // pathLength includes the working directory. This should be accurate unless 132 // the working directory has changed without using os.Chdir. 133 n := uint32(pathLength) + 1 134 var buf []uint16 135 for { 136 buf = make([]uint16, n+uint32(len(prefix))) 137 n, err = syscall.GetFullPathName(&p[0], n, &buf[len(prefix)], nil) 138 if err != nil { 139 return path 140 } 141 if n <= uint32(len(buf)-len(prefix)) { 142 buf = buf[:n+uint32(len(prefix))] 143 break 144 } 145 } 146 if isUNC { 147 // Remove leading \\. 148 buf = buf[2:] 149 } 150 copy(buf, prefix) 151 return syscall.UTF16ToString(buf) 152 } 153