github.com/pkg/sftp@v1.13.6/client_integration_test.go (about) 1 package sftp 2 3 // sftp integration tests 4 // enable with -integration 5 6 import ( 7 "bytes" 8 "crypto/sha1" 9 "errors" 10 "fmt" 11 "io" 12 "io/ioutil" 13 "math/rand" 14 "net" 15 "os" 16 "os/exec" 17 "os/user" 18 "path" 19 "path/filepath" 20 "reflect" 21 "regexp" 22 "runtime" 23 "sort" 24 "strconv" 25 "sync" 26 "testing" 27 "testing/quick" 28 "time" 29 30 "github.com/kr/fs" 31 "github.com/stretchr/testify/assert" 32 "github.com/stretchr/testify/require" 33 ) 34 35 const ( 36 READONLY = true 37 READWRITE = false 38 NODELAY time.Duration = 0 39 40 debuglevel = "ERROR" // set to "DEBUG" for debugging 41 ) 42 43 type delayedWrite struct { 44 t time.Time 45 b []byte 46 } 47 48 // delayedWriter wraps a writer and artificially delays the write. This is 49 // meant to mimic connections with various latencies. Error's returned from the 50 // underlying writer will panic so this should only be used over reliable 51 // connections. 52 type delayedWriter struct { 53 closed chan struct{} 54 55 mu sync.Mutex 56 ch chan delayedWrite 57 closing chan struct{} 58 } 59 60 func newDelayedWriter(w io.WriteCloser, delay time.Duration) io.WriteCloser { 61 dw := &delayedWriter{ 62 ch: make(chan delayedWrite, 128), 63 closed: make(chan struct{}), 64 closing: make(chan struct{}), 65 } 66 67 go func() { 68 defer close(dw.closed) 69 defer w.Close() 70 71 for writeMsg := range dw.ch { 72 time.Sleep(time.Until(writeMsg.t.Add(delay))) 73 74 n, err := w.Write(writeMsg.b) 75 if err != nil { 76 panic("write error") 77 } 78 79 if n < len(writeMsg.b) { 80 panic("showrt write") 81 } 82 } 83 }() 84 85 return dw 86 } 87 88 func (dw *delayedWriter) Write(b []byte) (int, error) { 89 dw.mu.Lock() 90 defer dw.mu.Unlock() 91 92 write := delayedWrite{ 93 t: time.Now(), 94 b: append([]byte(nil), b...), 95 } 96 97 select { 98 case <-dw.closing: 99 return 0, errors.New("delayedWriter is closing") 100 case dw.ch <- write: 101 } 102 103 return len(b), nil 104 } 105 106 func (dw *delayedWriter) Close() error { 107 dw.mu.Lock() 108 defer dw.mu.Unlock() 109 110 select { 111 case <-dw.closing: 112 default: 113 close(dw.ch) 114 close(dw.closing) 115 } 116 117 <-dw.closed 118 return nil 119 } 120 121 // netPipe provides a pair of io.ReadWriteClosers connected to each other. 122 // The functions is identical to os.Pipe with the exception that netPipe 123 // provides the Read/Close guarantees that os.File derrived pipes do not. 124 func netPipe(t testing.TB) (io.ReadWriteCloser, io.ReadWriteCloser) { 125 type result struct { 126 net.Conn 127 error 128 } 129 130 l, err := net.Listen("tcp", "127.0.0.1:0") 131 if err != nil { 132 t.Fatal(err) 133 } 134 135 closeListener := make(chan struct{}, 1) 136 closeListener <- struct{}{} 137 138 ch := make(chan result, 1) 139 go func() { 140 conn, err := l.Accept() 141 ch <- result{conn, err} 142 143 if _, ok := <-closeListener; ok { 144 err = l.Close() 145 if err != nil { 146 t.Error(err) 147 } 148 close(closeListener) 149 } 150 }() 151 152 c1, err := net.Dial("tcp", l.Addr().String()) 153 if err != nil { 154 if _, ok := <-closeListener; ok { 155 l.Close() 156 close(closeListener) 157 } 158 t.Fatal(err) 159 } 160 161 r := <-ch 162 if r.error != nil { 163 t.Fatal(err) 164 } 165 166 return c1, r.Conn 167 } 168 169 func testClientGoSvr(t testing.TB, readonly bool, delay time.Duration, opts ...ClientOption) (*Client, *exec.Cmd) { 170 c1, c2 := netPipe(t) 171 172 options := []ServerOption{WithDebug(os.Stderr)} 173 if readonly { 174 options = append(options, ReadOnly()) 175 } 176 177 server, err := NewServer(c1, options...) 178 if err != nil { 179 t.Fatal(err) 180 } 181 go server.Serve() 182 183 var wr io.WriteCloser = c2 184 if delay > NODELAY { 185 wr = newDelayedWriter(wr, delay) 186 } 187 188 client, err := NewClientPipe(c2, wr, opts...) 189 if err != nil { 190 t.Fatal(err) 191 } 192 193 // dummy command... 194 return client, exec.Command("true") 195 } 196 197 // testClient returns a *Client connected to a locally running sftp-server 198 // the *exec.Cmd returned must be defer Wait'd. 199 func testClient(t testing.TB, readonly bool, delay time.Duration, opts ...ClientOption) (*Client, *exec.Cmd) { 200 if !*testIntegration { 201 t.Skip("skipping integration test") 202 } 203 204 if *testServerImpl { 205 return testClientGoSvr(t, readonly, delay, opts...) 206 } 207 208 cmd := exec.Command(*testSftp, "-e", "-R", "-l", debuglevel) // log to stderr, read only 209 if !readonly { 210 cmd = exec.Command(*testSftp, "-e", "-l", debuglevel) // log to stderr 211 } 212 213 cmd.Stderr = os.Stdout 214 215 pw, err := cmd.StdinPipe() 216 if err != nil { 217 t.Fatal(err) 218 } 219 220 if delay > NODELAY { 221 pw = newDelayedWriter(pw, delay) 222 } 223 224 pr, err := cmd.StdoutPipe() 225 if err != nil { 226 t.Fatal(err) 227 } 228 229 if err := cmd.Start(); err != nil { 230 t.Skipf("could not start sftp-server process: %v", err) 231 } 232 233 sftp, err := NewClientPipe(pr, pw, opts...) 234 if err != nil { 235 t.Fatal(err) 236 } 237 238 return sftp, cmd 239 } 240 241 func TestNewClient(t *testing.T) { 242 sftp, cmd := testClient(t, READONLY, NODELAY) 243 defer cmd.Wait() 244 245 if err := sftp.Close(); err != nil { 246 t.Fatal(err) 247 } 248 } 249 250 func TestClientLstat(t *testing.T) { 251 sftp, cmd := testClient(t, READONLY, NODELAY) 252 defer cmd.Wait() 253 defer sftp.Close() 254 255 f, err := ioutil.TempFile("", "sftptest-lstat") 256 if err != nil { 257 t.Fatal(err) 258 } 259 f.Close() 260 defer os.Remove(f.Name()) 261 262 want, err := os.Lstat(f.Name()) 263 if err != nil { 264 t.Fatal(err) 265 } 266 267 got, err := sftp.Lstat(f.Name()) 268 if err != nil { 269 t.Fatal(err) 270 } 271 272 if !sameFile(want, got) { 273 t.Fatalf("Lstat(%q): want %#v, got %#v", f.Name(), want, got) 274 } 275 } 276 277 func TestClientLstatIsNotExist(t *testing.T) { 278 sftp, cmd := testClient(t, READONLY, NODELAY) 279 defer cmd.Wait() 280 defer sftp.Close() 281 282 f, err := ioutil.TempFile("", "sftptest-lstatisnotexist") 283 if err != nil { 284 t.Fatal(err) 285 } 286 f.Close() 287 os.Remove(f.Name()) 288 289 if _, err := sftp.Lstat(f.Name()); !os.IsNotExist(err) { 290 t.Errorf("os.IsNotExist(%v) = false, want true", err) 291 } 292 } 293 294 func TestClientMkdir(t *testing.T) { 295 sftp, cmd := testClient(t, READWRITE, NODELAY) 296 defer cmd.Wait() 297 defer sftp.Close() 298 299 dir, err := ioutil.TempDir("", "sftptest-mkdir") 300 if err != nil { 301 t.Fatal(err) 302 } 303 defer os.RemoveAll(dir) 304 305 sub := path.Join(dir, "mkdir1") 306 if err := sftp.Mkdir(sub); err != nil { 307 t.Fatal(err) 308 } 309 if _, err := os.Lstat(sub); err != nil { 310 t.Fatal(err) 311 } 312 } 313 func TestClientMkdirAll(t *testing.T) { 314 sftp, cmd := testClient(t, READWRITE, NODELAY) 315 defer cmd.Wait() 316 defer sftp.Close() 317 318 dir, err := ioutil.TempDir("", "sftptest-mkdirall") 319 if err != nil { 320 t.Fatal(err) 321 } 322 defer os.RemoveAll(dir) 323 324 sub := path.Join(dir, "mkdir1", "mkdir2", "mkdir3") 325 if err := sftp.MkdirAll(sub); err != nil { 326 t.Fatal(err) 327 } 328 info, err := os.Lstat(sub) 329 if err != nil { 330 t.Fatal(err) 331 } 332 if !info.IsDir() { 333 t.Fatalf("Expected mkdirall to create dir at: %s", sub) 334 } 335 } 336 337 func TestClientOpen(t *testing.T) { 338 sftp, cmd := testClient(t, READONLY, NODELAY) 339 defer cmd.Wait() 340 defer sftp.Close() 341 342 f, err := ioutil.TempFile("", "sftptest-open") 343 if err != nil { 344 t.Fatal(err) 345 } 346 f.Close() 347 defer os.Remove(f.Name()) 348 349 got, err := sftp.Open(f.Name()) 350 if err != nil { 351 t.Fatal(err) 352 } 353 if err := got.Close(); err != nil { 354 t.Fatal(err) 355 } 356 } 357 358 func TestClientOpenIsNotExist(t *testing.T) { 359 sftp, cmd := testClient(t, READONLY, NODELAY) 360 defer cmd.Wait() 361 defer sftp.Close() 362 363 if _, err := sftp.Open("/doesnt/exist"); !os.IsNotExist(err) { 364 t.Errorf("os.IsNotExist(%v) = false, want true", err) 365 } 366 } 367 368 func TestClientStatIsNotExist(t *testing.T) { 369 sftp, cmd := testClient(t, READONLY, NODELAY) 370 defer cmd.Wait() 371 defer sftp.Close() 372 373 if _, err := sftp.Stat("/doesnt/exist"); !os.IsNotExist(err) { 374 t.Errorf("os.IsNotExist(%v) = false, want true", err) 375 } 376 } 377 378 const seekBytes = 128 * 1024 379 380 type seek struct { 381 offset int64 382 } 383 384 func (s seek) Generate(r *rand.Rand, _ int) reflect.Value { 385 s.offset = int64(r.Int31n(seekBytes)) 386 return reflect.ValueOf(s) 387 } 388 389 func (s seek) set(t *testing.T, r io.ReadSeeker) { 390 if _, err := r.Seek(s.offset, io.SeekStart); err != nil { 391 t.Fatalf("error while seeking with %+v: %v", s, err) 392 } 393 } 394 395 func (s seek) current(t *testing.T, r io.ReadSeeker) { 396 const mid = seekBytes / 2 397 398 skip := s.offset / 2 399 if s.offset > mid { 400 skip = -skip 401 } 402 403 if _, err := r.Seek(mid, io.SeekStart); err != nil { 404 t.Fatalf("error seeking to midpoint with %+v: %v", s, err) 405 } 406 if _, err := r.Seek(skip, io.SeekCurrent); err != nil { 407 t.Fatalf("error seeking from %d with %+v: %v", mid, s, err) 408 } 409 } 410 411 func (s seek) end(t *testing.T, r io.ReadSeeker) { 412 if _, err := r.Seek(-s.offset, io.SeekEnd); err != nil { 413 t.Fatalf("error seeking from end with %+v: %v", s, err) 414 } 415 } 416 417 func TestClientSeek(t *testing.T) { 418 sftp, cmd := testClient(t, READONLY, NODELAY) 419 defer cmd.Wait() 420 defer sftp.Close() 421 422 fOS, err := ioutil.TempFile("", "sftptest-seek") 423 if err != nil { 424 t.Fatal(err) 425 } 426 defer os.Remove(fOS.Name()) 427 defer fOS.Close() 428 429 fSFTP, err := sftp.Open(fOS.Name()) 430 if err != nil { 431 t.Fatal(err) 432 } 433 defer fSFTP.Close() 434 435 writeN(t, fOS, seekBytes) 436 437 if err := quick.CheckEqual( 438 func(s seek) (string, int64) { s.set(t, fOS); return readHash(t, fOS) }, 439 func(s seek) (string, int64) { s.set(t, fSFTP); return readHash(t, fSFTP) }, 440 nil, 441 ); err != nil { 442 t.Errorf("Seek: expected equal absolute seeks: %v", err) 443 } 444 445 if err := quick.CheckEqual( 446 func(s seek) (string, int64) { s.current(t, fOS); return readHash(t, fOS) }, 447 func(s seek) (string, int64) { s.current(t, fSFTP); return readHash(t, fSFTP) }, 448 nil, 449 ); err != nil { 450 t.Errorf("Seek: expected equal seeks from middle: %v", err) 451 } 452 453 if err := quick.CheckEqual( 454 func(s seek) (string, int64) { s.end(t, fOS); return readHash(t, fOS) }, 455 func(s seek) (string, int64) { s.end(t, fSFTP); return readHash(t, fSFTP) }, 456 nil, 457 ); err != nil { 458 t.Errorf("Seek: expected equal seeks from end: %v", err) 459 } 460 } 461 462 func TestClientCreate(t *testing.T) { 463 sftp, cmd := testClient(t, READWRITE, NODELAY) 464 defer cmd.Wait() 465 defer sftp.Close() 466 467 f, err := ioutil.TempFile("", "sftptest-create") 468 if err != nil { 469 t.Fatal(err) 470 } 471 defer f.Close() 472 defer os.Remove(f.Name()) 473 474 f2, err := sftp.Create(f.Name()) 475 if err != nil { 476 t.Fatal(err) 477 } 478 defer f2.Close() 479 } 480 481 func TestClientAppend(t *testing.T) { 482 sftp, cmd := testClient(t, READWRITE, NODELAY) 483 defer cmd.Wait() 484 defer sftp.Close() 485 486 f, err := ioutil.TempFile("", "sftptest-append") 487 if err != nil { 488 t.Fatal(err) 489 } 490 defer f.Close() 491 defer os.Remove(f.Name()) 492 493 f2, err := sftp.OpenFile(f.Name(), os.O_RDWR|os.O_APPEND) 494 if err != nil { 495 t.Fatal(err) 496 } 497 defer f2.Close() 498 } 499 500 func TestClientCreateFailed(t *testing.T) { 501 sftp, cmd := testClient(t, READONLY, NODELAY) 502 defer cmd.Wait() 503 defer sftp.Close() 504 505 f, err := ioutil.TempFile("", "sftptest-createfailed") 506 require.NoError(t, err) 507 508 defer f.Close() 509 defer os.Remove(f.Name()) 510 511 f2, err := sftp.Create(f.Name()) 512 require.True(t, os.IsPermission(err)) 513 if err == nil { 514 f2.Close() 515 } 516 } 517 518 func TestClientFileName(t *testing.T) { 519 sftp, cmd := testClient(t, READONLY, NODELAY) 520 defer cmd.Wait() 521 defer sftp.Close() 522 523 f, err := ioutil.TempFile("", "sftptest-filename") 524 if err != nil { 525 t.Fatal(err) 526 } 527 defer os.Remove(f.Name()) 528 529 f2, err := sftp.Open(f.Name()) 530 if err != nil { 531 t.Fatal(err) 532 } 533 defer f2.Close() 534 535 if got, want := f2.Name(), f.Name(); got != want { 536 t.Fatalf("Name: got %q want %q", want, got) 537 } 538 } 539 540 func TestClientFileStat(t *testing.T) { 541 sftp, cmd := testClient(t, READONLY, NODELAY) 542 defer cmd.Wait() 543 defer sftp.Close() 544 545 f, err := ioutil.TempFile("", "sftptest-filestat") 546 if err != nil { 547 t.Fatal(err) 548 } 549 defer os.Remove(f.Name()) 550 551 want, err := os.Lstat(f.Name()) 552 if err != nil { 553 t.Fatal(err) 554 } 555 556 f2, err := sftp.Open(f.Name()) 557 if err != nil { 558 t.Fatal(err) 559 } 560 defer f2.Close() 561 562 got, err := f2.Stat() 563 if err != nil { 564 t.Fatal(err) 565 } 566 567 if !sameFile(want, got) { 568 t.Fatalf("Lstat(%q): want %#v, got %#v", f.Name(), want, got) 569 } 570 } 571 572 func TestClientStatLink(t *testing.T) { 573 skipIfWindows(t) // Windows does not support links. 574 575 sftp, cmd := testClient(t, READONLY, NODELAY) 576 defer cmd.Wait() 577 defer sftp.Close() 578 579 f, err := ioutil.TempFile("", "sftptest-statlink") 580 if err != nil { 581 t.Fatal(err) 582 } 583 defer os.Remove(f.Name()) 584 585 realName := f.Name() 586 linkName := f.Name() + ".softlink" 587 588 // create a symlink that points at sftptest 589 if err := os.Symlink(realName, linkName); err != nil { 590 t.Fatal(err) 591 } 592 defer os.Remove(linkName) 593 594 // compare Lstat of links 595 wantLstat, err := os.Lstat(linkName) 596 if err != nil { 597 t.Fatal(err) 598 } 599 wantStat, err := os.Stat(linkName) 600 if err != nil { 601 t.Fatal(err) 602 } 603 604 gotLstat, err := sftp.Lstat(linkName) 605 if err != nil { 606 t.Fatal(err) 607 } 608 gotStat, err := sftp.Stat(linkName) 609 if err != nil { 610 t.Fatal(err) 611 } 612 613 // check that stat is not lstat from os package 614 if sameFile(wantLstat, wantStat) { 615 t.Fatalf("Lstat / Stat(%q): both %#v %#v", f.Name(), wantLstat, wantStat) 616 } 617 618 // compare Lstat of links 619 if !sameFile(wantLstat, gotLstat) { 620 t.Fatalf("Lstat(%q): want %#v, got %#v", f.Name(), wantLstat, gotLstat) 621 } 622 623 // compare Stat of links 624 if !sameFile(wantStat, gotStat) { 625 t.Fatalf("Stat(%q): want %#v, got %#v", f.Name(), wantStat, gotStat) 626 } 627 628 // check that stat is not lstat 629 if sameFile(gotLstat, gotStat) { 630 t.Fatalf("Lstat / Stat(%q): both %#v %#v", f.Name(), gotLstat, gotStat) 631 } 632 } 633 634 func TestClientRemove(t *testing.T) { 635 sftp, cmd := testClient(t, READWRITE, NODELAY) 636 defer cmd.Wait() 637 defer sftp.Close() 638 639 f, err := ioutil.TempFile("", "sftptest-remove") 640 if err != nil { 641 t.Fatal(err) 642 } 643 defer os.Remove(f.Name()) 644 f.Close() 645 646 if err := sftp.Remove(f.Name()); err != nil { 647 t.Fatal(err) 648 } 649 if _, err := os.Lstat(f.Name()); !os.IsNotExist(err) { 650 t.Fatal(err) 651 } 652 } 653 654 func TestClientRemoveAll(t *testing.T) { 655 sftp, cmd := testClient(t, READWRITE, NODELAY) 656 defer cmd.Wait() 657 defer sftp.Close() 658 659 // Create a temporary directory for testing 660 tempDir, err := ioutil.TempDir("", "sftptest-removeAll") 661 if err != nil { 662 t.Fatal(err) 663 } 664 defer os.RemoveAll(tempDir) 665 666 // Create a directory tree 667 dir1, err := ioutil.TempDir(tempDir, "foo") 668 if err != nil { 669 t.Fatal(err) 670 } 671 dir2, err := ioutil.TempDir(dir1, "bar") 672 if err != nil { 673 t.Fatal(err) 674 } 675 676 // Create some files within the directory tree 677 file1 := tempDir + "/file1.txt" 678 file2 := dir1 + "/file2.txt" 679 file3 := dir2 + "/file3.txt" 680 err = ioutil.WriteFile(file1, []byte("File 1"), 0644) 681 if err != nil { 682 t.Fatalf("Failed to create file: %v", err) 683 } 684 err = ioutil.WriteFile(file2, []byte("File 2"), 0644) 685 if err != nil { 686 t.Fatalf("Failed to create file: %v", err) 687 } 688 err = ioutil.WriteFile(file3, []byte("File 3"), 0644) 689 if err != nil { 690 t.Fatalf("Failed to create file: %v", err) 691 } 692 693 // Call the function to delete the files recursively 694 err = sftp.RemoveAll(tempDir) 695 if err != nil { 696 t.Fatalf("Failed to delete files recursively: %v", err) 697 } 698 699 // Check if the directories and files have been deleted 700 if _, err := os.Stat(dir1); !os.IsNotExist(err) { 701 t.Errorf("Directory %s still exists", dir1) 702 } 703 if _, err := os.Stat(dir2); !os.IsNotExist(err) { 704 t.Errorf("Directory %s still exists", dir2) 705 } 706 if _, err := os.Stat(file1); !os.IsNotExist(err) { 707 t.Errorf("File %s still exists", file1) 708 } 709 if _, err := os.Stat(file2); !os.IsNotExist(err) { 710 t.Errorf("File %s still exists", file2) 711 } 712 if _, err := os.Stat(file3); !os.IsNotExist(err) { 713 t.Errorf("File %s still exists", file3) 714 } 715 } 716 717 func TestClientRemoveDir(t *testing.T) { 718 sftp, cmd := testClient(t, READWRITE, NODELAY) 719 defer cmd.Wait() 720 defer sftp.Close() 721 722 dir, err := ioutil.TempDir("", "sftptest-removedir") 723 if err != nil { 724 t.Fatal(err) 725 } 726 defer os.RemoveAll(dir) 727 728 if err := sftp.Remove(dir); err != nil { 729 t.Fatal(err) 730 } 731 if _, err := os.Lstat(dir); !os.IsNotExist(err) { 732 t.Fatal(err) 733 } 734 } 735 736 func TestClientRemoveFailed(t *testing.T) { 737 sftp, cmd := testClient(t, READONLY, NODELAY) 738 defer cmd.Wait() 739 defer sftp.Close() 740 741 f, err := ioutil.TempFile("", "sftptest-removefailed") 742 if err != nil { 743 t.Fatal(err) 744 } 745 defer os.Remove(f.Name()) 746 747 if err := sftp.Remove(f.Name()); err == nil { 748 t.Fatalf("Remove(%v): want: permission denied, got %v", f.Name(), err) 749 } 750 if _, err := os.Lstat(f.Name()); err != nil { 751 t.Fatal(err) 752 } 753 } 754 755 func TestClientRename(t *testing.T) { 756 sftp, cmd := testClient(t, READWRITE, NODELAY) 757 defer cmd.Wait() 758 defer sftp.Close() 759 760 dir, err := ioutil.TempDir("", "sftptest-rename") 761 require.NoError(t, err) 762 defer os.RemoveAll(dir) 763 f, err := os.Create(filepath.Join(dir, "old")) 764 require.NoError(t, err) 765 f.Close() 766 767 f2 := filepath.Join(dir, "new") 768 if err := sftp.Rename(f.Name(), f2); err != nil { 769 t.Fatal(err) 770 } 771 if _, err := os.Lstat(f.Name()); !os.IsNotExist(err) { 772 t.Fatal(err) 773 } 774 if _, err := os.Lstat(f2); err != nil { 775 t.Fatal(err) 776 } 777 } 778 779 func TestClientPosixRename(t *testing.T) { 780 sftp, cmd := testClient(t, READWRITE, NODELAY) 781 defer cmd.Wait() 782 defer sftp.Close() 783 784 dir, err := ioutil.TempDir("", "sftptest-posixrename") 785 require.NoError(t, err) 786 defer os.RemoveAll(dir) 787 f, err := os.Create(filepath.Join(dir, "old")) 788 require.NoError(t, err) 789 f.Close() 790 791 f2 := filepath.Join(dir, "new") 792 if err := sftp.PosixRename(f.Name(), f2); err != nil { 793 t.Fatal(err) 794 } 795 if _, err := os.Lstat(f.Name()); !os.IsNotExist(err) { 796 t.Fatal(err) 797 } 798 if _, err := os.Lstat(f2); err != nil { 799 t.Fatal(err) 800 } 801 } 802 803 func TestClientGetwd(t *testing.T) { 804 sftp, cmd := testClient(t, READONLY, NODELAY) 805 defer cmd.Wait() 806 defer sftp.Close() 807 808 lwd, err := os.Getwd() 809 if err != nil { 810 t.Fatal(err) 811 } 812 rwd, err := sftp.Getwd() 813 if err != nil { 814 t.Fatal(err) 815 } 816 if !filepath.IsAbs(rwd) { 817 t.Fatalf("Getwd: wanted absolute path, got %q", rwd) 818 } 819 if filepath.ToSlash(lwd) != filepath.ToSlash(rwd) { 820 t.Fatalf("Getwd: want %q, got %q", lwd, rwd) 821 } 822 } 823 824 func TestClientReadLink(t *testing.T) { 825 if runtime.GOOS == "windows" && *testServerImpl { 826 // os.Symlink requires privilege escalation. 827 t.Skip() 828 } 829 830 sftp, cmd := testClient(t, READWRITE, NODELAY) 831 defer cmd.Wait() 832 defer sftp.Close() 833 834 dir, err := ioutil.TempDir("", "sftptest-readlink") 835 require.NoError(t, err) 836 defer os.RemoveAll(dir) 837 f, err := os.Create(filepath.Join(dir, "file")) 838 require.NoError(t, err) 839 f.Close() 840 841 f2 := filepath.Join(dir, "symlink") 842 if err := os.Symlink(f.Name(), f2); err != nil { 843 t.Fatal(err) 844 } 845 if rl, err := sftp.ReadLink(f2); err != nil { 846 t.Fatal(err) 847 } else if rl != f.Name() { 848 t.Fatalf("unexpected link target: %v, not %v", rl, f.Name()) 849 } 850 } 851 852 func TestClientLink(t *testing.T) { 853 sftp, cmd := testClient(t, READWRITE, NODELAY) 854 defer cmd.Wait() 855 defer sftp.Close() 856 857 dir, err := ioutil.TempDir("", "sftptest-link") 858 require.NoError(t, err) 859 defer os.RemoveAll(dir) 860 861 f, err := os.Create(filepath.Join(dir, "file")) 862 require.NoError(t, err) 863 data := []byte("linktest") 864 _, err = f.Write(data) 865 f.Close() 866 if err != nil { 867 t.Fatal(err) 868 } 869 870 f2 := filepath.Join(dir, "link") 871 if err := sftp.Link(f.Name(), f2); err != nil { 872 t.Fatal(err) 873 } 874 if st2, err := sftp.Stat(f2); err != nil { 875 t.Fatal(err) 876 } else if int(st2.Size()) != len(data) { 877 t.Fatalf("unexpected link size: %v, not %v", st2.Size(), len(data)) 878 } 879 } 880 881 func TestClientSymlink(t *testing.T) { 882 if runtime.GOOS == "windows" && *testServerImpl { 883 // os.Symlink requires privilege escalation. 884 t.Skip() 885 } 886 887 sftp, cmd := testClient(t, READWRITE, NODELAY) 888 defer cmd.Wait() 889 defer sftp.Close() 890 891 dir, err := ioutil.TempDir("", "sftptest-symlink") 892 require.NoError(t, err) 893 defer os.RemoveAll(dir) 894 f, err := os.Create(filepath.Join(dir, "file")) 895 require.NoError(t, err) 896 f.Close() 897 898 f2 := filepath.Join(dir, "symlink") 899 if err := sftp.Symlink(f.Name(), f2); err != nil { 900 t.Fatal(err) 901 } 902 if rl, err := sftp.ReadLink(f2); err != nil { 903 t.Fatal(err) 904 } else if rl != f.Name() { 905 t.Fatalf("unexpected link target: %v, not %v", rl, f.Name()) 906 } 907 } 908 909 func TestClientChmod(t *testing.T) { 910 skipIfWindows(t) // No UNIX permissions. 911 sftp, cmd := testClient(t, READWRITE, NODELAY) 912 defer cmd.Wait() 913 defer sftp.Close() 914 915 f, err := ioutil.TempFile("", "sftptest-chmod") 916 if err != nil { 917 t.Fatal(err) 918 } 919 defer os.Remove(f.Name()) 920 f.Close() 921 922 if err := sftp.Chmod(f.Name(), 0531); err != nil { 923 t.Fatal(err) 924 } 925 if stat, err := os.Stat(f.Name()); err != nil { 926 t.Fatal(err) 927 } else if stat.Mode()&os.ModePerm != 0531 { 928 t.Fatalf("invalid perm %o\n", stat.Mode()) 929 } 930 931 sf, err := sftp.Open(f.Name()) 932 require.NoError(t, err) 933 require.NoError(t, sf.Chmod(0500)) 934 sf.Close() 935 936 stat, err := os.Stat(f.Name()) 937 require.NoError(t, err) 938 require.EqualValues(t, 0500, stat.Mode()) 939 } 940 941 func TestClientChmodReadonly(t *testing.T) { 942 skipIfWindows(t) // No UNIX permissions. 943 sftp, cmd := testClient(t, READONLY, NODELAY) 944 defer cmd.Wait() 945 defer sftp.Close() 946 947 f, err := ioutil.TempFile("", "sftptest-chmodreadonly") 948 if err != nil { 949 t.Fatal(err) 950 } 951 defer os.Remove(f.Name()) 952 f.Close() 953 954 if err := sftp.Chmod(f.Name(), 0531); err == nil { 955 t.Fatal("expected error") 956 } 957 } 958 959 func TestClientSetuid(t *testing.T) { 960 skipIfWindows(t) // No UNIX permissions. 961 if *testServerImpl { 962 t.Skipf("skipping with -testserver") 963 } 964 965 sftp, cmd := testClient(t, READWRITE, NODELAY) 966 defer cmd.Wait() 967 defer sftp.Close() 968 969 f, err := ioutil.TempFile("", "sftptest-setuid") 970 if err != nil { 971 t.Fatal(err) 972 } 973 defer os.Remove(f.Name()) 974 f.Close() 975 976 const allPerm = os.ModePerm | os.ModeSetuid | os.ModeSetgid | os.ModeSticky | 977 s_ISUID | s_ISGID | s_ISVTX 978 979 for _, c := range []struct { 980 goPerm os.FileMode 981 posixPerm uint32 982 }{ 983 {os.ModeSetuid, s_ISUID}, 984 {os.ModeSetgid, s_ISGID}, 985 {os.ModeSticky, s_ISVTX}, 986 {os.ModeSetuid | os.ModeSticky, s_ISUID | s_ISVTX}, 987 } { 988 goPerm := 0700 | c.goPerm 989 posixPerm := 0700 | c.posixPerm 990 991 err = sftp.Chmod(f.Name(), goPerm) 992 require.NoError(t, err) 993 994 info, err := sftp.Stat(f.Name()) 995 require.NoError(t, err) 996 require.Equal(t, goPerm, info.Mode()&allPerm) 997 998 err = sftp.Chmod(f.Name(), 0700) // Reset funny bits. 999 require.NoError(t, err) 1000 1001 // For historical reasons, we also support literal POSIX mode bits in 1002 // Chmod. Stat should still translate these to Go os.FileMode bits. 1003 err = sftp.Chmod(f.Name(), os.FileMode(posixPerm)) 1004 require.NoError(t, err) 1005 1006 info, err = sftp.Stat(f.Name()) 1007 require.NoError(t, err) 1008 require.Equal(t, goPerm, info.Mode()&allPerm) 1009 } 1010 } 1011 1012 func TestClientChown(t *testing.T) { 1013 skipIfWindows(t) // No UNIX permissions. 1014 sftp, cmd := testClient(t, READWRITE, NODELAY) 1015 defer cmd.Wait() 1016 defer sftp.Close() 1017 1018 usr, err := user.Current() 1019 if err != nil { 1020 t.Fatal(err) 1021 } 1022 if usr.Uid != "0" { 1023 t.Log("must be root to run chown tests") 1024 t.Skip() 1025 } 1026 1027 chownto, err := user.Lookup("daemon") // seems common-ish... 1028 if err != nil { 1029 t.Fatal(err) 1030 } 1031 toUID, err := strconv.Atoi(chownto.Uid) 1032 if err != nil { 1033 t.Fatal(err) 1034 } 1035 toGID, err := strconv.Atoi(chownto.Gid) 1036 if err != nil { 1037 t.Fatal(err) 1038 } 1039 1040 f, err := ioutil.TempFile("", "sftptest-chown") 1041 if err != nil { 1042 t.Fatal(err) 1043 } 1044 defer os.Remove(f.Name()) 1045 f.Close() 1046 1047 before, err := exec.Command("ls", "-nl", f.Name()).Output() 1048 if err != nil { 1049 t.Fatal(err) 1050 } 1051 if err := sftp.Chown(f.Name(), toUID, toGID); err != nil { 1052 t.Fatal(err) 1053 } 1054 after, err := exec.Command("ls", "-nl", f.Name()).Output() 1055 if err != nil { 1056 t.Fatal(err) 1057 } 1058 1059 spaceRegex := regexp.MustCompile(`\s+`) 1060 1061 beforeWords := spaceRegex.Split(string(before), -1) 1062 if beforeWords[2] != "0" { 1063 t.Fatalf("bad previous user? should be root") 1064 } 1065 afterWords := spaceRegex.Split(string(after), -1) 1066 if afterWords[2] != chownto.Uid || afterWords[3] != chownto.Gid { 1067 t.Fatalf("bad chown: %#v", afterWords) 1068 } 1069 t.Logf("before: %v", string(before)) 1070 t.Logf(" after: %v", string(after)) 1071 } 1072 1073 func TestClientChownReadonly(t *testing.T) { 1074 skipIfWindows(t) // No UNIX permissions. 1075 sftp, cmd := testClient(t, READONLY, NODELAY) 1076 defer cmd.Wait() 1077 defer sftp.Close() 1078 1079 usr, err := user.Current() 1080 if err != nil { 1081 t.Fatal(err) 1082 } 1083 if usr.Uid != "0" { 1084 t.Log("must be root to run chown tests") 1085 t.Skip() 1086 } 1087 1088 chownto, err := user.Lookup("daemon") // seems common-ish... 1089 if err != nil { 1090 t.Fatal(err) 1091 } 1092 toUID, err := strconv.Atoi(chownto.Uid) 1093 if err != nil { 1094 t.Fatal(err) 1095 } 1096 toGID, err := strconv.Atoi(chownto.Gid) 1097 if err != nil { 1098 t.Fatal(err) 1099 } 1100 1101 f, err := ioutil.TempFile("", "sftptest-chownreadonly") 1102 if err != nil { 1103 t.Fatal(err) 1104 } 1105 defer os.Remove(f.Name()) 1106 f.Close() 1107 1108 if err := sftp.Chown(f.Name(), toUID, toGID); err == nil { 1109 t.Fatal("expected error") 1110 } 1111 } 1112 1113 func TestClientChtimes(t *testing.T) { 1114 sftp, cmd := testClient(t, READWRITE, NODELAY) 1115 defer cmd.Wait() 1116 defer sftp.Close() 1117 1118 f, err := ioutil.TempFile("", "sftptest-chtimes") 1119 if err != nil { 1120 t.Fatal(err) 1121 } 1122 defer os.Remove(f.Name()) 1123 f.Close() 1124 1125 atime := time.Date(2013, 2, 23, 13, 24, 35, 0, time.UTC) 1126 mtime := time.Date(1985, 6, 12, 6, 6, 6, 0, time.UTC) 1127 if err := sftp.Chtimes(f.Name(), atime, mtime); err != nil { 1128 t.Fatal(err) 1129 } 1130 if stat, err := os.Stat(f.Name()); err != nil { 1131 t.Fatal(err) 1132 } else if stat.ModTime().Sub(mtime) != 0 { 1133 t.Fatalf("incorrect mtime: %v vs %v", stat.ModTime(), mtime) 1134 } 1135 } 1136 1137 func TestClientChtimesReadonly(t *testing.T) { 1138 sftp, cmd := testClient(t, READONLY, NODELAY) 1139 defer cmd.Wait() 1140 defer sftp.Close() 1141 1142 f, err := ioutil.TempFile("", "sftptest-chtimesreadonly") 1143 if err != nil { 1144 t.Fatal(err) 1145 } 1146 defer os.Remove(f.Name()) 1147 f.Close() 1148 1149 atime := time.Date(2013, 2, 23, 13, 24, 35, 0, time.UTC) 1150 mtime := time.Date(1985, 6, 12, 6, 6, 6, 0, time.UTC) 1151 if err := sftp.Chtimes(f.Name(), atime, mtime); err == nil { 1152 t.Fatal("expected error") 1153 } 1154 } 1155 1156 func TestClientTruncate(t *testing.T) { 1157 sftp, cmd := testClient(t, READWRITE, NODELAY) 1158 defer cmd.Wait() 1159 defer sftp.Close() 1160 1161 f, err := ioutil.TempFile("", "sftptest-truncate") 1162 if err != nil { 1163 t.Fatal(err) 1164 } 1165 defer os.Remove(f.Name()) 1166 fname := f.Name() 1167 1168 if n, err := f.Write([]byte("hello world")); n != 11 || err != nil { 1169 t.Fatal(err) 1170 } 1171 f.Close() 1172 1173 if err := sftp.Truncate(fname, 5); err != nil { 1174 t.Fatal(err) 1175 } 1176 if stat, err := os.Stat(fname); err != nil { 1177 t.Fatal(err) 1178 } else if stat.Size() != 5 { 1179 t.Fatalf("unexpected size: %d", stat.Size()) 1180 } 1181 } 1182 1183 func TestClientTruncateReadonly(t *testing.T) { 1184 sftp, cmd := testClient(t, READONLY, NODELAY) 1185 defer cmd.Wait() 1186 defer sftp.Close() 1187 1188 f, err := ioutil.TempFile("", "sftptest-truncreadonly") 1189 if err != nil { 1190 t.Fatal(err) 1191 } 1192 defer os.Remove(f.Name()) 1193 fname := f.Name() 1194 1195 if n, err := f.Write([]byte("hello world")); n != 11 || err != nil { 1196 t.Fatal(err) 1197 } 1198 f.Close() 1199 1200 if err := sftp.Truncate(fname, 5); err == nil { 1201 t.Fatal("expected error") 1202 } 1203 if stat, err := os.Stat(fname); err != nil { 1204 t.Fatal(err) 1205 } else if stat.Size() != 11 { 1206 t.Fatalf("unexpected size: %d", stat.Size()) 1207 } 1208 } 1209 1210 func sameFile(want, got os.FileInfo) bool { 1211 _, wantName := filepath.Split(want.Name()) 1212 _, gotName := filepath.Split(got.Name()) 1213 return wantName == gotName && 1214 want.Size() == got.Size() 1215 } 1216 1217 func TestClientReadSimple(t *testing.T) { 1218 sftp, cmd := testClient(t, READONLY, NODELAY) 1219 defer cmd.Wait() 1220 defer sftp.Close() 1221 1222 d, err := ioutil.TempDir("", "sftptest-readsimple") 1223 if err != nil { 1224 t.Fatal(err) 1225 } 1226 defer os.RemoveAll(d) 1227 1228 f, err := ioutil.TempFile(d, "read-test") 1229 if err != nil { 1230 t.Fatal(err) 1231 } 1232 fname := f.Name() 1233 f.Write([]byte("hello")) 1234 f.Close() 1235 1236 f2, err := sftp.Open(fname) 1237 if err != nil { 1238 t.Fatal(err) 1239 } 1240 defer f2.Close() 1241 stuff := make([]byte, 32) 1242 n, err := f2.Read(stuff) 1243 if err != nil && err != io.EOF { 1244 t.Fatalf("err: %v", err) 1245 } 1246 if n != 5 { 1247 t.Fatalf("n should be 5, is %v", n) 1248 } 1249 if string(stuff[0:5]) != "hello" { 1250 t.Fatalf("invalid contents") 1251 } 1252 } 1253 1254 func TestClientReadSequential(t *testing.T) { 1255 sftp, cmd := testClient(t, READONLY, NODELAY) 1256 defer cmd.Wait() 1257 defer sftp.Close() 1258 1259 sftp.disableConcurrentReads = true 1260 d, err := ioutil.TempDir("", "sftptest-readsequential") 1261 require.NoError(t, err) 1262 1263 defer os.RemoveAll(d) 1264 1265 f, err := ioutil.TempFile(d, "read-sequential-test") 1266 require.NoError(t, err) 1267 fname := f.Name() 1268 content := []byte("hello world") 1269 f.Write(content) 1270 f.Close() 1271 1272 for _, maxPktSize := range []int{1, 2, 3, 4} { 1273 sftp.maxPacket = maxPktSize 1274 1275 sftpFile, err := sftp.Open(fname) 1276 require.NoError(t, err) 1277 1278 stuff := make([]byte, 32) 1279 n, err := sftpFile.Read(stuff) 1280 require.ErrorIs(t, err, io.EOF) 1281 require.Equal(t, len(content), n) 1282 require.Equal(t, content, stuff[0:len(content)]) 1283 1284 err = sftpFile.Close() 1285 require.NoError(t, err) 1286 1287 sftpFile, err = sftp.Open(fname) 1288 require.NoError(t, err) 1289 1290 stuff = make([]byte, 5) 1291 n, err = sftpFile.Read(stuff) 1292 require.NoError(t, err) 1293 require.Equal(t, len(stuff), n) 1294 require.Equal(t, content[:len(stuff)], stuff) 1295 1296 err = sftpFile.Close() 1297 require.NoError(t, err) 1298 1299 // now read from a offset 1300 off := int64(3) 1301 sftpFile, err = sftp.Open(fname) 1302 require.NoError(t, err) 1303 1304 stuff = make([]byte, 5) 1305 n, err = sftpFile.ReadAt(stuff, off) 1306 require.NoError(t, err) 1307 require.Equal(t, len(stuff), n) 1308 require.Equal(t, content[off:off+int64(len(stuff))], stuff) 1309 1310 err = sftpFile.Close() 1311 require.NoError(t, err) 1312 } 1313 } 1314 1315 // this writer requires maxPacket = 3 and always returns an error for the second write call 1316 type lastChunkErrSequentialWriter struct { 1317 counter int 1318 } 1319 1320 func (w *lastChunkErrSequentialWriter) Write(b []byte) (int, error) { 1321 w.counter++ 1322 if w.counter == 1 { 1323 if len(b) != 3 { 1324 return 0, errors.New("this writer requires maxPacket = 3, please set MaxPacketChecked(3)") 1325 } 1326 return len(b), nil 1327 } 1328 return 1, errors.New("this writer fails after the first write") 1329 } 1330 1331 func TestClientWriteSequentialWriterErr(t *testing.T) { 1332 client, cmd := testClient(t, READONLY, NODELAY, MaxPacketChecked(3)) 1333 defer cmd.Wait() 1334 defer client.Close() 1335 1336 d, err := ioutil.TempDir("", "sftptest-writesequential-writeerr") 1337 require.NoError(t, err) 1338 1339 defer os.RemoveAll(d) 1340 1341 f, err := ioutil.TempFile(d, "write-sequential-writeerr-test") 1342 require.NoError(t, err) 1343 fname := f.Name() 1344 _, err = f.Write([]byte("12345")) 1345 require.NoError(t, err) 1346 require.NoError(t, f.Close()) 1347 1348 sftpFile, err := client.Open(fname) 1349 require.NoError(t, err) 1350 defer sftpFile.Close() 1351 1352 w := &lastChunkErrSequentialWriter{} 1353 written, err := sftpFile.writeToSequential(w) 1354 assert.Error(t, err) 1355 expected := int64(4) 1356 if written != expected { 1357 t.Errorf("sftpFile.Write() = %d, but expected %d", written, expected) 1358 } 1359 assert.Equal(t, 2, w.counter) 1360 } 1361 1362 func TestClientReadDir(t *testing.T) { 1363 sftp1, cmd1 := testClient(t, READONLY, NODELAY) 1364 sftp2, cmd2 := testClientGoSvr(t, READONLY, NODELAY) 1365 defer cmd1.Wait() 1366 defer cmd2.Wait() 1367 defer sftp1.Close() 1368 defer sftp2.Close() 1369 1370 dir := os.TempDir() 1371 1372 d, err := os.Open(dir) 1373 if err != nil { 1374 t.Fatal(err) 1375 } 1376 defer d.Close() 1377 osfiles, err := d.Readdir(4096) 1378 if err != nil { 1379 t.Fatal(err) 1380 } 1381 1382 sftp1Files, err := sftp1.ReadDir(dir) 1383 if err != nil { 1384 t.Fatal(err) 1385 } 1386 sftp2Files, err := sftp2.ReadDir(dir) 1387 if err != nil { 1388 t.Fatal(err) 1389 } 1390 1391 osFilesByName := map[string]os.FileInfo{} 1392 for _, f := range osfiles { 1393 osFilesByName[f.Name()] = f 1394 } 1395 sftp1FilesByName := map[string]os.FileInfo{} 1396 for _, f := range sftp1Files { 1397 sftp1FilesByName[f.Name()] = f 1398 } 1399 sftp2FilesByName := map[string]os.FileInfo{} 1400 for _, f := range sftp2Files { 1401 sftp2FilesByName[f.Name()] = f 1402 } 1403 1404 if len(osFilesByName) != len(sftp1FilesByName) || len(sftp1FilesByName) != len(sftp2FilesByName) { 1405 t.Fatalf("os gives %v, sftp1 gives %v, sftp2 gives %v", len(osFilesByName), len(sftp1FilesByName), len(sftp2FilesByName)) 1406 } 1407 1408 for name, osF := range osFilesByName { 1409 sftp1F, ok := sftp1FilesByName[name] 1410 if !ok { 1411 t.Fatalf("%v present in os but not sftp1", name) 1412 } 1413 sftp2F, ok := sftp2FilesByName[name] 1414 if !ok { 1415 t.Fatalf("%v present in os but not sftp2", name) 1416 } 1417 1418 //t.Logf("%v: %v %v %v", name, osF, sftp1F, sftp2F) 1419 if osF.Size() != sftp1F.Size() || sftp1F.Size() != sftp2F.Size() { 1420 t.Fatalf("size %v %v %v", osF.Size(), sftp1F.Size(), sftp2F.Size()) 1421 } 1422 if osF.IsDir() != sftp1F.IsDir() || sftp1F.IsDir() != sftp2F.IsDir() { 1423 t.Fatalf("isdir %v %v %v", osF.IsDir(), sftp1F.IsDir(), sftp2F.IsDir()) 1424 } 1425 if osF.ModTime().Sub(sftp1F.ModTime()) > time.Second || sftp1F.ModTime() != sftp2F.ModTime() { 1426 t.Fatalf("modtime %v %v %v", osF.ModTime(), sftp1F.ModTime(), sftp2F.ModTime()) 1427 } 1428 if osF.Mode() != sftp1F.Mode() || sftp1F.Mode() != sftp2F.Mode() { 1429 t.Fatalf("mode %x %x %x", osF.Mode(), sftp1F.Mode(), sftp2F.Mode()) 1430 } 1431 } 1432 } 1433 1434 var clientReadTests = []struct { 1435 n int64 1436 }{ 1437 {0}, 1438 {1}, 1439 {1000}, 1440 {1024}, 1441 {1025}, 1442 {2048}, 1443 {4096}, 1444 {1 << 12}, 1445 {1 << 13}, 1446 {1 << 14}, 1447 {1 << 15}, 1448 {1 << 16}, 1449 {1 << 17}, 1450 {1 << 18}, 1451 {1 << 19}, 1452 {1 << 20}, 1453 } 1454 1455 func TestClientRead(t *testing.T) { 1456 sftp, cmd := testClient(t, READONLY, NODELAY) 1457 defer cmd.Wait() 1458 defer sftp.Close() 1459 1460 d, err := ioutil.TempDir("", "sftptest-read") 1461 if err != nil { 1462 t.Fatal(err) 1463 } 1464 defer os.RemoveAll(d) 1465 1466 for _, disableConcurrentReads := range []bool{true, false} { 1467 for _, tt := range clientReadTests { 1468 f, err := ioutil.TempFile(d, "read-test") 1469 if err != nil { 1470 t.Fatal(err) 1471 } 1472 defer f.Close() 1473 hash := writeN(t, f, tt.n) 1474 sftp.disableConcurrentReads = disableConcurrentReads 1475 f2, err := sftp.Open(f.Name()) 1476 if err != nil { 1477 t.Fatal(err) 1478 } 1479 defer f2.Close() 1480 hash2, n := readHash(t, f2) 1481 if hash != hash2 || tt.n != n { 1482 t.Errorf("Read: hash: want: %q, got %q, read: want: %v, got %v", hash, hash2, tt.n, n) 1483 } 1484 } 1485 } 1486 } 1487 1488 // readHash reads r until EOF returning the number of bytes read 1489 // and the hash of the contents. 1490 func readHash(t *testing.T, r io.Reader) (string, int64) { 1491 h := sha1.New() 1492 read, err := io.Copy(h, r) 1493 if err != nil { 1494 t.Fatal(err) 1495 } 1496 return string(h.Sum(nil)), read 1497 } 1498 1499 // writeN writes n bytes of random data to w and returns the 1500 // hash of that data. 1501 func writeN(t *testing.T, w io.Writer, n int64) string { 1502 r := rand.New(rand.NewSource(time.Now().UnixNano())) 1503 1504 h := sha1.New() 1505 1506 mw := io.MultiWriter(w, h) 1507 1508 written, err := io.CopyN(mw, r, n) 1509 if err != nil { 1510 t.Fatal(err) 1511 } 1512 if written != n { 1513 t.Fatalf("CopyN(%v): wrote: %v", n, written) 1514 } 1515 return string(h.Sum(nil)) 1516 } 1517 1518 var clientWriteTests = []struct { 1519 n int 1520 total int64 // cumulative file size 1521 }{ 1522 {0, 0}, 1523 {1, 1}, 1524 {0, 1}, 1525 {999, 1000}, 1526 {24, 1024}, 1527 {1023, 2047}, 1528 {2048, 4095}, 1529 {1 << 12, 8191}, 1530 {1 << 13, 16383}, 1531 {1 << 14, 32767}, 1532 {1 << 15, 65535}, 1533 {1 << 16, 131071}, 1534 {1 << 17, 262143}, 1535 {1 << 18, 524287}, 1536 {1 << 19, 1048575}, 1537 {1 << 20, 2097151}, 1538 {1 << 21, 4194303}, 1539 } 1540 1541 func TestClientWrite(t *testing.T) { 1542 sftp, cmd := testClient(t, READWRITE, NODELAY) 1543 defer cmd.Wait() 1544 defer sftp.Close() 1545 1546 d, err := ioutil.TempDir("", "sftptest-write") 1547 if err != nil { 1548 t.Fatal(err) 1549 } 1550 defer os.RemoveAll(d) 1551 1552 f := path.Join(d, "writeTest") 1553 w, err := sftp.Create(f) 1554 if err != nil { 1555 t.Fatal(err) 1556 } 1557 defer w.Close() 1558 1559 for _, tt := range clientWriteTests { 1560 got, err := w.Write(make([]byte, tt.n)) 1561 if err != nil { 1562 t.Fatal(err) 1563 } 1564 if got != tt.n { 1565 t.Errorf("Write(%v): wrote: want: %v, got %v", tt.n, tt.n, got) 1566 } 1567 fi, err := os.Stat(f) 1568 if err != nil { 1569 t.Fatal(err) 1570 } 1571 if total := fi.Size(); total != tt.total { 1572 t.Errorf("Write(%v): size: want: %v, got %v", tt.n, tt.total, total) 1573 } 1574 } 1575 } 1576 1577 // ReadFrom is basically Write with io.Reader as the arg 1578 func TestClientReadFrom(t *testing.T) { 1579 sftp, cmd := testClient(t, READWRITE, NODELAY) 1580 defer cmd.Wait() 1581 defer sftp.Close() 1582 1583 d, err := ioutil.TempDir("", "sftptest-readfrom") 1584 if err != nil { 1585 t.Fatal(err) 1586 } 1587 defer os.RemoveAll(d) 1588 1589 f := path.Join(d, "writeTest") 1590 w, err := sftp.Create(f) 1591 if err != nil { 1592 t.Fatal(err) 1593 } 1594 defer w.Close() 1595 1596 for _, tt := range clientWriteTests { 1597 got, err := w.ReadFrom(bytes.NewReader(make([]byte, tt.n))) 1598 if err != nil { 1599 t.Fatal(err) 1600 } 1601 if got != int64(tt.n) { 1602 t.Errorf("Write(%v): wrote: want: %v, got %v", tt.n, tt.n, got) 1603 } 1604 fi, err := os.Stat(f) 1605 if err != nil { 1606 t.Fatal(err) 1607 } 1608 if total := fi.Size(); total != tt.total { 1609 t.Errorf("Write(%v): size: want: %v, got %v", tt.n, tt.total, total) 1610 } 1611 } 1612 } 1613 1614 // A sizedReader is a Reader with a completely arbitrary Size. 1615 type sizedReader struct { 1616 io.Reader 1617 size int 1618 } 1619 1620 func (r *sizedReader) Size() int { return r.size } 1621 1622 // Test File.ReadFrom's handling of a Reader's Size: 1623 // it should be used as a heuristic for determining concurrency only. 1624 func TestClientReadFromSizeMismatch(t *testing.T) { 1625 const ( 1626 packetSize = 1024 1627 filesize = 4 * packetSize 1628 ) 1629 1630 sftp, cmd := testClient(t, READWRITE, NODELAY, MaxPacketChecked(packetSize), UseConcurrentWrites(true)) 1631 defer cmd.Wait() 1632 defer sftp.Close() 1633 1634 d, err := ioutil.TempDir("", "sftptest-readfrom-size-mismatch") 1635 if err != nil { 1636 t.Fatal("cannot create temp dir:", err) 1637 } 1638 defer os.RemoveAll(d) 1639 1640 buf := make([]byte, filesize) 1641 1642 for i, reportedSize := range []int{ 1643 -1, filesize - 100, filesize, filesize + 100, 1644 } { 1645 t.Run(fmt.Sprint(i), func(t *testing.T) { 1646 r := &sizedReader{Reader: bytes.NewReader(buf), size: reportedSize} 1647 1648 f := path.Join(d, fmt.Sprint(i)) 1649 w, err := sftp.Create(f) 1650 if err != nil { 1651 t.Fatal("unexpected error:", err) 1652 } 1653 defer w.Close() 1654 1655 n, err := w.ReadFrom(r) 1656 assert.EqualValues(t, filesize, n) 1657 1658 fi, err := os.Stat(f) 1659 if err != nil { 1660 t.Fatal("unexpected error:", err) 1661 } 1662 assert.EqualValues(t, filesize, fi.Size()) 1663 }) 1664 } 1665 } 1666 1667 // Issue #145 in github 1668 // Deadlock in ReadFrom when network drops after 1 good packet. 1669 // Deadlock would occur anytime desiredInFlight-inFlight==2 and 2 errors 1670 // occurred in a row. The channel to report the errors only had a buffer 1671 // of 1 and 2 would be sent. 1672 var errFakeNet = errors.New("Fake network issue") 1673 1674 func TestClientReadFromDeadlock(t *testing.T) { 1675 for i := 0; i < 5; i++ { 1676 clientWriteDeadlock(t, i, func(f *File) { 1677 b := make([]byte, 32768*4) 1678 content := bytes.NewReader(b) 1679 _, err := f.ReadFrom(content) 1680 if !errors.Is(err, errFakeNet) { 1681 t.Fatal("Didn't receive correct error:", err) 1682 } 1683 }) 1684 } 1685 } 1686 1687 // Write has exact same problem 1688 func TestClientWriteDeadlock(t *testing.T) { 1689 for i := 0; i < 5; i++ { 1690 clientWriteDeadlock(t, i, func(f *File) { 1691 b := make([]byte, 32768*4) 1692 1693 _, err := f.Write(b) 1694 if !errors.Is(err, errFakeNet) { 1695 t.Fatal("Didn't receive correct error:", err) 1696 } 1697 }) 1698 } 1699 } 1700 1701 type timeBombWriter struct { 1702 count int 1703 w io.WriteCloser 1704 } 1705 1706 func (w *timeBombWriter) Write(b []byte) (int, error) { 1707 if w.count < 1 { 1708 return 0, errFakeNet 1709 } 1710 1711 w.count-- 1712 return w.w.Write(b) 1713 } 1714 1715 func (w *timeBombWriter) Close() error { 1716 return w.w.Close() 1717 } 1718 1719 // shared body for both previous tests 1720 func clientWriteDeadlock(t *testing.T, N int, badfunc func(*File)) { 1721 if !*testServerImpl { 1722 t.Skipf("skipping without -testserver") 1723 } 1724 1725 sftp, cmd := testClient(t, READWRITE, NODELAY) 1726 defer cmd.Wait() 1727 defer sftp.Close() 1728 1729 d, err := ioutil.TempDir("", "sftptest-writedeadlock") 1730 if err != nil { 1731 t.Fatal(err) 1732 } 1733 defer os.RemoveAll(d) 1734 1735 f := path.Join(d, "writeTest") 1736 w, err := sftp.Create(f) 1737 if err != nil { 1738 t.Fatal(err) 1739 } 1740 defer w.Close() 1741 1742 // Override the clienConn Writer with a failing version 1743 // Replicates network error/drop part way through (after N good writes) 1744 wrap := sftp.clientConn.conn.WriteCloser 1745 sftp.clientConn.conn.WriteCloser = &timeBombWriter{ 1746 count: N, 1747 w: wrap, 1748 } 1749 1750 // this locked (before the fix) 1751 badfunc(w) 1752 } 1753 1754 // Read/WriteTo has this issue as well 1755 func TestClientReadDeadlock(t *testing.T) { 1756 for i := 0; i < 3; i++ { 1757 clientReadDeadlock(t, i, func(f *File) { 1758 b := make([]byte, 32768*4) 1759 1760 _, err := f.Read(b) 1761 if !errors.Is(err, errFakeNet) { 1762 t.Fatal("Didn't receive correct error:", err) 1763 } 1764 }) 1765 } 1766 } 1767 1768 func TestClientWriteToDeadlock(t *testing.T) { 1769 for i := 0; i < 3; i++ { 1770 clientReadDeadlock(t, i, func(f *File) { 1771 b := make([]byte, 32768*4) 1772 1773 buf := bytes.NewBuffer(b) 1774 1775 _, err := f.WriteTo(buf) 1776 if !errors.Is(err, errFakeNet) { 1777 t.Fatal("Didn't receive correct error:", err) 1778 } 1779 }) 1780 } 1781 } 1782 1783 func clientReadDeadlock(t *testing.T, N int, badfunc func(*File)) { 1784 if !*testServerImpl { 1785 t.Skipf("skipping without -testserver") 1786 } 1787 sftp, cmd := testClient(t, READWRITE, NODELAY) 1788 defer cmd.Wait() 1789 defer sftp.Close() 1790 1791 d, err := ioutil.TempDir("", "sftptest-readdeadlock") 1792 if err != nil { 1793 t.Fatal(err) 1794 } 1795 defer os.RemoveAll(d) 1796 1797 f := path.Join(d, "writeTest") 1798 1799 w, err := sftp.Create(f) 1800 if err != nil { 1801 t.Fatal(err) 1802 } 1803 defer w.Close() 1804 1805 // write the data for the read tests 1806 b := make([]byte, 32768*4) 1807 w.Write(b) 1808 1809 // open new copy of file for read tests 1810 r, err := sftp.Open(f) 1811 if err != nil { 1812 t.Fatal(err) 1813 } 1814 defer r.Close() 1815 1816 // Override the clienConn Writer with a failing version 1817 // Replicates network error/drop part way through (after N good writes) 1818 wrap := sftp.clientConn.conn.WriteCloser 1819 sftp.clientConn.conn.WriteCloser = &timeBombWriter{ 1820 count: N, 1821 w: wrap, 1822 } 1823 1824 // this locked (before the fix) 1825 badfunc(r) 1826 } 1827 1828 func TestClientSyncGo(t *testing.T) { 1829 if !*testServerImpl { 1830 t.Skipf("skipping without -testserver") 1831 } 1832 err := testClientSync(t) 1833 1834 // Since Server does not support the fsync extension, we can only 1835 // check that we get the right error. 1836 require.Error(t, err) 1837 1838 switch err := err.(type) { 1839 case *StatusError: 1840 assert.Equal(t, ErrSSHFxOpUnsupported, err.FxCode()) 1841 default: 1842 t.Error(err) 1843 } 1844 } 1845 1846 func TestClientSyncSFTP(t *testing.T) { 1847 if *testServerImpl { 1848 t.Skipf("skipping with -testserver") 1849 } 1850 err := testClientSync(t) 1851 assert.NoError(t, err) 1852 } 1853 1854 func testClientSync(t *testing.T) error { 1855 sftp, cmd := testClient(t, READWRITE, NODELAY) 1856 defer cmd.Wait() 1857 defer sftp.Close() 1858 1859 d, err := ioutil.TempDir("", "sftptest.sync") 1860 require.NoError(t, err) 1861 defer os.RemoveAll(d) 1862 1863 f := path.Join(d, "syncTest") 1864 w, err := sftp.Create(f) 1865 require.NoError(t, err) 1866 defer w.Close() 1867 1868 return w.Sync() 1869 } 1870 1871 // taken from github.com/kr/fs/walk_test.go 1872 1873 type Node struct { 1874 name string 1875 entries []*Node // nil if the entry is a file 1876 mark int 1877 } 1878 1879 var tree = &Node{ 1880 "testdata", 1881 []*Node{ 1882 {"a", nil, 0}, 1883 {"b", []*Node{}, 0}, 1884 {"c", nil, 0}, 1885 { 1886 "d", 1887 []*Node{ 1888 {"x", nil, 0}, 1889 {"y", []*Node{}, 0}, 1890 { 1891 "z", 1892 []*Node{ 1893 {"u", nil, 0}, 1894 {"v", nil, 0}, 1895 }, 1896 0, 1897 }, 1898 }, 1899 0, 1900 }, 1901 }, 1902 0, 1903 } 1904 1905 func walkTree(n *Node, path string, f func(path string, n *Node)) { 1906 f(path, n) 1907 for _, e := range n.entries { 1908 walkTree(e, filepath.Join(path, e.name), f) 1909 } 1910 } 1911 1912 func makeTree(t *testing.T) { 1913 walkTree(tree, tree.name, func(path string, n *Node) { 1914 if n.entries == nil { 1915 fd, err := os.Create(path) 1916 if err != nil { 1917 t.Errorf("makeTree: %v", err) 1918 return 1919 } 1920 fd.Close() 1921 } else { 1922 os.Mkdir(path, 0770) 1923 } 1924 }) 1925 } 1926 1927 func markTree(n *Node) { walkTree(n, "", func(path string, n *Node) { n.mark++ }) } 1928 1929 func checkMarks(t *testing.T, report bool) { 1930 walkTree(tree, tree.name, func(path string, n *Node) { 1931 if n.mark != 1 && report { 1932 t.Errorf("node %s mark = %d; expected 1", path, n.mark) 1933 } 1934 n.mark = 0 1935 }) 1936 } 1937 1938 // Assumes that each node name is unique. Good enough for a test. 1939 // If clear is true, any incoming error is cleared before return. The errors 1940 // are always accumulated, though. 1941 func mark(path string, info os.FileInfo, err error, errors *[]error, clear bool) error { 1942 if err != nil { 1943 *errors = append(*errors, err) 1944 if clear { 1945 return nil 1946 } 1947 return err 1948 } 1949 name := info.Name() 1950 walkTree(tree, tree.name, func(path string, n *Node) { 1951 if n.name == name { 1952 n.mark++ 1953 } 1954 }) 1955 return nil 1956 } 1957 1958 func TestClientWalk(t *testing.T) { 1959 sftp, cmd := testClient(t, READONLY, NODELAY) 1960 defer cmd.Wait() 1961 defer sftp.Close() 1962 1963 makeTree(t) 1964 errors := make([]error, 0, 10) 1965 clear := true 1966 markFn := func(walker *fs.Walker) error { 1967 for walker.Step() { 1968 err := mark(walker.Path(), walker.Stat(), walker.Err(), &errors, clear) 1969 if err != nil { 1970 return err 1971 } 1972 } 1973 return nil 1974 } 1975 // Expect no errors. 1976 err := markFn(sftp.Walk(tree.name)) 1977 if err != nil { 1978 t.Fatalf("no error expected, found: %s", err) 1979 } 1980 if len(errors) != 0 { 1981 t.Fatalf("unexpected errors: %s", errors) 1982 } 1983 checkMarks(t, true) 1984 errors = errors[0:0] 1985 1986 // Test permission errors. Only possible if we're not root 1987 // and only on some file systems (AFS, FAT). To avoid errors during 1988 // all.bash on those file systems, skip during go test -short. 1989 if os.Getuid() > 0 && !testing.Short() { 1990 // introduce 2 errors: chmod top-level directories to 0 1991 os.Chmod(filepath.Join(tree.name, tree.entries[1].name), 0) 1992 os.Chmod(filepath.Join(tree.name, tree.entries[3].name), 0) 1993 1994 // 3) capture errors, expect two. 1995 // mark respective subtrees manually 1996 markTree(tree.entries[1]) 1997 markTree(tree.entries[3]) 1998 // correct double-marking of directory itself 1999 tree.entries[1].mark-- 2000 tree.entries[3].mark-- 2001 err := markFn(sftp.Walk(tree.name)) 2002 if err != nil { 2003 t.Fatalf("expected no error return from Walk, got %s", err) 2004 } 2005 if len(errors) != 2 { 2006 t.Errorf("expected 2 errors, got %d: %s", len(errors), errors) 2007 } 2008 // the inaccessible subtrees were marked manually 2009 checkMarks(t, true) 2010 errors = errors[0:0] 2011 2012 // 4) capture errors, stop after first error. 2013 // mark respective subtrees manually 2014 markTree(tree.entries[1]) 2015 markTree(tree.entries[3]) 2016 // correct double-marking of directory itself 2017 tree.entries[1].mark-- 2018 tree.entries[3].mark-- 2019 clear = false // error will stop processing 2020 err = markFn(sftp.Walk(tree.name)) 2021 if err == nil { 2022 t.Fatalf("expected error return from Walk") 2023 } 2024 if len(errors) != 1 { 2025 t.Errorf("expected 1 error, got %d: %s", len(errors), errors) 2026 } 2027 // the inaccessible subtrees were marked manually 2028 checkMarks(t, false) 2029 errors = errors[0:0] 2030 2031 // restore permissions 2032 os.Chmod(filepath.Join(tree.name, tree.entries[1].name), 0770) 2033 os.Chmod(filepath.Join(tree.name, tree.entries[3].name), 0770) 2034 } 2035 2036 // cleanup 2037 if err := os.RemoveAll(tree.name); err != nil { 2038 t.Errorf("removeTree: %v", err) 2039 } 2040 } 2041 2042 type MatchTest struct { 2043 pattern, s string 2044 match bool 2045 err error 2046 } 2047 2048 var matchTests = []MatchTest{ 2049 {"abc", "abc", true, nil}, 2050 {"*", "abc", true, nil}, 2051 {"*c", "abc", true, nil}, 2052 {"a*", "a", true, nil}, 2053 {"a*", "abc", true, nil}, 2054 {"a*", "ab/c", false, nil}, 2055 {"a*/b", "abc/b", true, nil}, 2056 {"a*/b", "a/c/b", false, nil}, 2057 {"a*b*c*d*e*/f", "axbxcxdxe/f", true, nil}, 2058 {"a*b*c*d*e*/f", "axbxcxdxexxx/f", true, nil}, 2059 {"a*b*c*d*e*/f", "axbxcxdxe/xxx/f", false, nil}, 2060 {"a*b*c*d*e*/f", "axbxcxdxexxx/fff", false, nil}, 2061 {"a*b?c*x", "abxbbxdbxebxczzx", true, nil}, 2062 {"a*b?c*x", "abxbbxdbxebxczzy", false, nil}, 2063 {"ab[c]", "abc", true, nil}, 2064 {"ab[b-d]", "abc", true, nil}, 2065 {"ab[e-g]", "abc", false, nil}, 2066 {"ab[^c]", "abc", false, nil}, 2067 {"ab[^b-d]", "abc", false, nil}, 2068 {"ab[^e-g]", "abc", true, nil}, 2069 {"a\\*b", "a*b", true, nil}, 2070 {"a\\*b", "ab", false, nil}, 2071 {"a?b", "a☺b", true, nil}, 2072 {"a[^a]b", "a☺b", true, nil}, 2073 {"a???b", "a☺b", false, nil}, 2074 {"a[^a][^a][^a]b", "a☺b", false, nil}, 2075 {"[a-ζ]*", "α", true, nil}, 2076 {"*[a-ζ]", "A", false, nil}, 2077 {"a?b", "a/b", false, nil}, 2078 {"a*b", "a/b", false, nil}, 2079 {"[\\]a]", "]", true, nil}, 2080 {"[\\-]", "-", true, nil}, 2081 {"[x\\-]", "x", true, nil}, 2082 {"[x\\-]", "-", true, nil}, 2083 {"[x\\-]", "z", false, nil}, 2084 {"[\\-x]", "x", true, nil}, 2085 {"[\\-x]", "-", true, nil}, 2086 {"[\\-x]", "a", false, nil}, 2087 {"[]a]", "]", false, ErrBadPattern}, 2088 {"[-]", "-", false, ErrBadPattern}, 2089 {"[x-]", "x", false, ErrBadPattern}, 2090 {"[x-]", "-", false, ErrBadPattern}, 2091 {"[x-]", "z", false, ErrBadPattern}, 2092 {"[-x]", "x", false, ErrBadPattern}, 2093 {"[-x]", "-", false, ErrBadPattern}, 2094 {"[-x]", "a", false, ErrBadPattern}, 2095 {"\\", "a", false, ErrBadPattern}, 2096 {"[a-b-c]", "a", false, ErrBadPattern}, 2097 {"[", "a", false, ErrBadPattern}, 2098 {"[^", "a", false, ErrBadPattern}, 2099 {"[^bc", "a", false, ErrBadPattern}, 2100 {"a[", "ab", false, ErrBadPattern}, 2101 {"*x", "xxx", true, nil}, 2102 2103 // The following test behaves differently on Go 1.15.3 and Go tip as 2104 // https://github.com/golang/go/commit/b5ddc42b465dd5b9532ee336d98343d81a6d35b2 2105 // (pre-Go 1.16). TODO: reevaluate when Go 1.16 is released. 2106 //{"a[", "a", false, nil}, 2107 } 2108 2109 func errp(e error) string { 2110 if e == nil { 2111 return "<nil>" 2112 } 2113 return e.Error() 2114 } 2115 2116 // contains returns true if vector contains the string s. 2117 func contains(vector []string, s string) bool { 2118 for _, elem := range vector { 2119 if elem == s { 2120 return true 2121 } 2122 } 2123 return false 2124 } 2125 2126 var globTests = []struct { 2127 pattern, result string 2128 }{ 2129 {"match.go", "match.go"}, 2130 {"mat?h.go", "match.go"}, 2131 {"ma*ch.go", "match.go"}, 2132 {`\m\a\t\c\h\.\g\o`, "match.go"}, 2133 {"../*/match.go", "../sftp/match.go"}, 2134 } 2135 2136 type globTest struct { 2137 pattern string 2138 matches []string 2139 } 2140 2141 func (test *globTest) buildWant(root string) []string { 2142 var want []string 2143 for _, m := range test.matches { 2144 want = append(want, root+filepath.FromSlash(m)) 2145 } 2146 sort.Strings(want) 2147 return want 2148 } 2149 2150 func TestMatch(t *testing.T) { 2151 for _, tt := range matchTests { 2152 pattern := tt.pattern 2153 s := tt.s 2154 ok, err := Match(pattern, s) 2155 if ok != tt.match || err != tt.err { 2156 t.Errorf("Match(%#q, %#q) = %v, %q want %v, %q", pattern, s, ok, errp(err), tt.match, errp(tt.err)) 2157 } 2158 } 2159 } 2160 2161 func TestGlob(t *testing.T) { 2162 sftp, cmd := testClient(t, READONLY, NODELAY) 2163 defer cmd.Wait() 2164 defer sftp.Close() 2165 2166 for _, tt := range globTests { 2167 pattern := tt.pattern 2168 result := tt.result 2169 matches, err := sftp.Glob(pattern) 2170 if err != nil { 2171 t.Errorf("Glob error for %q: %s", pattern, err) 2172 continue 2173 } 2174 if !contains(matches, result) { 2175 t.Errorf("Glob(%#q) = %#v want %v", pattern, matches, result) 2176 } 2177 } 2178 for _, pattern := range []string{"no_match", "../*/no_match"} { 2179 matches, err := sftp.Glob(pattern) 2180 if err != nil { 2181 t.Errorf("Glob error for %q: %s", pattern, err) 2182 continue 2183 } 2184 if len(matches) != 0 { 2185 t.Errorf("Glob(%#q) = %#v want []", pattern, matches) 2186 } 2187 } 2188 } 2189 2190 func TestGlobError(t *testing.T) { 2191 sftp, cmd := testClient(t, READONLY, NODELAY) 2192 defer cmd.Wait() 2193 defer sftp.Close() 2194 2195 _, err := sftp.Glob("[7]") 2196 if err != nil { 2197 t.Error("expected error for bad pattern; got none") 2198 } 2199 } 2200 2201 func TestGlobUNC(t *testing.T) { 2202 sftp, cmd := testClient(t, READONLY, NODELAY) 2203 defer cmd.Wait() 2204 defer sftp.Close() 2205 // Just make sure this runs without crashing for now. 2206 // See issue 15879. 2207 sftp.Glob(`\\?\C:\*`) 2208 } 2209 2210 // sftp/issue/42, abrupt server hangup would result in client hangs. 2211 func TestServerRoughDisconnect(t *testing.T) { 2212 skipIfWindows(t) 2213 if *testServerImpl { 2214 t.Skipf("skipping with -testserver") 2215 } 2216 sftp, cmd := testClient(t, READONLY, NODELAY) 2217 defer cmd.Wait() 2218 defer sftp.Close() 2219 2220 f, err := sftp.Open("/dev/zero") 2221 if err != nil { 2222 t.Fatal(err) 2223 } 2224 defer f.Close() 2225 go func() { 2226 time.Sleep(100 * time.Millisecond) 2227 cmd.Process.Kill() 2228 }() 2229 2230 _, err = io.Copy(ioutil.Discard, f) 2231 assert.Error(t, err) 2232 } 2233 2234 // sftp/issue/181, abrupt server hangup would result in client hangs. 2235 // due to broadcastErr filling up the request channel 2236 // this reproduces it about 50% of the time 2237 func TestServerRoughDisconnect2(t *testing.T) { 2238 skipIfWindows(t) 2239 if *testServerImpl { 2240 t.Skipf("skipping with -testserver") 2241 } 2242 sftp, cmd := testClient(t, READONLY, NODELAY) 2243 defer cmd.Wait() 2244 defer sftp.Close() 2245 2246 f, err := sftp.Open("/dev/zero") 2247 if err != nil { 2248 t.Fatal(err) 2249 } 2250 defer f.Close() 2251 b := make([]byte, 32768*100) 2252 go func() { 2253 time.Sleep(1 * time.Millisecond) 2254 cmd.Process.Kill() 2255 }() 2256 for { 2257 _, err = f.Read(b) 2258 if err != nil { 2259 break 2260 } 2261 } 2262 } 2263 2264 // sftp/issue/234 - abrupt shutdown during ReadFrom hangs client 2265 func TestServerRoughDisconnect3(t *testing.T) { 2266 skipIfWindows(t) 2267 if *testServerImpl { 2268 t.Skipf("skipping with -testserver") 2269 } 2270 2271 sftp, cmd := testClient(t, READWRITE, NODELAY) 2272 defer cmd.Wait() 2273 defer sftp.Close() 2274 2275 dest, err := sftp.OpenFile("/dev/null", os.O_RDWR) 2276 if err != nil { 2277 t.Fatal(err) 2278 } 2279 defer dest.Close() 2280 2281 src, err := os.Open("/dev/zero") 2282 if err != nil { 2283 t.Fatal(err) 2284 } 2285 defer src.Close() 2286 2287 go func() { 2288 time.Sleep(10 * time.Millisecond) 2289 cmd.Process.Kill() 2290 }() 2291 2292 _, err = io.Copy(dest, src) 2293 assert.Error(t, err) 2294 } 2295 2296 // sftp/issue/234 - also affected Write 2297 func TestServerRoughDisconnect4(t *testing.T) { 2298 skipIfWindows(t) 2299 if *testServerImpl { 2300 t.Skipf("skipping with -testserver") 2301 } 2302 sftp, cmd := testClient(t, READWRITE, NODELAY) 2303 defer cmd.Wait() 2304 defer sftp.Close() 2305 2306 dest, err := sftp.OpenFile("/dev/null", os.O_RDWR) 2307 if err != nil { 2308 t.Fatal(err) 2309 } 2310 defer dest.Close() 2311 2312 src, err := os.Open("/dev/zero") 2313 if err != nil { 2314 t.Fatal(err) 2315 } 2316 defer src.Close() 2317 2318 go func() { 2319 time.Sleep(10 * time.Millisecond) 2320 cmd.Process.Kill() 2321 }() 2322 2323 b := make([]byte, 32768*200) 2324 src.Read(b) 2325 for { 2326 _, err = dest.Write(b) 2327 if err != nil { 2328 assert.NotEqual(t, io.EOF, err) 2329 break 2330 } 2331 } 2332 2333 _, err = io.Copy(dest, src) 2334 assert.Error(t, err) 2335 } 2336 2337 // sftp/issue/390 - server disconnect should not cause io.EOF or 2338 // io.ErrUnexpectedEOF in sftp.File.Read, because those confuse io.ReadFull. 2339 func TestServerRoughDisconnectEOF(t *testing.T) { 2340 skipIfWindows(t) 2341 if *testServerImpl { 2342 t.Skipf("skipping with -testserver") 2343 } 2344 sftp, cmd := testClient(t, READONLY, NODELAY) 2345 defer cmd.Wait() 2346 defer sftp.Close() 2347 2348 f, err := sftp.Open("/dev/null") 2349 if err != nil { 2350 t.Fatal(err) 2351 } 2352 defer f.Close() 2353 go func() { 2354 time.Sleep(100 * time.Millisecond) 2355 cmd.Process.Kill() 2356 }() 2357 2358 _, err = io.ReadFull(f, make([]byte, 10)) 2359 assert.Error(t, err) 2360 assert.NotEqual(t, io.ErrUnexpectedEOF, err) 2361 } 2362 2363 // sftp/issue/26 writing to a read only file caused client to loop. 2364 func TestClientWriteToROFile(t *testing.T) { 2365 skipIfWindows(t) 2366 2367 sftp, cmd := testClient(t, READWRITE, NODELAY) 2368 defer cmd.Wait() 2369 2370 defer func() { 2371 err := sftp.Close() 2372 assert.NoError(t, err) 2373 }() 2374 2375 // TODO (puellanivis): /dev/zero is not actually a read-only file. 2376 // So, this test works purely by accident. 2377 f, err := sftp.Open("/dev/zero") 2378 if err != nil { 2379 t.Fatal(err) 2380 } 2381 defer f.Close() 2382 2383 _, err = f.Write([]byte("hello")) 2384 if err == nil { 2385 t.Fatal("expected error, got", err) 2386 } 2387 } 2388 2389 func benchmarkRead(b *testing.B, bufsize int, delay time.Duration) { 2390 skipIfWindows(b) 2391 size := 10*1024*1024 + 123 // ~10MiB 2392 2393 // open sftp client 2394 sftp, cmd := testClient(b, READONLY, delay) 2395 defer cmd.Wait() 2396 defer sftp.Close() 2397 2398 buf := make([]byte, bufsize) 2399 2400 b.ResetTimer() 2401 b.SetBytes(int64(size)) 2402 2403 for i := 0; i < b.N; i++ { 2404 offset := 0 2405 2406 f2, err := sftp.Open("/dev/zero") 2407 if err != nil { 2408 b.Fatal(err) 2409 } 2410 2411 for offset < size { 2412 n, err := io.ReadFull(f2, buf) 2413 offset += n 2414 if err == io.ErrUnexpectedEOF && offset != size { 2415 b.Fatalf("read too few bytes! want: %d, got: %d", size, n) 2416 } 2417 2418 if err != nil { 2419 b.Fatal(err) 2420 } 2421 2422 offset += n 2423 } 2424 2425 f2.Close() 2426 } 2427 } 2428 2429 func BenchmarkRead1k(b *testing.B) { 2430 benchmarkRead(b, 1*1024, NODELAY) 2431 } 2432 2433 func BenchmarkRead16k(b *testing.B) { 2434 benchmarkRead(b, 16*1024, NODELAY) 2435 } 2436 2437 func BenchmarkRead32k(b *testing.B) { 2438 benchmarkRead(b, 32*1024, NODELAY) 2439 } 2440 2441 func BenchmarkRead128k(b *testing.B) { 2442 benchmarkRead(b, 128*1024, NODELAY) 2443 } 2444 2445 func BenchmarkRead512k(b *testing.B) { 2446 benchmarkRead(b, 512*1024, NODELAY) 2447 } 2448 2449 func BenchmarkRead1MiB(b *testing.B) { 2450 benchmarkRead(b, 1024*1024, NODELAY) 2451 } 2452 2453 func BenchmarkRead4MiB(b *testing.B) { 2454 benchmarkRead(b, 4*1024*1024, NODELAY) 2455 } 2456 2457 func BenchmarkRead4MiBDelay10Msec(b *testing.B) { 2458 benchmarkRead(b, 4*1024*1024, 10*time.Millisecond) 2459 } 2460 2461 func BenchmarkRead4MiBDelay50Msec(b *testing.B) { 2462 benchmarkRead(b, 4*1024*1024, 50*time.Millisecond) 2463 } 2464 2465 func BenchmarkRead4MiBDelay150Msec(b *testing.B) { 2466 benchmarkRead(b, 4*1024*1024, 150*time.Millisecond) 2467 } 2468 2469 func benchmarkWrite(b *testing.B, bufsize int, delay time.Duration) { 2470 size := 10*1024*1024 + 123 // ~10MiB 2471 2472 // open sftp client 2473 sftp, cmd := testClient(b, false, delay) 2474 defer cmd.Wait() 2475 defer sftp.Close() 2476 2477 data := make([]byte, size) 2478 2479 b.ResetTimer() 2480 b.SetBytes(int64(size)) 2481 2482 for i := 0; i < b.N; i++ { 2483 offset := 0 2484 2485 f, err := ioutil.TempFile("", "sftptest-benchwrite") 2486 if err != nil { 2487 b.Fatal(err) 2488 } 2489 defer os.Remove(f.Name()) // actually queue up a series of removes for these files 2490 2491 f2, err := sftp.Create(f.Name()) 2492 if err != nil { 2493 b.Fatal(err) 2494 } 2495 2496 for offset < size { 2497 buf := data[offset:] 2498 if len(buf) > bufsize { 2499 buf = buf[:bufsize] 2500 } 2501 2502 n, err := f2.Write(buf) 2503 if err != nil { 2504 b.Fatal(err) 2505 } 2506 2507 if offset+n < size && n != bufsize { 2508 b.Fatalf("wrote too few bytes! want: %d, got: %d", size, n) 2509 } 2510 2511 offset += n 2512 } 2513 2514 f2.Close() 2515 2516 fi, err := os.Stat(f.Name()) 2517 if err != nil { 2518 b.Fatal(err) 2519 } 2520 2521 if fi.Size() != int64(size) { 2522 b.Fatalf("wrong file size: want %d, got %d", size, fi.Size()) 2523 } 2524 2525 os.Remove(f.Name()) 2526 } 2527 } 2528 2529 func BenchmarkWrite1k(b *testing.B) { 2530 benchmarkWrite(b, 1*1024, NODELAY) 2531 } 2532 2533 func BenchmarkWrite16k(b *testing.B) { 2534 benchmarkWrite(b, 16*1024, NODELAY) 2535 } 2536 2537 func BenchmarkWrite32k(b *testing.B) { 2538 benchmarkWrite(b, 32*1024, NODELAY) 2539 } 2540 2541 func BenchmarkWrite128k(b *testing.B) { 2542 benchmarkWrite(b, 128*1024, NODELAY) 2543 } 2544 2545 func BenchmarkWrite512k(b *testing.B) { 2546 benchmarkWrite(b, 512*1024, NODELAY) 2547 } 2548 2549 func BenchmarkWrite1MiB(b *testing.B) { 2550 benchmarkWrite(b, 1024*1024, NODELAY) 2551 } 2552 2553 func BenchmarkWrite4MiB(b *testing.B) { 2554 benchmarkWrite(b, 4*1024*1024, NODELAY) 2555 } 2556 2557 func BenchmarkWrite4MiBDelay10Msec(b *testing.B) { 2558 benchmarkWrite(b, 4*1024*1024, 10*time.Millisecond) 2559 } 2560 2561 func BenchmarkWrite4MiBDelay50Msec(b *testing.B) { 2562 benchmarkWrite(b, 4*1024*1024, 50*time.Millisecond) 2563 } 2564 2565 func BenchmarkWrite4MiBDelay150Msec(b *testing.B) { 2566 benchmarkWrite(b, 4*1024*1024, 150*time.Millisecond) 2567 } 2568 2569 func benchmarkReadFrom(b *testing.B, bufsize int, delay time.Duration) { 2570 size := 10*1024*1024 + 123 // ~10MiB 2571 2572 // open sftp client 2573 sftp, cmd := testClient(b, false, delay) 2574 defer cmd.Wait() 2575 defer sftp.Close() 2576 2577 data := make([]byte, size) 2578 2579 b.ResetTimer() 2580 b.SetBytes(int64(size)) 2581 2582 for i := 0; i < b.N; i++ { 2583 f, err := ioutil.TempFile("", "sftptest-benchreadfrom") 2584 if err != nil { 2585 b.Fatal(err) 2586 } 2587 defer os.Remove(f.Name()) 2588 2589 f2, err := sftp.Create(f.Name()) 2590 if err != nil { 2591 b.Fatal(err) 2592 } 2593 defer f2.Close() 2594 2595 f2.ReadFrom(bytes.NewReader(data)) 2596 f2.Close() 2597 2598 fi, err := os.Stat(f.Name()) 2599 if err != nil { 2600 b.Fatal(err) 2601 } 2602 2603 if fi.Size() != int64(size) { 2604 b.Fatalf("wrong file size: want %d, got %d", size, fi.Size()) 2605 } 2606 2607 os.Remove(f.Name()) 2608 } 2609 } 2610 2611 func BenchmarkReadFrom1k(b *testing.B) { 2612 benchmarkReadFrom(b, 1*1024, NODELAY) 2613 } 2614 2615 func BenchmarkReadFrom16k(b *testing.B) { 2616 benchmarkReadFrom(b, 16*1024, NODELAY) 2617 } 2618 2619 func BenchmarkReadFrom32k(b *testing.B) { 2620 benchmarkReadFrom(b, 32*1024, NODELAY) 2621 } 2622 2623 func BenchmarkReadFrom128k(b *testing.B) { 2624 benchmarkReadFrom(b, 128*1024, NODELAY) 2625 } 2626 2627 func BenchmarkReadFrom512k(b *testing.B) { 2628 benchmarkReadFrom(b, 512*1024, NODELAY) 2629 } 2630 2631 func BenchmarkReadFrom1MiB(b *testing.B) { 2632 benchmarkReadFrom(b, 1024*1024, NODELAY) 2633 } 2634 2635 func BenchmarkReadFrom4MiB(b *testing.B) { 2636 benchmarkReadFrom(b, 4*1024*1024, NODELAY) 2637 } 2638 2639 func BenchmarkReadFrom4MiBDelay10Msec(b *testing.B) { 2640 benchmarkReadFrom(b, 4*1024*1024, 10*time.Millisecond) 2641 } 2642 2643 func BenchmarkReadFrom4MiBDelay50Msec(b *testing.B) { 2644 benchmarkReadFrom(b, 4*1024*1024, 50*time.Millisecond) 2645 } 2646 2647 func BenchmarkReadFrom4MiBDelay150Msec(b *testing.B) { 2648 benchmarkReadFrom(b, 4*1024*1024, 150*time.Millisecond) 2649 } 2650 2651 func benchmarkWriteTo(b *testing.B, bufsize int, delay time.Duration) { 2652 size := 10*1024*1024 + 123 // ~10MiB 2653 2654 // open sftp client 2655 sftp, cmd := testClient(b, false, delay) 2656 defer cmd.Wait() 2657 defer sftp.Close() 2658 2659 f, err := ioutil.TempFile("", "sftptest-benchwriteto") 2660 if err != nil { 2661 b.Fatal(err) 2662 } 2663 defer os.Remove(f.Name()) 2664 2665 data := make([]byte, size) 2666 2667 f.Write(data) 2668 f.Close() 2669 2670 buf := bytes.NewBuffer(make([]byte, 0, size)) 2671 2672 b.ResetTimer() 2673 b.SetBytes(int64(size)) 2674 2675 for i := 0; i < b.N; i++ { 2676 buf.Reset() 2677 2678 f2, err := sftp.Open(f.Name()) 2679 if err != nil { 2680 b.Fatal(err) 2681 } 2682 2683 f2.WriteTo(buf) 2684 f2.Close() 2685 2686 if buf.Len() != size { 2687 b.Fatalf("wrote buffer size: want %d, got %d", size, buf.Len()) 2688 } 2689 } 2690 } 2691 2692 func BenchmarkWriteTo1k(b *testing.B) { 2693 benchmarkWriteTo(b, 1*1024, NODELAY) 2694 } 2695 2696 func BenchmarkWriteTo16k(b *testing.B) { 2697 benchmarkWriteTo(b, 16*1024, NODELAY) 2698 } 2699 2700 func BenchmarkWriteTo32k(b *testing.B) { 2701 benchmarkWriteTo(b, 32*1024, NODELAY) 2702 } 2703 2704 func BenchmarkWriteTo128k(b *testing.B) { 2705 benchmarkWriteTo(b, 128*1024, NODELAY) 2706 } 2707 2708 func BenchmarkWriteTo512k(b *testing.B) { 2709 benchmarkWriteTo(b, 512*1024, NODELAY) 2710 } 2711 2712 func BenchmarkWriteTo1MiB(b *testing.B) { 2713 benchmarkWriteTo(b, 1024*1024, NODELAY) 2714 } 2715 2716 func BenchmarkWriteTo4MiB(b *testing.B) { 2717 benchmarkWriteTo(b, 4*1024*1024, NODELAY) 2718 } 2719 2720 func BenchmarkWriteTo4MiBDelay10Msec(b *testing.B) { 2721 benchmarkWriteTo(b, 4*1024*1024, 10*time.Millisecond) 2722 } 2723 2724 func BenchmarkWriteTo4MiBDelay50Msec(b *testing.B) { 2725 benchmarkWriteTo(b, 4*1024*1024, 50*time.Millisecond) 2726 } 2727 2728 func BenchmarkWriteTo4MiBDelay150Msec(b *testing.B) { 2729 benchmarkWriteTo(b, 4*1024*1024, 150*time.Millisecond) 2730 } 2731 2732 func benchmarkCopyDown(b *testing.B, fileSize int64, delay time.Duration) { 2733 skipIfWindows(b) 2734 // Create a temp file and fill it with zero's. 2735 src, err := ioutil.TempFile("", "sftptest-benchcopydown") 2736 if err != nil { 2737 b.Fatal(err) 2738 } 2739 defer src.Close() 2740 srcFilename := src.Name() 2741 defer os.Remove(srcFilename) 2742 zero, err := os.Open("/dev/zero") 2743 if err != nil { 2744 b.Fatal(err) 2745 } 2746 n, err := io.Copy(src, io.LimitReader(zero, fileSize)) 2747 if err != nil { 2748 b.Fatal(err) 2749 } 2750 if n < fileSize { 2751 b.Fatal("short copy") 2752 } 2753 zero.Close() 2754 src.Close() 2755 2756 sftp, cmd := testClient(b, READONLY, delay) 2757 defer cmd.Wait() 2758 defer sftp.Close() 2759 b.ResetTimer() 2760 b.SetBytes(fileSize) 2761 2762 for i := 0; i < b.N; i++ { 2763 dst, err := ioutil.TempFile("", "sftptest-benchcopydown") 2764 if err != nil { 2765 b.Fatal(err) 2766 } 2767 defer os.Remove(dst.Name()) 2768 2769 src, err := sftp.Open(srcFilename) 2770 if err != nil { 2771 b.Fatal(err) 2772 } 2773 defer src.Close() 2774 n, err := io.Copy(dst, src) 2775 if err != nil { 2776 b.Fatalf("copy error: %v", err) 2777 } 2778 if n < fileSize { 2779 b.Fatal("unable to copy all bytes") 2780 } 2781 dst.Close() 2782 fi, err := os.Stat(dst.Name()) 2783 if err != nil { 2784 b.Fatal(err) 2785 } 2786 2787 if fi.Size() != fileSize { 2788 b.Fatalf("wrong file size: want %d, got %d", fileSize, fi.Size()) 2789 } 2790 os.Remove(dst.Name()) 2791 } 2792 } 2793 2794 func BenchmarkCopyDown10MiBDelay10Msec(b *testing.B) { 2795 benchmarkCopyDown(b, 10*1024*1024, 10*time.Millisecond) 2796 } 2797 2798 func BenchmarkCopyDown10MiBDelay50Msec(b *testing.B) { 2799 benchmarkCopyDown(b, 10*1024*1024, 50*time.Millisecond) 2800 } 2801 2802 func BenchmarkCopyDown10MiBDelay150Msec(b *testing.B) { 2803 benchmarkCopyDown(b, 10*1024*1024, 150*time.Millisecond) 2804 } 2805 2806 func benchmarkCopyUp(b *testing.B, fileSize int64, delay time.Duration) { 2807 skipIfWindows(b) 2808 // Create a temp file and fill it with zero's. 2809 src, err := ioutil.TempFile("", "sftptest-benchcopyup") 2810 if err != nil { 2811 b.Fatal(err) 2812 } 2813 defer src.Close() 2814 srcFilename := src.Name() 2815 defer os.Remove(srcFilename) 2816 zero, err := os.Open("/dev/zero") 2817 if err != nil { 2818 b.Fatal(err) 2819 } 2820 n, err := io.Copy(src, io.LimitReader(zero, fileSize)) 2821 if err != nil { 2822 b.Fatal(err) 2823 } 2824 if n < fileSize { 2825 b.Fatal("short copy") 2826 } 2827 zero.Close() 2828 src.Close() 2829 2830 sftp, cmd := testClient(b, false, delay) 2831 defer cmd.Wait() 2832 defer sftp.Close() 2833 2834 b.ResetTimer() 2835 b.SetBytes(fileSize) 2836 2837 for i := 0; i < b.N; i++ { 2838 tmp, err := ioutil.TempFile("", "sftptest-benchcopyup") 2839 if err != nil { 2840 b.Fatal(err) 2841 } 2842 tmp.Close() 2843 defer os.Remove(tmp.Name()) 2844 2845 dst, err := sftp.Create(tmp.Name()) 2846 if err != nil { 2847 b.Fatal(err) 2848 } 2849 defer dst.Close() 2850 src, err := os.Open(srcFilename) 2851 if err != nil { 2852 b.Fatal(err) 2853 } 2854 defer src.Close() 2855 n, err := io.Copy(dst, src) 2856 if err != nil { 2857 b.Fatalf("copy error: %v", err) 2858 } 2859 if n < fileSize { 2860 b.Fatal("unable to copy all bytes") 2861 } 2862 2863 fi, err := os.Stat(tmp.Name()) 2864 if err != nil { 2865 b.Fatal(err) 2866 } 2867 2868 if fi.Size() != fileSize { 2869 b.Fatalf("wrong file size: want %d, got %d", fileSize, fi.Size()) 2870 } 2871 os.Remove(tmp.Name()) 2872 } 2873 } 2874 2875 func BenchmarkCopyUp10MiBDelay10Msec(b *testing.B) { 2876 benchmarkCopyUp(b, 10*1024*1024, 10*time.Millisecond) 2877 } 2878 2879 func BenchmarkCopyUp10MiBDelay50Msec(b *testing.B) { 2880 benchmarkCopyUp(b, 10*1024*1024, 50*time.Millisecond) 2881 } 2882 2883 func BenchmarkCopyUp10MiBDelay150Msec(b *testing.B) { 2884 benchmarkCopyUp(b, 10*1024*1024, 150*time.Millisecond) 2885 }