use avrox_storage::fs::filesystem::{BlockNumber, FileSystemRead, FileSystemResult, FileSystemWrite};
use avr_oxide::io::{IoError, Read, Write};
use avr_oxide::util::datatypes::{BitField, BitFieldAccess, BitIndex};
use avrox_storage::{Seek, SeekFrom, FileAddr, FileOffset};
use avr_oxide::OxideResult::{Ok,Err};
#[cfg_attr(not(target_arch="avr"), derive(Debug))]
struct FileCursor {
block_number: BlockNumber,
offset: usize,
absolute: FileAddr
}
pub struct File<'f,FS>
where
FS: 'static + FileSystemRead
{
filesystem: &'f FS,
id: FS::FID,
cursor: FileCursor,
options: OpenOptions
}
unsafe impl<'f,FS> Send for File<'f,FS>
where
FS: 'static + FileSystemRead + Sync
{}
pub struct ReadDir<'rd,FS>
where
FS: 'static + FileSystemRead
{
filesystem: &'rd FS,
parent: Option<FS::FID>,
last_entry: Option<FS::FID>
}
#[derive(Clone)]
#[cfg_attr(not(target_arch="avr"), derive(Debug))]
pub struct OpenOptions(BitField);
const OO_READ: BitIndex = BitIndex::bit_c(0);
const OO_WRITE: BitIndex = BitIndex::bit_c(1);
const OO_APPEND: BitIndex = BitIndex::bit_c(2);
const OO_TRUNCATE: BitIndex = BitIndex::bit_c(3);
const OO_CREATE: BitIndex = BitIndex::bit_c(4);
const OO_CREATENEW: BitIndex = BitIndex::bit_c(5);
impl OpenOptions {
pub fn new() -> Self {
OpenOptions(BitField::all_clr())
}
pub fn read(&mut self, read: bool) -> &mut Self {
self.0.set_or_clr(OO_READ, read);
self
}
pub fn write(&mut self, write: bool) -> &mut Self {
self.0.set_or_clr(OO_WRITE, write);
self
}
pub fn append(&mut self, append: bool) -> &mut Self {
self.0.set_or_clr(OO_APPEND, append);
self
}
pub fn truncate(&mut self, truncate: bool) -> &mut Self {
self.0.set_or_clr(OO_TRUNCATE, truncate);
self
}
pub fn create(&mut self, create: bool) -> &mut Self {
self.0.set_or_clr(OO_CREATE, create);
self
}
pub fn create_new(&mut self, create_new: bool) -> &mut Self {
self.0.set_or_clr(OO_CREATENEW, create_new);
self
}
fn open_default() -> Self {
OpenOptions(BitField::with_bits_set(&[OO_READ]))
}
fn create_default() -> Self {
OpenOptions(BitField::with_bits_set(&[OO_WRITE,OO_TRUNCATE,OO_CREATE]))
}
fn check_sanity(&self) -> FileSystemResult<()> {
if self.0.is_set(OO_CREATE) || self.0.is_set(OO_CREATENEW) || self.0.is_set(OO_TRUNCATE) {
if !self.0.is_set(OO_WRITE) {
return Err(IoError::BadOptions);
}
}
if self.0.is_set(OO_APPEND) && self.0.is_set(OO_TRUNCATE) {
return Err(IoError::BadOptions);
}
Ok(())
}
fn check_allow_read(&self) -> FileSystemResult<()> {
match self.0.is_set(OO_READ) {
true => Ok(()),
false => Err(IoError::ReadProhibited)
}
}
fn check_allow_write(&self) -> FileSystemResult<()> {
match self.0.is_set(OO_WRITE) {
true => Ok(()),
false => Err(IoError::WriteProhibited)
}
}
}
impl<'f,FS> File<'f,FS>
where
FS: FileSystemRead
{
pub fn open_on<F: Into<&'f FS>, P: Into<FS::FID>>(fs: F, path: P) -> FileSystemResult<File<'f,FS>>{
Self::open(fs, path, &OpenOptions::open_default())
}
fn open<F: Into<&'f FS>, P: Into<FS::FID>>(fs: F, path: P, options: &OpenOptions) -> FileSystemResult<File<'f,FS>>{
let fs : &'f FS = fs.into();
let fid : FS::FID = path.into();
options.check_sanity()?;
if fs.check_exists(fid) {
let ( first_block, first_offset ) = fs.block_and_offset_for_file_location(fid, FileAddr::MIN)?;
Ok(File {
filesystem: fs,
id: fid,
cursor: FileCursor {
block_number: first_block,
offset: first_offset,
absolute: FileAddr::MIN
},
options: options.clone()
})
} else {
Err(IoError::NotFound)
}
}
pub fn len(&self) -> FileSystemResult<FileAddr> {
self.filesystem.get_file_size(self.id)
}
pub fn read_dir<F: Into<&'f FS>, P: Into<FS::FID>>(fs: F, path: P) -> FileSystemResult<ReadDir<'f,FS>> {
let fs : &'f FS = fs.into();
Ok(ReadDir {
filesystem: fs,
parent: Some(path.into()),
last_entry: None
})
}
}
impl<'f,FS> File<'f,FS>
where
FS: FileSystemWrite
{
pub fn create_on<F:Into<&'f FS>, P: Into<FS::FID>>(fs: F, path: P) -> FileSystemResult<File<'f,FS>>{
Self::open_or_create(fs,path,OpenOptions::create_default())
}
pub(crate) fn open_or_create<F:Into<&'f FS>, P: Into<FS::FID>>(fs: F, path: P, options: OpenOptions) -> FileSystemResult<File<'f,FS>>{
let fs : &'f FS = fs.into();
let fid : FS::FID = path.into();
options.check_sanity()?;
if !fs.check_exists(fid) {
if options.0.is_set(OO_CREATE) || options.0.is_set(OO_CREATENEW) {
fs.create_file(fid)?;
} else {
return Err(IoError::NotFound);
}
} else {
if options.0.is_set(OO_CREATENEW) {
return Err(IoError::Exists);
}
}
let ( absolute_pos, (start_block, start_offset)) =
if options.0.is_set(OO_APPEND) {
let end_pos = fs.get_file_size(fid)?;
(end_pos, fs.block_and_offset_for_file_location(fid, end_pos)?)
} else {
(FileAddr::MIN, fs.block_and_offset_for_file_location(fid, FileAddr::MIN)?)
};
if options.0.is_set(OO_TRUNCATE) {
fs.truncate_at(fid, start_block, start_offset)?;
}
Ok(File {
filesystem: fs,
id: fid,
cursor: FileCursor {
block_number: start_block,
offset: start_offset,
absolute: absolute_pos
},
options: options
})
}
pub fn sync_all(&self) -> FileSystemResult<()>{
self.filesystem.sync()
}
pub fn sync_data(&self) -> FileSystemResult<()>{
self.sync_all()
}
pub fn set_len(&self, size: FileAddr) -> FileSystemResult<()>{
let (block, offset) = self.filesystem.block_and_offset_for_file_location(self.id, size)?;
self.filesystem.truncate_at(self.id, block, offset)?;
Ok(())
}
}
impl<'f,FS> Seek for File<'f,FS>
where
FS: FileSystemWrite
{
fn seek(&mut self, pos: SeekFrom) -> avr_oxide::io::Result<FileAddr> {
let old_posn = self.cursor.absolute;
let max_posn = self.len()?;
let new_posn = match pos {
SeekFrom::Start(p) => p as FileAddr,
SeekFrom::End(p) =>{
if (p < 0) && (p.abs() as FileAddr > max_posn) {
return Err(IoError::OutOfRange)
}
((max_posn as FileOffset) + p) as FileAddr
},
SeekFrom::Current(p) => {
if (p < 0) && (p.abs() as FileAddr > old_posn) {
return Err(IoError::OutOfRange)
}
((old_posn as FileOffset) + p) as FileAddr
}
};
if new_posn == old_posn {
return Ok(new_posn);
}
if new_posn < old_posn &&
((new_posn / (FS::BLOCK_DATA_SIZE as FileAddr)) == (old_posn / (FS::BLOCK_DATA_SIZE as FileAddr))) {
self.cursor.absolute = new_posn as FileAddr;
self.cursor.offset -= (old_posn - new_posn) as usize;
return Ok(new_posn);
}
if new_posn > old_posn &&
((new_posn / (FS::BLOCK_DATA_SIZE as FileAddr)) == (old_posn / (FS::BLOCK_DATA_SIZE as FileAddr))) {
self.cursor.absolute = new_posn as FileAddr;
self.cursor.offset += (new_posn - old_posn) as usize;
return Ok(new_posn);
}
let (block, offset) = self.filesystem.block_and_offset_for_file_location(self.id, new_posn as FileAddr)?;
self.cursor = FileCursor {
block_number: block,
offset: offset,
absolute: new_posn as FileAddr
};
Ok(new_posn as FileAddr)
}
fn stream_position(&mut self) -> avr_oxide::io::Result<FileAddr> {
Ok(self.cursor.absolute)
}
}
impl<'f,FS> Read for File<'f,FS>
where
FS: FileSystemWrite
{
fn read(&mut self, buf: &mut [u8]) -> avr_oxide::io::Result<usize> {
self.options.check_allow_read()?;
let (bytes,(new_block,new_offset)) =
self.filesystem.read_from_location(self.id,
self.cursor.block_number,
self.cursor.offset,
buf)?;
if bytes > 0 {
self.cursor.block_number = new_block;
self.cursor.offset = new_offset;
self.cursor.absolute += bytes as FileAddr;
Ok(bytes)
} else {
Err(IoError::EndOfFile)
}
}
}
impl<'f,FS> Write for File<'f,FS>
where
FS: FileSystemWrite
{
fn flush(&mut self) -> avr_oxide::io::Result<()> {
self.filesystem.sync()
}
fn write_buffered(&mut self, buf: &[u8]) -> avr_oxide::io::Result<usize> {
self.options.check_allow_write()?;
let (bytes,(new_block,new_offset)) =
self.filesystem.write_at_location(self.id,
self.cursor.block_number,
self.cursor.offset,
buf)?;
if bytes > 0 {
self.cursor.block_number = new_block;
self.cursor.offset = new_offset;
self.cursor.absolute += bytes as FileAddr;
Ok(bytes)
} else {
Err(IoError::NoFreeSpace)
}
}
}
impl<'rd,FS> Iterator for ReadDir<'rd,FS>
where
FS: 'static + FileSystemRead {
type Item = FS::FID;
fn next(&mut self) -> Option<Self::Item> {
self.last_entry = self.filesystem.get_next_file_in_directory(self.parent, self.last_entry);
self.last_entry
}
}
#[cfg(test)]
mod tests {
use rand::rngs::SmallRng;
use rand::{Rng,SeedableRng};
use avrox_storage::fs::{File, FileSystemWrite, FileUid, OpenOptions, SnafusFileSystem};
use avrox_storage::fs::snafus::BLOCKSIZE_USABLE;
use avrox_storage::{FileAddr, PageBuffer, Seek, SeekFrom};
use avrox_storage::serprom::composite::tests::TestCompositeProm;
use avrox_storage::serprom::generic::dummy::DummyPromBusClient;
use avr_oxide::devices::serialbus::UsesSerialBusClient;
use avr_oxide::io::{Write, Read};
use avrox_storage::fs::filesystem::FileSystemRead;
type TestBuffer = PageBuffer<32,TestCompositeProm>;
#[test]
fn test_file_create() {
let mut test_fs = SnafusFileSystem::with_driver(TestBuffer::with_driver(TestCompositeProm::using_client(DummyPromBusClient::new())));
test_fs.format().unwrap();
let file = File::create_on(&test_fs, 0).unwrap();
assert_eq!(file.len().unwrap(), FileAddr::MIN);
}
#[test]
fn test_file_double_create() {
let mut test_fs = SnafusFileSystem::with_driver(TestBuffer::with_driver(TestCompositeProm::using_client(DummyPromBusClient::new())));
test_fs.format().unwrap();
let file = File::create_on(&test_fs, 0).unwrap();
let file = File::create_on(&test_fs, 0).unwrap();
}
#[test]
fn test_iterate_directory() {
let mut test_fs = SnafusFileSystem::with_driver(TestBuffer::with_driver(TestCompositeProm::using_client(DummyPromBusClient::new())));
test_fs.format().unwrap();
let mut file = File::create_on(&test_fs, 1).unwrap();
file.write_all(b"Hello, World!");
let mut file = File::create_on(&test_fs, 3).unwrap();
file.write_all(b"The quick brown fox jumped over the slow lazy dog");
let file = File::create_on(&test_fs, 4).unwrap();
let mut file = File::create_on(&test_fs, 19).unwrap();
file.write_all(b"Hold me closer, Tiny Dancer");
println!("Directory listing:");
for filename in File::read_dir(&test_fs, 0).unwrap() {
let file = File::open_on(&test_fs, filename).unwrap();
println!(" {:?} - {} bytes", filename, file.len().unwrap());
if filename == FileUid::with_id(1) {
assert_eq!(file.len().unwrap(), 13);
} else if filename == FileUid::with_id(3) {
assert_eq!(file.len().unwrap(), 49);
} else if filename == FileUid::with_id(4) {
assert_eq!(file.len().unwrap(), 0);
} else if filename == FileUid::with_id(19) {
assert_eq!(file.len().unwrap(), 27);
} else {
panic!("Filename {:?} should not exist", filename);
}
}
}
#[test]
#[should_panic]
fn test_file_open_fail() {
let mut test_fs = SnafusFileSystem::with_driver(TestBuffer::with_driver(TestCompositeProm::using_client(DummyPromBusClient::new())));
test_fs.format().unwrap();
let file = File::open_on(&test_fs, 0).unwrap();
}
#[test]
fn test_file_write_and_read() {
let mut test_fs = SnafusFileSystem::with_driver(TestBuffer::with_driver(TestCompositeProm::using_client(DummyPromBusClient::new())));
test_fs.format().unwrap();
let mut write_file = File::create_on(&test_fs, 5).unwrap();
let test_data = b"This is a nice string of test data";
write_file.write_all(test_data);
let mut buf = [0x00u8; 8];
let mut read_file = File::open_on(&test_fs, 5).unwrap();
read_file.read_exact(&mut buf).unwrap();
println!("Read back: {:?}", buf);
assert_eq!(buf, *b"This is ");
read_file.read_exact(&mut buf).unwrap();
println!("Read back: {:?}", buf);
assert_eq!(buf, *b"a nice s");
read_file.read_exact(&mut buf).unwrap();
println!("Read back: {:?}", buf);
assert_eq!(buf, *b"tring of");
}
#[test]
fn test_file_seek() {
let mut test_fs = SnafusFileSystem::with_driver(TestBuffer::with_driver(TestCompositeProm::using_client(DummyPromBusClient::new())));
test_fs.format().unwrap();
let mut write_file = File::create_on(&test_fs, 5).unwrap();
let test_data = [
0u8, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19,
20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36 ];
write_file.write_all(&test_data);
let mut buf = [0x00u8; 8];
let mut read_file = File::open_on(&test_fs, 5).unwrap();
read_file.read_exact(&mut buf).unwrap();
println!("No-Seek read back: {:?}", buf);
assert_eq!(buf, [0,1,2,3,4,5,6,7]);
read_file.seek(SeekFrom::Start(10));
read_file.read_exact(&mut buf).unwrap();
println!("Seek @ 10: {:?}", buf);
assert_eq!(buf, [10,11,12,13,14,15,16,17]);
read_file.seek(SeekFrom::Current(3));
read_file.read_exact(&mut buf).unwrap();
println!("Seek @ Current+3: {:?}", buf);
assert_eq!(buf, [21,22,23,24,25,26,27,28]);
read_file.seek(SeekFrom::End(-8));
read_file.read_exact(&mut buf).unwrap();
println!("Seek @ -8: {:?}", buf);
assert_eq!(buf, [29,30,31,32,33,34,35,36]);
}
#[test]
fn test_read_exact() {
let mut test_fs = SnafusFileSystem::with_driver(TestBuffer::with_driver(TestCompositeProm::using_client(DummyPromBusClient::new())));
test_fs.format().unwrap();
let mut write_file = File::create_on(&test_fs, 5).unwrap();
let test_data = b"Stood still on a highway, I saw a woman by the side of the road";
write_file.write_all(test_data);
let mut buf = [0x00u8; 63];
let mut read_file = File::open_on(&test_fs, 5).unwrap();
read_file.read_exact(&mut buf).unwrap();
assert_eq!(buf, *test_data);
println!("Test 1 OK");
write_file.rewind();
read_file.rewind();
let test_data = b"Stood still on a highway, I saw a woman by the side of the road; with a face that I knew like my own, reflected, in my window; Well she walked up to my quarterlight, and she bent down real slow... A fearful pressure, paralysed me, in my shadows. She said - Son, what are you doing here?";
write_file.write_all(test_data);
let mut buf = [0x00u8; 288];
let mut read_file = File::open_on(&test_fs, 5).unwrap();
read_file.read_exact(&mut buf).unwrap();
assert_eq!(buf, *test_data);
println!("Test 2 OK");
write_file.rewind();
read_file.rewind();
let test_data = [ 0xabu8; BLOCKSIZE_USABLE * 3 ];
write_file.write_all(&test_data);
let mut buf = [0x00u8; BLOCKSIZE_USABLE * 3];
let mut read_file = File::open_on(&test_fs, 5).unwrap();
read_file.read_exact(&mut buf).unwrap();
assert_eq!(buf, test_data);
println!("Test 3 OK");
}
#[test]
#[should_panic]
fn test_read_beyond_eof() {
let mut test_fs = SnafusFileSystem::with_driver(TestBuffer::with_driver(TestCompositeProm::using_client(DummyPromBusClient::new())));
test_fs.format().unwrap();
let mut write_file = File::create_on(&test_fs, 5).unwrap();
let test_data = b"This is a nice string of test data";
write_file.write_all(test_data);
let mut buf = [0x00u8; 128];
let mut read_file = File::open_on(&test_fs, 5).unwrap();
read_file.read_exact(&mut buf).unwrap();
}
#[test]
fn test_large_writes_and_read() {
const FILE1LEN :usize = 39129;
const FILE2LEN :usize = 16372;
let mut test_fs = SnafusFileSystem::with_driver(TestBuffer::with_driver(TestCompositeProm::using_client(DummyPromBusClient::new())));
test_fs.format().unwrap();
let mut small_rng = SmallRng::seed_from_u64(0xcc72535ae0c1cd97 );
let mut buffer1 = [0x00u8; FILE1LEN];
let mut buffer2 = [0x00u8; FILE2LEN];
for i in 0..buffer1.len() {
buffer1[i] = small_rng.gen_range(0..256) as u8;
}
for i in 0..buffer2.len() {
buffer2[i] = small_rng.gen_range(0..256) as u8;
}
let mut write_file1 = File::create_on(&test_fs, 0).unwrap();
let mut write_file2 = File::create_on(&test_fs, 1).unwrap();
let mut file1idx = 0usize;
let mut file2idx = 0usize;
while (file1idx < buffer1.len()) || (file2idx < buffer2.len()) {
let mut file1bytestowrite = small_rng.gen_range(20..500);
let mut file2bytestowrite = small_rng.gen_range(20..500);
if file1bytestowrite > (buffer1.len() - file1idx) {
file1bytestowrite = buffer1.len() - file1idx;
}
if file2bytestowrite > (buffer2.len() - file2idx) {
file2bytestowrite = buffer2.len() - file2idx;
}
if file1bytestowrite > 0 {
println!("Writing {} byte chunk to file1", file1bytestowrite);
write_file1.write_all(&buffer1[file1idx..(file1idx+file1bytestowrite)]).unwrap();
file1idx += file1bytestowrite;
}
if file2bytestowrite > 0 {
println!("Writing {} byte chunk to file2", file2bytestowrite);
write_file2.write_all(&buffer2[file2idx..(file2idx+file2bytestowrite)]).unwrap();
file2idx += file2bytestowrite;
}
}
let mut compare1 = [0x00u8; FILE1LEN];
let mut compare2 = [0x00u8; FILE2LEN];
let mut read_file1 = File::open_on(&test_fs, 0).unwrap();
read_file1.read_exact(&mut compare1).unwrap();
let mut read_file2 = File::open_on(&test_fs, 1).unwrap();
read_file2.read_exact(&mut compare2).unwrap();
println!("Free space: {}", test_fs.get_free_space().unwrap());
}
#[test]
fn test_oo_create_new() {
let mut test_fs = SnafusFileSystem::with_driver(TestBuffer::with_driver(TestCompositeProm::using_client(DummyPromBusClient::new())));
test_fs.format().unwrap();
assert!(test_fs.open(0, OpenOptions::new().write(true).create_new(true)).is_ok());
assert!(test_fs.open(0, OpenOptions::new().write(true).create_new(true)).is_err());
assert!(test_fs.open(0, OpenOptions::new().write(true).create(true)).is_ok());
}
#[test]
fn test_oo_open_append() {
let mut test_fs = SnafusFileSystem::with_driver(TestBuffer::with_driver(TestCompositeProm::using_client(DummyPromBusClient::new())));
test_fs.format().unwrap();
let buffer = [0xabu8; 273];
let mut file = test_fs.open(0, OpenOptions::new().write(true).create_new(true)).unwrap();
file.write_all(&buffer).unwrap();
assert_eq!(file.len().unwrap() as usize, buffer.len());
let mut appendfile = test_fs.open(0, OpenOptions::new().write(true).append(true)).unwrap();
appendfile.write_all(&buffer).unwrap();
assert_eq!(file.len().unwrap() as usize, buffer.len() * 2);
}
#[test]
fn test_oo_open_overwrite() {
let mut test_fs = SnafusFileSystem::with_driver(TestBuffer::with_driver(TestCompositeProm::using_client(DummyPromBusClient::new())));
test_fs.format().unwrap();
let shortbuffer = [0xabu8; 273];
let longbuffer = [0xabu8; 392];
let mut file = test_fs.open(0, OpenOptions::new().write(true).create_new(true)).unwrap();
file.write_all(&shortbuffer).unwrap();
assert_eq!(file.len().unwrap() as usize, shortbuffer.len());
let mut appendfile = test_fs.open(0, OpenOptions::new().write(true)).unwrap();
appendfile.write_all(&longbuffer).unwrap();
assert_eq!(file.len().unwrap() as usize, longbuffer.len());
}
#[test]
fn test_oo_open_truncate() {
let mut test_fs = SnafusFileSystem::with_driver(TestBuffer::with_driver(TestCompositeProm::using_client(DummyPromBusClient::new())));
test_fs.format().unwrap();
let shortbuffer = [0xabu8; 273];
let longbuffer = [0xabu8; 392];
let mut file = test_fs.open(0, OpenOptions::new().write(true).create_new(true)).unwrap();
file.write_all(&longbuffer).unwrap();
assert_eq!(file.len().unwrap() as usize, longbuffer.len());
let mut appendfile = test_fs.open(0, OpenOptions::new().write(true).truncate(true)).unwrap();
assert_eq!(file.len().unwrap() as usize, 0usize);
appendfile.write_all(&shortbuffer).unwrap();
assert_eq!(file.len().unwrap() as usize, shortbuffer.len());
}
#[test]
#[should_panic]
fn test_oo_open_readonly() {
let mut test_fs = SnafusFileSystem::with_driver(TestBuffer::with_driver(TestCompositeProm::using_client(DummyPromBusClient::new())));
test_fs.format().unwrap();
let shortbuffer = [0xabu8; 273];
let longbuffer = [0xabu8; 392];
let mut file = test_fs.open(0, OpenOptions::new().write(true).create_new(true)).unwrap();
file.write_all(&longbuffer).unwrap();
assert_eq!(file.len().unwrap() as usize, longbuffer.len());
let mut appendfile = test_fs.open(0, OpenOptions::new().read(true)).unwrap();
appendfile.write_all(&shortbuffer).unwrap();
}
#[test]
#[should_panic]
fn test_oo_open_writeonly() {
let mut test_fs = SnafusFileSystem::with_driver(TestBuffer::with_driver(TestCompositeProm::using_client(DummyPromBusClient::new())));
test_fs.format().unwrap();
let mut shortbuffer = [0xabu8; 273];
let longbuffer = [0xabu8; 392];
let mut file = test_fs.open(0, OpenOptions::new().write(true).create_new(true)).unwrap();
file.write_all(&longbuffer).unwrap();
assert_eq!(file.len().unwrap() as usize, longbuffer.len());
let mut readfile = test_fs.open(0, OpenOptions::new().write(true)).unwrap();
readfile.read_exact(&mut shortbuffer).unwrap();
}
}