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