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:
Julien Fontanet
2018-06-20 17:53:20 +02:00
committed by GitHub
parent bebb9bf0df
commit ee99ef6264
4 changed files with 71 additions and 0 deletions

View 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))
)
}

View 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]
)
}

View 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)
}
})

View File

@@ -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,