Implement most of the scheme.
This commit is contained in:
Generated
+17
@@ -1,5 +1,22 @@
|
||||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "1.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693"
|
||||
|
||||
[[package]]
|
||||
name = "ramfs"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"redox_syscall",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "redox_syscall"
|
||||
version = "0.1.56"
|
||||
source = "git+https://gitlab.redox-os.org/redox-os/syscall.git#8d0015be8693a81c2a4459f3c09fb47b98ff07b1"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
]
|
||||
|
||||
@@ -7,3 +7,4 @@ edition = "2018"
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
redox_syscall = { git = "https://gitlab.redox-os.org/redox-os/syscall.git" }
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
use std::collections::BTreeMap;
|
||||
use std::os::unix::io::AsRawFd;
|
||||
use std::fs;
|
||||
|
||||
use syscall::{Error, Result, StatVfs, TimeSpec};
|
||||
use syscall::error::EIO;
|
||||
use syscall::flag::{O_RDONLY, O_STAT};
|
||||
|
||||
pub struct File {
|
||||
pub mode: u16,
|
||||
pub uid: u32,
|
||||
pub gid: u32,
|
||||
pub ino: usize,
|
||||
pub nlink: usize,
|
||||
|
||||
pub atime: TimeSpec,
|
||||
pub ctime: TimeSpec,
|
||||
pub mtime: TimeSpec,
|
||||
pub crtime: TimeSpec,
|
||||
|
||||
pub data: FileData,
|
||||
}
|
||||
|
||||
pub enum FileData {
|
||||
File(Vec<u8>),
|
||||
Directory(Vec<Vec<u8>>),
|
||||
}
|
||||
impl FileData {
|
||||
pub fn size(&self) -> usize {
|
||||
match self {
|
||||
&Self::File(ref data) => data.len(),
|
||||
&Self::Directory(ref names) => names.iter().map(|name| name.len()).sum(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Filesystem {
|
||||
pub files: BTreeMap<usize, File>,
|
||||
pub tree: BTreeMap<Vec<u8>, usize>,
|
||||
pub memory_file: fs::File,
|
||||
}
|
||||
impl Filesystem {
|
||||
pub const DEFAULT_BLOCK_SIZE: u32 = 4096;
|
||||
|
||||
pub fn new() -> Result<Self> {
|
||||
Ok(Self {
|
||||
files: BTreeMap::new(),
|
||||
tree: BTreeMap::new(),
|
||||
memory_file: fs::File::open("memory:").or(Err(Error::new(EIO)))?,
|
||||
})
|
||||
}
|
||||
pub fn get_block_size(&self) -> Result<u32> {
|
||||
let mut statvfs = StatVfs::default();
|
||||
syscall::fstatvfs(self.memory_file.as_raw_fd() as usize, &mut statvfs)?;
|
||||
Ok(statvfs.f_bsize)
|
||||
}
|
||||
pub fn block_size(&self) -> u32 {
|
||||
self.get_block_size().unwrap_or(Self::DEFAULT_BLOCK_SIZE)
|
||||
}
|
||||
pub fn resolve(&self, mut path: &[u8], uid: u32, gid: u32) -> Option<usize> {
|
||||
// TODO: CoW
|
||||
let mut path = path.to_owned();
|
||||
// TODO: Replace follow symlinks, and replace double slashes with single slashes.
|
||||
|
||||
// TODO: Check that every directory in the path grant the user the search permission.
|
||||
self.tree.get(&path).copied()
|
||||
}
|
||||
}
|
||||
+37
-1
@@ -1,3 +1,39 @@
|
||||
#![feature(try_reserve)]
|
||||
|
||||
use std::fs::File;
|
||||
use std::io::prelude::*;
|
||||
use std::env;
|
||||
|
||||
use syscall::{CloneFlags, Packet, SchemeMut};
|
||||
|
||||
mod filesystem;
|
||||
mod scheme;
|
||||
|
||||
use self::scheme::Scheme;
|
||||
|
||||
fn main() {
|
||||
println!("Hello, world!");
|
||||
let scheme_name = env::args().nth(1).expect("Usage:\n\tramfs SCHEME_NAME");
|
||||
let mut socket = File::create(format!(":{}", scheme_name)).expect("ramfs: failed to create socket");
|
||||
|
||||
if unsafe { syscall::clone(CloneFlags::empty()) }.expect("ramfs: failed to fork") != 0 {
|
||||
return;
|
||||
}
|
||||
|
||||
let mut scheme = Scheme::new(scheme_name).expect("ramfs: failed to initialize scheme");
|
||||
|
||||
loop {
|
||||
let mut packet = Packet::default();
|
||||
match socket.read(&mut packet) {
|
||||
Ok(0) => break,
|
||||
Ok(_) => (),
|
||||
Err(error) => panic!("ramfs: failed to read from socket: {:?}", error),
|
||||
}
|
||||
scheme.handle(&mut packet);
|
||||
|
||||
match socket.write(&packet) {
|
||||
Ok(0) => break,
|
||||
Ok(_) => (),
|
||||
Err(error) => panic!("ramfs: failed to write to socket: {:?}", error),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+319
@@ -0,0 +1,319 @@
|
||||
use std::collections::BTreeMap;
|
||||
use std::convert::{TryFrom, TryInto};
|
||||
use std::os::unix::io::AsRawFd;
|
||||
use std::{cmp, ops};
|
||||
|
||||
use syscall::{Error, EventFlags, Map, Result, SchemeMut, Stat, StatVfs, TimeSpec};
|
||||
use syscall::flag::{O_RDWR, O_RDONLY, O_WRONLY, O_DIRECTORY, O_STAT};
|
||||
use syscall::{MODE_DIR, MODE_PERM, MODE_TYPE, SEEK_CUR, SEEK_END, SEEK_SET};
|
||||
use syscall::error::{EACCES, EBADF, EBADFD, EFBIG, EINVAL, EISDIR, ENOENT, ENOMEM, ENOSYS, ENOTDIR, EOPNOTSUPP, EOVERFLOW};
|
||||
|
||||
use crate::filesystem::{FileData, Filesystem};
|
||||
|
||||
#[derive(Clone)]
|
||||
struct Handle {
|
||||
inode: usize,
|
||||
offset: usize,
|
||||
}
|
||||
|
||||
pub struct Scheme {
|
||||
scheme_name: String,
|
||||
handles: BTreeMap<usize, Handle>,
|
||||
next_fd: usize,
|
||||
filesystem: Filesystem,
|
||||
}
|
||||
impl Scheme {
|
||||
pub fn new(scheme_name: String) -> Result<Self> {
|
||||
Ok(Self {
|
||||
scheme_name,
|
||||
handles: BTreeMap::new(),
|
||||
filesystem: Filesystem::new()?,
|
||||
next_fd: 0,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl SchemeMut for Scheme {
|
||||
fn open(&mut self, path: &[u8], flags: usize, uid: u32, gid: u32) -> Result<usize> {
|
||||
let inode = self.filesystem.resolve(path, uid, gid).ok_or(Error::new(ENOENT))?;
|
||||
let file = self.filesystem.files.get(&inode).ok_or(Error::new(ENOENT))?;
|
||||
|
||||
if flags & O_STAT == 0 && flags & O_DIRECTORY != 0 && file.mode & MODE_TYPE != MODE_DIR {
|
||||
return Err(Error::new(ENOTDIR));
|
||||
}
|
||||
if flags & O_STAT == 0 && flags & O_DIRECTORY == 0 && file.mode & MODE_TYPE == MODE_DIR {
|
||||
return Err(Error::new(EISDIR));
|
||||
}
|
||||
|
||||
let perm = file.mode & MODE_PERM;
|
||||
|
||||
if uid == file.uid {
|
||||
check_permissions(flags, (perm & 0o700) >> 6)?;
|
||||
} else if gid == file.gid {
|
||||
check_permissions(flags, (perm & 0o70) >> 3)?;
|
||||
} else {
|
||||
check_permissions(flags, perm & 0o7)?;
|
||||
}
|
||||
|
||||
let handle = Handle {
|
||||
inode,
|
||||
offset: 0,
|
||||
};
|
||||
|
||||
let fd = self.next_fd;
|
||||
self.next_fd += 1;
|
||||
|
||||
self.handles.insert(fd, handle);
|
||||
|
||||
Ok(fd)
|
||||
}
|
||||
fn chmod(&mut self, path: &[u8], mode: u16, uid: u32, gid: u32) -> Result<usize> {
|
||||
// TODO
|
||||
Err(Error::new(ENOSYS))
|
||||
}
|
||||
fn rmdir(&mut self, path: &[u8], uid: u32, gid: u32) -> Result<usize> {
|
||||
// TODO
|
||||
Err(Error::new(ENOSYS))
|
||||
}
|
||||
fn unlink(&mut self, path: &[u8], uid: u32, gid: u32) -> Result<usize> {
|
||||
// TODO
|
||||
Err(Error::new(ENOSYS))
|
||||
}
|
||||
fn dup(&mut self, old_fd: usize, _buf: &[u8]) -> Result<usize> {
|
||||
let handle = self.handles.get_mut(&old_fd).ok_or(Error::new(EBADF))?.clone();
|
||||
|
||||
let fd = self.next_fd;
|
||||
self.next_fd += 1;
|
||||
|
||||
self.handles.insert(fd, handle);
|
||||
Ok(fd)
|
||||
}
|
||||
fn read(&mut self, fd: usize, buf: &mut [u8]) -> Result<usize> {
|
||||
let handle = self.handles.get_mut(&fd).ok_or(Error::new(EBADF))?;
|
||||
let file = self.filesystem.files.get_mut(&handle.inode).ok_or(Error::new(EBADFD))?;
|
||||
|
||||
match file.data {
|
||||
FileData::File(ref bytes) => {
|
||||
if file.mode & MODE_TYPE == MODE_DIR { return Err(Error::new(EBADFD)) }
|
||||
if handle.offset >= bytes.len() {
|
||||
return Ok(0);
|
||||
}
|
||||
let bytes_to_read = cmp::min(bytes.len(), buf.len() + handle.offset) - handle.offset;
|
||||
buf[..bytes_to_read].copy_from_slice(&bytes[handle.offset..handle.offset + bytes_to_read]);
|
||||
Ok(bytes_to_read)
|
||||
}
|
||||
FileData::Directory(ref entries) => {
|
||||
if file.mode & MODE_TYPE != MODE_DIR { return Err(Error::new(EBADFD)) }
|
||||
|
||||
let mut bytes_to_skip = handle.offset;
|
||||
let mut bytes_left_to_read = buf.len();
|
||||
let mut bytes_read = 0;
|
||||
|
||||
for entry_bytes in entries {
|
||||
// skip the whole entry if it fits
|
||||
if bytes_to_skip >= entry_bytes.len() {
|
||||
bytes_to_skip -= entry_bytes.len();
|
||||
continue;
|
||||
}
|
||||
|
||||
let bytes_to_read = cmp::min(entry_bytes.len() - bytes_to_skip, bytes_left_to_read);
|
||||
|
||||
let entry_bytes = &entry_bytes[bytes_to_skip..bytes_to_skip + bytes_to_read];
|
||||
bytes_to_skip -= bytes_to_skip;
|
||||
|
||||
buf[handle.offset..handle.offset + bytes_to_read].copy_from_slice(&entry_bytes[..bytes_to_read]);
|
||||
bytes_left_to_read -= bytes_to_read;
|
||||
bytes_read += bytes_to_read;
|
||||
}
|
||||
Ok(bytes_read)
|
||||
}
|
||||
}
|
||||
}
|
||||
fn write(&mut self, fd: usize, buf: &[u8]) -> Result<usize> {
|
||||
let handle = self.handles.get_mut(&fd).ok_or(Error::new(EBADF))?;
|
||||
let file = self.filesystem.files.get_mut(&handle.inode).ok_or(Error::new(EBADFD))?;
|
||||
|
||||
if let &mut FileData::File(ref mut bytes) = &mut file.data {
|
||||
if file.mode & MODE_TYPE == MODE_DIR { return Err(Error::new(EBADFD)) }
|
||||
|
||||
// if there's a seek hole, fill it with 0 and continue writing.
|
||||
if handle.offset > bytes.len() {
|
||||
let additional = handle.offset - bytes.len();
|
||||
bytes.try_reserve(additional).or(Err(Error::new(ENOMEM)))?;
|
||||
bytes.resize(handle.offset, 0u8);
|
||||
}
|
||||
bytes.extend(buf);
|
||||
Ok(buf.len())
|
||||
} else {
|
||||
Err(Error::new(EISDIR))
|
||||
}
|
||||
}
|
||||
fn seek(&mut self, fd: usize, pos: usize, whence: usize) -> Result<usize> {
|
||||
let handle = self.handles.get_mut(&fd).ok_or(Error::new(EBADF))?;
|
||||
let file = self.filesystem.files.get_mut(&handle.inode).ok_or(Error::new(EBADFD))?;
|
||||
|
||||
// cast to isize, possibly making the offset negative
|
||||
let pos = pos as isize;
|
||||
|
||||
handle.offset = match whence {
|
||||
SEEK_SET => cmp::max(0, pos) as usize,
|
||||
SEEK_CUR => cmp::max(0, pos + isize::try_from(handle.offset).or(Err(Error::new(EOVERFLOW)))?) as usize,
|
||||
SEEK_END => cmp::max(0, pos + isize::try_from(file.data.size()).or(Err(Error::new(EOVERFLOW)))?) as usize,
|
||||
_ => return Err(Error::new(EINVAL)),
|
||||
};
|
||||
Ok(handle.offset)
|
||||
}
|
||||
fn fchmod(&mut self, fd: usize, mode: u16) -> Result<usize> {
|
||||
let handle = self.handles.get_mut(&fd).ok_or(Error::new(EBADF))?;
|
||||
let file = self.filesystem.files.get_mut(&handle.inode).ok_or(Error::new(EBADFD))?;
|
||||
|
||||
// TODO: Check that no file becomes a directory etc.
|
||||
file.mode = mode;
|
||||
|
||||
Ok(0)
|
||||
}
|
||||
fn fchown(&mut self, fd: usize, uid: u32, gid: u32) -> Result<usize> {
|
||||
let handle = self.handles.get_mut(&fd).ok_or(Error::new(EBADF))?;
|
||||
let file = self.filesystem.files.get_mut(&handle.inode).ok_or(Error::new(EBADFD))?;
|
||||
|
||||
file.uid = uid;
|
||||
file.gid = gid;
|
||||
|
||||
Ok(0)
|
||||
}
|
||||
fn fcntl(&mut self, fd: usize, _cmd: usize, _arg: usize) -> Result<usize> {
|
||||
if ! self.handles.contains_key(&fd) {
|
||||
return Err(Error::new(EBADF));
|
||||
}
|
||||
Ok(0)
|
||||
}
|
||||
fn fevent(&mut self, fd: usize, _flags: EventFlags) -> Result<EventFlags> {
|
||||
if ! self.handles.contains_key(&fd) {
|
||||
return Err(Error::new(EBADF));
|
||||
}
|
||||
Err(Error::new(ENOSYS))
|
||||
}
|
||||
fn fmap(&mut self, fd: usize, _map: &Map) -> Result<usize> {
|
||||
if ! self.handles.contains_key(&fd) {
|
||||
return Err(Error::new(EBADF));
|
||||
}
|
||||
// TODO
|
||||
Err(Error::new(ENOSYS))
|
||||
}
|
||||
fn funmap(&mut self, _address: usize) -> Result<usize> {
|
||||
// TODO
|
||||
Err(Error::new(ENOSYS))
|
||||
}
|
||||
fn fpath(&mut self, fd: usize, buf: &mut [u8]) -> Result<usize> {
|
||||
if ! self.handles.contains_key(&fd) {
|
||||
return Err(Error::new(EBADF));
|
||||
}
|
||||
// TODO
|
||||
Err(Error::new(ENOSYS))
|
||||
}
|
||||
fn frename(&mut self, fd: usize, path: &[u8], uid: u32, gid: u32) -> Result<usize> {
|
||||
if ! self.handles.contains_key(&fd) {
|
||||
return Err(Error::new(EBADF));
|
||||
}
|
||||
// TODO
|
||||
Err(Error::new(ENOSYS))
|
||||
}
|
||||
fn fstat(&mut self, fd: usize, stat: &mut Stat) -> Result<usize> {
|
||||
let handle = self.handles.get_mut(&fd).ok_or(Error::new(EBADF))?;
|
||||
let block_size = self.filesystem.block_size();
|
||||
let file = self.filesystem.files.get_mut(&handle.inode).ok_or(Error::new(EBADFD))?;
|
||||
|
||||
*stat = Stat {
|
||||
st_mode: file.mode,
|
||||
st_uid: file.uid,
|
||||
st_gid: file.gid,
|
||||
st_ino: handle.inode.try_into().or(Err(Error::new(EOVERFLOW)))?,
|
||||
st_nlink: file.nlink.try_into().or(Err(Error::new(EOVERFLOW)))?,
|
||||
st_dev: 0,
|
||||
|
||||
st_size: file.data.size().try_into().or(Err(Error::new(EOVERFLOW)))?,
|
||||
st_blksize: block_size,
|
||||
st_blocks: div_round_up(stat.st_size, u64::from(stat.st_blksize)),
|
||||
|
||||
st_atime: file.atime.tv_sec.try_into().or(Err(Error::new(EOVERFLOW)))?,
|
||||
st_atime_nsec: file.atime.tv_nsec.try_into().or(Err(Error::new(EOVERFLOW)))?,
|
||||
|
||||
st_ctime: file.ctime.tv_sec.try_into().or(Err(Error::new(EOVERFLOW)))?,
|
||||
st_ctime_nsec: file.ctime.tv_nsec.try_into().or(Err(Error::new(EOVERFLOW)))?,
|
||||
|
||||
st_mtime: file.mtime.tv_sec.try_into().or(Err(Error::new(EOVERFLOW)))?,
|
||||
st_mtime_nsec: file.mtime.tv_nsec.try_into().or(Err(Error::new(EOVERFLOW)))?,
|
||||
};
|
||||
|
||||
Ok(0)
|
||||
}
|
||||
fn fstatvfs(&mut self, fd: usize, stat: &mut StatVfs) -> Result<usize> {
|
||||
if ! self.handles.contains_key(&fd) {
|
||||
return Err(Error::new(EBADF));
|
||||
}
|
||||
syscall::fstatvfs(self.filesystem.memory_file.as_raw_fd() as usize, stat)?;
|
||||
|
||||
Ok(0)
|
||||
}
|
||||
fn fsync(&mut self, fd: usize) -> Result<usize> {
|
||||
if ! self.handles.contains_key(&fd) {
|
||||
return Err(Error::new(EBADF));
|
||||
}
|
||||
Ok(0)
|
||||
}
|
||||
fn ftruncate(&mut self, fd: usize, size: usize) -> Result<usize> {
|
||||
let handle = self.handles.get_mut(&fd).ok_or(Error::new(EBADF))?;
|
||||
let file = self.filesystem.files.get_mut(&handle.inode).ok_or(Error::new(EBADFD))?;
|
||||
|
||||
if file.mode & MODE_TYPE == MODE_DIR {
|
||||
return Err(Error::new(EISDIR));
|
||||
}
|
||||
match &mut file.data {
|
||||
&mut FileData::File(ref mut bytes) => {
|
||||
if size > bytes.len() {
|
||||
let additional = size - bytes.len();
|
||||
bytes.try_reserve(additional).or(Err(Error::new(ENOMEM)))?;
|
||||
bytes.resize(size, 0u8)
|
||||
} else {
|
||||
bytes.resize(size, 0u8)
|
||||
}
|
||||
}
|
||||
&mut FileData::Directory(_) => return Err(Error::new(EBADFD)),
|
||||
}
|
||||
Ok(0)
|
||||
}
|
||||
fn futimens(&mut self, fd: usize, times: &[TimeSpec]) -> Result<usize> {
|
||||
let handle = self.handles.get_mut(&fd).ok_or(Error::new(EBADF))?;
|
||||
let file = self.filesystem.files.get_mut(&handle.inode).ok_or(Error::new(EBADFD))?;
|
||||
|
||||
let new_atime = *times.get(0).ok_or(Error::new(EINVAL))?;
|
||||
let new_mtime = *times.get(1).ok_or(Error::new(EINVAL))?;
|
||||
|
||||
file.atime = new_atime;
|
||||
file.mtime = new_mtime;
|
||||
|
||||
Ok(0)
|
||||
}
|
||||
fn close(&mut self, fd: usize) -> Result<usize> {
|
||||
if self.handles.remove(&fd).is_none() {
|
||||
return Err(Error::new(EBADF));
|
||||
}
|
||||
Ok(0)
|
||||
}
|
||||
}
|
||||
fn div_round_up<T>(numer: T, denom: T) -> T
|
||||
where
|
||||
T: Copy + ops::Add<T, Output = T> + ops::Sub<T, Output = T> + ops::Div<T, Output = T> + From<u8>,
|
||||
{
|
||||
(numer + (denom - T::from(1u8))) / denom
|
||||
}
|
||||
fn check_permissions(flags: usize, single_mode: u16) -> Result<()> {
|
||||
if flags & O_RDWR == O_RDONLY && single_mode & 0o4 == 0 {
|
||||
return Err(Error::new(EACCES));
|
||||
} else if flags & O_RDWR == O_WRONLY && single_mode & 0o2 == 0 {
|
||||
return Err(Error::new(EACCES));
|
||||
} else if flags & O_RDWR == O_RDWR && single_mode & 0o6 != 0o6 {
|
||||
return Err(Error::new(EACCES));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
Reference in New Issue
Block a user