UNPKG

xo-vmdk-to-vhd

Version:
196 lines (195 loc) 8.29 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.default = void 0; var _assert = _interopRequireDefault(require("assert")); var _zlib = _interopRequireDefault(require("zlib")); var _definitions = require("./definitions"); var _virtualBuffer = require("./virtual-buffer"); function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; } const SECTOR_SIZE = 512; const HEADER_SIZE = 512; const VERSION_OFFSET = 4; function parseDescriptor(descriptorSlice) { const descriptorText = descriptorSlice.toString('ascii').replace(/\x00+$/, ''); const descriptorDict = {}; const extentList = []; const lines = descriptorText.split(/\r?\n/).filter(line => { return line.trim().length > 0 && line[0] !== '#'; }); for (const line of lines) { const defLine = line.split('='); if (defLine.length === 2 && defLine[0].indexOf('"') === -1) { descriptorDict[defLine[0]] = defLine[1].replace(/['"]+/g, ''); } else { const items = line.split(' '); extentList.push({ access: items[0], sizeSectors: items[1], size: items[1] * 512, type: items[2], name: items[3], offset: items.length > 4 ? items[4] : 0 }); } } return { descriptor: descriptorDict, extents: extentList }; } function readGrain(offsetSectors, buffer, compressed) { const offset = offsetSectors * SECTOR_SIZE; const size = buffer.readUInt32LE(offset + 8); const grainBuffer = buffer.slice(offset + 12, offset + 12 + size); const grainContent = compressed ? _zlib.default.inflateSync(grainBuffer) : grainBuffer; const lba = (0, _definitions.parseU64b)(buffer, offset, 'l2Lba'); return { offsetSectors, offset, lba, lbaBytes: lba * SECTOR_SIZE, size, buffer: grainBuffer, grain: grainContent, grainSize: grainContent.byteLength }; } function parseMarker(buffer) { const value = buffer.readUInt32LE(0); const size = buffer.readUInt32LE(8); const type = buffer.readUInt32LE(12); return { value, size, type }; } function alignSectors(number) { return Math.ceil(number / SECTOR_SIZE) * SECTOR_SIZE; } class VMDKDirectParser { constructor(readStream, grainLogicalAddressList, grainFileOffsetList, gzipped = false, length) { if (gzipped) { const unzipStream = _zlib.default.createGunzip(); readStream.pipe(unzipStream); readStream = unzipStream; } this.grainLogicalAddressList = grainLogicalAddressList; this.grainFileOffsetList = grainFileOffsetList; this.virtualBuffer = new _virtualBuffer.VirtualBuffer(readStream); this.header = null; this._length = length; } async _readL1() { const position = this.virtualBuffer.position; const l1entries = Math.floor((this.header.capacitySectors + this.header.l1EntrySectors - 1) / this.header.l1EntrySectors); const sectorAlignedL1Bytes = alignSectors(l1entries * 4); const l1Buffer = await this.virtualBuffer.readChunk(sectorAlignedL1Bytes, 'L1 table ' + position); let l2Start = 0; let l2IsContiguous = true; for (let i = 0; i < l1entries; i++) { const l1Entry = l1Buffer.readUInt32LE(i * 4); if (i > 0) { const previousL1Entry = l1Buffer.readUInt32LE((i - 1) * 4); l2IsContiguous = l2IsContiguous && l1Entry - previousL1Entry === 4; } else { l2IsContiguous = l1Entry * SECTOR_SIZE === this.virtualBuffer.position || l1Entry * SECTOR_SIZE === this.virtualBuffer.position + SECTOR_SIZE; l2Start = l1Entry * SECTOR_SIZE; } } if (!l2IsContiguous) { return null; } const l1L2FreeSpace = l2Start - this.virtualBuffer.position; if (l1L2FreeSpace > 0) { await this.virtualBuffer.readChunk(l1L2FreeSpace, 'freeSpace between L1 and L2'); } const l2entries = Math.ceil(this.header.capacitySectors / this.header.grainSizeSectors); const l2ByteSize = alignSectors(l1entries * this.header.numGTEsPerGT * 4); const l2Buffer = await this.virtualBuffer.readChunk(l2ByteSize, 'L2 table ' + position); let firstGrain = null; for (let i = 0; i < l2entries; i++) { const l2Entry = l2Buffer.readUInt32LE(i * 4); if (firstGrain === null) { firstGrain = l2Entry; } } const freeSpace = firstGrain * SECTOR_SIZE - this.virtualBuffer.position; if (freeSpace > 0) { await this.virtualBuffer.readChunk(freeSpace, 'freeSpace after L2'); } } async readHeader() { const headerBuffer = await this.virtualBuffer.readChunk(HEADER_SIZE, 'readHeader'); const magicString = headerBuffer.slice(0, 4).toString('ascii'); if (magicString !== 'KDMV') { throw new Error('not a VMDK file'); } const version = headerBuffer.readUInt32LE(VERSION_OFFSET); if (version !== 1 && version !== 3) { throw new Error('unsupported VMDK version ' + version + ', only version 1 and 3 are supported'); } this.header = (0, _definitions.unpackHeader)(headerBuffer); const descriptorLength = this.header.descriptorSizeSectors * SECTOR_SIZE; const descriptorBuffer = await this.virtualBuffer.readChunk(descriptorLength, 'descriptor'); this.descriptor = parseDescriptor(descriptorBuffer); let l1PositionBytes = null; if (this.header.grainDirectoryOffsetSectors !== -1 && this.header.grainDirectoryOffsetSectors !== 0) { l1PositionBytes = this.header.grainDirectoryOffsetSectors * SECTOR_SIZE; } const endOfDescriptor = this.virtualBuffer.position; if (l1PositionBytes !== null && (l1PositionBytes === endOfDescriptor || l1PositionBytes === endOfDescriptor + SECTOR_SIZE)) { if (l1PositionBytes === endOfDescriptor + SECTOR_SIZE) { await this.virtualBuffer.readChunk(SECTOR_SIZE, 'skipping L1 marker'); } await this._readL1(); } return this.header; } async parseMarkedGrain(expectedLogicalAddress) { const position = this.virtualBuffer.position; const sector = await this.virtualBuffer.readChunk(SECTOR_SIZE, `marker starting at ${position}`); const marker = parseMarker(sector); if (marker.size === 0) { throw new Error(`expected grain marker, received ${marker}`); } else if (marker.size > 10) { const grainDiskSize = marker.size + 12; const alignedGrainDiskSize = alignSectors(grainDiskSize); const remainOfBufferSize = alignedGrainDiskSize - SECTOR_SIZE; const remainderOfGrainBuffer = await this.virtualBuffer.readChunk(remainOfBufferSize, `grain remainder ${this.virtualBuffer.position} -> ${this.virtualBuffer.position + remainOfBufferSize}`); const grainBuffer = Buffer.concat([sector, remainderOfGrainBuffer]); const grainObject = readGrain(0, grainBuffer, this.header.compressionMethod === _definitions.compressionDeflate && this.header.flags.compressedGrains); _assert.default.strictEqual(grainObject.lba * SECTOR_SIZE, expectedLogicalAddress); return grainObject.grain; } } async *blockIterator() { for (let tableIndex = 0; tableIndex < this.grainFileOffsetList.length; tableIndex++) { const position = this.virtualBuffer.position; const grainPosition = this.grainFileOffsetList[tableIndex] * SECTOR_SIZE; const grainSizeBytes = this.header.grainSizeSectors * SECTOR_SIZE; const lba = this.grainLogicalAddressList[tableIndex] * grainSizeBytes; _assert.default.strictEqual(grainPosition >= position, true, `Grain position ${grainPosition} must be after current position ${position}`); await this.virtualBuffer.readChunk(grainPosition - position, `blank from ${position} to ${grainPosition}`); let grain; if (this.header.flags.hasMarkers) { grain = await this.parseMarkedGrain(lba); } else { grain = await this.virtualBuffer.readChunk(grainSizeBytes, 'grain ' + this.virtualBuffer.position); } yield { logicalAddressBytes: lba, data: grain }; } if (this._length !== undefined) { while (this.virtualBuffer.position < this._length) { await this.virtualBuffer.readChunk(Math.min(this._length - this.virtualBuffer.position, 1024 * 1024), 'draining'); } } } } exports.default = VMDKDirectParser; //# sourceMappingURL=vmdk-read.js.map