use crate::{
error::Error,
runtime::types::{FdFlags, FdStat, Whence},
storage::{
types::{FileChunkIndex, FileSize, FileType, Node, FILE_CHUNK_SIZE},
Storage,
},
};
#[derive(Clone, Debug)]
pub struct File {
pub node: Node,
pub cursor: FileSize,
pub stat: FdStat,
}
impl File {
pub fn new(node: Node, stat: FdStat, storage: &dyn Storage) -> Result<Self, Error> {
let metadata = storage.get_metadata(node)?;
let file_type = metadata.file_type;
match file_type {
FileType::RegularFile => {}
FileType::Directory => {
unreachable!("Unexpected file type, expected a regular file.");
}
FileType::SymbolicLink => unimplemented!("Symbolic links are not implemented yet"),
};
let cursor = if stat.flags.contains(FdFlags::APPEND) {
metadata.size
} else {
0
};
Ok(Self { node, cursor, stat })
}
pub fn seek(
&mut self,
delta: i64,
whence: Whence,
storage: &dyn Storage,
) -> Result<FileSize, Error> {
let size = storage.get_metadata(self.node)?.size;
let position = match whence {
Whence::SET => {
if delta < 0 {
return Err(Error::InvalidOffset);
}
delta as FileSize
}
Whence::CUR => {
let back = if delta < 0 {
(-delta).try_into().map_err(|_| Error::InvalidOffset)?
} else {
0
};
let fwd = if delta >= 0 { delta as FileSize } else { 0 };
if back > self.cursor {
return Err(Error::InvalidOffset);
}
self.cursor + fwd - back
}
Whence::END => {
let back: FileSize = (-delta).try_into().map_err(|_| Error::InvalidOffset)?;
if back > size {
return Err(Error::InvalidOffset);
}
size - back
}
};
self.cursor = position;
Ok(self.cursor)
}
pub fn tell(&self) -> FileSize {
self.cursor
}
pub fn read_with_cursor(
&mut self,
buf: &mut [u8],
storage: &mut dyn Storage,
) -> Result<FileSize, Error> {
let read_size = self.read_with_offset(self.cursor, buf, storage)?;
self.cursor += read_size;
Ok(read_size)
}
pub fn write_with_cursor(
&mut self,
buf: &[u8],
storage: &mut dyn Storage,
) -> Result<FileSize, Error> {
let written_size = self.write_with_offset(self.cursor, buf, storage)?;
self.cursor += written_size;
Ok(written_size)
}
pub fn read_with_offset(
&self,
offset: FileSize,
buf: &mut [u8],
storage: &mut dyn Storage,
) -> Result<FileSize, Error> {
if buf.is_empty() {
return Ok(0 as FileSize);
}
let file_size = storage.get_metadata(self.node)?.size;
let end = (offset + buf.len() as FileSize).min(file_size);
let chunk_infos = get_chunk_infos(offset, end);
let mut read_size = 0;
for chunk in chunk_infos.into_iter() {
storage.read_filechunk(
self.node,
chunk.index,
chunk.offset,
&mut buf[read_size..read_size + chunk.len as usize],
)?;
read_size += chunk.len as usize;
}
Ok(read_size as FileSize)
}
pub fn write_with_offset(
&self,
offset: FileSize,
buf: &[u8],
storage: &mut dyn Storage,
) -> Result<FileSize, Error> {
let mut metadata = storage.get_metadata(self.node)?;
let end = offset + buf.len() as FileSize;
let chunk_infos = get_chunk_infos(offset, end);
let mut written_size = 0;
for chunk in chunk_infos.into_iter() {
storage.write_filechunk(
self.node,
chunk.index,
chunk.offset,
&buf[written_size..written_size + chunk.len as usize],
);
written_size += chunk.len as usize;
}
if end > metadata.size {
metadata.size = end;
storage.put_metadata(self.node, metadata)
}
Ok(written_size as FileSize)
}
pub fn truncate(&self, storage: &mut dyn Storage) -> Result<(), Error> {
let mut metadata = storage.get_metadata(self.node)?;
metadata.size = 0;
storage.put_metadata(self.node, metadata);
Ok(())
}
}
#[derive(Debug, PartialEq, Eq)]
struct ChunkHandle {
index: FileChunkIndex,
offset: FileSize,
len: FileSize,
}
fn offset_to_file_chunk_index(offset: FileSize) -> FileChunkIndex {
(offset / FILE_CHUNK_SIZE as FileSize) as FileChunkIndex
}
fn file_chunk_index_to_offset(index: FileChunkIndex) -> FileSize {
index as FileSize * FILE_CHUNK_SIZE as FileSize
}
fn get_chunk_infos(start: FileSize, end: FileSize) -> Vec<ChunkHandle> {
let mut result = vec![];
let start_index = offset_to_file_chunk_index(start);
let end_index = offset_to_file_chunk_index(end);
for index in start_index..=end_index {
let start_of_chunk = file_chunk_index_to_offset(index);
assert!(start_of_chunk <= end);
let start_in_chunk = start_of_chunk.max(start) - start_of_chunk;
let end_in_chunk = (start_of_chunk + FILE_CHUNK_SIZE as FileSize).min(end) - start_of_chunk;
if start_in_chunk < end_in_chunk {
result.push(ChunkHandle {
index,
offset: start_in_chunk,
len: end_in_chunk - start_in_chunk,
});
}
}
result
}
#[cfg(test)]
mod tests {
use crate::test_utils::test_fs;
use super::*;
#[test]
fn get_chunk_infos_parital() {
let chunks = get_chunk_infos(
FILE_CHUNK_SIZE as FileSize - 1,
2 * FILE_CHUNK_SIZE as FileSize + 1,
);
assert_eq!(
chunks[0],
ChunkHandle {
index: 0,
offset: FILE_CHUNK_SIZE as FileSize - 1,
len: 1
}
);
assert_eq!(
chunks[1],
ChunkHandle {
index: 1,
offset: 0,
len: FILE_CHUNK_SIZE as FileSize,
}
);
assert_eq!(
chunks[2],
ChunkHandle {
index: 2,
offset: 0,
len: 1,
}
);
}
#[test]
fn get_chunk_infos_full() {
let chunks = get_chunk_infos(0, 10 * FILE_CHUNK_SIZE as FileSize);
#[allow(clippy::needless_range_loop)]
for i in 0..10 {
assert_eq!(
chunks[i],
ChunkHandle {
index: i as FileChunkIndex,
offset: 0,
len: FILE_CHUNK_SIZE as FileSize,
}
);
}
}
#[test]
fn seek_and_tell() {
let mut fs = test_fs();
let fd = fs
.create_file(fs.root_fd(), "test", FdStat::default(), 0)
.unwrap();
let mut file = fs.get_test_file(fd);
let storage = fs.get_test_storage();
file.write_with_offset(0, &[0; 1000], storage).unwrap();
assert_eq!(file.tell(), 0);
let pos = file.seek(10, Whence::CUR, storage).unwrap();
assert_eq!(pos, 10);
assert_eq!(file.tell(), 10);
let pos = file.seek(-9, Whence::CUR, storage).unwrap();
assert_eq!(pos, 1);
assert_eq!(file.tell(), 1);
let err = file.seek(-2, Whence::CUR, storage).unwrap_err();
assert_eq!(err, Error::InvalidOffset);
assert_eq!(file.tell(), 1);
let pos = file.seek(0, Whence::END, storage).unwrap();
assert_eq!(pos, 1000);
assert_eq!(file.tell(), 1000);
let pos = file.seek(500, Whence::SET, storage).unwrap();
assert_eq!(pos, 500);
assert_eq!(file.tell(), 500);
let err = file.seek(-1, Whence::SET, storage).unwrap_err();
assert_eq!(err, Error::InvalidOffset);
assert_eq!(file.tell(), 500);
let pos = file.seek(1001, Whence::SET, storage).unwrap();
assert_eq!(pos, 1001);
assert_eq!(file.tell(), 1001);
}
#[test]
fn read_and_write_cursor() {
let mut fs = test_fs();
let fd = fs
.create_file(fs.root_fd(), "test", FdStat::default(), 0)
.unwrap();
let mut file = fs.get_test_file(fd);
let storage = fs.get_test_storage();
for i in 0..1000 {
let buf = [(i % 256) as u8; 16];
file.write_with_cursor(&buf, storage).unwrap();
}
file.seek(-1000 * 16, Whence::END, storage).unwrap();
for i in 0..1000 {
let mut buf = [0; 16];
file.read_with_cursor(&mut buf, storage).unwrap();
let expected = [(i % 256) as u8; 16];
assert_eq!(buf, expected);
}
}
#[test]
fn read_and_write_offset() {
let mut fs = test_fs();
let fd = fs
.create_file(fs.root_fd(), "test", FdStat::default(), 0)
.unwrap();
let mut file = fs.get_test_file(fd);
let storage = fs.get_test_storage();
for i in 0..1000 {
let buf = [(i % 256) as u8; 16];
file.write_with_offset(i * 16, &buf, storage).unwrap();
}
file.seek(-1000 * 16, Whence::END, storage).unwrap();
for i in 0..1000 {
let mut buf = [0; 16];
file.read_with_offset(i * 16, &mut buf, storage).unwrap();
let expected = [(i % 256) as u8; 16];
assert_eq!(buf, expected);
}
}
}