commit bc2d90f5317cd60e65d7e8eecaf2107961e43693 Author: Jeremy Soller Date: Wed Feb 24 14:02:37 2016 -0700 Allow read of filesystem names diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000000..f857248de5 --- /dev/null +++ b/.gitignore @@ -0,0 +1,12 @@ +# Compiled files +*.o +*.so +*.rlib +*.dll + +# Executables +*.exe + +# Generated by Cargo +Cargo.lock +/target/ diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000000..63af98548a --- /dev/null +++ b/.travis.yml @@ -0,0 +1,6 @@ +language: rust +rust: + - nightly +sudo: false +notifications: + email: false diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000000..0eb14b8af4 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "redoxfs" +description = "The Redox Filesystem" +repository = "https://github.com/redox-os/redoxfs" +version = "0.1.0" +license-file = "LICENSE" +readme = "README.md" +authors = ["Jeremy Soller "] + +[lib] +name = "redoxfs" +path = "src/lib.rs" + +[[bin]] +name = "redoxfs-utility" +path = "src/utility.rs" diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000000..7a0e9c2714 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2016 Jeremy Soller + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000000..f4ba44593a --- /dev/null +++ b/README.md @@ -0,0 +1,6 @@ +# redoxfs +The Redox Filesystem. Compatible with Redox and Linux. + +[![Travis Build Status](https://travis-ci.org/redox-os/redoxfs.svg?branch=master)](https://travis-ci.org/redox-os/redoxfs) +[![MIT licensed](https://img.shields.io/badge/license-MIT-blue.svg)](./LICENSE) +[![crates.io](http://meritbadge.herokuapp.com/redoxfs)](https://crates.io/crates/redoxfs) diff --git a/src/disk.rs b/src/disk.rs new file mode 100644 index 0000000000..d87dea1b60 --- /dev/null +++ b/src/disk.rs @@ -0,0 +1,8 @@ +use std::io::Result; + +/// A disk +pub trait Disk { + fn name(&self) -> &str; + fn read_at(&mut self, block: u64, buffer: &mut [u8]) -> Result; + fn write_at(&mut self, block: u64, buffer: &[u8]) -> Result; +} diff --git a/src/extent.rs b/src/extent.rs new file mode 100644 index 0000000000..def358c253 --- /dev/null +++ b/src/extent.rs @@ -0,0 +1,7 @@ +/// A disk extent +#[derive(Copy, Clone, Debug, Default)] +#[repr(packed)] +pub struct Extent { + pub block: u64, + pub length: u64, +} diff --git a/src/filesystem.rs b/src/filesystem.rs new file mode 100644 index 0000000000..660b0016d5 --- /dev/null +++ b/src/filesystem.rs @@ -0,0 +1,45 @@ +use std::cmp; +use std::collections::BTreeMap; + +use super::{Disk, Header, Node}; + +/// A file system +pub struct FileSystem { + pub disk: Box, + pub header: Header, + pub nodes: BTreeMap, +} + +impl FileSystem { + /// Create a file system from a disk + pub fn new(mut disk: Box) -> Result { + let mut header = Header::new(); + try!(disk.read_at(1, &mut header).map_err(|err| format!("{}: could not read header: {}", disk.name(), err))); + if header.valid() { + let mut nodes = BTreeMap::new(); + for extent in &header.extents { + if extent.block > 0 && extent.length > 0 { + let current_sectors = (extent.length + 511) / 512; + let max_size = current_sectors * 512; + + let size = cmp::min(extent.length, max_size); + + for i in 0..size / 512 { + let node_block = extent.block + i; + let mut node = Node::new(); + try!(disk.read_at(node_block, &mut node).map_err(|err| format!("{}: could not read node {}: {}", disk.name(), node_block, err))); + nodes.insert(node_block, node); + } + } + } + + Ok(FileSystem { + disk: disk, + header: header, + nodes: nodes, + }) + }else{ + Err(format!("{}: invalid header", disk.name())) + } + } +} diff --git a/src/header.rs b/src/header.rs new file mode 100644 index 0000000000..3575d248b0 --- /dev/null +++ b/src/header.rs @@ -0,0 +1,47 @@ +use std::{mem, slice}; +use std::ops::{Deref, DerefMut}; + +use super::Extent; + +/// The header of the filesystem +#[repr(packed)] +pub struct Header { + pub signature: [u8; 8], + pub version: u64, + pub free_space: Extent, + pub padding: [u8; 224], + pub extents: [Extent; 16], +} + +impl Header { + pub fn new() -> Header { + Header { + signature: [0; 8], + version: 0, + free_space: Extent::default(), + padding: [0; 224], + extents: [Extent::default(); 16] + } + } + + pub fn valid(&self) -> bool { + &self.signature == b"REDOXFS\0" && self.version == 1 + } +} + +impl Deref for Header { + type Target = [u8]; + fn deref(&self) -> &[u8] { + unsafe { + slice::from_raw_parts(self as *const Header as *const u8, mem::size_of::
()) as &[u8] + } + } +} + +impl DerefMut for Header { + fn deref_mut(&mut self) -> &mut [u8] { + unsafe { + slice::from_raw_parts_mut(self as *mut Header as *mut u8, mem::size_of::
()) as &mut [u8] + } + } +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000000..5d035e74b3 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,11 @@ +pub use self::disk::Disk; +pub use self::extent::Extent; +pub use self::filesystem::FileSystem; +pub use self::header::Header; +pub use self::node::Node; + +pub mod disk; +pub mod extent; +pub mod filesystem; +pub mod header; +pub mod node; diff --git a/src/node.rs b/src/node.rs new file mode 100644 index 0000000000..946ceced6f --- /dev/null +++ b/src/node.rs @@ -0,0 +1,37 @@ +use std::{mem, slice}; +use std::ops::{Deref, DerefMut}; + +use super::Extent; + +/// A file/folder node +#[repr(packed)] +pub struct Node { + pub name: [u8; 256], + pub extents: [Extent; 16], +} + +impl Node { + pub fn new() -> Node { + Node { + name: [0; 256], + extents: [Extent::default(); 16] + } + } +} + +impl Deref for Node { + type Target = [u8]; + fn deref(&self) -> &[u8] { + unsafe { + slice::from_raw_parts(self as *const Node as *const u8, mem::size_of::()) as &[u8] + } + } +} + +impl DerefMut for Node { + fn deref_mut(&mut self) -> &mut [u8] { + unsafe { + slice::from_raw_parts_mut(self as *mut Node as *mut u8, mem::size_of::()) as &mut [u8] + } + } +} diff --git a/src/scheme.rs b/src/scheme.rs new file mode 100644 index 0000000000..972947e7a2 --- /dev/null +++ b/src/scheme.rs @@ -0,0 +1,381 @@ +use common::slice::GetSlice; + +use alloc::boxed::Box; + +use arch::memory::Memory; + +use collections::slice; +use collections::string::{String, ToString}; +use collections::vec::Vec; + +use common::debug; + +use core::cmp; + +use disk::Disk; +use disk::ide::Extent; + +use fs::redoxfs::{FileSystem, Node, NodeData}; + +use fs::{KScheme, Resource, ResourceSeek, Url, VecResource}; + +use syscall::{Error, Result, O_CREAT, ENOENT, EIO}; + +/// A file resource +pub struct FileResource { + pub scheme: *mut FileScheme, + pub node: Node, + pub vec: Vec, + pub seek: usize, + pub dirty: bool, +} + +impl Resource for FileResource { + fn dup(&self) -> Result> { + Ok(Box::new(FileResource { + scheme: self.scheme, + node: self.node.clone(), + vec: self.vec.clone(), + seek: self.seek, + dirty: self.dirty, + })) + } + + fn path(&self, buf: &mut [u8]) -> Result { + let path_a = b"file:/"; + let path_b = self.node.name.as_bytes(); + for (b, p) in buf.iter_mut().zip(path_a.iter().chain(path_b.iter())) { + *b = *p; + } + + Ok(cmp::min(buf.len(), path_a.len() + path_b.len())) + } + + fn read(&mut self, buf: &mut [u8]) -> Result { + let mut i = 0; + while i < buf.len() && self.seek < self.vec.len() { + match self.vec.get(self.seek) { + Some(b) => buf[i] = *b, + None => (), + } + self.seek += 1; + i += 1; + } + Ok(i) + } + + fn write(&mut self, buf: &[u8]) -> Result { + let mut i = 0; + while i < buf.len() && self.seek < self.vec.len() { + self.vec[self.seek] = buf[i]; + self.seek += 1; + i += 1; + } + while i < buf.len() { + self.vec.push(buf[i]); + self.seek += 1; + i += 1; + } + if i > 0 { + self.dirty = true; + } + Ok(i) + } + + fn seek(&mut self, pos: ResourceSeek) -> Result { + match pos { + ResourceSeek::Start(offset) => self.seek = offset, + ResourceSeek::Current(offset) => + self.seek = cmp::max(0, self.seek as isize + offset) as usize, + ResourceSeek::End(offset) => + self.seek = cmp::max(0, self.vec.len() as isize + offset) as usize, + } + while self.vec.len() < self.seek { + self.vec.push(0); + } + Ok(self.seek) + } + + // TODO: Check to make sure proper amount of bytes written. See Disk::write + // TODO: Allow reallocation + fn sync(&mut self) -> Result<()> { + if self.dirty { + let mut node_dirty = false; + let mut pos = 0; + let mut remaining = self.vec.len() as isize; + for ref mut extent in &mut self.node.extents { + if remaining > 0 && extent.empty() { + debug::d("Reallocate file, extra: "); + debug::ds(remaining); + debug::dl(); + + unsafe { + let sectors = ((remaining + 511) / 512) as u64; + if (*self.scheme).fs.header.free_space.length >= sectors * 512 { + extent.block = (*self.scheme).fs.header.free_space.block; + extent.length = remaining as u64; + (*self.scheme).fs.header.free_space.block = (*self.scheme) + .fs + .header + .free_space + .block + + sectors; + (*self.scheme).fs.header.free_space.length = (*self.scheme) + .fs + .header + .free_space + .length - + sectors * 512; + + node_dirty = true; + } + } + } + + // Make sure it is a valid extent + if !extent.empty() { + let current_sectors = (extent.length as usize + 511) / 512; + let max_size = current_sectors * 512; + + let size = cmp::min(remaining as usize, max_size); + + if size as u64 != extent.length { + extent.length = size as u64; + node_dirty = true; + } + + while self.vec.len() < pos + max_size { + self.vec.push(0); + } + + unsafe { + let _ = (*self.scheme).fs.disk.write(extent.block, &self.vec[pos .. pos + max_size]); + } + + self.vec.truncate(pos + size); + + pos += size; + remaining -= size as isize; + } + } + + if node_dirty { + debug::d("Node dirty, rewrite\n"); + + if self.node.block > 0 { + unsafe { + if let Some(mut node_data) = Memory::::new(1) { + node_data.write(0, self.node.data()); + + let mut buffer = slice::from_raw_parts(node_data.address() as *mut u8, 512); + let _ = (*self.scheme).fs.disk.write(self.node.block, &mut buffer); + + debug::d("Renode\n"); + + for mut node in (*self.scheme).fs.nodes.iter_mut() { + if node.block == self.node.block { + *node = self.node.clone(); + } + } + } + } + } else { + debug::d("Need to place Node block\n"); + } + } + + self.dirty = false; + + if remaining > 0 { + debug::d("Need to defragment file, extra: "); + debug::ds(remaining); + debug::dl(); + return Err(Error::new(EIO)); + } + } + Ok(()) + } + + fn truncate(&mut self, len: usize) -> Result<()> { + while len > self.vec.len() { + self.vec.push(0); + } + self.vec.truncate(len); + self.seek = cmp::min(self.seek, self.vec.len()); + self.dirty = true; + Ok(()) + } +} + +impl Drop for FileResource { + fn drop(&mut self) { + let _ = self.sync(); + } +} + +/// A file scheme (pci + fs) +pub struct FileScheme { + fs: FileSystem, +} + +impl FileScheme { + /// Create a new file scheme from an array of Disks + pub fn new(mut disks: Vec>) -> Option> { + while ! disks.is_empty() { + if let Some(fs) = FileSystem::from_disk(disks.remove(0)) { + return Some(Box::new(FileScheme { fs: fs })); + } + } + + None + } +} + +impl KScheme for FileScheme { + fn on_irq(&mut self, _irq: u8) { + /*if irq == self.fs.disk.irq { + }*/ + } + + fn scheme(&self) -> &str { + "file" + } + + fn open(&mut self, url: &Url, flags: usize) -> Result> { + let mut path = url.reference(); + while path.starts_with('/') { + path = &path[1..]; + } + if path.is_empty() || path.ends_with('/') { + let mut list = String::new(); + let mut dirs: Vec = Vec::new(); + + for file in self.fs.list(path).iter() { + let mut line = String::new(); + match file.find('/') { + Some(index) => { + let dirname = file.get_slice(..index + 1).to_string(); + let mut found = false; + for dir in dirs.iter() { + if dirname == *dir { + found = true; + break; + } + } + if found { + line.clear(); + } else { + line = dirname.clone(); + dirs.push(dirname); + } + } + None => line = file.clone(), + } + if !line.is_empty() { + if !list.is_empty() { + list = list + "\n" + &line; + } else { + list = line; + } + } + } + + if list.len() > 0 { + Ok(Box::new(VecResource::new(&url.string, list.into_bytes()))) + } else { + Err(Error::new(ENOENT)) + } + } else { + match self.fs.node(path) { + Some(node) => { + let mut vec: Vec = Vec::new(); + for extent in &node.extents { + if extent.block > 0 && extent.length > 0 { + let current_sectors = (extent.length as usize + 511) / 512; + let max_size = current_sectors * 512; + + let size = cmp::min(extent.length as usize, max_size); + + let pos = vec.len(); + + while vec.len() < pos + max_size { + vec.push(0); + } + + let _ = self.fs.disk.read(extent.block, &mut vec[pos..pos + max_size]); + + vec.truncate(pos + size); + } + } + + Ok(Box::new(FileResource { + scheme: self, + node: node, + vec: vec, + seek: 0, + dirty: false, + })) + } + None => { + if flags & O_CREAT == O_CREAT { + // TODO: Create file + let mut node = Node { + block: 0, + name: path.to_string(), + extents: [Extent { + block: 0, + length: 0, + }; 16], + }; + + if self.fs.header.free_space.length >= 512 { + node.block = self.fs.header.free_space.block; + self.fs.header.free_space.block = self.fs.header.free_space.block + 1; + self.fs.header.free_space.length = self.fs.header.free_space.length - + 512; + } + + self.fs.nodes.push(node.clone()); + + Ok(Box::new(FileResource { + scheme: self, + node: node, + vec: Vec::new(), + seek: 0, + dirty: false, + })) + } else { + Err(Error::new(ENOENT)) + } + } + } + } + } + + fn unlink(&mut self, url: &Url) -> Result<()> { + let mut ret = Err(Error::new(ENOENT)); + + let mut path = url.reference(); + while path.starts_with('/') { + path = &path[1..]; + } + + let mut i = 0; + while i < self.fs.nodes.len() { + let mut remove = false; + + if let Some(node) = self.fs.nodes.get(i) { + remove = node.name == path; + } + + if remove { + self.fs.nodes.remove(i); + ret = Ok(()); + } else { + i += 1; + } + } + + ret + } +} diff --git a/src/utility.rs b/src/utility.rs new file mode 100644 index 0000000000..c1fbdd2525 --- /dev/null +++ b/src/utility.rs @@ -0,0 +1,47 @@ +extern crate redoxfs; + +use std::fs::File; +use std::io::{Result, Read, Write, Seek, SeekFrom}; +use std::str; + +use redoxfs::{Disk, FileSystem}; + +pub struct FileDisk { + path: String, + file: File +} + +impl FileDisk { + pub fn new(path: &str) -> Result { + let file = try!(File::open(path)); + Ok(FileDisk { + path: path.to_string(), + file: file + }) + } +} + +impl Disk for FileDisk { + fn name(&self) -> &str { + &self.path + } + + fn read_at(&mut self, block: u64, buffer: &mut [u8]) -> Result { + try!(self.file.seek(SeekFrom::Start(block * 512))); + self.file.read(buffer) + } + + fn write_at(&mut self, block: u64, buffer: &[u8]) -> Result { + try!(self.file.seek(SeekFrom::Start(block * 512))); + self.file.write(buffer) + } +} + +fn main() { + let disk = FileDisk::new("../../build/i386-unknown-redox/debug/harddrive.bin").unwrap(); + let filesystem = FileSystem::new(Box::new(disk)).unwrap(); + for (node_block, node) in filesystem.nodes.iter() { + let name = unsafe { str::from_utf8_unchecked(&node.name) }; + println!("{}: {}", node_block, name); + } +}