diff --git a/example/gui-linux/main.go b/example/gui-linux/main.go index c850822..5db3f99 100644 --- a/example/gui-linux/main.go +++ b/example/gui-linux/main.go @@ -114,7 +114,7 @@ func createMainDiskImage(diskPath string) error { } func createBlockDeviceConfiguration(diskPath string) (*vz.VirtioBlockDeviceConfiguration, error) { - attachment, err := vz.NewDiskImageStorageDeviceAttachment(diskPath, false) + attachment, err := vz.NewDiskImageStorageDeviceAttachmentWithCacheAndSync(diskPath, false, vz.DiskImageCachingModeAutomatic, vz.DiskImageSynchronizationModeFsync) if err != nil { return nil, fmt.Errorf("failed to create a new disk image storage device attachment: %w", err) } diff --git a/osversion_test.go b/osversion_test.go index 7fe5ee3..c4d01b8 100644 --- a/osversion_test.go +++ b/osversion_test.go @@ -155,6 +155,10 @@ func TestAvailableVersion(t *testing.T) { "(*VirtualMachine).StartGraphicApplication": func() error { return (*VirtualMachine)(nil).StartGraphicApplication(0, 0) }, + "NewDiskImageStorageDeviceAttachmentWithCacheAndSync": func() error { + _, err := NewDiskImageStorageDeviceAttachmentWithCacheAndSync("test", false, DiskImageCachingModeAutomatic, DiskImageSynchronizationModeFsync) + return err + }, } for name, fn := range cases { t.Run(name, func(t *testing.T) { diff --git a/storage.go b/storage.go index 14ce4e2..a485af9 100644 --- a/storage.go +++ b/storage.go @@ -4,6 +4,7 @@ package vz #cgo darwin CFLAGS: -mmacosx-version-min=11 -x objective-c -fno-objc-arc #cgo darwin LDFLAGS: -lobjc -framework Foundation -framework Virtualization # include "virtualization_11.h" +# include "virtualization_12.h" # include "virtualization_12_3.h" # include "virtualization_13.h" */ @@ -41,6 +42,28 @@ type DiskImageStorageDeviceAttachment struct { *baseStorageDeviceAttachment } +// DiskImageCachingMode describes the disk image caching mode. +// +// see: https://developer.apple.com/documentation/virtualization/vzdiskimagecachingmode?language=objc +type DiskImageCachingMode int + +const ( + DiskImageCachingModeAutomatic DiskImageCachingMode = iota + DiskImageCachingModeUncached + DiskImageCachingModeCached +) + +// DiskImageSynchronizationMode describes the disk image synchronization mode. +// +// see: https://developer.apple.com/documentation/virtualization/vzdiskimagesynchronizationmode?language=objc +type DiskImageSynchronizationMode int + +const ( + DiskImageSynchronizationModeFull DiskImageSynchronizationMode = 1 + iota + DiskImageSynchronizationModeFsync + DiskImageSynchronizationModeNone +) + // NewDiskImageStorageDeviceAttachment initialize the attachment from a local file path. // Returns error is not nil, assigned with the error if the initialization failed. // @@ -79,6 +102,48 @@ func NewDiskImageStorageDeviceAttachment(diskPath string, readOnly bool) (*DiskI return attachment, nil } +// NewDiskImageStorageDeviceAttachmentWithCacheAndSync initialize the attachment from a local file path. +// Returns error is not nil, assigned with the error if the initialization failed. +// +// - diskPath is local file URL to the disk image in RAW format. +// - readOnly if YES, the device attachment is read-only, otherwise the device can write data to the disk image. +// - cachingMode is one of the available DiskImageCachingMode options. +// - syncMode is to define how the disk image synchronizes with the underlying storage when the guest operating system flushes data, described by one of the available DiskImageSynchronizationMode modes. +// +// This is only supported on macOS 12 and newer, error will +// be returned on older versions. +func NewDiskImageStorageDeviceAttachmentWithCacheAndSync(diskPath string, readOnly bool, cachingMode DiskImageCachingMode, syncMode DiskImageSynchronizationMode) (*DiskImageStorageDeviceAttachment, error) { + if err := macOSAvailable(12); err != nil { + return nil, err + } + if _, err := os.Stat(diskPath); err != nil { + return nil, err + } + + nserrPtr := newNSErrorAsNil() + + diskPathChar := charWithGoString(diskPath) + defer diskPathChar.Free() + attachment := &DiskImageStorageDeviceAttachment{ + pointer: objc.NewPointer( + C.newVZDiskImageStorageDeviceAttachmentWithCacheAndSyncMode( + diskPathChar.CString(), + C.bool(readOnly), + C.int(cachingMode), + C.int(syncMode), + &nserrPtr, + ), + ), + } + if err := newNSError(nserrPtr); err != nil { + return nil, err + } + objc.SetFinalizer(attachment, func(self *DiskImageStorageDeviceAttachment) { + objc.Release(self) + }) + return attachment, nil +} + // StorageDeviceConfiguration for a storage device configuration. type StorageDeviceConfiguration interface { objc.NSObject diff --git a/storage_test.go b/storage_test.go index 96c7f15..834cc3b 100644 --- a/storage_test.go +++ b/storage_test.go @@ -62,3 +62,39 @@ func TestBlockDeviceIdentifier(t *testing.T) { t.Fatalf("want %q but got %q", want, got2) } } + +func TestBlockDeviceWithCacheAndSyncMode(t *testing.T) { + if vz.Available(12) { + t.Skip("vz.NewDiskImageStorageDeviceAttachmentWithCacheAndSync is supported from macOS 12") + } + + container := newVirtualizationMachine(t, + func(vmc *vz.VirtualMachineConfiguration) error { + dir := t.TempDir() + path := filepath.Join(dir, "disk.img") + if err := vz.CreateDiskImage(path, 512); err != nil { + t.Fatal(err) + } + + attachment, err := vz.NewDiskImageStorageDeviceAttachmentWithCacheAndSync(path, false, vz.DiskImageCachingModeAutomatic, vz.DiskImageSynchronizationModeFsync) + if err != nil { + t.Fatal(err) + } + config, err := vz.NewVirtioBlockDeviceConfiguration(attachment) + if err != nil { + t.Fatal(err) + } + vmc.SetStorageDevicesVirtualMachineConfiguration([]vz.StorageDeviceConfiguration{ + config, + }) + return nil + }, + ) + defer container.Close() + + vm := container.VirtualMachine + + if got := vm.State(); vz.VirtualMachineStateRunning != got { + t.Fatalf("want state %v but got %v", vz.VirtualMachineStateRunning, got) + } +} diff --git a/virtualization_12.h b/virtualization_12.h index 961f4c3..d94c416 100644 --- a/virtualization_12.h +++ b/virtualization_12.h @@ -21,6 +21,7 @@ void *newVZVirtioSoundDeviceHostInputStreamConfiguration(); // use in Go void *newVZVirtioSoundDeviceOutputStreamConfiguration(); void *newVZVirtioSoundDeviceHostOutputStreamConfiguration(); // use in Go +void *newVZDiskImageStorageDeviceAttachmentWithCacheAndSyncMode(const char *diskPath, bool readOnly, int cacheMode, int syncMode, void **error); void *newVZUSBScreenCoordinatePointingDeviceConfiguration(); void *newVZUSBKeyboardConfiguration(); void *newVZVirtioSoundDeviceConfiguration(); diff --git a/virtualization_12.m b/virtualization_12.m index efbfef4..523fc93 100644 --- a/virtualization_12.m +++ b/virtualization_12.m @@ -210,6 +210,31 @@ void setStreamsVZVirtioSoundDeviceConfiguration(void *audioDeviceConfiguration, RAISE_UNSUPPORTED_MACOS_EXCEPTION(); } +/*! + @abstract Initialize the attachment from a local file url. + @param diskPath Local file path to the disk image in RAW format. + @param readOnly If YES, the device attachment is read-only, otherwise the device can write data to the disk image. + @param cacheMode The caching mode from one of the available VZDiskImageCachingMode options. + @param syncMode How the disk image synchronizes with the underlying storage when the guest operating system flushes data, described by one of the available VZDiskImageSynchronizationMode modes. + @param error If not nil, assigned with the error if the initialization failed. + @return A VZDiskImageStorageDeviceAttachment on success. Nil otherwise and the error parameter is populated if set. + */ +void *newVZDiskImageStorageDeviceAttachmentWithCacheAndSyncMode(const char *diskPath, bool readOnly, int cacheMode, int syncMode, void **error) +{ + if (@available(macOS 12, *)) { + NSString *diskPathNSString = [NSString stringWithUTF8String:diskPath]; + NSURL *diskURL = [NSURL fileURLWithPath:diskPathNSString]; + return [[VZDiskImageStorageDeviceAttachment alloc] + initWithURL:diskURL + readOnly:(BOOL)readOnly + cachingMode:(VZDiskImageCachingMode)cacheMode + synchronizationMode: (VZDiskImageSynchronizationMode)syncMode + error:(NSError *_Nullable *_Nullable)error]; + } + + RAISE_UNSUPPORTED_MACOS_EXCEPTION(); +} + /*! @abstract Initialize the VZSharedDirectory from the directory path and read only option. @param dirPath