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 };
142
143 self.writer.seek(SeekFrom::Start(0)).await?;
145 IsoImageWriter::format_new(&mut self.writer, input_files, format_options)?;
146
147 Ok(())
148 }
149
150 fn tree_to_iso_files(&self, dir: &Directory) -> Vec<hadris_iso::write::File> {
152 let mut files = Vec::new();
153
154 for file in &dir.files {
155 let data = match &file.data {
156 FileData::Buffer(b) => b.clone(),
157 FileData::Path(p) => std::fs::read(p).unwrap_or_default(),
158 };
159 files.push(hadris_iso::write::File::File {
160 name: file.name.clone(),
161 contents: data,
162 });
163 }
164
165 for subdir in &dir.subdirs {
166 files.push(hadris_iso::write::File::Directory {
167 name: subdir.name.clone(),
168 children: self.tree_to_iso_files(subdir),
169 });
170 }
171
172 files
173 }
174
175 async fn write_udf_structures(&mut self, tree: &FileTree, layout_info: &LayoutInfo) -> CdResult<()> {
177 let udf_options = UdfWriteOptions {
178 volume_id: self.options.volume_id.clone(),
179 revision: self.options.udf.revision,
180 partition_start: layout_info.udf_partition_start,
181 partition_length: layout_info.udf_partition_length(),
182 };
183
184 let mut udf_writer = UdfWriter::new(&mut self.writer, udf_options);
185
186 udf_writer.write_vrs()?;
188
189 let vds_start = 257u32;
191 let vds_length = 6u32; let reserve_vds_start = 263u32;
195
196 let main_vds = ExtentDescriptor {
198 length: vds_length * UDF_SECTOR_SIZE as u32,
199 location: vds_start,
200 };
201 let reserve_vds = ExtentDescriptor {
202 length: vds_length * UDF_SECTOR_SIZE as u32,
203 location: reserve_vds_start,
204 };
205 udf_writer.write_avdp(main_vds, reserve_vds)?;
206
207 let fsd_block = 0u32;
209 let fsd_icb = LongAllocationDescriptor {
210 extent_length: UDF_SECTOR_SIZE as u32,
211 logical_block_num: fsd_block,
212 partition_ref_num: 0,
213 impl_use: [0; 6],
214 };
215
216 let root_icb_block = 1u32;
218 let root_icb = LongAllocationDescriptor {
219 extent_length: UDF_SECTOR_SIZE as u32,
220 logical_block_num: root_icb_block,
221 partition_ref_num: 0,
222 impl_use: [0; 6],
223 };
224
225 let lvid_location = reserve_vds_start + vds_length;
227 let integrity_extent = ExtentDescriptor {
228 length: UDF_SECTOR_SIZE as u32,
229 location: lvid_location,
230 };
231
232 udf_writer.write_pvd(vds_start, 0)?;
234 udf_writer.write_iuvd(vds_start + 1, 1)?;
235 udf_writer.write_partition_descriptor(vds_start + 2, 2)?;
236 udf_writer.write_lvd(vds_start + 3, 3, fsd_icb, integrity_extent)?;
237 udf_writer.write_usd(vds_start + 4, 4)?;
238 udf_writer.write_terminating_descriptor(vds_start + 5)?;
239
240 udf_writer.write_pvd(reserve_vds_start, 0)?;
242 udf_writer.write_iuvd(reserve_vds_start + 1, 1)?;
243 udf_writer.write_partition_descriptor(reserve_vds_start + 2, 2)?;
244 udf_writer.write_lvd(reserve_vds_start + 3, 3, fsd_icb, integrity_extent)?;
245 udf_writer.write_usd(reserve_vds_start + 4, 4)?;
246 udf_writer.write_terminating_descriptor(reserve_vds_start + 5)?;
247
248 udf_writer.write_lvid(lvid_location, true)?;
250
251 udf_writer.write_fsd(fsd_block, root_icb)?;
253
254 Self::write_udf_directory_static(&mut udf_writer, &tree.root, root_icb_block, layout_info)?;
256
257 Ok(())
258 }
259
260 fn write_udf_directory_static<WR: Write + Seek>(
262 udf_writer: &mut UdfWriter<WR>,
263 dir: &Directory,
264 icb_block: u32,
265 layout_info: &LayoutInfo,
266 ) -> CdResult<()> {
267 let mut entries: Vec<(String, LongAllocationDescriptor, bool)> = Vec::new();
269 let mut next_icb = icb_block + 2; for file in &dir.files {
273 let file_icb_block = next_icb;
274 next_icb += 1;
275
276 let file_icb = LongAllocationDescriptor {
277 extent_length: UDF_SECTOR_SIZE as u32,
278 logical_block_num: file_icb_block,
279 partition_ref_num: 0,
280 impl_use: [0; 6],
281 };
282
283 entries.push((file.name.to_string(), file_icb, false));
284 }
285
286 for subdir in &dir.subdirs {
288 let subdir_icb_block = next_icb;
289 let subdir_entries = subdir.files.len() + subdir.subdirs.len() + 1; let fid_sectors = (subdir_entries * 50).div_ceil(UDF_SECTOR_SIZE);
292 next_icb += 1 + fid_sectors as u32;
293
294 let subdir_icb = LongAllocationDescriptor {
295 extent_length: UDF_SECTOR_SIZE as u32,
296 logical_block_num: subdir_icb_block,
297 partition_ref_num: 0,
298 impl_use: [0; 6],
299 };
300
301 entries.push((subdir.name.to_string(), subdir_icb, true));
302 }
303
304 let total_entries = entries.len() + 1; let estimated_fid_size = total_entries * 50; let dir_data_sectors =
308 estimated_fid_size.div_ceil(UDF_SECTOR_SIZE) as u32;
309 let dir_data_size = (dir_data_sectors as usize) * UDF_SECTOR_SIZE;
310
311 let dir_alloc = vec![ShortAllocationDescriptor {
313 extent_length: dir_data_size as u32,
314 extent_position: icb_block + 1, }];
316 udf_writer.write_file_entry(
317 icb_block,
318 FileType::Directory,
319 dir_data_size as u64,
320 &dir_alloc,
321 dir.unique_id,
322 )?;
323
324 let parent_icb = LongAllocationDescriptor {
326 extent_length: UDF_SECTOR_SIZE as u32,
327 logical_block_num: icb_block, partition_ref_num: 0,
329 impl_use: [0; 6],
330 };
331 udf_writer.write_fids(icb_block + 1, parent_icb, &entries)?;
332
333 let mut file_icb = icb_block + 2;
335 for file in &dir.files {
336 let file_alloc = if file.extent.length > 0 {
337 let logical_block = file.extent.sector - layout_info.udf_partition_start;
339 vec![ShortAllocationDescriptor {
340 extent_length: file.extent.length as u32,
341 extent_position: logical_block,
342 }]
343 } else {
344 vec![] };
346
347 udf_writer.write_file_entry(
348 file_icb,
349 FileType::RegularFile,
350 file.extent.length,
351 &file_alloc,
352 file.unique_id,
353 )?;
354 file_icb += 1;
355 }
356
357 let mut subdir_icb = file_icb;
359 for subdir in &dir.subdirs {
360 Self::write_udf_directory_static(udf_writer, subdir, subdir_icb, layout_info)?;
361 let subdir_entries = subdir.files.len() + subdir.subdirs.len() + 1;
363 let fid_sectors = (subdir_entries * 50).div_ceil(UDF_SECTOR_SIZE);
364 subdir_icb += 1 + fid_sectors as u32 + subdir.files.len() as u32;
365 }
366
367 Ok(())
368 }
369}
370
371} #[cfg(test)]
374mod tests {
375 use super::*;
376 use crate::tree::FileEntry;
377 use std::io::Cursor;
378
379 #[test]
380 fn test_basic_writer() {
381 let mut tree = FileTree::new();
382 tree.add_file(FileEntry::from_buffer(
383 "test.txt",
384 b"Hello, World!".to_vec(),
385 ));
386
387 let buffer = vec![0u8; 1024 * 1024]; let cursor = Cursor::new(buffer);
389
390 let options = CdOptions::with_volume_id("TEST");
391 let writer = CdWriter::new(cursor, options);
392
393 let result = writer.write(tree);
396 assert!(result.is_ok());
397 }
398}