UNPKG

19.4 kBJavaScriptView Raw
1'use strict';
2
3Object.defineProperty(exports, "__esModule", {
4 value: true
5});
6exports.convertFromVMDK = exports.ReadableRawVHDStream = exports.VHDFile = undefined;
7
8var _getPrototypeOf = require('babel-runtime/core-js/object/get-prototype-of');
9
10var _getPrototypeOf2 = _interopRequireDefault(_getPrototypeOf);
11
12var _possibleConstructorReturn2 = require('babel-runtime/helpers/possibleConstructorReturn');
13
14var _possibleConstructorReturn3 = _interopRequireDefault(_possibleConstructorReturn2);
15
16var _inherits2 = require('babel-runtime/helpers/inherits');
17
18var _inherits3 = _interopRequireDefault(_inherits2);
19
20var _regenerator = require('babel-runtime/regenerator');
21
22var _regenerator2 = _interopRequireDefault(_regenerator);
23
24var _asyncToGenerator2 = require('babel-runtime/helpers/asyncToGenerator');
25
26var _asyncToGenerator3 = _interopRequireDefault(_asyncToGenerator2);
27
28var _classCallCheck2 = require('babel-runtime/helpers/classCallCheck');
29
30var _classCallCheck3 = _interopRequireDefault(_classCallCheck2);
31
32var _createClass2 = require('babel-runtime/helpers/createClass');
33
34var _createClass3 = _interopRequireDefault(_createClass2);
35
36var convertFromVMDK = exports.convertFromVMDK = function () {
37 var _ref6 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee6(vmdkReadStream) {
38 var parser, header;
39 return _regenerator2.default.wrap(function _callee6$(_context6) {
40 while (1) {
41 switch (_context6.prev = _context6.next) {
42 case 0:
43 parser = new _vmdkRead.VMDKDirectParser(vmdkReadStream);
44 _context6.next = 3;
45 return parser.readHeader();
46
47 case 3:
48 header = _context6.sent;
49 return _context6.abrupt('return', new ReadableRawVHDStream(header.capacitySectors * sectorSize, parser));
50
51 case 5:
52 case 'end':
53 return _context6.stop();
54 }
55 }
56 }, _callee6, this);
57 }));
58
59 return function convertFromVMDK(_x9) {
60 return _ref6.apply(this, arguments);
61 };
62}();
63
64exports.computeChecksum = computeChecksum;
65exports.computeGeometryForSize = computeGeometryForSize;
66exports.createFooter = createFooter;
67exports.createDynamicDiskHeader = createDynamicDiskHeader;
68exports.createEmptyTable = createEmptyTable;
69
70var _fsPromise = require('fs-promise');
71
72var _stream = require('stream');
73
74var _stream2 = _interopRequireDefault(_stream);
75
76var _vmdkRead = require('./vmdk-read');
77
78function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
79
80var footerCookie = 'conectix';
81var creatorApp = 'xo ';
82// it looks like everybody is using Wi2k
83var osString = 'Wi2k';
84var headerCookie = 'cxsparse';
85var fixedHardDiskType = 2;
86var dynamicHardDiskType = 3;
87
88var sectorSize = 512;
89
90function computeChecksum(buffer) {
91 var sum = 0;
92 for (var i = 0; i < buffer.length; i++) {
93 sum += buffer[i];
94 }
95 // http://stackoverflow.com/a/1908655/72637 the >>> prevents the number from going negative
96 return ~sum >>> 0;
97}
98
99var Block = function () {
100 function Block(blockSize) {
101 (0, _classCallCheck3.default)(this, Block);
102
103 var bitmapSize = blockSize / sectorSize / 8;
104 var bufferSize = Math.ceil((blockSize + bitmapSize) / sectorSize) * sectorSize;
105 this.buffer = new Buffer(bufferSize);
106 this.buffer.fill(0);
107 this.bitmapBuffer = this.buffer.slice(0, bitmapSize);
108 this.dataBuffer = this.buffer.slice(bitmapSize);
109 this.bitmapBuffer.fill(0xff);
110 }
111
112 (0, _createClass3.default)(Block, [{
113 key: 'writeData',
114 value: function writeData(buffer) {
115 var offset = arguments.length <= 1 || arguments[1] === undefined ? 0 : arguments[1];
116
117 buffer.copy(this.dataBuffer, offset);
118 }
119 }, {
120 key: 'writeOnFile',
121 value: function () {
122 var _ref = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee(file) {
123 return _regenerator2.default.wrap(function _callee$(_context) {
124 while (1) {
125 switch (_context.prev = _context.next) {
126 case 0:
127 _context.next = 2;
128 return (0, _fsPromise.write)(file, this.buffer, 0, this.buffer.length);
129
130 case 2:
131 case 'end':
132 return _context.stop();
133 }
134 }
135 }, _callee, this);
136 }));
137
138 function writeOnFile(_x2) {
139 return _ref.apply(this, arguments);
140 }
141
142 return writeOnFile;
143 }()
144 }]);
145 return Block;
146}();
147
148var SparseExtent = function () {
149 function SparseExtent(dataSize, blockSize, startOffset) {
150 (0, _classCallCheck3.default)(this, SparseExtent);
151
152 this.table = createEmptyTable(dataSize, blockSize);
153 this.blockSize = blockSize;
154 this.startOffset = (startOffset + this.table.buffer.length) / sectorSize;
155 }
156
157 (0, _createClass3.default)(SparseExtent, [{
158 key: '_writeBlock',
159 value: function _writeBlock(blockBuffer, tableIndex, offset) {
160 if (blockBuffer.length + offset > this.blockSize) {
161 throw new Error('invalid block geometry');
162 }
163 var entry = this.table.entries[tableIndex];
164 if (entry === undefined) {
165 entry = new Block(this.blockSize);
166 this.table.entries[tableIndex] = entry;
167 }
168 entry.writeData(blockBuffer, offset);
169 }
170 }, {
171 key: 'writeBuffer',
172 value: function writeBuffer(buffer) {
173 var offset = arguments.length <= 1 || arguments[1] === undefined ? 0 : arguments[1];
174
175 var startBlock = Math.floor(offset / this.blockSize);
176 var endBlock = Math.ceil((offset + buffer.length) / this.blockSize);
177 for (var i = startBlock; i < endBlock; i++) {
178 var blockDelta = offset - i * this.blockSize;
179 var blockBuffer = void 0,
180 blockOffset = void 0;
181 if (blockDelta > 0) {
182 blockBuffer = buffer.slice(0, (i + 1) * this.blockSize - offset);
183 blockOffset = blockDelta;
184 } else {
185 blockBuffer = buffer.slice(-blockDelta, (i + 1) * this.blockSize - offset);
186 blockOffset = 0;
187 }
188 this._writeBlock(blockBuffer, i, blockOffset);
189 }
190 }
191 }, {
192 key: 'writeOnFile',
193 value: function () {
194 var _ref2 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee2(file) {
195 var currentOffset, i, block, _i, _block;
196
197 return _regenerator2.default.wrap(function _callee2$(_context2) {
198 while (1) {
199 switch (_context2.prev = _context2.next) {
200 case 0:
201 currentOffset = this.startOffset;
202
203 for (i = 0; i < this.table.entryCount; i++) {
204 block = this.table.entries[i];
205
206 if (block !== undefined) {
207 this.table.buffer.writeUInt32BE(currentOffset, i * 4);
208 currentOffset += block.buffer.length / sectorSize;
209 }
210 }
211 _context2.next = 4;
212 return (0, _fsPromise.write)(file, this.table.buffer, 0, this.table.buffer.length);
213
214 case 4:
215 _i = 0;
216
217 case 5:
218 if (!(_i < this.table.entryCount)) {
219 _context2.next = 13;
220 break;
221 }
222
223 _block = this.table.entries[_i];
224
225 if (!(_block !== undefined)) {
226 _context2.next = 10;
227 break;
228 }
229
230 _context2.next = 10;
231 return _block.writeOnFile(file);
232
233 case 10:
234 _i++;
235 _context2.next = 5;
236 break;
237
238 case 13:
239 case 'end':
240 return _context2.stop();
241 }
242 }
243 }, _callee2, this);
244 }));
245
246 function writeOnFile(_x4) {
247 return _ref2.apply(this, arguments);
248 }
249
250 return writeOnFile;
251 }()
252 }, {
253 key: 'entryCount',
254 get: function get() {
255 return this.table.entryCount;
256 }
257 }]);
258 return SparseExtent;
259}();
260
261var VHDFile = exports.VHDFile = function () {
262 function VHDFile(virtualSize, timestamp) {
263 (0, _classCallCheck3.default)(this, VHDFile);
264
265 this.geomtry = computeGeometryForSize(virtualSize);
266 this.timestamp = timestamp;
267 this.blockSize = 0x00200000;
268 this.sparseFile = new SparseExtent(this.geomtry.actualSize, this.blockSize, sectorSize * 3);
269 }
270
271 (0, _createClass3.default)(VHDFile, [{
272 key: 'writeBuffer',
273 value: function writeBuffer(buffer) {
274 var offset = arguments.length <= 1 || arguments[1] === undefined ? 0 : arguments[1];
275
276 this.sparseFile.writeBuffer(buffer, offset);
277 }
278 }, {
279 key: 'writeFile',
280 value: function () {
281 var _ref3 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee3(fileName) {
282 var fileFooter, diskHeader, file;
283 return _regenerator2.default.wrap(function _callee3$(_context3) {
284 while (1) {
285 switch (_context3.prev = _context3.next) {
286 case 0:
287 fileFooter = createFooter(this.geomtry.actualSize, this.timestamp, this.geomtry, dynamicHardDiskType, 512, 0);
288 diskHeader = createDynamicDiskHeader(this.sparseFile.entryCount, this.blockSize);
289 _context3.next = 4;
290 return (0, _fsPromise.open)(fileName, 'w');
291
292 case 4:
293 file = _context3.sent;
294 _context3.next = 7;
295 return (0, _fsPromise.write)(file, fileFooter, 0, fileFooter.length);
296
297 case 7:
298 _context3.next = 9;
299 return (0, _fsPromise.write)(file, diskHeader, 0, diskHeader.length);
300
301 case 9:
302 _context3.next = 11;
303 return this.sparseFile.writeOnFile(file);
304
305 case 11:
306 _context3.next = 13;
307 return (0, _fsPromise.write)(file, fileFooter, 0, fileFooter.length);
308
309 case 13:
310 case 'end':
311 return _context3.stop();
312 }
313 }
314 }, _callee3, this);
315 }));
316
317 function writeFile(_x6) {
318 return _ref3.apply(this, arguments);
319 }
320
321 return writeFile;
322 }()
323 }]);
324 return VHDFile;
325}();
326
327function computeGeometryForSize(size) {
328 var totalSectors = Math.ceil(size / 512);
329 var sectorsPerTrack = void 0;
330 var heads = void 0;
331 var cylinderTimesHeads = void 0;
332 if (totalSectors > 65535 * 16 * 255) {
333 throw Error('disk is too big');
334 }
335 // straight copypasta from the file spec appendix on CHS Calculation
336 if (totalSectors >= 65535 * 16 * 63) {
337 sectorsPerTrack = 255;
338 heads = 16;
339 cylinderTimesHeads = totalSectors / sectorsPerTrack;
340 } else {
341 sectorsPerTrack = 17;
342 cylinderTimesHeads = totalSectors / sectorsPerTrack;
343 heads = Math.floor((cylinderTimesHeads + 1023) / 1024);
344 if (heads < 4) {
345 heads = 4;
346 }
347 if (cylinderTimesHeads >= heads * 1024 || heads > 16) {
348 sectorsPerTrack = 31;
349 heads = 16;
350 cylinderTimesHeads = totalSectors / sectorsPerTrack;
351 }
352 if (cylinderTimesHeads >= heads * 1024) {
353 sectorsPerTrack = 63;
354 heads = 16;
355 cylinderTimesHeads = totalSectors / sectorsPerTrack;
356 }
357 }
358 var cylinders = Math.floor(cylinderTimesHeads / heads);
359 var actualSize = cylinders * heads * sectorsPerTrack * sectorSize;
360 return { cylinders: cylinders, heads: heads, sectorsPerTrack: sectorsPerTrack, actualSize: actualSize };
361}
362
363function createFooter(size, timestamp, geometry, diskType) {
364 var dataOffsetLow = arguments.length <= 4 || arguments[4] === undefined ? 0xFFFFFFFF : arguments[4];
365 var dataOffsetHigh = arguments.length <= 5 || arguments[5] === undefined ? 0xFFFFFFFF : arguments[5];
366
367 var footer = new Buffer(512);
368 footer.fill(0);
369 new Buffer(footerCookie, 'ascii').copy(footer);
370 footer.writeUInt32BE(2, 8);
371 footer.writeUInt32BE(0x00010000, 12);
372 footer.writeUInt32BE(dataOffsetHigh, 16);
373 footer.writeUInt32BE(dataOffsetLow, 20);
374 footer.writeUInt32BE(timestamp, 24);
375 new Buffer(creatorApp, 'ascii').copy(footer, 28);
376 new Buffer(osString, 'ascii').copy(footer, 36);
377 // do not use & 0xFFFFFFFF to extract lower bits, that would propagate a negative sign if the 2^31 bit is one
378 var sizeHigh = Math.floor(size / Math.pow(2, 32)) % Math.pow(2, 32);
379 var sizeLow = size % Math.pow(2, 32);
380 footer.writeUInt32BE(sizeHigh, 40);
381 footer.writeUInt32BE(sizeLow, 44);
382 footer.writeUInt32BE(sizeHigh, 48);
383 footer.writeUInt32BE(sizeLow, 52);
384 footer.writeUInt16BE(geometry['cylinders'], 56);
385 footer.writeUInt8(geometry['heads'], 58);
386 footer.writeUInt8(geometry['sectorsPerTrack'], 59);
387 footer.writeUInt32BE(diskType, 60);
388 var checksum = computeChecksum(footer);
389 footer.writeUInt32BE(checksum, 64);
390 return footer;
391}
392
393function createDynamicDiskHeader(tableEntries, blockSize) {
394 var header = new Buffer(1024);
395 header.fill(0);
396 new Buffer(headerCookie, 'ascii').copy(header);
397 // hard code no next data
398 header.writeUInt32BE(0xFFFFFFFF, 8);
399 header.writeUInt32BE(0xFFFFFFFF, 12);
400 // hard code table offset
401 header.writeUInt32BE(0, 16);
402 header.writeUInt32BE(sectorSize * 3, 20);
403 header.writeUInt32BE(0x00010000, 24);
404 header.writeUInt32BE(tableEntries, 28);
405 header.writeUInt32BE(blockSize, 32);
406 var checksum = computeChecksum(header);
407 header.writeUInt32BE(checksum, 36);
408 return header;
409}
410
411function createEmptyTable(dataSize, blockSize) {
412 var blockCount = Math.ceil(dataSize / blockSize);
413 var tableSizeSectors = Math.ceil(blockCount * 4 / sectorSize);
414 var buffer = new Buffer(tableSizeSectors * sectorSize);
415 buffer.fill(0xff);
416 return { entryCount: blockCount, buffer: buffer, entries: [] };
417}
418
419var ReadableRawVHDStream = exports.ReadableRawVHDStream = function (_stream$Readable) {
420 (0, _inherits3.default)(ReadableRawVHDStream, _stream$Readable);
421
422 function ReadableRawVHDStream(size, vmdkParser) {
423 (0, _classCallCheck3.default)(this, ReadableRawVHDStream);
424
425 var _this = (0, _possibleConstructorReturn3.default)(this, (0, _getPrototypeOf2.default)(ReadableRawVHDStream).call(this));
426
427 _this.size = size;
428 var geometry = computeGeometryForSize(size);
429 _this.footer = createFooter(size, Math.floor(Date.now() / 1000), geometry, fixedHardDiskType);
430 _this.position = 0;
431 _this.vmdkParser = vmdkParser;
432 _this.done = false;
433 _this.busy = false;
434 _this.currentFile = [];
435 return _this;
436 }
437
438 (0, _createClass3.default)(ReadableRawVHDStream, [{
439 key: 'filePadding',
440 value: function filePadding(paddingLength) {
441 var _this2 = this;
442
443 if (paddingLength !== 0) {
444 (function () {
445 var chunkSize = 1024 * 1024; // 1Mo
446 var chunkCount = Math.floor(paddingLength / chunkSize);
447 for (var i = 0; i < chunkCount; i++) {
448 _this2.currentFile.push(function () {
449 var paddingBuffer = new Buffer(chunkSize);
450 paddingBuffer.fill(0);
451 return paddingBuffer;
452 });
453 }
454 _this2.currentFile.push(function () {
455 var paddingBuffer = new Buffer(paddingLength % chunkSize);
456 paddingBuffer.fill(0);
457 return paddingBuffer;
458 });
459 })();
460 }
461 }
462 }, {
463 key: 'pushNextBlock',
464 value: function () {
465 var _ref4 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee4() {
466 var _this3 = this;
467
468 var next, paddingLength;
469 return _regenerator2.default.wrap(function _callee4$(_context4) {
470 while (1) {
471 switch (_context4.prev = _context4.next) {
472 case 0:
473 _context4.next = 2;
474 return this.vmdkParser.next();
475
476 case 2:
477 next = _context4.sent;
478
479 if (next === null) {
480 paddingLength = this.size - this.position;
481
482 this.filePadding(paddingLength);
483 this.currentFile.push(function () {
484 return _this3.footer;
485 });
486 this.currentFile.push(function () {
487 _this3.done = true;
488 return null;
489 });
490 } else {
491 (function () {
492 var offset = next.lbaBytes;
493 var buffer = next.grain;
494 var paddingLength = offset - _this3.position;
495 if (paddingLength < 0) {
496 process.nextTick(function () {
497 return _this3.emit('error', 'This VMDK file does not have its blocks in the correct order');
498 });
499 }
500 _this3.filePadding(paddingLength);
501 _this3.currentFile.push(function () {
502 return buffer;
503 });
504 _this3.position = offset + buffer.length;
505 })();
506 }
507 return _context4.abrupt('return', this.pushFileUntilFull());
508
509 case 5:
510 case 'end':
511 return _context4.stop();
512 }
513 }
514 }, _callee4, this);
515 }));
516
517 function pushNextBlock() {
518 return _ref4.apply(this, arguments);
519 }
520
521 return pushNextBlock;
522 }()
523
524 // returns true if the file is empty
525
526 }, {
527 key: 'pushFileUntilFull',
528 value: function pushFileUntilFull() {
529 while (true) {
530 if (this.currentFile.length === 0) {
531 break;
532 }
533 var result = this.push(this.currentFile.shift()());
534 if (!result) {
535 break;
536 }
537 }
538 return this.currentFile.length === 0;
539 }
540 }, {
541 key: 'pushNextUntilFull',
542 value: function () {
543 var _ref5 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee5() {
544 return _regenerator2.default.wrap(function _callee5$(_context5) {
545 while (1) {
546 switch (_context5.prev = _context5.next) {
547 case 0:
548 _context5.t0 = !this.done;
549
550 if (!_context5.t0) {
551 _context5.next = 5;
552 break;
553 }
554
555 _context5.next = 4;
556 return this.pushNextBlock();
557
558 case 4:
559 _context5.t0 = _context5.sent;
560
561 case 5:
562 if (!_context5.t0) {
563 _context5.next = 8;
564 break;
565 }
566
567 _context5.next = 0;
568 break;
569
570 case 8:
571 case 'end':
572 return _context5.stop();
573 }
574 }
575 }, _callee5, this);
576 }));
577
578 function pushNextUntilFull() {
579 return _ref5.apply(this, arguments);
580 }
581
582 return pushNextUntilFull;
583 }()
584 }, {
585 key: '_read',
586 value: function _read() {
587 var _this4 = this;
588
589 if (this.busy || this.done) {
590 return;
591 }
592 if (this.pushFileUntilFull()) {
593 this.busy = true;
594 this.pushNextUntilFull().then(function () {
595 _this4.busy = false;
596 }).catch(function (error) {
597 process.nextTick(function () {
598 return _this4.emit('error', error);
599 });
600 });
601 }
602 }
603 }]);
604 return ReadableRawVHDStream;
605}(_stream2.default.Readable);
606//# sourceMappingURL=vhd-write.js.map
\No newline at end of file