1use crate::constants::*;
8use crate::types::*;
9use std::io::{self, Write};
10
11#[inline]
17fn align_up(n: usize, align: usize) -> usize {
18 (n + align - 1) & !(align - 1)
19}
20
21fn dir_file_type(mode: u16) -> u8 {
23 FileType::from_mode(mode) as u8
24}
25
26pub fn write_dir_entry<W: Write>(
41 writer: &mut W,
42 name: &str,
43 inode: u32,
44 mode: u16,
45 link_inode: Option<u32>,
46 link_mode: Option<u16>,
47 block_size: u32,
48 left: &mut i32,
49) -> io::Result<()> {
50 let name_bytes = name.as_bytes();
51 let entry_size = align_up(DirectoryEntry::SIZE + name_bytes.len(), 4);
52
53 let min_trailing = 12;
55
56 if (*left as usize) < entry_size + min_trailing {
59 finish_dir_entry_block(writer, left, block_size)?;
60 }
61
62 let actual_inode = link_inode.unwrap_or(inode);
64 let actual_mode = link_mode.unwrap_or(mode);
65
66 let entry = DirectoryEntry {
67 inode: actual_inode,
68 rec_len: entry_size as u16,
69 name_len: name_bytes.len() as u8,
70 file_type: dir_file_type(actual_mode),
71 };
72
73 let mut header_buf = [0u8; DirectoryEntry::SIZE];
74 entry.write_to(&mut header_buf);
75 writer.write_all(&header_buf)?;
76
77 writer.write_all(name_bytes)?;
79
80 let padding = entry_size - DirectoryEntry::SIZE - name_bytes.len();
82 if padding > 0 {
83 let zeros = [0u8; 4];
84 writer.write_all(&zeros[..padding])?;
85 }
86
87 *left -= entry_size as i32;
88
89 Ok(())
90}
91
92pub fn finish_dir_entry_block<W: Write>(
98 writer: &mut W,
99 left: &mut i32,
100 block_size: u32,
101) -> io::Result<()> {
102 if *left <= 0 {
103 *left = block_size as i32;
104 return Ok(());
105 }
106
107 let remaining = *left as usize;
108
109 let term = DirectoryEntry {
111 inode: 0,
112 rec_len: remaining as u16,
113 name_len: 0,
114 file_type: 0,
115 };
116
117 let mut header_buf = [0u8; DirectoryEntry::SIZE];
118 term.write_to(&mut header_buf);
119 writer.write_all(&header_buf)?;
120
121 let fill = remaining - DirectoryEntry::SIZE;
123 if fill > 0 {
124 let zeros = vec![0u8; fill];
125 writer.write_all(&zeros)?;
126 }
127
128 *left = block_size as i32;
129
130 Ok(())
131}
132
133pub fn parse_dir_entries(data: &[u8]) -> Vec<(String, u32)> {
142 let mut entries = Vec::new();
143 let mut offset = 0;
144
145 while offset + DirectoryEntry::SIZE <= data.len() {
146 let entry = DirectoryEntry::read_from(&data[offset..]);
147
148 if entry.rec_len == 0 {
150 break;
151 }
152
153 if entry.inode != 0 && entry.name_len > 0 {
154 let name_start = offset + DirectoryEntry::SIZE;
155 let name_end = name_start + entry.name_len as usize;
156
157 if name_end <= data.len() {
158 let name = String::from_utf8_lossy(&data[name_start..name_end]).into_owned();
159 entries.push((name, entry.inode));
160 }
161 }
162
163 offset += entry.rec_len as usize;
164 }
165
166 entries
167}
168
169#[cfg(test)]
170mod tests {
171 use super::*;
172
173 #[test]
174 fn test_align_up() {
175 assert_eq!(align_up(0, 4), 0);
176 assert_eq!(align_up(1, 4), 4);
177 assert_eq!(align_up(4, 4), 4);
178 assert_eq!(align_up(5, 4), 8);
179 assert_eq!(align_up(8, 4), 8);
180 assert_eq!(align_up(9, 4), 12);
181 }
182
183 #[test]
184 fn test_write_and_parse_dir_entries() {
185 let block_size = 4096u32;
186 let mut buf = Vec::new();
187 let mut left = block_size as i32;
188
189 write_dir_entry(
191 &mut buf, ".", 2, file_mode::S_IFDIR | 0o755,
192 None, None, block_size, &mut left,
193 ).unwrap();
194
195 write_dir_entry(
197 &mut buf, "..", 2, file_mode::S_IFDIR | 0o755,
198 None, None, block_size, &mut left,
199 ).unwrap();
200
201 write_dir_entry(
203 &mut buf, "hello.txt", 11, file_mode::S_IFREG | 0o644,
204 None, None, block_size, &mut left,
205 ).unwrap();
206
207 finish_dir_entry_block(&mut buf, &mut left, block_size).unwrap();
209
210 assert_eq!(buf.len(), block_size as usize);
211 assert_eq!(left, block_size as i32);
212
213 let entries = parse_dir_entries(&buf);
215 assert_eq!(entries.len(), 3);
216 assert_eq!(entries[0], (".".to_string(), 2));
217 assert_eq!(entries[1], ("..".to_string(), 2));
218 assert_eq!(entries[2], ("hello.txt".to_string(), 11));
219 }
220
221 #[test]
222 fn test_finish_dir_entry_block_at_boundary() {
223 let block_size = 4096u32;
224 let mut buf = Vec::new();
225 let mut left = 0i32;
226
227 finish_dir_entry_block(&mut buf, &mut left, block_size).unwrap();
229 assert_eq!(buf.len(), 0);
230 assert_eq!(left, block_size as i32);
231 }
232
233 #[test]
234 fn test_hard_link_entry() {
235 let block_size = 4096u32;
236 let mut buf = Vec::new();
237 let mut left = block_size as i32;
238
239 write_dir_entry(
242 &mut buf, "link.txt", 99, file_mode::S_IFREG | 0o644,
243 Some(42), Some(file_mode::S_IFREG | 0o644),
244 block_size, &mut left,
245 ).unwrap();
246
247 finish_dir_entry_block(&mut buf, &mut left, block_size).unwrap();
248
249 let entries = parse_dir_entries(&buf);
250 assert_eq!(entries.len(), 1);
251 assert_eq!(entries[0], ("link.txt".to_string(), 42));
253 }
254
255 #[test]
256 fn test_parse_empty_block() {
257 let data = vec![0u8; 4096];
258 let entries = parse_dir_entries(&data);
259 assert!(entries.is_empty());
261 }
262}