package sftp import ( "bytes" "encoding/binary" "io" "os" "path" "sync/atomic" "syscall" "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 // MaxPacketChecked sets the maximum size of the payload, measured in bytes.// This option only accepts sizes servers should support, ie. <= 32768 bytes.//// If you get the error "failed to send packet header: EOF" when copying a// large file, try lowering this number.//// The default packet size is 32768 bytes.func MaxPacketChecked(size int) ClientOption { return func(c *Client) error { if size < 1 { return errors.Errorf("size must be greater or equal to 1") } if size > 32768 { return errors.Errorf("sizes larger than 32KB might not work with all servers") } c.maxPacket = size return nil }} // MaxPacketUnchecked sets the maximum size of the payload, measured in bytes.// It accepts sizes larger than the 32768 bytes all servers should support.// Only use a setting higher than 32768 if your application always connects to// the same server or after sufficiently broad testing.//// If you get the error "failed to send packet header: EOF" when copying a// large file, try lowering this number.//// The default packet size is 32768 bytes.func MaxPacketUnchecked(size int) ClientOption { return func(c *Client) error { if size < 1 { return errors.Errorf("size must be greater or equal to 1") } c.maxPacket = size return nil }} // MaxPacket sets the maximum size of the payload, measured in bytes.// This option only accepts sizes servers should support, ie. <= 32768 bytes.// This is a synonym for MaxPacketChecked that provides backward compatibility.//// If you get the error "failed to send packet header: EOF" when copying a// large file, try lowering this number.//// The default packet size is 32768 bytes.func MaxPacket(size int) ClientOption { return MaxPacketChecked(size)} // MaxConcurrentRequestsPerFile sets the maximum concurrent requests allowed for a single file.//// The default maximum concurrent requests is 64.func MaxConcurrentRequestsPerFile(n int) ClientOption { return func(c *Client) error { if n < 1 { return errors.Errorf("n must be greater or equal to 1") } c.maxConcurrentRequests = n 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, maxConcurrentRequests: 64, } 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 maxConcurrentRequests int} // 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.nextidfunc (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 {Lines 1–500 of 1282. Load more
package sftp import ( "bytes" "encoding/binary" "io" "os" "path" "sync/atomic" "syscall" "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 // MaxPacketChecked sets the maximum size of the payload, measured in bytes. // This option only accepts sizes servers should support, ie. <= 32768 bytes. // // If you get the error "failed to send packet header: EOF" when copying a // large file, try lowering this number. // // The default packet size is 32768 bytes. func MaxPacketChecked(size int) ClientOption { return func(c *Client) error { if size < 1 { return errors.Errorf("size must be greater or equal to 1") } if size > 32768 { return errors.Errorf("sizes larger than 32KB might not work with all servers") } c.maxPacket = size return nil } } // MaxPacketUnchecked sets the maximum size of the payload, measured in bytes. // It accepts sizes larger than the 32768 bytes all servers should support. // Only use a setting higher than 32768 if your application always connects to // the same server or after sufficiently broad testing. // // If you get the error "failed to send packet header: EOF" when copying a // large file, try lowering this number. // // The default packet size is 32768 bytes. func MaxPacketUnchecked(size int) ClientOption { return func(c *Client) error { if size < 1 { return errors.Errorf("size must be greater or equal to 1") } c.maxPacket = size return nil } } // MaxPacket sets the maximum size of the payload, measured in bytes. // This option only accepts sizes servers should support, ie. <= 32768 bytes. // This is a synonym for MaxPacketChecked that provides backward compatibility. // // If you get the error "failed to send packet header: EOF" when copying a // large file, try lowering this number. // // The default packet size is 32768 bytes. func MaxPacket(size int) ClientOption { return MaxPacketChecked(size) } // MaxConcurrentRequestsPerFile sets the maximum concurrent requests allowed for a single file. // // The default maximum concurrent requests is 64. func MaxConcurrentRequestsPerFile(n int) ClientOption { return func(c *Client) error { if n < 1 { return errors.Errorf("n must be greater or equal to 1") } c.maxConcurrentRequests = n 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, maxConcurrentRequests: 64, } 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 maxConcurrentRequests int } // 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 {