Add 'ramfs/' from commit '306433d0a553bbea81c7ef92a233d215dff1cf62'

git-subtree-dir: ramfs
git-subtree-mainline: daa4622b38
git-subtree-split: 306433d0a5
This commit is contained in:
bjorn3
2025-02-18 21:32:04 +01:00
8 changed files with 965 additions and 0 deletions
+1
View File
@@ -0,0 +1 @@
/target
+104
View File
@@ -0,0 +1,104 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "autocfg"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26"
[[package]]
name = "bitflags"
version = "2.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de"
[[package]]
name = "equivalent"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"
[[package]]
name = "hashbrown"
version = "0.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e087f84d4f86bf4b218b927129862374b72199ae7d8657835f1e89000eea4fb"
[[package]]
name = "indexmap"
version = "2.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "707907fe3c25f5424cce2cb7e1cbcafee6bdbe735ca90ef77c29e84591e5b9da"
dependencies = [
"equivalent",
"hashbrown",
]
[[package]]
name = "libc"
version = "0.2.160"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f0b21006cd1874ae9e650973c565615676dc4a274c965bb0a73796dac838ce4f"
[[package]]
name = "libredox"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d"
dependencies = [
"bitflags",
"libc",
"redox_syscall",
]
[[package]]
name = "ramfs"
version = "0.1.0"
dependencies = [
"indexmap",
"libredox",
"redox-daemon",
"redox-scheme",
"redox_syscall",
"slab",
]
[[package]]
name = "redox-daemon"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7fbdeffdb03cf2961b211a2023e683d71f2c225ea93404da5dc34b0dc94f0341"
dependencies = [
"libc",
"libredox",
]
[[package]]
name = "redox-scheme"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "95624e20d2c1808f7ca0820720d30aad9f5d2fc404e1ef379431ad7a790c3f7e"
dependencies = [
"libredox",
"redox_syscall",
]
[[package]]
name = "redox_syscall"
version = "0.5.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b6dfecf2c74bce2466cabf93f6664d6998a69eb21e39f4207930065b27b771f"
dependencies = [
"bitflags",
]
[[package]]
name = "slab"
version = "0.4.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67"
dependencies = [
"autocfg",
]
+17
View File
@@ -0,0 +1,17 @@
[package]
name = "ramfs"
version = "0.1.0"
authors = ["4lDO2 <4lDO2@protonmail.com>"]
edition = "2018"
license = "MIT"
description = "In-memory filesystem for Redox, which is useful for early logging"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
redox-daemon = "0.1.2"
redox_syscall = "0.5.3"
redox-scheme = "0.2.2"
libredox = "0.1.3"
indexmap = "2.5.0"
slab = "0.4.9"
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2020-2021 4lDO2
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+18
View File
@@ -0,0 +1,18 @@
# ramfs
A mountable filesystem, which contents is stored in RAM. Useful for early logging, before `redoxfs` has been started. The `initfs:` scheme seems to lack read-write support, and thus this ramfs driver allows logs to be written in initfs drivers.
## How To Contribute
To learn how to contribute to this system component you need to read the following document:
- [CONTRIBUTING.md](https://gitlab.redox-os.org/redox-os/redox/-/blob/master/CONTRIBUTING.md)
## Development
To learn how to do development with this system component inside the Redox build system you need to read the [Build System](https://doc.redox-os.org/book/build-system-reference.html) and [Coding and Building](https://doc.redox-os.org/book/coding-and-building.html) pages.
### How To Build
To build this system component you need to download the Redox build system, you can learn how to do it on the [Building Redox](https://doc.redox-os.org/book/podman-build.html) page.
This is necessary because they only work with cross-compilation to a Redox virtual machine, but you can do some testing from Linux.
+193
View File
@@ -0,0 +1,193 @@
use std::collections::BTreeMap;
use std::convert::TryInto;
use std::{fs, iter, time};
use std::os::unix::io::AsRawFd;
use indexmap::IndexMap;
use syscall::error::{EACCES, EIO, ENFILE, ENOENT};
use syscall::{Error, Result, TimeSpec, MODE_DIR};
use super::scheme::current_perm;
#[derive(Debug)]
pub struct File {
pub mode: u16,
pub uid: u32,
pub gid: u32,
pub nlink: usize,
pub parent: Inode,
pub open_handles: usize,
pub atime: TimeSpec,
pub ctime: TimeSpec,
pub mtime: TimeSpec,
pub crtime: TimeSpec,
pub data: FileData,
}
#[derive(Clone, Debug)]
pub struct Inode(pub usize);
#[derive(Debug)]
pub enum FileData {
File(Vec<u8>),
Directory(IndexMap<String, Inode>),
}
impl FileData {
pub fn size(&self) -> usize {
match self {
&Self::File(ref data) => data.len(),
&Self::Directory(_) => 0,
}
}
}
pub struct Filesystem {
pub files: BTreeMap<usize, File>,
pub memory_file: fs::File,
pub last_inode_number: usize,
}
impl Filesystem {
pub const DEFAULT_BLOCK_SIZE: u32 = 4096;
pub const ROOT_INODE: usize = 1;
pub fn new() -> Result<Self> {
Ok(Self {
files: iter::once((Self::ROOT_INODE, Self::create_root_inode())).collect(),
memory_file: fs::File::open("/scheme/memory").or(Err(Error::new(EIO)))?,
last_inode_number: Self::ROOT_INODE,
})
}
fn create_root_inode() -> File {
let cur_time = current_time();
File {
atime: cur_time,
crtime: cur_time,
ctime: cur_time,
mtime: cur_time,
mode: MODE_DIR | 0o755,
nlink: 1,
open_handles: 0,
uid: 0,
gid: 0,
data: FileData::Directory(IndexMap::new()),
parent: Inode(Self::ROOT_INODE),
}
}
pub fn get_block_size(&self) -> Result<u32> {
Ok(libredox::call::fstatvfs(self.memory_file.as_raw_fd() as usize)?.f_bsize as u32)
}
pub fn block_size(&self) -> u32 {
self.get_block_size().unwrap_or(Self::DEFAULT_BLOCK_SIZE)
}
pub fn next_inode_number(&mut self) -> Result<usize> {
let next = self
.last_inode_number
.checked_add(1)
.ok_or(Error::new(ENFILE))?;
self.last_inode_number = next;
Ok(next)
}
fn resolve_generic(&self, mut parts: Vec<&str>, uid: u32, gid: u32) -> Result<usize> {
let mut current_file = self
.files
.get(&Self::ROOT_INODE)
.ok_or(Error::new(ENOENT))?;
let mut current_inode = Self::ROOT_INODE;
let mut i = 0;
loop {
let Some(&part) = parts.get(i) else {
break;
};
let dentries = match current_file.data {
FileData::Directory(ref dentries) => dentries,
FileData::File(_) => return Err(Error::new(ENOENT)),
};
let perm = current_perm(&current_file, uid, gid);
if perm & 0o1 == 0 {
return Err(Error::new(EACCES));
}
if part == "." || part == ".." {
parts.remove(i);
}
let part = *parts.get(i).unwrap();
if part == ".." && i > 0 {
i -= 1;
parts.remove(i);
}
let part = *parts.get(i).unwrap();
current_inode = dentries.get(part).ok_or(Error::new(ENOENT))?.0;
current_file = self.files.get(&current_inode).ok_or(Error::new(EIO))?;
i += 1;
}
Ok(current_inode)
}
pub fn resolve_except_last<'a>(
&self,
path_bytes: &'a str,
uid: u32,
gid: u32,
) -> Result<(usize, Option<&'a str>)> {
let mut parts = path_components_iter(path_bytes.trim_start_matches('/')).collect::<Vec<_>>();
let last = if parts.len() >= 1 {
Some(parts.pop().unwrap())
} else {
None
};
Ok((self.resolve_generic(parts, uid, gid)?, last))
}
pub fn resolve(&self, path: &str, uid: u32, gid: u32) -> Result<usize> {
let parts = path_components_iter(path.trim_start_matches('/')).collect::<Vec<_>>();
self.resolve_generic(parts, uid, gid)
}
}
pub fn path_components_iter(bytes: &str) -> impl Iterator<Item = &str> + '_ {
let components_iter = bytes.split(|c| c == '/');
components_iter.filter(|item| !item.is_empty())
}
pub fn current_time() -> TimeSpec {
let sys_time = time::SystemTime::now();
let duration = match sys_time.duration_since(time::SystemTime::UNIX_EPOCH) {
Ok(d) => d,
Err(e) => {
eprintln!("The time is apparently now before the Unix epoch...");
let negative_duration = e.duration();
return TimeSpec {
tv_sec: negative_duration
.as_secs()
.try_into()
.unwrap_or(i64::min_value()),
tv_nsec: negative_duration
.subsec_nanos()
.try_into()
.unwrap_or(i32::min_value()),
};
}
};
TimeSpec {
tv_sec: duration.as_secs().try_into().unwrap_or(i64::max_value()),
tv_nsec: duration
.subsec_nanos()
.try_into()
.unwrap_or(i32::max_value()),
}
}
+46
View File
@@ -0,0 +1,46 @@
use std::{env, process};
mod filesystem;
mod scheme;
use redox_scheme::{RequestKind, SignalBehavior, V2};
use self::scheme::Scheme;
fn main() {
let scheme_name = env::args().nth(1).expect("Usage:\n\tramfs SCHEME_NAME");
redox_daemon::Daemon::new(move |daemon| {
let socket = redox_scheme::Socket::<V2>::create(&scheme_name).expect("ramfs: failed to create socket");
let mut scheme = Scheme::new(scheme_name).expect("ramfs: failed to initialize scheme");
libredox::call::setrens(0, 0).expect("ramfs: failed to enter null namespace");
daemon
.ready()
.expect("ramfs: failed to mark daemon as ready");
loop {
let Some(request) = socket
.next_request(SignalBehavior::Restart)
.expect("ramfs: failed to get next scheme request")
else {
break;
};
match request.kind() {
RequestKind::Call(call) => {
let response = call.handle_scheme_mut(&mut scheme);
socket
.write_responses(&[response], SignalBehavior::Restart)
.expect("ramfs: failed to write next scheme response");
}
_ => (),
}
}
process::exit(0);
})
.expect("ramfs: failed to create daemon");
}
+565
View File
@@ -0,0 +1,565 @@
use std::convert::{TryFrom, TryInto};
use std::io::{Cursor, Seek};
use std::iter;
use std::os::unix::io::AsRawFd;
use indexmap::IndexMap;
use syscall::dirent::{DirEntry, DirentBuf, DirentKind};
use syscall::error::{
EACCES, EBADF, EBADFD, EEXIST, EINVAL, EIO, EISDIR, ENOMEM, ENOSYS, ENOTDIR, ENOTEMPTY,
EOVERFLOW,
};
use syscall::flag::{
O_ACCMODE, O_CREAT, O_DIRECTORY, O_EXCL, O_RDONLY, O_RDWR, O_STAT, O_TRUNC, O_WRONLY,
};
use syscall::schemev2::NewFdFlags;
use syscall::{Error, EventFlags, Result, Stat, StatVfs, TimeSpec, ENOENT};
use syscall::{MODE_DIR, MODE_FILE, MODE_PERM, MODE_TYPE};
use redox_scheme::{CallerCtx, OpenResult, SchemeMut};
use crate::filesystem::{self, File, FileData, Filesystem, Inode};
pub struct Scheme {
scheme_name: String,
filesystem: Filesystem,
}
impl Scheme {
/// Create the scheme, with the name being used for `fpath`.
pub fn new(scheme_name: String) -> Result<Self> {
Ok(Self {
scheme_name,
filesystem: Filesystem::new()?,
})
}
/// Remove a directory entry, where the entry can be both a file or a directory. Used by both
/// `unlink` and `rmdir`.
pub fn remove_dentry(
&mut self,
path: &str,
uid: u32,
gid: u32,
directory: bool,
) -> Result<usize> {
let removed_inode = {
let (parent_dir_inode, name_to_delete) =
self.filesystem.resolve_except_last(path, uid, gid)?;
let name_to_delete = name_to_delete.ok_or(Error::new(EINVAL))?; // can't remove root
let parent = self
.filesystem
.files
.get_mut(&parent_dir_inode)
.ok_or(Error::new(EIO))?;
let mode = current_perm(parent, uid, gid);
if mode & 0o2 == 0 {
return Err(Error::new(EACCES));
}
let FileData::Directory(ref mut dentries) = parent.data else {
return Err(Error::new(ENOTDIR));
};
let Inode(entry_inode) = dentries.shift_remove(name_to_delete).ok_or(Error::new(ENOENT))?;
if let Some(File {
data: FileData::Directory(ref data),
..
}) = self.filesystem.files.get(&entry_inode)
{
if !directory {
return Err(Error::new(EISDIR));
} else if !data.is_empty() {
return Err(Error::new(ENOTEMPTY));
}
let parent = self
.filesystem
.files
.get_mut(&parent_dir_inode)
.ok_or(Error::new(EIO))?;
parent.nlink -= 1; // '..' of subdirectory
}
entry_inode
};
let removed_inode_info = self
.filesystem
.files
.get_mut(&removed_inode)
.ok_or(Error::new(EIO))?;
if let FileData::File(_) = removed_inode_info.data {
if directory {
return Err(Error::new(EISDIR));
}
removed_inode_info.nlink -= 1; // only the parent entry
} else {
if !directory {
return Err(Error::new(ENOTDIR));
}
removed_inode_info.nlink -= 2; // both the parent entry and '.'
}
if removed_inode_info.nlink == 0 && removed_inode_info.open_handles == 0 {
self.filesystem.files.remove(&removed_inode);
}
Ok(0)
}
fn open_existing(&mut self, path: &str, flags: usize, uid: u32, gid: u32) -> Result<Inode> {
let inode = self.filesystem.resolve(path, uid, gid)?;
let file = self
.filesystem
.files
.get_mut(&inode)
.ok_or(Error::new(EIO))?;
if flags & O_STAT == 0 && flags & O_DIRECTORY != 0 && file.mode & MODE_TYPE != MODE_DIR {
return Err(Error::new(ENOTDIR));
}
// Unlike on Linux, which allows directories to be opened without O_DIRECTORY, Redox has no
// getdents(2) syscall, and thus it adds the additional restriction that directories have
// to be opened with O_DIRECTORY, if they aren't opened with O_STAT to check whether it's a
// directory.
if flags & O_STAT == 0 && flags & O_DIRECTORY == 0 && file.mode & MODE_TYPE == MODE_DIR {
return Err(Error::new(EISDIR));
}
let current_perm = current_perm(file, uid, gid);
check_permissions(flags, current_perm)?;
let opened_as_write = flags & O_ACCMODE == O_WRONLY || flags & O_ACCMODE == O_RDWR;
if flags & O_TRUNC == O_TRUNC && opened_as_write {
match file.data {
// file.data and file.mode should match
FileData::Directory(_) => return Err(Error::new(EBADFD)),
// If we opened an existing file with O_CREAT and O_TRUNC
FileData::File(ref mut data) => data.clear(),
}
}
file.open_handles += 1;
Ok(Inode(inode))
}
}
impl SchemeMut for Scheme {
fn xopen(&mut self, path: &str, flags: usize, ctx: &CallerCtx) -> Result<OpenResult> {
let exists = self.filesystem.resolve(path, 0, 0).is_ok();
if flags & O_CREAT != 0 && flags & O_EXCL != 0 && exists {
return Err(Error::new(EEXIST));
}
let inode = if flags & O_CREAT != 0 && exists {
self.open_existing(path, flags, ctx.uid, ctx.gid)?.0
} else if flags & O_CREAT != 0 {
if flags & O_STAT != 0 {
return Err(Error::new(EINVAL));
}
let (parent_dir_inode, new_name) =
self.filesystem
.resolve_except_last(path, ctx.uid, ctx.gid)?;
let new_name = new_name.ok_or(Error::new(EINVAL))?; // cannot mkdir /
let current_time = filesystem::current_time();
let new_inode_number = self.filesystem.next_inode_number()?;
let mut mode = (flags & 0xFFFF) as u16;
let new_inode = if flags & O_DIRECTORY != 0 {
if mode & MODE_TYPE == 0 {
mode |= MODE_DIR
}
if mode & MODE_TYPE != MODE_DIR {
return Err(Error::new(EINVAL));
}
File {
atime: current_time,
crtime: current_time,
ctime: current_time,
mtime: current_time,
gid: ctx.gid,
uid: ctx.uid,
mode,
nlink: 2, // parent entry, "."
data: FileData::Directory(IndexMap::new()),
open_handles: 1,
parent: Inode(parent_dir_inode),
}
} else {
if mode & MODE_TYPE == 0 {
mode |= MODE_FILE
}
if mode & MODE_TYPE == MODE_DIR {
return Err(Error::new(EINVAL));
}
File {
atime: current_time,
crtime: current_time,
ctime: current_time,
mtime: current_time,
gid: ctx.gid,
uid: ctx.uid,
mode,
nlink: 1,
data: FileData::File(Vec::new()),
open_handles: 1,
parent: Inode(parent_dir_inode),
}
};
let current_perm = current_perm(&new_inode, ctx.uid, ctx.gid);
check_permissions(flags, current_perm)?;
self.filesystem.files.insert(new_inode_number, new_inode);
let parent_file = self
.filesystem
.files
.get_mut(&parent_dir_inode)
.ok_or(Error::new(EIO))?;
match parent_file.data {
FileData::File(_) => return Err(Error::new(EIO)),
FileData::Directory(ref mut entries) => {
entries.insert(new_name.to_owned(), Inode(new_inode_number));
}
}
new_inode_number
} else {
self.open_existing(path, flags, ctx.uid, ctx.gid)?.0
};
Ok(OpenResult::ThisScheme { number: inode, flags: NewFdFlags::POSITIONED })
}
fn rmdir(&mut self, path: &str, uid: u32, gid: u32) -> Result<usize> {
self.remove_dentry(path, uid, gid, true)
}
fn unlink(&mut self, path: &str, uid: u32, gid: u32) -> Result<usize> {
self.remove_dentry(path, uid, gid, false)
}
fn dup(&mut self, old_inode: usize, _buf: &[u8]) -> Result<usize> {
Ok(old_inode)
}
fn read(&mut self, inode: usize, buf: &mut [u8], offset: u64, fcntl_flags: u32) -> Result<usize> {
let Ok(offset) = usize::try_from(offset) else {
return Ok(0);
};
let file = self
.filesystem
.files
.get_mut(&inode)
.ok_or(Error::new(EBADFD))?;
if !matches!((fcntl_flags as usize) & O_ACCMODE, O_RDONLY | O_RDWR) {
return Err(Error::new(EBADF));
}
match file.data {
FileData::File(ref bytes) => {
if file.mode & MODE_TYPE == MODE_DIR {
return Err(Error::new(EBADFD));
}
let src_bytes = bytes.get(offset..).unwrap_or(&[]);
let bytes_to_read = src_bytes.len().min(buf.len());
buf[..bytes_to_read].copy_from_slice(&src_bytes[..bytes_to_read]);
Ok(bytes_to_read)
}
FileData::Directory(_) => return Err(Error::new(EISDIR)),
}
}
fn getdents<'buf>(&mut self, inode: usize, mut buf: DirentBuf<&'buf mut [u8]>, opaque_offset: u64) -> Result<DirentBuf<&'buf mut [u8]>> {
let Ok(offset) = usize::try_from(opaque_offset) else {
return Ok(buf);
};
let file = self
.filesystem
.files
.get_mut(&inode)
.ok_or(Error::new(EBADFD))?;
let FileData::Directory(ref dir) = file.data else {
return Err(Error::new(ENOTDIR));
};
for (i, (dent_name, Inode(dent_inode))) in dir.iter().enumerate().skip(offset) {
buf.entry(DirEntry {
inode: *dent_inode as u64,
name: dent_name,
kind: DirentKind::Unspecified,
next_opaque_id: i as u64 + 1,
})?;
}
Ok(buf)
}
fn write(&mut self, inode: usize, buf: &[u8], offset: u64, _fcntl_flags: u32) -> Result<usize> {
let Ok(offset) = usize::try_from(offset) else {
return Ok(0);
};
let file = self
.filesystem
.files
.get_mut(&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.
let end_off = offset.checked_add(buf.len()).ok_or(Error::new(EOVERFLOW))?;
if end_off > bytes.len() {
let additional = end_off - bytes.len();
bytes.try_reserve(additional).or(Err(Error::new(ENOMEM)))?;
bytes.resize(end_off, 0u8);
}
bytes[offset..][..buf.len()].copy_from_slice(buf);
Ok(buf.len())
} else {
Err(Error::new(EISDIR))
}
}
fn fchmod(&mut self, inode: usize, mode: u16) -> Result<usize> {
let file = self
.filesystem
.files
.get_mut(&inode)
.ok_or(Error::new(EBADFD))?;
let cur_type = file.mode & MODE_TYPE;
/*
if mode & MODE_TYPE != 0 {
return Err(Error::new(EINVAL));
}
*/
file.mode = mode | cur_type;
Ok(0)
}
fn fchown(&mut self, inode: usize, uid: u32, gid: u32) -> Result<usize> {
let file = self
.filesystem
.files
.get_mut(&inode)
.ok_or(Error::new(EBADFD))?;
file.uid = uid;
file.gid = gid;
Ok(0)
}
fn fcntl(&mut self, _inode: usize, _cmd: usize, _arg: usize) -> Result<usize> {
Ok(0)
}
fn fevent(&mut self, _inode: usize, _flags: EventFlags) -> Result<EventFlags> {
// TODO?
Err(Error::new(ENOSYS))
}
fn mmap_prep(&mut self, _inode: usize, _offset: u64, _size: usize, _flags: syscall::MapFlags) -> Result<usize> {
// TODO
Err(Error::new(ENOSYS))
}
fn fpath(&mut self, mut current_inode: usize, buf: &mut [u8]) -> Result<usize> {
let mut chain = Vec::new();
let mut current_info = self
.filesystem
.files
.get(&current_inode)
.ok_or(Error::new(EBADFD))?;
while current_inode != Filesystem::ROOT_INODE {
let parent_info = self
.filesystem
.files
.get(&current_info.parent.0)
.ok_or(Error::new(EBADFD))?;
let FileData::Directory(ref dir) = parent_info.data else {
return Err(Error::new(EBADFD));
};
// TODO: error handling?
let (name, _) = dir.iter().find(|(_name, inode)| inode.0 == current_inode).ok_or(Error::new(ENOENT))?;
chain.push(&**name);
current_inode = current_info.parent.0;
current_info = parent_info;
}
let mut cursor = Cursor::new(buf);
for component in iter::once(self.scheme_name.trim_start_matches('/')).chain(chain.iter().copied().rev()) {
use std::io::Write;
write!(cursor, "/{component}").unwrap();
}
Ok(cursor.stream_position().unwrap() as usize)
}
fn frename(&mut self, _inode: usize, _path: &str, _uid: u32, _gid: u32) -> Result<usize> {
// TODO
Err(Error::new(ENOSYS))
}
fn fstat(&mut self, inode: usize, stat: &mut Stat) -> Result<usize> {
let block_size = self.filesystem.block_size();
let file = self
.filesystem
.files
.get_mut(&inode)
.ok_or(Error::new(EBADFD))?;
let size = file.data.size().try_into().or(Err(Error::new(EOVERFLOW)))?;
*stat = Stat {
st_mode: file.mode,
st_uid: file.uid,
st_gid: file.gid,
st_ino: inode.try_into().map_err(|_| Error::new(EOVERFLOW))?,
st_nlink: file.nlink.try_into().or(Err(Error::new(EOVERFLOW)))?,
st_dev: 0,
st_size: size,
st_blksize: block_size,
st_blocks: size.next_multiple_of(u64::from(block_size)),
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, _inode: usize, stat: &mut StatVfs) -> Result<usize> {
let abi_stat = libredox::call::fstatvfs(self.filesystem.memory_file.as_raw_fd() as usize)?;
// TODO: From impl
*stat = StatVfs {
f_bavail: abi_stat.f_bavail as u64,
f_bfree: abi_stat.f_bfree as u64,
f_blocks: abi_stat.f_blocks as u64,
f_bsize: abi_stat.f_bsize as u32,
};
Ok(0)
}
fn fsync(&mut self, _inode: usize) -> Result<usize> {
Ok(0)
}
fn ftruncate(&mut self, inode: usize, size: usize) -> Result<usize> {
let file = self
.filesystem
.files
.get_mut(&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, inode: usize, times: &[TimeSpec]) -> Result<usize> {
let file = self
.filesystem
.files
.get_mut(&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, inode: usize) -> Result<usize> {
let inode_info = self
.filesystem
.files
.get_mut(&inode)
.ok_or(Error::new(EIO))?;
inode_info.open_handles -= 1;
if inode_info.nlink == 0 && inode_info.open_handles == 0 {
self.filesystem.files.remove(&inode);
}
Ok(0)
}
}
pub fn current_perm(file: &crate::filesystem::File, uid: u32, gid: u32) -> u8 {
let perm = file.mode & MODE_PERM;
if uid == 0 {
// root doesn't have to be checked
0o7
} else if uid == file.uid {
((perm & 0o700) >> 6) as u8
} else if gid == file.gid {
((perm & 0o70) >> 3) as u8
} else {
(perm & 0o7) as u8
}
}
fn check_permissions(flags: usize, single_mode: u8) -> Result<()> {
if flags & O_ACCMODE == O_RDONLY && single_mode & 0o4 == 0 {
return Err(Error::new(EACCES));
} else if flags & O_ACCMODE == O_WRONLY && single_mode & 0o2 == 0 {
return Err(Error::new(EACCES));
} else if flags & O_ACCMODE == O_RDWR && single_mode & 0o6 != 0o6 {
return Err(Error::new(EACCES));
}
Ok(())
}