Source file src/os/removeall_at.go

     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 unix || wasip1 || windows
     6  
     7  package os
     8  
     9  import (
    10  	"io"
    11  	"runtime"
    12  	"syscall"
    13  )
    14  
    15  func removeAll(path string) error {
    16  	if path == "" {
    17  		// fail silently to retain compatibility with previous behavior
    18  		// of RemoveAll. See issue 28830.
    19  		return nil
    20  	}
    21  
    22  	// The rmdir system call does not permit removing ".",
    23  	// so we don't permit it either.
    24  	if endsWithDot(path) {
    25  		return &PathError{Op: "RemoveAll", Path: path, Err: syscall.EINVAL}
    26  	}
    27  
    28  	// Simple case: if Remove works, we're done.
    29  	err := Remove(path)
    30  	if err == nil || IsNotExist(err) {
    31  		return nil
    32  	}
    33  
    34  	// RemoveAll recurses by deleting the path base from
    35  	// its parent directory
    36  	parentDir, base := splitPath(path)
    37  
    38  	flag := O_RDONLY
    39  	if runtime.GOOS == "windows" {
    40  		// On Windows, the process might not have read permission on the parent directory,
    41  		// but still can delete files in it. See https://go.dev/issue/74134.
    42  		// We can open a file even if we don't have read permission by passing the
    43  		// O_WRONLY | O_RDWR flag, which is mapped to FILE_READ_ATTRIBUTES.
    44  		flag = O_WRONLY | O_RDWR
    45  	}
    46  	parent, err := OpenFile(parentDir, flag, 0)
    47  	if IsNotExist(err) {
    48  		// If parent does not exist, base cannot exist. Fail silently
    49  		return nil
    50  	}
    51  	if err != nil {
    52  		return err
    53  	}
    54  	defer parent.Close()
    55  
    56  	if err := removeAllFrom(sysfdType(parent.Fd()), base); err != nil {
    57  		if pathErr, ok := err.(*PathError); ok {
    58  			pathErr.Path = parentDir + string(PathSeparator) + pathErr.Path
    59  			err = pathErr
    60  		}
    61  		return err
    62  	}
    63  	return nil
    64  }
    65  
    66  func removeAllFrom(parentFd sysfdType, base string) error {
    67  	// Simple case: if Unlink (aka remove) works, we're done.
    68  	err := removefileat(parentFd, base)
    69  	if err == nil || IsNotExist(err) {
    70  		return nil
    71  	}
    72  
    73  	// EISDIR means that we have a directory, and we need to
    74  	// remove its contents.
    75  	// EPERM or EACCES means that we don't have write permission on
    76  	// the parent directory, but this entry might still be a directory
    77  	// whose contents need to be removed.
    78  	// Otherwise just return the error.
    79  	if err != syscall.EISDIR && err != syscall.EPERM && err != syscall.EACCES {
    80  		return &PathError{Op: "unlinkat", Path: base, Err: err}
    81  	}
    82  	uErr := err
    83  
    84  	// Remove the directory's entries.
    85  	var recurseErr error
    86  	for {
    87  		const reqSize = 1024
    88  		var respSize int
    89  
    90  		// Open the directory to recurse into.
    91  		file, err := openDirAt(parentFd, base)
    92  		if err != nil {
    93  			if IsNotExist(err) {
    94  				return nil
    95  			}
    96  			if err == syscall.ENOTDIR || isErrNoFollow(err) {
    97  				// Not a directory; return the error from the unix.Unlinkat.
    98  				return &PathError{Op: "unlinkat", Path: base, Err: uErr}
    99  			}
   100  			recurseErr = &PathError{Op: "openfdat", Path: base, Err: err}
   101  			break
   102  		}
   103  
   104  		for {
   105  			numErr := 0
   106  
   107  			names, readErr := file.Readdirnames(reqSize)
   108  			// Errors other than EOF should stop us from continuing.
   109  			if readErr != nil && readErr != io.EOF {
   110  				file.Close()
   111  				if IsNotExist(readErr) {
   112  					return nil
   113  				}
   114  				return &PathError{Op: "readdirnames", Path: base, Err: readErr}
   115  			}
   116  
   117  			respSize = len(names)
   118  			for _, name := range names {
   119  				err := removeAllFrom(sysfdType(file.Fd()), name)
   120  				if err != nil {
   121  					if pathErr, ok := err.(*PathError); ok {
   122  						pathErr.Path = base + string(PathSeparator) + pathErr.Path
   123  					}
   124  					numErr++
   125  					if recurseErr == nil {
   126  						recurseErr = err
   127  					}
   128  				}
   129  			}
   130  
   131  			// If we can delete any entry, break to start new iteration.
   132  			// Otherwise, we discard current names, get next entries and try deleting them.
   133  			if numErr != reqSize {
   134  				break
   135  			}
   136  		}
   137  
   138  		// Removing files from the directory may have caused
   139  		// the OS to reshuffle it. Simply calling Readdirnames
   140  		// again may skip some entries. The only reliable way
   141  		// to avoid this is to close and re-open the
   142  		// directory. See issue 20841.
   143  		file.Close()
   144  
   145  		// Finish when the end of the directory is reached
   146  		if respSize < reqSize {
   147  			break
   148  		}
   149  	}
   150  
   151  	// Remove the directory itself.
   152  	unlinkError := removedirat(parentFd, base)
   153  	if unlinkError == nil || IsNotExist(unlinkError) {
   154  		return nil
   155  	}
   156  
   157  	if recurseErr != nil {
   158  		return recurseErr
   159  	}
   160  	return &PathError{Op: "unlinkat", Path: base, Err: unlinkError}
   161  }
   162  
   163  // openDirAt opens a directory name relative to the directory referred to by
   164  // the file descriptor dirfd. If name is anything but a directory (this
   165  // includes a symlink to one), it should return an error. Other than that this
   166  // should act like openFileNolog.
   167  //
   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 openDirAt(dirfd sysfdType, name string) (*File, error) {
   172  	fd, err := rootOpenDir(dirfd, name)
   173  	if err != nil {
   174  		return nil, err
   175  	}
   176  	return newDirFile(fd, name)
   177  }
   178  

View as plain text