Home | History | Annotate | Download | only in os
      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 // fixLongPath returns the extended-length (\\?\-prefixed) form of
    132 // path when needed, in order to avoid the default 260 character file
    133 // path limit imposed by Windows. If path is not easily converted to
    134 // the extended-length form (for example, if path is a relative path
    135 // or contains .. elements), or is short enough, fixLongPath returns
    136 // path unmodified.
    137 //
    138 // See https://msdn.microsoft.com/en-us/library/windows/desktop/aa365247(v=vs.85).aspx#maxpath
    139 func fixLongPath(path string) string {
    140 	// Do nothing (and don't allocate) if the path is "short".
    141 	// Empirically (at least on the Windows Server 2013 builder),
    142 	// the kernel is arbitrarily okay with < 248 bytes. That
    143 	// matches what the docs above say:
    144 	// "When using an API to create a directory, the specified
    145 	// path cannot be so long that you cannot append an 8.3 file
    146 	// name (that is, the directory name cannot exceed MAX_PATH
    147 	// minus 12)." Since MAX_PATH is 260, 260 - 12 = 248.
    148 	//
    149 	// The MSDN docs appear to say that a normal path that is 248 bytes long
    150 	// will work; empirically the path must be less then 248 bytes long.
    151 	if len(path) < 248 {
    152 		// Don't fix. (This is how Go 1.7 and earlier worked,
    153 		// not automatically generating the \\?\ form)
    154 		return path
    155 	}
    156 
    157 	// The extended form begins with \\?\, as in
    158 	// \\?\c:\windows\foo.txt or \\?\UNC\server\share\foo.txt.
    159 	// The extended form disables evaluation of . and .. path
    160 	// elements and disables the interpretation of / as equivalent
    161 	// to \. The conversion here rewrites / to \ and elides
    162 	// . elements as well as trailing or duplicate separators. For
    163 	// simplicity it avoids the conversion entirely for relative
    164 	// paths or paths containing .. elements. For now,
    165 	// \\server\share paths are not converted to
    166 	// \\?\UNC\server\share paths because the rules for doing so
    167 	// are less well-specified.
    168 	if len(path) >= 2 && path[:2] == `\\` {
    169 		// Don't canonicalize UNC paths.
    170 		return path
    171 	}
    172 	if !isAbs(path) {
    173 		// Relative path
    174 		return path
    175 	}
    176 
    177 	const prefix = `\\?`
    178 
    179 	pathbuf := make([]byte, len(prefix)+len(path)+len(`\`))
    180 	copy(pathbuf, prefix)
    181 	n := len(path)
    182 	r, w := 0, len(prefix)
    183 	for r < n {
    184 		switch {
    185 		case IsPathSeparator(path[r]):
    186 			// empty block
    187 			r++
    188 		case path[r] == '.' && (r+1 == n || IsPathSeparator(path[r+1])):
    189 			// /./
    190 			r++
    191 		case r+1 < n && path[r] == '.' && path[r+1] == '.' && (r+2 == n || IsPathSeparator(path[r+2])):
    192 			// /../ is currently unhandled
    193 			return path
    194 		default:
    195 			pathbuf[w] = '\\'
    196 			w++
    197 			for ; r < n && !IsPathSeparator(path[r]); r++ {
    198 				pathbuf[w] = path[r]
    199 				w++
    200 			}
    201 		}
    202 	}
    203 	// A drive's root directory needs a trailing \
    204 	if w == len(`\\?\c:`) {
    205 		pathbuf[w] = '\\'
    206 		w++
    207 	}
    208 	return string(pathbuf[:w])
    209 }
    210