Skip to content

Commit d4972a3

Browse files
committed
defined a common interface for nativeimgutil & qemuimgutil
Signed-off-by: Horiodino <holiodin@gmail.com> refactor: remove old qemuimgutil and nativeimgutil packages Signed-off-by: Horiodino <holiodin@gmail.com> fixed golangci-lint errs Signed-off-by: Horiodino <holiodin@gmail.com> refactor: added proxyimgutil for unified image utility interface Signed-off-by: Horiodino <holiodin@gmail.com> Only fall back to native if qemu-img is not found Signed-off-by: Praful Khanduri <holiodin@gmail.com>
1 parent dbf3cd9 commit d4972a3

File tree

14 files changed

+365
-82
lines changed

14 files changed

+365
-82
lines changed

cmd/limactl/disk.go

Lines changed: 5 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,7 @@ import (
1818
"github.com/sirupsen/logrus"
1919
"github.com/spf13/cobra"
2020

21-
"github.com/lima-vm/lima/pkg/nativeimgutil"
22-
"github.com/lima-vm/lima/pkg/qemu/imgutil"
21+
"github.com/lima-vm/lima/pkg/imgutil/proxyimgutil"
2322
"github.com/lima-vm/lima/pkg/store"
2423
"github.com/lima-vm/lima/pkg/store/filenames"
2524
)
@@ -113,11 +112,8 @@ func diskCreateAction(cmd *cobra.Command, args []string) error {
113112

114113
// qemu may not be available, use it only if needed.
115114
dataDisk := filepath.Join(diskDir, filenames.DataDisk)
116-
if format == "raw" {
117-
err = nativeimgutil.CreateRawDisk(dataDisk, int(diskSize))
118-
} else {
119-
err = imgutil.CreateDisk(dataDisk, format, int(diskSize))
120-
}
115+
diskUtil, _ := proxyimgutil.NewProxyImageUtil()
116+
err = diskUtil.CreateDisk(dataDisk, int(diskSize))
121117
if err != nil {
122118
rerr := os.RemoveAll(diskDir)
123119
if rerr != nil {
@@ -410,11 +406,8 @@ func diskResizeAction(cmd *cobra.Command, args []string) error {
410406

411407
// qemu may not be available, use it only if needed.
412408
dataDisk := filepath.Join(disk.Dir, filenames.DataDisk)
413-
if disk.Format == "raw" {
414-
err = nativeimgutil.ResizeRawDisk(dataDisk, int(diskSize))
415-
} else {
416-
err = imgutil.ResizeDisk(dataDisk, disk.Format, int(diskSize))
417-
}
409+
diskUtil, _ := proxyimgutil.NewProxyImageUtil()
410+
err = diskUtil.ResizeDisk(dataDisk, int(diskSize))
418411
if err != nil {
419412
return fmt.Errorf("failed to resize disk %q: %w", diskName, err)
420413
}

pkg/imgutil/interface.go

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
// SPDX-FileCopyrightText: Copyright The Lima Authors
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
package imgutil
5+
6+
import (
7+
"encoding/json"
8+
"os"
9+
)
10+
11+
// ImageDiskManager defines the common operations for disk image utilities.
12+
type ImageDiskManager interface {
13+
// CreateDisk creates a new disk image with the specified size.
14+
CreateDisk(disk string, size int) error
15+
16+
// ResizeDisk resizes an existing disk image to the specified size.
17+
ResizeDisk(disk string, size int) error
18+
19+
// ConvertToRaw converts a disk image to raw format.
20+
ConvertToRaw(source, dest string, size *int64, allowSourceWithBackingFile bool) error
21+
22+
// MakeSparse makes a file sparse, starting from the specified offset.
23+
MakeSparse(f *os.File, offset int64) error
24+
}
25+
26+
type InfoChild struct {
27+
Name string `json:"name,omitempty"` // since QEMU 8.0
28+
Info Info `json:"info,omitempty"` // since QEMU 8.0
29+
}
30+
31+
type InfoFormatSpecific struct {
32+
Type string `json:"type,omitempty"` // since QEMU 1.7
33+
Data json.RawMessage `json:"data,omitempty"` // since QEMU 1.7
34+
}
35+
36+
func (sp *InfoFormatSpecific) Qcow2() *InfoFormatSpecificDataQcow2 {
37+
if sp.Type != "qcow2" {
38+
return nil
39+
}
40+
var x InfoFormatSpecificDataQcow2
41+
if err := json.Unmarshal(sp.Data, &x); err != nil {
42+
panic(err)
43+
}
44+
return &x
45+
}
46+
47+
func (sp *InfoFormatSpecific) Vmdk() *InfoFormatSpecificDataVmdk {
48+
if sp.Type != "vmdk" {
49+
return nil
50+
}
51+
var x InfoFormatSpecificDataVmdk
52+
if err := json.Unmarshal(sp.Data, &x); err != nil {
53+
panic(err)
54+
}
55+
return &x
56+
}
57+
58+
type InfoFormatSpecificDataQcow2 struct {
59+
Compat string `json:"compat,omitempty"` // since QEMU 1.7
60+
LazyRefcounts bool `json:"lazy-refcounts,omitempty"` // since QEMU 1.7
61+
Corrupt bool `json:"corrupt,omitempty"` // since QEMU 2.2
62+
RefcountBits int `json:"refcount-bits,omitempty"` // since QEMU 2.3
63+
CompressionType string `json:"compression-type,omitempty"` // since QEMU 5.1
64+
ExtendedL2 bool `json:"extended-l2,omitempty"` // since QEMU 5.2
65+
}
66+
67+
type InfoFormatSpecificDataVmdk struct {
68+
CreateType string `json:"create-type,omitempty"` // since QEMU 1.7
69+
CID int `json:"cid,omitempty"` // since QEMU 1.7
70+
ParentCID int `json:"parent-cid,omitempty"` // since QEMU 1.7
71+
Extents []InfoFormatSpecificDataVmdkExtent `json:"extents,omitempty"` // since QEMU 1.7
72+
}
73+
74+
type InfoFormatSpecificDataVmdkExtent struct {
75+
Filename string `json:"filename,omitempty"` // since QEMU 1.7
76+
Format string `json:"format,omitempty"` // since QEMU 1.7
77+
VSize int64 `json:"virtual-size,omitempty"` // since QEMU 1.7
78+
ClusterSize int `json:"cluster-size,omitempty"` // since QEMU 1.7
79+
}
80+
81+
// Info corresponds to the output of `qemu-img info --output=json FILE`.
82+
type Info struct {
83+
Filename string `json:"filename,omitempty"` // since QEMU 1.3
84+
Format string `json:"format,omitempty"` // since QEMU 1.3
85+
VSize int64 `json:"virtual-size,omitempty"` // since QEMU 1.3
86+
ActualSize int64 `json:"actual-size,omitempty"` // since QEMU 1.3
87+
DirtyFlag bool `json:"dirty-flag,omitempty"` // since QEMU 5.2
88+
ClusterSize int `json:"cluster-size,omitempty"` // since QEMU 1.3
89+
BackingFilename string `json:"backing-filename,omitempty"` // since QEMU 1.3
90+
FullBackingFilename string `json:"full-backing-filename,omitempty"` // since QEMU 1.3
91+
BackingFilenameFormat string `json:"backing-filename-format,omitempty"` // since QEMU 1.3
92+
FormatSpecific *InfoFormatSpecific `json:"format-specific,omitempty"` // since QEMU 1.7
93+
Children []InfoChild `json:"children,omitempty"` // since QEMU 8.0
94+
}

pkg/nativeimgutil/fuzz_test.go renamed to pkg/imgutil/nativeimgutil/fuzz_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,6 @@ func FuzzConvertToRaw(f *testing.F) {
1717
destPath := filepath.Join(t.TempDir(), "dest.img")
1818
err := os.WriteFile(srcPath, imgData, 0o600)
1919
assert.NilError(t, err)
20-
_ = ConvertToRaw(srcPath, destPath, &size, withBacking)
20+
_ = convertToRaw(srcPath, destPath, &size, withBacking)
2121
})
2222
}

pkg/nativeimgutil/nativeimgutil.go renamed to pkg/imgutil/nativeimgutil/nativeimgutil.go

Lines changed: 18 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -28,14 +28,17 @@ import (
2828
// aligned to 512 bytes.
2929
const sectorSize = 512
3030

31-
// RoundUp rounds size up to sectorSize.
32-
func RoundUp(size int) int {
31+
// NativeImageUtil is the native implementation of the imgutil Interface.
32+
type NativeImageUtil struct{}
33+
34+
// roundUp rounds size up to sectorSize.
35+
func roundUp(size int) int {
3336
sectors := (size + sectorSize - 1) / sectorSize
3437
return sectors * sectorSize
3538
}
3639

37-
// CreateRawDisk creates an empty raw data disk.
38-
func CreateRawDisk(disk string, size int) error {
40+
// createRawDisk creates an empty raw data disk.
41+
func createRawDisk(disk string, size int) error {
3942
if _, err := os.Stat(disk); err == nil || !errors.Is(err, fs.ErrNotExist) {
4043
return err
4144
}
@@ -44,20 +47,20 @@ func CreateRawDisk(disk string, size int) error {
4447
return err
4548
}
4649
defer f.Close()
47-
roundedSize := RoundUp(size)
50+
roundedSize := roundUp(size)
4851
return f.Truncate(int64(roundedSize))
4952
}
5053

51-
// ResizeRawDisk resizes a raw data disk.
52-
func ResizeRawDisk(disk string, size int) error {
53-
roundedSize := RoundUp(size)
54+
// resizeRawDisk resizes a raw data disk.
55+
func resizeRawDisk(disk string, size int) error {
56+
roundedSize := roundUp(size)
5457
return os.Truncate(disk, int64(roundedSize))
5558
}
5659

57-
// ConvertToRaw converts a source disk into a raw disk.
60+
// convertToRaw converts a source disk into a raw disk.
5861
// source and dest may be same.
59-
// ConvertToRaw is a NOP if source == dest, and no resizing is needed.
60-
func ConvertToRaw(source, dest string, size *int64, allowSourceWithBackingFile bool) error {
62+
// convertToRaw is a NOP if source == dest, and no resizing is needed.
63+
func convertToRaw(source, dest string, size *int64, allowSourceWithBackingFile bool) error {
6164
srcF, err := os.Open(source)
6265
if err != nil {
6366
return err
@@ -106,7 +109,7 @@ func ConvertToRaw(source, dest string, size *int64, allowSourceWithBackingFile b
106109
// Truncating before copy eliminates the seeks during copy and provide a
107110
// hint to the file system that may minimize allocations and fragmentation
108111
// of the file.
109-
if err := MakeSparse(destTmpF, srcImg.Size()); err != nil {
112+
if err := makeSparse(destTmpF, srcImg.Size()); err != nil {
110113
return err
111114
}
112115

@@ -125,7 +128,7 @@ func ConvertToRaw(source, dest string, size *int64, allowSourceWithBackingFile b
125128
// Resize
126129
if size != nil {
127130
logrus.Infof("Expanding to %s", units.BytesSize(float64(*size)))
128-
if err = MakeSparse(destTmpF, *size); err != nil {
131+
if err = makeSparse(destTmpF, *size); err != nil {
129132
return err
130133
}
131134
}
@@ -153,7 +156,7 @@ func convertRawToRaw(source, dest string, size *int64) error {
153156
if err != nil {
154157
return err
155158
}
156-
if err = MakeSparse(destF, *size); err != nil {
159+
if err = makeSparse(destF, *size); err != nil {
157160
_ = destF.Close()
158161
return err
159162
}
@@ -162,7 +165,7 @@ func convertRawToRaw(source, dest string, size *int64) error {
162165
return nil
163166
}
164167

165-
func MakeSparse(f *os.File, n int64) error {
168+
func makeSparse(f *os.File, n int64) error {
166169
if _, err := f.Seek(n, io.SeekStart); err != nil {
167170
return err
168171
}

pkg/nativeimgutil/nativeimgutil_test.go renamed to pkg/imgutil/nativeimgutil/nativeimgutil_test.go

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ func TestRoundUp(t *testing.T) {
2525
{123456789, 123457024},
2626
}
2727
for _, tc := range tests {
28-
if RoundUp(tc.Size) != tc.Rounded {
28+
if roundUp(tc.Size) != tc.Rounded {
2929
t.Errorf("expected %d, got %d", tc.Rounded, tc.Size)
3030
}
3131
}
@@ -63,15 +63,15 @@ func TestConvertToRaw(t *testing.T) {
6363
t.Run("qcow without backing file", func(t *testing.T) {
6464
resultImage := filepath.Join(tmpDir, strings.ReplaceAll(strings.ReplaceAll(t.Name(), string(os.PathSeparator), "_"), "/", "_"))
6565

66-
err = ConvertToRaw(qcowImage.Name(), resultImage, nil, false)
66+
err = convertToRaw(qcowImage.Name(), resultImage, nil, false)
6767
assert.NilError(t, err)
6868
assertFileEquals(t, rawImage.Name(), resultImage)
6969
})
7070

7171
t.Run("qcow with backing file", func(t *testing.T) {
7272
resultImage := filepath.Join(tmpDir, strings.ReplaceAll(strings.ReplaceAll(t.Name(), string(os.PathSeparator), "_"), "/", "_"))
7373

74-
err = ConvertToRaw(qcowImage.Name(), resultImage, nil, true)
74+
err = convertToRaw(qcowImage.Name(), resultImage, nil, true)
7575
assert.NilError(t, err)
7676
assertFileEquals(t, rawImage.Name(), resultImage)
7777
})
@@ -80,15 +80,15 @@ func TestConvertToRaw(t *testing.T) {
8080
resultImage := filepath.Join(tmpDir, strings.ReplaceAll(strings.ReplaceAll(t.Name(), string(os.PathSeparator), "_"), "/", "_"))
8181

8282
size := int64(2_097_152) // 2mb
83-
err = ConvertToRaw(qcowImage.Name(), resultImage, &size, false)
83+
err = convertToRaw(qcowImage.Name(), resultImage, &size, false)
8484
assert.NilError(t, err)
8585
assertFileEquals(t, rawImageExtended.Name(), resultImage)
8686
})
8787

8888
t.Run("raw", func(t *testing.T) {
8989
resultImage := filepath.Join(tmpDir, strings.ReplaceAll(strings.ReplaceAll(t.Name(), string(os.PathSeparator), "_"), "/", "_"))
9090

91-
err = ConvertToRaw(rawImage.Name(), resultImage, nil, false)
91+
err = convertToRaw(rawImage.Name(), resultImage, nil, false)
9292
assert.NilError(t, err)
9393
assertFileEquals(t, rawImage.Name(), resultImage)
9494
})

pkg/imgutil/nativeimgutil/util.go

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
// SPDX-FileCopyrightText: Copyright The Lima Authors
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
package nativeimgutil
5+
6+
import (
7+
"os"
8+
)
9+
10+
// NewNativeImageUtil returns a new NativeImageUtil instance.
11+
func NewNativeImageUtil() *NativeImageUtil {
12+
return &NativeImageUtil{}
13+
}
14+
15+
// CreateDisk creates a new raw disk image with the specified size.
16+
func (n *NativeImageUtil) CreateDisk(disk string, size int) error {
17+
return createRawDisk(disk, size)
18+
}
19+
20+
// ResizeDisk resizes an existing raw disk image to the specified size.
21+
func (n *NativeImageUtil) ResizeDisk(disk string, size int) error {
22+
return resizeRawDisk(disk, size)
23+
}
24+
25+
// ConvertToRaw converts a disk image to raw format.
26+
func (n *NativeImageUtil) ConvertToRaw(source, dest string, size *int64, allowSourceWithBackingFile bool) error {
27+
return convertToRaw(source, dest, size, allowSourceWithBackingFile)
28+
}
29+
30+
func (n *NativeImageUtil) MakeSparse(f *os.File, offset int64) error {
31+
return makeSparse(f, offset)
32+
}
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
// SPDX-FileCopyrightText: Copyright The Lima Authors
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
package proxyimgutil
5+
6+
import (
7+
"errors"
8+
"os"
9+
"os/exec"
10+
11+
"github.com/lima-vm/lima/pkg/imgutil"
12+
"github.com/lima-vm/lima/pkg/imgutil/nativeimgutil"
13+
"github.com/lima-vm/lima/pkg/imgutil/qemuimgutil"
14+
)
15+
16+
type ProxyImageDiskManager struct {
17+
qemu imgutil.ImageDiskManager
18+
native imgutil.ImageDiskManager
19+
}
20+
21+
func NewProxyImageUtil() (imgutil.ImageDiskManager, *qemuimgutil.QemuInfoProvider) {
22+
return &ProxyImageDiskManager{
23+
qemu: qemuimgutil.NewQemuImageUtil(),
24+
native: nativeimgutil.NewNativeImageUtil(),
25+
}, qemuimgutil.NewQemuInfoProvider()
26+
}
27+
28+
func (p *ProxyImageDiskManager) CreateDisk(disk string, size int) error {
29+
if err := p.qemu.CreateDisk(disk, size); err != nil {
30+
if errors.Is(err, exec.ErrNotFound) {
31+
return p.native.CreateDisk(disk, size)
32+
}
33+
return err
34+
}
35+
return nil
36+
}
37+
38+
func (p *ProxyImageDiskManager) ResizeDisk(disk string, size int) error {
39+
if err := p.qemu.ResizeDisk(disk, size); err != nil {
40+
if errors.Is(err, exec.ErrNotFound) {
41+
return p.native.ResizeDisk(disk, size)
42+
}
43+
return err
44+
}
45+
return nil
46+
}
47+
48+
func (p *ProxyImageDiskManager) ConvertToRaw(source, dest string, size *int64, allowSourceWithBackingFile bool) error {
49+
if err := p.qemu.ConvertToRaw(source, dest, size, allowSourceWithBackingFile); err != nil {
50+
if errors.Is(err, exec.ErrNotFound) {
51+
return p.native.ConvertToRaw(source, dest, size, allowSourceWithBackingFile)
52+
}
53+
return err
54+
}
55+
return nil
56+
}
57+
58+
func (p *ProxyImageDiskManager) MakeSparse(f *os.File, offset int64) error {
59+
if err := p.qemu.MakeSparse(f, offset); err != nil {
60+
if errors.Is(err, exec.ErrNotFound) {
61+
return p.native.MakeSparse(f, offset)
62+
}
63+
return err
64+
}
65+
return nil
66+
}

0 commit comments

Comments
 (0)