mirror of
https://github.com/restic/restic.git
synced 2025-12-04 01:31:48 +00:00
vendor everything
This commit is contained in:
2
Godeps/_workspace/src/github.com/pkg/sftp/CONTRIBUTORS
generated
vendored
Normal file
2
Godeps/_workspace/src/github.com/pkg/sftp/CONTRIBUTORS
generated
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
Dave Cheney <dave@cheney.net>
|
||||
Saulius Gurklys <s4uliu5@gmail.com>
|
||||
9
Godeps/_workspace/src/github.com/pkg/sftp/LICENSE
generated
vendored
Normal file
9
Godeps/_workspace/src/github.com/pkg/sftp/LICENSE
generated
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
Copyright (c) 2013, Dave Cheney
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
27
Godeps/_workspace/src/github.com/pkg/sftp/README.md
generated
vendored
Normal file
27
Godeps/_workspace/src/github.com/pkg/sftp/README.md
generated
vendored
Normal file
@@ -0,0 +1,27 @@
|
||||
sftp
|
||||
----
|
||||
|
||||
The `sftp` package provides support for file system operations on remote ssh servers using the SFTP subsystem.
|
||||
|
||||
[](https://drone.io/github.com/pkg/sftp/latest)
|
||||
|
||||
usage and examples
|
||||
------------------
|
||||
|
||||
See [godoc.org/github.com/pkg/sftp](http://godoc.org/github.com/pkg/sftp) for examples and usage.
|
||||
|
||||
The basic operation of the package mirrors the facilities of the [os](http://golang.org/pkg/os) package.
|
||||
|
||||
The Walker interface for directory traversal is heavily inspired by Keith Rarick's [fs](http://godoc.org/github.com/kr/fs) package.
|
||||
|
||||
roadmap
|
||||
-------
|
||||
|
||||
* Currently all traffic with the server is serialized, this can be improved by allowing overlapping requests/responses.
|
||||
* There is way too much duplication in the Client methods. If there was an unmarshal(interface{}) method this would reduce a heap of the duplication.
|
||||
* Implement integration tests by talking directly to a real opensftp-server process. This shouldn't be too difficult to implement with a small refactoring to the sftp.NewClient method. These tests should be gated on an -sftp.integration test flag. _in progress_
|
||||
|
||||
contributing
|
||||
------------
|
||||
|
||||
Features, Issues, and Pull Requests are always welcome.
|
||||
138
Godeps/_workspace/src/github.com/pkg/sftp/attrs.go
generated
vendored
Normal file
138
Godeps/_workspace/src/github.com/pkg/sftp/attrs.go
generated
vendored
Normal file
@@ -0,0 +1,138 @@
|
||||
package sftp
|
||||
|
||||
// ssh_FXP_ATTRS support
|
||||
// see http://tools.ietf.org/html/draft-ietf-secsh-filexfer-02#section-5
|
||||
|
||||
import (
|
||||
"os"
|
||||
"syscall"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
ssh_FILEXFER_ATTR_SIZE = 0x00000001
|
||||
ssh_FILEXFER_ATTR_UIDGID = 0x00000002
|
||||
ssh_FILEXFER_ATTR_PERMISSIONS = 0x00000004
|
||||
ssh_FILEXFER_ATTR_ACMODTIME = 0x00000008
|
||||
ssh_FILEXFER_ATTR_EXTENDED = 0x80000000
|
||||
)
|
||||
|
||||
// fileInfo is an artificial type designed to satisfy os.FileInfo.
|
||||
type fileInfo struct {
|
||||
name string
|
||||
size int64
|
||||
mode os.FileMode
|
||||
mtime time.Time
|
||||
sys interface{}
|
||||
}
|
||||
|
||||
// Name returns the base name of the file.
|
||||
func (fi *fileInfo) Name() string { return fi.name }
|
||||
|
||||
// Size returns the length in bytes for regular files; system-dependent for others.
|
||||
func (fi *fileInfo) Size() int64 { return fi.size }
|
||||
|
||||
// Mode returns file mode bits.
|
||||
func (fi *fileInfo) Mode() os.FileMode { return fi.mode }
|
||||
|
||||
// ModTime returns the last modification time of the file.
|
||||
func (fi *fileInfo) ModTime() time.Time { return fi.mtime }
|
||||
|
||||
// IsDir returns true if the file is a directory.
|
||||
func (fi *fileInfo) IsDir() bool { return fi.Mode().IsDir() }
|
||||
|
||||
func (fi *fileInfo) Sys() interface{} { return fi.sys }
|
||||
|
||||
// FileStat holds the original unmarshalled values from a call to READDIR or *STAT.
|
||||
// It is exported for the purposes of accessing the raw values via os.FileInfo.Sys()
|
||||
type FileStat struct {
|
||||
Size uint64
|
||||
Mode uint32
|
||||
Mtime uint32
|
||||
Atime uint32
|
||||
Uid uint32
|
||||
Gid uint32
|
||||
Extended []StatExtended
|
||||
}
|
||||
|
||||
type StatExtended struct {
|
||||
ExtType string
|
||||
ExtData string
|
||||
}
|
||||
|
||||
func fileInfoFromStat(st *FileStat, name string) os.FileInfo {
|
||||
fs := &fileInfo{
|
||||
name: name,
|
||||
size: int64(st.Size),
|
||||
mode: toFileMode(st.Mode),
|
||||
mtime: time.Unix(int64(st.Mtime), 0),
|
||||
sys: st,
|
||||
}
|
||||
return fs
|
||||
}
|
||||
|
||||
func unmarshalAttrs(b []byte) (*FileStat, []byte) {
|
||||
flags, b := unmarshalUint32(b)
|
||||
var fs FileStat
|
||||
if flags&ssh_FILEXFER_ATTR_SIZE == ssh_FILEXFER_ATTR_SIZE {
|
||||
fs.Size, b = unmarshalUint64(b)
|
||||
}
|
||||
if flags&ssh_FILEXFER_ATTR_UIDGID == ssh_FILEXFER_ATTR_UIDGID {
|
||||
fs.Uid, b = unmarshalUint32(b)
|
||||
}
|
||||
if flags&ssh_FILEXFER_ATTR_UIDGID == ssh_FILEXFER_ATTR_UIDGID {
|
||||
fs.Gid, b = unmarshalUint32(b)
|
||||
}
|
||||
if flags&ssh_FILEXFER_ATTR_PERMISSIONS == ssh_FILEXFER_ATTR_PERMISSIONS {
|
||||
fs.Mode, b = unmarshalUint32(b)
|
||||
}
|
||||
if flags&ssh_FILEXFER_ATTR_ACMODTIME == ssh_FILEXFER_ATTR_ACMODTIME {
|
||||
fs.Atime, b = unmarshalUint32(b)
|
||||
fs.Mtime, b = unmarshalUint32(b)
|
||||
}
|
||||
if flags&ssh_FILEXFER_ATTR_EXTENDED == ssh_FILEXFER_ATTR_EXTENDED {
|
||||
var count uint32
|
||||
count, b = unmarshalUint32(b)
|
||||
ext := make([]StatExtended, count, count)
|
||||
for i := uint32(0); i < count; i++ {
|
||||
var typ string
|
||||
var data string
|
||||
typ, b = unmarshalString(b)
|
||||
data, b = unmarshalString(b)
|
||||
ext[i] = StatExtended{typ, data}
|
||||
}
|
||||
fs.Extended = ext
|
||||
}
|
||||
return &fs, b
|
||||
}
|
||||
|
||||
// toFileMode converts sftp filemode bits to the os.FileMode specification
|
||||
func toFileMode(mode uint32) os.FileMode {
|
||||
var fm = os.FileMode(mode & 0777)
|
||||
switch mode & syscall.S_IFMT {
|
||||
case syscall.S_IFBLK:
|
||||
fm |= os.ModeDevice
|
||||
case syscall.S_IFCHR:
|
||||
fm |= os.ModeDevice | os.ModeCharDevice
|
||||
case syscall.S_IFDIR:
|
||||
fm |= os.ModeDir
|
||||
case syscall.S_IFIFO:
|
||||
fm |= os.ModeNamedPipe
|
||||
case syscall.S_IFLNK:
|
||||
fm |= os.ModeSymlink
|
||||
case syscall.S_IFREG:
|
||||
// nothing to do
|
||||
case syscall.S_IFSOCK:
|
||||
fm |= os.ModeSocket
|
||||
}
|
||||
if mode&syscall.S_ISGID != 0 {
|
||||
fm |= os.ModeSetgid
|
||||
}
|
||||
if mode&syscall.S_ISUID != 0 {
|
||||
fm |= os.ModeSetuid
|
||||
}
|
||||
if mode&syscall.S_ISVTX != 0 {
|
||||
fm |= os.ModeSticky
|
||||
}
|
||||
return fm
|
||||
}
|
||||
45
Godeps/_workspace/src/github.com/pkg/sftp/attrs_test.go
generated
vendored
Normal file
45
Godeps/_workspace/src/github.com/pkg/sftp/attrs_test.go
generated
vendored
Normal file
@@ -0,0 +1,45 @@
|
||||
package sftp
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"os"
|
||||
"reflect"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
// ensure that attrs implemenst os.FileInfo
|
||||
var _ os.FileInfo = new(fileInfo)
|
||||
|
||||
var unmarshalAttrsTests = []struct {
|
||||
b []byte
|
||||
want *fileInfo
|
||||
rest []byte
|
||||
}{
|
||||
{marshal(nil, struct{ Flags uint32 }{}), &fileInfo{mtime: time.Unix(int64(0), 0)}, nil},
|
||||
{marshal(nil, struct {
|
||||
Flags uint32
|
||||
Size uint64
|
||||
}{ssh_FILEXFER_ATTR_SIZE, 20}), &fileInfo{size: 20, mtime: time.Unix(int64(0), 0)}, nil},
|
||||
{marshal(nil, struct {
|
||||
Flags uint32
|
||||
Size uint64
|
||||
Permissions uint32
|
||||
}{ssh_FILEXFER_ATTR_SIZE | ssh_FILEXFER_ATTR_PERMISSIONS, 20, 0644}), &fileInfo{size: 20, mode: os.FileMode(0644), mtime: time.Unix(int64(0), 0)}, nil},
|
||||
{marshal(nil, struct {
|
||||
Flags uint32
|
||||
Size uint64
|
||||
Uid, Gid, Permissions uint32
|
||||
}{ssh_FILEXFER_ATTR_SIZE | ssh_FILEXFER_ATTR_UIDGID | ssh_FILEXFER_ATTR_UIDGID | ssh_FILEXFER_ATTR_PERMISSIONS, 20, 1000, 1000, 0644}), &fileInfo{size: 20, mode: os.FileMode(0644), mtime: time.Unix(int64(0), 0)}, nil},
|
||||
}
|
||||
|
||||
func TestUnmarshalAttrs(t *testing.T) {
|
||||
for _, tt := range unmarshalAttrsTests {
|
||||
stat, rest := unmarshalAttrs(tt.b)
|
||||
got := fileInfoFromStat(stat, "")
|
||||
tt.want.sys = got.Sys()
|
||||
if !reflect.DeepEqual(got, tt.want) || !bytes.Equal(tt.rest, rest) {
|
||||
t.Errorf("unmarshalAttrs(%#v): want %#v, %#v, got: %#v, %#v", tt.b, tt.want, tt.rest, got, rest)
|
||||
}
|
||||
}
|
||||
}
|
||||
717
Godeps/_workspace/src/github.com/pkg/sftp/client.go
generated
vendored
Normal file
717
Godeps/_workspace/src/github.com/pkg/sftp/client.go
generated
vendored
Normal file
@@ -0,0 +1,717 @@
|
||||
package sftp
|
||||
|
||||
import (
|
||||
"encoding"
|
||||
"io"
|
||||
"os"
|
||||
"path"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/kr/fs"
|
||||
|
||||
"golang.org/x/crypto/ssh"
|
||||
)
|
||||
|
||||
// New creates a new SFTP client on conn.
|
||||
func NewClient(conn *ssh.Client) (*Client, error) {
|
||||
s, err := conn.NewSession()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := s.RequestSubsystem("sftp"); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
pw, err := s.StdinPipe()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
pr, err := s.StdoutPipe()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return NewClientPipe(pr, pw)
|
||||
}
|
||||
|
||||
// NewClientPipe creates a new SFTP client given a Reader and a WriteCloser.
|
||||
// This can be used for connecting to an SFTP server over TCP/TLS or by using
|
||||
// the system's ssh client program (e.g. via exec.Command).
|
||||
func NewClientPipe(rd io.Reader, wr io.WriteCloser) (*Client, error) {
|
||||
sftp := &Client{
|
||||
w: wr,
|
||||
r: rd,
|
||||
}
|
||||
if err := sftp.sendInit(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return sftp, sftp.recvVersion()
|
||||
}
|
||||
|
||||
// Client represents an SFTP session on a *ssh.ClientConn SSH connection.
|
||||
// Multiple Clients can be active on a single SSH connection, and a Client
|
||||
// may be called concurrently from multiple Goroutines.
|
||||
//
|
||||
// Client implements the github.com/kr/fs.FileSystem interface.
|
||||
type Client struct {
|
||||
w io.WriteCloser
|
||||
r io.Reader
|
||||
mu sync.Mutex // locks mu and seralises commands to the server
|
||||
nextid uint32
|
||||
}
|
||||
|
||||
// Close closes the SFTP session.
|
||||
func (c *Client) Close() error { return c.w.Close() }
|
||||
|
||||
// Create creates the named file mode 0666 (before umask), truncating it if
|
||||
// it already exists. If successful, methods on the returned File can be
|
||||
// used for I/O; the associated file descriptor has mode O_RDWR.
|
||||
func (c *Client) Create(path string) (*File, error) {
|
||||
return c.open(path, flags(os.O_RDWR|os.O_CREATE|os.O_TRUNC))
|
||||
}
|
||||
|
||||
const sftpProtocolVersion = 3 // http://tools.ietf.org/html/draft-ietf-secsh-filexfer-02
|
||||
|
||||
func (c *Client) sendInit() error {
|
||||
return sendPacket(c.w, sshFxInitPacket{
|
||||
Version: sftpProtocolVersion, // http://tools.ietf.org/html/draft-ietf-secsh-filexfer-02
|
||||
})
|
||||
}
|
||||
|
||||
// returns the current value of c.nextid and increments it
|
||||
// callers is expected to hold c.mu
|
||||
func (c *Client) nextId() uint32 {
|
||||
v := c.nextid
|
||||
c.nextid++
|
||||
return v
|
||||
}
|
||||
|
||||
func (c *Client) recvVersion() error {
|
||||
typ, data, err := recvPacket(c.r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if typ != ssh_FXP_VERSION {
|
||||
return &unexpectedPacketErr{ssh_FXP_VERSION, typ}
|
||||
}
|
||||
|
||||
version, _ := unmarshalUint32(data)
|
||||
if version != sftpProtocolVersion {
|
||||
return &unexpectedVersionErr{sftpProtocolVersion, version}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Walk returns a new Walker rooted at root.
|
||||
func (c *Client) Walk(root string) *fs.Walker {
|
||||
return fs.WalkFS(root, c)
|
||||
}
|
||||
|
||||
// ReadDir reads the directory named by dirname and returns a list of
|
||||
// directory entries.
|
||||
func (c *Client) ReadDir(p string) ([]os.FileInfo, error) {
|
||||
handle, err := c.opendir(p)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer c.close(handle) // this has to defer earlier than the lock below
|
||||
var attrs []os.FileInfo
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
var done = false
|
||||
for !done {
|
||||
id := c.nextId()
|
||||
typ, data, err1 := c.sendRequest(sshFxpReaddirPacket{
|
||||
Id: id,
|
||||
Handle: handle,
|
||||
})
|
||||
if err1 != nil {
|
||||
err = err1
|
||||
done = true
|
||||
break
|
||||
}
|
||||
switch typ {
|
||||
case ssh_FXP_NAME:
|
||||
sid, data := unmarshalUint32(data)
|
||||
if sid != id {
|
||||
return nil, &unexpectedIdErr{id, sid}
|
||||
}
|
||||
count, data := unmarshalUint32(data)
|
||||
for i := uint32(0); i < count; i++ {
|
||||
var filename string
|
||||
filename, data = unmarshalString(data)
|
||||
_, data = unmarshalString(data) // discard longname
|
||||
var attr *FileStat
|
||||
attr, data = unmarshalAttrs(data)
|
||||
if filename == "." || filename == ".." {
|
||||
continue
|
||||
}
|
||||
attrs = append(attrs, fileInfoFromStat(attr, path.Base(filename)))
|
||||
}
|
||||
case ssh_FXP_STATUS:
|
||||
// TODO(dfc) scope warning!
|
||||
err = eofOrErr(unmarshalStatus(id, data))
|
||||
done = true
|
||||
default:
|
||||
return nil, unimplementedPacketErr(typ)
|
||||
}
|
||||
}
|
||||
if err == io.EOF {
|
||||
err = nil
|
||||
}
|
||||
return attrs, err
|
||||
}
|
||||
func (c *Client) opendir(path string) (string, error) {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
id := c.nextId()
|
||||
typ, data, err := c.sendRequest(sshFxpOpendirPacket{
|
||||
Id: id,
|
||||
Path: path,
|
||||
})
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
switch typ {
|
||||
case ssh_FXP_HANDLE:
|
||||
sid, data := unmarshalUint32(data)
|
||||
if sid != id {
|
||||
return "", &unexpectedIdErr{id, sid}
|
||||
}
|
||||
handle, _ := unmarshalString(data)
|
||||
return handle, nil
|
||||
case ssh_FXP_STATUS:
|
||||
return "", unmarshalStatus(id, data)
|
||||
default:
|
||||
return "", unimplementedPacketErr(typ)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Client) Lstat(p string) (os.FileInfo, error) {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
id := c.nextId()
|
||||
typ, data, err := c.sendRequest(sshFxpLstatPacket{
|
||||
Id: id,
|
||||
Path: p,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
switch typ {
|
||||
case ssh_FXP_ATTRS:
|
||||
sid, data := unmarshalUint32(data)
|
||||
if sid != id {
|
||||
return nil, &unexpectedIdErr{id, sid}
|
||||
}
|
||||
attr, _ := unmarshalAttrs(data)
|
||||
return fileInfoFromStat(attr, path.Base(p)), nil
|
||||
case ssh_FXP_STATUS:
|
||||
return nil, unmarshalStatus(id, data)
|
||||
default:
|
||||
return nil, unimplementedPacketErr(typ)
|
||||
}
|
||||
}
|
||||
|
||||
// ReadLink reads the target of a symbolic link.
|
||||
func (c *Client) ReadLink(p string) (string, error) {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
id := c.nextId()
|
||||
typ, data, err := c.sendRequest(sshFxpReadlinkPacket{
|
||||
Id: id,
|
||||
Path: p,
|
||||
})
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
switch typ {
|
||||
case ssh_FXP_NAME:
|
||||
sid, data := unmarshalUint32(data)
|
||||
if sid != id {
|
||||
return "", &unexpectedIdErr{id, sid}
|
||||
}
|
||||
count, data := unmarshalUint32(data)
|
||||
if count != 1 {
|
||||
return "", unexpectedCount(1, count)
|
||||
}
|
||||
filename, _ := unmarshalString(data) // ignore dummy attributes
|
||||
return filename, nil
|
||||
case ssh_FXP_STATUS:
|
||||
return "", unmarshalStatus(id, data)
|
||||
default:
|
||||
return "", unimplementedPacketErr(typ)
|
||||
}
|
||||
}
|
||||
|
||||
// setstat is a convience wrapper to allow for changing of various parts of the file descriptor.
|
||||
func (c *Client) setstat(path string, flags uint32, attrs interface{}) error {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
id := c.nextId()
|
||||
typ, data, err := c.sendRequest(sshFxpSetstatPacket{
|
||||
Id: id,
|
||||
Path: path,
|
||||
Flags: flags,
|
||||
Attrs: attrs,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
switch typ {
|
||||
case ssh_FXP_STATUS:
|
||||
return okOrErr(unmarshalStatus(id, data))
|
||||
default:
|
||||
return unimplementedPacketErr(typ)
|
||||
}
|
||||
}
|
||||
|
||||
// Chtimes changes the access and modification times of the named file.
|
||||
func (c *Client) Chtimes(path string, atime time.Time, mtime time.Time) error {
|
||||
type times struct {
|
||||
Atime uint32
|
||||
Mtime uint32
|
||||
}
|
||||
attrs := times{uint32(atime.Unix()), uint32(mtime.Unix())}
|
||||
return c.setstat(path, ssh_FILEXFER_ATTR_ACMODTIME, attrs)
|
||||
}
|
||||
|
||||
// Chown changes the user and group owners of the named file.
|
||||
func (c *Client) Chown(path string, uid, gid int) error {
|
||||
type owner struct {
|
||||
Uid uint32
|
||||
Gid uint32
|
||||
}
|
||||
attrs := owner{uint32(uid), uint32(gid)}
|
||||
return c.setstat(path, ssh_FILEXFER_ATTR_UIDGID, attrs)
|
||||
}
|
||||
|
||||
// Chmod changes the permissions of the named file.
|
||||
func (c *Client) Chmod(path string, mode os.FileMode) error {
|
||||
return c.setstat(path, ssh_FILEXFER_ATTR_PERMISSIONS, uint32(mode))
|
||||
}
|
||||
|
||||
// Truncate sets the size of the named file. Although it may be safely assumed
|
||||
// that if the size is less than its current size it will be truncated to fit,
|
||||
// the SFTP protocol does not specify what behavior the server should do when setting
|
||||
// size greater than the current size.
|
||||
func (c *Client) Truncate(path string, size int64) error {
|
||||
return c.setstat(path, ssh_FILEXFER_ATTR_SIZE, uint64(size))
|
||||
}
|
||||
|
||||
// Open opens the named file for reading. If successful, methods on the
|
||||
// returned file can be used for reading; the associated file descriptor
|
||||
// has mode O_RDONLY.
|
||||
func (c *Client) Open(path string) (*File, error) {
|
||||
return c.open(path, flags(os.O_RDONLY))
|
||||
}
|
||||
|
||||
// OpenFile is the generalized open call; most users will use Open or
|
||||
// Create instead. It opens the named file with specified flag (O_RDONLY
|
||||
// etc.). If successful, methods on the returned File can be used for I/O.
|
||||
func (c *Client) OpenFile(path string, f int) (*File, error) {
|
||||
return c.open(path, flags(f))
|
||||
}
|
||||
|
||||
func (c *Client) open(path string, pflags uint32) (*File, error) {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
id := c.nextId()
|
||||
typ, data, err := c.sendRequest(sshFxpOpenPacket{
|
||||
Id: id,
|
||||
Path: path,
|
||||
Pflags: pflags,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
switch typ {
|
||||
case ssh_FXP_HANDLE:
|
||||
sid, data := unmarshalUint32(data)
|
||||
if sid != id {
|
||||
return nil, &unexpectedIdErr{id, sid}
|
||||
}
|
||||
handle, _ := unmarshalString(data)
|
||||
return &File{c: c, path: path, handle: handle}, nil
|
||||
case ssh_FXP_STATUS:
|
||||
return nil, unmarshalStatus(id, data)
|
||||
default:
|
||||
return nil, unimplementedPacketErr(typ)
|
||||
}
|
||||
}
|
||||
|
||||
// readAt reads len(buf) bytes from the remote file indicated by handle starting
|
||||
// from offset.
|
||||
func (c *Client) readAt(handle string, offset uint64, buf []byte) (uint32, error) {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
id := c.nextId()
|
||||
typ, data, err := c.sendRequest(sshFxpReadPacket{
|
||||
Id: id,
|
||||
Handle: handle,
|
||||
Offset: offset,
|
||||
Len: uint32(len(buf)),
|
||||
})
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
switch typ {
|
||||
case ssh_FXP_DATA:
|
||||
sid, data := unmarshalUint32(data)
|
||||
if sid != id {
|
||||
return 0, &unexpectedIdErr{id, sid}
|
||||
}
|
||||
l, data := unmarshalUint32(data)
|
||||
n := copy(buf, data[:l])
|
||||
return uint32(n), nil
|
||||
case ssh_FXP_STATUS:
|
||||
return 0, eofOrErr(unmarshalStatus(id, data))
|
||||
default:
|
||||
return 0, unimplementedPacketErr(typ)
|
||||
}
|
||||
}
|
||||
|
||||
// close closes a handle handle previously returned in the response
|
||||
// to SSH_FXP_OPEN or SSH_FXP_OPENDIR. The handle becomes invalid
|
||||
// immediately after this request has been sent.
|
||||
func (c *Client) close(handle string) error {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
id := c.nextId()
|
||||
typ, data, err := c.sendRequest(sshFxpClosePacket{
|
||||
Id: id,
|
||||
Handle: handle,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
switch typ {
|
||||
case ssh_FXP_STATUS:
|
||||
return okOrErr(unmarshalStatus(id, data))
|
||||
default:
|
||||
return unimplementedPacketErr(typ)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Client) fstat(handle string) (*FileStat, error) {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
id := c.nextId()
|
||||
typ, data, err := c.sendRequest(sshFxpFstatPacket{
|
||||
Id: id,
|
||||
Handle: handle,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
switch typ {
|
||||
case ssh_FXP_ATTRS:
|
||||
sid, data := unmarshalUint32(data)
|
||||
if sid != id {
|
||||
return nil, &unexpectedIdErr{id, sid}
|
||||
}
|
||||
attr, _ := unmarshalAttrs(data)
|
||||
return attr, nil
|
||||
case ssh_FXP_STATUS:
|
||||
return nil, unmarshalStatus(id, data)
|
||||
default:
|
||||
return nil, unimplementedPacketErr(typ)
|
||||
}
|
||||
}
|
||||
|
||||
// Join joins any number of path elements into a single path, adding a
|
||||
// separating slash if necessary. The result is Cleaned; in particular, all
|
||||
// empty strings are ignored.
|
||||
func (c *Client) Join(elem ...string) string { return path.Join(elem...) }
|
||||
|
||||
// Remove removes the specified file or directory. An error will be returned if no
|
||||
// file or directory with the specified path exists, or if the specified directory
|
||||
// is not empty.
|
||||
func (c *Client) Remove(path string) error {
|
||||
err := c.removeFile(path)
|
||||
if status, ok := err.(*StatusError); ok && status.Code == ssh_FX_FAILURE {
|
||||
err = c.removeDirectory(path)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *Client) removeFile(path string) error {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
id := c.nextId()
|
||||
typ, data, err := c.sendRequest(sshFxpRemovePacket{
|
||||
Id: id,
|
||||
Filename: path,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
switch typ {
|
||||
case ssh_FXP_STATUS:
|
||||
return okOrErr(unmarshalStatus(id, data))
|
||||
default:
|
||||
return unimplementedPacketErr(typ)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Client) removeDirectory(path string) error {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
id := c.nextId()
|
||||
typ, data, err := c.sendRequest(sshFxpRmdirPacket{
|
||||
Id: id,
|
||||
Path: path,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
switch typ {
|
||||
case ssh_FXP_STATUS:
|
||||
return okOrErr(unmarshalStatus(id, data))
|
||||
default:
|
||||
return unimplementedPacketErr(typ)
|
||||
}
|
||||
}
|
||||
|
||||
// Rename renames a file.
|
||||
func (c *Client) Rename(oldname, newname string) error {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
id := c.nextId()
|
||||
typ, data, err := c.sendRequest(sshFxpRenamePacket{
|
||||
Id: id,
|
||||
Oldpath: oldname,
|
||||
Newpath: newname,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
switch typ {
|
||||
case ssh_FXP_STATUS:
|
||||
return okOrErr(unmarshalStatus(id, data))
|
||||
default:
|
||||
return unimplementedPacketErr(typ)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Client) sendRequest(p encoding.BinaryMarshaler) (byte, []byte, error) {
|
||||
if err := sendPacket(c.w, p); err != nil {
|
||||
return 0, nil, err
|
||||
}
|
||||
return recvPacket(c.r)
|
||||
}
|
||||
|
||||
// writeAt writes len(buf) bytes from the remote file indicated by handle starting
|
||||
// from offset.
|
||||
func (c *Client) writeAt(handle string, offset uint64, buf []byte) (uint32, error) {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
id := c.nextId()
|
||||
typ, data, err := c.sendRequest(sshFxpWritePacket{
|
||||
Id: id,
|
||||
Handle: handle,
|
||||
Offset: offset,
|
||||
Length: uint32(len(buf)),
|
||||
Data: buf,
|
||||
})
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
switch typ {
|
||||
case ssh_FXP_STATUS:
|
||||
if err := okOrErr(unmarshalStatus(id, data)); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return uint32(len(buf)), nil
|
||||
default:
|
||||
return 0, unimplementedPacketErr(typ)
|
||||
}
|
||||
}
|
||||
|
||||
// Creates the specified directory. An error will be returned if a file or
|
||||
// directory with the specified path already exists, or if the directory's
|
||||
// parent folder does not exist (the method cannot create complete paths).
|
||||
func (c *Client) Mkdir(path string) error {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
id := c.nextId()
|
||||
typ, data, err := c.sendRequest(sshFxpMkdirPacket{
|
||||
Id: id,
|
||||
Path: path,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
switch typ {
|
||||
case ssh_FXP_STATUS:
|
||||
return okOrErr(unmarshalStatus(id, data))
|
||||
default:
|
||||
return unimplementedPacketErr(typ)
|
||||
}
|
||||
}
|
||||
|
||||
// File represents a remote file.
|
||||
type File struct {
|
||||
c *Client
|
||||
path string
|
||||
handle string
|
||||
offset uint64 // current offset within remote file
|
||||
}
|
||||
|
||||
// Close closes the File, rendering it unusable for I/O. It returns an
|
||||
// error, if any.
|
||||
func (f *File) Close() error {
|
||||
return f.c.close(f.handle)
|
||||
}
|
||||
|
||||
// Read reads up to len(b) bytes from the File. It returns the number of
|
||||
// bytes read and an error, if any. EOF is signaled by a zero count with
|
||||
// err set to io.EOF.
|
||||
func (f *File) Read(b []byte) (int, error) {
|
||||
var read int
|
||||
for len(b) > 0 {
|
||||
n, err := f.c.readAt(f.handle, f.offset, b[:min(len(b), maxWritePacket)])
|
||||
f.offset += uint64(n)
|
||||
read += int(n)
|
||||
if err != nil {
|
||||
return read, err
|
||||
}
|
||||
b = b[n:]
|
||||
}
|
||||
return read, nil
|
||||
}
|
||||
|
||||
// Stat returns the FileInfo structure describing file. If there is an
|
||||
// error.
|
||||
func (f *File) Stat() (os.FileInfo, error) {
|
||||
fs, err := f.c.fstat(f.handle)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return fileInfoFromStat(fs, path.Base(f.path)), nil
|
||||
}
|
||||
|
||||
// clamp writes to less than 32k
|
||||
const maxWritePacket = 1 << 15
|
||||
|
||||
// Write writes len(b) bytes to the File. It returns the number of bytes
|
||||
// written and an error, if any. Write returns a non-nil error when n !=
|
||||
// len(b).
|
||||
func (f *File) Write(b []byte) (int, error) {
|
||||
var written int
|
||||
for len(b) > 0 {
|
||||
n, err := f.c.writeAt(f.handle, f.offset, b[:min(len(b), maxWritePacket)])
|
||||
f.offset += uint64(n)
|
||||
written += int(n)
|
||||
if err != nil {
|
||||
return written, err
|
||||
}
|
||||
b = b[n:]
|
||||
}
|
||||
return written, nil
|
||||
}
|
||||
|
||||
// Seek implements io.Seeker by setting the client offset for the next Read or
|
||||
// Write. It returns the next offset read. Seeking before or after the end of
|
||||
// the file is undefined. Seeking relative to the end calls Stat.
|
||||
func (f *File) Seek(offset int64, whence int) (int64, error) {
|
||||
switch whence {
|
||||
case os.SEEK_SET:
|
||||
f.offset = uint64(offset)
|
||||
case os.SEEK_CUR:
|
||||
f.offset = uint64(int64(f.offset) + offset)
|
||||
case os.SEEK_END:
|
||||
fi, err := f.Stat()
|
||||
if err != nil {
|
||||
return int64(f.offset), err
|
||||
}
|
||||
f.offset = uint64(fi.Size() + offset)
|
||||
default:
|
||||
return int64(f.offset), unimplementedSeekWhence(whence)
|
||||
}
|
||||
return int64(f.offset), nil
|
||||
}
|
||||
|
||||
// Chown changes the uid/gid of the current file.
|
||||
func (f *File) Chown(uid, gid int) error {
|
||||
return f.c.Chown(f.path, uid, gid)
|
||||
}
|
||||
|
||||
// Chmod changes the permissions of the current file.
|
||||
func (f *File) Chmod(mode os.FileMode) error {
|
||||
return f.c.Chmod(f.path, mode)
|
||||
}
|
||||
|
||||
// Truncate sets the size of the current file. Although it may be safely assumed
|
||||
// that if the size is less than its current size it will be truncated to fit,
|
||||
// the SFTP protocol does not specify what behavior the server should do when setting
|
||||
// size greater than the current size.
|
||||
func (f *File) Truncate(size int64) error {
|
||||
return f.c.Truncate(f.path, size)
|
||||
}
|
||||
|
||||
func min(a, b int) int {
|
||||
if a > b {
|
||||
return b
|
||||
}
|
||||
return a
|
||||
}
|
||||
|
||||
// okOrErr returns nil if Err.Code is SSH_FX_OK, otherwise it returns the error.
|
||||
func okOrErr(err error) error {
|
||||
if err, ok := err.(*StatusError); ok && err.Code == ssh_FX_OK {
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func eofOrErr(err error) error {
|
||||
if err, ok := err.(*StatusError); ok && err.Code == ssh_FX_EOF {
|
||||
return io.EOF
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func unmarshalStatus(id uint32, data []byte) error {
|
||||
sid, data := unmarshalUint32(data)
|
||||
if sid != id {
|
||||
return &unexpectedIdErr{id, sid}
|
||||
}
|
||||
code, data := unmarshalUint32(data)
|
||||
msg, data := unmarshalString(data)
|
||||
lang, _ := unmarshalString(data)
|
||||
return &StatusError{
|
||||
Code: code,
|
||||
msg: msg,
|
||||
lang: lang,
|
||||
}
|
||||
}
|
||||
|
||||
// flags converts the flags passed to OpenFile into ssh flags.
|
||||
// Unsupported flags are ignored.
|
||||
func flags(f int) uint32 {
|
||||
var out uint32
|
||||
switch f & os.O_WRONLY {
|
||||
case os.O_WRONLY:
|
||||
out |= ssh_FXF_WRITE
|
||||
case os.O_RDONLY:
|
||||
out |= ssh_FXF_READ
|
||||
}
|
||||
if f&os.O_RDWR == os.O_RDWR {
|
||||
out |= ssh_FXF_READ | ssh_FXF_WRITE
|
||||
}
|
||||
if f&os.O_APPEND == os.O_APPEND {
|
||||
out |= ssh_FXF_APPEND
|
||||
}
|
||||
if f&os.O_CREATE == os.O_CREATE {
|
||||
out |= ssh_FXF_CREAT
|
||||
}
|
||||
if f&os.O_TRUNC == os.O_TRUNC {
|
||||
out |= ssh_FXF_TRUNC
|
||||
}
|
||||
if f&os.O_EXCL == os.O_EXCL {
|
||||
out |= ssh_FXF_EXCL
|
||||
}
|
||||
return out
|
||||
}
|
||||
898
Godeps/_workspace/src/github.com/pkg/sftp/client_integration_test.go
generated
vendored
Normal file
898
Godeps/_workspace/src/github.com/pkg/sftp/client_integration_test.go
generated
vendored
Normal file
@@ -0,0 +1,898 @@
|
||||
package sftp
|
||||
|
||||
// sftp integration tests
|
||||
// enable with -integration
|
||||
|
||||
import (
|
||||
"crypto/sha1"
|
||||
"flag"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"math/rand"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"testing"
|
||||
"testing/quick"
|
||||
|
||||
"github.com/kr/fs"
|
||||
)
|
||||
|
||||
const (
|
||||
READONLY = true
|
||||
READWRITE = false
|
||||
|
||||
debuglevel = "ERROR" // set to "DEBUG" for debugging
|
||||
)
|
||||
|
||||
var testIntegration = flag.Bool("integration", false, "perform integration tests against sftp server process")
|
||||
var testSftp = flag.String("sftp", "/usr/lib/openssh/sftp-server", "location of the sftp server binary")
|
||||
|
||||
// testClient returns a *Client connected to a localy running sftp-server
|
||||
// the *exec.Cmd returned must be defer Wait'd.
|
||||
func testClient(t testing.TB, readonly bool) (*Client, *exec.Cmd) {
|
||||
if !*testIntegration {
|
||||
t.Skip("skipping intergration test")
|
||||
}
|
||||
cmd := exec.Command(*testSftp, "-e", "-R", "-l", debuglevel) // log to stderr, read only
|
||||
if !readonly {
|
||||
cmd = exec.Command(*testSftp, "-e", "-l", debuglevel) // log to stderr
|
||||
}
|
||||
cmd.Stderr = os.Stdout
|
||||
pw, err := cmd.StdinPipe()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
pr, err := cmd.StdoutPipe()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := cmd.Start(); err != nil {
|
||||
t.Skipf("could not start sftp-server process: %v", err)
|
||||
}
|
||||
|
||||
sftp, err := NewClientPipe(pr, pw)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if err := sftp.sendInit(); err != nil {
|
||||
defer cmd.Wait()
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := sftp.recvVersion(); err != nil {
|
||||
defer cmd.Wait()
|
||||
t.Fatal(err)
|
||||
}
|
||||
return sftp, cmd
|
||||
}
|
||||
|
||||
func TestNewClient(t *testing.T) {
|
||||
sftp, cmd := testClient(t, READONLY)
|
||||
defer cmd.Wait()
|
||||
|
||||
if err := sftp.Close(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestClientLstat(t *testing.T) {
|
||||
sftp, cmd := testClient(t, READONLY)
|
||||
defer cmd.Wait()
|
||||
defer sftp.Close()
|
||||
|
||||
f, err := ioutil.TempFile("", "sftptest")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer os.Remove(f.Name())
|
||||
|
||||
want, err := os.Lstat(f.Name())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
got, err := sftp.Lstat(f.Name())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if !sameFile(want, got) {
|
||||
t.Fatalf("Lstat(%q): want %#v, got %#v", f.Name(), want, got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestClientLstatMissing(t *testing.T) {
|
||||
sftp, cmd := testClient(t, READONLY)
|
||||
defer cmd.Wait()
|
||||
defer sftp.Close()
|
||||
|
||||
f, err := ioutil.TempFile("", "sftptest")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
os.Remove(f.Name())
|
||||
|
||||
_, err = sftp.Lstat(f.Name())
|
||||
if err1, ok := err.(*StatusError); !ok || err1.Code != ssh_FX_NO_SUCH_FILE {
|
||||
t.Fatalf("Lstat: want: %v, got %#v", ssh_FX_NO_SUCH_FILE, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestClientMkdir(t *testing.T) {
|
||||
sftp, cmd := testClient(t, READWRITE)
|
||||
defer cmd.Wait()
|
||||
defer sftp.Close()
|
||||
|
||||
dir, err := ioutil.TempDir("", "sftptest")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
sub := path.Join(dir, "mkdir1")
|
||||
if err := sftp.Mkdir(sub); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := os.Lstat(sub); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestClientOpen(t *testing.T) {
|
||||
sftp, cmd := testClient(t, READONLY)
|
||||
defer cmd.Wait()
|
||||
defer sftp.Close()
|
||||
|
||||
f, err := ioutil.TempFile("", "sftptest")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
const seekBytes = 128 * 1024
|
||||
|
||||
type seek struct {
|
||||
offset int64
|
||||
}
|
||||
|
||||
func (s seek) Generate(r *rand.Rand, _ int) reflect.Value {
|
||||
s.offset = int64(r.Int31n(seekBytes))
|
||||
return reflect.ValueOf(s)
|
||||
}
|
||||
|
||||
func (s seek) set(t *testing.T, r io.ReadSeeker) {
|
||||
if _, err := r.Seek(s.offset, os.SEEK_SET); err != nil {
|
||||
t.Fatalf("error while seeking with %+v: %v", s, err)
|
||||
}
|
||||
}
|
||||
|
||||
func (s seek) current(t *testing.T, r io.ReadSeeker) {
|
||||
const mid = seekBytes / 2
|
||||
|
||||
skip := s.offset / 2
|
||||
if s.offset > mid {
|
||||
skip = -skip
|
||||
}
|
||||
|
||||
if _, err := r.Seek(mid, os.SEEK_SET); err != nil {
|
||||
t.Fatalf("error seeking to midpoint with %+v: %v", s, err)
|
||||
}
|
||||
if _, err := r.Seek(skip, os.SEEK_CUR); err != nil {
|
||||
t.Fatalf("error seeking from %d with %+v: %v", mid, s, err)
|
||||
}
|
||||
}
|
||||
|
||||
func (s seek) end(t *testing.T, r io.ReadSeeker) {
|
||||
if _, err := r.Seek(-s.offset, os.SEEK_END); err != nil {
|
||||
t.Fatalf("error seeking from end with %+v: %v", s, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestClientSeek(t *testing.T) {
|
||||
sftp, cmd := testClient(t, READONLY)
|
||||
defer cmd.Wait()
|
||||
defer sftp.Close()
|
||||
|
||||
fOS, err := ioutil.TempFile("", "seek-test")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer fOS.Close()
|
||||
|
||||
fSFTP, err := sftp.Open(fOS.Name())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer fSFTP.Close()
|
||||
|
||||
writeN(t, fOS, seekBytes)
|
||||
|
||||
if err := quick.CheckEqual(
|
||||
func(s seek) (string, int64) { s.set(t, fOS); return readHash(t, fOS) },
|
||||
func(s seek) (string, int64) { s.set(t, fSFTP); return readHash(t, fSFTP) },
|
||||
nil,
|
||||
); err != nil {
|
||||
t.Errorf("Seek: expected equal absolute seeks: %v", err)
|
||||
}
|
||||
|
||||
if err := quick.CheckEqual(
|
||||
func(s seek) (string, int64) { s.current(t, fOS); return readHash(t, fOS) },
|
||||
func(s seek) (string, int64) { s.current(t, fSFTP); return readHash(t, fSFTP) },
|
||||
nil,
|
||||
); err != nil {
|
||||
t.Errorf("Seek: expected equal seeks from middle: %v", err)
|
||||
}
|
||||
|
||||
if err := quick.CheckEqual(
|
||||
func(s seek) (string, int64) { s.end(t, fOS); return readHash(t, fOS) },
|
||||
func(s seek) (string, int64) { s.end(t, fSFTP); return readHash(t, fSFTP) },
|
||||
nil,
|
||||
); err != nil {
|
||||
t.Errorf("Seek: expected equal seeks from end: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestClientCreate(t *testing.T) {
|
||||
sftp, cmd := testClient(t, READWRITE)
|
||||
defer cmd.Wait()
|
||||
defer sftp.Close()
|
||||
|
||||
f, err := ioutil.TempFile("", "sftptest")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer f.Close()
|
||||
defer os.Remove(f.Name())
|
||||
|
||||
f2, err := sftp.Create(f.Name())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer f2.Close()
|
||||
}
|
||||
|
||||
func TestClientAppend(t *testing.T) {
|
||||
sftp, cmd := testClient(t, READWRITE)
|
||||
defer cmd.Wait()
|
||||
defer sftp.Close()
|
||||
|
||||
f, err := ioutil.TempFile("", "sftptest")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer f.Close()
|
||||
defer os.Remove(f.Name())
|
||||
|
||||
f2, err := sftp.OpenFile(f.Name(), os.O_RDWR|os.O_APPEND)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer f2.Close()
|
||||
}
|
||||
|
||||
func TestClientCreateFailed(t *testing.T) {
|
||||
sftp, cmd := testClient(t, READONLY)
|
||||
defer cmd.Wait()
|
||||
defer sftp.Close()
|
||||
|
||||
f, err := ioutil.TempFile("", "sftptest")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer f.Close()
|
||||
defer os.Remove(f.Name())
|
||||
|
||||
f2, err := sftp.Create(f.Name())
|
||||
if err1, ok := err.(*StatusError); !ok || err1.Code != ssh_FX_PERMISSION_DENIED {
|
||||
t.Fatalf("Create: want: %v, got %#v", ssh_FX_PERMISSION_DENIED, err)
|
||||
}
|
||||
if err == nil {
|
||||
f2.Close()
|
||||
}
|
||||
}
|
||||
|
||||
func TestClientFileStat(t *testing.T) {
|
||||
sftp, cmd := testClient(t, READONLY)
|
||||
defer cmd.Wait()
|
||||
defer sftp.Close()
|
||||
|
||||
f, err := ioutil.TempFile("", "sftptest")
|
||||
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)
|
||||
}
|
||||
|
||||
got, err := f2.Stat()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if !sameFile(want, got) {
|
||||
t.Fatalf("Lstat(%q): want %#v, got %#v", f.Name(), want, got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestClientRemove(t *testing.T) {
|
||||
sftp, cmd := testClient(t, READWRITE)
|
||||
defer cmd.Wait()
|
||||
defer sftp.Close()
|
||||
|
||||
f, err := ioutil.TempFile("", "sftptest")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := sftp.Remove(f.Name()); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := os.Lstat(f.Name()); !os.IsNotExist(err) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestClientRemoveDir(t *testing.T) {
|
||||
sftp, cmd := testClient(t, READWRITE)
|
||||
defer cmd.Wait()
|
||||
defer sftp.Close()
|
||||
|
||||
dir, err := ioutil.TempDir("", "sftptest")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := sftp.Remove(dir); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := os.Lstat(dir); !os.IsNotExist(err) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestClientRemoveFailed(t *testing.T) {
|
||||
sftp, cmd := testClient(t, READONLY)
|
||||
defer cmd.Wait()
|
||||
defer sftp.Close()
|
||||
|
||||
f, err := ioutil.TempFile("", "sftptest")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := sftp.Remove(f.Name()); err == nil {
|
||||
t.Fatalf("Remove(%v): want: permission denied, got %v", f.Name(), err)
|
||||
}
|
||||
if _, err := os.Lstat(f.Name()); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestClientRename(t *testing.T) {
|
||||
sftp, cmd := testClient(t, READWRITE)
|
||||
defer cmd.Wait()
|
||||
defer sftp.Close()
|
||||
|
||||
f, err := ioutil.TempFile("", "sftptest")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
f2 := f.Name() + ".new"
|
||||
if err := sftp.Rename(f.Name(), f2); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := os.Lstat(f.Name()); !os.IsNotExist(err) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := os.Lstat(f2); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestClientReadLine(t *testing.T) {
|
||||
sftp, cmd := testClient(t, READWRITE)
|
||||
defer cmd.Wait()
|
||||
defer sftp.Close()
|
||||
|
||||
f, err := ioutil.TempFile("", "sftptest")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
f2 := f.Name() + ".sym"
|
||||
if err := os.Symlink(f.Name(), f2); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := sftp.ReadLink(f2); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func sameFile(want, got os.FileInfo) bool {
|
||||
return want.Name() == got.Name() &&
|
||||
want.Size() == got.Size()
|
||||
}
|
||||
|
||||
var clientReadTests = []struct {
|
||||
n int64
|
||||
}{
|
||||
{0},
|
||||
{1},
|
||||
{1000},
|
||||
{1024},
|
||||
{1025},
|
||||
{2048},
|
||||
{4096},
|
||||
{1 << 12},
|
||||
{1 << 13},
|
||||
{1 << 14},
|
||||
{1 << 15},
|
||||
{1 << 16},
|
||||
{1 << 17},
|
||||
{1 << 18},
|
||||
{1 << 19},
|
||||
{1 << 20},
|
||||
}
|
||||
|
||||
func TestClientRead(t *testing.T) {
|
||||
sftp, cmd := testClient(t, READONLY)
|
||||
defer cmd.Wait()
|
||||
defer sftp.Close()
|
||||
|
||||
d, err := ioutil.TempDir("", "sftptest")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer os.RemoveAll(d)
|
||||
|
||||
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)
|
||||
f2, err := sftp.Open(f.Name())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer f2.Close()
|
||||
hash2, n := readHash(t, f2)
|
||||
if hash != hash2 || tt.n != n {
|
||||
t.Errorf("Read: hash: want: %q, got %q, read: want: %v, got %v", hash, hash2, tt.n, n)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// readHash reads r until EOF returning the number of bytes read
|
||||
// and the hash of the contents.
|
||||
func readHash(t *testing.T, r io.Reader) (string, int64) {
|
||||
h := sha1.New()
|
||||
tr := io.TeeReader(r, h)
|
||||
read, err := io.Copy(ioutil.Discard, tr)
|
||||
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 {
|
||||
rand, err := os.Open("/dev/urandom")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer rand.Close()
|
||||
|
||||
h := sha1.New()
|
||||
|
||||
mw := io.MultiWriter(w, h)
|
||||
|
||||
written, err := io.CopyN(mw, rand, n)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if written != n {
|
||||
t.Fatalf("CopyN(%v): wrote: %v", n, written)
|
||||
}
|
||||
return string(h.Sum(nil))
|
||||
}
|
||||
|
||||
var clientWriteTests = []struct {
|
||||
n int
|
||||
total int64 // cumulative file size
|
||||
}{
|
||||
{0, 0},
|
||||
{1, 1},
|
||||
{0, 1},
|
||||
{999, 1000},
|
||||
{24, 1024},
|
||||
{1023, 2047},
|
||||
{2048, 4095},
|
||||
{1 << 12, 8191},
|
||||
{1 << 13, 16383},
|
||||
{1 << 14, 32767},
|
||||
{1 << 15, 65535},
|
||||
{1 << 16, 131071},
|
||||
{1 << 17, 262143},
|
||||
{1 << 18, 524287},
|
||||
{1 << 19, 1048575},
|
||||
{1 << 20, 2097151},
|
||||
{1 << 21, 4194303},
|
||||
}
|
||||
|
||||
func TestClientWrite(t *testing.T) {
|
||||
sftp, cmd := testClient(t, READWRITE)
|
||||
defer cmd.Wait()
|
||||
defer sftp.Close()
|
||||
|
||||
d, err := ioutil.TempDir("", "sftptest")
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// taken from github.com/kr/fs/walk_test.go
|
||||
|
||||
type PathTest struct {
|
||||
path, result string
|
||||
}
|
||||
|
||||
type Node struct {
|
||||
name string
|
||||
entries []*Node // nil if the entry is a file
|
||||
mark int
|
||||
}
|
||||
|
||||
var tree = &Node{
|
||||
"testdata",
|
||||
[]*Node{
|
||||
{"a", nil, 0},
|
||||
{"b", []*Node{}, 0},
|
||||
{"c", nil, 0},
|
||||
{
|
||||
"d",
|
||||
[]*Node{
|
||||
{"x", nil, 0},
|
||||
{"y", []*Node{}, 0},
|
||||
{
|
||||
"z",
|
||||
[]*Node{
|
||||
{"u", nil, 0},
|
||||
{"v", nil, 0},
|
||||
},
|
||||
0,
|
||||
},
|
||||
},
|
||||
0,
|
||||
},
|
||||
},
|
||||
0,
|
||||
}
|
||||
|
||||
func walkTree(n *Node, path string, f func(path string, n *Node)) {
|
||||
f(path, n)
|
||||
for _, e := range n.entries {
|
||||
walkTree(e, filepath.Join(path, e.name), f)
|
||||
}
|
||||
}
|
||||
|
||||
func makeTree(t *testing.T) {
|
||||
walkTree(tree, tree.name, func(path string, n *Node) {
|
||||
if n.entries == nil {
|
||||
fd, err := os.Create(path)
|
||||
if err != nil {
|
||||
t.Errorf("makeTree: %v", err)
|
||||
return
|
||||
}
|
||||
fd.Close()
|
||||
} else {
|
||||
os.Mkdir(path, 0770)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func markTree(n *Node) { walkTree(n, "", func(path string, n *Node) { n.mark++ }) }
|
||||
|
||||
func checkMarks(t *testing.T, report bool) {
|
||||
walkTree(tree, tree.name, func(path string, n *Node) {
|
||||
if n.mark != 1 && report {
|
||||
t.Errorf("node %s mark = %d; expected 1", path, n.mark)
|
||||
}
|
||||
n.mark = 0
|
||||
})
|
||||
}
|
||||
|
||||
// Assumes that each node name is unique. Good enough for a test.
|
||||
// If clear is true, any incoming error is cleared before return. The errors
|
||||
// are always accumulated, though.
|
||||
func mark(path string, info os.FileInfo, err error, errors *[]error, clear bool) error {
|
||||
if err != nil {
|
||||
*errors = append(*errors, err)
|
||||
if clear {
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
name := info.Name()
|
||||
walkTree(tree, tree.name, func(path string, n *Node) {
|
||||
if n.name == name {
|
||||
n.mark++
|
||||
}
|
||||
})
|
||||
return nil
|
||||
}
|
||||
|
||||
func TestClientWalk(t *testing.T) {
|
||||
sftp, cmd := testClient(t, READONLY)
|
||||
defer cmd.Wait()
|
||||
defer sftp.Close()
|
||||
|
||||
makeTree(t)
|
||||
errors := make([]error, 0, 10)
|
||||
clear := true
|
||||
markFn := func(walker *fs.Walker) (err error) {
|
||||
for walker.Step() {
|
||||
err = mark(walker.Path(), walker.Stat(), walker.Err(), &errors, clear)
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
// 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)
|
||||
}
|
||||
}
|
||||
|
||||
func benchmarkRead(b *testing.B, bufsize int) {
|
||||
size := 10*1024*1024 + 123 // ~10MiB
|
||||
|
||||
// open sftp client
|
||||
sftp, cmd := testClient(b, READONLY)
|
||||
defer cmd.Wait()
|
||||
defer sftp.Close()
|
||||
|
||||
buf := make([]byte, bufsize)
|
||||
|
||||
b.ResetTimer()
|
||||
b.SetBytes(int64(size))
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
offset := 0
|
||||
|
||||
f2, err := sftp.Open("/dev/zero")
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
defer f2.Close()
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkRead1k(b *testing.B) {
|
||||
benchmarkRead(b, 1*1024)
|
||||
}
|
||||
|
||||
func BenchmarkRead16k(b *testing.B) {
|
||||
benchmarkRead(b, 16*1024)
|
||||
}
|
||||
|
||||
func BenchmarkRead32k(b *testing.B) {
|
||||
benchmarkRead(b, 32*1024)
|
||||
}
|
||||
|
||||
func BenchmarkRead128k(b *testing.B) {
|
||||
benchmarkRead(b, 128*1024)
|
||||
}
|
||||
|
||||
func BenchmarkRead512k(b *testing.B) {
|
||||
benchmarkRead(b, 512*1024)
|
||||
}
|
||||
|
||||
func BenchmarkRead1MiB(b *testing.B) {
|
||||
benchmarkRead(b, 1024*1024)
|
||||
}
|
||||
|
||||
func BenchmarkRead4MiB(b *testing.B) {
|
||||
benchmarkRead(b, 4*1024*1024)
|
||||
}
|
||||
|
||||
func benchmarkWrite(b *testing.B, bufsize int) {
|
||||
size := 10*1024*1024 + 123 // ~10MiB
|
||||
|
||||
// open sftp client
|
||||
sftp, cmd := testClient(b, false)
|
||||
defer cmd.Wait()
|
||||
defer sftp.Close()
|
||||
|
||||
data := make([]byte, size)
|
||||
|
||||
b.ResetTimer()
|
||||
b.SetBytes(int64(size))
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
offset := 0
|
||||
|
||||
f, err := ioutil.TempFile("", "sftptest")
|
||||
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()
|
||||
|
||||
for offset < size {
|
||||
n, err := f2.Write(data[offset:min(len(data), offset+bufsize)])
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
|
||||
if offset+n < size && n != bufsize {
|
||||
b.Fatalf("wrote too few bytes! want: %d, got: %d", size, n)
|
||||
}
|
||||
|
||||
offset += n
|
||||
}
|
||||
|
||||
f2.Close()
|
||||
|
||||
fi, err := os.Stat(f.Name())
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
|
||||
if fi.Size() != int64(size) {
|
||||
b.Fatalf("wrong file size: want %d, got %d", size, fi.Size())
|
||||
}
|
||||
|
||||
os.Remove(f.Name())
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkWrite1k(b *testing.B) {
|
||||
benchmarkWrite(b, 1*1024)
|
||||
}
|
||||
|
||||
func BenchmarkWrite16k(b *testing.B) {
|
||||
benchmarkWrite(b, 16*1024)
|
||||
}
|
||||
|
||||
func BenchmarkWrite32k(b *testing.B) {
|
||||
benchmarkWrite(b, 32*1024)
|
||||
}
|
||||
|
||||
func BenchmarkWrite128k(b *testing.B) {
|
||||
benchmarkWrite(b, 128*1024)
|
||||
}
|
||||
|
||||
func BenchmarkWrite512k(b *testing.B) {
|
||||
benchmarkWrite(b, 512*1024)
|
||||
}
|
||||
|
||||
func BenchmarkWrite1MiB(b *testing.B) {
|
||||
benchmarkWrite(b, 1024*1024)
|
||||
}
|
||||
|
||||
func BenchmarkWrite4MiB(b *testing.B) {
|
||||
benchmarkWrite(b, 4*1024*1024)
|
||||
}
|
||||
75
Godeps/_workspace/src/github.com/pkg/sftp/client_test.go
generated
vendored
Normal file
75
Godeps/_workspace/src/github.com/pkg/sftp/client_test.go
generated
vendored
Normal file
@@ -0,0 +1,75 @@
|
||||
package sftp
|
||||
|
||||
import (
|
||||
"io"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/kr/fs"
|
||||
)
|
||||
|
||||
// assert that *Client implements fs.FileSystem
|
||||
var _ fs.FileSystem = new(Client)
|
||||
|
||||
// assert that *File implements io.ReadWriteCloser
|
||||
var _ io.ReadWriteCloser = new(File)
|
||||
|
||||
var ok = &StatusError{Code: ssh_FX_OK}
|
||||
var eof = &StatusError{Code: ssh_FX_EOF}
|
||||
var fail = &StatusError{Code: ssh_FX_FAILURE}
|
||||
|
||||
var eofOrErrTests = []struct {
|
||||
err, want error
|
||||
}{
|
||||
{nil, nil},
|
||||
{eof, io.EOF},
|
||||
{ok, ok},
|
||||
{io.EOF, io.EOF},
|
||||
}
|
||||
|
||||
func TestEofOrErr(t *testing.T) {
|
||||
for _, tt := range eofOrErrTests {
|
||||
got := eofOrErr(tt.err)
|
||||
if got != tt.want {
|
||||
t.Errorf("eofOrErr(%#v): want: %#v, got: %#v", tt.err, tt.want, got)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var okOrErrTests = []struct {
|
||||
err, want error
|
||||
}{
|
||||
{nil, nil},
|
||||
{eof, eof},
|
||||
{ok, nil},
|
||||
{io.EOF, io.EOF},
|
||||
}
|
||||
|
||||
func TestOkOrErr(t *testing.T) {
|
||||
for _, tt := range okOrErrTests {
|
||||
got := okOrErr(tt.err)
|
||||
if got != tt.want {
|
||||
t.Errorf("okOrErr(%#v): want: %#v, got: %#v", tt.err, tt.want, got)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var flagsTests = []struct {
|
||||
flags int
|
||||
want uint32
|
||||
}{
|
||||
{os.O_RDONLY, ssh_FXF_READ},
|
||||
{os.O_WRONLY, ssh_FXF_WRITE},
|
||||
{os.O_RDWR, ssh_FXF_READ | ssh_FXF_WRITE},
|
||||
{os.O_RDWR | os.O_CREATE | os.O_TRUNC, ssh_FXF_READ | ssh_FXF_WRITE | ssh_FXF_CREAT | ssh_FXF_TRUNC},
|
||||
{os.O_WRONLY | os.O_APPEND, ssh_FXF_WRITE | ssh_FXF_APPEND},
|
||||
}
|
||||
|
||||
func TestFlags(t *testing.T) {
|
||||
for i, tt := range flagsTests {
|
||||
got := flags(tt.flags)
|
||||
if got != tt.want {
|
||||
t.Errorf("test %v: flags(%x): want: %x, got: %x", i, tt.flags, tt.want, got)
|
||||
}
|
||||
}
|
||||
}
|
||||
9
Godeps/_workspace/src/github.com/pkg/sftp/debug.go
generated
vendored
Normal file
9
Godeps/_workspace/src/github.com/pkg/sftp/debug.go
generated
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
// +build debug
|
||||
|
||||
package sftp
|
||||
|
||||
import "log"
|
||||
|
||||
func debug(fmt string, args ...interface{}) {
|
||||
log.Printf(fmt, args...)
|
||||
}
|
||||
91
Godeps/_workspace/src/github.com/pkg/sftp/example_test.go
generated
vendored
Normal file
91
Godeps/_workspace/src/github.com/pkg/sftp/example_test.go
generated
vendored
Normal file
@@ -0,0 +1,91 @@
|
||||
package sftp_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"os/exec"
|
||||
|
||||
"golang.org/x/crypto/ssh"
|
||||
|
||||
"github.com/pkg/sftp"
|
||||
)
|
||||
|
||||
func Example(conn *ssh.Client) {
|
||||
// open an SFTP session over an existing ssh connection.
|
||||
sftp, err := sftp.NewClient(conn)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
defer sftp.Close()
|
||||
|
||||
// walk a directory
|
||||
w := sftp.Walk("/home/user")
|
||||
for w.Step() {
|
||||
if w.Err() != nil {
|
||||
continue
|
||||
}
|
||||
log.Println(w.Path())
|
||||
}
|
||||
|
||||
// leave your mark
|
||||
f, err := sftp.Create("hello.txt")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
if _, err := f.Write([]byte("Hello world!")); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
// check it's there
|
||||
fi, err := sftp.Lstat("hello.txt")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
log.Println(fi)
|
||||
}
|
||||
|
||||
func ExampleNewClientPipe() {
|
||||
// Connect to a remote host and request the sftp subsystem via the 'ssh'
|
||||
// command. This assumes that passwordless login is correctly configured.
|
||||
cmd := exec.Command("ssh", "example.com", "-s", "sftp")
|
||||
|
||||
// send errors from ssh to stderr
|
||||
cmd.Stderr = os.Stderr
|
||||
|
||||
// get stdin and stdout
|
||||
wr, err := cmd.StdinPipe()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
rd, err := cmd.StdoutPipe()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
// start the process
|
||||
if err := cmd.Start(); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
defer cmd.Wait()
|
||||
|
||||
// open the SFTP session
|
||||
client, err := sftp.NewClientPipe(rd, wr)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
// read a directory
|
||||
list, err := client.ReadDir("/")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
// print contents
|
||||
for _, item := range list {
|
||||
fmt.Println(item.Name())
|
||||
}
|
||||
|
||||
// close the connection
|
||||
client.Close()
|
||||
}
|
||||
76
Godeps/_workspace/src/github.com/pkg/sftp/examples/buffered-read-benchmark/main.go
generated
vendored
Normal file
76
Godeps/_workspace/src/github.com/pkg/sftp/examples/buffered-read-benchmark/main.go
generated
vendored
Normal file
@@ -0,0 +1,76 @@
|
||||
// buffered-read-benchmark benchmarks the peformance of reading
|
||||
// from /dev/zero on the server to a []byte on the client via io.Copy.
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"net"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"golang.org/x/crypto/ssh"
|
||||
"golang.org/x/crypto/ssh/agent"
|
||||
|
||||
"github.com/pkg/sftp"
|
||||
)
|
||||
|
||||
var (
|
||||
USER = flag.String("user", os.Getenv("USER"), "ssh username")
|
||||
HOST = flag.String("host", "localhost", "ssh server hostname")
|
||||
PORT = flag.Int("port", 22, "ssh server port")
|
||||
PASS = flag.String("pass", os.Getenv("SOCKSIE_SSH_PASSWORD"), "ssh password")
|
||||
)
|
||||
|
||||
func init() {
|
||||
flag.Parse()
|
||||
}
|
||||
|
||||
func main() {
|
||||
var auths []ssh.AuthMethod
|
||||
if aconn, err := net.Dial("unix", os.Getenv("SSH_AUTH_SOCK")); err == nil {
|
||||
auths = append(auths, ssh.PublicKeysCallback(agent.NewClient(aconn).Signers))
|
||||
|
||||
}
|
||||
if *PASS != "" {
|
||||
auths = append(auths, ssh.Password(*PASS))
|
||||
}
|
||||
|
||||
config := ssh.ClientConfig{
|
||||
User: *USER,
|
||||
Auth: auths,
|
||||
}
|
||||
addr := fmt.Sprintf("%s:%d", *HOST, *PORT)
|
||||
conn, err := ssh.Dial("tcp", addr, &config)
|
||||
if err != nil {
|
||||
log.Fatalf("unable to connect to [%s]: %v", addr, err)
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
c, err := sftp.NewClient(conn)
|
||||
if err != nil {
|
||||
log.Fatalf("unable to start sftp subsytem: %v", err)
|
||||
}
|
||||
defer c.Close()
|
||||
|
||||
r, err := c.Open("/dev/zero")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
defer r.Close()
|
||||
|
||||
const size = 1e9
|
||||
|
||||
log.Printf("reading %v bytes", size)
|
||||
t1 := time.Now()
|
||||
n, err := io.ReadFull(r, make([]byte, size))
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
if n != size {
|
||||
log.Fatalf("copy: expected %v bytes, got %d", size, n)
|
||||
}
|
||||
log.Printf("read %v bytes in %s", size, time.Since(t1))
|
||||
}
|
||||
82
Godeps/_workspace/src/github.com/pkg/sftp/examples/buffered-write-benchmark/main.go
generated
vendored
Normal file
82
Godeps/_workspace/src/github.com/pkg/sftp/examples/buffered-write-benchmark/main.go
generated
vendored
Normal file
@@ -0,0 +1,82 @@
|
||||
// buffered-write-benchmark benchmarks the peformance of writing
|
||||
// a single large []byte on the client to /dev/null on the server via io.Copy.
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"log"
|
||||
"net"
|
||||
"os"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"golang.org/x/crypto/ssh"
|
||||
"golang.org/x/crypto/ssh/agent"
|
||||
|
||||
"github.com/pkg/sftp"
|
||||
)
|
||||
|
||||
var (
|
||||
USER = flag.String("user", os.Getenv("USER"), "ssh username")
|
||||
HOST = flag.String("host", "localhost", "ssh server hostname")
|
||||
PORT = flag.Int("port", 22, "ssh server port")
|
||||
PASS = flag.String("pass", os.Getenv("SOCKSIE_SSH_PASSWORD"), "ssh password")
|
||||
)
|
||||
|
||||
func init() {
|
||||
flag.Parse()
|
||||
}
|
||||
|
||||
func main() {
|
||||
var auths []ssh.AuthMethod
|
||||
if aconn, err := net.Dial("unix", os.Getenv("SSH_AUTH_SOCK")); err == nil {
|
||||
auths = append(auths, ssh.PublicKeysCallback(agent.NewClient(aconn).Signers))
|
||||
|
||||
}
|
||||
if *PASS != "" {
|
||||
auths = append(auths, ssh.Password(*PASS))
|
||||
}
|
||||
|
||||
config := ssh.ClientConfig{
|
||||
User: *USER,
|
||||
Auth: auths,
|
||||
}
|
||||
addr := fmt.Sprintf("%s:%d", *HOST, *PORT)
|
||||
conn, err := ssh.Dial("tcp", addr, &config)
|
||||
if err != nil {
|
||||
log.Fatalf("unable to connect to [%s]: %v", addr, err)
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
c, err := sftp.NewClient(conn)
|
||||
if err != nil {
|
||||
log.Fatalf("unable to start sftp subsytem: %v", err)
|
||||
}
|
||||
defer c.Close()
|
||||
|
||||
w, err := c.OpenFile("/dev/null", syscall.O_WRONLY)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
defer w.Close()
|
||||
|
||||
f, err := os.Open("/dev/zero")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
const size = 1e9
|
||||
|
||||
log.Printf("writing %v bytes", size)
|
||||
t1 := time.Now()
|
||||
n, err := w.Write(make([]byte, size))
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
if n != size {
|
||||
log.Fatalf("copy: expected %v bytes, got %d", size, n)
|
||||
}
|
||||
log.Printf("wrote %v bytes in %s", size, time.Since(t1))
|
||||
}
|
||||
147
Godeps/_workspace/src/github.com/pkg/sftp/examples/gsftp/main.go
generated
vendored
Normal file
147
Godeps/_workspace/src/github.com/pkg/sftp/examples/gsftp/main.go
generated
vendored
Normal file
@@ -0,0 +1,147 @@
|
||||
// gsftp implements a simple sftp client.
|
||||
//
|
||||
// gsftp understands the following commands:
|
||||
//
|
||||
// List a directory (and its subdirectories)
|
||||
// gsftp ls DIR
|
||||
//
|
||||
// Fetch a remote file
|
||||
// gsftp fetch FILE
|
||||
//
|
||||
// Put the contents of stdin to a remote file
|
||||
// cat LOCALFILE | gsftp put REMOTEFILE
|
||||
//
|
||||
// Print the details of a remote file
|
||||
// gsftp stat FILE
|
||||
//
|
||||
// Remove a remote file
|
||||
// gsftp rm FILE
|
||||
//
|
||||
// Rename a file
|
||||
// gsftp mv OLD NEW
|
||||
//
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"net"
|
||||
"os"
|
||||
|
||||
"golang.org/x/crypto/ssh"
|
||||
"golang.org/x/crypto/ssh/agent"
|
||||
|
||||
"github.com/pkg/sftp"
|
||||
)
|
||||
|
||||
var (
|
||||
USER = flag.String("user", os.Getenv("USER"), "ssh username")
|
||||
HOST = flag.String("host", "localhost", "ssh server hostname")
|
||||
PORT = flag.Int("port", 22, "ssh server port")
|
||||
PASS = flag.String("pass", os.Getenv("SOCKSIE_SSH_PASSWORD"), "ssh password")
|
||||
)
|
||||
|
||||
func init() {
|
||||
flag.Parse()
|
||||
if len(flag.Args()) < 1 {
|
||||
log.Fatal("subcommand required")
|
||||
}
|
||||
}
|
||||
|
||||
func main() {
|
||||
var auths []ssh.AuthMethod
|
||||
if aconn, err := net.Dial("unix", os.Getenv("SSH_AUTH_SOCK")); err == nil {
|
||||
auths = append(auths, ssh.PublicKeysCallback(agent.NewClient(aconn).Signers))
|
||||
|
||||
}
|
||||
if *PASS != "" {
|
||||
auths = append(auths, ssh.Password(*PASS))
|
||||
}
|
||||
|
||||
config := ssh.ClientConfig{
|
||||
User: *USER,
|
||||
Auth: auths,
|
||||
}
|
||||
addr := fmt.Sprintf("%s:%d", *HOST, *PORT)
|
||||
conn, err := ssh.Dial("tcp", addr, &config)
|
||||
if err != nil {
|
||||
log.Fatalf("unable to connect to [%s]: %v", addr, err)
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
client, err := sftp.NewClient(conn)
|
||||
if err != nil {
|
||||
log.Fatalf("unable to start sftp subsytem: %v", err)
|
||||
}
|
||||
defer client.Close()
|
||||
switch cmd := flag.Args()[0]; cmd {
|
||||
case "ls":
|
||||
if len(flag.Args()) < 2 {
|
||||
log.Fatalf("%s %s: remote path required", cmd, os.Args[0])
|
||||
}
|
||||
walker := client.Walk(flag.Args()[1])
|
||||
for walker.Step() {
|
||||
if err := walker.Err(); err != nil {
|
||||
log.Println(err)
|
||||
continue
|
||||
}
|
||||
fmt.Println(walker.Path())
|
||||
}
|
||||
case "fetch":
|
||||
if len(flag.Args()) < 2 {
|
||||
log.Fatalf("%s %s: remote path required", cmd, os.Args[0])
|
||||
}
|
||||
f, err := client.Open(flag.Args()[1])
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
defer f.Close()
|
||||
if _, err := io.Copy(os.Stdout, f); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
case "put":
|
||||
if len(flag.Args()) < 2 {
|
||||
log.Fatalf("%s %s: remote path required", cmd, os.Args[0])
|
||||
}
|
||||
f, err := client.Create(flag.Args()[1])
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
defer f.Close()
|
||||
if _, err := io.Copy(f, os.Stdin); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
case "stat":
|
||||
if len(flag.Args()) < 2 {
|
||||
log.Fatalf("%s %s: remote path required", cmd, os.Args[0])
|
||||
}
|
||||
f, err := client.Open(flag.Args()[1])
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
defer f.Close()
|
||||
fi, err := f.Stat()
|
||||
if err != nil {
|
||||
log.Fatalf("unable to stat file: %v", err)
|
||||
}
|
||||
fmt.Printf("%s %d %v\n", fi.Name(), fi.Size(), fi.Mode())
|
||||
case "rm":
|
||||
if len(flag.Args()) < 2 {
|
||||
log.Fatalf("%s %s: remote path required", cmd, os.Args[0])
|
||||
}
|
||||
if err := client.Remove(flag.Args()[1]); err != nil {
|
||||
log.Fatalf("unable to remove file: %v", err)
|
||||
}
|
||||
case "mv":
|
||||
if len(flag.Args()) < 3 {
|
||||
log.Fatalf("%s %s: old and new name required", cmd, os.Args[0])
|
||||
}
|
||||
if err := client.Rename(flag.Args()[1], flag.Args()[2]); err != nil {
|
||||
log.Fatalf("unable to rename file: %v", err)
|
||||
}
|
||||
default:
|
||||
log.Fatalf("unknown subcommand: %v", cmd)
|
||||
}
|
||||
}
|
||||
83
Godeps/_workspace/src/github.com/pkg/sftp/examples/streaming-read-benchmark/main.go
generated
vendored
Normal file
83
Godeps/_workspace/src/github.com/pkg/sftp/examples/streaming-read-benchmark/main.go
generated
vendored
Normal file
@@ -0,0 +1,83 @@
|
||||
// streaming-read-benchmark benchmarks the peformance of reading
|
||||
// from /dev/zero on the server to /dev/null on the client via io.Copy.
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"net"
|
||||
"os"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"golang.org/x/crypto/ssh"
|
||||
"golang.org/x/crypto/ssh/agent"
|
||||
|
||||
"github.com/pkg/sftp"
|
||||
)
|
||||
|
||||
var (
|
||||
USER = flag.String("user", os.Getenv("USER"), "ssh username")
|
||||
HOST = flag.String("host", "localhost", "ssh server hostname")
|
||||
PORT = flag.Int("port", 22, "ssh server port")
|
||||
PASS = flag.String("pass", os.Getenv("SOCKSIE_SSH_PASSWORD"), "ssh password")
|
||||
)
|
||||
|
||||
func init() {
|
||||
flag.Parse()
|
||||
}
|
||||
|
||||
func main() {
|
||||
var auths []ssh.AuthMethod
|
||||
if aconn, err := net.Dial("unix", os.Getenv("SSH_AUTH_SOCK")); err == nil {
|
||||
auths = append(auths, ssh.PublicKeysCallback(agent.NewClient(aconn).Signers))
|
||||
|
||||
}
|
||||
if *PASS != "" {
|
||||
auths = append(auths, ssh.Password(*PASS))
|
||||
}
|
||||
|
||||
config := ssh.ClientConfig{
|
||||
User: *USER,
|
||||
Auth: auths,
|
||||
}
|
||||
addr := fmt.Sprintf("%s:%d", *HOST, *PORT)
|
||||
conn, err := ssh.Dial("tcp", addr, &config)
|
||||
if err != nil {
|
||||
log.Fatalf("unable to connect to [%s]: %v", addr, err)
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
c, err := sftp.NewClient(conn)
|
||||
if err != nil {
|
||||
log.Fatalf("unable to start sftp subsytem: %v", err)
|
||||
}
|
||||
defer c.Close()
|
||||
|
||||
r, err := c.Open("/dev/zero")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
defer r.Close()
|
||||
|
||||
w, err := os.OpenFile("/dev/null", syscall.O_WRONLY, 0600)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
defer w.Close()
|
||||
|
||||
const size int64 = 1e9
|
||||
|
||||
log.Printf("reading %v bytes", size)
|
||||
t1 := time.Now()
|
||||
n, err := io.Copy(w, io.LimitReader(r, size))
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
if n != size {
|
||||
log.Fatalf("copy: expected %v bytes, got %d", size, n)
|
||||
}
|
||||
log.Printf("read %v bytes in %s", size, time.Since(t1))
|
||||
}
|
||||
83
Godeps/_workspace/src/github.com/pkg/sftp/examples/streaming-write-benchmark/main.go
generated
vendored
Normal file
83
Godeps/_workspace/src/github.com/pkg/sftp/examples/streaming-write-benchmark/main.go
generated
vendored
Normal file
@@ -0,0 +1,83 @@
|
||||
// streaming-write-benchmark benchmarks the peformance of writing
|
||||
// from /dev/zero on the client to /dev/null on the server via io.Copy.
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"net"
|
||||
"os"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"golang.org/x/crypto/ssh"
|
||||
"golang.org/x/crypto/ssh/agent"
|
||||
|
||||
"github.com/pkg/sftp"
|
||||
)
|
||||
|
||||
var (
|
||||
USER = flag.String("user", os.Getenv("USER"), "ssh username")
|
||||
HOST = flag.String("host", "localhost", "ssh server hostname")
|
||||
PORT = flag.Int("port", 22, "ssh server port")
|
||||
PASS = flag.String("pass", os.Getenv("SOCKSIE_SSH_PASSWORD"), "ssh password")
|
||||
)
|
||||
|
||||
func init() {
|
||||
flag.Parse()
|
||||
}
|
||||
|
||||
func main() {
|
||||
var auths []ssh.AuthMethod
|
||||
if aconn, err := net.Dial("unix", os.Getenv("SSH_AUTH_SOCK")); err == nil {
|
||||
auths = append(auths, ssh.PublicKeysCallback(agent.NewClient(aconn).Signers))
|
||||
|
||||
}
|
||||
if *PASS != "" {
|
||||
auths = append(auths, ssh.Password(*PASS))
|
||||
}
|
||||
|
||||
config := ssh.ClientConfig{
|
||||
User: *USER,
|
||||
Auth: auths,
|
||||
}
|
||||
addr := fmt.Sprintf("%s:%d", *HOST, *PORT)
|
||||
conn, err := ssh.Dial("tcp", addr, &config)
|
||||
if err != nil {
|
||||
log.Fatalf("unable to connect to [%s]: %v", addr, err)
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
c, err := sftp.NewClient(conn)
|
||||
if err != nil {
|
||||
log.Fatalf("unable to start sftp subsytem: %v", err)
|
||||
}
|
||||
defer c.Close()
|
||||
|
||||
w, err := c.OpenFile("/dev/null", syscall.O_WRONLY)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
defer w.Close()
|
||||
|
||||
f, err := os.Open("/dev/zero")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
const size int64 = 1e9
|
||||
|
||||
log.Printf("writing %v bytes", size)
|
||||
t1 := time.Now()
|
||||
n, err := io.Copy(w, io.LimitReader(f, size))
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
if n != size {
|
||||
log.Fatalf("copy: expected %v bytes, got %d", size, n)
|
||||
}
|
||||
log.Printf("wrote %v bytes in %s", size, time.Since(t1))
|
||||
}
|
||||
331
Godeps/_workspace/src/github.com/pkg/sftp/packet.go
generated
vendored
Normal file
331
Godeps/_workspace/src/github.com/pkg/sftp/packet.go
generated
vendored
Normal file
@@ -0,0 +1,331 @@
|
||||
package sftp
|
||||
|
||||
import (
|
||||
"encoding"
|
||||
"fmt"
|
||||
"io"
|
||||
"reflect"
|
||||
)
|
||||
|
||||
func marshalUint32(b []byte, v uint32) []byte {
|
||||
return append(b, byte(v>>24), byte(v>>16), byte(v>>8), byte(v))
|
||||
}
|
||||
|
||||
func marshalUint64(b []byte, v uint64) []byte {
|
||||
return marshalUint32(marshalUint32(b, uint32(v>>32)), uint32(v))
|
||||
}
|
||||
|
||||
func marshalString(b []byte, v string) []byte {
|
||||
return append(marshalUint32(b, uint32(len(v))), v...)
|
||||
}
|
||||
|
||||
func marshal(b []byte, v interface{}) []byte {
|
||||
switch v := v.(type) {
|
||||
case uint8:
|
||||
return append(b, v)
|
||||
case uint32:
|
||||
return marshalUint32(b, v)
|
||||
case uint64:
|
||||
return marshalUint64(b, v)
|
||||
case string:
|
||||
return marshalString(b, v)
|
||||
default:
|
||||
switch d := reflect.ValueOf(v); d.Kind() {
|
||||
case reflect.Struct:
|
||||
for i, n := 0, d.NumField(); i < n; i++ {
|
||||
b = append(marshal(b, d.Field(i).Interface()))
|
||||
}
|
||||
return b
|
||||
case reflect.Slice:
|
||||
for i, n := 0, d.Len(); i < n; i++ {
|
||||
b = append(marshal(b, d.Index(i).Interface()))
|
||||
}
|
||||
return b
|
||||
default:
|
||||
panic(fmt.Sprintf("marshal(%#v): cannot handle type %T", v, v))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func unmarshalUint32(b []byte) (uint32, []byte) {
|
||||
v := uint32(b[3]) | uint32(b[2])<<8 | uint32(b[1])<<16 | uint32(b[0])<<24
|
||||
return v, b[4:]
|
||||
}
|
||||
|
||||
func unmarshalUint64(b []byte) (uint64, []byte) {
|
||||
h, b := unmarshalUint32(b)
|
||||
l, b := unmarshalUint32(b)
|
||||
return uint64(h)<<32 | uint64(l), b
|
||||
}
|
||||
|
||||
func unmarshalString(b []byte) (string, []byte) {
|
||||
n, b := unmarshalUint32(b)
|
||||
return string(b[:n]), b[n:]
|
||||
}
|
||||
|
||||
// sendPacket marshals p according to RFC 4234.
|
||||
|
||||
func sendPacket(w io.Writer, m encoding.BinaryMarshaler) error {
|
||||
bb, err := m.MarshalBinary()
|
||||
if err != nil {
|
||||
return fmt.Errorf("marshal2(%#v): binary marshaller failed", err)
|
||||
}
|
||||
l := uint32(len(bb))
|
||||
hdr := []byte{byte(l >> 24), byte(l >> 16), byte(l >> 8), byte(l)}
|
||||
debug("send packet %T, len: %v", m, l)
|
||||
_, err = w.Write(hdr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = w.Write(bb)
|
||||
return err
|
||||
}
|
||||
|
||||
func recvPacket(r io.Reader) (uint8, []byte, error) {
|
||||
var b = []byte{0, 0, 0, 0}
|
||||
if _, err := io.ReadFull(r, b); err != nil {
|
||||
return 0, nil, err
|
||||
}
|
||||
l, _ := unmarshalUint32(b)
|
||||
b = make([]byte, l)
|
||||
if _, err := io.ReadFull(r, b); err != nil {
|
||||
return 0, nil, err
|
||||
}
|
||||
return b[0], b[1:], nil
|
||||
}
|
||||
|
||||
// Here starts the definition of packets along with their MarshalBinary
|
||||
// implementations.
|
||||
// Manually writing the marshalling logic wins us a lot of time and
|
||||
// allocation.
|
||||
|
||||
type sshFxInitPacket struct {
|
||||
Version uint32
|
||||
Extensions []struct {
|
||||
Name, Data string
|
||||
}
|
||||
}
|
||||
|
||||
func (p sshFxInitPacket) MarshalBinary() ([]byte, error) {
|
||||
l := 1 + 4 // byte + uint32
|
||||
for _, e := range p.Extensions {
|
||||
l += 4 + len(e.Name) + 4 + len(e.Data)
|
||||
}
|
||||
|
||||
b := make([]byte, 0, l)
|
||||
b = append(b, ssh_FXP_INIT)
|
||||
b = marshalUint32(b, p.Version)
|
||||
for _, e := range p.Extensions {
|
||||
b = marshalString(b, e.Name)
|
||||
b = marshalString(b, e.Data)
|
||||
}
|
||||
return b, nil
|
||||
}
|
||||
|
||||
func marshalIdString(packetType byte, id uint32, str string) ([]byte, error) {
|
||||
l := 1 + 4 + // type(byte) + uint32
|
||||
4 + len(str)
|
||||
|
||||
b := make([]byte, 0, l)
|
||||
b = append(b, packetType)
|
||||
b = marshalUint32(b, id)
|
||||
b = marshalString(b, str)
|
||||
return b, nil
|
||||
}
|
||||
|
||||
type sshFxpReaddirPacket struct {
|
||||
Id uint32
|
||||
Handle string
|
||||
}
|
||||
|
||||
func (p sshFxpReaddirPacket) MarshalBinary() ([]byte, error) {
|
||||
return marshalIdString(ssh_FXP_READDIR, p.Id, p.Handle)
|
||||
}
|
||||
|
||||
type sshFxpOpendirPacket struct {
|
||||
Id uint32
|
||||
Path string
|
||||
}
|
||||
|
||||
func (p sshFxpOpendirPacket) MarshalBinary() ([]byte, error) {
|
||||
return marshalIdString(ssh_FXP_OPENDIR, p.Id, p.Path)
|
||||
}
|
||||
|
||||
type sshFxpLstatPacket struct {
|
||||
Id uint32
|
||||
Path string
|
||||
}
|
||||
|
||||
func (p sshFxpLstatPacket) MarshalBinary() ([]byte, error) {
|
||||
return marshalIdString(ssh_FXP_LSTAT, p.Id, p.Path)
|
||||
}
|
||||
|
||||
type sshFxpFstatPacket struct {
|
||||
Id uint32
|
||||
Handle string
|
||||
}
|
||||
|
||||
func (p sshFxpFstatPacket) MarshalBinary() ([]byte, error) {
|
||||
return marshalIdString(ssh_FXP_FSTAT, p.Id, p.Handle)
|
||||
}
|
||||
|
||||
type sshFxpClosePacket struct {
|
||||
Id uint32
|
||||
Handle string
|
||||
}
|
||||
|
||||
func (p sshFxpClosePacket) MarshalBinary() ([]byte, error) {
|
||||
return marshalIdString(ssh_FXP_CLOSE, p.Id, p.Handle)
|
||||
}
|
||||
|
||||
type sshFxpRemovePacket struct {
|
||||
Id uint32
|
||||
Filename string
|
||||
}
|
||||
|
||||
func (p sshFxpRemovePacket) MarshalBinary() ([]byte, error) {
|
||||
return marshalIdString(ssh_FXP_REMOVE, p.Id, p.Filename)
|
||||
}
|
||||
|
||||
type sshFxpRmdirPacket struct {
|
||||
Id uint32
|
||||
Path string
|
||||
}
|
||||
|
||||
func (p sshFxpRmdirPacket) MarshalBinary() ([]byte, error) {
|
||||
return marshalIdString(ssh_FXP_RMDIR, p.Id, p.Path)
|
||||
}
|
||||
|
||||
type sshFxpReadlinkPacket struct {
|
||||
Id uint32
|
||||
Path string
|
||||
}
|
||||
|
||||
func (p sshFxpReadlinkPacket) MarshalBinary() ([]byte, error) {
|
||||
return marshalIdString(ssh_FXP_READLINK, p.Id, p.Path)
|
||||
}
|
||||
|
||||
type sshFxpOpenPacket struct {
|
||||
Id uint32
|
||||
Path string
|
||||
Pflags uint32
|
||||
Flags uint32 // ignored
|
||||
}
|
||||
|
||||
func (p sshFxpOpenPacket) MarshalBinary() ([]byte, error) {
|
||||
l := 1 + 4 +
|
||||
4 + len(p.Path) +
|
||||
4 + 4
|
||||
|
||||
b := make([]byte, 0, l)
|
||||
b = append(b, ssh_FXP_OPEN)
|
||||
b = marshalUint32(b, p.Id)
|
||||
b = marshalString(b, p.Path)
|
||||
b = marshalUint32(b, p.Pflags)
|
||||
b = marshalUint32(b, p.Flags)
|
||||
return b, nil
|
||||
}
|
||||
|
||||
type sshFxpReadPacket struct {
|
||||
Id uint32
|
||||
Handle string
|
||||
Offset uint64
|
||||
Len uint32
|
||||
}
|
||||
|
||||
func (p sshFxpReadPacket) MarshalBinary() ([]byte, error) {
|
||||
l := 1 + 4 + // type(byte) + uint32
|
||||
4 + len(p.Handle) +
|
||||
8 + 4 // uint64 + uint32
|
||||
|
||||
b := make([]byte, 0, l)
|
||||
b = append(b, ssh_FXP_READ)
|
||||
b = marshalUint32(b, p.Id)
|
||||
b = marshalString(b, p.Handle)
|
||||
b = marshalUint64(b, p.Offset)
|
||||
b = marshalUint32(b, p.Len)
|
||||
return b, nil
|
||||
}
|
||||
|
||||
type sshFxpRenamePacket struct {
|
||||
Id uint32
|
||||
Oldpath string
|
||||
Newpath string
|
||||
}
|
||||
|
||||
func (p sshFxpRenamePacket) MarshalBinary() ([]byte, error) {
|
||||
l := 1 + 4 + // type(byte) + uint32
|
||||
4 + len(p.Oldpath) +
|
||||
4 + len(p.Newpath)
|
||||
|
||||
b := make([]byte, 0, l)
|
||||
b = append(b, ssh_FXP_RENAME)
|
||||
b = marshalUint32(b, p.Id)
|
||||
b = marshalString(b, p.Oldpath)
|
||||
b = marshalString(b, p.Newpath)
|
||||
return b, nil
|
||||
}
|
||||
|
||||
type sshFxpWritePacket struct {
|
||||
Id uint32
|
||||
Handle string
|
||||
Offset uint64
|
||||
Length uint32
|
||||
Data []byte
|
||||
}
|
||||
|
||||
func (s sshFxpWritePacket) MarshalBinary() ([]byte, error) {
|
||||
l := 1 + 4 + // type(byte) + uint32
|
||||
4 + len(s.Handle) +
|
||||
8 + 4 + // uint64 + uint32
|
||||
len(s.Data)
|
||||
|
||||
b := make([]byte, 0, l)
|
||||
b = append(b, ssh_FXP_WRITE)
|
||||
b = marshalUint32(b, s.Id)
|
||||
b = marshalString(b, s.Handle)
|
||||
b = marshalUint64(b, s.Offset)
|
||||
b = marshalUint32(b, s.Length)
|
||||
b = append(b, s.Data...)
|
||||
return b, nil
|
||||
}
|
||||
|
||||
type sshFxpMkdirPacket struct {
|
||||
Id uint32
|
||||
Path string
|
||||
Flags uint32 // ignored
|
||||
}
|
||||
|
||||
func (p sshFxpMkdirPacket) MarshalBinary() ([]byte, error) {
|
||||
l := 1 + 4 + // type(byte) + uint32
|
||||
4 + len(p.Path) +
|
||||
4 // uint32
|
||||
|
||||
b := make([]byte, 0, l)
|
||||
b = append(b, ssh_FXP_MKDIR)
|
||||
b = marshalUint32(b, p.Id)
|
||||
b = marshalString(b, p.Path)
|
||||
b = marshalUint32(b, p.Flags)
|
||||
return b, nil
|
||||
}
|
||||
|
||||
type sshFxpSetstatPacket struct {
|
||||
Id uint32
|
||||
Path string
|
||||
Flags uint32
|
||||
Attrs interface{}
|
||||
}
|
||||
|
||||
func (p sshFxpSetstatPacket) MarshalBinary() ([]byte, error) {
|
||||
l := 1 + 4 + // type(byte) + uint32
|
||||
4 + len(p.Path) +
|
||||
4 // uint32 + uint64
|
||||
|
||||
b := make([]byte, 0, l)
|
||||
b = append(b, ssh_FXP_SETSTAT)
|
||||
b = marshalUint32(b, p.Id)
|
||||
b = marshalString(b, p.Path)
|
||||
b = marshalUint32(b, p.Flags)
|
||||
b = marshal(b, p.Attrs)
|
||||
return b, nil
|
||||
}
|
||||
261
Godeps/_workspace/src/github.com/pkg/sftp/packet_test.go
generated
vendored
Normal file
261
Godeps/_workspace/src/github.com/pkg/sftp/packet_test.go
generated
vendored
Normal file
@@ -0,0 +1,261 @@
|
||||
package sftp
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding"
|
||||
"os"
|
||||
"testing"
|
||||
)
|
||||
|
||||
var marshalUint32Tests = []struct {
|
||||
v uint32
|
||||
want []byte
|
||||
}{
|
||||
{1, []byte{0, 0, 0, 1}},
|
||||
{256, []byte{0, 0, 1, 0}},
|
||||
{^uint32(0), []byte{255, 255, 255, 255}},
|
||||
}
|
||||
|
||||
func TestMarshalUint32(t *testing.T) {
|
||||
for _, tt := range marshalUint32Tests {
|
||||
got := marshalUint32(nil, tt.v)
|
||||
if !bytes.Equal(tt.want, got) {
|
||||
t.Errorf("marshalUint32(%d): want %v, got %v", tt.v, tt.want, got)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var marshalUint64Tests = []struct {
|
||||
v uint64
|
||||
want []byte
|
||||
}{
|
||||
{1, []byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1}},
|
||||
{256, []byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x0}},
|
||||
{^uint64(0), []byte{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}},
|
||||
{1 << 32, []byte{0x0, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x0}},
|
||||
}
|
||||
|
||||
func TestMarshalUint64(t *testing.T) {
|
||||
for _, tt := range marshalUint64Tests {
|
||||
got := marshalUint64(nil, tt.v)
|
||||
if !bytes.Equal(tt.want, got) {
|
||||
t.Errorf("marshalUint64(%d): want %#v, got %#v", tt.v, tt.want, got)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var marshalStringTests = []struct {
|
||||
v string
|
||||
want []byte
|
||||
}{
|
||||
{"", []byte{0, 0, 0, 0}},
|
||||
{"/foo", []byte{0x0, 0x0, 0x0, 0x4, 0x2f, 0x66, 0x6f, 0x6f}},
|
||||
}
|
||||
|
||||
func TestMarshalString(t *testing.T) {
|
||||
for _, tt := range marshalStringTests {
|
||||
got := marshalString(nil, tt.v)
|
||||
if !bytes.Equal(tt.want, got) {
|
||||
t.Errorf("marshalString(%q): want %#v, got %#v", tt.v, tt.want, got)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var marshalTests = []struct {
|
||||
v interface{}
|
||||
want []byte
|
||||
}{
|
||||
{uint8(1), []byte{1}},
|
||||
{byte(1), []byte{1}},
|
||||
{uint32(1), []byte{0, 0, 0, 1}},
|
||||
{uint64(1), []byte{0, 0, 0, 0, 0, 0, 0, 1}},
|
||||
{"foo", []byte{0x0, 0x0, 0x0, 0x3, 0x66, 0x6f, 0x6f}},
|
||||
{[]uint32{1, 2, 3, 4}, []byte{0x0, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x2, 0x0, 0x0, 0x0, 0x3, 0x0, 0x0, 0x0, 0x4}},
|
||||
}
|
||||
|
||||
func TestMarshal(t *testing.T) {
|
||||
for _, tt := range marshalTests {
|
||||
got := marshal(nil, tt.v)
|
||||
if !bytes.Equal(tt.want, got) {
|
||||
t.Errorf("marshal(%v): want %#v, got %#v", tt.v, tt.want, got)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var unmarshalUint32Tests = []struct {
|
||||
b []byte
|
||||
want uint32
|
||||
rest []byte
|
||||
}{
|
||||
{[]byte{0, 0, 0, 0}, 0, nil},
|
||||
{[]byte{0, 0, 1, 0}, 256, nil},
|
||||
{[]byte{255, 0, 0, 255}, 4278190335, nil},
|
||||
}
|
||||
|
||||
func TestUnmarshalUint32(t *testing.T) {
|
||||
for _, tt := range unmarshalUint32Tests {
|
||||
got, rest := unmarshalUint32(tt.b)
|
||||
if got != tt.want || !bytes.Equal(rest, tt.rest) {
|
||||
t.Errorf("unmarshalUint32(%v): want %v, %#v, got %v, %#v", tt.b, tt.want, tt.rest, got, rest)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var unmarshalUint64Tests = []struct {
|
||||
b []byte
|
||||
want uint64
|
||||
rest []byte
|
||||
}{
|
||||
{[]byte{0, 0, 0, 0, 0, 0, 0, 0}, 0, nil},
|
||||
{[]byte{0, 0, 0, 0, 0, 0, 1, 0}, 256, nil},
|
||||
{[]byte{255, 0, 0, 0, 0, 0, 0, 255}, 18374686479671623935, nil},
|
||||
}
|
||||
|
||||
func TestUnmarshalUint64(t *testing.T) {
|
||||
for _, tt := range unmarshalUint64Tests {
|
||||
got, rest := unmarshalUint64(tt.b)
|
||||
if got != tt.want || !bytes.Equal(rest, tt.rest) {
|
||||
t.Errorf("unmarshalUint64(%v): want %v, %#v, got %v, %#v", tt.b, tt.want, tt.rest, got, rest)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var unmarshalStringTests = []struct {
|
||||
b []byte
|
||||
want string
|
||||
rest []byte
|
||||
}{
|
||||
{marshalString(nil, ""), "", nil},
|
||||
{marshalString(nil, "blah"), "blah", nil},
|
||||
}
|
||||
|
||||
func TestUnmarshalString(t *testing.T) {
|
||||
for _, tt := range unmarshalStringTests {
|
||||
got, rest := unmarshalString(tt.b)
|
||||
if got != tt.want || !bytes.Equal(rest, tt.rest) {
|
||||
t.Errorf("unmarshalUint64(%v): want %q, %#v, got %q, %#v", tt.b, tt.want, tt.rest, got, rest)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var sendPacketTests = []struct {
|
||||
p encoding.BinaryMarshaler
|
||||
want []byte
|
||||
}{
|
||||
{sshFxInitPacket{
|
||||
Version: 3,
|
||||
Extensions: []struct{ Name, Data string }{
|
||||
{"posix-rename@openssh.com", "1"},
|
||||
},
|
||||
}, []byte{0x0, 0x0, 0x0, 0x26, 0x1, 0x0, 0x0, 0x0, 0x3, 0x0, 0x0, 0x0, 0x18, 0x70, 0x6f, 0x73, 0x69, 0x78, 0x2d, 0x72, 0x65, 0x6e, 0x61, 0x6d, 0x65, 0x40, 0x6f, 0x70, 0x65, 0x6e, 0x73, 0x73, 0x68, 0x2e, 0x63, 0x6f, 0x6d, 0x0, 0x0, 0x0, 0x1, 0x31}},
|
||||
|
||||
{sshFxpOpenPacket{
|
||||
Id: 1,
|
||||
Path: "/foo",
|
||||
Pflags: flags(os.O_RDONLY),
|
||||
}, []byte{0x0, 0x0, 0x0, 0x15, 0x3, 0x0, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x4, 0x2f, 0x66, 0x6f, 0x6f, 0x0, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x0}},
|
||||
|
||||
{sshFxpWritePacket{
|
||||
Id: 124,
|
||||
Handle: "foo",
|
||||
Offset: 13,
|
||||
Length: uint32(len([]byte("bar"))),
|
||||
Data: []byte("bar"),
|
||||
}, []byte{0x0, 0x0, 0x0, 0x1b, 0x6, 0x0, 0x0, 0x0, 0x7c, 0x0, 0x0, 0x0, 0x3, 0x66, 0x6f, 0x6f, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xd, 0x0, 0x0, 0x0, 0x3, 0x62, 0x61, 0x72}},
|
||||
|
||||
{sshFxpSetstatPacket{
|
||||
Id: 31,
|
||||
Path: "/bar",
|
||||
Flags: flags(os.O_WRONLY),
|
||||
Attrs: struct {
|
||||
Uid uint32
|
||||
Gid uint32
|
||||
}{1000, 100},
|
||||
}, []byte{0x0, 0x0, 0x0, 0x19, 0x9, 0x0, 0x0, 0x0, 0x1f, 0x0, 0x0, 0x0, 0x4, 0x2f, 0x62, 0x61, 0x72, 0x0, 0x0, 0x0, 0x2, 0x0, 0x0, 0x3, 0xe8, 0x0, 0x0, 0x0, 0x64}},
|
||||
}
|
||||
|
||||
func TestSendPacket(t *testing.T) {
|
||||
for _, tt := range sendPacketTests {
|
||||
var w bytes.Buffer
|
||||
sendPacket(&w, tt.p)
|
||||
if got := w.Bytes(); !bytes.Equal(tt.want, got) {
|
||||
t.Errorf("sendPacket(%v): want %#v, got %#v", tt.p, tt.want, got)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func sp(p encoding.BinaryMarshaler) []byte {
|
||||
var w bytes.Buffer
|
||||
sendPacket(&w, p)
|
||||
return w.Bytes()
|
||||
}
|
||||
|
||||
var recvPacketTests = []struct {
|
||||
b []byte
|
||||
want uint8
|
||||
rest []byte
|
||||
}{
|
||||
{sp(sshFxInitPacket{
|
||||
Version: 3,
|
||||
Extensions: []struct{ Name, Data string }{
|
||||
{"posix-rename@openssh.com", "1"},
|
||||
},
|
||||
}), ssh_FXP_INIT, []byte{0x0, 0x0, 0x0, 0x3, 0x0, 0x0, 0x0, 0x18, 0x70, 0x6f, 0x73, 0x69, 0x78, 0x2d, 0x72, 0x65, 0x6e, 0x61, 0x6d, 0x65, 0x40, 0x6f, 0x70, 0x65, 0x6e, 0x73, 0x73, 0x68, 0x2e, 0x63, 0x6f, 0x6d, 0x0, 0x0, 0x0, 0x1, 0x31}},
|
||||
}
|
||||
|
||||
func TestRecvPacket(t *testing.T) {
|
||||
for _, tt := range recvPacketTests {
|
||||
r := bytes.NewReader(tt.b)
|
||||
got, rest, _ := recvPacket(r)
|
||||
if got != tt.want || !bytes.Equal(rest, tt.rest) {
|
||||
t.Errorf("recvPacket(%#v): want %v, %#v, got %v, %#v", tt.b, tt.want, tt.rest, got, rest)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkMarshalInit(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
sp(sshFxInitPacket{
|
||||
Version: 3,
|
||||
Extensions: []struct{ Name, Data string }{
|
||||
{"posix-rename@openssh.com", "1"},
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkMarshalOpen(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
sp(sshFxpOpenPacket{
|
||||
Id: 1,
|
||||
Path: "/home/test/some/random/path",
|
||||
Pflags: flags(os.O_RDONLY),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkMarshalWriteWorstCase(b *testing.B) {
|
||||
data := make([]byte, 32*1024)
|
||||
for i := 0; i < b.N; i++ {
|
||||
sp(sshFxpWritePacket{
|
||||
Id: 1,
|
||||
Handle: "someopaquehandle",
|
||||
Offset: 0,
|
||||
Length: uint32(len(data)),
|
||||
Data: data,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkMarshalWrite1k(b *testing.B) {
|
||||
data := make([]byte, 1024)
|
||||
for i := 0; i < b.N; i++ {
|
||||
sp(sshFxpWritePacket{
|
||||
Id: 1,
|
||||
Handle: "someopaquehandle",
|
||||
Offset: 0,
|
||||
Length: uint32(len(data)),
|
||||
Data: data,
|
||||
})
|
||||
}
|
||||
}
|
||||
5
Godeps/_workspace/src/github.com/pkg/sftp/release.go
generated
vendored
Normal file
5
Godeps/_workspace/src/github.com/pkg/sftp/release.go
generated
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
// +build !debug
|
||||
|
||||
package sftp
|
||||
|
||||
func debug(fmt string, args ...interface{}) {}
|
||||
187
Godeps/_workspace/src/github.com/pkg/sftp/sftp.go
generated
vendored
Normal file
187
Godeps/_workspace/src/github.com/pkg/sftp/sftp.go
generated
vendored
Normal file
@@ -0,0 +1,187 @@
|
||||
// Package sftp implements the SSH File Transfer Protocol as described in
|
||||
// https://filezilla-project.org/specs/draft-ietf-secsh-filexfer-02.txt
|
||||
package sftp
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
const (
|
||||
ssh_FXP_INIT = 1
|
||||
ssh_FXP_VERSION = 2
|
||||
ssh_FXP_OPEN = 3
|
||||
ssh_FXP_CLOSE = 4
|
||||
ssh_FXP_READ = 5
|
||||
ssh_FXP_WRITE = 6
|
||||
ssh_FXP_LSTAT = 7
|
||||
ssh_FXP_FSTAT = 8
|
||||
ssh_FXP_SETSTAT = 9
|
||||
ssh_FXP_FSETSTAT = 10
|
||||
ssh_FXP_OPENDIR = 11
|
||||
ssh_FXP_READDIR = 12
|
||||
ssh_FXP_REMOVE = 13
|
||||
ssh_FXP_MKDIR = 14
|
||||
ssh_FXP_RMDIR = 15
|
||||
ssh_FXP_REALPATH = 16
|
||||
ssh_FXP_STAT = 17
|
||||
ssh_FXP_RENAME = 18
|
||||
ssh_FXP_READLINK = 19
|
||||
ssh_FXP_SYMLINK = 20
|
||||
ssh_FXP_STATUS = 101
|
||||
ssh_FXP_HANDLE = 102
|
||||
ssh_FXP_DATA = 103
|
||||
ssh_FXP_NAME = 104
|
||||
ssh_FXP_ATTRS = 105
|
||||
ssh_FXP_EXTENDED = 200
|
||||
ssh_FXP_EXTENDED_REPLY = 201
|
||||
)
|
||||
|
||||
const (
|
||||
ssh_FX_OK = 0
|
||||
ssh_FX_EOF = 1
|
||||
ssh_FX_NO_SUCH_FILE = 2
|
||||
ssh_FX_PERMISSION_DENIED = 3
|
||||
ssh_FX_FAILURE = 4
|
||||
ssh_FX_BAD_MESSAGE = 5
|
||||
ssh_FX_NO_CONNECTION = 6
|
||||
ssh_FX_CONNECTION_LOST = 7
|
||||
ssh_FX_OP_UNSUPPORTED = 8
|
||||
)
|
||||
|
||||
const (
|
||||
ssh_FXF_READ = 0x00000001
|
||||
ssh_FXF_WRITE = 0x00000002
|
||||
ssh_FXF_APPEND = 0x00000004
|
||||
ssh_FXF_CREAT = 0x00000008
|
||||
ssh_FXF_TRUNC = 0x00000010
|
||||
ssh_FXF_EXCL = 0x00000020
|
||||
)
|
||||
|
||||
type fxp uint8
|
||||
|
||||
func (f fxp) String() string {
|
||||
switch f {
|
||||
case ssh_FXP_INIT:
|
||||
return "SSH_FXP_INIT"
|
||||
case ssh_FXP_VERSION:
|
||||
return "SSH_FXP_VERSION"
|
||||
case ssh_FXP_OPEN:
|
||||
return "SSH_FXP_OPEN"
|
||||
case ssh_FXP_CLOSE:
|
||||
return "SSH_FXP_CLOSE"
|
||||
case ssh_FXP_READ:
|
||||
return "SSH_FXP_READ"
|
||||
case ssh_FXP_WRITE:
|
||||
return "SSH_FXP_WRITE"
|
||||
case ssh_FXP_LSTAT:
|
||||
return "SSH_FXP_LSTAT"
|
||||
case ssh_FXP_FSTAT:
|
||||
return "SSH_FXP_FSTAT"
|
||||
case ssh_FXP_SETSTAT:
|
||||
return "SSH_FXP_SETSTAT"
|
||||
case ssh_FXP_FSETSTAT:
|
||||
return "SSH_FXP_FSETSTAT"
|
||||
case ssh_FXP_OPENDIR:
|
||||
return "SSH_FXP_OPENDIR"
|
||||
case ssh_FXP_READDIR:
|
||||
return "SSH_FXP_READDIR"
|
||||
case ssh_FXP_REMOVE:
|
||||
return "SSH_FXP_REMOVE"
|
||||
case ssh_FXP_MKDIR:
|
||||
return "SSH_FXP_MKDIR"
|
||||
case ssh_FXP_RMDIR:
|
||||
return "SSH_FXP_RMDIR"
|
||||
case ssh_FXP_REALPATH:
|
||||
return "SSH_FXP_REALPATH"
|
||||
case ssh_FXP_STAT:
|
||||
return "SSH_FXP_STAT"
|
||||
case ssh_FXP_RENAME:
|
||||
return "SSH_FXP_RENAME"
|
||||
case ssh_FXP_READLINK:
|
||||
return "SSH_FXP_READLINK"
|
||||
case ssh_FXP_SYMLINK:
|
||||
return "SSH_FXP_SYMLINK"
|
||||
case ssh_FXP_STATUS:
|
||||
return "SSH_FXP_STATUS"
|
||||
case ssh_FXP_HANDLE:
|
||||
return "SSH_FXP_HANDLE"
|
||||
case ssh_FXP_DATA:
|
||||
return "SSH_FXP_DATA"
|
||||
case ssh_FXP_NAME:
|
||||
return "SSH_FXP_NAME"
|
||||
case ssh_FXP_ATTRS:
|
||||
return "SSH_FXP_ATTRS"
|
||||
case ssh_FXP_EXTENDED:
|
||||
return "SSH_FXP_EXTENDED"
|
||||
case ssh_FXP_EXTENDED_REPLY:
|
||||
return "SSH_FXP_EXTENDED_REPLY"
|
||||
default:
|
||||
return "unknown"
|
||||
}
|
||||
}
|
||||
|
||||
type fx uint8
|
||||
|
||||
func (f fx) String() string {
|
||||
switch f {
|
||||
case ssh_FX_OK:
|
||||
return "SSH_FX_OK"
|
||||
case ssh_FX_EOF:
|
||||
return "SSH_FX_EOF"
|
||||
case ssh_FX_NO_SUCH_FILE:
|
||||
return "SSH_FX_NO_SUCH_FILE"
|
||||
case ssh_FX_PERMISSION_DENIED:
|
||||
return "SSH_FX_PERMISSION_DENIED"
|
||||
case ssh_FX_FAILURE:
|
||||
return "SSH_FX_FAILURE"
|
||||
case ssh_FX_BAD_MESSAGE:
|
||||
return "SSH_FX_BAD_MESSAGE"
|
||||
case ssh_FX_NO_CONNECTION:
|
||||
return "SSH_FX_NO_CONNECTION"
|
||||
case ssh_FX_CONNECTION_LOST:
|
||||
return "SSH_FX_CONNECTION_LOST"
|
||||
case ssh_FX_OP_UNSUPPORTED:
|
||||
return "SSH_FX_OP_UNSUPPORTED"
|
||||
default:
|
||||
return "unknown"
|
||||
}
|
||||
}
|
||||
|
||||
type unexpectedPacketErr struct {
|
||||
want, got uint8
|
||||
}
|
||||
|
||||
func (u *unexpectedPacketErr) Error() string {
|
||||
return fmt.Sprintf("sftp: unexpected packet: want %v, got %v", fxp(u.want), fxp(u.got))
|
||||
}
|
||||
|
||||
func unimplementedPacketErr(u uint8) error {
|
||||
return fmt.Errorf("sftp: unimplemented packet type: got %v", fxp(u))
|
||||
}
|
||||
|
||||
type unexpectedIdErr struct{ want, got uint32 }
|
||||
|
||||
func (u *unexpectedIdErr) Error() string {
|
||||
return fmt.Sprintf("sftp: unexpected id: want %v, got %v", u.want, u.got)
|
||||
}
|
||||
|
||||
func unimplementedSeekWhence(whence int) error {
|
||||
return fmt.Errorf("sftp: unimplemented seek whence %v", whence)
|
||||
}
|
||||
|
||||
func unexpectedCount(want, got uint32) error {
|
||||
return fmt.Errorf("sftp: unexpected count: want %v, got %v", want, got)
|
||||
}
|
||||
|
||||
type unexpectedVersionErr struct{ want, got uint32 }
|
||||
|
||||
func (u *unexpectedVersionErr) Error() string {
|
||||
return fmt.Sprintf("sftp: unexpected server version: want %v, got %v", u.want, u.got)
|
||||
}
|
||||
|
||||
type StatusError struct {
|
||||
Code uint32
|
||||
msg, lang string
|
||||
}
|
||||
|
||||
func (s *StatusError) Error() string { return fmt.Sprintf("sftp: %q (%v)", s.msg, fx(s.Code)) }
|
||||
1
Godeps/_workspace/src/github.com/pkg/sftp/wercker.yml
generated
vendored
Normal file
1
Godeps/_workspace/src/github.com/pkg/sftp/wercker.yml
generated
vendored
Normal file
@@ -0,0 +1 @@
|
||||
box: wercker/golang
|
||||
Reference in New Issue
Block a user