Skip to content

Commit 2126d64

Browse files
committed
WIP: mounting Windows volumes using SetVolumeMountPath
Signed-off-by: Darren Stahl <[email protected]>
1 parent e10a628 commit 2126d64

11 files changed

Lines changed: 183 additions & 17 deletions

File tree

mount/mount_windows.go

Lines changed: 95 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,15 @@ package mount
1818

1919
import (
2020
"encoding/json"
21+
"io/ioutil"
22+
"os"
2123
"path/filepath"
2224
"strings"
25+
"syscall"
2326

2427
"github.com/Microsoft/hcsshim"
2528
"github.com/pkg/errors"
29+
"golang.org/x/sys/windows"
2630
)
2731

2832
var (
@@ -31,7 +35,7 @@ var (
3135
)
3236

3337
// Mount to the provided target
34-
func (m *Mount) Mount(target string) error {
38+
func (m *Mount) Mount(target string) (retErr error) {
3539
home, layerID := filepath.Split(m.Source)
3640

3741
parentLayerPaths, err := m.GetParentPaths()
@@ -47,14 +51,67 @@ func (m *Mount) Mount(target string) error {
4751
return errors.Wrapf(err, "failed to activate layer %s", m.Source)
4852
}
4953
defer func() {
50-
if err != nil {
54+
if retErr != nil {
5155
hcsshim.DeactivateLayer(di, layerID)
5256
}
5357
}()
5458

5559
if err = hcsshim.PrepareLayer(di, layerID, parentLayerPaths); err != nil {
5660
return errors.Wrapf(err, "failed to prepare layer %s", m.Source)
5761
}
62+
layerPath, err := hcsshim.GetLayerMountPath(di, layerID)
63+
if err != nil {
64+
return errors.Wrapf(err, "failed to get mount path for layer %s", m.Source)
65+
}
66+
67+
if !strings.HasPrefix(layerPath, "\\\\?\\") {
68+
layerPath = filepath.Join(layerPath, "Files")
69+
if _, err := os.Lstat(layerPath); err != nil {
70+
if !os.IsNotExist(err) {
71+
return errors.Wrapf(err, "failed to find Files dir")
72+
}
73+
if err := os.Mkdir(layerPath, 0755); err != nil {
74+
return errors.Wrap(err, "failed to create Files dir")
75+
}
76+
}
77+
if err := os.Remove(target); err != nil {
78+
return errors.Wrapf(err, "remove target prior to mounting %q", target)
79+
}
80+
if err := os.Symlink(layerPath, target); err != nil {
81+
return errors.Wrapf(err, "failed to mount layer %q at %q", m.Source, target)
82+
}
83+
} else {
84+
target = filepath.Clean(target) + string(filepath.Separator)
85+
targetp, err := syscall.UTF16PtrFromString(target)
86+
if err != nil {
87+
return err
88+
}
89+
90+
volName := filepath.Clean(layerPath) + string(filepath.Separator)
91+
volNamep, err := syscall.UTF16PtrFromString(volName)
92+
if err != nil {
93+
return err
94+
}
95+
96+
if err := windows.SetVolumeMountPoint(targetp, volNamep); err != nil {
97+
return errors.Wrapf(err, "failed to mount layer %q at %q, volume: %q", m.Source, target, volName)
98+
}
99+
}
100+
101+
for strings.HasSuffix(target, string(os.PathSeparator)) {
102+
target = target[:len(target)-1]
103+
}
104+
105+
idf, err := os.Create(target + ":layerid")
106+
if err != nil {
107+
return err
108+
}
109+
defer idf.Close()
110+
111+
_, err = idf.Write([]byte(m.Source))
112+
if err != nil {
113+
return err
114+
}
58115
return nil
59116
}
60117

@@ -78,13 +135,48 @@ func (m *Mount) GetParentPaths() ([]string, error) {
78135

79136
// Unmount the mount at the provided path
80137
func Unmount(mount string, flags int) error {
138+
fi, err := os.Lstat(mount)
139+
if err != nil {
140+
return errors.Wrapf(err, "unable to find mounted volume %s", mount)
141+
}
142+
143+
layerPathb, err := ioutil.ReadFile(mount + ":layerid")
144+
if err != nil {
145+
return err
146+
}
147+
layerPath := string(layerPathb)
148+
81149
var (
82-
home, layerID = filepath.Split(mount)
150+
home, layerID = filepath.Split(layerPath)
83151
di = hcsshim.DriverInfo{
84152
HomeDir: home,
85153
}
86154
)
87155

156+
if fi.Mode()&os.ModeSymlink != 0 {
157+
if err := os.Remove(mount); err != nil {
158+
return errors.Wrap(err, "failed to delete mount")
159+
}
160+
} else {
161+
mount = filepath.Clean(mount) + string(filepath.Separator)
162+
mountp, err := syscall.UTF16PtrFromString(mount)
163+
if err != nil {
164+
return err
165+
}
166+
167+
const volumeNameLen = 50
168+
volumeNamep := make([]uint16, volumeNameLen)
169+
volumeNamep[0] = 0
170+
171+
if err := windows.GetVolumeNameForVolumeMountPoint(mountp, &volumeNamep[0], volumeNameLen); err != nil {
172+
return errors.Wrapf(err, "unable to find mounted volume %s", mount)
173+
}
174+
175+
if err := windows.DeleteVolumeMountPoint(&volumeNamep[0]); err != nil {
176+
return errors.Wrapf(err, "unable to delete mounted volume %s", mount)
177+
}
178+
}
179+
88180
if err := hcsshim.UnprepareLayer(di, layerID); err != nil {
89181
return errors.Wrapf(err, "failed to unprepare layer %s", mount)
90182
}

snapshots/testsuite/testsuite.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,7 @@ func checkSnapshotterBasic(ctx context.Context, t *testing.T, snapshotter snapsh
120120
fstest.CreateDir("/a", 0755),
121121
fstest.CreateDir("/a/b", 0755),
122122
fstest.CreateDir("/a/b/c", 0755),
123+
fstest.Base(),
123124
)
124125

125126
diffApplier := fstest.Apply(
@@ -152,6 +153,10 @@ func checkSnapshotterBasic(ctx context.Context, t *testing.T, snapshotter snapsh
152153
t.Fatalf("failure reason: %+v", err)
153154
}
154155

156+
if err := setupBaseSnapshot(mounts[0].Source); err != nil {
157+
t.Fatalf("failed to set up base snapshot: %+v", err)
158+
}
159+
155160
committed := filepath.Join(work, "committed")
156161
if err := snapshotter.Commit(ctx, committed, preparing, opt); err != nil {
157162
t.Fatalf("failure reason: %+v", err)

snapshots/testsuite/testsuite_unix.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,3 +26,8 @@ func clearMask() func() {
2626
syscall.Umask(oldumask)
2727
}
2828
}
29+
30+
// setupBaseSnapshot is a no-op
31+
func setupBaseSnapshot(root string) error {
32+
return nil
33+
}

snapshots/testsuite/testsuite_windows.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,15 @@
1616

1717
package testsuite
1818

19+
import "github.com/Microsoft/hcsshim"
20+
1921
func clearMask() func() {
2022
return func() {}
2123
}
24+
25+
func setupBaseSnapshot(root string) error {
26+
if err := hcsshim.ProcessBaseLayer(root); err != nil {
27+
return err
28+
}
29+
return nil
30+
}

snapshots/windows/windows_test.go

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
/*
2+
Copyright The containerd Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package windows
18+
19+
import (
20+
"context"
21+
"testing"
22+
23+
"github.com/containerd/containerd/snapshots"
24+
"github.com/containerd/containerd/snapshots/testsuite"
25+
)
26+
27+
func newSnapshotter(ctx context.Context, root string) (snapshots.Snapshotter, func() error, error) {
28+
snapshotter, err := NewSnapshotter(root)
29+
if err != nil {
30+
return nil, nil, err
31+
}
32+
33+
return snapshotter, func() error { return snapshotter.Close() }, nil
34+
}
35+
36+
func TestWindows(t *testing.T) {
37+
testsuite.SnapshotterSuite(t, "Windows", newSnapshotter)
38+
}

testutil/helpers.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,9 @@ import (
2424
"path/filepath"
2525
"strconv"
2626
"testing"
27+
28+
"github.com/containerd/containerd/mount"
29+
"github.com/gotestyourself/gotestyourself/assert"
2730
)
2831

2932
var rootEnabled bool
@@ -80,3 +83,10 @@ func DumpDirOnFailure(t *testing.T, root string) {
8083
DumpDir(t, root)
8184
}
8285
}
86+
87+
// Unmount unmounts a given mountPoint and sets t.Error if it fails
88+
func Unmount(t testing.TB, mountPoint string) {
89+
t.Log("unmount", mountPoint)
90+
err := mount.UnmountAll(mountPoint, umountflags)
91+
assert.NilError(t, err)
92+
}

testutil/helpers_unix.go

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -23,17 +23,9 @@ import (
2323
"os"
2424
"testing"
2525

26-
"github.com/containerd/containerd/mount"
2726
"github.com/gotestyourself/gotestyourself/assert"
2827
)
2928

30-
// Unmount unmounts a given mountPoint and sets t.Error if it fails
31-
func Unmount(t testing.TB, mountPoint string) {
32-
t.Log("unmount", mountPoint)
33-
err := mount.UnmountAll(mountPoint, umountflags)
34-
assert.NilError(t, err)
35-
}
36-
3729
// RequiresRoot skips tests that require root, unless the test.root flag has
3830
// been set
3931
func RequiresRoot(t testing.TB) {

testutil/helpers_windows.go

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,3 @@ func RequiresRoot(t testing.TB) {
2525
// RequiresRootM is similar to RequiresRoot but intended to be called from *testing.M.
2626
func RequiresRootM() {
2727
}
28-
29-
// Unmount unmounts a given mountPoint and sets t.Error if it fails
30-
// Does nothing on Windows
31-
func Unmount(t *testing.T, mountPoint string) {
32-
}

testutil/mount_other.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// +build !linux,!windows
1+
// +build !linux
22

33
/*
44
Copyright The containerd Authors.

vendor/github.com/containerd/continuity/fs/fstest/file_unix.go

Lines changed: 7 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)