mirror of https://github.com/pkg/sftp.git
				
				
				
			
		
			
				
	
	
		
			2886 lines
		
	
	
		
			62 KiB
		
	
	
	
		
			Go
		
	
	
	
			
		
		
	
	
			2886 lines
		
	
	
		
			62 KiB
		
	
	
	
		
			Go
		
	
	
	
| package sftp
 | ||
| 
 | ||
| // sftp integration tests
 | ||
| // enable with -integration
 | ||
| 
 | ||
| import (
 | ||
| 	"bytes"
 | ||
| 	"crypto/sha1"
 | ||
| 	"errors"
 | ||
| 	"fmt"
 | ||
| 	"io"
 | ||
| 	"io/ioutil"
 | ||
| 	"math/rand"
 | ||
| 	"net"
 | ||
| 	"os"
 | ||
| 	"os/exec"
 | ||
| 	"os/user"
 | ||
| 	"path"
 | ||
| 	"path/filepath"
 | ||
| 	"reflect"
 | ||
| 	"regexp"
 | ||
| 	"runtime"
 | ||
| 	"sort"
 | ||
| 	"strconv"
 | ||
| 	"sync"
 | ||
| 	"testing"
 | ||
| 	"testing/quick"
 | ||
| 	"time"
 | ||
| 
 | ||
| 	"github.com/kr/fs"
 | ||
| 	"github.com/stretchr/testify/assert"
 | ||
| 	"github.com/stretchr/testify/require"
 | ||
| )
 | ||
| 
 | ||
| const (
 | ||
| 	READONLY                = true
 | ||
| 	READWRITE               = false
 | ||
| 	NODELAY   time.Duration = 0
 | ||
| 
 | ||
| 	debuglevel = "ERROR" // set to "DEBUG" for debugging
 | ||
| )
 | ||
| 
 | ||
| type delayedWrite struct {
 | ||
| 	t time.Time
 | ||
| 	b []byte
 | ||
| }
 | ||
| 
 | ||
| // delayedWriter wraps a writer and artificially delays the write. This is
 | ||
| // meant to mimic connections with various latencies. Error's returned from the
 | ||
| // underlying writer will panic so this should only be used over reliable
 | ||
| // connections.
 | ||
| type delayedWriter struct {
 | ||
| 	closed chan struct{}
 | ||
| 
 | ||
| 	mu      sync.Mutex
 | ||
| 	ch      chan delayedWrite
 | ||
| 	closing chan struct{}
 | ||
| }
 | ||
| 
 | ||
| func newDelayedWriter(w io.WriteCloser, delay time.Duration) io.WriteCloser {
 | ||
| 	dw := &delayedWriter{
 | ||
| 		ch:      make(chan delayedWrite, 128),
 | ||
| 		closed:  make(chan struct{}),
 | ||
| 		closing: make(chan struct{}),
 | ||
| 	}
 | ||
| 
 | ||
| 	go func() {
 | ||
| 		defer close(dw.closed)
 | ||
| 		defer w.Close()
 | ||
| 
 | ||
| 		for writeMsg := range dw.ch {
 | ||
| 			time.Sleep(time.Until(writeMsg.t.Add(delay)))
 | ||
| 
 | ||
| 			n, err := w.Write(writeMsg.b)
 | ||
| 			if err != nil {
 | ||
| 				panic("write error")
 | ||
| 			}
 | ||
| 
 | ||
| 			if n < len(writeMsg.b) {
 | ||
| 				panic("showrt write")
 | ||
| 			}
 | ||
| 		}
 | ||
| 	}()
 | ||
| 
 | ||
| 	return dw
 | ||
| }
 | ||
| 
 | ||
| func (dw *delayedWriter) Write(b []byte) (int, error) {
 | ||
| 	dw.mu.Lock()
 | ||
| 	defer dw.mu.Unlock()
 | ||
| 
 | ||
| 	write := delayedWrite{
 | ||
| 		t: time.Now(),
 | ||
| 		b: append([]byte(nil), b...),
 | ||
| 	}
 | ||
| 
 | ||
| 	select {
 | ||
| 	case <-dw.closing:
 | ||
| 		return 0, errors.New("delayedWriter is closing")
 | ||
| 	case dw.ch <- write:
 | ||
| 	}
 | ||
| 
 | ||
| 	return len(b), nil
 | ||
| }
 | ||
| 
 | ||
| func (dw *delayedWriter) Close() error {
 | ||
| 	dw.mu.Lock()
 | ||
| 	defer dw.mu.Unlock()
 | ||
| 
 | ||
| 	select {
 | ||
| 	case <-dw.closing:
 | ||
| 	default:
 | ||
| 		close(dw.ch)
 | ||
| 		close(dw.closing)
 | ||
| 	}
 | ||
| 
 | ||
| 	<-dw.closed
 | ||
| 	return nil
 | ||
| }
 | ||
| 
 | ||
| // netPipe provides a pair of io.ReadWriteClosers connected to each other.
 | ||
| // The functions is identical to os.Pipe with the exception that netPipe
 | ||
| // provides the Read/Close guarantees that os.File derrived pipes do not.
 | ||
| func netPipe(t testing.TB) (io.ReadWriteCloser, io.ReadWriteCloser) {
 | ||
| 	type result struct {
 | ||
| 		net.Conn
 | ||
| 		error
 | ||
| 	}
 | ||
| 
 | ||
| 	l, err := net.Listen("tcp", "127.0.0.1:0")
 | ||
| 	if err != nil {
 | ||
| 		t.Fatal(err)
 | ||
| 	}
 | ||
| 
 | ||
| 	closeListener := make(chan struct{}, 1)
 | ||
| 	closeListener <- struct{}{}
 | ||
| 
 | ||
| 	ch := make(chan result, 1)
 | ||
| 	go func() {
 | ||
| 		conn, err := l.Accept()
 | ||
| 		ch <- result{conn, err}
 | ||
| 
 | ||
| 		if _, ok := <-closeListener; ok {
 | ||
| 			err = l.Close()
 | ||
| 			if err != nil {
 | ||
| 				t.Error(err)
 | ||
| 			}
 | ||
| 			close(closeListener)
 | ||
| 		}
 | ||
| 	}()
 | ||
| 
 | ||
| 	c1, err := net.Dial("tcp", l.Addr().String())
 | ||
| 	if err != nil {
 | ||
| 		if _, ok := <-closeListener; ok {
 | ||
| 			l.Close()
 | ||
| 			close(closeListener)
 | ||
| 		}
 | ||
| 		t.Fatal(err)
 | ||
| 	}
 | ||
| 
 | ||
| 	r := <-ch
 | ||
| 	if r.error != nil {
 | ||
| 		t.Fatal(err)
 | ||
| 	}
 | ||
| 
 | ||
| 	return c1, r.Conn
 | ||
| }
 | ||
| 
 | ||
| func testClientGoSvr(t testing.TB, readonly bool, delay time.Duration, opts ...ClientOption) (*Client, *exec.Cmd) {
 | ||
| 	c1, c2 := netPipe(t)
 | ||
| 
 | ||
| 	options := []ServerOption{WithDebug(os.Stderr)}
 | ||
| 	if readonly {
 | ||
| 		options = append(options, ReadOnly())
 | ||
| 	}
 | ||
| 
 | ||
| 	server, err := NewServer(c1, options...)
 | ||
| 	if err != nil {
 | ||
| 		t.Fatal(err)
 | ||
| 	}
 | ||
| 	go server.Serve()
 | ||
| 
 | ||
| 	var wr io.WriteCloser = c2
 | ||
| 	if delay > NODELAY {
 | ||
| 		wr = newDelayedWriter(wr, delay)
 | ||
| 	}
 | ||
| 
 | ||
| 	client, err := NewClientPipe(c2, wr, opts...)
 | ||
| 	if err != nil {
 | ||
| 		t.Fatal(err)
 | ||
| 	}
 | ||
| 
 | ||
| 	// dummy command...
 | ||
| 	return client, exec.Command("true")
 | ||
| }
 | ||
| 
 | ||
| // testClient returns a *Client connected to a locally running sftp-server
 | ||
| // the *exec.Cmd returned must be defer Wait'd.
 | ||
| func testClient(t testing.TB, readonly bool, delay time.Duration, opts ...ClientOption) (*Client, *exec.Cmd) {
 | ||
| 	if !*testIntegration {
 | ||
| 		t.Skip("skipping integration test")
 | ||
| 	}
 | ||
| 
 | ||
| 	if *testServerImpl {
 | ||
| 		return testClientGoSvr(t, readonly, delay, opts...)
 | ||
| 	}
 | ||
| 
 | ||
| 	cmd := exec.Command(*testSftp, "-e", "-R", "-l", debuglevel) // log to stderr, read only
 | ||
| 	if !readonly {
 | ||
| 		cmd = exec.Command(*testSftp, "-e", "-l", debuglevel) // log to stderr
 | ||
| 	}
 | ||
| 
 | ||
| 	cmd.Stderr = os.Stdout
 | ||
| 
 | ||
| 	pw, err := cmd.StdinPipe()
 | ||
| 	if err != nil {
 | ||
| 		t.Fatal(err)
 | ||
| 	}
 | ||
| 
 | ||
| 	if delay > NODELAY {
 | ||
| 		pw = newDelayedWriter(pw, delay)
 | ||
| 	}
 | ||
| 
 | ||
| 	pr, err := cmd.StdoutPipe()
 | ||
| 	if err != nil {
 | ||
| 		t.Fatal(err)
 | ||
| 	}
 | ||
| 
 | ||
| 	if err := cmd.Start(); err != nil {
 | ||
| 		t.Skipf("could not start sftp-server process: %v", err)
 | ||
| 	}
 | ||
| 
 | ||
| 	sftp, err := NewClientPipe(pr, pw, opts...)
 | ||
| 	if err != nil {
 | ||
| 		t.Fatal(err)
 | ||
| 	}
 | ||
| 
 | ||
| 	return sftp, cmd
 | ||
| }
 | ||
| 
 | ||
| func TestNewClient(t *testing.T) {
 | ||
| 	sftp, cmd := testClient(t, READONLY, NODELAY)
 | ||
| 	defer cmd.Wait()
 | ||
| 
 | ||
| 	if err := sftp.Close(); err != nil {
 | ||
| 		t.Fatal(err)
 | ||
| 	}
 | ||
| }
 | ||
| 
 | ||
| func TestClientLstat(t *testing.T) {
 | ||
| 	sftp, cmd := testClient(t, READONLY, NODELAY)
 | ||
| 	defer cmd.Wait()
 | ||
| 	defer sftp.Close()
 | ||
| 
 | ||
| 	f, err := ioutil.TempFile("", "sftptest-lstat")
 | ||
| 	if err != nil {
 | ||
| 		t.Fatal(err)
 | ||
| 	}
 | ||
| 	f.Close()
 | ||
| 	defer os.Remove(f.Name())
 | ||
| 
 | ||
| 	want, err := os.Lstat(f.Name())
 | ||
| 	if err != nil {
 | ||
| 		t.Fatal(err)
 | ||
| 	}
 | ||
| 
 | ||
| 	got, err := sftp.Lstat(f.Name())
 | ||
| 	if err != nil {
 | ||
| 		t.Fatal(err)
 | ||
| 	}
 | ||
| 
 | ||
| 	if !sameFile(want, got) {
 | ||
| 		t.Fatalf("Lstat(%q): want %#v, got %#v", f.Name(), want, got)
 | ||
| 	}
 | ||
| }
 | ||
| 
 | ||
| func TestClientLstatIsNotExist(t *testing.T) {
 | ||
| 	sftp, cmd := testClient(t, READONLY, NODELAY)
 | ||
| 	defer cmd.Wait()
 | ||
| 	defer sftp.Close()
 | ||
| 
 | ||
| 	f, err := ioutil.TempFile("", "sftptest-lstatisnotexist")
 | ||
| 	if err != nil {
 | ||
| 		t.Fatal(err)
 | ||
| 	}
 | ||
| 	f.Close()
 | ||
| 	os.Remove(f.Name())
 | ||
| 
 | ||
| 	if _, err := sftp.Lstat(f.Name()); !os.IsNotExist(err) {
 | ||
| 		t.Errorf("os.IsNotExist(%v) = false, want true", err)
 | ||
| 	}
 | ||
| }
 | ||
| 
 | ||
| func TestClientMkdir(t *testing.T) {
 | ||
| 	sftp, cmd := testClient(t, READWRITE, NODELAY)
 | ||
| 	defer cmd.Wait()
 | ||
| 	defer sftp.Close()
 | ||
| 
 | ||
| 	dir, err := ioutil.TempDir("", "sftptest-mkdir")
 | ||
| 	if err != nil {
 | ||
| 		t.Fatal(err)
 | ||
| 	}
 | ||
| 	defer os.RemoveAll(dir)
 | ||
| 
 | ||
| 	sub := path.Join(dir, "mkdir1")
 | ||
| 	if err := sftp.Mkdir(sub); err != nil {
 | ||
| 		t.Fatal(err)
 | ||
| 	}
 | ||
| 	if _, err := os.Lstat(sub); err != nil {
 | ||
| 		t.Fatal(err)
 | ||
| 	}
 | ||
| }
 | ||
| func TestClientMkdirAll(t *testing.T) {
 | ||
| 	sftp, cmd := testClient(t, READWRITE, NODELAY)
 | ||
| 	defer cmd.Wait()
 | ||
| 	defer sftp.Close()
 | ||
| 
 | ||
| 	dir, err := ioutil.TempDir("", "sftptest-mkdirall")
 | ||
| 	if err != nil {
 | ||
| 		t.Fatal(err)
 | ||
| 	}
 | ||
| 	defer os.RemoveAll(dir)
 | ||
| 
 | ||
| 	sub := path.Join(dir, "mkdir1", "mkdir2", "mkdir3")
 | ||
| 	if err := sftp.MkdirAll(sub); err != nil {
 | ||
| 		t.Fatal(err)
 | ||
| 	}
 | ||
| 	info, err := os.Lstat(sub)
 | ||
| 	if err != nil {
 | ||
| 		t.Fatal(err)
 | ||
| 	}
 | ||
| 	if !info.IsDir() {
 | ||
| 		t.Fatalf("Expected mkdirall to create dir at: %s", sub)
 | ||
| 	}
 | ||
| }
 | ||
| 
 | ||
| func TestClientOpen(t *testing.T) {
 | ||
| 	sftp, cmd := testClient(t, READONLY, NODELAY)
 | ||
| 	defer cmd.Wait()
 | ||
| 	defer sftp.Close()
 | ||
| 
 | ||
| 	f, err := ioutil.TempFile("", "sftptest-open")
 | ||
| 	if err != nil {
 | ||
| 		t.Fatal(err)
 | ||
| 	}
 | ||
| 	f.Close()
 | ||
| 	defer os.Remove(f.Name())
 | ||
| 
 | ||
| 	got, err := sftp.Open(f.Name())
 | ||
| 	if err != nil {
 | ||
| 		t.Fatal(err)
 | ||
| 	}
 | ||
| 	if err := got.Close(); err != nil {
 | ||
| 		t.Fatal(err)
 | ||
| 	}
 | ||
| }
 | ||
| 
 | ||
| func TestClientOpenIsNotExist(t *testing.T) {
 | ||
| 	sftp, cmd := testClient(t, READONLY, NODELAY)
 | ||
| 	defer cmd.Wait()
 | ||
| 	defer sftp.Close()
 | ||
| 
 | ||
| 	if _, err := sftp.Open("/doesnt/exist"); !os.IsNotExist(err) {
 | ||
| 		t.Errorf("os.IsNotExist(%v) = false, want true", err)
 | ||
| 	}
 | ||
| }
 | ||
| 
 | ||
| func TestClientStatIsNotExist(t *testing.T) {
 | ||
| 	sftp, cmd := testClient(t, READONLY, NODELAY)
 | ||
| 	defer cmd.Wait()
 | ||
| 	defer sftp.Close()
 | ||
| 
 | ||
| 	if _, err := sftp.Stat("/doesnt/exist"); !os.IsNotExist(err) {
 | ||
| 		t.Errorf("os.IsNotExist(%v) = false, want true", err)
 | ||
| 	}
 | ||
| }
 | ||
| 
 | ||
| const seekBytes = 128 * 1024
 | ||
| 
 | ||
| type seek struct {
 | ||
| 	offset int64
 | ||
| }
 | ||
| 
 | ||
| func (s seek) Generate(r *rand.Rand, _ int) reflect.Value {
 | ||
| 	s.offset = int64(r.Int31n(seekBytes))
 | ||
| 	return reflect.ValueOf(s)
 | ||
| }
 | ||
| 
 | ||
