etcher-sdk
Version:
153 lines • 6 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.File = exports.ProgressWriteStream = void 0;
const fs_1 = require("fs");
const path_1 = require("path");
const progress_1 = require("./progress");
const source_destination_1 = require("./source-destination");
const block_read_stream_1 = require("../block-read-stream");
const constants_1 = require("../constants");
const errors_1 = require("../errors");
const sparse_write_stream_1 = require("../sparse-stream/sparse-write-stream");
exports.ProgressWriteStream = (0, progress_1.makeClassEmitProgressEvents)(fs_1.WriteStream, 'bytesWritten', 'bytesWritten');
const READ_TRIES = 5;
const readEndMarker = Buffer.from(`not the correct data ${Math.random()}`);
class File extends source_destination_1.SourceDestination {
constructor({ path, write = false }) {
super();
this.path = path;
this.oWrite = write;
}
getOpenFlags() {
return this.oWrite
? // tslint:disable-next-line:no-bitwise
fs_1.constants.O_CREAT | fs_1.constants.O_RDWR
: fs_1.constants.O_RDONLY;
}
async canRead() {
return true;
}
async canWrite() {
return this.oWrite;
}
async canCreateReadStream() {
return true;
}
canCreateWriteStream() {
return this.canWrite();
}
canCreateSparseWriteStream() {
return this.canWrite();
}
async _getMetadata() {
return {
size: (await fs_1.promises.stat(this.path)).size,
name: (0, path_1.basename)(this.path),
};
}
async read(buffer, bufferOffset, length, sourceOffset) {
// In very rare occasions (happened on Linux with node 12 reading from a block device: O_DIRECT + O_SYNC into an aligned buffer),
// the read does not read the whole required length (up to 4KiB can be missing at the end of 1MiB reads).
// This was checked by filling the buffer with a specific pattern before reading and looking for this pattern
// in the buffer after the read.
// To mitigate this, we write a specific marker at the end of the buffer before reading and retry the read if
// it is still there after reading.
let result;
let tries = READ_TRIES;
const markerPosition = bufferOffset + length - readEndMarker.length;
if (length >= readEndMarker.length) {
readEndMarker.copy(buffer, markerPosition);
}
do {
if (tries < READ_TRIES) {
console.warn('Incomplete read', {
path: this.path,
bufferOffset,
length,
sourceOffset,
bufferLength: buffer.length,
});
}
result = await this.fileHandle.read(buffer, bufferOffset, length, sourceOffset);
tries -= 1;
} while (tries > 0 &&
length >= readEndMarker.length &&
result.bytesRead === length &&
readEndMarker.compare(buffer, markerPosition, markerPosition + readEndMarker.length) === 0);
return result;
}
write(buffer, bufferOffset, length, fileOffset) {
return this.fileHandle.write(buffer, bufferOffset, length, fileOffset);
}
async createReadStream({ emitProgress = false, start = 0, end, alignment, numBuffers, } = {}) {
await this.open();
const metadata = await this.getMetadata();
if (metadata.size !== 0) {
// workaround for special files like /dev/zero or /dev/random
const lastByte = metadata.size - 1;
end = end === undefined ? lastByte : Math.min(end, lastByte);
}
if (emitProgress) {
return new block_read_stream_1.ProgressBlockReadStream({
source: this,
alignment,
start,
end,
numBuffers,
});
}
else {
return new block_read_stream_1.BlockReadStream({
source: this,
alignment,
start,
end,
numBuffers,
});
}
}
async createWriteStream({ highWaterMark, } = {}) {
// TODO: use SourceDestinationFs (implement write) when node 14 becomes LTS
// @ts-expect-error: @types/node is wrong about fs.WriteStream constructor: it takes 2 arguments, the first one is the file path
const stream = new exports.ProgressWriteStream(null, {
fd: this.fileHandle.fd,
autoClose: false,
highWaterMark,
});
stream.on('finish', stream.emit.bind(stream, 'done'));
return stream;
}
async createSparseWriteStream({ highWaterMark, } = {}) {
const stream = new sparse_write_stream_1.ProgressSparseWriteStream({
destination: this,
highWaterMark,
});
stream.on('finish', stream.emit.bind(stream, 'done'));
return stream;
}
async _open() {
await (0, errors_1.retryOnTransientError)(async () => {
this.fileHandle = await fs_1.promises.open(this.path, this.getOpenFlags());
}, 150, constants_1.RETRY_BASE_TIMEOUT);
}
async _close() {
await this.fileHandle.close();
}
}
exports.File = File;
//# sourceMappingURL=file.js.map