Skip to content

Commit

Permalink
runtime,os,syscall,internal/poll: replace getdirentries on iOS
Browse files Browse the repository at this point in the history
The getdirentries syscall is considered private API on iOS and is
rejected by the App Store submission checks. Replace it with the
fdopendir/readdir_r/closedir syscalls.

Fixes #28984

Change-Id: I73341b124310e9cb34834a95f946769f337ec5b7
Reviewed-on: https://go-review.googlesource.com/c/153338
Reviewed-by: Keith Randall <khr@golang.org>
  • Loading branch information
Elias Naur authored and bradfitz committed Dec 13, 2018
1 parent 571d93e commit 9eb383e
Show file tree
Hide file tree
Showing 24 changed files with 400 additions and 128 deletions.
35 changes: 35 additions & 0 deletions src/internal/poll/fd_opendir_ios.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
// Copyright 2018 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// +build darwin
// +build arm arm64

package poll

import (
"syscall"
_ "unsafe" // for go:linkname
)

// OpenDir returns a pointer to a DIR structure suitable for
// ReadDir. In case of an error, the name of the failed
// syscall is returned along with a syscall.Errno.
func (fd *FD) OpenDir() (uintptr, string, error) {
// fdopendir(3) takes control of the file descriptor,
// so use a dup.
fd2, call, err := fd.Dup()
if err != nil {
return 0, call, err
}
dir, err := fdopendir(fd2)
if err != nil {
syscall.Close(fd2)
return 0, "fdopendir", err
}
return dir, "", nil
}

// Implemented in syscall/syscall_darwin.go.
//go:linkname fdopendir syscall.fdopendir
func fdopendir(fd int) (dir uintptr, err error)
87 changes: 87 additions & 0 deletions src/os/dir_ios.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// +build darwin
// +build arm arm64

package os

import (
"io"
"runtime"
"syscall"
"unsafe"
)

// Auxiliary information if the File describes a directory
type dirInfo struct {
dir uintptr // Pointer to DIR structure from dirent.h
}

func (d *dirInfo) close() {
if d.dir == 0 {
return
}
closedir(d.dir)
d.dir = 0
}

func (f *File) readdirnames(n int) (names []string, err error) {
if f.dirinfo == nil {
dir, call, errno := f.pfd.OpenDir()
if errno != nil {
return nil, wrapSyscallError(call, errno)
}
f.dirinfo = &dirInfo{
dir: dir,
}
}
d := f.dirinfo

size := n
if size <= 0 {
size = 100
n = -1
}

names = make([]string, 0, size)
var dirent syscall.Dirent
var entptr uintptr
for len(names) < size {
if res := readdir_r(d.dir, uintptr(unsafe.Pointer(&dirent)), uintptr(unsafe.Pointer(&entptr))); res != 0 {
return names, wrapSyscallError("readdir", syscall.Errno(res))
}
if entptr == 0 { // EOF
break
}
if dirent.Ino == 0 {
continue
}
name := (*[len(syscall.Dirent{}.Name)]byte)(unsafe.Pointer(&dirent.Name))[:]
for i, c := range name {
if c == 0 {
name = name[:i]
break
}
}
// Check for useless names before allocating a string.
if string(name) == "." || string(name) == ".." {
continue
}
names = append(names, string(name))
runtime.KeepAlive(f)
}
if n >= 0 && len(names) == 0 {
return names, io.EOF
}
return names, nil
}

// Implemented in syscall/syscall_darwin.go.

//go:linkname closedir syscall.closedir
func closedir(dir uintptr) (err error)

//go:linkname readdir_r syscall.readdir_r
func readdir_r(dir, entry, result uintptr) (res int)
36 changes: 9 additions & 27 deletions src/os/dir_unix.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// +build aix darwin dragonfly freebsd js,wasm linux nacl netbsd openbsd solaris
// +build aix darwin,!arm,!arm64 dragonfly freebsd js,wasm linux nacl netbsd openbsd solaris

package os

Expand All @@ -12,37 +12,19 @@ import (
"syscall"
)

// Auxiliary information if the File describes a directory
type dirInfo struct {
buf []byte // buffer for directory I/O
nbuf int // length of buf; return value from Getdirentries
bufp int // location of next record in buf.
}

const (
// More than 5760 to work around https://golang.org/issue/24015.
blockSize = 8192
)

func (f *File) readdir(n int) (fi []FileInfo, err error) {
dirname := f.name
if dirname == "" {
dirname = "."
}
names, err := f.Readdirnames(n)
fi = make([]FileInfo, 0, len(names))
for _, filename := range names {
fip, lerr := lstat(dirname + "/" + filename)
if IsNotExist(lerr) {
// File disappeared between readdir + stat.
// Just treat it as if it didn't exist.
continue
}
if lerr != nil {
return fi, lerr
}
fi = append(fi, fip)
}
if len(fi) == 0 && err == nil && n > 0 {
// Per File.Readdir, the slice must be non-empty or err
// must be non-nil if n > 0.
err = io.EOF
}
return fi, err
}
func (d *dirInfo) close() {}

