2013-11-06 12:40:35 +08:00
|
|
|
|
package sftp
|
|
|
|
|
|
|
|
|
|
// sftp integration tests
|
|
|
|
|
// enable with -integration
|
|
|
|
|
|
|
|
|
|
import (
|
2017-02-13 11:37:13 +08:00
|
|
|
|
"bytes"
|
2013-11-15 18:47:28 +08:00
|
|
|
|
"crypto/sha1"
|
2017-02-13 15:18:54 +08:00
|
|
|
|
"errors"
|
2013-11-08 19:11:02 +08:00
|
|
|
|
"io"
|
2013-11-06 12:40:35 +08:00
|
|
|
|
"io/ioutil"
|
2014-06-12 03:17:53 +08:00
|
|
|
|
"math/rand"
|
2016-05-29 15:22:48 +08:00
|
|
|
|
"net"
|
2013-11-06 12:40:35 +08:00
|
|
|
|
"os"
|
|
|
|
|
"os/exec"
|
2015-08-05 11:47:26 +08:00
|
|
|
|
"os/user"
|
2013-11-08 18:00:26 +08:00
|
|
|
|
"path"
|
2013-11-08 18:24:50 +08:00
|
|
|
|
"path/filepath"
|
2014-06-12 03:17:53 +08:00
|
|
|
|
"reflect"
|
2015-08-05 11:47:26 +08:00
|
|
|
|
"regexp"
|
2019-01-19 07:59:30 +08:00
|
|
|
|
"sort"
|
2015-08-05 11:47:26 +08:00
|
|
|
|
"strconv"
|
2020-10-29 04:03:11 +08:00
|
|
|
|
"sync"
|
2013-11-06 12:40:35 +08:00
|
|
|
|
"testing"
|
2014-06-12 03:17:53 +08:00
|
|
|
|
"testing/quick"
|
2015-08-05 11:47:26 +08:00
|
|
|
|
"time"
|
2013-11-08 18:24:50 +08:00
|
|
|
|
|
2017-03-15 03:27:51 +08:00
|
|
|
|
"github.com/kr/fs"
|
2020-10-11 20:03:31 +08:00
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
|
|
|
"github.com/stretchr/testify/require"
|
2013-11-06 12:40:35 +08:00
|
|
|
|
)
|
|
|
|
|
|
2013-11-06 16:29:59 +08:00
|
|
|
|
const (
|
2015-06-03 00:36:35 +08:00
|
|
|
|
READONLY = true
|
|
|
|
|
READWRITE = false
|
2019-08-30 23:04:37 +08:00
|
|
|
|
NODELAY time.Duration = 0
|
2013-11-08 18:00:26 +08:00
|
|
|
|
|
2013-11-08 18:24:50 +08:00
|
|
|
|
debuglevel = "ERROR" // set to "DEBUG" for debugging
|
2013-11-06 16:29:59 +08:00
|
|
|
|
)
|
|
|
|
|
|
2015-06-03 00:36:35 +08:00
|
|
|
|
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{}
|
2020-10-29 04:03:11 +08:00
|
|
|
|
|
|
|
|
|
mu sync.Mutex
|
2020-11-15 07:53:25 +08:00
|
|
|
|
ch chan delayedWrite
|
2020-10-29 04:03:11 +08:00
|
|
|
|
closing chan struct{}
|
2015-06-03 00:36:35 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func newDelayedWriter(w io.WriteCloser, delay time.Duration) io.WriteCloser {
|
2020-10-29 04:03:11 +08:00
|
|
|
|
dw := &delayedWriter{
|
|
|
|
|
ch: make(chan delayedWrite, 128),
|
|
|
|
|
closed: make(chan struct{}),
|
|
|
|
|
closing: make(chan struct{}),
|
|
|
|
|
}
|
|
|
|
|
|
2015-06-03 00:36:35 +08:00
|
|
|
|
go func() {
|
2020-11-15 07:53:25 +08:00
|
|
|
|
defer close(dw.closed)
|
|
|
|
|
defer w.Close()
|
|
|
|
|
|
2020-10-29 04:03:11 +08:00
|
|
|
|
for writeMsg := range dw.ch {
|
2018-02-16 03:17:19 +08:00
|
|
|
|
time.Sleep(time.Until(writeMsg.t.Add(delay)))
|
2020-11-15 07:53:25 +08:00
|
|
|
|
|
2015-06-03 00:36:35 +08:00
|
|
|
|
n, err := w.Write(writeMsg.b)
|
|
|
|
|
if err != nil {
|
|
|
|
|
panic("write error")
|
|
|
|
|
}
|
2020-11-15 07:53:25 +08:00
|
|
|
|
|
2015-06-03 00:36:35 +08:00
|
|
|
|
if n < len(writeMsg.b) {
|
|
|
|
|
panic("showrt write")
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}()
|
2020-10-29 04:03:11 +08:00
|
|
|
|
|
|
|
|
|
return dw
|
2015-06-03 00:36:35 +08:00
|
|
|
|
}
|
|
|
|
|
|
2020-10-29 04:03:11 +08:00
|
|
|
|
func (dw *delayedWriter) Write(b []byte) (int, error) {
|
2020-11-15 07:53:25 +08:00
|
|
|
|
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:
|
|
|
|
|
}
|
|
|
|
|
|
2015-06-03 00:36:35 +08:00
|
|
|
|
return len(b), nil
|
|
|
|
|
}
|
|
|
|
|
|
2020-10-29 04:03:11 +08:00
|
|
|
|
func (dw *delayedWriter) Close() error {
|
|
|
|
|
dw.mu.Lock()
|
2020-11-15 07:53:25 +08:00
|
|
|
|
defer dw.mu.Unlock()
|
|
|
|
|
|
2020-10-29 04:03:11 +08:00
|
|
|
|
select {
|
|
|
|
|
case <-dw.closing:
|
|
|
|
|
default:
|
|
|
|
|
close(dw.ch)
|
|
|
|
|
close(dw.closing)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
<-dw.closed
|
2015-06-03 00:36:35 +08:00
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
2016-05-29 15:22:48 +08:00
|
|
|
|
// netPipe provides a pair of io.ReadWriteClosers connected to each other.
|
|
|
|
|
// The functions is identical to os.Pipe with the exception that netPipe
|
2017-07-04 04:14:51 +08:00
|
|
|
|
// provides the Read/Close guarantees that os.File derrived pipes do not.
|
2016-05-29 15:22:48 +08:00
|
|
|
|
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")
|
2016-01-01 22:31:52 +08:00
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
2016-05-29 15:22:48 +08:00
|
|
|
|
|
2020-11-15 07:53:25 +08:00
|
|
|
|
closeListener := make(chan struct{}, 1)
|
|
|
|
|
closeListener <- struct{}{}
|
|
|
|
|
|
2016-05-29 15:22:48 +08:00
|
|
|
|
ch := make(chan result, 1)
|
|
|
|
|
go func() {
|
|
|
|
|
conn, err := l.Accept()
|
|
|
|
|
ch <- result{conn, err}
|
2020-11-15 07:53:25 +08:00
|
|
|
|
|
|
|
|
|
if _, ok := <-closeListener; ok {
|
|
|
|
|
err = l.Close()
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Error(err)
|
|
|
|
|
}
|
|
|
|
|
close(closeListener)
|
2016-05-29 15:22:48 +08:00
|
|
|
|
}
|
|
|
|
|
}()
|
2020-11-15 07:53:25 +08:00
|
|
|
|
|
2016-05-29 15:22:48 +08:00
|
|
|
|
c1, err := net.Dial("tcp", l.Addr().String())
|
2016-01-01 22:31:52 +08:00
|
|
|
|
if err != nil {
|
2020-11-15 07:53:25 +08:00
|
|
|
|
if _, ok := <-closeListener; ok {
|
|
|
|
|
l.Close()
|
|
|
|
|
close(closeListener)
|
|
|
|
|
}
|
2016-01-01 22:31:52 +08:00
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
2020-11-15 07:53:25 +08:00
|
|
|
|
|
2016-05-29 15:22:48 +08:00
|
|
|
|
r := <-ch
|
|
|
|
|
if r.error != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
2020-11-15 07:53:25 +08:00
|
|
|
|
|
2016-05-29 15:22:48 +08:00
|
|
|
|
return c1, r.Conn
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func testClientGoSvr(t testing.TB, readonly bool, delay time.Duration) (*Client, *exec.Cmd) {
|
|
|
|
|
c1, c2 := netPipe(t)
|
2015-09-07 10:36:47 +08:00
|
|
|
|
|
2016-01-11 04:10:18 +08:00
|
|
|
|
options := []ServerOption{WithDebug(os.Stderr)}
|
|
|
|
|
if readonly {
|
|
|
|
|
options = append(options, ReadOnly())
|
|
|
|
|
}
|
|
|
|
|
|
2016-05-29 15:22:48 +08:00
|
|
|
|
server, err := NewServer(c1, options...)
|
2015-09-07 10:36:47 +08:00
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
2015-09-07 14:55:15 +08:00
|
|
|
|
go server.Serve()
|
2015-09-07 10:36:47 +08:00
|
|
|
|
|
2020-11-15 07:53:25 +08:00
|
|
|
|
var wr io.WriteCloser = c2
|
2019-08-30 23:04:37 +08:00
|
|
|
|
if delay > NODELAY {
|
2020-11-15 07:53:25 +08:00
|
|
|
|
wr = newDelayedWriter(wr, delay)
|
2015-09-07 10:36:47 +08:00
|
|
|
|
}
|
|
|
|
|
|
2020-11-15 07:53:25 +08:00
|
|
|
|
client, err := NewClientPipe(c2, wr)
|
2015-09-07 10:36:47 +08:00
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// dummy command...
|
|
|
|
|
return client, exec.Command("true")
|
|
|
|
|
}
|
|
|
|
|
|
2020-10-23 18:54:04 +08:00
|
|
|
|
// testClient returns a *Client connected to a locally running sftp-server
|
2013-11-06 12:40:35 +08:00
|
|
|
|
// the *exec.Cmd returned must be defer Wait'd.
|
2015-06-03 00:36:35 +08:00
|
|
|
|
func testClient(t testing.TB, readonly bool, delay time.Duration) (*Client, *exec.Cmd) {
|
2013-11-08 18:00:26 +08:00
|
|
|
|
if !*testIntegration {
|
2020-10-23 18:54:04 +08:00
|
|
|
|
t.Skip("skipping integration test")
|
2013-11-08 18:00:26 +08:00
|
|
|
|
}
|
2015-08-01 14:09:51 +08:00
|
|
|
|
|
|
|
|
|
if *testServerImpl {
|
2015-09-07 10:36:47 +08:00
|
|
|
|
return testClientGoSvr(t, readonly, delay)
|
2015-08-01 14:09:51 +08:00
|
|
|
|
}
|
|
|
|
|
|
2014-01-05 05:50:15 +08:00
|
|
|
|
cmd := exec.Command(*testSftp, "-e", "-R", "-l", debuglevel) // log to stderr, read only
|
2013-11-06 16:29:59 +08:00
|
|
|
|
if !readonly {
|
2014-01-05 05:50:15 +08:00
|
|
|
|
cmd = exec.Command(*testSftp, "-e", "-l", debuglevel) // log to stderr
|
2013-11-06 16:29:59 +08:00
|
|
|
|
}
|
2020-11-15 07:53:25 +08:00
|
|
|
|
|
2013-11-06 12:40:35 +08:00
|
|
|
|
cmd.Stderr = os.Stdout
|
2020-11-15 07:53:25 +08:00
|
|
|
|
|
2013-11-06 12:40:35 +08:00
|
|
|
|
pw, err := cmd.StdinPipe()
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
2020-11-15 07:53:25 +08:00
|
|
|
|
|
2019-08-30 23:04:37 +08:00
|
|
|
|
if delay > NODELAY {
|
2015-06-03 00:36:35 +08:00
|
|
|
|
pw = newDelayedWriter(pw, delay)
|
|
|
|
|
}
|
2020-11-15 07:53:25 +08:00
|
|
|
|
|
2013-11-06 12:40:35 +08:00
|
|
|
|
pr, err := cmd.StdoutPipe()
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
2020-11-15 07:53:25 +08:00
|
|
|
|
|
2013-11-06 12:40:35 +08:00
|
|
|
|
if err := cmd.Start(); err != nil {
|
|
|
|
|
t.Skipf("could not start sftp-server process: %v", err)
|
|
|
|
|
}
|
2014-09-30 06:43:45 +08:00
|
|
|
|
|
|
|
|
|
sftp, err := NewClientPipe(pr, pw)
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatal(err)
|
2013-11-06 12:40:35 +08:00
|
|
|
|
}
|
2014-09-30 06:43:45 +08:00
|
|
|
|
|
2013-11-06 12:40:35 +08:00
|
|
|
|
return sftp, cmd
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestNewClient(t *testing.T) {
|
2019-08-30 23:04:37 +08:00
|
|
|
|
sftp, cmd := testClient(t, READONLY, NODELAY)
|
2013-11-06 12:40:35 +08:00
|
|
|
|
defer cmd.Wait()
|
|
|
|
|
|
|
|
|
|
if err := sftp.Close(); err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestClientLstat(t *testing.T) {
|
2019-08-30 23:04:37 +08:00
|
|
|
|
sftp, cmd := testClient(t, READONLY, NODELAY)
|
2013-11-06 12:40:35 +08:00
|
|
|
|
defer cmd.Wait()
|
|
|
|
|
defer sftp.Close()
|
|
|
|
|
|
2020-10-23 18:56:36 +08:00
|
|
|
|
f, err := ioutil.TempFile("", "sftptest-lstat")
|
2013-11-06 12:40:35 +08:00
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
2019-01-19 07:59:30 +08:00
|
|
|
|
f.Close()
|
2013-11-06 12:40:35 +08:00
|
|
|
|
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)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2016-01-04 03:54:19 +08:00
|
|
|
|
func TestClientLstatIsNotExist(t *testing.T) {
|
2019-08-30 23:04:37 +08:00
|
|
|
|
sftp, cmd := testClient(t, READONLY, NODELAY)
|
2013-11-06 16:29:59 +08:00
|
|
|
|
defer cmd.Wait()
|
|
|
|
|
defer sftp.Close()
|
|
|
|
|
|
2020-10-23 18:56:36 +08:00
|
|
|
|
f, err := ioutil.TempFile("", "sftptest-lstatisnotexist")
|
2013-11-06 16:29:59 +08:00
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
2019-01-19 07:59:30 +08:00
|
|
|
|
f.Close()
|
2013-11-06 16:29:59 +08:00
|
|
|
|
os.Remove(f.Name())
|
|
|
|
|
|
2016-01-04 03:54:19 +08:00
|
|
|
|
if _, err := sftp.Lstat(f.Name()); !os.IsNotExist(err) {
|
|
|
|
|
t.Errorf("os.IsNotExist(%v) = false, want true", err)
|
2013-11-06 16:29:59 +08:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2014-09-28 10:52:54 +08:00
|
|
|
|
func TestClientMkdir(t *testing.T) {
|
2019-08-30 23:04:37 +08:00
|
|
|
|
sftp, cmd := testClient(t, READWRITE, NODELAY)
|
2014-09-30 05:32:16 +08:00
|
|
|
|
defer cmd.Wait()
|
|
|
|
|
defer sftp.Close()
|
|
|
|
|
|
2020-10-23 18:56:36 +08:00
|
|
|
|
dir, err := ioutil.TempDir("", "sftptest-mkdir")
|
2014-09-30 05:32:16 +08:00
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
2020-10-23 18:56:36 +08:00
|
|
|
|
defer os.RemoveAll(dir)
|
|
|
|
|
|
2014-09-28 10:52:54 +08:00
|
|
|
|
sub := path.Join(dir, "mkdir1")
|
2014-09-30 05:32:16 +08:00
|
|
|
|
if err := sftp.Mkdir(sub); err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
if _, err := os.Lstat(sub); err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
2014-09-28 10:52:54 +08:00
|
|
|
|
}
|
2018-04-25 14:58:10 +08:00
|
|
|
|
func TestClientMkdirAll(t *testing.T) {
|
2019-08-30 23:04:37 +08:00
|
|
|
|
sftp, cmd := testClient(t, READWRITE, NODELAY)
|
2018-04-25 14:58:10 +08:00
|
|
|
|
defer cmd.Wait()
|
|
|
|
|
defer sftp.Close()
|
|
|
|
|
|
2020-10-23 18:56:36 +08:00
|
|
|
|
dir, err := ioutil.TempDir("", "sftptest-mkdirall")
|
2018-04-25 14:58:10 +08:00
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
2020-10-23 18:56:36 +08:00
|
|
|
|
defer os.RemoveAll(dir)
|
|
|
|
|
|
2018-04-25 14:58:10 +08:00
|
|
|
|
sub := path.Join(dir, "mkdir1", "mkdir2", "mkdir3")
|
|
|
|
|
if err := sftp.MkdirAll(sub); err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
2018-04-25 23:11:13 +08:00
|
|
|
|
info, err := os.Lstat(sub)
|
|
|
|
|
if err != nil {
|
2018-04-25 14:58:10 +08:00
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
2018-04-25 23:11:13 +08:00
|
|
|
|
if !info.IsDir() {
|
|
|
|
|
t.Fatalf("Expected mkdirall to create dir at: %s", sub)
|
|
|
|
|
}
|
2018-04-25 14:58:10 +08:00
|
|
|
|
}
|
2014-09-28 10:52:54 +08:00
|
|
|
|
|
2013-11-06 12:53:14 +08:00
|
|
|
|
func TestClientOpen(t *testing.T) {
|
2019-08-30 23:04:37 +08:00
|
|
|
|
sftp, cmd := testClient(t, READONLY, NODELAY)
|
2013-11-06 12:53:14 +08:00
|
|
|
|
defer cmd.Wait()
|
|
|
|
|
defer sftp.Close()
|
|
|
|
|
|
2020-10-23 18:56:36 +08:00
|
|
|
|
f, err := ioutil.TempFile("", "sftptest-open")
|
2013-11-06 12:53:14 +08:00
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
2019-01-19 07:59:30 +08:00
|
|
|
|
f.Close()
|
2013-11-06 12:53:14 +08:00
|
|
|
|
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)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2016-01-04 03:54:19 +08:00
|
|
|
|
func TestClientOpenIsNotExist(t *testing.T) {
|
2019-08-30 23:04:37 +08:00
|
|
|
|
sftp, cmd := testClient(t, READONLY, NODELAY)
|
2016-01-04 03:54:19 +08:00
|
|
|
|
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) {
|
2019-08-30 23:04:37 +08:00
|
|
|
|
sftp, cmd := testClient(t, READONLY, NODELAY)
|
2016-01-04 03:54:19 +08:00
|
|
|
|
defer cmd.Wait()
|
|
|
|
|
defer sftp.Close()
|
|
|
|
|
|
|
|
|
|
if _, err := sftp.Stat("/doesnt/exist/"); !os.IsNotExist(err) {
|
|
|
|
|
t.Errorf("os.IsNotExist(%v) = false, want true", err)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2014-06-12 03:17:53 +08:00
|
|
|
|
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)
|
|
|
|
|
}
|
|
|
|
|
|
2014-06-13 16:22:04 +08:00
|
|
|
|
func (s seek) set(t *testing.T, r io.ReadSeeker) {
|
2018-02-16 02:27:33 +08:00
|
|
|
|
if _, err := r.Seek(s.offset, io.SeekStart); err != nil {
|
2014-06-13 16:22:04 +08:00
|
|
|
|
t.Fatalf("error while seeking with %+v: %v", s, err)
|
2014-06-12 03:17:53 +08:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2014-06-13 16:22:04 +08:00
|
|
|
|
func (s seek) current(t *testing.T, r io.ReadSeeker) {
|
2014-06-12 03:17:53 +08:00
|
|
|
|
const mid = seekBytes / 2
|
|
|
|
|
|
2014-06-13 16:22:04 +08:00
|
|
|
|
skip := s.offset / 2
|
|
|
|
|
if s.offset > mid {
|
2014-06-12 03:17:53 +08:00
|
|
|
|
skip = -skip
|
|
|
|
|
}
|
|
|
|
|
|
2018-02-16 02:27:33 +08:00
|
|
|
|
if _, err := r.Seek(mid, io.SeekStart); err != nil {
|
2014-06-13 16:22:04 +08:00
|
|
|
|
t.Fatalf("error seeking to midpoint with %+v: %v", s, err)
|
2014-06-12 03:17:53 +08:00
|
|
|
|
}
|
2018-02-16 02:27:33 +08:00
|
|
|
|
if _, err := r.Seek(skip, io.SeekCurrent); err != nil {
|
2014-06-13 16:22:04 +08:00
|
|
|
|
t.Fatalf("error seeking from %d with %+v: %v", mid, s, err)
|
2014-06-12 03:17:53 +08:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2014-06-13 16:22:04 +08:00
|
|
|
|
func (s seek) end(t *testing.T, r io.ReadSeeker) {
|
2018-02-16 02:27:33 +08:00
|
|
|
|
if _, err := r.Seek(-s.offset, io.SeekEnd); err != nil {
|
2014-06-13 16:22:04 +08:00
|
|
|
|
t.Fatalf("error seeking from end with %+v: %v", s, err)
|
2014-06-12 03:17:53 +08:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestClientSeek(t *testing.T) {
|
2019-08-30 23:04:37 +08:00
|
|
|
|
sftp, cmd := testClient(t, READONLY, NODELAY)
|
2014-06-12 03:17:53 +08:00
|
|
|
|
defer cmd.Wait()
|
|
|
|
|
defer sftp.Close()
|
|
|
|
|
|
2020-10-23 18:56:36 +08:00
|
|
|
|
fOS, err := ioutil.TempFile("", "sftptest-seek")
|
2014-06-12 03:17:53 +08:00
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
2020-10-23 18:56:36 +08:00
|
|
|
|
defer os.Remove(fOS.Name())
|
2014-06-12 03:17:53 +08:00
|
|
|
|
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)
|
|
|
|
|
}
|
2013-11-08 19:11:02 +08:00
|
|
|
|
}
|
|
|
|
|
|
2013-11-06 16:29:59 +08:00
|
|
|
|
func TestClientCreate(t *testing.T) {
|
2019-08-30 23:04:37 +08:00
|
|
|
|
sftp, cmd := testClient(t, READWRITE, NODELAY)
|
2013-11-06 16:29:59 +08:00
|
|
|
|
defer cmd.Wait()
|
|
|
|
|
defer sftp.Close()
|
|
|
|
|
|
2020-10-23 18:56:36 +08:00
|
|
|
|
f, err := ioutil.TempFile("", "sftptest-create")
|
2013-11-06 16:29:59 +08:00
|
|
|
|
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()
|
|
|
|
|
}
|
|
|
|
|
|
2013-11-14 12:32:21 +08:00
|
|
|
|
func TestClientAppend(t *testing.T) {
|
2019-08-30 23:04:37 +08:00
|
|
|
|
sftp, cmd := testClient(t, READWRITE, NODELAY)
|
2013-11-14 12:32:21 +08:00
|
|
|
|
defer cmd.Wait()
|
|
|
|
|
defer sftp.Close()
|
|
|
|
|
|
2020-10-23 18:56:36 +08:00
|
|
|
|
f, err := ioutil.TempFile("", "sftptest-append")
|
2013-11-14 12:32:21 +08:00
|
|
|
|
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()
|
|
|
|
|
}
|
|
|
|
|
|
2013-11-06 16:29:59 +08:00
|
|
|
|
func TestClientCreateFailed(t *testing.T) {
|
2019-08-30 23:04:37 +08:00
|
|
|
|
sftp, cmd := testClient(t, READONLY, NODELAY)
|
2013-11-06 16:29:59 +08:00
|
|
|
|
defer cmd.Wait()
|
|
|
|
|
defer sftp.Close()
|
|
|
|
|
|
2020-10-23 18:56:36 +08:00
|
|
|
|
f, err := ioutil.TempFile("", "sftptest-createfailed")
|
2020-12-08 19:14:21 +08:00
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
2013-11-06 16:29:59 +08:00
|
|
|
|
defer f.Close()
|
|
|
|
|
defer os.Remove(f.Name())
|
|
|
|
|
|
|
|
|
|
f2, err := sftp.Create(f.Name())
|
2020-12-08 19:14:21 +08:00
|
|
|
|
require.True(t, os.IsPermission(err))
|
2013-11-06 16:29:59 +08:00
|
|
|
|
if err == nil {
|
|
|
|
|
f2.Close()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2015-09-25 16:05:59 +08:00
|
|
|
|
func TestClientFileName(t *testing.T) {
|
2019-08-30 23:04:37 +08:00
|
|
|
|
sftp, cmd := testClient(t, READONLY, NODELAY)
|
2015-09-25 16:05:59 +08:00
|
|
|
|
defer cmd.Wait()
|
|
|
|
|
defer sftp.Close()
|
|
|
|
|
|
2020-10-23 18:56:36 +08:00
|
|
|
|
f, err := ioutil.TempFile("", "sftptest-filename")
|
2015-09-25 16:05:59 +08:00
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
defer os.Remove(f.Name())
|
|
|
|
|
|
|
|
|
|
f2, err := sftp.Open(f.Name())
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
2020-11-15 07:53:25 +08:00
|
|
|
|
defer f2.Close()
|
2015-09-25 16:05:59 +08:00
|
|
|
|
|
|
|
|
|
if got, want := f2.Name(), f.Name(); got != want {
|
2015-09-25 16:17:46 +08:00
|
|
|
|
t.Fatalf("Name: got %q want %q", want, got)
|
2015-09-25 16:05:59 +08:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2013-11-06 13:03:08 +08:00
|
|
|
|
func TestClientFileStat(t *testing.T) {
|
2019-08-30 23:04:37 +08:00
|
|
|
|
sftp, cmd := testClient(t, READONLY, NODELAY)
|
2013-11-06 13:03:08 +08:00
|
|
|
|
defer cmd.Wait()
|
|
|
|
|
defer sftp.Close()
|
|
|
|
|
|
2020-10-23 18:56:36 +08:00
|
|
|
|
f, err := ioutil.TempFile("", "sftptest-filestat")
|
2013-11-06 13:03:08 +08:00
|
|
|
|
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)
|
|
|
|
|
}
|
2020-11-15 07:53:25 +08:00
|
|
|
|
defer f2.Close()
|
2013-11-06 13:03:08 +08:00
|
|
|
|
|
|
|
|
|
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)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2016-04-27 03:07:21 +08:00
|
|
|
|
func TestClientStatLink(t *testing.T) {
|
2019-01-19 07:59:30 +08:00
|
|
|
|
skipIfWindows(t) // Windows does not support links.
|
|
|
|
|
|
2019-08-30 23:04:37 +08:00
|
|
|
|
sftp, cmd := testClient(t, READONLY, NODELAY)
|
2016-04-27 03:07:21 +08:00
|
|
|
|
defer cmd.Wait()
|
|
|
|
|
defer sftp.Close()
|
|
|
|
|
|
2020-10-23 18:56:36 +08:00
|
|
|
|
f, err := ioutil.TempFile("", "sftptest-statlink")
|
2016-04-27 03:07:21 +08:00
|
|
|
|
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)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2013-11-08 18:56:25 +08:00
|
|
|
|
func TestClientRemove(t *testing.T) {
|
2019-08-30 23:04:37 +08:00
|
|
|
|
sftp, cmd := testClient(t, READWRITE, NODELAY)
|
2013-11-08 18:56:25 +08:00
|
|
|
|
defer cmd.Wait()
|
|
|
|
|
defer sftp.Close()
|
|
|
|
|
|
2020-10-23 18:56:36 +08:00
|
|
|
|
f, err := ioutil.TempFile("", "sftptest-remove")
|
2013-11-08 18:56:25 +08:00
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
2020-10-23 18:56:36 +08:00
|
|
|
|
defer os.Remove(f.Name())
|
2019-01-19 07:59:30 +08:00
|
|
|
|
f.Close()
|
|
|
|
|
|
2013-11-08 18:56:25 +08:00
|
|
|
|
if err := sftp.Remove(f.Name()); err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
if _, err := os.Lstat(f.Name()); !os.IsNotExist(err) {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2014-09-28 10:48:54 +08:00
|
|
|
|
func TestClientRemoveDir(t *testing.T) {
|
2019-08-30 23:04:37 +08:00
|
|
|
|
sftp, cmd := testClient(t, READWRITE, NODELAY)
|
2014-09-28 10:48:54 +08:00
|
|
|
|
defer cmd.Wait()
|
|
|
|
|
defer sftp.Close()
|
|
|
|
|
|
2020-10-23 18:56:36 +08:00
|
|
|
|
dir, err := ioutil.TempDir("", "sftptest-removedir")
|
2014-09-28 10:48:54 +08:00
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
2020-10-23 18:56:36 +08:00
|
|
|
|
defer os.RemoveAll(dir)
|
|
|
|
|
|
2014-09-28 10:48:54 +08:00
|
|
|
|
if err := sftp.Remove(dir); err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
if _, err := os.Lstat(dir); !os.IsNotExist(err) {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2013-11-08 18:56:25 +08:00
|
|
|
|
func TestClientRemoveFailed(t *testing.T) {
|
2019-08-30 23:04:37 +08:00
|
|
|
|
sftp, cmd := testClient(t, READONLY, NODELAY)
|
2013-11-08 18:56:25 +08:00
|
|
|
|
defer cmd.Wait()
|
|
|
|
|
defer sftp.Close()
|
|
|
|
|
|
2020-10-23 18:56:36 +08:00
|
|
|
|
f, err := ioutil.TempFile("", "sftptest-removefailed")
|
2013-11-08 18:56:25 +08:00
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
2020-10-23 18:56:36 +08:00
|
|
|
|
defer os.Remove(f.Name())
|
|
|
|
|
|
2013-11-08 18:56:25 +08:00
|
|
|
|
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)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2013-11-08 19:11:02 +08:00
|
|
|
|
func TestClientRename(t *testing.T) {
|
2019-08-30 23:04:37 +08:00
|
|
|
|
sftp, cmd := testClient(t, READWRITE, NODELAY)
|
2013-11-08 19:11:02 +08:00
|
|
|
|
defer cmd.Wait()
|
|
|
|
|
defer sftp.Close()
|
|
|
|
|
|
2020-10-23 18:56:36 +08:00
|
|
|
|
dir, err := ioutil.TempDir("", "sftptest-rename")
|
2020-10-29 23:02:25 +08:00
|
|
|
|
require.NoError(t, err)
|
2020-10-23 18:56:36 +08:00
|
|
|
|
defer os.RemoveAll(dir)
|
|
|
|
|
f, err := os.Create(filepath.Join(dir, "old"))
|
2020-10-29 23:02:25 +08:00
|
|
|
|
require.NoError(t, err)
|
2019-01-19 07:59:30 +08:00
|
|
|
|
f.Close()
|
|
|
|
|
|
2020-10-23 18:56:36 +08:00
|
|
|
|
f2 := filepath.Join(dir, "new")
|
2013-11-08 19:11:02 +08:00
|
|
|
|
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)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2017-09-05 19:46:03 +08:00
|
|
|
|
func TestClientPosixRename(t *testing.T) {
|
2019-08-30 23:04:37 +08:00
|
|
|
|
sftp, cmd := testClient(t, READWRITE, NODELAY)
|
2017-09-05 19:46:03 +08:00
|
|
|
|
defer cmd.Wait()
|
|
|
|
|
defer sftp.Close()
|
|
|
|
|
|
2020-10-23 18:56:36 +08:00
|
|
|
|
dir, err := ioutil.TempDir("", "sftptest-posixrename")
|
2020-10-29 23:02:25 +08:00
|
|
|
|
require.NoError(t, err)
|
2020-10-23 18:56:36 +08:00
|
|
|
|
defer os.RemoveAll(dir)
|
|
|
|
|
f, err := os.Create(filepath.Join(dir, "old"))
|
2020-10-29 23:02:25 +08:00
|
|
|
|
require.NoError(t, err)
|
2019-01-19 07:59:30 +08:00
|
|
|
|
f.Close()
|
|
|
|
|
|
2020-10-23 18:56:36 +08:00
|
|
|
|
f2 := filepath.Join(dir, "new")
|
2017-09-05 19:46:03 +08:00
|
|
|
|
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)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2015-12-22 05:45:40 +08:00
|
|
|
|
func TestClientGetwd(t *testing.T) {
|
2019-08-30 23:04:37 +08:00
|
|
|
|
sftp, cmd := testClient(t, READONLY, NODELAY)
|
2015-12-22 05:45:40 +08:00
|
|
|
|
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)
|
|
|
|
|
}
|
2019-01-19 07:59:30 +08:00
|
|
|
|
if !filepath.IsAbs(rwd) {
|
2015-12-22 05:45:40 +08:00
|
|
|
|
t.Fatalf("Getwd: wanted absolute path, got %q", rwd)
|
|
|
|
|
}
|
2019-01-19 07:59:30 +08:00
|
|
|
|
if filepath.ToSlash(lwd) != filepath.ToSlash(rwd) {
|
2015-12-22 05:45:40 +08:00
|
|
|
|
t.Fatalf("Getwd: want %q, got %q", lwd, rwd)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2015-08-01 06:46:13 +08:00
|
|
|
|
func TestClientReadLink(t *testing.T) {
|
2019-08-30 23:04:37 +08:00
|
|
|
|
sftp, cmd := testClient(t, READWRITE, NODELAY)
|
2014-09-28 10:48:54 +08:00
|
|
|
|
defer cmd.Wait()
|
|
|
|
|
defer sftp.Close()
|
|
|
|
|
|
2020-10-23 18:56:36 +08:00
|
|
|
|
dir, err := ioutil.TempDir("", "sftptest-readlink")
|
2020-10-29 23:02:25 +08:00
|
|
|
|
require.NoError(t, err)
|
2020-10-23 18:56:36 +08:00
|
|
|
|
defer os.RemoveAll(dir)
|
|
|
|
|
f, err := os.Create(filepath.Join(dir, "file"))
|
2020-10-29 23:02:25 +08:00
|
|
|
|
require.NoError(t, err)
|
2020-10-23 18:56:36 +08:00
|
|
|
|
f.Close()
|
|
|
|
|
|
|
|
|
|
f2 := filepath.Join(dir, "symlink")
|
2014-09-28 10:48:54 +08:00
|
|
|
|
if err := os.Symlink(f.Name(), f2); err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
2015-09-07 16:05:16 +08:00
|
|
|
|
if rl, err := sftp.ReadLink(f2); err != nil {
|
2014-09-28 10:48:54 +08:00
|
|
|
|
t.Fatal(err)
|
2015-09-07 16:05:16 +08:00
|
|
|
|
} else if rl != f.Name() {
|
|
|
|
|
t.Fatalf("unexpected link target: %v, not %v", rl, f.Name())
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2019-05-25 03:23:18 +08:00
|
|
|
|
func TestClientLink(t *testing.T) {
|
2019-08-30 23:04:37 +08:00
|
|
|
|
sftp, cmd := testClient(t, READWRITE, NODELAY)
|
2019-05-25 03:23:18 +08:00
|
|
|
|
defer cmd.Wait()
|
|
|
|
|
defer sftp.Close()
|
|
|
|
|
|
2020-10-23 18:56:36 +08:00
|
|
|
|
dir, err := ioutil.TempDir("", "sftptest-link")
|
2020-10-29 23:02:25 +08:00
|
|
|
|
require.NoError(t, err)
|
2020-10-23 18:56:36 +08:00
|
|
|
|
defer os.RemoveAll(dir)
|
|
|
|
|
|
|
|
|
|
f, err := os.Create(filepath.Join(dir, "file"))
|
2020-10-29 23:02:25 +08:00
|
|
|
|
require.NoError(t, err)
|
2019-05-25 03:23:18 +08:00
|
|
|
|
data := []byte("linktest")
|
|
|
|
|
_, err = f.Write(data)
|
|
|
|
|
f.Close()
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
2020-10-23 18:56:36 +08:00
|
|
|
|
|
|
|
|
|
f2 := filepath.Join(dir, "link")
|
2019-05-25 03:23:18 +08:00
|
|
|
|
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))
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2015-09-07 16:05:16 +08:00
|
|
|
|
func TestClientSymlink(t *testing.T) {
|
2019-08-30 23:04:37 +08:00
|
|
|
|
sftp, cmd := testClient(t, READWRITE, NODELAY)
|
2015-09-07 16:05:16 +08:00
|
|
|
|
defer cmd.Wait()
|
|
|
|
|
defer sftp.Close()
|
|
|
|
|
|
2020-10-23 18:56:36 +08:00
|
|
|
|
dir, err := ioutil.TempDir("", "sftptest-symlink")
|
2020-10-29 23:02:25 +08:00
|
|
|
|
require.NoError(t, err)
|
2020-10-23 18:56:36 +08:00
|
|
|
|
defer os.RemoveAll(dir)
|
|
|
|
|
f, err := os.Create(filepath.Join(dir, "file"))
|
2020-10-29 23:02:25 +08:00
|
|
|
|
require.NoError(t, err)
|
2020-10-23 18:56:36 +08:00
|
|
|
|
f.Close()
|
|
|
|
|
|
|
|
|
|
f2 := filepath.Join(dir, "symlink")
|
2015-09-07 16:05:16 +08:00
|
|
|
|
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())
|
2014-09-28 10:48:54 +08:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2015-08-05 11:47:26 +08:00
|
|
|
|
func TestClientChmod(t *testing.T) {
|
2019-01-19 07:59:30 +08:00
|
|
|
|
skipIfWindows(t) // No UNIX permissions.
|
2019-08-30 23:04:37 +08:00
|
|
|
|
sftp, cmd := testClient(t, READWRITE, NODELAY)
|
2015-08-05 11:47:26 +08:00
|
|
|
|
defer cmd.Wait()
|
|
|
|
|
defer sftp.Close()
|
|
|
|
|
|
2020-10-31 21:34:16 +08:00
|
|
|
|
f, err := ioutil.TempFile("", "sftptest-chmod")
|
2015-08-05 11:47:26 +08:00
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
2020-10-23 18:56:36 +08:00
|
|
|
|
defer os.Remove(f.Name())
|
2019-01-19 07:59:30 +08:00
|
|
|
|
f.Close()
|
|
|
|
|
|
2015-08-05 11:47:26 +08:00
|
|
|
|
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())
|
|
|
|
|
}
|
2021-03-17 00:56:03 +08:00
|
|
|
|
|
|
|
|
|
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())
|
2015-08-05 11:47:26 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestClientChmodReadonly(t *testing.T) {
|
2019-01-19 07:59:30 +08:00
|
|
|
|
skipIfWindows(t) // No UNIX permissions.
|
2019-08-30 23:04:37 +08:00
|
|
|
|
sftp, cmd := testClient(t, READONLY, NODELAY)
|
2015-08-05 11:47:26 +08:00
|
|
|
|
defer cmd.Wait()
|
|
|
|
|
defer sftp.Close()
|
|
|
|
|
|
2020-10-31 21:34:16 +08:00
|
|
|
|
f, err := ioutil.TempFile("", "sftptest-chmodreadonly")
|
2015-08-05 11:47:26 +08:00
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
2020-10-23 18:56:36 +08:00
|
|
|
|
defer os.Remove(f.Name())
|
2019-01-19 07:59:30 +08:00
|
|
|
|
f.Close()
|
|
|
|
|
|
2015-08-05 11:47:26 +08:00
|
|
|
|
if err := sftp.Chmod(f.Name(), 0531); err == nil {
|
|
|
|
|
t.Fatal("expected error")
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
Support os.Mode{Setuid,Setgid,Sticky} in Chmod
Previously, these bits were ignored by Chmod, which sent the numerical
value of the mode argument as-is to the server. As a result, callers had
to supply POSIX values for setuid, setgid and sticky to Chmod.
The new version supports both the POSIX values and the Go values.
Also added a note to the docs to clarify that the umask is not
subtracted from the mode, and why that is. The only portable way to get
the umask is to set it, then reset it, but that's racy. On Linux, we
could parse /proc/self/status, but that doesn't work portably and will
fail where /proc is not available (some Docker containers, notably).
2021-03-12 00:37:08 +08:00
|
|
|
|
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 |
|
|
|
|
|
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)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2015-08-05 11:47:26 +08:00
|
|
|
|
func TestClientChown(t *testing.T) {
|
2019-01-19 07:59:30 +08:00
|
|
|
|
skipIfWindows(t) // No UNIX permissions.
|
2019-08-30 23:04:37 +08:00
|
|
|
|
sftp, cmd := testClient(t, READWRITE, NODELAY)
|
2015-08-05 11:47:26 +08:00
|
|
|
|
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()
|
|
|
|
|
}
|
2020-10-23 19:11:23 +08:00
|
|
|
|
|
|
|
|
|
chownto, err := user.Lookup("daemon") // seems common-ish...
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
2016-01-05 05:15:21 +08:00
|
|
|
|
toUID, err := strconv.Atoi(chownto.Uid)
|
2015-08-05 11:47:26 +08:00
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
2016-01-05 05:15:21 +08:00
|
|
|
|
toGID, err := strconv.Atoi(chownto.Gid)
|
2015-08-05 11:47:26 +08:00
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
|
2020-10-23 18:56:36 +08:00
|
|
|
|
f, err := ioutil.TempFile("", "sftptest-chown")
|
2015-08-05 11:47:26 +08:00
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
2020-10-23 18:56:36 +08:00
|
|
|
|
defer os.Remove(f.Name())
|
|
|
|
|
f.Close()
|
|
|
|
|
|
2015-08-05 11:47:26 +08:00
|
|
|
|
before, err := exec.Command("ls", "-nl", f.Name()).Output()
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
2016-01-05 05:15:21 +08:00
|
|
|
|
if err := sftp.Chown(f.Name(), toUID, toGID); err != nil {
|
2015-08-05 11:47:26 +08:00
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
after, err := exec.Command("ls", "-nl", f.Name()).Output()
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
|
2015-09-07 14:55:15 +08:00
|
|
|
|
spaceRegex := regexp.MustCompile(`\s+`)
|
|
|
|
|
|
2015-08-05 11:47:26 +08:00
|
|
|
|
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) {
|
2019-01-19 07:59:30 +08:00
|
|
|
|
skipIfWindows(t) // No UNIX permissions.
|
2019-08-30 23:04:37 +08:00
|
|
|
|
sftp, cmd := testClient(t, READONLY, NODELAY)
|
2015-08-05 11:47:26 +08:00
|
|
|
|
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()
|
|
|
|
|
}
|
2020-10-23 19:11:23 +08:00
|
|
|
|
|
|
|
|
|
chownto, err := user.Lookup("daemon") // seems common-ish...
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
2016-01-05 05:15:21 +08:00
|
|
|
|
toUID, err := strconv.Atoi(chownto.Uid)
|
2015-08-05 11:47:26 +08:00
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
2016-01-05 05:15:21 +08:00
|
|
|
|
toGID, err := strconv.Atoi(chownto.Gid)
|
2015-08-05 11:47:26 +08:00
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
|
2020-10-23 18:56:36 +08:00
|
|
|
|
f, err := ioutil.TempFile("", "sftptest-chownreadonly")
|
2015-08-05 11:47:26 +08:00
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
2020-10-23 18:56:36 +08:00
|
|
|
|
defer os.Remove(f.Name())
|
|
|
|
|
f.Close()
|
|
|
|
|
|
2016-01-05 05:15:21 +08:00
|
|
|
|
if err := sftp.Chown(f.Name(), toUID, toGID); err == nil {
|
2015-08-05 11:47:26 +08:00
|
|
|
|
t.Fatal("expected error")
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestClientChtimes(t *testing.T) {
|
2019-08-30 23:04:37 +08:00
|
|
|
|
sftp, cmd := testClient(t, READWRITE, NODELAY)
|
2015-08-05 11:47:26 +08:00
|
|
|
|
defer cmd.Wait()
|
|
|
|
|
defer sftp.Close()
|
|
|
|
|
|
2020-10-23 18:56:36 +08:00
|
|
|
|
f, err := ioutil.TempFile("", "sftptest-chtimes")
|
2015-08-05 11:47:26 +08:00
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
2020-10-23 18:56:36 +08:00
|
|
|
|
defer os.Remove(f.Name())
|
|
|
|
|
f.Close()
|
2015-08-05 11:47:26 +08:00
|
|
|
|
|
|
|
|
|
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) {
|
2019-08-30 23:04:37 +08:00
|
|
|
|
sftp, cmd := testClient(t, READONLY, NODELAY)
|
2015-08-05 11:47:26 +08:00
|
|
|
|
defer cmd.Wait()
|
|
|
|
|
defer sftp.Close()
|
|
|
|
|
|
2020-10-23 18:56:36 +08:00
|
|
|
|
f, err := ioutil.TempFile("", "sftptest-chtimesreadonly")
|
2015-08-05 11:47:26 +08:00
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
2020-10-23 18:56:36 +08:00
|
|
|
|
defer os.Remove(f.Name())
|
|
|
|
|
f.Close()
|
2015-08-05 11:47:26 +08:00
|
|
|
|
|
|
|
|
|
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")
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2015-08-05 13:21:35 +08:00
|
|
|
|
func TestClientTruncate(t *testing.T) {
|
2019-08-30 23:04:37 +08:00
|
|
|
|
sftp, cmd := testClient(t, READWRITE, NODELAY)
|
2015-08-05 11:47:26 +08:00
|
|
|
|
defer cmd.Wait()
|
|
|
|
|
defer sftp.Close()
|
|
|
|
|
|
2020-10-23 18:56:36 +08:00
|
|
|
|
f, err := ioutil.TempFile("", "sftptest-truncate")
|
2015-08-05 11:47:26 +08:00
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
2020-10-23 18:56:36 +08:00
|
|
|
|
defer os.Remove(f.Name())
|
2015-08-05 13:21:35 +08:00
|
|
|
|
fname := f.Name()
|
2015-08-05 11:47:26 +08:00
|
|
|
|
|
2015-08-05 13:21:35 +08:00
|
|
|
|
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 {
|
2015-08-05 11:47:26 +08:00
|
|
|
|
t.Fatal(err)
|
2015-08-05 13:21:35 +08:00
|
|
|
|
} else if stat.Size() != 5 {
|
|
|
|
|
t.Fatalf("unexpected size: %d", stat.Size())
|
2015-08-05 11:47:26 +08:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2015-08-05 13:21:35 +08:00
|
|
|
|
func TestClientTruncateReadonly(t *testing.T) {
|
2019-08-30 23:04:37 +08:00
|
|
|
|
sftp, cmd := testClient(t, READONLY, NODELAY)
|
2015-08-05 13:21:35 +08:00
|
|
|
|
defer cmd.Wait()
|
|
|
|
|
defer sftp.Close()
|
|
|
|
|
|
2020-10-31 21:34:16 +08:00
|
|
|
|
f, err := ioutil.TempFile("", "sftptest-truncreadonly")
|
2015-08-05 13:21:35 +08:00
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
2020-10-23 18:56:36 +08:00
|
|
|
|
defer os.Remove(f.Name())
|
2015-08-05 13:21:35 +08:00
|
|
|
|
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())
|
|
|
|
|
}
|
|
|
|
|
}
|
2015-08-05 11:47:26 +08:00
|
|
|
|
|
2013-11-06 12:40:35 +08:00
|
|
|
|
func sameFile(want, got os.FileInfo) bool {
|
2019-01-19 07:59:30 +08:00
|
|
|
|
_, wantName := filepath.Split(want.Name())
|
|
|
|
|
_, gotName := filepath.Split(got.Name())
|
|
|
|
|
return wantName == gotName &&
|
2013-11-06 12:40:35 +08:00
|
|
|
|
want.Size() == got.Size()
|
|
|
|
|
}
|
2013-11-08 18:00:26 +08:00
|
|
|
|
|
2015-07-30 08:24:24 +08:00
|
|
|
|
func TestClientReadSimple(t *testing.T) {
|
2019-08-30 23:04:37 +08:00
|
|
|
|
sftp, cmd := testClient(t, READONLY, NODELAY)
|
2015-07-30 08:24:24 +08:00
|
|
|
|
defer cmd.Wait()
|
|
|
|
|
defer sftp.Close()
|
|
|
|
|
|
2020-10-31 21:34:16 +08:00
|
|
|
|
d, err := ioutil.TempDir("", "sftptest-readsimple")
|
2015-07-30 08:24:24 +08:00
|
|
|
|
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")
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2021-03-05 22:04:17 +08:00
|
|
|
|
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.NoError(t, err)
|
|
|
|
|
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)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2015-09-07 10:36:47 +08:00
|
|
|
|
func TestClientReadDir(t *testing.T) {
|
2019-08-30 23:04:37 +08:00
|
|
|
|
sftp1, cmd1 := testClient(t, READONLY, NODELAY)
|
|
|
|
|
sftp2, cmd2 := testClientGoSvr(t, READONLY, NODELAY)
|
2015-09-07 10:36:47 +08:00
|
|
|
|
defer cmd1.Wait()
|
|
|
|
|
defer cmd2.Wait()
|
|
|
|
|
defer sftp1.Close()
|
|
|
|
|
defer sftp2.Close()
|
|
|
|
|
|
2019-01-19 07:59:30 +08:00
|
|
|
|
dir := os.TempDir()
|
2015-09-07 10:36:47 +08:00
|
|
|
|
|
|
|
|
|
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())
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2013-11-15 18:17:11 +08:00
|
|
|
|
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) {
|
2019-08-30 23:04:37 +08:00
|
|
|
|
sftp, cmd := testClient(t, READONLY, NODELAY)
|
2013-11-15 18:17:11 +08:00
|
|
|
|
defer cmd.Wait()
|
|
|
|
|
defer sftp.Close()
|
|
|
|
|
|
2020-10-31 21:34:16 +08:00
|
|
|
|
d, err := ioutil.TempDir("", "sftptest-read")
|
2013-11-15 18:17:11 +08:00
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
defer os.RemoveAll(d)
|
|
|
|
|
|
2021-02-24 02:13:26 +08:00
|
|
|
|
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)
|
|
|
|
|
}
|
2013-11-15 18:17:11 +08:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2013-11-15 18:47:28 +08:00
|
|
|
|
// 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()
|
2021-03-20 18:43:59 +08:00
|
|
|
|
read, err := io.Copy(h, r)
|
2013-11-15 18:47:28 +08:00
|
|
|
|
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 {
|
2019-01-19 07:59:30 +08:00
|
|
|
|
r := rand.New(rand.NewSource(time.Now().UnixNano()))
|
2013-11-15 18:47:28 +08:00
|
|
|
|
|
|
|
|
|
h := sha1.New()
|
|
|
|
|
|
|
|
|
|
mw := io.MultiWriter(w, h)
|
|
|
|
|
|
2019-01-19 07:59:30 +08:00
|
|
|
|
written, err := io.CopyN(mw, r, n)
|
2013-11-15 18:47:28 +08:00
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
if written != n {
|
|
|
|
|
t.Fatalf("CopyN(%v): wrote: %v", n, written)
|
|
|
|
|
}
|
|
|
|
|
return string(h.Sum(nil))
|
|
|
|
|
}
|
|
|
|
|
|
2013-11-08 18:00:26 +08:00
|
|
|
|
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},
|
2013-11-08 18:24:50 +08:00
|
|
|
|
{1 << 18, 524287},
|
|
|
|
|
{1 << 19, 1048575},
|
|
|
|
|
{1 << 20, 2097151},
|
|
|
|
|
{1 << 21, 4194303},
|
2013-11-08 18:00:26 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestClientWrite(t *testing.T) {
|
2019-08-30 23:04:37 +08:00
|
|
|
|
sftp, cmd := testClient(t, READWRITE, NODELAY)
|
2013-11-08 18:00:26 +08:00
|
|
|
|
defer cmd.Wait()
|
|
|
|
|
defer sftp.Close()
|
|
|
|
|
|
2020-10-31 21:34:16 +08:00
|
|
|
|
d, err := ioutil.TempDir("", "sftptest-write")
|
2013-11-08 18:00:26 +08:00
|
|
|
|
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)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2013-11-08 18:24:50 +08:00
|
|
|
|
|
2017-02-13 11:37:13 +08:00
|
|
|
|
// ReadFrom is basically Write with io.Reader as the arg
|
|
|
|
|
func TestClientReadFrom(t *testing.T) {
|
2019-08-30 23:04:37 +08:00
|
|
|
|
sftp, cmd := testClient(t, READWRITE, NODELAY)
|
2017-02-13 11:37:13 +08:00
|
|
|
|
defer cmd.Wait()
|
|
|
|
|
defer sftp.Close()
|
|
|
|
|
|
2020-10-31 21:34:16 +08:00
|
|
|
|
d, err := ioutil.TempDir("", "sftptest-readfrom")
|
2017-02-13 11:37:13 +08:00
|
|
|
|
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)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2017-02-13 15:18:54 +08:00
|
|
|
|
// Issue #145 in github
|
|
|
|
|
// Deadlock in ReadFrom when network drops after 1 good packet.
|
|
|
|
|
// Deadlock would occur anytime desiredInFlight-inFlight==2 and 2 errors
|
2021-08-21 03:00:33 +08:00
|
|
|
|
// occurred in a row. The channel to report the errors only had a buffer
|
2017-02-13 15:18:54 +08:00
|
|
|
|
// of 1 and 2 would be sent.
|
2019-08-30 23:04:37 +08:00
|
|
|
|
var errFakeNet = errors.New("Fake network issue")
|
2017-02-14 08:24:04 +08:00
|
|
|
|
|
2017-02-13 15:18:54 +08:00
|
|
|
|
func TestClientReadFromDeadlock(t *testing.T) {
|
2021-03-17 19:03:24 +08:00
|
|
|
|
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) {
|
2021-08-21 03:00:33 +08:00
|
|
|
|
t.Fatal("Didn't receive correct error:", err)
|
2021-03-17 19:03:24 +08:00
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
}
|
2017-02-13 15:18:54 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Write has exact same problem
|
|
|
|
|
func TestClientWriteDeadlock(t *testing.T) {
|
2021-03-17 19:03:24 +08:00
|
|
|
|
for i := 0; i < 5; i++ {
|
|
|
|
|
clientWriteDeadlock(t, i, func(f *File) {
|
|
|
|
|
b := make([]byte, 32768*4)
|
2020-11-15 07:53:25 +08:00
|
|
|
|
|
2021-03-17 19:03:24 +08:00
|
|
|
|
_, err := f.Write(b)
|
|
|
|
|
if !errors.Is(err, errFakeNet) {
|
2021-08-21 03:00:33 +08:00
|
|
|
|
t.Fatal("Didn't receive correct error:", err)
|
2021-03-17 19:03:24 +08:00
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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()
|
2017-02-13 15:18:54 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// shared body for both previous tests
|
2017-02-14 08:24:04 +08:00
|
|
|
|
func clientWriteDeadlock(t *testing.T, N int, badfunc func(*File)) {
|
2017-02-13 15:18:54 +08:00
|
|
|
|
if !*testServerImpl {
|
|
|
|
|
t.Skipf("skipping without -testserver")
|
|
|
|
|
}
|
2019-08-30 23:04:37 +08:00
|
|
|
|
sftp, cmd := testClient(t, READWRITE, NODELAY)
|
2017-02-13 15:18:54 +08:00
|
|
|
|
defer cmd.Wait()
|
|
|
|
|
defer sftp.Close()
|
|
|
|
|
|
2020-10-31 21:34:16 +08:00
|
|
|
|
d, err := ioutil.TempDir("", "sftptest-writedeadlock")
|
2017-02-13 15:18:54 +08:00
|
|
|
|
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()
|
|
|
|
|
|
2021-03-17 19:03:24 +08:00
|
|
|
|
// 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,
|
2017-02-13 15:18:54 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// this locked (before the fix)
|
|
|
|
|
badfunc(w)
|
|
|
|
|
}
|
|
|
|
|
|
2017-02-14 08:24:04 +08:00
|
|
|
|
// Read/WriteTo has this issue as well
|
|
|
|
|
func TestClientReadDeadlock(t *testing.T) {
|
2021-03-17 20:05:00 +08:00
|
|
|
|
for i := 0; i < 3; i++ {
|
2021-03-17 19:03:24 +08:00
|
|
|
|
clientReadDeadlock(t, i, func(f *File) {
|
|
|
|
|
b := make([]byte, 32768*4)
|
2020-11-15 07:53:25 +08:00
|
|
|
|
|
2021-03-17 19:03:24 +08:00
|
|
|
|
_, err := f.Read(b)
|
|
|
|
|
if !errors.Is(err, errFakeNet) {
|
2021-08-21 03:00:33 +08:00
|
|
|
|
t.Fatal("Didn't receive correct error:", err)
|
2021-03-17 19:03:24 +08:00
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
}
|
2017-02-14 08:24:04 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestClientWriteToDeadlock(t *testing.T) {
|
2021-03-17 20:05:00 +08:00
|
|
|
|
for i := 0; i < 3; i++ {
|
2021-03-17 19:03:24 +08:00
|
|
|
|
clientReadDeadlock(t, i, func(f *File) {
|
|
|
|
|
b := make([]byte, 32768*4)
|
2020-11-15 07:53:25 +08:00
|
|
|
|
|
2021-03-17 19:03:24 +08:00
|
|
|
|
buf := bytes.NewBuffer(b)
|
2020-11-15 07:53:25 +08:00
|
|
|
|
|
2021-03-17 19:03:24 +08:00
|
|
|
|
_, err := f.WriteTo(buf)
|
|
|
|
|
if !errors.Is(err, errFakeNet) {
|
2021-08-21 03:00:33 +08:00
|
|
|
|
t.Fatal("Didn't receive correct error:", err)
|
2021-03-17 19:03:24 +08:00
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
}
|
2017-02-14 08:24:04 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func clientReadDeadlock(t *testing.T, N int, badfunc func(*File)) {
|
|
|
|
|
if !*testServerImpl {
|
|
|
|
|
t.Skipf("skipping without -testserver")
|
|
|
|
|
}
|
2019-08-30 23:04:37 +08:00
|
|
|
|
sftp, cmd := testClient(t, READWRITE, NODELAY)
|
2017-02-14 08:24:04 +08:00
|
|
|
|
defer cmd.Wait()
|
|
|
|
|
defer sftp.Close()
|
|
|
|
|
|
2020-10-31 21:34:16 +08:00
|
|
|
|
d, err := ioutil.TempDir("", "sftptest-readdeadlock")
|
2017-02-14 08:24:04 +08:00
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
defer os.RemoveAll(d)
|
|
|
|
|
|
|
|
|
|
f := path.Join(d, "writeTest")
|
2020-11-15 07:53:25 +08:00
|
|
|
|
|
2017-02-14 08:24:04 +08:00
|
|
|
|
w, err := sftp.Create(f)
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
2020-11-15 07:53:25 +08:00
|
|
|
|
defer w.Close()
|
|
|
|
|
|
2017-02-14 08:24:04 +08:00
|
|
|
|
// 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()
|
|
|
|
|
|
2021-03-17 19:03:24 +08:00
|
|
|
|
// 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,
|
2017-02-14 08:24:04 +08:00
|
|
|
|
}
|
2020-11-15 07:53:25 +08:00
|
|
|
|
|
2017-02-14 08:24:04 +08:00
|
|
|
|
// this locked (before the fix)
|
|
|
|
|
badfunc(r)
|
|
|
|
|
}
|
|
|
|
|
|
2020-10-11 20:03:31 +08:00
|
|
|
|
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.
|
2020-10-29 23:02:25 +08:00
|
|
|
|
require.Error(t, err)
|
2020-10-11 20:03:31 +08:00
|
|
|
|
|
|
|
|
|
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)
|
2020-10-29 23:02:25 +08:00
|
|
|
|
assert.NoError(t, err)
|
2020-10-11 20:03:31 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func testClientSync(t *testing.T) error {
|
|
|
|
|
sftp, cmd := testClient(t, READWRITE, NODELAY)
|
|
|
|
|
defer cmd.Wait()
|
|
|
|
|
defer sftp.Close()
|
|
|
|
|
|
|
|
|
|
d, err := ioutil.TempDir("", "sftptest.sync")
|
2020-10-29 23:02:25 +08:00
|
|
|
|
require.NoError(t, err)
|
2020-10-11 20:03:31 +08:00
|
|
|
|
defer os.RemoveAll(d)
|
|
|
|
|
|
|
|
|
|
f := path.Join(d, "syncTest")
|
|
|
|
|
w, err := sftp.Create(f)
|
2020-10-29 23:02:25 +08:00
|
|
|
|
require.NoError(t, err)
|
2020-10-11 20:03:31 +08:00
|
|
|
|
defer w.Close()
|
|
|
|
|
|
|
|
|
|
return w.Sync()
|
|
|
|
|
}
|
|
|
|
|
|
2013-11-08 18:24:50 +08:00
|
|
|
|
// 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) {
|
2019-08-30 23:04:37 +08:00
|
|
|
|
sftp, cmd := testClient(t, READONLY, NODELAY)
|
2013-11-08 18:24:50 +08:00
|
|
|
|
defer cmd.Wait()
|
|
|
|
|
defer sftp.Close()
|
|
|
|
|
|
|
|
|
|
makeTree(t)
|
|
|
|
|
errors := make([]error, 0, 10)
|
|
|
|
|
clear := true
|
2016-01-08 04:56:04 +08:00
|
|
|
|
markFn := func(walker *fs.Walker) error {
|
2013-11-08 18:24:50 +08:00
|
|
|
|
for walker.Step() {
|
2016-01-08 04:56:04 +08:00
|
|
|
|
err := mark(walker.Path(), walker.Stat(), walker.Err(), &errors, clear)
|
2013-11-08 18:24:50 +08:00
|
|
|
|
if err != nil {
|
2016-01-08 04:56:04 +08:00
|
|
|
|
return err
|
2013-11-08 18:24:50 +08:00
|
|
|
|
}
|
|
|
|
|
}
|
2016-01-08 04:56:04 +08:00
|
|
|
|
return nil
|
2013-11-08 18:24:50 +08:00
|
|
|
|
}
|
|
|
|
|
// 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)
|
|
|
|
|
}
|
|
|
|
|
}
|
2014-10-09 05:41:54 +08:00
|
|
|
|
|
2016-11-18 21:20:24 +08:00
|
|
|
|
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},
|
2020-10-25 18:09:04 +08:00
|
|
|
|
|
|
|
|
|
// 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},
|
2016-11-18 21:20:24 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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
|
|
|
|
|
}{
|
2017-07-21 06:25:28 +08:00
|
|
|
|
{"match.go", "match.go"},
|
|
|
|
|
{"mat?h.go", "match.go"},
|
|
|
|
|
{"ma*ch.go", "match.go"},
|
2021-03-16 01:11:02 +08:00
|
|
|
|
{`\m\a\t\c\h\.\g\o`, "match.go"},
|
2016-11-18 21:20:24 +08:00
|
|
|
|
{"../*/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) {
|
2019-08-30 23:04:37 +08:00
|
|
|
|
sftp, cmd := testClient(t, READONLY, NODELAY)
|
2016-11-18 21:20:24 +08:00
|
|
|
|
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) {
|
2019-08-30 23:04:37 +08:00
|
|
|
|
sftp, cmd := testClient(t, READONLY, NODELAY)
|
2016-11-18 21:20:24 +08:00
|
|
|
|
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) {
|
2019-08-30 23:04:37 +08:00
|
|
|
|
sftp, cmd := testClient(t, READONLY, NODELAY)
|
2016-11-18 21:20:24 +08:00
|
|
|
|
defer cmd.Wait()
|
|
|
|
|
defer sftp.Close()
|
|
|
|
|
// Just make sure this runs without crashing for now.
|
|
|
|
|
// See issue 15879.
|
|
|
|
|
sftp.Glob(`\\?\C:\*`)
|
|
|
|
|
}
|
|
|
|
|
|
2015-06-15 18:12:08 +08:00
|
|
|
|
// sftp/issue/42, abrupt server hangup would result in client hangs.
|
|
|
|
|
func TestServerRoughDisconnect(t *testing.T) {
|
2019-01-19 07:59:30 +08:00
|
|
|
|
skipIfWindows(t)
|
2016-01-01 22:31:52 +08:00
|
|
|
|
if *testServerImpl {
|
|
|
|
|
t.Skipf("skipping with -testserver")
|
|
|
|
|
}
|
2019-08-30 23:04:37 +08:00
|
|
|
|
sftp, cmd := testClient(t, READONLY, NODELAY)
|
2016-01-01 22:31:52 +08:00
|
|
|
|
defer cmd.Wait()
|
|
|
|
|
defer sftp.Close()
|
2015-06-15 18:12:08 +08:00
|
|
|
|
|
|
|
|
|
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()
|
|
|
|
|
}()
|
|
|
|
|
|
2020-11-02 02:58:45 +08:00
|
|
|
|
_, err = io.Copy(ioutil.Discard, f)
|
|
|
|
|
assert.Error(t, err)
|
2015-06-15 18:12:08 +08:00
|
|
|
|
}
|
|
|
|
|
|
2017-06-30 02:27:52 +08:00
|
|
|
|
// 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) {
|
2019-01-19 07:59:30 +08:00
|
|
|
|
skipIfWindows(t)
|
2017-06-30 02:27:52 +08:00
|
|
|
|
if *testServerImpl {
|
|
|
|
|
t.Skipf("skipping with -testserver")
|
|
|
|
|
}
|
2019-08-30 23:04:37 +08:00
|
|
|
|
sftp, cmd := testClient(t, READONLY, NODELAY)
|
2017-06-30 02:27:52 +08:00
|
|
|
|
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
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2018-03-16 04:46:20 +08:00
|
|
|
|
// sftp/issue/234 - abrupt shutdown during ReadFrom hangs client
|
|
|
|
|
func TestServerRoughDisconnect3(t *testing.T) {
|
2019-01-19 07:59:30 +08:00
|
|
|
|
skipIfWindows(t)
|
2018-03-16 04:46:20 +08:00
|
|
|
|
if *testServerImpl {
|
|
|
|
|
t.Skipf("skipping with -testserver")
|
|
|
|
|
}
|
2020-11-15 07:53:25 +08:00
|
|
|
|
|
2019-08-30 23:04:37 +08:00
|
|
|
|
sftp, cmd := testClient(t, READWRITE, NODELAY)
|
2018-03-16 04:46:20 +08:00
|
|
|
|
defer cmd.Wait()
|
|
|
|
|
defer sftp.Close()
|
|
|
|
|
|
2020-11-15 07:53:25 +08:00
|
|
|
|
dest, err := sftp.OpenFile("/dev/null", os.O_RDWR)
|
2018-03-16 04:46:20 +08:00
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
2020-11-15 07:53:25 +08:00
|
|
|
|
defer dest.Close()
|
|
|
|
|
|
|
|
|
|
src, err := os.Open("/dev/zero")
|
2018-03-16 04:46:20 +08:00
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
2020-11-15 07:53:25 +08:00
|
|
|
|
defer src.Close()
|
|
|
|
|
|
2018-03-16 04:46:20 +08:00
|
|
|
|
go func() {
|
|
|
|
|
time.Sleep(10 * time.Millisecond)
|
|
|
|
|
cmd.Process.Kill()
|
|
|
|
|
}()
|
|
|
|
|
|
2020-11-15 07:53:25 +08:00
|
|
|
|
_, err = io.Copy(dest, src)
|
2020-11-02 02:58:45 +08:00
|
|
|
|
assert.Error(t, err)
|
2018-03-16 04:46:20 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// sftp/issue/234 - also affected Write
|
|
|
|
|
func TestServerRoughDisconnect4(t *testing.T) {
|
2019-01-19 07:59:30 +08:00
|
|
|
|
skipIfWindows(t)
|
2018-03-16 04:46:20 +08:00
|
|
|
|
if *testServerImpl {
|
|
|
|
|
t.Skipf("skipping with -testserver")
|
|
|
|
|
}
|
2019-08-30 23:04:37 +08:00
|
|
|
|
sftp, cmd := testClient(t, READWRITE, NODELAY)
|
2018-03-16 04:46:20 +08:00
|
|
|
|
defer cmd.Wait()
|
|
|
|
|
defer sftp.Close()
|
|
|
|
|
|
2020-11-15 07:53:25 +08:00
|
|
|
|
dest, err := sftp.OpenFile("/dev/null", os.O_RDWR)
|
2018-03-16 04:46:20 +08:00
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
2020-11-15 07:53:25 +08:00
|
|
|
|
defer dest.Close()
|
|
|
|
|
|
|
|
|
|
src, err := os.Open("/dev/zero")
|
2018-03-16 04:46:20 +08:00
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
2020-11-15 07:53:25 +08:00
|
|
|
|
defer src.Close()
|
|
|
|
|
|
2018-03-16 04:46:20 +08:00
|
|
|
|
go func() {
|
|
|
|
|
time.Sleep(10 * time.Millisecond)
|
|
|
|
|
cmd.Process.Kill()
|
|
|
|
|
}()
|
2020-11-15 07:53:25 +08:00
|
|
|
|
|
2018-03-16 04:46:20 +08:00
|
|
|
|
b := make([]byte, 32768*200)
|
2020-11-15 07:53:25 +08:00
|
|
|
|
src.Read(b)
|
2018-03-16 04:46:20 +08:00
|
|
|
|
for {
|
2020-11-15 07:53:25 +08:00
|
|
|
|
_, err = dest.Write(b)
|
2018-03-16 04:46:20 +08:00
|
|
|
|
if err != nil {
|
2020-11-02 02:58:45 +08:00
|
|
|
|
assert.NotEqual(t, io.EOF, err)
|
2018-03-16 04:46:20 +08:00
|
|
|
|
break
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-11-15 07:53:25 +08:00
|
|
|
|
_, err = io.Copy(dest, src)
|
2020-11-02 02:58:45 +08:00
|
|
|
|
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)
|
2018-03-16 04:46:20 +08:00
|
|
|
|
}
|
|
|
|
|
|
2015-12-26 01:44:19 +08:00
|
|
|
|
// sftp/issue/26 writing to a read only file caused client to loop.
|
|
|
|
|
func TestClientWriteToROFile(t *testing.T) {
|
2019-01-19 07:59:30 +08:00
|
|
|
|
skipIfWindows(t)
|
2019-08-30 23:04:37 +08:00
|
|
|
|
sftp, cmd := testClient(t, READWRITE, NODELAY)
|
2015-12-26 01:44:19 +08:00
|
|
|
|
defer cmd.Wait()
|
2020-11-02 02:58:45 +08:00
|
|
|
|
defer func() {
|
|
|
|
|
err := sftp.Close()
|
|
|
|
|
assert.NoError(t, err)
|
|
|
|
|
}()
|
2015-12-26 01:44:19 +08:00
|
|
|
|
|
|
|
|
|
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)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2015-06-03 00:36:35 +08:00
|
|
|
|
func benchmarkRead(b *testing.B, bufsize int, delay time.Duration) {
|
2019-01-19 07:59:30 +08:00
|
|
|
|
skipIfWindows(b)
|
2014-10-09 05:41:54 +08:00
|
|
|
|
size := 10*1024*1024 + 123 // ~10MiB
|
|
|
|
|
|
|
|
|
|
// open sftp client
|
2015-06-03 00:36:35 +08:00
|
|
|
|
sftp, cmd := testClient(b, READONLY, delay)
|
2014-10-09 05:41:54 +08:00
|
|
|
|
defer cmd.Wait()
|
2020-10-29 03:58:57 +08:00
|
|
|
|
defer sftp.Close()
|
2014-10-09 05:41:54 +08:00
|
|
|
|
|
|
|
|
|
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
|
|
|
|
|
}
|
2020-10-29 04:02:33 +08:00
|
|
|
|
|
|
|
|
|
f2.Close()
|
2014-10-09 05:41:54 +08:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func BenchmarkRead1k(b *testing.B) {
|
2019-08-30 23:04:37 +08:00
|
|
|
|
benchmarkRead(b, 1*1024, NODELAY)
|
2014-10-09 05:41:54 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func BenchmarkRead16k(b *testing.B) {
|
2019-08-30 23:04:37 +08:00
|
|
|
|
benchmarkRead(b, 16*1024, NODELAY)
|
2014-10-09 05:41:54 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func BenchmarkRead32k(b *testing.B) {
|
2019-08-30 23:04:37 +08:00
|
|
|
|
benchmarkRead(b, 32*1024, NODELAY)
|
2014-10-09 05:41:54 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func BenchmarkRead128k(b *testing.B) {
|
2019-08-30 23:04:37 +08:00
|
|
|
|
benchmarkRead(b, 128*1024, NODELAY)
|
2014-10-09 05:41:54 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func BenchmarkRead512k(b *testing.B) {
|
2019-08-30 23:04:37 +08:00
|
|
|
|
benchmarkRead(b, 512*1024, NODELAY)
|
2014-10-09 05:41:54 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func BenchmarkRead1MiB(b *testing.B) {
|
2019-08-30 23:04:37 +08:00
|
|
|
|
benchmarkRead(b, 1024*1024, NODELAY)
|
2014-10-09 05:41:54 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func BenchmarkRead4MiB(b *testing.B) {
|
2019-08-30 23:04:37 +08:00
|
|
|
|
benchmarkRead(b, 4*1024*1024, NODELAY)
|
2014-10-09 05:41:54 +08:00
|
|
|
|
}
|
|
|
|
|
|
2015-06-03 00:36:35 +08:00
|
|
|
|
func BenchmarkRead4MiBDelay10Msec(b *testing.B) {
|
|
|
|
|
benchmarkRead(b, 4*1024*1024, 10*time.Millisecond)
|
2014-10-09 05:41:54 +08:00
|
|
|
|
}
|
|
|
|
|
|
2015-06-03 00:36:35 +08:00
|
|
|
|
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) {
|
2014-10-09 05:41:54 +08:00
|
|
|
|
size := 10*1024*1024 + 123 // ~10MiB
|
|
|
|
|
|
|
|
|
|
// open sftp client
|
2015-06-03 00:36:35 +08:00
|
|
|
|
sftp, cmd := testClient(b, false, delay)
|
2014-10-09 05:41:54 +08:00
|
|
|
|
defer cmd.Wait()
|
2020-10-29 03:58:57 +08:00
|
|
|
|
defer sftp.Close()
|
2014-10-09 05:41:54 +08:00
|
|
|
|
|
|
|
|
|
data := make([]byte, size)
|
|
|
|
|
|
|
|
|
|
b.ResetTimer()
|
|
|
|
|
b.SetBytes(int64(size))
|
|
|
|
|
|
|
|
|
|
for i := 0; i < b.N; i++ {
|
|
|
|
|
offset := 0
|
|
|
|
|
|
2020-10-31 21:34:16 +08:00
|
|
|
|
f, err := ioutil.TempFile("", "sftptest-benchwrite")
|
2014-10-09 05:41:54 +08:00
|
|
|
|
if err != nil {
|
|
|
|
|
b.Fatal(err)
|
|
|
|
|
}
|
2020-10-29 04:02:33 +08:00
|
|
|
|
defer os.Remove(f.Name()) // actually queue up a series of removes for these files
|
2014-10-09 05:41:54 +08:00
|
|
|
|
|
|
|
|
|
f2, err := sftp.Create(f.Name())
|
|
|
|
|
if err != nil {
|
|
|
|
|
b.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for offset < size {
|
2021-08-11 21:29:16 +08:00
|
|
|
|
buf := data[offset:]
|
|
|
|
|
if len(buf) > bufsize {
|
|
|
|
|
buf = buf[:bufsize]
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
n, err := f2.Write(buf)
|
2014-10-09 05:41:54 +08:00
|
|
|
|
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) {
|
2019-08-30 23:04:37 +08:00
|
|
|
|
benchmarkWrite(b, 1*1024, NODELAY)
|
2014-10-09 05:41:54 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func BenchmarkWrite16k(b *testing.B) {
|
2019-08-30 23:04:37 +08:00
|
|
|
|
benchmarkWrite(b, 16*1024, NODELAY)
|
2014-10-09 05:41:54 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func BenchmarkWrite32k(b *testing.B) {
|
2019-08-30 23:04:37 +08:00
|
|
|
|
benchmarkWrite(b, 32*1024, NODELAY)
|
2014-10-09 05:41:54 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func BenchmarkWrite128k(b *testing.B) {
|
2019-08-30 23:04:37 +08:00
|
|
|
|
benchmarkWrite(b, 128*1024, NODELAY)
|
2014-10-09 05:41:54 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func BenchmarkWrite512k(b *testing.B) {
|
2019-08-30 23:04:37 +08:00
|
|
|
|
benchmarkWrite(b, 512*1024, NODELAY)
|
2014-10-09 05:41:54 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func BenchmarkWrite1MiB(b *testing.B) {
|
2019-08-30 23:04:37 +08:00
|
|
|
|
benchmarkWrite(b, 1024*1024, NODELAY)
|
2014-10-09 05:41:54 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func BenchmarkWrite4MiB(b *testing.B) {
|
2019-08-30 23:04:37 +08:00
|
|
|
|
benchmarkWrite(b, 4*1024*1024, NODELAY)
|
2015-06-03 00:36:35 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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)
|
2014-10-09 05:41:54 +08:00
|
|
|
|
}
|
2015-05-16 06:54:02 +08:00
|
|
|
|
|
2017-06-26 10:21:59 +08:00
|
|
|
|
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()
|
2020-10-29 03:58:57 +08:00
|
|
|
|
defer sftp.Close()
|
2017-06-26 10:21:59 +08:00
|
|
|
|
|
|
|
|
|
data := make([]byte, size)
|
|
|
|
|
|
|
|
|
|
b.ResetTimer()
|
|
|
|
|
b.SetBytes(int64(size))
|
|
|
|
|
|
|
|
|
|
for i := 0; i < b.N; i++ {
|
2020-10-31 21:34:16 +08:00
|
|
|
|
f, err := ioutil.TempFile("", "sftptest-benchreadfrom")
|
2017-06-26 10:21:59 +08:00
|
|
|
|
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) {
|
2019-08-30 23:04:37 +08:00
|
|
|
|
benchmarkReadFrom(b, 1*1024, NODELAY)
|
2017-06-26 10:21:59 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func BenchmarkReadFrom16k(b *testing.B) {
|
2019-08-30 23:04:37 +08:00
|
|
|
|
benchmarkReadFrom(b, 16*1024, NODELAY)
|
2017-06-26 10:21:59 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func BenchmarkReadFrom32k(b *testing.B) {
|
2019-08-30 23:04:37 +08:00
|
|
|
|
benchmarkReadFrom(b, 32*1024, NODELAY)
|
2017-06-26 10:21:59 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func BenchmarkReadFrom128k(b *testing.B) {
|
2019-08-30 23:04:37 +08:00
|
|
|
|
benchmarkReadFrom(b, 128*1024, NODELAY)
|
2017-06-26 10:21:59 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func BenchmarkReadFrom512k(b *testing.B) {
|
2019-08-30 23:04:37 +08:00
|
|
|
|
benchmarkReadFrom(b, 512*1024, NODELAY)
|
2017-06-26 10:21:59 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func BenchmarkReadFrom1MiB(b *testing.B) {
|
2019-08-30 23:04:37 +08:00
|
|
|
|
benchmarkReadFrom(b, 1024*1024, NODELAY)
|
2017-06-26 10:21:59 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func BenchmarkReadFrom4MiB(b *testing.B) {
|
2019-08-30 23:04:37 +08:00
|
|
|
|
benchmarkReadFrom(b, 4*1024*1024, NODELAY)
|
2017-06-26 10:21:59 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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)
|
|
|
|
|
}
|
|
|
|
|
|
2021-01-23 00:26:36 +08:00
|
|
|
|
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()
|
|
|
|
|
|
2021-03-18 04:18:50 +08:00
|
|
|
|
buf := bytes.NewBuffer(make([]byte, 0, size))
|
2021-01-23 00:26:36 +08:00
|
|
|
|
|
|
|
|
|
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)
|
|
|
|
|
}
|
|
|
|
|
|
2015-06-03 00:36:35 +08:00
|
|
|
|
func benchmarkCopyDown(b *testing.B, fileSize int64, delay time.Duration) {
|
2019-01-19 07:59:30 +08:00
|
|
|
|
skipIfWindows(b)
|
2015-06-03 00:36:35 +08:00
|
|
|
|
// Create a temp file and fill it with zero's.
|
2020-10-31 21:34:16 +08:00
|
|
|
|
src, err := ioutil.TempFile("", "sftptest-benchcopydown")
|
2015-06-03 00:36:35 +08:00
|
|
|
|
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()
|
2020-10-29 03:58:57 +08:00
|
|
|
|
defer sftp.Close()
|
2015-06-03 00:36:35 +08:00
|
|
|
|
b.ResetTimer()
|
|
|
|
|
b.SetBytes(fileSize)
|
|
|
|
|
|
|
|
|
|
for i := 0; i < b.N; i++ {
|
2020-10-31 21:34:16 +08:00
|
|
|
|
dst, err := ioutil.TempFile("", "sftptest-benchcopydown")
|
2015-06-03 00:36:35 +08:00
|
|
|
|
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) {
|
2019-01-19 07:59:30 +08:00
|
|
|
|
skipIfWindows(b)
|
2015-06-03 00:36:35 +08:00
|
|
|
|
// Create a temp file and fill it with zero's.
|
2020-10-31 21:34:16 +08:00
|
|
|
|
src, err := ioutil.TempFile("", "sftptest-benchcopyup")
|
2015-06-03 00:36:35 +08:00
|
|
|
|
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()
|
2020-10-29 03:58:57 +08:00
|
|
|
|
defer sftp.Close()
|
2015-06-03 00:36:35 +08:00
|
|
|
|
|
|
|
|
|
b.ResetTimer()
|
|
|
|
|
b.SetBytes(fileSize)
|
|
|
|
|
|
|
|
|
|
for i := 0; i < b.N; i++ {
|
2020-10-31 21:34:16 +08:00
|
|
|
|
tmp, err := ioutil.TempFile("", "sftptest-benchcopyup")
|
2015-06-03 00:36:35 +08:00
|
|
|
|
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)
|
2014-10-09 05:41:54 +08:00
|
|
|
|
}
|