From ff2411442b429ae67adb24c5404a3e54287c7508 Mon Sep 17 00:00:00 2001 From: Akihiro Suda Date: Thu, 16 Oct 2025 00:04:46 +0900 Subject: [PATCH] Decouple diffdisk (misnomer) from basedisk Fix issue 1706. Now basedisk is immediately renamed/converted to diffdisk (not a diff despite the name) by the VM driver, except when basedisk is an ISO9660 image. Decoupling diffdisk from basedisk will eliminate the overhead of differencing I/O and save some disk space. I cannot remember why I designed the disk to be split into basedisk and diffdisk. Maybe it was to allow `limactl factory-reset` to retain the initial state, although the command was apparently never implemented in that way. Maybe it was to allow multiple instances to share the same basedisk, although it was never implemented in that way, either. Signed-off-by: Akihiro Suda --- pkg/driver/qemu/qemu.go | 26 ++++++++++----- pkg/driver/vz/vm_darwin.go | 12 +++++-- pkg/driverutil/disk.go | 40 +++++++++++++++--------- pkg/driverutil/disk_test.go | 13 +++++--- pkg/instance/start.go | 10 +++--- pkg/limatype/filenames/filenames.go | 4 +-- website/content/en/docs/dev/internals.md | 9 ++++-- 7 files changed, 76 insertions(+), 38 deletions(-) diff --git a/pkg/driver/qemu/qemu.go b/pkg/driver/qemu/qemu.go index c139348a5a6..a8debbc392d 100644 --- a/pkg/driver/qemu/qemu.go +++ b/pkg/driver/qemu/qemu.go @@ -87,7 +87,8 @@ func minimumQemuVersion() (hardMin, softMin semver.Version) { return hardMin, softMin } -// EnsureDisk also ensures the kernel and the initrd. +// EnsureDisk just renames baseDisk to diffDisk, unless baseDisk is an ISO9660 image. +// Note that "diffDisk" is a misnomer, it is actually created as a full disk since Lima v2.1. func EnsureDisk(ctx context.Context, cfg Config) error { diffDisk := filepath.Join(cfg.InstanceDir, filenames.DiffDisk) if _, err := os.Stat(diffDisk); err == nil || !errors.Is(err, os.ErrNotExist) { @@ -115,10 +116,14 @@ func EnsureDisk(ctx context.Context, cfg Config) error { if baseDiskInfo.Format == "" { return fmt.Errorf("failed to inspect the format of %q", baseDisk) } - args := []string{"create", "-f", "qcow2"} if !isBaseDiskISO { - args = append(args, "-F", baseDiskInfo.Format, "-b", baseDisk) + // "diffdisk" is a misnomer, it is actually created as a full disk since Lima v2.1. + if err = os.Rename(baseDisk, diffDisk); err != nil { + return err + } + return nil } + args := []string{"create", "-f", "qcow2"} args = append(args, diffDisk, strconv.Itoa(int(diskSize))) cmd := exec.CommandContext(ctx, "qemu-img", args...) if out, err := cmd.CombinedOutput(); err != nil { @@ -719,9 +724,15 @@ func Cmdline(ctx context.Context, cfg Config) (exe string, args []string, err er extraDisks = append(extraDisks, dataDisk) } - isBaseDiskCDROM, err := iso9660util.IsISO9660(baseDisk) - if err != nil { - return "", nil, err + var baseDiskExists, isBaseDiskCDROM bool + if _, err := os.Stat(baseDisk); !errors.Is(err, os.ErrNotExist) { + baseDiskExists = true + } + if baseDiskExists { + isBaseDiskCDROM, err = iso9660util.IsISO9660(baseDisk) + if err != nil { + return "", nil, err + } } if isBaseDiskCDROM { args = appendArgsIfNoConflict(args, "-boot", "order=d,splash-time=0,menu=on") @@ -731,7 +742,8 @@ func Cmdline(ctx context.Context, cfg Config) (exe string, args []string, err er } if diskSize, _ := units.RAMInBytes(*cfg.LimaYAML.Disk); diskSize > 0 { args = append(args, "-drive", fmt.Sprintf("file=%s,if=virtio,discard=on", diffDisk)) - } else if !isBaseDiskCDROM { + } else if baseDiskExists && !isBaseDiskCDROM { // FIXME: How does this happen? Is this even a valid case? + logrus.Errorf("weird configuration, how does this happen?: diskSize /* %d */ <= 0 && baseDiskExists && !isBaseDiskCDROM", diskSize) baseDiskInfo, err := qemuimgutil.GetInfo(ctx, baseDisk) if err != nil { return "", nil, fmt.Errorf("failed to get the information of %q: %w", baseDisk, err) diff --git a/pkg/driver/vz/vm_darwin.go b/pkg/driver/vz/vm_darwin.go index 397c3d5f3d2..066a7747a30 100644 --- a/pkg/driver/vz/vm_darwin.go +++ b/pkg/driver/vz/vm_darwin.go @@ -463,9 +463,15 @@ func attachDisks(ctx context.Context, inst *limatype.Instance, vmConfig *vz.Virt baseDiskPath := filepath.Join(inst.Dir, filenames.BaseDisk) diffDiskPath := filepath.Join(inst.Dir, filenames.DiffDisk) ciDataPath := filepath.Join(inst.Dir, filenames.CIDataISO) - isBaseDiskCDROM, err := iso9660util.IsISO9660(baseDiskPath) - if err != nil { - return err + var ( + isBaseDiskCDROM bool + err error + ) + if _, err := os.Stat(baseDiskPath); !errors.Is(err, os.ErrNotExist) { + isBaseDiskCDROM, err = iso9660util.IsISO9660(baseDiskPath) + if err != nil { + return err + } } var configurations []vz.StorageDeviceConfiguration diff --git a/pkg/driverutil/disk.go b/pkg/driverutil/disk.go index 7ccd5644c11..9dc92f2349f 100644 --- a/pkg/driverutil/disk.go +++ b/pkg/driverutil/disk.go @@ -19,6 +19,8 @@ import ( ) // EnsureDisk ensures that the diff disk exists with the specified size and format. +// EnsureDisk usually just converts baseDisk (can be qcow2) to diffDisk (raw), unless baseDisk is an ISO9660 image. +// Note that "diffDisk" is a misnomer, it is actually created as a full disk since Lima v2.1. func EnsureDisk(ctx context.Context, instDir, diskSize string, diskImageFormat image.Type) error { diffDisk := filepath.Join(instDir, filenames.DiffDisk) if _, err := os.Stat(diffDisk); err == nil || !errors.Is(err, os.ErrNotExist) { @@ -29,33 +31,43 @@ func EnsureDisk(ctx context.Context, instDir, diskSize string, diskImageFormat i diskUtil := proxyimgutil.NewDiskUtil(ctx) baseDisk := filepath.Join(instDir, filenames.BaseDisk) + srcDisk := baseDisk diskSizeInBytes, _ := units.RAMInBytes(diskSize) if diskSizeInBytes == 0 { return nil } - isBaseDiskISO, err := iso9660util.IsISO9660(baseDisk) - if err != nil { - return err - } - srcDisk := baseDisk - if isBaseDiskISO { - srcDisk = diffDisk - - // Create an empty data volume for the diff disk - diffDiskF, err := os.Create(diffDisk) + var isBaseDiskISO bool + if _, err := os.Stat(baseDisk); !errors.Is(err, os.ErrNotExist) { + isBaseDiskISO, err = iso9660util.IsISO9660(baseDisk) if err != nil { return err } + if isBaseDiskISO { + srcDisk = diffDisk - if err = diffDiskF.Close(); err != nil { - return err + // Create an empty data volume for the diff disk + diffDiskF, err := os.Create(diffDisk) + if err != nil { + return err + } + + if err = diffDiskF.Close(); err != nil { + return err + } } } + // Check whether to use ASIF format - if err = diskUtil.Convert(ctx, diskImageFormat, srcDisk, diffDisk, &diskSizeInBytes, false); err != nil { + if err := diskUtil.Convert(ctx, diskImageFormat, srcDisk, diffDisk, &diskSizeInBytes, false); err != nil { return fmt.Errorf("failed to convert %q to a disk %q: %w", srcDisk, diffDisk, err) } - return err + + if !isBaseDiskISO { + if err := os.RemoveAll(baseDisk); err != nil { + return err + } + } + return nil } diff --git a/pkg/driverutil/disk_test.go b/pkg/driverutil/disk_test.go index 583feb19c62..3e5ef861b94 100644 --- a/pkg/driverutil/disk_test.go +++ b/pkg/driverutil/disk_test.go @@ -111,18 +111,21 @@ func TestEnsureDisk_WithNonISOBaseImage(t *testing.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() { formats = append(formats, typeASIF) } for _, format := range formats { + writeNonISO(t, base) + isISO, err := iso9660util.IsISO9660(base) + assert.NilError(t, err) + assert.Assert(t, !isISO) + assert.NilError(t, EnsureDisk(t.Context(), instDir, "2MiB", format)) + _, err = os.Stat(base) + // Starting with Lima v2.1, the base disk is removed after creating the "diff disk" (misnomer). + assert.ErrorIs(t, err, os.ErrNotExist) checkDisk(t, diff, format) assert.NilError(t, os.Remove(diff)) } diff --git a/pkg/instance/start.go b/pkg/instance/start.go index 8e5c3806f81..1c8df511a8f 100644 --- a/pkg/instance/start.go +++ b/pkg/instance/start.go @@ -29,6 +29,7 @@ import ( "github.com/lima-vm/lima/v2/pkg/imgutil/proxyimgutil" "github.com/lima-vm/lima/v2/pkg/limatype" "github.com/lima-vm/lima/v2/pkg/limatype/filenames" + "github.com/lima-vm/lima/v2/pkg/limayaml" "github.com/lima-vm/lima/v2/pkg/registry" "github.com/lima-vm/lima/v2/pkg/store" "github.com/lima-vm/lima/v2/pkg/usrlocal" @@ -66,11 +67,12 @@ func Prepare(ctx context.Context, inst *limatype.Instance, guestAgent string) (* return nil, err } - // Check if the instance has been created (the base disk already exists) - baseDisk := filepath.Join(inst.Dir, filenames.BaseDisk) - _, err = os.Stat(baseDisk) - created := err == nil + // Check if the instance has been created + created := limayaml.IsExistingInstanceDir(inst.Dir) + // baseDisk is usually immediately renamed to diffDisk (misnomer) by the VM driver, + // except when baseDisk is an ISO9660 image. + baseDisk := filepath.Join(inst.Dir, filenames.BaseDisk) kernel := filepath.Join(inst.Dir, filenames.Kernel) kernelCmdline := filepath.Join(inst.Dir, filenames.KernelCmdline) initrd := filepath.Join(inst.Dir, filenames.Initrd) diff --git a/pkg/limatype/filenames/filenames.go b/pkg/limatype/filenames/filenames.go index a8459d77888..c9cc9158507 100644 --- a/pkg/limatype/filenames/filenames.go +++ b/pkg/limatype/filenames/filenames.go @@ -36,8 +36,8 @@ const ( CIDataISO = "cidata.iso" CIDataISODir = "cidata" CloudConfig = "cloud-config.yaml" - BaseDisk = "basedisk" - DiffDisk = "diffdisk" + BaseDisk = "basedisk" // usually immediately converted/renamed to diffDisk by the VM driver + DiffDisk = "diffdisk" // misnomer; actually a full disk since Lima v2.1 Kernel = "kernel" KernelCmdline = "kernel.cmdline" Initrd = "initrd" diff --git a/website/content/en/docs/dev/internals.md b/website/content/en/docs/dev/internals.md index 559f9ed75bd..1146c1d6442 100644 --- a/website/content/en/docs/dev/internals.md +++ b/website/content/en/docs/dev/internals.md @@ -44,8 +44,11 @@ Ansible: - `ansible-inventory.yaml`: the Ansible node inventory. See [ansible](#ansible). disk: -- `basedisk`: the base image -- `diffdisk`: the diff image (QCOW2) +- `basedisk`: the historical base image. Can be missing since Lima v2.1. + - The main `limactl` process prepares this `basedisk`, however, a [VM driver](./drivers.md) may convert and rename `basedisk` to `diffdisk` immediately. +- `diffdisk`: the image, historically a QCOW2 diff from `basedisk`. + - `diffdisk` is a misnomer; it does not necessarily have a reference to `basedisk`. + Notably, when a `basedisk` is an ISO9660 image, or the VM driver does not support differencing, `diffdisk` is an independent image. kernel: - `kernel`: the kernel @@ -190,4 +193,4 @@ The volume label is "cidata", as defined by [cloud-init NoCloud](https://docs.cl ![](/images/internals/lima-sequence-diagram.png) -(based on Lima 0.8.3) \ No newline at end of file +(based on Lima 0.8.3)