mirror of
https://github.com/octoleo/restic.git
synced 2025-01-22 14:48:24 +00:00
Update sftp library
This commit is contained in:
parent
ed09887d9e
commit
a7e64afc0d
4
vendor/manifest
vendored
4
vendor/manifest
vendored
@ -40,8 +40,8 @@
|
||||
{
|
||||
"importpath": "github.com/pkg/sftp",
|
||||
"repository": "https://github.com/pkg/sftp",
|
||||
"revision": "e84cc8c755ca39b7b64f510fe1fffc1b51f210a5",
|
||||
"branch": "HEAD"
|
||||
"revision": "a71e8f580e3b622ebff585309160b1cc549ef4d2",
|
||||
"branch": "master"
|
||||
},
|
||||
{
|
||||
"importpath": "github.com/restic/chunker",
|
||||
|
2
vendor/src/github.com/pkg/sftp/README.md
vendored
2
vendor/src/github.com/pkg/sftp/README.md
vendored
@ -17,9 +17,7 @@ The Walker interface for directory traversal is heavily inspired by Keith Rarick
|
||||
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
|
||||
------------
|
||||
|
152
vendor/src/github.com/pkg/sftp/client.go
vendored
152
vendor/src/github.com/pkg/sftp/client.go
vendored
@ -2,19 +2,15 @@ package sftp
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/kr/fs"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"golang.org/x/crypto/ssh"
|
||||
)
|
||||
|
||||
@ -22,7 +18,7 @@ import (
|
||||
func MaxPacket(size int) func(*Client) error {
|
||||
return func(c *Client) error {
|
||||
if size < 1<<15 {
|
||||
return fmt.Errorf("size must be greater or equal to 32k")
|
||||
return errors.Errorf("size must be greater or equal to 32k")
|
||||
}
|
||||
c.maxPacket = size
|
||||
return nil
|
||||
@ -56,11 +52,14 @@ func NewClient(conn *ssh.Client, opts ...func(*Client) error) (*Client, error) {
|
||||
// the system's ssh client program (e.g. via exec.Command).
|
||||
func NewClientPipe(rd io.Reader, wr io.WriteCloser, opts ...func(*Client) error) (*Client, error) {
|
||||
sftp := &Client{
|
||||
w: wr,
|
||||
r: rd,
|
||||
maxPacket: 1 << 15,
|
||||
inflight: make(map[uint32]chan<- result),
|
||||
recvClosed: make(chan struct{}),
|
||||
clientConn: clientConn{
|
||||
conn: conn{
|
||||
Reader: rd,
|
||||
WriteCloser: wr,
|
||||
},
|
||||
inflight: make(map[uint32]chan<- result),
|
||||
},
|
||||
maxPacket: 1 << 15,
|
||||
}
|
||||
if err := sftp.applyOptions(opts...); err != nil {
|
||||
wr.Close()
|
||||
@ -74,7 +73,8 @@ func NewClientPipe(rd io.Reader, wr io.WriteCloser, opts ...func(*Client) error)
|
||||
wr.Close()
|
||||
return nil, err
|
||||
}
|
||||
go sftp.recv()
|
||||
sftp.clientConn.wg.Add(1)
|
||||
go sftp.loop()
|
||||
return sftp, nil
|
||||
}
|
||||
|
||||
@ -84,22 +84,10 @@ func NewClientPipe(rd io.Reader, wr io.WriteCloser, opts ...func(*Client) error)
|
||||
//
|
||||
// Client implements the github.com/kr/fs.FileSystem interface.
|
||||
type Client struct {
|
||||
w io.WriteCloser
|
||||
r io.Reader
|
||||
clientConn
|
||||
|
||||
maxPacket int // max packet size read or written.
|
||||
nextid uint32
|
||||
|
||||
mu sync.Mutex // ensures only on request is in flight to the server at once
|
||||
inflight map[uint32]chan<- result // outstanding requests
|
||||
recvClosed chan struct{} // remote end has closed the connection
|
||||
}
|
||||
|
||||
// Close closes the SFTP session.
|
||||
func (c *Client) Close() error {
|
||||
err := c.w.Close()
|
||||
<-c.recvClosed
|
||||
return err
|
||||
}
|
||||
|
||||
// Create creates the named file mode 0666 (before umask), truncating it if
|
||||
@ -112,7 +100,7 @@ func (c *Client) Create(path string) (*File, error) {
|
||||
const sftpProtocolVersion = 3 // http://tools.ietf.org/html/draft-ietf-secsh-filexfer-02
|
||||
|
||||
func (c *Client) sendInit() error {
|
||||
return sendPacket(c.w, sshFxInitPacket{
|
||||
return c.clientConn.conn.sendPacket(sshFxInitPacket{
|
||||
Version: sftpProtocolVersion, // http://tools.ietf.org/html/draft-ietf-secsh-filexfer-02
|
||||
})
|
||||
}
|
||||
@ -123,7 +111,7 @@ func (c *Client) nextID() uint32 {
|
||||
}
|
||||
|
||||
func (c *Client) recvVersion() error {
|
||||
typ, data, err := recvPacket(c.r)
|
||||
typ, data, err := c.recvPacket()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -139,46 +127,6 @@ func (c *Client) recvVersion() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// broadcastErr sends an error to all goroutines waiting for a response.
|
||||
func (c *Client) broadcastErr(err error) {
|
||||
c.mu.Lock()
|
||||
listeners := make([]chan<- result, 0, len(c.inflight))
|
||||
for _, ch := range c.inflight {
|
||||
listeners = append(listeners, ch)
|
||||
}
|
||||
c.mu.Unlock()
|
||||
for _, ch := range listeners {
|
||||
ch <- result{err: err}
|
||||
}
|
||||
}
|
||||
|
||||
// recv continuously reads from the server and forwards responses to the
|
||||
// appropriate channel.
|
||||
func (c *Client) recv() {
|
||||
defer close(c.recvClosed)
|
||||
for {
|
||||
typ, data, err := recvPacket(c.r)
|
||||
if err != nil {
|
||||
// Return the error to all listeners.
|
||||
c.broadcastErr(err)
|
||||
return
|
||||
}
|
||||
sid, _ := unmarshalUint32(data)
|
||||
c.mu.Lock()
|
||||
ch, ok := c.inflight[sid]
|
||||
delete(c.inflight, sid)
|
||||
c.mu.Unlock()
|
||||
if !ok {
|
||||
// This is an unexpected occurrence. Send the error
|
||||
// back to all listeners so that they terminate
|
||||
// gracefully.
|
||||
c.broadcastErr(fmt.Errorf("sid: %v not fond", sid))
|
||||
return
|
||||
}
|
||||
ch <- result{typ: typ, data: data}
|
||||
}
|
||||
}
|
||||
|
||||
// Walk returns a new Walker rooted at root.
|
||||
func (c *Client) Walk(root string) *fs.Walker {
|
||||
return fs.WalkFS(root, c)
|
||||
@ -196,7 +144,7 @@ func (c *Client) ReadDir(p string) ([]os.FileInfo, error) {
|
||||
var done = false
|
||||
for !done {
|
||||
id := c.nextID()
|
||||
typ, data, err1 := c.sendRequest(sshFxpReaddirPacket{
|
||||
typ, data, err1 := c.sendPacket(sshFxpReaddirPacket{
|
||||
ID: id,
|
||||
Handle: handle,
|
||||
})
|
||||
@ -239,7 +187,7 @@ func (c *Client) ReadDir(p string) ([]os.FileInfo, error) {
|
||||
|
||||
func (c *Client) opendir(path string) (string, error) {
|
||||
id := c.nextID()
|
||||
typ, data, err := c.sendRequest(sshFxpOpendirPacket{
|
||||
typ, data, err := c.sendPacket(sshFxpOpendirPacket{
|
||||
ID: id,
|
||||
Path: path,
|
||||
})
|
||||
@ -265,7 +213,7 @@ func (c *Client) opendir(path string) (string, error) {
|
||||
// If 'p' is a symbolic link, the returned FileInfo structure describes the referent file.
|
||||
func (c *Client) Stat(p string) (os.FileInfo, error) {
|
||||
id := c.nextID()
|
||||
typ, data, err := c.sendRequest(sshFxpStatPacket{
|
||||
typ, data, err := c.sendPacket(sshFxpStatPacket{
|
||||
ID: id,
|
||||
Path: p,
|
||||
})
|
||||
@ -291,7 +239,7 @@ func (c *Client) Stat(p string) (os.FileInfo, error) {
|
||||
// If 'p' is a symbolic link, the returned FileInfo structure describes the symbolic link.
|
||||
func (c *Client) Lstat(p string) (os.FileInfo, error) {
|
||||
id := c.nextID()
|
||||
typ, data, err := c.sendRequest(sshFxpLstatPacket{
|
||||
typ, data, err := c.sendPacket(sshFxpLstatPacket{
|
||||
ID: id,
|
||||
Path: p,
|
||||
})
|
||||
@ -316,7 +264,7 @@ func (c *Client) Lstat(p string) (os.FileInfo, error) {
|
||||
// ReadLink reads the target of a symbolic link.
|
||||
func (c *Client) ReadLink(p string) (string, error) {
|
||||
id := c.nextID()
|
||||
typ, data, err := c.sendRequest(sshFxpReadlinkPacket{
|
||||
typ, data, err := c.sendPacket(sshFxpReadlinkPacket{
|
||||
ID: id,
|
||||
Path: p,
|
||||
})
|
||||
@ -345,7 +293,7 @@ func (c *Client) ReadLink(p string) (string, error) {
|
||||
// Symlink creates a symbolic link at 'newname', pointing at target 'oldname'
|
||||
func (c *Client) Symlink(oldname, newname string) error {
|
||||
id := c.nextID()
|
||||
typ, data, err := c.sendRequest(sshFxpSymlinkPacket{
|
||||
typ, data, err := c.sendPacket(sshFxpSymlinkPacket{
|
||||
ID: id,
|
||||
Linkpath: newname,
|
||||
Targetpath: oldname,
|
||||
@ -364,7 +312,7 @@ func (c *Client) Symlink(oldname, newname string) error {
|
||||
// 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 {
|
||||
id := c.nextID()
|
||||
typ, data, err := c.sendRequest(sshFxpSetstatPacket{
|
||||
typ, data, err := c.sendPacket(sshFxpSetstatPacket{
|
||||
ID: id,
|
||||
Path: path,
|
||||
Flags: flags,
|
||||
@ -430,7 +378,7 @@ func (c *Client) OpenFile(path string, f int) (*File, error) {
|
||||
|
||||
func (c *Client) open(path string, pflags uint32) (*File, error) {
|
||||
id := c.nextID()
|
||||
typ, data, err := c.sendRequest(sshFxpOpenPacket{
|
||||
typ, data, err := c.sendPacket(sshFxpOpenPacket{
|
||||
ID: id,
|
||||
Path: path,
|
||||
Pflags: pflags,
|
||||
@ -458,7 +406,7 @@ func (c *Client) open(path string, pflags uint32) (*File, error) {
|
||||
// immediately after this request has been sent.
|
||||
func (c *Client) close(handle string) error {
|
||||
id := c.nextID()
|
||||
typ, data, err := c.sendRequest(sshFxpClosePacket{
|
||||
typ, data, err := c.sendPacket(sshFxpClosePacket{
|
||||
ID: id,
|
||||
Handle: handle,
|
||||
})
|
||||
@ -475,7 +423,7 @@ func (c *Client) close(handle string) error {
|
||||
|
||||
func (c *Client) fstat(handle string) (*FileStat, error) {
|
||||
id := c.nextID()
|
||||
typ, data, err := c.sendRequest(sshFxpFstatPacket{
|
||||
typ, data, err := c.sendPacket(sshFxpFstatPacket{
|
||||
ID: id,
|
||||
Handle: handle,
|
||||
})
|
||||
@ -504,7 +452,7 @@ func (c *Client) fstat(handle string) (*FileStat, error) {
|
||||
func (c *Client) StatVFS(path string) (*StatVFS, error) {
|
||||
// send the StatVFS packet to the server
|
||||
id := c.nextID()
|
||||
typ, data, err := c.sendRequest(sshFxpStatvfsPacket{
|
||||
typ, data, err := c.sendPacket(sshFxpStatvfsPacket{
|
||||
ID: id,
|
||||
Path: path,
|
||||
})
|
||||
@ -560,7 +508,7 @@ func (c *Client) Remove(path string) error {
|
||||
|
||||
func (c *Client) removeFile(path string) error {
|
||||
id := c.nextID()
|
||||
typ, data, err := c.sendRequest(sshFxpRemovePacket{
|
||||
typ, data, err := c.sendPacket(sshFxpRemovePacket{
|
||||
ID: id,
|
||||
Filename: path,
|
||||
})
|
||||
@ -577,7 +525,7 @@ func (c *Client) removeFile(path string) error {
|
||||
|
||||
func (c *Client) removeDirectory(path string) error {
|
||||
id := c.nextID()
|
||||
typ, data, err := c.sendRequest(sshFxpRmdirPacket{
|
||||
typ, data, err := c.sendPacket(sshFxpRmdirPacket{
|
||||
ID: id,
|
||||
Path: path,
|
||||
})
|
||||
@ -595,7 +543,7 @@ func (c *Client) removeDirectory(path string) error {
|
||||
// Rename renames a file.
|
||||
func (c *Client) Rename(oldname, newname string) error {
|
||||
id := c.nextID()
|
||||
typ, data, err := c.sendRequest(sshFxpRenamePacket{
|
||||
typ, data, err := c.sendPacket(sshFxpRenamePacket{
|
||||
ID: id,
|
||||
Oldpath: oldname,
|
||||
Newpath: newname,
|
||||
@ -613,7 +561,7 @@ func (c *Client) Rename(oldname, newname string) error {
|
||||
|
||||
func (c *Client) realpath(path string) (string, error) {
|
||||
id := c.nextID()
|
||||
typ, data, err := c.sendRequest(sshFxpRealpathPacket{
|
||||
typ, data, err := c.sendPacket(sshFxpRealpathPacket{
|
||||
ID: id,
|
||||
Path: path,
|
||||
})
|
||||
@ -645,41 +593,12 @@ func (c *Client) Getwd() (string, error) {
|
||||
return c.realpath(".")
|
||||
}
|
||||
|
||||
// result captures the result of receiving the a packet from the server
|
||||
type result struct {
|
||||
typ byte
|
||||
data []byte
|
||||
err error
|
||||
}
|
||||
|
||||
type idmarshaler interface {
|
||||
id() uint32
|
||||
encoding.BinaryMarshaler
|
||||
}
|
||||
|
||||
func (c *Client) sendRequest(p idmarshaler) (byte, []byte, error) {
|
||||
ch := make(chan result, 1)
|
||||
c.dispatchRequest(ch, p)
|
||||
s := <-ch
|
||||
return s.typ, s.data, s.err
|
||||
}
|
||||
|
||||
func (c *Client) dispatchRequest(ch chan<- result, p idmarshaler) {
|
||||
c.mu.Lock()
|
||||
c.inflight[p.id()] = ch
|
||||
if err := sendPacket(c.w, p); err != nil {
|
||||
delete(c.inflight, p.id())
|
||||
ch <- result{err: err}
|
||||
}
|
||||
c.mu.Unlock()
|
||||
}
|
||||
|
||||
// Mkdir 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 {
|
||||
id := c.nextID()
|
||||
typ, data, err := c.sendRequest(sshFxpMkdirPacket{
|
||||
typ, data, err := c.sendPacket(sshFxpMkdirPacket{
|
||||
ID: id,
|
||||
Path: path,
|
||||
})
|
||||
@ -784,7 +703,7 @@ func (f *File) Read(b []byte) (int, error) {
|
||||
reqID, data := unmarshalUint32(res.data)
|
||||
req, ok := reqs[reqID]
|
||||
if !ok {
|
||||
firstErr = offsetErr{offset: 0, err: fmt.Errorf("sid: %v not found", reqID)}
|
||||
firstErr = offsetErr{offset: 0, err: errors.Errorf("sid: %v not found", reqID)}
|
||||
break
|
||||
}
|
||||
delete(reqs, reqID)
|
||||
@ -885,7 +804,7 @@ func (f *File) WriteTo(w io.Writer) (int64, error) {
|
||||
reqID, data := unmarshalUint32(res.data)
|
||||
req, ok := reqs[reqID]
|
||||
if !ok {
|
||||
firstErr = offsetErr{offset: 0, err: fmt.Errorf("sid: %v not found", reqID)}
|
||||
firstErr = offsetErr{offset: 0, err: errors.Errorf("sid: %v not found", reqID)}
|
||||
break
|
||||
}
|
||||
delete(reqs, reqID)
|
||||
@ -1166,7 +1085,10 @@ func unmarshalStatus(id uint32, data []byte) error {
|
||||
return &unexpectedIDErr{id, sid}
|
||||
}
|
||||
code, data := unmarshalUint32(data)
|
||||
msg, data := unmarshalString(data)
|
||||
msg, data, err := unmarshalStringSafe(data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
lang, _, _ := unmarshalStringSafe(data)
|
||||
return &StatusError{
|
||||
Code: code,
|
||||
|
@ -39,8 +39,4 @@ func TestClientStatVFS(t *testing.T) {
|
||||
if vfs.Favail != uint64(s.Ffree) {
|
||||
t.Fatal("f_namemax does not match")
|
||||
}
|
||||
|
||||
if vfs.Bavail != s.Bavail {
|
||||
t.Fatal("f_bavail does not match")
|
||||
}
|
||||
}
|
||||
|
@ -29,18 +29,14 @@ func TestClientStatVFS(t *testing.T) {
|
||||
|
||||
// check some stats
|
||||
if vfs.Frsize != uint64(s.Frsize) {
|
||||
t.Fatal("fr_size does not match")
|
||||
t.Fatal("fr_size does not match, expected: %v, got: %v", s.Frsize, vfs.Frsize)
|
||||
}
|
||||
|
||||
if vfs.Bsize != uint64(s.Bsize) {
|
||||
t.Fatal("f_bsize does not match")
|
||||
t.Fatal("f_bsize does not match, expected: %v, got: %v", s.Bsize, vfs.Bsize)
|
||||
}
|
||||
|
||||
if vfs.Namemax != uint64(s.Namelen) {
|
||||
t.Fatal("f_namemax does not match")
|
||||
}
|
||||
|
||||
if vfs.Bavail != s.Bavail {
|
||||
t.Fatal("f_bavail does not match")
|
||||
t.Fatal("f_namemax does not match, expected: %v, got: %v", s.Namelen, vfs.Namemax)
|
||||
}
|
||||
}
|
||||
|
@ -9,6 +9,7 @@ import (
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"math/rand"
|
||||
"net"
|
||||
"os"
|
||||
"os/exec"
|
||||
"os/user"
|
||||
@ -84,37 +85,61 @@ func (w delayedWriter) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// netPipe provides a pair of io.ReadWriteClosers connected to each other.
|
||||
// The functions is identical to os.Pipe with the exception that netPipe
|
||||
// provides the Read/Close guarentees that os.File derrived pipes do not.
|
||||
func netPipe(t testing.TB) (io.ReadWriteCloser, io.ReadWriteCloser) {
|
||||
type result struct {
|
||||
net.Conn
|
||||
error
|
||||
}
|
||||
|
||||
l, err := net.Listen("tcp", "127.0.0.1:0")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
ch := make(chan result, 1)
|
||||
go func() {
|
||||
conn, err := l.Accept()
|
||||
ch <- result{conn, err}
|
||||
err = l.Close()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}()
|
||||
c1, err := net.Dial("tcp", l.Addr().String())
|
||||
if err != nil {
|
||||
l.Close() // might cause another in the listening goroutine, but too bad
|
||||
t.Fatal(err)
|
||||
}
|
||||
r := <-ch
|
||||
if r.error != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return c1, r.Conn
|
||||
}
|
||||
|
||||
func testClientGoSvr(t testing.TB, readonly bool, delay time.Duration) (*Client, *exec.Cmd) {
|
||||
txPipeRd, txPipeWr, err := os.Pipe()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
rxPipeRd, rxPipeWr, err := os.Pipe()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
c1, c2 := netPipe(t)
|
||||
|
||||
options := []ServerOption{WithDebug(os.Stderr)}
|
||||
if readonly {
|
||||
options = append(options, ReadOnly())
|
||||
}
|
||||
|
||||
server, err := NewServer(
|
||||
txPipeRd,
|
||||
rxPipeWr,
|
||||
options...,
|
||||
)
|
||||
server, err := NewServer(c1, options...)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
go server.Serve()
|
||||
|
||||
var ctx io.WriteCloser = txPipeWr
|
||||
var ctx io.WriteCloser = c2
|
||||
if delay > NO_DELAY {
|
||||
ctx = newDelayedWriter(ctx, delay)
|
||||
}
|
||||
|
||||
client, err := NewClientPipe(rxPipeRd, ctx)
|
||||
client, err := NewClientPipe(c2, ctx)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@ -465,6 +490,66 @@ func TestClientFileStat(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestClientStatLink(t *testing.T) {
|
||||
sftp, cmd := testClient(t, READONLY, NO_DELAY)
|
||||
defer cmd.Wait()
|
||||
defer sftp.Close()
|
||||
|
||||
f, err := ioutil.TempFile("", "sftptest")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer os.Remove(f.Name())
|
||||
|
||||
realName := f.Name()
|
||||
linkName := f.Name() + ".softlink"
|
||||
|
||||
// create a symlink that points at sftptest
|
||||
if err := os.Symlink(realName, linkName); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer os.Remove(linkName)
|
||||
|
||||
// compare Lstat of links
|
||||
wantLstat, err := os.Lstat(linkName)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
wantStat, err := os.Stat(linkName)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
gotLstat, err := sftp.Lstat(linkName)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
gotStat, err := sftp.Stat(linkName)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// check that stat is not lstat from os package
|
||||
if sameFile(wantLstat, wantStat) {
|
||||
t.Fatalf("Lstat / Stat(%q): both %#v %#v", f.Name(), wantLstat, wantStat)
|
||||
}
|
||||
|
||||
// compare Lstat of links
|
||||
if !sameFile(wantLstat, gotLstat) {
|
||||
t.Fatalf("Lstat(%q): want %#v, got %#v", f.Name(), wantLstat, gotLstat)
|
||||
}
|
||||
|
||||
// compare Stat of links
|
||||
if !sameFile(wantStat, gotStat) {
|
||||
t.Fatalf("Stat(%q): want %#v, got %#v", f.Name(), wantStat, gotStat)
|
||||
}
|
||||
|
||||
// check that stat is not lstat
|
||||
if sameFile(gotLstat, gotStat) {
|
||||
t.Fatalf("Lstat / Stat(%q): both %#v %#v", f.Name(), gotLstat, gotStat)
|
||||
}
|
||||
}
|
||||
|
||||
func TestClientRemove(t *testing.T) {
|
||||
sftp, cmd := testClient(t, READWRITE, NO_DELAY)
|
||||
defer cmd.Wait()
|
||||
@ -1076,10 +1161,6 @@ func TestClientWrite(t *testing.T) {
|
||||
|
||||
// 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
|
||||
|
62
vendor/src/github.com/pkg/sftp/client_test.go
vendored
62
vendor/src/github.com/pkg/sftp/client_test.go
vendored
@ -4,6 +4,7 @@ import (
|
||||
"errors"
|
||||
"io"
|
||||
"os"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/kr/fs"
|
||||
@ -87,13 +88,58 @@ func TestFlags(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestMissingLangTag(t *testing.T) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
t.Fail()
|
||||
func TestUnmarshalStatus(t *testing.T) {
|
||||
requestID := uint32(1)
|
||||
|
||||
id := marshalUint32([]byte{}, requestID)
|
||||
idCode := marshalUint32(id, ssh_FX_FAILURE)
|
||||
idCodeMsg := marshalString(idCode, "err msg")
|
||||
idCodeMsgLang := marshalString(idCodeMsg, "lang tag")
|
||||
|
||||
var tests = []struct {
|
||||
desc string
|
||||
reqID uint32
|
||||
status []byte
|
||||
want error
|
||||
}{
|
||||
{
|
||||
desc: "well-formed status",
|
||||
reqID: 1,
|
||||
status: idCodeMsgLang,
|
||||
want: &StatusError{
|
||||
Code: ssh_FX_FAILURE,
|
||||
msg: "err msg",
|
||||
lang: "lang tag",
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "missing error message and language tag",
|
||||
reqID: 1,
|
||||
status: idCode,
|
||||
want: errShortPacket,
|
||||
},
|
||||
{
|
||||
desc: "missing language tag",
|
||||
reqID: 1,
|
||||
status: idCodeMsg,
|
||||
want: &StatusError{
|
||||
Code: ssh_FX_FAILURE,
|
||||
msg: "err msg",
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "request identifier mismatch",
|
||||
reqID: 2,
|
||||
status: idCodeMsgLang,
|
||||
want: &unexpectedIDErr{2, requestID},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
got := unmarshalStatus(tt.reqID, tt.status)
|
||||
if !reflect.DeepEqual(got, tt.want) {
|
||||
t.Errorf("unmarshalStatus(%v, %v), test %q\n- want: %#v\n- got: %#v",
|
||||
requestID, tt.status, tt.desc, tt.want, got)
|
||||
}
|
||||
}()
|
||||
buf := marshalUint32([]byte{}, 0)
|
||||
buf = marshalStatus(buf, StatusError{})
|
||||
_ = unmarshalStatus(0, buf[:len(buf)-4])
|
||||
}
|
||||
}
|
||||
|
122
vendor/src/github.com/pkg/sftp/conn.go
vendored
Normal file
122
vendor/src/github.com/pkg/sftp/conn.go
vendored
Normal file
@ -0,0 +1,122 @@
|
||||
package sftp
|
||||
|
||||
import (
|
||||
"encoding"
|
||||
"io"
|
||||
"sync"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// conn implements a bidirectional channel on which client and server
|
||||
// connections are multiplexed.
|
||||
type conn struct {
|
||||
io.Reader
|
||||
io.WriteCloser
|
||||
sync.Mutex // used to serialise writes to sendPacket
|
||||
}
|
||||
|
||||
func (c *conn) recvPacket() (uint8, []byte, error) {
|
||||
return recvPacket(c)
|
||||
}
|
||||
|
||||
func (c *conn) sendPacket(m encoding.BinaryMarshaler) error {
|
||||
c.Lock()
|
||||
defer c.Unlock()
|
||||
return sendPacket(c, m)
|
||||
}
|
||||
|
||||
type clientConn struct {
|
||||
conn
|
||||
wg sync.WaitGroup
|
||||
sync.Mutex // protects inflight
|
||||
inflight map[uint32]chan<- result // outstanding requests
|
||||
}
|
||||
|
||||
// Close closes the SFTP session.
|
||||
func (c *clientConn) Close() error {
|
||||
defer c.wg.Wait()
|
||||
return c.conn.Close()
|
||||
}
|
||||
|
||||
func (c *clientConn) loop() {
|
||||
defer c.wg.Done()
|
||||
err := c.recv()
|
||||
if err != nil {
|
||||
c.broadcastErr(err)
|
||||
}
|
||||
}
|
||||
|
||||
// recv continuously reads from the server and forwards responses to the
|
||||
// appropriate channel.
|
||||
func (c *clientConn) recv() error {
|
||||
defer c.conn.Close()
|
||||
for {
|
||||
typ, data, err := c.recvPacket()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
sid, _ := unmarshalUint32(data)
|
||||
c.Lock()
|
||||
ch, ok := c.inflight[sid]
|
||||
delete(c.inflight, sid)
|
||||
c.Unlock()
|
||||
if !ok {
|
||||
// This is an unexpected occurrence. Send the error
|
||||
// back to all listeners so that they terminate
|
||||
// gracefully.
|
||||
return errors.Errorf("sid: %v not fond", sid)
|
||||
}
|
||||
ch <- result{typ: typ, data: data}
|
||||
}
|
||||
}
|
||||
|
||||
// result captures the result of receiving the a packet from the server
|
||||
type result struct {
|
||||
typ byte
|
||||
data []byte
|
||||
err error
|
||||
}
|
||||
|
||||
type idmarshaler interface {
|
||||
id() uint32
|
||||
encoding.BinaryMarshaler
|
||||
}
|
||||
|
||||
func (c *clientConn) sendPacket(p idmarshaler) (byte, []byte, error) {
|
||||
ch := make(chan result, 1)
|
||||
c.dispatchRequest(ch, p)
|
||||
s := <-ch
|
||||
return s.typ, s.data, s.err
|
||||
}
|
||||
|
||||
func (c *clientConn) dispatchRequest(ch chan<- result, p idmarshaler) {
|
||||
c.Lock()
|
||||
c.inflight[p.id()] = ch
|
||||
if err := c.conn.sendPacket(p); err != nil {
|
||||
delete(c.inflight, p.id())
|
||||
ch <- result{err: err}
|
||||
}
|
||||
c.Unlock()
|
||||
}
|
||||
|
||||
// broadcastErr sends an error to all goroutines waiting for a response.
|
||||
func (c *clientConn) broadcastErr(err error) {
|
||||
c.Lock()
|
||||
listeners := make([]chan<- result, 0, len(c.inflight))
|
||||
for _, ch := range c.inflight {
|
||||
listeners = append(listeners, ch)
|
||||
}
|
||||
c.Unlock()
|
||||
for _, ch := range listeners {
|
||||
ch <- result{err: err}
|
||||
}
|
||||
}
|
||||
|
||||
type serverConn struct {
|
||||
conn
|
||||
}
|
||||
|
||||
func (s *serverConn) sendError(p id, err error) error {
|
||||
return s.sendPacket(statusFromError(p, err))
|
||||
}
|
@ -118,11 +118,20 @@ func main() {
|
||||
}
|
||||
}(requests)
|
||||
|
||||
serverOptions := []sftp.ServerOption{
|
||||
sftp.WithDebug(debugStream),
|
||||
}
|
||||
|
||||
if readOnly {
|
||||
serverOptions = append(serverOptions, sftp.ReadOnly())
|
||||
fmt.Fprintf(debugStream, "Read-only server\n")
|
||||
} else {
|
||||
fmt.Fprintf(debugStream, "Read write server\n")
|
||||
}
|
||||
|
||||
server, err := sftp.NewServer(
|
||||
channel,
|
||||
channel,
|
||||
sftp.WithDebug(debugStream),
|
||||
sftp.ReadOnly(),
|
||||
serverOptions...,
|
||||
)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
|
95
vendor/src/github.com/pkg/sftp/packet.go
vendored
95
vendor/src/github.com/pkg/sftp/packet.go
vendored
@ -1,16 +1,20 @@
|
||||
package sftp
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding"
|
||||
"errors"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"reflect"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
var (
|
||||
errShortPacket = errors.New("packet too short")
|
||||
errShortPacket = errors.New("packet too short")
|
||||
errUnknownExtendedPacket = errors.New("unknown extended packet")
|
||||
)
|
||||
|
||||
const (
|
||||
@ -114,7 +118,7 @@ func unmarshalStringSafe(b []byte) (string, []byte, error) {
|
||||
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)
|
||||
return errors.Errorf("binary marshaller failed: %v", err)
|
||||
}
|
||||
if debugDumpTxPacketBytes {
|
||||
debug("send packet: %s %d bytes %x", fxp(bb[0]), len(bb), bb[1:])
|
||||
@ -125,17 +129,13 @@ func sendPacket(w io.Writer, m encoding.BinaryMarshaler) error {
|
||||
hdr := []byte{byte(l >> 24), byte(l >> 16), byte(l >> 8), byte(l)}
|
||||
_, err = w.Write(hdr)
|
||||
if err != nil {
|
||||
return err
|
||||
return errors.Errorf("failed to send packet header: %v", err)
|
||||
}
|
||||
_, err = w.Write(bb)
|
||||
return err
|
||||
}
|
||||
|
||||
func (svr *Server) sendPacket(m encoding.BinaryMarshaler) error {
|
||||
// any responder can call sendPacket(); actual socket access must be serialized
|
||||
svr.outMutex.Lock()
|
||||
defer svr.outMutex.Unlock()
|
||||
return sendPacket(svr.out, m)
|
||||
if err != nil {
|
||||
return errors.Errorf("failed to send packet body: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func recvPacket(r io.Reader) (uint8, []byte, error) {
|
||||
@ -259,10 +259,7 @@ func unmarshalIDString(b []byte, id *uint32, str *string) error {
|
||||
return err
|
||||
}
|
||||
*str, b, err = unmarshalStringSafe(b)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
return err
|
||||
}
|
||||
|
||||
type sshFxpReaddirPacket struct {
|
||||
@ -318,7 +315,7 @@ type sshFxpStatPacket struct {
|
||||
func (p sshFxpStatPacket) id() uint32 { return p.ID }
|
||||
|
||||
func (p sshFxpStatPacket) MarshalBinary() ([]byte, error) {
|
||||
return marshalIDString(ssh_FXP_LSTAT, p.ID, p.Path)
|
||||
return marshalIDString(ssh_FXP_STAT, p.ID, p.Path)
|
||||
}
|
||||
|
||||
func (p *sshFxpStatPacket) UnmarshalBinary(b []byte) error {
|
||||
@ -838,3 +835,67 @@ func (p *StatVFS) TotalSpace() uint64 {
|
||||
func (p *StatVFS) FreeSpace() uint64 {
|
||||
return p.Frsize * p.Bfree
|
||||
}
|
||||
|
||||
// Convert to ssh_FXP_EXTENDED_REPLY packet binary format
|
||||
func (p *StatVFS) MarshalBinary() ([]byte, error) {
|
||||
var buf bytes.Buffer
|
||||
buf.Write([]byte{ssh_FXP_EXTENDED_REPLY})
|
||||
err := binary.Write(&buf, binary.BigEndian, p)
|
||||
return buf.Bytes(), err
|
||||
}
|
||||
|
||||
type sshFxpExtendedPacket struct {
|
||||
ID uint32
|
||||
ExtendedRequest string
|
||||
SpecificPacket interface {
|
||||
serverRespondablePacket
|
||||
readonly() bool
|
||||
}
|
||||
}
|
||||
|
||||
func (p sshFxpExtendedPacket) id() uint32 { return p.ID }
|
||||
func (p sshFxpExtendedPacket) readonly() bool { return p.SpecificPacket.readonly() }
|
||||
|
||||
func (p sshFxpExtendedPacket) respond(svr *Server) error {
|
||||
return p.SpecificPacket.respond(svr)
|
||||
}
|
||||
|
||||
func (p *sshFxpExtendedPacket) UnmarshalBinary(b []byte) error {
|
||||
var err error
|
||||
bOrig := b
|
||||
if p.ID, b, err = unmarshalUint32Safe(b); err != nil {
|
||||
return err
|
||||
} else if p.ExtendedRequest, b, err = unmarshalStringSafe(b); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// specific unmarshalling
|
||||
switch p.ExtendedRequest {
|
||||
case "statvfs@openssh.com":
|
||||
p.SpecificPacket = &sshFxpExtendedPacketStatVFS{}
|
||||
default:
|
||||
return errUnknownExtendedPacket
|
||||
}
|
||||
|
||||
return p.SpecificPacket.UnmarshalBinary(bOrig)
|
||||
}
|
||||
|
||||
type sshFxpExtendedPacketStatVFS struct {
|
||||
ID uint32
|
||||
ExtendedRequest string
|
||||
Path string
|
||||
}
|
||||
|
||||
func (p sshFxpExtendedPacketStatVFS) id() uint32 { return p.ID }
|
||||
func (p sshFxpExtendedPacketStatVFS) readonly() bool { return true }
|
||||
func (p *sshFxpExtendedPacketStatVFS) UnmarshalBinary(b []byte) error {
|
||||
var err error
|
||||
if p.ID, b, err = unmarshalUint32Safe(b); err != nil {
|
||||
return err
|
||||
} else if p.ExtendedRequest, b, err = unmarshalStringSafe(b); err != nil {
|
||||
return err
|
||||
} else if p.Path, b, err = unmarshalStringSafe(b); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
553
vendor/src/github.com/pkg/sftp/server.go
vendored
553
vendor/src/github.com/pkg/sftp/server.go
vendored
@ -13,6 +13,8 @@ import (
|
||||
"sync"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
const (
|
||||
@ -24,18 +26,14 @@ const (
|
||||
// This implementation currently supports most of sftp server protocol version 3,
|
||||
// as specified at http://tools.ietf.org/html/draft-ietf-secsh-filexfer-02
|
||||
type Server struct {
|
||||
in io.Reader
|
||||
out io.WriteCloser
|
||||
outMutex *sync.Mutex
|
||||
serverConn
|
||||
debugStream io.Writer
|
||||
readOnly bool
|
||||
lastID uint32
|
||||
pktChan chan rxPacket
|
||||
openFiles map[string]*os.File
|
||||
openFilesLock *sync.RWMutex
|
||||
openFilesLock sync.RWMutex
|
||||
handleCount int
|
||||
maxTxPacket uint32
|
||||
workerCount int
|
||||
}
|
||||
|
||||
func (svr *Server) nextHandle(f *os.File) string {
|
||||
@ -69,7 +67,6 @@ type serverRespondablePacket interface {
|
||||
encoding.BinaryUnmarshaler
|
||||
id() uint32
|
||||
respond(svr *Server) error
|
||||
readonly() bool
|
||||
}
|
||||
|
||||
// NewServer creates a new Server instance around the provided streams, serving
|
||||
@ -77,17 +74,18 @@ type serverRespondablePacket interface {
|
||||
// functions may be specified to further configure the Server.
|
||||
//
|
||||
// A subsequent call to Serve() is required to begin serving files over SFTP.
|
||||
func NewServer(in io.Reader, out io.WriteCloser, options ...ServerOption) (*Server, error) {
|
||||
func NewServer(rwc io.ReadWriteCloser, options ...ServerOption) (*Server, error) {
|
||||
s := &Server{
|
||||
in: in,
|
||||
out: out,
|
||||
outMutex: &sync.Mutex{},
|
||||
debugStream: ioutil.Discard,
|
||||
pktChan: make(chan rxPacket, sftpServerWorkerCount),
|
||||
openFiles: map[string]*os.File{},
|
||||
openFilesLock: &sync.RWMutex{},
|
||||
maxTxPacket: 1 << 15,
|
||||
workerCount: sftpServerWorkerCount,
|
||||
serverConn: serverConn{
|
||||
conn: conn{
|
||||
Reader: rwc,
|
||||
WriteCloser: rwc,
|
||||
},
|
||||
},
|
||||
debugStream: ioutil.Discard,
|
||||
pktChan: make(chan rxPacket, sftpServerWorkerCount),
|
||||
openFiles: make(map[string]*os.File),
|
||||
maxTxPacket: 1 << 15,
|
||||
}
|
||||
|
||||
for _, o := range options {
|
||||
@ -123,123 +121,261 @@ type rxPacket struct {
|
||||
pktBytes []byte
|
||||
}
|
||||
|
||||
// Unmarshal a single logical packet from the secure channel
|
||||
func (svr *Server) rxPackets() error {
|
||||
defer close(svr.pktChan)
|
||||
|
||||
for {
|
||||
pktType, pktBytes, err := recvPacket(svr.in)
|
||||
switch err {
|
||||
case nil:
|
||||
svr.pktChan <- rxPacket{fxp(pktType), pktBytes}
|
||||
case io.EOF:
|
||||
return nil
|
||||
// Up to N parallel servers
|
||||
func (svr *Server) sftpServerWorker() error {
|
||||
for p := range svr.pktChan {
|
||||
var pkt interface {
|
||||
encoding.BinaryUnmarshaler
|
||||
id() uint32
|
||||
}
|
||||
var readonly = true
|
||||
switch p.pktType {
|
||||
case ssh_FXP_INIT:
|
||||
pkt = &sshFxInitPacket{}
|
||||
case ssh_FXP_LSTAT:
|
||||
pkt = &sshFxpLstatPacket{}
|
||||
case ssh_FXP_OPEN:
|
||||
pkt = &sshFxpOpenPacket{}
|
||||
// readonly handled specially below
|
||||
case ssh_FXP_CLOSE:
|
||||
pkt = &sshFxpClosePacket{}
|
||||
case ssh_FXP_READ:
|
||||
pkt = &sshFxpReadPacket{}
|
||||
case ssh_FXP_WRITE:
|
||||
pkt = &sshFxpWritePacket{}
|
||||
readonly = false
|
||||
case ssh_FXP_FSTAT:
|
||||
pkt = &sshFxpFstatPacket{}
|
||||
case ssh_FXP_SETSTAT:
|
||||
pkt = &sshFxpSetstatPacket{}
|
||||
readonly = false
|
||||
case ssh_FXP_FSETSTAT:
|
||||
pkt = &sshFxpFsetstatPacket{}
|
||||
readonly = false
|
||||
case ssh_FXP_OPENDIR:
|
||||
pkt = &sshFxpOpendirPacket{}
|
||||
case ssh_FXP_READDIR:
|
||||
pkt = &sshFxpReaddirPacket{}
|
||||
case ssh_FXP_REMOVE:
|
||||
pkt = &sshFxpRemovePacket{}
|
||||
readonly = false
|
||||
case ssh_FXP_MKDIR:
|
||||
pkt = &sshFxpMkdirPacket{}
|
||||
readonly = false
|
||||
case ssh_FXP_RMDIR:
|
||||
pkt = &sshFxpRmdirPacket{}
|
||||
readonly = false
|
||||
case ssh_FXP_REALPATH:
|
||||
pkt = &sshFxpRealpathPacket{}
|
||||
case ssh_FXP_STAT:
|
||||
pkt = &sshFxpStatPacket{}
|
||||
case ssh_FXP_RENAME:
|
||||
pkt = &sshFxpRenamePacket{}
|
||||
readonly = false
|
||||
case ssh_FXP_READLINK:
|
||||
pkt = &sshFxpReadlinkPacket{}
|
||||
case ssh_FXP_SYMLINK:
|
||||
pkt = &sshFxpSymlinkPacket{}
|
||||
readonly = false
|
||||
case ssh_FXP_EXTENDED:
|
||||
pkt = &sshFxpExtendedPacket{}
|
||||
default:
|
||||
fmt.Fprintf(svr.debugStream, "recvPacket error: %v\n", err)
|
||||
return errors.Errorf("unhandled packet type: %s", p.pktType)
|
||||
}
|
||||
if err := pkt.UnmarshalBinary(p.pktBytes); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Up to N parallel servers
|
||||
func (svr *Server) sftpServerWorker(doneChan chan error) {
|
||||
for pkt := range svr.pktChan {
|
||||
dPkt, err := svr.decodePacket(pkt.pktType, pkt.pktBytes)
|
||||
if err != nil {
|
||||
fmt.Fprintf(svr.debugStream, "decodePacket error: %v\n", err)
|
||||
doneChan <- err
|
||||
return
|
||||
// handle FXP_OPENDIR specially
|
||||
switch pkt := pkt.(type) {
|
||||
case *sshFxpOpenPacket:
|
||||
readonly = pkt.readonly()
|
||||
case *sshFxpExtendedPacket:
|
||||
readonly = pkt.SpecificPacket.readonly()
|
||||
}
|
||||
|
||||
// If server is operating read-only and a write operation is requested,
|
||||
// return permission denied
|
||||
if !dPkt.readonly() && svr.readOnly {
|
||||
_ = svr.sendPacket(statusFromError(dPkt.id(), syscall.EPERM))
|
||||
if !readonly && svr.readOnly {
|
||||
if err := svr.sendError(pkt, syscall.EPERM); err != nil {
|
||||
return errors.Wrap(err, "failed to send read only packet response")
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
_ = dPkt.respond(svr)
|
||||
if err := handlePacket(svr, pkt); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func handlePacket(s *Server, p interface{}) error {
|
||||
switch p := p.(type) {
|
||||
case *sshFxInitPacket:
|
||||
return s.sendPacket(sshFxVersionPacket{sftpProtocolVersion, nil})
|
||||
case *sshFxpStatPacket:
|
||||
// stat the requested file
|
||||
info, err := os.Stat(p.Path)
|
||||
if err != nil {
|
||||
return s.sendError(p, err)
|
||||
}
|
||||
return s.sendPacket(sshFxpStatResponse{
|
||||
ID: p.ID,
|
||||
info: info,
|
||||
})
|
||||
case *sshFxpLstatPacket:
|
||||
// stat the requested file
|
||||
info, err := os.Lstat(p.Path)
|
||||
if err != nil {
|
||||
return s.sendError(p, err)
|
||||
}
|
||||
return s.sendPacket(sshFxpStatResponse{
|
||||
ID: p.ID,
|
||||
info: info,
|
||||
})
|
||||
case *sshFxpFstatPacket:
|
||||
f, ok := s.getHandle(p.Handle)
|
||||
if !ok {
|
||||
return s.sendError(p, syscall.EBADF)
|
||||
}
|
||||
|
||||
info, err := f.Stat()
|
||||
if err != nil {
|
||||
return s.sendError(p, err)
|
||||
}
|
||||
|
||||
return s.sendPacket(sshFxpStatResponse{
|
||||
ID: p.ID,
|
||||
info: info,
|
||||
})
|
||||
case *sshFxpMkdirPacket:
|
||||
// TODO FIXME: ignore flags field
|
||||
err := os.Mkdir(p.Path, 0755)
|
||||
return s.sendError(p, err)
|
||||
case *sshFxpRmdirPacket:
|
||||
err := os.Remove(p.Path)
|
||||
return s.sendError(p, err)
|
||||
case *sshFxpRemovePacket:
|
||||
err := os.Remove(p.Filename)
|
||||
return s.sendError(p, err)
|
||||
case *sshFxpRenamePacket:
|
||||
err := os.Rename(p.Oldpath, p.Newpath)
|
||||
return s.sendError(p, err)
|
||||
case *sshFxpSymlinkPacket:
|
||||
err := os.Symlink(p.Targetpath, p.Linkpath)
|
||||
return s.sendError(p, err)
|
||||
case *sshFxpClosePacket:
|
||||
return s.sendError(p, s.closeHandle(p.Handle))
|
||||
case *sshFxpReadlinkPacket:
|
||||
f, err := os.Readlink(p.Path)
|
||||
if err != nil {
|
||||
return s.sendError(p, err)
|
||||
}
|
||||
|
||||
return s.sendPacket(sshFxpNamePacket{
|
||||
ID: p.ID,
|
||||
NameAttrs: []sshFxpNameAttr{{
|
||||
Name: f,
|
||||
LongName: f,
|
||||
Attrs: emptyFileStat,
|
||||
}},
|
||||
})
|
||||
|
||||
case *sshFxpRealpathPacket:
|
||||
f, err := filepath.Abs(p.Path)
|
||||
if err != nil {
|
||||
return s.sendError(p, err)
|
||||
}
|
||||
f = filepath.Clean(f)
|
||||
return s.sendPacket(sshFxpNamePacket{
|
||||
ID: p.ID,
|
||||
NameAttrs: []sshFxpNameAttr{{
|
||||
Name: f,
|
||||
LongName: f,
|
||||
Attrs: emptyFileStat,
|
||||
}},
|
||||
})
|
||||
case *sshFxpOpendirPacket:
|
||||
return sshFxpOpenPacket{
|
||||
ID: p.ID,
|
||||
Path: p.Path,
|
||||
Pflags: ssh_FXF_READ,
|
||||
}.respond(s)
|
||||
case *sshFxpReadPacket:
|
||||
f, ok := s.getHandle(p.Handle)
|
||||
if !ok {
|
||||
return s.sendError(p, syscall.EBADF)
|
||||
}
|
||||
|
||||
data := make([]byte, clamp(p.Len, s.maxTxPacket))
|
||||
n, err := f.ReadAt(data, int64(p.Offset))
|
||||
if err != nil && (err != io.EOF || n == 0) {
|
||||
return s.sendError(p, err)
|
||||
}
|
||||
return s.sendPacket(sshFxpDataPacket{
|
||||
ID: p.ID,
|
||||
Length: uint32(n),
|
||||
Data: data[:n],
|
||||
})
|
||||
case *sshFxpWritePacket:
|
||||
f, ok := s.getHandle(p.Handle)
|
||||
if !ok {
|
||||
return s.sendError(p, syscall.EBADF)
|
||||
}
|
||||
|
||||
_, err := f.WriteAt(p.Data, int64(p.Offset))
|
||||
return s.sendError(p, err)
|
||||
case serverRespondablePacket:
|
||||
err := p.respond(s)
|
||||
return errors.Wrap(err, "pkt.respond failed")
|
||||
default:
|
||||
return errors.Errorf("unexpected packet type %T", p)
|
||||
}
|
||||
doneChan <- nil
|
||||
}
|
||||
|
||||
// Serve serves SFTP connections until the streams stop or the SFTP subsystem
|
||||
// is stopped.
|
||||
func (svr *Server) Serve() error {
|
||||
go svr.rxPackets()
|
||||
doneChan := make(chan error)
|
||||
for i := 0; i < svr.workerCount; i++ {
|
||||
go svr.sftpServerWorker(doneChan)
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(sftpServerWorkerCount)
|
||||
for i := 0; i < sftpServerWorkerCount; i++ {
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
if err := svr.sftpServerWorker(); err != nil {
|
||||
svr.conn.Close() // shuts down recvPacket
|
||||
}
|
||||
}()
|
||||
}
|
||||
for i := 0; i < svr.workerCount; i++ {
|
||||
if err := <-doneChan; err != nil {
|
||||
// abort early and shut down the session on un-decodable packets
|
||||
|
||||
var err error
|
||||
var pktType uint8
|
||||
var pktBytes []byte
|
||||
for {
|
||||
pktType, pktBytes, err = svr.recvPacket()
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
svr.pktChan <- rxPacket{fxp(pktType), pktBytes}
|
||||
}
|
||||
|
||||
close(svr.pktChan) // shuts down sftpServerWorkers
|
||||
wg.Wait() // wait for all workers to exit
|
||||
|
||||
// close any still-open files
|
||||
for handle, file := range svr.openFiles {
|
||||
fmt.Fprintf(svr.debugStream, "sftp server file with handle '%v' left open: %v\n", handle, file.Name())
|
||||
fmt.Fprintf(svr.debugStream, "sftp server file with handle %q left open: %v\n", handle, file.Name())
|
||||
file.Close()
|
||||
}
|
||||
return svr.out.Close()
|
||||
return err // error from recvPacket
|
||||
}
|
||||
|
||||
func (svr *Server) decodePacket(pktType fxp, pktBytes []byte) (serverRespondablePacket, error) {
|
||||
var pkt serverRespondablePacket
|
||||
switch pktType {
|
||||
case ssh_FXP_INIT:
|
||||
pkt = &sshFxInitPacket{}
|
||||
case ssh_FXP_LSTAT:
|
||||
pkt = &sshFxpLstatPacket{}
|
||||
case ssh_FXP_OPEN:
|
||||
pkt = &sshFxpOpenPacket{}
|
||||
case ssh_FXP_CLOSE:
|
||||
pkt = &sshFxpClosePacket{}
|
||||
case ssh_FXP_READ:
|
||||
pkt = &sshFxpReadPacket{}
|
||||
case ssh_FXP_WRITE:
|
||||
pkt = &sshFxpWritePacket{}
|
||||
case ssh_FXP_FSTAT:
|
||||
pkt = &sshFxpFstatPacket{}
|
||||
case ssh_FXP_SETSTAT:
|
||||
pkt = &sshFxpSetstatPacket{}
|
||||
case ssh_FXP_FSETSTAT:
|
||||
pkt = &sshFxpFsetstatPacket{}
|
||||
case ssh_FXP_OPENDIR:
|
||||
pkt = &sshFxpOpendirPacket{}
|
||||
case ssh_FXP_READDIR:
|
||||
pkt = &sshFxpReaddirPacket{}
|
||||
case ssh_FXP_REMOVE:
|
||||
pkt = &sshFxpRemovePacket{}
|
||||
case ssh_FXP_MKDIR:
|
||||
pkt = &sshFxpMkdirPacket{}
|
||||
case ssh_FXP_RMDIR:
|
||||
pkt = &sshFxpRmdirPacket{}
|
||||
case ssh_FXP_REALPATH:
|
||||
pkt = &sshFxpRealpathPacket{}
|
||||
case ssh_FXP_STAT:
|
||||
pkt = &sshFxpStatPacket{}
|
||||
case ssh_FXP_RENAME:
|
||||
pkt = &sshFxpRenamePacket{}
|
||||
case ssh_FXP_READLINK:
|
||||
pkt = &sshFxpReadlinkPacket{}
|
||||
case ssh_FXP_SYMLINK:
|
||||
pkt = &sshFxpSymlinkPacket{}
|
||||
default:
|
||||
return nil, fmt.Errorf("unhandled packet type: %s", pktType)
|
||||
}
|
||||
err := pkt.UnmarshalBinary(pktBytes)
|
||||
return pkt, err
|
||||
}
|
||||
|
||||
func (p sshFxInitPacket) respond(svr *Server) error {
|
||||
return svr.sendPacket(sshFxVersionPacket{sftpProtocolVersion, nil})
|
||||
type id interface {
|
||||
id() uint32
|
||||
}
|
||||
|
||||
// The init packet has no ID, so we just return a zero-value ID
|
||||
func (p sshFxInitPacket) id() uint32 { return 0 }
|
||||
func (p sshFxInitPacket) readonly() bool { return true }
|
||||
func (p sshFxInitPacket) id() uint32 { return 0 }
|
||||
|
||||
type sshFxpStatResponse struct {
|
||||
ID uint32
|
||||
@ -253,141 +389,8 @@ func (p sshFxpStatResponse) MarshalBinary() ([]byte, error) {
|
||||
return b, nil
|
||||
}
|
||||
|
||||
func (p sshFxpLstatPacket) readonly() bool { return true }
|
||||
|
||||
func (p sshFxpLstatPacket) respond(svr *Server) error {
|
||||
// stat the requested file
|
||||
info, err := os.Lstat(p.Path)
|
||||
if err != nil {
|
||||
return svr.sendPacket(statusFromError(p.ID, err))
|
||||
}
|
||||
|
||||
return svr.sendPacket(sshFxpStatResponse{
|
||||
ID: p.ID,
|
||||
info: info,
|
||||
})
|
||||
}
|
||||
|
||||
func (p sshFxpStatPacket) readonly() bool { return true }
|
||||
|
||||
func (p sshFxpStatPacket) respond(svr *Server) error {
|
||||
// stat the requested file
|
||||
info, err := os.Stat(p.Path)
|
||||
if err != nil {
|
||||
return svr.sendPacket(statusFromError(p.ID, err))
|
||||
}
|
||||
|
||||
return svr.sendPacket(sshFxpStatResponse{
|
||||
ID: p.ID,
|
||||
info: info,
|
||||
})
|
||||
}
|
||||
|
||||
func (p sshFxpFstatPacket) readonly() bool { return true }
|
||||
|
||||
func (p sshFxpFstatPacket) respond(svr *Server) error {
|
||||
f, ok := svr.getHandle(p.Handle)
|
||||
if !ok {
|
||||
return svr.sendPacket(statusFromError(p.ID, syscall.EBADF))
|
||||
}
|
||||
|
||||
info, err := f.Stat()
|
||||
if err != nil {
|
||||
return svr.sendPacket(statusFromError(p.ID, err))
|
||||
}
|
||||
|
||||
return svr.sendPacket(sshFxpStatResponse{
|
||||
ID: p.ID,
|
||||
info: info,
|
||||
})
|
||||
}
|
||||
|
||||
func (p sshFxpMkdirPacket) readonly() bool { return false }
|
||||
|
||||
func (p sshFxpMkdirPacket) respond(svr *Server) error {
|
||||
// TODO FIXME: ignore flags field
|
||||
err := os.Mkdir(p.Path, 0755)
|
||||
return svr.sendPacket(statusFromError(p.ID, err))
|
||||
}
|
||||
|
||||
func (p sshFxpRmdirPacket) readonly() bool { return false }
|
||||
|
||||
func (p sshFxpRmdirPacket) respond(svr *Server) error {
|
||||
err := os.Remove(p.Path)
|
||||
return svr.sendPacket(statusFromError(p.ID, err))
|
||||
}
|
||||
|
||||
func (p sshFxpRemovePacket) readonly() bool { return false }
|
||||
|
||||
func (p sshFxpRemovePacket) respond(svr *Server) error {
|
||||
err := os.Remove(p.Filename)
|
||||
return svr.sendPacket(statusFromError(p.ID, err))
|
||||
}
|
||||
|
||||
func (p sshFxpRenamePacket) readonly() bool { return false }
|
||||
|
||||
func (p sshFxpRenamePacket) respond(svr *Server) error {
|
||||
err := os.Rename(p.Oldpath, p.Newpath)
|
||||
return svr.sendPacket(statusFromError(p.ID, err))
|
||||
}
|
||||
|
||||
func (p sshFxpSymlinkPacket) readonly() bool { return false }
|
||||
|
||||
func (p sshFxpSymlinkPacket) respond(svr *Server) error {
|
||||
err := os.Symlink(p.Targetpath, p.Linkpath)
|
||||
return svr.sendPacket(statusFromError(p.ID, err))
|
||||
}
|
||||
|
||||
var emptyFileStat = []interface{}{uint32(0)}
|
||||
|
||||
func (p sshFxpReadlinkPacket) readonly() bool { return true }
|
||||
|
||||
func (p sshFxpReadlinkPacket) respond(svr *Server) error {
|
||||
f, err := os.Readlink(p.Path)
|
||||
if err != nil {
|
||||
return svr.sendPacket(statusFromError(p.ID, err))
|
||||
}
|
||||
|
||||
return svr.sendPacket(sshFxpNamePacket{
|
||||
ID: p.ID,
|
||||
NameAttrs: []sshFxpNameAttr{{
|
||||
Name: f,
|
||||
LongName: f,
|
||||
Attrs: emptyFileStat,
|
||||
}},
|
||||
})
|
||||
}
|
||||
|
||||
func (p sshFxpRealpathPacket) readonly() bool { return true }
|
||||
|
||||
func (p sshFxpRealpathPacket) respond(svr *Server) error {
|
||||
f, err := filepath.Abs(p.Path)
|
||||
if err != nil {
|
||||
return svr.sendPacket(statusFromError(p.ID, err))
|
||||
}
|
||||
|
||||
f = filepath.Clean(f)
|
||||
|
||||
return svr.sendPacket(sshFxpNamePacket{
|
||||
ID: p.ID,
|
||||
NameAttrs: []sshFxpNameAttr{{
|
||||
Name: f,
|
||||
LongName: f,
|
||||
Attrs: emptyFileStat,
|
||||
}},
|
||||
})
|
||||
}
|
||||
|
||||
func (p sshFxpOpendirPacket) readonly() bool { return true }
|
||||
|
||||
func (p sshFxpOpendirPacket) respond(svr *Server) error {
|
||||
return sshFxpOpenPacket{
|
||||
ID: p.ID,
|
||||
Path: p.Path,
|
||||
Pflags: ssh_FXF_READ,
|
||||
}.respond(svr)
|
||||
}
|
||||
|
||||
func (p sshFxpOpenPacket) readonly() bool {
|
||||
return !p.hasPflags(ssh_FXF_WRITE)
|
||||
}
|
||||
@ -398,7 +401,6 @@ func (p sshFxpOpenPacket) hasPflags(flags ...uint32) bool {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
@ -412,7 +414,7 @@ func (p sshFxpOpenPacket) respond(svr *Server) error {
|
||||
osFlags |= os.O_RDONLY
|
||||
} else {
|
||||
// how are they opening?
|
||||
return svr.sendPacket(statusFromError(p.ID, syscall.EINVAL))
|
||||
return svr.sendError(p, syscall.EINVAL)
|
||||
}
|
||||
|
||||
if p.hasPflags(ssh_FXF_APPEND) {
|
||||
@ -430,69 +432,23 @@ func (p sshFxpOpenPacket) respond(svr *Server) error {
|
||||
|
||||
f, err := os.OpenFile(p.Path, osFlags, 0644)
|
||||
if err != nil {
|
||||
return svr.sendPacket(statusFromError(p.ID, err))
|
||||
return svr.sendError(p, err)
|
||||
}
|
||||
|
||||
handle := svr.nextHandle(f)
|
||||
return svr.sendPacket(sshFxpHandlePacket{p.ID, handle})
|
||||
}
|
||||
|
||||
func (p sshFxpClosePacket) readonly() bool { return true }
|
||||
|
||||
func (p sshFxpClosePacket) respond(svr *Server) error {
|
||||
return svr.sendPacket(statusFromError(p.ID, svr.closeHandle(p.Handle)))
|
||||
}
|
||||
|
||||
func (p sshFxpReadPacket) readonly() bool { return true }
|
||||
|
||||
func (p sshFxpReadPacket) respond(svr *Server) error {
|
||||
f, ok := svr.getHandle(p.Handle)
|
||||
if !ok {
|
||||
return svr.sendPacket(statusFromError(p.ID, syscall.EBADF))
|
||||
}
|
||||
|
||||
if p.Len > svr.maxTxPacket {
|
||||
p.Len = svr.maxTxPacket
|
||||
}
|
||||
ret := sshFxpDataPacket{
|
||||
ID: p.ID,
|
||||
Length: p.Len,
|
||||
Data: make([]byte, p.Len),
|
||||
}
|
||||
|
||||
n, err := f.ReadAt(ret.Data, int64(p.Offset))
|
||||
if err != nil && (err != io.EOF || n == 0) {
|
||||
return svr.sendPacket(statusFromError(p.ID, err))
|
||||
}
|
||||
|
||||
ret.Length = uint32(n)
|
||||
return svr.sendPacket(ret)
|
||||
}
|
||||
|
||||
func (p sshFxpWritePacket) readonly() bool { return false }
|
||||
|
||||
func (p sshFxpWritePacket) respond(svr *Server) error {
|
||||
f, ok := svr.getHandle(p.Handle)
|
||||
if !ok {
|
||||
return svr.sendPacket(statusFromError(p.ID, syscall.EBADF))
|
||||
}
|
||||
|
||||
_, err := f.WriteAt(p.Data, int64(p.Offset))
|
||||
return svr.sendPacket(statusFromError(p.ID, err))
|
||||
}
|
||||
|
||||
func (p sshFxpReaddirPacket) readonly() bool { return true }
|
||||
|
||||
func (p sshFxpReaddirPacket) respond(svr *Server) error {
|
||||
f, ok := svr.getHandle(p.Handle)
|
||||
if !ok {
|
||||
return svr.sendPacket(statusFromError(p.ID, syscall.EBADF))
|
||||
return svr.sendError(p, syscall.EBADF)
|
||||
}
|
||||
|
||||
dirname := f.Name()
|
||||
dirents, err := f.Readdir(128)
|
||||
if err != nil {
|
||||
return svr.sendPacket(statusFromError(p.ID, err))
|
||||
return svr.sendError(p, err)
|
||||
}
|
||||
|
||||
ret := sshFxpNamePacket{ID: p.ID}
|
||||
@ -506,8 +462,6 @@ func (p sshFxpReaddirPacket) respond(svr *Server) error {
|
||||
return svr.sendPacket(ret)
|
||||
}
|
||||
|
||||
func (p sshFxpSetstatPacket) readonly() bool { return false }
|
||||
|
||||
func (p sshFxpSetstatPacket) respond(svr *Server) error {
|
||||
// additional unmarshalling is required for each possibility here
|
||||
b := p.Attrs.([]byte)
|
||||
@ -547,15 +501,13 @@ func (p sshFxpSetstatPacket) respond(svr *Server) error {
|
||||
}
|
||||
}
|
||||
|
||||
return svr.sendPacket(statusFromError(p.ID, err))
|
||||
return svr.sendError(p, err)
|
||||
}
|
||||
|
||||
func (p sshFxpFsetstatPacket) readonly() bool { return false }
|
||||
|
||||
func (p sshFxpFsetstatPacket) respond(svr *Server) error {
|
||||
f, ok := svr.getHandle(p.Handle)
|
||||
if !ok {
|
||||
return svr.sendPacket(statusFromError(p.ID, syscall.EBADF))
|
||||
return svr.sendError(p, syscall.EBADF)
|
||||
}
|
||||
|
||||
// additional unmarshalling is required for each possibility here
|
||||
@ -596,7 +548,7 @@ func (p sshFxpFsetstatPacket) respond(svr *Server) error {
|
||||
}
|
||||
}
|
||||
|
||||
return svr.sendPacket(statusFromError(p.ID, err))
|
||||
return svr.sendError(p, err)
|
||||
}
|
||||
|
||||
// translateErrno translates a syscall error number to a SFTP error code.
|
||||
@ -613,9 +565,9 @@ func translateErrno(errno syscall.Errno) uint32 {
|
||||
return ssh_FX_FAILURE
|
||||
}
|
||||
|
||||
func statusFromError(id uint32, err error) sshFxpStatusPacket {
|
||||
func statusFromError(p id, err error) sshFxpStatusPacket {
|
||||
ret := sshFxpStatusPacket{
|
||||
ID: id,
|
||||
ID: p.id(),
|
||||
StatusError: StatusError{
|
||||
// ssh_FX_OK = 0
|
||||
// ssh_FX_EOF = 1
|
||||
@ -646,3 +598,10 @@ func statusFromError(id uint32, err error) sshFxpStatusPacket {
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
func clamp(v, max uint32) uint32 {
|
||||
if v > max {
|
||||
return max
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
@ -295,7 +295,6 @@ func (chsvr *sshSessionChannelServer) handleSubsystem(req *ssh.Request) error {
|
||||
}
|
||||
|
||||
sftpServer, err := NewServer(
|
||||
chsvr.ch,
|
||||
chsvr.ch,
|
||||
WithDebug(sftpServerDebugStream),
|
||||
)
|
||||
|
@ -6,6 +6,7 @@ package main
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
|
||||
@ -16,10 +17,12 @@ func main() {
|
||||
var (
|
||||
readOnly bool
|
||||
debugStderr bool
|
||||
debugLevel string
|
||||
)
|
||||
|
||||
flag.BoolVar(&readOnly, "R", false, "read-only server")
|
||||
flag.BoolVar(&debugStderr, "e", false, "debug to stderr")
|
||||
flag.StringVar(&debugLevel, "l", "none", "debug level (ignored)")
|
||||
flag.Parse()
|
||||
|
||||
debugStream := ioutil.Discard
|
||||
@ -28,8 +31,12 @@ func main() {
|
||||
}
|
||||
|
||||
svr, _ := sftp.NewServer(
|
||||
os.Stdin,
|
||||
os.Stdout,
|
||||
struct {
|
||||
io.Reader
|
||||
io.WriteCloser
|
||||
}{os.Stdin,
|
||||
os.Stdout,
|
||||
},
|
||||
sftp.WithDebug(debugStream),
|
||||
sftp.ReadOnly(),
|
||||
)
|
||||
|
21
vendor/src/github.com/pkg/sftp/server_statvfs_darwin.go
vendored
Normal file
21
vendor/src/github.com/pkg/sftp/server_statvfs_darwin.go
vendored
Normal file
@ -0,0 +1,21 @@
|
||||
package sftp
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
)
|
||||
|
||||
func statvfsFromStatfst(stat *syscall.Statfs_t) (*StatVFS, error) {
|
||||
return &StatVFS{
|
||||
Bsize: uint64(stat.Bsize),
|
||||
Frsize: uint64(stat.Bsize), // fragment size is a linux thing; use block size here
|
||||
Blocks: stat.Blocks,
|
||||
Bfree: stat.Bfree,
|
||||
Bavail: stat.Bavail,
|
||||
Files: stat.Files,
|
||||
Ffree: stat.Ffree,
|
||||
Favail: stat.Ffree, // not sure how to calculate Favail
|
||||
Fsid: uint64(uint64(stat.Fsid.Val[1])<<32 | uint64(stat.Fsid.Val[0])), // endianness?
|
||||
Flag: uint64(stat.Flags), // assuming POSIX?
|
||||
Namemax: 1024, // man 2 statfs shows: #define MAXPATHLEN 1024
|
||||
}, nil
|
||||
}
|
25
vendor/src/github.com/pkg/sftp/server_statvfs_impl.go
vendored
Normal file
25
vendor/src/github.com/pkg/sftp/server_statvfs_impl.go
vendored
Normal file
@ -0,0 +1,25 @@
|
||||
// +build darwin linux,!gccgo
|
||||
|
||||
// fill in statvfs structure with OS specific values
|
||||
// Statfs_t is different per-kernel, and only exists on some unixes (not Solaris for instance)
|
||||
|
||||
package sftp
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
)
|
||||
|
||||
func (p sshFxpExtendedPacketStatVFS) respond(svr *Server) error {
|
||||
stat := &syscall.Statfs_t{}
|
||||
if err := syscall.Statfs(p.Path, stat); err != nil {
|
||||
return svr.sendPacket(statusFromError(p, err))
|
||||
}
|
||||
|
||||
retPkt, err := statvfsFromStatfst(stat)
|
||||
if err != nil {
|
||||
return svr.sendPacket(statusFromError(p, err))
|
||||
}
|
||||
retPkt.ID = p.ID
|
||||
|
||||
return svr.sendPacket(retPkt)
|
||||
}
|
23
vendor/src/github.com/pkg/sftp/server_statvfs_linux.go
vendored
Normal file
23
vendor/src/github.com/pkg/sftp/server_statvfs_linux.go
vendored
Normal file
@ -0,0 +1,23 @@
|
||||
// +build !gccgo,linux
|
||||
|
||||
package sftp
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
)
|
||||
|
||||
func statvfsFromStatfst(stat *syscall.Statfs_t) (*StatVFS, error) {
|
||||
return &StatVFS{
|
||||
Bsize: uint64(stat.Bsize),
|
||||
Frsize: uint64(stat.Frsize),
|
||||
Blocks: stat.Blocks,
|
||||
Bfree: stat.Bfree,
|
||||
Bavail: stat.Bavail,
|
||||
Files: stat.Files,
|
||||
Ffree: stat.Ffree,
|
||||
Favail: stat.Ffree, // not sure how to calculate Favail
|
||||
Fsid: uint64(uint64(stat.Fsid.X__val[1])<<32 | uint64(stat.Fsid.X__val[0])), // endianness?
|
||||
Flag: uint64(stat.Flags), // assuming POSIX?
|
||||
Namemax: uint64(stat.Namelen),
|
||||
}, nil
|
||||
}
|
11
vendor/src/github.com/pkg/sftp/server_statvfs_stubs.go
vendored
Normal file
11
vendor/src/github.com/pkg/sftp/server_statvfs_stubs.go
vendored
Normal file
@ -0,0 +1,11 @@
|
||||
// +build !darwin,!linux gccgo
|
||||
|
||||
package sftp
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
)
|
||||
|
||||
func (p sshFxpExtendedPacketStatVFS) respond(svr *Server) error {
|
||||
return syscall.ENOTSUP
|
||||
}
|
64
vendor/src/github.com/pkg/sftp/server_test.go
vendored
Normal file
64
vendor/src/github.com/pkg/sftp/server_test.go
vendored
Normal file
@ -0,0 +1,64 @@
|
||||
package sftp
|
||||
|
||||
import (
|
||||
"io"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func clientServerPair(t *testing.T) (*Client, *Server) {
|
||||
cr, sw := io.Pipe()
|
||||
sr, cw := io.Pipe()
|
||||
server, err := NewServer(struct {
|
||||
io.Reader
|
||||
io.WriteCloser
|
||||
}{sr, sw})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
go server.Serve()
|
||||
client, err := NewClientPipe(cr, cw)
|
||||
if err != nil {
|
||||
t.Fatalf("%+v\n", err)
|
||||
}
|
||||
return client, server
|
||||
}
|
||||
|
||||
type sshFxpTestBadExtendedPacket struct {
|
||||
ID uint32
|
||||
Extension string
|
||||
Data string
|
||||
}
|
||||
|
||||
func (p sshFxpTestBadExtendedPacket) id() uint32 { return p.ID }
|
||||
|
||||
func (p sshFxpTestBadExtendedPacket) MarshalBinary() ([]byte, error) {
|
||||
l := 1 + 4 + 4 + // type(byte) + uint32 + uint32
|
||||
len(p.Extension) +
|
||||
len(p.Data)
|
||||
|
||||
b := make([]byte, 0, l)
|
||||
b = append(b, ssh_FXP_EXTENDED)
|
||||
b = marshalUint32(b, p.ID)
|
||||
b = marshalString(b, p.Extension)
|
||||
b = marshalString(b, p.Data)
|
||||
return b, nil
|
||||
}
|
||||
|
||||
// test that errors are sent back when we request an invalid extended packet operation
|
||||
func TestInvalidExtendedPacket(t *testing.T) {
|
||||
client, _ := clientServerPair(t)
|
||||
defer client.Close()
|
||||
badPacket := sshFxpTestBadExtendedPacket{client.nextID(), "thisDoesn'tExist", "foobar"}
|
||||
_, _, err := client.clientConn.sendPacket(badPacket)
|
||||
if err == nil {
|
||||
t.Fatal("expected error from bad packet")
|
||||
}
|
||||
|
||||
// try to stat a file; the client should have shut down.
|
||||
filePath := "/etc/passwd"
|
||||
_, err = client.Stat(filePath)
|
||||
if err == nil {
|
||||
t.Fatal("expected error from closed connection")
|
||||
}
|
||||
|
||||
}
|
8
vendor/src/github.com/pkg/sftp/sftp.go
vendored
8
vendor/src/github.com/pkg/sftp/sftp.go
vendored
@ -4,6 +4,8 @@ package sftp
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
const (
|
||||
@ -182,7 +184,7 @@ func (u *unexpectedPacketErr) Error() string {
|
||||
}
|
||||
|
||||
func unimplementedPacketErr(u uint8) error {
|
||||
return fmt.Errorf("sftp: unimplemented packet type: got %v", fxp(u))
|
||||
return errors.Errorf("sftp: unimplemented packet type: got %v", fxp(u))
|
||||
}
|
||||
|
||||
type unexpectedIDErr struct{ want, got uint32 }
|
||||
@ -192,11 +194,11 @@ func (u *unexpectedIDErr) Error() string {
|
||||
}
|
||||
|
||||
func unimplementedSeekWhence(whence int) error {
|
||||
return fmt.Errorf("sftp: unimplemented seek whence %v", whence)
|
||||
return errors.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)
|
||||
return errors.Errorf("sftp: unexpected count: want %v, got %v", want, got)
|
||||
}
|
||||
|
||||
type unexpectedVersionErr struct{ want, got uint32 }
|
||||
|
Loading…
x
Reference in New Issue
Block a user