github.com/ledgerwatch/erigon-lib@v1.0.0/downloader/path.go (about) 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 downloader 13 14 import ( 15 "io/fs" 16 "os" 17 "runtime" 18 "strings" 19 ) 20 21 // A lazybuf is a lazily constructed path buffer. 22 // It supports append, reading previously appended bytes, 23 // and retrieving the final string. It does not allocate a buffer 24 // to hold the output until that output diverges from s. 25 type lazybuf struct { 26 path string 27 buf []byte 28 w int 29 volAndPath string 30 volLen int 31 } 32 33 func (b *lazybuf) index(i int) byte { 34 if b.buf != nil { 35 return b.buf[i] 36 } 37 return b.path[i] 38 } 39 40 func (b *lazybuf) append(c byte) { 41 if b.buf == nil { 42 if b.w < len(b.path) && b.path[b.w] == c { 43 b.w++ 44 return 45 } 46 b.buf = make([]byte, len(b.path)) 47 copy(b.buf, b.path[:b.w]) 48 } 49 b.buf[b.w] = c 50 b.w++ 51 } 52 53 func (b *lazybuf) string() string { 54 if b.buf == nil { 55 return b.volAndPath[:b.volLen+b.w] 56 } 57 return b.volAndPath[:b.volLen] + string(b.buf[:b.w]) 58 } 59 60 const ( 61 Separator = os.PathSeparator 62 ListSeparator = os.PathListSeparator 63 ) 64 65 // Clean returns the shortest path name equivalent to path 66 // by purely lexical processing. It applies the following rules 67 // iteratively until no further processing can be done: 68 // 69 // 1. Replace multiple Separator elements with a single one. 70 // 2. Eliminate each . path name element (the current directory). 71 // 3. Eliminate each inner .. path name element (the parent directory) 72 // along with the non-.. element that precedes it. 73 // 4. Eliminate .. elements that begin a rooted path: 74 // that is, replace "/.." by "/" at the beginning of a path, 75 // assuming Separator is '/'. 76 // 77 // The returned path ends in a slash only if it represents a root directory, 78 // such as "/" on Unix or `C:\` on Windows. 79 // 80 // Finally, any occurrences of slash are replaced by Separator. 81 // 82 // If the result of this process is an empty string, Clean 83 // returns the string ".". 84 // 85 // See also Rob Pike, “Lexical File Names in Plan 9 or 86 // Getting Dot-Dot Right,” 87 // https://9p.io/sys/doc/lexnames.html 88 func Clean(path string) string { 89 originalPath := path 90 volLen := volumeNameLen(path) 91 path = path[volLen:] 92 if path == "" { 93 if volLen > 1 && os.IsPathSeparator(originalPath[0]) && os.IsPathSeparator(originalPath[1]) { 94 // should be UNC 95 return FromSlash(originalPath) 96 } 97 return originalPath + "." 98 } 99 rooted := os.IsPathSeparator(path[0]) 100 101 // Invariants: 102 // reading from path; r is index of next byte to process. 103 // writing to buf; w is index of next byte to write. 104 // dotdot is index in buf where .. must stop, either because 105 // it is the leading slash or it is a leading ../../.. prefix. 106 n := len(path) 107 out := lazybuf{path: path, volAndPath: originalPath, volLen: volLen} 108 r, dotdot := 0, 0 109 if rooted { 110 out.append(Separator) 111 r, dotdot = 1, 1 112 } 113 114 for r < n { 115 switch { 116 case os.IsPathSeparator(path[r]): 117 // empty path element 118 r++ 119 case path[r] == '.' && (r+1 == n || os.IsPathSeparator(path[r+1])): 120 // . element 121 r++ 122 case path[r] == '.' && path[r+1] == '.' && (r+2 == n || os.IsPathSeparator(path[r+2])): 123 // .. element: remove to last separator 124 r += 2 125 switch { 126 case out.w > dotdot: 127 // can backtrack 128 out.w-- 129 for out.w > dotdot && !os.IsPathSeparator(out.index(out.w)) { 130 out.w-- 131 } 132 case !rooted: 133 // cannot backtrack, but not rooted, so append .. element. 134 if out.w > 0 { 135 out.append(Separator) 136 } 137 out.append('.') 138 out.append('.') 139 dotdot = out.w 140 } 141 default: 142 // real path element. 143 // add slash if needed 144 if rooted && out.w != 1 || !rooted && out.w != 0 { 145 out.append(Separator) 146 } 147 // If a ':' appears in the path element at the start of a Windows path, 148 // insert a .\ at the beginning to avoid converting relative paths 149 // like a/../c: into c:. 150 if runtime.GOOS == "windows" && out.w == 0 && out.volLen == 0 && r != 0 { 151 for i := r; i < n && !os.IsPathSeparator(path[i]); i++ { 152 if path[i] == ':' { 153 out.append('.') 154 out.append(Separator) 155 break 156 } 157 } 158 } 159 // copy element 160 for ; r < n && !os.IsPathSeparator(path[r]); r++ { 161 out.append(path[r]) 162 } 163 } 164 } 165 166 // Turn empty string into "." 167 if out.w == 0 { 168 out.append('.') 169 } 170 171 return FromSlash(out.string()) 172 } 173 174 func unixIsLocal(path string) bool { 175 if IsAbs(path) || path == "" { 176 return false 177 } 178 hasDots := false 179 for p := path; p != ""; { 180 var part string 181 part, p, _ = strings.Cut(p, "/") 182 if part == "." || part == ".." { 183 hasDots = true 184 break 185 } 186 } 187 if hasDots { 188 path = Clean(path) 189 } 190 if path == ".." || strings.HasPrefix(path, "../") { 191 return false 192 } 193 return true 194 } 195 196 // FromSlash returns the result of replacing each slash ('/') character 197 // in path with a separator character. Multiple slashes are replaced 198 // by multiple separators. 199 func FromSlash(path string) string { 200 if Separator == '/' { 201 return path 202 } 203 return strings.ReplaceAll(path, "/", string(Separator)) 204 } 205 206 // Join joins any number of path elements into a single path, 207 // separating them with an OS specific Separator. Empty elements 208 // are ignored. The result is Cleaned. However, if the argument 209 // list is empty or all its elements are empty, Join returns 210 // an empty string. 211 // On Windows, the result will only be a UNC path if the first 212 // non-empty element is a UNC path. 213 func Join(elem ...string) string { 214 return join(elem) 215 } 216 217 // nolint 218 func unixAbs(path string) (string, error) { 219 if IsAbs(path) { 220 return Clean(path), nil 221 } 222 wd, err := os.Getwd() 223 if err != nil { 224 return "", err 225 } 226 return Join(wd, path), nil 227 } 228 229 // SkipDir is used as a return value from WalkFuncs to indicate that 230 // the directory named in the call is to be skipped. It is not returned 231 // as an error by any function. 232 var SkipDir error = fs.SkipDir 233 234 // WalkFunc is the type of the function called by Walk to visit each 235 // file or directory. 236 // 237 // The path argument contains the argument to Walk as a prefix. 238 // That is, if Walk is called with root argument "dir" and finds a file 239 // named "a" in that directory, the walk function will be called with 240 // argument "dir/a". 241 // 242 // The directory and file are joined with Join, which may clean the 243 // directory name: if Walk is called with the root argument "x/../dir" 244 // and finds a file named "a" in that directory, the walk function will 245 // be called with argument "dir/a", not "x/../dir/a". 246 // 247 // The info argument is the fs.FileInfo for the named path. 248 // 249 // The error result returned by the function controls how Walk continues. 250 // If the function returns the special value SkipDir, Walk skips the 251 // current directory (path if info.IsDir() is true, otherwise path's 252 // parent directory). If the function returns the special value SkipAll, 253 // Walk skips all remaining files and directories. Otherwise, if the function 254 // returns a non-nil error, Walk stops entirely and returns that error. 255 // 256 // The err argument reports an error related to path, signaling that Walk 257 // will not walk into that directory. The function can decide how to 258 // handle that error; as described earlier, returning the error will 259 // cause Walk to stop walking the entire tree. 260 // 261 // Walk calls the function with a non-nil err argument in two cases. 262 // 263 // First, if an os.Lstat on the root directory or any directory or file 264 // in the tree fails, Walk calls the function with path set to that 265 // directory or file's path, info set to nil, and err set to the error 266 // from os.Lstat. 267 // 268 // Second, if a directory's Readdirnames method fails, Walk calls the 269 // function with path set to the directory's path, info, set to an 270 // fs.FileInfo describing the directory, and err set to the error from 271 // Readdirnames. 272 type WalkFunc func(path string, info fs.FileInfo, err error) error