github.com/golang/dep@v0.5.4/internal/fs/fs.go (about) 1 // Copyright 2016 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 fs 6 7 import ( 8 "io" 9 "io/ioutil" 10 "os" 11 "path/filepath" 12 "runtime" 13 "strings" 14 "syscall" 15 "unicode" 16 17 "github.com/pkg/errors" 18 ) 19 20 // HasFilepathPrefix will determine if "path" starts with "prefix" from 21 // the point of view of a filesystem. 22 // 23 // Unlike filepath.HasPrefix, this function is path-aware, meaning that 24 // it knows that two directories /foo and /foobar are not the same 25 // thing, and therefore HasFilepathPrefix("/foobar", "/foo") will return 26 // false. 27 // 28 // This function also handles the case where the involved filesystems 29 // are case-insensitive, meaning /foo/bar and /Foo/Bar correspond to the 30 // same file. In that situation HasFilepathPrefix("/Foo/Bar", "/foo") 31 // will return true. The implementation is *not* OS-specific, so a FAT32 32 // filesystem mounted on Linux will be handled correctly. 33 func HasFilepathPrefix(path, prefix string) (bool, error) { 34 // this function is more convoluted then ideal due to need for special 35 // handling of volume name/drive letter on Windows. vnPath and vnPrefix 36 // are first compared, and then used to initialize initial values of p and 37 // d which will be appended to for incremental checks using 38 // IsCaseSensitiveFilesystem and then equality. 39 40 // no need to check IsCaseSensitiveFilesystem because VolumeName return 41 // empty string on all non-Windows machines 42 vnPath := strings.ToLower(filepath.VolumeName(path)) 43 vnPrefix := strings.ToLower(filepath.VolumeName(prefix)) 44 if vnPath != vnPrefix { 45 return false, nil 46 } 47 48 // Because filepath.Join("c:","dir") returns "c:dir", we have to manually 49 // add path separator to drive letters. Also, we need to set the path root 50 // on *nix systems, since filepath.Join("", "dir") returns a relative path. 51 vnPath += string(os.PathSeparator) 52 vnPrefix += string(os.PathSeparator) 53 54 var dn string 55 56 if isDir, err := IsDir(path); err != nil { 57 return false, errors.Wrap(err, "failed to check filepath prefix") 58 } else if isDir { 59 dn = path 60 } else { 61 dn = filepath.Dir(path) 62 } 63 64 dn = filepath.Clean(dn) 65 prefix = filepath.Clean(prefix) 66 67 // [1:] in the lines below eliminates empty string on *nix and volume name on Windows 68 dirs := strings.Split(dn, string(os.PathSeparator))[1:] 69 prefixes := strings.Split(prefix, string(os.PathSeparator))[1:] 70 71 if len(prefixes) > len(dirs) { 72 return false, nil 73 } 74 75 // d,p are initialized with "/" on *nix and volume name on Windows 76 d := vnPath 77 p := vnPrefix 78 79 for i := range prefixes { 80 // need to test each component of the path for 81 // case-sensitiveness because on Unix we could have 82 // something like ext4 filesystem mounted on FAT 83 // mountpoint, mounted on ext4 filesystem, i.e. the 84 // problematic filesystem is not the last one. 85 caseSensitive, err := IsCaseSensitiveFilesystem(filepath.Join(d, dirs[i])) 86 if err != nil { 87 return false, errors.Wrap(err, "failed to check filepath prefix") 88 } 89 if caseSensitive { 90 d = filepath.Join(d, dirs[i]) 91 p = filepath.Join(p, prefixes[i]) 92 } else { 93 d = filepath.Join(d, strings.ToLower(dirs[i])) 94 p = filepath.Join(p, strings.ToLower(prefixes[i])) 95 } 96 97 if p != d { 98 return false, nil 99 } 100 } 101 102 return true, nil 103 } 104 105 // EquivalentPaths compares the paths passed to check if they are equivalent. 106 // It respects the case-sensitivity of the underlying filesysyems. 107 func EquivalentPaths(p1, p2 string) (bool, error) { 108 p1 = filepath.Clean(p1) 109 p2 = filepath.Clean(p2) 110 111 fi1, err := os.Stat(p1) 112 if err != nil { 113 return false, errors.Wrapf(err, "could not check for path equivalence") 114 } 115 fi2, err := os.Stat(p2) 116 if err != nil { 117 return false, errors.Wrapf(err, "could not check for path equivalence") 118 } 119 120 p1Filename, p2Filename := "", "" 121 122 if !fi1.IsDir() { 123 p1, p1Filename = filepath.Split(p1) 124 } 125 if !fi2.IsDir() { 126 p2, p2Filename = filepath.Split(p2) 127 } 128 129 if isPrefix1, err := HasFilepathPrefix(p1, p2); err != nil { 130 return false, errors.Wrap(err, "failed to check for path equivalence") 131 } else if isPrefix2, err := HasFilepathPrefix(p2, p1); err != nil { 132 return false, errors.Wrap(err, "failed to check for path equivalence") 133 } else if !isPrefix1 || !isPrefix2 { 134 return false, nil 135 } 136 137 if p1Filename != "" || p2Filename != "" { 138 caseSensitive, err := IsCaseSensitiveFilesystem(filepath.Join(p1, p1Filename)) 139 if err != nil { 140 return false, errors.Wrap(err, "could not check for filesystem case-sensitivity") 141 } 142 if caseSensitive { 143 if p1Filename != p2Filename { 144 return false, nil 145 } 146 } else { 147 if !strings.EqualFold(p1Filename, p2Filename) { 148 return false, nil 149 } 150 } 151 } 152 153 return true, nil 154 } 155 156 // RenameWithFallback attempts to rename a file or directory, but falls back to 157 // copying in the event of a cross-device link error. If the fallback copy 158 // succeeds, src is still removed, emulating normal rename behavior. 159 func RenameWithFallback(src, dst string) error { 160 _, err := os.Stat(src) 161 if err != nil { 162 return errors.Wrapf(err, "cannot stat %s", src) 163 } 164 165 err = os.Rename(src, dst) 166 if err == nil { 167 return nil 168 } 169 170 return renameFallback(err, src, dst) 171 } 172 173 // renameByCopy attempts to rename a file or directory by copying it to the 174 // destination and then removing the src thus emulating the rename behavior. 175 func renameByCopy(src, dst string) error { 176 var cerr error 177 if dir, _ := IsDir(src); dir { 178 cerr = CopyDir(src, dst) 179 if cerr != nil { 180 cerr = errors.Wrap(cerr, "copying directory failed") 181 } 182 } else { 183 cerr = copyFile(src, dst) 184 if cerr != nil { 185 cerr = errors.Wrap(cerr, "copying file failed") 186 } 187 } 188 189 if cerr != nil { 190 return errors.Wrapf(cerr, "rename fallback failed: cannot rename %s to %s", src, dst) 191 } 192 193 return errors.Wrapf(os.RemoveAll(src), "cannot delete %s", src) 194 } 195 196 // IsCaseSensitiveFilesystem determines if the filesystem where dir 197 // exists is case sensitive or not. 198 // 199 // CAVEAT: this function works by taking the last component of the given 200 // path and flipping the case of the first letter for which case 201 // flipping is a reversible operation (/foo/Bar → /foo/bar), then 202 // testing for the existence of the new filename. There are two 203 // possibilities: 204 // 205 // 1. The alternate filename does not exist. We can conclude that the 206 // filesystem is case sensitive. 207 // 208 // 2. The filename happens to exist. We have to test if the two files 209 // are the same file (case insensitive file system) or different ones 210 // (case sensitive filesystem). 211 // 212 // If the input directory is such that the last component is composed 213 // exclusively of case-less codepoints (e.g. numbers), this function will 214 // return false. 215 func IsCaseSensitiveFilesystem(dir string) (bool, error) { 216 alt := filepath.Join(filepath.Dir(dir), genTestFilename(filepath.Base(dir))) 217 218 dInfo, err := os.Stat(dir) 219 if err != nil { 220 return false, errors.Wrap(err, "could not determine the case-sensitivity of the filesystem") 221 } 222 223 aInfo, err := os.Stat(alt) 224 if err != nil { 225 // If the file doesn't exists, assume we are on a case-sensitive filesystem. 226 if os.IsNotExist(err) { 227 return true, nil 228 } 229 230 return false, errors.Wrap(err, "could not determine the case-sensitivity of the filesystem") 231 } 232 233 return !os.SameFile(dInfo, aInfo), nil 234 } 235 236 // genTestFilename returns a string with at most one rune case-flipped. 237 // 238 // The transformation is applied only to the first rune that can be 239 // reversibly case-flipped, meaning: 240 // 241 // * A lowercase rune for which it's true that lower(upper(r)) == r 242 // * An uppercase rune for which it's true that upper(lower(r)) == r 243 // 244 // All the other runes are left intact. 245 func genTestFilename(str string) string { 246 flip := true 247 return strings.Map(func(r rune) rune { 248 if flip { 249 if unicode.IsLower(r) { 250 u := unicode.ToUpper(r) 251 if unicode.ToLower(u) == r { 252 r = u 253 flip = false 254 } 255 } else if unicode.IsUpper(r) { 256 l := unicode.ToLower(r) 257 if unicode.ToUpper(l) == r { 258 r = l 259 flip = false 260 } 261 } 262 } 263 return r 264 }, str) 265 } 266 267 var errPathNotDir = errors.New("given path is not a directory") 268 269 // ReadActualFilenames is used to determine the actual file names in given directory. 270 // 271 // On case sensitive file systems like ext4, it will check if those files exist using 272 // `os.Stat` and return a map with key and value as filenames which exist in the folder. 273 // 274 // Otherwise, it reads the contents of the directory and returns a map which has the 275 // given file name as the key and actual filename as the value(if it was found). 276 func ReadActualFilenames(dirPath string, names []string) (map[string]string, error) { 277 actualFilenames := make(map[string]string, len(names)) 278 if len(names) == 0 { 279 // This isn't expected to happen for current usage. Adding edge case handling, 280 // as it may be useful in future. 281 return actualFilenames, nil 282 } 283 // First, check that the given path is valid and it is a directory 284 dirStat, err := os.Stat(dirPath) 285 if err != nil { 286 return nil, errors.Wrap(err, "failed to read actual filenames") 287 } 288 289 if !dirStat.IsDir() { 290 return nil, errPathNotDir 291 } 292 293 // Ideally, we would use `os.Stat` for getting the actual file names but that returns 294 // the name we passed in as an argument and not the actual filename. So we are forced 295 // to list the directory contents and check against that. Since this check is costly, 296 // we do it only if absolutely necessary. 297 caseSensitive, err := IsCaseSensitiveFilesystem(dirPath) 298 if err != nil { 299 return nil, errors.Wrap(err, "failed to read actual filenames") 300 } 301 if caseSensitive { 302 // There will be no difference between actual filename and given filename. So 303 // just check if those files exist. 304 for _, name := range names { 305 _, err := os.Stat(filepath.Join(dirPath, name)) 306 if err == nil { 307 actualFilenames[name] = name 308 } else if !os.IsNotExist(err) { 309 // Some unexpected err, wrap and return it. 310 return nil, errors.Wrap(err, "failed to read actual filenames") 311 } 312 } 313 return actualFilenames, nil 314 } 315 316 dir, err := os.Open(dirPath) 317 if err != nil { 318 return nil, errors.Wrap(err, "failed to read actual filenames") 319 } 320 defer dir.Close() 321 322 // Pass -1 to read all filenames in directory 323 filenames, err := dir.Readdirnames(-1) 324 if err != nil { 325 return nil, errors.Wrap(err, "failed to read actual filenames") 326 } 327 328 // namesMap holds the mapping from lowercase name to search name. Using this, we can 329 // avoid repeatedly looping through names. 330 namesMap := make(map[string]string, len(names)) 331 for _, name := range names { 332 namesMap[strings.ToLower(name)] = name 333 } 334 335 for _, filename := range filenames { 336 searchName, ok := namesMap[strings.ToLower(filename)] 337 if ok { 338 // We are interested in this file, case insensitive match successful. 339 actualFilenames[searchName] = filename 340 if len(actualFilenames) == len(names) { 341 // We found all that we were looking for. 342 return actualFilenames, nil 343 } 344 } 345 } 346 return actualFilenames, nil 347 } 348 349 var ( 350 errSrcNotDir = errors.New("source is not a directory") 351 errDstExist = errors.New("destination already exists") 352 ) 353 354 // CopyDir recursively copies a directory tree, attempting to preserve permissions. 355 // Source directory must exist, destination directory must *not* exist. 356 func CopyDir(src, dst string) error { 357 src = filepath.Clean(src) 358 dst = filepath.Clean(dst) 359 360 // We use os.Lstat() here to ensure we don't fall in a loop where a symlink 361 // actually links to a one of its parent directories. 362 fi, err := os.Lstat(src) 363 if err != nil { 364 return err 365 } 366 if !fi.IsDir() { 367 return errSrcNotDir 368 } 369 370 _, err = os.Stat(dst) 371 if err != nil && !os.IsNotExist(err) { 372 return err 373 } 374 if err == nil { 375 return errDstExist 376 } 377 378 if err = os.MkdirAll(dst, fi.Mode()); err != nil { 379 return errors.Wrapf(err, "cannot mkdir %s", dst) 380 } 381 382 entries, err := ioutil.ReadDir(src) 383 if err != nil { 384 return errors.Wrapf(err, "cannot read directory %s", dst) 385 } 386 387 for _, entry := range entries { 388 srcPath := filepath.Join(src, entry.Name()) 389 dstPath := filepath.Join(dst, entry.Name()) 390 391 if entry.IsDir() { 392 if err = CopyDir(srcPath, dstPath); err != nil { 393 return errors.Wrap(err, "copying directory failed") 394 } 395 } else { 396 // This will include symlinks, which is what we want when 397 // copying things. 398 if err = copyFile(srcPath, dstPath); err != nil { 399 return errors.Wrap(err, "copying file failed") 400 } 401 } 402 } 403 404 return nil 405 } 406 407 // copyFile copies the contents of the file named src to the file named 408 // by dst. The file will be created if it does not already exist. If the 409 // destination file exists, all its contents will be replaced by the contents 410 // of the source file. The file mode will be copied from the source. 411 func copyFile(src, dst string) (err error) { 412 if sym, err := IsSymlink(src); err != nil { 413 return errors.Wrap(err, "symlink check failed") 414 } else if sym { 415 if err := cloneSymlink(src, dst); err != nil { 416 if runtime.GOOS == "windows" { 417 // If cloning the symlink fails on Windows because the user 418 // does not have the required privileges, ignore the error and 419 // fall back to copying the file contents. 420 // 421 // ERROR_PRIVILEGE_NOT_HELD is 1314 (0x522): 422 // https://msdn.microsoft.com/en-us/library/windows/desktop/ms681385(v=vs.85).aspx 423 if lerr, ok := err.(*os.LinkError); ok && lerr.Err != syscall.Errno(1314) { 424 return err 425 } 426 } else { 427 return err 428 } 429 } else { 430 return nil 431 } 432 } 433 434 in, err := os.Open(src) 435 if err != nil { 436 return 437 } 438 defer in.Close() 439 440 out, err := os.Create(dst) 441 if err != nil { 442 return 443 } 444 445 if _, err = io.Copy(out, in); err != nil { 446 out.Close() 447 return 448 } 449 450 // Check for write errors on Close 451 if err = out.Close(); err != nil { 452 return 453 } 454 455 si, err := os.Stat(src) 456 if err != nil { 457 return 458 } 459 460 // Temporary fix for Go < 1.9 461 // 462 // See: https://github.com/golang/dep/issues/774 463 // and https://github.com/golang/go/issues/20829 464 if runtime.GOOS == "windows" { 465 dst = fixLongPath(dst) 466 } 467 err = os.Chmod(dst, si.Mode()) 468 469 return 470 } 471 472 // cloneSymlink will create a new symlink that points to the resolved path of sl. 473 // If sl is a relative symlink, dst will also be a relative symlink. 474 func cloneSymlink(sl, dst string) error { 475 resolved, err := os.Readlink(sl) 476 if err != nil { 477 return err 478 } 479 480 return os.Symlink(resolved, dst) 481 } 482 483 // EnsureDir tries to ensure that a directory is present at the given path. It first 484 // checks if the directory already exists at the given path. If there isn't one, it tries 485 // to create it with the given permissions. However, it does not try to create the 486 // directory recursively. 487 func EnsureDir(path string, perm os.FileMode) error { 488 _, err := IsDir(path) 489 490 if os.IsNotExist(err) { 491 err = os.Mkdir(path, perm) 492 if err != nil { 493 return errors.Wrapf(err, "failed to ensure directory at %q", path) 494 } 495 } 496 497 return err 498 } 499 500 // IsDir determines is the path given is a directory or not. 501 func IsDir(name string) (bool, error) { 502 fi, err := os.Stat(name) 503 if err != nil { 504 return false, err 505 } 506 if !fi.IsDir() { 507 return false, errors.Errorf("%q is not a directory", name) 508 } 509 return true, nil 510 } 511 512 // IsNonEmptyDir determines if the path given is a non-empty directory or not. 513 func IsNonEmptyDir(name string) (bool, error) { 514 isDir, err := IsDir(name) 515 if err != nil && !os.IsNotExist(err) { 516 return false, err 517 } else if !isDir { 518 return false, nil 519 } 520 521 // Get file descriptor 522 f, err := os.Open(name) 523 if err != nil { 524 return false, err 525 } 526 defer f.Close() 527 528 // Query only 1 child. EOF if no children. 529 _, err = f.Readdirnames(1) 530 switch err { 531 case io.EOF: 532 return false, nil 533 case nil: 534 return true, nil 535 default: 536 return false, err 537 } 538 } 539 540 // IsRegular determines if the path given is a regular file or not. 541 func IsRegular(name string) (bool, error) { 542 fi, err := os.Stat(name) 543 if os.IsNotExist(err) { 544 return false, nil 545 } 546 if err != nil { 547 return false, err 548 } 549 mode := fi.Mode() 550 if mode&os.ModeType != 0 { 551 return false, errors.Errorf("%q is a %v, expected a file", name, mode) 552 } 553 return true, nil 554 } 555 556 // IsSymlink determines if the given path is a symbolic link. 557 func IsSymlink(path string) (bool, error) { 558 l, err := os.Lstat(path) 559 if err != nil { 560 return false, err 561 } 562 563 return l.Mode()&os.ModeSymlink == os.ModeSymlink, nil 564 } 565 566 // fixLongPath returns the extended-length (\\?\-prefixed) form of 567 // path when needed, in order to avoid the default 260 character file 568 // path limit imposed by Windows. If path is not easily converted to 569 // the extended-length form (for example, if path is a relative path 570 // or contains .. elements), or is short enough, fixLongPath returns 571 // path unmodified. 572 // 573 // See https://msdn.microsoft.com/en-us/library/windows/desktop/aa365247(v=vs.85).aspx#maxpath 574 func fixLongPath(path string) string { 575 // Do nothing (and don't allocate) if the path is "short". 576 // Empirically (at least on the Windows Server 2013 builder), 577 // the kernel is arbitrarily okay with < 248 bytes. That 578 // matches what the docs above say: 579 // "When using an API to create a directory, the specified 580 // path cannot be so long that you cannot append an 8.3 file 581 // name (that is, the directory name cannot exceed MAX_PATH 582 // minus 12)." Since MAX_PATH is 260, 260 - 12 = 248. 583 // 584 // The MSDN docs appear to say that a normal path that is 248 bytes long 585 // will work; empirically the path must be less then 248 bytes long. 586 if len(path) < 248 { 587 // Don't fix. (This is how Go 1.7 and earlier worked, 588 // not automatically generating the \\?\ form) 589 return path 590 } 591 592 // The extended form begins with \\?\, as in 593 // \\?\c:\windows\foo.txt or \\?\UNC\server\share\foo.txt. 594 // The extended form disables evaluation of . and .. path 595 // elements and disables the interpretation of / as equivalent 596 // to \. The conversion here rewrites / to \ and elides 597 // . elements as well as trailing or duplicate separators. For 598 // simplicity it avoids the conversion entirely for relative 599 // paths or paths containing .. elements. For now, 600 // \\server\share paths are not converted to 601 // \\?\UNC\server\share paths because the rules for doing so 602 // are less well-specified. 603 if len(path) >= 2 && path[:2] == `\\` { 604 // Don't canonicalize UNC paths. 605 return path 606 } 607 if !isAbs(path) { 608 // Relative path 609 return path 610 } 611 612 const prefix = `\\?` 613 614 pathbuf := make([]byte, len(prefix)+len(path)+len(`\`)) 615 copy(pathbuf, prefix) 616 n := len(path) 617 r, w := 0, len(prefix) 618 for r < n { 619 switch { 620 case os.IsPathSeparator(path[r]): 621 // empty block 622 r++ 623 case path[r] == '.' && (r+1 == n || os.IsPathSeparator(path[r+1])): 624 // /./ 625 r++ 626 case r+1 < n && path[r] == '.' && path[r+1] == '.' && (r+2 == n || os.IsPathSeparator(path[r+2])): 627 // /../ is currently unhandled 628 return path 629 default: 630 pathbuf[w] = '\\' 631 w++ 632 for ; r < n && !os.IsPathSeparator(path[r]); r++ { 633 pathbuf[w] = path[r] 634 w++ 635 } 636 } 637 } 638 // A drive's root directory needs a trailing \ 639 if w == len(`\\?\c:`) { 640 pathbuf[w] = '\\' 641 w++ 642 } 643 return string(pathbuf[:w]) 644 } 645 646 func isAbs(path string) (b bool) { 647 v := volumeName(path) 648 if v == "" { 649 return false 650 } 651 path = path[len(v):] 652 if path == "" { 653 return false 654 } 655 return os.IsPathSeparator(path[0]) 656 } 657 658 func volumeName(path string) (v string) { 659 if len(path) < 2 { 660 return "" 661 } 662 // with drive letter 663 c := path[0] 664 if path[1] == ':' && 665 ('0' <= c && c <= '9' || 'a' <= c && c <= 'z' || 666 'A' <= c && c <= 'Z') { 667 return path[:2] 668 } 669 // is it UNC 670 if l := len(path); l >= 5 && os.IsPathSeparator(path[0]) && os.IsPathSeparator(path[1]) && 671 !os.IsPathSeparator(path[2]) && path[2] != '.' { 672 // first, leading `\\` and next shouldn't be `\`. its server name. 673 for n := 3; n < l-1; n++ { 674 // second, next '\' shouldn't be repeated. 675 if os.IsPathSeparator(path[n]) { 676 n++ 677 // third, following something characters. its share name. 678 if !os.IsPathSeparator(path[n]) { 679 if path[n] == '.' { 680 break 681 } 682 for ; n < l; n++ { 683 if os.IsPathSeparator(path[n]) { 684 break 685 } 686 } 687 return path[:n] 688 } 689 break 690 } 691 } 692 } 693 return "" 694 }