| // 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 hurd linux netbsd openbsd solaris |
| |
| package os |
| |
| import ( |
| "internal/syscall/unix" |
| "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 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() |
| |
| 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 { |
| parentFd := int(parent.Fd()) |
| // Simple case: if Unlink (aka remove) works, we're done. |
| err := unix.Unlinkat(parentFd, base, 0) |
| 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 != syscall.EISDIR && err != syscall.EPERM && err != syscall.EACCES { |
| return &PathError{"unlinkat", base, err} |
| } |
| |
| // Is this a directory we need to recurse into? |
| var statInfo syscall.Stat_t |
| statErr := unix.Fstatat(parentFd, base, &statInfo, unix.AT_SYMLINK_NOFOLLOW) |
| if statErr != nil { |
| if IsNotExist(statErr) { |
| return nil |
| } |
| return &PathError{"fstatat", base, statErr} |
| } |
| if statInfo.Mode&syscall.S_IFMT != syscall.S_IFDIR { |
| // Not a directory; return the error from the unix.Unlinkat. |
| return &PathError{"unlinkat", base, err} |
| } |
| |
| // Remove the directory's entries. |
| var recurseErr error |
| for { |
| const reqSize = 1024 |
| var respSize int |
| |
| // Open the directory to recurse into |
| file, err := openFdAt(parentFd, base) |
| if err != nil { |
| if IsNotExist(err) { |
| return nil |
| } |
| recurseErr = &PathError{"openfdat", base, 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{"readdirnames", base, 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 := unix.Unlinkat(parentFd, base, unix.AT_REMOVEDIR) |
| unlinkError = removeAllTestHook(unlinkError) |
| if unlinkError == nil || IsNotExist(unlinkError) { |
| return nil |
| } |
| |
| if recurseErr != nil { |
| return recurseErr |
| } |
| return &PathError{"unlinkat", base, unlinkError} |
| } |
| |
| // openFdAt opens path relative to the directory in fd. |
| // 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 openFdAt(dirfd int, name string) (*File, error) { |
| var r int |
| for { |
| var e error |
| r, e = unix.Openat(dirfd, name, O_RDONLY|syscall.O_CLOEXEC, 0) |
| if e == nil { |
| break |
| } |
| |
| // See comment in openFileNolog. |
| if runtime.GOOS == "darwin" && e == syscall.EINTR { |
| continue |
| } |
| |
| return nil, e |
| } |
| |
| if !supportsCloseOnExec { |
| syscall.CloseOnExec(r) |
| } |
| |
| return newFile(uintptr(r), name, kindOpenFile), nil |
| } |