Skip to content

Commit

Permalink
support handlers returning explicit error codes
Browse files Browse the repository at this point in the history
Add errors for all the SSH_FXP_STATUS codes to give the developer
implementing request server handlers greater control over the returned
codes. Most helpful in cases where nothing currently would work (eg.
unsupported).

Fixes pkg#223
  • Loading branch information
eikenb committed Jan 27, 2018
1 parent 0cf3188 commit 738e088
Show file tree
Hide file tree
Showing 3 changed files with 80 additions and 4 deletions.
42 changes: 42 additions & 0 deletions request-errors.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package sftp

// Error types that match the SFTP's SSH_FXP_STATUS codes. Gives you more
// direct control of the errors being sent vs. letting the library work them
// out from the standard os/io errors.

type fxerr uint32

const (
ErrSshFxOk = fxerr(ssh_FX_OK)
ErrSshFxEof = fxerr(ssh_FX_EOF)
ErrSshFxNoSuchFile = fxerr(ssh_FX_NO_SUCH_FILE)
ErrSshFxPermissionDenied = fxerr(ssh_FX_PERMISSION_DENIED)
ErrSshFxFailure = fxerr(ssh_FX_FAILURE)
ErrSshFxBadMessage = fxerr(ssh_FX_BAD_MESSAGE)
ErrSshFxNoConnection = fxerr(ssh_FX_NO_CONNECTION)
ErrSshFxConnectionLost = fxerr(ssh_FX_CONNECTION_LOST)
ErrSshFxOpUnsupported = fxerr(ssh_FX_OP_UNSUPPORTED)
)

func (e fxerr) Error() string {
switch e {
case ErrSshFxOk:
return "OK"
case ErrSshFxEof:
return "EOF"
case ErrSshFxNoSuchFile:
return "No Such File"
case ErrSshFxPermissionDenied:
return "Permission Denied"
case ErrSshFxBadMessage:
return "Bad Message"
case ErrSshFxNoConnection:
return "No Connection"
case ErrSshFxConnectionLost:
return "Connection Lost"
case ErrSshFxOpUnsupported:
return "Operation Unsupported"
default:
return "Failure"
}
}
2 changes: 2 additions & 0 deletions server.go
Original file line number Diff line number Diff line change
Expand Up @@ -564,6 +564,8 @@ func statusFromError(p ider, err error) sshFxpStatusPacket {
if errno, ok := e.Err.(syscall.Errno); ok {
ret.StatusError.Code = translateErrno(errno)
}
case fxerr:
ret.StatusError.Code = uint32(e)
default:
switch e {
case io.EOF:
Expand Down
40 changes: 36 additions & 4 deletions server_test.go
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
package sftp

import (
"testing"
"io"
"os"
"regexp"
"time"
"io"
"sync"
"syscall"
"testing"
"time"

"github.com/pkg/errors"
"github.com/stretchr/testify/assert"
)

const (
Expand Down Expand Up @@ -61,7 +65,7 @@ func TestRunLsWithLicensesFile(t *testing.T) {
where `id' is the request identifier, and `attrs' is the returned
file attributes as described in Section ``File Attributes''.
*/
*/
func runLsTestHelper(t *testing.T, result, expectedType, path string) {
// using regular expressions to make tests work on all systems
// a virtual file system (like afero) would be needed to mock valid filesystem checks
Expand Down Expand Up @@ -241,3 +245,31 @@ func TestConcurrentRequests(t *testing.T) {
}
wg.Wait()
}

// Test error conversion
func TestStatusFromError(t *testing.T) {
type test struct {
err error
pkt sshFxpStatusPacket
}
tpkt := func(id, code uint32) sshFxpStatusPacket {
return sshFxpStatusPacket{
ID: id,
StatusError: StatusError{Code: code},
}
}
test_cases := []test{
test{syscall.ENOENT, tpkt(1, ssh_FX_NO_SUCH_FILE)},
test{&os.PathError{Err: syscall.ENOENT},
tpkt(2, ssh_FX_NO_SUCH_FILE)},
test{&os.PathError{Err: errors.New("foo")}, tpkt(3, ssh_FX_FAILURE)},
test{ErrSshFxEof, tpkt(4, ssh_FX_EOF)},
test{ErrSshFxOpUnsupported, tpkt(5, ssh_FX_OP_UNSUPPORTED)},
test{io.EOF, tpkt(6, ssh_FX_EOF)},
test{os.ErrNotExist, tpkt(7, ssh_FX_NO_SUCH_FILE)},
}
for _, tc := range test_cases {
tc.pkt.StatusError.msg = tc.err.Error()
assert.Equal(t, tc.pkt, statusFromError(tc.pkt, tc.err))
}
}

0 comments on commit 738e088

Please sign in to comment.