@@ -5,7 +5,9 @@ package mount
55
66import (
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