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  

View as plain text