| func (s seek) set(t *testing.T, r io.ReadSeeker) {
 | ||
| 	if _, err := r.Seek(s.offset, io.SeekStart); err != nil {
 | ||
| 		t.Fatalf("error while seeking with %+v: %v", s, err)
 | ||
| 	}
 | ||
| }
 | ||
| 
 | ||
| func (s seek) current(t *testing.T, r io.ReadSeeker) {
 | ||
| 	const mid = seekBytes / 2
 | ||
| 
 | ||
| 	skip := s.offset / 2
 | ||
| 	if s.offset > mid {
 | ||
| 		skip = -skip
 | ||
| 	}
 | ||
| 
 | ||
| 	if _, err := r.Seek(mid, io.SeekStart); err != nil {
 | ||
| 		t.Fatalf("error seeking to midpoint with %+v: %v", s, err)
 | ||
| 	}
 | ||
| 	if _, err := r.Seek(skip, io.SeekCurrent); err != nil {
 | ||
| 		t.Fatalf("error seeking from %d with %+v: %v", mid, s, err)
 | ||
| 	}
 | ||
| }
 | ||
| 
 | ||
| func (s seek) end(t *testing.T, r io.ReadSeeker) {
 | ||
| 	if _, err := r.Seek(-s.offset, io.SeekEnd); err != nil {
 | ||
| 		t.Fatalf("error seeking from end with %+v: %v", s, err)
 | ||
| 	}
 | ||
| }
 | ||
| 
 | ||
| func TestClientSeek(t *testing.T) {
 | ||
| 	sftp, cmd := testClient(t, READONLY, NODELAY)
 | ||
| 	defer cmd.Wait()
 | ||
| 	defer sftp.Close()
 | ||
| 
 | ||
| 	fOS, err := ioutil.TempFile("", "sftptest-seek")
 | ||
| 	if err != nil {
 | ||
| 		t.Fatal(err)
 | ||
| 	}
 | ||
| 	defer os.Remove(fOS.Name())
 | ||
| 	defer fOS.Close()
 | ||
| 
 | ||
| 	fSFTP, err := sftp.Open(fOS.Name())
 | ||
| 	if err != nil {
 | ||
| 		t.Fatal(err)
 | ||
| 	}
 | ||
| 	defer fSFTP.Close()
 | ||
| 
 | ||
| 	writeN(t, fOS, seekBytes)
 | ||
| 
 | ||
| 	if err := quick.CheckEqual(
 | ||
| 		func(s seek) (string, int64) { s.set(t, fOS); return readHash(t, fOS) },
 | ||
| 		func(s seek) (string, int64) { s.set(t, fSFTP); return readHash(t, fSFTP) },
 | ||
| 		nil,
 | ||
| 	); err != nil {
 | ||
| 		t.Errorf("Seek: expected equal absolute seeks: %v", err)
 | ||
| 	}
 | ||
| 
 | ||
| 	if err := quick.CheckEqual(
 | ||
| 		func(s seek) (string, int64) { s.current(t, fOS); return readHash(t, fOS) },
 | ||
| 		func(s seek) (string, int64) { s.current(t, fSFTP); return readHash(t, fSFTP) },
 | ||
| 		nil,
 | ||
| 	); err != nil {
 | ||
| 		t.Errorf("Seek: expected equal seeks from middle: %v", err)
 | ||
| 	}
 | ||
| 
 | ||
| 	if err := quick.CheckEqual(
 | ||
| 		func(s seek) (string, int64) { s.end(t, fOS); return readHash(t, fOS) },
 | ||
| 		func(s seek) (string, int64) { s.end(t, fSFTP); return readHash(t, fSFTP) },
 | ||
| 		nil,
 | ||
| 	); err != nil {
 | ||
| 		t.Errorf("Seek: expected equal seeks from end: %v", err)
 | ||
| 	}
 | ||
| }
 | ||
| 
 | ||
| func TestClientCreate(t *testing.T) {
 | ||
| 	sftp, cmd := testClient(t, READWRITE, NODELAY)
 | ||
| 	defer cmd.Wait()
 | ||
| 	defer sftp.Close()
 | ||
| 
 | ||
| 	f, err := ioutil.TempFile("", "sftptest-create")
 | ||
| 	if err != nil {
 | ||
| 		t.Fatal(err)
 | ||
| 	}
 | ||
| 	defer f.Close()
 | ||
| 	defer os.Remove(f.Name())
 | ||
| 
 | ||
| 	f2, err := sftp.Create(f.Name())
 | ||
| 	if err != nil {
 | ||
| 		t.Fatal(err)
 | ||
| 	}
 | ||
| 	defer f2.Close()
 | ||
| }
 | ||
| 
 | ||
| func TestClientAppend(t *testing.T) {
 | ||
| 	sftp, cmd := testClient(t, READWRITE, NODELAY)
 | ||
| 	defer cmd.Wait()
 | ||
| 	defer sftp.Close()
 | ||
| 
 | ||
| 	f, err := ioutil.TempFile("", "sftptest-append")
 | ||
| 	if err != nil {
 | ||
| 		t.Fatal(err)
 | ||
| 	}
 | ||
| 	defer f.Close()
 | ||
| 	defer os.Remove(f.Name())
 | ||
| 
 | ||
| 	f2, err := sftp.OpenFile(f.Name(), os.O_RDWR|os.O_APPEND)
 | ||
| 	if err != nil {
 | ||
| 		t.Fatal(err)
 | ||
| 	}
 | ||
| 	defer f2.Close()
 | ||
| }
 | ||
| 
 | ||
| func TestClientCreateFailed(t *testing.T) {
 | ||
| 	sftp, cmd := testClient(t, READONLY, NODELAY)
 | ||
| 	defer cmd.Wait()
 | ||
| 	defer sftp.Close()
 | ||
| 
 | ||
| 	f, err := ioutil.TempFile("", "sftptest-createfailed")
 | ||
| 	require.NoError(t, err)
 | ||
| 
 | ||
| 	defer f.Close()
 | ||
| 	defer os.Remove(f.Name())
 | ||
| 
 | ||
| 	f2, err := sftp.Create(f.Name())
 | ||
| 	require.True(t, os.IsPermission(err))
 | ||
| 	if err == nil {
 | ||
| 		f2.Close()
 | ||
| 	}
 | ||
| }
 | ||
| 
 | ||
| func TestClientFileName(t *testing.T) {
 | ||
| 	sftp, cmd := testClient(t, READONLY, NODELAY)
 | ||
| 	defer cmd.Wait()
 | ||
| 	defer sftp.Close()
 | ||
| 
 | ||
| 	f, err := ioutil.TempFile("", "sftptest-filename")
 | ||
| 	if err != nil {
 | ||
| 		t.Fatal(err)
 | ||
| 	}
 | ||
| 	defer os.Remove(f.Name())
 | ||
| 
 | ||
| 	f2, err := sftp.Open(f.Name())
 | ||
| 	if err != nil {
 | ||
| 		t.Fatal(err)
 | ||
| 	}
 | ||
| 	defer f2.Close()
 | ||
| 
 | ||
| 	if got, want := f2.Name(), f.Name(); got != want {
 | ||
| 		t.Fatalf("Name: got %q want %q", want, got)
 | ||
| 	}
 | ||
| }
 | ||
| 
 | ||
| func TestClientFileStat(t *testing.T) {
 | ||
| 	sftp, cmd := testClient(t, READONLY, NODELAY)
 | ||
| 	defer cmd.Wait()
 | ||
| 	defer sftp.Close()
 | ||
| 
 | ||
| 	f, err := ioutil.TempFile("", "sftptest-filestat")
 | ||
| 	if err != nil {
 | ||
| 		t.Fatal(err)
 | ||
| 	}
 | ||
| 	defer os.Remove(f.Name())
 | ||
| 
 | ||
| 	want, err := os.Lstat(f.Name())
 | ||
| 	if err != nil {
 | ||
| 		t.Fatal(err)
 | ||
| 	}
 | ||
| 
 | ||
| 	f2, err := sftp.Open(f.Name())
 | ||
| 	if err != nil {
 | ||
| 		t.Fatal(err)
 | ||
| 	}
 | ||
| 	defer f2.Close()
 | ||
| 
 | ||
| 	got, err := f2.Stat()
 | ||
| 	if err != nil {
 | ||
| 		t.Fatal(err)
 | ||
| 	}
 | ||
| 
 | ||
| 	if !sameFile(want, got) {
 | ||
| 		t.Fatalf("Lstat(%q): want %#v, got %#v", f.Name(), want, got)
 | ||
| 	}
 | ||
| }
 | ||
| 
 | ||
| func TestClientStatLink(t *testing.T) {
 | ||
| 	skipIfWindows(t) // Windows does not support links.
 | ||
| 
 | ||
| 	sftp, cmd := testClient(t, READONLY, NODELAY)
 | ||
| 	defer cmd.Wait()
 | ||
| 	defer sftp.Close()
 | ||
| 
 | ||
| 	f, err := ioutil.TempFile("", "sftptest-statlink")
 | ||
| 	if err != nil {
 | ||
| 		t.Fatal(err)
 | ||
| 	}
 | ||
| 	defer os.Remove(f.Name())
 | ||
| 
 | ||
| 	realName := f.Name()
 | ||
| 	linkName := f.Name() + ".softlink"
 | ||
| 
 | ||
| 	// create a symlink that points at sftptest
 | ||
| 	if err := os.Symlink(realName, linkName); err != nil {
 | ||
| 		t.Fatal(err)
 | ||
| 	}
 | ||
| 	defer os.Remove(linkName)
 | ||
| 
 | ||
| 	// compare Lstat of links
 | ||
| 	wantLstat, err := os.Lstat(linkName)
 | ||
| 	if err != nil {
 | ||
| 		t.Fatal(err)
 | ||
| 	}
 | ||
| 	wantStat, err := os.Stat(linkName)
 | ||
| 	if err != nil {
 | ||
| 		t.Fatal(err)
 | ||
| 	}
 | ||
| 
 | ||
| 	gotLstat, err := sftp.Lstat(linkName)
 | ||
| 	if err != nil {
 | ||
| 		t.Fatal(err)
 | ||
| 	}
 | ||
| 	gotStat, err := sftp.Stat(linkName)
 | ||
| 	if err != nil {
 | ||
| 		t.Fatal(err)
 | ||
| 	}
 | ||
| 
 | ||
| 	// check that stat is not lstat from os package
 | ||
| 	if sameFile(wantLstat, wantStat) {
 | ||
| 		t.Fatalf("Lstat / Stat(%q): both %#v %#v", f.Name(), wantLstat, wantStat)
 | ||
| 	}
 | ||
| 
 | ||
| 	// compare Lstat of links
 | ||
| 	if !sameFile(wantLstat, gotLstat) {
 | ||
| 		t.Fatalf("Lstat(%q): want %#v, got %#v", f.Name(), wantLstat, gotLstat)
 | ||
| 	}
 | ||
| 
 | ||
| 	// compare Stat of links
 | ||
| 	if !sameFile(wantStat, gotStat) {
 | ||
| 		t.Fatalf("Stat(%q): want %#v, got %#v", f.Name(), wantStat, gotStat)
 | ||
| 	}
 | ||
| 
 | ||
| 	// check that stat is not lstat
 | ||
| 	if sameFile(gotLstat, gotStat) {
 | ||
| 		t.Fatalf("Lstat / Stat(%q): both %#v %#v", f.Name(), gotLstat, gotStat)
 | ||
| 	}
 | ||
| }
 | ||
| 
 | ||
| func TestClientRemove(t *testing.T) {
 | ||
| 	sftp, cmd := testClient(t, READWRITE, NODELAY)
 | ||
| 	defer cmd.Wait()
 | ||
| 	defer sftp.Close()
 | ||
| 
 | ||
| 	f, err := ioutil.TempFile("", "sftptest-remove")
 | ||
| 	if err != nil {
 | ||
| 		t.Fatal(err)
 | ||
| 	}
 | ||
| 	defer os.Remove(f.Name())
 | ||
| 	f.Close()
 | ||
| 
 | ||
| 	if err := sftp.Remove(f.Name()); err != nil {
 | ||
| 		t.Fatal(err)
 | ||
| 	}
 | ||
| 	if _, err := os.Lstat(f.Name()); !os.IsNotExist(err) {
 | ||
| 		t.Fatal(err)
 | ||
| 	}
 | ||
| }
 | ||
| 
 | ||
| func TestClientRemoveAll(t *testing.T) {
 | ||
| 	sftp, cmd := testClient(t, READWRITE, NODELAY)
 | ||
| 	defer cmd.Wait()
 | ||
| 	defer sftp.Close()
 | ||
| 
 | ||
| 	// Create a temporary directory for testing
 | ||
| 	tempDir, err := ioutil.TempDir("", "sftptest-removeAll")
 | ||
| 	if err != nil {
 | ||
| 		t.Fatal(err)
 | ||
| 	}
 | ||
| 	defer os.RemoveAll(tempDir)
 | ||
| 
 | ||
| 	// Create a directory tree
 | ||
| 	dir1, err := ioutil.TempDir(tempDir, "foo")
 | ||
| 	if err != nil {
 | ||
| 		t.Fatal(err)
 | ||
| 	}
 | ||
| 	dir2, err := ioutil.TempDir(dir1, "bar")
 | ||
| 	if err != nil {
 | ||
| 		t.Fatal(err)
 | ||
| 	}
 | ||
| 
 | ||
| 	// Create some files within the directory tree
 | ||
| 	file1 := tempDir + "/file1.txt"
 | ||
| 	file2 := dir1 + "/file2.txt"
 | ||
| 	file3 := dir2 + "/file3.txt"
 | ||
| 	err = ioutil.WriteFile(file1, []byte("File 1"), 0644)
 | ||
| 	if err != nil {
 | ||
| 		t.Fatalf("Failed to create file: %v", err)
 | ||
| 	}
 | ||
| 	err = ioutil.WriteFile(file2, []byte("File 2"), 0644)
 | ||
| 	if err != nil {
 | ||
| 		t.Fatalf("Failed to create file: %v", err)
 | ||
| 	}
 | ||
| 	err = ioutil.WriteFile(file3, []byte("File 3"), 0644)
 | ||
| 	if err != nil {
 | ||
| 		t.Fatalf("Failed to create file: %v", err)
 | ||
| 	}
 | ||
| 
 | ||
| 	// Call the function to delete the files recursively
 | ||
| 	err = sftp.RemoveAll(tempDir)
 | ||
| 	if err != nil {
 | ||
| 		t.Fatalf("Failed to delete files recursively: %v", err)
 | ||
| 	}
 | ||
| 
 | ||
| 	// Check if the directories and files have been deleted
 | ||
| 	if _, err := os.Stat(dir1); !os.IsNotExist(err) {
 | ||
| 		t.Errorf("Directory %s still exists", dir1)
 | ||
| 	}
 | ||
| 	if _, err := os.Stat(dir2); !os.IsNotExist(err) {
 | ||
| 		t.Errorf("Directory %s still exists", dir2)
 | ||
| 	}
 | ||
| 	if _, err := os.Stat(file1); !os.IsNotExist(err) {
 | ||
| 		t.Errorf("File %s still exists", file1)
 | ||
| 	}
 | ||
| 	if _, err := os.Stat(file2); !os.IsNotExist(err) {
 | ||
| 		t.Errorf("File %s still exists", file2)
 | ||
| 	}
 | ||
| 	if _, err := os.Stat(file3); !os.IsNotExist(err) {
 | ||
| 		t.Errorf("File %s still exists", file3)
 | ||
| 	}
 | ||
| }
 | ||
| 
 | ||
| func TestClientRemoveDir(t *testing.T) {
 | ||
| 	sftp, cmd := testClient(t, READWRITE, NODELAY)
 | ||
| 	defer cmd.Wait()
 | ||
| 	defer sftp.Close()
 | ||
| 
 | ||
| 	dir, err := ioutil.TempDir("", "sftptest-removedir")
 | ||
| 	if err != nil {
 | ||
| 		t.Fatal(err)
 | ||
| 	}
 | ||
| 	defer os.RemoveAll(dir)
 | ||
| 
 | ||
| 	if err := sftp.Remove(dir); err != nil {
 | ||
| 		t.Fatal(err)
 | ||
| 	}
 | ||
| 	if _, err := os.Lstat(dir); !os.IsNotExist(err) {
 | ||
| 		t.Fatal(err)
 | ||
| 	}
 | ||
| }
 | ||
| 
 | ||
