etcher-sdk
Version:
214 lines • 8.3 kB
JavaScript
;
/*
* Copyright 2018 balena.io
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.ConfiguredSource = exports.SourceDisk = void 0;
const balena_image_fs_1 = require("balena-image-fs");
const debug_1 = require("debug");
const file_disk_1 = require("file-disk");
const partitioninfo_1 = require("partitioninfo");
const constants_1 = require("../../constants");
const errors_1 = require("../../errors");
const shared_1 = require("../../sparse-stream/shared");
const sparse_filter_stream_1 = require("../../sparse-stream/sparse-filter-stream");
const sparse_read_stream_1 = require("../../sparse-stream/sparse-read-stream");
const block_device_1 = require("../block-device");
const source_source_1 = require("../source-source");
const configure_1 = require("./configure");
const debug = (0, debug_1.default)('etcher-sdk:configured-source');
class SourceDisk extends file_disk_1.Disk {
constructor(source) {
super(true, // readOnly
true, // recordWrites
true, // recordReads
true);
this.source = source;
if (source instanceof block_device_1.BlockDevice && source.oDirect) {
// Reads into non aligned buffers won't work in partitioninfo and file-disk
throw new Error("Can't create a SourceDisk from a BlockDevice opened with O_DIRECT");
}
}
async _getCapacity() {
// Don't create SourceDisks with sources that do not define a size
const size = (await this.source.getMetadata()).size;
if (size === undefined) {
throw new errors_1.NotCapable();
}
return size;
}
async _read(buffer, bufferOffset, length, fileOffset) {
return await this.source.read(buffer, bufferOffset, length, fileOffset);
}
async _write(_buffer, // eslint-disable-line @typescript-eslint/no-unused-vars
_bufferOffset, // eslint-disable-line @typescript-eslint/no-unused-vars
_length, // eslint-disable-line @typescript-eslint/no-unused-vars
_fileOffset) {
throw new Error("Can't write to a SourceDisk");
}
async _flush() {
// noop
}
}
exports.SourceDisk = SourceDisk;
class ConfiguredSource extends source_source_1.SourceSource {
constructor({ source, // source needs to implement read and createReadStream
shouldTrimPartitions, createStreamFromDisk, configure, checksumType = 'xxhash3', chunkSize = constants_1.CHUNK_SIZE, }) {
super(source);
this.shouldTrimPartitions = shouldTrimPartitions;
this.createStreamFromDisk = createStreamFromDisk;
this.checksumType = checksumType;
this.chunkSize = chunkSize;
this.disk = new SourceDisk(source);
if (configure === 'legacy') {
this.configure = configure_1.configure;
}
else {
this.configure = configure;
}
}
async getBlocks() {
// Align ranges to this.chunkSize
const blocks = await this.disk.getRanges(this.chunkSize);
if (blocks.length) {
const lastBlock = blocks[blocks.length - 1];
const metadata = await this.source.getMetadata();
const overflow = lastBlock.offset + lastBlock.length - metadata.size;
if (overflow > 0) {
lastBlock.length -= overflow;
}
}
return blocks.map((block) => ({ blocks: [block] }));
}
async getBlocksWithChecksumType(generateChecksums) {
let blocks = await this.getBlocks();
if (generateChecksums) {
blocks = blocks.map((block) => ({
...block,
checksumType: this.checksumType,
}));
}
return blocks;
}
async canRead() {
return true;
}
async canCreateReadStream() {
return true;
}
async canCreateSparseReadStream() {
return true;
}
async read(buffer, bufferOffset, length, sourceOffset) {
return await this.disk.read(buffer, bufferOffset, length, sourceOffset);
}
async createReadStream(options) {
const imageStream = await this.source.createReadStream(options);
const transform = this.disk.getTransformStream();
imageStream.on('error', (err) => {
transform.emit('error', err);
});
imageStream.pipe(transform);
return transform;
}
async createSparseReadStreamFromDisk(generateChecksums, alignment, numBuffers = 2) {
return new sparse_read_stream_1.SparseReadStream({
source: this,
blocks: await this.getBlocksWithChecksumType(generateChecksums),
chunkSize: constants_1.CHUNK_SIZE,
verify: false,
generateChecksums,
alignment,
numBuffers,
});
}
async createSparseReadStreamFromStream(generateChecksums, alignment, numBuffers = 2) {
const stream = await this.createReadStream({
alignment,
numBuffers,
});
const transform = new sparse_filter_stream_1.SparseFilterStream({
blocks: await this.getBlocksWithChecksumType(generateChecksums),
verify: false,
generateChecksums,
});
stream.on('error', transform.emit.bind(transform, 'error'));
stream.pipe(transform);
return transform;
}
async createSparseReadStream({ generateChecksums = false, alignment, numBuffers = 2, } = {}) {
if (this.createStreamFromDisk) {
return await this.createSparseReadStreamFromDisk(generateChecksums, alignment, numBuffers);
}
else {
return await this.createSparseReadStreamFromStream(generateChecksums, alignment, numBuffers);
}
}
async _getMetadata() {
const sourceMetadata = await this.source.getMetadata();
const blocks = await this.getBlocks();
const blockmappedSize = (0, shared_1.blocksLength)(blocks);
return { ...sourceMetadata, blocks, blockmappedSize };
}
async trimPartitions() {
let partitions;
try {
({ partitions } = await (0, partitioninfo_1.getPartitions)(this.disk, {
includeExtended: false,
}));
}
catch (error) {
debug("Couldn't read partition table", error);
return;
}
for (const partition of partitions) {
try {
await (0, balena_image_fs_1.interact)(this.disk, partition.index, async (trimableFs) => {
if (trimableFs.trim !== undefined) {
await trimableFs.trim();
}
});
}
catch (_a) {
// Unsupported filesystem
}
}
const discards = this.disk.getDiscardedChunks();
const discardedBytes = discards
.map((d) => d.end - d.start + 1)
.reduce((a, b) => a + b, 0);
// TODO: discarededBytes in metadata ?
const metadata = await this.getMetadata();
if (metadata.size !== undefined) {
const percentage = Math.round((discardedBytes / metadata.size) * 100);
debug(`discarded ${discards.length} chunks, ${discardedBytes} bytes, ${percentage}% of the image`);
}
}
async _open() {
await super._open();
if (this.configure !== undefined) {
await this.configure(this.disk);
}
if (this.shouldTrimPartitions) {
await this.trimPartitions();
}
this.disk.recordReads = false;
}
async _close() {
await super._close();
}
}
exports.ConfiguredSource = ConfiguredSource;
//# sourceMappingURL=configured-source.js.map