Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
104 changes: 101 additions & 3 deletions mount/mount_windows.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,15 @@ package mount

import (
"encoding/json"
"io/ioutil"
"os"
"path/filepath"
"strings"
"syscall"

"github.com/Microsoft/hcsshim"
"github.com/pkg/errors"
"golang.org/x/sys/windows"
)

var (
Expand All @@ -31,7 +35,7 @@ var (
)

// Mount to the provided target
func (m *Mount) Mount(target string) error {
func (m *Mount) Mount(target string) (retErr error) {
if m.Type != "windows-layer" {
return errors.Errorf("invalid windows mount type: '%s'", m.Type)
}
Expand All @@ -51,14 +55,70 @@ func (m *Mount) Mount(target string) error {
return errors.Wrapf(err, "failed to activate layer %s", m.Source)
}
defer func() {
if err != nil {
if retErr != nil {
hcsshim.DeactivateLayer(di, layerID)
}
}()

if err = hcsshim.PrepareLayer(di, layerID, parentLayerPaths); err != nil {
return errors.Wrapf(err, "failed to prepare layer %s", m.Source)
}
layerPath, err := hcsshim.GetLayerMountPath(di, layerID)
if err != nil {
return errors.Wrapf(err, "failed to get mount path for layer %s", m.Source)
}

// Check if the returned path is a new mounted volume, or the path of the expanded
// layer on disk.
// TODO: Is there a better way to check this than looking for \\?\Volume{*?
if !strings.HasPrefix(layerPath, `\\?\Volume{`) {
layerPath = filepath.Join(layerPath, "Files")
if _, err := os.Lstat(layerPath); err != nil {
if !os.IsNotExist(err) {
return errors.Wrapf(err, "failed to find Files dir")
}
if err := os.Mkdir(layerPath, 0755); err != nil {
return errors.Wrap(err, "failed to create Files dir")
}
}
if err := os.Remove(target); err != nil {
return errors.Wrapf(err, "remove target prior to mounting %q", target)
}
if err := os.Symlink(layerPath, target); err != nil {
return errors.Wrapf(err, "failed to mount layer %q at %q", m.Source, target)
}
} else {
target = filepath.Clean(target) + string(filepath.Separator)
targetp, err := syscall.UTF16PtrFromString(target)
if err != nil {
return err
}

volName := filepath.Clean(layerPath) + string(filepath.Separator)
volNamep, err := syscall.UTF16PtrFromString(volName)
if err != nil {
return err
}

if err := windows.SetVolumeMountPoint(targetp, volNamep); err != nil {
return errors.Wrapf(err, "failed to mount layer %q at %q, volume: %q", m.Source, target, volName)
}
}

// Remove any trailing slashes in preparation to add an Alternate Data Stream for the layerid,
// see https://blogs.technet.microsoft.com/askcore/2013/03/24/alternate-data-streams-in-ntfs/
// for details on Alternate Data Streams.
target = filepath.Clean(target)
idf, err := os.Create(target + ":layerid")
if err != nil {
return err
}
defer idf.Close()

_, err = idf.Write([]byte(m.Source))
if err != nil {
return err
}
return nil
}

Expand All @@ -82,13 +142,51 @@ func (m *Mount) GetParentPaths() ([]string, error) {

// Unmount the mount at the provided path
func Unmount(mount string, flags int) error {
fi, err := os.Lstat(mount)
if err != nil {
return errors.Wrapf(err, "unable to find mounted volume %s", mount)
}

// Remove any trailing slashes
mount = filepath.Clean(mount)
dir, file := filepath.Split(mount)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

looks like this is not necessary, dir and file are only used on the next line :layerid could be appended directly to the call to ReadFile

layerPathb, err := ioutil.ReadFile(filepath.Join(dir, file+":layerid"))
if err != nil {
return err
}
layerPath := string(layerPathb)

var (
home, layerID = filepath.Split(mount)
home, layerID = filepath.Split(layerPath)
di = hcsshim.DriverInfo{
HomeDir: home,
}
)

if fi.Mode()&os.ModeSymlink != 0 {
if err := os.Remove(mount); err != nil {
return errors.Wrap(err, "failed to delete mount")
}
} else {
mount = filepath.Clean(mount) + string(filepath.Separator)
mountp, err := syscall.UTF16PtrFromString(mount)
if err != nil {
return err
}

const volumeNameLen = 50
volumeNamep := make([]uint16, volumeNameLen)
volumeNamep[0] = 0

if err := windows.GetVolumeNameForVolumeMountPoint(mountp, &volumeNamep[0], volumeNameLen); err != nil {
return errors.Wrapf(err, "unable to find mounted volume %s", mount)
}

if err := windows.DeleteVolumeMountPoint(&volumeNamep[0]); err != nil {
return errors.Wrapf(err, "unable to delete mounted volume %s", mount)
}
}

if err := hcsshim.UnprepareLayer(di, layerID); err != nil {
return errors.Wrapf(err, "failed to unprepare layer %s", mount)
}
Expand Down
19 changes: 19 additions & 0 deletions pkg/testutil/helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,19 @@
package testutil

import (
"context"
"flag"
"fmt"
"io/ioutil"
"os"
"path/filepath"
"strconv"
"testing"

"github.com/containerd/containerd/mount"
"github.com/containerd/containerd/snapshots"
"github.com/pkg/errors"
"gotest.tools/assert"
)

var rootEnabled bool
Expand Down Expand Up @@ -80,3 +86,16 @@ func DumpDirOnFailure(t *testing.T, root string) {
DumpDir(t, root)
}
}

// Unmount unmounts a given mountPoint and sets t.Error if it fails
func Unmount(t testing.TB, mountPoint string) {
t.Log("unmount", mountPoint)
err := mount.UnmountAll(mountPoint, umountflags)
assert.NilError(t, errors.Wrap(err, "failed to unmount"))
}

// RemoveSnapshot removes the snapshot from the snapshotter and sets t.Error if it fails
func RemoveSnapshot(ctx context.Context, t testing.TB, sn snapshots.Snapshotter, snapshot string) {
err := sn.Remove(ctx, snapshot)
assert.NilError(t, errors.Wrap(err, "failed to remove snapshot"))
}
8 changes: 0 additions & 8 deletions pkg/testutil/helpers_unix.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,17 +23,9 @@ import (
"os"
"testing"

"github.com/containerd/containerd/mount"
"gotest.tools/assert"
)

// Unmount unmounts a given mountPoint and sets t.Error if it fails
func Unmount(t testing.TB, mountPoint string) {
t.Log("unmount", mountPoint)
err := mount.UnmountAll(mountPoint, umountflags)
assert.NilError(t, err)
}

// RequiresRoot skips tests that require root, unless the test.root flag has
// been set
func RequiresRoot(t testing.TB) {
Expand Down
5 changes: 0 additions & 5 deletions pkg/testutil/helpers_windows.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,3 @@ func RequiresRoot(t testing.TB) {
// RequiresRootM is similar to RequiresRoot but intended to be called from *testing.M.
func RequiresRootM() {
}

// Unmount unmounts a given mountPoint and sets t.Error if it fails
// Does nothing on Windows
func Unmount(t *testing.T, mountPoint string) {
}
2 changes: 1 addition & 1 deletion pkg/testutil/mount_other.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// +build !linux,!windows
// +build !linux

/*
Copyright The containerd Authors.
Expand Down
43 changes: 40 additions & 3 deletions snapshots/testsuite/helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,32 @@ func createSnapshot(ctx context.Context, sn snapshots.Snapshotter, parent, work
return n, nil
}

// createBaseSnapshot creates a new base snapshot in the snapshotter
// given an applier to run, and applies base processing if necessary.
func createBaseSnapshot(ctx context.Context, sn snapshots.Snapshotter, work string, a fstest.Applier) (string, error) {
n := fmt.Sprintf("%p-%d", a, rand.Int())
prepare := fmt.Sprintf("%s-prepare", n)

m, err := sn.Prepare(ctx, prepare, "", opt)
if err != nil {
return "", errors.Wrap(err, "failed to prepare snapshot")
}

if err := applyToMounts(m, work, a); err != nil {
return "", errors.Wrap(err, "failed to apply")
}

if err := setupBaseSnapshot(ctx, sn, prepare); err != nil {
return "", errors.Wrap(err, "failed to set up base snapshot")
}

if err := sn.Commit(ctx, n, prepare, opt); err != nil {
return "", errors.Wrap(err, "failed to commit")
}

return n, nil
}

func checkSnapshot(ctx context.Context, sn snapshots.Snapshotter, work, name, check string) (err error) {
td, err := ioutil.TempDir(work, "check")
if err != nil {
Expand Down Expand Up @@ -120,9 +146,20 @@ func checkSnapshots(ctx context.Context, sn snapshots.Snapshotter, work string,

var parentID string
for i, a := range as {
s, err := createSnapshot(ctx, sn, parentID, work, a)
if err != nil {
return errors.Wrapf(err, "failed to create snapshot %d", i+1)
var (
s string
err error
)
if i == 0 {
s, err = createBaseSnapshot(ctx, sn, work, a)
if err != nil {
return errors.Wrapf(err, "failed to create base snapshot %d", i+1)
}
} else {
s, err = createSnapshot(ctx, sn, parentID, work, a)
if err != nil {
return errors.Wrapf(err, "failed to create snapshot %d", i+1)
}
}

if err := a.Apply(td); err != nil {
Expand Down
34 changes: 30 additions & 4 deletions snapshots/testsuite/issues.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ package testsuite
import (
"context"
"fmt"
"runtime"
"strings"
"testing"
"time"
Expand All @@ -44,6 +45,7 @@ func checkLayerFileUpdate(ctx context.Context, t *testing.T, sn snapshots.Snapsh
fstest.CreateDir("/etc", 0700),
fstest.CreateFile("/etc/hosts", []byte("mydomain 10.0.0.1"), 0644),
fstest.CreateFile("/etc/profile", []byte("PATH=/usr/bin"), 0644),
fstest.Base(),
)
l2Init := fstest.Apply(
fstest.CreateFile("/etc/hosts", []byte("mydomain 10.0.0.2"), 0644),
Expand Down Expand Up @@ -71,9 +73,13 @@ func checkLayerFileUpdate(ctx context.Context, t *testing.T, sn snapshots.Snapsh
// checkRemoveDirectoryInLowerLayer
// See https://github.com/docker/docker/issues/25244
func checkRemoveDirectoryInLowerLayer(ctx context.Context, t *testing.T, sn snapshots.Snapshotter, work string) {
if runtime.GOOS == "windows" {
t.Skip("this test is failing on Windows, the reason is unclear")
}
l1Init := fstest.Apply(
fstest.CreateDir("/lib", 0700),
fstest.CreateFile("/lib/hidden", []byte{}, 0644),
fstest.Base(),
)
l2Init := fstest.Apply(
fstest.RemoveAll("/lib"),
Expand All @@ -94,6 +100,9 @@ func checkRemoveDirectoryInLowerLayer(ctx context.Context, t *testing.T, sn snap
// See https://github.com/docker/docker/issues/24913 overlay
// see https://github.com/docker/docker/issues/28391 overlay2
func checkChown(ctx context.Context, t *testing.T, sn snapshots.Snapshotter, work string) {
if runtime.GOOS == "windows" {
t.Skip("Windows does not support chown")
}
l1Init := fstest.Apply(
fstest.CreateDir("/opt", 0700),
fstest.CreateDir("/opt/a", 0700),
Expand All @@ -115,12 +124,15 @@ func checkChown(ctx context.Context, t *testing.T, sn snapshots.Snapshotter, wor
// checkRename
// https://github.com/docker/docker/issues/25409
func checkRename(ctx context.Context, t *testing.T, sn snapshots.Snapshotter, work string) {
t.Skip("rename test still fails on some kernels with overlay")
if runtime.GOOS != "windows" {
t.Skip("rename test still fails on some kernels with overlay")
}
l1Init := fstest.Apply(
fstest.CreateDir("/dir1", 0700),
fstest.CreateDir("/somefiles", 0700),
fstest.CreateFile("/somefiles/f1", []byte("was here first!"), 0644),
fstest.CreateFile("/somefiles/f2", []byte("nothing interesting"), 0644),
fstest.Base(),
)
l2Init := fstest.Apply(
fstest.Rename("/dir1", "/dir2"),
Expand All @@ -137,6 +149,9 @@ func checkRename(ctx context.Context, t *testing.T, sn snapshots.Snapshotter, wo
// checkDirectoryPermissionOnCommit
// https://github.com/docker/docker/issues/27298
func checkDirectoryPermissionOnCommit(ctx context.Context, t *testing.T, sn snapshots.Snapshotter, work string) {
if runtime.GOOS == "windows" {
t.Skip("Windows does not support chown")
}
l1Init := fstest.Apply(
fstest.CreateDir("/dir1", 0700),
fstest.CreateDir("/dir2", 0700),
Expand Down Expand Up @@ -173,7 +188,7 @@ func checkDirectoryPermissionOnCommit(ctx context.Context, t *testing.T, sn snap
// checkStatInWalk ensures that a stat can be called during a walk
func checkStatInWalk(ctx context.Context, t *testing.T, sn snapshots.Snapshotter, work string) {
prefix := "stats-in-walk-"
if err := createNamedSnapshots(ctx, sn, prefix); err != nil {
if err := createNamedSnapshots(ctx, sn, prefix, work); err != nil {
t.Fatal(err)
}

Expand All @@ -194,12 +209,23 @@ func checkStatInWalk(ctx context.Context, t *testing.T, sn snapshots.Snapshotter
}
}

func createNamedSnapshots(ctx context.Context, snapshotter snapshots.Snapshotter, ns string) error {
func createNamedSnapshots(ctx context.Context, snapshotter snapshots.Snapshotter, ns string, work string) error {
c1 := fmt.Sprintf("%sc1", ns)
c2 := fmt.Sprintf("%sc2", ns)
if _, err := snapshotter.Prepare(ctx, c1+"-a", "", opt); err != nil {

m, err := snapshotter.Prepare(ctx, c1+"-a", "", opt)
if err != nil {
return err
}

if err := applyToMounts(m, work, fstest.Base()); err != nil {
return err
}

if err := setupBaseSnapshot(ctx, snapshotter, c1+"-a"); err != nil {
return err
}

if err := snapshotter.Commit(ctx, c1, c1+"-a", opt); err != nil {
return err
}
Expand Down
Loading