Skip to content

Commit

Permalink
Merge pull request #156 from saracen/nbd-support
Browse files Browse the repository at this point in the history
added VZNetworkBlockDeviceStorageDeviceAttachment
  • Loading branch information
Code-Hex authored Oct 19, 2024
2 parents d1fe5bd + ca48d37 commit dc6221c
Show file tree
Hide file tree
Showing 6 changed files with 154 additions and 26 deletions.
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
- macos-15
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
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
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
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();
}

0 comments on commit dc6221c

Please sign in to comment.