...

Source file src/os/removeall_at.go

Documentation: os

		 1  // Copyright 2018 The Go Authors. All rights reserved.
		 2  // Use of this source code is governed by a BSD-style
		 3  // license that can be found in the LICENSE file.
		 4  
		 5  //go:build aix || darwin || dragonfly || freebsd || linux || netbsd || openbsd || solaris
		 6  // +build aix darwin dragonfly freebsd linux netbsd openbsd solaris
		 7  
		 8  package os
		 9  
		10  import (
		11  	"internal/syscall/unix"
		12  	"io"
		13  	"syscall"
		14  )
		15  
		16  func removeAll(path string) error {
		17  	if path == "" {
		18  		// fail silently to retain compatibility with previous behavior
		19  		// of RemoveAll. See issue 28830.
		20  		return nil
		21  	}
		22  
		23  	// The rmdir system call does not permit removing ".",
		24  	// so we don't permit it either.
		25  	if endsWithDot(path) {
		26  		return &PathError{Op: "RemoveAll", Path: path, Err: syscall.EINVAL}
		27  	}
		28  
		29  	// Simple case: if Remove works, we're done.
		30  	err := Remove(path)
		31  	if err == nil || IsNotExist(err) {
		32  		return nil
		33  	}
		34  
		35  	// RemoveAll recurses by deleting the path base from
		36  	// its parent directory
		37  	parentDir, base := splitPath(path)
		38  
		39  	parent, err := Open(parentDir)
		40  	if IsNotExist(err) {
		41  		// If parent does not exist, base cannot exist. Fail silently
		42  		return nil
		43  	}
		44  	if err != nil {
		45  		return err
		46  	}
		47  	defer parent.Close()
		48  
		49  	if err := removeAllFrom(parent, base); err != nil {
		50  		if pathErr, ok := err.(*PathError); ok {
		51  			pathErr.Path = parentDir + string(PathSeparator) + pathErr.Path
		52  			err = pathErr
		53  		}
		54  		return err
		55  	}
		56  	return nil
		57  }
		58  
		59  func removeAllFrom(parent *File, base string) error {
		60  	parentFd := int(parent.Fd())
		61  	// Simple case: if Unlink (aka remove) works, we're done.
		62  	err := unix.Unlinkat(parentFd, base, 0)
		63  	if err == nil || IsNotExist(err) {
		64  		return nil
		65  	}
		66  
		67  	// EISDIR means that we have a directory, and we need to
		68  	// remove its contents.
		69  	// EPERM or EACCES means that we don't have write permission on
		70  	// the parent directory, but this entry might still be a directory
		71  	// whose contents need to be removed.
		72  	// Otherwise just return the error.
		73  	if err != syscall.EISDIR && err != syscall.EPERM && err != syscall.EACCES {
		74  		return &PathError{Op: "unlinkat", Path: base, Err: err}
		75  	}
		76  
		77  	// Is this a directory we need to recurse into?
		78  	var statInfo syscall.Stat_t
		79  	statErr := unix.Fstatat(parentFd, base, &statInfo, unix.AT_SYMLINK_NOFOLLOW)
		80  	if statErr != nil {
		81  		if IsNotExist(statErr) {
		82  			return nil
		83  		}
		84  		return &PathError{Op: "fstatat", Path: base, Err: statErr}
		85  	}
		86  	if statInfo.Mode&syscall.S_IFMT != syscall.S_IFDIR {
		87  		// Not a directory; return the error from the unix.Unlinkat.
		88  		return &PathError{Op: "unlinkat", Path: base, Err: err}
		89  	}
		90  
		91  	// Remove the directory's entries.
		92  	var recurseErr error
		93  	for {
		94  		const reqSize = 1024
		95  		var respSize int
		96  
		97  		// Open the directory to recurse into
		98  		file, err := openFdAt(parentFd, base)
		99  		if err != nil {
	 100  			if IsNotExist(err) {
	 101  				return nil
	 102  			}
	 103  			recurseErr = &PathError{Op: "openfdat", Path: base, Err: err}
	 104  			break
	 105  		}
	 106  
	 107  		for {
	 108  			numErr := 0
	 109  
	 110  			names, readErr := file.Readdirnames(reqSize)
	 111  			// Errors other than EOF should stop us from continuing.
	 112  			if readErr != nil && readErr != io.EOF {
	 113  				file.Close()
	 114  				if IsNotExist(readErr) {
	 115  					return nil
	 116  				}
	 117  				return &PathError{Op: "readdirnames", Path: base, Err: readErr}
	 118  			}
	 119  
	 120  			respSize = len(names)
	 121  			for _, name := range names {
	 122  				err := removeAllFrom(file, name)
	 123  				if err != nil {
	 124  					if pathErr, ok := err.(*PathError); ok {
	 125  						pathErr.Path = base + string(PathSeparator) + pathErr.Path
	 126  					}
	 127  					numErr++
	 128  					if recurseErr == nil {
	 129  						recurseErr = err
	 130  					}
	 131  				}
	 132  			}
	 133  
	 134  			// If we can delete any entry, break to start new iteration.
	 135  			// Otherwise, we discard current names, get next entries and try deleting them.
	 136  			if numErr != reqSize {
	 137  				break
	 138  			}
	 139  		}
	 140  
	 141  		// Removing files from the directory may have caused
	 142  		// the OS to reshuffle it. Simply calling Readdirnames
	 143  		// again may skip some entries. The only reliable way
	 144  		// to avoid this is to close and re-open the
	 145  		// directory. See issue 20841.
	 146  		file.Close()
	 147  
	 148  		// Finish when the end of the directory is reached
	 149  		if respSize < reqSize {
	 150  			break
	 151  		}
	 152  	}
	 153  
	 154  	// Remove the directory itself.
	 155  	unlinkError := unix.Unlinkat(parentFd, base, unix.AT_REMOVEDIR)
	 156  	if unlinkError == nil || IsNotExist(unlinkError) {
	 157  		return nil
	 158  	}
	 159  
	 160  	if recurseErr != nil {
	 161  		return recurseErr
	 162  	}
	 163  	return &PathError{Op: "unlinkat", Path: base, Err: unlinkError}
	 164  }
	 165  
	 166  // openFdAt opens path relative to the directory in fd.
	 167  // Other than that this should act like openFileNolog.
	 168  // This acts like openFileNolog rather than OpenFile because
	 169  // we are going to (try to) remove the file.
	 170  // The contents of this file are not relevant for test caching.
	 171  func openFdAt(dirfd int, name string) (*File, error) {
	 172  	var r int
	 173  	for {
	 174  		var e error
	 175  		r, e = unix.Openat(dirfd, name, O_RDONLY|syscall.O_CLOEXEC, 0)
	 176  		if e == nil {
	 177  			break
	 178  		}
	 179  
	 180  		// See comment in openFileNolog.
	 181  		if e == syscall.EINTR {
	 182  			continue
	 183  		}
	 184  
	 185  		return nil, e
	 186  	}
	 187  
	 188  	if !supportsCloseOnExec {
	 189  		syscall.CloseOnExec(r)
	 190  	}
	 191  
	 192  	return newFile(uintptr(r), name, kindOpenFile), nil
	 193  }
	 194  

View as plain text