@@ -5,7 +5,9 @@ package mount
5
5
6
6
import (
7
7
"fmt"
8
+ "path"
8
9
"sort"
10
+ "strings"
9
11
10
12
"github.com/moby/sys/mountinfo"
11
13
"golang.org/x/sys/unix"
@@ -38,35 +40,56 @@ func Unmount(target string) error {
38
40
}
39
41
}
40
42
41
- // RecursiveUnmount unmounts the target and all mounts underneath, starting
42
- // with the deepest mount first. The argument does not have to be a mount
43
- // point itself.
44
- func RecursiveUnmount (target string ) error {
45
- // Fast path, works if target is a mount point that can be unmounted.
43
+ // UnmountAll unmounts all mounts and submounts underneath parent,
44
+ func UnmountAll (parent string ) error {
45
+ // Get all mounts in "parent"
46
+ mounts , err := mountinfo .GetMounts (mountinfo .PrefixFilter (parent ))
47
+ if err != nil {
48
+ return err
49
+ }
50
+
51
+ // Fast path: try to unmount top-level mounts first. This works if target is
52
+ // a mount point that can be unmounted.
46
53
// On Linux, mntDetach flag ensures a recursive unmount. For other
47
54
// platforms, if there are submounts, we'll get EBUSY (and fall back
48
- // to the slow path). NOTE we do not ignore EINVAL here as target might
49
- // not be a mount point itself (but there can be mounts underneath).
50
- if err := unix .Unmount (target , mntDetach ); err == nil {
51
- return nil
55
+ // to the slow path). We're not using RecursiveUnmount() here, to avoid
56
+ // repeatedly calling mountinfo.GetMounts()
57
+
58
+ var skipParents []string
59
+ for _ , m := range mounts {
60
+ // Skip parent itself, and skip non-top-level mounts
61
+ if m .Mountpoint == parent || path .Dir (m .Mountpoint ) != parent {
62
+ continue
63
+ }
64
+ if err := unix .Unmount (m .Mountpoint , mntDetach ); err == nil {
65
+ skipParents = append (skipParents , m .Mountpoint )
66
+ }
52
67
}
53
68
54
- // Slow path: get all submounts, sort, unmount one by one.
55
- mounts , err := mountinfo .GetMounts (mountinfo .PrefixFilter (target ))
56
- if err != nil {
57
- return err
69
+ // Remove all sub-mounts of paths that were successfully unmounted from the list
70
+ subMounts := mounts [:0 ]
71
+ for _ , m := range mounts {
72
+ for _ , p := range skipParents {
73
+ if m .Mountpoint == parent || m .Mountpoint == p {
74
+ // Skip parent itself, and mounts that already were unmounted
75
+ continue
76
+ }
77
+ if ! strings .HasPrefix (m .Mountpoint , p ) {
78
+ subMounts = append (subMounts , m )
79
+ }
80
+ }
58
81
}
59
82
60
83
// Make the deepest mount be first
61
- sort .Slice (mounts , func (i , j int ) bool {
62
- return len (mounts [i ].Mountpoint ) > len (mounts [j ].Mountpoint )
84
+ sort .Slice (subMounts , func (i , j int ) bool {
85
+ return len (subMounts [i ].Mountpoint ) > len (subMounts [j ].Mountpoint )
63
86
})
64
87
65
88
var (
66
89
suberr error
67
90
lastMount = len (mounts ) - 1
68
91
)
69
- for i , m := range mounts {
92
+ for i , m := range subMounts {
70
93
err = Unmount (m .Mountpoint )
71
94
if err != nil {
72
95
if i == lastMount {
@@ -86,3 +109,20 @@ func RecursiveUnmount(target string) error {
86
109
}
87
110
return nil
88
111
}
112
+
113
+ // RecursiveUnmount unmounts the target and all mounts underneath, starting
114
+ // with the deepest mount first. The argument does not have to be a mount
115
+ // point itself.
116
+ func RecursiveUnmount (target string ) error {
117
+ // Fast path, works if target is a mount point that can be unmounted.
118
+ // On Linux, mntDetach flag ensures a recursive unmount. For other
119
+ // platforms, if there are submounts, we'll get EBUSY (and fall back
120
+ // to the slow path). NOTE we do not ignore EINVAL here as target might
121
+ // not be a mount point itself (but there can be mounts underneath).
122
+ if err := unix .Unmount (target , mntDetach ); err == nil {
123
+ return nil
124
+ }
125
+
126
+ // Slow path: unmount all mounts inside target one by one.
127
+ return UnmountAll (target )
128
+ }
0 commit comments