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  // +build aix darwin dragonfly freebsd linux netbsd openbsd solaris
     6  
     7  package os
     8  
     9  import (
    10  	"internal/syscall/unix"
    11  	"io"
    12  	"runtime"
    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{"RemoveAll", path, 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{"unlinkat", base, 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{"fstatat", base, 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{"unlinkat", base, 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{"openfdat", base, 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{"readdirnames", base, 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  	unlinkError = removeAllTestHook(unlinkError)
   157  	if unlinkError == nil || IsNotExist(unlinkError) {
   158  		return nil
   159  	}
   160  
   161  	if recurseErr != nil {
   162  		return recurseErr
   163  	}
   164  	return &PathError{"unlinkat", base, unlinkError}
   165  }
   166  
   167  // openFdAt opens path relative to the directory in fd.
   168  // Other than that this should act like openFileNolog.
   169  // This acts like openFileNolog rather than OpenFile because
   170  // we are going to (try to) remove the file.
   171  // The contents of this file are not relevant for test caching.
   172  func openFdAt(dirfd int, name string) (*File, error) {
   173  	var r int
   174  	for {
   175  		var e error
   176  		r, e = unix.Openat(dirfd, name, O_RDONLY|syscall.O_CLOEXEC, 0)
   177  		if e == nil {
   178  			break
   179  		}
   180  
   181  		// See comment in openFileNolog.
   182  		if runtime.GOOS == "darwin" && e == syscall.EINTR {
   183  			continue
   184  		}
   185  
   186  		return nil, e
   187  	}
   188  
   189  	if !supportsCloseOnExec {
   190  		syscall.CloseOnExec(r)
   191  	}
   192  
   193  	return newFile(uintptr(r), name, kindOpenFile), nil
   194  }
   195  

View as plain text