github.com/demonoid81/moby@v0.0.0-20200517203328-62dd8e17c460/pkg/system/rm.go (about)

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