func (f *File) readdirnames(n int) (names []string, err error) {
// If this file has no dirinfo, create one.
Expand Down
38 changes: 31 additions & 7 deletions src/os/file_unix.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ package os
import (
"internal/poll"
"internal/syscall/unix"
"io"
"runtime"
"syscall"
)
Expand Down Expand Up @@ -155,13 +156,6 @@ func newFile(fd uintptr, name string, kind newFileKind) *File {
return f
}

// Auxiliary information if the File describes a directory
type dirInfo struct {
buf []byte // buffer for directory I/O
nbuf int // length of buf; return value from Getdirentries
bufp int // location of next record in buf.
}

// epipecheck raises SIGPIPE if we get an EPIPE error on standard
// output or standard error. See the SIGPIPE docs in os/signal, and
// issue 11845.
Expand Down Expand Up @@ -230,6 +224,9 @@ func (file *file) close() error {
if file == nil {
return syscall.EINVAL
}
if file.dirinfo != nil {
file.dirinfo.close()
}
var err error
if e := file.pfd.Close(); e != nil {
if e == poll.ErrFileClosing {
Expand Down Expand Up @@ -358,3 +355,30 @@ func Symlink(oldname, newname string) error {
}
return nil
}

func (f *File) readdir(n int) (fi []FileInfo, err error) {
dirname := f.name
if dirname == "" {
dirname = "."
}
names, err := f.Readdirnames(n)
fi = make([]FileInfo, 0, len(names))
for _, filename := range names {
fip, lerr := lstat(dirname + "/" + filename)
if IsNotExist(lerr) {
// File disappeared between readdir + stat.
// Just treat it as if it didn't exist.
continue
}
if lerr != nil {
return fi, lerr
}
fi = append(fi, fip)
}
if len(fi) == 0 && err == nil && n > 0 {
// Per File.Readdir, the slice must be non-empty or err
// must be non-nil if n > 0.
err = io.EOF
}
return fi, err
}
11 changes: 11 additions & 0 deletions src/runtime/sys_darwin_32.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,14 @@ func syscall_syscall9(fn, a1, a2, a3, a4, a5, a6, a7, a8, a9 uintptr) (r1, r2, e
return
}
func syscall9()

//go:linkname syscall_syscallPtr syscall.syscallPtr
//go:nosplit
//go:cgo_unsafe_args
func syscall_syscallPtr(fn, a1, a2, a3 uintptr) (r1, r2, err uintptr) {
entersyscallblock()
libcCall(unsafe.Pointer(funcPC(syscallPtr)), unsafe.Pointer(&fn))
exitsyscall()
return
}
func syscallPtr()
5 changes: 5 additions & 0 deletions src/runtime/sys_darwin_386.s
Original file line number Diff line number Diff line change
Expand Up @@ -675,6 +675,11 @@ ok:
POPL BP
RET

// Not used on 386.
TEXT runtime·syscallPtr(SB),NOSPLIT,$0
MOVL $0xf1, 0xf1 // crash
RET

// syscall6 calls a function in libc on behalf of the syscall package.
// syscall6 takes a pointer to a struct like:
// struct {
Expand Down
11 changes: 11 additions & 0 deletions src/runtime/sys_darwin_64.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,14 @@ func syscall_syscallX(fn, a1, a2, a3 uintptr) (r1, r2, err uintptr) {
return
}
func syscallX()

//go:linkname syscall_syscallXPtr syscall.syscallXPtr
//go:nosplit
//go:cgo_unsafe_args
func syscall_syscallXPtr(fn, a1, a2, a3 uintptr) (r1, r2, err uintptr) {
entersyscallblock()
libcCall(unsafe.Pointer(funcPC(syscallXPtr)), unsafe.Pointer(&fn))
exitsyscall()
return
}
func syscallXPtr()
5 changes: 5 additions & 0 deletions src/runtime/sys_darwin_amd64.s
Original file line number Diff line number Diff line change
Expand Up @@ -637,6 +637,11 @@ ok:
POPQ BP
RET

// Not used on amd64.
TEXT runtime·syscallXPtr(SB),NOSPLIT,$0
MOVL $0xf1, 0xf1 // crash
RET

// syscall6 calls a function in libc on behalf of the syscall package.
// syscall6 takes a pointer to a struct like:
// struct {
Expand Down
23 changes: 23 additions & 0 deletions src/runtime/sys_darwin_arm.s
Original file line number Diff line number Diff line change
Expand Up @@ -417,6 +417,29 @@ TEXT runtime·syscall(SB),NOSPLIT,$0
ok:
RET

// syscallPtr is like syscall except the libc function reports an
// error by returning NULL.
TEXT runtime·syscallPtr(SB),NOSPLIT,$0
MOVW.W R0, -4(R13) // push structure pointer
MOVW 0(R0), R12 // fn
MOVW 8(R0), R1 // a2
MOVW 12(R0), R2 // a3
MOVW 4(R0), R0 // a1
BL (R12)
MOVW.P 4(R13), R2 // pop structure pointer
MOVW R0, 16(R2) // save r1
MOVW R1, 20(R2) // save r2
MOVW $0, R3
CMP R0, R3
BNE ok
MOVW.W R2, -4(R13) // push structure pointer
BL libc_error(SB)
MOVW (R0), R0
MOVW.P 4(R13), R2 // pop structure pointer
MOVW R0, 24(R2) // save err
ok:
RET

// syscall6 calls a function in libc on behalf of the syscall package.
// syscall6 takes a pointer to a struct like:
// struct {
Expand Down
28 changes: 28 additions & 0 deletions src/runtime/sys_darwin_arm64.s
Original file line number Diff line number Diff line change
Expand Up @@ -465,6 +465,34 @@ TEXT runtime·syscallX(SB),NOSPLIT,$0
ok:
RET

// syscallXPtr is like syscallX except that the libc function reports an
// error by returning NULL.
TEXT runtime·syscallXPtr(SB),NOSPLIT,$0
SUB $16, RSP // push structure pointer
MOVD R0, (RSP)

MOVD 0(R0), R12 // fn
MOVD 16(R0), R1 // a2
MOVD 24(R0), R2 // a3
MOVD 8(R0), R0 // a1
BL (R12)

MOVD (RSP), R2 // pop structure pointer
ADD $16, RSP
MOVD R0, 32(R2) // save r1
MOVD R1, 40(R2) // save r2
CMP $0, R0
BNE ok
SUB $16, RSP // push structure pointer
MOVD R2, (RSP)
BL libc_error(SB)
MOVW (R0), R0
MOVD (RSP), R2 // pop structure pointer
ADD $16, RSP
MOVD R0, 48(R2) // save err
ok:
RET

// syscall6 calls a function in libc on behalf of the syscall package.
// syscall6 takes a pointer to a struct like:
// struct {
Expand Down
2 changes: 1 addition & 1 deletion src/syscall/dirent_bsd_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// +build darwin dragonfly freebsd netbsd openbsd
// +build darwin,!arm,!arm64 dragonfly freebsd netbsd openbsd

package syscall_test

Expand Down
1 change: 0 additions & 1 deletion src/syscall/syscall_darwin.go
Original file line number Diff line number Diff line change
Expand Up @@ -270,7 +270,6 @@ func Kill(pid int, signum Signal) (err error) { return kill(pid, int(signum), 1)
//sys Fsync(fd int) (err error)
// Fsync is not called for os.File.Sync(). Please see internal/poll/fd_fsync_darwin.go
//sys Ftruncate(fd int, length int64) (err error)
//sys Getdirentries(fd int, buf []byte, basep *uintptr) (n int, err error) = SYS___getdirentries64
//sys Getdtablesize() (size int)
//sysnb Getegid() (egid int)
//sysnb Geteuid() (uid int)
Expand Down
1 change: 1 addition & 0 deletions src/syscall/syscall_darwin_386.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ func setTimeval(sec, usec int64) Timeval {

//sys Fstat(fd int, stat *Stat_t) (err error) = SYS_fstat64
//sys Fstatfs(fd int, stat *Statfs_t) (err error) = SYS_fstatfs64
//sys Getdirentries(fd int, buf []byte, basep *uintptr) (n int, err error) = SYS___getdirentries64
//sysnb Gettimeofday(tp *Timeval) (err error)
//sys Lstat(path string, stat *Stat_t) (err error) = SYS_lstat64
//sys Stat(path string, stat *Stat_t) (err error) = SYS_stat64
Expand Down
1 change: 1 addition & 0 deletions src/syscall/syscall_darwin_amd64.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ func setTimeval(sec, usec int64) Timeval {

//sys Fstat(fd int, stat *Stat_t) (err error) = SYS_fstat64
//sys Fstatfs(fd int, stat *Statfs_t) (err error) = SYS_fstatfs64
//sys Getdirentries(fd int, buf []byte, basep *uintptr) (n int, err error) = SYS___getdirentries64
//sysnb Gettimeofday(tp *Timeval) (err error)
//sys Lstat(path string, stat *Stat_t) (err error) = SYS_lstat64
//sys Stat(path string, stat *Stat_t) (err error) = SYS_stat64
Expand Down
Loading

0 comments on commit 9eb383e

Please sign in to comment.