etcher-sdk
Version:
236 lines (235 loc) • 8.81 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.writeNetworkConfig = exports.getTargetBlockDevice = exports.copyPartitionFromImageToDevice = exports.calcRequiredPartitionSize = exports.getPartitionBoundaries = exports.findFilesystemLabel = exports.findNewPartitions = exports.MS_DATA_PARTITION_ID = void 0;
const promises_1 = require("fs/promises");
const process = require("process");
const drivelist = require("drivelist");
const source_destination_1 = require("../source-destination");
exports.MS_DATA_PARTITION_ID = 'EBD0A0A2-B9E5-4433-87C0-68B6B72699C7';
/**
* Finds partitions in newTable with a partition offset not found in oldTable.
* Expects paritioninfo partition objects.
* @returns Array of new partitions
*/
const findNewPartitions = (oldTable, newTable) => {
if ((newTable === null || newTable === void 0 ? void 0 : newTable.partitions) === undefined ||
(oldTable === null || oldTable === void 0 ? void 0 : oldTable.partitions) === undefined) {
throw Error('Partitions undefined in request');
}
return newTable.partitions.filter((n) => {
return !oldTable.partitions.some((o) => o.offset === n.offset);
});
};
exports.findNewPartitions = findNewPartitions;
/**
* Scans the filesystem on the partitions for the provided device for the filesystem
* label text, assuming the filesystem is the provided type.
*
* Partition type must be GPT.
*
* @returns partition containing the label, or null if not found
*/
const findFilesystemLabel = async (table, device, label, fs) => {
if (table.type === 'mbr') {
throw Error("Can't read MBR table");
}
// Only check non-system partitions on Windows
let partitions = table.partitions;
if (process.platform === 'win32') {
partitions = table.partitions.filter((p) => p.type.toUpperCase() === exports.MS_DATA_PARTITION_ID);
}
// Determine label offset
let offset = 0;
if (fs === 'fat16') {
// https://en.wikipedia.org/wiki/Desian_of_the_FAT_file_system#Extended_BIOS_Parameter_Block
offset = 0x2b;
}
else if (fs === 'ext4') {
// https://www.kernel.org/doc/html/latest/filesystems/ext4/index.html
offset = 0x400 + 0x78;
}
const buf = Buffer.alloc(label.length);
for (const p of partitions) {
// Satisfy TypeScript that p is not an MBRPartition even though we tested above on the table
if (!('guid' in p)) {
continue;
}
await device.read(buf, 0, buf.length, p.offset + offset);
if (buf.toString() === label) {
return p;
}
}
return null;
};
exports.findFilesystemLabel = findFilesystemLabel;
/**
* Provides the boundary offsets for a partition on a device
* @param {SourceDestination} source - Device containing requested partition
* @param {number} partitionIndex - 1-based index of requested partition
* @returns {start, end} object with offsets
*/
const getPartitionBoundaries = async (source, partitionIndex = 1) => {
const partitioninfo = await source.getPartitionTable();
const partitions = partitioninfo === null || partitioninfo === void 0 ? void 0 : partitioninfo.partitions;
if (partitions === undefined) {
throw Error("Can't read partitions");
}
const currentPartition = partitions.filter((p) => p.index === partitionIndex)[0];
const start = currentPartition.offset;
let end = undefined;
if (start) {
end = start + currentPartition.size;
}
return {
start,
end,
};
};
exports.getPartitionBoundaries = getPartitionBoundaries;
/**
* Calculate the size required for a partition to contain the contents of
* the provided source partition. On Windows, rounds up to nearest MB due to
* limitations of partitioning tools.
*
* @param {SourceDestination} source - Device containing requested partition
* @param {number} partitionIndex - 1-based index of requested partition
* @returns calculated size in bytes
*/
const calcRequiredPartitionSize = async (source, partitionIndex = 1) => {
const sourceBoundaries = await (0, exports.getPartitionBoundaries)(source, partitionIndex);
const sourcePartitionSize = sourceBoundaries.end - sourceBoundaries.start;
const alignmentBuffer = 4096;
if (isNaN(sourcePartitionSize)) {
throw Error('Not able to find source partition size.');
}
if (process.platform === 'win32') {
const sizeMB = Math.ceil((sourcePartitionSize + alignmentBuffer) / (1024 * 1024));
return sizeMB * 1024 * 1024;
}
else {
return sourcePartitionSize + alignmentBuffer;
}
};
exports.calcRequiredPartitionSize = calcRequiredPartitionSize;
/**
* Copy a partition, referenced by index, from an image file to a block device,
* @param {File} source - source image for partition
* @param {number} sourcePartitionIndex
* @param {BlockDevice} target - Target device for partition
* @param {number} targetOffset - For partition on device
* @returns Promise<>
*/
const copyPartitionFromImageToDevice = async (source, sourcePartitionIndex, target, targetOffset) => {
const sourceBoundaries = await (0, exports.getPartitionBoundaries)(source, sourcePartitionIndex);
const sourcePartitionSize = sourceBoundaries.end - sourceBoundaries.start;
if (isNaN(targetOffset) || isNaN(sourcePartitionSize)) {
throw Error('Not able to find source partition size or target offset.');
}
const alignments = [source.getAlignment(), target.getAlignment()].filter((a) => a !== undefined);
let alignment;
if (alignments.length) {
alignment = Math.max(...alignments);
}
const sourceReadStream = await source.createReadStream({
alignment,
emitProgress: true,
start: sourceBoundaries.start,
end: sourceBoundaries.end,
});
sourceReadStream.on('progress', (c) => {
// console.clear();
console.log(`read: ${JSON.stringify(c)}`);
});
void target.open();
const targetStream = await target.createWriteStream({
startOffset: targetOffset,
});
targetStream.on('progress', (p) => {
// console.clear();
console.log(`write: ${JSON.stringify(p)}`);
});
return new Promise((resolve, reject) => {
sourceReadStream
.on('error', reject)
.pipe(targetStream)
.on('error', reject)
.on('close', resolve);
});
};
exports.copyPartitionFromImageToDevice = copyPartitionFromImageToDevice;
/**
* Creates a block device for the drive on this machine device named with the requested label.
* @returns created device
*/
const getTargetBlockDevice = async (mountLabel = 'C') => {
const drives = await drivelist.list();
const drive = drives.filter((d) => d.mountpoints.some((m) => m.path.startsWith(mountLabel)))[0];
return new source_destination_1.BlockDevice({
drive,
unmountOnSuccess: false,
direct: true,
write: true,
keepOriginal: true,
});
};
exports.getTargetBlockDevice = getTargetBlockDevice;
/** Generates the contents for a NetworkManager configuration file. */
const generateWifiConfig = (profile) => {
const connectionSection = `[connection]
id=${profile.name}
type=wifi
`;
const wifiSection = `
[wifi]
mode=infrastructure
ssid=${profile.wifiSsid}
`;
let keyMgmt;
switch (profile.wifiAuthType) {
case 0 /* WifiAuthType.NONE */:
keyMgmt = '';
break;
case 1 /* WifiAuthType.WPA2_PSK */:
keyMgmt = 'wpa-psk';
break;
case 2 /* WifiAuthType.WPA3_SAE */:
keyMgmt = 'sae';
break;
default:
throw Error(`WifiAuthType ${profile.wifiAuthType} not defined`);
}
let wifiSecuritySection = '';
if (keyMgmt) {
wifiSecuritySection = `
[wifi-security]
auth-alg=open
key-mgmt=${keyMgmt}
psk=${profile.wifiKey}
`;
}
const ipSection = `
[ipv4]
method=auto
[ipv6]
addr-gen-mode=stable-privacy
method=auto
`;
return connectionSection.concat(wifiSection, wifiSecuritySection, ipSection);
};
/**
* Writes a NetworkManager configuration file for the provided network connection
* profile. Assumes profile is for WiFi configuration.
*
* @param {string} pathname - name for file
* @param {ConnectionProfile} profile - Profile data to write; must define WiFi SSID
* @returns Promise<void> on file write
*/
const writeNetworkConfig = async (pathname, profile) => {
if (!profile.wifiSsid) {
throw Error('WiFi SSID not defined for profile');
}
const config = generateWifiConfig(profile);
return (0, promises_1.writeFile)(pathname, config);
};
exports.writeNetworkConfig = writeNetworkConfig;
//# sourceMappingURL=helpers.js.map