| package system // import "github.com/docker/docker/pkg/system" |
| |
| import ( |
| "os" |
| "syscall" |
| "time" |
| |
| "github.com/docker/docker/pkg/mount" |
| "github.com/pkg/errors" |
| ) |
| |
| // EnsureRemoveAll wraps `os.RemoveAll` to check for specific errors that can |
| // often be remedied. |
| // Only use `EnsureRemoveAll` if you really want to make every effort to remove |
| // a directory. |
| // |
| // Because of the way `os.Remove` (and by extension `os.RemoveAll`) works, there |
| // can be a race between reading directory entries and then actually attempting |
| // to remove everything in the directory. |
| // These types of errors do not need to be returned since it's ok for the dir to |
| // be gone we can just retry the remove operation. |
| // |
| // This should not return a `os.ErrNotExist` kind of error under any circumstances |
| func EnsureRemoveAll(dir string) error { |
| notExistErr := make(map[string]bool) |
| |
| // track retries |
| exitOnErr := make(map[string]int) |
| maxRetry := 50 |
| |
| // Attempt to unmount anything beneath this dir first |
| mount.RecursiveUnmount(dir) |
| |
| for { |
| err := os.RemoveAll(dir) |
| if err == nil { |
| return nil |
| } |
| |
| pe, ok := err.(*os.PathError) |
| if !ok { |
| return err |
| } |
| |
| if os.IsNotExist(err) { |
| if notExistErr[pe.Path] { |
| return err |
| } |
| notExistErr[pe.Path] = true |
| |
| // There is a race where some subdir can be removed but after the parent |
| // dir entries have been read. |
| // So the path could be from `os.Remove(subdir)` |
| // If the reported non-existent path is not the passed in `dir` we |
| // should just retry, but otherwise return with no error. |
| if pe.Path == dir { |
| return nil |
| } |
| continue |
| } |
| |
| if pe.Err != syscall.EBUSY { |
| return err |
| } |
| |
| if mounted, _ := mount.Mounted(pe.Path); mounted { |
| if e := mount.Unmount(pe.Path); e != nil { |
| if mounted, _ := mount.Mounted(pe.Path); mounted { |
| return errors.Wrapf(e, "error while removing %s", dir) |
| } |
| } |
| } |
| |
| if exitOnErr[pe.Path] == maxRetry { |
| return err |
| } |
| exitOnErr[pe.Path]++ |
| time.Sleep(100 * time.Millisecond) |
| } |
| } |