"use strict";
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