Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for zos/s390x #582

Merged
merged 4 commits into from Apr 6, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
11 changes: 5 additions & 6 deletions client.go
Expand Up @@ -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) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider that POSIX standard requires the assertion: O_RDONLY | O_WRONLY == O_RDWR[1]

However, due to constant folding, I think we’re still fine here regardless.

1: https://pubs.opengroup.org/onlinepubs/9699919799/functions/open.html

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 {
Expand All @@ -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 {
Expand Down
2 changes: 1 addition & 1 deletion client_integration_test.go
Expand Up @@ -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
Expand Down
42 changes: 42 additions & 0 deletions 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
}
45 changes: 45 additions & 0 deletions 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
}
7 changes: 7 additions & 0 deletions ls_formatting_test.go
Expand Up @@ -3,6 +3,7 @@ package sftp
import (
"os"
"regexp"
"runtime"
"strings"
"testing"
"time"
Expand Down Expand Up @@ -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]"
Expand Down
9 changes: 7 additions & 2 deletions request-server_test.go
Expand Up @@ -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("/")
Expand Down
9 changes: 7 additions & 2 deletions server_test.go
Expand Up @@ -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()
Expand Down
89 changes: 89 additions & 0 deletions 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
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The problem is that the sshfx package is internal and so no one else can import it. So, the given alternative isn’t useful.

I would say that the local OS might use a different value from what we need to use internally, and there is no reason anyone should need this specific internal value.

We can also merge, and then I can adjust it after.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah I see. That makes more sense. Better to have you leave the comment anyways. Thanks again

const S_IFMT = uint32(sshfx.ModeType)
103 changes: 0 additions & 103 deletions stat_plan9.go

This file was deleted.