Skip to content

Commit f6c8b99

Browse files
authored
Merge pull request #196 from nagypeterjob/feat-add-asif-support
feat: add ASIF support (macOS 26.0)
2 parents 81b9ba8 + bdc0db4 commit f6c8b99

File tree

4 files changed

+175
-7
lines changed

4 files changed

+175
-7
lines changed

disk.go

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
11
package vz
22

33
import (
4+
"context"
5+
"fmt"
46
"os"
7+
"os/exec"
8+
"strconv"
59
)
610

711
// CreateDiskImage is creating disk image with specified filename and filesize.
@@ -21,3 +25,37 @@ func CreateDiskImage(pathname string, size int64) error {
2125
}
2226
return nil
2327
}
28+
29+
// CreateSparseDiskImage is creating an "Apple Sparse Image Format" disk image
30+
// with specified filename and filesize. The function "shells out" to diskutil, as currently
31+
// this is the only known way of creating ASIF images.
32+
// For example, if you want to create disk with 64GiB, you can set "64 * 1024 * 1024 * 1024" to size.
33+
//
34+
// Note that ASIF is only available from macOS Tahoe, so the function will return error
35+
// on earlier versions.
36+
func CreateSparseDiskImage(ctx context.Context, pathname string, size int64) error {
37+
if err := macOSAvailable(26); err != nil {
38+
return err
39+
}
40+
diskutil, err := exec.LookPath("diskutil")
41+
if err != nil {
42+
return fmt.Errorf("failed to find disktuil: %w", err)
43+
}
44+
45+
sizeStr := strconv.FormatInt(size, 10)
46+
cmd := exec.CommandContext(ctx,
47+
diskutil,
48+
"image",
49+
"create",
50+
"blank",
51+
"--fs", "none",
52+
"--format", "ASIF",
53+
"--size", sizeStr,
54+
pathname)
55+
56+
if err := cmd.Run(); err != nil {
57+
return fmt.Errorf("failed to create ASIF disk image: %w", err)
58+
}
59+
60+
return nil
61+
}

disk_test.go

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
package vz_test
2+
3+
import (
4+
"context"
5+
"os"
6+
"os/exec"
7+
"path/filepath"
8+
"strconv"
9+
"strings"
10+
"testing"
11+
12+
"github.com/Code-Hex/vz/v3"
13+
)
14+
15+
func TestCreateSparseDiskImage_FileCreated(t *testing.T) {
16+
if vz.Available(26) {
17+
t.Skip("CreateSparseDiskImage is supported from macOS 26")
18+
}
19+
20+
dir := t.TempDir()
21+
path := filepath.Join(dir, "sparse_disk.img")
22+
23+
ctx := context.Background()
24+
size := int64(1024 * 1024 * 1024) // 1 GiB
25+
26+
err := vz.CreateSparseDiskImage(ctx, path, size)
27+
if err != nil {
28+
t.Fatalf("failed to create sparse disk image: %v", err)
29+
}
30+
31+
if _, err := os.Stat(path); os.IsNotExist(err) {
32+
t.Fatal("disk image file was not created")
33+
}
34+
}
35+
36+
func TestCreateSparseDiskImage_ASIFFormat(t *testing.T) {
37+
if vz.Available(26) {
38+
t.Skip("CreateSparseDiskImage is supported from macOS 26")
39+
}
40+
41+
dir := t.TempDir()
42+
path := filepath.Join(dir, "sparse_disk.img")
43+
44+
ctx := context.Background()
45+
size := int64(1024 * 1024 * 1024) // 1 GiB
46+
47+
err := vz.CreateSparseDiskImage(ctx, path, size)
48+
if err != nil {
49+
t.Fatalf("failed to create sparse disk image: %v", err)
50+
}
51+
52+
// Check if the format is ASIF using diskutil
53+
cmd := exec.Command("diskutil", "image", "info", path)
54+
output, err := cmd.Output()
55+
if err != nil {
56+
t.Fatalf("failed to get disk image info: %v", err)
57+
}
58+
59+
outputStr := string(output)
60+
lines := strings.Split(outputStr, "\n")
61+
62+
foundASIF := false
63+
// Check if ASIF is mentioned in the first line
64+
if len(lines) != 0 && strings.Contains(lines[0], "ASIF") {
65+
foundASIF = true
66+
}
67+
68+
if !foundASIF {
69+
t.Errorf("disk image is not in ASIF format. Output: %v", lines[:1])
70+
}
71+
}
72+
73+
func TestCreateSparseDiskImage_CorrectSize(t *testing.T) {
74+
if vz.Available(26) {
75+
t.Skip("CreateSparseDiskImage is supported from macOS 26")
76+
}
77+
78+
dir := t.TempDir()
79+
path := filepath.Join(dir, "sparse_disk.img")
80+
81+
ctx := context.Background()
82+
desiredSize := int64(2 * 1024 * 1024 * 1024) // 2 GiB
83+
84+
err := vz.CreateSparseDiskImage(ctx, path, desiredSize)
85+
if err != nil {
86+
t.Fatalf("failed to create sparse disk image: %v", err)
87+
}
88+
89+
cmd := exec.Command("diskutil", "image", "info", path)
90+
output, err := cmd.Output()
91+
if err != nil {
92+
t.Fatalf("failed to get disk image info: %v", err)
93+
}
94+
95+
var sizeStr string
96+
for _, line := range strings.Split(string(output), "\n") {
97+
if strings.Contains(line, "Total Bytes") {
98+
components := strings.Split(strings.TrimSpace(line), ":")
99+
if len(components) > 1 {
100+
sizeStr = strings.TrimSpace(components[1])
101+
break
102+
}
103+
}
104+
}
105+
actualSize, err := strconv.ParseInt(sizeStr, 10, 64)
106+
if err != nil {
107+
t.Fatalf("failed to parse string to int: %v", err)
108+
}
109+
110+
if desiredSize != actualSize {
111+
t.Fatalf("actual disk size (%d) doesn't equal to desired size (%d)", actualSize, desiredSize)
112+
}
113+
}

