runtime: testing runtime

This commit is contained in:
Li Jie
2025-02-14 17:18:13 +08:00
parent 66909b3000
commit 5329f28580
83 changed files with 3376 additions and 2061 deletions

View 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])
}

View File

@@ -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 {

View File

@@ -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
}

View File

@@ -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
)

View File

@@ -53,3 +53,7 @@ func MkdirAll(path string, perm FileMode) error {
}
return nil
}
func RemoveAll(path string) error {
return removeAll(path)
}

View 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
}

View 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
}

View 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")
}

View File

@@ -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.