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