Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add support for VZNetworkBlockDeviceStorageDeviceAttachmentDelegate #167

Closed
wants to merge 2 commits into from
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
14 changes: 14 additions & 0 deletions configuration.go
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,20 @@ func (v *VirtualMachineConfiguration) SetStorageDevicesVirtualMachineConfigurati
C.setStorageDevicesVZVirtualMachineConfiguration(objc.Ptr(v), objc.Ptr(array))
}

// StorageDevices return the list of storage device configuration configured in this virtual machine configuration.
// Return an empty array if no storage device configuration is set.
func (v *VirtualMachineConfiguration) StorageDevices() []StorageDeviceConfiguration {
nsArray := objc.NewNSArray(
C.storageDevicesVZVirtualMachineConfiguration(objc.Ptr(v)),
)
ptrs := nsArray.ToPointerSlice()
storageDevices := make([]StorageDeviceConfiguration, len(ptrs))
for i, ptr := range ptrs {
storageDevices[i] = newVirtioBlockDeviceConfiguration(ptr)
}
return storageDevices
}

// SetDirectorySharingDevicesVirtualMachineConfiguration sets list of directory sharing devices. Empty by default.
//
// This is only supported on macOS 12 and newer. Older versions do nothing.
Expand Down
24 changes: 24 additions & 0 deletions example/macOS/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,9 @@ func runVM(ctx context.Context) error {
}
}()

// it start listening to the NBD server, if any
listenNetworkBlockDevice(config)

// cleanup is this function is useful when finished graphic application.
cleanup := func() {
for i := 1; vm.CanRequestStop(); i++ {
Expand Down Expand Up @@ -331,3 +334,24 @@ func setupVMConfiguration(platformConfig vz.PlatformConfiguration) (*vz.VirtualM

return config, nil
}

func listenNetworkBlockDevice(vm *vz.VirtualMachineConfiguration) error {
storages := vm.StorageDevices()
for _, storage := range storages {
attachment := storage.Attachment()
if nbdAttachment, isNbdAttachment := attachment.(*vz.NetworkBlockDeviceStorageDeviceAttachment); isNbdAttachment {
nbdAttachmentStatusCh := make(chan vz.NetworkBlockDeviceStorageDeviceAttachmentStatus)
nbdAttachment.Listen(nbdAttachmentStatusCh)
go func() {
for status := range nbdAttachmentStatusCh {
if status.IsConnected() {
log.Println("Successfully connected to NBD server")
} else {
log.Printf("Disconnected from NBD server. Error %v\n", status.Error().Error())
}
}
}()
}
}
return nil
}
101 changes: 90 additions & 11 deletions storage.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,9 @@ package vz
import "C"
import (
"os"
"runtime/cgo"
"time"
"unsafe"

"github.com/Code-Hex/vz/v3/internal/objc"
)
Expand Down Expand Up @@ -151,11 +153,17 @@ type StorageDeviceConfiguration interface {
objc.NSObject

storageDeviceConfiguration()
Attachment() StorageDeviceAttachment
}

type baseStorageDeviceConfiguration struct{}
type baseStorageDeviceConfiguration struct {
attachment StorageDeviceAttachment
}

func (*baseStorageDeviceConfiguration) storageDeviceConfiguration() {}
func (b *baseStorageDeviceConfiguration) Attachment() StorageDeviceAttachment {
return b.attachment
}

var _ StorageDeviceConfiguration = (*VirtioBlockDeviceConfiguration)(nil)

Expand Down Expand Up @@ -192,13 +200,22 @@ func NewVirtioBlockDeviceConfiguration(attachment StorageDeviceAttachment) (*Vir
objc.Ptr(attachment),
),
),
baseStorageDeviceConfiguration: &baseStorageDeviceConfiguration{
attachment: attachment,
},
}
objc.SetFinalizer(config, func(self *VirtioBlockDeviceConfiguration) {
objc.Release(self)
})
return config, nil
}

func newVirtioBlockDeviceConfiguration(ptr unsafe.Pointer) *VirtioBlockDeviceConfiguration {
return &VirtioBlockDeviceConfiguration{
pointer: objc.NewPointer(ptr),
}
}

// BlockDeviceIdentifier returns the device identifier is a string identifying the Virtio block device.
// Empty string by default.
//
Expand Down Expand Up @@ -250,10 +267,6 @@ type USBMassStorageDeviceConfiguration struct {
*pointer

*baseStorageDeviceConfiguration

// marking as currently reachable.
// This ensures that the object is not freed, and its finalizer is not run
attachment StorageDeviceAttachment
}

