Use mount.Target to specify subdirectory of rootfs mount
- Add Target to mount.Mount. - Add UnmountMounts to unmount a list of mounts in reverse order. - Add UnmountRecursive to unmount deepest mount first for a given target, using moby/sys/mountinfo. Signed-off-by: Edgar Lee <edgarhinshunlee@gmail.com>
This commit is contained in:
@@ -16,6 +16,12 @@
|
||||
|
||||
package mount
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/containerd/continuity/fs"
|
||||
)
|
||||
|
||||
// Mount is the lingua franca of containerd. A mount represents a
|
||||
// serialized mount syscall. Components either emit or consume mounts.
|
||||
type Mount struct {
|
||||
@@ -24,12 +30,16 @@ type Mount struct {
|
||||
// Source specifies where to mount from. Depending on the host system, this
|
||||
// can be a source path or device.
|
||||
Source string
|
||||
// Target specifies an optional subdirectory as a mountpoint. It assumes that
|
||||
// the subdirectory exists in a parent mount.
|
||||
Target string
|
||||
// Options contains zero or more fstab-style mount options. Typically,
|
||||
// these are platform specific.
|
||||
Options []string
|
||||
}
|
||||
|
||||
// All mounts all the provided mounts to the provided target
|
||||
// All mounts all the provided mounts to the provided target. If submounts are
|
||||
// present, it assumes that parent mounts come before child mounts.
|
||||
func All(mounts []Mount, target string) error {
|
||||
for _, m := range mounts {
|
||||
if err := m.Mount(target); err != nil {
|
||||
@@ -38,3 +48,30 @@ func All(mounts []Mount, target string) error {
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// UnmountMounts unmounts all the mounts under a target in the reverse order of
|
||||
// the mounts array provided.
|
||||
func UnmountMounts(mounts []Mount, target string, flags int) error {
|
||||
for i := len(mounts) - 1; i >= 0; i-- {
|
||||
mountpoint, err := fs.RootPath(target, mounts[i].Target)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := UnmountAll(mountpoint, flags); err != nil {
|
||||
if i == len(mounts)-1 { // last mount
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Mount to the provided target path.
|
||||
func (m *Mount) Mount(target string) error {
|
||||
target, err := fs.RootPath(target, m.Target)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to join path %q with root %q: %w", m.Target, target, err)
|
||||
}
|
||||
return m.mount(target)
|
||||
}
|
||||
|
||||
@@ -31,15 +31,12 @@ var (
|
||||
ErrNotImplementOnUnix = errors.New("not implemented under unix")
|
||||
)
|
||||
|
||||
// Mount to the provided target path.
|
||||
func (m *Mount) Mount(target string) error {
|
||||
// The "syscall" and "golang.org/x/sys/unix" packages do not define a Mount
|
||||
// function for FreeBSD, so instead we execute mount(8) and trust it to do
|
||||
// the right thing
|
||||
return m.mountWithHelper(target)
|
||||
}
|
||||
|
||||
func (m *Mount) mountWithHelper(target string) error {
|
||||
// Mount to the provided target.
|
||||
//
|
||||
// The "syscall" and "golang.org/x/sys/unix" packages do not define a Mount
|
||||
// function for FreeBSD, so instead we execute mount(8) and trust it to do
|
||||
// the right thing
|
||||
func (m *Mount) mount(target string) error {
|
||||
// target: "/foo/target"
|
||||
// command: "mount -o ro -t nullfs /foo/source /foo/merged"
|
||||
// Note: FreeBSD mount(8) is particular about the order of flags and arguments
|
||||
|
||||
@@ -42,7 +42,7 @@ func init() {
|
||||
//
|
||||
// If m.Type starts with "fuse." or "fuse3.", "mount.fuse" or "mount.fuse3"
|
||||
// helper binary is called.
|
||||
func (m *Mount) Mount(target string) (err error) {
|
||||
func (m *Mount) mount(target string) (err error) {
|
||||
for _, helperBinary := range allowedHelperBinaries {
|
||||
// helperBinary = "mount.fuse", typePrefix = "fuse."
|
||||
typePrefix := strings.TrimPrefix(helperBinary, "mount.") + "."
|
||||
|
||||
@@ -172,3 +172,67 @@ func TestMountAt(t *testing.T) {
|
||||
t.Fatalf("unexpected working directory: %s", newWD)
|
||||
}
|
||||
}
|
||||
|
||||
func TestUnmountMounts(t *testing.T) {
|
||||
testutil.RequiresRoot(t)
|
||||
|
||||
target, mounts := setupMounts(t)
|
||||
if err := UnmountMounts(mounts, target, 0); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestUnmountRecursive(t *testing.T) {
|
||||
testutil.RequiresRoot(t)
|
||||
|
||||
target, _ := setupMounts(t)
|
||||
if err := UnmountRecursive(target, 0); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func setupMounts(t *testing.T) (target string, mounts []Mount) {
|
||||
dir1 := t.TempDir()
|
||||
dir2 := t.TempDir()
|
||||
|
||||
if err := os.Mkdir(filepath.Join(dir1, "foo"), 0755); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
mounts = append(mounts, Mount{
|
||||
Type: "bind",
|
||||
Source: dir1,
|
||||
Options: []string{
|
||||
"ro",
|
||||
"rbind",
|
||||
},
|
||||
})
|
||||
|
||||
if err := os.WriteFile(filepath.Join(dir2, "bar"), []byte("bar"), 0644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
mounts = append(mounts, Mount{
|
||||
Type: "bind",
|
||||
Source: dir2,
|
||||
Target: "foo",
|
||||
Options: []string{
|
||||
"ro",
|
||||
"rbind",
|
||||
},
|
||||
})
|
||||
|
||||
target = t.TempDir()
|
||||
if err := All(mounts, target); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
b, err := os.ReadFile(filepath.Join(target, "foo/bar"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if string(b) != "bar" {
|
||||
t.Fatalf("unexpected file content: %s", b)
|
||||
}
|
||||
|
||||
return target, mounts
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
//go:build darwin || openbsd
|
||||
//go:build !windows && !darwin && !openbsd
|
||||
|
||||
/*
|
||||
Copyright The containerd Authors.
|
||||
@@ -18,24 +18,41 @@
|
||||
|
||||
package mount
|
||||
|
||||
import "errors"
|
||||
import (
|
||||
"sort"
|
||||
|
||||
var (
|
||||
// ErrNotImplementOnUnix is returned for methods that are not implemented
|
||||
ErrNotImplementOnUnix = errors.New("not implemented under unix")
|
||||
"github.com/moby/sys/mountinfo"
|
||||
)
|
||||
|
||||
// Mount is not implemented on this platform
|
||||
func (m *Mount) Mount(target string) error {
|
||||
return ErrNotImplementOnUnix
|
||||
}
|
||||
// UnmountRecursive unmounts the target and all mounts underneath, starting
|
||||
// with the deepest mount first.
|
||||
func UnmountRecursive(target string, flags int) error {
|
||||
mounts, err := mountinfo.GetMounts(mountinfo.PrefixFilter(target))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Unmount is not implemented on this platform
|
||||
func Unmount(mount string, flags int) error {
|
||||
return ErrNotImplementOnUnix
|
||||
}
|
||||
targetSet := make(map[string]struct{})
|
||||
for _, m := range mounts {
|
||||
targetSet[m.Mountpoint] = struct{}{}
|
||||
}
|
||||
|
||||
// UnmountAll is not implemented on this platform
|
||||
func UnmountAll(mount string, flags int) error {
|
||||
return ErrNotImplementOnUnix
|
||||
var targets []string
|
||||
for m := range targetSet {
|
||||
targets = append(targets, m)
|
||||
}
|
||||
|
||||
// Make the deepest mount be first
|
||||
sort.SliceStable(targets, func(i, j int) bool {
|
||||
return len(targets[i]) > len(targets[j])
|
||||
})
|
||||
|
||||
for i, target := range targets {
|
||||
if err := UnmountAll(target, flags); err != nil {
|
||||
if i == len(targets)-1 { // last mount
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
46
mount/mount_unsupported.go
Normal file
46
mount/mount_unsupported.go
Normal file
@@ -0,0 +1,46 @@
|
||||
//go:build darwin || openbsd
|
||||
|
||||
/*
|
||||
Copyright The containerd Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package mount
|
||||
|
||||
import "errors"
|
||||
|
||||
var (
|
||||
// ErrNotImplementOnUnix is returned for methods that are not implemented
|
||||
ErrNotImplementOnUnix = errors.New("not implemented under unix")
|
||||
)
|
||||
|
||||
// Mount is not implemented on this platform
|
||||
func (m *Mount) mount(target string) error {
|
||||
return ErrNotImplementOnUnix
|
||||
}
|
||||
|
||||
// Unmount is not implemented on this platform
|
||||
func Unmount(mount string, flags int) error {
|
||||
return ErrNotImplementOnUnix
|
||||
}
|
||||
|
||||
// UnmountAll is not implemented on this platform
|
||||
func UnmountAll(mount string, flags int) error {
|
||||
return ErrNotImplementOnUnix
|
||||
}
|
||||
|
||||
// UnmountRecursive is not implemented on this platform
|
||||
func UnmountRecursive(mount string, flags int) error {
|
||||
return ErrNotImplementOnUnix
|
||||
}
|
||||
@@ -26,8 +26,8 @@ import (
|
||||
"github.com/Microsoft/hcsshim"
|
||||
)
|
||||
|
||||
// Mount to the provided target
|
||||
func (m *Mount) Mount(target string) error {
|
||||
// Mount to the provided target.
|
||||
func (m *Mount) mount(target string) error {
|
||||
if m.Type != "windows-layer" {
|
||||
return fmt.Errorf("invalid windows mount type: '%s'", m.Type)
|
||||
}
|
||||
@@ -58,8 +58,9 @@ func (m *Mount) Mount(target string) error {
|
||||
return fmt.Errorf("failed to get layer mount path for %s: %w", m.Source, err)
|
||||
}
|
||||
mountPath = mountPath + `\`
|
||||
|
||||
if err = os.Symlink(mountPath, target); err != nil {
|
||||
return fmt.Errorf("failed to link mount to taget %s: %w", target, err)
|
||||
return fmt.Errorf("failed to link mount to target %s: %w", target, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -105,3 +106,8 @@ func Unmount(mount string, flags int) error {
|
||||
func UnmountAll(mount string, flags int) error {
|
||||
return Unmount(mount, flags)
|
||||
}
|
||||
|
||||
// UnmountRecursive unmounts from the provided path
|
||||
func UnmountRecursive(mount string, flags int) error {
|
||||
return UnmountAll(mount, flags)
|
||||
}
|
||||
|
||||
@@ -49,7 +49,7 @@ func WithTempMount(ctx context.Context, mounts []Mount, f func(root string) erro
|
||||
|
||||
// We should do defer first, if not we will not do Unmount when only a part of Mounts are failed.
|
||||
defer func() {
|
||||
if uerr = UnmountAll(root, 0); uerr != nil {
|
||||
if uerr = UnmountMounts(mounts, root, 0); uerr != nil {
|
||||
uerr = fmt.Errorf("failed to unmount %s: %w", root, uerr)
|
||||
if err == nil {
|
||||
err = uerr
|
||||
|
||||
Reference in New Issue
Block a user