From e2bb7bc10b02a6c901c8e7fca4df70cf3c12295a Mon Sep 17 00:00:00 2001 From: Claudio Felber Date: Sat, 23 Nov 2013 00:35:12 +0100 Subject: [PATCH 1/4] Added client methods CreateDirectory() and RemoveDirectory() --- client.go | 62 +++++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 60 insertions(+), 2 deletions(-) diff --git a/client.go b/client.go index 3b16c3ff..bf38a185 100644 --- a/client.go +++ b/client.go @@ -378,9 +378,8 @@ 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 named file. func (c *Client) Remove(path string) error { - // TODO(dfc) can't handle directories, yet type packet struct { Type byte Id uint32 @@ -475,6 +474,65 @@ 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) CreateDirectory(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) + } +} + +// Removes the specified directory. An error will be returned if no directory +// with the specified path exists, or if the specified directory is not +// empty, or if the path specified a file system object other than a +// directory. +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) + } +} + // File represents a remote file. type File struct { c *Client From e8b14451d6c62d63aa3d5dab67fcbd11e7740381 Mon Sep 17 00:00:00 2001 From: Claudio Felber Date: Sat, 23 Nov 2013 14:00:17 +0100 Subject: [PATCH 2/4] For better standard file system interface compatibility: - Renamed CreateDirectory() to MkDir() - Integrated RemoveDirectory() into Remove() --- client.go | 68 ++++++++++++++++++++++++++++++------------------------- 1 file changed, 37 insertions(+), 31 deletions(-) diff --git a/client.go b/client.go index bf38a185..6dc55fec 100644 --- a/client.go +++ b/client.go @@ -378,8 +378,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. +// 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 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 @@ -404,6 +414,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 { @@ -477,7 +512,7 @@ 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) CreateDirectory(path string) error { +func (c *Client) Mkdir(path string) error { type packet struct { Type byte Id uint32 @@ -504,35 +539,6 @@ func (c *Client) CreateDirectory(path string) error { } } -// Removes the specified directory. An error will be returned if no directory -// with the specified path exists, or if the specified directory is not -// empty, or if the path specified a file system object other than a -// directory. -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) - } -} - // File represents a remote file. type File struct { c *Client From 119022645abe7b07d7ff4d70e1b13bb6079e3043 Mon Sep 17 00:00:00 2001 From: Claudio Felber Date: Wed, 11 Dec 2013 00:14:59 +0100 Subject: [PATCH 3/4] Fixed an error in marshalUint64() that resulted in incorrect marshalling of values >= 2^24 --- packet.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 { From c9c2be8791ed161fdbce9d2250b32037df4a421e Mon Sep 17 00:00:00 2001 From: Claudio Felber Date: Wed, 11 Dec 2013 00:18:53 +0100 Subject: [PATCH 4/4] Added function Chtimes() to modify access and modification time of a file --- client.go | 57 +++++++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 45 insertions(+), 12 deletions(-) diff --git a/client.go b/client.go index 6dc55fec..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. @@ -386,7 +419,7 @@ func (c *Client) Remove(path string) error { if status, ok := err.(*StatusError); ok && (status.Code == ssh_FX_FAILURE) { err = c.removeDirectory(path) } - return err; + return err } func (c *Client) removeFile(path string) error { @@ -424,9 +457,9 @@ func (c *Client) removeDirectory(path string) error { defer c.mu.Unlock() id := c.nextId() typ, data, err := c.sendRequest(packet{ - Type: ssh_FXP_RMDIR, - Id: id, - Path: path, + Type: ssh_FXP_RMDIR, + Id: id, + Path: path, }) if err != nil { return err @@ -514,19 +547,19 @@ func (c *Client) writeAt(handle string, offset uint64, buf []byte) (uint32, erro // 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 + 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, + Type: ssh_FXP_MKDIR, + Id: id, + Path: path, }) if err != nil { return err