1use super::*;
2
3impl ImageBuilder {
4 pub fn new(cfg: Config) -> Result<Self> {
11 Self::new_with_options(cfg, FilesystemOptions::default())
12 }
13
14 pub fn new_with_options(cfg: Config, options: FilesystemOptions) -> Result<Self> {
21 let options = options.validate(cfg)?;
22 if cfg.block_size < 64 || cfg.block_count < 2 {
23 return Err(Error::InvalidConfig);
24 }
25 Ok(Self {
26 cfg,
27 options,
28 entries: BTreeMap::new(),
29 visible_entries: BTreeMap::new(),
30 update_commits: Vec::new(),
31 allocator: FreshAllocator::new(cfg),
32 })
33 }
34
35 pub(crate) fn empty_root_block_with_options(
36 cfg: Config,
37 options: FilesystemOptions,
38 ) -> Result<Vec<u8>> {
39 let builder = Self::new_with_options(cfg, options)?;
40 let root = RootCommit::from_builder(&builder)?;
41 let mut block = vec![0xff; cfg.block_size];
42 root.write_into(&mut block, cfg, builder.options.prog_size)?;
43 Ok(block)
44 }
45
46 pub fn add_inline_file(&mut self, path: &str, data: &[u8]) -> Result<&mut Self> {
52 if !self.update_commits.is_empty() {
53 return Err(Error::Unsupported);
54 }
55 let parts = components(path)?;
56 let (name, parents) = split_parent(&parts)?;
57 if !parents.is_empty() {
58 return self.add_directory_inline_file(parents, name, data);
59 }
60 if name.len() > self.options.name_max as usize
61 || data.len()
62 > self
63 .options
64 .inline_threshold(self.cfg, self.options.attr_max)
65 {
66 return Err(Error::Unsupported);
67 }
68 if matches!(self.entries.get(name), Some(RootEntry::Dir(_))) {
69 return Err(Error::Unsupported);
70 }
71
72 self.entries.insert(
73 name.to_string(),
74 RootEntry::File(InlineFile {
75 storage: FileStorage::Inline(data.to_vec()),
76 attrs: BTreeMap::new(),
77 }),
78 );
79 self.visible_entries
80 .insert(name.to_string(), RootKind::File);
81 Ok(self)
82 }
83
84 pub fn add_ctz_file(&mut self, path: &str, data: &[u8]) -> Result<&mut Self> {
90 if !self.update_commits.is_empty() {
91 return Err(Error::Unsupported);
92 }
93 let parts = components(path)?;
94 let (name, parents) = split_parent(&parts)?;
95 if name.len() > self.options.name_max as usize {
96 return Err(Error::Unsupported);
97 }
98 if !parents.is_empty() {
99 self.directory(parents)?;
100 let blocks = self.allocator.alloc_ctz_blocks(data.len())?;
101 let parent = self.directory_mut(parents)?;
102 return parent.add_ctz_file(name, data, blocks).map(|_| self);
103 }
104 if matches!(self.entries.get(name), Some(RootEntry::Dir(_))) {
105 return Err(Error::Unsupported);
106 }
107 let blocks = self.allocator.alloc_ctz_blocks(data.len())?;
108 self.entries.insert(
109 name.to_string(),
110 RootEntry::File(InlineFile {
111 storage: FileStorage::Ctz(CtzFile::new(data, blocks)),
112 attrs: BTreeMap::new(),
113 }),
114 );
115 self.visible_entries
116 .insert(name.to_string(), RootKind::File);
117 Ok(self)
118 }
119
120 pub fn create_dir(&mut self, path: &str) -> Result<&mut Self> {
125 if !self.update_commits.is_empty() {
126 return Err(Error::Unsupported);
127 }
128 let parts = components(path)?;
129 let (name, parents) = split_parent(&parts)?;
130 if name.len() > self.options.name_max as usize {
131 return Err(Error::Unsupported);
132 }
133 if !parents.is_empty() {
134 self.directory(parents)?;
135 let pair = self.allocator.alloc_pair()?;
136 let parent = self.directory_mut(parents)?;
137 return parent.create_dir(name, pair).map(|_| self);
138 }
139 if self.entries.contains_key(name) {
140 return Err(Error::Unsupported);
141 }
142 let pair = self.allocator.alloc_pair()?;
143 self.entries.insert(
144 name.to_string(),
145 RootEntry::Dir(Directory::new(pair, self.cfg, self.options)),
146 );
147 self.visible_entries.insert(name.to_string(), RootKind::Dir);
148 Ok(self)
149 }
150
151 pub fn set_attr(&mut self, path: &str, attr_type: u8, data: &[u8]) -> Result<&mut Self> {
158 if !self.update_commits.is_empty() {
159 return Err(Error::Unsupported);
160 }
161 let parts = components(path)?;
162 let (name, parents) = split_parent(&parts)?;
163 if data.len() > self.options.attr_max as usize {
164 return Err(Error::Unsupported);
165 }
166 if !parents.is_empty() {
167 let parent = self.directory_mut(parents)?;
168 return parent.set_attr(name, attr_type, data).map(|_| self);
169 }
170
171 let entry = self.entries.get_mut(name).ok_or(Error::NotFound)?;
172 let RootEntry::File(file) = entry else {
173 return Err(Error::Unsupported);
174 };
175 file.attrs.insert(attr_type, data.to_vec());
176 Ok(self)
177 }
178
179 pub fn update_inline_file(&mut self, path: &str, data: &[u8]) -> Result<&mut Self> {
185 let parts = components(path)?;
186 let (name, parents) = split_parent(&parts)?;
187 if !parents.is_empty() {
188 let parent = self.directory_mut(parents)?;
189 return parent.update_inline_file(name, data).map(|_| self);
190 }
191 if data.len()
192 > self
193 .options
194 .inline_threshold(self.cfg, self.options.attr_max)
195 {
196 return Err(Error::Unsupported);
197 }
198 let id = root_entry_id(&self.visible_entries, name)?;
199 match self.visible_entries.get(name) {
200 Some(RootKind::File) => {}
201 Some(RootKind::Dir) => return Err(Error::Unsupported),
202 None => return Err(Error::NotFound),
203 }
204
205 self.push_root_storage_update(id, FileStorage::Inline(data.to_vec()));
206 Ok(self)
207 }
208
209 pub fn update_file(&mut self, path: &str, data: &[u8]) -> Result<&mut Self> {
216 if data.len()
217 <= self
218 .options
219 .inline_threshold(self.cfg, self.options.attr_max)
220 {
221 return self.update_inline_file(path, data);
222 }
223
224 let parts = components(path)?;
225 let (name, parents) = split_parent(&parts)?;
226 if !parents.is_empty() {
227 let parent = self.directory(parents)?;
228 child_file_id(&parent.visible_entries, name)?;
229 let blocks = self.allocator.alloc_ctz_blocks(data.len())?;
230 let parent = self.directory_mut(parents)?;
231 return parent
232 .update_storage(name, FileStorage::Ctz(CtzFile::new(data, blocks)))
233 .map(|_| self);
234 }
235
236 let id = root_entry_id(&self.visible_entries, name)?;
237 match self.visible_entries.get(name) {
238 Some(RootKind::File) => {}
239 Some(RootKind::Dir) => return Err(Error::Unsupported),
240 None => return Err(Error::NotFound),
241 }
242
243 let blocks = self.allocator.alloc_ctz_blocks(data.len())?;
244 self.push_root_storage_update(id, FileStorage::Ctz(CtzFile::new(data, blocks)));
245 Ok(self)
246 }
247
248 fn push_root_storage_update(&mut self, id: u16, storage: FileStorage) {
249 self.update_commits.push(RootUpdateCommit {
250 id,
251 storage: Some(storage),
252 attrs: BTreeMap::new(),
253 delete_file: false,
254 });
255 }
256
257 pub fn update_attr(&mut self, path: &str, attr_type: u8, data: &[u8]) -> Result<&mut Self> {
262 let parts = components(path)?;
263 let (name, parents) = split_parent(&parts)?;
264 if data.len() > self.options.attr_max as usize {
265 return Err(Error::Unsupported);
266 }
267 if !parents.is_empty() {
268 let parent = self.directory_mut(parents)?;
269 return parent.update_attr(name, attr_type, data).map(|_| self);
270 }
271 let id = root_entry_id(&self.visible_entries, name)?;
272 match self.visible_entries.get(name) {
273 Some(RootKind::File) => {}
274 Some(RootKind::Dir) => return Err(Error::Unsupported),
275 None => return Err(Error::NotFound),
276 }
277
278 let mut attrs = BTreeMap::new();
279 attrs.insert(attr_type, Some(data.to_vec()));
280 self.update_commits.push(RootUpdateCommit {
281 id,
282 storage: None,
283 attrs,
284 delete_file: false,
285 });
286 Ok(self)
287 }
288
289 pub fn delete_attr(&mut self, path: &str, attr_type: u8) -> Result<&mut Self> {
295 let parts = components(path)?;
296 let (name, parents) = split_parent(&parts)?;
297 if !parents.is_empty() {
298 let parent = self.directory_mut(parents)?;
299 return parent.delete_attr(name, attr_type).map(|_| self);
300 }
301 let id = root_entry_id(&self.visible_entries, name)?;
302 match self.visible_entries.get(name) {
303 Some(RootKind::File) => {}
304 Some(RootKind::Dir) => return Err(Error::Unsupported),
305 None => return Err(Error::NotFound),
306 }
307
308 let mut attrs = BTreeMap::new();
309 attrs.insert(attr_type, None);
310 self.update_commits.push(RootUpdateCommit {
311 id,
312 storage: None,
313 attrs,
314 delete_file: false,
315 });
316 Ok(self)
317 }
318
319 pub fn delete_file(&mut self, path: &str) -> Result<&mut Self> {
326 let parts = components(path)?;
327 let (name, parents) = split_parent(&parts)?;
328 if !parents.is_empty() {
329 let parent = self.directory_mut(parents)?;
330 return parent.delete_file(name).map(|_| self);
331 }
332 let id = root_entry_id(&self.visible_entries, name)?;
333 if self.visible_entries.remove(name).is_none() {
334 return Err(Error::NotFound);
335 }
336
337 self.update_commits.push(RootUpdateCommit {
338 id,
339 storage: None,
340 attrs: BTreeMap::new(),
341 delete_file: true,
342 });
343 Ok(self)
344 }
345
346 pub fn delete_dir(&mut self, path: &str) -> Result<&mut Self> {
354 let parts = components(path)?;
355 let (name, parents) = split_parent(&parts)?;
356 if !parents.is_empty() {
357 let parent = self.directory_mut(parents)?;
358 return parent.delete_dir(name).map(|_| self);
359 }
360
361 let id = root_entry_id(&self.visible_entries, name)?;
362 match self.visible_entries.get(name) {
363 Some(RootKind::Dir) => {}
364 Some(RootKind::File) => return Err(Error::Unsupported),
365 None => return Err(Error::NotFound),
366 }
367 let RootEntry::Dir(dir) = self.entries.get(name).ok_or(Error::Corrupt)? else {
368 return Err(Error::Unsupported);
369 };
370 if !dir.is_empty_for_delete() {
371 return Err(Error::Unsupported);
372 }
373
374 self.visible_entries.remove(name);
375 self.update_commits.push(RootUpdateCommit {
376 id,
377 storage: None,
378 attrs: BTreeMap::new(),
379 delete_file: true,
380 });
381 Ok(self)
382 }
383
384 pub fn build(&self) -> Result<Vec<u8>> {
391 let image_len = self
392 .cfg
393 .block_size
394 .checked_mul(self.cfg.block_count)
395 .ok_or(Error::InvalidConfig)?;
396 let mut image = vec![0xff; image_len];
397 for entry in self.entries.values() {
398 match entry {
399 RootEntry::Dir(dir) => dir.write_empty_pair(&mut image, self.cfg)?,
400 RootEntry::File(file) => {
401 file.storage.write_blocks(&mut image, self.cfg)?;
402 }
403 }
404 }
405 for update in &self.update_commits {
406 update.write_blocks(&mut image, self.cfg)?;
407 }
408 if let Err(err) = self.write_root_log(&mut image) {
409 if err != Error::NoSpace {
410 return Err(err);
411 }
412
413 let compacted = self.compacted_root_entries()?;
419 let root = RootCommit::from_entries(self.cfg, self.options, &compacted)?;
420 root.write_into_rev(
421 &mut image[self.cfg.block_size..2 * self.cfg.block_size],
422 self.cfg,
423 self.options.prog_size,
424 2,
425 )?;
426 }
427 Ok(image)
428 }
429
430 fn write_root_log(&self, image: &mut [u8]) -> Result<()> {
431 let root = RootCommit::from_builder(self)?;
432 let mut state = root.write_into(
433 &mut image[0..self.cfg.block_size],
434 self.cfg,
435 self.options.prog_size,
436 )?;
437 for update in &self.update_commits {
438 state = update.write_into(
439 &mut image[0..self.cfg.block_size],
440 self.cfg,
441 self.options.prog_size,
442 state,
443 )?;
444 }
445 Ok(())
446 }
447
448 fn compacted_root_entries(&self) -> Result<BTreeMap<String, RootEntry>> {
449 let mut entries = self.entries.clone();
450 for update in &self.update_commits {
451 let key = root_key_for_id(&entries, update.id)?.to_string();
452 if update.delete_file {
453 entries.remove(&key);
454 continue;
455 }
456
457 let entry = entries.get_mut(&key).ok_or(Error::Corrupt)?;
458 let RootEntry::File(file) = entry else {
459 return Err(Error::Unsupported);
460 };
461 if let Some(storage) = &update.storage {
462 file.storage = storage.clone();
463 }
464 for (attr_type, attr) in &update.attrs {
465 match attr {
466 Some(data) => {
467 file.attrs.insert(*attr_type, data.clone());
468 }
469 None => {
470 file.attrs.remove(attr_type);
471 }
472 }
473 }
474 }
475 Ok(entries)
476 }
477}
478
479impl ImageBuilder {
480 fn add_directory_inline_file(
481 &mut self,
482 parents: &[&str],
483 name: &str,
484 data: &[u8],
485 ) -> Result<&mut Self> {
486 let parent = self.directory_mut(parents)?;
487 parent.add_inline_file(name, data)?;
488 Ok(self)
489 }
490
491 fn directory(&self, path: &[&str]) -> Result<&Directory> {
492 let (name, rest) = path.split_first().ok_or(Error::Unsupported)?;
493 let entry = self.entries.get(*name).ok_or(Error::NotFound)?;
494 let RootEntry::Dir(dir) = entry else {
495 return Err(Error::Unsupported);
496 };
497 if rest.is_empty() {
498 Ok(dir)
499 } else {
500 dir.directory(rest)
501 }
502 }
503
504 fn directory_mut(&mut self, path: &[&str]) -> Result<&mut Directory> {
505 let (name, rest) = path.split_first().ok_or(Error::Unsupported)?;
506 let entry = self.entries.get_mut(*name).ok_or(Error::NotFound)?;
507 let RootEntry::Dir(dir) = entry else {
508 return Err(Error::Unsupported);
509 };
510 if rest.is_empty() {
511 Ok(dir)
512 } else {
513 dir.directory_mut(rest)
514 }
515 }
516}