summaryrefslogtreecommitdiffstats
path: root/vendor/github.com/pkg/sftp/client.go
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/github.com/pkg/sftp/client.go')
-rw-r--r--vendor/github.com/pkg/sftp/client.go1174
1 files changed, 0 insertions, 1174 deletions
diff --git a/vendor/github.com/pkg/sftp/client.go b/vendor/github.com/pkg/sftp/client.go
deleted file mode 100644
index c90d0b23..00000000
--- a/vendor/github.com/pkg/sftp/client.go
+++ /dev/null
@@ -1,1174 +0,0 @@
-package sftp
-
-import (
- "bytes"
- "encoding/binary"
- "io"
- "os"
- "path"
- "sync/atomic"
- "time"
-
- "github.com/kr/fs"
- "github.com/pkg/errors"
- "golang.org/x/crypto/ssh"
-)
-
-// InternalInconsistency indicates the packets sent and the data queued to be
-// written to the file don't match up. It is an unusual error and usually is
-// caused by bad behavior server side or connection issues. The error is
-// limited in scope to the call where it happened, the client object is still
-// OK to use as long as the connection is still open.
-var InternalInconsistency = errors.New("internal inconsistency")
-
-// A ClientOption is a function which applies configuration to a Client.
-type ClientOption func(*Client) error
-
-// This is based on Openssh's max accepted size of 1<<18 - overhead
-const maxMaxPacket = (1 << 18) - 1024
-
-// MaxPacket sets the maximum size of the payload. The size param must be
-// between 32768 (1<<15) and 261120 ((1 << 18) - 1024). The minimum size is
-// given by the RFC, while the maximum size is a de-facto standard based on
-// Openssh's SFTP server which won't accept packets much larger than that.
-//
-// Note if you aren't using Openssh's sftp server and get the error "failed to
-// send packet header: EOF" when copying a large file try lowering this number.
-func MaxPacket(size int) ClientOption {
- return func(c *Client) error {
- if size < 1<<15 {
- return errors.Errorf("size must be greater or equal to 32k")
- }
- if size > maxMaxPacket {
- return errors.Errorf("max packet size is too large (see docs)")
- }
- c.maxPacket = size
- return nil
- }
-}
-
-// NewClient creates a new SFTP client on conn, using zero or more option
-// functions.
-func NewClient(conn *ssh.Client, opts ...ClientOption) (*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, opts...)
-}
-
-// 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, opts ...ClientOption) (*Client, error) {
- sftp := &Client{
- 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()
- return nil, err
- }
- if err := sftp.sendInit(); err != nil {
- wr.Close()
- return nil, err
- }
- if err := sftp.recvVersion(); err != nil {
- wr.Close()
- return nil, err
- }
- sftp.clientConn.wg.Add(1)
- go sftp.loop()
- return sftp, nil
-}
-
-// 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 {
- clientConn
-
- maxPacket int // max packet size read or written.
- nextid uint32
-}
-
-// 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. If you need more
-// control over the flags/mode used to open the file see client.OpenFile.
-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 c.clientConn.conn.sendPacket(sshFxInitPacket{
- Version: sftpProtocolVersion, // http://tools.ietf.org/html/draft-ietf-secsh-filexfer-02
- })
-}
-
-// returns the next value of c.nextid
-func (c *Client) nextID() uint32 {
- return atomic.AddUint32(&c.nextid, 1)
-}
-
-func (c *Client) recvVersion() error {
- typ, data, err := c.recvPacket()
- 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
- var done = false
- for !done {
- id := c.nextID()
- typ, data, err1 := c.sendPacket(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 = normaliseError(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) {
- id := c.nextID()
- typ, data, err := c.sendPacket(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 "", normaliseError(unmarshalStatus(id, data))
- default:
- return "", unimplementedPacketErr(typ)
- }
-}
-
-// Stat returns a FileInfo structure describing the file specified by path 'p'.
-// 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.sendPacket(sshFxpStatPacket{
- 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, normaliseError(unmarshalStatus(id, data))
- default:
- return nil, unimplementedPacketErr(typ)
- }
-}
-
-// Lstat returns a FileInfo structure describing the file specified by path 'p'.
-// 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.sendPacket(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, normaliseError(unmarshalStatus(id, data))
- default:
- return nil, unimplementedPacketErr(typ)
- }
-}
-
-// ReadLink reads the target of a symbolic link.
-func (c *Client) ReadLink(p string) (string, error) {
- id := c.nextID()
- typ, data, err := c.sendPacket(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 "", normaliseError(unmarshalStatus(id, data))
- default:
- return "", unimplementedPacketErr(typ)
- }
-}
-
-// 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.sendPacket(sshFxpSymlinkPacket{
- ID: id,
- Linkpath: newname,
- Targetpath: oldname,
- })
- if err != nil {
- return err
- }
- switch typ {
- case ssh_FXP_STATUS:
- return normaliseError(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 {
- id := c.nextID()
- typ, data, err := c.sendPacket(sshFxpSetstatPacket{
- ID: id,
- Path: path,
- Flags: flags,
- Attrs: attrs,
- })
- if err != nil {
- return err
- }
- switch typ {
- case ssh_FXP_STATUS:
- return normaliseError(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) {
- id := c.nextID()
- typ, data, err := c.sendPacket(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, normaliseError(unmarshalStatus(id, data))
- default:
- return nil, 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 {
- id := c.nextID()
- typ, data, err := c.sendPacket(sshFxpClosePacket{
- ID: id,
- Handle: handle,
- })
- if err != nil {
- return err
- }
- switch typ {
- case ssh_FXP_STATUS:
- return normaliseError(unmarshalStatus(id, data))
- default:
- return unimplementedPacketErr(typ)
- }
-}
-
-func (c *Client) fstat(handle string) (*FileStat, error) {
- id := c.nextID()
- typ, data, err := c.sendPacket(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, normaliseError(unmarshalStatus(id, data))
- default:
- return nil, unimplementedPacketErr(typ)
- }
-}
-
-// StatVFS retrieves VFS statistics from a remote host.
-//
-// It implements the statvfs@openssh.com SSH_FXP_EXTENDED feature
-// from http://www.opensource.apple.com/source/OpenSSH/OpenSSH-175/openssh/PROTOCOL?txt.
-func (c *Client) StatVFS(path string) (*StatVFS, error) {
- // send the StatVFS packet to the server
- id := c.nextID()
- typ, data, err := c.sendPacket(sshFxpStatvfsPacket{
- ID: id,
- Path: path,
- })
- if err != nil {
- return nil, err
- }
-
- switch typ {
- // server responded with valid data
- case ssh_FXP_EXTENDED_REPLY:
- var response StatVFS
- err = binary.Read(bytes.NewReader(data), binary.BigEndian, &response)
- if err != nil {
- return nil, errors.New("can not parse reply")
- }
-
- return &response, nil
-
- // the resquest failed
- case ssh_FXP_STATUS:
- return nil, errors.New(fxp(ssh_FXP_STATUS).String())
-
- 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 err, ok := err.(*StatusError); ok {
- switch err.Code {
- // some servers, *cough* osx *cough*, return EPERM, not ENODIR.
- // serv-u returns ssh_FX_FILE_IS_A_DIRECTORY
- case ssh_FX_PERMISSION_DENIED, ssh_FX_FAILURE, ssh_FX_FILE_IS_A_DIRECTORY:
- return c.RemoveDirectory(path)
- }
- }
- return err
-}
-
-func (c *Client) removeFile(path string) error {
- id := c.nextID()
- typ, data, err := c.sendPacket(sshFxpRemovePacket{
- ID: id,
- Filename: path,
- })
- if err != nil {
- return err
- }
- switch typ {
- case ssh_FXP_STATUS:
- return normaliseError(unmarshalStatus(id, data))
- default:
- return unimplementedPacketErr(typ)
- }
-}
-
-// RemoveDirectory removes a directory path.
-func (c *Client) RemoveDirectory(path string) error {
- id := c.nextID()
- typ, data, err := c.sendPacket(sshFxpRmdirPacket{
- ID: id,
- Path: path,
- })
- if err != nil {
- return err
- }
- switch typ {
- case ssh_FXP_STATUS:
- return normaliseError(unmarshalStatus(id, data))
- default:
- return unimplementedPacketErr(typ)
- }
-}
-
-// Rename renames a file.
-func (c *Client) Rename(oldname, newname string) error {
- id := c.nextID()
- typ, data, err := c.sendPacket(sshFxpRenamePacket{
- ID: id,
- Oldpath: oldname,
- Newpath: newname,
- })
- if err != nil {
- return err
- }
- switch typ {
- case ssh_FXP_STATUS:
- return normaliseError(unmarshalStatus(id, data))
- default:
- return unimplementedPacketErr(typ)
- }
-}
-
-// PosixRename renames a file using the posix-rename@openssh.com extension
-// which will replace newname if it already exists.
-func (c *Client) PosixRename(oldname, newname string) error {
- id := c.nextID()
- typ, data, err := c.sendPacket(sshFxpPosixRenamePacket{
- ID: id,
- Oldpath: oldname,
- Newpath: newname,
- })
- if err != nil {
- return err
- }
- switch typ {
- case ssh_FXP_STATUS:
- return normaliseError(unmarshalStatus(id, data))
- default:
- return unimplementedPacketErr(typ)
- }
-}
-
-func (c *Client) realpath(path string) (string, error) {
- id := c.nextID()
- typ, data, err := c.sendPacket(sshFxpRealpathPacket{
- ID: id,
- Path: path,
- })
- 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 attributes
- return filename, nil
- case ssh_FXP_STATUS:
- return "", normaliseError(unmarshalStatus(id, data))
- default:
- return "", unimplementedPacketErr(typ)
- }
-}
-
-// Getwd returns the current working directory of the server. Operations
-// involving relative paths will be based at this location.
-func (c *Client) Getwd() (string, error) {
- return c.realpath(".")
-}
-
-// 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.sendPacket(sshFxpMkdirPacket{
- ID: id,
- Path: path,
- })
- if err != nil {
- return err
- }
- switch typ {
- case ssh_FXP_STATUS:
- return normaliseError(unmarshalStatus(id, data))
- default:
- return unimplementedPacketErr(typ)
- }
-}
-
-// applyOptions applies options functions to the Client.
-// If an error is encountered, option processing ceases.
-func (c *Client) applyOptions(opts ...ClientOption) error {
- for _, f := range opts {
- if err := f(c); err != nil {
- return err
- }
- }
- return nil
-}
-
-// 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)
-}
-
-// Name returns the name of the file as presented to Open or Create.
-func (f *File) Name() string {
- return f.path
-}
-
-const maxConcurrentRequests = 64
-
-// Read reads up to len(b) bytes from the File. It returns the number of bytes
-// read and an error, if any. Read follows io.Reader semantics, so when Read
-// encounters an error or EOF condition after successfully reading n > 0 bytes,
-// it returns the number of bytes read.
-func (f *File) Read(b []byte) (int, error) {
- // Split the read into multiple maxPacket sized concurrent reads
- // bounded by maxConcurrentRequests. This allows reads with a suitably
- // large buffer to transfer data at a much faster rate due to
- // overlapping round trip times.
- inFlight := 0
- desiredInFlight := 1
- offset := f.offset
- // maxConcurrentRequests buffer to deal with broadcastErr() floods
- // also must have a buffer of max value of (desiredInFlight - inFlight)
- ch := make(chan result, maxConcurrentRequests)
- type inflightRead struct {
- b []byte
- offset uint64
- }
- reqs := map[uint32]inflightRead{}
- type offsetErr struct {
- offset uint64
- err error
- }
- var firstErr offsetErr
-
- sendReq := func(b []byte, offset uint64) {
- reqID := f.c.nextID()
- f.c.dispatchRequest(ch, sshFxpReadPacket{
- ID: reqID,
- Handle: f.handle,
- Offset: offset,
- Len: uint32(len(b)),
- })
- inFlight++
- reqs[reqID] = inflightRead{b: b, offset: offset}
- }
-
- var read int
- for len(b) > 0 || inFlight > 0 {
- for inFlight < desiredInFlight && len(b) > 0 && firstErr.err == nil {
- l := min(len(b), f.c.maxPacket)
- rb := b[:l]
- sendReq(rb, offset)
- offset += uint64(l)
- b = b[l:]
- }
-
- if inFlight == 0 {
- break
- }
- res := <-ch
- inFlight--
- if res.err != nil {
- firstErr = offsetErr{offset: 0, err: res.err}
- continue
- }
- reqID, data := unmarshalUint32(res.data)
- req, ok := reqs[reqID]
- if !ok {
- firstErr = offsetErr{offset: 0, err: errors.Errorf("sid: %v not found", reqID)}
- continue
- }
- delete(reqs, reqID)
- switch res.typ {
- case ssh_FXP_STATUS:
- if firstErr.err == nil || req.offset < firstErr.offset {
- firstErr = offsetErr{
- offset: req.offset,
- err: normaliseError(unmarshalStatus(reqID, res.data)),
- }
- }
- case ssh_FXP_DATA:
- l, data := unmarshalUint32(data)
- n := copy(req.b, data[:l])
- read += n
- if n < len(req.b) {
- sendReq(req.b[l:], req.offset+uint64(l))
- }
- if desiredInFlight < maxConcurrentRequests {
- desiredInFlight++
- }
- default:
- firstErr = offsetErr{offset: 0, err: unimplementedPacketErr(res.typ)}
- }
- }
- // If the error is anything other than EOF, then there
- // may be gaps in the data copied to the buffer so it's
- // best to return 0 so the caller can't make any
- // incorrect assumptions about the state of the buffer.
- if firstErr.err != nil && firstErr.err != io.EOF {
- read = 0
- }
- f.offset += uint64(read)
- return read, firstErr.err
-}
-
-// WriteTo writes the file to w. The return value is the number of bytes
-// written. Any error encountered during the write is also returned.
-func (f *File) WriteTo(w io.Writer) (int64, error) {
- fi, err := f.Stat()
- if err != nil {
- return 0, err
- }
- inFlight := 0
- desiredInFlight := 1
- offset := f.offset
- writeOffset := offset
- fileSize := uint64(fi.Size())
- // see comment on same line in Read() above
- ch := make(chan result, maxConcurrentRequests)
- type inflightRead struct {
- b []byte
- offset uint64
- }
- reqs := map[uint32]inflightRead{}
- pendingWrites := map[uint64][]byte{}
- type offsetErr struct {
- offset uint64
- err error
- }
- var firstErr offsetErr
-
- sendReq := func(b []byte, offset uint64) {
- reqID := f.c.nextID()
- f.c.dispatchRequest(ch, sshFxpReadPacket{
- ID: reqID,
- Handle: f.handle,
- Offset: offset,
- Len: uint32(len(b)),
- })
- inFlight++
- reqs[reqID] = inflightRead{b: b, offset: offset}
- }
-
- var copied int64
- for firstErr.err == nil || inFlight > 0 {
- if firstErr.err == nil {
- for inFlight+len(pendingWrites) < desiredInFlight {
- b := make([]byte, f.c.maxPacket)
- sendReq(b, offset)
- offset += uint64(f.c.maxPacket)
- if offset > fileSize {
- desiredInFlight = 1
- }
- }
- }
-
- if inFlight == 0 {
- if firstErr.err == nil && len(pendingWrites) > 0 {
- return copied, InternalInconsistency
- }
- break
- }
- res := <-ch
- inFlight--
- if res.err != nil {
- firstErr = offsetErr{offset: 0, err: res.err}
- continue
- }
- reqID, data := unmarshalUint32(res.data)
- req, ok := reqs[reqID]
- if !ok {
- firstErr = offsetErr{offset: 0, err: errors.Errorf("sid: %v not found", reqID)}
- continue
- }
- delete(reqs, reqID)
- switch res.typ {
- case ssh_FXP_STATUS:
- if firstErr.err == nil || req.offset < firstErr.offset {
- firstErr = offsetErr{offset: req.offset, err: normaliseError(unmarshalStatus(reqID, res.data))}
- }
- case ssh_FXP_DATA:
- l, data := unmarshalUint32(data)
- if req.offset == writeOffset {
- nbytes, err := w.Write(data)
- copied += int64(nbytes)
- if err != nil {
- // We will never receive another DATA with offset==writeOffset, so
- // the loop will drain inFlight and then exit.
- firstErr = offsetErr{offset: req.offset + uint64(nbytes), err: err}
- break
- }
- if nbytes < int(l) {
- firstErr = offsetErr{offset: req.offset + uint64(nbytes), err: io.ErrShortWrite}
- break
- }
- switch {
- case offset > fileSize:
- desiredInFlight = 1
- case desiredInFlight < maxConcurrentRequests:
- desiredInFlight++
- }
- writeOffset += uint64(nbytes)
- for {
- pendingData, ok := pendingWrites[writeOffset]
- if !ok {
- break
- }
- // Give go a chance to free the memory.
- delete(pendingWrites, writeOffset)
- nbytes, err := w.Write(pendingData)
- // Do not move writeOffset on error so subsequent iterations won't trigger
- // any writes.
- if err != nil {
- firstErr = offsetErr{offset: writeOffset + uint64(nbytes), err: err}
- break
- }
- if nbytes < len(pendingData) {
- firstErr = offsetErr{offset: writeOffset + uint64(nbytes), err: io.ErrShortWrite}
- break
- }
- writeOffset += uint64(nbytes)
- }
- } else {
- // Don't write the data yet because
- // this response came in out of order
- // and we need to wait for responses
- // for earlier segments of the file.
- pendingWrites[req.offset] = data
- }
- default:
- firstErr = offsetErr{offset: 0, err: unimplementedPacketErr(res.typ)}
- }
- }
- if firstErr.err != io.EOF {
- return copied, firstErr.err
- }
- return copied, 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
-}
-
-// 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) {
- // Split the write into multiple maxPacket sized concurrent writes
- // bounded by maxConcurrentRequests. This allows writes with a suitably
- // large buffer to transfer data at a much faster rate due to
- // overlapping round trip times.
- inFlight := 0
- desiredInFlight := 1
- offset := f.offset
- // see comment on same line in Read() above
- ch := make(chan result, maxConcurrentRequests)
- var firstErr error
- written := len(b)
- for len(b) > 0 || inFlight > 0 {
- for inFlight < desiredInFlight && len(b) > 0 && firstErr == nil {
- l := min(len(b), f.c.maxPacket)
- rb := b[:l]
- f.c.dispatchRequest(ch, sshFxpWritePacket{
- ID: f.c.nextID(),
- Handle: f.handle,
- Offset: offset,
- Length: uint32(len(rb)),
- Data: rb,
- })
- inFlight++
- offset += uint64(l)
- b = b[l:]
- }
-
- if inFlight == 0 {
- break
- }
- res := <-ch
- inFlight--
- if res.err != nil {
- firstErr = res.err
- continue
- }
- switch res.typ {
- case ssh_FXP_STATUS:
- id, _ := unmarshalUint32(res.data)
- err := normaliseError(unmarshalStatus(id, res.data))
- if err != nil && firstErr == nil {
- firstErr = err
- break
- }
- if desiredInFlight < maxConcurrentRequests {
- desiredInFlight++
- }
- default:
- firstErr = unimplementedPacketErr(res.typ)
- }
- }
- // If error is non-nil, then there may be gaps in the data written to
- // the file so it's best to return 0 so the caller can't make any
- // incorrect assumptions about the state of the file.
- if firstErr != nil {
- written = 0
- }
- f.offset += uint64(written)
- return written, firstErr
-}
-
-// ReadFrom reads data from r until EOF and writes it to the file. The return
-// value is the number of bytes read. Any error except io.EOF encountered
-// during the read is also returned.
-func (f *File) ReadFrom(r io.Reader) (int64, error) {
- inFlight := 0
- desiredInFlight := 1
- offset := f.offset
- // see comment on same line in Read() above
- ch := make(chan result, maxConcurrentRequests)
- var firstErr error
- read := int64(0)
- b := make([]byte, f.c.maxPacket)
- for inFlight > 0 || firstErr == nil {
- for inFlight < desiredInFlight && firstErr == nil {
- n, err := r.Read(b)
- if err != nil {
- firstErr = err
- }
- f.c.dispatchRequest(ch, sshFxpWritePacket{
- ID: f.c.nextID(),
- Handle: f.handle,
- Offset: offset,
- Length: uint32(n),
- Data: b[:n],
- })
- inFlight++
- offset += uint64(n)
- read += int64(n)
- }
-
- if inFlight == 0 {
- break
- }
- res := <-ch
- inFlight--
- if res.err != nil {
- firstErr = res.err
- continue
- }
- switch res.typ {
- case ssh_FXP_STATUS:
- id, _ := unmarshalUint32(res.data)
- err := normaliseError(unmarshalStatus(id, res.data))
- if err != nil && firstErr == nil {
- firstErr = err
- break
- }
- if desiredInFlight < maxConcurrentRequests {
- desiredInFlight++
- }
- default:
- firstErr = unimplementedPacketErr(res.typ)
- }
- }
- if firstErr == io.EOF {
- firstErr = nil
- }
- // If error is non-nil, then there may be gaps in the data written to
- // the file so it's best to return 0 so the caller can't make any
- // incorrect assumptions about the state of the file.
- if firstErr != nil {
- read = 0
- }
- f.offset += uint64(read)
- return read, firstErr
-}
-
-// 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 io.SeekStart:
- f.offset = uint64(offset)
- case io.SeekCurrent:
- f.offset = uint64(int64(f.offset) + offset)
- case io.SeekEnd:
- 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
-}
-
-// normaliseError normalises an error into a more standard form that can be
-// checked against stdlib errors like io.EOF or os.ErrNotExist.
-func normaliseError(err error) error {
- switch err := err.(type) {
- case *StatusError:
- switch err.Code {
- case ssh_FX_EOF:
- return io.EOF
- case ssh_FX_NO_SUCH_FILE:
- return os.ErrNotExist
- case ssh_FX_OK:
- return nil
- default:
- return err
- }
- default:
- 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, _ := unmarshalStringSafe(data)
- lang, _, _ := unmarshalStringSafe(data)
- return &StatusError{
- Code: code,
- msg: msg,
- lang: lang,
- }
-}
-
-func marshalStatus(b []byte, err StatusError) []byte {
- b = marshalUint32(b, err.Code)
- b = marshalString(b, err.msg)
- b = marshalString(b, err.lang)
- return b
-}
-
-// 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
-}