feat: Add std_fs_call support

This commit is contained in:
Ibuki Omatsu
2026-02-28 15:05:08 +00:00
committed by Jeremy Soller
parent e75d311776
commit 04dbcbcd91
4 changed files with 182 additions and 88 deletions
Generated
+13 -6
View File
@@ -417,12 +417,13 @@ checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981"
[[package]]
name = "libredox"
version = "0.1.12"
version = "0.1.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3d0b95e02c851351f877147b7deea7b1afb1df71b63aa5f8270716e0c5720616"
checksum = "062b52cd41eb8d929e81b592a47df833c33c15684933a9329440137a6d9f134c"
dependencies = [
"bitflags",
"libc",
"plain",
"redox_syscall",
]
@@ -484,6 +485,12 @@ version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "487f2ccd1e17ce8c1bfab3a65c89525af41cfad4c8659021a1e9a2aacd73b89b"
[[package]]
name = "plain"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6"
[[package]]
name = "portable-atomic"
version = "1.13.1"
@@ -574,9 +581,9 @@ checksum = "436d45c2b6a5b159d43da708e62b25be3a4a3d5550d654b72216ade4c4bfd717"
[[package]]
name = "redox-scheme"
version = "0.10.0"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "36a837ee249c650536f3c41697cf277c8ca64a78888ce221cec574226cb9d658"
checksum = "e58cb7dee1058575f320f9bfa32c7c0eda857e285c2a163a0eb595d37add09cd"
dependencies = [
"libredox",
"redox_syscall",
@@ -584,9 +591,9 @@ dependencies = [
[[package]]
name = "redox_syscall"
version = "0.7.2"
version = "0.7.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6d94dd2f7cd932d4dc02cc8b2b50dfd38bd079a4e5d79198b99743d7fcf9a4b4"
checksum = "6ce70a74e890531977d37e532c34d45e9055d2409ed08ddba14529471ed0be16"
dependencies = [
"bitflags",
]
+3 -3
View File
@@ -56,7 +56,7 @@ log = { version = "0.4.14", default-features = false, optional = true }
lz4_flex = { version = "0.11", default-features = false, features = ["checked-decode"] }
parse-size = { version = "1", optional = true }
range-tree = { version = "0.1", optional = true }
redox_syscall = "0.7.2"
redox_syscall = "0.7.3"
seahash = { version = "4.1.0", default-features = false }
termion = { version = "4", optional = true }
uuid = { version = "1.4", default-features = false }
@@ -85,9 +85,9 @@ std = [
fuser = { version = "0.16", optional = true }
[target.'cfg(target_os = "redox")'.dependencies]
libredox = { version = "0.1.12", features = ["call"], optional = true }
libredox = { version = "0.1.13", optional = true }
redox-path = "0.3.0"
redox-scheme = { version = "0.10.0", optional = true }
redox-scheme = { version = "0.11.0", optional = true }
[lints.rust]
unexpected_cfgs = { level = "warn", check-cfg = ['cfg(fuzzing)'] }
+1 -1
View File
@@ -29,7 +29,7 @@ where
let mounted_path = format!("/scheme/{}", mountpoint.display());
let mut state = SchemeState::new();
let mut scheme = FileScheme::new(scheme_name, mounted_path.clone(), filesystem, &socket);
let mut scheme = FileScheme::new(scheme_name, mounted_path.clone(), filesystem, &socket)?;
redox_scheme::scheme::register_sync_scheme(
&socket,
+165 -78
View File
@@ -1,18 +1,20 @@
use std::collections::BTreeMap;
use std::mem;
use std::str;
use std::sync::atomic::{AtomicUsize, Ordering};
use std::time::{SystemTime, UNIX_EPOCH};
use libredox::Fd;
use redox_scheme::{scheme::SchemeSync, CallerCtx, OpenResult, SendFdRequest, Socket};
use syscall::data::{Stat, StatVfs, TimeSpec};
use syscall::data::{Stat, StatVfs, StdFsCallMeta, TimeSpec};
use syscall::dirent::DirentBuf;
use syscall::error::{
Error, Result, EACCES, EBADF, EBUSY, EEXIST, EINVAL, EISDIR, ELOOP, ENOENT, ENOTDIR, ENOTEMPTY,
EOPNOTSUPP, EPERM, EXDEV,
};
use syscall::flag::{
EventFlags, MapFlags, O_ACCMODE, O_CREAT, O_DIRECTORY, O_EXCL, O_NOFOLLOW, O_RDONLY, O_RDWR,
O_STAT, O_SYMLINK, O_TRUNC, O_WRONLY,
EventFlags, MapFlags, StdFsCallKind, O_ACCMODE, O_CREAT, O_DIRECTORY, O_EXCL, O_NOFOLLOW,
O_RDONLY, O_RDWR, O_STAT, O_SYMLINK, O_TRUNC, O_WRONLY,
};
use syscall::schemev2::NewFdFlags;
use syscall::FobtainFdFlags;
@@ -43,7 +45,9 @@ pub struct FileScheme<'sock, D: Disk> {
fmap: super::resource::Fmaps,
// Map of file id to other scheme's file descriptor.
other_scheme_fd_map: BTreeMap<u32, usize>,
other_scheme_fd_map: BTreeMap<u32, Fd>,
proc_creds_capability: Fd,
}
impl<'sock, D: Disk> FileScheme<'sock, D> {
@@ -52,8 +56,8 @@ impl<'sock, D: Disk> FileScheme<'sock, D> {
mounted_path: String,
fs: FileSystem<D>,
socket: &'sock Socket,
) -> FileScheme<'sock, D> {
FileScheme {
) -> Result<FileScheme<'sock, D>> {
Ok(FileScheme {
scheme_name,
mounted_path,
fs,
@@ -62,7 +66,14 @@ impl<'sock, D: Disk> FileScheme<'sock, D> {
handles: BTreeMap::new(),
fmap: BTreeMap::new(),
other_scheme_fd_map: BTreeMap::new(),
}
proc_creds_capability: {
libredox::Fd::open(
"/scheme/proc/proc-creds-capability",
libredox::flag::O_RDONLY,
0,
)?
},
})
}
fn resolve_symlink(
@@ -139,7 +150,7 @@ impl<'sock, D: Disk> FileScheme<'sock, D> {
.other_scheme_fd_map
.get(&inode_id)
.ok_or(Error::new(EBADF))?;
let len = libredox::call::get_socket_token(*target_fd, payload)?;
let len = libredox::call::get_socket_token(target_fd.raw(), payload)?;
return Ok(len);
}
@@ -303,7 +314,7 @@ impl<'sock, D: Disk> FileScheme<'sock, D> {
let mut node = tx.create_node(
parent.ptr(),
&last_part,
mode_type | (flags as u16 & Node::MODE_PERM),
mode_type as u16 | (flags as u16 & Node::MODE_PERM),
ctime.as_secs(),
ctime.subsec_nanos(),
)?;
@@ -367,6 +378,77 @@ impl<'sock, D: Disk> FileScheme<'sock, D> {
})
}
fn unlink_internal(
&mut self,
start_ptr: TreePtr<Node>,
path: &str,
flags: usize,
uid: u32,
gid: u32,
) -> Result<()> {
let scheme_name = &self.scheme_name;
let unlink_result = self.fs.tx(|tx| {
let mut nodes = Vec::new();
let Some((child, child_name)) =
Self::path_nodes(scheme_name, tx, start_ptr, path, uid, gid, &mut nodes)?
else {
return Err(Error::new(ENOENT));
};
let Some((parent, _parent_name)) = nodes.last() else {
return Err(Error::new(EPERM));
};
if !parent.data().permission(uid, gid, Node::MODE_WRITE) {
// println!("dir not writable {:o}", parent.1.mode);
return Err(Error::new(EACCES));
}
// Check AT_REMOVEDIR
if flags & syscall::AT_REMOVEDIR == syscall::AT_REMOVEDIR {
// --- rmdir ---
if child.data().is_dir() {
if !child.data().permission(uid, gid, Node::MODE_WRITE) {
return Err(Error::new(EACCES));
}
tx.remove_node(parent.ptr(), &child_name, Node::MODE_DIR)
} else {
Err(Error::new(ENOTDIR))
}
} else {
// --- unlink ---
if !child.data().is_dir() {
if child.data().uid() != uid && uid != 0 {
// println!("file not owned by current user {}", parent.1.uid);
return Err(Error::new(EACCES));
}
let mode = if child.data().is_symlink() {
Node::MODE_SYMLINK
} else if child.data().is_sock() {
Node::MODE_SOCK
} else {
Node::MODE_FILE
};
tx.remove_node(parent.ptr(), &child_name, mode)
} else {
Err(Error::new(EISDIR))
}
}
});
let Some(node_id) = unlink_result? else {
return Ok(());
};
let _ = self.other_scheme_fd_map.remove(&node_id);
Ok(())
}
fn path_nodes(
scheme_name: &str,
tx: &mut Transaction<D>,
@@ -434,7 +516,7 @@ fn dirname(path: &str) -> Option<String> {
// TODO: Reimplement these using the scheme common path related crate
pub fn resolve_path<D: Disk>(dir: &Box<dyn Resource<D>>, path: String) -> Option<String> {
let max_upward_depth = 64; // Same value as MAX_LEVEL of sym loops in relibc
let (canon, depth) =
let (canon, _depth) =
canonicalize_using_cwd_with_max_upward_depth(dir.path(), &path, max_upward_depth)?;
Some(canon)
@@ -525,69 +607,7 @@ impl<'sock, D: Disk> SchemeSync for FileScheme<'sock, D> {
// println!("Unlinkat '{}' flags: {:X}", path, flags);
let scheme_name = &self.scheme_name;
let unlink_result = self.fs.tx(|tx| {
let mut nodes = Vec::new();
let Some((child, child_name)) =
Self::path_nodes(scheme_name, tx, start_ptr, path, uid, gid, &mut nodes)?
else {
return Err(Error::new(ENOENT));
};
let Some((parent, _parent_name)) = nodes.last() else {
return Err(Error::new(EPERM));
};
if !parent.data().permission(uid, gid, Node::MODE_WRITE) {
// println!("dir not writable {:o}", parent.1.mode);
return Err(Error::new(EACCES));
}
// Check AT_REMOVEDIR
if flags & syscall::AT_REMOVEDIR == syscall::AT_REMOVEDIR {
// --- rmdir ---
if child.data().is_dir() {
if !child.data().permission(uid, gid, Node::MODE_WRITE) {
return Err(Error::new(EACCES));
}
tx.remove_node(parent.ptr(), &child_name, Node::MODE_DIR)
} else {
Err(Error::new(ENOTDIR))
}
} else {
// --- unlink ---
if !child.data().is_dir() {
if child.data().uid() != uid && uid != 0 {
// println!("file not owned by current user {}", parent.1.uid);
return Err(Error::new(EACCES));
}
let mode = if child.data().is_symlink() {
Node::MODE_SYMLINK
} else if child.data().is_sock() {
Node::MODE_SOCK
} else {
Node::MODE_FILE
};
tx.remove_node(parent.ptr(), &child_name, mode)
} else {
Err(Error::new(EISDIR))
}
}
});
let Some(node_id) = unlink_result? else {
return Ok(());
};
if let Some(fd) = self.other_scheme_fd_map.remove(&node_id) {
let _ = syscall::close(fd);
}
Ok(())
self.unlink_internal(start_ptr, path, flags, uid, gid)
}
/* Resource operations */
@@ -1046,6 +1066,7 @@ impl<'sock, D: Disk> SchemeSync for FileScheme<'sock, D> {
) {
return Err(e);
}
let other_scheme_fd = Fd::new(new_fd);
let parent_resource_ptr = parent_resource.node_ptr();
@@ -1061,7 +1082,7 @@ impl<'sock, D: Disk> SchemeSync for FileScheme<'sock, D> {
// TODO: Move the PATH_MAX definition to a more appropriate place.
const PATH_MAX: usize = 4096;
let mut url_buf = [0u8; PATH_MAX];
let url_len = syscall::fpath(new_fd, &mut url_buf)?;
let url_len = other_scheme_fd.fpath(&mut url_buf)?;
let url_str = str::from_utf8(&url_buf[..url_len]).map_err(|_| Error::new(EINVAL))?;
let redox_path = RedoxPath::from_absolute(url_str).ok_or(Error::new(EINVAL))?;
let (_, path) = redox_path.as_parts().ok_or(Error::new(EINVAL))?;
@@ -1074,9 +1095,8 @@ impl<'sock, D: Disk> SchemeSync for FileScheme<'sock, D> {
}
let (resource, node_id): (Box<dyn Resource<D>>, u32) = if !last_part.is_empty() {
let mut stat = Stat::default();
syscall::fstat(new_fd, &mut stat)?;
let mode_type = stat.st_mode & Node::MODE_TYPE;
let stat = other_scheme_fd.stat()?;
let mode_type = stat.st_mode as u16 & Node::MODE_TYPE;
let flags = 0o777;
let node_ptr = self.fs.tx(|tx| {
@@ -1134,7 +1154,7 @@ impl<'sock, D: Disk> SchemeSync for FileScheme<'sock, D> {
let id = self.next_id.fetch_add(1, Ordering::Relaxed);
self.handles.insert(id, Handle::Resource(resource));
self.other_scheme_fd_map.insert(node_id, new_fd);
self.other_scheme_fd_map.insert(node_id, other_scheme_fd);
Ok(new_fd)
}
@@ -1154,6 +1174,44 @@ impl<'sock, D: Disk> SchemeSync for FileScheme<'sock, D> {
}
}
fn std_fs_call(
&mut self,
id: usize,
kind: StdFsCallKind,
payload: &mut [u8],
metadata: StdFsCallMeta,
ctx: &CallerCtx,
) -> Result<usize> {
match kind {
StdFsCallKind::Fchown => {
let (new_uid, new_gid) = (metadata.arg1 as u32, metadata.arg2 >> 32 as u32);
let (_pid, uid, gid) = get_uid_gid_from_pid(&self.proc_creds_capability, ctx.pid)?;
if uid != 0 && (uid != ctx.uid || gid != ctx.gid) {
return Err(Error::new(EPERM));
}
self.fchown(id, new_uid, new_gid as u32, ctx).map(|_| 0)
}
/* TODO: Support Unlinkat using std_fs_call
Unlinkat => {
let path = unsafe { str::from_utf8_unchecked(payload) };
let flags = metadata.arg1;
let dir_node_ptr = match self.handles.get(&id).ok_or(Error::new(EBADF))? {
// If pathname is absolute, then dirfd is ignored.
Handle::Resource(dir_resource) if !path.starts_with('/') => {
// only allow dirresource as base for openat
dir_resource.node_ptr()
}
_ => TreePtr::root(),
};
let (_pid, uid, gid) = get_uid_gid_from_pid(&self.proc_creds_capability, ctx.pid)?;
self.unlink_internal(dir_node_ptr, path, *flags as usize, uid, gid)
.map(|_| 0)
}
*/
_ => Err(Error::new(EOPNOTSUPP)),
}
}
fn inode(&self, id: usize) -> Result<usize> {
let Some(Handle::Resource(resource)) = self.handles.get(&id) else {
return Err(Error::new(EBADF));
@@ -1161,3 +1219,32 @@ impl<'sock, D: Disk> SchemeSync for FileScheme<'sock, D> {
Ok(resource.node_ptr().id() as usize)
}
}
fn get_uid_gid_from_pid(cap_fd: &Fd, target_pid: usize) -> Result<(u32, u32, u32)> {
let mut buffer = [0u8; mem::size_of::<libredox::protocol::ProcMeta>()];
let _ = libredox::call::get_proc_credentials(cap_fd.raw(), target_pid, &mut buffer).map_err(
|e| {
eprintln!(
"Failed to get process credentials for pid {}: {:?}",
target_pid, e
);
Error::new(EINVAL)
},
)?;
let mut cursor = 0;
let pid = read_u32(&buffer, cursor)?;
cursor += mem::size_of::<u32>() * 3;
let uid = read_u32(&buffer, cursor)?;
cursor += mem::size_of::<u32>() * 3;
let gid = read_u32(&buffer, cursor)?;
Ok((pid, uid, gid))
}
fn read_u32(buffer: &[u8], offset: usize) -> Result<u32> {
let bytes = buffer
.get(offset..offset + 4)
.and_then(|slice| slice.try_into().ok())
.ok_or_else(|| Error::new(EINVAL))?;
Ok(u32::from_le_bytes(bytes))
}