@@ -55,6 +55,11 @@ extension EXT4 {
5555 ( ( groupCount - 1 ) / groupsPerDescriptorBlock + 1 ) * 32
5656 }
5757
58+ private var blocksInLastGroup : UInt32 {
59+ let remainder = blockCount % blocksPerGroup
60+ return remainder == 0 ? blocksPerGroup : remainder
61+ }
62+
5863 /// Initializes an ext4 filesystem formatter.
5964 ///
6065 /// This constructor creates an instance of the ext4 formatter designed to format a block device
@@ -685,11 +690,6 @@ extension EXT4 {
685690 if newSize < contentRequiredSize {
686691 newSize = contentRequiredSize
687692 }
688- // number of blocks needed for group descriptors
689- let groupDescriptorBlockCount : UInt32 = ( blockGroupSize. blockGroups - 1 ) / self . groupsPerDescriptorBlock + 1
690- guard groupDescriptorBlockCount <= self . groupDescriptorBlocks else {
691- throw Error . insufficientSpaceForGroupDescriptorBlocks
692- }
693693
694694 var totalBlocks : UInt32 = 0
695695 var totalInodes : UInt32 = 0
@@ -700,20 +700,33 @@ extension EXT4 {
700700 if newSize < minGroups * blocksPerGroup * blockSize {
701701 newSize = UInt64 ( minGroups * blocksPerGroup * blockSize)
702702 }
703- let totalGroups = ( ( ( newSize / UInt64( self . blockSize) ) - 1 ) / UInt64( self . blocksPerGroup) ) + 1
704703
705- // If the provided disk size is not aligned to a blockgroup boundary, it needs to
706- // be expanded to the next blockgroup boundary.
707- // Example:
708- // Provided disk size: 2 GB + 100MB: 2148 MB
709- // BlockSize: 4096
710- // Blockgroup size: 32768 blocks: 128MB
711- // Number of blocks: 549888
712- // Number of blockgroups = 549888 / 32768 = 16.78125
713- // Aligned disk size = 557056 blocks = 17 blockgroups: 2176 MB
714- if newSize < totalGroups * blocksPerGroup * blockSize {
715- newSize = UInt64 ( totalGroups * blocksPerGroup * blockSize)
704+ // Preserve the requested filesystem size exactly when possible.
705+ // Any trailing partial group is kept as-is; we do not round up to a full
706+ // block-group boundary just to place that group's metadata.
707+ //
708+ // For groups beyond blockGroupSize.blockGroups, metadata is packed into a
709+ // reserved region starting at dataBlocks:
710+ // - inode table: inodeTableSizePerGroup blocks
711+ // - block bitmap: 1 block
712+ // - inode bitmap: 1 block
713+ //
714+ // This keeps descriptor pointers in-bounds even when the last group is tiny
715+ // (for example, 128 MiB + 4 KiB), while still preserving exact-size images
716+ // for larger partial tails (for example, 160 MiB).
717+
718+ let fsBlocks : UInt64 = ( newSize + UInt64( self . blockSize) - 1 ) / UInt64( self . blockSize) // round up to block boundary
719+ let totalGroups = ( ( fsBlocks - 1 ) / UInt64( self . blocksPerGroup) ) + 1 // round up to group boundary
720+ let groupDescriptorBlockCount : UInt32 = ( UInt32 ( totalGroups) - 1 ) / self . groupsPerDescriptorBlock + 1 // round up to descriptor block boundary
721+ guard groupDescriptorBlockCount <= self . groupDescriptorBlocks else {
722+ throw Error . insufficientSpaceForGroupDescriptorBlocks
716723 }
724+ let extraGroupCount = UInt64 ( UInt32 ( totalGroups) - blockGroupSize. blockGroups) // count of groups beyond blockGroupSize.blockGroups that require packed metadata layout
725+ let packedMetadataStart = UInt64 ( dataBlocks) // start block (inclusive) of packed metadata region for extra groups
726+ let packedMetadataBlocks = extraGroupCount * UInt64( inodeTableSizePerGroup + 2 ) // each extra group has inodeTableSizePerGroup blocks for the inode table, plus 1 block for the block bitmap and 1 block for the inode bitmap
727+ let packedMetadataEnd = UInt32 ( packedMetadataStart + packedMetadataBlocks) // end block (exclusive) of packed metadata region for extra groups
728+ let reservedDataBlocks = max ( dataBlocks, packedMetadataEnd) // exclusive upper bound of reserved blocks (data/metadata), used for bitmap marking
729+
717730 // Snapshot groupDescriptorBlocks before self.size potentially changes: the bitmap
718731 // loop uses this to identify which GDT slots were physically reserved at init time,
719732 // so it can mark any unused slots as free without accidentally freeing content blocks
@@ -739,13 +752,13 @@ extension EXT4 {
739752 var blocks : UInt32 = 0
740753 // blocks bitmap
741754 var bitmap : [ UInt8 ] = . init( repeating: 0 , count: self . blockSize * 2 ) // 1 for blocks, 1 for inodes
742- if ( group + 1 ) * UInt32( self . blocksPerGroup) <= dataBlocks { // fully allocated group
755+ if ( group + 1 ) * UInt32( self . blocksPerGroup) <= reservedDataBlocks { // fully allocated group
743756 for i in 0 ..< ( self . blockSize) {
744757 bitmap [ Int ( i) ] = 0xff // mark as allocated
745758 }
746759 blocks = UInt32 ( self . blocksPerGroup)
747- } else if group * UInt32( self . blocksPerGroup) < dataBlocks { // partially allocated group
748- for i in 0 ..< dataBlocks - group * UInt32( self . blocksPerGroup) {
760+ } else if group * UInt32( self . blocksPerGroup) < reservedDataBlocks { // partially allocated group
761+ for i in 0 ..< reservedDataBlocks - group * UInt32( self . blocksPerGroup) {
749762 bitmap [ Int ( i / 8 ) ] |= 1 << ( i % 8 )
750763 blocks += 1
751764 }
@@ -766,17 +779,13 @@ extension EXT4 {
766779 }
767780 }
768781
769- // last blockGroup if not aligned with total size should be marked as allocated
770- let remainingBlocks = diskBlocks % self . blocksPerGroup
771- if group == totalGroups - 1 && remainingBlocks != 0 && self . size / self . blockSize < self . blocksPerGroup {
772- for i in remainingBlocks..< self . blocksPerGroup {
782+ // The true last group may be smaller than blocksPerGroup; blocks beyond
783+ // groupBlockCount don't physically exist and must be marked used.
784+ let groupBlockCount = group == totalGroups. lo - 1 ? blocksInLastGroup : self . blocksPerGroup
785+ if groupBlockCount < self . blocksPerGroup {
786+ for i in groupBlockCount..< self . blocksPerGroup {
773787 bitmap [ Int ( i / 8 ) ] |= 1 << ( i % 8 )
774788 }
775- if remainingBlocks < self . size / self . blockSize {
776- for i in remainingBlocks..< self . size / self . blockSize {
777- bitmap [ Int ( i / 8 ) ] &= ~ ( 1 << ( i % 8 ) )
778- }
779- }
780789 }
781790
782791 // mark deleted blocks as free
@@ -819,7 +828,7 @@ extension EXT4 {
819828 let blockBitmap = UInt64 ( bitmapOffset + 2 * group)
820829 let inodeBitmap = UInt64 ( bitmapOffset + 2 * group + 1 )
821830 let inodeTable = inodeTableOffset + UInt64( group * inodeTableSizePerGroup)
822- let freeBlocksCount = UInt32 ( self . blocksPerGroup - blocks)
831+ let freeBlocksCount = UInt32 ( groupBlockCount - blocks)
823832 let freeInodesCount = UInt32 ( blockGroupSize. inodesPerGroup - inodes)
824833 groupDescriptors. append (
825834 // low bits
@@ -843,21 +852,29 @@ extension EXT4 {
843852
844853 // Since the bitmaps for unoccupied block groups are the same, there is no need
845854 // to allocate separate memory or storage for each individual bitmap.
846- var blockBitmap : [ UInt8 ] = . init( repeating: 0 , count: Int ( self . blocksPerGroup) / 8 )
855+ let blockBitmap : [ UInt8 ] = . init( repeating: 0 , count: Int ( self . blocksPerGroup) / 8 )
847856 var inodeBitmap : [ UInt8 ] = . init( repeating: 0xff , count: Int ( self . blocksPerGroup) / 8 )
848- for i in 0 ..< inodeTableSizePerGroup + 2 {
849- blockBitmap [ Int ( i) / 8 ] |= 1 << ( i % 8 )
850- }
851857 for i in 0 ..< UInt16 ( blockGroupSize. inodesPerGroup) {
852858 inodeBitmap [ Int ( i) / 8 ] &= ~ ( 1 << ( i % 8 ) )
853859 }
860+ var packedMetadataCursor = packedMetadataStart
854861 for group in blockGroupSize. blockGroups..< totalGroups. lo {
855- let blocksInGroup = UInt32 ( self . blocksPerGroup)
856- let blockBitmapOffset = UInt64 ( group * self . blocksPerGroup + inodeTableSizePerGroup)
857- let inodeBitmapOffset = UInt64 ( group * self . blocksPerGroup + inodeTableSizePerGroup + 1 )
858- let inodeTableOffset = UInt64 ( self . blocksPerGroup) * group
859- let freeBlocksCount = UInt32 ( blocksInGroup - inodeTableSizePerGroup - 2 )
862+ let groupStart = UInt64 ( group) * UInt64( self . blocksPerGroup)
863+ let blocksInGroup = UInt32 ( min ( UInt64 ( self . blocksPerGroup) , fsBlocks - groupStart) )
864+ let inodeTableOffset = packedMetadataCursor
865+ let blockBitmapOffset = inodeTableOffset + UInt64( inodeTableSizePerGroup)
866+ let inodeBitmapOffset = blockBitmapOffset + 1
867+ packedMetadataCursor += UInt64 ( inodeTableSizePerGroup + 2 )
868+ let freeBlocksCount = blocksInGroup
860869 let freeInodesCount = UInt32 ( blockGroupSize. inodesPerGroup)
870+ let zeroInodeTable = [ UInt8] ( repeating: 0 , count: Int ( inodeTableSizePerGroup) * Int( self . blockSize) )
871+
872+ var groupBlockBitmap = blockBitmap
873+ if blocksInGroup < self . blocksPerGroup {
874+ for i in blocksInGroup..< self . blocksPerGroup {
875+ groupBlockBitmap [ Int ( i / 8 ) ] |= 1 << ( i % 8 )
876+ }
877+ }
861878 groupDescriptors. append (
862879 // low bits
863880 GroupDescriptor (
@@ -874,9 +891,11 @@ extension EXT4 {
874891 itableUnusedLow: 0x0000 ,
875892 checksum: 0x0000
876893 ) )
877- totalBlocks += ( inodeTableSizePerGroup + 2 )
878- try self . seek ( block: group * self . blocksPerGroup + inodeTableSizePerGroup)
879- try self . handle. write ( contentsOf: blockBitmap)
894+ try self . seek ( block: UInt32 ( inodeTableOffset) )
895+ try self . handle. write ( contentsOf: zeroInodeTable)
896+
897+ try self . seek ( block: UInt32 ( blockBitmapOffset) )
898+ try self . handle. write ( contentsOf: groupBlockBitmap)
880899 try self . handle. write ( contentsOf: inodeBitmap)
881900 }
882901
@@ -890,10 +909,9 @@ extension EXT4 {
890909 // write superblock
891910 try self . seek ( block: 0 )
892911 try self . handle. write ( contentsOf: Array< UInt8> . init( repeating: 0 , count: 1024 ) )
893-
894912 let computedInodes = totalGroups * blockGroupSize. inodesPerGroup
895- var blocksCount = totalGroups * self . blocksPerGroup
896- while blocksCount < totalBlocks {
913+ var blocksCount = ( newSize + UInt64 ( self . blockSize ) - 1 ) / UInt64 ( self . blockSize )
914+ if blocksCount < totalBlocks {
897915 blocksCount = UInt64 ( totalBlocks)
898916 }
899917 let totalFreeBlocks : UInt64
0 commit comments