vhd.go 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481
  1. // Package to work with VHD images
  2. // See https://technet.microsoft.com/en-us/virtualization/bb676673.aspx
  3. package vhd
  4. import (
  5. "bytes"
  6. "encoding/binary"
  7. "encoding/hex"
  8. "fmt"
  9. "math"
  10. "os"
  11. "strconv"
  12. "time"
  13. )
  14. const VHD_COOKIE = "636f6e6563746978" // conectix
  15. const VHD_DYN_COOKIE = "6378737061727365" // cxsparse
  16. const VHD_CREATOR_APP = "676f2d766864" // go-vhd
  17. const VHD_CREATOR_HOST_OS = "5769326B" // Win2k
  18. const VHD_BLOCK_SIZE = 2 * 1024 * 1024 // 2MB
  19. const VHD_HEADER_SIZE = 512
  20. const SECTOR_SIZE = 512
  21. const FOURK_SECTOR_SIZE = 4096
  22. const VHD_EXTRA_HEADER_SIZE = 1024
  23. // A VDH file
  24. type VHD struct {
  25. Footer VHDHeader
  26. ExtraHeader VHDExtraHeader
  27. }
  28. // VHD Header
  29. type VHDHeader struct {
  30. Cookie [8]byte
  31. Features [4]byte
  32. FileFormatVersion [4]byte
  33. DataOffset [8]byte
  34. Timestamp [4]byte
  35. CreatorApplication [4]byte
  36. CreatorVersion [4]byte
  37. CreatorHostOS [4]byte
  38. OriginalSize [8]byte
  39. CurrentSize [8]byte
  40. DiskGeometry [4]byte
  41. DiskType [4]byte
  42. Checksum [4]byte
  43. UniqueId [16]byte
  44. SavedState [1]byte
  45. Reserved [427]byte
  46. }
  47. // VHD extra header, for dynamic and differential disks
  48. type VHDExtraHeader struct {
  49. Cookie [8]byte
  50. DataOffset [8]byte
  51. TableOffset [8]byte
  52. HeaderVersion [4]byte
  53. MaxTableEntries [4]byte
  54. BlockSize [4]byte
  55. Checksum [4]byte
  56. ParentUUID [16]byte
  57. ParentTimestamp [4]byte
  58. Reserved [4]byte
  59. ParentUnicodeName [512]byte
  60. ParentLocatorEntry1 [24]byte
  61. ParentLocatorEntry2 [24]byte
  62. ParentLocatorEntry3 [24]byte
  63. ParentLocatorEntry4 [24]byte
  64. ParentLocatorEntry5 [24]byte
  65. ParentLocatorEntry6 [24]byte
  66. ParentLocatorEntry7 [24]byte
  67. ParentLocatorEntry8 [24]byte
  68. Reserved2 [256]byte
  69. }
  70. // Options for the CreateSparseVHD function
  71. type VHDOptions struct {
  72. UUID string
  73. Timestamp int64
  74. }
  75. /*
  76. * VHDExtraHeader methods
  77. */
  78. func (header *VHDExtraHeader) CookieString() string {
  79. return string(header.Cookie[:])
  80. }
  81. // Calculate and add the VHD dynamic/differential header checksum
  82. func (h *VHDExtraHeader) addChecksum() {
  83. buffer := new(bytes.Buffer)
  84. binary.Write(buffer, binary.BigEndian, h)
  85. checksum := 0
  86. bb := buffer.Bytes()
  87. for counter := 0; counter < VHD_EXTRA_HEADER_SIZE; counter++ {
  88. checksum += int(bb[counter])
  89. }
  90. binary.BigEndian.PutUint32(h.Checksum[:], uint32(^checksum))
  91. }
  92. /*
  93. * VHDHeader methods
  94. */
  95. func (h *VHDHeader) DiskTypeStr() (dt string) {
  96. switch h.DiskType[3] {
  97. case 0x00:
  98. dt = "None"
  99. case 0x01:
  100. dt = "Deprecated"
  101. case 0x02:
  102. dt = "Fixed"
  103. case 0x03:
  104. dt = "Dynamic"
  105. case 0x04:
  106. dt = "Differential"
  107. case 0x05:
  108. dt = "Reserved"
  109. case 0x06:
  110. dt = "Reserved"
  111. default:
  112. panic("Invalid disk type detected!")
  113. }
  114. return
  115. }
  116. // Return the timestamp of the header
  117. func (h *VHDHeader) TimestampTime() time.Time {
  118. tstamp := binary.BigEndian.Uint32(h.Timestamp[:])
  119. return time.Unix(int64(946684800+tstamp), 0)
  120. }
  121. // Calculate and add the VHD header checksum
  122. func (h *VHDHeader) addChecksum() {
  123. buffer := new(bytes.Buffer)
  124. binary.Write(buffer, binary.BigEndian, h)
  125. checksum := 0
  126. bb := buffer.Bytes()
  127. for counter := 0; counter < VHD_HEADER_SIZE; counter++ {
  128. checksum += int(bb[counter])
  129. }
  130. binary.BigEndian.PutUint32(h.Checksum[:], uint32(^checksum))
  131. }
  132. func CreateFixedHeader(size uint64, options *VHDOptions) VHDHeader {
  133. header := VHDHeader{}
  134. hexToField(VHD_COOKIE, header.Cookie[:])
  135. hexToField("00000002", header.Features[:])
  136. hexToField("00010000", header.FileFormatVersion[:])
  137. hexToField("ffffffffffffffff", header.DataOffset[:])
  138. // LOL Y2038
  139. if options.Timestamp != 0 {
  140. binary.BigEndian.PutUint32(header.Timestamp[:], uint32(options.Timestamp))
  141. } else {
  142. t := uint32(time.Now().Unix() - 946684800)
  143. binary.BigEndian.PutUint32(header.Timestamp[:], t)
  144. }
  145. hexToField(VHD_CREATOR_APP, header.CreatorApplication[:])
  146. hexToField(VHD_CREATOR_HOST_OS, header.CreatorHostOS[:])
  147. binary.BigEndian.PutUint64(header.OriginalSize[:], size)
  148. binary.BigEndian.PutUint64(header.CurrentSize[:], size)
  149. // total sectors = disk size / 512b sector size
  150. totalSectors := math.Floor(float64(size / 512))
  151. // [C, H, S]
  152. geometry := calculateCHS(uint64(totalSectors))
  153. binary.BigEndian.PutUint16(header.DiskGeometry[:2], uint16(geometry[0]))
  154. header.DiskGeometry[2] = uint8(geometry[1])
  155. header.DiskGeometry[3] = uint8(geometry[2])
  156. hexToField("00000002", header.DiskType[:]) // Fixed 0x00000002
  157. hexToField("00000000", header.Checksum[:])
  158. if options.UUID != "" {
  159. copy(header.UniqueId[:], uuidToBytes(options.UUID))
  160. } else {
  161. copy(header.UniqueId[:], uuidgenBytes())
  162. }
  163. header.addChecksum()
  164. return header
  165. }
  166. func RawToFixed(f *os.File, options *VHDOptions) {
  167. info, err := f.Stat()
  168. check(err)
  169. size := uint64(info.Size())
  170. header := CreateFixedHeader(size, options)
  171. binary.Write(f, binary.BigEndian, header)
  172. }
  173. func VHDCreateSparse(size uint64, name string, options VHDOptions) VHD {
  174. header := VHDHeader{}
  175. hexToField(VHD_COOKIE, header.Cookie[:])
  176. hexToField("00000002", header.Features[:])
  177. hexToField("00010000", header.FileFormatVersion[:])
  178. hexToField("0000000000000200", header.DataOffset[:])
  179. // LOL Y2038
  180. if options.Timestamp != 0 {
  181. binary.BigEndian.PutUint32(header.Timestamp[:], uint32(options.Timestamp))
  182. } else {
  183. t := uint32(time.Now().Unix() - 946684800)
  184. binary.BigEndian.PutUint32(header.Timestamp[:], t)
  185. }
  186. hexToField(VHD_CREATOR_APP, header.CreatorApplication[:])
  187. hexToField(VHD_CREATOR_HOST_OS, header.CreatorHostOS[:])
  188. binary.BigEndian.PutUint64(header.OriginalSize[:], size)
  189. binary.BigEndian.PutUint64(header.CurrentSize[:], size)
  190. // total sectors = disk size / 512b sector size
  191. totalSectors := math.Floor(float64(size / 512))
  192. // [C, H, S]
  193. geometry := calculateCHS(uint64(totalSectors))
  194. binary.BigEndian.PutUint16(header.DiskGeometry[:2], uint16(geometry[0]))
  195. header.DiskGeometry[2] = uint8(geometry[1])
  196. header.DiskGeometry[3] = uint8(geometry[2])
  197. hexToField("00000003", header.DiskType[:]) // Sparse 0x00000003
  198. hexToField("00000000", header.Checksum[:])
  199. if options.UUID != "" {
  200. copy(header.UniqueId[:], uuidToBytes(options.UUID))
  201. } else {
  202. copy(header.UniqueId[:], uuidgenBytes())
  203. }
  204. header.addChecksum()
  205. // Fill the sparse header
  206. header2 := VHDExtraHeader{}
  207. hexToField(VHD_DYN_COOKIE, header2.Cookie[:])
  208. hexToField("ffffffffffffffff", header2.DataOffset[:])
  209. // header size + sparse header size
  210. binary.BigEndian.PutUint64(header2.TableOffset[:], uint64(VHD_EXTRA_HEADER_SIZE+VHD_HEADER_SIZE))
  211. hexToField("00010000", header2.HeaderVersion[:])
  212. maxTableSize := uint32(size / (VHD_BLOCK_SIZE))
  213. binary.BigEndian.PutUint32(header2.MaxTableEntries[:], maxTableSize)
  214. binary.BigEndian.PutUint32(header2.BlockSize[:], VHD_BLOCK_SIZE)
  215. binary.BigEndian.PutUint32(header2.ParentTimestamp[:], uint32(0))
  216. header2.addChecksum()
  217. f, err := os.Create(name)
  218. check(err)
  219. defer f.Close()
  220. binary.Write(f, binary.BigEndian, header)
  221. binary.Write(f, binary.BigEndian, header2)
  222. /*
  223. Write BAT entries
  224. The BAT is always extended to a sector (4K) boundary
  225. 1536 = 512 + 1024 (the VHD Header + VHD Sparse header size)
  226. */
  227. for count := uint32(0); count < (FOURK_SECTOR_SIZE - 1536); count += 1 {
  228. f.Write([]byte{0xff})
  229. }
  230. /* Windows creates 8K VHDs by default */
  231. for i := 0; i < (FOURK_SECTOR_SIZE - VHD_HEADER_SIZE); i += 1 {
  232. f.Write([]byte{0x0})
  233. }
  234. binary.Write(f, binary.BigEndian, header)
  235. return VHD{
  236. Footer: header,
  237. ExtraHeader: header2,
  238. }
  239. }
  240. /*
  241. * VHD
  242. */
  243. func FromFile(f *os.File) (vhd VHD) {
  244. vhd = VHD{}
  245. vhd.Footer = readVHDFooter(f)
  246. vhd.ExtraHeader = readVHDExtraHeader(f)
  247. return vhd
  248. }
  249. func (vhd *VHD) PrintInfo() {
  250. fmt.Println("\nVHD footer")
  251. fmt.Println("==========")
  252. vhd.PrintFooter()
  253. if vhd.Footer.DiskType[3] == 0x3 || vhd.Footer.DiskType[3] == 0x04 {
  254. fmt.Println("\nVHD sparse/differential header")
  255. fmt.Println("===============================")
  256. vhd.PrintExtraHeader()
  257. }
  258. }
  259. func (vhd *VHD) PrintExtraHeader() {
  260. header := vhd.ExtraHeader
  261. fmtField("Cookie", fmt.Sprintf("%s (%s)",
  262. hexs(header.Cookie[:]), header.CookieString()))
  263. fmtField("Data offset", hexs(header.DataOffset[:]))
  264. fmtField("Table offset", hexs(header.TableOffset[:]))
  265. fmtField("Header version", hexs(header.HeaderVersion[:]))
  266. fmtField("Max table entries", hexs(header.MaxTableEntries[:]))
  267. fmtField("Block size", hexs(header.BlockSize[:]))
  268. fmtField("Checksum", hexs(header.Checksum[:]))
  269. fmtField("Parent UUID", uuid(header.ParentUUID[:]))
  270. // Seconds since January 1, 1970 12:00:00 AM in UTC/GMT.
  271. // 946684800 = January 1, 2000 12:00:00 AM in UTC/GMT.
  272. tstamp := binary.BigEndian.Uint32(header.ParentTimestamp[:])
  273. t := time.Unix(int64(946684800+tstamp), 0)
  274. fmtField("Parent timestamp", fmt.Sprintf("%s", t))
  275. fmtField("Reserved", hexs(header.Reserved[:]))
  276. parentName := utf16BytesToString(header.ParentUnicodeName[:],
  277. binary.BigEndian)
  278. fmtField("Parent Name", parentName)
  279. // Parent locator entries ignored since it's a dynamic disk
  280. sum := 0
  281. for _, b := range header.Reserved2 {
  282. sum += int(b)
  283. }
  284. fmtField("Reserved2", strconv.Itoa(sum))
  285. }
  286. func (vhd *VHD) PrintFooter() {
  287. header := vhd.Footer
  288. //fmtField("Cookie", string(header.Cookie[:]))
  289. fmtField("Cookie", fmt.Sprintf("%s (%s)",
  290. hexs(header.Cookie[:]), string(header.Cookie[:])))
  291. fmtField("Features", hexs(header.Features[:]))
  292. fmtField("File format version", hexs(header.FileFormatVersion[:]))
  293. dataOffset := binary.BigEndian.Uint64(header.DataOffset[:])
  294. fmtField("Data offset",
  295. fmt.Sprintf("%s (%d bytes)", hexs(header.DataOffset[:]), dataOffset))
  296. //// Seconds since January 1, 1970 12:00:00 AM in UTC/GMT.
  297. //// 946684800 = January 1, 2000 12:00:00 AM in UTC/GMT.
  298. t := time.Unix(int64(946684800+binary.BigEndian.Uint32(header.Timestamp[:])), 0)
  299. fmtField("Timestamp", fmt.Sprintf("%s", t))
  300. fmtField("Creator application", string(header.CreatorApplication[:]))
  301. fmtField("Creator version", hexs(header.CreatorVersion[:]))
  302. fmtField("Creator OS", string(header.CreatorHostOS[:]))
  303. originalSize := binary.BigEndian.Uint64(header.OriginalSize[:])
  304. fmtField("Original size",
  305. fmt.Sprintf("%s ( %d bytes )", hexs(header.OriginalSize[:]), originalSize))
  306. currentSize := binary.BigEndian.Uint64(header.OriginalSize[:])
  307. fmtField("Current size",
  308. fmt.Sprintf("%s ( %d bytes )", hexs(header.CurrentSize[:]), currentSize))
  309. cilinders := int64(binary.BigEndian.Uint16(header.DiskGeometry[:2]))
  310. heads := int64(header.DiskGeometry[2])
  311. sectors := int64(header.DiskGeometry[3])
  312. dsize := cilinders * heads * sectors * 512
  313. fmtField("Disk geometry",
  314. fmt.Sprintf("%s (c: %d, h: %d, s: %d) (%d bytes)",
  315. hexs(header.DiskGeometry[:]),
  316. cilinders,
  317. heads,
  318. sectors,
  319. dsize))
  320. fmtField("Disk type",
  321. fmt.Sprintf("%s (%s)", hexs(header.DiskType[:]), header.DiskTypeStr()))
  322. fmtField("Checksum", hexs(header.Checksum[:]))
  323. fmtField("UUID", uuid(header.UniqueId[:]))
  324. fmtField("Saved state", fmt.Sprintf("%d", header.SavedState[0]))
  325. }
  326. /*
  327. Utility functions
  328. */
  329. func calculateCHS(ts uint64) []uint {
  330. var sectorsPerTrack,
  331. heads,
  332. cylinderTimesHeads,
  333. cylinders float64
  334. totalSectors := float64(ts)
  335. ret := make([]uint, 3)
  336. if totalSectors > 65535*16*255 {
  337. totalSectors = 65535 * 16 * 255
  338. }
  339. if totalSectors >= 65535*16*63 {
  340. sectorsPerTrack = 255
  341. heads = 16
  342. cylinderTimesHeads = math.Floor(totalSectors / sectorsPerTrack)
  343. } else {
  344. sectorsPerTrack = 17
  345. cylinderTimesHeads = math.Floor(totalSectors / sectorsPerTrack)
  346. heads = math.Floor((cylinderTimesHeads + 1023) / 1024)
  347. if heads < 4 {
  348. heads = 4
  349. }
  350. if (cylinderTimesHeads >= (heads * 1024)) || heads > 16 {
  351. sectorsPerTrack = 31
  352. heads = 16
  353. cylinderTimesHeads = math.Floor(totalSectors / sectorsPerTrack)
  354. }
  355. if cylinderTimesHeads >= (heads * 1024) {
  356. sectorsPerTrack = 63
  357. heads = 16
  358. cylinderTimesHeads = math.Floor(totalSectors / sectorsPerTrack)
  359. }
  360. }
  361. cylinders = cylinderTimesHeads / heads
  362. // This will floor the values
  363. ret[0] = uint(cylinders)
  364. ret[1] = uint(heads)
  365. ret[2] = uint(sectorsPerTrack)
  366. return ret
  367. }
  368. func hexToField(hexs string, field []byte) {
  369. h, err := hex.DecodeString(hexs)
  370. check(err)
  371. copy(field, h)
  372. }
  373. // Return the number of blocks in the disk, diskSize in bytes
  374. func getMaxTableEntries(diskSize uint64) uint64 {
  375. return diskSize * (2 * 1024 * 1024) // block size is 2M
  376. }
  377. func readVHDExtraHeader(f *os.File) (header VHDExtraHeader) {
  378. buff := make([]byte, 1024)
  379. _, err := f.ReadAt(buff, 512)
  380. check(err)
  381. binary.Read(bytes.NewBuffer(buff[:]), binary.BigEndian, &header)
  382. return header
  383. }
  384. func readVHDFooter(f *os.File) (header VHDHeader) {
  385. info, err := f.Stat()
  386. check(err)
  387. buff := make([]byte, 512)
  388. _, err = f.ReadAt(buff, info.Size()-512)
  389. check(err)
  390. binary.Read(bytes.NewBuffer(buff[:]), binary.BigEndian, &header)
  391. return header
  392. }
  393. func readVHDHeader(f *os.File) (header VHDHeader) {
  394. buff := make([]byte, 512)
  395. _, err := f.ReadAt(buff, 0)
  396. check(err)
  397. binary.Read(bytes.NewBuffer(buff[:]), binary.BigEndian, &header)
  398. return header
  399. }