use crate::inner::disk::Disk;
use crate::IoResult;
use super::TypePerm;
use libc::c_char;
use nix::errno::Errno;
use nix::sys::stat::SFlag;
use std::convert::{TryFrom, TryInto};
use std::fmt;
use std::io::{Read, Seek, Write};
use std::mem::size_of;
const FILENAME_MAX: usize = 255;
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
#[repr(packed)]
pub struct DirectoryEntryHeader {
pub inode: u32,
pub size: u16,
pub name_length: u8,
pub type_indicator: DirectoryEntryType,
}
#[derive(Copy, Clone, PartialEq, Eq)]
#[repr(packed)]
pub struct DirectoryEntry {
pub header: DirectoryEntryHeader,
pub filename: Filename,
}
impl fmt::Debug for DirectoryEntry {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(
f,
"filename: {:?}\nheader: {:#?}",
unsafe { self.get_filename() },
self.header
)
}
}
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
#[repr(u8)]
pub enum DirectoryEntryType {
RegularFile = 1,
Directory,
CharacterDevice,
BlockDevice,
Fifo,
Socket,
SymbolicLink,
}
impl TryFrom<TypePerm> for DirectoryEntryType {
type Error = Errno;
fn try_from(file_type: TypePerm) -> Result<Self, Self::Error> {
Ok(match file_type.extract_type() {
SFlag::S_IFSOCK => DirectoryEntryType::Socket,
SFlag::S_IFLNK => DirectoryEntryType::SymbolicLink,
SFlag::S_IFREG => DirectoryEntryType::RegularFile,
SFlag::S_IFBLK => DirectoryEntryType::BlockDevice,
SFlag::S_IFDIR => DirectoryEntryType::Directory,
SFlag::S_IFCHR => DirectoryEntryType::CharacterDevice,
SFlag::S_IFIFO => DirectoryEntryType::Fifo,
_ => Err(Errno::EINVAL)?,
})
}
}
impl DirectoryEntry {
pub fn new(filename: &str, type_indicator: DirectoryEntryType, inode: u32) -> IoResult<Self> {
Ok(Self {
header: DirectoryEntryHeader {
inode,
size: size_of::<DirectoryEntry>() as u16,
name_length: filename.len() as u8,
type_indicator,
},
filename: filename.try_into()?,
})
}
pub fn set_filename(&mut self, filename: &str) -> IoResult<()> {
let filenamelen = filename.len();
assert!(filenamelen <= FILENAME_MAX as usize);
self.filename = filename.try_into()?;
self.header.name_length = filenamelen as u8;
Ok(())
}
pub unsafe fn get_filename(&self) -> &str {
let slice: &[u8] = std::slice::from_raw_parts(
&self.filename.0 as *const c_char as *const u8,
self.header.name_length as usize,
);
std::str::from_utf8_unchecked(slice)
}
pub fn get_inode(&self) -> u32 {
self.header.inode
}
pub fn get_size(&self) -> u16 {
self.header.size
}
pub fn size(&self) -> u16 {
self.header.name_length as u16 + size_of::<DirectoryEntryHeader>() as u16
}
pub fn set_size(&mut self, new_size: u16) {
self.header.size = new_size;
}
pub fn write_on_disk<T>(&self, addr: u64, disk: &mut Disk<T>) -> IoResult<u64>
where
T: Read + Write + Seek,
{
disk.write_struct(addr, &self.header)?;
disk.write_buffer(addr + size_of::<DirectoryEntryHeader>() as u64, unsafe {
self.get_filename().as_bytes()
})
}
}
#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd)]
#[repr(transparent)]
pub struct Filename(pub [c_char; FILENAME_MAX as usize + 1]);
impl TryFrom<&str> for Filename {
type Error = Errno;
fn try_from(s: &str) -> Result<Self, Errno> {
let mut n = [0; FILENAME_MAX as usize + 1];
if s.len() > FILENAME_MAX as usize {
return Err(Errno::ENAMETOOLONG);
} else if s.len() == 0 {
return Err(Errno::ENOTEMPTY);
} else {
for (n, c) in n.iter_mut().zip(s.bytes()) {
if c == '/' as u8 {
return Err(Errno::ENOSTR);
}
*n = c as c_char;
}
Ok(Self(n))
}
}
}
impl Default for Filename {
fn default() -> Self {
Self([0; FILENAME_MAX as usize + 1])
}
}
impl fmt::Debug for Filename {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
unsafe {
let slice: &[u8] = std::slice::from_raw_parts(
&self.0 as *const c_char as *const u8,
FILENAME_MAX as usize,
);
let s = std::str::from_utf8_unchecked(slice);
write!(f, "{:?}", s)
}
}
}
#[cfg(test)]
mod test {
use super::*;
macro_rules! make_test {
(pass, $test_name: ident, $body: tt) => {
#[test]
fn $test_name() {
$body
}
};
(fail, $test_name: ident, $body: tt) => {
#[test]
#[should_panic]
fn $test_name() {
$body
}
};
}
macro_rules! make_filename_creation_test {
($body: block, $test_name: ident) => {
make_test! {pass, $test_name, {
Filename::try_from($body.as_str()).unwrap();
}
}
};
(fail, $body: block, $test_name: ident) => {
make_test! {fail, $test_name, {
Filename::try_from($body.as_str()).unwrap();
}
}
};
($filename: expr, $test_name: ident) => {
make_test! {pass, $test_name, {
Filename::try_from($filename).unwrap();
}
}
};
(fail, $filename: expr, $test_name: ident) => {
make_test! {fail, $test_name, {
Filename::try_from($filename).unwrap();
}
}
};
}
make_filename_creation_test! {fail, {
let make_component = |count: usize| {
let mut s = String::new();
for _ in 0..count {
s.push_str("a");
}
s
};
make_component(0)
}, test_filename_posix_filename_cant_be_zero_len
}
make_filename_creation_test! {fail, {
let make_component = |count: usize| {
let mut s = String::new();
for _ in 0..count {
s.push_str("a");
}
s
};
make_component(FILENAME_MAX + 1)
}, test_filename_posix_filename_cant_be_greater_than_name_max
}
make_filename_creation_test! {fail, {
use std::str::FromStr;
String::from_str("aaa/bbb.txt").expect("This should never happened") }, test_filename_posix_filename_cant_be_have_slash
}
make_filename_creation_test! {{
let make_component = |count: usize| {
let mut s = String::new();
for _ in 0..count {
s.push_str("a");
}
s
};
make_component(FILENAME_MAX)
}, test_filename_posix_filename_can_be_name_max
}
make_filename_creation_test! {{
let make_component = |count: usize| {
let mut s = String::new();
for _ in 0..count {
s.push_str("a");
}
s
};
make_component(1)
}, test_filename_posix_filename_can_be_one
}
}