Skip to content

Commit

Permalink
Merge pull request #168 from Code-Hex/fix/delegate-pr
Browse files Browse the repository at this point in the history
  • Loading branch information
Code-Hex authored Nov 2, 2024
2 parents 8aa9759 + 239cc8d commit 46a19ee
Show file tree
Hide file tree
Showing 8 changed files with 206 additions and 40 deletions.
9 changes: 9 additions & 0 deletions configuration.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ type VirtualMachineConfiguration struct {
cpuCount uint
memorySize uint64
*pointer

storageDeviceConfiguration []StorageDeviceConfiguration
}

// NewVirtualMachineConfiguration creates a new configuration.
Expand Down Expand Up @@ -172,6 +174,13 @@ func (v *VirtualMachineConfiguration) SetStorageDevicesVirtualMachineConfigurati
}
array := objc.ConvertToNSMutableArray(ptrs)
C.setStorageDevicesVZVirtualMachineConfiguration(objc.Ptr(v), objc.Ptr(array))
v.storageDeviceConfiguration = cs
}

// 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 {
return v.storageDeviceConfiguration
}

// SetDirectorySharingDevicesVirtualMachineConfiguration sets list of directory sharing devices. Empty by default.
Expand Down
78 changes: 55 additions & 23 deletions example/macOS/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,21 @@ func runVM(ctx context.Context) error {
}
}()

// it start listening to the NBD server, if any
nbdAttachment := retrieveNetworkBlockDeviceStorageDeviceAttachment(config.StorageDevices())
if nbdAttachment != nil {
go func() {
for {
select {
case err := <-nbdAttachment.DidEncounterError():
log.Printf("NBD client has been encountered error: %v\n", err)
case <-nbdAttachment.Connected():
log.Println("NBD client connected with the server")
}
}
}()
}

