runtime: testing runtime
This commit is contained in:
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
|
||||
}
|
||||
Reference in New Issue
Block a user