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 aix || darwin || dragonfly || freebsd || linux || netbsd || openbsd || solaris
     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 := unix.Unlinkat(parentFd, base, 0)
    62  	if err == nil || IsNotExist(err) {
    63  		return nil
    64  	}
    65  
    66  	// EISDIR means that we have a directory, and we need to
    67  	// remove its contents.
    68  	// EPERM or EACCES means that we don't have write permission on
    69  	// the parent directory, but this entry might still be a directory
    70  	// whose contents need to be removed.
    71  	// Otherwise just return the error.
    72  	if err != syscall.EISDIR && err != syscall.EPERM && err != syscall.EACCES {
    73  		return &PathError{Op: "unlinkat", Path: base, Err: err}
    74  	}
    75  
    76  	// Is this a directory we need to recurse into?
    77  	var statInfo syscall.Stat_t
    78  	statErr := unix.Fstatat(parentFd, base, &statInfo, unix.AT_SYMLINK_NOFOLLOW)
    79  	if statErr != nil {
    80  		if IsNotExist(statErr) {
    81  			return nil
    82  		}
    83  		return &PathError{Op: "fstatat", Path: base, Err: statErr}
    84  	}
    85  	if statInfo.Mode&syscall.S_IFMT != syscall.S_IFDIR {
    86  		// Not a directory; return the error from the unix.Unlinkat.
    87  		return &PathError{Op: "unlinkat", Path: base, Err: err}
    88  	}
    89  
    90  	// Remove the directory's entries.
    91  	var recurseErr error
    92  	for {
    93  		const reqSize = 1024
    94  		var respSize int
    95  
    96  		// Open the directory to recurse into
    97  		file, err := openFdAt(parentFd, base)
    98  		if err != nil {
    99  			if IsNotExist(err) {
   100  				return nil
   101  			}
   102  			recurseErr = &PathError{Op: "openfdat", Path: base, Err: err}
   103  			break
   104  		}
   105  
   106  		for {
   107  			numErr := 0
   108  
   109  			names, readErr := file.Readdirnames(reqSize)
   110  			// Errors other than EOF should stop us from continuing.
   111  			if readErr != nil && readErr != io.EOF {
   112  				file.Close()
   113  				if IsNotExist(readErr) {
   114  					return nil
   115  				}
   116  				return &PathError{Op: "readdirnames", Path: base, Err: readErr}
   117  			}
   118  
   119  			respSize = len(names)
   120  			for _, name := range names {
   121  				err := removeAllFrom(file, name)
   122  				if err != nil {
   123  					if pathErr, ok := err.(*PathError); ok {
   124  						pathErr.Path = base + string(PathSeparator) + pathErr.Path
   125  					}
   126  					numErr++
   127  					if recurseErr == nil {
   128  						recurseErr = err
   129  					}
   130  				}
   131  			}
   132  
   133  			// If we can delete any entry, break to start new iteration.
   134  			// Otherwise, we discard current names, get next entries and try deleting them.
   135  			if numErr != reqSize {
   136  				break
   137  			}
   138  		}
   139  
   140  		// Removing files from the directory may have caused
   141  		// the OS to reshuffle it. Simply calling Readdirnames
   142  		// again may skip some entries. The only reliable way
   143  		// to avoid this is to close and re-open the
   144  		// directory. See issue 20841.
   145  		file.Close()
   146  
   147  		// Finish when the end of the directory is reached
   148  		if respSize < reqSize {
   149  			break
   150  		}
   151  	}
   152  
   153  	// Remove the directory itself.
   154  	unlinkError := unix.Unlinkat(parentFd, base, unix.AT_REMOVEDIR)
   155  	if unlinkError == nil || IsNotExist(unlinkError) {
   156  		return nil
   157  	}
   158  
   159  	if recurseErr != nil {
   160  		return recurseErr
   161  	}
   162  	return &PathError{Op: "unlinkat", Path: base, Err: unlinkError}
   163  }
   164  
   165  // openFdAt opens path relative to the directory in fd.
   166  // Other than that this should act like openFileNolog.
   167  // This acts like openFileNolog rather than OpenFile because
   168  // we are going to (try to) remove the file.
   169  // The contents of this file are not relevant for test caching.
   170  func openFdAt(dirfd int, name string) (*File, error) {
   171  	var r int
   172  	for {
   173  		var e error
   174  		r, e = unix.Openat(dirfd, name, O_RDONLY|syscall.O_CLOEXEC, 0)
   175  		if e == nil {
   176  			break
   177  		}
   178  
   179  		// See comment in openFileNolog.
   180  		if e == syscall.EINTR {
   181  			continue
   182  		}
   183  
   184  		return nil, e
   185  	}
   186  
   187  	if !supportsCloseOnExec {
   188  		syscall.CloseOnExec(r)
   189  	}
   190  
   191  	return newFile(uintptr(r), name, kindOpenFile), nil
   192  }
   193  

View as plain text