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