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  

View as plain text