Source file src/path/filepath/path.go

     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 string) string {
    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 string) 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 string) (string, 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 string) string {
    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 string) string {
   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 string) []string {
   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 string) (dir, file string) {
   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 ...string) string {
   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 string) string {
   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 string) (string, error) {
   148  	return evalSymlinks(path)
   149  }
   150  
   151  // IsAbs reports whether the path is absolute.
   152  func IsAbs(path string) 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 string) (string, error) {
   162  	return abs(path)
   163  }
   164  
   165  func unixAbs(path string) (string, 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 string) (string, 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 = string(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 := make([]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 string(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 string, 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 string, 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 string, 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 string, 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 string, 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 string) ([]string, error) {
   438  	f, err := os.Open(dirname)
   439  	if err != nil {
   440  		return nil, err
   441  	}
   442  	names, err := f.Readdirnames(-1)
   443  	f.Close()
   444  	if err != nil {
   445  		return nil, err
   446  	}
   447  	slices.Sort(names)
   448  	return names, nil
   449  }
   450  
   451  // Base returns the last element of path.
   452  // Trailing path separators are removed before extracting the last element.
   453  // If the path is empty, Base returns ".".
   454  // If the path consists entirely of separators, Base returns a single separator.
   455  func Base(path string) string {
   456  	return filepathlite.Base(path)
   457  }
   458  
   459  // Dir returns all but the last element of path, typically the path's directory.
   460  // After dropping the final element, Dir calls [Clean] on the path and trailing
   461  // slashes are removed.
   462  // If the path is empty, Dir returns ".".
   463  // If the path consists entirely of separators, Dir returns a single separator.
   464  // The returned path does not end in a separator unless it is the root directory.
   465  func Dir(path string) string {
   466  	return filepathlite.Dir(path)
   467  }
   468  
   469  // VolumeName returns leading volume name.
   470  // Given "C:\foo\bar" it returns "C:" on Windows.
   471  // Given "\\host\share\foo" it returns "\\host\share".
   472  // On other platforms it returns "".
   473  func VolumeName(path string) string {
   474  	return filepathlite.VolumeName(path)
   475  }
   476  

View as plain text