partitioninfo
Version:
Get information about a partition from an image file
253 lines • 9.03 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.getPartitions = exports.get = exports.PartitionNotFound = void 0;
const file_disk_1 = require("file-disk");
const GPT = require("gpt");
const MBR = require("mbr");
const typed_error_1 = require("typed-error");
/**
* @module partitioninfo
*/
const MBR_SIZE = 512;
const GPT_SIZE = 512 * 41;
const GPT_PROTECTIVE_MBR = 0xee;
const MBR_LAST_PRIMARY_PARTITION = 4;
const MBR_FIRST_LOGICAL_PARTITION = 5;
function mbrPartitionDict(p, offset, index) {
return {
offset: offset + p.byteOffset(),
size: p.byteSize(),
type: p.type,
index,
};
}
function gptPartitionDict(gpt, p, index) {
return {
offset: p.firstLBA * gpt.blockSize,
size: (p.lastLBA - p.firstLBA + 1) * gpt.blockSize,
type: p.type,
guid: p.guid,
name: p.name,
index,
};
}
// Only for MBR
function getPartitionsFromMBRBuf(buf) {
return new MBR(buf).partitions.filter((p) => p.type);
}
async function readFromDisk(disk, offset, size) {
const { buffer } = await disk.read(Buffer.alloc(size), 0, size, offset);
return buffer;
}
// Only for MBR
async function getLogicalPartitions(disk, index, offset, extendedPartitionOffset, limit) {
if (extendedPartitionOffset === undefined) {
extendedPartitionOffset = offset;
}
if (limit === undefined) {
limit = Infinity;
}
const result = [];
if (limit <= 0) {
return result;
}
const buf = await readFromDisk(disk, offset, MBR_SIZE);
for (const p of getPartitionsFromMBRBuf(buf)) {
if (!p.extended) {
result.push(mbrPartitionDict(p, offset, index));
}
else if (limit > 0) {
const logicalPartitions = await getLogicalPartitions(disk, index + 1, extendedPartitionOffset + p.byteOffset(), extendedPartitionOffset, limit - 1);
result.push(...logicalPartitions);
return result;
}
}
return result;
}
function detectGPT(buffer) {
let blockSize = MBR_SIZE;
// Attempt to parse the GPT from several offsets,
// as the block size of the image may vary (512,1024,2048,4096);
// For example, ISOs will usually have a block size of 4096,
// but raw images a block size of 512 bytes
let lastError;
while (blockSize <= 4096) {
try {
return GPT.parse(buffer.slice(blockSize));
}
catch (error) {
lastError = error;
}
blockSize *= 2;
}
throw lastError;
}
async function getDiskPartitions(disk, { offset, includeExtended, getLogical, }) {
let extended = null;
const mbrBuf = await readFromDisk(disk, offset, MBR_SIZE);
const partitions = getPartitionsFromMBRBuf(mbrBuf);
if (partitions.length >= 1 && partitions[0].type === GPT_PROTECTIVE_MBR) {
const gptBuf = await readFromDisk(disk, 0, GPT_SIZE);
const gpt = detectGPT(gptBuf);
return {
type: 'gpt',
partitions: gpt.partitions.map((partition, index) => gptPartitionDict(gpt, partition, index + 1)),
};
}
else {
const mbrPartitions = [];
for (let index = 0; index < partitions.length; index++) {
const p = partitions[index];
if (p.extended) {
extended = p;
if (includeExtended) {
mbrPartitions.push(mbrPartitionDict(p, offset, index + 1));
}
}
else {
mbrPartitions.push(mbrPartitionDict(p, offset, index + 1));
}
}
if (extended != null && getLogical) {
const logicalPartitions = await getLogicalPartitions(disk, MBR_FIRST_LOGICAL_PARTITION, extended.byteOffset());
mbrPartitions.push(...logicalPartitions);
}
return { type: 'mbr', partitions: mbrPartitions };
}
}
class PartitionNotFound extends typed_error_1.TypedError {
constructor(partitionNumber) {
super(`Partition not found: ${partitionNumber}.`);
}
}
exports.PartitionNotFound = PartitionNotFound;
async function getPartition(disk, partitionNumber) {
if (partitionNumber < 1) {
throw new Error('The partition number must be at least 1.');
}
const info = await getDiskPartitions(disk, {
includeExtended: true,
offset: 0,
getLogical: false,
});
if (info.type === 'gpt') {
if (info.partitions.length < partitionNumber) {
throw new PartitionNotFound(partitionNumber);
}
else {
return info.partitions[partitionNumber - 1];
}
}
if (partitionNumber <= MBR_LAST_PRIMARY_PARTITION) {
if (partitionNumber <= info.partitions.length) {
return info.partitions[partitionNumber - 1];
}
else {
throw new PartitionNotFound(partitionNumber);
}
}
const extended = info.partitions.find((p) => MBR.Partition.isExtended(p.type));
if (!extended) {
throw new PartitionNotFound(partitionNumber);
}
else {
const logicalPartitionPosition = partitionNumber - MBR_FIRST_LOGICAL_PARTITION;
const logicalPartitions = await getLogicalPartitions(disk, MBR_FIRST_LOGICAL_PARTITION, extended.offset, extended.offset, logicalPartitionPosition + 1);
if (logicalPartitionPosition < logicalPartitions.length) {
return logicalPartitions[logicalPartitionPosition];
}
else {
throw new PartitionNotFound(partitionNumber);
}
}
}
function isString(x) {
return typeof x === 'string';
}
async function callWithDisk(fn, pathOrBufferOrDisk, arg) {
if (isString(pathOrBufferOrDisk)) {
return await (0, file_disk_1.withOpenFile)(pathOrBufferOrDisk, 'r', async (fd) => {
return await fn(new file_disk_1.FileDisk(fd), arg);
});
}
else if (Buffer.isBuffer(pathOrBufferOrDisk)) {
return await fn(new file_disk_1.BufferDisk(pathOrBufferOrDisk), arg);
}
else {
return await fn(pathOrBufferOrDisk, arg);
}
}
/**
* @summary Get information from a partition
* @public
* @function
*
* @param {String|Buffer|filedisk.Disk} image - image path or buffer or filedisk.Disk instance
* @param {Object} number - partition number
*
* @returns {Promise<Object>} partition information
*
* @example
* partitioninfo.get('foo/bar.img', 5)
* .then (information) ->
* console.log(information.offset)
* console.log(information.size)
* console.log(information.type)
* console.log(information.index)
*/
async function get(disk, partitionNumber) {
return await callWithDisk(getPartition, disk, partitionNumber);
}
exports.get = get;
/**
* @summary Read all partition tables from a disk image recursively.
* @public
* @function
*
* @description `getPartitions()` returns an Array.
* `getPartitions(image)[N - 1]` may not be equal to `get(image, N)`
* For example on a disk with no primary partitions and one extended partition
* containing a logical one, `getPartitions(image)` would return an array of 2 partitions
* (the extended then the logical one), `get(image, 1)` would return the extended
* partition and `get(image, 5)` would return the logical partition. All other
* numbers would throw an error.
* Partition numbers for `get(image, N)` are like Linux's `/dev/sdaN`.
*
* The array returned by `getPartitions()` contains primary (or extended) partitions
* first then the logical ones. This is true even if the extended partition is not the
* last one of the disk image. Order will always be 1, [2, 3, 4, 5, 6, 7] even if
* the logical partitions 5, 6 and 7 are physically contained in partiton 1, 2 or 3.
*
* @param {String|Buffer|filedisk.Disk} image - image path or buffer or filedisk.Disk instance
* @param {Object} options
* @param {Number} [options.offset=0] - where the first partition table will be read from, in bytes
* @param {Boolean} [options.includeExtended=true] - whether to include extended partitions or not (only for MBR partition tables)
* @param {Boolean} [options.getLogical=true] - whether to include logical partitions or not (only for MBR partition tables)
*
* @throws {Error} if there is no such partition
*
* @returns {Promise<Object>} partitions information
*
* @example
* partitioninfo.getPartitions('foo/bar.img')
* .then (information) ->
* console.log(information.type)
* for partition in information.partitions
* console.log(partition.offset)
* console.log(partition.size)
* console.log(partition.type)
* console.log(partition.index)
*/
async function getPartitions(disk, { offset = 0, includeExtended = true, getLogical = true, } = {
offset: 0,
includeExtended: true,
getLogical: true,
}) {
return await callWithDisk(getDiskPartitions, disk, {
offset,
includeExtended,
getLogical,
});
}
exports.getPartitions = getPartitions;
//# sourceMappingURL=partitioninfo.js.map