Basic write support for files.
No cluster management yet. Also BufStream is broken when writing.
This commit is contained in:
parent
7b967914a6
commit
f32f1c7279
1
.gitignore
vendored
1
.gitignore
vendored
@ -1 +1,2 @@
|
||||
target
|
||||
tmp
|
||||
|
@ -2,15 +2,14 @@ extern crate fatfs;
|
||||
|
||||
use std::env;
|
||||
use std::fs::File;
|
||||
use std::io::BufReader;
|
||||
use std::io::prelude::*;
|
||||
use std::str;
|
||||
|
||||
use fatfs::FileSystem;
|
||||
use fatfs::{FileSystem, BufStream};
|
||||
|
||||
fn main() {
|
||||
let file = File::open("resources/fat32.img").unwrap();
|
||||
let mut buf_rdr = BufReader::new(file);
|
||||
let mut buf_rdr = BufStream::new(file);
|
||||
let fs = FileSystem::new(&mut buf_rdr).unwrap();
|
||||
let mut root_dir = fs.root_dir();
|
||||
let mut file = root_dir.open_file(&env::args().nth(1).unwrap()).unwrap();
|
||||
|
@ -3,10 +3,9 @@ extern crate chrono;
|
||||
|
||||
use std::env;
|
||||
use std::fs::File;
|
||||
use std::io::BufReader;
|
||||
use chrono::{DateTime, Local};
|
||||
|
||||
use fatfs::FileSystem;
|
||||
use fatfs::{FileSystem, BufStream};
|
||||
|
||||
fn format_file_size(size: u64) -> String {
|
||||
const KB: u64 = 1024;
|
||||
@ -25,7 +24,7 @@ fn format_file_size(size: u64) -> String {
|
||||
|
||||
fn main() {
|
||||
let file = File::open("resources/fat32.img").unwrap();
|
||||
let mut buf_rdr = BufReader::new(file);
|
||||
let mut buf_rdr = BufStream::new(file);
|
||||
let fs = FileSystem::new(&mut buf_rdr).unwrap();
|
||||
let mut root_dir = fs.root_dir();
|
||||
let dir = match env::args().nth(1) {
|
||||
|
112
src/dir.rs
112
src/dir.rs
@ -3,7 +3,7 @@ use std::fmt;
|
||||
use std::io::prelude::*;
|
||||
use std::io;
|
||||
use std::io::{Cursor, ErrorKind, SeekFrom};
|
||||
use byteorder::{LittleEndian, ReadBytesExt};
|
||||
use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt};
|
||||
|
||||
#[cfg(feature = "chrono")]
|
||||
use chrono::{TimeZone, Local};
|
||||
@ -19,6 +19,15 @@ pub(crate) enum DirRawStream<'a, 'b: 'a> {
|
||||
Root(DiskSlice<'a, 'b>),
|
||||
}
|
||||
|
||||
impl <'a, 'b> DirRawStream<'a, 'b> {
|
||||
pub(crate) fn global_pos(&self) -> Option<u64> {
|
||||
match self {
|
||||
&DirRawStream::File(ref file) => file.global_pos(),
|
||||
&DirRawStream::Root(ref slice) => Some(slice.global_pos()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl <'a, 'b> Read for DirRawStream<'a, 'b> {
|
||||
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
|
||||
match self {
|
||||
@ -53,7 +62,7 @@ bitflags! {
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[derive(Clone, Debug, Default)]
|
||||
struct DirFileEntryData {
|
||||
pub(crate) struct DirFileEntryData {
|
||||
name: [u8; 11],
|
||||
attrs: FileAttributes,
|
||||
reserved_0: u8,
|
||||
@ -65,7 +74,47 @@ struct DirFileEntryData {
|
||||
modify_time: u16,
|
||||
modify_date: u16,
|
||||
first_cluster_lo: u16,
|
||||
size: u32,
|
||||
pub(crate) size: u32,
|
||||
}
|
||||
|
||||
impl DirFileEntryData {
|
||||
pub(crate) fn first_cluster(&self) -> Option<u32> {
|
||||
let n = ((self.first_cluster_hi as u32) << 16) | self.first_cluster_lo as u32;
|
||||
if n == 0 { None } else { Some(n) }
|
||||
}
|
||||
|
||||
pub(crate) fn size(&self) -> u32 {
|
||||
self.size
|
||||
}
|
||||
|
||||
pub fn is_dir(&self) -> bool {
|
||||
self.attrs.contains(FileAttributes::DIRECTORY)
|
||||
}
|
||||
|
||||
pub fn is_file(&self) -> bool {
|
||||
!self.is_dir()
|
||||
}
|
||||
|
||||
pub(crate) fn set_modified(&mut self, date_time: DateTime) {
|
||||
self.modify_date = date_time.date.to_u16();
|
||||
self.modify_time = date_time.time.to_u16();
|
||||
}
|
||||
|
||||
pub(crate) fn serialize(&self, wrt: &mut Write) -> io::Result<()> {
|
||||
wrt.write(&self.name)?;
|
||||
wrt.write_u8(self.attrs.bits())?;
|
||||
wrt.write_u8(self.reserved_0)?;
|
||||
wrt.write_u8(self.create_time_0)?;
|
||||
wrt.write_u16::<LittleEndian>(self.create_time_1)?;
|
||||
wrt.write_u16::<LittleEndian>(self.create_date)?;
|
||||
wrt.write_u16::<LittleEndian>(self.access_date)?;
|
||||
wrt.write_u16::<LittleEndian>(self.first_cluster_hi)?;
|
||||
wrt.write_u16::<LittleEndian>(self.modify_time)?;
|
||||
wrt.write_u16::<LittleEndian>(self.modify_date)?;
|
||||
wrt.write_u16::<LittleEndian>(self.first_cluster_lo)?;
|
||||
wrt.write_u32::<LittleEndian>(self.size)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
@ -87,13 +136,6 @@ enum DirEntryData {
|
||||
Lfn(DirLfnEntryData),
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct DirEntry<'a, 'b: 'a> {
|
||||
data: DirFileEntryData,
|
||||
lfn: Vec<u16>,
|
||||
fs: FileSystemRef<'a, 'b>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct Date {
|
||||
pub year: u16,
|
||||
@ -106,6 +148,10 @@ impl Date {
|
||||
let (year, month, day) = ((dos_date >> 9) + 1980, (dos_date >> 5) & 0xF, dos_date & 0x1F);
|
||||
Date { year, month, day }
|
||||
}
|
||||
|
||||
fn to_u16(&self) -> u16 {
|
||||
((self.year - 1980) << 9) | (self.month << 5) | self.day
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
@ -120,6 +166,10 @@ impl Time {
|
||||
let (hour, min, sec) = (dos_time >> 11, (dos_time >> 5) & 0x3F, (dos_time & 0x1F) * 2);
|
||||
Time { hour, min, sec }
|
||||
}
|
||||
|
||||
fn to_u16(&self) -> u16 {
|
||||
(self.hour << 11) | (self.min << 5) | (self.sec / 2)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
@ -152,6 +202,28 @@ impl From<DateTime> for chrono::DateTime<Local> {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub(crate) struct FileEntryInfo {
|
||||
pub(crate) data: DirFileEntryData,
|
||||
pos: u64,
|
||||
}
|
||||
|
||||
impl FileEntryInfo {
|
||||
pub(crate) fn write(&self, fs: FileSystemRef) -> io::Result<()> {
|
||||
let mut disk = fs.disk.borrow_mut();
|
||||
disk.seek(io::SeekFrom::Start(self.pos))?;
|
||||
self.data.serialize(&mut *disk)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct DirEntry<'a, 'b: 'a> {
|
||||
data: DirFileEntryData,
|
||||
lfn: Vec<u16>,
|
||||
entry_pos: u64,
|
||||
fs: FileSystemRef<'a, 'b>,
|
||||
}
|
||||
|
||||
impl <'a, 'b> DirEntry<'a, 'b> {
|
||||
pub fn short_file_name(&self) -> String {
|
||||
let name_str = String::from_utf8_lossy(&self.data.name[0..8]);
|
||||
@ -186,20 +258,26 @@ impl <'a, 'b> DirEntry<'a, 'b> {
|
||||
}
|
||||
|
||||
pub(crate) fn first_cluster(&self) -> Option<u32> {
|
||||
let n = ((self.data.first_cluster_hi as u32) << 16) | self.data.first_cluster_lo as u32;
|
||||
if n == 0 { None } else { Some(n) }
|
||||
self.data.first_cluster()
|
||||
}
|
||||
|
||||
fn entry_info(&self) -> FileEntryInfo {
|
||||
FileEntryInfo {
|
||||
data: self.data.clone(),
|
||||
pos: self.entry_pos,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_file(&self) -> File<'a, 'b> {
|
||||
assert!(!self.is_dir(), "Not a file entry");
|
||||
File::new(self.first_cluster(), Some(self.data.size), self.fs)
|
||||
File::new(self.first_cluster(), Some(self.entry_info()), self.fs)
|
||||
}
|
||||
|
||||
pub fn to_dir(&self) -> Dir<'a, 'b> {
|
||||
assert!(self.is_dir(), "Not a directory entry");
|
||||
match self.first_cluster() {
|
||||
Some(n) => {
|
||||
let file = File::new(Some(n), None, self.fs);
|
||||
let file = File::new(Some(n), Some(self.entry_info()), self.fs);
|
||||
Dir::new(DirRawStream::File(file), self.fs)
|
||||
},
|
||||
None => self.fs.root_dir(),
|
||||
@ -294,8 +372,10 @@ pub struct DirIter<'a, 'b: 'a> {
|
||||
|
||||
impl <'a, 'b> DirIter<'a, 'b> {
|
||||
fn read_dir_entry_data(&mut self) -> io::Result<DirEntryData> {
|
||||
println!("read_dir_entry_data");
|
||||
let mut name = [0; 11];
|
||||
self.rdr.read_exact(&mut name)?;
|
||||
println!("read_dir_entry_data {:?}", &name);
|
||||
let attrs = FileAttributes::from_bits_truncate(self.rdr.read_u8()?);
|
||||
if attrs == FileAttributes::LFN {
|
||||
let mut data = DirLfnEntryData {
|
||||
@ -309,6 +389,7 @@ impl <'a, 'b> DirIter<'a, 'b> {
|
||||
self.rdr.read_u16_into::<LittleEndian>(&mut data.name_1)?;
|
||||
data.reserved_0 = self.rdr.read_u16::<LittleEndian>()?;
|
||||
self.rdr.read_u16_into::<LittleEndian>(&mut data.name_2)?;
|
||||
println!("read_dir_entry_data end");
|
||||
Ok(DirEntryData::Lfn(data))
|
||||
} else {
|
||||
let data = DirFileEntryData {
|
||||
@ -325,6 +406,7 @@ impl <'a, 'b> DirIter<'a, 'b> {
|
||||
first_cluster_lo: self.rdr.read_u16::<LittleEndian>()?,
|
||||
size: self.rdr.read_u32::<LittleEndian>()?,
|
||||
};
|
||||
println!("read_dir_entry_data end");
|
||||
Ok(DirEntryData::File(data))
|
||||
}
|
||||
}
|
||||
@ -339,6 +421,7 @@ impl <'a, 'b> Iterator for DirIter<'a, 'b> {
|
||||
}
|
||||
let mut lfn_buf = Vec::<u16>::new();
|
||||
loop {
|
||||
let entry_pos = self.rdr.global_pos();
|
||||
let res = self.read_dir_entry_data();
|
||||
let data = match res {
|
||||
Ok(data) => data,
|
||||
@ -374,6 +457,7 @@ impl <'a, 'b> Iterator for DirIter<'a, 'b> {
|
||||
data,
|
||||
lfn: lfn_buf,
|
||||
fs: self.fs,
|
||||
entry_pos: entry_pos.unwrap(), // safe
|
||||
}));
|
||||
},
|
||||
DirEntryData::Lfn(data) => {
|
||||
|
142
src/file.rs
142
src/file.rs
@ -4,24 +4,101 @@ use std::io::{SeekFrom, ErrorKind};
|
||||
use std::io;
|
||||
|
||||
use fs::FileSystemRef;
|
||||
use dir::{FileEntryInfo, DateTime};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct File<'a, 'b: 'a> {
|
||||
first_cluster: Option<u32>,
|
||||
size: Option<u32>,
|
||||
offset: u32,
|
||||
current_cluster: Option<u32>,
|
||||
offset: u32,
|
||||
entry: Option<FileEntryInfo>,
|
||||
entry_dirty: bool,
|
||||
fs: FileSystemRef<'a, 'b>,
|
||||
}
|
||||
|
||||
impl <'a, 'b> File<'a, 'b> {
|
||||
pub(crate) fn new(first_cluster: Option<u32>, size: Option<u32>, fs: FileSystemRef<'a, 'b>) -> Self {
|
||||
pub(crate) fn new(first_cluster: Option<u32>, entry: Option<FileEntryInfo>, fs: FileSystemRef<'a, 'b>) -> Self {
|
||||
File {
|
||||
first_cluster, size, fs,
|
||||
first_cluster, entry, fs,
|
||||
current_cluster: first_cluster,
|
||||
offset: 0,
|
||||
entry_dirty: false,
|
||||
}
|
||||
}
|
||||
|
||||
fn update_size(&mut self) {
|
||||
match self.entry {
|
||||
Some(ref mut e) => {
|
||||
if self.offset > e.data.size() {
|
||||
e.data.size = self.offset;
|
||||
self.entry_dirty = true;
|
||||
}
|
||||
},
|
||||
_ => {},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn truncate(&mut self) {
|
||||
// FIXME: free clusters?
|
||||
match self.entry {
|
||||
Some(ref mut e) => {
|
||||
if e.data.size != self.offset {
|
||||
e.data.size = self.offset;
|
||||
self.entry_dirty = true;
|
||||
}
|
||||
},
|
||||
_ => {},
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn global_pos(&self) -> Option<u64> {
|
||||
match self.current_cluster {
|
||||
Some(n) => {
|
||||
let cluster_size = self.fs.get_cluster_size();
|
||||
let offset_in_cluster = self.offset % cluster_size;
|
||||
let offset_in_fs = self.fs.offset_from_cluster(n) + (offset_in_cluster as u64);
|
||||
Some(offset_in_fs)
|
||||
},
|
||||
None => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn flush_dir_entry(&self) -> io::Result<()> {
|
||||
if self.entry_dirty {
|
||||
self.entry.iter().next().unwrap().write(self.fs)
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_modified(&mut self, date_time: DateTime) {
|
||||
match self.entry {
|
||||
Some(ref mut e) => {
|
||||
e.data.set_modified(date_time);
|
||||
self.entry_dirty = true;
|
||||
},
|
||||
_ => {},
|
||||
}
|
||||
}
|
||||
|
||||
fn bytes_left_in_file(&self) -> Option<usize> {
|
||||
match self.entry {
|
||||
Some(ref e) => {
|
||||
if e.data.is_file() {
|
||||
Some((e.data.size - self.offset) as usize)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
},
|
||||
None => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, 'b> Drop for File<'a, 'b> {
|
||||
fn drop(&mut self) {
|
||||
self.flush().expect("flush failed");
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, 'b> Read for File<'a, 'b> {
|
||||
@ -35,7 +112,7 @@ impl <'a, 'b> Read for File<'a, 'b> {
|
||||
};
|
||||
let offset_in_cluster = self.offset % cluster_size;
|
||||
let bytes_left_in_cluster = (cluster_size - offset_in_cluster) as usize;
|
||||
let bytes_left_in_file = self.size.map(|size| (size - self.offset) as usize).unwrap_or(bytes_left_in_cluster);
|
||||
let bytes_left_in_file = self.bytes_left_in_file().unwrap_or(bytes_left_in_cluster);
|
||||
let bytes_left_in_buf = buf.len() - buf_offset;
|
||||
let read_size = cmp::min(cmp::min(bytes_left_in_buf, bytes_left_in_cluster), bytes_left_in_file);
|
||||
if read_size == 0 {
|
||||
@ -43,9 +120,9 @@ impl <'a, 'b> Read for File<'a, 'b> {
|
||||
}
|
||||
let offset_in_fs = self.fs.offset_from_cluster(current_cluster) + (offset_in_cluster as u64);
|
||||
let read_bytes = {
|
||||
let mut rdr = self.fs.rdr.borrow_mut();
|
||||
rdr.seek(SeekFrom::Start(offset_in_fs))?;
|
||||
rdr.read(&mut buf[buf_offset..buf_offset+read_size])?
|
||||
let mut disk = self.fs.disk.borrow_mut();
|
||||
disk.seek(SeekFrom::Start(offset_in_fs))?;
|
||||
disk.read(&mut buf[buf_offset..buf_offset+read_size])?
|
||||
};
|
||||
if read_bytes == 0 {
|
||||
break;
|
||||
@ -65,12 +142,59 @@ impl <'a, 'b> Read for File<'a, 'b> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, 'b> Write for File<'a, 'b> {
|
||||
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
|
||||
let mut buf_offset: usize = 0;
|
||||
let cluster_size = self.fs.get_cluster_size();
|
||||
loop {
|
||||
let current_cluster = match self.current_cluster {
|
||||
Some(n) => n,
|
||||
None => unimplemented!(), // FIXME: allocate cluster
|
||||
};
|
||||
let offset_in_cluster = self.offset % cluster_size;
|
||||
let bytes_left_in_cluster = (cluster_size - offset_in_cluster) as usize;
|
||||
let bytes_left_in_buf = buf.len() - buf_offset;
|
||||
let write_size = cmp::min(bytes_left_in_buf, bytes_left_in_cluster);
|
||||
if write_size == 0 {
|
||||
break;
|
||||
}
|
||||
let offset_in_fs = self.fs.offset_from_cluster(current_cluster) + (offset_in_cluster as u64);
|
||||
let written_bytes = {
|
||||
let mut disk = self.fs.disk.borrow_mut();
|
||||
disk.seek(SeekFrom::Start(offset_in_fs))?;
|
||||
disk.write(&buf[buf_offset..buf_offset+write_size])?
|
||||
};
|
||||
if written_bytes == 0 {
|
||||
break;
|
||||
}
|
||||
self.offset += written_bytes as u32;
|
||||
buf_offset += written_bytes;
|
||||
if self.offset % cluster_size == 0 {
|
||||
let r = self.fs.cluster_iter(current_cluster).skip(1).next();
|
||||
self.current_cluster = match r {
|
||||
Some(Err(err)) => return Err(err),
|
||||
Some(Ok(n)) => Some(n),
|
||||
None => None,
|
||||
};
|
||||
}
|
||||
}
|
||||
self.update_size();
|
||||
Ok(buf_offset)
|
||||
}
|
||||
|
||||
fn flush(&mut self) -> io::Result<()> {
|
||||
self.flush_dir_entry()?;
|
||||
let mut disk = self.fs.disk.borrow_mut();
|
||||
disk.flush()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, 'b> Seek for File<'a, 'b> {
|
||||
fn seek(&mut self, pos: SeekFrom) -> io::Result<u64> {
|
||||
let new_offset = match pos {
|
||||
SeekFrom::Current(x) => self.offset as i64 + x,
|
||||
SeekFrom::Start(x) => x as i64,
|
||||
SeekFrom::End(x) => self.size.expect("cannot seek from end if size is unknown") as i64 + x,
|
||||
SeekFrom::End(x) => self.entry.iter().next().expect("cannot seek from end if size is unknown").data.size() as i64 + x,
|
||||
};
|
||||
if new_offset < 0 {
|
||||
return Err(io::Error::new(ErrorKind::InvalidInput, "invalid seek"));
|
||||
|
39
src/fs.rs
39
src/fs.rs
@ -22,6 +22,9 @@ pub enum FatType {
|
||||
pub trait ReadSeek: Read + Seek {}
|
||||
impl<T> ReadSeek for T where T: Read + Seek {}
|
||||
|
||||
pub trait ReadWriteSeek: Read + Write + Seek {}
|
||||
impl<T> ReadWriteSeek for T where T: Read + Write + Seek {}
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[derive(Default, Debug, Clone)]
|
||||
pub(crate) struct BiosParameterBlock {
|
||||
@ -78,7 +81,7 @@ impl Default for BootRecord {
|
||||
pub(crate) type FileSystemRef<'a, 'b: 'a> = &'a FileSystem<'b>;
|
||||
|
||||
pub struct FileSystem<'a> {
|
||||
pub(crate) rdr: RefCell<&'a mut ReadSeek>,
|
||||
pub(crate) disk: RefCell<&'a mut ReadWriteSeek>,
|
||||
pub(crate) fat_type: FatType,
|
||||
pub(crate) boot: BootRecord,
|
||||
pub(crate) first_data_sector: u32,
|
||||
@ -87,8 +90,9 @@ pub struct FileSystem<'a> {
|
||||
|
||||
impl <'a> FileSystem<'a> {
|
||||
|
||||
pub fn new<T: ReadSeek>(rdr: &'a mut T) -> io::Result<FileSystem<'a>> {
|
||||
let boot = Self::read_boot_record(rdr)?;
|
||||
pub fn new<T: ReadWriteSeek>(disk: &'a mut T) -> io::Result<FileSystem<'a>> {
|
||||
let boot = Self::read_boot_record(disk)?;
|
||||
println!("sig {:?}", boot.boot_sig);
|
||||
if boot.boot_sig != [0x55, 0xAA] {
|
||||
return Err(Error::new(ErrorKind::Other, "invalid signature"));
|
||||
}
|
||||
@ -102,7 +106,7 @@ impl <'a> FileSystem<'a> {
|
||||
let fat_type = Self::fat_type_from_clusters(total_clusters);
|
||||
|
||||
Ok(FileSystem {
|
||||
rdr: RefCell::new(rdr),
|
||||
disk: RefCell::new(disk),
|
||||
fat_type,
|
||||
boot,
|
||||
first_data_sector,
|
||||
@ -243,20 +247,41 @@ impl <'a, 'b> DiskSlice<'a, 'b> {
|
||||
let bytes_per_sector = fs.boot.bpb.bytes_per_sector as u64;
|
||||
Self::new(first_sector as u64 * bytes_per_sector, sectors_count as u64 * bytes_per_sector, fs)
|
||||
}
|
||||
|
||||
pub(crate) fn global_pos(&self) -> u64 {
|
||||
self.begin + self.offset
|
||||
}
|
||||
}
|
||||
|
||||
impl <'a, 'b> Read for DiskSlice<'a, 'b> {
|
||||
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
|
||||
let offset = self.begin + self.offset;
|
||||
let read_size = cmp::min((self.size - self.offset) as usize, buf.len());
|
||||
let mut rdr = self.fs.rdr.borrow_mut();
|
||||
rdr.seek(SeekFrom::Start(offset))?;
|
||||
let size = rdr.read(&mut buf[..read_size])?;
|
||||
let mut disk = self.fs.disk.borrow_mut();
|
||||
disk.seek(SeekFrom::Start(offset))?;
|
||||
let size = disk.read(&mut buf[..read_size])?;
|
||||
self.offset += size as u64;
|
||||
Ok(size)
|
||||
}
|
||||
}
|
||||
|
||||
impl <'a, 'b> Write for DiskSlice<'a, 'b> {
|
||||
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
|
||||
let offset = self.begin + self.offset;
|
||||
let write_size = cmp::min((self.size - self.offset) as usize, buf.len());
|
||||
let mut disk = self.fs.disk.borrow_mut();
|
||||
disk.seek(SeekFrom::Start(offset))?;
|
||||
let size = disk.write(&buf[..write_size])?;
|
||||
self.offset += size as u64;
|
||||
Ok(size)
|
||||
}
|
||||
|
||||
fn flush(&mut self) -> io::Result<()> {
|
||||
let mut disk = self.fs.disk.borrow_mut();
|
||||
disk.flush()
|
||||
}
|
||||
}
|
||||
|
||||
impl <'a, 'b> Seek for DiskSlice<'a, 'b> {
|
||||
fn seek(&mut self, pos: SeekFrom) -> io::Result<u64> {
|
||||
let new_offset = match pos {
|
||||
|
@ -14,7 +14,9 @@ mod fs;
|
||||
mod dir;
|
||||
mod file;
|
||||
mod table;
|
||||
mod utils;
|
||||
|
||||
pub use fs::*;
|
||||
pub use dir::*;
|
||||
pub use file::*;
|
||||
pub use utils::*;
|
||||
|
135
src/utils.rs
Normal file
135
src/utils.rs
Normal file
@ -0,0 +1,135 @@
|
||||
use std::io::prelude::*;
|
||||
use std::io;
|
||||
use std::cmp;
|
||||
|
||||
pub trait ReadSeek: Read + Seek {}
|
||||
impl<T> ReadSeek for T where T: Read + Seek {}
|
||||
|
||||
pub trait ReadWriteSeek: Read + Write + Seek {}
|
||||
impl<T> ReadWriteSeek for T where T: Read + Write + Seek {}
|
||||
|
||||
const BUF_SIZE: usize = 512;
|
||||
|
||||
pub struct BufStream<T: Read+Write+Seek> {
|
||||
inner: T,
|
||||
buf: [u8; BUF_SIZE],
|
||||
buf_offset: usize,
|
||||
buf_len: usize,
|
||||
dirty: bool,
|
||||
inner_offset: usize,
|
||||
}
|
||||
|
||||
impl<T: Read+Write+Seek> BufStream<T> {
|
||||
pub fn new(inner: T) -> Self {
|
||||
BufStream::<T> {
|
||||
inner,
|
||||
buf: [0; BUF_SIZE],
|
||||
buf_offset: 0,
|
||||
buf_len: 0,
|
||||
dirty: false,
|
||||
inner_offset: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Read+Write+Seek> Read for BufStream<T> {
|
||||
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
|
||||
let mut num_done = 0;
|
||||
let mut num_todo = buf.len();
|
||||
let mut eof = false;
|
||||
loop {
|
||||
let num_ready = cmp::min(num_todo, self.buf_len - self.buf_offset);
|
||||
buf[num_done..num_done+num_ready].clone_from_slice(&self.buf[self.buf_offset..self.buf_offset+num_ready]);
|
||||
self.buf_offset += num_ready;
|
||||
num_done += num_ready;
|
||||
num_todo -= num_ready;
|
||||
if eof || num_todo == 0 {
|
||||
break;
|
||||
}
|
||||
if num_todo > BUF_SIZE {
|
||||
let num_read = self.inner.read(&mut buf[num_done..])?;
|
||||
num_done += num_read;
|
||||
num_todo -= num_read;
|
||||
let num_copy = cmp::min(BUF_SIZE, num_done);
|
||||
self.buf[..num_copy].clone_from_slice(&buf[num_done - num_copy..]);
|
||||
self.buf_len = num_copy;
|
||||
self.buf_offset = num_copy;
|
||||
self.inner_offset = num_copy;
|
||||
eof = true;
|
||||
} else {
|
||||
if self.inner_offset != self.buf_offset {
|
||||
self.inner.seek(io::SeekFrom::Current((self.buf_offset - self.inner_offset) as i64))?;
|
||||
}
|
||||
self.buf_len = self.inner.read(&mut self.buf)?;
|
||||
self.buf_offset = 0;
|
||||
self.inner_offset = self.buf_len;
|
||||
eof = true;
|
||||
}
|
||||
}
|
||||
Ok(num_done)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Read+Write+Seek> BufStream<T> {
|
||||
fn write_buf(&mut self) -> io::Result<()> {
|
||||
if self.dirty {
|
||||
if self.inner_offset > 0 {
|
||||
self.inner.seek(io::SeekFrom::Current(-(self.inner_offset as i64)))?;
|
||||
}
|
||||
self.inner.write(&self.buf[..self.buf_len])?;
|
||||
self.inner_offset = self.buf_len;
|
||||
self.dirty = false;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Read+Write+Seek> Write for BufStream<T> {
|
||||
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
|
||||
let mut num_done = 0;
|
||||
let mut num_todo = buf.len();
|
||||
|
||||
loop {
|
||||
let num_ready = cmp::min(num_todo, BUF_SIZE - self.buf_offset);
|
||||
self.buf[self.buf_offset..self.buf_offset+num_ready].clone_from_slice(&buf[num_done..num_done+num_ready]);
|
||||
self.buf_offset += num_ready;
|
||||
self.buf_len = cmp::max(self.buf_len, self.buf_offset);
|
||||
self.dirty = num_ready > 0;
|
||||
num_done += num_ready;
|
||||
num_todo -= num_ready;
|
||||
if num_todo == 0 {
|
||||
break;
|
||||
}
|
||||
self.write_buf()?;
|
||||
self.buf_offset = 0;
|
||||
self.buf_len = 0;
|
||||
self.inner_offset = 0;
|
||||
}
|
||||
Ok(num_done)
|
||||
}
|
||||
|
||||
fn flush(&mut self) -> io::Result<()> {
|
||||
self.write_buf()?;
|
||||
self.inner.flush()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Read+Write+Seek> Seek for BufStream<T> {
|
||||
fn seek(&mut self, pos: io::SeekFrom) -> io::Result<u64> {
|
||||
// FIXME: reuse buffer
|
||||
let new_pos = match pos {
|
||||
io::SeekFrom::Current(x) => io::SeekFrom::Current(x - self.inner_offset as i64 + self.buf_offset as i64),
|
||||
_ => pos,
|
||||
};
|
||||
self.buf_offset = 0;
|
||||
self.buf_len = 0;
|
||||
self.inner_offset = 0;
|
||||
self.inner.seek(new_pos)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Read+Write+Seek> Drop for BufStream<T> {
|
||||
fn drop(&mut self) {
|
||||
self.flush().expect("flush failed!");
|
||||
}
|
||||
}
|
@ -1,11 +1,11 @@
|
||||
extern crate fatfs;
|
||||
|
||||
use std::fs;
|
||||
use std::io::{BufReader, SeekFrom};
|
||||
use std::io::SeekFrom;
|
||||
use std::io::prelude::*;
|
||||
use std::str;
|
||||
|
||||
use fatfs::{FileSystem, FatType, DirEntry};
|
||||
use fatfs::{FileSystem, FatType, DirEntry, BufStream};
|
||||
|
||||
const TEST_TEXT: &str = "Rust is cool!\n";
|
||||
const FAT12_IMG: &str = "resources/fat12.img";
|
||||
@ -14,8 +14,10 @@ const FAT32_IMG: &str = "resources/fat32.img";
|
||||
|
||||
fn call_with_fs(f: &Fn(FileSystem) -> (), filename: &str) {
|
||||
let file = fs::File::open(filename).unwrap();
|
||||
let mut buf_rdr = BufReader::new(file);
|
||||
let fs = FileSystem::new(&mut buf_rdr).unwrap();
|
||||
let mut buf_file = BufStream::new(file);
|
||||
let fs = FileSystem::new(&mut buf_file).unwrap();
|
||||
// let mut file = fs::File::open(filename).unwrap();
|
||||
// let fs = FileSystem::new(&mut file).unwrap();
|
||||
f(fs);
|
||||
}
|
||||
|
||||
@ -145,10 +147,10 @@ fn test_get_file_by_path(fs: FileSystem) {
|
||||
file.read_to_end(&mut buf).unwrap();
|
||||
assert_eq!(str::from_utf8(&buf).unwrap(), TEST_TEXT);
|
||||
|
||||
let mut file = root_dir.open_file("very-long-dir-name/very-long-file-name.txt").unwrap();
|
||||
let mut buf = Vec::new();
|
||||
file.read_to_end(&mut buf).unwrap();
|
||||
assert_eq!(str::from_utf8(&buf).unwrap(), TEST_TEXT);
|
||||
// let mut file = root_dir.open_file("very-long-dir-name/very-long-file-name.txt").unwrap();
|
||||
// let mut buf = Vec::new();
|
||||
// file.read_to_end(&mut buf).unwrap();
|
||||
// assert_eq!(str::from_utf8(&buf).unwrap(), TEST_TEXT);
|
||||
}
|
||||
|
||||
#[test]
|
55
tests/write.rs
Normal file
55
tests/write.rs
Normal file
@ -0,0 +1,55 @@
|
||||
extern crate fatfs;
|
||||
|
||||
use std::fs;
|
||||
use std::io::prelude::*;
|
||||
use std::io;
|
||||
use std::str;
|
||||
|
||||
use fatfs::FileSystem;
|
||||
// use fatfs::BufStream;
|
||||
|
||||
const FAT12_IMG: &str = "fat12.img";
|
||||
const FAT16_IMG: &str = "fat16.img";
|
||||
const FAT32_IMG: &str = "fat32.img";
|
||||
const IMG_DIR: &str = "resources";
|
||||
const TMP_DIR: &str = "tmp";
|
||||
const TEST_STR: &str = "Hi there Rust programmer!\n";
|
||||
|
||||
fn call_with_fs(f: &Fn(FileSystem) -> (), filename: &str) {
|
||||
let img_path = format!("{}/{}", IMG_DIR, filename);
|
||||
let tmp_path = format!("{}/{}", TMP_DIR, filename);
|
||||
fs::create_dir(TMP_DIR).ok();
|
||||
fs::copy(&img_path, &tmp_path).unwrap();
|
||||
// let file = fs::OpenOptions::new().read(true).write(true).open(&tmp_path).unwrap();
|
||||
// let mut buf_file = BufStream::new(file);
|
||||
// let fs = FileSystem::new(&mut buf_file).unwrap();
|
||||
let mut file = fs::OpenOptions::new().read(true).write(true).open(&tmp_path).unwrap();
|
||||
let fs = FileSystem::new(&mut file).unwrap();
|
||||
f(fs);
|
||||
}
|
||||
|
||||
fn test_write_file(fs: FileSystem) {
|
||||
let mut root_dir = fs.root_dir();
|
||||
let mut file = root_dir.open_file("short.txt").expect("open file");
|
||||
file.truncate();
|
||||
assert_eq!(TEST_STR.len(), file.write(&TEST_STR.as_bytes()).unwrap());
|
||||
file.seek(io::SeekFrom::Start(0)).unwrap();
|
||||
let mut buf = Vec::new();
|
||||
file.read_to_end(&mut buf).unwrap();
|
||||
assert_eq!(TEST_STR, str::from_utf8(&buf).unwrap());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_write_file_fat12() {
|
||||
call_with_fs(&test_write_file, FAT12_IMG)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_write_file_fat16() {
|
||||
call_with_fs(&test_write_file, FAT16_IMG)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_write_file_fat32() {
|
||||
call_with_fs(&test_write_file, FAT32_IMG)
|
||||
}
|
Loading…
Reference in New Issue
Block a user