mirror of
https://github.com/k3s-io/k3s.git
synced 2024-06-07 19:41:36 +00:00
481 lines
13 KiB
Go
481 lines
13 KiB
Go
// 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
|
|
}
|