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.
12package filepath
13
14import (
15	"errors"
16	"internal/bytealg"
17	"internal/filepathlite"
18	"io/fs"
19	"os"
20	"slices"
21)
22
23const (
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
55func 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.
73func 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.
85func 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.
92func 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.
102func 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.
110func 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.
119func 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.
130func 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.
138func 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.
147func EvalSymlinks(path string) (string, error) {
148	return evalSymlinks(path)
149}
150
151// IsAbs reports whether the path is absolute.
152func 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.
161func Abs(path string) (string, error) {
162	return abs(path)
163}
164
165func 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.
184func 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.
259var 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.
264var 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.
304type WalkFunc func(path string, info fs.FileInfo, err error) error
305
306var lstat = os.Lstat // for testing
307
308// walkDir recursively descends path, calling walkDirFn.
309func 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.
343func 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.
395func 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.
422func 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.
437func 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.
455func 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.
465func 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 "".
473func VolumeName(path string) string {
474	return filepathlite.VolumeName(path)
475}
476