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

sys/unix - fix looping for ReadDir #131

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
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
1,974 changes: 1,974 additions & 0 deletions syscall_zos_s390x.go

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions unix/dirent.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

//go:build aix || darwin || dragonfly || freebsd || linux || netbsd || openbsd || solaris
// +build aix darwin dragonfly freebsd linux netbsd openbsd solaris
//go:build aix || darwin || dragonfly || freebsd || linux || netbsd || openbsd || solaris || zos
// +build aix darwin dragonfly freebsd linux netbsd openbsd solaris zos

package unix

Expand Down
8 changes: 6 additions & 2 deletions unix/dirent_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

//go:build aix || darwin || dragonfly || freebsd || linux || netbsd || openbsd || solaris
// +build aix darwin dragonfly freebsd linux netbsd openbsd solaris
//go:build aix || darwin || dragonfly || freebsd || linux || netbsd || openbsd || solaris || zos
// +build aix darwin dragonfly freebsd linux netbsd openbsd solaris zos

package unix_test

Expand Down Expand Up @@ -45,6 +45,10 @@ func TestDirent(t *testing.T) {
}

buf := bytes.Repeat([]byte("DEADBEAF"), direntBufSize/8)
if runtime.GOOS == "zos" {
buf = bytes.Repeat([]byte("DEADBEAF"), (direntBufSize*2)/8)
}

