Files
go-winio/pkg/fs/resolve_test.go
Hamza El-Saawy bc421d9108 Bug: Close hvsock handle on listen error; fix tests (#310)
* Bug: Close hvsock handle on listen error; fix tests

Close the socket created in
`github.com/Microsoft/go-winio/pkg/ListenHvsock` if either the `Bind` or
`Listen` calls fail.

Go changed `filepath.VolumeName` code, resulting in different behavior in
`github.com/Microsoft/go-winio/pkg/fs.GetFileSystemType`.
Update test accordingly.
Also add more debug logs to `pkg\fs\resolve_test.go`.

Also, move add skip for fuzzing on WS2019 or older to `FuzzHvSockRxTx`
code directly, instead of in ci.yml.

See: https://go-review.googlesource.com/c/go/+/540277

Signed-off-by: Hamza El-Saawy <hamzaelsaawy@microsoft.com>

* PR: unskip TestResolvePath

Signed-off-by: Hamza El-Saawy <hamzaelsaawy@microsoft.com>

---------

Signed-off-by: Hamza El-Saawy <hamzaelsaawy@microsoft.com>
2023-12-18 11:11:12 -05:00

247 lines
7.4 KiB
Go

//go:build windows
package fs
import (
"os"
"path/filepath"
"strings"
"syscall"
"testing"
"golang.org/x/sys/windows"
"github.com/Microsoft/go-winio/internal/computestorage"
"github.com/Microsoft/go-winio/internal/fs"
"github.com/Microsoft/go-winio/vhd"
)
func getWindowsBuildNumber() uint32 {
// RtlGetVersion ignores manifest requirements
vex := windows.RtlGetVersion()
return vex.BuildNumber
}
func makeSymlink(t *testing.T, oldName string, newName string) {
t.Helper()
t.Logf("make symlink: %s -> %s", oldName, newName)
if _, err := os.Lstat(oldName); err != nil {
t.Fatalf("could not open file %q: %v", oldName, err)
}
if err := os.Symlink(oldName, newName); err != nil {
t.Fatalf("creating symlink: %s", err)
}
if _, err := os.Lstat(newName); err != nil {
t.Fatalf("could not open file %q: %v", newName, err)
}
}
func getVolumeGUIDPath(t *testing.T, path string) string {
t.Helper()
h, err := openMetadata(path)
if err != nil {
t.Fatal(err)
}
defer windows.CloseHandle(h) //nolint:errcheck
final, err := fs.GetFinalPathNameByHandle(h, fs.FILE_NAME_OPENED|fs.VOLUME_NAME_GUID)
if err != nil {
t.Fatal(err)
}
return final
}
func openDisk(path string) (windows.Handle, error) {
h, err := fs.CreateFile(
path,
windows.GENERIC_READ|windows.GENERIC_WRITE,
fs.FILE_SHARE_READ|fs.FILE_SHARE_WRITE,
nil, // security attributes
fs.OPEN_EXISTING,
windows.FILE_ATTRIBUTE_NORMAL|fs.FILE_FLAG_NO_BUFFERING,
fs.NullHandle)
if err != nil {
return 0, &os.PathError{
Op: "CreateFile",
Path: path,
Err: err,
}
}
return h, nil
}
func formatVHD(vhdHandle windows.Handle) error {
h := vhdHandle
// Pre-19H1 HcsFormatWritableLayerVhd expects a disk handle.
// On newer builds it expects a VHD handle instead.
// Open a handle to the VHD's disk object if needed.
// Windows Server 1903, aka 19H1
if getWindowsBuildNumber() < 18362 {
diskPath, err := vhd.GetVirtualDiskPhysicalPath(syscall.Handle(h))
if err != nil {
return err
}
diskHandle, err := openDisk(diskPath)
if err != nil {
return err
}
defer windows.CloseHandle(diskHandle) //nolint:errcheck // cleanup code
h = diskHandle
}
// Formatting a disk directly in Windows is a pain, so we use FormatWritableLayerVhd to do it.
// It has a side effect of creating a sandbox directory on the formatted volume, but it's safe
// to just ignore that for our purposes here.
return computestorage.FormatWritableLayerVHD(h)
}
// Creates a VHD with a NTFS volume. Returns the volume path.
func setupVHDVolume(t *testing.T, vhdPath string) string {
t.Helper()
vhdHandle, err := vhd.CreateVirtualDisk(vhdPath,
vhd.VirtualDiskAccessNone, vhd.CreateVirtualDiskFlagNone,
&vhd.CreateVirtualDiskParameters{
Version: 2,
Version2: vhd.CreateVersion2{
MaximumSize: 5 * 1024 * 1024 * 1024, // 5GB, thin provisioned
BlockSizeInBytes: 1 * 1024 * 1024, // 1MB
},
})
if err != nil {
t.Fatal(err)
}
t.Cleanup(func() {
_ = windows.CloseHandle(windows.Handle(vhdHandle))
})
if err := vhd.AttachVirtualDisk(vhdHandle, vhd.AttachVirtualDiskFlagNone, &vhd.AttachVirtualDiskParameters{Version: 1}); err != nil {
t.Fatal(err)
}
t.Cleanup(func() {
if err := vhd.DetachVirtualDisk(vhdHandle); err != nil {
t.Fatal(err)
}
})
if err := formatVHD(windows.Handle(vhdHandle)); err != nil {
t.Fatalf("failed to format VHD: %s", err)
}
// Get the path for the volume that was just created on the disk.
volumePath, err := computestorage.GetLayerVHDMountPath(windows.Handle(vhdHandle))
if err != nil {
t.Fatal(err)
}
return volumePath
}
func writeFile(t *testing.T, path string, content []byte) {
t.Helper()
if err := os.WriteFile(path, content, 0644); err != nil { //nolint:gosec // test file, can have permissive mode
t.Fatal(err)
}
}
func mountVolume(t *testing.T, volumePath string, mountPoint string) {
t.Helper()
// Create the mount point directory.
if err := os.Mkdir(mountPoint, 0644); err != nil {
t.Fatal(err)
}
t.Cleanup(func() {
if err := os.Remove(mountPoint); err != nil {
t.Fatal(err)
}
})
// Volume path must end in a slash.
if !strings.HasSuffix(volumePath, `\`) {
volumePath += `\`
}
volumePathU16, err := windows.UTF16PtrFromString(volumePath)
if err != nil {
t.Fatal(err)
}
// Mount point must end in a slash.
if !strings.HasSuffix(mountPoint, `\`) {
mountPoint += `\`
}
mountPointU16, err := windows.UTF16PtrFromString(mountPoint)
if err != nil {
t.Fatal(err)
}
if err := windows.SetVolumeMountPoint(mountPointU16, volumePathU16); err != nil {
t.Fatalf("failed to mount %s onto %s: %s", volumePath, mountPoint, err)
}
t.Cleanup(func() {
if err := windows.DeleteVolumeMountPoint(mountPointU16); err != nil {
t.Fatalf("failed to delete mount on %s: %s", mountPoint, err)
}
})
}
func TestResolvePath(t *testing.T) {
if !windows.GetCurrentProcessToken().IsElevated() {
t.Skip("requires elevated privileges")
}
// Set up some data to be used by the test cases.
volumePathC := getVolumeGUIDPath(t, `C:\`)
dir := t.TempDir()
makeSymlink(t, `C:\windows`, filepath.Join(dir, "lnk1"))
makeSymlink(t, `\\localhost\c$\windows`, filepath.Join(dir, "lnk2"))
volumePathVHD1 := setupVHDVolume(t, filepath.Join(dir, "foo.vhdx"))
writeFile(t, filepath.Join(volumePathVHD1, "data.txt"), []byte("test content 1"))
makeSymlink(t, filepath.Join(volumePathVHD1, "data.txt"), filepath.Join(dir, "lnk3"))
volumePathVHD2 := setupVHDVolume(t, filepath.Join(dir, "bar.vhdx"))
writeFile(t, filepath.Join(volumePathVHD2, "data.txt"), []byte("test content 2"))
makeSymlink(t, filepath.Join(volumePathVHD2, "data.txt"), filepath.Join(dir, "lnk4"))
mountVolume(t, volumePathVHD2, filepath.Join(dir, "mnt"))
for _, tc := range []struct {
input string
expected string
description string
}{
{`C:\windows`, volumePathC + `Windows`, "local path"},
{filepath.Join(dir, "lnk1"), volumePathC + `Windows`, "symlink to local path"},
{`\\localhost\c$\windows`, `\\localhost\c$\Windows`, "UNC path"},
{filepath.Join(dir, "lnk2"), `\\localhost\c$\Windows`, "symlink to UNC path"},
{filepath.Join(volumePathVHD1, "data.txt"), filepath.Join(volumePathVHD1, "data.txt"), "volume with no mount point"},
{filepath.Join(dir, "lnk3"), filepath.Join(volumePathVHD1, "data.txt"), "symlink to volume with no mount point"},
{filepath.Join(dir, "mnt", "data.txt"), filepath.Join(volumePathVHD2, "data.txt"), "volume with mount point"},
{filepath.Join(dir, "lnk4"), filepath.Join(volumePathVHD2, "data.txt"), "symlink to volume with mount point"},
} {
t.Run(tc.description, func(t *testing.T) {
t.Logf("resolving: %s -> %s", tc.input, tc.expected)
actual, err := ResolvePath(tc.input)
if err != nil {
t.Fatalf("ResolvePath should return no error, but: %v", err)
}
if actual != tc.expected {
t.Fatalf("expected %v but got %v", tc.expected, actual)
}
// Make sure EvalSymlinks works with the resolved path, as an extra safety measure.
t.Logf("filepath.EvalSymlinks(%s)", actual)
p, err := filepath.EvalSymlinks(actual)
if err != nil {
t.Fatalf("EvalSymlinks should return no error, but %v", err)
}
// As an extra-extra safety, check that resolvePath(x) == EvalSymlinks(resolvePath(x)).
// EvalSymlinks normalizes UNC path casing, but resolvePath may not, so compare with
// case-insensitivity here.
if !strings.EqualFold(actual, p) {
t.Fatalf("EvalSymlinks should resolve to the same path. Expected %v but got %v", actual, p)
}
})
}
}