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") var ( // ErrInternalInconsistency 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. ErrInternalInconsistency = errors.New("internal inconsistency") // InternalInconsistency alias for ErrInternalInconsistency. // // Deprecated: please use ErrInternalInconsistency InternalInconsistency = ErrInternalInconsistency) // 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 }} // UseFstat sets whether to use Fstat or Stat when File.WriteTo is called// (usually when copying files).// Some servers limit the amount of open files and calling Stat after opening// the file will throw an error From the server. Setting this flag will call// Fstat instead of Stat which is suppose to be called on an open file handle.//// It has been found that that with IBM Sterling SFTP servers which have// "extractability" level set to 1 which means only 1 file can be opened at// any given time.//// If the server you are working with still has an issue with both Stat and// Fstat calls you can always open a file and read it until the end.//// Another reason to read the file until its end and Fstat doesn't work is// that in some servers, reading a full file will automatically delete the// file as some of these mainframes map the file to a message in a queue.// Once the file has been read it will get deleted.func UseFstat(value bool) ClientOption { return func(c *Client) error { c.useFstat = value 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), closed: make(chan struct{}), }, 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 useFstat bool} // 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.//// Note that some SFTP servers (eg. AWS Transfer) do not support opening files// read/write at the same time. For those services you will need to use// `client.OpenFile(os.O_WRONLY|os.O_CREATE|os.O_TRUNC)`.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 != sshFxpVersion { return &unexpectedPacketErr{sshFxpVersion, 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 sshFxpName: 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 sshFxpStatus: // 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 sshFxpHandle: sid, data := unmarshalUint32(data) if sid != id { return "", &unexpectedIDErr{id, sid} } handle, _ := unmarshalString(data) return handle, nil case sshFxpStatus: 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 sshFxpAttrs: sid, data := unmarshalUint32(data) if sid != id { return nil, &unexpectedIDErr{id, sid} } attr, _ := unmarshalAttrs(data) return fileInfoFromStat(attr, path.Base(p)), nil case sshFxpStatus: 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 sshFxpAttrs: sid, data := unmarshalUint32(data) if sid != id { return nil, &unexpectedIDErr{id, sid} } attr, _ := unmarshalAttrs(data) return fileInfoFromStat(attr, path.Base(p)), nil case sshFxpStatus: 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 sshFxpName: 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 sshFxpStatus: return "", normaliseError(unmarshalStatus(id, data)) default: return "", unimplementedPacketErr(typ) }} // Link creates a hard link at 'newname', pointing at the same inode as 'oldname'func (c *Client) Link(oldname, newname string) error { id := c.nextID() typ, data, err := c.sendPacket(sshFxpHardlinkPacket{ ID: id, Oldpath: oldname, Newpath: newname, }) if err != nil { return err } switch typ { case sshFxpStatus: 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 sshFxpStatus: 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 sshFxpStatus: 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, sshFileXferAttrACmodTime, 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, sshFileXferAttrUIDGID, attrs)} // Chmod changes the permissions of the named file.func (c *Client) Chmod(path string, mode os.FileMode) error { return c.setstat(path, sshFileXferAttrPermissions, 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, sshFileXferAttrSize, 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))} Lines 1–500 of 1348. 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" ) var ( // ErrInternalInconsistency 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. ErrInternalInconsistency = errors.New("internal inconsistency") // InternalInconsistency alias for ErrInternalInconsistency. // // Deprecated: please use ErrInternalInconsistency InternalInconsistency = ErrInternalInconsistency ) // 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 } } // UseFstat sets whether to use Fstat or Stat when File.WriteTo is called // (usually when copying files). // Some servers limit the amount of open files and calling Stat after opening // the file will throw an error From the server. Setting this flag will call // Fstat instead of Stat which is suppose to be called on an open file handle. // // It has been found that that with IBM Sterling SFTP servers which have // "extractability" level set to 1 which means only 1 file can be opened at // any given time. // // If the server you are working with still has an issue with both Stat and // Fstat calls you can always open a file and read it until the end. // // Another reason to read the file until its end and Fstat doesn't work is // that in some servers, reading a full file will automatically delete the // file as some of these mainframes map the file to a message in a queue. // Once the file has been read it will get deleted. func UseFstat(value bool) ClientOption { return func(c *Client) error { c.useFstat = value 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), closed: make(chan struct{}), }, 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 useFstat bool } // 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. // // Note that some SFTP servers (eg. AWS Transfer) do not support opening files // read/write at the same time. For those services you will need to use // `client.OpenFile(os.O_WRONLY|os.O_CREATE|os.O_TRUNC)`. 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 != sshFxpVersion { return &unexpectedPacketErr{sshFxpVersion, 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 sshFxpName: 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 sshFxpStatus: // 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 sshFxpHandle: sid, data := unmarshalUint32(data) if sid != id { return "", &unexpectedIDErr{id, sid} } handle, _ := unmarshalString(data) return handle, nil case sshFxpStatus: 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 sshFxpAttrs: sid, data := unmarshalUint32(data) if sid != id { return nil, &unexpectedIDErr{id, sid} } attr, _ := unmarshalAttrs(data) return fileInfoFromStat(attr, path.Base(p)), nil case sshFxpStatus: 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 sshFxpAttrs: sid, data := unmarshalUint32(data) if sid != id { return nil, &unexpectedIDErr{id, sid} } attr, _ := unmarshalAttrs(data) return fileInfoFromStat(attr, path.Base(p)), nil case sshFxpStatus: 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 sshFxpName: 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 sshFxpStatus: return "", normaliseError(unmarshalStatus(id, data)) default: return "", unimplementedPacketErr(typ) } } // Link creates a hard link at 'newname', pointing at the same inode as 'oldname' func (c *Client) Link(oldname, newname string) error { id := c.nextID() typ, data, err := c.sendPacket(sshFxpHardlinkPacket{ ID: id, Oldpath: oldname, Newpath: newname, }) if err != nil { return err } switch typ { case sshFxpStatus: 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 sshFxpStatus: 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 sshFxpStatus: 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, sshFileXferAttrACmodTime, 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, sshFileXferAttrUIDGID, attrs) } // Chmod changes the permissions of the named file. func (c *Client) Chmod(path string, mode os.FileMode) error { return c.setstat(path, sshFileXferAttrPermissions, 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, sshFileXferAttrSize, 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)) }