Merge pull request #213 from vansante/master
Add PosixRename method which uses the posix-rename@openssh.com extension (without server support)
diff --git a/client.go b/client.go
index ab6163a..ea0fc6d 100644
--- a/client.go
+++ b/client.go
@@ -577,6 +577,26 @@
}
}
+// 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{
diff --git a/client_integration_test.go b/client_integration_test.go
index ef73a08..1411dca 100644
--- a/client_integration_test.go
+++ b/client_integration_test.go
@@ -627,6 +627,27 @@
}
}
+func TestClientPosixRename(t *testing.T) {
+ sftp, cmd := testClient(t, READWRITE, NO_DELAY)
+ defer cmd.Wait()
+ defer sftp.Close()
+
+ f, err := ioutil.TempFile("", "sftptest")
+ if err != nil {
+ t.Fatal(err)
+ }
+ f2 := f.Name() + ".new"
+ if err := sftp.PosixRename(f.Name(), f2); err != nil {
+ t.Fatal(err)
+ }
+ if _, err := os.Lstat(f.Name()); !os.IsNotExist(err) {
+ t.Fatal(err)
+ }
+ if _, err := os.Lstat(f2); err != nil {
+ t.Fatal(err)
+ }
+}
+
func TestClientGetwd(t *testing.T) {
sftp, cmd := testClient(t, READONLY, NO_DELAY)
defer cmd.Wait()
diff --git a/packet-typing.go b/packet-typing.go
index 920851d..d3cca73 100644
--- a/packet-typing.go
+++ b/packet-typing.go
@@ -51,6 +51,7 @@
func (p sshFxpStatvfsPacket) getPath() string { return p.Path }
func (p sshFxpRemovePacket) getPath() string { return p.Filename }
func (p sshFxpRenamePacket) getPath() string { return p.Oldpath }
+func (p sshFxpExtendedPacketPosixRename) getPath() string { return p.Oldpath }
func (p sshFxpSymlinkPacket) getPath() string { return p.Targetpath }
// Openers implement hasPath and isOpener
@@ -74,6 +75,7 @@
func (p sshFxpMkdirPacket) notReadOnly() {}
func (p sshFxpRmdirPacket) notReadOnly() {}
func (p sshFxpRenamePacket) notReadOnly() {}
+func (p sshFxpExtendedPacketPosixRename) notReadOnly() {}
func (p sshFxpSymlinkPacket) notReadOnly() {}
// this has a handle, but is only used for close
diff --git a/packet.go b/packet.go
index db4fbb3..9b68138 100644
--- a/packet.go
+++ b/packet.go
@@ -586,6 +586,30 @@
return nil
}
+type sshFxpPosixRenamePacket struct {
+ ID uint32
+ Oldpath string
+ Newpath string
+}
+
+func (p sshFxpPosixRenamePacket) id() uint32 { return p.ID }
+
+func (p sshFxpPosixRenamePacket) MarshalBinary() ([]byte, error) {
+ const ext = "posix-rename@openssh.com"
+ l := 1 + 4 + // type(byte) + uint32
+ 4 + len(ext) +
+ 4 + len(p.Oldpath) +
+ 4 + len(p.Newpath)
+
+ b := make([]byte, 0, l)
+ b = append(b, ssh_FXP_EXTENDED)
+ b = marshalUint32(b, p.ID)
+ b = marshalString(b, ext)
+ b = marshalString(b, p.Oldpath)
+ b = marshalString(b, p.Newpath)
+ return b, nil
+}
+
type sshFxpWritePacket struct {
ID uint32
Handle string
@@ -870,6 +894,8 @@
switch p.ExtendedRequest {
case "statvfs@openssh.com":
p.SpecificPacket = &sshFxpExtendedPacketStatVFS{}
+ case "posix-rename@openssh.com":
+ p.SpecificPacket = &sshFxpExtendedPacketPosixRename{}
default:
return errUnknownExtendedPacket
}
@@ -896,3 +922,31 @@
}
return nil
}
+
+type sshFxpExtendedPacketPosixRename struct {
+ ID uint32
+ ExtendedRequest string
+ Oldpath string
+ Newpath string
+}
+
+func (p sshFxpExtendedPacketPosixRename) id() uint32 { return p.ID }
+func (p sshFxpExtendedPacketPosixRename) readonly() bool { return false }
+func (p *sshFxpExtendedPacketPosixRename) UnmarshalBinary(b []byte) error {
+ var err error
+ if p.ID, b, err = unmarshalUint32Safe(b); err != nil {
+ return err
+ } else if p.ExtendedRequest, b, err = unmarshalStringSafe(b); err != nil {
+ return err
+ } else if p.Oldpath, b, err = unmarshalStringSafe(b); err != nil {
+ return err
+ } else if p.Newpath, b, err = unmarshalStringSafe(b); err != nil {
+ return err
+ }
+ return nil
+}
+
+func (p sshFxpExtendedPacketPosixRename) respond(s *Server) error {
+ err := os.Rename(p.Oldpath, p.Newpath)
+ return s.sendError(p, err)
+}