2021-08-23 13:14:53 +00:00
|
|
|
//go:build !darwin && !windows
|
2020-11-10 16:43:44 +00:00
|
|
|
// +build !darwin,!windows
|
2020-09-21 08:21:24 +00:00
|
|
|
|
pkg/system: move EnsureRemoveAll() to pkg/containerfs
pkg/system historically has been a bit of a kitchen-sink of things that were
somewhat "system" related, but didn't have a good place for. EnsureRemoveAll()
is one of those utilities. EnsureRemoveAll() is used to both unmount and remove
a path, for which it depends on both github.com/moby/sys/mount, which in turn
depends on github.com/moby/sys/mountinfo.
pkg/system is imported in the CLI, but neither EnsureRemoveAll(), nor any of its
moby/sys dependencies are used on the client side, so let's move this function
somewhere else, to remove those dependencies from the CLI.
I looked for plausible locations that were related; it's used in:
- daemon
- daemon/graphdriver/XXX/
- plugin
I considered moving it into a (e.g.) "utils" package within graphdriver (but not
a huge fan of "utils" packages), and given that it felt (mostly) related to
cleaning up container filesystems, I decided to move it there.
Some things to follow-up on after this:
- Verify if this function is still needed (it feels a bit like a big hammer in
a "YOLO, let's try some things just in case it fails")
- Perhaps it should be integrated in `containerfs.Remove()` (so that it's used
automatically)
- Look if there's other implementations (and if they should be consolidated),
although (e.g.) the one in containerd is a copy of ours:
https://github.com/containerd/containerd/blob/v1.5.9/pkg/cri/server/helpers_linux.go#L200
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2022-03-02 21:43:07 +00:00
|
|
|
package containerfs // import "github.com/docker/docker/pkg/containerfs"
|
2017-02-14 18:35:20 +00:00
|
|
|
|
|
|
|
import (
|
|
|
|
"os"
|
|
|
|
"syscall"
|
|
|
|
"time"
|
|
|
|
|
2020-03-13 23:38:24 +00:00
|
|
|
"github.com/moby/sys/mount"
|
2017-02-14 18:35:20 +00:00
|
|
|
"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.
|
|
|
|
//
|
2017-05-21 23:24:07 +00:00
|
|
|
// This should not return a `os.ErrNotExist` kind of error under any circumstances
|
2017-02-14 18:35:20 +00:00
|
|
|
func EnsureRemoveAll(dir string) error {
|
|
|
|
notExistErr := make(map[string]bool)
|
|
|
|
|
|
|
|
// track retries
|
|
|
|
exitOnErr := make(map[string]int)
|
2017-09-22 13:52:41 +00:00
|
|
|
maxRetry := 50
|
2017-02-14 18:35:20 +00:00
|
|
|
|
|
|
|
// Attempt to unmount anything beneath this dir first
|
|
|
|
mount.RecursiveUnmount(dir)
|
|
|
|
|
|
|
|
for {
|
|
|
|
err := os.RemoveAll(dir)
|
|
|
|
if err == nil {
|
2018-07-28 22:31:08 +00:00
|
|
|
return nil
|
2017-02-14 18:35:20 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2020-03-06 20:28:41 +00:00
|
|
|
if e := mount.Unmount(pe.Path); e != nil {
|
|
|
|
return errors.Wrapf(e, "error while removing %s", dir)
|
2017-02-14 18:35:20 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if exitOnErr[pe.Path] == maxRetry {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
exitOnErr[pe.Path]++
|
|
|
|
time.Sleep(100 * time.Millisecond)
|
|
|
|
}
|
|
|
|
}
|