// EXT filesystem implementation (ext2/ext3/ext4) import { BaseFilesystem } from './base.js'; import { checkDependencies, updateResultItem } from '../utils.js'; export class EXTFilesystem extends BaseFilesystem { constructor() { super('EXT', [ { id: 'ext', name: 'EXT (ext2/ext3/ext4)', constants: [ { id: 'partitionStartEXT', label: 'Partitionsstart (Basis-Offset)', unit: 'Bytes', default: '0x0' }, { id: 'superblockOffsetEXT', label: 'Superblock-Offset (relativ zur Partition)', unit: 'Bytes', default: '0x400' } ], inputs: [ { id: 'blockSizeExpEXT', label: 'Block-Größe Exponent (Superblock-Offset 0x18)', unit: 'Exponent n', placeholder: '0x0' }, { id: 'blocksPerGroupEXT', label: 'Blöcke pro Blockgruppe (Superblock-Offset 0x20)', unit: 'Anzahl', placeholder: '0x8000' }, { id: 'inodesPerGroupEXT', label: 'Inodes pro Blockgruppe (Superblock-Offset 0x28)', unit: 'Anzahl', placeholder: '0x2000' }, { id: 'inodeSizeEXT', label: 'Inode-Größe (Superblock-Offset 0x58)', unit: 'Bytes', placeholder: '0x100' }, { id: 'totalBlocksEXT', label: 'Gesamtanzahl Blöcke (Superblock-Offset 0x04)', unit: 'Anzahl', placeholder: '0x40000' }, { id: 'groupDescSizeEXT', label: 'Größe Gruppendeskriptor (Superblock-Offset 0xFE)', unit: 'Bytes', placeholder: '0x20' }, { id: 'inodeTableBlockGroupEXT', label: 'Inode-Tabelle Block (GDT-Offset 0x08)', unit: 'Block-Nummer', placeholder: '0x21' }, { id: 'targetInodeEXT', label: 'Gesuchter Inode', unit: 'Nummer', placeholder: '0x02' }, { id: 'targetBlockEXT', label: 'Gesuchter Block', unit: 'Nummer', placeholder: '0x100' }, { id: 'blockgroupNumberEXT', label: 'Blockgruppen-Nummer', unit: 'Nummer', placeholder: '0x0' } ], resultGroups: [ { name: 'Basis-Berechnungen', results: [ { id: 'superblockPosEXT', label: 'Superblock Position', dependencies: ['partitionStartEXT', 'superblockOffsetEXT'], formula: 'Partitionsstart + 0x400' }, { id: 'blockSizeBytesEXT', label: 'Block-Größe (Bytes)', dependencies: ['blockSizeExpEXT'], formula: '2^(10+n) Bytes' }, { id: 'totalBlockgroupsEXT', label: 'Anzahl Blockgruppen', dependencies: ['totalBlocksEXT', 'blocksPerGroupEXT'], formula: 'Aufrunden(Gesamtblöcke ÷ Blöcke pro Gruppe)' } ] }, { name: 'Gruppendeskriptor-Tabelle', results: [ { id: 'gdtStartEXT', label: 'GDT Start', dependencies: ['partitionStartEXT', 'superblockOffsetEXT', 'blockSizeExpEXT'], formula: 'Partitionsstart + Block nach Superblock' }, { id: 'gdtBlockgroupOffsetEXT', label: 'GDT-Eintrag für Blockgruppe', dependencies: ['partitionStartEXT', 'superblockOffsetEXT', 'blockSizeExpEXT', 'blockgroupNumberEXT', 'groupDescSizeEXT'], formula: 'GDT Start + (Blockgruppe × GD-Größe)' } ] }, { name: 'Inode-Berechnungen', results: [ { id: 'inodeBlockgroupEXT', label: 'Blockgruppe des Inodes', dependencies: ['targetInodeEXT', 'inodesPerGroupEXT'], formula: 'Abrunden((Inode - 1) ÷ Inodes pro Gruppe)' }, { id: 'inodeRelativeIndexEXT', label: 'Relativer Index in Blockgruppe', dependencies: ['targetInodeEXT', 'inodesPerGroupEXT'], formula: '(Inode - 1) % Inodes pro Gruppe' }, { id: 'inodeTableStartEXT', label: 'Inode-Tabelle Start', dependencies: ['partitionStartEXT', 'inodeTableBlockGroupEXT', 'blockSizeExpEXT'], formula: 'Partitionsstart + (Block × Block-Größe)' }, { id: 'inodeOffsetEXT', label: 'Inode Offset', dependencies: ['partitionStartEXT', 'inodeTableBlockGroupEXT', 'blockSizeExpEXT', 'targetInodeEXT', 'inodesPerGroupEXT', 'inodeSizeEXT'], formula: 'Inode-Tabelle Start + (Relativer Index × Inode-Größe)' } ] }, { name: 'Block-Berechnungen', results: [ { id: 'blockOffsetEXT', label: 'Block Offset', dependencies: ['partitionStartEXT', 'targetBlockEXT', 'blockSizeExpEXT'], formula: 'Partitionsstart + (Block-Nummer × Block-Größe)' } ] }, { name: 'Reservierte Inodes (Systemdateien)', results: [ { id: 'badBlocksInodeEXT', label: 'Inode 1 - Beschädigte Blöcke', dependencies: ['partitionStartEXT', 'inodeTableBlockGroupEXT', 'blockSizeExpEXT', 'inodeSizeEXT'], formula: 'Inode-Tabelle Start + (0 × Inode-Größe)' }, { id: 'rootDirInodeEXT', label: 'Inode 2 - Wurzelverzeichnis', dependencies: ['partitionStartEXT', 'inodeTableBlockGroupEXT', 'blockSizeExpEXT', 'inodeSizeEXT'], formula: 'Inode-Tabelle Start + (1 × Inode-Größe)' }, { id: 'userQuotaInodeEXT', label: 'Inode 3 - Benutzer-Quotas', dependencies: ['partitionStartEXT', 'inodeTableBlockGroupEXT', 'blockSizeExpEXT', 'inodeSizeEXT'], formula: 'Inode-Tabelle Start + (2 × Inode-Größe)' }, { id: 'groupQuotaInodeEXT', label: 'Inode 4 - Gruppen-Quotas', dependencies: ['partitionStartEXT', 'inodeTableBlockGroupEXT', 'blockSizeExpEXT', 'inodeSizeEXT'], formula: 'Inode-Tabelle Start + (3 × Inode-Größe)' }, { id: 'bootloaderInodeEXT', label: 'Inode 5 - Bootloader', dependencies: ['partitionStartEXT', 'inodeTableBlockGroupEXT', 'blockSizeExpEXT', 'inodeSizeEXT'], formula: 'Inode-Tabelle Start + (4 × Inode-Größe)' }, { id: 'journalInodeEXT', label: 'Inode 8 - Journal (ext3/ext4)', dependencies: ['partitionStartEXT', 'inodeTableBlockGroupEXT', 'blockSizeExpEXT', 'inodeSizeEXT'], formula: 'Inode-Tabelle Start + (7 × Inode-Größe)' } ] }, { name: 'Inode-Tabellen-Übersicht', results: [ { id: 'inodeTableSizeEXT', label: 'Inode-Tabelle Größe (Bytes)', dependencies: ['inodesPerGroupEXT', 'inodeSizeEXT'], formula: 'Inodes pro Gruppe × Inode-Größe' }, { id: 'inodeTableSizeBlocksEXT', label: 'Inode-Tabelle Größe (Blöcke)', dependencies: ['inodesPerGroupEXT', 'inodeSizeEXT', 'blockSizeExpEXT'], formula: 'Aufrunden(Tabellengröße ÷ Block-Größe)' } ] } ], formulas: [ { name: 'Superblock Position', expression: 'Partitionsstart + 0x400 (immer fix bei Offset 0x400)' }, { name: 'Block-Größe', expression: '2^(10+n) Bytes, wobei n der Exponent aus Offset 0x418 ist (0 = 1024, 1 = 2048, 2 = 4096, etc.)' }, { name: 'Anzahl Blockgruppen', expression: 'Aufrunden(Gesamtanzahl Blöcke ÷ Blöcke pro Blockgruppe)' }, { name: 'GDT Start', expression: 'Partitionsstart + (Block nach Superblock × Block-Größe)' }, { name: 'GDT-Eintrag Offset', expression: 'GDT Start + (Blockgruppen-Nummer × Gruppendeskriptor-Größe)' }, { name: 'Blockgruppe des Inodes', expression: 'Abrunden((Inode-Nummer - 1) ÷ Inodes pro Blockgruppe)' }, { name: 'Relativer Index', expression: '(Inode-Nummer - 1) % Inodes pro Blockgruppe' }, { name: 'Inode-Tabelle Start', expression: 'Partitionsstart + (Inode-Tabelle Block-Nummer × Block-Größe)' }, { name: 'Inode Offset', expression: 'Inode-Tabelle Start + (Relativer Index × Inode-Größe)' }, { name: 'Block Offset', expression: 'Partitionsstart + (Block-Nummer × Block-Größe)' }, { name: 'Reservierte Inodes', expression: 'Inode-Tabelle Start + ((Inode-Nummer - 1) × Inode-Größe)' }, { name: 'Inode-Tabelle Größe', expression: 'Inodes pro Blockgruppe × Inode-Größe' } ] } ]); } calculate(variantId) { if (variantId !== 'ext') return; const values = this.getInputValues(variantId); // Validate input ranges specific to EXT filesystem const validation = this.validateFilesystemSpecific(variantId, values); if (!validation.valid) { console.warn('EXT validation errors:', validation.errors); // Continue with calculation but log warnings } const results = {}; this.calculateEXT(values, results); } validateFilesystemSpecific(variantId, values) { if (variantId !== 'ext') return { valid: true, errors: [] }; const errors = []; // Validate blockgroup number doesn't exceed total blockgroups if (values.blockgroupNumberEXT !== undefined && values.totalBlocksEXT !== undefined && values.blocksPerGroupEXT !== undefined && values.blocksPerGroupEXT > 0) { const totalBlockgroups = Math.ceil(values.totalBlocksEXT / values.blocksPerGroupEXT); if (values.blockgroupNumberEXT >= totalBlockgroups) { errors.push(`blockgroupNumberEXT: ${values.blockgroupNumberEXT} >= Gesamtblockgruppen: ${totalBlockgroups}`); } } // Validate inode number isn't 0 (no inode 0 exists) if (values.targetInodeEXT !== undefined && values.targetInodeEXT === 0) { errors.push('targetInodeEXT: Inode 0 existiert nicht'); } // Validate block and inode sizes are reasonable if (values.blockSizeExpEXT !== undefined) { const blockSize = Math.pow(2, 10 + values.blockSizeExpEXT); if (blockSize < 1024 || blockSize > 65536) { errors.push(`Block-Größe: ${blockSize} außerhalb akzeptabeler Grenzen (1024-65536)`); } } if (values.inodeSizeEXT !== undefined) { if (values.inodeSizeEXT < 128 || values.inodeSizeEXT > 4096) { errors.push(`Inode-Größe: ${values.inodeSizeEXT} außerhalb akzeptabeler Grenzen (128-4096)`); } } return { valid: errors.length === 0, errors: errors }; } calculateEXT(values, results) { try { // Superblock Position if (checkDependencies(['partitionStartEXT', 'superblockOffsetEXT'])) { results.superblockPos = values.partitionStartEXT + values.superblockOffsetEXT; updateResultItem('superblockPosEXT', { bytes: results.superblockPos, other: `Block 1 (bei 1024-Byte Blöcken)` }, true, `0x${Math.floor(values.partitionStartEXT).toString(16).toUpperCase()} + 0x${Math.floor(values.superblockOffsetEXT).toString(16).toUpperCase()} = 0x${Math.floor(results.superblockPos).toString(16).toUpperCase()}`); } else { updateResultItem('superblockPosEXT', 0, false); } // Block Size (2^(10+n)) if (checkDependencies(['blockSizeExpEXT'])) { results.blockSize = Math.pow(2, 10 + values.blockSizeExpEXT); updateResultItem('blockSizeBytesEXT', { bytes: results.blockSize, other: `2^${10 + values.blockSizeExpEXT}` }, true, `2^(10 + ${values.blockSizeExpEXT}) = 2^${10 + values.blockSizeExpEXT} = 0x${Math.floor(results.blockSize).toString(16).toUpperCase()} (${results.blockSize} Bytes)`); } else { updateResultItem('blockSizeBytesEXT', 0, false); } // Total Blockgroups if (checkDependencies(['totalBlocksEXT', 'blocksPerGroupEXT'])) { results.totalBlockgroups = Math.ceil(values.totalBlocksEXT / values.blocksPerGroupEXT); updateResultItem('totalBlockgroupsEXT', results.totalBlockgroups, true, `Aufrunden(0x${values.totalBlocksEXT.toString(16)} ÷ 0x${values.blocksPerGroupEXT.toString(16)}) = ${results.totalBlockgroups}`); } else { updateResultItem('totalBlockgroupsEXT', 0, false); } // GDT Start (block after superblock) if (checkDependencies(['partitionStartEXT', 'superblockOffsetEXT', 'blockSizeExpEXT']) && results.blockSize !== undefined) { // Special handling for 1024-byte block size: // - Superblock is in block 1 (not block 0) because it starts at offset 0x400 // - GDT follows in block 2 // For other block sizes (2048, 4096, ...): // - Superblock is in block 0 (starts at offset 0) // - GDT follows in block 1 const superblockBlock = (results.blockSize === 1024) ? 1 : 0; const gdtBlock = superblockBlock + 1; results.gdtStart = values.partitionStartEXT + (gdtBlock * results.blockSize); const gdtBlockNum = Math.floor(results.gdtStart / results.blockSize) - Math.floor(values.partitionStartEXT / results.blockSize); updateResultItem('gdtStartEXT', { bytes: results.gdtStart, other: `Block ${gdtBlock}` }, true, `0x${Math.floor(values.partitionStartEXT).toString(16).toUpperCase()} + (Block ${gdtBlock} × 0x${Math.floor(results.blockSize).toString(16).toUpperCase()}) = 0x${Math.floor(results.gdtStart).toString(16).toUpperCase()}`); } else { updateResultItem('gdtStartEXT', 0, false); } // GDT Entry for specific Blockgroup if (checkDependencies(['blockgroupNumberEXT', 'groupDescSizeEXT']) && results.gdtStart !== undefined) { results.gdtBlockgroupOffset = results.gdtStart + (values.blockgroupNumberEXT * values.groupDescSizeEXT); updateResultItem('gdtBlockgroupOffsetEXT', { bytes: results.gdtBlockgroupOffset, other: `Eintrag ${values.blockgroupNumberEXT}` }, true, `0x${Math.floor(results.gdtStart).toString(16).toUpperCase()} + (0x${Math.floor(values.blockgroupNumberEXT).toString(16).toUpperCase()} × 0x${Math.floor(values.groupDescSizeEXT).toString(16).toUpperCase()}) = 0x${Math.floor(results.gdtBlockgroupOffset).toString(16).toUpperCase()}`); } else { updateResultItem('gdtBlockgroupOffsetEXT', 0, false); } // Inode Blockgroup if (checkDependencies(['targetInodeEXT', 'inodesPerGroupEXT'])) { results.inodeBlockgroup = Math.floor((values.targetInodeEXT - 1) / values.inodesPerGroupEXT); updateResultItem('inodeBlockgroupEXT', results.inodeBlockgroup, true, `Abrunden((0x${values.targetInodeEXT.toString(16)} - 1) ÷ 0x${values.inodesPerGroupEXT.toString(16)}) = ${results.inodeBlockgroup}`); } else { updateResultItem('inodeBlockgroupEXT', 0, false); } // Inode Relative Index if (checkDependencies(['targetInodeEXT', 'inodesPerGroupEXT'])) { results.inodeRelativeIndex = (values.targetInodeEXT - 1) % values.inodesPerGroupEXT; updateResultItem('inodeRelativeIndexEXT', results.inodeRelativeIndex, true, `(0x${values.targetInodeEXT.toString(16)} - 1) % 0x${values.inodesPerGroupEXT.toString(16)} = 0x${results.inodeRelativeIndex.toString(16)}`); } else { updateResultItem('inodeRelativeIndexEXT', 0, false); } // Inode Table Start if (checkDependencies(['partitionStartEXT', 'inodeTableBlockGroupEXT', 'blockSizeExpEXT']) && results.blockSize !== undefined) { results.inodeTableStart = values.partitionStartEXT + (values.inodeTableBlockGroupEXT * results.blockSize); updateResultItem('inodeTableStartEXT', { bytes: results.inodeTableStart, other: `Block ${Math.floor(values.inodeTableBlockGroupEXT)}` }, true, `0x${Math.floor(values.partitionStartEXT).toString(16).toUpperCase()} + (0x${Math.floor(values.inodeTableBlockGroupEXT).toString(16).toUpperCase()} × 0x${Math.floor(results.blockSize).toString(16).toUpperCase()}) = 0x${Math.floor(results.inodeTableStart).toString(16).toUpperCase()}`); } else { updateResultItem('inodeTableStartEXT', 0, false); } // Inode Offset if (checkDependencies(['targetInodeEXT', 'inodesPerGroupEXT', 'inodeSizeEXT']) && results.inodeTableStart !== undefined && results.inodeRelativeIndex !== undefined) { results.inodeOffset = results.inodeTableStart + (results.inodeRelativeIndex * values.inodeSizeEXT); updateResultItem('inodeOffsetEXT', { bytes: results.inodeOffset, other: `Inode-Größe: 0x${Math.floor(values.inodeSizeEXT).toString(16).toUpperCase()}` }, true, `0x${Math.floor(results.inodeTableStart).toString(16).toUpperCase()} + (0x${Math.floor(results.inodeRelativeIndex).toString(16).toUpperCase()} × 0x${Math.floor(values.inodeSizeEXT).toString(16).toUpperCase()}) = 0x${Math.floor(results.inodeOffset).toString(16).toUpperCase()}`); } else { updateResultItem('inodeOffsetEXT', 0, false); } // Block Offset if (checkDependencies(['partitionStartEXT', 'targetBlockEXT', 'blockSizeExpEXT']) && results.blockSize !== undefined) { results.blockOffset = values.partitionStartEXT + (values.targetBlockEXT * results.blockSize); updateResultItem('blockOffsetEXT', { bytes: results.blockOffset, other: `Block 0x${Math.floor(values.targetBlockEXT).toString(16).toUpperCase()}` }, true, `0x${Math.floor(values.partitionStartEXT).toString(16).toUpperCase()} + (0x${Math.floor(values.targetBlockEXT).toString(16).toUpperCase()} × 0x${Math.floor(results.blockSize).toString(16).toUpperCase()}) = 0x${Math.floor(results.blockOffset).toString(16).toUpperCase()}`); } else { updateResultItem('blockOffsetEXT', 0, false); } // Reserved Inodes (always in blockgroup 0, inode table) if (results.inodeTableStart !== undefined && checkDependencies(['inodeSizeEXT'])) { // Inode 1 (Bad Blocks) - Index 0 results.badBlocksInode = results.inodeTableStart + (0 * values.inodeSizeEXT); updateResultItem('badBlocksInodeEXT', { bytes: results.badBlocksInode, other: 'Inode 1 (Bad Blocks)' }, true, `0x${Math.floor(results.inodeTableStart).toString(16).toUpperCase()} + (0 × 0x${Math.floor(values.inodeSizeEXT).toString(16).toUpperCase()}) = 0x${Math.floor(results.badBlocksInode).toString(16).toUpperCase()}`); // Inode 2 (Root Directory) - Index 1 results.rootDirInode = results.inodeTableStart + (1 * values.inodeSizeEXT); updateResultItem('rootDirInodeEXT', { bytes: results.rootDirInode, other: 'Inode 2 (Root Dir)' }, true, `0x${Math.floor(results.inodeTableStart).toString(16).toUpperCase()} + (1 × 0x${Math.floor(values.inodeSizeEXT).toString(16).toUpperCase()}) = 0x${Math.floor(results.rootDirInode).toString(16).toUpperCase()}`); // Inode 3 (User Quotas) - Index 2 results.userQuotaInode = results.inodeTableStart + (2 * values.inodeSizeEXT); updateResultItem('userQuotaInodeEXT', { bytes: results.userQuotaInode, other: 'Inode 3 (User Quota)' }, true, `0x${Math.floor(results.inodeTableStart).toString(16).toUpperCase()} + (2 × 0x${Math.floor(values.inodeSizeEXT).toString(16).toUpperCase()}) = 0x${Math.floor(results.userQuotaInode).toString(16).toUpperCase()}`); // Inode 4 (Group Quotas) - Index 3 results.groupQuotaInode = results.inodeTableStart + (3 * values.inodeSizeEXT); updateResultItem('groupQuotaInodeEXT', { bytes: results.groupQuotaInode, other: 'Inode 4 (Group Quota)' }, true, `0x${Math.floor(results.inodeTableStart).toString(16).toUpperCase()} + (3 × 0x${Math.floor(values.inodeSizeEXT).toString(16).toUpperCase()}) = 0x${Math.floor(results.groupQuotaInode).toString(16).toUpperCase()}`); // Inode 5 (Bootloader) - Index 4 results.bootloaderInode = results.inodeTableStart + (4 * values.inodeSizeEXT); updateResultItem('bootloaderInodeEXT', { bytes: results.bootloaderInode, other: 'Inode 5 (Bootloader)' }, true, `0x${Math.floor(results.inodeTableStart).toString(16).toUpperCase()} + (4 × 0x${Math.floor(values.inodeSizeEXT).toString(16).toUpperCase()}) = 0x${Math.floor(results.bootloaderInode).toString(16).toUpperCase()}`); // Inode 8 (Journal) - Index 7 results.journalInode = results.inodeTableStart + (7 * values.inodeSizeEXT); updateResultItem('journalInodeEXT', { bytes: results.journalInode, other: 'Inode 8 (Journal)' }, true, `0x${Math.floor(results.inodeTableStart).toString(16).toUpperCase()} + (7 × 0x${Math.floor(values.inodeSizeEXT).toString(16).toUpperCase()}) = 0x${Math.floor(results.journalInode).toString(16).toUpperCase()}`); } else { updateResultItem('badBlocksInodeEXT', 0, false); updateResultItem('rootDirInodeEXT', 0, false); updateResultItem('userQuotaInodeEXT', 0, false); updateResultItem('groupQuotaInodeEXT', 0, false); updateResultItem('bootloaderInodeEXT', 0, false); updateResultItem('journalInodeEXT', 0, false); } // Inode Table Size (Bytes) if (checkDependencies(['inodesPerGroupEXT', 'inodeSizeEXT'])) { results.inodeTableSize = values.inodesPerGroupEXT * values.inodeSizeEXT; updateResultItem('inodeTableSizeEXT', { bytes: results.inodeTableSize, other: `für 0x${Math.floor(values.inodesPerGroupEXT).toString(16).toUpperCase()} Inodes` }, true, `0x${Math.floor(values.inodesPerGroupEXT).toString(16).toUpperCase()} × 0x${Math.floor(values.inodeSizeEXT).toString(16).toUpperCase()} = 0x${Math.floor(results.inodeTableSize).toString(16).toUpperCase()} (${Math.floor(results.inodeTableSize)} Bytes)`); } else { updateResultItem('inodeTableSizeEXT', 0, false); } // Inode Table Size (Blocks) if (checkDependencies(['inodesPerGroupEXT', 'inodeSizeEXT', 'blockSizeExpEXT']) && results.blockSize !== undefined && results.inodeTableSize !== undefined) { results.inodeTableSizeBlocks = Math.ceil(results.inodeTableSize / results.blockSize); updateResultItem('inodeTableSizeBlocksEXT', results.inodeTableSizeBlocks, true, `Aufrunden(0x${results.inodeTableSize.toString(16)} ÷ 0x${results.blockSize.toString(16)}) = ${results.inodeTableSizeBlocks} Blöcke`); } else { updateResultItem('inodeTableSizeBlocksEXT', 0, false); } } catch (error) { console.error('Fehler in EXT-Berechnung:', error); // Mark all results as unavailable on error updateResultItem('superblockPosEXT', 0, false); updateResultItem('blockSizeBytesEXT', 0, false); updateResultItem('totalBlockgroupsEXT', 0, false); } } }