Skip to content

Commit e5c198b

Browse files
authored
Merge pull request #1596 from AkihiroSuda/vz-make-qemu-img-optional
vz: drop dependency on qemu-img
2 parents 85ed52a + 20c2224 commit e5c198b

File tree

9 files changed

+240
-71
lines changed

9 files changed

+240
-71
lines changed

cmd/limactl/stop.go

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -133,18 +133,21 @@ func stopInstanceForcibly(inst *store.Instance) {
133133
logrus.Info("The host agent process seems already stopped")
134134
}
135135

136-
logrus.Infof("Removing *.pid *.sock under %q", inst.Dir)
136+
suffixesToBeRemoved := []string{".pid", ".sock", ".tmp"}
137+
logrus.Infof("Removing %s under %q", inst.Dir, strings.ReplaceAll(strings.Join(suffixesToBeRemoved, " "), ".", "*."))
137138
fi, err := os.ReadDir(inst.Dir)
138139
if err != nil {
139140
logrus.Error(err)
140141
return
141142
}
142143
for _, f := range fi {
143144
path := filepath.Join(inst.Dir, f.Name())
144-
if strings.HasSuffix(path, ".pid") || strings.HasSuffix(path, ".sock") {
145-
logrus.Infof("Removing %q", path)
146-
if err := os.Remove(path); err != nil {
147-
logrus.Error(err)
145+
for _, suffix := range suffixesToBeRemoved {
146+
if strings.HasSuffix(path, suffix) {
147+
logrus.Infof("Removing %q", path)
148+
if err := os.Remove(path); err != nil {
149+
logrus.Error(err)
150+
}
148151
}
149152
}
150153
}

go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ require (
2323
github.com/google/go-cmp v0.5.9
2424
github.com/gorilla/mux v1.8.0
2525
github.com/hashicorp/go-multierror v1.1.1
26+
github.com/lima-vm/go-qcow2reader v0.1.1
2627
github.com/lima-vm/sshocker v0.3.2
2728
github.com/lithammer/dedent v1.1.0
2829
github.com/mattn/go-isatty v0.0.19

go.sum

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,8 @@ github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
177177
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
178178
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
179179
github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y=
180+
github.com/lima-vm/go-qcow2reader v0.1.1 h1:UegxTAU4usoSzKLDqhbaNmmV4Ec4qhQO11QYeLLy7Jc=
181+
github.com/lima-vm/go-qcow2reader v0.1.1/go.mod h1:e3p29BzLT8hNh4jbLezdFAHU4eBijf0bm5GilStCRKE=
180182
github.com/lima-vm/sshocker v0.3.2 h1:o0WqVzcpt6mzVCuqtS3N3O8kwTx6X4SLr4h7YaRISuE=
181183
github.com/lima-vm/sshocker v0.3.2/go.mod h1:9SWN6wob210VM6oJkkzvWQOlHSp/rQLB+0fSEc92zig=
182184
github.com/linuxkit/virtsock v0.0.0-20220523201153-1a23e78aa7a2 h1:DZMFueDbfz6PNc1GwDRA8+6lBx1TB9UnxDQliCqR73Y=

pkg/nativeimgutil/nativeimgutil.go

Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
// Package nativeimgutil provides image utilities that do not depend on `qemu-img` binary.
2+
package nativeimgutil
3+
4+
import (
5+
"bytes"
6+
"errors"
7+
"fmt"
8+
"io"
9+
"os"
10+
"path/filepath"
11+
12+
"github.com/containerd/continuity/fs"
13+
"github.com/lima-vm/go-qcow2reader"
14+
"github.com/lima-vm/go-qcow2reader/image/qcow2"
15+
"github.com/lima-vm/go-qcow2reader/image/raw"
16+
"github.com/lima-vm/lima/pkg/osutil"
17+
"github.com/sirupsen/logrus"
18+
)
19+
20+
// ConvertToRaw converts a source disk into a raw disk.
21+
// source and dest may be same.
22+
// ConvertToRaw is a NOP if source == dest, and no resizing is needed.
23+
func ConvertToRaw(source, dest string, size *int64, allowSourceWithBackingFile bool) error {
24+
srcF, err := os.Open(source)
25+
if err != nil {
26+
return err
27+
}
28+
defer srcF.Close()
29+
srcImg, err := qcow2reader.Open(srcF)
30+
if err != nil {
31+
return fmt.Errorf("failed to detect the format of %q: %w", source, err)
32+
}
33+
if size != nil && *size < srcImg.Size() {
34+
return fmt.Errorf("specified size %d is smaller than the original image size (%d) of %q", *size, srcImg.Size(), source)
35+
}
36+
switch t := srcImg.Type(); t {
37+
case raw.Type:
38+
if err = srcF.Close(); err != nil {
39+
return err
40+
}
41+
return convertRawToRaw(source, dest, size)
42+
case qcow2.Type:
43+
if !allowSourceWithBackingFile {
44+
q, ok := srcImg.(*qcow2.Qcow2)
45+
if !ok {
46+
return fmt.Errorf("unexpected qcow2 image %T", srcImg)
47+
}
48+
if q.BackingFile != "" {
49+
return fmt.Errorf("qcow2 image %q has an unexpected backing file: %q", source, q.BackingFile)
50+
}
51+
}
52+
default:
53+
logrus.Warnf("image %q has an unexpected format: %q", source, t)
54+
}
55+
if err = srcImg.Readable(); err != nil {
56+
return fmt.Errorf("image %q is not readable: %w", source, err)
57+
}
58+
59+
// Create a tmp file because source and dest can be same.
60+
destTmpF, err := os.CreateTemp(filepath.Dir(dest), filepath.Base(dest)+".lima-*.tmp")
61+
if err != nil {
62+
return err
63+
}
64+
destTmp := destTmpF.Name()
65+
defer os.RemoveAll(destTmp)
66+
defer destTmpF.Close()
67+
68+
// Copy
69+
srcImgR := io.NewSectionReader(srcImg, 0, srcImg.Size())
70+
const bufSize = 1024 * 1024
71+
if copied, err := copySparse(destTmpF, srcImgR, bufSize); err != nil {
72+
return fmt.Errorf("failed to call copySparse(), bufSize=%d, copied=%d: %w", bufSize, copied, err)
73+
}
74+
75+
// Resize
76+
if size != nil {
77+
if err = MakeSparse(destTmpF, *size); err != nil {
78+
return err
79+
}
80+
}
81+
if err = destTmpF.Close(); err != nil {
82+
return err
83+
}
84+
85+
// Rename destTmp into dest
86+
if err = os.RemoveAll(dest); err != nil {
87+
return err
88+
}
89+
return os.Rename(destTmp, dest)
90+
}
91+
92+
func convertRawToRaw(source, dest string, size *int64) error {
93+
if source != dest {
94+
// continuity attempts clonefile
95+
if err := fs.CopyFile(dest, source); err != nil {
96+
return fmt.Errorf("failed to copy %q into %q: %w", source, dest, err)
97+
}
98+
}
99+
if size != nil {
100+
destF, err := os.OpenFile(dest, os.O_RDWR, 0644)
101+
if err != nil {
102+
return err
103+
}
104+
if err = MakeSparse(destF, *size); err != nil {
105+
_ = destF.Close()
106+
return err
107+
}
108+
return destF.Close()
109+
}
110+
return nil
111+
}
112+
113+
func copySparse(w *os.File, r io.Reader, bufSize int64) (int64, error) {
114+
var n int64
115+
zeroBuf := make([]byte, bufSize)
116+
buf := make([]byte, bufSize)
117+
var eof bool
118+
for !eof {
119+
rN, rErr := r.Read(buf)
120+
if rErr != nil {
121+
eof = errors.Is(rErr, io.EOF)
122+
if !eof {
123+
return n, fmt.Errorf("failed to read: %w", rErr)
124+
}
125+
}
126+
// TODO: qcow2reader should have a method to notify whether buf is zero
127+
if bytes.Equal(buf, zeroBuf) {
128+
if _, sErr := w.Seek(int64(rN), io.SeekCurrent); sErr != nil {
129+
return n, fmt.Errorf("failed seek: %w", sErr)
130+
}
131+
// no need to ftruncate here
132+
n += int64(rN)
133+
} else {
134+
wN, wErr := w.Write(buf)
135+
if wN > 0 {
136+
n += int64(wN)
137+
}
138+
if wErr != nil {
139+
return n, fmt.Errorf("failed to read: %w", wErr)
140+
}
141+
if wN != rN {
142+
return n, fmt.Errorf("read %d, but wrote %d bytes", rN, wN)
143+
}
144+
}
145+
}
146+
return n, nil
147+
}
148+
149+
func MakeSparse(f *os.File, n int64) error {
150+
if _, err := f.Seek(n, io.SeekStart); err != nil {
151+
return err
152+
}
153+
return osutil.Ftruncate(int(f.Fd()), n)
154+
}

pkg/osutil/osutil_unix.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
//go:build !windows
2+
3+
package osutil
4+
5+
import "golang.org/x/sys/unix"
6+
7+
func Ftruncate(fd int, length int64) (err error) {
8+
return unix.Ftruncate(fd, length)
9+
}

pkg/osutil/osutil_windows.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,3 +29,7 @@ type Signal int
2929
func SysKill(pid int, sig Signal) error {
3030
return fmt.Errorf("unimplemented")
3131
}
32+
33+
func Ftruncate(fd int, length int64) (err error) {
34+
return fmt.Errorf("unimplemented")
35+
}

pkg/store/disk.go

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"os"
88
"path/filepath"
99

10+
"github.com/lima-vm/go-qcow2reader"
1011
"github.com/lima-vm/lima/pkg/qemu/imgutil"
1112
"github.com/lima-vm/lima/pkg/store/filenames"
1213
)
@@ -36,11 +37,10 @@ func InspectDisk(diskName string) (*Disk, error) {
3637
return nil, err
3738
}
3839

39-
info, err := imgutil.GetInfo(dataDisk)
40+
disk.Size, err = inspectDiskSize(dataDisk)
4041
if err != nil {
4142
return nil, err
4243
}
43-
disk.Size = info.VSize
4444

4545
instDir, err := os.Readlink(filepath.Join(diskDir, filenames.InUseBy))
4646
if err != nil {
@@ -57,6 +57,34 @@ func InspectDisk(diskName string) (*Disk, error) {
5757
return disk, nil
5858
}
5959

60+
// inspectDiskSize attempts to inspect the disk size by itself,
61+
// and falls back to inspectDiskSizeWithQemuImg on an error.
62+
func inspectDiskSize(fName string) (int64, error) {
63+
f, err := os.Open(fName)
64+
if err != nil {
65+
return inspectDiskSizeWithQemuImg(fName)
66+
}
67+
defer f.Close()
68+
img, err := qcow2reader.Open(f)
69+
if err != nil {
70+
return inspectDiskSizeWithQemuImg(fName)
71+
}
72+
sz := img.Size()
73+
if sz < 0 {
74+
return inspectDiskSizeWithQemuImg(fName)
75+
}
76+
return sz, nil
77+
}
78+
79+
// inspectDiskSizeWithQemuImg invokes `qemu-img` binary to inspect the disk size.
80+
func inspectDiskSizeWithQemuImg(fName string) (int64, error) {
81+
info, err := imgutil.GetInfo(fName)
82+
if err != nil {
83+
return -1, err
84+
}
85+
return info.VSize, nil
86+
}
87+
6088
func (d *Disk) Lock(instanceDir string) error {
6189
inUseBy := filepath.Join(d.Dir, filenames.InUseBy)
6290
return os.Symlink(instanceDir, inUseBy)

pkg/vz/disk.go

Lines changed: 17 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -4,20 +4,18 @@ import (
44
"errors"
55
"fmt"
66
"os"
7-
"os/exec"
87
"path/filepath"
9-
"strconv"
108

119
"github.com/docker/go-units"
1210
"github.com/lima-vm/lima/pkg/driver"
1311
"github.com/lima-vm/lima/pkg/fileutils"
1412
"github.com/lima-vm/lima/pkg/iso9660util"
15-
"github.com/lima-vm/lima/pkg/qemu/imgutil"
13+
"github.com/lima-vm/lima/pkg/nativeimgutil"
1614
"github.com/lima-vm/lima/pkg/store/filenames"
15+
"github.com/sirupsen/logrus"
1716
)
1817

1918
func EnsureDisk(driver *driver.BaseDriver) error {
20-
2119
diffDisk := filepath.Join(driver.Instance.Dir, filenames.DiffDisk)
2220
if _, err := os.Stat(diffDisk); err == nil || !errors.Is(err, os.ErrNotExist) {
2321
// disk is already ensured
@@ -44,32 +42,25 @@ func EnsureDisk(driver *driver.BaseDriver) error {
4442
if diskSize == 0 {
4543
return nil
4644
}
47-
//TODO - Break qemu dependency
4845
isBaseDiskISO, err := iso9660util.IsISO9660(baseDisk)
4946
if err != nil {
5047
return err
5148
}
52-
baseDiskInfo, err := imgutil.GetInfo(baseDisk)
53-
if err != nil {
54-
return fmt.Errorf("failed to get the information of base disk %q: %w", baseDisk, err)
55-
}
56-
if err = imgutil.AcceptableAsBasedisk(baseDiskInfo); err != nil {
57-
return fmt.Errorf("file %q is not acceptable as the base disk: %w", baseDisk, err)
58-
}
59-
if baseDiskInfo.Format == "" {
60-
return fmt.Errorf("failed to inspect the format of %q", baseDisk)
61-
}
62-
args := []string{"create", "-f", "qcow2"}
63-
if !isBaseDiskISO {
64-
args = append(args, "-F", baseDiskInfo.Format, "-b", baseDisk)
65-
}
66-
args = append(args, diffDisk, strconv.Itoa(int(diskSize)))
67-
cmd := exec.Command("qemu-img", args...)
68-
if out, err := cmd.CombinedOutput(); err != nil {
69-
return fmt.Errorf("failed to run %v: %q: %w", cmd.Args, string(out), err)
49+
if isBaseDiskISO {
50+
// Create an empty data volume (sparse)
51+
diffDiskF, err := os.Create(diffDisk)
52+
if err != nil {
53+
return err
54+
}
55+
if err = nativeimgutil.MakeSparse(diffDiskF, diskSize); err != nil {
56+
diffDiskF.Close()
57+
return err
58+
}
59+
return diffDiskF.Close()
7060
}
71-
if err = imgutil.ConvertToRaw(diffDisk, diffDisk); err != nil {
72-
return fmt.Errorf("cannot convert qcow2 to raw for vz driver")
61+
logrus.Infof("Converting %q to a raw disk %q (size=%d)", baseDisk, diffDisk, diskSize)
62+
if err = nativeimgutil.ConvertToRaw(baseDisk, diffDisk, &diskSize, false); err != nil {
63+
return fmt.Errorf("failed to convert %q to a raw disk %q (size=%d): %w", baseDisk, diffDisk, diskSize, err)
7364
}
74-
return nil
65+
return err
7566
}

0 commit comments

Comments
 (0)