2022-02-22 12:29:26 +01:00
|
|
|
'use strict'
|
|
|
|
|
|
2021-12-23 10:31:29 +01:00
|
|
|
const {
|
|
|
|
|
BLOCK_UNUSED,
|
|
|
|
|
FOOTER_SIZE,
|
|
|
|
|
HEADER_SIZE,
|
|
|
|
|
PLATFORMS,
|
|
|
|
|
SECTOR_SIZE,
|
|
|
|
|
PARENT_LOCATOR_ENTRIES,
|
|
|
|
|
} = require('../_constants')
|
|
|
|
|
const { computeBatSize, sectorsToBytes, unpackHeader, unpackFooter, BUF_BLOCK_UNUSED } = require('./_utils')
|
|
|
|
|
const { createLogger } = require('@xen-orchestra/log')
|
|
|
|
|
const { fuFooter, fuHeader, checksumStruct } = require('../_structs')
|
|
|
|
|
const { set: mapSetBit } = require('../_bitmap')
|
|
|
|
|
const { VhdAbstract } = require('./VhdAbstract')
|
|
|
|
|
const assert = require('assert')
|
|
|
|
|
const getFirstAndLastBlocks = require('../_getFirstAndLastBlocks')
|
2016-01-22 15:49:45 +01:00
|
|
|
|
2021-10-18 14:56:58 +02:00
|
|
|
const { debug } = createLogger('vhd-lib:VhdFile')
|
2016-01-22 15:49:45 +01:00
|
|
|
|
|
|
|
|
// ===================================================================
|
|
|
|
|
//
|
|
|
|
|
// Spec:
|
|
|
|
|
// https://www.microsoft.com/en-us/download/details.aspx?id=23850
|
|
|
|
|
//
|
|
|
|
|
// C implementation:
|
|
|
|
|
// https://github.com/rubiojr/vhd-util-convert
|
|
|
|
|
//
|
|
|
|
|
// ===================================================================
|
|
|
|
|
|
|
|
|
|
// ===================================================================
|
|
|
|
|
|
2018-03-14 15:51:02 +01:00
|
|
|
// Format:
|
|
|
|
|
//
|
2018-03-16 17:47:10 +01:00
|
|
|
// 1. Footer (512)
|
|
|
|
|
// 2. Header (1024)
|
|
|
|
|
// 3. Unordered entries
|
|
|
|
|
// - BAT (batSize @ header.tableOffset)
|
|
|
|
|
// - Blocks (@ blockOffset(i))
|
|
|
|
|
// - bitmap (blockBitmapSize)
|
|
|
|
|
// - data (header.blockSize)
|
|
|
|
|
// - Parent locators (parentLocatorSize(i) @ parentLocatorOffset(i))
|
|
|
|
|
// 4. Footer (512 @ vhdSize - 512)
|
2018-03-14 15:51:02 +01:00
|
|
|
//
|
|
|
|
|
// Variables:
|
|
|
|
|
//
|
2018-03-16 17:47:10 +01:00
|
|
|
// - batSize = min(1, ceil(header.maxTableEntries * 4 / sectorSize)) * sectorSize
|
2018-03-14 15:51:02 +01:00
|
|
|
// - blockBitmapSize = ceil(header.blockSize / sectorSize / 8 / sectorSize) * sectorSize
|
|
|
|
|
// - blockOffset(i) = bat[i] * sectorSize
|
2018-03-16 17:47:10 +01:00
|
|
|
// - nBlocks = ceil(footer.currentSize / header.blockSize)
|
|
|
|
|
// - parentLocatorOffset(i) = header.parentLocatorEntry[i].platformDataOffset
|
|
|
|
|
// - parentLocatorSize(i) = header.parentLocatorEntry[i].platformDataSpace * sectorSize
|
2018-03-14 15:51:02 +01:00
|
|
|
// - sectorSize = 512
|
2018-05-14 04:48:16 -07:00
|
|
|
|
2021-12-23 10:31:29 +01:00
|
|
|
exports.VhdFile = class VhdFile extends VhdAbstract {
|
2021-10-18 14:56:58 +02:00
|
|
|
#uncheckedBlockTable
|
2021-12-01 20:16:54 +01:00
|
|
|
#header
|
|
|
|
|
footer
|
2021-10-18 14:56:58 +02:00
|
|
|
|
2021-10-18 23:13:55 +02:00
|
|
|
get #blockTable() {
|
2021-10-18 16:21:40 +02:00
|
|
|
assert.notStrictEqual(this.#uncheckedBlockTable, undefined, 'Block table must be initialized before access')
|
2021-10-18 14:56:58 +02:00
|
|
|
return this.#uncheckedBlockTable
|
|
|
|
|
}
|
|
|
|
|
|
2021-10-18 23:13:55 +02:00
|
|
|
set #blockTable(blockTable) {
|
|
|
|
|
this.#uncheckedBlockTable = blockTable
|
2021-10-18 14:56:58 +02:00
|
|
|
}
|
|
|
|
|
|
2018-12-11 10:37:46 +01:00
|
|
|
get batSize() {
|
2018-03-27 09:39:36 -07:00
|
|
|
return computeBatSize(this.header.maxTableEntries)
|
|
|
|
|
}
|
|
|
|
|
|
2021-10-18 14:56:58 +02:00
|
|
|
set header(header) {
|
2021-12-01 20:16:54 +01:00
|
|
|
this.#header = header
|
2021-10-18 14:56:58 +02:00
|
|
|
const size = this.batSize
|
|
|
|
|
this.#blockTable = Buffer.alloc(size)
|
|
|
|
|
for (let i = 0; i < this.header.maxTableEntries; i++) {
|
|
|
|
|
this.#blockTable.writeUInt32BE(BLOCK_UNUSED, i * 4)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
get header() {
|
2021-12-01 20:16:54 +01:00
|
|
|
return this.#header
|
2021-10-18 14:56:58 +02:00
|
|
|
}
|
|
|
|
|
|
2021-11-18 11:30:04 +01:00
|
|
|
static async open(handler, path, { flags, checkSecondFooter = true } = {}) {
|
|
|
|
|
const fd = await handler.openFile(path, flags ?? 'r+')
|
2021-10-18 14:56:58 +02:00
|
|
|
const vhd = new VhdFile(handler, fd)
|
|
|
|
|
// openning a file for reading does not trigger EISDIR as long as we don't really read from it :
|
|
|
|
|
// https://man7.org/linux/man-pages/man2/open.2.html
|
|
|
|
|
// EISDIR pathname refers to a directory and the access requested
|
|
|
|
|
// involved writing (that is, O_WRONLY or O_RDWR is set).
|
|
|
|
|
// reading the header ensure we have a well formed file immediatly
|
2021-11-18 11:30:04 +01:00
|
|
|
await vhd.readHeaderAndFooter(checkSecondFooter)
|
2021-10-18 14:56:58 +02:00
|
|
|
return {
|
|
|
|
|
dispose: () => handler.closeFile(fd),
|
2021-10-18 23:13:55 +02:00
|
|
|
value: vhd,
|
2021-10-18 14:56:58 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2021-11-18 11:30:04 +01:00
|
|
|
static async create(handler, path, { flags } = {}) {
|
|
|
|
|
const fd = await handler.openFile(path, flags ?? 'wx')
|
2021-10-18 14:56:58 +02:00
|
|
|
const vhd = new VhdFile(handler, fd)
|
|
|
|
|
return {
|
|
|
|
|
dispose: () => handler.closeFile(fd),
|
2021-10-18 23:13:55 +02:00
|
|
|
value: vhd,
|
2021-10-18 14:56:58 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2018-12-11 10:37:46 +01:00
|
|
|
constructor(handler, path) {
|
2021-10-18 14:56:58 +02:00
|
|
|
super()
|
2016-01-22 15:49:45 +01:00
|
|
|
this._handler = handler
|
|
|
|
|
this._path = path
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// =================================================================
|
|
|
|
|
// Read functions.
|
|
|
|
|
// =================================================================
|
|
|
|
|
|
2018-12-11 10:37:46 +01:00
|
|
|
async _read(start, n) {
|
2020-11-24 10:50:40 +01:00
|
|
|
const { bytesRead, buffer } = await this._handler.read(this._path, Buffer.alloc(n), start)
|
2018-08-10 11:37:47 +02:00
|
|
|
assert.strictEqual(bytesRead, n)
|
2018-06-07 17:19:33 +02:00
|
|
|
return buffer
|
2017-02-16 17:25:46 +01:00
|
|
|
}
|
2016-01-22 15:49:45 +01:00
|
|
|
// Returns the first address after metadata. (In bytes)
|
2019-10-30 10:18:29 +01:00
|
|
|
_getEndOfHeaders() {
|
2016-01-22 15:49:45 +01:00
|
|
|
const { header } = this
|
|
|
|
|
|
2018-05-14 04:48:16 -07:00
|
|
|
let end = FOOTER_SIZE + HEADER_SIZE
|
2016-01-22 15:49:45 +01:00
|
|
|
|
|
|
|
|
// Max(end, block allocation table end)
|
2018-03-27 09:39:36 -07:00
|
|
|
end = Math.max(end, header.tableOffset + this.batSize)
|
2016-01-22 15:49:45 +01:00
|
|
|
|
2018-05-14 04:48:16 -07:00
|
|
|
for (let i = 0; i < PARENT_LOCATOR_ENTRIES; i++) {
|
2016-01-22 15:49:45 +01:00
|
|
|
const entry = header.parentLocatorEntry[i]
|
|
|
|
|
|
2021-11-25 17:53:20 +01:00
|
|
|
if (entry.platformCode !== PLATFORMS.NONE) {
|
2020-11-24 10:50:40 +01:00
|
|
|
end = Math.max(end, entry.platformDataOffset + sectorsToBytes(entry.platformDataSpace))
|
2016-01-22 15:49:45 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
debug(`End of headers: ${end}.`)
|
|
|
|
|
|
|
|
|
|
return end
|
|
|
|
|
}
|
|
|
|
|
|
2021-10-18 14:56:58 +02:00
|
|
|
// return the first sector (bitmap) of a block
|
|
|
|
|
_getBatEntry(blockId) {
|
|
|
|
|
const i = blockId * 4
|
|
|
|
|
const blockTable = this.#blockTable
|
|
|
|
|
return i < blockTable.length ? blockTable.readUInt32BE(i) : BLOCK_UNUSED
|
|
|
|
|
}
|
|
|
|
|
|
2016-01-22 15:49:45 +01:00
|
|
|
// Returns the first sector after data.
|
2019-10-30 10:18:29 +01:00
|
|
|
_getEndOfData() {
|
|
|
|
|
let end = Math.ceil(this._getEndOfHeaders() / SECTOR_SIZE)
|
2016-01-22 15:49:45 +01:00
|
|
|
|
2021-10-18 14:56:58 +02:00
|
|
|
const sectorsOfFullBlock = this.sectorsOfBitmap + this.sectorsPerBlock
|
2016-01-22 15:49:45 +01:00
|
|
|
const { maxTableEntries } = this.header
|
|
|
|
|
for (let i = 0; i < maxTableEntries; i++) {
|
2017-02-21 15:42:51 +01:00
|
|
|
const blockAddr = this._getBatEntry(i)
|
2016-01-22 15:49:45 +01:00
|
|
|
|
|
|
|
|
if (blockAddr !== BLOCK_UNUSED) {
|
2021-10-18 14:56:58 +02:00
|
|
|
end = Math.max(end, blockAddr + sectorsOfFullBlock)
|
2016-01-22 15:49:45 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
debug(`End of data: ${end}.`)
|
|
|
|
|
|
|
|
|
|
return sectorsToBytes(end)
|
|
|
|
|
}
|
|
|
|
|
|
2021-10-18 14:56:58 +02:00
|
|
|
containsBlock(id) {
|
|
|
|
|
return this._getBatEntry(id) !== BLOCK_UNUSED
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// TODO:
|
2018-05-14 04:48:16 -07:00
|
|
|
// - better human reporting
|
|
|
|
|
// - auto repair if possible
|
2018-12-11 10:37:46 +01:00
|
|
|
async readHeaderAndFooter(checkSecondFooter = true) {
|
2018-05-14 04:48:16 -07:00
|
|
|
const buf = await this._read(0, FOOTER_SIZE + HEADER_SIZE)
|
|
|
|
|
const bufFooter = buf.slice(0, FOOTER_SIZE)
|
|
|
|
|
const bufHeader = buf.slice(FOOTER_SIZE)
|
2016-01-22 15:49:45 +01:00
|
|
|
|
2021-11-24 23:34:23 +01:00
|
|
|
const footer = unpackFooter(bufFooter)
|
|
|
|
|
const header = unpackHeader(bufHeader, footer)
|
2016-01-22 15:49:45 +01:00
|
|
|
|
2018-05-14 04:48:16 -07:00
|
|
|
if (checkSecondFooter) {
|
|
|
|
|
const size = await this._handler.getSize(this._path)
|
2020-11-24 10:50:40 +01:00
|
|
|
assert(bufFooter.equals(await this._read(size - FOOTER_SIZE, FOOTER_SIZE)), 'footer1 !== footer2')
|
2018-05-14 04:48:16 -07:00
|
|
|
}
|
|
|
|
|
|
2021-10-18 14:56:58 +02:00
|
|
|
this.footer = footer
|
|
|
|
|
this.header = header
|
2016-01-22 15:49:45 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Returns a buffer that contains the block allocation table of a vhd file.
|
2018-12-11 10:37:46 +01:00
|
|
|
async readBlockAllocationTable() {
|
2016-01-22 15:49:45 +01:00
|
|
|
const { header } = this
|
2021-10-18 14:56:58 +02:00
|
|
|
this.#blockTable = await this._read(header.tableOffset, header.maxTableEntries * 4)
|
2016-01-22 15:49:45 +01:00
|
|
|
}
|
|
|
|
|
|
2021-10-18 14:56:58 +02:00
|
|
|
readBlock(blockId, onlyBitmap = false) {
|
2017-02-22 14:42:09 +01:00
|
|
|
const blockAddr = this._getBatEntry(blockId)
|
2022-05-13 16:46:22 +02:00
|
|
|
assert(blockAddr !== BLOCK_UNUSED, `no such block ${blockId}`)
|
2016-01-22 15:49:45 +01:00
|
|
|
|
2020-11-24 10:50:40 +01:00
|
|
|
return this._read(sectorsToBytes(blockAddr), onlyBitmap ? this.bitmapSize : this.fullBlockSize).then(buf =>
|
2018-11-07 18:37:23 +01:00
|
|
|
onlyBitmap
|
|
|
|
|
? { id: blockId, bitmap: buf }
|
|
|
|
|
: {
|
|
|
|
|
id: blockId,
|
|
|
|
|
bitmap: buf.slice(0, this.bitmapSize),
|
|
|
|
|
data: buf.slice(this.bitmapSize),
|
2021-10-18 23:13:55 +02:00
|
|
|
buffer: buf,
|
2018-11-07 18:37:23 +01:00
|
|
|
}
|
2017-02-22 14:42:09 +01:00
|
|
|
)
|
2016-01-22 15:49:45 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// =================================================================
|
|
|
|
|
// Write functions.
|
|
|
|
|
// =================================================================
|
|
|
|
|
|
2019-05-03 14:07:10 +02:00
|
|
|
// Write a buffer at a given position in a vhd file.
|
2018-12-11 10:37:46 +01:00
|
|
|
async _write(data, offset) {
|
2019-05-03 14:07:10 +02:00
|
|
|
assert(Buffer.isBuffer(data))
|
|
|
|
|
debug(`_write offset=${offset} size=${data.length}`)
|
|
|
|
|
return this._handler.write(this._path, data, offset)
|
2017-02-16 16:11:24 +01:00
|
|
|
}
|
|
|
|
|
|
2018-12-11 10:37:46 +01:00
|
|
|
async _freeFirstBlockSpace(spaceNeededBytes) {
|
2021-10-18 14:56:58 +02:00
|
|
|
const firstAndLastBlocks = getFirstAndLastBlocks(this.#blockTable)
|
2019-04-01 07:53:03 -07:00
|
|
|
if (firstAndLastBlocks === undefined) {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const { first, firstSector, lastSector } = firstAndLastBlocks
|
|
|
|
|
const tableOffset = this.header.tableOffset
|
|
|
|
|
const { batSize } = this
|
2020-11-24 10:50:40 +01:00
|
|
|
const newMinSector = Math.ceil((tableOffset + batSize + spaceNeededBytes) / SECTOR_SIZE)
|
|
|
|
|
if (tableOffset + batSize + spaceNeededBytes >= sectorsToBytes(firstSector)) {
|
2019-04-01 07:53:03 -07:00
|
|
|
const { fullBlockSize } = this
|
2020-11-24 10:50:40 +01:00
|
|
|
const newFirstSector = Math.max(lastSector + fullBlockSize / SECTOR_SIZE, newMinSector)
|
|
|
|
|
debug(`freeFirstBlockSpace: move first block ${firstSector} -> ${newFirstSector}`)
|
2019-04-01 07:53:03 -07:00
|
|
|
// copy the first block at the end
|
|
|
|
|
const block = await this._read(sectorsToBytes(firstSector), fullBlockSize)
|
|
|
|
|
await this._write(block, sectorsToBytes(newFirstSector))
|
|
|
|
|
await this._setBatEntry(first, newFirstSector)
|
|
|
|
|
await this.writeFooter(true)
|
|
|
|
|
spaceNeededBytes -= this.fullBlockSize
|
|
|
|
|
if (spaceNeededBytes > 0) {
|
|
|
|
|
return this._freeFirstBlockSpace(spaceNeededBytes)
|
2018-03-27 09:39:36 -07:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2017-02-16 17:06:36 +01:00
|
|
|
|
2018-12-11 10:37:46 +01:00
|
|
|
async ensureBatSize(entries) {
|
2018-03-27 09:39:36 -07:00
|
|
|
const { header } = this
|
2017-02-17 17:53:45 +01:00
|
|
|
const prevMaxTableEntries = header.maxTableEntries
|
2018-03-27 09:39:36 -07:00
|
|
|
if (prevMaxTableEntries >= entries) {
|
2017-02-16 17:06:36 +01:00
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
2018-03-27 09:39:36 -07:00
|
|
|
const newBatSize = computeBatSize(entries)
|
|
|
|
|
await this._freeFirstBlockSpace(newBatSize - this.batSize)
|
|
|
|
|
const maxTableEntries = (header.maxTableEntries = entries)
|
2021-10-18 14:56:58 +02:00
|
|
|
const prevBat = this.#blockTable
|
|
|
|
|
const bat = (this.#blockTable = Buffer.allocUnsafe(newBatSize))
|
2017-02-21 15:54:18 +01:00
|
|
|
prevBat.copy(bat)
|
2018-05-14 04:48:16 -07:00
|
|
|
bat.fill(BUF_BLOCK_UNUSED, prevMaxTableEntries * 4)
|
2020-11-24 10:50:40 +01:00
|
|
|
debug(`ensureBatSize: extend BAT ${prevMaxTableEntries} -> ${maxTableEntries}`)
|
2018-03-27 09:39:36 -07:00
|
|
|
await this._write(
|
2019-05-03 14:07:10 +02:00
|
|
|
Buffer.alloc(maxTableEntries - prevMaxTableEntries, BUF_BLOCK_UNUSED),
|
2018-03-27 09:39:36 -07:00
|
|
|
header.tableOffset + prevBat.length
|
|
|
|
|
)
|
|
|
|
|
await this.writeHeader()
|
2017-02-16 17:06:36 +01:00
|
|
|
}
|
|
|
|
|
|
2017-02-16 16:38:46 +01:00
|
|
|
// set the first sector (bitmap) of a block
|
2018-12-11 10:37:46 +01:00
|
|
|
_setBatEntry(block, blockSector) {
|
2018-05-14 04:48:16 -07:00
|
|
|
const i = block * 4
|
2021-10-18 14:56:58 +02:00
|
|
|
const blockTable = this.#blockTable
|
2017-02-16 16:25:18 +01:00
|
|
|
|
2017-02-16 16:38:46 +01:00
|
|
|
blockTable.writeUInt32BE(blockSector, i)
|
2017-02-16 16:25:18 +01:00
|
|
|
|
2018-05-14 04:48:16 -07:00
|
|
|
return this._write(blockTable.slice(i, i + 4), this.header.tableOffset + i)
|
2016-01-22 15:49:45 +01:00
|
|
|
}
|
|
|
|
|
|
2020-10-19 22:30:57 +02:00
|
|
|
// Allocate a new uninitialized block in the BAT
|
2019-10-30 10:18:29 +01:00
|
|
|
async _createBlock(blockId) {
|
2020-10-19 22:33:58 +02:00
|
|
|
assert.strictEqual(this._getBatEntry(blockId), BLOCK_UNUSED)
|
|
|
|
|
|
2019-10-30 10:18:29 +01:00
|
|
|
const blockAddr = Math.ceil(this._getEndOfData() / SECTOR_SIZE)
|
2016-01-22 15:49:45 +01:00
|
|
|
|
2017-02-21 15:42:51 +01:00
|
|
|
debug(`create block ${blockId} at ${blockAddr}`)
|
2016-01-22 15:49:45 +01:00
|
|
|
|
2020-10-19 22:30:57 +02:00
|
|
|
await this._setBatEntry(blockId, blockAddr)
|
2016-01-22 15:49:45 +01:00
|
|
|
|
|
|
|
|
return blockAddr
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Write a bitmap at a block address.
|
2019-10-30 10:18:29 +01:00
|
|
|
async _writeBlockBitmap(blockAddr, bitmap) {
|
2016-01-22 15:49:45 +01:00
|
|
|
const { bitmapSize } = this
|
|
|
|
|
|
|
|
|
|
if (bitmap.length !== bitmapSize) {
|
|
|
|
|
throw new Error(`Bitmap length is not correct ! ${bitmap.length}`)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const offset = sectorsToBytes(blockAddr)
|
|
|
|
|
|
2020-11-24 10:50:40 +01:00
|
|
|
debug(`Write bitmap at: ${offset}. (size=${bitmapSize}, data=${bitmap.toString('hex')})`)
|
2016-01-22 15:49:45 +01:00
|
|
|
await this._write(bitmap, sectorsToBytes(blockAddr))
|
|
|
|
|
}
|
|
|
|
|
|
2021-10-18 14:56:58 +02:00
|
|
|
async writeEntireBlock(block) {
|
2018-03-02 11:08:01 -07:00
|
|
|
let blockAddr = this._getBatEntry(block.id)
|
|
|
|
|
|
|
|
|
|
if (blockAddr === BLOCK_UNUSED) {
|
2019-10-30 10:18:29 +01:00
|
|
|
blockAddr = await this._createBlock(block.id)
|
2018-03-02 11:08:01 -07:00
|
|
|
}
|
|
|
|
|
await this._write(block.buffer, sectorsToBytes(blockAddr))
|
|
|
|
|
}
|
|
|
|
|
|
2019-10-30 10:18:29 +01:00
|
|
|
async _writeBlockSectors(block, beginSectorId, endSectorId, parentBitmap) {
|
2017-02-16 16:38:46 +01:00
|
|
|
let blockAddr = this._getBatEntry(block.id)
|
2016-01-22 15:49:45 +01:00
|
|
|
|
|
|
|
|
if (blockAddr === BLOCK_UNUSED) {
|
2019-10-30 10:18:29 +01:00
|
|
|
blockAddr = await this._createBlock(block.id)
|
2018-03-27 09:39:36 -07:00
|
|
|
parentBitmap = Buffer.alloc(this.bitmapSize, 0)
|
|
|
|
|
} else if (parentBitmap === undefined) {
|
2021-10-18 14:56:58 +02:00
|
|
|
parentBitmap = (await this.readBlock(block.id, true)).bitmap
|
2016-01-22 15:49:45 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const offset = blockAddr + this.sectorsOfBitmap + beginSectorId
|
|
|
|
|
|
2020-11-24 10:50:40 +01:00
|
|
|
debug(`_writeBlockSectors at ${offset} block=${block.id}, sectors=${beginSectorId}...${endSectorId}`)
|
2016-01-22 15:49:45 +01:00
|
|
|
|
2018-03-02 11:08:01 -07:00
|
|
|
for (let i = beginSectorId; i < endSectorId; ++i) {
|
|
|
|
|
mapSetBit(parentBitmap, i)
|
|
|
|
|
}
|
|
|
|
|
|
2019-10-30 10:18:29 +01:00
|
|
|
await this._writeBlockBitmap(blockAddr, parentBitmap)
|
2016-01-22 15:49:45 +01:00
|
|
|
await this._write(
|
2020-11-24 10:50:40 +01:00
|
|
|
block.data.slice(sectorsToBytes(beginSectorId), sectorsToBytes(endSectorId)),
|
2016-01-22 15:49:45 +01:00
|
|
|
sectorsToBytes(offset)
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
2016-09-29 08:31:36 -07:00
|
|
|
// Write a context footer. (At the end and beginning of a vhd file.)
|
2018-12-11 10:37:46 +01:00
|
|
|
async writeFooter(onlyEndFooter = false) {
|
2016-01-22 15:49:45 +01:00
|
|
|
const { footer } = this
|
|
|
|
|
|
|
|
|
|
const rawFooter = fuFooter.pack(footer)
|
2018-03-27 09:39:36 -07:00
|
|
|
const eof = await this._handler.getSize(this._path)
|
|
|
|
|
// sometimes the file is longer than anticipated, we still need to put the footer at the end
|
2019-10-30 10:18:29 +01:00
|
|
|
const offset = Math.max(this._getEndOfData(), eof - rawFooter.length)
|
2016-01-22 15:49:45 +01:00
|
|
|
|
2016-10-13 18:49:58 +02:00
|
|
|
footer.checksum = checksumStruct(rawFooter, fuFooter)
|
2020-11-24 10:50:40 +01:00
|
|
|
debug(`Write footer at: ${offset} (checksum=${footer.checksum}). (data=${rawFooter.toString('hex')})`)
|
2018-03-27 09:39:36 -07:00
|
|
|
if (!onlyEndFooter) {
|
|
|
|
|
await this._write(rawFooter, 0)
|
|
|
|
|
}
|
2016-03-03 18:25:10 +01:00
|
|
|
await this._write(rawFooter, offset)
|
2016-01-22 15:49:45 +01:00
|
|
|
}
|
2016-09-29 08:31:36 -07:00
|
|
|
|
2018-12-11 10:37:46 +01:00
|
|
|
writeHeader() {
|
2016-09-29 08:31:36 -07:00
|
|
|
const { header } = this
|
|
|
|
|
const rawHeader = fuHeader.pack(header)
|
2016-10-13 18:49:58 +02:00
|
|
|
header.checksum = checksumStruct(rawHeader, fuHeader)
|
2018-05-14 04:48:16 -07:00
|
|
|
const offset = FOOTER_SIZE
|
2020-11-24 10:50:40 +01:00
|
|
|
debug(`Write header at: ${offset} (checksum=${header.checksum}). (data=${rawHeader.toString('hex')})`)
|
2017-02-21 18:10:50 +01:00
|
|
|
return this._write(rawHeader, offset)
|
2016-09-29 08:31:36 -07:00
|
|
|
}
|
2018-03-27 09:39:36 -07:00
|
|
|
|
2021-10-18 14:56:58 +02:00
|
|
|
writeBlockAllocationTable() {
|
|
|
|
|
const header = this.header
|
|
|
|
|
const blockTable = this.#blockTable
|
|
|
|
|
debug(`Write BlockAllocationTable at: ${header.tableOffset} ). (data=${blockTable.toString('hex')})`)
|
|
|
|
|
return this._write(blockTable, header.tableOffset)
|
|
|
|
|
}
|
|
|
|
|
|
2018-12-11 10:37:46 +01:00
|
|
|
async writeData(offsetSectors, buffer) {
|
2018-05-14 04:48:16 -07:00
|
|
|
const bufferSizeSectors = Math.ceil(buffer.length / SECTOR_SIZE)
|
2018-03-27 09:39:36 -07:00
|
|
|
const startBlock = Math.floor(offsetSectors / this.sectorsPerBlock)
|
|
|
|
|
const endBufferSectors = offsetSectors + bufferSizeSectors
|
|
|
|
|
const lastBlock = Math.ceil(endBufferSectors / this.sectorsPerBlock) - 1
|
|
|
|
|
await this.ensureBatSize(lastBlock)
|
2018-05-14 04:48:16 -07:00
|
|
|
const blockSizeBytes = this.sectorsPerBlock * SECTOR_SIZE
|
2018-03-27 09:39:36 -07:00
|
|
|
const coversWholeBlock = (offsetInBlockSectors, endInBlockSectors) =>
|
|
|
|
|
offsetInBlockSectors === 0 && endInBlockSectors === this.sectorsPerBlock
|
|
|
|
|
|
2020-11-24 10:50:40 +01:00
|
|
|
for (let currentBlock = startBlock; currentBlock <= lastBlock; currentBlock++) {
|
|
|
|
|
const offsetInBlockSectors = Math.max(0, offsetSectors - currentBlock * this.sectorsPerBlock)
|
|
|
|
|
const endInBlockSectors = Math.min(endBufferSectors - currentBlock * this.sectorsPerBlock, this.sectorsPerBlock)
|
|
|
|
|
const startInBuffer = Math.max(0, (currentBlock * this.sectorsPerBlock - offsetSectors) * SECTOR_SIZE)
|
2018-03-27 09:39:36 -07:00
|
|
|
const endInBuffer = Math.min(
|
2020-11-24 10:50:40 +01:00
|
|
|
((currentBlock + 1) * this.sectorsPerBlock - offsetSectors) * SECTOR_SIZE,
|
2018-03-27 09:39:36 -07:00
|
|
|
buffer.length
|
|
|
|
|
)
|
|
|
|
|
let inputBuffer
|
|
|
|
|
if (coversWholeBlock(offsetInBlockSectors, endInBlockSectors)) {
|
|
|
|
|
inputBuffer = buffer.slice(startInBuffer, endInBuffer)
|
|
|
|
|
} else {
|
|
|
|
|
inputBuffer = Buffer.alloc(blockSizeBytes, 0)
|
2020-11-24 10:50:40 +01:00
|
|
|
buffer.copy(inputBuffer, offsetInBlockSectors * SECTOR_SIZE, startInBuffer, endInBuffer)
|
2018-03-27 09:39:36 -07:00
|
|
|
}
|
2020-11-24 10:50:40 +01:00
|
|
|
await this._writeBlockSectors({ id: currentBlock, data: inputBuffer }, offsetInBlockSectors, endInBlockSectors)
|
2018-03-27 09:39:36 -07:00
|
|
|
}
|
|
|
|
|
await this.writeFooter()
|
|
|
|
|
}
|
|
|
|
|
|
2019-10-30 10:18:29 +01:00
|
|
|
async _ensureSpaceForParentLocators(neededSectors) {
|
2018-05-14 04:48:16 -07:00
|
|
|
const firstLocatorOffset = FOOTER_SIZE + HEADER_SIZE
|
2020-11-24 10:50:40 +01:00
|
|
|
const currentSpace = Math.floor(this.header.tableOffset / SECTOR_SIZE) - firstLocatorOffset / SECTOR_SIZE
|
2018-03-27 09:39:36 -07:00
|
|
|
if (currentSpace < neededSectors) {
|
|
|
|
|
const deltaSectors = neededSectors - currentSpace
|
|
|
|
|
await this._freeFirstBlockSpace(sectorsToBytes(deltaSectors))
|
|
|
|
|
this.header.tableOffset += sectorsToBytes(deltaSectors)
|
2021-10-18 14:56:58 +02:00
|
|
|
await this._write(this.#blockTable, this.header.tableOffset)
|
2018-03-27 09:39:36 -07:00
|
|
|
}
|
|
|
|
|
return firstLocatorOffset
|
|
|
|
|
}
|
2018-03-16 17:47:10 +01:00
|
|
|
|
2021-10-18 14:56:58 +02:00
|
|
|
async _readParentLocatorData(parentLocatorId) {
|
|
|
|
|
const { platformDataOffset, platformDataLength } = this.header.parentLocatorEntry[parentLocatorId]
|
|
|
|
|
if (platformDataLength > 0) {
|
2021-12-07 14:14:39 +01:00
|
|
|
return await this._read(platformDataOffset, platformDataLength)
|
2021-10-18 14:56:58 +02:00
|
|
|
}
|
|
|
|
|
return Buffer.alloc(0)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async _writeParentLocatorData(parentLocatorId, data) {
|
|
|
|
|
let position
|
2018-05-14 04:48:16 -07:00
|
|
|
const { header } = this
|
2021-10-18 14:56:58 +02:00
|
|
|
if (data.length === 0) {
|
|
|
|
|
// reset offset if data is empty
|
|
|
|
|
header.parentLocatorEntry[parentLocatorId].platformDataOffset = 0
|
|
|
|
|
} else {
|
2021-12-01 20:16:12 +01:00
|
|
|
const space = header.parentLocatorEntry[parentLocatorId].platformDataSpace * SECTOR_SIZE
|
|
|
|
|
if (data.length <= space) {
|
2021-10-18 14:56:58 +02:00
|
|
|
// new parent locator length is smaller than available space : keep it in place
|
|
|
|
|
position = header.parentLocatorEntry[parentLocatorId].platformDataOffset
|
|
|
|
|
} else {
|
2021-11-04 15:38:27 +01:00
|
|
|
const firstAndLastBlocks = getFirstAndLastBlocks(this.#blockTable)
|
|
|
|
|
if (firstAndLastBlocks === undefined) {
|
|
|
|
|
// no block in data : put the parent locatorn entry at the end
|
|
|
|
|
position = this._getEndOfData()
|
|
|
|
|
} else {
|
|
|
|
|
// need more size
|
|
|
|
|
|
|
|
|
|
// since there can be multiple parent locator entry, we can't extend the entry in place
|
|
|
|
|
// move the first(s) block(s) at the end of the data
|
|
|
|
|
// move the parent locator to the precedent position of the first block
|
|
|
|
|
const { firstSector } = firstAndLastBlocks
|
2021-12-01 20:16:12 +01:00
|
|
|
await this._freeFirstBlockSpace(space)
|
2021-11-04 15:38:27 +01:00
|
|
|
position = sectorsToBytes(firstSector)
|
|
|
|
|
}
|
2021-10-18 14:56:58 +02:00
|
|
|
}
|
|
|
|
|
await this._write(data, position)
|
|
|
|
|
header.parentLocatorEntry[parentLocatorId].platformDataOffset = position
|
2018-03-16 17:47:10 +01:00
|
|
|
}
|
2018-03-26 16:26:12 +02:00
|
|
|
}
|
2021-11-22 17:14:29 +01:00
|
|
|
|
|
|
|
|
async getSize() {
|
|
|
|
|
return await this._handler.getSize(this._path)
|
|
|
|
|
}
|
2018-03-12 17:26:20 +01:00
|
|
|
}
|