1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
// Copyright 2024 The ChromiumOS Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

//! Defines structs for metadata of block groups.

use std::collections::BTreeMap;

use anyhow::Result;
use zerocopy::AsBytes;
use zerocopy_derive::FromBytes;
use zerocopy_derive::FromZeroes;

use crate::arena::Arena;
use crate::arena::BlockId;
use crate::bitmap::BitMap;
use crate::inode::Inode;
use crate::inode::InodeNum;
use crate::superblock::SuperBlock;

/// The size of a block in bytes.
/// We only support 4K-byte blocks.
pub const BLOCK_SIZE: usize = 4096;

/// A block group descriptor.
///
/// See [the specification](https://www.nongnu.org/ext2-doc/ext2.html#block-group-descriptor-table) for the details.
#[repr(C)]
#[derive(Default, Debug, Copy, Clone, FromZeroes, FromBytes, AsBytes)]
pub(crate) struct BlockGroupDescriptor {
    /// Index of the first block of the block bitmap.
    pub block_bitmap: u32,
    /// Index of the first block of the inode bitmap.
    pub inode_bitmap: u32,
    /// Index of the first block of the inode table.
    pub inode_table: u32,
    /// Number of free blocks.
    pub free_blocks_count: u16,
    /// Number of free inodes.
    pub free_inodes_count: u16,
    /// Number of directories.
    pub used_dirs_count: u16,
    pad: u16,
    reserved: [u8; 12],
}

pub(crate) struct GroupMetaData<'a> {
    pub group_desc: &'a mut BlockGroupDescriptor,
    pub block_bitmap: BitMap<'a>,
    pub inode_bitmap: BitMap<'a>,

    pub inode_table: BTreeMap<InodeNum, &'a mut Inode>,

    pub first_free_block: u32,
    pub first_free_inode: u32,
}

impl<'a> GroupMetaData<'a> {
    // Write GroupMetaData to the first block group.
    // This data need to be copied to other block gropups' descriptor tables.
    pub fn new(arena: &'a Arena<'a>, sb: &mut SuperBlock, group_id: u16) -> Result<Self> {
        let gd_size = std::mem::size_of::<BlockGroupDescriptor>() as u32;
        let num_blocks_for_gds = (gd_size * sb.num_groups() as u32).div_ceil(BLOCK_SIZE as u32);

        let inodes_per_block = BLOCK_SIZE as u64 / sb.inode_size as u64;
        let num_blocks_for_inode_table =
            (sb.inodes_per_group as usize).div_ceil(inodes_per_block as usize);

        // Allocate a block group descriptor at Block 1.
        let group_desc = arena.allocate::<BlockGroupDescriptor>(
            BlockId::from(1),
            std::mem::size_of::<BlockGroupDescriptor>() * group_id as usize,
        )?;

        // First blocks for block_bitmap, inode_bitmap, and inode_table.
        let super_block_id = group_id as u32 * sb.blocks_per_group;
        let group_desc_id = super_block_id + 1;
        group_desc.block_bitmap = group_desc_id + num_blocks_for_gds;
        group_desc.inode_bitmap = group_desc.block_bitmap + 1;
        group_desc.inode_table = group_desc.inode_bitmap + 1;

        // First free block is the one after inode table.
        let first_free_block = group_desc.inode_table + num_blocks_for_inode_table as u32;
        // Free blocks are from `first_free_block` to `blocks_per_group`, inclusive.
        group_desc.free_blocks_count =
            (sb.blocks_per_group * (group_id as u32 + 1) - first_free_block) as u16;
        sb.free_blocks_count += group_desc.free_blocks_count as u32;

        // 10 inodes should be reserved in ext2.
        let reserved_inode = if group_id == 0 { 10 } else { 0 };
        let first_free_inode = group_id as u32 * sb.inodes_per_group + reserved_inode + 1;
        group_desc.free_inodes_count = sb.inodes_per_group as u16 - reserved_inode as u16;
        sb.free_inodes_count -= reserved_inode;

        // Initialize block bitmap block.
        let bmap = arena.allocate::<[u8; BLOCK_SIZE]>(BlockId::from(group_desc.block_bitmap), 0)?;
        let valid_bmap_bytes = (sb.blocks_per_group / 8) as usize;
        // Unused parts in the block is marked as 1.
        bmap[valid_bmap_bytes..].iter_mut().for_each(|x| *x = 0xff);
        // Interpret the region as BitMap and mask bits for blocks used for metadata.
        let mut block_bitmap = BitMap::from_slice_mut(&mut bmap[..valid_bmap_bytes]);
        block_bitmap.mark_first_elems(
            (first_free_block - group_id as u32 * sb.blocks_per_group) as usize,
            true,
        );

        let imap = arena.allocate::<[u8; BLOCK_SIZE]>(BlockId::from(group_desc.inode_bitmap), 0)?;
        let valid_imap_bytes = (sb.inodes_per_group / 8) as usize;
        // Unused parts in the block is marked as 1.
        imap[valid_imap_bytes..].iter_mut().for_each(|x| *x = 0xff);
        // Interpret the region as BitMap and mask bits for reserved inodes.
        let mut inode_bitmap =
            BitMap::from_slice_mut(&mut imap[..(sb.inodes_per_group / 8) as usize]);
        inode_bitmap.mark_first_elems(reserved_inode as usize, true);

        Ok(GroupMetaData {
            group_desc,
            block_bitmap,
            inode_bitmap,

            inode_table: BTreeMap::new(),

            first_free_block,
            first_free_inode,
        })
    }
}

