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
// 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.

//! Provides structs and logic to build ext2 file system with configurations.

use std::path::PathBuf;

use anyhow::bail;
use anyhow::Context;
use anyhow::Result;
use base::MappedRegion;
use base::MemoryMapping;
use base::MemoryMappingArena;
use base::MemoryMappingBuilder;
use base::Protection;
use base::SharedMemory;

use crate::arena::Arena;
use crate::arena::FileMappingInfo;
use crate::fs::Ext2;
use crate::BLOCK_SIZE;

/// A struct to represent the configuration of an ext2 filesystem.
pub struct Builder {
    /// The number of blocks per group.
    pub blocks_per_group: u32,
    /// The number of inodes per group.
    pub inodes_per_group: u32,
    /// The size of the memory region.
    pub size: u32,
    /// The roof directory to be copied to the file system.
    pub root_dir: Option<PathBuf>,
}

impl Default for Builder {
    fn default() -> Self {
        Self {
            blocks_per_group: 4096,
            inodes_per_group: 4096,
            size: 4096 * 4096,
            root_dir: None,
        }
    }
}

impl Builder {
    /// Validates field values and adjusts them if needed.
    fn validate(&mut self) -> Result<()> {
        let block_group_size = BLOCK_SIZE as u32 * self.blocks_per_group;
        if self.size < block_group_size {
            bail!(
            "memory size {} is too small to have a block group: block_size={},  block_per_group={}",
            self.size,
            BLOCK_SIZE,
            block_group_size
        );
        }
        if self.size % block_group_size != 0 {
            // Round down to the largest multiple of block_group_size that is smaller than self.size
            self.size = self.size.next_multiple_of(block_group_size) - block_group_size
        };
        Ok(())
    }

    /// Allocates memory region with the given configuration.
    pub fn allocate_memory(mut self) -> Result<MemRegion> {
        self.validate()
            .context("failed to validate the ext2 config")?;
        let mem = MemoryMappingBuilder::new(self.size as usize)
            .build()
            .context("failed to allocate memory for ext2")?;
        Ok(MemRegion { cfg: self, mem })
    }

    /// Builds memory region on the given shared memory.
    pub fn build_on_shm(self, shm: &SharedMemory) -> Result<MemRegion> {
        let mem = MemoryMappingBuilder::new(shm.size() as usize)
            .from_shared_memory(shm)
            .build()
            .expect("failed to build MemoryMapping from shared memory");
        Ok(MemRegion { cfg: self, mem })
    }
}

/// Memory region for ext2 with its config.
pub struct MemRegion {
    cfg: Builder,
    mem: MemoryMapping,
}

impl MemRegion {
    /// Constructs an ext2 metadata by traversing `src_dir`.
    pub fn build_mmap_info(mut self) -> Result<MemRegionWithMappingInfo> {
        let arena = Arena::new(BLOCK_SIZE, &mut self.mem).context("failed to allocate arena")?;
        let mut ext2 = Ext2::new(&self.cfg, &arena).context("failed to create Ext2 struct")?;
        if let Some(dir) = self.cfg.root_dir {
            ext2.copy_dirtree(&arena, dir)
                .context("failed to copy directory tree")?;
        }
        ext2.copy_backup_metadata(&arena)
            .context("failed to copy metadata for backup")?;
        let mapping_info = arena.into_mapping_info();

        self.mem
            .msync()
            .context("failed to msyn of ext2's memory region")?;
        Ok(MemRegionWithMappingInfo {
            mem: self.mem,
            mapping_info,
        })
    }
}

/// Memory regions where ext2 metadata were written with information of mmap operations to be done.
pub struct MemRegionWithMappingInfo {
    mem: MemoryMapping,
    pub mapping_info: Vec<FileMappingInfo>,
}

impl MemRegionWithMappingInfo {
    /// Do mmap and returns the memory region where ext2 was created.
    pub fn do_mmap(self) -> Result<MemoryMappingArena> {
        let mut mmap_arena = MemoryMappingArena::from(self.mem);
        for FileMappingInfo {
            mem_offset,
            file,
            length,
            file_offset,
        } in self.mapping_info
        {
            mmap_arena
                .add_fd_mapping(
                    mem_offset,
                    length,
                    &file,
                    file_offset as u64, /* fd_offset */
                    Protection::read(),
                )
                .context("failed mmaping an fd for ext2")?;
        }

        Ok(mmap_arena)
    }
}