github.com/cloud-foundations/dominator@v0.0.0-20221004181915-6e4fee580046/lib/images/virtualbox/reader.go (about) 1 package virtualbox 2 3 import ( 4 "bufio" 5 "encoding/binary" 6 "fmt" 7 "io" 8 9 "github.com/Cloud-Foundations/Dominator/lib/format" 10 ) 11 12 type headerType struct { 13 Header [0x40]byte 14 Signature uint32 15 VersionMajor uint16 16 VersionMinor uint16 17 HeaderSize uint32 18 ImageType uint32 19 ImageFlags uint32 20 Description [0x100]byte 21 OffsetBocks uint32 22 OffsetData uint32 23 Cylinders uint32 24 Heads uint32 25 Sectors uint32 26 SectorSize uint32 27 Unused [4]byte 28 DiscSize uint64 29 BlockSize uint32 30 BlockExtraData uint32 31 BlocksInHDD uint32 32 BlocksAllocated uint32 33 UuidImage uint64 34 UuidLastSnap uint64 35 UuidLink uint64 36 UuidParent uint64 37 } 38 39 func newReader(rawReader io.Reader) (*Reader, error) { 40 r := bufio.NewReaderSize(rawReader, 1<<20) 41 var header headerType 42 if err := binary.Read(r, binary.LittleEndian, &header); err != nil { 43 return nil, fmt.Errorf("error reading VDI header: %s", err) 44 } 45 if header.Signature != 0xbeda107f { 46 return nil, fmt.Errorf("%x not a VDI signature", header.Signature) 47 } 48 if header.VersionMajor < 1 { 49 return nil, 50 fmt.Errorf("VDI major version: %d not supported", 51 header.VersionMajor) 52 } 53 if header.ImageType != 1 { 54 return nil, fmt.Errorf("VDI image type: %d not supported", 55 header.ImageType) 56 } 57 if header.BlockSize != 1<<20 { 58 return nil, fmt.Errorf("VDI block size: 0x%x (%s) not supported", 59 header.BlockSize, format.FormatBytes(uint64(header.BlockSize))) 60 } 61 mul := uint64(header.BlockSize) * uint64(header.BlocksInHDD) 62 if mul != header.DiscSize { 63 return nil, fmt.Errorf("blockSize*blocksInHdd: %d != discSize: %d", 64 mul, header.DiscSize) 65 } 66 r.Reset(rawReader) // Discard until 1 MiB boundary. 67 lastPointer := int32(-1) 68 blockMap := make(map[uint32]struct{}, header.BlocksInHDD) 69 for index := uint32(0); index < header.BlocksInHDD; index++ { 70 var pointer int32 71 if err := binary.Read(r, binary.LittleEndian, &pointer); err != nil { 72 return nil, 73 fmt.Errorf("error reading block pointer: %d: %s", index, err) 74 } 75 if pointer >= 0 { 76 if pointer <= lastPointer { 77 return nil, 78 fmt.Errorf("VDI pointer: %d not greater than last: %d", 79 pointer, lastPointer) 80 } 81 blockMap[index] = struct{}{} 82 } 83 lastPointer = pointer 84 } 85 r.Reset(rawReader) // Discard until 1 MiB boundary. 86 return &Reader{ 87 Description: string(header.Description[:]), 88 Header: string(header.Header[:]), 89 MajorVersion: header.VersionMajor, 90 MinorVersion: header.VersionMinor, 91 Size: header.DiscSize, 92 blockMap: blockMap, 93 blockSize: header.BlockSize, 94 blocksInHDD: header.BlocksInHDD, 95 reader: r, 96 }, nil 97 } 98 99 func (r *Reader) read(p []byte) (int, error) { 100 if r.blockIndex >= r.blocksInHDD { 101 return 0, io.EOF 102 } 103 blockIndex := r.blockIndex 104 if maxLength := r.blockSize - r.blockOffset; uint32(len(p)) >= maxLength { 105 p = p[:maxLength] 106 r.blockIndex++ 107 r.blockOffset = 0 108 } else { 109 r.blockOffset += uint32(len(p)) 110 } 111 if _, ok := r.blockMap[blockIndex]; !ok { 112 for index := range p { 113 p[index] = 0 114 } 115 return len(p), nil 116 } 117 return io.ReadFull(r.reader, p) 118 }