diff --git a/client.go b/client.go index 5f457221..3a9a8bf1 100644 --- a/client.go +++ b/client.go @@ -2113,13 +2113,12 @@ func normaliseError(err error) error { // 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 @@ func toPflags(f int) uint32 { // 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 35ccbea5..546ba211 100644 --- a/client_integration_test.go +++ b/client_integration_test.go @@ -974,7 +974,7 @@ func TestClientSetuid(t *testing.T) { 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 00000000..cf9d3902 --- /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 00000000..cd87e1b5 --- /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 8d0fefbc..d6e5e036 100644 --- a/ls_formatting_test.go +++ b/ls_formatting_test.go @@ -3,6 +3,7 @@ package sftp import ( "os" "regexp" + "runtime" "strings" "testing" "time" @@ -101,6 +102,12 @@ func runLsTestHelper(t *testing.T, result, expectedType, path string) { 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/request-server_test.go b/request-server_test.go index 6825bf43..668d803a 100644 --- a/request-server_test.go +++ b/request-server_test.go @@ -780,8 +780,13 @@ func TestRequestReaddir(t *testing.T) { } } _, 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 4cec3123..5bac81eb 100644 --- a/server_test.go +++ b/server_test.go @@ -99,9 +99,14 @@ 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" { + 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 00000000..a34a98f5 --- /dev/null +++ b/stat.go @@ -0,0 +1,89 @@ +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. `sshfx.ModeType` should be used instead +const S_IFMT = uint32(sshfx.ModeType) diff --git a/stat_plan9.go b/stat_plan9.go deleted file mode 100644 index 761abdf5..00000000 --- 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 5b870e23..00000000 --- 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 e8443083..00000000 --- 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 50052189..00000000 --- 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