Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 16 additions & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ jobs:
steps:
- uses: actions/setup-go@v6
with:
go-version: 1.22.x
go-version: 1.24.x
- uses: actions/checkout@v5
with:
fetch-depth: 1
Expand Down Expand Up @@ -58,3 +58,18 @@ jobs:
run: hack/compare-with-qemu-img.sh test-images/debian-11-genericcloud-amd64-20230501-1367.zstd.qcow2
- name: "Test debian-11-genericcloud-amd64-20230501-1367.ext_l2.qcow2"
run: hack/compare-with-qemu-img.sh test-images/debian-11-genericcloud-amd64-20230501-1367.ext_l2.qcow2

macos-unit-tests:
runs-on: macos-latest
timeout-minutes: 30
steps:
- uses: actions/setup-go@v6
with:
go-version: 1.24.x
- uses: actions/checkout@v5
with:
fetch-depth: 1
- name: Install qemu-img as a test dependency
run: brew install qemu
- name: Unit tests
run: go test -v ./...
2 changes: 1 addition & 1 deletion cmd/go-qcow2reader-example/go.mod
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
module github.com/lima-vm/go-qcow2reader/cmd/go-qcow2reader-example

go 1.22
go 1.24

require (
github.com/cheggaaa/pb/v3 v3.1.5
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
module github.com/lima-vm/go-qcow2reader

go 1.22
go 1.24
25 changes: 16 additions & 9 deletions image/asif/asif.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,26 +17,33 @@ func Open(ra io.ReaderAt) (*Asif, error) {
return nil, err
}
// Block count seems to be stored at offset 48 as a big-endian uint64.
buf := make([]byte, 8)
if _, err := ra.ReadAt(buf, 48); err != nil {
bufToSectorCount := make([]byte, 8)
if _, err := ra.ReadAt(bufToSectorCount, 48); err != nil {
return nil, err
}
blocks := binary.BigEndian.Uint64(buf)
sectorCount := binary.BigEndian.Uint64(bufToSectorCount)
// Block size
// ref: https://github.com/fox-it/dissect.hypervisor/blob/0c8976613a369923e69022304b2f0ed587e997e2/dissect/hypervisor/disk/c_asif.py#L19
bufToBlockSize := make([]byte, 2)
if _, err := ra.ReadAt(bufToBlockSize, 68); err != nil {
return nil, err
}
blockSize := binary.BigEndian.Uint16(bufToBlockSize)
return &Asif{
// Block size is 512 bytes.
// It might be stored in the header, but for now we assume it's 512 bytes.
size: int64(blocks) * 512,
Stub: *stub,
sectorCount: sectorCount,
blockSize: blockSize,
Stub: *stub,
}, nil
}

type Asif struct {
size int64
sectorCount uint64
blockSize uint16
stub.Stub
}

var _ image.Image = (*Asif)(nil)

func (a *Asif) Size() int64 {
return a.size
return int64(a.sectorCount) * int64(a.blockSize)
}
76 changes: 76 additions & 0 deletions image/asif/asif_darwin_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
package asif

import (
"os"
"os/exec"
"path/filepath"
"regexp"
"strconv"
"strings"
"testing"
)

func TestOpenASIF(t *testing.T) {
// Check macOS version
if productVersion, err := exec.CommandContext(t.Context(), "sw_vers", "--productVersion").Output(); err != nil {
t.Fatalf("failed to get product version: %v", err)
} else if majorVersion, err := strconv.ParseInt(strings.Split(string(productVersion), ".")[0], 10, 64); err != nil {
t.Fatalf("failed to parse product version: %v", err)
} else if majorVersion < 26 {
t.Skipf("skipping test on macOS version < 26: %s", productVersion)
}

tempDir := t.TempDir()
asifFilePath := filepath.Join(tempDir, "diffdisk.asif")

// Create a blank ASIF disk image using diskutil
if err := exec.CommandContext(t.Context(), "diskutil", "image", "create", "blank", "--fs", "none", "--format", "ASIF", "--size", "100GiB", asifFilePath).Run(); err != nil {
t.Fatalf("failed to create disk image: %v", err)
}

// Get disk image info using diskutil
var sectorCount uint64
var totalBytes int64
out, err := exec.CommandContext(t.Context(), "diskutil", "image", "info", asifFilePath).Output()
if err != nil {
t.Fatalf("failed to get disk image info: %v", err)
}

// Parse sector count from the output
reSectorCount := regexp.MustCompile(`Sector Count: (\d+)`)
if sectorCountMatch := reSectorCount.FindStringSubmatch(string(out)); len(sectorCountMatch) != 2 {
t.Fatalf("failed to parse sector count from disk image info")
} else if parsedSectorCount, err := strconv.ParseUint(sectorCountMatch[1], 10, 64); err != nil {
t.Fatalf("failed to parse sector count: %v", err)
} else {
sectorCount = parsedSectorCount
}

// Block size is not included in the output of `diskutil image info`

// Parse total bytes from the output
reTotalBytes := regexp.MustCompile(`Total Bytes: (\d+)`)
if totalBytesMatch := reTotalBytes.FindStringSubmatch(string(out)); len(totalBytesMatch) != 2 {
t.Fatalf("failed to parse block size from disk image info")
} else if parsedTotalBytes, err := strconv.ParseInt(totalBytesMatch[1], 10, 64); err != nil {
t.Fatalf("failed to parse block size: %v", err)
} else {
totalBytes = parsedTotalBytes
}

// Open the ASIF image
f, err := os.Open(asifFilePath)
if err != nil {
t.Fatalf("failed to open ASIF file: %v", err)
}
defer f.Close() //nolint:errcheck

// Open ASIF image and verify properties
if img, err := Open(f); err != nil {
t.Fatalf("failed to open ASIF image: %v", err)
} else if img.sectorCount != sectorCount {
t.Fatalf("unexpected sector count: got %d, want %d", img.sectorCount, sectorCount)
} else if img.Size() != totalBytes {
t.Fatalf("unexpected size: got %d, want %d", img.Size(), totalBytes)
}
}
Loading