UNPKG

partitioninfo

Version:

Get information about a partition from an image file

253 lines 9.03 kB
"use strict"; 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