Files
xen-orchestra/packages/vhd-lib/Vhd/VhdSynthetic.js
2023-11-30 10:12:20 +01:00

149 lines
4.5 KiB
JavaScript

'use strict'
const UUID = require('uuid')
const cloneDeep = require('lodash/cloneDeep.js')
const Disposable = require('promise-toolbox/Disposable')
const { asyncMap } = require('@xen-orchestra/async-map')
const assert = require('assert')
const { DISK_TYPES, FOOTER_SIZE, HEADER_SIZE } = require('../_constants')
const { openVhd } = require('../openVhd')
const resolveRelativeFromFile = require('../_resolveRelativeFromFile')
const { VhdAbstract } = require('./VhdAbstract')
const VhdSynthetic = class VhdSynthetic extends VhdAbstract {
#vhds = []
get header() {
// this the most recent vhd
const vhd = this.#vhds[this.#vhds.length - 1]
// this is the root VHD
const rootVhd = this.#vhds[0]
// data of our synthetic VHD
return {
...vhd.header,
parentLocatorEntry: cloneDeep(rootVhd.header.parentLocatorEntry),
tableOffset: FOOTER_SIZE + HEADER_SIZE,
parentTimestamp: rootVhd.header.parentTimestamp,
parentUnicodeName: rootVhd.header.parentUnicodeName,
parentUuid: rootVhd.header.parentUuid,
}
}
get footer() {
// this the most recent vhd
const vhd = this.#vhds[this.#vhds.length - 1]
// this is the oldest VHD
const rootVhd = this.#vhds[0]
return {
...vhd.footer,
dataOffset: FOOTER_SIZE,
diskType: rootVhd.footer.diskType,
}
}
get compressionType() {
const compressionType = this.vhds[0].compressionType
for (let i = 0; i < this.vhds.length; i++) {
if (compressionType !== this.vhds[i].compressionType) {
return 'MIXED'
}
}
return compressionType
}
/**
* @param {Array<VhdAbstract>} vhds the chain of Vhds used to compute this Vhd, from the deepest child (in position 0), to the root (in the last position)
* only the last one can have any type. Other must have type DISK_TYPES.DIFFERENCING (delta)
*/
constructor(vhds) {
assert(vhds.length > 0)
super()
this.#vhds = vhds
}
async readBlockAllocationTable() {
await asyncMap(this.#vhds, vhd => vhd.readBlockAllocationTable())
}
containsBlock(blockId) {
return this.#vhds.some(vhd => vhd.containsBlock(blockId))
}
async readHeaderAndFooter() {
const vhds = this.#vhds
await asyncMap(vhds, vhd => vhd.readHeaderAndFooter())
for (let i = 0, n = vhds.length - 1; i < n; ++i) {
const parent = vhds[i]
const child = vhds[i + 1]
assert.strictEqual(child.footer.diskType, DISK_TYPES.DIFFERENCING)
assert.strictEqual(UUID.stringify(child.header.parentUuid), UUID.stringify(parent.footer.uuid))
}
}
#getVhdWithBlock(blockId) {
for (let i = this.#vhds.length - 1; i >= 0; i--) {
const vhd = this.#vhds[i]
if (vhd.containsBlock(blockId)) {
return vhd
}
}
assert(false, `no such block ${blockId}`)
}
async readBlock(blockId, onlyBitmap = false) {
// only read the content of the first vhd containing this block
return await this.#getVhdWithBlock(blockId).readBlock(blockId, onlyBitmap)
}
async mergeBlock(child, blockId) {
throw new Error(`can't coalesce block into a vhd synthetic`)
}
_readParentLocatorData(id) {
return this.#vhds[this.#vhds.length - 1]._readParentLocatorData(id)
}
_getFullBlockPath(blockId) {
const vhd = this.#getVhdWithBlock(blockId)
return vhd?._getFullBlockPath(blockId)
}
// return true if all the vhds ar an instance of cls
checkVhdsClass(cls) {
return this.#vhds.every(vhd => vhd instanceof cls)
}
}
// add decorated static method
// until is not included in the result , the chain will stop at its child
VhdSynthetic.fromVhdChain = Disposable.factory(async function* fromVhdChain(handler, childPath, { until } = {}) {
let vhdPath = childPath
let vhd
const vhds = []
do {
vhd = yield openVhd(handler, vhdPath)
vhds.unshift(vhd) // from oldest to most recent
vhdPath = resolveRelativeFromFile(vhdPath, vhd.header.parentUnicodeName)
} while (vhd.footer.diskType !== DISK_TYPES.DYNAMIC && vhdPath !== until)
if (until !== undefined && vhdPath !== until) {
throw new Error(`Didn't find ${until} as a parent of ${childPath}`)
}
const synthetic = new VhdSynthetic(vhds)
await synthetic.readHeaderAndFooter()
yield synthetic
})
VhdSynthetic.open = Disposable.factory(async function* open(handler, paths, opts) {
const synthetic = new VhdSynthetic(yield Disposable.all(paths.map(path => openVhd(handler, path, opts))))
await synthetic.readHeaderAndFooter()
yield synthetic
})
exports.VhdSynthetic = VhdSynthetic