1 // Copyright 2024 The ChromiumOS Authors 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 //! Provides structs and logic to build ext2 file system with configurations. 6 7 use std::path::PathBuf; 8 9 use anyhow::bail; 10 use anyhow::Context; 11 use anyhow::Result; 12 use base::MappedRegion; 13 use base::MemoryMapping; 14 use base::MemoryMappingArena; 15 use base::MemoryMappingBuilder; 16 use base::Protection; 17 use base::SharedMemory; 18 19 use crate::arena::Arena; 20 use crate::arena::FileMappingInfo; 21 use crate::fs::Ext2; 22 use crate::BLOCK_SIZE; 23 24 /// A struct to represent the configuration of an ext2 filesystem. 25 pub struct Builder { 26 /// The number of blocks per group. 27 pub blocks_per_group: u32, 28 /// The number of inodes per group. 29 pub inodes_per_group: u32, 30 /// The size of the memory region. 31 pub size: u32, 32 /// The roof directory to be copied to the file system. 33 pub root_dir: Option<PathBuf>, 34 } 35 36 impl Default for Builder { default() -> Self37 fn default() -> Self { 38 Self { 39 blocks_per_group: 4096, 40 inodes_per_group: 4096, 41 size: 4096 * 4096, 42 root_dir: None, 43 } 44 } 45 } 46 47 impl Builder { 48 /// Validates field values and adjusts them if needed. validate(&mut self) -> Result<()>49 fn validate(&mut self) -> Result<()> { 50 let block_group_size = BLOCK_SIZE as u32 * self.blocks_per_group; 51 if self.size < block_group_size { 52 bail!( 53 "memory size {} is too small to have a block group: block_size={}, block_per_group={}", 54 self.size, 55 BLOCK_SIZE, 56 block_group_size 57 ); 58 } 59 if self.size % block_group_size != 0 { 60 // Round down to the largest multiple of block_group_size that is smaller than self.size 61 self.size = self.size.next_multiple_of(block_group_size) - block_group_size 62 }; 63 Ok(()) 64 } 65 66 /// Allocates memory region with the given configuration. allocate_memory(mut self) -> Result<MemRegion>67 pub fn allocate_memory(mut self) -> Result<MemRegion> { 68 self.validate() 69 .context("failed to validate the ext2 config")?; 70 let mem = MemoryMappingBuilder::new(self.size as usize) 71 .build() 72 .context("failed to allocate memory for ext2")?; 73 Ok(MemRegion { cfg: self, mem }) 74 } 75 76 /// Builds memory region on the given shared memory. build_on_shm(self, shm: &SharedMemory) -> Result<MemRegion>77 pub fn build_on_shm(self, shm: &SharedMemory) -> Result<MemRegion> { 78 let mem = MemoryMappingBuilder::new(shm.size() as usize) 79 .from_shared_memory(shm) 80 .build() 81 .expect("failed to build MemoryMapping from shared memory"); 82 Ok(MemRegion { cfg: self, mem }) 83 } 84 } 85 86 /// Memory region for ext2 with its config. 87 pub struct MemRegion { 88 cfg: Builder, 89 mem: MemoryMapping, 90 } 91 92 impl MemRegion { 93 /// Constructs an ext2 metadata by traversing `src_dir`. build_mmap_info(mut self) -> Result<MemRegionWithMappingInfo>94 pub fn build_mmap_info(mut self) -> Result<MemRegionWithMappingInfo> { 95 let arena = Arena::new(BLOCK_SIZE, &mut self.mem).context("failed to allocate arena")?; 96 let mut ext2 = Ext2::new(&self.cfg, &arena).context("failed to create Ext2 struct")?; 97 if let Some(dir) = self.cfg.root_dir { 98 ext2.copy_dirtree(&arena, dir) 99 .context("failed to copy directory tree")?; 100 } 101 ext2.copy_backup_metadata(&arena) 102 .context("failed to copy metadata for backup")?; 103 let mapping_info = arena.into_mapping_info(); 104 105 self.mem 106 .msync() 107 .context("failed to msyn of ext2's memory region")?; 108 Ok(MemRegionWithMappingInfo { 109 mem: self.mem, 110 mapping_info, 111 }) 112 } 113 } 114 115 /// Memory regions where ext2 metadata were written with information of mmap operations to be done. 116 pub struct MemRegionWithMappingInfo { 117 mem: MemoryMapping, 118 pub mapping_info: Vec<FileMappingInfo>, 119 } 120 121 impl MemRegionWithMappingInfo { 122 /// Do mmap and returns the memory region where ext2 was created. do_mmap(self) -> Result<MemoryMappingArena>123 pub fn do_mmap(self) -> Result<MemoryMappingArena> { 124 let mut mmap_arena = MemoryMappingArena::from(self.mem); 125 for FileMappingInfo { 126 mem_offset, 127 file, 128 length, 129 file_offset, 130 } in self.mapping_info 131 { 132 mmap_arena 133 .add_fd_mapping( 134 mem_offset, 135 length, 136 &file, 137 file_offset as u64, /* fd_offset */ 138 Protection::read(), 139 ) 140 .context("failed mmaping an fd for ext2")?; 141 } 142 143 Ok(mmap_arena) 144 } 145 } 146