From ee941e5c56b520396b0619bf08ae5abaaccc6eff Mon Sep 17 00:00:00 2001 From: Julio Montes Date: Fri, 28 Feb 2020 21:01:57 +0000 Subject: [PATCH] virtcontainers: Implement function to get the pmem DeviceInfo Implement function to get the pmem `DeviceInfo` from a volume. `PmemDeviceInfo` return a new `DeviceInfo` object if a volume has a loop device as backend and the backing file for such loop device contains the PFN signature, needed to enable DAX in the guest. Signed-off-by: Julio Montes --- virtcontainers/device/config/config.go | 8 ++ virtcontainers/device/config/pmem.go | 116 ++++++++++++++++++++++ virtcontainers/device/config/pmem_test.go | 49 +++++++++ 3 files changed, 173 insertions(+) create mode 100644 virtcontainers/device/config/pmem.go create mode 100644 virtcontainers/device/config/pmem_test.go diff --git a/virtcontainers/device/config/config.go b/virtcontainers/device/config/config.go index c054f0df55..18fcfedcec 100644 --- a/virtcontainers/device/config/config.go +++ b/virtcontainers/device/config/config.go @@ -113,6 +113,10 @@ type DeviceInfo struct { Major int64 Minor int64 + // Pmem enabled persistent memory. Use HostPath as backing file + // for a nvdimm device in the guest. + Pmem bool + // FileMode permission bits for the device. FileMode os.FileMode @@ -169,6 +173,10 @@ type BlockDrive struct { // ReadOnly sets the device file readonly ReadOnly bool + + // Pmem enables persistent memory. Use File as backing file + // for a nvdimm device in the guest + Pmem bool } // VFIODeviceType indicates VFIO device type diff --git a/virtcontainers/device/config/pmem.go b/virtcontainers/device/config/pmem.go new file mode 100644 index 0000000000..9cfe3b58f4 --- /dev/null +++ b/virtcontainers/device/config/pmem.go @@ -0,0 +1,116 @@ +// Copyright (c) 2020 Intel Corporation +// +// SPDX-License-Identifier: Apache-2.0 +// + +package config + +import ( + "fmt" + "os" + "syscall" + + "github.com/kata-containers/runtime/virtcontainers/utils" + "github.com/sirupsen/logrus" + "golang.org/x/sys/unix" +) + +const ( + // This signature is defined in the linux NVDIMM driver. + // devices or backing files with this signature can be used + // as pmem (persistent memory) devices in the guest. + pfnSignature = "NVDIMM_PFN_INFO" + + // offset in the backing file where the signature should be + pfnSignatureOffset = int64(4 * 1024) +) + +var ( + pmemLog = logrus.WithField("source", "virtcontainers/device/config") +) + +// PmemDeviceInfo returns a DeviceInfo if a loop device +// is mounted on source, and the backing file of the loop device +// has the PFN signature. +func PmemDeviceInfo(source, destination string) (*DeviceInfo, error) { + stat := syscall.Stat_t{} + err := syscall.Stat(source, &stat) + if err != nil { + return nil, err + } + + // device object is still incomplete, + // but it can be used to fetch the backing file + device := &DeviceInfo{ + ContainerPath: destination, + DevType: "b", + Major: int64(unix.Major(stat.Dev)), + Minor: int64(unix.Minor(stat.Dev)), + Pmem: true, + DriverOptions: make(map[string]string), + } + + pmemLog.WithFields( + logrus.Fields{ + "major": device.Major, + "minor": device.Minor, + }).Debug("looking for backing file") + + device.HostPath, err = getBackingFile(*device) + if err != nil { + return nil, err + } + + pmemLog.WithField("backing-file", device.HostPath). + Debug("backing file found: looking for PFN signature") + + if !hasPFNSignature(device.HostPath) { + return nil, fmt.Errorf("backing file %v has not PFN signature", device.HostPath) + } + + _, fstype, err := utils.GetDevicePathAndFsType(source) + if err != nil { + pmemLog.WithError(err).WithField("mount-point", source).Warn("failed to get fstype: using ext4") + fstype = "ext4" + } + + pmemLog.WithField("fstype", fstype).Debug("filesystem for mount point") + device.DriverOptions["fstype"] = fstype + + return device, nil +} + +// returns true if the file/device path has the PFN signature +// required to use it as PMEM device and enable DAX. +// See [1] to know more about the PFN signature. +// +// [1] - https://github.com/kata-containers/osbuilder/blob/master/image-builder/nsdax.gpl.c +func hasPFNSignature(path string) bool { + f, err := os.Open(path) + if err != nil { + pmemLog.WithError(err).Error("Could not get PFN signature") + return false + } + defer f.Close() + + signatureLen := len(pfnSignature) + signature := make([]byte, signatureLen) + + l, err := f.ReadAt(signature, pfnSignatureOffset) + if err != nil { + pmemLog.WithError(err).Debug("Could not read pfn signature") + return false + } + + pmemLog.WithFields(logrus.Fields{ + "path": path, + "signature": string(signature), + }).Debug("got signature") + + if l != signatureLen { + pmemLog.WithField("read-bytes", l).Debug("Incomplete signature") + return false + } + + return pfnSignature == string(signature) +} diff --git a/virtcontainers/device/config/pmem_test.go b/virtcontainers/device/config/pmem_test.go new file mode 100644 index 0000000000..319adc2536 --- /dev/null +++ b/virtcontainers/device/config/pmem_test.go @@ -0,0 +1,49 @@ +// Copyright (c) 2020 Intel Corporation +// +// SPDX-License-Identifier: Apache-2.0 +// + +package config + +import ( + "io/ioutil" + "os" + "path/filepath" + "testing" + + "github.com/stretchr/testify/assert" +) + +func createPFNFile(assert *assert.Assertions, dir string) string { + pfnPath := filepath.Join(dir, "pfn") + file, err := os.Create(pfnPath) + assert.NoError(err) + defer file.Close() + + l, err := file.WriteAt([]byte(pfnSignature), pfnSignatureOffset) + assert.NoError(err) + assert.Equal(len(pfnSignature), l) + + return pfnPath +} + +func TestHasPFNSignature(t *testing.T) { + assert := assert.New(t) + + b := hasPFNSignature("/abc/xyz/123/sw") + assert.False(b) + + f, err := ioutil.TempFile("", "pfn") + assert.NoError(err) + f.Close() + defer os.Remove(f.Name()) + + b = hasPFNSignature(f.Name()) + assert.False(b) + + pfnFile := createPFNFile(assert, os.TempDir()) + defer os.Remove(pfnFile) + + b = hasPFNSignature(pfnFile) + assert.True(b) +}