| func TestClientRemoveFailed(t *testing.T) {
 | ||
| 	sftp, cmd := testClient(t, READONLY, NODELAY)
 | ||
| 	defer cmd.Wait()
 | ||
| 	defer sftp.Close()
 | ||
| 
 | ||
| 	f, err := ioutil.TempFile("", "sftptest-removefailed")
 | ||
| 	if err != nil {
 | ||
| 		t.Fatal(err)
 | ||
| 	}
 | ||
| 	defer os.Remove(f.Name())
 | ||
| 
 | ||
| 	if err := sftp.Remove(f.Name()); err == nil {
 | ||
| 		t.Fatalf("Remove(%v): want: permission denied, got %v", f.Name(), err)
 | ||
| 	}
 | ||
| 	if _, err := os.Lstat(f.Name()); err != nil {
 | ||
| 		t.Fatal(err)
 | ||
| 	}
 | ||
| }
 | ||
| 
 | ||
| func TestClientRename(t *testing.T) {
 | ||
| 	sftp, cmd := testClient(t, READWRITE, NODELAY)
 | ||
| 	defer cmd.Wait()
 | ||
| 	defer sftp.Close()
 | ||
| 
 | ||
| 	dir, err := ioutil.TempDir("", "sftptest-rename")
 | ||
| 	require.NoError(t, err)
 | ||
| 	defer os.RemoveAll(dir)
 | ||
| 	f, err := os.Create(filepath.Join(dir, "old"))
 | ||
| 	require.NoError(t, err)
 | ||
| 	f.Close()
 | ||
| 
 | ||
| 	f2 := filepath.Join(dir, "new")
 | ||
| 	if err := sftp.Rename(f.Name(), f2); err != nil {
 | ||
| 		t.Fatal(err)
 | ||
| 	}
 | ||
| 	if _, err := os.Lstat(f.Name()); !os.IsNotExist(err) {
 | ||
| 		t.Fatal(err)
 | ||
| 	}
 | ||
| 	if _, err := os.Lstat(f2); err != nil {
 | ||
| 		t.Fatal(err)
 | ||
| 	}
 | ||
| }
 | ||
| 
 | ||
| func TestClientPosixRename(t *testing.T) {
 | ||
| 	sftp, cmd := testClient(t, READWRITE, NODELAY)
 | ||
| 	defer cmd.Wait()
 | ||
| 	defer sftp.Close()
 | ||
| 
 | ||
| 	dir, err := ioutil.TempDir("", "sftptest-posixrename")
 | ||
| 	require.NoError(t, err)
 | ||
| 	defer os.RemoveAll(dir)
 | ||
| 	f, err := os.Create(filepath.Join(dir, "old"))
 | ||
| 	require.NoError(t, err)
 | ||
| 	f.Close()
 | ||
| 
 | ||
| 	f2 := filepath.Join(dir, "new")
 | ||
| 	if err := sftp.PosixRename(f.Name(), f2); err != nil {
 | ||
| 		t.Fatal(err)
 | ||
| 	}
 | ||
| 	if _, err := os.Lstat(f.Name()); !os.IsNotExist(err) {
 | ||
| 		t.Fatal(err)
 | ||
| 	}
 | ||
| 	if _, err := os.Lstat(f2); err != nil {
 | ||
| 		t.Fatal(err)
 | ||
| 	}
 | ||
| }
 | ||
| 
 | ||
| func TestClientGetwd(t *testing.T) {
 | ||
| 	sftp, cmd := testClient(t, READONLY, NODELAY)
 | ||
| 	defer cmd.Wait()
 | ||
| 	defer sftp.Close()
 | ||
| 
 | ||
| 	lwd, err := os.Getwd()
 | ||
| 	if err != nil {
 | ||
| 		t.Fatal(err)
 | ||
| 	}
 | ||
| 	rwd, err := sftp.Getwd()
 | ||
| 	if err != nil {
 | ||
| 		t.Fatal(err)
 | ||
| 	}
 | ||
| 	if !filepath.IsAbs(rwd) {
 | ||
| 		t.Fatalf("Getwd: wanted absolute path, got %q", rwd)
 | ||
| 	}
 | ||
| 	if filepath.ToSlash(lwd) != filepath.ToSlash(rwd) {
 | ||
| 		t.Fatalf("Getwd: want %q, got %q", lwd, rwd)
 | ||
| 	}
 | ||
| }
 | ||
| 
 | ||
| func TestClientReadLink(t *testing.T) {
 | ||
| 	if runtime.GOOS == "windows" && *testServerImpl {
 | ||
| 		// os.Symlink requires privilege escalation.
 | ||
| 		t.Skip()
 | ||
| 	}
 | ||
| 
 | ||
| 	sftp, cmd := testClient(t, READWRITE, NODELAY)
 | ||
| 	defer cmd.Wait()
 | ||
| 	defer sftp.Close()
 | ||
| 
 | ||
| 	dir, err := ioutil.TempDir("", "sftptest-readlink")
 | ||
| 	require.NoError(t, err)
 | ||
| 	defer os.RemoveAll(dir)
 | ||
| 	f, err := os.Create(filepath.Join(dir, "file"))
 | ||
| 	require.NoError(t, err)
 | ||
| 	f.Close()
 | ||
| 
 | ||
| 	f2 := filepath.Join(dir, "symlink")
 | ||
| 	if err := os.Symlink(f.Name(), f2); err != nil {
 | ||
| 		t.Fatal(err)
 | ||
| 	}
 | ||
| 	if rl, err := sftp.ReadLink(f2); err != nil {
 | ||
| 		t.Fatal(err)
 | ||
| 	} else if rl != f.Name() {
 | ||
| 		t.Fatalf("unexpected link target: %v, not %v", rl, f.Name())
 | ||
| 	}
 | ||
| }
 | ||
| 
 | ||
| func TestClientLink(t *testing.T) {
 | ||
| 	sftp, cmd := testClient(t, READWRITE, NODELAY)
 | ||
| 	defer cmd.Wait()
 | ||
| 	defer sftp.Close()
 | ||
| 
 | ||
| 	dir, err := ioutil.TempDir("", "sftptest-link")
 | ||
| 	require.NoError(t, err)
 | ||
| 	defer os.RemoveAll(dir)
 | ||
| 
 | ||
| 	f, err := os.Create(filepath.Join(dir, "file"))
 | ||
| 	require.NoError(t, err)
 | ||
| 	data := []byte("linktest")
 | ||
| 	_, err = f.Write(data)
 | ||
| 	f.Close()
 | ||
| 	if err != nil {
 | ||
| 		t.Fatal(err)
 | ||
| 	}
 | ||
| 
 | ||
| 	f2 := filepath.Join(dir, "link")
 | ||
| 	if err := sftp.Link(f.Name(), f2); err != nil {
 | ||
| 		t.Fatal(err)
 | ||
| 	}
 | ||
| 	if st2, err := sftp.Stat(f2); err != nil {
 | ||
| 		t.Fatal(err)
 | ||
| 	} else if int(st2.Size()) != len(data) {
 | ||
| 		t.Fatalf("unexpected link size: %v, not %v", st2.Size(), len(data))
 | ||
| 	}
 | ||
| }
 | ||
| 
 | ||
| func TestClientSymlink(t *testing.T) {
 | ||
| 	if runtime.GOOS == "windows" && *testServerImpl {
 | ||
| 		// os.Symlink requires privilege escalation.
 | ||
| 		t.Skip()
 | ||
| 	}
 | ||
| 
 | ||
| 	sftp, cmd := testClient(t, READWRITE, NODELAY)
 | ||
| 	defer cmd.Wait()
 | ||
| 	defer sftp.Close()
 | ||
| 
 | ||
| 	dir, err := ioutil.TempDir("", "sftptest-symlink")
 | ||
| 	require.NoError(t, err)
 | ||
| 	defer os.RemoveAll(dir)
 | ||
| 	f, err := os.Create(filepath.Join(dir, "file"))
 | ||
| 	require.NoError(t, err)
 | ||
| 	f.Close()
 | ||
| 
 | ||
| 	f2 := filepath.Join(dir, "symlink")
 | ||
| 	if err := sftp.Symlink(f.Name(), f2); err != nil {
 | ||
| 		t.Fatal(err)
 | ||
| 	}
 | ||
| 	if rl, err := sftp.ReadLink(f2); err != nil {
 | ||
| 		t.Fatal(err)
 | ||
| 	} else if rl != f.Name() {
 | ||
| 		t.Fatalf("unexpected link target: %v, not %v", rl, f.Name())
 | ||
| 	}
 | ||
| }
 | ||
| 
 | ||
| func TestClientChmod(t *testing.T) {
 | ||
| 	skipIfWindows(t) // No UNIX permissions.
 | ||
| 	sftp, cmd := testClient(t, READWRITE, NODELAY)
 | ||
| 	defer cmd.Wait()
 | ||
| 	defer sftp.Close()
 | ||
| 
 | ||
| 	f, err := ioutil.TempFile("", "sftptest-chmod")
 | ||
| 	if err != nil {
 | ||
| 		t.Fatal(err)
 | ||
| 	}
 | ||
| 	defer os.Remove(f.Name())
 | ||
| 	f.Close()
 | ||
| 
 | ||
| 	if err := sftp.Chmod(f.Name(), 0531); err != nil {
 | ||
| 		t.Fatal(err)
 | ||
| 	}
 | ||
| 	if stat, err := os.Stat(f.Name()); err != nil {
 | ||
| 		t.Fatal(err)
 | ||
| 	} else if stat.Mode()&os.ModePerm != 0531 {
 | ||
| 		t.Fatalf("invalid perm %o\n", stat.Mode())
 | ||
| 	}
 | ||
| 
 | ||
| 	sf, err := sftp.Open(f.Name())
 | ||
| 	require.NoError(t, err)
 | ||
| 	require.NoError(t, sf.Chmod(0500))
 | ||
| 	sf.Close()
 | ||
| 
 | ||
| 	stat, err := os.Stat(f.Name())
 | ||
| 	require.NoError(t, err)
 | ||
| 	require.EqualValues(t, 0500, stat.Mode())
 | ||
| }
 | ||
| 
 | ||
| func TestClientChmodReadonly(t *testing.T) {
 | ||
| 	skipIfWindows(t) // No UNIX permissions.
 | ||
| 	sftp, cmd := testClient(t, READONLY, NODELAY)
 | ||
| 	defer cmd.Wait()
 | ||
| 	defer sftp.Close()
 | ||
| 
 | ||
| 	f, err := ioutil.TempFile("", "sftptest-chmodreadonly")
 | ||
| 	if err != nil {
 | ||
| 		t.Fatal(err)
 | ||
| 	}
 | ||
| 	defer os.Remove(f.Name())
 | ||
| 	f.Close()
 | ||
| 
 | ||
| 	if err := sftp.Chmod(f.Name(), 0531); err == nil {
 | ||
| 		t.Fatal("expected error")
 | ||
| 	}
 | ||
| }
 | ||
| 
 | ||
| func TestClientSetuid(t *testing.T) {
 | ||
| 	skipIfWindows(t) // No UNIX permissions.
 | ||
| 	if *testServerImpl {
 | ||
| 		t.Skipf("skipping with -testserver")
 | ||
| 	}
 | ||
| 
 | ||
| 	sftp, cmd := testClient(t, READWRITE, NODELAY)
 | ||
| 	defer cmd.Wait()
 | ||
| 	defer sftp.Close()
 | ||
| 
 | ||
| 	f, err := ioutil.TempFile("", "sftptest-setuid")
 | ||
| 	if err != nil {
 | ||
| 		t.Fatal(err)
 | ||
| 	}
 | ||
| 	defer os.Remove(f.Name())
 | ||
| 	f.Close()
 | ||
| 
 | ||
| 	const allPerm = os.ModePerm | os.ModeSetuid | os.ModeSetgid | os.ModeSticky |
 | ||
| 		os.FileMode(s_ISUID|s_ISGID|s_ISVTX)
 | ||
| 
 | ||
| 	for _, c := range []struct {
 | ||
| 		goPerm    os.FileMode
 | ||
| 		posixPerm uint32
 | ||
| 	}{
 | ||
| 		{os.ModeSetuid, s_ISUID},
 | ||
| 		{os.ModeSetgid, s_ISGID},
 | ||
| 		{os.ModeSticky, s_ISVTX},
 | ||
| 		{os.ModeSetuid | os.ModeSticky, s_ISUID | s_ISVTX},
 | ||
| 	} {
 | ||
| 		goPerm := 0700 | c.goPerm
 | ||
| 		posixPerm := 0700 | c.posixPerm
 | ||
| 
 | ||
| 		err = sftp.Chmod(f.Name(), goPerm)
 | ||
| 		require.NoError(t, err)
 | ||
| 
 | ||
| 		info, err := sftp.Stat(f.Name())
 | ||
| 		require.NoError(t, err)
 | ||
| 		require.Equal(t, goPerm, info.Mode()&allPerm)
 | ||
| 
 | ||
| 		err = sftp.Chmod(f.Name(), 0700) // Reset funny bits.
 | ||
| 		require.NoError(t, err)
 | ||
| 
 | ||
| 		// For historical reasons, we also support literal POSIX mode bits in
 | ||
| 		// Chmod. Stat should still translate these to Go os.FileMode bits.
 | ||
| 		err = sftp.Chmod(f.Name(), os.FileMode(posixPerm))
 | ||
| 		require.NoError(t, err)
 | ||
| 
 | ||
| 		info, err = sftp.Stat(f.Name())
 | ||
| 		require.NoError(t, err)
 | ||
| 		require.Equal(t, goPerm, info.Mode()&allPerm)
 | ||
| 	}
 | ||
| }
 | ||
| 
 | ||
| func TestClientChown(t *testing.T) {
 | ||
| 	skipIfWindows(t) // No UNIX permissions.
 | ||
| 	sftp, cmd := testClient(t, READWRITE, NODELAY)
 | ||
| 	defer cmd.Wait()
 | ||
| 	defer sftp.Close()
 | ||
| 
 | ||
| 	usr, err := user.Current()
 | ||
| 	if err != nil {
 | ||
| 		t.Fatal(err)
 | ||
| 	}
 | ||
| 	if usr.Uid != "0" {
 | ||
| 		t.Log("must be root to run chown tests")
 | ||
| 		t.Skip()
 | ||
| 	}
 | ||
| 
 | ||
| 	chownto, err := user.Lookup("daemon") // seems common-ish...
 | ||
| 	if err != nil {
 | ||
| 		t.Fatal(err)
 | ||
| 	}
 | ||
| 	toUID, err := strconv.Atoi(chownto.Uid)
 | ||
| 	if err != nil {
 | ||
| 		t.Fatal(err)
 | ||
| 	}
 | ||
| 	toGID, err := strconv.Atoi(chownto.Gid)
 | ||
| 	if err != nil {
 | ||
| 		t.Fatal(err)
 | ||
| 	}
 | ||
| 
 | ||
| 	f, err := ioutil.TempFile("", "sftptest-chown")
 | ||
| 	if err != nil {
 | ||
| 		t.Fatal(err)
 | ||
| 	}
 | ||
| 	defer os.Remove(f.Name())
 | ||
| 	f.Close()
 | ||
| 
 | ||
| 	before, err := exec.Command("ls", "-nl", f.Name()).Output()
 | ||
| 	if err != nil {
 | ||
| 		t.Fatal(err)
 | ||
| 	}
 | ||
| 	if err := sftp.Chown(f.Name(), toUID, toGID); err != nil {
 | ||
| 		t.Fatal(err)
 | ||
| 	}
 | ||
| 	after, err := exec.Command("ls", "-nl", f.Name()).Output()
 | ||
| 	if err != nil {
 | ||
| 		t.Fatal(err)
 | ||
| 	}
 | ||
| 
 | ||
| 	spaceRegex := regexp.MustCompile(`\s+`)
 | ||
| 
 | ||
| 	beforeWords := spaceRegex.Split(string(before), -1)
 | ||
| 	if beforeWords[2] != "0" {
 | ||
| 		t.Fatalf("bad previous user? should be root")
 | ||
| 	}
 | ||
| 	afterWords := spaceRegex.Split(string(after), -1)
 | ||
| 	if afterWords[2] != chownto.Uid || afterWords[3] != chownto.Gid {
 | ||
| 		t.Fatalf("bad chown: %#v", afterWords)
 | ||
| 	}
 | ||
| 	t.Logf("before: %v", string(before))
 | ||
| 	t.Logf(" after: %v", string(after))
 | ||
| }
 | ||
| 
 | ||
