2013-11-05 19:16:36 +08:00
|
|
|
package sftp
|
|
|
|
|
|
|
|
import (
|
|
|
|
"io"
|
|
|
|
"os"
|
2013-11-06 12:40:35 +08:00
|
|
|
"path"
|
2013-11-05 19:16:36 +08:00
|
|
|
"sync"
|
|
|
|
|
2013-11-11 09:57:03 +08:00
|
|
|
"github.com/kr/fs"
|
2013-11-07 08:31:46 +08:00
|
|
|
|
2013-11-05 19:16:36 +08:00
|
|
|
"code.google.com/p/go.crypto/ssh"
|
|
|
|
)
|
|
|
|
|
2013-11-06 11:50:04 +08:00
|
|
|
// New creates a new SFTP client on conn.
|
2013-11-05 19:16:36 +08:00
|
|
|
func NewClient(conn *ssh.ClientConn) (*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
|
|
|
|
}
|
|
|
|
sftp := &Client{
|
|
|
|
w: pw,
|
|
|
|
r: pr,
|
|
|
|
}
|
|
|
|
if err := sftp.sendInit(); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
return sftp, sftp.recvVersion()
|
|
|
|
}
|
|
|
|
|
2013-11-06 10:04:40 +08:00
|
|
|
// 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.
|
2013-11-07 14:43:06 +08:00
|
|
|
//
|
|
|
|
// Client implements the github.com/kr/fs.FileSystem interface.
|
2013-11-05 19:16:36 +08:00
|
|
|
type Client struct {
|
|
|
|
w io.WriteCloser
|
|
|
|
r io.Reader
|
|
|
|
mu sync.Mutex // locks mu and seralises commands to the server
|
|
|
|
nextid uint32
|
|
|
|
}
|
|
|
|
|
2013-11-06 10:04:40 +08:00
|
|
|
// Close closes the SFTP session.
|
2013-11-05 19:16:36 +08:00
|
|
|
func (c *Client) Close() error { return c.w.Close() }
|
|
|
|
|
2013-11-06 10:04:40 +08:00
|
|
|
// 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) {
|
2013-11-06 16:10:28 +08:00
|
|
|
return c.open(path, ssh_FXF_READ|ssh_FXF_WRITE|ssh_FXF_CREAT|ssh_FXF_TRUNC)
|
2013-11-06 10:04:40 +08:00
|
|
|
}
|
|
|
|
|
2013-11-05 19:16:36 +08:00
|
|
|
func (c *Client) sendInit() error {
|
|
|
|
type packet struct {
|
|
|
|
Type byte
|
|
|
|
Version uint32
|
|
|
|
Extensions []struct {
|
|
|
|
Name, Data string
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return sendPacket(c.w, packet{
|
2013-11-06 16:10:28 +08:00
|
|
|
Type: ssh_FXP_INIT,
|
2013-11-05 19:16:36 +08:00
|
|
|
Version: 3, // 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, _, err := recvPacket(c.r)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2013-11-06 16:10:28 +08:00
|
|
|
if typ != ssh_FXP_VERSION {
|
|
|
|
return &unexpectedPacketErr{ssh_FXP_VERSION, typ}
|
2013-11-05 19:16:36 +08:00
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Walk returns a new Walker rooted at root.
|
2013-11-07 08:31:46 +08:00
|
|
|
func (c *Client) Walk(root string) *fs.Walker {
|
2013-11-07 14:23:51 +08:00
|
|
|
return fs.WalkFS(root, c)
|
2013-11-05 19:16:36 +08:00
|
|
|
}
|
|
|
|
|
2013-11-07 14:23:51 +08:00
|
|
|
// ReadDir reads the directory named by dirname and returns a list of
|
|
|
|
// directory entries.
|
|
|
|
func (c *Client) ReadDir(p string) ([]os.FileInfo, error) {
|
2013-11-06 12:40:35 +08:00
|
|
|
handle, err := c.opendir(p)
|
2013-11-05 19:16:36 +08:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2013-11-08 18:24:50 +08:00
|
|
|
defer c.close(handle) // this has to defer earlier than the lock below
|
2013-11-05 19:16:36 +08:00
|
|
|
var attrs []os.FileInfo
|
|
|
|
c.mu.Lock()
|
|
|
|
defer c.mu.Unlock()
|
|
|
|
var done = false
|
|
|
|
for !done {
|
|
|
|
type packet struct {
|
|
|
|
Type byte
|
|
|
|
Id uint32
|
|
|
|
Handle string
|
|
|
|
}
|
|
|
|
id := c.nextId()
|
2013-11-08 18:56:25 +08:00
|
|
|
typ, data, err1 := c.sendRequest(packet{
|
2013-11-06 16:10:28 +08:00
|
|
|
Type: ssh_FXP_READDIR,
|
2013-11-05 19:16:36 +08:00
|
|
|
Id: id,
|
|
|
|
Handle: handle,
|
2013-11-08 18:56:25 +08:00
|
|
|
})
|
|
|
|
if err1 != nil {
|
|
|
|
err = err1
|
|
|
|
done = true
|
|
|
|
break
|
2013-11-05 19:16:36 +08:00
|
|
|
}
|
|
|
|
switch typ {
|
2013-11-06 16:10:28 +08:00
|
|
|
case ssh_FXP_NAME:
|
2013-11-05 19:16:36 +08:00
|
|
|
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 *attr
|
|
|
|
attr, data = unmarshalAttrs(data)
|
|
|
|
if filename == "." || filename == ".." {
|
|
|
|
continue
|
|
|
|
}
|
2013-11-06 12:40:35 +08:00
|
|
|
attr.name = path.Base(filename)
|
2013-11-05 19:16:36 +08:00
|
|
|
attrs = append(attrs, attr)
|
|
|
|
}
|
2013-11-06 16:10:28 +08:00
|
|
|
case ssh_FXP_STATUS:
|
2013-11-08 18:56:25 +08:00
|
|
|
// TODO(dfc) scope warning!
|
|
|
|
err = eofOrErr(unmarshalStatus(id, data))
|
2013-11-05 19:16:36 +08:00
|
|
|
done = true
|
|
|
|
default:
|
|
|
|
return nil, unimplementedPacketErr(typ)
|
|
|
|
}
|
|
|
|
}
|
2013-11-08 18:56:25 +08:00
|
|
|
if err == io.EOF {
|
|
|
|
err = nil
|
|
|
|
}
|
2013-11-05 19:16:36 +08:00
|
|
|
return attrs, err
|
|
|
|
}
|
|
|
|
func (c *Client) opendir(path string) (string, error) {
|
|
|
|
type packet struct {
|
|
|
|
Type byte
|
|
|
|
Id uint32
|
|
|
|
Path string
|
|
|
|
}
|
|
|
|
c.mu.Lock()
|
|
|
|
defer c.mu.Unlock()
|
|
|
|
id := c.nextId()
|
2013-11-08 18:56:25 +08:00
|
|
|
typ, data, err := c.sendRequest(packet{
|
2013-11-06 16:10:28 +08:00
|
|
|
Type: ssh_FXP_OPENDIR,
|
2013-11-05 19:16:36 +08:00
|
|
|
Id: id,
|
|
|
|
Path: path,
|
2013-11-08 18:56:25 +08:00
|
|
|
})
|
2013-11-05 19:16:36 +08:00
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
switch typ {
|
2013-11-06 16:10:28 +08:00
|
|
|
case ssh_FXP_HANDLE:
|
2013-11-05 19:16:36 +08:00
|
|
|
sid, data := unmarshalUint32(data)
|
|
|
|
if sid != id {
|
|
|
|
return "", &unexpectedIdErr{id, sid}
|
|
|
|
}
|
|
|
|
handle, _ := unmarshalString(data)
|
|
|
|
return handle, nil
|
2013-11-06 16:10:28 +08:00
|
|
|
case ssh_FXP_STATUS:
|
2013-11-06 11:50:04 +08:00
|
|
|
return "", unmarshalStatus(id, data)
|
2013-11-05 19:16:36 +08:00
|
|
|
default:
|
|
|
|
return "", unimplementedPacketErr(typ)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-11-06 12:40:35 +08:00
|
|
|
func (c *Client) Lstat(p string) (os.FileInfo, error) {
|
2013-11-05 19:16:36 +08:00
|
|
|
type packet struct {
|
|
|
|
Type byte
|
|
|
|
Id uint32
|
|
|
|
Path string
|
|
|
|
}
|
|
|
|
c.mu.Lock()
|
|
|
|
defer c.mu.Unlock()
|
|
|
|
id := c.nextId()
|
2013-11-08 18:56:25 +08:00
|
|
|
typ, data, err := c.sendRequest(packet{
|
2013-11-06 16:10:28 +08:00
|
|
|
Type: ssh_FXP_LSTAT,
|
2013-11-05 19:16:36 +08:00
|
|
|
Id: id,
|
2013-11-06 12:40:35 +08:00
|
|
|
Path: p,
|
2013-11-08 18:56:25 +08:00
|
|
|
})
|
2013-11-05 19:16:36 +08:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
switch typ {
|
2013-11-06 16:10:28 +08:00
|
|
|
case ssh_FXP_ATTRS:
|
2013-11-05 19:16:36 +08:00
|
|
|
sid, data := unmarshalUint32(data)
|
|
|
|
if sid != id {
|
|
|
|
return nil, &unexpectedIdErr{id, sid}
|
|
|
|
}
|
|
|
|
attr, _ := unmarshalAttrs(data)
|
2013-11-06 12:40:35 +08:00
|
|
|
attr.name = path.Base(p)
|
2013-11-05 19:16:36 +08:00
|
|
|
return attr, nil
|
2013-11-06 16:10:28 +08:00
|
|
|
case ssh_FXP_STATUS:
|
2013-11-06 11:50:04 +08:00
|
|
|
return nil, unmarshalStatus(id, data)
|
2013-11-05 19:16:36 +08:00
|
|
|
default:
|
|
|
|
return nil, unimplementedPacketErr(typ)
|
|
|
|
}
|
|
|
|
}
|
2013-11-06 08:04:26 +08:00
|
|
|
|
|
|
|
// 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) {
|
2013-11-06 16:10:28 +08:00
|
|
|
return c.open(path, ssh_FXF_READ)
|
2013-11-06 09:53:45 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
func (c *Client) open(path string, pflags uint32) (*File, error) {
|
2013-11-06 08:04:26 +08:00
|
|
|
type packet struct {
|
|
|
|
Type byte
|
|
|
|
Id uint32
|
|
|
|
Path string
|
|
|
|
Pflags uint32
|
|
|
|
Flags uint32 // ignored
|
|
|
|
Size uint64 // ignored
|
|
|
|
}
|
|
|
|
c.mu.Lock()
|
|
|
|
defer c.mu.Unlock()
|
|
|
|
id := c.nextId()
|
2013-11-08 18:56:25 +08:00
|
|
|
typ, data, err := c.sendRequest(packet{
|
2013-11-06 16:10:28 +08:00
|
|
|
Type: ssh_FXP_OPEN,
|
2013-11-06 08:04:26 +08:00
|
|
|
Id: id,
|
|
|
|
Path: path,
|
2013-11-06 09:53:45 +08:00
|
|
|
Pflags: pflags,
|
2013-11-08 18:56:25 +08:00
|
|
|
})
|
2013-11-06 08:04:26 +08:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
switch typ {
|
2013-11-06 16:10:28 +08:00
|
|
|
case ssh_FXP_HANDLE:
|
2013-11-06 08:04:26 +08:00
|
|
|
sid, data := unmarshalUint32(data)
|
|
|
|
if sid != id {
|
|
|
|
return nil, &unexpectedIdErr{id, sid}
|
|
|
|
}
|
|
|
|
handle, _ := unmarshalString(data)
|
2013-11-06 09:36:05 +08:00
|
|
|
return &File{c: c, path: path, handle: handle}, nil
|
2013-11-06 16:10:28 +08:00
|
|
|
case ssh_FXP_STATUS:
|
2013-11-06 11:50:04 +08:00
|
|
|
return nil, unmarshalStatus(id, data)
|
2013-11-06 08:04:26 +08:00
|
|
|
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) {
|
|
|
|
type packet struct {
|
|
|
|
Type byte
|
|
|
|
Id uint32
|
|
|
|
Handle string
|
|
|
|
Offset uint64
|
|
|
|
Len uint32
|
|
|
|
}
|
|
|
|
c.mu.Lock()
|
|
|
|
defer c.mu.Unlock()
|
|
|
|
id := c.nextId()
|
2013-11-08 18:56:25 +08:00
|
|
|
typ, data, err := c.sendRequest(packet{
|
2013-11-06 16:10:28 +08:00
|
|
|
Type: ssh_FXP_READ,
|
2013-11-06 08:04:26 +08:00
|
|
|
Id: id,
|
|
|
|
Handle: handle,
|
|
|
|
Offset: offset,
|
|
|
|
Len: uint32(len(buf)),
|
2013-11-08 18:56:25 +08:00
|
|
|
})
|
2013-11-06 08:04:26 +08:00
|
|
|
if err != nil {
|
|
|
|
return 0, err
|
|
|
|
}
|
|
|
|
switch typ {
|
2013-11-06 16:10:28 +08:00
|
|
|
case ssh_FXP_DATA:
|
2013-11-06 08:04:26 +08:00
|
|
|
sid, data := unmarshalUint32(data)
|
|
|
|
if sid != id {
|
|
|
|
return 0, &unexpectedIdErr{id, sid}
|
|
|
|
}
|
2013-11-06 12:53:14 +08:00
|
|
|
l, data := unmarshalUint32(data)
|
|
|
|
n := copy(buf, data[:l])
|
2013-11-06 08:04:26 +08:00
|
|
|
return uint32(n), nil
|
2013-11-06 16:10:28 +08:00
|
|
|
case ssh_FXP_STATUS:
|
2013-11-06 12:53:14 +08:00
|
|
|
return 0, eofOrErr(unmarshalStatus(id, data))
|
2013-11-06 08:04:26 +08:00
|
|
|
default:
|
|
|
|
return 0, unimplementedPacketErr(typ)
|
|
|
|
}
|
|
|
|
}
|
2013-11-06 08:30:01 +08:00
|
|
|
|
|
|
|
// 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 {
|
|
|
|
type packet struct {
|
|
|
|
Type byte
|
|
|
|
Id uint32
|
|
|
|
Handle string
|
|
|
|
}
|
|
|
|
c.mu.Lock()
|
|
|
|
defer c.mu.Unlock()
|
|
|
|
id := c.nextId()
|
2013-11-08 18:56:25 +08:00
|
|
|
typ, data, err := c.sendRequest(packet{
|
2013-11-06 16:10:28 +08:00
|
|
|
Type: ssh_FXP_CLOSE,
|
2013-11-06 08:30:01 +08:00
|
|
|
Id: id,
|
|
|
|
Handle: handle,
|
2013-11-08 18:56:25 +08:00
|
|
|
})
|
2013-11-06 08:30:01 +08:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
switch typ {
|
2013-11-06 16:10:28 +08:00
|
|
|
case ssh_FXP_STATUS:
|
2013-11-06 11:50:04 +08:00
|
|
|
return okOrErr(unmarshalStatus(id, data))
|
2013-11-06 08:30:01 +08:00
|
|
|
default:
|
|
|
|
return unimplementedPacketErr(typ)
|
|
|
|
}
|
|
|
|
}
|
2013-11-06 09:36:05 +08:00
|
|
|
|
|
|
|
func (c *Client) fstat(handle string) (*attr, error) {
|
|
|
|
type packet struct {
|
|
|
|
Type byte
|
|
|
|
Id uint32
|
|
|
|
Handle string
|
|
|
|
}
|
|
|
|
c.mu.Lock()
|
|
|
|
defer c.mu.Unlock()
|
|
|
|
id := c.nextId()
|
2013-11-08 18:56:25 +08:00
|
|
|
typ, data, err := c.sendRequest(packet{
|
2013-11-06 16:10:28 +08:00
|
|
|
Type: ssh_FXP_FSTAT,
|
2013-11-06 09:36:05 +08:00
|
|
|
Id: id,
|
|
|
|
Handle: handle,
|
2013-11-08 18:56:25 +08:00
|
|
|
})
|
2013-11-06 09:36:05 +08:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
switch typ {
|
2013-11-06 16:10:28 +08:00
|
|
|
case ssh_FXP_ATTRS:
|
2013-11-06 09:36:05 +08:00
|
|
|
sid, data := unmarshalUint32(data)
|
|
|
|
if sid != id {
|
|
|
|
return nil, &unexpectedIdErr{id, sid}
|
|
|
|
}
|
|
|
|
attr, _ := unmarshalAttrs(data)
|
|
|
|
return attr, nil
|
2013-11-06 16:10:28 +08:00
|
|
|
case ssh_FXP_STATUS:
|
2013-11-06 11:50:04 +08:00
|
|
|
return nil, unmarshalStatus(id, data)
|
2013-11-06 09:36:05 +08:00
|
|
|
default:
|
|
|
|
return nil, unimplementedPacketErr(typ)
|
|
|
|
}
|
|
|
|
}
|
2013-11-06 09:42:14 +08:00
|
|
|
|
2013-11-07 14:23:51 +08:00
|
|
|
// 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...) }
|
|
|
|
|
2013-11-06 11:08:26 +08:00
|
|
|
// Remove removes the named file or directory.
|
|
|
|
func (c *Client) Remove(path string) error {
|
|
|
|
// TODO(dfc) can't handle directories, yet
|
|
|
|
type packet struct {
|
|
|
|
Type byte
|
|
|
|
Id uint32
|
|
|
|
Filename string
|
|
|
|
}
|
|
|
|
c.mu.Lock()
|
|
|
|
defer c.mu.Unlock()
|
|
|
|
id := c.nextId()
|
2013-11-08 18:56:25 +08:00
|
|
|
typ, data, err := c.sendRequest(packet{
|
2013-11-06 16:10:28 +08:00
|
|
|
Type: ssh_FXP_REMOVE,
|
2013-11-06 11:08:26 +08:00
|
|
|
Id: id,
|
|
|
|
Filename: path,
|
2013-11-08 18:56:25 +08:00
|
|
|
})
|
2013-11-06 11:08:26 +08:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
switch typ {
|
2013-11-06 16:10:28 +08:00
|
|
|
case ssh_FXP_STATUS:
|
2013-11-06 11:50:04 +08:00
|
|
|
return okOrErr(unmarshalStatus(id, data))
|
2013-11-06 11:08:26 +08:00
|
|
|
default:
|
|
|
|
return unimplementedPacketErr(typ)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-11-06 11:15:26 +08:00
|
|
|
// Rename renames a file.
|
|
|
|
func (c *Client) Rename(oldname, newname string) error {
|
|
|
|
type packet struct {
|
|
|
|
Type byte
|
|
|
|
Id uint32
|
|
|
|
Oldpath, Newpath string
|
|
|
|
}
|
|
|
|
c.mu.Lock()
|
|
|
|
defer c.mu.Unlock()
|
|
|
|
id := c.nextId()
|
2013-11-08 18:56:25 +08:00
|
|
|
typ, data, err := c.sendRequest(packet{
|
2013-11-06 16:10:28 +08:00
|
|
|
Type: ssh_FXP_RENAME,
|
2013-11-06 11:15:26 +08:00
|
|
|
Id: id,
|
|
|
|
Oldpath: oldname,
|
|
|
|
Newpath: newname,
|
2013-11-08 18:56:25 +08:00
|
|
|
})
|
2013-11-06 11:15:26 +08:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
switch typ {
|
2013-11-06 16:10:28 +08:00
|
|
|
case ssh_FXP_STATUS:
|
2013-11-06 11:15:26 +08:00
|
|
|
return okOrErr(unmarshalStatus(id, data))
|
|
|
|
default:
|
|
|
|
return unimplementedPacketErr(typ)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-11-08 18:56:25 +08:00
|
|
|
func (c *Client) sendRequest(p interface{}) (byte, []byte, error) {
|
|
|
|
if err := sendPacket(c.w, p); err != nil {
|
|
|
|
return 0, nil, err
|
|
|
|
}
|
|
|
|
return recvPacket(c.r)
|
|
|
|
}
|
|
|
|
|
2013-11-06 10:04:40 +08:00
|
|
|
// 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) {
|
|
|
|
type packet struct {
|
|
|
|
Type byte
|
|
|
|
Id uint32
|
|
|
|
Handle string
|
|
|
|
Offset uint64
|
2013-11-06 13:09:06 +08:00
|
|
|
Length uint32
|
2013-11-06 10:04:40 +08:00
|
|
|
Data []byte
|
|
|
|
}
|
|
|
|
c.mu.Lock()
|
|
|
|
defer c.mu.Unlock()
|
|
|
|
id := c.nextId()
|
2013-11-08 18:56:25 +08:00
|
|
|
typ, data, err := c.sendRequest(packet{
|
2013-11-06 16:10:28 +08:00
|
|
|
Type: ssh_FXP_WRITE,
|
2013-11-06 10:04:40 +08:00
|
|
|
Id: id,
|
|
|
|
Handle: handle,
|
|
|
|
Offset: offset,
|
2013-11-06 13:09:06 +08:00
|
|
|
Length: uint32(len(buf)),
|
2013-11-06 10:04:40 +08:00
|
|
|
Data: buf,
|
2013-11-08 18:56:25 +08:00
|
|
|
})
|
2013-11-06 10:04:40 +08:00
|
|
|
if err != nil {
|
|
|
|
return 0, err
|
|
|
|
}
|
|
|
|
switch typ {
|
2013-11-06 16:10:28 +08:00
|
|
|
case ssh_FXP_STATUS:
|
2013-11-06 11:50:04 +08:00
|
|
|
if err := okOrErr(unmarshalStatus(id, data)); err != nil {
|
|
|
|
return 0, nil
|
2013-11-06 10:04:40 +08:00
|
|
|
}
|
|
|
|
return uint32(len(buf)), nil
|
|
|
|
default:
|
|
|
|
return 0, unimplementedPacketErr(typ)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-11-06 09:42:14 +08:00
|
|
|
// File represents a remote file.
|
|
|
|
type File struct {
|
2013-11-06 09:53:45 +08:00
|
|
|
c *Client
|
|
|
|
path string
|
|
|
|
handle string
|
|
|
|
offset uint64 // current offset within remote file
|
2013-11-06 09:42:14 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
// Close closes the File, rendering it unusable for I/O. It returns an
|
|
|
|
// error, if any.
|
|
|
|
func (f *File) Close() error {
|
2013-11-06 09:53:45 +08:00
|
|
|
return f.c.close(f.handle)
|
2013-11-06 09:42:14 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
// 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) {
|
2013-11-06 09:53:45 +08:00
|
|
|
n, err := f.c.readAt(f.handle, f.offset, b)
|
|
|
|
f.offset += uint64(n)
|
|
|
|
return int(n), err
|
2013-11-06 09:42:14 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
// ReadAt reads len(b) bytes from the File starting at byte offset off. It
|
|
|
|
// returns the number of bytes read and the error, if any. ReadAt always
|
|
|
|
// returns a non-nil error when n < len(b). At end of file, that error is
|
|
|
|
// io.EOF.
|
|
|
|
func (f *File) ReadAt(b []byte, off int64) (int, error) {
|
2013-11-06 09:53:45 +08:00
|
|
|
n, err := f.c.readAt(f.handle, uint64(off), b)
|
|
|
|
return int(n), err
|
2013-11-06 09:42:14 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
// Stat returns the FileInfo structure describing file. If there is an
|
2013-11-06 16:48:37 +08:00
|
|
|
// error.
|
2013-11-06 09:42:14 +08:00
|
|
|
func (f *File) Stat() (os.FileInfo, error) {
|
2013-11-06 09:53:45 +08:00
|
|
|
fi, err := f.c.fstat(f.handle)
|
|
|
|
if err == nil {
|
2013-11-06 13:03:08 +08:00
|
|
|
fi.name = path.Base(f.path)
|
2013-11-06 09:53:45 +08:00
|
|
|
}
|
|
|
|
return fi, err
|
2013-11-06 09:42:14 +08:00
|
|
|
}
|
|
|
|
|
2013-11-08 18:24:50 +08:00
|
|
|
// clamp writes to less than 32k
|
|
|
|
const maxWritePacket = 1 << 15
|
|
|
|
|
2013-11-06 10:04:40 +08:00
|
|
|
// 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) {
|
2013-11-08 18:24:50 +08:00
|
|
|
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
|
|
|
|
}
|
|
|
|
|
|
|
|
func min(a, b int) int {
|
|
|
|
if a > b {
|
|
|
|
return b
|
|
|
|
}
|
|
|
|
return a
|
2013-11-06 10:04:40 +08:00
|
|
|
}
|
|
|
|
|
2013-11-06 11:08:26 +08:00
|
|
|
// okOrErr returns nil if Err.Code is SSH_FX_OK, otherwise it returns the error.
|
2013-11-06 11:15:26 +08:00
|
|
|
func okOrErr(err error) error {
|
2013-11-06 16:10:28 +08:00
|
|
|
if err, ok := err.(*StatusError); ok && err.Code == ssh_FX_OK {
|
2013-11-06 11:08:26 +08:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
return err
|
|
|
|
}
|
2013-11-06 11:15:26 +08:00
|
|
|
|
2013-11-06 12:00:04 +08:00
|
|
|
func eofOrErr(err error) error {
|
2013-11-06 16:10:28 +08:00
|
|
|
if err, ok := err.(*StatusError); ok && err.Code == ssh_FX_EOF {
|
2013-11-06 12:00:04 +08:00
|
|
|
return io.EOF
|
|
|
|
}
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2013-11-06 11:15:26 +08:00
|
|
|
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,
|
|
|
|
}
|
|
|
|
}
|