1// Copyright 2016 Google Inc. All rights reserved. 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 15package pathtools 16 17import ( 18 "bytes" 19 "fmt" 20 "io" 21 "io/ioutil" 22 "os" 23 "path/filepath" 24 "sort" 25 "strings" 26 "syscall" 27 "time" 28) 29 30// Based on Andrew Gerrand's "10 things you (probably) dont' know about Go" 31 32type ShouldFollowSymlinks bool 33 34const ( 35 FollowSymlinks = ShouldFollowSymlinks(true) 36 DontFollowSymlinks = ShouldFollowSymlinks(false) 37) 38 39var OsFs FileSystem = &osFs{} 40 41func MockFs(files map[string][]byte) FileSystem { 42 fs := &mockFs{ 43 files: make(map[string][]byte, len(files)), 44 dirs: make(map[string]bool), 45 symlinks: make(map[string]string), 46 all: []string(nil), 47 } 48 49 for f, b := range files { 50 if tokens := strings.SplitN(f, "->", 2); len(tokens) == 2 { 51 fs.symlinks[strings.TrimSpace(tokens[0])] = strings.TrimSpace(tokens[1]) 52 continue 53 } 54 55 fs.files[filepath.Clean(f)] = b 56 dir := filepath.Dir(f) 57 for dir != "." && dir != "/" { 58 fs.dirs[dir] = true 59 dir = filepath.Dir(dir) 60 } 61 fs.dirs[dir] = true 62 } 63 64 fs.dirs["."] = true 65 fs.dirs["/"] = true 66 67 for f := range fs.files { 68 fs.all = append(fs.all, f) 69 } 70 71 for d := range fs.dirs { 72 fs.all = append(fs.all, d) 73 } 74 75 for s := range fs.symlinks { 76 fs.all = append(fs.all, s) 77 } 78 79 sort.Strings(fs.all) 80 81 return fs 82} 83 84type ReaderAtSeekerCloser interface { 85 io.Reader 86 io.ReaderAt 87 io.Seeker 88 io.Closer 89} 90 91type FileSystem interface { 92 // Open opens a file for reading. Follows symlinks. 93 Open(name string) (ReaderAtSeekerCloser, error) 94 95 // Exists returns whether the file exists and whether it is a directory. Follows symlinks. 96 Exists(name string) (bool, bool, error) 97 98 Glob(pattern string, excludes []string, follow ShouldFollowSymlinks) (GlobResult, error) 99 glob(pattern string) (matches []string, err error) 100 101 // IsDir returns true if the path points to a directory, false it it points to a file. Follows symlinks. 102 // Returns os.ErrNotExist if the path does not exist or is a symlink to a path that does not exist. 103 IsDir(name string) (bool, error) 104 105 // IsSymlink returns true if the path points to a symlink, even if that symlink points to a path that does 106 // not exist. Returns os.ErrNotExist if the path does not exist. 107 IsSymlink(name string) (bool, error) 108 109 // Lstat returns info on a file without following symlinks. 110 Lstat(name string) (os.FileInfo, error) 111 112 // Lstat returns info on a file. 113 Stat(name string) (os.FileInfo, error) 114 115 // ListDirsRecursive returns a list of all the directories in a path, following symlinks if requested. 116 ListDirsRecursive(name string, follow ShouldFollowSymlinks) (dirs []string, err error) 117 118 // ReadDirNames returns a list of everything in a directory. 119 ReadDirNames(name string) ([]string, error) 120 121 // Readlink returns the destination of the named symbolic link. 122 Readlink(name string) (string, error) 123} 124 125// osFs implements FileSystem using the local disk. 126type osFs struct { 127 srcDir string 128} 129 130func NewOsFs(path string) FileSystem { 131 return &osFs{srcDir: path} 132} 133 134func (fs *osFs) toAbs(path string) string { 135 if filepath.IsAbs(path) { 136 return path 137 } 138 return filepath.Join(fs.srcDir, path) 139} 140 141func (fs *osFs) removeSrcDirPrefix(path string) string { 142 if fs.srcDir == "" { 143 return path 144 } 145 rel, err := filepath.Rel(fs.srcDir, path) 146 if err != nil { 147 panic(fmt.Errorf("unexpected failure in removeSrcDirPrefix filepath.Rel(%s, %s): %s", 148 fs.srcDir, path, err)) 149 } 150 if strings.HasPrefix(rel, "../") { 151 panic(fmt.Errorf("unexpected relative path outside directory in removeSrcDirPrefix filepath.Rel(%s, %s): %s", 152 fs.srcDir, path, rel)) 153 } 154 return rel 155} 156 157func (fs *osFs) removeSrcDirPrefixes(paths []string) []string { 158 if fs.srcDir != "" { 159 for i, path := range paths { 160 paths[i] = fs.removeSrcDirPrefix(path) 161 } 162 } 163 return paths 164} 165 166func (fs *osFs) Open(name string) (ReaderAtSeekerCloser, error) { 167 return os.Open(fs.toAbs(name)) 168} 169 170func (fs *osFs) Exists(name string) (bool, bool, error) { 171 stat, err := os.Stat(fs.toAbs(name)) 172 if err == nil { 173 return true, stat.IsDir(), nil 174 } else if os.IsNotExist(err) { 175 return false, false, nil 176 } else { 177 return false, false, err 178 } 179} 180 181func (fs *osFs) IsDir(name string) (bool, error) { 182 info, err := os.Stat(fs.toAbs(name)) 183 if err != nil { 184 return false, err 185 } 186 return info.IsDir(), nil 187} 188 189func (fs *osFs) IsSymlink(name string) (bool, error) { 190 if info, err := os.Lstat(fs.toAbs(name)); err != nil { 191 return false, err 192 } else { 193 return info.Mode()&os.ModeSymlink != 0, nil 194 } 195} 196 197func (fs *osFs) Glob(pattern string, excludes []string, follow ShouldFollowSymlinks) (GlobResult, error) { 198 return startGlob(fs, pattern, excludes, follow) 199} 200 201func (fs *osFs) glob(pattern string) ([]string, error) { 202 paths, err := filepath.Glob(fs.toAbs(pattern)) 203 fs.removeSrcDirPrefixes(paths) 204 return paths, err 205} 206 207func (fs *osFs) Lstat(path string) (stats os.FileInfo, err error) { 208 return os.Lstat(fs.toAbs(path)) 209} 210 211func (fs *osFs) Stat(path string) (stats os.FileInfo, err error) { 212 return os.Stat(fs.toAbs(path)) 213} 214 215// Returns a list of all directories under dir 216func (fs *osFs) ListDirsRecursive(name string, follow ShouldFollowSymlinks) (dirs []string, err error) { 217 return listDirsRecursive(fs, name, follow) 218} 219 220func (fs *osFs) ReadDirNames(name string) ([]string, error) { 221 dir, err := os.Open(fs.toAbs(name)) 222 if err != nil { 223 return nil, err 224 } 225 defer dir.Close() 226 227 contents, err := dir.Readdirnames(-1) 228 if err != nil { 229 return nil, err 230 } 231 232 sort.Strings(contents) 233 return contents, nil 234} 235 236func (fs *osFs) Readlink(name string) (string, error) { 237 return os.Readlink(fs.toAbs(name)) 238} 239 240type mockFs struct { 241 files map[string][]byte 242 dirs map[string]bool 243 symlinks map[string]string 244 all []string 245} 246 247func (m *mockFs) followSymlinks(name string) string { 248 dir, file := saneSplit(name) 249 if dir != "." && dir != "/" { 250 dir = m.followSymlinks(dir) 251 } 252 name = filepath.Join(dir, file) 253 254 for i := 0; i < 255; i++ { 255 i++ 256 if i > 255 { 257 panic("symlink loop") 258 } 259 to, exists := m.symlinks[name] 260 if !exists { 261 break 262 } 263 if filepath.IsAbs(to) { 264 name = to 265 } else { 266 name = filepath.Join(dir, to) 267 } 268 } 269 return name 270} 271 272func (m *mockFs) Open(name string) (ReaderAtSeekerCloser, error) { 273 name = filepath.Clean(name) 274 name = m.followSymlinks(name) 275 if f, ok := m.files[name]; ok { 276 return struct { 277 io.Closer 278 *bytes.Reader 279 }{ 280 ioutil.NopCloser(nil), 281 bytes.NewReader(f), 282 }, nil 283 } 284 285 return nil, &os.PathError{ 286 Op: "open", 287 Path: name, 288 Err: os.ErrNotExist, 289 } 290} 291 292func (m *mockFs) Exists(name string) (bool, bool, error) { 293 name = filepath.Clean(name) 294 name = m.followSymlinks(name) 295 if _, ok := m.files[name]; ok { 296 return ok, false, nil 297 } 298 if _, ok := m.dirs[name]; ok { 299 return ok, true, nil 300 } 301 return false, false, nil 302} 303 304func (m *mockFs) IsDir(name string) (bool, error) { 305 dir := filepath.Dir(name) 306 if dir != "." && dir != "/" { 307 isDir, err := m.IsDir(dir) 308 309 if serr, ok := err.(*os.SyscallError); ok && serr.Err == syscall.ENOTDIR { 310 isDir = false 311 } else if err != nil { 312 return false, err 313 } 314 315 if !isDir { 316 return false, os.NewSyscallError("stat "+name, syscall.ENOTDIR) 317 } 318 } 319 320 name = filepath.Clean(name) 321 name = m.followSymlinks(name) 322 323 if _, ok := m.dirs[name]; ok { 324 return true, nil 325 } 326 if _, ok := m.files[name]; ok { 327 return false, nil 328 } 329 return false, os.ErrNotExist 330} 331 332func (m *mockFs) IsSymlink(name string) (bool, error) { 333 dir, file := saneSplit(name) 334 dir = m.followSymlinks(dir) 335 name = filepath.Join(dir, file) 336 337 if _, isSymlink := m.symlinks[name]; isSymlink { 338 return true, nil 339 } 340 if _, isDir := m.dirs[name]; isDir { 341 return false, nil 342 } 343 if _, isFile := m.files[name]; isFile { 344 return false, nil 345 } 346 return false, os.ErrNotExist 347} 348 349func (m *mockFs) Glob(pattern string, excludes []string, follow ShouldFollowSymlinks) (GlobResult, error) { 350 return startGlob(m, pattern, excludes, follow) 351} 352 353func unescapeGlob(s string) string { 354 i := 0 355 for i < len(s) { 356 if s[i] == '\\' { 357 s = s[:i] + s[i+1:] 358 } else { 359 i++ 360 } 361 } 362 return s 363} 364 365func (m *mockFs) glob(pattern string) ([]string, error) { 366 dir, file := saneSplit(pattern) 367 368 dir = unescapeGlob(dir) 369 toDir := m.followSymlinks(dir) 370 371 var matches []string 372 for _, f := range m.all { 373 fDir, fFile := saneSplit(f) 374 if toDir == fDir { 375 match, err := filepath.Match(file, fFile) 376 if err != nil { 377 return nil, err 378 } 379 if (f == "." || f == "/") && f != pattern { 380 // filepath.Glob won't return "." or "/" unless the pattern was "." or "/" 381 match = false 382 } 383 if match { 384 matches = append(matches, filepath.Join(dir, fFile)) 385 } 386 } 387 } 388 return matches, nil 389} 390 391type mockStat struct { 392 name string 393 size int64 394 mode os.FileMode 395} 396 397func (ms *mockStat) Name() string { return ms.name } 398func (ms *mockStat) IsDir() bool { return ms.Mode().IsDir() } 399func (ms *mockStat) Size() int64 { return ms.size } 400func (ms *mockStat) Mode() os.FileMode { return ms.mode } 401func (ms *mockStat) ModTime() time.Time { return time.Time{} } 402func (ms *mockStat) Sys() interface{} { return nil } 403 404func (m *mockFs) Lstat(name string) (os.FileInfo, error) { 405 dir, file := saneSplit(name) 406 dir = m.followSymlinks(dir) 407 name = filepath.Join(dir, file) 408 409 ms := mockStat{ 410 name: file, 411 } 412 413 if symlink, isSymlink := m.symlinks[name]; isSymlink { 414 ms.mode = os.ModeSymlink 415 ms.size = int64(len(symlink)) 416 } else if _, isDir := m.dirs[name]; isDir { 417 ms.mode = os.ModeDir 418 } else if _, isFile := m.files[name]; isFile { 419 ms.mode = 0 420 ms.size = int64(len(m.files[name])) 421 } else { 422 return nil, os.ErrNotExist 423 } 424 425 return &ms, nil 426} 427 428func (m *mockFs) Stat(name string) (os.FileInfo, error) { 429 name = filepath.Clean(name) 430 origName := name 431 name = m.followSymlinks(name) 432 433 ms := mockStat{ 434 name: filepath.Base(origName), 435 size: int64(len(m.files[name])), 436 } 437 438 if _, isDir := m.dirs[name]; isDir { 439 ms.mode = os.ModeDir 440 } else if _, isFile := m.files[name]; isFile { 441 ms.mode = 0 442 ms.size = int64(len(m.files[name])) 443 } else { 444 return nil, os.ErrNotExist 445 } 446 447 return &ms, nil 448} 449 450func (m *mockFs) ReadDirNames(name string) ([]string, error) { 451 name = filepath.Clean(name) 452 name = m.followSymlinks(name) 453 454 exists, isDir, err := m.Exists(name) 455 if err != nil { 456 return nil, err 457 } 458 if !exists { 459 return nil, os.ErrNotExist 460 } 461 if !isDir { 462 return nil, os.NewSyscallError("readdir", syscall.ENOTDIR) 463 } 464 465 var ret []string 466 for _, f := range m.all { 467 dir, file := saneSplit(f) 468 if dir == name && len(file) > 0 && file[0] != '.' { 469 ret = append(ret, file) 470 } 471 } 472 return ret, nil 473} 474 475func (m *mockFs) ListDirsRecursive(name string, follow ShouldFollowSymlinks) ([]string, error) { 476 return listDirsRecursive(m, name, follow) 477} 478 479func (m *mockFs) Readlink(name string) (string, error) { 480 dir, file := saneSplit(name) 481 dir = m.followSymlinks(dir) 482 483 origName := name 484 name = filepath.Join(dir, file) 485 486 if dest, isSymlink := m.symlinks[name]; isSymlink { 487 return dest, nil 488 } 489 490 if exists, _, err := m.Exists(name); err != nil { 491 return "", err 492 } else if !exists { 493 return "", os.ErrNotExist 494 } else { 495 return "", os.NewSyscallError("readlink: "+origName, syscall.EINVAL) 496 } 497} 498 499func listDirsRecursive(fs FileSystem, name string, follow ShouldFollowSymlinks) ([]string, error) { 500 name = filepath.Clean(name) 501 502 isDir, err := fs.IsDir(name) 503 if err != nil { 504 return nil, err 505 } 506 507 if !isDir { 508 return nil, nil 509 } 510 511 dirs := []string{name} 512 513 subDirs, err := listDirsRecursiveRelative(fs, name, follow, 0) 514 if err != nil { 515 return nil, err 516 } 517 518 for _, d := range subDirs { 519 dirs = append(dirs, filepath.Join(name, d)) 520 } 521 522 return dirs, nil 523} 524 525func listDirsRecursiveRelative(fs FileSystem, name string, follow ShouldFollowSymlinks, depth int) ([]string, error) { 526 depth++ 527 if depth > 255 { 528 return nil, fmt.Errorf("too many symlinks") 529 } 530 contents, err := fs.ReadDirNames(name) 531 if err != nil { 532 return nil, err 533 } 534 535 var dirs []string 536 for _, f := range contents { 537 if f[0] == '.' { 538 continue 539 } 540 f = filepath.Join(name, f) 541 var info os.FileInfo 542 if follow == DontFollowSymlinks { 543 info, err = fs.Lstat(f) 544 if err != nil { 545 continue 546 } 547 if info.Mode()&os.ModeSymlink != 0 { 548 continue 549 } 550 } else { 551 info, err = fs.Stat(f) 552 if err != nil { 553 continue 554 } 555 } 556 if info.IsDir() { 557 dirs = append(dirs, f) 558 subDirs, err := listDirsRecursiveRelative(fs, f, follow, depth) 559 if err != nil { 560 return nil, err 561 } 562 for _, s := range subDirs { 563 dirs = append(dirs, filepath.Join(f, s)) 564 } 565 } 566 } 567 568 for i, d := range dirs { 569 rel, err := filepath.Rel(name, d) 570 if err != nil { 571 return nil, err 572 } 573 dirs[i] = rel 574 } 575 576 return dirs, nil 577} 578