| func TestClientChownReadonly(t *testing.T) {
 | ||
| 	skipIfWindows(t) // No UNIX permissions.
 | ||
| 	sftp, cmd := testClient(t, READONLY, NODELAY)
 | ||
| 	defer cmd.Wait()
 | ||
| 	defer sftp.Close()
 | ||
| 
 | ||
| 	usr, err := user.Current()
 | ||
| 	if err != nil {
 | ||
| 		t.Fatal(err)
 | ||
| 	}
 | ||
| 	if usr.Uid != "0" {
 | ||
| 		t.Log("must be root to run chown tests")
 | ||
| 		t.Skip()
 | ||
| 	}
 | ||
| 
 | ||
| 	chownto, err := user.Lookup("daemon") // seems common-ish...
 | ||
| 	if err != nil {
 | ||
| 		t.Fatal(err)
 | ||
| 	}
 | ||
| 	toUID, err := strconv.Atoi(chownto.Uid)
 | ||
| 	if err != nil {
 | ||
| 		t.Fatal(err)
 | ||
| 	}
 | ||
| 	toGID, err := strconv.Atoi(chownto.Gid)
 | ||
| 	if err != nil {
 | ||
| 		t.Fatal(err)
 | ||
| 	}
 | ||
| 
 | ||
| 	f, err := ioutil.TempFile("", "sftptest-chownreadonly")
 | ||
| 	if err != nil {
 | ||
| 		t.Fatal(err)
 | ||
| 	}
 | ||
| 	defer os.Remove(f.Name())
 | ||
| 	f.Close()
 | ||
| 
 | ||
| 	if err := sftp.Chown(f.Name(), toUID, toGID); err == nil {
 | ||
| 		t.Fatal("expected error")
 | ||
| 	}
 | ||
| }
 | ||
| 
 | ||
| func TestClientChtimes(t *testing.T) {
 | ||
| 	sftp, cmd := testClient(t, READWRITE, NODELAY)
 | ||
| 	defer cmd.Wait()
 | ||
| 	defer sftp.Close()
 | ||
| 
 | ||
| 	f, err := ioutil.TempFile("", "sftptest-chtimes")
 | ||
| 	if err != nil {
 | ||
| 		t.Fatal(err)
 | ||
| 	}
 | ||
| 	defer os.Remove(f.Name())
 | ||
| 	f.Close()
 | ||
| 
 | ||
| 	atime := time.Date(2013, 2, 23, 13, 24, 35, 0, time.UTC)
 | ||
| 	mtime := time.Date(1985, 6, 12, 6, 6, 6, 0, time.UTC)
 | ||
| 	if err := sftp.Chtimes(f.Name(), atime, mtime); err != nil {
 | ||
| 		t.Fatal(err)
 | ||
| 	}
 | ||
| 	if stat, err := os.Stat(f.Name()); err != nil {
 | ||
| 		t.Fatal(err)
 | ||
| 	} else if stat.ModTime().Sub(mtime) != 0 {
 | ||
| 		t.Fatalf("incorrect mtime: %v vs %v", stat.ModTime(), mtime)
 | ||
| 	}
 | ||
| }
 | ||
| 
 | ||
| func TestClientChtimesReadonly(t *testing.T) {
 | ||
| 	sftp, cmd := testClient(t, READONLY, NODELAY)
 | ||
| 	defer cmd.Wait()
 | ||
| 	defer sftp.Close()
 | ||
| 
 | ||
| 	f, err := ioutil.TempFile("", "sftptest-chtimesreadonly")
 | ||
| 	if err != nil {
 | ||
| 		t.Fatal(err)
 | ||
| 	}
 | ||
| 	defer os.Remove(f.Name())
 | ||
| 	f.Close()
 | ||
| 
 | ||
| 	atime := time.Date(2013, 2, 23, 13, 24, 35, 0, time.UTC)
 | ||
| 	mtime := time.Date(1985, 6, 12, 6, 6, 6, 0, time.UTC)
 | ||
| 	if err := sftp.Chtimes(f.Name(), atime, mtime); err == nil {
 | ||
| 		t.Fatal("expected error")
 | ||
| 	}
 | ||
| }
 | ||
| 
 | ||
| func TestClientTruncate(t *testing.T) {
 | ||
| 	sftp, cmd := testClient(t, READWRITE, NODELAY)
 | ||
| 	defer cmd.Wait()
 | ||
| 	defer sftp.Close()
 | ||
| 
 | ||
| 	f, err := ioutil.TempFile("", "sftptest-truncate")
 | ||
| 	if err != nil {
 | ||
| 		t.Fatal(err)
 | ||
| 	}
 | ||
| 	defer os.Remove(f.Name())
 | ||
| 	fname := f.Name()
 | ||
| 
 | ||
| 	if n, err := f.Write([]byte("hello world")); n != 11 || err != nil {
 | ||
| 		t.Fatal(err)
 | ||
| 	}
 | ||
| 	f.Close()
 | ||
| 
 | ||
| 	if err := sftp.Truncate(fname, 5); err != nil {
 | ||
| 		t.Fatal(err)
 | ||
| 	}
 | ||
| 	if stat, err := os.Stat(fname); err != nil {
 | ||
| 		t.Fatal(err)
 | ||
| 	} else if stat.Size() != 5 {
 | ||
| 		t.Fatalf("unexpected size: %d", stat.Size())
 | ||
| 	}
 | ||
| }
 | ||
| 
 | ||
| func TestClientTruncateReadonly(t *testing.T) {
 | ||
| 	sftp, cmd := testClient(t, READONLY, NODELAY)
 | ||
| 	defer cmd.Wait()
 | ||
| 	defer sftp.Close()
 | ||
| 
 | ||
| 	f, err := ioutil.TempFile("", "sftptest-truncreadonly")
 | ||
| 	if err != nil {
 | ||
| 		t.Fatal(err)
 | ||
| 	}
 | ||
| 	defer os.Remove(f.Name())
 | ||
| 	fname := f.Name()
 | ||
| 
 | ||
| 	if n, err := f.Write([]byte("hello world")); n != 11 || err != nil {
 | ||
| 		t.Fatal(err)
 | ||
| 	}
 | ||
| 	f.Close()
 | ||
| 
 | ||
| 	if err := sftp.Truncate(fname, 5); err == nil {
 | ||
| 		t.Fatal("expected error")
 | ||
| 	}
 | ||
| 	if stat, err := os.Stat(fname); err != nil {
 | ||
| 		t.Fatal(err)
 | ||
| 	} else if stat.Size() != 11 {
 | ||
| 		t.Fatalf("unexpected size: %d", stat.Size())
 | ||
| 	}
 | ||
| }
 | ||
| 
 | ||
| func sameFile(want, got os.FileInfo) bool {
 | ||
| 	_, wantName := filepath.Split(want.Name())
 | ||
| 	_, gotName := filepath.Split(got.Name())
 | ||
| 	return wantName == gotName &&
 | ||
| 		want.Size() == got.Size()
 | ||
| }
 | ||
| 
 | ||
| func TestClientReadSimple(t *testing.T) {
 | ||
| 	sftp, cmd := testClient(t, READONLY, NODELAY)
 | ||
| 	defer cmd.Wait()
 | ||
| 	defer sftp.Close()
 | ||
| 
 | ||
| 	d, err := ioutil.TempDir("", "sftptest-readsimple")
 | ||
| 	if err != nil {
 | ||
| 		t.Fatal(err)
 | ||
| 	}
 | ||
| 	defer os.RemoveAll(d)
 | ||
| 
 | ||
| 	f, err := ioutil.TempFile(d, "read-test")
 | ||
| 	if err != nil {
 | ||
| 		t.Fatal(err)
 | ||
| 	}
 | ||
| 	fname := f.Name()
 | ||
| 	f.Write([]byte("hello"))
 | ||
| 	f.Close()
 | ||
| 
 | ||
| 	f2, err := sftp.Open(fname)
 | ||
| 	if err != nil {
 | ||
| 		t.Fatal(err)
 | ||
| 	}
 | ||
| 	defer f2.Close()
 | ||
| 	stuff := make([]byte, 32)
 | ||
| 	n, err := f2.Read(stuff)
 | ||
| 	if err != nil && err != io.EOF {
 | ||
| 		t.Fatalf("err: %v", err)
 | ||
| 	}
 | ||
| 	if n != 5 {
 | ||
| 		t.Fatalf("n should be 5, is %v", n)
 | ||
| 	}
 | ||
| 	if string(stuff[0:5]) != "hello" {
 | ||
| 		t.Fatalf("invalid contents")
 | ||
| 	}
 | ||
| }
 | ||
| 
 | ||
| func TestClientReadSequential(t *testing.T) {
 | ||
| 	sftp, cmd := testClient(t, READONLY, NODELAY)
 | ||
| 	defer cmd.Wait()
 | ||
| 	defer sftp.Close()
 | ||
| 
 | ||
| 	sftp.disableConcurrentReads = true
 | ||
| 	d, err := ioutil.TempDir("", "sftptest-readsequential")
 | ||
| 	require.NoError(t, err)
 | ||
| 
 | ||
| 	defer os.RemoveAll(d)
 | ||
| 
 | ||
| 	f, err := ioutil.TempFile(d, "read-sequential-test")
 | ||
| 	require.NoError(t, err)
 | ||
| 	fname := f.Name()
 | ||
| 	content := []byte("hello world")
 | ||
| 	f.Write(content)
 | ||
| 	f.Close()
 | ||
| 
 | ||
| 	for _, maxPktSize := range []int{1, 2, 3, 4} {
 | ||
| 		sftp.maxPacket = maxPktSize
 | ||
| 
 | ||
| 		sftpFile, err := sftp.Open(fname)
 | ||
| 		require.NoError(t, err)
 | ||
| 
 | ||
| 		stuff := make([]byte, 32)
 | ||
| 		n, err := sftpFile.Read(stuff)
 | ||
| 		require.ErrorIs(t, err, io.EOF)
 | ||
| 		require.Equal(t, len(content), n)
 | ||
| 		require.Equal(t, content, stuff[0:len(content)])
 | ||
| 
 | ||
| 		err = sftpFile.Close()
 | ||
| 		require.NoError(t, err)
 | ||
| 
 | ||
| 		sftpFile, err = sftp.Open(fname)
 | ||
| 		require.NoError(t, err)
 | ||
| 
 | ||
| 		stuff = make([]byte, 5)
 | ||
| 		n, err = sftpFile.Read(stuff)
 | ||
| 		require.NoError(t, err)
 | ||
| 		require.Equal(t, len(stuff), n)
 | ||
| 		require.Equal(t, content[:len(stuff)], stuff)
 | ||
| 
 | ||
| 		err = sftpFile.Close()
 | ||
| 		require.NoError(t, err)
 | ||
| 
 | ||
| 		// now read from a offset
 | ||
| 		off := int64(3)
 | ||
| 		sftpFile, err = sftp.Open(fname)
 | ||
| 		require.NoError(t, err)
 | ||
| 
 | ||
| 		stuff = make([]byte, 5)
 | ||
| 		n, err = sftpFile.ReadAt(stuff, off)
 | ||
| 		require.NoError(t, err)
 | ||
| 		require.Equal(t, len(stuff), n)
 | ||
| 		require.Equal(t, content[off:off+int64(len(stuff))], stuff)
 | ||
| 
 | ||
| 		err = sftpFile.Close()
 | ||
| 		require.NoError(t, err)
 | ||
| 	}
 | ||
| }
 | ||
| 
 | ||
| // this writer requires maxPacket = 3 and always returns an error for the second write call
 | ||
| type lastChunkErrSequentialWriter struct {
 | ||
| 	counter int
 | ||
| }
 | ||
| 
 | ||
| func (w *lastChunkErrSequentialWriter) Write(b []byte) (int, error) {
 | ||
| 	w.counter++
 | ||
| 	if w.counter == 1 {
 | ||
| 		if len(b) != 3 {
 | ||
| 			return 0, errors.New("this writer requires maxPacket = 3, please set MaxPacketChecked(3)")
 | ||
| 		}
 | ||
| 		return len(b), nil
 | ||
| 	}
 | ||
| 	return 1, errors.New("this writer fails after the first write")
 | ||
| }
 | ||
| 
 | ||
| func TestClientWriteSequentialWriterErr(t *testing.T) {
 | ||
| 	client, cmd := testClient(t, READONLY, NODELAY, MaxPacketChecked(3))
 | ||
| 	defer cmd.Wait()
 | ||
| 	defer client.Close()
 | ||
| 
 | ||
| 	d, err := ioutil.TempDir("", "sftptest-writesequential-writeerr")
 | ||
| 	require.NoError(t, err)
 | ||
| 
 | ||
| 	defer os.RemoveAll(d)
 | ||
| 
 | ||
| 	f, err := ioutil.TempFile(d, "write-sequential-writeerr-test")
 | ||
| 	require.NoError(t, err)
 | ||
| 	fname := f.Name()
 | ||
| 	_, err = f.Write([]byte("12345"))
 | ||
| 	require.NoError(t, err)
 | ||
| 	require.NoError(t, f.Close())
 | ||
| 
 | ||
| 	sftpFile, err := client.Open(fname)
 | ||
| 	require.NoError(t, err)
 | ||
| 	defer sftpFile.Close()
 | ||
| 
 | ||
| 	w := &lastChunkErrSequentialWriter{}
 | ||
| 	written, err := sftpFile.writeToSequential(w)
 | ||
| 	assert.Error(t, err)
 | ||
| 	expected := int64(4)
 | ||
| 	if written != expected {
 | ||
| 		t.Errorf("sftpFile.Write() = %d, but expected %d", written, expected)
 | ||
| 	}
 | ||
| 	assert.Equal(t, 2, w.counter)
 | ||
| }
 | ||
| 
 | ||
| func TestClientReadDir(t *testing.T) {
 | ||
| 	sftp1, cmd1 := testClient(t, READONLY, NODELAY)
 | ||
| 	sftp2, cmd2 := testClientGoSvr(t, READONLY, NODELAY)
 | ||
| 	defer cmd1.Wait()
 | ||
| 	defer cmd2.Wait()
 | ||
| 	defer sftp1.Close()
 | ||
| 	defer sftp2.Close()
 | ||
| 
 | ||
| 	dir := os.TempDir()
 | ||
| 
 | ||
| 	d, err := os.Open(dir)
 | ||
| 	if err != nil {
 | ||
| 		t.Fatal(err)
 | ||
| 	}
 | ||
| 	defer d.Close()
 | ||
| 	osfiles, err := d.Readdir(4096)
 | ||
| 	if err != nil {
 | ||
| 		t.Fatal(err)
 | ||
| 	}
 | ||
| 
 | ||
| 	sftp1Files, err := sftp1.ReadDir(dir)
 | ||
| 	if err != nil {
 | ||
| 		t.Fatal(err)
 | ||
| 	}
 | ||
| 	sftp2Files, err := sftp2.ReadDir(dir)
 | ||
| 	if err != nil {
 | ||
| 		t.Fatal(err)
 | ||
| 	}
 | ||
| 
 | ||
| 	osFilesByName := map[string]os.FileInfo{}
 | ||
| 	for _, f := range osfiles {
 | ||
| 		osFilesByName[f.Name()] = f
 | ||
| 	}
 | ||
| 	sftp1FilesByName := map[string]os.FileInfo{}
 | ||
| 	for _, f := range sftp1Files {
 | ||
| 		sftp1FilesByName[f.Name()] = f
 | ||
| 	}
 | ||
| 	sftp2FilesByName := map[string]os.FileInfo{}
 | ||
| 	for _, f := range sftp2Files {
 | ||
| 		sftp2FilesByName[f.Name()] = f
 | ||
| 	}
 | ||
| 
 | ||
| 	if len(osFilesByName) != len(sftp1FilesByName) || len(sftp1FilesByName) != len(sftp2FilesByName) {
 | ||
| 		t.Fatalf("os gives %v, sftp1 gives %v, sftp2 gives %v", len(osFilesByName), len(sftp1FilesByName), len(sftp2FilesByName))
 | ||
| 	}
 | ||
| 
 | ||
| 	for name, osF := range osFilesByName {
 | ||
| 		sftp1F, ok := sftp1FilesByName[name]
 | ||
| 		if !ok {
 | ||
| 			t.Fatalf("%v present in os but not sftp1", name)
 | ||
| 		}
 | ||
| 		sftp2F, ok := sftp2FilesByName[name]
 | ||
| 		if !ok {
 | ||
| 			t.Fatalf("%v present in os but not sftp2", name)
 | ||
| 		}
 | ||
| 
 | ||
| 		//t.Logf("%v: %v %v %v", name, osF, sftp1F, sftp2F)
 | ||
| 		if osF.Size() != sftp1F.Size() || sftp1F.Size() != sftp2F.Size() {
 | ||
| 			t.Fatalf("size %v %v %v", osF.Size(), sftp1F.Size(), sftp2F.Size())
 | ||
| 		}
 | ||
| 		if osF.IsDir() != sftp1F.IsDir() || sftp1F.IsDir() != sftp2F.IsDir() {
 | ||
| 			t.Fatalf("isdir %v %v %v", osF.IsDir(), sftp1F.IsDir(), sftp2F.IsDir())
 | ||
| 		}
 | ||
| 		if osF.ModTime().Sub(sftp1F.ModTime()) > time.Second || sftp1F.ModTime() != sftp2F.ModTime() {
 | ||
| 			t.Fatalf("modtime %v %v %v", osF.ModTime(), sftp1F.ModTime(), sftp2F.ModTime())
 | ||
| 		}
 | ||
| 		if osF.Mode() != sftp1F.Mode() || sftp1F.Mode() != sftp2F.Mode() {
 | ||
| 			t.Fatalf("mode %x %x %x", osF.Mode(), sftp1F.Mode(), sftp2F.Mode())
 | ||
| 		}
 | ||
| 	}
 | ||
| }
 | ||
