//go:build linux // +build linux package pmem import ( "context" "fmt" "os" "github.com/pkg/errors" "go.opencensus.io/trace" "golang.org/x/sys/unix" "github.com/Microsoft/hcsshim/internal/guest/storage" dm "github.com/Microsoft/hcsshim/internal/guest/storage/devicemapper" "github.com/Microsoft/hcsshim/internal/log" "github.com/Microsoft/hcsshim/internal/oc" "github.com/Microsoft/hcsshim/internal/protocol/guestresource" ) // Test dependencies var ( osMkdirAll = os.MkdirAll osRemoveAll = os.RemoveAll unixMount = unix.Mount mountInternal = mount createZeroSectorLinearTarget = dm.CreateZeroSectorLinearTarget createVerityTarget = dm.CreateVerityTarget removeDevice = dm.RemoveDevice ) const ( pMemFmt = "/dev/pmem%d" linearDeviceFmt = "dm-linear-pmem%d-%d-%d" verityDeviceFmt = "dm-verity-pmem%d-%s" ) // mount mounts source to target via unix.Mount func mount(ctx context.Context, source, target string) (err error) { if err := osMkdirAll(target, 0700); err != nil { return err } defer func() { if err != nil { if err := osRemoveAll(target); err != nil { log.G(ctx).WithError(err).Debugf("error cleaning up target: %s", target) } } }() flags := uintptr(unix.MS_RDONLY) if err := unixMount(source, target, "ext4", flags, "noload"); err != nil { return errors.Wrapf(err, "failed to mount %s onto %s", source, target) } return nil } // Mount mounts the pmem device at `/dev/pmem` to `target` in a basic scenario. // If either mappingInfo or verityInfo are non-nil, the device-mapper framework is used // to create linear and verity targets accordingly. If both are non-nil, the linear // target is created first and used as the data/hash device for the verity target. // // `target` will be created. On mount failure the created `target` will be // automatically cleaned up. // // Note: For now the platform only supports readonly pmem that is assumed to be // `ext4`. // // Note: both mappingInfo and verityInfo can be non-nil at the same time, in that case // linear target is created first and it becomes the data/hash device for verity target. func Mount( ctx context.Context, device uint32, target string, mappingInfo *guestresource.LCOWVPMemMappingInfo, verityInfo *guestresource.DeviceVerityInfo, ) (err error) { mCtx, span := oc.StartSpan(ctx, "pmem::Mount") defer span.End() defer func() { oc.SetSpanStatus(span, err) }() span.AddAttributes( trace.Int64Attribute("deviceNumber", int64(device)), trace.StringAttribute("target", target)) devicePath := fmt.Sprintf(pMemFmt, device) // dm-linear target has to be created first. When verity info is also present, the linear target becomes the data // device instead of the original VPMem. if mappingInfo != nil { dmLinearName := fmt.Sprintf(linearDeviceFmt, device, mappingInfo.DeviceOffsetInBytes, mappingInfo.DeviceSizeInBytes) if devicePath, err = createZeroSectorLinearTarget(mCtx, devicePath, dmLinearName, mappingInfo); err != nil { return err } defer func() { if err != nil { if err := removeDevice(dmLinearName); err != nil { log.G(mCtx).WithError(err).Debugf("failed to cleanup linear target: %s", dmLinearName) } } }() } if verityInfo != nil { dmVerityName := fmt.Sprintf(verityDeviceFmt, device, verityInfo.RootDigest) if devicePath, err = createVerityTarget(mCtx, devicePath, dmVerityName, verityInfo); err != nil { return err } defer func() { if err != nil { if err := removeDevice(dmVerityName); err != nil { log.G(mCtx).WithError(err).Debugf("failed to cleanup verity target: %s", dmVerityName) } } }() } return mountInternal(mCtx, devicePath, target) } // Unmount unmounts `target` and removes corresponding linear and verity targets when needed func Unmount( ctx context.Context, devNumber uint32, target string, mappingInfo *guestresource.LCOWVPMemMappingInfo, verityInfo *guestresource.DeviceVerityInfo, ) (err error) { _, span := oc.StartSpan(ctx, "pmem::Unmount") defer span.End() defer func() { oc.SetSpanStatus(span, err) }() span.AddAttributes( trace.Int64Attribute("device", int64(devNumber)), trace.StringAttribute("target", target)) if err := storage.UnmountPath(ctx, target, true); err != nil { return errors.Wrapf(err, "failed to unmount target: %s", target) } if verityInfo != nil { dmVerityName := fmt.Sprintf(verityDeviceFmt, devNumber, verityInfo.RootDigest) if err := dm.RemoveDevice(dmVerityName); err != nil { // The target is already unmounted at this point, ignore potential errors log.G(ctx).WithError(err).Debugf("failed to remove dm verity target: %s", dmVerityName) } } if mappingInfo != nil { dmLinearName := fmt.Sprintf(linearDeviceFmt, devNumber, mappingInfo.DeviceOffsetInBytes, mappingInfo.DeviceSizeInBytes) if err := dm.RemoveDevice(dmLinearName); err != nil { // The target is already unmounted at this point, ignore potential errors log.G(ctx).WithError(err).Debugf("failed to remove dm linear target: %s", dmLinearName) } } return nil }