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

added VZNetworkBlockDeviceStorageDeviceAttachment #156

Merged
merged 6 commits into from
Oct 19, 2024
Merged
Show file tree
Hide file tree
Changes from 5 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
49 changes: 34 additions & 15 deletions .github/workflows/compile.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,40 +15,59 @@ jobs:
name: Formatting Check
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Run clang-format style check for Objective-C files.
uses: jidicula/clang-format-action@v4.8.0
uses: jidicula/clang-format-action@v4.13.0
with:
clang-format-version: '13'
build:
needs: formatting-check
runs-on: ${{ matrix.os }}
timeout-minutes: 6
timeout-minutes: 30
strategy:
fail-fast: false
matrix:
os:
- macOS-11
- macOS-12
- macOS-13
- macos-13 # Intel
- macos-14-large # Intel
- macos-15-large # Intel
Code-Hex marked this conversation as resolved.
Show resolved Hide resolved
go:
- '^1.20'
- '^1.21'
- '^1.22'
- '^1.23'
steps:
- name: Check out repository code
uses: actions/checkout@v3
uses: actions/checkout@v4
- name: Set up Go
uses: actions/setup-go@v4
uses: actions/setup-go@v5
with:
go-version: ${{ matrix.go }}
- name: vet
run: go vet ./...
- name: Download Linux kernel
run: make download_kernel
- name: Unit Test
run: make test
timeout-minutes: 3
- name: Build Linux
run: make -C example/linux
- name: Build GUI Linux
run: make -C example/gui-linux
test:
needs: build
runs-on: ${{ matrix.os }}
timeout-minutes: 30
strategy:
fail-fast: false
# Can't expand the matrix due to the flakiness of the CI infra
matrix:
os:
- macos-15-large # Intel
Code-Hex marked this conversation as resolved.
Show resolved Hide resolved
go:
- '^1.23'
steps:
- name: Check out repository code
uses: actions/checkout@v4
- name: Set up Go
uses: actions/setup-go@v5
with:
go-version: ${{ matrix.go }}
- name: Download Linux kernel
run: make download_kernel
- name: Unit Test
run: make test
timeout-minutes: 10
34 changes: 24 additions & 10 deletions example/macOS/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,11 @@ import (
)

var install bool
var nbdURL string

func init() {
flag.BoolVar(&install, "install", false, "run command as install mode")
flag.StringVar(&nbdURL, "nbd-url", "", "nbd url (e.g. nbd+unix:///export?socket=nbd.sock)")
}

func main() {
Expand Down Expand Up @@ -142,21 +144,33 @@ func computeMemorySize() uint64 {
}

func createBlockDeviceConfiguration(diskPath string) (*vz.VirtioBlockDeviceConfiguration, error) {
// 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)
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)
}
}
}

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

