github.com/nicocha30/gvisor-ligolo@v0.0.0-20230726075806-989fa2c0a413/pkg/fspath/fspath.go (about) 1 // Copyright 2019 The gVisor Authors. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 // Package fspath provides efficient tools for working with file paths in 16 // Linux-compatible filesystem implementations. 17 package fspath 18 19 import ( 20 "strings" 21 ) 22 23 const pathSep = '/' 24 25 // Parse parses a pathname as described by path_resolution(7), except that 26 // empty pathnames will be parsed successfully to a Path for which 27 // Path.Absolute == Path.Dir == Path.HasComponents() == false. (This is 28 // necessary to support AT_EMPTY_PATH.) 29 func Parse(pathname string) Path { 30 if len(pathname) == 0 { 31 return Path{} 32 } 33 // Skip leading path separators. 34 i := 0 35 for pathname[i] == pathSep { 36 i++ 37 if i == len(pathname) { 38 // pathname consists entirely of path separators. 39 return Path{ 40 Absolute: true, 41 Dir: true, 42 } 43 } 44 } 45 // Skip trailing path separators. This is required by Iterator.Next. This 46 // loop is guaranteed to terminate with j >= 0 because otherwise the 47 // pathname would consist entirely of path separators, so we would have 48 // returned above. 49 j := len(pathname) - 1 50 for pathname[j] == pathSep { 51 j-- 52 } 53 // Find the end of the first path component. 54 firstEnd := i + 1 55 for firstEnd != len(pathname) && pathname[firstEnd] != pathSep { 56 firstEnd++ 57 } 58 return Path{ 59 Begin: Iterator{ 60 partialPathname: pathname[i : j+1], 61 end: firstEnd - i, 62 }, 63 Absolute: i != 0, 64 Dir: j != len(pathname)-1, 65 } 66 } 67 68 // Path contains the information contained in a pathname string. 69 // 70 // Path is copyable by value. The zero value for Path is equivalent to 71 // fspath.Parse(""), i.e. the empty path. 72 type Path struct { 73 // Begin is an iterator to the first path component in the relative part of 74 // the path. 75 // 76 // Path doesn't store information about path components after the first 77 // since this would require allocation. 78 Begin Iterator 79 80 // If true, the path is absolute, such that lookup should begin at the 81 // filesystem root. If false, the path is relative, such that where lookup 82 // begins is unspecified. 83 Absolute bool 84 85 // If true, the pathname contains trailing path separators, so the last 86 // path component must exist and resolve to a directory. 87 Dir bool 88 } 89 90 // String returns a pathname string equivalent to p. Note that the returned 91 // string is not necessarily equal to the string p was parsed from; in 92 // particular, redundant path separators will not be present. 93 func (p Path) String() string { 94 var b strings.Builder 95 if p.Absolute { 96 b.WriteByte(pathSep) 97 } 98 sep := false 99 for pit := p.Begin; pit.Ok(); pit = pit.Next() { 100 if sep { 101 b.WriteByte(pathSep) 102 } 103 b.WriteString(pit.String()) 104 sep = true 105 } 106 // Don't return "//" for Parse("/"). 107 if p.Dir && p.Begin.Ok() { 108 b.WriteByte(pathSep) 109 } 110 return b.String() 111 } 112 113 // HasComponents returns true if p contains a non-zero number of path 114 // components. 115 func (p Path) HasComponents() bool { 116 return p.Begin.Ok() 117 } 118 119 // An Iterator represents either a path component in a Path or a terminal 120 // iterator indicating that the end of the path has been reached. 121 // 122 // Iterator is immutable and copyable by value. The zero value of Iterator is 123 // valid, and represents a terminal iterator. 124 type Iterator struct { 125 // partialPathname is a substring of the original pathname beginning at the 126 // start of the represented path component and ending immediately after the 127 // end of the last path component in the pathname. If partialPathname is 128 // empty, the PathnameIterator is terminal. 129 // 130 // See TestParseIteratorPartialPathnames in fspath_test.go for a worked 131 // example. 132 partialPathname string 133 134 // end is the offset into partialPathname of the first byte after the end 135 // of the represented path component. 136 end int 137 } 138 139 // Ok returns true if it is not terminal. 140 func (it Iterator) Ok() bool { 141 return len(it.partialPathname) != 0 142 } 143 144 // String returns the path component represented by it. 145 // 146 // Preconditions: it.Ok(). 147 func (it Iterator) String() string { 148 return it.partialPathname[:it.end] 149 } 150 151 // Next returns an iterator to the path component after it. If it is the last 152 // component in the path, Next returns a terminal iterator. 153 // 154 // Preconditions: it.Ok(). 155 func (it Iterator) Next() Iterator { 156 if it.end == len(it.partialPathname) { 157 // End of the path. 158 return Iterator{} 159 } 160 // Skip path separators. Since Parse trims trailing path separators, if we 161 // aren't at the end of the path, there is definitely another path 162 // component. 163 i := it.end + 1 164 for { 165 if it.partialPathname[i] != pathSep { 166 break 167 } 168 i++ 169 } 170 nextPartialPathname := it.partialPathname[i:] 171 // Find the end of this path component. 172 nextEnd := 1 173 for nextEnd < len(nextPartialPathname) && nextPartialPathname[nextEnd] != pathSep { 174 nextEnd++ 175 } 176 return Iterator{ 177 partialPathname: nextPartialPathname, 178 end: nextEnd, 179 } 180 } 181 182 // NextOk is equivalent to it.Next().Ok(), but is faster. 183 // 184 // Preconditions: it.Ok(). 185 func (it Iterator) NextOk() bool { 186 return it.end != len(it.partialPathname) 187 }