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