1use super::super::{Read, Seek, SeekFrom, Write};
11
12use hadris_iso::read::PathSeparator;
13use hadris_udf::descriptor::{
14 ExtentDescriptor, LongAllocationDescriptor, ShortAllocationDescriptor,
15};
16use hadris_udf::write::{UdfWriteOptions, UdfWriter};
17use hadris_udf::{FileType, SECTOR_SIZE as UDF_SECTOR_SIZE};
18
19use crate::error::CdResult;
20use crate::layout::{LayoutInfo, LayoutManager};
21use crate::options::CdOptions;
22use crate::tree::{Directory, FileData, FileTree};
23
24pub struct CdWriter<W: Read + Write + Seek> {
26 writer: W,
27 options: CdOptions,
28}
29
30io_transform! {
31
32impl<W: Read + Write + Seek> CdWriter<W> {
33 pub fn new(writer: W, options: CdOptions) -> Self {
35 Self { writer, options }
36 }
37
38 pub async fn write(mut self, mut tree: FileTree) -> CdResult<()> {
40 tree.sort();
42
43 let mut layout_manager = LayoutManager::new(self.options.sector_size);
45 let layout_info = layout_manager.layout_files(&mut tree, &self.options)?;
46
47 self.write_file_data(&tree, &layout_info).await?;
49
50 if self.options.iso.enabled {
52 self.write_iso_structures(&tree, &layout_info).await?;
53 }
54
55 if self.options.udf.enabled {
57 self.write_udf_structures(&tree, &layout_info).await?;
58 }
59
60 Ok(())
61 }
62
63 async fn write_file_data(&mut self, tree: &FileTree, _layout_info: &LayoutInfo) -> CdResult<()> {
65 self.write_directory_file_data(&tree.root).await?;
66 Ok(())
67 }
68
69 async fn write_directory_file_data(&mut self, dir: &Directory) -> CdResult<()> {
70 for file in &dir.files {
71 if file.extent.length == 0 {
72 continue; }
74
75 let offset = (file.extent.sector as u64) * self.options.sector_size as u64;
77 self.writer.seek(SeekFrom::Start(offset)).await?;
78
79 match &file.data {
81 FileData::Buffer(data) => {
82 self.writer.write_all(data).await?;
83 }
84 FileData::Path(path) => {
85 let data = std::fs::read(path)?;
86 self.writer.write_all(&data).await?;
87 }
88 }
89
90 let written = file.extent.length as usize;
92 let padded = written.div_ceil(self.options.sector_size)
93 * self.options.sector_size;
94 if padded > written {
95 let padding = vec![0u8; padded - written];
96 self.writer.write_all(&padding).await?;
97 }
98 }
99
100 for subdir in &dir.subdirs {
102 self.write_directory_file_data(subdir).await?;
103 }
104
105 Ok(())
106 }
107
108 async fn write_iso_structures(&mut self, tree: &FileTree, _layout_info: &LayoutInfo) -> CdResult<()> {
110 use hadris_iso::write::options::{CreationFeatures, FormatOptions};
111 use hadris_iso::write::{InputFiles, IsoImageWriter};
112
113 let iso_files = self.tree_to_iso_files(&tree.root);
115
116 let input_files = InputFiles {
117 path_separator: PathSeparator::ForwardSlash,
118 files: iso_files,
119 };
120
121 let features = CreationFeatures {
123 filenames: self.options.iso.level,
124 long_filenames: self.options.iso.long_filenames,
125 joliet: self.options.iso.joliet,
126 rock_ridge: self.options.iso.rock_ridge,
127 el_torito: self.options.boot.clone(),
128 hybrid_boot: self.options.hybrid_boot.clone(),
129 };
130
131 let format_options = FormatOptions {
132 volume_name: self.options.volume_id.clone(),
133 system_id: None,
134 volume_set_id: None,
135 publisher_id: None,
136 preparer_id: None,
137 application_id: None,
138 sector_size: self.options.sector_size,
139 features,
140 path_separator: PathSeparator::ForwardSlash,
141 strict_charset: false,
142 };
143
144 self.writer.seek(SeekFrom::Start(0)).await?;
146 IsoImageWriter::format_new(&mut self.writer, input_files, format_options)?;
147
148 Ok(())
149 }
150
151 fn tree_to_iso_files(&self, dir: &Directory) -> Vec<hadris_iso::write::File> {
153 let mut files = Vec::new();
154
155 for file in &dir.files {
156 let data = match &file.data {
157 FileData::Buffer(b) => b.clone(),
158 FileData::Path(p) => std::fs::read(p).unwrap_or_default(),
159 };
160 files.push(hadris_iso::write::File::File {
161 name: file.name.clone(),
162 contents: data,
163 });
164 }
165
166 for subdir in &dir.subdirs {
167 files.push(hadris_iso::write::File::Directory {
168 name: subdir.name.clone(),
169 children: self.tree_to_iso_files(subdir),
170 });
171 }
172
173 files
174 }
175
176 async fn write_udf_structures(&mut self, tree: &FileTree, layout_info: &LayoutInfo) -> CdResult<()> {
178 let udf_options = UdfWriteOptions {
179 volume_id: self.options.volume_id.clone(),
180 revision: self.options.udf.revision,
181 partition_start: layout_info.udf_partition_start,
182 partition_length: layout_info.udf_partition_length(),
183 };
184
185 let mut udf_writer = UdfWriter::new(&mut self.writer, udf_options);
186
187 udf_writer.write_vrs()?;
189
190 let vds_start = 257u32;
192 let vds_length = 6u32; let reserve_vds_start = 263u32;
196
197 let main_vds = ExtentDescriptor {
199 length: vds_length * UDF_SECTOR_SIZE as u32,
200 location: vds_start,
201 };
202 let reserve_vds = ExtentDescriptor {
203 length: vds_length * UDF_SECTOR_SIZE as u32,
204 location: reserve_vds_start,
205 };
206 udf_writer.write_avdp(main_vds, reserve_vds)?;
207
208 let fsd_block = 0u32;
210 let fsd_icb = LongAllocationDescriptor {
211 extent_length: UDF_SECTOR_SIZE as u32,
212 logical_block_num: fsd_block,
213 partition_ref_num: 0,
214 impl_use: [0; 6],
215 };
216
217 let root_icb_block = 1u32;
219 let root_icb = LongAllocationDescriptor {
220 extent_length: UDF_SECTOR_SIZE as u32,
221 logical_block_num: root_icb_block,
222 partition_ref_num: 0,
223 impl_use: [0; 6],
224 };
225
226 let lvid_location = reserve_vds_start + vds_length;
228 let integrity_extent = ExtentDescriptor {
229 length: UDF_SECTOR_SIZE as u32,
230 location: lvid_location,
231 };
232
233 udf_writer.write_pvd(vds_start, 0)?;
235 udf_writer.write_iuvd(vds_start + 1, 1)?;
236 udf_writer.write_partition_descriptor(vds_start + 2, 2)?;
237 udf_writer.write_lvd(vds_start + 3, 3, fsd_icb, integrity_extent)?;
238 udf_writer.write_usd(vds_start + 4, 4)?;
239 udf_writer.write_terminating_descriptor(vds_start + 5)?;
240
241 udf_writer.write_pvd(reserve_vds_start, 0)?;
243 udf_writer.write_iuvd(reserve_vds_start + 1, 1)?;
244 udf_writer.write_partition_descriptor(reserve_vds_start + 2, 2)?;
245 udf_writer.write_lvd(reserve_vds_start + 3, 3, fsd_icb, integrity_extent)?;
246 udf_writer.write_usd(reserve_vds_start + 4, 4)?;
247 udf_writer.write_terminating_descriptor(reserve_vds_start + 5)?;
248
249 udf_writer.write_lvid(lvid_location, true)?;
251
252 udf_writer.write_fsd(fsd_block, root_icb)?;
254
255 Self::write_udf_directory_static(&mut udf_writer, &tree.root, root_icb_block, layout_info)?;
257
258 Ok(())
259 }
260
261 fn write_udf_directory_static<WR: Write + Seek>(
263 udf_writer: &mut UdfWriter<WR>,
264 dir: &Directory,
265 icb_block: u32,
266 layout_info: &LayoutInfo,
267 ) -> CdResult<()> {
268 let mut entries: Vec<(String, LongAllocationDescriptor, bool)> = Vec::new();
270 let mut next_icb = icb_block + 2; for file in &dir.files {
274 let file_icb_block = next_icb;
275 next_icb += 1;
276
277 let file_icb = LongAllocationDescriptor {
278 extent_length: UDF_SECTOR_SIZE as u32,
279 logical_block_num: file_icb_block,
280 partition_ref_num: 0,
281 impl_use: [0; 6],
282 };
283
284 entries.push((file.name.to_string(), file_icb, false));
285 }
286
287 for subdir in &dir.subdirs {
289 let subdir_icb_block = next_icb;
290 let subdir_entries = subdir.files.len() + subdir.subdirs.len() + 1; let fid_sectors = (subdir_entries * 50).div_ceil(UDF_SECTOR_SIZE);
293 next_icb += 1 + fid_sectors as u32;
294
295 let subdir_icb = LongAllocationDescriptor {
296 extent_length: UDF_SECTOR_SIZE as u32,
297 logical_block_num: subdir_icb_block,
298 partition_ref_num: 0,
299 impl_use: [0; 6],
300 };
301
302 entries.push((subdir.name.to_string(), subdir_icb, true));
303 }
304
305 let total_entries = entries.len() + 1; let estimated_fid_size = total_entries * 50; let dir_data_sectors =
309 estimated_fid_size.div_ceil(UDF_SECTOR_SIZE) as u32;
310 let dir_data_size = (dir_data_sectors as usize) * UDF_SECTOR_SIZE;
311
312 let dir_alloc = vec![ShortAllocationDescriptor {
314 extent_length: dir_data_size as u32,
315 extent_position: icb_block + 1, }];
317 udf_writer.write_file_entry(
318 icb_block,
319 FileType::Directory,
320 dir_data_size as u64,
321 &dir_alloc,
322 dir.unique_id,
323 )?;
324
325 let parent_icb = LongAllocationDescriptor {
327 extent_length: UDF_SECTOR_SIZE as u32,
328 logical_block_num: icb_block, partition_ref_num: 0,
330 impl_use: [0; 6],
331 };
332 udf_writer.write_fids(icb_block + 1, parent_icb, &entries)?;
333
334 let mut file_icb = icb_block + 2;
336 for file in &dir.files {
337 let file_alloc = if file.extent.length > 0 {
338 let logical_block = file.extent.sector - layout_info.udf_partition_start;
340 vec![ShortAllocationDescriptor {
341 extent_length: file.extent.length as u32,
342 extent_position: logical_block,
343 }]
344 } else {
345 vec![] };
347
348 udf_writer.write_file_entry(
349 file_icb,
350 FileType::RegularFile,
351 file.extent.length,
352 &file_alloc,
353 file.unique_id,
354 )?;
355 file_icb += 1;
356 }
357
358 let mut subdir_icb = file_icb;
360 for subdir in &dir.subdirs {
361 Self::write_udf_directory_static(udf_writer, subdir, subdir_icb, layout_info)?;
362 let subdir_entries = subdir.files.len() + subdir.subdirs.len() + 1;
364 let fid_sectors = (subdir_entries * 50).div_ceil(UDF_SECTOR_SIZE);
365 subdir_icb += 1 + fid_sectors as u32 + subdir.files.len() as u32;
366 }
367
368 Ok(())
369 }
370}
371
372} #[cfg(test)]
375mod tests {
376 use super::*;
377 use crate::tree::FileEntry;
378 use std::io::Cursor;
379
380 #[test]
381 fn test_basic_writer() {
382 let mut tree = FileTree::new();
383 tree.add_file(FileEntry::from_buffer(
384 "test.txt",
385 b"Hello, World!".to_vec(),
386 ));
387
388 let buffer = vec![0u8; 1024 * 1024]; let cursor = Cursor::new(buffer);
390
391 let options = CdOptions::with_volume_id("TEST");
392 let writer = CdWriter::new(cursor, options);
393
394 let result = writer.write(tree);
397 assert!(result.is_ok());
398 }
399}