// Copyright 2019 The gVisor Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // Package fspath provides efficient tools for working with file paths in // Linux-compatible filesystem implementations. package fspath import ( "strings" "gvisor.dev/gvisor/pkg/syserror" ) const pathSep = '/' // Parse parses a pathname as described by path_resolution(7). func Parse(pathname string) (Path, error) { if len(pathname) == 0 { // "... POSIX decrees that an empty pathname must not be resolved // successfully. Linux returns ENOENT in this case." - // path_resolution(7) return Path{}, syserror.ENOENT } // Skip leading path separators. i := 0 for pathname[i] == pathSep { i++ if i == len(pathname) { // pathname consists entirely of path separators. return Path{ Absolute: true, Dir: true, }, nil } } // Skip trailing path separators. This is required by Iterator.Next. This // loop is guaranteed to terminate with j >= 0 because otherwise the // pathname would consist entirely of path separators, so we would have // returned above. j := len(pathname) - 1 for pathname[j] == pathSep { j-- } // Find the end of the first path component. firstEnd := i + 1 for firstEnd != len(pathname) && pathname[firstEnd] != pathSep { firstEnd++ } return Path{ Begin: Iterator{ partialPathname: pathname[i : j+1], end: firstEnd - i, }, Absolute: i != 0, Dir: j != len(pathname)-1, }, nil } // Path contains the information contained in a pathname string. // // Path is copyable by value. type Path struct { // Begin is an iterator to the first path component in the relative part of // the path. // // Path doesn't store information about path components after the first // since this would require allocation. Begin Iterator // If true, the path is absolute, such that lookup should begin at the // filesystem root. If false, the path is relative, such that where lookup // begins is unspecified. Absolute bool // If true, the pathname contains trailing path separators, so the last // path component must exist and resolve to a directory. Dir bool } // String returns a pathname string equivalent to p. Note that the returned // string is not necessarily equal to the string p was parsed from; in // particular, redundant path separators will not be present. func (p Path) String() string { var b strings.Builder if p.Absolute { b.WriteByte(pathSep) } sep := false for pit := p.Begin; pit.Ok(); pit = pit.Next() { if sep { b.WriteByte(pathSep) } b.WriteString(pit.String()) sep = true } // Don't return "//" for Parse("/"). if p.Dir && p.Begin.Ok() { b.WriteByte(pathSep) } return b.String() } // An Iterator represents either a path component in a Path or a terminal // iterator indicating that the end of the path has been reached. // // Iterator is immutable and copyable by value. The zero value of Iterator is // valid, and represents a terminal iterator. type Iterator struct { // partialPathname is a substring of the original pathname beginning at the // start of the represented path component and ending immediately after the // end of the last path component in the pathname. If partialPathname is // empty, the PathnameIterator is terminal. // // See TestParseIteratorPartialPathnames in fspath_test.go for a worked // example. partialPathname string // end is the offset into partialPathname of the first byte after the end // of the represented path component. end int } // Ok returns true if it is not terminal. func (it Iterator) Ok() bool { return len(it.partialPathname) != 0 } // String returns the path component represented by it. // // Preconditions: it.Ok(). func (it Iterator) String() string { return it.partialPathname[:it.end] } // Next returns an iterator to the path component after it. If it is the last // component in the path, Next returns a terminal iterator. // // Preconditions: it.Ok(). func (it Iterator) Next() Iterator { if it.end == len(it.partialPathname) { // End of the path. return Iterator{} } // Skip path separators. Since Parse trims trailing path separators, if we // aren't at the end of the path, there is definitely another path // component. i := it.end + 1 for { if it.partialPathname[i] != pathSep { break } i++ } nextPartialPathname := it.partialPathname[i:] // Find the end of this path component. nextEnd := 1 for nextEnd < len(nextPartialPathname) && nextPartialPathname[nextEnd] != pathSep { nextEnd++ } return Iterator{ partialPathname: nextPartialPathname, end: nextEnd, } } // NextOk is equivalent to it.Next().Ok(), but is faster. // // Preconditions: it.Ok(). func (it Iterator) NextOk() bool { return it.end != len(it.partialPathname) }