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 {
    97  				// Not a directory; return the error from the removefileat.
    98  				return &PathError{Op: "unlinkat", Path: base, Err: uErr}
    99  			}
   100  			if _, ok := err.(errSymlink); ok {
   101  				// Not a user-visible error.
   102  				err = uErr
   103  			}
   104  			recurseErr = &PathError{Op: "openfdat", Path: base, Err: err}
   105  			break
   106  		}
   107  
   108  		for {
   109  			numErr := 0
   110  
   111  			names, readErr := file.Readdirnames(reqSize)
   112  			// Errors other than EOF should stop us from continuing.
   113  			if readErr != nil && readErr != io.EOF {
   114  				file.Close()
   115  				if IsNotExist(readErr) {
   116  					return nil
   117  				}
   118  				return &PathError{Op: "readdirnames", Path: base, Err: readErr}
   119  			}
   120  
   121  			respSize = len(names)
   122  			for _, name := range names {
   123  				err := removeAllFrom(sysfdType(file.Fd()), name)
   124  				if err != nil {
   125  					if pathErr, ok := err.(*PathError); ok {
   126  						pathErr.Path = base + string(PathSeparator) + pathErr.Path
   127  					}
   128  					numErr++
   129  					if recurseErr == nil {
   130  						recurseErr = err
   131  					}
   132  				}
   133  			}
   134  
   135  			// If we can delete any entry, break to start new iteration.
   136  			// Otherwise, we discard current names, get next entries and try deleting them.
   137  			if numErr != reqSize {
   138  				break
   139  			}
   140  		}
   141  
   142  		// Removing files from the directory may have caused
   143  		// the OS to reshuffle it. Simply calling Readdirnames
   144  		// again may skip some entries. The only reliable way
   145  		// to avoid this is to close and re-open the
   146  		// directory. See issue 20841.
   147  		file.Close()
   148  
   149  		// Finish when the end of the directory is reached
   150  		if respSize < reqSize {
   151  			break
   152  		}
   153  	}
   154  
   155  	// Remove the directory itself.
   156  	unlinkError := removedirat(parentFd, base)
   157  	if unlinkError == nil || IsNotExist(unlinkError) {
   158  		return nil
   159  	}
   160  
   161  	if recurseErr != nil {
   162  		return recurseErr
   163  	}
   164  	return &PathError{Op: "unlinkat", Path: base, Err: unlinkError}
   165  }
   166  
   167  // openDirAt opens a directory name relative to the directory referred to by
   168  // the file descriptor dirfd. If name is anything but a directory (this
   169  // includes a symlink to one), it should return an error. Other than that this
   170  // should act like openFileNolog.
   171  //
   172  // This acts like openFileNolog rather than OpenFile because
   173  // we are going to (try to) remove the file.
   174  // The contents of this file are not relevant for test caching.
   175  func openDirAt(dirfd sysfdType, name string) (*File, error) {
   176  	fd, err := rootOpenDir(dirfd, name)
   177  	if err != nil {
   178  		return nil, err
   179  	}
   180  	return newDirFile(fd, name)
   181  }
   182  

View as plain text