fd, err := unix.Open(d, unix.O_RDONLY, 0)
if err != nil {
t.Fatalf("Open: %v", err)
Expand Down
4 changes: 2 additions & 2 deletions unix/getdirentries_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

//go:build darwin || dragonfly || freebsd || openbsd || netbsd
// +build darwin dragonfly freebsd openbsd netbsd
//go:build darwin || dragonfly || freebsd || openbsd || netbsd || zos
// +build darwin dragonfly freebsd openbsd netbsd zos

package unix_test

Expand Down
192 changes: 174 additions & 18 deletions unix/syscall_zos_s390x.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,15 @@ package unix

import (
"bytes"
"fmt"
"runtime"
"sort"
"strings"
"sync"
"syscall"
"unsafe"

"golang.org/x/sys/internal/unsafeheader"
)

const (
Expand Down Expand Up @@ -55,7 +59,13 @@ func (d *Dirent) NameString() string {
if d == nil {
return ""
}
return string(d.Name[:d.Namlen])
s := string(d.Name[:])
idx := strings.Index(s, string(rune(0)))
if idx == -1 {
return s
} else {
return s[:idx]
}
}

func (sa *SockaddrInet4) sockaddr() (unsafe.Pointer, _Socklen, error) {
Expand Down Expand Up @@ -1210,24 +1220,12 @@ func Opendir(name string) (uintptr, error) {
// clearsyscall.Errno resets the errno value to 0.
func clearErrno()

func Readdir(dir uintptr) (*Dirent, error) {
var ent Dirent
var res uintptr
// __readdir_r_a returns errno at the end of the directory stream, rather than 0.
// Therefore to avoid false positives we clear errno before calling it.

// TODO(neeilan): Commented this out to get sys/unix compiling on z/OS. Uncomment and fix. Error: "undefined: clearsyscall"
//clearsyscall.Errno() // TODO(mundaym): check pre-emption rules.

e, _, _ := syscall_syscall(SYS___READDIR_R_A, dir, uintptr(unsafe.Pointer(&ent)), uintptr(unsafe.Pointer(&res)))
var err error
if e != 0 {
err = errnoErr(Errno(e))
}
if res == 0 {
return nil, err
func Readdir_r(dirp uintptr, entry *direntLE, result **direntLE) (err error) {
r0, _, e1 := syscall_syscall(SYS___READDIR_R_A, dirp, uintptr(unsafe.Pointer(entry)), uintptr(unsafe.Pointer(result)))
if int64(r0) == -1 {
err = errnoErr(Errno(e1))
}
return &ent, err
return
}

func Closedir(dir uintptr) error {
Expand Down Expand Up @@ -1821,3 +1819,161 @@ func Unmount(name string, mtm int) (err error) {
}
return err
}

func fdToPath(dirfd int) (path string, err error) {
var buffer [1024]byte
// w_ctrl()
ret := runtime.CallLeFuncByPtr(runtime.XplinkLibvec+0x1a2<<4,
[]uintptr{uintptr(dirfd), 17, 1024, uintptr(unsafe.Pointer(&buffer[0]))})
if ret == 0 {
zb := bytes.IndexByte(buffer[:], 0)
if zb == -1 {
zb = len(buffer)
}
// __e2a_l()
runtime.CallLeFuncByPtr(runtime.XplinkLibvec+0x6e3<<4,
[]uintptr{uintptr(unsafe.Pointer(&buffer[0])), uintptr(zb)})
return string(buffer[:zb]), nil
}
// __errno()
errno := int(*(*int32)(unsafe.Pointer(runtime.CallLeFuncByPtr(runtime.XplinkLibvec+0x156<<4,
[]uintptr{}))))
// __errno2()
errno2 := int(runtime.CallLeFuncByPtr(runtime.XplinkLibvec+0x157<<4,
[]uintptr{}))
// strerror_r()
ret = runtime.CallLeFuncByPtr(runtime.XplinkLibvec+0xb35<<4,
[]uintptr{uintptr(errno), uintptr(unsafe.Pointer(&buffer[0])), 1024})
if ret == 0 {
zb := bytes.IndexByte(buffer[:], 0)
if zb == -1 {
zb = len(buffer)
}
return "", fmt.Errorf("%s (errno2=0x%x)", buffer[:zb], errno2)
} else {
return "", fmt.Errorf("fdToPath errno %d (errno2=0x%x)", errno, errno2)
}
}

func direntLeToDirentUnix(dirent *direntLE, dir uintptr, path string) (Dirent, error) {
var d Dirent

d.Ino = uint64(dirent.Ino)
offset, err := Telldir(dir)
if err != nil {
return d, err
}

d.Off = int64(offset)
d.Reclen = dirent.Reclen
s := string(bytes.Split(dirent.Name[:], []byte{0})[0])
var st Stat_t
path = path + "/" + s
err = Lstat(path, &st)
if err != nil {
return d, err
}

d.Type = uint8(st.Mode >> 24)
copy(d.Name[:], s)
return d, err
}

func Getdirentries(fd int, buf []byte, basep *uintptr) (n int, err error) {
// Simulation of Getdirentries port from the Darwin implementation.
// COMMENTS FROM DARWIN:
// It's not the full required semantics, but should handle the case
// of calling Getdirentries or ReadDirent repeatedly.
// It won't handle assigning the results of lseek to *basep, or handle
// the directory being edited underfoot.

skip, err := Seek(fd, 0, 1 /* SEEK_CUR */)
if err != nil {
return 0, err
}

// Get path from fd to avoid unavailable call (fdopendir)
path, err := fdToPath(fd)
if err != nil {
return 0, err
}
d, err := Opendir(path)
if err != nil {
return 0, err
}
defer Closedir(d)

var cnt int64
for {
var entryLE direntLE
var entrypLE *direntLE
e := Readdir_r(d, &entryLE, &entrypLE)
if e != nil {
return n, e
}
if entrypLE == nil {
break
}
if skip > 0 {
skip--
cnt++
continue
}

// Dirent on zos has a different structure
entry, e := direntLeToDirentUnix(&entryLE, d, path)
if e != nil {
return n, e
}

reclen := int(entry.Reclen)
if reclen > len(buf) {
// Not enough room. Return for now.
// The counter will let us know where we should start up again.
// Note: this strategy for suspending in the middle and
// restarting is O(n^2) in the length of the directory. Oh well.
break
}

// Copy entry into return buffer.
var s []byte
hdr := (*unsafeheader.Slice)(unsafe.Pointer(&s))
hdr.Data = unsafe.Pointer(&entry)
hdr.Cap = reclen
hdr.Len = reclen
copy(buf, s)

buf = buf[reclen:]
n += reclen
cnt++
}
// Set the seek offset of the input fd to record
// how many files we've already returned.
_, err = Seek(fd, cnt, 0 /* SEEK_SET */)
if err != nil {
return n, err
}

return n, nil
}

func ReadDirent(fd int, buf []byte) (n int, err error) {
var base = (*uintptr)(unsafe.Pointer(new(uint64)))
return Getdirentries(fd, buf, base)
}

func direntIno(buf []byte) (uint64, bool) {
return readInt(buf, unsafe.Offsetof(Dirent{}.Ino), unsafe.Sizeof(Dirent{}.Ino))
}

func direntReclen(buf []byte) (uint64, bool) {
return readInt(buf, unsafe.Offsetof(Dirent{}.Reclen), unsafe.Sizeof(Dirent{}.Reclen))
}

func direntNamlen(buf []byte) (uint64, bool) {
reclen, ok := direntReclen(buf)
if !ok {
return 0, false
}
return reclen - uint64(unsafe.Offsetof(Dirent{}.Name)), true
}
52 changes: 52 additions & 0 deletions unix/syscall_zos_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -861,3 +861,55 @@ func TestSelect(t *testing.T) {
break
}
}

func TestReadDirent(t *testing.T) {
// Create temporary directory and files
tempDir, err := ioutil.TempDir("", "TestReadDirent")
if err != nil {
t.Fatalf("TempDir: %v", err)
}
defer os.RemoveAll(tempDir)
_, err = ioutil.TempFile(tempDir, "ReadDirent_test_file")
if err != nil {
t.Fatalf("TempFile: %v", err)
}
_, err = ioutil.TempFile(tempDir, "ReadDirent_test_file")
if err != nil {
t.Fatalf("TempFile: %v", err)
}

tempSubDir, err := ioutil.TempDir(tempDir, "ReadDirent_SubDir")
if err != nil {
t.Fatalf("TempDir: %v", err)
}
_, err = ioutil.TempFile(tempSubDir, "ReadDirent_subDir_test_file")
if err != nil {
t.Fatalf("TempFile: %v", err)
}

// Get fd of tempDir
dir, err := os.Open(tempDir)
if err != nil {
t.Fatalf("Open: %v", err)
}
fd := int(dir.Fd())

// Run Getdirentries
buf := make([]byte, 2048)
n, err := unix.ReadDirent(fd, buf)
if err != nil {
t.Fatalf("ReadDirent: %v", err)
}
if n == 0 {
t.Fatalf("ReadDirent: 0 bytes read")
}

names := make([]string, 0)
consumed, count, _ := unix.ParseDirent(buf, 100, names)
if consumed == 0 {
t.Fatalf("ParseDirent: consumed 0 bytes")
}
if count != 3 {
t.Fatalf("ParseDirent: only recorded %d entries, expected 3", count)
}
}
9 changes: 9 additions & 0 deletions unix/zerrors_zos_s390x.go
Original file line number Diff line number Diff line change
Expand Up @@ -377,6 +377,15 @@ const (
S_IFSHARELIB = 0x00000010
S_IFMT = 0xFF000000
S_IFMST = 0x00FF0000
DT_BLK = 0x6
DT_CHR = 0x2
DT_DIR = 0x1
DT_FIFO = 0x4
DT_LNK = 0x5
DT_REG = 0x3
DT_SOCK = 0x7
DT_UNKNOWN = 0x0
DT_WHT = 0xe
TCP_KEEPALIVE = 0x8
TCP_NODELAY = 0x1
TCP_INFO = 0xb
Expand Down
11 changes: 10 additions & 1 deletion unix/ztypes_zos_s390x.go
Original file line number Diff line number Diff line change
Expand Up @@ -339,14 +339,23 @@ type Statfs_t struct {
Flags uint64
}

type Dirent struct {
type direntLE struct {
Reclen uint16
Namlen uint16
Ino uint32
Extra uintptr
Name [256]byte
}

type Dirent struct {
Ino uint64
Off int64
Reclen uint16
Type uint8
Name [256]uint8
_ [5]byte
}

type FdSet struct {
Bits [64]int32
}
Expand Down