Repository: pkg/sftp
Pull request #380 (2020-09-10 22:21:27)

Fix build on Plan 9
All test are passing on Plan 9, and I've also verified
`examples/go-sftp-server` is working.


Patch:
diff --git a/attrs.go b/attrs.go
index a51cd09..9097a4d 100644
--- a/attrs.go
+++ b/attrs.go
@@ -5,7 +5,6 @@ package sftp
 
 import (
 	"os"
-	"syscall"
 	"time"
 )
 
@@ -172,75 +171,3 @@ func marshalFileInfo(b []byte, fi os.FileInfo) []byte {
 
 	return b
 }
-
-// toFileMode converts sftp filemode bits to the os.FileMode specification
-func toFileMode(mode uint32) os.FileMode {
-	var fm = os.FileMode(mode & 0777)
-	switch mode & S_IFMT {
-	case syscall.S_IFBLK:
-		fm |= os.ModeDevice
-	case syscall.S_IFCHR:
-		fm |= os.ModeDevice | os.ModeCharDevice
-	case syscall.S_IFDIR:
-		fm |= os.ModeDir
-	case syscall.S_IFIFO:
-		fm |= os.ModeNamedPipe
-	case syscall.S_IFLNK:
-		fm |= os.ModeSymlink
-	case syscall.S_IFREG:
-		// nothing to do
-	case syscall.S_IFSOCK:
-		fm |= os.ModeSocket
-	}
-	if mode&syscall.S_ISGID != 0 {
-		fm |= os.ModeSetgid
-	}
-	if mode&syscall.S_ISUID != 0 {
-		fm |= os.ModeSetuid
-	}
-	if mode&syscall.S_ISVTX != 0 {
-		fm |= os.ModeSticky
-	}
-	return fm
-}
-
-// fromFileMode converts from the os.FileMode specification to sftp filemode bits
-func fromFileMode(mode os.FileMode) uint32 {
-	ret := uint32(0)
-
-	if mode&os.ModeDevice != 0 {
-		if mode&os.ModeCharDevice != 0 {
-			ret |= syscall.S_IFCHR
-		} else {
-			ret |= syscall.S_IFBLK
-		}
-	}
-	if mode&os.ModeDir != 0 {
-		ret |= syscall.S_IFDIR
-	}
-	if mode&os.ModeSymlink != 0 {
-		ret |= syscall.S_IFLNK
-	}
-	if mode&os.ModeNamedPipe != 0 {
-		ret |= syscall.S_IFIFO
-	}
-	if mode&os.ModeSetgid != 0 {
-		ret |= syscall.S_ISGID
-	}
-	if mode&os.ModeSetuid != 0 {
-		ret |= syscall.S_ISUID
-	}
-	if mode&os.ModeSticky != 0 {
-		ret |= syscall.S_ISVTX
-	}
-	if mode&os.ModeSocket != 0 {
-		ret |= syscall.S_IFSOCK
-	}
-
-	if mode&os.ModeType == 0 {
-		ret |= syscall.S_IFREG
-	}
-	ret |= uint32(mode & os.ModePerm)
-
-	return ret
-}
diff --git a/attrs_stubs.go b/attrs_stubs.go
index 81cf3ea..ba72e30 100644
--- a/attrs_stubs.go
+++ b/attrs_stubs.go
@@ -1,4 +1,4 @@
-// +build !cgo,!plan9 windows android
+// +build !cgo plan9 windows android
 
 package sftp
 
diff --git a/request-plan9.go b/request-plan9.go
new file mode 100644
index 0000000..0074e8a
--- /dev/null
+++ b/request-plan9.go
@@ -0,0 +1,13 @@
+// +build plan9
+
+package sftp
+
+import "syscall"
+
+func fakeFileInfoSys() interface{} {
+	return &syscall.Dir{}
+}
+
+func testOsSys(sys interface{}) error {
+	return nil
+}
diff --git a/request-server.go b/request-server.go
index 05dfa6d..b1fee47 100644
--- a/request-server.go
+++ b/request-server.go
@@ -7,7 +7,6 @@ import (
 	"path/filepath"
 	"strconv"
 	"sync"
-	"syscall"
 
 	"github.com/pkg/errors"
 )
@@ -100,7 +99,7 @@ func (rs *RequestServer) closeRequest(handle string) error {
 		delete(rs.openRequests, handle)
 		return r.close()
 	}
-	return syscall.EBADF
+	return EBADF
 }
 
 // Close the read/write/closer to trigger exiting the main server loop