func createGraphicsDeviceConfiguration() (*vz.MacGraphicsDeviceConfiguration, error) {
Expand Down
2 changes: 2 additions & 0 deletions osversion.go
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,8 @@ func macOSBuildTargetAvailable(version float64) error {
target = 130000 // __MAC_13_0
case 14:
target = 140000 // __MAC_14_0
case 15:
target = 150000 // __MAC_15_0
}
if allowedVersion < target {
return fmt.Errorf("%w for %.1f (the binary was built with __MAC_OS_X_VERSION_MAX_ALLOWED=%d; needs recompilation)",
Expand Down
4 changes: 4 additions & 0 deletions osversion_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -319,6 +319,10 @@ func TestAvailableVersion(t *testing.T) {
_, err := NewDiskBlockDeviceStorageDeviceAttachment(nil, false, DiskSynchronizationModeFull)
return err
},
"NewNetworkBlockDeviceStorageDeviceAttachment": func() error {
_, err := NewNetworkBlockDeviceStorageDeviceAttachment("", 0, false, DiskSynchronizationModeFull)
return err
},
}
for name, fn := range cases {
t.Run(name, func(t *testing.T) {
Expand Down
23 changes: 23 additions & 0 deletions platform.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ package vz
# include "virtualization_11.h"
# include "virtualization_12.h"
# include "virtualization_13.h"
# include "virtualization_15.h"
*/
import "C"
import (
Expand Down Expand Up @@ -40,6 +41,28 @@ func (m *GenericPlatformConfiguration) MachineIdentifier() *GenericMachineIdenti
return m.machineIdentifier
}

// IsNestedVirtualizationSupported reports if nested virtualization is supported.
func IsNestedVirtualizationSupported() bool {
if err := macOSAvailable(15); err != nil {
return false
}

return (bool)(C.isNestedVirtualizationSupported())
}

// SetNestedVirtualizationEnabled toggles nested virtualization.
func (m *GenericPlatformConfiguration) SetNestedVirtualizationEnabled(enable bool) error {
if err := macOSAvailable(15); err != nil {
return err
}

C.setNestedVirtualizationEnabled(
objc.Ptr(m),
C.bool(enable),
)
return nil
}

var _ PlatformConfiguration = (*GenericPlatformConfiguration)(nil)

// NewGenericPlatformConfiguration creates a new generic platform configuration.
Expand Down
57 changes: 57 additions & 0 deletions storage.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ package vz
import "C"
import (
"os"
"time"

"github.com/Code-Hex/vz/v3/internal/objc"
)
Expand Down Expand Up @@ -397,3 +398,59 @@ func NewDiskBlockDeviceStorageDeviceAttachment(file *os.File, readOnly bool, syn
})
return attachment, nil
}

// NetworkBlockDeviceStorageDeviceAttachment is a storage device attachment that is backed by a
// NBD (Network Block Device) server.
//
// Using this attachment requires the app to have the com.apple.security.network.client entitlement
// because this attachment opens an outgoing network connection.
//
// For more information about the NBD URL format read:
// https://github.com/NetworkBlockDevice/nbd/blob/master/doc/uri.md
type NetworkBlockDeviceStorageDeviceAttachment struct {
*pointer

*baseStorageDeviceAttachment
}

var _ StorageDeviceAttachment = (*NetworkBlockDeviceStorageDeviceAttachment)(nil)

// NewNetworkBlockDeviceStorageDeviceAttachment creates a new network block device storage attachment from an NBD
// Uniform Resource Indicator (URI) represented as a URL, timeout value, and read-only and synchronization modes
// that you provide.
//
// - 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.
// - syncMode is one of the available DiskSynchronizationMode options.
//
// This is only supported on macOS 14 and newer, error will
// be returned on older versions.
func NewNetworkBlockDeviceStorageDeviceAttachment(url string, timeout time.Duration, forcedReadOnly bool, syncMode DiskSynchronizationMode) (*NetworkBlockDeviceStorageDeviceAttachment, error) {
if err := macOSAvailable(14); err != nil {
return nil, err
}

nserrPtr := newNSErrorAsNil()

urlChar := charWithGoString(url)
defer urlChar.Free()
attachment := &NetworkBlockDeviceStorageDeviceAttachment{
pointer: objc.NewPointer(
C.newVZNetworkBlockDeviceStorageDeviceAttachment(
urlChar.CString(),
C.double(timeout.Seconds()),
C.bool(forcedReadOnly),
C.int(syncMode),
&nserrPtr,
),
),
}
if err := newNSError(nserrPtr); err != nil {
return nil, err
}
objc.SetFinalizer(attachment, func(self *NetworkBlockDeviceStorageDeviceAttachment) {
objc.Release(self)
})
return attachment, nil
}
3 changes: 2 additions & 1 deletion virtualization_14.h
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,5 @@

/* macOS 14 API */
void *newVZNVMExpressControllerDeviceConfiguration(void *attachment);
void *newVZDiskBlockDeviceStorageDeviceAttachment(int fileDescriptor, bool readOnly, int syncMode, void **error);
void *newVZDiskBlockDeviceStorageDeviceAttachment(int fileDescriptor, bool readOnly, int syncMode, void **error);
void *newVZNetworkBlockDeviceStorageDeviceAttachment(const char *url, double timeout, bool forcedReadOnly, int syncMode, void **error);
33 changes: 33 additions & 0 deletions virtualization_14.m
Original file line number Diff line number Diff line change
Expand Up @@ -50,4 +50,37 @@
}
#endif
RAISE_UNSUPPORTED_MACOS_EXCEPTION();
}

/*!
@abstract Initialize a network block device storage attachment from an NBD URI.
@param uri The NBD’s URI represented as a URL.
@param timeout The timeout value in seconds for the connection between the client and server. When the timeout expires, an attempt to reconnect with the server takes place.
@param forcedReadOnly If YES, the framework forces the disk attachment to be read-only, regardless of whether or not the NBD server supports write requests.
@param synchronizationMode Defines how the disk synchronizes with the underlying storage when the guest operating system flushes data.
@param error If not nil, assigned with the error if the initialization failed.
@return An initialized `VZDiskBlockDeviceStorageDeviceAttachment` or nil if there was an error.
@discussion
The forcedReadOnly parameter affects how framework exposes the NBD client to the guest operating
system by the storage controller. As part of the NBD protocol, the NBD server advertises whether
or not the disk exposed by the NBD client is read-only during the handshake phase of the protocol.

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)
{
#ifdef INCLUDE_TARGET_OSX_14
if (@available(macOS 14, *)) {
NSURL *url = [NSURL URLWithString:[NSString stringWithUTF8String:uri]];

return [[VZNetworkBlockDeviceStorageDeviceAttachment alloc]
initWithURL:url
timeout:(NSTimeInterval)timeout
forcedReadOnly:(BOOL)forcedReadOnly
synchronizationMode:(VZDiskSynchronizationMode)syncMode
error:(NSError *_Nullable *_Nullable)error];
}
#endif
RAISE_UNSUPPORTED_MACOS_EXCEPTION();
}
15 changes: 15 additions & 0 deletions virtualization_15.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
//
// virtualization_15.h

#pragma once

// FIXME(codehex): this is dirty hack to avoid clang-format error like below
// "Configuration file(s) do(es) not support C++: /github.com/Code-Hex/vz/.clang-format"
#define NSURLComponents NSURLComponents

#import "virtualization_helper.h"
#import <Virtualization/Virtualization.h>

/* macOS 15 API */
bool isNestedVirtualizationSupported();
void setNestedVirtualizationEnabled(void *config, bool nestedVirtualizationEnabled);
33 changes: 33 additions & 0 deletions virtualization_15.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
//
// virtualization_15.m
//
#import "virtualization_15.h"

/*!
@abstract Check if nested virtualization is supported.
@return true if supported.
*/
bool isNestedVirtualizationSupported()
{
#ifdef INCLUDE_TARGET_OSX_15
if (@available(macOS 15, *)) {
return (bool) VZGenericPlatformConfiguration.isNestedVirtualizationSupported;
}
#endif
RAISE_UNSUPPORTED_MACOS_EXCEPTION();
}

/*!
@abstract Set nestedVirtualizationEnabled. The default is false.
*/
void setNestedVirtualizationEnabled(void *config, bool nestedVirtualizationEnabled)
{
#ifdef INCLUDE_TARGET_OSX_15
if (@available(macOS 15, *)) {
VZGenericPlatformConfiguration *platformConfig = (VZGenericPlatformConfiguration *)config;
platformConfig.nestedVirtualizationEnabled = (BOOL) nestedVirtualizationEnabled;
return;
}
#endif
RAISE_UNSUPPORTED_MACOS_EXCEPTION();
}
7 changes: 7 additions & 0 deletions virtualization_helper.h
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,13 @@ NSDictionary *dumpProcessinfo();
#pragma message("macOS 14 API has been disabled")
#endif

// for macOS 15 API
#if __MAC_OS_X_VERSION_MAX_ALLOWED >= 150000
#define INCLUDE_TARGET_OSX_15 1
#else
#pragma message("macOS 15 API has been disabled")
#endif

static inline int mac_os_x_version_max_allowed()
{
#ifdef __MAC_OS_X_VERSION_MAX_ALLOWED
Expand Down
Loading