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 // Functions in this package replace any occurrences of the slash ('/') character 9 // with os.PathSeparator when returning paths unless otherwise specified. 10 package filepath 11 12 import ( 13 "errors" 14 "os" 15 "sort" 16 "strings" 17 ) 18 19 // A lazybuf is a lazily constructed path buffer. 20 // It supports append, reading previously appended bytes, 21 // and retrieving the final string. It does not allocate a buffer 22 // to hold the output until that output diverges from s. 23 type lazybuf struct { 24 path string 25 buf []byte 26 w int 27 volAndPath string 28 volLen int 29 } 30 31 func (b *lazybuf) index(i int) byte { 32 if b.buf != nil { 33 return b.buf[i] 34 } 35 return b.path[i] 36 } 37 38 func (b *lazybuf) append(c byte) { 39 if b.buf == nil { 40 if b.w < len(b.path) && b.path[b.w] == c { 41 b.w++ 42 return 43 } 44 b.buf = make([]byte, len(b.path)) 45 copy(b.buf, b.path[:b.w]) 46 } 47 b.buf[b.w] = c 48 b.w++ 49 } 50 51 func (b *lazybuf) string() string { 52 if b.buf == nil { 53 return b.volAndPath[:b.volLen+b.w] 54 } 55 return b.volAndPath[:b.volLen] + string(b.buf[:b.w]) 56 } 57 58 const ( 59 Separator = os.PathSeparator 60 ListSeparator = os.PathListSeparator 61 ) 62 63 // Clean returns the shortest path name equivalent to path 64 // by purely lexical processing. It applies the following rules 65 // iteratively until no further processing can be done: 66 // 67 // 1. Replace multiple Separator elements with a single one. 68 // 2. Eliminate each . path name element (the current directory). 69 // 3. Eliminate each inner .. path name element (the parent directory) 70 // along with the non-.. element that precedes it. 71 // 4. Eliminate .. elements that begin a rooted path: 72 // that is, replace "/.." by "/" at the beginning of a path, 73 // assuming Separator is '/'. 74 // 75 // The returned path ends in a slash only if it represents a root directory, 76 // such as "/" on Unix or `C:\` on Windows. 77 // 78 // If the result of this process is an empty string, Clean 79 // returns the string ".". 80 // 81 // See also Rob Pike, ``Lexical File Names in Plan 9 or 82 // Getting Dot-Dot Right,'' 83 // http://plan9.bell-labs.com/sys/doc/lexnames.html 84 func Clean(path string) string { 85 originalPath := path 86 volLen := volumeNameLen(path) 87 path = path[volLen:] 88 if path == "" { 89 if volLen > 1 && originalPath[1] != ':' { 90 // should be UNC 91 return FromSlash(originalPath) 92 } 93 return originalPath + "." 94 } 95 rooted := os.IsPathSeparator(path[0]) 96 97 // Invariants: 98 // reading from path; r is index of next byte to process. 99 // writing to buf; w is index of next byte to write. 100 // dotdot is index in buf where .. must stop, either because 101 // it is the leading slash or it is a leading ../../.. prefix. 102 n := len(path) 103 out := lazybuf{path: path, volAndPath: originalPath, volLen: volLen} 104 r, dotdot := 0, 0 105 if rooted { 106 out.append(Separator) 107 r, dotdot = 1, 1 108 } 109 110 for r < n { 111 switch { 112 case os.IsPathSeparator(path[r]): 113 // empty path element 114 r++ 115 case path[r] == '.' && (r+1 == n || os.IsPathSeparator(path[r+1])): 116 // . element 117 r++ 118 case path[r] == '.' && path[r+1] == '.' && (r+2 == n || os.IsPathSeparator(path[r+2])): 119 // .. element: remove to last separator 120 r += 2 121 switch { 122 case out.w > dotdot: 123 // can backtrack 124 out.w-- 125 for out.w > dotdot && !os.IsPathSeparator(out.index(out.w)) { 126 out.w-- 127 } 128 case !rooted: 129 // cannot backtrack, but not rooted, so append .. element. 130 if out.w > 0 { 131 out.append(Separator) 132 } 133 out.append('.') 134 out.append('.') 135 dotdot = out.w 136 } 137 default: 138 // real path element. 139 // add slash if needed 140 if rooted && out.w != 1 || !rooted && out.w != 0 { 141 out.append(Separator) 142 } 143 // copy element 144 for ; r < n && !os.IsPathSeparator(path[r]); r++ { 145 out.append(path[r]) 146 } 147 } 148 } 149 150 // Turn empty string into "." 151 if out.w == 0 { 152 out.append('.') 153 } 154 155 return FromSlash(out.string()) 156 } 157 158 // ToSlash returns the result of replacing each separator character 159 // in path with a slash ('/') character. Multiple separators are 160 // replaced by multiple slashes. 161 func ToSlash(path string) string { 162 if Separator == '/' { 163 return path 164 } 165 return strings.Replace(path, string(Separator), "/", -1) 166 } 167 168 // FromSlash returns the result of replacing each slash ('/') character 169 // in path with a separator character. Multiple slashes are replaced 170 // by multiple separators. 171 func FromSlash(path string) string { 172 if Separator == '/' { 173 return path 174 } 175 return strings.Replace(path, "/", string(Separator), -1) 176 } 177 178 // SplitList splits a list of paths joined by the OS-specific ListSeparator, 179 // usually found in PATH or GOPATH environment variables. 180 // Unlike strings.Split, SplitList returns an empty slice when passed an empty 181 // string. SplitList does not replace slash characters in the returned paths. 182 func SplitList(path string) []string { 183 return splitList(path) 184 } 185 186 // Split splits path immediately following the final Separator, 187 // separating it into a directory and file name component. 188 // If there is no Separator in path, Split returns an empty dir 189 // and file set to path. 190 // The returned values have the property that path = dir+file. 191 func Split(path string) (dir, file string) { 192 vol := VolumeName(path) 193 i := len(path) - 1 194 for i >= len(vol) && !os.IsPathSeparator(path[i]) { 195 i-- 196 } 197 return path[:i+1], path[i+1:] 198 } 199 200 // Join joins any number of path elements into a single path, adding 201 // a Separator if necessary. The result is Cleaned, in particular 202 // all empty strings are ignored. 203 // On Windows, the result is a UNC path if and only if the first path 204 // element is a UNC path. 205 func Join(elem ...string) string { 206 return join(elem) 207 } 208 209 // Ext returns the file name extension used by path. 210 // The extension is the suffix beginning at the final dot 211 // in the final element of path; it is empty if there is 212 // no dot. 213 func Ext(path string) string { 214 for i := len(path) - 1; i >= 0 && !os.IsPathSeparator(path[i]); i-- { 215 if path[i] == '.' { 216 return path[i:] 217 } 218 } 219 return "" 220 } 221 222 // EvalSymlinks returns the path name after the evaluation of any symbolic 223 // links. 224 // If path is relative the result will be relative to the current directory, 225 // unless one of the components is an absolute symbolic link. 226 func EvalSymlinks(path string) (string, error) { 227 return evalSymlinks(path) 228 } 229 230 // Abs returns an absolute representation of path. 231 // If the path is not absolute it will be joined with the current 232 // working directory to turn it into an absolute path. The absolute 233 // path name for a given file is not guaranteed to be unique. 234 func Abs(path string) (string, error) { 235 return abs(path) 236 } 237 238 func unixAbs(path string) (string, error) { 239 if IsAbs(path) { 240 return Clean(path), nil 241 } 242 wd, err := os.Getwd() 243 if err != nil { 244 return "", err 245 } 246 return Join(wd, path), nil 247 } 248 249 // Rel returns a relative path that is lexically equivalent to targpath when 250 // joined to basepath with an intervening separator. That is, 251 // Join(basepath, Rel(basepath, targpath)) is equivalent to targpath itself. 252 // On success, the returned path will always be relative to basepath, 253 // even if basepath and targpath share no elements. 254 // An error is returned if targpath can't be made relative to basepath or if 255 // knowing the current working directory would be necessary to compute it. 256 func Rel(basepath, targpath string) (string, error) { 257 baseVol := VolumeName(basepath) 258 targVol := VolumeName(targpath) 259 base := Clean(basepath) 260 targ := Clean(targpath) 261 if targ == base { 262 return ".", nil 263 } 264 base = base[len(baseVol):] 265 targ = targ[len(targVol):] 266 if base == "." { 267 base = "" 268 } 269 // Can't use IsAbs - `\a` and `a` are both relative in Windows. 270 baseSlashed := len(base) > 0 && base[0] == Separator 271 targSlashed := len(targ) > 0 && targ[0] == Separator 272 if baseSlashed != targSlashed || baseVol != targVol { 273 return "", errors.New("Rel: can't make " + targ + " relative to " + base) 274 } 275 // Position base[b0:bi] and targ[t0:ti] at the first differing elements. 276 bl := len(base) 277 tl := len(targ) 278 var b0, bi, t0, ti int 279 for { 280 for bi < bl && base[bi] != Separator { 281 bi++ 282 } 283 for ti < tl && targ[ti] != Separator { 284 ti++ 285 } 286 if targ[t0:ti] != base[b0:bi] { 287 break 288 } 289 if bi < bl { 290 bi++ 291 } 292 if ti < tl { 293 ti++ 294 } 295 b0 = bi 296 t0 = ti 297 } 298 if base[b0:bi] == ".." { 299 return "", errors.New("Rel: can't make " + targ + " relative to " + base) 300 } 301 if b0 != bl { 302 // Base elements left. Must go up before going down. 303 seps := strings.Count(base[b0:bl], string(Separator)) 304 size := 2 + seps*3 305 if tl != t0 { 306 size += 1 + tl - t0 307 } 308 buf := make([]byte, size) 309 n := copy(buf, "..") 310 for i := 0; i < seps; i++ { 311 buf[n] = Separator 312 copy(buf[n+1:], "..") 313 n += 3 314 } 315 if t0 != tl { 316 buf[n] = Separator 317 copy(buf[n+1:], targ[t0:]) 318 } 319 return string(buf), nil 320 } 321 return targ[t0:], nil 322 } 323 324 // SkipDir is used as a return value from WalkFuncs to indicate that 325 // the directory named in the call is to be skipped. It is not returned 326 // as an error by any function. 327 var SkipDir = errors.New("skip this directory") 328 329 // WalkFunc is the type of the function called for each file or directory 330 // visited by Walk. The path argument contains the argument to Walk as a 331 // prefix; that is, if Walk is called with "dir", which is a directory 332 // containing the file "a", the walk function will be called with argument 333 // "dir/a". The info argument is the os.FileInfo for the named path. 334 // 335 // If there was a problem walking to the file or directory named by path, the 336 // incoming error will describe the problem and the function can decide how 337 // to handle that error (and Walk will not descend into that directory). If 338 // an error is returned, processing stops. The sole exception is when the function 339 // returns the special value SkipDir. If the function returns SkipDir when invoked 340 // on a directory, Walk skips the directory's contents entirely. 341 // If the function returns SkipDir when invoked on a non-directory file, 342 // Walk skips the remaining files in the containing directory. 343 type WalkFunc func(path string, info os.FileInfo, err error) error 344 345 var lstat = os.Lstat // for testing 346 347 // walk recursively descends path, calling w. 348 func walk(path string, info os.FileInfo, walkFn WalkFunc) error { 349 err := walkFn(path, info, nil) 350 if err != nil { 351 if info.IsDir() && err == SkipDir { 352 return nil 353 } 354 return err 355 } 356 357 if !info.IsDir() { 358 return nil 359 } 360 361 names, err := readDirNames(path) 362 if err != nil { 363 return walkFn(path, info, err) 364 } 365 366 for _, name := range names { 367 filename := Join(path, name) 368 fileInfo, err := lstat(filename) 369 if err != nil { 370 if err := walkFn(filename, fileInfo, err); err != nil && err != SkipDir { 371 return err 372 } 373 } else { 374 err = walk(filename, fileInfo, walkFn) 375 if err != nil { 376 if !fileInfo.IsDir() || err != SkipDir { 377 return err 378 } 379 } 380 } 381 } 382 return nil 383 } 384 385 // Walk walks the file tree rooted at root, calling walkFn for each file or 386 // directory in the tree, including root. All errors that arise visiting files 387 // and directories are filtered by walkFn. The files are walked in lexical 388 // order, which makes the output deterministic but means that for very 389 // large directories Walk can be inefficient. 390 // Walk does not follow symbolic links. 391 func Walk(root string, walkFn WalkFunc) error { 392 info, err := os.Lstat(root) 393 if err != nil { 394 return walkFn(root, nil, err) 395 } 396 return walk(root, info, walkFn) 397 } 398 399 // readDirNames reads the directory named by dirname and returns 400 // a sorted list of directory entries. 401 func readDirNames(dirname string) ([]string, error) { 402 f, err := os.Open(dirname) 403 if err != nil { 404 return nil, err 405 } 406 names, err := f.Readdirnames(-1) 407 f.Close() 408 if err != nil { 409 return nil, err 410 } 411 sort.Strings(names) 412 return names, nil 413 } 414 415 // Base returns the last element of path. 416 // Trailing path separators are removed before extracting the last element. 417 // If the path is empty, Base returns ".". 418 // If the path consists entirely of separators, Base returns a single separator. 419 func Base(path string) string { 420 if path == "" { 421 return "." 422 } 423 // Strip trailing slashes. 424 for len(path) > 0 && os.IsPathSeparator(path[len(path)-1]) { 425 path = path[0 : len(path)-1] 426 } 427 // Throw away volume name 428 path = path[len(VolumeName(path)):] 429 // Find the last element 430 i := len(path) - 1 431 for i >= 0 && !os.IsPathSeparator(path[i]) { 432 i-- 433 } 434 if i >= 0 { 435 path = path[i+1:] 436 } 437 // If empty now, it had only slashes. 438 if path == "" { 439 return string(Separator) 440 } 441 return path 442 } 443 444 // Dir returns all but the last element of path, typically the path's directory. 445 // After dropping the final element, the path is Cleaned and trailing 446 // slashes are removed. 447 // If the path is empty, Dir returns ".". 448 // If the path consists entirely of separators, Dir returns a single separator. 449 // The returned path does not end in a separator unless it is the root directory. 450 func Dir(path string) string { 451 vol := VolumeName(path) 452 i := len(path) - 1 453 for i >= len(vol) && !os.IsPathSeparator(path[i]) { 454 i-- 455 } 456 dir := Clean(path[len(vol) : i+1]) 457 return vol + dir 458 } 459 460 // VolumeName returns leading volume name. 461 // Given "C:\foo\bar" it returns "C:" on Windows. 462 // Given "\\host\share\foo" it returns "\\host\share". 463 // On other platforms it returns "". 464 func VolumeName(path string) string { 465 return path[:volumeNameLen(path)] 466 } 467