@@ -209,7 +208,7 @@ func (rs *RequestServer) packetWorker(
 			handle := pkt.getHandle()
 			request, ok := rs.getRequest(handle)
 			if !ok {
-				rpkt = statusFromError(pkt, syscall.EBADF)
+				rpkt = statusFromError(pkt, EBADF)
 			} else {
 				request = NewRequest("Stat", request.Filepath)
 				rpkt = request.call(rs.Handlers, pkt, rs.pktMgr.alloc, orderID)
@@ -218,7 +217,7 @@ func (rs *RequestServer) packetWorker(
 			handle := pkt.getHandle()
 			request, ok := rs.getRequest(handle)
 			if !ok {
-				rpkt = statusFromError(pkt, syscall.EBADF)
+				rpkt = statusFromError(pkt, EBADF)
 			} else {
 				request = NewRequest("Setstat", request.Filepath)
 				rpkt = request.call(rs.Handlers, pkt, rs.pktMgr.alloc, orderID)
@@ -231,7 +230,7 @@ func (rs *RequestServer) packetWorker(
 			handle := pkt.getHandle()
 			request, ok := rs.getRequest(handle)
 			if !ok {
-				rpkt = statusFromError(pkt, syscall.EBADF)
+				rpkt = statusFromError(pkt, EBADF)
 			} else {
 				rpkt = request.call(rs.Handlers, pkt, rs.pktMgr.alloc, orderID)
 			}
diff --git a/request-unix.go b/request-unix.go
index e2c586f..d30b256 100644
--- a/request-unix.go
+++ b/request-unix.go
@@ -1,4 +1,4 @@
-// +build !windows
+// +build !windows,!plan9
 
 package sftp
 
diff --git a/request.go b/request.go
index 3fdd5a0..4f3095c 100644
--- a/request.go
+++ b/request.go
@@ -262,11 +262,7 @@ func (r *Request) opendir(h Handlers, pkt requestPacket) responsePacket {
 	r.Method = "List"
 	la, err := h.FileList.Filelist(r)
 	if err != nil {
-		switch err.(type) {
-		case syscall.Errno:
-			err = &os.PathError{Path: r.Filepath, Err: err}
-		}
-		return statusFromError(pkt, err)
+		return statusFromError(pkt, wrapPathError(r.Filepath, err))
 	}
 	r.state.listerAt = la
 	return &sshFxpHandlePacket{ID: pkt.id(), Handle: r.handle}
diff --git a/server.go b/server.go
index 985a844..f595518 100644
--- a/server.go
+++ b/server.go
@@ -53,7 +53,7 @@ func (svr *Server) closeHandle(handle string) error {
 		return f.Close()
 	}
 
-	return syscall.EBADF
+	return EBADF
 }
 
 func (svr *Server) getHandle(handle string) (*os.File, bool) {
@@ -195,7 +195,7 @@ func handlePacket(s *Server, p orderedRequest) error {
 		}
 	case *sshFxpFstatPacket:
 		f, ok := s.getHandle(p.Handle)
-		var err error = syscall.EBADF
+		var err error = EBADF
 		var info os.FileInfo
 		if ok {
 			info, err = f.Stat()
@@ -266,7 +266,7 @@ func handlePacket(s *Server, p orderedRequest) error {
 			}.respond(s)
 		}
 	case *sshFxpReadPacket:
-		var err error = syscall.EBADF
+		var err error = EBADF
 		f, ok := s.getHandle(p.Handle)
 		if ok {
 			err = nil
@@ -288,7 +288,7 @@ func handlePacket(s *Server, p orderedRequest) error {
 
 	case *sshFxpWritePacket:
 		f, ok := s.getHandle(p.Handle)
-		var err error = syscall.EBADF
+		var err error = EBADF
 		if ok {
 			_, err = f.WriteAt(p.Data, int64(p.Offset))
 		}
@@ -442,7 +442,7 @@ func (p sshFxpOpenPacket) respond(svr *Server) responsePacket {
 func (p sshFxpReaddirPacket) respond(svr *Server) responsePacket {
 	f, ok := svr.getHandle(p.Handle)
 	if !ok {
-		return statusFromError(p, syscall.EBADF)
+		return statusFromError(p, EBADF)
 	}
 
 	dirname := f.Name()
@@ -507,7 +507,7 @@ func (p sshFxpSetstatPacket) respond(svr *Server) responsePacket {
 func (p sshFxpFsetstatPacket) respond(svr *Server) responsePacket {
 	f, ok := svr.getHandle(p.Handle)
 	if !ok {
-		return statusFromError(p, syscall.EBADF)
+		return statusFromError(p, EBADF)
 	}
 
 	// additional unmarshalling is required for each possibility here
@@ -551,20 +551,6 @@ func (p sshFxpFsetstatPacket) respond(svr *Server) responsePacket {
 	return statusFromError(p, err)
 }
 
-// translateErrno translates a syscall error number to a SFTP error code.
-func translateErrno(errno syscall.Errno) uint32 {
-	switch errno {
-	case 0:
-		return sshFxOk
-	case syscall.ENOENT:
-		return sshFxNoSuchFile
-	case syscall.EPERM:
-		return sshFxPermissionDenied
-	}
-
-	return sshFxFailure
-}
-
 func statusFromError(p ider, err error) sshFxpStatusPacket {
 	ret := sshFxpStatusPacket{
 		ID: p.id(),
@@ -593,15 +579,12 @@ func statusFromError(p ider, err error) sshFxpStatusPacket {
 		ret.StatusError.Code = sshFxNoSuchFile
 		return ret
 	}
+	if code, ok := translateSyscallError(err); ok {
+		ret.StatusError.Code = code
+		return ret
+	}
 
 	switch e := err.(type) {
-	case syscall.Errno:
-		ret.StatusError.Code = translateErrno(e)
-	case *os.PathError:
-		debug("statusFromError,pathError: error is %T %#v", e.Err, e.Err)
-		if errno, ok := e.Err.(syscall.Errno); ok {
-			ret.StatusError.Code = translateErrno(errno)
-		}
 	case fxerr:
 		ret.StatusError.Code = uint32(e)
 	default:
diff --git a/server_statvfs_plan9.go b/server_statvfs_plan9.go
new file mode 100644
index 0000000..13c53f3
--- /dev/null
+++ b/server_statvfs_plan9.go
@@ -0,0 +1,9 @@
+package sftp
+
+import (
+	"syscall"
+)
+
+func (p sshFxpExtendedPacketStatVFS) respond(svr *Server) responsePacket {
+	return statusFromError(p, syscall.EPLAN9)
+}
diff --git a/server_statvfs_stubs.go b/server_statvfs_stubs.go
index c6f6164..7c98e32 100644
--- a/server_statvfs_stubs.go
+++ b/server_statvfs_stubs.go
@@ -1,4 +1,4 @@
-// +build !darwin,!linux
+// +build !darwin,!linux,!plan9
 
 package sftp
 
diff --git a/server_stubs.go b/server_stubs.go
index a14c734..62c9fa1 100644
--- a/server_stubs.go
+++ b/server_stubs.go
@@ -1,11 +1,11 @@
-// +build !cgo,!plan9 windows android
+// +build !cgo plan9 windows android
 
 package sftp
 
 import (
+	"fmt"
 	"os"
 	"time"
-	"fmt"
 )
 
 func runLs(dirname string, dirent os.FileInfo) string {
diff --git a/stat_plan9.go b/stat_plan9.go
new file mode 100644
index 0000000..7d82032
--- /dev/null
+++ b/stat_plan9.go
@@ -0,0 +1,96 @@
+package sftp
+
+import (
+	"os"
+	"syscall"
+)
+
+var EBADF = syscall.NewError("fd out of range or not open")
+
+func wrapPathError(filepath string, err error) error {
+	if errno, ok := err.(syscall.ErrorString); ok {
+		return &os.PathError{Path: filepath, Err: errno }
+	}
+	return err
+}
+
+// translateErrno translates a syscall error number to a SFTP error code.
+func translateErrno(errno syscall.ErrorString) uint32 {
+	switch errno {
+	case "":
+		return sshFxOk
+	case syscall.ENOENT:
+		return sshFxNoSuchFile
+	case syscall.EPERM:
+		return sshFxPermissionDenied
+	}
+
+	return sshFxFailure
+}
+
+func translateSyscallError(err error) (uint32, bool) {
+	switch e := err.(type) {
+	case syscall.ErrorString:
+		return translateErrno(e), true
+	case *os.PathError:
+		debug("statusFromError,pathError: error is %T %#v", e.Err, e.Err)
+		if errno, ok := e.Err.(syscall.ErrorString); ok {
+			return translateErrno(errno), true
+		}
+	}
+	return 0, false
+}
+
+// toFileMode converts sftp filemode bits to the os.FileMode specification
+func toFileMode(mode uint32) os.FileMode {
+	var fm = os.FileMode(mode & 0777)
+	switch mode & S_IFMT {
+	case syscall.S_IFBLK:
+		fm |= os.ModeDevice
+	case syscall.S_IFCHR:
+		fm |= os.ModeDevice | os.ModeCharDevice
+	case syscall.S_IFDIR:
+		fm |= os.ModeDir
+	case syscall.S_IFIFO:
+		fm |= os.ModeNamedPipe
+	case syscall.S_IFLNK:
+		fm |= os.ModeSymlink
+	case syscall.S_IFREG:
+		// nothing to do
+	case syscall.S_IFSOCK:
+		fm |= os.ModeSocket
+	}
+	return fm
+}
+
+// fromFileMode converts from the os.FileMode specification to sftp filemode bits
+func fromFileMode(mode os.FileMode) uint32 {
+	ret := uint32(0)
+
+	if mode&os.ModeDevice != 0 {
+		if mode&os.ModeCharDevice != 0 {
+			ret |= syscall.S_IFCHR
+		} else {
+			ret |= syscall.S_IFBLK
+		}
+	}
+	if mode&os.ModeDir != 0 {
+		ret |= syscall.S_IFDIR
+	}
+	if mode&os.ModeSymlink != 0 {
+		ret |= syscall.S_IFLNK
+	}
+	if mode&os.ModeNamedPipe != 0 {
+		ret |= syscall.S_IFIFO
+	}
+	if mode&os.ModeSocket != 0 {
+		ret |= syscall.S_IFSOCK
+	}
+
+	if mode&os.ModeType == 0 {
+		ret |= syscall.S_IFREG
+	}
+	ret |= uint32(mode & os.ModePerm)
+
+	return ret
+}
diff --git a/stat_posix.go b/stat_posix.go
new file mode 100644
index 0000000..45eff04
--- /dev/null
+++ b/stat_posix.go
@@ -0,0 +1,116 @@
+// +build !plan9
+
+package sftp
+
+import (
+	"os"
+	"syscall"
+)
+
+const EBADF = syscall.EBADF
+
+func wrapPathError(filepath string, err error) error {
+	if errno, ok := err.(syscall.Errno); ok {
+		return &os.PathError{Path: filepath, Err: errno }
+	}
+	return err
+}
+
+// translateErrno translates a syscall error number to a SFTP error code.
+func translateErrno(errno syscall.Errno) uint32 {
+	switch errno {
+	case 0:
+		return sshFxOk
+	case syscall.ENOENT:
+		return sshFxNoSuchFile
+	case syscall.EPERM:
+		return sshFxPermissionDenied
+	}
+
+	return sshFxFailure
+}
+
+func translateSyscallError(err error) (uint32, bool) {
+	switch e := err.(type) {
+	case syscall.Errno:
+		return translateErrno(e), true
+	case *os.PathError:
+		debug("statusFromError,pathError: error is %T %#v", e.Err, e.Err)
+		if errno, ok := e.Err.(syscall.Errno); ok {
+			return translateErrno(errno), true
+		}
+	}
+	return 0, false
+}
+
+// toFileMode converts sftp filemode bits to the os.FileMode specification
+func toFileMode(mode uint32) os.FileMode {
+	var fm = os.FileMode(mode & 0777)
+	switch mode & S_IFMT {
+	case syscall.S_IFBLK:
+		fm |= os.ModeDevice
+	case syscall.S_IFCHR:
+		fm |= os.ModeDevice | os.ModeCharDevice
+	case syscall.S_IFDIR:
+		fm |= os.ModeDir
+	case syscall.S_IFIFO:
+		fm |= os.ModeNamedPipe
+	case syscall.S_IFLNK:
+		fm |= os.ModeSymlink
+	case syscall.S_IFREG:
+		// nothing to do
+	case syscall.S_IFSOCK:
+		fm |= os.ModeSocket
+	}
+	if mode&syscall.S_ISGID != 0 {
+		fm |= os.ModeSetgid
+	}
+	if mode&syscall.S_ISUID != 0 {
+		fm |= os.ModeSetuid
+	}
+	if mode&syscall.S_ISVTX != 0 {
+		fm |= os.ModeSticky
+	}
+	return fm
+}
+
+// fromFileMode converts from the os.FileMode specification to sftp filemode bits
+func fromFileMode(mode os.FileMode) uint32 {
+	ret := uint32(0)
+
+	if mode&os.ModeDevice != 0 {
+		if mode&os.ModeCharDevice != 0 {
+			ret |= syscall.S_IFCHR
+		} else {
+			ret |= syscall.S_IFBLK
+		}
+	}
+	if mode&os.ModeDir != 0 {
+		ret |= syscall.S_IFDIR
+	}
+	if mode&os.ModeSymlink != 0 {
+		ret |= syscall.S_IFLNK
+	}
+	if mode&os.ModeNamedPipe != 0 {
+		ret |= syscall.S_IFIFO
+	}
+	if mode&os.ModeSetgid != 0 {
+		ret |= syscall.S_ISGID
+	}
+	if mode&os.ModeSetuid != 0 {
+		ret |= syscall.S_ISUID
+	}
+	if mode&os.ModeSticky != 0 {
+		ret |= syscall.S_ISVTX
+	}
+	if mode&os.ModeSocket != 0 {
+		ret |= syscall.S_IFSOCK
+	}
+
+	if mode&os.ModeType == 0 {
+		ret |= syscall.S_IFREG
+	}
+	ret |= uint32(mode & os.ModePerm)
+
+	return ret
+}


Test patch:
diff --git a/request-server_test.go b/request-server_test.go
index 3f79b8d..d216d2d 100644
--- a/request-server_test.go
+++ b/request-server_test.go
@@ -34,6 +34,7 @@ const sock = "/tmp/rstest.sock"
 
 func clientRequestServerPair(t *testing.T) *csPair {
 	skipIfWindows(t)
+	skipIfPlan9(t)
 	ready := make(chan bool)
 	os.Remove(sock) // either this or signal handling
 	var server *RequestServer
diff --git a/server_integration_test.go b/server_integration_test.go
index 0ad87e0..c4fb3b9 100644
--- a/server_integration_test.go
+++ b/server_integration_test.go
@@ -63,6 +63,12 @@ func skipIfWindows(t testing.TB) {
 	}
 }
 
+func skipIfPlan9(t testing.TB) {
+	if runtime.GOOS == "plan9" {
+		t.Skip("skipping test on plan9")
+	}
+}
+
 var testServerImpl = flag.Bool("testserver", false, "perform integration tests against sftp package server instance")
 var testIntegration = flag.Bool("integration", false, "perform integration tests against sftp server process")
 var testAllocator = flag.Bool("allocator", false, "perform tests using the allocator")
diff --git a/server_test.go b/server_test.go
index 4ca2d2a..bf905c0 100644
--- a/server_test.go
+++ b/server_test.go
@@ -5,6 +5,7 @@ import (
 	"os"
 	"path"
 	"regexp"
+	"runtime"
 	"sync"
 	"syscall"
 	"testing"
@@ -241,6 +242,10 @@ func TestInvalidExtendedPacket(t *testing.T) {
 // test that server handles concurrent requests correctly
 func TestConcurrentRequests(t *testing.T) {
 	skipIfWindows(t)
+	filename := "/etc/passwd"
+	if runtime.GOOS == "plan9" {
+		filename = "/lib/ndb/local"
+	}
 	client, server := clientServerPair(t)
 	defer client.Close()
 	defer server.Close()
@@ -254,9 +259,10 @@ func TestConcurrentRequests(t *testing.T) {
 			defer wg.Done()
 
 			for j := 0; j < 1024; j++ {
-				f, err := client.Open("/etc/passwd")
+				f, err := client.Open(filename)
 				if err != nil {
 					t.Errorf("failed to open file: %v", err)
+					continue
 				}
 				if err := f.Close(); err != nil {
 					t.Errorf("failed t close file: %v", err)
diff --git a/sftp_test.go b/sftp_test.go
index ee0483c..174a457 100644
--- a/sftp_test.go
+++ b/sftp_test.go
@@ -16,7 +16,7 @@ func TestErrFxCode(t *testing.T) {
 		fx  fxerr
 	}{
 		{err: errors.New("random error"), fx: ErrSSHFxFailure},
-		{err: syscall.EBADF, fx: ErrSSHFxFailure},
+		{err: EBADF, fx: ErrSSHFxFailure},
 		{err: syscall.ENOENT, fx: ErrSSHFxNoSuchFile},
 		{err: syscall.EPERM, fx: ErrSSHFxPermissionDenied},
 		{err: io.EOF, fx: ErrSSHFxEOF},