use crate::constants::*;
use crate::types::*;
use std::io::{self, Write};
#[inline]
fn align_up(n: usize, align: usize) -> usize {
(n + align - 1) & !(align - 1)
}
fn dir_file_type(mode: u16) -> u8 {
FileType::from_mode(mode) as u8
}
pub fn write_dir_entry<W: Write>(
writer: &mut W,
name: &str,
inode: u32,
mode: u16,
link_inode: Option<u32>,
link_mode: Option<u16>,
block_size: u32,
left: &mut i32,
) -> io::Result<()> {
let name_bytes = name.as_bytes();
let entry_size = align_up(DirectoryEntry::SIZE + name_bytes.len(), 4);
let min_trailing = 12;
if (*left as usize) < entry_size + min_trailing {
finish_dir_entry_block(writer, left, block_size)?;
}
let actual_inode = link_inode.unwrap_or(inode);
let actual_mode = link_mode.unwrap_or(mode);
let entry = DirectoryEntry {
inode: actual_inode,
rec_len: entry_size as u16,
name_len: name_bytes.len() as u8,
file_type: dir_file_type(actual_mode),
};
let mut header_buf = [0u8; DirectoryEntry::SIZE];
entry.write_to(&mut header_buf);
writer.write_all(&header_buf)?;
writer.write_all(name_bytes)?;
let padding = entry_size - DirectoryEntry::SIZE - name_bytes.len();
if padding > 0 {
let zeros = [0u8; 4];
writer.write_all(&zeros[..padding])?;
}
*left -= entry_size as i32;
Ok(())
}
pub fn finish_dir_entry_block<W: Write>(
writer: &mut W,
left: &mut i32,
block_size: u32,
) -> io::Result<()> {
if *left <= 0 {
*left = block_size as i32;
return Ok(());
}
let remaining = *left as usize;
let term = DirectoryEntry {
inode: 0,
rec_len: remaining as u16,
name_len: 0,
file_type: 0,
};
let mut header_buf = [0u8; DirectoryEntry::SIZE];
term.write_to(&mut header_buf);
writer.write_all(&header_buf)?;
let fill = remaining - DirectoryEntry::SIZE;
if fill > 0 {
let zeros = vec![0u8; fill];
writer.write_all(&zeros)?;
}
*left = block_size as i32;
Ok(())
}
pub fn parse_dir_entries(data: &[u8]) -> Vec<(String, u32)> {
let mut entries = Vec::new();
let mut offset = 0;
while offset + DirectoryEntry::SIZE <= data.len() {
let entry = DirectoryEntry::read_from(&data[offset..]);
if entry.rec_len == 0 {
break;
}
if entry.inode != 0 && entry.name_len > 0 {
let name_start = offset + DirectoryEntry::SIZE;
let name_end = name_start + entry.name_len as usize;
if name_end <= data.len() {
let name = String::from_utf8_lossy(&data[name_start..name_end]).into_owned();
entries.push((name, entry.inode));
}
}
offset += entry.rec_len as usize;
}
entries
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_align_up() {
assert_eq!(align_up(0, 4), 0);
assert_eq!(align_up(1, 4), 4);
assert_eq!(align_up(4, 4), 4);
assert_eq!(align_up(5, 4), 8);
assert_eq!(align_up(8, 4), 8);
assert_eq!(align_up(9, 4), 12);
}
#[test]
fn test_write_and_parse_dir_entries() {
let block_size = 4096u32;
let mut buf = Vec::new();
let mut left = block_size as i32;
write_dir_entry(
&mut buf, ".", 2, file_mode::S_IFDIR | 0o755,
None, None, block_size, &mut left,
).unwrap();
write_dir_entry(
&mut buf, "..", 2, file_mode::S_IFDIR | 0o755,
None, None, block_size, &mut left,
).unwrap();
write_dir_entry(
&mut buf, "hello.txt", 11, file_mode::S_IFREG | 0o644,
None, None, block_size, &mut left,
).unwrap();
finish_dir_entry_block(&mut buf, &mut left, block_size).unwrap();
assert_eq!(buf.len(), block_size as usize);
assert_eq!(left, block_size as i32);
let entries = parse_dir_entries(&buf);
assert_eq!(entries.len(), 3);
assert_eq!(entries[0], (".".to_string(), 2));
assert_eq!(entries[1], ("..".to_string(), 2));
assert_eq!(entries[2], ("hello.txt".to_string(), 11));
}
#[test]
fn test_finish_dir_entry_block_at_boundary() {
let block_size = 4096u32;
let mut buf = Vec::new();
let mut left = 0i32;
finish_dir_entry_block(&mut buf, &mut left, block_size).unwrap();
assert_eq!(buf.len(), 0);
assert_eq!(left, block_size as i32);
}
#[test]
fn test_hard_link_entry() {
let block_size = 4096u32;
let mut buf = Vec::new();
let mut left = block_size as i32;
write_dir_entry(
&mut buf, "link.txt", 99, file_mode::S_IFREG | 0o644,
Some(42), Some(file_mode::S_IFREG | 0o644),
block_size, &mut left,
).unwrap();
finish_dir_entry_block(&mut buf, &mut left, block_size).unwrap();
let entries = parse_dir_entries(&buf);
assert_eq!(entries.len(), 1);
assert_eq!(entries[0], ("link.txt".to_string(), 42));
}
#[test]
fn test_parse_empty_block() {
let data = vec![0u8; 4096];
let entries = parse_dir_entries(&data);
assert!(entries.is_empty());
}
}