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