1use crate::error::{Error, Result};
26use crate::options::OpticalImageOptions;
27use crate::tree::{Directory, FileExtent, FileTree};
28
29#[derive(Debug)]
31pub struct LayoutManager {
32 sector_size: usize,
34 next_file_sector: u32,
36 next_udf_block: u32,
38 next_unique_id: u64,
40}
41
42impl LayoutManager {
43 pub fn new(sector_size: usize) -> Self {
45 Self {
46 sector_size,
47 next_file_sector: 0,
50 next_udf_block: 0,
51 next_unique_id: 16, }
53 }
54
55 pub fn layout_files(
61 &mut self,
62 tree: &mut FileTree,
63 options: &OpticalImageOptions,
64 ) -> Result<LayoutInfo> {
65 let vds_end = self.calculate_vds_end(options);
67
68 let udf_partition_start = 290;
70
71 let mut next_udf_block = 1;
74 let udf_root =
75 Self::plan_udf_directory(&tree.root, &mut next_udf_block, None, self.sector_size)?;
76 let udf_metadata_sectors = next_udf_block;
77
78 self.next_udf_block = udf_metadata_sectors;
80 self.next_file_sector = udf_partition_start + udf_metadata_sectors;
81
82 self.assign_file_extents(&mut tree.root)?;
84
85 self.assign_unique_ids(&mut tree.root);
87
88 let file_data_end = self.next_file_sector;
89
90 Ok(LayoutInfo {
91 vds_end,
92 udf_partition_start,
93 udf_metadata_sectors,
94 file_data_start: udf_partition_start + udf_metadata_sectors,
95 file_data_end,
96 total_sectors: file_data_end + 100, udf_root,
98 })
99 }
100
101 fn calculate_vds_end(&self, options: &OpticalImageOptions) -> u32 {
103 let mut sector = 16; if options.iso.enabled {
107 sector += 1;
108 }
109
110 if options.iso.joliet.is_some() {
116 sector += 1;
117 }
118
119 if options.iso.long_filenames {
121 sector += 1;
122 }
123
124 if options.boot.is_some() {
126 sector += 1;
127 }
128
129 sector += 1;
131
132 sector
133 }
134
135 fn plan_udf_directory(
136 dir: &Directory,
137 next_block: &mut u32,
138 parent_icb: Option<u32>,
139 sector_size: usize,
140 ) -> Result<UdfDirectoryLayout> {
141 let icb_block = *next_block;
142 *next_block = next_block
143 .checked_add(1)
144 .ok_or_else(|| Error::InvalidConfig("UDF metadata block overflow".into()))?;
145
146 let mut fid_bytes = 40usize; for name in dir
148 .files
149 .iter()
150 .map(|file| file.name.as_str())
151 .chain(dir.subdirs.iter().map(|child| child.name.as_str()))
152 {
153 let encoded_len = cs0_filename_len(name)?;
154 fid_bytes = fid_bytes
155 .checked_add((38 + encoded_len + 3) & !3)
156 .ok_or_else(|| Error::InvalidConfig("UDF FID size overflow".into()))?;
157 }
158 let fid_sectors = fid_bytes.div_ceil(sector_size) as u32;
159 let fid_block = *next_block;
160 *next_block = next_block
161 .checked_add(fid_sectors)
162 .ok_or_else(|| Error::InvalidConfig("UDF metadata block overflow".into()))?;
163
164 let mut file_icb_blocks = Vec::with_capacity(dir.files.len());
165 for _ in &dir.files {
166 file_icb_blocks.push(*next_block);
167 *next_block = next_block
168 .checked_add(1)
169 .ok_or_else(|| Error::InvalidConfig("UDF metadata block overflow".into()))?;
170 }
171
172 let mut subdirs = Vec::with_capacity(dir.subdirs.len());
173 for child in &dir.subdirs {
174 subdirs.push(Self::plan_udf_directory(
175 child,
176 next_block,
177 Some(icb_block),
178 sector_size,
179 )?);
180 }
181
182 Ok(UdfDirectoryLayout {
183 icb_block,
184 parent_icb_block: parent_icb.unwrap_or(icb_block),
185 fid_block,
186 fid_bytes,
187 file_icb_blocks,
188 subdirs,
189 })
190 }
191
192 fn assign_file_extents(&mut self, dir: &mut Directory) -> Result<()> {
194 for file in &mut dir.files {
196 let size = file
197 .size()
198 .map_err(|error| Error::Io(hadris_io::Error::from_source(error).erase()))?;
199
200 if size == 0 {
201 file.extent = FileExtent::new(0, 0);
203 } else {
204 file.extent = FileExtent::new(self.next_file_sector, size);
205 let sectors = file.extent.sector_count(self.sector_size);
206 self.next_file_sector += sectors;
207 }
208 }
209
210 for subdir in &mut dir.subdirs {
212 self.assign_file_extents(subdir)?;
213 }
214
215 Ok(())
216 }
217
218 fn assign_unique_ids(&mut self, dir: &mut Directory) {
220 dir.unique_id = self.next_unique_id;
221 self.next_unique_id += 1;
222
223 for file in &mut dir.files {
224 file.unique_id = self.next_unique_id;
225 self.next_unique_id += 1;
226 }
227
228 for subdir in &mut dir.subdirs {
229 self.assign_unique_ids(subdir);
230 }
231 }
232
233 pub fn allocate_udf_block(&mut self) -> u32 {
235 let block = self.next_udf_block;
236 self.next_udf_block += 1;
237 block
238 }
239
240 pub fn next_unique_id(&mut self) -> u64 {
242 let id = self.next_unique_id;
243 self.next_unique_id += 1;
244 id
245 }
246}
247
248#[derive(Debug, Clone)]
250pub struct LayoutInfo {
251 pub vds_end: u32,
253 pub udf_partition_start: u32,
255 pub udf_metadata_sectors: u32,
257 pub file_data_start: u32,
259 pub file_data_end: u32,
261 pub total_sectors: u32,
263 pub(crate) udf_root: UdfDirectoryLayout,
265}
266
267#[derive(Debug, Clone)]
269pub(crate) struct UdfDirectoryLayout {
270 pub(crate) icb_block: u32,
271 pub(crate) parent_icb_block: u32,
272 pub(crate) fid_block: u32,
273 pub(crate) fid_bytes: usize,
274 pub(crate) file_icb_blocks: Vec<u32>,
275 pub(crate) subdirs: Vec<UdfDirectoryLayout>,
276}
277
278fn cs0_filename_len(name: &str) -> Result<usize> {
279 let content_len = if name.chars().all(|ch| (ch as u32) <= 0xff) {
280 name.chars().count()
281 } else {
282 name.encode_utf16()
283 .count()
284 .checked_mul(2)
285 .ok_or_else(|| Error::InvalidConfig("UDF filename encoded length overflow".into()))?
286 };
287 let encoded_len = content_len + 1;
288 if encoded_len > u8::MAX as usize {
289 return Err(Error::InvalidPath(format!(
290 "UDF filename exceeds the 255-byte encoded limit: {name}"
291 )));
292 }
293 Ok(encoded_len)
294}
295
296impl core::fmt::Display for LayoutInfo {
297 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
298 write!(
299 f,
300 "layout: {} total sectors (files at sectors {}-{})",
301 self.total_sectors, self.file_data_start, self.file_data_end
302 )
303 }
304}
305
306impl LayoutInfo {
307 pub fn udf_partition_length(&self) -> u32 {
309 self.total_sectors - self.udf_partition_start
310 }
311}
312
313#[cfg(test)]
314mod tests {
315 use super::*;
316 use crate::tree::FileEntry;
317
318 #[test]
319 fn test_layout_empty_tree() {
320 let mut tree = FileTree::new();
321 let options = OpticalImageOptions::default();
322 let mut layout = LayoutManager::new(2048);
323
324 let info = layout.layout_files(&mut tree, &options).unwrap();
325 assert!(info.file_data_end >= info.file_data_start);
326 }
327
328 #[test]
329 fn test_layout_with_files() {
330 let mut tree = FileTree::new();
331 tree.add_file(FileEntry::from_buffer("test.txt", vec![0u8; 4096]));
332 tree.add_file(FileEntry::from_buffer("small.txt", vec![0u8; 100]));
333
334 let options = OpticalImageOptions::default();
335 let mut layout = LayoutManager::new(2048);
336
337 layout.layout_files(&mut tree, &options).unwrap();
338
339 let file1 = tree.root.files.first().unwrap();
341 assert!(file1.extent.sector > 0);
342 assert_eq!(file1.extent.length, 4096);
343
344 let file2 = tree.root.files.get(1).unwrap();
346 assert!(file2.extent.sector > file1.extent.sector);
347 }
348
349 #[test]
350 fn test_layout_zero_size_file() {
351 let mut tree = FileTree::new();
352 tree.add_file(FileEntry::from_buffer("empty.txt", vec![]));
353
354 let options = OpticalImageOptions::default();
355 let mut layout = LayoutManager::new(2048);
356
357 layout.layout_files(&mut tree, &options).unwrap();
358
359 let file = tree.root.files.first().unwrap();
360 assert_eq!(file.extent.sector, 0);
361 assert_eq!(file.extent.length, 0);
362 }
363}