blob: f0fed6dc33f4cdedc8c3f04542a6ab5a06ba24b2 [file] [log] [blame]
// 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 aix darwin dragonfly freebsd linux netbsd openbsd solaris
package os
import (
"internal/syscall/unix"
"io"
"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 does not permit removing ".",
// so we don't permit it either.
if endsWithDot(path) {
return &PathError{"RemoveAll", path, syscall.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()
return removeAllFrom(parent, base)
}
func removeAllFrom(parent *File, path string) error {
parentFd := int(parent.Fd())
// Simple case: if Unlink (aka remove) works, we're done.
err := unix.Unlinkat(parentFd, path, 0)
if err == nil || IsNotExist(err) {
return nil
}
// If not a "is directory" error, we have a problem
if err != syscall.EISDIR && err != syscall.EPERM {
return err
}
// Is this a directory we need to recurse into?
var statInfo syscall.Stat_t
statErr := unix.Fstatat(parentFd, path, &statInfo, unix.AT_SYMLINK_NOFOLLOW)
if statErr != nil {
return statErr
}
if statInfo.Mode&syscall.S_IFMT != syscall.S_IFDIR {
// Not a directory; return the error from the Remove
return err
}
// Remove the directory's entries
var recurseErr error
for {
const request = 1024
// Open the directory to recurse into
file, err := openFdAt(parentFd, path)
if err != nil {
if IsNotExist(err) {
return nil
}
return err
}
names, readErr := file.Readdirnames(request)
// Errors other than EOF should stop us from continuing
if readErr != nil && readErr != io.EOF {
file.Close()
if IsNotExist(readErr) {
return nil
}
return readErr
}
for _, name := range names {
err := removeAllFrom(file, name)
if err != nil {
recurseErr = err
}
}
// 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 len(names) < request {
break
}
}
// Remove the directory itself
unlinkError := unix.Unlinkat(parentFd, path, unix.AT_REMOVEDIR)
if unlinkError == nil || IsNotExist(unlinkError) {
return nil
}
if recurseErr != nil {
return recurseErr
}
return unlinkError
}
func openFdAt(fd int, path string) (*File, error) {
fd, err := unix.Openat(fd, path, O_RDONLY, 0)
if err != nil {
return nil, err
}
return NewFile(uintptr(fd), path), nil
}