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  

View as plain text