example/macOS/installer.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ func installMacOS(ctx context.Context) error {
2929
}
3030
configurationRequirements := restoreImage.MostFeaturefulSupportedConfiguration()
3131
config, err := setupVirtualMachineWithMacOSConfigurationRequirements(
32+
ctx,
3233
configurationRequirements,
3334
)
3435
if err != nil {
@@ -67,12 +68,12 @@ func installMacOS(ctx context.Context) error {
6768
return installer.Install(ctx)
6869
}
6970

70-
func setupVirtualMachineWithMacOSConfigurationRequirements(macOSConfiguration *vz.MacOSConfigurationRequirements) (*vz.VirtualMachineConfiguration, error) {
71+
func setupVirtualMachineWithMacOSConfigurationRequirements(ctx context.Context, macOSConfiguration *vz.MacOSConfigurationRequirements) (*vz.VirtualMachineConfiguration, error) {
7172
platformConfig, err := createMacInstallerPlatformConfiguration(macOSConfiguration)
7273
if err != nil {
7374
return nil, fmt.Errorf("failed to create mac platform config: %w", err)
7475
}
75-
return setupVMConfiguration(platformConfig)
76+
return setupVMConfiguration(ctx, platformConfig)
7677
}
7778

7879
func createMacInstallerPlatformConfiguration(macOSConfiguration *vz.MacOSConfigurationRequirements) (*vz.MacPlatformConfiguration, error) {

example/macOS/main.go

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,12 @@ import (
1515

1616
var install bool
1717
var nbdURL string
18+
var asifDiskImage bool
1819

1920
func init() {
2021
flag.BoolVar(&install, "install", false, "run command as install mode")
2122
flag.StringVar(&nbdURL, "nbd-url", "", "nbd url (e.g. nbd+unix:///export?socket=nbd.sock)")
23+
flag.BoolVar(&asifDiskImage, "asif", false, "use ASIF disk image instead of raw")
2224
}
2325

2426
func main() {
@@ -44,7 +46,7 @@ func runVM(ctx context.Context) error {
4446
if err != nil {
4547
return err
4648
}
47-
config, err := setupVMConfiguration(platformConfig)
49+
config, err := setupVMConfiguration(ctx, platformConfig)
4850
if err != nil {
4951
return err
5052
}
@@ -156,9 +158,9 @@ func computeMemorySize() uint64 {
156158
return memorySize
157159
}
158160

159-
func createBlockDeviceConfiguration(diskPath string) (*vz.VirtioBlockDeviceConfiguration, error) {
161+
func createBlockDeviceConfiguration(ctx context.Context, diskPath string) (*vz.VirtioBlockDeviceConfiguration, error) {
160162
// create disk image with 64 GiB
161-
if err := vz.CreateDiskImage(diskPath, 64*1024*1024*1024); err != nil {
163+
if err := createDiskImage(ctx, diskPath, 64*1024*1024*1024); err != nil {
162164
if !os.IsExist(err) {
163165
return nil, fmt.Errorf("failed to create disk image: %w", err)
164166
}
@@ -265,7 +267,7 @@ func createMacPlatformConfiguration() (*vz.MacPlatformConfiguration, error) {
265267
)
266268
}
267269

268-
func setupVMConfiguration(platformConfig vz.PlatformConfiguration) (*vz.VirtualMachineConfiguration, error) {
270+
func setupVMConfiguration(ctx context.Context, platformConfig vz.PlatformConfiguration) (*vz.VirtualMachineConfiguration, error) {
269271
bootloader, err := vz.NewMacOSBootLoader()
270272
if err != nil {
271273
return nil, err
@@ -287,7 +289,7 @@ func setupVMConfiguration(platformConfig vz.PlatformConfiguration) (*vz.VirtualM
287289
config.SetGraphicsDevicesVirtualMachineConfiguration([]vz.GraphicsDeviceConfiguration{
288290
graphicsDeviceConfig,
289291
})
290-
blockDeviceConfig, err := createBlockDeviceConfiguration(GetDiskImagePath())
292+
blockDeviceConfig, err := createBlockDeviceConfiguration(ctx, GetDiskImagePath())
291293
if err != nil {
292294
return nil, fmt.Errorf("failed to create block device configuration: %w", err)
293295
}
@@ -363,3 +365,17 @@ func retrieveNetworkBlockDeviceStorageDeviceAttachment(storages []vz.StorageDevi
363365
}
364366
return nil
365367
}
368+
369+
func createDiskImage(ctx context.Context, diskpath string, size int64) error {
370+
if asifDiskImage {
371+
if err := vz.CreateSparseDiskImage(ctx, diskpath, size); err != nil {
372+
return err
373+
}
374+
return nil
375+
}
376+
377+
if err := vz.CreateDiskImage(diskpath, size); err != nil {
378+
return err
379+
}
380+
return nil
381+
}

0 commit comments

Comments
 (0)