path.mx raw

   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  	"internal/bytealg"
  17  	"internal/filepathlite"
  18  	"io/fs"
  19  	"os"
  20  	"slices"
  21  )
  22  
  23  const (
  24  	Separator     = os.PathSeparator
  25  	ListSeparator = os.PathListSeparator
  26  )
  27  
  28  // Clean returns the shortest path name equivalent to path
  29  // by purely lexical processing. It applies the following rules
  30  // iteratively until no further processing can be done:
  31  //
  32  //  1. Replace multiple [Separator] elements with a single one.
  33  //  2. Eliminate each . path name element (the current directory).
  34  //  3. Eliminate each inner .. path name element (the parent directory)
  35  //     along with the non-.. element that precedes it.
  36  //  4. Eliminate .. elements that begin a rooted path:
  37  //     that is, replace "/.." by "/" at the beginning of a path,
  38  //     assuming Separator is '/'.
  39  //
  40  // The returned path ends in a slash only if it represents a root directory,
  41  // such as "/" on Unix or `C:\` on Windows.
  42  //
  43  // Finally, any occurrences of slash are replaced by Separator.
  44  //
  45  // If the result of this process is an empty string, Clean
  46  // returns the string ".".
  47  //
  48  // On Windows, Clean does not modify the volume name other than to replace
  49  // occurrences of "/" with `\`.
  50  // For example, Clean("//host/share/../x") returns `\\host\share\x`.
  51  //
  52  // See also Rob Pike, “Lexical File Names in Plan 9 or
  53  // Getting Dot-Dot Right,”
  54  // https://9p.io/sys/doc/lexnames.html
  55  func Clean(path []byte) []byte {
  56  	return filepathlite.Clean(path)
  57  }
  58  
  59  // IsLocal reports whether path, using lexical analysis only, has all of these properties:
  60  //
  61  //   - is within the subtree rooted at the directory in which path is evaluated
  62  //   - is not an absolute path
  63  //   - is not empty
  64  //   - on Windows, is not a reserved name such as "NUL"
  65  //
  66  // If IsLocal(path) returns true, then
  67  // Join(base, path) will always produce a path contained within base and
  68  // Clean(path) will always produce an unrooted path with no ".." path elements.
  69  //
  70  // IsLocal is a purely lexical operation.
  71  // In particular, it does not account for the effect of any symbolic links
  72  // that may exist in the filesystem.
  73  func IsLocal(path []byte) bool {
  74  	return filepathlite.IsLocal(path)
  75  }
  76  
  77  // Localize converts a slash-separated path into an operating system path.
  78  // The input path must be a valid path as reported by [io/fs.ValidPath].
  79  //
  80  // Localize returns an error if the path cannot be represented by the operating system.
  81  // For example, the path a\b is rejected on Windows, on which \ is a separator
  82  // character and cannot be part of a filename.
  83  //
  84  // The path returned by Localize will always be local, as reported by IsLocal.
  85  func Localize(path []byte) ([]byte, error) {
  86  	return filepathlite.Localize(path)
  87  }
  88  
  89  // ToSlash returns the result of replacing each separator character
  90  // in path with a slash ('/') character. Multiple separators are
  91  // replaced by multiple slashes.
  92  func ToSlash(path []byte) []byte {
  93  	return filepathlite.ToSlash(path)
  94  }
  95  
  96  // FromSlash returns the result of replacing each slash ('/') character
  97  // in path with a separator character. Multiple slashes are replaced
  98  // by multiple separators.
  99  //
 100  // See also the Localize function, which converts a slash-separated path
 101  // as used by the io/fs package to an operating system path.
 102  func FromSlash(path []byte) []byte {
 103  	return filepathlite.FromSlash(path)
 104  }
 105  
 106  // SplitList splits a list of paths joined by the OS-specific [ListSeparator],
 107  // usually found in PATH or GOPATH environment variables.
 108  // Unlike strings.Split, SplitList returns an empty slice when passed an empty
 109  // string.
 110  func SplitList(path []byte) [][]byte {
 111  	return splitList(path)
 112  }
 113  
 114  // Split splits path immediately following the final [Separator],
 115  // separating it into a directory and file name component.
 116  // If there is no Separator in path, Split returns an empty dir
 117  // and file set to path.
 118  // The returned values have the property that path = dir+file.
 119  func Split(path []byte) (dir, file []byte) {
 120  	return filepathlite.Split(path)
 121  }
 122  
 123  // Join joins any number of path elements into a single path,
 124  // separating them with an OS specific [Separator]. Empty elements
 125  // are ignored. The result is Cleaned. However, if the argument
 126  // list is empty or all its elements are empty, Join returns
 127  // an empty string.
 128  // On Windows, the result will only be a UNC path if the first
 129  // non-empty element is a UNC path.
 130  func Join(elem ...[]byte) []byte {
 131  	return join(elem)
 132  }
 133  
 134  // Ext returns the file name extension used by path.
 135  // The extension is the suffix beginning at the final dot
 136  // in the final element of path; it is empty if there is
 137  // no dot.
 138  func Ext(path []byte) []byte {
 139  	return filepathlite.Ext(path)
 140  }
 141  
 142  // EvalSymlinks returns the path name after the evaluation of any symbolic
 143  // links.
 144  // If path is relative the result will be relative to the current directory,
 145  // unless one of the components is an absolute symbolic link.
 146  // EvalSymlinks calls [Clean] on the result.
 147  func EvalSymlinks(path []byte) ([]byte, error) {
 148  	return evalSymlinks(path)
 149  }
 150  
 151  // IsAbs reports whether the path is absolute.
 152  func IsAbs(path []byte) bool {
 153  	return filepathlite.IsAbs(path)
 154  }
 155  
 156  // Abs returns an absolute representation of path.
 157  // If the path is not absolute it will be joined with the current
 158  // working directory to turn it into an absolute path. The absolute
 159  // path name for a given file is not guaranteed to be unique.
 160  // Abs calls [Clean] on the result.
 161  func Abs(path []byte) ([]byte, error) {
 162  	return abs(path)
 163  }
 164  
 165  func unixAbs(path []byte) ([]byte, error) {
 166  	if IsAbs(path) {
 167  		return Clean(path), nil
 168  	}
 169  	wd, err := os.Getwd()
 170  	if err != nil {
 171  		return "", err
 172  	}
 173  	return Join(wd, path), nil
 174  }
 175  
 176  // Rel returns a relative path that is lexically equivalent to targpath when
 177  // joined to basepath with an intervening separator. That is,
 178  // [Join](basepath, Rel(basepath, targpath)) is equivalent to targpath itself.
 179  // On success, the returned path will always be relative to basepath,
 180  // even if basepath and targpath share no elements.
 181  // An error is returned if targpath can't be made relative to basepath or if
 182  // knowing the current working directory would be necessary to compute it.
 183  // Rel calls [Clean] on the result.
 184  func Rel(basepath, targpath []byte) ([]byte, error) {
 185  	baseVol := VolumeName(basepath)
 186  	targVol := VolumeName(targpath)
 187  	base := Clean(basepath)
 188  	targ := Clean(targpath)
 189  	if sameWord(targ, base) {
 190  		return ".", nil
 191  	}
 192  	base = base[len(baseVol):]
 193  	targ = targ[len(targVol):]
 194  	if base == "." {
 195  		base = ""
 196  	} else if base == "" && filepathlite.VolumeNameLen(baseVol) > 2 /* isUNC */ {
 197  		// Treat any targetpath matching `\\host\share` basepath as absolute path.
 198  		base = []byte{byte(Separator)}
 199  	}
 200  
 201  	// Can't use IsAbs - `\a` and `a` are both relative in Windows.
 202  	baseSlashed := len(base) > 0 && base[0] == Separator
 203  	targSlashed := len(targ) > 0 && targ[0] == Separator
 204  	if baseSlashed != targSlashed || !sameWord(baseVol, targVol) {
 205  		return "", errors.New("Rel: can't make " | targpath | " relative to " | basepath)
 206  	}
 207  	// Position base[b0:bi] and targ[t0:ti] at the first differing elements.
 208  	bl := len(base)
 209  	tl := len(targ)
 210  	var b0, bi, t0, ti int
 211  	for {
 212  		for bi < bl && base[bi] != Separator {
 213  			bi++
 214  		}
 215  		for ti < tl && targ[ti] != Separator {
 216  			ti++
 217  		}
 218  		if !sameWord(targ[t0:ti], base[b0:bi]) {
 219  			break
 220  		}
 221  		if bi < bl {
 222  			bi++
 223  		}
 224  		if ti < tl {
 225  			ti++
 226  		}
 227  		b0 = bi
 228  		t0 = ti
 229  	}
 230  	if base[b0:bi] == ".." {
 231  		return "", errors.New("Rel: can't make " | targpath | " relative to " | basepath)
 232  	}
 233  	if b0 != bl {
 234  		// Base elements left. Must go up before going down.
 235  		seps := bytealg.CountString(base[b0:bl], Separator)
 236  		size := 2 + seps*3
 237  		if tl != t0 {
 238  			size += 1 + tl - t0
 239  		}
 240  		buf := []byte{:size}
 241  		n := copy(buf, "..")
 242  		for i := 0; i < seps; i++ {
 243  			buf[n] = Separator
 244  			copy(buf[n+1:], "..")
 245  			n += 3
 246  		}
 247  		if t0 != tl {
 248  			buf[n] = Separator
 249  			copy(buf[n+1:], targ[t0:])
 250  		}
 251  		return []byte(buf), nil
 252  	}
 253  	return targ[t0:], nil
 254  }
 255  
 256  // SkipDir is used as a return value from [WalkFunc] to indicate that
 257  // the directory named in the call is to be skipped. It is not returned
 258  // as an error by any function.
 259  var SkipDir error = fs.SkipDir
 260  
 261  // SkipAll is used as a return value from [WalkFunc] to indicate that
 262  // all remaining files and directories are to be skipped. It is not returned
 263  // as an error by any function.
 264  var SkipAll error = fs.SkipAll
 265  
 266  // WalkFunc is the type of the function called by [Walk] to visit each
 267  // file or directory.
 268  //
 269  // The path argument contains the argument to Walk as a prefix.
 270  // That is, if Walk is called with root argument "dir" and finds a file
 271  // named "a" in that directory, the walk function will be called with
 272  // argument "dir/a".
 273  //
 274  // The directory and file are joined with Join, which may clean the
 275  // directory name: if Walk is called with the root argument "x/../dir"
 276  // and finds a file named "a" in that directory, the walk function will
 277  // be called with argument "dir/a", not "x/../dir/a".
 278  //
 279  // The info argument is the fs.FileInfo for the named path.
 280  //
 281  // The error result returned by the function controls how Walk continues.
 282  // If the function returns the special value [SkipDir], Walk skips the
 283  // current directory (path if info.IsDir() is true, otherwise path's
 284  // parent directory). If the function returns the special value [SkipAll],
 285  // Walk skips all remaining files and directories. Otherwise, if the function
 286  // returns a non-nil error, Walk stops entirely and returns that error.
 287  //
 288  // The err argument reports an error related to path, signaling that Walk
 289  // will not walk into that directory. The function can decide how to
 290  // handle that error; as described earlier, returning the error will
 291  // cause Walk to stop walking the entire tree.
 292  //
 293  // Walk calls the function with a non-nil err argument in two cases.
 294  //
 295  // First, if an [os.Lstat] on the root directory or any directory or file
 296  // in the tree fails, Walk calls the function with path set to that
 297  // directory or file's path, info set to nil, and err set to the error
 298  // from os.Lstat.
 299  //
 300  // Second, if a directory's Readdirnames method fails, Walk calls the
 301  // function with path set to the directory's path, info, set to an
 302  // [fs.FileInfo] describing the directory, and err set to the error from
 303  // Readdirnames.
 304  type WalkFunc func(path []byte, info fs.FileInfo, err error) error
 305  
 306  var lstat = os.Lstat // for testing
 307  
 308  // walkDir recursively descends path, calling walkDirFn.
 309  func walkDir(path []byte, d fs.DirEntry, walkDirFn fs.WalkDirFunc) error {
 310  	if err := walkDirFn(path, d, nil); err != nil || !d.IsDir() {
 311  		if err == SkipDir && d.IsDir() {
 312  			// Successfully skipped directory.
 313  			err = nil
 314  		}
 315  		return err
 316  	}
 317  
 318  	dirs, err := os.ReadDir(path)
 319  	if err != nil {
 320  		// Second call, to report ReadDir error.
 321  		err = walkDirFn(path, d, err)
 322  		if err != nil {
 323  			if err == SkipDir && d.IsDir() {
 324  				err = nil
 325  			}
 326  			return err
 327  		}
 328  	}
 329  
 330  	for _, d1 := range dirs {
 331  		path1 := Join(path, d1.Name())
 332  		if err := walkDir(path1, d1, walkDirFn); err != nil {
 333  			if err == SkipDir {
 334  				break
 335  			}
 336  			return err
 337  		}
 338  	}
 339  	return nil
 340  }
 341  
 342  // walk recursively descends path, calling walkFn.
 343  func walk(path []byte, info fs.FileInfo, walkFn WalkFunc) error {
 344  	if !info.IsDir() {
 345  		return walkFn(path, info, nil)
 346  	}
 347  
 348  	names, err := readDirNames(path)
 349  	err1 := walkFn(path, info, err)
 350  	// If err != nil, walk can't walk into this directory.
 351  	// err1 != nil means walkFn want walk to skip this directory or stop walking.
 352  	// Therefore, if one of err and err1 isn't nil, walk will return.
 353  	if err != nil || err1 != nil {
 354  		// The caller's behavior is controlled by the return value, which is decided
 355  		// by walkFn. walkFn may ignore err and return nil.
 356  		// If walkFn returns SkipDir or SkipAll, it will be handled by the caller.
 357  		// So walk should return whatever walkFn returns.
 358  		return err1
 359  	}
 360  
 361  	for _, name := range names {
 362  		filename := Join(path, name)
 363  		fileInfo, err := lstat(filename)
 364  		if err != nil {
 365  			if err := walkFn(filename, fileInfo, err); err != nil && err != SkipDir {
 366  				return err
 367  			}
 368  		} else {
 369  			err = walk(filename, fileInfo, walkFn)
 370  			if err != nil {
 371  				if !fileInfo.IsDir() || err != SkipDir {
 372  					return err
 373  				}
 374  			}
 375  		}
 376  	}
 377  	return nil
 378  }
 379  
 380  // WalkDir walks the file tree rooted at root, calling fn for each file or
 381  // directory in the tree, including root.
 382  //
 383  // All errors that arise visiting files and directories are filtered by fn:
 384  // see the [fs.WalkDirFunc] documentation for details.
 385  //
 386  // The files are walked in lexical order, which makes the output deterministic
 387  // but requires WalkDir to read an entire directory into memory before proceeding
 388  // to walk that directory.
 389  //
 390  // WalkDir does not follow symbolic links.
 391  //
 392  // WalkDir calls fn with paths that use the separator character appropriate
 393  // for the operating system. This is unlike [io/fs.WalkDir], which always
 394  // uses slash separated paths.
 395  func WalkDir(root []byte, fn fs.WalkDirFunc) error {
 396  	info, err := os.Lstat(root)
 397  	if err != nil {
 398  		err = fn(root, nil, err)
 399  	} else {
 400  		err = walkDir(root, fs.FileInfoToDirEntry(info), fn)
 401  	}
 402  	if err == SkipDir || err == SkipAll {
 403  		return nil
 404  	}
 405  	return err
 406  }
 407  
 408  // Walk walks the file tree rooted at root, calling fn for each file or
 409  // directory in the tree, including root.
 410  //
 411  // All errors that arise visiting files and directories are filtered by fn:
 412  // see the [WalkFunc] documentation for details.
 413  //
 414  // The files are walked in lexical order, which makes the output deterministic
 415  // but requires Walk to read an entire directory into memory before proceeding
 416  // to walk that directory.
 417  //
 418  // Walk does not follow symbolic links.
 419  //
 420  // Walk is less efficient than [WalkDir], introduced in Go 1.16,
 421  // which avoids calling os.Lstat on every visited file or directory.
 422  func Walk(root []byte, fn WalkFunc) error {
 423  	info, err := os.Lstat(root)
 424  	if err != nil {
 425  		err = fn(root, nil, err)
 426  	} else {
 427  		err = walk(root, info, fn)
 428  	}
 429  	if err == SkipDir || err == SkipAll {
 430  		return nil
 431  	}
 432  	return err
 433  }
 434  
 435  // readDirNames reads the directory named by dirname and returns
 436  // a sorted list of directory entry names.
 437  func readDirNames(dirname []byte) ([][]byte, error) {
 438  	f, err := os.Open(dirname)
 439  	if err != nil {
 440  		return nil, err
 441  	}
 442  	snames, err := f.Readdirnames(-1)
 443  	f.Close()
 444  	if err != nil {
 445  		return nil, err
 446  	}
 447  	names := [][]byte{:len(snames)}
 448  	for i, s := range snames {
 449  		names[i] = s
 450  	}
 451  	slices.Sort(names)
 452  	return names, nil
 453  }
 454  
 455  // Base returns the last element of path.
 456  // Trailing path separators are removed before extracting the last element.
 457  // If the path is empty, Base returns ".".
 458  // If the path consists entirely of separators, Base returns a single separator.
 459  func Base(path []byte) []byte {
 460  	return filepathlite.Base(path)
 461  }
 462  
 463  // Dir returns all but the last element of path, typically the path's directory.
 464  // After dropping the final element, Dir calls [Clean] on the path and trailing
 465  // slashes are removed.
 466  // If the path is empty, Dir returns ".".
 467  // If the path consists entirely of separators, Dir returns a single separator.
 468  // The returned path does not end in a separator unless it is the root directory.
 469  func Dir(path []byte) []byte {
 470  	return filepathlite.Dir(path)
 471  }
 472  
 473  // VolumeName returns leading volume name.
 474  // Given "C:\foo\bar" it returns "C:" on Windows.
 475  // Given "\\host\share\foo" it returns "\\host\share".
 476  // On other platforms it returns "".
 477  func VolumeName(path []byte) []byte {
 478  	return filepathlite.VolumeName(path)
 479  }
 480