balena-image-fs
Version:
Image filesystem manipulation utilities
125 lines • 5.35 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.LabelNotFound = void 0;
exports.getFsLabel = getFsLabel;
const typed_error_1 = require("typed-error");
// GPT partition GUIDs
// https://en.wikipedia.org/wiki/GUID_Partition_Table#Partition_type_GUIDs
const GUID_EFI_SYSTEM = 'C12A7328-F81F-11D2-BA4B-00A0C93EC93B';
const GUID_MS_BASIC_DATA = 'EBD0A0A2-B9E5-4433-87C0-68B6B72699C7';
const GUID_LINUX_NATIVE = '0FC63DAF-8483-4772-8E79-3D69D8477DE4';
// Maximum length to read among various filesystem metadata. Change as needed
// to support additional filesystems.
const FS_METADATA_MAXLEN = 0x100;
// MBR partition type IDs
// https://en.wikipedia.org/wiki/Partition_type
const PARTID_FAT32_CHS = 0xB;
const PARTID_FAT32_LBA = 0xC;
const PARTID_FAT16_LBA = 0xE;
const PARTID_LINUX_NATIVE = 0x83;
// FAT constants
// https://en.wikipedia.org/wiki/Design_of_the_FAT_file_system#Extended_BIOS_Parameter_Block
// https://en.wikipedia.org/wiki/Design_of_the_FAT_file_system#FAT32_Extended_BIOS_Parameter_Block
const FAT_LABEL_MAXLEN = 11;
const FAT_MAGIC_VALUE = 0x29;
const FAT16_MAGIC_OFFSET = 0x26;
const FAT16_LABEL_OFFSET = 0x2B;
const FAT32_MAGIC_OFFSET = 0x42;
const FAT32_LABEL_OFFSET = 0x47;
// EXT4 constants
// https://www.kernel.org/doc/html/latest/filesystems/ext4/index.html
const EXT4_SUPERBLOCK_OFFSET = 0x400;
const EXT4_MAGIC_OFFSET = 0x38;
const EXT4_MAGIC_VALUE_LE = 0xEF53;
const EXT4_LABEL_OFFSET = 0x78;
const EXT4_LABEL_MAXLEN = 16;
/**
* @summary An error to describe why getFsLabel() failed to find a filesystem label.
*/
class LabelNotFound extends typed_error_1.TypedError {
constructor(partitionIndex, description) {
super(`Label not found: ${partitionIndex}; ${description}`);
}
}
exports.LabelNotFound = LabelNotFound;
/**
* @summary Returns the label encoded in the filesystem for a given partition,
* or throws LabelNotFound if can't determine label location.
*
* This function focuses on balenaOS devices and does not attempt to read the
* many possible filesystem types.
*
* @example
*
* await filedisk.withOpenFile('/foo/bar.img', 'r', async (handle) => {
* const disk = new filedisk.FileDisk(handle);
* const info = partitioninfo.getPartitions(disk);
* for (const partition of info.partitions) {
* const label = await getFsLabel(disk, partition);
* console.log(`${partition.index}: ${label}`);
* }
* }
*/
async function getFsLabel(disk, partition) {
const isGpt = 'guid' in partition;
const isMbr = !isGpt;
// Use a buffer capable of reading any filesystem metadata. Defer actual
// read until select filesystem.
const buf = Buffer.alloc(FS_METADATA_MAXLEN);
let labelOffset;
let maxLength;
// A filesystem places the label in a certain position in its metadata. So
// we must determine the type of filesystem. We first narrow the possibilities
// by checking the partition type (MBR) or GUID (GPT) for the containing partition.
// There are two broad categories of filesystem -- FAT types and Linux native
// types. Finally we confirm the presence of signature bytes within the filesystem
// metadata.
// FAT types
const mbrFatTypes = [PARTID_FAT32_CHS, PARTID_FAT32_LBA, PARTID_FAT16_LBA];
const gptFatTypes = [GUID_EFI_SYSTEM, GUID_MS_BASIC_DATA];
if (isMbr && mbrFatTypes.includes(partition.type)
|| isGpt && gptFatTypes.includes(partition.type)) {
maxLength = FAT_LABEL_MAXLEN;
await disk.read(buf, 0, buf.length, partition.offset);
// FAT16
if (buf.readUInt8(FAT16_MAGIC_OFFSET) === FAT_MAGIC_VALUE) {
labelOffset = FAT16_LABEL_OFFSET;
// FAT32
}
else if (buf.readUInt8(FAT32_MAGIC_OFFSET) === FAT_MAGIC_VALUE) {
labelOffset = FAT32_LABEL_OFFSET;
}
}
// Linux filesytem of some kind; expecting ext2+
const gptLinuxTypes = [GUID_LINUX_NATIVE, GUID_MS_BASIC_DATA];
if (labelOffset == null && ((isMbr && partition.type === PARTID_LINUX_NATIVE)
|| (isGpt && gptLinuxTypes.includes(partition.type)))) {
maxLength = EXT4_LABEL_MAXLEN;
// ext2+; reload buffer within superblock
await disk.read(buf, 0, buf.length, partition.offset + EXT4_SUPERBLOCK_OFFSET);
if (buf.readUInt16LE(EXT4_MAGIC_OFFSET) === EXT4_MAGIC_VALUE_LE) {
labelOffset = EXT4_LABEL_OFFSET;
}
}
// Fail on unexpected partition/filesystem
// If max length for a label not defined, then implicitly we have not even
// found a partition type.
if (maxLength == null) {
throw new LabelNotFound(partition.index, 'unexpected partition type');
}
if (labelOffset == null) {
throw new LabelNotFound(partition.index, 'can\'t read filesystem');
}
// Exclude trailing /0 bytes to stringify.
let zeroBytePosition;
for (let i = 0; i < maxLength; i++) {
if (buf.readUInt8(labelOffset + i) === 0) {
zeroBytePosition = i;
break;
}
}
// Didn't find a /0 byte; must be max length.
zeroBytePosition !== null && zeroBytePosition !== void 0 ? zeroBytePosition : (zeroBytePosition = maxLength);
return buf.toString('utf8', labelOffset, labelOffset + zeroBytePosition).trim();
}
//# sourceMappingURL=fsLabel.js.map