Skip to content

Commit 4c1c932

Browse files
committed
mount: implement UnmountAll()
`UnmountAll()` unmounts all mounts underneath the specified path, but (contrary to `RecursiveUnmount()`) does not unmount `path` itself. Signed-off-by: Sebastiaan van Stijn <[email protected]>
1 parent 5a29239 commit 4c1c932

File tree

1 file changed

+56
-16
lines changed

1 file changed

+56
-16
lines changed

Diff for: mount/mount_unix.go

+56-16
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,9 @@ package mount
44

55
import (
66
"fmt"
7+
"path"
78
"sort"
9+
"strings"
810

911
"github.com/moby/sys/mountinfo"
1012
"golang.org/x/sys/unix"
@@ -37,35 +39,56 @@ func Unmount(target string) error {
3739
}
3840
}
3941

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.
4552
// On Linux, mntDetach flag ensures a recursive unmount. For other
4653
// 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+
}
5166
}
5267

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+
}
5780
}
5881

5982
// 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)
6285
})
6386

6487
var (
6588
suberr error
6689
lastMount = len(mounts) - 1
6790
)
68-
for i, m := range mounts {
91+
for i, m := range subMounts {
6992
err = Unmount(m.Mountpoint)
7093
if err != nil {
7194
if i == lastMount {
@@ -85,3 +108,20 @@ func RecursiveUnmount(target string) error {
85108
}
86109
return nil
87110
}
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

Comments
 (0)