#[cfg(test)]
mod test {
    use base::MemoryMappingBuilder;

    use super::*;
    use crate::Builder;

    // Check if `GroupMetaData` is correctly initialized from `SuperBlock` with one block group.
    #[test]
    fn test_group_metadata_with_one_block_group() {
        let blocks_per_group = 1024;
        let num_groups = 1;
        let size = BLOCK_SIZE as u32 * blocks_per_group * num_groups;
        let mut mem = MemoryMappingBuilder::new(size as usize).build().unwrap();
        let arena = Arena::new(BLOCK_SIZE, &mut mem).unwrap();
        let sb = SuperBlock::new(
            &arena,
            &Builder {
                inodes_per_group: 1024,
                blocks_per_group,
                size,
                root_dir: None,
            },
        )
        .unwrap();
        let group = GroupMetaData::new(&arena, sb, 0).unwrap();

        assert_eq!(sb.block_group_nr, 1);

        // First a few blocks are used for specific purposes.
        // Their indexes are arbitrary but we can assume the following values unless we use much
        // larger parameters:
        // 0: 1024-byte offset + superblock
        // 1: group descriptor(s)
        // 2: block bitmap
        // 3: inode bitmap
        // 4+ : inode table
        assert_eq!(group.group_desc.block_bitmap, 2);
        assert_eq!(group.group_desc.inode_bitmap, 3);
        assert_eq!(group.group_desc.inode_table, 4);

        assert_eq!(
            group.group_desc.free_blocks_count as u32,
            sb.free_blocks_count
        );
        assert_eq!(
            group.group_desc.free_inodes_count as u32,
            sb.free_inodes_count
        );
        assert_eq!(group.block_bitmap.len(), sb.blocks_per_group as usize);
        assert_eq!(
            group.block_bitmap.count_zeros(),
            group.group_desc.free_blocks_count as usize,
        );
        assert_eq!(
            group.inode_bitmap.count_zeros(),
            group.group_desc.free_inodes_count as usize,
        );
    }

    #[test]
    fn test_group_metadata_with_multiple_block_groups() {
        let blocks_per_group = 1024u32;
        let num_groups = 10u32;
        let mem_size = BLOCK_SIZE as u32 * blocks_per_group * num_groups;
        let mut mem = MemoryMappingBuilder::new(mem_size as usize)
            .build()
            .unwrap();
        let arena = Arena::new(BLOCK_SIZE, &mut mem).unwrap();
        let sb = SuperBlock::new(
            &arena,
            &Builder {
                inodes_per_group: 512,
                blocks_per_group,
                size: mem_size,
                root_dir: None,
            },
        )
        .unwrap();

        let groups = (0..num_groups)
            .map(|group_id| GroupMetaData::new(&arena, sb, group_id as u16).unwrap())
            .collect::<Vec<_>>();
        assert_eq!(
            groups
                .iter()
                .map(|gd| gd.group_desc.free_blocks_count as u32)
                .sum::<u32>(),
            sb.free_blocks_count
        );
        assert_eq!(
            groups
                .iter()
                .map(|gd| gd.group_desc.free_inodes_count as u32)
                .sum::<u32>(),
            sb.free_inodes_count
        );
    }
}