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