diff --git a/client.go b/client.go index 5f457221..9423b4dd 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 { 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_posix.go b/stat_posix.go index 5b870e23..a15b9c3b 100644 --- a/stat_posix.go +++ b/stat_posix.go @@ -1,5 +1,6 @@ -//go:build !plan9 +//go:build !plan9 && !zos // +build !plan9 +// +build !zos package sftp diff --git a/stat_zos.go b/stat_zos.go new file mode 100644 index 00000000..3643e5ea --- /dev/null +++ b/stat_zos.go @@ -0,0 +1,137 @@ +//go:build zos +// +build zos + +package sftp + +import ( + "os" + "syscall" +) + +const EBADF = syscall.EBADF + +// z/OS syscall constants are not compatible with the stat structure returned +// from the server. Define the OpenBSD ones here instead. +const ( + S_IFMT = 0xF000 + S_IFIFO = 0x1000 + S_IFCHR = 0x2000 + S_IFDIR = 0x4000 + S_IFBLK = 0x6000 + S_IFREG = 0x8000 + S_IFLNK = 0xA000 + S_IFSOCK = 0xC000 +) + +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 == 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 S_IFBLK: + fm |= os.ModeDevice + case S_IFCHR: + fm |= os.ModeDevice | os.ModeCharDevice + case S_IFDIR: + fm |= os.ModeDir + case S_IFIFO: + fm |= os.ModeNamedPipe + case S_IFLNK: + fm |= os.ModeSymlink + case S_IFREG: + // nothing to do + case 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 |= S_IFCHR + case os.ModeDevice: + ret |= S_IFBLK + case os.ModeDir: + ret |= S_IFDIR + case os.ModeNamedPipe: + ret |= S_IFIFO + case os.ModeSymlink: + ret |= S_IFLNK + case 0: + ret |= S_IFREG + case os.ModeSocket: + ret |= 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_good.go b/syscall_good.go index 50052189..235d6039 100644 --- a/syscall_good.go +++ b/syscall_good.go @@ -1,6 +1,7 @@ -//go:build !plan9 && !windows && (!js || !wasm) +//go:build !plan9 && !windows && !zos && (!js || !wasm) // +build !plan9 // +build !windows +// +build !zos // +build !js !wasm package sftp