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)
+}