|
9 | 9 | "io/ioutil"
|
10 | 10 | "os"
|
11 | 11 | "path/filepath"
|
| 12 | + "runtime" |
12 | 13 | "strings"
|
13 | 14 | "unicode"
|
14 | 15 |
|
@@ -306,6 +307,13 @@ func copyFile(src, dst string) (err error) {
|
306 | 307 | if err != nil {
|
307 | 308 | return
|
308 | 309 | }
|
| 310 | + |
| 311 | + // Temporary fix for Go < 1.9 |
| 312 | + // |
| 313 | + // See: https://github.com/golang/dep/issues/774 & https://github.com/golang/go/issues/20829 |
| 314 | + if runtime.GOOS == "windows" { |
| 315 | + dst = fixLongPath(dst) |
| 316 | + } |
309 | 317 | err = os.Chmod(dst, si.Mode())
|
310 | 318 | if err != nil {
|
311 | 319 | return
|
@@ -396,3 +404,133 @@ func IsSymlink(path string) (bool, error) {
|
396 | 404 |
|
397 | 405 | return l.Mode()&os.ModeSymlink == os.ModeSymlink, nil
|
398 | 406 | }
|
| 407 | + |
| 408 | +// fixLongPath returns the extended-length (\\?\-prefixed) form of |
| 409 | +// path when needed, in order to avoid the default 260 character file |
| 410 | +// path limit imposed by Windows. If path is not easily converted to |
| 411 | +// the extended-length form (for example, if path is a relative path |
| 412 | +// or contains .. elements), or is short enough, fixLongPath returns |
| 413 | +// path unmodified. |
| 414 | +// |
| 415 | +// See https://msdn.microsoft.com/en-us/library/windows/desktop/aa365247(v=vs.85).aspx#maxpath |
| 416 | +func fixLongPath(path string) string { |
| 417 | + // Do nothing (and don't allocate) if the path is "short". |
| 418 | + // Empirically (at least on the Windows Server 2013 builder), |
| 419 | + // the kernel is arbitrarily okay with < 248 bytes. That |
| 420 | + // matches what the docs above say: |
| 421 | + // "When using an API to create a directory, the specified |
| 422 | + // path cannot be so long that you cannot append an 8.3 file |
| 423 | + // name (that is, the directory name cannot exceed MAX_PATH |
| 424 | + // minus 12)." Since MAX_PATH is 260, 260 - 12 = 248. |
| 425 | + // |
| 426 | + // The MSDN docs appear to say that a normal path that is 248 bytes long |
| 427 | + // will work; empirically the path must be less then 248 bytes long. |
| 428 | + if len(path) < 248 { |
| 429 | + // Don't fix. (This is how Go 1.7 and earlier worked, |
| 430 | + // not automatically generating the \\?\ form) |
| 431 | + return path |
| 432 | + } |
| 433 | + |
| 434 | + // The extended form begins with \\?\, as in |
| 435 | + // \\?\c:\windows\foo.txt or \\?\UNC\server\share\foo.txt. |
| 436 | + // The extended form disables evaluation of . and .. path |
| 437 | + // elements and disables the interpretation of / as equivalent |
| 438 | + // to \. The conversion here rewrites / to \ and elides |
| 439 | + // . elements as well as trailing or duplicate separators. For |
| 440 | + // simplicity it avoids the conversion entirely for relative |
| 441 | + // paths or paths containing .. elements. For now, |
| 442 | + // \\server\share paths are not converted to |
| 443 | + // \\?\UNC\server\share paths because the rules for doing so |
| 444 | + // are less well-specified. |
| 445 | + if len(path) >= 2 && path[:2] == `\\` { |
| 446 | + // Don't canonicalize UNC paths. |
| 447 | + return path |
| 448 | + } |
| 449 | + if !isAbs(path) { |
| 450 | + // Relative path |
| 451 | + return path |
| 452 | + } |
| 453 | + |
| 454 | + const prefix = `\\?` |
| 455 | + |
| 456 | + pathbuf := make([]byte, len(prefix)+len(path)+len(`\`)) |
| 457 | + copy(pathbuf, prefix) |
| 458 | + n := len(path) |
| 459 | + r, w := 0, len(prefix) |
| 460 | + for r < n { |
| 461 | + switch { |
| 462 | + case os.IsPathSeparator(path[r]): |
| 463 | + // empty block |
| 464 | + r++ |
| 465 | + case path[r] == '.' && (r+1 == n || os.IsPathSeparator(path[r+1])): |
| 466 | + // /./ |
| 467 | + r++ |
| 468 | + case r+1 < n && path[r] == '.' && path[r+1] == '.' && (r+2 == n || os.IsPathSeparator(path[r+2])): |
| 469 | + // /../ is currently unhandled |
| 470 | + return path |
| 471 | + default: |
| 472 | + pathbuf[w] = '\\' |
| 473 | + w++ |
| 474 | + for ; r < n && !os.IsPathSeparator(path[r]); r++ { |
| 475 | + pathbuf[w] = path[r] |
| 476 | + w++ |
| 477 | + } |
| 478 | + } |
| 479 | + } |
| 480 | + // A drive's root directory needs a trailing \ |
| 481 | + if w == len(`\\?\c:`) { |
| 482 | + pathbuf[w] = '\\' |
| 483 | + w++ |
| 484 | + } |
| 485 | + return string(pathbuf[:w]) |
| 486 | +} |
| 487 | + |
| 488 | +func isAbs(path string) (b bool) { |
| 489 | + v := volumeName(path) |
| 490 | + if v == "" { |
| 491 | + return false |
| 492 | + } |
| 493 | + path = path[len(v):] |
| 494 | + if path == "" { |
| 495 | + return false |
| 496 | + } |
| 497 | + return os.IsPathSeparator(path[0]) |
| 498 | +} |
| 499 | + |
| 500 | +func volumeName(path string) (v string) { |
| 501 | + if len(path) < 2 { |
| 502 | + return "" |
| 503 | + } |
| 504 | + // with drive letter |
| 505 | + c := path[0] |
| 506 | + if path[1] == ':' && |
| 507 | + ('0' <= c && c <= '9' || 'a' <= c && c <= 'z' || |
| 508 | + 'A' <= c && c <= 'Z') { |
| 509 | + return path[:2] |
| 510 | + } |
| 511 | + // is it UNC |
| 512 | + if l := len(path); l >= 5 && os.IsPathSeparator(path[0]) && os.IsPathSeparator(path[1]) && |
| 513 | + !os.IsPathSeparator(path[2]) && path[2] != '.' { |
| 514 | + // first, leading `\\` and next shouldn't be `\`. its server name. |
| 515 | + for n := 3; n < l-1; n++ { |
| 516 | + // second, next '\' shouldn't be repeated. |
| 517 | + if os.IsPathSeparator(path[n]) { |
| 518 | + n++ |
| 519 | + // third, following something characters. its share name. |
| 520 | + if !os.IsPathSeparator(path[n]) { |
| 521 | + if path[n] == '.' { |
| 522 | + break |
| 523 | + } |
| 524 | + for ; n < l; n++ { |
| 525 | + if os.IsPathSeparator(path[n]) { |
| 526 | + break |
| 527 | + } |
| 528 | + } |
| 529 | + return path[:n] |
| 530 | + } |
| 531 | + break |
| 532 | + } |
| 533 | + } |
| 534 | + } |
| 535 | + return "" |
| 536 | +} |
0 commit comments