| 
 | ||
| var clientReadTests = []struct {
 | ||
| 	n int64
 | ||
| }{
 | ||
| 	{0},
 | ||
| 	{1},
 | ||
| 	{1000},
 | ||
| 	{1024},
 | ||
| 	{1025},
 | ||
| 	{2048},
 | ||
| 	{4096},
 | ||
| 	{1 << 12},
 | ||
| 	{1 << 13},
 | ||
| 	{1 << 14},
 | ||
| 	{1 << 15},
 | ||
| 	{1 << 16},
 | ||
| 	{1 << 17},
 | ||
| 	{1 << 18},
 | ||
| 	{1 << 19},
 | ||
| 	{1 << 20},
 | ||
| }
 | ||
| 
 | ||
| func TestClientRead(t *testing.T) {
 | ||
| 	sftp, cmd := testClient(t, READONLY, NODELAY)
 | ||
| 	defer cmd.Wait()
 | ||
| 	defer sftp.Close()
 | ||
| 
 | ||
| 	d, err := ioutil.TempDir("", "sftptest-read")
 | ||
| 	if err != nil {
 | ||
| 		t.Fatal(err)
 | ||
| 	}
 | ||
| 	defer os.RemoveAll(d)
 | ||
| 
 | ||
| 	for _, disableConcurrentReads := range []bool{true, false} {
 | ||
| 		for _, tt := range clientReadTests {
 | ||
| 			f, err := ioutil.TempFile(d, "read-test")
 | ||
| 			if err != nil {
 | ||
| 				t.Fatal(err)
 | ||
| 			}
 | ||
| 			defer f.Close()
 | ||
| 			hash := writeN(t, f, tt.n)
 | ||
| 			sftp.disableConcurrentReads = disableConcurrentReads
 | ||
| 			f2, err := sftp.Open(f.Name())
 | ||
| 			if err != nil {
 | ||
| 				t.Fatal(err)
 | ||
| 			}
 | ||
| 			defer f2.Close()
 | ||
| 			hash2, n := readHash(t, f2)
 | ||
| 			if hash != hash2 || tt.n != n {
 | ||
| 				t.Errorf("Read: hash: want: %q, got %q, read: want: %v, got %v", hash, hash2, tt.n, n)
 | ||
| 			}
 | ||
| 		}
 | ||
| 	}
 | ||
| }
 | ||
| 
 | ||
| // readHash reads r until EOF returning the number of bytes read
 | ||
| // and the hash of the contents.
 | ||
| func readHash(t *testing.T, r io.Reader) (string, int64) {
 | ||
| 	h := sha1.New()
 | ||
| 	read, err := io.Copy(h, r)
 | ||
| 	if err != nil {
 | ||
| 		t.Fatal(err)
 | ||
| 	}
 | ||
| 	return string(h.Sum(nil)), read
 | ||
| }
 | ||
| 
 | ||
| // writeN writes n bytes of random data to w and returns the
 | ||
| // hash of that data.
 | ||
| func writeN(t *testing.T, w io.Writer, n int64) string {
 | ||
| 	r := rand.New(rand.NewSource(time.Now().UnixNano()))
 | ||
| 
 | ||
| 	h := sha1.New()
 | ||
| 
 | ||
| 	mw := io.MultiWriter(w, h)
 | ||
| 
 | ||
| 	written, err := io.CopyN(mw, r, n)
 | ||
| 	if err != nil {
 | ||
| 		t.Fatal(err)
 | ||
| 	}
 | ||
| 	if written != n {
 | ||
| 		t.Fatalf("CopyN(%v): wrote: %v", n, written)
 | ||
| 	}
 | ||
| 	return string(h.Sum(nil))
 | ||
| }
 | ||
| 
 | ||
| var clientWriteTests = []struct {
 | ||
| 	n     int
 | ||
| 	total int64 // cumulative file size
 | ||
| }{
 | ||
| 	{0, 0},
 | ||
| 	{1, 1},
 | ||
| 	{0, 1},
 | ||
| 	{999, 1000},
 | ||
| 	{24, 1024},
 | ||
| 	{1023, 2047},
 | ||
| 	{2048, 4095},
 | ||
| 	{1 << 12, 8191},
 | ||
| 	{1 << 13, 16383},
 | ||
| 	{1 << 14, 32767},
 | ||
| 	{1 << 15, 65535},
 | ||
| 	{1 << 16, 131071},
 | ||
| 	{1 << 17, 262143},
 | ||
| 	{1 << 18, 524287},
 | ||
| 	{1 << 19, 1048575},
 | ||
| 	{1 << 20, 2097151},
 | ||
| 	{1 << 21, 4194303},
 | ||
| }
 | ||
| 
 | ||
| func TestClientWrite(t *testing.T) {
 | ||
| 	sftp, cmd := testClient(t, READWRITE, NODELAY)
 | ||
| 	defer cmd.Wait()
 | ||
| 	defer sftp.Close()
 | ||
| 
 | ||
| 	d, err := ioutil.TempDir("", "sftptest-write")
 | ||
| 	if err != nil {
 | ||
| 		t.Fatal(err)
 | ||
| 	}
 | ||
| 	defer os.RemoveAll(d)
 | ||
| 
 | ||
| 	f := path.Join(d, "writeTest")
 | ||
| 	w, err := sftp.Create(f)
 | ||
| 	if err != nil {
 | ||
| 		t.Fatal(err)
 | ||
| 	}
 | ||
| 	defer w.Close()
 | ||
| 
 | ||
| 	for _, tt := range clientWriteTests {
 | ||
| 		got, err := w.Write(make([]byte, tt.n))
 | ||
| 		if err != nil {
 | ||
| 			t.Fatal(err)
 | ||
| 		}
 | ||
| 		if got != tt.n {
 | ||
| 			t.Errorf("Write(%v): wrote: want: %v, got %v", tt.n, tt.n, got)
 | ||
| 		}
 | ||
| 		fi, err := os.Stat(f)
 | ||
| 		if err != nil {
 | ||
| 			t.Fatal(err)
 | ||
| 		}
 | ||
| 		if total := fi.Size(); total != tt.total {
 | ||
| 			t.Errorf("Write(%v): size: want: %v, got %v", tt.n, tt.total, total)
 | ||
| 		}
 | ||
| 	}
 | ||
| }
 | ||
| 
 | ||
| // ReadFrom is basically Write with io.Reader as the arg
 | ||
| func TestClientReadFrom(t *testing.T) {
 | ||
| 	sftp, cmd := testClient(t, READWRITE, NODELAY)
 | ||
| 	defer cmd.Wait()
 | ||
| 	defer sftp.Close()
 | ||
| 
 | ||
| 	d, err := ioutil.TempDir("", "sftptest-readfrom")
 | ||
| 	if err != nil {
 | ||
| 		t.Fatal(err)
 | ||
| 	}
 | ||
| 	defer os.RemoveAll(d)
 | ||
| 
 | ||
| 	f := path.Join(d, "writeTest")
 | ||
| 	w, err := sftp.Create(f)
 | ||
| 	if err != nil {
 | ||
| 		t.Fatal(err)
 | ||
| 	}
 | ||
| 	defer w.Close()
 | ||
| 
 | ||
| 	for _, tt := range clientWriteTests {
 | ||
| 		got, err := w.ReadFrom(bytes.NewReader(make([]byte, tt.n)))
 | ||
| 		if err != nil {
 | ||
| 			t.Fatal(err)
 | ||
| 		}
 | ||
| 		if got != int64(tt.n) {
 | ||
| 			t.Errorf("Write(%v): wrote: want: %v, got %v", tt.n, tt.n, got)
 | ||
| 		}
 | ||
| 		fi, err := os.Stat(f)
 | ||
| 		if err != nil {
 | ||
| 			t.Fatal(err)
 | ||
| 		}
 | ||
| 		if total := fi.Size(); total != tt.total {
 | ||
| 			t.Errorf("Write(%v): size: want: %v, got %v", tt.n, tt.total, total)
 | ||
| 		}
 | ||
| 	}
 | ||
| }
 | ||
| 
 | ||
| // A sizedReader is a Reader with a completely arbitrary Size.
 | ||
| type sizedReader struct {
 | ||
| 	io.Reader
 | ||
| 	size int
 | ||
| }
 | ||
| 
 | ||
| func (r *sizedReader) Size() int { return r.size }
 | ||
| 
 | ||
| // Test File.ReadFrom's handling of a Reader's Size:
 | ||
| // it should be used as a heuristic for determining concurrency only.
 | ||
| func TestClientReadFromSizeMismatch(t *testing.T) {
 | ||
| 	const (
 | ||
| 		packetSize = 1024
 | ||
| 		filesize   = 4 * packetSize
 | ||
| 	)
 | ||
| 
 | ||
| 	sftp, cmd := testClient(t, READWRITE, NODELAY, MaxPacketChecked(packetSize), UseConcurrentWrites(true))
 | ||
| 	defer cmd.Wait()
 | ||
| 	defer sftp.Close()
 | ||
| 
 | ||
| 	d, err := ioutil.TempDir("", "sftptest-readfrom-size-mismatch")
 | ||
| 	if err != nil {
 | ||
| 		t.Fatal("cannot create temp dir:", err)
 | ||
| 	}
 | ||
| 	defer os.RemoveAll(d)
 | ||
| 
 | ||
| 	buf := make([]byte, filesize)
 | ||
| 
 | ||
| 	for i, reportedSize := range []int{
 | ||
| 		-1, filesize - 100, filesize, filesize + 100,
 | ||
| 	} {
 | ||
| 		t.Run(fmt.Sprint(i), func(t *testing.T) {
 | ||
| 			r := &sizedReader{Reader: bytes.NewReader(buf), size: reportedSize}
 | ||
| 
 | ||
| 			f := path.Join(d, fmt.Sprint(i))
 | ||
| 			w, err := sftp.Create(f)
 | ||
| 			if err != nil {
 | ||
| 				t.Fatal("unexpected error:", err)
 | ||
| 			}
 | ||
| 			defer w.Close()
 | ||
| 
 | ||
| 			n, err := w.ReadFrom(r)
 | ||
| 			assert.EqualValues(t, filesize, n)
 | ||
| 
 | ||
| 			fi, err := os.Stat(f)
 | ||
| 			if err != nil {
 | ||
| 				t.Fatal("unexpected error:", err)
 | ||
| 			}
 | ||
| 			assert.EqualValues(t, filesize, fi.Size())
 | ||
| 		})
 | ||
| 	}
 | ||
| }
 | ||
| 
 | ||
| // Issue #145 in github
 | ||
| // Deadlock in ReadFrom when network drops after 1 good packet.
 | ||
| // Deadlock would occur anytime desiredInFlight-inFlight==2 and 2 errors
 | ||
| // occurred in a row. The channel to report the errors only had a buffer
 | ||
| // of 1 and 2 would be sent.
 | ||
| var errFakeNet = errors.New("Fake network issue")
 | ||
| 
 | ||
| func TestClientReadFromDeadlock(t *testing.T) {
 | ||
| 	for i := 0; i < 5; i++ {
 | ||
| 		clientWriteDeadlock(t, i, func(f *File) {
 | ||
| 			b := make([]byte, 32768*4)
 | ||
| 			content := bytes.NewReader(b)
 | ||
| 			_, err := f.ReadFrom(content)
 | ||
| 			if !errors.Is(err, errFakeNet) {
 | ||
| 				t.Fatal("Didn't receive correct error:", err)
 | ||
| 			}
 | ||
| 		})
 | ||
| 	}
 | ||
| }
 | ||
| 
 | ||
| // Write has exact same problem
 | ||
| func TestClientWriteDeadlock(t *testing.T) {
 | ||
| 	for i := 0; i < 5; i++ {
 | ||
| 		clientWriteDeadlock(t, i, func(f *File) {
 | ||
| 			b := make([]byte, 32768*4)
 | ||
| 
 | ||
| 			_, err := f.Write(b)
 | ||
| 			if !errors.Is(err, errFakeNet) {
 | ||
| 				t.Fatal("Didn't receive correct error:", err)
 | ||
| 			}
 | ||
| 		})
 | ||
| 	}
 | ||
| }
 | ||
| 
 | ||
| type timeBombWriter struct {
 | ||
| 	count int
 | ||
| 	w     io.WriteCloser
 | ||
| }
 | ||
| 
 | ||
| func (w *timeBombWriter) Write(b []byte) (int, error) {
 | ||
| 	if w.count < 1 {
 | ||
| 		return 0, errFakeNet
 | ||
| 	}
 | ||
| 
 | ||
| 	w.count--
 | ||
| 	return w.w.Write(b)
 | ||
| }
 | ||
| 
 | ||
| func (w *timeBombWriter) Close() error {
 | ||
| 	return w.w.Close()
 | ||
| }
 | ||
| 
 | ||
| // shared body for both previous tests
 | ||
| func clientWriteDeadlock(t *testing.T, N int, badfunc func(*File)) {
 | ||
| 	if !*testServerImpl {
 | ||
| 		t.Skipf("skipping without -testserver")
 | ||
| 	}
 | ||
| 
 | ||
| 	sftp, cmd := testClient(t, READWRITE, NODELAY)
 | ||
| 	defer cmd.Wait()
 | ||
| 	defer sftp.Close()
 | ||
| 
 | ||
| 	d, err := ioutil.TempDir("", "sftptest-writedeadlock")
 | ||
| 	if err != nil {
 | ||
| 		t.Fatal(err)
 | ||
| 	}
 | ||
| 	defer os.RemoveAll(d)
 | ||
| 
 | ||
| 	f := path.Join(d, "writeTest")
 | ||
| 	w, err := sftp.Create(f)
 | ||
| 	if err != nil {
 | ||
| 		t.Fatal(err)
 | ||
| 	}
 | ||
| 	defer w.Close()
 | ||
| 
 | ||
| 	// Override the clienConn Writer with a failing version
 | ||
| 	// Replicates network error/drop part way through (after N good writes)
 | ||
| 	wrap := sftp.clientConn.conn.WriteCloser
 | ||
| 	sftp.clientConn.conn.WriteCloser = &timeBombWriter{
 | ||
| 		count: N,
 | ||
| 		w:     wrap,
 | ||
| 	}
 | ||
| 
 | ||
| 	// this locked (before the fix)
 | ||
| 	badfunc(w)
 | ||
| }
 | ||
| 
 | ||
| // Read/WriteTo has this issue as well
 | ||
| func TestClientReadDeadlock(t *testing.T) {
 | ||
| 	for i := 0; i < 3; i++ {
 | ||
| 		clientReadDeadlock(t, i, func(f *File) {
 | ||
| 			b := make([]byte, 32768*4)
 | ||
| 
 | ||
| 			_, err := f.Read(b)
 | ||
| 			if !errors.Is(err, errFakeNet) {
 | ||
| 				t.Fatal("Didn't receive correct error:", err)
 | ||
| 			}
 | ||
| 		})
 | ||
| 	}
 | ||
| }
 | ||
| 
 | ||
| func TestClientWriteToDeadlock(t *testing.T) {
 | ||
| 	for i := 0; i < 3; i++ {
 | ||
| 		clientReadDeadlock(t, i, func(f *File) {
 | ||
| 			b := make([]byte, 32768*4)
 | ||
| 
 | ||
| 			buf := bytes.NewBuffer(b)
 | ||
| 
 | ||
| 			_, err := f.WriteTo(buf)
 | ||
| 			if !errors.Is(err, errFakeNet) {
 | ||
| 				t.Fatal("Didn't receive correct error:", err)
 | ||
| 			}
 | ||
| 		})
 | ||
| 	}
 | ||
| }
 | ||
| 
 | ||