// NewUSBMassStorageDeviceConfiguration initialize a USBMassStorageDeviceConfiguration
Expand All @@ -269,7 +282,9 @@ func NewUSBMassStorageDeviceConfiguration(attachment StorageDeviceAttachment) (*
pointer: objc.NewPointer(
C.newVZUSBMassStorageDeviceConfiguration(objc.Ptr(attachment)),
),
attachment: attachment,
baseStorageDeviceConfiguration: &baseStorageDeviceConfiguration{
attachment: attachment,
},
}
objc.SetFinalizer(usbMass, func(self *USBMassStorageDeviceConfiguration) {
objc.Release(self)
Expand All @@ -284,10 +299,6 @@ type NVMExpressControllerDeviceConfiguration struct {
*pointer

*baseStorageDeviceConfiguration

// marking as currently reachable.
// This ensures that the object is not freed, and its finalizer is not run
attachment StorageDeviceAttachment
}

// NewNVMExpressControllerDeviceConfiguration creates a new NVMExpressControllerDeviceConfiguration with
Expand All @@ -306,7 +317,9 @@ func NewNVMExpressControllerDeviceConfiguration(attachment StorageDeviceAttachme
pointer: objc.NewPointer(
C.newVZNVMExpressControllerDeviceConfiguration(objc.Ptr(attachment)),
),
attachment: attachment,
baseStorageDeviceConfiguration: &baseStorageDeviceConfiguration{
attachment: attachment,
},
}
objc.SetFinalizer(nvmExpress, func(self *NVMExpressControllerDeviceConfiguration) {
objc.Release(self)
Expand Down Expand Up @@ -411,6 +424,18 @@ type NetworkBlockDeviceStorageDeviceAttachment struct {
*pointer

*baseStorageDeviceAttachment

attachmentStatusCh chan networkBlockDeviceStorageDeviceAttachmentStatusData
}

type NetworkBlockDeviceStorageDeviceAttachmentStatus interface {
IsConnected() bool
Error() error
}

type networkBlockDeviceStorageDeviceAttachmentStatusData struct {
connected bool
err error
}

var _ StorageDeviceAttachment = (*NetworkBlockDeviceStorageDeviceAttachment)(nil)
Expand All @@ -419,6 +444,9 @@ var _ StorageDeviceAttachment = (*NetworkBlockDeviceStorageDeviceAttachment)(nil
// Uniform Resource Indicator (URI) represented as a URL, timeout value, and read-only and synchronization modes
// that you provide.
//
// It also set up a channel that will be used by the VZNetworkBlockDeviceStorageDeviceAttachmentDelegate to
// return changes to the NetworkBlockDeviceAttachment
//
// - url is the NBD server URI. The format specified by https://github.com/NetworkBlockDevice/nbd/blob/master/doc/uri.md
// - timeout is the duration for the connection between the client and server. When the timeout expires, an attempt to reconnect with the server takes place.
// - forcedReadOnly if true forces the disk attachment to be read-only, regardless of whether or not the NBD server supports write requests.
Expand All @@ -431,6 +459,16 @@ func NewNetworkBlockDeviceStorageDeviceAttachment(url string, timeout time.Durat
return nil, err
}

ch := make(chan networkBlockDeviceStorageDeviceAttachmentStatusData)

handle := cgo.NewHandle(func(err error) {
connected := true
if err != nil {
connected = false
}
ch <- networkBlockDeviceStorageDeviceAttachmentStatusData{connected, err}
})

nserrPtr := newNSErrorAsNil()

urlChar := charWithGoString(url)
Expand All @@ -443,8 +481,10 @@ func NewNetworkBlockDeviceStorageDeviceAttachment(url string, timeout time.Durat
C.bool(forcedReadOnly),
C.int(syncMode),
&nserrPtr,
C.uintptr_t(handle),
),
),
attachmentStatusCh: ch,
}
if err := newNSError(nserrPtr); err != nil {
return nil, err
Expand All @@ -454,3 +494,42 @@ func NewNetworkBlockDeviceStorageDeviceAttachment(url string, timeout time.Durat
})
return attachment, nil
}

// Returns the connected value. It is true if the NBD client successfully connected to the server. False if there was an error that prevented the connection
func (n *networkBlockDeviceStorageDeviceAttachmentStatusData) IsConnected() bool {
return n.connected
}

// Returns the error associated to the failed connection of the NBD client to the server
func (n *networkBlockDeviceStorageDeviceAttachmentStatusData) Error() error {
return n.err
}

// It allows the caller to subscribe to the attachmentStatus channel to listen to changes of the network block device attachment
// This way it can be informed if the NBD client successfully connects/reconnects to the server or it encounter an error
func (n *NetworkBlockDeviceStorageDeviceAttachment) Listen(statusCh chan NetworkBlockDeviceStorageDeviceAttachmentStatus) {
go func() {
for {
status := <-n.attachmentStatusCh
statusCh <- &status
}
}()
}

//export attachmentHandler
func attachmentHandler(cgoHandleUintptr C.uintptr_t, errorPtr unsafe.Pointer) {
cgoHandle := cgo.Handle(cgoHandleUintptr)
handler := cgoHandle.Value().(func(error))

err := newNSError(errorPtr)

go handler(err)
}

//export attachmentWasConnectedHandler
func attachmentWasConnectedHandler(cgoHandleUintptr C.uintptr_t) {
cgoHandle := cgo.Handle(cgoHandleUintptr)
handler := cgoHandle.Value().(func(error))

go handler(nil)
}
1 change: 1 addition & 0 deletions virtualization_11.h
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ void setSocketDevicesVZVirtualMachineConfiguration(void *config,
void *socketDevicesVZVirtualMachineConfiguration(void *config);
void setStorageDevicesVZVirtualMachineConfiguration(void *config,
void *storageDevices);
void *storageDevicesVZVirtualMachineConfiguration(void *config);

/* Configurations */
void *newVZFileHandleSerialPortAttachment(int readFileDescriptor, int writeFileDescriptor);
Expand Down
12 changes: 12 additions & 0 deletions virtualization_11.m
Original file line number Diff line number Diff line change
Expand Up @@ -327,6 +327,18 @@ void setStorageDevicesVZVirtualMachineConfiguration(void *config,
RAISE_UNSUPPORTED_MACOS_EXCEPTION();
}

/*!
@abstract Return the list of storage devices configurations for this VZVirtualMachineConfiguration. Return an empty array if no storage device configuration is set.
*/
void *storageDevicesVZVirtualMachineConfiguration(void *config)
{
if (@available(macOS 11, *)) {
return [(VZVirtualMachineConfiguration *)config storageDevices]; // NSArray<VZStorageDeviceConfiguration *>
}

RAISE_UNSUPPORTED_MACOS_EXCEPTION();
}

/*!
@abstract Intialize the VZFileHandleSerialPortAttachment from file descriptors.
@param readFileDescriptor File descriptor for reading from the file.
Expand Down
12 changes: 11 additions & 1 deletion virtualization_14.h
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,17 @@
#import "virtualization_helper.h"
#import <Virtualization/Virtualization.h>

/* exported from cgo */
void attachmentHandler(uintptr_t cgoHandle, void *err);
void attachmentWasConnectedHandler(uintptr_t cgoHandle);

/* macOS 14 API */
void *newVZNVMExpressControllerDeviceConfiguration(void *attachment);
void *newVZDiskBlockDeviceStorageDeviceAttachment(int fileDescriptor, bool readOnly, int syncMode, void **error);
void *newVZNetworkBlockDeviceStorageDeviceAttachment(const char *url, double timeout, bool forcedReadOnly, int syncMode, void **error);
void *newVZNetworkBlockDeviceStorageDeviceAttachment(const char *url, double timeout, bool forcedReadOnly, int syncMode, void **error, uintptr_t cgoHandle);

@interface VZNetworkBlockDeviceStorageDeviceAttachmentDelegateImpl : NSObject <VZNetworkBlockDeviceStorageDeviceAttachmentDelegate>
- (instancetype)initWithHandle:(uintptr_t)cgoHandle;
- (void)attachment:(VZNetworkBlockDeviceStorageDeviceAttachment *)attachment didEncounterError:(NSError *)error;
- (void)attachmentWasConnected:(VZNetworkBlockDeviceStorageDeviceAttachment *)attachment;
@end
35 changes: 32 additions & 3 deletions virtualization_14.m
Original file line number Diff line number Diff line change
Expand Up @@ -68,19 +68,48 @@
Setting forcedReadOnly to YES forces the NBD client to show up as read-only to the guest
regardless of whether or not the NBD server advertises itself as read-only.
*/
void *newVZNetworkBlockDeviceStorageDeviceAttachment(const char *uri, double timeout, bool forcedReadOnly, int syncMode, void **error)
void *newVZNetworkBlockDeviceStorageDeviceAttachment(const char *uri, double timeout, bool forcedReadOnly, int syncMode, void **error, uintptr_t cgoHandle)
{
#ifdef INCLUDE_TARGET_OSX_14
if (@available(macOS 14, *)) {
NSURL *url = [NSURL URLWithString:[NSString stringWithUTF8String:uri]];

return [[VZNetworkBlockDeviceStorageDeviceAttachment alloc]
VZNetworkBlockDeviceStorageDeviceAttachment *attachment = [[VZNetworkBlockDeviceStorageDeviceAttachment alloc]
initWithURL:url
timeout:(NSTimeInterval)timeout
forcedReadOnly:(BOOL)forcedReadOnly
synchronizationMode:(VZDiskSynchronizationMode)syncMode
error:(NSError *_Nullable *_Nullable)error];

if (attachment) {
[attachment setDelegate:[[[VZNetworkBlockDeviceStorageDeviceAttachmentDelegateImpl alloc] initWithHandle:cgoHandle] autorelease]];
}

return attachment;
}
#endif
RAISE_UNSUPPORTED_MACOS_EXCEPTION();
}
}

@implementation VZNetworkBlockDeviceStorageDeviceAttachmentDelegateImpl {
uintptr_t _cgoHandle;
}

- (instancetype)initWithHandle:(uintptr_t)cgoHandle
{
self = [super init];
_cgoHandle = cgoHandle;
return self;
}

- (void)attachment:(VZNetworkBlockDeviceStorageDeviceAttachment *)attachment didEncounterError:(NSError *)error
{
attachmentHandler(_cgoHandle, error);
}

- (void)attachmentWasConnected:(VZNetworkBlockDeviceStorageDeviceAttachment *)attachment
{
attachmentWasConnectedHandler(_cgoHandle);
}
@end

Loading