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  }