Black Lives Matter. Support the Equal Justice Initiative.

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