@nraynaud/xo-vmdk-to-vhd
Version:
JS lib streaming a vmdk file to a vhd
606 lines (515 loc) • 19.4 kB
JavaScript
'use strict';
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.convertFromVMDK = exports.ReadableRawVHDStream = exports.VHDFile = undefined;
var _getPrototypeOf = require('babel-runtime/core-js/object/get-prototype-of');
var _getPrototypeOf2 = _interopRequireDefault(_getPrototypeOf);
var _possibleConstructorReturn2 = require('babel-runtime/helpers/possibleConstructorReturn');
var _possibleConstructorReturn3 = _interopRequireDefault(_possibleConstructorReturn2);
var _inherits2 = require('babel-runtime/helpers/inherits');
var _inherits3 = _interopRequireDefault(_inherits2);
var _regenerator = require('babel-runtime/regenerator');
var _regenerator2 = _interopRequireDefault(_regenerator);
var _asyncToGenerator2 = require('babel-runtime/helpers/asyncToGenerator');
var _asyncToGenerator3 = _interopRequireDefault(_asyncToGenerator2);
var _classCallCheck2 = require('babel-runtime/helpers/classCallCheck');
var _classCallCheck3 = _interopRequireDefault(_classCallCheck2);
var _createClass2 = require('babel-runtime/helpers/createClass');
var _createClass3 = _interopRequireDefault(_createClass2);
var convertFromVMDK = exports.convertFromVMDK = function () {
var _ref6 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee6(vmdkReadStream) {
var parser, header;
return _regenerator2.default.wrap(function _callee6$(_context6) {
while (1) {
switch (_context6.prev = _context6.next) {
case 0:
parser = new _vmdkRead.VMDKDirectParser(vmdkReadStream);
_context6.next = 3;
return parser.readHeader();
case 3:
header = _context6.sent;
return _context6.abrupt('return', new ReadableRawVHDStream(header.capacitySectors * sectorSize, parser));
case 5:
case 'end':
return _context6.stop();
}
}
}, _callee6, this);
}));
return function convertFromVMDK(_x9) {
return _ref6.apply(this, arguments);
};
}();
exports.computeChecksum = computeChecksum;
exports.computeGeometryForSize = computeGeometryForSize;
exports.createFooter = createFooter;
exports.createDynamicDiskHeader = createDynamicDiskHeader;
exports.createEmptyTable = createEmptyTable;
var _fsPromise = require('fs-promise');
var _stream = require('stream');
var _stream2 = _interopRequireDefault(_stream);
var _vmdkRead = require('./vmdk-read');
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
var footerCookie = 'conectix';
var creatorApp = 'xo ';
// it looks like everybody is using Wi2k
var osString = 'Wi2k';
var headerCookie = 'cxsparse';
var fixedHardDiskType = 2;
var dynamicHardDiskType = 3;
var sectorSize = 512;
function computeChecksum(buffer) {
var sum = 0;
for (var i = 0; i < buffer.length; i++) {
sum += buffer[i];
}
// http://stackoverflow.com/a/1908655/72637 the >>> prevents the number from going negative
return ~sum >>> 0;
}
var Block = function () {
function Block(blockSize) {
(0, _classCallCheck3.default)(this, Block);
var bitmapSize = blockSize / sectorSize / 8;
var bufferSize = Math.ceil((blockSize + bitmapSize) / sectorSize) * sectorSize;
this.buffer = new Buffer(bufferSize);
this.buffer.fill(0);
this.bitmapBuffer = this.buffer.slice(0, bitmapSize);
this.dataBuffer = this.buffer.slice(bitmapSize);
this.bitmapBuffer.fill(0xff);
}
(0, _createClass3.default)(Block, [{
key: 'writeData',
value: function writeData(buffer) {
var offset = arguments.length <= 1 || arguments[1] === undefined ? 0 : arguments[1];
buffer.copy(this.dataBuffer, offset);
}
}, {
key: 'writeOnFile',
value: function () {
var _ref = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee(file) {
return _regenerator2.default.wrap(function _callee$(_context) {
while (1) {
switch (_context.prev = _context.next) {
case 0:
_context.next = 2;
return (0, _fsPromise.write)(file, this.buffer, 0, this.buffer.length);
case 2:
case 'end':
return _context.stop();
}
}
}, _callee, this);
}));
function writeOnFile(_x2) {
return _ref.apply(this, arguments);
}
return writeOnFile;
}()
}]);
return Block;
}();
var SparseExtent = function () {
function SparseExtent(dataSize, blockSize, startOffset) {
(0, _classCallCheck3.default)(this, SparseExtent);
this.table = createEmptyTable(dataSize, blockSize);
this.blockSize = blockSize;
this.startOffset = (startOffset + this.table.buffer.length) / sectorSize;
}
(0, _createClass3.default)(SparseExtent, [{
key: '_writeBlock',
value: function _writeBlock(blockBuffer, tableIndex, offset) {
if (blockBuffer.length + offset > this.blockSize) {
throw new Error('invalid block geometry');
}
var entry = this.table.entries[tableIndex];
if (entry === undefined) {
entry = new Block(this.blockSize);
this.table.entries[tableIndex] = entry;
}
entry.writeData(blockBuffer, offset);
}
}, {
key: 'writeBuffer',
value: function writeBuffer(buffer) {
var offset = arguments.length <= 1 || arguments[1] === undefined ? 0 : arguments[1];
var startBlock = Math.floor(offset / this.blockSize);
var endBlock = Math.ceil((offset + buffer.length) / this.blockSize);
for (var i = startBlock; i < endBlock; i++) {
var blockDelta = offset - i * this.blockSize;
var blockBuffer = void 0,
blockOffset = void 0;
if (blockDelta > 0) {
blockBuffer = buffer.slice(0, (i + 1) * this.blockSize - offset);
blockOffset = blockDelta;
} else {
blockBuffer = buffer.slice(-blockDelta, (i + 1) * this.blockSize - offset);
blockOffset = 0;
}
this._writeBlock(blockBuffer, i, blockOffset);
}
}
}, {
key: 'writeOnFile',
value: function () {
var _ref2 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee2(file) {
var currentOffset, i, block, _i, _block;
return _regenerator2.default.wrap(function _callee2$(_context2) {
while (1) {
switch (_context2.prev = _context2.next) {
case 0:
currentOffset = this.startOffset;
for (i = 0; i < this.table.entryCount; i++) {
block = this.table.entries[i];
if (block !== undefined) {
this.table.buffer.writeUInt32BE(currentOffset, i * 4);
currentOffset += block.buffer.length / sectorSize;
}
}
_context2.next = 4;
return (0, _fsPromise.write)(file, this.table.buffer, 0, this.table.buffer.length);
case 4:
_i = 0;
case 5:
if (!(_i < this.table.entryCount)) {
_context2.next = 13;
break;
}
_block = this.table.entries[_i];
if (!(_block !== undefined)) {
_context2.next = 10;
break;
}
_context2.next = 10;
return _block.writeOnFile(file);
case 10:
_i++;
_context2.next = 5;
break;
case 13:
case 'end':
return _context2.stop();
}
}
}, _callee2, this);
}));
function writeOnFile(_x4) {
return _ref2.apply(this, arguments);
}
return writeOnFile;
}()
}, {
key: 'entryCount',
get: function get() {
return this.table.entryCount;
}
}]);
return SparseExtent;
}();
var VHDFile = exports.VHDFile = function () {
function VHDFile(virtualSize, timestamp) {
(0, _classCallCheck3.default)(this, VHDFile);
this.geomtry = computeGeometryForSize(virtualSize);
this.timestamp = timestamp;
this.blockSize = 0x00200000;
this.sparseFile = new SparseExtent(this.geomtry.actualSize, this.blockSize, sectorSize * 3);
}
(0, _createClass3.default)(VHDFile, [{
key: 'writeBuffer',
value: function writeBuffer(buffer) {
var offset = arguments.length <= 1 || arguments[1] === undefined ? 0 : arguments[1];
this.sparseFile.writeBuffer(buffer, offset);
}
}, {
key: 'writeFile',
value: function () {
var _ref3 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee3(fileName) {
var fileFooter, diskHeader, file;
return _regenerator2.default.wrap(function _callee3$(_context3) {
while (1) {
switch (_context3.prev = _context3.next) {
case 0:
fileFooter = createFooter(this.geomtry.actualSize, this.timestamp, this.geomtry, dynamicHardDiskType, 512, 0);
diskHeader = createDynamicDiskHeader(this.sparseFile.entryCount, this.blockSize);
_context3.next = 4;
return (0, _fsPromise.open)(fileName, 'w');
case 4:
file = _context3.sent;
_context3.next = 7;
return (0, _fsPromise.write)(file, fileFooter, 0, fileFooter.length);
case 7:
_context3.next = 9;
return (0, _fsPromise.write)(file, diskHeader, 0, diskHeader.length);
case 9:
_context3.next = 11;
return this.sparseFile.writeOnFile(file);
case 11:
_context3.next = 13;
return (0, _fsPromise.write)(file, fileFooter, 0, fileFooter.length);
case 13:
case 'end':
return _context3.stop();
}
}
}, _callee3, this);
}));
function writeFile(_x6) {
return _ref3.apply(this, arguments);
}
return writeFile;
}()
}]);
return VHDFile;
}();
function computeGeometryForSize(size) {
var totalSectors = Math.ceil(size / 512);
var sectorsPerTrack = void 0;
var heads = void 0;
var cylinderTimesHeads = void 0;
if (totalSectors > 65535 * 16 * 255) {
throw Error('disk is too big');
}
// straight copypasta from the file spec appendix on CHS Calculation
if (totalSectors >= 65535 * 16 * 63) {
sectorsPerTrack = 255;
heads = 16;
cylinderTimesHeads = totalSectors / sectorsPerTrack;
} else {
sectorsPerTrack = 17;
cylinderTimesHeads = totalSectors / sectorsPerTrack;
heads = Math.floor((cylinderTimesHeads + 1023) / 1024);
if (heads < 4) {
heads = 4;
}
if (cylinderTimesHeads >= heads * 1024 || heads > 16) {
sectorsPerTrack = 31;
heads = 16;
cylinderTimesHeads = totalSectors / sectorsPerTrack;
}
if (cylinderTimesHeads >= heads * 1024) {
sectorsPerTrack = 63;
heads = 16;
cylinderTimesHeads = totalSectors / sectorsPerTrack;
}
}
var cylinders = Math.floor(cylinderTimesHeads / heads);
var actualSize = cylinders * heads * sectorsPerTrack * sectorSize;
return { cylinders: cylinders, heads: heads, sectorsPerTrack: sectorsPerTrack, actualSize: actualSize };
}
function createFooter(size, timestamp, geometry, diskType) {
var dataOffsetLow = arguments.length <= 4 || arguments[4] === undefined ? 0xFFFFFFFF : arguments[4];
var dataOffsetHigh = arguments.length <= 5 || arguments[5] === undefined ? 0xFFFFFFFF : arguments[5];
var footer = new Buffer(512);
footer.fill(0);
new Buffer(footerCookie, 'ascii').copy(footer);
footer.writeUInt32BE(2, 8);
footer.writeUInt32BE(0x00010000, 12);
footer.writeUInt32BE(dataOffsetHigh, 16);
footer.writeUInt32BE(dataOffsetLow, 20);
footer.writeUInt32BE(timestamp, 24);
new Buffer(creatorApp, 'ascii').copy(footer, 28);
new Buffer(osString, 'ascii').copy(footer, 36);
// do not use & 0xFFFFFFFF to extract lower bits, that would propagate a negative sign if the 2^31 bit is one
var sizeHigh = Math.floor(size / Math.pow(2, 32)) % Math.pow(2, 32);
var sizeLow = size % Math.pow(2, 32);
footer.writeUInt32BE(sizeHigh, 40);
footer.writeUInt32BE(sizeLow, 44);
footer.writeUInt32BE(sizeHigh, 48);
footer.writeUInt32BE(sizeLow, 52);
footer.writeUInt16BE(geometry['cylinders'], 56);
footer.writeUInt8(geometry['heads'], 58);
footer.writeUInt8(geometry['sectorsPerTrack'], 59);
footer.writeUInt32BE(diskType, 60);
var checksum = computeChecksum(footer);
footer.writeUInt32BE(checksum, 64);
return footer;
}
function createDynamicDiskHeader(tableEntries, blockSize) {
var header = new Buffer(1024);
header.fill(0);
new Buffer(headerCookie, 'ascii').copy(header);
// hard code no next data
header.writeUInt32BE(0xFFFFFFFF, 8);
header.writeUInt32BE(0xFFFFFFFF, 12);
// hard code table offset
header.writeUInt32BE(0, 16);
header.writeUInt32BE(sectorSize * 3, 20);
header.writeUInt32BE(0x00010000, 24);
header.writeUInt32BE(tableEntries, 28);
header.writeUInt32BE(blockSize, 32);
var checksum = computeChecksum(header);
header.writeUInt32BE(checksum, 36);
return header;
}
function createEmptyTable(dataSize, blockSize) {
var blockCount = Math.ceil(dataSize / blockSize);
var tableSizeSectors = Math.ceil(blockCount * 4 / sectorSize);
var buffer = new Buffer(tableSizeSectors * sectorSize);
buffer.fill(0xff);
return { entryCount: blockCount, buffer: buffer, entries: [] };
}
var ReadableRawVHDStream = exports.ReadableRawVHDStream = function (_stream$Readable) {
(0, _inherits3.default)(ReadableRawVHDStream, _stream$Readable);
function ReadableRawVHDStream(size, vmdkParser) {
(0, _classCallCheck3.default)(this, ReadableRawVHDStream);
var _this = (0, _possibleConstructorReturn3.default)(this, (0, _getPrototypeOf2.default)(ReadableRawVHDStream).call(this));
_this.size = size;
var geometry = computeGeometryForSize(size);
_this.footer = createFooter(size, Math.floor(Date.now() / 1000), geometry, fixedHardDiskType);
_this.position = 0;
_this.vmdkParser = vmdkParser;
_this.done = false;
_this.busy = false;
_this.currentFile = [];
return _this;
}
(0, _createClass3.default)(ReadableRawVHDStream, [{
key: 'filePadding',
value: function filePadding(paddingLength) {
var _this2 = this;
if (paddingLength !== 0) {
(function () {
var chunkSize = 1024 * 1024; // 1Mo
var chunkCount = Math.floor(paddingLength / chunkSize);
for (var i = 0; i < chunkCount; i++) {
_this2.currentFile.push(function () {
var paddingBuffer = new Buffer(chunkSize);
paddingBuffer.fill(0);
return paddingBuffer;
});
}
_this2.currentFile.push(function () {
var paddingBuffer = new Buffer(paddingLength % chunkSize);
paddingBuffer.fill(0);
return paddingBuffer;
});
})();
}
}
}, {
key: 'pushNextBlock',
value: function () {
var _ref4 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee4() {
var _this3 = this;
var next, paddingLength;
return _regenerator2.default.wrap(function _callee4$(_context4) {
while (1) {
switch (_context4.prev = _context4.next) {
case 0:
_context4.next = 2;
return this.vmdkParser.next();
case 2:
next = _context4.sent;
if (next === null) {
paddingLength = this.size - this.position;
this.filePadding(paddingLength);
this.currentFile.push(function () {
return _this3.footer;
});
this.currentFile.push(function () {
_this3.done = true;
return null;
});
} else {
(function () {
var offset = next.lbaBytes;
var buffer = next.grain;
var paddingLength = offset - _this3.position;
if (paddingLength < 0) {
process.nextTick(function () {
return _this3.emit('error', 'This VMDK file does not have its blocks in the correct order');
});
}
_this3.filePadding(paddingLength);
_this3.currentFile.push(function () {
return buffer;
});
_this3.position = offset + buffer.length;
})();
}
return _context4.abrupt('return', this.pushFileUntilFull());
case 5:
case 'end':
return _context4.stop();
}
}
}, _callee4, this);
}));
function pushNextBlock() {
return _ref4.apply(this, arguments);
}
return pushNextBlock;
}()
// returns true if the file is empty
}, {
key: 'pushFileUntilFull',
value: function pushFileUntilFull() {
while (true) {
if (this.currentFile.length === 0) {
break;
}
var result = this.push(this.currentFile.shift()());
if (!result) {
break;
}
}
return this.currentFile.length === 0;
}
}, {
key: 'pushNextUntilFull',
value: function () {
var _ref5 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee5() {
return _regenerator2.default.wrap(function _callee5$(_context5) {
while (1) {
switch (_context5.prev = _context5.next) {
case 0:
_context5.t0 = !this.done;
if (!_context5.t0) {
_context5.next = 5;
break;
}
_context5.next = 4;
return this.pushNextBlock();
case 4:
_context5.t0 = _context5.sent;
case 5:
if (!_context5.t0) {
_context5.next = 8;
break;
}
_context5.next = 0;
break;
case 8:
case 'end':
return _context5.stop();
}
}
}, _callee5, this);
}));
function pushNextUntilFull() {
return _ref5.apply(this, arguments);
}
return pushNextUntilFull;
}()
}, {
key: '_read',
value: function _read() {
var _this4 = this;
if (this.busy || this.done) {
return;
}
if (this.pushFileUntilFull()) {
this.busy = true;
this.pushNextUntilFull().then(function () {
_this4.busy = false;
}).catch(function (error) {
process.nextTick(function () {
return _this4.emit('error', error);
});
});
}
}
}]);
return ReadableRawVHDStream;
}(_stream2.default.Readable);
//# sourceMappingURL=vhd-write.js.map