123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481 |
- // Package to work with VHD images
- // See https://technet.microsoft.com/en-us/virtualization/bb676673.aspx
- package vhd
- import (
- "bytes"
- "encoding/binary"
- "encoding/hex"
- "fmt"
- "math"
- "os"
- "strconv"
- "time"
- )
- const VHD_COOKIE = "636f6e6563746978" // conectix
- const VHD_DYN_COOKIE = "6378737061727365" // cxsparse
- const VHD_CREATOR_APP = "676f2d766864" // go-vhd
- const VHD_CREATOR_HOST_OS = "5769326B" // Win2k
- const VHD_BLOCK_SIZE = 2 * 1024 * 1024 // 2MB
- const VHD_HEADER_SIZE = 512
- const SECTOR_SIZE = 512
- const FOURK_SECTOR_SIZE = 4096
- const VHD_EXTRA_HEADER_SIZE = 1024
- // A VDH file
- type VHD struct {
- Footer VHDHeader
- ExtraHeader VHDExtraHeader
- }
- // VHD Header
- type VHDHeader struct {
- Cookie [8]byte
- Features [4]byte
- FileFormatVersion [4]byte
- DataOffset [8]byte
- Timestamp [4]byte
- CreatorApplication [4]byte
- CreatorVersion [4]byte
- CreatorHostOS [4]byte
- OriginalSize [8]byte
- CurrentSize [8]byte
- DiskGeometry [4]byte
- DiskType [4]byte
- Checksum [4]byte
- UniqueId [16]byte
- SavedState [1]byte
- Reserved [427]byte
- }
- // VHD extra header, for dynamic and differential disks
- type VHDExtraHeader struct {
- Cookie [8]byte
- DataOffset [8]byte
- TableOffset [8]byte
- HeaderVersion [4]byte
- MaxTableEntries [4]byte
- BlockSize [4]byte
- Checksum [4]byte
- ParentUUID [16]byte
- ParentTimestamp [4]byte
- Reserved [4]byte
- ParentUnicodeName [512]byte
- ParentLocatorEntry1 [24]byte
- ParentLocatorEntry2 [24]byte
- ParentLocatorEntry3 [24]byte
- ParentLocatorEntry4 [24]byte
- ParentLocatorEntry5 [24]byte
- ParentLocatorEntry6 [24]byte
- ParentLocatorEntry7 [24]byte
- ParentLocatorEntry8 [24]byte
- Reserved2 [256]byte
- }
- // Options for the CreateSparseVHD function
- type VHDOptions struct {
- UUID string
- Timestamp int64
- }
- /*
- * VHDExtraHeader methods
- */
- func (header *VHDExtraHeader) CookieString() string {
- return string(header.Cookie[:])
- }
- // Calculate and add the VHD dynamic/differential header checksum
- func (h *VHDExtraHeader) addChecksum() {
- buffer := new(bytes.Buffer)
- binary.Write(buffer, binary.BigEndian, h)
- checksum := 0
- bb := buffer.Bytes()
- for counter := 0; counter < VHD_EXTRA_HEADER_SIZE; counter++ {
- checksum += int(bb[counter])
- }
- binary.BigEndian.PutUint32(h.Checksum[:], uint32(^checksum))
- }
- /*
- * VHDHeader methods
- */
- func (h *VHDHeader) DiskTypeStr() (dt string) {
- switch h.DiskType[3] {
- case 0x00:
- dt = "None"
- case 0x01:
- dt = "Deprecated"
- case 0x02:
- dt = "Fixed"
- case 0x03:
- dt = "Dynamic"
- case 0x04:
- dt = "Differential"
- case 0x05:
- dt = "Reserved"
- case 0x06:
- dt = "Reserved"
- default:
- panic("Invalid disk type detected!")
- }
- return
- }
- // Return the timestamp of the header
- func (h *VHDHeader) TimestampTime() time.Time {
- tstamp := binary.BigEndian.Uint32(h.Timestamp[:])
- return time.Unix(int64(946684800+tstamp), 0)
- }
- // Calculate and add the VHD header checksum
- func (h *VHDHeader) addChecksum() {
- buffer := new(bytes.Buffer)
- binary.Write(buffer, binary.BigEndian, h)
- checksum := 0
- bb := buffer.Bytes()
- for counter := 0; counter < VHD_HEADER_SIZE; counter++ {
- checksum += int(bb[counter])
- }
- binary.BigEndian.PutUint32(h.Checksum[:], uint32(^checksum))
- }
- func CreateFixedHeader(size uint64, options *VHDOptions) VHDHeader {
- header := VHDHeader{}
- hexToField(VHD_COOKIE, header.Cookie[:])
- hexToField("00000002", header.Features[:])
- hexToField("00010000", header.FileFormatVersion[:])
- hexToField("ffffffffffffffff", header.DataOffset[:])
- // LOL Y2038
- if options.Timestamp != 0 {
- binary.BigEndian.PutUint32(header.Timestamp[:], uint32(options.Timestamp))
- } else {
- t := uint32(time.Now().Unix() - 946684800)
- binary.BigEndian.PutUint32(header.Timestamp[:], t)
- }
- hexToField(VHD_CREATOR_APP, header.CreatorApplication[:])
- hexToField(VHD_CREATOR_HOST_OS, header.CreatorHostOS[:])
- binary.BigEndian.PutUint64(header.OriginalSize[:], size)
- binary.BigEndian.PutUint64(header.CurrentSize[:], size)
- // total sectors = disk size / 512b sector size
- totalSectors := math.Floor(float64(size / 512))
- // [C, H, S]
- geometry := calculateCHS(uint64(totalSectors))
- binary.BigEndian.PutUint16(header.DiskGeometry[:2], uint16(geometry[0]))
- header.DiskGeometry[2] = uint8(geometry[1])
- header.DiskGeometry[3] = uint8(geometry[2])
- hexToField("00000002", header.DiskType[:]) // Fixed 0x00000002
- hexToField("00000000", header.Checksum[:])
- if options.UUID != "" {
- copy(header.UniqueId[:], uuidToBytes(options.UUID))
- } else {
- copy(header.UniqueId[:], uuidgenBytes())
- }
- header.addChecksum()
- return header
- }
- func RawToFixed(f *os.File, options *VHDOptions) {
- info, err := f.Stat()
- check(err)
- size := uint64(info.Size())
- header := CreateFixedHeader(size, options)
- binary.Write(f, binary.BigEndian, header)
- }
- func VHDCreateSparse(size uint64, name string, options VHDOptions) VHD {
- header := VHDHeader{}
- hexToField(VHD_COOKIE, header.Cookie[:])
- hexToField("00000002", header.Features[:])
- hexToField("00010000", header.FileFormatVersion[:])
- hexToField("0000000000000200", header.DataOffset[:])
- // LOL Y2038
- if options.Timestamp != 0 {
- binary.BigEndian.PutUint32(header.Timestamp[:], uint32(options.Timestamp))
- } else {
- t := uint32(time.Now().Unix() - 946684800)
- binary.BigEndian.PutUint32(header.Timestamp[:], t)
- }
- hexToField(VHD_CREATOR_APP, header.CreatorApplication[:])
- hexToField(VHD_CREATOR_HOST_OS, header.CreatorHostOS[:])
- binary.BigEndian.PutUint64(header.OriginalSize[:], size)
- binary.BigEndian.PutUint64(header.CurrentSize[:], size)
- // total sectors = disk size / 512b sector size
- totalSectors := math.Floor(float64(size / 512))
- // [C, H, S]
- geometry := calculateCHS(uint64(totalSectors))
- binary.BigEndian.PutUint16(header.DiskGeometry[:2], uint16(geometry[0]))
- header.DiskGeometry[2] = uint8(geometry[1])
- header.DiskGeometry[3] = uint8(geometry[2])
- hexToField("00000003", header.DiskType[:]) // Sparse 0x00000003
- hexToField("00000000", header.Checksum[:])
- if options.UUID != "" {
- copy(header.UniqueId[:], uuidToBytes(options.UUID))
- } else {
- copy(header.UniqueId[:], uuidgenBytes())
- }
- header.addChecksum()
- // Fill the sparse header
- header2 := VHDExtraHeader{}
- hexToField(VHD_DYN_COOKIE, header2.Cookie[:])
- hexToField("ffffffffffffffff", header2.DataOffset[:])
- // header size + sparse header size
- binary.BigEndian.PutUint64(header2.TableOffset[:], uint64(VHD_EXTRA_HEADER_SIZE+VHD_HEADER_SIZE))
- hexToField("00010000", header2.HeaderVersion[:])
- maxTableSize := uint32(size / (VHD_BLOCK_SIZE))
- binary.BigEndian.PutUint32(header2.MaxTableEntries[:], maxTableSize)
- binary.BigEndian.PutUint32(header2.BlockSize[:], VHD_BLOCK_SIZE)
- binary.BigEndian.PutUint32(header2.ParentTimestamp[:], uint32(0))
- header2.addChecksum()
- f, err := os.Create(name)
- check(err)
- defer f.Close()
- binary.Write(f, binary.BigEndian, header)
- binary.Write(f, binary.BigEndian, header2)
- /*
- Write BAT entries
- The BAT is always extended to a sector (4K) boundary
- 1536 = 512 + 1024 (the VHD Header + VHD Sparse header size)
- */
- for count := uint32(0); count < (FOURK_SECTOR_SIZE - 1536); count += 1 {
- f.Write([]byte{0xff})
- }
- /* Windows creates 8K VHDs by default */
- for i := 0; i < (FOURK_SECTOR_SIZE - VHD_HEADER_SIZE); i += 1 {
- f.Write([]byte{0x0})
- }
- binary.Write(f, binary.BigEndian, header)
- return VHD{
- Footer: header,
- ExtraHeader: header2,
- }
- }
- /*
- * VHD
- */
- func FromFile(f *os.File) (vhd VHD) {
- vhd = VHD{}
- vhd.Footer = readVHDFooter(f)
- vhd.ExtraHeader = readVHDExtraHeader(f)
- return vhd
- }
- func (vhd *VHD) PrintInfo() {
- fmt.Println("\nVHD footer")
- fmt.Println("==========")
- vhd.PrintFooter()
- if vhd.Footer.DiskType[3] == 0x3 || vhd.Footer.DiskType[3] == 0x04 {
- fmt.Println("\nVHD sparse/differential header")
- fmt.Println("===============================")
- vhd.PrintExtraHeader()
- }
- }
- func (vhd *VHD) PrintExtraHeader() {
- header := vhd.ExtraHeader
- fmtField("Cookie", fmt.Sprintf("%s (%s)",
- hexs(header.Cookie[:]), header.CookieString()))
- fmtField("Data offset", hexs(header.DataOffset[:]))
- fmtField("Table offset", hexs(header.TableOffset[:]))
- fmtField("Header version", hexs(header.HeaderVersion[:]))
- fmtField("Max table entries", hexs(header.MaxTableEntries[:]))
- fmtField("Block size", hexs(header.BlockSize[:]))
- fmtField("Checksum", hexs(header.Checksum[:]))
- fmtField("Parent UUID", uuid(header.ParentUUID[:]))
- // Seconds since January 1, 1970 12:00:00 AM in UTC/GMT.
- // 946684800 = January 1, 2000 12:00:00 AM in UTC/GMT.
- tstamp := binary.BigEndian.Uint32(header.ParentTimestamp[:])
- t := time.Unix(int64(946684800+tstamp), 0)
- fmtField("Parent timestamp", fmt.Sprintf("%s", t))
- fmtField("Reserved", hexs(header.Reserved[:]))
- parentName := utf16BytesToString(header.ParentUnicodeName[:],
- binary.BigEndian)
- fmtField("Parent Name", parentName)
- // Parent locator entries ignored since it's a dynamic disk
- sum := 0
- for _, b := range header.Reserved2 {
- sum += int(b)
- }
- fmtField("Reserved2", strconv.Itoa(sum))
- }
- func (vhd *VHD) PrintFooter() {
- header := vhd.Footer
- //fmtField("Cookie", string(header.Cookie[:]))
- fmtField("Cookie", fmt.Sprintf("%s (%s)",
- hexs(header.Cookie[:]), string(header.Cookie[:])))
- fmtField("Features", hexs(header.Features[:]))
- fmtField("File format version", hexs(header.FileFormatVersion[:]))
- dataOffset := binary.BigEndian.Uint64(header.DataOffset[:])
- fmtField("Data offset",
- fmt.Sprintf("%s (%d bytes)", hexs(header.DataOffset[:]), dataOffset))
- //// Seconds since January 1, 1970 12:00:00 AM in UTC/GMT.
- //// 946684800 = January 1, 2000 12:00:00 AM in UTC/GMT.
- t := time.Unix(int64(946684800+binary.BigEndian.Uint32(header.Timestamp[:])), 0)
- fmtField("Timestamp", fmt.Sprintf("%s", t))
- fmtField("Creator application", string(header.CreatorApplication[:]))
- fmtField("Creator version", hexs(header.CreatorVersion[:]))
- fmtField("Creator OS", string(header.CreatorHostOS[:]))
- originalSize := binary.BigEndian.Uint64(header.OriginalSize[:])
- fmtField("Original size",
- fmt.Sprintf("%s ( %d bytes )", hexs(header.OriginalSize[:]), originalSize))
- currentSize := binary.BigEndian.Uint64(header.OriginalSize[:])
- fmtField("Current size",
- fmt.Sprintf("%s ( %d bytes )", hexs(header.CurrentSize[:]), currentSize))
- cilinders := int64(binary.BigEndian.Uint16(header.DiskGeometry[:2]))
- heads := int64(header.DiskGeometry[2])
- sectors := int64(header.DiskGeometry[3])
- dsize := cilinders * heads * sectors * 512
- fmtField("Disk geometry",
- fmt.Sprintf("%s (c: %d, h: %d, s: %d) (%d bytes)",
- hexs(header.DiskGeometry[:]),
- cilinders,
- heads,
- sectors,
- dsize))
- fmtField("Disk type",
- fmt.Sprintf("%s (%s)", hexs(header.DiskType[:]), header.DiskTypeStr()))
- fmtField("Checksum", hexs(header.Checksum[:]))
- fmtField("UUID", uuid(header.UniqueId[:]))
- fmtField("Saved state", fmt.Sprintf("%d", header.SavedState[0]))
- }
- /*
- Utility functions
- */
- func calculateCHS(ts uint64) []uint {
- var sectorsPerTrack,
- heads,
- cylinderTimesHeads,
- cylinders float64
- totalSectors := float64(ts)
- ret := make([]uint, 3)
- if totalSectors > 65535*16*255 {
- totalSectors = 65535 * 16 * 255
- }
- if totalSectors >= 65535*16*63 {
- sectorsPerTrack = 255
- heads = 16
- cylinderTimesHeads = math.Floor(totalSectors / sectorsPerTrack)
- } else {
- sectorsPerTrack = 17
- cylinderTimesHeads = math.Floor(totalSectors / sectorsPerTrack)
- heads = math.Floor((cylinderTimesHeads + 1023) / 1024)
- if heads < 4 {
- heads = 4
- }
- if (cylinderTimesHeads >= (heads * 1024)) || heads > 16 {
- sectorsPerTrack = 31
- heads = 16
- cylinderTimesHeads = math.Floor(totalSectors / sectorsPerTrack)
- }
- if cylinderTimesHeads >= (heads * 1024) {
- sectorsPerTrack = 63
- heads = 16
- cylinderTimesHeads = math.Floor(totalSectors / sectorsPerTrack)
- }
- }
- cylinders = cylinderTimesHeads / heads
- // This will floor the values
- ret[0] = uint(cylinders)
- ret[1] = uint(heads)
- ret[2] = uint(sectorsPerTrack)
- return ret
- }
- func hexToField(hexs string, field []byte) {
- h, err := hex.DecodeString(hexs)
- check(err)
- copy(field, h)
- }
- // Return the number of blocks in the disk, diskSize in bytes
- func getMaxTableEntries(diskSize uint64) uint64 {
- return diskSize * (2 * 1024 * 1024) // block size is 2M
- }
- func readVHDExtraHeader(f *os.File) (header VHDExtraHeader) {
- buff := make([]byte, 1024)
- _, err := f.ReadAt(buff, 512)
- check(err)
- binary.Read(bytes.NewBuffer(buff[:]), binary.BigEndian, &header)
- return header
- }
- func readVHDFooter(f *os.File) (header VHDHeader) {
- info, err := f.Stat()
- check(err)
- buff := make([]byte, 512)
- _, err = f.ReadAt(buff, info.Size()-512)
- check(err)
- binary.Read(bytes.NewBuffer(buff[:]), binary.BigEndian, &header)
- return header
- }
- func readVHDHeader(f *os.File) (header VHDHeader) {
- buff := make([]byte, 512)
- _, err := f.ReadAt(buff, 0)
- check(err)
- binary.Read(bytes.NewBuffer(buff[:]), binary.BigEndian, &header)
- return header
- }
|