etcher-sdk
Version:
204 lines • 7.67 kB
JavaScript
"use strict";
/*
* 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.BlockDevice = void 0;
const direct_io_1 = require("@ronomon/direct-io");
const fs_1 = require("fs");
const os_1 = require("os");
const block_write_stream_1 = require("../block-write-stream");
const constants_1 = require("../constants");
const diskpart_1 = require("../diskpart");
const lazy_1 = require("../lazy");
const sparse_write_stream_1 = require("../sparse-stream/sparse-write-stream");
const utils_1 = require("../utils");
const file_1 = require("./file");
/**
* @summary Time, in milliseconds, to wait before unmounting on success
*/
const UNMOUNT_ON_SUCCESS_TIMEOUT_MS = 2000;
const WIN32_FIRST_BYTES_TO_KEEP = 64 * 1024;
class BlockDevice extends file_1.File {
constructor({ drive, unmountOnSuccess = false, write = false, direct = true, keepOriginal = false, }) {
super({ path: drive.raw, write });
this.emitsProgress = false;
this.keepOriginal = false;
this.drive = drive;
this.unmountOnSuccess = unmountOnSuccess;
this.oDirect = direct;
this.keepOriginal = keepOriginal; // skip clean of drive before write
// alignment must be at most 4k
this.alignment = Math.min(drive.blockSize || constants_1.DEFAULT_ALIGNMENT, 4 * 1024 ** 2);
}
getAlignment() {
if (this.oDirect) {
return this.alignment;
}
}
getOpenFlags() {
// tslint:disable:no-bitwise
let flags = this.oWrite ? fs_1.constants.O_RDWR : fs_1.constants.O_RDONLY;
if (this.oDirect) {
flags |= fs_1.constants.O_DIRECT;
}
if (this.oWrite) {
const plat = (0, os_1.platform)();
if (plat === 'linux') {
flags |= fs_1.constants.O_EXCL;
}
else if (plat === 'darwin') {
flags |= direct_io_1.O_EXLOCK;
}
else if (plat === 'win32') {
flags |= direct_io_1.O_EXLOCK;
}
}
// tslint:enable:no-bitwise
return flags;
}
get isSystem() {
return this.drive.isSystem;
}
get raw() {
return this.drive.raw;
}
get device() {
return this.drive.device;
}
get devicePath() {
return this.drive.devicePath;
}
get description() {
return this.drive.description;
}
get mountpoints() {
return this.drive.mountpoints;
}
get size() {
return this.drive.size;
}
async _getMetadata() {
return {
size: this.drive.size || undefined,
name: this.drive.device,
};
}
async canWrite() {
return !this.drive.isReadOnly;
}
async canCreateWriteStream() {
return !this.drive.isReadOnly;
}
async canCreateSparseWriteStream() {
return !this.drive.isReadOnly;
}
async createWriteStream({ highWaterMark, startOffset, } = {}) {
const stream = new block_write_stream_1.ProgressBlockWriteStream({
destination: this,
delayFirstBuffer: (0, os_1.platform)() === 'win32',
highWaterMark,
startOffset,
});
stream.on('finish', stream.emit.bind(stream, 'done'));
return stream;
}
async createSparseWriteStream({ highWaterMark, } = {}) {
const stream = new sparse_write_stream_1.ProgressSparseWriteStream({
destination: this,
firstBytesToKeep: (0, os_1.platform)() === 'win32' ? WIN32_FIRST_BYTES_TO_KEEP : 0,
highWaterMark,
});
stream.on('finish', stream.emit.bind(stream, 'done'));
return stream;
}
async _open() {
const plat = (0, os_1.platform)();
if (this.oWrite) {
if (plat !== 'win32') {
const unmountDisk = (0, lazy_1.getUnmountDisk)();
await unmountDisk(this.drive.device);
}
// diskpart clean on windows
if (!this.keepOriginal) {
await (0, diskpart_1.clean)(this.drive.device);
}
}
await super._open();
if (plat === 'darwin') {
await (0, utils_1.fromCallback)((cb) => {
(0, direct_io_1.setF_NOCACHE)(this.fileHandle.fd, 1, cb);
});
}
}
async _close() {
await super._close();
// Closing a file descriptor on a drive containing mountable
// partitions causes macOS to mount the drive. If we try to
// unmount too quickly, then the drive might get re-mounted
// right afterwards.
if (this.unmountOnSuccess) {
await (0, utils_1.delay)(UNMOUNT_ON_SUCCESS_TIMEOUT_MS);
const unmountDisk = (0, lazy_1.getUnmountDisk)();
await unmountDisk(this.drive.device);
}
}
offsetIsAligned(offset) {
return offset % this.alignment === 0;
}
alignOffsetBefore(offset) {
return Math.floor(offset / this.alignment) * this.alignment;
}
alignOffsetAfter(offset) {
return Math.ceil(offset / this.alignment) * this.alignment;
}
async alignedRead(buffer, bufferOffset, length, sourceOffset) {
const start = this.alignOffsetBefore(sourceOffset);
const end = this.alignOffsetAfter(sourceOffset + length);
const alignedBuffer = (0, direct_io_1.getAlignedBuffer)(end - start, this.alignment);
const { bytesRead } = await super.read(alignedBuffer, 0, alignedBuffer.length, start);
const offset = sourceOffset - start;
alignedBuffer.copy(buffer, bufferOffset, offset, offset + length);
return { buffer, bytesRead: Math.min(length, bytesRead - offset) };
}
read(buffer, bufferOffset, length, sourceOffset) {
if (!(this.offsetIsAligned(sourceOffset) && this.offsetIsAligned(length))) {
return this.alignedRead(buffer, bufferOffset, length, sourceOffset);
}
else {
return super.read(buffer, bufferOffset, length, sourceOffset);
}
}
async alignedWrite(buffer, bufferOffset, length, fileOffset) {
const start = this.alignOffsetBefore(fileOffset);
const end = this.alignOffsetAfter(fileOffset + length);
const alignedBuffer = (0, direct_io_1.getAlignedBuffer)(end - start, this.alignment);
await super.read(alignedBuffer, 0, alignedBuffer.length, start);
const offset = fileOffset - start;
buffer.copy(alignedBuffer, offset, bufferOffset, length);
await super.write(alignedBuffer, 0, alignedBuffer.length, start);
return { buffer, bytesWritten: length };
}
write(buffer, bufferOffset, length, fileOffset) {
if (!(this.offsetIsAligned(fileOffset) && this.offsetIsAligned(length))) {
return this.alignedWrite(buffer, bufferOffset, length, fileOffset);
}
else {
return super.write(buffer, bufferOffset, length, fileOffset);
}
}
}
exports.BlockDevice = BlockDevice;
//# sourceMappingURL=block-device.js.map