UNPKG

@nraynaud/xo-vmdk-to-vhd

Version:
606 lines (515 loc) 19.4 kB
'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