diff --git a/client.go b/client.go index 3b16c3ff..cbe34869 100644 --- a/client.go +++ b/client.go @@ -5,6 +5,7 @@ import ( "os" "path" "sync" + "time" "github.com/kr/fs" @@ -224,6 +225,38 @@ func (c *Client) Lstat(p string) (os.FileInfo, error) { } } +// 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 packet struct { + Type byte + Id uint32 + Path string + Flags uint32 + Atime uint32 + Mtime uint32 + } + c.mu.Lock() + defer c.mu.Unlock() + id := c.nextId() + typ, data, err := c.sendRequest(packet{ + Type: ssh_FXP_SETSTAT, + Id: id, + Path: path, + Flags: ssh_FILEXFER_ATTR_ACMODTIME, + Atime: uint32(atime.Unix()), + Mtime: uint32(mtime.Unix()), + }) + if err != nil { + return err + } + switch typ { + case ssh_FXP_STATUS: + return okOrErr(unmarshalStatus(id, data)) + default: + return unimplementedPacketErr(typ) + } +} + // 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. @@ -378,9 +411,18 @@ func (c *Client) fstat(handle string) (*attr, error) { // empty strings are ignored. func (c *Client) Join(elem ...string) string { return path.Join(elem...) } -// Remove removes the named file or directory. +// 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 { - // TODO(dfc) can't handle directories, yet + err := c.removeFile(path) + if status, ok := err.(*StatusError); ok && (status.Code == ssh_FX_FAILURE) { + err = c.removeDirectory(path) + } + return err +} + +func (c *Client) removeFile(path string) error { type packet struct { Type byte Id uint32 @@ -405,6 +447,31 @@ func (c *Client) Remove(path string) error { } } +func (c *Client) removeDirectory(path string) error { + type packet struct { + Type byte + Id uint32 + Path string + } + c.mu.Lock() + defer c.mu.Unlock() + id := c.nextId() + typ, data, err := c.sendRequest(packet{ + Type: ssh_FXP_RMDIR, + Id: id, + Path: path, + }) + if err != nil { + return err + } + switch typ { + case ssh_FXP_STATUS: + return okOrErr(unmarshalStatus(id, data)) + default: + return unimplementedPacketErr(typ) + } +} + // Rename renames a file. func (c *Client) Rename(oldname, newname string) error { type packet struct { @@ -475,6 +542,36 @@ func (c *Client) writeAt(handle string, offset uint64, buf []byte) (uint32, erro } } +// 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 { + type packet struct { + Type byte + Id uint32 + Path string + Flags uint32 // ignored + Size uint64 // ignored + } + c.mu.Lock() + defer c.mu.Unlock() + id := c.nextId() + typ, data, err := c.sendRequest(packet{ + Type: ssh_FXP_MKDIR, + Id: id, + Path: path, + }) + if err != nil { + return err + } + switch typ { + case ssh_FXP_STATUS: + return okOrErr(unmarshalStatus(id, data)) + default: + return unimplementedPacketErr(typ) + } +} + // File represents a remote file. type File struct { c *Client diff --git a/packet.go b/packet.go index 67df19af..99a16ae2 100644 --- a/packet.go +++ b/packet.go @@ -11,7 +11,7 @@ func marshalUint32(b []byte, v uint32) []byte { } func marshalUint64(b []byte, v uint64) []byte { - return marshalUint32(marshalUint32(b, uint32(v>>24)), uint32(v)) + return marshalUint32(marshalUint32(b, uint32(v>>32)), uint32(v)) } func marshalString(b []byte, v string) []byte {