Home | History | Annotate | Download | only in filepath
      1 // Copyright 2009 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 filepath implements utility routines for manipulating filename paths
      6 // in a way compatible with the target operating system-defined file paths.
      7 //
      8 // The filepath package uses either forward slashes or backslashes,
      9 // depending on the operating system. To process paths such as URLs
     10 // that always use forward slashes regardless of the operating
     11 // system, see the path package.
     12 package filepath
     13 
     14 import (
     15 	"errors"
     16 	"os"
     17 	"sort"
     18 	"strings"
     19 )
     20 
     21 // A lazybuf is a lazily constructed path buffer.
     22 // It supports append, reading previously appended bytes,
     23 // and retrieving the final string. It does not allocate a buffer
     24 // to hold the output until that output diverges from s.
     25 type lazybuf struct {
     26 	path       string
     27 	buf        []byte
     28 	w          int
     29 	volAndPath string
     30 	volLen     int
     31 }
     32 
     33 func (b *lazybuf) index(i int) byte {
     34 	if b.buf != nil {
     35 		return b.buf[i]
     36 	}
     37 	return b.path[i]
     38 }
     39 
     40 func (b *lazybuf) append(c byte) {
     41 	if b.buf == nil {
     42 		if b.w < len(b.path) && b.path[b.w] == c {
     43 			b.w++
     44 			return
     45 		}
     46 		b.buf = make([]byte, len(b.path))
     47 		copy(b.buf, b.path[:b.w])
     48 	}
     49 	b.buf[b.w] = c
     50 	b.w++
     51 }
     52 
     53 func (b *lazybuf) string() string {
     54 	if b.buf == nil {
     55 		return b.volAndPath[:b.volLen+b.w]
     56 	}
     57 	return b.volAndPath[:b.volLen] + string(b.buf[:b.w])
     58 }
     59 
     60 const (
     61 	Separator     = os.PathSeparator
     62 	ListSeparator = os.PathListSeparator
     63 )
     64 
     65 // Clean returns the shortest path name equivalent to path
     66 // by purely lexical processing. It applies the following rules
     67 // iteratively until no further processing can be done:
     68 //
     69 //	1. Replace multiple Separator elements with a single one.
     70 //	2. Eliminate each . path name element (the current directory).
     71 //	3. Eliminate each inner .. path name element (the parent directory)
     72 //	   along with the non-.. element that precedes it.
     73 //	4. Eliminate .. elements that begin a rooted path:
     74 //	   that is, replace "/.." by "/" at the beginning of a path,
     75 //	   assuming Separator is '/'.
     76 //
     77 // The returned path ends in a slash only if it represents a root directory,
     78 // such as "/" on Unix or `C:\` on Windows.
     79 //
     80 // Finally, any occurrences of slash are replaced by Separator.
     81 //
     82 // If the result of this process is an empty string, Clean
     83 // returns the string ".".
     84 //
     85 // See also Rob Pike, ``Lexical File Names in Plan 9 or
     86 // Getting Dot-Dot Right,''
     87 // https://9p.io/sys/doc/lexnames.html
     88 func Clean(path string) string {
     89 	originalPath := path
     90 	volLen := volumeNameLen(path)
     91 	path = path[volLen:]
     92 	if path == "" {
     93 		if volLen > 1 && originalPath[1] != ':' {
     94 			// should be UNC
     95 			return FromSlash(originalPath)
     96 		}
     97 		return originalPath + "."
     98 	}
     99 	rooted := os.IsPathSeparator(path[0])
    100 
    101 	// Invariants:
    102 	//	reading from path; r is index of next byte to process.
    103 	//	writing to buf; w is index of next byte to write.
    104 	//	dotdot is index in buf where .. must stop, either because
    105 	//		it is the leading slash or it is a leading ../../.. prefix.
    106 	n := len(path)
    107 	out := lazybuf{path: path, volAndPath: originalPath, volLen: volLen}
    108 	r, dotdot := 0, 0
    109 	if rooted {
    110 		out.append(Separator)
    111 		r, dotdot = 1, 1
    112 	}
    113 
    114 	for r < n {
    115 		switch {
    116 		case os.IsPathSeparator(path[r]):
    117 			// empty path element
    118 			r++
    119 		case path[r] == '.' && (r+1 == n || os.IsPathSeparator(path[r+1])):
    120 			// . element
    121 			r++
    122 		case path[r] == '.' && path[r+1] == '.' && (r+2 == n || os.IsPathSeparator(path[r+2])):
    123 			// .. element: remove to last separator
    124 			r += 2
    125 			switch {
    126 			case out.w > dotdot:
    127 				// can backtrack
    128 				out.w--
    129 				for out.w > dotdot && !os.IsPathSeparator(out.index(out.w)) {
    130 					out.w--
    131 				}
    132 			case !rooted:
    133 				// cannot backtrack, but not rooted, so append .. element.
    134 				if out.w > 0 {
    135 					out.append(Separator)
    136 				}
    137 				out.append('.')
    138 				out.append('.')
    139 				dotdot = out.w
    140 			}
    141 		default:
    142 			// real path element.
    143 			// add slash if needed
    144 			if rooted && out.w != 1 || !rooted && out.w != 0 {
    145 				out.append(Separator)
    146 			}
    147 			// copy element
    148 			for ; r < n && !os.IsPathSeparator(path[r]); r++ {
    149 				out.append(path[r])
    150 			}
    151 		}
    152 	}
    153 
    154 	// Turn empty string into "."
    155 	if out.w == 0 {
    156 		out.append('.')
    157 	}
    158 
    159 	return FromSlash(out.string())
    160 }
    161 
    162 // ToSlash returns the result of replacing each separator character
    163 // in path with a slash ('/') character. Multiple separators are
    164 // replaced by multiple slashes.
    165 func ToSlash(path string) string {
    166 	if Separator == '/' {
    167 		return path
    168 	}
    169 	return strings.Replace(path, string(Separator), "/", -1)
    170 }
    171 
    172 // FromSlash returns the result of replacing each slash ('/') character
    173 // in path with a separator character. Multiple slashes are replaced
    174 // by multiple separators.
    175 func FromSlash(path string) string {
    176 	if Separator == '/' {
    177 		return path
    178 	}
    179 	return strings.Replace(path, "/", string(Separator), -1)
    180 }
    181 
    182 // SplitList splits a list of paths joined by the OS-specific ListSeparator,
    183 // usually found in PATH or GOPATH environment variables.
    184 // Unlike strings.Split, SplitList returns an empty slice when passed an empty
    185 // string.
    186 func SplitList(path string) []string {
    187 	return splitList(path)
    188 }
    189 
    190 // Split splits path immediately following the final Separator,
    191 // separating it into a directory and file name component.
    192 // If there is no Separator in path, Split returns an empty dir
    193 // and file set to path.
    194 // The returned values have the property that path = dir+file.
    195 func Split(path string) (dir, file string) {
    196 	vol := VolumeName(path)
    197 	i := len(path) - 1
    198 	for i >= len(vol) && !os.IsPathSeparator(path[i]) {
    199 		i--
    200 	}
    201 	return path[:i+1], path[i+1:]
    202 }
    203 
    204 // Join joins any number of path elements into a single path, adding
    205 // a Separator if necessary. Join calls Clean on the result; in particular,
    206 // all empty strings are ignored.
    207 // On Windows, the result is a UNC path if and only if the first path
    208 // element is a UNC path.
    209 func Join(elem ...string) string {
    210 	return join(elem)
    211 }
    212 
    213 // Ext returns the file name extension used by path.
    214 // The extension is the suffix beginning at the final dot
    215 // in the final element of path; it is empty if there is
    216 // no dot.
    217 func Ext(path string) string {
    218 	for i := len(path) - 1; i >= 0 && !os.IsPathSeparator(path[i]); i-- {
    219 		if path[i] == '.' {
    220 			return path[i:]
    221 		}
    222 	}
    223 	return ""
    224 }
    225 
    226 // EvalSymlinks returns the path name after the evaluation of any symbolic
    227 // links.
    228 // If path is relative the result will be relative to the current directory,
    229 // unless one of the components is an absolute symbolic link.
    230 // EvalSymlinks calls Clean on the result.
    231 func EvalSymlinks(path string) (string, error) {
    232 	return evalSymlinks(path)
    233 }
    234 
    235 // Abs returns an absolute representation of path.
    236 // If the path is not absolute it will be joined with the current
    237 // working directory to turn it into an absolute path. The absolute
    238 // path name for a given file is not guaranteed to be unique.
    239 // Abs calls Clean on the result.
    240 func Abs(path string) (string, error) {
    241 	return abs(path)
    242 }
    243 
    244 func unixAbs(path string) (string, error) {
    245 	if IsAbs(path) {
    246 		return Clean(path), nil
    247 	}
    248 	wd, err := os.Getwd()
    249 	if err != nil {
    250 		return "", err
    251 	}
    252 	return Join(wd, path), nil
    253 }
    254 
    255 // Rel returns a relative path that is lexically equivalent to targpath when
    256 // joined to basepath with an intervening separator. That is,
    257 // Join(basepath, Rel(basepath, targpath)) is equivalent to targpath itself.
    258 // On success, the returned path will always be relative to basepath,
    259 // even if basepath and targpath share no elements.
    260 // An error is returned if targpath can't be made relative to basepath or if
    261 // knowing the current working directory would be necessary to compute it.
    262 // Rel calls Clean on the result.
    263 func Rel(basepath, targpath string) (string, error) {
    264 	baseVol := VolumeName(basepath)
    265 	targVol := VolumeName(targpath)
    266 	base := Clean(basepath)
    267 	targ := Clean(targpath)
    268 	if sameWord(targ, base) {
    269 		return ".", nil
    270 	}
    271 	base = base[len(baseVol):]
    272 	targ = targ[len(targVol):]
    273 	if base == "." {
    274 		base = ""
    275 	}
    276 	// Can't use IsAbs - `\a` and `a` are both relative in Windows.
    277 	baseSlashed := len(base) > 0 && base[0] == Separator
    278 	targSlashed := len(targ) > 0 && targ[0] == Separator
    279 	if baseSlashed != targSlashed || !sameWord(baseVol, targVol) {
    280 		return "", errors.New("Rel: can't make " + targpath + " relative to " + basepath)
    281 	}
    282 	// Position base[b0:bi] and targ[t0:ti] at the first differing elements.
    283 	bl := len(base)
    284 	tl := len(targ)
    285 	var b0, bi, t0, ti int
    286 	for {
    287 		for bi < bl && base[bi] != Separator {
    288 			bi++
    289 		}
    290 		for ti < tl && targ[ti] != Separator {
    291 			ti++
    292 		}
    293 		if !sameWord(targ[t0:ti], base[b0:bi]) {
    294 			break
    295 		}
    296 		if bi < bl {
    297 			bi++
    298 		}
    299 		if ti < tl {
    300 			ti++
    301 		}
    302 		b0 = bi
    303 		t0 = ti
    304 	}
    305 	if base[b0:bi] == ".." {
    306 		return "", errors.New("Rel: can't make " + targpath + " relative to " + basepath)
    307 	}
    308 	if b0 != bl {
    309 		// Base elements left. Must go up before going down.
    310 		seps := strings.Count(base[b0:bl], string(Separator))
    311 		size := 2 + seps*3
    312 		if tl != t0 {
    313 			size += 1 + tl - t0
    314 		}
    315 		buf := make([]byte, size)
    316 		n := copy(buf, "..")
    317 		for i := 0; i < seps; i++ {
    318 			buf[n] = Separator
    319 			copy(buf[n+1:], "..")
    320 			n += 3
    321 		}
    322 		if t0 != tl {
    323 			buf[n] = Separator
    324 			copy(buf[n+1:], targ[t0:])
    325 		}
    326 		return string(buf), nil
    327 	}
    328 	return targ[t0:], nil
    329 }
    330 
    331 // SkipDir is used as a return value from WalkFuncs to indicate that
    332 // the directory named in the call is to be skipped. It is not returned
    333 // as an error by any function.
    334 var SkipDir = errors.New("skip this directory")
    335 
    336 // WalkFunc is the type of the function called for each file or directory
    337 // visited by Walk. The path argument contains the argument to Walk as a
    338 // prefix; that is, if Walk is called with "dir", which is a directory
    339 // containing the file "a", the walk function will be called with argument
    340 // "dir/a". The info argument is the os.FileInfo for the named path.
    341 //
    342 // If there was a problem walking to the file or directory named by path, the
    343 // incoming error will describe the problem and the function can decide how
    344 // to handle that error (and Walk will not descend into that directory). If
    345 // an error is returned, processing stops. The sole exception is when the function
    346 // returns the special value SkipDir. If the function returns SkipDir when invoked
    347 // on a directory, Walk skips the directory's contents entirely.
    348 // If the function returns SkipDir when invoked on a non-directory file,
    349 // Walk skips the remaining files in the containing directory.
    350 type WalkFunc func(path string, info os.FileInfo, err error) error
    351 
    352 var lstat = os.Lstat // for testing
    353 
    354 // walk recursively descends path, calling walkFn.
    355 func walk(path string, info os.FileInfo, walkFn WalkFunc) error {
    356 	if !info.IsDir() {
    357 		return walkFn(path, info, nil)
    358 	}
    359 
    360 	names, err := readDirNames(path)
    361 	err1 := walkFn(path, info, err)
    362 	// If err != nil, walk can't walk into this directory.
    363 	// err1 != nil means walkFn want walk to skip this directory or stop walking.
    364 	// Therefore, if one of err and err1 isn't nil, walk will return.
    365 	if err != nil || err1 != nil {
    366 		// The caller's behavior is controlled by the return value, which is decided
    367 		// by walkFn. walkFn may ignore err and return nil.
    368 		// If walkFn returns SkipDir, it will be handled by the caller.
    369 		// So walk should return whatever walkFn returns.
    370 		return err1
    371 	}
    372 
    373 	for _, name := range names {
    374 		filename := Join(path, name)
    375 		fileInfo, err := lstat(filename)
    376 		if err != nil {
    377 			if err := walkFn(filename, fileInfo, err); err != nil && err != SkipDir {
    378 				return err
    379 			}
    380 		} else {
    381 			err = walk(filename, fileInfo, walkFn)
    382 			if err != nil {
    383 				if !fileInfo.IsDir() || err != SkipDir {
    384 					return err
    385 				}
    386 			}
    387 		}
    388 	}
    389 	return nil
    390 }
    391 
    392 // Walk walks the file tree rooted at root, calling walkFn for each file or
    393 // directory in the tree, including root. All errors that arise visiting files
    394 // and directories are filtered by walkFn. The files are walked in lexical
    395 // order, which makes the output deterministic but means that for very
    396 // large directories Walk can be inefficient.
    397 // Walk does not follow symbolic links.
    398 func Walk(root string, walkFn WalkFunc) error {
    399 	info, err := os.Lstat(root)
    400 	if err != nil {
    401 		err = walkFn(root, nil, err)
    402 	} else {
    403 		err = walk(root, info, walkFn)
    404 	}
    405 	if err == SkipDir {
    406 		return nil
    407 	}
    408 	return err
    409 }
    410 
    411 // readDirNames reads the directory named by dirname and returns
    412 // a sorted list of directory entries.
    413 func readDirNames(dirname string) ([]string, error) {
    414 	f, err := os.Open(dirname)
    415 	if err != nil {
    416 		return nil, err
    417 	}
    418 	names, err := f.Readdirnames(-1)
    419 	f.Close()
    420 	if err != nil {
    421 		return nil, err
    422 	}
    423 	sort.Strings(names)
    424 	return names, nil
    425 }
    426 
    427 // Base returns the last element of path.
    428 // Trailing path separators are removed before extracting the last element.
    429 // If the path is empty, Base returns ".".
    430 // If the path consists entirely of separators, Base returns a single separator.
    431 func Base(path string) string {
    432 	if path == "" {
    433 		return "."
    434 	}
    435 	// Strip trailing slashes.
    436 	for len(path) > 0 && os.IsPathSeparator(path[len(path)-1]) {
    437 		path = path[0 : len(path)-1]
    438 	}
    439 	// Throw away volume name
    440 	path = path[len(VolumeName(path)):]
    441 	// Find the last element
    442 	i := len(path) - 1
    443 	for i >= 0 && !os.IsPathSeparator(path[i]) {
    444 		i--
    445 	}
    446 	if i >= 0 {
    447 		path = path[i+1:]
    448 	}
    449 	// If empty now, it had only slashes.
    450 	if path == "" {
    451 		return string(Separator)
    452 	}
    453 	return path
    454 }
    455 
    456 // Dir returns all but the last element of path, typically the path's directory.
    457 // After dropping the final element, Dir calls Clean on the path and trailing
    458 // slashes are removed.
    459 // If the path is empty, Dir returns ".".
    460 // If the path consists entirely of separators, Dir returns a single separator.
    461 // The returned path does not end in a separator unless it is the root directory.
    462 func Dir(path string) string {
    463 	vol := VolumeName(path)
    464 	i := len(path) - 1
    465 	for i >= len(vol) && !os.IsPathSeparator(path[i]) {
    466 		i--
    467 	}
    468 	dir := Clean(path[len(vol) : i+1])
    469 	if dir == "." && len(vol) > 2 {
    470 		// must be UNC
    471 		return vol
    472 	}
    473 	return vol + dir
    474 }
    475 
    476 // VolumeName returns leading volume name.
    477 // Given "C:\foo\bar" it returns "C:" on Windows.
    478 // Given "\\host\share\foo" it returns "\\host\share".
    479 // On other platforms it returns "".
    480 func VolumeName(path string) string {
    481 	return path[:volumeNameLen(path)]
    482 }
    483