github.com/pkg/sftp@v1.13.6/request-server_test.go (about) 1 package sftp 2 3 import ( 4 "context" 5 "fmt" 6 "io" 7 "io/ioutil" 8 "net" 9 "os" 10 "path" 11 "runtime" 12 "testing" 13 "time" 14 15 "github.com/stretchr/testify/assert" 16 "github.com/stretchr/testify/require" 17 ) 18 19 var _ = fmt.Print 20 21 type csPair struct { 22 cli *Client 23 svr *RequestServer 24 svrResult chan error 25 } 26 27 // these must be closed in order, else client.Close will hang 28 func (cs csPair) Close() { 29 cs.svr.Close() 30 cs.cli.Close() 31 os.Remove(sock) 32 } 33 34 func (cs csPair) testHandler() *root { 35 return cs.svr.Handlers.FileGet.(*root) 36 } 37 38 const sock = "/tmp/rstest.sock" 39 40 func clientRequestServerPairWithHandlers(t *testing.T, handlers Handlers, options ...RequestServerOption) *csPair { 41 skipIfWindows(t) 42 skipIfPlan9(t) 43 44 ready := make(chan struct{}) 45 canReturn := make(chan struct{}) 46 os.Remove(sock) // either this or signal handling 47 pair := &csPair{ 48 svrResult: make(chan error, 1), 49 } 50 51 var server *RequestServer 52 go func() { 53 l, err := net.Listen("unix", sock) 54 if err != nil { 55 // neither assert nor t.Fatal reliably exit before Accept errors 56 panic(err) 57 } 58 59 close(ready) 60 61 fd, err := l.Accept() 62 require.NoError(t, err) 63 64 if *testAllocator { 65 options = append(options, WithRSAllocator()) 66 } 67 68 server = NewRequestServer(fd, handlers, options...) 69 close(canReturn) 70 71 err = server.Serve() 72 pair.svrResult <- err 73 }() 74 75 <-ready 76 defer os.Remove(sock) 77 78 c, err := net.Dial("unix", sock) 79 require.NoError(t, err) 80 81 client, err := NewClientPipe(c, c) 82 if err != nil { 83 t.Fatalf("unexpected error: %+v", err) 84 } 85 86 <-canReturn 87 pair.svr = server 88 pair.cli = client 89 return pair 90 } 91 92 func clientRequestServerPair(t *testing.T, options ...RequestServerOption) *csPair { 93 return clientRequestServerPairWithHandlers(t, InMemHandler(), options...) 94 } 95 96 func checkRequestServerAllocator(t *testing.T, p *csPair) { 97 if p.svr.pktMgr.alloc == nil { 98 return 99 } 100 checkAllocatorBeforeServerClose(t, p.svr.pktMgr.alloc) 101 p.Close() 102 checkAllocatorAfterServerClose(t, p.svr.pktMgr.alloc) 103 } 104 105 // after adding logging, maybe check log to make sure packet handling 106 // was split over more than one worker 107 func TestRequestSplitWrite(t *testing.T) { 108 p := clientRequestServerPair(t) 109 defer p.Close() 110 w, err := p.cli.Create("/foo") 111 require.NoError(t, err) 112 p.cli.maxPacket = 3 // force it to send in small chunks 113 contents := "one two three four five six seven eight nine ten" 114 w.Write([]byte(contents)) 115 w.Close() 116 r := p.testHandler() 117 f, err := r.fetch("/foo") 118 require.NoError(t, err) 119 assert.Equal(t, contents, string(f.content)) 120 checkRequestServerAllocator(t, p) 121 } 122 123 func TestRequestCache(t *testing.T) { 124 p := clientRequestServerPair(t) 125 defer p.Close() 126 foo := NewRequest("", "foo") 127 foo.ctx, foo.cancelCtx = context.WithCancel(context.Background()) 128 bar := NewRequest("", "bar") 129 fh := p.svr.nextRequest(foo) 130 bh := p.svr.nextRequest(bar) 131 assert.Len(t, p.svr.openRequests, 2) 132 _foo, ok := p.svr.getRequest(fh) 133 assert.Equal(t, foo.Method, _foo.Method) 134 assert.Equal(t, foo.Filepath, _foo.Filepath) 135 assert.Equal(t, foo.Target, _foo.Target) 136 assert.Equal(t, foo.Flags, _foo.Flags) 137 assert.Equal(t, foo.Attrs, _foo.Attrs) 138 assert.Equal(t, foo.state, _foo.state) 139 assert.NotNil(t, _foo.ctx) 140 assert.Equal(t, _foo.Context().Err(), nil, "context is still valid") 141 assert.True(t, ok) 142 _, ok = p.svr.getRequest("zed") 143 assert.False(t, ok) 144 p.svr.closeRequest(fh) 145 assert.Equal(t, _foo.Context().Err(), context.Canceled, "context is now canceled") 146 p.svr.closeRequest(bh) 147 assert.Len(t, p.svr.openRequests, 0) 148 checkRequestServerAllocator(t, p) 149 } 150 151 func TestRequestCacheState(t *testing.T) { 152 // test operation that uses open/close 153 p := clientRequestServerPair(t) 154 defer p.Close() 155 _, err := putTestFile(p.cli, "/foo", "hello") 156 require.NoError(t, err) 157 assert.Len(t, p.svr.openRequests, 0) 158 // test operation that doesn't open/close 159 err = p.cli.Remove("/foo") 160 assert.NoError(t, err) 161 assert.Len(t, p.svr.openRequests, 0) 162 checkRequestServerAllocator(t, p) 163 } 164 165 func putTestFile(cli *Client, path, content string) (int, error) { 166 w, err := cli.Create(path) 167 if err != nil { 168 return 0, err 169 } 170 defer w.Close() 171 172 return w.Write([]byte(content)) 173 } 174 175 func getTestFile(cli *Client, path string) ([]byte, error) { 176 r, err := cli.Open(path) 177 if err != nil { 178 return nil, err 179 } 180 defer r.Close() 181 182 return ioutil.ReadAll(r) 183 } 184 185 func TestRequestWrite(t *testing.T) { 186 p := clientRequestServerPair(t) 187 defer p.Close() 188 n, err := putTestFile(p.cli, "/foo", "hello") 189 require.NoError(t, err) 190 assert.Equal(t, 5, n) 191 r := p.testHandler() 192 f, err := r.fetch("/foo") 193 require.NoError(t, err) 194 assert.False(t, f.isdir) 195 assert.Equal(t, f.content, []byte("hello")) 196 checkRequestServerAllocator(t, p) 197 } 198 199 func TestRequestWriteEmpty(t *testing.T) { 200 p := clientRequestServerPair(t) 201 defer p.Close() 202 n, err := putTestFile(p.cli, "/foo", "") 203 require.NoError(t, err) 204 assert.Equal(t, 0, n) 205 r := p.testHandler() 206 f, err := r.fetch("/foo") 207 require.NoError(t, err) 208 assert.False(t, f.isdir) 209 assert.Len(t, f.content, 0) 210 // lets test with an error 211 r.returnErr(os.ErrInvalid) 212 n, err = putTestFile(p.cli, "/bar", "") 213 require.Error(t, err) 214 r.returnErr(nil) 215 assert.Equal(t, 0, n) 216 checkRequestServerAllocator(t, p) 217 } 218 219 func TestRequestFilename(t *testing.T) { 220 p := clientRequestServerPair(t) 221 defer p.Close() 222 _, err := putTestFile(p.cli, "/foo", "hello") 223 require.NoError(t, err) 224 r := p.testHandler() 225 f, err := r.fetch("/foo") 226 require.NoError(t, err) 227 assert.Equal(t, f.Name(), "foo") 228 _, err = r.fetch("/bar") 229 assert.Error(t, err) 230 checkRequestServerAllocator(t, p) 231 } 232 233 func TestRequestJustRead(t *testing.T) { 234 p := clientRequestServerPair(t) 235 defer p.Close() 236 _, err := putTestFile(p.cli, "/foo", "hello") 237 require.NoError(t, err) 238 rf, err := p.cli.Open("/foo") 239 require.NoError(t, err) 240 defer rf.Close() 241 contents := make([]byte, 5) 242 n, err := rf.Read(contents) 243 if err != nil && err != io.EOF { 244 t.Fatalf("err: %v", err) 245 } 246 assert.Equal(t, 5, n) 247 assert.Equal(t, "hello", string(contents[0:5])) 248 checkRequestServerAllocator(t, p) 249 } 250 251 func TestRequestOpenFail(t *testing.T) { 252 p := clientRequestServerPair(t) 253 defer p.Close() 254 rf, err := p.cli.Open("/foo") 255 assert.Exactly(t, os.ErrNotExist, err) 256 assert.Nil(t, rf) 257 // if we return an error the sftp client will not close the handle 258 // ensure that we close it ourself 259 assert.Len(t, p.svr.openRequests, 0) 260 checkRequestServerAllocator(t, p) 261 } 262 263 func TestRequestCreate(t *testing.T) { 264 p := clientRequestServerPair(t) 265 defer p.Close() 266 fh, err := p.cli.Create("foo") 267 require.NoError(t, err) 268 err = fh.Close() 269 assert.NoError(t, err) 270 checkRequestServerAllocator(t, p) 271 } 272 273 func TestRequestReadAndWrite(t *testing.T) { 274 p := clientRequestServerPair(t) 275 defer p.Close() 276 277 file, err := p.cli.OpenFile("/foo", os.O_RDWR|os.O_CREATE) 278 require.NoError(t, err) 279 defer file.Close() 280 281 n, err := file.Write([]byte("hello")) 282 require.NoError(t, err) 283 assert.Equal(t, 5, n) 284 285 buf := make([]byte, 4) 286 n, err = file.ReadAt(buf, 1) 287 require.NoError(t, err) 288 assert.Equal(t, 4, n) 289 assert.Equal(t, []byte{'e', 'l', 'l', 'o'}, buf) 290 291 checkRequestServerAllocator(t, p) 292 } 293 294 func TestOpenFileExclusive(t *testing.T) { 295 p := clientRequestServerPair(t) 296 defer p.Close() 297 298 // first open should work 299 file, err := p.cli.OpenFile("/foo", os.O_RDWR|os.O_CREATE|os.O_EXCL) 300 require.NoError(t, err) 301 file.Close() 302 303 // second open should return error 304 _, err = p.cli.OpenFile("/foo", os.O_RDWR|os.O_CREATE|os.O_EXCL) 305 assert.Error(t, err) 306 307 checkRequestServerAllocator(t, p) 308 } 309 310 func TestOpenFileExclusiveNoSymlinkFollowing(t *testing.T) { 311 p := clientRequestServerPair(t) 312 defer p.Close() 313 314 // make a directory 315 err := p.cli.Mkdir("/foo") 316 require.NoError(t, err) 317 318 // make a symlink to that directory 319 err = p.cli.Symlink("/foo", "/foo2") 320 require.NoError(t, err) 321 322 // with O_EXCL, we can follow directory symlinks 323 file, err := p.cli.OpenFile("/foo2/bar", os.O_RDWR|os.O_CREATE|os.O_EXCL) 324 require.NoError(t, err) 325 err = file.Close() 326 require.NoError(t, err) 327 328 // we should have created the file above; and this create should fail. 329 _, err = p.cli.OpenFile("/foo/bar", os.O_RDWR|os.O_CREATE|os.O_EXCL) 330 require.Error(t, err) 331 332 // create a dangling symlink 333 err = p.cli.Symlink("/notexist", "/bar") 334 require.NoError(t, err) 335 336 // opening a dangling symlink with O_CREATE and O_EXCL should fail, regardless of target not existing. 337 _, err = p.cli.OpenFile("/bar", os.O_RDWR|os.O_CREATE|os.O_EXCL) 338 require.Error(t, err) 339 340 checkRequestServerAllocator(t, p) 341 } 342 343 func TestRequestMkdir(t *testing.T) { 344 p := clientRequestServerPair(t) 345 defer p.Close() 346 err := p.cli.Mkdir("/foo") 347 require.NoError(t, err) 348 r := p.testHandler() 349 f, err := r.fetch("/foo") 350 require.NoError(t, err) 351 assert.True(t, f.IsDir()) 352 checkRequestServerAllocator(t, p) 353 } 354 355 func TestRequestRemove(t *testing.T) { 356 p := clientRequestServerPair(t) 357 defer p.Close() 358 _, err := putTestFile(p.cli, "/foo", "hello") 359 require.NoError(t, err) 360 r := p.testHandler() 361 _, err = r.fetch("/foo") 362 assert.NoError(t, err) 363 err = p.cli.Remove("/foo") 364 assert.NoError(t, err) 365 _, err = r.fetch("/foo") 366 assert.Equal(t, err, os.ErrNotExist) 367 checkRequestServerAllocator(t, p) 368 } 369 370 func TestRequestRename(t *testing.T) { 371 p := clientRequestServerPair(t) 372 defer p.Close() 373 374 _, err := putTestFile(p.cli, "/foo", "hello") 375 require.NoError(t, err) 376 content, err := getTestFile(p.cli, "/foo") 377 require.NoError(t, err) 378 require.Equal(t, []byte("hello"), content) 379 380 err = p.cli.Rename("/foo", "/bar") 381 require.NoError(t, err) 382 383 // file contents are now at /bar 384 content, err = getTestFile(p.cli, "/bar") 385 require.NoError(t, err) 386 require.Equal(t, []byte("hello"), content) 387 388 // /foo no longer exists 389 _, err = getTestFile(p.cli, "/foo") 390 require.Error(t, err) 391 392 _, err = putTestFile(p.cli, "/baz", "goodbye") 393 require.NoError(t, err) 394 content, err = getTestFile(p.cli, "/baz") 395 require.NoError(t, err) 396 require.Equal(t, []byte("goodbye"), content) 397 398 // SFTP-v2: SSH_FXP_RENAME may not overwrite existing files. 399 err = p.cli.Rename("/bar", "/baz") 400 require.Error(t, err) 401 402 // /bar and /baz are unchanged 403 content, err = getTestFile(p.cli, "/bar") 404 require.NoError(t, err) 405 require.Equal(t, []byte("hello"), content) 406 content, err = getTestFile(p.cli, "/baz") 407 require.NoError(t, err) 408 require.Equal(t, []byte("goodbye"), content) 409 410 // posix-rename@openssh.com extension allows overwriting existing files. 411 err = p.cli.PosixRename("/bar", "/baz") 412 require.NoError(t, err) 413 414 // /baz now has the contents of /bar 415 content, err = getTestFile(p.cli, "/baz") 416 require.NoError(t, err) 417 require.Equal(t, []byte("hello"), content) 418 419 // /bar no longer exists 420 _, err = getTestFile(p.cli, "/bar") 421 require.Error(t, err) 422 423 checkRequestServerAllocator(t, p) 424 } 425 426 func TestRequestRenameFail(t *testing.T) { 427 p := clientRequestServerPair(t) 428 defer p.Close() 429 _, err := putTestFile(p.cli, "/foo", "hello") 430 require.NoError(t, err) 431 _, err = putTestFile(p.cli, "/bar", "goodbye") 432 require.NoError(t, err) 433 err = p.cli.Rename("/foo", "/bar") 434 assert.IsType(t, &StatusError{}, err) 435 checkRequestServerAllocator(t, p) 436 } 437 438 func TestRequestStat(t *testing.T) { 439 p := clientRequestServerPair(t) 440 defer p.Close() 441 _, err := putTestFile(p.cli, "/foo", "hello") 442 require.NoError(t, err) 443 fi, err := p.cli.Stat("/foo") 444 require.NoError(t, err) 445 assert.Equal(t, "foo", fi.Name()) 446 assert.Equal(t, int64(5), fi.Size()) 447 assert.Equal(t, os.FileMode(0644), fi.Mode()) 448 assert.NoError(t, testOsSys(fi.Sys())) 449 checkRequestServerAllocator(t, p) 450 } 451 452 // NOTE: Setstat is a noop in the request server tests, but we want to test 453 // that is does nothing without crapping out. 454 func TestRequestSetstat(t *testing.T) { 455 p := clientRequestServerPair(t) 456 defer p.Close() 457 _, err := putTestFile(p.cli, "/foo", "hello") 458 require.NoError(t, err) 459 mode := os.FileMode(0644) 460 err = p.cli.Chmod("/foo", mode) 461 require.NoError(t, err) 462 fi, err := p.cli.Stat("/foo") 463 require.NoError(t, err) 464 assert.Equal(t, "foo", fi.Name()) 465 assert.Equal(t, int64(5), fi.Size()) 466 assert.Equal(t, os.FileMode(0644), fi.Mode()) 467 assert.NoError(t, testOsSys(fi.Sys())) 468 checkRequestServerAllocator(t, p) 469 } 470 471 func TestRequestFstat(t *testing.T) { 472 p := clientRequestServerPair(t) 473 defer p.Close() 474 _, err := putTestFile(p.cli, "/foo", "hello") 475 require.NoError(t, err) 476 fp, err := p.cli.Open("/foo") 477 require.NoError(t, err) 478 fi, err := fp.Stat() 479 require.NoError(t, err) 480 assert.Equal(t, "foo", fi.Name()) 481 assert.Equal(t, int64(5), fi.Size()) 482 assert.Equal(t, os.FileMode(0644), fi.Mode()) 483 assert.NoError(t, testOsSys(fi.Sys())) 484 checkRequestServerAllocator(t, p) 485 } 486 487 func TestRequestFsetstat(t *testing.T) { 488 p := clientRequestServerPair(t) 489 defer p.Close() 490 _, err := putTestFile(p.cli, "/foo", "hello") 491 require.NoError(t, err) 492 fp, err := p.cli.OpenFile("/foo", os.O_WRONLY) 493 require.NoError(t, err) 494 err = fp.Truncate(2) 495 require.NoError(t, err) 496 fi, err := fp.Stat() 497 require.NoError(t, err) 498 assert.Equal(t, fi.Name(), "foo") 499 assert.Equal(t, fi.Size(), int64(2)) 500 err = fp.Truncate(5) 501 require.NoError(t, err) 502 fi, err = fp.Stat() 503 require.NoError(t, err) 504 assert.Equal(t, fi.Name(), "foo") 505 assert.Equal(t, fi.Size(), int64(5)) 506 err = fp.Close() 507 assert.NoError(t, err) 508 rf, err := p.cli.Open("/foo") 509 assert.NoError(t, err) 510 defer rf.Close() 511 contents := make([]byte, 20) 512 n, err := rf.Read(contents) 513 assert.EqualError(t, err, io.EOF.Error()) 514 assert.Equal(t, 5, n) 515 assert.Equal(t, []byte{'h', 'e', 0, 0, 0}, contents[0:n]) 516 checkRequestServerAllocator(t, p) 517 } 518 519 func TestRequestStatFail(t *testing.T) { 520 p := clientRequestServerPair(t) 521 defer p.Close() 522 fi, err := p.cli.Stat("/foo") 523 assert.Nil(t, fi) 524 assert.True(t, os.IsNotExist(err)) 525 checkRequestServerAllocator(t, p) 526 } 527 528 func TestRequestLstat(t *testing.T) { 529 p := clientRequestServerPair(t) 530 defer p.Close() 531 _, err := putTestFile(p.cli, "/foo", "hello") 532 require.NoError(t, err) 533 err = p.cli.Symlink("/foo", "/bar") 534 require.NoError(t, err) 535 fi, err := p.cli.Lstat("/bar") 536 require.NoError(t, err) 537 assert.True(t, fi.Mode()&os.ModeSymlink == os.ModeSymlink) 538 checkRequestServerAllocator(t, p) 539 } 540 541 func TestRequestLink(t *testing.T) { 542 p := clientRequestServerPair(t) 543 defer p.Close() 544 545 _, err := putTestFile(p.cli, "/foo", "hello") 546 require.NoError(t, err) 547 548 err = p.cli.Link("/foo", "/bar") 549 require.NoError(t, err) 550 551 content, err := getTestFile(p.cli, "/bar") 552 assert.NoError(t, err) 553 assert.Equal(t, []byte("hello"), content) 554 555 checkRequestServerAllocator(t, p) 556 } 557 558 func TestRequestLinkFail(t *testing.T) { 559 p := clientRequestServerPair(t) 560 defer p.Close() 561 err := p.cli.Link("/foo", "/bar") 562 t.Log(err) 563 assert.True(t, os.IsNotExist(err)) 564 checkRequestServerAllocator(t, p) 565 } 566 567 func TestRequestSymlink(t *testing.T) { 568 p := clientRequestServerPair(t) 569 defer p.Close() 570 571 const CONTENT_FOO = "hello" 572 const CONTENT_DIR_FILE_TXT = "file" 573 const CONTENT_SUB_FILE_TXT = "file-in-sub" 574 575 // prepare all files 576 _, err := putTestFile(p.cli, "/foo", CONTENT_FOO) 577 require.NoError(t, err) 578 err = p.cli.Mkdir("/dir") 579 require.NoError(t, err) 580 err = p.cli.Mkdir("/dir/sub") 581 require.NoError(t, err) 582 _, err = putTestFile(p.cli, "/dir/file.txt", CONTENT_DIR_FILE_TXT) 583 require.NoError(t, err) 584 _, err = putTestFile(p.cli, "/dir/sub/file-in-sub.txt", CONTENT_SUB_FILE_TXT) 585 require.NoError(t, err) 586 587 type symlink struct { 588 name string // this is the filename of the symbolic link 589 target string // this is the file or directory the link points to 590 591 //for testing 592 expectsNotExist bool 593 expectedFileContent string 594 } 595 596 symlinks := []symlink{ 597 {name: "/bar", target: "/foo", expectedFileContent: CONTENT_FOO}, 598 {name: "/baz", target: "/bar", expectedFileContent: CONTENT_FOO}, 599 {name: "/link-to-non-existent-file", target: "non-existent-file", expectsNotExist: true}, 600 {name: "/dir/rel-link.txt", target: "file.txt", expectedFileContent: CONTENT_DIR_FILE_TXT}, 601 {name: "/dir/abs-link.txt", target: "/dir/file.txt", expectedFileContent: CONTENT_DIR_FILE_TXT}, 602 {name: "/dir/rel-subdir-link.txt", target: "sub/file-in-sub.txt", expectedFileContent: CONTENT_SUB_FILE_TXT}, 603 {name: "/dir/abs-subdir-link.txt", target: "/dir/sub/file-in-sub.txt", expectedFileContent: CONTENT_SUB_FILE_TXT}, 604 {name: "/dir/sub/parentdir-link.txt", target: "../file.txt", expectedFileContent: CONTENT_DIR_FILE_TXT}, 605 } 606 607 for _, s := range symlinks { 608 err := p.cli.Symlink(s.target, s.name) 609 require.NoError(t, err, "Creating symlink %q with target %q failed", s.name, s.target) 610 611 rl, err := p.cli.ReadLink(s.name) 612 require.NoError(t, err, "ReadLink(%q) failed", s.name) 613 require.Equal(t, s.target, rl, "Unexpected result when reading symlink %q", s.name) 614 } 615 616 // test fetching via symlink 617 r := p.testHandler() 618 619 for _, s := range symlinks { 620 fi, err := r.lfetch(s.name) 621 require.NoError(t, err, "lfetch(%q) failed", s.name) 622 require.True(t, fi.Mode()&os.ModeSymlink == os.ModeSymlink, "Expected %q to be a symlink but it is not.", s.name) 623 624 content, err := getTestFile(p.cli, s.name) 625 if s.expectsNotExist { 626 require.True(t, os.IsNotExist(err), "Reading symlink %q expected os.ErrNotExist", s.name) 627 } else { 628 require.NoError(t, err, "getTestFile(%q) failed", s.name) 629 require.Equal(t, []byte(s.expectedFileContent), content, "Reading symlink %q returned unexpected content", s.name) 630 } 631 } 632 633 checkRequestServerAllocator(t, p) 634 } 635 636 func TestRequestSymlinkLoop(t *testing.T) { 637 p := clientRequestServerPair(t) 638 defer p.Close() 639 640 err := p.cli.Symlink("/foo", "/bar") 641 require.NoError(t, err) 642 err = p.cli.Symlink("/bar", "/baz") 643 require.NoError(t, err) 644 err = p.cli.Symlink("/baz", "/foo") 645 require.NoError(t, err) 646 647 // test should fail if we reach this point 648 timer := time.NewTimer(1 * time.Second) 649 defer timer.Stop() 650 651 var content []byte 652 653 done := make(chan struct{}) 654 go func() { 655 defer close(done) 656 657 content, err = getTestFile(p.cli, "/bar") 658 }() 659 660 select { 661 case <-timer.C: 662 t.Fatal("symlink loop following timed out") 663 return // just to let the compiler be absolutely sure 664 665 case <-done: 666 } 667 668 assert.Error(t, err) 669 assert.Len(t, content, 0) 670 671 checkRequestServerAllocator(t, p) 672 } 673 674 func TestRequestSymlinkDanglingFiles(t *testing.T) { 675 p := clientRequestServerPair(t) 676 defer p.Close() 677 678 // dangling links are ok. We will use "/foo" later. 679 err := p.cli.Symlink("/foo", "/bar") 680 require.NoError(t, err) 681 682 // creating a symlink in a non-existent directory should fail. 683 err = p.cli.Symlink("/dangle", "/foo/bar") 684 require.Error(t, err) 685 686 // creating a symlink under a dangling symlink should fail. 687 err = p.cli.Symlink("/dangle", "/bar/bar") 688 require.Error(t, err) 689 690 // opening a dangling link without O_CREATE should fail with os.IsNotExist == true 691 _, err = p.cli.OpenFile("/bar", os.O_RDONLY) 692 require.True(t, os.IsNotExist(err)) 693 694 // overwriting a symlink is not allowed. 695 err = p.cli.Symlink("/dangle", "/bar") 696 require.Error(t, err) 697 698 // double symlink 699 err = p.cli.Symlink("/bar", "/baz") 700 require.NoError(t, err) 701 702 // opening a dangling link with O_CREATE should work. 703 _, err = putTestFile(p.cli, "/baz", "hello") 704 require.NoError(t, err) 705 706 // dangling link creation should create the target file itself. 707 content, err := getTestFile(p.cli, "/foo") 708 require.NoError(t, err) 709 assert.Equal(t, []byte("hello"), content) 710 711 // creating a symlink under a non-directory file should fail. 712 err = p.cli.Symlink("/dangle", "/foo/bar") 713 assert.Error(t, err) 714 715 checkRequestServerAllocator(t, p) 716 } 717 718 func TestRequestSymlinkDanglingDirectories(t *testing.T) { 719 p := clientRequestServerPair(t) 720 defer p.Close() 721 722 // dangling links are ok. We will use "/foo" later. 723 err := p.cli.Symlink("/foo", "/bar") 724 require.NoError(t, err) 725 726 // reading from a dangling symlink should fail. 727 _, err = p.cli.ReadDir("/bar") 728 require.True(t, os.IsNotExist(err)) 729 730 // making a directory on a dangling symlink SHOULD NOT work. 731 err = p.cli.Mkdir("/bar") 732 require.Error(t, err) 733 734 // ok, now make directory, so we can test make files through the symlink. 735 err = p.cli.Mkdir("/foo") 736 require.NoError(t, err) 737 738 // should be able to make a file in that symlinked directory. 739 _, err = putTestFile(p.cli, "/bar/baz", "hello") 740 require.NoError(t, err) 741 742 // dangling directory creation should create the target directory itself. 743 content, err := getTestFile(p.cli, "/foo/baz") 744 assert.NoError(t, err) 745 assert.Equal(t, []byte("hello"), content) 746 747 checkRequestServerAllocator(t, p) 748 } 749 750 func TestRequestReadlink(t *testing.T) { 751 p := clientRequestServerPair(t) 752 defer p.Close() 753 _, err := putTestFile(p.cli, "/foo", "hello") 754 require.NoError(t, err) 755 err = p.cli.Symlink("/foo", "/bar") 756 require.NoError(t, err) 757 758 rl, err := p.cli.ReadLink("/bar") 759 assert.NoError(t, err) 760 assert.Equal(t, "/foo", rl) 761 762 _, err = p.cli.ReadLink("/foo") 763 assert.Error(t, err, "Readlink on non-symlink should fail") 764 765 _, err = p.cli.ReadLink("/does-not-exist") 766 assert.Error(t, err, "Readlink on non-existent file should fail") 767 768 checkRequestServerAllocator(t, p) 769 } 770 771 func TestRequestReaddir(t *testing.T) { 772 p := clientRequestServerPair(t) 773 MaxFilelist = 22 // make not divisible by our test amount (100) 774 defer p.Close() 775 for i := 0; i < 100; i++ { 776 fname := fmt.Sprintf("/foo_%02d", i) 777 _, err := putTestFile(p.cli, fname, fname) 778 if err != nil { 779 t.Fatal("expected no error, got:", err) 780 } 781 } 782 _, err := p.cli.ReadDir("/foo_01") 783 assert.Equal(t, &StatusError{Code: sshFxFailure, 784 msg: " /foo_01: not a directory"}, err) 785 _, err = p.cli.ReadDir("/does_not_exist") 786 assert.Equal(t, os.ErrNotExist, err) 787 di, err := p.cli.ReadDir("/") 788 require.NoError(t, err) 789 require.Len(t, di, 100) 790 names := []string{di[18].Name(), di[81].Name()} 791 assert.Equal(t, []string{"foo_18", "foo_81"}, names) 792 assert.Len(t, p.svr.openRequests, 0) 793 checkRequestServerAllocator(t, p) 794 } 795 796 func TestRequestStatVFS(t *testing.T) { 797 if runtime.GOOS != "linux" && runtime.GOOS != "darwin" { 798 t.Skip("StatVFS is implemented on linux and darwin") 799 } 800 801 p := clientRequestServerPair(t) 802 defer p.Close() 803 804 _, ok := p.cli.HasExtension("statvfs@openssh.com") 805 require.True(t, ok, "request server doesn't list statvfs extension") 806 vfs, err := p.cli.StatVFS("/") 807 require.NoError(t, err) 808 expected, err := getStatVFSForPath("/") 809 require.NoError(t, err) 810 require.NotEqual(t, 0, expected.ID) 811 // check some stats 812 require.Equal(t, expected.Bavail, vfs.Bavail) 813 require.Equal(t, expected.Bfree, vfs.Bfree) 814 require.Equal(t, expected.Blocks, vfs.Blocks) 815 816 checkRequestServerAllocator(t, p) 817 } 818 819 func TestRequestStatVFSError(t *testing.T) { 820 if runtime.GOOS != "linux" && runtime.GOOS != "darwin" { 821 t.Skip("StatVFS is implemented on linux and darwin") 822 } 823 824 p := clientRequestServerPair(t) 825 defer p.Close() 826 827 _, err := p.cli.StatVFS("a missing path") 828 require.Error(t, err) 829 require.True(t, os.IsNotExist(err)) 830 831 checkRequestServerAllocator(t, p) 832 } 833 834 func TestRequestStartDirOption(t *testing.T) { 835 startDir := "/start/dir" 836 p := clientRequestServerPair(t, WithStartDirectory(startDir)) 837 defer p.Close() 838 839 // create the start directory 840 err := p.cli.MkdirAll(startDir) 841 require.NoError(t, err) 842 // the working directory must be the defined start directory 843 wd, err := p.cli.Getwd() 844 require.NoError(t, err) 845 require.Equal(t, startDir, wd) 846 // upload a file using a relative path, it must be uploaded to the start directory 847 fileName := "file.txt" 848 _, err = putTestFile(p.cli, fileName, "") 849 require.NoError(t, err) 850 // we must be able to stat the file using both a relative and an absolute path 851 for _, filePath := range []string{fileName, path.Join(startDir, fileName)} { 852 fi, err := p.cli.Stat(filePath) 853 require.NoError(t, err) 854 assert.Equal(t, fileName, fi.Name()) 855 } 856 // list dir contents using a relative path 857 entries, err := p.cli.ReadDir(".") 858 assert.NoError(t, err) 859 assert.Len(t, entries, 1) 860 // delete the file using a relative path 861 err = p.cli.Remove(fileName) 862 assert.NoError(t, err) 863 } 864 865 func TestCleanDisconnect(t *testing.T) { 866 p := clientRequestServerPair(t) 867 defer p.Close() 868 869 err := p.cli.conn.Close() 870 require.NoError(t, err) 871 // server must return io.EOF after a clean client close 872 // with no pending open requests 873 err = <-p.svrResult 874 require.EqualError(t, err, io.EOF.Error()) 875 checkRequestServerAllocator(t, p) 876 } 877 878 func TestUncleanDisconnect(t *testing.T) { 879 p := clientRequestServerPair(t) 880 defer p.Close() 881 882 foo := NewRequest("", "foo") 883 p.svr.nextRequest(foo) 884 err := p.cli.conn.Close() 885 require.NoError(t, err) 886 // the foo request above is still open after the client disconnects 887 // so the server will convert io.EOF to io.ErrUnexpectedEOF 888 err = <-p.svrResult 889 require.EqualError(t, err, io.ErrUnexpectedEOF.Error()) 890 checkRequestServerAllocator(t, p) 891 } 892 893 func TestRealPath(t *testing.T) { 894 startDir := "/startdir" 895 // the default InMemHandler does not implement the RealPathFileLister interface 896 // so we are using the builtin implementation here 897 p := clientRequestServerPair(t, WithStartDirectory(startDir)) 898 defer p.Close() 899 900 realPath, err := p.cli.RealPath(".") 901 require.NoError(t, err) 902 assert.Equal(t, startDir, realPath) 903 realPath, err = p.cli.RealPath("/") 904 require.NoError(t, err) 905 assert.Equal(t, "/", realPath) 906 realPath, err = p.cli.RealPath("..") 907 require.NoError(t, err) 908 assert.Equal(t, "/", realPath) 909 realPath, err = p.cli.RealPath("../../..") 910 require.NoError(t, err) 911 assert.Equal(t, "/", realPath) 912 // test a relative path 913 realPath, err = p.cli.RealPath("relpath") 914 require.NoError(t, err) 915 assert.Equal(t, path.Join(startDir, "relpath"), realPath) 916 } 917 918 // In memory file-system which implements RealPathFileLister 919 type rootWithRealPather struct { 920 root 921 } 922 923 // implements RealpathFileLister interface 924 func (fs *rootWithRealPather) RealPath(p string) (string, error) { 925 if fs.mockErr != nil { 926 return "", fs.mockErr 927 } 928 return cleanPath(p), nil 929 } 930 931 func TestRealPathFileLister(t *testing.T) { 932 root := &rootWithRealPather{ 933 root: root{ 934 rootFile: &memFile{name: "/", modtime: time.Now(), isdir: true}, 935 files: make(map[string]*memFile), 936 }, 937 } 938 handlers := Handlers{root, root, root, root} 939 p := clientRequestServerPairWithHandlers(t, handlers) 940 defer p.Close() 941 942 realPath, err := p.cli.RealPath(".") 943 require.NoError(t, err) 944 assert.Equal(t, "/", realPath) 945 realPath, err = p.cli.RealPath("relpath") 946 require.NoError(t, err) 947 assert.Equal(t, "/relpath", realPath) 948 // test an error 949 root.returnErr(ErrSSHFxPermissionDenied) 950 _, err = p.cli.RealPath("/") 951 require.ErrorIs(t, err, os.ErrPermission) 952 } 953 954 // In memory file-system which implements legacyRealPathFileLister 955 type rootWithLegacyRealPather struct { 956 root 957 } 958 959 // implements RealpathFileLister interface 960 func (fs *rootWithLegacyRealPather) RealPath(p string) string { 961 return cleanPath(p) 962 } 963 964 func TestLegacyRealPathFileLister(t *testing.T) { 965 root := &rootWithLegacyRealPather{ 966 root: root{ 967 rootFile: &memFile{name: "/", modtime: time.Now(), isdir: true}, 968 files: make(map[string]*memFile), 969 }, 970 } 971 handlers := Handlers{root, root, root, root} 972 p := clientRequestServerPairWithHandlers(t, handlers) 973 defer p.Close() 974 975 realPath, err := p.cli.RealPath(".") 976 require.NoError(t, err) 977 assert.Equal(t, "/", realPath) 978 realPath, err = p.cli.RealPath("..") 979 require.NoError(t, err) 980 assert.Equal(t, "/", realPath) 981 realPath, err = p.cli.RealPath("relpath") 982 require.NoError(t, err) 983 assert.Equal(t, "/relpath", realPath) 984 } 985 986 func TestCleanPath(t *testing.T) { 987 assert.Equal(t, "/", cleanPath("/")) 988 assert.Equal(t, "/", cleanPath(".")) 989 assert.Equal(t, "/", cleanPath("")) 990 assert.Equal(t, "/", cleanPath("/.")) 991 assert.Equal(t, "/", cleanPath("/a/..")) 992 assert.Equal(t, "/a/c", cleanPath("/a/b/../c")) 993 assert.Equal(t, "/a/c", cleanPath("/a/b/../c/")) 994 assert.Equal(t, "/a", cleanPath("/a/b/..")) 995 assert.Equal(t, "/a/b/c", cleanPath("/a/b/c")) 996 assert.Equal(t, "/", cleanPath("//")) 997 assert.Equal(t, "/a", cleanPath("/a/")) 998 assert.Equal(t, "/a", cleanPath("a/")) 999 assert.Equal(t, "/a/b/c", cleanPath("/a//b//c/")) 1000 1001 // filepath.ToSlash does not touch \ as char on unix systems 1002 // so os.PathSeparator is used for windows compatible tests 1003 bslash := string(os.PathSeparator) 1004 assert.Equal(t, "/", cleanPath(bslash)) 1005 assert.Equal(t, "/", cleanPath(bslash+bslash)) 1006 assert.Equal(t, "/a", cleanPath(bslash+"a"+bslash)) 1007 assert.Equal(t, "/a", cleanPath("a"+bslash)) 1008 assert.Equal(t, "/a/b/c", 1009 cleanPath(bslash+"a"+bslash+bslash+"b"+bslash+bslash+"c"+bslash)) 1010 assert.Equal(t, "/C:/a", cleanPath("C:"+bslash+"a")) 1011 }