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

View as plain text