diff --git a/Cargo.lock b/Cargo.lock
index ea855cd..e313876 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -306,7 +306,6 @@ dependencies = [
 [[package]]
 name = "ext2"
 version = "0.1.1"
-source = "git+https://git.ablecorp.us/able/ext2-rs.git#15bcf9f72e2523e7ebe2a8d09c1231ca9139f326"
 dependencies = [
  "bitflags",
  "genfs",
diff --git a/Cargo.toml b/Cargo.toml
index 7053fe6..b342862 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,9 +1,3 @@
 [workspace]
 
-members = [
-    "ableos",
-    "kernel",
-    "facepalm",
-    "shadeable",
-    "repbuild",
-]
\ No newline at end of file
+members = ["ableos", "ext2-rs", "kernel", "facepalm", "shadeable", "repbuild"]
diff --git a/ableos/Cargo.toml b/ableos/Cargo.toml
index b660d1e..cffe6fb 100644
--- a/ableos/Cargo.toml
+++ b/ableos/Cargo.toml
@@ -95,7 +95,7 @@ features = ["size_64", "alloc"]
 git = "https://git.ablecorp.us:443/able/y-compositor-protocol.git"
 
 [dependencies.ext2]
-git = "https://git.ablecorp.us:443/able/ext2-rs.git"
+path = "../ext2-rs"
 
 [dependencies.toml]
 git = "https://github.com/diondokter/toml-rs"
diff --git a/ableos/src/scratchpad.rs b/ableos/src/scratchpad.rs
index ca2ce06..dfb5c2e 100644
--- a/ableos/src/scratchpad.rs
+++ b/ableos/src/scratchpad.rs
@@ -12,6 +12,7 @@ use cpuio::inb;
 use cpuio::outb;
 use genfs::Fs;
 use genfs::OpenOptions;
+use kernel::proccess::PID;
 use vga::writers::GraphicsWriter;
 
 // TODO: move to a better place
@@ -113,6 +114,15 @@ pub fn scratchpad() {
     // */
     // kernel::aalloc::AAlloc::intialize();
 
+    let mut chan = Channel::new();
+    let perms = ChannelPermission {
+        owner: true,
+        producer: false,
+        consumer: false,
+        distructive_consumer: false,
+    };
+    chan.permission_list.push((0, perms));
+
     real_shell();
 }
 use crate::graphics::VgaBuffer;
@@ -270,3 +280,47 @@ pub fn sound_off() {
     };
     reset_pit_for_cpu();
 }
+///
+///
+///
+///
+///
+///
+///
+///
+///
+///
+///
+///
+///
+///
+///
+///
+
+#[derive(Debug)]
+pub struct ChannelPermission {
+    owner: bool,
+    producer: bool,
+
+    consumer: bool,
+    /// Whether or not the process can be destructive about reading
+    distructive_consumer: bool,
+}
+
+#[derive(Debug)]
+pub struct Channel {
+    inner: Vec<u8>,
+
+    permission_list: Vec<(PID, ChannelPermission)>,
+}
+
+impl Channel {
+    pub fn new() -> Self {
+        Self {
+            inner: vec![],
+            permission_list: vec![],
+        }
+    }
+}
+
+pub enum ChannelErrors {}
diff --git a/ext2-rs/Cargo.toml b/ext2-rs/Cargo.toml
new file mode 100644
index 0000000..f72ff11
--- /dev/null
+++ b/ext2-rs/Cargo.toml
@@ -0,0 +1,16 @@
+[package]
+name = "ext2"
+version = "0.1.1"
+authors = ["Szymon Walter <walter.szymon.98@gmail.com>",
+"able <abl3theabove@gmail.com>"]
+
+
+[dependencies]
+bitflags = "1.0"
+rlibc = { version = "1.0", optional = true }
+spin = "0.9.2"
+genfs = "^0.1.4"
+
+[features]
+default = ["no_std"]
+no_std = ["rlibc"]
diff --git a/ext2-rs/LICENSE.md b/ext2-rs/LICENSE.md
new file mode 100644
index 0000000..9894777
--- /dev/null
+++ b/ext2-rs/LICENSE.md
@@ -0,0 +1,22 @@
+# ext2-rs
+## an ext2 implementation
+
+Copyright © 2018, Szymon Walter
+
+This software is provided 'as-is', without any express or implied warranty.
+In no event will the authors be held liable for any damages arising from
+the use of this software.
+
+Permission is granted to anyone to use this software for any purpose,
+including commercial applications, and to alter it and redistribute it
+freely, subject to the following restrictions:
+
+1. The origin of this software must not be misrepresented; you must not
+   claim that you wrote the original software. If you use this software
+   in a product, an acknowledgment in the product documentation would be
+   appreciated but is not required.
+2. Altered source versions must be plainly marked as such, and must not
+   be misrepresented as being the original software.
+3. This notice may not be removed or altered from any source distribution.
+
+#### walter.szymon.98@gmail.com
diff --git a/ext2-rs/README.md b/ext2-rs/README.md
new file mode 100644
index 0000000..8dd53d6
--- /dev/null
+++ b/ext2-rs/README.md
@@ -0,0 +1,3 @@
+# ext2-rs
+
+An OS and architecture independent implementation of ext2 in pure Rust.
diff --git a/ext2-rs/ext2.img b/ext2-rs/ext2.img
new file mode 100644
index 0000000..3af4aae
Binary files /dev/null and b/ext2-rs/ext2.img differ
diff --git a/ext2-rs/rustfmt.toml b/ext2-rs/rustfmt.toml
new file mode 100644
index 0000000..3450fc4
--- /dev/null
+++ b/ext2-rs/rustfmt.toml
@@ -0,0 +1,2 @@
+max_width = 80
+wrap_comments = true
diff --git a/ext2-rs/src/error.rs b/ext2-rs/src/error.rs
new file mode 100644
index 0000000..10cb16f
--- /dev/null
+++ b/ext2-rs/src/error.rs
@@ -0,0 +1,124 @@
+//! Errors
+use {
+    alloc::string::String,
+    core::fmt::{self, Display},
+};
+
+#[cfg(any(test, not(feature = "no_std")))]
+use std::io;
+
+/// The set of all possible errors
+#[derive(Debug)]
+pub enum Error {
+    /// Generic error
+    Other(String),
+    /// Bad magic number
+    BadMagic {
+        /// The magic number
+        magic: u16,
+    },
+    /// Out of bounds error
+    OutOfBounds {
+        /// index
+        index: usize,
+    },
+    /// Address out of bounds
+    AddressOutOfBounds {
+        ///
+        sector: u32,
+        ///
+        offset: u32,
+
+        ///
+        size: usize,
+    },
+    /// Bad block group count
+    BadBlockGroupCount {
+        ///
+        by_blocks: u32,
+        ///
+        by_inodes: u32,
+    },
+    /// Inode Not Found
+    InodeNotFound {
+        /// inode number
+        inode: u32,
+    },
+    /// Inode is not a directory
+    NotADirectory {
+        /// inode number
+        inode: u32,
+        /// inode name
+        name: String,
+    },
+    /// Not Absolute Path
+    NotAbsolute {
+        /// path name
+        name: String,
+    },
+
+    /// Not Found
+    NotFound {
+        /// inode name
+        name: String,
+    },
+    #[cfg(any(test, not(feature = "no_std")))]
+    Io { inner: io::Error },
+}
+
+impl Display for Error {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        match *self {
+            Error::Other(ref msg) => write!(f, "{}", msg),
+            Error::BadMagic {
+                magic,
+            } => write!(f, "invalid magic value: {}", magic),
+            Error::OutOfBounds {
+                index,
+            } => write!(f, "index ouf of bounds: {}", index),
+            Error::AddressOutOfBounds {
+                sector,
+                offset,
+                size,
+            } => write!(f, "address ouf of bounds: {}:{} with a block size of: {}",
+                   sector, offset, size),
+            Error::BadBlockGroupCount {
+                by_blocks,
+                by_inodes,
+            } => write!(f, "conflicting block group count data; by blocks: {}, by inodes: {}", by_blocks, by_inodes),
+            Error::InodeNotFound {
+                inode,
+            } => write!(f, "couldn't find inode no. {}", &inode),
+            Error::NotADirectory {
+                inode,
+                ref name,
+            } => write!(f, "inode no. {} at: {} is not a directory", inode, &name),
+            Error::NotAbsolute {
+                ref name,
+            } => write!(f, "{} is not an absolute path", &name),
+            Error::NotFound {
+                ref name,
+            } => write!(f, "couldn't find {}", &name),
+            #[cfg(any(test, not(feature = "no_std")))]
+            Error::Io {
+                ref inner,
+            } => write!(f, "io error: {}", inner),
+        }
+    }
+}
+
+impl From<Infallible> for Error {
+    fn from(_: Infallible) -> Error {
+        unreachable!()
+    }
+}
+
+#[cfg(any(test, not(feature = "no_std")))]
+impl From<io::Error> for Error {
+    fn from(inner: io::Error) -> Error {
+        Error::Io { inner }
+    }
+}
+
+/// Infalliable
+pub enum Infallible {}
diff --git a/ext2-rs/src/fs/mod.rs b/ext2-rs/src/fs/mod.rs
new file mode 100644
index 0000000..1844e31
--- /dev/null
+++ b/ext2-rs/src/fs/mod.rs
@@ -0,0 +1,177 @@
+//!
+
+use {
+    alloc::vec::Vec,
+    core::mem,
+    error::Error,
+    sector::{Address, SectorSize},
+    sys::{
+        block_group::BlockGroupDescriptor, inode::Inode as RawInode,
+        superblock::Superblock,
+    },
+    volume::Volume,
+};
+
+pub mod sync;
+
+#[allow(dead_code)]
+pub(crate) struct Struct<T, S: SectorSize> {
+    pub inner: T,
+    pub offset: Address<S>,
+}
+
+impl<T, S: SectorSize> From<(T, Address<S>)> for Struct<T, S> {
+    #[inline]
+    fn from((inner, offset): (T, Address<S>)) -> Struct<T, S> {
+        Struct { inner, offset }
+    }
+}
+
+/// Safe wrapper for raw sys structs
+pub struct Ext2<S: SectorSize, V: Volume<u8, S>> {
+    // TODO: should this have some different vis?
+    pub(crate) volume: V,
+    pub(crate) superblock: Struct<Superblock, S>,
+    pub(crate) block_groups: Struct<Vec<BlockGroupDescriptor>, S>,
+}
+
+impl<S: SectorSize, V: Volume<u8, S>> Ext2<S, V> {
+    ///
+    pub fn new(volume: V) -> Result<Ext2<S, V>, Error> {
+        let superblock = unsafe { Struct::from(Superblock::find(&volume)?) };
+        let block_groups_offset = Address::with_block_size(
+            superblock.inner.first_data_block + 1,
+            0,
+            superblock.inner.log_block_size + 10,
+        );
+        let block_groups_count = superblock
+            .inner
+            .block_group_count()
+            .map(|count| count as usize)
+            .map_err(|(a, b)| Error::BadBlockGroupCount {
+                by_blocks: a,
+                by_inodes: b,
+            })?;
+        let block_groups = unsafe {
+            BlockGroupDescriptor::find_descriptor_table(
+                &volume,
+                block_groups_offset,
+                block_groups_count,
+            )?
+        };
+        let block_groups = Struct::from(block_groups);
+        Ok(Ext2 {
+            volume,
+            superblock,
+            block_groups,
+        })
+    }
+    /// Return the version of the filesystem
+    pub fn version(&self) -> (u32, u16) {
+        (
+            self.superblock.inner.rev_major,
+            self.superblock.inner.rev_minor,
+        )
+    }
+    /// Return inode size
+    pub fn inode_size(&self) -> usize {
+        if self.version().0 == 0 {
+            mem::size_of::<RawInode>()
+        } else {
+            // note: inodes bigger than 128 are not supported
+            self.superblock.inner.inode_size as usize
+        }
+    }
+    ///
+    pub fn inodes_count(&self) -> usize {
+        self.superblock.inner.inodes_per_group as _
+    }
+    ///
+    pub fn total_inodes_count(&self) -> usize {
+        self.superblock.inner.inodes_count as _
+    }
+    ///
+    pub fn block_group_count(&self) -> Result<usize, Error> {
+        self.superblock
+            .inner
+            .block_group_count()
+            .map(|count| count as usize)
+            .map_err(|(a, b)| Error::BadBlockGroupCount {
+                by_blocks: a,
+                by_inodes: b,
+            })
+    }
+    ///
+    pub fn total_block_count(&self) -> usize {
+        self.superblock.inner.blocks_count as _
+    }
+    ///
+    pub fn free_block_count(&self) -> usize {
+        self.superblock.inner.free_blocks_count as _
+    }
+    ///
+    pub fn block_size(&self) -> usize {
+        self.superblock.inner.block_size()
+    }
+    ///
+    pub fn log_block_size(&self) -> u32 {
+        self.superblock.inner.log_block_size + 10
+    }
+    ///
+    pub fn sector_size(&self) -> usize {
+        S::SIZE
+    }
+    ///
+    pub fn log_sector_size(&self) -> u32 {
+        S::LOG_SIZE
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use std::cell::RefCell;
+    use std::fs::File;
+
+    use sector::{Address, Size512};
+    use volume::Volume;
+
+    use super::Ext2;
+
+    #[test]
+    fn file_len() {
+        let file = RefCell::new(File::open("ext2.img").unwrap());
+        assert_eq!(
+            Address::<Size512>::from(2048_u64)
+                - Address::<Size512>::from(1024_u64),
+            Address::<Size512>::new(2, 0)
+        );
+        assert_eq!(
+            unsafe {
+                file.slice_unchecked(
+                    Address::<Size512>::from(1024_u64)
+                        ..Address::<Size512>::from(2048_u64),
+                )
+                .len()
+            },
+            1024
+        );
+    }
+
+    #[test]
+    fn file() {
+        let file = RefCell::new(File::open("ext2.img").unwrap());
+        let fs = Ext2::<Size512, _>::new(file);
+
+        assert!(
+            fs.is_ok(),
+            "Err({:?})",
+            fs.err().unwrap_or_else(|| unreachable!()),
+        );
+
+        let fs = fs.unwrap();
+
+        let vers = fs.version();
+        println!("version: {}.{}", vers.0, vers.1);
+        assert_eq!(128, fs.inode_size());
+    }
+}
diff --git a/ext2-rs/src/fs/sync.rs b/ext2-rs/src/fs/sync.rs
new file mode 100644
index 0000000..bbf8521
--- /dev/null
+++ b/ext2-rs/src/fs/sync.rs
@@ -0,0 +1,889 @@
+//!
+
+use {
+    super::Ext2,
+    alloc::{
+        sync::Arc,
+        {string::String, vec::Vec},
+    },
+    core::{
+        fmt::{self, Debug},
+        iter::Iterator,
+        num::NonZeroU32,
+    },
+    error::Error,
+    genfs::*,
+    sector::{Address, SectorSize},
+    spin::{Mutex, MutexGuard},
+    sys::inode::Inode as RawInode,
+    volume::Volume,
+};
+/// DOCME: what is this?
+pub struct Synced<T> {
+    inner: Arc<Mutex<T>>,
+}
+
+impl<T> Synced<T> {
+    /// DOCME: what is this?
+    pub fn with_inner(inner: T) -> Synced<T> {
+        Synced {
+            inner: Arc::new(Mutex::new(inner)),
+        }
+    }
+
+    /// DOCME: what is this?
+    pub fn inner<'a>(&'a self) -> MutexGuard<'a, T> {
+        self.inner.lock()
+    }
+}
+
+impl<T> Clone for Synced<T> {
+    fn clone(&self) -> Self {
+        Synced {
+            inner: self.inner.clone(),
+        }
+    }
+}
+
+impl<S: SectorSize, V: Volume<u8, S>> Synced<Ext2<S, V>> {
+    /// DOCME: what is this?
+    pub fn new(volume: V) -> Result<Synced<Ext2<S, V>>, Error> {
+        Ext2::new(volume).map(|inner| Synced::with_inner(inner))
+    }
+    /// Get the root inode.
+    pub fn root_inode(&self) -> Inode<S, V> {
+        self.inode_nth(2).unwrap()
+    }
+    /// Get the inode at the given index.
+    pub fn inode_nth(&self, index: usize) -> Option<Inode<S, V>> {
+        self.inodes_nth(index).next()
+    }
+    /// DOCME: what is this?
+    pub fn inodes(&self) -> Inodes<S, V> {
+        self.inodes_nth(1)
+    }
+    /// DOCME: what is this?
+    pub fn inodes_nth(&self, index: usize) -> Inodes<S, V> {
+        assert!(index > 0, "inodes are 1-indexed");
+        let inner = self.inner();
+        Inodes {
+            fs: self.clone(),
+            log_block_size: inner.log_block_size(),
+            inode_size: inner.inode_size(),
+            inodes_per_group: inner.inodes_count(),
+            inodes_count: inner.total_inodes_count(),
+            index,
+        }
+    }
+    /// DOCME: what is this?
+    pub fn sector_size(&self) -> usize {
+        S::SIZE
+    }
+    /// DOCME: what is this?
+    pub fn log_sector_size(&self) -> u32 {
+        S::LOG_SIZE
+    }
+}
+
+impl<S: SectorSize, V: Volume<u8, S>> Fs for Synced<Ext2<S, V>> {
+    type Path = [u8];
+    type PathOwned = Vec<u8>;
+    type File = Inode<S, V>;
+    type Dir = Directory<S, V>;
+    type DirEntry = DirectoryEntry;
+    type Metadata = (); // TODO
+    type Permissions = (); // TODO
+    type Error = Error;
+
+    fn open(
+        &self,
+        abs_path: &Self::Path,
+        _options: &OpenOptions<Self::Permissions>,
+    ) -> Result<Self::File, Self::Error> {
+        fn inner<'a, S, V, I>(
+            fs: &Synced<Ext2<S, V>>,
+            inode: Inode<S, V>,
+            mut path: I,
+            abs_path: &[u8],
+        ) -> Result<Inode<S, V>, Error>
+        where
+            S: SectorSize,
+            V: Volume<u8, S>,
+            I: Iterator<Item = &'a [u8]>,
+        {
+            let name = match path.next() {
+                Some(name) => name,
+                None => return Ok(inode),
+            };
+
+            let mut dir =
+                inode.directory().ok_or_else(|| Error::NotADirectory {
+                    inode: inode.num,
+                    name: String::from_utf8_lossy(abs_path).into_owned(),
+                })?;
+
+            let entry = dir
+                .find(|entry| {
+                    entry.is_err() || entry.as_ref().unwrap().name == name
+                })
+                .ok_or_else(|| Error::NotFound {
+                    name: String::from_utf8_lossy(abs_path).into_owned(),
+                })??;
+
+            let inode = fs
+                .inode_nth(entry.inode)
+                .ok_or(Error::InodeNotFound { inode: inode.num })?;
+
+            inner(fs, inode, path, abs_path)
+        }
+
+        if abs_path.len() == 0 || abs_path[0] != b'/' {
+            return Err(Error::NotAbsolute {
+                name: String::from_utf8_lossy(abs_path).into_owned(),
+            });
+        }
+
+        if abs_path == b"/" {
+            return Ok(self.root_inode());
+        }
+
+        let mut path = abs_path.split(|byte| *byte == b'/');
+        path.next();
+        let root = self.root_inode();
+
+        inner(self, root, path, abs_path)
+    }
+
+    fn remove_file(&mut self, _path: &Self::Path) -> Result<(), Self::Error> {
+        unimplemented!()
+    }
+
+    fn metadata(
+        &self,
+        _path: &Self::Path,
+    ) -> Result<Self::Metadata, Self::Error> {
+        unimplemented!()
+    }
+
+    fn symlink_metadata(
+        &self,
+        _path: &Self::Path,
+    ) -> Result<Self::Metadata, Self::Error> {
+        unimplemented!()
+    }
+
+    fn rename(
+        &mut self,
+        _from: &Self::Path,
+        _to: &Self::Path,
+    ) -> Result<(), Self::Error> {
+        unimplemented!()
+    }
+
+    fn copy(
+        &mut self,
+        _from: &Self::Path,
+        _to: &Self::Path,
+    ) -> Result<u64, Self::Error> {
+        unimplemented!()
+    }
+
+    fn hard_link(
+        &mut self,
+        _src: &Self::Path,
+        _dst: &Self::Path,
+    ) -> Result<(), Self::Error> {
+        unimplemented!()
+    }
+
+    fn symlink(
+        &mut self,
+        _src: &Self::Path,
+        _dst: &Self::Path,
+    ) -> Result<(), Self::Error> {
+        unimplemented!()
+    }
+
+    fn read_link(
+        &self,
+        _path: &Self::Path,
+    ) -> Result<Self::PathOwned, Self::Error> {
+        unimplemented!()
+    }
+
+    fn canonicalize(
+        &self,
+        _path: &Self::Path,
+    ) -> Result<Self::PathOwned, Self::Error> {
+        unimplemented!()
+    }
+
+    fn create_dir(
+        &mut self,
+        _path: &Self::Path,
+        _options: &DirOptions<Self::Permissions>,
+    ) -> Result<(), Self::Error> {
+        unimplemented!()
+    }
+
+    fn remove_dir(&mut self, _path: &Self::Path) -> Result<(), Self::Error> {
+        unimplemented!()
+    }
+
+    fn remove_dir_all(
+        &mut self,
+        _path: &Self::Path,
+    ) -> Result<(), Self::Error> {
+        unimplemented!()
+    }
+
+    fn read_dir(&self, path: &Self::Path) -> Result<Self::Dir, Self::Error> {
+        let inode = self.open(path, OpenOptions::new().read(true))?;
+        inode.directory().ok_or(Error::NotADirectory {
+            inode: inode.num,
+            name: String::from_utf8_lossy(path).into_owned(),
+        })
+    }
+
+    fn set_permissions(
+        &mut self,
+        _path: &Self::Path,
+        _perm: Self::Permissions,
+    ) -> Result<(), Self::Error> {
+        unimplemented!()
+    }
+}
+
+impl<S: SectorSize, V: Volume<u8, S>> Debug for Synced<Ext2<S, V>> {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        write!(f, "Synced<Ext2<{}>>", S::SIZE)
+    }
+}
+
+#[derive(Debug, Clone)]
+/// A collection of inodes.
+pub struct Inodes<S: SectorSize, V: Volume<u8, S>> {
+    fs: Synced<Ext2<S, V>>,
+    log_block_size: u32,
+    inode_size: usize,
+    inodes_per_group: usize,
+    inodes_count: usize,
+    index: usize,
+}
+
+impl<S: SectorSize, V: Volume<u8, S>> Iterator for Inodes<S, V> {
+    type Item = Inode<S, V>;
+
+    fn next(&mut self) -> Option<Self::Item> {
+        if self.index < self.inodes_count {
+            let block_group = (self.index - 1) / self.inodes_per_group;
+            let index = (self.index - 1) % self.inodes_per_group;
+            self.index += 1;
+
+            let fs = self.fs.inner();
+
+            let inodes_block =
+                fs.block_groups.inner[block_group].inode_table_block;
+
+            let offset = Address::with_block_size(
+                inodes_block,
+                (index * self.inode_size) as i32,
+                self.log_block_size,
+            );
+            let raw = unsafe {
+                RawInode::find_inode(&fs.volume, offset, self.inode_size).ok()
+            };
+            raw.map(|(raw, offset)| {
+                Inode::new(
+                    self.fs.clone(),
+                    raw,
+                    offset,
+                    (self.index - 1) as u32,
+                )
+            })
+        } else {
+            None
+        }
+    }
+}
+
+#[derive(Debug)]
+/// A single inode in an ext2 filesystem.
+pub struct Inode<S: SectorSize, V: Volume<u8, S>> {
+    fs: Synced<Ext2<S, V>>,
+    inner: RawInode,
+    addr: Address<S>,
+    num: u32,
+}
+
+impl<S: SectorSize, V: Volume<u8, S>> Clone for Inode<S, V> {
+    fn clone(&self) -> Self {
+        Inode {
+            fs: self.fs.clone(),
+            inner: self.inner,
+            addr: self.addr,
+            num: self.num,
+        }
+    }
+}
+
+impl<S: SectorSize, V: Volume<u8, S>> Inode<S, V> {
+    ///
+    pub fn new(
+        fs: Synced<Ext2<S, V>>,
+        inner: RawInode,
+        addr: Address<S>,
+        num: u32,
+    ) -> Inode<S, V> {
+        Inode {
+            fs,
+            inner,
+            addr,
+            num,
+        }
+    }
+    /// Read to the end of a buffer.
+    pub fn read_to_end(&self, buf: &mut Vec<u8>) -> Result<usize, Error> {
+        let total_size = self.size();
+        let capacity = buf.capacity();
+        if capacity < total_size {
+            buf.reserve_exact(total_size - capacity);
+        }
+        unsafe {
+            buf.set_len(total_size);
+        }
+        let size = self.read(&mut buf[..]);
+        size.and_then(|size| {
+            unsafe {
+                buf.set_len(size);
+            }
+            Ok(size)
+        })
+        .or_else(|err| {
+            unsafe {
+                buf.set_len(0);
+            }
+            Err(err)
+        })
+    }
+    /// Return blocks on a sector
+    pub fn blocks(&self) -> InodeBlocks<S, V> {
+        InodeBlocks {
+            inode: self.clone(),
+            index: 0,
+        }
+    }
+    /// return a directory iterator
+    pub fn directory(&self) -> Option<Directory<S, V>> {
+        if self.is_dir() {
+            Some(Directory {
+                blocks: self.blocks(),
+                offset: 0,
+                buffer: None,
+                block_size: {
+                    let fs = self.fs.inner();
+                    fs.block_size()
+                },
+            })
+        } else {
+            None
+        }
+    }
+    /// Determine if an inode is a directory
+    pub fn is_dir(&self) -> bool {
+        use sys::inode::TypePerm;
+
+        { self.inner.type_perm }.contains(TypePerm::DIRECTORY)
+        // self.inner.type_perm.contains(TypePerm::DIRECTORY)
+    }
+    ///
+    pub fn block(&self, index: usize) -> Option<NonZeroU32> {
+        self.try_block(index).ok().and_then(|block| block)
+    }
+    /// Try to get a block
+    pub fn try_block(
+        &self,
+        mut index: usize,
+    ) -> Result<Option<NonZeroU32>, Error> {
+        // number of blocks in direct table: 12
+        // number of blocks in indirect table: block_size/4
+        //   why?
+        //     - a block is n bytes long
+        //     - a block address occupies 32 bits, or 4 bytes
+        //     - thus, n/4
+        // number of blocks in doubly table: (block_size/4)^2
+        //   why?
+        //     - every entry in the doubly table points to another block
+        //     - that's n/4 blocks, where n is the block size
+        //     - every block contains n/4 block pointers
+        //     - that's n/4 blocks with n/4 pointers each = (n/4)^2
+        // number of blocks in triply table: (block_size/4)^3
+
+        fn block_index<S: SectorSize, V: Volume<u8, S>>(
+            volume: &V,
+            block: u32,
+            index: usize,
+            log_block_size: u32,
+        ) -> Result<Option<NonZeroU32>, Error> {
+            let offset = (index * 4) as i32;
+            let end = offset + 4;
+            let addr = Address::with_block_size(block, offset, log_block_size);
+            let end = Address::with_block_size(block, end, log_block_size);
+            let block = volume.slice(addr..end);
+            match block {
+                Ok(block) => unsafe {
+                    Ok(NonZeroU32::new(block.dynamic_cast::<u32>().0))
+                },
+                Err(err) => Err(err.into()),
+            }
+        }
+
+        let fs = self.fs.inner();
+
+        let bs4 = fs.block_size() / 4;
+        let log_block_size = fs.log_block_size();
+
+        if index < 12 {
+            return Ok(NonZeroU32::new(self.inner.direct_pointer[index]));
+        }
+
+        index -= 12;
+
+        if index < bs4 {
+            let block = self.inner.indirect_pointer;
+            return block_index(&fs.volume, block, index, log_block_size);
+        }
+
+        index -= bs4;
+
+        if index < bs4 * bs4 {
+            let indirect_index = index >> (log_block_size + 2);
+            let block = match block_index(
+                &fs.volume,
+                self.inner.doubly_indirect,
+                indirect_index,
+                log_block_size,
+            ) {
+                Ok(Some(block)) => block.get(),
+                Ok(None) => return Ok(None),
+                Err(err) => return Err(err),
+            };
+            return block_index(
+                &fs.volume,
+                block,
+                index & (bs4 - 1),
+                log_block_size,
+            );
+        }
+
+        index -= bs4 * bs4;
+
+        if index < bs4 * bs4 * bs4 {
+            let doubly_index = index >> (2 * log_block_size + 4);
+            let indirect = match block_index(
+                &fs.volume,
+                self.inner.triply_indirect,
+                doubly_index,
+                log_block_size,
+            ) {
+                Ok(Some(block)) => block.get(),
+                Ok(None) => return Ok(None),
+                Err(err) => return Err(err),
+            };
+            let indirect_index = (index >> (log_block_size + 2)) & (bs4 - 1);
+            let block = match block_index(
+                &fs.volume,
+                indirect as u32,
+                indirect_index,
+                log_block_size,
+            ) {
+                Ok(Some(block)) => block.get(),
+                Ok(None) => return Ok(None),
+                Err(err) => return Err(err),
+            };
+            return block_index(
+                &fs.volume,
+                block,
+                index & (bs4 - 1),
+                log_block_size,
+            );
+        }
+
+        Ok(None)
+    }
+    ///
+    pub fn in_use(&self) -> bool {
+        self.inner.hard_links > 0
+    }
+    /// return the uid
+    pub fn uid(&self) -> u16 {
+        self.inner.uid
+    }
+    ///
+    pub fn sectors(&self) -> usize {
+        self.inner.sectors_count as usize
+    }
+    ///
+    pub fn size32(&self) -> u32 {
+        self.inner.size_low
+    }
+    ///
+    pub fn size64(&self) -> u64 {
+        self.inner.size_low as u64 | (self.inner.size_high as u64) << 32
+    }
+    ///
+    #[cfg(target_pointer_width = "64")]
+    #[inline]
+    pub fn size(&self) -> usize {
+        self.size64() as usize
+    }
+
+    #[cfg(target_pointer_width = "32")]
+    #[inline]
+    pub fn size(&self) -> usize {
+        self.size32() as usize
+    }
+}
+
+impl<S: SectorSize, V: Volume<u8, S>> File for Inode<S, V> {
+    type Error = Error;
+
+    fn read(&self, buf: &mut [u8]) -> Result<usize, Error> {
+        let total_size = self.size();
+        let block_size = {
+            let fs = self.fs.inner();
+            fs.block_size()
+        };
+        let mut offset = 0;
+
+        for block in self.blocks() {
+            match block {
+                Ok((data, _)) => {
+                    let data_size = block_size
+                        .min(total_size - offset)
+                        .min(buf.len() - offset);
+                    let end = offset + data_size;
+                    buf[offset..end].copy_from_slice(&data[..data_size]);
+                    offset += data_size;
+                }
+                Err(err) => return Err(err.into()),
+            }
+        }
+
+        Ok(offset)
+    }
+
+    fn write(&mut self, _buf: &[u8]) -> Result<usize, Self::Error> {
+        unimplemented!()
+    }
+
+    fn flush(&mut self) -> Result<(), Self::Error> {
+        unimplemented!()
+    }
+
+    fn seek(&mut self, _pos: SeekFrom) -> Result<u64, Self::Error> {
+        unimplemented!()
+    }
+}
+
+///
+#[derive(Debug, Clone)]
+pub struct InodeBlocks<S: SectorSize, V: Volume<u8, S>> {
+    inode: Inode<S, V>,
+    index: usize,
+}
+
+impl<S: SectorSize, V: Volume<u8, S>> Iterator for InodeBlocks<S, V> {
+    type Item = Result<(Vec<u8>, Address<S>), Error>;
+
+    fn next(&mut self) -> Option<Self::Item> {
+        let block = self.inode.try_block(self.index);
+        let block = match block {
+            Ok(Some(ok)) => ok,
+            Ok(None) => return None,
+            Err(err) => return Some(Err(err)),
+        };
+
+        self.index += 1;
+        let fs = self.inode.fs.inner();
+
+        let block = block.get();
+        let log_block_size = fs.log_block_size();
+        let offset = Address::with_block_size(block, 0, log_block_size);
+        let end = Address::with_block_size(block + 1, 0, log_block_size);
+
+        let slice = fs
+            .volume
+            .slice(offset..end)
+            .map(|slice| (slice.to_vec(), offset))
+            .map_err(|err| err.into());
+        Some(slice)
+    }
+}
+
+#[derive(Debug, Clone)]
+/// A directory structure
+pub struct Directory<S: SectorSize, V: Volume<u8, S>> {
+    blocks: InodeBlocks<S, V>,
+    offset: usize,
+    buffer: Option<Vec<u8>>,
+    block_size: usize,
+}
+
+impl<S: SectorSize, V: Volume<u8, S>> Dir<DirectoryEntry, Error>
+    for Directory<S, V>
+{
+}
+
+impl<S: SectorSize, V: Volume<u8, S>> Iterator for Directory<S, V> {
+    type Item = Result<DirectoryEntry, Error>;
+
+    fn next(&mut self) -> Option<Self::Item> {
+        if self.buffer.is_none() || self.offset >= self.block_size {
+            self.buffer = match self.blocks.next() {
+                None => return None,
+                Some(Ok((block, _))) => Some(block),
+                Some(Err(err)) => return Some(Err(err)),
+            };
+
+            self.offset = 0;
+        }
+
+        let buffer = &self.buffer.as_ref().unwrap()[self.offset..];
+
+        let inode = buffer[0] as u32
+            | (buffer[1] as u32) << 8
+            | (buffer[2] as u32) << 16
+            | (buffer[3] as u32) << 24;
+        if inode == 0 {
+            return None;
+        }
+
+        let size = buffer[4] as u16 | (buffer[5] as u16) << 8;
+        let len = buffer[6];
+        let ty = buffer[7];
+
+        let name = buffer[8..8 + len as usize].to_vec();
+
+        self.offset += size as usize;
+
+        Some(Ok(DirectoryEntry {
+            name: name,
+            inode: inode as usize,
+            ty: ty,
+        }))
+    }
+}
+
+#[derive(Clone)]
+/// A directory entry
+pub struct DirectoryEntry {
+    /// The name of the entry
+    pub name: Vec<u8>,
+    /// The inode of the entry
+    pub inode: usize,
+    ///
+    pub ty: u8,
+}
+
+impl DirEntry for DirectoryEntry {
+    type Path = [u8];
+    type PathOwned = Vec<u8>;
+    type Metadata = (); // TODO
+    type FileType = u8; // TODO: enum FileType
+    type Error = Error;
+
+    fn path(&self) -> Self::PathOwned {
+        unimplemented!()
+    }
+
+    fn metadata(&self) -> Result<Self::Metadata, Self::Error> {
+        unimplemented!()
+    }
+
+    fn file_type(&self) -> Result<Self::FileType, Self::Error> {
+        Ok(self.ty)
+    }
+
+    fn file_name(&self) -> &Self::Path {
+        &self.name
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use std::cell::RefCell;
+    use std::fs::File;
+
+    use genfs::{File as GenFile, Fs, OpenOptions};
+
+    use sector::{SectorSize, Size512};
+    use volume::Volume;
+
+    use super::{Ext2, Inode, Synced};
+
+    #[test]
+    fn file() {
+        let file = RefCell::new(File::open("ext2.img").unwrap());
+        let fs = Synced::<Ext2<Size512, _>>::new(file);
+
+        assert!(
+            fs.is_ok(),
+            "Err({:?})",
+            fs.err().unwrap_or_else(|| unreachable!()),
+        );
+
+        let fs = fs.unwrap();
+        let inner = fs.inner();
+
+        let vers = inner.version();
+        println!("version: {}.{}", vers.0, vers.1);
+        assert_eq!(128, inner.inode_size());
+    }
+
+    #[test]
+    fn inodes() {
+        let file = RefCell::new(File::open("ext2.img").unwrap());
+        let fs = Synced::<Ext2<Size512, _>>::new(file);
+
+        assert!(
+            fs.is_ok(),
+            "Err({:?})",
+            fs.err().unwrap_or_else(|| unreachable!()),
+        );
+
+        let fs = fs.unwrap();
+
+        let inodes = fs.inodes().filter(|inode| inode.in_use());
+        for inode in inodes {
+            println!("{:?}", inode);
+        }
+    }
+
+    #[test]
+    fn inode_blocks() {
+        use std::str;
+        let file = RefCell::new(File::open("ext2.img").unwrap());
+        let fs = Synced::<Ext2<Size512, _>>::new(file).unwrap();
+
+        let inodes = fs.inodes().filter(|inode| {
+            inode.in_use() && inode.uid() == 1000 && inode.size() < 1024
+        });
+        for inode in inodes {
+            println!("{:?}", inode);
+            let size = inode.size();
+            for block in inode.blocks() {
+                let (data, _) = block.unwrap();
+                assert_eq!(data.len(), {
+                    let fs = fs.inner();
+                    fs.block_size()
+                });
+                println!("{:?}", &data[..size]);
+                let _ = str::from_utf8(&data[..size])
+                    .map(|string| println!("{}", string));
+            }
+        }
+    }
+
+    #[test]
+    fn read_inode() {
+        let file = RefCell::new(File::open("ext2.img").unwrap());
+        let fs = Synced::<Ext2<Size512, _>>::new(file).unwrap();
+
+        let inodes = fs.inodes().filter(|inode| {
+            inode.in_use() && inode.uid() == 1000 && inode.size() < 1024
+        });
+        for inode in inodes {
+            let mut buf = Vec::with_capacity(inode.size());
+            unsafe {
+                buf.set_len(inode.size());
+            }
+            let size = inode.read(&mut buf[..]);
+            assert!(size.is_ok());
+            let size = size.unwrap();
+            assert_eq!(size, inode.size());
+            unsafe {
+                buf.set_len(size);
+            }
+        }
+    }
+
+    #[test]
+    fn read_big() {
+        let file = RefCell::new(File::open("ext2.img").unwrap());
+        let fs = Synced::<Ext2<Size512, _>>::new(file).unwrap();
+
+        let inodes = fs.inodes().filter(|inode| {
+            inode.in_use() && inode.uid() == 1000 && inode.size() == 537600
+        });
+        for inode in inodes {
+            let mut buf = Vec::with_capacity(inode.size());
+            unsafe {
+                buf.set_len(inode.size());
+            }
+            let size = inode.read(&mut buf[..]);
+            assert!(size.is_ok());
+            let size = size.unwrap();
+            assert_eq!(size, inode.size());
+            unsafe {
+                buf.set_len(size);
+            }
+
+            for (i, &x) in buf.iter().enumerate() {
+                if i & 1 == 0 {
+                    assert_eq!(x, b'u', "{}", i);
+                } else {
+                    assert_eq!(x, b'\n', "{}", i);
+                }
+            }
+        }
+    }
+
+    #[test]
+    fn walkdir() {
+        use std::str;
+
+        fn walk<'vol, S: SectorSize, V: Volume<u8, S>>(
+            fs: &'vol Synced<Ext2<S, V>>,
+            inode: Inode<S, V>,
+            name: String,
+        ) {
+            inode.directory().map(|dir| {
+                for entry in dir {
+                    assert!(entry.is_ok());
+                    let entry = entry.unwrap();
+                    let entry_name = str::from_utf8(&entry.name).unwrap_or("?");
+                    println!("{}/{} => {}", name, entry_name, entry.inode,);
+                    if entry_name != "." && entry_name != ".." {
+                        walk(
+                            fs,
+                            fs.inode_nth(entry.inode).unwrap(),
+                            format!("{}/{}", name, entry_name),
+                        );
+                    }
+                }
+            });
+        }
+
+        let file = RefCell::new(File::open("ext2.img").unwrap());
+        let fs = Synced::<Ext2<Size512, _>>::new(file).unwrap();
+
+        let root = fs.root_inode();
+        walk(&fs, root, String::new());
+    }
+
+    #[test]
+    fn find() {
+        use std::str;
+        let file = RefCell::new(File::open("ext2.img").unwrap());
+        let fs = Synced::<Ext2<Size512, _>>::new(file).unwrap();
+
+        let found = fs.open(b"/home/funky/README.md", &OpenOptions::new());
+
+        assert!(found.is_ok());
+        let inode = found.unwrap();
+        let mut vec = Vec::new();
+        assert!(inode.read_to_end(&mut vec).is_ok());
+        println!("{}", str::from_utf8(&vec).unwrap());
+    }
+}
diff --git a/ext2-rs/src/lib.rs b/ext2-rs/src/lib.rs
new file mode 100644
index 0000000..549af9c
--- /dev/null
+++ b/ext2-rs/src/lib.rs
@@ -0,0 +1,41 @@
+//! Ext2 crate for ableOS
+
+#![deny(missing_docs)]
+#![feature(
+    min_specialization,
+    const_fn_trait_bound,
+    step_trait,
+    associated_type_defaults
+)]
+#![cfg_attr(all(not(test), feature = "no_std"), no_std)]
+
+extern crate alloc;
+
+#[macro_use]
+extern crate bitflags;
+extern crate genfs;
+extern crate spin;
+
+#[cfg(any(test, not(feature = "no_std")))]
+extern crate core;
+
+pub mod error;
+pub mod fs;
+pub mod sector;
+pub mod sys;
+pub mod volume;
+
+#[cfg(test)]
+mod tests {
+    use sys::block_group::*;
+    use sys::inode::*;
+    use sys::superblock::*;
+
+    #[test]
+    fn sizes() {
+        use std::mem::size_of;
+        assert_eq!(size_of::<Superblock>(), 1024);
+        assert_eq!(size_of::<BlockGroupDescriptor>(), 32);
+        assert_eq!(size_of::<Inode>(), 128);
+    }
+}
diff --git a/ext2-rs/src/sector.rs b/ext2-rs/src/sector.rs
new file mode 100644
index 0000000..eb4a392
--- /dev/null
+++ b/ext2-rs/src/sector.rs
@@ -0,0 +1,242 @@
+//! Sector data.
+
+use core::{
+    fmt::{self, Debug, Display, LowerHex},
+    iter::Step,
+    marker::PhantomData,
+    ops::{Add, Sub},
+};
+/// Size of a sector in bytes
+pub trait SectorSize: Clone + Copy + PartialEq + PartialOrd + 'static {
+    /// DOCME: What is this?
+    const LOG_SIZE: u32;
+    /// DOCME: What is this?
+    const SIZE: usize = 1 << Self::LOG_SIZE;
+    /// DOCME: What is this?
+    const OFFSET_MASK: u32 = (Self::SIZE - 1) as u32;
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
+/// DOCME: What is this?    
+pub struct Size512;
+impl SectorSize for Size512 {
+    const LOG_SIZE: u32 = 9;
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
+/// DOCME: What is this?
+pub struct Size1024;
+impl SectorSize for Size1024 {
+    const LOG_SIZE: u32 = 10;
+}
+/// DOCME: What is this?
+#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
+pub struct Size2048;
+impl SectorSize for Size2048 {
+    const LOG_SIZE: u32 = 11;
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
+/// DOCME: What is this?
+pub struct Size4096;
+impl SectorSize for Size4096 {
+    const LOG_SIZE: u32 = 12;
+}
+
+/// Address in a physical sector
+#[derive(Clone, Debug, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
+pub struct Address<S: SectorSize> {
+    sector: u32,
+    offset: u32,
+    _phantom: PhantomData<S>,
+}
+
+impl<S: SectorSize> Address<S> {
+    ///
+    pub unsafe fn new_unchecked(sector: u32, offset: u32) -> Address<S> {
+        assert!((offset as usize) < S::SIZE, "offset out of sector bounds");
+        let _phantom = PhantomData;
+        Address {
+            sector,
+            offset,
+            _phantom,
+        }
+    }
+    ///
+    pub fn new(sector: u32, offset: i32) -> Address<S> {
+        let sector = (sector as i32 + (offset >> S::LOG_SIZE)) as u32;
+        let offset = offset.abs() as u32 & S::OFFSET_MASK;
+        unsafe { Address::new_unchecked(sector, offset) }
+    }
+    ///
+    pub fn with_block_size(
+        block: u32,
+        offset: i32,
+        log_block_size: u32,
+    ) -> Address<S> {
+        let block = (block as i32 + (offset >> log_block_size)) as u32;
+        let offset = offset.abs() as u32 & ((1 << log_block_size) - 1);
+
+        let log_diff = log_block_size as i32 - S::LOG_SIZE as i32;
+        let top_offset = offset >> S::LOG_SIZE;
+        let offset = offset & ((1 << S::LOG_SIZE) - 1);
+        let sector = block << log_diff | top_offset;
+        unsafe { Address::new_unchecked(sector, offset) }
+    }
+    ///
+    pub fn into_index(&self) -> u64 {
+        ((self.sector as u64) << S::LOG_SIZE) + self.offset as u64
+    }
+    /// Get the size of the sector
+    pub const fn sector_size(&self) -> usize {
+        S::SIZE
+    }
+    /// DOCME: What is this?
+    pub const fn log_sector_size(&self) -> u32 {
+        S::LOG_SIZE
+    }
+    /// Return the sector number
+    pub fn sector(&self) -> u32 {
+        self.sector
+    }
+    /// Return the offset in the sector
+    pub fn offset(&self) -> u32 {
+        self.offset
+    }
+}
+
+impl<S: SectorSize> Step for Address<S> {
+    fn steps_between(start: &Self, end: &Self) -> Option<usize> {
+        if end.sector >= start.sector {
+            Some(end.sector as usize - start.sector as usize)
+        } else {
+            None
+        }
+    }
+
+    /*
+    fn replace_one(&mut self) -> Self {
+        mem::replace(self, Address::new(1, 0))
+    }
+
+    fn replace_zero(&mut self) -> Self {
+        mem::replace(self, Address::new(0, 0))
+    }
+
+    fn add_one(&self) -> Self {
+        Address::new(self.sector + 1, 0)
+    }
+
+    fn sub_one(&self) -> Self {
+        Address::new(self.sector - 1, 0)
+    }
+
+    fn add_usize(&self, n: usize) -> Option<Self> {
+        self.sector
+        .checked_add(n as u32)
+        .map(|sector| Address::new(sector, 0))
+    }
+    */
+
+    fn forward_checked(_start: Self, count: usize) -> Option<Self> {
+        todo!("forward_checked:  count: {}", count);
+    }
+
+    fn backward_checked(_start: Self, count: usize) -> Option<Self> {
+        todo!("backward_checked count: {}", count);
+    }
+}
+
+impl<S: SectorSize> Display for Address<S> {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        write!(f, "{}:{}", self.sector, self.offset)
+    }
+}
+
+impl<S: SectorSize> LowerHex for Address<S> {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        write!(f, "{:x}:{:x}", self.sector, self.offset)
+    }
+}
+
+impl<S: SectorSize> From<u64> for Address<S> {
+    fn from(idx: u64) -> Address<S> {
+        let sector = idx >> S::LOG_SIZE;
+        let offset = idx & S::OFFSET_MASK as u64;
+        Address::new(sector as u32, offset as i32)
+    }
+}
+
+impl<S: SectorSize> From<usize> for Address<S> {
+    fn from(idx: usize) -> Address<S> {
+        let sector = idx >> S::LOG_SIZE;
+        let offset = idx & S::OFFSET_MASK as usize;
+        Address::new(sector as u32, offset as i32)
+    }
+}
+
+impl<S: SectorSize> Add for Address<S> {
+    type Output = Address<S>;
+    fn add(self, rhs: Address<S>) -> Address<S> {
+        Address::new(
+            self.sector + rhs.sector,
+            (self.offset + rhs.offset) as i32,
+        )
+    }
+}
+
+impl<S: SectorSize> Sub for Address<S> {
+    type Output = Address<S>;
+    fn sub(self, rhs: Address<S>) -> Address<S> {
+        Address::new(
+            self.sector - rhs.sector,
+            self.offset as i32 - rhs.offset as i32,
+        )
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+
+    #[test]
+    fn conv() {
+        assert_eq!(Address::<Size512>::new(0, 1024).into_index(), 1024);
+        assert_eq!(Address::<Size512>::from(1024_u64).into_index(), 1024);
+        assert_eq!(
+            Address::<Size512>::with_block_size(1, 256, 10).into_index(),
+            1024 + 256
+        );
+        assert_eq!(
+            Address::<Size512>::with_block_size(2, 0, 10).into_index(),
+            2048
+        );
+        assert_eq!(
+            Address::<Size512>::with_block_size(0, 1792, 10).into_index(),
+            1792
+        );
+    }
+
+    #[test]
+    fn arithmetic() {
+        assert_eq!(
+            Address::<Size512>::new(0, 512),
+            Address::<Size512>::new(1, 0),
+        );
+
+        assert_eq!(
+            Address::<Size512>::new(2, -256),
+            Address::<Size512>::new(1, 256),
+        );
+
+        let a = Address::<Size2048>::new(0, 1024);
+        let b = Address::<Size2048>::new(0, 1024);
+        assert_eq!(a + b, Address::<Size2048>::new(1, 0));
+        assert_eq!((a + b).into_index(), 2048);
+
+        let a = Address::<Size512>::new(0, 2048);
+        let b = Address::<Size512>::new(0, 256);
+        assert_eq!(a - b, Address::<Size512>::new(3, 256));
+        assert_eq!((a - b).into_index(), 1792);
+    }
+}
diff --git a/ext2-rs/src/sys/block_group.rs b/ext2-rs/src/sys/block_group.rs
new file mode 100644
index 0000000..90bf165
--- /dev/null
+++ b/ext2-rs/src/sys/block_group.rs
@@ -0,0 +1,116 @@
+//!
+
+use {
+    alloc::vec::Vec,
+    core::{fmt::Debug, mem},
+    error::Error,
+    sector::{Address, SectorSize},
+    volume::Volume,
+};
+
+/// The Block Group Descriptor Table contains a descriptor for each block group
+/// within the file system. The number of block groups within the file system,
+/// and correspondingly, the number of entries in the Block Group Descriptor
+/// Table, is described above. Each descriptor contains information regarding
+/// where important data structures for that group are located.
+///
+/// The (`BlockGroupDescriptor`) table is located in the block immediately
+/// following the Superblock. So if the block size (determined from a field in
+/// the superblock) is 1024 bytes per block, the Block Group Descriptor Table
+/// will begin at block 2. For any other block size, it will begin at block 1.
+/// Remember that blocks are numbered starting at 0, and that block numbers
+/// don't usually correspond to physical block addresses.
+#[repr(C, packed)]
+#[derive(Clone, Debug, Copy)]
+pub struct BlockGroupDescriptor {
+    /// Block address of block usage bitmap
+    pub block_usage_addr: u32,
+    /// Block address of inode usage bitmap
+    pub inode_usage_addr: u32,
+    /// Starting block address of inode table
+    pub inode_table_block: u32,
+    /// Number of unallocated blocks in group
+    pub free_blocks_count: u16,
+    /// Number of unallocated inodes in group
+    pub free_inodes_count: u16,
+    /// Number of directories in group
+    pub dirs_count: u16,
+    #[doc(hidden)]
+    _reserved: [u8; 14],
+}
+
+impl BlockGroupDescriptor {
+    /// Find a descriptor in a descriptor table
+    pub unsafe fn find_descriptor<S: SectorSize, V: Volume<u8, S>>(
+        haystack: &V,
+        offset: Address<S>,
+    ) -> Result<(BlockGroupDescriptor, Address<S>), Error> {
+        let end =
+            offset + Address::from(mem::size_of::<BlockGroupDescriptor>());
+        if haystack.size() < end {
+            return Err(Error::AddressOutOfBounds {
+                sector: end.sector(),
+                offset: end.offset(),
+                size: end.sector_size(),
+            });
+        }
+
+        let descr = haystack
+            .slice_unchecked(offset..end)
+            .dynamic_cast::<BlockGroupDescriptor>();
+
+        Ok(descr)
+    }
+    /// find a descriptor table
+    pub unsafe fn find_descriptor_table<S: SectorSize, V: Volume<u8, S>>(
+        haystack: &V,
+        offset: Address<S>,
+        count: usize,
+    ) -> Result<(Vec<BlockGroupDescriptor>, Address<S>), Error> {
+        let end = offset
+            + Address::from(count * mem::size_of::<BlockGroupDescriptor>());
+        if haystack.size() < end {
+            return Err(Error::AddressOutOfBounds {
+                sector: end.sector(),
+                offset: end.offset(),
+                size: end.sector_size(),
+            });
+        }
+
+        let mut vec = Vec::with_capacity(count);
+        for i in 0..count {
+            let offset = offset
+                + Address::from(i * mem::size_of::<BlockGroupDescriptor>());
+            vec.push({
+                BlockGroupDescriptor::find_descriptor(haystack, offset)?.0
+            });
+        }
+
+        Ok((vec, offset))
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use sector::{Address, Size512};
+
+    #[test]
+    fn find() {
+        let volume = vec![0_u8; 4096];
+        let table = unsafe {
+            BlockGroupDescriptor::find_descriptor_table(
+                &volume,
+                Address::<Size512>::new(4, 0),
+                8,
+            )
+        };
+        assert!(
+            table.is_ok(),
+            "Err({:?})",
+            table.err().unwrap_or_else(|| unreachable!()),
+        );
+        let table = table.unwrap_or_else(|_| unreachable!());
+        assert_eq!(table.0.len(), 8);
+    }
+}
diff --git a/ext2-rs/src/sys/inode.rs b/ext2-rs/src/sys/inode.rs
new file mode 100644
index 0000000..0a129b6
--- /dev/null
+++ b/ext2-rs/src/sys/inode.rs
@@ -0,0 +1,188 @@
+//!
+use {
+    core::{fmt::Debug, mem},
+    error::Error,
+    sector::{Address, SectorSize},
+    volume::Volume,
+};
+
+/// An inode is a structure on the disk that represents a file, directory,
+/// symbolic link, etc. Inodes do not contain the data of the file / directory /
+/// etc. that they represent. Instead, they link to the blocks that actually
+/// contain the data. This lets the inodes themselves have a well-defined size
+/// which lets them be placed in easily indexed arrays. Each block group has an
+/// array of inodes it is responsible for, and conversely every inode within a
+/// file system belongs to one of such tables (and one of such block groups).
+#[repr(C, packed)]
+#[derive(Clone, Debug, Copy)]
+pub struct Inode {
+    /// Type and Permissions (see below)
+    pub type_perm: TypePerm,
+    /// User ID
+    pub uid: u16,
+    /// Lower 32 bits of size in bytes
+    pub size_low: u32,
+    /// Last Access Time (in POSIX time)
+    pub atime: u32,
+    /// Creation Time (in POSIX time)
+    pub ctime: u32,
+    /// Last Modification time (in POSIX time)
+    pub mtime: u32,
+    /// Deletion time (in POSIX time)
+    pub dtime: u32,
+    /// Group ID
+    pub gid: u16,
+    /// Count of hard links (directory entries) to this inode. When this
+    /// reaches 0, the data blocks are marked as unallocated.
+    pub hard_links: u16,
+    /// Count of disk sectors (not Ext2 blocks) in use by this inode, not
+    /// counting the actual inode structure nor directory entries linking
+    /// to the inode.
+    pub sectors_count: u32,
+    /// Flags
+    pub flags: Flags,
+    /// Operating System Specific value #1
+    pub _os_specific_1: [u8; 4],
+    /// Direct block pointers
+    pub direct_pointer: [u32; 12],
+    /// Singly Indirect Block Pointer (Points to a block that is a list of
+    /// block pointers to data)
+    pub indirect_pointer: u32,
+    /// Doubly Indirect Block Pointer (Points to a block that is a list of
+    /// block pointers to Singly Indirect Blocks)
+    pub doubly_indirect: u32,
+    /// Triply Indirect Block Pointer (Points to a block that is a list of
+    /// block pointers to Doubly Indirect Blocks)
+    pub triply_indirect: u32,
+    /// Generation number (Primarily used for NFS)
+    pub gen_number: u32,
+    /// In Ext2 version 0, this field is reserved. In version >= 1,
+    /// Extended attribute block (File ACL).
+    pub ext_attribute_block: u32,
+    /// In Ext2 version 0, this field is reserved. In version >= 1, Upper
+    /// 32 bits of file size (if feature bit set) if it's a file,
+    /// Directory ACL if it's a directory
+    pub size_high: u32,
+    /// Block address of fragment
+    pub frag_block_addr: u32,
+    /// Operating System Specific Value #2
+    pub _os_specific_2: [u8; 12],
+}
+
+impl Inode {
+    /// Discover the inode location on the disk.
+    pub unsafe fn find_inode<S: SectorSize, V: Volume<u8, S>>(
+        haystack: &V,
+        offset: Address<S>,
+        size: usize,
+    ) -> Result<(Inode, Address<S>), Error> {
+        if size != mem::size_of::<Inode>() {
+            unimplemented!("inodes with a size != 128");
+        }
+
+        let end = offset + Address::from(size);
+        if haystack.size() < end {
+            return Err(Error::AddressOutOfBounds {
+                sector: end.sector(),
+                offset: end.offset(),
+                size: end.sector_size(),
+            });
+        }
+
+        let inode = haystack
+            .slice_unchecked(offset..end)
+            .dynamic_cast::<Inode>();
+
+        Ok(inode)
+    }
+}
+
+bitflags! {
+    ///
+    // #[derive(Copy)]
+    pub struct TypePerm: u16 {
+        /// FIFO
+        const FIFO = 0x1000;
+        /// Character device
+        const CHAR_DEVICE = 0x2000;
+        /// Directory
+        const DIRECTORY = 0x4000;
+        /// Block device
+        const BLOCK_DEVICE = 0x6000;
+        /// Regular file
+        const FILE = 0x8000;
+        /// Symbolic link
+        const SYMLINK = 0xA000;
+        /// Unix socket
+        const SOCKET = 0xC000;
+        /// Other—execute permission
+        const O_EXEC = 0x001;
+        /// Other—write permission
+        const O_WRITE = 0x002;
+        /// Other—read permission
+        const O_READ = 0x004;
+        /// Group—execute permission
+        const G_EXEC = 0x008;
+        /// Group—write permission
+        const G_WRITE = 0x010;
+        /// Group—read permission
+        const G_READ = 0x020;
+        /// User—execute permission
+        const U_EXEC = 0x040;
+        /// User—write permission
+        const U_WRITE = 0x080;
+        /// User—read permission
+        const U_READ = 0x100;
+        /// Sticky Bit
+        const STICKY = 0x200;
+        /// Set group ID
+        const SET_GID = 0x400;
+        /// Set user ID
+        const SET_UID = 0x800;
+    }
+}
+
+bitflags! {
+    /// Flags
+    pub struct Flags: u32 {
+        /// Secure deletion (not used)
+        const SECURE_DEL = 0x00000001;
+        /// Keep a copy of data when deleted (not used)
+        const KEEP_COPY = 0x00000002;
+        /// File compression (not used)
+        const COMPRESSION = 0x00000004;
+        /// Synchronous updates—new data is written immediately to disk
+        const SYNC_UPDATE = 0x00000008;
+        /// Immutable file (content cannot be changed)
+        const IMMUTABLE = 0x00000010;
+        /// Append only
+        const APPEND_ONLY = 0x00000020;
+        /// File is not included in 'dump' command
+        const NODUMP = 0x00000040;
+        /// Last accessed time should not updated
+        const DONT_ATIME = 0x00000080;
+        /// Hash indexed directory
+        const HASH_DIR = 0x00010000;
+        /// AFS directory
+        const AFS_DIR = 0x00020000;
+        /// Journal file data
+        const JOURNAL_DATA = 0x00040000;
+    }
+}
+
+/// Unknown entry type
+pub const UNKNOWN: u8 = 0;
+/// FIFO entry type
+pub const FIFO: u8 = 1;
+/// Character device entry type
+pub const CHAR_DEVICE: u8 = 2;
+/// Directory entry type
+pub const DIRECTORY: u8 = 3;
+/// Block device entry type
+pub const BLOCK_DEVICE: u8 = 4;
+/// Regular file entry type
+pub const FILE: u8 = 5;
+/// Symbolic link entry type
+pub const SYMLINK: u8 = 6;
+/// Unix socket entry type
+pub const SOCKET: u8 = 7;
diff --git a/ext2-rs/src/sys/mod.rs b/ext2-rs/src/sys/mod.rs
new file mode 100644
index 0000000..32ec8fb
--- /dev/null
+++ b/ext2-rs/src/sys/mod.rs
@@ -0,0 +1,5 @@
+//!
+
+pub mod block_group;
+pub mod inode;
+pub mod superblock;
diff --git a/ext2-rs/src/sys/superblock.rs b/ext2-rs/src/sys/superblock.rs
new file mode 100644
index 0000000..d97952e
--- /dev/null
+++ b/ext2-rs/src/sys/superblock.rs
@@ -0,0 +1,284 @@
+//! Superblock information
+
+use {
+    core::{fmt::Debug, mem},
+    error::Error,
+    sector::{Address, SectorSize},
+    volume::Volume,
+};
+
+/// Ext2 signature (0xef53), used to help confirm the presence of Ext2 on a
+/// volume
+pub const EXT2_MAGIC: u16 = 0xef53;
+
+/// Filesystem is free of errors
+pub const FS_CLEAN: u16 = 1;
+/// Filesystem has errors
+pub const FS_ERR: u16 = 2;
+
+/// Ignore errors
+pub const ERR_IGNORE: u16 = 1;
+/// Remount as read-only on error
+pub const ERR_RONLY: u16 = 2;
+/// Panic on error
+pub const ERR_PANIC: u16 = 3;
+
+/// Creator OS is Linux
+pub const OS_LINUX: u32 = 0;
+/// Creator OS is Hurd
+pub const OS_HURD: u32 = 1;
+/// Creator OS is Masix
+pub const OS_MASIX: u32 = 2;
+/// Creator OS is FreeBSD
+pub const OS_FREEBSD: u32 = 3;
+/// Creator OS is a BSD4.4-Lite derivative
+pub const OS_LITE: u32 = 4;
+
+/// The Superblock contains all information about the layout of the file system
+/// and possibly contains other important information like what optional
+/// features were used to create the file system.
+///
+/// The Superblock is always located at byte 1024 from the beginning of the
+/// volume and is exactly 1024 bytes in length. For example, if the disk uses
+/// 512 byte sectors, the Superblock will begin at LBA 2 and will occupy all of
+/// sector 2 and 3.
+#[repr(C, packed)]
+#[derive(Clone, Debug, Copy)]
+pub struct Superblock {
+    // taken from https://wiki.osdev.org/Ext2
+    /// Total number of inodes in file system
+    pub inodes_count: u32,
+    /// Total number of blocks in file system
+    pub blocks_count: u32,
+    /// Number of blocks reserved for superuser (see offset 80)
+    pub r_blocks_count: u32,
+    /// Total number of unallocated blocks
+    pub free_blocks_count: u32,
+    /// Total number of unallocated inodes
+    pub free_inodes_count: u32,
+    /// Block number of the block containing the superblock
+    pub first_data_block: u32,
+    /// log2 (block size) - 10. (In other words, the number to shift 1,024
+    /// to the left by to obtain the block size)
+    pub log_block_size: u32,
+    /// log2 (fragment size) - 10. (In other words, the number to shift
+    /// 1,024 to the left by to obtain the fragment size)
+    pub log_frag_size: i32,
+    /// Number of blocks in each block group
+    pub blocks_per_group: u32,
+    /// Number of fragments in each block group
+    pub frags_per_group: u32,
+    /// Number of inodes in each block group
+    pub inodes_per_group: u32,
+    /// Last mount time (in POSIX time)
+    pub mtime: u32,
+    /// Last written time (in POSIX time)
+    pub wtime: u32,
+    /// Number of times the volume has been mounted since its last
+    /// consistency check (fsck)
+    pub mnt_count: u16,
+    /// Number of mounts allowed before a consistency check (fsck) must be
+    /// done
+    pub max_mnt_count: i16,
+    /// Ext2 signature (0xef53), used to help confirm the presence of Ext2
+    /// on a volume
+    pub magic: u16,
+    /// File system state (see `FS_CLEAN` and `FS_ERR`)
+    pub state: u16,
+    /// What to do when an error is detected (see `ERR_IGNORE`, `ERR_RONLY` and
+    /// `ERR_PANIC`)
+    pub errors: u16,
+    /// Minor portion of version (combine with Major portion below to
+    /// construct full version field)
+    pub rev_minor: u16,
+    /// POSIX time of last consistency check (fsck)
+    pub lastcheck: u32,
+    /// Interval (in POSIX time) between forced consistency checks (fsck)
+    pub checkinterval: u32,
+    /// Operating system ID from which the filesystem on this volume was
+    /// created
+    pub creator_os: u32,
+    /// Major portion of version (combine with Minor portion above to
+    /// construct full version field)
+    pub rev_major: u32,
+    /// User ID that can use reserved blocks
+    pub block_uid: u16,
+    /// Group ID that can use reserved blocks
+    pub block_gid: u16,
+
+    /// First non-reserved inode in file system.
+    pub first_inode: u32,
+    /// SectorSize of each inode structure in bytes.
+    pub inode_size: u16,
+    /// Block group that this superblock is part of (if backup copy)
+    pub block_group: u16,
+    /// Optional features present (features that are not required to read
+    /// or write, but usually result in a performance increase)
+    pub features_opt: FeaturesOptional,
+    /// Required features present (features that are required to be
+    /// supported to read or write)
+    pub features_req: FeaturesRequired,
+    /// Features that if not supported, the volume must be mounted
+    /// read-only)
+    pub features_ronly: FeaturesROnly,
+    /// File system ID (what is output by blkid)
+    pub fs_id: [u8; 16],
+    /// Volume name (C-style string: characters terminated by a 0 byte)
+    pub volume_name: [u8; 16],
+    /// Path volume was last mounted to (C-style string: characters
+    /// terminated by a 0 byte)
+    pub last_mnt_path: [u8; 64],
+    /// Compression algorithms used (see Required features above)
+    pub compression: u32,
+    /// Number of blocks to preallocate for files
+    pub prealloc_blocks_files: u8,
+    /// Number of blocks to preallocate for directories
+    pub prealloc_blocks_dirs: u8,
+    #[doc(hidden)]
+    _unused: [u8; 2],
+    /// Journal ID (same style as the File system ID above)
+    pub journal_id: [u8; 16],
+    /// Journal inode
+    pub journal_inode: u32,
+    /// Journal device
+    pub journal_dev: u32,
+    /// Head of orphan inode list
+    pub journal_orphan_head: u32,
+    #[doc(hidden)]
+    _reserved: [u8; 788],
+}
+impl Superblock {
+    /// Discover the location of the superblock in the given block device.
+    pub unsafe fn find<S: SectorSize, V: Volume<u8, S>>(
+        haystack: &V,
+    ) -> Result<(Superblock, Address<S>), Error> {
+        let offset = Address::from(1024_usize);
+        let end = offset + Address::from(mem::size_of::<Superblock>());
+        if haystack.size() < end {
+            return Err(Error::AddressOutOfBounds {
+                sector: end.sector(),
+                offset: end.offset(),
+                size: end.sector_size(),
+            });
+        }
+
+        let superblock = {
+            haystack
+                .slice_unchecked(offset..end)
+                .dynamic_cast::<Superblock>()
+        };
+
+        if superblock.0.magic != EXT2_MAGIC {
+            Err(Error::BadMagic {
+                magic: superblock.0.magic,
+            })
+        } else {
+            Ok(superblock)
+        }
+    }
+
+    #[inline]
+    /// Return the block size
+    pub fn block_size(&self) -> usize {
+        1024 << self.log_block_size
+    }
+
+    #[inline]
+    /// Return the fragment size
+    pub fn frag_size(&self) -> usize {
+        1024 << self.log_frag_size
+    }
+    /// Return the number of blocks per group
+    pub fn block_group_count(&self) -> Result<u32, (u32, u32)> {
+        let blocks_mod = self.blocks_count % self.blocks_per_group;
+        let inodes_mod = self.inodes_count % self.inodes_per_group;
+        let blocks_inc = if blocks_mod == 0 { 0 } else { 1 };
+        let inodes_inc = if inodes_mod == 0 { 0 } else { 1 };
+        let by_blocks = self.blocks_count / self.blocks_per_group + blocks_inc;
+        let by_inodes = self.inodes_count / self.inodes_per_group + inodes_inc;
+        if by_blocks == by_inodes {
+            Ok(by_blocks)
+        } else {
+            Err((by_blocks, by_inodes))
+        }
+    }
+}
+
+bitflags! {
+    /// Optional features
+    pub struct FeaturesOptional: u32 {
+        /// Preallocate some number of (contiguous?) blocks (see
+        /// `Superblock::prealloc_blocks_dirs`) to a directory when creating a new one
+        const PREALLOCATE = 0x0001;
+        /// AFS server inodes exist
+        const AFS = 0x0002;
+        /// File system has a journal (Ext3)
+        const JOURNAL = 0x0004;
+        /// Inodes have extended attributes
+        const EXTENDED_INODE = 0x0008;
+        /// File system can resize itself for larger partitions
+        const SELF_RESIZE = 0x0010;
+        /// Directories use hash index
+        const HASH_INDEX = 0x0020;
+    }
+}
+
+bitflags! {
+    /// Required features. If these are not supported; can't mount
+    pub struct FeaturesRequired: u32 {
+        /// Compression is used
+        const REQ_COMPRESSION = 0x0001;
+        /// Directory entries contain a type field
+        const REQ_DIRECTORY_TYPE = 0x0002;
+        /// File system needs to replay its journal
+        const REQ_REPLAY_JOURNAL = 0x0004;
+        /// File system uses a journal device
+        const REQ_JOURNAL_DEVICE = 0x0008;
+    }
+}
+
+bitflags! {
+    /// ROnly features. If these are not supported; remount as read-only
+    pub struct FeaturesROnly: u32 {
+        /// Sparse superblocks and group descriptor tables
+        const RONLY_SPARSE = 0x0001;
+        /// File system uses a 64-bit file size
+        const RONLY_FILE_SIZE_64 = 0x0002;
+        /// Directory contents are stored in the form of a Binary Tree
+        const RONLY_BTREE_DIRECTORY = 0x0004;
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use sector::Size512;
+
+    #[test]
+    fn find() {
+        let mut volume = vec![0_u8; 4096];
+        // magic
+        volume[1024 + 56] = EXT2_MAGIC as u8;
+        volume[1024 + 57] = (EXT2_MAGIC >> 8) as u8;
+        let superblock = unsafe { Superblock::find::<Size512, _>(&volume) };
+        assert!(
+            superblock.is_ok(),
+            "Err({:?})",
+            superblock.err().unwrap_or_else(|| unreachable!()),
+        );
+    }
+
+    #[test]
+    fn superblock() {
+        use std::cell::RefCell;
+        use std::fs::File;
+
+        let file = RefCell::new(File::open("ext2.img").unwrap());
+        let superblock = unsafe { Superblock::find::<Size512, _>(&file) };
+        assert!(
+            superblock.is_ok(),
+            "Err({:?})",
+            superblock.err().unwrap_or_else(|| unreachable!()),
+        );
+    }
+}
diff --git a/ext2-rs/src/volume/mod.rs b/ext2-rs/src/volume/mod.rs
new file mode 100644
index 0000000..386fdda
--- /dev/null
+++ b/ext2-rs/src/volume/mod.rs
@@ -0,0 +1,371 @@
+#![allow(missing_docs)]
+use {
+    alloc::{
+        borrow::{Cow, ToOwned},
+        boxed::Box,
+        vec::Vec,
+    },
+    core::{
+        mem,
+        ops::{Deref, DerefMut, Range},
+        slice,
+    },
+    error::Error,
+    sector::{Address, SectorSize},
+};
+
+pub mod size;
+use self::size::Size;
+
+pub trait Volume<T: Clone, S: SectorSize> {
+    type Error: Into<Error>;
+
+    fn size(&self) -> Size<S>;
+    fn commit(
+        &mut self,
+        slice: Option<VolumeCommit<T, S>>,
+    ) -> Result<(), Self::Error>;
+    unsafe fn slice_unchecked<'a>(
+        &'a self,
+        range: Range<Address<S>>,
+    ) -> VolumeSlice<'a, T, S>;
+
+    fn slice<'a>(
+        &'a self,
+        range: Range<Address<S>>,
+    ) -> Result<VolumeSlice<'a, T, S>, Self::Error>;
+}
+
+#[derive(Debug, Clone, PartialEq, Hash)]
+pub struct VolumeSlice<'a, T: 'a + Clone, S: SectorSize> {
+    inner: Cow<'a, [T]>,
+    index: Address<S>,
+}
+
+impl<T: Clone, S: SectorSize> VolumeSlice<'static, T, S> {
+    pub fn with_static(inner: &'static [T]) -> VolumeSlice<'static, T, S> {
+        VolumeSlice {
+            inner: Cow::Borrowed(inner),
+            index: Address::new(0, 0),
+        }
+    }
+
+    pub fn new_owned(
+        inner: <[T] as ToOwned>::Owned,
+        index: Address<S>,
+    ) -> VolumeSlice<'static, T, S> {
+        VolumeSlice {
+            inner: Cow::Owned(inner),
+            index,
+        }
+    }
+}
+
+impl<'a, T: Clone, S: SectorSize> VolumeSlice<'a, T, S> {
+    pub fn new(inner: &'a [T], index: Address<S>) -> VolumeSlice<'a, T, S> {
+        VolumeSlice {
+            inner: Cow::Borrowed(inner),
+            index,
+        }
+    }
+
+    pub fn is_mutated(&self) -> bool {
+        match self.inner {
+            Cow::Borrowed(_) => false,
+            Cow::Owned(_) => true,
+        }
+    }
+
+    pub fn address(&self) -> Address<S> {
+        self.index
+    }
+}
+
+impl<'a, S: SectorSize> VolumeSlice<'a, u8, S> {
+    pub unsafe fn dynamic_cast<T: Copy>(&self) -> (T, Address<S>) {
+        assert!(self.inner.len() >= mem::size_of::<T>());
+        let index = self.index;
+        let cast = self.inner.as_ptr().cast::<T>().read_unaligned();
+
+        // mem::transmute_copy(self.inner.as_ptr().as_ref().unwrap());
+        (cast, index)
+    }
+
+    pub fn from_cast<T: Copy>(
+        cast: &'a T,
+        index: Address<S>,
+    ) -> VolumeSlice<'a, u8, S> {
+        let len = mem::size_of::<T>();
+        let ptr = cast as *const T as *const u8;
+        let slice = unsafe { slice::from_raw_parts(ptr, len) };
+        VolumeSlice::new(slice, index)
+    }
+}
+
+impl<'a, T: Clone, S: SectorSize> VolumeSlice<'a, T, S> {
+    pub fn commit(self) -> Option<VolumeCommit<T, S>> {
+        if self.is_mutated() {
+            Some(VolumeCommit::new(self.inner.into_owned(), self.index))
+        } else {
+            None
+        }
+    }
+}
+
+impl<'a, T: Clone, S: SectorSize> AsRef<[T]> for VolumeSlice<'a, T, S> {
+    fn as_ref(&self) -> &[T] {
+        self.inner.as_ref()
+    }
+}
+
+impl<'a, T: Clone, S: SectorSize> AsMut<[T]> for VolumeSlice<'a, T, S> {
+    fn as_mut(&mut self) -> &mut [T] {
+        self.inner.to_mut().as_mut()
+    }
+}
+
+impl<'a, T: Clone, S: SectorSize> Deref for VolumeSlice<'a, T, S> {
+    type Target = [T];
+
+    fn deref(&self) -> &Self::Target {
+        self.as_ref()
+    }
+}
+
+impl<'a, T: Clone, S: SectorSize> DerefMut for VolumeSlice<'a, T, S> {
+    fn deref_mut(&mut self) -> &mut Self::Target {
+        self.as_mut()
+    }
+}
+
+pub struct VolumeCommit<T, S: SectorSize> {
+    inner: Vec<T>,
+    index: Address<S>,
+}
+
+impl<T: Clone, S: SectorSize> VolumeCommit<T, S> {
+    pub fn with_vec(inner: Vec<T>) -> VolumeCommit<T, S> {
+        VolumeCommit {
+            inner,
+            index: Address::new(0, 0),
+        }
+    }
+}
+
+impl<T: Clone, S: SectorSize> VolumeCommit<T, S> {
+    pub fn new(inner: Vec<T>, index: Address<S>) -> VolumeCommit<T, S> {
+        VolumeCommit { inner, index }
+    }
+
+    pub fn into_inner(self) -> Vec<T> {
+        self.inner
+    }
+
+    pub fn address(&self) -> Address<S> {
+        self.index
+    }
+}
+
+impl<T: Clone, S: SectorSize> AsRef<[T]> for VolumeCommit<T, S> {
+    fn as_ref(&self) -> &[T] {
+        self.inner.as_ref()
+    }
+}
+
+impl<T: Clone, S: SectorSize> AsMut<[T]> for VolumeCommit<T, S> {
+    fn as_mut(&mut self) -> &mut [T] {
+        self.inner.as_mut()
+    }
+}
+
+impl<T: Clone, S: SectorSize> Deref for VolumeCommit<T, S> {
+    type Target = [T];
+
+    fn deref(&self) -> &Self::Target {
+        self.as_ref()
+    }
+}
+
+impl<T: Clone, S: SectorSize> DerefMut for VolumeCommit<T, S> {
+    fn deref_mut(&mut self) -> &mut Self::Target {
+        self.as_mut()
+    }
+}
+
+macro_rules! impl_slice {
+    (@inner $volume:ty $( , $lt:lifetime )* ) => {
+        impl<$( $lt, )* T: Clone, S: SectorSize> Volume<T, S>
+            for $volume
+        {
+            type Error = Error;
+
+            fn size(&self) -> Size<S> {
+                Size::Bounded(
+                    Address::from(<Self as AsRef<[T]>>::as_ref(self).len())
+                )
+            }
+
+            fn commit(
+                &mut self,
+                slice: Option<VolumeCommit<T, S>>,
+            ) -> Result<(), Self::Error> {
+                slice.map(|slice| {
+                    let index = slice.address().into_index() as usize;
+                    let end = index + slice.as_ref().len();
+                    // XXX: it would be much better to drop the contents of dst
+                    // and move the contents of slice instead of cloning
+                    let dst =
+                        &mut <Self as AsMut<[T]>>::as_mut(self)[index..end];
+                    dst.clone_from_slice(slice.as_ref());
+                });
+                Ok(())
+            }
+
+            unsafe fn slice_unchecked<'a>(
+                &'a self,
+                range: Range<Address<S>>,
+            ) -> VolumeSlice<'a, T, S> {
+                let index = range.start;
+                let range = range.start.into_index() as usize
+                    ..range.end.into_index() as usize;
+                VolumeSlice::new(
+                    <Self as AsRef<[T]>>::as_ref(self).get_unchecked(range),
+                    index,
+                )
+            }
+
+            fn slice<'a>(
+                &'a self,
+                range: Range<Address<S>>,
+            ) -> Result<VolumeSlice<'a, T, S>, Self::Error> {
+                if self.size() >= range.end {
+                    unsafe { Ok(self.slice_unchecked(range)) }
+                } else {
+                    Err(Error::AddressOutOfBounds {
+                        sector: range.end.sector(),
+                        offset: range.end.offset(),
+                        size: range.end.sector_size()
+                    })
+                }
+            }
+        }
+    };
+    ($volume:ty) => {
+        impl_slice!(@inner $volume);
+    };
+    ($volume:ty $( , $lt:lifetime )* ) => {
+        impl_slice!(@inner $volume $( , $lt )* );
+    };
+}
+
+impl_slice!(&'b mut [T], 'b);
+impl_slice!(Vec<T>);
+impl_slice!(Box<[T]>);
+
+#[cfg(any(test, not(feature = "no_std")))]
+mod file {
+    use std::cell::RefCell;
+    use std::fs::File;
+    use std::io::{self, Read, Seek, SeekFrom, Write};
+    use std::ops::Range;
+
+    use sector::{Address, SectorSize};
+
+    use super::size::Size;
+    use super::{Volume, VolumeCommit, VolumeSlice};
+
+    impl<S: SectorSize> Volume<u8, S> for RefCell<File> {
+        type Error = io::Error;
+
+        fn size(&self) -> Size<S> {
+            Size::Bounded(
+                self.borrow()
+                    .metadata()
+                    .map(|data| Address::from(data.len()))
+                    .unwrap_or(Address::new(0, 0)),
+            )
+        }
+
+        fn commit(
+            &mut self,
+            slice: Option<VolumeCommit<u8, S>>,
+        ) -> Result<(), Self::Error> {
+            slice
+                .map(|slice| {
+                    let index = slice.address();
+                    let mut refmut = self.borrow_mut();
+                    refmut
+                        .seek(SeekFrom::Start(index.into_index()))
+                        .and_then(|_| refmut.write(slice.as_ref()))
+                        .map(|_| ())
+                })
+                .unwrap_or(Ok(()))
+        }
+
+        unsafe fn slice_unchecked<'a>(
+            &'a self,
+            range: Range<Address<S>>,
+        ) -> VolumeSlice<'a, u8, S> {
+            let index = range.start;
+            let len = range.end - range.start;
+            let mut vec = Vec::with_capacity(len.into_index() as usize);
+            vec.set_len(len.into_index() as usize);
+            let mut refmut = self.borrow_mut();
+            refmut
+                .seek(SeekFrom::Start(index.into_index()))
+                .and_then(|_| refmut.read_exact(&mut vec[..]))
+                .unwrap_or_else(|err| {
+                    panic!("could't read from File Volume: {:?}", err)
+                });
+            VolumeSlice::new_owned(vec, index)
+        }
+
+        fn slice<'a>(
+            &'a self,
+            range: Range<Address<S>>,
+        ) -> Result<VolumeSlice<'a, u8, S>, Self::Error> {
+            let index = range.start;
+            let mut vec = Vec::with_capacity(
+                (range.end - range.start).into_index() as usize,
+            );
+            unsafe {
+                vec.set_len((range.end - range.start).into_index() as usize);
+            }
+            let mut refmut = self.borrow_mut();
+            refmut
+                .seek(SeekFrom::Start(index.into_index()))
+                .and_then(|_| refmut.read_exact(&mut vec[..]))
+                .map(move |_| VolumeSlice::new_owned(vec, index))
+        }
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use sector::{Address, Size512};
+
+    #[test]
+    fn volume() {
+        let mut volume = vec![0; 1024];
+        let commit = {
+            let mut slice = volume
+                .slice(
+                    Address::<Size512>::from(256_u64)
+                        ..Address::<Size512>::from(512_u64),
+                )
+                .unwrap();
+            slice.iter_mut().for_each(|x| *x = 1);
+            slice.commit()
+        };
+        assert!(volume.commit(commit).is_ok());
+
+        for (i, &x) in volume.iter().enumerate() {
+            if i < 256 || i >= 512 {
+                assert_eq!(x, 0);
+            } else {
+                assert_eq!(x, 1);
+            }
+        }
+    }
+}
diff --git a/ext2-rs/src/volume/size.rs b/ext2-rs/src/volume/size.rs
new file mode 100644
index 0000000..b8b5b26
--- /dev/null
+++ b/ext2-rs/src/volume/size.rs
@@ -0,0 +1,115 @@
+//!
+
+use {
+    core::{
+        cmp::Ordering,
+        fmt::{self, Display},
+    },
+    sector::{Address, SectorSize},
+};
+
+#[derive(Clone, Copy, Debug, Hash)]
+/// A size
+pub enum Size<S: SectorSize> {
+    /// An unbounded size
+    Unbounded,
+    /// A bounded size
+    Bounded(Address<S>),
+}
+
+impl<S: SectorSize> Size<S> {
+    /// Try to get the length of the sector
+    pub fn try_len(&self) -> Option<Address<S>> {
+        match *self {
+            Size::Unbounded => None,
+            Size::Bounded(n) => Some(n),
+        }
+    }
+    /// Get the length of the sector unsafely
+    ///
+    /// # Safety
+    ///
+    /// This function is unsafe because it does not check that the size is
+    /// bounded.
+    pub unsafe fn len(&self) -> Address<S> {
+        match *self {
+            Size::Unbounded => panic!(
+                "attempt to convert `Size::Unbounded` to a concrete length"
+            ),
+            Size::Bounded(n) => n,
+        }
+    }
+}
+
+impl<S: SectorSize> Size<S> {
+    /// Check if the size is unbounded
+    pub fn is_bounded(&self) -> bool {
+        match *self {
+            Size::Unbounded => false,
+            Size::Bounded(_) => true,
+        }
+    }
+}
+
+impl<S: SectorSize> Display for Size<S> {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        match *self {
+            Size::Unbounded => write!(f, "Unbounded"),
+            Size::Bounded(n) => write!(f, "Bounded({})", n),
+        }
+    }
+}
+
+impl<S: SectorSize> PartialEq for Size<S> {
+    fn eq(&self, rhs: &Self) -> bool {
+        match (self, rhs) {
+            (&Size::Unbounded, _) => false,
+            (_, &Size::Unbounded) => false,
+            (&Size::Bounded(ref a), &Size::Bounded(ref b)) => a.eq(b),
+        }
+    }
+
+    fn ne(&self, rhs: &Self) -> bool {
+        match (self, rhs) {
+            (&Size::Unbounded, _) => false,
+            (_, &Size::Unbounded) => false,
+            (&Size::Bounded(ref a), &Size::Bounded(ref b)) => a.ne(b),
+        }
+    }
+}
+
+impl<S: SectorSize> PartialEq<Address<S>> for Size<S> {
+    fn eq(&self, rhs: &Address<S>) -> bool {
+        match *self {
+            Size::Unbounded => false,
+            Size::Bounded(ref n) => n.eq(rhs),
+        }
+    }
+
+    fn ne(&self, rhs: &Address<S>) -> bool {
+        match *self {
+            Size::Unbounded => false,
+            Size::Bounded(ref n) => n.eq(rhs),
+        }
+    }
+}
+
+impl<S: SectorSize> PartialOrd for Size<S> {
+    fn partial_cmp(&self, rhs: &Self) -> Option<Ordering> {
+        match (self, rhs) {
+            (&Size::Unbounded, &Size::Unbounded) => None,
+            (&Size::Unbounded, _) => Some(Ordering::Greater),
+            (_, &Size::Unbounded) => Some(Ordering::Less),
+            (&Size::Bounded(ref a), &Size::Bounded(ref b)) => a.partial_cmp(b),
+        }
+    }
+}
+
+impl<S: SectorSize> PartialOrd<Address<S>> for Size<S> {
+    fn partial_cmp(&self, rhs: &Address<S>) -> Option<Ordering> {
+        match *self {
+            Size::Unbounded => Some(Ordering::Greater),
+            Size::Bounded(ref n) => n.partial_cmp(rhs),
+        }
+    }
+}
diff --git a/rust-toolchain.toml b/rust-toolchain.toml
index c2a0ee9..d32c413 100644
--- a/rust-toolchain.toml
+++ b/rust-toolchain.toml
@@ -1,3 +1,3 @@
 [toolchain]
 channel = "nightly"
-components = ["rust-src", "llvm-tools-preview"]
\ No newline at end of file
+components = ["rust-src", "llvm-tools-preview"]