Skip to content

Commit e6ec185

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 416188a commit e6ec185

File tree

1 file changed

+56
-16
lines changed

1 file changed

+56
-16
lines changed

mount/mount_unix.go

Lines changed: 56 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,9 @@ package mount
55

66
import (
77
"fmt"
8+
"path"
89
"sort"
10+
"strings"
911

1012
"github.com/moby/sys/mountinfo"
1113
"golang.org/x/sys/unix"
@@ -38,35 +40,56 @@ func Unmount(target string) error {
3840
}
3941
}
4042

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

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

6083
// 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)
6386
})
6487

6588
var (
6689
suberr error
6790
lastMount = len(mounts) - 1
6891
)
69-
for i, m := range mounts {
92+
for i, m := range subMounts {
7093
err = Unmount(m.Mountpoint)
7194
if err != nil {
7295
if i == lastMount {
@@ -86,3 +109,20 @@ func RecursiveUnmount(target string) error {
86109
}
87110
return nil
88111
}
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

Comments
 (0)