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