README.md
Rendering markdown...
diff --git a/extractor/filesystem/embeddedfs/vmdk/vmdk.go b/extractor/filesystem/embeddedfs/vmdk/vmdk.go
index 1998fa86..214b7e3b 100644
--- a/extractor/filesystem/embeddedfs/vmdk/vmdk.go
+++ b/extractor/filesystem/embeddedfs/vmdk/vmdk.go
@@ -20,35 +20,38 @@ import (
"compress/zlib"
"context"
"encoding/binary"
- "errors"
"fmt"
"io"
+ "io/fs"
"os"
- "path/filepath"
+ "path"
"strings"
"sync"
+ "time"
+ "github.com/diskfs/go-diskfs"
+ diskfsfilesystem "github.com/diskfs/go-diskfs/filesystem"
+ "github.com/diskfs/go-diskfs/filesystem/fat32"
"github.com/google/osv-scalibr/extractor/filesystem"
- "github.com/google/osv-scalibr/extractor/filesystem/embeddedfs/common"
+ scalibrfs "github.com/google/osv-scalibr/fs"
"github.com/google/osv-scalibr/inventory"
"github.com/google/osv-scalibr/plugin"
+ "github.com/masahiro331/go-ext4-filesystem/ext4"
)
const (
// Name is the unique identifier for the vmdk extractor.
- Name = "embeddedfs/vmdk"
- // SectorSize is the default sector size (512 bytes).
- SectorSize = 512
- // SparseMagic is always 'KDMV'.
- SparseMagic = 0x564d444b
- // GDAtEnd indicates that the Grain Directory is stored in the footer at the end of the VMDK file.
- GDAtEnd = 0xFFFFFFFFFFFFFFFF
- // DefaultGrainSec is default sectors if header invalid (64KiB).
- DefaultGrainSec = 128
+ Name = "embeddedfs/vmdk"
+ SectorSize = 512
+ SPARSE_MAGIC = 0x564d444b // 'KDMV'
+ GDAtEnd = 0xFFFFFFFFFFFFFFFF
+ DefaultGrainSec = 128 // sectors if header invalid (64KiB)
+ defaultPageSize = 1024 * 1024
+ defaultCacheSize = 100 * 1024 * 1024
)
-// sparseExtentHeader defines the VMDK sparse extent header structure.
-type sparseExtentHeader struct {
+// SparseExtentHeader defines the VMDK sparse extent header structure.
+type SparseExtentHeader struct {
MagicNumber uint32
Version uint32
Flags uint32
@@ -57,8 +60,8 @@ type sparseExtentHeader struct {
DescriptorOffset uint64
DescriptorSize uint64
NumGTEsPerGT uint32
- RGDOffset uint64
- GDOffset uint64
+ RgdOffset uint64
+ GdOffset uint64
OverHead uint64
UncleanShutdown byte
SingleEndLineChar byte
@@ -69,8 +72,8 @@ type sparseExtentHeader struct {
Pad [433]byte
}
-// gdgtInfo holds GD/GT allocation information.
-type gdgtInfo struct {
+// GDGTInfo holds GD/GT allocation information.
+type GDGTInfo struct {
GTEs uint64
GTs uint32
GDsectors uint32
@@ -81,7 +84,7 @@ type gdgtInfo struct {
// Extractor implements the filesystem.Extractor interface for vmdk.
type Extractor struct{}
-// New returns a new VMDK extractor.
+// New returns a new ova extractor.
func New() filesystem.Extractor {
return &Extractor{}
}
@@ -103,7 +106,13 @@ func (e *Extractor) Version() int {
// Requirements returns the requirements for the extractor.
func (e *Extractor) Requirements() *plugin.Capabilities {
- return &plugin.Capabilities{}
+ return &plugin.Capabilities{
+ OS: plugin.OSAny,
+ Network: plugin.NetworkAny,
+ DirectFS: true, // Requires direct filesystem access for GetRealPath
+ RunningSystem: false,
+ ExtractFromDirs: false,
+ }
}
// FileRequired checks if the file is a .vmdk file based on its extension.
@@ -112,52 +121,118 @@ func (e *Extractor) FileRequired(api filesystem.FileAPI) bool {
return strings.HasSuffix(strings.ToLower(path), ".vmdk")
}
-// Extract returns an Inventory with embedded filesystems which contains mount functions for each filesystem in the .vmdk file.
+// Extract returns an Inventory with a DiskImage for each partition in the .vmdk file.
func (e *Extractor) Extract(ctx context.Context, input *filesystem.ScanInput) (inventory.Inventory, error) {
vmdkPath, err := input.GetRealPath()
if err != nil {
- return inventory.Inventory{}, fmt.Errorf("failed to get real path for %s: %w", input.Path, err)
- }
- // If called on a virtual FS, clean up the temporary directory
- if input.Root == "" {
- defer func() {
- dir := filepath.Dir(vmdkPath)
- if err := os.RemoveAll(dir); err != nil {
- fmt.Printf("os.RemoveAll(%q): %v\n", dir, err)
- }
- }()
+ return inventory.Inventory{}, fmt.Errorf("failed to get real path for %s: %v", input.Path, err)
}
// Create a temporary file for the raw disk image
tmpRaw, err := os.CreateTemp("", "scalibr-vmdk-raw-*.raw")
if err != nil {
- return inventory.Inventory{}, fmt.Errorf("failed to create temporary raw file: %w", err)
+ return inventory.Inventory{}, fmt.Errorf("failed to create temporary raw file: %v", err)
}
tmpRawPath := tmpRaw.Name()
// Convert VMDK to raw
if err := convertVMDKToRaw(vmdkPath, tmpRawPath); err != nil {
os.Remove(tmpRawPath)
- return inventory.Inventory{}, fmt.Errorf("failed to convert %s to raw image: %w", vmdkPath, err)
+ return inventory.Inventory{}, fmt.Errorf("failed to convert %s to raw image: %v", vmdkPath, err)
+ }
+
+ // Open the raw disk image with go-diskfs
+ disk, err := diskfs.Open(tmpRawPath, diskfs.WithOpenMode(diskfs.ReadOnly))
+ if err != nil {
+ os.Remove(tmpRawPath)
+ return inventory.Inventory{}, fmt.Errorf("failed to open raw disk image %s: %v", tmpRawPath, err)
}
- // Retrieve all partitions and the associated disk handle from the raw disk image.
- partitionList, disk, err := common.GetDiskPartitions(tmpRawPath)
+ // Get the partition table
+ partitions, err := disk.GetPartitionTable()
if err != nil {
disk.Close()
os.Remove(tmpRawPath)
- return inventory.Inventory{}, err
+ return inventory.Inventory{}, fmt.Errorf("failed to get partition table: %v", err)
+ }
+ partitionList := partitions.GetPartitions()
+ if len(partitionList) == 0 {
+ disk.Close()
+ os.Remove(tmpRawPath)
+ return inventory.Inventory{}, fmt.Errorf("no partitions found in raw disk image")
}
// Create a reference counter for the temporary file
var refCount int32
var refMu sync.Mutex
- // Create an Embedded filesystem for each valid partition
+ // Create a DiskImage for each valid partition
var embeddedFSs []*inventory.EmbeddedFS
for i, p := range partitionList {
partitionIndex := i + 1 // go-diskfs uses 1-based indexing
- getEmbeddedFS := common.NewPartitionEmbeddedFSGetter("vmdk", partitionIndex, p, disk, tmpRawPath, &refMu, &refCount)
+
+ getEmbeddedFS := func(ctx context.Context) (scalibrfs.FS, error) {
+
+ // Open raw image for ext4 parser
+ f, err := os.Open(tmpRawPath)
+ if err != nil {
+ return nil, fmt.Errorf("failed to open raw image %s: %v", tmpRawPath, err)
+ }
+
+ // Get partition offset and size (They are already multiplied by sector size)
+ offset := p.GetStart()
+ size := p.GetSize()
+ section := io.NewSectionReader(f, offset, size)
+ fsType := detectFilesystem(section, 0)
+
+ switch fsType {
+ case "ext4":
+ fs, err := ext4.NewFS(*section, nil)
+ if err != nil {
+ f.Close()
+ return nil, fmt.Errorf("failed to create ext4 filesystem for partition %d: %v", partitionIndex, err)
+ }
+ refMu.Lock()
+ refCount++
+ refMu.Unlock()
+ ext4fs := &ext4FS{
+ fs: fs,
+ file: f,
+ tmpRawPath: tmpRawPath,
+ refCount: &refCount,
+ refMu: &refMu,
+ }
+ return ext4fs, nil
+ case "FAT32":
+ f.Close() // Close the file as GetFilesystem reopens it
+ fs, err := disk.GetFilesystem(partitionIndex)
+ if err != nil {
+ return nil, fmt.Errorf("failed to get filesystem for partition %d: %v", partitionIndex, err)
+ }
+ fat32fs, ok := fs.(*fat32.FileSystem)
+ if !ok {
+ return nil, fmt.Errorf("partition %d is not a FAT32 filesystem", partitionIndex)
+ }
+ f, err = os.Open(tmpRawPath)
+ if err != nil {
+ return nil, fmt.Errorf("failed to reopen raw image %s: %v", tmpRawPath, err)
+ }
+ refMu.Lock()
+ refCount++
+ refMu.Unlock()
+ return &fat32FS{
+ fs: fat32fs,
+ file: f,
+ tmpRawPath: tmpRawPath,
+ refCount: &refCount,
+ refMu: &refMu,
+ }, nil
+ default:
+ f.Close()
+ return nil, fmt.Errorf("unsupported filesystem type %s for partition %d", fsType, partitionIndex)
+ }
+ }
+
embeddedFSs = append(embeddedFSs, &inventory.EmbeddedFS{
Path: fmt.Sprintf("%s:%d", vmdkPath, partitionIndex),
GetEmbeddedFS: getEmbeddedFS,
@@ -166,14 +241,302 @@ func (e *Extractor) Extract(ctx context.Context, input *filesystem.ScanInput) (i
return inventory.Inventory{EmbeddedFSs: embeddedFSs}, nil
}
+// detectFilesystem identifies the filesystem type by magic bytes
+func detectFilesystem(r io.ReaderAt, offset int64) string {
+ buf := make([]byte, 4096)
+ _, err := r.ReadAt(buf, offset)
+ if err != nil {
+ return fmt.Sprintf("read error: %v", err)
+ }
+ // EXT4 magic at offset 0x438
+ if len(buf) > 0x438+2 {
+ if binary.LittleEndian.Uint16(buf[0x438:0x43A]) == 0xEF53 {
+ return "ext4"
+ }
+ }
+ // FAT32: "FAT32 " at offset 0x52
+ if len(buf) > 0x52+8 {
+ if string(buf[0x52:0x52+8]) == "FAT32 " {
+ return "FAT32"
+ }
+ }
+ return "unknown"
+}
+
+// ext4FS wraps go-ext4-filesystem to implement scalibrfs.FS
+type ext4FS struct {
+ fs *ext4.FileSystem
+ file *os.File
+ tmpRawPath string
+ refCount *int32
+ refMu *sync.Mutex
+}
+
+func (e *ext4FS) Open(name string) (fs.File, error) {
+ //fmt.Printf("ext4FS Open(%s)", name)
+ file, err := e.fs.Open(name)
+ if err != nil {
+ //fmt.Printf("ext4FS.Open(%q) failed: %v\n", name, err)
+ return nil, fmt.Errorf("failed to open file %s: %v", name, err)
+ }
+ ext4File, ok := file.(*ext4.File)
+ if !ok {
+ return nil, fmt.Errorf("opened file %s is not an ext4.File", name)
+ }
+ return &ext4FileWrapper{file: ext4File, name: name}, nil
+}
+
+func (e *ext4FS) ReadDir(name string) ([]fs.DirEntry, error) {
+ entries, err := e.fs.ReadDir(name)
+ if err != nil {
+ fmt.Printf("ext4.ReadDir(%q) failed: %v\n", name, err)
+ return nil, fmt.Errorf("failed to read directory %s: %v", name, err)
+ }
+ return entries, nil
+}
+
+func (e *ext4FS) Stat(name string) (fs.FileInfo, error) {
+ if name == "." || name == "" || name == "/" {
+ // Return synthetic FileInfo for root directory
+ return &fileInfo{
+ name: name,
+ isDir: true,
+ modTime: time.Now(),
+ }, nil
+ }
+ info, err := e.fs.Stat(name)
+ if err != nil {
+ fmt.Printf("ext4FS.Stat(%q) failed: %v\n", name, err)
+ return nil, fmt.Errorf("failed to stat %s: %v", name, err)
+ }
+ return info, nil
+}
+
+func (e *ext4FS) Close() error {
+ e.refMu.Lock()
+ defer e.refMu.Unlock()
+ if e.file == nil {
+ return nil // Already closed
+ }
+ *e.refCount--
+ if *e.refCount == 0 {
+ err := e.file.Close()
+ e.file = nil // Prevent double close
+ if err != nil {
+ return fmt.Errorf("failed to close raw file %s: %v", e.tmpRawPath, err)
+ }
+ if err := os.Remove(e.tmpRawPath); err != nil {
+ return fmt.Errorf("failed to remove temporary raw file %s: %v", e.tmpRawPath, err)
+ }
+ }
+ return nil
+}
+
+// ext4FileWrapper wraps ext4.File to implement fs.File and io.ReaderAt
+type ext4FileWrapper struct {
+ file *ext4.File
+ name string
+}
+
+func (e *ext4FileWrapper) Read(p []byte) (int, error) {
+ return e.file.Read(p)
+}
+
+func (e *ext4FileWrapper) Close() error {
+ return e.file.Close()
+}
+
+func (e *ext4FileWrapper) Stat() (fs.FileInfo, error) {
+ return e.file.Stat()
+}
+
+// Implement io.ReaderAt for scalibrfs.File (assumed to require it)
+func (e *ext4FileWrapper) ReadAt(p []byte, off int64) (int, error) {
+ // Read the entire file into memory (suitable for small files like private-key.pem)
+ data, err := io.ReadAll(e.file)
+ if err != nil {
+ return 0, fmt.Errorf("failed to read file %s: %v", e.name, err)
+ }
+ if off >= int64(len(data)) {
+ return 0, io.EOF
+ }
+ n := copy(p, data[off:])
+ if n < len(p) {
+ return n, io.EOF
+ }
+ return n, nil
+}
+
+// fat32FS wraps go-diskfs fat32.FileSystem to implement scalibrfs.FS
+type fat32FS struct {
+ fs *fat32.FileSystem
+ file *os.File
+ tmpRawPath string
+ refCount *int32
+ refMu *sync.Mutex
+}
+
+func (f *fat32FS) Open(name string) (fs.File, error) {
+ file, err := f.fs.OpenFile(name, os.O_RDONLY)
+ if err != nil {
+ //fmt.Printf("fat32FS.Open(%q) failed: %v\n", name, err)
+ return nil, fmt.Errorf("failed to open file %s: %v", name, err)
+ }
+ return &fat32FileWrapper{file: file, name: name, fs: f.fs}, nil
+}
+
+func (f *fat32FS) ReadDir(name string) ([]fs.DirEntry, error) {
+ if name == "." || name == "" {
+ // Return synthetic FileInfo for root directory
+ name = "/"
+ }
+ fis, err := f.fs.ReadDir(name)
+ if err != nil {
+ //fmt.Printf("fat32FS.ReadDir(%q) failed: %v\n", name, err)
+ return nil, fmt.Errorf("failed to read directory %s: %v", name, err)
+ }
+ entries := make([]fs.DirEntry, 0, len(fis))
+ for _, fi := range fis {
+ entries = append(entries, fs.FileInfoToDirEntry(fi))
+ }
+ return entries, nil
+}
+
+func (f *fat32FS) Stat(name string) (fs.FileInfo, error) {
+ if name == "/" || name == "" || name == "." {
+ // Return synthetic FileInfo for root directory
+ return &fileInfo{
+ name: name,
+ isDir: true,
+ modTime: time.Now(),
+ }, nil
+ }
+ fis, err := f.fs.ReadDir(path.Dir(name))
+ if err != nil {
+ //fmt.Printf("fat32FS.Stat(%q) failed: %v\n", name, err)
+ return nil, fmt.Errorf("failed to stat %s: %v", name, err)
+ }
+ base := path.Base(name)
+ for _, fi := range fis {
+ if fi.Name() == base {
+ return fi, nil
+ }
+ }
+ return nil, fmt.Errorf("file %s not found", name)
+}
+
+func (f *fat32FS) Close() error {
+ f.refMu.Lock()
+ defer f.refMu.Unlock()
+ if f.file == nil {
+ return nil
+ }
+ *f.refCount--
+ if *f.refCount == 0 {
+ err := f.file.Close()
+ f.file = nil
+ if err != nil {
+ return fmt.Errorf("failed to close raw file %s: %v", f.tmpRawPath, err)
+ }
+ if err := os.Remove(f.tmpRawPath); err != nil {
+ return fmt.Errorf("failed to remove temporary raw file %s: %v", f.tmpRawPath, err)
+ }
+ }
+ return nil
+}
+
+// fat32FileWrapper wraps diskfsfilesystem.File to implement scalibrfs.FS and io.ReaderAt
+type fat32FileWrapper struct {
+ file diskfsfilesystem.File
+ name string
+ fs *fat32.FileSystem
+}
+
+func (f *fat32FileWrapper) Read(p []byte) (int, error) {
+ return f.file.Read(p)
+}
+
+func (f *fat32FileWrapper) Close() error {
+ return f.file.Close()
+}
+
+func (f *fat32FileWrapper) Stat() (fs.FileInfo, error) {
+ if f.name == "/" || f.name == "" || f.name == "." {
+ // Return synthetic FileInfo for root directory
+ return &fileInfo{
+ name: f.name,
+ isDir: true,
+ modTime: time.Now(),
+ }, nil
+ }
+ fis, err := f.fs.ReadDir(path.Dir(f.name))
+ if err != nil {
+ return nil, fmt.Errorf("failed to read directory %s: %v", path.Dir(f.name), err)
+ }
+ base := path.Base(f.name)
+ for _, fi := range fis {
+ if fi.Name() == base {
+ return fi, nil
+ }
+ }
+ return nil, fmt.Errorf("file %s not found", f.name)
+}
+
+func (f *fat32FileWrapper) ReadAt(p []byte, off int64) (int, error) {
+ // diskfsfilesystem.File implements io.ReadWriteSeeker, so we can use Seek and Read
+ _, err := f.file.Seek(off, io.SeekStart)
+ if err != nil {
+ return 0, fmt.Errorf("failed to seek to offset %d in file %s: %v", off, f.name, err)
+ }
+ n, err := f.file.Read(p)
+ if err != nil {
+ return n, fmt.Errorf("failed to read at offset %d in file %s: %v", off, f.name, err)
+ }
+ return n, nil
+}
+
+// fileInfo is a simple implementation of fs.FileInfo for the root directory
+type fileInfo struct {
+ name string
+ isDir bool
+ modTime time.Time
+}
+
+func (fi *fileInfo) Name() string {
+ return fi.name
+}
+
+func (fi *fileInfo) Size() int64 {
+ return 0
+}
+
+func (fi *fileInfo) Mode() fs.FileMode {
+ if fi.isDir {
+ return fs.ModeDir | 0755
+ }
+ return 0644
+}
+
+func (fi *fileInfo) ModTime() time.Time {
+ return fi.modTime
+}
+
+func (fi *fileInfo) IsDir() bool {
+ return fi.isDir
+}
+
+func (fi *fileInfo) Sys() interface{} {
+ return nil
+}
+
// VMDK conversion functions
// readHeaderAt reads the 512-byte header at the given offset.
-func readHeaderAt(r io.ReaderAt, offset int64) (sparseExtentHeader, error) {
- var hdr sparseExtentHeader
+func readHeaderAt(r io.ReaderAt, offset int64) (SparseExtentHeader, error) {
+ var hdr SparseExtentHeader
buf := make([]byte, SectorSize)
n, err := r.ReadAt(buf, offset)
- if err != nil && !errors.Is(err, io.EOF) {
+ if err != nil && err != io.EOF {
return hdr, fmt.Errorf("read header at %d: %w", offset, err)
}
if n < SectorSize {
@@ -183,15 +546,15 @@ func readHeaderAt(r io.ReaderAt, offset int64) (sparseExtentHeader, error) {
if err := binary.Read(br, binary.LittleEndian, &hdr); err != nil {
return hdr, fmt.Errorf("parse header: %w", err)
}
- if hdr.MagicNumber != SparseMagic {
+ if hdr.MagicNumber != SPARSE_MAGIC {
return hdr, fmt.Errorf("invalid magic: 0x%x", hdr.MagicNumber)
}
return hdr, nil
}
-// readFooterIfGDAtEnd reads the footer header near EOF if GDOffset is GDAtEnd.
-func readFooterIfGDAtEnd(f *os.File, hdr *sparseExtentHeader) error {
- if hdr.GDOffset != GDAtEnd {
+// readFooterIfGDAtEnd reads the footer header near EOF if GdOffset is GDAtEnd.
+func readFooterIfGDAtEnd(f *os.File, hdr *SparseExtentHeader) error {
+ if hdr.GdOffset != GDAtEnd {
return nil
}
fi, err := f.Stat()
@@ -199,17 +562,17 @@ func readFooterIfGDAtEnd(f *os.File, hdr *sparseExtentHeader) error {
return err
}
if fi.Size() < 1536 {
- return errors.New("file too small to contain footer/EOS")
+ return fmt.Errorf("file too small to contain footer/EOS")
}
base := fi.Size() - 1536
footerHeaderBlock := make([]byte, 512)
if _, err := f.ReadAt(footerHeaderBlock, base+512); err != nil {
return fmt.Errorf("read footer header block: %w", err)
}
- if binary.LittleEndian.Uint32(footerHeaderBlock[0:4]) != SparseMagic {
+ if binary.LittleEndian.Uint32(footerHeaderBlock[0:4]) != SPARSE_MAGIC {
return fmt.Errorf("footer magic mismatch: 0x%x", binary.LittleEndian.Uint32(footerHeaderBlock[0:4]))
}
- var foot sparseExtentHeader
+ var foot SparseExtentHeader
r := bytes.NewReader(footerHeaderBlock[4:])
if err := binary.Read(r, binary.LittleEndian, &foot); err != nil {
return fmt.Errorf("parse footer header: %w", err)
@@ -218,6 +581,46 @@ func readFooterIfGDAtEnd(f *os.File, hdr *sparseExtentHeader) error {
return nil
}
+// alignUp aligns to sector boundary (upwards).
+func alignUp(x int64, sector int64) int64 {
+ if x%sector == 0 {
+ return x
+ }
+ return ((x / sector) + 1) * sector
+}
+
+// copyNToFileAt copies n bytes from src to out at offset.
+func copyNToFileAt(out *os.File, src io.Reader, offset int64, n int64) error {
+ const chunk = 1 << 20 // 1MiB
+ buf := make([]byte, chunk)
+ written := int64(0)
+ for written < n {
+ toRead := chunk
+ if n-written < int64(toRead) {
+ toRead = int(n - written)
+ }
+ read, rerr := io.ReadFull(src, buf[:toRead])
+ if read > 0 {
+ if wn, werr := out.WriteAt(buf[:read], offset+written); werr != nil {
+ return werr
+ } else if wn != read {
+ return io.ErrShortWrite
+ }
+ written += int64(read)
+ }
+ if rerr != nil {
+ if rerr == io.EOF || rerr == io.ErrUnexpectedEOF {
+ if written == n {
+ break
+ }
+ return rerr
+ }
+ return rerr
+ }
+ }
+ return nil
+}
+
// readStreamMarker reads a VMDK stream marker.
func readStreamMarker(f *os.File) (val uint64, size uint32, typ uint32, data []byte, err error) {
head := make([]byte, 12)
@@ -259,8 +662,8 @@ func readStreamMarker(f *os.File) (val uint64, size uint32, typ uint32, data []b
}
// convertStreamOptimizedExtent converts a stream-optimized VMDK extent.
-func convertStreamOptimizedExtent(f *os.File, out *os.File, hdr sparseExtentHeader) error {
- if hdr.GDOffset == GDAtEnd {
+func convertStreamOptimizedExtent(f *os.File, out *os.File, hdr SparseExtentHeader) error {
+ if hdr.GdOffset == GDAtEnd {
if err := readFooterIfGDAtEnd(f, &hdr); err != nil {
return fmt.Errorf("read footer: %w", err)
}
@@ -282,7 +685,7 @@ func convertStreamOptimizedExtent(f *os.File, out *os.File, hdr sparseExtentHead
for {
val, size, typ, payload, err := readStreamMarker(f)
if err != nil {
- if errors.Is(err, io.EOF) {
+ if err == io.EOF {
break
}
return fmt.Errorf("read marker: %w", err)
@@ -301,7 +704,7 @@ func convertStreamOptimizedExtent(f *os.File, out *os.File, hdr sparseExtentHead
}
dec, derr := io.ReadAll(zr)
zr.Close()
- if derr != nil && !errors.Is(derr, io.EOF) {
+ if derr != nil && derr != io.EOF {
return fmt.Errorf("zlib read at lba %d: %w", lba, derr)
}
if int64(len(dec)) < grainBytes {
@@ -342,8 +745,8 @@ func convertStreamOptimizedExtent(f *os.File, out *os.File, hdr sparseExtentHead
if _, err := io.ReadFull(f, meta); err != nil {
return fmt.Errorf("read footer meta: %w", err)
}
- if len(meta) >= 4 && binary.LittleEndian.Uint32(meta[0:4]) == SparseMagic {
- var foot sparseExtentHeader
+ if len(meta) >= 4 && binary.LittleEndian.Uint32(meta[0:4]) == SPARSE_MAGIC {
+ var foot SparseExtentHeader
br := bytes.NewReader(meta[4:])
if err := binary.Read(br, binary.LittleEndian, &foot); err == nil {
hdr = foot
@@ -375,7 +778,7 @@ func convertStreamOptimizedExtent(f *os.File, out *os.File, hdr sparseExtentHead
}
// getGDGT computes GD/GT sizes and allocates structures.
-func getGDGT(hdr sparseExtentHeader) (*gdgtInfo, error) {
+func getGDGT(hdr SparseExtentHeader) (*GDGTInfo, error) {
if hdr.GrainSize < 1 || hdr.GrainSize > 128 || (hdr.GrainSize&(hdr.GrainSize-1)) != 0 {
return nil, fmt.Errorf("invalid grainSize %d", hdr.GrainSize)
}
@@ -402,7 +805,7 @@ func getGDGT(hdr sparseExtentHeader) (*gdgtInfo, error) {
return nil, fmt.Errorf("gd/gt allocation too large: %d bytes", totalBytes)
}
gdarr := make([]uint32, (GDsectors*SectorSize)/4+(GTsectors*GTs*SectorSize)/4)
- info := &gdgtInfo{
+ info := &GDGTInfo{
GTEs: GTEs,
GTs: GTs,
GDsectors: GDsectors,
@@ -413,34 +816,34 @@ func getGDGT(hdr sparseExtentHeader) (*gdgtInfo, error) {
}
// readGD reads GD sectors from file.
-func readGD(f *os.File, hdr sparseExtentHeader, info *gdgtInfo) error {
- if hdr.GDOffset == 0 {
- return errors.New("no GD offset")
+func readGD(f *os.File, hdr SparseExtentHeader, info *GDGTInfo) error {
+ if hdr.GdOffset == 0 {
+ return fmt.Errorf("no GD offset")
}
- start := int64(hdr.GDOffset) * SectorSize
+ start := int64(hdr.GdOffset) * SectorSize
totalBytes := int64(info.GDsectors) * SectorSize
buf := make([]byte, totalBytes)
if _, err := f.ReadAt(buf, start); err != nil {
return fmt.Errorf("read GD at %d: %w", start, err)
}
- for i := range int(info.GDsectors * SectorSize / 4) {
+ for i := 0; i < int(info.GDsectors*SectorSize/4); i++ {
info.gd[i] = binary.LittleEndian.Uint32(buf[i*4 : i*4+4])
}
return nil
}
// convertMonolithicSparse converts a monolithic sparse VMDK.
-func convertMonolithicSparse(f *os.File, out *os.File, hdr sparseExtentHeader) error {
+func convertMonolithicSparse(f *os.File, out *os.File, hdr SparseExtentHeader) error {
info, err := getGDGT(hdr)
if err != nil {
return err
}
- GDOffset := hdr.GDOffset
- if hdr.RGDOffset != 0 {
- GDOffset = hdr.RGDOffset
+ gdOffset := hdr.GdOffset
+ if hdr.RgdOffset != 0 {
+ gdOffset = hdr.RgdOffset
}
- if GDOffset == 0 || GDOffset == GDAtEnd {
- return errors.New("gd offset missing for monolithicSparse")
+ if gdOffset == 0 || gdOffset == GDAtEnd {
+ return fmt.Errorf("gd offset missing for monolithicSparse")
}
if err := readGD(f, hdr, info); err != nil {
return fmt.Errorf("readGD: %w", err)
@@ -451,7 +854,7 @@ func convertMonolithicSparse(f *os.File, out *os.File, hdr sparseExtentHeader) e
return fmt.Errorf("truncate out: %w", err)
}
numGTEsPerGT := int64(hdr.NumGTEsPerGT)
- for g := range totalGrains {
+ for g := int64(0); g < totalGrains; g++ {
gdIdx := int(g / numGTEsPerGT)
gtIdx := int(g % numGTEsPerGT)
if gdIdx >= len(info.gd) {
@@ -492,13 +895,13 @@ func convertMonolithicSparse(f *os.File, out *os.File, hdr sparseExtentHeader) e
}
grainSector := int64(gte)
grainOffset := grainSector * SectorSize
- var toRead = grainBytes
+ var toRead int64 = grainBytes
if g == totalGrains-1 {
lastSectors := int64(hdr.Capacity % hdr.GrainSize)
if lastSectors == 0 {
lastSectors = int64(hdr.GrainSize)
}
- toRead = lastSectors * SectorSize
+ toRead = int64(lastSectors) * SectorSize
}
grainData := make([]byte, toRead)
if _, err := f.ReadAt(grainData, grainOffset); err != nil {