Merge pull request #577 from drakkan/lister-close
add CloserListerAt
diff --git a/attrs_unix.go b/attrs_unix.go
index 371ae9b..96ffc03 100644
--- a/attrs_unix.go
+++ b/attrs_unix.go
@@ -1,5 +1,5 @@
-//go:build darwin || dragonfly || freebsd || (!android && linux) || netbsd || openbsd || solaris || aix || js
-// +build darwin dragonfly freebsd !android,linux netbsd openbsd solaris aix js
+//go:build darwin || dragonfly || freebsd || (!android && linux) || netbsd || openbsd || solaris || aix || js || zos
+// +build darwin dragonfly freebsd !android,linux netbsd openbsd solaris aix js zos
package sftp
diff --git a/client.go b/client.go
index 5f45722..3a9a8bf 100644
--- a/client.go
+++ b/client.go
@@ -2113,13 +2113,12 @@
// Unsupported flags are ignored.
func toPflags(f int) uint32 {
var out uint32
- switch f & os.O_WRONLY {
- case os.O_WRONLY:
- out |= sshFxfWrite
+ switch f & (os.O_RDONLY | os.O_WRONLY | os.O_RDWR) {
case os.O_RDONLY:
out |= sshFxfRead
- }
- if f&os.O_RDWR == os.O_RDWR {
+ case os.O_WRONLY:
+ out |= sshFxfWrite
+ case os.O_RDWR:
out |= sshFxfRead | sshFxfWrite
}
if f&os.O_APPEND == os.O_APPEND {
@@ -2143,7 +2142,7 @@
// setuid, setgid and sticky in m, because we've historically supported those
// bits, and we mask off any non-permission bits.
func toChmodPerm(m os.FileMode) (perm uint32) {
- const mask = os.ModePerm | s_ISUID | s_ISGID | s_ISVTX
+ const mask = os.ModePerm | os.FileMode(s_ISUID|s_ISGID|s_ISVTX)
perm = uint32(m & mask)
if m&os.ModeSetuid != 0 {
diff --git a/client_integration_test.go b/client_integration_test.go
index 35ccbea..546ba21 100644
--- a/client_integration_test.go
+++ b/client_integration_test.go
@@ -974,7 +974,7 @@
f.Close()
const allPerm = os.ModePerm | os.ModeSetuid | os.ModeSetgid | os.ModeSticky |
- s_ISUID | s_ISGID | s_ISVTX
+ os.FileMode(s_ISUID|s_ISGID|s_ISVTX)
for _, c := range []struct {
goPerm os.FileMode
diff --git a/errno_plan9.go b/errno_plan9.go
new file mode 100644
index 0000000..cf9d390
--- /dev/null
+++ b/errno_plan9.go
@@ -0,0 +1,42 @@
+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
+}
diff --git a/errno_posix.go b/errno_posix.go
new file mode 100644
index 0000000..cd87e1b
--- /dev/null
+++ b/errno_posix.go
@@ -0,0 +1,45 @@
+//go:build !plan9
+// +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.EACCES, 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
+}
diff --git a/ls_formatting_test.go b/ls_formatting_test.go
index 8d0fefb..d6e5e03 100644
--- a/ls_formatting_test.go
+++ b/ls_formatting_test.go
@@ -3,6 +3,7 @@
import (
"os"
"regexp"
+ "runtime"
"strings"
"testing"
"time"
@@ -101,6 +102,12 @@
dateTime := strings.Join(fields[5:8], " ")
filename := fields[8]
+ if runtime.GOOS == "zos" {
+ // User and Group are always only uppercase characters on z/OS
+ user = strings.ToLower(user)
+ group = strings.ToLower(group)
+ }
+
// permissions (len 10, "drwxr-xr-x")
const (
rwxs = "[-r][-w][-xsS]"
diff --git a/ls_unix.go b/ls_unix.go
index 0beba32..aa230dc 100644
--- a/ls_unix.go
+++ b/ls_unix.go
@@ -1,5 +1,5 @@
-//go:build aix || darwin || dragonfly || freebsd || (!android && linux) || netbsd || openbsd || solaris || js
-// +build aix darwin dragonfly freebsd !android,linux netbsd openbsd solaris js
+//go:build aix || darwin || dragonfly || freebsd || (!android && linux) || netbsd || openbsd || solaris || js || zos
+// +build aix darwin dragonfly freebsd !android,linux netbsd openbsd solaris js zos
package sftp
diff --git a/request-server_test.go b/request-server_test.go
index 64f380c..93011a5 100644
--- a/request-server_test.go
+++ b/request-server_test.go
@@ -780,8 +780,13 @@
}
}
_, err := p.cli.ReadDir("/foo_01")
- assert.Equal(t, &StatusError{Code: sshFxFailure,
- msg: " /foo_01: not a directory"}, err)
+ if runtime.GOOS == "zos" {
+ assert.Equal(t, &StatusError{Code: sshFxFailure,
+ msg: " /foo_01: EDC5135I Not a directory."}, err)
+ } else {
+ assert.Equal(t, &StatusError{Code: sshFxFailure,
+ msg: " /foo_01: not a directory"}, err)
+ }
_, err = p.cli.ReadDir("/does_not_exist")
assert.Equal(t, os.ErrNotExist, err)
di, err := p.cli.ReadDir("/")
diff --git a/server_test.go b/server_test.go
index 4cec312..5bac81e 100644
--- a/server_test.go
+++ b/server_test.go
@@ -99,9 +99,14 @@
// test that server handles concurrent requests correctly
func TestConcurrentRequests(t *testing.T) {
skipIfWindows(t)
- filename := "/etc/passwd"
- if runtime.GOOS == "plan9" {
+ var filename string
+ switch runtime.GOOS {
+ case "plan9":
filename = "/lib/ndb/local"
+ case "zos":
+ filename = "/etc/.shrc"
+ default:
+ filename = "/etc/passwd"
}
client, server := clientServerPair(t)
defer client.Close()
diff --git a/stat.go b/stat.go
new file mode 100644
index 0000000..2bb2c13
--- /dev/null
+++ b/stat.go
@@ -0,0 +1,94 @@
+package sftp
+
+import (
+ "os"
+
+ sshfx "github.com/pkg/sftp/internal/encoding/ssh/filexfer"
+)
+
+// isRegular returns true if the mode describes a regular file.
+func isRegular(mode uint32) bool {
+ return sshfx.FileMode(mode)&sshfx.ModeType == sshfx.ModeRegular
+}
+
+// toFileMode converts sftp filemode bits to the os.FileMode specification
+func toFileMode(mode uint32) os.FileMode {
+ var fm = os.FileMode(mode & 0777)
+
+ switch sshfx.FileMode(mode) & sshfx.ModeType {
+ case sshfx.ModeDevice:
+ fm |= os.ModeDevice
+ case sshfx.ModeCharDevice:
+ fm |= os.ModeDevice | os.ModeCharDevice
+ case sshfx.ModeDir:
+ fm |= os.ModeDir
+ case sshfx.ModeNamedPipe:
+ fm |= os.ModeNamedPipe
+ case sshfx.ModeSymlink:
+ fm |= os.ModeSymlink
+ case sshfx.ModeRegular:
+ // nothing to do
+ case sshfx.ModeSocket:
+ fm |= os.ModeSocket
+ }
+
+ if sshfx.FileMode(mode)&sshfx.ModeSetUID != 0 {
+ fm |= os.ModeSetuid
+ }
+ if sshfx.FileMode(mode)&sshfx.ModeSetGID != 0 {
+ fm |= os.ModeSetgid
+ }
+ if sshfx.FileMode(mode)&sshfx.ModeSticky != 0 {
+ fm |= os.ModeSticky
+ }
+
+ return fm
+}
+
+// fromFileMode converts from the os.FileMode specification to sftp filemode bits
+func fromFileMode(mode os.FileMode) uint32 {
+ ret := sshfx.FileMode(mode & os.ModePerm)
+
+ switch mode & os.ModeType {
+ case os.ModeDevice | os.ModeCharDevice:
+ ret |= sshfx.ModeCharDevice
+ case os.ModeDevice:
+ ret |= sshfx.ModeDevice
+ case os.ModeDir:
+ ret |= sshfx.ModeDir
+ case os.ModeNamedPipe:
+ ret |= sshfx.ModeNamedPipe
+ case os.ModeSymlink:
+ ret |= sshfx.ModeSymlink
+ case 0:
+ ret |= sshfx.ModeRegular
+ case os.ModeSocket:
+ ret |= sshfx.ModeSocket
+ }
+
+ if mode&os.ModeSetuid != 0 {
+ ret |= sshfx.ModeSetUID
+ }
+ if mode&os.ModeSetgid != 0 {
+ ret |= sshfx.ModeSetGID
+ }
+ if mode&os.ModeSticky != 0 {
+ ret |= sshfx.ModeSticky
+ }
+
+ return uint32(ret)
+}
+
+const (
+ s_ISUID = uint32(sshfx.ModeSetUID)
+ s_ISGID = uint32(sshfx.ModeSetGID)
+ s_ISVTX = uint32(sshfx.ModeSticky)
+)
+
+// S_IFMT is a legacy export, and was brought in to support GOOS environments whose sysconfig.S_IFMT may be different from the value used internally by SFTP standards.
+// There should be no reason why you need to import it, or use it, but unexporting it could cause code to break in a way that cannot be readily fixed.
+// As such, we continue to export this value as the value used in the SFTP standard.
+//
+// Deprecated: Remove use of this value, and avoid any future use as well.
+// There is no alternative provided, you should never need to access this value.
+const S_IFMT = uint32(sshfx.ModeType)
diff --git a/stat_plan9.go b/stat_plan9.go
deleted file mode 100644
index 761abdf..0000000
--- a/stat_plan9.go
+++ /dev/null
@@ -1,103 +0,0 @@
-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
-}
-
-// isRegular returns true if the mode describes a regular file.
-func isRegular(mode uint32) bool {
- return mode&S_IFMT == syscall.S_IFREG
-}
-
-// 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(mode & os.ModePerm)
-
- switch mode & os.ModeType {
- case os.ModeDevice | os.ModeCharDevice:
- ret |= syscall.S_IFCHR
- case os.ModeDevice:
- ret |= syscall.S_IFBLK
- case os.ModeDir:
- ret |= syscall.S_IFDIR
- case os.ModeNamedPipe:
- ret |= syscall.S_IFIFO
- case os.ModeSymlink:
- ret |= syscall.S_IFLNK
- case 0:
- ret |= syscall.S_IFREG
- case os.ModeSocket:
- ret |= syscall.S_IFSOCK
- }
-
- return ret
-}
-
-// Plan 9 doesn't have setuid, setgid or sticky, but a Plan 9 client should
-// be able to send these bits to a POSIX server.
-const (
- s_ISUID = 04000
- s_ISGID = 02000
- s_ISVTX = 01000
-)
diff --git a/stat_posix.go b/stat_posix.go
deleted file mode 100644
index 5b870e2..0000000
--- a/stat_posix.go
+++ /dev/null
@@ -1,124 +0,0 @@
-//go:build !plan9
-// +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.EACCES, 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
-}
-
-// isRegular returns true if the mode describes a regular file.
-func isRegular(mode uint32) bool {
- return mode&S_IFMT == syscall.S_IFREG
-}
-
-// 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_ISUID != 0 {
- fm |= os.ModeSetuid
- }
- if mode&syscall.S_ISGID != 0 {
- fm |= os.ModeSetgid
- }
- 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(mode & os.ModePerm)
-
- switch mode & os.ModeType {
- case os.ModeDevice | os.ModeCharDevice:
- ret |= syscall.S_IFCHR
- case os.ModeDevice:
- ret |= syscall.S_IFBLK
- case os.ModeDir:
- ret |= syscall.S_IFDIR
- case os.ModeNamedPipe:
- ret |= syscall.S_IFIFO
- case os.ModeSymlink:
- ret |= syscall.S_IFLNK
- case 0:
- ret |= syscall.S_IFREG
- case os.ModeSocket:
- ret |= syscall.S_IFSOCK
- }
-
- if mode&os.ModeSetuid != 0 {
- ret |= syscall.S_ISUID
- }
- if mode&os.ModeSetgid != 0 {
- ret |= syscall.S_ISGID
- }
- if mode&os.ModeSticky != 0 {
- ret |= syscall.S_ISVTX
- }
-
- return ret
-}
-
-const (
- s_ISUID = syscall.S_ISUID
- s_ISGID = syscall.S_ISGID
- s_ISVTX = syscall.S_ISVTX
-)
diff --git a/syscall_fixed.go b/syscall_fixed.go
deleted file mode 100644
index e844308..0000000
--- a/syscall_fixed.go
+++ /dev/null
@@ -1,10 +0,0 @@
-//go:build plan9 || windows || (js && wasm)
-// +build plan9 windows js,wasm
-
-// Go defines S_IFMT on windows, plan9 and js/wasm as 0x1f000 instead of
-// 0xf000. None of the the other S_IFxyz values include the "1" (in 0x1f000)
-// which prevents them from matching the bitmask.
-
-package sftp
-
-const S_IFMT = 0xf000
diff --git a/syscall_good.go b/syscall_good.go
deleted file mode 100644
index 5005218..0000000
--- a/syscall_good.go
+++ /dev/null
@@ -1,10 +0,0 @@
-//go:build !plan9 && !windows && (!js || !wasm)
-// +build !plan9
-// +build !windows
-// +build !js !wasm
-
-package sftp
-
-import "syscall"
-
-const S_IFMT = syscall.S_IFMT