| func clientReadDeadlock(t *testing.T, N int, badfunc func(*File)) {
 | ||
| 	if !*testServerImpl {
 | ||
| 		t.Skipf("skipping without -testserver")
 | ||
| 	}
 | ||
| 	sftp, cmd := testClient(t, READWRITE, NODELAY)
 | ||
| 	defer cmd.Wait()
 | ||
| 	defer sftp.Close()
 | ||
| 
 | ||
| 	d, err := ioutil.TempDir("", "sftptest-readdeadlock")
 | ||
| 	if err != nil {
 | ||
| 		t.Fatal(err)
 | ||
| 	}
 | ||
| 	defer os.RemoveAll(d)
 | ||
| 
 | ||
| 	f := path.Join(d, "writeTest")
 | ||
| 
 | ||
| 	w, err := sftp.Create(f)
 | ||
| 	if err != nil {
 | ||
| 		t.Fatal(err)
 | ||
| 	}
 | ||
| 	defer w.Close()
 | ||
| 
 | ||
| 	// write the data for the read tests
 | ||
| 	b := make([]byte, 32768*4)
 | ||
| 	w.Write(b)
 | ||
| 
 | ||
| 	// open new copy of file for read tests
 | ||
| 	r, err := sftp.Open(f)
 | ||
| 	if err != nil {
 | ||
| 		t.Fatal(err)
 | ||
| 	}
 | ||
| 	defer r.Close()
 | ||
| 
 | ||
| 	// Override the clienConn Writer with a failing version
 | ||
| 	// Replicates network error/drop part way through (after N good writes)
 | ||
| 	wrap := sftp.clientConn.conn.WriteCloser
 | ||
| 	sftp.clientConn.conn.WriteCloser = &timeBombWriter{
 | ||
| 		count: N,
 | ||
| 		w:     wrap,
 | ||
| 	}
 | ||
| 
 | ||
| 	// this locked (before the fix)
 | ||
| 	badfunc(r)
 | ||
| }
 | ||
| 
 | ||
| func TestClientSyncGo(t *testing.T) {
 | ||
| 	if !*testServerImpl {
 | ||
| 		t.Skipf("skipping without -testserver")
 | ||
| 	}
 | ||
| 	err := testClientSync(t)
 | ||
| 
 | ||
| 	// Since Server does not support the fsync extension, we can only
 | ||
| 	// check that we get the right error.
 | ||
| 	require.Error(t, err)
 | ||
| 
 | ||
| 	switch err := err.(type) {
 | ||
| 	case *StatusError:
 | ||
| 		assert.Equal(t, ErrSSHFxOpUnsupported, err.FxCode())
 | ||
| 	default:
 | ||
| 		t.Error(err)
 | ||
| 	}
 | ||
| }
 | ||
| 
 | ||
| func TestClientSyncSFTP(t *testing.T) {
 | ||
| 	if *testServerImpl {
 | ||
| 		t.Skipf("skipping with -testserver")
 | ||
| 	}
 | ||
| 	err := testClientSync(t)
 | ||
| 	assert.NoError(t, err)
 | ||
| }
 | ||
| 
 | ||
| func testClientSync(t *testing.T) error {
 | ||
| 	sftp, cmd := testClient(t, READWRITE, NODELAY)
 | ||
| 	defer cmd.Wait()
 | ||
| 	defer sftp.Close()
 | ||
| 
 | ||
| 	d, err := ioutil.TempDir("", "sftptest.sync")
 | ||
| 	require.NoError(t, err)
 | ||
| 	defer os.RemoveAll(d)
 | ||
| 
 | ||
| 	f := path.Join(d, "syncTest")
 | ||
| 	w, err := sftp.Create(f)
 | ||
| 	require.NoError(t, err)
 | ||
| 	defer w.Close()
 | ||
| 
 | ||
| 	return w.Sync()
 | ||
| }
 | ||
| 
 | ||
| // taken from github.com/kr/fs/walk_test.go
 | ||
| 
 | ||
| type Node struct {
 | ||
| 	name    string
 | ||
| 	entries []*Node // nil if the entry is a file
 | ||
| 	mark    int
 | ||
| }
 | ||
| 
 | ||
| var tree = &Node{
 | ||
| 	"testdata",
 | ||
| 	[]*Node{
 | ||
| 		{"a", nil, 0},
 | ||
| 		{"b", []*Node{}, 0},
 | ||
| 		{"c", nil, 0},
 | ||
| 		{
 | ||
| 			"d",
 | ||
| 			[]*Node{
 | ||
| 				{"x", nil, 0},
 | ||
| 				{"y", []*Node{}, 0},
 | ||
| 				{
 | ||
| 					"z",
 | ||
| 					[]*Node{
 | ||
| 						{"u", nil, 0},
 | ||
| 						{"v", nil, 0},
 | ||
| 					},
 | ||
| 					0,
 | ||
| 				},
 | ||
| 			},
 | ||
| 			0,
 | ||
| 		},
 | ||
| 	},
 | ||
| 	0,
 | ||
| }
 | ||
| 
 | ||
| func walkTree(n *Node, path string, f func(path string, n *Node)) {
 | ||
| 	f(path, n)
 | ||
| 	for _, e := range n.entries {
 | ||
| 		walkTree(e, filepath.Join(path, e.name), f)
 | ||
| 	}
 | ||
| }
 | ||
| 
 | ||
| func makeTree(t *testing.T) {
 | ||
| 	walkTree(tree, tree.name, func(path string, n *Node) {
 | ||
| 		if n.entries == nil {
 | ||
| 			fd, err := os.Create(path)
 | ||
| 			if err != nil {
 | ||
| 				t.Errorf("makeTree: %v", err)
 | ||
| 				return
 | ||
| 			}
 | ||
| 			fd.Close()
 | ||
| 		} else {
 | ||
| 			os.Mkdir(path, 0770)
 | ||
| 		}
 | ||
| 	})
 | ||
| }
 | ||
| 
 | ||
| func markTree(n *Node) { walkTree(n, "", func(path string, n *Node) { n.mark++ }) }
 | ||
| 
 | ||
| func checkMarks(t *testing.T, report bool) {
 | ||
| 	walkTree(tree, tree.name, func(path string, n *Node) {
 | ||
| 		if n.mark != 1 && report {
 | ||
| 			t.Errorf("node %s mark = %d; expected 1", path, n.mark)
 | ||
| 		}
 | ||
| 		n.mark = 0
 | ||
| 	})
 | ||
| }
 | ||
| 
 | ||
| // Assumes that each node name is unique. Good enough for a test.
 | ||
| // If clear is true, any incoming error is cleared before return. The errors
 | ||
| // are always accumulated, though.
 | ||
| func mark(path string, info os.FileInfo, err error, errors *[]error, clear bool) error {
 | ||
| 	if err != nil {
 | ||
| 		*errors = append(*errors, err)
 | ||
| 		if clear {
 | ||
| 			return nil
 | ||
| 		}
 | ||
| 		return err
 | ||
| 	}
 | ||
| 	name := info.Name()
 | ||
| 	walkTree(tree, tree.name, func(path string, n *Node) {
 | ||
| 		if n.name == name {
 | ||
| 			n.mark++
 | ||
| 		}
 | ||
| 	})
 | ||
| 	return nil
 | ||
| }
 | ||
| 
 | ||
| func TestClientWalk(t *testing.T) {
 | ||
| 	sftp, cmd := testClient(t, READONLY, NODELAY)
 | ||
| 	defer cmd.Wait()
 | ||
| 	defer sftp.Close()
 | ||
| 
 | ||
| 	makeTree(t)
 | ||
| 	errors := make([]error, 0, 10)
 | ||
| 	clear := true
 | ||
| 	markFn := func(walker *fs.Walker) error {
 | ||
| 		for walker.Step() {
 | ||
| 			err := mark(walker.Path(), walker.Stat(), walker.Err(), &errors, clear)
 | ||
| 			if err != nil {
 | ||
| 				return err
 | ||
| 			}
 | ||
| 		}
 | ||
| 		return nil
 | ||
| 	}
 | ||
| 	// Expect no errors.
 | ||
| 	err := markFn(sftp.Walk(tree.name))
 | ||
| 	if err != nil {
 | ||
| 		t.Fatalf("no error expected, found: %s", err)
 | ||
| 	}
 | ||
| 	if len(errors) != 0 {
 | ||
| 		t.Fatalf("unexpected errors: %s", errors)
 | ||
| 	}
 | ||
| 	checkMarks(t, true)
 | ||
| 	errors = errors[0:0]
 | ||
| 
 | ||
| 	// Test permission errors.  Only possible if we're not root
 | ||
| 	// and only on some file systems (AFS, FAT).  To avoid errors during
 | ||
| 	// all.bash on those file systems, skip during go test -short.
 | ||
| 	if os.Getuid() > 0 && !testing.Short() {
 | ||
| 		// introduce 2 errors: chmod top-level directories to 0
 | ||
| 		os.Chmod(filepath.Join(tree.name, tree.entries[1].name), 0)
 | ||
| 		os.Chmod(filepath.Join(tree.name, tree.entries[3].name), 0)
 | ||
| 
 | ||
| 		// 3) capture errors, expect two.
 | ||
| 		// mark respective subtrees manually
 | ||
| 		markTree(tree.entries[1])
 | ||
| 		markTree(tree.entries[3])
 | ||
| 		// correct double-marking of directory itself
 | ||
| 		tree.entries[1].mark--
 | ||
| 		tree.entries[3].mark--
 | ||
| 		err := markFn(sftp.Walk(tree.name))
 | ||
| 		if err != nil {
 | ||
| 			t.Fatalf("expected no error return from Walk, got %s", err)
 | ||
| 		}
 | ||
| 		if len(errors) != 2 {
 | ||
| 			t.Errorf("expected 2 errors, got %d: %s", len(errors), errors)
 | ||
| 		}
 | ||
| 		// the inaccessible subtrees were marked manually
 | ||
| 		checkMarks(t, true)
 | ||
| 		errors = errors[0:0]
 | ||
| 
 | ||
| 		// 4) capture errors, stop after first error.
 | ||
| 		// mark respective subtrees manually
 | ||
| 		markTree(tree.entries[1])
 | ||
| 		markTree(tree.entries[3])
 | ||
| 		// correct double-marking of directory itself
 | ||
| 		tree.entries[1].mark--
 | ||
| 		tree.entries[3].mark--
 | ||
| 		clear = false // error will stop processing
 | ||
| 		err = markFn(sftp.Walk(tree.name))
 | ||
| 		if err == nil {
 | ||
| 			t.Fatalf("expected error return from Walk")
 | ||
| 		}
 | ||
| 		if len(errors) != 1 {
 | ||
| 			t.Errorf("expected 1 error, got %d: %s", len(errors), errors)
 | ||
| 		}
 | ||
| 		// the inaccessible subtrees were marked manually
 | ||
| 		checkMarks(t, false)
 | ||
| 		errors = errors[0:0]
 | ||
| 
 | ||
| 		// restore permissions
 | ||
| 		os.Chmod(filepath.Join(tree.name, tree.entries[1].name), 0770)
 | ||
| 		os.Chmod(filepath.Join(tree.name, tree.entries[3].name), 0770)
 | ||
| 	}
 | ||
| 
 | ||
| 	// cleanup
 | ||
| 	if err := os.RemoveAll(tree.name); err != nil {
 | ||
| 		t.Errorf("removeTree: %v", err)
 | ||
| 	}
 | ||
| }
 | ||
| 
 | ||
| type MatchTest struct {
 | ||
| 	pattern, s string
 | ||
| 	match      bool
 | ||
| 	err        error
 | ||
| }
 | ||
| 
 | ||
| var matchTests = []MatchTest{
 | ||
| 	{"abc", "abc", true, nil},
 | ||
| 	{"*", "abc", true, nil},
 | ||
| 	{"*c", "abc", true, nil},
 | ||
| 	{"a*", "a", true, nil},
 | ||
| 	{"a*", "abc", true, nil},
 | ||
| 	{"a*", "ab/c", false, nil},
 | ||
| 	{"a*/b", "abc/b", true, nil},
 | ||
| 	{"a*/b", "a/c/b", false, nil},
 | ||
| 	{"a*b*c*d*e*/f", "axbxcxdxe/f", true, nil},
 | ||
| 	{"a*b*c*d*e*/f", "axbxcxdxexxx/f", true, nil},
 | ||
| 	{"a*b*c*d*e*/f", "axbxcxdxe/xxx/f", false, nil},
 | ||
| 	{"a*b*c*d*e*/f", "axbxcxdxexxx/fff", false, nil},
 | ||
| 	{"a*b?c*x", "abxbbxdbxebxczzx", true, nil},
 | ||
| 	{"a*b?c*x", "abxbbxdbxebxczzy", false, nil},
 | ||
| 	{"ab[c]", "abc", true, nil},
 | ||
| 	{"ab[b-d]", "abc", true, nil},
 | ||
| 	{"ab[e-g]", "abc", false, nil},
 | ||
| 	{"ab[^c]", "abc", false, nil},
 | ||
| 	{"ab[^b-d]", "abc", false, nil},
 | ||
| 	{"ab[^e-g]", "abc", true, nil},
 | ||
| 	{"a\\*b", "a*b", true, nil},
 | ||
| 	{"a\\*b", "ab", false, nil},
 | ||
| 	{"a?b", "a☺b", true, nil},
 | ||
| 	{"a[^a]b", "a☺b", true, nil},
 | ||
| 	{"a???b", "a☺b", false, nil},
 | ||
| 	{"a[^a][^a][^a]b", "a☺b", false, nil},
 | ||
| 	{"[a-ζ]*", "α", true, nil},
 | ||
| 	{"*[a-ζ]", "A", false, nil},
 | ||
| 	{"a?b", "a/b", false, nil},
 | ||
| 	{"a*b", "a/b", false, nil},
 | ||
| 	{"[\\]a]", "]", true, nil},
 | ||
| 	{"[\\-]", "-", true, nil},
 | ||
| 	{"[x\\-]", "x", true, nil},
 | ||
| 	{"[x\\-]", "-", true, nil},
 | ||
| 	{"[x\\-]", "z", false, nil},
 | ||
| 	{"[\\-x]", "x", true, nil},
 | ||
| 	{"[\\-x]", "-", true, nil},
 | ||
| 	{"[\\-x]", "a", false, nil},
 | ||
| 	{"[]a]", "]", false, ErrBadPattern},
 | ||
| 	{"[-]", "-", false, ErrBadPattern},
 | ||
| 	{"[x-]", "x", false, ErrBadPattern},
 | ||
| 	{"[x-]", "-", false, ErrBadPattern},
 | ||
| 	{"[x-]", "z", false, ErrBadPattern},
 | ||
| 	{"[-x]", "x", false, ErrBadPattern},
 | ||
| 	{"[-x]", "-", false, ErrBadPattern},
 | ||
| 	{"[-x]", "a", false, ErrBadPattern},
 | ||
| 	{"\\", "a", false, ErrBadPattern},
 | ||
| 	{"[a-b-c]", "a", false, ErrBadPattern},
 | ||
| 	{"[", "a", false, ErrBadPattern},
 | ||
| 	{"[^", "a", false, ErrBadPattern},
 | ||
| 	{"[^bc", "a", false, ErrBadPattern},
 | ||
| 	{"a[", "ab", false, ErrBadPattern},
 | ||
| 	{"*x", "xxx", true, nil},
 | ||
| 
 | ||
| 	// The following test behaves differently on Go 1.15.3 and Go tip as
 | ||
| 	// https://github.com/golang/go/commit/b5ddc42b465dd5b9532ee336d98343d81a6d35b2
 | ||
| 	// (pre-Go 1.16). TODO: reevaluate when Go 1.16 is released.
 | ||
| 	//{"a[", "a", false, nil},
 | ||
| }
 | ||
| 
 | ||
| func errp(e error) string {
 | ||
| 	if e == nil {
 | ||
| 		return "<nil>"
 | ||
| 	}
 | ||
| 	return e.Error()
 | ||
| }
 | ||
| 
 | ||
| // contains returns true if vector contains the string s.
 | ||
| func contains(vector []string, s string) bool {
 | ||
| 	for _, elem := range vector {
 | ||
| 		if elem == s {
 | ||
| 			return true
 | ||
| 		}
 | ||
| 	}
 | ||
| 	return false
 | ||
| }
 | ||
| 
 | ||
| var globTests = []struct {
 | ||
| 	pattern, result string
 | ||
| }{
 | ||
| 	{"match.go", "match.go"},
 | ||
| 	{"mat?h.go", "match.go"},
 | ||
| 	{"ma*ch.go", "match.go"},
 | ||
| 	{`\m\a\t\c\h\.\g\o`, "match.go"},
 | ||
| 	{"../*/match.go", "../sftp/match.go"},
 | ||
| }
 | ||
| 
 | ||
| type globTest struct {
 | ||
| 	pattern string
 | ||
| 	matches []string
 | ||
| }
 | ||
| 
 | ||
| func (test *globTest) buildWant(root string) []string {
 | ||
| 	var want []string
 | ||
| 	for _, m := range test.matches {
 | ||
| 		want = append(want, root+filepath.FromSlash(m))
 | ||
| 	}
 | ||
| 	sort.Strings(want)
 | ||
| 	return want
 | ||
| }
 | ||
| 
 | ||
