feat(vhd-lib): createContentStream (#3086)
Export the raw content of the VHD as a stream. This features is exposed in CLI: `vhd-cli raw input.vhd output.raw` Related to #3083 Perf comparison between qemu-img and our vhd-cli to convert a 10GiB VHD file to raw: ``` > time qemu-img convert -f vpc -O raw origin.vhd expected.raw 1.40user 15.19system 1:01.88elapsed 26%CPU (0avgtext+0avgdata 24264maxresident)k 20979008inputs+20971520outputs (12major+4648minor)pagefaults 0swaps > time vhd-cli raw origin.vhd actual.raw 21.97user 16.03system 1:09.11elapsed 54%CPU (0avgtext+0avgdata 65208maxresident)k 20956008inputs+20972448outputs (1major+754101minor)pagefaults 0swaps > md5sum *.raw b55ec6924be750edd2423e4a7aa262c3 actual.raw b55ec6924be750edd2423e4a7aa262c3 expected.raw ```
This commit is contained in:
23
packages/vhd-cli/src/_utils.js
Normal file
23
packages/vhd-cli/src/_utils.js
Normal file
@@ -0,0 +1,23 @@
|
||||
const { createWriteStream } = require('fs')
|
||||
const { PassThrough } = require('stream')
|
||||
|
||||
const createOutputStream = path => {
|
||||
if (path !== undefined && path !== '-') {
|
||||
return createWriteStream(path)
|
||||
}
|
||||
|
||||
// introduce a through stream because stdout is not a normal stream!
|
||||
const stream = new PassThrough()
|
||||
stream.pipe(process.stdout)
|
||||
return stream
|
||||
}
|
||||
|
||||
export const writeStream = (input, path) => {
|
||||
const output = createOutputStream(path)
|
||||
|
||||
return new Promise((resolve, reject) =>
|
||||
input
|
||||
.on('error', reject)
|
||||
.pipe(output.on('error', reject).on('finish', resolve))
|
||||
)
|
||||
}
|
||||
16
packages/vhd-cli/src/commands/raw.js
Normal file
16
packages/vhd-cli/src/commands/raw.js
Normal file
@@ -0,0 +1,16 @@
|
||||
import { createContentStream } from 'vhd-lib'
|
||||
import { getHandler } from '@xen-orchestra/fs'
|
||||
import { resolve } from 'path'
|
||||
|
||||
import { writeStream } from '../_utils'
|
||||
|
||||
export default async args => {
|
||||
if (args.length < 2 || args.some(_ => _ === '-h' || _ === '--help')) {
|
||||
return `Usage: ${this.command} <input VHD> [<output raw>]`
|
||||
}
|
||||
|
||||
await writeStream(
|
||||
createContentStream(getHandler({ url: 'file:///' }), resolve(args[0])),
|
||||
args[1]
|
||||
)
|
||||
}
|
||||
31
packages/vhd-lib/src/createContentStream.js
Normal file
31
packages/vhd-lib/src/createContentStream.js
Normal file
@@ -0,0 +1,31 @@
|
||||
import asyncIteratorToStream from 'async-iterator-to-stream'
|
||||
|
||||
import Vhd from './vhd'
|
||||
|
||||
export default asyncIteratorToStream(async function * (handler, path) {
|
||||
const fd = await handler.openFile(path, 'r')
|
||||
try {
|
||||
const vhd = new Vhd(handler, fd)
|
||||
await vhd.readHeaderAndFooter()
|
||||
await vhd.readBlockAllocationTable()
|
||||
const {
|
||||
footer: { currentSize },
|
||||
header: { blockSize },
|
||||
} = vhd
|
||||
const nFullBlocks = Math.floor(currentSize / blockSize)
|
||||
const nLeftoverBytes = currentSize % blockSize
|
||||
|
||||
const emptyBlock = Buffer.alloc(blockSize)
|
||||
for (let i = 0; i < nFullBlocks; ++i) {
|
||||
yield vhd.containsBlock(i) ? (await vhd._readBlock(i)).data : emptyBlock
|
||||
}
|
||||
if (nLeftoverBytes !== 0) {
|
||||
yield (vhd.containsBlock(nFullBlocks)
|
||||
? (await vhd._readBlock(nFullBlocks)).data
|
||||
: emptyBlock
|
||||
).slice(0, nLeftoverBytes)
|
||||
}
|
||||
} finally {
|
||||
await handler.closeFile(fd)
|
||||
}
|
||||
})
|
||||
@@ -1,5 +1,6 @@
|
||||
export { default } from './vhd'
|
||||
export { default as chainVhd } from './chain'
|
||||
export { default as createContentStream } from './createContentStream'
|
||||
export { default as createReadableRawStream } from './createReadableRawStream'
|
||||
export {
|
||||
default as createReadableSparseStream,
|
||||
|
||||
Reference in New Issue
Block a user