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

View as plain text