runtime: testing runtime
This commit is contained in:
180
runtime/internal/lib/os/dir.go
Normal file
180
runtime/internal/lib/os/dir.go
Normal file
@@ -0,0 +1,180 @@
|
||||
package os
|
||||
|
||||
import (
|
||||
"io/fs"
|
||||
"sort"
|
||||
origSyscall "syscall"
|
||||
"unsafe"
|
||||
|
||||
c "github.com/goplus/llgo/runtime/internal/clite"
|
||||
"github.com/goplus/llgo/runtime/internal/clite/os"
|
||||
"github.com/goplus/llgo/runtime/internal/lib/internal/bytealg"
|
||||
"github.com/goplus/llgo/runtime/internal/lib/syscall"
|
||||
)
|
||||
|
||||
type readdirMode int
|
||||
|
||||
const (
|
||||
readdirName readdirMode = iota
|
||||
readdirDirEntry
|
||||
readdirFileInfo
|
||||
)
|
||||
|
||||
type DirEntry = fs.DirEntry
|
||||
|
||||
func (f *File) Readdirnames(n int) (names []string, err error) {
|
||||
if f == nil {
|
||||
return nil, ErrInvalid
|
||||
}
|
||||
|
||||
entries, err := f.ReadDir(n)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
names = make([]string, len(entries))
|
||||
for i, entry := range entries {
|
||||
names[i] = entry.Name()
|
||||
}
|
||||
return names, err
|
||||
}
|
||||
|
||||
func open(path string, flag int, perm uint32) (int, error) {
|
||||
fd, err := syscall.Open(path, flag, perm)
|
||||
return fd, err
|
||||
}
|
||||
|
||||
func openDirNolog(name string) (*File, error) {
|
||||
var (
|
||||
r int
|
||||
e error
|
||||
)
|
||||
ignoringEINTR(func() error {
|
||||
r, e = open(name, O_RDONLY|origSyscall.O_CLOEXEC, 0)
|
||||
return e
|
||||
})
|
||||
if e != nil {
|
||||
return nil, &PathError{Op: "open", Path: name, Err: e}
|
||||
}
|
||||
|
||||
if !supportsCloseOnExec {
|
||||
origSyscall.CloseOnExec(r)
|
||||
}
|
||||
|
||||
f := newFile(r, name, kindNoPoll)
|
||||
return f, nil
|
||||
}
|
||||
|
||||
func openDir(name string) (*File, error) {
|
||||
return openDirNolog(name)
|
||||
}
|
||||
|
||||
func ReadDir(name string) ([]DirEntry, error) {
|
||||
f, err := openDir(name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
dirs, err := f.ReadDir(-1)
|
||||
sort.Slice(dirs, func(i, j int) bool {
|
||||
return bytealg.CompareString(dirs[i].Name(), dirs[j].Name()) < 0
|
||||
})
|
||||
return dirs, err
|
||||
}
|
||||
|
||||
//go:linkname c_fdopendir C.fdopendir
|
||||
func c_fdopendir(fd c.Int) uintptr
|
||||
|
||||
func fdopendir(fd int) (dir uintptr, err error) {
|
||||
return c_fdopendir(c.Int(fd)), nil
|
||||
}
|
||||
|
||||
//go:linkname c_closedir C.closedir
|
||||
func c_closedir(dir uintptr) c.Int
|
||||
|
||||
func closedir(dir uintptr) error {
|
||||
if c_closedir(dir) != 0 {
|
||||
return syscall.Errno(os.Errno())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
//go:linkname c_readdir C.readdir
|
||||
func c_readdir(dir uintptr) ([]syscall.Dirent, error)
|
||||
|
||||
func readdir(dir uintptr) ([]syscall.Dirent, error) {
|
||||
return c_readdir(dir)
|
||||
}
|
||||
|
||||
func (f *File) ReadDir(n int) (dirents []DirEntry, err error) {
|
||||
if f == nil {
|
||||
return nil, ErrInvalid
|
||||
}
|
||||
|
||||
// Open directory using file descriptor
|
||||
dir, err := fdopendir(int(f.fd))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer closedir(dir)
|
||||
|
||||
// Match Readdir and Readdirnames: don't return nil slices.
|
||||
dirents = []DirEntry{}
|
||||
|
||||
// Read directory entries
|
||||
for n < 0 || len(dirents) < n {
|
||||
entries, err := readdir(dir)
|
||||
if err != nil {
|
||||
return dirents, err
|
||||
}
|
||||
if len(entries) == 0 {
|
||||
break
|
||||
}
|
||||
|
||||
for _, entry := range entries {
|
||||
// Convert syscall.Dirent to fs.DirEntry
|
||||
name := bytesToString((*[1024]byte)(unsafe.Pointer(&entry.Name[0]))[:])
|
||||
if name == "." || name == ".." {
|
||||
continue
|
||||
}
|
||||
|
||||
typ := fs.FileMode(0)
|
||||
switch entry.Type {
|
||||
case origSyscall.DT_REG:
|
||||
typ = 0
|
||||
case origSyscall.DT_DIR:
|
||||
typ = fs.ModeDir
|
||||
case origSyscall.DT_LNK:
|
||||
typ = fs.ModeSymlink
|
||||
case origSyscall.DT_SOCK:
|
||||
typ = fs.ModeSocket
|
||||
case origSyscall.DT_FIFO:
|
||||
typ = fs.ModeNamedPipe
|
||||
case origSyscall.DT_CHR:
|
||||
typ = fs.ModeCharDevice
|
||||
case origSyscall.DT_BLK:
|
||||
typ = fs.ModeDevice
|
||||
}
|
||||
|
||||
dirents = append(dirents, &unixDirent{
|
||||
parent: f.name,
|
||||
name: name,
|
||||
typ: typ,
|
||||
})
|
||||
|
||||
if n > 0 && len(dirents) >= n {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return dirents, nil
|
||||
}
|
||||
|
||||
// bytesToString converts byte slice to string without allocation.
|
||||
func bytesToString(b []byte) string {
|
||||
var i int
|
||||
for i = 0; i < len(b) && b[i] != 0; i++ {
|
||||
}
|
||||
return string(b[0:i])
|
||||
}
|
||||
@@ -16,7 +16,6 @@
|
||||
|
||||
package exec
|
||||
|
||||
// llgo:skipall
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
@@ -33,6 +32,9 @@ import (
|
||||
"github.com/goplus/llgo/runtime/internal/lib/internal/syscall/execenv"
|
||||
)
|
||||
|
||||
// llgo:skipall
|
||||
type _exec struct{}
|
||||
|
||||
// Error is returned by LookPath when it fails to classify a file as an
|
||||
// executable.
|
||||
type Error struct {
|
||||
|
||||
@@ -59,8 +59,12 @@ func startProcess(name string, argv []string, attr *ProcAttr) (p *Process, err e
|
||||
// runtime.KeepAlive(attr)
|
||||
|
||||
if e != nil {
|
||||
return nil, &PathError{Op: "fork/exec", Path: name, Err: e}
|
||||
// TODO(lijie): workaround with type assertion
|
||||
if r, ok := e.(syscall.Errno); !ok || r != 0 {
|
||||
return nil, &PathError{Op: "fork/exec", Path: name, Err: e}
|
||||
}
|
||||
}
|
||||
println("StartProcess", pid, h, e)
|
||||
|
||||
return newProcess(pid, h), nil
|
||||
}
|
||||
|
||||
@@ -16,7 +16,6 @@
|
||||
|
||||
package os
|
||||
|
||||
// llgo:skipall
|
||||
import (
|
||||
"errors"
|
||||
"runtime"
|
||||
@@ -27,6 +26,9 @@ import (
|
||||
"github.com/goplus/llgo/runtime/internal/clite/os"
|
||||
)
|
||||
|
||||
// llgo:skipall
|
||||
type _os struct{}
|
||||
|
||||
const (
|
||||
LLGoPackage = true
|
||||
)
|
||||
|
||||
@@ -53,3 +53,7 @@ func MkdirAll(path string, perm FileMode) error {
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func RemoveAll(path string) error {
|
||||
return removeAll(path)
|
||||
}
|
||||
|
||||
222
runtime/internal/lib/os/removeall_at.go
Normal file
222
runtime/internal/lib/os/removeall_at.go
Normal file
@@ -0,0 +1,222 @@
|
||||
// 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.
|
||||
|
||||
//go:build unix
|
||||
|
||||
package os
|
||||
|
||||
import (
|
||||
"io"
|
||||
"syscall"
|
||||
origSyscall "syscall"
|
||||
"unsafe"
|
||||
|
||||
c "github.com/goplus/llgo/runtime/internal/clite"
|
||||
"github.com/goplus/llgo/runtime/internal/clite/os"
|
||||
"github.com/goplus/llgo/runtime/internal/lib/internal/syscall/unix"
|
||||
)
|
||||
|
||||
func removeAll(path string) error {
|
||||
if path == "" {
|
||||
// fail silently to retain compatibility with previous behavior
|
||||
// of RemoveAll. See issue 28830.
|
||||
return nil
|
||||
}
|
||||
|
||||
// The rmdir system call does not permit removing ".",
|
||||
// so we don't permit it either.
|
||||
if endsWithDot(path) {
|
||||
return &PathError{Op: "RemoveAll", Path: path, Err: origSyscall.EINVAL}
|
||||
}
|
||||
|
||||
// Simple case: if Remove works, we're done.
|
||||
err := Remove(path)
|
||||
if err == nil || IsNotExist(err) {
|
||||
return nil
|
||||
}
|
||||
|
||||
// RemoveAll recurses by deleting the path base from
|
||||
// its parent directory
|
||||
parentDir, base := splitPath(path)
|
||||
|
||||
parent, err := Open(parentDir)
|
||||
if IsNotExist(err) {
|
||||
// If parent does not exist, base cannot exist. Fail silently
|
||||
return nil
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer parent.Close()
|
||||
|
||||
if err := removeAllFrom(parent, base); err != nil {
|
||||
if pathErr, ok := err.(*PathError); ok {
|
||||
pathErr.Path = parentDir + string(PathSeparator) + pathErr.Path
|
||||
err = pathErr
|
||||
}
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func removeAllFrom(parent *File, base string) error {
|
||||
p, err := syscall.BytePtrFromString(base)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
parentFd := int(parent.Fd())
|
||||
// Simple case: if Unlink (aka remove) works, we're done.
|
||||
err = ignoringEINTR(func() error {
|
||||
if os.Unlinkat(c.Int(parentFd), (*c.Char)(unsafe.Pointer(p)), 0) < 0 {
|
||||
return origSyscall.Errno(os.Errno())
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err == nil || IsNotExist(err) {
|
||||
return nil
|
||||
}
|
||||
|
||||
// EISDIR means that we have a directory, and we need to
|
||||
// remove its contents.
|
||||
// EPERM or EACCES means that we don't have write permission on
|
||||
// the parent directory, but this entry might still be a directory
|
||||
// whose contents need to be removed.
|
||||
// Otherwise just return the error.
|
||||
if err != origSyscall.EISDIR && err != origSyscall.EPERM && err != origSyscall.EACCES {
|
||||
return &PathError{Op: "unlinkat", Path: base, Err: err}
|
||||
}
|
||||
uErr := err
|
||||
|
||||
// Remove the directory's entries.
|
||||
var recurseErr error
|
||||
for {
|
||||
const reqSize = 1024
|
||||
var respSize int
|
||||
|
||||
// Open the directory to recurse into
|
||||
file, err := openDirAt(parentFd, base)
|
||||
if err != nil {
|
||||
if IsNotExist(err) {
|
||||
return nil
|
||||
}
|
||||
if err == origSyscall.ENOTDIR || err == unix.NoFollowErrno {
|
||||
// Not a directory; return the error from the unix.Unlinkat.
|
||||
return &PathError{Op: "unlinkat", Path: base, Err: uErr}
|
||||
}
|
||||
recurseErr = &PathError{Op: "openfdat", Path: base, Err: err}
|
||||
break
|
||||
}
|
||||
|
||||
for {
|
||||
numErr := 0
|
||||
|
||||
names, readErr := file.Readdirnames(reqSize)
|
||||
// Errors other than EOF should stop us from continuing.
|
||||
if readErr != nil && readErr != io.EOF {
|
||||
file.Close()
|
||||
if IsNotExist(readErr) {
|
||||
return nil
|
||||
}
|
||||
return &PathError{Op: "readdirnames", Path: base, Err: readErr}
|
||||
}
|
||||
|
||||
respSize = len(names)
|
||||
for _, name := range names {
|
||||
err := removeAllFrom(file, name)
|
||||
if err != nil {
|
||||
if pathErr, ok := err.(*PathError); ok {
|
||||
pathErr.Path = base + string(PathSeparator) + pathErr.Path
|
||||
}
|
||||
numErr++
|
||||
if recurseErr == nil {
|
||||
recurseErr = err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If we can delete any entry, break to start new iteration.
|
||||
// Otherwise, we discard current names, get next entries and try deleting them.
|
||||
if numErr != reqSize {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// Removing files from the directory may have caused
|
||||
// the OS to reshuffle it. Simply calling Readdirnames
|
||||
// again may skip some entries. The only reliable way
|
||||
// to avoid this is to close and re-open the
|
||||
// directory. See issue 20841.
|
||||
file.Close()
|
||||
|
||||
// Finish when the end of the directory is reached
|
||||
if respSize < reqSize {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// Remove the directory itself.
|
||||
unlinkError := ignoringEINTR(func() error {
|
||||
if os.Unlinkat(c.Int(parentFd), (*c.Char)(unsafe.Pointer(p)), unix.AT_REMOVEDIR) < 0 {
|
||||
return origSyscall.Errno(os.Errno())
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if unlinkError == nil || IsNotExist(unlinkError) {
|
||||
return nil
|
||||
}
|
||||
|
||||
if recurseErr != nil {
|
||||
return recurseErr
|
||||
}
|
||||
return &PathError{Op: "unlinkat", Path: base, Err: unlinkError}
|
||||
}
|
||||
|
||||
// openDirAt opens a directory name relative to the directory referred to by
|
||||
// the file descriptor dirfd. If name is anything but a directory (this
|
||||
// includes a symlink to one), it should return an error. Other than that this
|
||||
// should act like openFileNolog.
|
||||
//
|
||||
// This acts like openFileNolog rather than OpenFile because
|
||||
// we are going to (try to) remove the file.
|
||||
// The contents of this file are not relevant for test caching.
|
||||
func openDirAt(dirfd int, name string) (*File, error) {
|
||||
p, err := syscall.BytePtrFromString(name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var r int
|
||||
for {
|
||||
var e error
|
||||
r = int(os.Openat(c.Int(dirfd), (*c.Char)(unsafe.Pointer(p)), origSyscall.O_RDONLY|origSyscall.O_CLOEXEC|origSyscall.O_DIRECTORY|origSyscall.O_NOFOLLOW, 0))
|
||||
if r >= 0 {
|
||||
break
|
||||
}
|
||||
e = origSyscall.Errno(r)
|
||||
|
||||
// See comment in openFileNolog.
|
||||
if e == origSyscall.EINTR {
|
||||
continue
|
||||
}
|
||||
|
||||
return nil, e
|
||||
}
|
||||
|
||||
if !supportsCloseOnExec {
|
||||
origSyscall.CloseOnExec(r)
|
||||
}
|
||||
|
||||
// We use kindNoPoll because we know that this is a directory.
|
||||
return newFile(r, name, kindNoPoll), nil
|
||||
}
|
||||
|
||||
// endsWithDot reports whether the final component of path is ".".
|
||||
func endsWithDot(path string) bool {
|
||||
if path == "." {
|
||||
return true
|
||||
}
|
||||
if len(path) >= 2 && path[len(path)-1] == '.' && IsPathSeparator(path[len(path)-2]) {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
142
runtime/internal/lib/os/removeall_noat.go
Normal file
142
runtime/internal/lib/os/removeall_noat.go
Normal file
@@ -0,0 +1,142 @@
|
||||
// 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.
|
||||
|
||||
//go:build !unix
|
||||
|
||||
package os
|
||||
|
||||
import (
|
||||
"io"
|
||||
"runtime"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
func removeAll(path string) error {
|
||||
if path == "" {
|
||||
// fail silently to retain compatibility with previous behavior
|
||||
// of RemoveAll. See issue 28830.
|
||||
return nil
|
||||
}
|
||||
|
||||
// The rmdir system call permits removing "." on Plan 9,
|
||||
// so we don't permit it to remain consistent with the
|
||||
// "at" implementation of RemoveAll.
|
||||
if endsWithDot(path) {
|
||||
return &PathError{Op: "RemoveAll", Path: path, Err: syscall.EINVAL}
|
||||
}
|
||||
|
||||
// Simple case: if Remove works, we're done.
|
||||
err := Remove(path)
|
||||
if err == nil || IsNotExist(err) {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Otherwise, is this a directory we need to recurse into?
|
||||
dir, serr := Lstat(path)
|
||||
if serr != nil {
|
||||
if serr, ok := serr.(*PathError); ok && (IsNotExist(serr.Err) || serr.Err == syscall.ENOTDIR) {
|
||||
return nil
|
||||
}
|
||||
return serr
|
||||
}
|
||||
if !dir.IsDir() {
|
||||
// Not a directory; return the error from Remove.
|
||||
return err
|
||||
}
|
||||
|
||||
// Remove contents & return first error.
|
||||
err = nil
|
||||
for {
|
||||
fd, err := Open(path)
|
||||
if err != nil {
|
||||
if IsNotExist(err) {
|
||||
// Already deleted by someone else.
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
const reqSize = 1024
|
||||
var names []string
|
||||
var readErr error
|
||||
|
||||
for {
|
||||
numErr := 0
|
||||
names, readErr = fd.Readdirnames(reqSize)
|
||||
|
||||
for _, name := range names {
|
||||
err1 := RemoveAll(path + string(PathSeparator) + name)
|
||||
if err == nil {
|
||||
err = err1
|
||||
}
|
||||
if err1 != nil {
|
||||
numErr++
|
||||
}
|
||||
}
|
||||
|
||||
// If we can delete any entry, break to start new iteration.
|
||||
// Otherwise, we discard current names, get next entries and try deleting them.
|
||||
if numErr != reqSize {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// Removing files from the directory may have caused
|
||||
// the OS to reshuffle it. Simply calling Readdirnames
|
||||
// again may skip some entries. The only reliable way
|
||||
// to avoid this is to close and re-open the
|
||||
// directory. See issue 20841.
|
||||
fd.Close()
|
||||
|
||||
if readErr == io.EOF {
|
||||
break
|
||||
}
|
||||
// If Readdirnames returned an error, use it.
|
||||
if err == nil {
|
||||
err = readErr
|
||||
}
|
||||
if len(names) == 0 {
|
||||
break
|
||||
}
|
||||
|
||||
// We don't want to re-open unnecessarily, so if we
|
||||
// got fewer than request names from Readdirnames, try
|
||||
// simply removing the directory now. If that
|
||||
// succeeds, we are done.
|
||||
if len(names) < reqSize {
|
||||
err1 := Remove(path)
|
||||
if err1 == nil || IsNotExist(err1) {
|
||||
return nil
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
// We got some error removing the
|
||||
// directory contents, and since we
|
||||
// read fewer names than we requested
|
||||
// there probably aren't more files to
|
||||
// remove. Don't loop around to read
|
||||
// the directory again. We'll probably
|
||||
// just get the same error.
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Remove directory.
|
||||
err1 := Remove(path)
|
||||
if err1 == nil || IsNotExist(err1) {
|
||||
return nil
|
||||
}
|
||||
if runtime.GOOS == "windows" && IsPermission(err1) {
|
||||
if fs, err := Stat(path); err == nil {
|
||||
if err = Chmod(path, FileMode(0200|int(fs.Mode()))); err == nil {
|
||||
err1 = Remove(path)
|
||||
}
|
||||
}
|
||||
}
|
||||
if err == nil {
|
||||
err = err1
|
||||
}
|
||||
return err
|
||||
}
|
||||
25
runtime/internal/lib/os/signal/signal.go
Normal file
25
runtime/internal/lib/os/signal/signal.go
Normal file
@@ -0,0 +1,25 @@
|
||||
package signal
|
||||
|
||||
func signal_disable(uint32) {
|
||||
panic("signal_disable not implemented")
|
||||
}
|
||||
|
||||
func signal_enable(uint32) {
|
||||
panic("signal_enable not implemented")
|
||||
}
|
||||
|
||||
func signal_ignore(uint32) {
|
||||
panic("signal_ignore not implemented")
|
||||
}
|
||||
|
||||
func signal_ignored(uint32) bool {
|
||||
panic("signal_ignored not implemented")
|
||||
}
|
||||
|
||||
func signal_recv() uint32 {
|
||||
panic("signal_recv not implemented")
|
||||
}
|
||||
|
||||
func signalWaitUntilIdle() {
|
||||
panic("signalWaitUntilIdle not implemented")
|
||||
}
|
||||
@@ -9,7 +9,7 @@ package os
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/goplus/llgo/runtime/internal/lib/syscall"
|
||||
"github.com/goplus/llgo/runtime/internal/clite/syscall"
|
||||
)
|
||||
|
||||
// A fileStat is the implementation of FileInfo returned by Stat and Lstat.
|
||||
|
||||
Reference in New Issue
Block a user