| func TestMatch(t *testing.T) {
 | ||
| 	for _, tt := range matchTests {
 | ||
| 		pattern := tt.pattern
 | ||
| 		s := tt.s
 | ||
| 		ok, err := Match(pattern, s)
 | ||
| 		if ok != tt.match || err != tt.err {
 | ||
| 			t.Errorf("Match(%#q, %#q) = %v, %q want %v, %q", pattern, s, ok, errp(err), tt.match, errp(tt.err))
 | ||
| 		}
 | ||
| 	}
 | ||
| }
 | ||
| 
 | ||
| func TestGlob(t *testing.T) {
 | ||
| 	sftp, cmd := testClient(t, READONLY, NODELAY)
 | ||
| 	defer cmd.Wait()
 | ||
| 	defer sftp.Close()
 | ||
| 
 | ||
| 	for _, tt := range globTests {
 | ||
| 		pattern := tt.pattern
 | ||
| 		result := tt.result
 | ||
| 		matches, err := sftp.Glob(pattern)
 | ||
| 		if err != nil {
 | ||
| 			t.Errorf("Glob error for %q: %s", pattern, err)
 | ||
| 			continue
 | ||
| 		}
 | ||
| 		if !contains(matches, result) {
 | ||
| 			t.Errorf("Glob(%#q) = %#v want %v", pattern, matches, result)
 | ||
| 		}
 | ||
| 	}
 | ||
| 	for _, pattern := range []string{"no_match", "../*/no_match"} {
 | ||
| 		matches, err := sftp.Glob(pattern)
 | ||
| 		if err != nil {
 | ||
| 			t.Errorf("Glob error for %q: %s", pattern, err)
 | ||
| 			continue
 | ||
| 		}
 | ||
| 		if len(matches) != 0 {
 | ||
| 			t.Errorf("Glob(%#q) = %#v want []", pattern, matches)
 | ||
| 		}
 | ||
| 	}
 | ||
| }
 | ||
| 
 | ||
| func TestGlobError(t *testing.T) {
 | ||
| 	sftp, cmd := testClient(t, READONLY, NODELAY)
 | ||
| 	defer cmd.Wait()
 | ||
| 	defer sftp.Close()
 | ||
| 
 | ||
| 	_, err := sftp.Glob("[7]")
 | ||
| 	if err != nil {
 | ||
| 		t.Error("expected error for bad pattern; got none")
 | ||
| 	}
 | ||
| }
 | ||
| 
 | ||
| func TestGlobUNC(t *testing.T) {
 | ||
| 	sftp, cmd := testClient(t, READONLY, NODELAY)
 | ||
| 	defer cmd.Wait()
 | ||
| 	defer sftp.Close()
 | ||
| 	// Just make sure this runs without crashing for now.
 | ||
| 	// See issue 15879.
 | ||
| 	sftp.Glob(`\\?\C:\*`)
 | ||
| }
 | ||
| 
 | ||
| // sftp/issue/42, abrupt server hangup would result in client hangs.
 | ||
| func TestServerRoughDisconnect(t *testing.T) {
 | ||
| 	skipIfWindows(t)
 | ||
| 	if *testServerImpl {
 | ||
| 		t.Skipf("skipping with -testserver")
 | ||
| 	}
 | ||
| 	sftp, cmd := testClient(t, READONLY, NODELAY)
 | ||
| 	defer cmd.Wait()
 | ||
| 	defer sftp.Close()
 | ||
| 
 | ||
| 	f, err := sftp.Open("/dev/zero")
 | ||
| 	if err != nil {
 | ||
| 		t.Fatal(err)
 | ||
| 	}
 | ||
| 	defer f.Close()
 | ||
| 	go func() {
 | ||
| 		time.Sleep(100 * time.Millisecond)
 | ||
| 		cmd.Process.Kill()
 | ||
| 	}()
 | ||
| 
 | ||
| 	_, err = io.Copy(ioutil.Discard, f)
 | ||
| 	assert.Error(t, err)
 | ||
| }
 | ||
| 
 | ||
| // sftp/issue/181, abrupt server hangup would result in client hangs.
 | ||
| // due to broadcastErr filling up the request channel
 | ||
| // this reproduces it about 50% of the time
 | ||
| func TestServerRoughDisconnect2(t *testing.T) {
 | ||
| 	skipIfWindows(t)
 | ||
| 	if *testServerImpl {
 | ||
| 		t.Skipf("skipping with -testserver")
 | ||
| 	}
 | ||
| 	sftp, cmd := testClient(t, READONLY, NODELAY)
 | ||
| 	defer cmd.Wait()
 | ||
| 	defer sftp.Close()
 | ||
| 
 | ||
| 	f, err := sftp.Open("/dev/zero")
 | ||
| 	if err != nil {
 | ||
| 		t.Fatal(err)
 | ||
| 	}
 | ||
| 	defer f.Close()
 | ||
| 	b := make([]byte, 32768*100)
 | ||
| 	go func() {
 | ||
| 		time.Sleep(1 * time.Millisecond)
 | ||
| 		cmd.Process.Kill()
 | ||
| 	}()
 | ||
| 	for {
 | ||
| 		_, err = f.Read(b)
 | ||
| 		if err != nil {
 | ||
| 			break
 | ||
| 		}
 | ||
| 	}
 | ||
| }
 | ||
| 
 | ||
| // sftp/issue/234 - abrupt shutdown during ReadFrom hangs client
 | ||
| func TestServerRoughDisconnect3(t *testing.T) {
 | ||
| 	skipIfWindows(t)
 | ||
| 	if *testServerImpl {
 | ||
| 		t.Skipf("skipping with -testserver")
 | ||
| 	}
 | ||
| 
 | ||
| 	sftp, cmd := testClient(t, READWRITE, NODELAY)
 | ||
| 	defer cmd.Wait()
 | ||
| 	defer sftp.Close()
 | ||
| 
 | ||
| 	dest, err := sftp.OpenFile("/dev/null", os.O_RDWR)
 | ||
| 	if err != nil {
 | ||
| 		t.Fatal(err)
 | ||
| 	}
 | ||
| 	defer dest.Close()
 | ||
| 
 | ||
| 	src, err := os.Open("/dev/zero")
 | ||
| 	if err != nil {
 | ||
| 		t.Fatal(err)
 | ||
| 	}
 | ||
| 	defer src.Close()
 | ||
| 
 | ||
| 	go func() {
 | ||
| 		time.Sleep(10 * time.Millisecond)
 | ||
| 		cmd.Process.Kill()
 | ||
| 	}()
 | ||
| 
 | ||
| 	_, err = io.Copy(dest, src)
 | ||
| 	assert.Error(t, err)
 | ||
| }
 | ||
| 
 | ||
| // sftp/issue/234 - also affected Write
 | ||
| func TestServerRoughDisconnect4(t *testing.T) {
 | ||
| 	skipIfWindows(t)
 | ||
| 	if *testServerImpl {
 | ||
| 		t.Skipf("skipping with -testserver")
 | ||
| 	}
 | ||
| 	sftp, cmd := testClient(t, READWRITE, NODELAY)
 | ||
| 	defer cmd.Wait()
 | ||
| 	defer sftp.Close()
 | ||
| 
 | ||
| 	dest, err := sftp.OpenFile("/dev/null", os.O_RDWR)
 | ||
| 	if err != nil {
 | ||
| 		t.Fatal(err)
 | ||
| 	}
 | ||
| 	defer dest.Close()
 | ||
| 
 | ||
| 	src, err := os.Open("/dev/zero")
 | ||
| 	if err != nil {
 | ||
| 		t.Fatal(err)
 | ||
| 	}
 | ||
| 	defer src.Close()
 | ||
| 
 | ||
| 	go func() {
 | ||
| 		time.Sleep(10 * time.Millisecond)
 | ||
| 		cmd.Process.Kill()
 | ||
| 	}()
 | ||
| 
 | ||
| 	b := make([]byte, 32768*200)
 | ||
| 	src.Read(b)
 | ||
| 	for {
 | ||
| 		_, err = dest.Write(b)
 | ||
| 		if err != nil {
 | ||
| 			assert.NotEqual(t, io.EOF, err)
 | ||
| 			break
 | ||
| 		}
 | ||
| 	}
 | ||
| 
 | ||
| 	_, err = io.Copy(dest, src)
 | ||
| 	assert.Error(t, err)
 | ||
| }
 | ||
| 
 | ||
| // sftp/issue/390 - server disconnect should not cause io.EOF or
 | ||
| // io.ErrUnexpectedEOF in sftp.File.Read, because those confuse io.ReadFull.
 | ||
| func TestServerRoughDisconnectEOF(t *testing.T) {
 | ||
| 	skipIfWindows(t)
 | ||
| 	if *testServerImpl {
 | ||
| 		t.Skipf("skipping with -testserver")
 | ||
| 	}
 | ||
| 	sftp, cmd := testClient(t, READONLY, NODELAY)
 | ||
| 	defer cmd.Wait()
 | ||
| 	defer sftp.Close()
 | ||
| 
 | ||
| 	f, err := sftp.Open("/dev/null")
 | ||
| 	if err != nil {
 | ||
| 		t.Fatal(err)
 | ||
| 	}
 | ||
| 	defer f.Close()
 | ||
| 	go func() {
 | ||
| 		time.Sleep(100 * time.Millisecond)
 | ||
| 		cmd.Process.Kill()
 | ||
| 	}()
 | ||
| 
 | ||
| 	_, err = io.ReadFull(f, make([]byte, 10))
 | ||
| 	assert.Error(t, err)
 | ||
| 	assert.NotEqual(t, io.ErrUnexpectedEOF, err)
 | ||
| }
 | ||
| 
 | ||
| // sftp/issue/26 writing to a read only file caused client to loop.
 | ||
| func TestClientWriteToROFile(t *testing.T) {
 | ||
| 	skipIfWindows(t)
 | ||
| 
 | ||
| 	sftp, cmd := testClient(t, READWRITE, NODELAY)
 | ||
| 	defer cmd.Wait()
 | ||
| 
 | ||
| 	defer func() {
 | ||
| 		err := sftp.Close()
 | ||
| 		assert.NoError(t, err)
 | ||
| 	}()
 | ||
| 
 | ||
| 	// TODO (puellanivis): /dev/zero is not actually a read-only file.
 | ||
| 	// So, this test works purely by accident.
 | ||
| 	f, err := sftp.Open("/dev/zero")
 | ||
| 	if err != nil {
 | ||
| 		t.Fatal(err)
 | ||
| 	}
 | ||
| 	defer f.Close()
 | ||
| 
 | ||
| 	_, err = f.Write([]byte("hello"))
 | ||
| 	if err == nil {
 | ||
| 		t.Fatal("expected error, got", err)
 | ||
| 	}
 | ||
| }
 | ||
| 
 | ||
| func benchmarkRead(b *testing.B, bufsize int, delay time.Duration) {
 | ||
| 	skipIfWindows(b)
 | ||
| 	size := 10*1024*1024 + 123 // ~10MiB
 | ||
| 
 | ||
| 	// open sftp client
 | ||
| 	sftp, cmd := testClient(b, READONLY, delay)
 | ||
| 	defer cmd.Wait()
 | ||
| 	defer sftp.Close()
 | ||
| 
 | ||
| 	buf := make([]byte, bufsize)
 | ||
| 
 | ||
| 	b.ResetTimer()
 | ||
| 	b.SetBytes(int64(size))
 | ||
| 
 | ||
| 	for i := 0; i < b.N; i++ {
 | ||
| 		offset := 0
 | ||
| 
 | ||
| 		f2, err := sftp.Open("/dev/zero")
 | ||
| 		if err != nil {
 | ||
| 			b.Fatal(err)
 | ||
| 		}
 | ||
| 
 | ||
| 		for offset < size {
 | ||
| 			n, err := io.ReadFull(f2, buf)
 | ||
| 			offset += n
 | ||
| 			if err == io.ErrUnexpectedEOF && offset != size {
 | ||
| 				b.Fatalf("read too few bytes! want: %d, got: %d", size, n)
 | ||
| 			}
 | ||
| 
 | ||
| 			if err != nil {
 | ||
| 				b.Fatal(err)
 | ||
| 			}
 | ||
| 
 | ||
| 			offset += n
 | ||
| 		}
 | ||
| 
 | ||
| 		f2.Close()
 | ||
| 	}
 | ||
| }
 | ||
| 
 | ||
| func BenchmarkRead1k(b *testing.B) {
 | ||
| 	benchmarkRead(b, 1*1024, NODELAY)
 | ||
| }
 | ||
| 
 | ||
| func BenchmarkRead16k(b *testing.B) {
 | ||
| 	benchmarkRead(b, 16*1024, NODELAY)
 | ||
| }
 | ||
| 
 | ||
| func BenchmarkRead32k(b *testing.B) {
 | ||
| 	benchmarkRead(b, 32*1024, NODELAY)
 | ||
| }
 | ||
| 
 | ||
| func BenchmarkRead128k(b *testing.B) {
 | ||
| 	benchmarkRead(b, 128*1024, NODELAY)
 | ||
| }
 | ||
| 
 | ||
| func BenchmarkRead512k(b *testing.B) {
 | ||
| 	benchmarkRead(b, 512*1024, NODELAY)
 | ||
| }
 | ||
| 
 | ||
| func BenchmarkRead1MiB(b *testing.B) {
 | ||
| 	benchmarkRead(b, 1024*1024, NODELAY)
 | ||
| }
 | ||
| 
 | ||
| func BenchmarkRead4MiB(b *testing.B) {
 | ||
| 	benchmarkRead(b, 4*1024*1024, NODELAY)
 | ||
| }
 | ||
| 
 | ||
| func BenchmarkRead4MiBDelay10Msec(b *testing.B) {
 | ||
| 	benchmarkRead(b, 4*1024*1024, 10*time.Millisecond)
 | ||
| }
 | ||
| 
 | ||
| func BenchmarkRead4MiBDelay50Msec(b *testing.B) {
 | ||
| 	benchmarkRead(b, 4*1024*1024, 50*time.Millisecond)
 | ||
| }
 | ||
| 
 | ||
| func BenchmarkRead4MiBDelay150Msec(b *testing.B) {
 | ||
| 	benchmarkRead(b, 4*1024*1024, 150*time.Millisecond)
 | ||
| }
 | ||
| 
 | ||
| func benchmarkWrite(b *testing.B, bufsize int, delay time.Duration) {
 | ||
| 	size := 10*1024*1024 + 123 // ~10MiB
 | ||
| 
 | ||
| 	// open sftp client
 | ||
| 	sftp, cmd := testClient(b, false, delay)
 | ||
| 	defer cmd.Wait()
 | ||
| 	defer sftp.Close()
 | ||
| 
 | ||
| 	data := make([]byte, size)
 | ||
| 
 | ||
| 	b.ResetTimer()
 | ||
| 	b.SetBytes(int64(size))
 | ||
| 
 | ||
| 	for i := 0; i < b.N; i++ {
 | ||
| 		offset := 0
 | ||
| 
 | ||
| 		f, err := ioutil.TempFile("", "sftptest-benchwrite")
 | ||
| 		if err != nil {
 | ||
| 			b.Fatal(err)
 | ||
| 		}
 | ||
| 		defer os.Remove(f.Name()) // actually queue up a series of removes for these files
 | ||
| 
 | ||
| 		f2, err := sftp.Create(f.Name())
 | ||
| 		if err != nil {
 | ||
| 			b.Fatal(err)
 | ||
| 		}
 | ||
| 
 | ||
| 		for offset < size {
 | ||
| 			buf := data[offset:]
 | ||
| 			if len(buf) > bufsize {
 | ||
| 				buf = buf[:bufsize]
 | ||
| 			}
 | ||
| 
 | ||
| 			n, err := f2.Write(buf)
 | ||
| 			if err != nil {
 | ||
| 				b.Fatal(err)
 | ||
| 			}
 | ||
| 
 | ||
| 			if offset+n < size && n != bufsize {
 | ||
| 				b.Fatalf("wrote too few bytes! want: %d, got: %d", size, n)
 | ||
| 			}
 | ||
| 
 | ||
| 			offset += n
 | ||
| 		}
 | ||
| 
 | ||
| 		f2.Close()
 | ||
| 
 | ||
| 		fi, err := os.Stat(f.Name())
 | ||
| 		if err != nil {
 | ||
| 			b.Fatal(err)
 | ||
| 		}
 | ||
| 
 | ||
| 		if fi.Size() != int64(size) {
 | ||
| 			b.Fatalf("wrong file size: want %d, got %d", size, fi.Size())
 | ||
| 		}
 | ||
| 
 | ||
| 		os.Remove(f.Name())
 | ||
| 	}
 | ||
| }
 | ||
| 
 | ||
| func BenchmarkWrite1k(b *testing.B) {
 | ||
| 	benchmarkWrite(b, 1*1024, NODELAY)
 | ||
| }
 | ||
