1io_transform! {
4
5#[cfg(feature = "write")]
6use core::ops::DerefMut;
7
8#[cfg(feature = "write")]
9use crate::{
10 raw::{DirEntryAttrFlags, RawDirectoryEntry, RawFileEntry},
11 error::{Error, Result},
12 file::ShortFileName,
13};
14#[cfg(feature = "write")]
15use super::{
16 fat_table::Fat, dir::{FatDir, FileEntry}, fs::FatVolume,
17 io::{Cluster, ClusterLike, Read, ReadExt, Seek, SeekFrom, Write},
18};
19
20#[cfg(feature = "write")]
21use hadris_common::types::endian::{Endian, LittleEndian};
22
23#[cfg(feature = "write")]
25pub struct FileWriter<'a, DATA: Read + Write + Seek> {
26 fs: &'a FatVolume<DATA>,
27 first_cluster: Option<Cluster<usize>>,
29 current_cluster: Option<Cluster<usize>>,
31 offset_in_cluster: usize,
33 total_written: usize,
35 entry_parent: Cluster<usize>,
37 entry_offset: usize,
39 fixed_root: Option<(usize, usize)>,
41 pending_modified: Option<crate::time::FatDateTime>,
44 pending_accessed: Option<u16>,
47 pending_created: Option<crate::time::FatDateTime>,
49 finished: bool,
53}
54
55#[cfg(feature = "write")]
56impl<'a, DATA: Read + Write + Seek> Drop for FileWriter<'a, DATA> {
57 fn drop(&mut self) {
58 #[cfg(feature = "dirty-file-panic")]
64 if !self.finished {
65 panic!(
66 "FileWriter dropped without calling finish() — \
67 directory entry size/timestamps are NOT committed. \
68 Disable the `dirty-file-panic` feature if this is intended."
69 );
70 }
71 }
72}
73
74#[cfg(feature = "write")]
75impl<'a, DATA: Read + Write + Seek> FileWriter<'a, DATA> {
76 pub fn new(fs: &'a FatVolume<DATA>, entry: &FileEntry) -> Result<Self> {
80 if entry.is_directory() {
81 return Err(Error::NotAFile);
82 }
83
84 let first_cluster = if entry.cluster().0 >= 2 {
85 Some(entry.cluster())
86 } else {
87 None
88 };
89
90 let fixed_root = if entry.parent_clus.0 == 0 {
93 fs.fixed_root_dir_info()
94 } else {
95 None
96 };
97
98 Ok(Self {
99 fs,
100 first_cluster,
101 current_cluster: first_cluster,
102 offset_in_cluster: 0,
103 total_written: 0,
104 entry_parent: entry.parent_clus,
105 entry_offset: entry.offset_within_cluster,
106 fixed_root,
107 pending_modified: None,
108 pending_accessed: None,
109 pending_created: None,
110 finished: false,
111 })
112 }
113
114 pub async fn new_append(fs: &'a FatVolume<DATA>, entry: &FileEntry) -> Result<Self> {
120 if entry.is_directory() {
121 return Err(Error::NotAFile);
122 }
123
124 let fixed_root = if entry.parent_clus.0 == 0 {
125 fs.fixed_root_dir_info()
126 } else {
127 None
128 };
129
130 let file_size = entry.len() as usize;
131 let first_cluster = if entry.cluster().0 >= 2 {
132 Some(entry.cluster())
133 } else {
134 None
135 };
136
137 if file_size == 0 || first_cluster.is_none() {
138 return Ok(Self {
140 fs,
141 first_cluster,
142 current_cluster: first_cluster,
143 offset_in_cluster: 0,
144 total_written: 0,
145 entry_parent: entry.parent_clus,
146 entry_offset: entry.offset_within_cluster,
147 fixed_root,
148 pending_modified: None,
149 pending_accessed: None,
150 pending_created: None,
151 finished: false,
152 });
153 }
154
155 let cluster_size = {
156 let data = fs.data.lock();
157 data.cluster_size
158 };
159
160 let max_steps = fs.fat.max_cluster();
163 let last = {
164 let mut data = fs.data.lock();
165 fs.fat
166 .walk_chain(data.deref_mut(), first_cluster.unwrap().0 as u32, max_steps, |_| {})
167 .await?
168 };
169 let current = Cluster(last as usize);
170
171 let offset_in_last = file_size % cluster_size;
172
173 Ok(Self {
174 fs,
175 first_cluster,
176 current_cluster: Some(current),
177 offset_in_cluster: offset_in_last,
178 total_written: file_size,
179 entry_parent: entry.parent_clus,
180 entry_offset: entry.offset_within_cluster,
181 fixed_root,
182 pending_modified: None,
183 pending_accessed: None,
184 pending_created: None,
185 finished: false,
186 })
187 }
188
189 pub async fn write(&mut self, buf: &[u8]) -> Result<usize> {
193 if buf.is_empty() {
194 return Ok(0);
195 }
196
197 let cluster_size = self.fs.info.cluster_size;
198 let mut written = 0;
199
200 while written < buf.len() {
201 if self.current_cluster.is_none() || self.offset_in_cluster >= cluster_size {
203 let hint = self.current_cluster.map(|c| c.0 as u32 + 1).unwrap_or(2);
207 let new_cluster = self.fs.allocate_cluster_routed(hint).await?;
208
209 self.fs.decrement_free_count();
211 self.fs.update_next_free_hint(new_cluster);
212
213 if let Some(prev) = self.current_cluster {
215 self.fs.write_clus_routed(prev.0, new_cluster).await?;
216 }
217
218 if self.first_cluster.is_none() {
220 self.first_cluster = Some(Cluster(new_cluster as usize));
221 }
222
223 self.current_cluster = Some(Cluster(new_cluster as usize));
224 self.offset_in_cluster = 0;
225 }
226
227 let cluster = self.current_cluster.unwrap();
228 let bytes_left_in_cluster = cluster_size - self.offset_in_cluster;
229 let to_write = (buf.len() - written).min(bytes_left_in_cluster);
230
231 {
233 let mut data = self.fs.data.lock();
234 let seek_pos = cluster.to_bytes(self.fs.info.data_start, cluster_size)
235 + self.offset_in_cluster;
236 data.seek(SeekFrom::Start(seek_pos as u64)).await?;
237 data.write_all(&buf[written..written + to_write]).await?;
238 }
239
240 self.offset_in_cluster += to_write;
241 self.total_written += to_write;
242 written += to_write;
243 }
244
245 Ok(written)
246 }
247
248 pub fn bytes_written(&self) -> usize {
250 self.total_written
251 }
252
253 pub fn set_modified(&mut self, dt: crate::time::FatDateTime) -> &mut Self {
260 self.pending_modified = Some(dt);
261 self
262 }
263
264 pub fn set_accessed(&mut self, date: u16) -> &mut Self {
269 self.pending_accessed = Some(date);
270 self
271 }
272
273 pub fn set_created(&mut self, dt: crate::time::FatDateTime) -> &mut Self {
279 self.pending_created = Some(dt);
280 self
281 }
282
283 pub async fn finish(mut self) -> Result<()> {
293 {
294 let mut data = self.fs.data.lock();
295 let cluster_size = data.cluster_size;
296
297 let entry_pos = if self.entry_parent.0 == 0 {
299 let (root_start, _) = self.fixed_root.expect("Fixed root info required");
301 root_start + self.entry_offset
302 } else {
303 self.entry_parent
305 .to_bytes(self.fs.info.data_start, cluster_size)
306 + self.entry_offset
307 };
308
309 data.seek(SeekFrom::Start(entry_pos as u64)).await?;
311
312 let mut raw_entry = data.read_struct::<RawDirectoryEntry>().await?;
313 let file_entry = unsafe { &mut raw_entry.file };
314
315 file_entry.size =
317 hadris_common::types::number::U32::<LittleEndian>::new(self.total_written as u32);
318
319 if let Some(cluster) = self.first_cluster {
321 let (high, low) = match &self.fs.fat {
322 Fat::Fat12(_) | Fat::Fat16(_) => (0u16, cluster.0 as u16),
323 Fat::Fat32(_) => ((cluster.0 >> 16) as u16, cluster.0 as u16),
324 };
325 file_entry.first_cluster_high =
326 hadris_common::types::number::U16::<LittleEndian>::new(high);
327 file_entry.first_cluster_low =
328 hadris_common::types::number::U16::<LittleEndian>::new(low);
329 } else {
330 file_entry.first_cluster_high =
331 hadris_common::types::number::U16::<LittleEndian>::new(0);
332 file_entry.first_cluster_low =
333 hadris_common::types::number::U16::<LittleEndian>::new(0);
334 }
335
336 let modified = self.pending_modified.unwrap_or_else(|| self.fs.time_provider().now());
339 file_entry.last_write_date = modified.date.to_le_bytes();
340 file_entry.last_write_time = modified.time.to_le_bytes();
341 file_entry.last_access_date = self
342 .pending_accessed
343 .unwrap_or(modified.date)
344 .to_le_bytes();
345 if let Some(created) = self.pending_created {
346 file_entry.creation_date = created.date.to_le_bytes();
347 file_entry.creation_time = created.time.to_le_bytes();
348 file_entry.creation_time_tenth = created.time_tenth;
349 }
350
351 data.seek(SeekFrom::Start(entry_pos as u64)).await?;
353 data.write_all(bytemuck::bytes_of(&raw_entry)).await?;
354 data.flush().await?;
355 }
356
357 self.fs.write_fsinfo().await?;
361
362 self.finished = true;
365
366 Ok(())
367 }
368}
369
370#[cfg(feature = "write")]
372pub trait FatVolumeWriteExt<DATA: Read + Write + Seek> {
373 fn write_file<'a>(&'a self, entry: &FileEntry) -> Result<FileWriter<'a, DATA>>;
375
376 async fn truncate(&self, entry: &FileEntry, new_size: usize) -> Result<()>;
386
387 async fn set_times(
398 &self,
399 entry: &FileEntry,
400 modified: Option<crate::time::FatDateTime>,
401 accessed_date: Option<u16>,
402 created: Option<crate::time::FatDateTime>,
403 ) -> Result<()>;
404}
405
406#[cfg(feature = "write")]
407impl<DATA: Read + Write + Seek> FatVolumeWriteExt<DATA> for FatVolume<DATA> {
408 fn write_file<'a>(&'a self, entry: &FileEntry) -> Result<FileWriter<'a, DATA>> {
409 FileWriter::new(self, entry)
410 }
411
412 async fn truncate(&self, entry: &FileEntry, new_size: usize) -> Result<()> {
413 if !entry.is_file() {
414 return Err(Error::NotAFile);
415 }
416
417 let current_size = entry.len() as usize;
418 if new_size >= current_size {
419 return Ok(()); }
421
422 let first_cluster = entry.cluster();
423 let cluster_size = self.info.cluster_size;
424
425 let fixed_root = if entry.parent_clus.0 == 0 {
427 self.fixed_root_dir_info()
428 } else {
429 None
430 };
431
432 if new_size == 0 {
433 let freed_count = if first_cluster.0 >= 2 {
435 self.free_chain_routed(first_cluster.0 as u32).await?
436 } else {
437 0
438 };
439 self.increment_free_count(freed_count);
441 self.update_entry_size_and_cluster(entry, 0, Cluster(0), fixed_root).await?;
443 } else {
444 let clusters_needed = new_size.div_ceil(cluster_size);
446
447 let max_cluster = self.fat.max_cluster();
451 let hops = ((clusters_needed.saturating_sub(1)) as u32).min(max_cluster);
452 let current = {
453 let mut steps_remaining = hops;
454 let mut cur = first_cluster.0 as u32;
455 while steps_remaining > 0 {
456 match self.next_cluster_routed(cur as usize).await? {
457 Some(next) => cur = next,
458 None => break,
459 }
460 steps_remaining -= 1;
461 }
462 Cluster(cur as usize)
463 };
464
465 let freed_count = self.truncate_chain_routed(current.0 as u32).await?;
467 self.increment_free_count(freed_count);
469
470 self.update_entry_size_and_cluster(entry, new_size, first_cluster, fixed_root).await?;
472 }
473
474 self.write_fsinfo().await?;
476
477 Ok(())
478 }
479
480 async fn set_times(
481 &self,
482 entry: &FileEntry,
483 modified: Option<crate::time::FatDateTime>,
484 accessed_date: Option<u16>,
485 created: Option<crate::time::FatDateTime>,
486 ) -> Result<()> {
487 if modified.is_none() && accessed_date.is_none() && created.is_none() {
488 return Ok(());
489 }
490
491 let mut data = self.data.lock();
492 let cluster_size = data.cluster_size;
493
494 let entry_pos = if entry.parent_clus.0 == 0 {
495 let (root_start, _) = self
496 .fixed_root_dir_info()
497 .expect("Fixed root info required for cluster 0");
498 root_start + entry.offset_within_cluster
499 } else {
500 entry
501 .parent_clus
502 .to_bytes(self.info.data_start, cluster_size)
503 + entry.offset_within_cluster
504 };
505
506 data.seek(SeekFrom::Start(entry_pos as u64)).await?;
507 let mut raw_entry = data.read_struct::<RawDirectoryEntry>().await?;
508 let file_entry = unsafe { &mut raw_entry.file };
509
510 if let Some(m) = modified {
511 file_entry.last_write_date = m.date.to_le_bytes();
512 file_entry.last_write_time = m.time.to_le_bytes();
513 }
514 if let Some(date) = accessed_date {
515 file_entry.last_access_date = date.to_le_bytes();
516 }
517 if let Some(c) = created {
518 file_entry.creation_date = c.date.to_le_bytes();
519 file_entry.creation_time = c.time.to_le_bytes();
520 file_entry.creation_time_tenth = c.time_tenth;
521 }
522
523 data.seek(SeekFrom::Start(entry_pos as u64)).await?;
524 data.write_all(bytemuck::bytes_of(&raw_entry)).await?;
525 data.flush().await?;
526
527 Ok(())
528 }
529}
530
531#[cfg(feature = "write")]
537fn kanji_short_name_fixup(name: &mut [u8; 11]) {
538 if name[0] == 0xE5 {
539 name[0] = 0x05;
540 }
541}
542
543#[cfg(feature = "write")]
547pub(crate) const MAX_LFN_ENTRIES: usize = 20;
548
549#[cfg(feature = "write")]
563fn short_name_case_bits(name: &str) -> Option<u8> {
564 const LOWER_BASE: u8 = 0x08;
565 const LOWER_EXT: u8 = 0x10;
566
567 let (base, ext) = match name.rfind('.') {
568 Some(pos) if pos > 0 => (&name[..pos], &name[pos + 1..]),
569 _ => (name, ""),
570 };
571 if base.is_empty() || base.chars().count() > 8 || ext.chars().count() > 3 {
572 return None;
573 }
574 if name.matches('.').count() > 1 {
575 return None;
576 }
577
578 fn part_is_lower(part: &str) -> Option<bool> {
582 let mut seen_lower = false;
583 let mut seen_upper = false;
584 for c in part.chars() {
585 if !c.is_ascii() {
586 return None;
587 }
588 let upper = (c as u8).to_ascii_uppercase();
589 let representable = upper.is_ascii_uppercase()
590 || upper.is_ascii_digit()
591 || ShortFileName::ALLOWED_SYMBOLS.contains(&upper);
592 if !representable {
593 return None;
594 }
595 if c.is_ascii_lowercase() {
596 seen_lower = true;
597 } else if c.is_ascii_uppercase() {
598 seen_upper = true;
599 }
600 }
601 if seen_lower && seen_upper {
602 return None;
603 }
604 Some(seen_lower)
605 }
606
607 let mut bits = 0;
608 if part_is_lower(base)? {
609 bits |= LOWER_BASE;
610 }
611 if part_is_lower(ext)? {
612 bits |= LOWER_EXT;
613 }
614 Some(bits)
615}
616
617#[cfg(feature = "write")]
622const LFN_CLEANUP_SCAN_LIMIT: usize = 20;
623
624#[cfg(feature = "write")]
625#[derive(Clone, Copy, Debug, PartialEq, Eq)]
626struct DirectoryEntryPosition {
627 cluster: Cluster<usize>,
628 offset: usize,
629}
630
631#[cfg(feature = "write")]
632const MAX_DIRECTORY_ENTRY_RUN: usize = MAX_LFN_ENTRIES + 1;
633
634#[cfg(feature = "write")]
635#[derive(Clone, Copy, Debug)]
636struct DirectoryEntryRun {
637 positions: [DirectoryEntryPosition; MAX_DIRECTORY_ENTRY_RUN],
638 len: usize,
639}
640
641#[cfg(feature = "write")]
642impl DirectoryEntryRun {
643 fn new() -> Self {
644 Self {
645 positions: [DirectoryEntryPosition {
646 cluster: Cluster(0),
647 offset: 0,
648 }; MAX_DIRECTORY_ENTRY_RUN],
649 len: 0,
650 }
651 }
652
653 fn push(&mut self, position: DirectoryEntryPosition) {
654 debug_assert!(self.len < self.positions.len());
655 self.positions[self.len] = position;
656 self.len += 1;
657 }
658
659 fn clear(&mut self) {
660 self.len = 0;
661 }
662
663 fn get(&self, index: usize) -> DirectoryEntryPosition {
664 debug_assert!(index < self.len);
665 self.positions[index]
666 }
667
668 fn last(&self) -> DirectoryEntryPosition {
669 self.get(self.len - 1)
670 }
671}
672
673#[cfg(all(feature = "write", feature = "lfn"))]
684fn build_lfn_entries(
685 name: &str,
686 short_checksum: u8,
687 out: &mut [RawDirectoryEntry],
688) -> Option<usize> {
689 use crate::raw::RawLfnEntry;
690
691 const STAGING_CAP: usize = MAX_LFN_ENTRIES * crate::file::LongFileName::CHARS_PER_ENTRY;
697 let mut u16_buf = [0u16; STAGING_CAP];
698 let mut u16_len = 0usize;
699 for ch in name.chars() {
700 let mut tmp = [0u16; 2];
701 for &c in ch.encode_utf16(&mut tmp).iter() {
702 if u16_len >= crate::file::LFN_MAX_UTF16_UNITS {
705 return None;
706 }
707 u16_buf[u16_len] = c;
708 u16_len += 1;
709 }
710 }
711
712 let chars_per_entry = crate::file::LongFileName::CHARS_PER_ENTRY;
713 let num_lfn = u16_len.div_ceil(chars_per_entry);
714 if num_lfn == 0 || num_lfn > MAX_LFN_ENTRIES || out.len() < num_lfn {
715 return None;
716 }
717
718 let total_capacity = num_lfn * chars_per_entry;
723 if u16_len < total_capacity {
724 u16_buf[u16_len] = 0x0000;
725 for slot in &mut u16_buf[u16_len + 1..total_capacity] {
726 *slot = 0xFFFF;
727 }
728 }
729
730 for (entry_idx, out_entry) in out.iter_mut().enumerate().take(num_lfn) {
735 let seq_num = (num_lfn - entry_idx) as u8;
736 let seq_byte = if entry_idx == 0 {
737 seq_num | crate::file::LfnBuilder::LAST_ENTRY_MASK
738 } else {
739 seq_num
740 };
741
742 let chunk_start = (seq_num as usize - 1) * chars_per_entry;
743 let chunk = &u16_buf[chunk_start..chunk_start + chars_per_entry];
744
745 let mut name1 = [0u8; 10];
746 let mut name2 = [0u8; 12];
747 let mut name3 = [0u8; 4];
748 for i in 0..5 {
749 let bytes = chunk[i].to_le_bytes();
750 name1[i * 2] = bytes[0];
751 name1[i * 2 + 1] = bytes[1];
752 }
753 for i in 0..6 {
754 let bytes = chunk[5 + i].to_le_bytes();
755 name2[i * 2] = bytes[0];
756 name2[i * 2 + 1] = bytes[1];
757 }
758 for i in 0..2 {
759 let bytes = chunk[11 + i].to_le_bytes();
760 name3[i * 2] = bytes[0];
761 name3[i * 2 + 1] = bytes[1];
762 }
763
764 let lfn = RawLfnEntry {
765 sequence_number: seq_byte,
766 name1,
767 attributes: DirEntryAttrFlags::LONG_NAME.bits(),
768 ty: 0,
769 checksum: short_checksum,
770 name2,
771 first_cluster_low: [0, 0],
772 name3,
773 };
774 *out_entry = RawDirectoryEntry { lfn };
775 }
776
777 Some(num_lfn)
778}
779
780#[cfg(feature = "write")]
782impl<DATA: Read + Write + Seek> FatVolume<DATA> {
783 async fn mark_entry_span_deleted(&self, entry: &FileEntry) -> Result<()> {
784 let entry_size = core::mem::size_of::<RawDirectoryEntry>();
785 let cluster_size = self.info.cluster_size;
786 let target = DirectoryEntryPosition {
787 cluster: entry.parent_clus,
788 offset: entry.offset_within_cluster,
789 };
790 let mut pending = DirectoryEntryRun::new();
791
792 if entry.parent_dir_clus.0 == 0 {
793 let (root_start, root_size) = self
794 .fixed_root_dir_info()
795 .expect("Fixed root info required for cluster 0");
796 let max_entries = root_size / entry_size;
797 let mut data = self.data.lock();
798 for i in 0..max_entries {
799 let position = DirectoryEntryPosition {
800 cluster: Cluster(0),
801 offset: i * entry_size,
802 };
803 if position == target {
804 for index in 0..pending.len {
805 data.seek(SeekFrom::Start(
806 (root_start + pending.get(index).offset) as u64,
807 ))
808 .await?;
809 data.write_all(&[0xE5]).await?;
810 }
811 data.seek(SeekFrom::Start((root_start + position.offset) as u64))
812 .await?;
813 data.write_all(&[0xE5]).await?;
814 return Ok(());
815 }
816
817 data.seek(SeekFrom::Start((root_start + position.offset) as u64))
818 .await?;
819 let raw = data.read_struct::<RawDirectoryEntry>().await?;
820 let bytes = unsafe { raw.bytes };
821 if bytes[0] == 0x00 {
822 break;
823 }
824 if bytes[0] != 0xE5
825 && unsafe { raw.file }.attributes == DirEntryAttrFlags::LONG_NAME.bits()
826 {
827 if pending.len == LFN_CLEANUP_SCAN_LIMIT {
828 for index in 1..pending.len {
829 pending.positions[index - 1] = pending.positions[index];
830 }
831 pending.len -= 1;
832 }
833 pending.push(position);
834 } else {
835 pending.clear();
836 }
837 }
838 return Err(Error::EntryNotFound);
839 }
840
841 let mut current = entry.parent_dir_clus;
842 let mut steps = 0u32;
843 loop {
844 steps = steps.saturating_add(1);
845 if steps > self.fat.max_cluster() {
846 return Err(Error::ClusterLoop {
847 cluster: current.0 as u32,
848 });
849 }
850
851 {
852 let mut data = self.data.lock();
853 for offset in (0..cluster_size).step_by(entry_size) {
854 let position = DirectoryEntryPosition {
855 cluster: current,
856 offset,
857 };
858 let seek_pos =
859 current.to_bytes(self.info.data_start, cluster_size) + offset;
860 if position == target {
861 for index in 0..pending.len {
862 let previous = pending.get(index);
863 let previous_pos = previous
864 .cluster
865 .to_bytes(self.info.data_start, cluster_size)
866 + previous.offset;
867 data.seek(SeekFrom::Start(previous_pos as u64)).await?;
868 data.write_all(&[0xE5]).await?;
869 }
870 data.seek(SeekFrom::Start(seek_pos as u64)).await?;
871 data.write_all(&[0xE5]).await?;
872 return Ok(());
873 }
874
875 data.seek(SeekFrom::Start(seek_pos as u64)).await?;
876 let raw = data.read_struct::<RawDirectoryEntry>().await?;
877 let bytes = unsafe { raw.bytes };
878 if bytes[0] == 0x00 {
879 return Err(Error::EntryNotFound);
880 }
881 if bytes[0] != 0xE5
882 && unsafe { raw.file }.attributes == DirEntryAttrFlags::LONG_NAME.bits()
883 {
884 if pending.len == LFN_CLEANUP_SCAN_LIMIT {
885 for index in 1..pending.len {
886 pending.positions[index - 1] = pending.positions[index];
887 }
888 pending.len -= 1;
889 }
890 pending.push(position);
891 } else {
892 pending.clear();
893 }
894 }
895 }
896
897 match self.next_cluster_routed(current.0).await? {
898 Some(next) => current = Cluster(next as usize),
899 None => return Err(Error::EntryNotFound),
900 }
901 }
902 }
903
904 async fn find_free_entry_run_in_dir(
910 &self,
911 dir: &FatDir<'_, DATA>,
912 count: usize,
913 ) -> Result<DirectoryEntryRun> {
914 debug_assert!((1..=MAX_DIRECTORY_ENTRY_RUN).contains(&count));
915 if let Some((root_start, root_size)) = dir.fixed_root {
916 self.find_free_entry_run_in_fixed_root(root_start, root_size, count)
917 .await
918 } else {
919 self.find_free_entry_run_in_cluster_chain(dir.cluster, count)
920 .await
921 }
922 }
923
924 async fn find_free_entry_run_in_fixed_root(
928 &self,
929 root_start: usize,
930 root_size: usize,
931 count: usize,
932 ) -> Result<DirectoryEntryRun> {
933 let mut data = self.data.lock();
934 let entry_size = core::mem::size_of::<RawDirectoryEntry>();
935 let max_entries = root_size / entry_size;
936 let mut run = DirectoryEntryRun::new();
937 let mut end_seen = false;
938
939 for i in 0..max_entries {
940 let offset = i * entry_size;
941 let free = if end_seen {
942 true
943 } else {
944 data.seek(SeekFrom::Start((root_start + offset) as u64))
945 .await?;
946 let raw_entry = data.read_struct::<RawDirectoryEntry>().await?;
947 let first_byte = unsafe { raw_entry.bytes[0] };
948 if first_byte == 0x00 {
949 end_seen = true;
950 true
951 } else {
952 first_byte == 0xE5
953 }
954 };
955
956 if free {
957 run.push(DirectoryEntryPosition {
958 cluster: Cluster(0),
959 offset,
960 });
961 if run.len == count {
962 return Ok(run);
963 }
964 } else {
965 run.clear();
966 }
967 }
968
969 Err(Error::DirectoryFull)
970 }
971
972 async fn find_free_entry_run_in_cluster_chain(
976 &self,
977 dir_cluster: Cluster<usize>,
978 count: usize,
979 ) -> Result<DirectoryEntryRun> {
980 let cluster_size = self.info.cluster_size;
981 let entry_size = core::mem::size_of::<RawDirectoryEntry>();
982 let entries_per_cluster = cluster_size / entry_size;
983 let mut current_cluster = dir_cluster;
984 let mut run = DirectoryEntryRun::new();
985 let mut end_seen = false;
986 let chain_limit = self.fat.max_cluster();
989 let mut steps: u32 = 0;
990
991 loop {
992 steps = steps.saturating_add(1);
993 if steps > chain_limit {
994 return Err(Error::ClusterLoop {
995 cluster: current_cluster.0 as u32,
996 });
997 }
998 {
999 let mut data = self.data.lock();
1000 for i in 0..entries_per_cluster {
1001 let offset = i * entry_size;
1002 let free = if end_seen {
1003 true
1004 } else {
1005 let seek_pos =
1006 current_cluster.to_bytes(self.info.data_start, cluster_size) + offset;
1007 data.seek(SeekFrom::Start(seek_pos as u64)).await?;
1008 let raw_entry = data.read_struct::<RawDirectoryEntry>().await?;
1009 let first_byte = unsafe { raw_entry.bytes[0] };
1010 if first_byte == 0x00 {
1011 end_seen = true;
1012 true
1013 } else {
1014 first_byte == 0xE5
1015 }
1016 };
1017
1018 if free {
1019 run.push(DirectoryEntryPosition {
1020 cluster: current_cluster,
1021 offset,
1022 });
1023 if run.len == count {
1024 return Ok(run);
1025 }
1026 } else {
1027 run.clear();
1028 }
1029 }
1030 }
1031
1032 let next = self.next_cluster_routed(current_cluster.0).await?;
1034 match next {
1035 Some(cluster) => {
1036 current_cluster = Cluster(cluster as usize);
1037 }
1038 None => {
1039 let hint = current_cluster.0 as u32 + 1;
1041 let new_cluster = self.allocate_cluster_routed(hint).await?;
1042 let new_cluster_pos = Cluster(new_cluster as usize)
1043 .to_bytes(self.info.data_start, cluster_size);
1044 let zero_result = {
1045 let mut data = self.data.lock();
1046 data.seek(SeekFrom::Start(new_cluster_pos as u64)).await?;
1047 let zeros = alloc::vec![0u8; cluster_size];
1048 data.write_all(&zeros).await
1049 };
1050 if let Err(error) = zero_result {
1051 let _ = self.free_chain_routed(new_cluster).await;
1052 return Err(error.into());
1053 }
1054 if let Err(error) = self
1055 .write_clus_routed(current_cluster.0, new_cluster)
1056 .await
1057 {
1058 let _ = self.free_chain_routed(new_cluster).await;
1059 return Err(error);
1060 }
1061
1062 self.decrement_free_count();
1063 self.update_next_free_hint(new_cluster);
1064 current_cluster = Cluster(new_cluster as usize);
1065 end_seen = true;
1066 }
1067 }
1068 }
1069 }
1070
1071 async fn write_raw_entry(
1075 &self,
1076 cluster: Cluster<usize>,
1077 offset: usize,
1078 entry: &RawFileEntry,
1079 fixed_root: Option<(usize, usize)>,
1080 ) -> Result<()> {
1081 let mut data = self.data.lock();
1082 let cluster_size = data.cluster_size;
1083
1084 let seek_pos = if cluster.0 == 0 {
1086 let (root_start, _) = fixed_root.expect("Fixed root info required for cluster 0");
1088 root_start + offset
1089 } else {
1090 cluster.to_bytes(self.info.data_start, cluster_size) + offset
1092 };
1093
1094 data.seek(SeekFrom::Start(seek_pos as u64)).await?;
1095 data.write_all(bytemuck::bytes_of(entry)).await?;
1096 Ok(())
1097 }
1098
1099 #[cfg(feature = "lfn")]
1104 async fn write_raw_directory_entry(
1105 &self,
1106 cluster: Cluster<usize>,
1107 offset: usize,
1108 entry: &RawDirectoryEntry,
1109 fixed_root: Option<(usize, usize)>,
1110 ) -> Result<()> {
1111 let mut data = self.data.lock();
1112 let cluster_size = data.cluster_size;
1113 let seek_pos = if cluster.0 == 0 {
1114 let (root_start, _) = fixed_root.expect("Fixed root info required for cluster 0");
1115 root_start + offset
1116 } else {
1117 cluster.to_bytes(self.info.data_start, cluster_size) + offset
1118 };
1119 data.seek(SeekFrom::Start(seek_pos as u64)).await?;
1120 data.write_all(bytemuck::bytes_of(entry)).await?;
1125 Ok(())
1126 }
1127
1128 pub async fn create_file(&self, parent: &FatDir<'_, DATA>, name: &str) -> Result<FileEntry> {
1132 if parent.find(name).await?.is_some() {
1134 return Err(Error::AlreadyExists);
1135 }
1136
1137 let short_name = ShortFileName::from_long_name_with(name, 0, self.oem_converter())
1139 .map_err(|_| Error::InvalidFilename)?;
1140
1141 let case_bits = short_name_case_bits(name);
1145 #[cfg(feature = "lfn")]
1146 let mut lfn_buf: [RawDirectoryEntry; MAX_LFN_ENTRIES] = unsafe { core::mem::zeroed() };
1147 #[cfg(feature = "lfn")]
1148 let (lfn_count, nt_res) = match case_bits {
1149 Some(bits) => (0usize, bits),
1150 None => (
1151 build_lfn_entries(name, short_name.lfn_checksum(), &mut lfn_buf)
1152 .ok_or(Error::InvalidFilename)?,
1153 0u8,
1154 ),
1155 };
1156 #[cfg(not(feature = "lfn"))]
1157 let (lfn_count, nt_res) = (0usize, case_bits.unwrap_or(0));
1158
1159 let total_slots = lfn_count + 1;
1161 let run = self.find_free_entry_run_in_dir(parent, total_slots).await?;
1162
1163 let now = self.time_provider().now();
1165 let (date, time, time_tenth) = now.to_raw();
1166
1167 let mut raw_name = short_name.to_raw_bytes();
1168 kanji_short_name_fixup(&mut raw_name);
1169
1170 let entry = RawFileEntry {
1171 name: raw_name,
1172 attributes: DirEntryAttrFlags::ARCHIVE.bits(),
1173 reserved: nt_res,
1174 creation_time_tenth: time_tenth,
1175 creation_time: time.to_le_bytes(),
1176 creation_date: date.to_le_bytes(),
1177 last_access_date: date.to_le_bytes(),
1178 first_cluster_high: hadris_common::types::number::U16::<LittleEndian>::new(0),
1179 last_write_time: time.to_le_bytes(),
1180 last_write_date: date.to_le_bytes(),
1181 first_cluster_low: hadris_common::types::number::U16::<LittleEndian>::new(0),
1182 size: hadris_common::types::number::U32::<LittleEndian>::new(0),
1183 };
1184
1185 #[cfg(feature = "lfn")]
1186 for (i, lfn_entry) in lfn_buf.iter().enumerate().take(lfn_count) {
1187 let position = run.get(i);
1188 self.write_raw_directory_entry(
1189 position.cluster,
1190 position.offset,
1191 lfn_entry,
1192 parent.fixed_root,
1193 )
1194 .await?;
1195 }
1196 let short_position = run.last();
1198 self.write_raw_entry(
1199 short_position.cluster,
1200 short_position.offset,
1201 &entry,
1202 parent.fixed_root,
1203 )
1204 .await?;
1205
1206 self.write_fsinfo().await?;
1209
1210 Ok(FileEntry {
1211 short_name,
1212 nt_case: crate::raw::NtCaseFlags::from_bits_truncate(nt_res),
1213 #[cfg(feature = "lfn")]
1214 long_name: if lfn_count > 0 {
1215 crate::file::LongFileName::from_str_utf16(name)
1216 } else {
1217 None
1218 },
1219 attr: DirEntryAttrFlags::ARCHIVE,
1220 size: 0,
1221 parent_dir_clus: parent.cluster,
1222 parent_clus: short_position.cluster,
1223 offset_within_cluster: short_position.offset,
1224 cluster: Cluster(0),
1225 created: now,
1226 last_access_date: now.date,
1227 modified: crate::time::FatDateTime::from_raw(now.date, now.time, 0),
1228 })
1229 }
1230
1231 pub async fn create_dir<'a>(
1235 &'a self,
1236 parent: &FatDir<'a, DATA>,
1237 name: &str,
1238 ) -> Result<FatDir<'a, DATA>> {
1239 if parent.find(name).await?.is_some() {
1241 return Err(Error::AlreadyExists);
1242 }
1243
1244 let short_name = ShortFileName::from_long_name_with(name, 0, self.oem_converter())
1246 .map_err(|_| Error::InvalidFilename)?;
1247
1248 let new_cluster = self.allocate_cluster_routed(2).await?;
1250
1251 self.decrement_free_count();
1253 self.update_next_free_hint(new_cluster);
1254
1255 let case_bits = short_name_case_bits(name);
1259 #[cfg(feature = "lfn")]
1260 let mut lfn_buf: [RawDirectoryEntry; MAX_LFN_ENTRIES] = unsafe { core::mem::zeroed() };
1261 #[cfg(feature = "lfn")]
1262 let (lfn_count, nt_res) = match case_bits {
1263 Some(bits) => (0usize, bits),
1264 None => (
1265 build_lfn_entries(name, short_name.lfn_checksum(), &mut lfn_buf)
1266 .ok_or(Error::InvalidFilename)?,
1267 0u8,
1268 ),
1269 };
1270 #[cfg(not(feature = "lfn"))]
1271 let (lfn_count, nt_res) = (0usize, case_bits.unwrap_or(0));
1272
1273 let total_slots = lfn_count + 1;
1275 let run = self.find_free_entry_run_in_dir(parent, total_slots).await?;
1276
1277 let now = self.time_provider().now();
1279 let (date, time, time_tenth) = now.to_raw();
1280
1281 let (cluster_high, cluster_low) = match &self.fat {
1283 Fat::Fat12(_) | Fat::Fat16(_) => (0u16, new_cluster as u16),
1284 Fat::Fat32(_) => ((new_cluster >> 16) as u16, new_cluster as u16),
1285 };
1286
1287 let mut raw_name = short_name.to_raw_bytes();
1288 kanji_short_name_fixup(&mut raw_name);
1289
1290 let entry = RawFileEntry {
1291 name: raw_name,
1292 attributes: DirEntryAttrFlags::DIRECTORY.bits(),
1293 reserved: nt_res,
1294 creation_time_tenth: time_tenth,
1295 creation_time: time.to_le_bytes(),
1296 creation_date: date.to_le_bytes(),
1297 last_access_date: date.to_le_bytes(),
1298 first_cluster_high: hadris_common::types::number::U16::<LittleEndian>::new(
1299 cluster_high,
1300 ),
1301 last_write_time: time.to_le_bytes(),
1302 last_write_date: date.to_le_bytes(),
1303 first_cluster_low: hadris_common::types::number::U16::<LittleEndian>::new(cluster_low),
1304 size: hadris_common::types::number::U32::<LittleEndian>::new(0),
1305 };
1306
1307 #[cfg(feature = "lfn")]
1308 for (i, lfn_entry) in lfn_buf.iter().enumerate().take(lfn_count) {
1309 let position = run.get(i);
1310 self.write_raw_directory_entry(
1311 position.cluster,
1312 position.offset,
1313 lfn_entry,
1314 parent.fixed_root,
1315 )
1316 .await?;
1317 }
1318 let short_position = run.last();
1319 let (slot_cluster, slot_offset) = (short_position.cluster, short_position.offset);
1320 self.write_raw_entry(slot_cluster, slot_offset, &entry, parent.fixed_root).await?;
1321
1322 {
1324 let mut data = self.data.lock();
1325 let cluster_size = data.cluster_size;
1326 let dir_pos =
1327 Cluster(new_cluster as usize).to_bytes(self.info.data_start, cluster_size);
1328
1329 data.seek(SeekFrom::Start(dir_pos as u64)).await?;
1331 let zeros = alloc::vec![0u8; cluster_size];
1332 data.write_all(&zeros).await?;
1333
1334 let dot_entry = RawFileEntry {
1336 name: *b". ",
1337 attributes: DirEntryAttrFlags::DIRECTORY.bits(),
1338 reserved: 0,
1339 creation_time_tenth: time_tenth,
1340 creation_time: time.to_le_bytes(),
1341 creation_date: date.to_le_bytes(),
1342 last_access_date: date.to_le_bytes(),
1343 first_cluster_high: hadris_common::types::number::U16::<LittleEndian>::new(
1344 cluster_high,
1345 ),
1346 last_write_time: time.to_le_bytes(),
1347 last_write_date: date.to_le_bytes(),
1348 first_cluster_low: hadris_common::types::number::U16::<LittleEndian>::new(
1349 cluster_low,
1350 ),
1351 size: hadris_common::types::number::U32::<LittleEndian>::new(0),
1352 };
1353 data.seek(SeekFrom::Start(dir_pos as u64)).await?;
1354 data.write_all(bytemuck::bytes_of(&dot_entry)).await?;
1355
1356 let parent_cluster = parent.cluster.0 as u32;
1362 let dotdot_cluster = if self.is_fat32_root_cluster(parent_cluster) {
1363 0
1364 } else {
1365 parent_cluster
1366 };
1367 let (parent_high, parent_low) = match &self.fat {
1368 Fat::Fat12(_) | Fat::Fat16(_) => (0u16, dotdot_cluster as u16),
1369 Fat::Fat32(_) => ((dotdot_cluster >> 16) as u16, dotdot_cluster as u16),
1370 };
1371
1372 let dotdot_entry = RawFileEntry {
1373 name: *b".. ",
1374 attributes: DirEntryAttrFlags::DIRECTORY.bits(),
1375 reserved: 0,
1376 creation_time_tenth: time_tenth,
1377 creation_time: time.to_le_bytes(),
1378 creation_date: date.to_le_bytes(),
1379 last_access_date: date.to_le_bytes(),
1380 first_cluster_high: hadris_common::types::number::U16::<LittleEndian>::new(
1381 parent_high,
1382 ),
1383 last_write_time: time.to_le_bytes(),
1384 last_write_date: date.to_le_bytes(),
1385 first_cluster_low: hadris_common::types::number::U16::<LittleEndian>::new(
1386 parent_low,
1387 ),
1388 size: hadris_common::types::number::U32::<LittleEndian>::new(0),
1389 };
1390 let dotdot_pos = dir_pos + core::mem::size_of::<RawDirectoryEntry>();
1391 data.seek(SeekFrom::Start(dotdot_pos as u64)).await?;
1392 data.write_all(bytemuck::bytes_of(&dotdot_entry)).await?;
1393 }
1394
1395 self.write_fsinfo().await?;
1397
1398 Ok(FatDir {
1399 data: self,
1400 cluster: Cluster(new_cluster as usize),
1401 fixed_root: None, })
1403 }
1404
1405 pub async fn delete(&self, entry: &FileEntry) -> Result<()> {
1407 if entry.is_directory() {
1409 let dir = FatDir {
1410 data: self,
1411 cluster: entry.cluster(),
1412 fixed_root: None, };
1414
1415 let mut count = 0;
1416 let mut iter = dir.entries();
1417 while let Some(item) = iter.next_entry().await {
1418 let item = item?;
1419 let name = item.name();
1420 if name != "." && name != ".." {
1421 count += 1;
1422 }
1423 }
1424
1425 if count > 0 {
1426 return Err(Error::DirectoryNotEmpty);
1427 }
1428 }
1429
1430 if entry.cluster().0 >= 2 {
1432 let freed_count = self.free_chain_routed(entry.cluster().0 as u32).await?;
1433 self.increment_free_count(freed_count);
1435 }
1436
1437 self.mark_entry_span_deleted(entry).await?;
1443
1444 self.write_fsinfo().await?;
1446
1447 Ok(())
1448 }
1449
1450 pub async fn rename(
1460 &self,
1461 entry: &FileEntry,
1462 dest_dir: &FatDir<'_, DATA>,
1463 new_name: &str,
1464 ) -> Result<FileEntry> {
1465 if dest_dir.find(new_name).await?.is_some() {
1467 return Err(Error::AlreadyExists);
1468 }
1469
1470 let short_name = ShortFileName::from_long_name_with(new_name, 0, self.oem_converter())
1472 .map_err(|_| Error::InvalidFilename)?;
1473
1474 let case_bits = short_name_case_bits(new_name);
1478 #[cfg(feature = "lfn")]
1479 let mut lfn_buf: [RawDirectoryEntry; MAX_LFN_ENTRIES] = unsafe { core::mem::zeroed() };
1480 #[cfg(feature = "lfn")]
1481 let (lfn_count, nt_res) = match case_bits {
1482 Some(bits) => (0usize, bits),
1483 None => (
1484 build_lfn_entries(new_name, short_name.lfn_checksum(), &mut lfn_buf)
1485 .ok_or(Error::InvalidFilename)?,
1486 0u8,
1487 ),
1488 };
1489 #[cfg(not(feature = "lfn"))]
1490 let (lfn_count, nt_res) = (0usize, case_bits.unwrap_or(0));
1491
1492 let total_slots = lfn_count + 1;
1494 let run = self
1495 .find_free_entry_run_in_dir(dest_dir, total_slots)
1496 .await?;
1497 #[cfg(feature = "lfn")]
1498 for (i, lfn_entry) in lfn_buf.iter().enumerate().take(lfn_count) {
1499 let position = run.get(i);
1500 self.write_raw_directory_entry(
1501 position.cluster,
1502 position.offset,
1503 lfn_entry,
1504 dest_dir.fixed_root,
1505 )
1506 .await?;
1507 }
1508 let short_position = run.last();
1509 let slot_cluster = short_position.cluster;
1510 let slot_offset = short_position.offset;
1511
1512 let original_raw = {
1514 let mut data = self.data.lock();
1515 let cluster_size = data.cluster_size;
1516
1517 let entry_pos = if entry.parent_clus.0 == 0 {
1518 let (root_start, _) = self
1519 .fixed_root_dir_info()
1520 .expect("Fixed root info required for cluster 0");
1521 root_start + entry.offset_within_cluster
1522 } else {
1523 entry
1524 .parent_clus
1525 .to_bytes(self.info.data_start, cluster_size)
1526 + entry.offset_within_cluster
1527 };
1528
1529 data.seek(SeekFrom::Start(entry_pos as u64)).await?;
1530 data.read_struct::<RawDirectoryEntry>().await?
1531 };
1532
1533 let original_file = unsafe { &original_raw.file };
1535 let mut raw_name = short_name.to_raw_bytes();
1536 kanji_short_name_fixup(&mut raw_name);
1537
1538 let now = self.time_provider().now();
1539 let new_entry = RawFileEntry {
1540 name: raw_name,
1541 reserved: nt_res,
1543 attributes: original_file.attributes,
1544 creation_time_tenth: original_file.creation_time_tenth,
1545 creation_time: original_file.creation_time,
1546 creation_date: original_file.creation_date,
1547 last_access_date: now.date.to_le_bytes(),
1548 first_cluster_high: original_file.first_cluster_high,
1549 last_write_time: now.time.to_le_bytes(),
1550 last_write_date: now.date.to_le_bytes(),
1551 first_cluster_low: original_file.first_cluster_low,
1552 size: original_file.size,
1553 };
1554
1555 self.write_raw_entry(slot_cluster, slot_offset, &new_entry, dest_dir.fixed_root)
1557 .await?;
1558
1559 if entry.is_directory()
1561 && entry.cluster().0 >= 2
1562 && dest_dir.cluster != entry.parent_dir_clus
1563 {
1564 let mut data = self.data.lock();
1565 let cluster_size = data.cluster_size;
1566 let dir_data_start =
1567 entry.cluster().to_bytes(self.info.data_start, cluster_size);
1568 let dotdot_pos = dir_data_start + core::mem::size_of::<RawDirectoryEntry>();
1570 data.seek(SeekFrom::Start(dotdot_pos as u64)).await?;
1571 let mut dotdot = data.read_struct::<RawDirectoryEntry>().await?;
1572 let dotdot_file = unsafe { &mut dotdot.file };
1573
1574 let parent_cluster = dest_dir.cluster.0 as u32;
1577 let dotdot_cluster = if self.is_fat32_root_cluster(parent_cluster) {
1578 0
1579 } else {
1580 parent_cluster
1581 };
1582 let (parent_high, parent_low) = match &self.fat {
1583 Fat::Fat12(_) | Fat::Fat16(_) => (0u16, dotdot_cluster as u16),
1584 Fat::Fat32(_) => ((dotdot_cluster >> 16) as u16, dotdot_cluster as u16),
1585 };
1586 dotdot_file.first_cluster_high =
1587 hadris_common::types::number::U16::<LittleEndian>::new(parent_high);
1588 dotdot_file.first_cluster_low =
1589 hadris_common::types::number::U16::<LittleEndian>::new(parent_low);
1590
1591 data.seek(SeekFrom::Start(dotdot_pos as u64)).await?;
1592 data.write_all(bytemuck::bytes_of(&dotdot)).await?;
1593 }
1594
1595 self.mark_entry_span_deleted(entry).await?;
1598
1599 self.write_fsinfo().await?;
1602
1603 Ok(FileEntry {
1604 short_name,
1605 nt_case: crate::raw::NtCaseFlags::from_bits_truncate(nt_res),
1606 #[cfg(feature = "lfn")]
1607 long_name: if lfn_count > 0 {
1608 crate::file::LongFileName::from_str_utf16(new_name)
1609 } else {
1610 None
1611 },
1612 attr: DirEntryAttrFlags::from_bits_retain(original_file.attributes),
1613 size: original_file.size.get() as usize,
1614 parent_dir_clus: dest_dir.cluster,
1615 parent_clus: slot_cluster,
1616 offset_within_cluster: slot_offset,
1617 cluster: entry.cluster(),
1618 created: crate::time::FatDateTime::from_raw(
1620 u16::from_le_bytes(original_file.creation_date),
1621 u16::from_le_bytes(original_file.creation_time),
1622 original_file.creation_time_tenth,
1623 ),
1624 last_access_date: now.date,
1625 modified: crate::time::FatDateTime::from_raw(now.date, now.time, 0),
1626 })
1627 }
1628
1629 async fn update_entry_size_and_cluster(
1633 &self,
1634 entry: &FileEntry,
1635 new_size: usize,
1636 first_cluster: Cluster<usize>,
1637 fixed_root: Option<(usize, usize)>,
1638 ) -> Result<()> {
1639 use super::fat_table::Fat;
1640
1641 let mut data = self.data.lock();
1642 let cluster_size = data.cluster_size;
1643
1644 let entry_pos = if entry.parent_clus.0 == 0 {
1646 let (root_start, _) = fixed_root.expect("Fixed root info required for cluster 0");
1648 root_start + entry.offset_within_cluster
1649 } else {
1650 entry
1652 .parent_clus
1653 .to_bytes(self.info.data_start, cluster_size)
1654 + entry.offset_within_cluster
1655 };
1656
1657 data.seek(SeekFrom::Start(entry_pos as u64)).await?;
1659
1660 let mut raw_entry = data.read_struct::<RawDirectoryEntry>().await?;
1661 let file_entry = unsafe { &mut raw_entry.file };
1662
1663 file_entry.size = hadris_common::types::number::U32::<LittleEndian>::new(new_size as u32);
1665
1666 let (high, low) = if first_cluster.0 >= 2 {
1668 match &self.fat {
1669 Fat::Fat12(_) | Fat::Fat16(_) => (0u16, first_cluster.0 as u16),
1670 Fat::Fat32(_) => ((first_cluster.0 >> 16) as u16, first_cluster.0 as u16),
1671 }
1672 } else {
1673 (0u16, 0u16)
1674 };
1675 file_entry.first_cluster_high =
1676 hadris_common::types::number::U16::<LittleEndian>::new(high);
1677 file_entry.first_cluster_low = hadris_common::types::number::U16::<LittleEndian>::new(low);
1678
1679 let now = self.time_provider().now();
1681 file_entry.last_write_date = now.date.to_le_bytes();
1682 file_entry.last_write_time = now.time.to_le_bytes();
1683 file_entry.last_access_date = now.date.to_le_bytes();
1684
1685 data.seek(SeekFrom::Start(entry_pos as u64)).await?;
1687 data.write_all(bytemuck::bytes_of(&raw_entry)).await?;
1688
1689 Ok(())
1690 }
1691}
1692
1693#[cfg(feature = "write")]
1695impl<DATA: Read + Write + Seek> FatVolume<DATA> {
1696 pub async fn set_root_label(&self, name: &[u8; 11]) -> Result<()> {
1706 let (pos, raw) = self
1707 .find_root_label_entry()
1708 .await?
1709 .ok_or(Error::EntryNotFound)?;
1710 let mut updated = raw;
1711 updated.file.name = *name;
1714
1715 let mut data = self.data.lock();
1716 data.seek(SeekFrom::Start(pos as u64)).await?;
1717 data.write_all(bytemuck::bytes_of(&updated)).await?;
1718 data.flush().await?;
1719 Ok(())
1720 }
1721}
1722
1723#[cfg(feature = "write")]
1725impl<DATA: Read + Write + Seek> FatVolume<DATA> {
1726 pub async fn set_attributes(
1734 &self,
1735 entry: &FileEntry,
1736 attrs: DirEntryAttrFlags,
1737 ) -> Result<()> {
1738 let current = entry.attributes();
1740 let immutable = DirEntryAttrFlags::DIRECTORY | DirEntryAttrFlags::VOLUME_ID;
1741 let changed = (current ^ attrs) & immutable;
1742 if changed.contains(DirEntryAttrFlags::DIRECTORY) {
1743 return Err(Error::InvalidAttributeChange { bit: "DIRECTORY" });
1744 }
1745 if changed.contains(DirEntryAttrFlags::VOLUME_ID) {
1746 return Err(Error::InvalidAttributeChange { bit: "VOLUME_ID" });
1747 }
1748
1749 let mut data = self.data.lock();
1750 let cluster_size = data.cluster_size;
1751
1752 let entry_pos = if entry.parent_clus.0 == 0 {
1753 let (root_start, _) = self
1754 .fixed_root_dir_info()
1755 .expect("Fixed root info required for cluster 0");
1756 root_start + entry.offset_within_cluster
1757 } else {
1758 entry
1759 .parent_clus
1760 .to_bytes(self.info.data_start, cluster_size)
1761 + entry.offset_within_cluster
1762 };
1763
1764 data.seek(SeekFrom::Start(entry_pos as u64)).await?;
1766 let mut raw_entry = data.read_struct::<RawDirectoryEntry>().await?;
1767 let file_entry = unsafe { &mut raw_entry.file };
1768
1769 file_entry.attributes = attrs.bits();
1771
1772 data.seek(SeekFrom::Start(entry_pos as u64)).await?;
1774 data.write_all(bytemuck::bytes_of(&raw_entry)).await?;
1775
1776 Ok(())
1777 }
1778}
1779
1780#[cfg(feature = "write")]
1782impl<DATA: Read + Write + Seek> FatVolume<DATA> {
1783 pub async fn sync(&self) -> Result<()> {
1789 self.write_fsinfo().await?;
1790
1791 let mut data = self.data.lock();
1792 data.flush().await?;
1793 Ok(())
1794 }
1795
1796 async fn write_fsinfo(&self) -> Result<()> {
1801 use super::fs::FatFsExt;
1802 use crate::raw::RawFsInfo;
1803
1804 let ext = match &self.ext {
1805 FatFsExt::Fat32(ext) => ext,
1806 _ => return Ok(()), };
1808
1809 let mut data = self.data.lock();
1810
1811 data.seek_sector(ext.fs_info_sec).await?;
1813
1814 let mut fs_info = data.read_struct::<RawFsInfo>().await?;
1816
1817 fs_info.free_count =
1819 hadris_common::types::number::U32::<LittleEndian>::new(ext.free_count.get());
1820 fs_info.next_free =
1821 hadris_common::types::number::U32::<LittleEndian>::new(ext.next_free.get().0);
1822
1823 data.seek_sector(ext.fs_info_sec).await?;
1825 data.write_all(bytemuck::bytes_of(&fs_info)).await?;
1826
1827 Ok(())
1828 }
1829
1830 pub(crate) fn decrement_free_count(&self) {
1834 use super::fs::FatFsExt;
1835
1836 if let FatFsExt::Fat32(ext) = &self.ext {
1837 let count = ext.free_count.get();
1838 if count > 0 && count != 0xFFFFFFFF {
1839 ext.free_count.set(count - 1);
1840 }
1841 }
1842 }
1843
1844 pub(crate) fn increment_free_count(&self, amount: u32) {
1848 use super::fs::FatFsExt;
1849
1850 if let FatFsExt::Fat32(ext) = &self.ext {
1851 let count = ext.free_count.get();
1852 if count != 0xFFFFFFFF {
1853 ext.free_count.set(count.saturating_add(amount));
1854 }
1855 }
1856 }
1857
1858 pub(crate) fn update_next_free_hint(&self, cluster: u32) {
1862 use super::fs::FatFsExt;
1863
1864 if let FatFsExt::Fat32(ext) = &self.ext {
1865 ext.next_free.set(Cluster(cluster.saturating_add(1)));
1867 }
1868 }
1869
1870 pub fn free_cluster_count(&self) -> Option<u32> {
1874 use super::fs::FatFsExt;
1875
1876 match &self.ext {
1877 FatFsExt::Fat32(ext) => {
1878 let count = ext.free_count.get();
1879 if count != 0xFFFFFFFF {
1880 Some(count)
1881 } else {
1882 None
1883 }
1884 }
1885 _ => None,
1886 }
1887 }
1888
1889 pub fn next_free_cluster_hint(&self) -> Option<u32> {
1893 use super::fs::FatFsExt;
1894
1895 match &self.ext {
1896 FatFsExt::Fat32(ext) => {
1897 let hint = ext.next_free.get().0;
1898 if hint >= 2 && hint != 0xFFFFFFFF {
1899 Some(hint)
1900 } else {
1901 None
1902 }
1903 }
1904 _ => None,
1905 }
1906 }
1907}
1908
1909#[cfg(all(test, feature = "write", feature = "lfn"))]
1917mod lfn_write_safety_tests {
1918 use super::{build_lfn_entries, MAX_LFN_ENTRIES};
1919 use crate::raw::{DirEntryAttrFlags, RawDirectoryEntry};
1920
1921 fn fresh_out() -> [RawDirectoryEntry; MAX_LFN_ENTRIES] {
1922 unsafe { core::mem::zeroed() }
1925 }
1926
1927 #[test]
1931 fn build_lfn_entries_at_spec_cap_255_units_does_not_oob() {
1932 let name: alloc::string::String = core::iter::repeat_n('a', 255).collect();
1933 let mut out = fresh_out();
1934 let n = build_lfn_entries(&name, 0, &mut out).expect("must accept 255 chars");
1935 assert_eq!(n, 20);
1936 for entry in out.iter().take(n) {
1937 let bytes = unsafe { entry.bytes };
1940 assert_eq!(bytes.len(), 32);
1941 }
1942 }
1943
1944 #[test]
1948 fn build_lfn_entries_overlong_returns_none() {
1949 let name: alloc::string::String = core::iter::repeat_n('a', 256).collect();
1950 let mut out = fresh_out();
1951 assert!(build_lfn_entries(&name, 0, &mut out).is_none());
1952 }
1953
1954 #[test]
1958 fn build_lfn_entries_supplementary_plane_writes_both_surrogates() {
1959 let name: alloc::string::String = core::iter::repeat_n('\u{1F31F}', 100).collect();
1961 let mut out = fresh_out();
1962 let n = build_lfn_entries(&name, 0, &mut out).expect("100 emoji fits");
1963 assert_eq!(n, 16);
1965 let mut saw_high = false;
1969 let mut saw_low = false;
1970 for entry in out.iter().take(n) {
1971 let lfn = unsafe { entry.lfn };
1972 for chunk in lfn.name1.chunks_exact(2) {
1973 let unit = u16::from_le_bytes([chunk[0], chunk[1]]);
1974 if (0xD800..0xDC00).contains(&unit) {
1975 saw_high = true;
1976 }
1977 if (0xDC00..0xE000).contains(&unit) {
1978 saw_low = true;
1979 }
1980 }
1981 }
1982 assert!(saw_high && saw_low, "must encode both halves of the surrogate pair");
1983 }
1984
1985 #[test]
1989 fn build_lfn_entries_exact_13_unit_multiple_no_padding() {
1990 let name: alloc::string::String = core::iter::repeat_n('a', 13).collect();
1991 let mut out = fresh_out();
1992 let n = build_lfn_entries(&name, 0, &mut out).expect("13 chars fits");
1993 assert_eq!(n, 1);
1994
1995 let lfn = unsafe { out[0].lfn };
1996 for chunk in lfn
1998 .name1
1999 .chunks_exact(2)
2000 .chain(lfn.name2.chunks_exact(2))
2001 .chain(lfn.name3.chunks_exact(2))
2002 {
2003 let unit = u16::from_le_bytes([chunk[0], chunk[1]]);
2004 assert_eq!(unit, b'a' as u16, "exact-fill must contain only real chars");
2005 }
2006 assert_eq!(lfn.sequence_number, 0x41); assert_eq!(lfn.attributes, DirEntryAttrFlags::LONG_NAME.bits());
2009 }
2010
2011 #[test]
2015 fn build_lfn_entries_first_slot_has_last_entry_mask() {
2016 let name = "longishname.tx"; let mut out = fresh_out();
2018 let n = build_lfn_entries(name, 0xAB, &mut out).expect("ok");
2019 assert_eq!(n, 2);
2020 let first = unsafe { out[0].lfn };
2021 assert_eq!(first.sequence_number, 0x42); assert_eq!(first.checksum, 0xAB);
2023 let second = unsafe { out[1].lfn };
2024 assert_eq!(second.sequence_number, 0x01); assert_eq!(second.checksum, 0xAB);
2026 }
2027}
2028
2029}