// cleanup is this function is useful when finished graphic application.
cleanup := func() {
for i := 1; vm.CanRequestStop(); i++ {
Expand All @@ -102,9 +117,7 @@ func runVM(ctx context.Context) error {
log.Println("finished cleanup")
}

runtime.LockOSThread()
vm.StartGraphicApplication(960, 600)
runtime.UnlockOSThread()

cleanup()

Expand Down Expand Up @@ -144,29 +157,30 @@ func computeMemorySize() uint64 {
}

func createBlockDeviceConfiguration(diskPath string) (*vz.VirtioBlockDeviceConfiguration, error) {
var attachment vz.StorageDeviceAttachment
var err error

if nbdURL == "" {
// create disk image with 64 GiB
if err := vz.CreateDiskImage(diskPath, 64*1024*1024*1024); err != nil {
if !os.IsExist(err) {
return nil, fmt.Errorf("failed to create disk image: %w", err)
}
// create disk image with 64 GiB
if err := vz.CreateDiskImage(diskPath, 64*1024*1024*1024); err != nil {
if !os.IsExist(err) {
return nil, fmt.Errorf("failed to create disk image: %w", err)
}
}

attachment, err = vz.NewDiskImageStorageDeviceAttachment(
diskPath,
false,
)
} else {
attachment, err = vz.NewNetworkBlockDeviceStorageDeviceAttachment(
nbdURL,
10*time.Second,
false,
vz.DiskSynchronizationModeFull,
)
attachment, err := vz.NewDiskImageStorageDeviceAttachment(
diskPath,
false,
)
if err != nil {
return nil, err
}
return vz.NewVirtioBlockDeviceConfiguration(attachment)
}

func createNetworkBlockDeviceConfiguration(nbdURL string) (*vz.VirtioBlockDeviceConfiguration, error) {
attachment, err := vz.NewNetworkBlockDeviceStorageDeviceAttachment(
nbdURL,
10*time.Second,
false,
vz.DiskSynchronizationModeFull,
)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -277,7 +291,15 @@ func setupVMConfiguration(platformConfig vz.PlatformConfiguration) (*vz.VirtualM
if err != nil {
return nil, fmt.Errorf("failed to create block device configuration: %w", err)
}
config.SetStorageDevicesVirtualMachineConfiguration([]vz.StorageDeviceConfiguration{blockDeviceConfig})
sdconfigs := []vz.StorageDeviceConfiguration{blockDeviceConfig}
if nbdURL != "" {
ndbConfig, err := createNetworkBlockDeviceConfiguration(nbdURL)
if err != nil {
return nil, fmt.Errorf("failed to create network block device configuration: %w", err)
}
sdconfigs = append(sdconfigs, ndbConfig)
}
config.SetStorageDevicesVirtualMachineConfiguration(sdconfigs)

networkDeviceConfig, err := createNetworkDeviceConfiguration()
if err != nil {
Expand Down Expand Up @@ -331,3 +353,13 @@ func setupVMConfiguration(platformConfig vz.PlatformConfiguration) (*vz.VirtualM

return config, nil
}

func retrieveNetworkBlockDeviceStorageDeviceAttachment(storages []vz.StorageDeviceConfiguration) *vz.NetworkBlockDeviceStorageDeviceAttachment {
for _, storage := range storages {
attachment := storage.Attachment()
if nbdAttachment, ok := attachment.(*vz.NetworkBlockDeviceStorageDeviceAttachment); ok {
return nbdAttachment
}
}
return nil
}
96 changes: 85 additions & 11 deletions storage.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,11 @@ package vz
import "C"
import (
"os"
"runtime/cgo"
"time"
"unsafe"

infinity "github.com/Code-Hex/go-infinity-channel"
"github.com/Code-Hex/vz/v3/internal/objc"
)

Expand Down Expand Up @@ -151,11 +154,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,6 +201,9 @@ func NewVirtioBlockDeviceConfiguration(attachment StorageDeviceAttachment) (*Vir
objc.Ptr(attachment),
),
),
baseStorageDeviceConfiguration: &baseStorageDeviceConfiguration{
attachment: attachment,
},
}
objc.SetFinalizer(config, func(self *VirtioBlockDeviceConfiguration) {
objc.Release(self)
Expand Down Expand Up @@ -250,10 +262,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 +277,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 +294,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 +312,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 +419,9 @@ type NetworkBlockDeviceStorageDeviceAttachment struct {
*pointer

*baseStorageDeviceAttachment

didEncounterError *infinity.Channel[error]
connected *infinity.Channel[struct{}]
}

var _ StorageDeviceAttachment = (*NetworkBlockDeviceStorageDeviceAttachment)(nil)
Expand All @@ -419,6 +430,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 +445,17 @@ func NewNetworkBlockDeviceStorageDeviceAttachment(url string, timeout time.Durat
return nil, err
}

didEncounterError := infinity.NewChannel[error]()
connected := infinity.NewChannel[struct{}]()

handle := cgo.NewHandle(func(err error) {
if err != nil {
didEncounterError.In() <- err
return
}
connected.In() <- struct{}{}
})

nserrPtr := newNSErrorAsNil()

urlChar := charWithGoString(url)
Expand All @@ -443,8 +468,11 @@ func NewNetworkBlockDeviceStorageDeviceAttachment(url string, timeout time.Durat
C.bool(forcedReadOnly),
C.int(syncMode),
&nserrPtr,
C.uintptr_t(handle),
),
),
didEncounterError: didEncounterError,
connected: connected,
}
if err := newNSError(nserrPtr); err != nil {
return nil, err
Expand All @@ -454,3 +482,49 @@ func NewNetworkBlockDeviceStorageDeviceAttachment(url string, timeout time.Durat
})
return attachment, nil
}

// Connected receive the signal via channel when the NBD client successfully connects or reconnects with the server.
//
// The NBD connection with the server takes place when the VM is first started, and reconnection attempts take place when the connection
// times out or when the NBD client has encountered a recoverable error, such as an I/O error from the server.
//
// Note that the Virtualization framework may call this method multiple times during a VM’s life cycle. Reconnections are transparent to the guest.
func (n *NetworkBlockDeviceStorageDeviceAttachment) Connected() <-chan struct{} {
return n.connected.Out()
}

// The DidEncounterError is triggered via the channel when the NBD client encounters an error that cannot be resolved on the client side.
// In this state, the client will continue attempting to reconnect, but recovery depends entirely on the server's availability.
// If the server resumes operation, the connection will recover automatically; however, until the server is restored, the client will continue to experience errors.
func (n *NetworkBlockDeviceStorageDeviceAttachment) DidEncounterError() <-chan error {
return n.didEncounterError.Out()
}

// attachmentDidEncounterErrorHandler function is called when the NBD client encounters a nonrecoverable error.
// After the attachment object calls this method, the NBD client is in a nonfunctional state.
//
//export attachmentDidEncounterErrorHandler
func attachmentDidEncounterErrorHandler(cgoHandleUintptr C.uintptr_t, errorPtr unsafe.Pointer) {
cgoHandle := cgo.Handle(cgoHandleUintptr)
handler := cgoHandle.Value().(func(error))

err := newNSError(errorPtr)

handler(err)
}

// attachmentWasConnectedHandler function is called when a connection to the server is first established as the VM starts,
// and during any reconnection attempts triggered by connection timeouts or recoverable errors encountered by the NBD client,
// such as server-side I/O errors.
//
// Note that the Virtualization framework may invoke this method multiple times throughout the VM’s lifecycle,
// ensuring reconnection processes remain seamless and transparent to the guest.
// For more details, see: https://developer.apple.com/documentation/virtualization/vznetworkblockdevicestoragedeviceattachmentdelegate/4168511-attachmentwasconnected?language=objc
//
//export attachmentWasConnectedHandler
func attachmentWasConnectedHandler(cgoHandleUintptr C.uintptr_t) {
cgoHandle := cgo.Handle(cgoHandleUintptr)
handler := cgoHandle.Value().(func(error))

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 attachmentDidEncounterErrorHandler(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
Loading

0 comments on commit 46a19ee

Please sign in to comment.