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
     6  
     7  package os
     8  
     9  import (
    10  	"internal/syscall/unix"
    11  	"io"
    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  	parent, err := Open(parentDir)
    39  	if IsNotExist(err) {
    40  		// If parent does not exist, base cannot exist. Fail silently
    41  		return nil
    42  	}
    43  	if err != nil {
    44  		return err
    45  	}
    46  	defer parent.Close()
    47  
    48  	if err := removeAllFrom(parent, base); err != nil {
    49  		if pathErr, ok := err.(*PathError); ok {
    50  			pathErr.Path = parentDir + string(PathSeparator) + pathErr.Path
    51  			err = pathErr
    52  		}
    53  		return err
    54  	}
    55  	return nil
    56  }
    57  
    58  func removeAllFrom(parent *File, base string) error {
    59  	parentFd := int(parent.Fd())
    60  	// Simple case: if Unlink (aka remove) works, we're done.
    61  	err := ignoringEINTR(func() error {
    62  		return unix.Unlinkat(parentFd, base, 0)
    63  	})
    64  	if err == nil || IsNotExist(err) {
    65  		return nil
    66  	}
    67  
    68  	// EISDIR means that we have a directory, and we need to
    69  	// remove its contents.
    70  	// EPERM or EACCES means that we don't have write permission on
    71  	// the parent directory, but this entry might still be a directory
    72  	// whose contents need to be removed.
    73  	// Otherwise just return the error.
    74  	if err != syscall.EISDIR && err != syscall.EPERM && err != syscall.EACCES {
    75  		return &PathError{Op: "unlinkat", Path: base, Err: err}
    76  	}
    77  	uErr := err
    78  
    79  	// Remove the directory's entries.
    80  	var recurseErr error
    81  	for {
    82  		const reqSize = 1024
    83  		var respSize int
    84  
    85  		// Open the directory to recurse into
    86  		file, err := openDirAt(parentFd, base)
    87  		if err != nil {
    88  			if IsNotExist(err) {
    89  				return nil
    90  			}
    91  			if err == syscall.ENOTDIR || err == unix.NoFollowErrno {
    92  				// Not a directory; return the error from the unix.Unlinkat.
    93  				return &PathError{Op: "unlinkat", Path: base, Err: uErr}
    94  			}
    95  			recurseErr = &PathError{Op: "openfdat", Path: base, Err: err}
    96  			break
    97  		}
    98  
    99  		for {
   100  			numErr := 0
   101  
   102  			names, readErr := file.Readdirnames(reqSize)
   103  			// Errors other than EOF should stop us from continuing.
   104  			if readErr != nil && readErr != io.EOF {
   105  				file.Close()
   106  				if IsNotExist(readErr) {
   107  					return nil
   108  				}
   109  				return &PathError{Op: "readdirnames", Path: base, Err: readErr}
   110  			}
   111  
   112  			respSize = len(names)
   113  			for _, name := range names {
   114  				err := removeAllFrom(file, name)
   115  				if err != nil {
   116  					if pathErr, ok := err.(*PathError); ok {
   117  						pathErr.Path = base + string(PathSeparator) + pathErr.Path
   118  					}
   119  					numErr++
   120  					if recurseErr == nil {
   121  						recurseErr = err
   122  					}
   123  				}
   124  			}
   125  
   126  			// If we can delete any entry, break to start new iteration.
   127  			// Otherwise, we discard current names, get next entries and try deleting them.
   128  			if numErr != reqSize {
   129  				break
   130  			}
   131  		}
   132  
   133  		// Removing files from the directory may have caused
   134  		// the OS to reshuffle it. Simply calling Readdirnames
   135  		// again may skip some entries. The only reliable way
   136  		// to avoid this is to close and re-open the
   137  		// directory. See issue 20841.
   138  		file.Close()
   139  
   140  		// Finish when the end of the directory is reached
   141  		if respSize < reqSize {
   142  			break
   143  		}
   144  	}
   145  
   146  	// Remove the directory itself.
   147  	unlinkError := ignoringEINTR(func() error {
   148  		return unix.Unlinkat(parentFd, base, unix.AT_REMOVEDIR)
   149  	})
   150  	if unlinkError == nil || IsNotExist(unlinkError) {
   151  		return nil
   152  	}
   153  
   154  	if recurseErr != nil {
   155  		return recurseErr
   156  	}
   157  	return &PathError{Op: "unlinkat", Path: base, Err: unlinkError}
   158  }
   159  
   160  // openDirAt opens a directory name relative to the directory referred to by
   161  // the file descriptor dirfd. If name is anything but a directory (this
   162  // includes a symlink to one), it should return an error. Other than that this
   163  // should act like openFileNolog.
   164  //
   165  // This acts like openFileNolog rather than OpenFile because
   166  // we are going to (try to) remove the file.
   167  // The contents of this file are not relevant for test caching.
   168  func openDirAt(dirfd int, name string) (*File, error) {
   169  	var r int
   170  	for {
   171  		var e error
   172  		r, e = unix.Openat(dirfd, name, O_RDONLY|syscall.O_CLOEXEC|syscall.O_DIRECTORY|syscall.O_NOFOLLOW, 0)
   173  		if e == nil {
   174  			break
   175  		}
   176  
   177  		// See comment in openFileNolog.
   178  		if e == syscall.EINTR {
   179  			continue
   180  		}
   181  
   182  		return nil, e
   183  	}
   184  
   185  	if !supportsCloseOnExec {
   186  		syscall.CloseOnExec(r)
   187  	}
   188  
   189  	// We use kindNoPoll because we know that this is a directory.
   190  	return newFile(r, name, kindNoPoll, false), nil
   191  }
   192  

View as plain text