| 
 | ||
| func BenchmarkWrite16k(b *testing.B) {
 | ||
| 	benchmarkWrite(b, 16*1024, NODELAY)
 | ||
| }
 | ||
| 
 | ||
| func BenchmarkWrite32k(b *testing.B) {
 | ||
| 	benchmarkWrite(b, 32*1024, NODELAY)
 | ||
| }
 | ||
| 
 | ||
| func BenchmarkWrite128k(b *testing.B) {
 | ||
| 	benchmarkWrite(b, 128*1024, NODELAY)
 | ||
| }
 | ||
| 
 | ||
| func BenchmarkWrite512k(b *testing.B) {
 | ||
| 	benchmarkWrite(b, 512*1024, NODELAY)
 | ||
| }
 | ||
| 
 | ||
| func BenchmarkWrite1MiB(b *testing.B) {
 | ||
| 	benchmarkWrite(b, 1024*1024, NODELAY)
 | ||
| }
 | ||
| 
 | ||
| func BenchmarkWrite4MiB(b *testing.B) {
 | ||
| 	benchmarkWrite(b, 4*1024*1024, NODELAY)
 | ||
| }
 | ||
| 
 | ||
| func BenchmarkWrite4MiBDelay10Msec(b *testing.B) {
 | ||
| 	benchmarkWrite(b, 4*1024*1024, 10*time.Millisecond)
 | ||
| }
 | ||
| 
 | ||
| func BenchmarkWrite4MiBDelay50Msec(b *testing.B) {
 | ||
| 	benchmarkWrite(b, 4*1024*1024, 50*time.Millisecond)
 | ||
| }
 | ||
| 
 | ||
| func BenchmarkWrite4MiBDelay150Msec(b *testing.B) {
 | ||
| 	benchmarkWrite(b, 4*1024*1024, 150*time.Millisecond)
 | ||
| }
 | ||
| 
 | ||
| func benchmarkReadFrom(b *testing.B, bufsize int, delay time.Duration) {
 | ||
| 	size := 10*1024*1024 + 123 // ~10MiB
 | ||
| 
 | ||
| 	// open sftp client
 | ||
| 	sftp, cmd := testClient(b, false, delay)
 | ||
| 	defer cmd.Wait()
 | ||
| 	defer sftp.Close()
 | ||
| 
 | ||
| 	data := make([]byte, size)
 | ||
| 
 | ||
| 	b.ResetTimer()
 | ||
| 	b.SetBytes(int64(size))
 | ||
| 
 | ||
| 	for i := 0; i < b.N; i++ {
 | ||
| 		f, err := ioutil.TempFile("", "sftptest-benchreadfrom")
 | ||
| 		if err != nil {
 | ||
| 			b.Fatal(err)
 | ||
| 		}
 | ||
| 		defer os.Remove(f.Name())
 | ||
| 
 | ||
| 		f2, err := sftp.Create(f.Name())
 | ||
| 		if err != nil {
 | ||
| 			b.Fatal(err)
 | ||
| 		}
 | ||
| 		defer f2.Close()
 | ||
| 
 | ||
| 		f2.ReadFrom(bytes.NewReader(data))
 | ||
| 		f2.Close()
 | ||
| 
 | ||
| 		fi, err := os.Stat(f.Name())
 | ||
| 		if err != nil {
 | ||
| 			b.Fatal(err)
 | ||
| 		}
 | ||
| 
 | ||
| 		if fi.Size() != int64(size) {
 | ||
| 			b.Fatalf("wrong file size: want %d, got %d", size, fi.Size())
 | ||
| 		}
 | ||
| 
 | ||
| 		os.Remove(f.Name())
 | ||
| 	}
 | ||
| }
 | ||
| 
 | ||
| func BenchmarkReadFrom1k(b *testing.B) {
 | ||
| 	benchmarkReadFrom(b, 1*1024, NODELAY)
 | ||
| }
 | ||
| 
 | ||
| func BenchmarkReadFrom16k(b *testing.B) {
 | ||
| 	benchmarkReadFrom(b, 16*1024, NODELAY)
 | ||
| }
 | ||
| 
 | ||
| func BenchmarkReadFrom32k(b *testing.B) {
 | ||
| 	benchmarkReadFrom(b, 32*1024, NODELAY)
 | ||
| }
 | ||
| 
 | ||
| func BenchmarkReadFrom128k(b *testing.B) {
 | ||
| 	benchmarkReadFrom(b, 128*1024, NODELAY)
 | ||
| }
 | ||
| 
 | ||
| func BenchmarkReadFrom512k(b *testing.B) {
 | ||
| 	benchmarkReadFrom(b, 512*1024, NODELAY)
 | ||
| }
 | ||
| 
 | ||
| func BenchmarkReadFrom1MiB(b *testing.B) {
 | ||
| 	benchmarkReadFrom(b, 1024*1024, NODELAY)
 | ||
| }
 | ||
| 
 | ||
| func BenchmarkReadFrom4MiB(b *testing.B) {
 | ||
| 	benchmarkReadFrom(b, 4*1024*1024, NODELAY)
 | ||
| }
 | ||
| 
 | ||
| func BenchmarkReadFrom4MiBDelay10Msec(b *testing.B) {
 | ||
| 	benchmarkReadFrom(b, 4*1024*1024, 10*time.Millisecond)
 | ||
| }
 | ||
| 
 | ||
| func BenchmarkReadFrom4MiBDelay50Msec(b *testing.B) {
 | ||
| 	benchmarkReadFrom(b, 4*1024*1024, 50*time.Millisecond)
 | ||
| }
 | ||
| 
 | ||
| func BenchmarkReadFrom4MiBDelay150Msec(b *testing.B) {
 | ||
| 	benchmarkReadFrom(b, 4*1024*1024, 150*time.Millisecond)
 | ||
| }
 | ||
| 
 | ||
| func benchmarkWriteTo(b *testing.B, bufsize int, delay time.Duration) {
 | ||
| 	size := 10*1024*1024 + 123 // ~10MiB
 | ||
| 
 | ||
| 	// open sftp client
 | ||
| 	sftp, cmd := testClient(b, false, delay)
 | ||
| 	defer cmd.Wait()
 | ||
| 	defer sftp.Close()
 | ||
| 
 | ||
| 	f, err := ioutil.TempFile("", "sftptest-benchwriteto")
 | ||
| 	if err != nil {
 | ||
| 		b.Fatal(err)
 | ||
| 	}
 | ||
| 	defer os.Remove(f.Name())
 | ||
| 
 | ||
| 	data := make([]byte, size)
 | ||
| 
 | ||
| 	f.Write(data)
 | ||
| 	f.Close()
 | ||
| 
 | ||
| 	buf := bytes.NewBuffer(make([]byte, 0, size))
 | ||
| 
 | ||
| 	b.ResetTimer()
 | ||
| 	b.SetBytes(int64(size))
 | ||
| 
 | ||
| 	for i := 0; i < b.N; i++ {
 | ||
| 		buf.Reset()
 | ||
| 
 | ||
| 		f2, err := sftp.Open(f.Name())
 | ||
| 		if err != nil {
 | ||
| 			b.Fatal(err)
 | ||
| 		}
 | ||
| 
 | ||
| 		f2.WriteTo(buf)
 | ||
| 		f2.Close()
 | ||
| 
 | ||
| 		if buf.Len() != size {
 | ||
| 			b.Fatalf("wrote buffer size: want %d, got %d", size, buf.Len())
 | ||
| 		}
 | ||
| 	}
 | ||
| }
 | ||
| 
 | ||
| func BenchmarkWriteTo1k(b *testing.B) {
 | ||
| 	benchmarkWriteTo(b, 1*1024, NODELAY)
 | ||
| }
 | ||
| 
 | ||
| func BenchmarkWriteTo16k(b *testing.B) {
 | ||
| 	benchmarkWriteTo(b, 16*1024, NODELAY)
 | ||
| }
 | ||
| 
 | ||
| func BenchmarkWriteTo32k(b *testing.B) {
 | ||
| 	benchmarkWriteTo(b, 32*1024, NODELAY)
 | ||
| }
 | ||
| 
 | ||
| func BenchmarkWriteTo128k(b *testing.B) {
 | ||
| 	benchmarkWriteTo(b, 128*1024, NODELAY)
 | ||
| }
 | ||
| 
 | ||
| func BenchmarkWriteTo512k(b *testing.B) {
 | ||
| 	benchmarkWriteTo(b, 512*1024, NODELAY)
 | ||
| }
 | ||
| 
 | ||
| func BenchmarkWriteTo1MiB(b *testing.B) {
 | ||
| 	benchmarkWriteTo(b, 1024*1024, NODELAY)
 | ||
| }
 | ||
| 
 | ||
| func BenchmarkWriteTo4MiB(b *testing.B) {
 | ||
| 	benchmarkWriteTo(b, 4*1024*1024, NODELAY)
 | ||
| }
 | ||
| 
 | ||
| func BenchmarkWriteTo4MiBDelay10Msec(b *testing.B) {
 | ||
| 	benchmarkWriteTo(b, 4*1024*1024, 10*time.Millisecond)
 | ||
| }
 | ||
| 
 | ||
| func BenchmarkWriteTo4MiBDelay50Msec(b *testing.B) {
 | ||
| 	benchmarkWriteTo(b, 4*1024*1024, 50*time.Millisecond)
 | ||
| }
 | ||
| 
 | ||
| func BenchmarkWriteTo4MiBDelay150Msec(b *testing.B) {
 | ||
| 	benchmarkWriteTo(b, 4*1024*1024, 150*time.Millisecond)
 | ||
| }
 | ||
| 
 | ||
| func benchmarkCopyDown(b *testing.B, fileSize int64, delay time.Duration) {
 | ||
| 	skipIfWindows(b)
 | ||
| 	// Create a temp file and fill it with zero's.
 | ||
| 	src, err := ioutil.TempFile("", "sftptest-benchcopydown")
 | ||
| 	if err != nil {
 | ||
| 		b.Fatal(err)
 | ||
| 	}
 | ||
| 	defer src.Close()
 | ||
| 	srcFilename := src.Name()
 | ||
| 	defer os.Remove(srcFilename)
 | ||
| 	zero, err := os.Open("/dev/zero")
 | ||
| 	if err != nil {
 | ||
| 		b.Fatal(err)
 | ||
| 	}
 | ||
| 	n, err := io.Copy(src, io.LimitReader(zero, fileSize))
 | ||
| 	if err != nil {
 | ||
| 		b.Fatal(err)
 | ||
| 	}
 | ||
| 	if n < fileSize {
 | ||
| 		b.Fatal("short copy")
 | ||
| 	}
 | ||
| 	zero.Close()
 | ||
| 	src.Close()
 | ||
| 
 | ||
| 	sftp, cmd := testClient(b, READONLY, delay)
 | ||
| 	defer cmd.Wait()
 | ||
| 	defer sftp.Close()
 | ||
| 	b.ResetTimer()
 | ||
| 	b.SetBytes(fileSize)
 | ||
| 
 | ||
| 	for i := 0; i < b.N; i++ {
 | ||
| 		dst, err := ioutil.TempFile("", "sftptest-benchcopydown")
 | ||
| 		if err != nil {
 | ||
| 			b.Fatal(err)
 | ||
| 		}
 | ||
| 		defer os.Remove(dst.Name())
 | ||
| 
 | ||
| 		src, err := sftp.Open(srcFilename)
 | ||
| 		if err != nil {
 | ||
| 			b.Fatal(err)
 | ||
| 		}
 | ||
| 		defer src.Close()
 | ||
| 		n, err := io.Copy(dst, src)
 | ||
| 		if err != nil {
 | ||
| 			b.Fatalf("copy error: %v", err)
 | ||
| 		}
 | ||
| 		if n < fileSize {
 | ||
| 			b.Fatal("unable to copy all bytes")
 | ||
| 		}
 | ||
| 		dst.Close()
 | ||
| 		fi, err := os.Stat(dst.Name())
 | ||
| 		if err != nil {
 | ||
| 			b.Fatal(err)
 | ||
| 		}
 | ||
| 
 | ||
| 		if fi.Size() != fileSize {
 | ||
| 			b.Fatalf("wrong file size: want %d, got %d", fileSize, fi.Size())
 | ||
| 		}
 | ||
| 		os.Remove(dst.Name())
 | ||
| 	}
 | ||
| }
 | ||
| 
 | ||
| func BenchmarkCopyDown10MiBDelay10Msec(b *testing.B) {
 | ||
| 	benchmarkCopyDown(b, 10*1024*1024, 10*time.Millisecond)
 | ||
| }
 | ||
| 
 | ||
| func BenchmarkCopyDown10MiBDelay50Msec(b *testing.B) {
 | ||
| 	benchmarkCopyDown(b, 10*1024*1024, 50*time.Millisecond)
 | ||
| }
 | ||
| 
 | ||
| func BenchmarkCopyDown10MiBDelay150Msec(b *testing.B) {
 | ||
| 	benchmarkCopyDown(b, 10*1024*1024, 150*time.Millisecond)
 | ||
| }
 | ||
| 
 | ||
| func benchmarkCopyUp(b *testing.B, fileSize int64, delay time.Duration) {
 | ||
| 	skipIfWindows(b)
 | ||
| 	// Create a temp file and fill it with zero's.
 | ||
| 	src, err := ioutil.TempFile("", "sftptest-benchcopyup")
 | ||
| 	if err != nil {
 | ||
| 		b.Fatal(err)
 | ||
| 	}
 | ||
| 	defer src.Close()
 | ||
| 	srcFilename := src.Name()
 | ||
| 	defer os.Remove(srcFilename)
 | ||
| 	zero, err := os.Open("/dev/zero")
 | ||
| 	if err != nil {
 | ||
| 		b.Fatal(err)
 | ||
| 	}
 | ||
| 	n, err := io.Copy(src, io.LimitReader(zero, fileSize))
 | ||
| 	if err != nil {
 | ||
| 		b.Fatal(err)
 | ||
| 	}
 | ||
| 	if n < fileSize {
 | ||
| 		b.Fatal("short copy")
 | ||
| 	}
 | ||
| 	zero.Close()
 | ||
| 	src.Close()
 | ||
| 
 | ||
| 	sftp, cmd := testClient(b, false, delay)
 | ||
| 	defer cmd.Wait()
 | ||
| 	defer sftp.Close()
 | ||
| 
 | ||
| 	b.ResetTimer()
 | ||
| 	b.SetBytes(fileSize)
 | ||
| 
 | ||
| 	for i := 0; i < b.N; i++ {
 | ||
| 		tmp, err := ioutil.TempFile("", "sftptest-benchcopyup")
 | ||
| 		if err != nil {
 | ||
| 			b.Fatal(err)
 | ||
| 		}
 | ||
| 		tmp.Close()
 | ||
| 		defer os.Remove(tmp.Name())
 | ||
| 
 | ||
| 		dst, err := sftp.Create(tmp.Name())
 | ||
| 		if err != nil {
 | ||
| 			b.Fatal(err)
 | ||
| 		}
 | ||
| 		defer dst.Close()
 | ||
| 		src, err := os.Open(srcFilename)
 | ||
| 		if err != nil {
 | ||
| 			b.Fatal(err)
 | ||
| 		}
 | ||
| 		defer src.Close()
 | ||
| 		n, err := io.Copy(dst, src)
 | ||
| 		if err != nil {
 | ||
| 			b.Fatalf("copy error: %v", err)
 | ||
| 		}
 | ||
| 		if n < fileSize {
 | ||
| 			b.Fatal("unable to copy all bytes")
 | ||
| 		}
 | ||
| 
 | ||
| 		fi, err := os.Stat(tmp.Name())
 | ||
| 		if err != nil {
 | ||
| 			b.Fatal(err)
 | ||
| 		}
 | ||
| 
 | ||
| 		if fi.Size() != fileSize {
 | ||
| 			b.Fatalf("wrong file size: want %d, got %d", fileSize, fi.Size())
 | ||
| 		}
 | ||
| 		os.Remove(tmp.Name())
 | ||
| 	}
 | ||
| }
 | ||
| 
 | ||
| func BenchmarkCopyUp10MiBDelay10Msec(b *testing.B) {
 | ||
| 	benchmarkCopyUp(b, 10*1024*1024, 10*time.Millisecond)
 | ||
| }
 | ||
| 
 | ||
| func BenchmarkCopyUp10MiBDelay50Msec(b *testing.B) {
 | ||
| 	benchmarkCopyUp(b, 10*1024*1024, 50*time.Millisecond)
 | ||
| }
 | ||
| 
 | ||
| func BenchmarkCopyUp10MiBDelay150Msec(b *testing.B) {
 | ||
| 	benchmarkCopyUp(b, 10*1024*1024, 150*time.Millisecond)
 | ||
| }
 |