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::{DirSlot, 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 entry_short_name: ShortFileName,
43 entry_created: crate::time::FatDateTime,
45 fixed_root: Option<(usize, usize)>,
47 pending_modified: Option<crate::time::FatDateTime>,
50 pending_accessed: Option<u16>,
53 pending_created: Option<crate::time::FatDateTime>,
55 finished: bool,
59}
60
61#[cfg(feature = "write")]
62impl<'a, DATA: Read + Write + Seek> Drop for FileWriter<'a, DATA> {
63 fn drop(&mut self) {
64 self.fs
67 .unregister_writer(self.entry_parent, self.entry_offset);
68
69 #[cfg(feature = "dirty-file-panic")]
75 if !self.finished {
76 panic!(
77 "FileWriter dropped without calling finish() — \
78 directory entry size/timestamps are NOT committed. \
79 Disable the `dirty-file-panic` feature if this is intended."
80 );
81 }
82 }
83}
84
85#[cfg(feature = "write")]
86impl<'a, DATA: Read + Write + Seek> FileWriter<'a, DATA> {
87 pub fn new(fs: &'a FatVolume<DATA>, entry: &FileEntry) -> Result<Self> {
91 if entry.is_directory() {
92 return Err(Error::NotAFile);
93 }
94
95 let first_cluster = if entry.cluster().0 >= 2 {
96 Some(entry.cluster())
97 } else {
98 None
99 };
100
101 let fixed_root = if entry.parent_clus.0 == 0 {
104 fs.fixed_root_dir_info()
105 } else {
106 None
107 };
108
109 fs.register_writer(entry.parent_clus, entry.offset_within_cluster)?;
111
112 Ok(Self {
113 fs,
114 first_cluster,
115 current_cluster: first_cluster,
116 offset_in_cluster: 0,
117 total_written: 0,
118 entry_parent: entry.parent_clus,
119 entry_offset: entry.offset_within_cluster,
120 entry_short_name: entry.short_name,
121entry_created: entry.created,
122 fixed_root,
123 pending_modified: None,
124 pending_accessed: None,
125 pending_created: None,
126 finished: false,
127 })
128 }
129
130 pub async fn new_append(fs: &'a FatVolume<DATA>, entry: &FileEntry) -> Result<Self> {
136 if entry.is_directory() {
137 return Err(Error::NotAFile);
138 }
139
140 let fixed_root = if entry.parent_clus.0 == 0 {
141 fs.fixed_root_dir_info()
142 } else {
143 None
144 };
145
146 let file_size = entry.len() as usize;
147 let first_cluster = if entry.cluster().0 >= 2 {
148 Some(entry.cluster())
149 } else {
150 None
151 };
152
153 if file_size == 0 || first_cluster.is_none() {
154 fs.register_writer(entry.parent_clus, entry.offset_within_cluster)?;
156 return Ok(Self {
157 fs,
158 first_cluster,
159 current_cluster: first_cluster,
160 offset_in_cluster: 0,
161 total_written: 0,
162 entry_parent: entry.parent_clus,
163 entry_offset: entry.offset_within_cluster,
164 entry_short_name: entry.short_name,
165entry_created: entry.created,
166 fixed_root,
167 pending_modified: None,
168 pending_accessed: None,
169 pending_created: None,
170 finished: false,
171 });
172 }
173
174 let cluster_size = {
175 let data = fs.data.lock();
176 data.cluster_size
177 };
178
179 let max_steps = fs.fat.max_cluster();
182 let last = {
183 let mut data = fs.data.lock();
184 fs.fat
185 .walk_chain(data.deref_mut(), first_cluster.unwrap().0 as u32, max_steps, |_| {})
186 .await?
187 };
188 let current = Cluster(last as usize);
189
190 let offset_in_last = match file_size % cluster_size {
196 0 => cluster_size,
197 rem => rem,
198 };
199
200 fs.register_writer(entry.parent_clus, entry.offset_within_cluster)?;
201
202 Ok(Self {
203 fs,
204 first_cluster,
205 current_cluster: Some(current),
206 offset_in_cluster: offset_in_last,
207 total_written: file_size,
208 entry_parent: entry.parent_clus,
209 entry_offset: entry.offset_within_cluster,
210 entry_short_name: entry.short_name,
211entry_created: entry.created,
212 fixed_root,
213 pending_modified: None,
214 pending_accessed: None,
215 pending_created: None,
216 finished: false,
217 })
218 }
219
220 pub async fn write(&mut self, buf: &[u8]) -> Result<usize> {
224 if buf.is_empty() {
225 return Ok(0);
226 }
227
228 let cluster_size = self.fs.info.cluster_size;
229 let mut written = 0;
230
231 while written < buf.len() {
232 if self.current_cluster.is_none() || self.offset_in_cluster >= cluster_size {
234 let existing_next = match self.current_cluster {
237 Some(cur) => self.fs.next_cluster_routed(cur.0).await?,
238 None => None,
239 };
240
241 let new_cluster = match existing_next {
242 Some(next) => next,
243 None => {
244 let hint = self.current_cluster.map(|c| c.0 as u32 + 1).unwrap_or(2);
249 let new_cluster = self.fs.allocate_cluster_routed(hint).await?;
250
251 self.fs.decrement_free_count();
253 self.fs.update_next_free_hint(new_cluster);
254
255 if let Some(prev) = self.current_cluster {
257 self.fs.write_clus_routed(prev.0, new_cluster).await?;
258 }
259
260 new_cluster
261 }
262 };
263
264 if self.first_cluster.is_none() {
266 self.first_cluster = Some(Cluster(new_cluster as usize));
267 }
268
269 self.current_cluster = Some(Cluster(new_cluster as usize));
270 self.offset_in_cluster = 0;
271 }
272
273 let cluster = self.current_cluster.unwrap();
274 let bytes_left_in_cluster = cluster_size - self.offset_in_cluster;
275 let to_write = (buf.len() - written).min(bytes_left_in_cluster);
276
277 {
279 let mut data = self.fs.data.lock();
280 let seek_pos = cluster.to_bytes(self.fs.info.data_start, cluster_size)
281 + self.offset_in_cluster;
282 data.seek(SeekFrom::Start(seek_pos as u64)).await?;
283 data.write_all(&buf[written..written + to_write]).await?;
284 }
285
286 self.offset_in_cluster += to_write;
287 self.total_written += to_write;
288 written += to_write;
289 }
290
291 Ok(written)
292 }
293
294 pub fn bytes_written(&self) -> usize {
296 self.total_written
297 }
298
299 pub fn set_modified(&mut self, dt: crate::time::FatDateTime) -> &mut Self {
306 self.pending_modified = Some(dt);
307 self
308 }
309
310 pub fn set_accessed(&mut self, date: u16) -> &mut Self {
315 self.pending_accessed = Some(date);
316 self
317 }
318
319 pub fn set_created(&mut self, dt: crate::time::FatDateTime) -> &mut Self {
325 self.pending_created = Some(dt);
326 self
327 }
328
329 pub async fn finish(mut self) -> Result<()> {
339 self.finished = true;
344
345 self.fs
356 .revalidate_slot(
357 self.entry_parent,
358 self.entry_offset,
359 &self.entry_short_name,
360 &self.entry_created,
361 )
362 .await?;
363
364 if self.total_written == 0 {
368 if let Some(first) = self.first_cluster.take() {
369 let freed_count = self.fs.free_chain_routed(first.0 as u32).await?;
370 self.fs.increment_free_count(freed_count);
371 self.current_cluster = None;
372 }
373 } else if let Some(last) = self.current_cluster {
374 let freed_count = self.fs.truncate_chain_routed(last.0 as u32).await?;
375 self.fs.increment_free_count(freed_count);
376 }
377
378 {
379 let mut data = self.fs.data.lock();
380 let cluster_size = data.cluster_size;
381
382 let entry_pos = if self.entry_parent.0 == 0 {
384 let (root_start, _) = self.fixed_root.expect("Fixed root info required");
386 root_start + self.entry_offset
387 } else {
388 self.entry_parent
390 .to_bytes(self.fs.info.data_start, cluster_size)
391 + self.entry_offset
392 };
393
394 data.seek(SeekFrom::Start(entry_pos as u64)).await?;
396
397 let mut raw_entry = data.read_struct::<RawDirectoryEntry>().await?;
398 let file_entry = unsafe { &mut raw_entry.file };
399
400 file_entry.size =
402 hadris_common::types::number::U32::<LittleEndian>::new(self.total_written as u32);
403
404 if let Some(cluster) = self.first_cluster {
406 let (high, low) = match &self.fs.fat {
407 Fat::Fat12(_) | Fat::Fat16(_) => (0u16, cluster.0 as u16),
408 Fat::Fat32(_) => ((cluster.0 >> 16) as u16, cluster.0 as u16),
409 };
410 file_entry.first_cluster_high =
411 hadris_common::types::number::U16::<LittleEndian>::new(high);
412 file_entry.first_cluster_low =
413 hadris_common::types::number::U16::<LittleEndian>::new(low);
414 } else {
415 file_entry.first_cluster_high =
416 hadris_common::types::number::U16::<LittleEndian>::new(0);
417 file_entry.first_cluster_low =
418 hadris_common::types::number::U16::<LittleEndian>::new(0);
419 }
420
421 let modified = self.pending_modified.unwrap_or_else(|| self.fs.time_provider().now());
424 file_entry.last_write_date = modified.date.to_le_bytes();
425 file_entry.last_write_time = modified.time.to_le_bytes();
426 file_entry.last_access_date = self
427 .pending_accessed
428 .unwrap_or(modified.date)
429 .to_le_bytes();
430 if let Some(created) = self.pending_created {
431 file_entry.creation_date = created.date.to_le_bytes();
432 file_entry.creation_time = created.time.to_le_bytes();
433 file_entry.creation_time_tenth = created.time_tenth;
434 }
435
436 data.seek(SeekFrom::Start(entry_pos as u64)).await?;
438 data.write_all(bytemuck::bytes_of(&raw_entry)).await?;
439 data.flush().await?;
440 }
441
442 self.fs.write_fsinfo().await?;
446
447
448 Ok(())
449 }
450}
451
452#[cfg(feature = "write")]
454pub trait FatVolumeWriteExt<DATA: Read + Write + Seek> {
455 fn write_file<'a>(&'a self, entry: &FileEntry) -> Result<FileWriter<'a, DATA>>;
457
458 async fn truncate(&self, entry: &FileEntry, new_size: usize) -> Result<()>;
468
469 async fn set_times(
485 &self,
486 entry: &FileEntry,
487 modified: Option<crate::time::FatDateTime>,
488 accessed_date: Option<u16>,
489 created: Option<crate::time::FatDateTime>,
490 ) -> Result<()>;
491}
492
493#[cfg(feature = "write")]
494impl<DATA: Read + Write + Seek> FatVolumeWriteExt<DATA> for FatVolume<DATA> {
495 fn write_file<'a>(&'a self, entry: &FileEntry) -> Result<FileWriter<'a, DATA>> {
496 FileWriter::new(self, entry)
497 }
498
499 async fn truncate(&self, entry: &FileEntry, new_size: usize) -> Result<()> {
500 if !entry.is_file() {
501 return Err(Error::NotAFile);
502 }
503 self.revalidate_entry(entry).await?;
506
507 let current_size = entry.len() as usize;
508 if new_size >= current_size {
509 return Ok(()); }
511
512 let first_cluster = entry.cluster();
513 let cluster_size = self.info.cluster_size;
514
515 let fixed_root = if entry.parent_clus.0 == 0 {
517 self.fixed_root_dir_info()
518 } else {
519 None
520 };
521
522 if new_size == 0 {
523 let freed_count = if first_cluster.0 >= 2 {
525 self.free_chain_routed(first_cluster.0 as u32).await?
526 } else {
527 0
528 };
529 self.increment_free_count(freed_count);
531 self.update_entry_size_and_cluster(entry, 0, Cluster(0), fixed_root).await?;
533 } else {
534 let clusters_needed = new_size.div_ceil(cluster_size);
536
537 let max_cluster = self.fat.max_cluster();
541 let hops = ((clusters_needed.saturating_sub(1)) as u32).min(max_cluster);
542 let current = {
543 let mut steps_remaining = hops;
544 let mut cur = first_cluster.0 as u32;
545 while steps_remaining > 0 {
546 match self.next_cluster_routed(cur as usize).await? {
547 Some(next) => cur = next,
548 None => break,
549 }
550 steps_remaining -= 1;
551 }
552 Cluster(cur as usize)
553 };
554
555 let freed_count = self.truncate_chain_routed(current.0 as u32).await?;
557 self.increment_free_count(freed_count);
559
560 self.update_entry_size_and_cluster(entry, new_size, first_cluster, fixed_root).await?;
562 }
563
564 self.write_fsinfo().await?;
566
567 Ok(())
568 }
569
570 async fn set_times(
571 &self,
572 entry: &FileEntry,
573 modified: Option<crate::time::FatDateTime>,
574 accessed_date: Option<u16>,
575 created: Option<crate::time::FatDateTime>,
576 ) -> Result<()> {
577 if modified.is_none() && accessed_date.is_none() && created.is_none() {
578 return Ok(());
579 }
580 self.revalidate_entry(entry).await?;
582
583 let mut data = self.data.lock();
584 let cluster_size = data.cluster_size;
585
586 let entry_pos = if entry.parent_clus.0 == 0 {
587 let (root_start, _) = self
588 .fixed_root_dir_info()
589 .expect("Fixed root info required for cluster 0");
590 root_start + entry.offset_within_cluster
591 } else {
592 entry
593 .parent_clus
594 .to_bytes(self.info.data_start, cluster_size)
595 + entry.offset_within_cluster
596 };
597
598 data.seek(SeekFrom::Start(entry_pos as u64)).await?;
599 let mut raw_entry = data.read_struct::<RawDirectoryEntry>().await?;
600 let file_entry = unsafe { &mut raw_entry.file };
601
602 if let Some(m) = modified {
603 file_entry.last_write_date = m.date.to_le_bytes();
604 file_entry.last_write_time = m.time.to_le_bytes();
605 }
606 if let Some(date) = accessed_date {
607 file_entry.last_access_date = date.to_le_bytes();
608 }
609 if let Some(c) = created {
610 file_entry.creation_date = c.date.to_le_bytes();
611 file_entry.creation_time = c.time.to_le_bytes();
612 file_entry.creation_time_tenth = c.time_tenth;
613 }
614
615 data.seek(SeekFrom::Start(entry_pos as u64)).await?;
616 data.write_all(bytemuck::bytes_of(&raw_entry)).await?;
617 data.flush().await?;
618
619 Ok(())
620 }
621}
622
623#[cfg(feature = "write")]
629fn kanji_short_name_fixup(name: &mut [u8; 11]) {
630 if name[0] == 0xE5 {
631 name[0] = 0x05;
632 }
633}
634
635#[cfg(feature = "write")]
639pub(crate) const MAX_LFN_ENTRIES: usize = 20;
640
641#[cfg(feature = "write")]
655fn short_name_case_bits(name: &str) -> Option<u8> {
656 const LOWER_BASE: u8 = 0x08;
657 const LOWER_EXT: u8 = 0x10;
658
659 let (base, ext) = match name.rfind('.') {
660 Some(pos) if pos > 0 => (&name[..pos], &name[pos + 1..]),
661 _ => (name, ""),
662 };
663 if base.is_empty() || base.chars().count() > 8 || ext.chars().count() > 3 {
664 return None;
665 }
666 if name.matches('.').count() > 1 {
667 return None;
668 }
669
670 fn part_is_lower(part: &str) -> Option<bool> {
674 let mut seen_lower = false;
675 let mut seen_upper = false;
676 for c in part.chars() {
677 if !c.is_ascii() {
678 return None;
679 }
680 let upper = (c as u8).to_ascii_uppercase();
681 let representable = upper.is_ascii_uppercase()
682 || upper.is_ascii_digit()
683 || ShortFileName::ALLOWED_SYMBOLS.contains(&upper);
684 if !representable {
685 return None;
686 }
687 if c.is_ascii_lowercase() {
688 seen_lower = true;
689 } else if c.is_ascii_uppercase() {
690 seen_upper = true;
691 }
692 }
693 if seen_lower && seen_upper {
694 return None;
695 }
696 Some(seen_lower)
697 }
698
699 let mut bits = 0;
700 if part_is_lower(base)? {
701 bits |= LOWER_BASE;
702 }
703 if part_is_lower(ext)? {
704 bits |= LOWER_EXT;
705 }
706 Some(bits)
707}
708
709#[cfg(feature = "write")]
714const LFN_CLEANUP_SCAN_LIMIT: usize = 20;
715
716#[cfg(feature = "write")]
717#[derive(Clone, Copy, Debug, PartialEq, Eq)]
718struct DirectoryEntryPosition {
719 cluster: Cluster<usize>,
720 offset: usize,
721}
722
723#[cfg(feature = "write")]
724const MAX_DIRECTORY_ENTRY_RUN: usize = MAX_LFN_ENTRIES + 1;
725
726#[cfg(feature = "write")]
727#[derive(Clone, Copy, Debug)]
728struct DirectoryEntryRun {
729 positions: [DirectoryEntryPosition; MAX_DIRECTORY_ENTRY_RUN],
730 len: usize,
731}
732
733#[cfg(feature = "write")]
734impl DirectoryEntryRun {
735 fn new() -> Self {
736 Self {
737 positions: [DirectoryEntryPosition {
738 cluster: Cluster(0),
739 offset: 0,
740 }; MAX_DIRECTORY_ENTRY_RUN],
741 len: 0,
742 }
743 }
744
745 fn push(&mut self, position: DirectoryEntryPosition) {
746 debug_assert!(self.len < self.positions.len());
747 self.positions[self.len] = position;
748 self.len += 1;
749 }
750
751 fn clear(&mut self) {
752 self.len = 0;
753 }
754
755 fn get(&self, index: usize) -> DirectoryEntryPosition {
756 debug_assert!(index < self.len);
757 self.positions[index]
758 }
759
760 fn last(&self) -> DirectoryEntryPosition {
761 self.get(self.len - 1)
762 }
763}
764
765#[cfg(all(feature = "write", feature = "lfn"))]
776fn build_lfn_entries(
777 name: &str,
778 short_checksum: u8,
779 out: &mut [RawDirectoryEntry],
780) -> Option<usize> {
781 use crate::raw::RawLfnEntry;
782
783 const STAGING_CAP: usize = MAX_LFN_ENTRIES * crate::file::LongFileName::CHARS_PER_ENTRY;
789 let mut u16_buf = [0u16; STAGING_CAP];
790 let mut u16_len = 0usize;
791 for ch in name.chars() {
792 let mut tmp = [0u16; 2];
793 for &c in ch.encode_utf16(&mut tmp).iter() {
794 if u16_len >= crate::file::LFN_MAX_UTF16_UNITS {
797 return None;
798 }
799 u16_buf[u16_len] = c;
800 u16_len += 1;
801 }
802 }
803
804 let chars_per_entry = crate::file::LongFileName::CHARS_PER_ENTRY;
805 let num_lfn = u16_len.div_ceil(chars_per_entry);
806 if num_lfn == 0 || num_lfn > MAX_LFN_ENTRIES || out.len() < num_lfn {
807 return None;
808 }
809
810 let total_capacity = num_lfn * chars_per_entry;
815 if u16_len < total_capacity {
816 u16_buf[u16_len] = 0x0000;
817 for slot in &mut u16_buf[u16_len + 1..total_capacity] {
818 *slot = 0xFFFF;
819 }
820 }
821
822 for (entry_idx, out_entry) in out.iter_mut().enumerate().take(num_lfn) {
827 let seq_num = (num_lfn - entry_idx) as u8;
828 let seq_byte = if entry_idx == 0 {
829 seq_num | crate::file::LfnBuilder::LAST_ENTRY_MASK
830 } else {
831 seq_num
832 };
833
834 let chunk_start = (seq_num as usize - 1) * chars_per_entry;
835 let chunk = &u16_buf[chunk_start..chunk_start + chars_per_entry];
836
837 let mut name1 = [0u8; 10];
838 let mut name2 = [0u8; 12];
839 let mut name3 = [0u8; 4];
840 for i in 0..5 {
841 let bytes = chunk[i].to_le_bytes();
842 name1[i * 2] = bytes[0];
843 name1[i * 2 + 1] = bytes[1];
844 }
845 for i in 0..6 {
846 let bytes = chunk[5 + i].to_le_bytes();
847 name2[i * 2] = bytes[0];
848 name2[i * 2 + 1] = bytes[1];
849 }
850 for i in 0..2 {
851 let bytes = chunk[11 + i].to_le_bytes();
852 name3[i * 2] = bytes[0];
853 name3[i * 2 + 1] = bytes[1];
854 }
855
856 let lfn = RawLfnEntry {
857 sequence_number: seq_byte,
858 name1,
859 attributes: DirEntryAttrFlags::LONG_NAME.bits(),
860 ty: 0,
861 checksum: short_checksum,
862 name2,
863 first_cluster_low: [0, 0],
864 name3,
865 };
866 *out_entry = RawDirectoryEntry { lfn };
867 }
868
869 Some(num_lfn)
870}
871
872#[cfg(feature = "write")]
874impl<DATA: Read + Seek> FatVolume<DATA> {
875 pub(crate) async fn revalidate_entry(&self, entry: &FileEntry) -> Result<()> {
893 self.revalidate_slot(
894 entry.parent_clus,
895 entry.offset_within_cluster,
896 &entry.short_name,
897 &entry.created,
898 )
899 .await
900 }
901
902 pub(crate) async fn revalidate_parent_dir(&self, parent: &FatDir<'_, DATA>) -> Result<()> {
906 match parent.dir_entry {
907 Some(slot) => {
908 self.revalidate_slot(
909 slot.parent_clus,
910 slot.offset_within_cluster,
911 &slot.short_name,
912 &slot.created,
913 )
914 .await
915 }
916 None => Ok(()),
917 }
918 }
919
920 pub(crate) fn register_writer(&self, parent: Cluster<usize>, offset: usize) -> Result<()> {
924 let key = (parent.0, offset);
925 let mut open = self.open_writers.lock();
926 if open.contains(&key) {
927 return Err(Error::WriterConflict);
928 }
929 open.push(key);
930 Ok(())
931 }
932
933 pub(crate) fn unregister_writer(&self, parent: Cluster<usize>, offset: usize) {
935 let key = (parent.0, offset);
936 let mut open = self.open_writers.lock();
937 if let Some(pos) = open.iter().position(|k| *k == key) {
938 open.swap_remove(pos);
939 }
940 }
941
942 pub(crate) async fn revalidate_slot(
946 &self,
947 parent_clus: Cluster<usize>,
948 offset_within_cluster: usize,
949 short_name: &ShortFileName,
950 created: &crate::time::FatDateTime,
951 ) -> Result<()> {
952 let mut data = self.data.lock();
953 let cluster_size = data.cluster_size;
954
955 let entry_pos = if parent_clus.0 == 0 {
956 let (root_start, _) = self.fixed_root_dir_info().ok_or(Error::StaleEntry)?;
957 root_start + offset_within_cluster
958 } else {
959 parent_clus.to_bytes(self.info.data_start, cluster_size) + offset_within_cluster
960 };
961
962 data.seek(SeekFrom::Start(entry_pos as u64)).await?;
963 let raw_entry = data.read_struct::<RawDirectoryEntry>().await?;
964 let file_entry = unsafe { &raw_entry.file };
965
966 let mut disk_name = file_entry.name;
970 if disk_name[0] == 0x00 || disk_name[0] == 0xE5 {
971 return Err(Error::StaleEntry);
972 }
973 if disk_name[0] == 0x05 {
974 disk_name[0] = 0xE5;
975 }
976 if disk_name != short_name.raw_bytes() {
977 return Err(Error::StaleEntry);
978 }
979
980 if file_entry.creation_time_tenth != created.time_tenth
988 || u16::from_le_bytes(file_entry.creation_time) != created.time
989 || u16::from_le_bytes(file_entry.creation_date) != created.date
990 {
991 return Err(Error::StaleEntry);
992 }
993 Ok(())
994 }
995}
996
997#[cfg(feature = "write")]
998impl<DATA: Read + Write + Seek> FatVolume<DATA> {
999 async fn mark_entry_span_deleted(&self, entry: &FileEntry) -> Result<()> {
1000 let entry_size = core::mem::size_of::<RawDirectoryEntry>();
1001 let cluster_size = self.info.cluster_size;
1002 let target = DirectoryEntryPosition {
1003 cluster: entry.parent_clus,
1004 offset: entry.offset_within_cluster,
1005 };
1006 let mut pending = DirectoryEntryRun::new();
1007
1008 if entry.parent_dir_clus.0 == 0 {
1009 let (root_start, root_size) = self
1010 .fixed_root_dir_info()
1011 .expect("Fixed root info required for cluster 0");
1012 let max_entries = root_size / entry_size;
1013 let mut data = self.data.lock();
1014 for i in 0..max_entries {
1015 let position = DirectoryEntryPosition {
1016 cluster: Cluster(0),
1017 offset: i * entry_size,
1018 };
1019 if position == target {
1020 for index in 0..pending.len {
1021 data.seek(SeekFrom::Start(
1022 (root_start + pending.get(index).offset) as u64,
1023 ))
1024 .await?;
1025 data.write_all(&[0xE5]).await?;
1026 }
1027 data.seek(SeekFrom::Start((root_start + position.offset) as u64))
1028 .await?;
1029 data.write_all(&[0xE5]).await?;
1030 return Ok(());
1031 }
1032
1033 data.seek(SeekFrom::Start((root_start + position.offset) as u64))
1034 .await?;
1035 let raw = data.read_struct::<RawDirectoryEntry>().await?;
1036 let bytes = unsafe { raw.bytes };
1037 if bytes[0] == 0x00 {
1038 break;
1039 }
1040 if bytes[0] != 0xE5
1041 && unsafe { raw.file }.attributes == DirEntryAttrFlags::LONG_NAME.bits()
1042 {
1043 if pending.len == LFN_CLEANUP_SCAN_LIMIT {
1044 for index in 1..pending.len {
1045 pending.positions[index - 1] = pending.positions[index];
1046 }
1047 pending.len -= 1;
1048 }
1049 pending.push(position);
1050 } else {
1051 pending.clear();
1052 }
1053 }
1054 return Err(Error::EntryNotFound);
1055 }
1056
1057 let mut current = entry.parent_dir_clus;
1058 let mut steps = 0u32;
1059 loop {
1060 steps = steps.saturating_add(1);
1061 if steps > self.fat.max_cluster() {
1062 return Err(Error::ClusterLoop {
1063 cluster: current.0 as u32,
1064 });
1065 }
1066
1067 {
1068 let mut data = self.data.lock();
1069 for offset in (0..cluster_size).step_by(entry_size) {
1070 let position = DirectoryEntryPosition {
1071 cluster: current,
1072 offset,
1073 };
1074 let seek_pos =
1075 current.to_bytes(self.info.data_start, cluster_size) + offset;
1076 if position == target {
1077 for index in 0..pending.len {
1078 let previous = pending.get(index);
1079 let previous_pos = previous
1080 .cluster
1081 .to_bytes(self.info.data_start, cluster_size)
1082 + previous.offset;
1083 data.seek(SeekFrom::Start(previous_pos as u64)).await?;
1084 data.write_all(&[0xE5]).await?;
1085 }
1086 data.seek(SeekFrom::Start(seek_pos as u64)).await?;
1087 data.write_all(&[0xE5]).await?;
1088 return Ok(());
1089 }
1090
1091 data.seek(SeekFrom::Start(seek_pos as u64)).await?;
1092 let raw = data.read_struct::<RawDirectoryEntry>().await?;
1093 let bytes = unsafe { raw.bytes };
1094 if bytes[0] == 0x00 {
1095 return Err(Error::EntryNotFound);
1096 }
1097 if bytes[0] != 0xE5
1098 && unsafe { raw.file }.attributes == DirEntryAttrFlags::LONG_NAME.bits()
1099 {
1100 if pending.len == LFN_CLEANUP_SCAN_LIMIT {
1101 for index in 1..pending.len {
1102 pending.positions[index - 1] = pending.positions[index];
1103 }
1104 pending.len -= 1;
1105 }
1106 pending.push(position);
1107 } else {
1108 pending.clear();
1109 }
1110 }
1111 }
1112
1113 match self.next_cluster_routed(current.0).await? {
1114 Some(next) => current = Cluster(next as usize),
1115 None => return Err(Error::EntryNotFound),
1116 }
1117 }
1118 }
1119
1120 async fn find_free_entry_run_in_dir(
1126 &self,
1127 dir: &FatDir<'_, DATA>,
1128 count: usize,
1129 ) -> Result<DirectoryEntryRun> {
1130 debug_assert!((1..=MAX_DIRECTORY_ENTRY_RUN).contains(&count));
1131 if let Some((root_start, root_size)) = dir.fixed_root {
1132 self.find_free_entry_run_in_fixed_root(root_start, root_size, count)
1133 .await
1134 } else {
1135 self.find_free_entry_run_in_cluster_chain(dir.cluster, count)
1136 .await
1137 }
1138 }
1139
1140 async fn find_free_entry_run_in_fixed_root(
1144 &self,
1145 root_start: usize,
1146 root_size: usize,
1147 count: usize,
1148 ) -> Result<DirectoryEntryRun> {
1149 let mut data = self.data.lock();
1150 let entry_size = core::mem::size_of::<RawDirectoryEntry>();
1151 let max_entries = root_size / entry_size;
1152 let mut run = DirectoryEntryRun::new();
1153 let mut end_seen = false;
1154
1155 for i in 0..max_entries {
1156 let offset = i * entry_size;
1157 let free = if end_seen {
1158 true
1159 } else {
1160 data.seek(SeekFrom::Start((root_start + offset) as u64))
1161 .await?;
1162 let raw_entry = data.read_struct::<RawDirectoryEntry>().await?;
1163 let first_byte = unsafe { raw_entry.bytes[0] };
1164 if first_byte == 0x00 {
1165 end_seen = true;
1166 true
1167 } else {
1168 first_byte == 0xE5
1169 }
1170 };
1171
1172 if free {
1173 run.push(DirectoryEntryPosition {
1174 cluster: Cluster(0),
1175 offset,
1176 });
1177 if run.len == count {
1178 return Ok(run);
1179 }
1180 } else {
1181 run.clear();
1182 }
1183 }
1184
1185 Err(Error::DirectoryFull)
1186 }
1187
1188 async fn find_free_entry_run_in_cluster_chain(
1192 &self,
1193 dir_cluster: Cluster<usize>,
1194 count: usize,
1195 ) -> Result<DirectoryEntryRun> {
1196 let cluster_size = self.info.cluster_size;
1197 let entry_size = core::mem::size_of::<RawDirectoryEntry>();
1198 let entries_per_cluster = cluster_size / entry_size;
1199 let mut current_cluster = dir_cluster;
1200 let mut run = DirectoryEntryRun::new();
1201 let mut end_seen = false;
1202 let chain_limit = self.fat.max_cluster();
1205 let mut steps: u32 = 0;
1206
1207 loop {
1208 steps = steps.saturating_add(1);
1209 if steps > chain_limit {
1210 return Err(Error::ClusterLoop {
1211 cluster: current_cluster.0 as u32,
1212 });
1213 }
1214 {
1215 let mut data = self.data.lock();
1216 for i in 0..entries_per_cluster {
1217 let offset = i * entry_size;
1218 let free = if end_seen {
1219 true
1220 } else {
1221 let seek_pos =
1222 current_cluster.to_bytes(self.info.data_start, cluster_size) + offset;
1223 data.seek(SeekFrom::Start(seek_pos as u64)).await?;
1224 let raw_entry = data.read_struct::<RawDirectoryEntry>().await?;
1225 let first_byte = unsafe { raw_entry.bytes[0] };
1226 if first_byte == 0x00 {
1227 end_seen = true;
1228 true
1229 } else {
1230 first_byte == 0xE5
1231 }
1232 };
1233
1234 if free {
1235 run.push(DirectoryEntryPosition {
1236 cluster: current_cluster,
1237 offset,
1238 });
1239 if run.len == count {
1240 return Ok(run);
1241 }
1242 } else {
1243 run.clear();
1244 }
1245 }
1246 }
1247
1248 let next = self.next_cluster_routed(current_cluster.0).await?;
1250 match next {
1251 Some(cluster) => {
1252 current_cluster = Cluster(cluster as usize);
1253 }
1254 None => {
1255 let hint = current_cluster.0 as u32 + 1;
1257 let new_cluster = self.allocate_cluster_routed(hint).await?;
1258 let new_cluster_pos = Cluster(new_cluster as usize)
1259 .to_bytes(self.info.data_start, cluster_size);
1260 let zero_result = {
1261 let mut data = self.data.lock();
1262 data.seek(SeekFrom::Start(new_cluster_pos as u64)).await?;
1263 let zeros = alloc::vec![0u8; cluster_size];
1264 data.write_all(&zeros).await
1265 };
1266 if let Err(error) = zero_result {
1267 let _ = self.free_chain_routed(new_cluster).await;
1268 return Err(error.into());
1269 }
1270 if let Err(error) = self
1271 .write_clus_routed(current_cluster.0, new_cluster)
1272 .await
1273 {
1274 let _ = self.free_chain_routed(new_cluster).await;
1275 return Err(error);
1276 }
1277
1278 self.decrement_free_count();
1279 self.update_next_free_hint(new_cluster);
1280 current_cluster = Cluster(new_cluster as usize);
1281 end_seen = true;
1282 }
1283 }
1284 }
1285 }
1286
1287 async fn write_raw_entry(
1291 &self,
1292 cluster: Cluster<usize>,
1293 offset: usize,
1294 entry: &RawFileEntry,
1295 fixed_root: Option<(usize, usize)>,
1296 ) -> Result<()> {
1297 let mut data = self.data.lock();
1298 let cluster_size = data.cluster_size;
1299
1300 let seek_pos = if cluster.0 == 0 {
1302 let (root_start, _) = fixed_root.expect("Fixed root info required for cluster 0");
1304 root_start + offset
1305 } else {
1306 cluster.to_bytes(self.info.data_start, cluster_size) + offset
1308 };
1309
1310 data.seek(SeekFrom::Start(seek_pos as u64)).await?;
1311 data.write_all(bytemuck::bytes_of(entry)).await?;
1312 Ok(())
1313 }
1314
1315 #[cfg(feature = "lfn")]
1320 async fn write_raw_directory_entry(
1321 &self,
1322 cluster: Cluster<usize>,
1323 offset: usize,
1324 entry: &RawDirectoryEntry,
1325 fixed_root: Option<(usize, usize)>,
1326 ) -> Result<()> {
1327 let mut data = self.data.lock();
1328 let cluster_size = data.cluster_size;
1329 let seek_pos = if cluster.0 == 0 {
1330 let (root_start, _) = fixed_root.expect("Fixed root info required for cluster 0");
1331 root_start + offset
1332 } else {
1333 cluster.to_bytes(self.info.data_start, cluster_size) + offset
1334 };
1335 data.seek(SeekFrom::Start(seek_pos as u64)).await?;
1336 data.write_all(bytemuck::bytes_of(entry)).await?;
1341 Ok(())
1342 }
1343
1344 pub async fn create_file(&self, parent: &FatDir<'_, DATA>, name: &str) -> Result<FileEntry> {
1348 self.revalidate_parent_dir(parent).await?;
1352
1353 if parent.find(name).await?.is_some() {
1355 return Err(Error::AlreadyExists);
1356 }
1357
1358 let short_name = ShortFileName::from_long_name_with(name, 0, self.oem_converter())
1360 .map_err(|_| Error::InvalidFilename)?;
1361
1362 let case_bits = short_name_case_bits(name);
1366 #[cfg(feature = "lfn")]
1367 let mut lfn_buf: [RawDirectoryEntry; MAX_LFN_ENTRIES] = unsafe { core::mem::zeroed() };
1368 #[cfg(feature = "lfn")]
1369 let (lfn_count, nt_res) = match case_bits {
1370 Some(bits) => (0usize, bits),
1371 None => (
1372 build_lfn_entries(name, short_name.lfn_checksum(), &mut lfn_buf)
1373 .ok_or(Error::InvalidFilename)?,
1374 0u8,
1375 ),
1376 };
1377 #[cfg(not(feature = "lfn"))]
1378 let (lfn_count, nt_res) = (0usize, case_bits.unwrap_or(0));
1379
1380 let total_slots = lfn_count + 1;
1382 let run = self.find_free_entry_run_in_dir(parent, total_slots).await?;
1383
1384 let now = self.time_provider().now();
1386 let (date, time, time_tenth) = now.to_raw();
1387
1388 let mut raw_name = short_name.to_raw_bytes();
1389 kanji_short_name_fixup(&mut raw_name);
1390
1391 let entry = RawFileEntry {
1392 name: raw_name,
1393 attributes: DirEntryAttrFlags::ARCHIVE.bits(),
1394 reserved: nt_res,
1395 creation_time_tenth: time_tenth,
1396 creation_time: time.to_le_bytes(),
1397 creation_date: date.to_le_bytes(),
1398 last_access_date: date.to_le_bytes(),
1399 first_cluster_high: hadris_common::types::number::U16::<LittleEndian>::new(0),
1400 last_write_time: time.to_le_bytes(),
1401 last_write_date: date.to_le_bytes(),
1402 first_cluster_low: hadris_common::types::number::U16::<LittleEndian>::new(0),
1403 size: hadris_common::types::number::U32::<LittleEndian>::new(0),
1404 };
1405
1406 #[cfg(feature = "lfn")]
1407 for (i, lfn_entry) in lfn_buf.iter().enumerate().take(lfn_count) {
1408 let position = run.get(i);
1409 self.write_raw_directory_entry(
1410 position.cluster,
1411 position.offset,
1412 lfn_entry,
1413 parent.fixed_root,
1414 )
1415 .await?;
1416 }
1417 let short_position = run.last();
1419 self.write_raw_entry(
1420 short_position.cluster,
1421 short_position.offset,
1422 &entry,
1423 parent.fixed_root,
1424 )
1425 .await?;
1426
1427 self.write_fsinfo().await?;
1430
1431 Ok(FileEntry {
1432 short_name,
1433 nt_case: crate::raw::NtCaseFlags::from_bits_truncate(nt_res),
1434 #[cfg(feature = "lfn")]
1435 long_name: if lfn_count > 0 {
1436 crate::file::LongFileName::from_str_utf16(name)
1437 } else {
1438 None
1439 },
1440 attr: DirEntryAttrFlags::ARCHIVE,
1441 size: 0,
1442 parent_dir_clus: parent.cluster,
1443 parent_clus: short_position.cluster,
1444 offset_within_cluster: short_position.offset,
1445 cluster: Cluster(0),
1446 created: now,
1447 last_access_date: now.date,
1448 modified: crate::time::FatDateTime::from_raw(now.date, now.time, 0),
1449 })
1450 }
1451
1452 pub async fn create_dir<'a>(
1456 &'a self,
1457 parent: &FatDir<'a, DATA>,
1458 name: &str,
1459 ) -> Result<FatDir<'a, DATA>> {
1460 self.revalidate_parent_dir(parent).await?;
1463
1464 if parent.find(name).await?.is_some() {
1466 return Err(Error::AlreadyExists);
1467 }
1468
1469 let short_name = ShortFileName::from_long_name_with(name, 0, self.oem_converter())
1471 .map_err(|_| Error::InvalidFilename)?;
1472
1473 let case_bits = short_name_case_bits(name);
1477 #[cfg(feature = "lfn")]
1478 let mut lfn_buf: [RawDirectoryEntry; MAX_LFN_ENTRIES] = unsafe { core::mem::zeroed() };
1479 #[cfg(feature = "lfn")]
1480 let (lfn_count, nt_res) = match case_bits {
1481 Some(bits) => (0usize, bits),
1482 None => (
1483 build_lfn_entries(name, short_name.lfn_checksum(), &mut lfn_buf)
1484 .ok_or(Error::InvalidFilename)?,
1485 0u8,
1486 ),
1487 };
1488 #[cfg(not(feature = "lfn"))]
1489 let (lfn_count, nt_res) = (0usize, case_bits.unwrap_or(0));
1490
1491 let total_slots = lfn_count + 1;
1493 let run = self.find_free_entry_run_in_dir(parent, total_slots).await?;
1494
1495 let new_cluster = self.allocate_cluster_routed(2).await?;
1499 self.decrement_free_count();
1500 self.update_next_free_hint(new_cluster);
1501
1502 let now = self.time_provider().now();
1504 let (date, time, time_tenth) = now.to_raw();
1505
1506 let (cluster_high, cluster_low) = match &self.fat {
1508 Fat::Fat12(_) | Fat::Fat16(_) => (0u16, new_cluster as u16),
1509 Fat::Fat32(_) => ((new_cluster >> 16) as u16, new_cluster as u16),
1510 };
1511
1512 let mut raw_name = short_name.to_raw_bytes();
1513 kanji_short_name_fixup(&mut raw_name);
1514
1515 let entry = RawFileEntry {
1516 name: raw_name,
1517 attributes: DirEntryAttrFlags::DIRECTORY.bits(),
1518 reserved: nt_res,
1519 creation_time_tenth: time_tenth,
1520 creation_time: time.to_le_bytes(),
1521 creation_date: date.to_le_bytes(),
1522 last_access_date: date.to_le_bytes(),
1523 first_cluster_high: hadris_common::types::number::U16::<LittleEndian>::new(
1524 cluster_high,
1525 ),
1526 last_write_time: time.to_le_bytes(),
1527 last_write_date: date.to_le_bytes(),
1528 first_cluster_low: hadris_common::types::number::U16::<LittleEndian>::new(cluster_low),
1529 size: hadris_common::types::number::U32::<LittleEndian>::new(0),
1530 };
1531
1532 #[cfg(feature = "lfn")]
1533 for (i, lfn_entry) in lfn_buf.iter().enumerate().take(lfn_count) {
1534 let position = run.get(i);
1535 self.write_raw_directory_entry(
1536 position.cluster,
1537 position.offset,
1538 lfn_entry,
1539 parent.fixed_root,
1540 )
1541 .await?;
1542 }
1543 let short_position = run.last();
1544 let (slot_cluster, slot_offset) = (short_position.cluster, short_position.offset);
1545 self.write_raw_entry(slot_cluster, slot_offset, &entry, parent.fixed_root).await?;
1546
1547 {
1549 let mut data = self.data.lock();
1550 let cluster_size = data.cluster_size;
1551 let dir_pos =
1552 Cluster(new_cluster as usize).to_bytes(self.info.data_start, cluster_size);
1553
1554 data.seek(SeekFrom::Start(dir_pos as u64)).await?;
1556 let zeros = alloc::vec![0u8; cluster_size];
1557 data.write_all(&zeros).await?;
1558
1559 let dot_entry = RawFileEntry {
1561 name: *b". ",
1562 attributes: DirEntryAttrFlags::DIRECTORY.bits(),
1563 reserved: 0,
1564 creation_time_tenth: time_tenth,
1565 creation_time: time.to_le_bytes(),
1566 creation_date: date.to_le_bytes(),
1567 last_access_date: date.to_le_bytes(),
1568 first_cluster_high: hadris_common::types::number::U16::<LittleEndian>::new(
1569 cluster_high,
1570 ),
1571 last_write_time: time.to_le_bytes(),
1572 last_write_date: date.to_le_bytes(),
1573 first_cluster_low: hadris_common::types::number::U16::<LittleEndian>::new(
1574 cluster_low,
1575 ),
1576 size: hadris_common::types::number::U32::<LittleEndian>::new(0),
1577 };
1578 data.seek(SeekFrom::Start(dir_pos as u64)).await?;
1579 data.write_all(bytemuck::bytes_of(&dot_entry)).await?;
1580
1581 let parent_cluster = parent.cluster.0 as u32;
1587 let dotdot_cluster = if self.is_fat32_root_cluster(parent_cluster) {
1588 0
1589 } else {
1590 parent_cluster
1591 };
1592 let (parent_high, parent_low) = match &self.fat {
1593 Fat::Fat12(_) | Fat::Fat16(_) => (0u16, dotdot_cluster as u16),
1594 Fat::Fat32(_) => ((dotdot_cluster >> 16) as u16, dotdot_cluster as u16),
1595 };
1596
1597 let dotdot_entry = RawFileEntry {
1598 name: *b".. ",
1599 attributes: DirEntryAttrFlags::DIRECTORY.bits(),
1600 reserved: 0,
1601 creation_time_tenth: time_tenth,
1602 creation_time: time.to_le_bytes(),
1603 creation_date: date.to_le_bytes(),
1604 last_access_date: date.to_le_bytes(),
1605 first_cluster_high: hadris_common::types::number::U16::<LittleEndian>::new(
1606 parent_high,
1607 ),
1608 last_write_time: time.to_le_bytes(),
1609 last_write_date: date.to_le_bytes(),
1610 first_cluster_low: hadris_common::types::number::U16::<LittleEndian>::new(
1611 parent_low,
1612 ),
1613 size: hadris_common::types::number::U32::<LittleEndian>::new(0),
1614 };
1615 let dotdot_pos = dir_pos + core::mem::size_of::<RawDirectoryEntry>();
1616 data.seek(SeekFrom::Start(dotdot_pos as u64)).await?;
1617 data.write_all(bytemuck::bytes_of(&dotdot_entry)).await?;
1618 }
1619
1620 self.write_fsinfo().await?;
1622
1623 Ok(FatDir {
1624 data: self,
1625 cluster: Cluster(new_cluster as usize),
1626 fixed_root: None, dir_entry: Some(DirSlot {
1628 parent_clus: slot_cluster,
1629 offset_within_cluster: slot_offset,
1630 short_name,
1631 created: now,
1632 }),
1633 })
1634 }
1635
1636 pub async fn delete(&self, entry: &FileEntry) -> Result<()> {
1638 self.revalidate_entry(entry).await?;
1641
1642 if entry.is_directory() {
1644 let dir = FatDir {
1645 data: self,
1646 cluster: entry.cluster(),
1647 fixed_root: None, dir_entry: None,
1650 };
1651
1652 let mut count = 0;
1653 let mut iter = dir.entries();
1654 while let Some(item) = iter.next_entry().await {
1655 let item = item?;
1656 let name = item.name();
1657 if name != "." && name != ".." {
1658 count += 1;
1659 }
1660 }
1661
1662 if count > 0 {
1663 return Err(Error::DirectoryNotEmpty);
1664 }
1665 }
1666
1667 self.mark_entry_span_deleted(entry).await?;
1675
1676 if entry.cluster().0 >= 2 {
1678 let freed_count = self.free_chain_routed(entry.cluster().0 as u32).await?;
1679 self.increment_free_count(freed_count);
1681 }
1682
1683 self.write_fsinfo().await?;
1685
1686 Ok(())
1687 }
1688
1689 pub async fn rename(
1699 &self,
1700 entry: &FileEntry,
1701 dest_dir: &FatDir<'_, DATA>,
1702 new_name: &str,
1703 ) -> Result<FileEntry> {
1704 self.revalidate_entry(entry).await?;
1708
1709 if dest_dir.find(new_name).await?.is_some() {
1711 return Err(Error::AlreadyExists);
1712 }
1713
1714 let short_name = ShortFileName::from_long_name_with(new_name, 0, self.oem_converter())
1716 .map_err(|_| Error::InvalidFilename)?;
1717
1718 let case_bits = short_name_case_bits(new_name);
1722 #[cfg(feature = "lfn")]
1723 let mut lfn_buf: [RawDirectoryEntry; MAX_LFN_ENTRIES] = unsafe { core::mem::zeroed() };
1724 #[cfg(feature = "lfn")]
1725 let (lfn_count, nt_res) = match case_bits {
1726 Some(bits) => (0usize, bits),
1727 None => (
1728 build_lfn_entries(new_name, short_name.lfn_checksum(), &mut lfn_buf)
1729 .ok_or(Error::InvalidFilename)?,
1730 0u8,
1731 ),
1732 };
1733 #[cfg(not(feature = "lfn"))]
1734 let (lfn_count, nt_res) = (0usize, case_bits.unwrap_or(0));
1735
1736 let total_slots = lfn_count + 1;
1738 let run = self
1739 .find_free_entry_run_in_dir(dest_dir, total_slots)
1740 .await?;
1741 #[cfg(feature = "lfn")]
1742 for (i, lfn_entry) in lfn_buf.iter().enumerate().take(lfn_count) {
1743 let position = run.get(i);
1744 self.write_raw_directory_entry(
1745 position.cluster,
1746 position.offset,
1747 lfn_entry,
1748 dest_dir.fixed_root,
1749 )
1750 .await?;
1751 }
1752 let short_position = run.last();
1753 let slot_cluster = short_position.cluster;
1754 let slot_offset = short_position.offset;
1755
1756 let original_raw = {
1758 let mut data = self.data.lock();
1759 let cluster_size = data.cluster_size;
1760
1761 let entry_pos = if entry.parent_clus.0 == 0 {
1762 let (root_start, _) = self
1763 .fixed_root_dir_info()
1764 .expect("Fixed root info required for cluster 0");
1765 root_start + entry.offset_within_cluster
1766 } else {
1767 entry
1768 .parent_clus
1769 .to_bytes(self.info.data_start, cluster_size)
1770 + entry.offset_within_cluster
1771 };
1772
1773 data.seek(SeekFrom::Start(entry_pos as u64)).await?;
1774 data.read_struct::<RawDirectoryEntry>().await?
1775 };
1776
1777 let original_file = unsafe { &original_raw.file };
1779 let mut raw_name = short_name.to_raw_bytes();
1780 kanji_short_name_fixup(&mut raw_name);
1781
1782 let now = self.time_provider().now();
1783 let new_entry = RawFileEntry {
1784 name: raw_name,
1785 reserved: nt_res,
1787 attributes: original_file.attributes,
1788 creation_time_tenth: original_file.creation_time_tenth,
1789 creation_time: original_file.creation_time,
1790 creation_date: original_file.creation_date,
1791 last_access_date: now.date.to_le_bytes(),
1792 first_cluster_high: original_file.first_cluster_high,
1793 last_write_time: now.time.to_le_bytes(),
1794 last_write_date: now.date.to_le_bytes(),
1795 first_cluster_low: original_file.first_cluster_low,
1796 size: original_file.size,
1797 };
1798
1799 self.write_raw_entry(slot_cluster, slot_offset, &new_entry, dest_dir.fixed_root)
1801 .await?;
1802
1803 if entry.is_directory()
1805 && entry.cluster().0 >= 2
1806 && dest_dir.cluster != entry.parent_dir_clus
1807 {
1808 let mut data = self.data.lock();
1809 let cluster_size = data.cluster_size;
1810 let dir_data_start =
1811 entry.cluster().to_bytes(self.info.data_start, cluster_size);
1812 let dotdot_pos = dir_data_start + core::mem::size_of::<RawDirectoryEntry>();
1814 data.seek(SeekFrom::Start(dotdot_pos as u64)).await?;
1815 let mut dotdot = data.read_struct::<RawDirectoryEntry>().await?;
1816 let dotdot_file = unsafe { &mut dotdot.file };
1817
1818 let parent_cluster = dest_dir.cluster.0 as u32;
1821 let dotdot_cluster = if self.is_fat32_root_cluster(parent_cluster) {
1822 0
1823 } else {
1824 parent_cluster
1825 };
1826 let (parent_high, parent_low) = match &self.fat {
1827 Fat::Fat12(_) | Fat::Fat16(_) => (0u16, dotdot_cluster as u16),
1828 Fat::Fat32(_) => ((dotdot_cluster >> 16) as u16, dotdot_cluster as u16),
1829 };
1830 dotdot_file.first_cluster_high =
1831 hadris_common::types::number::U16::<LittleEndian>::new(parent_high);
1832 dotdot_file.first_cluster_low =
1833 hadris_common::types::number::U16::<LittleEndian>::new(parent_low);
1834
1835 data.seek(SeekFrom::Start(dotdot_pos as u64)).await?;
1836 data.write_all(bytemuck::bytes_of(&dotdot)).await?;
1837 }
1838
1839 self.mark_entry_span_deleted(entry).await?;
1842
1843 self.write_fsinfo().await?;
1846
1847 Ok(FileEntry {
1848 short_name,
1849 nt_case: crate::raw::NtCaseFlags::from_bits_truncate(nt_res),
1850 #[cfg(feature = "lfn")]
1851 long_name: if lfn_count > 0 {
1852 crate::file::LongFileName::from_str_utf16(new_name)
1853 } else {
1854 None
1855 },
1856 attr: DirEntryAttrFlags::from_bits_retain(original_file.attributes),
1857 size: original_file.size.get() as usize,
1858 parent_dir_clus: dest_dir.cluster,
1859 parent_clus: slot_cluster,
1860 offset_within_cluster: slot_offset,
1861 cluster: entry.cluster(),
1862 created: crate::time::FatDateTime::from_raw(
1864 u16::from_le_bytes(original_file.creation_date),
1865 u16::from_le_bytes(original_file.creation_time),
1866 original_file.creation_time_tenth,
1867 ),
1868 last_access_date: now.date,
1869 modified: crate::time::FatDateTime::from_raw(now.date, now.time, 0),
1870 })
1871 }
1872
1873 async fn update_entry_size_and_cluster(
1877 &self,
1878 entry: &FileEntry,
1879 new_size: usize,
1880 first_cluster: Cluster<usize>,
1881 fixed_root: Option<(usize, usize)>,
1882 ) -> Result<()> {
1883 use super::fat_table::Fat;
1884
1885 let mut data = self.data.lock();
1886 let cluster_size = data.cluster_size;
1887
1888 let entry_pos = if entry.parent_clus.0 == 0 {
1890 let (root_start, _) = fixed_root.expect("Fixed root info required for cluster 0");
1892 root_start + entry.offset_within_cluster
1893 } else {
1894 entry
1896 .parent_clus
1897 .to_bytes(self.info.data_start, cluster_size)
1898 + entry.offset_within_cluster
1899 };
1900
1901 data.seek(SeekFrom::Start(entry_pos as u64)).await?;
1903
1904 let mut raw_entry = data.read_struct::<RawDirectoryEntry>().await?;
1905 let file_entry = unsafe { &mut raw_entry.file };
1906
1907 file_entry.size = hadris_common::types::number::U32::<LittleEndian>::new(new_size as u32);
1909
1910 let (high, low) = if first_cluster.0 >= 2 {
1912 match &self.fat {
1913 Fat::Fat12(_) | Fat::Fat16(_) => (0u16, first_cluster.0 as u16),
1914 Fat::Fat32(_) => ((first_cluster.0 >> 16) as u16, first_cluster.0 as u16),
1915 }
1916 } else {
1917 (0u16, 0u16)
1918 };
1919 file_entry.first_cluster_high =
1920 hadris_common::types::number::U16::<LittleEndian>::new(high);
1921 file_entry.first_cluster_low = hadris_common::types::number::U16::<LittleEndian>::new(low);
1922
1923 let now = self.time_provider().now();
1925 file_entry.last_write_date = now.date.to_le_bytes();
1926 file_entry.last_write_time = now.time.to_le_bytes();
1927 file_entry.last_access_date = now.date.to_le_bytes();
1928
1929 data.seek(SeekFrom::Start(entry_pos as u64)).await?;
1931 data.write_all(bytemuck::bytes_of(&raw_entry)).await?;
1932
1933 Ok(())
1934 }
1935}
1936
1937#[cfg(feature = "write")]
1939impl<DATA: Read + Write + Seek> FatVolume<DATA> {
1940 pub async fn set_root_label(&self, name: &[u8; 11]) -> Result<()> {
1950 let (pos, raw) = self
1951 .find_root_label_entry()
1952 .await?
1953 .ok_or(Error::EntryNotFound)?;
1954 let mut updated = raw;
1955 updated.file.name = *name;
1958
1959 let mut data = self.data.lock();
1960 data.seek(SeekFrom::Start(pos as u64)).await?;
1961 data.write_all(bytemuck::bytes_of(&updated)).await?;
1962 data.flush().await?;
1963 Ok(())
1964 }
1965}
1966
1967#[cfg(feature = "write")]
1969impl<DATA: Read + Write + Seek> FatVolume<DATA> {
1970 pub async fn set_attributes(
1978 &self,
1979 entry: &FileEntry,
1980 attrs: DirEntryAttrFlags,
1981 ) -> Result<()> {
1982 self.revalidate_entry(entry).await?;
1986
1987 let current = entry.attributes();
1989 let immutable = DirEntryAttrFlags::DIRECTORY | DirEntryAttrFlags::VOLUME_ID;
1990 let changed = (current ^ attrs) & immutable;
1991 if changed.contains(DirEntryAttrFlags::DIRECTORY) {
1992 return Err(Error::InvalidAttributeChange { bit: "DIRECTORY" });
1993 }
1994 if changed.contains(DirEntryAttrFlags::VOLUME_ID) {
1995 return Err(Error::InvalidAttributeChange { bit: "VOLUME_ID" });
1996 }
1997
1998 let mut data = self.data.lock();
1999 let cluster_size = data.cluster_size;
2000
2001 let entry_pos = if entry.parent_clus.0 == 0 {
2002 let (root_start, _) = self
2003 .fixed_root_dir_info()
2004 .expect("Fixed root info required for cluster 0");
2005 root_start + entry.offset_within_cluster
2006 } else {
2007 entry
2008 .parent_clus
2009 .to_bytes(self.info.data_start, cluster_size)
2010 + entry.offset_within_cluster
2011 };
2012
2013 data.seek(SeekFrom::Start(entry_pos as u64)).await?;
2015 let mut raw_entry = data.read_struct::<RawDirectoryEntry>().await?;
2016 let file_entry = unsafe { &mut raw_entry.file };
2017
2018 file_entry.attributes = attrs.bits();
2020
2021 data.seek(SeekFrom::Start(entry_pos as u64)).await?;
2023 data.write_all(bytemuck::bytes_of(&raw_entry)).await?;
2024
2025 Ok(())
2026 }
2027}
2028
2029#[cfg(feature = "write")]
2031impl<DATA: Read + Write + Seek> FatVolume<DATA> {
2032 pub async fn sync(&self) -> Result<()> {
2038 self.write_fsinfo().await?;
2039
2040 let mut data = self.data.lock();
2041 data.flush().await?;
2042 Ok(())
2043 }
2044
2045 async fn write_fsinfo(&self) -> Result<()> {
2050 use super::fs::FatFsExt;
2051 use crate::raw::RawFsInfo;
2052
2053 let ext = match &self.ext {
2054 FatFsExt::Fat32(ext) => ext,
2055 _ => return Ok(()), };
2057
2058 let mut data = self.data.lock();
2059
2060 data.seek_sector(ext.fs_info_sec).await?;
2062
2063 let mut fs_info = data.read_struct::<RawFsInfo>().await?;
2065
2066 fs_info.free_count =
2068 hadris_common::types::number::U32::<LittleEndian>::new(ext.free_count.get());
2069 fs_info.next_free =
2070 hadris_common::types::number::U32::<LittleEndian>::new(ext.next_free.get().0);
2071
2072 data.seek_sector(ext.fs_info_sec).await?;
2074 data.write_all(bytemuck::bytes_of(&fs_info)).await?;
2075
2076 Ok(())
2077 }
2078
2079 pub(crate) fn decrement_free_count(&self) {
2083 use super::fs::FatFsExt;
2084
2085 if let FatFsExt::Fat32(ext) = &self.ext {
2086 let count = ext.free_count.get();
2087 if count > 0 && count != 0xFFFFFFFF {
2088 ext.free_count.set(count - 1);
2089 }
2090 }
2091 }
2092
2093 pub(crate) fn increment_free_count(&self, amount: u32) {
2097 use super::fs::FatFsExt;
2098
2099 if let FatFsExt::Fat32(ext) = &self.ext {
2100 let count = ext.free_count.get();
2101 if count != 0xFFFFFFFF {
2102 ext.free_count.set(count.saturating_add(amount));
2103 }
2104 }
2105 }
2106
2107 pub(crate) fn update_next_free_hint(&self, cluster: u32) {
2111 use super::fs::FatFsExt;
2112
2113 if let FatFsExt::Fat32(ext) = &self.ext {
2114 ext.next_free.set(Cluster(cluster.saturating_add(1)));
2116 }
2117 }
2118
2119 pub fn free_cluster_count(&self) -> Option<u32> {
2123 use super::fs::FatFsExt;
2124
2125 match &self.ext {
2126 FatFsExt::Fat32(ext) => {
2127 let count = ext.free_count.get();
2128 if count != 0xFFFFFFFF {
2129 Some(count)
2130 } else {
2131 None
2132 }
2133 }
2134 _ => None,
2135 }
2136 }
2137
2138 pub fn next_free_cluster_hint(&self) -> Option<u32> {
2142 use super::fs::FatFsExt;
2143
2144 match &self.ext {
2145 FatFsExt::Fat32(ext) => {
2146 let hint = ext.next_free.get().0;
2147 if hint >= 2 && hint != 0xFFFFFFFF {
2148 Some(hint)
2149 } else {
2150 None
2151 }
2152 }
2153 _ => None,
2154 }
2155 }
2156}
2157
2158#[cfg(all(test, feature = "write", feature = "lfn"))]
2166mod lfn_write_safety_tests {
2167 use super::{build_lfn_entries, MAX_LFN_ENTRIES};
2168 use crate::raw::{DirEntryAttrFlags, RawDirectoryEntry};
2169
2170 fn fresh_out() -> [RawDirectoryEntry; MAX_LFN_ENTRIES] {
2171 unsafe { core::mem::zeroed() }
2174 }
2175
2176 #[test]
2180 fn build_lfn_entries_at_spec_cap_255_units_does_not_oob() {
2181 let name: alloc::string::String = core::iter::repeat_n('a', 255).collect();
2182 let mut out = fresh_out();
2183 let n = build_lfn_entries(&name, 0, &mut out).expect("must accept 255 chars");
2184 assert_eq!(n, 20);
2185 for entry in out.iter().take(n) {
2186 let bytes = unsafe { entry.bytes };
2189 assert_eq!(bytes.len(), 32);
2190 }
2191 }
2192
2193 #[test]
2197 fn build_lfn_entries_overlong_returns_none() {
2198 let name: alloc::string::String = core::iter::repeat_n('a', 256).collect();
2199 let mut out = fresh_out();
2200 assert!(build_lfn_entries(&name, 0, &mut out).is_none());
2201 }
2202
2203 #[test]
2207 fn build_lfn_entries_supplementary_plane_writes_both_surrogates() {
2208 let name: alloc::string::String = core::iter::repeat_n('\u{1F31F}', 100).collect();
2210 let mut out = fresh_out();
2211 let n = build_lfn_entries(&name, 0, &mut out).expect("100 emoji fits");
2212 assert_eq!(n, 16);
2214 let mut saw_high = false;
2218 let mut saw_low = false;
2219 for entry in out.iter().take(n) {
2220 let lfn = unsafe { entry.lfn };
2221 for chunk in lfn.name1.chunks_exact(2) {
2222 let unit = u16::from_le_bytes([chunk[0], chunk[1]]);
2223 if (0xD800..0xDC00).contains(&unit) {
2224 saw_high = true;
2225 }
2226 if (0xDC00..0xE000).contains(&unit) {
2227 saw_low = true;
2228 }
2229 }
2230 }
2231 assert!(saw_high && saw_low, "must encode both halves of the surrogate pair");
2232 }
2233
2234 #[test]
2238 fn build_lfn_entries_exact_13_unit_multiple_no_padding() {
2239 let name: alloc::string::String = core::iter::repeat_n('a', 13).collect();
2240 let mut out = fresh_out();
2241 let n = build_lfn_entries(&name, 0, &mut out).expect("13 chars fits");
2242 assert_eq!(n, 1);
2243
2244 let lfn = unsafe { out[0].lfn };
2245 for chunk in lfn
2247 .name1
2248 .chunks_exact(2)
2249 .chain(lfn.name2.chunks_exact(2))
2250 .chain(lfn.name3.chunks_exact(2))
2251 {
2252 let unit = u16::from_le_bytes([chunk[0], chunk[1]]);
2253 assert_eq!(unit, b'a' as u16, "exact-fill must contain only real chars");
2254 }
2255 assert_eq!(lfn.sequence_number, 0x41); assert_eq!(lfn.attributes, DirEntryAttrFlags::LONG_NAME.bits());
2258 }
2259
2260 #[test]
2264 fn build_lfn_entries_first_slot_has_last_entry_mask() {
2265 let name = "longishname.tx"; let mut out = fresh_out();
2267 let n = build_lfn_entries(name, 0xAB, &mut out).expect("ok");
2268 assert_eq!(n, 2);
2269 let first = unsafe { out[0].lfn };
2270 assert_eq!(first.sequence_number, 0x42); assert_eq!(first.checksum, 0xAB);
2272 let second = unsafe { out[1].lfn };
2273 assert_eq!(second.sequence_number, 0x01); assert_eq!(second.checksum, 0xAB);
2275 }
2276}
2277
2278}