Merge pull request #618 from pkg/v1-backport-client-remove-and-fsync
Backport v2/Client.Remove and File.Sync
diff --git a/client.go b/client.go
index 8c27fb1..a5a915a 100644
--- a/client.go
+++ b/client.go
@@ -17,6 +17,8 @@
"github.com/kr/fs"
"golang.org/x/crypto/ssh"
+
+ "github.com/pkg/sftp/internal/encoding/ssh/filexfer/openssh"
)
var (
@@ -758,20 +760,39 @@
// 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)
- // some servers, *cough* osx *cough*, return EPERM, not ENODIR.
- // serv-u returns ssh_FX_FILE_IS_A_DIRECTORY
- // EPERM is converted to os.ErrPermission so it is not a StatusError
- if err, ok := err.(*StatusError); ok {
- switch err.Code {
- case sshFxFailure, sshFxFileIsADirectory:
- return c.RemoveDirectory(path)
+ errF := c.removeFile(path)
+ if errF == nil {
+ return nil
+ }
+
+ errD := c.RemoveDirectory(path)
+ if errD == nil {
+ return nil
+ }
+
+ // Both failed: figure out which error to return.
+
+ if errF, ok := errF.(*os.PathError); ok {
+ // The only time it makes sense to compare errors, is when both are `*os.PathError`.
+ // We cannot test these directly with errF == errD, as that would be a pointer comparison.
+
+ if errD, ok := errD.(*os.PathError); ok && errors.Is(errF.Err, errD.Err) {
+ // If they are both pointers to PathError,
+ // and the same underlying error, then return that.
+ return errF
}
}
- if os.IsPermission(err) {
- return c.RemoveDirectory(path)
+
+ fi, err := c.Stat(path)
+ if err != nil {
+ return err
}
- return err
+
+ if fi.IsDir() {
+ return errD
+ }
+
+ return errF
}
func (c *Client) removeFile(path string) error {
@@ -785,7 +806,15 @@
}
switch typ {
case sshFxpStatus:
- return normaliseError(unmarshalStatus(id, data))
+ err = normaliseError(unmarshalStatus(id, data))
+ if err == nil {
+ return nil
+ }
+ return &os.PathError{
+ Op: "remove",
+ Path: path,
+ Err: err,
+ }
default:
return unimplementedPacketErr(typ)
}
@@ -803,7 +832,15 @@
}
switch typ {
case sshFxpStatus:
- return normaliseError(unmarshalStatus(id, data))
+ err = normaliseError(unmarshalStatus(id, data))
+ if err == nil {
+ return nil
+ }
+ return &os.PathError{
+ Op: "remove",
+ Path: path,
+ Err: err,
+ }
default:
return unimplementedPacketErr(typ)
}
@@ -2122,6 +2159,13 @@
return os.ErrClosed
}
+ if data, ok := f.c.HasExtension(openssh.ExtensionFSync().Name); !ok || data != "1" {
+ return &StatusError{
+ Code: sshFxOPUnsupported,
+ msg: "fsync not supported",
+ }
+ }
+
id := f.c.nextID()
typ, data, err := f.c.sendPacket(context.Background(), nil, &sshFxpFsyncPacket{
ID: id,
diff --git a/go.mod b/go.mod
index ac5ee71..3a8303c 100644
--- a/go.mod
+++ b/go.mod
@@ -6,5 +6,5 @@
github.com/kr/fs v0.1.0
github.com/stretchr/testify v1.8.0
golang.org/x/crypto v0.31.0
- golang.org/x/sys v0.28.0 // indirect
+ golang.org/x/sys v0.28.0
)