github.com/Heebron/moby@v0.0.0-20221111184709-6eab4f55faf7/pkg/containerfs/rm.go (about)

     1  //go:build !darwin && !windows
     2  // +build !darwin,!windows
     3  
     4  package containerfs // import "github.com/docker/docker/pkg/containerfs"
     5  
     6  import (
     7  	"os"
     8  	"syscall"
     9  	"time"
    10  
    11  	"github.com/moby/sys/mount"
    12  	"github.com/pkg/errors"
    13  )
    14  
    15  // EnsureRemoveAll wraps `os.RemoveAll` to check for specific errors that can
    16  // often be remedied.
    17  // Only use `EnsureRemoveAll` if you really want to make every effort to remove
    18  // a directory.
    19  //
    20  // Because of the way `os.Remove` (and by extension `os.RemoveAll`) works, there
    21  // can be a race between reading directory entries and then actually attempting
    22  // to remove everything in the directory.
    23  // These types of errors do not need to be returned since it's ok for the dir to
    24  // be gone we can just retry the remove operation.
    25  //
    26  // This should not return a `os.ErrNotExist` kind of error under any circumstances
    27  func EnsureRemoveAll(dir string) error {
    28  	notExistErr := make(map[string]bool)
    29  
    30  	// track retries
    31  	exitOnErr := make(map[string]int)
    32  	maxRetry := 50
    33  
    34  	// Attempt to unmount anything beneath this dir first
    35  	mount.RecursiveUnmount(dir)
    36  
    37  	for {
    38  		err := os.RemoveAll(dir)
    39  		if err == nil {
    40  			return nil
    41  		}
    42  
    43  		pe, ok := err.(*os.PathError)
    44  		if !ok {
    45  			return err
    46  		}
    47  
    48  		if os.IsNotExist(err) {
    49  			if notExistErr[pe.Path] {
    50  				return err
    51  			}
    52  			notExistErr[pe.Path] = true
    53  
    54  			// There is a race where some subdir can be removed but after the parent
    55  			//   dir entries have been read.
    56  			// So the path could be from `os.Remove(subdir)`
    57  			// If the reported non-existent path is not the passed in `dir` we
    58  			// should just retry, but otherwise return with no error.
    59  			if pe.Path == dir {
    60  				return nil
    61  			}
    62  			continue
    63  		}
    64  
    65  		if pe.Err != syscall.EBUSY {
    66  			return err
    67  		}
    68  
    69  		if e := mount.Unmount(pe.Path); e != nil {
    70  			return errors.Wrapf(e, "error while removing %s", dir)
    71  		}
    72  
    73  		if exitOnErr[pe.Path] == maxRetry {
    74  			return err
    75  		}
    76  		exitOnErr[pe.Path]++
    77  		time.Sleep(100 * time.Millisecond)
    78  	}
    79  }