github.com/tinygo-org/tinygo@v0.31.3-0.20240404173401-90b0bf646c27/src/os/path_windows.go (about)

     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  const (
     8  	PathSeparator     = '\\' // OS-specific path separator
     9  	PathListSeparator = ';'  // OS-specific path list separator
    10  )
    11  
    12  // IsPathSeparator reports whether c is a directory separator character.
    13  func IsPathSeparator(c uint8) bool {
    14  	// NOTE: Windows accept / as path separator.
    15  	return c == '\\' || c == '/'
    16  }
    17  
    18  // basename removes trailing slashes and the leading
    19  // directory name and drive letter from path name.
    20  func basename(name string) string {
    21  	// Remove drive letter
    22  	if len(name) == 2 && name[1] == ':' {
    23  		name = "."
    24  	} else if len(name) > 2 && name[1] == ':' {
    25  		name = name[2:]
    26  	}
    27  	i := len(name) - 1
    28  	// Remove trailing slashes
    29  	for ; i > 0 && (name[i] == '/' || name[i] == '\\'); i-- {
    30  		name = name[:i]
    31  	}
    32  	// Remove leading directory name
    33  	for i--; i >= 0; i-- {
    34  		if name[i] == '/' || name[i] == '\\' {
    35  			name = name[i+1:]
    36  			break
    37  		}
    38  	}
    39  	return name
    40  }
    41  
    42  func isAbs(path string) (b bool) {
    43  	v := volumeName(path)
    44  	if v == "" {
    45  		return false
    46  	}
    47  	path = path[len(v):]
    48  	if path == "" {
    49  		return false
    50  	}
    51  	return IsPathSeparator(path[0])
    52  }
    53  
    54  func volumeName(path string) (v string) {
    55  	if len(path) < 2 {
    56  		return ""
    57  	}
    58  	// with drive letter
    59  	c := path[0]
    60  	if path[1] == ':' &&
    61  		('0' <= c && c <= '9' || 'a' <= c && c <= 'z' ||
    62  			'A' <= c && c <= 'Z') {
    63  		return path[:2]
    64  	}
    65  	// is it UNC
    66  	if l := len(path); l >= 5 && IsPathSeparator(path[0]) && IsPathSeparator(path[1]) &&
    67  		!IsPathSeparator(path[2]) && path[2] != '.' {
    68  		// first, leading `\\` and next shouldn't be `\`. its server name.
    69  		for n := 3; n < l-1; n++ {
    70  			// second, next '\' shouldn't be repeated.
    71  			if IsPathSeparator(path[n]) {
    72  				n++
    73  				// third, following something characters. its share name.
    74  				if !IsPathSeparator(path[n]) {
    75  					if path[n] == '.' {
    76  						break
    77  					}
    78  					for ; n < l; n++ {
    79  						if IsPathSeparator(path[n]) {
    80  							break
    81  						}
    82  					}
    83  					return path[:n]
    84  				}
    85  				break
    86  			}
    87  		}
    88  	}
    89  	return ""
    90  }
    91  
    92  func fromSlash(path string) string {
    93  	// Replace each '/' with '\\' if present
    94  	var pathbuf []byte
    95  	var lastSlash int
    96  	for i, b := range path {
    97  		if b == '/' {
    98  			if pathbuf == nil {
    99  				pathbuf = make([]byte, len(path))
   100  			}
   101  			copy(pathbuf[lastSlash:], path[lastSlash:i])
   102  			pathbuf[i] = '\\'
   103  			lastSlash = i + 1
   104  		}
   105  	}
   106  	if pathbuf == nil {
   107  		return path
   108  	}
   109  
   110  	copy(pathbuf[lastSlash:], path[lastSlash:])
   111  	return string(pathbuf)
   112  }
   113  
   114  func dirname(path string) string {
   115  	vol := volumeName(path)
   116  	i := len(path) - 1
   117  	for i >= len(vol) && !IsPathSeparator(path[i]) {
   118  		i--
   119  	}
   120  	dir := path[len(vol) : i+1]
   121  	last := len(dir) - 1
   122  	if last > 0 && IsPathSeparator(dir[last]) {
   123  		dir = dir[:last]
   124  	}
   125  	if dir == "" {
   126  		dir = "."
   127  	}
   128  	return vol + dir
   129  }
   130  
   131  // This is set via go:linkname on runtime.canUseLongPaths, and is true when the OS
   132  // supports opting into proper long path handling without the need for fixups.
   133  var canUseLongPaths bool
   134  
   135  // fixLongPath returns the extended-length (\\?\-prefixed) form of
   136  // path when needed, in order to avoid the default 260 character file
   137  // path limit imposed by Windows. If path is not easily converted to
   138  // the extended-length form (for example, if path is a relative path
   139  // or contains .. elements), or is short enough, fixLongPath returns
   140  // path unmodified.
   141  //
   142  // See https://msdn.microsoft.com/en-us/library/windows/desktop/aa365247(v=vs.85).aspx#maxpath
   143  func fixLongPath(path string) string {
   144  	if canUseLongPaths {
   145  		return path
   146  	}
   147  	// Do nothing (and don't allocate) if the path is "short".
   148  	// Empirically (at least on the Windows Server 2013 builder),
   149  	// the kernel is arbitrarily okay with < 248 bytes. That
   150  	// matches what the docs above say:
   151  	// "When using an API to create a directory, the specified
   152  	// path cannot be so long that you cannot append an 8.3 file
   153  	// name (that is, the directory name cannot exceed MAX_PATH
   154  	// minus 12)." Since MAX_PATH is 260, 260 - 12 = 248.
   155  	//
   156  	// The MSDN docs appear to say that a normal path that is 248 bytes long
   157  	// will work; empirically the path must be less then 248 bytes long.
   158  	if len(path) < 248 {
   159  		// Don't fix. (This is how Go 1.7 and earlier worked,
   160  		// not automatically generating the \\?\ form)
   161  		return path
   162  	}
   163  
   164  	// The extended form begins with \\?\, as in
   165  	// \\?\c:\windows\foo.txt or \\?\UNC\server\share\foo.txt.
   166  	// The extended form disables evaluation of . and .. path
   167  	// elements and disables the interpretation of / as equivalent
   168  	// to \. The conversion here rewrites / to \ and elides
   169  	// . elements as well as trailing or duplicate separators. For
   170  	// simplicity it avoids the conversion entirely for relative
   171  	// paths or paths containing .. elements. For now,
   172  	// \\server\share paths are not converted to
   173  	// \\?\UNC\server\share paths because the rules for doing so
   174  	// are less well-specified.
   175  	if len(path) >= 2 && path[:2] == `\\` {
   176  		// Don't canonicalize UNC paths.
   177  		return path
   178  	}
   179  	if !isAbs(path) {
   180  		// Relative path
   181  		return path
   182  	}
   183  
   184  	const prefix = `\\?`
   185  
   186  	pathbuf := make([]byte, len(prefix)+len(path)+len(`\`))
   187  	copy(pathbuf, prefix)
   188  	n := len(path)
   189  	r, w := 0, len(prefix)
   190  	for r < n {
   191  		switch {
   192  		case IsPathSeparator(path[r]):
   193  			// empty block
   194  			r++
   195  		case path[r] == '.' && (r+1 == n || IsPathSeparator(path[r+1])):
   196  			// /./
   197  			r++
   198  		case r+1 < n && path[r] == '.' && path[r+1] == '.' && (r+2 == n || IsPathSeparator(path[r+2])):
   199  			// /../ is currently unhandled
   200  			return path
   201  		default:
   202  			pathbuf[w] = '\\'
   203  			w++
   204  			for ; r < n && !IsPathSeparator(path[r]); r++ {
   205  				pathbuf[w] = path[r]
   206  				w++
   207  			}
   208  		}
   209  	}
   210  	// A drive's root directory needs a trailing \
   211  	if w == len(`\\?\c:`) {
   212  		pathbuf[w] = '\\'
   213  		w++
   214  	}
   215  	return string(pathbuf[:w])
   216  }
   217  
   218  // fixRootDirectory fixes a reference to a drive's root directory to
   219  // have the required trailing slash.
   220  func fixRootDirectory(p string) string {
   221  	if len(p) == len(`\\?\c:`) {
   222  		if IsPathSeparator(p[0]) && IsPathSeparator(p[1]) && p[2] == '?' && IsPathSeparator(p[3]) && p[5] == ':' {
   223  			return p + `\`
   224  		}
   225  	}
   226  	return p
   227  }