Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
| 1 | 1 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,89 @@ |
| 0 |
+package system // import "github.com/docker/docker/pkg/system" |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "os" |
|
| 4 |
+ "syscall" |
|
| 5 |
+ "testing" |
|
| 6 |
+ "time" |
|
| 7 |
+) |
|
| 8 |
+ |
|
| 9 |
+// TestChtimesLinux tests Chtimes access time on a tempfile on Linux |
|
| 10 |
+func TestChtimesLinux(t *testing.T) {
|
|
| 11 |
+ file, dir := prepareTempFile(t) |
|
| 12 |
+ defer os.RemoveAll(dir) |
|
| 13 |
+ |
|
| 14 |
+ beforeUnixEpochTime := time.Unix(0, 0).Add(-100 * time.Second) |
|
| 15 |
+ unixEpochTime := time.Unix(0, 0) |
|
| 16 |
+ afterUnixEpochTime := time.Unix(100, 0) |
|
| 17 |
+ unixMaxTime := maxTime |
|
| 18 |
+ |
|
| 19 |
+ // Test both aTime and mTime set to Unix Epoch |
|
| 20 |
+ Chtimes(file, unixEpochTime, unixEpochTime) |
|
| 21 |
+ |
|
| 22 |
+ f, err := os.Stat(file) |
|
| 23 |
+ if err != nil {
|
|
| 24 |
+ t.Fatal(err) |
|
| 25 |
+ } |
|
| 26 |
+ |
|
| 27 |
+ stat := f.Sys().(*syscall.Stat_t) |
|
| 28 |
+ aTime := time.Unix(int64(stat.Atim.Sec), int64(stat.Atim.Nsec)) // nolint: unconvert |
|
| 29 |
+ if aTime != unixEpochTime {
|
|
| 30 |
+ t.Fatalf("Expected: %s, got: %s", unixEpochTime, aTime)
|
|
| 31 |
+ } |
|
| 32 |
+ |
|
| 33 |
+ // Test aTime before Unix Epoch and mTime set to Unix Epoch |
|
| 34 |
+ Chtimes(file, beforeUnixEpochTime, unixEpochTime) |
|
| 35 |
+ |
|
| 36 |
+ f, err = os.Stat(file) |
|
| 37 |
+ if err != nil {
|
|
| 38 |
+ t.Fatal(err) |
|
| 39 |
+ } |
|
| 40 |
+ |
|
| 41 |
+ stat = f.Sys().(*syscall.Stat_t) |
|
| 42 |
+ aTime = time.Unix(int64(stat.Atim.Sec), int64(stat.Atim.Nsec)) // nolint: unconvert |
|
| 43 |
+ if aTime != unixEpochTime {
|
|
| 44 |
+ t.Fatalf("Expected: %s, got: %s", unixEpochTime, aTime)
|
|
| 45 |
+ } |
|
| 46 |
+ |
|
| 47 |
+ // Test aTime set to Unix Epoch and mTime before Unix Epoch |
|
| 48 |
+ Chtimes(file, unixEpochTime, beforeUnixEpochTime) |
|
| 49 |
+ |
|
| 50 |
+ f, err = os.Stat(file) |
|
| 51 |
+ if err != nil {
|
|
| 52 |
+ t.Fatal(err) |
|
| 53 |
+ } |
|
| 54 |
+ |
|
| 55 |
+ stat = f.Sys().(*syscall.Stat_t) |
|
| 56 |
+ aTime = time.Unix(int64(stat.Atim.Sec), int64(stat.Atim.Nsec)) // nolint: unconvert |
|
| 57 |
+ if aTime != unixEpochTime {
|
|
| 58 |
+ t.Fatalf("Expected: %s, got: %s", unixEpochTime, aTime)
|
|
| 59 |
+ } |
|
| 60 |
+ |
|
| 61 |
+ // Test both aTime and mTime set to after Unix Epoch (valid time) |
|
| 62 |
+ Chtimes(file, afterUnixEpochTime, afterUnixEpochTime) |
|
| 63 |
+ |
|
| 64 |
+ f, err = os.Stat(file) |
|
| 65 |
+ if err != nil {
|
|
| 66 |
+ t.Fatal(err) |
|
| 67 |
+ } |
|
| 68 |
+ |
|
| 69 |
+ stat = f.Sys().(*syscall.Stat_t) |
|
| 70 |
+ aTime = time.Unix(int64(stat.Atim.Sec), int64(stat.Atim.Nsec)) // nolint: unconvert |
|
| 71 |
+ if aTime != afterUnixEpochTime {
|
|
| 72 |
+ t.Fatalf("Expected: %s, got: %s", afterUnixEpochTime, aTime)
|
|
| 73 |
+ } |
|
| 74 |
+ |
|
| 75 |
+ // Test both aTime and mTime set to Unix max time |
|
| 76 |
+ Chtimes(file, unixMaxTime, unixMaxTime) |
|
| 77 |
+ |
|
| 78 |
+ f, err = os.Stat(file) |
|
| 79 |
+ if err != nil {
|
|
| 80 |
+ t.Fatal(err) |
|
| 81 |
+ } |
|
| 82 |
+ |
|
| 83 |
+ stat = f.Sys().(*syscall.Stat_t) |
|
| 84 |
+ aTime = time.Unix(int64(stat.Atim.Sec), int64(stat.Atim.Nsec)) // nolint: unconvert |
|
| 85 |
+ if aTime.Truncate(time.Second) != unixMaxTime.Truncate(time.Second) {
|
|
| 86 |
+ t.Fatalf("Expected: %s, got: %s", unixMaxTime.Truncate(time.Second), aTime.Truncate(time.Second))
|
|
| 87 |
+ } |
|
| 88 |
+} |
| 0 | 89 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,14 @@ |
| 0 |
+// +build !windows |
|
| 1 |
+ |
|
| 2 |
+package system // import "github.com/docker/docker/pkg/system" |
|
| 3 |
+ |
|
| 4 |
+import ( |
|
| 5 |
+ "time" |
|
| 6 |
+) |
|
| 7 |
+ |
|
| 8 |
+// setCTime will set the create time on a file. On Unix, the create |
|
| 9 |
+// time is updated as a side effect of setting the modified time, so |
|
| 10 |
+// no action is required. |
|
| 11 |
+func setCTime(path string, ctime time.Time) error {
|
|
| 12 |
+ return nil |
|
| 13 |
+} |
| 0 | 14 |
deleted file mode 100644 |
| ... | ... |
@@ -1,14 +0,0 @@ |
| 1 |
-// +build !windows |
|
| 2 |
- |
|
| 3 |
-package system // import "github.com/docker/docker/pkg/system" |
|
| 4 |
- |
|
| 5 |
-import ( |
|
| 6 |
- "time" |
|
| 7 |
-) |
|
| 8 |
- |
|
| 9 |
-// setCTime will set the create time on a file. On Unix, the create |
|
| 10 |
-// time is updated as a side effect of setting the modified time, so |
|
| 11 |
-// no action is required. |
|
| 12 |
-func setCTime(path string, ctime time.Time) error {
|
|
| 13 |
- return nil |
|
| 14 |
-} |
| 15 | 1 |
deleted file mode 100644 |
| ... | ... |
@@ -1,91 +0,0 @@ |
| 1 |
-// +build !windows |
|
| 2 |
- |
|
| 3 |
-package system // import "github.com/docker/docker/pkg/system" |
|
| 4 |
- |
|
| 5 |
-import ( |
|
| 6 |
- "os" |
|
| 7 |
- "syscall" |
|
| 8 |
- "testing" |
|
| 9 |
- "time" |
|
| 10 |
-) |
|
| 11 |
- |
|
| 12 |
-// TestChtimesLinux tests Chtimes access time on a tempfile on Linux |
|
| 13 |
-func TestChtimesLinux(t *testing.T) {
|
|
| 14 |
- file, dir := prepareTempFile(t) |
|
| 15 |
- defer os.RemoveAll(dir) |
|
| 16 |
- |
|
| 17 |
- beforeUnixEpochTime := time.Unix(0, 0).Add(-100 * time.Second) |
|
| 18 |
- unixEpochTime := time.Unix(0, 0) |
|
| 19 |
- afterUnixEpochTime := time.Unix(100, 0) |
|
| 20 |
- unixMaxTime := maxTime |
|
| 21 |
- |
|
| 22 |
- // Test both aTime and mTime set to Unix Epoch |
|
| 23 |
- Chtimes(file, unixEpochTime, unixEpochTime) |
|
| 24 |
- |
|
| 25 |
- f, err := os.Stat(file) |
|
| 26 |
- if err != nil {
|
|
| 27 |
- t.Fatal(err) |
|
| 28 |
- } |
|
| 29 |
- |
|
| 30 |
- stat := f.Sys().(*syscall.Stat_t) |
|
| 31 |
- aTime := time.Unix(int64(stat.Atim.Sec), int64(stat.Atim.Nsec)) // nolint: unconvert |
|
| 32 |
- if aTime != unixEpochTime {
|
|
| 33 |
- t.Fatalf("Expected: %s, got: %s", unixEpochTime, aTime)
|
|
| 34 |
- } |
|
| 35 |
- |
|
| 36 |
- // Test aTime before Unix Epoch and mTime set to Unix Epoch |
|
| 37 |
- Chtimes(file, beforeUnixEpochTime, unixEpochTime) |
|
| 38 |
- |
|
| 39 |
- f, err = os.Stat(file) |
|
| 40 |
- if err != nil {
|
|
| 41 |
- t.Fatal(err) |
|
| 42 |
- } |
|
| 43 |
- |
|
| 44 |
- stat = f.Sys().(*syscall.Stat_t) |
|
| 45 |
- aTime = time.Unix(int64(stat.Atim.Sec), int64(stat.Atim.Nsec)) // nolint: unconvert |
|
| 46 |
- if aTime != unixEpochTime {
|
|
| 47 |
- t.Fatalf("Expected: %s, got: %s", unixEpochTime, aTime)
|
|
| 48 |
- } |
|
| 49 |
- |
|
| 50 |
- // Test aTime set to Unix Epoch and mTime before Unix Epoch |
|
| 51 |
- Chtimes(file, unixEpochTime, beforeUnixEpochTime) |
|
| 52 |
- |
|
| 53 |
- f, err = os.Stat(file) |
|
| 54 |
- if err != nil {
|
|
| 55 |
- t.Fatal(err) |
|
| 56 |
- } |
|
| 57 |
- |
|
| 58 |
- stat = f.Sys().(*syscall.Stat_t) |
|
| 59 |
- aTime = time.Unix(int64(stat.Atim.Sec), int64(stat.Atim.Nsec)) // nolint: unconvert |
|
| 60 |
- if aTime != unixEpochTime {
|
|
| 61 |
- t.Fatalf("Expected: %s, got: %s", unixEpochTime, aTime)
|
|
| 62 |
- } |
|
| 63 |
- |
|
| 64 |
- // Test both aTime and mTime set to after Unix Epoch (valid time) |
|
| 65 |
- Chtimes(file, afterUnixEpochTime, afterUnixEpochTime) |
|
| 66 |
- |
|
| 67 |
- f, err = os.Stat(file) |
|
| 68 |
- if err != nil {
|
|
| 69 |
- t.Fatal(err) |
|
| 70 |
- } |
|
| 71 |
- |
|
| 72 |
- stat = f.Sys().(*syscall.Stat_t) |
|
| 73 |
- aTime = time.Unix(int64(stat.Atim.Sec), int64(stat.Atim.Nsec)) // nolint: unconvert |
|
| 74 |
- if aTime != afterUnixEpochTime {
|
|
| 75 |
- t.Fatalf("Expected: %s, got: %s", afterUnixEpochTime, aTime)
|
|
| 76 |
- } |
|
| 77 |
- |
|
| 78 |
- // Test both aTime and mTime set to Unix max time |
|
| 79 |
- Chtimes(file, unixMaxTime, unixMaxTime) |
|
| 80 |
- |
|
| 81 |
- f, err = os.Stat(file) |
|
| 82 |
- if err != nil {
|
|
| 83 |
- t.Fatal(err) |
|
| 84 |
- } |
|
| 85 |
- |
|
| 86 |
- stat = f.Sys().(*syscall.Stat_t) |
|
| 87 |
- aTime = time.Unix(int64(stat.Atim.Sec), int64(stat.Atim.Nsec)) // nolint: unconvert |
|
| 88 |
- if aTime.Truncate(time.Second) != unixMaxTime.Truncate(time.Second) {
|
|
| 89 |
- t.Fatalf("Expected: %s, got: %s", unixMaxTime.Truncate(time.Second), aTime.Truncate(time.Second))
|
|
| 90 |
- } |
|
| 91 |
-} |
| 92 | 1 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,78 @@ |
| 0 |
+// +build !darwin,!windows |
|
| 1 |
+ |
|
| 2 |
+package system // import "github.com/docker/docker/pkg/system" |
|
| 3 |
+ |
|
| 4 |
+import ( |
|
| 5 |
+ "os" |
|
| 6 |
+ "syscall" |
|
| 7 |
+ "time" |
|
| 8 |
+ |
|
| 9 |
+ "github.com/moby/sys/mount" |
|
| 10 |
+ "github.com/pkg/errors" |
|
| 11 |
+) |
|
| 12 |
+ |
|
| 13 |
+// EnsureRemoveAll wraps `os.RemoveAll` to check for specific errors that can |
|
| 14 |
+// often be remedied. |
|
| 15 |
+// Only use `EnsureRemoveAll` if you really want to make every effort to remove |
|
| 16 |
+// a directory. |
|
| 17 |
+// |
|
| 18 |
+// Because of the way `os.Remove` (and by extension `os.RemoveAll`) works, there |
|
| 19 |
+// can be a race between reading directory entries and then actually attempting |
|
| 20 |
+// to remove everything in the directory. |
|
| 21 |
+// These types of errors do not need to be returned since it's ok for the dir to |
|
| 22 |
+// be gone we can just retry the remove operation. |
|
| 23 |
+// |
|
| 24 |
+// This should not return a `os.ErrNotExist` kind of error under any circumstances |
|
| 25 |
+func EnsureRemoveAll(dir string) error {
|
|
| 26 |
+ notExistErr := make(map[string]bool) |
|
| 27 |
+ |
|
| 28 |
+ // track retries |
|
| 29 |
+ exitOnErr := make(map[string]int) |
|
| 30 |
+ maxRetry := 50 |
|
| 31 |
+ |
|
| 32 |
+ // Attempt to unmount anything beneath this dir first |
|
| 33 |
+ mount.RecursiveUnmount(dir) |
|
| 34 |
+ |
|
| 35 |
+ for {
|
|
| 36 |
+ err := os.RemoveAll(dir) |
|
| 37 |
+ if err == nil {
|
|
| 38 |
+ return nil |
|
| 39 |
+ } |
|
| 40 |
+ |
|
| 41 |
+ pe, ok := err.(*os.PathError) |
|
| 42 |
+ if !ok {
|
|
| 43 |
+ return err |
|
| 44 |
+ } |
|
| 45 |
+ |
|
| 46 |
+ if os.IsNotExist(err) {
|
|
| 47 |
+ if notExistErr[pe.Path] {
|
|
| 48 |
+ return err |
|
| 49 |
+ } |
|
| 50 |
+ notExistErr[pe.Path] = true |
|
| 51 |
+ |
|
| 52 |
+ // There is a race where some subdir can be removed but after the parent |
|
| 53 |
+ // dir entries have been read. |
|
| 54 |
+ // So the path could be from `os.Remove(subdir)` |
|
| 55 |
+ // If the reported non-existent path is not the passed in `dir` we |
|
| 56 |
+ // should just retry, but otherwise return with no error. |
|
| 57 |
+ if pe.Path == dir {
|
|
| 58 |
+ return nil |
|
| 59 |
+ } |
|
| 60 |
+ continue |
|
| 61 |
+ } |
|
| 62 |
+ |
|
| 63 |
+ if pe.Err != syscall.EBUSY {
|
|
| 64 |
+ return err |
|
| 65 |
+ } |
|
| 66 |
+ |
|
| 67 |
+ if e := mount.Unmount(pe.Path); e != nil {
|
|
| 68 |
+ return errors.Wrapf(e, "error while removing %s", dir) |
|
| 69 |
+ } |
|
| 70 |
+ |
|
| 71 |
+ if exitOnErr[pe.Path] == maxRetry {
|
|
| 72 |
+ return err |
|
| 73 |
+ } |
|
| 74 |
+ exitOnErr[pe.Path]++ |
|
| 75 |
+ time.Sleep(100 * time.Millisecond) |
|
| 76 |
+ } |
|
| 77 |
+} |
| 0 | 78 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,36 @@ |
| 0 |
+// +build !darwin |
|
| 1 |
+ |
|
| 2 |
+package system // import "github.com/docker/docker/pkg/system" |
|
| 3 |
+ |
|
| 4 |
+import ( |
|
| 5 |
+ "io/ioutil" |
|
| 6 |
+ "testing" |
|
| 7 |
+) |
|
| 8 |
+ |
|
| 9 |
+func TestEnsureRemoveAllNotExist(t *testing.T) {
|
|
| 10 |
+ // should never return an error for a non-existent path |
|
| 11 |
+ if err := EnsureRemoveAll("/non/existent/path"); err != nil {
|
|
| 12 |
+ t.Fatal(err) |
|
| 13 |
+ } |
|
| 14 |
+} |
|
| 15 |
+ |
|
| 16 |
+func TestEnsureRemoveAllWithDir(t *testing.T) {
|
|
| 17 |
+ dir, err := ioutil.TempDir("", "test-ensure-removeall-with-dir")
|
|
| 18 |
+ if err != nil {
|
|
| 19 |
+ t.Fatal(err) |
|
| 20 |
+ } |
|
| 21 |
+ if err := EnsureRemoveAll(dir); err != nil {
|
|
| 22 |
+ t.Fatal(err) |
|
| 23 |
+ } |
|
| 24 |
+} |
|
| 25 |
+ |
|
| 26 |
+func TestEnsureRemoveAllWithFile(t *testing.T) {
|
|
| 27 |
+ tmp, err := ioutil.TempFile("", "test-ensure-removeall-with-dir")
|
|
| 28 |
+ if err != nil {
|
|
| 29 |
+ t.Fatal(err) |
|
| 30 |
+ } |
|
| 31 |
+ tmp.Close() |
|
| 32 |
+ if err := EnsureRemoveAll(tmp.Name()); err != nil {
|
|
| 33 |
+ t.Fatal(err) |
|
| 34 |
+ } |
|
| 35 |
+} |
| ... | ... |
@@ -1,34 +1,56 @@ |
| 1 |
+// +build !darwin,!windows |
|
| 2 |
+ |
|
| 1 | 3 |
package system // import "github.com/docker/docker/pkg/system" |
| 2 | 4 |
|
| 3 | 5 |
import ( |
| 4 | 6 |
"io/ioutil" |
| 7 |
+ "os" |
|
| 8 |
+ "path/filepath" |
|
| 5 | 9 |
"testing" |
| 10 |
+ "time" |
|
| 11 |
+ |
|
| 12 |
+ "github.com/moby/sys/mount" |
|
| 13 |
+ "gotest.tools/v3/skip" |
|
| 6 | 14 |
) |
| 7 | 15 |
|
| 8 |
-func TestEnsureRemoveAllNotExist(t *testing.T) {
|
|
| 9 |
- // should never return an error for a non-existent path |
|
| 10 |
- if err := EnsureRemoveAll("/non/existent/path"); err != nil {
|
|
| 11 |
- t.Fatal(err) |
|
| 12 |
- } |
|
| 13 |
-} |
|
| 16 |
+func TestEnsureRemoveAllWithMount(t *testing.T) {
|
|
| 17 |
+ skip.If(t, os.Getuid() != 0, "skipping test that requires root") |
|
| 14 | 18 |
|
| 15 |
-func TestEnsureRemoveAllWithDir(t *testing.T) {
|
|
| 16 |
- dir, err := ioutil.TempDir("", "test-ensure-removeall-with-dir")
|
|
| 19 |
+ dir1, err := ioutil.TempDir("", "test-ensure-removeall-with-dir1")
|
|
| 17 | 20 |
if err != nil {
|
| 18 | 21 |
t.Fatal(err) |
| 19 | 22 |
} |
| 20 |
- if err := EnsureRemoveAll(dir); err != nil {
|
|
| 23 |
+ dir2, err := ioutil.TempDir("", "test-ensure-removeall-with-dir2")
|
|
| 24 |
+ if err != nil {
|
|
| 21 | 25 |
t.Fatal(err) |
| 22 | 26 |
} |
| 23 |
-} |
|
| 27 |
+ defer os.RemoveAll(dir2) |
|
| 24 | 28 |
|
| 25 |
-func TestEnsureRemoveAllWithFile(t *testing.T) {
|
|
| 26 |
- tmp, err := ioutil.TempFile("", "test-ensure-removeall-with-dir")
|
|
| 27 |
- if err != nil {
|
|
| 29 |
+ bindDir := filepath.Join(dir1, "bind") |
|
| 30 |
+ if err := os.MkdirAll(bindDir, 0755); err != nil {
|
|
| 28 | 31 |
t.Fatal(err) |
| 29 | 32 |
} |
| 30 |
- tmp.Close() |
|
| 31 |
- if err := EnsureRemoveAll(tmp.Name()); err != nil {
|
|
| 33 |
+ |
|
| 34 |
+ if err := mount.Mount(dir2, bindDir, "none", "bind"); err != nil {
|
|
| 32 | 35 |
t.Fatal(err) |
| 33 | 36 |
} |
| 37 |
+ |
|
| 38 |
+ done := make(chan struct{}, 1)
|
|
| 39 |
+ go func() {
|
|
| 40 |
+ err = EnsureRemoveAll(dir1) |
|
| 41 |
+ close(done) |
|
| 42 |
+ }() |
|
| 43 |
+ |
|
| 44 |
+ select {
|
|
| 45 |
+ case <-done: |
|
| 46 |
+ if err != nil {
|
|
| 47 |
+ t.Fatal(err) |
|
| 48 |
+ } |
|
| 49 |
+ case <-time.After(5 * time.Second): |
|
| 50 |
+ t.Fatal("timeout waiting for EnsureRemoveAll to finish")
|
|
| 51 |
+ } |
|
| 52 |
+ |
|
| 53 |
+ if _, err := os.Stat(dir1); !os.IsNotExist(err) {
|
|
| 54 |
+ t.Fatalf("expected %q to not exist", dir1)
|
|
| 55 |
+ } |
|
| 34 | 56 |
} |
| 35 | 57 |
deleted file mode 100644 |
| ... | ... |
@@ -1,78 +0,0 @@ |
| 1 |
-// +build !windows |
|
| 2 |
- |
|
| 3 |
-package system // import "github.com/docker/docker/pkg/system" |
|
| 4 |
- |
|
| 5 |
-import ( |
|
| 6 |
- "os" |
|
| 7 |
- "syscall" |
|
| 8 |
- "time" |
|
| 9 |
- |
|
| 10 |
- "github.com/moby/sys/mount" |
|
| 11 |
- "github.com/pkg/errors" |
|
| 12 |
-) |
|
| 13 |
- |
|
| 14 |
-// EnsureRemoveAll wraps `os.RemoveAll` to check for specific errors that can |
|
| 15 |
-// often be remedied. |
|
| 16 |
-// Only use `EnsureRemoveAll` if you really want to make every effort to remove |
|
| 17 |
-// a directory. |
|
| 18 |
-// |
|
| 19 |
-// Because of the way `os.Remove` (and by extension `os.RemoveAll`) works, there |
|
| 20 |
-// can be a race between reading directory entries and then actually attempting |
|
| 21 |
-// to remove everything in the directory. |
|
| 22 |
-// These types of errors do not need to be returned since it's ok for the dir to |
|
| 23 |
-// be gone we can just retry the remove operation. |
|
| 24 |
-// |
|
| 25 |
-// This should not return a `os.ErrNotExist` kind of error under any circumstances |
|
| 26 |
-func EnsureRemoveAll(dir string) error {
|
|
| 27 |
- notExistErr := make(map[string]bool) |
|
| 28 |
- |
|
| 29 |
- // track retries |
|
| 30 |
- exitOnErr := make(map[string]int) |
|
| 31 |
- maxRetry := 50 |
|
| 32 |
- |
|
| 33 |
- // Attempt to unmount anything beneath this dir first |
|
| 34 |
- mount.RecursiveUnmount(dir) |
|
| 35 |
- |
|
| 36 |
- for {
|
|
| 37 |
- err := os.RemoveAll(dir) |
|
| 38 |
- if err == nil {
|
|
| 39 |
- return nil |
|
| 40 |
- } |
|
| 41 |
- |
|
| 42 |
- pe, ok := err.(*os.PathError) |
|
| 43 |
- if !ok {
|
|
| 44 |
- return err |
|
| 45 |
- } |
|
| 46 |
- |
|
| 47 |
- if os.IsNotExist(err) {
|
|
| 48 |
- if notExistErr[pe.Path] {
|
|
| 49 |
- return err |
|
| 50 |
- } |
|
| 51 |
- notExistErr[pe.Path] = true |
|
| 52 |
- |
|
| 53 |
- // There is a race where some subdir can be removed but after the parent |
|
| 54 |
- // dir entries have been read. |
|
| 55 |
- // So the path could be from `os.Remove(subdir)` |
|
| 56 |
- // If the reported non-existent path is not the passed in `dir` we |
|
| 57 |
- // should just retry, but otherwise return with no error. |
|
| 58 |
- if pe.Path == dir {
|
|
| 59 |
- return nil |
|
| 60 |
- } |
|
| 61 |
- continue |
|
| 62 |
- } |
|
| 63 |
- |
|
| 64 |
- if pe.Err != syscall.EBUSY {
|
|
| 65 |
- return err |
|
| 66 |
- } |
|
| 67 |
- |
|
| 68 |
- if e := mount.Unmount(pe.Path); e != nil {
|
|
| 69 |
- return errors.Wrapf(e, "error while removing %s", dir) |
|
| 70 |
- } |
|
| 71 |
- |
|
| 72 |
- if exitOnErr[pe.Path] == maxRetry {
|
|
| 73 |
- return err |
|
| 74 |
- } |
|
| 75 |
- exitOnErr[pe.Path]++ |
|
| 76 |
- time.Sleep(100 * time.Millisecond) |
|
| 77 |
- } |
|
| 78 |
-} |
| 79 | 1 |
deleted file mode 100644 |
| ... | ... |
@@ -1,58 +0,0 @@ |
| 1 |
-// +build !windows |
|
| 2 |
- |
|
| 3 |
-package system // import "github.com/docker/docker/pkg/system" |
|
| 4 |
- |
|
| 5 |
-import ( |
|
| 6 |
- "io/ioutil" |
|
| 7 |
- "os" |
|
| 8 |
- "path/filepath" |
|
| 9 |
- "runtime" |
|
| 10 |
- "testing" |
|
| 11 |
- "time" |
|
| 12 |
- |
|
| 13 |
- "github.com/moby/sys/mount" |
|
| 14 |
- "gotest.tools/v3/skip" |
|
| 15 |
-) |
|
| 16 |
- |
|
| 17 |
-func TestEnsureRemoveAllWithMount(t *testing.T) {
|
|
| 18 |
- skip.If(t, runtime.GOOS == "windows", "mount not supported on Windows") |
|
| 19 |
- skip.If(t, os.Getuid() != 0, "skipping test that requires root") |
|
| 20 |
- |
|
| 21 |
- dir1, err := ioutil.TempDir("", "test-ensure-removeall-with-dir1")
|
|
| 22 |
- if err != nil {
|
|
| 23 |
- t.Fatal(err) |
|
| 24 |
- } |
|
| 25 |
- dir2, err := ioutil.TempDir("", "test-ensure-removeall-with-dir2")
|
|
| 26 |
- if err != nil {
|
|
| 27 |
- t.Fatal(err) |
|
| 28 |
- } |
|
| 29 |
- defer os.RemoveAll(dir2) |
|
| 30 |
- |
|
| 31 |
- bindDir := filepath.Join(dir1, "bind") |
|
| 32 |
- if err := os.MkdirAll(bindDir, 0755); err != nil {
|
|
| 33 |
- t.Fatal(err) |
|
| 34 |
- } |
|
| 35 |
- |
|
| 36 |
- if err := mount.Mount(dir2, bindDir, "none", "bind"); err != nil {
|
|
| 37 |
- t.Fatal(err) |
|
| 38 |
- } |
|
| 39 |
- |
|
| 40 |
- done := make(chan struct{}, 1)
|
|
| 41 |
- go func() {
|
|
| 42 |
- err = EnsureRemoveAll(dir1) |
|
| 43 |
- close(done) |
|
| 44 |
- }() |
|
| 45 |
- |
|
| 46 |
- select {
|
|
| 47 |
- case <-done: |
|
| 48 |
- if err != nil {
|
|
| 49 |
- t.Fatal(err) |
|
| 50 |
- } |
|
| 51 |
- case <-time.After(5 * time.Second): |
|
| 52 |
- t.Fatal("timeout waiting for EnsureRemoveAll to finish")
|
|
| 53 |
- } |
|
| 54 |
- |
|
| 55 |
- if _, err := os.Stat(dir1); !os.IsNotExist(err) {
|
|
| 56 |
- t.Fatalf("expected %q to not exist", dir1)
|
|
| 57 |
- } |
|
| 58 |
-} |