From d83b99afb471aafb28707bc4fa2a4f44e0a6b8ab Mon Sep 17 00:00:00 2001 From: ashwat287 Date: Tue, 2 Dec 2025 09:18:49 +0000 Subject: [PATCH 1/2] fix: for ISO base disk remove premature return after creating and empty volume for diff disk so Convert executes. Ensure diffDisk is converted to the requested format instead of staying sparse. Signed-off-by: ashwat287 --- pkg/driverutil/disk.go | 14 ++++++-------- pkg/imgutil/nativeimgutil/nativeimgutil.go | 6 +++--- 2 files changed, 9 insertions(+), 11 deletions(-) diff --git a/pkg/driverutil/disk.go b/pkg/driverutil/disk.go index a9ab18ef311..ac044b9ed57 100644 --- a/pkg/driverutil/disk.go +++ b/pkg/driverutil/disk.go @@ -38,23 +38,21 @@ func EnsureDisk(ctx context.Context, instDir, diskSize string, diskImageFormat i if err != nil { return err } + destDisk := baseDisk if isBaseDiskISO { - // Create an empty data volume (sparse) + destDisk = diffDisk + + // Create an empty data volume for the diff disk diffDiskF, err := os.Create(diffDisk) if err != nil { return err } - err = diskUtil.MakeSparse(ctx, diffDiskF, 0) - if err != nil { - diffDiskF.Close() - return fmt.Errorf("failed to create sparse diff disk %q: %w", diffDisk, err) - } - return diffDiskF.Close() + diffDiskF.Close() } // Check whether to use ASIF format - if err = diskUtil.Convert(ctx, diskImageFormat, baseDisk, diffDisk, &diskSizeInBytes, false); err != nil { + if err = diskUtil.Convert(ctx, diskImageFormat, destDisk, diffDisk, &diskSizeInBytes, false); err != nil { return fmt.Errorf("failed to convert %q to a disk %q: %w", baseDisk, diffDisk, err) } return err diff --git a/pkg/imgutil/nativeimgutil/nativeimgutil.go b/pkg/imgutil/nativeimgutil/nativeimgutil.go index 59e45c15267..e0c74830bdc 100644 --- a/pkg/imgutil/nativeimgutil/nativeimgutil.go +++ b/pkg/imgutil/nativeimgutil/nativeimgutil.go @@ -62,10 +62,10 @@ func convertTo(destType image.Type, source, dest string, size *int64, allowSourc logrus.Infof("Converting %q (%s) to a %s disk %q", source, srcImg.Type(), destType, dest) switch t := srcImg.Type(); t { case raw.Type: - if err = srcF.Close(); err != nil { - return err - } if destType == raw.Type { + if err = srcF.Close(); err != nil { + return err + } return convertRawToRaw(source, dest, size) } case qcow2.Type: From cde8922866b283df4d4cd4363c36e32fd8d596d3 Mon Sep 17 00:00:00 2001 From: ashwat287 Date: Tue, 2 Dec 2025 16:53:05 +0000 Subject: [PATCH 2/2] test(driverutil): add EnsureDisk tests for ISO and non-ISO base images Add pkg/driverutil/disk_test.go covering: - Base image is ISO: EnsureDisk creates/keeps diffdisk and converts to asif and raw; base ISO remains unchanged (content hash and ISO signature). - Base image is non-ISO: EnsureDisk converts diffdisk to raw and asif. Signed-off-by: ashwat287 --- pkg/driverutil/disk_test.go | 175 ++++++++++++++++++++++++++++++++++++ 1 file changed, 175 insertions(+) create mode 100644 pkg/driverutil/disk_test.go diff --git a/pkg/driverutil/disk_test.go b/pkg/driverutil/disk_test.go new file mode 100644 index 00000000000..66258285c95 --- /dev/null +++ b/pkg/driverutil/disk_test.go @@ -0,0 +1,175 @@ +// SPDX-FileCopyrightText: Copyright The Lima Authors +// SPDX-License-Identifier: Apache-2.0 + +package driverutil + +import ( + "crypto/sha256" + "encoding/hex" + "os" + "os/exec" + "path/filepath" + "runtime" + "strconv" + "strings" + "testing" + + "github.com/lima-vm/go-qcow2reader" + "github.com/lima-vm/go-qcow2reader/image" + "gotest.tools/v3/assert" + + "github.com/lima-vm/lima/v2/pkg/iso9660util" + "github.com/lima-vm/lima/v2/pkg/limatype/filenames" +) + +const ( + typeRAW = image.Type("raw") + typeASIF = image.Type("asif") +) + +func makeTempInstanceDir(t *testing.T) string { + t.Helper() + return t.TempDir() +} + +func writeMinimalISO(t *testing.T, path string) { + t.Helper() + entries := []iso9660util.Entry{ + {Path: "/hello.txt", Reader: strings.NewReader("hello world")}, + } + assert.NilError(t, iso9660util.Write(path, "TESTISO", entries)) +} + +func writeNonISO(t *testing.T, path string) { + t.Helper() + size := 64 * 1024 + buf := make([]byte, size) + copy(buf[0x8001:], "XXXXX") + assert.NilError(t, os.WriteFile(path, buf, 0o644)) +} + +func sha256File(t *testing.T, path string) string { + t.Helper() + b, err := os.ReadFile(path) + assert.NilError(t, err) + sum := sha256.Sum256(b) + return hex.EncodeToString(sum[:]) +} + +func detectImageType(t *testing.T, path string) image.Type { + t.Helper() + f, err := os.Open(path) + assert.NilError(t, err) + defer f.Close() + img, err := qcow2reader.Open(f) + assert.NilError(t, err) + return img.Type() +} + +func checkDisk(t *testing.T, diff string, expectedType image.Type) { + t.Helper() + fi, err := os.Stat(diff) + assert.NilError(t, err) + assert.Assert(t, fi.Size() > 0) + assert.Equal(t, detectImageType(t, diff), expectedType) +} + +func removeAndCheck(t *testing.T, path string) { + t.Helper() + assert.NilError(t, os.Remove(path)) + _, err := os.Stat(path) + assert.ErrorIs(t, err, os.ErrNotExist) +} + +func isMacOS26OrHigher(t *testing.T) bool { + if runtime.GOOS != "darwin" { + return false + } + out, err := exec.CommandContext(t.Context(), "sw_vers", "-productVersion").Output() + if err != nil { + return false + } + parts := strings.Split(strings.TrimSpace(string(out)), ".") + if len(parts) < 1 { + return false + } + major, err := strconv.Atoi(parts[0]) + if err != nil { + return false + } + return major >= 26 +} + +func TestEnsureDisk_WithISOBaseImage(t *testing.T) { + instDir := makeTempInstanceDir(t) + base := filepath.Join(instDir, filenames.BaseDisk) + diff := filepath.Join(instDir, filenames.DiffDisk) + + writeMinimalISO(t, base) + isISO, err := iso9660util.IsISO9660(base) + assert.NilError(t, err) + assert.Assert(t, isISO) + baseHashBefore := sha256File(t, base) + + formats := []image.Type{typeRAW} + if isMacOS26OrHigher(t) { + formats = append(formats, typeASIF) + } + + for _, format := range formats { + assert.NilError(t, EnsureDisk(t.Context(), instDir, "2MiB", format)) + isISO, err = iso9660util.IsISO9660(base) + assert.NilError(t, err) + assert.Assert(t, isISO) + assert.Equal(t, baseHashBefore, sha256File(t, base)) + checkDisk(t, diff, format) + if format != formats[len(formats)-1] { + removeAndCheck(t, diff) + } + } +} + +func TestEnsureDisk_WithNonISOBaseImage(t *testing.T) { + instDir := makeTempInstanceDir(t) + base := filepath.Join(instDir, filenames.BaseDisk) + diff := filepath.Join(instDir, filenames.DiffDisk) + + writeNonISO(t, base) + isISO, err := iso9660util.IsISO9660(base) + assert.NilError(t, err) + assert.Assert(t, !isISO) + + formats := []image.Type{typeRAW} + if isMacOS26OrHigher(t) { + formats = append(formats, typeASIF) + } + + for _, format := range formats { + assert.NilError(t, EnsureDisk(t.Context(), instDir, "2MiB", format)) + checkDisk(t, diff, format) + if format != formats[len(formats)-1] { + removeAndCheck(t, diff) + } + } +} + +func TestEnsureDisk_ExistingDiffDisk(t *testing.T) { + instDir := makeTempInstanceDir(t) + base := filepath.Join(instDir, filenames.BaseDisk) + diff := filepath.Join(instDir, filenames.DiffDisk) + + writeNonISO(t, base) + + formats := []image.Type{typeRAW} + if isMacOS26OrHigher(t) { + formats = append(formats, typeASIF) + } + + for _, format := range formats { + assert.NilError(t, os.WriteFile(diff, []byte("preexisting"), 0o644)) + origHash := sha256File(t, diff) + assert.NilError(t, EnsureDisk(t.Context(), instDir, "2MiB", format)) + assert.Equal(t, sha256File(t, diff), origHash) + removeAndCheck(t, diff) + } +}